///|
fn PdfDocument::pdf_font_list_entry(
  self : PdfDocument,
  page : Int,
  name : String,
  font : @syntax.PdfObject,
) -> @font.PdfFontListEntry raise @core.PdfError {
  let direct_font = self.direct(font)
  {
    page,
    name,
    subtype: self.pdf_font_name_field(direct_font, pdf_text_subtype_key_name),
    basefont: self.pdf_font_name_field(direct_font, pdf_text_base_font_key_name),
    encoding: self.pdf_font_name_field(direct_font, pdf_text_encoding_key_name),
    font: direct_font,
  }
}

///|
fn PdfDocument::pdf_font_list_fonts_dictionary(
  self : PdfDocument,
  page : Int,
  prefix : String,
  fonts : ArrayView[(@core.PdfName, @syntax.PdfObject)],
) -> Array[@font.PdfFontListEntry] raise @core.PdfError {
  let output : Array[@font.PdfFontListEntry] = Array(capacity=fonts.length())
  for entry in fonts {
    output.push(
      self.pdf_font_list_entry(
        page,
        prefix + pdf_util_string_from_name(entry.0),
        entry.1,
      ),
    )
  }
  output
}

///|
fn PdfDocument::pdf_font_list_xobjects(
  self : PdfDocument,
  page : Int,
  prefix : String,
  xobjects : ArrayView[(@core.PdfName, @syntax.PdfObject)],
  seen : Array[Int],
) -> Array[@font.PdfFontListEntry] raise @core.PdfError {
  let output : Array[@font.PdfFontListEntry] = Array(capacity=xobjects.length())
  for entry in xobjects {
    match entry.1 {
      PdfIndirect(number) =>
        output.append(
          self.pdf_font_list_xobject(
            page,
            prefix + pdf_util_string_from_name(entry.0),
            number,
            seen,
          ),
        )
      _ => ()
    }
  }
  output
}

///|
fn PdfDocument::pdf_font_list_resources(
  self : PdfDocument,
  page : Int,
  prefix : String,
  resources : @syntax.PdfObject,
  seen : Array[Int],
) -> Array[@font.PdfFontListEntry] raise @core.PdfError {
  let output : Array[@font.PdfFontListEntry] = []
  match self.lookup_direct(pdf_page_font_resource_name, resources) {
    Some(PdfDictionary(fonts)) =>
      output.append(self.pdf_font_list_fonts_dictionary(page, prefix, fonts))
    _ => ()
  }
  match self.lookup_direct(pdf_page_xobject_key(), resources) {
    Some(PdfDictionary(xobjects)) =>
      output.append(self.pdf_font_list_xobjects(page, prefix, xobjects, seen))
    _ => ()
  }
  output
}

///|
fn PdfDocument::pdf_font_list_xobject(
  self : PdfDocument,
  page : Int,
  prefix : String,
  xobject_number : Int,
  seen : Array[Int],
) -> Array[@font.PdfFontListEntry] raise @core.PdfError {
  if seen.contains(xobject_number) {
    []
  } else {
    seen.push(xobject_number)
    let xobject = self.lookup_object_or_null(xobject_number)
    match self.lookup_direct(pdf_page_resources_key(), xobject) {
      Some(resources) =>
        self.pdf_font_list_resources(page, prefix, resources, seen)
      _ => []
    }
  }
}

///|
/// Return cpdf's font resource listing for selected pages.
///
/// The listing walks page `/Resources` dictionaries and nested Form XObject
/// resources. Duplicate indirect XObjects are processed once per call, matching
/// cpdf's intended "do not process an xobject twice" behavior.
pub fn PdfDocument::list_fonts(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Array[@font.PdfFontListEntry] raise @core.PdfError {
  let pages = self.pages_of_pagetree()
  pdf_page_box_validate_page_numbers(page_numbers, pages.length(), "list_fonts")
  let selected_pages = @core.pdf_number_set(page_numbers)
  let output : Array[@font.PdfFontListEntry] = Array(
    capacity=page_numbers.length(),
  )
  let seen_xobjects : Array[Int] = 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_font_list_resources(
          page_number,
          "",
          page.resources,
          seen_xobjects,
        ),
      )
    }
  }
  output
}

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

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

///|
fn pdf_font_listing_line_length(entry : @font.PdfFontListEntry) -> 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) +
  5
}

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

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

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

///|
/// Return the bytes cpdf's `print_fonts` would print.
pub fn PdfDocument::print_fonts(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  json? : Bool = false,
) -> @core.PdfBytes raise @core.PdfError {
  if json {
    self.fonts_json_blob(page_numbers)
  } else {
    self.list_fonts_text(page_numbers)
  }
}

///|
/// Compatibility wrapper matching cpdffont's `print_fonts`.
pub fn pdf_print_fonts(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  json? : Bool = false,
) -> @core.PdfBytes raise @core.PdfError {
  document.print_fonts(page_numbers, json~)
}

///|
fn pdf_font_json_nullable_name(value : String) -> Json {
  if value == "" {
    Json::null()
  } else {
    Json::string(value)
  }
}

///|
fn pdf_font_entry_json(entry : @font.PdfFontListEntry) -> Json {
  Json::object(
    Map([
      ("page", pdf_util_json_number(entry.page)),
      ("name", Json::string(entry.name)),
      ("subtype", pdf_font_json_nullable_name(entry.subtype)),
      ("basefont", pdf_font_json_nullable_name(entry.basefont)),
      ("encoding", pdf_font_json_nullable_name(entry.encoding)),
    ]),
  )
}

///|
/// Return cpdf's JSON font listing for selected pages.
pub fn PdfDocument::fonts_json(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Json raise @core.PdfError {
  Json::array(
    [
      for entry in self.list_fonts(page_numbers) => pdf_font_entry_json(entry)
    ],
  )
}

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

///|
/// Compatibility wrapper matching cpdffont's `json_fonts`.
pub fn pdf_json_fonts(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Json raise @core.PdfError {
  document.fonts_json(page_numbers)
}

///|
/// Return cpdf's JSON font listing as UTF-8 JSON bytes.
pub fn PdfDocument::fonts_json_blob(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> @core.PdfBytes raise @core.PdfError {
  @utf8.encode(self.fonts_json(page_numbers).stringify())
}

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