ajout fichiers manquant
This commit is contained in:
1
venv/Lib/site-packages/fontTools/misc/__init__.py
Normal file
1
venv/Lib/site-packages/fontTools/misc/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Empty __init__.py file to signal Python this directory is a package."""
|
||||
46
venv/Lib/site-packages/fontTools/misc/cliTools.py
Normal file
46
venv/Lib/site-packages/fontTools/misc/cliTools.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Collection of utilities for command-line interfaces and console scripts."""
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
numberAddedRE = re.compile(r"#\d+$")
|
||||
|
||||
|
||||
def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False):
|
||||
"""Generates a suitable file name for writing output.
|
||||
|
||||
Often tools will want to take a file, do some kind of transformation to it,
|
||||
and write it out again. This function determines an appropriate name for the
|
||||
output file, through one or more of the following steps:
|
||||
|
||||
- changing the output directory
|
||||
- replacing the file extension
|
||||
- suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
|
||||
overwriting an existing file.
|
||||
|
||||
Args:
|
||||
input: Name of input file.
|
||||
outputDir: Optionally, a new directory to write the file into.
|
||||
extension: Optionally, a replacement for the current file extension.
|
||||
overWrite: Overwriting an existing file is permitted if true; if false
|
||||
and the proposed filename exists, a new name will be generated by
|
||||
adding an appropriate number suffix.
|
||||
|
||||
Returns:
|
||||
str: Suitable output filename
|
||||
"""
|
||||
dirName, fileName = os.path.split(input)
|
||||
fileName, ext = os.path.splitext(fileName)
|
||||
if outputDir:
|
||||
dirName = outputDir
|
||||
fileName = numberAddedRE.split(fileName)[0]
|
||||
if extension is None:
|
||||
extension = os.path.splitext(input)[1]
|
||||
output = os.path.join(dirName, fileName + extension)
|
||||
n = 1
|
||||
if not overWrite:
|
||||
while os.path.exists(output):
|
||||
output = os.path.join(
|
||||
dirName, fileName + "#" + repr(n) + extension)
|
||||
n += 1
|
||||
return output
|
||||
25
venv/Lib/site-packages/fontTools/misc/cython.py
Normal file
25
venv/Lib/site-packages/fontTools/misc/cython.py
Normal file
@@ -0,0 +1,25 @@
|
||||
""" Exports a no-op 'cython' namespace similar to
|
||||
https://github.com/cython/cython/blob/master/Cython/Shadow.py
|
||||
|
||||
This allows to optionally compile @cython decorated functions
|
||||
(when cython is available at built time), or run the same code
|
||||
as pure-python, without runtime dependency on cython module.
|
||||
|
||||
We only define the symbols that we use. E.g. see fontTools.cu2qu
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
def _empty_decorator(x):
|
||||
return x
|
||||
|
||||
compiled = False
|
||||
|
||||
for name in ("double", "complex", "int"):
|
||||
globals()[name] = None
|
||||
|
||||
for name in ("cfunc", "inline"):
|
||||
globals()[name] = _empty_decorator
|
||||
|
||||
locals = lambda **_: _empty_decorator
|
||||
returns = lambda _: _empty_decorator
|
||||
66
venv/Lib/site-packages/fontTools/misc/dictTools.py
Normal file
66
venv/Lib/site-packages/fontTools/misc/dictTools.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""Misc dict tools."""
|
||||
|
||||
|
||||
__all__ = ['hashdict']
|
||||
|
||||
# https://stackoverflow.com/questions/1151658/python-hashable-dicts
|
||||
class hashdict(dict):
|
||||
"""
|
||||
hashable dict implementation, suitable for use as a key into
|
||||
other dicts.
|
||||
|
||||
>>> h1 = hashdict({"apples": 1, "bananas":2})
|
||||
>>> h2 = hashdict({"bananas": 3, "mangoes": 5})
|
||||
>>> h1+h2
|
||||
hashdict(apples=1, bananas=3, mangoes=5)
|
||||
>>> d1 = {}
|
||||
>>> d1[h1] = "salad"
|
||||
>>> d1[h1]
|
||||
'salad'
|
||||
>>> d1[h2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: hashdict(bananas=3, mangoes=5)
|
||||
|
||||
based on answers from
|
||||
http://stackoverflow.com/questions/1151658/python-hashable-dicts
|
||||
|
||||
"""
|
||||
def __key(self):
|
||||
return tuple(sorted(self.items()))
|
||||
def __repr__(self):
|
||||
return "{0}({1})".format(self.__class__.__name__,
|
||||
", ".join("{0}={1}".format(
|
||||
str(i[0]),repr(i[1])) for i in self.__key()))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__key())
|
||||
def __setitem__(self, key, value):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
def __delitem__(self, key):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
def clear(self):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
def pop(self, *args, **kwargs):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
def popitem(self, *args, **kwargs):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
def setdefault(self, *args, **kwargs):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
def update(self, *args, **kwargs):
|
||||
raise TypeError("{0} does not support item assignment"
|
||||
.format(self.__class__.__name__))
|
||||
# update is not ok because it mutates the object
|
||||
# __add__ is ok because it creates a new object
|
||||
# while the new object is under construction, it's ok to mutate it
|
||||
def __add__(self, right):
|
||||
result = hashdict(self)
|
||||
dict.update(result, right)
|
||||
return result
|
||||
|
||||
242
venv/Lib/site-packages/fontTools/misc/filenames.py
Normal file
242
venv/Lib/site-packages/fontTools/misc/filenames.py
Normal file
@@ -0,0 +1,242 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
|
||||
illegalCharacters += [chr(i) for i in range(1, 32)]
|
||||
illegalCharacters += [chr(0x7F)]
|
||||
reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ")
|
||||
reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ")
|
||||
maxFileNameLength = 255
|
||||
|
||||
|
||||
class NameTranslationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def userNameToFileName(userName, 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 str
|
||||
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
|
||||
import sys
|
||||
sys.exit(doctest.testmod().failed)
|
||||
25
venv/Lib/site-packages/fontTools/misc/intTools.py
Normal file
25
venv/Lib/site-packages/fontTools/misc/intTools.py
Normal file
@@ -0,0 +1,25 @@
|
||||
__all__ = ["popCount"]
|
||||
|
||||
|
||||
try:
|
||||
bit_count = int.bit_count
|
||||
except AttributeError:
|
||||
|
||||
def bit_count(v):
|
||||
return bin(v).count("1")
|
||||
|
||||
|
||||
"""Return number of 1 bits (population count) of the absolute value of an integer.
|
||||
|
||||
See https://docs.python.org/3.10/library/stdtypes.html#int.bit_count
|
||||
"""
|
||||
popCount = bit_count
|
||||
|
||||
|
||||
def bit_indices(v):
|
||||
"""Return list of indices where bits are set, 0 being the index of the least significant bit.
|
||||
|
||||
>>> bit_indices(0b101)
|
||||
[0, 2]
|
||||
"""
|
||||
return [i for i, b in enumerate(bin(v)[::-1]) if b == "1"]
|
||||
216
venv/Lib/site-packages/fontTools/misc/sstruct.py
Normal file
216
venv/Lib/site-packages/fontTools/misc/sstruct.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""sstruct.py -- SuperStruct
|
||||
|
||||
Higher level layer on top of the struct module, enabling to
|
||||
bind names to struct elements. The interface is similar to
|
||||
struct, except the objects passed and returned are not tuples
|
||||
(or argument lists), but dictionaries or instances.
|
||||
|
||||
Just like struct, we use fmt strings to describe a data
|
||||
structure, except we use one line per element. Lines are
|
||||
separated by newlines or semi-colons. Each line contains
|
||||
either one of the special struct characters ('@', '=', '<',
|
||||
'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f').
|
||||
Repetitions, like the struct module offers them are not useful
|
||||
in this context, except for fixed length strings (eg. 'myInt:5h'
|
||||
is not allowed but 'myString:5s' is). The 'x' fmt character
|
||||
(pad byte) is treated as 'special', since it is by definition
|
||||
anonymous. Extra whitespace is allowed everywhere.
|
||||
|
||||
The sstruct module offers one feature that the "normal" struct
|
||||
module doesn't: support for fixed point numbers. These are spelled
|
||||
as "n.mF", where n is the number of bits before the point, and m
|
||||
the number of bits after the point. Fixed point numbers get
|
||||
converted to floats.
|
||||
|
||||
pack(fmt, object):
|
||||
'object' is either a dictionary or an instance (or actually
|
||||
anything that has a __dict__ attribute). If it is a dictionary,
|
||||
its keys are used for names. If it is an instance, it's
|
||||
attributes are used to grab struct elements from. Returns
|
||||
a string containing the data.
|
||||
|
||||
unpack(fmt, data, object=None)
|
||||
If 'object' is omitted (or None), a new dictionary will be
|
||||
returned. If 'object' is a dictionary, it will be used to add
|
||||
struct elements to. If it is an instance (or in fact anything
|
||||
that has a __dict__ attribute), an attribute will be added for
|
||||
each struct element. In the latter two cases, 'object' itself
|
||||
is returned.
|
||||
|
||||
unpack2(fmt, data, object=None)
|
||||
Convenience function. Same as unpack, except data may be longer
|
||||
than needed. The returned value is a tuple: (object, leftoverdata).
|
||||
|
||||
calcsize(fmt)
|
||||
like struct.calcsize(), but uses our own fmt strings:
|
||||
it returns the size of the data in bytes.
|
||||
"""
|
||||
|
||||
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
|
||||
from fontTools.misc.textTools import tobytes, tostr
|
||||
import struct
|
||||
import re
|
||||
|
||||
__version__ = "1.2"
|
||||
__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
def pack(fmt, obj):
|
||||
formatstring, names, fixes = getformat(fmt, keep_pad_byte=True)
|
||||
elements = []
|
||||
if not isinstance(obj, dict):
|
||||
obj = obj.__dict__
|
||||
for name in names:
|
||||
value = obj[name]
|
||||
if name in fixes:
|
||||
# fixed point conversion
|
||||
value = fl2fi(value, fixes[name])
|
||||
elif isinstance(value, str):
|
||||
value = tobytes(value)
|
||||
elements.append(value)
|
||||
data = struct.pack(*(formatstring,) + tuple(elements))
|
||||
return data
|
||||
|
||||
def unpack(fmt, data, obj=None):
|
||||
if obj is None:
|
||||
obj = {}
|
||||
data = tobytes(data)
|
||||
formatstring, names, fixes = getformat(fmt)
|
||||
if isinstance(obj, dict):
|
||||
d = obj
|
||||
else:
|
||||
d = obj.__dict__
|
||||
elements = struct.unpack(formatstring, data)
|
||||
for i in range(len(names)):
|
||||
name = names[i]
|
||||
value = elements[i]
|
||||
if name in fixes:
|
||||
# fixed point conversion
|
||||
value = fi2fl(value, fixes[name])
|
||||
elif isinstance(value, bytes):
|
||||
try:
|
||||
value = tostr(value)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
d[name] = value
|
||||
return obj
|
||||
|
||||
def unpack2(fmt, data, obj=None):
|
||||
length = calcsize(fmt)
|
||||
return unpack(fmt, data[:length], obj), data[length:]
|
||||
|
||||
def calcsize(fmt):
|
||||
formatstring, names, fixes = getformat(fmt)
|
||||
return struct.calcsize(formatstring)
|
||||
|
||||
|
||||
# matches "name:formatchar" (whitespace is allowed)
|
||||
_elementRE = re.compile(
|
||||
r"\s*" # whitespace
|
||||
r"([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier)
|
||||
r"\s*:\s*" # whitespace : whitespace
|
||||
r"([xcbB?hHiIlLqQfd]|" # formatchar...
|
||||
r"[0-9]+[ps]|" # ...formatchar...
|
||||
r"([0-9]+)\.([0-9]+)(F))" # ...formatchar
|
||||
r"\s*" # whitespace
|
||||
r"(#.*)?$" # [comment] + end of string
|
||||
)
|
||||
|
||||
# matches the special struct fmt chars and 'x' (pad byte)
|
||||
_extraRE = re.compile(r"\s*([x@=<>!])\s*(#.*)?$")
|
||||
|
||||
# matches an "empty" string, possibly containing whitespace and/or a comment
|
||||
_emptyRE = re.compile(r"\s*(#.*)?$")
|
||||
|
||||
_fixedpointmappings = {
|
||||
8: "b",
|
||||
16: "h",
|
||||
32: "l"}
|
||||
|
||||
_formatcache = {}
|
||||
|
||||
def getformat(fmt, keep_pad_byte=False):
|
||||
fmt = tostr(fmt, encoding="ascii")
|
||||
try:
|
||||
formatstring, names, fixes = _formatcache[fmt]
|
||||
except KeyError:
|
||||
lines = re.split("[\n;]", fmt)
|
||||
formatstring = ""
|
||||
names = []
|
||||
fixes = {}
|
||||
for line in lines:
|
||||
if _emptyRE.match(line):
|
||||
continue
|
||||
m = _extraRE.match(line)
|
||||
if m:
|
||||
formatchar = m.group(1)
|
||||
if formatchar != 'x' and formatstring:
|
||||
raise Error("a special fmt char must be first")
|
||||
else:
|
||||
m = _elementRE.match(line)
|
||||
if not m:
|
||||
raise Error("syntax error in fmt: '%s'" % line)
|
||||
name = m.group(1)
|
||||
formatchar = m.group(2)
|
||||
if keep_pad_byte or formatchar != "x":
|
||||
names.append(name)
|
||||
if m.group(3):
|
||||
# fixed point
|
||||
before = int(m.group(3))
|
||||
after = int(m.group(4))
|
||||
bits = before + after
|
||||
if bits not in [8, 16, 32]:
|
||||
raise Error("fixed point must be 8, 16 or 32 bits long")
|
||||
formatchar = _fixedpointmappings[bits]
|
||||
assert m.group(5) == "F"
|
||||
fixes[name] = after
|
||||
formatstring = formatstring + formatchar
|
||||
_formatcache[fmt] = formatstring, names, fixes
|
||||
return formatstring, names, fixes
|
||||
|
||||
def _test():
|
||||
fmt = """
|
||||
# comments are allowed
|
||||
> # big endian (see documentation for struct)
|
||||
# empty lines are allowed:
|
||||
|
||||
ashort: h
|
||||
along: l
|
||||
abyte: b # a byte
|
||||
achar: c
|
||||
astr: 5s
|
||||
afloat: f; adouble: d # multiple "statements" are allowed
|
||||
afixed: 16.16F
|
||||
abool: ?
|
||||
apad: x
|
||||
"""
|
||||
|
||||
print('size:', calcsize(fmt))
|
||||
|
||||
class foo(object):
|
||||
pass
|
||||
|
||||
i = foo()
|
||||
|
||||
i.ashort = 0x7fff
|
||||
i.along = 0x7fffffff
|
||||
i.abyte = 0x7f
|
||||
i.achar = "a"
|
||||
i.astr = "12345"
|
||||
i.afloat = 0.5
|
||||
i.adouble = 0.5
|
||||
i.afixed = 1.5
|
||||
i.abool = True
|
||||
|
||||
data = pack(fmt, i)
|
||||
print('data:', repr(data))
|
||||
print(unpack(fmt, data))
|
||||
i2 = foo()
|
||||
unpack(fmt, data, i2)
|
||||
print(vars(i2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test()
|
||||
192
venv/Lib/site-packages/fontTools/misc/symfont.py
Normal file
192
venv/Lib/site-packages/fontTools/misc/symfont.py
Normal file
@@ -0,0 +1,192 @@
|
||||
from fontTools.pens.basePen import BasePen
|
||||
from functools import partial
|
||||
from itertools import count
|
||||
import sympy as sp
|
||||
import sys
|
||||
|
||||
n = 3 # Max Bezier degree; 3 for cubic, 2 for quadratic
|
||||
|
||||
t, x, y = sp.symbols('t x y', real=True)
|
||||
c = sp.symbols('c', real=False) # Complex representation instead of x/y
|
||||
|
||||
X = tuple(sp.symbols('x:%d'%(n+1), real=True))
|
||||
Y = tuple(sp.symbols('y:%d'%(n+1), real=True))
|
||||
P = tuple(zip(*(sp.symbols('p:%d[%s]'%(n+1,w), real=True) for w in '01')))
|
||||
C = tuple(sp.symbols('c:%d'%(n+1), real=False))
|
||||
|
||||
# Cubic Bernstein basis functions
|
||||
BinomialCoefficient = [(1, 0)]
|
||||
for i in range(1, n+1):
|
||||
last = BinomialCoefficient[-1]
|
||||
this = tuple(last[j-1]+last[j] for j in range(len(last)))+(0,)
|
||||
BinomialCoefficient.append(this)
|
||||
BinomialCoefficient = tuple(tuple(item[:-1]) for item in BinomialCoefficient)
|
||||
del last, this
|
||||
|
||||
BernsteinPolynomial = tuple(
|
||||
tuple(c * t**i * (1-t)**(n-i) for i,c in enumerate(coeffs))
|
||||
for n,coeffs in enumerate(BinomialCoefficient))
|
||||
|
||||
BezierCurve = tuple(
|
||||
tuple(sum(P[i][j]*bernstein for i,bernstein in enumerate(bernsteins))
|
||||
for j in range(2))
|
||||
for n,bernsteins in enumerate(BernsteinPolynomial))
|
||||
BezierCurveC = tuple(
|
||||
sum(C[i]*bernstein for i,bernstein in enumerate(bernsteins))
|
||||
for n,bernsteins in enumerate(BernsteinPolynomial))
|
||||
|
||||
|
||||
def green(f, curveXY):
|
||||
f = -sp.integrate(sp.sympify(f), y)
|
||||
f = f.subs({x:curveXY[0], y:curveXY[1]})
|
||||
f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1))
|
||||
return f
|
||||
|
||||
|
||||
class _BezierFuncsLazy(dict):
|
||||
|
||||
def __init__(self, symfunc):
|
||||
self._symfunc = symfunc
|
||||
self._bezfuncs = {}
|
||||
|
||||
def __missing__(self, i):
|
||||
args = ['p%d'%d for d in range(i+1)]
|
||||
f = green(self._symfunc, BezierCurve[i])
|
||||
f = sp.gcd_terms(f.collect(sum(P,()))) # Optimize
|
||||
return sp.lambdify(args, f)
|
||||
|
||||
class GreenPen(BasePen):
|
||||
|
||||
_BezierFuncs = {}
|
||||
|
||||
@classmethod
|
||||
def _getGreenBezierFuncs(celf, func):
|
||||
funcstr = str(func)
|
||||
if not funcstr in celf._BezierFuncs:
|
||||
celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func)
|
||||
return celf._BezierFuncs[funcstr]
|
||||
|
||||
def __init__(self, func, glyphset=None):
|
||||
BasePen.__init__(self, glyphset)
|
||||
self._funcs = self._getGreenBezierFuncs(func)
|
||||
self.value = 0
|
||||
|
||||
def _moveTo(self, p0):
|
||||
self.__startPoint = p0
|
||||
|
||||
def _closePath(self):
|
||||
p0 = self._getCurrentPoint()
|
||||
if p0 != self.__startPoint:
|
||||
self._lineTo(self.__startPoint)
|
||||
|
||||
def _endPath(self):
|
||||
p0 = self._getCurrentPoint()
|
||||
if p0 != self.__startPoint:
|
||||
# Green theorem is not defined on open contours.
|
||||
raise NotImplementedError
|
||||
|
||||
def _lineTo(self, p1):
|
||||
p0 = self._getCurrentPoint()
|
||||
self.value += self._funcs[1](p0, p1)
|
||||
|
||||
def _qCurveToOne(self, p1, p2):
|
||||
p0 = self._getCurrentPoint()
|
||||
self.value += self._funcs[2](p0, p1, p2)
|
||||
|
||||
def _curveToOne(self, p1, p2, p3):
|
||||
p0 = self._getCurrentPoint()
|
||||
self.value += self._funcs[3](p0, p1, p2, p3)
|
||||
|
||||
# Sample pens.
|
||||
# Do not use this in real code.
|
||||
# Use fontTools.pens.momentsPen.MomentsPen instead.
|
||||
AreaPen = partial(GreenPen, func=1)
|
||||
MomentXPen = partial(GreenPen, func=x)
|
||||
MomentYPen = partial(GreenPen, func=y)
|
||||
MomentXXPen = partial(GreenPen, func=x*x)
|
||||
MomentYYPen = partial(GreenPen, func=y*y)
|
||||
MomentXYPen = partial(GreenPen, func=x*y)
|
||||
|
||||
|
||||
def printGreenPen(penName, funcs, file=sys.stdout):
|
||||
|
||||
print(
|
||||
'''from fontTools.pens.basePen import BasePen
|
||||
|
||||
class %s(BasePen):
|
||||
|
||||
def __init__(self, glyphset=None):
|
||||
BasePen.__init__(self, glyphset)
|
||||
'''%penName, file=file)
|
||||
for name,f in funcs:
|
||||
print(' self.%s = 0' % name, file=file)
|
||||
print('''
|
||||
def _moveTo(self, p0):
|
||||
self.__startPoint = p0
|
||||
|
||||
def _closePath(self):
|
||||
p0 = self._getCurrentPoint()
|
||||
if p0 != self.__startPoint:
|
||||
self._lineTo(self.__startPoint)
|
||||
|
||||
def _endPath(self):
|
||||
p0 = self._getCurrentPoint()
|
||||
if p0 != self.__startPoint:
|
||||
# Green theorem is not defined on open contours.
|
||||
raise NotImplementedError
|
||||
''', end='', file=file)
|
||||
|
||||
for n in (1, 2, 3):
|
||||
|
||||
if n == 1:
|
||||
print('''
|
||||
def _lineTo(self, p1):
|
||||
x0,y0 = self._getCurrentPoint()
|
||||
x1,y1 = p1
|
||||
''', file=file)
|
||||
elif n == 2:
|
||||
print('''
|
||||
def _qCurveToOne(self, p1, p2):
|
||||
x0,y0 = self._getCurrentPoint()
|
||||
x1,y1 = p1
|
||||
x2,y2 = p2
|
||||
''', file=file)
|
||||
elif n == 3:
|
||||
print('''
|
||||
def _curveToOne(self, p1, p2, p3):
|
||||
x0,y0 = self._getCurrentPoint()
|
||||
x1,y1 = p1
|
||||
x2,y2 = p2
|
||||
x3,y3 = p3
|
||||
''', file=file)
|
||||
subs = {P[i][j]: [X, Y][j][i] for i in range(n+1) for j in range(2)}
|
||||
greens = [green(f, BezierCurve[n]) for name,f in funcs]
|
||||
greens = [sp.gcd_terms(f.collect(sum(P,()))) for f in greens] # Optimize
|
||||
greens = [f.subs(subs) for f in greens] # Convert to p to x/y
|
||||
defs, exprs = sp.cse(greens,
|
||||
optimizations='basic',
|
||||
symbols=(sp.Symbol('r%d'%i) for i in count()))
|
||||
for name,value in defs:
|
||||
print(' %s = %s' % (name, value), file=file)
|
||||
print(file=file)
|
||||
for name,value in zip([f[0] for f in funcs], exprs):
|
||||
print(' self.%s += %s' % (name, value), file=file)
|
||||
|
||||
print('''
|
||||
if __name__ == '__main__':
|
||||
from fontTools.misc.symfont import x, y, printGreenPen
|
||||
printGreenPen('%s', ['''%penName, file=file)
|
||||
for name,f in funcs:
|
||||
print(" ('%s', %s)," % (name, str(f)), file=file)
|
||||
print(' ])', file=file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pen = AreaPen()
|
||||
pen.moveTo((100,100))
|
||||
pen.lineTo((100,200))
|
||||
pen.lineTo((200,200))
|
||||
pen.curveTo((200,250),(300,300),(250,350))
|
||||
pen.lineTo((200,100))
|
||||
pen.closePath()
|
||||
print(pen.value)
|
||||
198
venv/Lib/site-packages/fontTools/misc/testTools.py
Normal file
198
venv/Lib/site-packages/fontTools/misc/testTools.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""Helpers for writing unit tests."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
from io import BytesIO
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from unittest import TestCase as _TestCase
|
||||
from fontTools.misc.textTools import tobytes
|
||||
from fontTools.misc.xmlWriter import XMLWriter
|
||||
|
||||
|
||||
def parseXML(xmlSnippet):
|
||||
"""Parses a snippet of XML.
|
||||
|
||||
Input can be either a single string (unicode or UTF-8 bytes), or a
|
||||
a sequence of strings.
|
||||
|
||||
The result is in the same format that would be returned by
|
||||
XMLReader, but the parser imposes no constraints on the root
|
||||
element so it can be called on small snippets of TTX files.
|
||||
"""
|
||||
# To support snippets with multiple elements, we add a fake root.
|
||||
reader = TestXMLReader_()
|
||||
xml = b"<root>"
|
||||
if isinstance(xmlSnippet, bytes):
|
||||
xml += xmlSnippet
|
||||
elif isinstance(xmlSnippet, str):
|
||||
xml += tobytes(xmlSnippet, 'utf-8')
|
||||
elif isinstance(xmlSnippet, Iterable):
|
||||
xml += b"".join(tobytes(s, 'utf-8') for s in xmlSnippet)
|
||||
else:
|
||||
raise TypeError("expected string or sequence of strings; found %r"
|
||||
% type(xmlSnippet).__name__)
|
||||
xml += b"</root>"
|
||||
reader.parser.Parse(xml, 0)
|
||||
return reader.root[2]
|
||||
|
||||
|
||||
def parseXmlInto(font, parseInto, xmlSnippet):
|
||||
parsed_xml = [e for e in parseXML(xmlSnippet.strip()) if not isinstance(e, str)]
|
||||
for name, attrs, content in parsed_xml:
|
||||
parseInto.fromXML(name, attrs, content, font)
|
||||
parseInto.populateDefaults()
|
||||
return parseInto
|
||||
|
||||
|
||||
class FakeFont:
|
||||
def __init__(self, glyphs):
|
||||
self.glyphOrder_ = glyphs
|
||||
self.reverseGlyphOrderDict_ = {g: i for i, g in enumerate(glyphs)}
|
||||
self.lazy = False
|
||||
self.tables = {}
|
||||
|
||||
def __getitem__(self, tag):
|
||||
return self.tables[tag]
|
||||
|
||||
def __setitem__(self, tag, table):
|
||||
self.tables[tag] = table
|
||||
|
||||
def get(self, tag, default=None):
|
||||
return self.tables.get(tag, default)
|
||||
|
||||
def getGlyphID(self, name):
|
||||
return self.reverseGlyphOrderDict_[name]
|
||||
|
||||
def getGlyphIDMany(self, lst):
|
||||
return [self.getGlyphID(gid) for gid in lst]
|
||||
|
||||
def getGlyphName(self, glyphID):
|
||||
if glyphID < len(self.glyphOrder_):
|
||||
return self.glyphOrder_[glyphID]
|
||||
else:
|
||||
return "glyph%.5d" % glyphID
|
||||
def getGlyphNameMany(self, lst):
|
||||
return [self.getGlyphName(gid) for gid in lst]
|
||||
|
||||
def getGlyphOrder(self):
|
||||
return self.glyphOrder_
|
||||
|
||||
def getReverseGlyphMap(self):
|
||||
return self.reverseGlyphOrderDict_
|
||||
|
||||
def getGlyphNames(self):
|
||||
return sorted(self.getGlyphOrder())
|
||||
|
||||
|
||||
class TestXMLReader_(object):
|
||||
def __init__(self):
|
||||
from xml.parsers.expat import ParserCreate
|
||||
self.parser = ParserCreate()
|
||||
self.parser.StartElementHandler = self.startElement_
|
||||
self.parser.EndElementHandler = self.endElement_
|
||||
self.parser.CharacterDataHandler = self.addCharacterData_
|
||||
self.root = None
|
||||
self.stack = []
|
||||
|
||||
def startElement_(self, name, attrs):
|
||||
element = (name, attrs, [])
|
||||
if self.stack:
|
||||
self.stack[-1][2].append(element)
|
||||
else:
|
||||
self.root = element
|
||||
self.stack.append(element)
|
||||
|
||||
def endElement_(self, name):
|
||||
self.stack.pop()
|
||||
|
||||
def addCharacterData_(self, data):
|
||||
self.stack[-1][2].append(data)
|
||||
|
||||
|
||||
def makeXMLWriter(newlinestr='\n'):
|
||||
# don't write OS-specific new lines
|
||||
writer = XMLWriter(BytesIO(), newlinestr=newlinestr)
|
||||
# erase XML declaration
|
||||
writer.file.seek(0)
|
||||
writer.file.truncate()
|
||||
return writer
|
||||
|
||||
|
||||
def getXML(func, ttFont=None):
|
||||
"""Call the passed toXML function and return the written content as a
|
||||
list of lines (unicode strings).
|
||||
Result is stripped of XML declaration and OS-specific newline characters.
|
||||
"""
|
||||
writer = makeXMLWriter()
|
||||
func(writer, ttFont)
|
||||
xml = writer.file.getvalue().decode("utf-8")
|
||||
# toXML methods must always end with a writer.newline()
|
||||
assert xml.endswith("\n")
|
||||
return xml.splitlines()
|
||||
|
||||
|
||||
class MockFont(object):
|
||||
"""A font-like object that automatically adds any looked up glyphname
|
||||
to its glyphOrder."""
|
||||
|
||||
def __init__(self):
|
||||
self._glyphOrder = ['.notdef']
|
||||
|
||||
class AllocatingDict(dict):
|
||||
def __missing__(reverseDict, key):
|
||||
self._glyphOrder.append(key)
|
||||
gid = len(reverseDict)
|
||||
reverseDict[key] = gid
|
||||
return gid
|
||||
self._reverseGlyphOrder = AllocatingDict({'.notdef': 0})
|
||||
self.lazy = False
|
||||
|
||||
def getGlyphID(self, glyph):
|
||||
gid = self._reverseGlyphOrder[glyph]
|
||||
return gid
|
||||
|
||||
def getReverseGlyphMap(self):
|
||||
return self._reverseGlyphOrder
|
||||
|
||||
def getGlyphName(self, gid):
|
||||
return self._glyphOrder[gid]
|
||||
|
||||
def getGlyphOrder(self):
|
||||
return self._glyphOrder
|
||||
|
||||
|
||||
class TestCase(_TestCase):
|
||||
|
||||
def __init__(self, methodName):
|
||||
_TestCase.__init__(self, methodName)
|
||||
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
|
||||
# and fires deprecation warnings if a program uses the old name.
|
||||
if not hasattr(self, "assertRaisesRegex"):
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
|
||||
class DataFilesHandler(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.tempdir = None
|
||||
self.num_tempfiles = 0
|
||||
|
||||
def tearDown(self):
|
||||
if self.tempdir:
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def getpath(self, testfile):
|
||||
folder = os.path.dirname(sys.modules[self.__module__].__file__)
|
||||
return os.path.join(folder, "data", testfile)
|
||||
|
||||
def temp_dir(self):
|
||||
if not self.tempdir:
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
|
||||
def temp_font(self, font_path, file_name):
|
||||
self.temp_dir()
|
||||
temppath = os.path.join(self.tempdir, file_name)
|
||||
shutil.copy2(font_path, temppath)
|
||||
return temppath
|
||||
154
venv/Lib/site-packages/fontTools/misc/textTools.py
Normal file
154
venv/Lib/site-packages/fontTools/misc/textTools.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""fontTools.misc.textTools.py -- miscellaneous routines."""
|
||||
|
||||
|
||||
import ast
|
||||
import string
|
||||
|
||||
|
||||
# alias kept for backward compatibility
|
||||
safeEval = ast.literal_eval
|
||||
|
||||
|
||||
class Tag(str):
|
||||
@staticmethod
|
||||
def transcode(blob):
|
||||
if isinstance(blob, bytes):
|
||||
blob = blob.decode("latin-1")
|
||||
return blob
|
||||
|
||||
def __new__(self, content):
|
||||
return str.__new__(self, self.transcode(content))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __eq__(self, other):
|
||||
return str.__eq__(self, self.transcode(other))
|
||||
|
||||
def __hash__(self):
|
||||
return str.__hash__(self)
|
||||
|
||||
def tobytes(self):
|
||||
return self.encode("latin-1")
|
||||
|
||||
|
||||
def readHex(content):
|
||||
"""Convert a list of hex strings to binary data."""
|
||||
return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, str)))
|
||||
|
||||
|
||||
def deHexStr(hexdata):
|
||||
"""Convert a hex string to binary data."""
|
||||
hexdata = strjoin(hexdata.split())
|
||||
if len(hexdata) % 2:
|
||||
hexdata = hexdata + "0"
|
||||
data = []
|
||||
for i in range(0, len(hexdata), 2):
|
||||
data.append(bytechr(int(hexdata[i:i+2], 16)))
|
||||
return bytesjoin(data)
|
||||
|
||||
|
||||
def hexStr(data):
|
||||
"""Convert binary data to a hex string."""
|
||||
h = string.hexdigits
|
||||
r = ''
|
||||
for c in data:
|
||||
i = byteord(c)
|
||||
r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
|
||||
return r
|
||||
|
||||
|
||||
def num2binary(l, bits=32):
|
||||
items = []
|
||||
binary = ""
|
||||
for i in range(bits):
|
||||
if l & 0x1:
|
||||
binary = "1" + binary
|
||||
else:
|
||||
binary = "0" + binary
|
||||
l = l >> 1
|
||||
if not ((i+1) % 8):
|
||||
items.append(binary)
|
||||
binary = ""
|
||||
if binary:
|
||||
items.append(binary)
|
||||
items.reverse()
|
||||
assert l in (0, -1), "number doesn't fit in number of bits"
|
||||
return ' '.join(items)
|
||||
|
||||
|
||||
def binary2num(bin):
|
||||
bin = strjoin(bin.split())
|
||||
l = 0
|
||||
for digit in bin:
|
||||
l = l << 1
|
||||
if digit != "0":
|
||||
l = l | 0x1
|
||||
return l
|
||||
|
||||
|
||||
def caselessSort(alist):
|
||||
"""Return a sorted copy of a list. If there are only strings
|
||||
in the list, it will not consider case.
|
||||
"""
|
||||
|
||||
try:
|
||||
return sorted(alist, key=lambda a: (a.lower(), a))
|
||||
except TypeError:
|
||||
return sorted(alist)
|
||||
|
||||
|
||||
def pad(data, size):
|
||||
r""" Pad byte string 'data' with null bytes until its length is a
|
||||
multiple of 'size'.
|
||||
|
||||
>>> len(pad(b'abcd', 4))
|
||||
4
|
||||
>>> len(pad(b'abcde', 2))
|
||||
6
|
||||
>>> len(pad(b'abcde', 4))
|
||||
8
|
||||
>>> pad(b'abcdef', 4) == b'abcdef\x00\x00'
|
||||
True
|
||||
"""
|
||||
data = tobytes(data)
|
||||
if size > 1:
|
||||
remainder = len(data) % size
|
||||
if remainder:
|
||||
data += b"\0" * (size - remainder)
|
||||
return data
|
||||
|
||||
|
||||
def tostr(s, encoding="ascii", errors="strict"):
|
||||
if not isinstance(s, str):
|
||||
return s.decode(encoding, errors)
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
def tobytes(s, encoding="ascii", errors="strict"):
|
||||
if isinstance(s, str):
|
||||
return s.encode(encoding, errors)
|
||||
else:
|
||||
return bytes(s)
|
||||
|
||||
|
||||
def bytechr(n):
|
||||
return bytes([n])
|
||||
|
||||
|
||||
def byteord(c):
|
||||
return c if isinstance(c, int) else ord(c)
|
||||
|
||||
|
||||
def strjoin(iterable, joiner=""):
|
||||
return tostr(joiner).join(iterable)
|
||||
|
||||
|
||||
def bytesjoin(iterable, joiner=b""):
|
||||
return tobytes(joiner).join(tobytes(item) for item in iterable)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest, sys
|
||||
sys.exit(doctest.testmod().failed)
|
||||
68
venv/Lib/site-packages/fontTools/misc/timeTools.py
Normal file
68
venv/Lib/site-packages/fontTools/misc/timeTools.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""fontTools.misc.timeTools.py -- tools for working with OpenType timestamps.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
import calendar
|
||||
|
||||
|
||||
epoch_diff = calendar.timegm((1904, 1, 1, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
DAYNAMES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
|
||||
|
||||
def asctime(t=None):
|
||||
"""
|
||||
Convert a tuple or struct_time representing a time as returned by gmtime()
|
||||
or localtime() to a 24-character string of the following form:
|
||||
|
||||
>>> asctime(time.gmtime(0))
|
||||
'Thu Jan 1 00:00:00 1970'
|
||||
|
||||
If t is not provided, the current time as returned by localtime() is used.
|
||||
Locale information is not used by asctime().
|
||||
|
||||
This is meant to normalise the output of the built-in time.asctime() across
|
||||
different platforms and Python versions.
|
||||
In Python 3.x, the day of the month is right-justified, whereas on Windows
|
||||
Python 2.7 it is padded with zeros.
|
||||
|
||||
See https://github.com/fonttools/fonttools/issues/455
|
||||
"""
|
||||
if t is None:
|
||||
t = time.localtime()
|
||||
s = "%s %s %2s %s" % (
|
||||
DAYNAMES[t.tm_wday], MONTHNAMES[t.tm_mon], t.tm_mday,
|
||||
time.strftime("%H:%M:%S %Y", t))
|
||||
return s
|
||||
|
||||
|
||||
def timestampToString(value):
|
||||
return asctime(time.gmtime(max(0, value + epoch_diff)))
|
||||
|
||||
def timestampFromString(value):
|
||||
wkday, mnth = value[:7].split()
|
||||
t = datetime.strptime(value[7:], ' %d %H:%M:%S %Y')
|
||||
t = t.replace(month=MONTHNAMES.index(mnth), tzinfo=timezone.utc)
|
||||
wkday_idx = DAYNAMES.index(wkday)
|
||||
assert t.weekday() == wkday_idx, '"' + value + '" has inconsistent weekday'
|
||||
return int(t.timestamp()) - epoch_diff
|
||||
|
||||
def timestampNow():
|
||||
# https://reproducible-builds.org/specs/source-date-epoch/
|
||||
source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH")
|
||||
if source_date_epoch is not None:
|
||||
return int(source_date_epoch) - epoch_diff
|
||||
return int(time.time() - epoch_diff)
|
||||
|
||||
def timestampSinceEpoch(value):
|
||||
return int(value - epoch_diff)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import doctest
|
||||
sys.exit(doctest.testmod().failed)
|
||||
398
venv/Lib/site-packages/fontTools/misc/transform.py
Normal file
398
venv/Lib/site-packages/fontTools/misc/transform.py
Normal file
@@ -0,0 +1,398 @@
|
||||
"""Affine 2D transformation matrix class.
|
||||
|
||||
The Transform class implements various transformation matrix operations,
|
||||
both on the matrix itself, as well as on 2D coordinates.
|
||||
|
||||
Transform instances are effectively immutable: all methods that operate on the
|
||||
transformation itself always return a new instance. This has as the
|
||||
interesting side effect that Transform instances are hashable, ie. they can be
|
||||
used as dictionary keys.
|
||||
|
||||
This module exports the following symbols:
|
||||
|
||||
Transform
|
||||
this is the main class
|
||||
Identity
|
||||
Transform instance set to the identity transformation
|
||||
Offset
|
||||
Convenience function that returns a translating transformation
|
||||
Scale
|
||||
Convenience function that returns a scaling transformation
|
||||
|
||||
:Example:
|
||||
|
||||
>>> t = Transform(2, 0, 0, 3, 0, 0)
|
||||
>>> t.transformPoint((100, 100))
|
||||
(200, 300)
|
||||
>>> t = Scale(2, 3)
|
||||
>>> t.transformPoint((100, 100))
|
||||
(200, 300)
|
||||
>>> t.transformPoint((0, 0))
|
||||
(0, 0)
|
||||
>>> t = Offset(2, 3)
|
||||
>>> t.transformPoint((100, 100))
|
||||
(102, 103)
|
||||
>>> t.transformPoint((0, 0))
|
||||
(2, 3)
|
||||
>>> t2 = t.scale(0.5)
|
||||
>>> t2.transformPoint((100, 100))
|
||||
(52.0, 53.0)
|
||||
>>> import math
|
||||
>>> t3 = t2.rotate(math.pi / 2)
|
||||
>>> t3.transformPoint((0, 0))
|
||||
(2.0, 3.0)
|
||||
>>> t3.transformPoint((100, 100))
|
||||
(-48.0, 53.0)
|
||||
>>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2)
|
||||
>>> t.transformPoints([(0, 0), (1, 1), (100, 100)])
|
||||
[(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)]
|
||||
>>>
|
||||
"""
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
__all__ = ["Transform", "Identity", "Offset", "Scale"]
|
||||
|
||||
|
||||
_EPSILON = 1e-15
|
||||
_ONE_EPSILON = 1 - _EPSILON
|
||||
_MINUS_ONE_EPSILON = -1 + _EPSILON
|
||||
|
||||
|
||||
def _normSinCos(v):
|
||||
if abs(v) < _EPSILON:
|
||||
v = 0
|
||||
elif v > _ONE_EPSILON:
|
||||
v = 1
|
||||
elif v < _MINUS_ONE_EPSILON:
|
||||
v = -1
|
||||
return v
|
||||
|
||||
|
||||
class Transform(NamedTuple):
|
||||
|
||||
"""2x2 transformation matrix plus offset, a.k.a. Affine transform.
|
||||
Transform instances are immutable: all transforming methods, eg.
|
||||
rotate(), return a new Transform instance.
|
||||
|
||||
:Example:
|
||||
|
||||
>>> t = Transform()
|
||||
>>> t
|
||||
<Transform [1 0 0 1 0 0]>
|
||||
>>> t.scale(2)
|
||||
<Transform [2 0 0 2 0 0]>
|
||||
>>> t.scale(2.5, 5.5)
|
||||
<Transform [2.5 0 0 5.5 0 0]>
|
||||
>>>
|
||||
>>> t.scale(2, 3).transformPoint((100, 100))
|
||||
(200, 300)
|
||||
|
||||
Transform's constructor takes six arguments, all of which are
|
||||
optional, and can be used as keyword arguments::
|
||||
|
||||
>>> Transform(12)
|
||||
<Transform [12 0 0 1 0 0]>
|
||||
>>> Transform(dx=12)
|
||||
<Transform [1 0 0 1 12 0]>
|
||||
>>> Transform(yx=12)
|
||||
<Transform [1 0 12 1 0 0]>
|
||||
|
||||
Transform instances also behave like sequences of length 6::
|
||||
|
||||
>>> len(Identity)
|
||||
6
|
||||
>>> list(Identity)
|
||||
[1, 0, 0, 1, 0, 0]
|
||||
>>> tuple(Identity)
|
||||
(1, 0, 0, 1, 0, 0)
|
||||
|
||||
Transform instances are comparable::
|
||||
|
||||
>>> t1 = Identity.scale(2, 3).translate(4, 6)
|
||||
>>> t2 = Identity.translate(8, 18).scale(2, 3)
|
||||
>>> t1 == t2
|
||||
1
|
||||
|
||||
But beware of floating point rounding errors::
|
||||
|
||||
>>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
|
||||
>>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
|
||||
>>> t1
|
||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||
>>> t2
|
||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||
>>> t1 == t2
|
||||
0
|
||||
|
||||
Transform instances are hashable, meaning you can use them as
|
||||
keys in dictionaries::
|
||||
|
||||
>>> d = {Scale(12, 13): None}
|
||||
>>> d
|
||||
{<Transform [12 0 0 13 0 0]>: None}
|
||||
|
||||
But again, beware of floating point rounding errors::
|
||||
|
||||
>>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
|
||||
>>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
|
||||
>>> t1
|
||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||
>>> t2
|
||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||
>>> d = {t1: None}
|
||||
>>> d
|
||||
{<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
|
||||
>>> d[t2]
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||
"""
|
||||
|
||||
xx: float = 1
|
||||
xy: float = 0
|
||||
yx: float = 0
|
||||
yy: float = 1
|
||||
dx: float = 0
|
||||
dy: float = 0
|
||||
|
||||
def transformPoint(self, p):
|
||||
"""Transform a point.
|
||||
|
||||
:Example:
|
||||
|
||||
>>> t = Transform()
|
||||
>>> t = t.scale(2.5, 5.5)
|
||||
>>> t.transformPoint((100, 100))
|
||||
(250.0, 550.0)
|
||||
"""
|
||||
(x, y) = p
|
||||
xx, xy, yx, yy, dx, dy = self
|
||||
return (xx*x + yx*y + dx, xy*x + yy*y + dy)
|
||||
|
||||
def transformPoints(self, points):
|
||||
"""Transform a list of points.
|
||||
|
||||
:Example:
|
||||
|
||||
>>> t = Scale(2, 3)
|
||||
>>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)])
|
||||
[(0, 0), (0, 300), (200, 300), (200, 0)]
|
||||
>>>
|
||||
"""
|
||||
xx, xy, yx, yy, dx, dy = self
|
||||
return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points]
|
||||
|
||||
def transformVector(self, v):
|
||||
"""Transform an (dx, dy) vector, treating translation as zero.
|
||||
|
||||
:Example:
|
||||
|
||||
>>> t = Transform(2, 0, 0, 2, 10, 20)
|
||||
>>> t.transformVector((3, -4))
|
||||
(6, -8)
|
||||
>>>
|
||||
"""
|
||||
(dx, dy) = v
|
||||
xx, xy, yx, yy = self[:4]
|
||||
return (xx*dx + yx*dy, xy*dx + yy*dy)
|
||||
|
||||
def transformVectors(self, vectors):
|
||||
"""Transform a list of (dx, dy) vector, treating translation as zero.
|
||||
|
||||
:Example:
|
||||
>>> t = Transform(2, 0, 0, 2, 10, 20)
|
||||
>>> t.transformVectors([(3, -4), (5, -6)])
|
||||
[(6, -8), (10, -12)]
|
||||
>>>
|
||||
"""
|
||||
xx, xy, yx, yy = self[:4]
|
||||
return [(xx*dx + yx*dy, xy*dx + yy*dy) for dx, dy in vectors]
|
||||
|
||||
def translate(self, x=0, y=0):
|
||||
"""Return a new transformation, translated (offset) by x, y.
|
||||
|
||||
:Example:
|
||||
>>> t = Transform()
|
||||
>>> t.translate(20, 30)
|
||||
<Transform [1 0 0 1 20 30]>
|
||||
>>>
|
||||
"""
|
||||
return self.transform((1, 0, 0, 1, x, y))
|
||||
|
||||
def scale(self, x=1, y=None):
|
||||
"""Return a new transformation, scaled by x, y. The 'y' argument
|
||||
may be None, which implies to use the x value for y as well.
|
||||
|
||||
:Example:
|
||||
>>> t = Transform()
|
||||
>>> t.scale(5)
|
||||
<Transform [5 0 0 5 0 0]>
|
||||
>>> t.scale(5, 6)
|
||||
<Transform [5 0 0 6 0 0]>
|
||||
>>>
|
||||
"""
|
||||
if y is None:
|
||||
y = x
|
||||
return self.transform((x, 0, 0, y, 0, 0))
|
||||
|
||||
def rotate(self, angle):
|
||||
"""Return a new transformation, rotated by 'angle' (radians).
|
||||
|
||||
:Example:
|
||||
>>> import math
|
||||
>>> t = Transform()
|
||||
>>> t.rotate(math.pi / 2)
|
||||
<Transform [0 1 -1 0 0 0]>
|
||||
>>>
|
||||
"""
|
||||
import math
|
||||
c = _normSinCos(math.cos(angle))
|
||||
s = _normSinCos(math.sin(angle))
|
||||
return self.transform((c, s, -s, c, 0, 0))
|
||||
|
||||
def skew(self, x=0, y=0):
|
||||
"""Return a new transformation, skewed by x and y.
|
||||
|
||||
:Example:
|
||||
>>> import math
|
||||
>>> t = Transform()
|
||||
>>> t.skew(math.pi / 4)
|
||||
<Transform [1 0 1 1 0 0]>
|
||||
>>>
|
||||
"""
|
||||
import math
|
||||
return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
|
||||
|
||||
def transform(self, other):
|
||||
"""Return a new transformation, transformed by another
|
||||
transformation.
|
||||
|
||||
:Example:
|
||||
>>> t = Transform(2, 0, 0, 3, 1, 6)
|
||||
>>> t.transform((4, 3, 2, 1, 5, 6))
|
||||
<Transform [8 9 4 3 11 24]>
|
||||
>>>
|
||||
"""
|
||||
xx1, xy1, yx1, yy1, dx1, dy1 = other
|
||||
xx2, xy2, yx2, yy2, dx2, dy2 = self
|
||||
return self.__class__(
|
||||
xx1*xx2 + xy1*yx2,
|
||||
xx1*xy2 + xy1*yy2,
|
||||
yx1*xx2 + yy1*yx2,
|
||||
yx1*xy2 + yy1*yy2,
|
||||
xx2*dx1 + yx2*dy1 + dx2,
|
||||
xy2*dx1 + yy2*dy1 + dy2)
|
||||
|
||||
def reverseTransform(self, other):
|
||||
"""Return a new transformation, which is the other transformation
|
||||
transformed by self. self.reverseTransform(other) is equivalent to
|
||||
other.transform(self).
|
||||
|
||||
:Example:
|
||||
>>> t = Transform(2, 0, 0, 3, 1, 6)
|
||||
>>> t.reverseTransform((4, 3, 2, 1, 5, 6))
|
||||
<Transform [8 6 6 3 21 15]>
|
||||
>>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6))
|
||||
<Transform [8 6 6 3 21 15]>
|
||||
>>>
|
||||
"""
|
||||
xx1, xy1, yx1, yy1, dx1, dy1 = self
|
||||
xx2, xy2, yx2, yy2, dx2, dy2 = other
|
||||
return self.__class__(
|
||||
xx1*xx2 + xy1*yx2,
|
||||
xx1*xy2 + xy1*yy2,
|
||||
yx1*xx2 + yy1*yx2,
|
||||
yx1*xy2 + yy1*yy2,
|
||||
xx2*dx1 + yx2*dy1 + dx2,
|
||||
xy2*dx1 + yy2*dy1 + dy2)
|
||||
|
||||
def inverse(self):
|
||||
"""Return the inverse transformation.
|
||||
|
||||
:Example:
|
||||
>>> t = Identity.translate(2, 3).scale(4, 5)
|
||||
>>> t.transformPoint((10, 20))
|
||||
(42, 103)
|
||||
>>> it = t.inverse()
|
||||
>>> it.transformPoint((42, 103))
|
||||
(10.0, 20.0)
|
||||
>>>
|
||||
"""
|
||||
if self == Identity:
|
||||
return self
|
||||
xx, xy, yx, yy, dx, dy = self
|
||||
det = xx*yy - yx*xy
|
||||
xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
|
||||
dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
|
||||
return self.__class__(xx, xy, yx, yy, dx, dy)
|
||||
|
||||
def toPS(self):
|
||||
"""Return a PostScript representation
|
||||
|
||||
:Example:
|
||||
|
||||
>>> t = Identity.scale(2, 3).translate(4, 5)
|
||||
>>> t.toPS()
|
||||
'[2 0 0 3 8 15]'
|
||||
>>>
|
||||
"""
|
||||
return "[%s %s %s %s %s %s]" % self
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if transform is not identity, False otherwise.
|
||||
|
||||
:Example:
|
||||
|
||||
>>> bool(Identity)
|
||||
False
|
||||
>>> bool(Transform())
|
||||
False
|
||||
>>> bool(Scale(1.))
|
||||
False
|
||||
>>> bool(Scale(2))
|
||||
True
|
||||
>>> bool(Offset())
|
||||
False
|
||||
>>> bool(Offset(0))
|
||||
False
|
||||
>>> bool(Offset(2))
|
||||
True
|
||||
"""
|
||||
return self != Identity
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)
|
||||
|
||||
|
||||
Identity = Transform()
|
||||
|
||||
def Offset(x=0, y=0):
|
||||
"""Return the identity transformation offset by x, y.
|
||||
|
||||
:Example:
|
||||
>>> Offset(2, 3)
|
||||
<Transform [1 0 0 1 2 3]>
|
||||
>>>
|
||||
"""
|
||||
return Transform(1, 0, 0, 1, x, y)
|
||||
|
||||
def Scale(x, y=None):
|
||||
"""Return the identity transformation scaled by x, y. The 'y' argument
|
||||
may be None, which implies to use the x value for y as well.
|
||||
|
||||
:Example:
|
||||
>>> Scale(2, 3)
|
||||
<Transform [2 0 0 3 0 0]>
|
||||
>>>
|
||||
"""
|
||||
if y is None:
|
||||
y = x
|
||||
return Transform(x, 0, 0, y, 0, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import doctest
|
||||
sys.exit(doctest.testmod().failed)
|
||||
171
venv/Lib/site-packages/fontTools/misc/xmlReader.py
Normal file
171
venv/Lib/site-packages/fontTools/misc/xmlReader.py
Normal file
@@ -0,0 +1,171 @@
|
||||
from fontTools import ttLib
|
||||
from fontTools.misc.textTools import safeEval
|
||||
from fontTools.ttLib.tables.DefaultTable import DefaultTable
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class TTXParseError(Exception): pass
|
||||
|
||||
BUFSIZE = 0x4000
|
||||
|
||||
|
||||
class XMLReader(object):
|
||||
|
||||
def __init__(self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False):
|
||||
if fileOrPath == '-':
|
||||
fileOrPath = sys.stdin
|
||||
if not hasattr(fileOrPath, "read"):
|
||||
self.file = open(fileOrPath, "rb")
|
||||
self._closeStream = True
|
||||
else:
|
||||
# assume readable file object
|
||||
self.file = fileOrPath
|
||||
self._closeStream = False
|
||||
self.ttFont = ttFont
|
||||
self.progress = progress
|
||||
if quiet is not None:
|
||||
from fontTools.misc.loggingTools import deprecateArgument
|
||||
deprecateArgument("quiet", "configure logging instead")
|
||||
self.quiet = quiet
|
||||
self.root = None
|
||||
self.contentStack = []
|
||||
self.contentOnly = contentOnly
|
||||
self.stackSize = 0
|
||||
|
||||
def read(self, rootless=False):
|
||||
if rootless:
|
||||
self.stackSize += 1
|
||||
if self.progress:
|
||||
self.file.seek(0, 2)
|
||||
fileSize = self.file.tell()
|
||||
self.progress.set(0, fileSize // 100 or 1)
|
||||
self.file.seek(0)
|
||||
self._parseFile(self.file)
|
||||
if self._closeStream:
|
||||
self.close()
|
||||
if rootless:
|
||||
self.stackSize -= 1
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
|
||||
def _parseFile(self, file):
|
||||
from xml.parsers.expat import ParserCreate
|
||||
parser = ParserCreate()
|
||||
parser.StartElementHandler = self._startElementHandler
|
||||
parser.EndElementHandler = self._endElementHandler
|
||||
parser.CharacterDataHandler = self._characterDataHandler
|
||||
|
||||
pos = 0
|
||||
while True:
|
||||
chunk = file.read(BUFSIZE)
|
||||
if not chunk:
|
||||
parser.Parse(chunk, 1)
|
||||
break
|
||||
pos = pos + len(chunk)
|
||||
if self.progress:
|
||||
self.progress.set(pos // 100)
|
||||
parser.Parse(chunk, 0)
|
||||
|
||||
def _startElementHandler(self, name, attrs):
|
||||
if self.stackSize == 1 and self.contentOnly:
|
||||
# We already know the table we're parsing, skip
|
||||
# parsing the table tag and continue to
|
||||
# stack '2' which begins parsing content
|
||||
self.contentStack.append([])
|
||||
self.stackSize = 2
|
||||
return
|
||||
stackSize = self.stackSize
|
||||
self.stackSize = stackSize + 1
|
||||
subFile = attrs.get("src")
|
||||
if subFile is not None:
|
||||
if hasattr(self.file, 'name'):
|
||||
# if file has a name, get its parent directory
|
||||
dirname = os.path.dirname(self.file.name)
|
||||
else:
|
||||
# else fall back to using the current working directory
|
||||
dirname = os.getcwd()
|
||||
subFile = os.path.join(dirname, subFile)
|
||||
if not stackSize:
|
||||
if name != "ttFont":
|
||||
raise TTXParseError("illegal root tag: %s" % name)
|
||||
if self.ttFont.reader is None and not self.ttFont.tables:
|
||||
sfntVersion = attrs.get("sfntVersion")
|
||||
if sfntVersion is not None:
|
||||
if len(sfntVersion) != 4:
|
||||
sfntVersion = safeEval('"' + sfntVersion + '"')
|
||||
self.ttFont.sfntVersion = sfntVersion
|
||||
self.contentStack.append([])
|
||||
elif stackSize == 1:
|
||||
if subFile is not None:
|
||||
subReader = XMLReader(subFile, self.ttFont, self.progress)
|
||||
subReader.read()
|
||||
self.contentStack.append([])
|
||||
return
|
||||
tag = ttLib.xmlToTag(name)
|
||||
msg = "Parsing '%s' table..." % tag
|
||||
if self.progress:
|
||||
self.progress.setLabel(msg)
|
||||
log.info(msg)
|
||||
if tag == "GlyphOrder":
|
||||
tableClass = ttLib.GlyphOrder
|
||||
elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])):
|
||||
tableClass = DefaultTable
|
||||
else:
|
||||
tableClass = ttLib.getTableClass(tag)
|
||||
if tableClass is None:
|
||||
tableClass = DefaultTable
|
||||
if tag == 'loca' and tag in self.ttFont:
|
||||
# Special-case the 'loca' table as we need the
|
||||
# original if the 'glyf' table isn't recompiled.
|
||||
self.currentTable = self.ttFont[tag]
|
||||
else:
|
||||
self.currentTable = tableClass(tag)
|
||||
self.ttFont[tag] = self.currentTable
|
||||
self.contentStack.append([])
|
||||
elif stackSize == 2 and subFile is not None:
|
||||
subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True)
|
||||
subReader.read()
|
||||
self.contentStack.append([])
|
||||
self.root = subReader.root
|
||||
elif stackSize == 2:
|
||||
self.contentStack.append([])
|
||||
self.root = (name, attrs, self.contentStack[-1])
|
||||
else:
|
||||
l = []
|
||||
self.contentStack[-1].append((name, attrs, l))
|
||||
self.contentStack.append(l)
|
||||
|
||||
def _characterDataHandler(self, data):
|
||||
if self.stackSize > 1:
|
||||
self.contentStack[-1].append(data)
|
||||
|
||||
def _endElementHandler(self, name):
|
||||
self.stackSize = self.stackSize - 1
|
||||
del self.contentStack[-1]
|
||||
if not self.contentOnly:
|
||||
if self.stackSize == 1:
|
||||
self.root = None
|
||||
elif self.stackSize == 2:
|
||||
name, attrs, content = self.root
|
||||
self.currentTable.fromXML(name, attrs, content, self.ttFont)
|
||||
self.root = None
|
||||
|
||||
|
||||
class ProgressPrinter(object):
|
||||
|
||||
def __init__(self, title, maxval=100):
|
||||
print(title)
|
||||
|
||||
def set(self, val, maxval=None):
|
||||
pass
|
||||
|
||||
def increment(self, val=1):
|
||||
pass
|
||||
|
||||
def setLabel(self, text):
|
||||
print(text)
|
||||
194
venv/Lib/site-packages/fontTools/misc/xmlWriter.py
Normal file
194
venv/Lib/site-packages/fontTools/misc/xmlWriter.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""xmlWriter.py -- Simple XML authoring class"""
|
||||
|
||||
from fontTools.misc.textTools import byteord, strjoin, tobytes, tostr
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
|
||||
INDENT = " "
|
||||
|
||||
|
||||
class XMLWriter(object):
|
||||
|
||||
def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None, encoding="utf_8",
|
||||
newlinestr="\n"):
|
||||
if encoding.lower().replace('-','').replace('_','') != 'utf8':
|
||||
raise Exception('Only UTF-8 encoding is supported.')
|
||||
if fileOrPath == '-':
|
||||
fileOrPath = sys.stdout
|
||||
if not hasattr(fileOrPath, "write"):
|
||||
self.filename = fileOrPath
|
||||
self.file = open(fileOrPath, "wb")
|
||||
self._closeStream = True
|
||||
else:
|
||||
self.filename = None
|
||||
# assume writable file object
|
||||
self.file = fileOrPath
|
||||
self._closeStream = False
|
||||
|
||||
# Figure out if writer expects bytes or unicodes
|
||||
try:
|
||||
# The bytes check should be first. See:
|
||||
# https://github.com/fonttools/fonttools/pull/233
|
||||
self.file.write(b'')
|
||||
self.totype = tobytes
|
||||
except TypeError:
|
||||
# This better not fail.
|
||||
self.file.write('')
|
||||
self.totype = tostr
|
||||
self.indentwhite = self.totype(indentwhite)
|
||||
if newlinestr is None:
|
||||
self.newlinestr = self.totype(os.linesep)
|
||||
else:
|
||||
self.newlinestr = self.totype(newlinestr)
|
||||
self.indentlevel = 0
|
||||
self.stack = []
|
||||
self.needindent = 1
|
||||
self.idlefunc = idlefunc
|
||||
self.idlecounter = 0
|
||||
self._writeraw('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
self.newline()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exception_type, exception_value, traceback):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
if self._closeStream:
|
||||
self.file.close()
|
||||
|
||||
def write(self, string, indent=True):
|
||||
"""Writes text."""
|
||||
self._writeraw(escape(string), indent=indent)
|
||||
|
||||
def writecdata(self, string):
|
||||
"""Writes text in a CDATA section."""
|
||||
self._writeraw("<![CDATA[" + string + "]]>")
|
||||
|
||||
def write8bit(self, data, strip=False):
|
||||
"""Writes a bytes() sequence into the XML, escaping
|
||||
non-ASCII bytes. When this is read in xmlReader,
|
||||
the original bytes can be recovered by encoding to
|
||||
'latin-1'."""
|
||||
self._writeraw(escape8bit(data), strip=strip)
|
||||
|
||||
def write_noindent(self, string):
|
||||
"""Writes text without indentation."""
|
||||
self._writeraw(escape(string), indent=False)
|
||||
|
||||
def _writeraw(self, data, indent=True, strip=False):
|
||||
"""Writes bytes, possibly indented."""
|
||||
if indent and self.needindent:
|
||||
self.file.write(self.indentlevel * self.indentwhite)
|
||||
self.needindent = 0
|
||||
s = self.totype(data, encoding="utf_8")
|
||||
if (strip):
|
||||
s = s.strip()
|
||||
self.file.write(s)
|
||||
|
||||
def newline(self):
|
||||
self.file.write(self.newlinestr)
|
||||
self.needindent = 1
|
||||
idlecounter = self.idlecounter
|
||||
if not idlecounter % 100 and self.idlefunc is not None:
|
||||
self.idlefunc()
|
||||
self.idlecounter = idlecounter + 1
|
||||
|
||||
def comment(self, data):
|
||||
data = escape(data)
|
||||
lines = data.split("\n")
|
||||
self._writeraw("<!-- " + lines[0])
|
||||
for line in lines[1:]:
|
||||
self.newline()
|
||||
self._writeraw(" " + line)
|
||||
self._writeraw(" -->")
|
||||
|
||||
def simpletag(self, _TAG_, *args, **kwargs):
|
||||
attrdata = self.stringifyattrs(*args, **kwargs)
|
||||
data = "<%s%s/>" % (_TAG_, attrdata)
|
||||
self._writeraw(data)
|
||||
|
||||
def begintag(self, _TAG_, *args, **kwargs):
|
||||
attrdata = self.stringifyattrs(*args, **kwargs)
|
||||
data = "<%s%s>" % (_TAG_, attrdata)
|
||||
self._writeraw(data)
|
||||
self.stack.append(_TAG_)
|
||||
self.indent()
|
||||
|
||||
def endtag(self, _TAG_):
|
||||
assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
|
||||
del self.stack[-1]
|
||||
self.dedent()
|
||||
data = "</%s>" % _TAG_
|
||||
self._writeraw(data)
|
||||
|
||||
def dumphex(self, data):
|
||||
linelength = 16
|
||||
hexlinelength = linelength * 2
|
||||
chunksize = 8
|
||||
for i in range(0, len(data), linelength):
|
||||
hexline = hexStr(data[i:i+linelength])
|
||||
line = ""
|
||||
white = ""
|
||||
for j in range(0, hexlinelength, chunksize):
|
||||
line = line + white + hexline[j:j+chunksize]
|
||||
white = " "
|
||||
self._writeraw(line)
|
||||
self.newline()
|
||||
|
||||
def indent(self):
|
||||
self.indentlevel = self.indentlevel + 1
|
||||
|
||||
def dedent(self):
|
||||
assert self.indentlevel > 0
|
||||
self.indentlevel = self.indentlevel - 1
|
||||
|
||||
def stringifyattrs(self, *args, **kwargs):
|
||||
if kwargs:
|
||||
assert not args
|
||||
attributes = sorted(kwargs.items())
|
||||
elif args:
|
||||
assert len(args) == 1
|
||||
attributes = args[0]
|
||||
else:
|
||||
return ""
|
||||
data = ""
|
||||
for attr, value in attributes:
|
||||
if not isinstance(value, (bytes, str)):
|
||||
value = str(value)
|
||||
data = data + ' %s="%s"' % (attr, escapeattr(value))
|
||||
return data
|
||||
|
||||
|
||||
def escape(data):
|
||||
data = tostr(data, 'utf_8')
|
||||
data = data.replace("&", "&")
|
||||
data = data.replace("<", "<")
|
||||
data = data.replace(">", ">")
|
||||
data = data.replace("\r", " ")
|
||||
return data
|
||||
|
||||
def escapeattr(data):
|
||||
data = escape(data)
|
||||
data = data.replace('"', """)
|
||||
return data
|
||||
|
||||
def escape8bit(data):
|
||||
"""Input is Unicode string."""
|
||||
def escapechar(c):
|
||||
n = ord(c)
|
||||
if 32 <= n <= 127 and c not in "<&>":
|
||||
return c
|
||||
else:
|
||||
return "&#" + repr(n) + ";"
|
||||
return strjoin(map(escapechar, data.decode('latin-1')))
|
||||
|
||||
def hexStr(s):
|
||||
h = string.hexdigits
|
||||
r = ''
|
||||
for c in s:
|
||||
i = byteord(c)
|
||||
r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
|
||||
return r
|
||||
Reference in New Issue
Block a user