///|
fn PdfDocument::pdf_text_cid_string_entry(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
  key : @core.PdfName,
  error : @core.PdfError,
) -> @core.PdfBytes raise @core.PdfError {
  match self.lookup_direct(key, dictionary) {
    Some(PdfString(bytes)) => bytes
    _ => raise error
  }
}

///|
/// Read a CID-system-info dictionary.
///
/// Registry and ordering are returned as byte strings, matching PDF string
/// semantics. Missing or malformed required fields raise `@core.PdfError`.
pub fn PdfDocument::read_cid_system_info(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
) -> PdfCIDSystemInfo raise @core.PdfError {
  {
    registry: self.pdf_text_cid_string_entry(
      dictionary,
      pdf_text_registry_key_name,
      CIDRegistryExpected,
    ),
    ordering: self.pdf_text_cid_string_entry(
      dictionary,
      pdf_text_ordering_key_name,
      CIDOrderingExpected,
    ),
    supplement: match
      self.lookup_direct(pdf_text_supplement_key_name, dictionary) {
      Some(PdfInteger(value)) => value
      _ => raise CIDSupplementExpected
    },
  }
}

///|
fn PdfDocument::pdf_text_cid_number(
  self : PdfDocument,
  value : @syntax.PdfObject,
  error : @core.PdfError,
) -> Double raise @core.PdfError {
  self.get_number(value) catch {
    _ => raise error
  }
}

///|
fn PdfDocument::pdf_text_read_cid_widths(
  self : PdfDocument,
  values : ArrayView[@syntax.PdfObject],
) -> Array[(Int, Double)] raise @core.PdfError {
  let output : Array[(Int, Double)] = Array(capacity=values.length())
  let mut index = 0
  while index < values.length() {
    match self.direct(values[index]) {
      PdfInteger(first_code) => {
        guard index + 1 < values.length() else { raise CIDWidthsExpected }
        match self.direct(values[index + 1]) {
          PdfArray(widths) => {
            for offset in 0.. {
            guard index + 2 < values.length() && last_code >= first_code else {
              raise CIDWidthsExpected
            }
            let width = self.pdf_text_cid_number(
              values[index + 2],
              CIDWidthsExpected,
            )
            for code in first_code..<=last_code {
              output.push((code, width))
            }
            index += 3
          }
          _ => raise CIDWidthsExpected
        }
      }
      _ => raise CIDWidthsExpected
    }
  }
  output
}

///|
fn PdfDocument::pdf_text_read_cid_widths2(
  self : PdfDocument,
  values : ArrayView[@syntax.PdfObject],
) -> Array[(Int, PdfCIDVerticalWidth)] raise @core.PdfError {
  let output : Array[(Int, PdfCIDVerticalWidth)] = Array(
    capacity=values.length(),
  )
  let mut index = 0
  while index < values.length() {
    match self.direct(values[index]) {
      PdfInteger(first_code) => {
        guard index + 1 < values.length() else { raise CIDWidths2Expected }
        match self.direct(values[index + 1]) {
          PdfArray(widths) => {
            guard widths.length() % 3 == 0 else { raise CIDWidths2Expected }
            let mut code = first_code
            let mut width_index = 0
            while width_index < widths.length() {
              output.push(
                (
                  code,
                  {
                    width: self.pdf_text_cid_number(
                      widths[width_index],
                      CIDWidths2Expected,
                    ),
                    vx: self.pdf_text_cid_number(
                      widths[width_index + 1],
                      CIDWidths2Expected,
                    ),
                    vy: self.pdf_text_cid_number(
                      widths[width_index + 2],
                      CIDWidths2Expected,
                    ),
                  },
                ),
              )
              code += 1
              width_index += 3
            }
            index += 2
          }
          PdfInteger(last_code) => {
            guard index + 4 < values.length() && last_code >= first_code else {
              raise CIDWidths2Expected
            }
            let width = PdfCIDVerticalWidth::{
              width: self.pdf_text_cid_number(
                values[index + 2],
                CIDWidths2Expected,
              ),
              vx: self.pdf_text_cid_number(
                values[index + 3],
                CIDWidths2Expected,
              ),
              vy: self.pdf_text_cid_number(
                values[index + 4],
                CIDWidths2Expected,
              ),
            }
            for code in first_code..<=last_code {
              output.push((code, width))
            }
            index += 5
          }
          _ => raise CIDWidths2Expected
        }
      }
      _ => raise CIDWidths2Expected
    }
  }
  output
}

///|
fn PdfDocument::pdf_text_cid_widths(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
) -> Array[(Int, Double)] raise @core.PdfError {
  match self.lookup_direct(pdf_text_w_key_name, dictionary) {
    Some(PdfArray(values)) => self.pdf_text_read_cid_widths(values)
    _ => []
  }
}

///|
fn PdfDocument::pdf_text_cid_widths2(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
) -> Array[(Int, PdfCIDVerticalWidth)] raise @core.PdfError {
  match self.lookup_direct(pdf_text_w2_key_name, dictionary) {
    Some(PdfArray(values)) => self.pdf_text_read_cid_widths2(values)
    _ => []
  }
}

///|
fn PdfDocument::pdf_text_cid_default_width(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
) -> Double raise @core.PdfError {
  match self.lookup_direct(pdf_text_dw_key_name, dictionary) {
    Some(value) => self.pdf_text_cid_number(value, CIDWidthsExpected)
    None => 1000.0
  }
}

///|
fn PdfDocument::pdf_text_cid_default_width2(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
) -> Array[Double] raise @core.PdfError {
  match self.lookup_direct(pdf_text_dw2_key_name, dictionary) {
    Some(PdfArray(values)) => {
      let output : Array[Double] = Array(capacity=values.length())
      for value in values {
        output.push(self.pdf_text_cid_number(value, CIDWidths2Expected))
      }
      output
    }
    _ => [880.0, -1000.0]
  }
}

///|
fn PdfDocument::pdf_text_read_descendant(
  self : PdfDocument,
  dictionary : @syntax.PdfObject,
) -> PdfCompositeCIDFont raise @core.PdfError {
  let cid_system_info = match
    self.lookup_direct(pdf_text_cid_system_info_key_name, dictionary) {
    Some(system_info) => self.read_cid_system_info(system_info)
    None => raise CIDSystemInfoExpected
  }
  let cid_basefont = match
    self.lookup_direct(pdf_text_base_font_key_name, dictionary) {
    Some(PdfNameObject(name)) => name
    _ => raise CIDBaseFontExpected
  }
  let cid_fontdescriptor = match self.read_font_descriptor(dictionary) {
    Some(fontdescriptor) => fontdescriptor
    None => raise CIDFontDescriptorExpected
  }
  {
    cid_system_info,
    cid_basefont,
    cid_fontdescriptor,
    cid_widths: self.pdf_text_cid_widths(dictionary),
    cid_widths2: self.pdf_text_cid_widths2(dictionary),
    cid_default_width: self.pdf_text_cid_default_width(dictionary),
    cid_default_width2: self.pdf_text_cid_default_width2(dictionary),
  }
}

///|
fn PdfDocument::pdf_text_read_cmap_encoding(
  self : PdfDocument,
  font : @syntax.PdfObject,
) -> PdfCMapEncoding raise @core.PdfError {
  match self.lookup_direct(pdf_text_encoding_key_name, font) {
    Some(PdfNameObject(name)) => PdfPredefinedCMap(name)
    Some(PdfStreamObject(_)) =>
      match pdf_text_find_immediate_indirect(font, pdf_text_encoding_key_name) {
        Some(number) =>
          try self.parse_cmap(PdfIndirect(number)) catch {
            _ => PdfCMap(number)
          } noraise {
            parsed => PdfExternalCMap(number, parsed)
          }
        None => raise CMapEncodingExpected
      }
    _ => raise CMapEncodingExpected
  }
}

///|
/// Parse a Type 0 font dictionary into a CID-keyed font.
///
/// This reads the descendant CIDFont, width data, descriptor, base font, and
/// CMap encoding. External CMap streams are parsed when possible while still
/// preserving the referenced stream object number.
pub fn PdfDocument::read_cidkeyed_font(
  self : PdfDocument,
  font : @syntax.PdfObject,
) -> PdfCIDKeyedFont raise @core.PdfError {
  let basefont = match self.lookup_direct(pdf_text_base_font_key_name, font) {
    Some(PdfNameObject(name)) => name
    _ => raise BaseFontExpected
  }
  let composite = match
    self.lookup_direct(pdf_text_descendant_fonts_key_name, font) {
    Some(PdfArray([descendant])) =>
      self.pdf_text_read_descendant(self.direct(descendant))
    _ => raise DescendantFontExpected
  }
  { basefont, composite, encoding: self.pdf_text_read_cmap_encoding(font), }
}