Print: every copy on a sheet draws the same symbol instances

Each copy on a sheet draws within an equal share of the symbol objects the
export has left (MAX_SYMBOL_NODES), so a document near the limit leaves out
the same instances in every copy instead of dropping them from the later
copies. The limit stays export-wide; one copy per page draws as before.
This commit is contained in:
dotCooCoo
2026-11-08 18:22:08 -08:00
parent b9f735dd9a
commit 967d77919f
5 changed files with 76 additions and 13 deletions
+3
View File
@@ -118,6 +118,9 @@ pub(crate) struct Exporter<'a> {
/// Objects the symbol instances written held, together
/// ([`vectorcraft_doc::MAX_SYMBOL_NODES`]).
symbol_nodes: u64,
/// The most [`Self::symbol_nodes`] may reach: [`vectorcraft_doc::MAX_SYMBOL_NODES`], or less
/// while one of the copies on a sheet is drawn ([`Writer::page`]).
pub(crate) symbol_limit: u64,
}
#[cfg(test)]
+1
View File
@@ -47,6 +47,7 @@ impl<'a> Exporter<'a> {
in_layer: 0,
paper: Rect::ZERO,
symbol_nodes: 0,
symbol_limit: vectorcraft_doc::MAX_SYMBOL_NODES,
overprint: false,
overprinted: false,
invisible: false,
+16 -13
View File
@@ -272,20 +272,23 @@ impl Exporter<'_> {
let count = art.count() as u64;
let map = doc.symbol_slice_map(symbol, *xf);
let most = map.map_or(count, |m| m.most_placed(art, doc));
let placed = vectorcraft_doc::charge_symbol_art(&mut self.symbol_nodes, most, || {
let art = match map {
Some(m) => m.place(art, doc),
None => {
let mut a = art.clone();
a.transform(*xf, false);
a
}
};
// As `most_placed`, placed art counts its opacity-mask art.
let count = if map.is_some() { art.count_with_masks() } else { art.count() } as u64;
(art, count)
let within = self.symbol_nodes.saturating_add(most) <= self.symbol_limit;
let placed = within.then(|| {
vectorcraft_doc::charge_symbol_art(&mut self.symbol_nodes, most, || {
let art = match map {
Some(m) => m.place(art, doc),
None => {
let mut a = art.clone();
a.transform(*xf, false);
a
}
};
// As `most_placed`, placed art counts its opacity-mask art.
let count = if map.is_some() { art.count_with_masks() } else { art.count() } as u64;
(art, count)
})
});
if let Some(art) = placed {
if let Some(art) = placed.flatten() {
self.node(s, &art, page, true);
}
}
+6
View File
@@ -149,7 +149,12 @@ impl Writer {
let doc = ex.doc;
let page_group = fits && !self.pdf13 && (doc.page_isolate || doc.page_knockout);
let cmyk_page_group = fits && self.cmyk && !page_group && ex.shows_transparency();
// Each copy draws within an equal share of the symbol objects the export has left
// ([`vectorcraft_doc::MAX_SYMBOL_NODES`]), so every copy leaves out the same instances.
let limit = ex.symbol_limit;
let share = limit.saturating_sub(ex.symbol_nodes) / moves.len() as u64;
for m in moves {
ex.symbol_limit = ex.symbol_nodes.saturating_add(share);
let place = place_at(*m);
let placed = place != Affine::IDENTITY;
if placed {
@@ -180,6 +185,7 @@ impl Writer {
s.pop();
}
}
ex.symbol_limit = limit;
if fits && let Some(art) = sheet.marks {
// The drawing space the page shows (the marks' area).
let shown = sheet.view.inverse().transform_rect_bbox(paper);
+50
View File
@@ -301,6 +301,56 @@ fn each_copy_prints_with_its_marks() {
assert_eq!(marks, 8 * copies, "8 trim marks around each copy");
}
#[test]
fn every_copy_on_a_sheet_draws_the_same_symbol_instances() {
// A symbol of three squares (4 objects with its group), placed twice.
let mut d = doc(100.0, 100.0, &[]);
let squares = (0..3)
.map(|i| {
let r = Rect::new(f64::from(i) * 10.0, 0.0, f64::from(i) * 10.0 + 5.0, 5.0);
Arc::new(Node::path(d.alloc_id(), shapes::rectangle(r), Appearance::basic(rgb(1.0, 0.0, 0.0), Paint::None, 0.0)))
})
.collect();
let art = Node::group(d.alloc_id(), squares);
d.symbols.push(vectorcraft_doc::Symbol { name: "Squares".into(), art: Arc::new(art) });
let layer = d.default_layer().unwrap();
for y in [10.0, 50.0] {
let instance = NodeKind::SymbolInstance { symbol: "Squares".into(), xf: vectorcraft_geom::Affine::translate((10.0, y)) };
let id = d.alloc_id();
d.insert(Some(layer), 0, Node::new(id, instance)).unwrap();
}
let set = PdfSettings { compression: CompressionSettings { compress_text: false, ..Default::default() }, ..Default::default() };
let mut w = crate::export::Writer::new(&d, &set, "Copies", None, false).unwrap();
let mut ex = crate::export::Exporter::new(&d, &set);
// Room for 10 symbol objects: the first copy could write both instances (8) and leave the
// second copy none.
ex.symbol_limit = 10;
let copies = [vectorcraft_geom::Vec2::ZERO, vectorcraft_geom::Vec2::new(200.0, 0.0)];
let page = Rect::new(0.0, 0.0, 300.0, 100.0);
let sheet = crate::export::Sheet {
size: (300.0, 100.0),
unit: 1.0,
trim: page,
bleed: page,
view: vectorcraft_geom::Affine::IDENTITY,
window: None,
place: vectorcraft_geom::Affine::IDENTITY,
copies: &copies,
area: Rect::new(0.0, 0.0, 100.0, 100.0),
clip: false,
marks: None,
negative: false,
links: false,
};
w.page(&mut ex, &sheet).unwrap();
assert_eq!(ex.symbol_limit, 10, "the limit is the export's again after the sheet");
w.absorb(ex);
let (bytes, _) = w.finish(&mut std::collections::HashSet::new(), false).unwrap();
let p = pages(&bytes);
let left = p[0].1.iter().filter(|(r, _)| r.x0 < 200.0).count();
assert_eq!((left, p[0].1.len() - left), (3, 3), "one instance in each copy: {:?}", p[0].1);
}
#[test]
fn tiles_cover_the_artboard_with_overlap() {
// 1000 × 700 on Letter tiles (612 × 792), 36 pt overlap: 2 × 1.