///|
/// Error returned by the checked TTF parser entry point.
pub(all) enum FontParseError {
  UnexpectedEndOfData(Int, Int, Int)
  InvalidFontHeader(UInt)
  UnsupportedFontIndex(Int)
  MissingTable(String)
  TableOutOfBounds(String, Int, Int, Int)
  TableTooShort(String, Int, Int)
  UnsupportedIndexToLocFormat(Int)
  MissingHorizontalMetrics
  UnsortedLocaOffsets
  UnsortedGlyphContourEndpoints
  GlyphFlagRepeatOverflow
  UnsupportedCmapVersion(Int)
  InvalidCmapSegmentCount(Int)
  InvalidCmapSegmentRange(Int, Int)
  InvalidCmapGroupRange(Int, Int)
} derive(Eq, Debug)

///|
pub extend FontParseError with Debug::{to_repr}

///|
pub extend FontParseError with Eq::{not_equal, equal}

///|
/// Parse a TTF font file and report structural errors as Result values.
fn parse_font_result(data : Bytes) -> Result[Font, FontParseError] {
  parse_font_checked(data)
}

///|
fn parse_font_checked(data : Bytes) -> Result[Font, FontParseError] {
  let tables = match parse_table_directory(data) {
    Err(error) => return Err(error)
    Ok(tables) => tables
  }
  let head = match parse_head_checked(data, tables) {
    Err(error) => return Err(error)
    Ok(head) => head
  }
  let hhea = match parse_hhea_checked(data, tables) {
    Err(error) => return Err(error)
    Ok(hhea) => hhea
  }
  let hmtx = match parse_hmtx_checked(data, tables, hhea.num_h_metrics) {
    Err(error) => return Err(error)
    Ok(hmtx) => hmtx
  }
  let cmap = match parse_cmap_checked(data, tables) {
    Err(error) => return Err(error)
    Ok(cmap) => cmap
  }
  let num_glyphs = match parse_maxp_checked(data, tables) {
    Err(error) => return Err(error)
    Ok(num_glyphs) => num_glyphs
  }
  let loca = match
    parse_loca_checked(data, tables, head.index_to_loc_format, num_glyphs) {
    Err(error) => return Err(error)
    Ok(loca) => loca
  }
  let (glyphs, glyph_count) = match
    parse_glyf_checked(data, tables, loca, head.index_to_loc_format, num_glyphs) {
    Err(error) => return Err(error)
    Ok(result) => result
  }
  let kern = match parse_kern_checked(data, tables) {
    Err(error) => return Err(error)
    Ok(kern) => kern
  }
  Ok({ head, hhea, hmtx, cmap, glyphs, glyph_count, kern, })
}

///|
fn parse_table_directory(
  data : Bytes,
) -> Result[Map[String, TableRecord], FontParseError] {
  guard data.length() >= 12 else {
    return Err(UnexpectedEndOfData(0, 12, data.length()))
  }
  let sf_version = read_u32_be_at(data, 0)
  guard sf_version == 0x00010000 else {
    return Err(InvalidFontHeader(sf_version))
  }

  let num_tables = read_u16_be_at(data, 4).to_int()
  let directory_len = 12 + num_tables * 16
  guard data.length() >= directory_len else {
    return Err(UnexpectedEndOfData(12, num_tables * 16, data.length()))
  }

  let tables : Map[String, TableRecord] = Map([])
  for i in 0.. Result[FontHead, FontParseError] {
  let head = match require_table(data, tables, "head") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  match require_min_length(head, 54) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let head_offset = head.offset.reinterpret_as_int()
  let index_to_loc_format = read_i16_be_at(data, head_offset + 50)
  guard index_to_loc_format is 0 || index_to_loc_format is 1 else {
    return Err(UnsupportedIndexToLocFormat(index_to_loc_format))
  }
  let units_per_em = read_u16_be_at(data, head_offset + 18).to_int()
  Ok({ units_per_em, index_to_loc_format, })
}

///|
fn parse_hhea_checked(
  data : Bytes,
  tables : Map[String, TableRecord],
) -> Result[FontHhea, FontParseError] {
  let hhea = match require_table(data, tables, "hhea") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  match require_min_length(hhea, 36) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }

  let hhea_offset = hhea.offset.reinterpret_as_int()
  let num_h_metrics = read_u16_be_at(data, hhea_offset + 34).to_int()
  guard num_h_metrics > 0 else { return Err(MissingHorizontalMetrics) }
  let ascent = read_i16_be_at(data, hhea_offset + 4)
  let descent = read_i16_be_at(data, hhea_offset + 6)
  let line_gap = read_i16_be_at(data, hhea_offset + 8)
  Ok({ ascent, descent, line_gap, num_h_metrics, })
}

