Preflight: a frame with states is looked at as PDF export draws it (review)

Flex Layout's convert turns a multi-state group into a frame that keeps
its states. PDF export draws every object pasted into a frame, but the
white overprint check walked only the active state, so a white
overprint in another state printed unreported. Which children export
draws is now one function, drawn_children, used by both.
This commit is contained in:
Zinadin Zidan committed 2026-11-07 03:07:28 +06:00
1 parent ecf7e23085
commit cd315908aa
6 files changed
+37 -8

No files matched your search

@@ -15,7 +15,7 @@ use designcraft_color::{Color, ColorType, SwatchValue, swatch};
use designcraft_doc::cjk_settings::AdornmentOverprint;
use designcraft_doc::{AnchoredObject, CharProps, Content, Document, Item, ItemId, SpreadRef, Story, StrokeType};
use designcraft_geom::Affine;
use designcraft_pdf::{item_exported, layer_exported, shown_parent_items};
use designcraft_pdf::{drawn_children, item_exported, layer_exported, shown_parent_items};
use super::{Issue, page_of};
use crate::Session;
@@ -126,7 +126,7 @@ pub(super) fn printed(s: &Session, d: &Document) -> (Vec<Issue>, BTreeMap<ItemId
On::Page(page) => (page, on),
};
issues.extend(item_issue(d, it, page));
let children: Vec<_> = it.shown_children().map(|c| (Node::Item(c.clone()), inside, composed)).collect();
let children: Vec<_> = drawn_children(it).map(|c| (Node::Item(c.clone()), inside, composed)).collect();
if let Content::Text(tf) = &it.content {
frames.entry(it.id).or_insert(page);
let cs = s.cache.get(d, tf.story, composed);
@@ -235,3 +235,22 @@ fn what_pdf_export_leaves_out_is_not_reported() {
s.execute("object.hide", &json!({"ids": [group]})).unwrap();
assert_eq!(white_overprints(&mut s), [(PAPER_KENTEN.to_string(), r["id"].as_u64())]);
}
/// A multi-state object prints its active state only, but a frame made from one (Flex Layout's
/// convert keeps the states) prints every object in it, as PDF export draws a frame's pasted-in
/// objects: the inactive state's white overprint was left out (noted in review).
#[test]
fn a_frame_with_states_is_looked_at_as_pdf_export_draws_it() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let a = rect(&mut s, [72.0, 72.0, 200.0, 200.0]);
paper_overprint(&mut s, a);
let b = rect(&mut s, [300.0, 72.0, 400.0, 200.0]);
let ms = s.execute("states.create", &json!({"ids": [a, b]})).unwrap()["id"].as_u64().unwrap();
let at = s.doc().unwrap().doc.item(designcraft_doc::ItemId(ms)).unwrap().children().iter().position(|c| c.id.0 == a).unwrap();
s.execute("states.show", &json!({"id": ms, "index": 1 - at})).unwrap();
assert!(white_overprints(&mut s).is_empty(), "an inactive state doesn't print");
s.execute("flex.convert", &json!({"ids": [ms]})).unwrap();
assert!(!s.doc().unwrap().doc.item(designcraft_doc::ItemId(ms)).unwrap().states.is_empty(), "the frame keeps its states");
assert_eq!(white_overprints(&mut s), [(PAPER_FILL.to_string(), Some(a))]);
}
+3 -1
View File
@@ -46,7 +46,9 @@ pub(crate) use images::SRGB_ICC;
use images::{CmykImage, GreyImage, ImageContentCache, RasterMode, SrgbImage, embedded_icc, is_grey, is_grey_icc};
pub(crate) use metadata::civil;
pub(crate) use pipeline::{MARK_LEN, Sheet, exported_items, mark_start};
pub use pipeline::{export_pdf, export_pdf_with_report, item_exported, layer_exported, print_color, sheet_spreads, shown_parent_items};
pub use pipeline::{
drawn_children, export_pdf, export_pdf_with_report, item_exported, layer_exported, print_color, sheet_spreads, shown_parent_items,
};
#[cfg(test)]
impl std::hash::Hasher for ConstantImageHasher {
fn finish(&self) -> u64 {
+8
View File
@@ -30,6 +30,14 @@ pub fn item_exported(it: &Item) -> bool {
!it.hidden && !it.nonprinting
}
/// The items PDF export draws inside `it`: every item pasted into a frame (a frame keeps the
/// states of a multi-state group converted to it, and draws them all), or a group's shown
/// children (only the active state of a multi-state object).
pub fn drawn_children(it: &Item) -> impl Iterator<Item = &Arc<Item>> {
let nested = it.has_nested_items();
it.children().iter().filter(move |_| nested).chain(it.shown_children().filter(move |_| !nested))
}
/// The parent pages' items that page `pi` of spread `si` shows, on exported layers, each with the
/// offset that moves it onto the page: the parent's bases first, then the parent. None when the
/// page hides parent items; the items the page overrides are left out (their overrides are the
+3 -3
View File
@@ -1,6 +1,6 @@
use super::common::{TagEntry, blend, norm, rects_overlap, solid_fill, tf, to_path};
use super::graphics::draw_coverage;
use super::pipeline::{item_exported, layer_exported, shown_parent_items};
use super::pipeline::{drawn_children, item_exported, layer_exported, shown_parent_items};
use super::*;
impl Exporter<'_> {
@@ -249,7 +249,7 @@ impl Exporter<'_> {
if let Some(c) = &clip {
s.push_clip_path(c, &FillRule::NonZero);
}
for child in it.children() {
for child in drawn_children(it) {
self.item(s, child, xf, page);
}
if clip.is_some() {
@@ -269,7 +269,7 @@ impl Exporter<'_> {
fn draw_group_children(&mut self, s: &mut Surface, it: &Item, xf: Affine, page: Option<usize>) {
let pushes = Self::push_group(s, it.opacity, it.blend) + Self::push_isolation(s, it);
for child in it.shown_children() {
for child in drawn_children(it) {
self.item(s, child, xf, page);
}
for _ in 0..pushes {
+2 -2
View File
@@ -49,8 +49,8 @@ mod pdfx;
mod pdfx1a;
mod transitions;
pub use export::{
BookletKind, BookletOptions, booklet_pairs, export_booklet, export_pdf, export_pdf_with_report, item_exported, layer_exported, merge_pdfs,
print_color, sheet_spreads, shown_parent_items,
BookletKind, BookletOptions, booklet_pairs, drawn_children, export_booklet, export_pdf, export_pdf_with_report, item_exported, layer_exported,
merge_pdfs, print_color, sheet_spreads, shown_parent_items,
};
pub use pdfx::{check_pdfx4, has_rgb_groups, make_pdfx4, pdfx_version};
pub use pdfx1a::check_pdfx1a;