Skip to content

Commit

Permalink
[font_parser] Fallback to freetype if fontTools raise an exception wh…
Browse files Browse the repository at this point in the history
…en trying to read the cmap

Fix #44
  • Loading branch information
moi15moi committed May 3, 2024
1 parent c5c724a commit 542fd35
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 20 deletions.
2 changes: 1 addition & 1 deletion font_collector/font/abc_font_face.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def get_missing_glyphs(
if error: raise FT_Exception(error)

ttFont = TTFont(self.font_file.filename, fontNumber=self.font_index)
supported_cmaps = FontParser.get_supported_cmaps(ttFont)
supported_cmaps = FontParser.get_supported_cmaps(ttFont, self.font_file.filename, self.font_index)
supported_charmaps = []
for i in range(face.contents.num_charmaps):
charmap = face.contents.charmaps[i]
Expand Down
2 changes: 1 addition & 1 deletion font_collector/font/factory_abc_font_face.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def __create_font(ttFont: TTFont, font_path: Path, font_index: int) -> NormalFon
Returns:
An FontFace instance that represent the ttFont.
"""
cmaps = FontParser.get_supported_cmaps(ttFont)
cmaps = FontParser.get_supported_cmaps(ttFont, font_path, font_index)
if len(cmaps) == 0:
raise InvalidNormalFontFaceException(f"The font doesn't contain any valid cmap.")

Expand Down
39 changes: 28 additions & 11 deletions font_collector/font/font_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,16 @@ def get_symbol_cmap_encoding(face: FT_Face) -> Optional[str]:


@staticmethod
def get_supported_cmaps(font: TTFont) -> List[CMap]:
def get_supported_cmaps(
font: TTFont, font_path: Path, font_index: int
) -> List[CMap]:
"""
Retrieve supported CMaps from a TrueType font.
Args:
font: A fontTools object representing the font.
font_path: Font path.
font_index: Font index.
Returns:
A list of supported CMaps.
- To determine which CMaps are supported, refer to FontParser.get_cmap_encoding().
Expand All @@ -511,16 +515,29 @@ def get_supported_cmaps(font: TTFont) -> List[CMap]:
microsoft_cmaps: List[CMap] = []
macintosh_cmaps: List[CMap] = []

cmap_tables: List[CmapSubtable] = font["cmap"].tables

for table in cmap_tables:
encoding = FontParser.get_cmap_encoding(table.platformID, table.platEncID)
if encoding is not None:
cmap = CMap(table.platformID, table.platEncID)
if table.platformID == PlatformID.MICROSOFT:
microsoft_cmaps.append(cmap)
elif table.platformID == PlatformID.MACINTOSH:
macintosh_cmaps.append(cmap)
try:
cmap_tables: List[CmapSubtable] = font["cmap"].tables

for table in cmap_tables:
encoding = FontParser.get_cmap_encoding(table.platformID, table.platEncID)
if encoding is not None:
cmap = CMap(table.platformID, table.platEncID)
if table.platformID == PlatformID.MICROSOFT:
microsoft_cmaps.append(cmap)
elif table.platformID == PlatformID.MACINTOSH:
macintosh_cmaps.append(cmap)
except Exception:
with font_path.open("rb") as f:
face = Face(f, font_index)

for charmap in face.charmaps:
encoding = FontParser.get_cmap_encoding(charmap.platform_id, charmap.encoding_id)
if encoding is not None:
cmap = CMap(charmap.platform_id, charmap.encoding_id)
if charmap.platform_id == PlatformID.MICROSOFT:
microsoft_cmaps.append(cmap)
elif charmap.platform_id == PlatformID.MACINTOSH:
macintosh_cmaps.append(cmap)
return macintosh_cmaps if len(microsoft_cmaps) == 0 else microsoft_cmaps


Expand Down
2 changes: 1 addition & 1 deletion font_collector/font/variable_font_face.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def variable_font_to_collection(self, save_path: Path, cache_generated_font: boo
if len(fonts_face) == 0:
raise ValueError(f"There is no valid font at the index {self.font_index}")

cmaps = FontParser.get_supported_cmaps(ttFont)
cmaps = FontParser.get_supported_cmaps(ttFont, self.font_file.filename, self.font_index)

for font_face in fonts_face:
generated_font_face = instancer.instantiateVariableFont(ttFont, font_face.named_instance_coordinates)
Expand Down
Binary file added tests/file/fonts/invalid_cmap.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions tests/font/test_font_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def test_load_additional_fonts():
Path(os.path.join(font_directory, "font_mac.TTF")),
Path(os.path.join(font_directory, "font_with_invalid_os2_table.ttf")),
Path(os.path.join(font_directory, "font_without axis_value.ttf")),
Path(os.path.join(font_directory, "invalid_cmap.ttf")),
Path(os.path.join(font_directory, "opentype_font_collection.ttc")),
Path(os.path.join(font_directory, "PENBOX.otf")),
Path(os.path.join(font_directory, "SFProDisplay-Bold.ttf")),
Expand Down
18 changes: 12 additions & 6 deletions tests/font/test_font_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,23 +204,29 @@ def test_get_symbol_cmap_encoding():

def test_get_supported_cmaps():
# This font contain 1 valid mac cmap
font_path = os.path.join(os.path.dirname(dir_path), "file", "fonts", "font_mac.TTF")
font_path = Path(os.path.join(os.path.dirname(dir_path), "file", "fonts", "font_mac.TTF"))
font = TTFont(font_path)
cmaps = FontParser.get_supported_cmaps(font)
cmaps = FontParser.get_supported_cmaps(font, font_path, 0)
assert cmaps == [CMap(1, 0)]

# This font contain unicode cmap and multiple microsoft cmap
font_path = os.path.join(os.path.dirname(dir_path), "file", "fonts", "font_cmap_encoding_1.ttf")
font_path = Path(os.path.join(os.path.dirname(dir_path), "file", "fonts", "font_cmap_encoding_1.ttf"))
font = TTFont(font_path)
cmaps = FontParser.get_supported_cmaps(font)
cmaps = FontParser.get_supported_cmaps(font, font_path, 0)
assert cmaps == [CMap(3, 1), CMap(3, 10)]

# This font contain 1 microsoft cmap, 1 valid mac cmap and 1 unicode cmap
font_path = os.path.join(os.path.dirname(dir_path), "file", "fonts", "font_cmap_encoding_0.ttf")
font_path = Path(os.path.join(os.path.dirname(dir_path), "file", "fonts", "font_cmap_encoding_0.ttf"))
font = TTFont(font_path)
cmaps = FontParser.get_supported_cmaps(font)
cmaps = FontParser.get_supported_cmaps(font, font_path, 0)
assert cmaps == [CMap(3, 0)]

# This font contain a "invalid" cmap. fontTools raise a exception, so we fallback to freetype which doesn't raise an exception
font_path = Path(os.path.join(os.path.dirname(dir_path), "file", "fonts", "invalid_cmap.ttf"))
font = TTFont(font_path)
cmaps = FontParser.get_supported_cmaps(font, font_path, 0)
assert cmaps == [CMap(3, 1)]


def test_get_cmap_encoding():
# It could be any format
Expand Down

0 comments on commit 542fd35

Please # to comment.