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