Skip to main content

tex_glyphs/
encodings.rs

1/*! Glyph lists for fonts, font attributes etc. */
2mod enc_files;
3mod pfx_files;
4mod vf_files;
5
6use crate::fontstyles::{FontModifier, ModifierSeq};
7use crate::glyphs::{Glyph, GlyphI, UNDEFINED_LIST};
8use crate::parsing::Parser;
9use crate::{GlyphList, PATCHED_TABLES};
10use std::fmt::{Display, Write};
11use std::fs::File;
12use std::io::{BufRead, BufReader};
13use std::path::{Path, PathBuf};
14
15type HMap<A, B> = rustc_hash::FxHashMap<A, B>;
16
17/// Information about a font
18#[derive(Debug, Clone)]
19pub struct FontInfo {
20    /// The name of the `.tfm` file
21    pub tfm_name: Box<str>,
22    //ps_name: Box<str>,
23    //fontflags:u32,
24    //special: Vec<Box<str>>,
25    enc_file: Box<str>,
26    pfx_file: Box<str>,
27    vf_file: bool,
28    /// The [`FontModifier`]s of this font
29    pub styles: ModifierSeq,
30    glyphlist: Option<usize>,
31    /// The CSS name and web-font link for this font
32    pub weblink: Option<(&'static str, &'static str)>,
33}
34
35/// A font info store takes care of parsing the `pdftex.map` file and the `.enc` and `.pfb` files
36/// to obtain information about fonts.
37#[derive(Debug, Clone)]
38pub struct FontInfoStore<S: AsRef<str>, F: FnMut(&str) -> S> {
39    pdftex_map: HMap<Box<str>, FontInfo>,
40    enc_files: HMap<Box<str>, Vec<(Box<str>, usize)>>,
41    pfb_files: HMap<Box<str>, usize>,
42    glyph_lists: Vec<GlyphList>,
43    get: F,
44}
45
46impl<S: AsRef<str>, F: FnMut(&str) -> S> FontInfoStore<S, F> {
47    /// Create a new font info store. The function `get` is used to obtain the
48    /// full file paths of files based on their names; the canonical
49    /// implementation would be to call [`kpsewhich`](https://ctan.org/pkg/kpsewhich).
50    pub fn new(get: F) -> Self {
51        let mut map = HMap::default();
52        parse_pdftex_map(&mut map);
53        include!(concat!(env!("OUT_DIR"), "/codegen_patch.rs"));
54
55        Self {
56            pdftex_map: map,
57            enc_files: HMap::default(),
58            pfb_files: HMap::default(),
59            glyph_lists: PATCHED_TABLES.to_vec(),
60            get,
61        }
62    }
63    /// Displays the [`FontInfo`] and [`GlyphList`] of a font as a Markdown table, as it would
64    /// appear in [`patches.md`](https://github.com/Jazzpirate/RusTeX/blob/main/tex-glyphs/src/resources/patches.md).
65    pub fn display_encoding<S2: AsRef<str>>(&mut self, name: S2) -> Option<impl Display + '_> {
66        if let Some(u) = self.get_glyphlist_i(&name) {
67            let enc = self.pdftex_map.get(name.as_ref())?;
68            Some(DisplayEncoding {
69                enc,
70                list: self.glyph_lists.get(u)?,
71            })
72        } else {
73            None
74        }
75    }
76    /// Get the [`FontInfo`] for a font
77    pub fn get_info<S2: AsRef<str>>(&self, s: S2) -> Option<&FontInfo> {
78        self.pdftex_map.get(s.as_ref())
79    }
80    #[allow(dead_code)]
81    pub(crate) fn all_encs(&self) -> impl Iterator<Item = &FontInfo> {
82        self.pdftex_map.values()
83    }
84    /// Get the [`GlyphList`] for a font
85    pub fn get_glyphlist<S2: AsRef<str>>(&mut self, name: S2) -> &GlyphList {
86        match self.get_glyphlist_i(name) {
87            None => &UNDEFINED_LIST,
88            Some(u) => self.glyph_lists.get(u).unwrap_or_else(|| unreachable!()),
89        }
90    }
91
92    fn add_list(e: &mut Vec<GlyphList>, list: GlyphList) -> usize {
93        e.iter().position(|e| *e == list).unwrap_or_else(|| {
94            let i = e.len();
95            e.push(list);
96            i
97        })
98    }
99
100    #[allow(clippy::too_many_lines)]
101    fn get_glyphlist_i<S2: AsRef<str>>(&mut self, name: S2) -> Option<usize> {
102        let mut enc = match self.pdftex_map.get(name.as_ref()) {
103            Some(enc) => {
104                if let Some(idx) = enc.glyphlist {
105                    return Some(idx);
106                }
107                enc.clone()
108            }
109            _ => FontInfo {
110                tfm_name: name.as_ref().into(),
111                enc_file: "".into(),
112                pfx_file: "".into(),
113                vf_file: false,
114                styles: ModifierSeq::empty(),
115                glyphlist: None,
116                weblink: None,
117            },
118        };
119        if !enc.enc_file.is_empty() {
120            match self.enc_files.get(&enc.enc_file) {
121                Some(e) if e.len() == 1 => {
122                    let idx = e[0].1;
123                    enc.glyphlist = Some(idx);
124                    self.pdftex_map.insert(name.as_ref().into(), enc);
125                    return Some(idx);
126                }
127                None => {
128                    let f = (self.get)(enc.enc_file.as_ref());
129                    if PathBuf::from(f.as_ref()).exists() {
130                        let ls = enc_files::parse_enc(f.as_ref());
131                        let retls = ls
132                            .into_iter()
133                            .map(|l| (l.0, Self::add_list(&mut self.glyph_lists, l.1)))
134                            .collect::<Vec<_>>();
135                        self.enc_files.insert(enc.enc_file.clone(), retls.clone());
136                        if retls.len() == 1 {
137                            enc.glyphlist = Some(retls[0].1);
138                            self.pdftex_map.insert(name.as_ref().into(), enc);
139                            return Some(retls[0].1);
140                        }
141                    }
142                }
143                _ => (),
144            }
145            enc.enc_file = "".into();
146        }
147        if !enc.pfx_file.is_empty() {
148            if let Some(idx) = self.pfb_files.get(&enc.pfx_file) {
149                enc.glyphlist = Some(*idx);
150                self.pdftex_map.insert(name.as_ref().into(), enc);
151                return Some(*idx);
152            }
153            if enc.pfx_file.ends_with(".pfb") || enc.pfx_file.ends_with(".pfa") {
154                let f = (self.get)(enc.pfx_file.as_ref());
155                if PathBuf::from(f.as_ref()).exists() {
156                    if let Some(ls) = pfx_files::parse_pfb(f.as_ref(), &mut enc.styles) {
157                        let idx = Self::add_list(&mut self.glyph_lists, ls);
158                        self.pfb_files.insert(enc.pfx_file.clone(), idx);
159                        enc.glyphlist = Some(idx);
160                        self.pdftex_map.insert(name.as_ref().into(), enc);
161                        return Some(idx);
162                    }
163                }
164            }
165            enc.pfx_file = "".into();
166        }
167        if enc.vf_file {
168            self.pdftex_map.insert(name.as_ref().into(), enc);
169            return None;
170        }
171        enc.vf_file = true;
172        let file = format!("{}.vf", enc.tfm_name);
173        let file = (self.get)(&file);
174        let path = Path::new(file.as_ref());
175        if !path.is_file() {
176            self.pdftex_map.insert(name.as_ref().into(), enc);
177            return None;
178        }
179        let m = match vf_files::parse_vf(file.as_ref()) {
180            None => {
181                self.pdftex_map.insert(name.as_ref().into(), enc);
182                return None;
183            }
184            Some(m) => m,
185        };
186        let deps = m
187            .deps
188            .into_iter()
189            .map(|d| self.get_glyphlist_i(d))
190            .collect::<Vec<_>>();
191        let mut table = UNDEFINED_LIST.clone();
192        for (idx, v) in m.maps.into_iter().enumerate() {
193            let mut gls = v
194                .into_iter()
195                .flat_map(
196                    |(f, i)| match deps.get(f as usize).unwrap_or_else(|| unreachable!()) {
197                        None => vec![GlyphI::S(0)],
198                        Some(k) => match self
199                            .glyph_lists
200                            .get(*k)
201                            .unwrap_or_else(|| unreachable!())
202                            .get(i)
203                            .0
204                        {
205                            GlyphI::S(0) | GlyphI::Undefined(_) => vec![GlyphI::S(0)],
206                            GlyphI::S(i) => vec![GlyphI::S(i)],
207                            GlyphI::Unicode(c) => vec![GlyphI::Unicode(c)],
208                            GlyphI::Ls(ls) => ls.into_vec(),
209                        },
210                    },
211                )
212                .collect::<Vec<_>>();
213            let gl = if gls.len() == 1 {
214                Glyph(gls.pop().unwrap_or_else(|| unreachable!()))
215            } else if gls.is_empty() {
216                Glyph(GlyphI::S(0))
217            } else {
218                Glyph(GlyphI::Ls(gls.into()))
219            };
220            table.0[idx] = gl;
221        }
222        let i = Self::add_list(&mut self.glyph_lists, table);
223        enc.glyphlist = Some(i);
224        self.pdftex_map.insert(name.as_ref().into(), enc);
225        Some(i)
226    }
227}
228
229fn patch(
230    map: &mut HMap<Box<str>, FontInfo>,
231    name: &'static str,
232    modifiers: ModifierSeq,
233    table: Option<usize>,
234    link: Option<(&'static str, &'static str)>,
235) {
236    if let Some(enc) = map.get_mut(name) {
237        enc.styles = modifiers;
238        enc.glyphlist = table;
239        enc.weblink = link;
240    } else {
241        let enc = FontInfo {
242            tfm_name: name.into(),
243            enc_file: "".into(),
244            pfx_file: "".into(),
245            vf_file: false,
246            styles: modifiers,
247            glyphlist: table,
248            weblink: link,
249        };
250        map.insert(name.into(), enc);
251    }
252}
253
254fn parse_pdftex_map(map: &mut HMap<Box<str>, FontInfo>) {
255    let enc_file = std::str::from_utf8(
256        std::process::Command::new("kpsewhich")
257            .arg("pdftex.map")
258            .output()
259            .expect("kpsewhich not found!")
260            .stdout
261            .as_slice(),
262    )
263    .expect("kpsewhich failed")
264    .trim()
265    .to_string(); //get("pdftex.map");
266    let lines = BufReader::new(File::open(enc_file).expect("File does not exist")).lines();
267    for l in lines {
268        let line = l.expect("error reading file");
269        if line.starts_with('%') {
270            continue;
271        }
272        let res = parse_pdftex_map_line(&line);
273        map.insert(res.tfm_name.clone(), res);
274    }
275}
276
277#[allow(clippy::cognitive_complexity)]
278#[allow(clippy::case_sensitive_file_extension_comparisons)]
279fn parse_pdftex_map_line(st: &str) -> FontInfo {
280    let mut s = Parser::new(st);
281    while s.ends_with('\'') || s.ends_with(' ') {
282        s.drop_right(1);
283    }
284    let tfm_name: Box<str> = s.read_until_ws().into();
285
286    //let mut ps_name = String::new();
287    //let mut fontflags = 0;
288    //let mut special = vec!();
289    let mut enc_file = String::new();
290    let mut pfx_file = String::new();
291    let mut styles = ModifierSeq::empty();
292
293    macro_rules! modify {
294        ($s:expr) => {
295            let lc = $s.to_lowercase();
296            if lc.contains("bol") {
297                styles.add(FontModifier::Bold);
298            }
299            if lc.contains("ita") {
300                styles.add(FontModifier::Italic);
301            }
302            if lc.contains("obl") {
303                styles.add(FontModifier::Oblique);
304            }
305            if lc.contains("-sc") {
306                styles.add(FontModifier::Capitals);
307            }
308            if lc.contains("blackboard") {
309                styles.add(FontModifier::Blackboard);
310            }
311        };
312    }
313
314    modify!(tfm_name);
315
316    while !s.is_empty() {
317        if s.starts_with('\"') {
318            // special
319            s.skip(1);
320            s.read_until('\"');
321            continue;
322        }
323        if s.drop("<[") || s.drop("<<") || s.drop("<") {
324            let f: String = s.read_until_ws().into();
325            if f.ends_with(".pfb") || f.ends_with(".pfa") || f.ends_with(".ttf") {
326                pfx_file = f;
327                modify!(pfx_file);
328            } else if f.ends_with(".enc") {
329                enc_file = f;
330                modify!(enc_file);
331            }
332            continue;
333        }
334        if s.starts_with_digit() {
335            // fontflags
336            s.read_digit();
337            continue;
338        }
339        let ps_name = s.read_until_ws();
340        modify!(ps_name);
341    }
342
343    FontInfo {
344        tfm_name,
345        //ps_name:ps_name.into(),fontflags,special:special.into(),
346        enc_file: enc_file.into(),
347        pfx_file: pfx_file.into(),
348        vf_file: false,
349        styles,
350        glyphlist: None,
351        weblink: None,
352    }
353}
354
355struct DisplayEncoding<'a> {
356    enc: &'a FontInfo,
357    list: &'a GlyphList,
358}
359impl Display for DisplayEncoding<'_> {
360    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361        f.write_str("| `.tfm` name                          | Modifiers | Table (Optional)  | External Font (Optional)                                             |\n")?;
362        f.write_str("|--------------------------------------|-----------|-------------------|----------------------------------------------------------------------|\n")?;
363        write!(f, "| {} | ", self.enc.tfm_name)?;
364        if self.enc.styles.bold {
365            f.write_char('b')?;
366        }
367        if self.enc.styles.italic {
368            f.write_char('i')?;
369        }
370        if self.enc.styles.oblique {
371            f.write_char('o')?;
372        }
373        if self.enc.styles.sans_serif {
374            f.write_char('s')?;
375        }
376        if self.enc.styles.monospaced {
377            f.write_char('m')?;
378        }
379        if self.enc.styles.capitals {
380            f.write_char('c')?;
381        }
382        if self.enc.styles.script {
383            f.write_char('S')?;
384        }
385        if self.enc.styles.blackboard {
386            f.write_char('B')?;
387        }
388        if self.enc.styles.fraktur {
389            f.write_char('f')?;
390        }
391        write!(f, " | {}_table | ", self.enc.tfm_name)?;
392        match self.enc.weblink {
393            None => f.write_char('|')?,
394            Some((url, name)) => write!(f, "{name} {url} |")?,
395        }
396        write!(f, "\n\n- {}_table\n\n", self.enc.tfm_name)?;
397        f.write_str("| \\_x\\_  | 0   | 1   | 2    | 3   | 4   | 5   | 6   | 7   | 8   | 9   | A   | B    | C    | D    | E     | F     |\n")?;
398        f.write_str("|--------|-----|-----|------|-----|-----|-----|-----|-----|-----|-----|-----|------|------|------|-------|-------|\n")?;
399        for i in 0..16 {
400            write!(f, "| **{i:X}x** | ")?;
401            for j in 0..16 {
402                match &self.list.0[i * 16 + j].0 {
403                    GlyphI::S(0) => f.write_char('|')?,
404                    GlyphI::S(g) => write!(f, "/{} | ", crate::GLYPH_NAMES[*g as usize])?,
405                    GlyphI::Unicode(c) => write!(f, "\\u{:04X} | ", *c as u32)?,
406                    g @ GlyphI::Ls(_) => write!(
407                        f,
408                        "`{}` | ",
409                        g.to_string().replace('`', "\\`").replace('|', "\\|")
410                    )?,
411                    GlyphI::Undefined(_) => f.write_char('|')?,
412                }
413            }
414            f.write_char('\n')?;
415        }
416        Ok(())
417    }
418}