mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 20:27:05 -06:00
Fix #4036: a pattern defined only in a library's editing data keeps its swatch
A .ai swatch library without layers comes in as its PDF part with the swatches of its editing data's palette. A pattern swatch was kept only when the PDF part defined its pattern, and a pattern library's PDF part usually defines none, so its pattern swatches were dropped. ai::palette now gives pattern swatches for the patterns the editing data defines too, and returns the definitions the PDF part lacks that a palette swatch paints with, with the images their art shows. The document gets them with new object ids.
This commit is contained in:
@@ -33,7 +33,7 @@ use super::lex::find;
|
||||
use crate::ps;
|
||||
|
||||
pub(crate) use frames::text_frames;
|
||||
pub use read::{IMAGE_TOO_LARGE, Structure, artboards, palette, read, variables};
|
||||
pub use read::{IMAGE_TOO_LARGE, PaletteSwatches, Structure, artboards, palette, read, variables};
|
||||
pub(crate) use read::{slot_frame, slot_of};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -74,7 +74,7 @@ mod ops;
|
||||
mod types;
|
||||
|
||||
use art::preview_png;
|
||||
pub use document::{VariableLibrary, artboards, palette, variables};
|
||||
pub use document::{PaletteSwatches, VariableLibrary, artboards, palette, variables};
|
||||
pub(crate) use document::{slot_frame, slot_of};
|
||||
pub use types::IMAGE_TOO_LARGE;
|
||||
use types::{BLEND_BUDGET, FLIP, Frame, GState, Kind, PatternHead, Place, Raster};
|
||||
|
||||
@@ -72,15 +72,42 @@ fn artboard_array(data: &[u8]) -> Option<&[u8]> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The palette of a `.ai` file's editing data, as [`palette`] reads it.
|
||||
pub struct PaletteSwatches {
|
||||
pub swatches: Vec<Swatch>,
|
||||
pub groups: Vec<vectorcraft_color::swatch::SwatchGroup>,
|
||||
/// The patterns the editing data defines that the given patterns lack and a swatch paints with.
|
||||
pub patterns: Vec<PatternDef>,
|
||||
/// The image blobs the art of `patterns` shows, by key.
|
||||
pub images: std::collections::BTreeMap<String, ImageBlob>,
|
||||
}
|
||||
|
||||
/// The swatches and color groups of the palette of the editing data `data`, for a file whose
|
||||
/// layers aren't read: the swatches its PDF part has are the defaults (#4036). They come as
|
||||
/// [`read`] makes them, with pattern swatches only for the patterns in `patterns`; `None` when the
|
||||
/// palette is empty or reading stops before it.
|
||||
pub fn palette(data: &[u8], patterns: &[PatternDef]) -> Option<(Vec<Swatch>, Vec<vectorcraft_color::swatch::SwatchGroup>)> {
|
||||
/// [`read`] makes them, with pattern swatches only for the patterns in `patterns` and those the
|
||||
/// editing data defines; `None` when the palette is empty or reading stops before it.
|
||||
pub fn palette(data: &[u8], patterns: &[PatternDef]) -> Option<PaletteSwatches> {
|
||||
let mut r = Reader::new(data);
|
||||
// The palette and the gradients it names come before the art, so an error in the art leaves them read.
|
||||
// The palette, the gradients and the patterns it names come before the art, so an error in the
|
||||
// art leaves them read.
|
||||
let _ = r.run();
|
||||
r.palette_swatches(patterns)
|
||||
let mut known = patterns.to_vec();
|
||||
let defined = std::mem::take(&mut r.doc.patterns);
|
||||
known.extend(defined.iter().filter(|d| !patterns.iter().any(|p| p.name == d.name)).cloned());
|
||||
let (swatches, groups) = r.palette_swatches(&known)?;
|
||||
let painted = |name: &str| {
|
||||
swatches.iter().chain(groups.iter().flat_map(|g| &g.swatches)).any(|s| matches!(&s.paint, Paint::Pattern { pattern, .. } if pattern == name))
|
||||
};
|
||||
let added: Vec<PatternDef> = defined.into_iter().filter(|d| !patterns.iter().any(|p| p.name == d.name) && painted(&d.name)).collect();
|
||||
let mut shown = Document::new(1.0, 1.0);
|
||||
shown.patterns.clone_from(&added);
|
||||
let mut images = std::collections::BTreeMap::new();
|
||||
shown.visit_images(|_, im| {
|
||||
if let Some(blob) = r.doc.images.get(&im.key) {
|
||||
images.insert(im.key.clone(), blob.clone());
|
||||
}
|
||||
});
|
||||
Some(PaletteSwatches { swatches, groups, patterns: added, images })
|
||||
}
|
||||
|
||||
/// [`artboards`] as far as reading `data` from its start gets: those read before an error, and
|
||||
|
||||
@@ -89,9 +89,18 @@ pub fn layered_ai(private: &[u8], visible: Document, warnings: Vec<String>, outl
|
||||
/// `doc` (a `.ai` read as its PDF part, in place of its layers) with the swatches and color groups
|
||||
/// of the palette of its editing data `data` in place of the default ones, as [`build`] gives a
|
||||
/// document read from the layers. The spot and pattern swatches of the PDF part's art that the
|
||||
/// palette doesn't have follow the palette's swatches. Left as it is when the palette is empty.
|
||||
/// palette doesn't have follow the palette's swatches. The patterns that the editing data defines and
|
||||
/// the PDF part lacks come in for the palette's pattern swatches, with the images they show. Left as
|
||||
/// it is when the palette is empty.
|
||||
fn use_palette(data: &[u8], doc: &mut Document) {
|
||||
let Some((swatches, groups)) = ai::palette(data, &doc.patterns) else { return };
|
||||
let Some(ai::PaletteSwatches { swatches, groups, patterns, images }) = ai::palette(data, &doc.patterns) else { return };
|
||||
for (key, blob) in images {
|
||||
doc.images.entry(key).or_insert(blob);
|
||||
}
|
||||
for mut p in patterns {
|
||||
p.art = p.art.iter().map(|n| Arc::new(doc.reid(n))).collect();
|
||||
doc.patterns.push(p);
|
||||
}
|
||||
let named = |name: &str| swatches.iter().chain(groups.iter().flat_map(|g| &g.swatches)).any(|s| s.name == name);
|
||||
let art: Vec<_> =
|
||||
doc.swatches.iter().filter(|s| (s.spot || matches!(s.paint, vectorcraft_color::Paint::Pattern { .. })) && !named(&s.name)).cloned().collect();
|
||||
|
||||
@@ -290,6 +290,32 @@ fn an_ai_file_that_comes_in_as_its_pdf_part_keeps_the_swatches_of_its_palette()
|
||||
assert!(d.swatch("Red ink").is_some_and(|s| s.spot));
|
||||
}
|
||||
|
||||
/// A `.ai` pattern library (a palette of pattern swatches and no layers) whose PDF part defines no
|
||||
/// pattern gets the patterns its editing data defines with the palette's pattern swatches.
|
||||
#[test]
|
||||
fn an_ai_file_without_layers_keeps_the_patterns_of_its_palette() {
|
||||
let dots = "%AI3_BeginPattern: (Dots)\n(Dots) 0 0 4 4\n%_0 0 0 1 k\n%_1 1 m\n%_3 1 L\n%_3 3 L\n%_1 3 L\n%_f\nE\n%AI3_EndPattern\n";
|
||||
let palette =
|
||||
"%AI5_BeginPalette\n0 0 Pb\n0 0 0 0 1 1 1 Xa\n(White)\nPc\n(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p\n(Dots)\nPc\nPB\n%AI5_EndPalette\n";
|
||||
let art = native(&format!("{dots}{palette}"));
|
||||
let (d, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
|
||||
assert!(!read, "{warnings:?}");
|
||||
let swatches: Vec<&str> = d.swatches.iter().map(|s| s.name.as_str()).collect();
|
||||
assert_eq!(swatches, ["[None]", "White", "Dots"], "{warnings:?}");
|
||||
assert!(matches!(&d.swatch("Dots").map(|s| &s.paint), Some(vectorcraft_color::Paint::Pattern { pattern, .. }) if pattern == "Dots"));
|
||||
assert!(d.pattern("Dots").is_some_and(|p| !p.art.is_empty()));
|
||||
|
||||
// A pattern that shows an image brings the image's data with it.
|
||||
let pic = "%AI3_BeginPattern: (Pic)\n(Pic) 0 0 4 4\n%AI5_File:\n%AI5_BeginRaster\n() 1 XG\n/DeviceGray XN\n[ 4 0 0 4 0 4 ] 1 1 0 Xh\n\
|
||||
[ 4 0 0 4 0 4 ] 0 0 1 1 1 1 8 1 0 0 1 0 4 4 0 0\n%%BeginData: 1\rXI\n\u{0}%%EndData\r\nXH\r\n%AI5_EndRaster\r\nN\nE\n%AI3_EndPattern\n";
|
||||
let art = native(&format!("{pic}{}", palette.replace("(Dots)", "(Pic)")));
|
||||
let (d, warnings, _) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
|
||||
let mut keys = vec![];
|
||||
d.visit_images(|_, im| keys.push(im.key.clone()));
|
||||
assert_eq!(keys.len(), 1, "{warnings:?}");
|
||||
assert!(keys.iter().all(|k| d.images.contains_key(k)), "{keys:?}");
|
||||
}
|
||||
|
||||
/// An EPS whose editing data can't be used, a palette with no layers among others, comes in as its
|
||||
/// page with the swatches and color groups of that palette in place of the defaults.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user