1mod 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#[derive(Debug, Clone)]
19pub struct FontInfo {
20 pub tfm_name: Box<str>,
22 enc_file: Box<str>,
26 pfx_file: Box<str>,
27 vf_file: bool,
28 pub styles: ModifierSeq,
30 glyphlist: Option<usize>,
31 pub weblink: Option<(&'static str, &'static str)>,
33}
34
35#[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 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 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 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 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(); 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 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 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 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 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}