///|
fn pdf_truetype_required_subset_table(tag : String) -> Bool {
  tag == "head" ||
  tag == "hhea" ||
  tag == "loca" ||
  tag == "cmap" ||
  tag == "maxp" ||
  tag == "cvt " ||
  tag == "glyf" ||
  tag == "prep" ||
  tag == "hmtx" ||
  tag == "fpgm"
}

///|
/// Write the format-6 cmap table used by cpdf for implicit-font-file subsets.
///
/// Character codes start at `33`; each Unicode value in `subset_codepoints`
/// contributes its original glyph index, or `0` if the original cmap has no
/// mapping. The returned bytes are padded, while `length` is the unpadded table
/// length for directory metadata.
pub fn pdf_truetype_subset_cmap_table(
  data : BytesView,
  subset_codepoints : ArrayView[Int],
) -> @truetype.PdfTrueTypeSubsetTable raise @core.PdfError {
  let cmap_entries = pdf_truetype_cmap_glyphs(data)
  let glyph_indices : Array[Int] = []
  for codepoint in subset_codepoints {
    glyph_indices.push(
      match pdf_truetype_cmap_glyph_from_entries(cmap_entries, codepoint) {
        Some(glyph_index) => glyph_index
        None => 0
      },
    )
  }
  let length = 22 + glyph_indices.length() * 2
  let output = Array::make(length + pdf_truetype_table_padding(length), b'\x00')
  let mut position = pdf_truetype_write_u16_at(output, 0, 0)
  position = pdf_truetype_write_u16_at(output, position, 1)
  position = pdf_truetype_write_u16_at(output, position, 1)
  position = pdf_truetype_write_u16_at(output, position, 0)
  position = pdf_truetype_write_u32_at(output, position, 12)
  position = pdf_truetype_write_u16_at(output, position, 6)
  position = pdf_truetype_write_u16_at(
    output,
    position,
    10 + glyph_indices.length() * 2,
  )
  position = pdf_truetype_write_u16_at(output, position, 0)
  position = pdf_truetype_write_u16_at(output, position, 33)
  position = pdf_truetype_write_u16_at(output, position, glyph_indices.length())
  for glyph_index in glyph_indices {
    position = pdf_truetype_write_u16_at(
      output,
      position,
      pdf_truetype_normalize_u16(glyph_index),
    )
  }
  { length, bytes: Bytes::from_array(output), }
}

///|
fn pdf_truetype_sorted_tables(
  tables : ArrayView[@truetype.PdfTrueTypeTableRecord],
) -> Array[@truetype.PdfTrueTypeTableRecord] {
  let sorted : Array[@truetype.PdfTrueTypeTableRecord] = []
  for table in tables {
    sorted.push(table)
  }
  sorted.sort_by(fn(left, right) { left.offset.compare(right.offset) })
  sorted
}

///|
fn pdf_truetype_subset_font_kept_tables(
  sorted_tables : ArrayView[@truetype.PdfTrueTypeTableRecord],
) -> Array[@truetype.PdfTrueTypeTableRecord] {
  let kept : Array[@truetype.PdfTrueTypeTableRecord] = []
  let mut removed_data_before = 0
  for index in 0.. Int {
  length + pdf_truetype_table_padding(length)
}

///|
fn pdf_truetype_subset_original_table_span(
  data : BytesView,
  sorted_tables : ArrayView[@truetype.PdfTrueTypeTableRecord],
  tag : String,
) -> (Int, Int) raise @core.PdfError {
  for index in 0.. data.length() {
        raise SoftError("Truncated TrueType " + tag + " table")
      }
      break (table.offset, length)
    }
  } nobreak {
    raise SoftError("No " + tag + " table found in TrueType font")
  }
}

///|
fn pdf_truetype_subset_original_table(
  data : BytesView,
  sorted_tables : ArrayView[@truetype.PdfTrueTypeTableRecord],
  tag : String,
  directory_length : Int,
) -> @truetype.PdfTrueTypeSubsetTable raise @core.PdfError {
  let (offset, length) = pdf_truetype_subset_original_table_span(
    data, sorted_tables, tag,
  )
  { length: directory_length, bytes: data[offset:offset + length].to_owned(), }
}

///|
fn pdf_truetype_subset_font_table_payload(
  data : BytesView,
  sorted_tables : ArrayView[@truetype.PdfTrueTypeTableRecord],
  record : @truetype.PdfTrueTypeTableRecord,
  subset_codepoints : ArrayView[Int],
  rewrite_cmap : Bool,
) -> @truetype.PdfTrueTypeSubsetTable raise @core.PdfError {
  if record.tag == "loca" {
    {
      length: record.length,
      bytes: pdf_truetype_subset_loca_table(data, subset_codepoints),
    }
  } else if record.tag == "glyf" {
    pdf_truetype_subset_glyf_table(data, subset_codepoints)
  } else if record.tag == "cmap" && rewrite_cmap {
    pdf_truetype_subset_cmap_table(data, subset_codepoints)
  } else {
    pdf_truetype_subset_original_table(
      data,
      sorted_tables,
      record.tag,
      record.length,
    )
  }
}

