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
8 changes: 5 additions & 3 deletions parley/src/layout/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::inline_box::InlineBox;
use crate::layout::{ContentWidths, Glyph, LineMetrics, RunMetrics, Style};
use crate::style::Brush;
use crate::util::nearly_zero;
use crate::{Font, OverflowWrap};
use crate::{Font, OverflowWrap, TextWrapMode};
use core::ops::Range;
use swash::Synthesis;
use swash::shape::Shaper;
Expand Down Expand Up @@ -517,8 +517,10 @@ impl<B: Brush> LayoutData<B> {
for cluster in clusters {
let boundary = cluster.info.boundary();
let style = &self.styles[cluster.style_index as usize];
if matches!(boundary, Boundary::Line | Boundary::Mandatory)
|| style.overflow_wrap == OverflowWrap::Anywhere
if boundary == Boundary::Mandatory
|| (style.text_wrap_mode == TextWrapMode::Wrap
&& (boundary == Boundary::Line
|| style.overflow_wrap == OverflowWrap::Anywhere))
{
let trailing_whitespace = whitespace_advance(prev_cluster);
min_width = min_width.max(running_min_width - trailing_whitespace);
Expand Down
5 changes: 3 additions & 2 deletions parley/src/layout/line/greedy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ use swash::text::cluster::Whitespace;
#[allow(unused_imports)]
use core_maths::CoreFloat;

use crate::OverflowWrap;
use crate::layout::{
Boundary, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData,
LineMetrics, Run,
};
use crate::style::Brush;
use crate::{OverflowWrap, TextWrapMode};

use core::ops::Range;

Expand Down Expand Up @@ -262,7 +262,8 @@ impl<'a, B: Brush> BreakLines<'a, B> {
let boundary = cluster.info().boundary();
let style = &self.layout.data.styles[cluster.data.style_index as usize];

if boundary == Boundary::Line {
if boundary == Boundary::Line && style.text_wrap_mode == TextWrapMode::Wrap
{
// We do not currently handle breaking within a ligature, so we ignore boundaries in such a position.
//
// We also don't record boundaries when the advance is 0. As we do not want overflowing content to cause extra consecutive
Expand Down
4 changes: 3 additions & 1 deletion parley/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod editor;
use self::alignment::align;

use super::style::Brush;
use crate::{Font, InlineBox, OverflowWrap};
use crate::{Font, InlineBox, OverflowWrap, TextWrapMode};
#[cfg(feature = "accesskit")]
use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate};
use alignment::unjustify;
Expand Down Expand Up @@ -309,6 +309,8 @@ pub struct Style<B: Brush> {
pub strikethrough: Option<Decoration<B>>,
/// Partially resolved line height, either in in layout units or dependent on metrics
pub(crate) line_height: LayoutLineHeight,
/// Per-cluster text-wrap-mode setting
pub(crate) text_wrap_mode: TextWrapMode,
/// Per-cluster overflow-wrap setting
pub(crate) overflow_wrap: OverflowWrap,
}
Expand Down
12 changes: 11 additions & 1 deletion parley/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::style::{
use crate::font::FontContext;
use crate::style::TextStyle;
use crate::util::nearly_eq;
use crate::{LineHeight, OverflowWrap, WordBreakStrength, layout};
use crate::{LineHeight, OverflowWrap, TextWrapMode, WordBreakStrength, layout};
use core::borrow::Borrow;
use core::ops::Range;
use fontique::FamilyId;
Expand Down Expand Up @@ -157,6 +157,7 @@ impl ResolveContext {
StyleProperty::LetterSpacing(value) => LetterSpacing(*value * scale),
StyleProperty::WordBreak(value) => WordBreak(*value),
StyleProperty::OverflowWrap(value) => OverflowWrap(*value),
StyleProperty::TextWrapMode(value) => TextWrapMode(*value),
}
}

Expand Down Expand Up @@ -193,6 +194,7 @@ impl ResolveContext {
letter_spacing: raw_style.letter_spacing * scale,
word_break: raw_style.word_break,
overflow_wrap: raw_style.overflow_wrap,
text_wrap_mode: raw_style.text_wrap_mode,
}
}

Expand Down Expand Up @@ -374,6 +376,8 @@ pub(crate) enum ResolvedProperty<B: Brush> {
WordBreak(WordBreakStrength),
/// Control over "emergency" line-breaking.
OverflowWrap(OverflowWrap),
/// Control over non-"emergency" line-breaking.
TextWrapMode(TextWrapMode),
}

/// Flattened group of style properties.
Expand Down Expand Up @@ -411,6 +415,8 @@ pub(crate) struct ResolvedStyle<B: Brush> {
pub(crate) word_break: WordBreakStrength,
/// Control over "emergency" line-breaking.
pub(crate) overflow_wrap: OverflowWrap,
/// Control over non-"emergency" line-breaking.
pub(crate) text_wrap_mode: TextWrapMode,
}

impl<B: Brush> Default for ResolvedStyle<B> {
Expand All @@ -432,6 +438,7 @@ impl<B: Brush> Default for ResolvedStyle<B> {
letter_spacing: 0.,
word_break: Default::default(),
overflow_wrap: Default::default(),
text_wrap_mode: Default::default(),
}
}
}
Expand Down Expand Up @@ -463,6 +470,7 @@ impl<B: Brush> ResolvedStyle<B> {
LetterSpacing(value) => self.letter_spacing = value,
WordBreak(value) => self.word_break = value,
OverflowWrap(value) => self.overflow_wrap = value,
TextWrapMode(value) => self.text_wrap_mode = value,
}
}

Expand Down Expand Up @@ -491,6 +499,7 @@ impl<B: Brush> ResolvedStyle<B> {
LetterSpacing(value) => nearly_eq(self.letter_spacing, *value),
WordBreak(value) => self.word_break == *value,
OverflowWrap(value) => self.overflow_wrap == *value,
TextWrapMode(value) => self.text_wrap_mode == *value,
}
}

Expand All @@ -500,6 +509,7 @@ impl<B: Brush> ResolvedStyle<B> {
underline: self.underline.as_layout_decoration(&self.brush),
strikethrough: self.strikethrough.as_layout_decoration(&self.brush),
line_height: self.line_height.resolve(self.font_size),
text_wrap_mode: self.text_wrap_mode,
overflow_wrap: self.overflow_wrap,
}
}
Expand Down
18 changes: 18 additions & 0 deletions parley/src/style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ pub enum WhiteSpaceCollapse {
Preserve,
}

