///|
let pdf_page_marks_allowance : Double = 9.0

///|
fn PdfDocument::pdf_page_marks_rectangle(
  self : PdfDocument,
  page : @page.PdfPage,
  box_name : @core.PdfName,
) -> @geometry.PdfRectangle? raise @core.PdfError {
  let object = if box_name == pdf_page_mediabox_key() {
    Some(page.mediabox)
  } else {
    self.lookup_direct(box_name, page.rest)
  }
  match object {
    Some(PdfArray([_, _, _, _]) as rectangle) =>
      Some(self.parse_rectangle(rectangle))
    _ => None
  }
}

///|
fn PdfDocument::pdf_page_show_box_ops(
  self : PdfDocument,
  page : @page.PdfPage,
  colour : (Double, Double, Double),
  dash_on : Double,
  dash_off : Double,
  box_name : @core.PdfName,
) -> Array[@content.PdfContentOp] raise @core.PdfError {
  match self.pdf_page_marks_rectangle(page, box_name) {
    Some(bounds) => {
      let dash = if dash_on == 0.0 && dash_off == 0.0 {
        []
      } else {
        [dash_on, dash_off]
      }
      [
        Op_q,
        Op_RG(colour.0 / 255.0, colour.1 / 255.0, colour.2 / 255.0),
        Op_w(1.0),
        Op_d(dash, 0.0),
        Op_re(
          bounds.min_x,
          bounds.min_y,
          bounds.max_x - bounds.min_x,
          bounds.max_y - bounds.min_y,
        ),
        Op_S,
        Op_Q,
      ]
    }
    None => []
  }
}

///|
fn PdfDocument::pdf_page_show_boxes_ops(
  self : PdfDocument,
  page : @page.PdfPage,
) -> Array[@content.PdfContentOp] raise @core.PdfError {
  let ops : Array[@content.PdfContentOp] = [
    @content.pdf_content_begin_artifact(),
  ]
  for
    op in self.pdf_page_show_box_ops(
      page,
      (255.0, 0.0, 0.0),
      0.0,
      0.0,
      pdf_page_mediabox_key(),
    ) {
    ops.push(op)
  }
  for
    op in self.pdf_page_show_box_ops(
      page,
      (0.0, 255.0, 0.0),
      7.0,
      7.0,
      pdf_page_cropbox_key(),
    ) {
    ops.push(op)
  }
  for
    op in self.pdf_page_show_box_ops(
      page,
      (0.0, 0.0, 255.0),
      5.0,
      5.0,
      pdf_page_artbox_key(),
    ) {
    ops.push(op)
  }
  for
    op in self.pdf_page_show_box_ops(
      page,
      (255.0, 150.0, 0.0),
      3.0,
      3.0,
      pdf_page_trimbox_key(),
    ) {
    ops.push(op)
  }
  for
    op in self.pdf_page_show_box_ops(
      page,
      (255.0, 9.0, 147.0),
      2.0,
      2.0,
      pdf_page_bleedbox_key(),
    ) {
    ops.push(op)
  }
  ops.push(@content.pdf_content_end_artifact())
  ops
}

///|
fn pdf_page_trim_marks_push_line(
  ops : Array[@content.PdfContentOp],
  x0 : Double,
  y0 : Double,
  x1 : Double,
  y1 : Double,
) -> Unit {
  ops.push(Op_m(x0, y0))
  ops.push(Op_l(x1, y1))
  ops.push(Op_s)
}

///|
fn pdf_page_trim_marks_ops(
  trim : @geometry.PdfRectangle,
  media : @geometry.PdfRectangle,
) -> Array[@content.PdfContentOp] {
  let ops : Array[@content.PdfContentOp] = [
    Op_q,
    Op_K(1.0, 1.0, 1.0, 1.0),
    Op_w(1.0),
    @content.pdf_content_begin_artifact(),
  ]
  pdf_page_trim_marks_push_line(
    ops,
    media.min_x,
    trim.max_y,
    trim.min_x - pdf_page_marks_allowance,
    trim.max_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.min_x,
    trim.max_y + pdf_page_marks_allowance,
    trim.min_x,
    media.max_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.max_x + pdf_page_marks_allowance,
    trim.max_y,
    media.max_x,
    trim.max_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.max_x,
    trim.max_y + pdf_page_marks_allowance,
    trim.max_x,
    media.max_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.max_x + pdf_page_marks_allowance,
    trim.min_y,
    media.max_x,
    trim.min_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.max_x,
    trim.min_y - pdf_page_marks_allowance,
    trim.max_x,
    media.min_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.min_x - pdf_page_marks_allowance,
    trim.min_y,
    media.min_x,
    trim.min_y,
  )
  pdf_page_trim_marks_push_line(
    ops,
    trim.min_x,
    trim.min_y - pdf_page_marks_allowance,
    trim.min_x,
    media.min_y,
  )
  ops.push(@content.pdf_content_end_artifact())
  ops.push(Op_Q)
  ops
}

///|
fn PdfDocument::pdf_page_trim_marks_page(
  self : PdfDocument,
  page : @page.PdfPage,
  fast : Bool,
) -> @page.PdfPage raise @core.PdfError {
  match
    (
      self.pdf_page_marks_rectangle(page, pdf_page_trimbox_key()),
      self.pdf_page_marks_rectangle(page, pdf_page_mediabox_key()),
    ) {
    (Some(trim), Some(media)) =>
      self.postpend_operators(page, pdf_page_trim_marks_ops(trim, media), fast~)
    _ => page
  }
}

///|
/// Draw coloured outlines for the standard page boxes on selected pages.
///
/// This ports cpdf's `show_boxes`: `/MediaBox`, `/CropBox`, `/ArtBox`,
/// `/TrimBox`, and `/BleedBox` are appended as artifact-marked stroked
/// rectangles when present.
pub fn PdfDocument::show_page_boxes(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  self.pdf_page_box_rewrite_pages(page_numbers, "show_page_boxes", (_, _, page) => {
    self.postpend_operators(page, self.pdf_page_show_boxes_ops(page), fast~)
  })
}

///|
/// Compatibility wrapper for `PdfDocument::show_page_boxes`.
pub fn pdf_show_boxes(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  document.show_page_boxes(page_numbers, fast~)
}

///|
/// Add trim marks around `/TrimBox` on selected pages.
///
/// Pages without a valid `/TrimBox` are left unchanged, matching cpdf's
/// `trim_marks`.
pub fn PdfDocument::trim_marks(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  self.pdf_page_box_rewrite_pages(page_numbers, "trim_marks", (_, _, page) => {
    self.pdf_page_trim_marks_page(page, fast)
  })
}

///|
/// Compatibility wrapper for `PdfDocument::trim_marks`.
pub fn pdf_trim_marks(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  document.trim_marks(page_numbers, fast~)
}