From 53e24b5fdc3fe8df3c91a18b0b36f8694c53569e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 00:31:36 -0700 Subject: [PATCH] =?UTF-8?q?Export=20for=20Screens:=20a=20folder=20named=20?= =?UTF-8?q?with=20dotless=20=C4=B1=20is=20not=20merged=20into=20i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Export for Screens compared names and folders by upper-casing each character, which turned dotless ı into I. A row with the folder ı then wrote into the folder i, and an artboard named ı was numbered as a second i. NTFS keeps ı apart from i and I, and so does Unicode case folding outside Turkic languages, so fold now leaves ı as it is. --- crates/engine/src/cmd/fileio/screens.rs | 5 +++++ crates/engine/src/cmd/fileio/tests_screens.rs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/crates/engine/src/cmd/fileio/screens.rs b/crates/engine/src/cmd/fileio/screens.rs index 74848038b..ffc221de9 100644 --- a/crates/engine/src/cmd/fileio/screens.rs +++ b/crates/engine/src/cmd/fileio/screens.rs @@ -493,10 +493,15 @@ pub(crate) fn check_settings(s: &Session, p: &Value) -> Result<()> { /// `name` the way a case-insensitive file system compares names: lower-cased, then each character /// upper-cased where its upper case is one character. Names with the same lower case give the /// same key, and so do `ΟΣ`, `ος` and `οσ` (`ΟΣ`); `ᾈ` and `ᾀ` give `ᾀ`, and `ß` stays `ß`. +/// Dotless `ı` stays `ı`, as NTFS and Unicode case folding outside Turkic languages keep it apart +/// from `i` and `I`. fn fold(name: &str) -> String { name.to_lowercase() .chars() .map(|c| { + if c == 'ı' { + return c; + } let mut upper = c.to_uppercase(); match (upper.next(), upper.next()) { (Some(u), None) => u, diff --git a/crates/engine/src/cmd/fileio/tests_screens.rs b/crates/engine/src/cmd/fileio/tests_screens.rs index 2f9e80cfc..f1790225a 100644 --- a/crates/engine/src/cmd/fileio/tests_screens.rs +++ b/crates/engine/src/cmd/fileio/tests_screens.rs @@ -586,6 +586,22 @@ fn names_that_differ_only_in_greek_sigmas_are_numbered() { assert_eq!(names(&r), ["İ.svg", "i\u{307}-2.svg"]); } +/// Dotless `ı` and dotted `İ` are letters of their own, as on NTFS and in Unicode case folding +/// outside Turkic languages: names and folders spelled with them keep their own files, while `i` +/// and `I` still match. +#[test] +fn dotless_and_dotted_i_are_not_plain_i() { + let mut s = session(4); + for (index, name) in ["i", "ı", "İ", "I"].into_iter().enumerate() { + s.execute("artboard.setProps", &json!({"index": index, "name": name})).unwrap(); + } + let r = export(&mut s, json!({"name": "", "formats": [{"format": "svg"}]})); + assert_eq!(names(&r), ["i.svg", "ı.svg", "İ.svg", "I-2.svg"]); + let rows = json!([{"format": "svg", "folder": "i"}, {"format": "svg", "folder": "ı"}, {"format": "svg", "folder": "İ"}, {"format": "svg", "folder": "I"}]); + let r = export(&mut s, json!({"range": "1", "subfolders": true, "formats": rows})); + assert_eq!(names(&r), ["i/i.svg", "ı/i.svg", "İ/i.svg"]); +} + /// A fresh folder for one test, emptied first and removed when dropped (also when an assertion /// fails). #[cfg(not(target_arch = "wasm32"))]