///|
// Fonts of the caller's own, beside the bundled ones.
//
// A `FontRegistry` is an explicit value: a frontend creates one per
// document (or per set of documents sharing a font catalog), registers its
// faces, and hands the same value to whatever measures text and to the
// renderer that embeds the fonts (`@pdf.RenderOptions`). Nothing about it
// is process-wide, so two documents with different faces under the same
// family name cannot see each other's.
//
// Its lifetime has two phases. While it is *open*, faces can be added. The
// first lookup of any kind *seals* it, and from then on registration
// raises `FontRegistryError::Sealed`. That is what keeps measurement and
// rendering in step: once layout has asked for a face, the answer can no
// longer change underneath a laid-out model — a bold face registered after
// the layout measured bold with the regular fallback would otherwise be
// drawn at widths nobody measured. Registering the same family and style
// twice raises `FontRegistryError::Duplicate` for the same reason; nothing
// is ever replaced.
//
// A registry is a plain mutable value with no locking. Register from one
// task; once sealed it is read-only and may be shared freely.

///|
/// Why a `FontRegistry` refused a face.
pub(all) suberror FontRegistryError {
  /// The registry was already consulted, so its answers are fixed. The
  /// payload names the face (`family|bold|italic`) that came too late.
  Sealed(String)
  /// A face of this family and style (`family|bold|italic`) is already
  /// registered.
  Duplicate(String)
  /// `register_standard` was given a name that is not one of the twelve
  /// Latin standard fonts it supports (`standard_fonts`): a misspelling,
  /// an alias such as `Arial`, or one of the symbolic `Symbol` and
  /// `ZapfDingbats`, whose built-in encodings are not WinAnsi. The payload
  /// is the name.
  UnsupportedStandard(String)
} derive(Eq)

///|
pub extend FontRegistryError with Eq::{not_equal, equal}

///|
pub extend FontRegistryError with Show::{to_string, output}

///|
pub impl Show for FontRegistryError with fn output(self, logger) {
  match self {
    Sealed(face) =>
      logger.write_string(
        "font registry already in use; cannot register \{face}",
      )
    Duplicate(face) => logger.write_string("font already registered: \{face}")
    UnsupportedStandard("Symbol" | "ZapfDingbats" as standard) =>
      logger.write_string(
        "unsupported PDF standard font: \{standard} (a symbolic font, not WinAnsi-encoded)",
      )
    UnsupportedStandard(standard) => {
      let names = standard_fonts()
      let expected = names[:names.length() - 1].join(", ") +
        " or " +
        names[names.length() - 1]
      logger.write_string(
        "not a PDF standard font: \{standard} (expected \{expected})",
      )
    }
  }
}

///|
/// The PDF standard fonts `FontRegistry::register_standard` accepts, by
/// PostScript name: the twelve Latin faces of Helvetica, Times and Courier,
/// all declared with `/WinAnsiEncoding`. The two symbolic standard fonts,
/// `Symbol` and `ZapfDingbats`, are not among them: their text would be
/// encoded in each font's own built-in encoding (Prawn omits `/Encoding`
/// for them), which the renderer does not implement.
pub fn standard_fonts() -> Array[String] {
  [
    "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique",
    "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic", "Courier", "Courier-Bold",
    "Courier-Oblique", "Courier-BoldOblique",
  ]
}

///|
/// One registered face: the metrics layout measures with and how a PDF
/// draws it. A face registered from an sfnt (`register`) takes both its
/// metrics and its program from the same bytes, so that they cannot
/// disagree about glyph ids or widths. A standard font
/// (`register_standard`) has no program: the viewer draws it with its own
/// copy of the font, whose widths the metrics must be (see
/// `register_standard`).
pub struct RegisteredFace {
  metrics : FaceMetrics
  /// The complete sfnt to embed, or None when the bytes carry no TrueType
  /// outlines (no `glyf`/`loca`, as in a metrics-only sfnt or a CFF-flavoured
  /// OpenType font). A face without a program is declared in a PDF by name
  /// and drawn by whatever the viewer substitutes.
  program : Bytes?
  /// The PDF standard font the face is, when it is one: one of the twelve
  /// Latin faces of `standard_fonts` (`Helvetica`, `Times-Bold`, ...),
  /// declared by that name as a Type1 font with `/WinAnsiEncoding` and no
  /// program.
  standard : String?
}

