mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 09:39:52 +00:00
Ruler guides were a list kept beside the layers. Each guide is now an object of its layer or sublayer, always directly in it and above the layer's art. These behave as on main: - Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All, Select All, the art bounds, clicks and marquees, and a layer's Pathfinder and Transform effects leave them out. The Layers panel lists them as guide rows of their layer, the highest guide.list index first. - Deleting a layer deletes its guides, and merging layers keeps their guides in the merged layer. - The guide commands number guides as guide.list does, in the order they were made. New: each guide takes an id from the document's object ids. An object command given a guide's id fails with an error, and node.move cannot put a guide into a group or out of its layers. A guide made while an opacity mask, a symbol or a pattern is edited moves to the top visible, unlocked layer when editing ends. File format: native files still save the guides in the document's guides list, in every file version, so older builds read them. Each entry may now also carry the guide's id, name, locked and hidden flags; older builds ignore those fields. Opening a file puts each saved guide on its layer (on the top visible, unlocked layer when the file names none or a layer it does not have), and each saved guide counts against the object limit for opening a file. A ruler guide node that a file holds below a layer's art moves up to the layer's guides, and one anywhere else (in a group or another object, beside the layers, or in a symbol) is removed. Ruler guides read from an .ai file become guide objects on the layer they were on, numbered in the order the file lists them.
112 lines
5.5 KiB
Rust
112 lines
5.5 KiB
Rust
//! 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::{EngineError, Result, bad};
|
||
|
||
/// 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.
|
||
/// `rich_black`: Appearance of Black › Printing / Exporting › Output All Blacks as Rich Black.
|
||
pub(crate) fn prepare(cmd: &str, doc: &Document, set: &PrintSettings, saved: &[FlattenerPreset], rich_black: bool) -> 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, rich_black).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 guides out of `nodes`: guide paths (the renderer draws them; they never print) and ruler
|
||
/// guides.
|
||
fn drop_guides(nodes: &mut Vec<Arc<Node>>) {
|
||
fn has_guides(n: &Node) -> bool {
|
||
n.is_guide() || n.children().is_some_and(|c| c.iter().any(|c| has_guides(c)))
|
||
}
|
||
nodes.retain(|n| !n.is_guide());
|
||
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, rich_black: bool) -> 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, rich_black, ..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 no side longer than the renderer draws (rounded up to whole
|
||
// pixels below).
|
||
let side = area.width().max(area.height()).max(1.0);
|
||
let longest = f64::from(vectorcraft_render::MAX_RASTER_SIDE) - 2.0;
|
||
let scale = (doc.raster_effects_ppi / 72.0).min((MAX_PIXELS / (area.width() * area.height()).max(1.0)).sqrt()).min(longest / 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(), pixels: None };
|
||
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)
|
||
}
|