Skip to content

Commit 920535b

Browse files
committed
Add a test for COLRv0 fonts
1 parent 25fde29 commit 920535b

File tree

7 files changed

+556
-0
lines changed

7 files changed

+556
-0
lines changed

LICENSE/LICENSE_OPENMOJI

Lines changed: 427 additions & 0 deletions
Large diffs are not rendered by default.

lib/matplotlib/testing/__init__.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,93 @@ def _gen_multi_font_text():
314314
# The resulting string contains 491 unique characters. Some file formats use 8-bit
315315
# tables, which the large number of characters exercises twice over.
316316
return fonts, test_str
317+
318+
319+
def _add_family_suffix(font, suffix):
320+
"""
321+
Add a suffix to all names in a font.
322+
323+
This code comes from a fontTools snippet:
324+
https://github.com/fonttools/fonttools/blob/main/Snippets/rename-fonts.py
325+
"""
326+
WINDOWS_ENGLISH_IDS = 3, 1, 0x409
327+
MAC_ROMAN_IDS = 1, 0, 0
328+
329+
FAMILY_RELATED_IDS = dict(LEGACY_FAMILY=1, TRUETYPE_UNIQUE_ID=3, FULL_NAME=4,
330+
POSTSCRIPT_NAME=6, PREFERRED_FAMILY=16, WWS_FAMILY=21)
331+
332+
def get_current_family_name(table):
333+
family_name_rec = None
334+
for plat_id, enc_id, lang_id in (WINDOWS_ENGLISH_IDS, MAC_ROMAN_IDS):
335+
for name_id in (FAMILY_RELATED_IDS['PREFERRED_FAMILY'],
336+
FAMILY_RELATED_IDS['LEGACY_FAMILY']):
337+
family_name_rec = table.getName(nameID=name_id, platformID=plat_id,
338+
platEncID=enc_id, langID=lang_id)
339+
if family_name_rec is not None:
340+
return family_name_rec.toUnicode()
341+
raise ValueError("family name not found; can't add suffix")
342+
343+
def insert_suffix(string, family_name, suffix):
344+
# check whether family_name is a substring
345+
start = string.find(family_name)
346+
if start != -1:
347+
# insert suffix after the family_name substring
348+
end = start + len(family_name)
349+
return string[:end] + suffix + string[end:]
350+
else:
351+
# it's not, we just append the suffix at the end
352+
return string + suffix
353+
354+
def rename_record(name_record, family_name, suffix):
355+
string = name_record.toUnicode()
356+
new_string = insert_suffix(string, family_name, suffix)
357+
name_record.string = new_string
358+
return string, new_string
359+
360+
table = font['name']
361+
family_name = get_current_family_name(table)
362+
ps_family_name = family_name.replace(' ', '')
363+
ps_suffix = suffix.replace(' ', '')
364+
for rec in table.names:
365+
name_id = rec.nameID
366+
if name_id not in FAMILY_RELATED_IDS.values():
367+
continue
368+
if name_id == FAMILY_RELATED_IDS['POSTSCRIPT_NAME']:
369+
old, new = rename_record(rec, ps_family_name, ps_suffix)
370+
elif name_id == FAMILY_RELATED_IDS['TRUETYPE_UNIQUE_ID']:
371+
# The Truetype Unique ID rec may contain either the PostScript
372+
# Name or the Full Name string, so we try both
373+
if ps_family_name in rec.toUnicode():
374+
old, new = rename_record(rec, ps_family_name, ps_suffix)
375+
else:
376+
old, new = rename_record(rec, family_name, suffix)
377+
else:
378+
old, new = rename_record(rec, family_name, suffix)
379+
380+
return family_name
381+
382+
383+
def _generate_font_subset(path, text):
384+
"""
385+
Generate a subset of a font for testing purposes.
386+
387+
The font name will be suffixed with ' MplSubset'.
388+
389+
Parameters
390+
----------
391+
path : str or bytes or os.PathLike
392+
The path to the font to be subset. The new file will be saved in the same
393+
location with a ``-subset`` suffix.
394+
text : str
395+
The text from which characters to be subset will be derived. Usually fonts do
396+
not have a newline character, so any appearing in this text will be stripped
397+
before subsetting.
398+
"""
399+
from fontTools import subset
400+
options = subset.Options()
401+
font = subset.load_font(path, options)
402+
subsetter = subset.Subsetter(options=options)
403+
subsetter.populate(text=text.replace('\n', ''))
404+
subsetter.subset(font)
405+
_add_family_suffix(font, ' MplSubset')
406+
subset.save_font(font, path.with_stem(path.stem + '-subset'), options)