///|
fn parse_hmtx_checked(
  data : Bytes,
  tables : Map[String, TableRecord],
  num_h_metrics : Int,
) -> Result[Array[LongHorMetric], FontParseError] {
  let hmtx = match require_table(data, tables, "hmtx") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  match require_min_length(hmtx, num_h_metrics * 4) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let hmtx_offset = hmtx.offset.reinterpret_as_int()
  let metrics = []
  for i in 0.. Result[Map[Int, Int], FontParseError] {
  parse_cmap(data, tables)
}

///|
fn parse_maxp_checked(
  data : Bytes,
  tables : Map[String, TableRecord],
) -> Result[Int, FontParseError] {
  let maxp = match require_table(data, tables, "maxp") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  match require_min_length(maxp, 6) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let maxp_offset = maxp.offset.reinterpret_as_int()
  Ok(read_u16_be_at(data, maxp_offset + 4).to_int())
}

///|
fn parse_loca_checked(
  data : Bytes,
  tables : Map[String, TableRecord],
  index_to_loc_format : Int,
  num_glyphs : Int,
) -> Result[Array[Int], FontParseError] {
  let loca = match require_table(data, tables, "loca") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  let required_loca_len = if index_to_loc_format is 0 {
    (num_glyphs + 1) * 2
  } else {
    (num_glyphs + 1) * 4
  }
  match require_min_length(loca, required_loca_len) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let loca_offset = loca.offset.reinterpret_as_int()
  let offsets = []
  for i in 0..<=num_glyphs {
    if index_to_loc_format is 0 {
      offsets.push(read_u16_be_at(data, loca_offset + i * 2).to_int() * 2)
    } else {
      offsets.push(
        read_u32_be_at(data, loca_offset + i * 4).reinterpret_as_int(),
      )
    }
  }
  Ok(offsets)
}

///|
fn parse_glyf_checked(
  data : Bytes,
  tables : Map[String, TableRecord],
  loca : Array[Int],
  index_to_loc_format : Int,
  num_glyphs : Int,
) -> Result[(Array[GlyphData], Int), FontParseError] {
  let loca_table = match require_table(data, tables, "loca") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  let glyf = match require_table(data, tables, "glyf") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  match
    validate_loca_offsets(
      data, loca_table, glyf, index_to_loc_format, num_glyphs,
    ) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  match validate_glyf_table(data, glyf, loca) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  parse_glyf_data_checked(data, glyf, loca)
}

///|
fn parse_kern_checked(
  data : Bytes,
  tables : Map[String, TableRecord],
) -> Result[Array[KernPair], FontParseError] {
  parse_kern(data, tables)
}

///|
fn require_table(
  data : Bytes,
  tables : Map[String, TableRecord],
  tag : String,
) -> Result[TableRecord, FontParseError] {
  let table = match tables.get(tag) {
    Some(table) => table
    None => return Err(MissingTable(tag))
  }
  match require_table_bounds(data, table) {
    Err(error) => Err(error)
    Ok(_) => Ok(table)
  }
}

///|
fn require_table_bounds(
  data : Bytes,
  table : TableRecord,
) -> Result[Unit, FontParseError] {
  let offset = table.offset.reinterpret_as_int()
  let length = table.length.reinterpret_as_int()
  guard offset >= 0 &&
    length >= 0 &&
    offset <= data.length() &&
    length <= data.length() - offset else {
    return Err(TableOutOfBounds(table.tag, offset, length, data.length()))
  }
  Ok(())
}

///|
fn require_min_length(
  table : TableRecord,
  minimum : Int,
) -> Result[Unit, FontParseError] {
  let length = table.length.reinterpret_as_int()
  guard length >= minimum else {
    return Err(TableTooShort(table.tag, length, minimum))
  }
  Ok(())
}

///|
fn validate_loca_offsets(
  data : Bytes,
  loca : TableRecord,
  glyf : TableRecord,
  index_to_loc_format : Int,
  num_glyphs : Int,
) -> Result[Unit, FontParseError] {
  let loca_offset = loca.offset.reinterpret_as_int()
  let glyf_len = glyf.length.reinterpret_as_int()
  let loca_entry_size = if index_to_loc_format is 0 { 2 } else { 4 }
  let mut previous = 0
  for i in 0..<=num_glyphs {
    let entry_offset = loca_offset + i * loca_entry_size
    let glyph_offset = if index_to_loc_format is 0 {
      read_u16_be_at(data, entry_offset).to_int() * 2
    } else {
      read_u32_be_at(data, entry_offset).reinterpret_as_int()
    }
    guard glyph_offset >= previous else { return Err(UnsortedLocaOffsets) }
    guard glyph_offset <= glyf_len else {
      return Err(
        TableOutOfBounds(
          "glyf",
          glyf.offset.reinterpret_as_int() + glyph_offset,
          0,
          data.length(),
        ),
      )
    }
    previous = glyph_offset
  }
  Ok(())
}

///|
fn validate_glyf_table(
  data : Bytes,
  glyf : TableRecord,
  loca : Array[Int],
) -> Result[Unit, FontParseError] {
  let glyf_offset = glyf.offset.reinterpret_as_int()
  for i in 0..<(loca.length() - 1) {
    let offset = loca[i]
    let next_offset = loca[i + 1]
    if offset < next_offset {
      let glyph_length = next_offset - offset
      match validate_glyph_data(data, glyf_offset + offset, glyph_length) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
    }
  }
  Ok(())
}

///|
fn parse_glyf_data_checked(
  data : Bytes,
  glyf : TableRecord,
  loca : Array[Int],
) -> Result[(Array[GlyphData], Int), FontParseError] {
  let glyf_offset = glyf.offset.reinterpret_as_int()
  let glyph_count = loca.length() - 1
  let glyphs = []

  // CJK fonts can have many glyphs; keep the historical parse limit.
  let max_glyphs = if glyph_count > 65536 { 65536 } else { glyph_count }
  for i in 0..= next_offset {
      glyphs.push(GlyphData::Empty)
    } else {
      let glyph_data = match
        parse_glyph_checked(data, glyf_offset + offset, next_offset - offset) {
        Err(error) => return Err(error)
        Ok(glyph) => glyph
      }
      glyphs.push(glyph_data)
    }
  }
  for _ in max_glyphs.. Result[GlyphData, FontParseError] {
  match require_glyph_bytes(length, 0, 10) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let num_contours = read_i16_be_at(data, offset)
  let xmin = read_i16_be_at(data, offset + 2)
  let ymin = read_i16_be_at(data, offset + 4)
  let xmax = read_i16_be_at(data, offset + 6)
  let ymax = read_i16_be_at(data, offset + 8)

  guard num_contours != 0 else { return Ok(Empty) }
  if num_contours > 0 {
    parse_simple_glyph_checked(
      data, offset, length, num_contours, xmin, ymin, xmax, ymax,
    )
  } else {
    parse_composite_glyph_checked(data, offset, length)
  }
}

///|
fn parse_simple_glyph_checked(
  data : Bytes,
  offset : Int,
  length : Int,
  num_contours : Int,
  xmin : Int,
  ymin : Int,
  xmax : Int,
  ymax : Int,
) -> Result[GlyphData, FontParseError] {
  let mut pos = 10
  let end_points_len = num_contours * 2
  match require_glyph_bytes(length, pos, end_points_len) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let end_pts_of_contour = []
  let mut previous_end_point = -1
  for i in 0.. previous_end_point else {
      return Err(UnsortedGlyphContourEndpoints)
    }
    end_pts_of_contour.push(end_point)
    previous_end_point = end_point
  }
  pos = pos + end_points_len
  match require_glyph_bytes(length, pos, 2) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let instruction_length = read_u16_be_at(data, offset + pos).to_int()
  pos = pos + 2
  match require_glyph_bytes(length, pos, instruction_length) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  pos = pos + instruction_length

  let num_points = previous_end_point + 1
  let flags : Array[Int] = []
  while flags.length() < num_points {
    match require_glyph_bytes(length, pos, 1) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    let flag = data[offset + pos].to_int()
    pos = pos + 1
    flags.push(flag)
    if flag.land(0x08) != 0 {
      match require_glyph_bytes(length, pos, 1) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let repeat_count = data[offset + pos].to_int()
      pos = pos + 1
      for _ in 0.. return Err(error)
        Ok(_) => ()
      }
      let value = data[offset + pos].to_int()
      pos = pos + 1
      if flag.land(0x10) != 0 {
        value
      } else {
        -value
      }
    } else if flag.land(0x10) != 0 {
      0
    } else {
      match require_glyph_bytes(length, pos, 2) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let value = read_i16_be_at(data, offset + pos)
      pos = pos + 2
      value
    }
    prev_x = prev_x + dx
    x_coords.push(prev_x)
  }

  let y_coords = []
  let mut prev_y = 0
  for idx in 0.. return Err(error)
        Ok(_) => ()
      }
      let value = data[offset + pos].to_int()
      pos = pos + 1
      if flag.land(0x20) != 0 {
        value
      } else {
        -value
      }
    } else if flag.land(0x20) != 0 {
      0
    } else {
      match require_glyph_bytes(length, pos, 2) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let value = read_i16_be_at(data, offset + pos)
      pos = pos + 2
      value
    }
    prev_y = prev_y + dy
    y_coords.push(prev_y)
  }

  let contours = []
  let mut point_start = 0
  for ci in 0.. Result[GlyphData, FontParseError] {
  let components = []

  let arg_1_and_2_are_words = 0x0001
  let args_are_xy_values = 0x0002
  let we_have_a_scale = 0x0008
  let more_components = 0x0020
  let we_have_an_x_and_y_scale = 0x0040
  let we_have_a_two_by_two = 0x0080

  let mut pos = 10
  let mut has_more = true
  while has_more {
    match require_glyph_bytes(length, pos, 4) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    let flags = read_u16_be_at(data, offset + pos).to_int()
    let glyph_index = read_u16_be_at(data, offset + pos + 2).to_int()
    pos = pos + 4

    let (x_offset, y_offset) = if flags.land(arg_1_and_2_are_words) != 0 {
      match require_glyph_bytes(length, pos, 4) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let x = read_i16_be_at(data, offset + pos)
      let y = read_i16_be_at(data, offset + pos + 2)
      pos = pos + 4
      (x, y)
    } else {
      match require_glyph_bytes(length, pos, 2) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let arg1 = read_i8_at(data, offset + pos)
      let arg2 = read_i8_at(data, offset + pos + 1)
      pos = pos + 2
      if flags.land(args_are_xy_values) != 0 {
        (arg1, arg2)
      } else {
        (0, 0)
      }
    }

    let (scale_x, scale_y, scale_xy, scale_yx) = if flags.land(we_have_a_scale) !=
      0 {
      match require_glyph_bytes(length, pos, 2) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let scale = read_i16_be_at(data, offset + pos).to_double() / 16384.0
      pos = pos + 2
      (scale, scale, 0.0, 0.0)
    } else if flags.land(we_have_an_x_and_y_scale) != 0 {
      match require_glyph_bytes(length, pos, 4) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let sx = read_i16_be_at(data, offset + pos).to_double() / 16384.0
      let sy = read_i16_be_at(data, offset + pos + 2).to_double() / 16384.0
      pos = pos + 4
      (sx, sy, 0.0, 0.0)
    } else if flags.land(we_have_a_two_by_two) != 0 {
      match require_glyph_bytes(length, pos, 8) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let sx = read_i16_be_at(data, offset + pos).to_double() / 16384.0
      let sxy = read_i16_be_at(data, offset + pos + 2).to_double() / 16384.0
      let syx = read_i16_be_at(data, offset + pos + 4).to_double() / 16384.0
      let sy = read_i16_be_at(data, offset + pos + 6).to_double() / 16384.0
      pos = pos + 8
      (sx, sy, sxy, syx)
    } else {
      (1.0, 1.0, 0.0, 0.0)
    }

    components.push({
      glyph_index,
      x_offset,
      y_offset,
      scale_x,
      scale_y,
      scale_xy,
      scale_yx,
    })

    has_more = flags.land(more_components) != 0
  }

  Ok(Composite(components))
}

