///|
/// Return decoded `loca` offsets from a TrueType font.
///
/// This ports `read_loca_table`: short offsets are doubled, long offsets are
/// read directly, and unknown `indexToLocFormat` values raise the source error.
pub fn pdf_truetype_loca_offsets(
  data : BytesView,
) -> Array[Int] raise @core.PdfError {
  let head = pdf_truetype_required_table(data, "head")
  let maxp = pdf_truetype_required_table(data, "maxp")
  let loca = pdf_truetype_required_table(data, "loca")
  let index_to_loc_format = pdf_truetype_required_i16(
    data,
    head.offset + 50,
    "head table",
  )
  let num_glyphs = pdf_truetype_required_u16(
    data,
    maxp.offset + 4,
    "maxp table",
  )
  let offsets : Array[Int] = []
  match index_to_loc_format {
    0 =>
      for index in 0..<(num_glyphs + 1) {
        offsets.push(
          pdf_truetype_required_u16(data, loca.offset + index * 2, "loca table") *
          2,
        )
      }
    1 =>
      for index in 0..<(num_glyphs + 1) {
        offsets.push(
          pdf_truetype_required_u32_as_int(
            data,
            loca.offset + index * 4,
            "loca table",
          ),
        )
      }
    _ => raise SoftError("Unknown indexToLocFormat in read_loca_table")
  }
  offsets
}

///|
fn pdf_truetype_index_to_loc_format(
  data : BytesView,
) -> Int raise @core.PdfError {
  let head = pdf_truetype_required_table(data, "head")
  pdf_truetype_required_i16(data, head.offset + 50, "head table")
}

///|
fn pdf_truetype_validate_glyph_index(
  loca_offsets : ArrayView[Int],
  glyph_index : Int,
) -> Unit raise @core.PdfError {
  if glyph_index < 0 || glyph_index + 1 >= loca_offsets.length() {
    raise SoftError("Invalid TrueType glyph index")
  }
}

///|
fn pdf_truetype_validate_loca_range(
  loca_offsets : ArrayView[Int],
  glyph_index : Int,
) -> Unit raise @core.PdfError {
  pdf_truetype_validate_glyph_index(loca_offsets, glyph_index)
  if loca_offsets[glyph_index + 1] < loca_offsets[glyph_index] {
    raise SoftError("Invalid TrueType loca offsets")
  }
}

///|
fn pdf_truetype_composite_component_glyphs(
  data : BytesView,
  component_offset : Int,
) -> Array[Int] raise @core.PdfError {
  let components : Array[Int] = []
  let mut offset = component_offset
  let mut more_components = true
  while more_components {
    let flags = pdf_truetype_required_u16(data, offset, "glyf table")
    let glyph_index = pdf_truetype_required_u16(data, offset + 2, "glyf table")
    components.push(glyph_index)
    offset += 4
    offset += if (flags & 0x0001) > 0 { 4 } else { 2 }
    if (flags & 0x0008) > 0 {
      offset += 2
    } else if (flags & 0x0040) > 0 {
      offset += 4
    } else if (flags & 0x0080) > 0 {
      offset += 8
    }
    more_components = (flags & 0x0020) > 0
  }
  components
}

///|
fn pdf_truetype_expand_composite_glyphs_one(
  data : BytesView,
  glyf : @truetype.PdfTrueTypeTableRecord,
  loca_offsets : ArrayView[Int],
  glyph_indices : ArrayView[Int],
) -> Array[Int] raise @core.PdfError {
  let expanded : Array[Int] = []
  for glyph_index in glyph_indices {
    pdf_truetype_validate_glyph_index(loca_offsets, glyph_index)
    expanded.push(glyph_index)
    let glyph_offset = glyf.offset + loca_offsets[glyph_index]
    let number_of_contours = pdf_truetype_required_i16(
      data, glyph_offset, "glyf table",
    )
    if number_of_contours < 0 {
      for
        component in pdf_truetype_composite_component_glyphs(
          data,
          glyph_offset + 10,
        ) {
        expanded.push(component)
      }
    }
  }
  pdf_truetype_sorted_unique(expanded)
}

///|
/// Expand glyph indices to include nested TrueType composite glyph components.
///
/// This ports the `expand_composites` helper used by `Cpdftruetype` before
/// writing subset `loca` and `glyf` tables. Returned indices are sorted and
/// deduplicated, matching the source `setify`/`sort` behavior.
pub fn pdf_truetype_expand_composite_glyphs(
  data : BytesView,
  glyph_indices : ArrayView[Int],
) -> Array[Int] raise @core.PdfError {
  let glyf = pdf_truetype_required_table(data, "glyf")
  let loca_offsets = pdf_truetype_loca_offsets(data)
  let mut current = pdf_truetype_sorted_unique(glyph_indices)
  let mut done = false
  while !done {
    let expanded = pdf_truetype_expand_composite_glyphs_one(
      data, glyf, loca_offsets, current,
    )
    if expanded == current {
      done = true
    } else {
      current = expanded
    }
  }
  current
}

