1use std::collections::HashMap;
5use std::num::NonZeroU16;
6use std::sync::Arc;
7
8use fontdb::{Database, ID};
9use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv};
10use rustybuzz::ttf_parser;
11use rustybuzz::ttf_parser::{GlyphId, Tag};
12use strict_num::NonZeroPositiveF32;
13use tiny_skia_path::{NonZeroRect, Transform};
14use unicode_script::UnicodeScript;
15
16use crate::tree::{BBox, IsValidLength};
17use crate::{
18 AlignmentBaseline, ApproxZeroUlps, BaselineShift, DominantBaseline, Fill, FillRule, Font,
19 FontResolver, LengthAdjust, PaintOrder, Path, ShapeRendering, Stroke, Text, TextAnchor,
20 TextChunk, TextDecorationStyle, TextFlow, TextPath, TextSpan, WritingMode,
21};
22
23#[derive(Clone, Debug)]
28pub struct PositionedGlyph {
29 glyph_ts: Transform,
33 cluster_ts: Transform,
35 span_ts: Transform,
37 units_per_em: u16,
39 font_size: f32,
41 pub id: GlyphId,
43 pub text: String,
45 pub font: ID,
48}
49
50impl PositionedGlyph {
51 pub fn transform(&self) -> Transform {
53 let sx = self.font_size / self.units_per_em as f32;
54
55 self.span_ts
56 .pre_concat(self.cluster_ts)
57 .pre_concat(Transform::from_scale(sx, sx))
58 .pre_concat(self.glyph_ts)
59 }
60
61 pub fn outline_transform(&self) -> Transform {
64 self.transform()
66 .pre_concat(Transform::from_scale(1.0, -1.0))
67 }
68
69 pub fn cbdt_transform(&self, x: f32, y: f32, pixels_per_em: f32, height: f32) -> Transform {
72 self.transform()
73 .pre_concat(Transform::from_scale(
74 self.units_per_em as f32 / pixels_per_em,
75 self.units_per_em as f32 / pixels_per_em,
76 ))
77 .pre_translate(x, -height - y)
81 }
82
83 pub fn sbix_transform(
86 &self,
87 x: f32,
88 y: f32,
89 x_min: f32,
90 y_min: f32,
91 pixels_per_em: f32,
92 height: f32,
93 ) -> Transform {
94 let bbox_x_shift = -x_min;
96
97 let bbox_y_shift = if y_min.approx_zero_ulps(4) {
98 0.128 * self.units_per_em as f32
109 } else {
110 -y_min
111 };
112
113 self.transform()
114 .pre_concat(Transform::from_translate(bbox_x_shift, bbox_y_shift))
115 .pre_concat(Transform::from_scale(
116 self.units_per_em as f32 / pixels_per_em,
117 self.units_per_em as f32 / pixels_per_em,
118 ))
119 .pre_translate(x, -height - y)
123 }
124
125 pub fn svg_transform(&self) -> Transform {
128 self.transform()
129 }
130
131 pub fn colr_transform(&self) -> Transform {
134 self.outline_transform()
135 }
136}
137
138#[derive(Clone, Debug)]
141pub struct Span {
142 pub fill: Option<Fill>,
144 pub stroke: Option<Stroke>,
146 pub paint_order: PaintOrder,
148 pub font_size: NonZeroPositiveF32,
150 pub visible: bool,
152 pub positioned_glyphs: Vec<PositionedGlyph>,
154 pub underline: Option<Path>,
157 pub overline: Option<Path>,
160 pub line_through: Option<Path>,
163}
164
165#[derive(Clone, Debug)]
166struct GlyphCluster {
167 byte_idx: ByteIndex,
168 codepoint: char,
169 width: f32,
170 advance: f32,
171 ascent: f32,
172 descent: f32,
173 has_relative_shift: bool,
174 glyphs: Vec<PositionedGlyph>,
175 transform: Transform,
176 path_transform: Transform,
177 visible: bool,
178}
179
180impl GlyphCluster {
181 pub(crate) fn height(&self) -> f32 {
182 self.ascent - self.descent
183 }
184
185 pub(crate) fn transform(&self) -> Transform {
186 self.path_transform.post_concat(self.transform)
187 }
188}
189
190pub(crate) fn layout_text(
191 text_node: &Text,
192 resolver: &FontResolver,
193 fontdb: &mut Arc<fontdb::Database>,
194) -> Option<(Vec<Span>, NonZeroRect)> {
195 let mut fonts_cache: FontsCache = HashMap::new();
196
197 for chunk in &text_node.chunks {
198 for span in &chunk.spans {
199 if !fonts_cache.contains_key(&span.font) {
200 if let Some(font) =
201 (resolver.select_font)(&span.font, fontdb).and_then(|id| fontdb.load_font(id))
202 {
203 fonts_cache.insert(span.font.clone(), Arc::new(font));
204 }
205 }
206 }
207 }
208
209 let mut spans = vec![];
210 let mut char_offset = 0;
211 let mut last_x = 0.0;
212 let mut last_y = 0.0;
213 let mut bbox = BBox::default();
214 for chunk in &text_node.chunks {
215 let (x, y) = match chunk.text_flow {
216 TextFlow::Linear => (chunk.x.unwrap_or(last_x), chunk.y.unwrap_or(last_y)),
217 TextFlow::Path(_) => (0.0, 0.0),
218 };
219
220 let mut clusters = process_chunk(chunk, &fonts_cache, resolver, fontdb);
221 if clusters.is_empty() {
222 char_offset += chunk.text.chars().count();
223 continue;
224 }
225
226 apply_writing_mode(text_node.writing_mode, &mut clusters);
227 apply_letter_spacing(chunk, &mut clusters);
228 apply_word_spacing(chunk, &mut clusters);
229
230 apply_length_adjust(chunk, &mut clusters);
231 let mut curr_pos = resolve_clusters_positions(
232 text_node,
233 chunk,
234 char_offset,
235 text_node.writing_mode,
236 &fonts_cache,
237 &mut clusters,
238 );
239
240 let mut text_ts = Transform::default();
241 if text_node.writing_mode == WritingMode::TopToBottom {
242 if let TextFlow::Linear = chunk.text_flow {
243 text_ts = text_ts.pre_rotate_at(90.0, x, y);
244 }
245 }
246
247 for span in &chunk.spans {
248 let font = match fonts_cache.get(&span.font) {
249 Some(v) => v,
250 None => continue,
251 };
252
253 let decoration_spans = collect_decoration_spans(span, &clusters);
254
255 let mut span_ts = text_ts;
256 span_ts = span_ts.pre_translate(x, y);
257 if let TextFlow::Linear = chunk.text_flow {
258 let shift = resolve_baseline(span, font, text_node.writing_mode);
259
260 span_ts = span_ts.pre_translate(0.0, shift);
264 }
265
266 let mut underline = None;
267 let mut overline = None;
268 let mut line_through = None;
269
270 if let Some(decoration) = span.decoration.underline.clone() {
271 let offset = match text_node.writing_mode {
276 WritingMode::LeftToRight => -font.underline_position(span.font_size.get()),
277 WritingMode::TopToBottom => font.height(span.font_size.get()) / 2.0,
278 };
279
280 if let Some(path) =
281 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
282 {
283 bbox = bbox.expand(path.data.bounds());
284 underline = Some(path);
285 }
286 }
287
288 if let Some(decoration) = span.decoration.overline.clone() {
289 let offset = match text_node.writing_mode {
290 WritingMode::LeftToRight => -font.ascent(span.font_size.get()),
291 WritingMode::TopToBottom => -font.height(span.font_size.get()) / 2.0,
292 };
293
294 if let Some(path) =
295 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
296 {
297 bbox = bbox.expand(path.data.bounds());
298 overline = Some(path);
299 }
300 }
301
302 if let Some(decoration) = span.decoration.line_through.clone() {
303 let offset = match text_node.writing_mode {
304 WritingMode::LeftToRight => -font.line_through_position(span.font_size.get()),
305 WritingMode::TopToBottom => 0.0,
306 };
307
308 if let Some(path) =
309 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
310 {
311 bbox = bbox.expand(path.data.bounds());
312 line_through = Some(path);
313 }
314 }
315
316 let mut fill = span.fill.clone();
317 if let Some(ref mut fill) = fill {
318 fill.rule = FillRule::NonZero;
324 }
325
326 if let Some((span_fragments, span_bbox)) = convert_span(span, &clusters, span_ts) {
327 bbox = bbox.expand(span_bbox);
328
329 let positioned_glyphs = span_fragments
330 .into_iter()
331 .flat_map(|mut gc| {
332 let cluster_ts = gc.transform();
333 gc.glyphs.iter_mut().for_each(|pg| {
334 pg.cluster_ts = cluster_ts;
335 pg.span_ts = span_ts;
336 });
337 gc.glyphs
338 })
339 .collect();
340
341 spans.push(Span {
342 fill,
343 stroke: span.stroke.clone(),
344 paint_order: span.paint_order,
345 font_size: span.font_size,
346 visible: span.visible,
347 positioned_glyphs,
348 underline,
349 overline,
350 line_through,
351 });
352 }
353 }
354
355 char_offset += chunk.text.chars().count();
356
357 if text_node.writing_mode == WritingMode::TopToBottom {
358 if let TextFlow::Linear = chunk.text_flow {
359 std::mem::swap(&mut curr_pos.0, &mut curr_pos.1);
360 }
361 }
362
363 last_x = x + curr_pos.0;
364 last_y = y + curr_pos.1;
365 }
366
367 let bbox = bbox.to_non_zero_rect()?;
368
369 Some((spans, bbox))
370}
371
372fn convert_span(
373 span: &TextSpan,
374 clusters: &[GlyphCluster],
375 text_ts: Transform,
376) -> Option<(Vec<GlyphCluster>, NonZeroRect)> {
377 let mut span_clusters = vec![];
378 let mut bboxes_builder = tiny_skia_path::PathBuilder::new();
379
380 for cluster in clusters {
381 if !cluster.visible {
382 continue;
383 }
384
385 if span_contains(span, cluster.byte_idx) {
386 span_clusters.push(cluster.clone());
387 }
388
389 let mut advance = cluster.advance;
390 if advance <= 0.0 {
391 advance = 1.0;
392 }
393
394 if let Some(r) = NonZeroRect::from_xywh(0.0, -cluster.ascent, advance, cluster.height()) {
396 if let Some(r) = r.transform(cluster.transform()) {
397 bboxes_builder.push_rect(r.to_rect());
398 }
399 }
400 }
401
402 let mut bboxes = bboxes_builder.finish()?;
403 bboxes = bboxes.transform(text_ts)?;
404 let bbox = bboxes.compute_tight_bounds()?.to_non_zero_rect()?;
405
406 Some((span_clusters, bbox))
407}
408
409fn collect_decoration_spans(span: &TextSpan, clusters: &[GlyphCluster]) -> Vec<DecorationSpan> {
410 let mut spans = Vec::new();
411
412 let mut started = false;
413 let mut width = 0.0;
414 let mut transform = Transform::default();
415
416 for cluster in clusters {
417 if span_contains(span, cluster.byte_idx) {
418 if started && cluster.has_relative_shift {
419 started = false;
420 spans.push(DecorationSpan { width, transform });
421 }
422
423 if !started {
424 width = cluster.advance;
425 started = true;
426 transform = cluster.transform;
427 } else {
428 width += cluster.advance;
429 }
430 } else if started {
431 spans.push(DecorationSpan { width, transform });
432 started = false;
433 }
434 }
435
436 if started {
437 spans.push(DecorationSpan { width, transform });
438 }
439
440 spans
441}
442
443pub(crate) fn convert_decoration(
444 dy: f32,
445 span: &TextSpan,
446 font: &ResolvedFont,
447 mut decoration: TextDecorationStyle,
448 decoration_spans: &[DecorationSpan],
449 transform: Transform,
450) -> Option<Path> {
451 debug_assert!(!decoration_spans.is_empty());
452
453 let thickness = font.underline_thickness(span.font_size.get());
454
455 let mut builder = tiny_skia_path::PathBuilder::new();
456 for dec_span in decoration_spans {
457 let rect = match NonZeroRect::from_xywh(0.0, -thickness / 2.0, dec_span.width, thickness) {
458 Some(v) => v,
459 None => {
460 log::warn!("a decoration span has a malformed bbox");
461 continue;
462 }
463 };
464
465 let ts = dec_span.transform.pre_translate(0.0, dy);
466
467 let mut path = tiny_skia_path::PathBuilder::from_rect(rect.to_rect());
468 path = match path.transform(ts) {
469 Some(v) => v,
470 None => continue,
471 };
472
473 builder.push_path(&path);
474 }
475
476 let mut path_data = builder.finish()?;
477 path_data = path_data.transform(transform)?;
478
479 Path::new(
480 String::new(),
481 span.visible,
482 decoration.fill.take(),
483 decoration.stroke.take(),
484 PaintOrder::default(),
485 ShapeRendering::default(),
486 Arc::new(path_data),
487 Transform::default(),
488 )
489}
490
491#[derive(Clone, Copy)]
496pub(crate) struct DecorationSpan {
497 pub(crate) width: f32,
498 pub(crate) transform: Transform,
499}
500
501fn resolve_clusters_positions(
507 text: &Text,
508 chunk: &TextChunk,
509 char_offset: usize,
510 writing_mode: WritingMode,
511 fonts_cache: &FontsCache,
512 clusters: &mut [GlyphCluster],
513) -> (f32, f32) {
514 match chunk.text_flow {
515 TextFlow::Linear => {
516 resolve_clusters_positions_horizontal(text, chunk, char_offset, writing_mode, clusters)
517 }
518 TextFlow::Path(ref path) => resolve_clusters_positions_path(
519 text,
520 chunk,
521 char_offset,
522 path,
523 writing_mode,
524 fonts_cache,
525 clusters,
526 ),
527 }
528}
529
530fn clusters_length(clusters: &[GlyphCluster]) -> f32 {
531 clusters.iter().fold(0.0, |w, cluster| w + cluster.advance)
532}
533
534fn resolve_clusters_positions_horizontal(
535 text: &Text,
536 chunk: &TextChunk,
537 offset: usize,
538 writing_mode: WritingMode,
539 clusters: &mut [GlyphCluster],
540) -> (f32, f32) {
541 let mut x = process_anchor(chunk.anchor, clusters_length(clusters));
542 let mut y = 0.0;
543
544 for cluster in clusters {
545 let cp = offset + cluster.byte_idx.code_point_at(&chunk.text);
546 if let (Some(dx), Some(dy)) = (text.dx.get(cp), text.dy.get(cp)) {
547 if writing_mode == WritingMode::LeftToRight {
548 x += dx;
549 y += dy;
550 } else {
551 y -= dx;
552 x += dy;
553 }
554 cluster.has_relative_shift = !dx.approx_zero_ulps(4) || !dy.approx_zero_ulps(4);
555 }
556
557 cluster.transform = cluster.transform.pre_translate(x, y);
558
559 if let Some(angle) = text.rotate.get(cp).cloned() {
560 if !angle.approx_zero_ulps(4) {
561 cluster.transform = cluster.transform.pre_rotate(angle);
562 cluster.has_relative_shift = true;
563 }
564 }
565
566 x += cluster.advance;
567 }
568
569 (x, y)
570}
571
572pub(crate) fn resolve_baseline(
581 span: &TextSpan,
582 font: &ResolvedFont,
583 writing_mode: WritingMode,
584) -> f32 {
585 let mut shift = -resolve_baseline_shift(&span.baseline_shift, font, span.font_size.get());
586
587 if writing_mode == WritingMode::LeftToRight {
589 if span.alignment_baseline == AlignmentBaseline::Auto
590 || span.alignment_baseline == AlignmentBaseline::Baseline
591 {
592 shift += font.dominant_baseline_shift(span.dominant_baseline, span.font_size.get());
593 } else {
594 shift += font.alignment_baseline_shift(span.alignment_baseline, span.font_size.get());
595 }
596 }
597
598 shift
599}
600
601fn resolve_baseline_shift(baselines: &[BaselineShift], font: &ResolvedFont, font_size: f32) -> f32 {
602 let mut shift = 0.0;
603 for baseline in baselines.iter().rev() {
604 match baseline {
605 BaselineShift::Baseline => {}
606 BaselineShift::Subscript => shift -= font.subscript_offset(font_size),
607 BaselineShift::Superscript => shift += font.superscript_offset(font_size),
608 BaselineShift::Number(n) => shift += n,
609 }
610 }
611
612 shift
613}
614
615fn resolve_clusters_positions_path(
616 text: &Text,
617 chunk: &TextChunk,
618 char_offset: usize,
619 path: &TextPath,
620 writing_mode: WritingMode,
621 fonts_cache: &FontsCache,
622 clusters: &mut [GlyphCluster],
623) -> (f32, f32) {
624 let mut last_x = 0.0;
625 let mut last_y = 0.0;
626
627 let mut dy = 0.0;
628
629 let chunk_offset = match writing_mode {
632 WritingMode::LeftToRight => chunk.x.unwrap_or(0.0),
633 WritingMode::TopToBottom => chunk.y.unwrap_or(0.0),
634 };
635
636 let start_offset =
637 chunk_offset + path.start_offset + process_anchor(chunk.anchor, clusters_length(clusters));
638
639 let normals = collect_normals(text, chunk, clusters, &path.path, char_offset, start_offset);
640 for (cluster, normal) in clusters.iter_mut().zip(normals) {
641 let (x, y, angle) = match normal {
642 Some(normal) => (normal.x, normal.y, normal.angle),
643 None => {
644 cluster.visible = false;
646 continue;
647 }
648 };
649
650 cluster.has_relative_shift = true;
652
653 let orig_ts = cluster.transform;
654
655 let half_width = cluster.width / 2.0;
657 cluster.transform = Transform::default();
658 cluster.transform = cluster.transform.pre_translate(x - half_width, y);
659 cluster.transform = cluster.transform.pre_rotate_at(angle, half_width, 0.0);
660
661 let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);
662 dy += text.dy.get(cp).cloned().unwrap_or(0.0);
663
664 let baseline_shift = chunk_span_at(chunk, cluster.byte_idx)
665 .map(|span| {
666 let font = match fonts_cache.get(&span.font) {
667 Some(v) => v,
668 None => return 0.0,
669 };
670 -resolve_baseline(span, font, writing_mode)
671 })
672 .unwrap_or(0.0);
673
674 if !dy.approx_zero_ulps(4) || !baseline_shift.approx_zero_ulps(4) {
677 let shift = kurbo::Vec2::new(0.0, (dy - baseline_shift) as f64);
678 cluster.transform = cluster
679 .transform
680 .pre_translate(shift.x as f32, shift.y as f32);
681 }
682
683 if let Some(angle) = text.rotate.get(cp).cloned() {
684 if !angle.approx_zero_ulps(4) {
685 cluster.transform = cluster.transform.pre_rotate(angle);
686 }
687 }
688
689 cluster.transform = cluster.transform.pre_concat(orig_ts);
691
692 last_x = x + cluster.advance;
693 last_y = y;
694 }
695
696 (last_x, last_y)
697}
698
699pub(crate) fn process_anchor(a: TextAnchor, text_width: f32) -> f32 {
700 match a {
701 TextAnchor::Start => 0.0, TextAnchor::Middle => -text_width / 2.0,
703 TextAnchor::End => -text_width,
704 }
705}
706
707pub(crate) struct PathNormal {
708 pub(crate) x: f32,
709 pub(crate) y: f32,
710 pub(crate) angle: f32,
711}
712
713fn collect_normals(
714 text: &Text,
715 chunk: &TextChunk,
716 clusters: &[GlyphCluster],
717 path: &tiny_skia_path::Path,
718 char_offset: usize,
719 offset: f32,
720) -> Vec<Option<PathNormal>> {
721 let mut offsets = Vec::with_capacity(clusters.len());
722 let mut normals = Vec::with_capacity(clusters.len());
723 {
724 let mut advance = offset;
725 for cluster in clusters {
726 let half_width = cluster.width / 2.0;
728
729 let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);
731 advance += text.dx.get(cp).cloned().unwrap_or(0.0);
732
733 let offset = advance + half_width;
734
735 if offset < 0.0 {
737 normals.push(None);
738 }
739
740 offsets.push(offset as f64);
741 advance += cluster.advance;
742 }
743 }
744
745 let mut prev_mx = path.points()[0].x;
746 let mut prev_my = path.points()[0].y;
747 let mut prev_x = prev_mx;
748 let mut prev_y = prev_my;
749
750 fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez {
751 let line = kurbo::Line::new(
752 kurbo::Point::new(px as f64, py as f64),
753 kurbo::Point::new(x as f64, y as f64),
754 );
755 let p1 = line.eval(0.33);
756 let p2 = line.eval(0.66);
757 kurbo::CubicBez {
758 p0: line.p0,
759 p1,
760 p2,
761 p3: line.p1,
762 }
763 }
764
765 let mut length: f64 = 0.0;
766 for seg in path.segments() {
767 let curve = match seg {
768 tiny_skia_path::PathSegment::MoveTo(p) => {
769 prev_mx = p.x;
770 prev_my = p.y;
771 prev_x = p.x;
772 prev_y = p.y;
773 continue;
774 }
775 tiny_skia_path::PathSegment::LineTo(p) => {
776 create_curve_from_line(prev_x, prev_y, p.x, p.y)
777 }
778 tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez {
779 p0: kurbo::Point::new(prev_x as f64, prev_y as f64),
780 p1: kurbo::Point::new(p1.x as f64, p1.y as f64),
781 p2: kurbo::Point::new(p.x as f64, p.y as f64),
782 }
783 .raise(),
784 tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez {
785 p0: kurbo::Point::new(prev_x as f64, prev_y as f64),
786 p1: kurbo::Point::new(p1.x as f64, p1.y as f64),
787 p2: kurbo::Point::new(p2.x as f64, p2.y as f64),
788 p3: kurbo::Point::new(p.x as f64, p.y as f64),
789 },
790 tiny_skia_path::PathSegment::Close => {
791 create_curve_from_line(prev_x, prev_y, prev_mx, prev_my)
792 }
793 };
794
795 let arclen_accuracy = {
796 let base_arclen_accuracy = 0.5;
797 let (sx, sy) = text.abs_transform.get_scale();
801 base_arclen_accuracy / (sx * sy).sqrt().max(1.0)
803 };
804
805 let curve_len = curve.arclen(arclen_accuracy as f64);
806
807 for offset in &offsets[normals.len()..] {
808 if *offset >= length && *offset <= length + curve_len {
809 let mut offset = curve.inv_arclen(offset - length, arclen_accuracy as f64);
810 debug_assert!((-1.0e-3..=1.0 + 1.0e-3).contains(&offset));
812 offset = offset.clamp(0.0, 1.0);
813
814 let pos = curve.eval(offset);
815 let d = curve.deriv().eval(offset);
816 let d = kurbo::Vec2::new(-d.y, d.x); let angle = d.atan2().to_degrees() - 90.0;
818
819 normals.push(Some(PathNormal {
820 x: pos.x as f32,
821 y: pos.y as f32,
822 angle: angle as f32,
823 }));
824
825 if normals.len() == offsets.len() {
826 break;
827 }
828 }
829 }
830
831 length += curve_len;
832 prev_x = curve.p3.x as f32;
833 prev_y = curve.p3.y as f32;
834 }
835
836 for _ in 0..(offsets.len() - normals.len()) {
838 normals.push(None);
839 }
840
841 normals
842}
843
844fn process_chunk(
849 chunk: &TextChunk,
850 fonts_cache: &FontsCache,
851 resolver: &FontResolver,
852 fontdb: &mut Arc<fontdb::Database>,
853) -> Vec<GlyphCluster> {
854 let mut glyphs = Vec::new();
886 for span in &chunk.spans {
887 let font = match fonts_cache.get(&span.font) {
888 Some(v) => v.clone(),
889 None => continue,
890 };
891
892 let tmp_glyphs = shape_text(
893 &chunk.text,
894 font,
895 span.small_caps,
896 span.apply_kerning,
897 resolver,
898 fontdb,
899 );
900
901 if glyphs.is_empty() {
903 glyphs = tmp_glyphs;
904 continue;
905 }
906
907 let mut iter = tmp_glyphs.into_iter();
909 while let Some(new_glyph) = iter.next() {
910 if !span_contains(span, new_glyph.byte_idx) {
911 continue;
912 }
913
914 let Some(idx) = glyphs.iter().position(|g| g.byte_idx == new_glyph.byte_idx) else {
915 continue;
916 };
917
918 let prev_cluster_len = glyphs[idx].cluster_len;
919 if prev_cluster_len < new_glyph.cluster_len {
920 for _ in 1..new_glyph.cluster_len {
923 glyphs.remove(idx + 1);
924 }
925 } else if prev_cluster_len > new_glyph.cluster_len {
926 for j in 1..prev_cluster_len {
929 if let Some(g) = iter.next() {
930 glyphs.insert(idx + j, g);
931 }
932 }
933 }
934
935 glyphs[idx] = new_glyph;
936 }
937 }
938
939 let mut clusters = Vec::new();
941 for (range, byte_idx) in GlyphClusters::new(&glyphs) {
942 if let Some(span) = chunk_span_at(chunk, byte_idx) {
943 clusters.push(form_glyph_clusters(
944 &glyphs[range],
945 &chunk.text,
946 span.font_size.get(),
947 ));
948 }
949 }
950
951 clusters
952}
953
954fn apply_length_adjust(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
955 let is_horizontal = matches!(chunk.text_flow, TextFlow::Linear);
956
957 for span in &chunk.spans {
958 let target_width = match span.text_length {
959 Some(v) => v,
960 None => continue,
961 };
962
963 let mut width = 0.0;
964 let mut cluster_indexes = Vec::new();
965 for i in span.start..span.end {
966 if let Some(index) = clusters.iter().position(|c| c.byte_idx.value() == i) {
967 cluster_indexes.push(index);
968 }
969 }
970 cluster_indexes.sort();
972 cluster_indexes.dedup();
973
974 for i in &cluster_indexes {
975 width += clusters[*i].width;
978 }
979
980 if cluster_indexes.is_empty() {
981 continue;
982 }
983
984 if span.length_adjust == LengthAdjust::Spacing {
985 let factor = if cluster_indexes.len() > 1 {
986 (target_width - width) / (cluster_indexes.len() - 1) as f32
987 } else {
988 0.0
989 };
990
991 for i in cluster_indexes {
992 clusters[i].advance = clusters[i].width + factor;
993 }
994 } else {
995 let factor = target_width / width;
996 if factor < 0.001 {
998 continue;
999 }
1000
1001 for i in cluster_indexes {
1002 clusters[i].transform = clusters[i].transform.pre_scale(factor, 1.0);
1003
1004 if !is_horizontal {
1006 clusters[i].advance *= factor;
1007 clusters[i].width *= factor;
1008 }
1009 }
1010 }
1011 }
1012}
1013
1014fn apply_writing_mode(writing_mode: WritingMode, clusters: &mut [GlyphCluster]) {
1017 if writing_mode != WritingMode::TopToBottom {
1018 return;
1019 }
1020
1021 for cluster in clusters {
1022 let orientation = unicode_vo::char_orientation(cluster.codepoint);
1023 if orientation == unicode_vo::Orientation::Upright {
1024 let mut ts = Transform::default();
1025 ts = ts.pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);
1027 ts = ts.pre_rotate_at(
1029 -90.0,
1030 cluster.width / 2.0,
1031 -(cluster.ascent + cluster.descent) / 2.0,
1032 );
1033
1034 cluster.path_transform = ts;
1035
1036 cluster.ascent = cluster.width / 2.0;
1038 cluster.descent = -cluster.width / 2.0;
1039 } else {
1040 cluster.transform = cluster
1044 .transform
1045 .pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);
1046 }
1047 }
1048}
1049
1050fn apply_letter_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1054 if !chunk
1056 .spans
1057 .iter()
1058 .any(|span| !span.letter_spacing.approx_zero_ulps(4))
1059 {
1060 return;
1061 }
1062
1063 let num_clusters = clusters.len();
1064 for (i, cluster) in clusters.iter_mut().enumerate() {
1065 let script = cluster.codepoint.script();
1070 if script_supports_letter_spacing(script) {
1071 if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {
1072 if i != num_clusters - 1 {
1075 cluster.advance += span.letter_spacing;
1076 }
1077
1078 if !cluster.advance.is_valid_length() {
1081 cluster.width = 0.0;
1082 cluster.advance = 0.0;
1083 cluster.glyphs = vec![];
1084 }
1085 }
1086 }
1087 }
1088}
1089
1090fn apply_word_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1094 if !chunk
1096 .spans
1097 .iter()
1098 .any(|span| !span.word_spacing.approx_zero_ulps(4))
1099 {
1100 return;
1101 }
1102
1103 for cluster in clusters {
1104 if is_word_separator_characters(cluster.codepoint) {
1105 if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {
1106 cluster.advance += span.word_spacing;
1110
1111 }
1113 }
1114 }
1115}
1116
1117fn form_glyph_clusters(glyphs: &[Glyph], text: &str, font_size: f32) -> GlyphCluster {
1118 debug_assert!(!glyphs.is_empty());
1119
1120 let mut width = 0.0;
1121 let mut x: f32 = 0.0;
1122
1123 let mut positioned_glyphs = vec![];
1124
1125 for glyph in glyphs {
1126 let sx = glyph.font.scale(font_size);
1127
1128 let ts = Transform::from_translate(x + glyph.dx as f32, -glyph.dy as f32);
1135
1136 positioned_glyphs.push(PositionedGlyph {
1137 glyph_ts: ts,
1138 cluster_ts: Transform::default(),
1140 span_ts: Transform::default(),
1142 units_per_em: glyph.font.units_per_em.get(),
1143 font_size,
1144 font: glyph.font.id,
1145 text: glyph.text.clone(),
1146 id: glyph.id,
1147 });
1148
1149 x += glyph.width as f32;
1150
1151 let glyph_width = glyph.width as f32 * sx;
1152 if glyph_width > width {
1153 width = glyph_width;
1154 }
1155 }
1156
1157 let byte_idx = glyphs[0].byte_idx;
1158 let font = glyphs[0].font.clone();
1159 GlyphCluster {
1160 byte_idx,
1161 codepoint: byte_idx.char_from(text),
1162 width,
1163 advance: width,
1164 ascent: font.ascent(font_size),
1165 descent: font.descent(font_size),
1166 has_relative_shift: false,
1167 transform: Transform::default(),
1168 path_transform: Transform::default(),
1169 glyphs: positioned_glyphs,
1170 visible: true,
1171 }
1172}
1173
1174pub(crate) trait DatabaseExt {
1175 fn load_font(&self, id: ID) -> Option<ResolvedFont>;
1176 fn has_char(&self, id: ID, c: char) -> bool;
1177}
1178
1179impl DatabaseExt for Database {
1180 #[inline(never)]
1181 fn load_font(&self, id: ID) -> Option<ResolvedFont> {
1182 self.with_face_data(id, |data, face_index| -> Option<ResolvedFont> {
1183 let font = ttf_parser::Face::parse(data, face_index).ok()?;
1184
1185 let units_per_em = NonZeroU16::new(font.units_per_em())?;
1186
1187 let ascent = font.ascender();
1188 let descent = font.descender();
1189
1190 let x_height = font
1191 .x_height()
1192 .and_then(|x| u16::try_from(x).ok())
1193 .and_then(NonZeroU16::new);
1194 let x_height = match x_height {
1195 Some(height) => height,
1196 None => {
1197 u16::try_from((f32::from(ascent - descent) * 0.45) as i32)
1200 .ok()
1201 .and_then(NonZeroU16::new)?
1202 }
1203 };
1204
1205 let line_through = font.strikeout_metrics();
1206 let line_through_position = match line_through {
1207 Some(metrics) => metrics.position,
1208 None => x_height.get() as i16 / 2,
1209 };
1210
1211 let (underline_position, underline_thickness) = match font.underline_metrics() {
1212 Some(metrics) => {
1213 let thickness = u16::try_from(metrics.thickness)
1214 .ok()
1215 .and_then(NonZeroU16::new)
1216 .unwrap_or_else(|| NonZeroU16::new(units_per_em.get() / 12).unwrap());
1218
1219 (metrics.position, thickness)
1220 }
1221 None => (
1222 -(units_per_em.get() as i16) / 9,
1223 NonZeroU16::new(units_per_em.get() / 12).unwrap(),
1224 ),
1225 };
1226
1227 let mut subscript_offset = (units_per_em.get() as f32 / 0.2).round() as i16;
1229 let mut superscript_offset = (units_per_em.get() as f32 / 0.4).round() as i16;
1230 if let Some(metrics) = font.subscript_metrics() {
1231 subscript_offset = metrics.y_offset;
1232 }
1233
1234 if let Some(metrics) = font.superscript_metrics() {
1235 superscript_offset = metrics.y_offset;
1236 }
1237
1238 Some(ResolvedFont {
1239 id,
1240 units_per_em,
1241 ascent,
1242 descent,
1243 x_height,
1244 underline_position,
1245 underline_thickness,
1246 line_through_position,
1247 subscript_offset,
1248 superscript_offset,
1249 })
1250 })?
1251 }
1252
1253 #[inline(never)]
1254 fn has_char(&self, id: ID, c: char) -> bool {
1255 let res = self.with_face_data(id, |font_data, face_index| -> Option<bool> {
1256 let font = ttf_parser::Face::parse(font_data, face_index).ok()?;
1257 font.glyph_index(c)?;
1258 Some(true)
1259 });
1260
1261 res == Some(Some(true))
1262 }
1263}
1264
1265pub(crate) fn shape_text(
1267 text: &str,
1268 font: Arc<ResolvedFont>,
1269 small_caps: bool,
1270 apply_kerning: bool,
1271 resolver: &FontResolver,
1272 fontdb: &mut Arc<fontdb::Database>,
1273) -> Vec<Glyph> {
1274 let mut glyphs = shape_text_with_font(text, font.clone(), small_caps, apply_kerning, fontdb)
1275 .unwrap_or_default();
1276
1277 let mut used_fonts = vec![font.id];
1279
1280 'outer: loop {
1282 let mut missing = None;
1283 for glyph in &glyphs {
1284 if glyph.is_missing() {
1285 missing = Some(glyph.byte_idx.char_from(text));
1286 break;
1287 }
1288 }
1289
1290 if let Some(c) = missing {
1291 let fallback_font = match (resolver.select_fallback)(c, &used_fonts, fontdb)
1292 .and_then(|id| fontdb.load_font(id))
1293 {
1294 Some(v) => Arc::new(v),
1295 None => break 'outer,
1296 };
1297
1298 let fallback_glyphs = shape_text_with_font(
1300 text,
1301 fallback_font.clone(),
1302 small_caps,
1303 apply_kerning,
1304 fontdb,
1305 )
1306 .unwrap_or_default();
1307
1308 let all_matched = fallback_glyphs.iter().all(|g| !g.is_missing());
1309 if all_matched {
1310 glyphs = fallback_glyphs;
1312 break 'outer;
1313 }
1314
1315 if glyphs.len() != fallback_glyphs.len() {
1318 break 'outer;
1319 }
1320
1321 for i in 0..glyphs.len() {
1325 if glyphs[i].is_missing() && !fallback_glyphs[i].is_missing() {
1326 glyphs[i] = fallback_glyphs[i].clone();
1327 }
1328 }
1329
1330 used_fonts.push(fallback_font.id);
1332 } else {
1333 break 'outer;
1334 }
1335 }
1336
1337 for glyph in &glyphs {
1339 if glyph.is_missing() {
1340 let c = glyph.byte_idx.char_from(text);
1341 log::warn!(
1343 "No fonts with a {}/U+{:X} character were found.",
1344 c,
1345 c as u32
1346 );
1347 }
1348 }
1349
1350 glyphs
1351}
1352
1353fn shape_text_with_font(
1357 text: &str,
1358 font: Arc<ResolvedFont>,
1359 small_caps: bool,
1360 apply_kerning: bool,
1361 fontdb: &fontdb::Database,
1362) -> Option<Vec<Glyph>> {
1363 fontdb.with_face_data(font.id, |font_data, face_index| -> Option<Vec<Glyph>> {
1364 let rb_font = rustybuzz::Face::from_slice(font_data, face_index)?;
1365
1366 let bidi_info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
1367 let paragraph = &bidi_info.paragraphs[0];
1368 let line = paragraph.range.clone();
1369
1370 let mut glyphs = Vec::new();
1371
1372 let (levels, runs) = bidi_info.visual_runs(paragraph, line);
1373 for run in runs.iter() {
1374 let sub_text = &text[run.clone()];
1375 if sub_text.is_empty() {
1376 continue;
1377 }
1378
1379 let ltr = levels[run.start].is_ltr();
1380 let hb_direction = if ltr {
1381 rustybuzz::Direction::LeftToRight
1382 } else {
1383 rustybuzz::Direction::RightToLeft
1384 };
1385
1386 let mut buffer = rustybuzz::UnicodeBuffer::new();
1387 buffer.push_str(sub_text);
1388 buffer.set_direction(hb_direction);
1389
1390 let mut features = Vec::new();
1391 if small_caps {
1392 features.push(rustybuzz::Feature::new(Tag::from_bytes(b"smcp"), 1, ..));
1393 }
1394
1395 if !apply_kerning {
1396 features.push(rustybuzz::Feature::new(Tag::from_bytes(b"kern"), 0, ..));
1397 }
1398
1399 let output = rustybuzz::shape(&rb_font, &features, buffer);
1400
1401 let positions = output.glyph_positions();
1402 let infos = output.glyph_infos();
1403
1404 for i in 0..output.len() {
1405 let pos = positions[i];
1406 let info = infos[i];
1407 let idx = run.start + info.cluster as usize;
1408
1409 let start = info.cluster as usize;
1410
1411 let end = if ltr {
1412 i.checked_add(1)
1413 } else {
1414 i.checked_sub(1)
1415 }
1416 .and_then(|last| infos.get(last))
1417 .map_or(sub_text.len(), |info| info.cluster as usize);
1418
1419 glyphs.push(Glyph {
1420 byte_idx: ByteIndex::new(idx),
1421 cluster_len: end.checked_sub(start).unwrap_or(0), text: sub_text[start..end].to_string(),
1423 id: GlyphId(info.glyph_id as u16),
1424 dx: pos.x_offset,
1425 dy: pos.y_offset,
1426 width: pos.x_advance,
1427 font: font.clone(),
1428 });
1429 }
1430 }
1431
1432 Some(glyphs)
1433 })?
1434}
1435
1436pub(crate) struct GlyphClusters<'a> {
1441 data: &'a [Glyph],
1442 idx: usize,
1443}
1444
1445impl<'a> GlyphClusters<'a> {
1446 pub(crate) fn new(data: &'a [Glyph]) -> Self {
1447 GlyphClusters { data, idx: 0 }
1448 }
1449}
1450
1451impl Iterator for GlyphClusters<'_> {
1452 type Item = (std::ops::Range<usize>, ByteIndex);
1453
1454 fn next(&mut self) -> Option<Self::Item> {
1455 if self.idx == self.data.len() {
1456 return None;
1457 }
1458
1459 let start = self.idx;
1460 let cluster = self.data[self.idx].byte_idx;
1461 for g in &self.data[self.idx..] {
1462 if g.byte_idx != cluster {
1463 break;
1464 }
1465
1466 self.idx += 1;
1467 }
1468
1469 Some((start..self.idx, cluster))
1470 }
1471}
1472
1473pub(crate) fn script_supports_letter_spacing(script: unicode_script::Script) -> bool {
1479 use unicode_script::Script;
1480
1481 !matches!(
1482 script,
1483 Script::Arabic
1484 | Script::Syriac
1485 | Script::Nko
1486 | Script::Manichaean
1487 | Script::Psalter_Pahlavi
1488 | Script::Mandaic
1489 | Script::Mongolian
1490 | Script::Phags_Pa
1491 | Script::Devanagari
1492 | Script::Bengali
1493 | Script::Gurmukhi
1494 | Script::Modi
1495 | Script::Sharada
1496 | Script::Syloti_Nagri
1497 | Script::Tirhuta
1498 | Script::Ogham
1499 )
1500}
1501
1502#[derive(Clone)]
1506pub(crate) struct Glyph {
1507 pub(crate) id: GlyphId,
1509
1510 pub(crate) byte_idx: ByteIndex,
1514
1515 pub(crate) cluster_len: usize,
1517
1518 pub(crate) text: String,
1520
1521 pub(crate) dx: i32,
1523
1524 pub(crate) dy: i32,
1526
1527 pub(crate) width: i32,
1529
1530 pub(crate) font: Arc<ResolvedFont>,
1534}
1535
1536impl Glyph {
1537 fn is_missing(&self) -> bool {
1538 self.id.0 == 0
1539 }
1540}
1541
1542#[derive(Clone, Copy, Debug)]
1543pub(crate) struct ResolvedFont {
1544 pub(crate) id: ID,
1545
1546 units_per_em: NonZeroU16,
1547
1548 ascent: i16,
1550 descent: i16,
1551 x_height: NonZeroU16,
1552
1553 underline_position: i16,
1554 underline_thickness: NonZeroU16,
1555
1556 line_through_position: i16,
1560
1561 subscript_offset: i16,
1562 superscript_offset: i16,
1563}
1564
1565pub(crate) fn chunk_span_at(chunk: &TextChunk, byte_offset: ByteIndex) -> Option<&TextSpan> {
1566 chunk
1567 .spans
1568 .iter()
1569 .find(|&span| span_contains(span, byte_offset))
1570}
1571
1572pub(crate) fn span_contains(span: &TextSpan, byte_offset: ByteIndex) -> bool {
1573 byte_offset.value() >= span.start && byte_offset.value() < span.end
1574}
1575
1576pub(crate) fn is_word_separator_characters(c: char) -> bool {
1580 matches!(
1581 c as u32,
1582 0x0020 | 0x00A0 | 0x1361 | 0x010100 | 0x010101 | 0x01039F | 0x01091F
1583 )
1584}
1585
1586impl ResolvedFont {
1587 #[inline]
1588 pub(crate) fn scale(&self, font_size: f32) -> f32 {
1589 font_size / self.units_per_em.get() as f32
1590 }
1591
1592 #[inline]
1593 pub(crate) fn ascent(&self, font_size: f32) -> f32 {
1594 self.ascent as f32 * self.scale(font_size)
1595 }
1596
1597 #[inline]
1598 pub(crate) fn descent(&self, font_size: f32) -> f32 {
1599 self.descent as f32 * self.scale(font_size)
1600 }
1601
1602 #[inline]
1603 pub(crate) fn height(&self, font_size: f32) -> f32 {
1604 self.ascent(font_size) - self.descent(font_size)
1605 }
1606
1607 #[inline]
1608 pub(crate) fn x_height(&self, font_size: f32) -> f32 {
1609 self.x_height.get() as f32 * self.scale(font_size)
1610 }
1611
1612 #[inline]
1613 pub(crate) fn underline_position(&self, font_size: f32) -> f32 {
1614 self.underline_position as f32 * self.scale(font_size)
1615 }
1616
1617 #[inline]
1618 fn underline_thickness(&self, font_size: f32) -> f32 {
1619 self.underline_thickness.get() as f32 * self.scale(font_size)
1620 }
1621
1622 #[inline]
1623 pub(crate) fn line_through_position(&self, font_size: f32) -> f32 {
1624 self.line_through_position as f32 * self.scale(font_size)
1625 }
1626
1627 #[inline]
1628 fn subscript_offset(&self, font_size: f32) -> f32 {
1629 self.subscript_offset as f32 * self.scale(font_size)
1630 }
1631
1632 #[inline]
1633 fn superscript_offset(&self, font_size: f32) -> f32 {
1634 self.superscript_offset as f32 * self.scale(font_size)
1635 }
1636
1637 fn dominant_baseline_shift(&self, baseline: DominantBaseline, font_size: f32) -> f32 {
1638 let alignment = match baseline {
1639 DominantBaseline::Auto => AlignmentBaseline::Auto,
1640 DominantBaseline::UseScript => AlignmentBaseline::Auto, DominantBaseline::NoChange => AlignmentBaseline::Auto, DominantBaseline::ResetSize => AlignmentBaseline::Auto, DominantBaseline::Ideographic => AlignmentBaseline::Ideographic,
1644 DominantBaseline::Alphabetic => AlignmentBaseline::Alphabetic,
1645 DominantBaseline::Hanging => AlignmentBaseline::Hanging,
1646 DominantBaseline::Mathematical => AlignmentBaseline::Mathematical,
1647 DominantBaseline::Central => AlignmentBaseline::Central,
1648 DominantBaseline::Middle => AlignmentBaseline::Middle,
1649 DominantBaseline::TextAfterEdge => AlignmentBaseline::TextAfterEdge,
1650 DominantBaseline::TextBeforeEdge => AlignmentBaseline::TextBeforeEdge,
1651 };
1652
1653 self.alignment_baseline_shift(alignment, font_size)
1654 }
1655
1656 fn alignment_baseline_shift(&self, alignment: AlignmentBaseline, font_size: f32) -> f32 {
1686 match alignment {
1687 AlignmentBaseline::Auto => 0.0,
1688 AlignmentBaseline::Baseline => 0.0,
1689 AlignmentBaseline::BeforeEdge | AlignmentBaseline::TextBeforeEdge => {
1690 self.ascent(font_size)
1691 }
1692 AlignmentBaseline::Middle => self.x_height(font_size) * 0.5,
1693 AlignmentBaseline::Central => self.ascent(font_size) - self.height(font_size) * 0.5,
1694 AlignmentBaseline::AfterEdge | AlignmentBaseline::TextAfterEdge => {
1695 self.descent(font_size)
1696 }
1697 AlignmentBaseline::Ideographic => self.descent(font_size),
1698 AlignmentBaseline::Alphabetic => 0.0,
1699 AlignmentBaseline::Hanging => self.ascent(font_size) * 0.8,
1700 AlignmentBaseline::Mathematical => self.ascent(font_size) * 0.5,
1701 }
1702 }
1703}
1704
1705pub(crate) type FontsCache = HashMap<Font, Arc<ResolvedFont>>;
1706
1707#[derive(Clone, Copy, PartialEq, Debug)]
1711pub(crate) struct ByteIndex(usize);
1712
1713impl ByteIndex {
1714 fn new(i: usize) -> Self {
1715 ByteIndex(i)
1716 }
1717
1718 pub(crate) fn value(&self) -> usize {
1719 self.0
1720 }
1721
1722 pub(crate) fn code_point_at(&self, text: &str) -> usize {
1724 text.char_indices()
1725 .take_while(|(i, _)| *i != self.0)
1726 .count()
1727 }
1728
1729 pub(crate) fn char_from(&self, text: &str) -> char {
1731 text[self.0..].chars().next().unwrap()
1732 }
1733}