2022-01-06 22:10:05 +09:00

151 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""A pen that rasterises outlines with FreeType."""
__all__ = ['FTPen']
import os
import ctypes
import platform
import subprocess
import collections
import math
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_errors import FT_Exception
from fontTools.pens.basePen import BasePen
from fontTools.misc.roundTools import otRound
class FTPen(BasePen):
Contour = collections.namedtuple('Contour', ('points', 'tags'))
LINE = 0b00000001
CURVE = 0b00000011
OFFCURVE = 0b00000010
QCURVE = 0b00000001
QOFFCURVE = 0b00000000
def __init__(self, glyphSet):
self.contours = []
def outline(self, offset=None, scale=None, even_odd=False):
# Convert the current contours to FT_Outline.
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.
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)
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=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].
import numpy as np
buf, size = self.buffer(width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
return 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.
from matplotlib import pyplot as plt
a = self.array(width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
plt.imshow(a, cmap='gray_r', vmin=0, vmax=1)
plt.show()
def image(self, width=1000, ascender=880, descender=-120, even_odd=False, scale=None):
# Return a PIL image.
from PIL import Image
buf, size = self.buffer(width, ascender=ascender, descender=descender, even_odd=even_odd, scale=scale)
img = Image.new('L', size, 0)
img.putalpha(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()
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()
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)