fontTools.misc.* documentation, part 2 (#1981)
* Document misc.filenames * Document misc.fixedTools * Document misc.intTools * Document misc.loggingTools * Document misc.macCreatorType * Document misc.macRes * Document misc.plistlib
This commit is contained in:
parent
80dac9c0f4
commit
775dc6074e
@ -1,8 +1,7 @@
|
|||||||
#########
|
##########################################################
|
||||||
filenames
|
filenames: Implements UFO User Name to File Name Algorithm
|
||||||
#########
|
##########################################################
|
||||||
|
|
||||||
.. automodule:: fontTools.misc.filenames
|
.. automodule:: fontTools.misc.filenames
|
||||||
:inherited-members:
|
:members: userNameToFileName
|
||||||
:members:
|
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##########
|
######################################################
|
||||||
fixedTools
|
fixedTools: Tools for working with fixed-point numbers
|
||||||
##########
|
######################################################
|
||||||
|
|
||||||
.. automodule:: fontTools.misc.fixedTools
|
.. automodule:: fontTools.misc.fixedTools
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
@ -21,6 +21,7 @@ utilities by fontTools, but some of which may be more generally useful.
|
|||||||
loggingTools
|
loggingTools
|
||||||
macCreatorType
|
macCreatorType
|
||||||
macRes
|
macRes
|
||||||
|
plistlib
|
||||||
psCharStrings
|
psCharStrings
|
||||||
psLib
|
psLib
|
||||||
psOperators
|
psOperators
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
########
|
###############################################
|
||||||
intTools
|
intTools: Tools for working with integer values
|
||||||
########
|
###############################################
|
||||||
|
|
||||||
.. automodule:: fontTools.misc.intTools
|
.. automodule:: fontTools.misc.intTools
|
||||||
:inherited-members:
|
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
|
@ -1,8 +1,7 @@
|
|||||||
############
|
###################################################################
|
||||||
loggingTools
|
loggingTools: tools for interfacing with the Python logging package
|
||||||
############
|
###################################################################
|
||||||
|
|
||||||
.. automodule:: fontTools.misc.loggingTools
|
.. automodule:: fontTools.misc.loggingTools
|
||||||
:inherited-members:
|
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
##############
|
##############################################################
|
||||||
macCreatorType
|
macCreatorType: Functions for working with Mac file attributes
|
||||||
##############
|
##############################################################
|
||||||
|
|
||||||
|
This module requires the `xattr <https://pypi.org/project/xattr/>`_ module
|
||||||
|
to be installed in order to function correctly.
|
||||||
|
|
||||||
.. automodule:: fontTools.misc.macCreatorType
|
.. automodule:: fontTools.misc.macCreatorType
|
||||||
:inherited-members:
|
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
######
|
############################################
|
||||||
macRes
|
macRes: Tools for reading Mac resource forks
|
||||||
######
|
############################################
|
||||||
|
|
||||||
|
Classic Mac OS files are made up of two parts - the "data fork" which contains the file contents proper, and the "resource fork" which contains a number of structured data items called "resources". Some fonts, such as Mac "font suitcases" and Type 1 LWFN fonts, still use the resource fork for this kind of structured data, and so to read them, fontTools needs to have access to resource forks.
|
||||||
|
|
||||||
|
The Inside Macintosh volume `More Macintosh Toolbox <https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf#page=34>`_ explains the structure of resource and data forks.
|
||||||
|
|
||||||
.. automodule:: fontTools.misc.macRes
|
.. automodule:: fontTools.misc.macRes
|
||||||
:inherited-members:
|
:members: ResourceReader, Resource
|
||||||
:members:
|
:member-order: bysource
|
||||||
:undoc-members:
|
|
9
Doc/source/misc/plistlib.rst
Normal file
9
Doc/source/misc/plistlib.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#########################################
|
||||||
|
plistlib: Tools for handling .plist files
|
||||||
|
#########################################
|
||||||
|
|
||||||
|
.. automodule:: fontTools.misc.plistlib
|
||||||
|
:members: totree, fromtree, load, loads, dump, dumps
|
||||||
|
|
||||||
|
.. autoclass:: fontTools.misc.plistlib.Data
|
||||||
|
:members:
|
@ -1,19 +1,23 @@
|
|||||||
"""
|
"""
|
||||||
User name to file name conversion based on the UFO 3 spec:
|
This module implements the algorithm for converting between a "user name" -
|
||||||
http://unifiedfontobject.org/versions/ufo3/conventions/
|
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 code was copied from:
|
The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
|
||||||
https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py
|
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.
|
||||||
|
|
||||||
Author: Tal Leming
|
This code was originally copied from
|
||||||
Copyright (c) 2005-2016, The RoboFab Developers:
|
`ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
|
||||||
Erik van Blokland
|
by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
|
||||||
Tal Leming
|
|
||||||
Just van Rossum
|
- Erik van Blokland
|
||||||
|
- Tal Leming
|
||||||
|
- Just van Rossum
|
||||||
"""
|
"""
|
||||||
from fontTools.misc.py23 import basestring, unicode
|
from fontTools.misc.py23 import basestring, unicode
|
||||||
|
|
||||||
|
|
||||||
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
|
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
|
||||||
illegalCharacters += [chr(i) for i in range(1, 32)]
|
illegalCharacters += [chr(i) for i in range(1, 32)]
|
||||||
illegalCharacters += [chr(0x7F)]
|
illegalCharacters += [chr(0x7F)]
|
||||||
@ -27,9 +31,24 @@ class NameTranslationError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
def userNameToFileName(userName, existing=[], prefix="", suffix=""):
|
def userNameToFileName(userName, existing=[], prefix="", suffix=""):
|
||||||
"""
|
"""Converts from a user name to a file name.
|
||||||
existing should be a case-insensitive list
|
|
||||||
of all existing file names.
|
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"
|
>>> userNameToFileName("a") == "a"
|
||||||
True
|
True
|
||||||
|
@ -1,4 +1,20 @@
|
|||||||
"""fontTools.misc.fixedTools.py -- tools for working with fixed numbers.
|
"""
|
||||||
|
The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
|
||||||
|
defines two fixed-point data types:
|
||||||
|
|
||||||
|
``Fixed``
|
||||||
|
A 32-bit signed fixed-point number with a 16 bit twos-complement
|
||||||
|
magnitude component and 16 fractional bits.
|
||||||
|
``F2DOT14``
|
||||||
|
A 16-bit signed fixed-point number with a 2 bit twos-complement
|
||||||
|
magnitude component and 14 fractional bits.
|
||||||
|
|
||||||
|
To support reading and writing data with these data types, this module provides
|
||||||
|
functions for converting between fixed-point, float and string representations.
|
||||||
|
|
||||||
|
.. data:: MAX_F2DOT14
|
||||||
|
|
||||||
|
The maximum value that can still fit in an F2Dot14. (1.99993896484375)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
@ -8,6 +24,7 @@ import logging
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"MAX_F2DOT14",
|
||||||
"otRound",
|
"otRound",
|
||||||
"fixedToFloat",
|
"fixedToFloat",
|
||||||
"floatToFixed",
|
"floatToFixed",
|
||||||
@ -21,25 +38,45 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# the max value that can still fit in an F2Dot14:
|
|
||||||
# 1.99993896484375
|
|
||||||
MAX_F2DOT14 = 0x7FFF / (1 << 14)
|
MAX_F2DOT14 = 0x7FFF / (1 << 14)
|
||||||
|
|
||||||
|
|
||||||
def otRound(value):
|
def otRound(value):
|
||||||
"""Round float value to nearest integer towards +Infinity.
|
"""Round float value to nearest integer towards ``+Infinity``.
|
||||||
For fractional values of 0.5 and higher, take the next higher integer;
|
|
||||||
|
The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
|
||||||
|
defines the required method for converting floating point values to
|
||||||
|
fixed-point. In particular it specifies the following rounding strategy:
|
||||||
|
|
||||||
|
for fractional values of 0.5 and higher, take the next higher integer;
|
||||||
for other fractional values, truncate.
|
for other fractional values, truncate.
|
||||||
|
|
||||||
https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
|
This function rounds the floating-point value according to this strategy
|
||||||
https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
|
in preparation for conversion to fixed-point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): The input floating-point value.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
float: The rounded value.
|
||||||
"""
|
"""
|
||||||
|
# See this thread for how we ended up with this implementation:
|
||||||
|
# https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
|
||||||
return int(math.floor(value + 0.5))
|
return int(math.floor(value + 0.5))
|
||||||
|
|
||||||
|
|
||||||
def fixedToFloat(value, precisionBits):
|
def fixedToFloat(value, precisionBits):
|
||||||
"""Converts a fixed-point number to a float given the number of
|
"""Converts a fixed-point number to a float given the number of
|
||||||
precisionBits. Ie. value / (1 << precisionBits)
|
precision bits.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int): Number in fixed-point format.
|
||||||
|
precisionBits (int): Number of precision bits.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Floating point value.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
>>> import math
|
>>> import math
|
||||||
>>> f = fixedToFloat(-10139, precisionBits=14)
|
>>> f = fixedToFloat(-10139, precisionBits=14)
|
||||||
@ -51,7 +88,16 @@ def fixedToFloat(value, precisionBits):
|
|||||||
|
|
||||||
def floatToFixed(value, precisionBits):
|
def floatToFixed(value, precisionBits):
|
||||||
"""Converts a float to a fixed-point number given the number of
|
"""Converts a float to a fixed-point number given the number of
|
||||||
precisionBits. Ie. round(value * (1 << precisionBits)).
|
precision bits.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Floating point value.
|
||||||
|
precisionBits (int): Number of precision bits.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Fixed-point representation.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
>>> floatToFixed(-0.61883544921875, precisionBits=14)
|
>>> floatToFixed(-0.61883544921875, precisionBits=14)
|
||||||
-10139
|
-10139
|
||||||
@ -62,11 +108,22 @@ def floatToFixed(value, precisionBits):
|
|||||||
|
|
||||||
|
|
||||||
def floatToFixedToFloat(value, precisionBits):
|
def floatToFixedToFloat(value, precisionBits):
|
||||||
"""Converts a float to a fixed-point number given the number of
|
"""Converts a float to a fixed-point number and back again.
|
||||||
precisionBits, round it, then convert it back to float again.
|
|
||||||
Ie. round(value * (1<<precisionBits)) / (1<<precisionBits)
|
|
||||||
Note: this **is** equivalent to fixedToFloat(floatToFixed(value)).
|
|
||||||
|
|
||||||
|
By converting the float to fixed, rounding it, and converting it back
|
||||||
|
to float again, this returns a floating point values which is exactly
|
||||||
|
representable in fixed-point format.
|
||||||
|
|
||||||
|
Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): The input floating point value.
|
||||||
|
precisionBits (int): Number of precision bits.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The transformed and rounded value.
|
||||||
|
|
||||||
|
Examples::
|
||||||
>>> import math
|
>>> import math
|
||||||
>>> f1 = -0.61884
|
>>> f1 = -0.61884
|
||||||
>>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
|
>>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
|
||||||
@ -80,19 +137,27 @@ def floatToFixedToFloat(value, precisionBits):
|
|||||||
|
|
||||||
|
|
||||||
def fixedToStr(value, precisionBits):
|
def fixedToStr(value, precisionBits):
|
||||||
"""Converts a fixed-point number with 'precisionBits' number of fractional binary
|
"""Converts a fixed-point number to a string representing a decimal float.
|
||||||
digits to a string representing a decimal float, choosing the float that has the
|
|
||||||
shortest decimal representation (the least number of fractional decimal digits).
|
This chooses the float that has the shortest decimal representation (the least
|
||||||
Eg. to convert a fixed-point number in a 2.14 format, use precisionBits=14:
|
number of fractional decimal digits).
|
||||||
|
|
||||||
|
For example, to convert a fixed-point number in a 2.14 format, use
|
||||||
|
``precisionBits=14``::
|
||||||
|
|
||||||
>>> fixedToStr(-10139, precisionBits=14)
|
>>> fixedToStr(-10139, precisionBits=14)
|
||||||
'-0.61884'
|
'-0.61884'
|
||||||
|
|
||||||
This is pretty slow compared to the simple division used in fixedToFloat.
|
This is pretty slow compared to the simple division used in ``fixedToFloat``.
|
||||||
Use sporadically when you need to serialize or print the fixed-point number in
|
Use sporadically when you need to serialize or print the fixed-point number in
|
||||||
a human-readable form.
|
a human-readable form.
|
||||||
|
|
||||||
NOTE: precisionBits is only supported up to 16.
|
Args:
|
||||||
|
value (int): The fixed-point value to convert.
|
||||||
|
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A string representation of the value.
|
||||||
"""
|
"""
|
||||||
if not value: return "0.0"
|
if not value: return "0.0"
|
||||||
|
|
||||||
@ -118,10 +183,18 @@ def fixedToStr(value, precisionBits):
|
|||||||
|
|
||||||
|
|
||||||
def strToFixed(string, precisionBits):
|
def strToFixed(string, precisionBits):
|
||||||
"""Convert the string representation of a decimal float number to a fixed-point
|
"""Converts a string representing a decimal float to a fixed-point number.
|
||||||
number with 'precisionBits' fractional binary digits.
|
|
||||||
E.g. to convert a float string to a 2.14 fixed-point number:
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): A string representing a decimal float.
|
||||||
|
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Fixed-point representation.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
>>> ## to convert a float string to a 2.14 fixed-point number:
|
||||||
>>> strToFixed('-0.61884', precisionBits=14)
|
>>> strToFixed('-0.61884', precisionBits=14)
|
||||||
-10139
|
-10139
|
||||||
"""
|
"""
|
||||||
@ -130,10 +203,23 @@ def strToFixed(string, precisionBits):
|
|||||||
|
|
||||||
|
|
||||||
def strToFixedToFloat(string, precisionBits):
|
def strToFixedToFloat(string, precisionBits):
|
||||||
"""Convert a string to a decimal float, by first converting the float to a
|
"""Convert a string to a decimal float with fixed-point rounding.
|
||||||
fixed-point number with precisionBits fractional binary digits.
|
|
||||||
|
This first converts string to a float, then turns it into a fixed-point
|
||||||
|
number with ``precisionBits`` fractional binary digits, then back to a
|
||||||
|
float again.
|
||||||
|
|
||||||
This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
|
This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): A string representing a decimal float.
|
||||||
|
precisionBits (int): Number of precision bits.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The transformed and rounded value.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
>>> import math
|
>>> import math
|
||||||
>>> s = '-0.61884'
|
>>> s = '-0.61884'
|
||||||
>>> bits = 14
|
>>> bits = 14
|
||||||
@ -149,21 +235,43 @@ def strToFixedToFloat(string, precisionBits):
|
|||||||
|
|
||||||
|
|
||||||
def floatToFixedToStr(value, precisionBits):
|
def floatToFixedToStr(value, precisionBits):
|
||||||
"""Convert float to string using the shortest decimal representation (ie. the least
|
"""Convert float to string with fixed-point rounding.
|
||||||
number of fractional decimal digits) to represent the equivalent fixed-point number
|
|
||||||
with 'precisionBits' fractional binary digits.
|
This uses the shortest decimal representation (ie. the least
|
||||||
|
number of fractional decimal digits) to represent the equivalent
|
||||||
|
fixed-point number with ``precisionBits`` fractional binary digits.
|
||||||
It uses fixedToStr under the hood.
|
It uses fixedToStr under the hood.
|
||||||
|
|
||||||
>>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
|
>>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
|
||||||
'-0.61884'
|
'-0.61884'
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): The float value to convert.
|
||||||
|
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A string representation of the value.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fixed = otRound(value * (1 << precisionBits))
|
fixed = otRound(value * (1 << precisionBits))
|
||||||
return fixedToStr(fixed, precisionBits)
|
return fixedToStr(fixed, precisionBits)
|
||||||
|
|
||||||
|
|
||||||
def ensureVersionIsLong(value):
|
def ensureVersionIsLong(value):
|
||||||
"""Ensure a table version is an unsigned long (unsigned short major,
|
"""Ensure a table version is an unsigned long.
|
||||||
unsigned short minor) instead of a float."""
|
|
||||||
|
OpenType table version numbers are expressed as a single unsigned long
|
||||||
|
comprising of an unsigned short major version and unsigned short minor
|
||||||
|
version. This function detects if the value to be used as a version number
|
||||||
|
looks too small (i.e. is less than ``0x10000``), and converts it to
|
||||||
|
fixed-point using :func:`floatToFixed` if so.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (Number): a candidate table version number.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: A table version number, possibly corrected to fixed-point.
|
||||||
|
"""
|
||||||
if value < 0x10000:
|
if value < 0x10000:
|
||||||
newValue = floatToFixed(value, 16)
|
newValue = floatToFixed(value, 16)
|
||||||
log.warning(
|
log.warning(
|
||||||
@ -174,7 +282,14 @@ def ensureVersionIsLong(value):
|
|||||||
|
|
||||||
|
|
||||||
def versionToFixed(value):
|
def versionToFixed(value):
|
||||||
"""Converts a table version to a fixed"""
|
"""Ensure a table version number is fixed-point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): a candidate table version number.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: A table version number, possibly corrected to fixed-point.
|
||||||
|
"""
|
||||||
value = int(value, 0) if value.startswith("0") else float(value)
|
value = int(value, 0) if value.startswith("0") else float(value)
|
||||||
value = ensureVersionIsLong(value)
|
value = ensureVersionIsLong(value)
|
||||||
return value
|
return value
|
||||||
|
@ -1,12 +1,25 @@
|
|||||||
"""Misc integer tools."""
|
|
||||||
|
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
|
|
||||||
__all__ = ['popCount']
|
__all__ = ['popCount']
|
||||||
|
|
||||||
|
|
||||||
def popCount(v):
|
def popCount(v):
|
||||||
"""Return number of 1 bits in an integer."""
|
"""Return number of 1 bits (population count) of an integer.
|
||||||
|
|
||||||
|
If the integer is negative, the number of 1 bits in the
|
||||||
|
twos-complement representation of the integer is returned. i.e.
|
||||||
|
``popCount(-30) == 28`` because -30 is::
|
||||||
|
|
||||||
|
1111 1111 1111 1111 1111 1111 1110 0010
|
||||||
|
|
||||||
|
Uses the algorithm from `HAKMEM item 169 <https://www.inwap.com/pdp10/hbaker/hakmem/hacks.html#item169>`_.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
v (int): Value to count.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of 1 bits in the binary representation of ``v``.
|
||||||
|
"""
|
||||||
|
|
||||||
if v > 0xFFFFFFFF:
|
if v > 0xFFFFFFFF:
|
||||||
return popCount(v >> 32) + popCount(v & 0xFFFFFFFF)
|
return popCount(v >> 32) + popCount(v & 0xFFFFFFFF)
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
""" fontTools.misc.loggingTools.py -- tools for interfacing with the Python
|
|
||||||
logging package.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
@ -25,10 +21,18 @@ DEFAULT_FORMATS = {
|
|||||||
|
|
||||||
|
|
||||||
class LevelFormatter(logging.Formatter):
|
class LevelFormatter(logging.Formatter):
|
||||||
""" Formatter class which optionally takes a dict of logging levels to
|
"""Log formatter with level-specific formatting.
|
||||||
|
|
||||||
|
Formatter class which optionally takes a dict of logging levels to
|
||||||
format strings, allowing to customise the log records appearance for
|
format strings, allowing to customise the log records appearance for
|
||||||
specific levels.
|
specific levels.
|
||||||
The '*' key identifies the default format string.
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
fmt: A dictionary mapping logging levels to format strings.
|
||||||
|
The ``*`` key identifies the default format string.
|
||||||
|
datefmt: As per py:class:`logging.Formatter`
|
||||||
|
style: As per py:class:`logging.Formatter`
|
||||||
|
|
||||||
>>> import sys
|
>>> import sys
|
||||||
>>> handler = logging.StreamHandler(sys.stdout)
|
>>> handler = logging.StreamHandler(sys.stdout)
|
||||||
@ -83,46 +87,48 @@ class LevelFormatter(logging.Formatter):
|
|||||||
|
|
||||||
|
|
||||||
def configLogger(**kwargs):
|
def configLogger(**kwargs):
|
||||||
""" Do basic configuration for the logging system. This is more or less
|
"""A more sophisticated logging system configuation manager.
|
||||||
the same as logging.basicConfig with some additional options and defaults.
|
|
||||||
|
|
||||||
The default behaviour is to create a StreamHandler which writes to
|
This is more or less the same as :py:func:`logging.basicConfig`,
|
||||||
sys.stderr, set a formatter using the DEFAULT_FORMATS strings, and add
|
with some additional options and defaults.
|
||||||
|
|
||||||
|
The default behaviour is to create a ``StreamHandler`` which writes to
|
||||||
|
sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
|
||||||
the handler to the top-level library logger ("fontTools").
|
the handler to the top-level library logger ("fontTools").
|
||||||
|
|
||||||
A number of optional keyword arguments may be specified, which can alter
|
A number of optional keyword arguments may be specified, which can alter
|
||||||
the default behaviour.
|
the default behaviour.
|
||||||
|
|
||||||
logger Specifies the logger name or a Logger instance to be configured.
|
Args:
|
||||||
(it defaults to "fontTools" logger). Unlike basicConfig, this
|
|
||||||
function can be called multiple times to reconfigure a logger.
|
logger: Specifies the logger name or a Logger instance to be
|
||||||
If the logger or any of its children already exists before the
|
configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
|
||||||
call is made, they will be reset before the new configuration
|
this function can be called multiple times to reconfigure a logger.
|
||||||
is applied.
|
If the logger or any of its children already exists before the call is
|
||||||
filename Specifies that a FileHandler be created, using the specified
|
made, they will be reset before the new configuration is applied.
|
||||||
filename, rather than a StreamHandler.
|
filename: Specifies that a ``FileHandler`` be created, using the
|
||||||
filemode Specifies the mode to open the file, if filename is specified
|
specified filename, rather than a ``StreamHandler``.
|
||||||
(if filemode is unspecified, it defaults to 'a').
|
filemode: Specifies the mode to open the file, if filename is
|
||||||
format Use the specified format string for the handler. This argument
|
specified. (If filemode is unspecified, it defaults to ``a``).
|
||||||
also accepts a dictionary of format strings keyed by level name,
|
format: Use the specified format string for the handler. This
|
||||||
to allow customising the records appearance for specific levels.
|
argument also accepts a dictionary of format strings keyed by
|
||||||
The special '*' key is for 'any other' level.
|
level name, to allow customising the records appearance for
|
||||||
datefmt Use the specified date/time format.
|
specific levels. The special ``'*'`` key is for 'any other' level.
|
||||||
level Set the logger level to the specified level.
|
datefmt: Use the specified date/time format.
|
||||||
stream Use the specified stream to initialize the StreamHandler. Note
|
level: Set the logger level to the specified level.
|
||||||
that this argument is incompatible with 'filename' - if both
|
stream: Use the specified stream to initialize the StreamHandler. Note
|
||||||
are present, 'stream' is ignored.
|
that this argument is incompatible with ``filename`` - if both
|
||||||
handlers If specified, this should be an iterable of already created
|
are present, ``stream`` is ignored.
|
||||||
handlers, which will be added to the logger. Any handler
|
handlers: If specified, this should be an iterable of already created
|
||||||
in the list which does not have a formatter assigned will be
|
handlers, which will be added to the logger. Any handler in the
|
||||||
assigned the formatter created in this function.
|
list which does not have a formatter assigned will be assigned the
|
||||||
filters If specified, this should be an iterable of already created
|
formatter created in this function.
|
||||||
filters, which will be added to the handler(s), if the latter
|
filters: If specified, this should be an iterable of already created
|
||||||
do(es) not already have filters assigned.
|
filters. If the ``handlers`` do not already have filters assigned,
|
||||||
propagate All loggers have a "propagate" attribute initially set to True,
|
these filters will be added to them.
|
||||||
which determines whether to continue searching for handlers up
|
propagate: All loggers have a ``propagate`` attribute which determines
|
||||||
the logging hierarchy. By default, this arguments sets the
|
whether to continue searching for handlers up the logging hierarchy.
|
||||||
"propagate" attribute to False.
|
If not provided, the "propagate" attribute will be set to ``False``.
|
||||||
"""
|
"""
|
||||||
# using kwargs to enforce keyword-only arguments in py2.
|
# using kwargs to enforce keyword-only arguments in py2.
|
||||||
handlers = kwargs.pop("handlers", None)
|
handlers = kwargs.pop("handlers", None)
|
||||||
@ -382,9 +388,11 @@ class Timer(object):
|
|||||||
|
|
||||||
|
|
||||||
class ChannelsFilter(logging.Filter):
|
class ChannelsFilter(logging.Filter):
|
||||||
""" Filter out records emitted from a list of enabled channel names,
|
"""Provides a hierarchical filter for log entries based on channel names.
|
||||||
including their children. It works the same as the logging.Filter class,
|
|
||||||
but allows to specify multiple channel names.
|
Filters out records emitted from a list of enabled channel names,
|
||||||
|
including their children. It works the same as the ``logging.Filter``
|
||||||
|
class, but allows the user to specify multiple channel names.
|
||||||
|
|
||||||
>>> import sys
|
>>> import sys
|
||||||
>>> handler = logging.StreamHandler(sys.stdout)
|
>>> handler = logging.StreamHandler(sys.stdout)
|
||||||
@ -469,10 +477,12 @@ class CapturingLogHandler(logging.Handler):
|
|||||||
|
|
||||||
class LogMixin(object):
|
class LogMixin(object):
|
||||||
""" Mixin class that adds logging functionality to another class.
|
""" Mixin class that adds logging functionality to another class.
|
||||||
You can define a new class that subclasses from LogMixin as well as
|
|
||||||
|
You can define a new class that subclasses from ``LogMixin`` as well as
|
||||||
other base classes through multiple inheritance.
|
other base classes through multiple inheritance.
|
||||||
All instances of that class will have a 'log' property that returns
|
All instances of that class will have a ``log`` property that returns
|
||||||
a logging.Logger named after their respective <module>.<class>.
|
a ``logging.Logger`` named after their respective ``<module>.<class>``.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
>>> class BaseClass(object):
|
>>> class BaseClass(object):
|
||||||
|
@ -17,6 +17,16 @@ def _reverseString(s):
|
|||||||
|
|
||||||
|
|
||||||
def getMacCreatorAndType(path):
|
def getMacCreatorAndType(path):
|
||||||
|
"""Returns file creator and file type codes for a path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): A file path.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple of two :py:class:`fontTools.py23.Tag` objects, the first
|
||||||
|
representing the file creator and the second representing the
|
||||||
|
file type.
|
||||||
|
"""
|
||||||
if xattr is not None:
|
if xattr is not None:
|
||||||
try:
|
try:
|
||||||
finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo')
|
finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo')
|
||||||
@ -40,6 +50,17 @@ def getMacCreatorAndType(path):
|
|||||||
|
|
||||||
|
|
||||||
def setMacCreatorAndType(path, fileCreator, fileType):
|
def setMacCreatorAndType(path, fileCreator, fileType):
|
||||||
|
"""Set file creator and file type codes for a path.
|
||||||
|
|
||||||
|
Note that if the ``xattr`` module is not installed, no action is
|
||||||
|
taken but no error is raised.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): A file path.
|
||||||
|
fileCreator: A four-character file creator tag.
|
||||||
|
fileType: A four-character file type tag.
|
||||||
|
|
||||||
|
"""
|
||||||
if xattr is not None:
|
if xattr is not None:
|
||||||
from fontTools.misc.textTools import pad
|
from fontTools.misc.textTools import pad
|
||||||
if not all(len(s) == 4 for s in (fileCreator, fileType)):
|
if not all(len(s) == 4 for s in (fileCreator, fileType)):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
""" Tools for reading Mac resource forks. """
|
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
import struct
|
import struct
|
||||||
from fontTools.misc import sstruct
|
from fontTools.misc import sstruct
|
||||||
@ -11,8 +10,25 @@ class ResourceError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class ResourceReader(MutableMapping):
|
class ResourceReader(MutableMapping):
|
||||||
|
"""Reader for Mac OS resource forks.
|
||||||
|
|
||||||
|
Parses a resource fork and returns resources according to their type.
|
||||||
|
If run on OS X, this will open the resource fork in the filesystem.
|
||||||
|
Otherwise, it will open the file itself and attempt to read it as
|
||||||
|
though it were a resource fork.
|
||||||
|
|
||||||
|
The returned object can be indexed by type and iterated over,
|
||||||
|
returning in each case a list of py:class:`Resource` objects
|
||||||
|
representing all the resources of a certain type.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, fileOrPath):
|
def __init__(self, fileOrPath):
|
||||||
|
"""Open a file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fileOrPath: Either an object supporting a ``read`` method, an
|
||||||
|
``os.PathLike`` object, or a string.
|
||||||
|
"""
|
||||||
self._resources = OrderedDict()
|
self._resources = OrderedDict()
|
||||||
if hasattr(fileOrPath, 'read'):
|
if hasattr(fileOrPath, 'read'):
|
||||||
self.file = fileOrPath
|
self.file = fileOrPath
|
||||||
@ -121,6 +137,7 @@ class ResourceReader(MutableMapping):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def types(self):
|
def types(self):
|
||||||
|
"""A list of the types of resources in the resource fork."""
|
||||||
return list(self._resources.keys())
|
return list(self._resources.keys())
|
||||||
|
|
||||||
def countResources(self, resType):
|
def countResources(self, resType):
|
||||||
@ -131,6 +148,7 @@ class ResourceReader(MutableMapping):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def getIndices(self, resType):
|
def getIndices(self, resType):
|
||||||
|
"""Returns a list of indices of resources of a given type."""
|
||||||
numRes = self.countResources(resType)
|
numRes = self.countResources(resType)
|
||||||
if numRes:
|
if numRes:
|
||||||
return list(range(1, numRes+1))
|
return list(range(1, numRes+1))
|
||||||
@ -167,6 +185,15 @@ class ResourceReader(MutableMapping):
|
|||||||
|
|
||||||
|
|
||||||
class Resource(object):
|
class Resource(object):
|
||||||
|
"""Represents a resource stored within a resource fork.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
type: resource type.
|
||||||
|
data: resource data.
|
||||||
|
id: ID.
|
||||||
|
name: resource name.
|
||||||
|
attr: attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, resType=None, resData=None, resID=None, resName=None,
|
def __init__(self, resType=None, resData=None, resID=None, resName=None,
|
||||||
resAttr=None):
|
resAttr=None):
|
||||||
|
@ -91,8 +91,13 @@ def _encode_base64(data, maxlinelength=76, indent_level=1):
|
|||||||
|
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
"""Wrapper for binary data returned in place of the built-in bytes type
|
"""Represents binary data when ``use_builtin_types=False.``
|
||||||
when loading property list data with use_builtin_types=False.
|
|
||||||
|
This class wraps binary data loaded from a plist file when the
|
||||||
|
``use_builtin_types`` argument to the loading function (:py:func:`fromtree`,
|
||||||
|
:py:func:`load`, :py:func:`loads`) is false.
|
||||||
|
|
||||||
|
The actual binary data is retrieved using the ``data`` attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
@ -395,6 +400,31 @@ def totree(
|
|||||||
pretty_print=True,
|
pretty_print=True,
|
||||||
indent_level=1,
|
indent_level=1,
|
||||||
):
|
):
|
||||||
|
"""Convert a value derived from a plist into an XML tree.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Any kind of value to be serialized to XML.
|
||||||
|
sort_keys: Whether keys of dictionaries should be sorted.
|
||||||
|
skipkeys (bool): Whether to silently skip non-string dictionary
|
||||||
|
keys.
|
||||||
|
use_builtin_types (bool): If true, byte strings will be
|
||||||
|
encoded in Base-64 and wrapped in a ``data`` tag; if
|
||||||
|
false, they will be either stored as ASCII strings or an
|
||||||
|
exception raised if they cannot be decoded as such. Defaults
|
||||||
|
to ``True`` if not present. Deprecated.
|
||||||
|
pretty_print (bool): Whether to indent the output.
|
||||||
|
indent_level (int): Level of indentation when serializing.
|
||||||
|
|
||||||
|
Returns: an ``etree`` ``Element`` object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
``TypeError``
|
||||||
|
if non-string dictionary keys are serialized
|
||||||
|
and ``skipkeys`` is false.
|
||||||
|
``ValueError``
|
||||||
|
if non-ASCII binary data is present
|
||||||
|
and `use_builtin_types` is false.
|
||||||
|
"""
|
||||||
if use_builtin_types is None:
|
if use_builtin_types is None:
|
||||||
use_builtin_types = USE_BUILTIN_TYPES
|
use_builtin_types = USE_BUILTIN_TYPES
|
||||||
else:
|
else:
|
||||||
@ -410,6 +440,17 @@ def totree(
|
|||||||
|
|
||||||
|
|
||||||
def fromtree(tree, use_builtin_types=None, dict_type=dict):
|
def fromtree(tree, use_builtin_types=None, dict_type=dict):
|
||||||
|
"""Convert an XML tree to a plist structure.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tree: An ``etree`` ``Element``.
|
||||||
|
use_builtin_types: If True, binary data is deserialized to
|
||||||
|
bytes strings. If False, it is wrapped in :py:class:`Data`
|
||||||
|
objects. Defaults to True if not provided. Deprecated.
|
||||||
|
dict_type: What type to use for dictionaries.
|
||||||
|
|
||||||
|
Returns: An object (usually a dictionary).
|
||||||
|
"""
|
||||||
target = PlistTarget(
|
target = PlistTarget(
|
||||||
use_builtin_types=use_builtin_types, dict_type=dict_type
|
use_builtin_types=use_builtin_types, dict_type=dict_type
|
||||||
)
|
)
|
||||||
@ -429,6 +470,20 @@ def fromtree(tree, use_builtin_types=None, dict_type=dict):
|
|||||||
|
|
||||||
|
|
||||||
def load(fp, use_builtin_types=None, dict_type=dict):
|
def load(fp, use_builtin_types=None, dict_type=dict):
|
||||||
|
"""Load a plist file into an object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fp: An opened file.
|
||||||
|
use_builtin_types: If True, binary data is deserialized to
|
||||||
|
bytes strings. If False, it is wrapped in :py:class:`Data`
|
||||||
|
objects. Defaults to True if not provided. Deprecated.
|
||||||
|
dict_type: What type to use for dictionaries.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An object (usually a dictionary) representing the top level of
|
||||||
|
the plist file.
|
||||||
|
"""
|
||||||
|
|
||||||
if not hasattr(fp, "read"):
|
if not hasattr(fp, "read"):
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"'%s' object has no attribute 'read'" % type(fp).__name__
|
"'%s' object has no attribute 'read'" % type(fp).__name__
|
||||||
@ -447,6 +502,20 @@ def load(fp, use_builtin_types=None, dict_type=dict):
|
|||||||
|
|
||||||
|
|
||||||
def loads(value, use_builtin_types=None, dict_type=dict):
|
def loads(value, use_builtin_types=None, dict_type=dict):
|
||||||
|
"""Load a plist file from a string into an object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: A string containing a plist.
|
||||||
|
use_builtin_types: If True, binary data is deserialized to
|
||||||
|
bytes strings. If False, it is wrapped in :py:class:`Data`
|
||||||
|
objects. Defaults to True if not provided. Deprecated.
|
||||||
|
dict_type: What type to use for dictionaries.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An object (usually a dictionary) representing the top level of
|
||||||
|
the plist file.
|
||||||
|
"""
|
||||||
|
|
||||||
fp = BytesIO(value)
|
fp = BytesIO(value)
|
||||||
return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)
|
return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)
|
||||||
|
|
||||||
@ -459,6 +528,30 @@ def dump(
|
|||||||
use_builtin_types=None,
|
use_builtin_types=None,
|
||||||
pretty_print=True,
|
pretty_print=True,
|
||||||
):
|
):
|
||||||
|
"""Write a Python object to a plist file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: An object to write.
|
||||||
|
fp: A file opened for writing.
|
||||||
|
sort_keys (bool): Whether keys of dictionaries should be sorted.
|
||||||
|
skipkeys (bool): Whether to silently skip non-string dictionary
|
||||||
|
keys.
|
||||||
|
use_builtin_types (bool): If true, byte strings will be
|
||||||
|
encoded in Base-64 and wrapped in a ``data`` tag; if
|
||||||
|
false, they will be either stored as ASCII strings or an
|
||||||
|
exception raised if they cannot be represented. Defaults
|
||||||
|
pretty_print (bool): Whether to indent the output.
|
||||||
|
indent_level (int): Level of indentation when serializing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
``TypeError``
|
||||||
|
if non-string dictionary keys are serialized
|
||||||
|
and ``skipkeys`` is false.
|
||||||
|
``ValueError``
|
||||||
|
if non-representable binary data is present
|
||||||
|
and `use_builtin_types` is false.
|
||||||
|
"""
|
||||||
|
|
||||||
if not hasattr(fp, "write"):
|
if not hasattr(fp, "write"):
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"'%s' object has no attribute 'write'" % type(fp).__name__
|
"'%s' object has no attribute 'write'" % type(fp).__name__
|
||||||
@ -493,6 +586,31 @@ def dumps(
|
|||||||
use_builtin_types=None,
|
use_builtin_types=None,
|
||||||
pretty_print=True,
|
pretty_print=True,
|
||||||
):
|
):
|
||||||
|
"""Write a Python object to a string in plist format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: An object to write.
|
||||||
|
sort_keys (bool): Whether keys of dictionaries should be sorted.
|
||||||
|
skipkeys (bool): Whether to silently skip non-string dictionary
|
||||||
|
keys.
|
||||||
|
use_builtin_types (bool): If true, byte strings will be
|
||||||
|
encoded in Base-64 and wrapped in a ``data`` tag; if
|
||||||
|
false, they will be either stored as strings or an
|
||||||
|
exception raised if they cannot be represented. Defaults
|
||||||
|
pretty_print (bool): Whether to indent the output.
|
||||||
|
indent_level (int): Level of indentation when serializing.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: A plist representation of the Python object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
``TypeError``
|
||||||
|
if non-string dictionary keys are serialized
|
||||||
|
and ``skipkeys`` is false.
|
||||||
|
``ValueError``
|
||||||
|
if non-representable binary data is present
|
||||||
|
and `use_builtin_types` is false.
|
||||||
|
"""
|
||||||
fp = BytesIO()
|
fp = BytesIO()
|
||||||
dump(
|
dump(
|
||||||
value,
|
value,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user