Placed documents: File › Place links a VectorCraft document by default; output reads its file

This commit is contained in:
haven
2026-10-08 12:20:51 -07:00
parent 6d56195eda
commit afc45cdeb8
10 changed files with 575 additions and 21 deletions
+16
View File
@@ -364,6 +364,22 @@ pub fn encode_with_warnings(doc: &Document, format: &str, p: &Value) -> Result<(
/// [`encode`], with every file the export writes.
pub fn encode_all(doc: &Document, format: &str, p: &Value) -> Result<Encoded> {
// Placed documents output their files' art (a native file keeps what it keeps: see
// `native::encode`).
if !matches!(format, "vectorcraft" | "template") {
let (full, warnings) = crate::cmd::place::document::full_documents(doc);
if !warnings.is_empty() {
let mut enc = encode_all_of(&full, format, p)?;
enc.warnings.extend(warnings);
return Ok(enc);
}
return encode_all_of(&full, format, p);
}
encode_all_of(doc, format, p)
}
/// [`encode_all`] of `doc` as it is.
fn encode_all_of(doc: &Document, format: &str, p: &Value) -> Result<Encoded> {
let f = super::writable(C, Some(format), None)?;
let mut enc = encode_files(doc, f, p)?;
// Formats that draw type: missing fonts come out in the fallback font (PDF and SVG say so
+7
View File
@@ -127,6 +127,13 @@ pub(super) fn save_options(cmd: &str, f: &Format, doc: &Document, p: &Value) ->
/// The `.vectorcraft` file of `doc` with the options in `p`.
pub(super) fn encode(cmd: &str, f: &Format, doc: &Document, p: &Value) -> Result<Vec<u8>> {
let (mut so, extras) = save_options(cmd, f, doc, p)?;
// Include Linked Files keeps placed documents' files; older versions get their art.
let full = if so.include_linked || so.version < vectorcraft_format::VERSION {
crate::cmd::place::document::full_documents(doc).0
} else {
std::borrow::Cow::Borrowed(doc)
};
let doc = &*full;
if extras.preview {
so.preview = preview_png(doc)?;
}
+1 -1
View File
@@ -48,7 +48,7 @@ mod pathops;
mod patterncmds;
pub mod pdfcmds;
pub mod perspgrid;
mod place;
pub(crate) mod place;
pub mod plugin;
pub mod prefscmds;
pub mod print;
+2 -1
View File
@@ -67,6 +67,7 @@ fn refs(n: &mut Node, f: &mut impl FnMut(Ref)) {
}
}
NodeKind::Image(im) => f(Ref::Image(&mut im.key)),
NodeKind::PlacedDocument(pl) => f(Ref::Image(&mut pl.key)),
NodeKind::SymbolInstance { symbol, .. } => f(Ref::Symbol(symbol)),
_ => {}
}
@@ -129,7 +130,7 @@ fn copies(m: &Renames) -> Vec<(&str, &str)> {
/// Bring the resources `nodes` (art from `src`) use into `dst`, renaming on conflict, and point
/// `nodes` at the names they got.
pub(super) fn adopt(dst: &mut Document, src: &Document, nodes: &mut [Node]) {
pub(crate) fn adopt(dst: &mut Document, src: &Document, nodes: &mut [Node]) {
let mut used = Used::default();
for n in nodes.iter_mut() {
refs(n, &mut |r| used.note(r));
+246
View File
@@ -0,0 +1,246 @@
//! Placed documents (`file.place` of a VectorCraft document from a file, with `link`): one
//! locked object showing an artboard of the document, a [`PlacedDocument`].
//!
//! The object keeps the file's bytes (in the document's images, like image data) and the link; the
//! art it shows is read from them the way placing the file without Link reads it
//! ([`document_art`]: the artboard's art, with the file's own linked images found). The Links
//! commands ([`super::super::links`]) tell when the file changed and read it again.
//!
//! Each placed file also gets a preview ([`PREVIEW_PX_PER_PT`]; JPEG when opaque, else PNG), kept
//! as the blob's [`ImageBlob::proxy`]: a save writes only the preview (unless Include Linked
//! Files), as for linked images. The file is read again when it is needed ([`read_file_again`]:
//! output, or the screen zoomed past the preview), while it is unchanged, and output falls back to
//! the preview with a warning when it can't be ([`full_documents`]).
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::sync::Arc;
use vectorcraft_doc::links::hash_bytes;
use vectorcraft_doc::placed_document::{PREVIEW_MAX, PREVIEW_PX_PER_PT, key_for};
use vectorcraft_doc::{Document, ImageBlob, LinkInfo, Node, NodeKind, PlacedDocument, PlacementOptions};
use vectorcraft_geom::{Affine, Rect};
use super::super::fileio;
use super::super::*;
/// The largest file placed (bytes).
pub(crate) const MAX_FILE: usize = 256 << 20;
/// The format ids of VectorCraft documents.
const NATIVE: [&str; 2] = ["vectorcraft", "template"];
/// The type a placed file's bytes are stored with.
const MIME: &str = "application/json";
/// Is `name` with `bytes` a VectorCraft document?
pub(crate) fn is_native(name: &str, bytes: &[u8]) -> bool {
fileio::detect(name, bytes).is_some_and(|f| NATIVE.contains(&f.id))
}
/// An artboard of a VectorCraft document, read as art to place.
pub(crate) struct DocumentArt {
/// The document, its layers taken: the art's resources.
pub doc: Document,
/// The art on the artboard (guides, hidden and template layers left out).
pub nodes: Vec<Node>,
/// The artboard the art is clipped to (`None`: the art's bounds are shown).
pub clip: Option<Rect>,
/// What is shown: the artboard, or the art's bounds.
pub frame: Rect,
pub warnings: Vec<String>,
}
/// Artboard `page` (1-based) of the VectorCraft document `name` (`bytes`, read from `path` when
/// there is one, so its linked images are found), clipped to the artboard unless `bounding`.
pub(crate) fn document_art(name: &str, bytes: &[u8], path: Option<&str>, page: u32, bounding: bool, cmd: &str) -> Result<DocumentArt> {
let file = fileio::file_name(name);
if !is_native(name, bytes) {
return Err(bad(cmd, format!("`{file}` isn't a VectorCraft document")));
}
if bytes.len() > MAX_FILE {
return Err(bad(cmd, format!("`{file}` is over {} MB", MAX_FILE >> 20)));
}
let mut l = fileio::load(name, bytes)?;
// Its linked images show their files; its placed documents stay as they are.
super::super::links::resolve(&mut l.doc, path, false);
l.doc.drop_edit_modes();
let n = l.doc.artboards.len();
let board = (page as usize).checked_sub(1).and_then(|i| l.doc.artboards.get(i)).map(|a| a.rect);
let board = board.ok_or_else(|| bad(cmd, format!("page {page}: `{file}` has {n} artboard(s)")))?;
let layers = std::mem::take(&mut l.doc.layers);
let nodes = super::on_artboard(&layers, board);
let frame = if bounding { nodes.iter().fold(None, |acc, n| vectorcraft_geom::union_opt(acc, n.visual_bounds())) } else { Some(board) };
let frame = frame.filter(|r| r.width() > 0.0 || r.height() > 0.0).ok_or_else(|| bad(cmd, format!("`{file}` has no art to place")))?;
Ok(DocumentArt { doc: l.doc, nodes, clip: (!bounding).then_some(board), frame, warnings: l.warnings })
}
/// `nodes` as one group, clipped to `clip` (its own ids: what drawing reads).
fn group(nodes: Vec<Node>, clip: Option<Rect>) -> Node {
let mut children: Vec<Arc<Node>> = nodes.into_iter().map(Arc::new).collect();
if let Some(r) = clip {
children.insert(0, Arc::new(super::clip_path(NodeId(0), r)));
}
Node::new(NodeId(0), NodeKind::Group { children, clip: clip.is_some() })
}
/// `p`'s file (`bytes`) read as the art it shows: how the document model reads placed documents
/// ([`vectorcraft_doc::placed_document::set_loader`]).
pub(crate) fn read_document(p: &PlacedDocument, bytes: &[u8]) -> Option<(Document, Node, Rect)> {
let a = document_art(p.link.name(), bytes, Some(&p.link.path), p.page(), p.bounding, "draw").ok()?;
Some((a.doc, group(a.nodes, a.clip), a.frame))
}
/// `p`'s file read again, while it is the file it was read from (how the document model gets it
/// when only the preview is stored, [`vectorcraft_doc::placed_document::set_file_reader`]).
pub(crate) fn read_file_again(p: &PlacedDocument) -> Option<Vec<u8>> {
let bytes = fileio::read_file(&p.link.path).ok()?;
if p.link.hash.as_ref().is_some_and(|h| *h != hash_bytes(&bytes)) {
return None;
}
Some(bytes)
}
/// A VectorCraft document read for placing linked.
#[derive(Clone)]
pub(crate) struct Source {
/// The file's bytes, with their preview.
pub blob: ImageBlob,
/// The blob's key ([`key_for`]).
pub key: String,
/// What is shown, in the file's coordinates: the artboard or the art's bounds.
pub frame: Rect,
pub warnings: Vec<String>,
}
/// The VectorCraft document `name` (`bytes`, from `path` when there is one) read for showing
/// artboard `page` (its art's bounds when `bounding`).
pub(crate) fn source(name: &str, bytes: &[u8], path: Option<&str>, page: u32, bounding: bool, cmd: &str) -> Result<Source> {
// The same document placed the same way is read once (placing it again is free).
let key = key_for(&hash_bytes(bytes), page, bounding);
if let Some(s) = lock_memo().iter().find(|s| s.key == key).cloned() {
return Ok(s);
}
let a = document_art(name, bytes, path, page, bounding, cmd)?;
let mut blob = ImageBlob::new(MIME, bytes.to_vec());
let shown = PlacedDocument {
link: LinkInfo { page: Some(page), ..LinkInfo::new(path.unwrap_or(name)) },
key: key.clone(),
bounding,
width: a.frame.width(),
height: a.frame.height(),
xf: Affine::IDENTITY,
placement: PlacementOptions::default(),
};
let art = group(a.nodes, a.clip);
blob.proxy = preview(&a.doc, &art, a.frame).map(Arc::new);
// Drawing it needn't read it again.
vectorcraft_doc::placed_document::prime(&shown, blob.bytes.clone(), (a.doc, art, a.frame));
let s = Source { blob, key, frame: a.frame, warnings: a.warnings };
let mut m = lock_memo();
if m.len() >= MEMO_SIZE {
m.remove(0);
}
m.push(s.clone());
Ok(s)
}
/// The most files remembered.
const MEMO_SIZE: usize = 32;
/// Files read lately for placing, by key.
static MEMO: std::sync::Mutex<Vec<Source>> = std::sync::Mutex::new(Vec::new());
fn lock_memo() -> std::sync::MutexGuard<'static, Vec<Source>> {
// Entries are only pushed and removed: a panic while held leaves it whole.
MEMO.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
}
/// The preview of `art` (of document `doc`, showing `frame`): [`PREVIEW_PX_PER_PT`], at most
/// [`PREVIEW_MAX`] pixels a side; JPEG when opaque (inside its outer pixels), else PNG.
fn preview(doc: &Document, art: &Node, frame: Rect) -> Option<Vec<u8>> {
let k = PREVIEW_PX_PER_PT.min(PREVIEW_MAX as f64 / frame.width().max(frame.height()).max(1e-9));
let side = |v: f64| (v * k).round().clamp(1.0, PREVIEW_MAX as f64) as u32;
let (w, h) = (side(frame.width()), side(frame.height()));
let view = Affine::scale_non_uniform(w as f64 / frame.width().max(1e-9), h as f64 / frame.height().max(1e-9))
* Affine::translate((-frame.x0, -frame.y0));
let mut r = vectorcraft_render::Renderer::new();
let img = vectorcraft_render::placed_document::render_inside(&mut r, doc, &Arc::new(art.clone()), w as u16, h as u16, view)?;
// Premultiplied → straight.
let px = img
.pixels
.as_chunks::<4>()
.0
.iter()
.flat_map(|&[r, g, b, a]| {
let un = |c: u8| if a == 0 { 0 } else { ((c as u32 * 255 + a as u32 / 2) / a as u32).min(255) as u8 };
[un(r), un(g), un(b), a]
})
.collect();
let rgba = image::RgbaImage::from_raw(w, h, px)?;
let mut out = vec![];
// Opaque but for the edge's anti-aliasing (an artboard drawn to whole pixels) and compositing's
// rounding: JPEG.
let inner = |x: u32, y: u32| x > 0 && y > 0 && x + 1 < w && y + 1 < h;
if rgba.enumerate_pixels().all(|(x, y, p)| p[3] >= 250 || !inner(x, y)) {
let rgb = image::DynamicImage::ImageRgba8(rgba).to_rgb8();
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, 80).encode_image(&rgb).ok()?;
} else {
rgba.write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png).ok()?;
}
Some(out)
}
/// `s` stored in `d` (objects showing the same thing share one blob).
pub(crate) fn store(d: &mut Document, s: &Source) {
d.images.entry(s.key.clone()).or_insert_with(|| s.blob.clone());
}
/// A new placed document of `d` showing `s` at 100%, where its art lies in the file.
pub(crate) fn node(d: &mut Document, s: &Source, link: LinkInfo, bounding: bool) -> Node {
store(d, s);
let placed = PlacedDocument {
link,
key: s.key.clone(),
bounding,
width: s.frame.width(),
height: s.frame.height(),
xf: Affine::translate((s.frame.x0, s.frame.y0)),
placement: PlacementOptions::default(),
};
Node::new(d.alloc_id(), NodeKind::PlacedDocument(Box::new(placed)))
}
/// `d` for output: the placed documents that keep only their previews get their files back,
/// else (their files gone or changed) output as their previews, with a warning.
pub(crate) fn full_documents(d: &Document) -> (Cow<'_, Document>, Vec<String>) {
let mut need: Vec<PlacedDocument> = vec![];
d.visit_placed(|_, p| {
if d.images.get(&p.key).is_some_and(ImageBlob::is_proxy) && !need.iter().any(|n| n.key == p.key) {
need.push(p.clone());
}
});
if need.is_empty() {
return (Cow::Borrowed(d), vec![]);
}
let mut out = d.clone();
let mut previews = BTreeSet::new();
for p in need {
let Some(blob) = d.images.get(&p.key) else { continue };
match vectorcraft_doc::placed_document::full_bytes(&p, blob) {
Some(bytes) => {
out.images.insert(p.key.clone(), ImageBlob { mime: MIME.into(), bytes, proxy: blob.proxy.clone() });
}
None => {
previews.insert(p.key);
}
}
}
let mut warnings = vec![];
if !previews.is_empty() {
warnings.push(format!(
"{} placed document(s) output as their previews: their files can't be found, or changed since they were read (relink or update them)",
previews.len()
));
out.placed_as_previews(&previews);
}
(Cow::Owned(out), warnings)
}
+44 -18
View File
@@ -16,13 +16,17 @@
//! (`crop: "bounding"`).
//! - A text file becomes area type, decoded and cleaned up with the Text Import Options (`text`,
//! [`text`]).
//! - A VectorCraft document placed from a file with Link (the default) becomes a placed document:
//! one locked object showing the artboard `page`, linked to the file and read again when it
//! changes ([`document`]); without Link, an editable copy of its art.
//!
//! The art lands centred on `at`, fitted into `rect`, or (Replace) where the replaced object was,
//! with its transform; Template puts it on a new template layer. The images, symbols, patterns and
//! swatches it uses join the document ([`adopt`]). `file.place.queue` loads the place cursor (the
//! `place` tool) with several files, which emits one `file.place` per click or drag.
mod adopt;
pub(crate) mod adopt;
pub(crate) mod document;
mod text;
use std::sync::Arc;
@@ -53,7 +57,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Place…",
["File"],
Some("Cmd+Shift+P"),
"{path | name+dataBase64, link?: true (a raster image keeps its file's path; other files are embedded), text?: {characterSet?: \"unicode\" (UTF-8, or UTF-16 with a byte-order mark; other bytes as the platform's 8-bit set) | \"ansi\" (the platform's 8-bit set), platform?: \"windows\" (Windows-1252) | \"mac\" (Mac Roman), removeLineReturns?: false (each block of lines becomes one paragraph; blank lines end paragraphs), removeParagraphReturns?: false (drop blank lines), replaceSpaces?: n (runs of n ≥ 2 spaces become a tab)} (a .txt file, placed as area type filling rect, the replaced object's bounds, or else the artboard less a 36 pt margin), template?: false (onto a new locked template layer below the current layer), replace?: false (swap the one selected object, keeping its stacking place and transform; no at/rect), at?: [x, y] centre (default: the first artboard's centre), rect?: [x, y, width, height] fit inside, aspect kept (wins over at), page?: 1 (PDF/.ai page, or a native document's artboard), crop?: \"crop\" (clipped to that page or artboard, default) | \"bounding\" (the art's bounds) | \"art\" | \"trim\" | \"bleed\" | \"media\" (a PDF page's boxes), password? (an encrypted PDF), dxf?: {…the DXF options of document.open} (a .dxf drawing: fit fills the artboard under at, else the first; fitted or uncentred art lands with its drawing's artboard on that artboard, else it is centred on at)} → {ids, name, format, linked, width, height, warnings}. Raster images come in at 100% of their physical size (the file's ppi, else 72); SVG, DXF, PDF/.ai, EMF/WMF (clipped to the picture's frame), EPS (clipped to its bounding box; as document.open reads it) and native documents as one group, with the images, symbols, patterns and swatches they use. One undo step; selects what it placed (unless on a template layer); never touches the clipboard",
"{path | name+dataBase64, link?: true (a raster image keeps its file's path; a VectorCraft document at path becomes one locked placed document showing artboard `page`, linked to the file and read again when it changes (links.update; saving it in this app updates it at once), drawn as vectors in every output; false: an editable copy of its art, as from dataBase64; other files are embedded), text?: {characterSet?: \"unicode\" (UTF-8, or UTF-16 with a byte-order mark; other bytes as the platform's 8-bit set) | \"ansi\" (the platform's 8-bit set), platform?: \"windows\" (Windows-1252) | \"mac\" (Mac Roman), removeLineReturns?: false (each block of lines becomes one paragraph; blank lines end paragraphs), removeParagraphReturns?: false (drop blank lines), replaceSpaces?: n (runs of n ≥ 2 spaces become a tab)} (a .txt file, placed as area type filling rect, the replaced object's bounds, or else the artboard less a 36 pt margin), template?: false (onto a new locked template layer below the current layer), replace?: false (swap the one selected object, keeping its stacking place and transform; no at/rect), at?: [x, y] centre (default: the first artboard's centre), rect?: [x, y, width, height] fit inside, aspect kept (wins over at), page?: 1 (PDF/.ai page, or a native document's artboard), crop?: \"crop\" (clipped to that page or artboard, default) | \"bounding\" (the art's bounds) | \"art\" | \"trim\" | \"bleed\" | \"media\" (a PDF page's boxes), password? (an encrypted PDF), dxf?: {…the DXF options of document.open} (a .dxf drawing: fit fills the artboard under at, else the first; fitted or uncentred art lands with its drawing's artboard on that artboard, else it is centred on at)} → {ids, name, format, linked, width, height, warnings}. Raster images come in at 100% of their physical size (the file's ppi, else 72); SVG, DXF, PDF/.ai, EMF/WMF (clipped to the picture's frame), EPS (clipped to its bounding box; as document.open reads it) and native documents as one group, with the images, symbols, patterns and swatches they use. One undo step; selects what it placed (unless on a template layer); never touches the clipboard",
has_doc,
place
),
@@ -94,6 +98,9 @@ enum Art {
Image(RasterImage),
/// A text file's text, placed as area type filling the natural box.
Text(String),
/// A VectorCraft document placed linked: the file read, the link, and whether the art's
/// bounds (not the artboard) are shown.
Document(document::Source, LinkInfo, bool),
Vector {
/// The loaded document: the source of the art's resources.
src: Box<Document>,
@@ -157,6 +164,19 @@ fn load(p: &Value, cmd: &str, board: Option<Rect>) -> Result<Loaded> {
));
}
let crop = opts.crop != CropTo::Bounding;
// A VectorCraft document: placed linked from its file, else as an editable copy.
if matches!(format.id, "vectorcraft" | "template") {
let page = page as u32;
if let Some(path) = src.path.filter(|_| bool_or(p, "link", true)) {
let s = document::source(src.name, &src.bytes, Some(path), page, !crop, cmd)?;
let link = LinkInfo { page: Some(page), ..super::links::link_info(path, &src.bytes) };
let (natural, warnings) = (s.frame, s.warnings.clone());
return Ok(Loaded { name, format, art: Art::Document(s, link.clone(), !crop), natural, warnings, link: Some(link), board: None });
}
let a = document::document_art(src.name, &src.bytes, src.path, page, !crop, cmd)?;
let art = Art::Vector { src: Box::new(a.doc), nodes: a.nodes, clip: a.clip };
return Ok(Loaded { name, format, art, natural: a.frame, warnings: a.warnings, link: None, board: None });
}
if format.raster {
let img = fileio::raster_image(&src.bytes)?;
let (sx, sy) = pt_per_px(img.ppi);
@@ -176,7 +196,7 @@ fn load(p: &Value, cmd: &str, board: Option<Rect>) -> Result<Loaded> {
// Of the open options, only a DXF drawing's apply (the colour mode stays the file's).
let o = fileio::LoadOptions { dxf: opts.dxf, ..Default::default() };
let mut l = fileio::load_with(src.name, &src.bytes, &o)?;
// Linked images (a native document's) show their files, found from its folder.
// Linked images show their files, found from its folder.
super::links::resolve(&mut l.doc, src.path, false);
(l.doc, l.warnings)
}
@@ -190,22 +210,31 @@ fn load(p: &Value, cmd: &str, board: Option<Rect>) -> Result<Loaded> {
// The page placed is a PDF import's only one.
let index = if pdf { 0 } else { page - 1 };
let n = doc.artboards.len();
let board = doc.artboards.get(index).map(|a| a.rect).ok_or_else(|| {
let what = if matches!(format.id, "vectorcraft" | "template") { "artboard" } else { "page" };
bad(cmd, format!("page {page}: `{name}` has {n} {what}(s)"))
})?;
// A PDF import has one layer per page; a native document's (or an EPS file's) art is
// whatever lies on the artboard.
let pages: Vec<&Arc<Node>> = if pdf { layers.get(index).into_iter().collect() } else { layers.iter().collect() };
let on_board = |c: &&Arc<Node>| c.visual_bounds().is_some_and(|b| b.intersect(board).area() > 0.0 || board.contains(b.origin()));
let nodes = art_of(pages.into_iter().filter(|l| placeable(l)).flat_map(|l| l.children().into_iter().flatten()).filter(on_board));
(nodes, crop.then_some(board))
let board = doc.artboards.get(index).map(|a| a.rect).ok_or_else(|| bad(cmd, format!("page {page}: `{name}` has {n} page(s)")))?;
// A PDF import has one layer per page; an EPS file's art is whatever lies on the artboard.
let pages = if pdf { layers.get(index..=index).unwrap_or_default() } else { &layers[..] };
(on_artboard(pages, board), crop.then_some(board))
};
let natural = clip.or_else(|| nodes.iter().fold(None, |acc, n| vectorcraft_geom::union_opt(acc, n.visual_bounds())));
let natural = natural.filter(|r| r.width() > 0.0 || r.height() > 0.0).ok_or_else(|| bad(cmd, format!("`{name}` has no art to place")))?;
Ok(Loaded { name, format, art: Art::Vector { src: Box::new(doc), nodes, clip }, natural, warnings, link: None, board })
}
/// The placeable art of `layers` that lies on `board`.
fn on_artboard(layers: &[Arc<Node>], board: Rect) -> Vec<Node> {
let on_board = |c: &&Arc<Node>| c.visual_bounds().is_some_and(|b| b.intersect(board).area() > 0.0 || board.contains(b.origin()));
art_of(layers.iter().filter(|l| placeable(l)).flat_map(|l| l.children().into_iter().flatten()).filter(on_board))
}
/// A clipping path of rectangle `r`, with id `id`.
fn clip_path(id: NodeId, r: Rect) -> Node {
let mut c = Node::path(id, shapes::rectangle(r), Appearance::basic(Paint::None, Paint::None, 0.0));
if let NodeKind::Path { clipping, .. } = &mut c.kind {
*clipping = true;
}
c
}
/// A visible, non-template layer.
fn placeable(l: &Node) -> bool {
l.visible && !matches!(l.kind, NodeKind::Layer { template: true, .. })
@@ -243,15 +272,12 @@ fn build(d: &mut Document, l: Loaded, link: Option<LinkInfo>) -> Node {
Node::new(d.alloc_id(), NodeKind::Image(im))
}
Art::Text(t) => Node::new(d.alloc_id(), NodeKind::Text(Box::new(text::area_text(t, l.natural)))),
Art::Document(s, link, bounding) => document::node(d, &s, link, bounding),
Art::Vector { src, mut nodes, clip } => {
adopt::adopt(d, &src, &mut nodes);
let mut children: Vec<Arc<Node>> = nodes.iter().map(|n| Arc::new(d.reid(n))).collect();
if let Some(r) = clip {
let mut c = Node::path(d.alloc_id(), shapes::rectangle(r), Appearance::basic(Paint::None, Paint::None, 0.0));
if let NodeKind::Path { clipping, .. } = &mut c.kind {
*clipping = true;
}
children.insert(0, Arc::new(c));
children.insert(0, Arc::new(clip_path(d.alloc_id(), r)));
}
Node::new(d.alloc_id(), NodeKind::Group { children, clip: clip.is_some() })
}
+3
View File
@@ -118,6 +118,9 @@ fn print(s: &mut Session, p: &Value) -> Result<Value> {
let preset = given.or(Some(set.advanced.flattener_preset.as_str()).filter(|n| !n.trim().is_empty())).unwrap_or("medium");
set.advanced.flattener_preset = preset.to_string();
}
// Placed documents print their files' art.
let (full, _) = super::place::document::full_documents(doc);
let doc = &*full;
// Raster effects print as images at the document's raster effects resolution.
let flat = super::rasterfx::flatten_raster_effects(doc);
let doc = flat.as_ref().unwrap_or(doc);