mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M10: a fill whose second rectangle cuts a hole is no page background (#2992)
Review follow-ups on #2994: under even-odd any second rectangle, under nonzero one wound the other way, can leave a hole the Word page colour would cover. The parity note says the page colour hasn't been opened in Word yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8707e87aff
commit
ca0e0b08f3
@@ -51,7 +51,9 @@ pub fn page_background(doc: &Document, page: usize) -> Result<Option<Rgb>, EditE
|
||||
let mut saved: Vec<State> = Vec::new();
|
||||
let mut lost_saves = 0usize;
|
||||
// The path being built: its rectangles in page space, and whether it has anything else.
|
||||
let mut rects: Vec<[f64; 4]> = Vec::new();
|
||||
// Each rectangle of the current path: its box on the page, and whether it winds
|
||||
// counterclockwise there (its width and height and the CTM's determinant agree in sign).
|
||||
let mut rects: Vec<([f64; 4], bool)> = Vec::new();
|
||||
let mut curved = false;
|
||||
let mut clip_pending = false;
|
||||
for op in &ops {
|
||||
@@ -94,7 +96,10 @@ pub fn page_background(doc: &Document, page: usize) -> Result<Option<Rgb>, EditE
|
||||
}
|
||||
}
|
||||
b"re" => match op.nums::<4>() {
|
||||
Some([x, y, w, h]) => rects.push(bbox(&st.ctm, [x, y, x + w, y + h])),
|
||||
Some([x, y, w, h]) => {
|
||||
let [a, b, c, d, _, _] = st.ctm.0;
|
||||
rects.push((bbox(&st.ctm, [x, y, x + w, y + h]), (w * h) * (a * d - b * c) > 0.0));
|
||||
}
|
||||
None => curved = true,
|
||||
},
|
||||
b"m" | b"l" | b"c" | b"v" | b"y" | b"h" => curved = true,
|
||||
@@ -102,7 +107,7 @@ pub fn page_background(doc: &Document, page: usize) -> Result<Option<Rgb>, EditE
|
||||
b"n" => {
|
||||
if clip_pending {
|
||||
st.clip = match (st.clip, rects.as_slice(), curved) {
|
||||
(Some(c), [r], false) => Some(intersect(c, *r)),
|
||||
(Some(c), [(r, _)], false) => Some(intersect(c, *r)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@@ -110,7 +115,16 @@ pub fn page_background(doc: &Document, page: usize) -> Result<Option<Rgb>, EditE
|
||||
}
|
||||
b"f" | b"F" | b"f*" | b"B" | b"B*" => {
|
||||
let opaque = st.plain && st.alpha >= 0.999;
|
||||
let covers = !curved && st.clip.is_some_and(|c| covers(&c, &crop)) && rects.iter().any(|r| covers(r, &crop));
|
||||
// A second rectangle can cut a hole in the one that covers the page: under the
|
||||
// even-odd rule any second one, under nonzero one wound the other way (a
|
||||
// frame-style page border).
|
||||
let even_odd = matches!(op.op.as_slice(), b"f*" | b"B*");
|
||||
let solid = match rects.as_slice() {
|
||||
[_] => true,
|
||||
[(_, first), rest @ ..] => !even_odd && rest.iter().all(|(_, ccw)| ccw == first),
|
||||
[] => false,
|
||||
};
|
||||
let covers = !curved && solid && st.clip.is_some_and(|c| covers(&c, &crop)) && rects.iter().any(|(r, _)| covers(r, &crop));
|
||||
return Ok(st.fill.filter(|_| opaque && covers && !clip_pending).map(|c| c.map(|v| v.clamp(0.0, 1.0))));
|
||||
}
|
||||
// Anything else painted first: no page background.
|
||||
|
||||
@@ -5264,6 +5264,12 @@ fn a_page_painted_first_with_a_full_page_fill_has_that_background() {
|
||||
assert_eq!(page("1 0 0 rg 0 0 300 800 re f", ""), None, "half the page");
|
||||
assert_eq!(page("1 0 0 rg 0 0 300 800 re 300 0 300 800 re f", ""), None, "two halves are not one covering rectangle");
|
||||
assert_eq!(page("1 0 0 rg 0 0 300 800 re 0 0 600 800 re f", ""), Some([1.0, 0.0, 0.0]), "one of the rectangles covers it");
|
||||
// #2994 review: a second rectangle that cuts a hole: any one under even-odd, one wound the
|
||||
// other way under nonzero.
|
||||
assert_eq!(page("1 0 0 rg 0 0 600 800 re 100 100 200 200 re f*", ""), None, "even-odd hole");
|
||||
assert_eq!(page("1 0 0 rg 0 0 600 800 re 300 100 -200 200 re f", ""), None, "nonzero, reversed inner rectangle");
|
||||
assert_eq!(page("1 0 0 rg 0 0 600 800 re 100 100 200 200 re f", ""), Some([1.0, 0.0, 0.0]), "nonzero, same winding: no hole");
|
||||
assert_eq!(page("1 0 0 rg q -1 0 0 1 600 0 cm 0 0 600 800 re Q 100 100 200 200 re f", ""), None, "reversed by a mirroring cm");
|
||||
assert_eq!(page("/Half gs 1 0 0 rg 0 0 600 800 re f", gs), None, "translucent");
|
||||
assert_eq!(page("/Mul gs 1 0 0 rg 0 0 600 800 re f", gs), None, "blended");
|
||||
assert_eq!(page("0 0 100 100 re W n 1 0 0 rg 0 0 600 800 re f", ""), None, "clipped to a corner");
|
||||
|
||||
@@ -6977,7 +6977,7 @@ status = "partial"
|
||||
commands = ["export.docx"]
|
||||
tools = ["doc_export_office"]
|
||||
evidence = ["crates/export/src/lib.rs::word_does_not_invent_borders_for_aligned_invoice_fields", "crates/export/src/lib.rs::word_overlap_does_not_consume_invoice_body_space", "crates/export/src/lib.rs::word_keeps_a_page_with_a_clear_image_and_big_title_in_flow", "crates/export/src/lib.rs::word_rotated_pages_keep_separate_page_sizes_and_unique_shapes", "crates/engine/src/tests.rs::office_export_positions_follow_the_displayed_crop_rotation_and_units", "crates/automation/tests/automation.rs::exporting_an_invoice_to_word_keeps_the_watermark_out_of_flow", "crates/export/src/lib.rs::docx_is_a_word_package", "crates/export/src/lib.rs::text_colour_survives_every_format", "crates/engine/src/tests.rs::exporting_office_files_keeps_text_colour", "crates/export/src/lib.rs::grid_text_becomes_a_real_table_in_every_format", "crates/export/src/lib.rs::a_link_inside_a_sentence_is_not_a_table_column", "crates/export/src/lib.rs::sentences_that_run_on_into_links_are_not_a_table", "crates/export/src/lib.rs::a_superscript_beside_a_number_keeps_its_table", "crates/export/src/lib.rs::word_opens_text_with_control_characters_and_long_pages", "crates/engine/src/tests.rs::exporting_office_files_keeps_images", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf", "crates/export/src/lib.rs::docx_keeps_a_shared_page_background", "crates/edit/src/tests.rs::a_page_painted_first_with_a_full_page_fill_has_that_background", "crates/engine/src/tests.rs::exporting_to_word_keeps_the_page_background"]
|
||||
notes = "Flowing Word document: paragraphs, Heading 1/2 from larger text, bold/italic, text colour (fill as sRGB; gray/CMYK converted naively, colour spaces via cs/sc read as black), borderless detected tables with spans/empty cells (a row whose blocks run on into each other, such as a link or superscript inside a sentence, or overlap, as a paragraph's lines do, is text and not a table row, #2806), per-page sections/sizes, the page colour (a full-page opaque fill painted first, such as Edit a PDF ▸ Background) as the document background when every page shares one non-white colour (Word has no per-page background, so differing colours are dropped; #2992), and positioned editable text/artwork only for pages with rotated text or text overlapped by an image or watermark-sized text, other pages flowing with inline images (#773); synthetic invoice regression; the emitted package validates with Open XML SDK 2.20 (Office 2010 schema). Word/LibreOffice rendering of the positioned output remains unverified. Pictures on positioned pages are DrawingML anchors behind the text (a VML rectangle with an image showed no picture, #1936; not yet opened in Word); a Word file embeds at most 1000 pictures and 512 MiB of them. The earlier flowing output was checked with macOS textutil and opened in Microsoft Word (Windows). Characters XML forbids are dropped; the page size stays within Word's 22 in. Not yet: exact font matching, vector rules, arbitrary paint-order/transparency, colour per run within a paragraph, general exact layout fidelity."
|
||||
notes = "Flowing Word document: paragraphs, Heading 1/2 from larger text, bold/italic, text colour (fill as sRGB; gray/CMYK converted naively, colour spaces via cs/sc read as black), borderless detected tables with spans/empty cells (a row whose blocks run on into each other, such as a link or superscript inside a sentence, or overlap, as a paragraph's lines do, is text and not a table row, #2806), per-page sections/sizes, the page colour (a full-page opaque fill painted first, such as Edit a PDF ▸ Background) as the document background when every page shares one non-white colour (Word has no per-page background, so differing colours are dropped; #2992; checked in the XML only, not yet opened in Word), and positioned editable text/artwork only for pages with rotated text or text overlapped by an image or watermark-sized text, other pages flowing with inline images (#773); synthetic invoice regression; the emitted package validates with Open XML SDK 2.20 (Office 2010 schema). Word/LibreOffice rendering of the positioned output remains unverified. Pictures on positioned pages are DrawingML anchors behind the text (a VML rectangle with an image showed no picture, #1936; not yet opened in Word); a Word file embeds at most 1000 pictures and 512 MiB of them. The earlier flowing output was checked with macOS textutil and opened in Microsoft Word (Windows). Characters XML forbids are dropped; the page size stays within Word's 22 in. Not yet: exact font matching, vector rules, arbitrary paint-order/transparency, colour per run within a paragraph, general exact layout fidelity."
|
||||
|
||||
[[feature]]
|
||||
id = "create.export-rtf"
|
||||
|
||||
Reference in New Issue
Block a user