///|
/// A set of faces registered by the caller, consulted before the bundled
/// ones. See the module comment for its open/sealed lifetime.
pub struct FontRegistry {
  priv faces : Map[String, RegisteredFace]
  priv mut closed : Bool
}

///|
/// An empty, open registry. With nothing registered it resolves exactly
/// as the package-level functions do: bundled faces only.
pub fn FontRegistry::new() -> FontRegistry {
  { faces: Map([]), closed: false, }
}

///|
#deprecated("implicit trait-method promotion is being removed; call via the trait")
pub extend FontRegistry with Default::{default}

///|
/// Same as `FontRegistry::new`.
pub impl Default for FontRegistry with fn default() {
  FontRegistry::new()
}

///|
/// Register a face from a complete, uncompressed sfnt (a `.ttf` file's
/// bytes) and return its metrics. Metrics and program are registered
/// together, from the same bytes.
///
/// A registered family resolves to itself (matched exactly, case and all)
/// and shadows a bundled family of the same name — Noto Sans SC, the CJK
/// fallback, included (see `cjk_fallback`). Raises
/// `FontRegistryError` when the registry is sealed or the face is already
/// registered, and a parse error when the bytes are not a usable sfnt.
pub fn FontRegistry::register(
  self : FontRegistry,
  family : String,
  sfnt : Bytes,
  bold? : Bool = false,
  italic? : Bool = false,
) -> FaceMetrics raise {
  let key = face_key(family, bold, italic)
  if self.closed {
    raise FontRegistryError::Sealed(key)
  }
  if self.faces.contains(key) {
    raise FontRegistryError::Duplicate(key)
  }
  let metrics = parse_sfnt(family, bold, italic, sfnt)
  let program = if @pdflite.pdf_truetype_table(sfnt, "glyf") is Some(_) &&
    @pdflite.pdf_truetype_table(sfnt, "loca") is Some(_) {
    Some(sfnt)
  } else {
    None
  }
  self.faces[key] = { metrics, program, standard: None, }
  metrics
}

///|
/// Register a PDF standard font under `family` and return the metrics
/// layout will measure it with. `standard` is its PostScript name, one of
/// the twelve Latin faces of `standard_fonts` (`Helvetica`,
/// `Helvetica-Bold`, `Times-Roman`, `Courier-Oblique`, ...); `metrics` are
/// its metrics, built with `FaceMetrics::new` from the font's AFM.
///
/// A PDF declares the font by name as a Type1 font with `/WinAnsiEncoding`
/// and embeds nothing, so a viewer draws it with its own copy of the font
/// and advances each glyph by that copy's width. Its text is encoded over
/// the whole of WinAnsi (0x80–0x9F included: `€`, `’`, `™`, ...);
/// characters WinAnsi lacks are dropped, their advances kept. For glyphs to
/// land where the layout put them, `metrics` must measure each character
/// WinAnsi encodes with the font's AFM width: conventionally a `cmap` from
/// each such character to its WinAnsi code and `advances` indexed by code,
/// at 1000 units per em. Kerning is not part of the metrics; a caller that
/// kerns puts the kerned advances in its runs (`GlyphRun::advances_pt`) and
/// the renderer shows the difference as a displacement.
///
/// Like `register`, the face is registered under `family` and the style
/// given: the returned metrics are `metrics` with that family, bold and
/// italic (the tables unchanged), so that a run laid out with them names
/// the registered family and is drawn in this face — not in whatever
/// `metrics.family` (say `Helvetica`) would otherwise resolve to.
///
/// Raises `FontRegistryError` when the registry is sealed, the face is
/// already registered, or `standard` is not a supported name
/// (`UnsupportedStandard`).
pub fn FontRegistry::register_standard(
  self : FontRegistry,
  family : String,
  metrics : FaceMetrics,
  standard~ : String,
  bold? : Bool = false,
  italic? : Bool = false,
) -> FaceMetrics raise FontRegistryError {
  let key = face_key(family, bold, italic)
  if self.closed {
    raise Sealed(key)
  }
  if self.faces.contains(key) {
    raise Duplicate(key)
  }
  if !standard_fonts().contains(standard) {
    raise UnsupportedStandard(standard)
  }
  // the face's identity is its registration, as `register` gives it; the
  // tables are immutable and shared
  let metrics = { ..metrics, family, bold, italic, }
  self.faces[key] = { metrics, program: None, standard: Some(standard), }
  metrics
}

