///|
// The PDF standard fonts (Helvetica, Times, Courier), which Prawn uses
// when a theme names them without a font catalog entry (the base theme,
// and the Courier of a theme that does not extend the default): their AFM
// metrics (pdflite's copy of Adobe's), WinAnsi-encoded text, and nothing
// embedded (pagelayout declares them by name).

///|
/// Prawn's built-in families (`Font::AFM` via `font_families`): family →
/// the fonts of its normal, bold, italic and bold italic styles.
let standard_families : Array[(String, Array[String])] = [
  (
    "Helvetica",
    [
      "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique",
    ],
  ),
  (
    "Times-Roman",
    ["Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic"],
  ),
  (
    "Courier",
    ["Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique"],
  ),
]

///|
/// WinAnsi (`Encoding::WinAnsi::CHARACTERS`): the glyph name of each code
/// from 0x20, and the character it stands for.
let winansi_glyphs : Array[(String, Int)] = [
  ("space", 0x20),
  ("exclam", 0x21),
  ("quotedbl", 0x22),
  ("numbersign", 0x23),
  ("dollar", 0x24),
  ("percent", 0x25),
  ("ampersand", 0x26),
  ("quotesingle", 0x27),
  ("parenleft", 0x28),
  ("parenright", 0x29),
  ("asterisk", 0x2A),
  ("plus", 0x2B),
  ("comma", 0x2C),
  ("hyphen", 0x2D),
  ("period", 0x2E),
  ("slash", 0x2F),
  ("zero", 0x30),
  ("one", 0x31),
  ("two", 0x32),
  ("three", 0x33),
  ("four", 0x34),
  ("five", 0x35),
  ("six", 0x36),
  ("seven", 0x37),
  ("eight", 0x38),
  ("nine", 0x39),
  ("colon", 0x3A),
  ("semicolon", 0x3B),
  ("less", 0x3C),
  ("equal", 0x3D),
  ("greater", 0x3E),
  ("question", 0x3F),
  ("at", 0x40),
  ("A", 0x41),
  ("B", 0x42),
  ("C", 0x43),
  ("D", 0x44),
  ("E", 0x45),
  ("F", 0x46),
  ("G", 0x47),
  ("H", 0x48),
  ("I", 0x49),
  ("J", 0x4A),
  ("K", 0x4B),
  ("L", 0x4C),
  ("M", 0x4D),
  ("N", 0x4E),
  ("O", 0x4F),
  ("P", 0x50),
  ("Q", 0x51),
  ("R", 0x52),
  ("S", 0x53),
  ("T", 0x54),
  ("U", 0x55),
  ("V", 0x56),
  ("W", 0x57),
  ("X", 0x58),
  ("Y", 0x59),
  ("Z", 0x5A),
  ("bracketleft", 0x5B),
  ("backslash", 0x5C),
  ("bracketright", 0x5D),
  ("asciicircum", 0x5E),
  ("underscore", 0x5F),
  ("grave", 0x60),
  ("a", 0x61),
  ("b", 0x62),
  ("c", 0x63),
  ("d", 0x64),
  ("e", 0x65),
  ("f", 0x66),
  ("g", 0x67),
  ("h", 0x68),
  ("i", 0x69),
  ("j", 0x6A),
  ("k", 0x6B),
  ("l", 0x6C),
  ("m", 0x6D),
  ("n", 0x6E),
  ("o", 0x6F),
  ("p", 0x70),
  ("q", 0x71),
  ("r", 0x72),
  ("s", 0x73),
  ("t", 0x74),
  ("u", 0x75),
  ("v", 0x76),
  ("w", 0x77),
  ("x", 0x78),
  ("y", 0x79),
  ("z", 0x7A),
  ("braceleft", 0x7B),
  ("bar", 0x7C),
  ("braceright", 0x7D),
  ("asciitilde", 0x7E),
  (".notdef", 0),
  ("Euro", 0x20AC),
  (".notdef", 0),
  ("quotesinglbase", 0x201A),
  ("florin", 0x0192),
  ("quotedblbase", 0x201E),
  ("ellipsis", 0x2026),
  ("dagger", 0x2020),
  ("daggerdbl", 0x2021),
  ("circumflex", 0x02C6),
  ("perthousand", 0x2030),
  ("Scaron", 0x0160),
  ("guilsinglleft", 0x2039),
  ("OE", 0x0152),
  (".notdef", 0),
  ("Zcaron", 0x017D),
  (".notdef", 0),
  (".notdef", 0),
  ("quoteleft", 0x2018),
  ("quoteright", 0x2019),
  ("quotedblleft", 0x201C),
  ("quotedblright", 0x201D),
  ("bullet", 0x2022),
  ("endash", 0x2013),
  ("emdash", 0x2014),
  ("tilde", 0x02DC),
  ("trademark", 0x2122),
  ("scaron", 0x0161),
  ("guilsinglright", 0x203A),
  ("oe", 0x0153),
  (".notdef", 0),
  ("zcaron", 0x017E),
  ("Ydieresis", 0x0178),
  ("space", 0xA0),
  ("exclamdown", 0xA1),
  ("cent", 0xA2),
  ("sterling", 0xA3),
  ("currency", 0xA4),
  ("yen", 0xA5),
  ("brokenbar", 0xA6),
  ("section", 0xA7),
  ("dieresis", 0xA8),
  ("copyright", 0xA9),
  ("ordfeminine", 0xAA),
  ("guillemotleft", 0xAB),
  ("logicalnot", 0xAC),
  ("hyphen", 0xAD),
  ("registered", 0xAE),
  ("macron", 0xAF),
  ("degree", 0xB0),
  ("plusminus", 0xB1),
  ("twosuperior", 0xB2),
  ("threesuperior", 0xB3),
  ("acute", 0xB4),
  ("mu", 0xB5),
  ("paragraph", 0xB6),
  ("periodcentered", 0xB7),
  ("cedilla", 0xB8),
  ("onesuperior", 0xB9),
  ("ordmasculine", 0xBA),
  ("guillemotright", 0xBB),
  ("onequarter", 0xBC),
  ("onehalf", 0xBD),
  ("threequarters", 0xBE),
  ("questiondown", 0xBF),
  ("Agrave", 0xC0),
  ("Aacute", 0xC1),
  ("Acircumflex", 0xC2),
  ("Atilde", 0xC3),
  ("Adieresis", 0xC4),
  ("Aring", 0xC5),
  ("AE", 0xC6),
  ("Ccedilla", 0xC7),
  ("Egrave", 0xC8),
  ("Eacute", 0xC9),
  ("Ecircumflex", 0xCA),
  ("Edieresis", 0xCB),
  ("Igrave", 0xCC),
  ("Iacute", 0xCD),
  ("Icircumflex", 0xCE),
  ("Idieresis", 0xCF),
  ("Eth", 0xD0),
  ("Ntilde", 0xD1),
  ("Ograve", 0xD2),
  ("Oacute", 0xD3),
  ("Ocircumflex", 0xD4),
  ("Otilde", 0xD5),
  ("Odieresis", 0xD6),
  ("multiply", 0xD7),
  ("Oslash", 0xD8),
  ("Ugrave", 0xD9),
  ("Uacute", 0xDA),
  ("Ucircumflex", 0xDB),
  ("Udieresis", 0xDC),
  ("Yacute", 0xDD),
  ("Thorn", 0xDE),
  ("germandbls", 0xDF),
  ("agrave", 0xE0),
  ("aacute", 0xE1),
  ("acircumflex", 0xE2),
  ("atilde", 0xE3),
  ("adieresis", 0xE4),
  ("aring", 0xE5),
  ("ae", 0xE6),
  ("ccedilla", 0xE7),
  ("egrave", 0xE8),
  ("eacute", 0xE9),
  ("ecircumflex", 0xEA),
  ("edieresis", 0xEB),
  ("igrave", 0xEC),
  ("iacute", 0xED),
  ("icircumflex", 0xEE),
  ("idieresis", 0xEF),
  ("eth", 0xF0),
  ("ntilde", 0xF1),
  ("ograve", 0xF2),
  ("oacute", 0xF3),
  ("ocircumflex", 0xF4),
  ("otilde", 0xF5),
  ("odieresis", 0xF6),
  ("divide", 0xF7),
  ("oslash", 0xF8),
  ("ugrave", 0xF9),
  ("uacute", 0xFA),
  ("ucircumflex", 0xFB),
  ("udieresis", 0xFC),
  ("yacute", 0xFD),
  ("thorn", 0xFE),
  ("ydieresis", 0xFF),
]

