diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index fbe8fdfa..614c915c 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -52,7 +52,7 @@ fn main() { let bg_color = Rgba([255, 255, 255, 255]); // Padding around the output image - let padding = 20; + let padding = (20f32 * display_scale).round() as u32; // Create a FontContext, LayoutContext and ScaleContext // @@ -92,21 +92,21 @@ fn main() { builder.push_text(&text[5..40]); - builder.push_inline_box(InlineBox { - id: 0, - index: 0, - width: 50.0, - height: 50.0, - }); + builder.push_inline_box(InlineBox::new( + 0, + 0, + 50.0 * display_scale, + 50.0 * display_scale, + )); builder.push_text(&text[40..50]); - builder.push_inline_box(InlineBox { - id: 1, - index: 50, - width: 50.0, - height: 30.0, - }); + builder.push_inline_box(InlineBox::new( + 1, + 50, + 50.0 * display_scale, + 30.0 * display_scale, + )); builder.push_text(&text[50..141]); @@ -147,18 +147,18 @@ fn main() { builder.push(underline_style, 141..150); builder.push(strikethrough_style, 155..168); - builder.push_inline_box(InlineBox { - id: 0, - index: 40, - width: 50.0, - height: 50.0, - }); - builder.push_inline_box(InlineBox { - id: 1, - index: 50, - width: 50.0, - height: 30.0, - }); + builder.push_inline_box(InlineBox::new( + 0, + 40, + 50.0 * display_scale, + 50.0 * display_scale, + )); + builder.push_inline_box(InlineBox::new( + 1, + 50, + 50.0 * display_scale, + 30.0 * display_scale, + )); // Build the builder into a Layout // let mut layout: Layout = builder.build(&text); diff --git a/examples/tiny_skia_render/src/main.rs b/examples/tiny_skia_render/src/main.rs index 734d4fdc..465b0426 100644 --- a/examples/tiny_skia_render/src/main.rs +++ b/examples/tiny_skia_render/src/main.rs @@ -87,12 +87,7 @@ fn main() { builder.push(StyleProperty::Underline(true), 141..150); builder.push(StyleProperty::Strikethrough(true), 155..168); - builder.push_inline_box(InlineBox { - id: 0, - index: 40, - width: 50.0, - height: 50.0, - }); + builder.push_inline_box(InlineBox::new(0, 40, 50.0, 50.0)); // Build the builder into a Layout let mut layout: Layout = builder.build(&text); diff --git a/parley/src/inline_box.rs b/parley/src/inline_box.rs index 9731fd9e..aa512d97 100644 --- a/parley/src/inline_box.rs +++ b/parley/src/inline_box.rs @@ -1,6 +1,8 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::{ResolvedBaselineShift, VerticalAlign}; + /// A box to be laid out inline with text #[derive(Debug, Clone)] pub struct InlineBox { @@ -14,4 +16,21 @@ pub struct InlineBox { pub width: f32, /// The height of the box in pixels pub height: f32, + /// The baseline along which this item is aligned. + pub vertical_align: VerticalAlign, + /// Additional baseline alignment applied afterwards. + pub baseline_shift: ResolvedBaselineShift, +} + +impl InlineBox { + pub fn new(id: u64, index: usize, width: f32, height: f32) -> Self { + Self { + id, + index, + width, + height, + vertical_align: Default::default(), + baseline_shift: Default::default(), + } + } } diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index 4f80c5ef..b6ce65c3 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -86,6 +86,40 @@ pub(crate) struct RunData { pub(crate) advance: f32, } +impl RunData { + pub(crate) fn unique_styles<'a, 'b, B: Brush>( + &'a self, + layout: &'b LayoutData, + mut cb: impl FnMut(&'b Style), + ) { + let glyph_start = self.glyph_start; + for cluster in &layout.clusters[self.cluster_range.clone()] { + if cluster.glyph_len != 0xFF && cluster.has_divergent_styles() { + let mut start = glyph_start + cluster.glyph_offset as usize; + let end = start + cluster.glyph_len as usize; + + 'runs: loop { + let start_glyph = &layout.glyphs[start]; + cb(&layout.styles[start_glyph.style_index()]); + + loop { + start += 1; + + if start >= end { + break 'runs; + } + if layout.glyphs[start].style_index != start_glyph.style_index { + break; + } + } + } + } else { + cb(&layout.styles[cluster.style_index as usize]); + } + } + } +} + #[derive(Copy, Clone, Default, PartialEq, Debug)] pub enum BreakReason { #[default] @@ -335,6 +369,8 @@ impl LayoutData { ascent: metrics.ascent, descent: metrics.descent, leading: metrics.leading, + x_height: metrics.x_height, + font_size, underline_offset: metrics.underline_offset, underline_size: metrics.stroke_size, strikethrough_offset: metrics.strikeout_offset, diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index 2fccdc87..5def4e83 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/layout/line/greedy.rs @@ -15,6 +15,7 @@ use crate::layout::{ LineMetrics, Run, }; use crate::style::Brush; +use crate::{ResolvedBaselineShift, VerticalAlign}; use core::ops::Range; @@ -440,7 +441,7 @@ impl<'a, B: Brush> BreakLines<'a, B> { } } } - let mut y = 0.; + let mut y = 0f32; let mut prev_line_metrics = None; for line in &mut self.lines.lines { // Reset metrics for line @@ -454,12 +455,20 @@ impl<'a, B: Brush> BreakLines<'a, B> { line.text_range = self.layout.data.text_len..self.layout.data.text_len; } // Compute metrics for the line, but ignore trailing whitespace. - let mut have_metrics = false; let mut needs_reorder = false; + let mut line_relative_alignment_needed = false; + // Accumulate the top and bottom bounds of all the positioned items in the line, relative to the baseline (which is the origin) + let mut top = f32::INFINITY; + let mut ascent = f32::INFINITY; + let mut bottom = f32::NEG_INFINITY; + let mut descent = f32::NEG_INFINITY; + let mut min_line_height = 0f32; + for line_item in self.lines.line_items[line.item_range.clone()] .iter_mut() .rev() { + //let item_line_height = line_item.compute_line_height(&self.layout.data); match line_item.kind { LayoutItemKind::InlineBox => { let item = &self.layout.data.inline_boxes[line_item.index]; @@ -468,11 +477,28 @@ impl<'a, B: Brush> BreakLines<'a, B> { // Default vertical alignment is to align the bottom of boxes with the text baseline. // This is equivalent to the entire height of the box being "ascent" - line.metrics.ascent = line.metrics.ascent.max(item.height); - line.metrics.line_height = line.metrics.line_height.max(item.height); + min_line_height = min_line_height.max(item.height); + let (mut style_top, mut style_bottom) = match item.vertical_align { + VerticalAlign::Baseline | VerticalAlign::TextBottom => { + (-item.height, 0.0) + } + VerticalAlign::Middle => (-item.height * 0.5, item.height * 0.5), + VerticalAlign::TextTop => (0.0, item.height), + }; + let offset = match item.baseline_shift { + ResolvedBaselineShift::Absolute(value) => value, + ResolvedBaselineShift::Top + | ResolvedBaselineShift::Center + | ResolvedBaselineShift::Bottom => { + line_relative_alignment_needed = true; + continue; + } + }; - // Mark us as having seen non-whitespace content on this line - have_metrics = true; + style_top += offset; + style_bottom += offset; + top = top.min(style_top); + bottom = bottom.max(style_bottom); } LayoutItemKind::TextRun => { // Compute the text range for the line @@ -488,8 +514,10 @@ impl<'a, B: Brush> BreakLines<'a, B> { } let run = &self.layout.data.runs[line_item.index]; - let line_height = line_item.compute_line_height(&self.layout.data); - line.metrics.line_height = line.metrics.line_height.max(line_height); + + /*run.unique_styles(&self.layout.data, |style| { + line.metrics.line_height = line.metrics.line_height.max(style.line_height); + });*/ // Compute the run's advance by summing the advances of its constituent clusters line_item.advance = self.layout.data.clusters @@ -500,17 +528,101 @@ impl<'a, B: Brush> BreakLines<'a, B> { // Ignore trailing whitespace for metrics computation // (we are iterating backwards so trailing whitespace comes first) + let have_metrics = top.is_finite(); if !have_metrics && line_item.is_whitespace { continue; } - // Compute the run's vertical metrics - line.metrics.ascent = line.metrics.ascent.max(run.metrics.ascent); - line.metrics.descent = line.metrics.descent.max(run.metrics.descent); - line.metrics.leading = line.metrics.leading.max(run.metrics.leading); + // Compute the bounds of items whose baseline values are not line-relative + run.unique_styles(&self.layout.data, |style| { + min_line_height = min_line_height.max(style.line_height); + let (mut style_top, mut style_bottom) = match style.vertical_align { + VerticalAlign::Baseline => { + (-run.metrics.ascent, run.metrics.descent) + } + VerticalAlign::TextBottom => { + (-run.metrics.ascent - run.metrics.descent, 0.0) + } + VerticalAlign::Middle => { + let offset = run.metrics.x_height * 0.5; + (-run.metrics.ascent + offset, run.metrics.descent + offset) + } + VerticalAlign::TextTop => { + (0.0, run.metrics.ascent + run.metrics.descent) + } + }; + let offset = match style.baseline_shift { + ResolvedBaselineShift::Absolute(value) => -value, + ResolvedBaselineShift::Top + | ResolvedBaselineShift::Center + | ResolvedBaselineShift::Bottom => { + line_relative_alignment_needed = true; + return; + } + }; + style_top += offset; + style_bottom += offset; + top = top.min(style_top); + ascent = ascent.min(style_top); + bottom = bottom.max(style_bottom); + descent = descent.max(style_bottom); + }); + } + } + } - // Mark us as having seen non-whitespace content on this line - have_metrics = true; + if line_relative_alignment_needed { + // Align the line-relative items to the previously-computed bounds + let bounding_top = top; + let bounding_bottom = bottom; + + for line_item in self.lines.line_items[line.item_range.clone()] + .iter_mut() + .rev() + { + match line_item.kind { + LayoutItemKind::TextRun => { + let run = &self.layout.data.runs[line_item.index]; + let height = run.metrics.ascent + run.metrics.descent; + run.unique_styles(&self.layout.data, |style| { + match style.baseline_shift { + ResolvedBaselineShift::Absolute(_) => return, + ResolvedBaselineShift::Top => { + bottom = bottom.max(bounding_top + height); + ascent = ascent.min(bounding_top); + descent = descent.max(bounding_top + height); + } + ResolvedBaselineShift::Center => { + let center = (bounding_top + bounding_bottom) * 0.5; + top = top.min(center - (height * 0.5)); + ascent = ascent.min(center - (height * 0.5)); + descent = descent.max(center + (height * 0.5)); + } + ResolvedBaselineShift::Bottom => { + top = top.min(bounding_bottom - height); + descent = descent.max(bounding_bottom); + ascent = ascent.min(bounding_bottom - height); + } + } + }); + } + LayoutItemKind::InlineBox => { + let item = &self.layout.data.inline_boxes[line_item.index]; + match item.baseline_shift { + ResolvedBaselineShift::Absolute(_) => return, + ResolvedBaselineShift::Top => { + bottom = bottom.max(bounding_top + item.height); + } + ResolvedBaselineShift::Center => { + let center = (bounding_top + bounding_bottom) * 0.5; + top = top.min(center - (item.height * 0.5)); + bottom = bottom.max(center + (item.height * 0.5)); + } + ResolvedBaselineShift::Bottom => { + top = top.min(bounding_bottom - item.height); + } + } + } } } } @@ -543,7 +655,30 @@ impl<'a, B: Brush> BreakLines<'a, B> { }) .unwrap_or(0.0); - if !have_metrics { + let have_metrics = top.is_finite(); + if have_metrics { + if bottom - top < min_line_height { + let half_leading = (min_line_height - (bottom - top)) * 0.5; + top -= half_leading; + bottom += half_leading; + } + + line.metrics.line_height = bottom - top; + + let baseline_offset = -top; + line.metrics.ascent = -ascent - baseline_offset; + line.metrics.descent = descent - baseline_offset; + + line.metrics.min_coord = y.round(); + line.metrics.baseline = (y + baseline_offset).round(); + y += line.metrics.line_height; + line.metrics.max_coord = y.round(); + + // Round block/vertical axis metrics + line.metrics.ascent = line.metrics.ascent.round(); + line.metrics.descent = line.metrics.descent.round(); + line.metrics.line_height = line.metrics.line_height.round(); + } else { // Line consisting entirely of whitespace? if !line.item_range.is_empty() { let line_item = &self.lines.line_items[line.item_range.start]; @@ -586,23 +721,19 @@ impl<'a, B: Brush> BreakLines<'a, B> { line.item_range = run_index..run_index + 1; } } - } - // Round block/vertical axis metrics - line.metrics.ascent = line.metrics.ascent.round(); - line.metrics.descent = line.metrics.descent.round(); - line.metrics.line_height = line.metrics.line_height.round(); - line.metrics.leading = - line.metrics.line_height - (line.metrics.ascent + line.metrics.descent); - - // Compute - let above = (line.metrics.ascent + line.metrics.leading * 0.5).round(); - let below = (line.metrics.descent + line.metrics.leading * 0.5).round(); - line.metrics.min_coord = y; - line.metrics.baseline = y + above; - y = line.metrics.baseline + below; - line.metrics.max_coord = y; - prev_line_metrics = Some(line.metrics); + line.metrics.leading = + line.metrics.line_height - (line.metrics.ascent + line.metrics.descent); + + // Compute + let above = (line.metrics.ascent + line.metrics.leading * 0.5).round(); + let below = (line.metrics.descent + line.metrics.leading * 0.5).round(); + line.metrics.min_coord = y; + line.metrics.baseline = y + above; + y = line.metrics.baseline + below; + line.metrics.max_coord = y; + prev_line_metrics = Some(line.metrics); + } } if self.layout.data.text_len == 0 { if let Some(line) = self.lines.line_items.first_mut() { diff --git a/parley/src/layout/line/mod.rs b/parley/src/layout/line/mod.rs index 5c7ea96f..ac03be8d 100644 --- a/parley/src/layout/line/mod.rs +++ b/parley/src/layout/line/mod.rs @@ -156,6 +156,7 @@ pub struct GlyphRun<'a, B: Brush> { glyph_count: usize, offset: f32, baseline: f32, + min_coord: f32, advance: f32, } @@ -185,6 +186,11 @@ impl<'a, B: Brush> GlyphRun<'a, B> { self.advance } + pub fn y_offset(&self) -> f32 { + let line_ascent = self.baseline - self.min_coord; + self.run.metrics().ascent - line_ascent + } + /// Returns an iterator over the glyphs in the run. pub fn glyphs(&'a self) -> impl Iterator + 'a + Clone { self.run @@ -266,6 +272,7 @@ impl<'a, B: Brush> Iterator for GlyphRunIter<'a, B> { glyph_count, offset: offset + self.line.data.metrics.offset, baseline: self.line.data.metrics.baseline, + min_coord: self.line.data.metrics.min_coord, advance, })); } diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index 7ab63316..c4985145 100644 --- a/parley/src/layout/mod.rs +++ b/parley/src/layout/mod.rs @@ -16,7 +16,7 @@ pub mod editor; use self::alignment::align; use super::style::Brush; -use crate::{Font, InlineBox}; +use crate::{Font, InlineBox, ResolvedBaselineShift, VerticalAlign}; #[cfg(feature = "accesskit")] use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; use alignment::unjustify; @@ -301,6 +301,10 @@ pub struct Style { pub strikethrough: Option>, /// Absolute line height in layout units (style line height * font size) pub(crate) line_height: f32, + /// The baseline along which this item is aligned. + pub(crate) vertical_align: VerticalAlign, + /// Additional baseline alignment applied afterwards. + pub(crate) baseline_shift: ResolvedBaselineShift, } /// Underline or strikethrough decoration. diff --git a/parley/src/layout/run.rs b/parley/src/layout/run.rs index 601f6634..f806216d 100644 --- a/parley/src/layout/run.rs +++ b/parley/src/layout/run.rs @@ -3,7 +3,7 @@ use super::{ Brush, Cluster, ClusterPath, Font, Layout, LineItemData, NormalizedCoord, Range, Run, RunData, - Synthesis, + Style, Synthesis, }; impl<'a, B: Brush> Run<'a, B> { @@ -210,6 +210,11 @@ pub struct RunMetrics { pub descent: f32, /// Typographic leading. pub leading: f32, + /// Typographic x-height. + pub x_height: f32, + // TODO: this is necessary for subscript/superscript layout, but ideally it could be removed + /// The run's font size. + pub(crate) font_size: f32, /// Offset of the top of underline decoration from the baseline. pub underline_offset: f32, /// Thickness of the underline decoration. diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 42a6fc74..96be0f75 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -45,7 +45,7 @@ //! builder.push(StyleProperty::FontWeight(FontWeight::new(600.0)), 0..4); //! //! // Add a box to be laid out inline with the text -//! builder.push_inline_box(InlineBox { id: 0, index: 5, width: 50.0, height: 50.0 }); +//! builder.push_inline_box(InlineBox::new(0, 5, 50.0, 50.0)); //! //! // Build the builder into a Layout //! let mut layout: Layout<()> = builder.build(&TEXT); diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index a05eaa8c..21f56d5e 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -15,9 +15,9 @@ use super::style::{ FontWidth, StyleProperty, }; use crate::font::FontContext; -use crate::layout; use crate::style::TextStyle; use crate::util::nearly_eq; +use crate::{layout, BaselineShift, VerticalAlign}; use core::borrow::Borrow; use core::ops::Range; use fontique::FamilyId; @@ -155,6 +155,16 @@ impl ResolveContext { StyleProperty::LineHeight(value) => LineHeight(*value), StyleProperty::WordSpacing(value) => WordSpacing(*value * scale), StyleProperty::LetterSpacing(value) => LetterSpacing(*value * scale), + StyleProperty::VerticalAlign(value) => VerticalAlign(*value), + StyleProperty::BaselineShift(value) => BaselineShift(match value { + crate::BaselineShift::Absolute(value) => { + crate::BaselineShift::Absolute(*value * scale) + } + crate::BaselineShift::Relative(value) => { + crate::BaselineShift::Relative(*value * scale) + } + _ => *value, + }), } } @@ -189,6 +199,8 @@ impl ResolveContext { line_height: raw_style.line_height, word_spacing: raw_style.word_spacing * scale, letter_spacing: raw_style.letter_spacing * scale, + vertical_align: raw_style.vertical_align, + baseline_shift: raw_style.baseline_shift, } } @@ -366,6 +378,10 @@ pub(crate) enum ResolvedProperty { WordSpacing(f32), /// Extra spacing between letters. LetterSpacing(f32), + /// The baseline along which this item is aligned. + VerticalAlign(VerticalAlign), + /// Additional baseline alignment applied afterwards. + BaselineShift(BaselineShift), } /// Flattened group of style properties. @@ -399,6 +415,10 @@ pub(crate) struct ResolvedStyle { pub(crate) word_spacing: f32, /// Extra spacing between letters. pub(crate) letter_spacing: f32, + /// The baseline along which this item is aligned. + pub vertical_align: VerticalAlign, + /// Additional baseline alignment applied afterwards. + pub baseline_shift: BaselineShift, } impl Default for ResolvedStyle { @@ -418,6 +438,8 @@ impl Default for ResolvedStyle { line_height: 1., word_spacing: 0., letter_spacing: 0., + vertical_align: Default::default(), + baseline_shift: Default::default(), } } } @@ -447,6 +469,8 @@ impl ResolvedStyle { LineHeight(value) => self.line_height = value, WordSpacing(value) => self.word_spacing = value, LetterSpacing(value) => self.letter_spacing = value, + VerticalAlign(value) => self.vertical_align = value, + BaselineShift(value) => self.baseline_shift = value, } } @@ -473,6 +497,16 @@ impl ResolvedStyle { LineHeight(value) => nearly_eq(self.line_height, *value), WordSpacing(value) => nearly_eq(self.word_spacing, *value), LetterSpacing(value) => nearly_eq(self.letter_spacing, *value), + VerticalAlign(value) => self.vertical_align == *value, + BaselineShift(value) => match (self.baseline_shift, value) { + (crate::BaselineShift::Absolute(ours), crate::BaselineShift::Absolute(value)) => { + nearly_eq(ours, *value) + } + (crate::BaselineShift::Relative(ours), crate::BaselineShift::Relative(value)) => { + nearly_eq(ours, *value) + } + _ => false, + }, } } @@ -482,6 +516,10 @@ impl ResolvedStyle { underline: self.underline.as_layout_decoration(&self.brush), strikethrough: self.strikethrough.as_layout_decoration(&self.brush), line_height: self.line_height * self.font_size, + vertical_align: self.vertical_align, + baseline_shift: self + .baseline_shift + .resolve(self.font_size, self.line_height), } } } diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 841b5b29..e6031d10 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -22,6 +22,60 @@ pub enum WhiteSpaceCollapse { Preserve, } +#[derive(Clone, Copy, PartialEq, Debug, Default)] +pub enum VerticalAlign { + #[default] + Baseline, + TextBottom, + Middle, + TextTop, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum BaselineShift { + Absolute(f32), + Relative(f32), + Top, + Center, + Bottom, + // Sub, + // Super, +} + +impl Default for BaselineShift { + fn default() -> Self { + Self::Absolute(0.0) + } +} + +impl BaselineShift { + pub(crate) fn resolve(&self, font_size: f32, _line_height: f32) -> ResolvedBaselineShift { + match self { + Self::Absolute(value) => ResolvedBaselineShift::Absolute(*value), + Self::Relative(value) => ResolvedBaselineShift::Absolute(*value * font_size), + Self::Top => ResolvedBaselineShift::Top, + Self::Center => ResolvedBaselineShift::Center, + Self::Bottom => ResolvedBaselineShift::Bottom, + } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ResolvedBaselineShift { + Absolute(f32), + Top, + Center, + Bottom, + // Sub, + // Super, +} + +impl Default for ResolvedBaselineShift { + fn default() -> Self { + Self::Absolute(0.0) + } +} + /// Properties that define a style. #[derive(Clone, PartialEq, Debug)] pub enum StyleProperty<'a, B: Brush> { @@ -65,6 +119,10 @@ pub enum StyleProperty<'a, B: Brush> { WordSpacing(f32), /// Extra spacing between letters. LetterSpacing(f32), + /// The baseline along which this item is aligned. + VerticalAlign(VerticalAlign), + /// Additional baseline alignment applied afterwards. + BaselineShift(BaselineShift), } /// Unresolved styles. @@ -110,6 +168,10 @@ pub struct TextStyle<'a, B: Brush> { pub word_spacing: f32, /// Extra spacing between letters. pub letter_spacing: f32, + /// The baseline along which this item is aligned. + pub vertical_align: VerticalAlign, + /// Additional baseline alignment applied afterwards. + pub baseline_shift: BaselineShift, } impl Default for TextStyle<'_, B> { @@ -135,6 +197,8 @@ impl Default for TextStyle<'_, B> { line_height: 1.2, word_spacing: Default::default(), letter_spacing: Default::default(), + vertical_align: Default::default(), + baseline_shift: Default::default(), } } } diff --git a/parley/src/tests/test_basic.rs b/parley/src/tests/test_basic.rs index bdd671af..4b46850a 100644 --- a/parley/src/tests/test_basic.rs +++ b/parley/src/tests/test_basic.rs @@ -30,12 +30,7 @@ fn placing_inboxes() { ] { let text = "Hello world!\nLine 2\nLine 4"; let mut builder = env.ranged_builder(text); - builder.push_inline_box(InlineBox { - id: 0, - index: position, - width: 10.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(0, position, 10.0, 10.0)); let mut layout = builder.build(text); layout.break_all_lines(None); layout.align(None, Alignment::Start, AlignmentOptions::default()); @@ -50,12 +45,7 @@ fn only_inboxes_wrap() { let text = ""; let mut builder = env.ranged_builder(text); for id in 0..10 { - builder.push_inline_box(InlineBox { - id, - index: 0, - width: 10.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(id, 0, 10.0, 10.0)); } let mut layout = builder.build(text); layout.break_all_lines(Some(40.0)); @@ -71,24 +61,9 @@ fn full_width_inbox() { for (width, test_case_name) in [(99., "smaller"), (100., "exact"), (101., "larger")] { let text = "ABC"; let mut builder = env.ranged_builder(text); - builder.push_inline_box(InlineBox { - id: 0, - index: 1, - width: 10., - height: 10.0, - }); - builder.push_inline_box(InlineBox { - id: 1, - index: 1, - width, - height: 10.0, - }); - builder.push_inline_box(InlineBox { - id: 2, - index: 2, - width, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(0, 1, 10., 10.0)); + builder.push_inline_box(InlineBox::new(1, 1, width, 10.0)); + builder.push_inline_box(InlineBox::new(2, 2, width, 10.0)); let mut layout = builder.build(text); layout.break_all_lines(Some(100.)); layout.align(None, Alignment::Start, AlignmentOptions::default()); @@ -101,33 +76,13 @@ fn inbox_separated_by_whitespace() { let mut env = testenv!(); let mut builder = env.tree_builder(); - builder.push_inline_box(InlineBox { - id: 0, - index: 0, - width: 10., - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(0, 0, 10., 10.0)); builder.push_text(" "); - builder.push_inline_box(InlineBox { - id: 1, - index: 1, - width: 10.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(1, 1, 10.0, 10.0)); builder.push_text(" "); - builder.push_inline_box(InlineBox { - id: 2, - index: 2, - width: 10.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(2, 2, 10.0, 10.0)); builder.push_text(" "); - builder.push_inline_box(InlineBox { - id: 3, - index: 3, - width: 10.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(3, 3, 10.0, 10.0)); let (mut layout, _text) = builder.build(); layout.break_all_lines(Some(100.)); layout.align(None, Alignment::Start, AlignmentOptions::default()); @@ -276,12 +231,7 @@ fn inbox_content_width() { { let text = "Hello world!"; let mut builder = env.ranged_builder(text); - builder.push_inline_box(InlineBox { - id: 0, - index: 3, - width: 100.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(0, 3, 100.0, 10.0)); let mut layout = builder.build(text); layout.break_all_lines(Some(layout.min_content_width())); layout.align(None, Alignment::Start, AlignmentOptions::default()); @@ -292,12 +242,7 @@ fn inbox_content_width() { { let text = "A "; let mut builder = env.ranged_builder(text); - builder.push_inline_box(InlineBox { - id: 0, - index: 2, - width: 10.0, - height: 10.0, - }); + builder.push_inline_box(InlineBox::new(0, 2, 10.0, 10.0)); let mut layout = builder.build(text); layout.break_all_lines(Some(layout.max_content_width())); layout.align(None, Alignment::Start, AlignmentOptions::default());