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, or a font from Lua whose characters
183    /// have `commands`.
184    pub vf: Option<std::rc::Rc<VirtualFont>>,
185}
186
187/// The drawing instructions of a virtual font at the size it was loaded
188/// at: shipping out a character runs its packet instead of drawing it.
189#[derive(Debug, Default)]
190pub struct VirtualFont {
191    /// The engine font of each local font (0, the null font, if it could
192    /// not be loaded).
193    pub fonts: Vec<u32>,
194    /// The packet of each character, in sp.
195    pub packets: HashMap<u32, Box<[PacketCommand]>, crate::hash::FastHash>,
196}
197
198/// One command of a virtual character's packet: the DVI commands of a VF
199/// file, or the `commands` of a character in a font table from Lua
200/// (LuaTeX manual, "Virtual fonts"), which can also run Lua code.
201#[derive(Debug, Clone, PartialEq)]
202pub enum PacketCommand {
203    /// Selects local font `n` (an index into [`VirtualFont::fonts`]).
204    Font(usize),
205    /// Selects the engine font with this id (`vf.fontid`).
206    FontId(u32),
207    /// Sets a character of the current font and moves right by its width.
208    Char(u32),
209    /// Sets a rule (lower left corner at the current point) and moves
210    /// right by its width.
211    Rule {
212        /// Height.
213        height: i32,
214        /// Width.
215        width: i32,
216    },
217    /// Moves right.
218    Right(i32),
219    /// Moves down.
220    Down(i32),
221    /// Saves the position.
222    Push,
223    /// Restores the position saved last.
224    Pop,
225    /// A `\special`.
226    Special(Vec<u8>),
227    /// A PDF literal in a mode of [`crate::pdfext::literal_mode`].
228    Pdf(u8, Vec<u8>),
229    /// Lua code or a Lua function, which the host keeps under this
230    /// reference number (observed: each `lua` command takes one, as
231    /// node data does): the code is run, the function called with the
232    /// font id and the character, when the character is output.
233    Lua(i32),
234    /// A node list, output at the current point; moves right by its
235    /// width. The list belongs to the font.
236    Node(crate::nodes::NodeRef),
237}
238
239impl From<&retex_font::vf::Command> for PacketCommand {
240    fn from(c: &retex_font::vf::Command) -> Self {
241        use retex_font::vf::Command;
242        match c {
243            Command::Font(i) => PacketCommand::Font(*i),
244            Command::Char(c) => PacketCommand::Char(*c),
245            Command::Rule { height, width } => PacketCommand::Rule {
246                height: *height,
247                width: *width,
248            },
249            Command::Right(x) => PacketCommand::Right(*x),
250            Command::Down(y) => PacketCommand::Down(*y),
251            Command::Push => PacketCommand::Push,
252            Command::Pop => PacketCommand::Pop,
253            Command::Special(s) => PacketCommand::Special(s.clone()),
254        }
255    }
256}
257
258/// How deeply virtual fonts may refer to other virtual fonts.
259const VF_MAX_DEPTH: u32 = 8;
260
261/// One part of an extensible OpenType math character (scaled points).
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub struct ExtPart {
264    /// The character of the part.
265    pub code: u32,
266    /// Start connector length.
267    pub start: i32,
268    /// End connector length.
269    pub end: i32,
270    /// Full advance.
271    pub advance: i32,
272    /// Whether the part is repeatable.
273    pub extender: bool,
274}
275
276/// The OpenType math data of a character.
277#[derive(Debug, Clone, Default, PartialEq, Eq)]
278pub struct MathCharInfo {
279    /// The next larger variant.
280    pub next: Option<u32>,
281    /// The top accent attachment point.
282    pub top_accent: Option<i32>,
283    /// Vertical extensible parts, bottom first.
284    pub vert: Option<std::rc::Rc<[ExtPart]>>,
285    /// Horizontal extensible parts, left first.
286    pub horiz: Option<std::rc::Rc<[ExtPart]>>,
287}
288
289/// Named OpenType math constants (luaotfload's `MathConstants`).
290pub type MathConstants = HashMap<String, i32>;
291
292impl Default for FontInfo {
293    fn default() -> Self {
294        FontInfo::null()
295    }
296}
297
298impl FontInfo {
299    /// The null font: no characters, seven zero parameters.
300    pub fn null() -> Self {
301        FontInfo {
302            name: b"nullfont".to_vec(),
303            size: 0,
304            design_size: 0,
305            checksum: 0,
306            params: vec![0; 8],
307            chars: HashMap::default(),
308            hyphen_char: '-' as i32,
309            skew_char: -1,
310            kind: FontKind::Null,
311            handle: 0,
312            char_codes: Default::default(),
313            ignore_ligatures: 0,
314            lig_kern: LigKernMap::default(),
315            right_boundary: None,
316            math_constants: None,
317            math_chars: None,
318            size_spec: i32::MIN,
319            data: None,
320            expansion: None,
321            vf: None,
322        }
323    }
324}
325
326/// A font request from `\font`.
327#[derive(Clone, Debug)]
328pub struct FontRequest {
329    /// The name as written after `\font\cs=`.
330    pub name: Vec<u8>,
331    /// Size: positive is `at` (sp), negative is `scaled` (−1000 × factor),
332    /// −1000 when unspecified.
333    pub size: i32,
334}
335
336/// Loads fonts for the engine (LuaTeX's `define_font` callback).
337pub trait FontLoader {
338    /// Loads the font, or returns `None` if it cannot be found.
339    fn load(&mut self, req: &FontRequest) -> Option<FontInfo>;
340}
341
342/// The table of loaded fonts.
343#[derive(Default)]
344pub struct FontTable {
345    /// Fonts by id; id 0 is `\nullfont`.
346    pub fonts: Vec<FontInfo>,
347    /// Identifier control sequence of each font (TeX's `font_id_text`).
348    pub id_cs: Vec<CsId>,
349    /// The loader, if any.
350    pub loader: Option<Box<dyn FontLoader>>,
351    /// Dense metric tables for fast lookup of small character codes, by
352    /// font id (see [`FontTable::sync_dense`]).
353    pub dense: Vec<DenseMetrics>,
354}
355
356/// Number of character codes covered by [`DenseMetrics`].
357const DENSE_CHARS: usize = 0x800;
358
359/// Metrics of the characters below [`DENSE_CHARS`] in an array.
360#[derive(Clone, Debug, Default)]
361pub struct DenseMetrics {
362    metrics: Box<[CharMetrics]>,
363    present: Box<[u64]>,
364    /// For characters below 256: (start, length) of their lig/kern program
365    /// in `steps`.
366    lk: Box<[(u32, u32)]>,
367    steps: Box<[LigKernStep]>,
368    /// For pairs of characters below 128: index + 1 of their instruction
369    /// in `steps` (0 for none).
370    pairs: Box<[u16]>,
371}
372
373impl FontTable {
374    /// Builds the dense tables of fonts added since the last call. Call
375    /// before hot loops; the character metrics of a font must not change
376    /// afterwards (call [`FontTable::invalidate_dense`] if they do).
377    pub fn sync_dense(&mut self) {
378        while self.dense.len() < self.fonts.len() {
379            let f = &self.fonts[self.dense.len()];
380            let mut metrics = vec![CharMetrics::default(); DENSE_CHARS].into_boxed_slice();
381            let mut present = vec![0u64; DENSE_CHARS / 64].into_boxed_slice();
382            for (&c, m) in &f.chars {
383                if (c as usize) < DENSE_CHARS {
384                    metrics[c as usize] = *m;
385                    present[c as usize / 64] |= 1 << (c % 64);
386                }
387            }
388            let mut lk = Vec::new();
389            let mut steps: Vec<LigKernStep> = Vec::new();
390            let mut pairs = Vec::new();
391            if !f.lig_kern.is_empty() {
392                lk = vec![(0u32, 0u32); 256];
393                pairs = vec![0u16; 128 * 128];
394                for (c, e) in lk.iter_mut().enumerate() {
395                    if let Some(p) = f.lig_kern.get(&(c as u32)) {
396                        *e = (steps.len() as u32, p.len() as u32);
397                        for st in p.iter() {
398                            if c < 128 && st.next < 128 && steps.len() < 0xFFFF {
399                                pairs[c * 128 + st.next as usize] = steps.len() as u16 + 1;
400                            }
401                            steps.push(*st);
402                        }
403                    }
404                }
405            }
406            self.dense.push(DenseMetrics {
407                metrics,
408                present,
409                lk: lk.into_boxed_slice(),
410                steps: steps.into_boxed_slice(),
411                pairs: pairs.into_boxed_slice(),
412            });
413        }
414    }
415
416    /// Forgets the dense table of font `f` and those after it.
417    pub fn invalidate_dense(&mut self, f: u32) {
418        self.dense.truncate(f as usize);
419    }
420
421    /// The metrics of character `c` in font `f`.
422    #[inline]
423    pub fn char_metrics(&self, f: u32, c: u32) -> Option<&CharMetrics> {
424        if let Some(d) = self.dense.get(f as usize)
425            && (c as usize) < DENSE_CHARS
426        {
427            return if d.present[c as usize / 64] & (1 << (c % 64)) != 0 {
428                Some(&d.metrics[c as usize])
429            } else {
430                None
431            };
432        }
433        self.fonts.get(f as usize)?.chars.get(&c)
434    }
435}
436
437impl std::fmt::Debug for FontTable {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        f.debug_struct("FontTable")
440            .field("fonts", &self.fonts.len())
441            .finish_non_exhaustive()
442    }
443}
444
445/// The largest `\fontdimen` number retex accepts.
446pub const MAX_FONT_DIMEN: i32 = 1 << 20;
447
448impl Engine {
449    /// The font info of font `f` (the null font if `f` is invalid).
450    pub fn font(&self, f: u32) -> &FontInfo {
451        self.fonts
452            .fonts
453            .get(f as usize)
454            .unwrap_or(&self.fonts.fonts[0])
455    }
456
457    /// Whether font id `f` exists.
458    pub fn font_exists(&self, f: i32) -> bool {
459        f >= 0 && (f as usize) < self.fonts.fonts.len()
460    }
461
462    /// The identifier control sequence of font `f`.
463    pub fn font_id_cs(&self, f: u32) -> CsId {
464        self.fonts
465            .id_cs
466            .get(f as usize)
467            .copied()
468            .unwrap_or(self.cs.frozen_null_font)
469    }
470
471    /// `\fontdimen 6` (quad) of font `f`.
472    pub fn quad(&self, f: u32) -> i32 {
473        self.font(f).params.get(6).copied().unwrap_or(0)
474    }
475
476    /// `\fontdimen 5` (x-height) of font `f`.
477    pub fn x_height(&self, f: u32) -> i32 {
478        self.font(f).params.get(5).copied().unwrap_or(0)
479    }
480
481    /// Whether font `f` has character `c`.
482    pub fn font_has_char(&self, f: u32, c: u32) -> bool {
483        self.font(f).chars.contains_key(&c)
484    }
485
486    /// `\fontcharwd` etc.: `which` is 0 (wd), 1 (ht), 2 (dp), 3 (ic).
487    pub fn font_char_dimen(&self, f: u32, c: u32, which: u32) -> i32 {
488        match self.font(f).chars.get(&c) {
489            Some(m) => match which {
490                0 => m.width,
491                1 => m.height,
492                2 => m.depth,
493                _ => m.italic,
494            },
495            None => 0,
496        }
497    }
498
499    /// Prints a font name, with ` at …pt` if the size differs from the
500    /// design size.
501    pub fn print_font_name_and_size(&mut self, f: u32) {
502        let (name, size, dsize) = {
503            let fi = self.font(f);
504            (fi.name.clone(), fi.size, fi.design_size)
505        };
506        self.printer.print_bytes(&name);
507        if size != dsize {
508            self.printer.print_str(" at ");
509            self.printer.print_scaled(size);
510            self.printer.print_str("pt");
511        }
512    }
513
514    /// Scans a font identifier (`scan_font_ident`).
515    pub fn scan_font_ident(&mut self) {
516        self.get_nonblank_noncall();
517        let f: i32 = match self.cur_cmd {
518            Cmd::DefFont => self.eqtb.cur_font.val as i32,
519            Cmd::SetFont => self.cur_chr as i32,
520            Cmd::DefFamily => {
521                let size = self.cur_chr;
522                self.scan_math_family();
523                let fam = self.cur_val as u32;
524                self.eqtb.math_font[(size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize].val
525                    as i32
526            }
527            _ => {
528                self.print_err("Missing font identifier");
529                self.help(&[
530                    "I was looking for a control sequence whose",
531                    "current meaning has been defined by \\font.",
532                ]);
533                self.back_error();
534                0
535            }
536        };
537        self.cur_val = f;
538    }
539
540    /// Finds a font dimension (`find_font_dimen`); returns its value and
541    /// records its location in `font_dimen_loc` for assignments. Unlike
542    /// TeX82, LuaTeX extends the parameters of any font (not only the most
543    /// recently loaded one) when a parameter beyond the last is read or
544    /// written; only `n <= 0` is an error. (LuaTeX dies on absurdly large
545    /// `n`; retex reports those as errors too, above [`MAX_FONT_DIMEN`].)
546    pub fn find_font_dimen(&mut self, _writing: bool) -> i32 {
547        self.scan_int();
548        let n = self.cur_val;
549        self.scan_font_ident();
550        let f = self.cur_val as usize;
551        let ok = n > 0 && n <= MAX_FONT_DIMEN;
552        if ok {
553            let params = &mut self.fonts.fonts[f].params;
554            if n as usize >= params.len() {
555                params.resize(n as usize + 1, 0);
556            }
557        }
558        if !ok {
559            self.print_err("Font ");
560            let cs = self.font_id_cs(f as u32);
561            let name = self.hash.name(cs).to_vec();
562            self.print_esc_bytes(&name);
563            self.printer.print_str(" has only ");
564            self.printer
565                .print_int((self.fonts.fonts[f].params.len() as i64 - 1).max(0));
566            self.printer.print_str(" fontdimen parameters");
567            self.help(&[
568                "To increase the number of font parameters, you must",
569                "use \\fontdimen immediately after the \\font is loaded.",
570            ]);
571            self.error();
572            self.font_dimen_loc = None;
573            return 0;
574        }
575        self.font_dimen_loc = Some((f as u32, n as u32));
576        self.fonts.fonts[f].params[n as usize]
577    }
578
579    /// Stores a `\fontdimen` value found by `find_font_dimen`.
580    pub fn set_font_dimen(&mut self, loc: Option<(u32, u32)>, v: i32) {
581        if let Some((f, n)) = loc {
582            self.fonts.fonts[f as usize].params[n as usize] = v;
583        }
584    }
585
586    /// `\hyphenchar`, `\skewchar`, `\ignoreligaturesinfont` values.
587    pub fn font_int_param(&mut self, f: u32, n: u32) -> i32 {
588        match n {
589            0 => self.font(f).hyphen_char,
590            1 => self.font(f).skew_char,
591            2..=5 => {
592                self.scan_char_num();
593                let c = self.cur_val as u32;
594                // \lpcode, \rpcode and \efcode of a character that the
595                // font lacks are 0.
596                if n < 5 && self.fonts.char_metrics(f, c).is_none() {
597                    return 0;
598                }
599                if n == 5 {
600                    // \tagcode reads the TFM tag as flags (lig/kern 1,
601                    // list 2, extensible 4); other fonts give 0 and a
602                    // missing character -1 (observed).
603                    if self.fonts.char_metrics(f, c).is_none() {
604                        return -1;
605                    }
606                    let tag = self
607                        .tfm_of(f)
608                        .and_then(|t| t.char_info(c).map(|ci| ci.tag))
609                        .unwrap_or(0);
610                    return match tag {
611                        1 => 1,
612                        2 => 2,
613                        3 => 4,
614                        _ => 0,
615                    };
616                }
617                self.font(f).char_codes[(n - 2) as usize]
618                    .get(&c)
619                    .copied()
620                    .unwrap_or(if n == 4 { 1000 } else { 0 })
621            }
622            _ => self.font(f).ignore_ligatures,
623        }
624    }
625
626    /// Sets `\hyphenchar`, `\skewchar`, `\ignoreligaturesinfont`.
627    pub fn set_font_int_param(&mut self, f: u32, n: u32, v: i32) {
628        if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
629            match n {
630                0 => fi.hyphen_char = v,
631                1 => fi.skew_char = v,
632                _ => fi.ignore_ligatures = v,
633            }
634        }
635    }
636
637    /// Sets `\lpcode`, `\rpcode`, `\efcode`, `\tagcode`. The first three
638    /// are not set for characters that the font lacks.
639    pub fn set_font_char_code(&mut self, f: u32, n: u32, c: u32, v: i32) {
640        if n < 5 && self.fonts.char_metrics(f, c).is_none() {
641            return;
642        }
643        if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
644            fi.char_codes[(n - 2) as usize].insert(c, v);
645        }
646    }
647
648    /// `\font\cs=name at size` (`new_font`).
649    pub fn new_font(&mut self, global: bool) {
650        if self.job_name.is_none() {
651            self.open_log_file();
652        }
653        self.get_r_token();
654        let u = self.cur_cs;
655        self.define(
656            global,
657            Loc::Cs(u),
658            Val::Meaning(Meaning::prim(Cmd::SetFont, 0)),
659        );
660        self.scan_optional_equals();
661        let name = self.scan_file_name();
662        // Scan the font size specification.
663        self.files.name_in_progress = true;
664        let size: i32 = if self.scan_keyword("at") {
665            self.scan_normal_dimen();
666            let s = self.cur_val;
667            if s <= 0 || s >= 0o1000000000 {
668                self.print_err("Improper `at' size (");
669                self.printer.print_scaled(s);
670                self.printer.print_str("pt), replaced by 10pt");
671                self.help(&[
672                    "I can only handle fonts at positive sizes that are",
673                    "less than 2048pt, so I've changed what you said to 10pt.",
674                ]);
675                self.error();
676                10 * retex_arith::UNITY
677            } else {
678                s
679            }
680        } else if self.scan_keyword("scaled") {
681            self.scan_int();
682            let s = -self.cur_val;
683            if self.cur_val <= 0 || self.cur_val > 32768 {
684                self.print_err("Illegal magnification has been changed to 1000");
685                self.help(&["The magnification ratio must be between 1 and 32768."]);
686                self.int_error(self.cur_val);
687                -1000
688            } else {
689                s
690            }
691        } else {
692            -1000
693        };
694        self.files.name_in_progress = false;
695        // LuaTeX shares no fonts (unlike TeX's §1260): every \font loads
696        // again, through the define_font callback when there is one, which
697        // may return an existing id.
698        let f = {
699            match self.load_font(&name, size) {
700                Some(f) => f,
701                None if self
702                    .eqtb
703                    .int(crate::primitives_gen::assign_int::SUPPRESSFONTNOTFOUNDERROR)
704                    > 0 =>
705                {
706                    0
707                }
708                None => {
709                    self.print_err("Font ");
710                    self.sprint_cs(u);
711                    self.printer.print_char('=' as u32);
712                    self.printer.print_bytes(&name);
713                    if size >= 0 {
714                        // Observed: a whole size has no fraction (`at 12pt`).
715                        self.printer.print_str(" at ");
716                        if size % retex_arith::UNITY == 0 {
717                            self.printer.print_int(i64::from(size / retex_arith::UNITY));
718                        } else {
719                            self.printer.print_scaled(size);
720                        }
721                        self.printer.print_str("pt");
722                    } else if size != -1000 {
723                        self.printer.print_str(" scaled ");
724                        self.printer.print_int(i64::from(-size));
725                    }
726                    self.printer
727                        .print_str(" not loadable: metric data not found or bad");
728                    self.help(&[
729                        "I wasn't able to read the size data for this font,",
730                        "so I will ignore the font specification.",
731                        "[Wizards can fix TFM files using TFtoPL/PLtoTF.]",
732                        "You might try inserting a different font spec;",
733                        "e.g., type `I\\font<same font id>=<substitute font name>'.",
734                    ]);
735                    self.error();
736                    0
737                }
738            }
739        };
740        if f != 0 && self.fonts.fonts[f as usize].size_spec == i32::MIN {
741            self.fonts.fonts[f as usize].size_spec = size;
742        }
743        // The identifier text is that of the control sequence used last,
744        // also when the font existed already (TeX's `common_ending`).
745        if f != 0 {
746            let name = self.hash.name(u).to_vec();
747            let id = self.hash.new_frozen(&name);
748            self.eqtb.ensure_cs(id);
749            self.eqtb.cs[id.0 as usize].val = Meaning::prim(Cmd::SetFont, f);
750            self.fonts.id_cs[f as usize] = id;
751        }
752        self.define(
753            global,
754            Loc::Cs(u),
755            Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
756        );
757    }
758
759    /// Loads a font through the `define_font` callback, or the built-in
760    /// loader when no callback is set, and registers it.
761    pub fn load_font(&mut self, name: &[u8], size: i32) -> Option<u32> {
762        if let Some(h) = self.lua.clone()
763            && h.has_callback("define_font")
764        {
765            return h.define_font(self, name, size);
766        }
767        let req = FontRequest {
768            name: name.to_vec(),
769            size,
770        };
771        if self.fonts.loader.is_none() {
772            return self.load_tfm_font(name, size, name);
773        }
774        let mut loader = self.fonts.loader.take()?;
775        let info = loader.load(&req);
776        self.fonts.loader = Some(loader);
777        let mut info = info?;
778        info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
779        info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
780        Some(self.register_font(info))
781    }
782
783    /// Adds a loaded font to the font table and returns its id.
784    pub fn register_font(&mut self, mut info: FontInfo) -> u32 {
785        if info.hyphen_char == i32::MIN {
786            info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
787        }
788        if info.skew_char == i32::MIN {
789            info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
790        }
791        self.fonts.fonts.push(info);
792        self.fonts.id_cs.push(self.cs.frozen_null_font);
793        self.fonts.fonts.len() as u32 - 1
794    }
795
796    /// LuaTeX's warning for a font from the `define_font` callback without
797    /// characters (such as `dummy.tfm`, which syntonly loads).
798    pub fn check_lua_loaded_font(&mut self, f: u32) {
799        let info = &self.fonts.fonts[f as usize];
800        if info.chars.is_empty() {
801            let msg = format!(
802                "lua-loaded font '{f}' with name '{}' has no characters",
803                String::from_utf8_lossy(&info.name)
804            );
805            self.luatex_warning("font", &msg);
806        }
807    }
808
809    /// LuaTeX's built-in TFM loader (used when no `define_font` callback
810    /// is set, e.g. while building the LaTeX format).
811    pub fn load_tfm(&mut self, name: &[u8], size: i32) -> Option<FontInfo> {
812        let name_s = String::from_utf8_lossy(name).into_owned();
813        let path = self
814            .kpse
815            .as_ref()?
816            .borrow_mut()
817            .find_file(&name_s, retex_kpath::FileFormat::Tfm)?;
818        let data = self.read_file_recorded(&path)?;
819        let tfm = retex_font::tfm::Tfm::parse(&data).ok()?;
820        let scaled = tfm.scaled(size);
821        let mut params = vec![0; scaled.params.len() + 1];
822        params[1..].copy_from_slice(&scaled.params);
823        let mut chars =
824            HashMap::with_capacity_and_hasher(scaled.chars.len(), crate::hash::FastHash::default());
825        for (c, m) in &scaled.chars {
826            chars.insert(
827                u32::from(*c),
828                CharMetrics {
829                    width: m[0],
830                    height: m[1],
831                    depth: m[2],
832                    italic: m[3],
833                    index: u32::from(*c),
834                },
835            );
836        }
837        Some(FontInfo {
838            name: name.to_vec(),
839            size: scaled.size,
840            design_size: tfm.design_size,
841            checksum: tfm.checksum,
842            params,
843            chars,
844            hyphen_char: i32::MIN,
845            skew_char: i32::MIN,
846            kind: FontKind::Tfm,
847            handle: 0,
848            char_codes: Default::default(),
849            ignore_ligatures: 0,
850            lig_kern: lig_kern_table(&tfm, &scaled.kerns),
851            right_boundary: tfm.bchar.map(u32::from),
852            math_constants: None,
853            math_chars: None,
854            size_spec: i32::MIN,
855            data: Some(tfm_font_data(tfm, data)),
856            expansion: None,
857            vf: None,
858        })
859    }
860
861    /// Loads TFM font `tfm_name` at `size` and registers it under the name
862    /// `font_name` (what `\fontname` shows). If there is a virtual font of
863    /// the same name, its local fonts are loaded too (observed in LuaTeX:
864    /// right after the font, in definition order, reusing a loaded TFM font
865    /// of the same name and size) and its packets are attached.
866    pub fn load_tfm_font(&mut self, tfm_name: &[u8], size: i32, font_name: &[u8]) -> Option<u32> {
867        self.load_tfm_font_at_depth(tfm_name, size, font_name, 0)
868    }
869
870    fn load_tfm_font_at_depth(
871        &mut self,
872        tfm_name: &[u8],
873        size: i32,
874        font_name: &[u8],
875        depth: u32,
876    ) -> Option<u32> {
877        let mut info = self.load_tfm(tfm_name, size)?;
878        info.name = font_name.to_vec();
879        let size = info.size;
880        let f = self.register_font(info);
881        if depth < VF_MAX_DEPTH {
882            self.attach_virtual_font(f, tfm_name, size, depth);
883        }
884        Some(f)
885    }
886
887    /// Reads the VF file `name` for font `f` (loaded at `size`), if there
888    /// is one, and loads its local fonts.
889    fn attach_virtual_font(&mut self, f: u32, name: &[u8], size: i32, depth: u32) {
890        let name_s = String::from_utf8_lossy(name).into_owned();
891        let Some(path) = self.kpse.as_ref().and_then(|k| {
892            k.borrow_mut()
893                .find_file(&name_s, retex_kpath::FileFormat::Vf)
894        }) else {
895            return;
896        };
897        let Some(data) = self.read_file_recorded(&path) else {
898            return;
899        };
900        let Ok(vf) = retex_font::vf::Vf::parse(&data) else {
901            return;
902        };
903        let mut fonts = Vec::with_capacity(vf.fonts.len());
904        for (i, lf) in vf.fonts.iter().enumerate() {
905            let lsize = vf.font_size(i, size);
906            let lname = lf.name.as_bytes();
907            let existing = (1..self.fonts.fonts.len()).find(|&g| {
908                let fi = &self.fonts.fonts[g];
909                fi.kind == FontKind::Tfm && fi.size == lsize && fi.name == lname
910            });
911            let id = match existing {
912                Some(g) => g as u32,
913                None => self
914                    .load_tfm_font_at_depth(lname, lsize, lname, depth + 1)
915                    .unwrap_or(0),
916            };
917            fonts.push(id);
918        }
919        let mut packets =
920            HashMap::with_capacity_and_hasher(vf.packets.len(), crate::hash::FastHash::default());
921        for &c in vf.packets.keys() {
922            if let Some(cmds) = vf.commands(c, size) {
923                // Specials in VF files are dropped (observed; those of
924                // fonts from Lua are not).
925                let cmds = cmds
926                    .iter()
927                    .filter(|c| !matches!(c, retex_font::vf::Command::Special(_)))
928                    .map(PacketCommand::from);
929                packets.insert(c, cmds.collect());
930            }
931        }
932        self.fonts.fonts[f as usize].vf = Some(std::rc::Rc::new(VirtualFont { fonts, packets }));
933    }
934
935    /// The lig/kern instruction of font `f` for the pair (`left`, `right`).
936    #[inline]
937    pub fn lig_kern_step(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
938        if let Some(d) = self.fonts.dense.get(f as usize)
939            && left < 256
940        {
941            if d.lk.is_empty() {
942                return None;
943            }
944            if left < 128 && right < 128 {
945                let i = d.pairs[(left * 128 + right) as usize];
946                return if i == 0 {
947                    None
948                } else {
949                    Some(d.steps[i as usize - 1].op)
950                };
951            }
952            let (start, len) = d.lk[left as usize];
953            return d.steps[start as usize..(start + len) as usize]
954                .iter()
955                .find(|s| s.next == right)
956                .map(|s| s.op);
957        }
958        let fi = self.font(f);
959        if fi.lig_kern.is_empty() {
960            return None;
961        }
962        fi.lig_kern
963            .get(&left)?
964            .iter()
965            .find(|s| s.next == right)
966            .map(|s| s.op)
967    }
968
969    /// The kern between characters `left` and `right` of an OpenType font
970    /// loaded in base mode: luaotfload turns its `GPOS` kerns into a TeX
971    /// lig/kern program, which math uses between ordinary characters (text
972    /// is shaped by the luaotfload replacement).
973    #[cold]
974    pub fn base_mode_kern(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
975        let fi = self.font(f);
976        // Observed: not between the characters of OpenType math fonts.
977        if fi.kind != FontKind::OpenType || fi.math_constants.is_some() {
978            return None;
979        }
980        let lf = fi
981            .data
982            .as_ref()?
983            .0
984            .downcast_ref::<std::sync::Arc<retex_font::loader::LoadedFont>>()?;
985        if lf.request.mode != retex_font::request::Mode::Base {
986            return None;
987        }
988        let l = u16::try_from(fi.chars.get(&left)?.index).ok()?;
989        let r = u16::try_from(fi.chars.get(&right)?.index).ok()?;
990        let k = lf.shaper.pair_kern(&lf.file, &lf.tex, l, r);
991        (k != 0).then_some(LigOp::Kern(k))
992    }
993
994    /// The OpenType math data of character `c` in font `f`, if any.
995    pub fn math_char(&self, f: u32, c: u32) -> Option<&MathCharInfo> {
996        self.font(f).math_chars.as_ref()?.get(&c)
997    }
998
999    /// The TFM data of font `f`, for TeX's ligature/kerning of TFM fonts.
1000    pub fn tfm_of(&self, f: u32) -> Option<std::rc::Rc<retex_font::tfm::Tfm>> {
1001        let d = self.font(f).data.as_ref()?;
1002        d.0.downcast_ref::<TfmData>().map(|t| t.tfm.clone())
1003    }
1004
1005    /// `\letterspacefont` (LuaTeX).
1006    pub fn letterspace_font(&mut self, global: bool) {
1007        self.get_r_token();
1008        let u = self.cur_cs;
1009        self.scan_font_ident();
1010        let f = self.cur_val as u32;
1011        self.scan_int();
1012        let _amount = self.cur_val;
1013        // Letterspacing is not modelled yet: alias the font.
1014        self.define(
1015            global,
1016            Loc::Cs(u),
1017            Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
1018        );
1019    }
1020
1021    /// `\copyfont` (LuaTeX).
1022    pub fn copy_font(&mut self, global: bool) {
1023        self.get_r_token();
1024        let u = self.cur_cs;
1025        self.scan_font_ident();
1026        let f = self.cur_val as usize;
1027        let copy = self.fonts.fonts[f].clone();
1028        self.fonts.fonts.push(copy);
1029        self.fonts.id_cs.push(self.cs.frozen_null_font);
1030        let nf = self.fonts.fonts.len() as u32 - 1;
1031        self.define(
1032            global,
1033            Loc::Cs(u),
1034            Val::Meaning(Meaning::prim(Cmd::SetFont, nf)),
1035        );
1036    }
1037}
1038
1039/// Converts a TFM lig/kern program into per-character steps (only the
1040/// first instruction for each right character counts, §545).
1041pub(crate) fn lig_kern_table(tfm: &retex_font::tfm::Tfm, kerns: &[i32]) -> LigKernMap {
1042    let nl = tfm.lig_kern.len();
1043    let read = |start: usize| -> Vec<LigKernStep> {
1044        let mut steps: Vec<LigKernStep> = Vec::new();
1045        let mut k = start;
1046        for _ in 0..nl {
1047            let Some(w) = tfm.lig_kern.get(k) else { break };
1048            if w.skip <= 128 {
1049                let next = u32::from(w.next);
1050                if !steps.iter().any(|s| s.next == next) {
1051                    let op = if w.op >= 128 {
1052                        let ki = 256 * (usize::from(w.op) - 128) + usize::from(w.rem);
1053                        LigOp::Kern(kerns.get(ki).copied().unwrap_or(0))
1054                    } else {
1055                        LigOp::Lig {
1056                            op: w.op,
1057                            chr: u32::from(w.rem),
1058                        }
1059                    };
1060                    steps.push(LigKernStep { next, op });
1061                }
1062            }
1063            if w.skip >= 128 {
1064                break;
1065            }
1066            k += usize::from(w.skip) + 1;
1067        }
1068        steps
1069    };
1070    let mut map = LigKernMap::default();
1071    for c in u32::from(tfm.bc)..=u32::from(tfm.ec) {
1072        if let Some(start) = tfm.lig_kern_start(c) {
1073            let p = read(start);
1074            if !p.is_empty() {
1075                map.insert(c, p.into_boxed_slice());
1076            }
1077        }
1078    }
1079    if let Some(l) = tfm.bchar_label {
1080        let p = read(l);
1081        if !p.is_empty() {
1082            map.insert(LEFT_BOUNDARY, p.into_boxed_slice());
1083        }
1084    }
1085    map
1086}