1use std::collections::{HashMap, HashSet};
5use std::num::NonZeroU16;
6use std::sync::Arc;
7
8use fontdb::{Database, ID};
9use harfrust::ShapeOptions;
10use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv};
11use skrifa::MetadataProvider;
12use skrifa::Tag;
13use skrifa::prelude::Size;
14use skrifa::raw::TableProvider;
15use strict_num::NonZeroPositiveF32;
16use tiny_skia_path::{NonZeroRect, Transform};
17use unicode_script::UnicodeScript;
18
19use crate::tree::{BBox, IsValidLength};
20use crate::{
21 AlignmentBaseline, ApproxZeroUlps, BaselineShift, DominantBaseline, Fill, FillRule, Font,
22 FontResolver, GlyphId, LengthAdjust, PaintOrder, Path, ShapeRendering, Stroke, Text,
23 TextAnchor, TextChunk, TextDecorationStyle, TextFlow, TextPath, TextSpan, WritingMode,
24};
25
26#[derive(Clone, Debug)]
31pub struct PositionedGlyph {
32 glyph_ts: Transform,
36 cluster_ts: Transform,
38 span_ts: Transform,
40 units_per_em: u16,
42 font_size: f32,
44 pub id: GlyphId,
46 pub text: String,
48 pub font: ID,
51}
52
53impl PositionedGlyph {
54 pub fn font_size(&self) -> f32 {
56 self.font_size
57 }
58
59 pub fn transform(&self) -> Transform {
61 let sx = self.font_size / self.units_per_em as f32;
62
63 self.span_ts
64 .pre_concat(self.cluster_ts)
65 .pre_concat(Transform::from_scale(sx, sx))
66 .pre_concat(self.glyph_ts)
67 }
68
69 pub fn outline_transform(&self) -> Transform {
72 self.transform()
74 .pre_concat(Transform::from_scale(1.0, -1.0))
75 }
76
77 pub fn cbdt_transform(&self, x: f32, y: f32, pixels_per_em: f32) -> Transform {
80 self.transform()
81 .pre_concat(Transform::from_scale(
82 self.units_per_em as f32 / pixels_per_em,
83 self.units_per_em as f32 / pixels_per_em,
84 ))
85 .pre_translate(x, -y)
89 }
90
91 pub fn sbix_transform(
94 &self,
95 x: f32,
96 y: f32,
97 x_min: f32,
98 y_min: f32,
99 pixels_per_em: f32,
100 height: f32,
101 ) -> Transform {
102 let bbox_x_shift = -x_min;
104
105 let bbox_y_shift = if y_min.approx_zero_ulps(4) {
106 0.128 * self.units_per_em as f32
117 } else {
118 -y_min
119 };
120
121 self.transform()
122 .pre_concat(Transform::from_translate(bbox_x_shift, bbox_y_shift))
123 .pre_concat(Transform::from_scale(
124 self.units_per_em as f32 / pixels_per_em,
125 self.units_per_em as f32 / pixels_per_em,
126 ))
127 .pre_translate(x, -height - y)
131 }
132
133 pub fn svg_transform(&self) -> Transform {
136 self.transform()
137 }
138
139 pub fn colr_transform(&self) -> Transform {
142 self.outline_transform()
143 }
144}
145
146#[derive(Clone, Debug)]
149pub struct Span {
150 pub fill: Option<Fill>,
152 pub stroke: Option<Stroke>,
154 pub paint_order: PaintOrder,
156 pub font_size: NonZeroPositiveF32,
158 pub variations: Vec<crate::FontVariation>,
160 pub font_optical_sizing: crate::FontOpticalSizing,
162 pub visible: bool,
164 pub positioned_glyphs: Vec<PositionedGlyph>,
166 pub underline: Option<Path>,
169 pub overline: Option<Path>,
172 pub line_through: Option<Path>,
175}
176
177#[derive(Clone, Debug)]
178struct GlyphCluster {
179 byte_idx: ByteIndex,
180 codepoint: char,
181 width: f32,
182 advance: f32,
183 text_length_spacing: f32,
184 ascent: f32,
185 descent: f32,
186 has_relative_shift: bool,
187 glyphs: Vec<PositionedGlyph>,
188 transform: Transform,
189 path_transform: Transform,
190 visible: bool,
191}
192
193impl GlyphCluster {
194 pub(crate) fn height(&self) -> f32 {
195 self.ascent - self.descent
196 }
197
198 pub(crate) fn transform(&self) -> Transform {
199 self.path_transform.post_concat(self.transform)
200 }
201}
202
203pub(crate) fn layout_text(
204 text_node: &Text,
205 resolver: &FontResolver,
206 fontdb: &mut Arc<fontdb::Database>,
207) -> Option<(Vec<Span>, NonZeroRect)> {
208 let mut fonts_cache: FontsCache = HashMap::new();
209
210 for chunk in &text_node.chunks {
211 for span in &chunk.spans {
212 if !fonts_cache.contains_key(&span.font) {
213 if let Some(font) = (resolver.select_font)(&span.font, fontdb)
214 .and_then(|id| fontdb.load_font(id, &span.font.variations))
215 {
216 fonts_cache.insert(span.font.clone(), Arc::new(font));
217 }
218 }
219 }
220 }
221
222 let mut spans = vec![];
223 let mut char_offset = 0;
224 let mut last_x = 0.0;
225 let mut last_y = 0.0;
226 let mut bbox = BBox::default();
227 for chunk in &text_node.chunks {
228 let (x, y) = match chunk.text_flow {
229 TextFlow::Linear => (chunk.x.unwrap_or(last_x), chunk.y.unwrap_or(last_y)),
230 TextFlow::Path(_) => (0.0, 0.0),
231 };
232
233 let mut clusters = process_chunk(chunk, &fonts_cache, resolver, fontdb);
234 if clusters.is_empty() {
235 char_offset += chunk.text.chars().count();
236 continue;
237 }
238
239 apply_writing_mode(text_node.writing_mode, &mut clusters);
240 apply_letter_spacing(chunk, &mut clusters);
241 apply_word_spacing(chunk, &mut clusters);
242
243 apply_length_adjust(chunk, &mut clusters);
244 let mut curr_pos = resolve_clusters_positions(
245 text_node,
246 chunk,
247 char_offset,
248 text_node.writing_mode,
249 &fonts_cache,
250 &mut clusters,
251 );
252
253 let mut text_ts = Transform::default();
254 if text_node.writing_mode == WritingMode::TopToBottom {
255 if let TextFlow::Linear = chunk.text_flow {
256 text_ts = text_ts.pre_rotate_at(90.0, x, y);
257 }
258 }
259
260 for span in &chunk.spans {
261 let font = match fonts_cache.get(&span.font) {
262 Some(v) => v,
263 None => continue,
264 };
265
266 let decoration_spans = collect_decoration_spans(span, &clusters);
267
268 let mut span_ts = text_ts;
269 span_ts = span_ts.pre_translate(x, y);
270 if let TextFlow::Linear = chunk.text_flow {
271 let shift = resolve_baseline(span, font, text_node.writing_mode);
272
273 span_ts = span_ts.pre_translate(0.0, shift);
277 }
278
279 let mut underline = None;
280 let mut overline = None;
281 let mut line_through = None;
282
283 if let Some(decoration) = span.decoration.underline.clone() {
284 let offset = match text_node.writing_mode {
289 WritingMode::LeftToRight => -font.underline_position(span.font_size.get()),
290 WritingMode::TopToBottom => font.height(span.font_size.get()) / 2.0,
291 };
292
293 if let Some(path) =
294 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
295 {
296 bbox = bbox.expand(path.data.bounds());
297 underline = Some(path);
298 }
299 }
300
301 if let Some(decoration) = span.decoration.overline.clone() {
302 let offset = match text_node.writing_mode {
303 WritingMode::LeftToRight => -font.ascent(span.font_size.get()),
304 WritingMode::TopToBottom => -font.height(span.font_size.get()) / 2.0,
305 };
306
307 if let Some(path) =
308 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
309 {
310 bbox = bbox.expand(path.data.bounds());
311 overline = Some(path);
312 }
313 }
314
315 if let Some(decoration) = span.decoration.line_through.clone() {
316 let offset = match text_node.writing_mode {
317 WritingMode::LeftToRight => -font.line_through_position(span.font_size.get()),
318 WritingMode::TopToBottom => 0.0,
319 };
320
321 if let Some(path) =
322 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
323 {
324 bbox = bbox.expand(path.data.bounds());
325 line_through = Some(path);
326 }
327 }
328
329 let mut fill = span.fill.clone();
330 if let Some(ref mut fill) = fill {
331 fill.rule = FillRule::NonZero;
337 }
338
339 if let Some((span_fragments, span_bbox)) = convert_span(span, &clusters, span_ts) {
340 bbox = bbox.expand(span_bbox);
341
342 let positioned_glyphs = span_fragments
343 .into_iter()
344 .flat_map(|mut gc| {
345 let cluster_ts = gc.transform();
346 gc.glyphs.iter_mut().for_each(|pg| {
347 pg.cluster_ts = cluster_ts;
348 pg.span_ts = span_ts;
349 });
350 gc.glyphs
351 })
352 .collect();
353
354 spans.push(Span {
355 fill,
356 stroke: span.stroke.clone(),
357 paint_order: span.paint_order,
358 font_size: span.font_size,
359 variations: span.font.variations.clone(),
360 font_optical_sizing: span.font_optical_sizing,
361 visible: span.visible,
362 positioned_glyphs,
363 underline,
364 overline,
365 line_through,
366 });
367 }
368 }
369
370 char_offset += chunk.text.chars().count();
371
372 if text_node.writing_mode == WritingMode::TopToBottom {
373 if let TextFlow::Linear = chunk.text_flow {
374 std::mem::swap(&mut curr_pos.0, &mut curr_pos.1);
375 }
376 }
377
378 last_x = x + curr_pos.0;
379 last_y = y + curr_pos.1;
380 }
381
382 let bbox = bbox.to_non_zero_rect()?;
383
384 Some((spans, bbox))
385}
386
387fn convert_span(
388 span: &TextSpan,
389 clusters: &[GlyphCluster],
390 text_ts: Transform,
391) -> Option<(Vec<GlyphCluster>, NonZeroRect)> {
392 let mut span_clusters = vec![];
393 let mut bboxes_builder = tiny_skia_path::PathBuilder::new();
394
395 for cluster in clusters {
396 if !cluster.visible {
397 continue;
398 }
399
400 if span_contains(span, cluster.byte_idx) {
401 span_clusters.push(cluster.clone());
402 }
403
404 let mut advance = cluster.advance;
405 if advance <= 0.0 {
406 advance = 1.0;
407 }
408
409 if let Some(r) = NonZeroRect::from_xywh(0.0, -cluster.ascent, advance, cluster.height()) {
411 if let Some(r) = r.transform(cluster.transform()) {
412 bboxes_builder.push_rect(r.to_rect());
413 }
414 }
415 }
416
417 let mut bboxes = bboxes_builder.finish()?;
418 bboxes = bboxes.transform(text_ts)?;
419 let bbox = bboxes.compute_tight_bounds()?.to_non_zero_rect()?;
420
421 Some((span_clusters, bbox))
422}
423
424fn collect_decoration_spans(span: &TextSpan, clusters: &[GlyphCluster]) -> Vec<DecorationSpan> {
425 let mut spans = Vec::new();
426
427 let mut started = false;
428 let mut width = 0.0;
429 let mut transform = Transform::default();
430
431 for cluster in clusters {
432 if span_contains(span, cluster.byte_idx) {
433 if started && cluster.has_relative_shift {
434 started = false;
435 spans.push(DecorationSpan { width, transform });
436 }
437
438 if !started {
439 width = cluster.advance;
440 started = true;
441 transform = cluster.transform;
442 } else {
443 width += cluster.advance;
444 }
445 } else if started {
446 spans.push(DecorationSpan { width, transform });
447 started = false;
448 }
449 }
450
451 if started {
452 spans.push(DecorationSpan { width, transform });
453 }
454
455 spans
456}
457
458pub(crate) fn convert_decoration(
459 dy: f32,
460 span: &TextSpan,
461 font: &ResolvedFont,
462 mut decoration: TextDecorationStyle,
463 decoration_spans: &[DecorationSpan],
464 transform: Transform,
465) -> Option<Path> {
466 debug_assert!(!decoration_spans.is_empty());
467
468 let thickness = font.underline_thickness(span.font_size.get());
469
470 let mut builder = tiny_skia_path::PathBuilder::new();
471 for dec_span in decoration_spans {
472 let rect = match NonZeroRect::from_xywh(0.0, -thickness / 2.0, dec_span.width, thickness) {
473 Some(v) => v,
474 None => {
475 log::warn!("a decoration span has a malformed bbox");
476 continue;
477 }
478 };
479
480 let ts = dec_span.transform.pre_translate(0.0, dy);
481
482 let mut path = tiny_skia_path::PathBuilder::from_rect(rect.to_rect());
483 path = match path.transform(ts) {
484 Some(v) => v,
485 None => continue,
486 };
487
488 builder.push_path(&path);
489 }
490
491 let mut path_data = builder.finish()?;
492 path_data = path_data.transform(transform)?;
493
494 Path::new(
495 String::new(),
496 span.visible,
497 decoration.fill.take(),
498 decoration.stroke.take(),
499 PaintOrder::default(),
500 ShapeRendering::default(),
501 Arc::new(path_data),
502 Transform::default(),
503 )
504}
505
506#[derive(Clone, Copy)]
511pub(crate) struct DecorationSpan {
512 pub(crate) width: f32,
513 pub(crate) transform: Transform,
514}
515
516fn resolve_clusters_positions(
522 text: &Text,
523 chunk: &TextChunk,
524 char_offset: usize,
525 writing_mode: WritingMode,
526 fonts_cache: &FontsCache,
527 clusters: &mut [GlyphCluster],
528) -> (f32, f32) {
529 match chunk.text_flow {
530 TextFlow::Linear => {
531 resolve_clusters_positions_horizontal(text, chunk, char_offset, writing_mode, clusters)
532 }
533 TextFlow::Path(ref path) => resolve_clusters_positions_path(
534 text,
535 chunk,
536 char_offset,
537 path,
538 writing_mode,
539 fonts_cache,
540 clusters,
541 ),
542 }
543}
544
545fn clusters_length(clusters: &[GlyphCluster]) -> f32 {
546 clusters.iter().fold(0.0, |w, cluster| w + cluster.advance)
547}
548
549fn resolve_relative_shifts(
550 text: &Text,
551 chunk: &TextChunk,
552 offset: usize,
553 clusters: &[GlyphCluster],
554) -> (Vec<(f32, f32)>, Vec<usize>) {
555 let mut logical_order: Vec<usize> = (0..clusters.len()).collect();
558 logical_order.sort_by_key(|&i| clusters[i].byte_idx.value());
559
560 let mut shifts = vec![(0.0, 0.0); clusters.len()];
561 let (mut shift_x, mut shift_y) = (0.0, 0.0);
562 for &i in &logical_order {
563 let cp = offset + clusters[i].byte_idx.code_point_at(&chunk.text);
564 shift_x += text.dx.get(cp).cloned().unwrap_or(0.0);
565 shift_y += text.dy.get(cp).cloned().unwrap_or(0.0);
566 shifts[i] = (shift_x, shift_y);
567 }
568
569 (shifts, logical_order)
570}
571
572fn resolve_clusters_positions_horizontal(
573 text: &Text,
574 chunk: &TextChunk,
575 offset: usize,
576 writing_mode: WritingMode,
577 clusters: &mut [GlyphCluster],
578) -> (f32, f32) {
579 let (mut shifts, logical_order) = resolve_relative_shifts(text, chunk, offset, clusters);
580 let first = logical_order[0];
581 let last = logical_order[logical_order.len() - 1];
582
583 if writing_mode == WritingMode::TopToBottom {
585 for shift in &mut shifts {
586 *shift = (shift.1, -shift.0);
587 }
588 }
589
590 for i in 1..clusters.len() {
591 if shifts[i] != shifts[i - 1] {
592 clusters[i].has_relative_shift = true;
593 }
594 }
595
596 let mut left = f32::INFINITY;
597 let mut right = f32::NEG_INFINITY;
598 let mut advance = 0.0;
599 for (cluster, (shift_x, _)) in clusters.iter().zip(&shifts) {
600 let start = advance + shift_x;
601 let end = start + cluster.advance - cluster.text_length_spacing;
602 left = left.min(start).min(end);
603 right = right.max(start).max(end);
604 advance += cluster.advance;
605 }
606
607 let mut x = shifts[first].0 - left + process_anchor(chunk.anchor, right - left);
608
609 for (cluster, (shift_x, shift_y)) in clusters.iter_mut().zip(&shifts) {
610 let cp = offset + cluster.byte_idx.code_point_at(&chunk.text);
611
612 cluster.transform = cluster.transform.pre_translate(x + shift_x, *shift_y);
613
614 if let Some(angle) = text.rotate.get(cp).cloned() {
615 if !angle.approx_zero_ulps(4) {
616 cluster.transform = cluster.transform.pre_rotate(angle);
617 cluster.has_relative_shift = true;
618 }
619 }
620
621 x += cluster.advance;
622 }
623
624 (x + shifts[last].0, shifts[last].1)
625}
626
627pub(crate) fn resolve_baseline(
636 span: &TextSpan,
637 font: &ResolvedFont,
638 writing_mode: WritingMode,
639) -> f32 {
640 let mut shift = -resolve_baseline_shift(&span.baseline_shift, font, span.font_size.get());
641
642 if writing_mode == WritingMode::LeftToRight {
644 if span.alignment_baseline == AlignmentBaseline::Auto
645 || span.alignment_baseline == AlignmentBaseline::Baseline
646 {
647 shift += font.dominant_baseline_shift(span.dominant_baseline, span.font_size.get());
648 } else {
649 shift += font.alignment_baseline_shift(span.alignment_baseline, span.font_size.get());
650 }
651 }
652
653 shift
654}
655
656fn resolve_baseline_shift(baselines: &[BaselineShift], font: &ResolvedFont, font_size: f32) -> f32 {
657 let mut shift = 0.0;
658 for baseline in baselines.iter().rev() {
659 match baseline {
660 BaselineShift::Baseline => {}
661 BaselineShift::Subscript => shift -= font.subscript_offset(font_size),
662 BaselineShift::Superscript => shift += font.superscript_offset(font_size),
663 BaselineShift::Number(n) => shift += n,
664 }
665 }
666
667 shift
668}
669
670fn resolve_clusters_positions_path(
671 text: &Text,
672 chunk: &TextChunk,
673 char_offset: usize,
674 path: &TextPath,
675 writing_mode: WritingMode,
676 fonts_cache: &FontsCache,
677 clusters: &mut [GlyphCluster],
678) -> (f32, f32) {
679 let mut last_x = 0.0;
680 let mut last_y = 0.0;
681
682 let (shifts, _) = resolve_relative_shifts(text, chunk, char_offset, clusters);
683
684 let chunk_offset = match writing_mode {
687 WritingMode::LeftToRight => chunk.x.unwrap_or(0.0),
688 WritingMode::TopToBottom => chunk.y.unwrap_or(0.0),
689 };
690
691 let start_offset =
692 chunk_offset + path.start_offset + process_anchor(chunk.anchor, clusters_length(clusters));
693
694 let normals = collect_normals(text, clusters, &shifts, &path.path, start_offset);
695 for ((cluster, normal), (_, dy)) in clusters.iter_mut().zip(normals).zip(shifts) {
696 let (x, y, angle) = match normal {
697 Some(normal) => (normal.x, normal.y, normal.angle),
698 None => {
699 cluster.visible = false;
701 continue;
702 }
703 };
704
705 cluster.has_relative_shift = true;
707
708 let orig_ts = cluster.transform;
709
710 let half_width = cluster.width / 2.0;
712 cluster.transform = Transform::default();
713 cluster.transform = cluster.transform.pre_translate(x - half_width, y);
714 cluster.transform = cluster.transform.pre_rotate_at(angle, half_width, 0.0);
715
716 let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);
717
718 let baseline_shift = chunk_span_at(chunk, cluster.byte_idx)
719 .map(|span| {
720 let font = match fonts_cache.get(&span.font) {
721 Some(v) => v,
722 None => return 0.0,
723 };
724 -resolve_baseline(span, font, writing_mode)
725 })
726 .unwrap_or(0.0);
727
728 if !dy.approx_zero_ulps(4) || !baseline_shift.approx_zero_ulps(4) {
731 let shift = kurbo::Vec2::new(0.0, (dy - baseline_shift) as f64);
732 cluster.transform = cluster
733 .transform
734 .pre_translate(shift.x as f32, shift.y as f32);
735 }
736
737 if let Some(angle) = text.rotate.get(cp).cloned() {
738 if !angle.approx_zero_ulps(4) {
739 cluster.transform = cluster.transform.pre_rotate(angle);
740 }
741 }
742
743 cluster.transform = cluster.transform.pre_concat(orig_ts);
745
746 last_x = x + cluster.advance;
747 last_y = y;
748 }
749
750 (last_x, last_y)
751}
752
753pub(crate) fn process_anchor(a: TextAnchor, text_width: f32) -> f32 {
754 match a {
755 TextAnchor::Start => 0.0, TextAnchor::Middle => -text_width / 2.0,
757 TextAnchor::End => -text_width,
758 }
759}
760
761pub(crate) struct PathNormal {
762 pub(crate) x: f32,
763 pub(crate) y: f32,
764 pub(crate) angle: f32,
765}
766
767fn collect_normals(
768 text: &Text,
769 clusters: &[GlyphCluster],
770 shifts: &[(f32, f32)],
771 path: &tiny_skia_path::Path,
772 offset: f32,
773) -> Vec<Option<PathNormal>> {
774 let mut offsets = Vec::with_capacity(clusters.len());
775 let mut normals = Vec::with_capacity(clusters.len());
776 {
777 let mut advance = offset;
778 for (cluster, (shift_x, _)) in clusters.iter().zip(shifts) {
779 let half_width = cluster.width / 2.0;
781
782 let offset = advance + shift_x + half_width;
783
784 if offset < 0.0 {
786 normals.push(None);
787 }
788
789 offsets.push(offset as f64);
790 advance += cluster.advance;
791 }
792 }
793
794 let mut prev_mx = path.points()[0].x;
795 let mut prev_my = path.points()[0].y;
796 let mut prev_x = prev_mx;
797 let mut prev_y = prev_my;
798
799 fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez {
800 let line = kurbo::Line::new(
801 kurbo::Point::new(px as f64, py as f64),
802 kurbo::Point::new(x as f64, y as f64),
803 );
804 let p1 = line.eval(0.33);
805 let p2 = line.eval(0.66);
806 kurbo::CubicBez {
807 p0: line.p0,
808 p1,
809 p2,
810 p3: line.p1,
811 }
812 }
813
814 let mut length: f64 = 0.0;
815 for seg in path.segments() {
816 let curve = match seg {
817 tiny_skia_path::PathSegment::MoveTo(p) => {
818 prev_mx = p.x;
819 prev_my = p.y;
820 prev_x = p.x;
821 prev_y = p.y;
822 continue;
823 }
824 tiny_skia_path::PathSegment::LineTo(p) => {
825 create_curve_from_line(prev_x, prev_y, p.x, p.y)
826 }
827 tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez {
828 p0: kurbo::Point::new(prev_x as f64, prev_y as f64),
829 p1: kurbo::Point::new(p1.x as f64, p1.y as f64),
830 p2: kurbo::Point::new(p.x as f64, p.y as f64),
831 }
832 .raise(),
833 tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez {
834 p0: kurbo::Point::new(prev_x as f64, prev_y as f64),
835 p1: kurbo::Point::new(p1.x as f64, p1.y as f64),
836 p2: kurbo::Point::new(p2.x as f64, p2.y as f64),
837 p3: kurbo::Point::new(p.x as f64, p.y as f64),
838 },
839 tiny_skia_path::PathSegment::Close => {
840 create_curve_from_line(prev_x, prev_y, prev_mx, prev_my)
841 }
842 };
843
844 let arclen_accuracy = {
845 let base_arclen_accuracy = 0.5;
846 let (sx, sy) = text.abs_transform.get_scale();
850 base_arclen_accuracy / (sx * sy).sqrt().max(1.0)
852 };
853
854 let curve_len = curve.arclen(arclen_accuracy as f64);
855
856 for offset in &offsets[normals.len()..] {
857 if *offset >= length && *offset <= length + curve_len {
858 let mut offset = curve.inv_arclen(offset - length, arclen_accuracy as f64);
859 debug_assert!((-1.0e-3..=1.0 + 1.0e-3).contains(&offset));
861 offset = offset.clamp(0.0, 1.0);
862
863 let pos = curve.eval(offset);
864 let d = curve.deriv().eval(offset);
865 let d = kurbo::Vec2::new(-d.y, d.x); let angle = d.atan2().to_degrees() - 90.0;
867
868 normals.push(Some(PathNormal {
869 x: pos.x as f32,
870 y: pos.y as f32,
871 angle: angle as f32,
872 }));
873
874 if normals.len() == offsets.len() {
875 break;
876 }
877 }
878 }
879
880 length += curve_len;
881 prev_x = curve.p3.x as f32;
882 prev_y = curve.p3.y as f32;
883 }
884
885 for _ in 0..(offsets.len() - normals.len()) {
887 normals.push(None);
888 }
889
890 normals
891}
892
893fn process_chunk(
898 chunk: &TextChunk,
899 fonts_cache: &FontsCache,
900 resolver: &FontResolver,
901 fontdb: &mut Arc<fontdb::Database>,
902) -> Vec<GlyphCluster> {
903 let mut positions = HashSet::new();
938
939 let mut glyphs = Vec::new();
940 for span in &chunk.spans {
941 let font = match fonts_cache.get(&span.font) {
942 Some(v) => v.clone(),
943 None => continue,
944 };
945
946 let tmp_glyphs = shape_text(
947 &chunk.text,
948 font,
949 span.small_caps,
950 span.apply_kerning,
951 &span.font.variations,
952 span.font_size.get(),
953 span.font_optical_sizing,
954 resolver,
955 fontdb,
956 );
957
958 if glyphs.is_empty() {
960 glyphs = tmp_glyphs;
961 continue;
962 }
963
964 positions.clear();
965
966 let mut iter = tmp_glyphs.into_iter();
968 while let Some(new_glyph) = iter.next() {
969 if !span_contains(span, new_glyph.byte_idx) {
970 continue;
971 }
972
973 let Some(idx) = glyphs
974 .iter()
975 .position(|g| g.byte_idx == new_glyph.byte_idx)
976 .filter(|pos| !positions.contains(pos))
977 else {
978 continue;
979 };
980
981 positions.insert(idx);
982
983 let prev_cluster_len = glyphs[idx].cluster_len;
984 if prev_cluster_len < new_glyph.cluster_len {
985 for _ in 1..new_glyph.cluster_len {
988 glyphs.remove(idx + 1);
989 }
990 } else if prev_cluster_len > new_glyph.cluster_len {
991 for j in 1..prev_cluster_len {
994 if let Some(g) = iter.next() {
995 glyphs.insert(idx + j, g);
996 }
997 }
998 }
999
1000 glyphs[idx] = new_glyph;
1001 }
1002 }
1003
1004 let mut clusters = Vec::new();
1006 for (range, byte_idx) in GlyphClusters::new(&glyphs) {
1007 if let Some(span) = chunk_span_at(chunk, byte_idx) {
1008 clusters.push(form_glyph_clusters(
1009 &glyphs[range],
1010 &chunk.text,
1011 span.font_size.get(),
1012 ));
1013 }
1014 }
1015
1016 clusters
1017}
1018
1019fn apply_length_adjust(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1020 let is_horizontal = matches!(chunk.text_flow, TextFlow::Linear);
1021
1022 for span in &chunk.spans {
1023 let target_width = match span.text_length {
1024 Some(v) => v,
1025 None => continue,
1026 };
1027
1028 let mut width = 0.0;
1029 let mut cluster_indexes = Vec::new();
1030 for i in span.start..span.end {
1031 if let Some(index) = clusters.iter().position(|c| c.byte_idx.value() == i) {
1032 cluster_indexes.push(index);
1033 }
1034 }
1035 cluster_indexes.sort();
1037 cluster_indexes.dedup();
1038
1039 for i in &cluster_indexes {
1040 width += clusters[*i].width;
1043 }
1044
1045 if cluster_indexes.is_empty() {
1046 continue;
1047 }
1048
1049 if span.length_adjust == LengthAdjust::Spacing {
1050 let factor = if cluster_indexes.len() > 1 {
1051 (target_width - width) / (cluster_indexes.len() - 1) as f32
1052 } else {
1053 0.0
1054 };
1055
1056 for i in cluster_indexes {
1057 clusters[i].advance = clusters[i].width + factor;
1058 clusters[i].text_length_spacing = factor;
1059 }
1060 } else {
1061 let factor = target_width / width;
1062 if factor < 0.001 {
1064 continue;
1065 }
1066
1067 for i in cluster_indexes {
1068 clusters[i].transform = clusters[i].transform.pre_scale(factor, 1.0);
1069
1070 if !is_horizontal {
1072 clusters[i].advance *= factor;
1073 clusters[i].width *= factor;
1074 }
1075 }
1076 }
1077 }
1078}
1079
1080fn apply_writing_mode(writing_mode: WritingMode, clusters: &mut [GlyphCluster]) {
1083 if writing_mode != WritingMode::TopToBottom {
1084 return;
1085 }
1086
1087 for cluster in clusters {
1088 let orientation = unicode_vo::char_orientation(cluster.codepoint);
1089 if orientation == unicode_vo::Orientation::Upright {
1090 let mut ts = Transform::default();
1091 ts = ts.pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);
1093 ts = ts.pre_rotate_at(
1095 -90.0,
1096 cluster.width / 2.0,
1097 -(cluster.ascent + cluster.descent) / 2.0,
1098 );
1099
1100 cluster.path_transform = ts;
1101
1102 cluster.ascent = cluster.width / 2.0;
1104 cluster.descent = -cluster.width / 2.0;
1105 } else {
1106 cluster.transform = cluster
1110 .transform
1111 .pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);
1112 }
1113 }
1114}
1115
1116fn apply_letter_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1120 if !chunk
1122 .spans
1123 .iter()
1124 .any(|span| !span.letter_spacing.approx_zero_ulps(4))
1125 {
1126 return;
1127 }
1128
1129 let num_clusters = clusters.len();
1130 for (i, cluster) in clusters.iter_mut().enumerate() {
1131 let script = cluster.codepoint.script();
1136 if script_supports_letter_spacing(script) {
1137 if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {
1138 if i != num_clusters - 1 {
1141 cluster.advance += span.letter_spacing;
1142 }
1143
1144 if !cluster.advance.is_valid_length() {
1147 cluster.width = 0.0;
1148 cluster.advance = 0.0;
1149 cluster.glyphs = vec![];
1150 }
1151 }
1152 }
1153 }
1154}
1155
1156fn apply_word_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1160 if !chunk
1162 .spans
1163 .iter()
1164 .any(|span| !span.word_spacing.approx_zero_ulps(4))
1165 {
1166 return;
1167 }
1168
1169 for cluster in clusters {
1170 if is_word_separator_characters(cluster.codepoint) {
1171 if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {
1172 cluster.advance += span.word_spacing;
1176
1177 }
1179 }
1180 }
1181}
1182
1183fn form_glyph_clusters(glyphs: &[Glyph], text: &str, font_size: f32) -> GlyphCluster {
1184 debug_assert!(!glyphs.is_empty());
1185
1186 let mut x = 0.0;
1187 let mut width = 0.0;
1188 let mut advance = 0.0;
1189
1190 let mut positioned_glyphs = vec![];
1191
1192 for glyph in glyphs {
1193 let sx = glyph.font.scale(font_size);
1194
1195 let ts = Transform::from_translate(x + glyph.dx as f32, -glyph.dy as f32);
1202
1203 positioned_glyphs.push(PositionedGlyph {
1204 glyph_ts: ts,
1205 cluster_ts: Transform::default(),
1207 span_ts: Transform::default(),
1209 units_per_em: glyph.font.units_per_em.get(),
1210 font_size,
1211 font: glyph.font.id,
1212 text: glyph.text.clone(),
1213 id: glyph.id,
1214 });
1215
1216 x += glyph.width as f32;
1217
1218 let glyph_width = glyph.width as f32 * sx;
1219 advance += glyph_width;
1220 if glyph_width > width {
1221 width = glyph_width;
1222 }
1223 }
1224
1225 let byte_idx = glyphs[0].byte_idx;
1226 let font = glyphs[0].font.clone();
1227 GlyphCluster {
1228 byte_idx,
1229 codepoint: byte_idx.char_from(text),
1230 width,
1231 advance,
1232 text_length_spacing: 0.0,
1233 ascent: font.ascent(font_size),
1234 descent: font.descent(font_size),
1235 has_relative_shift: false,
1236 transform: Transform::default(),
1237 path_transform: Transform::default(),
1238 glyphs: positioned_glyphs,
1239 visible: true,
1240 }
1241}
1242
1243pub(crate) trait DatabaseExt {
1244 fn load_font(&self, id: ID, variations: &[crate::FontVariation]) -> Option<ResolvedFont>;
1245 fn has_char(&self, id: ID, c: char) -> bool;
1246}
1247
1248impl DatabaseExt for Database {
1249 #[inline(never)]
1250 fn load_font(&self, id: ID, variations: &[crate::FontVariation]) -> Option<ResolvedFont> {
1251 self.with_face_data(id, |data, face_index| -> Option<ResolvedFont> {
1252 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
1253
1254 let location = font.axes().location(
1258 variations
1259 .iter()
1260 .map(|v| (Tag::from_be_bytes(v.tag), v.value)),
1261 );
1262 let coords = location.coords();
1263 let metrics = font.metrics(Size::unscaled(), &location);
1264
1265 if !(16..=16384).contains(&metrics.units_per_em) {
1267 return None;
1268 }
1269 let units_per_em = NonZeroU16::new(metrics.units_per_em)?;
1270
1271 let ascent = metrics.ascent;
1272 let descent = metrics.descent;
1273
1274 let x_height = metrics
1275 .x_height
1276 .filter(|x| *x > 0.0)
1277 .and_then(|x| u16::try_from(x.round() as i32).ok())
1278 .and_then(NonZeroU16::new);
1279 let x_height = match x_height {
1280 Some(height) => height,
1281 None => {
1282 u16::try_from(((ascent - descent) * 0.45).round() as i32)
1285 .ok()
1286 .and_then(NonZeroU16::new)?
1287 }
1288 };
1289
1290 let line_through = metrics.strikeout;
1291 let line_through_position = match line_through {
1292 Some(metrics) => metrics.offset.round() as i16,
1293 None => x_height.get() as i16 / 2,
1294 };
1295
1296 let (underline_position, underline_thickness) = match metrics.underline {
1297 Some(metrics) => {
1298 let thickness = u16::try_from(metrics.thickness.round() as i32)
1299 .ok()
1300 .and_then(NonZeroU16::new)
1301 .unwrap_or_else(|| NonZeroU16::new(units_per_em.get() / 12).unwrap());
1303
1304 (metrics.offset.round() as i16, thickness)
1305 }
1306 None => (
1307 -(units_per_em.get() as i16) / 9,
1308 NonZeroU16::new(units_per_em.get() / 12).unwrap(),
1309 ),
1310 };
1311
1312 let mut subscript_offset = (units_per_em.get() as f32 / 0.2).round() as i16;
1314 let mut superscript_offset = (units_per_em.get() as f32 / 0.4).round() as i16;
1315
1316 if let Ok(os2) = font.os2() {
1318 subscript_offset = os2.y_subscript_y_offset();
1319 superscript_offset = os2.y_superscript_y_offset();
1320 }
1321 if let (Ok(mvar), true) = (font.mvar(), !coords.is_empty()) {
1322 use skrifa::raw::tables::mvar::tags::*;
1323 let metric_delta =
1324 |tag| mvar.metric_delta(tag, coords).unwrap_or_default().to_f32();
1325
1326 subscript_offset += metric_delta(SBYO).round() as i16;
1327 superscript_offset += metric_delta(SPYO).round() as i16;
1328 }
1329
1330 Some(ResolvedFont {
1331 id,
1332 units_per_em,
1333 ascent: ascent.round() as i16,
1334 descent: descent.round() as i16,
1335 x_height,
1336 underline_position,
1337 underline_thickness,
1338 line_through_position,
1339 subscript_offset,
1340 superscript_offset,
1341 })
1342 })?
1343 }
1344
1345 #[inline(never)]
1346 fn has_char(&self, id: ID, c: char) -> bool {
1347 let res = self.with_face_data(id, |font_data, face_index| -> Option<bool> {
1348 let font = skrifa::FontRef::from_index(font_data, face_index).ok()?;
1349 let char_map = skrifa::charmap::Charmap::new(&font);
1350 char_map.map(c)?;
1351 Some(true)
1352 });
1353
1354 res == Some(Some(true))
1355 }
1356}
1357
1358pub(crate) fn shape_text(
1360 text: &str,
1361 font: Arc<ResolvedFont>,
1362 small_caps: bool,
1363 apply_kerning: bool,
1364 variations: &[crate::FontVariation],
1365 font_size: f32,
1366 font_optical_sizing: crate::FontOpticalSizing,
1367 resolver: &FontResolver,
1368 fontdb: &mut Arc<fontdb::Database>,
1369) -> Vec<Glyph> {
1370 let mut glyphs = shape_text_with_font(
1371 text,
1372 font.clone(),
1373 small_caps,
1374 apply_kerning,
1375 variations,
1376 font_size,
1377 font_optical_sizing,
1378 fontdb,
1379 )
1380 .unwrap_or_default();
1381
1382 let mut used_fonts = vec![font.id];
1384
1385 'outer: loop {
1387 let mut missing = None;
1388 for glyph in &glyphs {
1389 if glyph.is_missing() {
1390 missing = Some(glyph.byte_idx.char_from(text));
1391 break;
1392 }
1393 }
1394
1395 if let Some(c) = missing {
1396 let fallback_font = match (resolver.select_fallback)(c, &used_fonts, fontdb)
1397 .and_then(|id| fontdb.load_font(id, variations))
1398 {
1399 Some(v) => Arc::new(v),
1400 None => break 'outer,
1401 };
1402
1403 let fallback_glyphs = shape_text_with_font(
1405 text,
1406 fallback_font.clone(),
1407 small_caps,
1408 apply_kerning,
1409 variations,
1410 font_size,
1411 font_optical_sizing,
1412 fontdb,
1413 )
1414 .unwrap_or_default();
1415
1416 let all_matched = fallback_glyphs.iter().all(|g| !g.is_missing());
1417 if all_matched {
1418 glyphs = fallback_glyphs;
1420 break 'outer;
1421 }
1422
1423 if glyphs.len() != fallback_glyphs.len() {
1426 break 'outer;
1427 }
1428
1429 for i in 0..glyphs.len() {
1433 if glyphs[i].is_missing() && !fallback_glyphs[i].is_missing() {
1434 glyphs[i] = fallback_glyphs[i].clone();
1435 }
1436 }
1437
1438 used_fonts.push(fallback_font.id);
1440 } else {
1441 break 'outer;
1442 }
1443 }
1444
1445 for glyph in &glyphs {
1447 if glyph.is_missing() {
1448 let c = glyph.byte_idx.char_from(text);
1449 log::warn!(
1451 "No fonts with a {}/U+{:X} character were found.",
1452 c,
1453 c as u32
1454 );
1455 }
1456 }
1457
1458 glyphs
1459}
1460
1461fn shape_text_with_font(
1465 text: &str,
1466 font: Arc<ResolvedFont>,
1467 small_caps: bool,
1468 apply_kerning: bool,
1469 variations: &[crate::FontVariation],
1470 font_size: f32,
1471 font_optical_sizing: crate::FontOpticalSizing,
1472 fontdb: &fontdb::Database,
1473) -> Option<Vec<Glyph>> {
1474 fontdb.with_face_data(font.id, |font_data, face_index| -> Option<Vec<Glyph>> {
1475 use harfrust::{Feature, ShaperData, ShaperInstance, Tag, UnicodeBuffer, Variation};
1476
1477 use crate::text::OPSZ;
1478
1479 let hr_font = harfrust::FontRef::from_index(font_data, face_index).ok()?;
1480
1481 let mut variations: Vec<Variation> = variations
1483 .iter()
1484 .map(|v| Variation {
1485 tag: Tag::from_be_bytes(v.tag),
1486 value: v.value,
1487 })
1488 .collect();
1489
1490 if font_optical_sizing == crate::FontOpticalSizing::Auto {
1494 let has_explicit_opsz = variations.iter().any(|v| v.tag == *b"opsz");
1495 if !has_explicit_opsz && hr_font.axes().get_by_tag(OPSZ).is_some() {
1496 variations.push(Variation {
1497 tag: OPSZ,
1498 value: font_size,
1499 });
1500 }
1501 }
1502
1503 let bidi_info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
1504 let paragraph = &bidi_info.paragraphs[0];
1505 let line = paragraph.range.clone();
1506
1507 let mut glyphs = Vec::new();
1508
1509 let instance_data = (!variations.is_empty())
1511 .then(|| ShaperInstance::from_variations(&hr_font, &variations));
1512 let shaper_data = ShaperData::new(&hr_font);
1513 let shaper = shaper_data
1514 .shaper(&hr_font)
1515 .instance(instance_data.as_ref())
1516 .build();
1517
1518 let mut features = Vec::new();
1519 if small_caps {
1520 features.push(Feature::new(Tag::new(b"smcp"), 1, ..));
1521 }
1522 if !apply_kerning {
1523 features.push(Feature::new(Tag::new(b"kern"), 0, ..));
1524 }
1525
1526 let (levels, runs) = bidi_info.visual_runs(paragraph, line);
1527 for run in runs.iter() {
1528 let sub_text = &text[run.clone()];
1529 if sub_text.is_empty() {
1530 continue;
1531 }
1532
1533 let ltr = levels[run.start].is_ltr();
1534 let direction = if ltr {
1535 harfrust::Direction::LeftToRight
1536 } else {
1537 harfrust::Direction::RightToLeft
1538 };
1539
1540 let mut buffer = UnicodeBuffer::new();
1541 buffer.push_str(sub_text);
1542 buffer.set_direction(direction);
1543
1544 buffer.guess_segment_properties();
1546
1547 let output = shaper.shape(buffer, ShapeOptions::new().features(&features));
1548
1549 let positions = output.glyph_positions();
1550 let infos = output.glyph_infos();
1551
1552 for i in 0..output.len() {
1553 let pos = positions[i];
1554 let info = infos[i];
1555 let idx = run.start + info.cluster as usize;
1556
1557 let start = info.cluster as usize;
1558
1559 let end = if ltr {
1560 i.checked_add(1)
1561 } else {
1562 i.checked_sub(1)
1563 }
1564 .and_then(|last| infos.get(last))
1565 .map_or(sub_text.len(), |info| info.cluster as usize);
1566
1567 glyphs.push(Glyph {
1568 byte_idx: ByteIndex::new(idx),
1569 cluster_len: end.checked_sub(start).unwrap_or(0), text: sub_text[start..end].to_string(),
1571 id: GlyphId(info.glyph_id),
1572 dx: pos.x_offset,
1573 dy: pos.y_offset,
1574 width: pos.x_advance,
1575 font: font.clone(),
1576 });
1577 }
1578 }
1579
1580 Some(glyphs)
1581 })?
1582}
1583
1584pub(crate) struct GlyphClusters<'a> {
1589 data: &'a [Glyph],
1590 idx: usize,
1591}
1592
1593impl<'a> GlyphClusters<'a> {
1594 pub(crate) fn new(data: &'a [Glyph]) -> Self {
1595 GlyphClusters { data, idx: 0 }
1596 }
1597}
1598
1599impl Iterator for GlyphClusters<'_> {
1600 type Item = (std::ops::Range<usize>, ByteIndex);
1601
1602 fn next(&mut self) -> Option<Self::Item> {
1603 if self.idx == self.data.len() {
1604 return None;
1605 }
1606
1607 let start = self.idx;
1608 let cluster = self.data[self.idx].byte_idx;
1609 for g in &self.data[self.idx..] {
1610 if g.byte_idx != cluster {
1611 break;
1612 }
1613
1614 self.idx += 1;
1615 }
1616
1617 Some((start..self.idx, cluster))
1618 }
1619}
1620
1621pub(crate) fn script_supports_letter_spacing(script: unicode_script::Script) -> bool {
1627 use unicode_script::Script;
1628
1629 !matches!(
1630 script,
1631 Script::Arabic
1632 | Script::Syriac
1633 | Script::Nko
1634 | Script::Manichaean
1635 | Script::Psalter_Pahlavi
1636 | Script::Mandaic
1637 | Script::Mongolian
1638 | Script::Phags_Pa
1639 | Script::Devanagari
1640 | Script::Bengali
1641 | Script::Gurmukhi
1642 | Script::Modi
1643 | Script::Sharada
1644 | Script::Syloti_Nagri
1645 | Script::Tirhuta
1646 | Script::Ogham
1647 )
1648}
1649
1650#[derive(Clone)]
1654pub(crate) struct Glyph {
1655 pub(crate) id: GlyphId,
1657
1658 pub(crate) byte_idx: ByteIndex,
1662
1663 pub(crate) cluster_len: usize,
1665
1666 pub(crate) text: String,
1668
1669 pub(crate) dx: i32,
1671
1672 pub(crate) dy: i32,
1674
1675 pub(crate) width: i32,
1677
1678 pub(crate) font: Arc<ResolvedFont>,
1682}
1683
1684impl Glyph {
1685 fn is_missing(&self) -> bool {
1686 self.id.0 == 0
1687 }
1688}
1689
1690#[derive(Clone, Copy, Debug)]
1691pub(crate) struct ResolvedFont {
1692 pub(crate) id: ID,
1693
1694 units_per_em: NonZeroU16,
1695
1696 ascent: i16,
1698 descent: i16,
1699 x_height: NonZeroU16,
1700
1701 underline_position: i16,
1702 underline_thickness: NonZeroU16,
1703
1704 line_through_position: i16,
1708
1709 subscript_offset: i16,
1710 superscript_offset: i16,
1711}
1712
1713pub(crate) fn chunk_span_at(chunk: &TextChunk, byte_offset: ByteIndex) -> Option<&TextSpan> {
1714 chunk
1715 .spans
1716 .iter()
1717 .find(|&span| span_contains(span, byte_offset))
1718}
1719
1720pub(crate) fn span_contains(span: &TextSpan, byte_offset: ByteIndex) -> bool {
1721 byte_offset.value() >= span.start && byte_offset.value() < span.end
1722}
1723
1724pub(crate) fn is_word_separator_characters(c: char) -> bool {
1728 matches!(
1729 c as u32,
1730 0x0020 | 0x00A0 | 0x1361 | 0x010100 | 0x010101 | 0x01039F | 0x01091F
1731 )
1732}
1733
1734impl ResolvedFont {
1735 #[inline]
1736 pub(crate) fn scale(&self, font_size: f32) -> f32 {
1737 font_size / self.units_per_em.get() as f32
1738 }
1739
1740 #[inline]
1741 pub(crate) fn ascent(&self, font_size: f32) -> f32 {
1742 self.ascent as f32 * self.scale(font_size)
1743 }
1744
1745 #[inline]
1746 pub(crate) fn descent(&self, font_size: f32) -> f32 {
1747 self.descent as f32 * self.scale(font_size)
1748 }
1749
1750 #[inline]
1751 pub(crate) fn height(&self, font_size: f32) -> f32 {
1752 self.ascent(font_size) - self.descent(font_size)
1753 }
1754
1755 #[inline]
1756 pub(crate) fn x_height(&self, font_size: f32) -> f32 {
1757 self.x_height.get() as f32 * self.scale(font_size)
1758 }
1759
1760 #[inline]
1761 pub(crate) fn underline_position(&self, font_size: f32) -> f32 {
1762 self.underline_position as f32 * self.scale(font_size)
1763 }
1764
1765 #[inline]
1766 fn underline_thickness(&self, font_size: f32) -> f32 {
1767 self.underline_thickness.get() as f32 * self.scale(font_size)
1768 }
1769
1770 #[inline]
1771 pub(crate) fn line_through_position(&self, font_size: f32) -> f32 {
1772 self.line_through_position as f32 * self.scale(font_size)
1773 }
1774
1775 #[inline]
1776 fn subscript_offset(&self, font_size: f32) -> f32 {
1777 self.subscript_offset as f32 * self.scale(font_size)
1778 }
1779
1780 #[inline]
1781 fn superscript_offset(&self, font_size: f32) -> f32 {
1782 self.superscript_offset as f32 * self.scale(font_size)
1783 }
1784
1785 fn dominant_baseline_shift(&self, baseline: DominantBaseline, font_size: f32) -> f32 {
1786 let alignment = match baseline {
1787 DominantBaseline::Auto => AlignmentBaseline::Auto,
1788 DominantBaseline::UseScript => AlignmentBaseline::Auto, DominantBaseline::NoChange => AlignmentBaseline::Auto, DominantBaseline::ResetSize => AlignmentBaseline::Auto, DominantBaseline::Ideographic => AlignmentBaseline::Ideographic,
1792 DominantBaseline::Alphabetic => AlignmentBaseline::Alphabetic,
1793 DominantBaseline::Hanging => AlignmentBaseline::Hanging,
1794 DominantBaseline::Mathematical => AlignmentBaseline::Mathematical,
1795 DominantBaseline::Central => AlignmentBaseline::Central,
1796 DominantBaseline::Middle => AlignmentBaseline::Middle,
1797 DominantBaseline::TextAfterEdge => AlignmentBaseline::TextAfterEdge,
1798 DominantBaseline::TextBeforeEdge => AlignmentBaseline::TextBeforeEdge,
1799 };
1800
1801 self.alignment_baseline_shift(alignment, font_size)
1802 }
1803
1804 fn alignment_baseline_shift(&self, alignment: AlignmentBaseline, font_size: f32) -> f32 {
1834 match alignment {
1835 AlignmentBaseline::Auto => 0.0,
1836 AlignmentBaseline::Baseline => 0.0,
1837 AlignmentBaseline::BeforeEdge | AlignmentBaseline::TextBeforeEdge => {
1838 self.ascent(font_size)
1839 }
1840 AlignmentBaseline::Middle => self.x_height(font_size) * 0.5,
1841 AlignmentBaseline::Central => self.ascent(font_size) - self.height(font_size) * 0.5,
1842 AlignmentBaseline::AfterEdge | AlignmentBaseline::TextAfterEdge => {
1843 self.descent(font_size)
1844 }
1845 AlignmentBaseline::Ideographic => self.descent(font_size),
1846 AlignmentBaseline::Alphabetic => 0.0,
1847 AlignmentBaseline::Hanging => self.ascent(font_size) * 0.8,
1848 AlignmentBaseline::Mathematical => self.ascent(font_size) * 0.5,
1849 }
1850 }
1851}
1852
1853pub(crate) type FontsCache = HashMap<Font, Arc<ResolvedFont>>;
1854
1855#[derive(Clone, Copy, PartialEq, Debug)]
1859pub(crate) struct ByteIndex(usize);
1860
1861impl ByteIndex {
1862 fn new(i: usize) -> Self {
1863 ByteIndex(i)
1864 }
1865
1866 pub(crate) fn value(&self) -> usize {
1867 self.0
1868 }
1869
1870 pub(crate) fn code_point_at(&self, text: &str) -> usize {
1872 text.char_indices()
1873 .take_while(|(i, _)| *i != self.0)
1874 .count()
1875 }
1876
1877 pub(crate) fn char_from(&self, text: &str) -> char {
1879 text[self.0..].chars().next().unwrap()
1880 }
1881}