Skip to main content

retex_engine/
nodeproc.rs

1//! LuaTeX's node processing before packaging and line breaking: the
2//! hyphenation, ligaturing and kerning passes (LuaTeX manual §5.5–§5.6).
3//!
4//! Characters are appended to lists as glyph nodes without any font
5//! processing. When an `\hbox` is packaged or a paragraph is broken into
6//! lines, LuaTeX first inserts discretionaries (explicit hyphens become
7//! "automatic" discretionaries; patterns insert "regular" ones), then
8//! builds ligatures, then inserts font kerns. Each step can be replaced by
9//! a Lua callback; without callbacks
10//! the built-in TeX-style processing below applies, using the font's
11//! lig/kern program.
12//!
13//! Observed behaviour reproduced here:
14//!
15//! * a single `\exhyphenchar` glyph immediately followed by another
16//!   glyph becomes an automatic discretionary (subtype 2) whose pre-break
17//!   and replacement texts are the hyphen, with `\exhyphenpenalty` as
18//!   current at processing time. Of a run of two or more hyphens (`--`,
19//!   `---`) only the last one becomes such a discretionary, and only when
20//!   the run follows a glyph with a non-zero hyphenation code and is
21//!   followed by a glyph, and at most once between two glues; a kern also
22//!   prevents it until the next glue. So `a--b` becomes `a-{-||-}b`, while
23//!   `16--19`, `1--a` and the second dash of `a--b--c` stay as they are
24//!   (observed with luatex);
25//! * ligatures form within the main list and within each list of a
26//!   discretionary; a glyph next to a discretionary moves into it when it
27//!   would form a ligature across it;
28//! * kerns between a glyph and a discretionary go into the discretionary's
29//!   lists (pre-break and replacement at the left, post-break and
30//!   replacement at the right);
31//! * boundary nodes are removed; glyphs lose their "character" subtype.
32
33use crate::Engine;
34use crate::fonts::LigOp;
35use crate::nodes::{GlyphData, NodeData, NodeRef, glyph_sub, id, kern_sub};
36use crate::primitives_gen::assign_int as ip;
37
38/// What a list is processed for (the callback group).
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum Context {
41    /// An `\hbox` (or local box) being packaged.
42    Hbox,
43    /// A paragraph before line breaking.
44    Paragraph,
45}
46
47/// One of the node processing passes.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum NodePass {
50    /// Hyphenation (`lang.hyphenate`).
51    Hyphenate,
52    /// Ligatures (`node.ligaturing`).
53    Ligaturing,
54    /// Font kerns (`node.kerning`).
55    Kerning,
56}
57
58impl Engine {
59    /// The hyphenation parameters of language `l`.
60    pub fn lang_params(&self, l: u32) -> retex_hyph::Settings {
61        self.language(l).map(|x| x.settings).unwrap_or_default()
62    }
63
64    /// A new glyph node in font `f` with the current language data
65    /// (LuaTeX's `new_char`).
66    pub fn new_char_node(&mut self, f: u32, c: u32) -> NodeRef {
67        let lang = self.cur_lang() as u16;
68        let left = self.eqtb.int(ip::LEFTHYPHENMIN).clamp(0, 63) as u8;
69        let right = self.eqtb.int(ip::RIGHTHYPHENMIN).clamp(0, 63) as u8;
70        let uchyph = self.eqtb.int(ip::UCHYPH) > 0;
71        self.new_node(
72            id::GLYPH,
73            glyph_sub::CHARACTER,
74            NodeData::Glyph(GlyphData {
75                chr: c,
76                font: f,
77                lang,
78                left,
79                right,
80                uchyph,
81                ..GlyphData::default()
82            }),
83        )
84    }
85
86    /// Runs the hyphenation, ligaturing and kerning passes over the list
87    /// starting at `first`; returns the new first node.
88    pub fn node_process_list(&mut self, first: NodeRef, ctx: Context) -> NodeRef {
89        if first.is_null() {
90            return first;
91        }
92        self.fonts.sync_dense();
93        let head = self.nodes.new_temp();
94        self.nodes.link(head, first);
95        let saved_par_temp = self.nodes.par_temp;
96        if ctx == Context::Paragraph {
97            self.nodes.par_temp = head;
98        }
99        // The hyphenate, ligaturing and kerning callbacks replace the
100        // built-in passes (a registered false skips them).
101        let host = self.lua.clone();
102        let pass = |e: &mut Engine, cb: &str| match &host {
103            Some(h) => h.node_pass(e, cb, head),
104            None => false,
105        };
106        if !pass(self, "hyphenate") {
107            self.hyphenate_list(head);
108        }
109        if !pass(self, "ligaturing") {
110            self.ligature_list(head, true);
111        }
112        if !pass(self, "kerning") {
113            self.kern_list(head);
114        }
115        self.nodes.par_temp = saved_par_temp;
116        let first = self.nodes.next(head);
117        if !first.is_null() {
118            self.nodes.set_prev(first, NodeRef::NULL);
119        }
120        self.nodes.free(head);
121        first
122    }
123
124    /// Runs one built-in pass (`node.ligaturing`, `node.kerning`,
125    /// `lang.hyphenate`) over the list `head`, which may start with a
126    /// temporary node (as in the callbacks); returns the new head and the
127    /// tail.
128    pub fn lua_node_pass(&mut self, pass: NodePass, head: NodeRef) -> (NodeRef, NodeRef) {
129        if head.is_null() {
130            return (head, head);
131        }
132        self.fonts.sync_dense();
133        let temp = self.nodes.id(head) == id::TEMP;
134        let h = if temp {
135            head
136        } else {
137            let t = self.nodes.new_temp();
138            self.nodes.link(t, head);
139            t
140        };
141        match pass {
142            NodePass::Hyphenate => self.hyphenate_list(h),
143            NodePass::Ligaturing => self.ligature_list(h, true),
144            NodePass::Kerning => self.kern_list(h),
145        }
146        let first = if temp {
147            head
148        } else {
149            let f = self.nodes.next(h);
150            if !f.is_null() {
151                self.nodes.set_prev(f, NodeRef::NULL);
152            }
153            self.nodes.free(h);
154            f
155        };
156        let tail = self.nodes.tail_of(first);
157        (first, tail)
158    }
159
160    // ----- hyphenation ---------------------------------------------------
161
162    /// The built-in `hyphenate` step: explicit hyphens become automatic
163    /// discretionaries; patterns (through the [`crate::typeset::Hyphenator`])
164    /// insert regular ones.
165    fn hyphenate_list(&mut self, head: NodeRef) {
166        let ex = self.eqtb.int(ip::EXHYPHENCHAR);
167        let pen = self.eqtb.int(ip::EXHYPHENPENALTY);
168        if ex > 0 {
169            let ex = ex as u32;
170            let is_hyphen = |e: &Engine, n: NodeRef| e.nodes.glyph(n).is_some_and(|g| g.chr == ex);
171            // Whether a run of several hyphens may still become a
172            // discretionary before the next glue.
173            let mut armed = true;
174            let mut prev = head;
175            let mut p = self.nodes.next(head);
176            while !p.is_null() {
177                // Observed: hyphens in a formula stay as they are (a URL
178                // set as math, with apacite's `\mathbin{}\mathchar"2D`).
179                if self.nodes.id(p) == id::MATH && self.nodes.subtype(p) == 0 {
180                    let mut q = self.nodes.next(p);
181                    while !(q.is_null()
182                        || (self.nodes.id(q) == id::MATH && self.nodes.subtype(q) == 1))
183                    {
184                        q = self.nodes.next(q);
185                    }
186                    if q.is_null() {
187                        break;
188                    }
189                    prev = q;
190                    p = self.nodes.next(q);
191                    continue;
192                }
193                if !is_hyphen(self, p) {
194                    match self.nodes.id(p) {
195                        id::GLUE => armed = true,
196                        id::KERN => armed = false,
197                        _ => {}
198                    }
199                    prev = p;
200                    p = self.nodes.next(p);
201                    continue;
202                }
203                // Find the run of hyphens.
204                let mut last = p;
205                let mut len = 1;
206                let mut q = self.nodes.next(p);
207                while !q.is_null() && is_hyphen(self, q) {
208                    last = q;
209                    len += 1;
210                    q = self.nodes.next(q);
211                }
212                let followed = !q.is_null() && self.nodes.is_glyph(q);
213                let convert =
214                    followed && (len == 1 || (armed && prev != head && self.has_hj_code(prev)));
215                if !convert {
216                    prev = last;
217                    p = q;
218                    continue;
219                }
220                if len > 1 {
221                    armed = false;
222                }
223                // Replace the last hyphen by an automatic discretionary.
224                let before = if len == 1 {
225                    prev
226                } else {
227                    self.nodes.prev(last)
228                };
229                self.nodes.set_next(last, NodeRef::NULL);
230                let d = self.nodes.new_disc(2, pen);
231                if let Some(a) = self.nodes.attr(last).cloned() {
232                    self.nodes.set_attr(d, Some(a));
233                }
234                let pre = self.nodes.copy_list(last);
235                self.nodes.set_prev(last, NodeRef::NULL);
236                self.nodes.set_disc_lists(d, (pre, NodeRef::NULL, last));
237                self.nodes.link(before, d);
238                self.nodes.link(d, q);
239                prev = d;
240                p = q;
241            }
242        }
243        if !self.languages.is_empty() {
244            self.pattern_hyphenate(head);
245        }
246    }
247
248    /// Whether `p` is a glyph whose character has a non-zero hyphenation
249    /// code in its language.
250    fn has_hj_code(&self, p: NodeRef) -> bool {
251        let Some(g) = self.nodes.glyph(p) else {
252            return false;
253        };
254        let lang = u32::from(g.lang);
255        let c = g.chr;
256        let code = match self.language(lang) {
257            Some(l) if l.has_hj_codes() => l.hj_code(c),
258            _ => self.eqtb.lc.get(c).max(0) as u32,
259        };
260        code != 0
261    }
262
263    /// Inserts pattern discretionaries into the words of the list
264    /// (LuaTeX's hyphenation pass, using `retex-hyph`).
265    ///
266    /// Observed: a word is hyphenated only if what precedes it lets a word
267    /// start and what follows it lets a word end. The list start and glue
268    /// let words start; once the list has had a glyph, kerns, penalties,
269    /// discretionaries, marks and `\vadjust`s stop that until the next
270    /// glue; other glyphs, boxes, rules, whatsits, formulas (with all
271    /// they hold) and direction nodes leave it as it is. A word followed by a discretionary, a math
272    /// node or a mark is not hyphenated; nor is any word of a run of glyphs
273    /// (with the automatic discretionaries of explicit hyphens) that ends
274    /// at a math node, a mark or another discretionary, even one followed
275    /// by punctuation or by other words of the run (`AUROC($x$)`,
276    /// `word,word-word\mark{}`). With `\automatichyphenmode` > 0 automatic
277    /// discretionaries (from explicit hyphens) count as letting words
278    /// start and end. Otherwise the automatic discretionary of a single
279    /// explicit hyphen lets words start again, but not the word attached
280    /// to it (`ab-online/on-line`); after `--` none does.
281    fn pattern_hyphenate(&mut self, head: NodeRef) {
282        let hpen = self.eqtb.int(ip::HYPHENPENALTY);
283        let auto_ok = self.eqtb.int(ip::AUTOMATICHYPHENMODE) != 0;
284        let mut run: Vec<NodeRef> = Vec::new();
285        let mut word: Vec<NodeRef> = Vec::new();
286        let mut codes: Vec<u32> = Vec::new();
287        let mut pos: Vec<usize> = Vec::new();
288        let mut can_start = true;
289        let mut seen_glyph = false;
290        // The run starts right after the automatic discretionary of a
291        // single explicit hyphen: its first word is not hyphenated.
292        let mut skip_first = false;
293        // Whether the current unit (runs joined by automatic
294        // discretionaries) ends where its words cannot be hyphenated.
295        let mut unit_kill: Option<bool> = None;
296        let mut p = self.nodes.next(head);
297        while !p.is_null() {
298            if self.nodes.is_glyph(p) && self.nodes.subtype(p) & glyph_sub::CHARACTER != 0 {
299                // A run of character glyphs.
300                run.clear();
301                let mut q = p;
302                while !q.is_null()
303                    && self.nodes.is_glyph(q)
304                    && self.nodes.subtype(q) & glyph_sub::CHARACTER != 0
305                {
306                    run.push(q);
307                    q = self.nodes.next(q);
308                }
309                let end_ok = q.is_null()
310                    || match self.nodes.id(q) {
311                        id::DISC => auto_ok && self.nodes.subtype(q) == 2,
312                        id::MATH | id::MARK => false,
313                        _ => true,
314                    };
315                let kill = *unit_kill.get_or_insert_with(|| self.unit_ends_badly(q));
316                let bounds = (can_start && !kill, end_ok, skip_first);
317                self.hyphenate_run(&run, hpen, bounds, &mut word, &mut codes, &mut pos);
318                seen_glyph = true;
319                skip_first = false;
320                p = q;
321                continue;
322            }
323            if !(self.nodes.id(p) == id::DISC && self.nodes.subtype(p) == 2) {
324                unit_kill = None;
325            }
326            // Observed: a formula's contents leave the state as it is
327            // (`$T-$dependencies` is hyphenated after the kern and the
328            // glyphs of the formula).
329            if self.nodes.id(p) == id::MATH && self.nodes.subtype(p) == 0 {
330                let mut q = self.nodes.next(p);
331                while !(q.is_null() || (self.nodes.id(q) == id::MATH && self.nodes.subtype(q) == 1))
332                {
333                    q = self.nodes.next(q);
334                }
335                p = if q.is_null() { q } else { self.nodes.next(q) };
336                continue;
337            }
338            match self.nodes.id(p) {
339                id::GLUE => can_start = true,
340                id::KERN | id::PENALTY | id::MARK | id::ADJUST => can_start &= !seen_glyph,
341                id::DISC if auto_ok && self.nodes.subtype(p) == 2 => can_start = true,
342                id::DISC if self.nodes.subtype(p) == 2 && self.single_hyphen_disc(p) => {
343                    can_start = true;
344                    skip_first = true;
345                }
346                id::DISC => can_start &= !seen_glyph,
347                _ => {}
348            }
349            p = self.nodes.next(p);
350        }
351    }
352
353    /// Whether automatic discretionary `d` comes from a single explicit
354    /// hyphen: after a glyph other than the hyphen character.
355    fn single_hyphen_disc(&self, d: NodeRef) -> bool {
356        let ex = self.eqtb.int(ip::EXHYPHENCHAR);
357        let prev = self.nodes.prev(d);
358        !prev.is_null()
359            && self
360                .nodes
361                .glyph(prev)
362                .is_some_and(|g| ex <= 0 || g.chr != ex as u32)
363    }
364
365    /// Whether the unit of glyph runs and automatic discretionaries that
366    /// goes on at `q` (the node after a run) ends at a math node, a mark or
367    /// a discretionary other than an automatic one.
368    fn unit_ends_badly(&self, mut q: NodeRef) -> bool {
369        loop {
370            if q.is_null() {
371                return false;
372            }
373            match self.nodes.id(q) {
374                id::DISC if self.nodes.subtype(q) == 2 => {}
375                id::DISC | id::MATH | id::MARK => return true,
376                _ if self.nodes.is_glyph(q)
377                    && self.nodes.subtype(q) & glyph_sub::CHARACTER != 0 => {}
378                _ => return false,
379            }
380            q = self.nodes.next(q);
381        }
382    }
383
384    /// Hyphenates the words in a run of character glyphs; `bounds` tells
385    /// whether words may start in the run and end at its end.
386    fn hyphenate_run(
387        &mut self,
388        run: &[NodeRef],
389        hpen: i32,
390        bounds: (bool, bool, bool),
391        word: &mut Vec<NodeRef>,
392        codes: &mut Vec<u32>,
393        pos: &mut Vec<usize>,
394    ) {
395        if !bounds.0 {
396            return;
397        }
398        let mut i = 0;
399        while i < run.len() {
400            // A word: glyphs of one language with non-zero hj codes.
401            let Some(g0) = self.nodes.glyph(run[i]).cloned() else {
402                i += 1;
403                continue;
404            };
405            let lang = u32::from(g0.lang);
406            word.clear();
407            codes.clear();
408            // Observed: a word next to or with an explicit hyphen that did
409            // not become a discretionary (`--word` after a space, `word--`;
410            // LaTeX gives the hyphen an hj code) is not hyphenated.
411            let ex = self.eqtb.int(ip::EXHYPHENCHAR);
412            let mut with_hyphen = false;
413            while i < run.len() {
414                let Some(g) = self.nodes.glyph(run[i]) else {
415                    break;
416                };
417                if u32::from(g.lang) != lang {
418                    break;
419                }
420                let c = g.chr;
421                let code = match self.language(lang) {
422                    Some(l) if l.has_hj_codes() => l.hj_code(c),
423                    _ => self.eqtb.lc.get(c).max(0) as u32,
424                };
425                if code == 0 {
426                    if word.is_empty() {
427                        with_hyphen = ex > 0 && c == ex as u32;
428                        i += 1;
429                        continue;
430                    }
431                    with_hyphen |= ex > 0 && c == ex as u32;
432                    break;
433                }
434                with_hyphen |= ex > 0 && c == ex as u32;
435                word.push(run[i]);
436                codes.push(code);
437                i += 1;
438            }
439            if word.len() < 2 || with_hyphen || (bounds.2 && word.first() == run.first()) {
440                continue;
441            }
442            if !bounds.1 && word.last() == run.last() {
443                continue;
444            }
445            let Some(first) = self.nodes.glyph(word[0]).cloned() else {
446                continue;
447            };
448            if first.font == 0 || self.font(first.font).hyphen_char < 0 {
449                continue;
450            }
451            if !first.uchyph && codes[0] != first.chr {
452                continue;
453            }
454            let Some(language) = self.language(lang) else {
455                continue;
456            };
457            pos.clear();
458            let _ =
459                language.hyphenate_into(codes, u32::from(first.left), u32::from(first.right), pos);
460            if pos.is_empty() {
461                continue;
462            }
463            let settings = language.settings;
464            pos.sort_unstable();
465            pos.dedup();
466            for &k in pos.iter().rev() {
467                if k == 0 || k >= word.len() {
468                    continue;
469                }
470                let left = word[k - 1];
471                let right = word[k];
472                let f = self.nodes.glyph(left).map_or(0, |g| g.font);
473                let d = self.nodes.new_disc(3, hpen);
474                if let Some(a) = self.nodes.attr(left).cloned() {
475                    self.nodes.set_attr(d, Some(a));
476                }
477                let mk = |e: &mut Engine, c: i32| {
478                    if c > 0 {
479                        let mut g = e.nodes.glyph(left).cloned().unwrap_or_default();
480                        g.chr = c as u32;
481                        g.font = f;
482                        g.components = NodeRef::NULL;
483                        // Observed: with the attributes of the glyph
484                        // before the break, like the discretionary.
485                        let a = e.nodes.attr(left).cloned();
486                        let n = e
487                            .nodes
488                            .alloc(id::GLYPH, glyph_sub::CHARACTER, NodeData::Glyph(g));
489                        e.nodes.set_attr(n, a);
490                        n
491                    } else {
492                        NodeRef::NULL
493                    }
494                };
495                let pre = mk(self, settings.pre_hyphen_char);
496                let post = mk(self, settings.post_hyphen_char);
497                self.nodes.set_disc_lists(d, (pre, post, NodeRef::NULL));
498                self.nodes.link(left, d);
499                self.nodes.link(d, right);
500            }
501        }
502    }
503
504    // ----- ligaturing ----------------------------------------------------
505
506    /// Whether `p` is a glyph of font `f` (any subtype except ghosts).
507    #[inline]
508    fn glyph_of_font(&self, p: NodeRef, f: u32) -> Option<u32> {
509        if p.is_null() {
510            return None;
511        }
512        match self.nodes.data(p) {
513            NodeData::Glyph(g) if g.font == f && self.nodes.subtype(p) & glyph_sub::GHOST == 0 => {
514                Some(g.chr)
515            }
516            _ => None,
517        }
518    }
519
520    /// The ligature step for (`l`, `r`) in font `f`, if it is a ligature.
521    /// A font with `\ignoreligaturesinfont` has none (its kerns stay).
522    #[inline]
523    fn lig_step(&self, f: u32, l: u32, r: u32) -> Option<(u8, u32)> {
524        if self.font(f).ignore_ligatures != 0 {
525            return None;
526        }
527        match self.lig_kern_step(f, l, r) {
528            Some(LigOp::Lig { op, chr }) => Some((op, chr)),
529            _ => None,
530        }
531    }
532
533    /// The components of a glyph for a new ligature: the glyph itself, or
534    /// its components if it is already a ligature.
535    fn take_components(&mut self, p: NodeRef) -> NodeRef {
536        let is_lig = self.nodes.subtype(p) & glyph_sub::LIGATURE != 0;
537        if is_lig {
538            let c = self
539                .nodes
540                .glyph_mut(p)
541                .map(|g| std::mem::take(&mut g.components));
542            let c = c.unwrap_or(NodeRef::NULL);
543            self.nodes.free(p);
544            if c.is_null() {
545                return NodeRef::NULL;
546            }
547            c
548        } else {
549            self.nodes.set_next(p, NodeRef::NULL);
550            self.nodes.set_prev(p, NodeRef::NULL);
551            let s = self.nodes.subtype(p) & !glyph_sub::CHARACTER;
552            self.nodes.set_subtype(p, s);
553            p
554        }
555    }
556
557    /// Makes a ligature glyph like `model` with character `c` and the given
558    /// components.
559    fn new_ligature(&mut self, model: NodeRef, c: u32, comps: NodeRef) -> NodeRef {
560        let mut g = self.nodes.glyph(model).cloned().unwrap_or_default();
561        g.chr = c;
562        g.components = comps;
563        let a = self.nodes.attr(model).cloned();
564        let n = self
565            .nodes
566            .alloc(id::GLYPH, glyph_sub::LIGATURE, NodeData::Glyph(g));
567        self.nodes.set_attr(n, a);
568        n
569    }
570
571    /// Concatenates two node lists.
572    fn concat(&mut self, a: NodeRef, b: NodeRef) -> NodeRef {
573        if a.is_null() {
574            return b;
575        }
576        if !b.is_null() {
577            let t = self.nodes.tail_of(a);
578            self.nodes.link(t, b);
579        }
580        a
581    }
582
583    /// Replaces `old` (whose predecessor is `prev`) by `new` in a list.
584    fn replace_node(&mut self, prev: NodeRef, old_next: NodeRef, new: NodeRef) {
585        self.nodes.link(prev, new);
586        self.nodes.link(new, old_next);
587        if old_next.is_null() {
588            self.nodes.set_next(new, NodeRef::NULL);
589        }
590    }
591
592    /// Builds ligatures in the list after `head`. `main` is true for the
593    /// main list (boundary nodes are removed there).
594    fn ligature_list(&mut self, head: NodeRef, main: bool) {
595        let mut prev = head;
596        let mut p = self.nodes.next(head);
597        while !p.is_null() {
598            match self.nodes.id(p) {
599                id::BOUNDARY if main => {
600                    let n = self.nodes.next(p);
601                    self.nodes.set_next(prev, n);
602                    if !n.is_null() {
603                        self.nodes.set_prev(n, prev);
604                    }
605                    self.nodes.free(p);
606                    p = n;
607                }
608                id::GLYPH => {
609                    let (last_prev, last) = self.ligature_run(prev, p);
610                    // `last` is the final glyph of the run; it may be
611                    // followed by a discretionary.
612                    let n = self.nodes.next(last);
613                    if main
614                        && (n.is_null()
615                            || !matches!(self.nodes.id(n), id::GLYPH | id::DISC | id::BOUNDARY))
616                    {
617                        self.right_boundary_kern(last);
618                    }
619                    if !n.is_null() && self.nodes.id(n) == id::DISC {
620                        let (np, nd) = self.lig_disc(last_prev, last, n);
621                        prev = np;
622                        p = nd;
623                    } else {
624                        prev = last;
625                        p = n;
626                    }
627                }
628                id::DISC => {
629                    self.lig_disc_lists(p);
630                    prev = p;
631                    p = self.nodes.next(p);
632                }
633                _ => {
634                    prev = p;
635                    p = self.nodes.next(p);
636                }
637            }
638        }
639    }
640
641    /// Appends the font kern between glyph `p` and its font's right
642    /// boundary character, if there is one. Observed: LuaTeX applies it
643    /// in the main list when the next node is not a glyph, a
644    /// discretionary or a boundary node (`\noboundary`, `\boundary`, ...),
645    /// even after a font kern (an unboxed list gets a second one).
646    fn right_boundary_kern(&mut self, p: NodeRef) {
647        let Some((f, l)) = self.nodes.glyph(p).map(|g| (g.font, g.chr)) else {
648            return;
649        };
650        let Some(b) = self.font(f).right_boundary else {
651            return;
652        };
653        if let Some(k) = self.kern_between(f, l, b) {
654            let kn = self.font_kern(k, p);
655            let n = self.nodes.next(p);
656            self.nodes.link(p, kn);
657            self.nodes.link(kn, n);
658        }
659    }
660
661    /// Ligatures the three lists of a discretionary independently.
662    fn lig_disc_lists(&mut self, d: NodeRef) {
663        let (pre, post, rep) = self.nodes.disc_lists(d);
664        let pre = self.ligature_sublist(pre);
665        let post = self.ligature_sublist(post);
666        let rep = self.ligature_sublist(rep);
667        self.nodes.set_disc_lists(d, (pre, post, rep));
668    }
669
670    /// Ligatures a detached list; returns its new head.
671    fn ligature_sublist(&mut self, l: NodeRef) -> NodeRef {
672        if l.is_null() {
673            return l;
674        }
675        let h = self.nodes.new_temp();
676        self.nodes.link(h, l);
677        self.ligature_list(h, false);
678        let r = self.nodes.next(h);
679        if !r.is_null() {
680            self.nodes.set_prev(r, NodeRef::NULL);
681        }
682        self.nodes.free(h);
683        r
684    }
685
686    /// Handles a discretionary `d` that follows the glyph `last` (whose
687    /// predecessor is found through `prev` links): moves neighbouring
688    /// glyphs into it when ligatures cross it, then ligatures its lists.
689    /// Returns (prev, next) for continuing the scan after `d`.
690    fn lig_disc(&mut self, before: NodeRef, last: NodeRef, d: NodeRef) -> (NodeRef, NodeRef) {
691        let f = self.nodes.glyph(last).map_or(0, |g| g.font);
692        if self.font(f).lig_kern.is_empty() {
693            self.lig_disc_lists(d);
694            return (d, self.nodes.next(d));
695        }
696        let lc = self.nodes.glyph(last).map_or(0, |g| g.chr);
697        let (pre, post, rep) = self.nodes.disc_lists(d);
698        let after = self.nodes.next(d);
699        let crosses =
700            |e: &Engine, l: NodeRef| e.glyph_of_font(l, f).and_then(|c| e.lig_step(f, lc, c));
701        let left = crosses(self, pre).is_some()
702            || (!rep.is_null() && crosses(self, rep).is_some())
703            || (rep.is_null() && crosses(self, after).is_some());
704        let (mut pre, mut post, mut rep) = (pre, post, rep);
705        if left {
706            // Move `last` into the discretionary.
707            self.nodes.link(before, d);
708            self.nodes.set_next(last, NodeRef::NULL);
709            self.nodes.set_prev(last, NodeRef::NULL);
710            let copy = self.nodes.copy_node(last);
711            pre = self.concat(copy, pre);
712            rep = self.concat(last, rep);
713        }
714        // Move following glyphs into the discretionary while a ligature
715        // crosses the right edge.
716        loop {
717            let n = self.nodes.next(d);
718            let Some(nc) = self.glyph_of_font(n, f) else {
719                break;
720            };
721            let ends = |e: &Engine, l: NodeRef| {
722                if l.is_null() {
723                    return false;
724                }
725                let t = e.nodes.tail_of(l);
726                e.glyph_of_font(t, f)
727                    .is_some_and(|c| e.lig_step(f, c, nc).is_some())
728            };
729            if !(ends(self, post) || ends(self, rep)) {
730                break;
731            }
732            let nn = self.nodes.next(n);
733            self.nodes.link(d, nn);
734            if nn.is_null() {
735                self.nodes.set_next(d, NodeRef::NULL);
736            }
737            self.nodes.set_next(n, NodeRef::NULL);
738            self.nodes.set_prev(n, NodeRef::NULL);
739            let copy = self.nodes.copy_node(n);
740            post = self.concat(post, copy);
741            rep = self.concat(rep, n);
742        }
743        self.nodes.set_disc_lists(d, (pre, post, rep));
744        self.lig_disc_lists(d);
745        (d, self.nodes.next(d))
746    }
747
748    /// Builds ligatures in the run of glyphs starting at `p` (with
749    /// predecessor `prev`); returns the last glyph of the processed run
750    /// and its predecessor.
751    fn ligature_run(&mut self, prev: NodeRef, p: NodeRef) -> (NodeRef, NodeRef) {
752        let f = self.nodes.glyph(p).map_or(0, |g| g.font);
753        if self.font(f).lig_kern.is_empty() {
754            // No ligatures in this font: skip the run.
755            let (mut qp, mut q) = (prev, p);
756            loop {
757                let n = self.nodes.next(q);
758                if self.glyph_of_font(n, f).is_none() {
759                    return (qp, q);
760                }
761                qp = q;
762                q = n;
763            }
764        }
765        let mut cur_prev = prev;
766        let mut cur = p;
767        let mut guard = 0u32;
768        loop {
769            let nxt = self.nodes.next(cur);
770            let (Some(l), Some(r)) = (self.glyph_of_font(cur, f), self.glyph_of_font(nxt, f))
771            else {
772                return (cur_prev, cur);
773            };
774            guard += 1;
775            if guard > 100_000 {
776                // An infinite ligature loop in a broken font.
777                return (cur_prev, cur);
778            }
779            let Some((op, c)) = self.lig_step(f, l, r) else {
780                cur_prev = cur;
781                cur = nxt;
782                continue;
783            };
784            let after = self.nodes.next(nxt);
785            match op {
786                0 => {
787                    // =: — both characters replaced.
788                    let a = self.take_components(cur);
789                    let b = self.take_components(nxt);
790                    let comps = self.concat(a, b);
791                    let model = if comps.is_null() { cur } else { comps };
792                    let lig = self.new_ligature(model, c, comps);
793                    self.replace_node(cur_prev, after, lig);
794                    cur = lig;
795                }
796                1 | 5 => {
797                    // =:| and =:|> — the left character is replaced.
798                    let a = self.take_components(cur);
799                    let model = if a.is_null() { nxt } else { a };
800                    let lig = self.new_ligature(model, c, a);
801                    self.replace_node(cur_prev, nxt, lig);
802                    if op == 5 {
803                        cur_prev = lig;
804                        cur = nxt;
805                    } else {
806                        cur = lig;
807                    }
808                }
809                2 | 6 => {
810                    // |=: and |=:> — the right character is replaced.
811                    let b = self.take_components(nxt);
812                    let model = if b.is_null() { cur } else { b };
813                    let lig = self.new_ligature(model, c, b);
814                    self.replace_node(cur, after, lig);
815                    if op == 6 {
816                        cur_prev = cur;
817                        cur = lig;
818                    }
819                }
820                _ => {
821                    // |=:|, |=:|>, |=:|>> — a character is inserted.
822                    let lig = self.new_ligature(cur, c, NodeRef::NULL);
823                    self.nodes.set_subtype(lig, 0);
824                    self.replace_node(cur, nxt, lig);
825                    match op {
826                        7 => {
827                            cur_prev = cur;
828                            cur = lig;
829                        }
830                        11 => {
831                            cur_prev = lig;
832                            cur = nxt;
833                        }
834                        _ => {}
835                    }
836                }
837            }
838        }
839    }
840
841    // ----- kerning -------------------------------------------------------
842
843    /// The font kern between (`l`, `r`) in font `f`.
844    #[inline]
845    fn kern_between(&self, f: u32, l: u32, r: u32) -> Option<i32> {
846        match self.lig_kern_step(f, l, r) {
847            Some(LigOp::Kern(k)) => Some(k),
848            _ => None,
849        }
850    }
851
852    /// A font kern node with the attributes of `model`.
853    fn font_kern(&mut self, k: i32, model: NodeRef) -> NodeRef {
854        let n = self.nodes.new_kern(k, kern_sub::FONT);
855        let a = self.nodes.attr(model).cloned();
856        self.nodes.set_attr(n, a);
857        n
858    }
859
860    /// Inserts font kerns into the list after `head` and clears the
861    /// character subtype of glyphs.
862    fn kern_list(&mut self, head: NodeRef) {
863        let mut p = self.nodes.next(head);
864        while !p.is_null() {
865            match self.nodes.id(p) {
866                id::GLYPH => {
867                    let s = self.nodes.subtype(p) & !glyph_sub::CHARACTER;
868                    self.nodes.set_subtype(p, s);
869                    let n = self.nodes.next(p);
870                    if n.is_null() {
871                        break;
872                    }
873                    let (f, l) = match self.nodes.glyph(p) {
874                        Some(g) => (g.font, g.chr),
875                        None => break,
876                    };
877                    if self.nodes.id(n) == id::GLYPH {
878                        if let Some(r) = self.glyph_of_font(n, f)
879                            && let Some(k) = self.kern_between(f, l, r)
880                        {
881                            let kn = self.font_kern(k, p);
882                            self.nodes.link(p, kn);
883                            self.nodes.link(kn, n);
884                        }
885                        p = n;
886                    } else if self.nodes.id(n) == id::DISC {
887                        self.kern_disc(p, n);
888                        p = self.nodes.next(n);
889                    } else {
890                        p = n;
891                    }
892                }
893                id::DISC => {
894                    self.kern_disc(NodeRef::NULL, p);
895                    p = self.nodes.next(p);
896                }
897                _ => p = self.nodes.next(p),
898            }
899        }
900    }
901
902    /// Kerns a discretionary with its neighbours (`before` may be null)
903    /// and kerns its lists.
904    fn kern_disc(&mut self, before: NodeRef, d: NodeRef) {
905        let (mut pre, mut post, mut rep) = self.nodes.disc_lists(d);
906        pre = self.kern_sublist(pre);
907        post = self.kern_sublist(post);
908        let rep_was_empty = rep.is_null();
909        rep = self.kern_sublist(rep);
910        let after = self.nodes.next(d);
911        let bg = if before.is_null() {
912            None
913        } else {
914            self.nodes.glyph(before).map(|g| (g.font, g.chr))
915        };
916        let ag = if after.is_null() || self.nodes.id(after) != id::GLYPH {
917            None
918        } else {
919            self.nodes.glyph(after).map(|g| (g.font, g.chr))
920        };
921        // Observed: the discretionaries of an explicit hyphen (subtype 2)
922        // and of hyphenation (3) are not kerned with the character before
923        // them in their pre-break text, nor (an explicit hyphen's) in
924        // their replacement; those of `\-` and `\discretionary` are.
925        let sub = self.nodes.subtype(d);
926        let left_kerns = !matches!(sub, 2 | 3);
927        if let Some((f, l)) = bg {
928            if left_kerns
929                && let Some(r) = self.glyph_of_font(pre, f)
930                && let Some(k) = self.kern_between(f, l, r)
931            {
932                let kn = self.font_kern(k, before);
933                self.nodes.link(kn, pre);
934                pre = kn;
935            }
936            if sub == 2 {
937                // No kern in the replacement.
938            } else if !rep_was_empty {
939                if let Some(r) = self.glyph_of_font(rep, f)
940                    && let Some(k) = self.kern_between(f, l, r)
941                {
942                    let kn = self.font_kern(k, before);
943                    self.nodes.link(kn, rep);
944                    rep = kn;
945                }
946            } else if let Some((af, ac)) = ag
947                && af == f
948                && let Some(k) = self.kern_between(f, l, ac)
949            {
950                rep = self.font_kern(k, before);
951            }
952        }
953        if let Some((f, r)) = ag {
954            if !post.is_null() {
955                let t = self.nodes.tail_of(post);
956                if let Some(l) = self.glyph_of_font(t, f)
957                    && let Some(k) = self.kern_between(f, l, r)
958                {
959                    let kn = self.font_kern(k, after);
960                    self.nodes.link(t, kn);
961                }
962            }
963            if !rep_was_empty {
964                let t = self.nodes.tail_of(rep);
965                if let Some(l) = self.glyph_of_font(t, f)
966                    && let Some(k) = self.kern_between(f, l, r)
967                {
968                    let kn = self.font_kern(k, after);
969                    self.nodes.link(t, kn);
970                }
971            }
972        }
973        self.nodes.set_disc_lists(d, (pre, post, rep));
974    }
975
976    /// Kerns a detached list; returns its new head.
977    fn kern_sublist(&mut self, l: NodeRef) -> NodeRef {
978        if l.is_null() {
979            return l;
980        }
981        let h = self.nodes.new_temp();
982        self.nodes.link(h, l);
983        self.kern_list(h);
984        let r = self.nodes.next(h);
985        if !r.is_null() {
986            self.nodes.set_prev(r, NodeRef::NULL);
987        }
988        self.nodes.free(h);
989        r
990    }
991}