From 5f61a1d64806fc8fdd8811aa9514e0eda5e5e328 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sat, 8 Jun 2024 08:44:41 +0400 Subject: [PATCH 1/3] Implement tree-style builder --- parley/src/builder.rs | 197 ++++++++++++++++++++++++++++++++++ parley/src/context.rs | 206 +++++++++++++----------------------- parley/src/lib.rs | 1 + parley/src/resolve/mod.rs | 53 +++++++++- parley/src/resolve/range.rs | 18 +--- parley/src/resolve/tree.rs | 187 ++++++++++++++++++++++++++++++++ parley/src/shape.rs | 3 +- parley/src/style/mod.rs | 78 ++++++++++++++ 8 files changed, 593 insertions(+), 150 deletions(-) create mode 100644 parley/src/builder.rs diff --git a/parley/src/builder.rs b/parley/src/builder.rs new file mode 100644 index 00000000..c0ea9054 --- /dev/null +++ b/parley/src/builder.rs @@ -0,0 +1,197 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Context for layout. + +use super::context::*; +use super::style::*; +use super::FontContext; + +#[cfg(feature = "std")] +use super::layout::Layout; + +use core::ops::RangeBounds; + +use crate::inline_box::InlineBox; + +/// Builder for constructing a text layout with ranged attributes. +pub struct RangedBuilder<'a, B: Brush> { + pub(crate) scale: f32, + pub(crate) lcx: &'a mut LayoutContext, + pub(crate) fcx: &'a mut FontContext, +} + +impl<'a, B: Brush> RangedBuilder<'a, B> { + pub fn push_default(&mut self, property: &StyleProperty) { + let resolved = self + .lcx + .rcx + .resolve_property(self.fcx, property, self.scale); + self.lcx.ranged_style_builder.push_default(resolved); + } + + pub fn push(&mut self, property: &StyleProperty, range: impl RangeBounds) { + let resolved = self + .lcx + .rcx + .resolve_property(self.fcx, property, self.scale); + self.lcx.ranged_style_builder.push(resolved, range); + } + + pub fn push_inline_box(&mut self, inline_box: InlineBox) { + self.lcx.inline_boxes.push(inline_box); + } + + #[cfg(feature = "std")] + pub fn build_into(&mut self, layout: &mut Layout, text: impl AsRef) { + // Apply RangedStyleBuilder styles to LayoutContext + 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); + } + + #[cfg(feature = "std")] + pub fn build(&mut self, text: impl AsRef) -> Layout { + let mut layout = Layout::default(); + self.build_into(&mut layout, text); + layout + } +} + +/// Builder for constructing a text layout with a tree of attributes. +pub struct TreeBuilder<'a, B: Brush> { + pub(crate) scale: f32, + pub(crate) lcx: &'a mut LayoutContext, + pub(crate) fcx: &'a mut FontContext, +} + +impl<'a, B: Brush> TreeBuilder<'a, B> { + pub fn push_style_span(&mut self, style: TextStyle) { + let resolved = self + .lcx + .rcx + .resolve_entire_style_set(self.fcx, &style, self.scale); + self.lcx.tree_style_builder.push_style_span(resolved); + } + + pub fn push_style_modification_span<'s, 'iter>( + &mut self, + properties: impl IntoIterator>, + ) where + 's: 'iter, + B: 'iter, + { + self.lcx.tree_style_builder.push_style_modification_span( + properties + .into_iter() + .map(|p| self.lcx.rcx.resolve_property(self.fcx, p, self.scale)), + ); + } + + pub fn pop_style_span(&mut self) { + self.lcx.tree_style_builder.pop_style_span(); + } + + pub fn push_text(&mut self, text: &str) { + self.lcx.tree_style_builder.push_text(text); + } + + pub fn push_inline_box(&mut self, mut inline_box: InlineBox) { + self.lcx.tree_style_builder.push_uncommitted_text(false); + // TODO: arrange type better here to factor out the index + inline_box.index = self.lcx.tree_style_builder.current_text_len(); + self.lcx.inline_boxes.push(inline_box); + } + + pub fn set_white_space_mode(&mut self, white_space_collapse: WhiteSpaceCollapse) { + self.lcx + .tree_style_builder + .set_white_space_mode(white_space_collapse); + } + + #[cfg(feature = "std")] + pub fn build_into(&mut self, layout: &mut Layout) -> String { + // Apply TreeStyleBuilder styles to LayoutContext + let text = self.lcx.tree_style_builder.finish(&mut self.lcx.styles); + + self.lcx.analyze_text(&text); + + // Call generic layout builder method + build_into_layout(layout, self.scale, &text, self.lcx, self.fcx); + + text + } + + #[cfg(feature = "std")] + pub fn build(&mut self) -> (Layout, String) { + let mut layout = Layout::default(); + let text = self.build_into(&mut layout); + (layout, text) + } +} + +#[cfg(feature = "std")] +fn build_into_layout( + layout: &mut Layout, + scale: f32, + text: &str, + lcx: &mut LayoutContext, + fcx: &mut FontContext, +) { + layout.data.clear(); + layout.data.scale = scale; + layout.data.has_bidi = !lcx.bidi.levels().is_empty(); + layout.data.base_level = lcx.bidi.base_level(); + layout.data.text_len = text.len(); + + // println!("BUILD INTO ({})", text.len()); + // for span in &lcx.styles { + // let stack = lcx.rcx.stack(span.style.font_stack); + // println!( + // "{:?} weight:{}, family: {:?}", + // span.range, span.style.font_weight, stack + // ); + // } + + let mut char_index = 0; + for (i, style) in lcx.styles.iter().enumerate() { + for _ in text[style.range.clone()].chars() { + lcx.info[char_index].1 = i as u16; + char_index += 1; + } + } + + // Copy the visual styles into the layout + layout + .data + .styles + .extend(lcx.styles.iter().map(|s| s.style.as_layout_style())); + + // Sort the inline boxes as subsequent code assumes that they are in text index order. + // Note: It's important that this is a stable sort to allow users to control the order of contiguous inline boxes + lcx.inline_boxes.sort_by_key(|b| b.index); + + // dbg!(&lcx.inline_boxes); + + { + let query = fcx.collection.query(&mut fcx.source_cache); + super::shape::shape_text( + &lcx.rcx, + query, + &lcx.styles, + &lcx.inline_boxes, + &lcx.info, + lcx.bidi.levels(), + &mut lcx.scx, + text, + layout, + ); + } + + // Move inline boxes into the layout + layout.data.inline_boxes.clear(); + core::mem::swap(&mut layout.data.inline_boxes, &mut lcx.inline_boxes); + + layout.data.finish(); +} diff --git a/parley/src/context.rs b/parley/src/context.rs index e2597374..aac5e61b 100644 --- a/parley/src/context.rs +++ b/parley/src/context.rs @@ -6,31 +6,33 @@ #[cfg(not(feature = "std"))] use alloc::{vec, vec::Vec}; +use self::tree::TreeStyleBuilder; + use super::bidi; -use super::resolve::range::*; +use super::builder::*; use super::resolve::*; use super::style::*; use super::FontContext; -#[cfg(feature = "std")] -use super::layout::Layout; - use swash::shape::ShapeContext; use swash::text::cluster::CharInfo; -use core::ops::RangeBounds; - +use crate::builder::TreeBuilder; use crate::inline_box::InlineBox; /// Context for building a text layout. pub struct LayoutContext { - bidi: bidi::BidiResolver, - rcx: ResolveContext, - styles: Vec>, - inline_boxes: Vec, - rsb: RangedStyleBuilder, - info: Vec<(CharInfo, u16)>, - scx: ShapeContext, + pub(crate) bidi: bidi::BidiResolver, + pub(crate) rcx: ResolveContext, + pub(crate) styles: Vec>, + pub(crate) inline_boxes: Vec, + + // Reusable style builders (to amortise allocations) + pub(crate) ranged_style_builder: RangedStyleBuilder, + pub(crate) tree_style_builder: TreeStyleBuilder, + + pub(crate) info: Vec<(CharInfo, u16)>, + pub(crate) scx: ShapeContext, } impl LayoutContext { @@ -40,35 +42,74 @@ impl LayoutContext { rcx: ResolveContext::default(), styles: vec![], inline_boxes: vec![], - rsb: RangedStyleBuilder::default(), + ranged_style_builder: RangedStyleBuilder::default(), + tree_style_builder: TreeStyleBuilder::default(), info: vec![], scx: ShapeContext::default(), } } + fn resolve_style_set( + &mut self, + font_ctx: &mut FontContext, + scale: f32, + raw_style: &TextStyle, + ) -> ResolvedStyle { + self.rcx + .resolve_entire_style_set(font_ctx, raw_style, scale) + } + + pub fn resolve_style( + &mut self, + font_ctx: &mut FontContext, + scale: f32, + raw_property: &StyleProperty, + ) -> ResolvedProperty { + self.rcx.resolve_property(font_ctx, raw_property, scale) + } + pub fn ranged_builder<'a>( &'a mut self, fcx: &'a mut FontContext, text: &'a str, scale: f32, - ) -> RangedBuilder<'a, B, &'a str> { - self.begin(text); + ) -> RangedBuilder<'a, B> { + self.begin(); + self.analyze_text(text); + self.ranged_style_builder.begin(text.len()); + #[cfg(feature = "std")] fcx.source_cache.prune(128, false); + RangedBuilder { - text, scale, lcx: self, fcx, } } - fn begin(&mut self, text: &str) { - self.rcx.clear(); - self.styles.clear(); - self.rsb.begin(text.len()); - self.info.clear(); - self.bidi.clear(); + pub fn tree_builder<'a>( + &'a mut self, + fcx: &'a mut FontContext, + scale: f32, + raw_style: &TextStyle, + ) -> TreeBuilder<'a, B> { + self.begin(); + + let resolved_root_style = self.resolve_style_set(fcx, scale, raw_style); + self.tree_style_builder.begin(resolved_root_style); + + #[cfg(feature = "std")] + fcx.source_cache.prune(128, false); + + TreeBuilder { + scale, + lcx: self, + fcx, + } + } + + pub(crate) fn analyze_text(&mut self, text: &str) { let text = if text.is_empty() { " " } else { text }; let mut a = swash::text::analyze(text.chars()); for x in a.by_ref() { @@ -82,6 +123,14 @@ impl LayoutContext { ); } } + + fn begin(&mut self) { + self.rcx.clear(); + self.styles.clear(); + self.inline_boxes.clear(); + self.info.clear(); + self.bidi.clear(); + } } impl Default for LayoutContext { @@ -96,114 +145,3 @@ impl Clone for LayoutContext { Self::new() } } - -/// Builder for constructing a text layout with ranged attributes. -pub struct RangedBuilder<'a, B: Brush, T: TextSource> { - text: T, - scale: f32, - lcx: &'a mut LayoutContext, - fcx: &'a mut FontContext, -} - -impl<'a, B: Brush, T: TextSource> RangedBuilder<'a, B, T> { - pub fn push_default(&mut self, property: &StyleProperty) { - let resolved = self.lcx.rcx.resolve(self.fcx, property, self.scale); - self.lcx.rsb.push_default(resolved); - } - - pub fn push(&mut self, property: &StyleProperty, range: impl RangeBounds) { - let resolved = self.lcx.rcx.resolve(self.fcx, property, self.scale); - self.lcx.rsb.push(resolved, range); - } - - pub fn push_inline_box(&mut self, inline_box: InlineBox) { - self.lcx.inline_boxes.push(inline_box); - } - - #[cfg(feature = "std")] - pub fn build_into(&mut self, layout: &mut Layout) { - layout.data.clear(); - layout.data.scale = self.scale; - let lcx = &mut self.lcx; - let mut text = self.text.as_str(); - let is_empty = text.is_empty(); - if is_empty { - // Force a layout to have at least one line. - // TODO: support layouts with no text - text = " "; - } - layout.data.has_bidi = !lcx.bidi.levels().is_empty(); - layout.data.base_level = lcx.bidi.base_level(); - layout.data.text_len = text.len(); - let fcx = &mut self.fcx; - lcx.rsb.finish(&mut lcx.styles); - let mut char_index = 0; - for (i, style) in lcx.styles.iter().enumerate() { - for _ in text[style.range.clone()].chars() { - lcx.info[char_index].1 = i as u16; - char_index += 1; - } - } - - // Copy the visual styles into the layout - layout - .data - .styles - .extend(lcx.styles.iter().map(|s| s.style.as_layout_style())); - - // Sort the inline boxes as subsequent code assumes that they are in text index order. - // Note: It's important that this is a stable sort to allow users to control the order of contiguous inline boxes - lcx.inline_boxes.sort_by_key(|b| b.index); - - // dbg!(&lcx.inline_boxes); - - { - let query = fcx.collection.query(&mut fcx.source_cache); - super::shape::shape_text( - &lcx.rcx, - query, - &lcx.styles, - &lcx.inline_boxes, - &lcx.info, - lcx.bidi.levels(), - &mut lcx.scx, - text, - layout, - ); - } - - // Move inline boxes into the layout - layout.data.inline_boxes.clear(); - core::mem::swap(&mut layout.data.inline_boxes, &mut lcx.inline_boxes); - - layout.data.finish(); - - // Extra processing if the text is empty - // TODO: update this logic to work with inline boxes - if is_empty { - layout.data.text_len = 0; - let run = &mut layout.data.runs[0]; - run.cluster_range.end = 0; - run.text_range.end = 0; - layout.data.clusters.clear(); - } - } - - #[cfg(feature = "std")] - pub fn build(&mut self) -> Layout { - let mut layout = Layout::default(); - self.build_into(&mut layout); - layout - } -} - -#[doc(hidden)] -pub trait TextSource { - fn as_str(&self) -> &str; -} - -impl<'a> TextSource for &'a str { - fn as_str(&self) -> &str { - self - } -} diff --git a/parley/src/lib.rs b/parley/src/lib.rs index c8d9e094..5aa278a6 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -23,6 +23,7 @@ mod shape; mod swash_convert; mod util; +pub mod builder; pub mod context; pub mod layout; pub mod style; diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index aad89632..16af7fb9 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -6,6 +6,8 @@ pub mod range; pub mod tree; +pub use range::RangedStyleBuilder; + #[cfg(not(feature = "std"))] use alloc::{vec, vec::Vec}; @@ -15,11 +17,26 @@ use super::style::{ }; use crate::font::FontContext; use crate::layout; +use crate::style::TextStyle; use crate::util::nearly_eq; +use core::ops::Range; use fontique::FamilyId; use swash::text::Language; use swash::Setting; +/// Style with an associated range. +#[derive(Debug, Clone)] +pub struct RangedStyle { + pub style: ResolvedStyle, + pub range: Range, +} + +#[derive(Clone)] +struct RangedProperty { + property: ResolvedProperty, + range: Range, +} + /// Handle for a managed property. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Resolved { @@ -109,7 +126,7 @@ pub struct ResolveContext { } impl ResolveContext { - pub fn resolve( + pub fn resolve_property( &mut self, fcx: &mut FontContext, property: &StyleProperty, @@ -142,6 +159,40 @@ impl ResolveContext { } } + pub fn resolve_entire_style_set( + &mut self, + fcx: &mut FontContext, + raw_style: &TextStyle, + scale: f32, + ) -> ResolvedStyle { + ResolvedStyle { + font_stack: self.resolve_stack(fcx, raw_style.font_stack), + font_size: raw_style.font_size * scale, + font_stretch: raw_style.font_stretch, + font_style: raw_style.font_style, + font_weight: raw_style.font_weight, + font_variations: self.resolve_variations(raw_style.font_variations), + font_features: self.resolve_features(raw_style.font_features), + locale: raw_style.locale.and_then(Language::parse), + brush: raw_style.brush.clone(), + underline: ResolvedDecoration { + enabled: raw_style.has_underline, + offset: raw_style.underline_offset.map(|x| x * scale), + size: raw_style.underline_size.map(|x| x * scale), + brush: raw_style.underline_brush.clone(), + }, + strikethrough: ResolvedDecoration { + enabled: raw_style.has_strikethrough, + offset: raw_style.strikethrough_offset.map(|x| x * scale), + size: raw_style.strikethrough_size.map(|x| x * scale), + brush: raw_style.strikethrough_brush.clone(), + }, + line_height: raw_style.line_height, + word_spacing: raw_style.word_spacing * scale, + letter_spacing: raw_style.letter_spacing * scale, + } + } + /// Resolves a font stack. pub fn resolve_stack(&mut self, fcx: &mut FontContext, stack: FontStack) -> Resolved { self.tmp_families.clear(); diff --git a/parley/src/resolve/range.rs b/parley/src/resolve/range.rs index 6c3f6671..28da7821 100644 --- a/parley/src/resolve/range.rs +++ b/parley/src/resolve/range.rs @@ -124,25 +124,17 @@ impl RangedStyleBuilder { } } styles.truncate(styles.len() - merged_count); + + // for span in styles.iter() { + // println!("{:?} weight:{}", span.range, span.style.font_weight); + // } + self.properties.clear(); self.default_style = ResolvedStyle::default(); self.len = !0; } } -/// Style with an associated range. -#[derive(Clone)] -pub struct RangedStyle { - pub style: ResolvedStyle, - pub range: Range, -} - -#[derive(Clone)] -struct RangedProperty { - property: ResolvedProperty, - range: Range, -} - #[derive(Default)] struct SplitRange { first: Option, diff --git a/parley/src/resolve/tree.rs b/parley/src/resolve/tree.rs index cbf07808..40867c77 100644 --- a/parley/src/resolve/tree.rs +++ b/parley/src/resolve/tree.rs @@ -2,3 +2,190 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Hierarchical tree based style application. +use alloc::borrow::Cow; +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +use crate::style::WhiteSpaceCollapse; + +use super::*; + +#[derive(Debug, Clone)] +struct StyleTreeNode { + parent: Option, + style: ResolvedStyle, +} + +/// Builder for constructing a tree of styles +#[derive(Clone)] +pub struct TreeStyleBuilder { + tree: Vec>, + flatted_styles: Vec>, + white_space_collapse: WhiteSpaceCollapse, + text: String, + uncommitted_text: String, + current_span: usize, + is_span_first: bool, +} + +impl TreeStyleBuilder { + fn current_style(&self) -> ResolvedStyle { + self.tree[self.current_span].style.clone() + } +} + +impl Default for TreeStyleBuilder { + fn default() -> Self { + Self { + tree: Vec::new(), + flatted_styles: Vec::new(), + white_space_collapse: WhiteSpaceCollapse::Preserve, + text: String::new(), + uncommitted_text: String::new(), + current_span: usize::MAX, + is_span_first: false, + } + } +} + +impl TreeStyleBuilder { + /// Prepares the builder for accepting a style tree for text of the specified length. + pub fn begin(&mut self, root_style: ResolvedStyle) { + self.tree.clear(); + self.flatted_styles.clear(); + self.white_space_collapse = WhiteSpaceCollapse::Preserve; + self.text.clear(); + self.uncommitted_text.clear(); + + self.tree.push(StyleTreeNode { + parent: None, + style: root_style, + }); + self.current_span = 0; + self.is_span_first = true; + } + + pub fn set_white_space_mode(&mut self, white_space_collapse: WhiteSpaceCollapse) { + self.white_space_collapse = white_space_collapse; + } + + pub fn push_uncommitted_text(&mut self, is_span_last: bool) { + let span_text: Cow = match self.white_space_collapse { + WhiteSpaceCollapse::Preserve => Cow::from(&self.uncommitted_text), + WhiteSpaceCollapse::Collapse => { + let mut span_text = self.uncommitted_text.as_str(); + + if self.is_span_first { + span_text = span_text.trim_start(); + } + if is_span_last { + span_text = span_text.trim_end(); + } + + // Collapse spaces + let mut last_char_whitespace = false; + let span_text: String = span_text + .chars() + .filter_map(|c: char| { + let this_char_whitespace = c.is_ascii_whitespace(); + let prev_char_whitespace = last_char_whitespace; + last_char_whitespace = this_char_whitespace; + + if this_char_whitespace { + if prev_char_whitespace { + None + } else { + Some(' ') + } + } else { + Some(c) + } + }) + .collect(); + + Cow::from(span_text) + } + }; + let span_text = span_text.as_ref(); + + // Nothing to do if there is no uncommitted text + if span_text.is_empty() { + // This is for the case of an inline box. This possibly ought to be made more explicit. + self.is_span_first = false; + return; + } + + let range = self.text.len()..(self.text.len() + span_text.len()); + let style = self.current_style(); + self.flatted_styles.push(RangedStyle { style, range }); + self.text.push_str(span_text); + self.uncommitted_text.clear(); + self.is_span_first = false; + } + + pub fn current_text_len(&self) -> usize { + self.text.len() + } + + pub fn push_style_span(&mut self, style: ResolvedStyle) { + self.push_uncommitted_text(false); + + self.tree.push(StyleTreeNode { + parent: Some(self.current_span), + style, + }); + self.current_span = self.tree.len() - 1; + self.is_span_first = true; + } + + pub fn push_style_modification_span( + &mut self, + properties: impl Iterator>, + ) { + let mut style = self.current_style(); + for prop in properties { + style.apply(prop.clone()); + } + self.push_style_span(style); + } + + pub fn pop_style_span(&mut self) { + self.push_uncommitted_text(true); + + self.current_span = self.tree[self.current_span] + .parent + .expect("Popped root style"); + } + + /// Pushes a property that covers the specified range of text. + pub fn push_text(&mut self, text: &str) { + if !text.is_empty() { + self.uncommitted_text.push_str(text); + } + } + + /// Computes the sequence of ranged styles. + pub fn finish(&mut self, styles: &mut Vec>) -> String { + while self.tree[self.current_span].parent.is_some() { + self.pop_style_span(); + } + + self.push_uncommitted_text(true); + + // println!("FINISH TREE"); + // dbg!(self.total_text_len); + // dbg!(&self.tree); + // for span in &self.flatted_styles { + // println!("{:?} weight:{}", span.range, span.style.font_weight); + // } + // dbg!(&self.flatted_styles); + + // println!("TEXT"); + // dbg!(&self.text); + + styles.clear(); + styles.extend_from_slice(&self.flatted_styles); + + core::mem::take(&mut self.text) + } +} diff --git a/parley/src/shape.rs b/parley/src/shape.rs index 79519b53..ac6823fe 100644 --- a/parley/src/shape.rs +++ b/parley/src/shape.rs @@ -3,8 +3,7 @@ #[cfg(feature = "std")] use super::layout::Layout; -use super::resolve::range::RangedStyle; -use super::resolve::{ResolveContext, Resolved}; +use super::resolve::{RangedStyle, ResolveContext, Resolved}; use super::style::{Brush, FontFeature, FontVariation}; #[cfg(feature = "std")] use crate::util::nearly_eq; diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 78042a1d..c7e76513 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -12,6 +12,12 @@ pub use font::{ FontWeight, GenericFamily, }; +#[derive(Debug, Clone, Copy)] +pub enum WhiteSpaceCollapse { + Collapse, + Preserve, +} + /// Properties that define a style. #[derive(Clone, PartialEq, Debug)] pub enum StyleProperty<'a, B: Brush> { @@ -56,3 +62,75 @@ pub enum StyleProperty<'a, B: Brush> { /// Extra spacing between letters. LetterSpacing(f32), } + +/// Unresolved styles. +#[derive(Clone, PartialEq, Debug)] +pub struct TextStyle<'a, B: Brush> { + /// Font family stack. + pub font_stack: FontStack<'a>, + /// Font size. + pub font_size: f32, + /// Font stretch. + pub font_stretch: FontStretch, + /// Font style. + pub font_style: FontStyle, + /// Font weight. + pub font_weight: FontWeight, + /// Font variation settings. + pub font_variations: FontSettings<'a, FontVariation>, + /// Font feature settings. + pub font_features: FontSettings<'a, FontFeature>, + /// Locale. + pub locale: Option<&'a str>, + /// Brush for rendering text. + pub brush: B, + /// Underline decoration. + pub has_underline: bool, + /// Offset of the underline decoration. + pub underline_offset: Option, + /// Size of the underline decoration. + pub underline_size: Option, + /// Brush for rendering the underline decoration. + pub underline_brush: Option, + /// Strikethrough decoration. + pub has_strikethrough: bool, + /// Offset of the strikethrough decoration. + pub strikethrough_offset: Option, + /// Size of the strikethrough decoration. + pub strikethrough_size: Option, + /// Brush for rendering the strikethrough decoration. + pub strikethrough_brush: Option, + /// Line height multiplier. + pub line_height: f32, + /// Extra spacing between words. + pub word_spacing: f32, + /// Extra spacing between letters. + pub letter_spacing: f32, +} + +impl<'a, B: Brush> Default for TextStyle<'a, B> { + fn default() -> Self { + TextStyle { + font_stack: FontStack::Source("sans-serif"), + font_size: 16.0, + font_stretch: Default::default(), + font_style: Default::default(), + font_weight: Default::default(), + font_variations: FontSettings::List(&[]), + font_features: FontSettings::List(&[]), + locale: Default::default(), + brush: Default::default(), + has_underline: Default::default(), + underline_offset: Default::default(), + underline_size: Default::default(), + underline_brush: Default::default(), + has_strikethrough: Default::default(), + strikethrough_offset: Default::default(), + strikethrough_size: Default::default(), + strikethrough_brush: Default::default(), + line_height: 1.2, + word_spacing: Default::default(), + letter_spacing: Default::default(), + } + } +} From 2244f53f1cbc45e016fc6978b61ff951f4794df7 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 9 Jun 2024 01:35:44 +0400 Subject: [PATCH 2/3] Tree builder example --- examples/swash_render/src/main.rs | 115 +++++++++++++++++++------- examples/tiny_skia_render/src/main.rs | 2 +- examples/vello_editor/src/text.rs | 2 +- 3 files changed, 86 insertions(+), 33 deletions(-) diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index 74959d9d..5b390153 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -7,7 +7,7 @@ use image::codecs::png::PngEncoder; use image::{self, Pixel, Rgba, RgbaImage}; use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; -use parley::style::{FontStack, FontWeight, StyleProperty}; +use parley::style::{FontStack, FontWeight, StyleProperty, TextStyle}; use parley::{FontContext, InlineBox, LayoutContext}; use peniko::Color; use std::fs::File; @@ -44,40 +44,93 @@ fn main() { let mut layout_cx = LayoutContext::new(); let mut scale_cx = ScaleContext::new(); - // Create a RangedBuilder - let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale); - - // Set default text colour styles (set foreground text color) + // Setup some Parley text styles let brush_style = StyleProperty::Brush(text_color); - builder.push_default(&brush_style); - - // Set default font family let font_stack = FontStack::Source("system-ui"); - let font_stack_style = StyleProperty::FontStack(font_stack); - builder.push_default(&font_stack_style); - builder.push_default(&StyleProperty::LineHeight(1.3)); - builder.push_default(&StyleProperty::FontSize(16.0)); - - // Set the first 4 characters to bold + let font_stack_style: StyleProperty = StyleProperty::FontStack(font_stack); let bold = FontWeight::new(600.0); let bold_style = StyleProperty::FontWeight(bold); - builder.push(&bold_style, 0..4); - - 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, - }); - - // Build the builder into a Layout - let mut layout: Layout = builder.build(); + + let mut layout = if std::env::args().any(|arg| arg == "--tree") { + // TREE BUILDER + // ============ + + // TODO: cleanup API + + let root_style = TextStyle { + brush: text_color, + font_stack, + line_height: 1.3, + font_size: 16.0, + ..Default::default() + }; + + let mut builder = layout_cx.tree_builder(&mut font_cx, display_scale, &root_style); + + builder.push_style_modification_span(&[bold_style]); + builder.push_text(&text[0..5]); + builder.pop_style_span(); + + builder.push_text(&text[5..40]); + + builder.push_inline_box(InlineBox { + id: 0, + index: 0, + width: 50.0, + height: 50.0, + }); + + builder.push_text(&text[40..50]); + + builder.push_inline_box(InlineBox { + id: 1, + index: 50, + width: 50.0, + height: 30.0, + }); + + builder.push_text(&text[50..]); + + // Build the builder into a Layout + // let mut layout: Layout = builder.build(&text); + let (layout, _text): (Layout, String) = builder.build(); + layout + } else { + // RANGE BUILDER + // ============ + + // Creatse a RangedBuilder + let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale); + + // Set default text colour styles (set foreground text color) + builder.push_default(&brush_style); + + // Set default font family + builder.push_default(&font_stack_style); + builder.push_default(&StyleProperty::LineHeight(1.3)); + builder.push_default(&StyleProperty::FontSize(16.0)); + + // Set the first 4 characters to bold + builder.push(&bold_style, 0..4); + + 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, + }); + + // Build the builder into a Layout + // let mut layout: Layout = builder.build(&text); + let layout: Layout = builder.build(&text); + layout + }; // Perform layout (including bidi resolution and shaping) with start alignment layout.break_all_lines(max_advance); diff --git a/examples/tiny_skia_render/src/main.rs b/examples/tiny_skia_render/src/main.rs index 10ee4876..e7d2211c 100644 --- a/examples/tiny_skia_render/src/main.rs +++ b/examples/tiny_skia_render/src/main.rs @@ -72,7 +72,7 @@ fn main() { }); // Build the builder into a Layout - let mut layout: Layout = builder.build(); + let mut layout: Layout = builder.build(&text); // Perform layout (including bidi resolution and shaping) with start alignment layout.break_all_lines(max_advance); diff --git a/examples/vello_editor/src/text.rs b/examples/vello_editor/src/text.rs index 897654a8..8de7d05e 100644 --- a/examples/vello_editor/src/text.rs +++ b/examples/vello_editor/src/text.rs @@ -57,7 +57,7 @@ impl Editor { builder.push_default(&parley::style::StyleProperty::FontStack( parley::style::FontStack::Source("system-ui"), )); - builder.build_into(&mut self.layout); + builder.build_into(&mut self.layout, &self.buffer); self.layout.break_all_lines(Some(width - INSET * 2.0)); self.layout .align(Some(width - INSET * 2.0), parley::layout::Alignment::Start); From 9f65a4fc755bde7898dcfb38f197539d3a07d693 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Thu, 19 Sep 2024 21:15:49 +0100 Subject: [PATCH 3/3] Remove commented debug code --- parley/src/builder.rs | 11 ----------- parley/src/resolve/range.rs | 4 ---- parley/src/resolve/tree.rs | 11 ----------- 3 files changed, 26 deletions(-) diff --git a/parley/src/builder.rs b/parley/src/builder.rs index c0ea9054..31def400 100644 --- a/parley/src/builder.rs +++ b/parley/src/builder.rs @@ -145,15 +145,6 @@ fn build_into_layout( layout.data.base_level = lcx.bidi.base_level(); layout.data.text_len = text.len(); - // println!("BUILD INTO ({})", text.len()); - // for span in &lcx.styles { - // let stack = lcx.rcx.stack(span.style.font_stack); - // println!( - // "{:?} weight:{}, family: {:?}", - // span.range, span.style.font_weight, stack - // ); - // } - let mut char_index = 0; for (i, style) in lcx.styles.iter().enumerate() { for _ in text[style.range.clone()].chars() { @@ -172,8 +163,6 @@ fn build_into_layout( // Note: It's important that this is a stable sort to allow users to control the order of contiguous inline boxes lcx.inline_boxes.sort_by_key(|b| b.index); - // dbg!(&lcx.inline_boxes); - { let query = fcx.collection.query(&mut fcx.source_cache); super::shape::shape_text( diff --git a/parley/src/resolve/range.rs b/parley/src/resolve/range.rs index 28da7821..ceb25932 100644 --- a/parley/src/resolve/range.rs +++ b/parley/src/resolve/range.rs @@ -125,10 +125,6 @@ impl RangedStyleBuilder { } styles.truncate(styles.len() - merged_count); - // for span in styles.iter() { - // println!("{:?} weight:{}", span.range, span.style.font_weight); - // } - self.properties.clear(); self.default_style = ResolvedStyle::default(); self.len = !0; diff --git a/parley/src/resolve/tree.rs b/parley/src/resolve/tree.rs index 40867c77..dbe88846 100644 --- a/parley/src/resolve/tree.rs +++ b/parley/src/resolve/tree.rs @@ -172,17 +172,6 @@ impl TreeStyleBuilder { self.push_uncommitted_text(true); - // println!("FINISH TREE"); - // dbg!(self.total_text_len); - // dbg!(&self.tree); - // for span in &self.flatted_styles { - // println!("{:?} weight:{}", span.range, span.style.font_weight); - // } - // dbg!(&self.flatted_styles); - - // println!("TEXT"); - // dbg!(&self.text); - styles.clear(); styles.extend_from_slice(&self.flatted_styles);