///|
/// Text-state fields needed by cpdfcontent's `process_tj` glyph placement.
pub(all) struct PdfContentTextLayout {
  font_size : Double
  horizontal_scaling : Double
  character_spacing : Double
  word_spacing : Double
  rise : Double
  ctm : @geometry.TransformMatrix
  text_matrix : @geometry.TransformMatrix
} derive(Debug, Eq, ToJson)

///|
/// Geometry emitted for one glyph by cpdfcontent-style text placement.
pub(all) struct PdfContentTextGlyphPlacement {
  charcode : Int
  text_matrix : @geometry.TransformMatrix
  width : Double
  pvx : Double
  pvy : Double
  advance_x : Double
  advance_y : Double
  bounding_box : PdfContentBoundingBox
} derive(Debug, Eq, ToJson)

///|
/// Result of placing one raw PDF text string with cpdfcontent text semantics.
pub(all) struct PdfContentTextLayoutResult {
  glyphs : Array[PdfContentTextGlyphPlacement]
  text_matrix : @geometry.TransformMatrix
} derive(Debug, Eq, ToJson)

///|
fn pdf_content_text_divisor(font : PdfFont) -> Double {
  match font {
    PdfFontSimple({ fonttype: PdfType3(_), .. }) => 1.0
    _ => 1000.0
  }
}

///|
fn pdf_content_text_render_matrix(
  layout : PdfContentTextLayout,
) -> @geometry.TransformMatrix {
  let parameters = @geometry.TransformMatrix::{
    a: layout.font_size * layout.horizontal_scaling,
    b: 0.0,
    c: 0.0,
    d: layout.font_size,
    e: 0.0,
    f: layout.rise,
  }
  @geometry.transform_matrix_compose(
    layout.ctm,
    @geometry.transform_matrix_compose(layout.text_matrix, parameters),
  )
}

///|
fn pdf_content_text_bounding_box(
  matrix : @geometry.TransformMatrix,
  width : Double,
  pvx : Double,
  pvy : Double,
  ascent : Double,
  descent : Double,
) -> PdfContentBoundingBox {
  let p0 = matrix.apply(@geometry.point2(pvx, descent - pvy))
  let p1 = matrix.apply(@geometry.point2(pvx, ascent - pvy))
  let p2 = matrix.apply(@geometry.point2(width + pvx, ascent - pvy))
  let p3 = matrix.apply(@geometry.point2(width + pvx, descent - pvy))
  {
    x0: p0.x,
    y0: p0.y,
    x1: p1.x,
    y1: p1.y,
    x2: p2.x,
    y2: p2.y,
    x3: p3.x,
    y3: p3.y,
  }
}

///|
fn pdf_content_text_advance(
  vertical : Bool,
  charcode : Int,
  width : Double,
  layout : PdfContentTextLayout,
) -> (Double, Double) {
  if vertical {
    (
      0.0,
      width * layout.font_size + layout.character_spacing + layout.word_spacing,
    )
  } else {
    let word_spacing = if charcode == 32 { layout.word_spacing } else { 0.0 }
    (
      (width * layout.font_size + layout.character_spacing + word_spacing) *
      layout.horizontal_scaling,
      0.0,
    )
  }
}

///|
/// Place one raw PDF text string as cpdfcontent's `process_tj` does.
///
/// The returned glyph widths and placement vectors are normalized through the
/// same Type 3-versus-1000-unit divisor as the source function. The result's
/// `text_matrix` is the matrix after all glyph advances have been applied.
pub fn PdfFont::content_text_layout_of_bytes(
  self : PdfFont,
  text : BytesView,
  layout : PdfContentTextLayout,
) -> PdfContentTextLayoutResult {
  let divisor = pdf_content_text_divisor(self)
  let extra_metrics = self.content_extra_metrics()
  let ascent = extra_metrics.0 / divisor
  let descent = extra_metrics.1 / divisor
  let vertical = self.content_is_vertical()
  let charcodes = self.content_charcodes_of_bytes(text)
  let glyphs : Array[PdfContentTextGlyphPlacement] = Array(
    capacity=charcodes.length(),
  )
  let mut text_matrix = layout.text_matrix
  for charcode in charcodes {
    let current_layout = { ..layout, text_matrix, }
    let width_data = self.content_width_of_charcode(charcode)
    let width = width_data.0 / divisor
    let pvx = width_data.1 / divisor
    let pvy = width_data.2 / divisor
    let bounding_box = pdf_content_text_bounding_box(
      pdf_content_text_render_matrix(current_layout),
      width,
      pvx,
      pvy,
      ascent,
      descent,
    )
    let advance = pdf_content_text_advance(
      vertical, charcode, width, current_layout,
    )
    glyphs.push({
      charcode,
      text_matrix,
      width,
      pvx,
      pvy,
      advance_x: advance.0,
      advance_y: advance.1,
      bounding_box,
    })
    text_matrix = @geometry.transform_matrix_compose(
      text_matrix,
      @geometry.transform_translate(advance.0, advance.1),
    )
  }
  { glyphs, text_matrix, }
}

///|
/// Compatibility wrapper for `PdfFont::content_text_layout_of_bytes`.
pub fn pdf_content_text_layout_of_bytes(
  font : PdfFont,
  text : BytesView,
  layout : PdfContentTextLayout,
) -> PdfContentTextLayoutResult {
  font.content_text_layout_of_bytes(text, layout)
}

///|
fn pdf_content_text_tj_adjustment(
  vertical : Bool,
  value : Double,
  layout : PdfContentTextLayout,
) -> (Double, Double) {
  if vertical {
    (0.0, -value / 1000.0 * layout.font_size)
  } else {
    (-value / 1000.0 * layout.horizontal_scaling * layout.font_size, 0.0)
  }
}

///|
/// Place a normalized PDF `TJ` array as cpdfcontent's `process_capital_tj` does.
///
/// `PdfString` operands are delegated to `content_text_layout_of_bytes`.
/// `PdfReal` operands adjust the current text matrix. Other operands are
/// ignored, matching the source function and the local content parser's
/// normalized `TJ` array shape.
pub fn PdfFont::content_text_layout_of_tj_array(
  self : PdfFont,
  elements : ArrayView[@syntax.PdfObject],
  layout : PdfContentTextLayout,
) -> PdfContentTextLayoutResult {
  let glyphs : Array[PdfContentTextGlyphPlacement] = Array(
    capacity=elements.length(),
  )
  let vertical = self.content_is_vertical()
  let mut text_matrix = layout.text_matrix
  for element in elements {
    match element {
      PdfString(text) => {
        let result = self.content_text_layout_of_bytes(text, {
          ..layout,
          text_matrix,
        })
        for glyph in result.glyphs {
          glyphs.push(glyph)
        }
        text_matrix = result.text_matrix
      }
      PdfReal(value) | PdfExactReal(value) => {
        let advance = pdf_content_text_tj_adjustment(vertical, value, {
          ..layout,
          text_matrix,
        })
        text_matrix = @geometry.transform_matrix_compose(
          text_matrix,
          @geometry.transform_translate(advance.0, advance.1),
        )
      }
      _ => ()
    }
  }
  { glyphs, text_matrix, }
}

///|
/// Compatibility wrapper for `PdfFont::content_text_layout_of_tj_array`.
pub fn pdf_content_text_layout_of_tj_array(
  font : PdfFont,
  elements : ArrayView[@syntax.PdfObject],
  layout : PdfContentTextLayout,
) -> PdfContentTextLayoutResult {
  font.content_text_layout_of_tj_array(elements, layout)
}