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
30pub struct Shaper {
34 faces: Vec<ShapingFace>,
35 buffer: Buffer,
36 features: Vec<Feature>,
37 pub(crate) words: WordCache,
38 word_ends: Vec<usize>,
41 pieces: Vec<(Range<usize>, Range<usize>)>,
42 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 plans: Vec<KeptPlan>,
56 plain: Vec<PlainGlyphs>,
59 last: Option<(PlanAsk, usize)>,
63}
64
65#[derive(Clone, Copy, PartialEq)]
67struct PlanAsk {
68 rtl: bool,
69 script: [u8; 4],
70}
71
72struct KeptPlan {
73 direction: Direction,
75 script: Option<ShapingScript>,
76 language: Option<Language>,
77 features: SmallVec<[Feature; 4]>,
78 harfrust: OnceCell<HarfRustPlan>,
81 plain: Option<usize>,
84 key: u64,
86}
87
88struct HarfRustPlan {
90 plan: ShapePlan,
91 split: Option<WordSplit>,
93}
94
95struct Setting<'a> {
97 font: ShaperFont<'a, 'a>,
98 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 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 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 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 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 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 pub(crate) fn note_ink_reach(&mut self, reach: InkReach) {
195 self.ink_reach = self.ink_reach.max(reach);
196 }
197
198 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 #[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 #[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 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 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 #[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 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 #[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
380type ShapingSettings = (Direction, Option<ShapingScript>, Option<Language>);
382
383#[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 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 #[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 #[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 #[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 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 #[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 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 #[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
555struct 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 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 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 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 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
668fn 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
727fn 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
737fn 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
751fn layout_tables(font: &Font) -> Option<FontRef<'_>> {
753 FontRef::from_index(font_bytes(font), 0).ok()
754}
755
756pub(crate) fn font_bytes(font: &Font) -> &[u8] {
758 match font.source() {
759 Source::Blob(bytes) => bytes.as_ref(),
760 _ => &[],
761 }
762}
763
764#[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}