Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions parley/src/analysis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub(crate) mod cluster;
use alloc::vec::Vec;
use core::marker::PhantomData;

use crate::resolve::{RangedStyle, ResolvedStyle};
use crate::resolve::StyleRun;
use crate::{Brush, LayoutContext, WordBreak};

use icu_normalizer::properties::{
Expand Down Expand Up @@ -217,7 +217,8 @@ pub(crate) enum Boundary {
pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, mut text: &str) {
struct WordBreakSegmentIter<'a, I: Iterator, B: Brush> {
text: &'a str,
styles: I,
style_runs: I,
lcx: &'a LayoutContext<B>,
char_indices: core::str::CharIndices<'a>,
current_char: (usize, char),
building_range_start: usize,
Expand All @@ -228,19 +229,26 @@ pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, mut text: &str)

impl<'a, I, B: Brush + 'a> WordBreakSegmentIter<'a, I, B>
where
I: Iterator<Item = &'a RangedStyle<B>>,
I: Iterator<Item = &'a StyleRun>,
{
fn new(text: &'a str, styles: I, first_style: &RangedStyle<B>) -> Self {
fn new(
text: &'a str,
style_runs: I,
lcx: &'a LayoutContext<B>,
first_style_run: &StyleRun,
) -> Self {
let mut char_indices = text.char_indices();
let current_char_len = char_indices.next().unwrap();
let first_style = &lcx.style_table[first_style_run.style_index as usize];

Self {
text,
styles,
style_runs,
lcx,
char_indices,
current_char: current_char_len,
building_range_start: first_style.range.start,
previous_word_break_style: first_style.style.word_break,
building_range_start: first_style_run.range.start,
previous_word_break_style: first_style.word_break,
done: false,
_phantom: PhantomData,
}
Expand All @@ -249,7 +257,7 @@ pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, mut text: &str)

impl<'a, I, B: Brush + 'a> Iterator for WordBreakSegmentIter<'a, I, B>
where
I: Iterator<Item = &'a RangedStyle<B>>,
I: Iterator<Item = &'a StyleRun>,
{
type Item = (&'a str, WordBreak, bool);

Expand All @@ -258,11 +266,11 @@ pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, mut text: &str)
return None;
}

for style in self.styles.by_ref() {
for style_run in self.style_runs.by_ref() {
// Empty style ranges are disallowed.
assert!(style.range.start < style.range.end);
assert!(style_run.range.start < style_run.range.end);

let style_start_index = style.range.start;
let style_start_index = style_run.range.start;
let mut prev_char_index = self.current_char;

// Find the character at the style boundary
Expand All @@ -271,7 +279,8 @@ pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, mut text: &str)
self.current_char = self.char_indices.next().unwrap();
}

let current_word_break_style = style.style.word_break;
let current_word_break_style =
self.lcx.style_table[style_run.style_index as usize].word_break;
if self.previous_word_break_style == current_word_break_style {
continue;
}
Expand All @@ -298,24 +307,19 @@ pub(crate) fn analyze_text<B: Brush>(lcx: &mut LayoutContext<B>, mut text: &str)

if text.is_empty() {
text = " ";
if lcx.styles.is_empty() {
lcx.styles.push(RangedStyle {
style: ResolvedStyle::default(),
range: 0..0,
});
}
}

// Line boundaries (word break naming refers to the line boundary determination config).
//
// This breaks text into sequences with similar line boundary config (part of style
// information). If this config is consistent for all text, we use a fast path through this.
let Some((first_style, rest)) = lcx.styles.split_first() else {
panic!("No style info");
};
let (first_style_run, rest_runs) = lcx
.style_runs
.split_first()
.expect("analyze_text requires at least one style run");

let contiguous_word_break_substrings =
WordBreakSegmentIter::new(text, rest.iter(), first_style);
WordBreakSegmentIter::new(text, rest_runs.iter(), lcx, first_style_run);
let mut global_offset = 0;
let mut line_boundary_positions: Vec<usize> = Vec::new();
for (substring_index, (substring, word_break_strength, last)) in
Expand Down
37 changes: 27 additions & 10 deletions parley/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use alloc::string::String;
use core::ops::RangeBounds;

use crate::inline_box::InlineBox;
use crate::resolve::tree::ItemKind;
use crate::resolve::{ResolvedStyle, StyleRun, tree::ItemKind};

/// Builder for constructing a text layout with ranged attributes.
#[must_use]
Expand Down Expand Up @@ -50,8 +50,10 @@ impl<B: Brush> RangedBuilder<'_, B> {
}

pub fn build_into(self, layout: &mut Layout<B>, text: impl AsRef<str>) {
// Apply RangedStyleBuilder styles to LayoutContext
self.lcx.ranged_style_builder.finish(&mut self.lcx.styles);
// Apply RangedStyleBuilder styles directly to style-table/style-run state.
self.lcx
.ranged_style_builder
.finish(&mut self.lcx.style_table, &mut self.lcx.style_runs);

// Call generic layout builder method
build_into_layout(
Expand Down Expand Up @@ -130,8 +132,11 @@ impl<B: Brush> TreeBuilder<'_, B> {

#[inline]
pub fn build_into(self, layout: &mut Layout<B>) -> String {
// Apply TreeStyleBuilder styles to LayoutContext
let text = self.lcx.tree_style_builder.finish(&mut self.lcx.styles);
// Apply TreeStyleBuilder styles to LayoutContext.
let text = self
.lcx
.tree_style_builder
.finish(&mut self.lcx.style_table, &mut self.lcx.style_runs);

// Call generic layout builder method
build_into_layout(layout, self.scale, self.quantize, &text, self.lcx, self.fcx);
Expand All @@ -155,6 +160,18 @@ fn build_into_layout<B: Brush>(
lcx: &mut LayoutContext<B>,
fcx: &mut FontContext,
) {
if text.is_empty() && lcx.style_runs.is_empty() {
lcx.style_table.push(ResolvedStyle::default());
lcx.style_runs.push(StyleRun {
style_index: 0,
range: 0..0,
});
}
assert!(
!lcx.style_runs.is_empty(),
"at least one style run is required"
);
Comment thread
waywardmonkeys marked this conversation as resolved.

crate::analysis::analyze_text(lcx, text);

layout.data.clear();
Expand All @@ -164,9 +181,9 @@ fn build_into_layout<B: Brush>(
layout.data.text_len = text.len();

let mut char_index = 0;
for (i, style) in lcx.styles.iter().enumerate() {
for _ in text[style.range.clone()].chars() {
lcx.info[char_index].1 = i as u16;
for style_run in &lcx.style_runs {
for _ in text[style_run.range.clone()].chars() {
lcx.info[char_index].1 = style_run.style_index;
char_index += 1;
}
}
Expand All @@ -175,7 +192,7 @@ fn build_into_layout<B: Brush>(
layout
.data
.styles
.extend(lcx.styles.iter().map(|s| s.style.as_layout_style()));
.extend(lcx.style_table.iter().map(|s| s.as_layout_style()));

// Sort the inline boxes as subsequent code assumes that they are in text index order.
// Note: It's important that this is a stable sort to allow users to control the order of contiguous inline boxes
Expand All @@ -186,7 +203,7 @@ fn build_into_layout<B: Brush>(
super::shape::shape_text(
&lcx.rcx,
query,
&lcx.styles,
&lcx.style_table,
&lcx.inline_boxes,
&lcx.info,
lcx.bidi.levels(),
Expand Down
11 changes: 7 additions & 4 deletions parley/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use alloc::{vec, vec::Vec};
use super::FontContext;
use super::builder::RangedBuilder;
use super::resolve::tree::TreeStyleBuilder;
use super::resolve::{RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle};
use super::resolve::{RangedStyleBuilder, ResolveContext, ResolvedStyle, StyleRun};
use super::style::{Brush, TextStyle};

use crate::analysis::{AnalysisDataSources, CharInfo};
Expand All @@ -22,7 +22,8 @@ use crate::shape::ShapeContext;
/// This type is designed to be a global resource with only one per-application (or per-thread).
pub struct LayoutContext<B: Brush = [u8; 4]> {
pub(crate) rcx: ResolveContext,
pub(crate) styles: Vec<RangedStyle<B>>,
pub(crate) style_table: Vec<ResolvedStyle<B>>,
pub(crate) style_runs: Vec<StyleRun>,
pub(crate) inline_boxes: Vec<InlineBox>,
pub(crate) bidi: BidiResolver,

Expand All @@ -42,7 +43,8 @@ impl<B: Brush> LayoutContext<B> {
pub fn new() -> Self {
Self {
rcx: ResolveContext::default(),
styles: vec![],
style_table: vec![],
style_runs: vec![],
inline_boxes: vec![],
bidi: BidiResolver::new(),
ranged_style_builder: RangedStyleBuilder::default(),
Expand Down Expand Up @@ -148,7 +150,8 @@ impl<B: Brush> LayoutContext<B> {

fn begin(&mut self) {
self.rcx.clear();
self.styles.clear();
self.style_table.clear();
self.style_runs.clear();
self.inline_boxes.clear();
self.info.clear();
self.bidi.clear();
Expand Down
7 changes: 7 additions & 0 deletions parley/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ pub(crate) struct RangedStyle<B: Brush> {
pub(crate) range: Range<usize>,
}

/// Run that references a style in a shared style table.
#[derive(Debug, Clone)]
pub(crate) struct StyleRun {
pub(crate) style_index: u16,
pub(crate) range: Range<usize>,
}

#[derive(Clone)]
struct RangedProperty<B: Brush> {
property: ResolvedProperty<B>,
Expand Down
27 changes: 24 additions & 3 deletions parley/src/resolve/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

use alloc::vec;

use super::{Brush, RangedProperty, RangedStyle, ResolvedProperty, ResolvedStyle, Vec};
use super::{Brush, RangedProperty, RangedStyle, ResolvedProperty, ResolvedStyle, StyleRun, 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.
#[derive(Clone)]
pub(crate) struct RangedStyleBuilder<B: Brush> {
properties: Vec<RangedProperty<B>>,
scratch_styles: Vec<RangedStyle<B>>,
root_style: ResolvedStyle<B>,
len: usize,
}
Expand All @@ -21,6 +22,7 @@ impl<B: Brush> Default for RangedStyleBuilder<B> {
fn default() -> Self {
Self {
properties: vec![],
scratch_styles: vec![],
root_style: ResolvedStyle::default(),
// We use `usize::MAX` as a sentinel that `begin` hasn't been called.
// This is required (rather than requiring the root style in the constructor)
Expand All @@ -36,6 +38,7 @@ impl<B: Brush> RangedStyleBuilder<B> {
/// The provided `root_style` is the default style applied to all text unless overridden.
pub(crate) fn begin(&mut self, root_style: ResolvedStyle<B>, len: usize) {
self.properties.clear();
self.scratch_styles.clear();
self.root_style = root_style;
self.len = len;
}
Expand Down Expand Up @@ -67,13 +70,21 @@ impl<B: Brush> RangedStyleBuilder<B> {
self.properties.push(RangedProperty { property, range });
}

/// Computes the sequence of ranged styles.
pub(crate) fn finish(&mut self, styles: &mut Vec<RangedStyle<B>>) {
/// Computes style table + style runs for the ranged properties.
pub(crate) fn finish(
&mut self,
style_table: &mut Vec<ResolvedStyle<B>>,
style_runs: &mut Vec<StyleRun>,
) {
style_table.clear();
style_runs.clear();
if self.len == usize::MAX {
self.properties.clear();
self.scratch_styles.clear();
self.root_style = ResolvedStyle::default();
return;
}
let styles = &mut self.scratch_styles;
styles.push(RangedStyle {
style: self.root_style.clone(),
range: 0..self.len,
Expand Down Expand Up @@ -142,6 +153,16 @@ impl<B: Brush> RangedStyleBuilder<B> {
}
styles.truncate(styles.len() - merged_count);

style_table.reserve(styles.len());
style_runs.reserve(styles.len());
for (style_index, style) in styles.drain(..).enumerate() {
style_table.push(style.style);
style_runs.push(StyleRun {
style_index: style_index as u16,
range: style.range,
});
}

self.properties.clear();
self.root_style = ResolvedStyle::default();
self.len = usize::MAX;
Expand Down
Loading