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                if !is_hyphen(self, p) {
178                    match self.nodes.id(p) {
179                        id::GLUE => armed = true,
180                        id::KERN => armed = false,
181                        _ => {}
182                    }
183                    prev = p;
184                    p = self.nodes.next(p);
185                    continue;
186                }
187                // Find the run of hyphens.
188                let mut last = p;
189                let mut len = 1;
190                let mut q = self.nodes.next(p);
191                while !q.is_null() && is_hyphen(self, q) {
192                    last = q;
193                    len += 1;
194                    q = self.nodes.next(q);
195                }
196                let followed = !q.is_null() && self.nodes.is_glyph(q);
197                let convert =
198                    followed && (len == 1 || (armed && prev != head && self.has_hj_code(prev)));
199                if !convert {
200                    prev = last;
201                    p = q;
202                    continue;
203                }
204                if len > 1 {
205                    armed = false;
206                }
207                // Replace the last hyphen by an automatic discretionary.
208                let before = if len == 1 {
209                    prev
210                } else {
211                    self.nodes.prev(last)
212                };
213                self.nodes.set_next(last, NodeRef::NULL);
214                let d = self.nodes.new_disc(2, pen);
215                if let Some(a) = self.nodes.attr(last).cloned() {
216                    self.nodes.set_attr(d, Some(a));
217                }
218                let pre = self.nodes.copy_list(last);
219                self.nodes.set_prev(last, NodeRef::NULL);
220                self.nodes.set_disc_lists(d, (pre, NodeRef::NULL, last));
221                self.nodes.link(before, d);
222                self.nodes.link(d, q);
223                prev = d;
224                p = q;
225            }
226        }
227        if !self.languages.is_empty() {
228            self.pattern_hyphenate(head);
229        }
230    }
231
232    /// Whether `p` is a glyph whose character has a non-zero hyphenation
233    /// code in its language.
234    fn has_hj_code(&self, p: NodeRef) -> bool {
235        let Some(g) = self.nodes.glyph(p) else {
236            return false;
237        };
238        let lang = u32::from(g.lang);
239        let c = g.chr;
240        let code = match self.language(lang) {
241            Some(l) if l.has_hj_codes() => l.hj_code(c),
242            _ => self.eqtb.lc.get(c).max(0) as u32,
243        };
244        code != 0
245    }
246
247    /// Inserts pattern discretionaries into the words of the list
248    /// (LuaTeX's hyphenation pass, using `retex-hyph`).
249    ///
250    /// Observed: a word is hyphenated only if what precedes it lets a word
251    /// start and what follows it lets a word end. The list start and glue
252    /// let words start; once the list has had a glyph, kerns, penalties,
253    /// discretionaries, marks and `\vadjust`s stop that until the next
254    /// glue; other glyphs, boxes, rules, whatsits, math and direction
255    /// nodes leave it as it is. A word followed by a discretionary, a math
256    /// node or a mark is not hyphenated. With `\automatichyphenmode` > 0 automatic
257    /// discretionaries (from explicit hyphens) count as letting words
258    /// start and end.
259    fn pattern_hyphenate(&mut self, head: NodeRef) {
260        let hpen = self.eqtb.int(ip::HYPHENPENALTY);
261        let auto_ok = self.eqtb.int(ip::AUTOMATICHYPHENMODE) != 0;
262        let mut run: Vec<NodeRef> = Vec::new();
263        let mut word: Vec<NodeRef> = Vec::new();
264        let mut codes: Vec<u32> = Vec::new();
265        let mut pos: Vec<usize> = Vec::new();
266        let mut can_start = true;
267        let mut seen_glyph = false;
268        let mut p = self.nodes.next(head);
269        while !p.is_null() {
270            if self.nodes.is_glyph(p) && self.nodes.subtype(p) & glyph_sub::CHARACTER != 0 {
271                // A run of character glyphs.
272                run.clear();
273                let mut q = p;
274                while !q.is_null()
275                    && self.nodes.is_glyph(q)
276                    && self.nodes.subtype(q) & glyph_sub::CHARACTER != 0
277                {
278                    run.push(q);
279                    q = self.nodes.next(q);
280                }
281                let end_ok = q.is_null()
282                    || match self.nodes.id(q) {
283                        id::DISC => auto_ok && self.nodes.subtype(q) == 2,
284                        id::MATH | id::MARK => false,
285                        _ => true,
286                    };
287                let bounds = (can_start, end_ok);
288                self.hyphenate_run(&run, hpen, bounds, &mut word, &mut codes, &mut pos);
289                seen_glyph = true;
290                p = q;
291                continue;
292            }
293            match self.nodes.id(p) {
294                id::GLUE => can_start = true,
295                id::KERN | id::PENALTY | id::MARK | id::ADJUST => can_start &= !seen_glyph,
296                id::DISC if auto_ok && self.nodes.subtype(p) == 2 => can_start = true,
297                id::DISC => can_start &= !seen_glyph,
298                _ => {}
299            }
300            p = self.nodes.next(p);
301        }
302    }
303
304    /// Hyphenates the words in a run of character glyphs; `bounds` tells
305    /// whether words may start in the run and end at its end.
306    fn hyphenate_run(
307        &mut self,
308        run: &[NodeRef],
309        hpen: i32,
310        bounds: (bool, bool),
311        word: &mut Vec<NodeRef>,
312        codes: &mut Vec<u32>,
313        pos: &mut Vec<usize>,
314    ) {
315        if !bounds.0 {
316            return;
317        }
318        let mut i = 0;
319        while i < run.len() {
320            // A word: glyphs of one language with non-zero hj codes.
321            let Some(g0) = self.nodes.glyph(run[i]).cloned() else {
322                i += 1;
323                continue;
324            };
325            let lang = u32::from(g0.lang);
326            word.clear();
327            codes.clear();
328            // Observed: a word next to or with an explicit hyphen that did
329            // not become a discretionary (`--word` after a space, `word--`;
330            // LaTeX gives the hyphen an hj code) is not hyphenated.
331            let ex = self.eqtb.int(ip::EXHYPHENCHAR);
332            let mut with_hyphen = false;
333            while i < run.len() {
334                let Some(g) = self.nodes.glyph(run[i]) else {
335                    break;
336                };
337                if u32::from(g.lang) != lang {
338                    break;
339                }
340                let c = g.chr;
341                let code = match self.language(lang) {
342                    Some(l) if l.has_hj_codes() => l.hj_code(c),
343                    _ => self.eqtb.lc.get(c).max(0) as u32,
344                };
345                if code == 0 {
346                    if word.is_empty() {
347                        with_hyphen = ex > 0 && c == ex as u32;
348                        i += 1;
349                        continue;
350                    }
351                    with_hyphen |= ex > 0 && c == ex as u32;
352                    break;
353                }
354                with_hyphen |= ex > 0 && c == ex as u32;
355                word.push(run[i]);
356                codes.push(code);
357                i += 1;
358            }
359            if word.len() < 2 || with_hyphen {
360                continue;
361            }
362            if !bounds.1 && word.last() == run.last() {
363                continue;
364            }
365            let Some(first) = self.nodes.glyph(word[0]).cloned() else {
366                continue;
367            };
368            if first.font == 0 || self.font(first.font).hyphen_char < 0 {
369                continue;
370            }
371            if !first.uchyph && codes[0] != first.chr {
372                continue;
373            }
374            let Some(language) = self.language(lang) else {
375                continue;
376            };
377            pos.clear();
378            let _ =
379                language.hyphenate_into(codes, u32::from(first.left), u32::from(first.right), pos);
380            if pos.is_empty() {
381                continue;
382            }
383            let settings = language.settings;
384            pos.sort_unstable();
385            pos.dedup();
386            for &k in pos.iter().rev() {
387                if k == 0 || k >= word.len() {
388                    continue;
389                }
390                let left = word[k - 1];
391                let right = word[k];
392                let f = self.nodes.glyph(left).map_or(0, |g| g.font);
393                let d = self.nodes.new_disc(3, hpen);
394                if let Some(a) = self.nodes.attr(left).cloned() {
395                    self.nodes.set_attr(d, Some(a));
396                }
397                let mk = |e: &mut Engine, c: i32| {
398                    if c > 0 {
399                        let mut g = e.nodes.glyph(left).cloned().unwrap_or_default();
400                        g.chr = c as u32;
401                        g.font = f;
402                        g.components = NodeRef::NULL;
403                        // Observed: with the attributes of the glyph
404                        // before the break, like the discretionary.
405                        let a = e.nodes.attr(left).cloned();
406                        let n = e
407                            .nodes
408                            .alloc(id::GLYPH, glyph_sub::CHARACTER, NodeData::Glyph(g));
409                        e.nodes.set_attr(n, a);
410                        n
411                    } else {
412                        NodeRef::NULL
413                    }
414                };
415                let pre = mk(self, settings.pre_hyphen_char);
416                let post = mk(self, settings.post_hyphen_char);
417                self.nodes.set_disc_lists(d, (pre, post, NodeRef::NULL));
418                self.nodes.link(left, d);
419                self.nodes.link(d, right);
420            }
421        }
422    }
423
424    // ----- ligaturing ----------------------------------------------------
425
426    /// Whether `p` is a glyph of font `f` (any subtype except ghosts).
427    #[inline]
428    fn glyph_of_font(&self, p: NodeRef, f: u32) -> Option<u32> {
429        if p.is_null() {
430            return None;
431        }
432        match self.nodes.data(p) {
433            NodeData::Glyph(g) if g.font == f && self.nodes.subtype(p) & glyph_sub::GHOST == 0 => {
434                Some(g.chr)
435            }
436            _ => None,
437        }
438    }
439
440    /// The ligature step for (`l`, `r`) in font `f`, if it is a ligature.
441    #[inline]
442    fn lig_step(&self, f: u32, l: u32, r: u32) -> Option<(u8, u32)> {
443        match self.lig_kern_step(f, l, r) {
444            Some(LigOp::Lig { op, chr }) => Some((op, chr)),
445            _ => None,
446        }
447    }
448
449    /// The components of a glyph for a new ligature: the glyph itself, or
450    /// its components if it is already a ligature.
451    fn take_components(&mut self, p: NodeRef) -> NodeRef {
452        let is_lig = self.nodes.subtype(p) & glyph_sub::LIGATURE != 0;
453        if is_lig {
454            let c = self
455                .nodes
456                .glyph_mut(p)
457                .map(|g| std::mem::take(&mut g.components));
458            let c = c.unwrap_or(NodeRef::NULL);
459            self.nodes.free(p);
460            if c.is_null() {
461                return NodeRef::NULL;
462            }
463            c
464        } else {
465            self.nodes.set_next(p, NodeRef::NULL);
466            self.nodes.set_prev(p, NodeRef::NULL);
467            let s = self.nodes.subtype(p) & !glyph_sub::CHARACTER;
468            self.nodes.set_subtype(p, s);
469            p
470        }
471    }
472
473    /// Makes a ligature glyph like `model` with character `c` and the given
474    /// components.
475    fn new_ligature(&mut self, model: NodeRef, c: u32, comps: NodeRef) -> NodeRef {
476        let mut g = self.nodes.glyph(model).cloned().unwrap_or_default();
477        g.chr = c;
478        g.components = comps;
479        let a = self.nodes.attr(model).cloned();
480        let n = self
481            .nodes
482            .alloc(id::GLYPH, glyph_sub::LIGATURE, NodeData::Glyph(g));
483        self.nodes.set_attr(n, a);
484        n
485    }
486
487    /// Concatenates two node lists.
488    fn concat(&mut self, a: NodeRef, b: NodeRef) -> NodeRef {
489        if a.is_null() {
490            return b;
491        }
492        if !b.is_null() {
493            let t = self.nodes.tail_of(a);
494            self.nodes.link(t, b);
495        }
496        a
497    }
498
499    /// Replaces `old` (whose predecessor is `prev`) by `new` in a list.
500    fn replace_node(&mut self, prev: NodeRef, old_next: NodeRef, new: NodeRef) {
501        self.nodes.link(prev, new);
502        self.nodes.link(new, old_next);
503        if old_next.is_null() {
504            self.nodes.set_next(new, NodeRef::NULL);
505        }
506    }
507
508    /// Builds ligatures in the list after `head`. `main` is true for the
509    /// main list (boundary nodes are removed there).
510    fn ligature_list(&mut self, head: NodeRef, main: bool) {
511        let mut prev = head;
512        let mut p = self.nodes.next(head);
513        while !p.is_null() {
514            match self.nodes.id(p) {
515                id::BOUNDARY if main => {
516                    let n = self.nodes.next(p);
517                    self.nodes.set_next(prev, n);
518                    if !n.is_null() {
519                        self.nodes.set_prev(n, prev);
520                    }
521                    self.nodes.free(p);
522                    p = n;
523                }
524                id::GLYPH => {
525                    let (last_prev, last) = self.ligature_run(prev, p);
526                    // `last` is the final glyph of the run; it may be
527                    // followed by a discretionary.
528                    let n = self.nodes.next(last);
529                    if main
530                        && (n.is_null()
531                            || !matches!(self.nodes.id(n), id::GLYPH | id::DISC | id::BOUNDARY))
532                    {
533                        self.right_boundary_kern(last);
534                    }
535                    if !n.is_null() && self.nodes.id(n) == id::DISC {
536                        let (np, nd) = self.lig_disc(last_prev, last, n);
537                        prev = np;
538                        p = nd;
539                    } else {
540                        prev = last;
541                        p = n;
542                    }
543                }
544                id::DISC => {
545                    self.lig_disc_lists(p);
546                    prev = p;
547                    p = self.nodes.next(p);
548                }
549                _ => {
550                    prev = p;
551                    p = self.nodes.next(p);
552                }
553            }
554        }
555    }
556
557    /// Appends the font kern between glyph `p` and its font's right
558    /// boundary character, if there is one. Observed: LuaTeX applies it
559    /// in the main list when the next node is not a glyph, a
560    /// discretionary or a boundary node (`\noboundary`, `\boundary`, ...),
561    /// even after a font kern (an unboxed list gets a second one).
562    fn right_boundary_kern(&mut self, p: NodeRef) {
563        let Some((f, l)) = self.nodes.glyph(p).map(|g| (g.font, g.chr)) else {
564            return;
565        };
566        let Some(b) = self.font(f).right_boundary else {
567            return;
568        };
569        if let Some(k) = self.kern_between(f, l, b) {
570            let kn = self.font_kern(k, p);
571            let n = self.nodes.next(p);
572            self.nodes.link(p, kn);
573            self.nodes.link(kn, n);
574        }
575    }
576
577    /// Ligatures the three lists of a discretionary independently.
578    fn lig_disc_lists(&mut self, d: NodeRef) {
579        let (pre, post, rep) = self.nodes.disc_lists(d);
580        let pre = self.ligature_sublist(pre);
581        let post = self.ligature_sublist(post);
582        let rep = self.ligature_sublist(rep);
583        self.nodes.set_disc_lists(d, (pre, post, rep));
584    }
585
586    /// Ligatures a detached list; returns its new head.
587    fn ligature_sublist(&mut self, l: NodeRef) -> NodeRef {
588        if l.is_null() {
589            return l;
590        }
591        let h = self.nodes.new_temp();
592        self.nodes.link(h, l);
593        self.ligature_list(h, false);
594        let r = self.nodes.next(h);
595        if !r.is_null() {
596            self.nodes.set_prev(r, NodeRef::NULL);
597        }
598        self.nodes.free(h);
599        r
600    }
601
602    /// Handles a discretionary `d` that follows the glyph `last` (whose
603    /// predecessor is found through `prev` links): moves neighbouring
604    /// glyphs into it when ligatures cross it, then ligatures its lists.
605    /// Returns (prev, next) for continuing the scan after `d`.
606    fn lig_disc(&mut self, before: NodeRef, last: NodeRef, d: NodeRef) -> (NodeRef, NodeRef) {
607        let f = self.nodes.glyph(last).map_or(0, |g| g.font);
608        if self.font(f).lig_kern.is_empty() {
609            self.lig_disc_lists(d);
610            return (d, self.nodes.next(d));
611        }
612        let lc = self.nodes.glyph(last).map_or(0, |g| g.chr);
613        let (pre, post, rep) = self.nodes.disc_lists(d);
614        let after = self.nodes.next(d);
615        let crosses =
616            |e: &Engine, l: NodeRef| e.glyph_of_font(l, f).and_then(|c| e.lig_step(f, lc, c));
617        let left = crosses(self, pre).is_some()
618            || (!rep.is_null() && crosses(self, rep).is_some())
619            || (rep.is_null() && crosses(self, after).is_some());
620        let (mut pre, mut post, mut rep) = (pre, post, rep);
621        if left {
622            // Move `last` into the discretionary.
623            self.nodes.link(before, d);
624            self.nodes.set_next(last, NodeRef::NULL);
625            self.nodes.set_prev(last, NodeRef::NULL);
626            let copy = self.nodes.copy_node(last);
627            pre = self.concat(copy, pre);
628            rep = self.concat(last, rep);
629        }
630        // Move following glyphs into the discretionary while a ligature
631        // crosses the right edge.
632        loop {
633            let n = self.nodes.next(d);
634            let Some(nc) = self.glyph_of_font(n, f) else {
635                break;
636            };
637            let ends = |e: &Engine, l: NodeRef| {
638                if l.is_null() {
639                    return false;
640                }
641                let t = e.nodes.tail_of(l);
642                e.glyph_of_font(t, f)
643                    .is_some_and(|c| e.lig_step(f, c, nc).is_some())
644            };
645            if !(ends(self, post) || ends(self, rep)) {
646                break;
647            }
648            let nn = self.nodes.next(n);
649            self.nodes.link(d, nn);
650            if nn.is_null() {
651                self.nodes.set_next(d, NodeRef::NULL);
652            }
653            self.nodes.set_next(n, NodeRef::NULL);
654            self.nodes.set_prev(n, NodeRef::NULL);
655            let copy = self.nodes.copy_node(n);
656            post = self.concat(post, copy);
657            rep = self.concat(rep, n);
658        }
659        self.nodes.set_disc_lists(d, (pre, post, rep));
660        self.lig_disc_lists(d);
661        (d, self.nodes.next(d))
662    }
663
664    /// Builds ligatures in the run of glyphs starting at `p` (with
665    /// predecessor `prev`); returns the last glyph of the processed run
666    /// and its predecessor.
667    fn ligature_run(&mut self, prev: NodeRef, p: NodeRef) -> (NodeRef, NodeRef) {
668        let f = self.nodes.glyph(p).map_or(0, |g| g.font);
669        if self.font(f).lig_kern.is_empty() {
670            // No ligatures in this font: skip the run.
671            let (mut qp, mut q) = (prev, p);
672            loop {
673                let n = self.nodes.next(q);
674                if self.glyph_of_font(n, f).is_none() {
675                    return (qp, q);
676                }
677                qp = q;
678                q = n;
679            }
680        }
681        let mut cur_prev = prev;
682        let mut cur = p;
683        let mut guard = 0u32;
684        loop {
685            let nxt = self.nodes.next(cur);
686            let (Some(l), Some(r)) = (self.glyph_of_font(cur, f), self.glyph_of_font(nxt, f))
687            else {
688                return (cur_prev, cur);
689            };
690            guard += 1;
691            if guard > 100_000 {
692                // An infinite ligature loop in a broken font.
693                return (cur_prev, cur);
694            }
695            let Some((op, c)) = self.lig_step(f, l, r) else {
696                cur_prev = cur;
697                cur = nxt;
698                continue;
699            };
700            let after = self.nodes.next(nxt);
701            match op {
702                0 => {
703                    // =: — both characters replaced.
704                    let a = self.take_components(cur);
705                    let b = self.take_components(nxt);
706                    let comps = self.concat(a, b);
707                    let model = if comps.is_null() { cur } else { comps };
708                    let lig = self.new_ligature(model, c, comps);
709                    self.replace_node(cur_prev, after, lig);
710                    cur = lig;
711                }
712                1 | 5 => {
713                    // =:| and =:|> — the left character is replaced.
714                    let a = self.take_components(cur);
715                    let model = if a.is_null() { nxt } else { a };
716                    let lig = self.new_ligature(model, c, a);
717                    self.replace_node(cur_prev, nxt, lig);
718                    if op == 5 {
719                        cur_prev = lig;
720                        cur = nxt;
721                    } else {
722                        cur = lig;
723                    }
724                }
725                2 | 6 => {
726                    // |=: and |=:> — the right character is replaced.
727                    let b = self.take_components(nxt);
728                    let model = if b.is_null() { cur } else { b };
729                    let lig = self.new_ligature(model, c, b);
730                    self.replace_node(cur, after, lig);
731                    if op == 6 {
732                        cur_prev = cur;
733                        cur = lig;
734                    }
735                }
736                _ => {
737                    // |=:|, |=:|>, |=:|>> — a character is inserted.
738                    let lig = self.new_ligature(cur, c, NodeRef::NULL);
739                    self.nodes.set_subtype(lig, 0);
740                    self.replace_node(cur, nxt, lig);
741                    match op {
742                        7 => {
743                            cur_prev = cur;
744                            cur = lig;
745                        }
746                        11 => {
747                            cur_prev = lig;
748                            cur = nxt;
749                        }
750                        _ => {}
751                    }
752                }
753            }
754        }
755    }
756
757    // ----- kerning -------------------------------------------------------
758
759    /// The font kern between (`l`, `r`) in font `f`.
760    #[inline]
761    fn kern_between(&self, f: u32, l: u32, r: u32) -> Option<i32> {
762        match self.lig_kern_step(f, l, r) {
763            Some(LigOp::Kern(k)) => Some(k),
764            _ => None,
765        }
766    }
767
768    /// A font kern node with the attributes of `model`.
769    fn font_kern(&mut self, k: i32, model: NodeRef) -> NodeRef {
770        let n = self.nodes.new_kern(k, kern_sub::FONT);
771        let a = self.nodes.attr(model).cloned();
772        self.nodes.set_attr(n, a);
773        n
774    }
775
776    /// Inserts font kerns into the list after `head` and clears the
777    /// character subtype of glyphs.
778    fn kern_list(&mut self, head: NodeRef) {
779        let mut p = self.nodes.next(head);
780        while !p.is_null() {
781            match self.nodes.id(p) {
782                id::GLYPH => {
783                    let s = self.nodes.subtype(p) & !glyph_sub::CHARACTER;
784                    self.nodes.set_subtype(p, s);
785                    let n = self.nodes.next(p);
786                    if n.is_null() {
787                        break;
788                    }
789                    let (f, l) = match self.nodes.glyph(p) {
790                        Some(g) => (g.font, g.chr),
791                        None => break,
792                    };
793                    if self.nodes.id(n) == id::GLYPH {
794                        if let Some(r) = self.glyph_of_font(n, f)
795                            && let Some(k) = self.kern_between(f, l, r)
796                        {
797                            let kn = self.font_kern(k, p);
798                            self.nodes.link(p, kn);
799                            self.nodes.link(kn, n);
800                        }
801                        p = n;
802                    } else if self.nodes.id(n) == id::DISC {
803                        self.kern_disc(p, n);
804                        p = self.nodes.next(n);
805                    } else {
806                        p = n;
807                    }
808                }
809                id::DISC => {
810                    self.kern_disc(NodeRef::NULL, p);
811                    p = self.nodes.next(p);
812                }
813                _ => p = self.nodes.next(p),
814            }
815        }
816    }
817
818    /// Kerns a discretionary with its neighbours (`before` may be null)
819    /// and kerns its lists.
820    fn kern_disc(&mut self, before: NodeRef, d: NodeRef) {
821        let (mut pre, mut post, mut rep) = self.nodes.disc_lists(d);
822        pre = self.kern_sublist(pre);
823        post = self.kern_sublist(post);
824        let rep_was_empty = rep.is_null();
825        rep = self.kern_sublist(rep);
826        let after = self.nodes.next(d);
827        let bg = if before.is_null() {
828            None
829        } else {
830            self.nodes.glyph(before).map(|g| (g.font, g.chr))
831        };
832        let ag = if after.is_null() || self.nodes.id(after) != id::GLYPH {
833            None
834        } else {
835            self.nodes.glyph(after).map(|g| (g.font, g.chr))
836        };
837        // Observed: the discretionaries of an explicit hyphen (subtype 2)
838        // and of hyphenation (3) are not kerned with the character before
839        // them in their pre-break text, nor (an explicit hyphen's) in
840        // their replacement; those of `\-` and `\discretionary` are.
841        let sub = self.nodes.subtype(d);
842        let left_kerns = !matches!(sub, 2 | 3);
843        if let Some((f, l)) = bg {
844            if left_kerns
845                && let Some(r) = self.glyph_of_font(pre, f)
846                && let Some(k) = self.kern_between(f, l, r)
847            {
848                let kn = self.font_kern(k, before);
849                self.nodes.link(kn, pre);
850                pre = kn;
851            }
852            if sub == 2 {
853                // No kern in the replacement.
854            } else if !rep_was_empty {
855                if let Some(r) = self.glyph_of_font(rep, f)
856                    && let Some(k) = self.kern_between(f, l, r)
857                {
858                    let kn = self.font_kern(k, before);
859                    self.nodes.link(kn, rep);
860                    rep = kn;
861                }
862            } else if let Some((af, ac)) = ag
863                && af == f
864                && let Some(k) = self.kern_between(f, l, ac)
865            {
866                rep = self.font_kern(k, before);
867            }
868        }
869        if let Some((f, r)) = ag {
870            if !post.is_null() {
871                let t = self.nodes.tail_of(post);
872                if let Some(l) = self.glyph_of_font(t, f)
873                    && let Some(k) = self.kern_between(f, l, r)
874                {
875                    let kn = self.font_kern(k, after);
876                    self.nodes.link(t, kn);
877                }
878            }
879            if !rep_was_empty {
880                let t = self.nodes.tail_of(rep);
881                if let Some(l) = self.glyph_of_font(t, f)
882                    && let Some(k) = self.kern_between(f, l, r)
883                {
884                    let kn = self.font_kern(k, after);
885                    self.nodes.link(t, kn);
886                }
887            }
888        }
889        self.nodes.set_disc_lists(d, (pre, post, rep));
890    }
891
892    /// Kerns a detached list; returns its new head.
893    fn kern_sublist(&mut self, l: NodeRef) -> NodeRef {
894        if l.is_null() {
895            return l;
896        }
897        let h = self.nodes.new_temp();
898        self.nodes.link(h, l);
899        self.kern_list(h);
900        let r = self.nodes.next(h);
901        if !r.is_null() {
902            self.nodes.set_prev(r, NodeRef::NULL);
903        }
904        self.nodes.free(h);
905        r
906    }
907}