///|
/// Return `glyf` byte ranges for glyphs after composite expansion.
///
/// Ranges are relative to the start of the `glyf` table, matching the source
/// byterange calculation immediately before subset `glyf` table writing.
pub fn pdf_truetype_glyph_byte_ranges(
  data : BytesView,
  glyph_indices : ArrayView[Int],
) -> Array[@truetype.PdfTrueTypeGlyphByteRange] raise @core.PdfError {
  let loca_offsets = pdf_truetype_loca_offsets(data)
  let expanded = pdf_truetype_expand_composite_glyphs(data, glyph_indices)
  let ranges : Array[@truetype.PdfTrueTypeGlyphByteRange] = []
  for glyph_index in expanded {
    pdf_truetype_validate_loca_range(loca_offsets, glyph_index)
    ranges.push({
      glyph_index,
      start_offset: loca_offsets[glyph_index],
      end_offset: loca_offsets[glyph_index + 1],
    })
  }
  ranges
}

///|
fn pdf_truetype_base_subset_glyph_indices(
  data : BytesView,
  subset_codepoints : ArrayView[Int],
) -> Array[Int] {
  let cmap_entries = pdf_truetype_cmap_glyphs(data)
  let glyph_indices : Array[Int] = [0]
  for codepoint in subset_codepoints {
    match pdf_truetype_cmap_glyph_from_entries(cmap_entries, codepoint) {
      Some(glyph_index) => glyph_indices.push(glyph_index)
      None => ()
    }
  }
  pdf_truetype_sorted_unique(glyph_indices)
}

///|
/// Return the glyph indices used by a TrueType Unicode subset.
///
/// This mirrors the glyph collection inside the source `write_loca_table` and
/// `write_glyf_table` helpers: `.notdef` glyph `0` is always included, missing
/// Unicode mappings are ignored, and composite glyph components are expanded
/// recursively before the sorted/deduplicated glyph-index set is returned.
pub fn pdf_truetype_subset_glyph_indices(
  data : BytesView,
  subset_codepoints : ArrayView[Int],
) -> Array[Int] raise @core.PdfError {
  pdf_truetype_expand_composite_glyphs(
    data,
    pdf_truetype_base_subset_glyph_indices(data, subset_codepoints),
  )
}

///|
fn pdf_truetype_subset_loca_write_entry(
  output : Array[Byte],
  output_position : Int,
  index_to_loc_format : Int,
  position : Int,
) -> Int raise @core.PdfError {
  match index_to_loc_format {
    0 => pdf_truetype_write_u16_at(output, output_position, position / 2)
    1 => pdf_truetype_write_u32_at(output, output_position, position)
    _ => raise SoftError("Unknown indexToLocFormat in write_loca_table")
  }
}

///|
fn pdf_truetype_subset_loca_entry_width(
  index_to_loc_format : Int,
) -> Int raise @core.PdfError {
  match index_to_loc_format {
    0 => 2
    1 => 4
    _ => raise SoftError("Unknown indexToLocFormat in write_loca_table")
  }
}

///|
/// Write a padded subset `loca` table for the given Unicode subset.
///
/// The original glyph indices are preserved: entries for skipped glyph IDs are
/// written as zero-length ranges pointing at the next selected glyph offset,
/// matching `Cpdftruetype.write_loca_table`.
pub fn pdf_truetype_subset_loca_table(
  data : BytesView,
  subset_codepoints : ArrayView[Int],
) -> @core.PdfBytes raise @core.PdfError {
  let index_to_loc_format = pdf_truetype_index_to_loc_format(data)
  let loca_offsets = pdf_truetype_loca_offsets(data)
  let glyph_indices = pdf_truetype_subset_glyph_indices(data, subset_codepoints)
  let pairs : Array[(Int, Int)] = []
  let mut position = 0
  for glyph_index in glyph_indices {
    pdf_truetype_validate_loca_range(loca_offsets, glyph_index)
    pairs.push((glyph_index, position))
    position += loca_offsets[glyph_index + 1] - loca_offsets[glyph_index]
  }
  pairs.push((loca_offsets.length() - 1, position))
  let unpadded_length = loca_offsets.length() *
    pdf_truetype_subset_loca_entry_width(index_to_loc_format)
  let output = Array::make(
    unpadded_length + pdf_truetype_table_padding(unpadded_length),
    b'\x00',
  )
  let mut output_position = 0
  for index in 0.. @truetype.PdfTrueTypeSubsetTable raise @core.PdfError {
  let glyf = pdf_truetype_required_table(data, "glyf")
  let ranges = pdf_truetype_glyph_byte_ranges(
    data,
    pdf_truetype_base_subset_glyph_indices(data, subset_codepoints),
  )
  let mut length = 0
  for range in ranges {
    if glyf.offset + range.end_offset > data.length() {
      raise SoftError("Truncated TrueType glyf table")
    }
    length += range.end_offset - range.start_offset
  }
  let output = Array::make(length + pdf_truetype_table_padding(length), b'\x00')
  let mut position = 0
  for range in ranges {
    for offset in range.start_offset..