1use 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
18pub struct CharMetrics {
19 pub width: i32,
21 pub height: i32,
23 pub depth: i32,
25 pub italic: i32,
27 pub index: u32,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum LigOp {
34 Kern(i32),
36 Lig {
40 op: u8,
42 chr: u32,
44 },
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct LigKernStep {
50 pub next: u32,
53 pub op: LigOp,
55}
56
57#[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
86pub type LigKernMap = HashMap<u32, Box<[LigKernStep]>, std::hash::BuildHasherDefault<FxHasher>>;
88
89pub const LEFT_BOUNDARY: u32 = 0x11_0000;
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub enum FontKind {
95 Null,
97 Tfm,
99 OpenType,
101}
102
103#[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#[derive(Debug)]
117pub struct TfmData {
118 pub tfm: std::rc::Rc<retex_font::tfm::Tfm>,
120 pub raw: Vec<u8>,
122}
123
124pub 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#[derive(Clone, Debug)]
134pub struct FontInfo {
135 pub name: Vec<u8>,
137 pub size: i32,
139 pub design_size: i32,
141 pub checksum: u32,
144 pub params: Vec<i32>,
146 pub chars: HashMap<u32, CharMetrics, crate::hash::FastHash>,
148 pub hyphen_char: i32,
150 pub skew_char: i32,
152 pub kind: FontKind,
154 pub handle: u64,
157 pub char_codes: [HashMap<u32, i32>; 4],
159 pub ignore_ligatures: i32,
161 pub data: Option<FontData>,
163 pub lig_kern: LigKernMap,
167 pub right_boundary: Option<u32>,
169 pub expansion: Option<crate::expansion::FontExpansion>,
171 pub math_constants: Option<std::rc::Rc<MathConstants>>,
175 pub math_chars: Option<std::rc::Rc<HashMap<u32, MathCharInfo>>>,
178 pub size_spec: i32,
181 pub vf: Option<std::rc::Rc<VirtualFont>>,
184}
185
186#[derive(Debug, Default)]
189pub struct VirtualFont {
190 pub fonts: Vec<u32>,
193 pub packets: HashMap<u32, Box<[retex_font::vf::Command]>, crate::hash::FastHash>,
195}
196
197const VF_MAX_DEPTH: u32 = 8;
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub struct ExtPart {
203 pub code: u32,
205 pub start: i32,
207 pub end: i32,
209 pub advance: i32,
211 pub extender: bool,
213}
214
215#[derive(Debug, Clone, Default, PartialEq, Eq)]
217pub struct MathCharInfo {
218 pub next: Option<u32>,
220 pub top_accent: Option<i32>,
222 pub vert: Option<std::rc::Rc<[ExtPart]>>,
224 pub horiz: Option<std::rc::Rc<[ExtPart]>>,
226}
227
228pub type MathConstants = HashMap<String, i32>;
230
231impl Default for FontInfo {
232 fn default() -> Self {
233 FontInfo::null()
234 }
235}
236
237impl FontInfo {
238 pub fn null() -> Self {
240 FontInfo {
241 name: b"nullfont".to_vec(),
242 size: 0,
243 design_size: 0,
244 checksum: 0,
245 params: vec![0; 8],
246 chars: HashMap::default(),
247 hyphen_char: '-' as i32,
248 skew_char: -1,
249 kind: FontKind::Null,
250 handle: 0,
251 char_codes: Default::default(),
252 ignore_ligatures: 0,
253 lig_kern: LigKernMap::default(),
254 right_boundary: None,
255 math_constants: None,
256 math_chars: None,
257 size_spec: i32::MIN,
258 data: None,
259 expansion: None,
260 vf: None,
261 }
262 }
263}
264
265#[derive(Clone, Debug)]
267pub struct FontRequest {
268 pub name: Vec<u8>,
270 pub size: i32,
273}
274
275pub trait FontLoader {
277 fn load(&mut self, req: &FontRequest) -> Option<FontInfo>;
279}
280
281#[derive(Default)]
283pub struct FontTable {
284 pub fonts: Vec<FontInfo>,
286 pub id_cs: Vec<CsId>,
288 pub loader: Option<Box<dyn FontLoader>>,
290 pub dense: Vec<DenseMetrics>,
293}
294
295const DENSE_CHARS: usize = 0x800;
297
298#[derive(Clone, Debug, Default)]
300pub struct DenseMetrics {
301 metrics: Box<[CharMetrics]>,
302 present: Box<[u64]>,
303 lk: Box<[(u32, u32)]>,
306 steps: Box<[LigKernStep]>,
307 pairs: Box<[u16]>,
310}
311
312impl FontTable {
313 pub fn sync_dense(&mut self) {
317 while self.dense.len() < self.fonts.len() {
318 let f = &self.fonts[self.dense.len()];
319 let mut metrics = vec![CharMetrics::default(); DENSE_CHARS].into_boxed_slice();
320 let mut present = vec![0u64; DENSE_CHARS / 64].into_boxed_slice();
321 for (&c, m) in &f.chars {
322 if (c as usize) < DENSE_CHARS {
323 metrics[c as usize] = *m;
324 present[c as usize / 64] |= 1 << (c % 64);
325 }
326 }
327 let mut lk = Vec::new();
328 let mut steps: Vec<LigKernStep> = Vec::new();
329 let mut pairs = Vec::new();
330 if !f.lig_kern.is_empty() {
331 lk = vec![(0u32, 0u32); 256];
332 pairs = vec![0u16; 128 * 128];
333 for (c, e) in lk.iter_mut().enumerate() {
334 if let Some(p) = f.lig_kern.get(&(c as u32)) {
335 *e = (steps.len() as u32, p.len() as u32);
336 for st in p.iter() {
337 if c < 128 && st.next < 128 && steps.len() < 0xFFFF {
338 pairs[c * 128 + st.next as usize] = steps.len() as u16 + 1;
339 }
340 steps.push(*st);
341 }
342 }
343 }
344 }
345 self.dense.push(DenseMetrics {
346 metrics,
347 present,
348 lk: lk.into_boxed_slice(),
349 steps: steps.into_boxed_slice(),
350 pairs: pairs.into_boxed_slice(),
351 });
352 }
353 }
354
355 pub fn invalidate_dense(&mut self, f: u32) {
357 self.dense.truncate(f as usize);
358 }
359
360 #[inline]
362 pub fn char_metrics(&self, f: u32, c: u32) -> Option<&CharMetrics> {
363 if let Some(d) = self.dense.get(f as usize)
364 && (c as usize) < DENSE_CHARS
365 {
366 return if d.present[c as usize / 64] & (1 << (c % 64)) != 0 {
367 Some(&d.metrics[c as usize])
368 } else {
369 None
370 };
371 }
372 self.fonts.get(f as usize)?.chars.get(&c)
373 }
374}
375
376impl std::fmt::Debug for FontTable {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 f.debug_struct("FontTable")
379 .field("fonts", &self.fonts.len())
380 .finish_non_exhaustive()
381 }
382}
383
384pub const MAX_FONT_DIMEN: i32 = 1 << 20;
386
387impl Engine {
388 pub fn font(&self, f: u32) -> &FontInfo {
390 self.fonts
391 .fonts
392 .get(f as usize)
393 .unwrap_or(&self.fonts.fonts[0])
394 }
395
396 pub fn font_exists(&self, f: i32) -> bool {
398 f >= 0 && (f as usize) < self.fonts.fonts.len()
399 }
400
401 pub fn font_id_cs(&self, f: u32) -> CsId {
403 self.fonts
404 .id_cs
405 .get(f as usize)
406 .copied()
407 .unwrap_or(self.cs.frozen_null_font)
408 }
409
410 pub fn quad(&self, f: u32) -> i32 {
412 self.font(f).params.get(6).copied().unwrap_or(0)
413 }
414
415 pub fn x_height(&self, f: u32) -> i32 {
417 self.font(f).params.get(5).copied().unwrap_or(0)
418 }
419
420 pub fn font_has_char(&self, f: u32, c: u32) -> bool {
422 self.font(f).chars.contains_key(&c)
423 }
424
425 pub fn font_char_dimen(&self, f: u32, c: u32, which: u32) -> i32 {
427 match self.font(f).chars.get(&c) {
428 Some(m) => match which {
429 0 => m.width,
430 1 => m.height,
431 2 => m.depth,
432 _ => m.italic,
433 },
434 None => 0,
435 }
436 }
437
438 pub fn print_font_name_and_size(&mut self, f: u32) {
441 let (name, size, dsize) = {
442 let fi = self.font(f);
443 (fi.name.clone(), fi.size, fi.design_size)
444 };
445 self.printer.print_bytes(&name);
446 if size != dsize {
447 self.printer.print_str(" at ");
448 self.printer.print_scaled(size);
449 self.printer.print_str("pt");
450 }
451 }
452
453 pub fn scan_font_ident(&mut self) {
455 self.get_nonblank_noncall();
456 let f: i32 = match self.cur_cmd {
457 Cmd::DefFont => self.eqtb.cur_font.val as i32,
458 Cmd::SetFont => self.cur_chr as i32,
459 Cmd::DefFamily => {
460 let size = self.cur_chr;
461 self.scan_math_family();
462 let fam = self.cur_val as u32;
463 self.eqtb.math_font[(size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize].val
464 as i32
465 }
466 _ => {
467 self.print_err("Missing font identifier");
468 self.help(&[
469 "I was looking for a control sequence whose",
470 "current meaning has been defined by \\font.",
471 ]);
472 self.back_error();
473 0
474 }
475 };
476 self.cur_val = f;
477 }
478
479 pub fn find_font_dimen(&mut self, _writing: bool) -> i32 {
486 self.scan_int();
487 let n = self.cur_val;
488 self.scan_font_ident();
489 let f = self.cur_val as usize;
490 let ok = n > 0 && n <= MAX_FONT_DIMEN;
491 if ok {
492 let params = &mut self.fonts.fonts[f].params;
493 if n as usize >= params.len() {
494 params.resize(n as usize + 1, 0);
495 }
496 }
497 if !ok {
498 self.print_err("Font ");
499 let cs = self.font_id_cs(f as u32);
500 let name = self.hash.name(cs).to_vec();
501 self.print_esc_bytes(&name);
502 self.printer.print_str(" has only ");
503 self.printer
504 .print_int((self.fonts.fonts[f].params.len() as i64 - 1).max(0));
505 self.printer.print_str(" fontdimen parameters");
506 self.help(&[
507 "To increase the number of font parameters, you must",
508 "use \\fontdimen immediately after the \\font is loaded.",
509 ]);
510 self.error();
511 self.font_dimen_loc = None;
512 return 0;
513 }
514 self.font_dimen_loc = Some((f as u32, n as u32));
515 self.fonts.fonts[f].params[n as usize]
516 }
517
518 pub fn set_font_dimen(&mut self, loc: Option<(u32, u32)>, v: i32) {
520 if let Some((f, n)) = loc {
521 self.fonts.fonts[f as usize].params[n as usize] = v;
522 }
523 }
524
525 pub fn font_int_param(&mut self, f: u32, n: u32) -> i32 {
527 match n {
528 0 => self.font(f).hyphen_char,
529 1 => self.font(f).skew_char,
530 2..=5 => {
531 self.scan_char_num();
532 let c = self.cur_val as u32;
533 if n < 5 && self.fonts.char_metrics(f, c).is_none() {
536 return 0;
537 }
538 if n == 5 {
539 if self.fonts.char_metrics(f, c).is_none() {
543 return -1;
544 }
545 let tag = self
546 .tfm_of(f)
547 .and_then(|t| t.char_info(c).map(|ci| ci.tag))
548 .unwrap_or(0);
549 return match tag {
550 1 => 1,
551 2 => 2,
552 3 => 4,
553 _ => 0,
554 };
555 }
556 self.font(f).char_codes[(n - 2) as usize]
557 .get(&c)
558 .copied()
559 .unwrap_or(if n == 4 { 1000 } else { 0 })
560 }
561 _ => self.font(f).ignore_ligatures,
562 }
563 }
564
565 pub fn set_font_int_param(&mut self, f: u32, n: u32, v: i32) {
567 if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
568 match n {
569 0 => fi.hyphen_char = v,
570 1 => fi.skew_char = v,
571 _ => fi.ignore_ligatures = v,
572 }
573 }
574 }
575
576 pub fn set_font_char_code(&mut self, f: u32, n: u32, c: u32, v: i32) {
579 if n < 5 && self.fonts.char_metrics(f, c).is_none() {
580 return;
581 }
582 if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
583 fi.char_codes[(n - 2) as usize].insert(c, v);
584 }
585 }
586
587 pub fn new_font(&mut self, global: bool) {
589 if self.job_name.is_none() {
590 self.open_log_file();
591 }
592 self.get_r_token();
593 let u = self.cur_cs;
594 self.define(
595 global,
596 Loc::Cs(u),
597 Val::Meaning(Meaning::prim(Cmd::SetFont, 0)),
598 );
599 self.scan_optional_equals();
600 let name = self.scan_file_name();
601 self.files.name_in_progress = true;
603 let size: i32 = if self.scan_keyword("at") {
604 self.scan_normal_dimen();
605 let s = self.cur_val;
606 if s <= 0 || s >= 0o1000000000 {
607 self.print_err("Improper `at' size (");
608 self.printer.print_scaled(s);
609 self.printer.print_str("pt), replaced by 10pt");
610 self.help(&[
611 "I can only handle fonts at positive sizes that are",
612 "less than 2048pt, so I've changed what you said to 10pt.",
613 ]);
614 self.error();
615 10 * retex_arith::UNITY
616 } else {
617 s
618 }
619 } else if self.scan_keyword("scaled") {
620 self.scan_int();
621 let s = -self.cur_val;
622 if self.cur_val <= 0 || self.cur_val > 32768 {
623 self.print_err("Illegal magnification has been changed to 1000");
624 self.help(&["The magnification ratio must be between 1 and 32768."]);
625 self.int_error(self.cur_val);
626 -1000
627 } else {
628 s
629 }
630 } else {
631 -1000
632 };
633 self.files.name_in_progress = false;
634 let f = {
638 match self.load_font(&name, size) {
639 Some(f) => f,
640 None if self
641 .eqtb
642 .int(crate::primitives_gen::assign_int::SUPPRESSFONTNOTFOUNDERROR)
643 > 0 =>
644 {
645 0
646 }
647 None => {
648 self.print_err("Font ");
649 self.sprint_cs(u);
650 self.printer.print_char('=' as u32);
651 self.printer.print_bytes(&name);
652 if size >= 0 {
653 self.printer.print_str(" at ");
655 if size % retex_arith::UNITY == 0 {
656 self.printer.print_int(i64::from(size / retex_arith::UNITY));
657 } else {
658 self.printer.print_scaled(size);
659 }
660 self.printer.print_str("pt");
661 } else if size != -1000 {
662 self.printer.print_str(" scaled ");
663 self.printer.print_int(i64::from(-size));
664 }
665 self.printer
666 .print_str(" not loadable: metric data not found or bad");
667 self.help(&[
668 "I wasn't able to read the size data for this font,",
669 "so I will ignore the font specification.",
670 "[Wizards can fix TFM files using TFtoPL/PLtoTF.]",
671 "You might try inserting a different font spec;",
672 "e.g., type `I\\font<same font id>=<substitute font name>'.",
673 ]);
674 self.error();
675 0
676 }
677 }
678 };
679 if f != 0 && self.fonts.fonts[f as usize].size_spec == i32::MIN {
680 self.fonts.fonts[f as usize].size_spec = size;
681 }
682 if f != 0 {
685 let name = self.hash.name(u).to_vec();
686 let id = self.hash.new_frozen(&name);
687 self.eqtb.ensure_cs(id);
688 self.eqtb.cs[id.0 as usize].val = Meaning::prim(Cmd::SetFont, f);
689 self.fonts.id_cs[f as usize] = id;
690 }
691 self.define(
692 global,
693 Loc::Cs(u),
694 Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
695 );
696 }
697
698 pub fn load_font(&mut self, name: &[u8], size: i32) -> Option<u32> {
701 if let Some(h) = self.lua.clone()
702 && h.has_callback("define_font")
703 {
704 return h.define_font(self, name, size);
705 }
706 let req = FontRequest {
707 name: name.to_vec(),
708 size,
709 };
710 if self.fonts.loader.is_none() {
711 return self.load_tfm_font(name, size, name);
712 }
713 let mut loader = self.fonts.loader.take()?;
714 let info = loader.load(&req);
715 self.fonts.loader = Some(loader);
716 let mut info = info?;
717 info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
718 info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
719 Some(self.register_font(info))
720 }
721
722 pub fn register_font(&mut self, mut info: FontInfo) -> u32 {
724 if info.hyphen_char == i32::MIN {
725 info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
726 }
727 if info.skew_char == i32::MIN {
728 info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
729 }
730 self.fonts.fonts.push(info);
731 self.fonts.id_cs.push(self.cs.frozen_null_font);
732 self.fonts.fonts.len() as u32 - 1
733 }
734
735 pub fn check_lua_loaded_font(&mut self, f: u32) {
738 let info = &self.fonts.fonts[f as usize];
739 if info.chars.is_empty() {
740 let msg = format!(
741 "lua-loaded font '{f}' with name '{}' has no characters",
742 String::from_utf8_lossy(&info.name)
743 );
744 self.luatex_warning("font", &msg);
745 }
746 }
747
748 pub fn load_tfm(&mut self, name: &[u8], size: i32) -> Option<FontInfo> {
751 let name_s = String::from_utf8_lossy(name).into_owned();
752 let path = self
753 .kpse
754 .as_ref()?
755 .borrow_mut()
756 .find_file(&name_s, retex_kpath::FileFormat::Tfm)?;
757 let data = self.read_file_recorded(&path)?;
758 let tfm = retex_font::tfm::Tfm::parse(&data).ok()?;
759 let scaled = tfm.scaled(size);
760 let mut params = vec![0; scaled.params.len() + 1];
761 params[1..].copy_from_slice(&scaled.params);
762 let mut chars =
763 HashMap::with_capacity_and_hasher(scaled.chars.len(), crate::hash::FastHash::default());
764 for (c, m) in &scaled.chars {
765 chars.insert(
766 u32::from(*c),
767 CharMetrics {
768 width: m[0],
769 height: m[1],
770 depth: m[2],
771 italic: m[3],
772 index: u32::from(*c),
773 },
774 );
775 }
776 Some(FontInfo {
777 name: name.to_vec(),
778 size: scaled.size,
779 design_size: tfm.design_size,
780 checksum: tfm.checksum,
781 params,
782 chars,
783 hyphen_char: i32::MIN,
784 skew_char: i32::MIN,
785 kind: FontKind::Tfm,
786 handle: 0,
787 char_codes: Default::default(),
788 ignore_ligatures: 0,
789 lig_kern: lig_kern_table(&tfm, &scaled.kerns),
790 right_boundary: tfm.bchar.map(u32::from),
791 math_constants: None,
792 math_chars: None,
793 size_spec: i32::MIN,
794 data: Some(tfm_font_data(tfm, data)),
795 expansion: None,
796 vf: None,
797 })
798 }
799
800 pub fn load_tfm_font(&mut self, tfm_name: &[u8], size: i32, font_name: &[u8]) -> Option<u32> {
806 self.load_tfm_font_at_depth(tfm_name, size, font_name, 0)
807 }
808
809 fn load_tfm_font_at_depth(
810 &mut self,
811 tfm_name: &[u8],
812 size: i32,
813 font_name: &[u8],
814 depth: u32,
815 ) -> Option<u32> {
816 let mut info = self.load_tfm(tfm_name, size)?;
817 info.name = font_name.to_vec();
818 let size = info.size;
819 let f = self.register_font(info);
820 if depth < VF_MAX_DEPTH {
821 self.attach_virtual_font(f, tfm_name, size, depth);
822 }
823 Some(f)
824 }
825
826 fn attach_virtual_font(&mut self, f: u32, name: &[u8], size: i32, depth: u32) {
829 let name_s = String::from_utf8_lossy(name).into_owned();
830 let Some(path) = self.kpse.as_ref().and_then(|k| {
831 k.borrow_mut()
832 .find_file(&name_s, retex_kpath::FileFormat::Vf)
833 }) else {
834 return;
835 };
836 let Some(data) = self.read_file_recorded(&path) else {
837 return;
838 };
839 let Ok(vf) = retex_font::vf::Vf::parse(&data) else {
840 return;
841 };
842 let mut fonts = Vec::with_capacity(vf.fonts.len());
843 for (i, lf) in vf.fonts.iter().enumerate() {
844 let lsize = vf.font_size(i, size);
845 let lname = lf.name.as_bytes();
846 let existing = (1..self.fonts.fonts.len()).find(|&g| {
847 let fi = &self.fonts.fonts[g];
848 fi.kind == FontKind::Tfm && fi.size == lsize && fi.name == lname
849 });
850 let id = match existing {
851 Some(g) => g as u32,
852 None => self
853 .load_tfm_font_at_depth(lname, lsize, lname, depth + 1)
854 .unwrap_or(0),
855 };
856 fonts.push(id);
857 }
858 let mut packets =
859 HashMap::with_capacity_and_hasher(vf.packets.len(), crate::hash::FastHash::default());
860 for &c in vf.packets.keys() {
861 if let Some(cmds) = vf.commands(c, size) {
862 packets.insert(c, cmds.into_boxed_slice());
863 }
864 }
865 self.fonts.fonts[f as usize].vf = Some(std::rc::Rc::new(VirtualFont { fonts, packets }));
866 }
867
868 #[inline]
870 pub fn lig_kern_step(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
871 if let Some(d) = self.fonts.dense.get(f as usize)
872 && left < 256
873 {
874 if d.lk.is_empty() {
875 return None;
876 }
877 if left < 128 && right < 128 {
878 let i = d.pairs[(left * 128 + right) as usize];
879 return if i == 0 {
880 None
881 } else {
882 Some(d.steps[i as usize - 1].op)
883 };
884 }
885 let (start, len) = d.lk[left as usize];
886 return d.steps[start as usize..(start + len) as usize]
887 .iter()
888 .find(|s| s.next == right)
889 .map(|s| s.op);
890 }
891 let fi = self.font(f);
892 if fi.lig_kern.is_empty() {
893 return None;
894 }
895 fi.lig_kern
896 .get(&left)?
897 .iter()
898 .find(|s| s.next == right)
899 .map(|s| s.op)
900 }
901
902 #[cold]
907 pub fn base_mode_kern(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
908 let fi = self.font(f);
909 if fi.kind != FontKind::OpenType || fi.math_constants.is_some() {
911 return None;
912 }
913 let lf = fi
914 .data
915 .as_ref()?
916 .0
917 .downcast_ref::<std::sync::Arc<retex_font::loader::LoadedFont>>()?;
918 if lf.request.mode != retex_font::request::Mode::Base {
919 return None;
920 }
921 let l = u16::try_from(fi.chars.get(&left)?.index).ok()?;
922 let r = u16::try_from(fi.chars.get(&right)?.index).ok()?;
923 let k = lf.shaper.pair_kern(&lf.file, &lf.tex, l, r);
924 (k != 0).then_some(LigOp::Kern(k))
925 }
926
927 pub fn math_char(&self, f: u32, c: u32) -> Option<&MathCharInfo> {
929 self.font(f).math_chars.as_ref()?.get(&c)
930 }
931
932 pub fn tfm_of(&self, f: u32) -> Option<std::rc::Rc<retex_font::tfm::Tfm>> {
934 let d = self.font(f).data.as_ref()?;
935 d.0.downcast_ref::<TfmData>().map(|t| t.tfm.clone())
936 }
937
938 pub fn letterspace_font(&mut self, global: bool) {
940 self.get_r_token();
941 let u = self.cur_cs;
942 self.scan_font_ident();
943 let f = self.cur_val as u32;
944 self.scan_int();
945 let _amount = self.cur_val;
946 self.define(
948 global,
949 Loc::Cs(u),
950 Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
951 );
952 }
953
954 pub fn copy_font(&mut self, global: bool) {
956 self.get_r_token();
957 let u = self.cur_cs;
958 self.scan_font_ident();
959 let f = self.cur_val as usize;
960 let copy = self.fonts.fonts[f].clone();
961 self.fonts.fonts.push(copy);
962 self.fonts.id_cs.push(self.cs.frozen_null_font);
963 let nf = self.fonts.fonts.len() as u32 - 1;
964 self.define(
965 global,
966 Loc::Cs(u),
967 Val::Meaning(Meaning::prim(Cmd::SetFont, nf)),
968 );
969 }
970}
971
972pub(crate) fn lig_kern_table(tfm: &retex_font::tfm::Tfm, kerns: &[i32]) -> LigKernMap {
975 let nl = tfm.lig_kern.len();
976 let read = |start: usize| -> Vec<LigKernStep> {
977 let mut steps: Vec<LigKernStep> = Vec::new();
978 let mut k = start;
979 for _ in 0..nl {
980 let Some(w) = tfm.lig_kern.get(k) else { break };
981 if w.skip <= 128 {
982 let next = u32::from(w.next);
983 if !steps.iter().any(|s| s.next == next) {
984 let op = if w.op >= 128 {
985 let ki = 256 * (usize::from(w.op) - 128) + usize::from(w.rem);
986 LigOp::Kern(kerns.get(ki).copied().unwrap_or(0))
987 } else {
988 LigOp::Lig {
989 op: w.op,
990 chr: u32::from(w.rem),
991 }
992 };
993 steps.push(LigKernStep { next, op });
994 }
995 }
996 if w.skip >= 128 {
997 break;
998 }
999 k += usize::from(w.skip) + 1;
1000 }
1001 steps
1002 };
1003 let mut map = LigKernMap::default();
1004 for c in u32::from(tfm.bc)..=u32::from(tfm.ec) {
1005 if let Some(start) = tfm.lig_kern_start(c) {
1006 let p = read(start);
1007 if !p.is_empty() {
1008 map.insert(c, p.into_boxed_slice());
1009 }
1010 }
1011 }
1012 if let Some(l) = tfm.bchar_label {
1013 let p = read(l);
1014 if !p.is_empty() {
1015 map.insert(LEFT_BOUNDARY, p.into_boxed_slice());
1016 }
1017 }
1018 map
1019}