From 5f2c492635d046be53395a6a19098a92a6145b01 Mon Sep 17 00:00:00 2001 From: Takaaki Fuji Date: Mon, 31 Jan 2022 20:56:03 +0900 Subject: [PATCH] freetypePen: format code with Black --- Lib/fontTools/pens/freetypePen.py | 100 ++++++++++++++++++++++-------- Tests/pens/freetypePen_test.py | 79 ++++++++++++++--------- 2 files changed, 124 insertions(+), 55 deletions(-) diff --git a/Lib/fontTools/pens/freetypePen.py b/Lib/fontTools/pens/freetypePen.py index 5ce0cede4..870776bc7 100644 --- a/Lib/fontTools/pens/freetypePen.py +++ b/Lib/fontTools/pens/freetypePen.py @@ -2,7 +2,7 @@ """Pen to rasterize paths with FreeType.""" -__all__ = ['FreeTypePen'] +__all__ = ["FreeTypePen"] import os import ctypes @@ -15,14 +15,22 @@ 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_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')) +Contour = collections.namedtuple("Contour", ("points", "tags")) + class FreeTypePen(BasePen): """Pen to rasterize paths with FreeType. Requires `freetype-py` module. @@ -115,15 +123,19 @@ class FreeTypePen(BasePen): evenOdd: Pass ``True`` for even-odd fill instead of non-zero. """ transform = transform or Transform() - if not hasattr(transform, 'transformPoint'): + if not hasattr(transform, "transformPoint"): transform = Transform(*transform) n_contours = len(self.contours) - n_points = sum((len(contour.points) for contour in 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)))) + 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: @@ -137,13 +149,15 @@ class FreeTypePen(BasePen): return FT_Outline( (ctypes.c_short)(n_contours), (ctypes.c_short)(n_points), - (FT_Vector * n_points)(*points), + (FT_Vector * n_points)(*points), (ctypes.c_ubyte * n_points)(*tags), (ctypes.c_short * n_contours)(*contours), - (ctypes.c_int)(flags) + (ctypes.c_int)(flags), ) - def buffer(self, width=None, height=None, transform=None, contain=False, evenOdd=False): + def buffer( + self, width=None, height=None, transform=None, contain=False, evenOdd=False + ): """Renders the current contours within a bitmap buffer. Args: @@ -185,13 +199,18 @@ class FreeTypePen(BasePen): """ transform = transform or Transform() - if not hasattr(transform, 'transformPoint'): + 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])) + 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: @@ -218,15 +237,19 @@ class FreeTypePen(BasePen): (ctypes.c_short)(256), (ctypes.c_ubyte)(FT_PIXEL_MODE_GRAY), (ctypes.c_char)(0), - (ctypes.c_void_p)(None) + (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)) + 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): + def array( + self, width=None, height=None, transform=None, contain=False, evenOdd=False + ): """Returns the rendered contours as a numpy array. Requires `numpy`. Args: @@ -266,10 +289,19 @@ class FreeTypePen(BasePen): (, (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): + 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`. @@ -304,11 +336,20 @@ class FreeTypePen(BasePen): >> 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) + + 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): + 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. @@ -349,9 +390,16 @@ class FreeTypePen(BasePen): (, (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)) + + 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 @@ -386,14 +434,14 @@ class FreeTypePen(BasePen): def _lineTo(self, pt): if not (self.contours and len(self.contours[-1].points) > 0): - raise PenError('Contour missing required initial moveTo') + 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') + 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)): @@ -402,7 +450,7 @@ class FreeTypePen(BasePen): def _qCurveToOne(self, p1, p2): if not (self.contours and len(self.contours[-1].points) > 0): - raise PenError('Contour missing required initial moveTo') + 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)): diff --git a/Tests/pens/freetypePen_test.py b/Tests/pens/freetypePen_test.py index c194b82f6..b6edc8bb9 100644 --- a/Tests/pens/freetypePen_test.py +++ b/Tests/pens/freetypePen_test.py @@ -4,21 +4,24 @@ 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') +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.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.lineTo((500 + offset[0], 0 + offset[1])) pen.closePath() + def draw_cubic(pen): pen.moveTo((50, 0)) pen.lineTo((50, 500)) @@ -27,6 +30,7 @@ def draw_cubic(pen): pen.curveTo((450, 100), (350, 0), (200, 0)) pen.closePath() + def draw_quadratic(pen): pen.moveTo((50, 0)) pen.lineTo((50, 500)) @@ -35,6 +39,7 @@ def draw_quadratic(pen): pen.qCurveTo((450, 176), (388, 62), (274, 0), (200, 0)) pen.closePath() + def star(pen): pen.moveTo((0, 420)) pen.lineTo((1000, 420)) @@ -43,32 +48,38 @@ def star(pen): 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() + 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()) + 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 + + 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 + @unittest.skipUnless(FREETYPE_PY_AVAILABLE, "freetype-py not installed") class FreeTypePenTest(unittest.TestCase): @@ -77,14 +88,14 @@ class FreeTypePenTest(unittest.TestCase): box(pen) width, height = 500, 500 buf1, _ = pen.buffer(width=width, height=height) - buf2 = b'\xff' * width * 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) + self.assertEqual(b"\0" * size[0] * size[1], buf) def test_bbox_and_cbox(self): pen = FreeTypePen(None) @@ -99,7 +110,7 @@ class FreeTypePenTest(unittest.TestCase): 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')) + 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) @@ -111,7 +122,7 @@ class FreeTypePenTest(unittest.TestCase): 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')) + 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) @@ -132,7 +143,7 @@ class FreeTypePenTest(unittest.TestCase): 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')) + 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) @@ -143,7 +154,7 @@ class FreeTypePenTest(unittest.TestCase): 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')) + 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) @@ -154,7 +165,7 @@ class FreeTypePenTest(unittest.TestCase): 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')) + 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) @@ -164,7 +175,7 @@ class FreeTypePenTest(unittest.TestCase): 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)) + buf2, _ = pen.buffer(width=1000, height=1000, transform=Offset(0, 200)) self.assertEqual(size, (1000, 1000)) self.assertEqual(buf1, buf2) @@ -172,7 +183,7 @@ class FreeTypePenTest(unittest.TestCase): 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)) + buf2, _ = pen.buffer(width=500, height=500, transform=Offset(-250, -250)) self.assertEqual(size, (500, 500)) self.assertEqual(buf1, buf2) @@ -180,7 +191,7 @@ class FreeTypePenTest(unittest.TestCase): 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)) + buf2, _ = pen.buffer(width=500, height=500, transform=Offset(1234, 5678)) self.assertEqual(size, (500, 500)) self.assertEqual(buf1, buf2) @@ -188,8 +199,12 @@ class FreeTypePenTest(unittest.TestCase): 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) + 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) @@ -197,7 +212,9 @@ class FreeTypePenTest(unittest.TestCase): 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) + buf2, _ = pen.buffer( + width=500, height=500, transform=Offset(0, 0), contain=True + ) self.assertEqual(size, (750, 750)) self.assertEqual(buf1, buf2) @@ -205,10 +222,14 @@ class FreeTypePenTest(unittest.TestCase): 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) + 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__': + +if __name__ == "__main__": import sys + sys.exit(unittest.main())