#![cfg_attr(not(feature = "font"), allow(unused_variables, dead_code))]
use crate::font::{Font, Kind};
use crate::layout::LaidOut;
pub fn scoped_name(kind: Kind) -> &'static str {
match kind {
Kind::Mono => "Lini Sans Code",
Kind::Prop => "Lini Sans",
}
}
pub fn lead_with_scoped(stack: &str) -> String {
let first = stack.split(',').next().unwrap_or(stack).trim();
let bare = first.trim_matches(['"', '\'']);
match bare {
"Google Sans Code" => format!("\"{}\", {}", scoped_name(Kind::Mono), stack),
"Google Sans" => format!("\"{}\", {}", scoped_name(Kind::Prop), stack),
_ => stack.to_string(),
}
}
pub struct FontSink {
pub root_font: Font,
pub root_size: f64,
#[cfg(feature = "font")]
used: std::cell::RefCell<std::collections::BTreeSet<(u8, u16, u16)>>,
}
impl FontSink {
pub fn new(laid: &LaidOut) -> FontSink {
FontSink {
root_font: Font::of(&laid.sheet.root_text),
root_size: laid.sheet.root_font_size,
#[cfg(feature = "font")]
used: Default::default(),
}
}
}
#[cfg(feature = "font")]
mod enabled {
use super::{FontSink, scoped_name};
use crate::font::{self, Font, Kind};
use std::fmt::Write;
use std::sync::OnceLock;
fn kind_tag(kind: Kind) -> u8 {
match kind {
Kind::Mono => 0,
Kind::Prop => 1,
}
}
fn kind_of_tag(tag: u8) -> Kind {
if tag == 0 { Kind::Mono } else { Kind::Prop }
}
fn face(kind: Kind, weight: u16) -> &'static ttf_parser::Face<'static> {
static FACES: OnceLock<Vec<ttf_parser::Face<'static>>> = OnceLock::new();
let faces = FACES.get_or_init(|| {
let mut v = Vec::with_capacity(8);
for kind in [Kind::Mono, Kind::Prop] {
for w in [400u16, 500, 600, 700] {
v.push(
ttf_parser::Face::parse(font::subset_bytes(kind, w), 0)
.expect("bundled subset parses"),
);
}
}
v
});
let wi = match weight {
500 => 1,
600 => 2,
700 => 3,
_ => 0,
};
&faces[kind_tag(kind) as usize * 4 + wi]
}
pub fn glyph_id(f: Font, ch: char) -> u16 {
let fc = face(f.kind, f.weight());
let direct = |c: char| fc.glyph_index(c).map(|g| g.0);
direct(ch)
.or_else(|| {
font::SUBSTITUTES
.iter()
.find(|&&(from, _)| from == ch)
.and_then(|&(_, to)| direct(to))
})
.unwrap_or(0)
}
impl FontSink {
pub fn register(&self, f: Font, content: &str, glyphs: bool) {
let mut used = self.used.borrow_mut();
let (k, w) = (kind_tag(f.kind), f.weight());
used.insert((k, w, 0)); if glyphs {
for ch in content.chars() {
used.insert((k, w, glyph_id(f, ch)));
}
}
}
}
pub fn glyph_ref(f: Font, gid: u16) -> String {
format!("lg{}{}-{}", kind_tag(f.kind), f.weight(), gid)
}
struct PathSink(String);
impl ttf_parser::OutlineBuilder for PathSink {
fn move_to(&mut self, x: f32, y: f32) {
let _ = write!(self.0, "M{} {}", x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
let _ = write!(self.0, "L{} {}", x, y);
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let _ = write!(self.0, "Q{} {} {} {}", x1, y1, x, y);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let _ = write!(self.0, "C{} {} {} {} {} {}", x1, y1, x2, y2, x, y);
}
fn close(&mut self) {
self.0.push('Z');
}
}
pub fn emit_glyph_defs(out: &mut String, sink: &FontSink) {
for &(k, w, gid) in sink.used.borrow().iter() {
let f = font_of(k, w);
let mut d = PathSink(String::new());
if face(f.kind, f.weight())
.outline_glyph(ttf_parser::GlyphId(gid), &mut d)
.is_none()
{
continue; }
writeln!(
out,
r##" <path id="{}" d="{}"/>"##,
glyph_ref(f, gid),
d.0
)
.unwrap();
}
}
fn font_of(k: u8, w: u16) -> Font {
let kind = kind_of_tag(k);
match w {
500 => Font::medium(kind),
600 => Font::semibold(kind),
700 => Font::bold(kind),
_ => Font::regular(kind),
}
}
pub fn has_glyphs(sink: &FontSink) -> bool {
!sink.used.borrow().is_empty()
}
pub fn emit_font_faces(out: &mut String, sink: &FontSink) {
let mut seen = std::collections::BTreeSet::new();
for &(k, w, _) in sink.used.borrow().iter() {
if !seen.insert((k, w)) {
continue;
}
let kind = kind_of_tag(k);
writeln!(
out,
" @font-face {{ font-family: \"{}\"; font-weight: {}; src: url(data:font/ttf;base64,{}) format(\"truetype\"); }}",
scoped_name(kind),
w,
base64(font::subset_bytes(kind, w)),
)
.unwrap();
}
}
pub fn decoration_band(f: Font, size: f64, strike: bool) -> (f64, f64) {
let fc = face(f.kind, f.weight());
let upem = fc.units_per_em() as f64;
let m = if strike {
fc.strikeout_metrics()
} else {
fc.underline_metrics()
};
match m {
Some(m) => (
-(m.position as f64) / upem * size,
m.thickness as f64 / upem * size,
),
None if strike => (-0.3 * size, 0.05 * size),
None => (0.1 * size, 0.05 * size),
}
}
fn base64(data: &[u8]) -> String {
const TBL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = u32::from_be_bytes([0, b[0], b[1], b[2]]);
out.push(TBL[(n >> 18) as usize & 63] as char);
out.push(TBL[(n >> 12) as usize & 63] as char);
out.push(if chunk.len() > 1 {
TBL[(n >> 6) as usize & 63] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
TBL[n as usize & 63] as char
} else {
'='
});
}
out
}
pub fn glyph_starts(line: &str, f: Font, size: f64, ls: f64, cx: f64) -> Vec<(char, f64)> {
let width = crate::layout::approx_width(line, f, size, ls);
let mut pos = cx - width / 2.0;
let mut out = Vec::with_capacity(line.chars().count());
for (i, ch) in line.chars().enumerate() {
if i > 0 {
pos += ls;
}
out.push((ch, pos));
pos += f.advance_em(ch) * size;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_subset_face_parses_and_outlines() {
for kind in [Kind::Mono, Kind::Prop] {
for w in [400u16, 500, 600, 700] {
let f = font_of(kind_tag(kind), w);
let gid = glyph_id(f, 'A');
assert_ne!(gid, 0, "{kind:?} {w} lacks 'A'");
let mut d = PathSink(String::new());
face(kind, w)
.outline_glyph(ttf_parser::GlyphId(gid), &mut d)
.expect("outline");
assert!(d.0.starts_with('M'), "{}", d.0);
}
}
}
#[test]
fn diameter_outlines_as_slashed_o() {
let f = Font::default();
assert_eq!(glyph_id(f, '⌀'), glyph_id(f, 'Ø'));
assert_ne!(glyph_id(f, 'Ø'), 0);
}
#[test]
fn base64_matches_reference() {
assert_eq!(base64(b"Man"), "TWFu");
assert_eq!(base64(b"Ma"), "TWE=");
assert_eq!(base64(b"M"), "TQ==");
}
#[test]
fn glyph_starts_mirror_measurement() {
let f = Font::default();
let starts = glyph_starts("abc", f, 10.0, 0.0, 0.0);
let xs: Vec<f64> = starts.iter().map(|&(_, x)| x).collect();
assert_eq!(xs, [-9.0, -3.0, 3.0]);
}
}
}
#[cfg(feature = "font")]
pub use enabled::*;
#[cfg(not(feature = "font"))]
mod disabled {
use super::FontSink;
use crate::font::Font;
impl FontSink {
pub fn register(&self, _f: Font, _content: &str, _glyphs: bool) {}
}
pub fn has_glyphs(_sink: &FontSink) -> bool {
false
}
pub fn emit_glyph_defs(_out: &mut String, _sink: &FontSink) {}
pub fn emit_font_faces(_out: &mut String, _sink: &FontSink) {}
}
#[cfg(not(feature = "font"))]
pub use disabled::*;