asd
This commit is contained in:
2477
venv/lib/python3.12/site-packages/fontTools/ufoLib/__init__.py
Normal file
2477
venv/lib/python3.12/site-packages/fontTools/ufoLib/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
334
venv/lib/python3.12/site-packages/fontTools/ufoLib/converters.py
Normal file
334
venv/lib/python3.12/site-packages/fontTools/ufoLib/converters.py
Normal file
@ -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()
|
||||
22
venv/lib/python3.12/site-packages/fontTools/ufoLib/errors.py
Normal file
22
venv/lib/python3.12/site-packages/fontTools/ufoLib/errors.py
Normal file
@ -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 *
|
||||
291
venv/lib/python3.12/site-packages/fontTools/ufoLib/filenames.py
Normal file
291
venv/lib/python3.12/site-packages/fontTools/ufoLib/filenames.py
Normal file
@ -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()
|
||||
2022
venv/lib/python3.12/site-packages/fontTools/ufoLib/glifLib.py
Normal file
2022
venv/lib/python3.12/site-packages/fontTools/ufoLib/glifLib.py
Normal file
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 *
|
||||
76
venv/lib/python3.12/site-packages/fontTools/ufoLib/utils.py
Normal file
76
venv/lib/python3.12/site-packages/fontTools/ufoLib/utils.py
Normal file
@ -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()
|
||||
1186
venv/lib/python3.12/site-packages/fontTools/ufoLib/validators.py
Normal file
1186
venv/lib/python3.12/site-packages/fontTools/ufoLib/validators.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user