From 567db40ce4d8ee9cecdbca36609388533aaaea5c Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 17:49:02 +0100 Subject: [PATCH 01/14] Create input module and move relevant files there --- examples/swash_render/src/main.rs | 2 +- parley/src/{ => inputs}/builder.rs | 11 +++--- parley/src/{ => inputs}/context.rs | 12 +++--- parley/src/{ => inputs}/font.rs | 0 parley/src/inputs/line_metrics.rs | 41 +++++++++++++++++++++ parley/src/inputs/mod.rs | 13 +++++++ parley/src/{ => inputs}/style/brush.rs | 0 parley/src/{ => inputs}/style/font.rs | 0 parley/src/{ => inputs}/style/mod.rs | 0 parley/src/{ => inputs}/style/styleset.rs | 0 parley/src/layout/alignment.rs | 2 +- parley/src/layout/data.rs | 2 +- parley/src/layout/editor.rs | 26 ++++++------- parley/src/layout/line/greedy.rs | 2 +- parley/src/layout/line/mod.rs | 45 +++-------------------- parley/src/layout/mod.rs | 5 ++- parley/src/lib.rs | 19 ++++++---- parley/src/resolve/mod.rs | 6 +-- parley/src/resolve/tree.rs | 2 +- parley/src/shape.rs | 6 +-- 20 files changed, 107 insertions(+), 87 deletions(-) rename parley/src/{ => inputs}/builder.rs (96%) rename parley/src/{ => inputs}/context.rs (93%) rename parley/src/{ => inputs}/font.rs (100%) create mode 100644 parley/src/inputs/line_metrics.rs create mode 100644 parley/src/inputs/mod.rs rename parley/src/{ => inputs}/style/brush.rs (100%) rename parley/src/{ => inputs}/style/font.rs (100%) rename parley/src/{ => inputs}/style/mod.rs (100%) rename parley/src/{ => inputs}/style/styleset.rs (100%) diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index b7cc2c12..9f2243e7 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -12,8 +12,8 @@ use image::codecs::png::PngEncoder; use image::{self, Pixel, Rgba, RgbaImage}; +use parley::inputs::style::{FontStack, FontWeight, StyleProperty, TextStyle}; use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; -use parley::style::{FontStack, FontWeight, StyleProperty, TextStyle}; use parley::{FontContext, InlineBox, LayoutContext}; use std::fs::File; use swash::scale::image::Content; diff --git a/parley/src/builder.rs b/parley/src/inputs/builder.rs similarity index 96% rename from parley/src/builder.rs rename to parley/src/inputs/builder.rs index abac1468..f47adbc6 100644 --- a/parley/src/builder.rs +++ b/parley/src/inputs/builder.rs @@ -3,11 +3,12 @@ //! Context for layout. -use super::context::LayoutContext; -use super::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; -use super::FontContext; +use crate::inputs::context::LayoutContext; +use crate::inputs::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; +use crate::shape::shape_text; +use crate::FontContext; -use super::layout::Layout; +use crate::layout::Layout; use alloc::string::String; use core::ops::RangeBounds; @@ -165,7 +166,7 @@ fn build_into_layout( { let query = fcx.collection.query(&mut fcx.source_cache); - super::shape::shape_text( + shape_text( &lcx.rcx, query, &lcx.styles, diff --git a/parley/src/context.rs b/parley/src/inputs/context.rs similarity index 93% rename from parley/src/context.rs rename to parley/src/inputs/context.rs index 11d9753c..43b6de39 100644 --- a/parley/src/context.rs +++ b/parley/src/inputs/context.rs @@ -7,17 +7,17 @@ use alloc::{vec, vec::Vec}; use self::tree::TreeStyleBuilder; -use super::bidi; -use super::builder::RangedBuilder; -use super::resolve::{tree, RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; -use super::style::{Brush, TextStyle}; -use super::FontContext; +use crate::bidi; +use crate::inputs::builder::RangedBuilder; +use crate::inputs::style::{Brush, TextStyle}; +use crate::resolve::{tree, RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; +use crate::FontContext; use swash::shape::ShapeContext; use swash::text::cluster::CharInfo; -use crate::builder::TreeBuilder; use crate::inline_box::InlineBox; +use crate::inputs::builder::TreeBuilder; /// Shared scratch space used when constructing text layouts. /// diff --git a/parley/src/font.rs b/parley/src/inputs/font.rs similarity index 100% rename from parley/src/font.rs rename to parley/src/inputs/font.rs diff --git a/parley/src/inputs/line_metrics.rs b/parley/src/inputs/line_metrics.rs new file mode 100644 index 00000000..c32dd3bc --- /dev/null +++ b/parley/src/inputs/line_metrics.rs @@ -0,0 +1,41 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// Metrics information for a line. +#[derive(Copy, Clone, Default, Debug)] +pub struct LineMetrics { + /// Typographic ascent. + pub ascent: f32, + /// Typographic descent. + pub descent: f32, + /// Typographic leading. + pub leading: f32, + /// The absolute line height (in layout units). + /// It matches the CSS definition of line height where it is derived as a multiple of the font size. + pub line_height: f32, + /// Offset to the baseline. + pub baseline: f32, + /// Offset for alignment. + pub offset: f32, + /// Full advance of the line. + pub advance: f32, + /// Advance of trailing whitespace. + pub trailing_whitespace: f32, + /// Minimum coordinate in the direction orthogonal to line + /// direction. + /// + /// For horizontal text, this would be the top of the line. + pub min_coord: f32, + /// Maximum coordinate in the direction orthogonal to line + /// direction. + /// + /// For horizontal text, this would be the bottom of the line. + pub max_coord: f32, +} + +impl LineMetrics { + /// Returns the size of the line + pub fn size(&self) -> f32 { + self.line_height + } +} diff --git a/parley/src/inputs/mod.rs b/parley/src/inputs/mod.rs new file mode 100644 index 00000000..1b015c74 --- /dev/null +++ b/parley/src/inputs/mod.rs @@ -0,0 +1,13 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// TODO - Remove pub(crate) + +pub(crate) mod builder; +pub(crate) mod context; +pub(crate) mod font; +pub(crate) mod line_metrics; + +pub mod style; + +pub use line_metrics::*; diff --git a/parley/src/style/brush.rs b/parley/src/inputs/style/brush.rs similarity index 100% rename from parley/src/style/brush.rs rename to parley/src/inputs/style/brush.rs diff --git a/parley/src/style/font.rs b/parley/src/inputs/style/font.rs similarity index 100% rename from parley/src/style/font.rs rename to parley/src/inputs/style/font.rs diff --git a/parley/src/style/mod.rs b/parley/src/inputs/style/mod.rs similarity index 100% rename from parley/src/style/mod.rs rename to parley/src/inputs/style/mod.rs diff --git a/parley/src/style/styleset.rs b/parley/src/inputs/style/styleset.rs similarity index 100% rename from parley/src/style/styleset.rs rename to parley/src/inputs/style/styleset.rs diff --git a/parley/src/layout/alignment.rs b/parley/src/layout/alignment.rs index cc45cc6f..0ccc7cfe 100644 --- a/parley/src/layout/alignment.rs +++ b/parley/src/layout/alignment.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use super::{Alignment, BreakReason, LayoutData}; -use crate::style::Brush; +use crate::inputs::style::Brush; pub(crate) fn align( layout: &mut LayoutData, diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index 42e80dcf..ec3b9da6 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -3,7 +3,7 @@ use crate::inline_box::InlineBox; use crate::layout::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; -use crate::style::Brush; +use crate::inputs::style::Brush; use crate::util::nearly_zero; use crate::Font; use core::ops::Range; diff --git a/parley/src/layout/editor.rs b/parley/src/layout/editor.rs index 403dc836..21a151ae 100644 --- a/parley/src/layout/editor.rs +++ b/parley/src/layout/editor.rs @@ -3,22 +3,18 @@ //! A simple plain text editor and related types. -use crate::{ - layout::{ - cursor::{Cursor, Selection}, - Affinity, Alignment, Layout, - }, - resolve::ResolvedStyle, - style::Brush, - FontContext, LayoutContext, Rect, StyleProperty, StyleSet, -}; +use crate::inputs::style::Brush; +use crate::layout::cursor::{Cursor, Selection}; +use crate::layout::{Affinity, Alignment, Layout}; +use crate::resolve::ResolvedStyle; +use crate::{FontContext, LayoutContext, Rect, StyleProperty, StyleSet}; + use alloc::{borrow::ToOwned, string::String, vec::Vec}; -use core::{ - cmp::PartialEq, - default::Default, - fmt::{Debug, Display}, - ops::Range, -}; + +use core::cmp::PartialEq; +use core::default::Default; +use core::fmt::{Debug, Display}; +use core::ops::Range; #[cfg(feature = "accesskit")] use crate::layout::LayoutAccessibility; diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index 6cb1dc26..2b67b503 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/layout/line/greedy.rs @@ -15,7 +15,7 @@ use crate::layout::{ Alignment, Boundary, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, LineMetrics, Run, }; -use crate::style::Brush; +use crate::inputs::style::Brush; use core::ops::Range; diff --git a/parley/src/layout/line/mod.rs b/parley/src/layout/line/mod.rs index 05ba6a8f..e666de30 100644 --- a/parley/src/layout/line/mod.rs +++ b/parley/src/layout/line/mod.rs @@ -1,7 +1,11 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::{BreakReason, Brush, Glyph, LayoutItemKind, Line, LineItemData, Range, Run, Style}; +use crate::inputs::LineMetrics; + +use crate::layout::{ + BreakReason, Brush, Glyph, LayoutItemKind, Line, LineItemData, Range, Run, Style, +}; pub(crate) mod greedy; @@ -91,45 +95,6 @@ impl<'a, B: Brush> Line<'a, B> { } } -/// Metrics information for a line. -#[derive(Copy, Clone, Default, Debug)] -pub struct LineMetrics { - /// Typographic ascent. - pub ascent: f32, - /// Typographic descent. - pub descent: f32, - /// Typographic leading. - pub leading: f32, - /// The absolute line height (in layout units). - /// It matches the CSS definition of line height where it is derived as a multiple of the font size. - pub line_height: f32, - /// Offset to the baseline. - pub baseline: f32, - /// Offset for alignment. - pub offset: f32, - /// Full advance of the line. - pub advance: f32, - /// Advance of trailing whitespace. - pub trailing_whitespace: f32, - /// Minimum coordinate in the direction orthogonal to line - /// direction. - /// - /// For horizontal text, this would be the top of the line. - pub min_coord: f32, - /// Maximum coordinate in the direction orthogonal to line - /// direction. - /// - /// For horizontal text, this would be the bottom of the line. - pub max_coord: f32, -} - -impl LineMetrics { - /// Returns the size of the line - pub fn size(&self) -> f32 { - self.line_height - } -} - /// The computed result of an item (glyph run or inline box) within a layout #[derive(Clone)] pub enum PositionedLayoutItem<'a, B: Brush> { diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index a4fb9a17..7cfd8670 100644 --- a/parley/src/layout/mod.rs +++ b/parley/src/layout/mod.rs @@ -15,7 +15,8 @@ pub mod editor; use self::alignment::align; -use super::style::Brush; +use crate::inputs::style::Brush; +use crate::inputs::LineMetrics; use crate::{Font, InlineBox}; #[cfg(feature = "accesskit")] use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; @@ -34,7 +35,7 @@ use swash::{GlyphId, NormalizedCoord, Synthesis}; pub use cluster::{Affinity, ClusterPath, ClusterSide}; pub use cursor::{Cursor, Selection}; pub use line::greedy::BreakLines; -pub use line::{GlyphRun, LineMetrics, PositionedInlineBox, PositionedLayoutItem}; +pub use line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}; pub use run::RunMetrics; /// Alignment of a layout. diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 0ba0d972..2fab2fb2 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -104,10 +104,12 @@ extern crate alloc; pub use fontique; pub use swash; +pub(crate) mod algos; + +pub mod inputs; +pub mod outputs; + mod bidi; -mod builder; -mod context; -mod font; mod inline_box; mod resolve; mod shape; @@ -115,7 +117,6 @@ mod swash_convert; mod util; pub mod layout; -pub mod style; #[cfg(test)] mod tests; @@ -123,9 +124,12 @@ mod tests; pub use peniko::kurbo::Rect; pub use peniko::Font; -pub use builder::{RangedBuilder, TreeBuilder}; -pub use context::LayoutContext; -pub use font::FontContext; +// TODO - Remove +pub use inputs::builder::{RangedBuilder, TreeBuilder}; +pub use inputs::context::LayoutContext; +pub use inputs::font::FontContext; +pub use inputs::style::*; + pub use inline_box::InlineBox; #[doc(inline)] pub use layout::Layout; @@ -133,4 +137,3 @@ pub use layout::Layout; pub use layout::editor::{PlainEditor, PlainEditorDriver}; pub use layout::*; -pub use style::*; diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index a05eaa8c..e0e0e7ca 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -10,13 +10,13 @@ pub(crate) use range::RangedStyleBuilder; use alloc::{vec, vec::Vec}; -use super::style::{ +use crate::inputs::font::FontContext; +use crate::inputs::style::TextStyle; +use crate::inputs::style::{ Brush, FontFamily, FontFeature, FontSettings, FontStack, FontStyle, FontVariation, FontWeight, FontWidth, StyleProperty, }; -use crate::font::FontContext; use crate::layout; -use crate::style::TextStyle; use crate::util::nearly_eq; use core::borrow::Borrow; use core::ops::Range; diff --git a/parley/src/resolve/tree.rs b/parley/src/resolve/tree.rs index 0ac5b039..a8b8d979 100644 --- a/parley/src/resolve/tree.rs +++ b/parley/src/resolve/tree.rs @@ -5,7 +5,7 @@ use alloc::borrow::Cow; use alloc::{string::String, vec::Vec}; -use crate::style::WhiteSpaceCollapse; +use crate::inputs::style::WhiteSpaceCollapse; use super::{Brush, RangedStyle, ResolvedProperty, ResolvedStyle}; diff --git a/parley/src/shape.rs b/parley/src/shape.rs index 4543ef9f..70bfb72e 100644 --- a/parley/src/shape.rs +++ b/parley/src/shape.rs @@ -1,9 +1,9 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::layout::Layout; -use super::resolve::{RangedStyle, ResolveContext, Resolved}; -use super::style::{Brush, FontFeature, FontVariation}; +use crate::inputs::style::{Brush, FontFeature, FontVariation}; +use crate::layout::Layout; +use crate::resolve::{RangedStyle, ResolveContext, Resolved}; use crate::util::nearly_eq; use crate::Font; use fontique::QueryFamily; From 0c97cd57858d71e77cdd6a13979da68703cff70b Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 17:56:15 +0100 Subject: [PATCH 02/14] Add mods --- parley/src/algos/mod.rs | 1 + parley/src/outputs/mod.rs | 0 2 files changed, 1 insertion(+) create mode 100644 parley/src/algos/mod.rs create mode 100644 parley/src/outputs/mod.rs diff --git a/parley/src/algos/mod.rs b/parley/src/algos/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/parley/src/algos/mod.rs @@ -0,0 +1 @@ + diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs new file mode 100644 index 00000000..e69de29b From 4c7665eb9a4ac5d639530e29906a95152303723b Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 18:04:46 +0100 Subject: [PATCH 03/14] Create algos module and move relevant files there --- parley/src/{ => algos}/bidi.rs | 0 parley/src/algos/mod.rs | 8 ++++++++ parley/src/{ => algos}/resolve/mod.rs | 0 parley/src/{ => algos}/resolve/range.rs | 0 parley/src/{ => algos}/resolve/tree.rs | 0 parley/src/{ => algos}/shape.rs | 8 ++++---- parley/src/{ => algos}/swash_convert.rs | 0 parley/src/inputs/builder.rs | 2 +- parley/src/inputs/context.rs | 7 +++---- parley/src/layout/data.rs | 2 +- parley/src/layout/editor.rs | 2 +- parley/src/layout/line/greedy.rs | 2 +- parley/src/lib.rs | 4 ---- parley/src/outputs/mod.rs | 1 + 14 files changed, 20 insertions(+), 16 deletions(-) rename parley/src/{ => algos}/bidi.rs (100%) rename parley/src/{ => algos}/resolve/mod.rs (100%) rename parley/src/{ => algos}/resolve/range.rs (100%) rename parley/src/{ => algos}/resolve/tree.rs (100%) rename parley/src/{ => algos}/shape.rs (97%) rename parley/src/{ => algos}/swash_convert.rs (100%) diff --git a/parley/src/bidi.rs b/parley/src/algos/bidi.rs similarity index 100% rename from parley/src/bidi.rs rename to parley/src/algos/bidi.rs diff --git a/parley/src/algos/mod.rs b/parley/src/algos/mod.rs index 8b137891..40811f74 100644 --- a/parley/src/algos/mod.rs +++ b/parley/src/algos/mod.rs @@ -1 +1,9 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// TODO - Remove pub(crate) + +pub(crate) mod bidi; +pub(crate) mod resolve; +pub(crate) mod shape; +pub(crate) mod swash_convert; diff --git a/parley/src/resolve/mod.rs b/parley/src/algos/resolve/mod.rs similarity index 100% rename from parley/src/resolve/mod.rs rename to parley/src/algos/resolve/mod.rs diff --git a/parley/src/resolve/range.rs b/parley/src/algos/resolve/range.rs similarity index 100% rename from parley/src/resolve/range.rs rename to parley/src/algos/resolve/range.rs diff --git a/parley/src/resolve/tree.rs b/parley/src/algos/resolve/tree.rs similarity index 100% rename from parley/src/resolve/tree.rs rename to parley/src/algos/resolve/tree.rs diff --git a/parley/src/shape.rs b/parley/src/algos/shape.rs similarity index 97% rename from parley/src/shape.rs rename to parley/src/algos/shape.rs index 70bfb72e..8f3a99ee 100644 --- a/parley/src/shape.rs +++ b/parley/src/algos/shape.rs @@ -1,9 +1,10 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::algos::resolve::{RangedStyle, ResolveContext, Resolved}; +use crate::algos::swash_convert::{locale_to_fontique, script_to_fontique, synthesis_to_swash}; use crate::inputs::style::{Brush, FontFeature, FontVariation}; use crate::layout::Layout; -use crate::resolve::{RangedStyle, ResolveContext, Resolved}; use crate::util::nearly_eq; use crate::Font; use fontique::QueryFamily; @@ -248,8 +249,8 @@ impl<'a, 'b, B: Brush> FontSelector<'a, 'b, B> { let variations = rcx.variations(style.font_variations).unwrap_or(&[]); let features = rcx.features(style.font_features).unwrap_or(&[]); query.set_families(fonts.iter().copied()); - let fb_script = crate::swash_convert::script_to_fontique(script); - let fb_language = locale.and_then(crate::swash_convert::locale_to_fontique); + let fb_script = script_to_fontique(script); + let fb_language = locale.and_then(locale_to_fontique); query.set_fallbacks(fontique::FallbackKey::new(fb_script, fb_language.as_ref())); query.set_attributes(attrs); Self { @@ -306,7 +307,6 @@ impl partition::Selector for FontSelector<'_, '_, B> { let mut selected_font = None; self.query.matches_with(|font| { if let Ok(font_ref) = skrifa::FontRef::from_index(font.blob.as_ref(), font.index) { - use crate::swash_convert::synthesis_to_swash; use skrifa::MetadataProvider; use swash::text::cluster::Status as MapStatus; let charmap = font_ref.charmap(); diff --git a/parley/src/swash_convert.rs b/parley/src/algos/swash_convert.rs similarity index 100% rename from parley/src/swash_convert.rs rename to parley/src/algos/swash_convert.rs diff --git a/parley/src/inputs/builder.rs b/parley/src/inputs/builder.rs index f47adbc6..d74f9098 100644 --- a/parley/src/inputs/builder.rs +++ b/parley/src/inputs/builder.rs @@ -3,9 +3,9 @@ //! Context for layout. +use crate::algos::shape::shape_text; use crate::inputs::context::LayoutContext; use crate::inputs::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; -use crate::shape::shape_text; use crate::FontContext; use crate::layout::Layout; diff --git a/parley/src/inputs/context.rs b/parley/src/inputs/context.rs index 43b6de39..dddc53eb 100644 --- a/parley/src/inputs/context.rs +++ b/parley/src/inputs/context.rs @@ -5,12 +5,11 @@ use alloc::{vec, vec::Vec}; -use self::tree::TreeStyleBuilder; - -use crate::bidi; +use crate::algos::bidi; +use crate::algos::resolve::tree::TreeStyleBuilder; +use crate::algos::resolve::{RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; use crate::inputs::builder::RangedBuilder; use crate::inputs::style::{Brush, TextStyle}; -use crate::resolve::{tree, RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; use crate::FontContext; use swash::shape::ShapeContext; diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index ec3b9da6..60800bd1 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::inline_box::InlineBox; -use crate::layout::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; use crate::inputs::style::Brush; +use crate::layout::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; use crate::util::nearly_zero; use crate::Font; use core::ops::Range; diff --git a/parley/src/layout/editor.rs b/parley/src/layout/editor.rs index 21a151ae..5397d57f 100644 --- a/parley/src/layout/editor.rs +++ b/parley/src/layout/editor.rs @@ -3,10 +3,10 @@ //! A simple plain text editor and related types. +use crate::algos::resolve::ResolvedStyle; use crate::inputs::style::Brush; use crate::layout::cursor::{Cursor, Selection}; use crate::layout::{Affinity, Alignment, Layout}; -use crate::resolve::ResolvedStyle; use crate::{FontContext, LayoutContext, Rect, StyleProperty, StyleSet}; use alloc::{borrow::ToOwned, string::String, vec::Vec}; diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index 2b67b503..dd418626 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::inputs::style::Brush; use crate::layout::alignment::unjustify; use crate::layout::{ Alignment, Boundary, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, LineMetrics, Run, }; -use crate::inputs::style::Brush; use core::ops::Range; diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 2fab2fb2..9d1adcd6 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -109,11 +109,7 @@ pub(crate) mod algos; pub mod inputs; pub mod outputs; -mod bidi; mod inline_box; -mod resolve; -mod shape; -mod swash_convert; mod util; pub mod layout; diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs index e69de29b..8b137891 100644 --- a/parley/src/outputs/mod.rs +++ b/parley/src/outputs/mod.rs @@ -0,0 +1 @@ + From 7f5a01bb0a3a371230dc67b5bfa965737c30596d Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 18:05:12 +0100 Subject: [PATCH 04/14] Fix doc tests --- parley/src/inputs/style/font.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parley/src/inputs/style/font.rs b/parley/src/inputs/style/font.rs index 90ac1180..3bac15b1 100644 --- a/parley/src/inputs/style/font.rs +++ b/parley/src/inputs/style/font.rs @@ -44,8 +44,8 @@ impl<'a> FontFamily<'a> { /// ``` /// # extern crate alloc; /// use alloc::borrow::Cow; - /// use parley::style::FontFamily::{self, *}; - /// use parley::style::GenericFamily::*; + /// use parley::inputs::style::FontFamily::{self, *}; + /// use parley::inputs::style::GenericFamily::*; /// /// assert_eq!(FontFamily::parse("Palatino Linotype"), Some(Named(Cow::Borrowed("Palatino Linotype")))); /// assert_eq!(FontFamily::parse("monospace"), Some(Generic(Monospace))); @@ -64,8 +64,8 @@ impl<'a> FontFamily<'a> { /// ``` /// # extern crate alloc; /// use alloc::borrow::Cow; - /// use parley::style::FontFamily::{self, *}; - /// use parley::style::GenericFamily::*; + /// use parley::inputs::style::FontFamily::{self, *}; + /// use parley::inputs::style::GenericFamily::*; /// /// let source = "Arial, 'Times New Roman', serif"; /// From d320e6cd31846592ec11a0bbc39c38982bceb6ba Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 18:14:49 +0100 Subject: [PATCH 05/14] Split up line mod --- .../line/greedy.rs => inputs/break_lines.rs} | 6 ++++-- parley/src/inputs/mod.rs | 1 + parley/src/layout/{line/mod.rs => line.rs} | 2 -- parley/src/layout/mod.rs | 21 ++++++++++--------- 4 files changed, 16 insertions(+), 14 deletions(-) rename parley/src/{layout/line/greedy.rs => inputs/break_lines.rs} (99%) rename parley/src/layout/{line/mod.rs => line.rs} (99%) diff --git a/parley/src/layout/line/greedy.rs b/parley/src/inputs/break_lines.rs similarity index 99% rename from parley/src/layout/line/greedy.rs rename to parley/src/inputs/break_lines.rs index dd418626..4510d79f 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/inputs/break_lines.rs @@ -4,6 +4,7 @@ //! Greedy line breaking. use alloc::vec::Vec; +use swash::text::cluster::Boundary; use swash::text::cluster::Whitespace; #[cfg(feature = "libm")] @@ -11,10 +12,11 @@ use swash::text::cluster::Whitespace; use core_maths::CoreFloat; use crate::inputs::style::Brush; +use crate::inputs::LineMetrics; use crate::layout::alignment::unjustify; use crate::layout::{ - Alignment, Boundary, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, - LineItemData, LineMetrics, Run, + Alignment, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, + Run, }; use core::ops::Range; diff --git a/parley/src/inputs/mod.rs b/parley/src/inputs/mod.rs index 1b015c74..baef982a 100644 --- a/parley/src/inputs/mod.rs +++ b/parley/src/inputs/mod.rs @@ -3,6 +3,7 @@ // TODO - Remove pub(crate) +pub(crate) mod break_lines; pub(crate) mod builder; pub(crate) mod context; pub(crate) mod font; diff --git a/parley/src/layout/line/mod.rs b/parley/src/layout/line.rs similarity index 99% rename from parley/src/layout/line/mod.rs rename to parley/src/layout/line.rs index e666de30..7959b365 100644 --- a/parley/src/layout/line/mod.rs +++ b/parley/src/layout/line.rs @@ -7,8 +7,6 @@ use crate::layout::{ BreakReason, Brush, Glyph, LayoutItemKind, Line, LineItemData, Range, Run, Style, }; -pub(crate) mod greedy; - impl<'a, B: Brush> Line<'a, B> { /// Returns the metrics for the line. pub fn metrics(&self) -> &LineMetrics { diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index 7cfd8670..e56feefe 100644 --- a/parley/src/layout/mod.rs +++ b/parley/src/layout/mod.rs @@ -3,10 +3,10 @@ //! Layout types. -mod alignment; -mod cluster; -mod line; -mod run; +pub(crate) mod alignment; +pub(crate) mod cluster; +pub(crate) mod line; +pub(crate) mod run; pub(crate) mod data; @@ -15,6 +15,7 @@ pub mod editor; use self::alignment::align; +use crate::inputs::break_lines::BreakLines; use crate::inputs::style::Brush; use crate::inputs::LineMetrics; use crate::{Font, InlineBox}; @@ -23,21 +24,21 @@ use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; #[cfg(feature = "accesskit")] use alloc::vec::Vec; use core::{cmp::Ordering, ops::Range}; -use data::{ - BreakReason, ClusterData, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, - RunData, -}; +use data::{ClusterData, RunData}; #[cfg(feature = "accesskit")] use hashbrown::{HashMap, HashSet}; -use swash::text::cluster::{Boundary, ClusterInfo}; +use swash::text::cluster::ClusterInfo; use swash::{GlyphId, NormalizedCoord, Synthesis}; pub use cluster::{Affinity, ClusterPath, ClusterSide}; pub use cursor::{Cursor, Selection}; -pub use line::greedy::BreakLines; pub use line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}; pub use run::RunMetrics; +pub(crate) use data::{ + BreakReason, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, +}; + /// Alignment of a layout. #[derive(Copy, Clone, Default, PartialEq, Eq, Debug)] #[repr(u8)] From 49df59977b0e9842165f8bf69b4b11153dd69d01 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 18:19:48 +0100 Subject: [PATCH 06/14] Create outputs module and move relevant files there --- examples/swash_render/src/main.rs | 2 +- examples/vello_editor/src/text.rs | 4 +- parley/src/algos/resolve/mod.rs | 10 +- parley/src/algos/shape.rs | 2 +- parley/src/inputs/break_lines.rs | 4 +- parley/src/inputs/builder.rs | 2 +- parley/src/layout/mod.rs | 423 -------------------- parley/src/lib.rs | 8 +- parley/src/{layout => outputs}/alignment.rs | 0 parley/src/{layout => outputs}/cluster.rs | 0 parley/src/{layout => outputs}/cursor.rs | 0 parley/src/{layout => outputs}/data.rs | 2 +- parley/src/{layout => outputs}/editor.rs | 6 +- parley/src/{layout => outputs}/line.rs | 2 +- parley/src/outputs/mod.rs | 422 +++++++++++++++++++ parley/src/{layout => outputs}/run.rs | 0 16 files changed, 442 insertions(+), 445 deletions(-) delete mode 100644 parley/src/layout/mod.rs rename parley/src/{layout => outputs}/alignment.rs (100%) rename parley/src/{layout => outputs}/cluster.rs (100%) rename parley/src/{layout => outputs}/cursor.rs (100%) rename parley/src/{layout => outputs}/data.rs (99%) rename parley/src/{layout => outputs}/editor.rs (99%) rename parley/src/{layout => outputs}/line.rs (99%) rename parley/src/{layout => outputs}/run.rs (100%) diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index 9f2243e7..b71fe5ef 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -13,7 +13,7 @@ use image::codecs::png::PngEncoder; use image::{self, Pixel, Rgba, RgbaImage}; use parley::inputs::style::{FontStack, FontWeight, StyleProperty, TextStyle}; -use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; +use parley::outputs::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; use parley::{FontContext, InlineBox, LayoutContext}; use std::fs::File; use swash::scale::image::Content; diff --git a/examples/vello_editor/src/text.rs b/examples/vello_editor/src/text.rs index 1bfe91c7..686b3149 100644 --- a/examples/vello_editor/src/text.rs +++ b/examples/vello_editor/src/text.rs @@ -3,7 +3,7 @@ use accesskit::{Node, TreeUpdate}; use core::default::Default; -use parley::{editor::SplitString, layout::PositionedLayoutItem, GenericFamily, StyleProperty}; +use parley::{editor::SplitString, outputs::PositionedLayoutItem, GenericFamily, StyleProperty}; use std::time::{Duration, Instant}; use vello::{ kurbo::{Affine, Line, Stroke}, @@ -16,7 +16,7 @@ use winit::{ keyboard::{Key, NamedKey}, }; -pub use parley::layout::editor::Generation; +pub use parley::outputs::editor::Generation; use parley::{FontContext, LayoutContext, PlainEditor, PlainEditorDriver}; use crate::access_ids::next_node_id; diff --git a/parley/src/algos/resolve/mod.rs b/parley/src/algos/resolve/mod.rs index e0e0e7ca..6944de74 100644 --- a/parley/src/algos/resolve/mod.rs +++ b/parley/src/algos/resolve/mod.rs @@ -16,7 +16,7 @@ use crate::inputs::style::{ Brush, FontFamily, FontFeature, FontSettings, FontStack, FontStyle, FontVariation, FontWeight, FontWidth, StyleProperty, }; -use crate::layout; +use crate::outputs; use crate::util::nearly_eq; use core::borrow::Borrow; use core::ops::Range; @@ -476,8 +476,8 @@ impl ResolvedStyle { } } - pub(crate) fn as_layout_style(&self) -> layout::Style { - layout::Style { + pub(crate) fn as_layout_style(&self) -> outputs::Style { + outputs::Style { brush: self.brush.clone(), underline: self.underline.as_layout_decoration(&self.brush), strikethrough: self.strikethrough.as_layout_decoration(&self.brush), @@ -501,9 +501,9 @@ pub(crate) struct ResolvedDecoration { impl ResolvedDecoration { /// Convert into a layout Decoration (filtering out disabled decorations) - pub(crate) fn as_layout_decoration(&self, default_brush: &B) -> Option> { + pub(crate) fn as_layout_decoration(&self, default_brush: &B) -> Option> { if self.enabled { - Some(layout::Decoration { + Some(outputs::Decoration { brush: self.brush.clone().unwrap_or_else(|| default_brush.clone()), offset: self.offset, size: self.size, diff --git a/parley/src/algos/shape.rs b/parley/src/algos/shape.rs index 8f3a99ee..7a95e496 100644 --- a/parley/src/algos/shape.rs +++ b/parley/src/algos/shape.rs @@ -4,7 +4,7 @@ use crate::algos::resolve::{RangedStyle, ResolveContext, Resolved}; use crate::algos::swash_convert::{locale_to_fontique, script_to_fontique, synthesis_to_swash}; use crate::inputs::style::{Brush, FontFeature, FontVariation}; -use crate::layout::Layout; +use crate::outputs::Layout; use crate::util::nearly_eq; use crate::Font; use fontique::QueryFamily; diff --git a/parley/src/inputs/break_lines.rs b/parley/src/inputs/break_lines.rs index 4510d79f..571ed33a 100644 --- a/parley/src/inputs/break_lines.rs +++ b/parley/src/inputs/break_lines.rs @@ -13,8 +13,8 @@ use core_maths::CoreFloat; use crate::inputs::style::Brush; use crate::inputs::LineMetrics; -use crate::layout::alignment::unjustify; -use crate::layout::{ +use crate::outputs::alignment::unjustify; +use crate::outputs::{ Alignment, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, Run, }; diff --git a/parley/src/inputs/builder.rs b/parley/src/inputs/builder.rs index d74f9098..ed4f4eab 100644 --- a/parley/src/inputs/builder.rs +++ b/parley/src/inputs/builder.rs @@ -8,7 +8,7 @@ use crate::inputs::context::LayoutContext; use crate::inputs::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; use crate::FontContext; -use crate::layout::Layout; +use crate::outputs::Layout; use alloc::string::String; use core::ops::RangeBounds; diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs deleted file mode 100644 index e56feefe..00000000 --- a/parley/src/layout/mod.rs +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright 2021 the Parley Authors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Layout types. - -pub(crate) mod alignment; -pub(crate) mod cluster; -pub(crate) mod line; -pub(crate) mod run; - -pub(crate) mod data; - -pub mod cursor; -pub mod editor; - -use self::alignment::align; - -use crate::inputs::break_lines::BreakLines; -use crate::inputs::style::Brush; -use crate::inputs::LineMetrics; -use crate::{Font, InlineBox}; -#[cfg(feature = "accesskit")] -use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; -#[cfg(feature = "accesskit")] -use alloc::vec::Vec; -use core::{cmp::Ordering, ops::Range}; -use data::{ClusterData, RunData}; -#[cfg(feature = "accesskit")] -use hashbrown::{HashMap, HashSet}; -use swash::text::cluster::ClusterInfo; -use swash::{GlyphId, NormalizedCoord, Synthesis}; - -pub use cluster::{Affinity, ClusterPath, ClusterSide}; -pub use cursor::{Cursor, Selection}; -pub use line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}; -pub use run::RunMetrics; - -pub(crate) use data::{ - BreakReason, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, -}; - -/// Alignment of a layout. -#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)] -#[repr(u8)] -pub enum Alignment { - #[default] - Start, - Middle, - End, - Justified, -} - -/// Text layout. -#[derive(Clone)] -pub struct Layout { - pub(crate) data: LayoutData, -} - -impl Layout { - /// Creates an empty layout. - pub fn new() -> Self { - Self::default() - } - - /// Returns the scale factor provided when creating the layout. - pub fn scale(&self) -> f32 { - self.data.scale - } - - /// Returns the style collection for the layout. - pub fn styles(&self) -> &[Style] { - &self.data.styles - } - - /// Returns the width of the layout. - pub fn width(&self) -> f32 { - self.data.width - } - - /// Returns the width of the layout, including the width of any trailing - /// whitespace. - pub fn full_width(&self) -> f32 { - self.data.full_width - } - - /// Returns the height of the layout. - pub fn height(&self) -> f32 { - self.data.height - } - - /// Returns the number of lines in the layout. - pub fn len(&self) -> usize { - self.data.lines.len() - } - - /// Returns `true` if the layout is empty. - pub fn is_empty(&self) -> bool { - self.data.lines.is_empty() - } - - /// Returns the line at the specified index. - pub fn get(&self, index: usize) -> Option> { - Some(Line { - index: index as u32, - layout: self, - data: self.data.lines.get(index)?, - }) - } - - /// Returns true if the dominant direction of the layout is right-to-left. - pub fn is_rtl(&self) -> bool { - self.data.base_level & 1 != 0 - } - - pub fn inline_boxes(&self) -> &[InlineBox] { - &self.data.inline_boxes - } - - pub fn inline_boxes_mut(&mut self) -> &mut [InlineBox] { - &mut self.data.inline_boxes - } - - /// Returns an iterator over the lines in the layout. - pub fn lines(&self) -> impl Iterator> + '_ + Clone { - self.data - .lines - .iter() - .enumerate() - .map(move |(index, data)| Line { - index: index as u32, - layout: self, - data, - }) - } - - /// Returns line breaker to compute lines for the layout. - pub fn break_lines(&mut self) -> BreakLines<'_, B> { - BreakLines::new(self) - } - - /// Breaks all lines with the specified maximum advance. - pub fn break_all_lines(&mut self, max_advance: Option) { - self.break_lines() - .break_remaining(max_advance.unwrap_or(f32::MAX)); - } - - // Apply to alignment to layout relative to the specified container width. If container_width is not - // specified then the max line length is used. - pub fn align(&mut self, container_width: Option, alignment: Alignment) { - align(&mut self.data, container_width, alignment); - } - - /// Returns the index and `Line` object for the line containing the - /// given byte `index` in the source text. - pub(crate) fn line_for_byte_index(&self, index: usize) -> Option<(usize, Line<'_, B>)> { - let line_index = self - .data - .lines - .binary_search_by(|line| { - if index < line.text_range.start { - Ordering::Greater - } else if index >= line.text_range.end { - Ordering::Less - } else { - Ordering::Equal - } - }) - .ok()?; - Some((line_index, self.get(line_index)?)) - } - - /// Returns the index and `Line` object for the line containing the - /// given `offset`. - /// - /// The offset is specified in the direction orthogonal to line direction. - /// For horizontal text, this is a vertical or y offset. If the offset is - /// on a line boundary, it is considered to be contained by the later line. - pub(crate) fn line_for_offset(&self, offset: f32) -> Option<(usize, Line<'_, B>)> { - if offset < 0.0 { - return Some((0, self.get(0)?)); - } - let maybe_line_index = self.data.lines.binary_search_by(|line| { - if offset < line.metrics.min_coord { - Ordering::Greater - } else if offset >= line.metrics.max_coord { - Ordering::Less - } else { - Ordering::Equal - } - }); - let line_index = match maybe_line_index { - Ok(index) => index, - Err(index) => index.saturating_sub(1), - }; - Some((line_index, self.get(line_index)?)) - } -} - -impl Default for Layout { - fn default() -> Self { - Self { - data: Default::default(), - } - } -} - -/// Sequence of clusters with a single font and style. -#[derive(Copy, Clone)] -pub struct Run<'a, B: Brush> { - layout: &'a Layout, - line_index: u32, - index: u32, - data: &'a RunData, - line_data: Option<&'a LineItemData>, -} - -/// Atomic unit of text. -#[derive(Copy, Clone)] -pub struct Cluster<'a, B: Brush> { - path: ClusterPath, - run: Run<'a, B>, - data: &'a ClusterData, -} - -/// Glyph with an offset and advance. -#[derive(Copy, Clone, Default, Debug)] -pub struct Glyph { - pub id: GlyphId, - pub style_index: u16, - pub x: f32, - pub y: f32, - pub advance: f32, -} - -impl Glyph { - /// Returns the index into the layout style collection. - pub fn style_index(&self) -> usize { - self.style_index as usize - } -} - -/// Line in a text layout. -#[derive(Copy, Clone)] -pub struct Line<'a, B: Brush> { - layout: &'a Layout, - index: u32, - data: &'a LineData, -} - -#[allow(clippy::partial_pub_fields)] -/// Style properties. -#[derive(Clone, Debug)] -pub struct Style { - /// Brush for drawing glyphs. - pub brush: B, - /// Underline decoration. - pub underline: Option>, - /// Strikethrough decoration. - pub strikethrough: Option>, - /// Absolute line height in layout units (style line height * font size) - pub(crate) line_height: f32, -} - -/// Underline or strikethrough decoration. -#[derive(Clone, Debug)] -pub struct Decoration { - /// Brush used to draw the decoration. - pub brush: B, - /// Offset of the decoration from the baseline. If `None`, use the metrics - /// of the containing run. - pub offset: Option, - /// Thickness of the decoration. If `None`, use the metrics of the - /// containing run. - pub size: Option, -} - -#[cfg(feature = "accesskit")] -#[derive(Clone, Default)] -pub struct LayoutAccessibility { - // The following two fields maintain a two-way mapping between runs - // and AccessKit node IDs, where each run is identified by its line index - // and run index within that line, or a run path for short. These maps - // are maintained by `LayoutAccess::build_nodes`, which ensures that removed - // runs are removed from the maps on the next accessibility pass. - pub(crate) access_ids_by_run_path: HashMap<(usize, usize), NodeId>, - pub(crate) run_paths_by_access_id: HashMap, -} - -#[cfg(feature = "accesskit")] -impl LayoutAccessibility { - #[allow(clippy::too_many_arguments)] - pub fn build_nodes( - &mut self, - text: &str, - layout: &Layout, - update: &mut TreeUpdate, - parent_node: &mut Node, - mut next_node_id: impl FnMut() -> NodeId, - x_offset: f64, - y_offset: f64, - ) { - // Build a set of node IDs for the runs encountered in this pass. - let mut ids = HashSet::::new(); - // Reuse scratch space for storing a sorted list of runs. - let mut runs = Vec::new(); - - for (line_index, line) in layout.lines().enumerate() { - let metrics = line.metrics(); - // Defer adding each run node until we reach either the next run - // or the end of the line. That way, we can set relations between - // runs in a line and do anything special that might be required - // for the last run in a line. - let mut last_node: Option<(NodeId, Node)> = None; - - // Iterate over the runs from left to right, computing their offsets, - // then sort them into text order. - runs.clear(); - runs.reserve(line.len()); - { - let mut run_offset = metrics.offset; - for run in line.runs() { - let advance = run.advance(); - runs.push((run, run_offset)); - run_offset += advance; - } - } - runs.sort_by_key(|(r, _)| r.text_range().start); - - for (run, run_offset) in runs.drain(..) { - let run_path = (line_index, run.index()); - // If we encountered this same run path in the previous - // accessibility pass, reuse the same AccessKit ID. Otherwise, - // allocate a new one. This enables stable node IDs when merely - // updating the content of existing runs. - let id = self - .access_ids_by_run_path - .get(&run_path) - .copied() - .unwrap_or_else(|| { - let id = next_node_id(); - self.access_ids_by_run_path.insert(run_path, id); - self.run_paths_by_access_id.insert(id, run_path); - id - }); - ids.insert(id); - let mut node = Node::new(Role::TextRun); - - if let Some((last_id, mut last_node)) = last_node.take() { - last_node.set_next_on_line(id); - node.set_previous_on_line(last_id); - update.nodes.push((last_id, last_node)); - parent_node.push_child(last_id); - } - - node.set_bounds(accesskit::Rect { - x0: x_offset + run_offset as f64, - y0: y_offset + metrics.min_coord as f64, - x1: x_offset + (run_offset + run.advance()) as f64, - y1: y_offset + metrics.max_coord as f64, - }); - node.set_text_direction(if run.is_rtl() { - TextDirection::RightToLeft - } else { - TextDirection::LeftToRight - }); - - let run_text = &text[run.text_range()]; - node.set_value(run_text); - - let mut character_lengths = Vec::new(); - let mut cluster_offset = 0.0; - let mut character_positions = Vec::new(); - let mut character_widths = Vec::new(); - let mut word_lengths = Vec::new(); - let mut last_word_start = 0; - - for cluster in run.clusters() { - let cluster_text = &text[cluster.text_range()]; - if cluster.is_word_boundary() - && !cluster.is_space_or_nbsp() - && !character_lengths.is_empty() - { - word_lengths.push((character_lengths.len() - last_word_start) as _); - last_word_start = character_lengths.len(); - } - character_lengths.push(cluster_text.len() as _); - character_positions.push(cluster_offset); - character_widths.push(cluster.advance()); - cluster_offset += cluster.advance(); - } - - word_lengths.push((character_lengths.len() - last_word_start) as _); - node.set_character_lengths(character_lengths); - node.set_character_positions(character_positions); - node.set_character_widths(character_widths); - node.set_word_lengths(word_lengths); - - last_node = Some((id, node)); - } - - if let Some((id, node)) = last_node { - update.nodes.push((id, node)); - parent_node.push_child(id); - } - } - - // Remove mappings for runs that no longer exist. - let mut ids_to_remove = Vec::::new(); - let mut run_paths_to_remove = Vec::<(usize, usize)>::new(); - for (access_id, run_path) in self.run_paths_by_access_id.iter() { - if !ids.contains(access_id) { - ids_to_remove.push(*access_id); - run_paths_to_remove.push(*run_path); - } - } - for id in ids_to_remove { - self.run_paths_by_access_id.remove(&id); - } - for run_path in run_paths_to_remove { - self.access_ids_by_run_path.remove(&run_path); - } - } -} diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 9d1adcd6..3ef022b4 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -112,8 +112,6 @@ pub mod outputs; mod inline_box; mod util; -pub mod layout; - #[cfg(test)] mod tests; @@ -128,8 +126,8 @@ pub use inputs::style::*; pub use inline_box::InlineBox; #[doc(inline)] -pub use layout::Layout; +pub use outputs::Layout; -pub use layout::editor::{PlainEditor, PlainEditorDriver}; +pub use outputs::editor::{PlainEditor, PlainEditorDriver}; -pub use layout::*; +pub use outputs::*; diff --git a/parley/src/layout/alignment.rs b/parley/src/outputs/alignment.rs similarity index 100% rename from parley/src/layout/alignment.rs rename to parley/src/outputs/alignment.rs diff --git a/parley/src/layout/cluster.rs b/parley/src/outputs/cluster.rs similarity index 100% rename from parley/src/layout/cluster.rs rename to parley/src/outputs/cluster.rs diff --git a/parley/src/layout/cursor.rs b/parley/src/outputs/cursor.rs similarity index 100% rename from parley/src/layout/cursor.rs rename to parley/src/outputs/cursor.rs diff --git a/parley/src/layout/data.rs b/parley/src/outputs/data.rs similarity index 99% rename from parley/src/layout/data.rs rename to parley/src/outputs/data.rs index 60800bd1..52712135 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/outputs/data.rs @@ -3,7 +3,7 @@ use crate::inline_box::InlineBox; use crate::inputs::style::Brush; -use crate::layout::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; +use crate::outputs::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; use crate::util::nearly_zero; use crate::Font; use core::ops::Range; diff --git a/parley/src/layout/editor.rs b/parley/src/outputs/editor.rs similarity index 99% rename from parley/src/layout/editor.rs rename to parley/src/outputs/editor.rs index 5397d57f..fe30b2cd 100644 --- a/parley/src/layout/editor.rs +++ b/parley/src/outputs/editor.rs @@ -5,8 +5,8 @@ use crate::algos::resolve::ResolvedStyle; use crate::inputs::style::Brush; -use crate::layout::cursor::{Cursor, Selection}; -use crate::layout::{Affinity, Alignment, Layout}; +use crate::outputs::cursor::{Cursor, Selection}; +use crate::outputs::{Affinity, Alignment, Layout}; use crate::{FontContext, LayoutContext, Rect, StyleProperty, StyleSet}; use alloc::{borrow::ToOwned, string::String, vec::Vec}; @@ -17,7 +17,7 @@ use core::fmt::{Debug, Display}; use core::ops::Range; #[cfg(feature = "accesskit")] -use crate::layout::LayoutAccessibility; +use crate::outputs::LayoutAccessibility; #[cfg(feature = "accesskit")] use accesskit::{Node, NodeId, TreeUpdate}; diff --git a/parley/src/layout/line.rs b/parley/src/outputs/line.rs similarity index 99% rename from parley/src/layout/line.rs rename to parley/src/outputs/line.rs index 7959b365..6385fe73 100644 --- a/parley/src/layout/line.rs +++ b/parley/src/outputs/line.rs @@ -3,7 +3,7 @@ use crate::inputs::LineMetrics; -use crate::layout::{ +use crate::outputs::{ BreakReason, Brush, Glyph, LayoutItemKind, Line, LineItemData, Range, Run, Style, }; diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs index 8b137891..e56feefe 100644 --- a/parley/src/outputs/mod.rs +++ b/parley/src/outputs/mod.rs @@ -1 +1,423 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Layout types. + +pub(crate) mod alignment; +pub(crate) mod cluster; +pub(crate) mod line; +pub(crate) mod run; + +pub(crate) mod data; + +pub mod cursor; +pub mod editor; + +use self::alignment::align; + +use crate::inputs::break_lines::BreakLines; +use crate::inputs::style::Brush; +use crate::inputs::LineMetrics; +use crate::{Font, InlineBox}; +#[cfg(feature = "accesskit")] +use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; +#[cfg(feature = "accesskit")] +use alloc::vec::Vec; +use core::{cmp::Ordering, ops::Range}; +use data::{ClusterData, RunData}; +#[cfg(feature = "accesskit")] +use hashbrown::{HashMap, HashSet}; +use swash::text::cluster::ClusterInfo; +use swash::{GlyphId, NormalizedCoord, Synthesis}; + +pub use cluster::{Affinity, ClusterPath, ClusterSide}; +pub use cursor::{Cursor, Selection}; +pub use line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}; +pub use run::RunMetrics; + +pub(crate) use data::{ + BreakReason, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, +}; + +/// Alignment of a layout. +#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)] +#[repr(u8)] +pub enum Alignment { + #[default] + Start, + Middle, + End, + Justified, +} + +/// Text layout. +#[derive(Clone)] +pub struct Layout { + pub(crate) data: LayoutData, +} + +impl Layout { + /// Creates an empty layout. + pub fn new() -> Self { + Self::default() + } + + /// Returns the scale factor provided when creating the layout. + pub fn scale(&self) -> f32 { + self.data.scale + } + + /// Returns the style collection for the layout. + pub fn styles(&self) -> &[Style] { + &self.data.styles + } + + /// Returns the width of the layout. + pub fn width(&self) -> f32 { + self.data.width + } + + /// Returns the width of the layout, including the width of any trailing + /// whitespace. + pub fn full_width(&self) -> f32 { + self.data.full_width + } + + /// Returns the height of the layout. + pub fn height(&self) -> f32 { + self.data.height + } + + /// Returns the number of lines in the layout. + pub fn len(&self) -> usize { + self.data.lines.len() + } + + /// Returns `true` if the layout is empty. + pub fn is_empty(&self) -> bool { + self.data.lines.is_empty() + } + + /// Returns the line at the specified index. + pub fn get(&self, index: usize) -> Option> { + Some(Line { + index: index as u32, + layout: self, + data: self.data.lines.get(index)?, + }) + } + + /// Returns true if the dominant direction of the layout is right-to-left. + pub fn is_rtl(&self) -> bool { + self.data.base_level & 1 != 0 + } + + pub fn inline_boxes(&self) -> &[InlineBox] { + &self.data.inline_boxes + } + + pub fn inline_boxes_mut(&mut self) -> &mut [InlineBox] { + &mut self.data.inline_boxes + } + + /// Returns an iterator over the lines in the layout. + pub fn lines(&self) -> impl Iterator> + '_ + Clone { + self.data + .lines + .iter() + .enumerate() + .map(move |(index, data)| Line { + index: index as u32, + layout: self, + data, + }) + } + + /// Returns line breaker to compute lines for the layout. + pub fn break_lines(&mut self) -> BreakLines<'_, B> { + BreakLines::new(self) + } + + /// Breaks all lines with the specified maximum advance. + pub fn break_all_lines(&mut self, max_advance: Option) { + self.break_lines() + .break_remaining(max_advance.unwrap_or(f32::MAX)); + } + + // Apply to alignment to layout relative to the specified container width. If container_width is not + // specified then the max line length is used. + pub fn align(&mut self, container_width: Option, alignment: Alignment) { + align(&mut self.data, container_width, alignment); + } + + /// Returns the index and `Line` object for the line containing the + /// given byte `index` in the source text. + pub(crate) fn line_for_byte_index(&self, index: usize) -> Option<(usize, Line<'_, B>)> { + let line_index = self + .data + .lines + .binary_search_by(|line| { + if index < line.text_range.start { + Ordering::Greater + } else if index >= line.text_range.end { + Ordering::Less + } else { + Ordering::Equal + } + }) + .ok()?; + Some((line_index, self.get(line_index)?)) + } + + /// Returns the index and `Line` object for the line containing the + /// given `offset`. + /// + /// The offset is specified in the direction orthogonal to line direction. + /// For horizontal text, this is a vertical or y offset. If the offset is + /// on a line boundary, it is considered to be contained by the later line. + pub(crate) fn line_for_offset(&self, offset: f32) -> Option<(usize, Line<'_, B>)> { + if offset < 0.0 { + return Some((0, self.get(0)?)); + } + let maybe_line_index = self.data.lines.binary_search_by(|line| { + if offset < line.metrics.min_coord { + Ordering::Greater + } else if offset >= line.metrics.max_coord { + Ordering::Less + } else { + Ordering::Equal + } + }); + let line_index = match maybe_line_index { + Ok(index) => index, + Err(index) => index.saturating_sub(1), + }; + Some((line_index, self.get(line_index)?)) + } +} + +impl Default for Layout { + fn default() -> Self { + Self { + data: Default::default(), + } + } +} + +/// Sequence of clusters with a single font and style. +#[derive(Copy, Clone)] +pub struct Run<'a, B: Brush> { + layout: &'a Layout, + line_index: u32, + index: u32, + data: &'a RunData, + line_data: Option<&'a LineItemData>, +} + +/// Atomic unit of text. +#[derive(Copy, Clone)] +pub struct Cluster<'a, B: Brush> { + path: ClusterPath, + run: Run<'a, B>, + data: &'a ClusterData, +} + +/// Glyph with an offset and advance. +#[derive(Copy, Clone, Default, Debug)] +pub struct Glyph { + pub id: GlyphId, + pub style_index: u16, + pub x: f32, + pub y: f32, + pub advance: f32, +} + +impl Glyph { + /// Returns the index into the layout style collection. + pub fn style_index(&self) -> usize { + self.style_index as usize + } +} + +/// Line in a text layout. +#[derive(Copy, Clone)] +pub struct Line<'a, B: Brush> { + layout: &'a Layout, + index: u32, + data: &'a LineData, +} + +#[allow(clippy::partial_pub_fields)] +/// Style properties. +#[derive(Clone, Debug)] +pub struct Style { + /// Brush for drawing glyphs. + pub brush: B, + /// Underline decoration. + pub underline: Option>, + /// Strikethrough decoration. + pub strikethrough: Option>, + /// Absolute line height in layout units (style line height * font size) + pub(crate) line_height: f32, +} + +/// Underline or strikethrough decoration. +#[derive(Clone, Debug)] +pub struct Decoration { + /// Brush used to draw the decoration. + pub brush: B, + /// Offset of the decoration from the baseline. If `None`, use the metrics + /// of the containing run. + pub offset: Option, + /// Thickness of the decoration. If `None`, use the metrics of the + /// containing run. + pub size: Option, +} + +#[cfg(feature = "accesskit")] +#[derive(Clone, Default)] +pub struct LayoutAccessibility { + // The following two fields maintain a two-way mapping between runs + // and AccessKit node IDs, where each run is identified by its line index + // and run index within that line, or a run path for short. These maps + // are maintained by `LayoutAccess::build_nodes`, which ensures that removed + // runs are removed from the maps on the next accessibility pass. + pub(crate) access_ids_by_run_path: HashMap<(usize, usize), NodeId>, + pub(crate) run_paths_by_access_id: HashMap, +} + +#[cfg(feature = "accesskit")] +impl LayoutAccessibility { + #[allow(clippy::too_many_arguments)] + pub fn build_nodes( + &mut self, + text: &str, + layout: &Layout, + update: &mut TreeUpdate, + parent_node: &mut Node, + mut next_node_id: impl FnMut() -> NodeId, + x_offset: f64, + y_offset: f64, + ) { + // Build a set of node IDs for the runs encountered in this pass. + let mut ids = HashSet::::new(); + // Reuse scratch space for storing a sorted list of runs. + let mut runs = Vec::new(); + + for (line_index, line) in layout.lines().enumerate() { + let metrics = line.metrics(); + // Defer adding each run node until we reach either the next run + // or the end of the line. That way, we can set relations between + // runs in a line and do anything special that might be required + // for the last run in a line. + let mut last_node: Option<(NodeId, Node)> = None; + + // Iterate over the runs from left to right, computing their offsets, + // then sort them into text order. + runs.clear(); + runs.reserve(line.len()); + { + let mut run_offset = metrics.offset; + for run in line.runs() { + let advance = run.advance(); + runs.push((run, run_offset)); + run_offset += advance; + } + } + runs.sort_by_key(|(r, _)| r.text_range().start); + + for (run, run_offset) in runs.drain(..) { + let run_path = (line_index, run.index()); + // If we encountered this same run path in the previous + // accessibility pass, reuse the same AccessKit ID. Otherwise, + // allocate a new one. This enables stable node IDs when merely + // updating the content of existing runs. + let id = self + .access_ids_by_run_path + .get(&run_path) + .copied() + .unwrap_or_else(|| { + let id = next_node_id(); + self.access_ids_by_run_path.insert(run_path, id); + self.run_paths_by_access_id.insert(id, run_path); + id + }); + ids.insert(id); + let mut node = Node::new(Role::TextRun); + + if let Some((last_id, mut last_node)) = last_node.take() { + last_node.set_next_on_line(id); + node.set_previous_on_line(last_id); + update.nodes.push((last_id, last_node)); + parent_node.push_child(last_id); + } + + node.set_bounds(accesskit::Rect { + x0: x_offset + run_offset as f64, + y0: y_offset + metrics.min_coord as f64, + x1: x_offset + (run_offset + run.advance()) as f64, + y1: y_offset + metrics.max_coord as f64, + }); + node.set_text_direction(if run.is_rtl() { + TextDirection::RightToLeft + } else { + TextDirection::LeftToRight + }); + + let run_text = &text[run.text_range()]; + node.set_value(run_text); + + let mut character_lengths = Vec::new(); + let mut cluster_offset = 0.0; + let mut character_positions = Vec::new(); + let mut character_widths = Vec::new(); + let mut word_lengths = Vec::new(); + let mut last_word_start = 0; + + for cluster in run.clusters() { + let cluster_text = &text[cluster.text_range()]; + if cluster.is_word_boundary() + && !cluster.is_space_or_nbsp() + && !character_lengths.is_empty() + { + word_lengths.push((character_lengths.len() - last_word_start) as _); + last_word_start = character_lengths.len(); + } + character_lengths.push(cluster_text.len() as _); + character_positions.push(cluster_offset); + character_widths.push(cluster.advance()); + cluster_offset += cluster.advance(); + } + + word_lengths.push((character_lengths.len() - last_word_start) as _); + node.set_character_lengths(character_lengths); + node.set_character_positions(character_positions); + node.set_character_widths(character_widths); + node.set_word_lengths(word_lengths); + + last_node = Some((id, node)); + } + + if let Some((id, node)) = last_node { + update.nodes.push((id, node)); + parent_node.push_child(id); + } + } + + // Remove mappings for runs that no longer exist. + let mut ids_to_remove = Vec::::new(); + let mut run_paths_to_remove = Vec::<(usize, usize)>::new(); + for (access_id, run_path) in self.run_paths_by_access_id.iter() { + if !ids.contains(access_id) { + ids_to_remove.push(*access_id); + run_paths_to_remove.push(*run_path); + } + } + for id in ids_to_remove { + self.run_paths_by_access_id.remove(&id); + } + for run_path in run_paths_to_remove { + self.access_ids_by_run_path.remove(&run_path); + } + } +} diff --git a/parley/src/layout/run.rs b/parley/src/outputs/run.rs similarity index 100% rename from parley/src/layout/run.rs rename to parley/src/outputs/run.rs From 0e76a9372a7e112afda99e39e1aca3061314210e Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 19:09:23 +0100 Subject: [PATCH 07/14] Refactor outputs module Move data types to their respective modules --- parley/src/outputs/accessibility.rs | 155 +++++++++ parley/src/outputs/alignment.rs | 13 +- parley/src/outputs/cluster.rs | 53 ++- parley/src/outputs/cursor.rs | 8 +- parley/src/outputs/{data.rs => layout.rs} | 343 +++++++++---------- parley/src/outputs/line.rs | 112 ++++++- parley/src/outputs/mod.rs | 388 ++-------------------- parley/src/outputs/run.rs | 49 ++- 8 files changed, 570 insertions(+), 551 deletions(-) create mode 100644 parley/src/outputs/accessibility.rs rename parley/src/outputs/{data.rs => layout.rs} (64%) diff --git a/parley/src/outputs/accessibility.rs b/parley/src/outputs/accessibility.rs new file mode 100644 index 00000000..93119d44 --- /dev/null +++ b/parley/src/outputs/accessibility.rs @@ -0,0 +1,155 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; +use alloc::vec::Vec; +use hashbrown::{HashMap, HashSet}; + +use crate::inputs::style::Brush; +use crate::outputs::*; + +#[derive(Clone, Default)] +pub struct LayoutAccessibility { + // The following two fields maintain a two-way mapping between runs + // and AccessKit node IDs, where each run is identified by its line index + // and run index within that line, or a run path for short. These maps + // are maintained by `LayoutAccess::build_nodes`, which ensures that removed + // runs are removed from the maps on the next accessibility pass. + pub(crate) access_ids_by_run_path: HashMap<(usize, usize), NodeId>, + pub(crate) run_paths_by_access_id: HashMap, +} + +impl LayoutAccessibility { + #[allow(clippy::too_many_arguments)] + pub fn build_nodes( + &mut self, + text: &str, + layout: &Layout, + update: &mut TreeUpdate, + parent_node: &mut Node, + mut next_node_id: impl FnMut() -> NodeId, + x_offset: f64, + y_offset: f64, + ) { + // Build a set of node IDs for the runs encountered in this pass. + let mut ids = HashSet::::new(); + // Reuse scratch space for storing a sorted list of runs. + let mut runs = Vec::new(); + + for (line_index, line) in layout.lines().enumerate() { + let metrics = line.metrics(); + // Defer adding each run node until we reach either the next run + // or the end of the line. That way, we can set relations between + // runs in a line and do anything special that might be required + // for the last run in a line. + let mut last_node: Option<(NodeId, Node)> = None; + + // Iterate over the runs from left to right, computing their offsets, + // then sort them into text order. + runs.clear(); + runs.reserve(line.len()); + { + let mut run_offset = metrics.offset; + for run in line.runs() { + let advance = run.advance(); + runs.push((run, run_offset)); + run_offset += advance; + } + } + runs.sort_by_key(|(r, _)| r.text_range().start); + + for (run, run_offset) in runs.drain(..) { + let run_path = (line_index, run.index()); + // If we encountered this same run path in the previous + // accessibility pass, reuse the same AccessKit ID. Otherwise, + // allocate a new one. This enables stable node IDs when merely + // updating the content of existing runs. + let id = self + .access_ids_by_run_path + .get(&run_path) + .copied() + .unwrap_or_else(|| { + let id = next_node_id(); + self.access_ids_by_run_path.insert(run_path, id); + self.run_paths_by_access_id.insert(id, run_path); + id + }); + ids.insert(id); + let mut node = Node::new(Role::TextRun); + + if let Some((last_id, mut last_node)) = last_node.take() { + last_node.set_next_on_line(id); + node.set_previous_on_line(last_id); + update.nodes.push((last_id, last_node)); + parent_node.push_child(last_id); + } + + node.set_bounds(accesskit::Rect { + x0: x_offset + run_offset as f64, + y0: y_offset + metrics.min_coord as f64, + x1: x_offset + (run_offset + run.advance()) as f64, + y1: y_offset + metrics.max_coord as f64, + }); + node.set_text_direction(if run.is_rtl() { + TextDirection::RightToLeft + } else { + TextDirection::LeftToRight + }); + + let run_text = &text[run.text_range()]; + node.set_value(run_text); + + let mut character_lengths = Vec::new(); + let mut cluster_offset = 0.0; + let mut character_positions = Vec::new(); + let mut character_widths = Vec::new(); + let mut word_lengths = Vec::new(); + let mut last_word_start = 0; + + for cluster in run.clusters() { + let cluster_text = &text[cluster.text_range()]; + if cluster.is_word_boundary() + && !cluster.is_space_or_nbsp() + && !character_lengths.is_empty() + { + word_lengths.push((character_lengths.len() - last_word_start) as _); + last_word_start = character_lengths.len(); + } + character_lengths.push(cluster_text.len() as _); + character_positions.push(cluster_offset); + character_widths.push(cluster.advance()); + cluster_offset += cluster.advance(); + } + + word_lengths.push((character_lengths.len() - last_word_start) as _); + node.set_character_lengths(character_lengths); + node.set_character_positions(character_positions); + node.set_character_widths(character_widths); + node.set_word_lengths(word_lengths); + + last_node = Some((id, node)); + } + + if let Some((id, node)) = last_node { + update.nodes.push((id, node)); + parent_node.push_child(id); + } + } + + // Remove mappings for runs that no longer exist. + let mut ids_to_remove = Vec::::new(); + let mut run_paths_to_remove = Vec::<(usize, usize)>::new(); + for (access_id, run_path) in self.run_paths_by_access_id.iter() { + if !ids.contains(access_id) { + ids_to_remove.push(*access_id); + run_paths_to_remove.push(*run_path); + } + } + for id in ids_to_remove { + self.run_paths_by_access_id.remove(&id); + } + for run_path in run_paths_to_remove { + self.access_ids_by_run_path.remove(&run_path); + } + } +} diff --git a/parley/src/outputs/alignment.rs b/parley/src/outputs/alignment.rs index 0ccc7cfe..0e0e587c 100644 --- a/parley/src/outputs/alignment.rs +++ b/parley/src/outputs/alignment.rs @@ -1,8 +1,19 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::{Alignment, BreakReason, LayoutData}; use crate::inputs::style::Brush; +use crate::outputs::{BreakReason, LayoutData}; + +/// Alignment of a layout. +#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)] +#[repr(u8)] +pub enum Alignment { + #[default] + Start, + Middle, + End, + Justified, +} pub(crate) fn align( layout: &mut LayoutData, diff --git a/parley/src/outputs/cluster.rs b/parley/src/outputs/cluster.rs index b500164c..6ce034b1 100644 --- a/parley/src/outputs/cluster.rs +++ b/parley/src/outputs/cluster.rs @@ -1,8 +1,57 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::{BreakReason, Brush, Cluster, ClusterInfo, Glyph, Layout, Line, Range, Run}; -use swash::text::cluster::Whitespace; +use crate::outputs::RunData; +use crate::outputs::{BreakReason, Brush, Glyph, Layout, Line, Run}; + +use core::ops::Range; +use swash::text::cluster::{ClusterInfo, Whitespace}; + +/// Atomic unit of text. +#[derive(Copy, Clone)] +pub struct Cluster<'a, B: Brush> { + pub(crate) path: ClusterPath, + pub(crate) run: Run<'a, B>, + pub(crate) data: &'a ClusterData, +} + +#[derive(Copy, Clone)] +pub(crate) struct ClusterData { + pub(crate) info: ClusterInfo, + pub(crate) flags: u16, + pub(crate) style_index: u16, + pub(crate) glyph_len: u8, + pub(crate) text_len: u8, + /// If `glyph_len == 0xFF`, then `glyph_offset` is a glyph identifier, + /// otherwise, it's an offset into the glyph array with the base + /// taken from the owning run. + pub(crate) glyph_offset: u16, + pub(crate) text_offset: u16, + pub(crate) advance: f32, +} + +impl ClusterData { + pub(crate) const LIGATURE_START: u16 = 1; + pub(crate) const LIGATURE_COMPONENT: u16 = 2; + pub(crate) const DIVERGENT_STYLES: u16 = 4; + + pub(crate) fn is_ligature_start(self) -> bool { + self.flags & Self::LIGATURE_START != 0 + } + + pub(crate) fn is_ligature_component(self) -> bool { + self.flags & Self::LIGATURE_COMPONENT != 0 + } + + pub(crate) fn has_divergent_styles(self) -> bool { + self.flags & Self::DIVERGENT_STYLES != 0 + } + + pub(crate) fn text_range(self, run: &RunData) -> Range { + let start = run.text_range.start + self.text_offset as usize; + start..start + self.text_len as usize + } +} /// Defines the visual side of the cluster for hit testing. /// diff --git a/parley/src/outputs/cursor.rs b/parley/src/outputs/cursor.rs index a60784c2..110319a7 100644 --- a/parley/src/outputs/cursor.rs +++ b/parley/src/outputs/cursor.rs @@ -4,8 +4,8 @@ //! Text selection support. #[cfg(feature = "accesskit")] -use super::LayoutAccessibility; -use super::{Affinity, BreakReason, Brush, Cluster, ClusterSide, Layout, Line}; +use crate::outputs::LayoutAccessibility; +use crate::outputs::{Affinity, BreakReason, Brush, Cluster, ClusterSide, Layout, Line}; #[cfg(feature = "accesskit")] use accesskit::TextPosition; use alloc::vec::Vec; @@ -17,8 +17,8 @@ use swash::text::cluster::Whitespace; /// Defines a position with a text layout. #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct Cursor { - index: usize, - affinity: Affinity, + pub(crate) index: usize, + pub(crate) affinity: Affinity, } impl Cursor { diff --git a/parley/src/outputs/data.rs b/parley/src/outputs/layout.rs similarity index 64% rename from parley/src/outputs/data.rs rename to parley/src/outputs/layout.rs index 52712135..3cc353d8 100644 --- a/parley/src/outputs/data.rs +++ b/parley/src/outputs/layout.rs @@ -1,190 +1,27 @@ -// Copyright 2021 the Parley Authors +// Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::inline_box::InlineBox; -use crate::inputs::style::Brush; -use crate::outputs::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; -use crate::util::nearly_zero; -use crate::Font; -use core::ops::Range; +use core::cmp::Ordering; use swash::shape::Shaper; -use swash::text::cluster::{Boundary, ClusterInfo}; +use swash::text::cluster::Boundary; use swash::Synthesis; -use alloc::vec::Vec; - -#[derive(Copy, Clone)] -pub(crate) struct ClusterData { - pub(crate) info: ClusterInfo, - pub(crate) flags: u16, - pub(crate) style_index: u16, - pub(crate) glyph_len: u8, - pub(crate) text_len: u8, - /// If `glyph_len == 0xFF`, then `glyph_offset` is a glyph identifier, - /// otherwise, it's an offset into the glyph array with the base - /// taken from the owning run. - pub(crate) glyph_offset: u16, - pub(crate) text_offset: u16, - pub(crate) advance: f32, -} - -impl ClusterData { - pub(crate) const LIGATURE_START: u16 = 1; - pub(crate) const LIGATURE_COMPONENT: u16 = 2; - pub(crate) const DIVERGENT_STYLES: u16 = 4; - - pub(crate) fn is_ligature_start(self) -> bool { - self.flags & Self::LIGATURE_START != 0 - } - - pub(crate) fn is_ligature_component(self) -> bool { - self.flags & Self::LIGATURE_COMPONENT != 0 - } - - pub(crate) fn has_divergent_styles(self) -> bool { - self.flags & Self::DIVERGENT_STYLES != 0 - } +use crate::inputs::break_lines::BreakLines; +use crate::inputs::style::Brush; +use crate::{Font, InlineBox}; - pub(crate) fn text_range(self, run: &RunData) -> Range { - let start = run.text_range.start + self.text_offset as usize; - start..start + self.text_len as usize - } -} +use crate::outputs::align; +use crate::outputs::run::RunMetrics; +use crate::outputs::{ + Alignment, ClusterData, Glyph, LayoutItem, LayoutItemKind, Line, LineData, LineItemData, + RunData, Style, +}; +use crate::util::nearly_zero; +/// Text layout. #[derive(Clone)] -pub(crate) struct RunData { - /// Index of the font for the run. - pub(crate) font_index: usize, - /// Font size. - pub(crate) font_size: f32, - /// Synthesis information for the font. - pub(crate) synthesis: Synthesis, - /// Range of normalized coordinates in the layout data. - pub(crate) coords_range: Range, - /// Range of the source text. - pub(crate) text_range: Range, - /// Bidi level for the run. - pub(crate) bidi_level: u8, - /// True if the run ends with a newline. - pub(crate) ends_with_newline: bool, - /// Range of clusters. - pub(crate) cluster_range: Range, - /// Base for glyph indices. - pub(crate) glyph_start: usize, - /// Metrics for the run. - pub(crate) metrics: RunMetrics, - /// Additional word spacing. - pub(crate) word_spacing: f32, - /// Additional letter spacing. - pub(crate) letter_spacing: f32, - /// Total advance of the run. - pub(crate) advance: f32, -} - -#[derive(Copy, Clone, Default, PartialEq, Debug)] -pub enum BreakReason { - #[default] - None, - Regular, - Explicit, - Emergency, -} - -#[derive(Clone, Default)] -pub(crate) struct LineData { - /// Range of the source text. - pub(crate) text_range: Range, - /// Range of line items. - pub(crate) item_range: Range, - /// Metrics for the line. - pub(crate) metrics: LineMetrics, - /// The cause of the line break. - pub(crate) break_reason: BreakReason, - /// Alignment. - pub(crate) alignment: Alignment, - /// Maximum advance for the line. - pub(crate) max_advance: f32, - /// Number of justified clusters on the line. - pub(crate) num_spaces: usize, -} - -impl LineData { - pub(crate) fn size(&self) -> f32 { - self.metrics.ascent + self.metrics.descent + self.metrics.leading - } -} - -#[derive(Debug, Clone)] -pub(crate) struct LineItemData { - /// Whether the item is a run or an inline box - pub(crate) kind: LayoutItemKind, - /// The index of the run or inline box in the runs or `inline_boxes` vec - pub(crate) index: usize, - /// Bidi level for the item (used for reordering) - pub(crate) bidi_level: u8, - /// Advance (size in direction of text flow) for the run. - pub(crate) advance: f32, - - // Fields that only apply to text runs (Ignored for boxes) - // TODO: factor this out? - /// True if the run is composed entirely of whitespace. - pub(crate) is_whitespace: bool, - /// True if the run ends in whitespace. - pub(crate) has_trailing_whitespace: bool, - /// Range of the source text. - pub(crate) text_range: Range, - /// Range of clusters. - pub(crate) cluster_range: Range, -} - -impl LineItemData { - pub(crate) fn is_text_run(&self) -> bool { - self.kind == LayoutItemKind::TextRun - } - - pub(crate) fn compute_line_height(&self, layout: &LayoutData) -> f32 { - match self.kind { - LayoutItemKind::TextRun => { - let mut line_height = 0_f32; - let run = &layout.runs[self.index]; - let glyph_start = run.glyph_start; - for cluster in &layout.clusters[run.cluster_range.clone()] { - if cluster.glyph_len != 0xFF && cluster.has_divergent_styles() { - let start = glyph_start + cluster.glyph_offset as usize; - let end = start + cluster.glyph_len as usize; - for glyph in &layout.glyphs[start..end] { - line_height = - line_height.max(layout.styles[glyph.style_index()].line_height); - } - } else { - line_height = line_height - .max(layout.styles[cluster.style_index as usize].line_height); - } - } - line_height - } - LayoutItemKind::InlineBox => { - // TODO: account for vertical alignment (e.g. baseline alignment) - layout.inline_boxes[self.index].height - } - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum LayoutItemKind { - TextRun, - InlineBox, -} - -#[derive(Debug, Clone)] -pub(crate) struct LayoutItem { - /// Whether the item is a run or an inline box - pub(crate) kind: LayoutItemKind, - /// The index of the run or inline box in the runs or `inline_boxes` vec - pub(crate) index: usize, - /// Bidi level for the item (used for reordering) - pub(crate) bidi_level: u8, +pub struct Layout { + pub(crate) data: LayoutData, } #[derive(Clone)] @@ -471,3 +308,151 @@ impl LayoutData { } } } + +impl Layout { + /// Creates an empty layout. + pub fn new() -> Self { + Self::default() + } + + /// Returns the scale factor provided when creating the layout. + pub fn scale(&self) -> f32 { + self.data.scale + } + + /// Returns the style collection for the layout. + pub fn styles(&self) -> &[Style] { + &self.data.styles + } + + /// Returns the width of the layout. + pub fn width(&self) -> f32 { + self.data.width + } + + /// Returns the width of the layout, including the width of any trailing + /// whitespace. + pub fn full_width(&self) -> f32 { + self.data.full_width + } + + /// Returns the height of the layout. + pub fn height(&self) -> f32 { + self.data.height + } + + /// Returns the number of lines in the layout. + pub fn len(&self) -> usize { + self.data.lines.len() + } + + /// Returns `true` if the layout is empty. + pub fn is_empty(&self) -> bool { + self.data.lines.is_empty() + } + + /// Returns the line at the specified index. + pub fn get(&self, index: usize) -> Option> { + Some(Line { + index: index as u32, + layout: self, + data: self.data.lines.get(index)?, + }) + } + + /// Returns true if the dominant direction of the layout is right-to-left. + pub fn is_rtl(&self) -> bool { + self.data.base_level & 1 != 0 + } + + pub fn inline_boxes(&self) -> &[InlineBox] { + &self.data.inline_boxes + } + + pub fn inline_boxes_mut(&mut self) -> &mut [InlineBox] { + &mut self.data.inline_boxes + } + + /// Returns an iterator over the lines in the layout. + pub fn lines(&self) -> impl Iterator> + '_ + Clone { + self.data + .lines + .iter() + .enumerate() + .map(move |(index, data)| Line { + index: index as u32, + layout: self, + data, + }) + } + + /// Returns line breaker to compute lines for the layout. + pub fn break_lines(&mut self) -> BreakLines<'_, B> { + BreakLines::new(self) + } + + /// Breaks all lines with the specified maximum advance. + pub fn break_all_lines(&mut self, max_advance: Option) { + self.break_lines() + .break_remaining(max_advance.unwrap_or(f32::MAX)); + } + + // Apply to alignment to layout relative to the specified container width. If container_width is not + // specified then the max line length is used. + pub fn align(&mut self, container_width: Option, alignment: Alignment) { + align(&mut self.data, container_width, alignment); + } + + /// Returns the index and `Line` object for the line containing the + /// given byte `index` in the source text. + pub(crate) fn line_for_byte_index(&self, index: usize) -> Option<(usize, Line<'_, B>)> { + let line_index = self + .data + .lines + .binary_search_by(|line| { + if index < line.text_range.start { + Ordering::Greater + } else if index >= line.text_range.end { + Ordering::Less + } else { + Ordering::Equal + } + }) + .ok()?; + Some((line_index, self.get(line_index)?)) + } + + /// Returns the index and `Line` object for the line containing the + /// given `offset`. + /// + /// The offset is specified in the direction orthogonal to line direction. + /// For horizontal text, this is a vertical or y offset. If the offset is + /// on a line boundary, it is considered to be contained by the later line. + pub(crate) fn line_for_offset(&self, offset: f32) -> Option<(usize, Line<'_, B>)> { + if offset < 0.0 { + return Some((0, self.get(0)?)); + } + let maybe_line_index = self.data.lines.binary_search_by(|line| { + if offset < line.metrics.min_coord { + Ordering::Greater + } else if offset >= line.metrics.max_coord { + Ordering::Less + } else { + Ordering::Equal + } + }); + let line_index = match maybe_line_index { + Ok(index) => index, + Err(index) => index.saturating_sub(1), + }; + Some((line_index, self.get(line_index)?)) + } +} + +impl Default for Layout { + fn default() -> Self { + Self { + data: Default::default(), + } + } +} diff --git a/parley/src/outputs/line.rs b/parley/src/outputs/line.rs index 6385fe73..6b0b8f87 100644 --- a/parley/src/outputs/line.rs +++ b/parley/src/outputs/line.rs @@ -1,11 +1,20 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use core::ops::Range; + use crate::inputs::LineMetrics; -use crate::outputs::{ - BreakReason, Brush, Glyph, LayoutItemKind, Line, LineItemData, Range, Run, Style, -}; +use crate::outputs::{Alignment, LayoutData}; +use crate::outputs::{BreakReason, Brush, Glyph, Layout, Run, Style}; + +/// Line in a text layout. +#[derive(Copy, Clone)] +pub struct Line<'a, B: Brush> { + pub(crate) layout: &'a Layout, + pub(crate) index: u32, + pub(crate) data: &'a LineData, +} impl<'a, B: Brush> Line<'a, B> { /// Returns the metrics for the line. @@ -93,6 +102,103 @@ impl<'a, B: Brush> Line<'a, B> { } } +#[derive(Clone, Default)] +pub(crate) struct LineData { + /// Range of the source text. + pub(crate) text_range: Range, + /// Range of line items. + pub(crate) item_range: Range, + /// Metrics for the line. + pub(crate) metrics: LineMetrics, + /// The cause of the line break. + pub(crate) break_reason: BreakReason, + /// Alignment. + pub(crate) alignment: Alignment, + /// Maximum advance for the line. + pub(crate) max_advance: f32, + /// Number of justified clusters on the line. + pub(crate) num_spaces: usize, +} + +impl LineData { + pub(crate) fn size(&self) -> f32 { + self.metrics.ascent + self.metrics.descent + self.metrics.leading + } +} + +#[derive(Debug, Clone)] +pub(crate) struct LineItemData { + /// Whether the item is a run or an inline box + pub(crate) kind: LayoutItemKind, + /// The index of the run or inline box in the runs or `inline_boxes` vec + pub(crate) index: usize, + /// Bidi level for the item (used for reordering) + pub(crate) bidi_level: u8, + /// Advance (size in direction of text flow) for the run. + pub(crate) advance: f32, + + // Fields that only apply to text runs (Ignored for boxes) + // TODO: factor this out? + /// True if the run is composed entirely of whitespace. + pub(crate) is_whitespace: bool, + /// True if the run ends in whitespace. + pub(crate) has_trailing_whitespace: bool, + /// Range of the source text. + pub(crate) text_range: Range, + /// Range of clusters. + pub(crate) cluster_range: Range, +} + +impl LineItemData { + pub(crate) fn is_text_run(&self) -> bool { + self.kind == LayoutItemKind::TextRun + } + + pub(crate) fn compute_line_height(&self, layout: &LayoutData) -> f32 { + match self.kind { + LayoutItemKind::TextRun => { + let mut line_height = 0_f32; + let run = &layout.runs[self.index]; + let glyph_start = run.glyph_start; + for cluster in &layout.clusters[run.cluster_range.clone()] { + if cluster.glyph_len != 0xFF && cluster.has_divergent_styles() { + let start = glyph_start + cluster.glyph_offset as usize; + let end = start + cluster.glyph_len as usize; + for glyph in &layout.glyphs[start..end] { + line_height = + line_height.max(layout.styles[glyph.style_index()].line_height); + } + } else { + line_height = line_height + .max(layout.styles[cluster.style_index as usize].line_height); + } + } + line_height + } + LayoutItemKind::InlineBox => { + // TODO: account for vertical alignment (e.g. baseline alignment) + layout.inline_boxes[self.index].height + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum LayoutItemKind { + TextRun, + InlineBox, +} + +#[derive(Debug, Clone)] +pub(crate) struct LayoutItem { + /// Whether the item is a run or an inline box + pub(crate) kind: LayoutItemKind, + /// The index of the run or inline box in the runs or `inline_boxes` vec + pub(crate) index: usize, + /// Bidi level for the item (used for reordering) + pub(crate) bidi_level: u8, +} + /// The computed result of an item (glyph run or inline box) within a layout #[derive(Clone)] pub enum PositionedLayoutItem<'a, B: Brush> { diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs index e56feefe..8052961c 100644 --- a/parley/src/outputs/mod.rs +++ b/parley/src/outputs/mod.rs @@ -5,221 +5,49 @@ pub(crate) mod alignment; pub(crate) mod cluster; +pub(crate) mod layout; pub(crate) mod line; pub(crate) mod run; -pub(crate) mod data; - pub mod cursor; pub mod editor; -use self::alignment::align; - -use crate::inputs::break_lines::BreakLines; -use crate::inputs::style::Brush; -use crate::inputs::LineMetrics; -use crate::{Font, InlineBox}; #[cfg(feature = "accesskit")] -use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; -#[cfg(feature = "accesskit")] -use alloc::vec::Vec; -use core::{cmp::Ordering, ops::Range}; -use data::{ClusterData, RunData}; +mod accessibility; + #[cfg(feature = "accesskit")] -use hashbrown::{HashMap, HashSet}; -use swash::text::cluster::ClusterInfo; -use swash::{GlyphId, NormalizedCoord, Synthesis}; +pub use accessibility::LayoutAccessibility; -pub use cluster::{Affinity, ClusterPath, ClusterSide}; -pub use cursor::{Cursor, Selection}; -pub use line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}; -pub use run::RunMetrics; +pub(crate) use self::alignment::align; -pub(crate) use data::{ - BreakReason, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, +use crate::inputs::style::Brush; +use swash::GlyphId; + +pub use self::{ + alignment::Alignment, + cluster::Cluster, + cluster::{Affinity, ClusterPath, ClusterSide}, + cursor::{Cursor, Selection}, + layout::Layout, + line::Line, + line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}, + run::Run, + run::RunMetrics, +}; +pub(crate) use self::{ + cluster::ClusterData, + layout::LayoutData, + line::{LayoutItem, LayoutItemKind, LineData, LineItemData}, + run::RunData, }; -/// Alignment of a layout. -#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)] -#[repr(u8)] -pub enum Alignment { +#[derive(Copy, Clone, Default, PartialEq, Debug)] +pub enum BreakReason { #[default] - Start, - Middle, - End, - Justified, -} - -/// Text layout. -#[derive(Clone)] -pub struct Layout { - pub(crate) data: LayoutData, -} - -impl Layout { - /// Creates an empty layout. - pub fn new() -> Self { - Self::default() - } - - /// Returns the scale factor provided when creating the layout. - pub fn scale(&self) -> f32 { - self.data.scale - } - - /// Returns the style collection for the layout. - pub fn styles(&self) -> &[Style] { - &self.data.styles - } - - /// Returns the width of the layout. - pub fn width(&self) -> f32 { - self.data.width - } - - /// Returns the width of the layout, including the width of any trailing - /// whitespace. - pub fn full_width(&self) -> f32 { - self.data.full_width - } - - /// Returns the height of the layout. - pub fn height(&self) -> f32 { - self.data.height - } - - /// Returns the number of lines in the layout. - pub fn len(&self) -> usize { - self.data.lines.len() - } - - /// Returns `true` if the layout is empty. - pub fn is_empty(&self) -> bool { - self.data.lines.is_empty() - } - - /// Returns the line at the specified index. - pub fn get(&self, index: usize) -> Option> { - Some(Line { - index: index as u32, - layout: self, - data: self.data.lines.get(index)?, - }) - } - - /// Returns true if the dominant direction of the layout is right-to-left. - pub fn is_rtl(&self) -> bool { - self.data.base_level & 1 != 0 - } - - pub fn inline_boxes(&self) -> &[InlineBox] { - &self.data.inline_boxes - } - - pub fn inline_boxes_mut(&mut self) -> &mut [InlineBox] { - &mut self.data.inline_boxes - } - - /// Returns an iterator over the lines in the layout. - pub fn lines(&self) -> impl Iterator> + '_ + Clone { - self.data - .lines - .iter() - .enumerate() - .map(move |(index, data)| Line { - index: index as u32, - layout: self, - data, - }) - } - - /// Returns line breaker to compute lines for the layout. - pub fn break_lines(&mut self) -> BreakLines<'_, B> { - BreakLines::new(self) - } - - /// Breaks all lines with the specified maximum advance. - pub fn break_all_lines(&mut self, max_advance: Option) { - self.break_lines() - .break_remaining(max_advance.unwrap_or(f32::MAX)); - } - - // Apply to alignment to layout relative to the specified container width. If container_width is not - // specified then the max line length is used. - pub fn align(&mut self, container_width: Option, alignment: Alignment) { - align(&mut self.data, container_width, alignment); - } - - /// Returns the index and `Line` object for the line containing the - /// given byte `index` in the source text. - pub(crate) fn line_for_byte_index(&self, index: usize) -> Option<(usize, Line<'_, B>)> { - let line_index = self - .data - .lines - .binary_search_by(|line| { - if index < line.text_range.start { - Ordering::Greater - } else if index >= line.text_range.end { - Ordering::Less - } else { - Ordering::Equal - } - }) - .ok()?; - Some((line_index, self.get(line_index)?)) - } - - /// Returns the index and `Line` object for the line containing the - /// given `offset`. - /// - /// The offset is specified in the direction orthogonal to line direction. - /// For horizontal text, this is a vertical or y offset. If the offset is - /// on a line boundary, it is considered to be contained by the later line. - pub(crate) fn line_for_offset(&self, offset: f32) -> Option<(usize, Line<'_, B>)> { - if offset < 0.0 { - return Some((0, self.get(0)?)); - } - let maybe_line_index = self.data.lines.binary_search_by(|line| { - if offset < line.metrics.min_coord { - Ordering::Greater - } else if offset >= line.metrics.max_coord { - Ordering::Less - } else { - Ordering::Equal - } - }); - let line_index = match maybe_line_index { - Ok(index) => index, - Err(index) => index.saturating_sub(1), - }; - Some((line_index, self.get(line_index)?)) - } -} - -impl Default for Layout { - fn default() -> Self { - Self { - data: Default::default(), - } - } -} - -/// Sequence of clusters with a single font and style. -#[derive(Copy, Clone)] -pub struct Run<'a, B: Brush> { - layout: &'a Layout, - line_index: u32, - index: u32, - data: &'a RunData, - line_data: Option<&'a LineItemData>, -} - -/// Atomic unit of text. -#[derive(Copy, Clone)] -pub struct Cluster<'a, B: Brush> { - path: ClusterPath, - run: Run<'a, B>, - data: &'a ClusterData, + None, + Regular, + Explicit, + Emergency, } /// Glyph with an offset and advance. @@ -239,14 +67,6 @@ impl Glyph { } } -/// Line in a text layout. -#[derive(Copy, Clone)] -pub struct Line<'a, B: Brush> { - layout: &'a Layout, - index: u32, - data: &'a LineData, -} - #[allow(clippy::partial_pub_fields)] /// Style properties. #[derive(Clone, Debug)] @@ -273,151 +93,3 @@ pub struct Decoration { /// containing run. pub size: Option, } - -#[cfg(feature = "accesskit")] -#[derive(Clone, Default)] -pub struct LayoutAccessibility { - // The following two fields maintain a two-way mapping between runs - // and AccessKit node IDs, where each run is identified by its line index - // and run index within that line, or a run path for short. These maps - // are maintained by `LayoutAccess::build_nodes`, which ensures that removed - // runs are removed from the maps on the next accessibility pass. - pub(crate) access_ids_by_run_path: HashMap<(usize, usize), NodeId>, - pub(crate) run_paths_by_access_id: HashMap, -} - -#[cfg(feature = "accesskit")] -impl LayoutAccessibility { - #[allow(clippy::too_many_arguments)] - pub fn build_nodes( - &mut self, - text: &str, - layout: &Layout, - update: &mut TreeUpdate, - parent_node: &mut Node, - mut next_node_id: impl FnMut() -> NodeId, - x_offset: f64, - y_offset: f64, - ) { - // Build a set of node IDs for the runs encountered in this pass. - let mut ids = HashSet::::new(); - // Reuse scratch space for storing a sorted list of runs. - let mut runs = Vec::new(); - - for (line_index, line) in layout.lines().enumerate() { - let metrics = line.metrics(); - // Defer adding each run node until we reach either the next run - // or the end of the line. That way, we can set relations between - // runs in a line and do anything special that might be required - // for the last run in a line. - let mut last_node: Option<(NodeId, Node)> = None; - - // Iterate over the runs from left to right, computing their offsets, - // then sort them into text order. - runs.clear(); - runs.reserve(line.len()); - { - let mut run_offset = metrics.offset; - for run in line.runs() { - let advance = run.advance(); - runs.push((run, run_offset)); - run_offset += advance; - } - } - runs.sort_by_key(|(r, _)| r.text_range().start); - - for (run, run_offset) in runs.drain(..) { - let run_path = (line_index, run.index()); - // If we encountered this same run path in the previous - // accessibility pass, reuse the same AccessKit ID. Otherwise, - // allocate a new one. This enables stable node IDs when merely - // updating the content of existing runs. - let id = self - .access_ids_by_run_path - .get(&run_path) - .copied() - .unwrap_or_else(|| { - let id = next_node_id(); - self.access_ids_by_run_path.insert(run_path, id); - self.run_paths_by_access_id.insert(id, run_path); - id - }); - ids.insert(id); - let mut node = Node::new(Role::TextRun); - - if let Some((last_id, mut last_node)) = last_node.take() { - last_node.set_next_on_line(id); - node.set_previous_on_line(last_id); - update.nodes.push((last_id, last_node)); - parent_node.push_child(last_id); - } - - node.set_bounds(accesskit::Rect { - x0: x_offset + run_offset as f64, - y0: y_offset + metrics.min_coord as f64, - x1: x_offset + (run_offset + run.advance()) as f64, - y1: y_offset + metrics.max_coord as f64, - }); - node.set_text_direction(if run.is_rtl() { - TextDirection::RightToLeft - } else { - TextDirection::LeftToRight - }); - - let run_text = &text[run.text_range()]; - node.set_value(run_text); - - let mut character_lengths = Vec::new(); - let mut cluster_offset = 0.0; - let mut character_positions = Vec::new(); - let mut character_widths = Vec::new(); - let mut word_lengths = Vec::new(); - let mut last_word_start = 0; - - for cluster in run.clusters() { - let cluster_text = &text[cluster.text_range()]; - if cluster.is_word_boundary() - && !cluster.is_space_or_nbsp() - && !character_lengths.is_empty() - { - word_lengths.push((character_lengths.len() - last_word_start) as _); - last_word_start = character_lengths.len(); - } - character_lengths.push(cluster_text.len() as _); - character_positions.push(cluster_offset); - character_widths.push(cluster.advance()); - cluster_offset += cluster.advance(); - } - - word_lengths.push((character_lengths.len() - last_word_start) as _); - node.set_character_lengths(character_lengths); - node.set_character_positions(character_positions); - node.set_character_widths(character_widths); - node.set_word_lengths(word_lengths); - - last_node = Some((id, node)); - } - - if let Some((id, node)) = last_node { - update.nodes.push((id, node)); - parent_node.push_child(id); - } - } - - // Remove mappings for runs that no longer exist. - let mut ids_to_remove = Vec::::new(); - let mut run_paths_to_remove = Vec::<(usize, usize)>::new(); - for (access_id, run_path) in self.run_paths_by_access_id.iter() { - if !ids.contains(access_id) { - ids_to_remove.push(*access_id); - run_paths_to_remove.push(*run_path); - } - } - for id in ids_to_remove { - self.run_paths_by_access_id.remove(&id); - } - for run_path in run_paths_to_remove { - self.access_ids_by_run_path.remove(&run_path); - } - } -} diff --git a/parley/src/outputs/run.rs b/parley/src/outputs/run.rs index 601f6634..29897ffb 100644 --- a/parley/src/outputs/run.rs +++ b/parley/src/outputs/run.rs @@ -1,10 +1,51 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::{ - Brush, Cluster, ClusterPath, Font, Layout, LineItemData, NormalizedCoord, Range, Run, RunData, - Synthesis, -}; +use core::ops::Range; +use peniko::Font; +use swash::{NormalizedCoord, Synthesis}; + +use crate::outputs::{Brush, Cluster, ClusterPath, Layout, LineItemData}; + +/// Sequence of clusters with a single font and style. +#[derive(Copy, Clone)] +pub struct Run<'a, B: Brush> { + pub(crate) layout: &'a Layout, + pub(crate) line_index: u32, + pub(crate) index: u32, + pub(crate) data: &'a RunData, + pub(crate) line_data: Option<&'a LineItemData>, +} + +#[derive(Clone)] +pub(crate) struct RunData { + /// Index of the font for the run. + pub(crate) font_index: usize, + /// Font size. + pub(crate) font_size: f32, + /// Synthesis information for the font. + pub(crate) synthesis: Synthesis, + /// Range of normalized coordinates in the layout data. + pub(crate) coords_range: Range, + /// Range of the source text. + pub(crate) text_range: Range, + /// Bidi level for the run. + pub(crate) bidi_level: u8, + /// True if the run ends with a newline. + pub(crate) ends_with_newline: bool, + /// Range of clusters. + pub(crate) cluster_range: Range, + /// Base for glyph indices. + pub(crate) glyph_start: usize, + /// Metrics for the run. + pub(crate) metrics: RunMetrics, + /// Additional word spacing. + pub(crate) word_spacing: f32, + /// Additional letter spacing. + pub(crate) letter_spacing: f32, + /// Total advance of the run. + pub(crate) advance: f32, +} impl<'a, B: Brush> Run<'a, B> { pub(crate) fn new( From 44f176eb1c8263a57ed7991d2a19799c587c8d49 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 19:16:55 +0100 Subject: [PATCH 08/14] Add missing Vec import --- parley/src/outputs/layout.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/parley/src/outputs/layout.rs b/parley/src/outputs/layout.rs index 3cc353d8..087dc3d5 100644 --- a/parley/src/outputs/layout.rs +++ b/parley/src/outputs/layout.rs @@ -1,6 +1,7 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use alloc::vec::Vec; use core::cmp::Ordering; use swash::shape::Shaper; use swash::text::cluster::Boundary; From 3fe9be7131413a583db545eca4c2ee68e25845a4 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 19:34:56 +0100 Subject: [PATCH 09/14] Fix clippy lint --- parley/src/outputs/accessibility.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parley/src/outputs/accessibility.rs b/parley/src/outputs/accessibility.rs index 93119d44..f9d40073 100644 --- a/parley/src/outputs/accessibility.rs +++ b/parley/src/outputs/accessibility.rs @@ -5,8 +5,8 @@ use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; use alloc::vec::Vec; use hashbrown::{HashMap, HashSet}; -use crate::inputs::style::Brush; -use crate::outputs::*; +use crate::inputs::Brush; +use crate::outputs::Layout; #[derive(Clone, Default)] pub struct LayoutAccessibility { From 2ad075eb9b298fd9bb31d3ebb98fa989fdcea77d Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 19:35:17 +0100 Subject: [PATCH 10/14] Refactor exports --- examples/swash_render/src/main.rs | 4 ++-- examples/tiny_skia_render/src/main.rs | 5 +++-- examples/vello_editor/src/main.rs | 7 ++++--- examples/vello_editor/src/text.rs | 6 +++--- parley/src/algos/resolve/mod.rs | 6 +++--- parley/src/algos/resolve/tree.rs | 2 +- parley/src/algos/shape.rs | 2 +- parley/src/inputs/break_lines.rs | 2 +- parley/src/inputs/builder.rs | 6 +++--- parley/src/inputs/context.rs | 8 ++++---- parley/src/inputs/mod.rs | 18 +++++++++++------- parley/src/inputs/style/styleset.rs | 21 +++++++++------------ parley/src/lib.rs | 11 ----------- parley/src/outputs/alignment.rs | 2 +- parley/src/outputs/cluster.rs | 5 ++--- parley/src/outputs/editor.rs | 5 +++-- parley/src/outputs/layout.rs | 4 ++-- parley/src/outputs/mod.rs | 2 +- parley/src/tests/test_basic.rs | 3 ++- parley/src/tests/test_cursor.rs | 2 +- parley/src/tests/utils/cursor_test.rs | 3 ++- parley/src/tests/utils/env.rs | 10 ++++++---- parley/src/tests/utils/renderer.rs | 2 +- 23 files changed, 66 insertions(+), 70 deletions(-) diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index b71fe5ef..89bb515c 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -12,9 +12,9 @@ use image::codecs::png::PngEncoder; use image::{self, Pixel, Rgba, RgbaImage}; -use parley::inputs::style::{FontStack, FontWeight, StyleProperty, TextStyle}; +use parley::inputs::{FontStack, FontWeight, StyleProperty, TextStyle}; use parley::outputs::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; -use parley::{FontContext, InlineBox, LayoutContext}; +use parley::{inputs::FontContext, inputs::LayoutContext, InlineBox}; use std::fs::File; use swash::scale::image::Content; use swash::scale::{Render, ScaleContext, Scaler, Source, StrikeWith}; diff --git a/examples/tiny_skia_render/src/main.rs b/examples/tiny_skia_render/src/main.rs index fcfd4e51..2a9638b7 100644 --- a/examples/tiny_skia_render/src/main.rs +++ b/examples/tiny_skia_render/src/main.rs @@ -14,8 +14,9 @@ )] use parley::{ - Alignment, FontContext, FontWeight, GenericFamily, GlyphRun, InlineBox, Layout, LayoutContext, - PositionedLayoutItem, StyleProperty, + inputs::{FontContext, FontWeight, GenericFamily, LayoutContext, StyleProperty}, + outputs::{Alignment, GlyphRun, Layout, PositionedLayoutItem}, + InlineBox, }; use skrifa::{ instance::{LocationRef, NormalizedCoord, Size}, diff --git a/examples/vello_editor/src/main.rs b/examples/vello_editor/src/main.rs index f1ec9a8c..6af33bb6 100644 --- a/examples/vello_editor/src/main.rs +++ b/examples/vello_editor/src/main.rs @@ -14,6 +14,7 @@ use accesskit::{Node, Role, Tree, TreeUpdate}; use anyhow::Result; +use parley::outputs::editor::Generation; use std::num::NonZeroUsize; use std::sync::Arc; use vello::kurbo; @@ -97,7 +98,7 @@ struct SimpleVelloApp<'s> { editor: text::Editor, /// The last generation of the editor layout that we drew. - last_drawn_generation: text::Generation, + last_drawn_generation: Generation, /// The IME cursor area we last sent to the platform. last_sent_ime_cursor_area: kurbo::Rect, @@ -185,7 +186,7 @@ impl ApplicationHandler for SimpleVelloApp<'_> { self.editor.cursor_blink(); if let Some(next_time) = self.editor.next_blink_time() { - self.last_drawn_generation = text::Generation::default(); + self.last_drawn_generation = Generation::default(); if let RenderState::Active(state) = &self.state { state.window.request_redraw(); } @@ -260,7 +261,7 @@ impl ApplicationHandler for SimpleVelloApp<'_> { WindowEvent::Focused(false) => { self.editor.disable_blink(); self.editor.cursor_blink(); - self.last_drawn_generation = text::Generation::default(); + self.last_drawn_generation = Generation::default(); if let RenderState::Active(state) = &self.state { state.window.request_redraw(); } diff --git a/examples/vello_editor/src/text.rs b/examples/vello_editor/src/text.rs index 686b3149..cfc6a9f7 100644 --- a/examples/vello_editor/src/text.rs +++ b/examples/vello_editor/src/text.rs @@ -3,7 +3,6 @@ use accesskit::{Node, TreeUpdate}; use core::default::Default; -use parley::{editor::SplitString, outputs::PositionedLayoutItem, GenericFamily, StyleProperty}; use std::time::{Duration, Instant}; use vello::{ kurbo::{Affine, Line, Stroke}, @@ -16,8 +15,9 @@ use winit::{ keyboard::{Key, NamedKey}, }; -pub use parley::outputs::editor::Generation; -use parley::{FontContext, LayoutContext, PlainEditor, PlainEditorDriver}; +use parley::inputs::{FontContext, GenericFamily, LayoutContext, StyleProperty}; +use parley::outputs::editor::{Generation, PlainEditor, PlainEditorDriver, SplitString}; +use parley::outputs::PositionedLayoutItem; use crate::access_ids::next_node_id; diff --git a/parley/src/algos/resolve/mod.rs b/parley/src/algos/resolve/mod.rs index 6944de74..1e8c9121 100644 --- a/parley/src/algos/resolve/mod.rs +++ b/parley/src/algos/resolve/mod.rs @@ -10,9 +10,9 @@ pub(crate) use range::RangedStyleBuilder; use alloc::{vec, vec::Vec}; -use crate::inputs::font::FontContext; -use crate::inputs::style::TextStyle; -use crate::inputs::style::{ +use crate::inputs::FontContext; +use crate::inputs::TextStyle; +use crate::inputs::{ Brush, FontFamily, FontFeature, FontSettings, FontStack, FontStyle, FontVariation, FontWeight, FontWidth, StyleProperty, }; diff --git a/parley/src/algos/resolve/tree.rs b/parley/src/algos/resolve/tree.rs index a8b8d979..151efbfe 100644 --- a/parley/src/algos/resolve/tree.rs +++ b/parley/src/algos/resolve/tree.rs @@ -5,7 +5,7 @@ use alloc::borrow::Cow; use alloc::{string::String, vec::Vec}; -use crate::inputs::style::WhiteSpaceCollapse; +use crate::inputs::WhiteSpaceCollapse; use super::{Brush, RangedStyle, ResolvedProperty, ResolvedStyle}; diff --git a/parley/src/algos/shape.rs b/parley/src/algos/shape.rs index 7a95e496..719e5f56 100644 --- a/parley/src/algos/shape.rs +++ b/parley/src/algos/shape.rs @@ -3,7 +3,7 @@ use crate::algos::resolve::{RangedStyle, ResolveContext, Resolved}; use crate::algos::swash_convert::{locale_to_fontique, script_to_fontique, synthesis_to_swash}; -use crate::inputs::style::{Brush, FontFeature, FontVariation}; +use crate::inputs::{Brush, FontFeature, FontVariation}; use crate::outputs::Layout; use crate::util::nearly_eq; use crate::Font; diff --git a/parley/src/inputs/break_lines.rs b/parley/src/inputs/break_lines.rs index 571ed33a..1ac3d509 100644 --- a/parley/src/inputs/break_lines.rs +++ b/parley/src/inputs/break_lines.rs @@ -11,7 +11,7 @@ use swash::text::cluster::Whitespace; #[allow(unused_imports)] use core_maths::CoreFloat; -use crate::inputs::style::Brush; +use crate::inputs::Brush; use crate::inputs::LineMetrics; use crate::outputs::alignment::unjustify; use crate::outputs::{ diff --git a/parley/src/inputs/builder.rs b/parley/src/inputs/builder.rs index ed4f4eab..8682ffb8 100644 --- a/parley/src/inputs/builder.rs +++ b/parley/src/inputs/builder.rs @@ -4,9 +4,9 @@ //! Context for layout. use crate::algos::shape::shape_text; -use crate::inputs::context::LayoutContext; -use crate::inputs::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; -use crate::FontContext; +use crate::inputs::FontContext; +use crate::inputs::LayoutContext; +use crate::inputs::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; use crate::outputs::Layout; diff --git a/parley/src/inputs/context.rs b/parley/src/inputs/context.rs index dddc53eb..0ca18257 100644 --- a/parley/src/inputs/context.rs +++ b/parley/src/inputs/context.rs @@ -8,15 +8,15 @@ use alloc::{vec, vec::Vec}; use crate::algos::bidi; use crate::algos::resolve::tree::TreeStyleBuilder; use crate::algos::resolve::{RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; -use crate::inputs::builder::RangedBuilder; -use crate::inputs::style::{Brush, TextStyle}; -use crate::FontContext; +use crate::inputs::FontContext; +use crate::inputs::RangedBuilder; +use crate::inputs::{Brush, TextStyle}; use swash::shape::ShapeContext; use swash::text::cluster::CharInfo; use crate::inline_box::InlineBox; -use crate::inputs::builder::TreeBuilder; +use crate::inputs::TreeBuilder; /// Shared scratch space used when constructing text layouts. /// diff --git a/parley/src/inputs/mod.rs b/parley/src/inputs/mod.rs index baef982a..5bdfd6c2 100644 --- a/parley/src/inputs/mod.rs +++ b/parley/src/inputs/mod.rs @@ -3,12 +3,16 @@ // TODO - Remove pub(crate) -pub(crate) mod break_lines; -pub(crate) mod builder; -pub(crate) mod context; -pub(crate) mod font; -pub(crate) mod line_metrics; - -pub mod style; +mod break_lines; +mod builder; +mod context; +mod font; +mod line_metrics; +mod style; +pub use break_lines::*; +pub use builder::*; +pub use context::*; +pub use font::*; pub use line_metrics::*; +pub use style::*; diff --git a/parley/src/inputs/style/styleset.rs b/parley/src/inputs/style/styleset.rs index f3dfd7ee..ce4e97f5 100644 --- a/parley/src/inputs/style/styleset.rs +++ b/parley/src/inputs/style/styleset.rs @@ -4,7 +4,9 @@ use core::mem::Discriminant; use hashbrown::HashMap; -type StyleProperty = crate::StyleProperty<'static, Brush>; +use crate::inputs::Brush; + +type StyleProperty = crate::inputs::StyleProperty<'static, Brush>; /// A long-lived collection of [`StyleProperties`](super::StyleProperty), containing at /// most one of each property. @@ -15,11 +17,9 @@ type StyleProperty = crate::StyleProperty<'static, Brush>; /// /// These styles do not have a corresponding range, and are generally unsuited for rich text. #[derive(Clone, Debug)] -pub struct StyleSet( - HashMap>, StyleProperty>, -); +pub struct StyleSet(HashMap>, StyleProperty>); -impl StyleSet { +impl StyleSet { /// Create a new collection of styles. /// /// The font size will be `font_size`, and can be overwritten at runtime by @@ -34,7 +34,7 @@ impl StyleSet { /// /// Note: Adding a [font stack](crate::StyleProperty::FontStack) to this collection is not /// additive, and instead overwrites any previously added font stack. - pub fn insert(&mut self, style: StyleProperty) -> Option> { + pub fn insert(&mut self, style: StyleProperty) -> Option> { let discriminant = core::mem::discriminant(&style); self.0.insert(discriminant, style) } @@ -45,7 +45,7 @@ impl StyleSet { /// /// Removing the [font size](crate::StyleProperty::FontSize) is not recommended, as an unspecified /// fallback font size will be used. - pub fn retain(&mut self, mut f: impl FnMut(&StyleProperty) -> bool) { + pub fn retain(&mut self, mut f: impl FnMut(&StyleProperty) -> bool) { self.0.retain(|_, v| f(v)); } @@ -59,10 +59,7 @@ impl StyleSet { /// /// Removing the [font size](crate::StyleProperty::FontSize) is not recommended, as an unspecified /// fallback font size will be used. - pub fn remove( - &mut self, - property: Discriminant>, - ) -> Option> { + pub fn remove(&mut self, property: Discriminant>) -> Option> { self.0.remove(&property) } @@ -70,7 +67,7 @@ impl StyleSet { /// /// Write access is not provided due to the invariant that keys /// are the discriminant of their corresponding value. - pub fn inner(&self) -> &HashMap>, StyleProperty> { + pub fn inner(&self) -> &HashMap>, StyleProperty> { &self.0 } } diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 3ef022b4..bdabf3cb 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -85,7 +85,6 @@ #![expect( missing_debug_implementations, single_use_lifetimes, - unnameable_types, clippy::allow_attributes, clippy::allow_attributes_without_reason, clippy::cast_possible_truncation, @@ -118,16 +117,6 @@ mod tests; pub use peniko::kurbo::Rect; pub use peniko::Font; -// TODO - Remove -pub use inputs::builder::{RangedBuilder, TreeBuilder}; -pub use inputs::context::LayoutContext; -pub use inputs::font::FontContext; -pub use inputs::style::*; - pub use inline_box::InlineBox; -#[doc(inline)] -pub use outputs::Layout; pub use outputs::editor::{PlainEditor, PlainEditorDriver}; - -pub use outputs::*; diff --git a/parley/src/outputs/alignment.rs b/parley/src/outputs/alignment.rs index 0e0e587c..97ff2bed 100644 --- a/parley/src/outputs/alignment.rs +++ b/parley/src/outputs/alignment.rs @@ -1,7 +1,7 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::inputs::style::Brush; +use crate::inputs::Brush; use crate::outputs::{BreakReason, LayoutData}; /// Alignment of a layout. diff --git a/parley/src/outputs/cluster.rs b/parley/src/outputs/cluster.rs index 6ce034b1..c5ee6247 100644 --- a/parley/src/outputs/cluster.rs +++ b/parley/src/outputs/cluster.rs @@ -515,9 +515,8 @@ impl Iterator for GlyphIter<'_> { #[cfg(test)] mod tests { - use crate::{ - Alignment, Cluster, FontContext, Layout, LayoutContext, PositionedLayoutItem, StyleProperty, - }; + use crate::inputs::{FontContext, LayoutContext, StyleProperty}; + use crate::outputs::{Alignment, Cluster, Layout, PositionedLayoutItem}; type Brush = (); diff --git a/parley/src/outputs/editor.rs b/parley/src/outputs/editor.rs index fe30b2cd..9ab1a3c2 100644 --- a/parley/src/outputs/editor.rs +++ b/parley/src/outputs/editor.rs @@ -4,10 +4,11 @@ //! A simple plain text editor and related types. use crate::algos::resolve::ResolvedStyle; -use crate::inputs::style::Brush; +use crate::inputs::Brush; +use crate::inputs::{FontContext, LayoutContext, StyleProperty, StyleSet}; use crate::outputs::cursor::{Cursor, Selection}; use crate::outputs::{Affinity, Alignment, Layout}; -use crate::{FontContext, LayoutContext, Rect, StyleProperty, StyleSet}; +use crate::Rect; use alloc::{borrow::ToOwned, string::String, vec::Vec}; diff --git a/parley/src/outputs/layout.rs b/parley/src/outputs/layout.rs index 087dc3d5..64b3cb4c 100644 --- a/parley/src/outputs/layout.rs +++ b/parley/src/outputs/layout.rs @@ -7,8 +7,8 @@ use swash::shape::Shaper; use swash::text::cluster::Boundary; use swash::Synthesis; -use crate::inputs::break_lines::BreakLines; -use crate::inputs::style::Brush; +use crate::inputs::BreakLines; +use crate::inputs::Brush; use crate::{Font, InlineBox}; use crate::outputs::align; diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs index 8052961c..323c36d6 100644 --- a/parley/src/outputs/mod.rs +++ b/parley/src/outputs/mod.rs @@ -20,7 +20,7 @@ pub use accessibility::LayoutAccessibility; pub(crate) use self::alignment::align; -use crate::inputs::style::Brush; +use crate::inputs::Brush; use swash::GlyphId; pub use self::{ diff --git a/parley/src/tests/test_basic.rs b/parley/src/tests/test_basic.rs index 8e779b62..15be65e6 100644 --- a/parley/src/tests/test_basic.rs +++ b/parley/src/tests/test_basic.rs @@ -1,7 +1,8 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{testenv, Alignment, InlineBox}; +use crate::outputs::Alignment; +use crate::{testenv, InlineBox}; #[test] fn plain_multiline_text() { diff --git a/parley/src/tests/test_cursor.rs b/parley/src/tests/test_cursor.rs index 8cccac43..78f48ae0 100644 --- a/parley/src/tests/test_cursor.rs +++ b/parley/src/tests/test_cursor.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::tests::utils::CursorTest; -use crate::{Cursor, FontContext, LayoutContext}; +use crate::{inputs::FontContext, inputs::LayoutContext, outputs::Cursor}; #[test] fn cursor_previous_visual() { diff --git a/parley/src/tests/utils/cursor_test.rs b/parley/src/tests/utils/cursor_test.rs index a7b1ced3..5b09a3e6 100644 --- a/parley/src/tests/utils/cursor_test.rs +++ b/parley/src/tests/utils/cursor_test.rs @@ -1,7 +1,8 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{Affinity, Cursor, FontContext, Layout, LayoutContext}; +use crate::inputs::{FontContext, LayoutContext}; +use crate::outputs::{Affinity, Cursor, Layout}; // Note: This module is only compiled when running tests, which requires std, // so we don't have to worry about being no_std-compatible. diff --git a/parley/src/tests/utils/env.rs b/parley/src/tests/utils/env.rs index 180130a8..59e3925a 100644 --- a/parley/src/tests/utils/env.rs +++ b/parley/src/tests/utils/env.rs @@ -1,11 +1,13 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::tests::utils::renderer::{render_layout, ColorBrush, RenderingConfig}; -use crate::{ - FontContext, FontFamily, FontStack, Layout, LayoutContext, PlainEditor, PlainEditorDriver, - RangedBuilder, Rect, StyleProperty, +use crate::inputs::{ + FontContext, FontFamily, FontStack, LayoutContext, RangedBuilder, StyleProperty, }; +use crate::outputs::editor::{PlainEditor, PlainEditorDriver}; +use crate::outputs::Layout; +use crate::tests::utils::renderer::{render_layout, ColorBrush, RenderingConfig}; +use crate::Rect; use fontique::{Collection, CollectionOptions}; use std::path::{Path, PathBuf}; use tiny_skia::{Color, Pixmap}; diff --git a/parley/src/tests/utils/renderer.rs b/parley/src/tests/utils/renderer.rs index 2169eb7b..b5232fee 100644 --- a/parley/src/tests/utils/renderer.rs +++ b/parley/src/tests/utils/renderer.rs @@ -7,7 +7,7 @@ //! Note: Emoji rendering is not currently implemented in this example. See the swash example //! if you need emoji rendering. -use crate::{GlyphRun, Layout, PositionedLayoutItem}; +use crate::outputs::{GlyphRun, Layout, PositionedLayoutItem}; use skrifa::{ instance::{LocationRef, NormalizedCoord, Size}, outline::{DrawSettings, OutlinePen}, From de18c768426c80264b44bd96392be8ec84fd58bb Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 19:50:19 +0100 Subject: [PATCH 11/14] Apply more consistent formatting to input statements --- examples/swash_render/src/main.rs | 10 ++++----- examples/vello_editor/src/access_ids.rs | 3 ++- examples/vello_editor/src/main.rs | 9 ++++---- parley/src/algos/resolve/mod.rs | 18 +++++++-------- parley/src/algos/resolve/range.rs | 2 +- parley/src/algos/resolve/tree.rs | 6 ++--- parley/src/algos/shape.rs | 19 ++++++++-------- parley/src/inputs/break_lines.rs | 9 +++----- parley/src/inputs/builder.rs | 12 +++++----- parley/src/inputs/context.rs | 15 +++++-------- parley/src/inputs/font.rs | 4 +--- parley/src/inputs/style/font.rs | 3 +-- parley/src/inputs/style/styleset.rs | 1 + parley/src/outputs/accessibility.rs | 3 ++- parley/src/outputs/cluster.rs | 6 ++--- parley/src/outputs/cursor.rs | 14 +++++++----- parley/src/outputs/editor.rs | 23 ++++++++++---------- parley/src/outputs/layout.rs | 10 ++++----- parley/src/outputs/line.rs | 4 +--- parley/src/outputs/mod.rs | 29 ++++++++++--------------- parley/src/outputs/run.rs | 1 + parley/src/tests/test_cursor.rs | 3 ++- parley/src/tests/utils/env.rs | 8 ++++--- parley/src/tests/utils/renderer.rs | 13 +++++------ 24 files changed, 106 insertions(+), 119 deletions(-) diff --git a/examples/swash_render/src/main.rs b/examples/swash_render/src/main.rs index 89bb515c..810889dd 100644 --- a/examples/swash_render/src/main.rs +++ b/examples/swash_render/src/main.rs @@ -10,16 +10,16 @@ reason = "Deferred" )] +use std::fs::File; + use image::codecs::png::PngEncoder; use image::{self, Pixel, Rgba, RgbaImage}; -use parley::inputs::{FontStack, FontWeight, StyleProperty, TextStyle}; +use parley::inputs::{FontContext, FontStack, FontWeight, LayoutContext, StyleProperty, TextStyle}; use parley::outputs::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem}; -use parley::{inputs::FontContext, inputs::LayoutContext, InlineBox}; -use std::fs::File; +use parley::InlineBox; use swash::scale::image::Content; use swash::scale::{Render, ScaleContext, Scaler, Source, StrikeWith}; -use swash::zeno; -use swash::FontRef; +use swash::{zeno, FontRef}; use zeno::{Format, Vector}; #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/examples/vello_editor/src/access_ids.rs b/examples/vello_editor/src/access_ids.rs index 60d694bd..2b8be600 100644 --- a/examples/vello_editor/src/access_ids.rs +++ b/examples/vello_editor/src/access_ids.rs @@ -1,9 +1,10 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use accesskit::NodeId; use core::sync::atomic::{AtomicU64, Ordering}; +use accesskit::NodeId; + pub const WINDOW_ID: NodeId = NodeId(0); pub const TEXT_INPUT_ID: NodeId = NodeId(1); diff --git a/examples/vello_editor/src/main.rs b/examples/vello_editor/src/main.rs index 6af33bb6..acf98faf 100644 --- a/examples/vello_editor/src/main.rs +++ b/examples/vello_editor/src/main.rs @@ -12,16 +12,15 @@ reason = "Deferred" )] +use std::num::NonZeroUsize; +use std::sync::Arc; + use accesskit::{Node, Role, Tree, TreeUpdate}; use anyhow::Result; use parley::outputs::editor::Generation; -use std::num::NonZeroUsize; -use std::sync::Arc; -use vello::kurbo; use vello::peniko::Color; use vello::util::{RenderContext, RenderSurface}; -use vello::wgpu; -use vello::{AaConfig, Renderer, RendererOptions, Scene}; +use vello::{kurbo, wgpu, AaConfig, Renderer, RendererOptions, Scene}; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event::{StartCause, WindowEvent}; diff --git a/parley/src/algos/resolve/mod.rs b/parley/src/algos/resolve/mod.rs index 1e8c9121..e4cdec3a 100644 --- a/parley/src/algos/resolve/mod.rs +++ b/parley/src/algos/resolve/mod.rs @@ -8,20 +8,20 @@ pub(crate) mod tree; pub(crate) use range::RangedStyleBuilder; -use alloc::{vec, vec::Vec}; +use alloc::vec; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::ops::Range; + +use fontique::FamilyId; +use swash::text::Language; -use crate::inputs::FontContext; -use crate::inputs::TextStyle; use crate::inputs::{ - Brush, FontFamily, FontFeature, FontSettings, FontStack, FontStyle, FontVariation, FontWeight, - FontWidth, StyleProperty, + Brush, FontContext, FontFamily, FontFeature, FontSettings, FontStack, FontStyle, FontVariation, + FontWeight, FontWidth, StyleProperty, TextStyle, }; use crate::outputs; use crate::util::nearly_eq; -use core::borrow::Borrow; -use core::ops::Range; -use fontique::FamilyId; -use swash::text::Language; /// Style with an associated range. #[derive(Debug, Clone)] diff --git a/parley/src/algos/resolve/range.rs b/parley/src/algos/resolve/range.rs index 658273d5..4e0f79f1 100644 --- a/parley/src/algos/resolve/range.rs +++ b/parley/src/algos/resolve/range.rs @@ -4,9 +4,9 @@ //! Range based style application. use alloc::vec; +use core::ops::{Bound, Range, RangeBounds}; use super::{Brush, RangedProperty, RangedStyle, ResolvedProperty, ResolvedStyle, Vec}; -use core::ops::{Bound, Range, RangeBounds}; /// Builder for constructing an ordered sequence of non-overlapping ranged /// styles from a collection of ranged style properties. diff --git a/parley/src/algos/resolve/tree.rs b/parley/src/algos/resolve/tree.rs index 151efbfe..7ba88b80 100644 --- a/parley/src/algos/resolve/tree.rs +++ b/parley/src/algos/resolve/tree.rs @@ -3,11 +3,11 @@ //! Hierarchical tree based style application. use alloc::borrow::Cow; -use alloc::{string::String, vec::Vec}; - -use crate::inputs::WhiteSpaceCollapse; +use alloc::string::String; +use alloc::vec::Vec; use super::{Brush, RangedStyle, ResolvedProperty, ResolvedStyle}; +use crate::inputs::WhiteSpaceCollapse; #[derive(Debug, Clone)] struct StyleTreeNode { diff --git a/parley/src/algos/shape.rs b/parley/src/algos/shape.rs index 719e5f56..c84bb8c6 100644 --- a/parley/src/algos/shape.rs +++ b/parley/src/algos/shape.rs @@ -1,22 +1,21 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::algos::resolve::{RangedStyle, ResolveContext, Resolved}; -use crate::algos::swash_convert::{locale_to_fontique, script_to_fontique, synthesis_to_swash}; -use crate::inputs::{Brush, FontFeature, FontVariation}; -use crate::outputs::Layout; -use crate::util::nearly_eq; -use crate::Font; -use fontique::QueryFamily; -use fontique::{self, Query, QueryFont}; +use alloc::vec::Vec; + +use fontique::{self, Query, QueryFamily, QueryFont}; use swash::shape::{partition, Direction, ShapeContext}; use swash::text::cluster::{CharCluster, CharInfo, Token}; use swash::text::{Language, Script}; use swash::{FontRef, Synthesis}; -use alloc::vec::Vec; - +use crate::algos::resolve::{RangedStyle, ResolveContext, Resolved}; +use crate::algos::swash_convert::{locale_to_fontique, script_to_fontique, synthesis_to_swash}; use crate::inline_box::InlineBox; +use crate::inputs::{Brush, FontFeature, FontVariation}; +use crate::outputs::Layout; +use crate::util::nearly_eq; +use crate::Font; struct Item { style_index: u16, diff --git a/parley/src/inputs/break_lines.rs b/parley/src/inputs/break_lines.rs index 1ac3d509..3073484c 100644 --- a/parley/src/inputs/break_lines.rs +++ b/parley/src/inputs/break_lines.rs @@ -4,23 +4,20 @@ //! Greedy line breaking. use alloc::vec::Vec; -use swash::text::cluster::Boundary; -use swash::text::cluster::Whitespace; +use core::ops::Range; #[cfg(feature = "libm")] #[allow(unused_imports)] use core_maths::CoreFloat; +use swash::text::cluster::{Boundary, Whitespace}; -use crate::inputs::Brush; -use crate::inputs::LineMetrics; +use crate::inputs::{Brush, LineMetrics}; use crate::outputs::alignment::unjustify; use crate::outputs::{ Alignment, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, Run, }; -use core::ops::Range; - #[derive(Default)] struct LineLayout { lines: Vec, diff --git a/parley/src/inputs/builder.rs b/parley/src/inputs/builder.rs index 8682ffb8..75a6f01b 100644 --- a/parley/src/inputs/builder.rs +++ b/parley/src/inputs/builder.rs @@ -3,17 +3,15 @@ //! Context for layout. -use crate::algos::shape::shape_text; -use crate::inputs::FontContext; -use crate::inputs::LayoutContext; -use crate::inputs::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; - -use crate::outputs::Layout; - use alloc::string::String; use core::ops::RangeBounds; +use crate::algos::shape::shape_text; use crate::inline_box::InlineBox; +use crate::inputs::{ + Brush, FontContext, LayoutContext, StyleProperty, TextStyle, WhiteSpaceCollapse, +}; +use crate::outputs::Layout; /// Builder for constructing a text layout with ranged attributes. pub struct RangedBuilder<'a, B: Brush> { diff --git a/parley/src/inputs/context.rs b/parley/src/inputs/context.rs index 0ca18257..01c63e03 100644 --- a/parley/src/inputs/context.rs +++ b/parley/src/inputs/context.rs @@ -3,20 +3,17 @@ //! Context for layout. -use alloc::{vec, vec::Vec}; - -use crate::algos::bidi; -use crate::algos::resolve::tree::TreeStyleBuilder; -use crate::algos::resolve::{RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; -use crate::inputs::FontContext; -use crate::inputs::RangedBuilder; -use crate::inputs::{Brush, TextStyle}; +use alloc::vec; +use alloc::vec::Vec; use swash::shape::ShapeContext; use swash::text::cluster::CharInfo; +use crate::algos::bidi; +use crate::algos::resolve::tree::TreeStyleBuilder; +use crate::algos::resolve::{RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; use crate::inline_box::InlineBox; -use crate::inputs::TreeBuilder; +use crate::inputs::{Brush, FontContext, RangedBuilder, TextStyle, TreeBuilder}; /// Shared scratch space used when constructing text layouts. /// diff --git a/parley/src/inputs/font.rs b/parley/src/inputs/font.rs index 02aba3f2..812a6c05 100644 --- a/parley/src/inputs/font.rs +++ b/parley/src/inputs/font.rs @@ -1,9 +1,7 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use fontique::Collection; - -use fontique::SourceCache; +use fontique::{Collection, SourceCache}; /// A font database/cache (wrapper around a Fontique [`Collection`] and [`SourceCache`]). /// diff --git a/parley/src/inputs/style/font.rs b/parley/src/inputs/style/font.rs index 3bac15b1..6e34259e 100644 --- a/parley/src/inputs/style/font.rs +++ b/parley/src/inputs/style/font.rs @@ -1,8 +1,7 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use alloc::borrow::Cow; -use alloc::borrow::ToOwned; +use alloc::borrow::{Cow, ToOwned}; use core::fmt; pub use fontique::{FontStyle, FontWeight, FontWidth, GenericFamily}; diff --git a/parley/src/inputs/style/styleset.rs b/parley/src/inputs/style/styleset.rs index ce4e97f5..a4387d88 100644 --- a/parley/src/inputs/style/styleset.rs +++ b/parley/src/inputs/style/styleset.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use core::mem::Discriminant; + use hashbrown::HashMap; use crate::inputs::Brush; diff --git a/parley/src/outputs/accessibility.rs b/parley/src/outputs/accessibility.rs index f9d40073..d87c1a09 100644 --- a/parley/src/outputs/accessibility.rs +++ b/parley/src/outputs/accessibility.rs @@ -1,8 +1,9 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; use alloc::vec::Vec; + +use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; use hashbrown::{HashMap, HashSet}; use crate::inputs::Brush; diff --git a/parley/src/outputs/cluster.rs b/parley/src/outputs/cluster.rs index c5ee6247..b204dc22 100644 --- a/parley/src/outputs/cluster.rs +++ b/parley/src/outputs/cluster.rs @@ -1,12 +1,12 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::outputs::RunData; -use crate::outputs::{BreakReason, Brush, Glyph, Layout, Line, Run}; - use core::ops::Range; + use swash::text::cluster::{ClusterInfo, Whitespace}; +use crate::outputs::{BreakReason, Brush, Glyph, Layout, Line, Run, RunData}; + /// Atomic unit of text. #[derive(Copy, Clone)] pub struct Cluster<'a, B: Brush> { diff --git a/parley/src/outputs/cursor.rs b/parley/src/outputs/cursor.rs index 110319a7..7ae40f10 100644 --- a/parley/src/outputs/cursor.rs +++ b/parley/src/outputs/cursor.rs @@ -3,17 +3,21 @@ //! Text selection support. -#[cfg(feature = "accesskit")] -use crate::outputs::LayoutAccessibility; -use crate::outputs::{Affinity, BreakReason, Brush, Cluster, ClusterSide, Layout, Line}; -#[cfg(feature = "accesskit")] -use accesskit::TextPosition; use alloc::vec::Vec; use core::ops::Range; + use peniko::kurbo::Rect; + +#[cfg(feature = "accesskit")] +use accesskit::TextPosition; #[cfg(feature = "accesskit")] use swash::text::cluster::Whitespace; +use crate::outputs::{Affinity, BreakReason, Brush, Cluster, ClusterSide, Layout, Line}; + +#[cfg(feature = "accesskit")] +use crate::outputs::LayoutAccessibility; + /// Defines a position with a text layout. #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct Cursor { diff --git a/parley/src/outputs/editor.rs b/parley/src/outputs/editor.rs index 9ab1a3c2..1ba46af2 100644 --- a/parley/src/outputs/editor.rs +++ b/parley/src/outputs/editor.rs @@ -3,25 +3,26 @@ //! A simple plain text editor and related types. -use crate::algos::resolve::ResolvedStyle; -use crate::inputs::Brush; -use crate::inputs::{FontContext, LayoutContext, StyleProperty, StyleSet}; -use crate::outputs::cursor::{Cursor, Selection}; -use crate::outputs::{Affinity, Alignment, Layout}; -use crate::Rect; - -use alloc::{borrow::ToOwned, string::String, vec::Vec}; - +use alloc::borrow::ToOwned; +use alloc::string::String; +use alloc::vec::Vec; use core::cmp::PartialEq; use core::default::Default; use core::fmt::{Debug, Display}; use core::ops::Range; -#[cfg(feature = "accesskit")] -use crate::outputs::LayoutAccessibility; #[cfg(feature = "accesskit")] use accesskit::{Node, NodeId, TreeUpdate}; +use crate::algos::resolve::ResolvedStyle; +use crate::inputs::{Brush, FontContext, LayoutContext, StyleProperty, StyleSet}; +use crate::outputs::cursor::{Cursor, Selection}; +use crate::outputs::{Affinity, Alignment, Layout}; +use crate::Rect; + +#[cfg(feature = "accesskit")] +use crate::outputs::LayoutAccessibility; + /// Opaque representation of a generation. /// /// Obtained from [`PlainEditor::generation`]. diff --git a/parley/src/outputs/layout.rs b/parley/src/outputs/layout.rs index 64b3cb4c..9dad32c6 100644 --- a/parley/src/outputs/layout.rs +++ b/parley/src/outputs/layout.rs @@ -3,21 +3,19 @@ use alloc::vec::Vec; use core::cmp::Ordering; + use swash::shape::Shaper; use swash::text::cluster::Boundary; use swash::Synthesis; -use crate::inputs::BreakLines; -use crate::inputs::Brush; -use crate::{Font, InlineBox}; - -use crate::outputs::align; +use crate::inputs::{BreakLines, Brush}; use crate::outputs::run::RunMetrics; use crate::outputs::{ - Alignment, ClusterData, Glyph, LayoutItem, LayoutItemKind, Line, LineData, LineItemData, + align, Alignment, ClusterData, Glyph, LayoutItem, LayoutItemKind, Line, LineData, LineItemData, RunData, Style, }; use crate::util::nearly_zero; +use crate::{Font, InlineBox}; /// Text layout. #[derive(Clone)] diff --git a/parley/src/outputs/line.rs b/parley/src/outputs/line.rs index 6b0b8f87..3f0b9514 100644 --- a/parley/src/outputs/line.rs +++ b/parley/src/outputs/line.rs @@ -4,9 +4,7 @@ use core::ops::Range; use crate::inputs::LineMetrics; - -use crate::outputs::{Alignment, LayoutData}; -use crate::outputs::{BreakReason, Brush, Glyph, Layout, Run, Style}; +use crate::outputs::{Alignment, BreakReason, Brush, Glyph, Layout, LayoutData, Run, Style}; /// Line in a text layout. #[derive(Copy, Clone)] diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs index 323c36d6..5a3f11ea 100644 --- a/parley/src/outputs/mod.rs +++ b/parley/src/outputs/mod.rs @@ -18,29 +18,22 @@ mod accessibility; #[cfg(feature = "accesskit")] pub use accessibility::LayoutAccessibility; +pub use self::alignment::Alignment; +pub use self::cluster::{Affinity, Cluster, ClusterPath, ClusterSide}; +pub use self::cursor::{Cursor, Selection}; +pub use self::layout::Layout; +pub use self::line::{GlyphRun, Line, PositionedInlineBox, PositionedLayoutItem}; +pub use self::run::{Run, RunMetrics}; + pub(crate) use self::alignment::align; +pub(crate) use self::cluster::ClusterData; +pub(crate) use self::layout::LayoutData; +pub(crate) use self::line::{LayoutItem, LayoutItemKind, LineData, LineItemData}; +pub(crate) use self::run::RunData; use crate::inputs::Brush; use swash::GlyphId; -pub use self::{ - alignment::Alignment, - cluster::Cluster, - cluster::{Affinity, ClusterPath, ClusterSide}, - cursor::{Cursor, Selection}, - layout::Layout, - line::Line, - line::{GlyphRun, PositionedInlineBox, PositionedLayoutItem}, - run::Run, - run::RunMetrics, -}; -pub(crate) use self::{ - cluster::ClusterData, - layout::LayoutData, - line::{LayoutItem, LayoutItemKind, LineData, LineItemData}, - run::RunData, -}; - #[derive(Copy, Clone, Default, PartialEq, Debug)] pub enum BreakReason { #[default] diff --git a/parley/src/outputs/run.rs b/parley/src/outputs/run.rs index 29897ffb..149647e1 100644 --- a/parley/src/outputs/run.rs +++ b/parley/src/outputs/run.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use core::ops::Range; + use peniko::Font; use swash::{NormalizedCoord, Synthesis}; diff --git a/parley/src/tests/test_cursor.rs b/parley/src/tests/test_cursor.rs index 78f48ae0..fff4c2bd 100644 --- a/parley/src/tests/test_cursor.rs +++ b/parley/src/tests/test_cursor.rs @@ -1,8 +1,9 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::inputs::{FontContext, LayoutContext}; +use crate::outputs::Cursor; use crate::tests::utils::CursorTest; -use crate::{inputs::FontContext, inputs::LayoutContext, outputs::Cursor}; #[test] fn cursor_previous_visual() { diff --git a/parley/src/tests/utils/env.rs b/parley/src/tests/utils/env.rs index 59e3925a..21e200d0 100644 --- a/parley/src/tests/utils/env.rs +++ b/parley/src/tests/utils/env.rs @@ -1,6 +1,11 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use std::path::{Path, PathBuf}; + +use fontique::{Collection, CollectionOptions}; +use tiny_skia::{Color, Pixmap}; + use crate::inputs::{ FontContext, FontFamily, FontStack, LayoutContext, RangedBuilder, StyleProperty, }; @@ -8,9 +13,6 @@ use crate::outputs::editor::{PlainEditor, PlainEditorDriver}; use crate::outputs::Layout; use crate::tests::utils::renderer::{render_layout, ColorBrush, RenderingConfig}; use crate::Rect; -use fontique::{Collection, CollectionOptions}; -use std::path::{Path, PathBuf}; -use tiny_skia::{Color, Pixmap}; // Creates a new instance of TestEnv and put current function name in constructor #[macro_export] diff --git a/parley/src/tests/utils/renderer.rs b/parley/src/tests/utils/renderer.rs index b5232fee..7e9c5df3 100644 --- a/parley/src/tests/utils/renderer.rs +++ b/parley/src/tests/utils/renderer.rs @@ -7,15 +7,14 @@ //! Note: Emoji rendering is not currently implemented in this example. See the swash example //! if you need emoji rendering. -use crate::outputs::{GlyphRun, Layout, PositionedLayoutItem}; -use skrifa::{ - instance::{LocationRef, NormalizedCoord, Size}, - outline::{DrawSettings, OutlinePen}, - raw::FontRef as ReadFontsRef, - GlyphId, MetadataProvider, OutlineGlyph, -}; +use skrifa::instance::{LocationRef, NormalizedCoord, Size}; +use skrifa::outline::{DrawSettings, OutlinePen}; +use skrifa::raw::FontRef as ReadFontsRef; +use skrifa::{GlyphId, MetadataProvider, OutlineGlyph}; use tiny_skia::{Color, FillRule, Paint, PathBuilder, Pixmap, PixmapMut, Rect, Transform}; +use crate::outputs::{GlyphRun, Layout, PositionedLayoutItem}; + #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct ColorBrush { pub(crate) color: Color, From 5f893aa4c5367ed69eeef2ba61f24b098297a8c2 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 19:56:11 +0100 Subject: [PATCH 12/14] Fix doctest errors --- parley/src/inputs/style/font.rs | 8 ++++---- parley/src/lib.rs | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/parley/src/inputs/style/font.rs b/parley/src/inputs/style/font.rs index 6e34259e..df4cc192 100644 --- a/parley/src/inputs/style/font.rs +++ b/parley/src/inputs/style/font.rs @@ -43,8 +43,8 @@ impl<'a> FontFamily<'a> { /// ``` /// # extern crate alloc; /// use alloc::borrow::Cow; - /// use parley::inputs::style::FontFamily::{self, *}; - /// use parley::inputs::style::GenericFamily::*; + /// use parley::inputs::FontFamily::{self, *}; + /// use parley::inputs::GenericFamily::*; /// /// assert_eq!(FontFamily::parse("Palatino Linotype"), Some(Named(Cow::Borrowed("Palatino Linotype")))); /// assert_eq!(FontFamily::parse("monospace"), Some(Generic(Monospace))); @@ -63,8 +63,8 @@ impl<'a> FontFamily<'a> { /// ``` /// # extern crate alloc; /// use alloc::borrow::Cow; - /// use parley::inputs::style::FontFamily::{self, *}; - /// use parley::inputs::style::GenericFamily::*; + /// use parley::inputs::FontFamily::{self, *}; + /// use parley::inputs::GenericFamily::*; /// /// let source = "Arial, 'Times New Roman', serif"; /// diff --git a/parley/src/lib.rs b/parley/src/lib.rs index bdabf3cb..804bc1cb 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -22,10 +22,9 @@ //! See the [examples](https://github.com/linebender/parley/tree/main/examples) directory for more complete usage examples that include rendering. //! //! ```rust -//! use parley::{ -//! Alignment, FontContext, FontWeight, InlineBox, Layout, LayoutContext, PositionedLayoutItem, -//! StyleProperty, -//! }; +//! use parley::InlineBox; +//! use parley::inputs::{FontContext, FontWeight, LayoutContext, StyleProperty}; +//! use parley::outputs::{Alignment, Layout, PositionedLayoutItem}; //! //! // Create a FontContext (font database) and LayoutContext (scratch space). //! // These are both intended to be constructed rarely (perhaps even once per app): From da5548290512ced8441e18caecf8bfef86af1959 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 20:01:59 +0100 Subject: [PATCH 13/14] Fix doc links --- parley/src/inputs/style/styleset.rs | 12 ++++++------ parley/src/lib.rs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/parley/src/inputs/style/styleset.rs b/parley/src/inputs/style/styleset.rs index a4387d88..6d68bdcd 100644 --- a/parley/src/inputs/style/styleset.rs +++ b/parley/src/inputs/style/styleset.rs @@ -12,9 +12,9 @@ type StyleProperty = crate::inputs::StyleProperty<'static, Brush>; /// A long-lived collection of [`StyleProperties`](super::StyleProperty), containing at /// most one of each property. /// -/// This is used by [`PlainEditor`](crate::editor::PlainEditor) to provide a reasonably ergonomic +/// This is used by [`PlainEditor`](crate::outputs::editor::PlainEditor) to provide a reasonably ergonomic /// mutable API for styles applied to all text managed by it. -/// This can be accessed using [`PlainEditor::edit_styles`](crate::editor::PlainEditor::edit_styles). +/// This can be accessed using [`PlainEditor::edit_styles`](crate::outputs::editor::PlainEditor::edit_styles). /// /// These styles do not have a corresponding range, and are generally unsuited for rich text. #[derive(Clone, Debug)] @@ -24,7 +24,7 @@ impl StyleSet { /// Create a new collection of styles. /// /// The font size will be `font_size`, and can be overwritten at runtime by - /// [inserting](Self::insert) a new [`FontSize`](crate::StyleProperty::FontSize). + /// [inserting](Self::insert) a new [`FontSize`](crate::inputs::StyleProperty::FontSize). pub fn new(font_size: f32) -> Self { let mut this = Self(Default::default()); this.insert(StyleProperty::FontSize(font_size)); @@ -33,7 +33,7 @@ impl StyleSet { /// Add `style` to this collection, returning any overwritten value. /// - /// Note: Adding a [font stack](crate::StyleProperty::FontStack) to this collection is not + /// Note: Adding a [font stack](crate::inputs::StyleProperty::FontStack) to this collection is not /// additive, and instead overwrites any previously added font stack. pub fn insert(&mut self, style: StyleProperty) -> Option> { let discriminant = core::mem::discriminant(&style); @@ -44,7 +44,7 @@ impl StyleSet { /// /// Styles which are removed return to their default values. /// - /// Removing the [font size](crate::StyleProperty::FontSize) is not recommended, as an unspecified + /// Removing the [font size](crate::inputs::StyleProperty::FontSize) is not recommended, as an unspecified /// fallback font size will be used. pub fn retain(&mut self, mut f: impl FnMut(&StyleProperty) -> bool) { self.0.retain(|_, v| f(v)); @@ -58,7 +58,7 @@ impl StyleSet { /// the desired property and passing it to [`core::mem::discriminant`]. /// Getting this discriminant is usually possible in a `const` context. /// - /// Removing the [font size](crate::StyleProperty::FontSize) is not recommended, as an unspecified + /// Removing the [font size](crate::inputs::StyleProperty::FontSize) is not recommended, as an unspecified /// fallback font size will be used. pub fn remove(&mut self, property: Discriminant>) -> Option> { self.0.remove(&property) diff --git a/parley/src/lib.rs b/parley/src/lib.rs index 804bc1cb..d043245e 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -4,15 +4,15 @@ //! Parley is a library for rich text layout. //! //! Some key types are: -//! - [`FontContext`] and [`LayoutContext`] are resources which should be shared globally (or at coarse-grained boundaries). -//! - [`FontContext`] is database of fonts. -//! - [`LayoutContext`] is scratch space that allows for reuse of allocations between layouts. -//! - [`RangedBuilder`] and [`TreeBuilder`] which are builders for creating a [`Layout`]. -//! - [`RangedBuilder`] allows styles to be specified as a flat `Vec` of spans -//! - [`TreeBuilder`] allows styles to be specified as a tree of spans +//! - [`FontContext`](crate::inputs::FontContext) and [`LayoutContext`](crate::inputs::LayoutContext) are resources which should be shared globally (or at coarse-grained boundaries). +//! - [`FontContext`](crate::inputs::FontContext) is database of fonts. +//! - [`LayoutContext`](crate::inputs::LayoutContext) is scratch space that allows for reuse of allocations between layouts. +//! - [`RangedBuilder`](crate::inputs::RangedBuilder) and [`TreeBuilder`](crate::inputs::TreeBuilder) which are builders for creating a [`Layout`](crate::outputs::Layout). +//! - [`RangedBuilder`](crate::inputs::RangedBuilder) allows styles to be specified as a flat `Vec` of spans +//! - [`TreeBuilder`](crate::inputs::TreeBuilder) allows styles to be specified as a tree of spans //! -//! They are constructed using the [`ranged_builder`](LayoutContext::ranged_builder) and [`tree_builder`](LayoutContext::ranged_builder) methods on [`LayoutContext`]. -//! - [`Layout`] which represents styled paragraph(s) of text and can perform shaping, line-breaking, bidi-reordering, and alignment of that text. +//! They are constructed using the [`ranged_builder`](crate::inputs::LayoutContext::ranged_builder) and [`tree_builder`](crate::inputs::LayoutContext::tree_builder) methods on [`LayoutContext`](crate::inputs::LayoutContext). +//! - [`Layout`](crate::outputs::Layout) which represents styled paragraph(s) of text and can perform shaping, line-breaking, bidi-reordering, and alignment of that text. //! //! `Layout` supports re-linebreaking and re-aligning many times (in case the width at which wrapping should occur changes). But if the text content or //! the styles applied to that content change then a new `Layout` must be created using a new `RangedBuilder` or `TreeBuilder`. From 80650d3c81167335e58f61ebb6854f67913c8e65 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Sun, 22 Dec 2024 20:13:09 +0100 Subject: [PATCH 14/14] Split off text-editing into separate module --- examples/vello_editor/src/main.rs | 2 +- examples/vello_editor/src/text.rs | 2 +- parley/src/editing/cursor.rs | 462 +++++++++++++++++ parley/src/{outputs => editing}/editor.rs | 2 +- parley/src/editing/mod.rs | 10 + .../cursor.rs => editing/selection.rs} | 470 +----------------- parley/src/inputs/style/styleset.rs | 4 +- parley/src/lib.rs | 3 +- parley/src/outputs/mod.rs | 4 - parley/src/tests/test_cursor.rs | 2 +- parley/src/tests/utils/cursor_test.rs | 3 +- parley/src/tests/utils/env.rs | 2 +- 12 files changed, 492 insertions(+), 474 deletions(-) create mode 100644 parley/src/editing/cursor.rs rename parley/src/{outputs => editing}/editor.rs (99%) create mode 100644 parley/src/editing/mod.rs rename parley/src/{outputs/cursor.rs => editing/selection.rs} (52%) diff --git a/examples/vello_editor/src/main.rs b/examples/vello_editor/src/main.rs index acf98faf..4c02d41b 100644 --- a/examples/vello_editor/src/main.rs +++ b/examples/vello_editor/src/main.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use accesskit::{Node, Role, Tree, TreeUpdate}; use anyhow::Result; -use parley::outputs::editor::Generation; +use parley::editing::Generation; use vello::peniko::Color; use vello::util::{RenderContext, RenderSurface}; use vello::{kurbo, wgpu, AaConfig, Renderer, RendererOptions, Scene}; diff --git a/examples/vello_editor/src/text.rs b/examples/vello_editor/src/text.rs index cfc6a9f7..2b791dc2 100644 --- a/examples/vello_editor/src/text.rs +++ b/examples/vello_editor/src/text.rs @@ -15,8 +15,8 @@ use winit::{ keyboard::{Key, NamedKey}, }; +use parley::editing::{Generation, PlainEditor, PlainEditorDriver, SplitString}; use parley::inputs::{FontContext, GenericFamily, LayoutContext, StyleProperty}; -use parley::outputs::editor::{Generation, PlainEditor, PlainEditorDriver, SplitString}; use parley::outputs::PositionedLayoutItem; use crate::access_ids::next_node_id; diff --git a/parley/src/editing/cursor.rs b/parley/src/editing/cursor.rs new file mode 100644 index 00000000..37de10a8 --- /dev/null +++ b/parley/src/editing/cursor.rs @@ -0,0 +1,462 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Text selection support. + +use peniko::kurbo::Rect; + +#[cfg(feature = "accesskit")] +use accesskit::TextPosition; +#[cfg(feature = "accesskit")] +use swash::text::cluster::Whitespace; + +use crate::inputs::Brush; +use crate::outputs::{Affinity, BreakReason, Cluster, ClusterSide, Layout, Line}; + +#[cfg(feature = "accesskit")] +use crate::outputs::LayoutAccessibility; + +/// Defines a position with a text layout. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub struct Cursor { + pub(crate) index: usize, + pub(crate) affinity: Affinity, +} + +impl Cursor { + /// Creates a new cursor from the given byte index and affinity. + pub fn from_byte_index(layout: &Layout, index: usize, affinity: Affinity) -> Self { + if let Some(cluster) = Cluster::from_byte_index(layout, index) { + let index = cluster.text_range().start; + let affinity = if cluster.is_line_break() == Some(BreakReason::Explicit) { + Affinity::Downstream + } else { + affinity + }; + Self { index, affinity } + } else { + Self { + index: layout.data.text_len, + affinity: Affinity::Upstream, + } + } + } + + /// Creates a new cursor from the given coordinates. + pub fn from_point(layout: &Layout, x: f32, y: f32) -> Self { + let (index, affinity) = if let Some((cluster, side)) = Cluster::from_point(layout, x, y) { + let is_leading = side == ClusterSide::Left; + if cluster.is_rtl() { + if is_leading { + (cluster.text_range().end, Affinity::Upstream) + } else { + (cluster.text_range().start, Affinity::Downstream) + } + } else { + // We never want to position the cursor _after_ a hard + // line since that cursor appears visually at the start + // of the next line + if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) { + (cluster.text_range().start, Affinity::Downstream) + } else { + (cluster.text_range().end, Affinity::Upstream) + } + } + } else { + (layout.data.text_len, Affinity::Downstream) + }; + Self { index, affinity } + } + + #[cfg(feature = "accesskit")] + pub fn from_access_position( + pos: &TextPosition, + layout: &Layout, + layout_access: &LayoutAccessibility, + ) -> Option { + let (line_index, run_index) = *layout_access.run_paths_by_access_id.get(&pos.node)?; + let line = layout.get(line_index)?; + let run = line.run(run_index)?; + let index = run + .get(pos.character_index) + .map(|cluster| cluster.text_range().start) + .unwrap_or(layout.data.text_len); + Some(Self::from_byte_index(layout, index, Affinity::Downstream)) + } + + pub(crate) fn from_cluster( + layout: &Layout, + cluster: Cluster<'_, B>, + moving_right: bool, + ) -> Self { + Self::from_byte_index( + layout, + cluster.text_range().start, + affinity_for_dir(cluster.is_rtl(), moving_right), + ) + } + + /// Returns the logical text index of the cursor. + pub fn index(&self) -> usize { + self.index + } + + /// Returns the affinity of the cursor. + /// + /// This defines the direction from which the cursor entered its current + /// position and affects the visual location of the rendered cursor. + pub fn affinity(&self) -> Affinity { + self.affinity + } + + /// Returns a new cursor that is guaranteed to be within the bounds of the + /// given layout. + #[must_use] + pub fn refresh(&self, layout: &Layout) -> Self { + Self::from_byte_index(layout, self.index, self.affinity) + } + + /// Returns a new cursor that is positioned at the previous cluster boundary + /// in visual order. + #[must_use] + pub fn previous_visual(&self, layout: &Layout) -> Self { + let [left, right] = self.visual_clusters(layout); + if let (Some(left), Some(right)) = (&left, &right) { + if left.is_soft_line_break() { + if left.is_rtl() && self.affinity == Affinity::Upstream { + let index = if right.is_rtl() { + left.text_range().start + } else { + left.text_range().end + }; + return Self::from_byte_index(layout, index, Affinity::Downstream); + } else if !left.is_rtl() && self.affinity == Affinity::Downstream { + let index = if right.is_rtl() { + right.text_range().end + } else { + right.text_range().start + }; + return Self::from_byte_index(layout, index, Affinity::Upstream); + } + } + } + if let Some(left) = left { + let index = if left.is_rtl() { + left.text_range().end + } else { + left.text_range().start + }; + return Self::from_byte_index(layout, index, affinity_for_dir(left.is_rtl(), false)); + } + *self + } + + /// Returns a new cursor that is positioned at the next cluster boundary + /// in visual order. + #[must_use] + pub fn next_visual(&self, layout: &Layout) -> Self { + let [left, right] = self.visual_clusters(layout); + if let (Some(left), Some(right)) = (&left, &right) { + if left.is_soft_line_break() { + if left.is_rtl() && self.affinity == Affinity::Downstream { + let index = if right.is_rtl() { + right.text_range().end + } else { + right.text_range().start + }; + return Self::from_byte_index(layout, index, Affinity::Upstream); + } else if !left.is_rtl() && self.affinity == Affinity::Upstream { + let index = if right.is_rtl() { + right.text_range().end + } else { + right.text_range().start + }; + return Self::from_byte_index(layout, index, Affinity::Downstream); + } + } + let index = if right.is_rtl() { + right.text_range().start + } else { + right.text_range().end + }; + return Self::from_byte_index(layout, index, affinity_for_dir(right.is_rtl(), true)); + } + if let Some(right) = right { + let index = if right.is_rtl() { + right.text_range().start + } else { + right.text_range().end + }; + return Self::from_byte_index(layout, index, affinity_for_dir(right.is_rtl(), true)); + } + *self + } + + /// Returns a new cursor that is positioned at the next word boundary + /// in visual order. + #[must_use] + pub fn next_visual_word(&self, layout: &Layout) -> Self { + let mut cur = *self; + loop { + let next = cur.next_visual(layout); + if next == cur { + break; + } + cur = next; + let [Some(left), Some(right)] = cur.visual_clusters(layout) else { + break; + }; + if left.is_rtl() { + if left.is_word_boundary() && !left.is_space_or_nbsp() { + break; + } + } else if right.is_word_boundary() && !left.is_space_or_nbsp() { + break; + } + } + cur + } + + /// Returns a new cursor that is positioned at the previous word boundary + /// in visual order. + #[must_use] + pub fn previous_visual_word(&self, layout: &Layout) -> Self { + let mut cur = *self; + loop { + let next = cur.previous_visual(layout); + if next == cur { + break; + } + cur = next; + let [Some(left), Some(right)] = cur.visual_clusters(layout) else { + break; + }; + if left.is_rtl() { + if left.is_word_boundary() + && (left.is_space_or_nbsp() + || (right.is_word_boundary() && !right.is_space_or_nbsp())) + { + break; + } + } else if right.is_word_boundary() && !right.is_space_or_nbsp() { + break; + } + } + cur + } + + /// Returns a new cursor that is positioned at the next word boundary + /// in logical order. + #[must_use] + pub fn next_logical_word(&self, layout: &Layout) -> Self { + let [left, right] = self.logical_clusters(layout); + if let Some(cluster) = right.or(left) { + let start = cluster.clone(); + let cluster = cluster.next_logical_word().unwrap_or(cluster); + if cluster.path == start.path { + return Self::from_byte_index(layout, usize::MAX, Affinity::Downstream); + } + return Self::from_cluster(layout, cluster, true); + } + *self + } + + /// Returns a new cursor that is positioned at the previous word boundary + /// in logical order. + #[must_use] + pub fn previous_logical_word(&self, layout: &Layout) -> Self { + let [left, right] = self.logical_clusters(layout); + if let Some(cluster) = left.or(right) { + let cluster = cluster.previous_logical_word().unwrap_or(cluster); + return Self::from_cluster(layout, cluster, true); + } + *self + } + + /// Returns a rectangle that represents the visual geometry of the cursor + /// in layout space. + /// + /// The `width` parameter defines the width of the resulting rectangle. + pub fn geometry(&self, layout: &Layout, width: f32) -> Rect { + match self.visual_clusters(layout) { + [Some(left), Some(right)] => { + if left.is_end_of_line() { + if left.is_soft_line_break() { + let (cluster, at_end) = if left.is_rtl() + && self.affinity == Affinity::Downstream + || !left.is_rtl() && self.affinity == Affinity::Upstream + { + (left, true) + } else { + (right, false) + }; + cursor_rect(&cluster, at_end, width) + } else { + cursor_rect(&right, false, width) + } + } else { + cursor_rect(&left, true, width) + } + } + [Some(left), None] if left.is_hard_line_break() => last_line_cursor_rect(layout, width), + [Some(left), _] => cursor_rect(&left, true, width), + [_, Some(right)] => cursor_rect(&right, false, width), + _ => last_line_cursor_rect(layout, width), + } + } + + /// Returns the pair of clusters that logically bound the cursor + /// position. + /// + /// The order in the array is upstream followed by downstream. + pub fn logical_clusters<'a, B: Brush>( + &self, + layout: &'a Layout, + ) -> [Option>; 2] { + let upstream = self + .index + .checked_sub(1) + .and_then(|index| Cluster::from_byte_index(layout, index)); + let downstream = Cluster::from_byte_index(layout, self.index); + [upstream, downstream] + } + + /// Returns the pair of clusters that visually bound the cursor + /// position. + /// + /// The order in the array is left followed by right. + pub fn visual_clusters<'a, B: Brush>( + &self, + layout: &'a Layout, + ) -> [Option>; 2] { + if self.affinity == Affinity::Upstream { + if let Some(cluster) = self.upstream_cluster(layout) { + if cluster.is_rtl() { + [cluster.previous_visual(), Some(cluster)] + } else { + [Some(cluster.clone()), cluster.next_visual()] + } + } else if let Some(cluster) = self.downstream_cluster(layout) { + if cluster.is_rtl() { + [None, Some(cluster)] + } else { + [Some(cluster), None] + } + } else { + [None, None] + } + } else if let Some(cluster) = self.downstream_cluster(layout) { + if cluster.is_rtl() { + [Some(cluster.clone()), cluster.next_visual()] + } else { + [cluster.previous_visual(), Some(cluster)] + } + } else if let Some(cluster) = self.upstream_cluster(layout) { + if cluster.is_rtl() { + [None, Some(cluster)] + } else { + [Some(cluster), None] + } + } else { + [None, None] + } + } + + pub(crate) fn line(self, layout: &Layout) -> Option<(usize, Line<'_, B>)> { + let geometry = self.geometry(layout, 0.0); + layout.line_for_offset(geometry.y0 as f32) + } + + fn upstream_cluster(self, layout: &Layout) -> Option> { + self.index + .checked_sub(1) + .and_then(|index| Cluster::from_byte_index(layout, index)) + } + + fn downstream_cluster(self, layout: &Layout) -> Option> { + Cluster::from_byte_index(layout, self.index) + } + + #[cfg(feature = "accesskit")] + pub fn to_access_position( + &self, + layout: &Layout, + layout_access: &LayoutAccessibility, + ) -> Option { + if layout.data.text_len == 0 { + // If the text is empty, just return the first node with a + // character index of 0. + return Some(TextPosition { + node: *layout_access.access_ids_by_run_path.get(&(0, 0))?, + character_index: 0, + }); + } + // Prefer the downstream cluster except at the end of the text + // where we'll choose the upstream cluster and add 1 to the + // character index. + let (offset, path) = self + .downstream_cluster(layout) + .map(|cluster| (0, cluster.path)) + .or_else(|| { + self.upstream_cluster(layout) + .map(|cluster| (1, cluster.path)) + })?; + // If we're at the end of the layout and the layout ends with a newline + // then make sure we use the "phantom" run at the end so that + // AccessKit has correct visual geometry for the cursor. + let (run_path, character_index) = if self.index == layout.data.text_len + && layout + .data + .clusters + .last() + .map(|cluster| cluster.info.whitespace() == Whitespace::Newline) + .unwrap_or_default() + { + ((path.line_index() + 1, 0), 0) + } else { + ( + (path.line_index(), path.run_index()), + path.logical_index() + offset, + ) + }; + let id = layout_access.access_ids_by_run_path.get(&run_path)?; + Some(TextPosition { + node: *id, + character_index, + }) + } +} + +fn cursor_rect(cluster: &Cluster<'_, B>, at_end: bool, size: f32) -> Rect { + let line_x = (cluster.visual_offset().unwrap_or_default() + + at_end.then(|| cluster.advance()).unwrap_or_default()) as f64; + let line = cluster.line(); + let metrics = line.metrics(); + Rect::new( + line_x, + metrics.min_coord as f64, + line_x + size as f64, + metrics.max_coord as f64, + ) +} + +fn last_line_cursor_rect(layout: &Layout, size: f32) -> Rect { + if let Some(line) = layout.get(layout.len().saturating_sub(1)) { + let metrics = line.metrics(); + Rect::new( + 0.0, + metrics.min_coord as f64, + size as f64, + metrics.max_coord as f64, + ) + } else { + Rect::default() + } +} + +fn affinity_for_dir(is_rtl: bool, moving_right: bool) -> Affinity { + match (is_rtl, moving_right) { + (true, true) | (false, false) => Affinity::Downstream, + _ => Affinity::Upstream, + } +} diff --git a/parley/src/outputs/editor.rs b/parley/src/editing/editor.rs similarity index 99% rename from parley/src/outputs/editor.rs rename to parley/src/editing/editor.rs index 1ba46af2..7db91e57 100644 --- a/parley/src/outputs/editor.rs +++ b/parley/src/editing/editor.rs @@ -15,8 +15,8 @@ use core::ops::Range; use accesskit::{Node, NodeId, TreeUpdate}; use crate::algos::resolve::ResolvedStyle; +use crate::editing::{Cursor, Selection}; use crate::inputs::{Brush, FontContext, LayoutContext, StyleProperty, StyleSet}; -use crate::outputs::cursor::{Cursor, Selection}; use crate::outputs::{Affinity, Alignment, Layout}; use crate::Rect; diff --git a/parley/src/editing/mod.rs b/parley/src/editing/mod.rs new file mode 100644 index 00000000..5b473917 --- /dev/null +++ b/parley/src/editing/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +mod cursor; +mod editor; +mod selection; + +pub use self::cursor::*; +pub use self::editor::*; +pub use self::selection::*; diff --git a/parley/src/outputs/cursor.rs b/parley/src/editing/selection.rs similarity index 52% rename from parley/src/outputs/cursor.rs rename to parley/src/editing/selection.rs index 7ae40f10..c201e488 100644 --- a/parley/src/outputs/cursor.rs +++ b/parley/src/editing/selection.rs @@ -1,432 +1,24 @@ -// Copyright 2021 the Parley Authors +// Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Text selection support. - use alloc::vec::Vec; use core::ops::Range; use peniko::kurbo::Rect; -#[cfg(feature = "accesskit")] -use accesskit::TextPosition; -#[cfg(feature = "accesskit")] -use swash::text::cluster::Whitespace; - -use crate::outputs::{Affinity, BreakReason, Brush, Cluster, ClusterSide, Layout, Line}; +use crate::editing::Cursor; +use crate::inputs::Brush; +use crate::outputs::{Affinity, BreakReason, Cluster, Layout}; #[cfg(feature = "accesskit")] use crate::outputs::LayoutAccessibility; -/// Defines a position with a text layout. -#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] -pub struct Cursor { - pub(crate) index: usize, - pub(crate) affinity: Affinity, -} - -impl Cursor { - /// Creates a new cursor from the given byte index and affinity. - pub fn from_byte_index(layout: &Layout, index: usize, affinity: Affinity) -> Self { - if let Some(cluster) = Cluster::from_byte_index(layout, index) { - let index = cluster.text_range().start; - let affinity = if cluster.is_line_break() == Some(BreakReason::Explicit) { - Affinity::Downstream - } else { - affinity - }; - Self { index, affinity } - } else { - Self { - index: layout.data.text_len, - affinity: Affinity::Upstream, - } - } - } - - /// Creates a new cursor from the given coordinates. - pub fn from_point(layout: &Layout, x: f32, y: f32) -> Self { - let (index, affinity) = if let Some((cluster, side)) = Cluster::from_point(layout, x, y) { - let is_leading = side == ClusterSide::Left; - if cluster.is_rtl() { - if is_leading { - (cluster.text_range().end, Affinity::Upstream) - } else { - (cluster.text_range().start, Affinity::Downstream) - } - } else { - // We never want to position the cursor _after_ a hard - // line since that cursor appears visually at the start - // of the next line - if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) { - (cluster.text_range().start, Affinity::Downstream) - } else { - (cluster.text_range().end, Affinity::Upstream) - } - } - } else { - (layout.data.text_len, Affinity::Downstream) - }; - Self { index, affinity } - } - - #[cfg(feature = "accesskit")] - pub fn from_access_position( - pos: &TextPosition, - layout: &Layout, - layout_access: &LayoutAccessibility, - ) -> Option { - let (line_index, run_index) = *layout_access.run_paths_by_access_id.get(&pos.node)?; - let line = layout.get(line_index)?; - let run = line.run(run_index)?; - let index = run - .get(pos.character_index) - .map(|cluster| cluster.text_range().start) - .unwrap_or(layout.data.text_len); - Some(Self::from_byte_index(layout, index, Affinity::Downstream)) - } - - fn from_cluster( - layout: &Layout, - cluster: Cluster<'_, B>, - moving_right: bool, - ) -> Self { - Self::from_byte_index( - layout, - cluster.text_range().start, - affinity_for_dir(cluster.is_rtl(), moving_right), - ) - } - - /// Returns the logical text index of the cursor. - pub fn index(&self) -> usize { - self.index - } - - /// Returns the affinity of the cursor. - /// - /// This defines the direction from which the cursor entered its current - /// position and affects the visual location of the rendered cursor. - pub fn affinity(&self) -> Affinity { - self.affinity - } - - /// Returns a new cursor that is guaranteed to be within the bounds of the - /// given layout. - #[must_use] - pub fn refresh(&self, layout: &Layout) -> Self { - Self::from_byte_index(layout, self.index, self.affinity) - } - - /// Returns a new cursor that is positioned at the previous cluster boundary - /// in visual order. - #[must_use] - pub fn previous_visual(&self, layout: &Layout) -> Self { - let [left, right] = self.visual_clusters(layout); - if let (Some(left), Some(right)) = (&left, &right) { - if left.is_soft_line_break() { - if left.is_rtl() && self.affinity == Affinity::Upstream { - let index = if right.is_rtl() { - left.text_range().start - } else { - left.text_range().end - }; - return Self::from_byte_index(layout, index, Affinity::Downstream); - } else if !left.is_rtl() && self.affinity == Affinity::Downstream { - let index = if right.is_rtl() { - right.text_range().end - } else { - right.text_range().start - }; - return Self::from_byte_index(layout, index, Affinity::Upstream); - } - } - } - if let Some(left) = left { - let index = if left.is_rtl() { - left.text_range().end - } else { - left.text_range().start - }; - return Self::from_byte_index(layout, index, affinity_for_dir(left.is_rtl(), false)); - } - *self - } - - /// Returns a new cursor that is positioned at the next cluster boundary - /// in visual order. - #[must_use] - pub fn next_visual(&self, layout: &Layout) -> Self { - let [left, right] = self.visual_clusters(layout); - if let (Some(left), Some(right)) = (&left, &right) { - if left.is_soft_line_break() { - if left.is_rtl() && self.affinity == Affinity::Downstream { - let index = if right.is_rtl() { - right.text_range().end - } else { - right.text_range().start - }; - return Self::from_byte_index(layout, index, Affinity::Upstream); - } else if !left.is_rtl() && self.affinity == Affinity::Upstream { - let index = if right.is_rtl() { - right.text_range().end - } else { - right.text_range().start - }; - return Self::from_byte_index(layout, index, Affinity::Downstream); - } - } - let index = if right.is_rtl() { - right.text_range().start - } else { - right.text_range().end - }; - return Self::from_byte_index(layout, index, affinity_for_dir(right.is_rtl(), true)); - } - if let Some(right) = right { - let index = if right.is_rtl() { - right.text_range().start - } else { - right.text_range().end - }; - return Self::from_byte_index(layout, index, affinity_for_dir(right.is_rtl(), true)); - } - *self - } - - /// Returns a new cursor that is positioned at the next word boundary - /// in visual order. - #[must_use] - pub fn next_visual_word(&self, layout: &Layout) -> Self { - let mut cur = *self; - loop { - let next = cur.next_visual(layout); - if next == cur { - break; - } - cur = next; - let [Some(left), Some(right)] = cur.visual_clusters(layout) else { - break; - }; - if left.is_rtl() { - if left.is_word_boundary() && !left.is_space_or_nbsp() { - break; - } - } else if right.is_word_boundary() && !left.is_space_or_nbsp() { - break; - } - } - cur - } - - /// Returns a new cursor that is positioned at the previous word boundary - /// in visual order. - #[must_use] - pub fn previous_visual_word(&self, layout: &Layout) -> Self { - let mut cur = *self; - loop { - let next = cur.previous_visual(layout); - if next == cur { - break; - } - cur = next; - let [Some(left), Some(right)] = cur.visual_clusters(layout) else { - break; - }; - if left.is_rtl() { - if left.is_word_boundary() - && (left.is_space_or_nbsp() - || (right.is_word_boundary() && !right.is_space_or_nbsp())) - { - break; - } - } else if right.is_word_boundary() && !right.is_space_or_nbsp() { - break; - } - } - cur - } - - /// Returns a new cursor that is positioned at the next word boundary - /// in logical order. - #[must_use] - pub fn next_logical_word(&self, layout: &Layout) -> Self { - let [left, right] = self.logical_clusters(layout); - if let Some(cluster) = right.or(left) { - let start = cluster.clone(); - let cluster = cluster.next_logical_word().unwrap_or(cluster); - if cluster.path == start.path { - return Self::from_byte_index(layout, usize::MAX, Affinity::Downstream); - } - return Self::from_cluster(layout, cluster, true); - } - *self - } - - /// Returns a new cursor that is positioned at the previous word boundary - /// in logical order. - #[must_use] - pub fn previous_logical_word(&self, layout: &Layout) -> Self { - let [left, right] = self.logical_clusters(layout); - if let Some(cluster) = left.or(right) { - let cluster = cluster.previous_logical_word().unwrap_or(cluster); - return Self::from_cluster(layout, cluster, true); - } - *self - } - - /// Returns a rectangle that represents the visual geometry of the cursor - /// in layout space. - /// - /// The `width` parameter defines the width of the resulting rectangle. - pub fn geometry(&self, layout: &Layout, width: f32) -> Rect { - match self.visual_clusters(layout) { - [Some(left), Some(right)] => { - if left.is_end_of_line() { - if left.is_soft_line_break() { - let (cluster, at_end) = if left.is_rtl() - && self.affinity == Affinity::Downstream - || !left.is_rtl() && self.affinity == Affinity::Upstream - { - (left, true) - } else { - (right, false) - }; - cursor_rect(&cluster, at_end, width) - } else { - cursor_rect(&right, false, width) - } - } else { - cursor_rect(&left, true, width) - } - } - [Some(left), None] if left.is_hard_line_break() => last_line_cursor_rect(layout, width), - [Some(left), _] => cursor_rect(&left, true, width), - [_, Some(right)] => cursor_rect(&right, false, width), - _ => last_line_cursor_rect(layout, width), - } - } - - /// Returns the pair of clusters that logically bound the cursor - /// position. - /// - /// The order in the array is upstream followed by downstream. - pub fn logical_clusters<'a, B: Brush>( - &self, - layout: &'a Layout, - ) -> [Option>; 2] { - let upstream = self - .index - .checked_sub(1) - .and_then(|index| Cluster::from_byte_index(layout, index)); - let downstream = Cluster::from_byte_index(layout, self.index); - [upstream, downstream] - } - - /// Returns the pair of clusters that visually bound the cursor - /// position. - /// - /// The order in the array is left followed by right. - pub fn visual_clusters<'a, B: Brush>( - &self, - layout: &'a Layout, - ) -> [Option>; 2] { - if self.affinity == Affinity::Upstream { - if let Some(cluster) = self.upstream_cluster(layout) { - if cluster.is_rtl() { - [cluster.previous_visual(), Some(cluster)] - } else { - [Some(cluster.clone()), cluster.next_visual()] - } - } else if let Some(cluster) = self.downstream_cluster(layout) { - if cluster.is_rtl() { - [None, Some(cluster)] - } else { - [Some(cluster), None] - } - } else { - [None, None] - } - } else if let Some(cluster) = self.downstream_cluster(layout) { - if cluster.is_rtl() { - [Some(cluster.clone()), cluster.next_visual()] - } else { - [cluster.previous_visual(), Some(cluster)] - } - } else if let Some(cluster) = self.upstream_cluster(layout) { - if cluster.is_rtl() { - [None, Some(cluster)] - } else { - [Some(cluster), None] - } - } else { - [None, None] - } - } - - fn line(self, layout: &Layout) -> Option<(usize, Line<'_, B>)> { - let geometry = self.geometry(layout, 0.0); - layout.line_for_offset(geometry.y0 as f32) - } - - fn upstream_cluster(self, layout: &Layout) -> Option> { - self.index - .checked_sub(1) - .and_then(|index| Cluster::from_byte_index(layout, index)) - } - - fn downstream_cluster(self, layout: &Layout) -> Option> { - Cluster::from_byte_index(layout, self.index) - } - - #[cfg(feature = "accesskit")] - pub fn to_access_position( - &self, - layout: &Layout, - layout_access: &LayoutAccessibility, - ) -> Option { - if layout.data.text_len == 0 { - // If the text is empty, just return the first node with a - // character index of 0. - return Some(TextPosition { - node: *layout_access.access_ids_by_run_path.get(&(0, 0))?, - character_index: 0, - }); - } - // Prefer the downstream cluster except at the end of the text - // where we'll choose the upstream cluster and add 1 to the - // character index. - let (offset, path) = self - .downstream_cluster(layout) - .map(|cluster| (0, cluster.path)) - .or_else(|| { - self.upstream_cluster(layout) - .map(|cluster| (1, cluster.path)) - })?; - // If we're at the end of the layout and the layout ends with a newline - // then make sure we use the "phantom" run at the end so that - // AccessKit has correct visual geometry for the cursor. - let (run_path, character_index) = if self.index == layout.data.text_len - && layout - .data - .clusters - .last() - .map(|cluster| cluster.info.whitespace() == Whitespace::Newline) - .unwrap_or_default() - { - ((path.line_index() + 1, 0), 0) - } else { - ( - (path.line_index(), path.run_index()), - path.logical_index() + offset, - ) - }; - let id = layout_access.access_ids_by_run_path.get(&run_path)?; - Some(TextPosition { - node: *id, - character_index, - }) - } +#[derive(Copy, Clone, Default, Debug)] +enum AnchorBase { + #[default] + Cluster, + Word(Cursor, Cursor), + Line(Cursor, Cursor), } /// Defines a range within a text layout. @@ -896,48 +488,6 @@ impl From for Selection { } } -#[derive(Copy, Clone, Default, Debug)] -enum AnchorBase { - #[default] - Cluster, - Word(Cursor, Cursor), - Line(Cursor, Cursor), -} - -fn cursor_rect(cluster: &Cluster<'_, B>, at_end: bool, size: f32) -> Rect { - let line_x = (cluster.visual_offset().unwrap_or_default() - + at_end.then(|| cluster.advance()).unwrap_or_default()) as f64; - let line = cluster.line(); - let metrics = line.metrics(); - Rect::new( - line_x, - metrics.min_coord as f64, - line_x + size as f64, - metrics.max_coord as f64, - ) -} - -fn last_line_cursor_rect(layout: &Layout, size: f32) -> Rect { - if let Some(line) = layout.get(layout.len().saturating_sub(1)) { - let metrics = line.metrics(); - Rect::new( - 0.0, - metrics.min_coord as f64, - size as f64, - metrics.max_coord as f64, - ) - } else { - Rect::default() - } -} - -fn affinity_for_dir(is_rtl: bool, moving_right: bool) -> Affinity { - match (is_rtl, moving_right) { - (true, true) | (false, false) => Affinity::Downstream, - _ => Affinity::Upstream, - } -} - /// Given four cursors, return the left-most and right-most cursors from /// the set. /// diff --git a/parley/src/inputs/style/styleset.rs b/parley/src/inputs/style/styleset.rs index 6d68bdcd..598d09d2 100644 --- a/parley/src/inputs/style/styleset.rs +++ b/parley/src/inputs/style/styleset.rs @@ -12,9 +12,9 @@ type StyleProperty = crate::inputs::StyleProperty<'static, Brush>; /// A long-lived collection of [`StyleProperties`](super::StyleProperty), containing at /// most one of each property. /// -/// This is used by [`PlainEditor`](crate::outputs::editor::PlainEditor) to provide a reasonably ergonomic +/// This is used by [`PlainEditor`](crate::editing::PlainEditor) to provide a reasonably ergonomic /// mutable API for styles applied to all text managed by it. -/// This can be accessed using [`PlainEditor::edit_styles`](crate::outputs::editor::PlainEditor::edit_styles). +/// This can be accessed using [`PlainEditor::edit_styles`](crate::editing::PlainEditor::edit_styles). /// /// These styles do not have a corresponding range, and are generally unsuited for rich text. #[derive(Clone, Debug)] diff --git a/parley/src/lib.rs b/parley/src/lib.rs index d043245e..f38f4e9c 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -104,6 +104,7 @@ pub use swash; pub(crate) mod algos; +pub mod editing; pub mod inputs; pub mod outputs; @@ -117,5 +118,3 @@ pub use peniko::kurbo::Rect; pub use peniko::Font; pub use inline_box::InlineBox; - -pub use outputs::editor::{PlainEditor, PlainEditorDriver}; diff --git a/parley/src/outputs/mod.rs b/parley/src/outputs/mod.rs index 5a3f11ea..78b9655c 100644 --- a/parley/src/outputs/mod.rs +++ b/parley/src/outputs/mod.rs @@ -9,9 +9,6 @@ pub(crate) mod layout; pub(crate) mod line; pub(crate) mod run; -pub mod cursor; -pub mod editor; - #[cfg(feature = "accesskit")] mod accessibility; @@ -20,7 +17,6 @@ pub use accessibility::LayoutAccessibility; pub use self::alignment::Alignment; pub use self::cluster::{Affinity, Cluster, ClusterPath, ClusterSide}; -pub use self::cursor::{Cursor, Selection}; pub use self::layout::Layout; pub use self::line::{GlyphRun, Line, PositionedInlineBox, PositionedLayoutItem}; pub use self::run::{Run, RunMetrics}; diff --git a/parley/src/tests/test_cursor.rs b/parley/src/tests/test_cursor.rs index fff4c2bd..79f1c92b 100644 --- a/parley/src/tests/test_cursor.rs +++ b/parley/src/tests/test_cursor.rs @@ -1,8 +1,8 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::editing::Cursor; use crate::inputs::{FontContext, LayoutContext}; -use crate::outputs::Cursor; use crate::tests::utils::CursorTest; #[test] diff --git a/parley/src/tests/utils/cursor_test.rs b/parley/src/tests/utils/cursor_test.rs index 5b09a3e6..35862e33 100644 --- a/parley/src/tests/utils/cursor_test.rs +++ b/parley/src/tests/utils/cursor_test.rs @@ -1,8 +1,9 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::editing::Cursor; use crate::inputs::{FontContext, LayoutContext}; -use crate::outputs::{Affinity, Cursor, Layout}; +use crate::outputs::{Affinity, Layout}; // Note: This module is only compiled when running tests, which requires std, // so we don't have to worry about being no_std-compatible. diff --git a/parley/src/tests/utils/env.rs b/parley/src/tests/utils/env.rs index 21e200d0..967f684f 100644 --- a/parley/src/tests/utils/env.rs +++ b/parley/src/tests/utils/env.rs @@ -6,10 +6,10 @@ use std::path::{Path, PathBuf}; use fontique::{Collection, CollectionOptions}; use tiny_skia::{Color, Pixmap}; +use crate::editing::{PlainEditor, PlainEditorDriver}; use crate::inputs::{ FontContext, FontFamily, FontStack, LayoutContext, RangedBuilder, StyleProperty, }; -use crate::outputs::editor::{PlainEditor, PlainEditorDriver}; use crate::outputs::Layout; use crate::tests::utils::renderer::{render_layout, ColorBrush, RenderingConfig}; use crate::Rect;