| 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 | |