PDF import: a knockout group in a form of its own opacity reads back as one group

PDF export writes a translucent knockout group as a form with its opacity around the /K group. PDF import now reads that pair back as one knockout group with the opacity, blend mode and isolation, instead of a translucent group holding a knockout group.
This commit is contained in:
dotCooCoo
2026-11-13 20:13:02 -08:00
parent 2720aca362
commit b1cf6f810b
2 changed files with 41 additions and 0 deletions
+15
View File
@@ -387,6 +387,21 @@ impl<'p> Builder<'p> {
only = fold_stop_opacity(&only, &m).unwrap_or_else(|| only.with_mask(Some(Box::new(m))));
}
Some(only)
} else if let [only] = children.as_slice()
&& matches!(only.kind, NodeKind::Group { clip: false, .. })
&& only.knockout == Knockout::On
&& only.opacity >= 0.999
&& only.blend == BlendMode::Normal
&& !only.isolate
&& only.extras().mask.is_none()
&& mask.is_none()
&& !knockout
{
// A knockout group drawn in a form that carries its opacity, blend mode or
// isolation: one knockout group with them.
let mut only = (**only).clone();
(only.opacity, only.blend, only.isolate) = (opacity, blend, isolate);
Some(only)
} else {
let mut g = Node::group(self.id(), children);
(g.opacity, g.blend, g.isolate) = (opacity, blend, isolate);
+26
View File
@@ -175,6 +175,32 @@ fn an_isolated_knockout_group_is_one_isolated_pdf_knockout_group() {
}
}
/// A translucent knockout group is written as a form with its opacity around the `/K` group, and
/// reads back as one knockout group with that opacity and its isolation.
#[test]
fn a_translucent_knockout_group_reads_back_as_one_group() {
for isolate in [false, true] {
let mut d = knockout_squares(3, false, isolate);
let g = d.layers[0].children().unwrap()[1].id;
d.node_mut(g).unwrap().opacity = 0.5;
let top = d.node(g).unwrap().children().unwrap()[2].id;
d.node_mut(top).unwrap().blend = BlendMode::Multiply;
let back = import(&export(&d, &PdfOptions::default()).unwrap()).unwrap();
let mut found = vec![];
back.walk(|n| {
if matches!(n.kind, vectorcraft_doc::NodeKind::Group { .. }) {
found.push((
n.knockout == vectorcraft_doc::Knockout::On,
n.isolate,
(n.opacity * 100.0).round(),
n.children().map_or(0, |c| c.len()),
));
}
});
assert_eq!(found, [(true, isolate, 50.0, 3)], "isolate {isolate}");
}
}
/// Elements whose opacity defines their knockout shape knock out less than a PDF knockout group
/// knocks out: each is drawn through one soft mask of the elements above it, so that 200 of them
/// still read back, every one of them.