up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
2472
venv/lib/python3.13/site-packages/fontTools/ufoLib/__init__.py
Normal file
2472
venv/lib/python3.13/site-packages/fontTools/ufoLib/__init__.py
Normal file
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.
398
venv/lib/python3.13/site-packages/fontTools/ufoLib/converters.py
Normal file
398
venv/lib/python3.13/site-packages/fontTools/ufoLib/converters.py
Normal file
|
@ -0,0 +1,398 @@
|
|||
"""
|
||||
Functions for converting UFO1 or UFO2 files into UFO3 format.
|
||||
|
||||
Currently provides functionality for converting kerning rules
|
||||
and kerning groups. Conversion is only supported _from_ UFO1
|
||||
or UFO2, and _to_ UFO3.
|
||||
"""
|
||||
|
||||
# adapted from the UFO spec
|
||||
|
||||
|
||||
def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
|
||||
"""Convert kerning data in UFO1 or UFO2 syntax into UFO3 syntax.
|
||||
|
||||
Args:
|
||||
kerning:
|
||||
A dictionary containing the kerning rules defined in
|
||||
the UFO font, as used in :class:`.UFOReader` objects.
|
||||
groups:
|
||||
A dictionary containing the groups defined in the UFO
|
||||
font, as used in :class:`.UFOReader` objects.
|
||||
glyphSet:
|
||||
Optional; a set of glyph objects to skip (default: None).
|
||||
|
||||
Returns:
|
||||
1. A dictionary representing the converted kerning data.
|
||||
2. A copy of the groups dictionary, with all groups renamed to UFO3 syntax.
|
||||
3. A dictionary containing the mapping of old group names to new group names.
|
||||
|
||||
"""
|
||||
# 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):
|
||||
"""Find all kerning groups in a UFO1 or UFO2 font that use known prefixes.
|
||||
|
||||
In some cases, not all kerning groups will be referenced
|
||||
by the kerning pairs in a UFO. The algorithm for locating
|
||||
groups in :func:`convertUFO1OrUFO2KerningToUFO3Kerning` will
|
||||
miss these unreferenced groups. By scanning for known prefixes,
|
||||
this function will catch all of the prefixed groups.
|
||||
|
||||
The prefixes and sides by this function are:
|
||||
|
||||
@MMK_L_ - side 1
|
||||
@MMK_R_ - side 2
|
||||
|
||||
as defined in the UFO1 specification.
|
||||
|
||||
Args:
|
||||
groups:
|
||||
A dictionary containing the groups defined in the UFO
|
||||
font, as read by :class:`.UFOReader`.
|
||||
|
||||
Returns:
|
||||
Two sets; the first containing the names of all
|
||||
first-side kerning groups identified in the ``groups``
|
||||
dictionary, and the second containing the names of all
|
||||
second-side kerning groups identified.
|
||||
|
||||
"First-side" and "second-side" are with respect to the
|
||||
writing direction of the script.
|
||||
|
||||
Example::
|
||||
|
||||
>>> 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):
|
||||
"""Make a kerning group name that will be unique within the set of group names.
|
||||
|
||||
If the requested kerning group name already exists within the set, this
|
||||
will return a new name by adding an incremented counter to the end
|
||||
of the requested name.
|
||||
|
||||
Args:
|
||||
name:
|
||||
The requested kerning group name.
|
||||
groupNames:
|
||||
A list of the existing kerning group names.
|
||||
counter:
|
||||
Optional; a counter of group names already seen (default: 0). If
|
||||
:attr:`.counter` is not provided, the function will recurse,
|
||||
incrementing the value of :attr:`.counter` until it finds the
|
||||
first unused ``name+counter`` combination, and return that result.
|
||||
|
||||
Returns:
|
||||
A unique kerning group name composed of the requested name suffixed
|
||||
by the smallest available integer counter.
|
||||
"""
|
||||
# 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():
|
||||
"""
|
||||
Tests for :func:`.convertUFO1OrUFO2KerningToUFO3Kerning`.
|
||||
|
||||
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()
|
30
venv/lib/python3.13/site-packages/fontTools/ufoLib/errors.py
Normal file
30
venv/lib/python3.13/site-packages/fontTools/ufoLib/errors.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
class UFOLibError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedUFOFormat(UFOLibError):
|
||||
pass
|
||||
|
||||
|
||||
class GlifLibError(UFOLibError):
|
||||
"""An error raised by glifLib.
|
||||
|
||||
This class is a loose backport of PEP 678, adding a :attr:`.note`
|
||||
attribute that can hold additional context for errors encountered.
|
||||
|
||||
It will be maintained until only Python 3.11-and-later are supported.
|
||||
"""
|
||||
|
||||
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 :mod:`fontTools.misc.etree`.
|
||||
Please use the latter instead.
|
||||
"""
|
||||
|
||||
from fontTools.misc.etree import *
|
346
venv/lib/python3.13/site-packages/fontTools/ufoLib/filenames.py
Normal file
346
venv/lib/python3.13/site-packages/fontTools/ufoLib/filenames.py
Normal file
|
@ -0,0 +1,346 @@
|
|||
"""
|
||||
Convert user-provided internal UFO names to spec-compliant filenames.
|
||||
|
||||
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 <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
|
||||
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 <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
|
||||
by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
|
||||
|
||||
- Erik van Blokland
|
||||
- Tal Leming
|
||||
- Just van Rossum
|
||||
"""
|
||||
|
||||
# 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=""):
|
||||
"""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.
|
||||
|
||||
Args:
|
||||
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
|
||||
"""
|
||||
# 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=""):
|
||||
"""A helper function that resolves collisions with existing names when choosing a filename.
|
||||
|
||||
This function attempts to append an unused integer counter to the filename.
|
||||
|
||||
Args:
|
||||
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.
|
||||
|
||||
>>> 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=""):
|
||||
"""A helper function that resolves collisions with existing names when choosing a filename.
|
||||
|
||||
This function is a fallback to :func:`handleClash1`. It attempts to append an unused integer counter to the filename.
|
||||
|
||||
Args:
|
||||
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::
|
||||
|
||||
>>> 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()
|
2024
venv/lib/python3.13/site-packages/fontTools/ufoLib/glifLib.py
Normal file
2024
venv/lib/python3.13/site-packages/fontTools/ufoLib/glifLib.py
Normal file
File diff suppressed because it is too large
Load diff
121
venv/lib/python3.13/site-packages/fontTools/ufoLib/kerning.py
Normal file
121
venv/lib/python3.13/site-packages/fontTools/ufoLib/kerning.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
def lookupKerningValue(
|
||||
pair, kerning, groups, fallback=0, glyphToFirstGroup=None, glyphToSecondGroup=None
|
||||
):
|
||||
"""Retrieve the kerning value (if any) between a pair of elements.
|
||||
|
||||
The elments can be either individual glyphs (by name) or kerning
|
||||
groups (by name), or any combination of the two.
|
||||
|
||||
Args:
|
||||
pair:
|
||||
A tuple, in logical order (first, second) with respect
|
||||
to the reading direction, to query the font for kerning
|
||||
information on. Each element in the tuple can be either
|
||||
a glyph name or a kerning group name.
|
||||
kerning:
|
||||
A dictionary of kerning pairs.
|
||||
groups:
|
||||
A set of kerning groups.
|
||||
fallback:
|
||||
The fallback value to return if no kern is found between
|
||||
the elements in ``pair``. Defaults to 0.
|
||||
glyphToFirstGroup:
|
||||
A dictionary mapping glyph names to the first-glyph kerning
|
||||
groups to which they belong. Defaults to ``None``.
|
||||
glyphToSecondGroup:
|
||||
A dictionary mapping glyph names to the second-glyph kerning
|
||||
groups to which they belong. Defaults to ``None``.
|
||||
|
||||
Returns:
|
||||
The kerning value between the element pair. If no kerning for
|
||||
the pair is found, the fallback value is returned.
|
||||
|
||||
Note: This function expects the ``kerning`` argument to be a flat
|
||||
dictionary of kerning pairs, not the nested structure used in a
|
||||
kerning.plist file.
|
||||
|
||||
Examples::
|
||||
|
||||
>>> 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 *
|
79
venv/lib/python3.13/site-packages/fontTools/ufoLib/utils.py
Normal file
79
venv/lib/python3.13/site-packages/fontTools/ufoLib/utils.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
"""This module contains miscellaneous helpers.
|
||||
|
||||
It is not considered part of the public ufoLib API. It does, however,
|
||||
define the :py:obj:`.deprecated` decorator that is used elsewhere in
|
||||
the module.
|
||||
"""
|
||||
|
||||
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()
|
1184
venv/lib/python3.13/site-packages/fontTools/ufoLib/validators.py
Normal file
1184
venv/lib/python3.13/site-packages/fontTools/ufoLib/validators.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue