mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-10 04:19:33 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e24c52646 |
@@ -148,19 +148,7 @@ pub fn rotate(dir: &Path) -> std::io::Result<(PathBuf, File)> {
|
|||||||
/// One formatted log line: `<timestamp> <LEVEL> [<thread>] <target>: <message>\n`.
|
/// One formatted log line: `<timestamp> <LEVEL> [<thread>] <target>: <message>\n`.
|
||||||
pub fn format_line(ts: &str, level: log::Level, thread: &str, target: &str, message: &str) -> String {
|
pub fn format_line(ts: &str, level: log::Level, thread: &str, target: &str, message: &str) -> String {
|
||||||
let message = message.strip_suffix('\n').unwrap_or(message);
|
let message = message.strip_suffix('\n').unwrap_or(message);
|
||||||
// Messages can embed document-controlled text (a part name, a relationship id): control
|
format!("{ts} {level:<5} [{thread}] {target}: {message}\n")
|
||||||
// characters other than tab are escaped and each continuation line is marked, so such text
|
|
||||||
// cannot start a line that looks like a record of its own.
|
|
||||||
let mut text = String::with_capacity(message.len());
|
|
||||||
for c in message.chars() {
|
|
||||||
match c {
|
|
||||||
'\n' => text.push_str("\n | "),
|
|
||||||
'\t' => text.push(c),
|
|
||||||
c if c.is_control() => text.extend(c.escape_default()),
|
|
||||||
c => text.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format!("{ts} {level:<5} [{thread}] {target}: {text}\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where formatted lines go besides standard error: kept in memory until a file is attached,
|
/// Where formatted lines go besides standard error: kept in memory until a file is attached,
|
||||||
@@ -458,13 +446,8 @@ mod tests {
|
|||||||
),
|
),
|
||||||
"2026-10-08T07:59:17.728Z WARN [main] deckcraft_pptx::opc: pptx: skipping oversized part ppt/media/image1.png\n"
|
"2026-10-08T07:59:17.728Z WARN [main] deckcraft_pptx::opc: pptx: skipping oversized part ppt/media/image1.png\n"
|
||||||
);
|
);
|
||||||
// A multi-line message keeps its lines, each continuation marked; the record still ends in
|
// A multi-line message keeps its lines; the record still ends in exactly one newline.
|
||||||
// exactly one newline.
|
assert!(format_line("t", log::Level::Error, "w", "x", "a\nb\n").ends_with("x: a\nb\n"));
|
||||||
assert!(format_line("t", log::Level::Error, "w", "x", "a\nb\n").ends_with("x: a\n | b\n"));
|
|
||||||
// Injected line breaks and escape sequences cannot forge a record.
|
|
||||||
let line = format_line("t", log::Level::Warn, "w", "x", "part a\r\n2026 ERROR [m] y: forged\x1b[2J");
|
|
||||||
assert_eq!(line.matches('\n').count(), 2);
|
|
||||||
assert!(!line.contains('\r') && !line.contains('\x1b'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -246,6 +246,9 @@ fn point_fills(w: &mut W, s: &Series, g: Group) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data points (categories and values counted per series) one chart group writes to its caches.
|
||||||
|
const MAX_EXPORT_POINTS: usize = 4_000_000;
|
||||||
|
|
||||||
fn series(w: &mut W, c: &Chart, s: &Series, i: usize, kind: ChartType, g: Group) {
|
fn series(w: &mut W, c: &Chart, s: &Series, i: usize, kind: ChartType, g: Group) {
|
||||||
w.open0("c:ser");
|
w.open0("c:ser");
|
||||||
w.val("c:idx", i);
|
w.val("c:idx", i);
|
||||||
@@ -529,7 +532,16 @@ fn plot_group(w: &mut W, c: &Chart, kind: ChartType, list: &[(usize, &Series)])
|
|||||||
if g != Group::Stock {
|
if g != Group::Stock {
|
||||||
w.val("c:varyColors", vary);
|
w.val("c:varyColors", vary);
|
||||||
}
|
}
|
||||||
for (i, s) in list {
|
// Each series repeats the categories in its own cache, so the output grows with series times
|
||||||
|
// categories: stop adding series once a group's caches pass the budget (the first is always written).
|
||||||
|
let mut left = MAX_EXPORT_POINTS;
|
||||||
|
for (n, (i, s)) in list.iter().enumerate() {
|
||||||
|
let cost = c.categories.len().max(s.values.len()).saturating_mul(2).saturating_add(s.x_values.len() + s.sizes.len());
|
||||||
|
if n > 0 && cost > left {
|
||||||
|
log::warn!("pptx: chart export stopped after {n} series; the data caches passed {MAX_EXPORT_POINTS} points");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
left = left.saturating_sub(cost);
|
||||||
series(w, c, s, *i, kind, g);
|
series(w, c, s, *i, kind, g);
|
||||||
}
|
}
|
||||||
if c.data_labels {
|
if c.data_labels {
|
||||||
|
|||||||
Reference in New Issue
Block a user