Skip to main content

cranpose_text/
shaper.rs

1use std::{
2    cell::OnceCell,
3    hash::{BuildHasher, Hash, Hasher},
4    ops::Range,
5};
6
7use foldhash::fast::FixedState;
8use harfrust::{
9    Buffer, BufferFlags, Direction, Feature, Language, Script as ShapingScript, ShapeOptions,
10    ShapePlan, ShaperFont, Tag,
11    font::{Font, Source, Variation},
12};
13use read_fonts::FontRef;
14use smallvec::SmallVec;
15
16use crate::{
17    font::{FontStyle, FontSynthesis, FontWeight},
18    glyph_id::GlyphId,
19    glyph_ink::InkReach,
20    itemizer::TextItem,
21    plain_glyphs::PlainGlyphs,
22    shaped_glyphs::{BreakBefore, GlyphOffset, ShapedGlyph, ShapedGlyphs},
23    shaping_span::ShapingSpan,
24    synthesized_style::SynthesizedStyle,
25    typeface::Typeface,
26    word_cache::WordCache,
27    word_split::{SpaceCut, WordSplit, word_split},
28};
29
30/// Shapes items with HarfRust. Keeps, for each typeface instance it met, the
31/// shaping font and its shape plans, the words it shaped where the font lets
32/// words shape apart, and one glyph buffer for every call.
33pub struct Shaper {
34    faces: Vec<ShapingFace>,
35    buffer: Buffer,
36    features: Vec<Feature>,
37    pub(crate) words: WordCache,
38    // The ends of an item's words, and its pieces shaped apart: their text
39    // and their glyphs, reused from item to item.
40    word_ends: Vec<usize>,
41    pieces: Vec<(Range<usize>, Range<usize>)>,
42    // How the ink of the glyphs shaped or noted since the layouter last took
43    // it reaches, and the bold and the italic the glyphs ask of faces that
44    // lack them.
45    ink_reach: InkReach,
46    synthesized: SynthesizedStyle,
47}
48
49struct ShapingFace {
50    content_hash: u64,
51    positions: SmallVec<[([u8; 4], f32); 3]>,
52    font: Font,
53    // The newest last: text uses few scripts and feature settings, and a
54    // plan takes as long to make as the shaping.
55    plans: Vec<KeptPlan>,
56    // The plain glyphs of the plans, one for the plans whose lookups leave
57    // the same glyphs plain, as the plans of numbers and of words do.
58    plain: Vec<PlainGlyphs>,
59    // The plan the last item with no language or features of its own took,
60    // and what it asked for: an item that asks the same takes it without
61    // matching the plans kept.
62    last: Option<(PlanAsk, usize)>,
63}
64
65// What chooses the plan of an item with no language or features of its own.
66#[derive(Clone, Copy, PartialEq)]
67struct PlanAsk {
68    rtl: bool,
69    script: [u8; 4],
70}
71
72struct KeptPlan {
73    // What the plan shapes runs for.
74    direction: Direction,
75    script: Option<ShapingScript>,
76    language: Option<Language>,
77    features: SmallVec<[Feature; 4]>,
78    // Made when HarfRust first shapes with the plan, as a run of plain
79    // glyphs needs neither its lookups nor its words.
80    harfrust: OnceCell<HarfRustPlan>,
81    // The index in its face of the glyphs the plan leaves as they are;
82    // `None` when it cannot tell.
83    plain: Option<usize>,
84    // The face and plan as one number, which keys the plan's words.
85    key: u64,
86}
87
88// What HarfRust shapes the runs of a plan with.
89struct HarfRustPlan {
90    plan: ShapePlan,
91    // Where runs cut into words; `None` when they shape whole.
92    split: Option<WordSplit>,
93}
94
95// What one shaping call shapes with.
96struct Setting<'a> {
97    font: ShaperFont<'a, 'a>,
98    // The font `font` shapes with, which plain glyphs read, and its
99    // typeface, whose glyphs' ink they read.
100    face: &'a Font,
101    typeface: &'a Typeface,
102    plan: &'a ShapePlan,
103    features: &'a [Feature],
104    direction: Direction,
105    script: Option<ShapingScript>,
106    language: Option<&'a Language>,
107}
108
109const KEPT_FACES: usize = 16;
110const KEPT_PLANS: usize = 8;
111const HASH_STATE: FixedState = FixedState::with_seed(0);
112
113impl Default for Shaper {
114    fn default() -> Self {
115        Self {
116            faces: Vec::new(),
117            buffer: Buffer::new(),
118            features: Vec::new(),
119            words: WordCache::default(),
120            word_ends: Vec::new(),
121            pieces: Vec::new(),
122            ink_reach: InkReach::Within,
123            synthesized: SynthesizedStyle::default(),
124        }
125    }
126}
127
128impl Shaper {
129    /// Shapes `item` of `text` in `span` and appends its glyphs to `glyphs`,
130    /// in the order they are drawn from left to right. The text of `context`
131    /// around the item is the shaping context, so joining and contextual
132    /// forms carry across item edges inside it.
133    pub fn shape(
134        &mut self,
135        text: &str,
136        item: &TextItem<'_>,
137        span: &ShapingSpan<'_>,
138        context: Range<usize>,
139        glyphs: &mut ShapedGlyphs,
140    ) {
141        let face = self.face(item.typeface, span.font.weight, span.font.style);
142        self.note_synthesis(item, span);
143        let asked = (span.language.is_none() && span.features.is_empty()).then_some(PlanAsk {
144            rtl: item.level.is_rtl(),
145            script: item.script.tag(),
146        });
147        // An item that searched the plans has its settings made, and the
148        // shaper's features set for it.
149        let (plan, settings) = match self.faces[face].last.filter(|last| Some(last.0) == asked) {
150            Some((_, plan)) => (plan, None),
151            None => {
152                let (plan, settings) = self.asked_plan(face, (item, span), asked);
153                (plan, Some(settings))
154            }
155        };
156        let ShapingFace {
157            font, plans, plain, ..
158        } = &mut self.faces[face];
159        // A run of plain glyphs needs neither shaping nor words.
160        if let Some(index) = plans[plan].plain
161            && let Some(inks_out) =
162                plain[index].append((font, item.typeface), text, item.range.clone(), glyphs)
163        {
164            if inks_out {
165                self.ink_reach = InkReach::Glyphs;
166            }
167            return;
168        }
169        // Made here, the HarfRust plan does not take its stack under the
170        // shaping font's.
171        plans[plan].harfrust(font);
172        let start = glyphs.len();
173        self.shape_with_plan((face, plan), text, (item, span, settings), context, glyphs);
174        let inks = item.typeface.glyph_inks();
175        if glyphs[start..]
176            .iter()
177            .any(|glyph| inks.leaves_band(item.typeface, glyph.id()))
178        {
179            self.ink_reach = InkReach::Glyphs;
180        }
181    }
182
183    // How the ink of the glyphs shaped or noted since the last call
184    // reaches, and the bold and the italic the glyphs ask of faces that
185    // lack them.
186    pub(crate) fn take_ink_reach(&mut self) -> (InkReach, SynthesizedStyle) {
187        (
188            std::mem::take(&mut self.ink_reach),
189            std::mem::take(&mut self.synthesized),
190        )
191    }
192
193    // Notes glyphs taken from a layout, whose ink reaches as `reach` says.
194    pub(crate) fn note_ink_reach(&mut self, reach: InkReach) {
195        self.ink_reach = self.ink_reach.max(reach);
196    }
197
198    // Notes the glyphs of `item` in `span`, shaped or taken from a layout,
199    // and the bold and the italic they ask of the item's face.
200    pub(crate) fn note_synthesis(&mut self, item: &TextItem<'_>, span: &ShapingSpan<'_>) {
201        let synthesis =
202            item.typeface
203                .synthesis(span.font.weight, span.font.style, FontSynthesis::All);
204        self.synthesized.add(synthesis, span.size);
205    }
206
207    // The plan of face `face` for `item` in `span`, which did not take the
208    // face's last plan as `asked`, and the settings it searched with. Out of
209    // line: most items take the plan the item before them took, and need
210    // neither the settings nor the search.
211    #[inline(never)]
212    fn asked_plan(
213        &mut self,
214        face: usize,
215        (item, span): (&TextItem<'_>, &ShapingSpan<'_>),
216        asked: Option<PlanAsk>,
217    ) -> (usize, ShapingSettings) {
218        let settings = shaping_settings(&mut self.features, item, span);
219        let (direction, script, language) = &settings;
220        let plan = self.plan(face, *direction, *script, language.as_ref());
221        self.faces[face].last = asked.map(|asked| (asked, plan));
222        (plan, settings)
223    }
224
225    // Shapes `item` as `shape` does, with plan `plan` of face `face`. Out of
226    // line, so the shaping font stays off the stack of the plain glyphs and
227    // of a new plan, which run at the bottom of a layout.
228    #[inline(never)]
229    fn shape_with_plan(
230        &mut self,
231        (face, plan): (usize, usize),
232        text: &str,
233        (item, span, settings): (&TextItem<'_>, &ShapingSpan<'_>, Option<ShapingSettings>),
234        context: Range<usize>,
235        glyphs: &mut ShapedGlyphs,
236    ) {
237        let Self {
238            faces,
239            buffer,
240            features,
241            words,
242            word_ends,
243            pieces,
244            // `shape` reads the item's glyphs once they are all placed.
245            ink_reach: _,
246            synthesized: _,
247        } = self;
248        let (direction, script, language) =
249            settings.unwrap_or_else(|| shaping_settings(features, item, span));
250        let ShapingFace {
251            font, plans, plain, ..
252        } = &mut faces[face];
253        let font = &*font;
254        let kept = &plans[plan];
255        let harfrust = kept.harfrust(font);
256        let plain = kept.plain.map(|index| &mut plain[index]);
257        let setting = Setting {
258            font: ShaperFont::new(font),
259            face: font,
260            typeface: item.typeface,
261            plan: &harfrust.plan,
262            features,
263            direction,
264            script,
265            language: language.as_ref(),
266        };
267        let Some(split) = &harfrust.split else {
268            shape_range(buffer, &setting, text, item.range.clone(), context, glyphs);
269            return;
270        };
271        WordShaping {
272            buffer,
273            setting,
274            plain,
275            words,
276            key: kept.key,
277            text,
278            context,
279        }
280        .shape_words(item.range.clone(), split, word_ends, pieces, glyphs);
281    }
282
283    // The index in its face of the plan for `direction`, `script`,
284    // `language` and the features just set, made when first asked for.
285    fn plan(
286        &mut self,
287        face: usize,
288        direction: Direction,
289        script: Option<ShapingScript>,
290        language: Option<&Language>,
291    ) -> usize {
292        let features = &self.features;
293        if let Some(plan) = self.faces[face]
294            .plans
295            .iter()
296            .position(|kept| kept.is_for(direction, script, language, features))
297        {
298            return plan;
299        }
300        self.new_plan(face, direction, script, language)
301    }
302
303    // Makes the plan `plan` found none of. It runs once a plan, at the bottom
304    // of a layout, so it and each of its parts stay out of line: the stack
305    // holds one part's frame at a time, which a microcontroller's main task
306    // has room for.
307    #[cold]
308    #[inline(never)]
309    fn new_plan(
310        &mut self,
311        face: usize,
312        direction: Direction,
313        script: Option<ShapingScript>,
314        language: Option<&Language>,
315    ) -> usize {
316        let features = &self.features;
317        let face = &mut self.faces[face];
318        if face.plans.len() == KEPT_PLANS {
319            face.forget_oldest_plan();
320        }
321        let key = face.plan_key(direction, script, language, features);
322        let plain = face.plan_glyphs(direction, script, features);
323        face.push_plan(direction, script, language, features, (plain, key));
324        face.plans.len() - 1
325    }
326
327    // The index of the shaping face for `typeface` at `weight` and `style`,
328    // made when first asked for.
329    fn face(&mut self, typeface: &Typeface, weight: FontWeight, style: FontStyle) -> usize {
330        let positions = typeface.axis_positions(weight, style);
331        let content_hash = typeface.content_hash();
332        if let Some(face) = self
333            .faces
334            .iter()
335            .position(|face| face.content_hash == content_hash && face.positions == positions)
336        {
337            return face;
338        }
339        self.new_face(typeface, positions, content_hash)
340    }
341
342    // Makes the face `face` found none of. Out of line: the instance of a
343    // variable font takes a deeper frame than shaping does.
344    #[cold]
345    #[inline(never)]
346    fn new_face(
347        &mut self,
348        typeface: &Typeface,
349        positions: SmallVec<[([u8; 4], f32); 3]>,
350        content_hash: u64,
351    ) -> usize {
352        if self.faces.len() == KEPT_FACES {
353            self.faces.remove(0);
354        }
355        let font = if positions.is_empty() {
356            typeface.font().clone()
357        } else {
358            typeface
359                .font()
360                .instance_builder()
361                .variations(
362                    positions
363                        .iter()
364                        .map(|(tag, value)| Variation::new(Tag::new(tag), *value)),
365                )
366                .build()
367        };
368        self.faces.push(ShapingFace {
369            content_hash,
370            positions,
371            font,
372            plans: Vec::new(),
373            plain: Vec::new(),
374            last: None,
375        });
376        self.faces.len() - 1
377    }
378}
379
380// The direction, script and language an item shapes in.
381type ShapingSettings = (Direction, Option<ShapingScript>, Option<Language>);
382
383// The settings of `item`, with the features of `span` set in `features`.
384// The layouter spaces letters by the font's `trak` table itself, at each
385// span's size, so shaped words keep to every size. Out of line: only a plan
386// search and HarfRust need them, and an item makes them once.
387#[inline(never)]
388fn shaping_settings(
389    features: &mut Vec<Feature>,
390    item: &TextItem<'_>,
391    span: &ShapingSpan<'_>,
392) -> ShapingSettings {
393    let direction = if item.level.is_rtl() {
394        Direction::RightToLeft
395    } else {
396        Direction::LeftToRight
397    };
398    let script = ShapingScript::from_iso15924_tag(Tag::new(&item.script.tag()));
399    let language = span.language.and_then(|tag| tag.parse::<Language>().ok());
400    features.clear();
401    features.push(Feature::new(Tag::new(b"trak"), 0, ..));
402    features.extend(
403        span.features
404            .iter()
405            .map(|feature| Feature::new(Tag::new(&feature.tag), feature.value, ..)),
406    );
407    (direction, script, language)
408}
409
410impl ShapingFace {
411    // The index of `plain` among the face's plain glyphs: of the ones equal
412    // to it, or of itself, kept.
413    fn keep_plain(&mut self, plain: PlainGlyphs) -> usize {
414        if let Some(index) = self.plain.iter().position(|kept| kept.reads_as(&plain)) {
415            return index;
416        }
417        self.plain.reserve_exact(1);
418        self.plain.push(plain);
419        self.plain.len() - 1
420    }
421
422    // The number that keys the words of the plan for `direction`, `script`,
423    // `language` and `features`.
424    #[inline(never)]
425    fn plan_key(
426        &self,
427        direction: Direction,
428        script: Option<ShapingScript>,
429        language: Option<&Language>,
430        features: &[Feature],
431    ) -> u64 {
432        let mut key = HASH_STATE.build_hasher();
433        self.content_hash.hash(&mut key);
434        for (tag, value) in &self.positions {
435            tag.hash(&mut key);
436            value.to_bits().hash(&mut key);
437        }
438        script
439            .map(|script| script.tag().to_be_bytes())
440            .hash(&mut key);
441        (direction == Direction::RightToLeft).hash(&mut key);
442        language.map(Language::as_str).hash(&mut key);
443        for feature in features {
444            (
445                feature.tag.to_be_bytes(),
446                feature.value,
447                feature.start,
448                feature.end,
449            )
450                .hash(&mut key);
451        }
452        key.finish()
453    }
454
455    // The index of the plain glyphs of a plan for `direction`, `script` and
456    // `features`.
457    #[inline(never)]
458    fn plan_glyphs(
459        &mut self,
460        direction: Direction,
461        script: Option<ShapingScript>,
462        features: &[Feature],
463    ) -> Option<usize> {
464        let plain = layout_tables(&self.font)
465            .and_then(|tables| PlainGlyphs::of(&tables, script, direction, features));
466        plain.map(|plain| self.keep_plain(plain))
467    }
468
469    // Keeps a new plan with its plain glyphs and key.
470    #[inline(never)]
471    fn push_plan(
472        &mut self,
473        direction: Direction,
474        script: Option<ShapingScript>,
475        language: Option<&Language>,
476        features: &[Feature],
477        (plain, key): (Option<usize>, u64),
478    ) {
479        // A face keeps a plan or two, each a few hundred bytes: room for
480        // more goes unused.
481        self.plans.reserve_exact(1);
482        self.plans.push(KeptPlan {
483            direction,
484            script,
485            language: language.cloned(),
486            features: SmallVec::from_slice(features),
487            harfrust: OnceCell::new(),
488            plain,
489            key,
490        });
491    }
492
493    // Drops the oldest plan, and its plain glyphs when no other plan has
494    // them.
495    #[inline(never)]
496    fn forget_oldest_plan(&mut self) {
497        let Some(index) = self.plans.remove(0).plain else {
498            return;
499        };
500        if self.plans.iter().any(|kept| kept.plain == Some(index)) {
501            return;
502        }
503        self.plain.remove(index);
504        for other in self.plans.iter_mut().filter_map(|kept| kept.plain.as_mut()) {
505            if *other > index {
506                *other -= 1;
507            }
508        }
509    }
510}
511
512impl KeptPlan {
513    // Whether the plan shapes runs for `direction`, `script`, `language` and
514    // `features`. HarfRust matches its plans by these and by the font's
515    // variations, which a face fixes.
516    fn is_for(
517        &self,
518        direction: Direction,
519        script: Option<ShapingScript>,
520        language: Option<&Language>,
521        features: &[Feature],
522    ) -> bool {
523        self.direction == direction
524            && self.script == script
525            && self.language.as_ref() == language
526            && self.features.as_slice() == features
527    }
528
529    // The HarfRust plan of `font` for the runs of this plan, made on first
530    // use. Out of line, so the plan in the making stays off the frame of
531    // the shaping that asks for it.
532    #[inline(never)]
533    fn harfrust(&self, font: &Font) -> &HarfRustPlan {
534        self.harfrust.get_or_init(|| HarfRustPlan::new(font, self))
535    }
536}
537
538impl HarfRustPlan {
539    #[cold]
540    #[inline(never)]
541    fn new(font: &Font, kept: &KeptPlan) -> Self {
542        Self {
543            plan: ShapePlan::new(
544                font,
545                kept.direction,
546                kept.script,
547                kept.language.as_ref(),
548                &kept.features,
549            ),
550            split: face_word_split(font, kept.script),
551        }
552    }
553}
554
555// One item's words being shaped: the buffer, the setting, the plan's plain
556// glyphs, the kept words and their key, and the text with its context.
557struct WordShaping<'a> {
558    buffer: &'a mut Buffer,
559    setting: Setting<'a>,
560    plain: Option<&'a mut PlainGlyphs>,
561    words: &'a mut WordCache,
562    key: u64,
563    text: &'a str,
564    context: Range<usize>,
565}
566
567impl WordShaping<'_> {
568    // Shapes `range` word by word, cut where `split` says, and appends the
569    // glyphs in drawing order. A word whose glyph meets the space at a cut
570    // and is one of the split's exceptions, which a lookup reads with that
571    // space, shapes together with its neighbor there.
572    fn shape_words(
573        mut self,
574        range: Range<usize>,
575        split: &WordSplit,
576        word_ends: &mut Vec<usize>,
577        pieces: &mut Vec<(Range<usize>, Range<usize>)>,
578        glyphs: &mut ShapedGlyphs,
579    ) {
580        let after_spaces = split.cut == SpaceCut::After;
581        word_ends.clear();
582        word_ends.extend(word_cuts(self.text, range.clone(), split.cut));
583        word_ends.push(range.end);
584        let rtl = self.setting.direction == Direction::RightToLeft;
585        let item_start = glyphs.len();
586        pieces.clear();
587        let mut start = range.start;
588        for &end in word_ends.iter() {
589            let mut word = start..end;
590            start = end;
591            self.word(word.clone(), glyphs);
592            // The glyph of a word that meets the space between it and its
593            // neighbor: the last in text order, or the first.
594            let meets = |glyphs: &[ShapedGlyph], last: bool| {
595                let glyph = if last == rtl {
596                    glyphs.first()
597                } else {
598                    glyphs.last()
599                };
600                glyph.is_some_and(|glyph| split.exceptions.contains(glyph.id().0))
601            };
602            let joins = pieces.last().is_some_and(|(_, previous)| {
603                if after_spaces {
604                    meets(&glyphs[previous.end..], false)
605                } else {
606                    meets(&glyphs[previous.clone()], true)
607                }
608            });
609            if joins && let Some((text_range, glyph_range)) = pieces.pop() {
610                glyphs.truncate(glyph_range.start);
611                word = text_range.start..word.end;
612                self.word(word.clone(), glyphs);
613            }
614            let first = pieces
615                .last()
616                .map_or(item_start, |(_, glyph_range)| glyph_range.end);
617            pieces.push((word, first..glyphs.len()));
618        }
619        if rtl {
620            // Words were shaped in text order; drawn right to left, the last
621            // comes first.
622            let end = glyphs.len();
623            glyphs.reverse(item_start..end);
624            for (_, piece) in pieces.iter() {
625                glyphs.reverse(item_start + end - piece.end..item_start + end - piece.start);
626            }
627        }
628    }
629    // Appends the glyphs of `word`: its plain glyphs, kept ones, or shaped
630    // and kept when the word shapes the same wherever it stands.
631    fn word(&mut self, word: Range<usize>, glyphs: &mut ShapedGlyphs) {
632        let Some(word_text) = self.text.get(word.clone()) else {
633            return;
634        };
635        let faces = (self.setting.face, self.setting.typeface);
636        if self.plain.as_deref_mut().is_some_and(|plain| {
637            plain
638                .append(faces, self.text, word.clone(), glyphs)
639                .is_some()
640        }) {
641            return;
642        }
643        let keeps = stands_alone(self.text, &word, &self.context);
644        if keeps && let Some((cached, offsets)) = self.words.get(self.key, word_text) {
645            glyphs.extend_from_word(cached, offsets, offset(word.start));
646            return;
647        }
648        let first = glyphs.len();
649        shape_range(
650            self.buffer,
651            &self.setting,
652            self.text,
653            word.clone(),
654            self.context.clone(),
655            glyphs,
656        );
657        if keeps && self.words.admits(self.key, word_text) {
658            let shaped: SmallVec<[ShapedGlyph; 16]> = glyphs[first..]
659                .iter()
660                .map(|glyph| glyph.in_cluster(glyph.cluster - offset(word.start)))
661                .collect();
662            let offsets = glyphs.offsets().get(first..).unwrap_or_default();
663            self.words.put(self.key, word_text, &shaped, offsets);
664        }
665    }
666}
667
668// Shapes `range` of `text` with `setting`, the text of `context` around it as
669// context, and appends the glyphs.
670fn shape_range(
671    buffer: &mut Buffer,
672    setting: &Setting<'_>,
673    text: &str,
674    range: Range<usize>,
675    context: Range<usize>,
676    glyphs: &mut ShapedGlyphs,
677) {
678    let (Ok(start), Some(before), Some(content), Some(after)) = (
679        u32::try_from(range.start),
680        text.get(context.start..range.start),
681        text.get(range.clone()),
682        text.get(range.end..context.end),
683    ) else {
684        return;
685    };
686    buffer.clear();
687    let mut flags = BufferFlags::empty();
688    flags.set(BufferFlags::BEGINNING_OF_TEXT, range.start == context.start);
689    flags.set(BufferFlags::END_OF_TEXT, range.end == context.end);
690    buffer.set_flags(flags);
691    buffer.set_pre_context(before);
692    buffer.set_post_context(after);
693    buffer.push_str(content);
694    buffer.set_direction(setting.direction);
695    buffer.set_script(setting.script);
696    buffer.set_language(setting.language.cloned());
697    let shaped = harfrust::shape(
698        &setting.font,
699        buffer,
700        ShapeOptions::new()
701            .plan(Some(setting.plan))
702            .features(setting.features),
703    );
704    if shaped.is_ok() {
705        glyphs.reserve(buffer.len());
706        for (info, position) in buffer.glyph_infos().iter().zip(buffer.glyph_positions()) {
707            glyphs.push(
708                ShapedGlyph::new(
709                    GlyphId(info.glyph_id),
710                    start + info.cluster,
711                    position.x_advance,
712                    if info.unsafe_to_break() {
713                        BreakBefore::Unsafe
714                    } else {
715                        BreakBefore::Safe
716                    },
717                ),
718                GlyphOffset {
719                    x: position.x_offset,
720                    y: position.y_offset,
721                },
722            );
723        }
724    }
725}
726
727// The offsets inside `range` of `text` where `cut` cuts it into words:
728// before each space, or after each run of spaces.
729fn word_cuts(text: &str, range: Range<usize>, cut: SpaceCut) -> impl Iterator<Item = usize> + '_ {
730    let bytes = text.as_bytes();
731    (range.start + 1..range.end).filter(move |&at| match cut {
732        SpaceCut::Before => bytes[at] == b' ',
733        SpaceCut::After => bytes[at - 1] == b' ' && bytes[at] != b' ',
734    })
735}
736
737// Whether `word` shapes the same wherever it stands: each of its edges is the
738// context's edge or meets a space, so no joining or contextual form reaches
739// across it.
740fn stands_alone(text: &str, word: &Range<usize>, context: &Range<usize>) -> bool {
741    let bytes = text.as_bytes();
742    let space = |at: usize| bytes.get(at) == Some(&b' ');
743    (word.start == context.start || space(word.start) || space(word.start - 1))
744        && (word.end == context.end || space(word.end) || space(word.end - 1))
745}
746
747fn offset(at: usize) -> u32 {
748    u32::try_from(at).unwrap_or(u32::MAX)
749}
750
751// The layout tables of `font`.
752fn layout_tables(font: &Font) -> Option<FontRef<'_>> {
753    FontRef::from_index(font_bytes(font), 0).ok()
754}
755
756// The bytes of `font`, empty when it is not read from bytes.
757pub(crate) fn font_bytes(font: &Font) -> &[u8] {
758    match font.source() {
759        Source::Blob(bytes) => bytes.as_ref(),
760        _ => &[],
761    }
762}
763
764// The word split of `font` for runs in `script`; `None` when they shape
765// whole.
766#[inline(never)]
767fn face_word_split(font: &Font, script: Option<ShapingScript>) -> Option<WordSplit> {
768    let tables = layout_tables(font)?;
769    let space = font.charmap().map_unicode(' ')?;
770    let tags = script.map(ShapingScript::tags);
771    word_split(
772        &tables,
773        space,
774        tags.as_ref().map_or(&[], |tags| tags.as_slice()),
775    )
776}