skia_safe/modules/paragraph/metrics.rs
1use crate::{paragraph::TextStyle, prelude::*, FontMetrics};
2use skia_bindings::{self as sb, skia_textlayout_LineMetrics, skia_textlayout_StyleMetrics};
3use std::{marker::PhantomData, ops::Range, ptr};
4
5#[repr(C)]
6#[derive(Clone, Debug)]
7pub struct StyleMetrics<'a> {
8 pub text_style: &'a TextStyle,
9
10 /// [`FontMetrics`] contains the following metrics:
11 ///
12 /// * Top distance to reserve above baseline
13 /// * Ascent distance to reserve below baseline
14 /// * Descent extent below baseline
15 /// * Bottom extent below baseline
16 /// * Leading distance to add between lines
17 /// * AvgCharWidth average character width
18 /// * MaxCharWidth maximum character width
19 /// * XMin minimum x
20 /// * XMax maximum x
21 /// * XHeight height of lower-case 'x'
22 /// * CapHeight height of an upper-case letter
23 /// * UnderlineThickness underline thickness
24 /// * UnderlinePosition underline position relative to baseline
25 /// * StrikeoutThickness strikeout thickness
26 /// * StrikeoutPosition strikeout position relative to baseline
27 pub font_metrics: FontMetrics,
28}
29
30native_transmutable!(skia_textlayout_StyleMetrics, StyleMetrics<'_>);
31
32impl<'a> StyleMetrics<'a> {
33 pub fn new(style: &'a TextStyle, metrics: impl Into<Option<FontMetrics>>) -> Self {
34 Self {
35 text_style: style,
36 font_metrics: metrics.into().unwrap_or_default(),
37 }
38 }
39}
40
41#[derive(Clone, Debug)]
42pub struct LineMetrics<'a> {
43 // The following fields are used in the layout process itself.
44 /// The index in the text buffer the line begins.
45 pub start_index: usize,
46 /// The index in the text buffer the line ends.
47 pub end_index: usize,
48 pub end_excluding_whitespaces: usize,
49 pub end_including_newline: usize,
50 pub hard_break: bool,
51
52 // The following fields are tracked after or during layout to provide to
53 // the user as well as for computing bounding boxes.
54 /// The final computed ascent and descent for the line. This can be impacted by
55 /// the strut, height, scaling, as well as outlying runs that are very tall.
56 ///
57 /// The top edge is `baseline - ascent` and the bottom edge is `baseline +
58 /// descent`. Ascent and descent are provided as positive numbers. Raw numbers
59 /// for specific runs of text can be obtained in run_metrics_map. These values
60 /// are the cumulative metrics for the entire line.
61 pub ascent: f64,
62 pub descent: f64,
63 pub unscaled_ascent: f64,
64 /// Total height of the paragraph including the current line.
65 ///
66 /// The height of the current line is `round(ascent + descent)`.
67 pub height: f64,
68 /// Width of the line.
69 pub width: f64,
70 /// The left edge of the line. The right edge can be obtained with `left +
71 /// width`
72 pub left: f64,
73 /// The y position of the baseline for this line from the top of the paragraph.
74 pub baseline: f64,
75 /// Zero indexed line number
76 pub line_number: usize,
77 /// Mapping between text index ranges and the FontMetrics associated with
78 /// them. The first run will be keyed under start_index. The metrics here
79 /// are before layout and are the base values we calculate from.
80 style_metrics: Vec<sb::IndexedStyleMetrics>,
81 pd: PhantomData<&'a StyleMetrics<'a>>,
82}
83
84impl<'a> LineMetrics<'a> {
85 // TODO: may support constructors (but what about the lifetime bounds?).
86
87 /// Returns the number of style metrics in the given index range.
88 pub fn get_style_metrics_count(&self, range: Range<usize>) -> usize {
89 let lower = self
90 .style_metrics
91 .partition_point(|ism| ism.index < range.start);
92 let upper = self
93 .style_metrics
94 .partition_point(|ism| ism.index < range.end);
95 upper - lower
96 }
97
98 /// Returns indices and references to style metrics in the given range.
99 pub fn get_style_metrics(&'a self, range: Range<usize>) -> Vec<(usize, &'a StyleMetrics<'a>)> {
100 let lower = self
101 .style_metrics
102 .partition_point(|ism| ism.index < range.start);
103 let upper = self
104 .style_metrics
105 .partition_point(|ism| ism.index < range.end);
106 self.style_metrics[lower..upper]
107 .iter()
108 .map(|ism| (ism.index, StyleMetrics::from_native_ref(&ism.metrics)))
109 .collect()
110 }
111
112 // We can't use a `std::map` in rust, it does not seem to be safe to move. So we copy it into a
113 // sorted Vec.
114 pub(crate) fn from_native_ref<'b>(lm: &skia_textlayout_LineMetrics) -> LineMetrics<'b> {
115 let sm_count = unsafe { sb::C_LineMetrics_styleMetricsCount(lm) };
116 let mut style_metrics = vec![
117 sb::IndexedStyleMetrics {
118 index: 0,
119 metrics: sb::skia_textlayout_StyleMetrics {
120 text_style: ptr::null(),
121 font_metrics: sb::SkFontMetrics {
122 fFlags: 0,
123 fTop: 0.0,
124 fAscent: 0.0,
125 fDescent: 0.0,
126 fBottom: 0.0,
127 fLeading: 0.0,
128 fAvgCharWidth: 0.0,
129 fMaxCharWidth: 0.0,
130 fXMin: 0.0,
131 fXMax: 0.0,
132 fXHeight: 0.0,
133 fCapHeight: 0.0,
134 fUnderlineThickness: 0.0,
135 fUnderlinePosition: 0.0,
136 fStrikeoutThickness: 0.0,
137 fStrikeoutPosition: 0.0
138 }
139 }
140 };
141 sm_count
142 ];
143
144 unsafe { sb::C_LineMetrics_getAllStyleMetrics(lm, style_metrics.as_mut_ptr()) }
145
146 LineMetrics {
147 start_index: lm.fStartIndex,
148 end_index: lm.fEndIndex,
149 end_excluding_whitespaces: lm.fEndExcludingWhitespaces,
150 end_including_newline: lm.fEndIncludingNewline,
151 hard_break: lm.fHardBreak,
152 ascent: lm.fAscent,
153 descent: lm.fDescent,
154 unscaled_ascent: lm.fUnscaledAscent,
155 height: lm.fHeight,
156 width: lm.fWidth,
157 left: lm.fLeft,
158 baseline: lm.fBaseline,
159 line_number: lm.fLineNumber,
160 style_metrics,
161 pd: PhantomData,
162 }
163 }
164}