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