///|
fn validate_glyph_data(
  data : Bytes,
  offset : Int,
  length : Int,
) -> Result[Unit, FontParseError] {
  match require_glyph_bytes(length, 0, 10) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let num_contours = read_i16_be_at(data, offset)
  guard num_contours != 0 else { return Ok(()) }
  if num_contours > 0 {
    validate_simple_glyph_data(data, offset, length, num_contours)
  } else {
    validate_composite_glyph_data(data, offset, length)
  }
}

///|
fn validate_simple_glyph_data(
  data : Bytes,
  offset : Int,
  length : Int,
  num_contours : Int,
) -> Result[Unit, FontParseError] {
  let mut pos = 10
  let end_points_len = num_contours * 2
  match require_glyph_bytes(length, pos, end_points_len) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let mut previous_end_point = -1
  for i in 0.. previous_end_point else {
      return Err(UnsortedGlyphContourEndpoints)
    }
    previous_end_point = end_point
  }
  pos = pos + end_points_len
  match require_glyph_bytes(length, pos, 2) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let instruction_length = read_u16_be_at(data, offset + pos).to_int()
  pos = pos + 2
  match require_glyph_bytes(length, pos, instruction_length) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  pos = pos + instruction_length

  let num_points = previous_end_point + 1
  let flags = []
  while flags.length() < num_points {
    match require_glyph_bytes(length, pos, 1) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    let flag = data[offset + pos].to_int()
    pos = pos + 1
    flags.push(flag)
    if flag.land(0x08) != 0 {
      match require_glyph_bytes(length, pos, 1) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      let repeat_count = data[offset + pos].to_int()
      pos = pos + 1
      for _ in 0.. return Err(error)
    Ok(_) => ()
  }
  pos = pos + x_bytes
  let y_bytes = glyph_coordinate_bytes(flags, num_points, is_x=false)
  match require_glyph_bytes(length, pos, y_bytes) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  Ok(())
}

