diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index 6bf60bfd..1e78ac85 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -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; @@ -517,8 +517,10 @@ impl LayoutData { 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); diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index 803cab9b..c262f341 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/layout/line/greedy.rs @@ -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; @@ -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 diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index 235abd45..a0dcd875 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, OverflowWrap}; +use crate::{Font, InlineBox, OverflowWrap, TextWrapMode}; #[cfg(feature = "accesskit")] use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; use alignment::unjustify; @@ -309,6 +309,8 @@ pub struct Style { pub strikethrough: Option>, /// 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, } diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index 0270b854..e3def92f 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -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; @@ -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), } } @@ -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, } } @@ -374,6 +376,8 @@ pub(crate) enum ResolvedProperty { WordBreak(WordBreakStrength), /// Control over "emergency" line-breaking. OverflowWrap(OverflowWrap), + /// Control over non-"emergency" line-breaking. + TextWrapMode(TextWrapMode), } /// Flattened group of style properties. @@ -411,6 +415,8 @@ pub(crate) struct ResolvedStyle { 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 Default for ResolvedStyle { @@ -432,6 +438,7 @@ impl Default for ResolvedStyle { letter_spacing: 0., word_break: Default::default(), overflow_wrap: Default::default(), + text_wrap_mode: Default::default(), } } } @@ -463,6 +470,7 @@ impl ResolvedStyle { 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, } } @@ -491,6 +499,7 @@ impl ResolvedStyle { 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, } } @@ -500,6 +509,7 @@ impl ResolvedStyle { 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, } } diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index f13f5a1a..5d597b1f 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -25,6 +25,19 @@ pub enum WhiteSpaceCollapse { Preserve, } +/// Control over non-"emergency" line-breaking. +/// +/// See 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, +} + /// Control over "emergency" line-breaking. /// /// See for more information. @@ -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. @@ -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 Default for TextStyle<'_, B> { @@ -216,6 +233,7 @@ impl Default for TextStyle<'_, B> { letter_spacing: Default::default(), word_break: Default::default(), overflow_wrap: Default::default(), + text_wrap_mode: Default::default(), } } }