Skip to main content

retex_engine/
fonts.rs

1//! Fonts as TeX sees them: metrics, parameters (`\fontdimen`), identifiers
2//! and font-related assignments (*TeX: The Program* §539–§582, §1252–§1261).
3//!
4//! Font files are not parsed here. A [`FontLoader`] supplied by the host
5//! (the native luaotfload replacement, or a TFM reader) turns a font request
6//! into a [`FontInfo`].
7
8use std::collections::HashMap;
9
10use crate::Engine;
11use crate::cmd::Cmd;
12use crate::eqtb::{Loc, Meaning, Val};
13use crate::hash::CsId;
14use crate::primitives_gen as pg;
15
16/// Metrics of one character.
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
18pub struct CharMetrics {
19    /// Width (sp).
20    pub width: i32,
21    /// Height (sp).
22    pub height: i32,
23    /// Depth (sp).
24    pub depth: i32,
25    /// Italic correction (sp).
26    pub italic: i32,
27    /// Glyph index in the font file (for the backend).
28    pub index: u32,
29}
30
31/// A ligature or kerning instruction of a font's lig/kern program.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum LigOp {
34    /// Insert a font kern of this amount between the two characters.
35    Kern(i32),
36    /// A ligature: `op` is TFM's op byte (`=:` 0, `=:|` 1, `|=:` 2,
37    /// `|=:|` 3, `=:|>` 5, `|=:>` 6, `|=:|>` 7, `|=:|>>` 11), `chr` the
38    /// ligature character.
39    Lig {
40        /// The operation.
41        op: u8,
42        /// The character inserted.
43        chr: u32,
44    },
45}
46
47/// One step of a lig/kern program: what to do when `next` follows.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct LigKernStep {
50    /// The right-hand character (the font's right boundary character for
51    /// the right word boundary).
52    pub next: u32,
53    /// The instruction.
54    pub op: LigOp,
55}
56
57/// A fast hasher for small integer keys (multiply-rotate, as in rustc's
58/// `FxHasher`).
59#[derive(Default, Clone, Copy)]
60pub struct FxHasher(u64);
61
62impl std::hash::Hasher for FxHasher {
63    #[inline]
64    fn write(&mut self, bytes: &[u8]) {
65        for &b in bytes {
66            self.write_u64(u64::from(b));
67        }
68    }
69
70    #[inline]
71    fn write_u32(&mut self, i: u32) {
72        self.write_u64(u64::from(i));
73    }
74
75    #[inline]
76    fn write_u64(&mut self, i: u64) {
77        self.0 = (self.0.rotate_left(5) ^ i).wrapping_mul(0x517c_c1b7_2722_0a95);
78    }
79
80    #[inline]
81    fn finish(&self) -> u64 {
82        self.0
83    }
84}
85
86/// Lig/kern programs by left character.
87pub type LigKernMap = HashMap<u32, Box<[LigKernStep]>, std::hash::BuildHasherDefault<FxHasher>>;
88
89/// Key of the left boundary program in [`FontInfo::lig_kern`].
90pub const LEFT_BOUNDARY: u32 = 0x11_0000;
91
92/// How a font was loaded.
93#[derive(Clone, Debug, PartialEq, Eq)]
94pub enum FontKind {
95    /// `\nullfont`.
96    Null,
97    /// A TFM font.
98    Tfm,
99    /// An OpenType font loaded through the luaotfload replacement.
100    OpenType,
101}
102
103/// Host data attached to a font (e.g. the parsed OpenType font used for
104/// shaping and embedding).
105#[derive(Clone)]
106pub struct FontData(pub std::rc::Rc<dyn std::any::Any>);
107
108impl std::fmt::Debug for FontData {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.write_str("FontData(..)")
111    }
112}
113
114/// Host data of a TFM font: the parsed file and its raw bytes (kept for
115/// the format file).
116#[derive(Debug)]
117pub struct TfmData {
118    /// The parsed metrics.
119    pub tfm: std::rc::Rc<retex_font::tfm::Tfm>,
120    /// The file content.
121    pub raw: Vec<u8>,
122}
123
124/// Wraps a TFM font as font data.
125pub fn tfm_font_data(tfm: retex_font::tfm::Tfm, raw: Vec<u8>) -> FontData {
126    FontData(std::rc::Rc::new(TfmData {
127        tfm: std::rc::Rc::new(tfm),
128        raw,
129    }))
130}
131
132/// A loaded font.
133#[derive(Clone, Debug)]
134pub struct FontInfo {
135    /// The name as `\fontname` prints it.
136    pub name: Vec<u8>,
137    /// The size (sp).
138    pub size: i32,
139    /// The design size (sp).
140    pub design_size: i32,
141    /// The TFM checksum (or the `checksum` of a Lua font table), written
142    /// to DVI font definitions.
143    pub checksum: u32,
144    /// Font parameters; `params[k]` is `\fontdimen k` (index 0 unused).
145    pub params: Vec<i32>,
146    /// Character metrics.
147    pub chars: HashMap<u32, CharMetrics, crate::hash::FastHash>,
148    /// `\hyphenchar`.
149    pub hyphen_char: i32,
150    /// `\skewchar`.
151    pub skew_char: i32,
152    /// The kind of font.
153    pub kind: FontKind,
154    /// Opaque handle for the loader/backend (e.g. index of the parsed
155    /// OpenType font and its feature set).
156    pub handle: u64,
157    /// Per-character `\lpcode`, `\rpcode`, `\efcode`, `\tagcode`.
158    pub char_codes: [HashMap<u32, i32>; 4],
159    /// `\ignoreligaturesinfont` and other per-font integers.
160    pub ignore_ligatures: i32,
161    /// Host data (shaping, embedding).
162    pub data: Option<FontData>,
163    /// Lig/kern programs by left character; only the first step for each
164    /// right character is kept. [`LEFT_BOUNDARY`] holds the left boundary
165    /// program.
166    pub lig_kern: LigKernMap,
167    /// The right boundary character (TFM's `bchar`), if any.
168    pub right_boundary: Option<u32>,
169    /// Font expansion parameters (`\expandglyphsinfont`), if expandable.
170    pub expansion: Option<crate::expansion::FontExpansion>,
171    /// The OpenType `MathConstants` (in scaled points, or percent), for
172    /// fonts loaded with a MATH table; they set the `\Umath…` parameters
173    /// when the font is assigned to a math family.
174    pub math_constants: Option<std::rc::Rc<MathConstants>>,
175    /// Per-character OpenType math data (variants, extensible parts, top
176    /// accent attachments), for math fonts.
177    pub math_chars: Option<std::rc::Rc<HashMap<u32, MathCharInfo>>>,
178    /// The size requested by `\font` (positive `at` size, or minus the
179    /// `scaled` factor); `i32::MIN` if not loaded by `\font`.
180    pub size_spec: i32,
181    /// How to draw the characters, for a TFM font that has a virtual
182    /// font (`.vf`) of the same name.
183    pub vf: Option<std::rc::Rc<VirtualFont>>,
184}
185
186/// The drawing instructions of a virtual font at the size it was loaded
187/// at: shipping out a character runs its packet instead of drawing it.
188#[derive(Debug, Default)]
189pub struct VirtualFont {
190    /// The engine font of each local font (0, the null font, if it could
191    /// not be loaded).
192    pub fonts: Vec<u32>,
193    /// The packet of each character, in sp.
194    pub packets: HashMap<u32, Box<[retex_font::vf::Command]>, crate::hash::FastHash>,
195}
196
197/// How deeply virtual fonts may refer to other virtual fonts.
198const VF_MAX_DEPTH: u32 = 8;
199
200/// One part of an extensible OpenType math character (scaled points).
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub struct ExtPart {
203    /// The character of the part.
204    pub code: u32,
205    /// Start connector length.
206    pub start: i32,
207    /// End connector length.
208    pub end: i32,
209    /// Full advance.
210    pub advance: i32,
211    /// Whether the part is repeatable.
212    pub extender: bool,
213}
214
215/// The OpenType math data of a character.
216#[derive(Debug, Clone, Default, PartialEq, Eq)]
217pub struct MathCharInfo {
218    /// The next larger variant.
219    pub next: Option<u32>,
220    /// The top accent attachment point.
221    pub top_accent: Option<i32>,
222    /// Vertical extensible parts, bottom first.
223    pub vert: Option<std::rc::Rc<[ExtPart]>>,
224    /// Horizontal extensible parts, left first.
225    pub horiz: Option<std::rc::Rc<[ExtPart]>>,
226}
227
228/// Named OpenType math constants (luaotfload's `MathConstants`).
229pub type MathConstants = HashMap<String, i32>;
230
231impl Default for FontInfo {
232    fn default() -> Self {
233        FontInfo::null()
234    }
235}
236
237impl FontInfo {
238    /// The null font: no characters, seven zero parameters.
239    pub fn null() -> Self {
240        FontInfo {
241            name: b"nullfont".to_vec(),
242            size: 0,
243            design_size: 0,
244            checksum: 0,
245            params: vec![0; 8],
246            chars: HashMap::default(),
247            hyphen_char: '-' as i32,
248            skew_char: -1,
249            kind: FontKind::Null,
250            handle: 0,
251            char_codes: Default::default(),
252            ignore_ligatures: 0,
253            lig_kern: LigKernMap::default(),
254            right_boundary: None,
255            math_constants: None,
256            math_chars: None,
257            size_spec: i32::MIN,
258            data: None,
259            expansion: None,
260            vf: None,
261        }
262    }
263}
264
265/// A font request from `\font`.
266#[derive(Clone, Debug)]
267pub struct FontRequest {
268    /// The name as written after `\font\cs=`.
269    pub name: Vec<u8>,
270    /// Size: positive is `at` (sp), negative is `scaled` (−1000 × factor),
271    /// −1000 when unspecified.
272    pub size: i32,
273}
274
275/// Loads fonts for the engine (LuaTeX's `define_font` callback).
276pub trait FontLoader {
277    /// Loads the font, or returns `None` if it cannot be found.
278    fn load(&mut self, req: &FontRequest) -> Option<FontInfo>;
279}
280
281/// The table of loaded fonts.
282#[derive(Default)]
283pub struct FontTable {
284    /// Fonts by id; id 0 is `\nullfont`.
285    pub fonts: Vec<FontInfo>,
286    /// Identifier control sequence of each font (TeX's `font_id_text`).
287    pub id_cs: Vec<CsId>,
288    /// The loader, if any.
289    pub loader: Option<Box<dyn FontLoader>>,
290    /// Dense metric tables for fast lookup of small character codes, by
291    /// font id (see [`FontTable::sync_dense`]).
292    pub dense: Vec<DenseMetrics>,
293}
294
295/// Number of character codes covered by [`DenseMetrics`].
296const DENSE_CHARS: usize = 0x800;
297
298/// Metrics of the characters below [`DENSE_CHARS`] in an array.
299#[derive(Clone, Debug, Default)]
300pub struct DenseMetrics {
301    metrics: Box<[CharMetrics]>,
302    present: Box<[u64]>,
303    /// For characters below 256: (start, length) of their lig/kern program
304    /// in `steps`.
305    lk: Box<[(u32, u32)]>,
306    steps: Box<[LigKernStep]>,
307    /// For pairs of characters below 128: index + 1 of their instruction
308    /// in `steps` (0 for none).
309    pairs: Box<[u16]>,
310}
311
312impl FontTable {
313    /// Builds the dense tables of fonts added since the last call. Call
314    /// before hot loops; the character metrics of a font must not change
315    /// afterwards (call [`FontTable::invalidate_dense`] if they do).
316    pub fn sync_dense(&mut self) {
317        while self.dense.len() < self.fonts.len() {
318            let f = &self.fonts[self.dense.len()];
319            let mut metrics = vec![CharMetrics::default(); DENSE_CHARS].into_boxed_slice();
320            let mut present = vec![0u64; DENSE_CHARS / 64].into_boxed_slice();
321            for (&c, m) in &f.chars {
322                if (c as usize) < DENSE_CHARS {
323                    metrics[c as usize] = *m;
324                    present[c as usize / 64] |= 1 << (c % 64);
325                }
326            }
327            let mut lk = Vec::new();
328            let mut steps: Vec<LigKernStep> = Vec::new();
329            let mut pairs = Vec::new();
330            if !f.lig_kern.is_empty() {
331                lk = vec![(0u32, 0u32); 256];
332                pairs = vec![0u16; 128 * 128];
333                for (c, e) in lk.iter_mut().enumerate() {
334                    if let Some(p) = f.lig_kern.get(&(c as u32)) {
335                        *e = (steps.len() as u32, p.len() as u32);
336                        for st in p.iter() {
337                            if c < 128 && st.next < 128 && steps.len() < 0xFFFF {
338                                pairs[c * 128 + st.next as usize] = steps.len() as u16 + 1;
339                            }
340                            steps.push(*st);
341                        }
342                    }
343                }
344            }
345            self.dense.push(DenseMetrics {
346                metrics,
347                present,
348                lk: lk.into_boxed_slice(),
349                steps: steps.into_boxed_slice(),
350                pairs: pairs.into_boxed_slice(),
351            });
352        }
353    }
354
355    /// Forgets the dense table of font `f` and those after it.
356    pub fn invalidate_dense(&mut self, f: u32) {
357        self.dense.truncate(f as usize);
358    }
359
360    /// The metrics of character `c` in font `f`.
361    #[inline]
362    pub fn char_metrics(&self, f: u32, c: u32) -> Option<&CharMetrics> {
363        if let Some(d) = self.dense.get(f as usize)
364            && (c as usize) < DENSE_CHARS
365        {
366            return if d.present[c as usize / 64] & (1 << (c % 64)) != 0 {
367                Some(&d.metrics[c as usize])
368            } else {
369                None
370            };
371        }
372        self.fonts.get(f as usize)?.chars.get(&c)
373    }
374}
375
376impl std::fmt::Debug for FontTable {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        f.debug_struct("FontTable")
379            .field("fonts", &self.fonts.len())
380            .finish_non_exhaustive()
381    }
382}
383
384/// The largest `\fontdimen` number retex accepts.
385pub const MAX_FONT_DIMEN: i32 = 1 << 20;
386
387impl Engine {
388    /// The font info of font `f` (the null font if `f` is invalid).
389    pub fn font(&self, f: u32) -> &FontInfo {
390        self.fonts
391            .fonts
392            .get(f as usize)
393            .unwrap_or(&self.fonts.fonts[0])
394    }
395
396    /// Whether font id `f` exists.
397    pub fn font_exists(&self, f: i32) -> bool {
398        f >= 0 && (f as usize) < self.fonts.fonts.len()
399    }
400
401    /// The identifier control sequence of font `f`.
402    pub fn font_id_cs(&self, f: u32) -> CsId {
403        self.fonts
404            .id_cs
405            .get(f as usize)
406            .copied()
407            .unwrap_or(self.cs.frozen_null_font)
408    }
409
410    /// `\fontdimen 6` (quad) of font `f`.
411    pub fn quad(&self, f: u32) -> i32 {
412        self.font(f).params.get(6).copied().unwrap_or(0)
413    }
414
415    /// `\fontdimen 5` (x-height) of font `f`.
416    pub fn x_height(&self, f: u32) -> i32 {
417        self.font(f).params.get(5).copied().unwrap_or(0)
418    }
419
420    /// Whether font `f` has character `c`.
421    pub fn font_has_char(&self, f: u32, c: u32) -> bool {
422        self.font(f).chars.contains_key(&c)
423    }
424
425    /// `\fontcharwd` etc.: `which` is 0 (wd), 1 (ht), 2 (dp), 3 (ic).
426    pub fn font_char_dimen(&self, f: u32, c: u32, which: u32) -> i32 {
427        match self.font(f).chars.get(&c) {
428            Some(m) => match which {
429                0 => m.width,
430                1 => m.height,
431                2 => m.depth,
432                _ => m.italic,
433            },
434            None => 0,
435        }
436    }
437
438    /// Prints a font name, with ` at …pt` if the size differs from the
439    /// design size.
440    pub fn print_font_name_and_size(&mut self, f: u32) {
441        let (name, size, dsize) = {
442            let fi = self.font(f);
443            (fi.name.clone(), fi.size, fi.design_size)
444        };
445        self.printer.print_bytes(&name);
446        if size != dsize {
447            self.printer.print_str(" at ");
448            self.printer.print_scaled(size);
449            self.printer.print_str("pt");
450        }
451    }
452
453    /// Scans a font identifier (`scan_font_ident`).
454    pub fn scan_font_ident(&mut self) {
455        self.get_nonblank_noncall();
456        let f: i32 = match self.cur_cmd {
457            Cmd::DefFont => self.eqtb.cur_font.val as i32,
458            Cmd::SetFont => self.cur_chr as i32,
459            Cmd::DefFamily => {
460                let size = self.cur_chr;
461                self.scan_math_family();
462                let fam = self.cur_val as u32;
463                self.eqtb.math_font[(size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize].val
464                    as i32
465            }
466            _ => {
467                self.print_err("Missing font identifier");
468                self.help(&[
469                    "I was looking for a control sequence whose",
470                    "current meaning has been defined by \\font.",
471                ]);
472                self.back_error();
473                0
474            }
475        };
476        self.cur_val = f;
477    }
478
479    /// Finds a font dimension (`find_font_dimen`); returns its value and
480    /// records its location in `font_dimen_loc` for assignments. Unlike
481    /// TeX82, LuaTeX extends the parameters of any font (not only the most
482    /// recently loaded one) when a parameter beyond the last is read or
483    /// written; only `n <= 0` is an error. (LuaTeX dies on absurdly large
484    /// `n`; retex reports those as errors too, above [`MAX_FONT_DIMEN`].)
485    pub fn find_font_dimen(&mut self, _writing: bool) -> i32 {
486        self.scan_int();
487        let n = self.cur_val;
488        self.scan_font_ident();
489        let f = self.cur_val as usize;
490        let ok = n > 0 && n <= MAX_FONT_DIMEN;
491        if ok {
492            let params = &mut self.fonts.fonts[f].params;
493            if n as usize >= params.len() {
494                params.resize(n as usize + 1, 0);
495            }
496        }
497        if !ok {
498            self.print_err("Font ");
499            let cs = self.font_id_cs(f as u32);
500            let name = self.hash.name(cs).to_vec();
501            self.print_esc_bytes(&name);
502            self.printer.print_str(" has only ");
503            self.printer
504                .print_int((self.fonts.fonts[f].params.len() as i64 - 1).max(0));
505            self.printer.print_str(" fontdimen parameters");
506            self.help(&[
507                "To increase the number of font parameters, you must",
508                "use \\fontdimen immediately after the \\font is loaded.",
509            ]);
510            self.error();
511            self.font_dimen_loc = None;
512            return 0;
513        }
514        self.font_dimen_loc = Some((f as u32, n as u32));
515        self.fonts.fonts[f].params[n as usize]
516    }
517
518    /// Stores a `\fontdimen` value found by `find_font_dimen`.
519    pub fn set_font_dimen(&mut self, loc: Option<(u32, u32)>, v: i32) {
520        if let Some((f, n)) = loc {
521            self.fonts.fonts[f as usize].params[n as usize] = v;
522        }
523    }
524
525    /// `\hyphenchar`, `\skewchar`, `\ignoreligaturesinfont` values.
526    pub fn font_int_param(&mut self, f: u32, n: u32) -> i32 {
527        match n {
528            0 => self.font(f).hyphen_char,
529            1 => self.font(f).skew_char,
530            2..=5 => {
531                self.scan_char_num();
532                let c = self.cur_val as u32;
533                // \lpcode, \rpcode and \efcode of a character that the
534                // font lacks are 0.
535                if n < 5 && self.fonts.char_metrics(f, c).is_none() {
536                    return 0;
537                }
538                self.font(f).char_codes[(n - 2) as usize]
539                    .get(&c)
540                    .copied()
541                    .unwrap_or(if n == 4 { 1000 } else { 0 })
542            }
543            _ => self.font(f).ignore_ligatures,
544        }
545    }
546
547    /// Sets `\hyphenchar`, `\skewchar`, `\ignoreligaturesinfont`.
548    pub fn set_font_int_param(&mut self, f: u32, n: u32, v: i32) {
549        if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
550            match n {
551                0 => fi.hyphen_char = v,
552                1 => fi.skew_char = v,
553                _ => fi.ignore_ligatures = v,
554            }
555        }
556    }
557
558    /// Sets `\lpcode`, `\rpcode`, `\efcode`, `\tagcode`. The first three
559    /// are not set for characters that the font lacks.
560    pub fn set_font_char_code(&mut self, f: u32, n: u32, c: u32, v: i32) {
561        if n < 5 && self.fonts.char_metrics(f, c).is_none() {
562            return;
563        }
564        if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
565            fi.char_codes[(n - 2) as usize].insert(c, v);
566        }
567    }
568
569    /// `\font\cs=name at size` (`new_font`).
570    pub fn new_font(&mut self, global: bool) {
571        if self.job_name.is_none() {
572            self.open_log_file();
573        }
574        self.get_r_token();
575        let u = self.cur_cs;
576        self.define(
577            global,
578            Loc::Cs(u),
579            Val::Meaning(Meaning::prim(Cmd::SetFont, 0)),
580        );
581        self.scan_optional_equals();
582        let name = self.scan_file_name();
583        // Scan the font size specification.
584        self.files.name_in_progress = true;
585        let size: i32 = if self.scan_keyword("at") {
586            self.scan_normal_dimen();
587            let s = self.cur_val;
588            if s <= 0 || s >= 0o1000000000 {
589                self.print_err("Improper `at' size (");
590                self.printer.print_scaled(s);
591                self.printer.print_str("pt), replaced by 10pt");
592                self.help(&[
593                    "I can only handle fonts at positive sizes that are",
594                    "less than 2048pt, so I've changed what you said to 10pt.",
595                ]);
596                self.error();
597                10 * retex_arith::UNITY
598            } else {
599                s
600            }
601        } else if self.scan_keyword("scaled") {
602            self.scan_int();
603            let s = -self.cur_val;
604            if self.cur_val <= 0 || self.cur_val > 32768 {
605                self.print_err("Illegal magnification has been changed to 1000");
606                self.help(&["The magnification ratio must be between 1 and 32768."]);
607                self.int_error(self.cur_val);
608                -1000
609            } else {
610                s
611            }
612        } else {
613            -1000
614        };
615        self.files.name_in_progress = false;
616        // Reuse a font loaded with the same name and size request (LuaTeX
617        // compares the requests: `at 10pt` does not reuse a font loaded at
618        // its design size of 10pt).
619        let existing = self
620            .fonts
621            .fonts
622            .iter()
623            .enumerate()
624            .skip(1)
625            .find(|(_, fi)| fi.name == name && fi.size_spec == size)
626            .map(|(i, _)| i);
627        let f = match existing {
628            Some(i) => i as u32,
629            None => match self.load_font(&name, size) {
630                Some(f) => f,
631                None => {
632                    self.print_err("Font ");
633                    self.sprint_cs(u);
634                    self.printer.print_char('=' as u32);
635                    self.printer.print_bytes(&name);
636                    if size >= 0 {
637                        self.printer.print_str(" at ");
638                        self.printer.print_scaled(size);
639                        self.printer.print_str("pt");
640                    } else if size != -1000 {
641                        self.printer.print_str(" scaled ");
642                        self.printer.print_int(i64::from(-size));
643                    }
644                    self.printer
645                        .print_str(" not loadable: Metric (TFM) file not found");
646                    self.help(&[
647                        "I wasn't able to read the size data for this font,",
648                        "so I will ignore the font specification.",
649                        "[Wizards can fix TFM files using TFtoPL/PLtoTF.]",
650                        "You might try inserting a different font spec;",
651                        "e.g., type `I\\font<same font id>=<substitute font name>'.",
652                    ]);
653                    self.error();
654                    0
655                }
656            },
657        };
658        if f != 0 && self.fonts.fonts[f as usize].size_spec == i32::MIN {
659            self.fonts.fonts[f as usize].size_spec = size;
660        }
661        // The identifier text is that of the control sequence used last,
662        // also when the font existed already (TeX's `common_ending`).
663        if f != 0 {
664            let name = self.hash.name(u).to_vec();
665            let id = self.hash.new_frozen(&name);
666            self.eqtb.ensure_cs(id);
667            self.eqtb.cs[id.0 as usize].val = Meaning::prim(Cmd::SetFont, f);
668            self.fonts.id_cs[f as usize] = id;
669        }
670        self.define(
671            global,
672            Loc::Cs(u),
673            Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
674        );
675    }
676
677    /// Loads a font through the `define_font` callback, or the built-in
678    /// loader when no callback is set, and registers it.
679    pub fn load_font(&mut self, name: &[u8], size: i32) -> Option<u32> {
680        if let Some(h) = self.lua.clone()
681            && h.has_callback("define_font")
682        {
683            return h.define_font(self, name, size);
684        }
685        let req = FontRequest {
686            name: name.to_vec(),
687            size,
688        };
689        if self.fonts.loader.is_none() {
690            return self.load_tfm_font(name, size, name);
691        }
692        let mut loader = self.fonts.loader.take()?;
693        let info = loader.load(&req);
694        self.fonts.loader = Some(loader);
695        let mut info = info?;
696        info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
697        info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
698        Some(self.register_font(info))
699    }
700
701    /// Adds a loaded font to the font table and returns its id.
702    pub fn register_font(&mut self, mut info: FontInfo) -> u32 {
703        if info.hyphen_char == i32::MIN {
704            info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
705        }
706        if info.skew_char == i32::MIN {
707            info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
708        }
709        self.fonts.fonts.push(info);
710        self.fonts.id_cs.push(self.cs.frozen_null_font);
711        self.fonts.fonts.len() as u32 - 1
712    }
713
714    /// LuaTeX's built-in TFM loader (used when no `define_font` callback
715    /// is set, e.g. while building the LaTeX format).
716    pub fn load_tfm(&mut self, name: &[u8], size: i32) -> Option<FontInfo> {
717        let name_s = String::from_utf8_lossy(name).into_owned();
718        let path = self
719            .kpse
720            .as_ref()?
721            .borrow_mut()
722            .find_file(&name_s, retex_kpath::FileFormat::Tfm)?;
723        let data = self.read_file_recorded(&path)?;
724        let tfm = retex_font::tfm::Tfm::parse(&data).ok()?;
725        let scaled = tfm.scaled(size);
726        let mut params = vec![0; scaled.params.len() + 1];
727        params[1..].copy_from_slice(&scaled.params);
728        let mut chars =
729            HashMap::with_capacity_and_hasher(scaled.chars.len(), crate::hash::FastHash::default());
730        for (c, m) in &scaled.chars {
731            chars.insert(
732                u32::from(*c),
733                CharMetrics {
734                    width: m[0],
735                    height: m[1],
736                    depth: m[2],
737                    italic: m[3],
738                    index: u32::from(*c),
739                },
740            );
741        }
742        Some(FontInfo {
743            name: name.to_vec(),
744            size: scaled.size,
745            design_size: tfm.design_size,
746            checksum: tfm.checksum,
747            params,
748            chars,
749            hyphen_char: i32::MIN,
750            skew_char: i32::MIN,
751            kind: FontKind::Tfm,
752            handle: 0,
753            char_codes: Default::default(),
754            ignore_ligatures: 0,
755            lig_kern: lig_kern_table(&tfm, &scaled.kerns),
756            right_boundary: tfm.bchar.map(u32::from),
757            math_constants: None,
758            math_chars: None,
759            size_spec: i32::MIN,
760            data: Some(tfm_font_data(tfm, data)),
761            expansion: None,
762            vf: None,
763        })
764    }
765
766    /// Loads TFM font `tfm_name` at `size` and registers it under the name
767    /// `font_name` (what `\fontname` shows). If there is a virtual font of
768    /// the same name, its local fonts are loaded too (observed in LuaTeX:
769    /// right after the font, in definition order, reusing a loaded TFM font
770    /// of the same name and size) and its packets are attached.
771    pub fn load_tfm_font(&mut self, tfm_name: &[u8], size: i32, font_name: &[u8]) -> Option<u32> {
772        self.load_tfm_font_at_depth(tfm_name, size, font_name, 0)
773    }
774
775    fn load_tfm_font_at_depth(
776        &mut self,
777        tfm_name: &[u8],
778        size: i32,
779        font_name: &[u8],
780        depth: u32,
781    ) -> Option<u32> {
782        let mut info = self.load_tfm(tfm_name, size)?;
783        info.name = font_name.to_vec();
784        let size = info.size;
785        let f = self.register_font(info);
786        if depth < VF_MAX_DEPTH {
787            self.attach_virtual_font(f, tfm_name, size, depth);
788        }
789        Some(f)
790    }
791
792    /// Reads the VF file `name` for font `f` (loaded at `size`), if there
793    /// is one, and loads its local fonts.
794    fn attach_virtual_font(&mut self, f: u32, name: &[u8], size: i32, depth: u32) {
795        let name_s = String::from_utf8_lossy(name).into_owned();
796        let Some(path) = self.kpse.as_ref().and_then(|k| {
797            k.borrow_mut()
798                .find_file(&name_s, retex_kpath::FileFormat::Vf)
799        }) else {
800            return;
801        };
802        let Some(data) = self.read_file_recorded(&path) else {
803            return;
804        };
805        let Ok(vf) = retex_font::vf::Vf::parse(&data) else {
806            return;
807        };
808        let mut fonts = Vec::with_capacity(vf.fonts.len());
809        for (i, lf) in vf.fonts.iter().enumerate() {
810            let lsize = vf.font_size(i, size);
811            let lname = lf.name.as_bytes();
812            let existing = (1..self.fonts.fonts.len()).find(|&g| {
813                let fi = &self.fonts.fonts[g];
814                fi.kind == FontKind::Tfm && fi.size == lsize && fi.name == lname
815            });
816            let id = match existing {
817                Some(g) => g as u32,
818                None => self
819                    .load_tfm_font_at_depth(lname, lsize, lname, depth + 1)
820                    .unwrap_or(0),
821            };
822            fonts.push(id);
823        }
824        let mut packets =
825            HashMap::with_capacity_and_hasher(vf.packets.len(), crate::hash::FastHash::default());
826        for &c in vf.packets.keys() {
827            if let Some(cmds) = vf.commands(c, size) {
828                packets.insert(c, cmds.into_boxed_slice());
829            }
830        }
831        self.fonts.fonts[f as usize].vf = Some(std::rc::Rc::new(VirtualFont { fonts, packets }));
832    }
833
834    /// The lig/kern instruction of font `f` for the pair (`left`, `right`).
835    #[inline]
836    pub fn lig_kern_step(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
837        if let Some(d) = self.fonts.dense.get(f as usize)
838            && left < 256
839        {
840            if d.lk.is_empty() {
841                return None;
842            }
843            if left < 128 && right < 128 {
844                let i = d.pairs[(left * 128 + right) as usize];
845                return if i == 0 {
846                    None
847                } else {
848                    Some(d.steps[i as usize - 1].op)
849                };
850            }
851            let (start, len) = d.lk[left as usize];
852            return d.steps[start as usize..(start + len) as usize]
853                .iter()
854                .find(|s| s.next == right)
855                .map(|s| s.op);
856        }
857        let fi = self.font(f);
858        if fi.lig_kern.is_empty() {
859            return None;
860        }
861        fi.lig_kern
862            .get(&left)?
863            .iter()
864            .find(|s| s.next == right)
865            .map(|s| s.op)
866    }
867
868    /// The OpenType math data of character `c` in font `f`, if any.
869    pub fn math_char(&self, f: u32, c: u32) -> Option<&MathCharInfo> {
870        self.font(f).math_chars.as_ref()?.get(&c)
871    }
872
873    /// The TFM data of font `f`, for TeX's ligature/kerning of TFM fonts.
874    pub fn tfm_of(&self, f: u32) -> Option<std::rc::Rc<retex_font::tfm::Tfm>> {
875        let d = self.font(f).data.as_ref()?;
876        d.0.downcast_ref::<TfmData>().map(|t| t.tfm.clone())
877    }
878
879    /// `\letterspacefont` (LuaTeX).
880    pub fn letterspace_font(&mut self, global: bool) {
881        self.get_r_token();
882        let u = self.cur_cs;
883        self.scan_font_ident();
884        let f = self.cur_val as u32;
885        self.scan_int();
886        let _amount = self.cur_val;
887        // Letterspacing is not modelled yet: alias the font.
888        self.define(
889            global,
890            Loc::Cs(u),
891            Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
892        );
893    }
894
895    /// `\copyfont` (LuaTeX).
896    pub fn copy_font(&mut self, global: bool) {
897        self.get_r_token();
898        let u = self.cur_cs;
899        self.scan_font_ident();
900        let f = self.cur_val as usize;
901        let copy = self.fonts.fonts[f].clone();
902        self.fonts.fonts.push(copy);
903        self.fonts.id_cs.push(self.cs.frozen_null_font);
904        let nf = self.fonts.fonts.len() as u32 - 1;
905        self.define(
906            global,
907            Loc::Cs(u),
908            Val::Meaning(Meaning::prim(Cmd::SetFont, nf)),
909        );
910    }
911}
912
913/// Converts a TFM lig/kern program into per-character steps (only the
914/// first instruction for each right character counts, §545).
915pub(crate) fn lig_kern_table(tfm: &retex_font::tfm::Tfm, kerns: &[i32]) -> LigKernMap {
916    let nl = tfm.lig_kern.len();
917    let read = |start: usize| -> Vec<LigKernStep> {
918        let mut steps: Vec<LigKernStep> = Vec::new();
919        let mut k = start;
920        for _ in 0..nl {
921            let Some(w) = tfm.lig_kern.get(k) else { break };
922            if w.skip <= 128 {
923                let next = u32::from(w.next);
924                if !steps.iter().any(|s| s.next == next) {
925                    let op = if w.op >= 128 {
926                        let ki = 256 * (usize::from(w.op) - 128) + usize::from(w.rem);
927                        LigOp::Kern(kerns.get(ki).copied().unwrap_or(0))
928                    } else {
929                        LigOp::Lig {
930                            op: w.op,
931                            chr: u32::from(w.rem),
932                        }
933                    };
934                    steps.push(LigKernStep { next, op });
935                }
936            }
937            if w.skip >= 128 {
938                break;
939            }
940            k += usize::from(w.skip) + 1;
941        }
942        steps
943    };
944    let mut map = LigKernMap::default();
945    for c in u32::from(tfm.bc)..=u32::from(tfm.ec) {
946        if let Some(start) = tfm.lig_kern_start(c) {
947            let p = read(start);
948            if !p.is_empty() {
949                map.insert(c, p.into_boxed_slice());
950            }
951        }
952    }
953    if let Some(l) = tfm.bchar_label {
954        let p = read(l);
955        if !p.is_empty() {
956            map.insert(LEFT_BOUNDARY, p.into_boxed_slice());
957        }
958    }
959    map
960}