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