///|
fn validate_composite_glyph_data(
  data : Bytes,
  offset : Int,
  length : Int,
) -> Result[Unit, FontParseError] {
  let arg_1_and_2_are_words = 0x0001
  let more_components = 0x0020
  let we_have_a_scale = 0x0008
  let we_have_an_x_and_y_scale = 0x0040
  let we_have_a_two_by_two = 0x0080

  let mut pos = 10
  let mut has_more = true
  while has_more {
    match require_glyph_bytes(length, pos, 4) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    let flags = read_u16_be_at(data, offset + pos).to_int()
    pos = pos + 4
    let argument_len = if flags.land(arg_1_and_2_are_words) != 0 {
      4
    } else {
      2
    }
    match require_glyph_bytes(length, pos, argument_len) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    pos = pos + argument_len
    let scale_len = if flags.land(we_have_a_scale) != 0 {
      2
    } else if flags.land(we_have_an_x_and_y_scale) != 0 {
      4
    } else if flags.land(we_have_a_two_by_two) != 0 {
      8
    } else {
      0
    }
    match require_glyph_bytes(length, pos, scale_len) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    pos = pos + scale_len
    has_more = flags.land(more_components) != 0
  }
  Ok(())
}

///|
fn glyph_coordinate_bytes(
  flags : Array[Int],
  num_points : Int,
  is_x~ : Bool,
) -> Int {
  let short_vector = if is_x { 0x02 } else { 0x04 }
  let same_or_positive = if is_x { 0x10 } else { 0x20 }
  let mut total = 0
  for i in 0.. Result[Unit, FontParseError] {
  guard pos >= 0 &&
    required >= 0 &&
    pos <= glyph_length &&
    required <= glyph_length - pos else {
    return Err(TableTooShort("glyf", glyph_length, pos + required))
  }
  Ok(())
}

///|
fn require_table_bytes(
  tag : String,
  table_length : Int,
  pos : Int,
  required : Int,
) -> Result[Unit, FontParseError] {
  guard pos >= 0 &&
    required >= 0 &&
    pos <= table_length &&
    required <= table_length - pos else {
    return Err(TableTooShort(tag, table_length, pos + required))
  }
  Ok(())
}

///|
fn require_cmap_subtable_offset(
  table : TableRecord,
  subtable_offset : Int,
  data_length : Int,
) -> Result[Unit, FontParseError] {
  let table_length = table.length.reinterpret_as_int()
  guard subtable_offset >= 0 && subtable_offset < table_length else {
    return Err(
      TableOutOfBounds(
        table.tag,
        table.offset.reinterpret_as_int() + subtable_offset,
        0,
        data_length,
      ),
    )
  }
  Ok(())
}

///|
fn read_u16_be_at(data : Bytes, offset : Int) -> UInt16 {
  let hi = data[offset].to_uint16()
  let lo = data[offset + 1].to_uint16()
  (hi << 8) | lo
}

///|
fn read_i16_be_at(data : Bytes, offset : Int) -> Int {
  let value = read_u16_be_at(data, offset).to_int()
  if value > 32767 {
    value - 65536
  } else {
    value
  }
}

///|
fn read_i8_at(data : Bytes, offset : Int) -> Int {
  let value = data[offset].to_int()
  if value > 127 {
    value - 256
  } else {
    value
  }
}

///|
fn read_u32_be_at(data : Bytes, offset : Int) -> UInt {
  let b1 = data[offset].to_uint()
  let b2 = data[offset + 1].to_uint()
  let b3 = data[offset + 2].to_uint()
  let b4 = data[offset + 3].to_uint()
  (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
}

///|
fn read_tag_at(data : Bytes, offset : Int) -> String {
  let sb = StringBuilder()
  sb.write_char(data[offset].to_uint16().unsafe_to_char())
  sb.write_char(data[offset + 1].to_uint16().unsafe_to_char())
  sb.write_char(data[offset + 2].to_uint16().unsafe_to_char())
  sb.write_char(data[offset + 3].to_uint16().unsafe_to_char())
  sb.to_string()
}

///|
/// Parse cmap table (supports format 4 and format 12)
fn parse_cmap(
  data : Bytes,
  tables : Map[String, TableRecord],
) -> Result[Map[Int, Int], FontParseError] {
  let table = match require_table(data, tables, "cmap") {
    Err(error) => return Err(error)
    Ok(table) => table
  }
  match require_min_length(table, 4) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let base_offset = table.offset.reinterpret_as_int()
  let table_length = table.length.reinterpret_as_int()
  let cmap : Map[Int, Int] = Map([])

  let version = read_u16_be_at(data, base_offset).to_int()
  guard version is 0 else { return Err(UnsupportedCmapVersion(version)) }
  let num_tables = read_u16_be_at(data, base_offset + 2).to_int()
  match require_table_bytes("cmap", table_length, 0, 4 + num_tables * 8) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }

  let mut format4_offset = -1
  let mut format12_offset = -1
  for i in 0.. return Err(error)
      Ok(_) => ()
    }

    // Prefer format 12 (full Unicode) over format 4 (BMP only).
    if platform_id is 0 && encoding_id is 4 {
      format12_offset = subtable_offset
    } else if platform_id is 3 && encoding_id is 10 {
      format12_offset = subtable_offset
    } else if platform_id is 3 && encoding_id is 1 {
      format4_offset = subtable_offset
    }
  }

  if format12_offset >= 0 {
    let result = match parse_cmap_format12(data, table, format12_offset) {
      Err(error) => return Err(error)
      Ok(result) => result
    }
    if result.length() > 0 {
      return Ok(result)
    }
  }

  if format4_offset >= 0 {
    return parse_cmap_format4(data, table, format4_offset)
  }

  Ok(cmap)
}

