mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
fix(render): knockout transparency groups knock out (#2971)
In a knockout group (/K true) each element composites with the group's initial backdrop, not with the elements drawn before it (ISO 32000-2, knockout groups). The renderer read /K (hayro-interpret patch 28) but drew such a group like any other, so overlapping semi-transparent shapes in it blended with each other. hayro-interpret patch (39) tells the device a group is a knockout group (`Device::set_knockout`, default no-op). hayro patch (19) then draws that group's fills, strokes, glyph outlines and shadings with Compose::Copy, which vello_cpu weights by coverage, so each replaces what the group drew under its shape. Images and nested groups are drawn into a group layer of their own and still composite normally: copying a whole layer would erase everything outside it. 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
43550c448d
commit
d2bcda45fd
@@ -6038,6 +6038,70 @@ trailer << /Root 1 0 R >>
|
||||
assert_eq!(pixel(&on, 150, 55), &[255, 255, 255, 255], "the image's white quadrant stays white");
|
||||
}
|
||||
|
||||
/// #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.
|
||||
#[test]
|
||||
fn knockout_groups_replace_what_their_elements_cover() {
|
||||
let render = |group: &str, form: &str| {
|
||||
let pdf = format!(
|
||||
"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R /Resources << /XObject << /Fm1 5 0 R >> /ExtGState << /G << /ca 0.5 >> >> >> >> endobj
|
||||
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
|
||||
{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
|
||||
| ||||