///|
/// Stop accepting faces. Every lookup does this implicitly; calling it
/// makes the point explicit (before sharing the registry, say).
pub fn FontRegistry::seal(self : FontRegistry) -> Unit {
  self.closed = true
}

///|
/// Whether the registry has stopped accepting faces.
pub fn FontRegistry::is_sealed(self : FontRegistry) -> Bool {
  self.closed
}

///|
/// Whether any face of `family` (matched exactly) is registered. Seals.
pub fn FontRegistry::is_registered(
  self : FontRegistry,
  family : String,
) -> Bool {
  self.closed = true
  self.has_family(family)
}

///|
fn FontRegistry::has_family(self : FontRegistry, family : String) -> Bool {
  self.faces.contains(face_key(family, false, false)) ||
  self.faces.contains(face_key(family, true, false)) ||
  self.faces.contains(face_key(family, false, true)) ||
  self.faces.contains(face_key(family, true, true))
}

///|
/// `resolve_family`, with registered families passed through unchanged.
/// Seals.
pub fn FontRegistry::resolve_family(
  self : FontRegistry,
  name : String,
) -> String {
  self.closed = true
  if self.has_family(name) {
    name
  } else {
    resolve_family(name)
  }
}

///|
/// The registered face that stands for `family` in the requested style, or
/// None when no face of `family` is registered. Seals.
///
/// A missing style falls back within the family, never outside it: the
/// exact face, then the face without italic, then without bold, then the
/// regular face, then whichever face the family does have. Metrics and
/// program always come from that one face, so a document asking for a
/// style the catalog lacks is measured and drawn in the same substitute.
pub fn FontRegistry::registered_face(
  self : FontRegistry,
  family : String,
  bold? : Bool = false,
  italic? : Bool = false,
) -> RegisteredFace? {
  self.closed = true
  let candidates = [
    (bold, italic),
    (bold, false),
    (false, italic),
    (false, false),
    (true, false),
    (false, true),
    (true, true),
  ]
  for candidate in candidates {
    match self.faces.get(face_key(family, candidate.0, candidate.1)) {
      Some(found) => return Some(found)
      None => ()
    }
  }
  None
}

///|
/// The metrics for a family name as `resolve_family` returns it: the
/// registered face (see `registered_face` for style fallback), else the
/// bundled one (see the package-level `face`). Seals.
pub fn FontRegistry::face(
  self : FontRegistry,
  family : String,
  bold? : Bool = false,
  italic? : Bool = false,
) -> FaceMetrics {
  match self.registered_face(family, bold~, italic~) {
    Some(registered) => registered.metrics
    None => face(family, bold~, italic~)
  }
}

///|
/// The face for CJK codepoints the requested face lacks, as this registry
/// resolves it: the fallback family (Noto Sans SC) through `face`, so a
/// registered face of that family is the fallback — style fallback
/// included — and shadows the bundled one entirely. Seals.
///
/// This is exactly the face a renderer resolves for a run the layout
/// placed in the fallback family with this style, so a fallback character
/// is measured with the metrics it is drawn with. When the registered
/// fallback lacks a character, the bundled one is *not* consulted: a run
/// cannot name it apart from the registered face. See `uncovered`.
pub fn FontRegistry::cjk_fallback(
  self : FontRegistry,
  bold? : Bool = false,
  italic? : Bool = false,
) -> FaceMetrics {
  let family = cjk_fallback().family
  self.face(self.resolve_family(family), bold~, italic~)
}

///|
/// The package-level `uncovered`, asked of this registry's faces: the
/// requested face as `face` resolves it, then `cjk_fallback` in the same
/// style — the two faces layout measures with. Seals.
pub fn FontRegistry::uncovered(
  self : FontRegistry,
  text : String,
  family : String,
  bold? : Bool = false,
  italic? : Bool = false,
) -> Array[(Int, Int)] {
  uncovered_by(
    text,
    self.face(self.resolve_family(family), bold~, italic~),
    self.cjk_fallback(bold~, italic~),
  )
}