Merge pull request #2517 from takaakifuji/ftpen-transform-fix
freetypePen: fit to content when rotated/skewed
This commit is contained in:
commit
d1eca1006a
@ -2,7 +2,7 @@
|
||||
|
||||
"""Pen to rasterize paths with FreeType."""
|
||||
|
||||
__all__ = ['FreeTypePen']
|
||||
__all__ = ["FreeTypePen"]
|
||||
|
||||
import os
|
||||
import ctypes
|
||||
@ -15,19 +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
|
||||
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
|
||||
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'))
|
||||
LINE = 0b00000001
|
||||
CURVE = 0b00000011
|
||||
OFFCURVE = 0b00000010
|
||||
QCURVE = 0b00000001
|
||||
QOFFCURVE = 0b00000000
|
||||
Contour = collections.namedtuple("Contour", ("points", "tags"))
|
||||
|
||||
|
||||
class FreeTypePen(BasePen):
|
||||
"""Pen to rasterize paths with FreeType. Requires `freetype-py` module.
|
||||
@ -114,21 +117,25 @@ class FreeTypePen(BasePen):
|
||||
"""Converts the current contours to ``FT_Outline``.
|
||||
|
||||
Args:
|
||||
transform: A optional 6-tuple containing an affine transformation,
|
||||
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'):
|
||||
if not hasattr(transform, "transformPoint"):
|
||||
transform = Transform(*transform)
|
||||
nContours = len(self.contours)
|
||||
n_points = sum((len(contour.points) for contour in self.contours))
|
||||
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))))
|
||||
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:
|
||||
@ -140,15 +147,17 @@ class FreeTypePen(BasePen):
|
||||
contours.append(contours_sum - 1)
|
||||
flags = FT_OUTLINE_EVEN_ODD_FILL if evenOdd else FT_OUTLINE_NONE
|
||||
return FT_Outline(
|
||||
(ctypes.c_short)(nContours),
|
||||
(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 * nContours)(*contours),
|
||||
(ctypes.c_int)(flags)
|
||||
(ctypes.c_short * n_contours)(*contours),
|
||||
(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:
|
||||
@ -156,7 +165,7 @@ class FreeTypePen(BasePen):
|
||||
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: A optional 6-tuple containing an affine transformation,
|
||||
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
|
||||
@ -169,33 +178,54 @@ class FreeTypePen(BasePen):
|
||||
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'):
|
||||
if not hasattr(transform, "transformPoint"):
|
||||
transform = Transform(*transform)
|
||||
contain_x, contain_y = contain or width is None, contain or height is None
|
||||
width, height = width or 0, height or 0
|
||||
if contain_x or contain_y:
|
||||
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]
|
||||
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:
|
||||
dx = min(-dx, bbox[0]) * -1.0
|
||||
width = max(width, bbox_size[0])
|
||||
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:
|
||||
dy = min(-dy, bbox[1]) * -1.0
|
||||
height = max(height, bbox_size[1])
|
||||
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)
|
||||
@ -207,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:
|
||||
@ -223,7 +257,7 @@ class FreeTypePen(BasePen):
|
||||
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: A optional 6-tuple containing an affine transformation,
|
||||
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
|
||||
@ -235,9 +269,19 @@ class FreeTypePen(BasePen):
|
||||
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)
|
||||
@ -245,10 +289,19 @@ class FreeTypePen(BasePen):
|
||||
(<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):
|
||||
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`.
|
||||
|
||||
@ -257,7 +310,7 @@ class FreeTypePen(BasePen):
|
||||
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: A optional 6-tuple containing an affine transformation,
|
||||
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
|
||||
@ -265,19 +318,38 @@ class FreeTypePen(BasePen):
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
@ -286,7 +358,7 @@ class FreeTypePen(BasePen):
|
||||
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: A optional 6-tuple containing an affine transformation,
|
||||
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
|
||||
@ -298,9 +370,19 @@ class FreeTypePen(BasePen):
|
||||
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)
|
||||
@ -308,9 +390,16 @@ class FreeTypePen(BasePen):
|
||||
(<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))
|
||||
|
||||
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
|
||||
@ -341,22 +430,28 @@ class FreeTypePen(BasePen):
|
||||
contour = Contour([], [])
|
||||
self.contours.append(contour)
|
||||
contour.points.append(pt)
|
||||
contour.tags.append(LINE)
|
||||
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(LINE)
|
||||
contour.tags.append(FT_CURVE_TAG_ON)
|
||||
|
||||
def _curveToOne(self, p1, p2, p3):
|
||||
t1, t2, t3 = OFFCURVE, OFFCURVE, CURVE
|
||||
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):
|
||||
t1, t2 = QOFFCURVE, QCURVE
|
||||
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)
|
||||
|
BIN
Tests/pens/data/test_rotate.pgm
Normal file
BIN
Tests/pens/data/test_rotate.pgm
Normal file
Binary file not shown.
BIN
Tests/pens/data/test_skew.pgm
Normal file
BIN
Tests/pens/data/test_skew.pgm
Normal file
Binary file not shown.
@ -1,23 +1,27 @@
|
||||
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')
|
||||
DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
|
||||
|
||||
def box(pen):
|
||||
pen.moveTo((0, 0))
|
||||
pen.lineTo((0, 500))
|
||||
pen.lineTo((500, 500))
|
||||
pen.lineTo((500, 0))
|
||||
|
||||
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))
|
||||
@ -26,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))
|
||||
@ -34,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))
|
||||
@ -42,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):
|
||||
@ -76,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)
|
||||
@ -98,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)
|
||||
@ -110,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)
|
||||
@ -131,29 +143,93 @@ 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)
|
||||
|
||||
def test_none_width(self):
|
||||
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, 1000
|
||||
width, height = None, None
|
||||
buf1, size = pen.buffer(width=width, height=height, transform=Offset(0, 200))
|
||||
buf2, _ = pen.buffer(width=1000, 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)
|
||||
|
||||
def test_none_height(self):
|
||||
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 = 1000, None
|
||||
buf1, size = pen.buffer(width=width, height=height)
|
||||
buf2, _ = pen.buffer(width=width, height=1000, transform=Offset(0, 200))
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
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())
|
||||
|
Loading…
x
Reference in New Issue
Block a user