From fc59f3529c07974eaca3c8613d5d7c1cba91a0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 30 Jul 2020 17:38:44 +0300 Subject: [PATCH 01/29] Proof of concept: Type42 subsetting in pdf --- lib/matplotlib/backends/backend_pdf.py | 47 +++++++++++++++++++------- lib/matplotlib/testing/conftest.py | 1 + setup.py | 1 + 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index ec6154234e5d..218ae560515c 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -15,11 +15,13 @@ import os import re import struct +import tempfile import time import types import warnings import zlib +from fontTools import subset import numpy as np from PIL import Image @@ -36,7 +38,7 @@ import matplotlib.type1font as type1font import matplotlib.dviread as dviread from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, - LOAD_NO_HINTING, KERNING_UNFITTED) + LOAD_NO_HINTING, KERNING_UNFITTED, FT2Font) from matplotlib.mathtext import MathTextParser from matplotlib.transforms import Affine2D, BboxBase from matplotlib.path import Path @@ -1209,6 +1211,17 @@ def embedTTFType42(font, characters, descriptor): wObject = self.reserveObject('Type 0 widths') toUnicodeMapObject = self.reserveObject('ToUnicode map') + print(f"SUBSET {filename} characters: {''.join(chr(c) for c in characters)}") + fontdata = self.getSubset(filename, ''.join(chr(c) for c in characters)) + print(f'SUBSET {filename} {os.stat(filename).st_size} -> {len(fontdata)}') + + # reload the font object from the subset + # (all the necessary data could probably be obtained directly using fontLib.ttLib) + with tempfile.NamedTemporaryFile(suffix='.ttf') as tmp: + tmp.write(fontdata) + tmp.seek(0,0) + font = FT2Font(tmp.name) + cidFontDict = { 'Type': Name('Font'), 'Subtype': Name('CIDFontType2'), @@ -1233,21 +1246,12 @@ def embedTTFType42(font, characters, descriptor): # Make fontfile stream descriptor['FontFile2'] = fontfileObject - length1Object = self.reserveObject('decoded length of a font') self.beginStream( fontfileObject.id, self.reserveObject('length of font stream'), - {'Length1': length1Object}) - with open(filename, 'rb') as fontfile: - length1 = 0 - while True: - data = fontfile.read(4096) - if not data: - break - length1 += len(data) - self.currentstream.write(data) + {'Length1': len(fontdata)}) + self.currentstream.write(fontdata) self.endStream() - self.writeObject(length1Object, length1) # Make the 'W' (Widths) array, CidToGidMap and ToUnicode CMap # at the same time @@ -1396,6 +1400,25 @@ def embedTTFType42(font, characters, descriptor): elif fonttype == 42: return embedTTFType42(font, characters, descriptor) + @classmethod + def getSubset(self, fontfile, characters): + """Read TTF font from the given file and subset it for the given characters. + + Returns a serialization of the subset font as bytes.""" + + options = subset.Options(glyph_names=True, recommended_glyphs=True) + options.drop_tables += ['FFTM'] + font = subset.load_font(fontfile, options) + try: + subsetter = subset.Subsetter(options=options) + subsetter.populate(text=characters) + subsetter.subset(font) + fh = BytesIO() + font.save(fh, reorderTables=False) + return fh.getvalue() + finally: + font.close() + def alphaState(self, alpha): """Return name of an ExtGState that sets alpha to the given value.""" diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index f35eddf96b00..ca6aae3635a8 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -19,6 +19,7 @@ def pytest_configure(config): ("markers", "pytz: Tests that require pytz to be installed."), ("markers", "network: Tests that reach out to the network."), ("filterwarnings", "error"), + #("filterwarnings", "error"), # fontTools.subset raises a pointless DeprecationWarning ]: config.addinivalue_line(key, value) diff --git a/setup.py b/setup.py index 8fcc3ad9bceb..37370068c4d8 100644 --- a/setup.py +++ b/setup.py @@ -325,6 +325,7 @@ def make_release_tree(self, base_dir, files): ], install_requires=[ "cycler>=0.10", + "fonttools>=4.13.0,<5.0", "kiwisolver>=1.0.1", "numpy>=1.17", "packaging>=20.0", From a632b256b560169761ac0d6bcab502f32b503fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Sat, 1 Aug 2020 18:46:50 +0300 Subject: [PATCH 02/29] flake8 --- lib/matplotlib/backends/backend_pdf.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 218ae560515c..98bb30e4213c 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1211,15 +1211,21 @@ def embedTTFType42(font, characters, descriptor): wObject = self.reserveObject('Type 0 widths') toUnicodeMapObject = self.reserveObject('ToUnicode map') - print(f"SUBSET {filename} characters: {''.join(chr(c) for c in characters)}") - fontdata = self.getSubset(filename, ''.join(chr(c) for c in characters)) - print(f'SUBSET {filename} {os.stat(filename).st_size} -> {len(fontdata)}') + print(f"SUBSET {filename} characters: " + f"{''.join(chr(c) for c in characters)}") + fontdata = self.getSubset( + filename, + ''.join(chr(c) for c in characters) + ) + print(f'SUBSET {filename} {os.stat(filename).st_size}' + f' ↦ {len(fontdata)}') # reload the font object from the subset - # (all the necessary data could probably be obtained directly using fontLib.ttLib) + # (all the necessary data could probably be obtained directly + # using fontLib.ttLib) with tempfile.NamedTemporaryFile(suffix='.ttf') as tmp: tmp.write(fontdata) - tmp.seek(0,0) + tmp.seek(0, 0) font = FT2Font(tmp.name) cidFontDict = { @@ -1402,9 +1408,11 @@ def embedTTFType42(font, characters, descriptor): @classmethod def getSubset(self, fontfile, characters): - """Read TTF font from the given file and subset it for the given characters. + """ + Read TTF font from the given file and subset it for the given characters. - Returns a serialization of the subset font as bytes.""" + Returns a serialization of the subset font as bytes. + """ options = subset.Options(glyph_names=True, recommended_glyphs=True) options.drop_tables += ['FFTM'] From 501b30e42f282419bb4993b22b22f44e7d624190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Sat, 1 Aug 2020 18:55:54 +0300 Subject: [PATCH 03/29] Filter out just the py23 warning --- lib/matplotlib/testing/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index ca6aae3635a8..d0aa85367529 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -19,7 +19,8 @@ def pytest_configure(config): ("markers", "pytz: Tests that require pytz to be installed."), ("markers", "network: Tests that reach out to the network."), ("filterwarnings", "error"), - #("filterwarnings", "error"), # fontTools.subset raises a pointless DeprecationWarning + ("filterwarnings", + "ignore:.*The py23 module has been deprecated:DeprecationWarning"), ]: config.addinivalue_line(key, value) From a5d527ae88099946c5c71c0d6b4608524f1df032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Sat, 1 Aug 2020 19:06:34 +0300 Subject: [PATCH 04/29] More flake8 --- lib/matplotlib/backends/backend_pdf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 98bb30e4213c..c61d2517297e 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1409,8 +1409,9 @@ def embedTTFType42(font, characters, descriptor): @classmethod def getSubset(self, fontfile, characters): """ - Read TTF font from the given file and subset it for the given characters. + Subset a TTF font + Reads the named fontfile and restricts the font to the characters. Returns a serialization of the subset font as bytes. """ From 8493184838d4beadb61894621f1af95a6530e323 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sun, 13 Jun 2021 14:32:39 +0530 Subject: [PATCH 05/29] Implement subsetting for PS backend --- lib/matplotlib/backends/backend_ps.py | 38 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index ef98eba4c38a..7ab6ea6e50a5 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -7,16 +7,18 @@ from enum import Enum import functools import glob -from io import StringIO +from io import BytesIO, StringIO, TextIOWrapper import logging import math import os import pathlib +import tempfile import re import shutil from tempfile import TemporaryDirectory import time +from fontTools import subset import numpy as np import matplotlib as mpl @@ -27,7 +29,7 @@ GraphicsContextBase, RendererBase) from matplotlib.cbook import is_writable_file_like, file_requires_unicode from matplotlib.font_manager import get_font -from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE +from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE, FT2Font from matplotlib._ttconv import convert_ttf_to_ps from matplotlib.mathtext import MathTextParser from matplotlib._mathtext_data import uni2type1 @@ -954,8 +956,36 @@ def print_figure_impl(fh): fh.write(_font_to_ps_type3(font_path, glyph_ids)) else: try: - convert_ttf_to_ps(os.fsencode(font_path), - fh, fonttype, glyph_ids) + _log.debug( + f"SUBSET {font_path} characters: " + f"{''.join(chr(c) for c in chars)}" + ) + fontdata = getSubset( + font_path, "".join(chr(c) for c in chars) + ) + _log.debug( + f"SUBSET {font_path} " + f"{os.stat(font_path).st_size} " + f"↦ {len(fontdata)}" + ) + + # give ttconv a subsetted font + # along with updated glyph_ids + with tempfile.NamedTemporaryFile( + suffix=".ttf" + ) as tmp: + tmp.write(fontdata) + tmp.seek(0, 0) + font = FT2Font(tmp.name) + glyph_ids = [ + font.get_char_index(c) for c in chars + ] + convert_ttf_to_ps( + os.fsencode(tmp.name), + fh, + fonttype, + glyph_ids, + ) except RuntimeError: _log.warning( "The PostScript backend does not currently " From b61744be90af2c48698e68c3af93ea1dd45910a1 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Tue, 15 Jun 2021 21:41:27 +0530 Subject: [PATCH 06/29] Move getSubset to common pdf/ps backend --- lib/matplotlib/backends/_backend_pdf_ps.py | 25 ++++++++++++++++++++ lib/matplotlib/backends/backend_pdf.py | 27 +--------------------- lib/matplotlib/backends/backend_ps.py | 5 ++-- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 780e79bf71b8..8f13ad63f22e 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -2,8 +2,11 @@ Common functionality between the PDF and PS backends. """ +from io import BytesIO import functools +from fontTools import subset + import matplotlib as mpl from .. import font_manager, ft2font from ..afm import AFM @@ -16,6 +19,28 @@ def _cached_get_afm_from_fname(fname): return AFM(fh) +def getSubset(self, fontfile, characters): + """ + Subset a TTF font + + Reads the named fontfile and restricts the font to the characters. + Returns a serialization of the subset font as bytes. + """ + + options = subset.Options(glyph_names=True, recommended_glyphs=True) + options.drop_tables += ['FFTM'] + font = subset.load_font(fontfile, options) + try: + subsetter = subset.Subsetter(options=options) + subsetter.populate(text=characters) + subsetter.subset(font) + fh = BytesIO() + font.save(fh, reorderTables=False) + return fh.getvalue() + finally: + font.close() + + class CharacterTracker: """ Helper for font subsetting by the pdf and ps backends. diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index c61d2517297e..40dffc408715 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -8,20 +8,17 @@ from datetime import datetime from enum import Enum from functools import total_ordering -from io import BytesIO import itertools import logging import math import os import re import struct -import tempfile import time import types import warnings import zlib -from fontTools import subset import numpy as np from PIL import Image @@ -1213,7 +1210,7 @@ def embedTTFType42(font, characters, descriptor): print(f"SUBSET {filename} characters: " f"{''.join(chr(c) for c in characters)}") - fontdata = self.getSubset( + fontdata = _backend_pdf_ps.getSubset( filename, ''.join(chr(c) for c in characters) ) @@ -1406,28 +1403,6 @@ def embedTTFType42(font, characters, descriptor): elif fonttype == 42: return embedTTFType42(font, characters, descriptor) - @classmethod - def getSubset(self, fontfile, characters): - """ - Subset a TTF font - - Reads the named fontfile and restricts the font to the characters. - Returns a serialization of the subset font as bytes. - """ - - options = subset.Options(glyph_names=True, recommended_glyphs=True) - options.drop_tables += ['FFTM'] - font = subset.load_font(fontfile, options) - try: - subsetter = subset.Subsetter(options=options) - subsetter.populate(text=characters) - subsetter.subset(font) - fh = BytesIO() - font.save(fh, reorderTables=False) - return fh.getvalue() - finally: - font.close() - def alphaState(self, alpha): """Return name of an ExtGState that sets alpha to the given value.""" diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 7ab6ea6e50a5..db52f9a90f65 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -7,7 +7,7 @@ from enum import Enum import functools import glob -from io import BytesIO, StringIO, TextIOWrapper +from io import StringIO, TextIOWrapper import logging import math import os @@ -18,7 +18,6 @@ from tempfile import TemporaryDirectory import time -from fontTools import subset import numpy as np import matplotlib as mpl @@ -960,7 +959,7 @@ def print_figure_impl(fh): f"SUBSET {font_path} characters: " f"{''.join(chr(c) for c in chars)}" ) - fontdata = getSubset( + fontdata = _backend_pdf_ps.getSubset( font_path, "".join(chr(c) for c in chars) ) _log.debug( From 44739429644caa5a0e6db22a35c210dcac3d99e2 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Tue, 15 Jun 2021 22:15:45 +0530 Subject: [PATCH 07/29] Handle file-like objects instead of saving --- lib/matplotlib/backends/_backend_pdf_ps.py | 6 +++--- lib/matplotlib/backends/backend_pdf.py | 18 ++++++++---------- lib/matplotlib/backends/backend_ps.py | 10 ++++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 8f13ad63f22e..97b396c28dbd 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -19,12 +19,12 @@ def _cached_get_afm_from_fname(fname): return AFM(fh) -def getSubset(self, fontfile, characters): +def getSubset(fontfile, characters): """ Subset a TTF font Reads the named fontfile and restricts the font to the characters. - Returns a serialization of the subset font as bytes. + Returns a serialization of the subset font as file-like object. """ options = subset.Options(glyph_names=True, recommended_glyphs=True) @@ -36,7 +36,7 @@ def getSubset(self, fontfile, characters): subsetter.subset(font) fh = BytesIO() font.save(fh, reorderTables=False) - return fh.getvalue() + return fh finally: font.close() diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 40dffc408715..70833bff5be8 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -8,6 +8,7 @@ from datetime import datetime from enum import Enum from functools import total_ordering +from io import BytesIO import itertools import logging import math @@ -1208,22 +1209,19 @@ def embedTTFType42(font, characters, descriptor): wObject = self.reserveObject('Type 0 widths') toUnicodeMapObject = self.reserveObject('ToUnicode map') - print(f"SUBSET {filename} characters: " - f"{''.join(chr(c) for c in characters)}") + _log.debug(f"SUBSET {filename} characters: " + f"{''.join(chr(c) for c in characters)}") fontdata = _backend_pdf_ps.getSubset( filename, ''.join(chr(c) for c in characters) ) - print(f'SUBSET {filename} {os.stat(filename).st_size}' - f' ↦ {len(fontdata)}') + _log.debug(f'SUBSET {filename} {os.stat(filename).st_size}' + f' ↦ {fontdata.getbuffer().nbytes}') # reload the font object from the subset # (all the necessary data could probably be obtained directly # using fontLib.ttLib) - with tempfile.NamedTemporaryFile(suffix='.ttf') as tmp: - tmp.write(fontdata) - tmp.seek(0, 0) - font = FT2Font(tmp.name) + font = FT2Font(fontdata) cidFontDict = { 'Type': Name('Font'), @@ -1252,8 +1250,8 @@ def embedTTFType42(font, characters, descriptor): self.beginStream( fontfileObject.id, self.reserveObject('length of font stream'), - {'Length1': len(fontdata)}) - self.currentstream.write(fontdata) + {'Length1': fontdata.getbuffer().nbytes}) + self.currentstream.write(fontdata.getvalue()) self.endStream() # Make the 'W' (Widths) array, CidToGidMap and ToUnicode CMap diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index db52f9a90f65..3124e223df92 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -965,7 +965,7 @@ def print_figure_impl(fh): _log.debug( f"SUBSET {font_path} " f"{os.stat(font_path).st_size} " - f"↦ {len(fontdata)}" + f"↦ {fontdata.getbuffer().nbytes}" ) # give ttconv a subsetted font @@ -973,12 +973,14 @@ def print_figure_impl(fh): with tempfile.NamedTemporaryFile( suffix=".ttf" ) as tmp: - tmp.write(fontdata) - tmp.seek(0, 0) - font = FT2Font(tmp.name) + font = FT2Font(fontdata) glyph_ids = [ font.get_char_index(c) for c in chars ] + tmp.write(fontdata.getvalue()) + tmp.seek(0, 0) + # TODO: allow convert_ttf_to_ps + # to input file objects (BytesIO) convert_ttf_to_ps( os.fsencode(tmp.name), fh, From 24219b9e3a24de05690f9214e823abd7fc29d71e Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Tue, 15 Jun 2021 22:26:56 +0530 Subject: [PATCH 08/29] Fix doc and warning --- lib/matplotlib/testing/conftest.py | 2 -- src/_ttconv.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index d0aa85367529..f35eddf96b00 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -19,8 +19,6 @@ def pytest_configure(config): ("markers", "pytz: Tests that require pytz to be installed."), ("markers", "network: Tests that reach out to the network."), ("filterwarnings", "error"), - ("filterwarnings", - "ignore:.*The py23 module has been deprecated:DeprecationWarning"), ]: config.addinivalue_line(key, value) diff --git a/src/_ttconv.cpp b/src/_ttconv.cpp index daa9f2639e55..635f7c7bcfde 100644 --- a/src/_ttconv.cpp +++ b/src/_ttconv.cpp @@ -164,7 +164,7 @@ static PyMethodDef ttconv_methods[] = "font data will be written to.\n" "fonttype may be either 3 or 42. Type 3 is a \"raw Postscript\" font. " "Type 42 is an embedded Truetype font. Glyph subsetting is not supported " - "for Type 42 fonts.\n" + "for Type 42 fonts within this module (needs to be done externally).\n" "glyph_ids (optional) is a list of glyph ids (integers) to keep when " "subsetting to a Type 3 font. If glyph_ids is not provided or is None, " "then all glyphs will be included. If any of the glyphs specified are " From 525760ec1cfa06a82c42394a2403b22f7dc2e68f Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 19 Jun 2021 02:57:21 +0530 Subject: [PATCH 09/29] Change function doc and context --- lib/matplotlib/backends/_backend_pdf_ps.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 97b396c28dbd..207e6e2527e5 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -19,26 +19,33 @@ def _cached_get_afm_from_fname(fname): return AFM(fh) -def getSubset(fontfile, characters): +def get_glyphs_subset(fontfile, characters): """ Subset a TTF font Reads the named fontfile and restricts the font to the characters. Returns a serialization of the subset font as file-like object. + + Parameters + ---------- + symbol : str + Path to the font file + characters : str + Continuous set of characters to include in subset """ options = subset.Options(glyph_names=True, recommended_glyphs=True) + + # prevent subsetting FontForge Timestamp table options.drop_tables += ['FFTM'] - font = subset.load_font(fontfile, options) - try: + + with subset.load_font(fontfile, options) as font: subsetter = subset.Subsetter(options=options) subsetter.populate(text=characters) subsetter.subset(font) fh = BytesIO() font.save(fh, reorderTables=False) return fh - finally: - font.close() class CharacterTracker: From 0d751175e82a363121bc644db66bbd85d5069424 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 19 Jun 2021 03:00:35 +0530 Subject: [PATCH 10/29] Log the correct way --- lib/matplotlib/backends/backend_pdf.py | 17 ++++++++++------- lib/matplotlib/backends/backend_ps.py | 12 ++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 70833bff5be8..4d38040d29b9 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1209,14 +1209,17 @@ def embedTTFType42(font, characters, descriptor): wObject = self.reserveObject('Type 0 widths') toUnicodeMapObject = self.reserveObject('ToUnicode map') - _log.debug(f"SUBSET {filename} characters: " - f"{''.join(chr(c) for c in characters)}") - fontdata = _backend_pdf_ps.getSubset( - filename, - ''.join(chr(c) for c in characters) + _log.debug( + "SUBSET %s characters: %s", + filename, "".join(chr(c) for c in characters) + ) + fontdata = _backend_pdf_ps.get_glyphs_subset( + filename, "".join(chr(c) for c in characters) + ) + _log.debug( + "SUBSET %s %d ↦ %d", filename, + os.stat(filename).st_size, fontdata.getbuffer().nbytes ) - _log.debug(f'SUBSET {filename} {os.stat(filename).st_size}' - f' ↦ {fontdata.getbuffer().nbytes}') # reload the font object from the subset # (all the necessary data could probably be obtained directly diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 3124e223df92..dd470ed62138 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -956,16 +956,16 @@ def print_figure_impl(fh): else: try: _log.debug( - f"SUBSET {font_path} characters: " - f"{''.join(chr(c) for c in chars)}" + "SUBSET %s characters: %s", font_path, + ''.join(chr(c) for c in chars) ) - fontdata = _backend_pdf_ps.getSubset( + fontdata = _backend_pdf_ps.get_glyphs_subset( font_path, "".join(chr(c) for c in chars) ) _log.debug( - f"SUBSET {font_path} " - f"{os.stat(font_path).st_size} " - f"↦ {fontdata.getbuffer().nbytes}" + "SUBSET %s %d ↦ %d", font_path, + os.stat(font_path).st_size, + fontdata.getbuffer().nbytes ) # give ttconv a subsetted font From f5eebbb8d35f42365ce92c153e57a39698a6d4fb Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 19 Jun 2021 03:02:04 +0530 Subject: [PATCH 11/29] Add fonttools min version for testing --- requirements/testing/minver.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/testing/minver.txt b/requirements/testing/minver.txt index 395a1d3e26c0..385de6d2dba0 100644 --- a/requirements/testing/minver.txt +++ b/requirements/testing/minver.txt @@ -7,3 +7,4 @@ packaging==20.0 pillow==6.2.0 pyparsing==2.2.1 python-dateutil==2.7 +fonttools==4.13.0 From 91417cde7ff2b5af5a365d5e0e4618def360fa42 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sun, 20 Jun 2021 00:42:02 +0530 Subject: [PATCH 12/29] Add fonttools in test workflow --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 64508593e238..c9e34bef1874 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,8 +145,7 @@ jobs: # Install dependencies from PyPI. python -m pip install --upgrade $PRE \ - cycler kiwisolver numpy packaging pillow pyparsing python-dateutil \ - setuptools-scm \ + cycler fonttools kiwisolver numpy pillow pyparsing python-dateutil setuptools-scm \ -r requirements/testing/all.txt \ ${{ matrix.extra-requirements }} From aca3bb53286a80d0dfe35361f25e254bc3176171 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 24 Jun 2021 03:54:06 +0530 Subject: [PATCH 13/29] Use ASCII characters for logging --- lib/matplotlib/backends/backend_pdf.py | 2 +- lib/matplotlib/backends/backend_ps.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 4d38040d29b9..ee1bd3673ce0 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1217,7 +1217,7 @@ def embedTTFType42(font, characters, descriptor): filename, "".join(chr(c) for c in characters) ) _log.debug( - "SUBSET %s %d ↦ %d", filename, + "SUBSET %s %d -> %d", filename, os.stat(filename).st_size, fontdata.getbuffer().nbytes ) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index dd470ed62138..b22137702fe8 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -963,7 +963,7 @@ def print_figure_impl(fh): font_path, "".join(chr(c) for c in chars) ) _log.debug( - "SUBSET %s %d ↦ %d", font_path, + "SUBSET %s %d -> %d", font_path, os.stat(font_path).st_size, fontdata.getbuffer().nbytes ) From 265a563afe83cb71768d45dbcbdc7150ac30fc80 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 24 Jun 2021 05:58:06 +0530 Subject: [PATCH 14/29] Add unit test for get_glyphs_subset --- lib/matplotlib/tests/test_backend_pdf.py | 34 +++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 384b88250c7f..353c8bd3525d 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -10,7 +10,14 @@ import matplotlib as mpl from matplotlib import dviread, pyplot as plt, checkdep_usetex, rcParams -from matplotlib.backends.backend_pdf import PdfPages +from matplotlib.cbook import _get_data_path +from matplotlib.ft2font import FT2Font + +# Weird py23 deprecation warning from fonttools +with pytest.warns(DeprecationWarning): + from matplotlib.backends._backend_pdf_ps import get_glyphs_subset + from matplotlib.backends.backend_pdf import PdfPages + from matplotlib.testing.decorators import check_figures_equal, image_comparison @@ -339,3 +346,28 @@ def test_kerning(): s = "AVAVAVAVAVAVAVAV€AAVV" fig.text(0, .25, s, size=5) fig.text(0, .75, s, size=20) + + +def test_glyphs_subset(): + fpath = str(_get_data_path("fonts/ttf/DejaVuSerif.ttf")) + chars = "these should be subsetted!" + + # non-subsetted FT2Font + nosubfont = FT2Font(fpath) + nosubfont.set_text(chars) + + # subsetted FT2Font + subfont = FT2Font(get_glyphs_subset(fpath, chars)) + subfont.set_text(chars) + + nosubcmap = nosubfont.get_charmap() + subcmap = subfont.get_charmap() + + # all unique chars must be available in subsetted font + assert set(chars).issuperset(set([chr(key) for key in subcmap.keys()])) + + # subsetted font's charmap should have less (or equal) entries + assert len(subcmap) <= len(nosubcmap) + + # since both objects are assigned same characters + assert subfont.get_num_glyphs() == nosubfont.get_num_glyphs() From 2193caa5c1f5780339ccee6b6def0c005be57d14 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 24 Jun 2021 06:00:50 +0530 Subject: [PATCH 15/29] Remove seek() --- lib/matplotlib/backends/backend_ps.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index b22137702fe8..94699313b2f1 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -978,7 +978,6 @@ def print_figure_impl(fh): font.get_char_index(c) for c in chars ] tmp.write(fontdata.getvalue()) - tmp.seek(0, 0) # TODO: allow convert_ttf_to_ps # to input file objects (BytesIO) convert_ttf_to_ps( From 5661f0d56ca074258066328a7048c92bb32949eb Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 24 Jun 2021 06:31:29 +0530 Subject: [PATCH 16/29] Add prefix to subsetted font names according to PDF spec --- lib/matplotlib/backends/backend_pdf.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index ee1bd3673ce0..defa57f44813 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -13,7 +13,9 @@ import logging import math import os +import random import re +import string import struct import time import types @@ -768,6 +770,13 @@ def newTextnote(self, text, positionRect=[-100, -100, 0, 0]): } self.pageAnnotations.append(theNote) + def _get_subsetted_psname(self, ps_name): + # first three characters as MPL + prefix = "MPL" + # rest 3 random characters + rand = ''.join(random.choice(string.ascii_uppercase) for x in range(3)) + return prefix + rand + "+" + ps_name + def finalize(self): """Write out the various deferred objects and the pdf end matter.""" @@ -1360,7 +1369,8 @@ def embedTTFType42(font, characters, descriptor): # Beginning of main embedTTF function... - ps_name = font.postscript_name.encode('ascii', 'replace') + ps_name = self._get_subsetted_psname(font.postscript_name) + ps_name = ps_name.encode('ascii', 'replace') ps_name = Name(ps_name) pclt = font.get_sfnt_table('pclt') or {'capHeight': 0, 'xHeight': 0} post = font.get_sfnt_table('post') or {'italicAngle': (0, 0)} From d0d766fe3e39f2615fa5b1d34c1c162316fac649 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 24 Jun 2021 10:03:10 +0530 Subject: [PATCH 17/29] Use charmap for prefix --- lib/matplotlib/backends/backend_pdf.py | 28 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index defa57f44813..991f3b1bed70 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -13,10 +13,10 @@ import logging import math import os -import random import re import string import struct +import sys import time import types import warnings @@ -770,12 +770,21 @@ def newTextnote(self, text, positionRect=[-100, -100, 0, 0]): } self.pageAnnotations.append(theNote) - def _get_subsetted_psname(self, ps_name): - # first three characters as MPL - prefix = "MPL" - # rest 3 random characters - rand = ''.join(random.choice(string.ascii_uppercase) for x in range(3)) - return prefix + rand + "+" + ps_name + def _get_subsetted_psname(self, ps_name, charmap): + def toStr(n, base): + if n < base: + return string.ascii_uppercase[n] + else: + return ( + toStr(n // base, base) + string.ascii_uppercase[n % base] + ) + + # encode to string using base 26 + hashed = hash(frozenset(charmap.keys())) % ((sys.maxsize + 1) * 2) + prefix = toStr(hashed, 26) + + # get first 6 characters from prefix + return prefix[:6] + "+" + ps_name def finalize(self): """Write out the various deferred objects and the pdf end matter.""" @@ -1369,7 +1378,10 @@ def embedTTFType42(font, characters, descriptor): # Beginning of main embedTTF function... - ps_name = self._get_subsetted_psname(font.postscript_name) + ps_name = self._get_subsetted_psname( + font.postscript_name, + font.get_charmap() + ) ps_name = ps_name.encode('ascii', 'replace') ps_name = Name(ps_name) pclt = font.get_sfnt_table('pclt') or {'capHeight': 0, 'xHeight': 0} From 5ea7f1bea766ca2f44c0323aa909e4880d903ec0 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 24 Jun 2021 10:09:10 +0530 Subject: [PATCH 18/29] Update fonttools requirements --- requirements/testing/minver.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/testing/minver.txt b/requirements/testing/minver.txt index 385de6d2dba0..578cd205f9b5 100644 --- a/requirements/testing/minver.txt +++ b/requirements/testing/minver.txt @@ -7,4 +7,4 @@ packaging==20.0 pillow==6.2.0 pyparsing==2.2.1 python-dateutil==2.7 -fonttools==4.13.0 +fonttools==4.22.0 diff --git a/setup.py b/setup.py index 37370068c4d8..c72e13623c2d 100644 --- a/setup.py +++ b/setup.py @@ -325,7 +325,7 @@ def make_release_tree(self, base_dir, files): ], install_requires=[ "cycler>=0.10", - "fonttools>=4.13.0,<5.0", + "fonttools>=4.22.0", "kiwisolver>=1.0.1", "numpy>=1.17", "packaging>=20.0", From 17873f392d44bd9dd73939d3fddb196dd6e6f105 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Wed, 7 Jul 2021 12:54:34 +0530 Subject: [PATCH 19/29] Drop PfEd table --- lib/matplotlib/backends/_backend_pdf_ps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 207e6e2527e5..15a5578461cc 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -36,8 +36,8 @@ def get_glyphs_subset(fontfile, characters): options = subset.Options(glyph_names=True, recommended_glyphs=True) - # prevent subsetting FontForge Timestamp table - options.drop_tables += ['FFTM'] + # prevent subsetting FontForge Timestamp and other tables + options.drop_tables += ['FFTM', 'PfEd'] with subset.load_font(fontfile, options) as font: subsetter = subset.Subsetter(options=options) From 9837733de6474c877ea825c7e4aeadbbe970e6d5 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 12 Jul 2021 13:34:13 +0530 Subject: [PATCH 20/29] flush before reading the contents back from tmp file --- lib/matplotlib/backends/backend_ps.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 94699313b2f1..1f37a30c748f 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -978,6 +978,8 @@ def print_figure_impl(fh): font.get_char_index(c) for c in chars ] tmp.write(fontdata.getvalue()) + tmp.flush() + # TODO: allow convert_ttf_to_ps # to input file objects (BytesIO) convert_ttf_to_ps( From f5097311c2238e9dc758acacd18c744bebd10c71 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 12 Jul 2021 13:35:35 +0530 Subject: [PATCH 21/29] Fix testing for subsetting --- lib/matplotlib/testing/conftest.py | 2 ++ lib/matplotlib/tests/test_backend_pdf.py | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index f35eddf96b00..d0aa85367529 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -19,6 +19,8 @@ def pytest_configure(config): ("markers", "pytz: Tests that require pytz to be installed."), ("markers", "network: Tests that reach out to the network."), ("filterwarnings", "error"), + ("filterwarnings", + "ignore:.*The py23 module has been deprecated:DeprecationWarning"), ]: config.addinivalue_line(key, value) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 353c8bd3525d..bd9ab6fcb2de 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -12,11 +12,8 @@ from matplotlib import dviread, pyplot as plt, checkdep_usetex, rcParams from matplotlib.cbook import _get_data_path from matplotlib.ft2font import FT2Font - -# Weird py23 deprecation warning from fonttools -with pytest.warns(DeprecationWarning): - from matplotlib.backends._backend_pdf_ps import get_glyphs_subset - from matplotlib.backends.backend_pdf import PdfPages +from matplotlib.backends._backend_pdf_ps import get_glyphs_subset +from matplotlib.backends.backend_pdf import PdfPages from matplotlib.testing.decorators import check_figures_equal, image_comparison @@ -350,7 +347,7 @@ def test_kerning(): def test_glyphs_subset(): fpath = str(_get_data_path("fonts/ttf/DejaVuSerif.ttf")) - chars = "these should be subsetted!" + chars = "these should be subsetted! 1234567890" # non-subsetted FT2Font nosubfont = FT2Font(fpath) @@ -364,7 +361,7 @@ def test_glyphs_subset(): subcmap = subfont.get_charmap() # all unique chars must be available in subsetted font - assert set(chars).issuperset(set([chr(key) for key in subcmap.keys()])) + assert set(chars).issubset(set([chr(key) for key in subcmap.keys()])) # subsetted font's charmap should have less (or equal) entries assert len(subcmap) <= len(nosubcmap) From a362601c934f3bd1eabd3c234d7870d19d99ac06 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 12 Jul 2021 13:53:07 +0530 Subject: [PATCH 22/29] Add whatsnew entry for Type42 subsetting --- doc/users/next_whats_new/subsetting.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 doc/users/next_whats_new/subsetting.rst diff --git a/doc/users/next_whats_new/subsetting.rst b/doc/users/next_whats_new/subsetting.rst new file mode 100644 index 000000000000..89c33f58d371 --- /dev/null +++ b/doc/users/next_whats_new/subsetting.rst @@ -0,0 +1,22 @@ +Type 42 Subsetting is now enabled for PDF/PS backends +----------------------------------------------------- + +`~matplotlib.backends.backend_pdf` and `~matplotlib.backends.backend_ps` now use +a unified Type 42 font subsetting interface, with the help of `fontTools `_ + +Set `~matplotlib.RcParams`'s *fonttype* value as ``42`` to trigger this workflow: + +.. code-block:: + + # for PDF backend + plt.rcParams['pdf.fonttype'] = 42 + + # for PS backend + plt.rcParams['ps.fonttype'] = 42 + + + fig, ax = plt.subplots() + ax.text(0.4, 0.5, 'subsetted document is smaller in size!') + + fig.savefig("document.pdf") + fig.savefig("document.ps") From 57267a3187b4a08b30c0c63705b7328d029c4bd7 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 17 Jul 2021 23:09:19 +0530 Subject: [PATCH 23/29] Fix subset tests --- lib/matplotlib/tests/test_backend_pdf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index bd9ab6fcb2de..8e16eb2b7b94 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -361,10 +361,10 @@ def test_glyphs_subset(): subcmap = subfont.get_charmap() # all unique chars must be available in subsetted font - assert set(chars).issubset(set([chr(key) for key in subcmap.keys()])) + assert set(chars) == set(chr(key) for key in subcmap.keys()) - # subsetted font's charmap should have less (or equal) entries - assert len(subcmap) <= len(nosubcmap) + # subsetted font's charmap should have less entries + assert len(subcmap) < len(nosubcmap) # since both objects are assigned same characters assert subfont.get_num_glyphs() == nosubfont.get_num_glyphs() From 7571055ea1160ff33e4cb2e7415962da616fef62 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sun, 18 Jul 2021 19:16:13 +0530 Subject: [PATCH 24/29] Add PS test for multiple fonttypes --- lib/matplotlib/tests/test_backend_ps.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index b1ea2cd1736d..07eb0382010b 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -207,3 +207,18 @@ def test_type42_font_without_prep(): mpl.rcParams["mathtext.fontset"] = "stix" plt.figtext(0.5, 0.5, "Mass $m$") + + +@pytest.mark.parametrize('fonttype', ["3", "42"]) +def test_fonttype(fonttype): + mpl.rcParams["ps.fonttype"] = fonttype + fig, ax = plt.subplots() + + ax.text(0.25, 0.5, "Forty-two is the answer to everything!") + + buf = io.BytesIO() + fig.savefig(buf, format="ps") + + test = b'/FontType ' + bytes(f"{fonttype}", encoding='utf-8') + b' def' + + assert re.search(test, buf.getvalue(), re.MULTILINE) From 1630ad90d11d1c5c718f3adb80229b2b35d42043 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 19 Jul 2021 14:08:09 +0530 Subject: [PATCH 25/29] Use TemporaryDirectory instead of NamedTemporaryFile --- lib/matplotlib/backends/backend_ps.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 1f37a30c748f..7cdab6c6a053 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -970,20 +970,21 @@ def print_figure_impl(fh): # give ttconv a subsetted font # along with updated glyph_ids - with tempfile.NamedTemporaryFile( - suffix=".ttf" - ) as tmp: + with TemporaryDirectory() as tmpdir: + tmpfile = os.path.join(tmpdir, "tmp.ttf") font = FT2Font(fontdata) glyph_ids = [ font.get_char_index(c) for c in chars ] - tmp.write(fontdata.getvalue()) - tmp.flush() + + with open(tmpfile, 'wb') as tmp: + tmp.write(fontdata.getvalue()) + tmp.flush() # TODO: allow convert_ttf_to_ps # to input file objects (BytesIO) convert_ttf_to_ps( - os.fsencode(tmp.name), + os.fsencode(tmpfile), fh, fonttype, glyph_ids, From fa197d2dc9a15df2fa03da69ab7aa193a4c80c8f Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 22 Jul 2021 01:09:36 +0530 Subject: [PATCH 26/29] Add fontTools in dependencies.rst --- doc/devel/dependencies.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index dff7a9b463b9..62453e5c18c9 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -22,6 +22,7 @@ reference. * `kiwisolver `_ (>= 1.0.1) * `Pillow `_ (>= 6.2) * `pyparsing `_ (>=2.2.1) +* `fontTools `_ (>=4.22.0) .. _optional_dependencies: From fe583dd2f8e46985b221b7860e746a923602e65c Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 22 Jul 2021 01:10:03 +0530 Subject: [PATCH 27/29] Add API changenote for new dependency --- doc/api/next_api_changes/development/20391-AG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 doc/api/next_api_changes/development/20391-AG.rst diff --git a/doc/api/next_api_changes/development/20391-AG.rst b/doc/api/next_api_changes/development/20391-AG.rst new file mode 100644 index 000000000000..37cc539c5ad2 --- /dev/null +++ b/doc/api/next_api_changes/development/20391-AG.rst @@ -0,0 +1,8 @@ +fontTools for type 42 subsetting +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A new dependency known as `fontTools `_ +is integrated in with Maptlotlib 3.5 + +It is designed to be used with PS/EPS and PDF documents; and handles +Type 42 font subsetting. From a95f2b6a4aba5be1bb6faf65f77189906c2b96a9 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 22 Jul 2021 01:12:52 +0530 Subject: [PATCH 28/29] Rebase tests.yml for packaging --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c9e34bef1874..28398cd1973e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,7 +145,8 @@ jobs: # Install dependencies from PyPI. python -m pip install --upgrade $PRE \ - cycler fonttools kiwisolver numpy pillow pyparsing python-dateutil setuptools-scm \ + cycler fonttools kiwisolver numpy packaging pillow pyparsing \ + python-dateutil setuptools-scm \ -r requirements/testing/all.txt \ ${{ matrix.extra-requirements }} From 85f4377181609f3b705de69cd8dbd12aef75c73a Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 22 Jul 2021 19:22:40 +0530 Subject: [PATCH 29/29] Keep a reference to non-subsetted font for XObjects --- lib/matplotlib/backends/backend_pdf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 991f3b1bed70..89afe92ce913 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1239,6 +1239,9 @@ def embedTTFType42(font, characters, descriptor): os.stat(filename).st_size, fontdata.getbuffer().nbytes ) + # We need this ref for XObjects + full_font = font + # reload the font object from the subset # (all the necessary data could probably be obtained directly # using fontLib.ttLib) @@ -1325,10 +1328,10 @@ def embedTTFType42(font, characters, descriptor): glyph_ids = [] for ccode in characters: if not _font_supports_char(fonttype, chr(ccode)): - gind = font.get_char_index(ccode) + gind = full_font.get_char_index(ccode) glyph_ids.append(gind) - bbox = [cvt(x, nearest=False) for x in font.bbox] + bbox = [cvt(x, nearest=False) for x in full_font.bbox] rawcharprocs = _get_pdf_charprocs(filename, glyph_ids) for charname in sorted(rawcharprocs): stream = rawcharprocs[charname]