1mod 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
17pub trait FontSystem: Clone + std::fmt::Debug {
20 type Char: Character;
22 type CS: CSName<Self::Char>;
25 type Int: TeXInt;
28 type Font: Font<Char = Self::Char, CS = Self::CS, Dim = Self::Dim, Int = Self::Int>;
30 type Dim: TeXDimen;
32 fn null(&self) -> Self::Font;
34 fn new<ET: EngineTypes<Char = Self::Char, CSName = Self::CS>>(aux: &mut EngineAux<ET>) -> Self;
36 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
45pub trait Font: Clone + std::fmt::Debug {
49 type Char: Character;
51 type Dim: TeXDimen;
53 type Int: TeXInt;
56 type CS: CSName<Self::Char>;
59 fn get_at(&self) -> Self::Dim;
61 fn has_at_set(&self) -> bool;
63 fn set_at(&mut self, d: Self::Dim);
65 fn name(&self) -> &Self::CS;
67 fn filename(&self) -> &str;
69 fn get_dim(&self, idx: u16) -> Self::Dim;
71 fn set_dim(&mut self, idx: u16, d: Self::Dim);
73 fn get_hyphenchar(&self) -> Self::Int;
75 fn set_hyphenchar(&mut self, c: Self::Int);
77 fn get_skewchar(&self) -> Self::Int;
79 fn set_skewchar(&mut self, c: Self::Int);
81 fn has_char(&self, c: Self::Char) -> bool;
83 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 fn get_wd(&self, c: Self::Char) -> Self::Dim;
92 fn get_ht(&self, c: Self::Char) -> Self::Dim;
94 fn get_dp(&self, c: Self::Char) -> Self::Dim;
96 fn get_ic(&self, c: Self::Char) -> Self::Dim;
98 fn set_ic(&mut self, c: Self::Char, d: Self::Dim);
100 fn ligature(&self, c1: Self::Char, c2: Self::Char) -> Option<Self::Char>;
103}
104
105#[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 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
193pub 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
210pub 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 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}