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 existing = self
638 .fonts
639 .fonts
640 .iter()
641 .enumerate()
642 .skip(1)
643 .find(|(_, fi)| fi.name == name && fi.size_spec == size)
644 .map(|(i, _)| i);
645 let f = match existing {
646 Some(i) => i as u32,
647 None => match self.load_font(&name, size) {
648 Some(f) => f,
649 None if self
650 .eqtb
651 .int(crate::primitives_gen::assign_int::SUPPRESSFONTNOTFOUNDERROR)
652 > 0 =>
653 {
654 0
655 }
656 None => {
657 self.print_err("Font ");
658 self.sprint_cs(u);
659 self.printer.print_char('=' as u32);
660 self.printer.print_bytes(&name);
661 if size >= 0 {
662 self.printer.print_str(" at ");
664 if size % retex_arith::UNITY == 0 {
665 self.printer.print_int(i64::from(size / retex_arith::UNITY));
666 } else {
667 self.printer.print_scaled(size);
668 }
669 self.printer.print_str("pt");
670 } else if size != -1000 {
671 self.printer.print_str(" scaled ");
672 self.printer.print_int(i64::from(-size));
673 }
674 self.printer
675 .print_str(" not loadable: metric data not found or bad");
676 self.help(&[
677 "I wasn't able to read the size data for this font,",
678 "so I will ignore the font specification.",
679 "[Wizards can fix TFM files using TFtoPL/PLtoTF.]",
680 "You might try inserting a different font spec;",
681 "e.g., type `I\\font<same font id>=<substitute font name>'.",
682 ]);
683 self.error();
684 0
685 }
686 },
687 };
688 if f != 0 && self.fonts.fonts[f as usize].size_spec == i32::MIN {
689 self.fonts.fonts[f as usize].size_spec = size;
690 }
691 if f != 0 {
694 let name = self.hash.name(u).to_vec();
695 let id = self.hash.new_frozen(&name);
696 self.eqtb.ensure_cs(id);
697 self.eqtb.cs[id.0 as usize].val = Meaning::prim(Cmd::SetFont, f);
698 self.fonts.id_cs[f as usize] = id;
699 }
700 self.define(
701 global,
702 Loc::Cs(u),
703 Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
704 );
705 }
706
707 pub fn load_font(&mut self, name: &[u8], size: i32) -> Option<u32> {
710 if let Some(h) = self.lua.clone()
711 && h.has_callback("define_font")
712 {
713 return h.define_font(self, name, size);
714 }
715 let req = FontRequest {
716 name: name.to_vec(),
717 size,
718 };
719 if self.fonts.loader.is_none() {
720 return self.load_tfm_font(name, size, name);
721 }
722 let mut loader = self.fonts.loader.take()?;
723 let info = loader.load(&req);
724 self.fonts.loader = Some(loader);
725 let mut info = info?;
726 info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
727 info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
728 Some(self.register_font(info))
729 }
730
731 pub fn register_font(&mut self, mut info: FontInfo) -> u32 {
733 if info.hyphen_char == i32::MIN {
734 info.hyphen_char = self.eqtb.int(pg::assign_int::DEFAULTHYPHENCHAR);
735 }
736 if info.skew_char == i32::MIN {
737 info.skew_char = self.eqtb.int(pg::assign_int::DEFAULTSKEWCHAR);
738 }
739 self.fonts.fonts.push(info);
740 self.fonts.id_cs.push(self.cs.frozen_null_font);
741 self.fonts.fonts.len() as u32 - 1
742 }
743
744 pub fn check_lua_loaded_font(&mut self, f: u32) {
747 let info = &self.fonts.fonts[f as usize];
748 if info.chars.is_empty() {
749 let msg = format!(
750 "lua-loaded font '{f}' with name '{}' has no characters",
751 String::from_utf8_lossy(&info.name)
752 );
753 self.luatex_warning("font", &msg);
754 }
755 }
756
757 pub fn load_tfm(&mut self, name: &[u8], size: i32) -> Option<FontInfo> {
760 let name_s = String::from_utf8_lossy(name).into_owned();
761 let path = self
762 .kpse
763 .as_ref()?
764 .borrow_mut()
765 .find_file(&name_s, retex_kpath::FileFormat::Tfm)?;
766 let data = self.read_file_recorded(&path)?;
767 let tfm = retex_font::tfm::Tfm::parse(&data).ok()?;
768 let scaled = tfm.scaled(size);
769 let mut params = vec![0; scaled.params.len() + 1];
770 params[1..].copy_from_slice(&scaled.params);
771 let mut chars =
772 HashMap::with_capacity_and_hasher(scaled.chars.len(), crate::hash::FastHash::default());
773 for (c, m) in &scaled.chars {
774 chars.insert(
775 u32::from(*c),
776 CharMetrics {
777 width: m[0],
778 height: m[1],
779 depth: m[2],
780 italic: m[3],
781 index: u32::from(*c),
782 },
783 );
784 }
785 Some(FontInfo {
786 name: name.to_vec(),
787 size: scaled.size,
788 design_size: tfm.design_size,
789 checksum: tfm.checksum,
790 params,
791 chars,
792 hyphen_char: i32::MIN,
793 skew_char: i32::MIN,
794 kind: FontKind::Tfm,
795 handle: 0,
796 char_codes: Default::default(),
797 ignore_ligatures: 0,
798 lig_kern: lig_kern_table(&tfm, &scaled.kerns),
799 right_boundary: tfm.bchar.map(u32::from),
800 math_constants: None,
801 math_chars: None,
802 size_spec: i32::MIN,
803 data: Some(tfm_font_data(tfm, data)),
804 expansion: None,
805 vf: None,
806 })
807 }
808
809 pub fn load_tfm_font(&mut self, tfm_name: &[u8], size: i32, font_name: &[u8]) -> Option<u32> {
815 self.load_tfm_font_at_depth(tfm_name, size, font_name, 0)
816 }
817
818 fn load_tfm_font_at_depth(
819 &mut self,
820 tfm_name: &[u8],
821 size: i32,
822 font_name: &[u8],
823 depth: u32,
824 ) -> Option<u32> {
825 let mut info = self.load_tfm(tfm_name, size)?;
826 info.name = font_name.to_vec();
827 let size = info.size;
828 let f = self.register_font(info);
829 if depth < VF_MAX_DEPTH {
830 self.attach_virtual_font(f, tfm_name, size, depth);
831 }
832 Some(f)
833 }
834
835 fn attach_virtual_font(&mut self, f: u32, name: &[u8], size: i32, depth: u32) {
838 let name_s = String::from_utf8_lossy(name).into_owned();
839 let Some(path) = self.kpse.as_ref().and_then(|k| {
840 k.borrow_mut()
841 .find_file(&name_s, retex_kpath::FileFormat::Vf)
842 }) else {
843 return;
844 };
845 let Some(data) = self.read_file_recorded(&path) else {
846 return;
847 };
848 let Ok(vf) = retex_font::vf::Vf::parse(&data) else {
849 return;
850 };
851 let mut fonts = Vec::with_capacity(vf.fonts.len());
852 for (i, lf) in vf.fonts.iter().enumerate() {
853 let lsize = vf.font_size(i, size);
854 let lname = lf.name.as_bytes();
855 let existing = (1..self.fonts.fonts.len()).find(|&g| {
856 let fi = &self.fonts.fonts[g];
857 fi.kind == FontKind::Tfm && fi.size == lsize && fi.name == lname
858 });
859 let id = match existing {
860 Some(g) => g as u32,
861 None => self
862 .load_tfm_font_at_depth(lname, lsize, lname, depth + 1)
863 .unwrap_or(0),
864 };
865 fonts.push(id);
866 }
867 let mut packets =
868 HashMap::with_capacity_and_hasher(vf.packets.len(), crate::hash::FastHash::default());
869 for &c in vf.packets.keys() {
870 if let Some(cmds) = vf.commands(c, size) {
871 packets.insert(c, cmds.into_boxed_slice());
872 }
873 }
874 self.fonts.fonts[f as usize].vf = Some(std::rc::Rc::new(VirtualFont { fonts, packets }));
875 }
876
877 #[inline]
879 pub fn lig_kern_step(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
880 if let Some(d) = self.fonts.dense.get(f as usize)
881 && left < 256
882 {
883 if d.lk.is_empty() {
884 return None;
885 }
886 if left < 128 && right < 128 {
887 let i = d.pairs[(left * 128 + right) as usize];
888 return if i == 0 {
889 None
890 } else {
891 Some(d.steps[i as usize - 1].op)
892 };
893 }
894 let (start, len) = d.lk[left as usize];
895 return d.steps[start as usize..(start + len) as usize]
896 .iter()
897 .find(|s| s.next == right)
898 .map(|s| s.op);
899 }
900 let fi = self.font(f);
901 if fi.lig_kern.is_empty() {
902 return None;
903 }
904 fi.lig_kern
905 .get(&left)?
906 .iter()
907 .find(|s| s.next == right)
908 .map(|s| s.op)
909 }
910
911 #[cold]
916 pub fn base_mode_kern(&self, f: u32, left: u32, right: u32) -> Option<LigOp> {
917 let fi = self.font(f);
918 if fi.kind != FontKind::OpenType || fi.math_constants.is_some() {
920 return None;
921 }
922 let lf = fi
923 .data
924 .as_ref()?
925 .0
926 .downcast_ref::<std::sync::Arc<retex_font::loader::LoadedFont>>()?;
927 if lf.request.mode != retex_font::request::Mode::Base {
928 return None;
929 }
930 let l = u16::try_from(fi.chars.get(&left)?.index).ok()?;
931 let r = u16::try_from(fi.chars.get(&right)?.index).ok()?;
932 let k = lf.shaper.pair_kern(&lf.file, &lf.tex, l, r);
933 (k != 0).then_some(LigOp::Kern(k))
934 }
935
936 pub fn math_char(&self, f: u32, c: u32) -> Option<&MathCharInfo> {
938 self.font(f).math_chars.as_ref()?.get(&c)
939 }
940
941 pub fn tfm_of(&self, f: u32) -> Option<std::rc::Rc<retex_font::tfm::Tfm>> {
943 let d = self.font(f).data.as_ref()?;
944 d.0.downcast_ref::<TfmData>().map(|t| t.tfm.clone())
945 }
946
947 pub fn letterspace_font(&mut self, global: bool) {
949 self.get_r_token();
950 let u = self.cur_cs;
951 self.scan_font_ident();
952 let f = self.cur_val as u32;
953 self.scan_int();
954 let _amount = self.cur_val;
955 self.define(
957 global,
958 Loc::Cs(u),
959 Val::Meaning(Meaning::prim(Cmd::SetFont, f)),
960 );
961 }
962
963 pub fn copy_font(&mut self, global: bool) {
965 self.get_r_token();
966 let u = self.cur_cs;
967 self.scan_font_ident();
968 let f = self.cur_val as usize;
969 let copy = self.fonts.fonts[f].clone();
970 self.fonts.fonts.push(copy);
971 self.fonts.id_cs.push(self.cs.frozen_null_font);
972 let nf = self.fonts.fonts.len() as u32 - 1;
973 self.define(
974 global,
975 Loc::Cs(u),
976 Val::Meaning(Meaning::prim(Cmd::SetFont, nf)),
977 );
978 }
979}
980
981pub(crate) fn lig_kern_table(tfm: &retex_font::tfm::Tfm, kerns: &[i32]) -> LigKernMap {
984 let nl = tfm.lig_kern.len();
985 let read = |start: usize| -> Vec<LigKernStep> {
986 let mut steps: Vec<LigKernStep> = Vec::new();
987 let mut k = start;
988 for _ in 0..nl {
989 let Some(w) = tfm.lig_kern.get(k) else { break };
990 if w.skip <= 128 {
991 let next = u32::from(w.next);
992 if !steps.iter().any(|s| s.next == next) {
993 let op = if w.op >= 128 {
994 let ki = 256 * (usize::from(w.op) - 128) + usize::from(w.rem);
995 LigOp::Kern(kerns.get(ki).copied().unwrap_or(0))
996 } else {
997 LigOp::Lig {
998 op: w.op,
999 chr: u32::from(w.rem),
1000 }
1001 };
1002 steps.push(LigKernStep { next, op });
1003 }
1004 }
1005 if w.skip >= 128 {
1006 break;
1007 }
1008 k += usize::from(w.skip) + 1;
1009 }
1010 steps
1011 };
1012 let mut map = LigKernMap::default();
1013 for c in u32::from(tfm.bc)..=u32::from(tfm.ec) {
1014 if let Some(start) = tfm.lig_kern_start(c) {
1015 let p = read(start);
1016 if !p.is_empty() {
1017 map.insert(c, p.into_boxed_slice());
1018 }
1019 }
1020 }
1021 if let Some(l) = tfm.bchar_label {
1022 let p = read(l);
1023 if !p.is_empty() {
1024 map.insert(LEFT_BOUNDARY, p.into_boxed_slice());
1025 }
1026 }
1027 map
1028}