Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe

This release has an [MSRV] of 1.88.

### Added

#### Parley

- Layouts now have a root-level style ([#718][] by [@nicoburns][]).
The new `RootStyle` struct nests a `TextStyle` (which acts as the layout's default style) plus a `strut` flag.
All three builder constructors (`ranged_builder`, `style_run_builder`, and `tree_builder`) now take a `&RootStyle` argument, which is a breaking change for `ranged_builder` and `style_run_builder`.
The resolved root style is stored on the layout and exposed via `Layout::{root_style, root_font_size, root_line_height, root_font_metrics}`.
Root font metrics are resolved at build time without shaping any text, so they are available even for empty layouts.
Empty layouts and the empty line following a trailing newline now derive their metrics from the root style.
When `RootStyle::strut` is `true`, the root style's metrics floor every line box's metrics, matching CSS strut behavior.

### Changed

#### Parley
Expand Down Expand Up @@ -704,6 +716,7 @@ This release has an [MSRV][] of 1.70.
[#661]: https://github.com/linebender/parley/pull/661
[#671]: https://github.com/linebender/parley/pull/671
[#697]: https://github.com/linebender/parley/pull/697
[#718]: https://github.com/linebender/parley/pull/718

[Unreleased]: https://github.com/linebender/parley/compare/v0.11.0...HEAD
[0.11.0]: https://github.com/linebender/parley/compare/v0.10.0...v0.11.0
Expand Down
19 changes: 15 additions & 4 deletions examples/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;

use parley::RootStyle;
use parley::fontique::Blob;
use parley::{
Alignment, AlignmentOptions, FontContext, FontFamily, FontWeight, GenericFamily, InlineBox,
Expand Down Expand Up @@ -124,8 +125,13 @@ pub fn build_simple_layout(
layout_cx: &mut LayoutContext<ColorBrush>,
config: &ExampleConfig,
) -> (Layout<ColorBrush>, u16, u16) {
let mut builder =
layout_cx.ranged_builder(font_cx, &config.text, config.display_scale, config.quantize);
let mut builder = layout_cx.ranged_builder(
font_cx,
&config.text,
config.display_scale,
config.quantize,
&RootStyle::default(),
);

let foreground_brush = ColorBrush {
color: config.foreground_color,
Expand Down Expand Up @@ -174,8 +180,13 @@ pub fn build_rich_layout(

let (underline_range, strikethrough_range, party_emoji_range) = style_ranges(&config.text);

let mut builder =
layout_cx.ranged_builder(font_cx, &config.text, config.display_scale, config.quantize);
let mut builder = layout_cx.ranged_builder(
font_cx,
&config.text,
config.display_scale,
config.quantize,
&RootStyle::default(),
);

let foreground_brush = ColorBrush {
color: config.foreground_color,
Expand Down
14 changes: 10 additions & 4 deletions examples/swash_render/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use image::codecs::png::PngEncoder;
use image::{self, Pixel, Rgba, RgbaImage};
use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem};
use parley::style::{FontFamily, FontWeight, StyleProperty, TextStyle};
use parley::style::{FontFamily, FontWeight, RootStyle, StyleProperty, TextStyle};
use parley::{AlignmentOptions, FontContext, InlineBox, InlineBoxKind, LayoutContext, LineHeight};
use std::fs::File;
use swash::FontRef;
Expand Down Expand Up @@ -75,13 +75,13 @@ fn main() {

// TODO: cleanup API

let root_style = TextStyle {
let root_style = RootStyle::from(TextStyle {
brush: text_brush,
font_family,
line_height: LineHeight::FontSizeRelative(1.3),
font_size: 16.0,
..TextStyle::default()
};
});

let mut builder =
layout_cx.tree_builder(&mut font_cx, display_scale, quantize, &root_style);
Expand Down Expand Up @@ -137,7 +137,13 @@ fn main() {
// ============

// Creates a RangedBuilder
let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale, quantize);
let mut builder = layout_cx.ranged_builder(
&mut font_cx,
&text,
display_scale,
quantize,
&RootStyle::default(),
);

// Set default text colour styles (set foreground text color)
builder.push_default(brush_style);
Expand Down
9 changes: 8 additions & 1 deletion examples/tiny_skia_render/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#![expect(clippy::cast_possible_truncation, reason = "Deferred")]

use parley::RootStyle;
use parley::{
Alignment, AlignmentOptions, FontContext, FontWeight, GenericFamily, GlyphRun, InlineBox,
InlineBoxKind, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
Expand Down Expand Up @@ -64,7 +65,13 @@ fn main() {
let mut layout_cx = LayoutContext::new();

// Create a RangedBuilder
let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale, quantize);
let mut builder = layout_cx.ranged_builder(
&mut font_cx,
&text,
display_scale,
quantize,
&RootStyle::default(),
);

// Set default text colour styles (set foreground text color)
let foreground_brush = ColorBrush {
Expand Down
3 changes: 2 additions & 1 deletion parley/src/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ pub(crate) fn analyze_text<B: Brush>(
lcx.word_break
.extend(lcx.style_runs.iter().filter_map(|sr| {
let word_break = lcx.style_table[sr.style_index as usize].word_break;
(word_break != WordBreak::Normal).then(|| (sr.range.clone(), word_break))
(!sr.range.is_empty() && word_break != WordBreak::Normal)
.then(|| (sr.range.clone(), word_break))
}));

let options = AnalysisOptions {
Expand Down
18 changes: 16 additions & 2 deletions parley/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use parley_engine::break_overrides::LineBreakOverrideFn;

use crate::InlineBoxKind;
use crate::inline_box::InlineBox;
use crate::resolve::{ResolvedStyle, StyleRun, tree::ItemKind};
use crate::resolve::{StyleRun, tree::ItemKind};

#[derive(Clone, Copy)]
pub(crate) struct BuilderOptions<'a> {
Expand Down Expand Up @@ -51,6 +51,7 @@ impl<'b, B: Brush> RangedBuilder<'b, B> {
self.lcx
.rcx
.resolve_property(self.fcx, &property.into(), self.options.scale);
self.lcx.root_style.apply(resolved.clone());
self.lcx.ranged_style_builder.push_default(resolved);
}

Expand Down Expand Up @@ -302,7 +303,9 @@ fn build_into_layout<B: Brush>(
options: BuilderOptions<'_>,
) {
if text.is_empty() && lcx.style_runs.is_empty() {
lcx.style_table.push(ResolvedStyle::default());
// Style the empty layout with the root style so that it produces
// meaningful metrics (e.g. for sizing a cursor).
lcx.style_table.push(lcx.root_style.clone());
lcx.style_runs.push(StyleRun {
style_index: 0,
range: 0..0,
Expand All @@ -326,6 +329,17 @@ fn build_into_layout<B: Brush>(
layout.data.base_level = lcx.analysis.paragraph_level();
layout.data.text_len = text.len();

layout.data.root_style = lcx.root_style.as_layout_style();
layout.data.root_font_size = lcx.root_style.font_size;
layout.data.strut = lcx.root_style_strut;
{
let mut query = fcx.collection.query(&mut fcx.source_cache);
let (metrics, line_height) =
super::shape::root_font_metrics(&lcx.rcx, &mut query, &lcx.root_style);
layout.data.root_font_metrics = metrics;
layout.data.root_line_height = line_height;
}

lcx.char_style_indices
.resize(lcx.analysis.char_info().len(), 0);
let mut char_index = 0;
Expand Down
23 changes: 19 additions & 4 deletions parley/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::FontContext;
use super::builder::{BuilderOptions, RangedBuilder, StyleRunBuilder};
use super::resolve::tree::TreeStyleBuilder;
use super::resolve::{RangedStyleBuilder, ResolveContext, ResolvedStyle, StyleRun};
use super::style::{Brush, TextStyle};
use super::style::{Brush, RootStyle, TextStyle};

use crate::builder::TreeBuilder;
use crate::inline_box::InlineBox;
Expand All @@ -37,6 +37,10 @@ pub struct LayoutContext<B: Brush = [u8; 4]> {
pub(crate) ranged_style_builder: RangedStyleBuilder<B>,
pub(crate) tree_style_builder: TreeStyleBuilder<B>,

// The resolved root style for the layout currently being built
pub(crate) root_style: ResolvedStyle<B>,
pub(crate) root_style_strut: bool,

/// Style index for each character, parallel to [`Analysis::char_info`].
pub(crate) char_style_indices: Vec<u16>,
pub(crate) scx: Shaper,
Expand All @@ -57,6 +61,8 @@ impl<B: Brush> LayoutContext<B> {
word_break: Vec::new(),
ranged_style_builder: RangedStyleBuilder::default(),
tree_style_builder: TreeStyleBuilder::default(),
root_style: ResolvedStyle::default(),
root_style_strut: false,
char_style_indices: vec![],
analysis_data_sources: AnalysisDataSources::new(),
scx: Shaper::default(),
Expand Down Expand Up @@ -98,10 +104,13 @@ impl<B: Brush> LayoutContext<B> {
text: &'a str,
scale: f32,
quantize: bool,
root_style: &RootStyle<'_, '_, B>,
) -> RangedBuilder<'a, B> {
self.begin();

let resolved_root_style = self.resolve_style_set(fcx, scale, &TextStyle::default());
let resolved_root_style = self.resolve_style_set(fcx, scale, &root_style.style);
self.root_style = resolved_root_style.clone();
self.root_style_strut = root_style.strut;
self.ranged_style_builder
.begin(resolved_root_style, text.len());

Expand All @@ -128,9 +137,13 @@ impl<B: Brush> LayoutContext<B> {
text: &'a str,
scale: f32,
quantize: bool,
root_style: &RootStyle<'_, '_, B>,
) -> StyleRunBuilder<'a, B> {
self.begin();

self.root_style = self.resolve_style_set(fcx, scale, &root_style.style);
self.root_style_strut = root_style.strut;

fcx.source_cache.prune(128, false);

StyleRunBuilder {
Expand Down Expand Up @@ -166,11 +179,13 @@ impl<B: Brush> LayoutContext<B> {
fcx: &'a mut FontContext,
scale: f32,
quantize: bool,
root_style: &TextStyle<'_, '_, B>,
root_style: &RootStyle<'_, '_, B>,
) -> TreeBuilder<'a, B> {
self.begin();

let resolved_root_style = self.resolve_style_set(fcx, scale, root_style);
let resolved_root_style = self.resolve_style_set(fcx, scale, &root_style.style);
self.root_style = resolved_root_style.clone();
self.root_style_strut = root_style.strut;
self.tree_style_builder.begin(resolved_root_style);

fcx.source_cache.prune(128, false);
Expand Down
11 changes: 8 additions & 3 deletions parley/src/editing/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use core::{
use crate::editing::{Cursor, Selection};
use crate::layout::{Affinity, Alignment, AlignmentOptions, Layout};
use crate::style::Brush;
use crate::{BoundingBox, FontContext, LayoutContext, StyleProperty, StyleSet};
use crate::{BoundingBox, FontContext, LayoutContext, RootStyle, StyleProperty, StyleSet};

#[cfg(feature = "accesskit")]
use crate::layout::LayoutAccessibility;
Expand Down Expand Up @@ -1227,8 +1227,13 @@ where
}
/// Update the layout.
fn update_layout(&mut self, font_cx: &mut FontContext, layout_cx: &mut LayoutContext<T>) {
let mut builder =
layout_cx.ranged_builder(font_cx, &self.buffer, self.scale, self.quantize);
let mut builder = layout_cx.ranged_builder(
font_cx,
&self.buffer,
self.scale,
self.quantize,
&RootStyle::default(),
);
for prop in self.default_style.inner().values() {
builder.push_default(prop.to_owned());
}
Expand Down
3 changes: 2 additions & 1 deletion parley/src/layout/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,8 @@ mod tests {
// TODO: Use a test font
let mut font_ctx = FontContext::new();
let text = "Parley exists";
let mut builder = layout_ctx.ranged_builder(&mut font_ctx, text, 1.0, true);
let mut builder =
layout_ctx.ranged_builder(&mut font_ctx, text, 1.0, true, &crate::RootStyle::default());
builder.push_default(StyleProperty::FontSize(10.));
let mut layout = builder.build(text);
layout.break_all_lines(None);
Expand Down
24 changes: 23 additions & 1 deletion parley/src/layout/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use core::ops::Range;

use alloc::vec::Vec;
use parley_engine::shape::ClusterData;
use parley_engine::{Boundary, ShapedText};
use parley_engine::{Boundary, FontMetrics, ShapedText};

/// `HarfRust`-based run data
#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -158,6 +158,18 @@ pub(crate) struct LayoutData<B: Brush> {
/// The length of the text in the layout
pub(crate) text_len: usize,

// Resolved root style
/// The layout's root style.
pub(crate) root_style: Style<B>,
/// The font size of the root style, in layout units.
pub(crate) root_font_size: f32,
/// The resolved line height of the root style, in layout units.
pub(crate) root_line_height: f32,
/// The metrics of the root style's font.
pub(crate) root_font_metrics: FontMetrics,
/// Whether the root style acts as a strut, flooring the metrics of every line box.
pub(crate) strut: bool,

// Output of style resolution (input to line breaking)
pub(crate) styles: Vec<Style<B>>,
pub(crate) inline_boxes: Vec<InlineBox>,
Expand Down Expand Up @@ -204,6 +216,11 @@ impl<B: Brush> Default for LayoutData<B> {
width: 0.,
full_width: 0.,
height: 0.,
root_style: Style::default(),
root_font_size: 0.,
root_line_height: 0.,
root_font_metrics: FontMetrics::default(),
strut: false,
styles: Vec::new(),
inline_boxes: Vec::new(),
shaped_text: ShapedText::new(),
Expand All @@ -230,6 +247,11 @@ impl<B: Brush> LayoutData<B> {
self.width = 0.;
self.full_width = 0.;
self.height = 0.;
self.root_style = Style::default();
self.root_font_size = 0.;
self.root_line_height = 0.;
self.root_font_metrics = FontMetrics::default();
self.strut = false;
self.styles.clear();
self.inline_boxes.clear();
self.shaped_text.clear();
Expand Down
25 changes: 25 additions & 0 deletions parley/src/layout/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::layout::data::LayoutData;
use crate::style::Brush;
use core::cmp::Ordering;
use core::fmt;
use parley_engine::FontMetrics;

use crate::IndentOptions;
use crate::layout::{
Expand Down Expand Up @@ -59,6 +60,30 @@ impl<B: Brush> Layout<B> {
&self.data.styles
}

/// Returns the layout's root style (see [`RootStyle`](crate::RootStyle)).
pub fn root_style(&self) -> &Style<B> {
&self.data.root_style
}

/// Returns the font size of the layout's root style, in layout units
/// (i.e. scaled by the layout's scale factor).
pub fn root_font_size(&self) -> f32 {
self.data.root_font_size
}

/// Returns the resolved line height of the layout's root style, in layout units.
pub fn root_line_height(&self) -> f32 {
self.data.root_line_height
}

/// Returns the metrics of the font selected for the layout's root style.
///
/// These are resolved at build time without shaping any text, so they are
/// available even for an empty layout.
pub fn root_font_metrics(&self) -> &FontMetrics {
&self.data.root_font_metrics
}

/// The `max_advance` that was used to line break the `Layout`
pub fn layout_max_advance(&self) -> f32 {
self.data.layout_max_advance
Expand Down
Loading