mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
fix(js): read printd literals in linear time
util.printd measured the run of identical characters at every position of the format, literals included, but copied a literal one character at a time. A format with a long stretch of one literal character rescanned the rest of that stretch at every step: quadratic work from one script-built string. Only the date letters are read as runs now.
This commit is contained in:
@@ -752,7 +752,11 @@ fn printd(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue>
|
||||
let mut i = 0;
|
||||
while i < b.len() {
|
||||
let c = b[i];
|
||||
let run = b[i..].iter().take_while(|x| **x == c).count();
|
||||
// Only date letters read as runs; a literal is copied one character at a time, so
|
||||
// measuring its run would rescan a long literal stretch at every position.
|
||||
let run = if b"ymdHhMst".contains(&c) { b[i..].iter().take_while(|x| **x == c).count() } else { 1 };
|
||||
#[cfg(test)]
|
||||
PRINTD_SCANNED.fetch_add(run as u64, std::sync::atomic::Ordering::Relaxed);
|
||||
let push_num = |out: &mut String, v: i64, n: usize| {
|
||||
if n >= 2 {
|
||||
out.push_str(&format!("{v:02}"));
|
||||
@@ -1011,6 +1015,10 @@ fn event_object(ctx: &mut Context, e: &Event) -> JsObject {
|
||||
// stack a script can use: brackets and prefix operators by the limits below, and every other
|
||||
// construct (a few KiB per level, several bytes of source each) by the length limit.
|
||||
|
||||
/// Bytes `util.printd` has scanned for runs of a format character, for tests that bound its work.
|
||||
#[cfg(test)]
|
||||
pub(crate) static PRINTD_SCANNED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
/// Longest script, in bytes.
|
||||
pub(crate) const MAX_SCRIPT_BYTES: usize = 256 * 1024;
|
||||
/// Largest width or precision accepted by `util.printf`.
|
||||
|
||||
@@ -244,6 +244,17 @@ fn document_requests_and_console() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn printd_reads_a_long_literal_run_in_linear_work() {
|
||||
crate::PRINTD_SCANNED.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||
let o = go("event.value = util.printd(new Array(20001).join('x'), new Date(2024, 0, 5)).length;", &Event::field("Calculate", "total", ""));
|
||||
assert_eq!(o.error, None);
|
||||
assert_eq!(o.value, "20000");
|
||||
// Other tests format short dates at the same time; they add a few dozen bytes at most.
|
||||
let scanned = crate::PRINTD_SCANNED.load(std::sync::atomic::Ordering::Relaxed);
|
||||
assert!(scanned < 40_000, "printd scanned {scanned} bytes for a 20000-byte format");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_level_functions_are_available() {
|
||||
let o = run(
|
||||
|
||||
Reference in New Issue
Block a user