mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 00:42:49 +00:00
perf(text): look only at a paragraph's own runs
This commit is contained in:
committed by
bflatastic
parent
8f8d04e5b5
commit
670968f60a
@@ -140,7 +140,9 @@ impl Ctx<'_> {
|
||||
/// stretch.
|
||||
fn char_directions(&self, r: &Range<usize>) -> Vec<(Range<usize>, ParaDirection)> {
|
||||
let mut out: Vec<(Range<usize>, ParaDirection)> = Vec::new();
|
||||
for (rr, st) in &self.runs {
|
||||
// Only the paragraph's own runs (they are in order), not every run of the text.
|
||||
let first = self.runs.partition_point(|(rr, _)| rr.end <= r.start);
|
||||
for (rr, st) in self.runs.iter().skip(first).take_while(|(rr, _)| rr.start < r.end) {
|
||||
let (a, b) = (rr.start.max(r.start), rr.end.min(r.end));
|
||||
let Some(d) = st.char_direction.filter(|_| a < b) else { continue };
|
||||
let (a, b) = (a - r.start, b - r.start);
|
||||
@@ -212,7 +214,10 @@ impl Ctx<'_> {
|
||||
fn prepend_marker(&self, pr: &Range<usize>, para: &ParaStyle, text: Option<&str>, rtl: bool, sg: &mut Vec<SGlyph>) -> usize {
|
||||
let Some(text) = text.filter(|_| para.list.is_some() && !pr.is_empty()) else { return 0 };
|
||||
let st = vectorcraft_doc::text::ParaList::marker_style(self.style_at(pr.start));
|
||||
let Some(run) = self.runs.iter().position(|(r, _)| r.start <= pr.start && pr.start < r.end) else { return 0 };
|
||||
let run = self.runs.partition_point(|(r, _)| r.end <= pr.start);
|
||||
if !self.runs.get(run).is_some_and(|(r, _)| r.start <= pr.start && pr.start < r.end) {
|
||||
return 0;
|
||||
}
|
||||
// The paragraph's direction: the marker starts a right-to-left paragraph at its right.
|
||||
let level = if rtl { Level::rtl() } else { Level::ltr() };
|
||||
let mut marker = Vec::with_capacity(text.len() + 1);
|
||||
|
||||
@@ -375,7 +375,13 @@ pub(crate) fn shape_range(
|
||||
art: Option<&'a InlineArt>,
|
||||
}
|
||||
let mut spans: Vec<ShapeSpan<'_>> = Vec::new();
|
||||
for (ri, (rr, st)) in runs.iter().enumerate() {
|
||||
// The runs are in order: start at the first that ends inside the range and stop past it, so
|
||||
// that each paragraph looks only at its own runs.
|
||||
let first = runs.partition_point(|(rr, _)| rr.end <= range.start);
|
||||
for (ri, (rr, st)) in runs.iter().enumerate().skip(first) {
|
||||
if rr.start >= range.end {
|
||||
break;
|
||||
}
|
||||
let a = rr.start.max(range.start);
|
||||
let b = rr.end.min(range.end);
|
||||
if a >= b || text.get(a..b).is_none() {
|
||||
|
||||
@@ -555,6 +555,19 @@ fn multiple_runs_and_styles() {
|
||||
assert_ne!(l.glyphs[0].font_id, l.glyphs[3].font_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_paragraphs_of_their_own_runs_lay_out_in_linear_time() {
|
||||
// 20,000 one-letter paragraphs, each a run of its own: every paragraph looking through every
|
||||
// run would be 400 million steps.
|
||||
let n = 20_000;
|
||||
let mut t = point("", style(10.0));
|
||||
t.runs = (0..n).map(|i| TextRun { text: "a\n".into(), style: style(if i % 2 == 0 { 10.0 } else { 11.0 }), inline: None }).collect();
|
||||
let start = std::time::Instant::now();
|
||||
let l = layout(db(), &t);
|
||||
assert!(start.elapsed() < std::time::Duration::from_secs(60), "{:?}", start.elapsed());
|
||||
assert!(l.glyphs.iter().any(|g| g.run == n - 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layout_is_fast() {
|
||||
let text: String = LOREM.chars().cycle().take(1000).collect();
|
||||
|
||||
Reference in New Issue
Block a user