Prefer single transform matrix over offset/scale

This commit is contained in:
Takaaki Fuji 2022-01-11 22:13:59 +09:00
parent 42bc1257b4
commit 6f7ef4a838
2 changed files with 69 additions and 57 deletions

View File

@ -20,6 +20,7 @@ from freetype.ft_errors import FT_Exception
from fontTools.pens.basePen import BasePen from fontTools.pens.basePen import BasePen
from fontTools.misc.roundTools import otRound from fontTools.misc.roundTools import otRound
from fontTools.misc.transform import Transform
Contour = collections.namedtuple('Contour', ('points', 'tags')) Contour = collections.namedtuple('Contour', ('points', 'tags'))
LINE = 0b00000001 LINE = 0b00000001
@ -48,13 +49,14 @@ class FreeTypePen(BasePen):
from fontTools.ttLib import TTFont from fontTools.ttLib import TTFont
from fontTools.pens.freetypePen import FreeTypePen from fontTools.pens.freetypePen import FreeTypePen
from fontTools.misc.transform import Offset
pen = FreeTypePen(None) pen = FreeTypePen(None)
font = TTFont('SourceSansPro-Regular.otf') font = TTFont('SourceSansPro-Regular.otf')
glyph = font.getGlyphSet()['fi'] glyph = font.getGlyphSet()['fi']
glyph.draw(pen) glyph.draw(pen)
width, ascender, descender = glyph.width, font['OS/2'].usWinAscent, -font['OS/2'].usWinDescent width, ascender, descender = glyph.width, font['OS/2'].usWinAscent, -font['OS/2'].usWinDescent
height = ascender - descender 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:: Combining with `uharfbuzz`, you can typeset a chunk of glyphs in a pen::
@ -98,7 +100,7 @@ class FreeTypePen(BasePen):
offset = (-vhea_descender, -y) offset = (-vhea_descender, -y)
width = vhea_ascender - vhea_descender width = vhea_ascender - vhea_descender
height = -y 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 For Jupyter Notebook, the rendered image will be displayed in a cell if
you replace ``show()`` with ``image()`` in the examples. you replace ``show()`` with ``image()`` in the examples.
@ -108,22 +110,25 @@ class FreeTypePen(BasePen):
BasePen.__init__(self, glyphSet) BasePen.__init__(self, glyphSet)
self.contours = [] 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``. """Converts the current contours to ``FT_Outline``.
Args: Args:
offset: A optional tuple of ``(x, y)`` used for translation. transform: A optional 6-tuple containing an affine transformation,
scale: A optional tuple of ``(scale_x, scale_y)`` used for scaling. or a ``Transform`` object from the ``fontTools.misc.transform``
module.
evenOdd: Pass ``True`` for even-odd fill instead of non-zero. evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
""" """
offset = offset or (0, 0) transform = transform or Transform()
scale = scale or (1.0, 1.0) if not hasattr(transform, 'transformPoint'):
transform = Transform(*transform)
nContours = len(self.contours) 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 = [] points = []
for contour in self.contours: for contour in self.contours:
for point in contour.points: 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 = [] tags = []
for contour in self.contours: for contour in self.contours:
for tag in contour.tags: for tag in contour.tags:
@ -143,16 +148,15 @@ class FreeTypePen(BasePen):
(ctypes.c_int)(flags) (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. """Renders the current contours within a bitmap buffer.
Args: 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. width: Image width of the bitmap in pixels.
height: Image height 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
contain: If ``True``, the image size will be automatically expanded contain: If ``True``, the image size will be automatically expanded
so that it fits to the bounding box of the paths. Useful for 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 >> type(buf), len(buf), size
(<class 'bytes'>, 500000, (500, 1000)) (<class 'bytes'>, 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: 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] bbox_size = bbox[2] - bbox[0], bbox[3] - bbox[1]
offset_x = min(offset_x, bbox[0]) * -1 dx = min(-transform.dx, bbox[0]) * -1.0
width = max(width, bbox_size[0]) dy = min(-transform.dy, bbox[1]) * -1.0
offset_y = min(offset_y, bbox[1]) * -1 width = max(width, bbox_size[0])
height = max(height, bbox_size[1]) height = max(height, bbox_size[1])
scale = scale or (1.0, 1.0) transform = Transform(*transform[:4], dx, dy)
width = math.ceil(width * scale[0]) width, height = math.ceil(width), math.ceil(height)
height = math.ceil(height * scale[1])
buf = ctypes.create_string_buffer(width * height) buf = ctypes.create_string_buffer(width * height)
bitmap = FT_Bitmap( bitmap = FT_Bitmap(
(ctypes.c_int)(height), (ctypes.c_int)(height),
@ -192,22 +199,21 @@ class FreeTypePen(BasePen):
(ctypes.c_char)(0), (ctypes.c_char)(0),
(ctypes.c_void_p)(None) (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)) err = FT_Outline_Get_Bitmap(freetype.get_handle(), ctypes.byref(outline), ctypes.byref(bitmap))
if err != 0: if err != 0:
raise FT_Exception(err) raise FT_Exception(err)
return buf.raw, (width, height) 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`. """Returns the rendered contours as a numpy array. Requires `numpy`.
Args: 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. width: Image width of the bitmap in pixels.
height: Image height 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
contain: If ``True``, the image size will be automatically expanded contain: If ``True``, the image size will be automatically expanded
so that it fits to the bounding box of the paths. Useful for so that it fits to the bounding box of the paths. Useful for
@ -225,20 +231,19 @@ class FreeTypePen(BasePen):
(<class 'numpy.ndarray'>, (1000, 500)) (<class 'numpy.ndarray'>, (1000, 500))
""" """
import numpy as np 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 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 """Plots the rendered contours with `pyplot`. Requires `numpy` and
`matplotlib`. `matplotlib`.
Args: 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. width: Image width of the bitmap in pixels.
height: Image height 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
contain: If ``True``, the image size will be automatically expanded contain: If ``True``, the image size will be automatically expanded
so that it fits to the bounding box of the paths. Useful for 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) >> pen.show(width=500, height=1000)
""" """
from matplotlib import pyplot as plt 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.imshow(a, cmap='gray_r', vmin=0, vmax=1)
plt.show() 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`. """Returns the rendered contours as a PIL image. Requires `Pillow`.
Can be used to display a glyph image in Jupyter Notebook. Can be used to display a glyph image in Jupyter Notebook.
Args: 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. width: Image width of the bitmap in pixels.
height: Image height 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. evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
contain: If ``True``, the image size will be automatically expanded contain: If ``True``, the image size will be automatically expanded
so that it fits to the bounding box of the paths. Useful for so that it fits to the bounding box of the paths. Useful for
@ -282,7 +286,7 @@ class FreeTypePen(BasePen):
(<class 'PIL.Image.Image'>, (500, 1000)) (<class 'PIL.Image.Image'>, (500, 1000))
""" """
from PIL import Image 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 = Image.new('L', size, 0)
img.putalpha(Image.frombuffer('L', size, buf)) img.putalpha(Image.frombuffer('L', size, buf))
return img return img

View File

@ -6,6 +6,8 @@ try:
except ImportError: except ImportError:
FREETYPE_PY_AVAILABLE = False FREETYPE_PY_AVAILABLE = False
from fontTools.misc.transform import Scale
def draw_cubic(pen): def draw_cubic(pen):
pen.moveTo((50, 0)) pen.moveTo((50, 0))
pen.lineTo((50, 500)) pen.lineTo((50, 500))
@ -47,8 +49,8 @@ class FreeTypePenTest(unittest.TestCase):
ZLIB_B64_BIN = 'eNrt3e1vleUdwPHf6QN2BEpPm9KWJa2Jh63DkAyqLwgUwxhLzDAsylwKGMgIWWG6hgSoyPaCKQNENCMDBTvBh1AKCps4I1OotFtcFF1ELEXrceumUFIeUrDQlh4Hbste7NWyvRj9fr//wifnuu7rOvd9XRH/aZ//XzZ4qaf7ZGfH8XePvHH4tZf3bHv4gSU1t0+qLM0L++/7/Prq0sn3X93xUO0dE4oT2kHM/9XldEvjhrqZqWwNMeb/rK9t79oFk5JKgsz/0enWhmUzCvUkmf+99O4V0wtERZl/Uceu5dNGKYsyv1amfducMeqizL/oxNaaMoVZ5rozza/V/ti0HKVZ5lc7t7PG53mY+dUGDi1N6c0yv1bb+snu08PMr9a5brzmvI7Wl2uOK/P6oqTmuPr2zc7THNeZjSnNeWP8gVnZmvOe41eVaM6b2RurNQeu3mrzNMd1qj5fc1zn1xRrjqt3U7nmuPq3V2qOa/D5iZrz9mmaUprzRvjNJZrjurB6pOa4uu7L1RzXRzUJzXG9PUVz3iP89mLNcZ2tzdIc15tVmvN25rYUaM5bt83XnFdLpea4eusSmuNqrtAcV89CzXntL9UcV/fdmvNqLNQc1ydTNcc1sCKhOa4Xk5rjSldpjuvyYs157RyhOa62cZrjunin5rgy9ZrzasjVHNfBAs1xtd+kOa7uKZrj6punOa/VmvPaktAc17PZmuPaO0xzXK8M1xxXS77muI4UaY7rWJnmuDoqNOehl2nOG96LNOc9yOVrzluyDdectzkzTHPeNmy25rieTWiOa4vmvFZrzmue5rj6pmiOq/smzXG1F2iO62Cu5rgaNOdVrzmuzJ2a47o4TnNcbSM0x7VTc16LNcd1uUpzXOmk5rheTGiOa4XmuAamao7rk0LNcTVqzutuzXF1l2qOa7/mvBZqjqunQnNczQnNcdVpjqu3UnNcLZrzmq85rq4CzXFt0RzXYJXmuN7M0hxXrea4zhZrjmu75rgyUzTH9XZCc1w1muP6KFdzXPdpjqtrpOa4VmuO60KJ5rg2a46rP6U5ribNeTuwEzXH9bzmuAYrNce1XXPeo3u55rg2aY6rt1hzXGs0x3U+X3Nc9ZrjOpWnOa5azXEd1ZxXtea4GjXH1VeiOa5VmuPqzNYc1yzNcR3QHFcmpTmujZrjOpOnOa7ZmuPapzlvLy6pOa5FmuN6XXPeEr1cc1z1muM6qjmv8ZrjWqc5rs6E5rgma45rvea42jTnldIc11LNcR3SHNdAgea4ajTHtVNzXOdyNMc1TXNcj2mOq11zXmWau1rTfMi3VXNcJzR3QtfcCV1zJ3TNndA1vw4bozmuOZrj2qY5rnbNcWVGaY5rmua4lmuOa5fmuDo051WgOa7pmuNaoTmu3ZrjSmvOq1BzXDM0x7VMc1wNmuNq1RzXac15JTXHNUlzXAs0x7VWc1x7NcfVpjmuvmzNcaU0xzVTc1x1muPaoDmuRs1xtWiOK605rssJzXEVa45rgua47tAcV63muB7SHNcOzXG9qjmu9zXHdVJzXJc055WnOa5SzXFVao5rkua4btccV43muJZojusBzXE9rDmubZrj2qM5rpc1x/Wa5rgOa47rDc1xHdEc17ua4zquOa4OzXF1ao7rpOa4ujXH1aM5rkua4xrUHNcVzR3bNfcZTnPXapq7J6P5dZd7r7z8j4WX/6Xy8p0JXr4bxct3IHn5rjMvv2ng5bdLvPxGkdeTmuPyzAFeni3CyzOEeHlWGC/PBOTl2Z+8POOXl2d58/LMflzezcHLO3h4edcWL+/U47VDc1zekcvLu7B5eec9rwma4yrWnFZfQnNa6dCcVovmuBo1x7VBc1x1muOaqTmusZrjlufZmtNqC81p7dMc11rNcS3QHNckzXElNad1OjSn1ao5rgbNcS3THNcMzXEVak4rHZrT2q05rnrNcU3XHFeB5rQ6QnNauzTHtVxzXNM0p5UZpTmt9tCc1jbNcc3RHNcYzWl9EJo7nWs+1KvRHFeZ5rROhOa0tmrudK6507nmQ6320JzWY5rj+obmtM7laE6rMTR3pab5EG8gqTmt5tCc1lLNcaU0p9UWmtNarzmuyZrT6kxoTmtdaE5rvOa0jobmtOo1p5Up15zW4dCc1iLNafUlNae1LzSnNVtzWmfzNKe1MTSnLc5TmtM6EJrTmqU5rc5szWmtCs1h9ZdoTqsxNKdVrTmt90JzWrWa0+rK05zW/aE5rPP5mtNaE5rD6h2tOa1NoTmsgXLNae0IzWFlKjWn9UJoTvuZT9ScVlNoDqs/pTmtzaE5rIslmtNaHZrDOj1Sc1o/Cs1hpYdpTmtOaA7rnYTmtKpDc1g7QnNY50ZrTmtxaA7rrSzNYQ3eEprD2hKaw+oq0JzW/NAcVmtoDqu3UnNadaE5rOaE5rB6bgzNYS0MzWG9FJrDOlOmOa3vheawdoXmsD4t1BzWlamhOaz60BzW/oTmsD5Ohuas+m4JzWEtCc1h7QzNYR0foTmsz24OzVll7grN3YzRfGj3VGgO61Cu5rDak6E5q+5UaA7bcq0OzWHdE5rD+mloDuuJ0BzWc1maw9qXE5qzOnBDaM6qdXhozupIfmjO6lhRaM6qoyw0h5FXhOawgf16+ZVr/j97fCsKzWGLtPzQHLYVMzw0h2243hCas3ouJzRn9URWaM7qwQjNUfXdE5qz6q4OzVm1p0JzVs3J0JzVU7mhOarMygjNUX12V2jO6vjNoTmrxhGhOWsj5t4IzVH96dbQnNVLydAc1ZWVidAc1ae3RWiOandRaI7qTE2E5qh+Uxaao+pZFKE5quYbQ3NUvUsToTmq1soIzUmdXpAIzUkNPp6M0JzUW7dGaE7q3JKs0BzVM6MjNCf1x6kRmpNKz0uE5qj1Wd2wCM1BXXxwZITmoAYeL43QnNSesRGag8rsrYrQnDSqP/21CM1B9f6iIkJzUOfXjo7QHFTXylERmoM6tvhLEZpz6m+6LVDhxTt/UhqhOWg1/tvvZEdozunso2ODGBa871ffzYvQnDOmt/ygMLAhV2YrK4IcDvyvG74e8FjgJx6pzorQnNKVw8u+ojfI/HzT3EKxQeYf/Hx6rtIc8w+fnDtGZY55R8O8LyvMMf9Qb5J5ek/9N5PKUsw/3nP/DLkp5t2/++Xyb7kcY5j3t/96/fcnFykJMO//8++bHl0666s5Gg5x876u4wef+dkPZ1WVJrQbWuaDly+cPfWXdPt77/yh9dArLzQ88uN753578rgxwwX7t/4Gpd/WjA==' ZLIB_B64_BIN = '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'
pen = FreeTypePen(None) pen = FreeTypePen(None)
draw_cubic(pen) draw_cubic(pen)
offset, width, height = (0, 0), 500, 500 width, height = 500, 500
buf1, _ = pen.buffer(offset=offset, width=width, height=height) buf1, _ = pen.buffer(width=width, height=height)
buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
self.assertEqual(len(buf1), len(buf2)) self.assertEqual(len(buf1), len(buf2))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) 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==' ZLIB_B64_BIN = 'eJy91r8NgkAUx/FLMHECCLMQC5gGK3UB4gJEXABhAbTAJXAJ7GkoBIornrQif/w2vvo+yXH37vFTqi/5rKYs8jhwDDVdMlp15ttM9NVFE2ZSiLShCYVI5VIhekeFSLKmQhIsZLixZaFdKqQyqZAQi9amQiIsOpsK8bHIsKgNKsTBIsAixiLHosCixKLB4vWHXfEv56fLb5B3Ce5E3u1XRQV+tXwy4OnDJxyeopUFhfYUFHsFRaqgSOGfUx+G65cSgPcNZlPGyRoBM0nmjJJMfdv+mpaa5+N+OW5W44vfouHQiw=='
pen = FreeTypePen(None) pen = FreeTypePen(None)
draw_cubic(pen) draw_cubic(pen)
offset, width, height = (0, 0), 500, 500 t = Scale(0.1, 0.1)
buf1, size = pen.buffer(offset=offset, width=width, height=height, 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)) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
self.assertEqual(size, (50, 50)) self.assertEqual(size, (50, 50))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
def test_empty(self): def test_empty(self):
pen = FreeTypePen(None) pen = FreeTypePen(None)
offset, width, height = (0, 0), 500, 500 width, height = 500, 500
buf, size = pen.buffer(offset=offset, width=width, height=height) 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): 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=' ZLIB_B64_BIN = 'eJzt2L0NglAUhmESTZxA4yzGAqfRSl3AuABRF/BnAbTAJXAJ7GkoBAqKo4WNhbk3OZ4vknzvAk/g/nC5QcAYY4wxxhhrRfJZmaXJfjXqWBrving6tDZe1dufKV8NkSrqmxsieWhvSDO3N0SOPXtDjgBD9K/LbTShvSG5dgp7GBIBjEq54n0M2QKMWvcgXoZMAUYMMArVR8vPkBHAWAGMPcBIAEYKMDKAUQKMB8BAvCvEmCPmLmINIvYSwJ6I2NvPGuJ/vrWIMwPg7IM4wwHOovnA3GgmSsLDWGgJt3FSE07jZP7P2Sz1gusOQD3cLqPaaCety6h3xncyxWVmd7dU3m/Xw3rc/R3AGGOMsbb3BMrP0Is='
pen = FreeTypePen(None) pen = FreeTypePen(None)
draw_cubic(pen) draw_cubic(pen)
offset, width, height = (0, 200), 1000, 1000 t = Scale(0.1, 0.1)
buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.1, 0.1), evenOdd=False) 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)) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
self.assertEqual(len(buf1), len(buf2)) self.assertEqual(len(buf1), len(buf2))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) 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=' ZLIB_B64_BIN = 'eJzt2L0NglAUhmESTZxA4yzGAqfRSl3AuABRF/BnAbTAJXAJ7GkoBAqKo4WNhbk3OZ4vknzvAk/g/nC5QcAYY4wxxhhrRfJZmaXJfjXqWBrving6tDZe1dufKV8NkSrqmxsieWhvSDO3N0SOPXtDjgBD9K/LbTShvSG5dgp7GBIBjEq54n0M2QKMWvcgXoZMAUYMMArVR8vPkBHAWAGMPcBIAEYKMDKAUQKMB8BAvCvEmCPmLmINIvYSwJ6I2NvPGuJ/vrWIMwPg7IM4wwHOovnA3GgmSsLDWGgJt3FSE07jZP7P2Sz1gusOQD3cLqPaaCety6h3xncyxWVmd7dU3m/Xw3rc/R3AGGOMsbb3BMrP0Is='
pen = FreeTypePen(None) pen = FreeTypePen(None)
draw_cubic(pen) draw_cubic(pen)
offset, width, height = (0, 200), 1000, 1000 t = Scale(0.1, 0.1)
buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.1, 0.1), evenOdd=True) 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)) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
self.assertEqual(len(buf1), len(buf2)) self.assertEqual(len(buf1), len(buf2))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
@ -102,9 +109,9 @@ class FreeTypePenTest(unittest.TestCase):
pen1, pen2 = FreeTypePen(None), FreeTypePen(None) pen1, pen2 = FreeTypePen(None), FreeTypePen(None)
draw_cubic(pen1) draw_cubic(pen1)
draw_quadratic(pen2) draw_quadratic(pen2)
offset, width, height = (0, 0), 500, 500 width, height = 500, 500
buf1, _ = pen1.buffer(offset=offset, width=width, height=height) buf1, _ = pen1.buffer(width=width, height=height)
buf2, _ = pen2.buffer(offset=offset, width=width, height=height) buf2, _ = pen2.buffer(width=width, height=height)
self.assertEqual(len(buf1), len(buf2)) self.assertEqual(len(buf1), len(buf2))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) 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=' ZLIB_B64_BIN = 'eJyVlKtvAkEQh5dHCEG0vQTbJgjOIVqB7VW0BoUsCQqHLElP4VCkV8sfQBpcqzCYYjEIDClNEFiaSxCEEB7bO/aWO2Bn2fmp22G+3ONjhhBxdB34AUzlBUt0v5GAtlpd4YgCpc84okXpBwqI2pTaUQxhUCf3GMJyiTcMMXKJHwSg010Q2iuMQGjvMkJdu7ZihLr2AvWirL3FCVXtrnAWVe0G3UdRu+UTIu2lBUVlUSJ3YwwwvnXuorXVgba2e7BQdaPWv6mG+Ms8/akA08fA+9/0zgO964NPFmucAxqx489cnMv650WBmcwvDIwyQteXXxDweQH9P8y1qH9tQv1OHkSEIQEIeT8FLCnAJzwY+bTzCQ9GPu2FU+DMtLdEhGza/QkPRjbthgiQTntgwodD/1qy5Ef7pnokUt8f4CWv85ZZ3j3mZ/wMLnlvpdNBmp3TA68ALnlPeDPBC4kmq0DamfBlOVgrL90apH0nfJI9LGYnEu2u8E7yuJrsgNod4dta+LQerm0B7Qa1c+Kb52yxdqufEgOEpPpC7WYcAgiJv/rX/4vPJ4U='
pen = FreeTypePen(None) pen = FreeTypePen(None)
star(pen) star(pen)
offset, width, height = (0, 0), 0, 0 t = Scale(0.05, 0.05)
buf1, size = pen.buffer(offset=offset, width=width, height=height, scale=(0.05, 0.05), contain=True) width, height = 0, 0
buf1, size = pen.buffer(width=width, height=height, transform=t, contain=True)
buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN)) buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
self.assertEqual(len(buf1), len(buf2)) self.assertEqual(len(buf1), len(buf2))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)