new ip
This commit is contained in:
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,334 @@
|
||||
"""
|
||||
Conversion functions.
|
||||
"""
|
||||
|
||||
# adapted from the UFO spec
|
||||
|
||||
|
||||
def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
|
||||
# gather known kerning groups based on the prefixes
|
||||
firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups)
|
||||
# Make lists of groups referenced in kerning pairs.
|
||||
for first, seconds in list(kerning.items()):
|
||||
if first in groups and first not in glyphSet:
|
||||
if not first.startswith("public.kern1."):
|
||||
firstReferencedGroups.add(first)
|
||||
for second in list(seconds.keys()):
|
||||
if second in groups and second not in glyphSet:
|
||||
if not second.startswith("public.kern2."):
|
||||
secondReferencedGroups.add(second)
|
||||
# Create new names for these groups.
|
||||
firstRenamedGroups = {}
|
||||
for first in firstReferencedGroups:
|
||||
# Make a list of existing group names.
|
||||
existingGroupNames = list(groups.keys()) + list(firstRenamedGroups.keys())
|
||||
# Remove the old prefix from the name
|
||||
newName = first.replace("@MMK_L_", "")
|
||||
# Add the new prefix to the name.
|
||||
newName = "public.kern1." + newName
|
||||
# Make a unique group name.
|
||||
newName = makeUniqueGroupName(newName, existingGroupNames)
|
||||
# Store for use later.
|
||||
firstRenamedGroups[first] = newName
|
||||
secondRenamedGroups = {}
|
||||
for second in secondReferencedGroups:
|
||||
# Make a list of existing group names.
|
||||
existingGroupNames = list(groups.keys()) + list(secondRenamedGroups.keys())
|
||||
# Remove the old prefix from the name
|
||||
newName = second.replace("@MMK_R_", "")
|
||||
# Add the new prefix to the name.
|
||||
newName = "public.kern2." + newName
|
||||
# Make a unique group name.
|
||||
newName = makeUniqueGroupName(newName, existingGroupNames)
|
||||
# Store for use later.
|
||||
secondRenamedGroups[second] = newName
|
||||
# Populate the new group names into the kerning dictionary as needed.
|
||||
newKerning = {}
|
||||
for first, seconds in list(kerning.items()):
|
||||
first = firstRenamedGroups.get(first, first)
|
||||
newSeconds = {}
|
||||
for second, value in list(seconds.items()):
|
||||
second = secondRenamedGroups.get(second, second)
|
||||
newSeconds[second] = value
|
||||
newKerning[first] = newSeconds
|
||||
# Make copies of the referenced groups and store them
|
||||
# under the new names in the overall groups dictionary.
|
||||
allRenamedGroups = list(firstRenamedGroups.items())
|
||||
allRenamedGroups += list(secondRenamedGroups.items())
|
||||
for oldName, newName in allRenamedGroups:
|
||||
group = list(groups[oldName])
|
||||
groups[newName] = group
|
||||
# Return the kerning and the groups.
|
||||
return newKerning, groups, dict(side1=firstRenamedGroups, side2=secondRenamedGroups)
|
||||
|
||||
|
||||
def findKnownKerningGroups(groups):
|
||||
"""
|
||||
This will find kerning groups with known prefixes.
|
||||
In some cases not all kerning groups will be referenced
|
||||
by the kerning pairs. The algorithm for locating groups
|
||||
in convertUFO1OrUFO2KerningToUFO3Kerning will miss these
|
||||
unreferenced groups. By scanning for known prefixes
|
||||
this function will catch all of the prefixed groups.
|
||||
|
||||
These are the prefixes and sides that are handled:
|
||||
@MMK_L_ - side 1
|
||||
@MMK_R_ - side 2
|
||||
|
||||
>>> testGroups = {
|
||||
... "@MMK_L_1" : None,
|
||||
... "@MMK_L_2" : None,
|
||||
... "@MMK_L_3" : None,
|
||||
... "@MMK_R_1" : None,
|
||||
... "@MMK_R_2" : None,
|
||||
... "@MMK_R_3" : None,
|
||||
... "@MMK_l_1" : None,
|
||||
... "@MMK_r_1" : None,
|
||||
... "@MMK_X_1" : None,
|
||||
... "foo" : None,
|
||||
... }
|
||||
>>> first, second = findKnownKerningGroups(testGroups)
|
||||
>>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
|
||||
True
|
||||
>>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
|
||||
True
|
||||
"""
|
||||
knownFirstGroupPrefixes = ["@MMK_L_"]
|
||||
knownSecondGroupPrefixes = ["@MMK_R_"]
|
||||
firstGroups = set()
|
||||
secondGroups = set()
|
||||
for groupName in list(groups.keys()):
|
||||
for firstPrefix in knownFirstGroupPrefixes:
|
||||
if groupName.startswith(firstPrefix):
|
||||
firstGroups.add(groupName)
|
||||
break
|
||||
for secondPrefix in knownSecondGroupPrefixes:
|
||||
if groupName.startswith(secondPrefix):
|
||||
secondGroups.add(groupName)
|
||||
break
|
||||
return firstGroups, secondGroups
|
||||
|
||||
|
||||
def makeUniqueGroupName(name, groupNames, counter=0):
|
||||
# Add a number to the name if the counter is higher than zero.
|
||||
newName = name
|
||||
if counter > 0:
|
||||
newName = "%s%d" % (newName, counter)
|
||||
# If the new name is in the existing group names, recurse.
|
||||
if newName in groupNames:
|
||||
return makeUniqueGroupName(name, groupNames, counter + 1)
|
||||
# Otherwise send back the new name.
|
||||
return newName
|
||||
|
||||
|
||||
def test():
|
||||
"""
|
||||
No known prefixes.
|
||||
|
||||
>>> testKerning = {
|
||||
... "A" : {
|
||||
... "A" : 1,
|
||||
... "B" : 2,
|
||||
... "CGroup" : 3,
|
||||
... "DGroup" : 4
|
||||
... },
|
||||
... "BGroup" : {
|
||||
... "A" : 5,
|
||||
... "B" : 6,
|
||||
... "CGroup" : 7,
|
||||
... "DGroup" : 8
|
||||
... },
|
||||
... "CGroup" : {
|
||||
... "A" : 9,
|
||||
... "B" : 10,
|
||||
... "CGroup" : 11,
|
||||
... "DGroup" : 12
|
||||
... },
|
||||
... }
|
||||
>>> testGroups = {
|
||||
... "BGroup" : ["B"],
|
||||
... "CGroup" : ["C"],
|
||||
... "DGroup" : ["D"],
|
||||
... }
|
||||
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
|
||||
... testKerning, testGroups, [])
|
||||
>>> expected = {
|
||||
... "A" : {
|
||||
... "A": 1,
|
||||
... "B": 2,
|
||||
... "public.kern2.CGroup": 3,
|
||||
... "public.kern2.DGroup": 4
|
||||
... },
|
||||
... "public.kern1.BGroup": {
|
||||
... "A": 5,
|
||||
... "B": 6,
|
||||
... "public.kern2.CGroup": 7,
|
||||
... "public.kern2.DGroup": 8
|
||||
... },
|
||||
... "public.kern1.CGroup": {
|
||||
... "A": 9,
|
||||
... "B": 10,
|
||||
... "public.kern2.CGroup": 11,
|
||||
... "public.kern2.DGroup": 12
|
||||
... }
|
||||
... }
|
||||
>>> kerning == expected
|
||||
True
|
||||
>>> expected = {
|
||||
... "BGroup": ["B"],
|
||||
... "CGroup": ["C"],
|
||||
... "DGroup": ["D"],
|
||||
... "public.kern1.BGroup": ["B"],
|
||||
... "public.kern1.CGroup": ["C"],
|
||||
... "public.kern2.CGroup": ["C"],
|
||||
... "public.kern2.DGroup": ["D"],
|
||||
... }
|
||||
>>> groups == expected
|
||||
True
|
||||
|
||||
Known prefixes.
|
||||
|
||||
>>> testKerning = {
|
||||
... "A" : {
|
||||
... "A" : 1,
|
||||
... "B" : 2,
|
||||
... "@MMK_R_CGroup" : 3,
|
||||
... "@MMK_R_DGroup" : 4
|
||||
... },
|
||||
... "@MMK_L_BGroup" : {
|
||||
... "A" : 5,
|
||||
... "B" : 6,
|
||||
... "@MMK_R_CGroup" : 7,
|
||||
... "@MMK_R_DGroup" : 8
|
||||
... },
|
||||
... "@MMK_L_CGroup" : {
|
||||
... "A" : 9,
|
||||
... "B" : 10,
|
||||
... "@MMK_R_CGroup" : 11,
|
||||
... "@MMK_R_DGroup" : 12
|
||||
... },
|
||||
... }
|
||||
>>> testGroups = {
|
||||
... "@MMK_L_BGroup" : ["B"],
|
||||
... "@MMK_L_CGroup" : ["C"],
|
||||
... "@MMK_L_XGroup" : ["X"],
|
||||
... "@MMK_R_CGroup" : ["C"],
|
||||
... "@MMK_R_DGroup" : ["D"],
|
||||
... "@MMK_R_XGroup" : ["X"],
|
||||
... }
|
||||
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
|
||||
... testKerning, testGroups, [])
|
||||
>>> expected = {
|
||||
... "A" : {
|
||||
... "A": 1,
|
||||
... "B": 2,
|
||||
... "public.kern2.CGroup": 3,
|
||||
... "public.kern2.DGroup": 4
|
||||
... },
|
||||
... "public.kern1.BGroup": {
|
||||
... "A": 5,
|
||||
... "B": 6,
|
||||
... "public.kern2.CGroup": 7,
|
||||
... "public.kern2.DGroup": 8
|
||||
... },
|
||||
... "public.kern1.CGroup": {
|
||||
... "A": 9,
|
||||
... "B": 10,
|
||||
... "public.kern2.CGroup": 11,
|
||||
... "public.kern2.DGroup": 12
|
||||
... }
|
||||
... }
|
||||
>>> kerning == expected
|
||||
True
|
||||
>>> expected = {
|
||||
... "@MMK_L_BGroup": ["B"],
|
||||
... "@MMK_L_CGroup": ["C"],
|
||||
... "@MMK_L_XGroup": ["X"],
|
||||
... "@MMK_R_CGroup": ["C"],
|
||||
... "@MMK_R_DGroup": ["D"],
|
||||
... "@MMK_R_XGroup": ["X"],
|
||||
... "public.kern1.BGroup": ["B"],
|
||||
... "public.kern1.CGroup": ["C"],
|
||||
... "public.kern1.XGroup": ["X"],
|
||||
... "public.kern2.CGroup": ["C"],
|
||||
... "public.kern2.DGroup": ["D"],
|
||||
... "public.kern2.XGroup": ["X"],
|
||||
... }
|
||||
>>> groups == expected
|
||||
True
|
||||
|
||||
>>> from .validators import kerningValidator
|
||||
>>> kerningValidator(kerning)
|
||||
(True, None)
|
||||
|
||||
Mixture of known prefixes and groups without prefixes.
|
||||
|
||||
>>> testKerning = {
|
||||
... "A" : {
|
||||
... "A" : 1,
|
||||
... "B" : 2,
|
||||
... "@MMK_R_CGroup" : 3,
|
||||
... "DGroup" : 4
|
||||
... },
|
||||
... "BGroup" : {
|
||||
... "A" : 5,
|
||||
... "B" : 6,
|
||||
... "@MMK_R_CGroup" : 7,
|
||||
... "DGroup" : 8
|
||||
... },
|
||||
... "@MMK_L_CGroup" : {
|
||||
... "A" : 9,
|
||||
... "B" : 10,
|
||||
... "@MMK_R_CGroup" : 11,
|
||||
... "DGroup" : 12
|
||||
... },
|
||||
... }
|
||||
>>> testGroups = {
|
||||
... "BGroup" : ["B"],
|
||||
... "@MMK_L_CGroup" : ["C"],
|
||||
... "@MMK_R_CGroup" : ["C"],
|
||||
... "DGroup" : ["D"],
|
||||
... }
|
||||
>>> kerning, groups, maps = convertUFO1OrUFO2KerningToUFO3Kerning(
|
||||
... testKerning, testGroups, [])
|
||||
>>> expected = {
|
||||
... "A" : {
|
||||
... "A": 1,
|
||||
... "B": 2,
|
||||
... "public.kern2.CGroup": 3,
|
||||
... "public.kern2.DGroup": 4
|
||||
... },
|
||||
... "public.kern1.BGroup": {
|
||||
... "A": 5,
|
||||
... "B": 6,
|
||||
... "public.kern2.CGroup": 7,
|
||||
... "public.kern2.DGroup": 8
|
||||
... },
|
||||
... "public.kern1.CGroup": {
|
||||
... "A": 9,
|
||||
... "B": 10,
|
||||
... "public.kern2.CGroup": 11,
|
||||
... "public.kern2.DGroup": 12
|
||||
... }
|
||||
... }
|
||||
>>> kerning == expected
|
||||
True
|
||||
>>> expected = {
|
||||
... "BGroup": ["B"],
|
||||
... "@MMK_L_CGroup": ["C"],
|
||||
... "@MMK_R_CGroup": ["C"],
|
||||
... "DGroup": ["D"],
|
||||
... "public.kern1.BGroup": ["B"],
|
||||
... "public.kern1.CGroup": ["C"],
|
||||
... "public.kern2.CGroup": ["C"],
|
||||
... "public.kern2.DGroup": ["D"],
|
||||
... }
|
||||
>>> groups == expected
|
||||
True
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class UFOLibError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedUFOFormat(UFOLibError):
|
||||
pass
|
||||
|
||||
|
||||
class GlifLibError(UFOLibError):
|
||||
def _add_note(self, note: str) -> None:
|
||||
# Loose backport of PEP 678 until we only support Python 3.11+, used for
|
||||
# adding additional context to errors.
|
||||
# TODO: Replace with https://docs.python.org/3.11/library/exceptions.html#BaseException.add_note
|
||||
(message, *rest) = self.args
|
||||
self.args = ((message + "\n" + note), *rest)
|
||||
|
||||
|
||||
class UnsupportedGLIFFormat(GlifLibError):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
"""DEPRECATED - This module is kept here only as a backward compatibility shim
|
||||
for the old ufoLib.etree module, which was moved to fontTools.misc.etree.
|
||||
Please use the latter instead.
|
||||
"""
|
||||
|
||||
from fontTools.misc.etree import *
|
||||
@@ -0,0 +1,291 @@
|
||||
"""
|
||||
User name to file name conversion.
|
||||
This was taken from the UFO 3 spec.
|
||||
"""
|
||||
|
||||
# Restrictions are taken mostly from
|
||||
# https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file#naming-conventions.
|
||||
#
|
||||
# 1. Integer value zero, sometimes referred to as the ASCII NUL character.
|
||||
# 2. Characters whose integer representations are in the range 1 to 31,
|
||||
# inclusive.
|
||||
# 3. Various characters that (mostly) Windows and POSIX-y filesystems don't
|
||||
# allow, plus "(" and ")", as per the specification.
|
||||
illegalCharacters = {
|
||||
"\x00",
|
||||
"\x01",
|
||||
"\x02",
|
||||
"\x03",
|
||||
"\x04",
|
||||
"\x05",
|
||||
"\x06",
|
||||
"\x07",
|
||||
"\x08",
|
||||
"\t",
|
||||
"\n",
|
||||
"\x0b",
|
||||
"\x0c",
|
||||
"\r",
|
||||
"\x0e",
|
||||
"\x0f",
|
||||
"\x10",
|
||||
"\x11",
|
||||
"\x12",
|
||||
"\x13",
|
||||
"\x14",
|
||||
"\x15",
|
||||
"\x16",
|
||||
"\x17",
|
||||
"\x18",
|
||||
"\x19",
|
||||
"\x1a",
|
||||
"\x1b",
|
||||
"\x1c",
|
||||
"\x1d",
|
||||
"\x1e",
|
||||
"\x1f",
|
||||
'"',
|
||||
"*",
|
||||
"+",
|
||||
"/",
|
||||
":",
|
||||
"<",
|
||||
">",
|
||||
"?",
|
||||
"[",
|
||||
"\\",
|
||||
"]",
|
||||
"(",
|
||||
")",
|
||||
"|",
|
||||
"\x7f",
|
||||
}
|
||||
reservedFileNames = {
|
||||
"aux",
|
||||
"clock$",
|
||||
"com1",
|
||||
"com2",
|
||||
"com3",
|
||||
"com4",
|
||||
"com5",
|
||||
"com6",
|
||||
"com7",
|
||||
"com8",
|
||||
"com9",
|
||||
"con",
|
||||
"lpt1",
|
||||
"lpt2",
|
||||
"lpt3",
|
||||
"lpt4",
|
||||
"lpt5",
|
||||
"lpt6",
|
||||
"lpt7",
|
||||
"lpt8",
|
||||
"lpt9",
|
||||
"nul",
|
||||
"prn",
|
||||
}
|
||||
maxFileNameLength = 255
|
||||
|
||||
|
||||
class NameTranslationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def userNameToFileName(userName: str, existing=(), prefix="", suffix=""):
|
||||
"""
|
||||
`existing` should be a set-like object.
|
||||
|
||||
>>> 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
|
||||
"""
|
||||
# the incoming name must be a string
|
||||
if not isinstance(userName, str):
|
||||
raise ValueError("The value for userName must be a string.")
|
||||
# establish the prefix and suffix lengths
|
||||
prefixLength = len(prefix)
|
||||
suffixLength = len(suffix)
|
||||
# replace an initial period with an _
|
||||
# if no prefix is to be added
|
||||
if not prefix and userName[0] == ".":
|
||||
userName = "_" + userName[1:]
|
||||
# filter the user name
|
||||
filteredUserName = []
|
||||
for character in userName:
|
||||
# replace illegal characters with _
|
||||
if character in illegalCharacters:
|
||||
character = "_"
|
||||
# add _ to all non-lower characters
|
||||
elif character != character.lower():
|
||||
character += "_"
|
||||
filteredUserName.append(character)
|
||||
userName = "".join(filteredUserName)
|
||||
# clip to 255
|
||||
sliceLength = maxFileNameLength - prefixLength - suffixLength
|
||||
userName = userName[:sliceLength]
|
||||
# test for illegal files names
|
||||
parts = []
|
||||
for part in userName.split("."):
|
||||
if part.lower() in reservedFileNames:
|
||||
part = "_" + part
|
||||
parts.append(part)
|
||||
userName = ".".join(parts)
|
||||
# test for clash
|
||||
fullName = prefix + userName + suffix
|
||||
if fullName.lower() in existing:
|
||||
fullName = handleClash1(userName, existing, prefix, suffix)
|
||||
# finished
|
||||
return fullName
|
||||
|
||||
|
||||
def handleClash1(userName, existing=[], prefix="", suffix=""):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
# if the prefix length + user name length + suffix length + 15 is at
|
||||
# or past the maximum length, silce 15 characters off of the user name
|
||||
prefixLength = len(prefix)
|
||||
suffixLength = len(suffix)
|
||||
if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
|
||||
l = prefixLength + len(userName) + suffixLength + 15
|
||||
sliceLength = maxFileNameLength - l
|
||||
userName = userName[:sliceLength]
|
||||
finalName = None
|
||||
# try to add numbers to create a unique name
|
||||
counter = 1
|
||||
while finalName is None:
|
||||
name = userName + str(counter).zfill(15)
|
||||
fullName = prefix + name + suffix
|
||||
if fullName.lower() not in existing:
|
||||
finalName = fullName
|
||||
break
|
||||
else:
|
||||
counter += 1
|
||||
if counter >= 999999999999999:
|
||||
break
|
||||
# if there is a clash, go to the next fallback
|
||||
if finalName is None:
|
||||
finalName = handleClash2(existing, prefix, suffix)
|
||||
# finished
|
||||
return finalName
|
||||
|
||||
|
||||
def handleClash2(existing=[], prefix="", suffix=""):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
# calculate the longest possible string
|
||||
maxLength = maxFileNameLength - len(prefix) - len(suffix)
|
||||
maxValue = int("9" * maxLength)
|
||||
# try to find a number
|
||||
finalName = None
|
||||
counter = 1
|
||||
while finalName is None:
|
||||
fullName = prefix + str(counter) + suffix
|
||||
if fullName.lower() not in existing:
|
||||
finalName = fullName
|
||||
break
|
||||
else:
|
||||
counter += 1
|
||||
if counter >= maxValue:
|
||||
break
|
||||
# raise an error if nothing has been found
|
||||
if finalName is None:
|
||||
raise NameTranslationError("No unique name could be found.")
|
||||
# finished
|
||||
return finalName
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
def lookupKerningValue(
|
||||
pair, kerning, groups, fallback=0, glyphToFirstGroup=None, glyphToSecondGroup=None
|
||||
):
|
||||
"""
|
||||
Note: This expects kerning to be a flat dictionary
|
||||
of kerning pairs, not the nested structure used
|
||||
in kerning.plist.
|
||||
|
||||
>>> groups = {
|
||||
... "public.kern1.O" : ["O", "D", "Q"],
|
||||
... "public.kern2.E" : ["E", "F"]
|
||||
... }
|
||||
>>> kerning = {
|
||||
... ("public.kern1.O", "public.kern2.E") : -100,
|
||||
... ("public.kern1.O", "F") : -200,
|
||||
... ("D", "F") : -300
|
||||
... }
|
||||
>>> lookupKerningValue(("D", "F"), kerning, groups)
|
||||
-300
|
||||
>>> lookupKerningValue(("O", "F"), kerning, groups)
|
||||
-200
|
||||
>>> lookupKerningValue(("O", "E"), kerning, groups)
|
||||
-100
|
||||
>>> lookupKerningValue(("O", "O"), kerning, groups)
|
||||
0
|
||||
>>> lookupKerningValue(("E", "E"), kerning, groups)
|
||||
0
|
||||
>>> lookupKerningValue(("E", "O"), kerning, groups)
|
||||
0
|
||||
>>> lookupKerningValue(("X", "X"), kerning, groups)
|
||||
0
|
||||
>>> lookupKerningValue(("public.kern1.O", "public.kern2.E"),
|
||||
... kerning, groups)
|
||||
-100
|
||||
>>> lookupKerningValue(("public.kern1.O", "F"), kerning, groups)
|
||||
-200
|
||||
>>> lookupKerningValue(("O", "public.kern2.E"), kerning, groups)
|
||||
-100
|
||||
>>> lookupKerningValue(("public.kern1.X", "public.kern2.X"), kerning, groups)
|
||||
0
|
||||
"""
|
||||
# quickly check to see if the pair is in the kerning dictionary
|
||||
if pair in kerning:
|
||||
return kerning[pair]
|
||||
# create glyph to group mapping
|
||||
if glyphToFirstGroup is not None:
|
||||
assert glyphToSecondGroup is not None
|
||||
if glyphToSecondGroup is not None:
|
||||
assert glyphToFirstGroup is not None
|
||||
if glyphToFirstGroup is None:
|
||||
glyphToFirstGroup = {}
|
||||
glyphToSecondGroup = {}
|
||||
for group, groupMembers in groups.items():
|
||||
if group.startswith("public.kern1."):
|
||||
for glyph in groupMembers:
|
||||
glyphToFirstGroup[glyph] = group
|
||||
elif group.startswith("public.kern2."):
|
||||
for glyph in groupMembers:
|
||||
glyphToSecondGroup[glyph] = group
|
||||
# get group names and make sure first and second are glyph names
|
||||
first, second = pair
|
||||
firstGroup = secondGroup = None
|
||||
if first.startswith("public.kern1."):
|
||||
firstGroup = first
|
||||
first = None
|
||||
else:
|
||||
firstGroup = glyphToFirstGroup.get(first)
|
||||
if second.startswith("public.kern2."):
|
||||
secondGroup = second
|
||||
second = None
|
||||
else:
|
||||
secondGroup = glyphToSecondGroup.get(second)
|
||||
# make an ordered list of pairs to look up
|
||||
pairs = [
|
||||
(first, second),
|
||||
(first, secondGroup),
|
||||
(firstGroup, second),
|
||||
(firstGroup, secondGroup),
|
||||
]
|
||||
# look up the pairs and return any matches
|
||||
for pair in pairs:
|
||||
if pair in kerning:
|
||||
return kerning[pair]
|
||||
# use the fallback value
|
||||
return fallback
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,47 @@
|
||||
"""DEPRECATED - This module is kept here only as a backward compatibility shim
|
||||
for the old `ufoLib.plistlib` module, which was moved to :class:`fontTools.misc.plistlib`.
|
||||
Please use the latter instead.
|
||||
"""
|
||||
|
||||
from fontTools.misc.plistlib import dump, dumps, load, loads
|
||||
from fontTools.misc.textTools import tobytes
|
||||
|
||||
# The following functions were part of the old py2-like ufoLib.plistlib API.
|
||||
# They are kept only for backward compatiblity.
|
||||
from fontTools.ufoLib.utils import deprecated
|
||||
|
||||
|
||||
@deprecated("Use 'fontTools.misc.plistlib.load' instead")
|
||||
def readPlist(path_or_file):
|
||||
did_open = False
|
||||
if isinstance(path_or_file, str):
|
||||
path_or_file = open(path_or_file, "rb")
|
||||
did_open = True
|
||||
try:
|
||||
return load(path_or_file, use_builtin_types=False)
|
||||
finally:
|
||||
if did_open:
|
||||
path_or_file.close()
|
||||
|
||||
|
||||
@deprecated("Use 'fontTools.misc.plistlib.dump' instead")
|
||||
def writePlist(value, path_or_file):
|
||||
did_open = False
|
||||
if isinstance(path_or_file, str):
|
||||
path_or_file = open(path_or_file, "wb")
|
||||
did_open = True
|
||||
try:
|
||||
dump(value, path_or_file, use_builtin_types=False)
|
||||
finally:
|
||||
if did_open:
|
||||
path_or_file.close()
|
||||
|
||||
|
||||
@deprecated("Use 'fontTools.misc.plistlib.loads' instead")
|
||||
def readPlistFromString(data):
|
||||
return loads(tobytes(data, encoding="utf-8"), use_builtin_types=False)
|
||||
|
||||
|
||||
@deprecated("Use 'fontTools.misc.plistlib.dumps' instead")
|
||||
def writePlistToString(value):
|
||||
return dumps(value, use_builtin_types=False)
|
||||
@@ -0,0 +1,6 @@
|
||||
"""DEPRECATED - This module is kept here only as a backward compatibility shim
|
||||
for the old `ufoLib.pointPen` module, which was moved to :class:`fontTools.pens.pointPen`.
|
||||
Please use the latter instead.
|
||||
"""
|
||||
|
||||
from fontTools.pens.pointPen import *
|
||||
@@ -0,0 +1,76 @@
|
||||
"""The module contains miscellaneous helpers.
|
||||
It's not considered part of the public ufoLib API.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
import functools
|
||||
|
||||
|
||||
numberTypes = (int, float)
|
||||
|
||||
|
||||
def deprecated(msg=""):
|
||||
"""Decorator factory to mark functions as deprecated with given message.
|
||||
|
||||
>>> @deprecated("Enough!")
|
||||
... def some_function():
|
||||
... "I just print 'hello world'."
|
||||
... print("hello world")
|
||||
>>> some_function()
|
||||
hello world
|
||||
>>> some_function.__doc__ == "I just print 'hello world'."
|
||||
True
|
||||
"""
|
||||
|
||||
def deprecated_decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warnings.warn(
|
||||
f"{func.__name__} function is a deprecated. {msg}",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return deprecated_decorator
|
||||
|
||||
|
||||
# To be mixed with enum.Enum in UFOFormatVersion and GLIFFormatVersion
|
||||
class _VersionTupleEnumMixin:
|
||||
@property
|
||||
def major(self):
|
||||
return self.value[0]
|
||||
|
||||
@property
|
||||
def minor(self):
|
||||
return self.value[1]
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
# allow to initialize a version enum from a single (major) integer
|
||||
if isinstance(value, int):
|
||||
return cls((value, 0))
|
||||
# or from None to obtain the current default version
|
||||
if value is None:
|
||||
return cls.default()
|
||||
return super()._missing_(value)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.major}.{self.minor}"
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
# get the latest defined version (i.e. the max of all versions)
|
||||
return max(cls.__members__.values())
|
||||
|
||||
@classmethod
|
||||
def supported_versions(cls):
|
||||
return frozenset(cls.__members__.values())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user