///|
fn PdfDocument::pdf_font_has_embedded_file(
  self : PdfDocument,
  descriptor : @syntax.PdfObject,
) -> Bool {
  self.lookup_direct(pdf_text_font_file_key_name, descriptor) is Some(_) ||
  self.lookup_direct(pdf_text_font_file2_key_name, descriptor) is Some(_) ||
  self.lookup_direct(pdf_text_font_file3_key_name, descriptor) is Some(_)
}

///|
/// Return whether a font dictionary has no embedded font program.
///
/// Type 3 fonts are never reported as missing, matching cpdf. Other fonts are
/// missing when they have no `/FontDescriptor`, or when their descriptor lacks
/// `/FontFile`, `/FontFile2`, and `/FontFile3`.
pub fn PdfDocument::is_missing_font(
  self : PdfDocument,
  font : @syntax.PdfObject,
) -> Bool {
  match self.lookup_direct(pdf_text_subtype_key_name, font) {
    Some(PdfNameObject(name)) if name == pdf_text_type3_name => false
    _ =>
      match self.lookup_direct(pdf_text_font_descriptor_key_name, font) {
        None => true
        Some(descriptor) => !self.pdf_font_has_embedded_file(descriptor)
      }
  }
}

///|
fn PdfDocument::pdf_missing_font_entry(
  self : PdfDocument,
  page : Int,
  name : @core.PdfName,
  font : @syntax.PdfObject,
) -> @font.PdfMissingFontEntry raise @core.PdfError {
  let encoding = match
    self.pdf_font_name_field(font, pdf_text_encoding_key_name) {
    "" => "Built-in"
    value => value
  }
  {
    page,
    name: pdf_util_string_from_name(name),
    subtype: self.pdf_font_name_field(font, pdf_text_subtype_key_name),
    basefont: self.pdf_font_name_field(font, pdf_text_base_font_key_name),
    encoding,
  }
}

///|
fn PdfDocument::pdf_missing_font_candidates(
  self : PdfDocument,
  name : @core.PdfName,
  font : @syntax.PdfObject,
) -> Array[(@core.PdfName, @syntax.PdfObject)] {
  match self.lookup_direct(pdf_text_descendant_fonts_key_name, font) {
    Some(PdfArray(descendants)) =>
      [
        for descendant in descendants => (name, descendant)
      ]
    _ => [(name, font)]
  }
}

///|
fn PdfDocument::pdf_missing_fonts_on_page(
  self : PdfDocument,
  page : Int,
  resources : @syntax.PdfObject,
) -> Array[@font.PdfMissingFontEntry] raise @core.PdfError {
  match self.lookup_direct(pdf_page_font_resource_name, resources) {
    Some(PdfDictionary(fonts)) => {
      let output : Array[@font.PdfMissingFontEntry] = Array(
        capacity=fonts.length(),
      )
      for font_entry in fonts {
        for
          candidate in self.pdf_missing_font_candidates(
            font_entry.0,
            font_entry.1,
          ) {
          if self.is_missing_font(candidate.1) {
            output.push(
              self.pdf_missing_font_entry(page, candidate.0, candidate.1),
            )
          }
        }
      }
      output
    }
    _ => []
  }
}

///|
/// Return cpdf's missing-font report for selected pages.
///
/// This follows cpdf's current behavior by inspecting page `/Font` resources and
/// descendant fonts. It does not recurse into Form XObjects; the cpdf source
/// marks that as a separate FIXME.
pub fn PdfDocument::missing_fonts(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Array[@font.PdfMissingFontEntry] raise @core.PdfError {
  let pages = self.pages_of_pagetree()
  pdf_page_box_validate_page_numbers(
    page_numbers,
    pages.length(),
    "missing_fonts",
  )
  let selected_pages = @core.pdf_number_set(page_numbers)
  let output : Array[@font.PdfMissingFontEntry] = Array(
    capacity=page_numbers.length(),
  )
  for index, page in pages {
    let page_number = index + 1
    if selected_pages.contains(page_number) {
      output.append(self.pdf_missing_fonts_on_page(page_number, page.resources))
    }
  }
  output
}

///|
/// Compatibility wrapper for `PdfDocument::missing_fonts`.
pub fn pdf_missing_fonts(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Array[@font.PdfMissingFontEntry] raise @core.PdfError {
  document.missing_fonts(page_numbers)
}

///|
fn pdf_missing_font_entry_tuple(
  entry : @font.PdfMissingFontEntry,
) -> (Int, String, String, String, String) {
  (entry.page, entry.name, entry.subtype, entry.basefont, entry.encoding)
}

///|
/// Return cpdffont's `missing_fonts_return` tuple rows.
pub fn PdfDocument::missing_fonts_return(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Array[(Int, String, String, String, String)] raise @core.PdfError {
  [
    for entry in self.missing_fonts(page_numbers) => {
      pdf_missing_font_entry_tuple(entry)
    }
  ]
}

///|
/// Compatibility wrapper for `PdfDocument::missing_fonts_return`.
pub fn pdf_missing_fonts_return(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Array[(Int, String, String, String, String)] raise @core.PdfError {
  document.missing_fonts_return(page_numbers)
}

///|
fn pdf_missing_font_write_text(
  output : Array[Byte],
  position : Int,
  entry : @font.PdfMissingFontEntry,
) -> Int {
  let mut current = pdf_font_write_utf8(
    output,
    position,
    entry.page.to_string(),
  )
  current = pdf_font_write_utf8(output, current, ", ")
  current = pdf_font_write_utf8(output, current, entry.name)
  current = pdf_font_write_utf8(output, current, ", ")
  current = pdf_font_write_utf8(output, current, entry.subtype)
  current = pdf_font_write_utf8(output, current, ", ")
  current = pdf_font_write_utf8(output, current, entry.basefont)
  current = pdf_font_write_utf8(output, current, ", ")
  current = pdf_font_write_utf8(output, current, entry.encoding)
  output[current] = 10
  current + 1
}

///|
fn pdf_missing_font_text_length(entry : @font.PdfMissingFontEntry) -> Int {
  pdf_font_utf8_length(entry.page.to_string()) +
  pdf_font_utf8_length(entry.name) +
  pdf_font_utf8_length(entry.subtype) +
  pdf_font_utf8_length(entry.basefont) +
  pdf_font_utf8_length(entry.encoding) +
  9
}

///|
fn pdf_missing_fonts_text_length(
  entries : ArrayView[@font.PdfMissingFontEntry],
) -> Int {
  let mut length = 0
  for entry in entries {
    length += pdf_missing_font_text_length(entry)
  }
  length
}

///|
/// Render cpdf's plain-text missing-font report for selected pages.
pub fn PdfDocument::missing_fonts_text(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> @core.PdfBytes raise @core.PdfError {
  let entries = self.missing_fonts(page_numbers)
  let output = Array::make(pdf_missing_fonts_text_length(entries), b'\x00')
  let mut position = 0
  for entry in entries {
    position = pdf_missing_font_write_text(output, position, entry)
  }
  Bytes::from_array(output)
}

///|
/// Compatibility wrapper for `PdfDocument::missing_fonts_text`.
pub fn pdf_missing_fonts_text(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
) -> @core.PdfBytes raise @core.PdfError {
  document.missing_fonts_text(page_numbers)
}