Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-FontTools for
openSUSE:Factory checked in at 2022-03-11 21:39:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-FontTools (Old)
and /work/SRC/openSUSE:Factory/.python-FontTools.new.25692 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-FontTools"
Fri Mar 11 21:39:51 2022 rev:12 rq:960245 version:4.29.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-FontTools/python-FontTools.changes
2022-01-17 22:33:59.426241527 +0100
+++
/work/SRC/openSUSE:Factory/.python-FontTools.new.25692/python-FontTools.changes
2022-03-11 21:39:51.722009574 +0100
@@ -1,0 +2,22 @@
+Mon Mar 7 10:28:35 UTC 2022 - Ben Greiner <[email protected]>
+
+- Update to 4.29.1
+ * [colorLib] Fixed rounding issue with radial gradient's start/end
+ circles inside one another (#2521).
+ * [freetypePen] Handle rotate/skew transform when auto-computing
+ width/height of the buffer; raise PenError wen missing moveTo
+ (#2517)
+- Release 4.29.0
+ * [ufoLib] Fixed illegal characters and expanded reserved
+ filenames (#2506).
+ * [COLRv1] Don't emit useless PaintColrLayers of lenght=1 in
+ LayerListBuilder (#2513).
+ * [ttx] Removed legacy waitForKeyPress method on Windows (#2509).
+ * [pens] Added FreeTypePen that uses freetype-py and the pen
+ protocol for rasterizating outline paths (#2494).
+ * [unicodedata] Updated the script direction list to Unicode 14.0
+ (#2484).
+ * Bumped unicodedata2 dependency to 14.0 (#2499).
+ * [psLib] Fixed type of fontName in suckfont (#2496).
+
+-------------------------------------------------------------------
Old:
----
fonttools-4.28.5-gh.tar.gz
New:
----
fonttools-4.29.1-gh.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-FontTools.spec ++++++
--- /var/tmp/diff_new_pack.2qrLlm/_old 2022-03-11 21:39:52.342010015 +0100
+++ /var/tmp/diff_new_pack.2qrLlm/_new 2022-03-11 21:39:52.346010018 +0100
@@ -24,11 +24,10 @@
%define psuffix %{nil}
%bcond_with test
%endif
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%{?!python_module:%define python_module() python3-%{**}}
%define skip_python2 1
-%define skip_python36 1
Name: python-FontTools%{psuffix}
-Version: 4.28.5
+Version: 4.29.1
Release: 0
Summary: Suite of Tools and Libraries for Manipulating Fonts
License: MIT AND OFL-1.1
@@ -47,7 +46,7 @@
Recommends: python-lxml >= 4.0
Recommends: python-scipy >= 1.5.1
Recommends: python-sympy
-Recommends: python-unicodedata2 >= 13.0.0
+Recommends: python-unicodedata2 >= 14.0.0
Recommends: python-zopfli >= 0.1.6
Requires(post): update-alternatives
Requires(postun):update-alternatives
++++++ fonttools-4.28.5-gh.tar.gz -> fonttools-4.29.1-gh.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Doc/docs-requirements.txt
new/fonttools-4.29.1/Doc/docs-requirements.txt
--- old/fonttools-4.28.5/Doc/docs-requirements.txt 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Doc/docs-requirements.txt 2022-02-01
12:30:24.000000000 +0100
@@ -1,3 +1,4 @@
-sphinx==4.3.1
+sphinx==4.3.2
sphinx_rtd_theme==1.0.0
-reportlab==3.6.3
+reportlab==3.6.5
+freetype-py==2.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Doc/source/optional.rst
new/fonttools-4.29.1/Doc/source/optional.rst
--- old/fonttools-4.28.5/Doc/source/optional.rst 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Doc/source/optional.rst 2022-02-01
12:30:24.000000000 +0100
@@ -59,8 +59,8 @@
The??version included in there varies between different Python versions.
To use the latest available data, you can install:
-* `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__:
``unicodedata`` backport for Python 2.7
- and 3.x updated to the latest Unicode version 12.0. Note this is not
necessary if you use Python 3.8
+* `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__:
``unicodedata`` backport for Python
+ 3.x updated to the latest Unicode version 14.0. Note this is not necessary
if you use Python 3.11
as the latter already comes with an up-to-date ``unicodedata``.
*Extra:* ``unicode``
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Doc/source/pens/freetypePen.rst
new/fonttools-4.29.1/Doc/source/pens/freetypePen.rst
--- old/fonttools-4.28.5/Doc/source/pens/freetypePen.rst 1970-01-01
01:00:00.000000000 +0100
+++ new/fonttools-4.29.1/Doc/source/pens/freetypePen.rst 2022-02-01
12:30:24.000000000 +0100
@@ -0,0 +1,8 @@
+###########
+freetypePen
+###########
+
+.. automodule:: fontTools.pens.freetypePen
+ :inherited-members:
+ :members:
+ :undoc-members:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Doc/source/pens/index.rst
new/fonttools-4.29.1/Doc/source/pens/index.rst
--- old/fonttools-4.28.5/Doc/source/pens/index.rst 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Doc/source/pens/index.rst 2022-02-01
12:30:24.000000000 +0100
@@ -11,6 +11,7 @@
cocoaPen
cu2quPen
filterPen
+ freetypePen
momentsPen
perimeterPen
pointInsidePen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/__init__.py
new/fonttools-4.29.1/Lib/fontTools/__init__.py
--- old/fonttools-4.28.5/Lib/fontTools/__init__.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/__init__.py 2022-02-01
12:30:24.000000000 +0100
@@ -3,6 +3,6 @@
log = logging.getLogger(__name__)
-version = __version__ = "4.28.5"
+version = __version__ = "4.29.1"
__all__ = ["version", "log", "configLogger"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/colorLib/builder.py
new/fonttools-4.29.1/Lib/fontTools/colorLib/builder.py
--- old/fonttools-4.28.5/Lib/fontTools/colorLib/builder.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/colorLib/builder.py 2022-02-01
12:30:24.000000000 +0100
@@ -444,14 +444,12 @@
class LayerListBuilder:
- slices: List[ot.Paint]
layers: List[ot.Paint]
reusePool: Mapping[Tuple[Any, ...], int]
tuples: Mapping[int, Tuple[Any, ...]]
keepAlive: List[ot.Paint] # we need id to remain valid
def __init__(self):
- self.slices = []
self.layers = []
self.reusePool = {}
self.tuples = {}
@@ -496,10 +494,6 @@
# COLR layers is unusual in that it modifies shared state
# so we need a callback into an object
def _beforeBuildPaintColrLayers(self, dest, source):
- paint = ot.Paint()
- paint.Format = int(ot.PaintFormat.PaintColrLayers)
- self.slices.append(paint)
-
# Sketchy gymnastics: a sequence input will have dropped it's layers
# into NumLayers; get it back
if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
@@ -557,6 +551,12 @@
layers = [listToColrLayers(l) for l in layers]
+ # No reason to have a colr layers with just one entry
+ if len(layers) == 1:
+ return layers[0], {}
+
+ paint = ot.Paint()
+ paint.Format = int(ot.PaintFormat.PaintColrLayers)
paint.NumLayers = len(layers)
paint.FirstLayerIndex = len(self.layers)
self.layers.extend(layers)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/colorLib/geometry.py
new/fonttools-4.29.1/Lib/fontTools/colorLib/geometry.py
--- old/fonttools-4.28.5/Lib/fontTools/colorLib/geometry.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/colorLib/geometry.py 2022-02-01
12:30:24.000000000 +0100
@@ -1,6 +1,6 @@
"""Helpers for manipulating 2D points and vectors in COLR table."""
-from math import copysign, cos, hypot, pi
+from math import copysign, cos, hypot, isclose, pi
from fontTools.misc.roundTools import otRound
@@ -19,9 +19,7 @@
return (vec[0] / length, vec[1] / length)
-# This is the same tolerance used by Skia's SkTwoPointConicalGradient.cpp to
detect
-# when a radial gradient's focal point lies on the end circle.
-_NEARLY_ZERO = 1 / (1 << 12) # 0.000244140625
+_CIRCLE_INSIDE_TOLERANCE = 1e-4
# The unit vector's X and Y components are respectively
@@ -64,10 +62,10 @@
def round(self):
return Circle(_round_point(self.centre), otRound(self.radius))
- def inside(self, outer_circle):
+ def inside(self, outer_circle, tolerance=_CIRCLE_INSIDE_TOLERANCE):
dist = self.radius + hypot(*_vector_between(self.centre,
outer_circle.centre))
return (
- abs(outer_circle.radius - dist) <= _NEARLY_ZERO
+ isclose(outer_circle.radius, dist,
rel_tol=_CIRCLE_INSIDE_TOLERANCE)
or outer_circle.radius > dist
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/feaLib/__main__.py
new/fonttools-4.29.1/Lib/fontTools/feaLib/__main__.py
--- old/fonttools-4.28.5/Lib/fontTools/feaLib/__main__.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/feaLib/__main__.py 2022-02-01
12:30:24.000000000 +0100
@@ -12,7 +12,7 @@
def main(args=None):
- """Add features from a feature file (.fea) into a OTF font"""
+ """Add features from a feature file (.fea) into an OTF font"""
parser = argparse.ArgumentParser(
description="Use fontTools to compile OpenType feature files (*.fea)."
)
@@ -46,7 +46,7 @@
parser.add_argument(
"-v",
"--verbose",
- help="increase the logger verbosity. Multiple -v " "options are
allowed.",
+ help="Increase the logger verbosity. Multiple -v " "options are
allowed.",
action="count",
default=0,
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/misc/psLib.py
new/fonttools-4.29.1/Lib/fontTools/misc/psLib.py
--- old/fonttools-4.28.5/Lib/fontTools/misc/psLib.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/misc/psLib.py 2022-02-01
12:30:24.000000000 +0100
@@ -365,6 +365,7 @@
m = re.search(br"/FontName\s+/([^ \t\n\r]+)\s+def", data)
if m:
fontName = m.group(1)
+ fontName = fontName.decode()
else:
fontName = None
interpreter = PSInterpreter(encoding=encoding)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/pens/freetypePen.py
new/fonttools-4.29.1/Lib/fontTools/pens/freetypePen.py
--- old/fonttools-4.28.5/Lib/fontTools/pens/freetypePen.py 1970-01-01
01:00:00.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/pens/freetypePen.py 2022-02-01
12:30:24.000000000 +0100
@@ -0,0 +1,458 @@
+# -*- coding: utf-8 -*-
+
+"""Pen to rasterize paths with FreeType."""
+
+__all__ = ["FreeTypePen"]
+
+import os
+import ctypes
+import platform
+import subprocess
+import collections
+import math
+
+import freetype
+from freetype.raw import FT_Outline_Get_Bitmap, FT_Outline_Get_BBox,
FT_Outline_Get_CBox
+from freetype.ft_types import FT_Pos
+from freetype.ft_structs import FT_Vector, FT_BBox, FT_Bitmap, FT_Outline
+from freetype.ft_enums import (
+ FT_OUTLINE_NONE,
+ FT_OUTLINE_EVEN_ODD_FILL,
+ FT_PIXEL_MODE_GRAY,
+ FT_CURVE_TAG_ON,
+ FT_CURVE_TAG_CONIC,
+ FT_CURVE_TAG_CUBIC,
+)
+from freetype.ft_errors import FT_Exception
+
+from fontTools.pens.basePen import BasePen, PenError
+from fontTools.misc.roundTools import otRound
+from fontTools.misc.transform import Transform
+
+Contour = collections.namedtuple("Contour", ("points", "tags"))
+
+
+class FreeTypePen(BasePen):
+ """Pen to rasterize paths with FreeType. Requires `freetype-py` module.
+
+ Constructs ``FT_Outline`` from the paths, and renders it within a bitmap
+ buffer.
+
+ For ``array()`` and ``show()``, `numpy` and `matplotlib` must be installed.
+ For ``image()``, `Pillow` is required. Each module is lazily loaded when
the
+ corresponding method is called.
+
+ Args:
+ glyphSet: a dictionary of drawable glyph objects keyed by name
+ used to resolve component references in composite glyphs.
+
+ :Examples:
+ If `numpy` and `matplotlib` is available, the following code will
+ show the glyph image of `fi` in a new window::
+
+ from fontTools.ttLib import TTFont
+ from fontTools.pens.freetypePen import FreeTypePen
+ from fontTools.misc.transform import Offset
+ pen = FreeTypePen(None)
+ font = TTFont('SourceSansPro-Regular.otf')
+ glyph = font.getGlyphSet()['fi']
+ glyph.draw(pen)
+ width, ascender, descender = glyph.width,
font['OS/2'].usWinAscent, -font['OS/2'].usWinDescent
+ height = ascender - descender
+ pen.show(width=width, height=height, transform=Offset(0,
-descender))
+
+ Combining with `uharfbuzz`, you can typeset a chunk of glyphs in a
pen::
+
+ import uharfbuzz as hb
+ from fontTools.pens.freetypePen import FreeTypePen
+ from fontTools.pens.transformPen import TransformPen
+ from fontTools.misc.transform import Offset
+
+ en1, en2, ar, ja = 'Typesetting', 'Jeff', '???? ????????????',
'??????????????????'
+ for text, font_path, direction, typo_ascender, typo_descender,
vhea_ascender, vhea_descender, contain, features in (
+ (en1, 'NotoSans-Regular.ttf', 'ltr', 2189, -600, None,
None, False, {"kern": True, "liga": True}),
+ (en2, 'NotoSans-Regular.ttf', 'ltr', 2189, -600, None,
None, True, {"kern": True, "liga": True}),
+ (ar, 'NotoSansArabic-Regular.ttf', 'rtl', 1374, -738, None,
None, False, {"kern": True, "liga": True}),
+ (ja, 'NotoSansJP-Regular.otf', 'ltr', 880, -120, 500,
-500, False, {"palt": True, "kern": True}),
+ (ja, 'NotoSansJP-Regular.otf', 'ttb', 880, -120, 500,
-500, False, {"vert": True, "vpal": True, "vkrn": True})
+ ):
+ blob = hb.Blob.from_file_path(font_path)
+ face = hb.Face(blob)
+ font = hb.Font(face)
+ buf = hb.Buffer()
+ buf.direction = direction
+ buf.add_str(text)
+ buf.guess_segment_properties()
+ hb.shape(font, buf, features)
+
+ x, y = 0, 0
+ pen = FreeTypePen(None)
+ for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
+ gid = info.codepoint
+ transformed = TransformPen(pen, Offset(x + pos.x_offset, y
+ pos.y_offset))
+ font.draw_glyph_with_pen(gid, transformed)
+ x += pos.x_advance
+ y += pos.y_advance
+
+ offset, width, height = None, None, None
+ if direction in ('ltr', 'rtl'):
+ offset = (0, -typo_descender)
+ width = x
+ height = typo_ascender - typo_descender
+ else:
+ offset = (-vhea_descender, -y)
+ width = vhea_ascender - vhea_descender
+ height = -y
+ pen.show(width=width, height=height,
transform=Offset(*offset), contain=contain)
+
+ For Jupyter Notebook, the rendered image will be displayed in a cell if
+ you replace ``show()`` with ``image()`` in the examples.
+ """
+
+ def __init__(self, glyphSet):
+ BasePen.__init__(self, glyphSet)
+ self.contours = []
+
+ def outline(self, transform=None, evenOdd=False):
+ """Converts the current contours to ``FT_Outline``.
+
+ Args:
+ transform: An optional 6-tuple containing an affine transformation,
+ or a ``Transform`` object from the ``fontTools.misc.transform``
+ module.
+ evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
+ """
+ transform = transform or Transform()
+ if not hasattr(transform, "transformPoint"):
+ transform = Transform(*transform)
+ n_contours = len(self.contours)
+ n_points = sum((len(contour.points) for contour in self.contours))
+ points = []
+ for contour in self.contours:
+ for point in contour.points:
+ point = transform.transformPoint(point)
+ points.append(
+ FT_Vector(
+ FT_Pos(otRound(point[0] * 64)),
FT_Pos(otRound(point[1] * 64))
+ )
+ )
+ tags = []
+ for contour in self.contours:
+ for tag in contour.tags:
+ tags.append(tag)
+ contours = []
+ contours_sum = 0
+ for contour in self.contours:
+ contours_sum += len(contour.points)
+ contours.append(contours_sum - 1)
+ flags = FT_OUTLINE_EVEN_ODD_FILL if evenOdd else FT_OUTLINE_NONE
+ return FT_Outline(
+ (ctypes.c_short)(n_contours),
+ (ctypes.c_short)(n_points),
+ (FT_Vector * n_points)(*points),
+ (ctypes.c_ubyte * n_points)(*tags),
+ (ctypes.c_short * n_contours)(*contours),
+ (ctypes.c_int)(flags),
+ )
+
+ def buffer(
+ self, width=None, height=None, transform=None, contain=False,
evenOdd=False
+ ):
+ """Renders the current contours within a bitmap buffer.
+
+ Args:
+ width: Image width of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ height: Image height of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ transform: An optional 6-tuple containing an affine transformation,
+ or a ``Transform`` object from the ``fontTools.misc.transform``
+ module. The bitmap size is not affected by this matrix.
+ contain: If ``True``, the image size will be automatically expanded
+ so that it fits to the bounding box of the paths. Useful for
+ rendering glyphs with negative sidebearings without clipping.
+ evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
+
+ Returns:
+ A tuple of ``(buffer, size)``, where ``buffer`` is a ``bytes``
+ object of the resulted bitmap and ``size`` is a 2-tuple of its
+ dimension.
+
+ :Notes:
+ The image size should always be given explicitly if you need to get
+ a proper glyph image. When ``width`` and ``height`` are omitted, it
+ forcifully fits to the bounding box and the side bearings get
+ cropped. If you pass ``0`` to both ``width`` and ``height`` and set
+ ``contain`` to ``True``, it expands to the bounding box while
+ maintaining the origin of the contours, meaning that LSB will be
+ maintained but RSB won???t. The difference between the two becomes
+ more obvious when rotate or skew transformation is applied.
+
+ :Example:
+ .. code-block::
+
+ >> pen = FreeTypePen(None)
+ >> glyph.draw(pen)
+ >> buf, size = pen.buffer(width=500, height=1000)
+ >> type(buf), len(buf), size
+ (<class 'bytes'>, 500000, (500, 1000))
+
+ """
+ transform = transform or Transform()
+ if not hasattr(transform, "transformPoint"):
+ transform = Transform(*transform)
+ contain_x, contain_y = contain or width is None, contain or height is
None
+ if contain_x or contain_y:
+ dx, dy = transform.dx, transform.dy
+ bbox = self.bbox
+ p1, p2, p3, p4 = (
+ transform.transformPoint((bbox[0], bbox[1])),
+ transform.transformPoint((bbox[2], bbox[1])),
+ transform.transformPoint((bbox[0], bbox[3])),
+ transform.transformPoint((bbox[2], bbox[3])),
+ )
+ px, py = (p1[0], p2[0], p3[0], p4[0]), (p1[1], p2[1], p3[1], p4[1])
+ if contain_x:
+ if width is None:
+ dx = dx - min(*px)
+ width = max(*px) - min(*px)
+ else:
+ dx = dx - min(min(*px), 0.0)
+ width = max(width, max(*px) - min(min(*px), 0.0))
+ if contain_y:
+ if height is None:
+ dy = dy - min(*py)
+ height = max(*py) - min(*py)
+ else:
+ dy = dy - min(min(*py), 0.0)
+ height = max(height, max(*py) - min(min(*py), 0.0))
+ transform = Transform(*transform[:4], dx, dy)
+ width, height = math.ceil(width), math.ceil(height)
+ buf = ctypes.create_string_buffer(width * height)
+ bitmap = FT_Bitmap(
+ (ctypes.c_int)(height),
+ (ctypes.c_int)(width),
+ (ctypes.c_int)(width),
+ (ctypes.POINTER(ctypes.c_ubyte))(buf),
+ (ctypes.c_short)(256),
+ (ctypes.c_ubyte)(FT_PIXEL_MODE_GRAY),
+ (ctypes.c_char)(0),
+ (ctypes.c_void_p)(None),
+ )
+ outline = self.outline(transform=transform, evenOdd=evenOdd)
+ err = FT_Outline_Get_Bitmap(
+ freetype.get_handle(), ctypes.byref(outline), ctypes.byref(bitmap)
+ )
+ if err != 0:
+ raise FT_Exception(err)
+ return buf.raw, (width, height)
+
+ def array(
+ self, width=None, height=None, transform=None, contain=False,
evenOdd=False
+ ):
+ """Returns the rendered contours as a numpy array. Requires `numpy`.
+
+ Args:
+ width: Image width of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ height: Image height of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ transform: An optional 6-tuple containing an affine transformation,
+ or a ``Transform`` object from the ``fontTools.misc.transform``
+ module. The bitmap size is not affected by this matrix.
+ contain: If ``True``, the image size will be automatically expanded
+ so that it fits to the bounding box of the paths. Useful for
+ rendering glyphs with negative sidebearings without clipping.
+ evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
+
+ Returns:
+ A ``numpy.ndarray`` object with a shape of ``(height, width)``.
+ Each element takes a value in the range of ``[0.0, 1.0]``.
+
+ :Notes:
+ The image size should always be given explicitly if you need to get
+ a proper glyph image. When ``width`` and ``height`` are omitted, it
+ forcifully fits to the bounding box and the side bearings get
+ cropped. If you pass ``0`` to both ``width`` and ``height`` and set
+ ``contain`` to ``True``, it expands to the bounding box while
+ maintaining the origin of the contours, meaning that LSB will be
+ maintained but RSB won???t. The difference between the two becomes
+ more obvious when rotate or skew transformation is applied.
+
+ :Example:
+ .. code-block::
+
+ >> pen = FreeTypePen(None)
+ >> glyph.draw(pen)
+ >> arr = pen.array(width=500, height=1000)
+ >> type(a), a.shape
+ (<class 'numpy.ndarray'>, (1000, 500))
+ """
+ import numpy as np
+
+ buf, size = self.buffer(
+ width=width,
+ height=height,
+ transform=transform,
+ contain=contain,
+ evenOdd=evenOdd,
+ )
+ return np.frombuffer(buf, "B").reshape((size[1], size[0])) / 255.0
+
+ def show(
+ self, width=None, height=None, transform=None, contain=False,
evenOdd=False
+ ):
+ """Plots the rendered contours with `pyplot`. Requires `numpy` and
+ `matplotlib`.
+
+ Args:
+ width: Image width of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ height: Image height of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ transform: An optional 6-tuple containing an affine transformation,
+ or a ``Transform`` object from the ``fontTools.misc.transform``
+ module. The bitmap size is not affected by this matrix.
+ contain: If ``True``, the image size will be automatically expanded
+ so that it fits to the bounding box of the paths. Useful for
+ rendering glyphs with negative sidebearings without clipping.
+ evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
+
+ :Notes:
+ The image size should always be given explicitly if you need to get
+ a proper glyph image. When ``width`` and ``height`` are omitted, it
+ forcifully fits to the bounding box and the side bearings get
+ cropped. If you pass ``0`` to both ``width`` and ``height`` and set
+ ``contain`` to ``True``, it expands to the bounding box while
+ maintaining the origin of the contours, meaning that LSB will be
+ maintained but RSB won???t. The difference between the two becomes
+ more obvious when rotate or skew transformation is applied.
+
+ :Example:
+ .. code-block::
+
+ >> pen = FreeTypePen(None)
+ >> glyph.draw(pen)
+ >> pen.show(width=500, height=1000)
+ """
+ from matplotlib import pyplot as plt
+
+ a = self.array(
+ width=width,
+ height=height,
+ transform=transform,
+ contain=contain,
+ evenOdd=evenOdd,
+ )
+ plt.imshow(a, cmap="gray_r", vmin=0, vmax=1)
+ plt.show()
+
+ def image(
+ self, width=None, height=None, transform=None, contain=False,
evenOdd=False
+ ):
+ """Returns the rendered contours as a PIL image. Requires `Pillow`.
+ Can be used to display a glyph image in Jupyter Notebook.
+
+ Args:
+ width: Image width of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ height: Image height of the bitmap in pixels. If omitted, it
+ automatically fits to the bounding box of the contours.
+ transform: An optional 6-tuple containing an affine transformation,
+ or a ``Transform`` object from the ``fontTools.misc.transform``
+ module. The bitmap size is not affected by this matrix.
+ contain: If ``True``, the image size will be automatically expanded
+ so that it fits to the bounding box of the paths. Useful for
+ rendering glyphs with negative sidebearings without clipping.
+ evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
+
+ Returns:
+ A ``PIL.image`` object. The image is filled in black with alpha
+ channel obtained from the rendered bitmap.
+
+ :Notes:
+ The image size should always be given explicitly if you need to get
+ a proper glyph image. When ``width`` and ``height`` are omitted, it
+ forcifully fits to the bounding box and the side bearings get
+ cropped. If you pass ``0`` to both ``width`` and ``height`` and set
+ ``contain`` to ``True``, it expands to the bounding box while
+ maintaining the origin of the contours, meaning that LSB will be
+ maintained but RSB won???t. The difference between the two becomes
+ more obvious when rotate or skew transformation is applied.
+
+ :Example:
+ .. code-block::
+
+ >> pen = FreeTypePen(None)
+ >> glyph.draw(pen)
+ >> img = pen.image(width=500, height=1000)
+ >> type(img), img.size
+ (<class 'PIL.Image.Image'>, (500, 1000))
+ """
+ from PIL import Image
+
+ buf, size = self.buffer(
+ width=width,
+ height=height,
+ transform=transform,
+ contain=contain,
+ evenOdd=evenOdd,
+ )
+ img = Image.new("L", size, 0)
+ img.putalpha(Image.frombuffer("L", size, buf))
+ return img
+
+ @property
+ def bbox(self):
+ """Computes the exact bounding box of an outline.
+
+ Returns:
+ A tuple of ``(xMin, yMin, xMax, yMax)``.
+ """
+ bbox = FT_BBox()
+ outline = self.outline()
+ FT_Outline_Get_BBox(ctypes.byref(outline), ctypes.byref(bbox))
+ return (bbox.xMin / 64.0, bbox.yMin / 64.0, bbox.xMax / 64.0,
bbox.yMax / 64.0)
+
+ @property
+ def cbox(self):
+ """Returns an outline's ???control box???.
+
+ Returns:
+ A tuple of ``(xMin, yMin, xMax, yMax)``.
+ """
+ cbox = FT_BBox()
+ outline = self.outline()
+ FT_Outline_Get_CBox(ctypes.byref(outline), ctypes.byref(cbox))
+ return (cbox.xMin / 64.0, cbox.yMin / 64.0, cbox.xMax / 64.0,
cbox.yMax / 64.0)
+
+ def _moveTo(self, pt):
+ contour = Contour([], [])
+ self.contours.append(contour)
+ contour.points.append(pt)
+ contour.tags.append(FT_CURVE_TAG_ON)
+
+ def _lineTo(self, pt):
+ if not (self.contours and len(self.contours[-1].points) > 0):
+ raise PenError("Contour missing required initial moveTo")
+ contour = self.contours[-1]
+ contour.points.append(pt)
+ contour.tags.append(FT_CURVE_TAG_ON)
+
+ def _curveToOne(self, p1, p2, p3):
+ if not (self.contours and len(self.contours[-1].points) > 0):
+ raise PenError("Contour missing required initial moveTo")
+ t1, t2, t3 = FT_CURVE_TAG_CUBIC, FT_CURVE_TAG_CUBIC, FT_CURVE_TAG_ON
+ contour = self.contours[-1]
+ for p, t in ((p1, t1), (p2, t2), (p3, t3)):
+ contour.points.append(p)
+ contour.tags.append(t)
+
+ def _qCurveToOne(self, p1, p2):
+ if not (self.contours and len(self.contours[-1].points) > 0):
+ raise PenError("Contour missing required initial moveTo")
+ t1, t2 = FT_CURVE_TAG_CONIC, FT_CURVE_TAG_ON
+ contour = self.contours[-1]
+ for p, t in ((p1, t1), (p2, t2)):
+ contour.points.append(p)
+ contour.tags.append(t)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/ttx.py
new/fonttools-4.29.1/Lib/fontTools/ttx.py
--- old/fonttools-4.28.5/Lib/fontTools/ttx.py 2021-12-19 11:49:06.000000000
+0100
+++ new/fonttools-4.29.1/Lib/fontTools/ttx.py 2022-02-01 12:30:24.000000000
+0100
@@ -387,15 +387,6 @@
action(input, output, options)
-def waitForKeyPress():
- """Force the DOS Prompt window to stay open so the user gets
- a chance to see what's wrong."""
- import msvcrt
- print('(Hit any key to exit)', file=sys.stderr)
- while not msvcrt.kbhit():
- pass
-
-
def main(args=None):
"""Convert OpenType fonts to XML and back"""
from fontTools import configLogger
@@ -416,16 +407,12 @@
log.error("(Cancelled.)")
sys.exit(1)
except SystemExit:
- if sys.platform == "win32":
- waitForKeyPress()
raise
except TTLibError as e:
log.error(e)
sys.exit(1)
except:
log.exception('Unhandled exception has occurred')
- if sys.platform == "win32":
- waitForKeyPress()
sys.exit(1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/ufoLib/filenames.py
new/fonttools-4.29.1/Lib/fontTools/ufoLib/filenames.py
--- old/fonttools-4.28.5/Lib/fontTools/ufoLib/filenames.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/ufoLib/filenames.py 2022-02-01
12:30:24.000000000 +0100
@@ -3,11 +3,88 @@
This was taken from the UFO 3 spec.
"""
-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(" ")
+# Restrictions are taken mostly from
+#
https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file#naming-conventions.
+#
+# 1. Integer value zero, sometimes referred to as the ASCII NUL character.
+# 2. Characters whose integer representations are in the range 1 to 31,
+# inclusive.
+# 3. Various characters that (mostly) Windows and POSIX-y filesystems don't
+# allow, plus "(" and ")", as per the specification.
+illegalCharacters = {
+ "\x00",
+ "\x01",
+ "\x02",
+ "\x03",
+ "\x04",
+ "\x05",
+ "\x06",
+ "\x07",
+ "\x08",
+ "\t",
+ "\n",
+ "\x0b",
+ "\x0c",
+ "\r",
+ "\x0e",
+ "\x0f",
+ "\x10",
+ "\x11",
+ "\x12",
+ "\x13",
+ "\x14",
+ "\x15",
+ "\x16",
+ "\x17",
+ "\x18",
+ "\x19",
+ "\x1a",
+ "\x1b",
+ "\x1c",
+ "\x1d",
+ "\x1e",
+ "\x1f",
+ '"',
+ "*",
+ "+",
+ "/",
+ ":",
+ "<",
+ ">",
+ "?",
+ "[",
+ "\\",
+ "]",
+ "(",
+ ")",
+ "|",
+ "\x7f",
+}
+reservedFileNames = {
+ "aux",
+ "clock$",
+ "com1",
+ "com2",
+ "com3",
+ "com4",
+ "com5",
+ "com6",
+ "com7",
+ "com8",
+ "com9",
+ "con",
+ "lpt1",
+ "lpt2",
+ "lpt3",
+ "lpt4",
+ "lpt5",
+ "lpt6",
+ "lpt7",
+ "lpt8",
+ "lpt9",
+ "nul",
+ "prn",
+}
maxFileNameLength = 255
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Lib/fontTools/unicodedata/OTTags.py
new/fonttools-4.29.1/Lib/fontTools/unicodedata/OTTags.py
--- old/fonttools-4.28.5/Lib/fontTools/unicodedata/OTTags.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/unicodedata/OTTags.py 2022-02-01
12:30:24.000000000 +0100
@@ -11,6 +11,10 @@
DEFAULT_SCRIPT = "DFLT"
+SCRIPT_ALIASES = {
+ "jamo": "hang",
+}
+
SCRIPT_EXCEPTIONS = {
"Hira": "kana",
"Hrkt": "kana",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/fonttools-4.28.5/Lib/fontTools/unicodedata/__init__.py
new/fonttools-4.29.1/Lib/fontTools/unicodedata/__init__.py
--- old/fonttools-4.28.5/Lib/fontTools/unicodedata/__init__.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Lib/fontTools/unicodedata/__init__.py 2022-02-01
12:30:24.000000000 +0100
@@ -5,7 +5,7 @@
try:
# use unicodedata backport compatible with python2:
- # https://github.com/mikekap/unicodedata2
+ # https://github.com/fonttools/unicodedata2
from unicodedata2 import *
except ImportError: # pragma: no cover
# fall back to built-in unicodedata (possibly outdated)
@@ -134,8 +134,10 @@
return default
-# The data on script direction is taken from CLDR 37:
-#
https://github.com/unicode-org/cldr/blob/release-37/common/properties/scriptMetadata.txt
+# The data on script direction is taken from Harfbuzz source code:
+# https://github.com/harfbuzz/harfbuzz/blob/3.2.0/src/hb-common.cc#L514-L613
+# This in turn references the following "Script_Metadata" document:
+#
https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o
RTL_SCRIPTS = {
# Unicode-1.1 additions
'Arab', # Arabic
@@ -200,6 +202,9 @@
# Unicode-13.0 additions
'Chrs', # Chorasmian
'Yezi', # Yezidi
+
+ # Unicode-14.0 additions
+ 'Ougr', # Old Uyghur
}
def script_horizontal_direction(script_code, default=KeyError):
@@ -259,6 +264,9 @@
if not tag or " " in tag or len(tag) > 4:
raise ValueError("invalid OpenType tag: %r" % tag)
+ if tag in OTTags.SCRIPT_ALIASES:
+ tag = OTTags.SCRIPT_ALIASES[tag]
+
while len(tag) != 4:
tag += str(" ") # pad with spaces
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/NEWS.rst
new/fonttools-4.29.1/NEWS.rst
--- old/fonttools-4.28.5/NEWS.rst 2021-12-19 11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/NEWS.rst 2022-02-01 12:30:24.000000000 +0100
@@ -1,3 +1,23 @@
+4.29.1 (released 2022-02-01)
+----------------------------
+
+- [colorLib] Fixed rounding issue with radial gradient's start/end circles
inside
+ one another (#2521).
+- [freetypePen] Handle rotate/skew transform when auto-computing width/height
of the
+ buffer; raise PenError wen missing moveTo (#2517)
+
+4.29.0 (released 2022-01-24)
+----------------------------
+
+- [ufoLib] Fixed illegal characters and expanded reserved filenames (#2506).
+- [COLRv1] Don't emit useless PaintColrLayers of lenght=1 in LayerListBuilder
(#2513).
+- [ttx] Removed legacy ``waitForKeyPress`` method on Windows (#2509).
+- [pens] Added FreeTypePen that uses ``freetype-py`` and the pen protocol for
+ rasterizating outline paths (#2494).
+- [unicodedata] Updated the script direction list to Unicode 14.0 (#2484).
+ Bumped unicodedata2 dependency to 14.0 (#2499).
+- [psLib] Fixed type of ``fontName`` in ``suckfont`` (#2496).
+
4.28.5 (released 2021-12-19)
----------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/README.rst
new/fonttools-4.29.1/README.rst
--- old/fonttools-4.28.5/README.rst 2021-12-19 11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/README.rst 2022-02-01 12:30:24.000000000 +0100
@@ -119,8 +119,8 @@
To use the latest available data, you can install:
* `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__:
- ``unicodedata`` backport for Python 2.7 and 3.x updated to the latest
- Unicode version 12.0. Note this is not necessary if you use Python 3.8
+ ``unicodedata`` backport for Python 3.x updated to the latest Unicode
+ version 14.0. Note this is not necessary if you use Python 3.11
as the latter already comes with an up-to-date ``unicodedata``.
*Extra:* ``unicode``
@@ -199,6 +199,13 @@
* `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit
for generating PDFs and graphics.
+- ``Lib/fontTools/pens/freetypePen.py``
+
+ Pen to drawing glyphs with FreeType as raster images, requires:
+
+ * `freetype-py <https://pypi.python.org/pypi/freetype-py>`__: Python binding
+ for the FreeType library.
+
How to make a new release
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -238,9 +245,9 @@
Jelle Bosma, Sascha Brawer, Tom Byrer, Antonio Cavedoni, Fr??d??ric
Coiffier, Vincent Connare, David Corbett, Simon Cozens, Dave Crossland,
Simon Daniels, Peter Dekkers, Behdad Esfahbod, Behnam Esfahbod, Hannes
-Famira, Sam Fishman, Matt Fontaine, Yannis Haralambous, Greg Hitchcock,
-Jeremie Hornus, Khaled Hosny, John Hudson, Denis Moyogo Jacquerye, Jack
-Jansen, Tom Kacvinsky, Jens Kutilek, Antoine Leca, Werner Lemberg, Tal
+Famira, Sam Fishman, Matt Fontaine, Takaaki Fuji, Yannis Haralambous, Greg
+Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson, Denis Moyogo Jacquerye,
+Jack Jansen, Tom Kacvinsky, Jens Kutilek, Antoine Leca, Werner Lemberg, Tal
Leming, Peter Lofting, Cosimo Lupo, Masaya Nakamura, Dave Opstad,
Laurence Penney, Roozbeh Pournader, Garret Rieger, Read Roberts, Guido
van Rossum, Just van Rossum, Andreas Seidel, Georg Seifert, Chris
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Snippets/README.md
new/fonttools-4.29.1/Snippets/README.md
--- old/fonttools-4.28.5/Snippets/README.md 2021-12-19 11:49:06.000000000
+0100
+++ new/fonttools-4.29.1/Snippets/README.md 2022-02-01 12:30:24.000000000
+0100
@@ -9,3 +9,4 @@
* https://github.com/googlei18n/nototools
* https://github.com/googlefonts/fontbakery
* https://github.com/Typefounding/setUseTypoMetricsFalse
+* https://github.com/ftCLI/ftCLI
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Tests/colorLib/builder_test.py
new/fonttools-4.29.1/Tests/colorLib/builder_test.py
--- old/fonttools-4.28.5/Tests/colorLib/builder_test.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Tests/colorLib/builder_test.py 2022-02-01
12:30:24.000000000 +0100
@@ -1,3 +1,4 @@
+from copy import deepcopy
from fontTools.ttLib import newTable
from fontTools.ttLib.tables import otTables as ot
from fontTools.colorLib import builder
@@ -1692,6 +1693,33 @@
assert clipBoxes["a"].Format == 2
assert clipBoxes["c"].Format == 1
+ def test_duplicate_base_glyphs(self):
+ # If > 1 base glyphs refer to equivalent list of layers we expect them
to share
+ # the same PaintColrLayers.
+ layers = {
+ "Format": ot.PaintFormat.PaintColrLayers,
+ "Layers": [
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 0),
"d"),
+ (ot.PaintFormat.PaintGlyph, (ot.PaintFormat.PaintSolid, 1),
"e"),
+ ],
+ }
+ # I copy the layers to ensure equality is by content, not by identity
+ colr = builder.buildCOLR(
+ {"a": layers, "b": deepcopy(layers), "c": deepcopy(layers)}
+ ).table
+
+ baseGlyphs = colr.BaseGlyphList.BaseGlyphPaintRecord
+ assert len(baseGlyphs) == 3
+
+ assert baseGlyphs[0].BaseGlyph == "a"
+ assert baseGlyphs[1].BaseGlyph == "b"
+ assert baseGlyphs[2].BaseGlyph == "c"
+
+ expected = {"Format": 1, "FirstLayerIndex": 0, "NumLayers": 2}
+ assert baseGlyphs[0].Paint.__dict__ == expected
+ assert baseGlyphs[1].Paint.__dict__ == expected
+ assert baseGlyphs[2].Paint.__dict__ == expected
+
class TrickyRadialGradientTest:
@staticmethod
@@ -1719,6 +1747,16 @@
r1 = 260.0072
assert self.round_start_circle(c0, r0, c1, r1, inside=True) == ((386,
71), 0)
+ def test_noto_emoji_horns_sign_u1f918_1f3fc(self):
+ # This radial gradient is taken from noto-emoji's 'SIGNS OF THE HORNS'
+ # (1f918_1f3fc). We check that c0 is inside c1 both before and after
rounding.
+ c0 = (-437.6789059060543, -2116.9237094478003)
+ r0 = 0.0
+ c1 = (-488.7330118252256, -1876.5036857045086)
+ r1 = 245.77147821915673
+ assert self.circle_inside_circle(c0, r0, c1, r1)
+ assert self.circle_inside_circle(c0, r0, c1, r1, rounded=True)
+
@pytest.mark.parametrize(
"c0, r0, c1, r1, inside, expected",
[
Binary files old/fonttools-4.28.5/Tests/pens/data/test_even_odd_fill.pgm and
new/fonttools-4.29.1/Tests/pens/data/test_even_odd_fill.pgm differ
Binary files old/fonttools-4.28.5/Tests/pens/data/test_non_zero_fill.pgm and
new/fonttools-4.29.1/Tests/pens/data/test_non_zero_fill.pgm differ
Binary files old/fonttools-4.28.5/Tests/pens/data/test_rotate.pgm and
new/fonttools-4.29.1/Tests/pens/data/test_rotate.pgm differ
Binary files old/fonttools-4.28.5/Tests/pens/data/test_skew.pgm and
new/fonttools-4.29.1/Tests/pens/data/test_skew.pgm differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Tests/pens/freetypePen_test.py
new/fonttools-4.29.1/Tests/pens/freetypePen_test.py
--- old/fonttools-4.28.5/Tests/pens/freetypePen_test.py 1970-01-01
01:00:00.000000000 +0100
+++ new/fonttools-4.29.1/Tests/pens/freetypePen_test.py 2022-02-01
12:30:24.000000000 +0100
@@ -0,0 +1,235 @@
+import unittest
+import os
+import math
+
+try:
+ from fontTools.pens.freetypePen import FreeTypePen
+
+ FREETYPE_PY_AVAILABLE = True
+except ImportError:
+ FREETYPE_PY_AVAILABLE = False
+
+from fontTools.misc.transform import Scale, Offset
+
+DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
+
+
+def box(pen, offset=(0, 0)):
+ pen.moveTo((0 + offset[0], 0 + offset[1]))
+ pen.lineTo((0 + offset[0], 500 + offset[1]))
+ pen.lineTo((500 + offset[0], 500 + offset[1]))
+ pen.lineTo((500 + offset[0], 0 + offset[1]))
+ pen.closePath()
+
+
+def draw_cubic(pen):
+ pen.moveTo((50, 0))
+ pen.lineTo((50, 500))
+ pen.lineTo((200, 500))
+ pen.curveTo((350, 500), (450, 400), (450, 250))
+ pen.curveTo((450, 100), (350, 0), (200, 0))
+ pen.closePath()
+
+
+def draw_quadratic(pen):
+ pen.moveTo((50, 0))
+ pen.lineTo((50, 500))
+ pen.lineTo((200, 500))
+ pen.qCurveTo((274, 500), (388, 438), (450, 324), (450, 250))
+ pen.qCurveTo((450, 176), (388, 62), (274, 0), (200, 0))
+ pen.closePath()
+
+
+def star(pen):
+ pen.moveTo((0, 420))
+ pen.lineTo((1000, 420))
+ pen.lineTo((200, -200))
+ pen.lineTo((500, 800))
+ pen.lineTo((800, -200))
+ pen.closePath()
+
+
+# For the PGM format, see the following resources:
+# https://en.wikipedia.org/wiki/Netpbm
+# http://netpbm.sourceforge.net/doc/pgm.html
+def load_pgm(filename):
+ with open(filename, "rb") as fp:
+ assert fp.readline() == "P5\n".encode()
+ w, h = (int(c) for c in fp.readline().decode().rstrip().split(" "))
+ assert fp.readline() == "255\n".encode()
+ return fp.read(), (w, h)
+
+
+def save_pgm(filename, buf, size):
+ with open(filename, "wb") as fp:
+ fp.write("P5\n".encode())
+ fp.write("{0:d} {1:d}\n".format(*size).encode())
+ fp.write("255\n".encode())
+ fp.write(buf)
+
+
+# Assume the buffers are equal when PSNR > 38dB. See also:
+# Peak signal-to-noise ratio
+# https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
+PSNR_THRESHOLD = 38.0
+
+
+def psnr(b1, b2):
+ import math
+
+ mse = sum((c1 - c2) * (c1 - c2) for c1, c2 in zip(b1, b2)) / float(len(b1))
+ return 10.0 * math.log10((255.0**2) / float(mse)) if mse > 0 else math.inf
+
+
[email protected](FREETYPE_PY_AVAILABLE, "freetype-py not installed")
+class FreeTypePenTest(unittest.TestCase):
+ def test_draw(self):
+ pen = FreeTypePen(None)
+ box(pen)
+ width, height = 500, 500
+ buf1, _ = pen.buffer(width=width, height=height)
+ buf2 = b"\xff" * width * height
+ self.assertEqual(buf1, buf2)
+
+ def test_empty(self):
+ pen = FreeTypePen(None)
+ width, height = 500, 500
+ buf, size = pen.buffer(width=width, height=height)
+ self.assertEqual(b"\0" * size[0] * size[1], buf)
+
+ def test_bbox_and_cbox(self):
+ pen = FreeTypePen(None)
+ draw_cubic(pen)
+ self.assertEqual(pen.bbox, (50.0, 0.0, 450.0, 500.0))
+ self.assertEqual(pen.cbox, (50.0, 0.0, 450.0, 500.0))
+
+ def test_non_zero_fill(self):
+ pen = FreeTypePen(None)
+ star(pen)
+ t = Scale(0.05, 0.05)
+ width, height = t.transformPoint((1000, 1000))
+ t = t.translate(0, 200)
+ buf1, size1 = pen.buffer(width=width, height=height, transform=t,
evenOdd=False)
+ buf2, size2 = load_pgm(os.path.join(DATA_DIR,
"test_non_zero_fill.pgm"))
+ self.assertEqual(len(buf1), len(buf2))
+ self.assertEqual(size1, size2)
+ self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
+
+ def test_even_odd_fill(self):
+ pen = FreeTypePen(None)
+ star(pen)
+ t = Scale(0.05, 0.05)
+ width, height = t.transformPoint((1000, 1000))
+ t = t.translate(0, 200)
+ buf1, size1 = pen.buffer(width=width, height=height, transform=t,
evenOdd=True)
+ buf2, size2 = load_pgm(os.path.join(DATA_DIR,
"test_even_odd_fill.pgm"))
+ self.assertEqual(len(buf1), len(buf2))
+ self.assertEqual(size1, size2)
+ self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
+
+ def test_cubic_vs_quadratic(self):
+ pen1, pen2 = FreeTypePen(None), FreeTypePen(None)
+ draw_cubic(pen1)
+ draw_quadratic(pen2)
+ width, height = 500, 500
+ buf1, _ = pen1.buffer(width=width, height=height)
+ buf2, _ = pen2.buffer(width=width, height=height)
+ self.assertEqual(len(buf1), len(buf2))
+ self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
+
+ def test_contain(self):
+ pen = FreeTypePen(None)
+ star(pen)
+ t = Scale(0.05, 0.05)
+ width, height = 0, 0
+ buf1, size1 = pen.buffer(width=width, height=height, transform=t,
contain=True)
+ buf2, size2 = load_pgm(os.path.join(DATA_DIR,
"test_non_zero_fill.pgm"))
+ self.assertEqual(len(buf1), len(buf2))
+ self.assertEqual(size1, size2)
+ self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
+
+ def test_rotate(self):
+ pen = FreeTypePen(None)
+ box(pen)
+ t = Scale(0.05, 0.05).rotate(math.pi / 4.0).translate(1234, 5678)
+ width, height = None, None
+ buf1, size1 = pen.buffer(width=width, height=height, transform=t)
+ buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_rotate.pgm"))
+ self.assertEqual(len(buf1), len(buf2))
+ self.assertEqual(size1, size2)
+ self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
+
+ def test_skew(self):
+ pen = FreeTypePen(None)
+ box(pen)
+ t = Scale(0.05, 0.05).skew(math.pi / 4.0).translate(1234, 5678)
+ width, height = None, None
+ buf1, size1 = pen.buffer(width=width, height=height, transform=t)
+ buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_skew.pgm"))
+ self.assertEqual(len(buf1), len(buf2))
+ self.assertEqual(size1, size2)
+ self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
+
+ def test_none_size(self):
+ pen = FreeTypePen(None)
+ star(pen)
+ width, height = None, None
+ buf1, size = pen.buffer(width=width, height=height,
transform=Offset(0, 200))
+ buf2, _ = pen.buffer(width=1000, height=1000, transform=Offset(0, 200))
+ self.assertEqual(size, (1000, 1000))
+ self.assertEqual(buf1, buf2)
+
+ pen = FreeTypePen(None)
+ box(pen, offset=(250, 250))
+ width, height = None, None
+ buf1, size = pen.buffer(width=width, height=height)
+ buf2, _ = pen.buffer(width=500, height=500, transform=Offset(-250,
-250))
+ self.assertEqual(size, (500, 500))
+ self.assertEqual(buf1, buf2)
+
+ pen = FreeTypePen(None)
+ box(pen, offset=(-1234, -5678))
+ width, height = None, None
+ buf1, size = pen.buffer(width=width, height=height)
+ buf2, _ = pen.buffer(width=500, height=500, transform=Offset(1234,
5678))
+ self.assertEqual(size, (500, 500))
+ self.assertEqual(buf1, buf2)
+
+ def test_zero_size(self):
+ pen = FreeTypePen(None)
+ star(pen)
+ width, height = 0, 0
+ buf1, size = pen.buffer(
+ width=width, height=height, transform=Offset(0, 200), contain=True
+ )
+ buf2, _ = pen.buffer(
+ width=1000, height=1000, transform=Offset(0, 200), contain=True
+ )
+ self.assertEqual(size, (1000, 1000))
+ self.assertEqual(buf1, buf2)
+
+ pen = FreeTypePen(None)
+ box(pen, offset=(250, 250))
+ width, height = 0, 0
+ buf1, size = pen.buffer(width=width, height=height, contain=True)
+ buf2, _ = pen.buffer(
+ width=500, height=500, transform=Offset(0, 0), contain=True
+ )
+ self.assertEqual(size, (750, 750))
+ self.assertEqual(buf1, buf2)
+
+ pen = FreeTypePen(None)
+ box(pen, offset=(-1234, -5678))
+ width, height = 0, 0
+ buf1, size = pen.buffer(width=width, height=height, contain=True)
+ buf2, _ = pen.buffer(
+ width=500, height=500, transform=Offset(1234, 5678), contain=True
+ )
+ self.assertEqual(size, (500, 500))
+ self.assertEqual(buf1, buf2)
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Tests/ttx/ttx_test.py
new/fonttools-4.29.1/Tests/ttx/ttx_test.py
--- old/fonttools-4.28.5/Tests/ttx/ttx_test.py 2021-12-19 11:49:06.000000000
+0100
+++ new/fonttools-4.29.1/Tests/ttx/ttx_test.py 2022-02-01 12:30:24.000000000
+0100
@@ -961,10 +961,6 @@
assert "(Cancelled.)" in caplog.text
[email protected](
- sys.platform == "win32",
- reason="waitForKeyPress function causes test to hang on Windows platform",
-)
def test_main_system_exit(tmpdir, monkeypatch):
with pytest.raises(SystemExit):
inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
@@ -991,10 +987,6 @@
assert "Test error" in caplog.text
[email protected](
- sys.platform == "win32",
- reason="waitForKeyPress function causes test to hang on Windows platform",
-)
def test_main_base_exception(tmpdir, monkeypatch, caplog):
with pytest.raises(SystemExit):
inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Tests/ufoLib/filenames_test.py
new/fonttools-4.29.1/Tests/ufoLib/filenames_test.py
--- old/fonttools-4.28.5/Tests/ufoLib/filenames_test.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Tests/ufoLib/filenames_test.py 2022-02-01
12:30:24.000000000 +0100
@@ -29,6 +29,16 @@
self.assertEqual(userNameToFileName("con.alt"), "_con.alt")
self.assertEqual(userNameToFileName("alt.con"), "alt._con")
+ self.assertEqual(
+ # Test output for ASCII range (up until 0x7F), except for illegal
+ # characters.
+ userNameToFileName("".join([chr(i) for i in range(0, 0x80)])),
+ "________________________________"
+ " !_#$%&'____,-._0123456789_;_=__"
+ "@A_B_C_D_E_F_G_H_I_J_K_L_M_N_O_P_Q_R_S_T_U_V_W_X_Y_Z_"
+ "___^_`abcdefghijklmnopqrstuvwxyz{_}~_",
+ )
+
def test_userNameToFileName_ValueError(self):
with self.assertRaises(ValueError):
userNameToFileName(b"a")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/Tests/unicodedata_test.py
new/fonttools-4.29.1/Tests/unicodedata_test.py
--- old/fonttools-4.28.5/Tests/unicodedata_test.py 2021-12-19
11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/Tests/unicodedata_test.py 2022-02-01
12:30:24.000000000 +0100
@@ -230,6 +230,9 @@
assert unicodedata.ot_tag_to_script("vai ") == "Vaii"
assert unicodedata.ot_tag_to_script("lao ") == "Laoo"
assert unicodedata.ot_tag_to_script("yi") == "Yiii"
+ # both 'hang' and 'jamo' tags map to the Hangul script
+ assert unicodedata.ot_tag_to_script("hang") == "Hang"
+ assert unicodedata.ot_tag_to_script("jamo") == "Hang"
for invalid_value in ("", " ", "z zz", "zzzzz"):
with pytest.raises(ValueError, match="invalid OpenType tag"):
@@ -240,6 +243,7 @@
assert unicodedata.script_horizontal_direction("Latn") == "LTR"
assert unicodedata.script_horizontal_direction("Arab") == "RTL"
assert unicodedata.script_horizontal_direction("Thaa") == "RTL"
+ assert unicodedata.script_horizontal_direction("Ougr") == "RTL"
with pytest.raises(KeyError):
unicodedata.script_horizontal_direction("Azzz")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/requirements.txt
new/fonttools-4.29.1/requirements.txt
--- old/fonttools-4.28.5/requirements.txt 2021-12-19 11:49:06.000000000
+0100
+++ new/fonttools-4.29.1/requirements.txt 2022-02-01 12:30:24.000000000
+0100
@@ -2,12 +2,13 @@
# extension 'brotlipy' on PyPy
brotli==1.0.9; platform_python_implementation != "PyPy"
brotlicffi==1.0.9.2; platform_python_implementation == "PyPy"
-unicodedata2==13.0.0.post2; python_version < '3.9' and
platform_python_implementation != "PyPy"
+unicodedata2==14.0.0; python_version < '3.11'
scipy==1.7.3; platform_python_implementation != "PyPy"
munkres==1.1.4; platform_python_implementation == "PyPy"
zopfli==0.1.9
fs==2.4.14
skia-pathops==0.7.2; platform_python_implementation != "PyPy"
# this is only required to run Tests/cu2qu/{ufo,cli}_test.py
-ufoLib2==0.12.1
+ufoLib2==0.13.0
pyobjc==8.1; sys_platform == "darwin"
+freetype-py==2.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/setup.cfg
new/fonttools-4.29.1/setup.cfg
--- old/fonttools-4.28.5/setup.cfg 2021-12-19 11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/setup.cfg 2022-02-01 12:30:24.000000000 +0100
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 4.28.5
+current_version = 4.29.1
commit = True
tag = False
tag_name = {new_version}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/fonttools-4.28.5/setup.py
new/fonttools-4.29.1/setup.py
--- old/fonttools-4.28.5/setup.py 2021-12-19 11:49:06.000000000 +0100
+++ new/fonttools-4.29.1/setup.py 2022-02-01 12:30:24.000000000 +0100
@@ -90,11 +90,9 @@
# of the Unicode Character Database instead of the built-in unicodedata
# which varies between python versions and may be outdated.
"unicode": [
- # the unicodedata2 extension module doesn't work on PyPy.
- # Python 3.9 already has Unicode 13.0, so the backport is not
needed.
+ # Python 3.11 already has Unicode 14.0, so the backport is not
needed.
(
- "unicodedata2 >= 13.0.0; "
- "python_version < '3.9' and
platform_python_implementation != 'PyPy'"
+ "unicodedata2 >= 14.0.0; python_version < '3.11'"
),
],
# for graphite type tables in ttLib/tables (Silf, Glat, Gloc)
@@ -441,7 +439,7 @@
setup_params = dict(
name="fonttools",
- version="4.28.5",
+ version="4.29.1",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="[email protected]",