Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ linebender_resource_handle = { version = "0.1.1", default-features = false }
packtab = { version = "1.8.0" }
parlance = { version = "0.1.0", default-features = false, path = "parlance" }
parley = { version = "0.10.0", default-features = false, path = "parley" }
parley_core = { version = "0.0.0", default-features = false, path = "parley_core" }
parley_data = { version = "0.10.0", path = "parley_data", default-features = false }
parley_data_gen = { path = "parley_data_gen" }
parley_dev = { default-features = false, path = "parley_dev" }
Expand Down
19 changes: 9 additions & 10 deletions parley/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,31 @@ workspace = true

[features]
default = ["system"]
std = ["fontique/std", "harfrust/std", "peniko/std", "skrifa/std", "parlance/std"]
libm = ["fontique/libm", "harfrust/libm", "peniko/libm", "skrifa/libm", "dep:core_maths"]
std = ["fontique/std", "peniko/std", "skrifa?/std", "parlance/std", "parley_core/std"]
libm = ["fontique/libm", "peniko/libm", "skrifa?/libm", "parley_core/libm", "dep:core_maths"]
# Enables support for system font backends
system = ["std", "fontique/system"]
accesskit = ["dep:accesskit"]
# `skrifa` is only needed to compute the metrics AccessKit reports.
accesskit = ["dep:accesskit", "dep:skrifa"]
# Enables dictionary-based line and word breaking for complex scripts (CJK, Thai, Khmer, Lao, Myanmar).
# When disabled, a lightweight segmenter is used that falls back to character-level breaks for those scripts.
complex-scripts = []
complex-scripts = ["parley_core/complex-scripts"]

[dependencies]
skrifa = { workspace = true }
skrifa = { workspace = true, optional = true }
linebender_resource_handle = { workspace = true }
fontique = { workspace = true }
parlance = { workspace = true }
parley_core = { workspace = true }
core_maths = { version = "0.1.1", optional = true }
parley_data = { workspace = true, features = ["baked"] }
accesskit = { workspace = true, optional = true }
hashbrown = { workspace = true }
harfrust = { workspace = true }
icu_normalizer = { workspace = true, features = ["compiled_data"] }
icu_properties = { workspace = true, features = ["compiled_data"] }
icu_segmenter = { workspace = true, features = ["compiled_data"] }

[dev-dependencies]
parley_dev = { workspace = true }
peniko = { workspace = true }
parley_data = { workspace = true, features = ["baked"] }
icu_properties = { workspace = true, features = ["compiled_data"] }

# We special-case android targets because oxipng doesn't build in Android CI.
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
Expand Down
43 changes: 43 additions & 0 deletions parley/src/analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2026 the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Text analysis.

use alloc::vec::Vec;
use core::ops::Range;

use crate::{Brush, LayoutContext, WordBreak};

use parlance::BaseDirection;
use parley_core::AnalysisOptions;

pub(crate) use parley_core::{Boundary, CharInfo};

/// Analyzes `text` into [`LayoutContext::analysis`], translating the layout's word-break
/// configuration into [`parley_core`] line-break overrides.
///
/// Empty text is analyzed as a single space so that a cursor can be sized from the resulting
/// metrics.
pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, text: &str) {
let text = if text.is_empty() { " " } else { text };

// Translate each style run's word-break into a line-break override. The analyzer treats gaps
// as `Normal`, so only non-default strengths need an entry.
lcx.line_break_overrides.clear();
for style_run in &lcx.style_runs {
let word_break = lcx.style_table[style_run.style_index as usize].word_break;
if word_break != WordBreak::Normal {
lcx.line_break_overrides
.push((style_run.range.clone(), word_break));
}
}

let options = AnalysisOptions {
base_direction: BaseDirection::Auto,
line_break_overrides: &lcx.line_break_overrides,
};
lcx.analyzer.analyze(text, &options, &mut lcx.analysis);
}

/// The per-style line-break overrides handed to [`parley_core::Analyzer`].
pub(crate) type LineBreakOverrides = Vec<(Range<usize>, WordBreak)>;
22 changes: 14 additions & 8 deletions parley/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,24 @@ fn build_into_layout<B: Brush>(
layout.data.clear();
layout.data.scale = scale;
layout.data.quantize = quantize;
layout.data.base_level = lcx.bidi.base_level();
layout.data.base_level = lcx.analysis.paragraph_level();
layout.data.text_len = text.len();

let mut char_index = 0;
lcx.info.clear();
lcx.info.reserve(lcx.analysis.char_infos().len());
let mut infos = lcx.analysis.char_infos().iter();
for style_run in &lcx.style_runs {
let style_index = style_run.style_index;
for _ in text[style_run.range.clone()].chars() {
lcx.info[char_index].1 = style_run.style_index;
char_index += 1;
let info = *infos.next().expect("char_infos covers every character");
lcx.info.push((info, style_index));
}
}
if let Some(&info) = infos.next() {
// Empty text is analyzed as a single synthetic space (so a cursor can be sized) that no
// style run covers; it takes the default style at index 0.
lcx.info.push((info, 0));
}

// Copy the visual styles into the layout
layout
Expand All @@ -300,19 +308,17 @@ fn build_into_layout<B: Brush>(
&lcx.style_table,
&lcx.inline_boxes,
&lcx.info,
lcx.bidi.levels(),
&lcx.analysis,
&mut lcx.scx,
&mut lcx.shaped,
text,
layout,
&lcx.analysis_data_sources,
);
}