/// Control over non-"emergency" line-breaking.
///
/// See <https://drafts.csswg.org/css-text-4/#text-wrap-mode> for more information.
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum TextWrapMode {
/// Wrap at non-emergency soft-wrap opportunities when necessary to prevent overflow.
#[default]
Wrap,
/// Do not wrap at non-emergency soft-wrap opportunities.
NoWrap,
Copy link
Contributor

@waywardmonkeys waywardmonkeys May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a u8 and might be involved in FFI, could you re-order these so that NoWrap is first and set to 0 and Wrap is set to 1?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't zero as default more common? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it depends. I think in this case this is like a Boolean where false is don’t wrap, more like zero. But I guess you came the counter argument that if you were to memset, you want the default…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the memset / zero-initialized value being default was what I was thinking.

Also, if we were to add support for more text-wrap values then this would go beyond boolean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can, but manually setting enum values sounds like an antipattern, even in C. Surely any FFI bindings would have enums with named variants on the foreign side anyway?

Copy link
Member

@DJMcNab DJMcNab May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the numbers is what allows for that enum with named variants on the foreign side. We don't want to encourage passing these over an FFI boundary, of course, but giving stable numbers still makes sense for when we implement the relevant bytemuck traits.

(We can ignore what the memset based default is; that's for any C projects using this to decide what is idiomatic on their side)

Either way, if you don't want to add this yourself, we can make it a trivial follow-up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're imagining someone making their own C bindings rather than us providing a C header?

I think you actually need repr(c) to pass things over FFI boundaries safely, and if you have that you don't technically need to specify numbers (although it could be nice for documentation) as there is a defined algorithm based on variant order (that matches the C algorithm).

}

/// Control over "emergency" line-breaking.
///
/// See <https://drafts.csswg.org/css-text/#overflow-wrap-property> for more information.
Expand Down Expand Up @@ -140,6 +153,8 @@ pub enum StyleProperty<'a, B: Brush> {
WordBreak(WordBreakStrength),
/// Control over "emergency" line-breaking.
OverflowWrap(OverflowWrap),
/// Control over non-"emergency" line-breaking.
TextWrapMode(TextWrapMode),
}

/// Unresolved styles.
Expand Down Expand Up @@ -189,6 +204,8 @@ pub struct TextStyle<'a, B: Brush> {
pub word_break: WordBreakStrength,
/// Control over "emergency" line-breaking.
pub overflow_wrap: OverflowWrap,
/// Control over non-"emergency" line-breaking.
pub text_wrap_mode: TextWrapMode,
}

impl<B: Brush> Default for TextStyle<'_, B> {
Expand Down Expand Up @@ -216,6 +233,7 @@ impl<B: Brush> Default for TextStyle<'_, B> {
letter_spacing: Default::default(),
word_break: Default::default(),
overflow_wrap: Default::default(),
text_wrap_mode: Default::default(),
}
}
}
Expand Down