1use std::{
2 borrow::{Borrow, Cow},
3 cell::Cell,
4 iter::Peekable,
5 rc::Rc,
6};
7
8use elsa::FrozenMap;
9use icu_properties::LineBreak;
10use icu_segmenter::LineBreakIteratorUtf8;
11
12use crate::{
13 LinkTarget,
14 fonts::{Font, GeneralMetrics, ShapedGlyph},
15};
16
17struct TextPiecesCacheKey<'a> {
18 text: Cow<'a, str>,
19 font_index: usize,
20 size: f32,
21 color: u32,
22 extra_character_spacing: f32,
23 extra_word_spacing: f32,
24 extra_line_height: f32,
25}
26
27#[derive(Hash, PartialEq, Eq)]
28struct OwnedKey(TextPiecesCacheKey<'static>);
29
30impl<'a> Borrow<TextPiecesCacheKey<'a>> for OwnedKey {
31 fn borrow(&self) -> &TextPiecesCacheKey<'a> {
32 &self.0
33 }
34}
35
36impl<'a> PartialEq for TextPiecesCacheKey<'a> {
37 fn eq(&self, other: &Self) -> bool {
38 self.text == other.text
39 && self.font_index == other.font_index
40 && self.size.to_bits() == other.size.to_bits()
41 && self.color == other.color
42 && self.extra_character_spacing.to_bits() == other.extra_character_spacing.to_bits()
43 && self.extra_word_spacing.to_bits() == other.extra_word_spacing.to_bits()
44 && self.extra_line_height.to_bits() == other.extra_line_height.to_bits()
45 }
46}
47
48impl<'a> Eq for TextPiecesCacheKey<'a> {}
49
50impl<'a> std::hash::Hash for TextPiecesCacheKey<'a> {
51 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
52 self.text.hash(state);
53 self.font_index.hash(state);
54 self.size.to_bits().hash(state);
55 self.color.hash(state);
56 self.extra_character_spacing.to_bits().hash(state);
57 self.extra_word_spacing.to_bits().hash(state);
58 self.extra_line_height.to_bits().hash(state);
59 }
60}
61
62pub struct TextPiecesCache {
67 line_segmenter: icu_segmenter::LineSegmenter,
68 line_break_map:
69 icu_properties::maps::CodePointMapDataBorrowed<'static, icu_properties::LineBreak>,
70 cache: FrozenMap<OwnedKey, Vec<Piece>>,
71 shape_buffer: Cell<Vec<(Option<usize>, ShapedGlyph)>>,
72}
73
74impl TextPiecesCache {
75 pub fn new() -> Self {
76 TextPiecesCache {
77 line_segmenter: icu_segmenter::LineSegmenter::new_auto(),
78 line_break_map: icu_properties::maps::line_break(),
79 cache: FrozenMap::new(),
80 shape_buffer: Cell::new(Vec::new()),
81 }
82 }
83
84 pub(crate) fn pieces<'a, F: Font>(
85 &'a self,
86 text: &str,
87 font: &F,
88 size: f32,
89 color: u32,
90 extra_character_spacing: f32,
91 extra_word_spacing: f32,
92 extra_line_height: f32,
93 link: Option<LinkTarget<'a>>,
94 ) -> &'a [Piece] {
95 assert!(size.is_finite());
96 assert!(extra_character_spacing.is_finite());
97 assert!(extra_word_spacing.is_finite());
98 assert!(extra_line_height.is_finite());
99
100 let key = TextPiecesCacheKey {
101 text: Cow::Borrowed(text),
102 font_index: font.index(),
103 size,
104 color,
105 extra_character_spacing,
106 extra_word_spacing,
107 extra_line_height,
108 };
109
110 if let Some(value) = self.cache.get(&key) {
111 value
112 } else {
113 let shaped_hyphen = font.shape(super::HYPHEN, 0., 0.).next().unwrap();
114
115 let pieces: Vec<Piece> = text
116 .split("\n")
117 .enumerate()
118 .flat_map(|(i, text_line)| {
119 let break_piece = if i > 0 {
120 Some(Piece {
121 text: String::new(),
122 color: 0x00_00_00_FF,
123 empty: true,
124 glyph_count: 0,
125 mandatory_break_after: true,
126 height_above_baseline: 0.,
127 height_below_baseline: 0.,
128 shaped: Vec::new(),
129 size: 0.,
130 trailing_hyphen: None,
131 trailing_whitespace_width: 0.,
132 width: None,
133 link: None,
134 })
135 } else {
136 None
137 };
138 let mut shaped = self.shape_buffer.take();
139 assert!(shaped.is_empty());
140
141 super::shaping::shape(
142 font,
143 font.fallback_fonts(),
144 None,
145 text_line,
146 extra_character_spacing / size,
147 extra_word_spacing / size,
148 &mut shaped,
149 0,
150 );
151
152 let segments = self.line_segmenter.segment_str(text_line).peekable();
153
154 let line_pieces: Vec<Piece> = Pieces {
155 current: Some(0),
156 text: text_line,
157 shaped: shaped.iter(),
158 segments,
159 shaped_hyphen: shaped_hyphen.clone(),
160 size,
161 color,
162 extra_line_height,
163 main_font: font,
164 main_font_metrics: font.general_metrics(),
165 fallback_fonts: font.fallback_fonts(),
166 line_break_map: &self.line_break_map,
167 link,
168 }
169 .collect();
170
171 shaped.clear();
172 self.shape_buffer.set(shaped);
173
174 break_piece.into_iter().chain(line_pieces)
175 })
176 .collect();
177
178 self.cache.insert(
179 OwnedKey(TextPiecesCacheKey {
180 text: Cow::Owned(text.to_string()),
181 ..key
182 }),
183 pieces,
184 )
185 }
186 }
187}
188
189pub struct Piece {
190 pub text: String,
191 pub shaped: Vec<(Option<usize>, ShapedGlyph)>,
192
193 pub width: Option<f32>,
201
202 pub height_above_baseline: f32,
203 pub height_below_baseline: f32,
204 pub trailing_whitespace_width: f32,
205
206 pub trailing_hyphen: Option<(Option<usize>, ShapedGlyph)>,
209 pub mandatory_break_after: bool,
210 pub glyph_count: usize,
211 pub empty: bool,
212 pub size: f32,
213 pub color: u32,
214
215 pub link: Option<CacheLinkTarget>,
216}
217
218pub enum CacheLinkTarget {
219 Uri(Rc<str>),
220}
221
222impl CacheLinkTarget {
223 pub fn as_link_target(&self) -> LinkTarget<'_> {
224 match self {
225 CacheLinkTarget::Uri(uri) => LinkTarget::Uri(uri),
226 }
227 }
228}
229
230impl<'a> From<LinkTarget<'a>> for CacheLinkTarget {
231 fn from(value: LinkTarget<'a>) -> Self {
232 match value {
233 LinkTarget::Uri(uri) => CacheLinkTarget::Uri(uri.into()),
234 }
235 }
236}
237
238pub struct Pieces<'a, 'b, 'c, F> {
239 current: Option<usize>,
240 text: &'a str,
241 shaped: std::slice::Iter<'c, (Option<usize>, ShapedGlyph)>,
242 segments: Peekable<LineBreakIteratorUtf8<'b, 'a>>,
243 main_font: &'a F,
244 main_font_metrics: GeneralMetrics,
245 fallback_fonts: &'a [F],
246 shaped_hyphen: ShapedGlyph,
247 size: f32,
248 color: u32,
249 extra_line_height: f32,
250 line_break_map: &'a icu_properties::maps::CodePointMapDataBorrowed<'static, LineBreak>,
251 link: Option<LinkTarget<'a>>,
252}
253
254impl<'a, 'b, 'c, F: Font> Iterator for Pieces<'a, 'b, 'c, F> {
255 type Item = Piece;
256
257 fn next(&mut self) -> Option<Self::Item> {
258 let mut shaped = self.shaped.clone();
259
260 let Some(current) = self.current else {
261 return None;
262 };
263
264 let segment = self.segments.find(|&s| s != 0).unwrap_or_else(|| {
269 self.current = None;
270 self.text.len()
271 });
272
273 let mut iter = std::iter::from_fn({
274 let mut done = false;
275 let shaped = &mut shaped;
276 move || {
277 if done {
278 return None;
279 }
280
281 let next = shaped.next()?;
282
283 if next.1.text_range.end >= segment {
284 done = true;
285 }
286
287 Some(next)
288 }
289 })
290 .peekable();
291
292 let mut width = None;
293 let mut whitespace_width = 0.;
294 let mut glyph_count = 0;
295 let mut mandatory_break_after = false;
296
297 let mut height_above_baseline = self.main_font_metrics.height_above_baseline;
302 let mut height_below_baseline = self.main_font_metrics.height_below_baseline;
303
304 while let Some(glyph) = iter.next() {
305 glyph_count += 1;
306
307 if matches!(
309 &self.text[glyph.1.text_range.clone()],
310 " " | "\u{00A0}" | " "
311 ) {
312 whitespace_width += glyph.1.x_advance;
313 } else if matches!(
314 self.text[glyph.1.text_range.clone()]
315 .chars()
316 .next()
317 .map(|c| self.line_break_map.get(c)),
318 Some(
319 LineBreak::MandatoryBreak
320 | LineBreak::CarriageReturn
321 | LineBreak::LineFeed
322 | LineBreak::NextLine,
323 )
324 ) {
325 mandatory_break_after = true;
328 } else {
329 *width.get_or_insert(0.) += whitespace_width + glyph.1.x_advance;
330 whitespace_width = 0.;
331 }
332
333 let font = glyph.0.map_or(self.main_font, |i| &self.fallback_fonts[i]);
334
335 let metrics = font.general_metrics();
336
337 height_above_baseline = height_above_baseline.max(metrics.height_above_baseline);
338 height_below_baseline = height_below_baseline.max(metrics.height_below_baseline);
339 }
340
341 let text = &self.text[current..segment];
342
343 let trailing_hyphen = text
346 .ends_with('\u{00AD}')
347 .then_some(self.shaped_hyphen.clone());
348
349 let piece = Piece {
350 text: text.to_string(),
351 shaped: self
352 .shaped
353 .by_ref()
354 .take(glyph_count)
355 .map(|&(f, ref g)| {
356 (
357 f,
358 ShapedGlyph {
359 text_range: (g.text_range.start - current)
360 ..(g.text_range.end - current),
361 ..g.clone()
362 },
363 )
364 })
365 .collect(),
366 width: width.map(|w| w * self.size),
367 height_above_baseline: height_above_baseline * self.size,
368 height_below_baseline: height_below_baseline * self.size + self.extra_line_height,
371 trailing_whitespace_width: whitespace_width * self.size,
372 trailing_hyphen: trailing_hyphen.map(|glyph| (None, glyph)), mandatory_break_after,
374 glyph_count,
375
376 empty: glyph_count == 0 || (glyph_count == 1 && mandatory_break_after),
380
381 size: self.size,
382 color: self.color,
383 link: self.link.map(Into::into),
384 };
385
386 self.current = self.current.and(Some(segment));
387 self.shaped = shaped;
388
389 if self.segments.peek().is_none() && !mandatory_break_after {
390 self.current = None;
391 }
392
393 Some(piece)
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use crate::{fonts::ShapedGlyph, text::pieces::Piece};
400
401 use super::*;
402
403 #[derive(Debug)]
404 struct FakeFont;
405
406 #[derive(Clone, Debug)]
407 struct FakeShaped<'a> {
408 inner: std::str::CharIndices<'a>,
410 }
411
412 impl<'a> Iterator for FakeShaped<'a> {
413 type Item = ShapedGlyph;
414
415 fn next(&mut self) -> Option<Self::Item> {
416 if let Some((i, c)) = self.inner.next() {
417 Some(ShapedGlyph {
418 unsafe_to_break: false,
419 glyph_id: c as u32,
420 text_range: i..i + c.len_utf8(),
421 x_advance_font: if matches!(c, '\u{00ad}') { 0. } else { 1. },
424 x_advance: if matches!(c, '\u{00ad}') { 0. } else { 1. },
425 x_offset: 0.,
426 y_offset: 0.,
427 y_advance: 0.,
428 })
429 } else {
430 None
431 }
432 }
433 }
434
435 impl Font for FakeFont {
436 type Shaped<'a>
437 = FakeShaped<'a>
438 where
439 Self: 'a;
440
441 fn shape<'a>(&'a self, text: &'a str, _: f32, _: f32) -> Self::Shaped<'a> {
442 FakeShaped {
443 inner: text.char_indices(),
444 }
445 }
446
447 fn index(&self) -> usize {
448 0
449 }
450
451 fn encode(&self, _: &mut crate::Pdf, _: u32, _: &str) -> crate::fonts::EncodedGlyph {
452 unreachable!()
453 }
454
455 fn resource_name(&self) -> pdf_writer::Name<'_> {
456 unreachable!()
457 }
458
459 fn general_metrics(&self) -> crate::fonts::GeneralMetrics {
460 crate::fonts::GeneralMetrics {
461 height_above_baseline: 0.5,
462 height_below_baseline: 0.5,
463 }
464 }
465
466 fn fallback_fonts(&self) -> &[Self] {
467 &[]
468 }
469 }
470
471 fn collect_piece<'a>(piece: &'a Piece) -> (&'a str, Option<f32>, f32, bool) {
472 let mut text = String::new();
473
474 for glyph in &piece.shaped {
475 let character = glyph.1.glyph_id as u8 as char;
476
477 assert_eq!(
478 character.to_string(),
479 piece.text[glyph.1.text_range.clone()]
480 );
481
482 text.push(glyph.1.glyph_id as u8 as char);
483 }
484
485 assert_eq!(text, piece.text);
486
487 (
488 &piece.text,
489 piece.width,
490 piece.trailing_whitespace_width,
491 piece.mandatory_break_after,
492 )
493 }
494
495 #[test]
496 fn test_empty() {
497 let text = "";
498
499 let cache = TextPiecesCache::new();
500 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
501 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
502
503 assert_eq!(&pieces, &[("", None, 0., false)]);
504 }
505
506 #[test]
507 fn test_one() {
508 let text = "abcde";
509
510 let cache = TextPiecesCache::new();
511 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
512 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
513
514 assert_eq!(&pieces, &[("abcde", Some(5.), 0., false)]);
515 }
516
517 #[test]
518 fn test_two() {
519 let text = "deadbeef defaced";
520
521 let cache = TextPiecesCache::new();
522 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
523 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
524
525 assert_eq!(
526 &pieces,
527 &[
528 ("deadbeef ", Some(8.), 1., false),
529 ("defaced", Some(7.), 0., false),
530 ]
531 );
532 }
533
534 #[test]
535 fn test_three() {
536 let text = "deadbeef defaced fart";
537
538 let cache = TextPiecesCache::new();
539 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
540 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
541
542 assert_eq!(
543 &pieces,
544 &[
545 ("deadbeef ", Some(8.), 1., false),
546 ("defaced ", Some(7.), 1., false),
547 ("fart", Some(4.), 0., false)
548 ],
549 );
550 }
551
552 #[test]
553 fn test_just_newline() {
554 let text = "\n";
555
556 let cache = TextPiecesCache::new();
557 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
558 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
559
560 assert_eq!(
561 &pieces,
562 &[
563 ("", None, 0., false),
564 ("", None, 0., true),
565 ("", None, 0., false)
566 ]
567 );
568 }
569
570 #[test]
571 fn test_surrounded_newline() {
572 let text = "abc\ndef";
573
574 let cache = TextPiecesCache::new();
575 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
576 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
577
578 assert_eq!(
579 &pieces,
580 &[
581 ("abc", Some(3.), 0., false),
582 ("", None, 0., true),
583 ("def", Some(3.), 0., false)
584 ]
585 );
586 }
587
588 #[test]
589 fn test_newline_at_start() {
590 let text = "\nabc def";
591
592 let cache = TextPiecesCache::new();
593 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
594 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
595
596 assert_eq!(
597 &pieces,
598 &[
599 ("", None, 0.0, false),
600 ("", None, 0., true),
601 ("abc ", Some(3.), 1., false),
602 ("def", Some(3.), 0., false),
603 ]
604 );
605 }
606
607 #[test]
608 fn test_trailing_newline() {
609 let text = "abc def\n";
610
611 let cache = TextPiecesCache::new();
612 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
613 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
614
615 assert_eq!(
616 &pieces,
617 &[
618 ("abc ", Some(3.), 1., false),
619 ("def", Some(3.), 0., false),
620 ("", None, 0., true),
621 ("", None, 0., false),
622 ]
623 );
624 }
625
626 #[test]
627 fn test_newline_after_space() {
628 let text = "abc \ndef";
629
630 let cache = TextPiecesCache::new();
631 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
632 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
633
634 assert_eq!(
635 &pieces,
636 &[
637 ("abc ", Some(3.), 1., false),
638 ("", None, 0., true),
639 ("def", Some(3.), 0., false)
640 ],
641 );
642 }
643
644 #[test]
645 fn test_trailing_soft_hyphen() {
646 let text = "abc\u{ad}";
647
648 let cache = TextPiecesCache::new();
649 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
650 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
651
652 assert_eq!(&pieces, &[("abc\u{ad}", Some(3.), 0., false)]);
653 }
654
655 #[test]
656 fn test_trailing_soft_hyphen_and_space() {
657 let text = "abc\u{ad} ";
658
659 let cache = TextPiecesCache::new();
660 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
661
662 let pieces: Vec<_> = pieces
663 .iter()
664 .map(|p| {
665 let collected = collect_piece(p);
666
667 (
668 collected.0,
669 collected.1,
670 collected.2,
671 collected.3,
672 p.trailing_hyphen.as_ref().map(|h| h.1.x_advance),
673 )
674 })
675 .collect();
676
677 assert_eq!(&pieces, &[("abc\u{ad} ", Some(3.), 1., false, None)]);
678 }
679
680 #[test]
681 fn test_soft_hyphen_after_space() {
682 let text = " \u{ad}abc";
683
684 let cache = TextPiecesCache::new();
685 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
686 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
687
688 assert_eq!(
689 &pieces,
690 &[
691 (" ", None, 1., false),
692 ("\u{ad}", Some(0.), 0., false),
693 ("abc", Some(3.), 0., false),
694 ],
695 );
696 }
697
698 #[test]
699 fn test_soft_hyphen_between_spaces() {
700 let text = " \u{ad} ";
701
702 let cache = TextPiecesCache::new();
703 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
704 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
705
706 assert_eq!(
707 &pieces,
708 &[(" ", None, 1., false), ("\u{ad} ", Some(0.), 1., false)],
709 );
710 }
711
712 #[test]
713 fn test_just_spaces() {
714 let text = " ";
715
716 let cache = TextPiecesCache::new();
717 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
718 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
719
720 assert_eq!(&pieces, &[(" ", None, 8., false)]);
721 }
722
723 #[test]
724 fn test_mixed_whitespace() {
725 let text = " abc \ndef the\tjflkdsa";
726
727 let cache = TextPiecesCache::new();
728 let pieces = cache.pieces(text, &FakeFont, 1., 0, 0., 0., 0., None);
729 let pieces: Vec<_> = pieces.iter().map(collect_piece).collect();
730
731 assert_eq!(
732 &pieces,
733 &[
734 (" ", None, 4., false),
735 ("abc ", Some(3.), 4., false),
738 ("", None, 0., true),
739 ("def ", Some(3.), 2., false),
740 ("the\t", Some(4.), 0., false),
741 ("jflkdsa", Some(7.), 0., false),
742 ],
743 );
744 }
745}