Skip to content
Merged
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions examples/swash_render/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion examples/tiny_skia_render/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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 {
Expand Down
15 changes: 13 additions & 2 deletions parley/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<B>,
pub(crate) fcx: &'a mut FontContext,
}
Expand Down Expand Up @@ -52,7 +53,14 @@ impl<B: Brush> 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<str>) -> Layout<B> {
Expand All @@ -65,6 +73,7 @@ impl<B: Brush> 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<B>,
pub(crate) fcx: &'a mut FontContext,
}
Expand Down Expand Up @@ -122,7 +131,7 @@ impl<B: Brush> 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
}
Expand All @@ -137,6 +146,7 @@ impl<B: Brush> TreeBuilder<'_, B> {
fn build_into_layout<B: Brush>(
layout: &mut Layout<B>,
scale: f32,
quantize: bool,
text: &str,
lcx: &mut LayoutContext<B>,
fcx: &mut FontContext,
Expand All @@ -145,6 +155,7 @@ fn build_into_layout<B: Brush>(

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();
Expand Down
42 changes: 42 additions & 0 deletions parley/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,31 @@ impl<B: Brush> LayoutContext<B> {
.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());
Expand All @@ -73,15 +93,36 @@ impl<B: Brush> LayoutContext<B> {

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();
Expand All @@ -93,6 +134,7 @@ impl<B: Brush> LayoutContext<B> {

TreeBuilder {
scale,
quantize,
lcx: self,
fcx,
}
Expand Down
2 changes: 1 addition & 1 deletion parley/src/layout/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions parley/src/layout/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ pub(crate) struct LayoutItem {
#[derive(Clone)]
pub(crate) struct LayoutData<B: Brush> {
pub(crate) scale: f32,
pub(crate) quantize: bool,
pub(crate) has_bidi: bool,
pub(crate) base_level: u8,
pub(crate) text_len: usize,
Expand Down Expand Up @@ -234,6 +235,7 @@ impl<B: Brush> Default for LayoutData<B> {
fn default() -> Self {
Self {
scale: 1.,
quantize: true,
has_bidi: false,
base_level: 0,
text_len: 0,
Expand All @@ -260,6 +262,7 @@ impl<B: Brush> Default for LayoutData<B> {
impl<B: Brush> LayoutData<B> {
pub(crate) fn clear(&mut self) {
self.scale = 1.;
self.quantize = true;
self.has_bidi = false;
self.base_level = 0;
self.text_len = 0;
Expand Down
29 changes: 28 additions & 1 deletion parley/src/layout/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ where
show_cursor: bool,
width: Option<f32>,
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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<T> {
self.layout_dirty = true;
Expand Down Expand Up @@ -1057,7 +1083,8 @@ 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);
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());
}
Expand Down
Loading