///|
fn PdfDocument::pdf_image_add_structure_tree(
  self : PdfDocument,
  subformat : PdfUASubformat?,
  title : String,
) -> Unit raise @core.PdfError {
  let structure_root_number = self.add_object(PdfNull)
  let figure_number = self.add_object(PdfNull)
  let parent_tree_number = self.add_object(PdfNull)
  let figure_reference = @syntax.PdfIndirect(figure_number)
  let parent_tree = @syntax.PdfDictionary([
    (
      pdf_image_nums_key(),
      PdfArray([PdfInteger(1), PdfArray([figure_reference])]),
    ),
  ])
  self.add_object_given_number(parent_tree_number, parent_tree)
  match subformat {
    Some(PdfUA2) => {
      let ns_object = @syntax.PdfIndirect(
        self.add_object(
          PdfDictionary([
            (pdf_image_ns_key(), PdfString(pdf_image_pdfua2_namespace)),
          ]),
        ),
      )
      let document_number = self.add_object(PdfNull)
      self.add_object_given_number(
        document_number,
        PdfDictionary([
          (pdf_structure_k_key(), PdfArray([figure_reference])),
          (pdf_image_p_key(), PdfIndirect(structure_root_number)),
          (pdf_image_s_key(), PdfNameObject(pdf_image_document_name())),
          (pdf_image_ns_key(), ns_object),
        ]),
      )
      self.add_object_given_number(
        figure_number,
        PdfDictionary([
          (pdf_structure_k_key(), PdfArray([PdfInteger(0)])),
          (pdf_image_p_key(), PdfIndirect(document_number)),
          (pdf_image_s_key(), PdfNameObject(pdf_image_figure_name())),
          (pdf_image_alt_key(), PdfString(@utf8.encode(title))),
        ]),
      )
      self.add_object_given_number(
        structure_root_number,
        PdfDictionary([
          (pdf_image_namespaces_key(), PdfArray([ns_object])),
          (pdf_ua_type_key(), PdfNameObject(pdf_ua_struct_tree_root())),
          (pdf_structure_k_key(), PdfArray([PdfIndirect(document_number)])),
          (pdf_structure_parent_tree_key(), PdfIndirect(parent_tree_number)),
        ]),
      )
    }
    _ => {
      self.add_object_given_number(
        figure_number,
        PdfDictionary([
          (pdf_structure_k_key(), PdfArray([PdfInteger(0)])),
          (pdf_image_p_key(), PdfIndirect(structure_root_number)),
          (pdf_image_s_key(), PdfNameObject(pdf_image_figure_name())),
          (pdf_image_alt_key(), PdfString(@utf8.encode(title))),
        ]),
      )
      self.add_object_given_number(
        structure_root_number,
        PdfDictionary([
          (pdf_ua_type_key(), PdfNameObject(pdf_ua_struct_tree_root())),
          (pdf_structure_k_key(), PdfArray([figure_reference])),
          (pdf_structure_parent_tree_key(), PdfIndirect(parent_tree_number)),
        ]),
      )
    }
  }
  self.replace_chain(
    [pdf_structure_root_key(), pdf_structure_struct_tree_root_key()],
    PdfIndirect(structure_root_number),
  )
}

///|
fn pdf_image_title_for_subformat(
  subformat : PdfUASubformat?,
  title : String?,
) -> String raise @core.PdfError {
  match (subformat, title) {
    (Some(_), Some(title)) => title
    (Some(_), None) => raise TitleExpected
    (None, Some(title)) => title
    (None, None) => ""
  }
}

///|
fn pdf_image_content_ops(
  structinfo : Bool,
  width : Int,
  height : Int,
) -> Array[@content.PdfContentOp] {
  let output : Array[@content.PdfContentOp] = Array(
    capacity=if structinfo { 4 } else { 2 },
  )
  if structinfo {
    output.push(
      Op_BDC(
        pdf_image_figure_name(),
        PdfDictionary([(pdf_image_mcid_key(), PdfInteger(0))]),
      ),
    )
  }
  output.push(
    Op_cm({
      ..@geometry.transform_identity_matrix,
      a: width.to_double(),
      d: height.to_double(),
    }),
  )
  output.push(Op_Do(pdf_image_i0_name()))
  if structinfo {
    output.push(Op_EMC)
  }
  output
}

