diff --git a/CHANGELOG.md b/CHANGELOG.md index cd56e8205..4e5ec8b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe This release has an [MSRV] of 1.88. +### Added + +#### Parley + +- Layouts now have a root-level style ([#718][] by [@nicoburns][]). + The new `RootStyle` struct nests a `TextStyle` (which acts as the layout's default style) plus a `strut` flag. + All three builder constructors (`ranged_builder`, `style_run_builder`, and `tree_builder`) now take a `&RootStyle` argument, which is a breaking change for `ranged_builder` and `style_run_builder`. + The resolved root style is stored on the layout and exposed via `Layout::{root_style, root_font_size, root_line_height, root_font_metrics}`. + Root font metrics are resolved at build time without shaping any text, so they are available even for empty layouts. + Empty layouts and the empty line following a trailing newline now derive their metrics from the root style. + When `RootStyle::strut` is `true`, the root style's metrics floor every line box's metrics, matching CSS strut behavior. + ### Changed #### Parley @@ -704,6 +716,7 @@ This release has an [MSRV][] of 1.70. [#661]: https://github.com/linebender/parley/pull/661 [#671]: https://github.com/linebender/parley/pull/671 [#697]: https://github.com/linebender/parley/pull/697 +[#718]: https://github.com/linebender/parley/pull/718 [Unreleased]: https://github.com/linebender/parley/compare/v0.11.0...HEAD [0.11.0]: https://github.com/linebender/parley/compare/v0.10.0...v0.11.0 diff --git a/examples/common/src/lib.rs b/examples/common/src/lib.rs index 1146adb7d..166e3af8b 100644 --- a/examples/common/src/lib.rs +++ b/examples/common/src/lib.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use std::time::Duration; use std::time::Instant; +use parley::RootStyle; use parley::fontique::Blob; use parley::{ Alignment, AlignmentOptions, FontContext, FontFamily, FontWeight, GenericFamily, InlineBox, @@ -124,8 +125,13 @@ pub fn build_simple_layout( layout_cx: &mut LayoutContext, config: &ExampleConfig, ) -> (Layout, u16, u16) { - let mut builder = - layout_cx.ranged_builder(font_cx, &config.text, config.display_scale, config.quantize); + let mut builder = layout_cx.ranged_builder( + font_cx, + &config.text, + config.display_scale, + config.quantize, + &RootStyle::default(), + ); let foreground_brush = ColorBrush { color: config.foreground_color, @@ -174,8 +180,13 @@ pub fn build_rich_layout( let (underline_range, strikethrough_range, party_emoji_range) = style_ranges(&config.text); - let mut builder = - layout_cx.ranged_builder(font_cx, &config.text, config.display_scale, config.quantize); + let mut builder = layout_cx.ranged_builder( + font_cx, + &config.text, + config.display_scale, + config.quantize, + &RootStyle::default(), + ); let foreground_brush = ColorBrush { color: config.foreground_color, diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index 96cee1de2..987e2d4f3 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -9,7 +9,7 @@ use image::codecs::png::PngEncoder; use image::{self, Pixel, Rgba, RgbaImage}; use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; -use parley::style::{FontFamily, FontWeight, StyleProperty, TextStyle}; +use parley::style::{FontFamily, FontWeight, RootStyle, StyleProperty, TextStyle}; use parley::{AlignmentOptions, FontContext, InlineBox, InlineBoxKind, LayoutContext, LineHeight}; use std::fs::File; use swash::FontRef; @@ -75,13 +75,13 @@ fn main() { // TODO: cleanup API - let root_style = TextStyle { + let root_style = RootStyle::from(TextStyle { brush: text_brush, font_family, line_height: LineHeight::FontSizeRelative(1.3), font_size: 16.0, ..TextStyle::default() - }; + }); let mut builder = layout_cx.tree_builder(&mut font_cx, display_scale, quantize, &root_style); @@ -137,7 +137,13 @@ fn main() { // ============ // Creates a RangedBuilder - let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale, quantize); + let mut builder = layout_cx.ranged_builder( + &mut font_cx, + &text, + display_scale, + quantize, + &RootStyle::default(), + ); // Set default text colour styles (set foreground text color) builder.push_default(brush_style); diff --git a/examples/tiny_skia_render/src/main.rs b/examples/tiny_skia_render/src/main.rs index 6adb05c2e..32b87bcb2 100644 --- a/examples/tiny_skia_render/src/main.rs +++ b/examples/tiny_skia_render/src/main.rs @@ -9,6 +9,7 @@ #![expect(clippy::cast_possible_truncation, reason = "Deferred")] +use parley::RootStyle; use parley::{ Alignment, AlignmentOptions, FontContext, FontWeight, GenericFamily, GlyphRun, InlineBox, InlineBoxKind, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty, @@ -64,7 +65,13 @@ fn main() { let mut layout_cx = LayoutContext::new(); // Create a RangedBuilder - let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale, quantize); + let mut builder = layout_cx.ranged_builder( + &mut font_cx, + &text, + display_scale, + quantize, + &RootStyle::default(), + ); // Set default text colour styles (set foreground text color) let foreground_brush = ColorBrush { diff --git a/parley/src/analysis.rs b/parley/src/analysis.rs index f23a54587..ef90c729d 100644 --- a/parley/src/analysis.rs +++ b/parley/src/analysis.rs @@ -23,7 +23,8 @@ pub(crate) fn analyze_text( lcx.word_break .extend(lcx.style_runs.iter().filter_map(|sr| { let word_break = lcx.style_table[sr.style_index as usize].word_break; - (word_break != WordBreak::Normal).then(|| (sr.range.clone(), word_break)) + (!sr.range.is_empty() && word_break != WordBreak::Normal) + .then(|| (sr.range.clone(), word_break)) })); let options = AnalysisOptions { diff --git a/parley/src/builder.rs b/parley/src/builder.rs index fcdc7fb46..f7a3b6ff9 100644 --- a/parley/src/builder.rs +++ b/parley/src/builder.rs @@ -16,7 +16,7 @@ use parley_engine::break_overrides::LineBreakOverrideFn; use crate::InlineBoxKind; use crate::inline_box::InlineBox; -use crate::resolve::{ResolvedStyle, StyleRun, tree::ItemKind}; +use crate::resolve::{StyleRun, tree::ItemKind}; #[derive(Clone, Copy)] pub(crate) struct BuilderOptions<'a> { @@ -51,6 +51,7 @@ impl<'b, B: Brush> RangedBuilder<'b, B> { self.lcx .rcx .resolve_property(self.fcx, &property.into(), self.options.scale); + self.lcx.root_style.apply(resolved.clone()); self.lcx.ranged_style_builder.push_default(resolved); } @@ -302,7 +303,9 @@ fn build_into_layout( options: BuilderOptions<'_>, ) { if text.is_empty() && lcx.style_runs.is_empty() { - lcx.style_table.push(ResolvedStyle::default()); + // Style the empty layout with the root style so that it produces + // meaningful metrics (e.g. for sizing a cursor). + lcx.style_table.push(lcx.root_style.clone()); lcx.style_runs.push(StyleRun { style_index: 0, range: 0..0, @@ -326,6 +329,17 @@ fn build_into_layout( layout.data.base_level = lcx.analysis.paragraph_level(); layout.data.text_len = text.len(); + layout.data.root_style = lcx.root_style.as_layout_style(); + layout.data.root_font_size = lcx.root_style.font_size; + layout.data.strut = lcx.root_style_strut; + { + let mut query = fcx.collection.query(&mut fcx.source_cache); + let (metrics, line_height) = + super::shape::root_font_metrics(&lcx.rcx, &mut query, &lcx.root_style); + layout.data.root_font_metrics = metrics; + layout.data.root_line_height = line_height; + } + lcx.char_style_indices .resize(lcx.analysis.char_info().len(), 0); let mut char_index = 0; diff --git a/parley/src/context.rs b/parley/src/context.rs index 15531dfb6..d1a3c7aff 100644 --- a/parley/src/context.rs +++ b/parley/src/context.rs @@ -14,7 +14,7 @@ use super::FontContext; use super::builder::{BuilderOptions, RangedBuilder, StyleRunBuilder}; use super::resolve::tree::TreeStyleBuilder; use super::resolve::{RangedStyleBuilder, ResolveContext, ResolvedStyle, StyleRun}; -use super::style::{Brush, TextStyle}; +use super::style::{Brush, RootStyle, TextStyle}; use crate::builder::TreeBuilder; use crate::inline_box::InlineBox; @@ -37,6 +37,10 @@ pub struct LayoutContext { pub(crate) ranged_style_builder: RangedStyleBuilder, pub(crate) tree_style_builder: TreeStyleBuilder, + // The resolved root style for the layout currently being built + pub(crate) root_style: ResolvedStyle, + pub(crate) root_style_strut: bool, + /// Style index for each character, parallel to [`Analysis::char_info`]. pub(crate) char_style_indices: Vec, pub(crate) scx: Shaper, @@ -57,6 +61,8 @@ impl LayoutContext { word_break: Vec::new(), ranged_style_builder: RangedStyleBuilder::default(), tree_style_builder: TreeStyleBuilder::default(), + root_style: ResolvedStyle::default(), + root_style_strut: false, char_style_indices: vec![], analysis_data_sources: AnalysisDataSources::new(), scx: Shaper::default(), @@ -98,10 +104,13 @@ impl LayoutContext { text: &'a str, scale: f32, quantize: bool, + root_style: &RootStyle<'_, '_, B>, ) -> RangedBuilder<'a, B> { self.begin(); - let resolved_root_style = self.resolve_style_set(fcx, scale, &TextStyle::default()); + let resolved_root_style = self.resolve_style_set(fcx, scale, &root_style.style); + self.root_style = resolved_root_style.clone(); + self.root_style_strut = root_style.strut; self.ranged_style_builder .begin(resolved_root_style, text.len()); @@ -128,9 +137,13 @@ impl LayoutContext { text: &'a str, scale: f32, quantize: bool, + root_style: &RootStyle<'_, '_, B>, ) -> StyleRunBuilder<'a, B> { self.begin(); + self.root_style = self.resolve_style_set(fcx, scale, &root_style.style); + self.root_style_strut = root_style.strut; + fcx.source_cache.prune(128, false); StyleRunBuilder { @@ -166,11 +179,13 @@ impl LayoutContext { fcx: &'a mut FontContext, scale: f32, quantize: bool, - root_style: &TextStyle<'_, '_, B>, + root_style: &RootStyle<'_, '_, B>, ) -> TreeBuilder<'a, B> { self.begin(); - let resolved_root_style = self.resolve_style_set(fcx, scale, root_style); + let resolved_root_style = self.resolve_style_set(fcx, scale, &root_style.style); + self.root_style = resolved_root_style.clone(); + self.root_style_strut = root_style.strut; self.tree_style_builder.begin(resolved_root_style); fcx.source_cache.prune(128, false); diff --git a/parley/src/editing/editor.rs b/parley/src/editing/editor.rs index 9582ef628..bf880b081 100644 --- a/parley/src/editing/editor.rs +++ b/parley/src/editing/editor.rs @@ -15,7 +15,7 @@ use core::{ use crate::editing::{Cursor, Selection}; use crate::layout::{Affinity, Alignment, AlignmentOptions, Layout}; use crate::style::Brush; -use crate::{BoundingBox, FontContext, LayoutContext, StyleProperty, StyleSet}; +use crate::{BoundingBox, FontContext, LayoutContext, RootStyle, StyleProperty, StyleSet}; #[cfg(feature = "accesskit")] use crate::layout::LayoutAccessibility; @@ -1227,8 +1227,13 @@ where } /// Update the layout. fn update_layout(&mut self, font_cx: &mut FontContext, layout_cx: &mut LayoutContext) { - let mut builder = - layout_cx.ranged_builder(font_cx, &self.buffer, self.scale, self.quantize); + let mut builder = layout_cx.ranged_builder( + font_cx, + &self.buffer, + self.scale, + self.quantize, + &RootStyle::default(), + ); for prop in self.default_style.inner().values() { builder.push_default(prop.to_owned()); } diff --git a/parley/src/layout/cluster.rs b/parley/src/layout/cluster.rs index c78c66cdc..f0bdb4eb8 100644 --- a/parley/src/layout/cluster.rs +++ b/parley/src/layout/cluster.rs @@ -568,7 +568,8 @@ mod tests { // TODO: Use a test font let mut font_ctx = FontContext::new(); let text = "Parley exists"; - let mut builder = layout_ctx.ranged_builder(&mut font_ctx, text, 1.0, true); + let mut builder = + layout_ctx.ranged_builder(&mut font_ctx, text, 1.0, true, &crate::RootStyle::default()); builder.push_default(StyleProperty::FontSize(10.)); let mut layout = builder.build(text); layout.break_all_lines(None); diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index c939ca9f8..467c82049 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -11,7 +11,7 @@ use core::ops::Range; use alloc::vec::Vec; use parley_engine::shape::ClusterData; -use parley_engine::{Boundary, ShapedText}; +use parley_engine::{Boundary, FontMetrics, ShapedText}; /// `HarfRust`-based run data #[derive(Clone, Debug, PartialEq)] @@ -158,6 +158,18 @@ pub(crate) struct LayoutData { /// The length of the text in the layout pub(crate) text_len: usize, + // Resolved root style + /// The layout's root style. + pub(crate) root_style: Style, + /// The font size of the root style, in layout units. + pub(crate) root_font_size: f32, + /// The resolved line height of the root style, in layout units. + pub(crate) root_line_height: f32, + /// The metrics of the root style's font. + pub(crate) root_font_metrics: FontMetrics, + /// Whether the root style acts as a strut, flooring the metrics of every line box. + pub(crate) strut: bool, + // Output of style resolution (input to line breaking) pub(crate) styles: Vec>, pub(crate) inline_boxes: Vec, @@ -204,6 +216,11 @@ impl Default for LayoutData { width: 0., full_width: 0., height: 0., + root_style: Style::default(), + root_font_size: 0., + root_line_height: 0., + root_font_metrics: FontMetrics::default(), + strut: false, styles: Vec::new(), inline_boxes: Vec::new(), shaped_text: ShapedText::new(), @@ -230,6 +247,11 @@ impl LayoutData { self.width = 0.; self.full_width = 0.; self.height = 0.; + self.root_style = Style::default(); + self.root_font_size = 0.; + self.root_line_height = 0.; + self.root_font_metrics = FontMetrics::default(); + self.strut = false; self.styles.clear(); self.inline_boxes.clear(); self.shaped_text.clear(); diff --git a/parley/src/layout/layout.rs b/parley/src/layout/layout.rs index 7e4836198..dc1e5b394 100644 --- a/parley/src/layout/layout.rs +++ b/parley/src/layout/layout.rs @@ -8,6 +8,7 @@ use crate::layout::data::LayoutData; use crate::style::Brush; use core::cmp::Ordering; use core::fmt; +use parley_engine::FontMetrics; use crate::IndentOptions; use crate::layout::{ @@ -59,6 +60,30 @@ impl Layout { &self.data.styles } + /// Returns the layout's root style (see [`RootStyle`](crate::RootStyle)). + pub fn root_style(&self) -> &Style { + &self.data.root_style + } + + /// Returns the font size of the layout's root style, in layout units + /// (i.e. scaled by the layout's scale factor). + pub fn root_font_size(&self) -> f32 { + self.data.root_font_size + } + + /// Returns the resolved line height of the layout's root style, in layout units. + pub fn root_line_height(&self) -> f32 { + self.data.root_line_height + } + + /// Returns the metrics of the font selected for the layout's root style. + /// + /// These are resolved at build time without shaping any text, so they are + /// available even for an empty layout. + pub fn root_font_metrics(&self) -> &FontMetrics { + &self.data.root_font_metrics + } + /// The `max_advance` that was used to line break the `Layout` pub fn layout_max_advance(&self) -> f32 { self.data.layout_max_advance diff --git a/parley/src/layout/line_break.rs b/parley/src/layout/line_break.rs index e33fc1026..f52c5ca3c 100644 --- a/parley/src/layout/line_break.rs +++ b/parley/src/layout/line_break.rs @@ -432,6 +432,9 @@ pub struct BreakLines<'a, B: Brush> { state: BreakerState, prev_state: Option, done: bool, + /// The metrics every line starts out with. These are zero unless the layout's root + /// style acts as a strut, in which case they floor every line box's metrics. + initial_box_metrics: LineBoxMetrics, } impl<'a, B: Brush> BreakLines<'a, B> { @@ -442,12 +445,23 @@ impl<'a, B: Brush> BreakLines<'a, B> { lines.swap(&mut layout.data); lines.lines.clear(); lines.line_items.clear(); + let mut initial_box_metrics = LineBoxMetrics::default(); + if layout.data.strut { + initial_box_metrics.add_text( + &layout.data.root_font_metrics, + layout.data.root_line_height, + layout.data.quantize, + ); + } + let mut state = BreakerState::default(); + state.line.box_metrics = initial_box_metrics; Self { layout, lines, - state: BreakerState::default(), + state, prev_state: None, done: false, + initial_box_metrics, } } @@ -479,6 +493,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { // it must run before we reset the per-line running state. self.finish_line(self.lines.lines.len() - 1, line_height); self.state.line.reset(); + self.state.line.box_metrics = self.initial_box_metrics; self.state.line_y += line_height as f64; @@ -1117,10 +1132,6 @@ impl<'a, B: Brush> BreakLines<'a, B> { } fn finish_line(&mut self, line_idx: usize, line_height: f32) { - let prev_line_metrics = match line_idx { - 0 => None, - idx => Some(self.lines.lines[idx - 1].metrics), - }; let line = &mut self.lines.lines[line_idx]; // Reset metrics for line @@ -1225,26 +1236,23 @@ impl<'a, B: Brush> BreakLines<'a, B> { let mut line_box_extents = self.state.line.box_metrics.line_box; let mut content_box_extents = self.state.line.box_metrics.content_box; - if !have_metrics - && line.item_range.is_empty() - && let Some(metrics) = prev_line_metrics - { - // HACK: copy metrics from previous line if we don't have - // any; this should only occur for an empty line following - // a newline at the end of a layout - line.metrics = metrics; - line_box_extents = Extents { - over: metrics.baseline - metrics.block_min_coord, - under: metrics.block_max_coord - metrics.baseline, - }; - content_box_extents = Extents { - over: metrics.baseline - metrics.content_block_min_coord, - under: metrics.content_block_max_coord - metrics.baseline, - }; - // If we have no items on this line, it must be the last (empty) - // line in a layout following a newline. Commit an empty run so - // that AccessKit has a node with which to identify the visual - // cursor position + if !have_metrics && line.item_range.is_empty() { + // An empty line has no content to derive metrics from, so derive + // them from the layout's root style. This occurs for the empty + // line following a newline at the end of a layout, and for a + // layout with no text at all. + let mut box_metrics = LineBoxMetrics::default(); + box_metrics.add_text( + &self.layout.data.root_font_metrics, + self.layout.data.root_line_height, + quantize, + ); + line.metrics.line_height = box_metrics.line_height(); + line_box_extents = box_metrics.line_box; + content_box_extents = box_metrics.content_box; + // If this line follows a newline at the end of a layout, commit + // an empty run so that AccessKit has a node with which to + // identify the visual cursor position if let Some((index, run)) = self .layout .data diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index 434738760..4010e4e12 100644 --- a/parley/src/layout/mod.rs +++ b/parley/src/layout/mod.rs @@ -67,6 +67,21 @@ pub struct Style { pub(crate) locale: Option, } +impl Default for Style { + fn default() -> Self { + Self { + brush: B::default(), + underline: None, + strikethrough: None, + line_height: LineHeight::default(), + overflow_wrap: OverflowWrap::default(), + text_wrap_mode: TextWrapMode::default(), + #[cfg(feature = "accesskit")] + locale: None, + } + } +} + /// Underline or strikethrough decoration. #[derive(Clone, Debug, PartialEq)] pub struct Decoration { diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 1aa7938a4..3e902c66d 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -27,7 +27,7 @@ //! ```rust //! use parley::{ //! Alignment, AlignmentOptions, FontContext, FontWeight, InlineBox, InlineBoxKind, Layout, -//! LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty, +//! LayoutContext, LineHeight, PositionedLayoutItem, RootStyle, StyleProperty, //! }; //! //! // Create a FontContext (font database) and LayoutContext (scratch space). @@ -38,7 +38,7 @@ //! // Create a `RangedBuilder` or a `TreeBuilder`, which are used to construct a `Layout`. //! const DISPLAY_SCALE : f32 = 1.0; //! const TEXT : &str = "Lorem Ipsum..."; -//! let mut builder = layout_cx.ranged_builder(&mut font_cx, &TEXT, DISPLAY_SCALE, true); +//! let mut builder = layout_cx.ranged_builder(&mut font_cx, &TEXT, DISPLAY_SCALE, true, &RootStyle::default()); //! //! // Set default styles that apply to the entire layout //! builder.push_default(StyleProperty::FontSize(16.0)); diff --git a/parley/src/shape/mod.rs b/parley/src/shape/mod.rs index 46d8f509f..77a8f4fb8 100644 --- a/parley/src/shape/mod.rs +++ b/parley/src/shape/mod.rs @@ -27,15 +27,10 @@ pub(crate) fn shape_text<'a, B: Brush>( analysis: &Analysis, char_style_indices: &[u16], scx: &mut Shaper, - mut text: &str, + text: &str, layout: &mut Layout, analysis_data_sources: &AnalysisDataSources, ) { - // If we have both empty text and no inline boxes, shape with a fake space - // to generate metrics that can be used to size a cursor. - if text.is_empty() && inline_boxes.is_empty() { - text = " "; - } // Do nothing if there is no text or styles (there should always be a default style) if text.is_empty() || styles.is_empty() { // Process any remaining inline boxes whose index is greater than the length of the text @@ -148,6 +143,56 @@ pub(crate) fn shape_text<'a, B: Brush>( } } +/// Selects the root font for `style` and computes its metrics and line height +/// without shaping any text. +pub(crate) fn root_font_metrics( + rcx: &ResolveContext, + fq: &mut Query<'_>, + style: &ResolvedStyle, +) -> (parley_engine::FontMetrics, f32) { + let fonts = rcx.stack(style.font_family).unwrap_or(&[]); + fq.set_families(fonts.iter().copied()); + fq.set_fallbacks(fontique::FallbackKey::new( + Script::from_bytes(*b"Latn"), + style.locale.as_ref(), + )); + fq.set_attributes(fontique::Attributes { + width: style.font_width, + weight: style.font_weight, + style: style.font_style, + }); + let mut selected: Option = None; + fq.matches_with(|font| { + selected = Some(font.clone()); + fontique::QueryStatus::Stop + }); + + let variations = rcx.variations(style.font_variations).unwrap_or(&[]); + let metrics = selected + .and_then(|font| { + parley_engine::FontMetrics::from_font( + &FontData { + data: font.blob, + index: font.index, + }, + style.font_size, + &font.synthesis, + variations, + ) + }) + .unwrap_or_default(); + + let line_height = match style.line_height { + crate::LineHeight::Absolute(value) => value, + crate::LineHeight::FontSizeRelative(value) => value * style.font_size, + crate::LineHeight::MetricsRelative(value) => { + (metrics.ascent + metrics.descent + metrics.leading) * value + } + }; + + (metrics, line_height) +} + struct FontSelector<'a, 'b, B: Brush> { query: &'b mut Query<'a>, fonts_id: Option, diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index a8f720e44..5df5b2284 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -200,6 +200,49 @@ impl Default for TextStyle<'static, 'static, B> { } } +/// Root-level style for a layout. +/// +/// The nested [`TextStyle`] acts as the default style for the layout: it is the style +/// which applies to any text that has no more specific style, and its font and +/// line-height properties determine the layout's root font metrics. These metrics are +/// used to size empty layouts and empty lines (for example, the line following a +/// trailing newline), and are exposed via [`Layout::root_style`] and +/// [`Layout::root_font_metrics`]. +/// +/// [`Layout::root_style`]: crate::Layout::root_style +/// [`Layout::root_font_metrics`]: crate::Layout::root_font_metrics +#[derive(Clone, PartialEq, Debug)] +pub struct RootStyle<'family, 'settings, B: Brush> { + /// The style of the layout's root, which is also the default style for the layout's text. + pub style: TextStyle<'family, 'settings, B>, + /// Whether the root style acts as a "strut" (in the CSS sense): if `true`, every line + /// box's metrics are floored by the metrics of the root style, as if each line began + /// with a zero-width glyph in the root style's font and line height. + /// + /// See . + pub strut: bool, +} + +impl Default for RootStyle<'static, 'static, B> { + fn default() -> Self { + Self { + style: TextStyle::default(), + strut: false, + } + } +} + +impl<'family, 'settings, B: Brush> From> + for RootStyle<'family, 'settings, B> +{ + fn from(style: TextStyle<'family, 'settings, B>) -> Self { + Self { + style, + strut: false, + } + } +} + impl<'a, B: Brush> From> for StyleProperty<'a, B> { fn from(value: FontFamily<'a>) -> Self { StyleProperty::FontFamily(value) diff --git a/parley/src/tests/test_analysis.rs b/parley/src/tests/test_analysis.rs index 7084d9c3a..8f8131e5b 100644 --- a/parley/src/tests/test_analysis.rs +++ b/parley/src/tests/test_analysis.rs @@ -150,6 +150,7 @@ fn verify_analysis( text, 1., true, + &crate::RootStyle::default(), ); // Apply test-specific configuration @@ -173,6 +174,7 @@ fn verify_analysis_with_override( text, 1., true, + &crate::RootStyle::default(), ); builder.set_line_break_override(Some(line_break_override)); _ = builder.build(text); diff --git a/parley/src/tests/test_builders.rs b/parley/src/tests/test_builders.rs index 9c48aefdc..157d494c2 100644 --- a/parley/src/tests/test_builders.rs +++ b/parley/src/tests/test_builders.rs @@ -12,7 +12,7 @@ use peniko::{Blob, color::palette}; use super::utils::{ColorBrush, asserts::assert_eq_layout_data}; use crate::{ BaseDirection, FontContext, FontFamily, FontFeatures, FontVariations, Layout, LayoutContext, - LineHeight, OverflowWrap, RangedBuilder, StyleProperty, StyleRunBuilder, TextStyle, + LineHeight, OverflowWrap, RangedBuilder, RootStyle, StyleProperty, StyleRunBuilder, TextStyle, TextWrapMode, TreeBuilder, WordBreak, }; @@ -71,11 +71,12 @@ fn create_font_context() -> FontContext { } /// Set of options for [`build_layout_with_ranged`]. -struct RangedOptions<'a> { +struct RangedOptions<'a, 'b> { scale: f32, quantize: bool, max_advance: Option, text: &'a str, + root_style: &'a RootStyle<'b, 'b, ColorBrush>, } /// Set of options for [`build_layout_with_tree`]. @@ -83,17 +84,17 @@ struct TreeOptions<'a, 'b> { scale: f32, quantize: bool, max_advance: Option, - root_style: &'a TextStyle<'b, 'b, ColorBrush>, + root_style: &'a RootStyle<'b, 'b, ColorBrush>, } /// Generates a `Layout` with a ranged builder. fn build_layout_with_ranged( fcx: &mut FontContext, lcx: &mut LayoutContext, - opts: &RangedOptions<'_>, + opts: &RangedOptions<'_, '_>, with_builder: impl Fn(&mut RangedBuilder<'_, ColorBrush>), ) -> Layout { - let mut rb = lcx.ranged_builder(fcx, opts.text, opts.scale, opts.quantize); + let mut rb = lcx.ranged_builder(fcx, opts.text, opts.scale, opts.quantize, opts.root_style); with_builder(&mut rb); let mut layout = rb.build(opts.text); layout.break_all_lines(opts.max_advance); @@ -118,10 +119,10 @@ fn build_layout_with_tree( fn build_layout_with_style_runs( fcx: &mut FontContext, lcx: &mut LayoutContext, - opts: &RangedOptions<'_>, + opts: &RangedOptions<'_, '_>, with_builder: impl Fn(&mut StyleRunBuilder<'_, ColorBrush>), ) -> Layout { - let mut rb = lcx.style_run_builder(fcx, opts.text, opts.scale, opts.quantize); + let mut rb = lcx.style_run_builder(fcx, opts.text, opts.scale, opts.quantize, opts.root_style); with_builder(&mut rb); let mut layout = rb.build(opts.text); layout.break_all_lines(opts.max_advance); @@ -134,7 +135,7 @@ fn builders_apply_base_direction() { let mut fcx = create_font_context(); let mut lcx: LayoutContext = LayoutContext::new(); - let mut ranged = lcx.ranged_builder(&mut fcx, text, 1.0, true); + let mut ranged = lcx.ranged_builder(&mut fcx, text, 1.0, true, &RootStyle::default()); ranged.push_default(FontFamily::from(FONT_FAMILY_LIST)); ranged.set_base_direction(BaseDirection::Rtl); let mut ranged_layout = ranged.build(text); @@ -149,10 +150,10 @@ fn builders_apply_base_direction() { [6..9, 3..6, 0..3] ); - let root_style = TextStyle { + let root_style = RootStyle::from(TextStyle { font_family: FontFamily::from(FONT_FAMILY_LIST), ..TextStyle::default() - }; + }); let mut tree = lcx.tree_builder(&mut fcx, 1.0, true, &root_style); tree.set_base_direction(BaseDirection::Rtl); tree.push_text(text); @@ -165,9 +166,9 @@ fn builders_apply_base_direction() { "tree base direction", ); - let mut style_runs = lcx.style_run_builder(&mut fcx, text, 1.0, true); + let mut style_runs = lcx.style_run_builder(&mut fcx, text, 1.0, true, &root_style); style_runs.set_base_direction(BaseDirection::Rtl); - let style = style_runs.push_style(root_style); + let style = style_runs.push_style(root_style.style.clone()); style_runs.push_style_run(style, ..); let mut style_run_layout = style_runs.build(text); style_run_layout.break_all_lines(None); @@ -199,7 +200,8 @@ fn assert_builders_produce_same_result<'b>( scale: f32, quantize: bool, max_advance: Option, - root_style: &TextStyle<'b, 'b, ColorBrush>, + ranged_root_style: &RootStyle<'b, 'b, ColorBrush>, + root_style: &RootStyle<'b, 'b, ColorBrush>, with_ranged_builder: impl Fn(&mut RangedBuilder<'_, ColorBrush>), with_tree_builder: impl Fn(&mut TreeBuilder<'_, ColorBrush>), expect_empty: bool, @@ -216,6 +218,7 @@ fn assert_builders_produce_same_result<'b>( quantize, max_advance, text, + root_style: ranged_root_style, }; let topts = TreeOptions { scale, @@ -281,8 +284,8 @@ fn assert_builders_produce_same_result<'b>( /// Returns a root style that uses non-default values. /// /// The [`TreeBuilder`] version of [`set_root_style`]. -fn create_root_style() -> TextStyle<'static, 'static, ColorBrush> { - TextStyle { +fn create_root_style() -> RootStyle<'static, 'static, ColorBrush> { + RootStyle::from(TextStyle { font_family: FontFamily::from(FONT_FAMILY_LIST), font_size: 20., font_width: FontWidth::CONDENSED, @@ -306,7 +309,7 @@ fn create_root_style() -> TextStyle<'static, 'static, ColorBrush> { word_break: WordBreak::BreakAll, overflow_wrap: OverflowWrap::Anywhere, text_wrap_mode: TextWrapMode::Wrap, - } + }) } /// Sets a root style with non-default values. @@ -348,10 +351,10 @@ fn builders_default() { let scale = 2.; let quantize = false; let max_advance = Some(50.); - let root_style = TextStyle { + let root_style = RootStyle::from(TextStyle { font_family: FontFamily::from(FONT_FAMILY_LIST), ..TextStyle::default() - }; + }); let with_ranged_builder = |rb: &mut RangedBuilder<'_, ColorBrush>| { rb.push_default(FontFamily::from(FONT_FAMILY_LIST)); @@ -365,6 +368,7 @@ fn builders_default() { scale, quantize, max_advance, + &RootStyle::default(), &root_style, with_ranged_builder, with_tree_builder, @@ -395,11 +399,13 @@ fn builders_style_runs_match_ranged() { let mut lcx_a: LayoutContext = LayoutContext::new(); let mut lcx_b: LayoutContext = LayoutContext::new(); + let root = RootStyle::from(root_style.clone()); let ropts = RangedOptions { scale, quantize, max_advance, text, + root_style: &root, }; let ranged = build_layout_with_ranged(&mut fcx, &mut lcx_a, &ropts, |rb| { @@ -442,7 +448,7 @@ fn style_runs_first_run_can_use_nonzero_style_index() { let scale = 2.; let quantize = false; let max_advance = Some(50.); - let root_style = create_root_style(); + let root_style = create_root_style().style; let mut modified_style = root_style.clone(); modified_style.font_size = 40.; @@ -450,11 +456,13 @@ fn style_runs_first_run_can_use_nonzero_style_index() { let mut lcx_a: LayoutContext = LayoutContext::new(); let mut lcx_b: LayoutContext = LayoutContext::new(); + let root = RootStyle::from(root_style.clone()); let ropts = RangedOptions { scale, quantize, max_advance, text, + root_style: &root, }; let ranged = build_layout_with_ranged(&mut fcx, &mut lcx_a, &ropts, |rb| { @@ -510,6 +518,7 @@ fn builders_root_only() { scale, quantize, max_advance, + &RootStyle::default(), &root_style, with_ranged_builder, with_tree_builder, @@ -535,6 +544,7 @@ fn builders_empty() { quantize, max_advance, &root_style, + &root_style, with_ranged_builder, with_tree_builder, true, @@ -589,6 +599,7 @@ fn builders_mixed_styles() { scale, quantize, max_advance, + &RootStyle::default(), &root_style, with_ranged_builder, with_tree_builder, @@ -609,17 +620,17 @@ fn ranged_builder_reuse_layout() { let mut layout = Layout::new(); - let mut builder = lcx.ranged_builder(&mut fcx, FIRST_TEXT, 1., false); + let mut builder = lcx.ranged_builder(&mut fcx, FIRST_TEXT, 1., false, &RootStyle::default()); builder.push_default(FontFamily::from(FONT_FAMILY_LIST)); builder.build_into(&mut layout, FIRST_TEXT); layout.break_all_lines(Some(MAX_ADVANCE)); - let mut builder = lcx.ranged_builder(&mut fcx, SECOND_TEXT, 1., false); + let mut builder = lcx.ranged_builder(&mut fcx, SECOND_TEXT, 1., false, &RootStyle::default()); builder.push_default(FontFamily::from(FONT_FAMILY_LIST)); builder.build_into(&mut layout, SECOND_TEXT); layout.break_all_lines(Some(MAX_ADVANCE)); - let mut builder = lcx.ranged_builder(&mut fcx, SECOND_TEXT, 1., false); + let mut builder = lcx.ranged_builder(&mut fcx, SECOND_TEXT, 1., false, &RootStyle::default()); builder.push_default(FontFamily::from(FONT_FAMILY_LIST)); let mut expected = builder.build(SECOND_TEXT); expected.break_all_lines(Some(MAX_ADVANCE)); @@ -636,11 +647,13 @@ fn builders_crlf_counts_as_single_line_break() { let mut fcx = create_font_context(); let mut line_count = |text: &str| -> usize { let mut lcx: LayoutContext = LayoutContext::new(); + let root_style = RootStyle::default(); let ropts = RangedOptions { scale: 1.0, quantize: false, max_advance: None, text, + root_style: &root_style, }; let layout = build_layout_with_ranged(&mut fcx, &mut lcx, &ropts, |rb| { set_root_style(rb); @@ -678,6 +691,106 @@ fn builders_crlf_counts_as_single_line_break() { ); } +/// Test that empty layouts derive their metrics from the root style, for all builders. +#[test] +fn root_style_sizes_empty_layout() { + let mut fcx = create_font_context(); + let mut lcx: LayoutContext = LayoutContext::new(); + + let root_style = RootStyle::from(TextStyle { + font_family: FontFamily::from(FONT_FAMILY_LIST), + font_size: 24., + line_height: LineHeight::Absolute(30.), + ..TextStyle::default() + }); + + let tree = lcx.tree_builder(&mut fcx, 1.0, false, &root_style); + let (mut tree_layout, _) = tree.build(); + tree_layout.break_all_lines(None); + + let ranged = lcx.ranged_builder(&mut fcx, "", 1.0, false, &root_style); + let mut ranged_layout = ranged.build(""); + ranged_layout.break_all_lines(None); + + for layout in [&tree_layout, &ranged_layout] { + assert_eq!(layout.root_font_size(), 24.); + assert_eq!(layout.root_line_height(), 30.); + assert!(layout.root_font_metrics().ascent > 0.); + assert_eq!(layout.len(), 1); + let line = layout.get(0).unwrap(); + assert_eq!(line.metrics().line_height, 30.); + assert!(line.metrics().baseline > 0.); + assert_eq!(line.metrics().advance, 0.); + } +} + +/// Test that the empty line following a trailing newline derives its metrics from the +/// root style rather than the preceding line. +#[test] +fn root_style_sizes_trailing_empty_line() { + let mut fcx = create_font_context(); + let mut lcx: LayoutContext = LayoutContext::new(); + + let text = "text\n"; + let root_style = RootStyle::from(TextStyle { + font_family: FontFamily::from(FONT_FAMILY_LIST), + font_size: 16., + line_height: LineHeight::Absolute(20.), + ..TextStyle::default() + }); + + let mut builder = lcx.ranged_builder(&mut fcx, text, 1.0, false, &root_style); + // Make the first line taller than the root style. + builder.push(StyleProperty::FontSize(32.), 0..text.len()); + builder.push( + StyleProperty::LineHeight(LineHeight::Absolute(40.)), + 0..text.len(), + ); + let mut layout = builder.build(text); + layout.break_all_lines(None); + + assert_eq!(layout.len(), 2); + assert_eq!(layout.get(0).unwrap().metrics().line_height, 40.); + assert_eq!(layout.get(1).unwrap().metrics().line_height, 20.); +} + +/// Test that a root style acting as a strut floors the metrics of every line box. +#[test] +fn strut_floors_line_metrics() { + let mut fcx = create_font_context(); + let mut lcx: LayoutContext = LayoutContext::new(); + + let text = "small text"; + let style = TextStyle { + font_family: FontFamily::from(FONT_FAMILY_LIST), + font_size: 12., + line_height: LineHeight::Absolute(60.), + ..TextStyle::default() + }; + + let build = |lcx: &mut LayoutContext, fcx: &mut FontContext, strut: bool| { + let root_style = RootStyle { + style: style.clone(), + strut, + }; + let mut builder = lcx.ranged_builder(fcx, text, 1.0, false, &root_style); + // Give the text a line height smaller than the root style's. + builder.push( + StyleProperty::LineHeight(LineHeight::Absolute(14.)), + 0..text.len(), + ); + let mut layout = builder.build(text); + layout.break_all_lines(None); + layout + }; + + let without_strut = build(&mut lcx, &mut fcx, false); + assert_eq!(without_strut.get(0).unwrap().metrics().line_height, 14.); + + let with_strut = build(&mut lcx, &mut fcx, true); + assert_eq!(with_strut.get(0).unwrap().metrics().line_height, 60.); +} + /// A CRLF whose `\r` and `\n` land in different shaped runs (because a style /// change starts at the `\n`) must still coalesce into a single hard break. #[test] @@ -686,11 +799,13 @@ fn builders_crlf_across_run_boundary_counts_as_single_line_break() { let styled_line_count = |fcx: &mut FontContext, text: &str, style_range: std::ops::Range| -> usize { let mut lcx: LayoutContext = LayoutContext::new(); + let root_style = RootStyle::default(); let ropts = RangedOptions { scale: 1.0, quantize: false, max_advance: None, text, + root_style: &root_style, }; let layout = build_layout_with_ranged(fcx, &mut lcx, &ropts, |rb| { set_root_style(rb); diff --git a/parley/src/tests/utils/asserts.rs b/parley/src/tests/utils/asserts.rs index d47b99142..f8a59e8d6 100644 --- a/parley/src/tests/utils/asserts.rs +++ b/parley/src/tests/utils/asserts.rs @@ -55,6 +55,22 @@ pub(crate) fn assert_eq_layout_data(a: &LayoutData, b: &LayoutData< "{case} coords mismatch" ); + // Root style + assert_eq!(a.root_style, b.root_style, "{case} root_style mismatch"); + assert_eq!( + a.root_font_size, b.root_font_size, + "{case} root_font_size mismatch" + ); + assert_eq!( + a.root_line_height, b.root_line_height, + "{case} root_line_height mismatch" + ); + assert_eq!( + a.root_font_metrics, b.root_font_metrics, + "{case} root_font_metrics mismatch" + ); + assert_eq!(a.strut, b.strut, "{case} strut mismatch"); + // Input (/ output of style resolution) assert_eq!(a.styles, b.styles, "{case} styles mismatch"); assert_eq!( diff --git a/parley_bench/src/benches.rs b/parley_bench/src/benches.rs index 841e04657..2a24847f4 100644 --- a/parley_bench/src/benches.rs +++ b/parley_bench/src/benches.rs @@ -6,6 +6,7 @@ //! This module provides benchmarks for text layout and rendering. use crate::{ColorBrush, FONT_FAMILY_LIST, get_samples, with_contexts}; +use parley::RootStyle; use parley::{ Alignment, AlignmentOptions, FontFamily, FontStyle, FontWeight, Layout, RangedBuilder, StyleProperty, @@ -30,8 +31,13 @@ pub fn defaults() -> Vec { b.iter(|| { let text = &sample.text; with_contexts(|font_cx, layout_cx| { - let mut builder = - layout_cx.ranged_builder(font_cx, text, DISPLAY_SCALE, QUANTIZE); + let mut builder = layout_cx.ranged_builder( + font_cx, + text, + DISPLAY_SCALE, + QUANTIZE, + &RootStyle::default(), + ); builder.push_default(FontFamily::from(FONT_FAMILY_LIST)); let mut layout: Layout = builder.build(text); @@ -81,8 +87,13 @@ pub fn styled() -> Vec { let text = &sample.text; with_contexts(|font_cx, layout_cx| { - let mut builder = - layout_cx.ranged_builder(font_cx, text, DISPLAY_SCALE, QUANTIZE); + let mut builder = layout_cx.ranged_builder( + font_cx, + text, + DISPLAY_SCALE, + QUANTIZE, + &RootStyle::default(), + ); builder.push_default(FontFamily::from(FONT_FAMILY_LIST)); // Apply different styles every `style_interval` characters diff --git a/parley_engine/src/shape/shaped_text.rs b/parley_engine/src/shape/shaped_text.rs index 73c63cb11..f9e5736ad 100644 --- a/parley_engine/src/shape/shaped_text.rs +++ b/parley_engine/src/shape/shaped_text.rs @@ -7,6 +7,10 @@ use core::ops::Range; use alloc::vec::Vec; +use linebender_resource_handle::FontData; +use parlance::FontVariation; +use skrifa::MetadataProvider; + use crate::{ CharInfo, FontInstance, Glyph, ShapeOptions, itemize::{Item, TextRange}, @@ -57,7 +61,7 @@ impl NormalizedCoord { // TODO: Perhaps it'd be nicer if we exposed unscaled metrics (design units) instead. This currently // just follows what `parley` used to do in its `RunMetrics`. If we go unscaled, we should then // either also store units per em, or em-normalize the values like CSS does. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct FontMetrics { /// Distance from the baseline to the top of the alignment box. pub ascent: f32, @@ -80,6 +84,69 @@ pub struct FontMetrics { pub x_height: Option, } +impl FontMetrics { + /// Compute the metrics of `font` at `font_size`, with the given synthesis and variation + /// settings applied, without shaping any text. + /// + /// Returns `None` if the font data cannot be read. + pub fn from_font( + font: &FontData, + font_size: f32, + synthesis: &fontique::Synthesis, + variations: &[FontVariation], + ) -> Option { + let font_ref = skrifa::FontRef::from_index(font.data.as_ref(), font.index).ok()?; + let location = font_ref.axes().location( + synthesis + .variation_settings() + .iter() + .map(|(tag, value)| (skrifa::Tag::new(&tag.to_be_bytes()), *value)) + .chain(variations.iter().map(|variation| { + (skrifa::Tag::new(&variation.tag.to_bytes()), variation.value) + })), + ); + let metrics = skrifa::metrics::Metrics::new( + &font_ref, + skrifa::prelude::Size::new(font_size), + &location, + ); + Some(Self::from_skrifa_metrics(&metrics)) + } + + fn from_skrifa_metrics(metrics: &skrifa::metrics::Metrics) -> Self { + let units_per_em = metrics.units_per_em as f32; + + // TODO: The following seems to be in the wrong scale, as its staying in design units rather + // than scaled to the font size like the other fields for `FontMetrics`. + let (underline_offset, underline_size) = if let Some(underline) = metrics.underline { + (underline.offset, underline.thickness) + } else { + // Default values from Harfbuzz: https://github.com/harfbuzz/harfbuzz/blob/00492ec7df0038f41f78d43d477c183e4e4c506e/src/hb-ot-metrics.cc#L334 + let default = units_per_em / 18.0; + (default, default) + }; + let (strikethrough_offset, strikethrough_size) = if let Some(strikeout) = metrics.strikeout + { + (strikeout.offset, strikeout.thickness) + } else { + // Default values from HarfBuzz: https://github.com/harfbuzz/harfbuzz/blob/00492ec7df0038f41f78d43d477c183e4e4c506e/src/hb-ot-metrics.cc#L334-L347 + (metrics.ascent / 2.0, units_per_em / 18.0) + }; + + Self { + ascent: metrics.ascent, + descent: -metrics.descent, + leading: metrics.leading, + underline_offset, + underline_size, + strikethrough_offset, + strikethrough_size, + x_height: metrics.x_height, + cap_height: metrics.cap_height, + } + } +} + /// The result of shaping. /// /// After [itemizing][crate::itemize::Item] your text, @@ -226,35 +293,7 @@ impl ShapedText { ) }; let units_per_em = metrics.units_per_em as f32; - - // TODO: The following seems to be in the wrong scale, as its staying in design units rather - // than scaled to the font size like the other fields for `FontMetrics`. - let (underline_offset, underline_size) = if let Some(underline) = metrics.underline { - (underline.offset, underline.thickness) - } else { - // Default values from Harfbuzz: https://github.com/harfbuzz/harfbuzz/blob/00492ec7df0038f41f78d43d477c183e4e4c506e/src/hb-ot-metrics.cc#L334 - let default = units_per_em / 18.0; - (default, default) - }; - let (strikethrough_offset, strikethrough_size) = if let Some(strikeout) = metrics.strikeout - { - (strikeout.offset, strikeout.thickness) - } else { - // Default values from HarfBuzz: https://github.com/harfbuzz/harfbuzz/blob/00492ec7df0038f41f78d43d477c183e4e4c506e/src/hb-ot-metrics.cc#L334-L347 - (metrics.ascent / 2.0, units_per_em / 18.0) - }; - - let font_metrics = FontMetrics { - ascent: metrics.ascent, - descent: -metrics.descent, - leading: metrics.leading, - underline_offset, - underline_size, - strikethrough_offset, - strikethrough_size, - x_height: metrics.x_height, - cap_height: metrics.cap_height, - }; + let font_metrics = FontMetrics::from_skrifa_metrics(&metrics); // `HarfRust` returns glyphs in visual order, so we need to process them as such while // maintaining logical ordering of clusters. diff --git a/parley_tests/tests/linebreaking_matches_chrome.rs b/parley_tests/tests/linebreaking_matches_chrome.rs index 87c02fc5a..247c93a63 100644 --- a/parley_tests/tests/linebreaking_matches_chrome.rs +++ b/parley_tests/tests/linebreaking_matches_chrome.rs @@ -24,6 +24,7 @@ use std::path::Path; use std::sync::Arc; use fontique::{Blob, Collection, CollectionOptions, SourceCache}; +use parley::RootStyle; use parley::{ CHROMIUM_LINE_BREAK_OVERRIDE, FontContext, FontFamily, Layout, LayoutContext, StyleProperty, }; @@ -105,7 +106,8 @@ fn check_font(font_family: &str, expected_residuals: u64) { "This code is not written to be robust against non-ASCII text, primarily to the DOM's use of UTF-16." ); - let mut builder = layout_cx.ranged_builder(&mut font_cx, &case.text, 1.0, false); + let mut builder = + layout_cx.ranged_builder(&mut font_cx, &case.text, 1.0, false, &RootStyle::default()); builder.set_line_break_override(Some(CHROMIUM_LINE_BREAK_OVERRIDE)); builder.push_default(FontFamily::named(font.family)); builder.push_default(StyleProperty::FontSize(chromium_quantized_font_size( diff --git a/parley_tests/tests/util/cursor_test.rs b/parley_tests/tests/util/cursor_test.rs index 1f55561cd..dcd2945f3 100644 --- a/parley_tests/tests/util/cursor_test.rs +++ b/parley_tests/tests/util/cursor_test.rs @@ -5,7 +5,7 @@ use peniko::Color; use vello_cpu::Pixmap; use super::renderer::{ColorBrush, RenderingConfig, draw_layout, render_to_pixmap}; -use parley::{Affinity, Cursor, FontContext, Layout, LayoutContext}; +use parley::{Affinity, Cursor, FontContext, Layout, LayoutContext, RootStyle}; // Note: This module is only compiled when running tests, which requires std, // so we don't have to worry about being no_std-compatible. @@ -50,7 +50,7 @@ impl CursorTest { lcx: &mut LayoutContext, fcx: &mut FontContext, ) -> Self { - let builder = lcx.ranged_builder(fcx, text, 1.0, true); + let builder = lcx.ranged_builder(fcx, text, 1.0, true, &RootStyle::default()); let mut layout = builder.build(text); layout.break_all_lines(None); diff --git a/parley_tests/tests/util/env.rs b/parley_tests/tests/util/env.rs index 14e8a21b4..d7a3dec71 100644 --- a/parley_tests/tests/util/env.rs +++ b/parley_tests/tests/util/env.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use fontique::{Blob, Collection, CollectionOptions, SourceCache}; use parley::{ BoundingBox, FontContext, FontFamily, FontFamilyName, Layout, LayoutContext, LineHeight, - PlainEditor, PlainEditorDriver, RangedBuilder, StyleProperty, TextStyle, TreeBuilder, + PlainEditor, PlainEditorDriver, RangedBuilder, RootStyle, StyleProperty, TreeBuilder, }; use peniko::{Color, kurbo::Size}; use vello_cpu::Pixmap; @@ -191,9 +191,13 @@ impl TestEnv { pub(crate) fn ranged_builder<'a>(&'a mut self, text: &'a str) -> RangedBuilder<'a, ColorBrush> { let default_style = self.default_style(); - let mut builder = self - .layout_cx - .ranged_builder(&mut self.font_cx, text, 1.0, true); + let mut builder = self.layout_cx.ranged_builder( + &mut self.font_cx, + text, + 1.0, + true, + &RootStyle::default(), + ); for style in default_style { builder.push_default(style); } @@ -204,7 +208,7 @@ impl TestEnv { let default_style = self.default_style(); let mut builder = self.layout_cx - .tree_builder(&mut self.font_cx, 1.0, true, &TextStyle::default()); + .tree_builder(&mut self.font_cx, 1.0, true, &RootStyle::default()); builder.push_style_modification_span(&default_style); builder }