filenames: Implements UFO User Name to File Name Algorithm

This module implements the algorithm for converting between a “user name” - something that a user can choose arbitrarily inside a font editor - and a file name suitable for use in a wide range of operating systems and filesystems.

The UFO 3 specification provides an example of an algorithm for such conversion, which avoids illegal characters, reserved file names, ambiguity between upper- and lower-case characters, and clashes with existing files.

This code was originally copied from ufoLib by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:

  • Erik van Blokland

  • Tal Leming

  • Just van Rossum

exception fontTools.misc.filenames.NameTranslationError[source]

Bases: Exception

fontTools.misc.filenames.userNameToFileName(userName, existing=[], prefix='', suffix='')[source]

Converts from a user name to a file name.

Takes care to avoid illegal characters, reserved file names, ambiguity between upper- and lower-case characters, and clashes with existing files.

Parameters:
  • userName (str) – The input file name.

  • existing – A case-insensitive list of all existing file names.

  • prefix – Prefix to be prepended to the file name.

  • suffix – Suffix to be appended to the file name.

Returns:

A suitable filename.

Raises:

NameTranslationError – If no suitable name could be generated.

Examples:

>>> userNameToFileName("a") == "a"
True
>>> userNameToFileName("A") == "A_"
True
>>> userNameToFileName("AE") == "A_E_"
True
>>> userNameToFileName("Ae") == "A_e"
True
>>> userNameToFileName("ae") == "ae"
True
>>> userNameToFileName("aE") == "aE_"
True
>>> userNameToFileName("a.alt") == "a.alt"
True
>>> userNameToFileName("A.alt") == "A_.alt"
True
>>> userNameToFileName("A.Alt") == "A_.A_lt"
True
>>> userNameToFileName("A.aLt") == "A_.aL_t"
True
>>> userNameToFileName(u"A.alT") == "A_.alT_"
True
>>> userNameToFileName("T_H") == "T__H_"
True
>>> userNameToFileName("T_h") == "T__h"
True
>>> userNameToFileName("t_h") == "t_h"
True
>>> userNameToFileName("F_F_I") == "F__F__I_"
True
>>> userNameToFileName("f_f_i") == "f_f_i"
True
>>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
True
>>> userNameToFileName(".notdef") == "_notdef"
True
>>> userNameToFileName("con") == "_con"
True
>>> userNameToFileName("CON") == "C_O_N_"
True
>>> userNameToFileName("con.alt") == "_con.alt"
True
>>> userNameToFileName("alt.con") == "alt._con"
True
fontTools.misc.filenames.handleClash1(userName, existing=[], prefix='', suffix='')[source]

existing should be a case-insensitive list of all existing file names.

>>> prefix = ("0" * 5) + "."
>>> suffix = "." + ("0" * 10)
>>> existing = ["a" * 5]
>>> e = list(existing)
>>> handleClash1(userName="A" * 5, existing=e,
...         prefix=prefix, suffix=suffix) == (
...         '00000.AAAAA000000000000001.0000000000')
True
>>> e = list(existing)
>>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
>>> handleClash1(userName="A" * 5, existing=e,
...         prefix=prefix, suffix=suffix) == (
...         '00000.AAAAA000000000000002.0000000000')
True
>>> e = list(existing)
>>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
>>> handleClash1(userName="A" * 5, existing=e,
...         prefix=prefix, suffix=suffix) == (
...         '00000.AAAAA000000000000001.0000000000')
True
fontTools.misc.filenames.handleClash2(existing=[], prefix='', suffix='')[source]

existing should be a case-insensitive list of all existing file names.

>>> prefix = ("0" * 5) + "."
>>> suffix = "." + ("0" * 10)
>>> existing = [prefix + str(i) + suffix for i in range(100)]
>>> e = list(existing)
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
...         '00000.100.0000000000')
True
>>> e = list(existing)
>>> e.remove(prefix + "1" + suffix)
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
...         '00000.1.0000000000')
True
>>> e = list(existing)
>>> e.remove(prefix + "2" + suffix)
>>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
...         '00000.2.0000000000')
True