swash/shape/
mod.rs

1/*!
2Mapping complex text to a sequence of positioned glyphs.
3
4Shaping is the process of converting a sequence of
5[character clusters](CharCluster) into a sequence of
6[glyph clusters](GlyphCluster) with respect to the rules of a particular
7writing system and the typographic features available in a font. The shaper
8operates on one _item_ at a time where an item is a run of text with
9a single script, language, direction, font, font size, and set of variation/feature
10settings. The process of producing these runs is called _itemization_
11and is out of scope for this crate.
12
13# Building the shaper
14
15All shaping in this crate takes place within the purview of a
16[`ShapeContext`]. This opaque struct manages internal LRU caches and scratch
17buffers that are necessary for the shaping process. Generally, you'll
18want to keep an instance that persists for more than one layout pass as
19this amortizes the cost of allocations, reduces contention for the global
20heap and increases the hit rate for the internal acceleration structures. If
21you're doing multithreaded layout, you should keep a context per thread.
22
23The only method available on the context is [`builder`](ShapeContext::builder)
24which takes a type that can be converted into a [`FontRef`] as an argument
25and produces a [`ShaperBuilder`] that provides options for configuring and
26building a [`Shaper`].
27
28Here, we'll create a context and build a shaper for Arabic text at 16px:
29```
30# use swash::{FontRef, CacheKey, shape::*, text::Script};
31# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
32// let font = ...;
33let mut context = ShapeContext::new();
34let mut shaper = context.builder(font)
35    .script(Script::Arabic)
36    .direction(Direction::RightToLeft)
37    .size(16.)
38    .build();
39```
40
41You can specify feature settings by calling the [`features`](ShaperBuilder::features)
42method with an iterator that yields a sequence of values that are convertible
43to [`Setting<u16>`]. Tuples of (&str, u16) will work in a pinch. For example,
44you can enable discretionary ligatures like this:
45```
46# use swash::{FontRef, CacheKey, shape::*, text::Script, tag_from_bytes};
47# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
48// let font = ...;
49let mut context = ShapeContext::new();
50let mut shaper = context.builder(font)
51    .script(Script::Latin)
52    .size(14.)
53    .features(&[("dlig", 1)])
54    .build();
55```
56
57A value of `0` will disable a feature while a non-zero value will enable it.
58Some features use non-zero values as an argument. The stylistic alternates
59feature, for example, often offers a collection of choices per glyph. The argument
60is used as an index to select among them. If a requested feature is not present
61in a font, the setting is ignored.
62
63Font variation settings are specified in a similar manner with the
64[`variations`](ShaperBuilder::variations) method but take an `f32`
65to define the value within the variation space for the requested axis:
66```
67# use swash::{FontRef, CacheKey, shape::*, text::Script, tag_from_bytes};
68# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
69// let font = ...;
70let mut context = ShapeContext::new();
71let mut shaper = context.builder(font)
72    .script(Script::Latin)
73    .size(14.)
74    .variations(&[("wght", 520.5)])
75    .build();
76```
77
78See [`ShaperBuilder`] for available options and default values.
79
80# Feeding the shaper
81
82Once we have a properly configured shaper, we need to feed it some
83clusters. The simplest approach is to call the [`add_str`](Shaper::add_str)
84method with a string:
85```
86# use swash::{FontRef, CacheKey, shape::*, text::Script, tag_from_bytes};
87# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
88# let mut context = ShapeContext::new();
89# let mut shaper = context.builder(font).build();
90shaper.add_str("a quick brown fox?");
91```
92
93You can call [`add_str`](Shaper::add_str) multiple times to add a sequence
94of text fragments to the shaper.
95
96This simple approach is certainly reasonable when dealing with text consisting
97of a single run on one line with a font that is known to contain all the
98necessary glyphs. A small text label in a UI is a good example.
99
100For more complex scenarios, the shaper can be fed a single cluster at a time.
101This method allows you to provide:
102- accurate source ranges per character even if your runs
103  and items span multiple non-contiguous fragments
104- user data per character (a single `u32`) that can be used, for
105  example, to associate each resulting glyph with a style span
106- boundary analysis per character, carrying word boundaries and
107  line break opportunities through the shaper.
108
109This also provides a junction point for inserting a font fallback
110mechanism.
111
112All of this is served by the functionality in the
113[`text::cluster`](crate::text::cluster) module.
114
115Let's see a somewhat contrived example that demonstrates the process:
116```
117use swash::text::cluster::{CharCluster, CharInfo, Parser, Token};
118# use swash::{FontRef, CacheKey, shape::*, text::Script, tag_from_bytes};
119# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
120# let mut context = ShapeContext::new();
121let mut shaper = context.builder(font)
122    .script(Script::Latin)
123    .build();
124// We'll need the character map for our font
125let charmap = font.charmap();
126// And some storage for the cluster we're working with
127let mut cluster = CharCluster::new();
128// Now we build a cluster parser which takes a script and
129// an iterator that yields a Token per character
130let mut parser = Parser::new(
131    Script::Latin,
132    "a quick brown fox?".char_indices().map(|(i, ch)| Token {
133        // The character
134        ch,
135        // Offset of the character in code units
136        offset: i as u32,
137        // Length of the character in code units
138        len: ch.len_utf8() as u8,
139        // Character information
140        info: ch.into(),
141        // Pass through user data
142        data: 0,
143    })
144);
145// Loop over all of the clusters
146while parser.next(&mut cluster) {
147    // Map all of the characters in the cluster
148    // to nominal glyph identifiers
149    cluster.map(|ch| charmap.map(ch));
150    // Add the cluster to the shaper
151    shaper.add_cluster(&cluster);
152}
153```
154
155Phew! That's quite a lot of work. It also happens to be exactly what
156[`add_str`](Shaper::add_str) does internally.
157
158So why bother? As mentioned earlier, this method allows you to customize
159the per-character data that passes through the shaper. Is your source text in
160UTF-16 instead of UTF-8? No problem. Set the [`offset`](Token::offset) and
161[`len`](Token::len) fields of your [`Token`]s to appropriate values. Are you shaping
162across style spans? Set the [`data`](Token::data) field to the index of your span so
163it can be recovered. Have you used the
164[`Analyze`](crate::text::Analyze) iterator to generate
165[`CharInfo`](crate::text::cluster::CharInfo)s containing boundary analysis? This
166is where you apply them to the [`info`](Token::info) fields of your [`Token`]s.
167
168That last one deserves a quick example, showing how you might build a cluster
169parser with boundary analysis:
170```
171use swash::text::{analyze, Script};
172use swash::text::cluster::{CharInfo, Parser, Token};
173let text = "a quick brown fox?";
174let mut parser = Parser::new(
175    Script::Latin,
176    text.char_indices()
177        // Call analyze passing the same text and zip
178        // the results
179        .zip(analyze(text.chars()))
180        // Analyze yields the tuple (Properties, Boundary)
181        .map(|((i, ch), (props, boundary))| Token {
182            ch,
183            offset: i as u32,
184            len: ch.len_utf8() as u8,
185            // Create character information from properties and boundary
186            info: CharInfo::new(props, boundary),
187            data: 0,
188        }),
189);
190```
191That leaves us with font fallback. This crate does not provide the infrastructure
192for such, but a small example can demonstrate the idea. The key is in
193the return value of the [`CharCluster::map`] method which describes the
194[`Status`](crate::text::cluster::Status) of the mapping operation. This function
195will return the index of the best matching font:
196```
197use swash::FontRef;
198use swash::text::cluster::{CharCluster, Status};
199
200fn select_font<'a>(fonts: &[FontRef<'a>], cluster: &mut CharCluster) -> Option<usize> {
201    let mut best = None;
202    for (i, font) in fonts.iter().enumerate() {
203        let charmap = font.charmap();
204        match cluster.map(|ch| charmap.map(ch)) {
205            // This font provided a glyph for every character
206            Status::Complete => return Some(i),
207            // This font provided the most complete mapping so far
208            Status::Keep => best = Some(i),
209            // A previous mapping was more complete
210            Status::Discard => {}
211        }
212    }
213    best
214}
215```
216
217Note that [`CharCluster`] maintains internal composed and decomposed sequences
218of the characters in the cluster so that it can select the best form for each
219candidate font.
220
221Since this process is done during shaping, upon return we compare the selected
222font with our current font and if they're different, we complete shaping for the
223clusters submitted so far and continue the process by building a new shaper with
224the selected font. By doing manual cluster parsing and nominal glyph mapping
225_outside_ the shaper, we can implement per-cluster font fallback without the costly
226technique of heuristically shaping runs.
227
228# Collecting the prize
229
230Finish up shaping by calling [`Shaper::shape_with`] with a closure that will be
231invoked with each resulting [`GlyphCluster`]. This structure contains borrowed data
232and thus cannot be stored directly. The data you extract from each cluster and the
233method in which you store it will depend entirely on the design of your text layout
234system.
235
236Please note that, unlike HarfBuzz, this shaper does _not_ reverse runs that are in
237right-to-left order. The reasoning is that, for correctness, line breaking must be
238done in logical order and reversing runs should occur during bidi reordering.
239
240Also pertinent to right-to-left runs: you'll need to ensure that you reverse
241_clusters_ and not _glyphs_. Intra-cluster glyphs must remain in logical order
242for proper mark placement.
243*/
244
245pub mod cluster;
246
247#[doc(hidden)]
248pub mod partition;
249
250mod aat;
251mod at;
252mod buffer;
253mod cache;
254mod engine;
255mod feature;
256
257use cluster::*;
258
259use super::{
260    cache::FontCache, charmap::Charmap, internal, metrics::Metrics, setting::Setting, FontRef,
261    NormalizedCoord,
262};
263use crate::text::{
264    cluster::{CharCluster, Parser, ShapeClass, Token},
265    Language, Script,
266};
267use alloc::vec::Vec;
268use at::{FeatureMask, FeatureStore, FeatureStoreBuilder};
269use buffer::*;
270use cache::{FeatureCache, FontEntry};
271use core::borrow::Borrow;
272use engine::{Engine, EngineMode};
273
274const DEFAULT_SIZE: usize = 16;
275
276/// Text direction.
277#[derive(Copy, Clone, PartialEq, Eq, Debug)]
278pub enum Direction {
279    LeftToRight,
280    RightToLeft,
281}
282
283/// Context that manages caches and transient buffers for shaping.
284///
285/// See the module level [documentation](index.html#building-the-shaper) for detail.
286pub struct ShapeContext {
287    font_cache: FontCache<FontEntry>,
288    feature_cache: FeatureCache,
289    coords: Vec<i16>,
290    state: State,
291}
292
293impl ShapeContext {
294    /// Creates a new shaping context.
295    pub fn new() -> Self {
296        Self::with_max_entries(DEFAULT_SIZE)
297    }
298
299    /// Creates a new shaping context with the specified maximum number of
300    /// cache entries.
301    pub fn with_max_entries(max_entries: usize) -> Self {
302        let max_entries = max_entries.clamp(1, 64);
303        Self {
304            font_cache: FontCache::new(max_entries),
305            feature_cache: FeatureCache::new(max_entries),
306            coords: Vec::new(),
307            state: State::new(),
308        }
309    }
310
311    /// Creates a new builder for constructing a shaper with this context
312    /// and the specified font.
313    pub fn builder<'a>(&'a mut self, font: impl Into<FontRef<'a>>) -> ShaperBuilder<'a> {
314        ShaperBuilder::new(self, font)
315    }
316
317    /// Creates a new builder for constructing a shaper with this context
318    /// and the specified font.
319    pub fn builder_with_id<'a>(
320        &'a mut self,
321        font: impl Into<FontRef<'a>>,
322        id: [u64; 2],
323    ) -> ShaperBuilder<'a> {
324        ShaperBuilder::new_with_id(self, font, id)
325    }
326}
327
328impl Default for ShapeContext {
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334struct State {
335    buffer: Buffer,
336    store_builder: FeatureStoreBuilder,
337    order: Vec<usize>,
338    glyphs: Vec<GlyphData>,
339    disable_kern: bool,
340    features: Vec<(u32, u16)>,
341    selectors: Vec<(u16, u16)>,
342}
343
344impl State {
345    pub fn new() -> Self {
346        Self {
347            buffer: Buffer::new(),
348            store_builder: FeatureStoreBuilder::default(),
349            order: Vec::new(),
350            glyphs: Vec::new(),
351            disable_kern: false,
352            features: Vec::new(),
353            selectors: Vec::new(),
354        }
355    }
356
357    pub fn reset(&mut self) {
358        self.buffer.clear();
359        self.features.clear();
360        self.disable_kern = false;
361    }
362}
363
364/// Builder for configuring a shaper.
365///
366/// See the module level [documentation](index.html#building-the-shaper) for more detail.
367pub struct ShaperBuilder<'a> {
368    state: &'a mut State,
369    feature_cache: &'a mut FeatureCache,
370    font: FontRef<'a>,
371    font_id: [u64; 2],
372    font_entry: &'a FontEntry,
373    coords: &'a mut Vec<i16>,
374    charmap: Charmap<'a>,
375    dotted_circle: Option<u16>,
376    retain_ignorables: bool,
377    size: f32,
378    script: Script,
379    lang: Option<Language>,
380    dir: Direction,
381}
382
383impl<'a> ShaperBuilder<'a> {
384    /// Creates a new builder for configuring a shaper with the specified
385    /// context and font.
386    fn new(context: &'a mut ShapeContext, font: impl Into<FontRef<'a>>) -> Self {
387        let font = font.into();
388        let id = [font.key.value(), u64::MAX];
389        Self::new_with_id(context, font, id)
390    }
391
392    /// Creates a new builder for configuring a shaper with the specified
393    /// context and font.
394    fn new_with_id(
395        context: &'a mut ShapeContext,
396        font: impl Into<FontRef<'a>>,
397        id: [u64; 2],
398    ) -> Self {
399        let font = font.into();
400        let (font_id, font_entry) = context.font_cache.get(&font, Some(id), FontEntry::new);
401        context.state.reset();
402        context.coords.clear();
403        Self {
404            state: &mut context.state,
405            feature_cache: &mut context.feature_cache,
406            font,
407            font_id,
408            font_entry,
409            coords: &mut context.coords,
410            charmap: font_entry.charmap.materialize(&font),
411            dotted_circle: None,
412            retain_ignorables: false,
413            size: 0.,
414            script: Script::Latin,
415            lang: None,
416            dir: Direction::LeftToRight,
417        }
418    }
419
420    /// Specifies the script. The default value is [`Script::Latin`].
421    pub fn script(mut self, script: Script) -> Self {
422        self.script = script;
423        self
424    }
425
426    /// Specifies the language. The default value is `None`.
427    pub fn language(mut self, language: Option<Language>) -> Self {
428        self.lang = language;
429        self
430    }
431
432    /// Specifies the text direction. The default value is [`Direction::LeftToRight`].
433    pub fn direction(mut self, direction: Direction) -> Self {
434        self.dir = direction;
435        self
436    }
437
438    /// Specifies the font size in pixels per em. The default value is `0`
439    /// which will produce glyphs with offsets and advances in font units.
440    pub fn size(mut self, ppem: f32) -> Self {
441        self.size = ppem.max(0.);
442        self
443    }
444
445    /// Adds feature settings to the shaper.
446    pub fn features<I>(self, settings: I) -> Self
447    where
448        I: IntoIterator,
449        I::Item: Into<Setting<u16>>,
450    {
451        for feature in settings {
452            let feature = feature.into();
453            if feature.tag == feature::KERN {
454                self.state.disable_kern = feature.value == 0;
455            }
456            self.state.features.push((feature.tag, feature.value));
457        }
458        self
459    }
460
461    /// Adds variation settings to the shaper.
462    pub fn variations<I>(self, settings: I) -> Self
463    where
464        I: IntoIterator,
465        I::Item: Into<Setting<f32>>,
466    {
467        if self.font_entry.coord_count != 0 {
468            let vars = self.font.variations();
469            self.coords.resize(vars.len(), 0);
470            for setting in settings {
471                let setting = setting.into();
472                for var in vars {
473                    if var.tag() == setting.tag {
474                        let value = var.normalize(setting.value);
475                        if let Some(c) = self.coords.get_mut(var.index()) {
476                            *c = value;
477                        }
478                    }
479                }
480            }
481        }
482        self
483    }
484
485    /// Specifies the variation settings in terms of normalized coordinates.
486    pub fn normalized_coords<I>(self, coords: I) -> Self
487    where
488        I: IntoIterator,
489        I::Item: Borrow<NormalizedCoord>,
490    {
491        self.coords.clear();
492        self.coords.extend(coords.into_iter().map(|c| *c.borrow()));
493        self
494    }
495
496    /// Specifies whether to insert dotted circles for broken clusters. The
497    /// default value is `false`.
498    pub fn insert_dotted_circles(mut self, yes: bool) -> Self {
499        if yes {
500            let gid = self.charmap.map('\u{25cc}');
501            if gid != 0 {
502                self.dotted_circle = Some(gid);
503            }
504        } else {
505            self.dotted_circle = None;
506        }
507        self
508    }
509
510    /// Specifies whether characters defined as default ignorable should be
511    /// retained by the shaper. The default is `false`.
512    pub fn retain_ignorables(mut self, yes: bool) -> Self {
513        self.retain_ignorables = yes;
514        self
515    }
516
517    /// Builds a shaper for the current configuration.
518    pub fn build(self) -> Shaper<'a> {
519        let engine = Engine::new(
520            &self.font_entry.metadata,
521            self.font.data,
522            &self.coords[..],
523            self.script,
524            self.lang,
525        );
526        self.state.buffer.dotted_circle = self.dotted_circle;
527        let rtl = self.dir == Direction::RightToLeft;
528        self.state.buffer.is_rtl = rtl;
529        let (store, sub_mask, pos_mask) = if engine.use_ot {
530            use cache::FeatureCacheEntry;
531            let store = match self.feature_cache.entry(
532                self.font_id,
533                &self.coords[..],
534                engine.has_feature_vars(),
535                engine.tags(),
536            ) {
537                FeatureCacheEntry::Present(store) => store,
538                FeatureCacheEntry::New(store) => {
539                    engine.collect_features(&mut self.state.store_builder, store);
540                    store
541                }
542            };
543            let buf = &mut self.state.buffer;
544            let (sub, pos) = store.custom_masks(
545                &self.state.features[..],
546                &mut buf.sub_args,
547                &mut buf.pos_args,
548                self.dir,
549            );
550            (Some(store as _), sub, pos)
551        } else {
552            (None, FeatureMask::default(), FeatureMask::default())
553        };
554        Shaper {
555            state: self.state,
556            font: self.font,
557            font_entry: self.font_entry,
558            charmap: self.charmap,
559            retain_ignorables: self.retain_ignorables,
560            size: self.size,
561            script: self.script,
562            joined: engine.use_ot && self.script.is_joined(),
563            dir: self.dir,
564            engine,
565            store,
566            sub_mask,
567            pos_mask,
568        }
569    }
570}
571
572/// Maps character clusters to positioned glyph clusters according to
573/// typographic rules and features.
574///
575/// See the module level [documentation](index.html#feeding-the-shaper) for detail.
576pub struct Shaper<'a> {
577    state: &'a mut State,
578    font: FontRef<'a>,
579    font_entry: &'a FontEntry,
580    charmap: Charmap<'a>,
581    retain_ignorables: bool,
582    size: f32,
583    script: Script,
584    joined: bool,
585    dir: Direction,
586    engine: Engine<'a>,
587    store: Option<&'a FeatureStore>,
588    sub_mask: FeatureMask,
589    pos_mask: FeatureMask,
590}
591
592impl<'a> Shaper<'a> {
593    /// Adds a character cluster to the shaper.
594    pub fn add_cluster(&mut self, cluster: &CharCluster) {
595        let buf = &mut self.state.buffer;
596        match self.engine.mode {
597            EngineMode::Simple => {
598                buf.push(cluster);
599            }
600            EngineMode::Myanmar => {
601                let e = &mut self.engine;
602                let s = self.store.unwrap();
603                let chars = cluster.mapped_chars();
604                reorder_myanmar(chars, &mut self.state.order);
605                let range = buf.push_order(cluster, &self.state.order);
606                e.set_classes(buf, Some(range.clone()));
607                let start = range.start;
608                e.gsub(s, s.groups.default, buf, Some(range));
609                let end = buf.len();
610                e.gsub(s, s.groups.reph, buf, Some(start..end));
611                let end = buf.len();
612                e.gsub(s, s.groups.pref, buf, Some(start..end));
613                let end = buf.len();
614                e.gsub(s, s.groups.stage1, buf, Some(start..end));
615                let end = buf.len();
616                e.gsub(s, s.groups.stage2, buf, Some(start..end));
617            }
618            EngineMode::Complex => {
619                let e = &mut self.engine;
620                let s = self.store.unwrap();
621                let range = buf.push(cluster);
622                e.set_classes(buf, Some(range.clone()));
623                let start = range.start;
624                // Default group
625                e.gsub(s, s.groups.default, buf, Some(range.clone()));
626                for g in &mut buf.glyphs[range] {
627                    if g.char_class == ShapeClass::Halant && g.flags & SUBSTITUTED != 0 {
628                        // Don't prevent reordering across a virama that has been substituted
629                        g.char_class = ShapeClass::Other;
630                    }
631                }
632                // Reph identification
633                let len = 3.min(buf.glyphs.len() - start);
634                let end = start + len;
635                buf.clear_flags(buffer::SUBSTITUTED, Some(start..end));
636                e.gsub(s, s.groups.reph, buf, Some(start..end));
637                for g in &mut buf.glyphs[start..end] {
638                    if g.flags & buffer::SUBSTITUTED != 0 {
639                        g.char_class = ShapeClass::Reph;
640                        break;
641                    }
642                }
643                // Pref identification
644                let end = buf.len();
645                buf.clear_flags(buffer::SUBSTITUTED, Some(start..end));
646                e.gsub(s, s.groups.pref, buf, Some(start..end));
647                for g in &mut buf.glyphs[start..end] {
648                    if g.flags & buffer::SUBSTITUTED != 0 {
649                        g.char_class = ShapeClass::Pref;
650                        break;
651                    }
652                }
653                // Orthographic group
654                let end = buf.len();
655                e.gsub(s, s.groups.stage1, buf, Some(start..end));
656                // Reordering
657                let len = (buf.len() - start).min(64);
658                let end = start + len;
659                reorder_complex(
660                    &mut buf.glyphs[start..end],
661                    &mut self.state.glyphs,
662                    &mut self.state.order,
663                );
664            }
665        }
666    }
667
668    /// Adds a string to the shaper.
669    pub fn add_str(&mut self, s: &str) {
670        use crate::text::Codepoint;
671        let mut cluster = CharCluster::new();
672        let mut parser = Parser::new(
673            self.script,
674            s.char_indices().map(|(i, ch)| Token {
675                ch,
676                offset: i as u32,
677                len: ch.len_utf8() as u8,
678                info: ch.properties().into(),
679                data: 0,
680            }),
681        );
682        let charmap = self.charmap;
683        while parser.next(&mut cluster) {
684            cluster.map(|ch| charmap.map(ch));
685            self.add_cluster(&cluster);
686        }
687    }
688
689    /// Returns the current normalized variation coordinates in use by the
690    /// shaper.
691    pub fn normalized_coords(&self) -> &[NormalizedCoord] {
692        self.engine.coords
693    }
694
695    /// Returns the current font metrics in use by the shaper.
696    pub fn metrics(&self) -> Metrics {
697        let scale = if self.size != 0. { self.size } else { 1. };
698        self.font_entry
699            .metrics
700            .materialize_metrics(&self.font, self.engine.coords)
701            .scale(scale)
702    }
703
704    /// Shapes the text and invokes the specified closure with each
705    /// resulting glyph cluster.
706    pub fn shape_with(mut self, mut f: impl FnMut(&GlyphCluster)) {
707        self.finish();
708        let buf = &mut self.state.buffer;
709        buf.shaped_glyphs.clear();
710        let mut sentinel = (
711            buffer::GlyphData::default(),
712            buffer::PositionData::default(),
713        );
714        sentinel.0.cluster = buf.ranges.len() as u32;
715        let mut last_cluster = 0;
716        for (g, p) in buf
717            .glyphs
718            .iter()
719            .zip(&buf.positions)
720            .chain(core::iter::once((&sentinel.0, &sentinel.1)))
721        {
722            if g.cluster != last_cluster {
723                // Simple and common case: no ligatures and no empty clusters.
724                if last_cluster > g.cluster || g.cluster - last_cluster == 1 {
725                    let index = last_cluster as usize;
726                    let info = &buf.infos[index];
727                    let cluster = GlyphCluster {
728                        source: buf.ranges[index],
729                        info: info.0,
730                        glyphs: &buf.shaped_glyphs,
731                        components: &[],
732                        data: info.2,
733                    };
734                    f(&cluster);
735                    buf.shaped_glyphs.clear();
736                } else {
737                    // Collect the range for the non-empty cluster.
738                    let end = g.cluster as usize;
739                    let start = last_cluster as usize;
740                    let mut group_end = start + 1;
741                    while group_end < end && buf.infos[group_end].1 {
742                        group_end += 1;
743                    }
744                    if !buf.shaped_glyphs.is_empty() {
745                        // We have some glyphs. Emit the cluster.
746                        let mut source = buf.ranges[start];
747                        source.end = buf.ranges[group_end - 1].end;
748                        // If the range spans more than one cluster, we have a ligature.
749                        let components = if group_end > start + 1 {
750                            &buf.ranges[start..group_end]
751                        } else {
752                            &[]
753                        };
754                        let info = &buf.infos[start];
755                        let cluster = GlyphCluster {
756                            source,
757                            info: info.0,
758                            glyphs: &buf.shaped_glyphs,
759                            components,
760                            data: info.2,
761                        };
762                        f(&cluster);
763                        buf.shaped_glyphs.clear();
764                    }
765                    if end > group_end {
766                        // We have a trailing sequence of empty clusters. Emit
767                        // them one by one.
768                        for (info, source) in buf.infos[group_end..end]
769                            .iter()
770                            .zip(&buf.ranges[group_end..end])
771                        {
772                            let cluster = GlyphCluster {
773                                source: *source,
774                                info: info.0,
775                                glyphs: &[],
776                                components: &[],
777                                data: info.2,
778                            };
779                            f(&cluster);
780                        }
781                    }
782                }
783            }
784            last_cluster = g.cluster;
785            if self.retain_ignorables || g.flags & IGNORABLE == 0 {
786                buf.shaped_glyphs.push(Glyph::new(g, p));
787            }
788        }
789    }
790
791    // FIXME: when writing docs, I realized that it's impossible
792    // to use the result of this function correctly with RTL runs
793    // that contain marks.
794
795    // /// Shapes the text and invokes the specified closure with each
796    // /// resulting glyph.
797    // pub fn shape_glyphs_with(mut self, mut f: impl FnMut(&Glyph)) {
798    //     self.finish();
799    //     let buf = &self.state.buffer;
800    //     for (g, p) in buf.glyphs.iter().zip(&buf.positions) {
801    //         if g.flags & IGNORABLE == 0 {
802    //             f(&Glyph::new(g, p))
803    //         }
804    //     }
805    // }
806
807    fn finish(&mut self) {
808        use engine::{PosMode, SubMode};
809        if self.state.buffer.glyphs.is_empty() {
810            return;
811        }
812        let e = &mut self.engine;
813        let buf = &mut self.state.buffer;
814        match e.mode {
815            EngineMode::Simple => match e.sub_mode {
816                SubMode::Gsub => {
817                    let s = self.store.unwrap();
818                    e.set_classes(buf, None);
819                    if self.joined {
820                        buf.set_join_masks();
821                    }
822                    e.gsub(s, self.sub_mask, buf, None);
823                }
824                SubMode::Morx => {
825                    e.collect_selectors(&self.state.features, &mut self.state.selectors);
826                    e.morx(buf, &self.state.selectors);
827                }
828                _ => {}
829            },
830            EngineMode::Myanmar => {
831                let s = self.store.unwrap();
832                e.gsub(s, self.sub_mask | s.groups.stage2, buf, None);
833            }
834            EngineMode::Complex => {
835                let s = self.store.unwrap();
836                if self.joined {
837                    buf.set_join_masks();
838                    e.gsub(s, s.groups.stage2 | self.sub_mask, buf, None);
839                } else {
840                    e.gsub(s, self.sub_mask, buf, None);
841                }
842            }
843        }
844        buf.setup_positions(e.sub_mode == SubMode::Morx);
845        match e.pos_mode {
846            PosMode::Gpos => {
847                let s = self.store.unwrap();
848                e.gpos(s, self.pos_mask, buf, None);
849            }
850            PosMode::Kerx => {
851                e.kerx(buf, self.state.disable_kern);
852            }
853            PosMode::Kern => {
854                if !self.state.disable_kern {
855                    e.kern(buf);
856                }
857            }
858            _ => {}
859        }
860        // let metrics = self
861        //     .font_entry
862        //     .metrics
863        //     .materialize_metrics(self.font.data, self.engine.coords);
864        let glyph_metrics = self
865            .font_entry
866            .metrics
867            .materialize_glyph_metrics(&self.font, self.engine.coords);
868        for (g, p) in buf.glyphs.iter_mut().zip(buf.positions.iter_mut()) {
869            if g.flags & MARK_ATTACH == 0 {
870                p.advance += glyph_metrics.advance_width(g.id);
871            }
872            g.flags |= p.flags;
873        }
874        if buf.has_cursive {
875            if self.dir == Direction::RightToLeft {
876                for (i, g) in buf.glyphs.iter().enumerate().rev() {
877                    if g.flags & buffer::CURSIVE_ATTACH != 0 {
878                        let base_offset = buf.positions[i].base as usize;
879                        if base_offset != 0 {
880                            let (x, y) = {
881                                let base = &buf.positions[i + base_offset];
882                                (base.x, base.y)
883                            };
884                            let pos = &mut buf.positions[i];
885                            pos.x += x;
886                            pos.y += y;
887                        }
888                    }
889                }
890            } else {
891                for (i, g) in buf.glyphs.iter().enumerate() {
892                    if g.flags & buffer::CURSIVE_ATTACH != 0 {
893                        let base_offset = buf.positions[i].base as usize;
894                        if base_offset != 0 {
895                            let (x, y) = {
896                                let base = &buf.positions[i + base_offset];
897                                (base.x, base.y)
898                            };
899                            let pos = &mut buf.positions[i];
900                            pos.x += x;
901                            pos.y += y;
902                        }
903                    }
904                }
905            }
906        }
907        if buf.has_marks {
908            fn round_f32(f: f32) -> f32 {
909                f
910            }
911            for (i, g) in buf.glyphs.iter().enumerate() {
912                if g.flags & buffer::MARK_ATTACH != 0 {
913                    let base_offset = buf.positions[i].base as usize;
914                    if base_offset != 0 {
915                        let (x, y) = {
916                            let base = &buf.positions[i - base_offset];
917                            (base.x - round_f32(base.advance), base.y)
918                        };
919                        let pos = &mut buf.positions[i];
920                        pos.x += x;
921                        pos.y += y;
922                    }
923                }
924            }
925        }
926        let upem = glyph_metrics.units_per_em();
927        if self.size != 0. && upem != 0 {
928            let s = self.size / upem as f32;
929            for p in buf.positions.iter_mut() {
930                p.x *= s;
931                p.y *= s;
932                p.advance *= s;
933            }
934        }
935    }
936}