Add FreeType-based Pen for rasterisation
This commit is contained in:
parent
892322aaff
commit
e2fad4db11
232
Lib/fontTools/pens/ftPen.py
Executable file
232
Lib/fontTools/pens/ftPen.py
Executable file
@ -0,0 +1,232 @@
|
||||
"""A pen that rasterises outlines with FreeType."""
|
||||
|
||||
__all__ = ['FTPen']
|
||||
|
||||
import os
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import platform
|
||||
import subprocess
|
||||
import collections
|
||||
import math
|
||||
|
||||
from fontTools.pens.basePen import BasePen
|
||||
from fontTools.misc.roundTools import otRound
|
||||
|
||||
class FT_LibraryRec(ctypes.Structure):
|
||||
_fields_ = []
|
||||
|
||||
FT_Library = ctypes.POINTER(FT_LibraryRec)
|
||||
FT_Pos = ctypes.c_long
|
||||
|
||||
class FT_Vector(ctypes.Structure):
|
||||
_fields_ = [('x', FT_Pos), ('y', FT_Pos)]
|
||||
|
||||
class FT_BBox(ctypes.Structure):
|
||||
_fields_ = [('xMin', FT_Pos), ('yMin', FT_Pos), ('xMax', FT_Pos), ('yMax', FT_Pos)]
|
||||
|
||||
class FT_Bitmap(ctypes.Structure):
|
||||
_fields_ = [('rows', ctypes.c_int), ('width', ctypes.c_int), ('pitch', ctypes.c_int), ('buffer', ctypes.POINTER(ctypes.c_ubyte)), ('num_grays', ctypes.c_short), ('pixel_mode', ctypes.c_ubyte), ('palette_mode', ctypes.c_char), ('palette', ctypes.c_void_p)]
|
||||
|
||||
class FT_Outline(ctypes.Structure):
|
||||
_fields_ = [('n_contours', ctypes.c_short), ('n_points', ctypes.c_short), ('points', ctypes.POINTER(FT_Vector)), ('tags', ctypes.POINTER(ctypes.c_ubyte)), ('contours', ctypes.POINTER(ctypes.c_short)), ('flags', ctypes.c_int)]
|
||||
|
||||
class FreeType(object):
|
||||
|
||||
@staticmethod
|
||||
def load_freetype_lib():
|
||||
lib_path = ctypes.util.find_library('freetype')
|
||||
if lib_path:
|
||||
return ctypes.cdll.LoadLibrary(lib_path)
|
||||
if platform.system() == 'Darwin':
|
||||
# Try again by searching inside the installation paths of Homebrew and MacPorts
|
||||
# This workaround is needed if Homebrew has been installed to a non-standard location.
|
||||
orig_dyld_path = os.environ.get('DYLD_LIBRARY_PATH')
|
||||
for dyld_path_func in (
|
||||
lambda: os.path.join(subprocess.check_output(('brew', '--prefix'), universal_newlines=True).rstrip(), 'lib'),
|
||||
lambda: os.path.join(os.path.dirname(os.path.dirname(subprocess.check_output(('which', 'port'), universal_newlines=True).rstrip())), 'lib')
|
||||
):
|
||||
try:
|
||||
dyld_path = dyld_path_func()
|
||||
os.environ['DYLD_LIBRARY_PATH'] = ':'.join(os.environ.get('DYLD_LIBRARY_PATH', '').split(':') + [dyld_path])
|
||||
lib_path = ctypes.util.find_library('freetype')
|
||||
if lib_path:
|
||||
return ctypes.cdll.LoadLibrary(lib_path)
|
||||
except CalledProcessError:
|
||||
pass
|
||||
finally:
|
||||
if orig_dyld_path:
|
||||
os.environ['DYLD_LIBRARY_PATH'] = orig_dyld_path
|
||||
else:
|
||||
os.environ.pop('DYLD_LIBRARY_PATH', None)
|
||||
return None
|
||||
|
||||
def __init__(self):
|
||||
lib = self.load_freetype_lib()
|
||||
self.handle = FT_Library()
|
||||
self.FT_Init_FreeType = lib.FT_Init_FreeType
|
||||
self.FT_Done_FreeType = lib.FT_Done_FreeType
|
||||
self.FT_Library_Version = lib.FT_Library_Version
|
||||
self.FT_Outline_Get_CBox = lib.FT_Outline_Get_CBox
|
||||
self.FT_Outline_Get_BBox = lib.FT_Outline_Get_BBox
|
||||
self.FT_Outline_Get_Bitmap = lib.FT_Outline_Get_Bitmap
|
||||
self.raise_error_if_needed(self.FT_Init_FreeType(ctypes.byref(self.handle)))
|
||||
|
||||
def raise_error_if_needed(self, err):
|
||||
# See the reference for error codes:
|
||||
# https://freetype.org/freetype2/docs/reference/ft2-error_code_values.html
|
||||
if err != 0:
|
||||
raise RuntimeError("FT_Error: 0x{0:02X}".format(err))
|
||||
|
||||
def __del__(self):
|
||||
if self.handle:
|
||||
self.FT_Done_FreeType(self.handle)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
major, minor, patch = ctypes.c_int(), ctypes.c_int(), ctypes.c_int()
|
||||
self.FT_Library_Version(self.handle, ctypes.byref(major), ctypes.byref(minor), ctypes.byref(patch))
|
||||
return "{0}.{1}.{2}".format(major.value, minor.value, patch.value)
|
||||
|
||||
class FTPen(BasePen):
|
||||
|
||||
ft = None
|
||||
np = None
|
||||
plt = None
|
||||
Image = None
|
||||
Contour = collections.namedtuple('Contour', ('points', 'tags'))
|
||||
LINE = 0b00000001
|
||||
CURVE = 0b00000011
|
||||
OFFCURVE = 0b00000010
|
||||
QCURVE = 0b00000001
|
||||
QOFFCURVE = 0b00000000
|
||||
|
||||
def __init__(self, glyphSet):
|
||||
if not self.__class__.ft:
|
||||
self.__class__.ft = FreeType()
|
||||
self.contours = []
|
||||
|
||||
def outline(self, offset=None, scale=None, even_odd=False):
|
||||
# Convert the current contours to FT_Outline.
|
||||
FT_OUTLINE_NONE = 0x0
|
||||
FT_OUTLINE_EVEN_ODD_FILL = 0x2
|
||||
offset = offset or (0, 0)
|
||||
scale = scale or (1.0, 1.0)
|
||||
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:
|
||||
points.append(FT_Vector(FT_Pos(otRound((point[0] + offset[0]) * scale[0] * 64)), FT_Pos(otRound((point[1] + offset[1]) * scale[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 even_odd 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=1000, ascender=880, descender=-120, even_odd=False, scale=None):
|
||||
# Return a tuple with the bitmap buffer and its dimension.
|
||||
FT_PIXEL_MODE_GRAY = 2
|
||||
scale = scale or (1.0, 1.0)
|
||||
width = math.ceil(width * scale[0])
|
||||
height = math.ceil((ascender - descender) * scale[1])
|
||||
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(offset=(0, -descender), even_odd=even_odd, scale=scale)
|
||||
self.ft.raise_error_if_needed(self.ft.FT_Outline_Get_Bitmap(self.ft.handle, ctypes.byref(outline), ctypes.byref(bitmap)))
|
||||
return buf.raw, (width, height)
|
||||
|
||||
def array(self, width=1000, ascender=880, descender=-120, even_odd=False, scale=None):
|
||||
# Return a numpy array. Each element takes values in the range of [0.0, 1.0].
|
||||
if not self.np:
|
||||
import numpy as np
|
||||
self.np = np
|
||||
buf, size = self.buffer(width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
|
||||
return self.np.frombuffer(buf, 'B').reshape((size[1], size[0])) / 255.0
|
||||
|
||||
def show(self, width=1000, ascender=880, descender=-120, even_odd=False, scale=None):
|
||||
# Plot the image with matplotlib.
|
||||
if not self.plt:
|
||||
from matplotlib import pyplot
|
||||
self.plt = pyplot
|
||||
a = self.array(width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
|
||||
self.plt.imshow(a, cmap='gray_r', vmin=0, vmax=1)
|
||||
self.plt.show()
|
||||
|
||||
def image(self, width=1000, ascender=880, descender=-120, even_odd=False, scale=None):
|
||||
# Return a PIL image.
|
||||
if not self.Image:
|
||||
from PIL import Image as PILImage
|
||||
self.Image = PILImage
|
||||
buf, size = self.buffer(width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
|
||||
img = self.Image.new('L', size, 0)
|
||||
img.putalpha(self.Image.frombuffer('L', size, buf))
|
||||
return img
|
||||
|
||||
def save(self, fp, width=1000, ascender=880, descender=-120, even_odd=False, scale=None, format=None, **kwargs):
|
||||
# Save the image as a file.
|
||||
img = self.image(width=width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
|
||||
img.save(fp, format=format, **kwargs)
|
||||
|
||||
@property
|
||||
def bbox(self):
|
||||
# Compute the exact bounding box of an outline.
|
||||
bbox = FT_BBox()
|
||||
outline = self.outline()
|
||||
self.ft.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):
|
||||
# Return an outline's ‘control box’.
|
||||
cbox = FT_BBox()
|
||||
outline = self.outline()
|
||||
self.ft.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 = self.Contour([], [])
|
||||
self.contours.append(contour)
|
||||
contour.points.append(pt)
|
||||
contour.tags.append(self.LINE)
|
||||
|
||||
def _lineTo(self, pt):
|
||||
contour = self.contours[-1]
|
||||
contour.points.append(pt)
|
||||
contour.tags.append(self.LINE)
|
||||
|
||||
def _curveToOne(self, p1, p2, p3):
|
||||
t1, t2, t3 = self.OFFCURVE, self.OFFCURVE, self.CURVE
|
||||
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 = self.QOFFCURVE, self.QCURVE
|
||||
contour = self.contours[-1]
|
||||
for p, t in ((p1, t1), (p2, t2)):
|
||||
contour.points.append(p)
|
||||
contour.tags.append(t)
|
105
Tests/pens/ftPen_test.py
Normal file
105
Tests/pens/ftPen_test.py
Normal file
@ -0,0 +1,105 @@
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from ftPen import FTPen
|
||||
FREETYPE_AVAILABLE = True
|
||||
except ImportError:
|
||||
FREETYPE_AVAILABLE = False
|
||||
|
||||
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()
|
||||
|
||||
@unittest.skipUnless(FREETYPE_AVAILABLE, "freetype not installed")
|
||||
class FTPenTest(unittest.TestCase):
|
||||
def test_draw(self):
|
||||
import base64, zlib
|
||||
ZLIB_B64_BIN = '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'
|
||||
pen = FTPen(None)
|
||||
draw_cubic(pen)
|
||||
width, ascender, descender = 500, 500, 0
|
||||
buf1, _ = pen.buffer(width, ascender, descender)
|
||||
buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
def test_scale(self):
|
||||
import base64, zlib
|
||||
ZLIB_B64_BIN = 'eJy91r8NgkAUx/FLMHECCLMQC5gGK3UB4gJEXABhAbTAJXAJ7GkoBIornrQif/w2vvo+yXH37vFTqi/5rKYs8jhwDDVdMlp15ttM9NVFE2ZSiLShCYVI5VIhekeFSLKmQhIsZLixZaFdKqQyqZAQi9amQiIsOpsK8bHIsKgNKsTBIsAixiLHosCixKLB4vWHXfEv56fLb5B3Ce5E3u1XRQV+tXwy4OnDJxyeopUFhfYUFHsFRaqgSOGfUx+G65cSgPcNZlPGyRoBM0nmjJJMfdv+mpaa5+N+OW5W44vfouHQiw=='
|
||||
pen = FTPen(None)
|
||||
draw_cubic(pen)
|
||||
width, ascender, descender = 500, 500, 0
|
||||
buf1, size = pen.buffer(width, ascender, descender, scale=(0.1, 0.1))
|
||||
buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
|
||||
self.assertEqual(buf1, buf2)
|
||||
self.assertEqual(size, (50, 50))
|
||||
|
||||
def test_empty(self):
|
||||
pen = FTPen(None)
|
||||
width, ascender, descender = 500, 880, -120
|
||||
buf, size = pen.buffer(width, ascender, descender)
|
||||
self.assertEqual(b'\0' * size[0] * size[1], buf)
|
||||
|
||||
def test_bbox_and_cbox(self):
|
||||
pen = FTPen(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):
|
||||
import base64, zlib
|
||||
ZLIB_B64_BIN = 'eJzt2L0NglAUhmESTZxA4yzGAqfRSl3AuABRF/BnAbTAJXAJ7GkoBAqKo4WNhbk3OZ4vknzvAk/g/nC5QcAYY4wxxhhrRfJZmaXJfjXqWBrving6tDZe1dufKV8NkSrqmxsieWhvSDO3N0SOPXtDjgBD9K/LbTShvSG5dgp7GBIBjEq54n0M2QKMWvcgXoZMAUYMMArVR8vPkBHAWAGMPcBIAEYKMDKAUQKMB8BAvCvEmCPmLmINIvYSwJ6I2NvPGuJ/vrWIMwPg7IM4wwHOovnA3GgmSsLDWGgJt3FSE07jZP7P2Sz1gusOQD3cLqPaaCety6h3xncyxWVmd7dU3m/Xw3rc/R3AGGOMsbb3BMrP0Is='
|
||||
pen = FTPen(None)
|
||||
draw_cubic(pen)
|
||||
width, ascender, descender = 1000, 800, -200
|
||||
buf1, size = pen.buffer(width, ascender, descender, scale=(0.1, 0.1), even_odd=False)
|
||||
buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
def test_even_odd_fill(self):
|
||||
import base64, zlib
|
||||
ZLIB_B64_BIN = 'eJzt2L0NglAUhmESTZxA4yzGAqfRSl3AuABRF/BnAbTAJXAJ7GkoBAqKo4WNhbk3OZ4vknzvAk/g/nC5QcAYY4wxxhhrRfJZmaXJfjXqWBrving6tDZe1dufKV8NkSrqmxsieWhvSDO3N0SOPXtDjgBD9K/LbTShvSG5dgp7GBIBjEq54n0M2QKMWvcgXoZMAUYMMArVR8vPkBHAWAGMPcBIAEYKMDKAUQKMB8BAvCvEmCPmLmINIvYSwJ6I2NvPGuJ/vrWIMwPg7IM4wwHOovnA3GgmSsLDWGgJt3FSE07jZP7P2Sz1gusOQD3cLqPaaCety6h3xncyxWVmd7dU3m/Xw3rc/R3AGGOMsbb3BMrP0Is='
|
||||
pen = FTPen(None)
|
||||
draw_cubic(pen)
|
||||
width, ascender, descender = 1000, 800, -200
|
||||
buf1, size = pen.buffer(width, ascender, descender, scale=(0.1, 0.1), even_odd=True)
|
||||
buf2 = zlib.decompress(base64.b64decode(ZLIB_B64_BIN))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
def test_cubic_vs_quadratic(self):
|
||||
# Assume the buffers are equal when PSNR > 38dB.
|
||||
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))
|
||||
pen1, pen2 = FTPen(None), FTPen(None)
|
||||
draw_cubic(pen1)
|
||||
draw_quadratic(pen2)
|
||||
width, ascender, descender = 500, 500, 0
|
||||
buf1, _ = pen1.buffer(width, ascender, descender)
|
||||
buf2, _ = pen2.buffer(width, ascender, descender)
|
||||
self.assertEqual(len(buf1), len(buf2))
|
||||
self.assertGreater(psnr(buf1, buf2), 38.0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
Loading…
x
Reference in New Issue
Block a user