diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ff23d8..a2297717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,18 @@ You can find its changes [documented below](#030---2025-02-27). This release has an [MSRV] of 1.82. +### Migration + +Quantization of vertical layout metrics is now optional. +For an easy upgrade we recommend enabling it by setting `quantize` to `true` when calling [`LayoutContext::ranged_builder`](https://docs.rs/parley/0.4.0/parley/struct.LayoutContext.html#method.ranged_builder) or [`LayoutContext::tree_builder`](https://docs.rs/parley/0.4.0/parley/struct.LayoutContext.html#method.tree_builder). + ### Added #### Parley - `PlainEditor::selection_geometry_with`, the equivalent of `Selection::geometry_with` method - The `WordBreak` and `OverflowWrap` style properties for controlling line wrapping. ([#315][] by [@valadaptive][]) +- Option to skip quantization of vertical layout metrics for advanced rendering use cases. ([#297][] by [@valadaptive][], [#344][] by [@xStrom]) ### Changed @@ -45,6 +51,8 @@ This release has an [MSRV] of 1.82. - Fix text editing for layouts which contain inline boxes ([#299][] by [@valadaptive][]) - Fix cursor navigation in RTL text sometimes getting stuck within a line ([#331][] by [@valadaptive][]) - Using `Layout::align` on an aligned layout without breaking lines again. ([#342][] by [@xStrom][]) +- Selection box height going below ascent + descent with small line heights. ([#344][] by [@xStrom]) +- Rounding error accumulation of vertical layout metrics. ([#344][] by [@xStrom]) ## [0.3.0] - 2025-02-27 @@ -227,6 +235,7 @@ This release has an [MSRV] of 1.70. [#280]: https://github.com/linebender/parley/pull/280 [#294]: https://github.com/linebender/parley/pull/294 [#296]: https://github.com/linebender/parley/pull/296 +[#297]: https://github.com/linebender/parley/pull/297 [#299]: https://github.com/linebender/parley/pull/299 [#300]: https://github.com/linebender/parley/pull/300 [#306]: https://github.com/linebender/parley/pull/306 @@ -235,6 +244,7 @@ This release has an [MSRV] of 1.70. [#318]: https://github.com/linebender/parley/pull/318 [#331]: https://github.com/linebender/parley/pull/331 [#342]: https://github.com/linebender/parley/pull/342 +[#344]: https://github.com/linebender/parley/pull/344 [Unreleased]: https://github.com/linebender/parley/compare/v0.3.0...HEAD [0.3.0]: https://github.com/linebender/parley/releases/tag/v0.3.0 diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index 3df00dae..49d5c579 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -44,6 +44,9 @@ fn main() { // The display scale for HiDPI rendering let display_scale = 1.0; + // Whether to automatically align the output to pixel boundaries, to avoid blurry text. + let quantize = true; + // The width for line wrapping let max_advance = Some(200.0 * display_scale); @@ -84,7 +87,8 @@ fn main() { ..Default::default() }; - let mut builder = layout_cx.tree_builder(&mut font_cx, display_scale, &root_style); + let mut builder = + layout_cx.tree_builder(&mut font_cx, display_scale, quantize, &root_style); builder.push_style_modification_span(&[bold_style]); builder.push_text(&text[0..5]); @@ -130,7 +134,7 @@ fn main() { // ============ // Creates a RangedBuilder - let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale); + let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale, quantize); // 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 195b162f..cf026bc8 100644 --- a/examples/tiny_skia_render/src/main.rs +++ b/examples/tiny_skia_render/src/main.rs @@ -47,6 +47,9 @@ fn main() { // The display scale for HiDPI rendering let display_scale = 1.0; + // Whether to automatically align the output to pixel boundaries, to avoid blurry text. + let quantize = true; + // The width for line wrapping let max_advance = Some(200.0 * display_scale); @@ -65,7 +68,7 @@ fn main() { let mut layout_cx = LayoutContext::new(); // Create a RangedBuilder - let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale); + let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale, quantize); // Set default text colour styles (set foreground text color) let foreground_brush = ColorBrush { diff --git a/parley/src/builder.rs b/parley/src/builder.rs index 8b9d05a2..ea324d40 100644 --- a/parley/src/builder.rs +++ b/parley/src/builder.rs @@ -18,6 +18,7 @@ use crate::resolve::tree::ItemKind; /// Builder for constructing a text layout with ranged attributes. pub struct RangedBuilder<'a, B: Brush> { pub(crate) scale: f32, + pub(crate) quantize: bool, pub(crate) lcx: &'a mut LayoutContext, pub(crate) fcx: &'a mut FontContext, } @@ -52,7 +53,14 @@ impl RangedBuilder<'_, B> { self.lcx.ranged_style_builder.finish(&mut self.lcx.styles); // Call generic layout builder method - build_into_layout(layout, self.scale, text.as_ref(), self.lcx, self.fcx); + build_into_layout( + layout, + self.scale, + self.quantize, + text.as_ref(), + self.lcx, + self.fcx, + ); } pub fn build(&mut self, text: impl AsRef) -> Layout { @@ -65,6 +73,7 @@ impl RangedBuilder<'_, B> { /// Builder for constructing a text layout with a tree of attributes. pub struct TreeBuilder<'a, B: Brush> { pub(crate) scale: f32, + pub(crate) quantize: bool, pub(crate) lcx: &'a mut LayoutContext, pub(crate) fcx: &'a mut FontContext, } @@ -122,7 +131,7 @@ impl TreeBuilder<'_, B> { let text = self.lcx.tree_style_builder.finish(&mut self.lcx.styles); // Call generic layout builder method - build_into_layout(layout, self.scale, &text, self.lcx, self.fcx); + build_into_layout(layout, self.scale, self.quantize, &text, self.lcx, self.fcx); text } @@ -137,6 +146,7 @@ impl TreeBuilder<'_, B> { fn build_into_layout( layout: &mut Layout, scale: f32, + quantize: bool, text: &str, lcx: &mut LayoutContext, fcx: &mut FontContext, @@ -145,6 +155,7 @@ fn build_into_layout( layout.data.clear(); layout.data.scale = scale; + layout.data.quantize = quantize; layout.data.has_bidi = !lcx.bidi.levels().is_empty(); layout.data.base_level = lcx.bidi.base_level(); layout.data.text_len = text.len(); diff --git a/parley/src/context.rs b/parley/src/context.rs index 7cfa952f..dc9898b2 100644 --- a/parley/src/context.rs +++ b/parley/src/context.rs @@ -60,11 +60,31 @@ impl LayoutContext { .resolve_entire_style_set(font_ctx, raw_style, scale) } + /// Create a ranged style layout builder. + /// + /// Set `quantize` as `true` to have the layout coordinates aligned to pixel boundaries. + /// That is the easiest way to avoid blurry text and to receive ready-to-paint layout metrics. + /// + /// For advanced rendering use cases you can set `quantize` as `false` and receive + /// fractional coordinates. This ensures the most accurate results if you want to perform + /// some post-processing on the coordinates before painting. To avoid blurry text you will + /// still need to quantize the coordinates just before painting. + /// + /// Your should round at least the following: + /// * Glyph run baseline + /// * Inline box baseline + /// - `box.y = (box.y + box.height).round() - box.height` + /// * Selection geometry's `y0` & `y1` + /// * Cursor geometry's `y0` & `y1` + /// + /// Keep in mind that for the simple `f32::round` to be effective, + /// you need to first ensure the coordinates are in physical pixel space. pub fn ranged_builder<'a>( &'a mut self, fcx: &'a mut FontContext, text: &'a str, scale: f32, + quantize: bool, ) -> RangedBuilder<'a, B> { self.begin(); self.ranged_style_builder.begin(text.len()); @@ -73,15 +93,36 @@ impl LayoutContext { RangedBuilder { scale, + quantize, lcx: self, fcx, } } + /// Create a tree style layout builder. + /// + /// Set `quantize` as `true` to have the layout coordinates aligned to pixel boundaries. + /// That is the easiest way to avoid blurry text and to receive ready-to-paint layout metrics. + /// + /// For advanced rendering use cases you can set `quantize` as `false` and receive + /// fractional coordinates. This ensures the most accurate results if you want to perform + /// some post-processing on the coordinates before painting. To avoid blurry text you will + /// still need to quantize the coordinates just before painting. + /// + /// Your should round at least the following: + /// * Glyph run baseline + /// * Inline box baseline + /// - `box.y = (box.y + box.height).round() - box.height` + /// * Selection geometry's `y0` & `y1` + /// * Cursor geometry's `y0` & `y1` + /// + /// Keep in mind that for the simple `f32::round` to be effective, + /// you need to first ensure the coordinates are in physical pixel space. pub fn tree_builder<'a>( &'a mut self, fcx: &'a mut FontContext, scale: f32, + quantize: bool, raw_style: &TextStyle<'_, B>, ) -> TreeBuilder<'a, B> { self.begin(); @@ -93,6 +134,7 @@ impl LayoutContext { TreeBuilder { scale, + quantize, lcx: self, fcx, } diff --git a/parley/src/layout/cluster.rs b/parley/src/layout/cluster.rs index 80ef6b8d..d2a6f741 100644 --- a/parley/src/layout/cluster.rs +++ b/parley/src/layout/cluster.rs @@ -500,7 +500,7 @@ 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); + let mut builder = layout_ctx.ranged_builder(&mut font_ctx, text, 1.0, true); 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 e97eb8a4..5f10fa99 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -197,6 +197,7 @@ pub(crate) struct LayoutItem { #[derive(Clone)] pub(crate) struct LayoutData { pub(crate) scale: f32, + pub(crate) quantize: bool, pub(crate) has_bidi: bool, pub(crate) base_level: u8, pub(crate) text_len: usize, @@ -234,6 +235,7 @@ impl Default for LayoutData { fn default() -> Self { Self { scale: 1., + quantize: true, has_bidi: false, base_level: 0, text_len: 0, @@ -260,6 +262,7 @@ impl Default for LayoutData { impl LayoutData { pub(crate) fn clear(&mut self) { self.scale = 1.; + self.quantize = true; self.has_bidi = false; self.base_level = 0; self.text_len = 0; diff --git a/parley/src/layout/editor.rs b/parley/src/layout/editor.rs index f7fd169c..7773d338 100644 --- a/parley/src/layout/editor.rs +++ b/parley/src/layout/editor.rs @@ -114,6 +114,7 @@ where show_cursor: bool, width: Option, scale: f32, + quantize: bool, // Simple tracking of when the layout needs to be updated // before it can be used for `Selection` calculations or // for drawing. @@ -145,6 +146,7 @@ where show_cursor: true, width: None, scale: 1.0, + quantize: true, layout_dirty: true, alignment: Alignment::Start, // We don't use the `default` value to start with, as our consumers @@ -904,6 +906,30 @@ where self.layout_dirty = true; } + /// Set whether to quantize the layout coordinates. + /// + /// Set `quantize` as `true` to have the layout coordinates aligned to pixel boundaries. + /// That is the easiest way to avoid blurry text and to receive ready-to-paint layout metrics. + /// + /// For advanced rendering use cases you can set `quantize` as `false` and receive + /// fractional coordinates. This ensures the most accurate results if you want to perform + /// some post-processing on the coordinates before painting. To avoid blurry text you will + /// still need to quantize the coordinates just before painting. + /// + /// Your should round at least the following: + /// * Glyph run baseline + /// * Inline box baseline + /// - `box.y = (box.y + box.height).round() - box.height` + /// * Selection geometry's `y0` & `y1` + /// * Cursor geometry's `y0` & `y1` + /// + /// Keep in mind that for the simple `f32::round` to be effective, + /// you need to first ensure the coordinates are in physical pixel space. + pub fn set_quantize(&mut self, quantize: bool) { + self.quantize = quantize; + self.layout_dirty = true; + } + /// Modify the styles provided for this editor. pub fn edit_styles(&mut self) -> &mut StyleSet { self.layout_dirty = true; @@ -1057,7 +1083,8 @@ 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); + let mut builder = + layout_cx.ranged_builder(font_cx, &self.buffer, self.scale, self.quantize); for prop in self.default_style.inner().values() { builder.push_default(prop.to_owned()); } diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index 6e99d829..0bcbb9bf 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/layout/line/greedy.rs @@ -419,6 +419,8 @@ impl<'a, B: Brush> BreakLines<'a, B> { /// Consumes the line breaker and finalizes all line computations. pub fn finish(mut self) { + // Whether metrics should be quantized to pixel boundaries + let quantize = self.layout.data.quantize; // For each run (item which is a text run): // - Determine if it consists entirely of whitespace (is_whitespace property) // - Determine if it has trailing whitespace (has_trailing_whitespace property) @@ -454,7 +456,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { } } } - let mut y = 0.; + let mut y: f64 = 0.; // f32 causes test failures due to accumulated error let mut prev_line_metrics = None; for line in &mut self.lines.lines { // Reset metrics for line @@ -605,13 +607,41 @@ impl<'a, B: Brush> BreakLines<'a, B> { line.metrics.leading = line.metrics.line_height - (line.metrics.ascent + line.metrics.descent); - // Compute - let above = line.metrics.ascent + line.metrics.leading * 0.5; - let below = line.metrics.descent + line.metrics.leading * 0.5; - line.metrics.min_coord = y; - line.metrics.baseline = y + above; - y = line.metrics.baseline + below; - line.metrics.max_coord = y; + let (ascent, descent) = if quantize { + // We mimic Chrome in rounding ascent and descent separately, + // before calculating the rest. + // See lines_integral_line_height_ascent_descent_rounding() for more details. + (line.metrics.ascent.round(), line.metrics.descent.round()) + } else { + (line.metrics.ascent, line.metrics.descent) + }; + + let (leading_above, leading_below) = if quantize { + // Calculate leading using the rounded ascent and descent. + let leading = line.metrics.line_height - (ascent + descent); + // We mimic Chrome in giving 'below' the larger leading half. + // Although the comment in Chromium's NGLineHeightMetrics::AddLeading function + // in ng_line_height_metrics.cc claims it's for legacy test compatibility. + // So we might want to think about giving 'above' the larger half instead. + let above = (leading * 0.5).floor(); + let below = leading.round() - above; + (above, below) + } else { + (line.metrics.leading * 0.5, line.metrics.leading * 0.5) + }; + + line.metrics.baseline = + ascent + leading_above + if quantize { y.round() as f32 } else { y as f32 }; + + // Small line heights will cause leading to be negative. + // Negative leadings are correct for baseline calculation, but not for min/max coords. + // We clamp leading to zero for the purposes of min/max coords, + // which in turn clamps the selection box minimum height to ascent + descent. + line.metrics.min_coord = line.metrics.baseline - ascent - leading_above.max(0.); + line.metrics.max_coord = line.metrics.baseline + descent + leading_below.max(0.); + + y += line.metrics.line_height as f64; + prev_line_metrics = Some(line.metrics); } if self.layout.data.text_len == 0 { @@ -629,17 +659,17 @@ impl Drop for BreakLines<'_, B> { // The "width" excludes trailing whitespace. The "full_width" includes it. let mut width = 0_f32; let mut full_width = 0_f32; - let mut height = 0_f32; + let mut height = 0_f64; // f32 causes test failures due to accumulated error for line in &self.lines.lines { width = width.max(line.metrics.advance - line.metrics.trailing_whitespace); full_width = full_width.max(line.metrics.advance); - height = height.max(line.metrics.max_coord); + height += line.metrics.line_height as f64; } // Save the computed widths/height to the layout self.layout.data.width = width; self.layout.data.full_width = full_width; - self.layout.data.height = height; + self.layout.data.height = height as f32; // for (i, line) in self.lines.lines.iter().enumerate() { // println!("LINE {i}"); diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 777b4100..41488249 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -35,7 +35,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); +//! let mut builder = layout_cx.ranged_builder(&mut font_cx, &TEXT, DISPLAY_SCALE, true); //! //! // Set default styles that apply to the entire layout //! builder.push_default(StyleProperty::LineHeight(1.3)); diff --git a/parley/src/tests/mod.rs b/parley/src/tests/mod.rs index d79607d7..be38a623 100644 --- a/parley/src/tests/mod.rs +++ b/parley/src/tests/mod.rs @@ -4,5 +4,6 @@ mod test_basic; mod test_cursor; mod test_editor; +mod test_lines; mod test_wrap; mod utils; diff --git a/parley/src/tests/test_basic.rs b/parley/src/tests/test_basic.rs index 688a848b..d0f6b7f2 100644 --- a/parley/src/tests/test_basic.rs +++ b/parley/src/tests/test_basic.rs @@ -4,11 +4,13 @@ use peniko::kurbo::Size; use crate::data::LayoutData; -use crate::{Alignment, AlignmentOptions, Brush, InlineBox, WhiteSpaceCollapse, testenv}; +use crate::{Alignment, AlignmentOptions, Brush, InlineBox, WhiteSpaceCollapse, test_name}; + +use super::utils::TestEnv; #[test] fn plain_multiline_text() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "Hello world!\nLine 2\nLine 4"; let mut builder = env.ranged_builder(text); @@ -21,7 +23,7 @@ fn plain_multiline_text() { #[test] fn placing_inboxes() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); for (position, test_case_name) in [ (0, "start"), @@ -46,7 +48,7 @@ fn placing_inboxes() { #[test] fn only_inboxes_wrap() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = ""; let mut builder = env.ranged_builder(text); @@ -67,7 +69,7 @@ fn only_inboxes_wrap() { #[test] fn full_width_inbox() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); for (width, test_case_name) in [(99., "smaller"), (100., "exact"), (101., "larger")] { let text = "ABC"; @@ -99,7 +101,7 @@ fn full_width_inbox() { #[test] fn inbox_separated_by_whitespace() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let mut builder = env.tree_builder(); builder.push_inline_box(InlineBox { @@ -137,7 +139,7 @@ fn inbox_separated_by_whitespace() { #[test] fn trailing_whitespace() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "AAA BBB"; let mut builder = env.ranged_builder(text); @@ -155,7 +157,7 @@ fn trailing_whitespace() { #[test] fn leading_whitespace() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); for (mode, test_case_name) in [ (WhiteSpaceCollapse::Preserve, "preserve"), @@ -179,7 +181,7 @@ fn leading_whitespace() { #[test] fn base_level_alignment_ltr() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); for (alignment, test_case_name) in [ (Alignment::Start, "start"), @@ -198,7 +200,7 @@ fn base_level_alignment_ltr() { #[test] fn base_level_alignment_rtl() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); for (alignment, test_case_name) in [ (Alignment::Start, "start"), @@ -219,7 +221,7 @@ fn base_level_alignment_rtl() { /// On overflow without alignment-on-overflow, RTL-text should be start-aligned (i.e., aligned to /// the right edge, overflowing on the left). fn overflow_alignment_rtl() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "عند برمجة أجهزة الكمبيوتر، قد تجد نفسك فجأة في مواقف غريبة، مثل الكتابة بلغة لا تتحدثها فعليًا."; let mut builder = env.ranged_builder(text); @@ -232,7 +234,7 @@ fn overflow_alignment_rtl() { #[test] fn content_widths() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "Hello world!\nLonger line with a looooooooong word."; let mut builder = env.ranged_builder(text); @@ -250,7 +252,7 @@ fn content_widths() { #[test] fn content_widths_rtl() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "بببب ااااا"; let mut builder = env.ranged_builder(text); @@ -272,7 +274,7 @@ fn content_widths_rtl() { #[test] fn inbox_content_width() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); { let text = "Hello world!"; @@ -316,7 +318,7 @@ fn inbox_content_width() { #[test] /// Layouts can be re-line-breaked and re-aligned. fn realign() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; let mut builder = env.ranged_builder(text); @@ -340,7 +342,7 @@ fn realign() { /// /// Rendering 684 snapshots takes over 10 seconds on a 5950X, so a simpler assert is used instead. fn realign_all() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let latin = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; let arabic = "عند برمجة أجهزة الكمبيوتر، قد تجد نفسك فجأة في مواقف غريبة، مثل الكتابة بلغة لا تتحدثها فعليًا."; diff --git a/parley/src/tests/test_editor.rs b/parley/src/tests/test_editor.rs index 74c83c23..04de47b6 100644 --- a/parley/src/tests/test_editor.rs +++ b/parley/src/tests/test_editor.rs @@ -1,13 +1,15 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::testenv; +use crate::test_name; + +use super::utils::TestEnv; // TODO - Use CursorTest API for these tests #[test] fn editor_simple_move() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let mut editor = env.editor("Hi, all!\nNext"); env.check_editor_snapshot(&mut editor); let mut drv = env.driver(&mut editor); @@ -26,7 +28,7 @@ fn editor_simple_move() { #[test] fn editor_select_all() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let mut editor = env.editor("Hi, all!\nNext"); env.driver(&mut editor).select_all(); env.check_editor_snapshot(&mut editor); @@ -34,7 +36,7 @@ fn editor_select_all() { #[test] fn editor_double_newline() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let mut editor = env.editor("Hi, all!\n\nNext"); env.driver(&mut editor).select_all(); env.check_editor_snapshot(&mut editor); diff --git a/parley/src/tests/test_lines.rs b/parley/src/tests/test_lines.rs new file mode 100644 index 00000000..14ffe7fd --- /dev/null +++ b/parley/src/tests/test_lines.rs @@ -0,0 +1,541 @@ +// Copyright 2025 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Test line layouts, including the vertical size and positioning of the line box. + +use peniko::kurbo::{Rect, Size}; + +use super::utils::{ColorBrush, TestEnv}; +use crate::{Affinity, Brush, Cursor, InlineBox, Layout, Selection, StyleProperty, test_name}; + +const TEXT: &str = "Some text here. Let's make\n\ + it a bit longer so that\n \ + we have more lines.\n\ + And also some latin text for\n\ + this spot right here.\n\ + This is underline and\n\ + strikethrough text and some\n\ + extra more text\n\ + and some extra more text\nand some extra more text\n\ + and some extra more text\nand some extra more text\n\ + and some extra more text\nand some extra more text"; + +const LINE_COUNT: f32 = 14.; + +/// Returns the integral max advance that these tests are designed around. +fn max_advance(font_size: f32) -> f32 { + (200. / 16. * font_size).ceil() +} + +/// Returns the precise ascent and descent of Roboto. +fn roboto_ascent_descent(font_size: f32) -> (f32, f32) { + // We calculate it based on these 16px values + let ascent = 14.84375; + let descent = 3.90625; + (ascent / 16. * font_size, descent / 16. * font_size) +} + +/// Returns the expected outputs for ascent, descent, and line box height. +fn ascent_descent_box_height(font_size: f32, line_height_px: f32) -> (f32, f32, f32) { + let (ascent, descent) = roboto_ascent_descent(font_size); + // Ascent and descent must be rounded separately to match the line box height of Chrome. + // See lines_integral_line_height_ascent_descent_rounding() for more details. + let ascent_descent = ascent.round() + descent.round(); + // Line box height does not get reduced by negative leading, so clamp leading to zero. + let line_box_height = ascent_descent + (line_height_px.round() - ascent_descent).max(0.); + (ascent, descent, line_box_height) +} + +/// Returns selection geometry such that every line is covered. +fn get_selections(layout: &Layout) -> Vec<(Rect, usize)> { + let selection_parts = [ + ("Some text here.", 4), + ("a bit longer", 8), + ("we have more", 5), + ("also some latin", 9), + ("this spot", 7), + ("is under", 6), + ("ough text", 4), + ("more", 4), + ("me ex", 5), + ("some", 4), + ("me ex", 5), + ("some", 4), + ("me ex", 5), + ("some", 4), + ]; + + let mut selections = Vec::new(); + let mut idx = 0; + for (substr, len) in selection_parts { + let i = idx + TEXT[idx..].find(substr).unwrap(); + let j = i + len; + idx = j; + let selection = Selection::new( + Cursor::from_byte_index(layout, i, Affinity::Downstream), + Cursor::from_byte_index(layout, j, Affinity::Downstream), + ); + selections.extend(selection.geometry(layout)); + } + selections +} + +/// Returns the layout. +fn build_layout>>( + env: &mut TestEnv, + font_size: f32, + line_height: f32, + max_advance: A, +) -> Layout { + let mut builder = env.ranged_builder(TEXT); + builder.push_default(StyleProperty::FontSize(font_size)); + builder.push_default(StyleProperty::LineHeight(line_height)); + + let underline_style = StyleProperty::Underline(true); + let strikethrough_style = StyleProperty::Strikethrough(true); + + // Set the underline & strikethrough style + let pos_u = TEXT.find("underline").unwrap(); + builder.push(underline_style, pos_u..pos_u + "underline".len()); + let pos_s = TEXT.find("strikethrough").unwrap(); + builder.push(strikethrough_style, pos_s..pos_s + "strikethrough".len()); + + builder.push_inline_box(InlineBox { + id: 0, + index: 40, + width: 50.0, + height: 5.0, + }); + builder.push_inline_box(InlineBox { + id: 1, + index: 51, + width: 50.0, + height: 3.0, + }); + + let mut layout = builder.build(TEXT); + layout.break_all_lines(max_advance.into()); + layout +} + +/// Returns the test environment, the layout, and the selections. +fn compute( + test_name: &str, + font_size: f32, + line_height_px: f32, +) -> (TestEnv, Layout, Vec<(Rect, usize)>) { + // Use max advance as the target width to ensure consistency in case of early line breaks + let width = max_advance(font_size); + // Calculate precise total height based on requested line height, + // then round it to match Chrome. + let height = (LINE_COUNT * line_height_px).round(); + // Compute the layout + let size = Size::new(width as f64, height as f64); + let mut env = TestEnv::new(test_name, size); + let layout = build_layout(&mut env, font_size, line_height_px / font_size, width); + let selections = get_selections(&layout); + (env, layout, selections) +} + +/// Assert expectations that are common across tests. +fn assert_common_truths( + layout: &Layout, + ascent: f32, + descent: f32, + line_box_height: f32, + line_height_px: f32, +) { + let layout_height = LINE_COUNT * line_height_px; + assert_eq!( + layout.height(), + layout_height, + "expected layout height {layout_height}" + ); + for line in layout.lines() { + let metrics = line.metrics(); + assert_eq!(metrics.ascent, ascent, "expected ascent {ascent}"); + assert_eq!(metrics.descent, descent, "expected descent {descent}"); + assert_eq!( + metrics.max_coord - metrics.min_coord, + line_box_height, + "expected line box height {line_box_height}" + ); + assert_eq!( + metrics.line_height, line_height_px, + "expected line height {line_height_px}" + ); + assert_eq!(metrics.baseline.fract(), 0., "expected integral baseline"); + } +} + +/// Test the happy path of integral line height with no leading. +#[test] +fn lines_integral_line_height_zero_leading() { + // Inputs + let font_size = 16.0; + let line_height_px = 19.0; + + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name!(), font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + ascent.fract() >= 0.5, + "expected ascent {ascent} to round up" + ); + assert!( + descent.fract() >= 0.5, + "expected descent {descent} to round up" + ); + let leading = metrics.leading - (1. - ascent.fract()) - (1. - descent.fract()); + assert_eq!(leading, 0., "expected zero leading"); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test integral line height that gives a negative leading of -1. +/// +/// The line box height must not be reduced by the negative leading. +#[test] +fn lines_integral_line_height_minus_one_leading() { + // Inputs + let font_size = 17.0; + let line_height_px = 19.0; + + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name!(), font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + ascent.fract() >= 0.5, + "expected ascent {ascent} to round up" + ); + assert!( + descent.fract() < 0.5, + "expected descent {descent} to round down" + ); + let leading = metrics.leading - (1. - ascent.fract()) + descent.fract(); + assert_eq!(leading, -1., "expected -1 leading"); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test integral line height that gives a positive leading of 1. +/// +/// The line box height must be increased by the leading on the correct side of the baseline. +#[test] +fn lines_integral_line_height_plus_one_leading() { + // Inputs + let font_size = 15.0; + let line_height_px = 19.0; + + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name!(), font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + ascent.fract() >= 0.5, + "expected ascent {ascent} to round up" + ); + assert!( + descent.fract() >= 0.5, + "expected descent {descent} to round up" + ); + let leading = metrics.leading - (1. - ascent.fract()) - (1. - descent.fract()); + assert_eq!(leading, 1., "expected +1 leading"); + + let above = line.metrics().baseline - line.metrics().min_coord; + assert_eq!( + above, + ascent.round(), + "expected above to be exactly rounded ascent {}", + ascent.round() + ); + let below = line.metrics().max_coord - line.metrics().baseline; + assert_eq!( + below, + descent.round() + 1., + "expected below to be exactly rounded descent {} + 1. = {}", + descent.round(), + descent.round() + 1. + ); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test that ascent and descent are rounded separately and then summed. +/// +/// With Roboto 19.0px the ascent and descent are 17.626953 and 4.638672. +/// When rounding before summing we get 23, when rounding after summing we get 22. +/// Chrome renders the selection box with a height of 23px. +/// +/// Roboto 20.0px would be another example with 18.554688 and 4.8828125. +#[test] +fn lines_integral_line_height_ascent_descent_rounding() { + // Inputs + let font_size = 19.0; + let line_height_px = 20.0; // Just something lower than 23.0 (ascent+descent) + + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name!(), font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + for line in layout.lines() { + let ascent_descent_round_before_sum = + line.metrics().ascent.round() + line.metrics().descent.round(); + let ascent_descent_round_after_sum = + (line.metrics().ascent + line.metrics().descent).round(); + assert_ne!( + ascent_descent_round_before_sum, ascent_descent_round_after_sum, + "expected ascent and descent to be such that the ordering of round and sum matters" + ); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test handling of line height that rounds up both individually and also as the total layout sum. +#[test] +fn lines_line_height_rounds_up() { + // Inputs + let font_size = 16.0; + let line_height_px = 20.7; // Greater than ascent + descent + + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name!(), font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + assert!( + layout.height().fract() >= 0.5, + "expected layout height to be fractional and round up" + ); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + ascent + descent < metrics.line_height, + "expected line height {} to be greater than {} (ascent {} + descent {})", + metrics.line_height, + ascent + descent, + ascent, + descent, + ); + assert!( + metrics.line_height.fract() >= 0.5, + "expected line height {} to be fractional and round up", + metrics.line_height, + ); + assert_eq!( + metrics.line_height.ceil(), + line_box_height, + "expected line box height {} to equal line height {} rounded up", + line_box_height, + metrics.line_height, + ); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test handling of line height that rounds down both individually and also as the total layout sum. +#[test] +fn lines_line_height_rounds_down() { + // Inputs + let font_size = 16.0; + let line_height_px = 20.3; // Greater than ascent + descent + + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name!(), font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + assert!( + layout.height().fract() > 0. && layout.height().fract() < 0.5, + "expected layout height to be fractional and round down" + ); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + ascent + descent < metrics.line_height, + "expected line height {} to be greater than {} (ascent {} + descent {})", + metrics.line_height, + ascent + descent, + ascent, + descent, + ); + assert!( + metrics.line_height.fract() > 0. && metrics.line_height.fract() < 0.5, + "expected line height {} to be fractional and round down", + metrics.line_height, + ); + assert_eq!( + metrics.line_height.floor(), + line_box_height, + "expected line box height {} to equal line height {} rounded down", + line_box_height, + metrics.line_height, + ); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test fractional line height with a negative leading. +/// +/// The line box height must not be reduced by the negative leading. +fn lines_fractional_line_height_negative_leading_internal( + test_name: &str, + font_size: f32, + line_height_px: f32, +) { + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name, font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + metrics.leading < 0., + "expected negative leading, but got {}", + metrics.leading + ); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test fractional line height with a negative leading. +/// +/// The line box height must not be reduced by the negative leading. +#[test] +fn lines_fractional_line_height_negative_leading() { + // Inputs + let font_size = 16.0; + let line_height_px = 12.75; + + // Run the test + lines_fractional_line_height_negative_leading_internal(test_name!(), font_size, line_height_px); +} + +/// Test fractional line height with a big negative leading. +/// +/// The line box height must not be reduced by the negative leading. +/// +/// NOTE: Going even lower (than the 0.675em in this test) will start divergence from Chrome. +#[test] +fn lines_fractional_line_height_big_negative_leading() { + // Inputs + let font_size = 16.0; + let line_height_px = 10.8; + + // Run the test + lines_fractional_line_height_negative_leading_internal(test_name!(), font_size, line_height_px); +} + +/// Test fractional line height with a positive leading. +/// +/// The line box height must be increased by the leading, +/// divided correctly between the sides of the baseline. +fn lines_fractional_line_height_positive_leading_internal( + test_name: &str, + font_size: f32, + line_height_px: f32, +) { + // Expected outputs + let (ascent, descent, line_box_height) = ascent_descent_box_height(font_size, line_height_px); + + // Compute + let (mut env, layout, selections) = compute(test_name, font_size, line_height_px); + + // Confirm metrics + assert_common_truths(&layout, ascent, descent, line_box_height, line_height_px); + for line in layout.lines() { + let metrics = line.metrics(); + assert!( + metrics.leading > 0., + "expected positive leading, but got {}", + metrics.leading + ); + + let above = metrics.baseline - metrics.min_coord; + let below = metrics.max_coord - metrics.baseline; + let above_leading = above - ascent.round(); + let below_leading = below - descent.round(); + assert!( + above_leading < below_leading, + "expected above leading {above_leading} to be less than below leading {below_leading}" + ); + } + + // Verify visuals + env.render_and_check_snapshot(&layout, None, &selections); +} + +/// Test fractional line height with a positive leading. +/// +/// The line box height must be increased by the leading, +/// divided correctly between the sides of the baseline. +#[test] +fn lines_fractional_line_height_positive_leading() { + // Inputs + let font_size = 16.0; + let line_height_px = 24.1; + + // Run the test + lines_fractional_line_height_positive_leading_internal(test_name!(), font_size, line_height_px); +} + +/// Test fractional line height with a big positive leading. +/// +/// The line box height must be increased by the leading, +/// divided correctly between the sides of the baseline. +#[test] +fn lines_fractional_line_height_big_positive_leading() { + // Inputs + let font_size = 16.0; + let line_height_px = 46.66; + + lines_fractional_line_height_positive_leading_internal(test_name!(), font_size, line_height_px); +} diff --git a/parley/src/tests/test_wrap.rs b/parley/src/tests/test_wrap.rs index 8f63e4a7..bc7ecf4f 100644 --- a/parley/src/tests/test_wrap.rs +++ b/parley/src/tests/test_wrap.rs @@ -3,7 +3,9 @@ use peniko::color::palette::css; -use crate::{Alignment, AlignmentOptions, OverflowWrap, StyleProperty, WordBreakStrength, testenv}; +use crate::{ + Alignment, AlignmentOptions, OverflowWrap, StyleProperty, WordBreakStrength, test_name, +}; use super::utils::{ColorBrush, TestEnv}; @@ -52,7 +54,7 @@ fn test_wrap_with_custom_text( #[test] fn overflow_wrap_off() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -65,7 +67,7 @@ fn overflow_wrap_off() { #[test] fn overflow_wrap_first_half() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -78,7 +80,7 @@ fn overflow_wrap_first_half() { #[test] fn overflow_wrap_second_half() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -91,7 +93,7 @@ fn overflow_wrap_second_half() { #[test] fn overflow_wrap_during() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -104,7 +106,7 @@ fn overflow_wrap_during() { #[test] fn overflow_wrap_everywhere() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -117,7 +119,7 @@ fn overflow_wrap_everywhere() { #[test] fn overflow_wrap_narrow() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -130,7 +132,7 @@ fn overflow_wrap_narrow() { #[test] fn overflow_wrap_anywhere_min_content_width() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "Hello world!\nLonger line with a looooooooong word."; let mut builder = env.ranged_builder(text); @@ -145,7 +147,7 @@ fn overflow_wrap_anywhere_min_content_width() { #[test] fn overflow_wrap_break_word_min_content_width() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "Hello world!\nLonger line with a looooooooong word."; let mut builder = env.ranged_builder(text); @@ -160,7 +162,7 @@ fn overflow_wrap_break_word_min_content_width() { #[test] fn word_break_break_all_first_half() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -173,7 +175,7 @@ fn word_break_break_all_first_half() { #[test] fn word_break_break_all_second_half() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -186,7 +188,7 @@ fn word_break_break_all_second_half() { #[test] fn word_break_break_all_during() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -199,7 +201,7 @@ fn word_break_break_all_during() { #[test] fn word_break_break_all_everywhere() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap( &mut env, @@ -212,7 +214,7 @@ fn word_break_break_all_everywhere() { #[test] fn word_break_break_all_min_content_width() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let text = "Hello world!\nLonger line with a looooooooong word."; let mut builder = env.ranged_builder(text); @@ -233,7 +235,7 @@ fn word_break_wpt007() { // // All browsers fail this currently, but we pass it. This means that word_break_break_all_first_half doesn't match // what any browsers do currently, but should be theoretically correct. - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); test_wrap_with_custom_text( &mut env, @@ -247,7 +249,7 @@ fn word_break_wpt007() { #[test] fn word_break_keep_all() { - let mut env = testenv!(); + let mut env = TestEnv::new(test_name!(), None); let mut test_text = |text, name, wrap_width| { let mut builder = env.ranged_builder(text); diff --git a/parley/src/tests/utils/cursor_test.rs b/parley/src/tests/utils/cursor_test.rs index d1a16d48..5b54eeff 100644 --- a/parley/src/tests/utils/cursor_test.rs +++ b/parley/src/tests/utils/cursor_test.rs @@ -49,7 +49,7 @@ impl CursorTest { lcx: &mut LayoutContext, fcx: &mut FontContext, ) -> Self { - let mut builder = lcx.ranged_builder(fcx, text, 1.0); + let mut builder = lcx.ranged_builder(fcx, text, 1.0, true); let mut layout = builder.build(text); layout.break_all_lines(None); @@ -185,19 +185,25 @@ impl CursorTest { let bg_color_expected = Color::from_rgba8(255, 255, 255, 255); let padding_color_expected = Color::from_rgba8(166, 200, 255, 255); - let cursor_color_expected = Color::from_rgba8(0, 255, 0, 255); - let selection_color_expected = Color::from_rgba8(0, 255, 0, 200); + let cursor_color_expected = Color::from_rgba8(0, 0, 255, 255); + let selection_colors_expected = [ + Color::from_rgba8(0, 255, 0, 255), + Color::from_rgba8(255, 0, 0, 255), + ]; let bg_color_actual = Color::from_rgba8(230, 230, 230, 255); let padding_color_actual = Color::from_rgba8(166, 255, 240, 255); - let cursor_color_actual = Color::from_rgba8(255, 0, 0, 255); - let selection_color_actual = Color::from_rgba8(255, 0, 0, 200); + let cursor_color_actual = Color::from_rgba8(0, 0, 128, 255); + let selection_colors_actual = [ + Color::from_rgba8(0, 128, 0, 255), + Color::from_rgba8(128, 0, 0, 255), + ]; let rendering_config_expected = RenderingConfig { background_color: bg_color_expected, padding_color: padding_color_expected, inline_box_color: bg_color_expected, cursor_color: cursor_color_expected, - selection_color: selection_color_expected, + selection_colors: selection_colors_expected, size: None, }; let rendering_config_actual = RenderingConfig { @@ -205,7 +211,7 @@ impl CursorTest { padding_color: padding_color_actual, inline_box_color: bg_color_actual, cursor_color: cursor_color_actual, - selection_color: selection_color_actual, + selection_colors: selection_colors_actual, size: None, }; @@ -354,17 +360,21 @@ impl CursorTest { #[allow(clippy::print_stderr)] #[allow(dead_code)] pub(crate) fn render_cursor(&self, cursor: Cursor) { - let bg_color_cursor = Color::from_rgba8(255, 255, 255, 255); - let padding_color_cursor = Color::from_rgba8(166, 200, 255, 255); - let cursor_color_cursor = Color::from_rgba8(0, 255, 0, 255); - let selection_color_cursor = Color::from_rgba8(0, 255, 0, 200); + let background_color = Color::from_rgba8(255, 255, 255, 255); + let padding_color = Color::from_rgba8(166, 200, 255, 255); + let inline_box_color = Color::from_rgba8(0, 0, 0, 255); + let cursor_color = Color::from_rgba8(0, 0, 255, 255); + let selection_colors = [ + Color::from_rgba8(0, 255, 0, 255), + Color::from_rgba8(255, 0, 0, 255), + ]; let rendering_config_cursor = RenderingConfig { - background_color: bg_color_cursor, - padding_color: padding_color_cursor, - inline_box_color: bg_color_cursor, - cursor_color: cursor_color_cursor, - selection_color: selection_color_cursor, + background_color, + padding_color, + inline_box_color, + cursor_color, + selection_colors, size: None, }; diff --git a/parley/src/tests/utils/env.rs b/parley/src/tests/utils/env.rs index 0000165b..9fc1462c 100644 --- a/parley/src/tests/utils/env.rs +++ b/parley/src/tests/utils/env.rs @@ -7,6 +7,7 @@ use crate::{ RangedBuilder, Rect, StyleProperty, TextStyle, TreeBuilder, }; use fontique::{Blob, Collection, CollectionOptions}; +use peniko::kurbo::Size; use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -14,11 +15,10 @@ use std::{ }; use tiny_skia::{Color, Pixmap}; -// Creates a new instance of TestEnv and put current function name in constructor +// Returns the current function name #[macro_export] -macro_rules! testenv { +macro_rules! test_name { () => {{ - // Get name of the current function fn f() {} fn type_name_of(_: T) -> &'static str { std::any::type_name::() @@ -27,8 +27,7 @@ macro_rules! testenv { let name = &name[..name.len() - 3]; let name = &name[name.rfind(':').map(|x| x + 1).unwrap_or(0)..]; - // Create test env - $crate::tests::utils::TestEnv::new(name) + name }}; } @@ -116,7 +115,7 @@ pub(crate) fn load_fonts( } impl TestEnv { - pub(crate) fn new(test_name: &str) -> Self { + pub(crate) fn new>>(test_name: &str, size: S) -> Self { let file_prefix = format!("{test_name}-"); let entries = std::fs::read_dir(current_imgs_dir()).unwrap(); for entry in entries.flatten() { @@ -156,10 +155,13 @@ impl TestEnv { rendering_config: RenderingConfig { background_color: Color::WHITE, padding_color: Color::from_rgba8(166, 200, 255, 255), - cursor_color: Color::from_rgba8(255, 0, 0, 255), - selection_color: Color::from_rgba8(196, 196, 0, 255), + cursor_color: Color::from_rgba8(0, 0, 255, 255), + selection_colors: [ + Color::from_rgba8(0, 255, 0, 255), + Color::from_rgba8(255, 0, 0, 255), + ], inline_box_color: Color::BLACK, - size: None, + size: size.into(), }, cursor_size: 2.0, errors: Vec::new(), @@ -182,7 +184,9 @@ 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); + let mut builder = self + .layout_cx + .ranged_builder(&mut self.font_cx, text, 1.0, true); for style in default_style { builder.push_default(style); } @@ -193,7 +197,7 @@ impl TestEnv { let default_style = self.default_style(); let mut builder = self.layout_cx - .tree_builder(&mut self.font_cx, 1.0, &TextStyle::default()); + .tree_builder(&mut self.font_cx, 1.0, true, &TextStyle::default()); builder.push_style_modification_span(&default_style); builder } @@ -296,7 +300,7 @@ impl TestEnv { self.render_and_check_snapshot(layout, None, &[]); } - fn render_and_check_snapshot( + pub(crate) fn render_and_check_snapshot( &mut self, layout: &Layout, cursor_rect: Option, diff --git a/parley/src/tests/utils/renderer.rs b/parley/src/tests/utils/renderer.rs index 13230fe5..18645123 100644 --- a/parley/src/tests/utils/renderer.rs +++ b/parley/src/tests/utils/renderer.rs @@ -44,7 +44,8 @@ pub(crate) struct RenderingConfig { pub padding_color: Color, pub inline_box_color: Color, pub cursor_color: Color, - pub selection_color: Color, + /// The selection color is chosen based on line index. + pub selection_colors: [Color; 2], /// The width of the pixmap in pixels, excluding padding. pub size: Option, @@ -96,14 +97,14 @@ pub(crate) fn render_layout( config.background_color, ); - for (rect, _) in selection_rects { + for (rect, lidx) in selection_rects { draw_rect( &mut pen, fpadding + rect.x0 as f32, fpadding + rect.y0 as f32, rect.width() as f32, rect.height() as f32, - config.selection_color, + config.selection_colors[lidx % 2], ); } diff --git a/parley/tests/README.md b/parley/tests/README.md index 123cfbd0..5d705c5e 100644 --- a/parley/tests/README.md +++ b/parley/tests/README.md @@ -37,3 +37,7 @@ The following command shows snapshots that are not used in any test. The command cargo xtask dead-snapshots ``` +## Matching Chrome in line height calculations + +The `lines.html` file can be used to generate images roughly similar to the ones in `test_lines.rs`. +It can be used to verify existing test goals or to create additional tests. diff --git a/parley/tests/lines.html b/parley/tests/lines.html new file mode 100644 index 00000000..6c0610ae --- /dev/null +++ b/parley/tests/lines.html @@ -0,0 +1,60 @@ + + + + + + + +
+ Some text here. Let's make
+ it a bit long
er so that
+
we have more lines.
+ And also some latin text for
+ this spot right here.
+ This is underline and
+ strikethrough text and some
+ extra more text
+ and some extra more text
+ and some extra more text
+ and some extra more text
+ and some extra more text
+ and some extra more text
+ and some extra more text
+
+ + diff --git a/parley/tests/snapshots/base_level_alignment_ltr-end.png b/parley/tests/snapshots/base_level_alignment_ltr-end.png index e9959a87..50020136 100644 --- a/parley/tests/snapshots/base_level_alignment_ltr-end.png +++ b/parley/tests/snapshots/base_level_alignment_ltr-end.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70c30d9dec139bea320bae3fa546f4d4dee1166fdabdd3a30ca4035218852cd1 -size 29801 +oid sha256:e59105730fc090644de67627bc01a9e2ea85753f700d1529e0259b62a3dbdfd3 +size 29504 diff --git a/parley/tests/snapshots/base_level_alignment_ltr-justified.png b/parley/tests/snapshots/base_level_alignment_ltr-justified.png index d1a9cdf2..f0732bae 100644 --- a/parley/tests/snapshots/base_level_alignment_ltr-justified.png +++ b/parley/tests/snapshots/base_level_alignment_ltr-justified.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a0e77a17a0b86191c33f5c5d91d3ba3186aaad8c39f6f3a795f33a4d0248a6d -size 29783 +oid sha256:393c1f787c966b33840446fd8ac23c649a01f4056bbfc6605a3c615b97457c31 +size 29450 diff --git a/parley/tests/snapshots/base_level_alignment_ltr-middle.png b/parley/tests/snapshots/base_level_alignment_ltr-middle.png index d7807eb1..f70c5718 100644 --- a/parley/tests/snapshots/base_level_alignment_ltr-middle.png +++ b/parley/tests/snapshots/base_level_alignment_ltr-middle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a231352fa63da3b2ff6a20e4fbd8899895a588dea2805c576a36d840a3bd622f -size 29829 +oid sha256:a60aa389920c7cbff664dcea603ca6810376dd75e4b4aadd61d9466fdbcf1287 +size 29473 diff --git a/parley/tests/snapshots/base_level_alignment_ltr-start.png b/parley/tests/snapshots/base_level_alignment_ltr-start.png index 213482bd..e2ca5182 100644 --- a/parley/tests/snapshots/base_level_alignment_ltr-start.png +++ b/parley/tests/snapshots/base_level_alignment_ltr-start.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3ad297afd138a8b54a903b3170646a7654cbf0d33c356b46fee82e3052567a1 -size 29586 +oid sha256:01b6e8896f8367d2b295f105d8a6fc9935fc1d4a39a2d015268f8e6bbfc87930 +size 29312 diff --git a/parley/tests/snapshots/base_level_alignment_rtl-end.png b/parley/tests/snapshots/base_level_alignment_rtl-end.png index 53409df8..54fd890d 100644 --- a/parley/tests/snapshots/base_level_alignment_rtl-end.png +++ b/parley/tests/snapshots/base_level_alignment_rtl-end.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:286421fd802c3f06410937e83908ad42885e1ef03d3134e0dc0eab9d5bce9914 -size 20202 +oid sha256:4a53d1f9f1177f5941c51eb593d250f421e79fc69cb0c4c5f8636149e3010a88 +size 20222 diff --git a/parley/tests/snapshots/base_level_alignment_rtl-justified.png b/parley/tests/snapshots/base_level_alignment_rtl-justified.png index d82c793c..f6c8c48a 100644 --- a/parley/tests/snapshots/base_level_alignment_rtl-justified.png +++ b/parley/tests/snapshots/base_level_alignment_rtl-justified.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:686b0a682834ddf4ea2c50b10f217db9b747b8697d1925b456c2bbae3d3fc38e -size 20313 +oid sha256:066576d38344b0b890fb789bc87bddf9e570910e18745fc08295b8307801605b +size 20363 diff --git a/parley/tests/snapshots/base_level_alignment_rtl-middle.png b/parley/tests/snapshots/base_level_alignment_rtl-middle.png index da9dbdd9..f4c2d0f2 100644 --- a/parley/tests/snapshots/base_level_alignment_rtl-middle.png +++ b/parley/tests/snapshots/base_level_alignment_rtl-middle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37478525eefc615916b23ae6a984250321a29ab59d0ffa6d8f98db34f287fda9 -size 20270 +oid sha256:abcdfd4a7ee9754a612c491a04ba41475d9ef0ea4b81da124437f2c6869fd15c +size 20250 diff --git a/parley/tests/snapshots/base_level_alignment_rtl-start.png b/parley/tests/snapshots/base_level_alignment_rtl-start.png index 310622ed..9c3a8def 100644 --- a/parley/tests/snapshots/base_level_alignment_rtl-start.png +++ b/parley/tests/snapshots/base_level_alignment_rtl-start.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3375cfcafa33fe3fa0c4aa5d12190f7a12594e97931d8275fc8e7bbc16cad561 -size 20168 +oid sha256:374a095e14512dfdba9c1e4db25f44da28425a76febf74981b874cb6ba5f693f +size 20167 diff --git a/parley/tests/snapshots/content_widths-max.png b/parley/tests/snapshots/content_widths-max.png index d8057178..717cd9e2 100644 --- a/parley/tests/snapshots/content_widths-max.png +++ b/parley/tests/snapshots/content_widths-max.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:038ec0496e2321f10a713709ff8427a444ed05f73bba6b509cc36cc894001ac0 -size 12166 +oid sha256:fa73d478c60bc2cd7515fee0cfb889c524ffb706c0f071b916b04b726a217a53 +size 12090 diff --git a/parley/tests/snapshots/content_widths-min.png b/parley/tests/snapshots/content_widths-min.png index 4f426abb..c83c7c60 100644 --- a/parley/tests/snapshots/content_widths-min.png +++ b/parley/tests/snapshots/content_widths-min.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78d25ca644650161df0aa0561ac8ac9266f1ebac4036e813ab9ab2ff647c8810 -size 12769 +oid sha256:a1d36ee08f0802547d8ded19436bc51ed679eeaf56f0a47a2439e5161289e3e7 +size 12700 diff --git a/parley/tests/snapshots/content_widths_rtl-max.png b/parley/tests/snapshots/content_widths_rtl-max.png index 304ce9d4..7d19c8d0 100644 --- a/parley/tests/snapshots/content_widths_rtl-max.png +++ b/parley/tests/snapshots/content_widths_rtl-max.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b4daeaacc405c5e6604d5905f4149daac6397a2c44b1347a71dd1b35a66373e -size 2359 +oid sha256:2b283e19e22bd605f228e645796a6501ca79df77c8f7b0e61f8b7c2c7359d51d +size 2373 diff --git a/parley/tests/snapshots/content_widths_rtl-min.png b/parley/tests/snapshots/content_widths_rtl-min.png index 6939f04a..2dc4942f 100644 --- a/parley/tests/snapshots/content_widths_rtl-min.png +++ b/parley/tests/snapshots/content_widths_rtl-min.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcb23b01cccb2b9f6c6a7231dce5a5630a57b2f00927ab5aad1428d4a7338eba -size 2545 +oid sha256:a6a04eeca152583bb6de0ff5e6bfdf1ccc8561e37717d7cc94dc27a5ffdda05d +size 2529 diff --git a/parley/tests/snapshots/editor_double_newline-0.png b/parley/tests/snapshots/editor_double_newline-0.png index 3f324d13..df6e1d82 100644 --- a/parley/tests/snapshots/editor_double_newline-0.png +++ b/parley/tests/snapshots/editor_double_newline-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2996984c03605e631d00e3acb10bc8ad5a96442ebc49b6f25a2de594a9ff071 -size 3579 +oid sha256:cd90d6e06f9837c0e60913784b680e7bcb4d3d4a26a0ccc2286203af374c536a +size 3000 diff --git a/parley/tests/snapshots/editor_select_all-0.png b/parley/tests/snapshots/editor_select_all-0.png index 416d0e01..1328e486 100644 --- a/parley/tests/snapshots/editor_select_all-0.png +++ b/parley/tests/snapshots/editor_select_all-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e998425d4877eea769beb76219611b586f107c5eb9099c7ab6ad7baed253073b -size 3131 +oid sha256:3db337060ed117ad1056176bff76e7f408e0b2b44f18f0b4bbf95731e98fb28d +size 2562 diff --git a/parley/tests/snapshots/editor_simple_move-0.png b/parley/tests/snapshots/editor_simple_move-0.png index 60a1d32d..4640ca67 100644 --- a/parley/tests/snapshots/editor_simple_move-0.png +++ b/parley/tests/snapshots/editor_simple_move-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5278bdb1d8ee466207c46db77a5089bdcdb4cca85a6bb40d542ec2e6872181a4 -size 3386 +oid sha256:afc11b4c36c40b8a78337104e6aff321d29232a4de5cb4e37a6d229131937e65 +size 3356 diff --git a/parley/tests/snapshots/editor_simple_move-1.png b/parley/tests/snapshots/editor_simple_move-1.png index 04a1b259..2dda97f9 100644 --- a/parley/tests/snapshots/editor_simple_move-1.png +++ b/parley/tests/snapshots/editor_simple_move-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7837b815107512caafbd1ef374dbb2f2b1a2cbee27e178c382d265eb4c0d8ae8 -size 3598 +oid sha256:100dbbe6427c0c753fa92f9508cc8fa0c1cd76b555c6bd394ae358519f55773c +size 3595 diff --git a/parley/tests/snapshots/editor_simple_move-2.png b/parley/tests/snapshots/editor_simple_move-2.png index 6049009f..4594fbd5 100644 --- a/parley/tests/snapshots/editor_simple_move-2.png +++ b/parley/tests/snapshots/editor_simple_move-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:623c8821e38d57ca69ea1832e8ce1d0d760645936186017f3c97b1503d5929d6 -size 3483 +oid sha256:1914cee272c76df5ea23558b1c882f5fa0cc2d8010b8393e930cf1b404ceae20 +size 3478 diff --git a/parley/tests/snapshots/editor_simple_move-3.png b/parley/tests/snapshots/editor_simple_move-3.png index 0a0ba980..842ccf25 100644 --- a/parley/tests/snapshots/editor_simple_move-3.png +++ b/parley/tests/snapshots/editor_simple_move-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f7624e07ddb71ca00a613d74c2363d3102597eb9f3a313083a9f7a5dce9bf7b -size 3515 +oid sha256:ddca1f7df01101437cf27a09a2fe0811f2e77badc0c7fcd1a3b53c53ced5a7c9 +size 3507 diff --git a/parley/tests/snapshots/editor_simple_move-4.png b/parley/tests/snapshots/editor_simple_move-4.png index 65790bf9..18717716 100644 --- a/parley/tests/snapshots/editor_simple_move-4.png +++ b/parley/tests/snapshots/editor_simple_move-4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4626ba2536ed2ec3a6b2162dee4840510347e4968b312d376ec16affcaaf7d0 -size 3458 +oid sha256:b04454a84b145068cc3a4cdef023378485cf4f06fc4551272c1f1b93eb69883e +size 3457 diff --git a/parley/tests/snapshots/full_width_inbox-exact.png b/parley/tests/snapshots/full_width_inbox-exact.png index 8d696be0..628111e7 100644 --- a/parley/tests/snapshots/full_width_inbox-exact.png +++ b/parley/tests/snapshots/full_width_inbox-exact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18383f153524d135132ac56bd3ba92cebcc4b8c11ea66244d91d2a702b107df7 -size 2996 +oid sha256:337871eecdaad8f09b9e83106304da965022f9118494e8297db61d9c64d63712 +size 2963 diff --git a/parley/tests/snapshots/full_width_inbox-larger.png b/parley/tests/snapshots/full_width_inbox-larger.png index 31791518..6adb678e 100644 --- a/parley/tests/snapshots/full_width_inbox-larger.png +++ b/parley/tests/snapshots/full_width_inbox-larger.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd203ab42ce35724a5a6ed5ea907dd17bccdb041bd03b2cdcbfb73add96c648e -size 3104 +oid sha256:78cf058c325e5c3bf4cf8602c6ca9db854191862157ba0d8e08443aa4193a579 +size 3071 diff --git a/parley/tests/snapshots/full_width_inbox-smaller.png b/parley/tests/snapshots/full_width_inbox-smaller.png index 2a1d903b..49e37247 100644 --- a/parley/tests/snapshots/full_width_inbox-smaller.png +++ b/parley/tests/snapshots/full_width_inbox-smaller.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9024d4d6bbc1620988b265d6ce6fcc5bd4baefcf44daf29282769920e2bf755 -size 3099 +oid sha256:286abb9c40822e3b5a0999772ab8e325727e81c6dece017fc94f5e690b1d3c1b +size 3066 diff --git a/parley/tests/snapshots/inbox_content_width-full_width.png b/parley/tests/snapshots/inbox_content_width-full_width.png index 92613dd6..1bb3fb9b 100644 --- a/parley/tests/snapshots/inbox_content_width-full_width.png +++ b/parley/tests/snapshots/inbox_content_width-full_width.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:433b28fb853cf5bf3cca25d0b9a7bda7df7632b6ec98986e35f2c9218d5b377e -size 4102 +oid sha256:33c19105b440a23c6dd1410192e6b7b26b37c5876c83e37e11c4f9277383a8a8 +size 4083 diff --git a/parley/tests/snapshots/inbox_content_width-trailing_whitespace.png b/parley/tests/snapshots/inbox_content_width-trailing_whitespace.png index 2efe21f7..5f067b5c 100644 --- a/parley/tests/snapshots/inbox_content_width-trailing_whitespace.png +++ b/parley/tests/snapshots/inbox_content_width-trailing_whitespace.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:118d1454482e3ee77669f64966d964a90fecfecbba515e6a16daf80ffc3c0c60 -size 1253 +oid sha256:a29a732afa171e5ec72397ec7ab8e6a252be632ee5308996aa026459796aac8f +size 1217 diff --git a/parley/tests/snapshots/inbox_separated_by_whitespace-0.png b/parley/tests/snapshots/inbox_separated_by_whitespace-0.png index bfa6865a..4deee47d 100644 --- a/parley/tests/snapshots/inbox_separated_by_whitespace-0.png +++ b/parley/tests/snapshots/inbox_separated_by_whitespace-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62e7483485cfbe703ff280d3d5d0d63d572ee315d4feaa24a51ca2d12e879c50 -size 1356 +oid sha256:1bbd86f7686865b4a175ddaca59cde9b8b9dff35639dfb3c1f7fbf4ee5ba8a3d +size 1308 diff --git a/parley/tests/snapshots/leading_whitespace-collapse.png b/parley/tests/snapshots/leading_whitespace-collapse.png index ddc5ead9..30777be5 100644 --- a/parley/tests/snapshots/leading_whitespace-collapse.png +++ b/parley/tests/snapshots/leading_whitespace-collapse.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fb86f523a0e448c2d2d068502827bb5d92ae744f67175ec1c508c168b913312 -size 3531 +oid sha256:18582d16d826122f9bab100506984a3d42d6727931a99ce93f296bb2e61a8b18 +size 3450 diff --git a/parley/tests/snapshots/leading_whitespace-preserve.png b/parley/tests/snapshots/leading_whitespace-preserve.png index 1dfc3b4a..4d162c6a 100644 --- a/parley/tests/snapshots/leading_whitespace-preserve.png +++ b/parley/tests/snapshots/leading_whitespace-preserve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29dd356aedfe2cafe6a74aa0e518a28bc55a984469a6b9f5bbd06ad430586ae5 -size 3541 +oid sha256:0cf1d549dbad7c240a76ef27c854bd89ad56f20d73aaf58e3ac4de72f1dd2538 +size 3460 diff --git a/parley/tests/snapshots/lines_fractional_line_height_big_negative_leading-0.png b/parley/tests/snapshots/lines_fractional_line_height_big_negative_leading-0.png new file mode 100644 index 00000000..180b7299 --- /dev/null +++ b/parley/tests/snapshots/lines_fractional_line_height_big_negative_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fa519d1a189efdd80799a5043ee1dc090fddcd1b7899a22d77b66ecce0714cc +size 62419 diff --git a/parley/tests/snapshots/lines_fractional_line_height_big_positive_leading-0.png b/parley/tests/snapshots/lines_fractional_line_height_big_positive_leading-0.png new file mode 100644 index 00000000..73f9de03 --- /dev/null +++ b/parley/tests/snapshots/lines_fractional_line_height_big_positive_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e97a18041c35a59b0b9c9d4fdf58086b20a5519f0b2782336c85cc9faae246d +size 83516 diff --git a/parley/tests/snapshots/lines_fractional_line_height_negative_leading-0.png b/parley/tests/snapshots/lines_fractional_line_height_negative_leading-0.png new file mode 100644 index 00000000..3287b3c5 --- /dev/null +++ b/parley/tests/snapshots/lines_fractional_line_height_negative_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2ce09ff1bab99321fad76f6d99a4d79632e70c74c6adaa50be83fb9c1ae671d +size 65687 diff --git a/parley/tests/snapshots/lines_fractional_line_height_positive_leading-0.png b/parley/tests/snapshots/lines_fractional_line_height_positive_leading-0.png new file mode 100644 index 00000000..7ff96d15 --- /dev/null +++ b/parley/tests/snapshots/lines_fractional_line_height_positive_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbac2ef521ab022979da73929a52cee9650f5d3ab6f7993eda553a4fdf8a283d +size 72261 diff --git a/parley/tests/snapshots/lines_integral_line_height_ascent_descent_rounding-0.png b/parley/tests/snapshots/lines_integral_line_height_ascent_descent_rounding-0.png new file mode 100644 index 00000000..5d75583f --- /dev/null +++ b/parley/tests/snapshots/lines_integral_line_height_ascent_descent_rounding-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:460d8af68eb6a74f4e38deda19b0782e49d0381b76539d900a56bb8fd9199484 +size 88963 diff --git a/parley/tests/snapshots/lines_integral_line_height_minus_one_leading-0.png b/parley/tests/snapshots/lines_integral_line_height_minus_one_leading-0.png new file mode 100644 index 00000000..911a1350 --- /dev/null +++ b/parley/tests/snapshots/lines_integral_line_height_minus_one_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b5aa46601ae25bb83c662d6368a2b094d30eeb51e79f8f27ee71095cc2aa246 +size 73742 diff --git a/parley/tests/snapshots/lines_integral_line_height_plus_one_leading-0.png b/parley/tests/snapshots/lines_integral_line_height_plus_one_leading-0.png new file mode 100644 index 00000000..e1db433f --- /dev/null +++ b/parley/tests/snapshots/lines_integral_line_height_plus_one_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c376c0a4daf3a3af07723eb5919fd8818e45ec99289a7f6cce5bc26dc7022b21 +size 62414 diff --git a/parley/tests/snapshots/lines_integral_line_height_zero_leading-0.png b/parley/tests/snapshots/lines_integral_line_height_zero_leading-0.png new file mode 100644 index 00000000..dddb2c0e --- /dev/null +++ b/parley/tests/snapshots/lines_integral_line_height_zero_leading-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b5f4889230c036dae9333c6a6abee095c701b3048ce3011f50d2de20847fbb8 +size 69761 diff --git a/parley/tests/snapshots/lines_line_height_rounds_down-0.png b/parley/tests/snapshots/lines_line_height_rounds_down-0.png new file mode 100644 index 00000000..0f8f6eeb --- /dev/null +++ b/parley/tests/snapshots/lines_line_height_rounds_down-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a26ca57037a2630ac38dd510f38f7e0ad8b1ba63ce6430315c5b7d31c5d07e5 +size 70340 diff --git a/parley/tests/snapshots/lines_line_height_rounds_up-0.png b/parley/tests/snapshots/lines_line_height_rounds_up-0.png new file mode 100644 index 00000000..eed8654a --- /dev/null +++ b/parley/tests/snapshots/lines_line_height_rounds_up-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2967695f1a851327426701e7810b1758123b7e0695e193eff951e30931370f4 +size 70640 diff --git a/parley/tests/snapshots/overflow_alignment_rtl-0.png b/parley/tests/snapshots/overflow_alignment_rtl-0.png index 59829bbf..5b6a037d 100644 --- a/parley/tests/snapshots/overflow_alignment_rtl-0.png +++ b/parley/tests/snapshots/overflow_alignment_rtl-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1867f910272a841b16fa771dc287fde6a3a6f7f8fd5a29366139d4a833fd21d -size 1136 +oid sha256:021eca1c49b7a21efe7d0eb2c2979aa54aad4a6625144eb9434d1e328f362358 +size 1148 diff --git a/parley/tests/snapshots/overflow_wrap_anywhere_min_content_width-0.png b/parley/tests/snapshots/overflow_wrap_anywhere_min_content_width-0.png index a2bf131d..dfef786b 100644 --- a/parley/tests/snapshots/overflow_wrap_anywhere_min_content_width-0.png +++ b/parley/tests/snapshots/overflow_wrap_anywhere_min_content_width-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:705fc0e15af25999be3f82b32f1c5b1b0698e35bb939016f8bbaabb46df5d45c -size 22105 +oid sha256:2857c59c38f8fe918b776a872ad1378ae94be6d26d551302c2f607999316aaf5 +size 22108 diff --git a/parley/tests/snapshots/overflow_wrap_break_word_min_content_width-0.png b/parley/tests/snapshots/overflow_wrap_break_word_min_content_width-0.png index 4f426abb..c83c7c60 100644 --- a/parley/tests/snapshots/overflow_wrap_break_word_min_content_width-0.png +++ b/parley/tests/snapshots/overflow_wrap_break_word_min_content_width-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78d25ca644650161df0aa0561ac8ac9266f1ebac4036e813ab9ab2ff647c8810 -size 12769 +oid sha256:a1d36ee08f0802547d8ded19436bc51ed679eeaf56f0a47a2439e5161289e3e7 +size 12700 diff --git a/parley/tests/snapshots/overflow_wrap_during-0.png b/parley/tests/snapshots/overflow_wrap_during-0.png index 2171c0e3..d3c1a96e 100644 --- a/parley/tests/snapshots/overflow_wrap_during-0.png +++ b/parley/tests/snapshots/overflow_wrap_during-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7169a3178d1b14bd481bb5d03c3899854cdcb6e0c5f0a318146b25af5c582977 -size 16300 +oid sha256:37ba6fdb0ee24d131c4b406ae7b1a691d8e769710e51d8b2722190b278f802bf +size 16055 diff --git a/parley/tests/snapshots/overflow_wrap_everywhere-0.png b/parley/tests/snapshots/overflow_wrap_everywhere-0.png index bc6028c0..3f7ada9e 100644 --- a/parley/tests/snapshots/overflow_wrap_everywhere-0.png +++ b/parley/tests/snapshots/overflow_wrap_everywhere-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:299a3f56c865f23bc7f52423cd53836c02f2e3e2b9501f0da447075ebb92e1ba -size 16310 +oid sha256:8993014a66c008cdbe8d72b872e9a445b2de8b68e5809d761818d7cbbb8a34c3 +size 16052 diff --git a/parley/tests/snapshots/overflow_wrap_first_half-0.png b/parley/tests/snapshots/overflow_wrap_first_half-0.png index 0647cde5..1289330f 100644 --- a/parley/tests/snapshots/overflow_wrap_first_half-0.png +++ b/parley/tests/snapshots/overflow_wrap_first_half-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58fa47ed618a508dc6e635949a0127cfaa89a844a62cfea73ef063633f7f0904 -size 16327 +oid sha256:6fd203a24f0442a71415b7e8d4a6ee38fdbd6fa9e7f630d255155c8eadfcadc2 +size 16096 diff --git a/parley/tests/snapshots/overflow_wrap_narrow-0.png b/parley/tests/snapshots/overflow_wrap_narrow-0.png index 218eea00..7358f17f 100644 --- a/parley/tests/snapshots/overflow_wrap_narrow-0.png +++ b/parley/tests/snapshots/overflow_wrap_narrow-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06d6c77532aa5d45c1ae840275bc10c9a06ba57df0d941a18c22eb8977b1d85d -size 34383 +oid sha256:c684f2b7d60e739e062aeeeac389d1a1906cc5c29fee1faa23a078eea4ae4c1b +size 33814 diff --git a/parley/tests/snapshots/overflow_wrap_off-0.png b/parley/tests/snapshots/overflow_wrap_off-0.png index f7f1c953..a276bdf5 100644 --- a/parley/tests/snapshots/overflow_wrap_off-0.png +++ b/parley/tests/snapshots/overflow_wrap_off-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e92dfbcc4fd0ceafd235c532e1ec74325f550f5abf44c947c424335771055839 -size 16171 +oid sha256:0ede75eb5a73ab63c6244b87430b244150a17103fc1763327f456de1c682b9bc +size 15921 diff --git a/parley/tests/snapshots/overflow_wrap_second_half-0.png b/parley/tests/snapshots/overflow_wrap_second_half-0.png index 22140cf4..a0287aa5 100644 --- a/parley/tests/snapshots/overflow_wrap_second_half-0.png +++ b/parley/tests/snapshots/overflow_wrap_second_half-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:160dd2e58db3f83f0a32666c578cf56a2654e87d97b5b8d5858b0b637e00c7e2 -size 16616 +oid sha256:2c5ee39cca4efbde77dd1bde1f5047a9007976b697dc0660c405d378a6956b2a +size 16367 diff --git a/parley/tests/snapshots/placing_inboxes-end_nl.png b/parley/tests/snapshots/placing_inboxes-end_nl.png index c9233edc..93e71c9c 100644 --- a/parley/tests/snapshots/placing_inboxes-end_nl.png +++ b/parley/tests/snapshots/placing_inboxes-end_nl.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a591054c3df4cf09009c957bb868196e8af58fc3bbc47c323e514d59512d2e70 -size 6576 +oid sha256:da0e16d831343a06b22362b0d4fbbefc46164f96507f714da06f373940b24c2c +size 6488 diff --git a/parley/tests/snapshots/placing_inboxes-in_word.png b/parley/tests/snapshots/placing_inboxes-in_word.png index 97a5790a..6b0c4526 100644 --- a/parley/tests/snapshots/placing_inboxes-in_word.png +++ b/parley/tests/snapshots/placing_inboxes-in_word.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23941ba3121686757e91ed4b5d68ff2fca443d52369af5285c4338917a882c48 -size 6571 +oid sha256:123d76e46ebbe2707054bc1728a3825c5ce8d86c9e8dd6049485b450509eea8a +size 6484 diff --git a/parley/tests/snapshots/placing_inboxes-start.png b/parley/tests/snapshots/placing_inboxes-start.png index 5e7f4043..c78be970 100644 --- a/parley/tests/snapshots/placing_inboxes-start.png +++ b/parley/tests/snapshots/placing_inboxes-start.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcaea2eff47334cd6c417aa6aacd0e33b9cd3519f9d4a4a0ecb8c040c2d95534 -size 6451 +oid sha256:d76feb694a19e7f42aaa72d395bfe67ffc8754600f483eb24b66d9ee8f1b67e8 +size 6368 diff --git a/parley/tests/snapshots/placing_inboxes-start_nl.png b/parley/tests/snapshots/placing_inboxes-start_nl.png index 61d9d557..132fdd9e 100644 --- a/parley/tests/snapshots/placing_inboxes-start_nl.png +++ b/parley/tests/snapshots/placing_inboxes-start_nl.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:888b645998b525d324525f4d488b4397eacf020bd1cef923c311affc80787eed -size 6426 +oid sha256:e12d754a6af62aa724d455dcb48e597edee1f4134923ae2142a9e3c55ac7378c +size 6341 diff --git a/parley/tests/snapshots/plain_multiline_text-0.png b/parley/tests/snapshots/plain_multiline_text-0.png index ceabdfb6..848e46da 100644 --- a/parley/tests/snapshots/plain_multiline_text-0.png +++ b/parley/tests/snapshots/plain_multiline_text-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9823427a1ed3ffc690191262643c5018c8745e21dad1af9f101717e82ee5933d -size 6379 +oid sha256:91f271409ca3b80f17af822bc7e4b6564578ddc1d843f3d0f0424d88f5201f38 +size 6305 diff --git a/parley/tests/snapshots/realign-0.png b/parley/tests/snapshots/realign-0.png index d1a9cdf2..f0732bae 100644 --- a/parley/tests/snapshots/realign-0.png +++ b/parley/tests/snapshots/realign-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a0e77a17a0b86191c33f5c5d91d3ba3186aaad8c39f6f3a795f33a4d0248a6d -size 29783 +oid sha256:393c1f787c966b33840446fd8ac23c649a01f4056bbfc6605a3c615b97457c31 +size 29450 diff --git a/parley/tests/snapshots/trailing_whitespace-0.png b/parley/tests/snapshots/trailing_whitespace-0.png index 113d88e4..6dcd1e83 100644 --- a/parley/tests/snapshots/trailing_whitespace-0.png +++ b/parley/tests/snapshots/trailing_whitespace-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d610221c71b66fa6d3f4e2b8bb805489fe727088e1749ff6ed13ff3f87f1dbc -size 3027 +oid sha256:5feaaf752ea6ee1ddabd6e32508eb0fd203e01dccca78097f4bb2925c16971f7 +size 3003 diff --git a/parley/tests/snapshots/word_break_break_all_during-0.png b/parley/tests/snapshots/word_break_break_all_during-0.png index 72a5eff4..f6f2f944 100644 --- a/parley/tests/snapshots/word_break_break_all_during-0.png +++ b/parley/tests/snapshots/word_break_break_all_during-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6771d5760c3aab4b8905321b56b6308c48ee54e7066b17400263c090ab809612 -size 17135 +oid sha256:c31e9adc43bc02ff6b013085c21d838ced159f6d11df43d691457b2fc12f2364 +size 16866 diff --git a/parley/tests/snapshots/word_break_break_all_everywhere-0.png b/parley/tests/snapshots/word_break_break_all_everywhere-0.png index 7005e817..ea4f0fe6 100644 --- a/parley/tests/snapshots/word_break_break_all_everywhere-0.png +++ b/parley/tests/snapshots/word_break_break_all_everywhere-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed2d3ef26e685bb417268b0973e6152b6ee945fb3aa8a4da502f2db399384224 -size 20381 +oid sha256:55b7368fec043dff57490c1a7458f5d95aa13001ae9c6db843c7d08512985293 +size 20079 diff --git a/parley/tests/snapshots/word_break_break_all_first_half-0.png b/parley/tests/snapshots/word_break_break_all_first_half-0.png index 9a3ab277..7db7f495 100644 --- a/parley/tests/snapshots/word_break_break_all_first_half-0.png +++ b/parley/tests/snapshots/word_break_break_all_first_half-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:842d32ac4b21b565e576ea4c2e45670651239d17120e6d46cac71df1f515e0de -size 16381 +oid sha256:2e3d9f7549fae764c158b7e21eb4f2cc2d72fe88fd2090075c4f090b7dc4cf77 +size 16113 diff --git a/parley/tests/snapshots/word_break_break_all_min_content_width-0.png b/parley/tests/snapshots/word_break_break_all_min_content_width-0.png index 97975058..91b3b904 100644 --- a/parley/tests/snapshots/word_break_break_all_min_content_width-0.png +++ b/parley/tests/snapshots/word_break_break_all_min_content_width-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3639a38ec77a3334a61e5b3381729ee87bb2c499f011cdadce9eff8d0b807c3 -size 20500 +oid sha256:47e85b7f00630368f839360a10d840b2c5fc0a234f7d47a95473fd6823c540a6 +size 20501 diff --git a/parley/tests/snapshots/word_break_break_all_second_half-0.png b/parley/tests/snapshots/word_break_break_all_second_half-0.png index 90b3e9bb..b26a8dbe 100644 --- a/parley/tests/snapshots/word_break_break_all_second_half-0.png +++ b/parley/tests/snapshots/word_break_break_all_second_half-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11c76fee8fec918fa495d646a4f91cb440c9071277da749caf7616be45965cc7 -size 17001 +oid sha256:18a43e09ee91b0c42eef330dce17cb348074f54698a85b8969111eb254e939f0 +size 16736 diff --git a/parley/tests/snapshots/word_break_keep_all-ID_and_CJ.png b/parley/tests/snapshots/word_break_keep_all-ID_and_CJ.png index 663f5869..e517d053 100644 --- a/parley/tests/snapshots/word_break_keep_all-ID_and_CJ.png +++ b/parley/tests/snapshots/word_break_keep_all-ID_and_CJ.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef0e54caac94ec5119c10cbf737d2bdeae959d556bee7f041c55d4f817ccb203 -size 2295 +oid sha256:6a49fe452e9655dccd9aa2ce1ce3f360048766dfcd0d9b7ed5942bec90b09383 +size 2335 diff --git a/parley/tests/snapshots/word_break_keep_all-japanese.png b/parley/tests/snapshots/word_break_keep_all-japanese.png index 6f815213..07f497e8 100644 --- a/parley/tests/snapshots/word_break_keep_all-japanese.png +++ b/parley/tests/snapshots/word_break_keep_all-japanese.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62126a81b13f30e3178b12d18a07a69f54a822f4df63d8e4803924d0891972b9 -size 4094 +oid sha256:1e8a07f836d10d3d701a26cb45da9c18d4a66f9d19fc453bcfb3fbab1c7174f9 +size 4120 diff --git a/parley/tests/snapshots/word_break_keep_all-korean.png b/parley/tests/snapshots/word_break_keep_all-korean.png index 6f815213..07f497e8 100644 --- a/parley/tests/snapshots/word_break_keep_all-korean.png +++ b/parley/tests/snapshots/word_break_keep_all-korean.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62126a81b13f30e3178b12d18a07a69f54a822f4df63d8e4803924d0891972b9 -size 4094 +oid sha256:1e8a07f836d10d3d701a26cb45da9c18d4a66f9d19fc453bcfb3fbab1c7174f9 +size 4120 diff --git a/parley/tests/snapshots/word_break_keep_all-korean_hangul_jamos.png b/parley/tests/snapshots/word_break_keep_all-korean_hangul_jamos.png index 7d71398b..418e8b3d 100644 --- a/parley/tests/snapshots/word_break_keep_all-korean_hangul_jamos.png +++ b/parley/tests/snapshots/word_break_keep_all-korean_hangul_jamos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1986951b2139a34843dd160ebbd79e07cbec61607b040f7ccfc5f623b6283f75 -size 7062 +oid sha256:0975e046088f83666fc5f22f7992234d2fccfb2f3fb03391561ae2a6fd3d233b +size 7011 diff --git a/parley/tests/snapshots/word_break_keep_all-latin.png b/parley/tests/snapshots/word_break_keep_all-latin.png index 5bc69963..a80ad2b8 100644 --- a/parley/tests/snapshots/word_break_keep_all-latin.png +++ b/parley/tests/snapshots/word_break_keep_all-latin.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c814b7c709370f0fc33b29f27b425c70d30300d0aeff80e20a58a8c32fd9e093 -size 5467 +oid sha256:867b28050305f37c9edbfef422b359c864253ee205fe61db563e5e5f3fc1c149 +size 5357 diff --git a/parley/tests/snapshots/word_break_wpt007-0.png b/parley/tests/snapshots/word_break_wpt007-0.png index 8b2e6a6a..921e5846 100644 --- a/parley/tests/snapshots/word_break_wpt007-0.png +++ b/parley/tests/snapshots/word_break_wpt007-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49a9289816ec6421d8a4ae3cfe47d3b8a21359d7be7d05ad0f45938a90b2674d -size 6381 +oid sha256:43ccfcd9e23058c6cc7692e0d3a66724d84b249423ddd124517e7021c108ae56 +size 6129