mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Place: Link keeps a placed PDF or .ai page linked, as it does images
The File Handling preference Link Placed PDF and .ai Files (linkPlacedPdf) is removed. File › Place with Link now places a page of a PDF, .ai or .ait file read as PDF pages as a linked placed document, as it links a raster image, and link: false embeds the page. Link is on by default in the Place dialog and in file.place, so a PDF or .ai page placed from a file, which used to be embedded, is now linked unless Link is off. A password or textAs: "outlines" still embeds, and so do EPS and PostScript .ai files. The Place dialog's Link tooltip describes a PDF or .ai file as a document. A .ai file read from its editing data (its PDF part only a placeholder) finds the files its art places by name, beside it. Package now copies those files into the same folder as the .ai file, and warns when one has to be copied under another name.
This commit is contained in:
@@ -148,7 +148,20 @@ impl<'a> Collector<'a> {
|
||||
self.warnings.push(message);
|
||||
}
|
||||
|
||||
fn collect(&mut self, doc: &mut Document, source: &str, destination: &str, depth: usize) -> Result<()> {
|
||||
/// Warns when a file that placed `.ai` file `source` links (`name`) was copied as `copy`: the
|
||||
/// copy of `source` finds it only by its name, beside it.
|
||||
fn renamed(&mut self, lines: &mut Vec<String>, source: &str, path: &str, name: &str, copy: &str) {
|
||||
if Path::new(copy).file_name().and_then(|n| n.to_str()) != Some(name) {
|
||||
let message = format!(
|
||||
"{path}: copied as {copy}; the copy of {source} looks for it as {name} and shows the file of that name, if any, else its preview"
|
||||
);
|
||||
self.warn(lines, message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects the files `doc` links. `by_name`: `doc` is a placed `.ai` file's editing data,
|
||||
/// whose links are not rewritten, so its copy finds them by name beside it.
|
||||
fn collect(&mut self, doc: &mut Document, source: &str, destination: &str, depth: usize, by_name: bool) -> Result<()> {
|
||||
// The caller checks cycles and depth before descending; active documents have
|
||||
// a destination reserved so back-links can point at their single packaged copy.
|
||||
self.visiting.insert(source.to_string(), destination.to_string());
|
||||
@@ -183,8 +196,11 @@ impl<'a> Collector<'a> {
|
||||
local.insert(file.path.clone(), Copied { name: ancestor, size: None, hash: None });
|
||||
continue;
|
||||
}
|
||||
if let Some(existing) = self.copies.get(found_path) {
|
||||
local.insert(file.path.clone(), existing.clone());
|
||||
if let Some(existing) = self.copies.get(found_path).cloned() {
|
||||
if by_name {
|
||||
self.renamed(&mut lines, source, &file.path, &file.name, &existing.name);
|
||||
}
|
||||
local.insert(file.path.clone(), existing);
|
||||
continue;
|
||||
}
|
||||
let Some(mut bytes) = file.bytes else {
|
||||
@@ -199,8 +215,20 @@ impl<'a> Collector<'a> {
|
||||
let name = safe_asset_name(&file.name);
|
||||
let unique = free_name(&name, if self.opts.links_folder { &mut self.link_names } else { &mut self.taken });
|
||||
let destination_file = format!("{dir}{unique}");
|
||||
// A placed PDF or .ai file is copied as it is, as an image is.
|
||||
if placed.contains(&file.path) && super::place::document::is_native(&file.name, &bytes) {
|
||||
if by_name {
|
||||
self.renamed(&mut lines, source, &file.path, &file.name, &destination_file);
|
||||
}
|
||||
// A placed PDF or .ai file is copied as it is, as an image is; the files a .ai file
|
||||
// read from its editing data links are copied beside it.
|
||||
let placed_file = placed.contains(&file.path);
|
||||
if placed_file && let Some(mut art) = super::place::document::editing_data(&file.name, &bytes) {
|
||||
if depth >= vectorcraft_doc::placed_document::MAX_DEPTH {
|
||||
self.warn(&mut lines, format!("{}: nesting limit reached; copied unchanged without collecting its own links", file.path));
|
||||
} else {
|
||||
self.collect(&mut art, found_path, &destination_file, depth + 1, true)?;
|
||||
}
|
||||
}
|
||||
if placed_file && super::place::document::is_native(&file.name, &bytes) {
|
||||
if depth >= vectorcraft_doc::placed_document::MAX_DEPTH {
|
||||
self.warn(&mut lines, format!("{}: nesting limit reached; copied unchanged without collecting its own links", file.path));
|
||||
} else {
|
||||
@@ -212,7 +240,7 @@ impl<'a> Collector<'a> {
|
||||
let include_linked =
|
||||
saved.doc.linked_only_images().iter().any(|key| saved.doc.images.get(key).is_some_and(|blob| !blob.is_proxy()));
|
||||
let mut nested = saved.doc;
|
||||
self.collect(&mut nested, found_path, &destination_file, depth + 1)?;
|
||||
self.collect(&mut nested, found_path, &destination_file, depth + 1, false)?;
|
||||
if self.opts.relink {
|
||||
// Preserve auxiliary native-file content when rewriting.
|
||||
let mut options = vectorcraft_format::SaveOptions::for_doc(&nested);
|
||||
@@ -394,7 +422,7 @@ fn collect_and_write(package: Package, write: &mut dyn FnMut(&str, &[u8]) -> Res
|
||||
doc = d;
|
||||
}
|
||||
let mut collector = Collector::new(&o, root.clone(), &doc_name);
|
||||
collector.collect(&mut doc, &doc_path, &doc_name, 0)?;
|
||||
collector.collect(&mut doc, &doc_path, &doc_name, 0, false)?;
|
||||
let mut entries = std::mem::take(&mut collector.entries);
|
||||
entries.insert(0, (doc_name.clone(), vectorcraft_format::save_file(&doc)));
|
||||
let mut taken = std::mem::take(&mut collector.taken);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Placed documents (`file.place` of a VectorCraft document from a file, with `link`; of a PDF or
|
||||
//! `.ai` file too, with Preferences › File Handling › Link Placed PDF and .ai Files): one locked
|
||||
//! object showing an artboard of the document or a page of the PDF, a [`PlacedDocument`].
|
||||
//! Placed documents (`file.place` of a VectorCraft document, a PDF or a `.ai` file from a file,
|
||||
//! with `link`): one locked object showing an artboard of the document or a page of the PDF, 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
|
||||
@@ -44,6 +44,20 @@ pub(crate) fn is_pdf(name: &str, bytes: &[u8]) -> bool {
|
||||
fileio::detect(name, bytes).is_some_and(|f| matches!(f.id, "pdf" | "ai" | "ait")) && !vectorcraft_pdf::is_postscript(bytes)
|
||||
}
|
||||
|
||||
/// The editing data of the `.ai` file `name` (`bytes`) when its PDF part is only a placeholder:
|
||||
/// the document its pages are read from ([`super::pdf_art`]), with the files it links. `None` for
|
||||
/// other files.
|
||||
pub(crate) fn editing_data(name: &str, bytes: &[u8]) -> Option<Document> {
|
||||
if !is_pdf(name, bytes) {
|
||||
return None;
|
||||
}
|
||||
let read = fileio::page_document(bytes, 0, &fileio::LoadOptions::default());
|
||||
match fileio::page_or_editing_data(name, bytes, None, read) {
|
||||
Ok((doc, _, true)) => Some(doc),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The type the bytes of placed file `name` (`bytes`) are stored with: a PDF's format's, else
|
||||
/// [`MIME`].
|
||||
fn mime(name: &str, bytes: &[u8]) -> &'static str {
|
||||
|
||||
@@ -13,9 +13,8 @@
|
||||
//! preview ([`fileio::load`]).
|
||||
//! - A PDF/.ai/.ait page or a native document's artboard becomes one group, clipped to the page
|
||||
//! (`crop: "crop"`; a PDF page's `art`, `trim`, `bleed` or `media` box too) or bounded by its art
|
||||
//! (`crop: "bounding"`). With Link and Preferences › File Handling › Link Placed PDF and .ai
|
||||
//! Files on, a PDF/.ai/.ait page placed from a file becomes a placed document instead, as a
|
||||
//! VectorCraft document does (below).
|
||||
//! (`crop: "bounding"`). With Link, a PDF/.ai/.ait page placed from a file becomes a placed
|
||||
//! document instead, as a VectorCraft document does (below).
|
||||
//! - A text file becomes area type, decoded and cleaned up with the Text Import Options (`text`,
|
||||
//! [`text`]).
|
||||
//! - An RTF file becomes area type with its character and paragraph formatting ([`rtf`]).
|
||||
@@ -64,7 +63,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Place…",
|
||||
["File"],
|
||||
Some("Cmd+Shift+P"),
|
||||
"{path | name | dataBase64 (a file's path, or its name with its bytes), 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; so does a PDF/.ai page at path, showing its `crop` box, when the preference linkPlacedPdf is on (off by default) and neither password nor textAs: \"outlines\" is given; 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; an SVG, only when given: framed and clipped by its viewBox, else by its art's bounds) | \"bounding\" (the bounds of the art that shows, with its strokes and effects, as an export measures them: hidden objects and guides left out) | \"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), a .ai saved without PDF compatibility (from its editing data, as document.open reads it: the art of the layers that show on artboard `page`, clipped to that artboard unless crop is \"bounding\") 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 (a file's path, or its name with its bytes), 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; so does a PDF/.ai page at path, showing its `crop` box, unless password or textAs: \"outlines\" is given; 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; an SVG, only when given: framed and clipped by its viewBox, else by its art's bounds) | \"bounding\" (the bounds of the art that shows, with its strokes and effects, as an export measures them: hidden objects and guides left out) | \"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), a .ai saved without PDF compatibility (from its editing data, as document.open reads it: the art of the layers that show on artboard `page`, clipped to that artboard unless crop is \"bounding\") 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
|
||||
),
|
||||
@@ -141,7 +140,7 @@ pub(crate) fn pt_per_px(ppi: Option<(f64, f64)>) -> (f64, f64) {
|
||||
|
||||
/// The file `p` names, read into placeable art; a DXF drawing's Fit to Artboard fills `board`
|
||||
/// (unless the params name their own box). A PDF/.ai page read from a path is placed linked with
|
||||
/// Link when `link_pdf` (Preferences › File Handling › Link Placed PDF and .ai Files).
|
||||
/// Link when `link_pdf` (false for the queries, which read it embedded).
|
||||
fn load(p: &Value, cmd: &str, board: Option<Rect>, link_pdf: bool) -> Result<Loaded> {
|
||||
let src = fileio::source(p, cmd)?;
|
||||
let name = fileio::file_name(src.name);
|
||||
@@ -474,7 +473,7 @@ fn place(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
(true, _) => return Err(bad(PLACE, "replace: select the one object to replace")),
|
||||
};
|
||||
let target = board_at(&st.doc, at);
|
||||
let mut loaded = load(p, PLACE, target, s.prefs.link_placed_pdf)?;
|
||||
let mut loaded = load(p, PLACE, target, true)?;
|
||||
let old_node = old.and_then(|id| st.doc.node(id));
|
||||
// Type reflows rather than scales: its frame takes the size it is placed at.
|
||||
let is_text = matches!(loaded.art, Art::Text(_));
|
||||
|
||||
@@ -315,7 +315,6 @@ pub const PREF_SPECS: &[PrefSpec] = &[
|
||||
p!("recoveryOffForComplex", "File Handling", "Data Recovery", "Turn off Data Recovery for complex documents", bool),
|
||||
p!("recentFilesCount", "File Handling", "Files", "Number of Recent Files to Display", int(0, 30)),
|
||||
p!("lowResProxyEps", "File Handling", "Files", "Use Low Resolution Proxy for Linked EPS", bool),
|
||||
p!("linkPlacedPdf", "File Handling", "Files", "Link Placed PDF and .ai Files", bool),
|
||||
p!("antiAliasedBitmaps", "File Handling", "Files", "Display Bitmaps as Anti-aliased Images in Pixel Preview", bool),
|
||||
p!(
|
||||
"updateLinks",
|
||||
|
||||
@@ -1034,10 +1034,6 @@ pub struct Prefs {
|
||||
pub recovery_off_for_complex: bool,
|
||||
pub recent_files_count: u32,
|
||||
pub low_res_proxy_eps: bool,
|
||||
/// Link Placed PDF and .ai Files (off by default): File › Place with Link places a page of a
|
||||
/// PDF or `.ai` file as a linked placed document, as it places a VectorCraft document; off,
|
||||
/// the page's art is embedded as one group.
|
||||
pub link_placed_pdf: bool,
|
||||
pub anti_aliased_bitmaps: bool,
|
||||
pub update_links: String,
|
||||
// Clipboard Handling
|
||||
@@ -1257,7 +1253,6 @@ impl Default for Prefs {
|
||||
recovery_off_for_complex: false,
|
||||
recent_files_count: 20,
|
||||
low_res_proxy_eps: false,
|
||||
link_placed_pdf: false,
|
||||
anti_aliased_bitmaps: false,
|
||||
update_links: s("askWhenModified"),
|
||||
copy_as_svg: true,
|
||||
|
||||
@@ -448,7 +448,7 @@ fn a_placed_document_brings_its_links_with_the_files_pixels() {
|
||||
|
||||
/// The placed (linked) files of a `.ai` file come in with the preview the file keeps of them: one
|
||||
/// not found shows it, one found is read at once into the same bounds, which isn't a change. A copy
|
||||
/// saved without PDF compatibility, placed, reads the found file too.
|
||||
/// saved without PDF compatibility, placed without Link, reads the found file too.
|
||||
#[test]
|
||||
fn links_of_an_imported_file_are_read_where_they_are_found() {
|
||||
use vectorcraft_testkit::ai;
|
||||
@@ -487,7 +487,7 @@ fn links_of_an_imported_file_are_read_where_they_are_found() {
|
||||
let alone = dir.file("alone.ai");
|
||||
write(&alone, &ai::ai(data.as_bytes(), "BT /F1 12 Tf 10 50 Td (Saved without its PDF part) Tj ET"));
|
||||
let mut s = session();
|
||||
s.execute("file.place", &json!({"path": alone})).unwrap();
|
||||
s.execute("file.place", &json!({"path": alone, "link": false})).unwrap();
|
||||
let (_, im) = placed(&s);
|
||||
assert!((im.width, im.height) == (600, 300) && !s.doc().unwrap().doc.images[&im.key].is_proxy(), "{im:?}");
|
||||
}
|
||||
|
||||
@@ -415,7 +415,6 @@ fn package_copies_a_linked_pdf_page_as_it_is() {
|
||||
let src = dir.file("art/flyer.pdf");
|
||||
write(&src, &pdf);
|
||||
let mut s = session();
|
||||
run(&mut s, "prefs.set", json!({"key": "linkPlacedPdf", "value": true}));
|
||||
let placed = run(&mut s, "file.place", json!({"path": src}));
|
||||
assert_eq!(placed["linked"], true, "{placed}");
|
||||
save(&mut s, &dir.file("poster.vectorcraft"));
|
||||
@@ -430,6 +429,71 @@ fn package_copies_a_linked_pdf_page_as_it_is() {
|
||||
assert!(s.doc().unwrap().doc.has_placed());
|
||||
}
|
||||
|
||||
/// `dir/art/badge.ai`, saved without its PDF part, its art placing each of `pics` (600 × 300 px,
|
||||
/// with a 2×1 preview) → its path.
|
||||
fn ai_placing(dir: &Folder, pics: &[&str]) -> String {
|
||||
use vectorcraft_testkit::ai;
|
||||
let place = |pic: &str| {
|
||||
let pic = pic.replace('\\', "/");
|
||||
format!(
|
||||
"%AI5_BeginPlace\n[0.1 0 0 -0.1 20 80] 0 0 600 300 4 4 0\n({pic})`\n%AI26_BeginPlacedObjectPreview\n2 1 8 32 17 \x7f\x10\x10\x60\x7f\x10\x10\x60\n%AI26_EndPlacedObjectPreview\n~\n%AI5_EndPlace\n"
|
||||
)
|
||||
};
|
||||
let data = ai::editing_data(200.0, 100.0, &ai::layer("Art", &pics.iter().map(|p| place(p)).collect::<String>()));
|
||||
let src = dir.file("art/badge.ai");
|
||||
write(&src, &ai::ai(data.as_bytes(), "BT /F1 12 Tf 10 50 Td (Saved without its PDF part) Tj ET"));
|
||||
src
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_copies_the_files_a_linked_ai_files_editing_data_places_beside_it() {
|
||||
let dir = Folder::new("package-placed-ai");
|
||||
let pic = dir.file("art/photo.png");
|
||||
write(&pic, &png(600, 300, RED));
|
||||
let src = ai_placing(&dir, &[&pic]);
|
||||
let mut s = session();
|
||||
let placed = run(&mut s, "file.place", json!({"path": src}));
|
||||
assert_eq!(placed["linked"], true, "{placed}");
|
||||
save(&mut s, &dir.file("poster.vectorcraft"));
|
||||
let r = run(&mut s, "file.package", json!({"folder": dir.file("out")}));
|
||||
assert_eq!((r["links"].as_u64(), r["missingLinks"].clone(), r["warnings"].clone()), (Some(2), json!([]), json!([])), "{r}");
|
||||
let root = dir.0.join("out/poster Folder");
|
||||
assert_eq!(std::fs::read(root.join("Links/photo.png")).unwrap(), png(600, 300, RED));
|
||||
// The copy of the .ai finds the picture beside it, not its preview.
|
||||
std::fs::remove_dir_all(dir.0.join("art")).unwrap();
|
||||
let copy = root.join("Links/badge.ai").to_string_lossy().into_owned();
|
||||
let shows_the_file = || {
|
||||
let bytes = std::fs::read(©).unwrap();
|
||||
let a = cmd::place::document::document_art("badge.ai", &bytes, Some(©), 1, false, vectorcraft_doc::PageBox::Crop, "t").unwrap();
|
||||
a.doc.images.values().any(|b| !b.is_proxy())
|
||||
};
|
||||
assert!(shows_the_file());
|
||||
std::fs::remove_file(root.join("Links/photo.png")).unwrap();
|
||||
assert!(!shows_the_file(), "the preview only");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_warns_when_a_file_a_linked_ai_file_places_is_copied_under_another_name() {
|
||||
let dir = Folder::new("package-placed-ai-names");
|
||||
let (one, two) = (dir.file("art/a/photo.png"), dir.file("art/b/photo.png"));
|
||||
write(&one, &png(600, 300, RED));
|
||||
write(&two, &png(600, 300, BLUE));
|
||||
let src = ai_placing(&dir, &[&one, &two]);
|
||||
let mut s = session();
|
||||
run(&mut s, "file.place", json!({"path": src}));
|
||||
save(&mut s, &dir.file("poster.vectorcraft"));
|
||||
let r = run(&mut s, "file.package", json!({"folder": dir.file("out")}));
|
||||
let warnings = r["warnings"].as_array().unwrap();
|
||||
assert_eq!(r["links"].as_u64(), Some(3), "{r}");
|
||||
assert!(warnings.len() == 1 && warnings[0].as_str().unwrap().contains("looks for it as photo.png and shows the file of that name"), "{r}");
|
||||
// Moved, the copy of the .ai shows Links/photo.png for both, as the warning says.
|
||||
std::fs::remove_dir_all(dir.0.join("art")).unwrap();
|
||||
let copy = dir.0.join("out/poster Folder/Links/badge.ai").to_string_lossy().into_owned();
|
||||
let bytes = std::fs::read(©).unwrap();
|
||||
let a = cmd::place::document::document_art("badge.ai", &bytes, Some(©), 1, false, vectorcraft_doc::PageBox::Crop, "t").unwrap();
|
||||
assert_eq!(a.doc.images.values().filter(|b| !b.is_proxy()).count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_keeps_distinct_linked_documents_with_colliding_file_names() {
|
||||
let dir = Folder::new("package-collisions");
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Placed documents: a VectorCraft document placed linked (`file.place`'s default for one read
|
||||
//! from a file), drawn and output as vectors, saved with its preview, checked, updated and
|
||||
//! relinked when the file changes, and turned into an editable copy (Break Link, Expand). A PDF
|
||||
//! or `.ai` page places the same way with Preferences › File Handling › Link Placed PDF and .ai
|
||||
//! Files on.
|
||||
//! or `.ai` page places the same way.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
@@ -462,39 +461,29 @@ fn pdf_source(path: &str, format: &str, pages: &[[u8; 3]], extra: usize) {
|
||||
write(path, &cmd::fileio::encode_all(&d, format, &p).unwrap().files[0].1);
|
||||
}
|
||||
|
||||
/// Preferences › File Handling › Link Placed PDF and .ai Files on.
|
||||
fn link_pdfs(s: &mut Session) {
|
||||
s.execute("prefs.set", &json!({"key": "linkPlacedPdf", "value": true})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pdf_page_stays_embedded_without_the_preference() {
|
||||
let dir = Folder::new("placed-pdf-default");
|
||||
fn a_pdf_page_placed_without_link_is_embedded() {
|
||||
let dir = Folder::new("placed-pdf-embedded");
|
||||
let src = dir.file("flyer.pdf");
|
||||
pdf_source(&src, "pdf", &[RED], 0);
|
||||
let mut s = session();
|
||||
assert_eq!(s.execute("prefs.get", &json!({"key": "linkPlacedPdf"})).unwrap(), json!(false), "off by default");
|
||||
let r = s.execute("file.place", &json!({"path": src, "link": true, "at": [200, 150]})).unwrap();
|
||||
let r = s.execute("file.place", &json!({"path": src, "link": false, "at": [200, 150]})).unwrap();
|
||||
assert_eq!(r["linked"], false, "{r}");
|
||||
let n = s.doc().unwrap().doc.node(NodeId(r["ids"][0].as_u64().unwrap())).unwrap();
|
||||
assert_eq!(n.kind_label(), "Clip Group");
|
||||
assert!(!s.doc().unwrap().doc.has_placed());
|
||||
// With the preference on, Link off still embeds, and so does text asked for as outlines.
|
||||
link_pdfs(&mut s);
|
||||
for p in [json!({"path": src, "link": false}), json!({"path": src, "textAs": "outlines"})] {
|
||||
let r = s.execute("file.place", &p).unwrap();
|
||||
assert_eq!(r["linked"], false, "{p}: {r}");
|
||||
}
|
||||
// Text asked for as outlines is embedded with Link too.
|
||||
let r = s.execute("file.place", &json!({"path": src, "textAs": "outlines"})).unwrap();
|
||||
assert_eq!(r["linked"], false, "{r}");
|
||||
assert!(!s.doc().unwrap().doc.has_placed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_the_preference_a_pdf_page_places_linked() {
|
||||
fn with_link_a_pdf_page_places_linked() {
|
||||
let dir = Folder::new("placed-pdf-linked");
|
||||
let src = dir.file("flyer.pdf");
|
||||
pdf_source(&src, "pdf", &[RED], 0);
|
||||
let mut s = session();
|
||||
link_pdfs(&mut s);
|
||||
let id = place(&mut s, &src);
|
||||
let p = placed(&s, id);
|
||||
assert_eq!((p.link.path.as_str(), p.link.page, p.bounding), (src.as_str(), Some(1), false), "{p:?}");
|
||||
@@ -517,7 +506,6 @@ fn a_changed_pdf_is_modified_and_update_links_reads_it_again() {
|
||||
let src = dir.file("flyer.pdf");
|
||||
pdf_source(&src, "pdf", &[RED], 0);
|
||||
let mut s = session();
|
||||
link_pdfs(&mut s);
|
||||
let id = place(&mut s, &src);
|
||||
pdf_source(&src, "pdf", &[BLUE], 1);
|
||||
assert_eq!(s.execute("links.check", &json!({})).unwrap()["links"][0]["status"], "modified");
|
||||
@@ -546,7 +534,6 @@ fn a_linked_pdf_page_outputs_as_vectors_after_the_document_reopens() {
|
||||
let src = dir.file("flyer.pdf");
|
||||
pdf_source(&src, "pdf", &[RED], 3);
|
||||
let mut s = session();
|
||||
link_pdfs(&mut s);
|
||||
let id = place(&mut s, &src);
|
||||
let doc_path = dir.file("poster.vectorcraft");
|
||||
save(&mut s, &doc_path);
|
||||
@@ -572,7 +559,6 @@ fn saving_and_reopening_keeps_a_linked_pdf_pages_number_and_box() {
|
||||
let src = dir.file("flyer.pdf");
|
||||
pdf_source(&src, "pdf", &[RED, BLUE], 4);
|
||||
let mut s = session();
|
||||
link_pdfs(&mut s);
|
||||
let r = s.execute("file.place", &json!({"path": src, "page": 2, "crop": "media", "at": [200, 150]})).unwrap();
|
||||
assert_eq!(r["linked"], true, "{r}");
|
||||
let id = NodeId(r["ids"][0].as_u64().unwrap());
|
||||
@@ -604,7 +590,6 @@ fn a_pdf_compatible_ai_file_places_linked_like_a_pdf() {
|
||||
let src = dir.file("badge.ai");
|
||||
pdf_source(&src, "ai", &[RED], 0);
|
||||
let mut s = session();
|
||||
link_pdfs(&mut s);
|
||||
let id = place(&mut s, &src);
|
||||
assert_eq!(placed(&s, id).link.path, src);
|
||||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||||
|
||||
@@ -3363,7 +3363,6 @@
|
||||
Use Even-Odd Fill Rule Използване на правило за запълване четно-нечетно
|
||||
Use Japanese Crop Marks Използване на японски марки за изрязване
|
||||
Use Low Resolution Proxy for Linked EPS Използване на нискорезолютивно прокси за свързани EPS файлове
|
||||
Link Placed PDF and .ai Files Свързване на поставените PDF и .ai файлове
|
||||
Use Non-Zero Winding Fill Rule Използване на правило за запълване с ненулево навиване
|
||||
Use Precise Cursors Използване на прецизни курсори
|
||||
Use Pressure Pen Използване на чувствителна на натиск писалка
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3522,7 +3522,6 @@ theme Appearance Motiv
|
||||
Use Compression Použít kompresi
|
||||
Use Japanese Crop Marks Použít japonské ořezové značky
|
||||
Use Low Resolution Proxy for Linked EPS Pro propojené EPS použít zástupce v nízkém rozlišení
|
||||
Link Placed PDF and .ai Files Propojit umístěné soubory PDF a .ai
|
||||
Use Precise Cursors Používat přesné kurzory
|
||||
Use Preview Bounds Použít hranice náhledu
|
||||
When pasting text Při vkládání textu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3273,7 +3273,6 @@ theme Appearance Udseende
|
||||
Use Compression Anvend komprimering
|
||||
Use Japanese Crop Marks Anvend japanske beskæringsmærker
|
||||
Use Low Resolution Proxy for Linked EPS Anvend proxy med lav opløsning til koblede EPS-filer
|
||||
Link Placed PDF and .ai Files Kobl placerede PDF- og .ai-filer
|
||||
Use Precise Cursors Anvend præcise markører
|
||||
Use Preview Bounds Anvend visningsgrænser
|
||||
When pasting text Når tekst indsættes
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3382,7 +3382,6 @@ effect Points Punkte
|
||||
Use Even-Odd Fill Rule Gerade-Ungerade-Füllregel verwenden
|
||||
Use Japanese Crop Marks Japanische Schnittmarken verwenden
|
||||
Use Low Resolution Proxy for Linked EPS Niedrig aufgelösten Platzhalter für verknüpfte EPS-Dateien verwenden
|
||||
Link Placed PDF and .ai Files Platzierte PDF- und .ai-Dateien verknüpfen
|
||||
Use Non-Zero Winding Fill Rule Nicht-Null-Füllregel verwenden
|
||||
Use Precise Cursors Präzise Cursor verwenden
|
||||
Use Pressure Pen Stiftdruck verwenden
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3720,7 +3720,6 @@ theme Appearance Εμφάνιση
|
||||
Use Even-Odd Fill Rule Χρήση κανόνα γεμίσματος άρτιου-περιττού
|
||||
Use Japanese Crop Marks Χρήση ιαπωνικών σημαδιών περικοπής
|
||||
Use Low Resolution Proxy for Linked EPS Χρήση αντιπροσώπου χαμηλής ανάλυσης για συνδεδεμένο EPS
|
||||
Link Placed PDF and .ai Files Σύνδεση τοποθετημένων αρχείων PDF και .ai
|
||||
Use Non-Zero Winding Fill Rule Χρήση κανόνα γεμίσματος μη μηδενικής περιέλιξης
|
||||
Use Precise Cursors Χρήση δεικτών ακριβείας
|
||||
Use Pressure Pen Χρήση πένας πίεσης
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2045 and column 19.
|
@@ -3429,7 +3429,6 @@
|
||||
Use Even-Odd Fill Rule Usar regla de relleno par-impar
|
||||
Use Japanese Crop Marks Usar marcas de corte japonesas
|
||||
Use Low Resolution Proxy for Linked EPS Usar proxy de baja resolución para EPS vinculados
|
||||
Link Placed PDF and .ai Files Vincular archivos PDF y .ai colocados
|
||||
Use Non-Zero Winding Fill Rule Usar regla de relleno distinto de cero
|
||||
Use Precise Cursors Usar cursores precisos
|
||||
Use Pressure Pen Usar lápiz de presión
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3450,7 +3450,6 @@ theme Appearance Ulkoasu
|
||||
Use Compression Käytä pakkausta
|
||||
Use Japanese Crop Marks Käytä japanilaisia rajausmerkkejä
|
||||
Use Low Resolution Proxy for Linked EPS Käytä matalan tarkkuuden välityskuvaa linkitetyille EPS-tiedostoille
|
||||
Link Placed PDF and .ai Files Linkitä sijoitetut PDF- ja .ai-tiedostot
|
||||
Use Precise Cursors Käytä tarkkoja kohdistimia
|
||||
Use Preview Bounds Käytä esikatselurajoja
|
||||
When pasting text Liitettäessä tekstiä
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3426,7 +3426,6 @@
|
||||
Use Even-Odd Fill Rule Utiliser la règle de remplissage pair-impair
|
||||
Use Japanese Crop Marks Utiliser les traits de coupe japonais
|
||||
Use Low Resolution Proxy for Linked EPS Utiliser une image d’aperçu basse résolution pour les EPS liés
|
||||
Link Placed PDF and .ai Files Lier les fichiers PDF et .ai importés
|
||||
Use Non-Zero Winding Fill Rule Utiliser la règle de remplissage non nul
|
||||
Use Precise Cursors Utiliser des pointeurs précis
|
||||
Use Pressure Pen Utiliser la pression du stylet
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4143,7 +4143,6 @@ mark-style Japanese Japán
|
||||
Use Japanese Crop Marks Japán vágójelek használata
|
||||
Use Library Item Könyvtárelem használata
|
||||
Use Low Resolution Proxy for Linked EPS Kis felbontású helyettesítő kép csatolt EPS-hez
|
||||
Link Placed PDF and .ai Files Elhelyezett PDF- és .ai-fájlok csatolása
|
||||
Use Non-Zero Winding Fill Rule Nem nulla körüljárási kitöltési szabály
|
||||
Use Precise Cursors Pontos kurzorok használata
|
||||
Use Pressure Pen Nyomásérzékeny toll használata
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3273,7 +3273,6 @@ theme Appearance Tampilan
|
||||
Use Compression Gunakan kompresi
|
||||
Use Japanese Crop Marks Gunakan tanda potong Jepang
|
||||
Use Low Resolution Proxy for Linked EPS Gunakan proksi resolusi rendah untuk EPS tertaut
|
||||
Link Placed PDF and .ai Files Tautkan file PDF dan .ai yang ditempatkan
|
||||
Use Precise Cursors Gunakan kursor presisi
|
||||
Use Preview Bounds Gunakan batas pratinjau
|
||||
When pasting text Saat menempel teks
|
||||
|
||||
|
@@ -3464,7 +3464,6 @@ mark-style Japanese Giapponese
|
||||
Use Even-Odd Fill Rule Usa regola di riempimento pari-dispari
|
||||
Use Japanese Crop Marks Usa segni di ritaglio giapponesi
|
||||
Use Low Resolution Proxy for Linked EPS Usa proxy a bassa risoluzione per EPS collegati
|
||||
Link Placed PDF and .ai Files Collega i file PDF e .ai inseriti
|
||||
Use Non-Zero Winding Fill Rule Usa regola di riempimento diversa da zero
|
||||
Use Precise Cursors Usa cursori di precisione
|
||||
Use Pressure Pen Usa penna a pressione
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3112,7 +3112,6 @@ mark-style Japanese 日本式
|
||||
Use Even-Odd Fill Rule 奇偶規則を使用
|
||||
Use Japanese Crop Marks 日本式トンボを使用
|
||||
Use Low Resolution Proxy for Linked EPS リンク EPS に低解像度プロキシを使用
|
||||
Link Placed PDF and .ai Files 配置した PDF と .ai ファイルをリンク
|
||||
Use Non-Zero Winding Fill Rule ノンゼロワインディング規則を使用
|
||||
Use Precise Cursors 精密カーソルを使用
|
||||
Use Pressure Pen 筆圧を使用
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3083,7 +3083,6 @@ theme Appearance იერსახე
|
||||
Use Compression შეკუმშვის გამოყენება
|
||||
Use Japanese Crop Marks იაპონური ჭრის ნიშნების გამოყენება
|
||||
Use Low Resolution Proxy for Linked EPS დაბალი გარჩევადობის პროქსის გამოყენება დაკავშირებული EPS-ისთვის
|
||||
Link Placed PDF and .ai Files განთავსებული PDF და .ai ფაილების დაკავშირება
|
||||
Use Precise Cursors ზუსტი კურსორების გამოყენება
|
||||
Use Preview Bounds გადახედვის საზღვრების გამოყენება
|
||||
When pasting text ტექსტის ჩასმისას
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3106,7 +3106,6 @@ mark-style Japanese 일본식
|
||||
Use Even-Odd Fill Rule 짝홀 칠 규칙 사용
|
||||
Use Japanese Crop Marks 일본식 자르기 표시 사용
|
||||
Use Low Resolution Proxy for Linked EPS 연결된 EPS에 저해상도 프록시 사용
|
||||
Link Placed PDF and .ai Files 가져온 PDF 및 .ai 파일 연결
|
||||
Use Non-Zero Winding Fill Rule 논제로 와인딩 칠 규칙 사용
|
||||
Use Precise Cursors 정밀 커서 사용
|
||||
Use Pressure Pen 필압 펜 사용
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3287,7 +3287,6 @@ theme Appearance Vormgeving
|
||||
Use Compression Compressie gebruiken
|
||||
Use Japanese Crop Marks Japanse aftekenlijnen gebruiken
|
||||
Use Low Resolution Proxy for Linked EPS Proxy met lage resolutie gebruiken voor gekoppelde EPS
|
||||
Link Placed PDF and .ai Files Geplaatste PDF- en .ai-bestanden koppelen
|
||||
Use Precise Cursors Nauwkeurige cursors gebruiken
|
||||
Use Preview Bounds Voorvertoningsgrenzen gebruiken
|
||||
When pasting text Bij het plakken van tekst
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3384,7 +3384,6 @@ theme Appearance Wygląd
|
||||
Use Compression Używaj kompresji
|
||||
Use Japanese Crop Marks Używaj japońskich znaczników przycięcia
|
||||
Use Low Resolution Proxy for Linked EPS Używaj podglądu niskiej rozdzielczości dla połączonych EPS
|
||||
Link Placed PDF and .ai Files Łącz umieszczone pliki PDF i .ai
|
||||
Use Precise Cursors Używaj precyzyjnych kursorów
|
||||
Use Preview Bounds Używaj granic podglądu
|
||||
When pasting text Przy wklejaniu tekstu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2797,7 +2797,6 @@
|
||||
Use Even-Odd Fill Rule Usar Regra de Preenchimento Par-Ímpar
|
||||
Use Japanese Crop Marks Usar Marcas de Corte Japonesas
|
||||
Use Low Resolution Proxy for Linked EPS Usar Proxy de Baixa Resolução para EPS Vinculado
|
||||
Link Placed PDF and .ai Files Vincular Arquivos PDF e .ai Inseridos
|
||||
Use Non-Zero Winding Fill Rule Usar Regra de Preenchimento de Enrolamento Diferente de Zero
|
||||
Use Precise Cursors Usar Cursores Precisos
|
||||
Use Pressure Pen Usar Caneta de Pressão
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3460,7 +3460,6 @@ mark-style Japanese Японский
|
||||
Use Even-Odd Fill Rule Использовать правило заливки «чёт-нечет»
|
||||
Use Japanese Crop Marks Использовать японские метки обрезки
|
||||
Use Low Resolution Proxy for Linked EPS Использовать низкоразрешённый прокси для связанных EPS
|
||||
Link Placed PDF and .ai Files Связывать размещённые файлы PDF и .ai
|
||||
Use Non-Zero Winding Fill Rule Использовать правило заливки «ненулевой»
|
||||
Use Precise Cursors Использовать точные курсоры
|
||||
Use Pressure Pen Использовать перо с нажимом
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3363,7 +3363,6 @@
|
||||
Use Even-Odd Fill Rule Använd fyllningsregeln jämn-udda
|
||||
Use Japanese Crop Marks Använd japanska beskärningsmärken
|
||||
Use Low Resolution Proxy for Linked EPS Använd proxy med låg upplösning för länkad EPS
|
||||
Link Placed PDF and .ai Files Länka placerade PDF- och .ai-filer
|
||||
Use Non-Zero Winding Fill Rule Använd fyllningsregeln nollskild lindning
|
||||
Use Precise Cursors Använd exakta markörer
|
||||
Use Pressure Pen Använd tryckkänslig penna
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3457,7 +3457,6 @@ effect Points Точки
|
||||
Use Even-Odd Fill Rule Використовувати правило заливки «парне–непарне»
|
||||
Use Japanese Crop Marks Використовувати японські мітки обрізання
|
||||
Use Low Resolution Proxy for Linked EPS Використовувати попередній перегляд низької роздільної здатності для пов’язаних EPS
|
||||
Link Placed PDF and .ai Files Пов’язувати розміщені файли PDF і .ai
|
||||
Use Non-Zero Winding Fill Rule Використовувати правило заливки «ненульове заповнення»
|
||||
Use Precise Cursors Використовувати точні курсори
|
||||
Use Pressure Pen Використовувати перо з натиском
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 733 and column 40.
|
@@ -3833,7 +3833,6 @@ mark-style Japanese Kiểu Nhật
|
||||
Use Japanese Crop Marks Dùng dấu cắt kiểu Nhật
|
||||
Use Library Item Dùng mục thư viện
|
||||
Use Low Resolution Proxy for Linked EPS Dùng proxy độ phân giải thấp cho EPS liên kết
|
||||
Link Placed PDF and .ai Files Liên kết tệp PDF và .ai đã đặt
|
||||
Use Non-Zero Winding Fill Rule Dùng quy tắc tô quấn khác không
|
||||
Use Outside Rectangle As Frame Dùng hình chữ nhật ngoài làm khung
|
||||
Use Precise Cursors Dùng con trỏ chính xác
|
||||
|
||||
|
@@ -3303,7 +3303,6 @@ font-script Japanese 汉字
|
||||
Use Even-Odd Fill Rule 使用奇偶填色规则
|
||||
Use Japanese Crop Marks 使用日式裁切标记
|
||||
Use Low Resolution Proxy for Linked EPS 链接 EPS 使用低分辨率代理
|
||||
Link Placed PDF and .ai Files 链接置入的 PDF 和 .ai 文件
|
||||
Use Non-Zero Winding Fill Rule 使用非零缠绕填色规则
|
||||
Use Precise Cursors 使用精确光标
|
||||
Use Pressure Pen 使用压感笔
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4223,7 +4223,6 @@ font-script Japanese 漢字
|
||||
Use Even-Odd Fill Rule 使用奇偶填色規則
|
||||
Use Japanese Crop Marks 使用日式裁切標記
|
||||
Use Low Resolution Proxy for Linked EPS 連結的 EPS 使用低解析度代理
|
||||
Link Placed PDF and .ai Files 連結置入的 PDF 和 .ai 檔案
|
||||
Use Non-Zero Winding Fill Rule 使用非零纏繞填色規則
|
||||
Use Precise Cursors 使用精確游標
|
||||
Use Pressure Pen 使用壓感筆
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -155,7 +155,6 @@ fn open_dialog(app: &mut VectorcraftApp, files: Vec<Value>) {
|
||||
let units = app.session.active().map(|d| d.doc.units).unwrap_or_default();
|
||||
let info: Vec<String> = files.iter().map(|f| summary(app, f, units)).collect();
|
||||
let one_object = app.session.active().is_some_and(|d| d.selection.objects.len() == 1);
|
||||
let pdf = app.session.prefs.link_placed_pdf;
|
||||
app.ui.dialog = Some(crate::state::Dialog::new(
|
||||
crate::dialogs::place::KIND,
|
||||
json!({
|
||||
@@ -164,20 +163,18 @@ fn open_dialog(app: &mut VectorcraftApp, files: Vec<Value>) {
|
||||
"template": false,
|
||||
"replace": false,
|
||||
"__replace": files.len() == 1 && one_object,
|
||||
"__documents": files.iter().any(|f| is_vectorcraft_file(f, pdf)),
|
||||
"__others": !files.iter().all(|f| is_vectorcraft_file(f, pdf)),
|
||||
"__documents": files.iter().any(is_vectorcraft_file),
|
||||
"__others": !files.iter().all(is_vectorcraft_file),
|
||||
"__info": info,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
/// Is dialog file entry `file` a VectorCraft document on disk (what Link places as one locked
|
||||
/// object), or, with `pdf` (Preferences › File Handling › Link Placed PDF and .ai Files), a PDF or
|
||||
/// `.ai` file on disk?
|
||||
fn is_vectorcraft_file(file: &Value, pdf: bool) -> bool {
|
||||
let native = ["vectorcraft", vectorcraft_format::LEGACY_EXTENSION, "vctemplate"];
|
||||
let linked = |ext: &str| native.contains(&ext) || (pdf && matches!(ext, "pdf" | "ai" | "ait"));
|
||||
file.get("path").and_then(Value::as_str).is_some_and(|p| linked(fileio::extension(p).as_str()))
|
||||
/// object), or a PDF or `.ai` file on disk?
|
||||
fn is_vectorcraft_file(file: &Value) -> bool {
|
||||
let linked = ["vectorcraft", vectorcraft_format::LEGACY_EXTENSION, "vctemplate", "pdf", "ai", "ait"];
|
||||
file.get("path").and_then(Value::as_str).is_some_and(|p| linked.contains(&fileio::extension(p).as_str()))
|
||||
}
|
||||
|
||||
/// The engine params naming `file` (a dialog file entry).
|
||||
|
||||
@@ -635,28 +635,28 @@ fn a_vectorcraft_document_places_linked_and_edit_original_opens_it() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_link_placed_pdf_on_the_place_dialog_links_a_pdf_page() {
|
||||
fn the_place_dialog_links_a_pdf_page_and_embeds_it_with_link_off() {
|
||||
let mut app = app();
|
||||
let mut src = Session::new();
|
||||
src.execute("file.new", &json!({"width": 100, "height": 50})).unwrap();
|
||||
src.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 100, "height": 50})).unwrap();
|
||||
let pdf = src.execute("document.serialize", &json!({"format": "pdf", "preserveEditing": false})).unwrap();
|
||||
let path = temp_file("flyer.pdf", &vectorcraft_format::base64_decode(pdf["dataBase64"].as_str().unwrap()).unwrap());
|
||||
// Off (the default): Link's tooltip speaks of images, and the page is embedded.
|
||||
// Link on (the default): one locked object, linked to the file.
|
||||
place_picked(&mut app, &path);
|
||||
let d = app.ui.dialog.as_ref().expect("the Place dialog");
|
||||
assert!(d.bool("link") && !d.bool("__documents") && crate::dialogs::place::link_tip(d).contains("image file"));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert!(!app.session.active().unwrap().doc.has_placed());
|
||||
app.run("prefs.set", json!({"key": "linkPlacedPdf", "value": true})).unwrap();
|
||||
place_picked(&mut app, &path);
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert!(d.bool("__documents") && !d.bool("__others"), "Link places it as one locked object");
|
||||
assert!(d.bool("link") && d.bool("__documents") && !d.bool("__others"), "Link places it as one locked object");
|
||||
assert!(crate::dialogs::place::link_tip(d).contains("locked object"));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
let st = app.session.active().unwrap();
|
||||
let vectorcraft_doc::NodeKind::PlacedDocument(p) = &st.doc.node(st.selection.objects[0]).unwrap().kind else { panic!("not a placed document") };
|
||||
assert_eq!(p.link.path, path);
|
||||
// Link off: the page's art is embedded.
|
||||
place_picked(&mut app, &path);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("link".into(), json!(false));
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
let st = app.session.active().unwrap();
|
||||
assert_eq!(st.doc.node(st.selection.objects[0]).unwrap().kind_label(), "Clip Group");
|
||||
}
|
||||
|
||||
/// Tool key `key` as the Control bar's Apply (Enter) and Cancel (Escape) send it.
|
||||
|
||||
+7
-6
@@ -2897,10 +2897,9 @@ unless `link: false`; a Photoshop document as its merged image), an SVG as one g
|
||||
and swatches it uses. A VectorCraft document read from `path` is a placed document: one locked object showing its
|
||||
artboard `page` (or, with `crop: "bounding"`, its art's bounds), linked to the file and read again when it changes
|
||||
(see Linked images), and vectors in every output; with `link: false` (or from `dataBase64`) it is an editable copy
|
||||
of its art, as one clipped group. With the File Handling preference `linkPlacedPdf` on (off by default), a PDF,
|
||||
`.ai` or `.ait` page read from `path` with `link` (and neither `password` nor `textAs: "outlines"`) is a placed
|
||||
document the same way, showing page `page` clipped to its `crop` box (or its art's bounds); with it off, the page
|
||||
is placed as one clipped group. EPS and PostScript `.ai` files are embedded. `at` centres it, `rect` fits it, `replace`
|
||||
of its art, as one clipped group. A PDF, `.ai` or `.ait` page read from `path` (with neither `password` nor
|
||||
`textAs: "outlines"`) is a placed document the same way, showing page `page` clipped to its `crop` box (or its
|
||||
art's bounds); with `link: false` it is placed as one clipped group. EPS and PostScript `.ai` files are embedded. `at` centres it, `rect` fits it, `replace`
|
||||
swaps the selected object (keeping its place and transform), `template` puts it on a new template layer.
|
||||
The art's bounds are the bounds of the art that shows, with its strokes and effects, as an export measures them:
|
||||
hidden objects, guides and template layers do not count, and symbol instances are measured by their art. A page or artboard holds the objects
|
||||
@@ -3169,13 +3168,15 @@ that includes a placed document fails and relinks nothing; placed documents are
|
||||
files are. Find in Folder… in the Linked File Not Found dialog and in the Links panel menu (`ui.findLinksInFolder`) runs
|
||||
both. The web build has no folder search.
|
||||
|
||||
Placed documents (a `.vectorcraft` file placed linked, or a PDF or `.ai` page placed linked with `linkPlacedPdf`)
|
||||
Placed documents (a `.vectorcraft` file, or a PDF or `.ai` page, placed linked)
|
||||
work the same way: the document keeps the file's bytes and a preview (JPEG or PNG, 1 px per point, at most 1024 px
|
||||
a side), and a save writes the preview (Include Linked Files: the file); output reads the file again, or uses the
|
||||
preview with a warning when it is gone or changed.
|
||||
`links.list` and `links.info` give them `document: true`, the file's `format` (`VectorCraft`, `PDF`,
|
||||
`PDF-compatible .ai`) and the artboard's or page's `pageWidth` and `pageHeight` (pt). A relinked PDF shows the same
|
||||
page and box of the new file. File › Package copies a placed PDF or `.ai` file as it is.
|
||||
page and box of the new file. File › Package copies a placed PDF or `.ai` file as it is. For a `.ai` file read from
|
||||
its editing data, Package also copies the files its art places into the same folder. That file finds them by name,
|
||||
and Package warns when one is copied under another name: the copy then shows the file of that name, or its preview.
|
||||
Saving the file in the app updates the open documents that place it. `links.embed` (the Links panel's Break Link)
|
||||
turns one into the editable copy placing without link gives, its symbols, patterns, swatches and images joining
|
||||
the document; so does `object.expand`.
|
||||
|
||||
@@ -854,7 +854,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- DXF control-point SPLINE import honors its closed flag (#1299 follow-up after #1320 fixed export), including rational curves; open curves whose endpoints meet stay open. Own-fixture regressions cover controls/knots, fit-point fallback and all export versions (`crates/cad/src/import/tests.rs`, `crates/cad/src/tests.rs`); native save/read checked. Circular cubic paths export as native CIRCLE/ARC where stroke semantics permit; R12 weighted strokes remain POLYLINE, and a non-circular ellipse keeps the existing fallback (`crates/cad/src/tests.rs`); CNC preset and VCarve QA remain pending.
|
||||
- Affinity documents in (AF.1–AF.4): `.af` (Affinity 3) and `.afdesign`/`.afphoto`/`.afpub` (Affinity 1 and 2, container versions 8–12) open and place as editable art: layers and sublayers, groups (pass-through or isolated), artboards (with their names and backgrounds) and Publisher pages side by side, curves with live corners, parametric shapes (rectangles with corner radii, ellipses, polygons, stars, pies, triangles, trapezoids), compound shapes, solid/linear/elliptical gradient fills and strokes (weight, dashes, alignment, scale-with-object), clipping by nesting, vector and pixel masks, artistic and frame text with fonts, sizes, tracking, leading and alignment, placed images (their original JPEG/PNG) and pixel layers (8/16-bit RGB, CMYK), opacity, visibility, locking and blend modes, at the document's resolution in points. Effects, adjustments, brushes and pressure profiles, transparency gradients, master pages, symbol instances (imported as their cached picture), special shapes (cloud, heart, cog…, imported as ellipses) and Affinity-only blend modes are named in the import warning. A file whose native data can't be read opens as its embedded preview with the reason, and that preview can't be placed. Save never writes back over the Affinity file and there is no Affinity export. AF.5 (from DesignCraft's import, storytold/designcraft#291) adds master pages (on a locked layer behind the pages that show them), tables (area type per cell and a line per edge), each paragraph's alignment, indents, spacing and leading, horizontal text scale, outlined text, frame text sized by its text scale, drop shadows and outer glows as live effects (inner shadows as inner glows), outline effects as strokes, colour and gradient overlays baked into the paint, embedded Affinity documents as editable content (their artboards don't become artboards), pixel layers over 64 MP at a reduced resolution and HEIC photos from their stored pixels; automatic leading (size plus the font's line gap) fitted on thumbnails; underlines and strikethrough; strokes whose style is None (every shape stores one) left out, which also brings 6 public files closer to their thumbnails; line breaks inside a paragraph; table cell text centred as Affinity centres it, also where it is taller than its cell; CMYK pixel layers converted through the document's embedded CMYK profile (relative colorimetric with black-point compensation, as each layer's colour settings name), or kept as CMYK images when there is none; outline effects on stroked shapes around their stroke; artistic text whose paragraphs align differently aligned within its longest line, a frame's first baseline at cap height, and frames whose last lines' descenders hang out made tall enough to show them (all measured on a local document's own PDF export: baselines match to 0.1 pt); a local 56-artboard document's mean thumbnail difference went from 16.2 to 11.4, and its pages' mean difference from the owner's PDF export from 12.9 to 9.3. AF.4 adds Affinity 3 artboard recognition and actual-outline clipping, source-backed JPEG/PNG raster tiles, Unicode text-run recovery, stored fallback/exact installed fonts, OpenType feature flags and compound-geometry limits. 51 public CC0/MIT/Apache-2.0 files (33 current `.af`), pinned and sha256-verified (`cargo xtask corpus --affinity`), test native retention and every-board native/SVG/PDF/PSD output for current files; selected files compare their saved thumbnails. No Affinity reopen or independent paired PSD oracle was available; [source audit and complex-font gaps](affinity-validation.md); [scope, provenance and limits](../crates/affinity/README.md).
|
||||
- Placed documents: File › Place of a `.vectorcraft` file links to it by default (`file.place`'s `link`): one locked object showing an artboard of the document (or its art's bounds), read the way placing it without Link reads it, its symbols, patterns and images kept apart from the document's; without Link (or from bytes, not a file) it is an editable copy as before. It stays vectors in every output (SVG, PDF, EPS, EMF, DXF), and so do placed documents inside it, read from their own files up to 8 levels deep (documents that place each other stop there). On screen they draw from bitmaps cached per file and zoom step, shared by every copy and made in the background (a placeholder until ready), so a sheet of them redraws in about a millisecond; exports and prints draw them exactly. Like linked images, a save keeps only a preview of each (JPEG when opaque) unless Include Linked Files; output, and zooming past the preview, read the file again, falling back to the preview with a warning when it is gone or changed. Saves for older versions write them as plain art. Links check, update, relink, Update Links on open, Link Info (the artboard's size), Placement Options, Break Link (also Embed, Object › Expand: the editable copy placing without Link gives, its resources joining the document), Edit Original (the document in a new tab) and the Links panel cover them; saving a document updates the open documents that place it. The native format (v4) keeps data of 8 KB or more after the JSON instead of base64 in it.
|
||||
- Linked PDF and `.ai` pages: with Preferences › File Handling › Link Placed PDF and .ai Files on (`linkPlacedPdf`, off by default), File › Place with Link places a page of a PDF, `.ai` or `.ait` file read as PDF pages as a placed document, the page and its box (`crop`) kept with the link: Links check, update and relink it, Break Link and Object › Expand give the group placing it without Link gives, every output draws the page's vectors, and Package copies the file. With the preference off, or with a password or `textAs: "outlines"`, the page is embedded as before. EPS and PostScript `.ai` files are still embedded (`crates/engine/src/tests_placed_document.rs`).
|
||||
- Linked PDF and `.ai` pages: File › Place with Link (the default) places a page of a PDF, `.ai` or `.ait` file read as PDF pages as a placed document, as it links an image, the page and its box (`crop`) kept with the link: Links check, update and relink it, Break Link and Object › Expand give the group placing it without Link gives, every output draws the page's vectors, and Package copies the file. Without Link, or with a password or `textAs: "outlines"`, the page is embedded. EPS and PostScript `.ai` files are still embedded (`crates/engine/src/tests_placed_document.rs`).
|
||||
- File › Place with Bounding Box (`file.place {crop: "bounding"}`) measures the art that shows, with its strokes and effects, as an export does: hidden objects, guides and template layers do not count, a group whose only part on the page or artboard is hidden is not placed, and symbol instances are measured by their art, for `at`, `rect`, the place cursor and `file.place.info` (`crates/engine/src/tests_place.rs`).
|
||||
- Desktop print transport uses `craft-print` for CUPS and modern Windows. Sheet composition, copies, separations and colour management remain in VectorCraft; Windows 7 retains WMI and the installed PDF viewer. The Print dialog's Media list also carries the Japanese papers — JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 128 × 182 mm beside the ISO B4/B5 (250 × 353 and 176 × 250 mm, kept so settings and presets saved before them keep their paper), and はがき 100 × 148 mm, 往復はがき 148 × 200 mm, 封筒 長形3号 120 × 235 mm, 長形4号 90 × 225 mm, 角形2号 240 × 332 mm, 角形3号 216 × 277 mm and 四六判 127 × 188 mm; the JSON id or the dialog label names a paper, any case and spacing (`B5 (JIS)` or `b5jis`) (`crates/pdf/src/print/settings.rs`, `crates/pdf/src/tests_print.rs`). The print-preview and physical-device fidelity gaps remain, so print scores and hours are unchanged.
|
||||
- A PDF, PDF-compatible `.ai` or `.ait` opened from its pages or its editing copy opens with the File Info that its XMP packet and its document information dictionary give: the author, description, keywords, copyright status, notice and URL, and the created and modified dates, the XMP packet's value where both give one; the title is not changed (`crates/pdf/src/tests_file_info.rs`, `crates/engine/src/tests_fileinfo.rs`).
|
||||
|
||||
Reference in New Issue
Block a user