///|
/// Parse cmap format 4 subtable (BMP characters)
fn parse_cmap_format4(
  data : Bytes,
  table : TableRecord,
  subtable_offset : Int,
) -> Result[Map[Int, Int], FontParseError] {
  let cmap : Map[Int, Int] = Map([])
  let table_base = table.offset.reinterpret_as_int()
  let table_length = table.length.reinterpret_as_int()
  match require_table_bytes("cmap", table_length, subtable_offset, 4) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let offset = table_base + subtable_offset
  let format = read_u16_be_at(data, offset).to_int()
  guard format is 4 else { return Ok(cmap) }

  let subtable_length = read_u16_be_at(data, offset + 2).to_int()
  guard subtable_length >= 16 else {
    return Err(TableTooShort("cmap", subtable_length, 16))
  }
  match
    require_table_bytes("cmap", table_length, subtable_offset, subtable_length) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }

  let seg_count_x2 = read_u16_be_at(data, offset + 6).to_int()
  guard (seg_count_x2 % 2) is 0 else {
    return Err(InvalidCmapSegmentCount(seg_count_x2))
  }
  let seg_count = seg_count_x2 / 2
  let required_length = 16 + seg_count * 8
  guard required_length <= subtable_length else {
    return Err(TableTooShort("cmap", subtable_length, required_length))
  }

  let end_codes_offset = 14
  let start_codes_offset = end_codes_offset + seg_count * 2 + 2
  let id_deltas_offset = start_codes_offset + seg_count * 2
  let id_range_offsets_offset = id_deltas_offset + seg_count * 2
  let end_codes = []
  let start_codes = []
  let id_deltas = []
  let id_range_offsets = []
  for i in 0.. return Err(error)
          Ok(_) => ()
        }
        let gid = read_u16_be_at(data, offset + glyph_rel).to_int()
        if gid is 0 {
          0
        } else {
          (gid + delta) % 65536
        }
      }
      if glyph_id > 0 {
        cmap[c] = glyph_id
      }
    }
  }

  Ok(cmap)
}

///|
/// Parse cmap format 12 subtable (full Unicode repertoire)
fn parse_cmap_format12(
  data : Bytes,
  table : TableRecord,
  subtable_offset : Int,
) -> Result[Map[Int, Int], FontParseError] {
  let cmap : Map[Int, Int] = Map([])
  let table_base = table.offset.reinterpret_as_int()
  let table_length = table.length.reinterpret_as_int()
  match require_table_bytes("cmap", table_length, subtable_offset, 16) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let offset = table_base + subtable_offset
  let format = read_u16_be_at(data, offset).to_int()
  guard format is 12 else { return Ok(cmap) }

  let subtable_length = read_u32_be_at(data, offset + 4).reinterpret_as_int()
  guard subtable_length >= 16 else {
    return Err(TableTooShort("cmap", subtable_length, 16))
  }
  match
    require_table_bytes("cmap", table_length, subtable_offset, subtable_length) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let n_groups = read_u32_be_at(data, offset + 12).reinterpret_as_int()
  guard n_groups >= 0 && n_groups <= (subtable_length - 16) / 12 else {
    return Err(TableTooShort("cmap", subtable_length, 16 + n_groups * 12))
  }

  for i in 0..= 0 &&
      end_char_code >= start_char_code &&
      end_char_code <= 0x10FFFF else {
      return Err(InvalidCmapGroupRange(start_char_code, end_char_code))
    }

    for c in start_char_code..<=end_char_code {
      let glyph_id = start_glyph_id + (c - start_char_code)
      cmap[c] = glyph_id
    }
  }

  Ok(cmap)
}

