Files
vectorcraft/crates/engine/src/cmd/printadvanced.rs
T
arhammusheer 5beac4f122 #1244: smaller objects: Node 352 -> 152 bytes, NodeKind 200 -> 80, and a cheaper undo estimate
Rarely set fields move into one optional boxed NodeExtras, and large NodeKind payloads (images,
live shapes, blends, envelopes) are boxed. Every object, and every undo step that copies it,
shrinks: a 50k-path move keeps ~24.5 MB per step instead of ~40 MB. Files stay byte-identical
(Node keeps its flat JSON through a borrowed Serialize and a from-wire Deserialize, no flatten),
and loading is slightly faster. Undo and redo reuse their step's size estimate instead of
walking the document again (8.3 -> 5.6 ms for a 50k-path move).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc

# Conflicts:
#	crates/engine/src/cmd/graph.rs
#	crates/svg/src/export.rs
2026-10-12 11:02:32 +07:00

108 lines
5.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! File → Print › Advanced, what needs the renderer and the flattener (the PDF writer does the
//! rest): Print as Bitmap renders each printed region as one image, and a flattener preset
//! flattens transparency, both on the document as it prints (only the layers that print,
//! overprints discarded or simulated in composite output) before it is laid out on paper.
use std::sync::Arc;
use vectorcraft_doc::marks::outset;
use vectorcraft_doc::{Document, ImageBlob, ImageObject, LayerColor, Node, NodeKind};
use vectorcraft_geom::{Affine, Rect};
use vectorcraft_pdf::{OutputMode, PrintOverprints, PrintSettings};
use vectorcraft_render::{AntiAlias, RenderOptions};
use super::fileio::pdf::pdf_error;
use super::flatten::{FlattenOptions, FlattenerPreset, flatten_document};
use super::menucmds::{MAX_PIXELS, unique_key};
use super::*;
/// The flattener options of `set`'s preset (`None`: transparency prints live); an error naming
/// `cmd` for a preset that is neither built in nor in `saved`.
pub(crate) fn flattener(cmd: &str, set: &PrintSettings, saved: &[FlattenerPreset]) -> Result<Option<FlattenOptions>> {
let name = set.advanced.flattener_preset.trim();
if name.is_empty() {
return Ok(None);
}
let o = FlattenOptions::find_preset(name, saved)
.ok_or_else(|| bad(cmd, format!("flattenerPreset `{name}`: high, medium, low or a saved one (see flattener.presets.list)")))?;
Ok(Some(o))
}
/// `doc` as Print as Bitmap and the flattener preset of `set` print it (from `saved` presets), or
/// `None` when neither applies. Print as Bitmap is for composite output.
pub(crate) fn prepare(cmd: &str, doc: &Document, set: &PrintSettings, saved: &[FlattenerPreset]) -> Result<Option<Document>> {
let composite = set.output.mode == OutputMode::Composite;
let bitmap = set.advanced.print_as_bitmap && composite;
let flattener = flattener(cmd, set, saved)?;
if !bitmap && flattener.is_none() {
return Ok(None);
}
let printed = vectorcraft_pdf::printed_document(doc, set);
if bitmap {
return bitmap_document(cmd, &printed, set).map(Some);
}
let Some(mut o) = flattener else { return Ok(None) };
// Composite overprints that were discarded or simulated aren't preserved either.
o.preserve_overprints &= !composite || set.advanced.overprints == PrintOverprints::Preserve;
Ok(Some(flatten_document(&printed, &o)?.unwrap_or_else(|| printed.into_owned())))
}
/// Leave guide paths out of `nodes` (the renderer draws them; they never print).
fn drop_guides(nodes: &mut Vec<Arc<Node>>) {
fn has_guides(n: &Node) -> bool {
matches!(n.kind, NodeKind::Path { guide: true, .. }) || n.children().is_some_and(|c| c.iter().any(|c| has_guides(c)))
}
nodes.retain(|n| !matches!(n.kind, NodeKind::Path { guide: true, .. }));
for n in nodes.iter_mut().filter(|n| has_guides(n)) {
if let Some(children) = Arc::make_mut(n).children_mut() {
drop_guides(children);
}
}
}
/// `doc` (as it prints) with its art replaced by one image of each printed region (an artboard
/// with its bleed, or all the art), at the document's raster effects resolution; regions where
/// nothing is drawn get none.
fn bitmap_document(cmd: &str, doc: &Document, set: &PrintSettings) -> Result<Document> {
let regions = vectorcraft_pdf::print_regions(doc, set).map_err(|e| pdf_error(cmd, e))?;
let bleed = set.bleed.of(doc);
let mut art = doc.clone();
drop_guides(&mut art.layers);
let anti_alias = if doc.raster_effects.anti_alias { AntiAlias::Art } else { AntiAlias::None };
let opts = RenderOptions { skip_templates: true, anti_alias, ..Default::default() };
let mut out = doc.clone();
let mut renderer = vectorcraft_render::Renderer::new();
let mut images = vec![];
for (_, r) in regions {
let area = outset(r, bleed);
// At most MAX_PIXELS, and 65535 pixels a side.
let side = area.width().max(area.height()).max(1.0);
let scale = (doc.raster_effects_ppi / 72.0).min((MAX_PIXELS / (area.width() * area.height()).max(1.0)).sqrt()).min(65_535.0 / side);
if !(scale.is_finite() && scale > 0.0) {
continue;
}
// Whole pixels.
let region = Rect::new(
area.x0,
area.y0,
area.x0 + (area.width() * scale).ceil().max(1.0) / scale,
area.y0 + (area.height() * scale).ceil().max(1.0) / scale,
);
let img = renderer.render_region_with(&art, region, scale, &opts);
if img.pixels.as_chunks::<4>().0.iter().all(|px| px[3] == 0) {
continue;
}
let key = unique_key(&out, "print-bitmap");
out.images.insert(key.clone(), ImageBlob::new("image/png", img.to_png().map_err(EngineError::Other)?));
let xf = Affine::translate(region.origin().to_vec2()) * Affine::scale(1.0 / scale);
let image = ImageObject { key, width: img.width, height: img.height, xf, link: None, placement: Default::default() };
images.push(Arc::new(Node::new(out.alloc_id(), NodeKind::Image(Box::new(image)))));
}
let mut layer = Node::layer(out.alloc_id(), "Bitmap", LayerColor::Preset(0));
if let Some(children) = layer.children_mut() {
*children = images;
}
out.layers = vec![Arc::new(layer)];
Ok(out)
}