//! 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> { 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> { 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>) { 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 { 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(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) }