///|
fn pdf_truetype_subset_font_directory(
  data : BytesView,
  sorted_tables : ArrayView[@truetype.PdfTrueTypeTableRecord],
  subset_codepoints : ArrayView[Int],
  rewrite_cmap : Bool,
) -> (
  Array[@truetype.PdfTrueTypeTableRecord],
  Array[@truetype.PdfTrueTypeSubsetTable],
) raise @core.PdfError {
  let kept = pdf_truetype_subset_font_kept_tables(sorted_tables)
  let header_size_reduction = 16 * (sorted_tables.length() - kept.length())
  let records : Array[@truetype.PdfTrueTypeTableRecord] = []
  let payloads : Array[@truetype.PdfTrueTypeSubsetTable] = []
  let mut glyf_table_size_reduction = 0
  let mut cmap_table_size_reduction = 0
  for kept_record in kept {
    let payload = pdf_truetype_subset_font_table_payload(
      data, sorted_tables, kept_record, subset_codepoints, rewrite_cmap,
    )
    let mut table_length = kept_record.length
    if kept_record.tag == "glyf" {
      table_length = payload.length
      glyf_table_size_reduction = pdf_truetype_table_padded_length(
          kept_record.length,
        ) -
        payload.bytes.length()
    } else if kept_record.tag == "cmap" && rewrite_cmap {
      table_length = payload.length
      cmap_table_size_reduction = pdf_truetype_table_padded_length(
          kept_record.length,
        ) -
        payload.bytes.length()
    }
    let prior_table_reduction = if kept_record.tag == "glyf" {
      cmap_table_size_reduction
    } else if kept_record.tag == "cmap" {
      glyf_table_size_reduction
    } else {
      glyf_table_size_reduction + cmap_table_size_reduction
    }
    records.push({
      tag: kept_record.tag,
      checksum: kept_record.checksum,
      offset: kept_record.offset - header_size_reduction - prior_table_reduction,
      length: table_length,
    })
    payloads.push(payload)
  }
  (records, payloads)
}

///|
/// Build a cpdf-style subset TrueType font file.
///
/// This ports the `subset_font` table-directory assembly around the already
/// ported `loca`, `glyf`, and format-6 `cmap` writers. Non-required TrueType
/// tables are removed, source checksums are preserved, table offsets are
/// adjusted by removed header/data spans and rewritten table sizes, and table
/// payloads are written in source table-order. Set `rewrite_cmap=true` for the
/// source `ImplicitInFontFile` higher-subset path; the default preserves the
/// original cmap table like the main subset path.
pub fn pdf_truetype_subset_font(
  data : BytesView,
  subset_codepoints : ArrayView[Int],
  rewrite_cmap? : Bool = false,
) -> @core.PdfBytes raise @core.PdfError {
  let major = pdf_truetype_required_u16(data, 0, "font header")
  let minor = pdf_truetype_required_u16(data, 2, "font header")
  let sorted_tables = pdf_truetype_sorted_tables(pdf_truetype_tables(data))
  let (records, payloads) = pdf_truetype_subset_font_directory(
    data, sorted_tables, subset_codepoints, rewrite_cmap,
  )
  let table_count = records.length()
  let search_power = pdf_truetype_power_two_leq(table_count)
  let mut output_length = 12 + table_count * 16
  for payload in payloads {
    output_length += payload.bytes.length()
  }
  let output = Array::make(output_length, b'\x00')
  let mut position = pdf_truetype_write_u16_at(output, 0, major)
  position = pdf_truetype_write_u16_at(output, position, minor)
  position = pdf_truetype_write_u16_at(output, position, table_count)
  position = pdf_truetype_write_u16_at(output, position, 16 * search_power)
  position = pdf_truetype_write_u16_at(
    output,
    position,
    pdf_truetype_source_entry_selector(search_power),
  )
  position = pdf_truetype_write_u16_at(output, position, table_count * 16)
  for record in records {
    position = pdf_truetype_write_tag_at(output, position, record.tag)
    position = pdf_truetype_write_u32_uint_at(output, position, record.checksum)
    position = pdf_truetype_write_u32_at(output, position, record.offset)
    position = pdf_truetype_write_u32_at(output, position, record.length)
  }
  for payload in payloads {
    position = pdf_truetype_write_view_at(output, position, payload.bytes)
  }
  Bytes::from_array(output)
}