///|
/// Parse kern table
fn parse_kern(
  data : Bytes,
  tables : Map[String, TableRecord],
) -> Result[Array[KernPair], FontParseError] {
  let kern = []

  match tables.get("kern") {
    Some(table) => {
      match require_table_bounds(data, table) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      match require_min_length(table, 4) {
        Err(error) => return Err(error)
        Ok(_) => ()
      }

      let offset = table.offset.reinterpret_as_int()
      let table_length = table.length.reinterpret_as_int()
      let version = read_u16_be_at(data, offset).to_int()
      guard version is 0 else { return Ok(kern) }

      let n_tables = read_u16_be_at(data, offset + 2).to_int()
      let mut pos = 4

      for _ in 0.. return Err(error)
          Ok(_) => ()
        }
        let subtable_length = read_u16_be_at(data, offset + pos + 2).to_int()
        let coverage = read_u16_be_at(data, offset + pos + 4).to_int()
        guard subtable_length >= 6 else {
          return Err(TableTooShort("kern", subtable_length, 6))
        }
        match require_table_bytes("kern", table_length, pos, subtable_length) {
          Err(error) => return Err(error)
          Ok(_) => ()
        }

        let format = (coverage >> 8).land(0xFF)
        if format is 0 {
          guard subtable_length >= 14 else {
            return Err(TableTooShort("kern", subtable_length, 14))
          }
          let n_pairs = read_u16_be_at(data, offset + pos + 6).to_int()
          let required_length = 14 + n_pairs * 6
          guard required_length <= subtable_length else {
            return Err(TableTooShort("kern", subtable_length, required_length))
          }
          let pairs_offset = pos + 14
          for i in 0..
      // No kern table
      ignore(0)
  }

  Ok(kern)
}