use std::collections::BTreeSet;
use std::fmt::Write as _;
use rustyfi_pdf::TtfFontStore;
use super::base64;
pub(super) fn font_family_name(file_idx: usize) -> String {
format!("rustyfi-html-font-{file_idx}")
}
pub(super) fn font_face_rules(store: &TtfFontStore, used: &BTreeSet<usize>) -> String {
let mut out = String::new();
for &file_idx in used {
let b64 = base64::encode(store.file_bytes(file_idx));
let family = font_family_name(file_idx);
let _ = write!(
out,
"@font-face {{ font-family: \"{family}\"; \
src: url(data:font/ttf;base64,{b64}) format(\"truetype\"); }}\n",
);
}
out
}
pub(super) fn reflow_font_stack(family: &str) -> String {
const SANS_MARKERS: [&str; 6] = [
"gothic",
"sans",
"grotesk",
"grotesque",
"helvetica",
"arial",
];
let lower = family.to_ascii_lowercase();
let safe: String = family
.chars()
.filter(|c| !matches!(c, '\'' | '"' | '\\' | '<' | '>'))
.collect();
if is_monospace_family(family) {
format!("'{safe}', 'DejaVu Sans Mono', Menlo, Consolas, monospace")
} else if SANS_MARKERS.iter().any(|m| lower.contains(m)) {
format!("'{safe}', 'Noto Sans CJK JP', 'Hiragino Sans', sans-serif")
} else {
format!("'{safe}', 'Noto Serif CJK JP', 'Hiragino Mincho ProN', Georgia, serif")
}
}
pub(super) fn is_monospace_family(family: &str) -> bool {
const MONO_MARKERS: [&str; 6] = ["mono", "courier", "consol", "menlo", "typewriter", "teletype"];
let lower = family.to_ascii_lowercase();
MONO_MARKERS.iter().any(|m| lower.contains(m))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn family_names_are_distinct_per_file_index() {
assert_ne!(font_family_name(0), font_family_name(1));
}
#[test]
fn the_reflow_stack_names_the_face_first_and_always_ends_in_a_generic() {
let mincho = reflow_font_stack("IPAexMincho");
assert!(mincho.starts_with("'IPAexMincho',"), "{mincho}");
assert!(mincho.ends_with("serif"), "{mincho}");
assert!(!mincho.contains('"'), "{mincho}");
assert!(!reflow_font_stack("Od\"d'Name").contains('"'));
let gothic = reflow_font_stack("IPAexGothic");
assert!(gothic.ends_with("sans-serif"), "{gothic}");
assert!(reflow_font_stack("Junicode").ends_with("serif"));
assert!(!reflow_font_stack("Junicode").ends_with("sans-serif"));
}
#[test]
fn a_fixed_pitch_face_ends_in_monospace_and_beats_the_sans_marker() {
let lm = reflow_font_stack("LMMono10");
assert!(lm.starts_with("'LMMono10',"), "{lm}");
assert!(lm.ends_with("monospace"), "{lm}");
assert!(reflow_font_stack("DejaVu Sans Mono").ends_with("monospace"));
assert!(!is_monospace_family("Junicode"));
assert!(!is_monospace_family("IPAexGothic"));
}
}