Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make name-to-file-name conversion functions public #282

Merged
merged 1 commit into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,5 @@ pub use identifier::Identifier;
pub use kerning::Kerning;
pub use layer::{Layer, LayerSet};
pub use shared_types::{Color, Plist};
pub use util::user_name_to_file_name;
pub use write::{QuoteChar, WriteOptions};
47 changes: 31 additions & 16 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,39 @@ pub(crate) fn recursive_sort_plist_keys(plist: &mut plist::Dictionary) {

/// Given a glyph `name`, return an appropriate file name.
pub(crate) fn default_file_name_for_glyph_name(name: &Name, existing: &HashSet<String>) -> PathBuf {
user_name_to_file_name(name, "", ".glif", existing)
user_name_to_file_name(name, "", ".glif", |name| !existing.contains(name))
}

/// Given a layer `name`, return an appropriate file name.
pub(crate) fn default_file_name_for_layer_name(name: &Name, existing: &HashSet<String>) -> PathBuf {
user_name_to_file_name(name, "glyphs.", "", existing)
user_name_to_file_name(name, "glyphs.", "", |name| !existing.contains(name))
}

/// Given a `name`, return an appropriate file name.
///
/// Expects `existing` to be a set of paths (potentially lossily) converted to
/// _lowercased [`String`]s_. The file names are going to end up in a UTF-8
/// encoded Apple property list XML file, so file names will be treated as
/// Unicode strings.
/// This file name is computed via the [Common User Name to File Name Algorithm][algo]
/// defined in the UFO spec.
///
/// the `prefix` and `suffix` fields will be added to the start and end of the
/// generated path; for instance the `suffix` might be a file extension.
///
/// The `accept_path` closure is a way of indicating whether or not a candidate
/// path should be used. In general, this involves ensuring that the candidate path
/// does not already exist. Paths are always lowercased, and case insensitive.
///
/// # Panics
///
/// Panics if a case-insensitive file name clash was detected and no unique
/// value could be created after 99 numbering attempts.
fn user_name_to_file_name(
name: &Name,
///
/// [algo]: https://unifiedfontobject.org/versions/ufo3/conventions/#common-user-name-to-file-name-algorithm
pub fn user_name_to_file_name(
name: impl AsRef<str>,
prefix: &str,
suffix: &str,
existing: &HashSet<String>,
mut accept_path: impl FnMut(&str) -> bool,
) -> PathBuf {
let name = name.as_ref();
let mut result = String::with_capacity(prefix.len() + name.len() + suffix.len());

// Filter illegal characters from name.
Expand Down Expand Up @@ -136,7 +144,7 @@ fn user_name_to_file_name(
// second way should one exhaust them, but it is unlikely to be needed in
// practice. 1e15 numbers is a ridicuously high number where holding all
// those glyph names in memory would exhaust it.
if existing.contains(&result.to_lowercase()) {
if !accept_path(&result.to_lowercase()) {
// First, cut off the suffix (plus the space needed for the number
// counter if necessary).
const NUMBER_LEN: usize = 2;
Expand All @@ -155,7 +163,8 @@ fn user_name_to_file_name(
for counter in 1..100u8 {
write!(&mut result, "{:0>2}", counter).unwrap();
result.push_str(suffix);
if !existing.contains(&result.to_lowercase()) {
if accept_path(&result.to_lowercase()) {
//if !existing.contains(&result.to_lowercase()) {
found_unique = true;
break;
}
Expand Down Expand Up @@ -184,9 +193,11 @@ mod tests {

fn file_name(name: &str, prefix: &str, suffix: &str) -> String {
let container: HashSet<String> = HashSet::new();
user_name_to_file_name(&Name::new_raw(name), prefix, suffix, &container)
.to_string_lossy()
.to_string()
user_name_to_file_name(&Name::new_raw(name), prefix, suffix, |name| {
!container.contains(name)
})
.to_string_lossy()
.to_string()
}

#[test]
Expand Down Expand Up @@ -281,7 +292,9 @@ mod tests {
let mut container = HashSet::new();
let mut existing = HashSet::new();
for name in ["Ab", "a_b"] {
let path = user_name_to_file_name(&Name::new_raw(name), "", ".glif", &existing);
let path = user_name_to_file_name(&Name::new_raw(name), "", ".glif", |name| {
!existing.contains(name)
});
existing.insert(path.to_string_lossy().to_string().to_lowercase());
container.insert(path.to_string_lossy().to_string());
}
Expand All @@ -298,7 +311,9 @@ mod tests {
let mut container = HashSet::new();
let mut existing = HashSet::new();
for name in ["A".repeat(300), "a_".repeat(150)] {
let path = user_name_to_file_name(&Name::new_raw(&name), "", ".glif", &existing);
let path = user_name_to_file_name(&Name::new_raw(&name), "", ".glif", |name| {
!existing.contains(name)
});
existing.insert(path.to_string_lossy().to_string().to_lowercase());
container.insert(path.to_string_lossy().to_string());
}
Expand Down