///|
fn PdfDocument::pdf_image_add_image_page(
  self : PdfDocument,
  image : @syntax.PdfObject,
  process_struct_tree : Bool,
  subformat : PdfUASubformat?,
  title : String?,
) -> Unit raise @core.PdfError {
  let title = pdf_image_title_for_subformat(subformat, title)
  let structinfo = process_struct_tree || subformat != None
  let width = self.pdf_image_lookup_int_or_else(
    pdf_image_width_key(),
    pdf_image_w_key(),
    image,
  )
  let height = self.pdf_image_lookup_int_or_else(
    pdf_image_height_key(),
    pdf_image_h_key(),
    image,
  )
  let image_number = self.add_object(image)
  let page = @page.PdfPage::{
    content: [
      pdf_content_stream_of_ops(
        pdf_image_content_ops(structinfo, width, height),
      ),
    ],
    mediabox: PdfArray([
      PdfReal(0.0),
      PdfReal(0.0),
      PdfReal(width.to_double()),
      PdfReal(height.to_double()),
    ]),
    resources: PdfDictionary([
      (
        pdf_image_xobject_key(),
        PdfDictionary([(pdf_image_i0_name(), PdfIndirect(image_number))]),
      ),
    ]),
    rotate: Rotate0,
    rest: if structinfo {
      PdfDictionary([(pdf_image_struct_parents_key(), PdfInteger(1))])
    } else {
      PdfDictionary([])
    },
  }
  let page_root = self.add_pagetree([page])
  ignore(self.add_root(page_root, []))
  if structinfo {
    self.pdf_image_add_structure_tree(subformat, title)
  }
  match subformat {
    Some(subformat) => self.pdf_ua_apply_subformat_metadata(subformat, title)
    None => ()
  }
}

///|
/// Build a single-page PDF document that draws `image` at its natural size.
pub fn pdf_image_document_of_object(
  image : @syntax.PdfObject,
  process_struct_tree? : Bool = false,
  subformat? : PdfUASubformat? = None,
  title? : String? = None,
) -> PdfDocument raise @core.PdfError {
  let document = pdf_document_empty()
  document.pdf_image_add_image_page(
    image, process_struct_tree, subformat, title,
  )
  document
}

///|
/// Compatibility wrapper matching cpdfimage's `image_of_input`.
///
/// The input side is represented by a byte view instead of `Pdfio.input`;
/// `object_of_data` receives the document so PNG masks and similar side objects
/// can be added through the same document before the page is built.
pub fn pdf_image_of_input(
  data : BytesView,
  object_of_data : (PdfDocument, BytesView) -> (
    @syntax.PdfObject,
    Array[(Int, @syntax.PdfObject)],
  ) raise @core.PdfError,
  process_struct_tree? : Bool = false,
  subformat? : PdfUASubformat? = None,
  title? : String? = None,
) -> PdfDocument raise @core.PdfError {
  let document = pdf_document_empty()
  let (image, extra_objects) = object_of_data(document, data)
  for extra in extra_objects {
    document.add_object_given_number(extra.0, extra.1)
  }
  document.pdf_image_add_image_page(
    image, process_struct_tree, subformat, title,
  )
  document
}

///|
/// Build a single-page PDF document from JPEG data.
pub fn pdf_image_document_of_jpeg_data(
  data : BytesView,
  process_struct_tree? : Bool = false,
  subformat? : PdfUASubformat? = None,
  title? : String? = None,
) -> PdfDocument raise @core.PdfError {
  pdf_image_document_of_object(
    pdf_image_object_of_jpeg_data(data),
    process_struct_tree~,
    subformat~,
    title~,
  )
}

///|
/// Build a single-page PDF document from JPEG2000 data.
pub fn pdf_image_document_of_jpeg2000_data(
  data : BytesView,
  process_struct_tree? : Bool = false,
  subformat? : PdfUASubformat? = None,
  title? : String? = None,
) -> PdfDocument raise @core.PdfError {
  pdf_image_document_of_object(
    pdf_image_object_of_jpeg2000_data(data),
    process_struct_tree~,
    subformat~,
    title~,
  )
}

///|
/// Build a single-page PDF document from PNG data.
pub fn pdf_image_document_of_png_data(
  data : BytesView,
  process_struct_tree? : Bool = false,
  subformat? : PdfUASubformat? = None,
  title? : String? = None,
) -> PdfDocument raise @core.PdfError {
  let document = pdf_document_empty()
  let image = document.pdf_image_object_of_png_data(data)
  document.pdf_image_add_image_page(
    image, process_struct_tree, subformat, title,
  )
  document
}

///|
/// Build a single-page PDF document from JBIG2 data.
pub fn pdf_image_document_of_jbig2_data(
  data : BytesView,
  global? : BytesView? = None,
  process_struct_tree? : Bool = false,
  subformat? : PdfUASubformat? = None,
  title? : String? = None,
) -> PdfDocument raise @core.PdfError {
  let document = pdf_document_empty()
  let (image, extra_objects) = pdf_image_object_of_jbig2_data(data, global~)
  for extra in extra_objects {
    document.add_object_given_number(extra.0, extra.1)
  }
  document.pdf_image_add_image_page(
    image, process_struct_tree, subformat, title,
  )
  document
}