From 43c4d308b85a67254fdf73f60294aac29df2a08f Mon Sep 17 00:00:00 2001 From: Frank Sauerburger Date: Wed, 15 Jun 2022 16:14:39 +0200 Subject: [PATCH 1/5] Split and cache TTC font to TTF fonts --- lib/matplotlib/font_manager.py | 102 ++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index f57fc9c051b0..5377326eeb89 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -35,6 +35,7 @@ from pathlib import Path import re import subprocess +import struct import sys import threading @@ -1099,7 +1100,7 @@ def __init__(self, size=None, weight='normal'): 'Matplotlib is building the font cache; this may take a moment.')) timer.start() try: - for fontext in ["afm", "ttf"]: + for fontext in ["afm", "ttf", "ttc"]: for path in [*findSystemFonts(paths, fontext=fontext), *findSystemFonts(fontext=fontext)]: try: @@ -1129,6 +1130,9 @@ def addfont(self, path): font = _afm.AFM(fh) prop = afmFontProperty(path, font) self.afmlist.append(prop) + elif Path(path).suffix.lower() == ".ttc": + for ttf_file in _split_ttc(path): + self.addfont(ttf_file) else: font = ft2font.FT2Font(path) prop = ttfFontProperty(font) @@ -1473,6 +1477,102 @@ def get_font(filename, hinting_factor=None): thread_id=threading.get_ident()) +def _split_ttc(ttc_path): + """SPlit a TTC ont into TTF files""" + res = _read_ttc(ttc_path) + ttf_fonts, table_index, table_data = res + out_base = Path( + mpl.get_cachedir(), + os.path.basename(ttc_path) + "-" + ) + return _dump_ttf(out_base, ttf_fonts, table_index, table_data) + + +def _read_ttc(ttc_path): + """ + Read a TTC font collection + + Returns an internal list of TTF fonts, table index data, and table + contents. + """ + with open(ttc_path, "rb") as ttc_file: + def read(fmt): + """Read with struct format""" + size = struct.calcsize(fmt) + data = ttc_file.read(size) + return struct.unpack(fmt, data) + + read(">HHI") # ttcf tag and version, ignore + num_fonts = read(">I")[0] # Number of fonts + font_offsets = read(f">{num_fonts:d}I") # offsets of TTF font + + # Set of tables referenced by any font + table_index = {} # (offset, length): tag, chksum + + # List of TTF fonts + ttf_fonts = [] # (version, num_entries, triple, referenced tables) + + # Read TTF headers and directory tables + for font_offset in font_offsets: + ttc_file.seek(font_offset) + + version = read(">HH") # TTF format version + num_entries = read(">H")[0] # Number of entried in directory table + triple = read(">HHH") # Weird triple, often invalid + referenced_tables = [] + + for _ in range(num_entries): + tag, chksum, offset, length = read(">IIII") + referenced_tables.append((offset, length)) + table_index[(offset, length)] = tag, chksum + + ttf_fonts.append((version, num_entries, triple, referenced_tables)) + + # Read data for all tables + table_data = {} + for (offset, length), (tag, chksum) in table_index.items(): + ttc_file.seek(offset) + table_data[(offset, length)] = ttc_file.read(length) + + return ttf_fonts, table_index, table_data + + +def _dump_ttf(base_name, ttf_fonts, table_index, table_data): + """Write each TTF font to a separate font""" + created_paths = [] + + # Dump TTF fonts into separate files + for i, font in enumerate(ttf_fonts): + version, num_entries, triple, referenced_tables = font + + def write(file, fmt, values): + raw = struct.pack(fmt, *values) + file.write(raw) + + out_path = f"{base_name}{i}.ttf" + created_paths.append(out_path) + with open(out_path, "wb") as ttf_file: + + write(ttf_file, ">HH", version) + write(ttf_file, ">H", (num_entries, )) + write(ttf_file, ">HHH", triple) + + # Length of header and directory + file_offset = 12 + len(referenced_tables) * 16 + + # Write directory + for (offset, length) in referenced_tables: + tag, chksum, = table_index[(offset, length)] + write(ttf_file, ">IIII", (tag, chksum, file_offset, length)) + file_offset += length + + # Write tables + for table_coord in referenced_tables: + data = table_data[table_coord] + ttf_file.write(data) + return created_paths + + def _load_fontmanager(*, try_read_cache=True): fm_path = Path( mpl.get_cachedir(), f"fontlist-v{FontManager.__version__}.json") From 59d9e629ed0d5bc315dca8349f73f6e32ef91a20 Mon Sep 17 00:00:00 2001 From: Frank Sauerburger Date: Mon, 20 Jun 2022 11:16:45 +0200 Subject: [PATCH 2/5] Add logging output for ttc font conversion --- lib/matplotlib/font_manager.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 5377326eeb89..ebecc1f964e1 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1502,7 +1502,15 @@ def read(fmt): data = ttc_file.read(size) return struct.unpack(fmt, data) - read(">HHI") # ttcf tag and version, ignore + tag, major, minor = read(">4sHH") # ttcf tag and version + if tag != b'ttcf': + _log.warning("Failed to read TTC file, invalid tag: %r", ttc_path) + return [], {}, {} + + if major > 2: + _log.info("TTC file format version > 2, parsing might fail: %r", + ttc_path) + num_fonts = read(">I")[0] # Number of fonts font_offsets = read(f">{num_fonts:d}I") # offsets of TTF font @@ -1534,6 +1542,8 @@ def read(fmt): ttc_file.seek(offset) table_data[(offset, length)] = ttc_file.read(length) + _log.debug("Extracted %d tables for %d fonts from TTC file %r", + len(table_index), len(ttf_fonts), ttc_path) return ttf_fonts, table_index, table_data @@ -1570,6 +1580,7 @@ def write(file, fmt, values): for table_coord in referenced_tables: data = table_data[table_coord] ttf_file.write(data) + _log.info("Created %r from TTC file", out_path) return created_paths From 1220c51ef2da22be7091d705807476c6484d542f Mon Sep 17 00:00:00 2001 From: Frank Sauerburger Date: Mon, 20 Jun 2022 11:26:26 +0200 Subject: [PATCH 3/5] Add "What's new" user note for ttc font support --- doc/users/next_whats_new/ttc_font_support.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 doc/users/next_whats_new/ttc_font_support.rst diff --git a/doc/users/next_whats_new/ttc_font_support.rst b/doc/users/next_whats_new/ttc_font_support.rst new file mode 100644 index 000000000000..7c0cdf418852 --- /dev/null +++ b/doc/users/next_whats_new/ttc_font_support.rst @@ -0,0 +1,7 @@ +TTC font collection support +--------------------------- + +Fonts in a TrueType collection file (TTC) can now be added and used. Internally, +the embedded TTF fonts are extracted and stored in the matplotlib cache +directory. Users upgrading to this version need to rebuild the font cache for +this feature to become effective. From 2219c1adbfc82a5537608aba0cad978e2653deed Mon Sep 17 00:00:00 2001 From: Frank Sauerburger Date: Tue, 21 Jun 2022 17:08:20 +0200 Subject: [PATCH 4/5] Consider extracted TTF fonts in font_names test --- lib/matplotlib/tests/test_font_manager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 254b9fdff38b..c3efac2f3352 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -15,8 +15,8 @@ findfont, findSystemFonts, FontEntry, FontProperties, fontManager, json_dump, json_load, get_font, is_opentype_cff_font, MSUserFontDirectories, _get_fontconfig_fonts, ft2font, - ttfFontProperty, cbook) -from matplotlib import pyplot as plt, rc_context + ttfFontProperty, cbook, _load_fontmanager) +from matplotlib import pyplot as plt, rc_context, get_cachedir has_fclist = shutil.which('fc-list') is not None @@ -297,11 +297,17 @@ def test_fontentry_dataclass_invalid_path(): @pytest.mark.skipif(sys.platform == 'win32', reason='Linux or OS only') def test_get_font_names(): + # Ensure fonts like 'mpltest' are not in cache + new_fm = _load_fontmanager(try_read_cache=False) + mpl_font_names = sorted(new_fm.get_font_names()) + paths_mpl = [cbook._get_data_path('fonts', subdir) for subdir in ['ttf']] fonts_mpl = findSystemFonts(paths_mpl, fontext='ttf') fonts_system = findSystemFonts(fontext='ttf') + # TTF extracted and cached from TTC + cached_fonts = findSystemFonts(get_cachedir(), fontext='ttf') ttf_fonts = [] - for path in fonts_mpl + fonts_system: + for path in fonts_mpl + fonts_system + cached_fonts: try: font = ft2font.FT2Font(path) prop = ttfFontProperty(font) @@ -309,6 +315,6 @@ def test_get_font_names(): except: pass available_fonts = sorted(list(set(ttf_fonts))) - mpl_font_names = sorted(fontManager.get_font_names()) + assert len(available_fonts) == len(mpl_font_names) assert available_fonts == mpl_font_names From 3cd9cf58ee9ad606df67147ff9ddaa701f682307 Mon Sep 17 00:00:00 2001 From: Frank Sauerburger Date: Mon, 4 Jul 2022 17:49:28 +0200 Subject: [PATCH 5/5] Update lib/matplotlib/font_manager.py Fix typos Co-authored-by: Oscar Gustafsson --- lib/matplotlib/font_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index ebecc1f964e1..5fe1d10cb207 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1478,7 +1478,7 @@ def get_font(filename, hinting_factor=None): def _split_ttc(ttc_path): - """SPlit a TTC ont into TTF files""" + """Split a TTC file into TTF files""" res = _read_ttc(ttc_path) ttf_fonts, table_index, table_data = res out_base = Path( pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy