Skip to content

Commit

Permalink
Make user_name_to_file_name public, tweak signature
Browse files Browse the repository at this point in the history
The function now takes a closure instead of a hashset, and now works
with any AsRef<str> type, anod not just Name; this makes it more
suitable as public API.
  • Loading branch information
cmyr committed Nov 30, 2022
1 parent 9a2bfbc commit cce003d
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 16 deletions.
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

0 comments on commit cce003d

Please sign in to comment.