Merge pull request #2975 from nrq6993-a11y/stream/vc-feat3-g12-low-res-proxy-eps

G12: honor Use Low Resolution Proxy for Linked EPS (#394)
This commit is contained in:
BFlat
2026-10-23 00:37:02 +07:00
committed by GitHub
8 changed files with 166 additions and 22 deletions
+4 -4
View File
@@ -370,7 +370,7 @@ pub fn open_bytes(s: &mut Session, name: &str, bytes: &[u8], path: Option<String
/// [`open_bytes`] with the `document.open` options in `p` ([`LoadOptions::from_params`]).
pub fn open_bytes_with(s: &mut Session, name: &str, bytes: &[u8], path: Option<String>, p: &Value) -> Result<Value> {
let update_links = s.prefs.update_links == "automatically";
open_read(s, read_for_open(name, bytes, path, p, update_links)?)
open_read(s, read_for_open(name, bytes, path, p, update_links, s.prefs.low_res_proxy_eps)?)
}
/// A file read for `document.open` but not opened yet ([`read_for_open`], [`open_read`]).
@@ -396,10 +396,10 @@ impl ReadFile {
/// [`open_read`] opens. The linked files are looked for from `path`, where the file was read from,
/// and modified ones are read again when `update_links` (Preferences › File Handling › Update
/// Links set to Automatically; see [`crate::cmd::links::resolve`]).
pub fn read_for_open(name: &str, bytes: &[u8], path: Option<String>, p: &Value, update_links: bool) -> Result<ReadFile> {
pub fn read_for_open(name: &str, bytes: &[u8], path: Option<String>, p: &Value, update_links: bool, low_res_proxy_eps: bool) -> Result<ReadFile> {
let opts = LoadOptions::from_params("document.open", p)?;
let mut loaded = load_with(name, bytes, &opts)?;
let links = crate::cmd::links::resolve(&mut loaded.doc, path.as_deref(), update_links);
let links = crate::cmd::links::resolve(&mut loaded.doc, path.as_deref(), update_links, low_res_proxy_eps);
Session::refresh_text_bounds(&mut loaded.doc);
Ok(ReadFile { loaded, opts, path, links })
}
@@ -419,7 +419,7 @@ pub fn open_template(s: &mut Session, name: &str, bytes: &[u8], path: Option<&st
"only this Affinity file's embedded preview could be read: use File › Open to see it with its warning, or export SVG or PDF from Affinity first",
));
}
let links = crate::cmd::links::resolve(&mut loaded.doc, path, s.prefs.update_links == "automatically");
let links = crate::cmd::links::resolve(&mut loaded.doc, path, s.prefs.update_links == "automatically", s.prefs.low_res_proxy_eps);
open_loaded(s, loaded, links, path.map(str::to_string), &LoadOptions::default(), true)
}
+59 -4
View File
@@ -21,7 +21,8 @@
//! Link) link to their files the same way: checking, updating, relinking and finding them on open
//! work alike, and they are listed. Embed (Break Link) turns one into an editable copy of its art.
use std::collections::BTreeMap;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Component, Path};
use serde_json::{Value, json};
@@ -286,6 +287,18 @@ fn previewing(d: &Document, key: &str) -> bool {
d.images.get(key).is_none_or(ImageBlob::is_proxy)
}
/// A linked raster image whose file is an EPS (Preferences › File Handling › Use Low Resolution
/// Proxy for Linked EPS).
fn linked_eps(link: &LinkInfo) -> bool {
fileio::format_for_name(link.name()).is_some_and(|f| f.id == "eps")
}
/// With `low_res_proxy_eps`, a linked EPS image that already shows its saved preview keeps it on
/// open instead of reading the file again (Update Links still reads modified files).
fn keep_eps_preview(low_res_proxy_eps: bool, g: &Group, d: &Document) -> bool {
low_res_proxy_eps && g.document.is_none() && linked_eps(&g.link) && previewing(d, &g.key)
}
/// Does group `g` of `d` show something other than its file: an image its preview, a placed
/// document nothing (its file's bytes are gone)?
fn stale(d: &Document, g: &Group) -> bool {
@@ -324,6 +337,47 @@ pub(crate) fn linked_files(d: &Document, doc_path: Option<&str>) -> Vec<LinkedFi
.collect()
}
/// `d` for output: linked raster images that still show only their saved preview read their files
/// again (Preferences › Use Low Resolution Proxy for Linked EPS keeps the preview on screen only).
pub(crate) fn full_linked_images<'a>(d: &'a Document, doc_path: Option<&str>) -> (Cow<'a, Document>, Vec<String>) {
let mut keys = BTreeSet::new();
d.visit_images(|_, im| {
if im.link.is_some() && d.images.get(&im.key).is_some_and(ImageBlob::is_proxy) {
keys.insert(im.key.clone());
}
});
if keys.is_empty() {
return (Cow::Borrowed(d), vec![]);
}
let mut out = d.clone();
let mut previews = BTreeSet::new();
let all = groups(d, None);
for key in keys {
let Some(g) = all.iter().find(|g| g.document.is_none() && g.key == key) else { continue };
let Some(mut f) = locate(&g.link, doc_path) else {
previews.insert(key);
continue;
};
let Some(bytes) = f.read().ok() else {
previews.insert(key);
continue;
};
let Ok(img) = fileio::raster_image(&bytes) else {
previews.insert(key);
continue;
};
let preview = d.images.get(&key).map(|b| b.bytes.clone());
let mime = fileio::format_for_name(g.link.name()).map(|f| f.mime.to_string()).unwrap_or_else(|| img.blob.mime.clone());
out.images.insert(key, ImageBlob { mime, bytes: img.blob.bytes, proxy: preview });
}
let mut warnings = vec![];
if !previews.is_empty() {
warnings
.push(format!("{} linked image(s) output as their previews: their files can't be found or read (relink or update them)", previews.len()));
}
(Cow::Owned(out), warnings)
}
/// The linked files of the open document `st` that `links.findFiles` looks for, each with the
/// objects in the layers that show it: the files of objects `ids` whatever their status, else the
/// files that can't be found.
@@ -559,11 +613,12 @@ impl Resolved {
/// away from their path take the new path. A link nothing is known of whose image shows a preview
/// (a file another app wrote kept only that) was never read: its file is read now, into the
/// image's bounds, which isn't a change to report. Nothing here is an undo step.
pub fn resolve(d: &mut Document, doc_path: Option<&str>, update: bool) -> Resolved {
pub fn resolve(d: &mut Document, doc_path: Option<&str>, update: bool, low_res_proxy_eps: bool) -> Resolved {
let mut out = Resolved::default();
for g in groups(d, None) {
let row = rows(std::iter::once(&g), doc_path);
let unread = g.document.is_none() && g.link.hash.is_none() && g.link.size.is_none() && previewing(d, &g.key);
let eps_preview = keep_eps_preview(low_res_proxy_eps, &g, d);
match probe(&g, doc_path) {
(Status::Missing, _) => out.missing.extend(row),
(Status::Modified, Some(mut f)) if update || unread => {
@@ -598,8 +653,8 @@ pub fn resolve(d: &mut Document, doc_path: Option<&str>, update: bool) -> Resolv
(Status::Ok, Some(mut f)) => {
let preview = previewing(d, &g.key);
// Read when the images show the preview, or when the file's details changed.
let bytes = if preview || f.bytes.is_some() { f.read().ok() } else { None };
if preview {
let bytes = if (preview && !eps_preview) || f.bytes.is_some() { f.read().ok() } else { None };
if preview && !eps_preview {
let Some(img) = bytes.as_deref().and_then(|b| fileio::raster_image(b).ok()) else {
out.missing.extend(row);
continue;
+16 -9
View File
@@ -127,7 +127,7 @@ pub(crate) fn document_art(
}
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);
super::super::links::resolve(&mut l.doc, path, false, 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);
@@ -281,22 +281,30 @@ pub(crate) fn node(d: &mut Document, s: &Source, link: LinkInfo, bounding: bool)
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.
/// `d` for output: linked images and placed documents that keep only their previews get their
/// files back, else output as their previews, with a warning.
pub(crate) fn full_documents(d: &Document) -> (Cow<'_, Document>, Vec<String>) {
let (d, mut warnings) = super::super::links::full_linked_images(d, None);
let doc = match &d {
Cow::Borrowed(doc) => doc,
Cow::Owned(doc) => doc,
};
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) {
doc.visit_placed(|_, p| {
if doc.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![]);
return (d, warnings);
}
let mut out = d.clone();
let mut out = match d {
Cow::Borrowed(doc) => doc.clone(),
Cow::Owned(doc) => doc,
};
let mut previews = BTreeSet::new();
for p in need {
let Some(blob) = d.images.get(&p.key) else { continue };
let Some(blob) = out.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(p.link.name(), &bytes).into(), bytes, proxy: blob.proxy.clone() });
@@ -306,7 +314,6 @@ pub(crate) fn full_documents(d: &Document) -> (Cow<'_, Document>, Vec<String>) {
}
}
}
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)",
+2 -2
View File
@@ -242,7 +242,7 @@ fn load(p: &Value, cmd: &str, board: Option<Rect>, link_pdf: bool) -> Result<Loa
));
}
// Linked images show their files, found from its folder.
super::links::resolve(&mut l.doc, src.path, false);
super::links::resolve(&mut l.doc, src.path, false, false);
(l.doc, l.warnings)
};
doc.drop_edit_modes();
@@ -297,7 +297,7 @@ pub(crate) fn pdf_art(
fileio::page_or_editing_data(src, bytes, o.password.as_deref(), read).map_err(|e| bad(cmd, format!("{name}: {e}")))?;
if alone {
// Linked images show their files, found from its folder.
super::links::resolve(&mut doc, path, false);
super::links::resolve(&mut doc, path, false, false);
}
doc.drop_edit_modes();
let layers = std::mem::take(&mut doc.layers);
+1 -1
View File
@@ -999,7 +999,7 @@ fn restore_one(s: &mut Session, store: &Arc<dyn RecoveryStore>, file: &str) -> R
doc.title = if meta.title.is_empty() { stem.to_string() } else { meta.title.clone() };
}
// Linked files are looked for from the document's own folder.
let links = super::links::resolve(&mut doc, meta.path.as_deref(), s.prefs.update_links == "automatically");
let links = super::links::resolve(&mut doc, meta.path.as_deref(), s.prefs.update_links == "automatically", s.prefs.low_res_proxy_eps);
let index = s.add_document(doc, meta.path.clone());
let st = s.doc_mut()?;
st.recovered = true;