function readColumn(reader, { groupStart, selectStart, selectEnd }, columnDecoder, onPage) {
  const { columnName } = columnDecoder
  /** @type {DecodedArray[]} */
  const chunks = []
  /** @type {DecodedArray | undefined} */
  let dictionary = undefined
  /** @type {DecodedArray | undefined} */
  let lastChunk = undefined
  let rowCount = 0

  const emitLastChunk = onPage && (() => {
    lastChunk && onPage({
      columnName,
      columnData: lastChunk,
      rowStart: groupStart + rowCount - lastChunk.length,
      rowEnd: groupStart + rowCount,
    })
  })

  while (rowCount < selectEnd) {
    if (reader.offset >= reader.view.byteLength - 1) break // end of reader

    // read page header
    const header = parquetHeader(reader)
    if (header.type === 'DICTIONARY_PAGE') {
      // assert(!dictionary)
      dictionary = readPage(reader, header, columnDecoder, dictionary, undefined, 0)
      dictionary = convert(dictionary, columnDecoder)
    } else {
      const lastChunkLength = lastChunk?.length || 0
      const values = readPage(reader, header, columnDecoder, dictionary, lastChunk, selectStart - rowCount)
      if (lastChunk === values) {
        // continued from previous page
        rowCount += values.length - lastChunkLength
      } else {
        emitLastChunk?.()
        chunks.push(values)
        rowCount += values.length
        lastChunk = values
      }
    }
  }
  emitLastChunk?.()
  // assert(rowCount >= selectEnd)
  if (rowCount > selectEnd && lastChunk) {
    // truncate last chunk to row limit
    chunks[chunks.length - 1] = lastChunk.slice(0, selectEnd - (rowCount - lastChunk.length))
  }
  return chunks
}
Parse column data from a buffer.

@param {DataReader} reader
@param {RowGroupSelect} rowGroupSelect row group selection
@param {ColumnDecoder} columnDecoder column decoder params
@param {(chunk: ColumnData) => void} [onPage] callback for each page
@returns {DecodedArray[]}