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 ascent: f32,
184 descent: f32,
185 has_relative_shift: bool,
186 glyphs: Vec<PositionedGlyph>,
187 transform: Transform,
188 path_transform: Transform,
189 visible: bool,
190}
191
192impl GlyphCluster {
193 pub(crate) fn height(&self) -> f32 {
194 self.ascent - self.descent
195 }
196
197 pub(crate) fn transform(&self) -> Transform {
198 self.path_transform.post_concat(self.transform)
199 }
200}
201
202pub(crate) fn layout_text(
203 text_node: &Text,
204 resolver: &FontResolver,
205 fontdb: &mut Arc<fontdb::Database>,
206) -> Option<(Vec<Span>, NonZeroRect)> {
207 let mut fonts_cache: FontsCache = HashMap::new();
208
209 for chunk in &text_node.chunks {
210 for span in &chunk.spans {
211 if !fonts_cache.contains_key(&span.font) {
212 if let Some(font) = (resolver.select_font)(&span.font, fontdb)
213 .and_then(|id| fontdb.load_font(id, &span.font.variations))
214 {
215 fonts_cache.insert(span.font.clone(), Arc::new(font));
216 }
217 }
218 }
219 }
220
221 let mut spans = vec![];
222 let mut char_offset = 0;
223 let mut last_x = 0.0;
224 let mut last_y = 0.0;
225 let mut bbox = BBox::default();
226 for chunk in &text_node.chunks {
227 let (x, y) = match chunk.text_flow {
228 TextFlow::Linear => (chunk.x.unwrap_or(last_x), chunk.y.unwrap_or(last_y)),
229 TextFlow::Path(_) => (0.0, 0.0),
230 };
231
232 let mut clusters = process_chunk(chunk, &fonts_cache, resolver, fontdb);
233 if clusters.is_empty() {
234 char_offset += chunk.text.chars().count();
235 continue;
236 }
237
238 apply_writing_mode(text_node.writing_mode, &mut clusters);
239 apply_letter_spacing(chunk, &mut clusters);
240 apply_word_spacing(chunk, &mut clusters);
241
242 apply_length_adjust(chunk, &mut clusters);
243 let mut curr_pos = resolve_clusters_positions(
244 text_node,
245 chunk,
246 char_offset,
247 text_node.writing_mode,
248 &fonts_cache,
249 &mut clusters,
250 );
251
252 let mut text_ts = Transform::default();
253 if text_node.writing_mode == WritingMode::TopToBottom {
254 if let TextFlow::Linear = chunk.text_flow {
255 text_ts = text_ts.pre_rotate_at(90.0, x, y);
256 }
257 }
258
259 for span in &chunk.spans {
260 let font = match fonts_cache.get(&span.font) {
261 Some(v) => v,
262 None => continue,
263 };
264
265 let decoration_spans = collect_decoration_spans(span, &clusters);
266
267 let mut span_ts = text_ts;
268 span_ts = span_ts.pre_translate(x, y);
269 if let TextFlow::Linear = chunk.text_flow {
270 let shift = resolve_baseline(span, font, text_node.writing_mode);
271
272 span_ts = span_ts.pre_translate(0.0, shift);
276 }
277
278 let mut underline = None;
279 let mut overline = None;
280 let mut line_through = None;
281
282 if let Some(decoration) = span.decoration.underline.clone() {
283 let offset = match text_node.writing_mode {
288 WritingMode::LeftToRight => -font.underline_position(span.font_size.get()),
289 WritingMode::TopToBottom => font.height(span.font_size.get()) / 2.0,
290 };
291
292 if let Some(path) =
293 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
294 {
295 bbox = bbox.expand(path.data.bounds());
296 underline = Some(path);
297 }
298 }
299
300 if let Some(decoration) = span.decoration.overline.clone() {
301 let offset = match text_node.writing_mode {
302 WritingMode::LeftToRight => -font.ascent(span.font_size.get()),
303 WritingMode::TopToBottom => -font.height(span.font_size.get()) / 2.0,
304 };
305
306 if let Some(path) =
307 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
308 {
309 bbox = bbox.expand(path.data.bounds());
310 overline = Some(path);
311 }
312 }
313
314 if let Some(decoration) = span.decoration.line_through.clone() {
315 let offset = match text_node.writing_mode {
316 WritingMode::LeftToRight => -font.line_through_position(span.font_size.get()),
317 WritingMode::TopToBottom => 0.0,
318 };
319
320 if let Some(path) =
321 convert_decoration(offset, span, font, decoration, &decoration_spans, span_ts)
322 {
323 bbox = bbox.expand(path.data.bounds());
324 line_through = Some(path);
325 }
326 }
327
328 let mut fill = span.fill.clone();
329 if let Some(ref mut fill) = fill {
330 fill.rule = FillRule::NonZero;
336 }
337
338 if let Some((span_fragments, span_bbox)) = convert_span(span, &clusters, span_ts) {
339 bbox = bbox.expand(span_bbox);
340
341 let positioned_glyphs = span_fragments
342 .into_iter()
343 .flat_map(|mut gc| {
344 let cluster_ts = gc.transform();
345 gc.glyphs.iter_mut().for_each(|pg| {
346 pg.cluster_ts = cluster_ts;
347 pg.span_ts = span_ts;
348 });
349 gc.glyphs
350 })
351 .collect();
352
353 spans.push(Span {
354 fill,
355 stroke: span.stroke.clone(),
356 paint_order: span.paint_order,
357 font_size: span.font_size,
358 variations: span.font.variations.clone(),
359 font_optical_sizing: span.font_optical_sizing,
360 visible: span.visible,
361 positioned_glyphs,
362 underline,
363 overline,
364 line_through,
365 });
366 }
367 }
368
369 char_offset += chunk.text.chars().count();
370
371 if text_node.writing_mode == WritingMode::TopToBottom {
372 if let TextFlow::Linear = chunk.text_flow {
373 std::mem::swap(&mut curr_pos.0, &mut curr_pos.1);
374 }
375 }
376
377 last_x = x + curr_pos.0;
378 last_y = y + curr_pos.1;
379 }
380
381 let bbox = bbox.to_non_zero_rect()?;
382
383 Some((spans, bbox))
384}
385
386fn convert_span(
387 span: &TextSpan,
388 clusters: &[GlyphCluster],
389 text_ts: Transform,
390) -> Option<(Vec<GlyphCluster>, NonZeroRect)> {
391 let mut span_clusters = vec![];
392 let mut bboxes_builder = tiny_skia_path::PathBuilder::new();
393
394 for cluster in clusters {
395 if !cluster.visible {
396 continue;
397 }
398
399 if span_contains(span, cluster.byte_idx) {
400 span_clusters.push(cluster.clone());
401 }
402
403 let mut advance = cluster.advance;
404 if advance <= 0.0 {
405 advance = 1.0;
406 }
407
408 if let Some(r) = NonZeroRect::from_xywh(0.0, -cluster.ascent, advance, cluster.height()) {
410 if let Some(r) = r.transform(cluster.transform()) {
411 bboxes_builder.push_rect(r.to_rect());
412 }
413 }
414 }
415
416 let mut bboxes = bboxes_builder.finish()?;
417 bboxes = bboxes.transform(text_ts)?;
418 let bbox = bboxes.compute_tight_bounds()?.to_non_zero_rect()?;
419
420 Some((span_clusters, bbox))
421}
422
423fn collect_decoration_spans(span: &TextSpan, clusters: &[GlyphCluster]) -> Vec<DecorationSpan> {
424 let mut spans = Vec::new();
425
426 let mut started = false;
427 let mut width = 0.0;
428 let mut transform = Transform::default();
429
430 for cluster in clusters {
431 if span_contains(span, cluster.byte_idx) {
432 if started && cluster.has_relative_shift {
433 started = false;
434 spans.push(DecorationSpan { width, transform });
435 }
436
437 if !started {
438 width = cluster.advance;
439 started = true;
440 transform = cluster.transform;
441 } else {
442 width += cluster.advance;
443 }
444 } else if started {
445 spans.push(DecorationSpan { width, transform });
446 started = false;
447 }
448 }
449
450 if started {
451 spans.push(DecorationSpan { width, transform });
452 }
453
454 spans
455}
456
457pub(crate) fn convert_decoration(
458 dy: f32,
459 span: &TextSpan,
460 font: &ResolvedFont,
461 mut decoration: TextDecorationStyle,
462 decoration_spans: &[DecorationSpan],
463 transform: Transform,
464) -> Option<Path> {
465 debug_assert!(!decoration_spans.is_empty());
466
467 let thickness = font.underline_thickness(span.font_size.get());
468
469 let mut builder = tiny_skia_path::PathBuilder::new();
470 for dec_span in decoration_spans {
471 let rect = match NonZeroRect::from_xywh(0.0, -thickness / 2.0, dec_span.width, thickness) {
472 Some(v) => v,
473 None => {
474 log::warn!("a decoration span has a malformed bbox");
475 continue;
476 }
477 };
478
479 let ts = dec_span.transform.pre_translate(0.0, dy);
480
481 let mut path = tiny_skia_path::PathBuilder::from_rect(rect.to_rect());
482 path = match path.transform(ts) {
483 Some(v) => v,
484 None => continue,
485 };
486
487 builder.push_path(&path);
488 }
489
490 let mut path_data = builder.finish()?;
491 path_data = path_data.transform(transform)?;
492
493 Path::new(
494 String::new(),
495 span.visible,
496 decoration.fill.take(),
497 decoration.stroke.take(),
498 PaintOrder::default(),
499 ShapeRendering::default(),
500 Arc::new(path_data),
501 Transform::default(),
502 )
503}
504
505#[derive(Clone, Copy)]
510pub(crate) struct DecorationSpan {
511 pub(crate) width: f32,
512 pub(crate) transform: Transform,
513}
514
515fn resolve_clusters_positions(
521 text: &Text,
522 chunk: &TextChunk,
523 char_offset: usize,
524 writing_mode: WritingMode,
525 fonts_cache: &FontsCache,
526 clusters: &mut [GlyphCluster],
527) -> (f32, f32) {
528 match chunk.text_flow {
529 TextFlow::Linear => {
530 resolve_clusters_positions_horizontal(text, chunk, char_offset, writing_mode, clusters)
531 }
532 TextFlow::Path(ref path) => resolve_clusters_positions_path(
533 text,
534 chunk,
535 char_offset,
536 path,
537 writing_mode,
538 fonts_cache,
539 clusters,
540 ),
541 }
542}
543
544fn clusters_length(clusters: &[GlyphCluster]) -> f32 {
545 clusters.iter().fold(0.0, |w, cluster| w + cluster.advance)
546}
547
548fn resolve_clusters_positions_horizontal(
549 text: &Text,
550 chunk: &TextChunk,
551 offset: usize,
552 writing_mode: WritingMode,
553 clusters: &mut [GlyphCluster],
554) -> (f32, f32) {
555 let mut x = process_anchor(chunk.anchor, clusters_length(clusters));
556 let mut y = 0.0;
557
558 for cluster in clusters {
559 let cp = offset + cluster.byte_idx.code_point_at(&chunk.text);
560 if let (Some(dx), Some(dy)) = (text.dx.get(cp), text.dy.get(cp)) {
561 if writing_mode == WritingMode::LeftToRight {
562 x += dx;
563 y += dy;
564 } else {
565 y -= dx;
566 x += dy;
567 }
568 cluster.has_relative_shift = !dx.approx_zero_ulps(4) || !dy.approx_zero_ulps(4);
569 }
570
571 cluster.transform = cluster.transform.pre_translate(x, y);
572
573 if let Some(angle) = text.rotate.get(cp).cloned() {
574 if !angle.approx_zero_ulps(4) {
575 cluster.transform = cluster.transform.pre_rotate(angle);
576 cluster.has_relative_shift = true;
577 }
578 }
579
580 x += cluster.advance;
581 }
582
583 (x, y)
584}
585
586pub(crate) fn resolve_baseline(
595 span: &TextSpan,
596 font: &ResolvedFont,
597 writing_mode: WritingMode,
598) -> f32 {
599 let mut shift = -resolve_baseline_shift(&span.baseline_shift, font, span.font_size.get());
600
601 if writing_mode == WritingMode::LeftToRight {
603 if span.alignment_baseline == AlignmentBaseline::Auto
604 || span.alignment_baseline == AlignmentBaseline::Baseline
605 {
606 shift += font.dominant_baseline_shift(span.dominant_baseline, span.font_size.get());
607 } else {
608 shift += font.alignment_baseline_shift(span.alignment_baseline, span.font_size.get());
609 }
610 }
611
612 shift
613}
614
615fn resolve_baseline_shift(baselines: &[BaselineShift], font: &ResolvedFont, font_size: f32) -> f32 {
616 let mut shift = 0.0;
617 for baseline in baselines.iter().rev() {
618 match baseline {
619 BaselineShift::Baseline => {}
620 BaselineShift::Subscript => shift -= font.subscript_offset(font_size),
621 BaselineShift::Superscript => shift += font.superscript_offset(font_size),
622 BaselineShift::Number(n) => shift += n,
623 }
624 }
625
626 shift
627}
628
629fn resolve_clusters_positions_path(
630 text: &Text,
631 chunk: &TextChunk,
632 char_offset: usize,
633 path: &TextPath,
634 writing_mode: WritingMode,
635 fonts_cache: &FontsCache,
636 clusters: &mut [GlyphCluster],
637) -> (f32, f32) {
638 let mut last_x = 0.0;
639 let mut last_y = 0.0;
640
641 let mut dy = 0.0;
642
643 let chunk_offset = match writing_mode {
646 WritingMode::LeftToRight => chunk.x.unwrap_or(0.0),
647 WritingMode::TopToBottom => chunk.y.unwrap_or(0.0),
648 };
649
650 let start_offset =
651 chunk_offset + path.start_offset + process_anchor(chunk.anchor, clusters_length(clusters));
652
653 let normals = collect_normals(text, chunk, clusters, &path.path, char_offset, start_offset);
654 for (cluster, normal) in clusters.iter_mut().zip(normals) {
655 let (x, y, angle) = match normal {
656 Some(normal) => (normal.x, normal.y, normal.angle),
657 None => {
658 cluster.visible = false;
660 continue;
661 }
662 };
663
664 cluster.has_relative_shift = true;
666
667 let orig_ts = cluster.transform;
668
669 let half_width = cluster.width / 2.0;
671 cluster.transform = Transform::default();
672 cluster.transform = cluster.transform.pre_translate(x - half_width, y);
673 cluster.transform = cluster.transform.pre_rotate_at(angle, half_width, 0.0);
674
675 let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);
676 dy += text.dy.get(cp).cloned().unwrap_or(0.0);
677
678 let baseline_shift = chunk_span_at(chunk, cluster.byte_idx)
679 .map(|span| {
680 let font = match fonts_cache.get(&span.font) {
681 Some(v) => v,
682 None => return 0.0,
683 };
684 -resolve_baseline(span, font, writing_mode)
685 })
686 .unwrap_or(0.0);
687
688 if !dy.approx_zero_ulps(4) || !baseline_shift.approx_zero_ulps(4) {
691 let shift = kurbo::Vec2::new(0.0, (dy - baseline_shift) as f64);
692 cluster.transform = cluster
693 .transform
694 .pre_translate(shift.x as f32, shift.y as f32);
695 }
696
697 if let Some(angle) = text.rotate.get(cp).cloned() {
698 if !angle.approx_zero_ulps(4) {
699 cluster.transform = cluster.transform.pre_rotate(angle);
700 }
701 }
702
703 cluster.transform = cluster.transform.pre_concat(orig_ts);
705
706 last_x = x + cluster.advance;
707 last_y = y;
708 }
709
710 (last_x, last_y)
711}
712
713pub(crate) fn process_anchor(a: TextAnchor, text_width: f32) -> f32 {
714 match a {
715 TextAnchor::Start => 0.0, TextAnchor::Middle => -text_width / 2.0,
717 TextAnchor::End => -text_width,
718 }
719}
720
721pub(crate) struct PathNormal {
722 pub(crate) x: f32,
723 pub(crate) y: f32,
724 pub(crate) angle: f32,
725}
726
727fn collect_normals(
728 text: &Text,
729 chunk: &TextChunk,
730 clusters: &[GlyphCluster],
731 path: &tiny_skia_path::Path,
732 char_offset: usize,
733 offset: f32,
734) -> Vec<Option<PathNormal>> {
735 let mut offsets = Vec::with_capacity(clusters.len());
736 let mut normals = Vec::with_capacity(clusters.len());
737 {
738 let mut advance = offset;
739 for cluster in clusters {
740 let half_width = cluster.width / 2.0;
742
743 let cp = char_offset + cluster.byte_idx.code_point_at(&chunk.text);
745 advance += text.dx.get(cp).cloned().unwrap_or(0.0);
746
747 let offset = advance + half_width;
748
749 if offset < 0.0 {
751 normals.push(None);
752 }
753
754 offsets.push(offset as f64);
755 advance += cluster.advance;
756 }
757 }
758
759 let mut prev_mx = path.points()[0].x;
760 let mut prev_my = path.points()[0].y;
761 let mut prev_x = prev_mx;
762 let mut prev_y = prev_my;
763
764 fn create_curve_from_line(px: f32, py: f32, x: f32, y: f32) -> kurbo::CubicBez {
765 let line = kurbo::Line::new(
766 kurbo::Point::new(px as f64, py as f64),
767 kurbo::Point::new(x as f64, y as f64),
768 );
769 let p1 = line.eval(0.33);
770 let p2 = line.eval(0.66);
771 kurbo::CubicBez {
772 p0: line.p0,
773 p1,
774 p2,
775 p3: line.p1,
776 }
777 }
778
779 let mut length: f64 = 0.0;
780 for seg in path.segments() {
781 let curve = match seg {
782 tiny_skia_path::PathSegment::MoveTo(p) => {
783 prev_mx = p.x;
784 prev_my = p.y;
785 prev_x = p.x;
786 prev_y = p.y;
787 continue;
788 }
789 tiny_skia_path::PathSegment::LineTo(p) => {
790 create_curve_from_line(prev_x, prev_y, p.x, p.y)
791 }
792 tiny_skia_path::PathSegment::QuadTo(p1, p) => kurbo::QuadBez {
793 p0: kurbo::Point::new(prev_x as f64, prev_y as f64),
794 p1: kurbo::Point::new(p1.x as f64, p1.y as f64),
795 p2: kurbo::Point::new(p.x as f64, p.y as f64),
796 }
797 .raise(),
798 tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => kurbo::CubicBez {
799 p0: kurbo::Point::new(prev_x as f64, prev_y as f64),
800 p1: kurbo::Point::new(p1.x as f64, p1.y as f64),
801 p2: kurbo::Point::new(p2.x as f64, p2.y as f64),
802 p3: kurbo::Point::new(p.x as f64, p.y as f64),
803 },
804 tiny_skia_path::PathSegment::Close => {
805 create_curve_from_line(prev_x, prev_y, prev_mx, prev_my)
806 }
807 };
808
809 let arclen_accuracy = {
810 let base_arclen_accuracy = 0.5;
811 let (sx, sy) = text.abs_transform.get_scale();
815 base_arclen_accuracy / (sx * sy).sqrt().max(1.0)
817 };
818
819 let curve_len = curve.arclen(arclen_accuracy as f64);
820
821 for offset in &offsets[normals.len()..] {
822 if *offset >= length && *offset <= length + curve_len {
823 let mut offset = curve.inv_arclen(offset - length, arclen_accuracy as f64);
824 debug_assert!((-1.0e-3..=1.0 + 1.0e-3).contains(&offset));
826 offset = offset.clamp(0.0, 1.0);
827
828 let pos = curve.eval(offset);
829 let d = curve.deriv().eval(offset);
830 let d = kurbo::Vec2::new(-d.y, d.x); let angle = d.atan2().to_degrees() - 90.0;
832
833 normals.push(Some(PathNormal {
834 x: pos.x as f32,
835 y: pos.y as f32,
836 angle: angle as f32,
837 }));
838
839 if normals.len() == offsets.len() {
840 break;
841 }
842 }
843 }
844
845 length += curve_len;
846 prev_x = curve.p3.x as f32;
847 prev_y = curve.p3.y as f32;
848 }
849
850 for _ in 0..(offsets.len() - normals.len()) {
852 normals.push(None);
853 }
854
855 normals
856}
857
858fn process_chunk(
863 chunk: &TextChunk,
864 fonts_cache: &FontsCache,
865 resolver: &FontResolver,
866 fontdb: &mut Arc<fontdb::Database>,
867) -> Vec<GlyphCluster> {
868 let mut positions = HashSet::new();
903
904 let mut glyphs = Vec::new();
905 for span in &chunk.spans {
906 let font = match fonts_cache.get(&span.font) {
907 Some(v) => v.clone(),
908 None => continue,
909 };
910
911 let tmp_glyphs = shape_text(
912 &chunk.text,
913 font,
914 span.small_caps,
915 span.apply_kerning,
916 &span.font.variations,
917 span.font_size.get(),
918 span.font_optical_sizing,
919 resolver,
920 fontdb,
921 );
922
923 if glyphs.is_empty() {
925 glyphs = tmp_glyphs;
926 continue;
927 }
928
929 positions.clear();
930
931 let mut iter = tmp_glyphs.into_iter();
933 while let Some(new_glyph) = iter.next() {
934 if !span_contains(span, new_glyph.byte_idx) {
935 continue;
936 }
937
938 let Some(idx) = glyphs
939 .iter()
940 .position(|g| g.byte_idx == new_glyph.byte_idx)
941 .filter(|pos| !positions.contains(pos))
942 else {
943 continue;
944 };
945
946 positions.insert(idx);
947
948 let prev_cluster_len = glyphs[idx].cluster_len;
949 if prev_cluster_len < new_glyph.cluster_len {
950 for _ in 1..new_glyph.cluster_len {
953 glyphs.remove(idx + 1);
954 }
955 } else if prev_cluster_len > new_glyph.cluster_len {
956 for j in 1..prev_cluster_len {
959 if let Some(g) = iter.next() {
960 glyphs.insert(idx + j, g);
961 }
962 }
963 }
964
965 glyphs[idx] = new_glyph;
966 }
967 }
968
969 let mut clusters = Vec::new();
971 for (range, byte_idx) in GlyphClusters::new(&glyphs) {
972 if let Some(span) = chunk_span_at(chunk, byte_idx) {
973 clusters.push(form_glyph_clusters(
974 &glyphs[range],
975 &chunk.text,
976 span.font_size.get(),
977 ));
978 }
979 }
980
981 clusters
982}
983
984fn apply_length_adjust(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
985 let is_horizontal = matches!(chunk.text_flow, TextFlow::Linear);
986
987 for span in &chunk.spans {
988 let target_width = match span.text_length {
989 Some(v) => v,
990 None => continue,
991 };
992
993 let mut width = 0.0;
994 let mut cluster_indexes = Vec::new();
995 for i in span.start..span.end {
996 if let Some(index) = clusters.iter().position(|c| c.byte_idx.value() == i) {
997 cluster_indexes.push(index);
998 }
999 }
1000 cluster_indexes.sort();
1002 cluster_indexes.dedup();
1003
1004 for i in &cluster_indexes {
1005 width += clusters[*i].width;
1008 }
1009
1010 if cluster_indexes.is_empty() {
1011 continue;
1012 }
1013
1014 if span.length_adjust == LengthAdjust::Spacing {
1015 let factor = if cluster_indexes.len() > 1 {
1016 (target_width - width) / (cluster_indexes.len() - 1) as f32
1017 } else {
1018 0.0
1019 };
1020
1021 for i in cluster_indexes {
1022 clusters[i].advance = clusters[i].width + factor;
1023 }
1024 } else {
1025 let factor = target_width / width;
1026 if factor < 0.001 {
1028 continue;
1029 }
1030
1031 for i in cluster_indexes {
1032 clusters[i].transform = clusters[i].transform.pre_scale(factor, 1.0);
1033
1034 if !is_horizontal {
1036 clusters[i].advance *= factor;
1037 clusters[i].width *= factor;
1038 }
1039 }
1040 }
1041 }
1042}
1043
1044fn apply_writing_mode(writing_mode: WritingMode, clusters: &mut [GlyphCluster]) {
1047 if writing_mode != WritingMode::TopToBottom {
1048 return;
1049 }
1050
1051 for cluster in clusters {
1052 let orientation = unicode_vo::char_orientation(cluster.codepoint);
1053 if orientation == unicode_vo::Orientation::Upright {
1054 let mut ts = Transform::default();
1055 ts = ts.pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);
1057 ts = ts.pre_rotate_at(
1059 -90.0,
1060 cluster.width / 2.0,
1061 -(cluster.ascent + cluster.descent) / 2.0,
1062 );
1063
1064 cluster.path_transform = ts;
1065
1066 cluster.ascent = cluster.width / 2.0;
1068 cluster.descent = -cluster.width / 2.0;
1069 } else {
1070 cluster.transform = cluster
1074 .transform
1075 .pre_translate(0.0, (cluster.ascent + cluster.descent) / 2.0);
1076 }
1077 }
1078}
1079
1080fn apply_letter_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1084 if !chunk
1086 .spans
1087 .iter()
1088 .any(|span| !span.letter_spacing.approx_zero_ulps(4))
1089 {
1090 return;
1091 }
1092
1093 let num_clusters = clusters.len();
1094 for (i, cluster) in clusters.iter_mut().enumerate() {
1095 let script = cluster.codepoint.script();
1100 if script_supports_letter_spacing(script) {
1101 if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {
1102 if i != num_clusters - 1 {
1105 cluster.advance += span.letter_spacing;
1106 }
1107
1108 if !cluster.advance.is_valid_length() {
1111 cluster.width = 0.0;
1112 cluster.advance = 0.0;
1113 cluster.glyphs = vec![];
1114 }
1115 }
1116 }
1117 }
1118}
1119
1120fn apply_word_spacing(chunk: &TextChunk, clusters: &mut [GlyphCluster]) {
1124 if !chunk
1126 .spans
1127 .iter()
1128 .any(|span| !span.word_spacing.approx_zero_ulps(4))
1129 {
1130 return;
1131 }
1132
1133 for cluster in clusters {
1134 if is_word_separator_characters(cluster.codepoint) {
1135 if let Some(span) = chunk_span_at(chunk, cluster.byte_idx) {
1136 cluster.advance += span.word_spacing;
1140
1141 }
1143 }
1144 }
1145}
1146
1147fn form_glyph_clusters(glyphs: &[Glyph], text: &str, font_size: f32) -> GlyphCluster {
1148 debug_assert!(!glyphs.is_empty());
1149
1150 let mut x = 0.0;
1151 let mut width = 0.0;
1152 let mut advance = 0.0;
1153
1154 let mut positioned_glyphs = vec![];
1155
1156 for glyph in glyphs {
1157 let sx = glyph.font.scale(font_size);
1158
1159 let ts = Transform::from_translate(x + glyph.dx as f32, -glyph.dy as f32);
1166
1167 positioned_glyphs.push(PositionedGlyph {
1168 glyph_ts: ts,
1169 cluster_ts: Transform::default(),
1171 span_ts: Transform::default(),
1173 units_per_em: glyph.font.units_per_em.get(),
1174 font_size,
1175 font: glyph.font.id,
1176 text: glyph.text.clone(),
1177 id: glyph.id,
1178 });
1179
1180 x += glyph.width as f32;
1181
1182 let glyph_width = glyph.width as f32 * sx;
1183 advance += glyph_width;
1184 if glyph_width > width {
1185 width = glyph_width;
1186 }
1187 }
1188
1189 let byte_idx = glyphs[0].byte_idx;
1190 let font = glyphs[0].font.clone();
1191 GlyphCluster {
1192 byte_idx,
1193 codepoint: byte_idx.char_from(text),
1194 width,
1195 advance,
1196 ascent: font.ascent(font_size),
1197 descent: font.descent(font_size),
1198 has_relative_shift: false,
1199 transform: Transform::default(),
1200 path_transform: Transform::default(),
1201 glyphs: positioned_glyphs,
1202 visible: true,
1203 }
1204}
1205
1206pub(crate) trait DatabaseExt {
1207 fn load_font(&self, id: ID, variations: &[crate::FontVariation]) -> Option<ResolvedFont>;
1208 fn has_char(&self, id: ID, c: char) -> bool;
1209}
1210
1211impl DatabaseExt for Database {
1212 #[inline(never)]
1213 fn load_font(&self, id: ID, variations: &[crate::FontVariation]) -> Option<ResolvedFont> {
1214 self.with_face_data(id, |data, face_index| -> Option<ResolvedFont> {
1215 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
1216
1217 let location = font.axes().location(
1221 variations
1222 .iter()
1223 .map(|v| (Tag::from_be_bytes(v.tag), v.value)),
1224 );
1225 let coords = location.coords();
1226 let metrics = font.metrics(Size::unscaled(), &location);
1227
1228 if !(16..=16384).contains(&metrics.units_per_em) {
1230 return None;
1231 }
1232 let units_per_em = NonZeroU16::new(metrics.units_per_em)?;
1233
1234 let ascent = metrics.ascent;
1235 let descent = metrics.descent;
1236
1237 let x_height = metrics
1238 .x_height
1239 .filter(|x| *x > 0.0)
1240 .and_then(|x| u16::try_from(x.round() as i32).ok())
1241 .and_then(NonZeroU16::new);
1242 let x_height = match x_height {
1243 Some(height) => height,
1244 None => {
1245 u16::try_from(((ascent - descent) * 0.45).round() as i32)
1248 .ok()
1249 .and_then(NonZeroU16::new)?
1250 }
1251 };
1252
1253 let line_through = metrics.strikeout;
1254 let line_through_position = match line_through {
1255 Some(metrics) => metrics.offset.round() as i16,
1256 None => x_height.get() as i16 / 2,
1257 };
1258
1259 let (underline_position, underline_thickness) = match metrics.underline {
1260 Some(metrics) => {
1261 let thickness = u16::try_from(metrics.thickness.round() as i32)
1262 .ok()
1263 .and_then(NonZeroU16::new)
1264 .unwrap_or_else(|| NonZeroU16::new(units_per_em.get() / 12).unwrap());
1266
1267 (metrics.offset.round() as i16, thickness)
1268 }
1269 None => (
1270 -(units_per_em.get() as i16) / 9,
1271 NonZeroU16::new(units_per_em.get() / 12).unwrap(),
1272 ),
1273 };
1274
1275 let mut subscript_offset = (units_per_em.get() as f32 / 0.2).round() as i16;
1277 let mut superscript_offset = (units_per_em.get() as f32 / 0.4).round() as i16;
1278
1279 if let Ok(os2) = font.os2() {
1281 subscript_offset = os2.y_subscript_y_offset();
1282 superscript_offset = os2.y_superscript_y_offset();
1283 }
1284 if let (Ok(mvar), true) = (font.mvar(), !coords.is_empty()) {
1285 use skrifa::raw::tables::mvar::tags::*;
1286 let metric_delta =
1287 |tag| mvar.metric_delta(tag, coords).unwrap_or_default().to_f32();
1288
1289 subscript_offset += metric_delta(SBYO).round() as i16;
1290 superscript_offset += metric_delta(SPYO).round() as i16;
1291 }
1292
1293 Some(ResolvedFont {
1294 id,
1295 units_per_em,
1296 ascent: ascent.round() as i16,
1297 descent: descent.round() as i16,
1298 x_height,
1299 underline_position,
1300 underline_thickness,
1301 line_through_position,
1302 subscript_offset,
1303 superscript_offset,
1304 })
1305 })?
1306 }
1307
1308 #[inline(never)]
1309 fn has_char(&self, id: ID, c: char) -> bool {
1310 let res = self.with_face_data(id, |font_data, face_index| -> Option<bool> {
1311 let font = skrifa::FontRef::from_index(font_data, face_index).ok()?;
1312 let char_map = skrifa::charmap::Charmap::new(&font);
1313 char_map.map(c)?;
1314 Some(true)
1315 });
1316
1317 res == Some(Some(true))
1318 }
1319}
1320
1321pub(crate) fn shape_text(
1323 text: &str,
1324 font: Arc<ResolvedFont>,
1325 small_caps: bool,
1326 apply_kerning: bool,
1327 variations: &[crate::FontVariation],
1328 font_size: f32,
1329 font_optical_sizing: crate::FontOpticalSizing,
1330 resolver: &FontResolver,
1331 fontdb: &mut Arc<fontdb::Database>,
1332) -> Vec<Glyph> {
1333 let mut glyphs = shape_text_with_font(
1334 text,
1335 font.clone(),
1336 small_caps,
1337 apply_kerning,
1338 variations,
1339 font_size,
1340 font_optical_sizing,
1341 fontdb,
1342 )
1343 .unwrap_or_default();
1344
1345 let mut used_fonts = vec![font.id];
1347
1348 'outer: loop {
1350 let mut missing = None;
1351 for glyph in &glyphs {
1352 if glyph.is_missing() {
1353 missing = Some(glyph.byte_idx.char_from(text));
1354 break;
1355 }
1356 }
1357
1358 if let Some(c) = missing {
1359 let fallback_font = match (resolver.select_fallback)(c, &used_fonts, fontdb)
1360 .and_then(|id| fontdb.load_font(id, variations))
1361 {
1362 Some(v) => Arc::new(v),
1363 None => break 'outer,
1364 };
1365
1366 let fallback_glyphs = shape_text_with_font(
1368 text,
1369 fallback_font.clone(),
1370 small_caps,
1371 apply_kerning,
1372 variations,
1373 font_size,
1374 font_optical_sizing,
1375 fontdb,
1376 )
1377 .unwrap_or_default();
1378
1379 let all_matched = fallback_glyphs.iter().all(|g| !g.is_missing());
1380 if all_matched {
1381 glyphs = fallback_glyphs;
1383 break 'outer;
1384 }
1385
1386 if glyphs.len() != fallback_glyphs.len() {
1389 break 'outer;
1390 }
1391
1392 for i in 0..glyphs.len() {
1396 if glyphs[i].is_missing() && !fallback_glyphs[i].is_missing() {
1397 glyphs[i] = fallback_glyphs[i].clone();
1398 }
1399 }
1400
1401 used_fonts.push(fallback_font.id);
1403 } else {
1404 break 'outer;
1405 }
1406 }
1407
1408 for glyph in &glyphs {
1410 if glyph.is_missing() {
1411 let c = glyph.byte_idx.char_from(text);
1412 log::warn!(
1414 "No fonts with a {}/U+{:X} character were found.",
1415 c,
1416 c as u32
1417 );
1418 }
1419 }
1420
1421 glyphs
1422}
1423
1424fn shape_text_with_font(
1428 text: &str,
1429 font: Arc<ResolvedFont>,
1430 small_caps: bool,
1431 apply_kerning: bool,
1432 variations: &[crate::FontVariation],
1433 font_size: f32,
1434 font_optical_sizing: crate::FontOpticalSizing,
1435 fontdb: &fontdb::Database,
1436) -> Option<Vec<Glyph>> {
1437 fontdb.with_face_data(font.id, |font_data, face_index| -> Option<Vec<Glyph>> {
1438 use harfrust::{Feature, ShaperData, ShaperInstance, Tag, UnicodeBuffer, Variation};
1439
1440 use crate::text::OPSZ;
1441
1442 let hr_font = harfrust::FontRef::from_index(font_data, face_index).ok()?;
1443
1444 let mut variations: Vec<Variation> = variations
1446 .iter()
1447 .map(|v| Variation {
1448 tag: Tag::from_be_bytes(v.tag),
1449 value: v.value,
1450 })
1451 .collect();
1452
1453 if font_optical_sizing == crate::FontOpticalSizing::Auto {
1457 let has_explicit_opsz = variations.iter().any(|v| v.tag == *b"opsz");
1458 if !has_explicit_opsz && hr_font.axes().get_by_tag(OPSZ).is_some() {
1459 variations.push(Variation {
1460 tag: OPSZ,
1461 value: font_size,
1462 });
1463 }
1464 }
1465
1466 let bidi_info = unicode_bidi::BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
1467 let paragraph = &bidi_info.paragraphs[0];
1468 let line = paragraph.range.clone();
1469
1470 let mut glyphs = Vec::new();
1471
1472 let instance_data = (!variations.is_empty())
1474 .then(|| ShaperInstance::from_variations(&hr_font, &variations));
1475 let shaper_data = ShaperData::new(&hr_font);
1476 let shaper = shaper_data
1477 .shaper(&hr_font)
1478 .instance(instance_data.as_ref())
1479 .build();
1480
1481 let mut features = Vec::new();
1482 if small_caps {
1483 features.push(Feature::new(Tag::new(b"smcp"), 1, ..));
1484 }
1485 if !apply_kerning {
1486 features.push(Feature::new(Tag::new(b"kern"), 0, ..));
1487 }
1488
1489 let (levels, runs) = bidi_info.visual_runs(paragraph, line);
1490 for run in runs.iter() {
1491 let sub_text = &text[run.clone()];
1492 if sub_text.is_empty() {
1493 continue;
1494 }
1495
1496 let ltr = levels[run.start].is_ltr();
1497 let direction = if ltr {
1498 harfrust::Direction::LeftToRight
1499 } else {
1500 harfrust::Direction::RightToLeft
1501 };
1502
1503 let mut buffer = UnicodeBuffer::new();
1504 buffer.push_str(sub_text);
1505 buffer.set_direction(direction);
1506
1507 buffer.guess_segment_properties();
1509
1510 let output = shaper.shape(buffer, ShapeOptions::new().features(&features));
1511
1512 let positions = output.glyph_positions();
1513 let infos = output.glyph_infos();
1514
1515 for i in 0..output.len() {
1516 let pos = positions[i];
1517 let info = infos[i];
1518 let idx = run.start + info.cluster as usize;
1519
1520 let start = info.cluster as usize;
1521
1522 let end = if ltr {
1523 i.checked_add(1)
1524 } else {
1525 i.checked_sub(1)
1526 }
1527 .and_then(|last| infos.get(last))
1528 .map_or(sub_text.len(), |info| info.cluster as usize);
1529
1530 glyphs.push(Glyph {
1531 byte_idx: ByteIndex::new(idx),
1532 cluster_len: end.checked_sub(start).unwrap_or(0), text: sub_text[start..end].to_string(),
1534 id: GlyphId(info.glyph_id),
1535 dx: pos.x_offset,
1536 dy: pos.y_offset,
1537 width: pos.x_advance,
1538 font: font.clone(),
1539 });
1540 }
1541 }
1542
1543 Some(glyphs)
1544 })?
1545}
1546
1547pub(crate) struct GlyphClusters<'a> {
1552 data: &'a [Glyph],
1553 idx: usize,
1554}
1555
1556impl<'a> GlyphClusters<'a> {
1557 pub(crate) fn new(data: &'a [Glyph]) -> Self {
1558 GlyphClusters { data, idx: 0 }
1559 }
1560}
1561
1562impl Iterator for GlyphClusters<'_> {
1563 type Item = (std::ops::Range<usize>, ByteIndex);
1564
1565 fn next(&mut self) -> Option<Self::Item> {
1566 if self.idx == self.data.len() {
1567 return None;
1568 }
1569
1570 let start = self.idx;
1571 let cluster = self.data[self.idx].byte_idx;
1572 for g in &self.data[self.idx..] {
1573 if g.byte_idx != cluster {
1574 break;
1575 }
1576
1577 self.idx += 1;
1578 }
1579
1580 Some((start..self.idx, cluster))
1581 }
1582}
1583
1584pub(crate) fn script_supports_letter_spacing(script: unicode_script::Script) -> bool {
1590 use unicode_script::Script;
1591
1592 !matches!(
1593 script,
1594 Script::Arabic
1595 | Script::Syriac
1596 | Script::Nko
1597 | Script::Manichaean
1598 | Script::Psalter_Pahlavi
1599 | Script::Mandaic
1600 | Script::Mongolian
1601 | Script::Phags_Pa
1602 | Script::Devanagari
1603 | Script::Bengali
1604 | Script::Gurmukhi
1605 | Script::Modi
1606 | Script::Sharada
1607 | Script::Syloti_Nagri
1608 | Script::Tirhuta
1609 | Script::Ogham
1610 )
1611}
1612
1613#[derive(Clone)]
1617pub(crate) struct Glyph {
1618 pub(crate) id: GlyphId,
1620
1621 pub(crate) byte_idx: ByteIndex,
1625
1626 pub(crate) cluster_len: usize,
1628
1629 pub(crate) text: String,
1631
1632 pub(crate) dx: i32,
1634
1635 pub(crate) dy: i32,
1637
1638 pub(crate) width: i32,
1640
1641 pub(crate) font: Arc<ResolvedFont>,
1645}
1646
1647impl Glyph {
1648 fn is_missing(&self) -> bool {
1649 self.id.0 == 0
1650 }
1651}
1652
1653#[derive(Clone, Copy, Debug)]
1654pub(crate) struct ResolvedFont {
1655 pub(crate) id: ID,
1656
1657 units_per_em: NonZeroU16,
1658
1659 ascent: i16,
1661 descent: i16,
1662 x_height: NonZeroU16,
1663
1664 underline_position: i16,
1665 underline_thickness: NonZeroU16,
1666
1667 line_through_position: i16,
1671
1672 subscript_offset: i16,
1673 superscript_offset: i16,
1674}
1675
1676pub(crate) fn chunk_span_at(chunk: &TextChunk, byte_offset: ByteIndex) -> Option<&TextSpan> {
1677 chunk
1678 .spans
1679 .iter()
1680 .find(|&span| span_contains(span, byte_offset))
1681}
1682
1683pub(crate) fn span_contains(span: &TextSpan, byte_offset: ByteIndex) -> bool {
1684 byte_offset.value() >= span.start && byte_offset.value() < span.end
1685}
1686
1687pub(crate) fn is_word_separator_characters(c: char) -> bool {
1691 matches!(
1692 c as u32,
1693 0x0020 | 0x00A0 | 0x1361 | 0x010100 | 0x010101 | 0x01039F | 0x01091F
1694 )
1695}
1696
1697impl ResolvedFont {
1698 #[inline]
1699 pub(crate) fn scale(&self, font_size: f32) -> f32 {
1700 font_size / self.units_per_em.get() as f32
1701 }
1702
1703 #[inline]
1704 pub(crate) fn ascent(&self, font_size: f32) -> f32 {
1705 self.ascent as f32 * self.scale(font_size)
1706 }
1707
1708 #[inline]
1709 pub(crate) fn descent(&self, font_size: f32) -> f32 {
1710 self.descent as f32 * self.scale(font_size)
1711 }
1712
1713 #[inline]
1714 pub(crate) fn height(&self, font_size: f32) -> f32 {
1715 self.ascent(font_size) - self.descent(font_size)
1716 }
1717
1718 #[inline]
1719 pub(crate) fn x_height(&self, font_size: f32) -> f32 {
1720 self.x_height.get() as f32 * self.scale(font_size)
1721 }
1722
1723 #[inline]
1724 pub(crate) fn underline_position(&self, font_size: f32) -> f32 {
1725 self.underline_position as f32 * self.scale(font_size)
1726 }
1727
1728 #[inline]
1729 fn underline_thickness(&self, font_size: f32) -> f32 {
1730 self.underline_thickness.get() as f32 * self.scale(font_size)
1731 }
1732
1733 #[inline]
1734 pub(crate) fn line_through_position(&self, font_size: f32) -> f32 {
1735 self.line_through_position as f32 * self.scale(font_size)
1736 }
1737
1738 #[inline]
1739 fn subscript_offset(&self, font_size: f32) -> f32 {
1740 self.subscript_offset as f32 * self.scale(font_size)
1741 }
1742
1743 #[inline]
1744 fn superscript_offset(&self, font_size: f32) -> f32 {
1745 self.superscript_offset as f32 * self.scale(font_size)
1746 }
1747
1748 fn dominant_baseline_shift(&self, baseline: DominantBaseline, font_size: f32) -> f32 {
1749 let alignment = match baseline {
1750 DominantBaseline::Auto => AlignmentBaseline::Auto,
1751 DominantBaseline::UseScript => AlignmentBaseline::Auto, DominantBaseline::NoChange => AlignmentBaseline::Auto, DominantBaseline::ResetSize => AlignmentBaseline::Auto, DominantBaseline::Ideographic => AlignmentBaseline::Ideographic,
1755 DominantBaseline::Alphabetic => AlignmentBaseline::Alphabetic,
1756 DominantBaseline::Hanging => AlignmentBaseline::Hanging,
1757 DominantBaseline::Mathematical => AlignmentBaseline::Mathematical,
1758 DominantBaseline::Central => AlignmentBaseline::Central,
1759 DominantBaseline::Middle => AlignmentBaseline::Middle,
1760 DominantBaseline::TextAfterEdge => AlignmentBaseline::TextAfterEdge,
1761 DominantBaseline::TextBeforeEdge => AlignmentBaseline::TextBeforeEdge,
1762 };
1763
1764 self.alignment_baseline_shift(alignment, font_size)
1765 }
1766
1767 fn alignment_baseline_shift(&self, alignment: AlignmentBaseline, font_size: f32) -> f32 {
1797 match alignment {
1798 AlignmentBaseline::Auto => 0.0,
1799 AlignmentBaseline::Baseline => 0.0,
1800 AlignmentBaseline::BeforeEdge | AlignmentBaseline::TextBeforeEdge => {
1801 self.ascent(font_size)
1802 }
1803 AlignmentBaseline::Middle => self.x_height(font_size) * 0.5,
1804 AlignmentBaseline::Central => self.ascent(font_size) - self.height(font_size) * 0.5,
1805 AlignmentBaseline::AfterEdge | AlignmentBaseline::TextAfterEdge => {
1806 self.descent(font_size)
1807 }
1808 AlignmentBaseline::Ideographic => self.descent(font_size),
1809 AlignmentBaseline::Alphabetic => 0.0,
1810 AlignmentBaseline::Hanging => self.ascent(font_size) * 0.8,
1811 AlignmentBaseline::Mathematical => self.ascent(font_size) * 0.5,
1812 }
1813 }
1814}
1815
1816pub(crate) type FontsCache = HashMap<Font, Arc<ResolvedFont>>;
1817
1818#[derive(Clone, Copy, PartialEq, Debug)]
1822pub(crate) struct ByteIndex(usize);
1823
1824impl ByteIndex {
1825 fn new(i: usize) -> Self {
1826 ByteIndex(i)
1827 }
1828
1829 pub(crate) fn value(&self) -> usize {
1830 self.0
1831 }
1832
1833 pub(crate) fn code_point_at(&self, text: &str) -> usize {
1835 text.char_indices()
1836 .take_while(|(i, _)| *i != self.0)
1837 .count()
1838 }
1839
1840 pub(crate) fn char_from(&self, text: &str) -> char {
1842 text[self.0..].chars().next().unwrap()
1843 }
1844}