mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Write legacy .ai layer names in Windows Latin-1 and warn when a character is replaced (#2693)
This commit is contained in:
committed by
bflatastic
parent
56df1a1663
commit
b5164825d6
@@ -85,6 +85,7 @@ pub const HIDDEN: &str = "hidden objects are left out (hidden layers are kept, h
|
||||
pub const HIDDEN_LAYER: &str = "hidden objects are moved to a hidden layer above their own (legacy .ai files can hide only layers)";
|
||||
pub const HIDDEN_SHOWN: &str = "hidden objects are written visible (legacy .ai files can hide only layers)";
|
||||
pub const TRANSPARENCY: &str = "transparency (opacity, blending modes, opacity masks) is written opaque: legacy .ai files have none";
|
||||
pub const LAYER_NAME: &str = "characters outside Windows Latin-1 in layer names are written as ? (legacy .ai files can't hold them)";
|
||||
pub const NAMES: &str = "object names are left out (legacy .ai files name only layers)";
|
||||
const PATTERNS: &str = "pattern fills are written as their tiles, clipped to the shape";
|
||||
const FREEFORM: &str = "freeform gradients are written as their average colour";
|
||||
@@ -452,6 +453,25 @@ impl<'a> Writer<'a> {
|
||||
|
||||
// ---------- layers and objects ----------
|
||||
|
||||
/// A layer's name as a PostScript string in Windows-1252, like the type; a character outside it
|
||||
/// is written as `?`, with a warning.
|
||||
fn layer_name(&mut self, name: &str) -> String {
|
||||
let mut lost = false;
|
||||
let bytes: Vec<u8> = name
|
||||
.chars()
|
||||
.map(|c| {
|
||||
latin::encode(c).unwrap_or_else(|| {
|
||||
lost = true;
|
||||
b'?'
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if lost {
|
||||
self.warn(LAYER_NAME);
|
||||
}
|
||||
ps::bytes(&bytes)
|
||||
}
|
||||
|
||||
/// A top-level layer. False when it isn't a layer.
|
||||
fn layer(&mut self, n: &Node) -> bool {
|
||||
let NodeKind::Layer { color, template, printable, children, clip, preview, dim_images } = &n.kind else { return false };
|
||||
@@ -464,6 +484,7 @@ impl<'a> Writer<'a> {
|
||||
_ => -1,
|
||||
};
|
||||
let flag = |b: bool| u8::from(b);
|
||||
let name = self.layer_name(n.name.as_deref().unwrap_or("Layer"));
|
||||
// A template layer doesn't print and shows dimmed.
|
||||
let _ = writeln!(
|
||||
self.body,
|
||||
@@ -473,7 +494,7 @@ impl<'a> Writer<'a> {
|
||||
flag(!n.locked),
|
||||
flag(*printable && !*template),
|
||||
flag(dim_images.is_some() || *template),
|
||||
ps::string(n.name.as_deref().unwrap_or("Layer")),
|
||||
name,
|
||||
);
|
||||
self.locked = false;
|
||||
self.line("0 A");
|
||||
@@ -501,13 +522,14 @@ impl<'a> Writer<'a> {
|
||||
_ => -1,
|
||||
};
|
||||
let name = format!("{} (hidden)", n.name.as_deref().unwrap_or("Layer"));
|
||||
let name = self.layer_name(&name);
|
||||
let _ = writeln!(
|
||||
self.body,
|
||||
"%AI5_BeginLayer\n0 1 {} {} {} 0 {index} {r} {g} {b} Lb\n{} Ln",
|
||||
u8::from(!n.locked),
|
||||
u8::from(*printable && !*template),
|
||||
u8::from(*template),
|
||||
ps::string(&name),
|
||||
name,
|
||||
);
|
||||
self.locked = false;
|
||||
self.line("0 A");
|
||||
|
||||
@@ -642,3 +642,26 @@ fn hidden_objects_can_be_left_out_or_shown() {
|
||||
assert_eq!(pair.len(), 2);
|
||||
assert!(pair.iter().all(|n| n.visible));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_names_outside_latin_1_are_replaced_and_warned() {
|
||||
let layered = |name: &str| {
|
||||
let mut doc = Document::new(100.0, 100.0);
|
||||
doc.layers.clear();
|
||||
let layer = doc.add_layer(Some(name));
|
||||
let n = shape(&mut doc, shapes::rectangle(Rect::new(10.0, 10.0, 40.0, 40.0)), red(), Paint::None);
|
||||
doc.insert(Some(layer), usize::MAX, n).unwrap();
|
||||
// A hidden object, so the layer also gets a hidden layer named after it.
|
||||
let mut hidden = shape(&mut doc, shapes::rectangle(Rect::new(50.0, 10.0, 80.0, 40.0)), red(), Paint::None);
|
||||
hidden.visible = false;
|
||||
doc.insert(Some(layer), usize::MAX, hidden).unwrap();
|
||||
write(&doc)
|
||||
};
|
||||
let out = layered("\u{b808}\u{c774}\u{c5b4} 1 Caf\u{e9}");
|
||||
let text = String::from_utf8_lossy(&out.bytes);
|
||||
assert!(text.contains("(??? 1 Caf\\351) Ln"), "{text}");
|
||||
assert!(text.contains("(??? 1 Caf\\351 \\(hidden\\)) Ln"), "{text}");
|
||||
assert!(out.warnings.iter().any(|w| w == LAYER_NAME), "{:?}", out.warnings);
|
||||
// A name Latin-1 can hold is written as it is, with no warning.
|
||||
assert!(!layered("Caf\u{e9}").warnings.iter().any(|w| w == LAYER_NAME));
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ private editing data. VectorCraft:
|
||||
graphics and gradient or pattern fills make that type outlines, and Text: Outlines (`outlineText: true`) writes
|
||||
all type as outlines. The format can hide only layers: hidden objects go to a hidden layer of their own above
|
||||
their layer, are omitted, or are made visible (Hidden Objects, `hiddenObjects: "layer"|"omit"|"visible"`).
|
||||
Sublayers become groups; appearances, effects and brushes are expanded; object names are left out.
|
||||
Sublayers become groups; appearances, effects and brushes are expanded; object names are left out; layer
|
||||
names are written in Windows-1252, with `?` for characters outside it.
|
||||
The file is also a PostScript program that runs: its prolog, a procedure set of VectorCraft's own written from
|
||||
the PostScript Language Reference, gives the format's operators their meaning, so it prints and previews
|
||||
(gradients filled with their first stop's colour, type left to the apps that read the format). VectorCraft
|
||||
|
||||
Reference in New Issue
Block a user