///|
fn pdf_truetype_subset_contains(
  subset_codepoints : ArrayView[Int],
  codepoint : Int,
) -> Bool {
  for subset_codepoint in subset_codepoints {
    if subset_codepoint == codepoint {
      break true
    }
  } nobreak {
    false
  }
}

///|
fn pdf_truetype_first_glyph_codepoint(name : @core.PdfName) -> Int {
  match pdf_text_codepoints_of_glyphname(name) {
    Some(codepoints) if codepoints.length() > 0 => codepoints[0]
    _ => 0
  }
}

///|
fn pdf_truetype_codepoint_of_pdf_code(
  encoding_table : ArrayView[(Int, @core.PdfName)],
  pdf_code : Int,
) -> Int {
  for entry in encoding_table {
    if entry.0 == pdf_code {
      break pdf_truetype_first_glyph_codepoint(entry.1)
    }
  } nobreak {
    0
  }
}

///|
fn pdf_truetype_encoding_code_defined(
  encoding_table : ArrayView[(Int, @core.PdfName)],
  codepoint : Int,
) -> Bool {
  for entry in encoding_table {
    if entry.0 == codepoint {
      break true
    }
  } nobreak {
    false
  }
}

///|
fn pdf_truetype_first_hmtx_width(widths : ArrayView[Int]) -> Int {
  if widths.length() == 0 {
    0
  } else {
    widths[0]
  }
}

///|
fn pdf_truetype_pdf_width_for_glyph(
  units_per_em : Int,
  widths : ArrayView[Int],
  glyph_index : Int,
) -> Int {
  if widths.length() == 0 {
    0
  } else {
    pdf_truetype_pdf_unit(
      units_per_em,
      if glyph_index >= 0 && glyph_index < widths.length() {
        widths[glyph_index]
      } else {
        widths[widths.length() - 1]
      },
    )
  }
}

///|
fn pdf_truetype_split_subsets(
  subset_codepoints : ArrayView[Int],
  chunk_size : Int,
) -> Array[Array[Int]] {
  let chunks : Array[Array[Int]] = []
  if chunk_size > 0 {
    let mut chunk : Array[Int] = []
    for codepoint in subset_codepoints {
      chunk.push(codepoint)
      if chunk.length() == chunk_size {
        chunks.push(chunk)
        chunk = []
      }
    }
    if chunk.length() > 0 {
      chunks.push(chunk)
    }
  }
  chunks
}

///|
/// Split a Unicode subset into the main simple-font subset and higher
/// implicit-font-file subsets.
///
/// This mirrors `Cpdftruetype.find_main`: codepoints whose numeric value is a
/// defined one-byte code in the selected encoding stay in the main subset, and
/// all remaining codepoints are split into chunks of 224 for higher subsets.
pub fn pdf_truetype_partition_subsets(
  encoding : PdfEncoding,
  subset_codepoints : ArrayView[Int],
) -> @truetype.PdfTrueTypeSubsetPartition {
  let encoding_table = encoding.table()
  let main_subset : Array[Int] = []
  let rest : Array[Int] = []
  for codepoint in subset_codepoints {
    if pdf_truetype_encoding_code_defined(encoding_table, codepoint) {
      main_subset.push(codepoint)
    } else {
      rest.push(codepoint)
    }
  }
  { main_subset, higher_subsets: pdf_truetype_split_subsets(rest, 224), }
}

///|
/// Calculate the `/Widths` array for the main TrueType subset.
///
/// Width lookup follows `Cpdftruetype.calculate_widths`: PDF character codes
/// are first mapped through the selected simple encoding to Unicode, characters
/// outside the subset get width `0`, glyph indexes outside `/hmtx` use the last
/// advance width, and missing cmap mappings fall back to the first raw hmtx
/// width as in the source.
pub fn pdf_truetype_widths(
  data : BytesView,
  encoding : PdfEncoding,
  firstchar : Int,
  lastchar : Int,
  subset_codepoints : ArrayView[Int],
) -> Array[Int] raise @core.PdfError {
  if lastchar < firstchar {
    raise SoftError("lastchar < firstchar")
  }
  let metrics = pdf_truetype_metrics(data)
  let cmap_entries = pdf_truetype_cmap_glyphs(data)
  let encoding_table = encoding.table()
  let widths : Array[Int] = []
  for position in 0..<(lastchar - firstchar + 1) {
    let codepoint = pdf_truetype_codepoint_of_pdf_code(
      encoding_table,
      firstchar + position,
    )
    if !pdf_truetype_subset_contains(subset_codepoints, codepoint) {
      widths.push(0)
    } else {
      match pdf_truetype_cmap_glyph_from_entries(cmap_entries, codepoint) {
        Some(glyph_index) =>
          widths.push(
            pdf_truetype_pdf_width_for_glyph(
              metrics.units_per_em,
              metrics.advance_widths,
              glyph_index,
            ),
          )
        None =>
          widths.push(pdf_truetype_first_hmtx_width(metrics.advance_widths))
      }
    }
  }
  widths
}

///|
/// Calculate the `/Widths` array for an implicit higher TrueType subset.
///
/// Character codes are positional starting at `firstchar`, so each position is
/// looked up directly in `subset_codepoints`, matching
/// `Cpdftruetype.calculate_width_higher`.
pub fn pdf_truetype_widths_higher(
  data : BytesView,
  firstchar : Int,
  lastchar : Int,
  subset_codepoints : ArrayView[Int],
) -> Array[Int] raise @core.PdfError {
  if lastchar < firstchar {
    raise SoftError("lastchar < firstchar")
  }
  let metrics = pdf_truetype_metrics(data)
  let cmap_entries = pdf_truetype_cmap_glyphs(data)
  let widths : Array[Int] = []
  for position in 0..<(lastchar - firstchar + 1) {
    if position >= subset_codepoints.length() {
      raise SoftError("TrueType higher subset width index out of range")
    }
    match
      pdf_truetype_cmap_glyph_from_entries(
        cmap_entries,
        subset_codepoints[position],
      ) {
      Some(glyph_index) =>
        widths.push(
          pdf_truetype_pdf_width_for_glyph(
            metrics.units_per_em,
            metrics.advance_widths,
            glyph_index,
          ),
        )
      None => widths.push(pdf_truetype_first_hmtx_width(metrics.advance_widths))
    }
  }
  widths
}

///|
fn pdf_truetype_utf16be_without_bom(
  codepoint : Int,
) -> @core.PdfBytes raise @core.PdfError {
  let bytes = pdf_utf16be_of_codepoints([codepoint])
  bytes[2:].to_owned()
}

///|
/// Build the ToUnicode entries used by cpdf's implicit higher TrueType subsets.
///
/// Source character codes start at `33` and each maps positionally to the
/// matching Unicode scalar from `subset_codepoints`. The stored bytes are
/// UTF-16BE without the PDF Unicode string BOM, matching
/// `Cpdftruetype.seconds_tounicodes`.
pub fn pdf_truetype_higher_tounicode(
  subset_codepoints : ArrayView[Int],
) -> Array[(Int, @core.PdfBytes)]? raise @core.PdfError {
  if subset_codepoints.length() == 0 {
    None
  } else {
    let entries : Array[(Int, @core.PdfBytes)] = []
    for index in 0..