mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 22:28:09 +00:00
Export for Screens: an artboard name keeps its dots (#6143)
The artboard and asset file stems came from unique_file_names, which turned every character except letters, digits, - and _ into a dash, so the dot of example.artboard became a dash. Export for Screens now uses a variant that keeps dots (a name of only dots takes the Artboard-N fallback); safe_file_name still drops trailing periods and guards device names. Test: an_artboard_name_keeps_its_dots.
This commit is contained in:
@@ -129,7 +129,9 @@ pub(crate) use access::{drive_share_path, share_path, touched};
|
||||
pub(crate) use access::{may_read, may_write};
|
||||
pub use commands::specs;
|
||||
pub use export_names::artboard_file_names;
|
||||
pub(crate) use export_names::{default_name, merge, unique_file_names, with_black_output, write_or_return};
|
||||
pub(crate) use export_names::{
|
||||
artboard_file_names_keeping_dots, default_name, merge, unique_file_names_keeping_dots, with_black_output, write_or_return,
|
||||
};
|
||||
use export_names::{distinct_names_by, write_encoded};
|
||||
use formats::{ARTBOARD, ARTBOARDS, RANGE, USE_ARTBOARDS, import_name, writable};
|
||||
pub use formats::{
|
||||
|
||||
@@ -5,19 +5,41 @@ use super::{Encoded, EngineError, Result, Session, Value, json};
|
||||
/// `-`, `Artboard-N` when unnamed, and `-2`, `-3`… after a name already used (compared without
|
||||
/// case: `Icon` and `icon` are one file on most desktop file systems).
|
||||
pub fn artboard_file_names(doc: &vectorcraft_doc::Document, boards: &[usize]) -> Vec<String> {
|
||||
unique_file_names(boards.iter().map(|&b| (doc.artboards.get(b).map_or("", |a| a.name.as_str()), format!("Artboard-{}", b + 1))))
|
||||
unique_file_names(artboard_names(doc, boards))
|
||||
}
|
||||
|
||||
/// [`artboard_file_names`] with the dots of a name kept (`example.artboard`), for Export for Screens,
|
||||
/// which puts every stem through `safe_file_name` after this (trailing periods, device names).
|
||||
pub(crate) fn artboard_file_names_keeping_dots(doc: &vectorcraft_doc::Document, boards: &[usize]) -> Vec<String> {
|
||||
unique_file_names_keeping_dots(artboard_names(doc, boards))
|
||||
}
|
||||
|
||||
/// Each of `boards`' name, with the file name to use when it has none.
|
||||
fn artboard_names<'a>(doc: &'a vectorcraft_doc::Document, boards: &'a [usize]) -> impl Iterator<Item = (&'a str, String)> {
|
||||
boards.iter().map(|&b| (doc.artboards.get(b).map_or("", |a| a.name.as_str()), format!("Artboard-{}", b + 1)))
|
||||
}
|
||||
|
||||
/// File-name parts for `(name, fallback)` pairs (artboards, assets): the name with unsafe
|
||||
/// characters as `-`, the fallback when it is empty, and `-2`, `-3`… after a name already used
|
||||
/// (compared without case).
|
||||
pub(crate) fn unique_file_names<'a>(names: impl IntoIterator<Item = (&'a str, String)>) -> Vec<String> {
|
||||
fn unique_file_names<'a>(names: impl IntoIterator<Item = (&'a str, String)>) -> Vec<String> {
|
||||
distinct_names(names.into_iter().map(|(name, fallback)| {
|
||||
let base: String = name.chars().map(|c| if c.is_alphanumeric() || c == '-' || c == '_' { c } else { '-' }).collect();
|
||||
if base.is_empty() { fallback } else { base }
|
||||
}))
|
||||
}
|
||||
|
||||
/// [`unique_file_names`] keeping `.` in a name (an artboard called `example.artboard` exports as
|
||||
/// `example.artboard.png`, #6143). A name of only dots (`.` and `..` name other folders) takes the
|
||||
/// fallback; the caller still runs each stem through `safe_file_name`, which drops trailing periods
|
||||
/// and guards Windows device names.
|
||||
pub(crate) fn unique_file_names_keeping_dots<'a>(names: impl IntoIterator<Item = (&'a str, String)>) -> Vec<String> {
|
||||
distinct_names(names.into_iter().map(|(name, fallback)| {
|
||||
let base: String = name.chars().map(|c| if c.is_alphanumeric() || matches!(c, '-' | '_' | '.') { c } else { '-' }).collect();
|
||||
if base.trim_matches('.').is_empty() { fallback } else { base }
|
||||
}))
|
||||
}
|
||||
|
||||
/// `names` with `-2`, `-3`… after a name already used (compared without case).
|
||||
pub(super) fn distinct_names(names: impl IntoIterator<Item = String>) -> Vec<String> {
|
||||
distinct_names_by(names, str::to_lowercase)
|
||||
|
||||
@@ -21,8 +21,8 @@ use super::encode::single_artboard;
|
||||
use super::export::{export_crop, isolated, selected_artwork, without_artboards};
|
||||
use super::naming::{self, NameParts};
|
||||
use super::{
|
||||
ARTBOARD_PARAMS, ArtboardPick, Format, artboard_file_names, create_dir, distinct_names_by, encode_with_warnings, merge, unique_file_names,
|
||||
with_black_output, writable, write_file,
|
||||
ARTBOARD_PARAMS, ArtboardPick, Format, artboard_file_names_keeping_dots, create_dir, distinct_names_by, encode_with_warnings, merge,
|
||||
unique_file_names_keeping_dots, with_black_output, writable, write_file,
|
||||
};
|
||||
|
||||
const C: &str = "document.exportForScreens";
|
||||
@@ -1184,7 +1184,7 @@ fn items(
|
||||
// the item folders are (`fold`).
|
||||
let safe = |stems: Vec<String>| distinct_names_by(stems.iter().map(|s| safe_file_name(s)), fold);
|
||||
let named = |pieces: Vec<(String, Arc<Document>, Option<usize>, Rect)>, fallback: &str| -> Vec<Item> {
|
||||
let stems = safe(unique_file_names(pieces.iter().map(|(n, ..)| (n.as_str(), fallback.to_string()))));
|
||||
let stems = safe(unique_file_names_keeping_dots(pieces.iter().map(|(n, ..)| (n.as_str(), fallback.to_string()))));
|
||||
pieces
|
||||
.into_iter()
|
||||
.zip(stems)
|
||||
@@ -1261,7 +1261,7 @@ fn items(
|
||||
if boards.is_empty() {
|
||||
return Err(bad(C, "the document has no artboard"));
|
||||
}
|
||||
let stems = safe(artboard_file_names(doc, &boards));
|
||||
let stems = safe(artboard_file_names_keeping_dots(doc, &boards));
|
||||
Ok(boards
|
||||
.into_iter()
|
||||
.zip(stems)
|
||||
|
||||
@@ -148,6 +148,18 @@ fn device_names_and_trailing_periods_follow_windows_naming_rules() {
|
||||
assert_eq!(r["name"], "nul-.zip");
|
||||
}
|
||||
|
||||
/// A dot in an artboard's name stays a dot (`example.artboard.png`, #6143); a name of only dots, or
|
||||
/// ending in one, still can't name another folder or end in a period, and `/` and `:` are dashes.
|
||||
#[test]
|
||||
fn an_artboard_name_keeps_its_dots() {
|
||||
let mut s = session(5);
|
||||
for (index, name) in ["example.artboard", "v1.2.final", "..", "a/b:c", "trail."].iter().enumerate() {
|
||||
s.execute("artboard.setProps", &json!({"index": index, "name": name})).unwrap();
|
||||
}
|
||||
let r = export(&mut s, json!({"formats": [{"format": "png"}]}));
|
||||
assert_eq!(names(&r), ["example.artboard.png", "v1.2.final.png", "Artboard-3.png", "a-b-c.png", "trail.png"]);
|
||||
}
|
||||
|
||||
/// `first_file` names the file the export writes first, for the same params: the item's file name,
|
||||
/// a prefix and a suffix with characters Windows does not allow, a range, a Windows device name,
|
||||
/// every kind of folder, presets, size suffixes, a name pattern with its counter and date, an
|
||||
|
||||
Reference in New Issue
Block a user