///|
/// Return the object number referenced by an embedded font-file marker.
pub fn PdfFontFile::object_number(self : PdfFontFile) -> Int {
  match self {
    PdfFontFile(number) | PdfFontFile2(number) | PdfFontFile3(number) => number
  }
}

///|
/// Return the embedded font-file stream object number for a parsed font.
pub fn PdfFont::embedded_fontfile_number(self : PdfFont) -> Int? {
  match self {
    PdfFontSimple(
      { fontdescriptor: Some({ fontfile: Some(fontfile), .. }), .. }
    ) => Some(fontfile.object_number())
    PdfFontCIDKeyed(
      {
        composite: { cid_fontdescriptor: { fontfile: Some(fontfile), .. }, .. },
        ..
      }
    ) => Some(fontfile.object_number())
    _ => None
  }
}

///|
fn PdfDocument::pdf_font_descriptor_file_number(
  self : PdfDocument,
  font : @syntax.PdfObject,
) -> Int? {
  match self.lookup_direct(pdf_text_font_descriptor_key_name, font) {
    Some(descriptor) =>
      match pdf_text_fontfile_of_descriptor(descriptor) {
        Some(fontfile) => Some(fontfile.object_number())
        None => None
      }
    None => None
  }
}

///|
fn PdfDocument::pdf_font_embedded_file_number(
  self : PdfDocument,
  font : @syntax.PdfObject,
) -> Int? {
  match self.pdf_font_descriptor_file_number(font) {
    Some(number) => Some(number)
    None =>
      match self.lookup_direct(pdf_text_descendant_fonts_key_name, font) {
        Some(PdfArray(descendants)) =>
          for descendant in descendants {
            match self.pdf_font_descriptor_file_number(descendant) {
              Some(number) => break Some(number)
              None => continue
            }
          } nobreak {
            None
          }
        _ => None
      }
  }
}

///|
/// Return decoded embedded font-file bytes for a named page font, if present.
///
/// This is the in-memory counterpart to cpdf's `extract_fontfile`, which writes
/// the decoded font program to a path. Missing names or fonts without embedded
/// font programs return `None`; malformed font dictionaries or font-file stream
/// objects raise through the normal PDF error path.
pub fn PdfDocument::extract_fontfile_bytes(
  self : PdfDocument,
  page_number : Int,
  font_name : String,
) -> @core.PdfBytes? raise @core.PdfError {
  let font = self.font_from_name(font_name, page_number)
  if font == PdfNull {
    None
  } else {
    match self.pdf_font_embedded_file_number(font) {
      Some(number) =>
        Some(self.pdf_decode_stream(PdfIndirect(number)).stream_bytes())
      None => None
    }
  }
}

///|
/// Compatibility wrapper for `PdfDocument::extract_fontfile_bytes`.
pub fn pdf_extract_fontfile_bytes(
  document : PdfDocument,
  page_number : Int,
  font_name : String,
) -> @core.PdfBytes? raise @core.PdfError {
  document.extract_fontfile_bytes(page_number, font_name)
}

///|
fn PdfDocument::pdf_font_copy_referenced_objects_from(
  self : PdfDocument,
  source : PdfDocument,
  font : @syntax.PdfObject,
) -> Unit {
  for number in source.referenced_object_numbers(font) {
    self.add_object_given_number(number, source.lookup_object_or_null(number))
  }
}

///|
fn PdfDocument::pdf_font_resource_dictionary_or_empty(
  self : PdfDocument,
  resources : @syntax.PdfObject,
) -> @syntax.PdfObject raise @core.PdfError {
  match self.lookup_direct(pdf_page_font_resource_name, resources) {
    Some(PdfDictionary(_) as fonts) => fonts
    Some(_) => raise DictionaryExpected
    None => PdfDictionary([])
  }
}

///|
fn PdfDocument::pdf_font_add_to_page_resources(
  self : PdfDocument,
  page : @page.PdfPage,
  resource_name : @core.PdfName,
  font : @syntax.PdfObject,
) -> @page.PdfPage raise @core.PdfError {
  let fonts = self.pdf_font_resource_dictionary_or_empty(page.resources)
  let updated_fonts = fonts.add_dict_entry(resource_name, font)
  {
    ..page,
    resources: page.resources.add_dict_entry(
      pdf_page_font_resource_name, updated_fonts,
    ),
  }
}

///|
/// Copy a named font from `source` onto selected pages of this document.
///
/// This ports cpdf's `copy_font`: both documents are first renumbered into one
/// non-overlapping object space, the direct source font dictionary is added to
/// selected target pages under its `/BaseFont` name, and all objects referenced
/// by the copied font dictionary are copied into the target document.
pub fn PdfDocument::copy_font_from(
  self : PdfDocument,
  source : PdfDocument,
  font_name : String,
  font_page : Int,
  page_numbers : ArrayView[Int],
) -> PdfDocument raise @core.PdfError {
  let renumbered = pdf_renumber_documents([source, self])
  let source_pdf = renumbered[0]
  let target_pdf = renumbered[1]
  let font = source_pdf.font_from_name(font_name, font_page)
  let resource_name = match
    source_pdf.lookup_direct(pdf_text_base_font_key_name, font) {
    Some(PdfNameObject(name)) => name
    _ => @core.pdf_name_of_bytes(b"/CopyFontAddedNoName")
  }
  target_pdf.pdf_font_copy_referenced_objects_from(source_pdf, font)
  let pages = target_pdf.pages_of_pagetree()
  pdf_page_box_validate_page_numbers(page_numbers, pages.length(), "copy_font")
  let selected_pages = @core.pdf_number_set(page_numbers)
  let rewritten : Array[@page.PdfPage] = Array(capacity=pages.length())
  for index, page in pages {
    let page_number = index + 1
    if selected_pages.contains(page_number) {
      rewritten.push(
        target_pdf.pdf_font_add_to_page_resources(page, resource_name, font),
      )
    } else {
      rewritten.push(page)
    }
  }
  target_pdf.change_pages(rewritten)
}

///|
/// Compatibility wrapper for `PdfDocument::copy_font_from`.
pub fn pdf_copy_font(
  source : PdfDocument,
  font_name : String,
  font_page : Int,
  page_numbers : ArrayView[Int],
  target : PdfDocument,
) -> PdfDocument raise @core.PdfError {
  target.copy_font_from(source, font_name, font_page, page_numbers)
}