// Move inline boxes into the layout
layout.data.inline_boxes.clear();
core::mem::swap(&mut layout.data.inline_boxes, &mut lcx.inline_boxes);

layout.data.finish();
}

fn resolve_range(range: impl RangeBounds<usize>, len: usize) -> Range<usize> {
Expand Down
24 changes: 14 additions & 10 deletions parley/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ use super::resolve::tree::TreeStyleBuilder;
use super::resolve::{RangedStyleBuilder, ResolveContext, ResolvedStyle, StyleRun};
use super::style::{Brush, TextStyle};

use crate::analysis::{AnalysisDataSources, CharInfo};
use crate::bidi::BidiResolver;
use crate::analysis::{CharInfo, LineBreakOverrides};
use crate::builder::TreeBuilder;
use crate::inline_box::InlineBox;
use crate::shape::ShapeContext;
use parley_core::{Analysis, Analyzer, ShapeContext, ShapedText};

/// Shared scratch space used when constructing text layouts.
///
Expand All @@ -25,7 +24,12 @@ pub struct LayoutContext<B: Brush = [u8; 4]> {
pub(crate) style_table: Vec<ResolvedStyle<B>>,
pub(crate) style_runs: Vec<StyleRun>,
pub(crate) inline_boxes: Vec<InlineBox>,
pub(crate) bidi: BidiResolver,

// Font-independent analysis (bidi, scripts, segmentation), performed by `parley_core`.
pub(crate) analyzer: Analyzer,
pub(crate) analysis: Analysis,
/// Reusable scratch for overrides passed to the analyzer.
pub(crate) line_break_overrides: LineBreakOverrides,

// Reusable style builders (to amortise allocations)
pub(crate) ranged_style_builder: RangedStyleBuilder<B>,
Expand All @@ -34,9 +38,8 @@ pub struct LayoutContext<B: Brush = [u8; 4]> {
// u16: style index for character
pub(crate) info: Vec<(CharInfo, u16)>,
pub(crate) scx: ShapeContext,

// Unicode analysis data sources (provided by icu)
pub(crate) analysis_data_sources: AnalysisDataSources,
/// Reusable shaped-text buffer, filled per itemized run and translated into the layout.
pub(crate) shaped: ShapedText,
}

impl<B: Brush> LayoutContext<B> {
Expand All @@ -46,12 +49,14 @@ impl<B: Brush> LayoutContext<B> {
style_table: vec![],
style_runs: vec![],
inline_boxes: vec![],
bidi: BidiResolver::new(),
analyzer: Analyzer::new(),
analysis: Analysis::new(),
line_break_overrides: Vec::new(),
ranged_style_builder: RangedStyleBuilder::default(),
tree_style_builder: TreeStyleBuilder::default(),
info: vec![],
analysis_data_sources: AnalysisDataSources::new(),
scx: ShapeContext::default(),
shaped: ShapedText::new(),
}
}

Expand Down Expand Up @@ -183,7 +188,6 @@ impl<B: Brush> LayoutContext<B> {
self.style_runs.clear();
self.inline_boxes.clear();
self.info.clear();
self.bidi.clear();
}
}

Expand Down
23 changes: 0 additions & 23 deletions parley/src/convert.rs

This file was deleted.

4 changes: 2 additions & 2 deletions parley/src/editing/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::BoundingBox;
#[cfg(feature = "accesskit")]
use crate::analysis::cluster::Whitespace;
use crate::layout::{Affinity, BreakReason, Cluster, ClusterSide, Layout, Line};
#[cfg(feature = "accesskit")]
use crate::layout::{ClusterPath, LayoutAccessibility};
use crate::style::Brush;
#[cfg(feature = "accesskit")]
use accesskit::TextPosition;
#[cfg(feature = "accesskit")]
use parley_core::Whitespace;

/// Defines a position with a text layout.
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
Expand Down
2 changes: 1 addition & 1 deletion parley/src/layout/cluster.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2021 the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::analysis::cluster::Whitespace;
use crate::layout::Style;
use crate::layout::data::BreakReason;
use crate::layout::data::ClusterData;
Expand All @@ -11,6 +10,7 @@ use crate::layout::line::{Line, LineItem};
use crate::layout::run::Run;
use crate::style::Brush;
use core::ops::Range;
use parley_core::Whitespace;

/// Atomic unit of text.
#[derive(Copy, Clone)]
Expand Down
Loading
Loading