mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
fix(render): images and nested groups knock out in a knockout group too (#2971)
An image or a nested group is drawn into a layer of its own, which covers the whole canvas (transparent where nothing was drawn), so it can't be composited with Compose::Copy. What is under its shape is now cleared first and the layer then composites over it: - an unmasked image's shape is its placement rectangle: the interpreter hands it to the device (`Device::knockout_shape`), which clears it with a transparent Copy fill; - a nested group's shape is the union of its elements' shapes: the interpreter draws its content once in the device's erase mode (`knockout_erase_wanted`, `begin_knockout_erase`, `end_knockout_erase`), where every paint clears under its shape and nothing is layered or drawn as an image, and then draws the group, always in a layer of its own (drawn straight onto the knockout group, its own elements knocked each other out). A masked image's shape is its mask; it composites as before, which matches MuPDF where the mask is opaque or transparent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d2bcda45fd
commit
2627384757
+79
-28
@@ -6039,11 +6039,12 @@ trailer << /Root 1 0 R >>
|
||||
}
|
||||
|
||||
/// #2971: in a knockout transparency group (`/K true`) each element composites with the
|
||||
/// group's initial backdrop, not with the elements drawn before it in the group. A blue
|
||||
/// square at 50 % opacity over a red one at 50 % shows only the blue where they overlap;
|
||||
/// without `/K` the two blend. An element drawn through a layer of its own (here a nested
|
||||
/// group, a stencil mask) doesn't knock out yet, but it must not erase what was drawn before it
|
||||
/// either.
|
||||
/// group's initial backdrop, not with the elements drawn before it in the group, over the
|
||||
/// element's shape. A blue square at 50 % opacity over a red one at 50 % shows only the blue
|
||||
/// where they overlap; without `/K` the two blend. The same holds for an image (its shape
|
||||
/// is its rectangle; a soft-masked image's shape is its mask, so its transparent part leaves
|
||||
/// what is under it) and for a nested group (its shape is the union of its elements'
|
||||
/// shapes, while its own elements still blend with each other).
|
||||
#[test]
|
||||
fn knockout_groups_replace_what_their_elements_cover() {
|
||||
let render = |group: &str, form: &str| {
|
||||
@@ -6055,14 +6056,35 @@ trailer << /Root 1 0 R >>
|
||||
4 0 obj << /Length 39 >> stream
|
||||
/Fm1 Do /G gs 0 1 0 rg 10 10 30 30 re f
|
||||
endstream endobj
|
||||
5 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 300 200] /Group << /S /Transparency {group} >> /Resources << /ExtGState << /A << /ca 0.5 >> >> /XObject << /In 6 0 R /Msk 7 0 R >> >> /Length {} >> stream
|
||||
5 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 300 200] /Group << /S /Transparency {group} >> /Resources << /ExtGState << /A << /ca 0.5 >> >> /XObject << /In 6 0 R /Msk 7 0 R /Neu 8 0 R /Img 9 0 R /Sm 10 0 R /Nimg 12 0 R /Deep 13 0 R >> >> /Length {} >> stream
|
||||
{form}
|
||||
endstream endobj
|
||||
6 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 300 200] /Group << /S /Transparency >> /Resources << /ExtGState << /A << /ca 0.5 >> >> >> /Length 30 >> stream
|
||||
/A gs 0 0 1 rg 100 50 150 120 re f
|
||||
endstream endobj
|
||||
7 0 obj << /Type /XObject /Subtype /Image /Width 2 /Height 2 /ImageMask true /Length 2 >> stream
|
||||
| ||||