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}