///|
/// The WinAnsi code of a character, when WinAnsi has it.
fn winansi_code_of(codepoint : Int) -> Int? {
  for i, g in winansi_glyphs {
    if g.1 == codepoint && g.1 != 0 {
      return Some(0x20 + i)
    }
  }
  None
}

///|
/// AFM text (ASCII) as a string.
fn ascii_text(bytes : Bytes) -> String {
  let sb = StringBuilder()
  for b in bytes {
    sb.write_char(b.to_int().unsafe_to_char())
  }
  sb.to_string()
}

///|
/// A header value of AFM data (`Ascender`, `FontBBox`, ...).
fn afm_header(data : @afm.PdfAfmData, key : String) -> String? {
  for h in data.headers {
    if ascii_text(h.0) == key {
      return Some(ascii_text(h.1))
    }
  }
  None
}

///|
/// Build the face of a standard font from its AFM data, registering it in
/// `registry` under `family` (Prawn's `Font::AFM`): glyph widths by WinAnsi
/// code, the kerning pairs of the glyphs WinAnsi encodes (a name WinAnsi
/// has twice, `space` and `hyphen`, kerns at its later code, as Prawn's
/// name table has it), ascender and descender, and the line gap the font
/// box leaves.
fn load_standard_face(
  registry : @fonts.FontRegistry,
  family : String,
  bold : Bool,
  italic : Bool,
  name : String,
) -> Face? raise {
  guard @pdflite.pdf_standard_font_of_name("/" + name) is Some(font) else {
    return None
  }
  let data = font.afm_data()
  // glyph name → width, and the encoded glyphs' names by code (the AFM
  // lists its encoded glyphs first, in code order)
  let widths : Map[String, Int] = Map([])
  for entry in data.char_name_widths {
    widths[ascii_text(entry.0)] = entry.1
  }
  let names_by_code : Map[Int, String] = Map([])
  for i, metric in data.char_metrics {
    if i < data.char_name_widths.length() {
      names_by_code[metric.0] = ascii_text(data.char_name_widths[i].0)
    }
  }
  let advances = Array::make(256, 0)
  let cmap : Map[Int, Int] = Map([])
  // the code of each glyph name (the later one for a name WinAnsi repeats)
  let code_of_name : Map[String, Int] = Map([])
  for i, glyph in winansi_glyphs {
    let code = 0x20 + i
    let (glyph_name, codepoint) = glyph
    if codepoint == 0 {
      continue
    }
    advances[code] = widths.get(glyph_name).unwrap_or(0)
    cmap[codepoint] = code
    code_of_name[glyph_name] = code
  }
  let kern : Map[Int, Double] = Map([])
  for entry in data.kerns {
    let (left, right, amount) = entry
    match (names_by_code.get(left), names_by_code.get(right)) {
      (Some(l), Some(r)) =>
        match (code_of_name.get(l), code_of_name.get(r)) {
          (Some(lc), Some(rc)) => kern[(lc << 16) | rc] = amount.to_double()
          _ => ()
        }
      _ => ()
    }
  }
  let ascender = afm_header(data, "Ascender")
    .map(s => @theme.ruby_string_to_i(s).to_int())
    .unwrap_or(718)
  let descender = afm_header(data, "Descender")
    .map(s => @theme.ruby_string_to_i(s).to_int())
    .unwrap_or(-207)
  let bbox = afm_header(data, "FontBBox")
    .unwrap_or("0 0 0 0")
    .split(" ")
    .filter(s => s != "")
    .map(s => @theme.ruby_string_to_i(s.to_owned()).to_int())
    .collect()
  let line_gap = if bbox.length() == 4 {
    bbox[3] - bbox[1] - (ascender - descender)
  } else {
    0
  }
  let metrics = @fonts.FaceMetrics::new(
    family~,
    bold~,
    italic~,
    units_per_em=1000,
    ascender~,
    descender~,
    line_gap~,
    advances~,
    cmap~,
  )
  // measure with the registered face: the one the renderer draws
  let metrics = registry.register_standard(
    family,
    metrics,
    standard=name,
    bold~,
    italic~,
  )
  Some({ metrics, ascender, descender, line_gap, kern, standard: true, })
}

///|
/// Prawn's `normalize_encoding` for AFM fonts, as asciidoctor-pdf patches
/// it: the text in WinAnsi, a few characters replaced by near equivalents
/// (`FALLBACK_CHARS`) and any other it lacks by `¬`.
fn winansi_text(text : String) -> String {
  let sb = StringBuilder()
  for c in text {
    let cp = c.to_int()
    match cp {
      0x200B => ()
      0x202F => sb.write_char('\u{a0}')
      0x2009 => sb.write_char(' ')
      0x2063 => sb.write_char('\u{ad}')
      0x25E6 => sb.write_char('-')
      0x25AA => sb.write_char('\u{b7}')
      0x0A => sb.write_char(c)
      _ =>
        if winansi_code_of(cp) is Some(_) {
          sb.write_char(c)
        } else {
          sb.write_char('\u{ac}')
        }
    }
  }
  sb.to_string()
}