diff --git a/crates/format/src/sketch/text.rs b/crates/format/src/sketch/text.rs index 74cfa21c9..4a7fe18ad 100644 --- a/crates/format/src/sketch/text.rs +++ b/crates/format/src/sketch/text.rs @@ -14,6 +14,9 @@ use super::tree::{Cx, color}; const MAX_CHARS: usize = 1 << 20; /// Most runs read from one text layer. const MAX_RUNS: usize = 10_000; +/// Most characters that styled runs may write in all, overlaps counted again (a run's range is +/// rewritten in full, even where later runs overwrite it). +const MAX_RUN_WORK: usize = 16 * MAX_CHARS; /// Fill `node` (a TEXT node) from text layer `l`. pub(crate) fn node(cx: &mut Cx, l: &Value, node: &mut JsonMap) { @@ -80,10 +83,16 @@ pub(crate) fn node(cx: &mut Cx, l: &Value, node: &mut JsonMap) { // Runs that differ from the first: a style id per UTF-16 unit. let mut ids = vec![0u64; units]; let mut table = JsonMap::new(); + let mut work = 0usize; for (start, end, s) in &styles { if *s == base { continue; } + work = work.saturating_add(end.saturating_sub(*start)); + if work > MAX_RUN_WORK { + cx.warn("text whose styled runs overlap a great deal keeps the first runs' styles"); + break; + } let id = table.len() as u64 + 1; table.insert(id.to_string(), s.clone()); for slot in ids.get_mut(*start..*end).into_iter().flatten() { diff --git a/crates/format/src/sketch/text_tests.rs b/crates/format/src/sketch/text_tests.rs index 0e8a4a279..f50bf74df 100644 --- a/crates/format/src/sketch/text_tests.rs +++ b/crates/format/src/sketch/text_tests.rs @@ -271,3 +271,13 @@ fn later_runs_dont_inherit_the_first_runs_settings() { assert_eq!(second.line_height, figcraft_doc::LineHeight::Auto, "{second:?}"); assert!(second.variations.is_empty(), "{second:?}"); } + +#[test] +fn runs_that_overlap_a_great_deal_stop_being_applied() { + // 17 runs over the whole of a text of 2^20 characters: 17 × 2^20 writes without the limit. + let n = 1 << 20; + let mut runs = vec![(0, 1, "Helvetica", 10.0, BLACK)]; + runs.extend((0..17).map(|i| (0, n, "Helvetica", 11.0 + f64::from(i), BLACK))); + let (_, warnings) = open(vec![text("Long", 0.0, 0.0, 200.0, 20.0, &"a".repeat(n), &runs)]); + assert!(warnings.iter().any(|w| w.contains("overlap a great deal")), "{warnings:?}"); +}