From d0906aaddb9e70960b22fcba48f9b793a111457d Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 26 Jul 2021 03:41:52 +0530 Subject: [PATCH 01/30] Parse fallback_list through wrapper --- src/ft2font_wrapper.cpp | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 06cbc22dad9c..634f1dd52986 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -264,6 +264,7 @@ typedef struct Py_ssize_t shape[2]; Py_ssize_t strides[2]; Py_ssize_t suboffsets[2]; + std::vector fallbacks; } PyFT2Font; static unsigned long read_from_file_callback(FT_Stream stream, @@ -361,15 +362,18 @@ const char *PyFT2Font_init__doc__ = static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) { - PyObject *filename = NULL, *open = NULL, *data = NULL; + printf("PyFT2Font init!\n"); + PyObject *filename = NULL, *open = NULL, *data = NULL, *fallback_list = NULL; FT_Open_Args open_args; long hinting_factor = 8; int kerning_factor = 0; - const char *names[] = { "filename", "hinting_factor", "_kerning_factor", NULL }; + const char *names[] = { + "filename", "hinting_factor", "_fallback_list", "_kerning_factor", NULL + }; if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O|l$i:FT2Font", (char **)names, &filename, - &hinting_factor, &kerning_factor)) { + args, kwds, "O|l$Oi:FT2Font", (char **)names, &filename, + &hinting_factor, &fallback_list, &kerning_factor)) { return -1; } @@ -382,6 +386,21 @@ static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) open_args.flags = FT_OPEN_STREAM; open_args.stream = &self->stream; + if (fallback_list && PyList_Check(fallback_list)) { + Py_ssize_t size = PyList_Size(fallback_list); + + for (int i = 0; i < size; ++i) { + PyObject* item = PyList_GetItem(fallback_list, i); + + // TODO: check whether item is actually an FT2Font + FT2Font *fback = reinterpret_cast(item)->x; + self->fallbacks.push_back(fback); + } + + Py_INCREF(fallback_list); + } + printf("Fallback SIZE: %lu \n", self->fallbacks.size()); + if (PyBytes_Check(filename) || PyUnicode_Check(filename)) { if (!(open = PyDict_GetItemString(PyEval_GetBuiltins(), "open")) // Borrowed reference. || !(self->py_file = PyObject_CallFunction(open, "Os", filename, "rb"))) { @@ -403,7 +422,7 @@ static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) Py_CLEAR(data); CALL_CPP_FULL( - "FT2Font", (self->x = new FT2Font(open_args, hinting_factor)), + "FT2Font", (self->x = new FT2Font(open_args, hinting_factor, self->fallbacks)), Py_CLEAR(self->py_file), -1); CALL_CPP_INIT("FT2Font->set_kerning_factor", (self->x->set_kerning_factor(kerning_factor))); @@ -444,6 +463,7 @@ static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args, PyObject *k { double ptsize; double dpi; + printf("PyFT, set_size called!\n"); if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) { return NULL; From f6f2fd7e0a17fc509463d1b21d5a63830c1852ec Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 26 Jul 2021 03:42:58 +0530 Subject: [PATCH 02/30] Define new/modify previous FT2Font functions --- src/ft2font.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ft2font.h b/src/ft2font.h index 0863f3450b36..b09ad801bd34 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -69,17 +69,27 @@ class FT2Font { public: - FT2Font(FT_Open_Args &open_args, long hinting_factor); + FT2Font(FT_Open_Args &open_args, long hinting_factor, std::vector &fallback_list); virtual ~FT2Font(); void clear(); void set_size(double ptsize, double dpi); void set_charmap(int i); + void set_fallbacks(std::vector &fallback_list); + void check(); void select_charmap(unsigned long i); void set_text( size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys); int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode); + int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta); void set_kerning_factor(int factor); void load_char(long charcode, FT_Int32 flags); + void load_char_with_fallback(FT2Font* &ft_object_with_glyph, + FT_UInt &final_glyph_index, + std::vector &parent_glyphs, + long charcode, + FT_Int32 flags, + FT_Error &charcode_error, + FT_Error &glyph_error); void load_glyph(FT_UInt glyph_index, FT_Int32 flags); void get_width_height(long *width, long *height); void get_bitmap_offset(long *x, long *y); @@ -123,6 +133,7 @@ class FT2Font FT_Face face; FT_Vector pen; /* untransformed origin */ std::vector glyphs; + std::vector fallbacks; FT_BBox bbox; FT_Pos advance; long hinting_factor; From 51997738d72dcce66c44fd15226578ed9efe79d2 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 26 Jul 2021 03:44:05 +0530 Subject: [PATCH 03/30] Implement new/modify previous FT2Font functions --- src/ft2font.cpp | 150 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 133 insertions(+), 17 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index fdea4c39bec0..0b52c72551db 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -169,12 +169,8 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, m_dirty = true; } -static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) +static FT_UInt ft_glyph_warn(FT_ULong charcode) { - FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - if (glyph_index) { - return glyph_index; - } PyObject *text_helpers = NULL, *tmp = NULL; if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) || !(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) { @@ -189,6 +185,14 @@ static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) return 0; } +static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + if (glyph_index) { + return glyph_index; + } + return ft_glyph_warn(charcode); +} // ft_outline_decomposer should be passed to FT_Outline_Decompose. On the // first pass, vertices and codes are set to NULL, and index is simply @@ -319,7 +323,10 @@ FT2Font::get_path() return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); } -FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face(NULL) +FT2Font::FT2Font(FT_Open_Args &open_args, + long hinting_factor_, + std::vector &fallback_list) + : image(), face(NULL) { clear(); @@ -353,6 +360,9 @@ FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face( FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); + + // Set fallbacks + set_fallbacks(fallback_list); } FT2Font::~FT2Font() @@ -364,6 +374,10 @@ FT2Font::~FT2Font() if (face) { FT_Done_Face(face); } + + for (uint i = 0; i < fallbacks.size(); i ++) { + fallbacks[i]->~FT2Font(); + } } void FT2Font::clear() @@ -376,6 +390,17 @@ void FT2Font::clear() } glyphs.clear(); + + for (uint i = 0; i < fallbacks.size(); i ++) { + fallbacks[i]->clear(); + } +} + +void FT2Font::check() { + printf("Fallback num: -1; Numface: %lu\n", get_face()->num_glyphs); + for (uint i = 0; i < fallbacks.size(); i++) { + printf("Fallback num: %u; Numface: %lu\n", i, fallbacks[i]->get_face()->num_glyphs); + } } void FT2Font::set_size(double ptsize, double dpi) @@ -387,8 +412,18 @@ void FT2Font::set_size(double ptsize, double dpi) } FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); + + for (uint i = 0; i < fallbacks.size(); i++) { + fallbacks[i]->set_size(ptsize, dpi); + } } +void FT2Font::set_fallbacks(std::vector &fallback_list) +{ + fallbacks.assign(fallback_list.begin(), fallback_list.end()); +} + + void FT2Font::set_charmap(int i) { if (i >= face->num_charmaps) { @@ -421,15 +456,32 @@ int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode) } } +int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta) +{ + if (!FT_HAS_KERNING(face)) { + return 0; + } + + if (!FT_Get_Kerning(face, left, right, mode, &delta)) { + return (int)(delta.x) / (hinting_factor << kerning_factor); + } else { + return 0; + } +} + void FT2Font::set_kerning_factor(int factor) { kerning_factor = factor; + for (uint i = 0; i < fallbacks.size(); i ++){ + fallbacks[i]->set_kerning_factor(factor); + } } void FT2Font::set_text( size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys) { FT_Matrix matrix; /* transformation matrix */ + check(); angle = angle / 360.0 * 2 * M_PI; @@ -452,28 +504,47 @@ void FT2Font::set_text( FT_BBox glyph_bbox; FT_Pos last_advance; - glyph_index = ft_get_char_index_or_warn(face, codepoints[n]); + FT_UInt final_glyph_index; + FT_Error charcode_error, glyph_error; + FT2Font *ft_object_with_glyph = this; + printf("\nBefore loading char! \n"); + load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, codepoints[n], + flags, charcode_error, glyph_error); + printf("Final ft2font: %lu\n", ft_object_with_glyph->get_face()->num_glyphs); + if (charcode_error || glyph_error) { + ft_glyph_warn((FT_ULong)codepoints[n]); + return; + } + + glyph_index = final_glyph_index; // retrieve kerning distance and move pen position if (use_kerning && previous && glyph_index) { FT_Vector delta; - FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); + ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT, delta); pen.x += delta.x / (hinting_factor << kerning_factor); } - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load glyph", error); - } + + // DONE with load_char_with_fallback + // if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { + // throw_ft_error("Could not load glyph", error); + // } + // ignore errors, jump to next glyph // extract glyph image and store it in our table - FT_Glyph thisGlyph; - if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { - throw_ft_error("Could not get glyph", error); - } + // DONE with load_char_with_fallback + // FT_Glyph thisGlyph; + // if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { + // throw_ft_error("Could not get glyph", error); + // } // ignore errors, jump to next glyph - last_advance = face->glyph->advance.x; + FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; + + // Compare with thisGlyph? + last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; FT_Glyph_Transform(thisGlyph, 0, &pen); FT_Glyph_Transform(thisGlyph, &matrix, 0); xys.push_back(pen.x); @@ -489,7 +560,10 @@ void FT2Font::set_text( pen.x += last_advance; previous = glyph_index; - glyphs.push_back(thisGlyph); + + printf("Set_Text complete! \n"); + // DONE with load_char_with_fallback + // glyphs.push_back(thisGlyph); } FT_Vector_Transform(&pen, &matrix); @@ -513,6 +587,48 @@ void FT2Font::load_char(long charcode, FT_Int32 flags) glyphs.push_back(thisGlyph); } +void FT2Font::load_char_with_fallback(FT2Font* &ft_object_with_glyph, + FT_UInt &final_glyph_index, + std::vector &parent_glyphs, + long charcode, + FT_Int32 flags, + FT_Error &charcode_error, + FT_Error &glyph_error) +{ + printf("loading char!\n"); + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + + if (glyph_index) { + printf("glyph found!\n"); + charcode_error = FT_Load_Glyph(face, glyph_index, flags); + // throw_ft_error("Could not load charcode", error); + FT_Glyph thisGlyph; + glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); + // throw_ft_error("Could not get glyph", error); + final_glyph_index = glyph_index; + parent_glyphs.push_back(thisGlyph); + } + + else { + printf("glyph not found! Fallback size: %lu\n", fallbacks.size()); + for (uint i = 0; i < fallbacks.size(); ++i) { + uint current_size = parent_glyphs.size(); + printf("Fallback %u: %u\n", i, current_size); + fallbacks[i]->load_char_with_fallback(ft_object_with_glyph, final_glyph_index, + parent_glyphs, charcode, flags, charcode_error, + glyph_error); + printf("Got back from fallback load char!\n"); + // jump out if glyph size increased + if (parent_glyphs.size() == current_size + 1) { + printf("size increased!\n"); + ft_object_with_glyph = fallbacks[i]; + printf("Ft object assigned!\n"); + return; + } + } + } +} + void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { From 88da97f58f7ac1836468802f28b2dfe685c28aa0 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 26 Jul 2021 04:02:18 +0530 Subject: [PATCH 04/30] Parse multiple fonts for a single FT2Font object --- lib/matplotlib/font_manager.py | 141 +++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 9d45575eb13d..02c44e781368 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -23,6 +23,7 @@ # - setWeights function needs improvement # - 'light' is an invalid weight value, remove it. +from collections import OrderedDict import dataclasses from functools import lru_cache import json @@ -1308,6 +1309,118 @@ def findfont(self, prop, fontext='ttf', directory=None, prop, fontext, directory, fallback_to_default, rebuild_if_missing, rc_params) + def find_fontsprop(self, prop, fontext='ttf', directory=None, + fallback_to_default=True, rebuild_if_missing=True): + """ + Find font families that most closely matches the given properties. + + Parameters + ---------- + prop : str or `~matplotlib.font_manager.FontProperties` + The font properties to search for. This can be either a + `.FontProperties` object or a string defining a + `fontconfig patterns`_. + + fontext : {'ttf', 'afm'}, default: 'ttf' + The extension of the font file: + + - 'ttf': TrueType and OpenType fonts (.ttf, .ttc, .otf) + - 'afm': Adobe Font Metrics (.afm) + + directory : str, optional + If given, only search this directory and its subdirectories. + + fallback_to_default : bool + If True, will fallback to the default font family (usually + "DejaVu Sans" or "Helvetica") if none of the families were found. + + rebuild_if_missing : bool + Whether to rebuild the font cache and search again if the first + match appears to point to a nonexisting font (i.e., the font cache + contains outdated entries). + + Returns + ------- + OrderedDict + key, value pair of families and their corresponding filepaths. + + Notes + ----- + This is a plugin to original findfont API, which only returns a + single font for given font properties. Instead, this API returns + an OrderedDict containing multiple fonts and their filepaths which + closely match the given font properties. + Since this internally uses original API, there's no change + to the logic of performing the nearest neighbor search. + See `findfont` for more details. + + """ + + rc_params = tuple(tuple(rcParams[key]) for key in [ + "font.serif", "font.sans-serif", "font.cursive", "font.fantasy", + "font.monospace"]) + + prop = FontProperties._from_any(prop) + ffamily = prop.get_family() + + fpaths = OrderedDict() + for fidx in range(len(ffamily)): + cprop = prop.copy() + + # set current prop's family + cprop.set_family(ffamily[fidx]) + + # do not fall back to default font + fpath = self._findfontsprop_cached( + ffamily[fidx], cprop, fontext, directory, + False, rebuild_if_missing, rc_params + ) + if fpath: + fpaths[ffamily[fidx]] = fpath + + # only add default family if no other font was found + # and fallback_to_default is enabled + if not fpaths: + if fallback_to_default: + dfamily = self.defaultFamily[fontext] + cprop = prop.copy().set_family(dfamily) + fpath = self._findfontsprop_cached( + dfamily, cprop, fontext, directory, + True, rebuild_if_missing, rc_params + ) + fpaths[dfamily] = fpath + else: + raise ValueError("Failed to find any font, and fallback " + "to the default font was disabled.") + + return fpaths + + + @lru_cache() + def _findfontsprop_cached( + self, family, prop, fontext, directory, + fallback_to_default, rebuild_if_missing, rc_params + ): + try: + return self._findfont_cached( + prop, fontext, directory, fallback_to_default, + rebuild_if_missing, rc_params + ) + except ValueError: + if not fallback_to_default: + if family.lower() in font_family_aliases: + _log.warning( + "findfont: Generic family %r not found because " + "none of the following families were found: %s", + family, + ", ".join(self._expand_aliases(family)) + ) + else: + _log.warning( + 'findfont: Font family \'%s\' not found.', family + ) + + @lru_cache() def _findfont_cached(self, prop, fontext, directory, fallback_to_default, rebuild_if_missing, rc_params): @@ -1401,9 +1514,23 @@ def is_opentype_cff_font(filename): @lru_cache(64) -def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id): - return ft2font.FT2Font( - filename, hinting_factor, _kerning_factor=_kerning_factor) +def _get_font(fpaths, hinting_factor, *, _kerning_factor, thread_id): + ftobjects = [] + for fpath in fpaths[1:]: + ftobject = ft2font.FT2Font( + fpath, hinting_factor, + _kerning_factor=_kerning_factor + ) + ftobjects.append(ftobject) + + print("\nIn Python:", ftobjects, "\n") + ft2font_object = ft2font.FT2Font( + fpaths[0], hinting_factor, + _fallback_list=ftobjects, + _kerning_factor=_kerning_factor + ) + print("\nBack to Python!\n") + return ft2font_object # FT2Font objects cannot be used across fork()s because they reference the same @@ -1417,11 +1544,14 @@ def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id): def get_font(filename, hinting_factor=None): # Resolving the path avoids embedding the font twice in pdf/ps output if a # single font is selected using two different relative paths. - filename = _cached_realpath(filename) + if isinstance(filename, OrderedDict): + paths = tuple(_cached_realpath(fname) for fname in filename.values()) + else: + paths = (_cached_realpath(filename),) if hinting_factor is None: hinting_factor = rcParams['text.hinting_factor'] # also key on the thread ID to prevent segfaults with multi-threading - return _get_font(filename, hinting_factor, + return _get_font(paths, hinting_factor, _kerning_factor=rcParams['text.kerning_factor'], thread_id=threading.get_ident()) @@ -1446,3 +1576,4 @@ def _load_fontmanager(*, try_read_cache=True): fontManager = _load_fontmanager() findfont = fontManager.findfont +find_fontsprop = fontManager.find_fontsprop From c6c3a45e07034cbded66f1ec4fad6281beeec070 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Mon, 26 Jul 2021 04:03:21 +0530 Subject: [PATCH 05/30] Trigger font fallback for Agg backend --- lib/matplotlib/backends/backend_agg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b937c64fce95..f15b7a2d1381 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -35,7 +35,7 @@ from matplotlib.backend_bases import ( _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase, RendererBase) -from matplotlib.font_manager import findfont, get_font +from matplotlib.font_manager import find_fontsprop, get_font from matplotlib.ft2font import (LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, LOAD_DEFAULT, LOAD_NO_AUTOHINT) from matplotlib.mathtext import MathTextParser @@ -186,6 +186,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # We pass '0' for angle here, since it will be rotated (in raster # space) in the following call to draw_text_image). font.set_text(s, 0, flags=flags) + font.draw_glyphs_to_bitmap( antialiased=mpl.rcParams['text.antialiased']) d = font.get_descent() / 64.0 @@ -251,7 +252,7 @@ def _get_agg_font(self, prop): """ Get the font for text instance t, caching for efficiency """ - fname = findfont(prop) + fname = find_fontsprop(prop) font = get_font(fname) font.clear() From e868fbc703b2b3a6978ca6f761823fec1a8b87e8 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 05:38:22 +0530 Subject: [PATCH 06/30] Remove prints --- lib/matplotlib/backends/backend_agg.py | 1 - lib/matplotlib/font_manager.py | 4 --- src/ft2font.cpp | 39 -------------------------- src/ft2font.h | 1 - src/ft2font_wrapper.cpp | 3 -- 5 files changed, 48 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index f15b7a2d1381..b5a2f85affc1 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -186,7 +186,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # We pass '0' for angle here, since it will be rotated (in raster # space) in the following call to draw_text_image). font.set_text(s, 0, flags=flags) - font.draw_glyphs_to_bitmap( antialiased=mpl.rcParams['text.antialiased']) d = font.get_descent() / 64.0 diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 02c44e781368..aca91f25d7e4 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1395,7 +1395,6 @@ def find_fontsprop(self, prop, fontext='ttf', directory=None, return fpaths - @lru_cache() def _findfontsprop_cached( self, family, prop, fontext, directory, @@ -1420,7 +1419,6 @@ def _findfontsprop_cached( 'findfont: Font family \'%s\' not found.', family ) - @lru_cache() def _findfont_cached(self, prop, fontext, directory, fallback_to_default, rebuild_if_missing, rc_params): @@ -1523,13 +1521,11 @@ def _get_font(fpaths, hinting_factor, *, _kerning_factor, thread_id): ) ftobjects.append(ftobject) - print("\nIn Python:", ftobjects, "\n") ft2font_object = ft2font.FT2Font( fpaths[0], hinting_factor, _fallback_list=ftobjects, _kerning_factor=_kerning_factor ) - print("\nBack to Python!\n") return ft2font_object diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 0b52c72551db..179256f5208d 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -396,13 +396,6 @@ void FT2Font::clear() } } -void FT2Font::check() { - printf("Fallback num: -1; Numface: %lu\n", get_face()->num_glyphs); - for (uint i = 0; i < fallbacks.size(); i++) { - printf("Fallback num: %u; Numface: %lu\n", i, fallbacks[i]->get_face()->num_glyphs); - } -} - void FT2Font::set_size(double ptsize, double dpi) { FT_Error error = FT_Set_Char_Size( @@ -481,7 +474,6 @@ void FT2Font::set_text( size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys) { FT_Matrix matrix; /* transformation matrix */ - check(); angle = angle / 360.0 * 2 * M_PI; @@ -507,10 +499,8 @@ void FT2Font::set_text( FT_UInt final_glyph_index; FT_Error charcode_error, glyph_error; FT2Font *ft_object_with_glyph = this; - printf("\nBefore loading char! \n"); load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, codepoints[n], flags, charcode_error, glyph_error); - printf("Final ft2font: %lu\n", ft_object_with_glyph->get_face()->num_glyphs); if (charcode_error || glyph_error) { ft_glyph_warn((FT_ULong)codepoints[n]); return; @@ -525,25 +515,9 @@ void FT2Font::set_text( pen.x += delta.x / (hinting_factor << kerning_factor); } - // DONE with load_char_with_fallback - // if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - // throw_ft_error("Could not load glyph", error); - // } - - // ignore errors, jump to next glyph - // extract glyph image and store it in our table - - // DONE with load_char_with_fallback - // FT_Glyph thisGlyph; - // if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { - // throw_ft_error("Could not get glyph", error); - // } - // ignore errors, jump to next glyph - FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; - // Compare with thisGlyph? last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; FT_Glyph_Transform(thisGlyph, 0, &pen); FT_Glyph_Transform(thisGlyph, &matrix, 0); @@ -560,10 +534,6 @@ void FT2Font::set_text( pen.x += last_advance; previous = glyph_index; - - printf("Set_Text complete! \n"); - // DONE with load_char_with_fallback - // glyphs.push_back(thisGlyph); } FT_Vector_Transform(&pen, &matrix); @@ -595,34 +565,25 @@ void FT2Font::load_char_with_fallback(FT2Font* &ft_object_with_glyph, FT_Error &charcode_error, FT_Error &glyph_error) { - printf("loading char!\n"); FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); if (glyph_index) { - printf("glyph found!\n"); charcode_error = FT_Load_Glyph(face, glyph_index, flags); - // throw_ft_error("Could not load charcode", error); FT_Glyph thisGlyph; glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); - // throw_ft_error("Could not get glyph", error); final_glyph_index = glyph_index; parent_glyphs.push_back(thisGlyph); } else { - printf("glyph not found! Fallback size: %lu\n", fallbacks.size()); for (uint i = 0; i < fallbacks.size(); ++i) { uint current_size = parent_glyphs.size(); - printf("Fallback %u: %u\n", i, current_size); fallbacks[i]->load_char_with_fallback(ft_object_with_glyph, final_glyph_index, parent_glyphs, charcode, flags, charcode_error, glyph_error); - printf("Got back from fallback load char!\n"); // jump out if glyph size increased if (parent_glyphs.size() == current_size + 1) { - printf("size increased!\n"); ft_object_with_glyph = fallbacks[i]; - printf("Ft object assigned!\n"); return; } } diff --git a/src/ft2font.h b/src/ft2font.h index b09ad801bd34..a656f08c5585 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -75,7 +75,6 @@ class FT2Font void set_size(double ptsize, double dpi); void set_charmap(int i); void set_fallbacks(std::vector &fallback_list); - void check(); void select_charmap(unsigned long i); void set_text( size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys); diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 634f1dd52986..118b39bd079d 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -362,7 +362,6 @@ const char *PyFT2Font_init__doc__ = static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) { - printf("PyFT2Font init!\n"); PyObject *filename = NULL, *open = NULL, *data = NULL, *fallback_list = NULL; FT_Open_Args open_args; long hinting_factor = 8; @@ -399,7 +398,6 @@ static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) Py_INCREF(fallback_list); } - printf("Fallback SIZE: %lu \n", self->fallbacks.size()); if (PyBytes_Check(filename) || PyUnicode_Check(filename)) { if (!(open = PyDict_GetItemString(PyEval_GetBuiltins(), "open")) // Borrowed reference. @@ -463,7 +461,6 @@ static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args, PyObject *k { double ptsize; double dpi; - printf("PyFT, set_size called!\n"); if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) { return NULL; From 4230469b53dc2777f07e702379c19ab05b9e5581 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:18:48 +0530 Subject: [PATCH 07/30] Cleanup wrapper --- src/forward.h | 5 + src/ft2font.cpp | 227 ++++++++++++++++++++++++++++++++++------ src/ft2font.h | 43 ++++++-- src/ft2font_wrapper.cpp | 213 ++++++++++++++++++++++++++++++------- 4 files changed, 413 insertions(+), 75 deletions(-) create mode 100644 src/forward.h diff --git a/src/forward.h b/src/forward.h new file mode 100644 index 000000000000..dd2f71e1d6c3 --- /dev/null +++ b/src/forward.h @@ -0,0 +1,5 @@ +#pragma once + +struct PyFT2Font; + +class FT2Font; diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 179256f5208d..ee2650475542 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -325,10 +325,12 @@ FT2Font::get_path() FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_, - std::vector &fallback_list) + std::vector &fallback_list, + PyFT2Font *py_pointer) : image(), face(NULL) { clear(); + py_font = py_pointer; FT_Error error = FT_Open_Face(_ft2Library, &open_args, 0, &face); @@ -372,12 +374,9 @@ FT2Font::~FT2Font() } if (face) { + printf("Deleting face from: %lu\n", face->num_glyphs); FT_Done_Face(face); } - - for (uint i = 0; i < fallbacks.size(); i ++) { - fallbacks[i]->~FT2Font(); - } } void FT2Font::clear() @@ -390,6 +389,8 @@ void FT2Font::clear() } glyphs.clear(); + glyph_to_font.clear(); + char_to_font.clear(); for (uint i = 0; i < fallbacks.size(); i ++) { fallbacks[i]->clear(); @@ -435,8 +436,19 @@ void FT2Font::select_charmap(unsigned long i) } } -int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode) +int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback = false) { + if (fallback && glyph_to_font.find(left) != glyph_to_font.end() && + glyph_to_font.find(right) != glyph_to_font.end()) { + FT2Font *left_ft_object = glyph_to_font[left]; + FT2Font *right_ft_object = glyph_to_font[right]; + if (left_ft_object != right_ft_object) { + printf("Prev FT2Font != Curr FT2Font!\n"); + } + // if left_ft_object is not the same the right_ft_object, + // do the exact same thing which set_text does. + return right_ft_object->get_kerning(left, right, mode, false); + } if (!FT_HAS_KERNING(face)) { return 0; } @@ -499,11 +511,12 @@ void FT2Font::set_text( FT_UInt final_glyph_index; FT_Error charcode_error, glyph_error; FT2Font *ft_object_with_glyph = this; - load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, codepoints[n], - flags, charcode_error, glyph_error); - if (charcode_error || glyph_error) { + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, + char_to_font, glyph_to_font, codepoints[n], flags, + charcode_error, glyph_error, false); + if (!was_found) { ft_glyph_warn((FT_ULong)codepoints[n]); - return; + continue; } glyph_index = final_glyph_index; @@ -544,50 +557,175 @@ void FT2Font::set_text( } } -void FT2Font::load_char(long charcode, FT_Int32 flags) +void FT2Font::fill_glyphs( + size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, bool warn = false) { - FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode); - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load charcode", error); + FT_Matrix matrix; /* transformation matrix */ + + angle = angle / 360.0 * 2 * M_PI; + + // this computes width and height in subpixels so we have to divide by 64 + matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L); + matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L); + matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L); + matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L); + + FT_Bool use_kerning = FT_HAS_KERNING(face); + FT_UInt previous = 0; + + clear(); + + bbox.xMin = bbox.yMin = 32000; + bbox.xMax = bbox.yMax = -32000; + + for (unsigned int n = 0; n < N; n++) { + FT_UInt glyph_index; + FT_BBox glyph_bbox; + FT_Pos last_advance; + + FT_UInt final_glyph_index; + FT_Error charcode_error, glyph_error; + FT2Font *ft_object_with_glyph = this; + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, + char_to_font, glyph_to_font, codepoints[n], flags, + charcode_error, glyph_error, false); + if (!was_found) { + if (warn) ft_glyph_warn((FT_ULong)codepoints[n]); + continue; + } + + glyph_index = final_glyph_index; + + // retrieve kerning distance and move pen position + if (use_kerning && previous && glyph_index) { + FT_Vector delta; + ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT, delta); + pen.x += delta.x / (hinting_factor << kerning_factor); + } + + // extract glyph image and store it in our table + + FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; + + last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; + FT_Glyph_Transform(thisGlyph, 0, &pen); + FT_Glyph_Transform(thisGlyph, &matrix, 0); + + FT_Glyph_Get_CBox(thisGlyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph_bbox); + + bbox.xMin = std::min(bbox.xMin, glyph_bbox.xMin); + bbox.xMax = std::max(bbox.xMax, glyph_bbox.xMax); + bbox.yMin = std::min(bbox.yMin, glyph_bbox.yMin); + bbox.yMax = std::max(bbox.yMax, glyph_bbox.yMax); + + pen.x += last_advance; + + previous = glyph_index; } - FT_Glyph thisGlyph; - if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { - throw_ft_error("Could not get glyph", error); + + FT_Vector_Transform(&pen, &matrix); + advance = pen.x; + + if (bbox.xMin > bbox.xMax) { + bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; } - glyphs.push_back(thisGlyph); } -void FT2Font::load_char_with_fallback(FT2Font* &ft_object_with_glyph, +void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback = false) +{ + // if this is parent FT2Font, cache will be filled in 2 ways: + // 1. set_text was previously called + // 2. set_text was not called and fallback was enabled + if (fallback && char_to_font.find(charcode) != char_to_font.end()) { + ft_object = char_to_font[charcode]; + // since it will be assigned to this object anyway + FT2Font *throwaway = NULL; + ft_object->load_char(charcode, flags, throwaway, false); + } else if (fallback) { + FT_UInt final_glyph_index; + FT_Error charcode_error, glyph_error; + FT2Font *ft_object_with_glyph = this; + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font, + glyph_to_font, charcode, flags, charcode_error, glyph_error, true); + if (!was_found) { + ft_glyph_warn(charcode); + if (charcode_error) throw_ft_error("Could not load charcode", charcode_error); + else if (glyph_error) throw_ft_error("Could not load charcode", glyph_error); + } + ft_object = ft_object_with_glyph; + } else { + ft_object = this; + FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode); + + if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { + throw_ft_error("Could not load charcode", error); + } + FT_Glyph thisGlyph; + if (FT_Error error = FT_Get_Glyph(face->glyph, &thisGlyph)) { + throw_ft_error("Could not get glyph", error); + } + glyphs.push_back(thisGlyph); + } +} + +bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, FT_UInt &final_glyph_index, std::vector &parent_glyphs, + std::unordered_map &parent_char_to_font, + std::unordered_map &parent_glyph_to_font, long charcode, FT_Int32 flags, FT_Error &charcode_error, - FT_Error &glyph_error) + FT_Error &glyph_error, + bool override = false) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - if (glyph_index) { + if (glyph_index || override) { charcode_error = FT_Load_Glyph(face, glyph_index, flags); FT_Glyph thisGlyph; glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); + + if (charcode_error || glyph_error) { + return false; + } final_glyph_index = glyph_index; + + // cache the result for future + // need to store this for anytime a character is loaded from a parent + // FT2Font object or to generate a mapping of individual characters to fonts + ft_object_with_glyph = this; + parent_glyph_to_font[final_glyph_index] = this; + parent_char_to_font[charcode] = this; parent_glyphs.push_back(thisGlyph); + return true; } else { for (uint i = 0; i < fallbacks.size(); ++i) { - uint current_size = parent_glyphs.size(); - fallbacks[i]->load_char_with_fallback(ft_object_with_glyph, final_glyph_index, - parent_glyphs, charcode, flags, charcode_error, - glyph_error); - // jump out if glyph size increased - if (parent_glyphs.size() == current_size + 1) { - ft_object_with_glyph = fallbacks[i]; - return; - } + bool was_found = fallbacks[i]->load_char_with_fallback( + ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font, + parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override); + if (was_found) + return true; } + return false; + } +} + +void FT2Font::load_glyph(FT_UInt glyph_index, + FT_Int32 flags, + FT2Font *&ft_object, + bool fallback = false) +{ + // cache is only for parent FT2Font + if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { + ft_object = glyph_to_font[glyph_index]; + } else { + ft_object = this; } + + ft_object->load_glyph(glyph_index, flags); } void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) @@ -602,6 +740,27 @@ void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) glyphs.push_back(thisGlyph); } +FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false) +{ + FT2Font *ft_object = NULL; + if (fallback && char_to_font.find(charcode) != char_to_font.end()) { + // fallback denotes whether we want to search fallback list. + // should call set_text/load_char_with_fallback to parent FT2Font before + // wanting to use fallback list here. (since that populates the cache) + ft_object = char_to_font[charcode]; + } else { + // set as self + ft_object = this; + } + + return ft_get_char_index_or_warn(ft_object->get_face(), charcode); +} + +void FT2Font::get_cbox(FT_BBox &bbox) +{ + FT_Glyph_Get_CBox(glyphs.back(), ft_glyph_bbox_subpixels, &bbox); +} + void FT2Font::get_width_height(long *width, long *height) { *width = advance; @@ -692,8 +851,14 @@ void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y); } -void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer) +void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback = false) { + if (fallback && glyph_to_font.find(glyph_number) != glyph_to_font.end()) { + // cache is only for parent FT2Font + FT2Font *ft_object = glyph_to_font[glyph_number]; + ft_object->get_glyph_name(glyph_number, buffer, false); + return; + } if (!FT_HAS_GLYPH_NAMES(face)) { /* Note that this generated name must match the name that is generated by ttconv in ttfont_CharStrings_getname. */ diff --git a/src/ft2font.h b/src/ft2font.h index a656f08c5585..20787ec61e7f 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -1,10 +1,13 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ /* A python interface to FreeType */ +#pragma once +#include "forward.h" #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H #include #include +#include extern "C" { #include @@ -69,7 +72,7 @@ class FT2Font { public: - FT2Font(FT_Open_Args &open_args, long hinting_factor, std::vector &fallback_list); + FT2Font(FT_Open_Args &open_args, long hinting_factor, std::vector &fallback_list, PyFT2Font *py_pointer); virtual ~FT2Font(); void clear(); void set_size(double ptsize, double dpi); @@ -78,18 +81,25 @@ class FT2Font void select_charmap(unsigned long i); void set_text( size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector &xys); - int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode); + void fill_glyphs( + size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, bool warn); + int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback); int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta); void set_kerning_factor(int factor); - void load_char(long charcode, FT_Int32 flags); - void load_char_with_fallback(FT2Font* &ft_object_with_glyph, + void load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback); + bool load_char_with_fallback(FT2Font *&ft_object_with_glyph, FT_UInt &final_glyph_index, std::vector &parent_glyphs, + std::unordered_map &parent_char_to_font, + std::unordered_map &parent_glyph_to_font, long charcode, FT_Int32 flags, FT_Error &charcode_error, - FT_Error &glyph_error); + FT_Error &glyph_error, + bool override); + void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback); void load_glyph(FT_UInt glyph_index, FT_Int32 flags); + void load_parent_glyph(std::vector &parent_glyphs, long charcode, FT_Int32 flags); void get_width_height(long *width, long *height); void get_bitmap_offset(long *x, long *y); long get_descent(); @@ -98,14 +108,32 @@ class FT2Font void get_xys(bool antialiased, std::vector &xys); void draw_glyphs_to_bitmap(bool antialiased); void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased); - void get_glyph_name(unsigned int glyph_number, char *buffer); + void get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback); long get_name_index(char *name); + FT_UInt get_char_index(FT_ULong charcode, bool fallback); + void get_cbox(FT_BBox &bbox); PyObject* get_path(); FT_Face &get_face() { return face; } + + PyFT2Font *get_pyfont() + { + return py_font; + } + + std::unordered_map &get_glyph_to_font() + { + return glyph_to_font; + } + + std::unordered_map &get_char_to_font() + { + return char_to_font; + } + FT2Image &get_image() { return image; @@ -133,6 +161,9 @@ class FT2Font FT_Vector pen; /* untransformed origin */ std::vector glyphs; std::vector fallbacks; + std::unordered_map glyph_to_font; + std::unordered_map char_to_font; + PyFT2Font *py_font; FT_BBox bbox; FT_Pos advance; long hinting_factor; diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 118b39bd079d..b08e937e3aa8 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1,3 +1,4 @@ +#include "forward.h" #include "mplutils.h" #include "ft2font.h" #include "py_converters.h" @@ -178,25 +179,25 @@ typedef struct static PyTypeObject PyGlyphType; -static PyObject * -PyGlyph_new(const FT_Face &face, const FT_Glyph &glyph, size_t ind, long hinting_factor) +static PyObject *PyGlyph_new(FT2Font *&parent_ft_object, FT2Font *&ft_object) { PyGlyph *self; self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0); - - self->glyphInd = ind; - - FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox); - - self->width = face->glyph->metrics.width / hinting_factor; - self->height = face->glyph->metrics.height; - self->horiBearingX = face->glyph->metrics.horiBearingX / hinting_factor; - self->horiBearingY = face->glyph->metrics.horiBearingY; - self->horiAdvance = face->glyph->metrics.horiAdvance; - self->linearHoriAdvance = face->glyph->linearHoriAdvance / hinting_factor; - self->vertBearingX = face->glyph->metrics.vertBearingX; - self->vertBearingY = face->glyph->metrics.vertBearingY; - self->vertAdvance = face->glyph->metrics.vertAdvance; + const long hinting_factor = ft_object->get_hinting_factor(); + + self->glyphInd = ft_object->get_last_glyph_index(); + + ft_object->get_cbox(self->bbox); + + self->width = ft_object->get_face()->glyph->metrics.width / hinting_factor; + self->height = ft_object->get_face()->glyph->metrics.height; + self->horiBearingX = ft_object->get_face()->glyph->metrics.horiBearingX / hinting_factor; + self->horiBearingY = ft_object->get_face()->glyph->metrics.horiBearingY; + self->horiAdvance = ft_object->get_face()->glyph->metrics.horiAdvance; + self->linearHoriAdvance = ft_object->get_face()->glyph->linearHoriAdvance / hinting_factor; + self->vertBearingX = ft_object->get_face()->glyph->metrics.vertBearingX; + self->vertBearingY = ft_object->get_face()->glyph->metrics.vertBearingY; + self->vertAdvance = ft_object->get_face()->glyph->metrics.vertAdvance; return (PyObject *)self; } @@ -254,7 +255,7 @@ static PyTypeObject *PyGlyph_init_type(PyObject *m, PyTypeObject *type) * FT2Font * */ -typedef struct +struct PyFT2Font { PyObject_HEAD FT2Font *x; @@ -265,7 +266,7 @@ typedef struct Py_ssize_t strides[2]; Py_ssize_t suboffsets[2]; std::vector fallbacks; -} PyFT2Font; +}; static unsigned long read_from_file_callback(FT_Stream stream, unsigned long offset, @@ -420,7 +421,7 @@ static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) Py_CLEAR(data); CALL_CPP_FULL( - "FT2Font", (self->x = new FT2Font(open_args, hinting_factor, self->fallbacks)), + "FT2Font", (self->x = new FT2Font(open_args, hinting_factor, self->fallbacks, self)), Py_CLEAR(self->py_file), -1); CALL_CPP_INIT("FT2Font->set_kerning_factor", (self->x->set_kerning_factor(kerning_factor))); @@ -520,12 +521,13 @@ static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args, PyObject { FT_UInt left, right, mode; int result; + int fallback = 1; if (!PyArg_ParseTuple(args, "III:get_kerning", &left, &right, &mode)) { return NULL; } - CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode))); + CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode, (bool)fallback))); return PyLong_FromLong(result); } @@ -602,6 +604,78 @@ static PyObject *PyFT2Font_set_text(PyFT2Font *self, PyObject *args, PyObject *k return convert_xys_to_array(xys); } +const char *PyFT2Font_fill_glyphs__doc__ = + "fill_glyphs(string, angle, flags=32)\n" + "--\n\n" + "Fill (and cache) a text *string* and *angle*.\n" + "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" + "the default value is LOAD_FORCE_AUTOHINT.\n" + "A mapping of character to FT2Font object is returned.\n"; + +static PyObject *PyFT2Font_fill_glyphs(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *textobj; + double angle = 0.0; + bool warn = false; + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; + const char *names[] = { "string", "angle", "flags", NULL }; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|di:fill_glyphs", (char **)names, &textobj, &angle, &flags)) { + return NULL; + } + + std::vector codepoints; + size_t size; + + if (PyUnicode_Check(textobj)) { + size = PyUnicode_GET_LENGTH(textobj); + codepoints.resize(size); +#if defined(PYPY_VERSION) && (PYPY_VERSION_NUM < 0x07040000) + // PyUnicode_ReadChar is available from PyPy 7.3.2, but wheels do not + // specify the micro-release version, so put the version bound at 7.4 + // to prevent generating wheels unusable on PyPy 7.3.{0,1}. + Py_UNICODE *unistr = PyUnicode_AsUnicode(textobj); + for (size_t i = 0; i < size; ++i) { + codepoints[i] = unistr[i]; + } +#else + for (size_t i = 0; i < size; ++i) { + codepoints[i] = PyUnicode_ReadChar(textobj, i); + } +#endif + } else { + PyErr_SetString(PyExc_TypeError, "String must be str"); + return NULL; + } + + uint32_t* codepoints_array = NULL; + if (size > 0) { + codepoints_array = &codepoints[0]; + } + CALL_CPP("fill_glyphs", self->x->fill_glyphs(size, codepoints_array, angle, flags, warn)); + + PyObject *char_to_font; + if (!(char_to_font = PyDict_New())) { + return NULL; + } + std::unordered_map from_ft = self->x->get_char_to_font(); + + for (std::pair &itr : from_ft) { + PyObject *key = NULL, *val = NULL; + bool error = (!(key = PyLong_FromLong(itr.first)) + || !(val = reinterpret_cast(itr.second->get_pyfont())) + || (PyDict_SetItem(char_to_font, key, val) == -1)); + Py_XDECREF(key); + Py_XDECREF(val); + if (error) { + Py_DECREF(char_to_font); + return NULL; + } + } + return char_to_font; +} + const char *PyFT2Font_get_num_glyphs__doc__ = "get_num_glyphs()\n" "--\n\n" @@ -612,8 +686,66 @@ static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args, PyObj return PyLong_FromLong(self->x->get_num_glyphs()); } +const char *PyFT2Font_get_char_to_font__doc__ = + "get_char_to_font()\n" + "--\n\n" + "Return a cache of characters mapped to FT2Font objects.\n"; + +static PyObject *PyFT2Font_get_char_to_font(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *char_to_font; + if (!(char_to_font = PyDict_New())) { + return NULL; + } + std::unordered_map from_ft = self->x->get_char_to_font(); + + for (std::pair &itr : from_ft) { + PyObject *key = NULL, *val = NULL; + bool error = (!(key = PyLong_FromLong(itr.first)) + || !(val = reinterpret_cast(itr.second->get_pyfont())) + || (PyDict_SetItem(char_to_font, key, val) == -1)); + Py_XDECREF(key); + Py_XDECREF(val); + if (error) { + Py_DECREF(char_to_font); + return NULL; + } + } + return char_to_font; +} + +const char *PyFT2Font_get_glyph_to_font__doc__ = + "get_glyph_to_font()\n" + "--\n\n" + "Return a cache of glyph indexes mapped to FT2Font objects.\n"; + +static PyObject *PyFT2Font_get_glyph_to_font(PyFT2Font *self, PyObject *args, PyObject *kwds) +{ + PyObject *glyph_to_font; + if (!(glyph_to_font = PyDict_New())) { + return NULL; + } + std::unordered_map from_ft = self->x->get_glyph_to_font(); + + for (std::pair &itr : from_ft) { + PyObject *key = NULL, *val = NULL; + bool error = (!(key = PyLong_FromLong(itr.first)) + || !(val = reinterpret_cast(itr.second->get_pyfont())) + || (PyDict_SetItem(glyph_to_font, key, val) == -1)); + // Py_XDECREF(key); + // Py_XDECREF(val); + Py_INCREF(val); + Py_INCREF(key); + if (error) { + Py_DECREF(glyph_to_font); + return NULL; + } + } + return glyph_to_font; +} + const char *PyFT2Font_load_char__doc__ = - "load_char(charcode, flags=32)\n" + "load_char(charcode, fallback=False, flags=32)\n" "--\n\n" "Load character with *charcode* in current fontfile and set glyph.\n" "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" @@ -632,23 +764,22 @@ const char *PyFT2Font_load_char__doc__ = static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject *kwds) { long charcode; + int fallback = 1; FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; const char *names[] = { "charcode", "flags", NULL }; /* This makes a technically incorrect assumption that FT_Int32 is int. In theory it can also be long, if the size of int is less than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "l|i:load_char", (char **)names, &charcode, &flags)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "l|i:load_char", (char **)names, &charcode, + &flags)) { return NULL; } - CALL_CPP("load_char", (self->x->load_char(charcode, flags))); + FT2Font *ft_object = NULL; + CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); - return PyGlyph_new(self->x->get_face(), - self->x->get_last_glyph(), - self->x->get_last_glyph_index(), - self->x->get_hinting_factor()); + return PyGlyph_new(self->x, ft_object); } const char *PyFT2Font_load_glyph__doc__ = @@ -672,22 +803,21 @@ static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject { FT_UInt glyph_index; FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; + int fallback = 1; const char *names[] = { "glyph_index", "flags", NULL }; /* This makes a technically incorrect assumption that FT_Int32 is int. In theory it can also be long, if the size of int is less than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "I|i:load_glyph", (char **)names, &glyph_index, &flags)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "I|i:load_glyph", (char **)names, &glyph_index, + &flags)) { return NULL; } - CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags))); + FT2Font *ft_object = NULL; + CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags, ft_object, (bool)fallback))); - return PyGlyph_new(self->x->get_face(), - self->x->get_last_glyph(), - self->x->get_last_glyph_index(), - self->x->get_hinting_factor()); + return PyGlyph_new(self->x, ft_object); } const char *PyFT2Font_get_width_height__doc__ = @@ -833,10 +963,13 @@ static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args, PyObj { unsigned int glyph_number; char buffer[128]; + int fallback = 1; + + // parse kwds too if (!PyArg_ParseTuple(args, "I:get_glyph_name", &glyph_number)) { return NULL; } - CALL_CPP("get_glyph_name", (self->x->get_glyph_name(glyph_number, buffer))); + CALL_CPP("get_glyph_name", (self->x->get_glyph_name(glyph_number, buffer, (bool)fallback))); return PyUnicode_FromString(buffer); } @@ -872,7 +1005,7 @@ static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args, PyObject const char *PyFT2Font_get_char_index__doc__ = - "get_char_index(codepoint)\n" + "get_char_index(codepoint, fallback=True)\n" "--\n\n" "Return the glyph index corresponding to a character *codepoint*.\n"; @@ -880,12 +1013,13 @@ static PyObject *PyFT2Font_get_char_index(PyFT2Font *self, PyObject *args, PyObj { FT_UInt index; FT_ULong ccode; + int fallback = 1; if (!PyArg_ParseTuple(args, "k:get_char_index", &ccode)) { return NULL; } - index = FT_Get_Char_Index(self->x->get_face(), ccode); + CALL_CPP("get_char_index", index = self->x->get_char_index(ccode, (bool)fallback)); return PyLong_FromLong(index); } @@ -1502,7 +1636,10 @@ static PyTypeObject *PyFT2Font_init_type(PyObject *m, PyTypeObject *type) {"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__}, {"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__}, {"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__}, + {"fill_glyphs", (PyCFunction)PyFT2Font_fill_glyphs, METH_VARARGS|METH_KEYWORDS, PyFT2Font_fill_glyphs__doc__}, {"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__}, + {"get_char_to_font", (PyCFunction)PyFT2Font_get_char_to_font, METH_NOARGS, PyFT2Font_get_char_to_font__doc__}, + {"get_glyph_to_font", (PyCFunction)PyFT2Font_get_glyph_to_font, METH_NOARGS, PyFT2Font_get_glyph_to_font__doc__}, {"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__}, {"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__}, {"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__}, From 991c79622895ef458169fb87e4d1506bfc226cec Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:22:15 +0530 Subject: [PATCH 08/30] Remove stale prints --- src/ft2font.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index ee2650475542..d97dc9cf5d65 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -374,7 +374,6 @@ FT2Font::~FT2Font() } if (face) { - printf("Deleting face from: %lu\n", face->num_glyphs); FT_Done_Face(face); } } From fa385eb33252481c8ae97f3b5bc95011f1e66139 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:43:11 +0530 Subject: [PATCH 09/30] Do not warn for get_char_index --- src/ft2font.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index d97dc9cf5d65..13e45b3ab8ee 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -185,13 +185,14 @@ static FT_UInt ft_glyph_warn(FT_ULong charcode) return 0; } -static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) +static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); if (glyph_index) { return glyph_index; } - return ft_glyph_warn(charcode); + if (warn) return ft_glyph_warn(charcode); + else return 0; } // ft_outline_decomposer should be passed to FT_Outline_Decompose. On the @@ -752,7 +753,8 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false) ft_object = this; } - return ft_get_char_index_or_warn(ft_object->get_face(), charcode); + // historically, get_char_index never raises a warning + return ft_get_char_index_or_warn(ft_object->get_face(), charcode, false); } void FT2Font::get_cbox(FT_BBox &bbox) From 471ae8cb94a0a16c45c533fe950f176256a73b54 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:55:14 +0530 Subject: [PATCH 10/30] Left != Right kerning comment --- src/ft2font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 13e45b3ab8ee..fdabb0bd2034 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -443,7 +443,7 @@ int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallbac FT2Font *left_ft_object = glyph_to_font[left]; FT2Font *right_ft_object = glyph_to_font[right]; if (left_ft_object != right_ft_object) { - printf("Prev FT2Font != Curr FT2Font!\n"); + // could potentially do something different? } // if left_ft_object is not the same the right_ft_object, // do the exact same thing which set_text does. From 86878c86b504d2c333be40f45c18f91bd95f4912 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 13:42:39 +0530 Subject: [PATCH 11/30] Windows compiler fix --- src/ft2font.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index fdabb0bd2034..654b17824f8a 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -392,7 +392,7 @@ void FT2Font::clear() glyph_to_font.clear(); char_to_font.clear(); - for (uint i = 0; i < fallbacks.size(); i ++) { + for (unsigned int i = 0; i < fallbacks.size(); i++) { fallbacks[i]->clear(); } } @@ -407,7 +407,7 @@ void FT2Font::set_size(double ptsize, double dpi) FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); - for (uint i = 0; i < fallbacks.size(); i++) { + for (unsigned int i = 0; i < fallbacks.size(); i++) { fallbacks[i]->set_size(ptsize, dpi); } } @@ -477,7 +477,7 @@ int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &d void FT2Font::set_kerning_factor(int factor) { kerning_factor = factor; - for (uint i = 0; i < fallbacks.size(); i ++){ + for (unsigned int i = 0; i < fallbacks.size(); i ++){ fallbacks[i]->set_kerning_factor(factor); } } @@ -702,7 +702,7 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, } else { - for (uint i = 0; i < fallbacks.size(); ++i) { + for (unsigned int i = 0; i < fallbacks.size(); ++i) { bool was_found = fallbacks[i]->load_char_with_fallback( ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font, parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override); From 5ccb7de7ca3278c6216c57ba95f4da0d8784fbec Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Fri, 13 Aug 2021 16:06:11 +0530 Subject: [PATCH 12/30] Add fallback test for Agg backend --- .../baseline_images/test_agg/font_fallback.png | Bin 0 -> 19954 bytes lib/matplotlib/tests/test_agg.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_agg/font_fallback.png diff --git a/lib/matplotlib/tests/baseline_images/test_agg/font_fallback.png b/lib/matplotlib/tests/baseline_images/test_agg/font_fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..546beec003eaf77b7767faa5a00472435a65916c GIT binary patch literal 19954 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 f|4V;cY-!U~K9_ Date: Wed, 28 Jul 2021 20:05:31 +0530 Subject: [PATCH 13/30] Debug fontNames --- lib/matplotlib/backends/backend_pdf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 89afe92ce913..3bc530ebcb04 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -907,6 +907,7 @@ def dviFontName(self, dvifont): return pdfname def writeFonts(self): + print(self.fontNames) fonts = {} for dviname, info in sorted(self.dviFontInfo.items()): Fx = info.pdfname From d87ada85783b108dbd4b49da49dd32d2d92f856a Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Thu, 29 Jul 2021 21:56:18 +0530 Subject: [PATCH 14/30] Segfaults on exit --- lib/matplotlib/backends/backend_agg.py | 1 + lib/matplotlib/backends/backend_pdf.py | 29 ++++++++++++++++---------- src/ft2font.cpp | 5 +++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b5a2f85affc1..ddc697f61180 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -185,6 +185,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): return None # We pass '0' for angle here, since it will be rotated (in raster # space) in the following call to draw_text_image). + print("BEFORE") font.set_text(s, 0, flags=flags) font.draw_glyphs_to_bitmap( antialiased=mpl.rcParams['text.antialiased']) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 3bc530ebcb04..b233350e479a 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -33,7 +33,7 @@ GraphicsContextBase, RendererBase) from matplotlib.backends.backend_mixed import MixedModeRenderer from matplotlib.figure import Figure -from matplotlib.font_manager import findfont, get_font +from matplotlib.font_manager import findfont, find_fontsprop, get_font from matplotlib.afm import AFM import matplotlib.type1font as type1font import matplotlib.dviread as dviread @@ -861,20 +861,24 @@ def fontName(self, fontprop): """ if isinstance(fontprop, str): - filename = fontprop + filename = [fontprop] elif mpl.rcParams['pdf.use14corefonts']: - filename = findfont( - fontprop, fontext='afm', directory=RendererPdf._afm_font_dir) + filename = find_fontsprop( + fontprop, fontext='afm', directory=RendererPdf._afm_font_dir + ).values() else: - filename = findfont(fontprop) + filename = find_fontsprop(fontprop).values() - Fx = self.fontNames.get(filename) - if Fx is None: - Fx = next(self._internal_font_seq) - self.fontNames[filename] = Fx - _log.debug('Assigning font %s = %r', Fx, filename) + Fxs = [] + for fname in filename: + Fx = self.fontNames.get(fname) + if Fx is None: + Fx = next(self._internal_font_seq) + self.fontNames[fname] = Fx + _log.debug('Assigning font %s = %r', Fx, fname) + Fxs.append(Fx) - return Fx + return Fxs[0] def dviFontName(self, dvifont): """ @@ -1066,6 +1070,9 @@ def createType1Descriptor(self, t1font, fontfile): def _get_xobject_symbol_name(self, filename, symbol_name): Fx = self.fontName(filename) + # TODO: XObject symbol name should be multiple names? + # list(map(lambda x: x.name.decode(), Fxs)) + # Fx = Fxs[0] return "-".join([ Fx.name.decode(), os.path.splitext(os.path.basename(filename))[0], diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 654b17824f8a..8b8f9bc76c73 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -555,6 +555,11 @@ void FT2Font::set_text( if (bbox.xMin > bbox.xMax) { bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; } + printf("\nMap: \n"); + // print out num_glyphs for the final FT2Font so its easy to track + for (std::pair &x: glyph_to_font) { + printf("%u: %lu \n", x.first, x.second->get_face()->num_glyphs); + } } void FT2Font::fill_glyphs( From adebae487e02f37544b42d9f7fc292104be4ce73 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 31 Jul 2021 23:58:05 +0530 Subject: [PATCH 15/30] More work on PDF backend --- lib/matplotlib/_text_helpers.py | 4 +++- lib/matplotlib/backends/_backend_pdf_ps.py | 2 +- lib/matplotlib/backends/backend_pdf.py | 21 ++++++++++++++++++--- src/ft2font.cpp | 11 +++++++++++ src/ft2font_wrapper.cpp | 7 ++++++- 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 75d84997be9f..1647eceb4fe9 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -57,12 +57,14 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): """ x = 0 prev_glyph_idx = None + print("Inside _text_helpers.py") + # breakpoint() for char in string: glyph_idx = font.get_char_index(ord(char)) kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64 if prev_glyph_idx is not None else 0.) x += kern - glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING) + glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING, fallback=True) yield LayoutItem(char, glyph_idx, x, kern) x += glyph.linearHoriAdvance / 65536 prev_glyph_idx = glyph_idx diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 15a5578461cc..5eace533087d 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -143,7 +143,7 @@ def _get_font_afm(self, prop): return _cached_get_afm_from_fname(fname) def _get_font_ttf(self, prop): - fname = font_manager.findfont(prop) + fname = font_manager.find_fontsprop(prop) font = font_manager.get_font(fname) font.clear() font.set_size(prop.get_size_in_points(), 72) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index b233350e479a..a93f1b74051a 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -692,6 +692,7 @@ def __init__(self, filename, metadata=None): # differently encoded Type-1 fonts may share the same descriptor self.type1Descriptors = {} self._character_tracker = _backend_pdf_ps.CharacterTracker() + # self._char_to_font = None self.alphaStates = {} # maps alpha values to graphics state objects self._alpha_state_seq = (Name(f'A{i}') for i in itertools.count(1)) @@ -911,7 +912,8 @@ def dviFontName(self, dvifont): return pdfname def writeFonts(self): - print(self.fontNames) + # print("OHNO") + # breakpoint() fonts = {} for dviname, info in sorted(self.dviFontInfo.items()): Fx = info.pdfname @@ -927,7 +929,15 @@ def writeFonts(self): else: # a normal TrueType font _log.debug('Writing TrueType font.') + # characters = [] + print("lalalla") + # breakpoint() + # for key, val in self._char_to_font: + # if val.fname == filename: + # print("nice", filename) + # characters.append(key) chars = self._character_tracker.used.get(filename) + print("chars:", chars, " for font:", filename) if chars: fonts[Fx] = self.embedTTF(filename, chars) self.writeObject(self.fontObject, fonts) @@ -1149,7 +1159,7 @@ def embedTTFType3(font, characters, descriptor): def get_char_width(charcode): s = ord(cp1252.decoding_table[charcode]) width = font.load_char( - s, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance + s, fallback=False, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance return cvt(width) with warnings.catch_warnings(): @@ -2344,7 +2354,12 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): fonttype = 1 else: font = self._get_font_ttf(prop) - self.file._character_tracker.track(font, s) + font.set_text(s) + char_to_font = font.get_char_to_font() + for char, font in char_to_font.items(): + print(chr(char), "to:", font.fname) + self.file._character_tracker.track(font, chr(char)) + print("\nFONT_TO_CHAR:", self.file._character_tracker.used) fonttype = mpl.rcParams['pdf.fonttype'] if gc.get_url() is not None: diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 8b8f9bc76c73..d3a8691d4ba9 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -735,6 +735,17 @@ void FT2Font::load_glyph(FT_UInt glyph_index, void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { + // search cache first + if (fallback == 1 && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { + ft_object = glyph_to_font[glyph_index]; + return; + } + // can not do fallback without a charcode + // so ignore exact condition fallback == 1 + + // set as self + ft_object = this; + if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { throw_ft_error("Could not load glyph", error); } diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index b08e937e3aa8..5373693db550 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -766,7 +766,8 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * long charcode; int fallback = 1; FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; - const char *names[] = { "charcode", "flags", NULL }; + const char *names[] = { "charcode", "flags", "fallback", NULL }; + printf("Loading char!\n"); /* This makes a technically incorrect assumption that FT_Int32 is int. In theory it can also be long, if the size of int is less @@ -775,6 +776,10 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * &flags)) { return NULL; } + + FT2Font *ft_object = NULL; + CALL_CPP("load_char", (self->x->load_char(charcode, flags, fallback, ft_object))); + printf("Char %ld loaded to: %lu\n", charcode, ft_object->get_face()->num_glyphs); FT2Font *ft_object = NULL; CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); From 9983c6610cb39c84e8ef2121170fcebee64c51d1 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sun, 1 Aug 2021 17:23:09 +0530 Subject: [PATCH 16/30] Implement another approach --- lib/matplotlib/_text_helpers.py | 3 +- lib/matplotlib/backends/backend_agg.py | 1 - lib/matplotlib/backends/backend_pdf.py | 38 ++++++++++++++------------ src/ft2font.cpp | 20 ++++++++++---- src/ft2font_wrapper.cpp | 12 +++++--- 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 1647eceb4fe9..17553b93d4a8 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -60,11 +60,12 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): print("Inside _text_helpers.py") # breakpoint() for char in string: + print("\nchecking:", char, "\n") glyph_idx = font.get_char_index(ord(char)) kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64 if prev_glyph_idx is not None else 0.) x += kern - glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING, fallback=True) + glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING) yield LayoutItem(char, glyph_idx, x, kern) x += glyph.linearHoriAdvance / 65536 prev_glyph_idx = glyph_idx diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index ddc697f61180..b5a2f85affc1 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -185,7 +185,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): return None # We pass '0' for angle here, since it will be rotated (in raster # space) in the following call to draw_text_image). - print("BEFORE") font.set_text(s, 0, flags=flags) font.draw_glyphs_to_bitmap( antialiased=mpl.rcParams['text.antialiased']) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index a93f1b74051a..fb56647d33ce 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -879,6 +879,7 @@ def fontName(self, fontprop): _log.debug('Assigning font %s = %r', Fx, fname) Fxs.append(Fx) + # return only the first for Op.selectfont to work return Fxs[0] def dviFontName(self, dvifont): @@ -912,8 +913,7 @@ def dviFontName(self, dvifont): return pdfname def writeFonts(self): - # print("OHNO") - # breakpoint() + print("fonts: ", self.fontNames) fonts = {} for dviname, info in sorted(self.dviFontInfo.items()): Fx = info.pdfname @@ -929,13 +929,6 @@ def writeFonts(self): else: # a normal TrueType font _log.debug('Writing TrueType font.') - # characters = [] - print("lalalla") - # breakpoint() - # for key, val in self._char_to_font: - # if val.fname == filename: - # print("nice", filename) - # characters.append(key) chars = self._character_tracker.used.get(filename) print("chars:", chars, " for font:", filename) if chars: @@ -1079,14 +1072,14 @@ def createType1Descriptor(self, t1font, fontfile): return fontdescObject def _get_xobject_symbol_name(self, filename, symbol_name): + # since filename is a string Fx = self.fontName(filename) - # TODO: XObject symbol name should be multiple names? - # list(map(lambda x: x.name.decode(), Fxs)) - # Fx = Fxs[0] - return "-".join([ + x = "-".join([ Fx.name.decode(), os.path.splitext(os.path.basename(filename))[0], symbol_name]) + print("\n\nXOBJECT", x, "\n\n") + return x _identityToUnicodeCMap = b"""/CIDInit /ProcSet findresource begin 12 dict begin @@ -1113,6 +1106,7 @@ def embedTTF(self, filename, characters): """Embed the TTF font from the named file into the document.""" font = get_font(filename) + print("embedding:", font.fname) fonttype = mpl.rcParams['pdf.fonttype'] def cvt(length, upe=font.units_per_EM, nearest=True): @@ -1159,9 +1153,8 @@ def embedTTFType3(font, characters, descriptor): def get_char_width(charcode): s = ord(cp1252.decoding_table[charcode]) width = font.load_char( - s, fallback=False, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance + s, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance return cvt(width) - with warnings.catch_warnings(): # Ignore 'Required glyph missing from current font' warning # from ft2font: here we're just building the widths table, but @@ -2424,6 +2417,11 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): -math.sin(a), math.cos(a), x, y, Op.concat_matrix) # Emit all the 1-byte characters in a BT/ET group. + + x = self.file.fontName(prop) + print(x) + # breakpoint() + self.file.output(Op.begin_text, self.file.fontName(prop), fontsize, Op.selectfont) prev_start_x = 0 @@ -2439,12 +2437,18 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): prev_start_x = start_x self.file.output(Op.end_text) # Then emit all the multibyte characters, one at a time. + glyph_to_font = font.get_glyph_to_font() for start_x, glyph_idx in multibyte_glyphs: - self._draw_xobject_glyph(font, fontsize, glyph_idx, start_x, 0) + self._draw_xobject_glyph(glyph_to_font, fontsize, glyph_idx, start_x, 0) self.file.output(Op.grestore) + # print("fine here") - def _draw_xobject_glyph(self, font, fontsize, glyph_idx, x, y): + def _draw_xobject_glyph(self, glyph_to_font, fontsize, glyph_idx, x, y): """Draw a multibyte character from a Type 3 font as an XObject.""" + if glyph_idx not in glyph_to_font: + # ideally raise. + pass + font = glyph_to_font[glyph_idx] symbol_name = font.get_glyph_name(glyph_idx) name = self.file._get_xobject_symbol_name(font.fname, symbol_name) self.file.output( diff --git a/src/ft2font.cpp b/src/ft2font.cpp index d3a8691d4ba9..8d1554b570d2 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -375,6 +375,7 @@ FT2Font::~FT2Font() } if (face) { + printf("Deleting face from: %lu\n", face->num_glyphs); FT_Done_Face(face); } } @@ -555,11 +556,11 @@ void FT2Font::set_text( if (bbox.xMin > bbox.xMax) { bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; } - printf("\nMap: \n"); - // print out num_glyphs for the final FT2Font so its easy to track - for (std::pair &x: glyph_to_font) { - printf("%u: %lu \n", x.first, x.second->get_face()->num_glyphs); - } + // printf("\nMap: \n"); + // // print out num_glyphs for the final FT2Font so its easy to track + // for (std::pair &x: glyph_to_font) { + // printf("%u: %lu \n", x.first, x.second->get_face()->num_glyphs); + // } } void FT2Font::fill_glyphs( @@ -685,6 +686,7 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, bool override = false) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + // printf("fallback glyph id: %u\n", glyph_index); if (glyph_index || override) { charcode_error = FT_Load_Glyph(face, glyph_index, flags); @@ -736,7 +738,8 @@ void FT2Font::load_glyph(FT_UInt glyph_index, void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { // search cache first - if (fallback == 1 && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { + if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { + // printf("load_glyph: Already present in cache.\n"); ft_object = glyph_to_font[glyph_index]; return; } @@ -778,6 +781,11 @@ void FT2Font::get_cbox(FT_BBox &bbox) FT_Glyph_Get_CBox(glyphs.back(), ft_glyph_bbox_subpixels, &bbox); } +void FT2Font::get_cbox(FT_BBox &bbox) +{ + FT_Glyph_Get_CBox(glyphs.back(), ft_glyph_bbox_subpixels, &bbox); +} + void FT2Font::get_width_height(long *width, long *height) { *width = advance; diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 5373693db550..c045f77953ee 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -766,8 +766,7 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * long charcode; int fallback = 1; FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; - const char *names[] = { "charcode", "flags", "fallback", NULL }; - printf("Loading char!\n"); + const char *names[] = { "charcode", "flags", NULL }; /* This makes a technically incorrect assumption that FT_Int32 is int. In theory it can also be long, if the size of int is less @@ -776,9 +775,9 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * &flags)) { return NULL; } - + FT2Font *ft_object = NULL; - CALL_CPP("load_char", (self->x->load_char(charcode, flags, fallback, ft_object))); + CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); printf("Char %ld loaded to: %lu\n", charcode, ft_object->get_face()->num_glyphs); FT2Font *ft_object = NULL; @@ -819,6 +818,11 @@ static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject return NULL; } + // if (!PyArg_ParseTupleAndKeywords( + // args, kwds, "I|i$p:load_glyph", (char **)names, &glyph_index, &flags, &fallback)) { + // return NULL; + // } + FT2Font *ft_object = NULL; CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags, ft_object, (bool)fallback))); From 2ef863e72cc320d9e02505c1134a54651a6e4263 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Tue, 3 Aug 2021 19:23:34 +0530 Subject: [PATCH 17/30] Revisit the approach --- lib/matplotlib/_text_helpers.py | 7 ++- lib/matplotlib/backends/backend_pdf.py | 76 ++++++++++++-------------- src/ft2font_wrapper.cpp | 2 +- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 17553b93d4a8..5c44009ae5f7 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -9,7 +9,7 @@ LayoutItem = dataclasses.make_dataclass( - "LayoutItem", ["char", "glyph_idx", "x", "prev_kern"]) + "LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"]) def warn_on_missing_glyph(codepoint): @@ -60,12 +60,13 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): print("Inside _text_helpers.py") # breakpoint() for char in string: - print("\nchecking:", char, "\n") + # print("\nchecking:", char, "\n") glyph_idx = font.get_char_index(ord(char)) kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64 if prev_glyph_idx is not None else 0.) x += kern glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING) - yield LayoutItem(char, glyph_idx, x, kern) + ft_object = font.get_glyph_to_font().get(glyph_idx) + yield LayoutItem(ft_object, char, glyph_idx, x, kern) x += glyph.linearHoriAdvance / 65536 prev_glyph_idx = glyph_idx diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index fb56647d33ce..c3a5ff216a8b 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -862,25 +862,21 @@ def fontName(self, fontprop): """ if isinstance(fontprop, str): - filename = [fontprop] + filename = fontprop elif mpl.rcParams['pdf.use14corefonts']: - filename = find_fontsprop( + filename = findfont( fontprop, fontext='afm', directory=RendererPdf._afm_font_dir - ).values() + ) else: - filename = find_fontsprop(fontprop).values() + filename = findfont(fontprop) - Fxs = [] - for fname in filename: - Fx = self.fontNames.get(fname) - if Fx is None: - Fx = next(self._internal_font_seq) - self.fontNames[fname] = Fx - _log.debug('Assigning font %s = %r', Fx, fname) - Fxs.append(Fx) + Fx = self.fontNames.get(filename) + if Fx is None: + Fx = next(self._internal_font_seq) + self.fontNames[filename] = Fx + _log.debug('Assigning font %s = %r', Fx, filename) - # return only the first for Op.selectfont to work - return Fxs[0] + return Fx def dviFontName(self, dvifont): """ @@ -1072,7 +1068,6 @@ def createType1Descriptor(self, t1font, fontfile): return fontdescObject def _get_xobject_symbol_name(self, filename, symbol_name): - # since filename is a string Fx = self.fontName(filename) x = "-".join([ Fx.name.decode(), @@ -1338,7 +1333,7 @@ def embedTTFType42(font, characters, descriptor): # Add XObjects for unsupported chars glyph_ids = [] for ccode in characters: - if not _font_supports_char(fonttype, chr(ccode)): + if _font_supports_char(fonttype, chr(ccode)): gind = full_font.get_char_index(ccode) glyph_ids.append(gind) @@ -1928,6 +1923,8 @@ class RendererPdf(_backend_pdf_ps.RendererPDFPSBase): def __init__(self, file, image_dpi, height, width): super().__init__(width, height) self.file = file + self.char_to_font = {} + self.glyph_to_font = {} self.gc = self.new_gc() self.image_dpi = image_dpi @@ -2348,8 +2345,11 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): else: font = self._get_font_ttf(prop) font.set_text(s) - char_to_font = font.get_char_to_font() - for char, font in char_to_font.items(): + self.char_to_font = font.get_char_to_font() + self.glyph_to_font = font.get_glyph_to_font() + # populate self.fontNames with all fonts + _ = [self.file.fontName(ft_object.fname) for ft_object in self.glyph_to_font.values()] + for char, font in self.char_to_font.items(): print(chr(char), "to:", font.fname) self.file._character_tracker.track(font, chr(char)) print("\nFONT_TO_CHAR:", self.file._character_tracker.used) @@ -2392,22 +2392,23 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # the regular text show command (TJ) with appropriate kerning between # chunks, whereas multibyte characters use the XObject command (Do). else: - # List of (start_x, [prev_kern, char, char, ...]), w/o zero kerns. + # List of (ft_object, start_x, [prev_kern, char, char, ...]), w/o zero kerns. singlebyte_chunks = [] - # List of (start_x, glyph_index). + # List of (ft_object, start_x, glyph_index). multibyte_glyphs = [] prev_was_multibyte = True for item in _text_helpers.layout( s, font, kern_mode=KERNING_UNFITTED): + print(item.ft_object.fname, item) if _font_supports_char(fonttype, item.char): if prev_was_multibyte: - singlebyte_chunks.append((item.x, [])) + singlebyte_chunks.append((item.ft_object, item.x, [])) if item.prev_kern: - singlebyte_chunks[-1][1].append(item.prev_kern) - singlebyte_chunks[-1][1].append(item.char) + singlebyte_chunks[-1][2].append(item.prev_kern) + singlebyte_chunks[-1][2].append(item.char) prev_was_multibyte = False else: - multibyte_glyphs.append((item.x, item.glyph_idx)) + multibyte_glyphs.append((item.ft_object, item.x, item.glyph_idx)) prev_was_multibyte = True # Do the rotation and global translation as a single matrix # concatenation up front @@ -2418,14 +2419,12 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): x, y, Op.concat_matrix) # Emit all the 1-byte characters in a BT/ET group. - x = self.file.fontName(prop) - print(x) - # breakpoint() - - self.file.output(Op.begin_text, - self.file.fontName(prop), fontsize, Op.selectfont) + self.file.output(Op.begin_text) + # ft, fontsize, Op.selectfont prev_start_x = 0 - for start_x, kerns_or_chars in singlebyte_chunks: + for ft_object, start_x, kerns_or_chars in singlebyte_chunks: + ft_name = self.file.fontName(ft_object.fname) + self.file.output(ft_name, fontsize, Op.selectfont) self._setup_textpos(start_x, 0, 0, prev_start_x, 0, 0) self.file.output( # See pdf spec "Text space details" for the 1000/fontsize @@ -2437,20 +2436,15 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): prev_start_x = start_x self.file.output(Op.end_text) # Then emit all the multibyte characters, one at a time. - glyph_to_font = font.get_glyph_to_font() - for start_x, glyph_idx in multibyte_glyphs: - self._draw_xobject_glyph(glyph_to_font, fontsize, glyph_idx, start_x, 0) + for ft_object, start_x, glyph_idx in multibyte_glyphs: + self._draw_xobject_glyph(ft_object, fontsize, glyph_idx, start_x, 0) self.file.output(Op.grestore) # print("fine here") - def _draw_xobject_glyph(self, glyph_to_font, fontsize, glyph_idx, x, y): + def _draw_xobject_glyph(self, ft_object, fontsize, glyph_idx, x, y): """Draw a multibyte character from a Type 3 font as an XObject.""" - if glyph_idx not in glyph_to_font: - # ideally raise. - pass - font = glyph_to_font[glyph_idx] - symbol_name = font.get_glyph_name(glyph_idx) - name = self.file._get_xobject_symbol_name(font.fname, symbol_name) + symbol_name = ft_object.get_glyph_name(glyph_idx) + name = self.file._get_xobject_symbol_name(ft_object.fname, symbol_name) self.file.output( Op.gsave, 0.001 * fontsize, 0, 0, 0.001 * fontsize, x, y, Op.concat_matrix, diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index c045f77953ee..118a7c899cf4 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -778,7 +778,7 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * FT2Font *ft_object = NULL; CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); - printf("Char %ld loaded to: %lu\n", charcode, ft_object->get_face()->num_glyphs); + // printf("Char %ld loaded to: %lu\n", charcode, ft_object->get_face()->num_glyphs); FT2Font *ft_object = NULL; CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); From f12c295101419e0d91f4c2fd9959ac23e1996040 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Fri, 6 Aug 2021 09:17:19 +0530 Subject: [PATCH 18/30] Type3 PDF Backend works! --- lib/matplotlib/_text_helpers.py | 5 +--- lib/matplotlib/backends/backend_pdf.py | 36 +++++++++++++++----------- lib/matplotlib/font_manager.py | 1 + src/ft2font.cpp | 16 ++++++------ 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 5c44009ae5f7..ca95c8081c47 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -57,16 +57,13 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): """ x = 0 prev_glyph_idx = None - print("Inside _text_helpers.py") - # breakpoint() for char in string: - # print("\nchecking:", char, "\n") glyph_idx = font.get_char_index(ord(char)) kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64 if prev_glyph_idx is not None else 0.) x += kern glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING) - ft_object = font.get_glyph_to_font().get(glyph_idx) + ft_object = font.get_glyph_to_font().get(glyph_idx, font) yield LayoutItem(ft_object, char, glyph_idx, x, kern) x += glyph.linearHoriAdvance / 65536 prev_glyph_idx = glyph_idx diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index c3a5ff216a8b..78b0828de11f 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -862,21 +862,28 @@ def fontName(self, fontprop): """ if isinstance(fontprop, str): - filename = fontprop + filenames = [fontprop] elif mpl.rcParams['pdf.use14corefonts']: - filename = findfont( + filenames = find_fontsprop( fontprop, fontext='afm', directory=RendererPdf._afm_font_dir - ) + ).values() else: - filename = findfont(fontprop) - - Fx = self.fontNames.get(filename) - if Fx is None: - Fx = next(self._internal_font_seq) - self.fontNames[filename] = Fx - _log.debug('Assigning font %s = %r', Fx, filename) - - return Fx + filenames = find_fontsprop(fontprop).values() + first_Fx = None + for fname in filenames: + Fx = self.fontNames.get(fname) + if not first_Fx: + first_Fx = Fx + if Fx is None: + Fx = next(self._internal_font_seq) + self.fontNames[fname] = Fx + _log.debug('Assigning font %s = %r', Fx, fname) + if not first_Fx: + first_Fx = Fx + + # find_fontsprop's first value always adheres to + # findfont's value, so technically no behaviour change + return first_Fx def dviFontName(self, dvifont): """ @@ -1073,7 +1080,6 @@ def _get_xobject_symbol_name(self, filename, symbol_name): Fx.name.decode(), os.path.splitext(os.path.basename(filename))[0], symbol_name]) - print("\n\nXOBJECT", x, "\n\n") return x _identityToUnicodeCMap = b"""/CIDInit /ProcSet findresource begin @@ -2350,7 +2356,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # populate self.fontNames with all fonts _ = [self.file.fontName(ft_object.fname) for ft_object in self.glyph_to_font.values()] for char, font in self.char_to_font.items(): - print(chr(char), "to:", font.fname) + # print(chr(char), "to:", font.fname) self.file._character_tracker.track(font, chr(char)) print("\nFONT_TO_CHAR:", self.file._character_tracker.used) fonttype = mpl.rcParams['pdf.fonttype'] @@ -2399,7 +2405,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): prev_was_multibyte = True for item in _text_helpers.layout( s, font, kern_mode=KERNING_UNFITTED): - print(item.ft_object.fname, item) + print(f"char: {item.char}, x: {item.x}, fname: {item.ft_object.fname}") if _font_supports_char(fonttype, item.char): if prev_was_multibyte: singlebyte_chunks.append((item.ft_object, item.x, [])) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index aca91f25d7e4..6c88622deecc 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1355,6 +1355,7 @@ def find_fontsprop(self, prop, fontext='ttf', directory=None, See `findfont` for more details. """ + # print("finding font!") rc_params = tuple(tuple(rcParams[key]) for key in [ "font.serif", "font.sans-serif", "font.cursive", "font.fantasy", diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 8d1554b570d2..c74def377d85 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -557,7 +557,7 @@ void FT2Font::set_text( bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; } // printf("\nMap: \n"); - // // print out num_glyphs for the final FT2Font so its easy to track + // print out num_glyphs for the final FT2Font so its easy to track // for (std::pair &x: glyph_to_font) { // printf("%u: %lu \n", x.first, x.second->get_face()->num_glyphs); // } @@ -692,7 +692,6 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, charcode_error = FT_Load_Glyph(face, glyph_index, flags); FT_Glyph thisGlyph; glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); - if (charcode_error || glyph_error) { return false; } @@ -739,16 +738,17 @@ void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { // search cache first if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { - // printf("load_glyph: Already present in cache.\n"); + printf("load_glyph: Already present in cache.\n"); ft_object = glyph_to_font[glyph_index]; - return; + } else { + ft_object = this; } - // can not do fallback without a charcode - // so ignore exact condition fallback == 1 - // set as self - ft_object = this; + ft_object->load_glyph(glyph_index, flags); +} +void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) +{ if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { throw_ft_error("Could not load glyph", error); } From 1327b8239fea21962fd23378417f8d0a93505f56 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 05:08:18 +0530 Subject: [PATCH 19/30] Type42 PDF fallback works! --- lib/matplotlib/backends/backend_pdf.py | 6 +++--- src/ft2font.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 78b0828de11f..42af5a4ca04f 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2403,12 +2403,13 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # List of (ft_object, start_x, glyph_index). multibyte_glyphs = [] prev_was_multibyte = True + prev_font = font for item in _text_helpers.layout( s, font, kern_mode=KERNING_UNFITTED): - print(f"char: {item.char}, x: {item.x}, fname: {item.ft_object.fname}") if _font_supports_char(fonttype, item.char): - if prev_was_multibyte: + if prev_was_multibyte or item.ft_object != prev_font: singlebyte_chunks.append((item.ft_object, item.x, [])) + prev_font = item.ft_object if item.prev_kern: singlebyte_chunks[-1][2].append(item.prev_kern) singlebyte_chunks[-1][2].append(item.char) @@ -2445,7 +2446,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): for ft_object, start_x, glyph_idx in multibyte_glyphs: self._draw_xobject_glyph(ft_object, fontsize, glyph_idx, start_x, 0) self.file.output(Op.grestore) - # print("fine here") def _draw_xobject_glyph(self, ft_object, fontsize, glyph_idx, x, y): """Draw a multibyte character from a Type 3 font as an XObject.""" diff --git a/src/ft2font.cpp b/src/ft2font.cpp index c74def377d85..a0f84cf11700 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -738,7 +738,7 @@ void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { // search cache first if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { - printf("load_glyph: Already present in cache.\n"); + // printf("load_glyph: Already present in cache.\n"); ft_object = glyph_to_font[glyph_index]; } else { ft_object = this; From 799ffde03c97d95637faca28107585922bba9045 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:14:33 +0530 Subject: [PATCH 20/30] Create a fill_glyphs method --- src/ft2font.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index a0f84cf11700..29ec2b1b0033 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -556,11 +556,6 @@ void FT2Font::set_text( if (bbox.xMin > bbox.xMax) { bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0; } - // printf("\nMap: \n"); - // print out num_glyphs for the final FT2Font so its easy to track - // for (std::pair &x: glyph_to_font) { - // printf("%u: %lu \n", x.first, x.second->get_face()->num_glyphs); - // } } void FT2Font::fill_glyphs( @@ -686,7 +681,6 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, bool override = false) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - // printf("fallback glyph id: %u\n", glyph_index); if (glyph_index || override) { charcode_error = FT_Load_Glyph(face, glyph_index, flags); @@ -738,7 +732,6 @@ void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { // search cache first if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { - // printf("load_glyph: Already present in cache.\n"); ft_object = glyph_to_font[glyph_index]; } else { ft_object = this; From 7ce180513e5acde7687670c06294388a4cce5432 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:14:49 +0530 Subject: [PATCH 21/30] Use fill_glyphs instead of set_text --- lib/matplotlib/backends/backend_pdf.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 42af5a4ca04f..757eb7b4de00 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2350,15 +2350,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): fonttype = 1 else: font = self._get_font_ttf(prop) - font.set_text(s) - self.char_to_font = font.get_char_to_font() - self.glyph_to_font = font.get_glyph_to_font() - # populate self.fontNames with all fonts - _ = [self.file.fontName(ft_object.fname) for ft_object in self.glyph_to_font.values()] - for char, font in self.char_to_font.items(): - # print(chr(char), "to:", font.fname) + char_to_font = font.fill_glyphs(s) + for char, font in char_to_font.items(): self.file._character_tracker.track(font, chr(char)) - print("\nFONT_TO_CHAR:", self.file._character_tracker.used) fonttype = mpl.rcParams['pdf.fonttype'] if gc.get_url() is not None: From ac1c9c9ff553b55e79111cf32bd608b7a9ff77e9 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:17:15 +0530 Subject: [PATCH 22/30] Cleanup wrapper --- src/ft2font_wrapper.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 118a7c899cf4..d504eae7c041 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -778,7 +778,6 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * FT2Font *ft_object = NULL; CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); - // printf("Char %ld loaded to: %lu\n", charcode, ft_object->get_face()->num_glyphs); FT2Font *ft_object = NULL; CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); @@ -818,11 +817,6 @@ static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject return NULL; } - // if (!PyArg_ParseTupleAndKeywords( - // args, kwds, "I|i$p:load_glyph", (char **)names, &glyph_index, &flags, &fallback)) { - // return NULL; - // } - FT2Font *ft_object = NULL; CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags, ft_object, (bool)fallback))); From 8a30aafa4bc8a4e1bbbe23d2b04254d22d583781 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 06:54:30 +0530 Subject: [PATCH 23/30] Few cleanups --- lib/matplotlib/backends/backend_pdf.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 757eb7b4de00..5b6d638cb193 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -692,7 +692,6 @@ def __init__(self, filename, metadata=None): # differently encoded Type-1 fonts may share the same descriptor self.type1Descriptors = {} self._character_tracker = _backend_pdf_ps.CharacterTracker() - # self._char_to_font = None self.alphaStates = {} # maps alpha values to graphics state objects self._alpha_state_seq = (Name(f'A{i}') for i in itertools.count(1)) @@ -916,7 +915,6 @@ def dviFontName(self, dvifont): return pdfname def writeFonts(self): - print("fonts: ", self.fontNames) fonts = {} for dviname, info in sorted(self.dviFontInfo.items()): Fx = info.pdfname @@ -933,7 +931,6 @@ def writeFonts(self): # a normal TrueType font _log.debug('Writing TrueType font.') chars = self._character_tracker.used.get(filename) - print("chars:", chars, " for font:", filename) if chars: fonts[Fx] = self.embedTTF(filename, chars) self.writeObject(self.fontObject, fonts) @@ -1076,11 +1073,10 @@ def createType1Descriptor(self, t1font, fontfile): def _get_xobject_symbol_name(self, filename, symbol_name): Fx = self.fontName(filename) - x = "-".join([ + return "-".join([ Fx.name.decode(), os.path.splitext(os.path.basename(filename))[0], symbol_name]) - return x _identityToUnicodeCMap = b"""/CIDInit /ProcSet findresource begin 12 dict begin @@ -1107,7 +1103,6 @@ def embedTTF(self, filename, characters): """Embed the TTF font from the named file into the document.""" font = get_font(filename) - print("embedding:", font.fname) fonttype = mpl.rcParams['pdf.fonttype'] def cvt(length, upe=font.units_per_EM, nearest=True): @@ -1339,7 +1334,7 @@ def embedTTFType42(font, characters, descriptor): # Add XObjects for unsupported chars glyph_ids = [] for ccode in characters: - if _font_supports_char(fonttype, chr(ccode)): + if not _font_supports_char(fonttype, chr(ccode)): gind = full_font.get_char_index(ccode) glyph_ids.append(gind) @@ -1929,8 +1924,6 @@ class RendererPdf(_backend_pdf_ps.RendererPDFPSBase): def __init__(self, file, image_dpi, height, width): super().__init__(width, height) self.file = file - self.char_to_font = {} - self.glyph_to_font = {} self.gc = self.new_gc() self.image_dpi = image_dpi @@ -2421,7 +2414,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # Emit all the 1-byte characters in a BT/ET group. self.file.output(Op.begin_text) - # ft, fontsize, Op.selectfont prev_start_x = 0 for ft_object, start_x, kerns_or_chars in singlebyte_chunks: ft_name = self.file.fontName(ft_object.fname) From e67504743c27756b5c990b3fd9b2c738ac51a99a Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Sat, 7 Aug 2021 07:19:52 +0530 Subject: [PATCH 24/30] Rebase from Agg backend --- src/ft2font.cpp | 18 ------------------ src/ft2font_wrapper.cpp | 3 --- 2 files changed, 21 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 29ec2b1b0033..0da6a52df9bf 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -375,7 +375,6 @@ FT2Font::~FT2Font() } if (face) { - printf("Deleting face from: %lu\n", face->num_glyphs); FT_Done_Face(face); } } @@ -728,18 +727,6 @@ void FT2Font::load_glyph(FT_UInt glyph_index, ft_object->load_glyph(glyph_index, flags); } -void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) -{ - // search cache first - if (fallback && glyph_to_font.find(glyph_index) != glyph_to_font.end()) { - ft_object = glyph_to_font[glyph_index]; - } else { - ft_object = this; - } - - ft_object->load_glyph(glyph_index, flags); -} - void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { @@ -774,11 +761,6 @@ void FT2Font::get_cbox(FT_BBox &bbox) FT_Glyph_Get_CBox(glyphs.back(), ft_glyph_bbox_subpixels, &bbox); } -void FT2Font::get_cbox(FT_BBox &bbox) -{ - FT_Glyph_Get_CBox(glyphs.back(), ft_glyph_bbox_subpixels, &bbox); -} - void FT2Font::get_width_height(long *width, long *height) { *width = advance; diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index d504eae7c041..b08e937e3aa8 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -779,9 +779,6 @@ static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject * FT2Font *ft_object = NULL; CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); - FT2Font *ft_object = NULL; - CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, (bool)fallback))); - return PyGlyph_new(self->x, ft_object); } From 1b53070f7c5ba8d9edbb855435960e30ae8cbd5a Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Fri, 13 Aug 2021 17:39:21 +0530 Subject: [PATCH 25/30] Specify font number for TTC fonts --- lib/matplotlib/backends/_backend_pdf_ps.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 5eace533087d..03906ad3188c 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -2,6 +2,7 @@ Common functionality between the PDF and PS backends. """ +import os from io import BytesIO import functools @@ -37,7 +38,11 @@ def get_glyphs_subset(fontfile, characters): options = subset.Options(glyph_names=True, recommended_glyphs=True) # prevent subsetting FontForge Timestamp and other tables - options.drop_tables += ['FFTM', 'PfEd'] + options.drop_tables += ['FFTM', 'PfEd', 'BDF'] + + # if fontfile is a ttc, specify font number + if os.path.splitext(fontfile)[1] == ".ttc": + options.font_number = 0 with subset.load_font(fontfile, options) as font: subsetter = subset.Subsetter(options=options) From 0e0d92cff3f85db3d74b05ac385c322d13dc069f Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Fri, 13 Aug 2021 17:39:33 +0530 Subject: [PATCH 26/30] Flake8 fixes --- lib/matplotlib/backends/backend_pdf.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 5b6d638cb193..16972ada2784 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2385,7 +2385,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # the regular text show command (TJ) with appropriate kerning between # chunks, whereas multibyte characters use the XObject command (Do). else: - # List of (ft_object, start_x, [prev_kern, char, char, ...]), w/o zero kerns. + # List of (ft_object, start_x, [prev_kern, char, char, ...]), + # w/o zero kerns. singlebyte_chunks = [] # List of (ft_object, start_x, glyph_index). multibyte_glyphs = [] @@ -2402,7 +2403,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): singlebyte_chunks[-1][2].append(item.char) prev_was_multibyte = False else: - multibyte_glyphs.append((item.ft_object, item.x, item.glyph_idx)) + multibyte_glyphs.append( + (item.ft_object, item.x, item.glyph_idx) + ) prev_was_multibyte = True # Do the rotation and global translation as a single matrix # concatenation up front @@ -2430,7 +2433,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): self.file.output(Op.end_text) # Then emit all the multibyte characters, one at a time. for ft_object, start_x, glyph_idx in multibyte_glyphs: - self._draw_xobject_glyph(ft_object, fontsize, glyph_idx, start_x, 0) + self._draw_xobject_glyph( + ft_object, fontsize, glyph_idx, start_x, 0 + ) self.file.output(Op.grestore) def _draw_xobject_glyph(self, ft_object, fontsize, glyph_idx, x, y): From cb505100a5dd5304224e54ab11892ce5cb188aab Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Fri, 13 Aug 2021 17:40:01 +0530 Subject: [PATCH 27/30] Add multi-font tests for PDF backend --- lib/matplotlib/tests/test_backend_pdf.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 8e16eb2b7b94..f8368b4f3cc7 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -368,3 +368,21 @@ def test_glyphs_subset(): # since both objects are assigned same characters assert subfont.get_num_glyphs() == nosubfont.get_num_glyphs() + + +@image_comparison(["multi_font_type3.pdf"]) +def test_multi_font_type3(): + plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + plt.rc('pdf', fonttype=3) + + fig = plt.figure() + fig.text(0.15, 0.475, "There are 多个汉字 in between!") + + +@image_comparison(["multi_font_type42.pdf"]) +def test_multi_font_type42(): + plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + plt.rc('pdf', fonttype=42) + + fig = plt.figure() + fig.text(0.15, 0.475, "There are 多个汉字 in between!") From 70b0e18ea68c614e482858901617784667d6da37 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Fri, 13 Aug 2021 17:40:23 +0530 Subject: [PATCH 28/30] Add baseline images --- .../test_backend_pdf/multi_font_type3.pdf | Bin 0 -> 9613 bytes .../test_backend_pdf/multi_font_type42.pdf | Bin 0 -> 9709 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4ff499347a5e210a6300929610eaad83cee14d36 GIT binary patch literal 9613 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 QY!(at_m!TtvkmG00N0-_N&o-= literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d5013dd41c9d499b681d447ee592ac7bcea1b9e0 GIT binary patch literal 9709 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 UxEtoUEyaM?w&dbc)KVh*KbuJJ@Bjb+ literal 0 HcmV?d00001 From 8fd0729d75505fcf38749b6b91f51756cadd46c7 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Wed, 18 Aug 2021 05:29:19 +0530 Subject: [PATCH 29/30] Fix memory leak and render tofu --- src/ft2font.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 0da6a52df9bf..088e23d1d703 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -377,6 +377,10 @@ FT2Font::~FT2Font() if (face) { FT_Done_Face(face); } + + for (size_t i = 0; i < fallbacks.size(); i++) { + Py_DECREF(fallbacks[i]->get_pyfont()); + } } void FT2Font::clear() @@ -508,7 +512,7 @@ void FT2Font::set_text( FT_BBox glyph_bbox; FT_Pos last_advance; - FT_UInt final_glyph_index; + FT_UInt final_glyph_index = 0; FT_Error charcode_error, glyph_error; FT2Font *ft_object_with_glyph = this; bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, @@ -516,7 +520,12 @@ void FT2Font::set_text( charcode_error, glyph_error, false); if (!was_found) { ft_glyph_warn((FT_ULong)codepoints[n]); - continue; + + // render tofu + // ft_object_with_glyph == this + char_to_font[codepoints[n]] = ft_object_with_glyph; + glyph_to_font[final_glyph_index] = ft_object_with_glyph; + ft_object_with_glyph->load_glyph(final_glyph_index, flags, ft_object_with_glyph, false); } glyph_index = final_glyph_index; From 7c88286156ed518aa1709066b2364ad424dc9b96 Mon Sep 17 00:00:00 2001 From: Aitik Gupta Date: Wed, 18 Aug 2021 05:35:25 +0530 Subject: [PATCH 30/30] Check if fallback font is available --- lib/matplotlib/tests/test_agg.py | 9 ++++++++- lib/matplotlib/tests/test_backend_pdf.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index bfbda95f52a3..df67c397dc10 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -1,4 +1,5 @@ import io +from pathlib import Path import numpy as np from numpy.testing import assert_array_almost_equal @@ -7,7 +8,9 @@ from matplotlib import ( - collections, path, pyplot as plt, transforms as mtransforms, rcParams) + collections, path, pyplot as plt, transforms as mtransforms, rcParams, + font_manager as fm +) from matplotlib.image import imread from matplotlib.figure import Figure from matplotlib.testing.decorators import image_comparison @@ -255,6 +258,10 @@ def test_draw_path_collection_error_handling(): @image_comparison(["font_fallback.png"]) def test_font_fallback(): + fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) + if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": + pytest.skip("Font may be missing") + plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=15) fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index f8368b4f3cc7..98e72d8756c0 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -9,7 +9,9 @@ import pytest import matplotlib as mpl -from matplotlib import dviread, pyplot as plt, checkdep_usetex, rcParams +from matplotlib import ( + dviread, pyplot as plt, checkdep_usetex, rcParams, font_manager as fm +) from matplotlib.cbook import _get_data_path from matplotlib.ft2font import FT2Font from matplotlib.backends._backend_pdf_ps import get_glyphs_subset @@ -372,6 +374,10 @@ def test_glyphs_subset(): @image_comparison(["multi_font_type3.pdf"]) def test_multi_font_type3(): + fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) + if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": + pytest.skip("Font may be missing") + plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) plt.rc('pdf', fonttype=3) @@ -381,6 +387,10 @@ def test_multi_font_type3(): @image_comparison(["multi_font_type42.pdf"]) def test_multi_font_type42(): + fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) + if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": + pytest.skip("Font may be missing") + plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) plt.rc('pdf', fonttype=42)