Add documentation
Note that the example covers a ltr/rtl/ttb typesetting with uharfbuzz.
This commit is contained in:
parent
f7c29e89be
commit
e4c1deb64f
8
Doc/source/pens/ftPen.rst
Normal file
8
Doc/source/pens/ftPen.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
##########
|
||||||
|
ftPen
|
||||||
|
##########
|
||||||
|
|
||||||
|
.. automodule:: fontTools.pens.ftPen
|
||||||
|
:inherited-members:
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
@ -11,6 +11,7 @@ pens
|
|||||||
cocoaPen
|
cocoaPen
|
||||||
cu2quPen
|
cu2quPen
|
||||||
filterPen
|
filterPen
|
||||||
|
ftPen
|
||||||
momentsPen
|
momentsPen
|
||||||
perimeterPen
|
perimeterPen
|
||||||
pointInsidePen
|
pointInsidePen
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""A pen that rasterises outlines with FreeType."""
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Pen to rasterize paths with FreeType."""
|
||||||
|
|
||||||
__all__ = ['FTPen']
|
__all__ = ['FTPen']
|
||||||
|
|
||||||
@ -19,23 +21,103 @@ 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
|
||||||
|
|
||||||
class FTPen(BasePen):
|
Contour = collections.namedtuple('Contour', ('points', 'tags'))
|
||||||
|
LINE = 0b00000001
|
||||||
|
CURVE = 0b00000011
|
||||||
|
OFFCURVE = 0b00000010
|
||||||
|
QCURVE = 0b00000001
|
||||||
|
QOFFCURVE = 0b00000000
|
||||||
|
|
||||||
Contour = collections.namedtuple('Contour', ('points', 'tags'))
|
class FTPen(BasePen):
|
||||||
LINE = 0b00000001
|
"""Pen to rasterize paths with FreeType. Requires `freetype-py` module.
|
||||||
CURVE = 0b00000011
|
|
||||||
OFFCURVE = 0b00000010
|
Constructs ``FT_Outline`` from the paths, and renders it within a bitmap
|
||||||
QCURVE = 0b00000001
|
buffer.
|
||||||
QOFFCURVE = 0b00000000
|
|
||||||
|
For ``array()`` and ``show()``, `numpy` and `matplotlib` must be installed.
|
||||||
|
For ``image()`` and ``save()``, `Pillow` is required. Each module is lazily
|
||||||
|
loaded when the corresponding method is called.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
glyphSet: a dictionary of drawable glyph objects keyed by name
|
||||||
|
used to resolve component references in composite glyphs.
|
||||||
|
|
||||||
|
:Examples:
|
||||||
|
If `numpy` and `matplotlib` is available, the following code will
|
||||||
|
show the glyph image of `fi` in a new window::
|
||||||
|
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools.pens.ftPen import FTPen
|
||||||
|
pen = FTPen(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)
|
||||||
|
|
||||||
|
Combining with `uharfbuzz`, you can typeset a chunk of glyphs in a pen::
|
||||||
|
|
||||||
|
import uharfbuzz as hb
|
||||||
|
from fontTools.pens.ftPen import FTPen
|
||||||
|
from fontTools.pens.transformPen import TransformPen
|
||||||
|
from fontTools.misc.transform import Offset
|
||||||
|
|
||||||
|
en1, en2, ar, ja = 'Typesetting', 'Jeff', 'صف الحروف', 'たいぷせっと'
|
||||||
|
for text, font_path, direction, typo_ascender, typo_descender, vhea_ascender, vhea_descender, contain, features in (
|
||||||
|
(en1, 'NotoSans-Regular.ttf', 'ltr', 2189, -600, None, None, False, {"kern": True, "liga": True}),
|
||||||
|
(en2, 'NotoSans-Regular.ttf', 'ltr', 2189, -600, None, None, True, {"kern": True, "liga": True}),
|
||||||
|
(ar, 'NotoSansArabic-Regular.ttf', 'rtl', 1374, -738, None, None, False, {"kern": True, "liga": True}),
|
||||||
|
(ja, 'NotoSansJP-Regular.otf', 'ltr', 880, -120, 500, -500, False, {"palt": True, "kern": True}),
|
||||||
|
(ja, 'NotoSansJP-Regular.otf', 'ttb', 880, -120, 500, -500, False, {"vert": True, "vpal": True, "vkrn": True})
|
||||||
|
):
|
||||||
|
blob = hb.Blob.from_file_path(font_path)
|
||||||
|
face = hb.Face(blob)
|
||||||
|
font = hb.Font(face)
|
||||||
|
buf = hb.Buffer()
|
||||||
|
buf.direction = direction
|
||||||
|
buf.add_str(text)
|
||||||
|
buf.guess_segment_properties()
|
||||||
|
hb.shape(font, buf, features)
|
||||||
|
|
||||||
|
x, y = 0, 0
|
||||||
|
pen = FTPen(None)
|
||||||
|
for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
|
||||||
|
gid = info.codepoint
|
||||||
|
transformed = TransformPen(pen, Offset(x + pos.x_offset, y + pos.y_offset))
|
||||||
|
font.draw_glyph_with_pen(gid, transformed)
|
||||||
|
x += pos.x_advance
|
||||||
|
y += pos.y_advance
|
||||||
|
|
||||||
|
offset, width, height = None, None, None
|
||||||
|
if direction in ('ltr', 'rtl'):
|
||||||
|
offset = (0, -typo_descender)
|
||||||
|
width = x
|
||||||
|
height = typo_ascender - typo_descender
|
||||||
|
else:
|
||||||
|
offset = (-vhea_descender, -y)
|
||||||
|
width = vhea_ascender - vhea_descender
|
||||||
|
height = -y
|
||||||
|
pen.show(offset=offset, width=width, height=height, contain=contain)
|
||||||
|
|
||||||
|
For Jupyter Notebook, the rendered image will be displayed in a cell if
|
||||||
|
you replace ``show()`` with ``image()`` in the examples.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, glyphSet):
|
def __init__(self, glyphSet):
|
||||||
self.contours = []
|
self.contours = []
|
||||||
|
|
||||||
def outline(self, offset=None, scale=None, even_odd=False):
|
def outline(self, offset=None, scale=None, even_odd=False):
|
||||||
# Convert the current contours to FT_Outline.
|
"""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.
|
||||||
|
even_odd: Pass ``True`` for even-odd fill instead of non-zero.
|
||||||
|
"""
|
||||||
offset = offset or (0, 0)
|
offset = offset or (0, 0)
|
||||||
scale = scale or (1.0, 1.0)
|
scale = scale or (1.0, 1.0)
|
||||||
n_contours = 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:
|
||||||
@ -52,16 +134,41 @@ class FTPen(BasePen):
|
|||||||
contours.append(contours_sum - 1)
|
contours.append(contours_sum - 1)
|
||||||
flags = FT_OUTLINE_EVEN_ODD_FILL if even_odd else FT_OUTLINE_NONE
|
flags = FT_OUTLINE_EVEN_ODD_FILL if even_odd else FT_OUTLINE_NONE
|
||||||
return FT_Outline(
|
return FT_Outline(
|
||||||
(ctypes.c_short)(n_contours),
|
(ctypes.c_short)(nContours),
|
||||||
(ctypes.c_short)(n_points),
|
(ctypes.c_short)(n_points),
|
||||||
(FT_Vector * n_points)(*points),
|
(FT_Vector * n_points)(*points),
|
||||||
(ctypes.c_ubyte * n_points)(*tags),
|
(ctypes.c_ubyte * n_points)(*tags),
|
||||||
(ctypes.c_short * n_contours)(*contours),
|
(ctypes.c_short * nContours)(*contours),
|
||||||
(ctypes.c_int)(flags)
|
(ctypes.c_int)(flags)
|
||||||
)
|
)
|
||||||
|
|
||||||
def buffer(self, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False):
|
def buffer(self, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False):
|
||||||
# Return a tuple with the bitmap buffer and its dimension.
|
"""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.
|
||||||
|
even_odd: 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
|
||||||
|
rendering glyphs with negative sidebearings without clipping.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple of ``(buffer, size)``, where ``buffer`` is a ``bytes``
|
||||||
|
object of the resulted bitmap and ``size` is a 2-tuple of its
|
||||||
|
dimension.
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
>>> pen = FTPen(None)
|
||||||
|
>>> glyph.draw(pen)
|
||||||
|
>>> buf, size = pen.buffer(width=500, height=1000)
|
||||||
|
>>> type(buf), len(buf), size
|
||||||
|
(<class 'bytes'>, 500000, (500, 1000))
|
||||||
|
"""
|
||||||
offset_x, offset_y = offset or (0, 0)
|
offset_x, offset_y = offset or (0, 0)
|
||||||
if contain:
|
if contain:
|
||||||
bbox = self.bbox
|
bbox = self.bbox
|
||||||
@ -91,20 +198,88 @@ class FTPen(BasePen):
|
|||||||
return buf.raw, (width, height)
|
return buf.raw, (width, height)
|
||||||
|
|
||||||
def array(self, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False):
|
def array(self, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False):
|
||||||
# Return a numpy array. Each element takes values in the range of [0.0, 1.0].
|
"""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.
|
||||||
|
even_odd: 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
|
||||||
|
rendering glyphs with negative sidebearings without clipping.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A ``numpy.ndarray`` object with a shape of ``(height, width)``.
|
||||||
|
Each element takes a value in the range of ``[0.0, 1.0]``.
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
>>> pen = FTPen(None)
|
||||||
|
>>> glyph.draw(pen)
|
||||||
|
>>> arr = pen.array(width=500, height=1000)
|
||||||
|
>>> type(a), a.shape
|
||||||
|
(<class 'numpy.ndarray'>, (1000, 500))
|
||||||
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
buf, size = self.buffer(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, contain=contain)
|
buf, size = self.buffer(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, 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, even_odd=False, scale=None, contain=False):
|
def show(self, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False):
|
||||||
# Plot the image with matplotlib.
|
"""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.
|
||||||
|
even_odd: 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
|
||||||
|
rendering glyphs with negative sidebearings without clipping.
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
>>> pen = FTPen(None)
|
||||||
|
>>> glyph.draw(pen)
|
||||||
|
>>> 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, even_odd=even_odd, scale=scale, contain=contain)
|
a = self.array(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, 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, even_odd=False, scale=None, contain=False):
|
def image(self, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False):
|
||||||
# Return a PIL image.
|
"""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.
|
||||||
|
even_odd: 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
|
||||||
|
rendering glyphs with negative sidebearings without clipping.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A ``PIL.image`` object. The image is filled in black with alpha
|
||||||
|
channel obtained from the rendered bitmap.
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
>>> pen = FTPen(None)
|
||||||
|
>>> glyph.draw(pen)
|
||||||
|
>>> img = pen.image(width=500, height=1000)
|
||||||
|
>>> type(img), img.size
|
||||||
|
(<class 'PIL.Image.Image'>, (500, 1000))
|
||||||
|
"""
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
buf, size = self.buffer(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, contain=contain)
|
buf, size = self.buffer(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, contain=contain)
|
||||||
img = Image.new('L', size, 0)
|
img = Image.new('L', size, 0)
|
||||||
@ -112,13 +287,38 @@ class FTPen(BasePen):
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
def save(self, fp, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False, format=None, **kwargs):
|
def save(self, fp, offset=None, width=1000, height=1000, even_odd=False, scale=None, contain=False, format=None, **kwargs):
|
||||||
# Save the image as a file.
|
"""Saves the image as a file. Requires `Pillow`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fp: A filename (string), pathlib. Path object or file object.
|
||||||
|
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.
|
||||||
|
even_odd: 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
|
||||||
|
rendering glyphs with negative sidebearings without clipping.
|
||||||
|
format: Optional format override. If omitted, the format to use is
|
||||||
|
determined from the filename extension.
|
||||||
|
|
||||||
|
:Example:
|
||||||
|
>>> pen = FTPen(None)
|
||||||
|
>>> glyph.draw(pen)
|
||||||
|
>>> pen.save('glyph.png' width=500, height=1000)
|
||||||
|
"""
|
||||||
img = self.image(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, contain=contain)
|
img = self.image(offset=offset, width=width, height=height, even_odd=even_odd, scale=scale, contain=contain)
|
||||||
img.save(fp, format=format, **kwargs)
|
img.save(fp, format=format, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bbox(self):
|
def bbox(self):
|
||||||
# Compute the exact bounding box of an outline.
|
"""Computes the exact bounding box of an outline.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple of ``(xMin, yMin, xMax, yMax)``.
|
||||||
|
"""
|
||||||
bbox = FT_BBox()
|
bbox = FT_BBox()
|
||||||
outline = self.outline()
|
outline = self.outline()
|
||||||
FT_Outline_Get_BBox(ctypes.byref(outline), ctypes.byref(bbox))
|
FT_Outline_Get_BBox(ctypes.byref(outline), ctypes.byref(bbox))
|
||||||
@ -126,32 +326,36 @@ class FTPen(BasePen):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def cbox(self):
|
def cbox(self):
|
||||||
# Return an outline's ‘control box’.
|
"""Returns an outline's ‘control box’.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple of ``(xMin, yMin, xMax, yMax)``.
|
||||||
|
"""
|
||||||
cbox = FT_BBox()
|
cbox = FT_BBox()
|
||||||
outline = self.outline()
|
outline = self.outline()
|
||||||
FT_Outline_Get_CBox(ctypes.byref(outline), ctypes.byref(cbox))
|
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)
|
return (cbox.xMin / 64.0, cbox.yMin / 64.0, cbox.xMax / 64.0, cbox.yMax / 64.0)
|
||||||
|
|
||||||
def _moveTo(self, pt):
|
def _moveTo(self, pt):
|
||||||
contour = self.Contour([], [])
|
contour = Contour([], [])
|
||||||
self.contours.append(contour)
|
self.contours.append(contour)
|
||||||
contour.points.append(pt)
|
contour.points.append(pt)
|
||||||
contour.tags.append(self.LINE)
|
contour.tags.append(LINE)
|
||||||
|
|
||||||
def _lineTo(self, pt):
|
def _lineTo(self, pt):
|
||||||
contour = self.contours[-1]
|
contour = self.contours[-1]
|
||||||
contour.points.append(pt)
|
contour.points.append(pt)
|
||||||
contour.tags.append(self.LINE)
|
contour.tags.append(LINE)
|
||||||
|
|
||||||
def _curveToOne(self, p1, p2, p3):
|
def _curveToOne(self, p1, p2, p3):
|
||||||
t1, t2, t3 = self.OFFCURVE, self.OFFCURVE, self.CURVE
|
t1, t2, t3 = OFFCURVE, OFFCURVE, CURVE
|
||||||
contour = self.contours[-1]
|
contour = self.contours[-1]
|
||||||
for p, t in ((p1, t1), (p2, t2), (p3, t3)):
|
for p, t in ((p1, t1), (p2, t2), (p3, t3)):
|
||||||
contour.points.append(p)
|
contour.points.append(p)
|
||||||
contour.tags.append(t)
|
contour.tags.append(t)
|
||||||
|
|
||||||
def _qCurveToOne(self, p1, p2):
|
def _qCurveToOne(self, p1, p2):
|
||||||
t1, t2 = self.QOFFCURVE, self.QCURVE
|
t1, t2 = QOFFCURVE, QCURVE
|
||||||
contour = self.contours[-1]
|
contour = self.contours[-1]
|
||||||
for p, t in ((p1, t1), (p2, t2)):
|
for p, t in ((p1, t1), (p2, t2)):
|
||||||
contour.points.append(p)
|
contour.points.append(p)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user