Compare commits

..
Author SHA1 Message Date
Brandon Thomas 5808531e53 Merge remote-tracking branch 'origin/main' into pr-5373
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
2026-11-03 09:02:40 -05:00
Dylan M. Taylor 472ee9b04a fix(docbin): attach sections to their paragraphs through an index of paragraph ends 2026-11-03 04:58:01 +00:00
6 changed files with 10 additions and 70 deletions
+1 -3
View File
File diff suppressed because one or more lines are too long
-5
View File
@@ -228,11 +228,6 @@ fn block_cost(b: &Block) -> (usize, bool) {
.saturating_add(p.objects.len().saturating_mul(std::mem::size_of::<InlineObject>()));
let objects = p.objects.iter().fold(0usize, |n, o| n.saturating_add(object_cost(o)));
cost = cost.saturating_add(p.text.len()).saturating_add(items).saturating_add(objects);
// The formatting a clone copies too: style ids, font names and the like in the runs,
// the mark and the paragraph's properties.
let runs = p.runs.iter().filter(|r| !r.props.is_empty()).fold(0usize, |n, r| n.saturating_add(serialized_len(&r.props)));
let props = serialized_len(&p.props).saturating_add(serialized_len(&p.mark)).saturating_add(p.section.as_ref().map_or(0, serialized_len));
cost = cost.saturating_add(runs).saturating_add(props);
boxes |= box_ids(p).next().is_some();
});
(cost, boxes)
-18
View File
@@ -415,24 +415,6 @@ fn pasted_box_copies_are_capped_in_bytes() {
assert_eq!(got.iter().flatten().count(), added);
}
#[test]
fn pasted_box_copies_count_the_formatting_they_copy() {
// A story holding a text box and a run whose font name is 1 MiB: each copy clones the
// formatting too, so 64 copies used to copy 64 MiB of it past the 32 MiB cap.
let props = CharProps { font: Some("f".repeat(1 << 20)), ..CharProps::default() };
let mut p = Paragraph::with_text("x", props);
p.insert_object(0, box_shape(Some(999)), &CharProps::default()).unwrap();
let f = fan_out(vec![para_block(p)], 64);
let mut d = doc(&["x"]);
let before = d.parts.len();
d.insert_fragment(&Pos::body(0, 1), &f).unwrap();
let added = d.parts.len() - before;
let font = |p: &Paragraph| p.runs.iter().map(|r| r.props.font.as_ref().map_or(0, String::len)).sum::<usize>();
let copied: usize = d.parts.values().flat_map(|p| p.blocks.iter()).map(|b| if let Block::Para(p) = &**b { font(p) } else { 0 }).sum();
assert!((1..64).contains(&added), "{added}");
assert!(copied <= MAX_PASTED_BOX_BYTES, "{copied}");
}
#[test]
fn pasted_box_copies_count_the_objects_they_copy() {
// #3614 review: a story holding a text box and a 1 MiB kept-for-round-trip object costs
+8 -26
View File
@@ -101,12 +101,6 @@ impl RowInfo {
self.widths.iter().rfind(|(k, _)| *k == i).map(|(_, w)| *w)
}
/// How many per-cell overrides the row keeps, for tests.
#[cfg(test)]
pub(crate) fn overrides(&self) -> usize {
self.vmerge.len() + self.shading.len() + self.widths.len() + self.width_pcts.len() + self.valign.len() + self.merges.len()
}
/// The width percentage cell `i` ends up with, for tests.
#[cfg(test)]
pub(crate) fn width_pct_of(&self, i: u8) -> Option<f32> {
@@ -114,15 +108,6 @@ impl RowInfo {
}
}
/// Set cell `c`'s override to `v`, replacing an earlier one: one slot per cell, however often
/// a PAPX repeats the sprm, so a shared PAPX can't pile up copies on every paragraph mark.
fn set<T>(slots: &mut Vec<(u8, T)>, c: u8, v: T) {
match slots.iter_mut().find(|(k, _)| *k == c) {
Some(slot) => slot.1 = v,
None => slots.push((c, v)),
}
}
pub(crate) fn decode(papx: &[u8]) -> RowInfo {
let mut r = RowInfo::default();
let mut gap_half: Option<i16> = None;
@@ -159,10 +144,8 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
T_DXA_GAP_HALF => gap_half = s16(&prl),
T_TABLE_BORDERS_80 => r.borders = table_borders(prl.operand),
T_MERGE => {
// Rows apply only the first MAX_COLS merges.
if let [a, b, ..] = prl.operand
&& b > a
&& r.merges.len() < MAX_COLS
{
r.merges.push((*a, *b));
}
@@ -175,8 +158,7 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
1 => VMerge::Continue,
_ => VMerge::None,
};
// The first override for a cell wins.
if m != VMerge::None && !r.vmerge.iter().any(|(k, _)| k == itc) {
if m != VMerge::None {
r.vmerge.push((*itc, m));
}
}
@@ -186,7 +168,7 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
&& let [a, b, ..] = prl.operand.get(1..3).unwrap_or(&[])
{
for c in *a..(*b).min(MAX_COLS as u8) {
set(&mut r.shading, c, Some(rgb));
r.shading.push((c, Some(rgb)));
}
}
}
@@ -194,7 +176,7 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
let cells = prl.operand.get(1..).unwrap_or(&[]);
for (i, b) in cells.as_chunks::<2>().0.iter().take(MAX_COLS).enumerate() {
if let Some(rgb) = shd80_rgb(b) {
set(&mut r.shading, i as u8, Some(rgb));
r.shading.push((i as u8, Some(rgb)));
}
}
}
@@ -214,7 +196,7 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
if raw && b.get(8..10) == Some(&[0xFF, 0xFF]) {
continue;
}
set(&mut r.shading, (first + i) as u8, shd_rgb(b));
r.shading.push(((first + i) as u8, shd_rgb(b)));
}
}
T_CELL_WIDTH => {
@@ -225,12 +207,12 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
if *fts == 3 {
let w = (i16::from_le_bytes([*w0, *w1]) as f32 / 20.0).clamp(0.0, 1584.0);
for c in *a..(*b).min(MAX_COLS as u8) {
set(&mut r.widths, c, w);
r.widths.push((c, w));
}
} else if *fts == 2 {
let pct = (u16::from_le_bytes([*w0, *w1]) as f32 / 50.0).clamp(0.0, 100.0);
for c in *a..(*b).min(MAX_COLS as u8) {
set(&mut r.width_pcts, c, pct);
r.width_pcts.push((c, pct));
}
}
}
@@ -242,8 +224,8 @@ pub(crate) fn decode(papx: &[u8]) -> RowInfo {
2 => VAlign::Bottom,
_ => VAlign::Top,
};
for c in *a..(*b).min(MAX_COLS as u8) {
set(&mut r.valign, c, v);
for c in *a..*b {
r.valign.push((c, v));
}
}
}
-17
View File
@@ -2401,23 +2401,6 @@ fn second_table_gets_its_own_grid() {
assert_eq!(tables[1].props.indent, None, "indent reset");
}
#[test]
fn repeated_cell_sprms_keep_one_override_per_cell() {
// A PAPX repeating cell-range sprms (shared by every paragraph mark that names it) keeps one
// slot per cell, the last value winning, instead of a copy per repetition.
let mut sprms = Vec::new();
for w in 0..200u16 {
let [w0, w1] = (w * 20).to_le_bytes();
sprms.push(sprm_cb(0xD635, &[0, 63, 3, w0, w1]));
sprms.push(sprm_cb(0xD62C, &[0, 255, 1]));
sprms.push([0x24, 0x56, 0, 2].to_vec());
}
let row = crate::table::decode(&sprms.concat());
assert_eq!(row.width_of(0), Some(199.0), "the last width wins");
assert_eq!(row.width_of(62), Some(199.0));
assert!(row.overrides() <= 63 + 255 + 63, "{}", row.overrides());
}
#[test]
fn sprm_t_cell_width_twips_and_percentage() {
// [MS-DOC] §2.9.290 sprmTCellWidth (0xD635):
+1 -1
View File
File diff suppressed because one or more lines are too long