///|
fn pdf_content_descriptor_extra_metrics(
  descriptor : PdfFontDescriptor,
) -> (Double, Double) {
  let bbox = descriptor.fontbbox
  if bbox.min_x == 0.0 &&
    bbox.min_y == 0.0 &&
    bbox.max_x == 0.0 &&
    bbox.max_y == 0.0 {
    (descriptor.ascent, descriptor.descent)
  } else {
    (bbox.min_y, bbox.max_y)
  }
}

///|
fn pdf_content_afm_header_value(
  data : @afm.PdfAfmData,
  key : String,
) -> BytesView? {
  let key_bytes = @ascii.encode(key)
  for entry in data.headers {
    if @afm.pdf_afm_view_equals(entry.0, key_bytes) {
      break Some(entry.1)
    }
  } nobreak {
    None
  }
}

///|
fn pdf_content_afm_parse_int(value : BytesView) -> Int? {
  let parsed : Result[Int, Error] = try @afm.pdf_afm_parse_int(value) catch {
    err => Err(err)
  } noraise {
    value => Ok(value)
  }
  match parsed {
    Ok(number) => Some(number)
    Err(_) => None
  }
}

///|
fn pdf_content_afm_header_number(
  data : @afm.PdfAfmData,
  key : String,
) -> Double {
  match pdf_content_afm_header_value(data, key) {
    Some(value) => {
      let tokens = @core.pdf_split_whitespace_tokens(value)
      if tokens.length() == 1 {
        let parsed : Result[Double, Error] = try
          @string.parse_double(@ascii.decode_lossy(tokens[0]))
        catch {
          err => Err(err)
        } noraise {
          value => Ok(value)
        }
        match parsed {
          Ok(number) => number
          Err(_) => 0.0
        }
      } else {
        0.0
      }
    }
    None => 0.0
  }
}

///|
fn pdf_content_standard_font_bbox_metrics(
  data : @afm.PdfAfmData,
) -> (Double, Double) {
  match pdf_content_afm_header_value(data, "FontBBox") {
    Some(value) => {
      let tokens = @core.pdf_split_whitespace_tokens(value)
      if tokens.length() == 4 {
        match
          (
            pdf_content_afm_parse_int(tokens[1]),
            pdf_content_afm_parse_int(tokens[3]),
          ) {
          (Some(y0), Some(y1)) =>
            if y0 < y1 {
              (y0.to_double(), y1.to_double())
            } else {
              (y1.to_double(), y0.to_double())
            }
          _ => (0.0, 0.0)
        }
      } else {
        (0.0, 0.0)
      }
    }
    None => (0.0, 0.0)
  }
}

///|
fn pdf_content_standard_font_extra_metrics(
  font : PdfStandardFont,
) -> (Double, Double) {
  let data = font.afm_data()
  let bbox_metrics = pdf_content_standard_font_bbox_metrics(data)
  if bbox_metrics.0 == 0.0 && bbox_metrics.1 == 0.0 {
    (
      pdf_content_afm_header_number(data, "Ascender"),
      pdf_content_afm_header_number(data, "Descender"),
    )
  } else {
    bbox_metrics
  }
}

///|
/// Return cpdfcontent's extra vertical metrics for text bounding boxes.
///
/// Type 3 fonts transform their font bbox through the font matrix. Other
/// described simple fonts and CID fonts use the descriptor bbox when present,
/// falling back to ascent/descent only for a zero bbox. Standard fonts read the
/// AFM `FontBBox` header, with `Ascender`/`Descender` as the zero-bbox fallback.
pub fn PdfFont::content_extra_metrics(self : PdfFont) -> (Double, Double) {
  match self {
    PdfFontSimple({ fonttype: PdfType3(data), .. }) => {
      let top = data.fontmatrix.apply(
        @geometry.point2(0.0, data.fontbbox.max_y),
      )
      let bottom = data.fontmatrix.apply(
        @geometry.point2(0.0, data.fontbbox.min_y),
      )
      (top.y, bottom.y)
    }
    PdfFontSimple({ fontdescriptor: Some(descriptor), .. }) =>
      pdf_content_descriptor_extra_metrics(descriptor)
    PdfFontCIDKeyed({ composite: { cid_fontdescriptor: descriptor, .. }, .. }) =>
      pdf_content_descriptor_extra_metrics(descriptor)
    PdfFontStandard({ font, .. }) =>
      pdf_content_standard_font_extra_metrics(font)
    PdfFontSimple(_) => (0.0, 0.0)
  }
}

///|
/// Compatibility wrapper for `PdfFont::content_extra_metrics`.
pub fn pdf_content_extra_metrics(font : PdfFont) -> (Double, Double) {
  font.content_extra_metrics()
}

///|
/// Return whether cpdfcontent treats this font as vertical text.
///
/// This intentionally mirrors cpdfcontent's narrow `vertical` helper: only the
/// predefined `/Identity-V` CMap is vertical among predefined maps, and parsed
/// external CMaps are vertical only when their `wmode` is `1`.
pub fn PdfFont::content_is_vertical(self : PdfFont) -> Bool {
  match self {
    PdfFontCIDKeyed({ encoding: PdfPredefinedCMap(name), .. }) =>
      name == pdf_text_identity_v_name
    PdfFontCIDKeyed(
      { encoding: PdfExternalCMap(_, { wmode: Some(1), .. }), .. }
    ) => true
    _ => false
  }
}

///|
/// Compatibility wrapper for `PdfFont::content_is_vertical`.
pub fn pdf_content_is_vertical(font : PdfFont) -> Bool {
  font.content_is_vertical()
}