lib/matplotlib/testing/__init__.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from collections.abc import Callable
2+
import os
23
import subprocess
34
from typing import Any, IO, Literal, overload
5+
from fontTools.ttLib import TTFont
46

57
def set_font_settings_for_testing() -> None: ...
68
def set_reproducibility_for_testing() -> None: ...
@@ -56,3 +58,5 @@ def ipython_in_subprocess(
5658
) -> None: ...
5759
def is_ci_environment() -> bool: ...
5860
def _gen_multi_font_text() -> tuple[list[str], str]: ...
61+
def _add_family_suffix(font: TTFont, suffix: str) -> None: ...
62+
def _generate_font_subset(path: str | bytes | os.PathLike, text: str) -> None: ...

lib/matplotlib/testing/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def pytest_configure(config):
2121
("filterwarnings",
2222
r"ignore:DynamicImporter.find_spec\(\) not found; "
2323
r"falling back to find_module\(\):ImportWarning"),
24+
("filterwarnings",
25+
r"ignore:Glyph .* \([lp]\) missing from font\(s\) OpenMoji MplSubset\."),
2426
]:
2527
config.addinivalue_line(key, value)
2628

80.6 KB
Loading
23.6 KB
Binary file not shown.

lib/matplotlib/tests/test_text.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import gc
33
import inspect
44
import io
5+
from pathlib import Path
56
import warnings
67

78
import numpy as np
@@ -1320,3 +1321,35 @@ def test_text_language():
13201321
t.set_language((('en', 0, 1), ('smn', 1, 2), ('en', 2, 3), ('smn', 3, 4)))
13211322
assert t.get_language() == (
13221323
('en', 0, 1), ('smn', 1, 2), ('en', 2, 3), ('smn', 3, 4))
1324+
1325+
1326+
@image_comparison(['colour.png'], remove_text=False, style='mpl20')
1327+
def test_colour_fonts():
1328+
zwj = '\U0000200D'
1329+
adult = '\U0001F9D1'
1330+
man = '\U0001F468'
1331+
woman = '\U0001F469'
1332+
science = '\U0001F52C'
1333+
technology = '\U0001F4BB'
1334+
skin_tones = ['', *(chr(0x1F3FB + i) for i in range(5))]
1335+
1336+
text = '\n'.join([
1337+
''.join(person + tone + zwj + occupation for tone in skin_tones)
1338+
for person in [adult, man, woman]
1339+
for occupation in [science, technology]
1340+
])
1341+
1342+
# To generate the subsetted test file, save the latest
1343+
# OpenMoji-color-glyf_colr_0.ttf from
1344+
# https://github.com/hfg-gmuend/openmoji/tree/master/font to the data directory,
1345+
# set this condition to True, and run the test.
1346+
path = Path(__file__).parent / 'data/OpenMoji-color-glyf_colr_0.ttf'
1347+
if False:
1348+
from matplotlib.testing import _generate_font_subset
1349+
_generate_font_subset(path, text)
1350+
path = path.with_stem(path.stem + '-subset')
1351+
1352+
fig = plt.figure()
1353+
fig.text(0.5, 0.5, text,
1354+
font=FontProperties(fname=path), fontsize=48,
1355+
horizontalalignment='center', verticalalignment='center')

0 commit comments

Comments
 (0)