Skip to content

Commit d07a6a7

Browse files
authored
Add Fallback '.notdef' Glyph for Improved Cross-Platform TrueType Font Support (py-pdf#1352)
* Add fallback '.notdef' glyph * add logging for missing .notdef glyph in TrueType fonts & add missing imports * add test for handling missing .notdef glyph in TrueType fonts. add font missing this glyph. * update CHANGELOG.md * Cannot include commercial font * Generate a more consistent .notdef outline instead of using arbitrary values * Modify test to use a test font instead of a commercial font * upload a reference notdef font file * assign to variable to avoid being a 'pointless-statement' * Add a comment explaining lazy loading in fontTools. * Regenerate test font * Remove test font generation script within test * Remove unused TTFont import
1 parent bc5189b commit d07a6a7

5 files changed

+49
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
2525
* support for [`v_align` at the row level in tables](https://py-pdf.github.io/fpdf2/Tables.html#setting-vertical-alignment-of-text-in-cells)
2626
* documentation on [verifying provenance of `fpdf2` releases](https://py-pdf.github.io/fpdf2/#verifying-provenance)
2727
* documentation on [`fpdf2` internals](https://py-pdf.github.io/fpdf2/Internals.html)
28+
* support for adding TrueType fonts that are missing the .notdef glyph - [issue #1161](https://github.com/py-pdf/fpdf2/issues/1161)
2829
### Fixed
2930
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): Fixed rendering of content following `<a>` tags; now correctly resets emphasis style post `</a>` tag: hyperlink styling contained within the tag authority. - [Issue #1311](https://github.com/py-pdf/fpdf2/issues/1311)
3031

fpdf/fonts.py

+38
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import re, warnings
11+
import logging
1112

1213
from bisect import bisect_left
1314
from collections import defaultdict
@@ -16,6 +17,7 @@
1617
from typing import List, Optional, Tuple, Union
1718

1819
from fontTools import ttLib
20+
from fontTools.pens.ttGlyphPen import TTGlyphPen
1921

2022
try:
2123
import uharfbuzz as hb
@@ -37,6 +39,8 @@ def __deepcopy__(self, _memo):
3739
from .syntax import Name, PDFObject
3840
from .util import escape_parens
3941

42+
LOGGER = logging.getLogger(__name__)
43+
4044

4145
@dataclass
4246
class FontFace:
@@ -267,6 +271,40 @@ def __init__(self, fpdf, font_file_path, fontkey, style):
267271
)
268272

269273
self.scale = 1000 / self.ttfont["head"].unitsPerEm
274+
275+
# check if the font is a TrueType and missing a .notdef glyph
276+
# if it is missing, provide a fallback glyph
277+
if "glyf" in self.ttfont and ".notdef" not in self.ttfont["glyf"]:
278+
LOGGER.warning(
279+
(
280+
"TrueType Font '%s' is missing the '.notdef' glyph. "
281+
"Fallback glyph will be provided."
282+
),
283+
self.fontkey,
284+
)
285+
# draw a diagonal cross .notdef glyph
286+
(xMin, xMax, yMin, yMax) = (
287+
self.ttfont["head"].xMin,
288+
self.ttfont["head"].xMax,
289+
self.ttfont["head"].yMin,
290+
self.ttfont["head"].yMax,
291+
)
292+
pen = TTGlyphPen(self.ttfont["glyf"])
293+
pen.moveTo((xMin, yMin))
294+
pen.lineTo((xMax, yMin))
295+
pen.lineTo((xMax, yMax))
296+
pen.lineTo((xMin, yMax))
297+
pen.closePath()
298+
pen.moveTo((xMin, yMin))
299+
pen.lineTo((xMax, yMax))
300+
pen.closePath()
301+
pen.moveTo((xMax, yMin))
302+
pen.lineTo((xMin, yMax))
303+
pen.closePath()
304+
305+
self.ttfont["glyf"][".notdef"] = pen.glyph()
306+
self.ttfont["hmtx"][".notdef"] = (xMax - xMin, yMax - yMin)
307+
270308
default_width = round(self.scale * self.ttfont["hmtx"].metrics[".notdef"][0])
271309

272310
os2_table = self.ttfont["OS/2"]
142 KB
Binary file not shown.
Binary file not shown.

test/fonts/test_add_font.py

+10
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ def test_add_font_uppercase():
144144
assert pdf.fonts is not None and len(pdf.fonts) != 0 # fonts add successful
145145

146146

147+
def test_add_font_missing_notdef_glyph(caplog):
148+
pdf = FPDF()
149+
pdf.add_font(family="Roboto", fname=HERE / "Roboto-Regular-without-notdef.ttf")
150+
assert pdf.fonts is not None and len(pdf.fonts) != 0 # fonts add successful
151+
assert (
152+
"TrueType Font 'roboto' is missing the '.notdef' glyph. "
153+
"Fallback glyph will be provided."
154+
) in caplog.text
155+
156+
147157
def test_font_missing_glyphs(caplog):
148158
pdf = FPDF()
149159
pdf.add_page()

0 commit comments

Comments
 (0)