diff --git a/Lib/fontTools/pens/freetypePen.py b/Lib/fontTools/pens/freetypePen.py index 319a9dd66..d7d418128 100644 --- a/Lib/fontTools/pens/freetypePen.py +++ b/Lib/fontTools/pens/freetypePen.py @@ -20,6 +20,7 @@ from freetype.ft_errors import FT_Exception from fontTools.pens.basePen import BasePen from fontTools.misc.roundTools import otRound +from fontTools.misc.transform import Transform Contour = collections.namedtuple('Contour', ('points', 'tags')) LINE = 0b00000001 @@ -48,13 +49,14 @@ class FreeTypePen(BasePen): 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(offset=(0, -descender), width=width, height=height) + pen.show(width=width, height=height, transform=Offset(0, -descender)) Combining with `uharfbuzz`, you can typeset a chunk of glyphs in a pen:: @@ -98,7 +100,7 @@ class FreeTypePen(BasePen): offset = (-vhea_descender, -y) width = vhea_ascender - vhea_descender height = -y - pen.show(offset=offset, width=width, height=height, contain=contain) + 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. @@ -108,22 +110,25 @@ class FreeTypePen(BasePen): BasePen.__init__(self, glyphSet) self.contours = [] - def outline(self, offset=None, scale=None, evenOdd=False): + def outline(self, transform=None, evenOdd=False): """Converts the current contours to ``FT_Outline``. Args: - offset: A optional tuple of ``(x, y)`` used for translation. - scale: A optional tuple of ``(scale_x, scale_y)`` used for scaling. + transform: A 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. """ - offset = offset or (0, 0) - scale = scale or (1.0, 1.0) + transform = transform or Transform() + if not hasattr(transform, 'transformPoint'): + transform = Transform(*transform) nContours = 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: - points.append(FT_Vector(FT_Pos(otRound((point[0] + offset[0]) * scale[0] * 64)), FT_Pos(otRound((point[1] + offset[1]) * scale[1] * 64)))) + 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: @@ -143,16 +148,15 @@ class FreeTypePen(BasePen): (ctypes.c_int)(flags) ) - def buffer(self, offset=None, width=1000, height=1000, evenOdd=False, scale=None, contain=False): + def buffer(self, width=1000, height=1000, transform=None, evenOdd=False, contain=False): """Renders the current contours within a bitmap buffer. Args: - offset: A optional tuple of ``(x, y)`` used for translation. - Typically ``(0, -descender)`` can be passed so that the glyph - image would not been clipped. width: Image width of the bitmap in pixels. height: Image height of the bitmap in pixels. - scale: A optional tuple of ``(scale_x, scale_y)`` used for scaling. + transform: A 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero. contain: If ``True``, the image size will be automatically expanded so that it fits to the bounding box of the paths. Useful for @@ -170,17 +174,20 @@ class FreeTypePen(BasePen): >> type(buf), len(buf), size (, 500000, (500, 1000)) """ - offset_x, offset_y = offset or (0, 0) + transform = transform or Transform() + if not hasattr(transform, 'transformPoint'): + transform = Transform(*transform) if contain: - bbox = self.bbox + bbox = self.bbox + bbox = transform.transformPoints((bbox[0:2], bbox[2:4])) + bbox = (*bbox[0], *bbox[1]) bbox_size = bbox[2] - bbox[0], bbox[3] - bbox[1] - offset_x = min(offset_x, bbox[0]) * -1 - width = max(width, bbox_size[0]) - offset_y = min(offset_y, bbox[1]) * -1 - height = max(height, bbox_size[1]) - scale = scale or (1.0, 1.0) - width = math.ceil(width * scale[0]) - height = math.ceil(height * scale[1]) + dx = min(-transform.dx, bbox[0]) * -1.0 + dy = min(-transform.dy, bbox[1]) * -1.0 + width = max(width, bbox_size[0]) + height = max(height, bbox_size[1]) + 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), @@ -192,22 +199,21 @@ class FreeTypePen(BasePen): (ctypes.c_char)(0), (ctypes.c_void_p)(None) ) - outline = self.outline(offset=(offset_x, offset_y), evenOdd=evenOdd, scale=scale) + 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, offset=None, width=1000, height=1000, evenOdd=False, scale=None, contain=False): + def array(self, width=1000, height=1000, transform=None, evenOdd=False, contain=False): """Returns the rendered contours as a numpy array. Requires `numpy`. Args: - offset: A optional tuple of ``(x, y)`` used for translation. - Typically ``(0, -descender)`` can be passed so that the glyph - image would not been clipped. width: Image width of the bitmap in pixels. height: Image height of the bitmap in pixels. - scale: A optional tuple of ``(scale_x, scale_y)`` used for scaling. + transform: A 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero. contain: If ``True``, the image size will be automatically expanded so that it fits to the bounding box of the paths. Useful for @@ -225,20 +231,19 @@ class FreeTypePen(BasePen): (, (1000, 500)) """ import numpy as np - buf, size = self.buffer(offset=offset, width=width, height=height, evenOdd=evenOdd, scale=scale, contain=contain) + buf, size = self.buffer(width=width, height=height, transform=transform, evenOdd=evenOdd, contain=contain) return np.frombuffer(buf, 'B').reshape((size[1], size[0])) / 255.0 - def show(self, offset=None, width=1000, height=1000, evenOdd=False, scale=None, contain=False): + def show(self, width=1000, height=1000, transform=None, evenOdd=False, contain=False): """Plots the rendered contours with `pyplot`. Requires `numpy` and `matplotlib`. Args: - offset: A optional tuple of ``(x, y)`` used for translation. - Typically ``(0, -descender)`` can be passed so that the glyph - image would not been clipped. width: Image width of the bitmap in pixels. height: Image height of the bitmap in pixels. - scale: A optional tuple of ``(scale_x, scale_y)`` used for scaling. + transform: A 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero. contain: If ``True``, the image size will be automatically expanded so that it fits to the bounding box of the paths. Useful for @@ -250,21 +255,20 @@ class FreeTypePen(BasePen): >> pen.show(width=500, height=1000) """ from matplotlib import pyplot as plt - a = self.array(offset=offset, width=width, height=height, evenOdd=evenOdd, scale=scale, contain=contain) + a = self.array(width=width, height=height, transform=transform, evenOdd=evenOdd, contain=contain) plt.imshow(a, cmap='gray_r', vmin=0, vmax=1) plt.show() - def image(self, offset=None, width=1000, height=1000, evenOdd=False, scale=None, contain=False): + def image(self, width=1000, height=1000, transform=None, evenOdd=False, contain=False): """Returns the rendered contours as a PIL image. Requires `Pillow`. Can be used to display a glyph image in Jupyter Notebook. Args: - offset: A optional tuple of ``(x, y)`` used for translation. - Typically ``(0, -descender)`` can be passed so that the glyph - image would not been clipped. width: Image width of the bitmap in pixels. height: Image height of the bitmap in pixels. - scale: A optional tuple of ``(scale_x, scale_y)`` used for scaling. + transform: A 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero. contain: If ``True``, the image size will be automatically expanded so that it fits to the bounding box of the paths. Useful for @@ -282,7 +286,7 @@ class FreeTypePen(BasePen): (, (500, 1000)) """ from PIL import Image - buf, size = self.buffer(offset=offset, width=width, height=height, evenOdd=evenOdd, scale=scale, contain=contain) + buf, size = self.buffer(width=width, height=height, transform=transform, evenOdd=evenOdd, contain=contain) img = Image.new('L', size, 0) img.putalpha(Image.frombuffer('L', size, buf)) return img diff --git a/Tests/pens/freetypePen_test.py b/Tests/pens/freetypePen_test.py index 44f37785a..36e5c2d02 100644 --- a/Tests/pens/freetypePen_test.py +++ b/Tests/pens/freetypePen_test.py @@ -6,6 +6,8 @@ try: except ImportError: FREETYPE_PY_AVAILABLE = False +from fontTools.misc.transform import Scale + def draw_cubic(pen): pen.moveTo((50, 0)) pen.lineTo((50, 500)) @@ -47,8 +49,8 @@ class FreeTypePenTest(unittest.TestCase): ZLIB_B64_BIN = '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' pen = FreeTypePen(None) draw_cubic(pen) - offset, width, height = (0, 0), 500, 500 - buf1, _ = pen.buffer(offset=offset, width=width, height=height) + width, height = 500, 500 + buf1, _ = pen.buffer(width=width, height=height) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) self.assertEqual(len(buf1), len(buf2)) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) @@ -58,16 +60,17 @@ class FreeTypePenTest(unittest.TestCase): ZLIB_B64_BIN = 'eJy91r8NgkAUx/FLMHECCLMQC5gGK3UB4gJEXABhAbTAJXAJ7GkoBIornrQif/w2vvo+yXH37vFTqi/5rKYs8jhwDDVdMlp15ttM9NVFE2ZSiLShCYVI5VIhekeFSLKmQhIsZLixZaFdKqQyqZAQi9amQiIsOpsK8bHIsKgNKsTBIsAixiLHosCixKLB4vWHXfEv56fLb5B3Ce5E3u1XRQV+tXwy4OnDJxyeopUFhfYUFHsFRaqgSOGfUx+G65cSgPcNZlPGyRoBM0nmjJJMfdv+mpaa5+N+OW5W44vfouHQiw==' pen = FreeTypePen(None) draw_cubic(pen) - offset, width, height = (0, 0), 500, 500 - buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.1, 0.1)) + t = Scale(0.1, 0.1) + width, height = t.transformPoint((500, 500)) + buf1, size = pen.buffer(width=width, height=height, transform=t) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) self.assertEqual(size, (50, 50)) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) def test_empty(self): pen = FreeTypePen(None) - offset, width, height = (0, 0), 500, 500 - buf, size = pen.buffer(offset=offset, width=width, height=height) + 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): @@ -81,8 +84,10 @@ class FreeTypePenTest(unittest.TestCase): ZLIB_B64_BIN = 'eJzt2L0NglAUhmESTZxA4yzGAqfRSl3AuABRF/BnAbTAJXAJ7GkoBAqKo4WNhbk3OZ4vknzvAk/g/nC5QcAYY4wxxhhrRfJZmaXJfjXqWBrving6tDZe1dufKV8NkSrqmxsieWhvSDO3N0SOPXtDjgBD9K/LbTShvSG5dgp7GBIBjEq54n0M2QKMWvcgXoZMAUYMMArVR8vPkBHAWAGMPcBIAEYKMDKAUQKMB8BAvCvEmCPmLmINIvYSwJ6I2NvPGuJ/vrWIMwPg7IM4wwHOovnA3GgmSsLDWGgJt3FSE07jZP7P2Sz1gusOQD3cLqPaaCety6h3xncyxWVmd7dU3m/Xw3rc/R3AGGOMsbb3BMrP0Is=' pen = FreeTypePen(None) draw_cubic(pen) - offset, width, height = (0, 200), 1000, 1000 - buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.1, 0.1), evenOdd=False) + t = Scale(0.1, 0.1) + width, height = t.transformPoint((1000, 1000)) + t = t.translate(0, 200) + buf1, size = pen.buffer(width=width, height=height, transform=t, evenOdd=False) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) self.assertEqual(len(buf1), len(buf2)) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) @@ -92,8 +97,10 @@ class FreeTypePenTest(unittest.TestCase): ZLIB_B64_BIN = 'eJzt2L0NglAUhmESTZxA4yzGAqfRSl3AuABRF/BnAbTAJXAJ7GkoBAqKo4WNhbk3OZ4vknzvAk/g/nC5QcAYY4wxxhhrRfJZmaXJfjXqWBrving6tDZe1dufKV8NkSrqmxsieWhvSDO3N0SOPXtDjgBD9K/LbTShvSG5dgp7GBIBjEq54n0M2QKMWvcgXoZMAUYMMArVR8vPkBHAWAGMPcBIAEYKMDKAUQKMB8BAvCvEmCPmLmINIvYSwJ6I2NvPGuJ/vrWIMwPg7IM4wwHOovnA3GgmSsLDWGgJt3FSE07jZP7P2Sz1gusOQD3cLqPaaCety6h3xncyxWVmd7dU3m/Xw3rc/R3AGGOMsbb3BMrP0Is=' pen = FreeTypePen(None) draw_cubic(pen) - offset, width, height = (0, 200), 1000, 1000 - buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.1, 0.1), evenOdd=True) + t = Scale(0.1, 0.1) + width, height = t.transformPoint((1000, 1000)) + t = t.translate(0, 200) + buf1, size = pen.buffer(width=width, height=height, transform=t, evenOdd=True) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) self.assertEqual(len(buf1), len(buf2)) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) @@ -102,9 +109,9 @@ class FreeTypePenTest(unittest.TestCase): pen1, pen2 = FreeTypePen(None), FreeTypePen(None) draw_cubic(pen1) draw_quadratic(pen2) - offset, width, height = (0, 0), 500, 500 - buf1, _ = pen1.buffer(offset=offset, width=width, height=height) - buf2, _ = pen2.buffer(offset=offset, width=width, height=height) + 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) @@ -113,8 +120,9 @@ class FreeTypePenTest(unittest.TestCase): ZLIB_B64_BIN = 'eJyVlKtvAkEQh5dHCEG0vQTbJgjOIVqB7VW0BoUsCQqHLElP4VCkV8sfQBpcqzCYYjEIDClNEFiaSxCEEB7bO/aWO2Bn2fmp22G+3ONjhhBxdB34AUzlBUt0v5GAtlpd4YgCpc84okXpBwqI2pTaUQxhUCf3GMJyiTcMMXKJHwSg010Q2iuMQGjvMkJdu7ZihLr2AvWirL3FCVXtrnAWVe0G3UdRu+UTIu2lBUVlUSJ3YwwwvnXuorXVgba2e7BQdaPWv6mG+Ms8/akA08fA+9/0zgO964NPFmucAxqx489cnMv650WBmcwvDIwyQteXXxDweQH9P8y1qH9tQv1OHkSEIQEIeT8FLCnAJzwY+bTzCQ9GPu2FU+DMtLdEhGza/QkPRjbthgiQTntgwodD/1qy5Ef7pnokUt8f4CWv85ZZ3j3mZ/wMLnlvpdNBmp3TA68ALnlPeDPBC4kmq0DamfBlOVgrL90apH0nfJI9LGYnEu2u8E7yuJrsgNod4dta+LQerm0B7Qa1c+Kb52yxdqufEgOEpPpC7WYcAgiJv/rX/4vPJ4U=' pen = FreeTypePen(None) star(pen) - offset, width, height = (0, 0), 0, 0 - buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.05, 0.05), contain=True) + t = Scale(0.05, 0.05) + width, height = 0, 0 + buf1, size = pen.buffer(width=width, height=height, transform=t, contain=True) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) self.assertEqual(len(buf1), len(buf2)) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)