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}