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!(
31 skia_textlayout_StyleMetrics,
32 StyleMetrics<'_>,
33 style_metrics_layout
34);
35
36impl<'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)]
46pub 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
88impl<'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