Retrieving bounding box and bezier curve data for all glyphs in a .ttf font file in python
Question:
I am interested in extracting the quadratic bezier curve information of all glyphs in a given ttf file. Currently, using the ttfquery library in python, I am able to extract the contours of a given glyph (such as a
) in the following manner:
from ttfquery import describe
from ttfquery import glyphquery
import ttfquery.glyph as glyph
char = "a"
font_url = "/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf"
font = describe.openFont(font_url)
g = glyph.Glyph(char)
contours = g.calculateContours(font)
for contour in contours:
for point, flag in contour:
print point, flag
This works well for alphabetical characters, but it provides the following key error for numbers, punctuation, spaces, etc:
Traceback (most recent call last):
File "demo.py", line 9, in <module>
contours = g.calculateContours(font)
File "/usr/local/lib/python2.7/dist-packages/ttfquery/glyph.py", line 33, in calculateContours
charglyf = glyf[self.glyphName]
File "/usr/local/lib/python2.7/dist-packages/FontTools/fontTools/ttLib/tables/_g_l_y_f.py", line 185, in __getitem__
glyph = self.glyphs[glyphName]
KeyError: '!'
What is a reliable way get both the the bezier curve points as well as the bounding box of each glyph (which I am currently calculating indirectly using the min and max x and y values retrieved from the contours)?
Answers:
Glyphs are not necessarily named after a character. There is a structure in a TTF file though that maps characters to glyphs, the cmap. ttyquery has an API to access that map:
>>> ttfquery.glyphquery.glyphName(font, "!")
"exclam"
That is, replace
g = glyph.Glyph(char)
with
g = glyph.Glyph(ttfquery.glyphquery.glyphName(f, char))
and your code should work.
I recently confronted this problem. Since TTFQuery is not maintained anymore, I solved the problem with FreeType Python(freetype-py)
, like this.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import freetype
face = freetype.Face('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')
face.load_char('a') # This also works for a special character such as '!'.
outline = face.glyph.outline
bbox = outline.get_cbox()
print('outline bbox:', [bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax])
points, codes = [], []
def move_to(p, _):
points.append((p.x, p.y))
codes.append(Path.MOVETO)
def segment_to(*args):
*args, _ = args
points.extend([(p.x, p.y) for p in args])
code = (Path.LINETO, Path.CURVE3, Path.CURVE4)[len(args)-1]
codes.extend([code] * len(args))
outline.decompose(None, move_to, segment_to, segment_to, segment_to)
plt.plot(*np.array(points).T, 'rx')
plt.gca().add_patch(PathPatch(Path(points, codes), fill=False))
plt.show()
By using the Outline.decompose()
, you don’t need to decode glyph tags.
I am interested in extracting the quadratic bezier curve information of all glyphs in a given ttf file. Currently, using the ttfquery library in python, I am able to extract the contours of a given glyph (such as a
) in the following manner:
from ttfquery import describe
from ttfquery import glyphquery
import ttfquery.glyph as glyph
char = "a"
font_url = "/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf"
font = describe.openFont(font_url)
g = glyph.Glyph(char)
contours = g.calculateContours(font)
for contour in contours:
for point, flag in contour:
print point, flag
This works well for alphabetical characters, but it provides the following key error for numbers, punctuation, spaces, etc:
Traceback (most recent call last):
File "demo.py", line 9, in <module>
contours = g.calculateContours(font)
File "/usr/local/lib/python2.7/dist-packages/ttfquery/glyph.py", line 33, in calculateContours
charglyf = glyf[self.glyphName]
File "/usr/local/lib/python2.7/dist-packages/FontTools/fontTools/ttLib/tables/_g_l_y_f.py", line 185, in __getitem__
glyph = self.glyphs[glyphName]
KeyError: '!'
What is a reliable way get both the the bezier curve points as well as the bounding box of each glyph (which I am currently calculating indirectly using the min and max x and y values retrieved from the contours)?
Glyphs are not necessarily named after a character. There is a structure in a TTF file though that maps characters to glyphs, the cmap. ttyquery has an API to access that map:
>>> ttfquery.glyphquery.glyphName(font, "!")
"exclam"
That is, replace
g = glyph.Glyph(char)
with
g = glyph.Glyph(ttfquery.glyphquery.glyphName(f, char))
and your code should work.
I recently confronted this problem. Since TTFQuery is not maintained anymore, I solved the problem with FreeType Python(freetype-py)
, like this.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import freetype
face = freetype.Face('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')
face.load_char('a') # This also works for a special character such as '!'.
outline = face.glyph.outline
bbox = outline.get_cbox()
print('outline bbox:', [bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax])
points, codes = [], []
def move_to(p, _):
points.append((p.x, p.y))
codes.append(Path.MOVETO)
def segment_to(*args):
*args, _ = args
points.extend([(p.x, p.y) for p in args])
code = (Path.LINETO, Path.CURVE3, Path.CURVE4)[len(args)-1]
codes.extend([code] * len(args))
outline.decompose(None, move_to, segment_to, segment_to, segment_to)
plt.plot(*np.array(points).T, 'rx')
plt.gca().add_patch(PathPatch(Path(points, codes), fill=False))
plt.show()
By using the Outline.decompose()
, you don’t need to decode glyph tags.