Skip to main content

tex_engine/engine/
fontsystem.rs

1/*! Fonts */
2mod tfm;
3
4use crate::commands::{PrimitiveCommand, ResolvedToken, TeXCommand};
5use crate::engine::filesystem::{File, FileSystem};
6use crate::engine::fontsystem::tfm::TfmFile;
7use crate::engine::{EngineAux, EngineReferences, EngineTypes};
8use crate::prelude::CommandCode;
9use crate::tex::characters::Character;
10use crate::tex::numerics::{Numeric, TeXDimen, TeXInt};
11use crate::tex::tokens::control_sequences::{CSHandler, CSName};
12use crate::utils::errors::{TeXError, TeXResult};
13use crate::utils::{HMap, Ptr};
14use std::path::PathBuf;
15use std::sync::RwLock;
16
17/// A font system provides [`Font`]s, which in turn provide various information about [`Character`]s (or, rather, glyphs)
18/// in that font.
19pub trait FontSystem: Clone + std::fmt::Debug {
20    /// The type of [`Character`]s used by this font system.
21    type Char: Character;
22    /// The type of [control sequences](CSName) used to give a name to a font; returned by e.g. `\the` when followed
23    /// by a font.
24    type CS: CSName<Self::Char>;
25    /// The type of [`TeXInt`]s used by this font system, for e.g. `\skewchar`, `\hyphenchar` etc. - these
26    /// numbers may escape the actual character range of the font, so we use [`TeXInt`]s instead of [`Character`]s.
27    type Int: TeXInt;
28    /// The type of [`Font`]s provided by this font system.
29    type Font: Font<Char = Self::Char, CS = Self::CS, Dim = Self::Dim, Int = Self::Int>;
30    /// The type of [`TeXDimen`]s used by this font system, for e.g. `\fontdimen`, `\fontcharwd` etc.
31    type Dim: TeXDimen;
32    /// Returns the `\nullfont`.
33    fn null(&self) -> Self::Font;
34    /// Creates a new font system.
35    fn new<ET: EngineTypes<Char = Self::Char, CSName = Self::CS>>(aux: &mut EngineAux<ET>) -> Self;
36    /// Creates a new font with the given macroname and filepath (in plain TeX a `.tfm`-file).
37    fn new_font<S: AsRef<str>, F: FileSystem>(
38        &mut self,
39        path: S,
40        macroname: <Self::Font as Font>::CS,
41        fs: &mut F,
42    ) -> Self::Font;
43}
44
45/// A font provides various information about [`Character`]s (or, rather, glyphs) in that font. Since in the [`Stomach`](crate::engine::Stomach)
46/// every glyph in the output is directly associated with a [`Font`], we clone them a lot, so they should be cheap to clone;
47/// e.g. using `Rc` or `Arc` is a good idea.
48pub trait Font: Clone + std::fmt::Debug {
49    /// The type of [`Character`]s used by this font.
50    type Char: Character;
51    /// The type of [`TeXDimen`]s used by this font, for e.g. `\fontdimen`, `\fontcharwd` etc.
52    type Dim: TeXDimen;
53    /// The type of [`TeXInt`]s used by this font, for e.g. `\skewchar`, `\hyphenchar` etc. - these
54    /// numbers may escape the actual character range of the font, so we use [`TeXInt`]s instead of [`Character`]s.
55    type Int: TeXInt;
56    /// The type of [control sequences](CSName) used to give a name to a font; returned by e.g. `\the` when followed
57    /// by a font.
58    type CS: CSName<Self::Char>;
59    /// The size of the font; initially provided by the font itself, but can be set via e.g. `\font\foo=cmr10 at 12pt`.
60    fn get_at(&self) -> Self::Dim;
61    /// Returns whether the size of the font has been set via e.g. `at ...`.
62    fn has_at_set(&self) -> bool;
63    /// Sets the size of the font.
64    fn set_at(&mut self, d: Self::Dim);
65    /// Returns the name of the font as a [`CSName`]; used by e.g. `\the\font`.
66    fn name(&self) -> &Self::CS;
67    /// Returns the filename of the font.
68    fn filename(&self) -> &str;
69    /// Returns the `\fontdimen` at the given index (-1); i.e. `\fontdimen5` returns `get_dim(4)`.
70    fn get_dim(&self, idx: u16) -> Self::Dim;
71    /// Sets the `\fontdimen` at the given index (-1); i.e. `\fontdimen5=...` calls `set_dim(4)`.
72    fn set_dim(&mut self, idx: u16, d: Self::Dim);
73    /// Returns the `\hyphenchar` of this font.
74    fn get_hyphenchar(&self) -> Self::Int;
75    /// Sets the `\hyphenchar` of this font.
76    fn set_hyphenchar(&mut self, c: Self::Int);
77    /// Returns the `\skewchar` of this font.
78    fn get_skewchar(&self) -> Self::Int;
79    /// Sets the `\skewchar` of this font.
80    fn set_skewchar(&mut self, c: Self::Int);
81    /// Returns whether this font has a glyph for the given [`Character`].
82    fn has_char(&self, c: Self::Char) -> bool;
83    /// Writes a human-readable representation of this font to the given [`std::fmt::Write`]r
84    /// - i.e. `<`[`name()`](Self::name)`>[ at <`[`get_at()`](Self::get_at)`>]`
85    fn display<W: std::fmt::Write>(
86        &self,
87        i: &<Self::CS as CSName<Self::Char>>::Handler,
88        w: W,
89    ) -> std::fmt::Result;
90    /// Returns the width the given [`Character`] in this font.
91    fn get_wd(&self, c: Self::Char) -> Self::Dim;
92    /// Returns the height the given [`Character`] in this font.
93    fn get_ht(&self, c: Self::Char) -> Self::Dim;
94    /// Returns the depth the given [`Character`] in this font.
95    fn get_dp(&self, c: Self::Char) -> Self::Dim;
96    /// Returns the litalic correction of the given [`Character`] in this font.
97    fn get_ic(&self, c: Self::Char) -> Self::Dim;
98    /// Sets the litalic correction of the given [`Character`] in this font.
99    fn set_ic(&mut self, c: Self::Char, d: Self::Dim);
100    /// Returns the ligature of the given [`Character`]s in this font, if any; e.g. most fonts
101    /// combine `-` and `-` into an endash.
102    fn ligature(&self, c1: Self::Char, c2: Self::Char) -> Option<Self::Char>;
103}
104
105/// A font system for `.tfm`-files, as used by plain TeX, eTeX and pdfTeX for [`Character`]`=u8`
106#[derive(Clone, Debug)]
107pub struct TfmFontSystem<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> {
108    files: HMap<PathBuf, Ptr<TfmFile>>,
109    null: Ptr<TfmFontI<I, D, CS>>,
110}
111
112impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> FontSystem for TfmFontSystem<I, D, CS> {
113    type Char = u8;
114    type Int = I;
115    type Font = TfmFont<I, D, CS>;
116    type Dim = D;
117    type CS = CS;
118
119    fn new<ET: EngineTypes<Char = Self::Char, CSName = Self::CS>>(aux: &mut EngineAux<ET>) -> Self {
120        let null_file = TfmFile {
121            hyphenchar: 45,
122            skewchar: 255,
123            dimen: vec![],
124            size: 0,
125            //typestr:"nullfont".to_string(),
126            widths: [0.0; 256],
127            defined: [false; 256],
128            heights: [0.0; 256],
129            depths: [0.0; 256],
130            ics: [0.0; 256],
131            ligs: HMap::default(),
132            filepath: std::path::PathBuf::from("/nullfont"),
133        };
134        let muts = Mutables::default();
135        let null = Ptr::new(TfmFontI {
136            file: Ptr::new(null_file),
137            muts: RwLock::new(muts),
138            name: aux.memory.cs_interner_mut().cs_from_str("nullfont"),
139        });
140        TfmFontSystem {
141            files: HMap::default(),
142            null,
143        }
144    }
145
146    fn new_font<S: AsRef<str>, F: FileSystem>(
147        &mut self,
148        path: S,
149        macroname: CS,
150        fs: &mut F,
151    ) -> Self::Font {
152        let path = path.as_ref();
153        let f = if path.ends_with(".tfm") {
154            fs.get(path)
155        } else {
156            fs.get(format!("{path}.tfm"))
157        };
158        let ff = match self.files.get(f.path()) {
159            Some(ff) => ff.clone(),
160            None => {
161                let ff = Ptr::new(TfmFile::new(f.path().to_path_buf()));
162                self.files.insert(f.path().to_path_buf(), ff.clone());
163                ff
164            }
165        };
166        let muts = Mutables::default();
167        let font = TfmFontI {
168            file: ff,
169            muts: RwLock::new(muts),
170            name: macroname,
171        };
172        Ptr::new(font)
173    }
174
175    fn null(&self) -> Self::Font {
176        self.null.clone()
177    }
178}
179
180#[derive(Default)]
181pub(crate) struct Mutables<I: TeXInt, D: TeXDimen + Numeric<I>> {
182    at: Option<D>,
183    #[cfg(feature = "pdflatex")]
184    pub(crate) lps: HMap<u8, I>,
185    #[cfg(feature = "pdflatex")]
186    pub(crate) rps: HMap<u8, I>,
187    ics: HMap<u8, D>,
188    hyphenchar: Option<I>,
189    skewchar: Option<I>,
190    dimens: Vec<D>,
191}
192
193/// See [`TfmFont`].
194pub struct TfmFontI<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> {
195    file: Ptr<TfmFile>,
196    name: CS,
197    pub(crate) muts: RwLock<Mutables<I, D>>,
198}
199impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> PartialEq for TfmFontI<I, D, CS> {
200    fn eq(&self, other: &Self) -> bool {
201        self.name == other.name
202    }
203}
204impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> std::fmt::Debug for TfmFontI<I, D, CS> {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        write!(f, "Font {:?}", self.name)
207    }
208}
209
210/// A [`Font`] represented by a `.tfm`-file, as used by plain TeX, eTeX and pdfTeX for [`Character`]`=u8`;
211/// defined as `Rc<`[`TfmFontI`]`>` for clonability.
212pub type TfmFont<I, D, CS> = Ptr<TfmFontI<I, D, CS>>;
213impl<I: TeXInt, D: TeXDimen + Numeric<I>, CS: CSName<u8>> Font for TfmFont<I, D, CS> {
214    type Char = u8;
215    type CS = CS;
216    type Int = I;
217    type Dim = D;
218
219    fn has_char(&self, c: Self::Char) -> bool {
220        self.file.defined[c as usize]
221    }
222    fn get_hyphenchar(&self) -> I {
223        match self.muts.read().unwrap().hyphenchar {
224            Some(c) => c,
225            None => (self.file.hyphenchar as i32).into(),
226        }
227    }
228    fn set_hyphenchar(&mut self, c: I) {
229        self.muts.write().unwrap().hyphenchar = Some(c);
230    }
231    fn get_skewchar(&self) -> I {
232        match self.muts.read().unwrap().skewchar {
233            Some(c) => c,
234            None => (self.file.skewchar as i32).into(),
235        }
236    }
237    fn set_skewchar(&mut self, c: I) {
238        self.muts.write().unwrap().skewchar = Some(c);
239    }
240    fn get_at(&self) -> Self::Dim {
241        let at = self.muts.read().unwrap().at;
242        match at {
243            Some(d) => d,
244            None => D::from_sp(self.file.size),
245        }
246    }
247    fn has_at_set(&self) -> bool {
248        self.muts.read().unwrap().at.is_some()
249    }
250    fn get_dim(&self, idx: u16) -> Self::Dim {
251        match self.muts.read().unwrap().dimens.get(idx as usize) {
252            Some(d) => *d,
253            None => match self.file.dimen.get(idx as usize) {
254                Some(d) => self.get_at().scale_float(*d as f64),
255                None => D::default(),
256            },
257        }
258    }
259    fn set_dim(&mut self, idx: u16, d: Self::Dim) {
260        let v = &mut self.muts.write().unwrap().dimens;
261        if idx as usize >= v.len() {
262            v.resize(idx as usize + 1, D::default());
263        }
264        v[idx as usize] = d;
265    }
266
267    fn set_at(&mut self, d: Self::Dim) {
268        self.muts.write().unwrap().at = Some(d);
269    }
270
271    fn name(&self) -> &Self::CS {
272        &self.name
273    }
274
275    fn filename(&self) -> &str {
276        self.file.name()
277    }
278    fn display<W: std::fmt::Write>(
279        &self,
280        _i: &<Self::CS as CSName<u8>>::Handler,
281        mut w: W,
282    ) -> std::fmt::Result {
283        let at = self.muts.read().unwrap().at;
284        match at {
285            Some(d) => write!(w, "{} at {}", self.file.name(), d),
286            None => write!(w, "{}", self.file.name()),
287        }
288    }
289
290    fn get_ic(&self, c: Self::Char) -> Self::Dim {
291        let v = &mut self.muts.write().unwrap().ics;
292        match v.get(&c) {
293            Some(d) => *d,
294            None => {
295                let d = self.file.ics[c as usize];
296                self.get_at().scale_float(d as f64)
297            }
298        }
299    }
300    fn set_ic(&mut self, c: Self::Char, d: Self::Dim) {
301        let v = &mut self.muts.write().unwrap().ics;
302        v.insert(c, d);
303    }
304
305    fn get_wd(&self, c: Self::Char) -> Self::Dim {
306        let d = self.file.widths[c as usize].max(0.0);
307        self.get_at().scale_float(d as f64)
308    }
309
310    fn get_ht(&self, c: Self::Char) -> Self::Dim {
311        let d = self.file.heights[c as usize].max(0.0);
312        self.get_at().scale_float(d as f64)
313    }
314
315    fn get_dp(&self, c: Self::Char) -> Self::Dim {
316        let d = self.file.depths[c as usize].max(0.0);
317        self.get_at().scale_float(d as f64)
318    }
319
320    fn ligature(&self, char1: Self::Char, char2: Self::Char) -> Option<Self::Char> {
321        self.file.ligs.get(&(char1, char2)).copied()
322    }
323}
324
325impl<ET: EngineTypes> EngineReferences<'_, ET> {
326    /// reads a font from the input stream (e.g. `\font` for the current font
327    /// or a font defined via `\font\foo=...`).
328    pub fn read_font(
329        &mut self,
330        skip_eq: bool,
331        token: &ET::Token,
332    ) -> TeXResult<<ET::FontSystem as FontSystem>::Font, ET> {
333        let mut had_eq = !skip_eq;
334        crate::expand_loop!(self,token,
335            ResolvedToken::Cmd(Some(c)) => match c {
336                TeXCommand::Font(f) => return Ok(f.clone()),
337                TeXCommand::Primitive{cmd:PrimitiveCommand::FontCmd{read,..},..} => {
338                    return read(self,token)
339                }
340                _ => {
341                    self.general_error("Missing font identifier.".to_string())?;
342                    return Ok(self.fontsystem.null())
343                }
344            }
345            ResolvedToken::Tk {char,code:CommandCode::Other} if !had_eq && matches!(char.try_into(),Ok(b'=')) => {
346                had_eq = true;
347            }
348            _ => {
349                self.general_error("Missing font identifier.".to_string())?;
350                return Ok(self.fontsystem.null())
351            }
352        );
353        TeXError::file_end_while_use(self.aux, self.state, self.mouth, token)?;
354        Ok(self.fontsystem.null())
355    }
356}