Skip to main content

retex_lua/
otfnodes.rs

1//! luaotfload's node processor: OpenType shaping applied to node lists.
2//!
3//! A *run* is a sequence of unprocessed glyphs of one OpenType font,
4//! possibly interrupted by discretionaries whose lists hold only such
5//! glyphs. Runs are shaped with [`retex_font::Shaper`] and the glyph nodes
6//! are replaced by the result (glyphs, ligatures with their components,
7//! font kerns). Shaped glyphs are marked as processed (subtype bit 256),
8//! as LuaTeX's `node.protect_glyphs` does.
9//!
10//! Discretionaries take part in shaping as luaotfload does. Observed with
11//! `luatex` (Latin Modern and Linux Libertine, see the regression tests):
12//!
13//! * The unbroken text (each discretionary replaced by its replacement
14//!   list) is shaped as a whole. Its glyphs that come entirely from text
15//!   before a discretionary stay in front of it, those entirely from text
16//!   after it stay behind it; everything in between (kerns across the
17//!   break point, ligatures that span it) becomes the replacement list. So
18//!   a kern between `n` and `t` gives `n{-||[k]}t`, and `of\-fice` gives
19//!   `o{f-|fi|ffi}ce`.
20//! * The pre-break list is the characters the replacement took from the
21//!   left followed by the pre-break text, shaped on their own; the
22//!   post-break list likewise. The glyph outside next to them only adds a
23//!   kern at the boundary: in bold Libertine (no `ffl` ligature)
24//!   `af\-fluent` gives `a{f-|f[k]|ff[k]}luent`, without an `fl` ligature
25//!   in the post-break list.
26//! * When two discretionaries follow each other directly, nothing is
27//!   shaped across them (`W\-\-o` gets no kern).
28//!
29//! `mode=harf` fonts (polyglossia's default) differ, as HarfBuzz reports
30//! where breaking the shaped text is safe: a discretionary also takes the
31//! glyphs on either side up to a boundary without a kern (`AV\-AV` puts
32//! the whole word into it), its pre-break and post-break texts get no
33//! kern with the glyphs outside, and discretionaries whose glyphs overlap
34//! become one, with the first one's pre-break and the last one's
35//! post-break text (`W\-\-o` gives `{W-|o|W[k]o}`).
36
37use std::collections::{HashMap, HashSet};
38use std::sync::Arc;
39
40use retex_engine::Engine;
41use retex_engine::nodes::{GlyphData, NodeData, NodeRef, glyph_sub, id, kern_sub};
42use retex_font::{LoadedFont, ShapedItem};
43
44use crate::luaotfload::loaded_font;
45
46/// Protection bit LuaTeX sets in the subtype of processed glyphs.
47const PROTECTED: u16 = 256;
48
49/// The unprocessed glyph `p` in font `font`, if it is one.
50fn is_raw_glyph(e: &Engine, p: NodeRef, font: u32) -> bool {
51    let n = e.nodes.get(p);
52    matches!(&n.data, NodeData::Glyph(g) if g.font == font && n.subtype < PROTECTED)
53}
54
55/// The nodes of the list starting at `head`, if all are unprocessed glyphs
56/// of `font`.
57fn glyph_list(e: &Engine, head: NodeRef, font: u32) -> Option<Vec<NodeRef>> {
58    let mut v = Vec::new();
59    let mut p = head;
60    while !p.is_null() {
61        if !is_raw_glyph(e, p, font) {
62            return None;
63        }
64        v.push(p);
65        p = e.nodes.next(p);
66    }
67    Some(v)
68}
69
70fn disc_lists(e: &Engine, d: NodeRef) -> [NodeRef; 3] {
71    match &e.nodes.get(d).data {
72        NodeData::Disc {
73            pre, post, replace, ..
74        } => [*pre, *post, *replace],
75        _ => [NodeRef::NULL; 3],
76    }
77}
78
79fn set_disc_lists(e: &mut Engine, d: NodeRef, lists: [NodeRef; 3]) {
80    if let NodeData::Disc {
81        pre, post, replace, ..
82    } = &mut e.nodes.get_mut(d).data
83    {
84        [*pre, *post, *replace] = lists;
85    }
86}
87
88/// How many characters of context [`context`] collects on each side.
89const CONTEXT_LEN: usize = 8;
90
91/// The text around a run that contextual lookups can see: from `p`
92/// onwards (or backwards), glue counts as a space and glyphs of `font` as
93/// their characters; anything else ends it. Observed with luaotfload (Inter's
94/// `calt`: `A) v` keeps the default `)`, `A)\kern0pt v`, `A)~v` and
95/// `A)\penalty0 v` get the capital form).
96fn context(e: &Engine, mut p: NodeRef, font: u32, forward: bool) -> Vec<u32> {
97    let mut out = Vec::new();
98    while !p.is_null() && out.len() < CONTEXT_LEN {
99        match &e.nodes.get(p).data {
100            NodeData::Glue { .. } => out.push(0x20),
101            NodeData::Glyph(g) if g.font == font => out.push(g.chr),
102            _ => break,
103        }
104        p = if forward {
105            e.nodes.next(p)
106        } else {
107            e.nodes.prev(p)
108        };
109    }
110    if !forward {
111        out.reverse();
112    }
113    out
114}
115
116fn chr(e: &Engine, p: NodeRef) -> u32 {
117    match &e.nodes.get(p).data {
118        NodeData::Glyph(g) => g.chr,
119        _ => 0,
120    }
121}
122
123/// A run: glyph segments separated by discretionaries
124/// (`segs.len() == discs.len() + 1`).
125struct DiscRun {
126    segs: Vec<Vec<NodeRef>>,
127    discs: Vec<NodeRef>,
128    /// Pre, post and replacement glyphs of each discretionary.
129    lists: Vec<[Vec<NodeRef>; 3]>,
130}
131
132/// Shapes all runs of unprocessed glyphs in the list starting at `head`
133/// (luaotfload's node mode). Returns the (possibly new) head of the list.
134pub fn process_list(e: &mut Engine, head: NodeRef) -> NodeRef {
135    let mut head = head;
136    let mut marks: HashMap<u32, bool> = HashMap::new();
137    // The text direction: the paragraph's (its local_par node), changed
138    // by direction nodes; right to left (TRT) shapes mode=harf text so.
139    let mut base_rtl = false;
140    let mut dirs: Vec<bool> = Vec::new();
141    let mut p = head;
142    while !p.is_null() {
143        let node = e.nodes.get(p);
144        match &node.data {
145            NodeData::Glyph(g) if node.subtype < PROTECTED => {
146                let font = g.font;
147                let Some(lf) = loaded_font(e, font) else {
148                    p = e.nodes.next(p);
149                    continue;
150                };
151                let mut run = DiscRun {
152                    segs: vec![vec![p]],
153                    discs: Vec::new(),
154                    lists: Vec::new(),
155                };
156                let mut q = e.nodes.next(p);
157                while !q.is_null() {
158                    if is_raw_glyph(e, q, font) {
159                        run.segs.last_mut().expect("segment").push(q);
160                    } else if e.nodes.id(q) == id::DISC {
161                        let [pre, post, rep] = disc_lists(e, q);
162                        let (Some(a), Some(b), Some(c)) = (
163                            glyph_list(e, pre, font),
164                            glyph_list(e, post, font),
165                            glyph_list(e, rep, font),
166                        ) else {
167                            break;
168                        };
169                        run.discs.push(q);
170                        run.lists.push([a, b, c]);
171                        run.segs.push(Vec::new());
172                    } else {
173                        break;
174                    }
175                    q = e.nodes.next(q);
176                }
177                let first = p;
178                let marks = *marks.entry(font).or_insert_with(|| {
179                    let mut chars = Vec::new();
180                    font_chars(e, head, font, &mut chars);
181                    lf.shaper.has_marks(&lf.file, &lf.tex, &chars)
182                });
183                let rtl = dirs.last().copied().unwrap_or(base_rtl);
184                let ctx = if lf.shaper.uses_context {
185                    (
186                        context(e, e.nodes.prev(first), font, false),
187                        context(e, q, font, true),
188                        marks,
189                        rtl,
190                    )
191                } else {
192                    (Vec::new(), Vec::new(), marks, rtl)
193                };
194                let new_first = if run.discs.is_empty() {
195                    shape_plain(e, &lf, &run.segs[0], &ctx)
196                } else {
197                    shape_disc_run(e, &lf, &run, &ctx)
198                        .unwrap_or_else(|| shape_separately(e, &lf, &run, ctx.2, ctx.3))
199                };
200                if first == head {
201                    head = new_first;
202                }
203                p = q;
204            }
205            NodeData::LocalPar { dir, .. } => {
206                base_rtl = *dir == 1;
207                p = e.nodes.next(p);
208            }
209            NodeData::Dir { dir, .. } => {
210                if node.subtype == 0 {
211                    dirs.push(*dir == 1);
212                } else {
213                    dirs.pop();
214                }
215                p = e.nodes.next(p);
216            }
217            NodeData::Disc { .. } => {
218                let lists = disc_lists(e, p);
219                let new = lists.map(|l| if l.is_null() { l } else { process_list(e, l) });
220                set_disc_lists(e, p, new);
221                p = e.nodes.next(p);
222            }
223            _ => p = e.nodes.next(p),
224        }
225    }
226    head
227}
228
229/// Builds nodes for shaped `items`; `src[i]` is the original glyph node of
230/// input character `i`, whose data and attributes the new glyph inherits.
231///
232/// A glyph that comes from a single source glyph reuses that node (once;
233/// `reused` collects them), as luaotfload changes glyph nodes in place: the
234/// node keeps its identity, so Lua properties set before shaping survive
235/// (tagpdf marks discretionary hyphens in `pre_shaping_filter` and rewrites
236/// them in `post_shaping_filter`).
237fn build(
238    e: &mut Engine,
239    items: &[ShapedItem],
240    src: &[NodeRef],
241    reused: &mut HashSet<NodeRef>,
242) -> Vec<NodeRef> {
243    let mut out = Vec::with_capacity(items.len());
244    let Some(&last_src) = src.last() else {
245        return out;
246    };
247    let source = |k: usize| src.get(k).copied().unwrap_or(last_src);
248    for it in items {
249        match it {
250            ShapedItem::Glyph(g) => {
251                let s = source(g.cluster);
252                let mut data = match &e.nodes.get(s).data {
253                    NodeData::Glyph(d) => d.clone(),
254                    _ => GlyphData::default(),
255                };
256                let attr = e.nodes.get(s).attr.clone();
257                data.chr = g.code;
258                data.xoffset = g.x_offset;
259                data.yoffset = g.y_offset;
260                data.components = NodeRef::NULL;
261                let mut subtype = glyph_sub::CHARACTER | PROTECTED;
262                if !g.components.is_empty() {
263                    let mut ch = NodeRef::NULL;
264                    let mut tail = NodeRef::NULL;
265                    for (k, &c) in g.components.iter().enumerate() {
266                        let n = e.nodes.copy_node(source(g.cluster + k));
267                        if let NodeData::Glyph(gd) = &mut e.nodes.get_mut(n).data {
268                            gd.chr = c;
269                        }
270                        if ch.is_null() {
271                            ch = n;
272                        } else {
273                            e.nodes.link(tail, n);
274                        }
275                        tail = n;
276                    }
277                    data.components = ch;
278                    // luaotfload keeps the components but does not mark
279                    // the glyph as a ligature (observed subtype 256), so
280                    // box displays show no "(ligature …)".
281                    subtype = PROTECTED;
282                }
283                let n = if data.components.is_null() && reused.insert(s) {
284                    let node = e.nodes.get_mut(s);
285                    node.subtype = subtype;
286                    node.data = NodeData::Glyph(data);
287                    s
288                } else {
289                    let n = e.nodes.alloc(id::GLYPH, subtype, NodeData::Glyph(data));
290                    e.nodes.get_mut(n).attr = attr;
291                    n
292                };
293                out.push(n);
294            }
295            ShapedItem::Kern(k) => {
296                let attr = e.nodes.get(last_src).attr.clone();
297                let n = e.nodes.new_kern(*k, kern_sub::FONT);
298                // luaotfload copies its kerns: they get the current line.
299                e.nodes.sync_as_copied(n);
300                e.nodes.get_mut(n).attr = attr;
301                out.push(n);
302            }
303        }
304    }
305    // Kerns take the attributes of the glyph before them.
306    for i in 1..out.len() {
307        if e.nodes.id(out[i]) == id::KERN {
308            let a = e.nodes.get(out[i - 1]).attr.clone();
309            e.nodes.get_mut(out[i]).attr = a;
310        }
311    }
312    out
313}
314
315/// Links `nodes` into a list and returns its head.
316fn link_all(e: &mut Engine, nodes: &[NodeRef]) -> NodeRef {
317    for w in nodes.windows(2) {
318        e.nodes.link(w[0], w[1]);
319    }
320    if let Some(&f) = nodes.first() {
321        e.nodes.get_mut(f).prev = NodeRef::NULL;
322    }
323    if let Some(&l) = nodes.last() {
324        e.nodes.get_mut(l).next = NodeRef::NULL;
325    }
326    nodes.first().copied().unwrap_or(NodeRef::NULL)
327}
328
329/// Frees glyph nodes (each a single node in some list), except those
330/// reused by [`build`].
331fn free_glyphs(e: &mut Engine, nodes: &[NodeRef], reused: &HashSet<NodeRef>) {
332    for &n in nodes {
333        if !reused.contains(&n) {
334            e.nodes.get_mut(n).next = NodeRef::NULL;
335            e.nodes.flush_list(n);
336        }
337    }
338}
339
340/// Replaces the nodes from `first` to `last` (inclusive) of a list by
341/// `new`; returns the node now in the place of `first`.
342/// Links `new` between `prev` and `next` (either may be null), replacing
343/// what was there. Returns the first node after `prev`.
344fn splice(e: &mut Engine, prev: NodeRef, next: NodeRef, new: &[NodeRef]) -> NodeRef {
345    let mut cur = prev;
346    for &n in new {
347        if cur.is_null() {
348            e.nodes.get_mut(n).prev = NodeRef::NULL;
349        } else {
350            e.nodes.link(cur, n);
351        }
352        cur = n;
353    }
354    if cur.is_null() {
355        return next;
356    }
357    e.nodes.link(cur, next);
358    if !next.is_null() {
359        e.nodes.get_mut(next).prev = cur;
360    }
361    new.first().copied().unwrap_or(next)
362}
363
364/// Shapes a run of glyphs without discretionaries.
365/// Context characters before and after a run (see [`context`]), and
366/// whether the list holds a combining mark of the run's font (see
367/// `Shaper::has_marks`).
368/// The text before and after a run, whether its font's characters
369/// include marks, and whether it is right to left.
370type Context = (Vec<u32>, Vec<u32>, bool, bool);
371
372/// Shapes `text`: with HarfBuzz for mode=harf fonts when the text needs it
373/// (complex scripts, right to left) and for the joining scripts (Arabic)
374/// in node mode, else with retex's own shaper.
375fn shape_text(
376    lf: &LoadedFont,
377    before: &[u32],
378    text: &[u32],
379    after: &[u32],
380    marks: bool,
381    rtl: bool,
382) -> Vec<retex_font::ShapedItem> {
383    let harf = lf.request.mode == retex_font::Mode::Harf;
384    let use_hb = if harf {
385        rtl || retex_font::harf::needs_harfbuzz(text)
386    } else {
387        lf.request.mode == retex_font::Mode::Node && retex_font::harf::joining_script(text)
388    };
389    if use_hb && let Some(h) = lf.harf() {
390        return h.shape(&lf.tex, text, rtl);
391    }
392    let mut items = lf
393        .shaper
394        .shape_marked(&lf.file, &lf.tex, before, text, after, marks);
395    if rtl {
396        mirror_mark_offsets(lf, &mut items);
397    }
398    items
399}
400
401/// Right-to-left text: a mark's horizontal offset counts from the left
402/// edge of its base, towards the left (as LuaTeX applies it there), not
403/// from the end of the base. retex's shaper gives `anchors − advances`
404/// (the advances from the base to the mark); the offset is then
405/// `−(anchors)`. Observed with node-mode Hebrew in DejaVu Sans.
406fn mirror_mark_offsets(lf: &LoadedFont, items: &mut [retex_font::ShapedItem]) {
407    use retex_font::ShapedItem;
408    let mut since_base = 0i32;
409    for it in items.iter_mut() {
410        match it {
411            ShapedItem::Glyph(g) if g.x_offset == 0 && g.y_offset == 0 => {
412                since_base = g
413                    .glyph
414                    .map(|gid| lf.tex.glyph_metrics(gid).width)
415                    .unwrap_or(0);
416            }
417            ShapedItem::Glyph(g) => {
418                if g.x_offset != 0 {
419                    g.x_offset = -(g.x_offset + since_base);
420                }
421                since_base += g
422                    .glyph
423                    .map(|gid| lf.tex.glyph_metrics(gid).width)
424                    .unwrap_or(0);
425            }
426            ShapedItem::Kern(k) => since_base += *k,
427        }
428    }
429}
430
431/// The characters of font `font` in the list `head`, discretionaries
432/// included.
433fn font_chars(e: &Engine, head: NodeRef, font: u32, out: &mut Vec<u32>) {
434    let mut p = head;
435    while !p.is_null() {
436        match &e.nodes.get(p).data {
437            NodeData::Glyph(g) if g.font == font => out.push(chr(e, p)),
438            NodeData::Disc { .. } => {
439                for l in disc_lists(e, p) {
440                    font_chars(e, l, font, out);
441                }
442            }
443            _ => {}
444        }
445        p = e.nodes.next(p);
446    }
447}
448
449fn shape_plain(e: &mut Engine, lf: &Arc<LoadedFont>, run: &[NodeRef], ctx: &Context) -> NodeRef {
450    let chars: Vec<u32> = run.iter().map(|&n| chr(e, n)).collect();
451    let items = shape_text(lf, &ctx.0, &chars, &ctx.1, ctx.2, ctx.3);
452    let unchanged = items.len() == run.len()
453        && items.iter().zip(&chars).all(|(it, &c)| {
454            matches!(it, ShapedItem::Glyph(g) if g.code == c && g.components.is_empty()
455                && g.x_offset == 0 && g.y_offset == 0)
456        });
457    let first = run[0];
458    let last = *run.last().expect("non-empty run");
459    if unchanged {
460        for &n in run {
461            e.nodes.get_mut(n).subtype |= PROTECTED;
462        }
463        return first;
464    }
465    let mut reused = HashSet::new();
466    let (prev, next) = (e.nodes.get(first).prev, e.nodes.next(last));
467    let new = build(e, &items, run, &mut reused);
468    let new_first = splice(e, prev, next, &new);
469    free_glyphs(e, run, &reused);
470    new_first
471}
472
473fn n_chars(it: &ShapedItem) -> usize {
474    match it {
475        ShapedItem::Glyph(g) => g.components.len().max(1),
476        ShapedItem::Kern(_) => 0,
477    }
478}
479
480/// Shapes a run with discretionaries as described in the module
481/// documentation. Returns `None` (leaving the nodes untouched) when the
482/// run cannot be handled that way.
483fn shape_disc_run(
484    e: &mut Engine,
485    lf: &Arc<LoadedFont>,
486    run: &DiscRun,
487    ctx: &Context,
488) -> Option<NodeRef> {
489    let k = run.discs.len();
490    let harf = lf.request.mode == retex_font::request::Mode::Harf;
491    // The run's neighbours, before its glyphs move into new lists.
492    let first = run.segs[0][0];
493    let last = run
494        .segs
495        .last()
496        .and_then(|s| s.last().copied())
497        .unwrap_or(*run.discs.last().expect("a discretionary"));
498    let (prev, next) = (e.nodes.get(first).prev, e.nodes.next(last));
499    if !harf && run.segs[1..k].iter().any(Vec::is_empty) {
500        return None;
501    }
502    // The unbroken text and the start of each replacement in it.
503    let mut usrc: Vec<NodeRef> = Vec::new();
504    let mut starts = Vec::with_capacity(k);
505    for i in 0..k {
506        usrc.extend(&run.segs[i]);
507        starts.push(usrc.len());
508        usrc.extend(&run.lists[i][2]);
509    }
510    usrc.extend(&run.segs[k]);
511    let uchars: Vec<u32> = usrc.iter().map(|&n| chr(e, n)).collect();
512    if lf.shaper.changes_text(&lf.tex, &uchars, ctx.2) {
513        return None;
514    }
515    let shape = |src: &[NodeRef]| {
516        let chars: Vec<u32> = src.iter().map(|&n| chr(e, n)).collect();
517        shape_text(lf, &[], &chars, &[], ctx.2, ctx.3)
518    };
519    let u = shape_text(lf, &ctx.0, &uchars, &ctx.1, ctx.2, ctx.3);
520    let n = u.len();
521    let is_kern = |i: usize| matches!(u[i], ShapedItem::Kern(_));
522    struct Piece {
523        pre: (Vec<ShapedItem>, Vec<NodeRef>),
524        post: (Vec<ShapedItem>, Vec<NodeRef>),
525        lo: usize,
526        hi: usize,
527        /// The discretionary, and those merged into it (harf mode).
528        disc: NodeRef,
529        merged: Vec<NodeRef>,
530    }
531    let glyph_id = |it: &ShapedItem| match it {
532        ShapedItem::Glyph(g) => g.glyph,
533        ShapedItem::Kern(_) => None,
534    };
535    // The items of `u` each discretionary takes: (lo, hi, a, b).
536    let mut regions: Vec<(usize, usize, usize, usize)> = Vec::with_capacity(k);
537    for (&a, lists) in starts.iter().zip(&run.lists) {
538        let b = a + lists[2].len();
539        // The items of `u` entirely before the replacement stay in front
540        // of the discretionary; those entirely after it, behind.
541        let mut lo = 0;
542        while lo < n
543            && match &u[lo] {
544                ShapedItem::Glyph(g) => g.cluster + n_chars(&u[lo]) <= a,
545                ShapedItem::Kern(_) => true,
546            }
547        {
548            lo += 1;
549        }
550        while lo > 0 && is_kern(lo - 1) {
551            lo -= 1;
552        }
553        let mut s = 0;
554        while s < n
555            && match &u[n - 1 - s] {
556                ShapedItem::Glyph(g) => g.cluster >= b,
557                ShapedItem::Kern(_) => true,
558            }
559        {
560            s += 1;
561        }
562        while s > 0 && is_kern(n - s) {
563            s -= 1;
564        }
565        let mut hi = n - s;
566        if harf {
567            // Out to boundaries where breaking is safe: no kern there.
568            while lo > 0 && (is_kern(lo - 1) || (lo < n && is_kern(lo))) {
569                lo -= 1;
570            }
571            while hi < n && (is_kern(hi) || (hi > 0 && is_kern(hi - 1))) {
572                hi += 1;
573            }
574        }
575        if hi < lo {
576            return None;
577        }
578        regions.push((lo, hi, a, b));
579    }
580    // Groups of discretionaries that become one: overlapping ones in harf
581    // mode (elsewhere an overlap is not handled).
582    let mut groups: Vec<(usize, usize)> = Vec::new();
583    for i in 0..k {
584        match groups.last_mut() {
585            Some(g) if regions[i].0 < regions[g.1].1 => {
586                if !harf {
587                    return None;
588                }
589                g.1 = i;
590            }
591            _ => groups.push((i, i)),
592        }
593    }
594    let mut pieces: Vec<Piece> = Vec::with_capacity(groups.len());
595    for &(first, last) in &groups {
596        let lo = (first..=last).map(|i| regions[i].0).min().unwrap_or(0);
597        let hi = (first..=last).map(|i| regions[i].1).max().unwrap_or(0);
598        let (a, b) = (regions[first].2, regions[last].3);
599        let (pre, post) = (&run.lists[first][0], &run.lists[last][1]);
600        // The characters the replacement took from either side.
601        let ca = u[..lo]
602            .iter()
603            .filter_map(|it| match it {
604                ShapedItem::Glyph(g) => Some(g.cluster + n_chars(it)),
605                ShapedItem::Kern(_) => None,
606            })
607            .max()
608            .unwrap_or(0)
609            .min(a);
610        let cb = u[hi..]
611            .iter()
612            .filter_map(|it| match it {
613                ShapedItem::Glyph(g) => Some(g.cluster),
614                ShapedItem::Kern(_) => None,
615            })
616            .min()
617            .unwrap_or(usrc.len())
618            .max(b);
619        // Pre-break and post-break texts are shaped on their own; in node
620        // mode the glyphs outside add a kern at the boundary.
621        let mut psrc = usrc[ca..a].to_vec();
622        psrc.extend(pre);
623        let mut qsrc = post.clone();
624        qsrc.extend(&usrc[b..cb]);
625        let mut pre_items = shape(&psrc);
626        let mut post_items = shape(&qsrc);
627        if !harf {
628            if let (Some(l), Some(r)) = (
629                lo.checked_sub(1).and_then(|i| glyph_id(&u[i])),
630                pre_items.first().and_then(glyph_id),
631            ) {
632                let k = lf.shaper.pair_kern(&lf.file, &lf.tex, l, r);
633                if k != 0 {
634                    pre_items.insert(0, ShapedItem::Kern(k));
635                }
636            }
637            if let (Some(l), Some(r)) = (
638                post_items.last().and_then(glyph_id),
639                u.get(hi).and_then(glyph_id),
640            ) {
641                let k = lf.shaper.pair_kern(&lf.file, &lf.tex, l, r);
642                if k != 0 {
643                    post_items.push(ShapedItem::Kern(k));
644                }
645            }
646        }
647        pieces.push(Piece {
648            pre: (pre_items, psrc),
649            post: (post_items, qsrc),
650            lo,
651            hi,
652            disc: run.discs[first],
653            merged: run.discs[first + 1..=last].to_vec(),
654        });
655    }
656    // Build the new list.
657    let mut out: Vec<NodeRef> = Vec::new();
658    let mut pos = 0usize;
659    let mut reused = HashSet::new();
660    for pc in &pieces {
661        out.extend(build(e, &u[pos..pc.lo], &usrc, &mut reused));
662        let pre = build(e, &pc.pre.0, &pc.pre.1, &mut reused);
663        let post = build(e, &pc.post.0, &pc.post.1, &mut reused);
664        let rep = build(e, &u[pc.lo..pc.hi], &usrc, &mut reused);
665        let d = pc.disc;
666        let lists = [link_all(e, &pre), link_all(e, &post), link_all(e, &rep)];
667        set_disc_lists(e, d, lists);
668        out.push(d);
669        pos = pc.hi;
670    }
671    out.extend(build(e, &u[pos..], &usrc, &mut reused));
672    // Splice, then free the old glyphs.
673    let new_first = splice(e, prev, next, &out);
674    for seg in &run.segs {
675        free_glyphs(e, seg, &reused);
676    }
677    for l in &run.lists {
678        for part in l {
679            free_glyphs(e, part, &reused);
680        }
681    }
682    // Discretionaries merged into another one (their glyphs are freed).
683    for &d in pieces.iter().flat_map(|pc| &pc.merged) {
684        set_disc_lists(e, d, [NodeRef::NULL; 3]);
685        e.nodes.get_mut(d).next = NodeRef::NULL;
686        e.nodes.flush_list(d);
687    }
688    Some(new_first)
689}
690
691/// Shapes the segments and discretionary lists of `run` independently.
692fn shape_separately(
693    e: &mut Engine,
694    lf: &Arc<LoadedFont>,
695    run: &DiscRun,
696    marks: bool,
697    rtl: bool,
698) -> NodeRef {
699    let mut first = NodeRef::NULL;
700    for (i, seg) in run.segs.iter().enumerate() {
701        if !seg.is_empty() {
702            let f = shape_plain(e, lf, seg, &(Vec::new(), Vec::new(), marks, rtl));
703            if i == 0 {
704                first = f;
705            }
706        }
707        if let Some(&d) = run.discs.get(i) {
708            let lists = disc_lists(e, d);
709            let new = lists.map(|l| if l.is_null() { l } else { process_list(e, l) });
710            set_disc_lists(e, d, new);
711        }
712    }
713    first
714}