diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 75d84997be9f..ca95c8081c47 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): @@ -63,6 +63,7 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): 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, 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_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 3224fb90e3a9..d51a50581378 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) @@ -136,7 +141,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_agg.py b/lib/matplotlib/backends/backend_agg.py index b937c64fce95..b5a2f85affc1 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 @@ -251,7 +251,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() diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 9980d49af85b..9d33a7bda630 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,28 @@ def fontName(self, fontprop): """ if isinstance(fontprop, str): - filename = fontprop + filenames = [fontprop] elif mpl.rcParams['pdf.use14corefonts']: - filename = findfont( - fontprop, fontext='afm', directory=RendererPdf._afm_font_dir) + 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): """ @@ -1143,7 +1151,6 @@ def get_char_width(charcode): width = font.load_char( 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 @@ -2331,7 +2338,9 @@ 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) + char_to_font = font.fill_glyphs(s) + for char, font in char_to_font.items(): + self.file._character_tracker.track(font, chr(char)) fonttype = mpl.rcParams['pdf.fonttype'] if gc.get_url() is not None: @@ -2371,22 +2380,27 @@ 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 + prev_font = font for item in _text_helpers.layout( s, font, kern_mode=KERNING_UNFITTED): if _font_supports_glyph(fonttype, ord(item.char)): if prev_was_multibyte: - singlebyte_chunks.append((item.x, [])) + singlebyte_chunks.append((item.ft_object, item.x, [])) + prev_font = item.ft_object 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 @@ -2396,10 +2410,12 @@ 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. - self.file.output(Op.begin_text, - self.file.fontName(prop), fontsize, Op.selectfont) + + self.file.output(Op.begin_text) 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 @@ -2411,14 +2427,16 @@ 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. - for start_x, glyph_idx in multibyte_glyphs: - self._draw_xobject_glyph(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) - def _draw_xobject_glyph(self, 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.""" - 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/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index f13e114a815b..9ef87cc2b373 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -640,10 +640,12 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): if ismath: return self.draw_mathtext(gc, x, y, s, prop, angle) + self.set_color(*gc.get_rgb()) + if mpl.rcParams['ps.useafm']: font = self._get_font_afm(prop) scale = 0.001 * prop.get_size_in_points() - + stream = [] thisx = 0 last_name = None # kerns returns 0 for None. xs_names = [] @@ -659,21 +661,36 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): thisx += kern * scale xs_names.append((thisx, name)) thisx += width * scale + ps_name = (font.postscript_name + .encode("ascii", "replace").decode("ascii")) + stream.append((ps_name, xs_names)) else: font = self._get_font_ttf(prop) - font.set_text(s, 0, flags=LOAD_NO_HINTING) - self._character_tracker.track(font, s) - xs_names = [(item.x, font.get_glyph_name(item.glyph_idx)) - for item in _text_helpers.layout(s, font)] - - self.set_color(*gc.get_rgb()) - ps_name = (font.postscript_name - .encode("ascii", "replace").decode("ascii")) - self.set_font(ps_name, prop.get_size_in_points()) - thetext = "\n".join(f"{x:f} 0 m /{name:s} glyphshow" - for x, name in xs_names) - self._pswriter.write(f"""\ + char_to_font = font.fill_glyphs(s, 0, flags=LOAD_NO_HINTING) + for char, font in char_to_font.items(): + self._character_tracker.track(font, chr(char)) + stream = [] + prev_font = curr_stream = None + for item in _text_helpers.layout(s, font): + ps_name = (item.ft_object.postscript_name + .encode("ascii", "replace").decode("ascii")) + if item.ft_object is not prev_font: + if curr_stream: + stream.append(curr_stream) + prev_font = item.ft_object + curr_stream = [ps_name, []] + curr_stream[1].append( + (item.x, item.ft_object.get_glyph_name(item.glyph_idx)) + ) + # append the last entry + stream.append(curr_stream) + + for ps_name, xs_names in stream: + self.set_font(ps_name, prop.get_size_in_points(), False) + thetext = "\n".join(f"{x:f} 0 m /{name:s} glyphshow" + for x, name in xs_names) + self._pswriter.write(f"""\ gsave {self._get_clip_cmd(gc)} {x:f} {y:f} translate diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 9d45575eb13d..6c88622deecc 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,117 @@ 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. + + """ + # print("finding font!") + + 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 +1513,21 @@ 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) + + ft2font_object = ft2font.FT2Font( + fpaths[0], hinting_factor, + _fallback_list=ftobjects, + _kerning_factor=_kerning_factor + ) + return ft2font_object # FT2Font objects cannot be used across fork()s because they reference the same @@ -1417,11 +1541,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 +1573,4 @@ def _load_fontmanager(*, try_read_cache=True): fontManager = _load_fontmanager() findfont = fontManager.findfont +find_fontsprop = fontManager.find_fontsprop 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 000000000000..546beec003ea Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_agg/font_fallback.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type3.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type3.eps new file mode 100644 index 000000000000..b94c4d56fc3a --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type3.eps @@ -0,0 +1,533 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: multi_font_type3.eps +%%Creator: Matplotlib v3.4.2.post1635+gbd11b22ee3.d20210813, https://matplotlib.org/ +%%CreationDate: Fri Aug 13 16:50:31 2021 +%%Orientation: portrait +%%BoundingBox: 18 180 594 612 +%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%EndComments +%%BeginProlog +/mpldict 12 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } _d +/clipbox { + box + clip + newpath + } _d +/sc { setcachedevice } _d +%!PS-Adobe-3.0 Resource-Font +%%Creator: Converted from TrueType to Type 3 by Matplotlib. +10 dict begin +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix [0.00048828125 0 0 0.00048828125 0 0] def +/FontBBox [-2090 -948 3673 2524] def +/FontType 3 def +/Encoding [/space /exclam /b /a /e /h /i /n /r /t /T /w] def +/CharStrings 13 dict dup begin +/.notdef 0 def +/space{651 0 0 0 0 0 sc +ce} _d +/exclam{821 0 309 0 512 1493 sc +309 254 m +512 254 l +512 0 l +309 0 l +309 254 l + +309 1493 m +512 1493 l +512 838 l +492 481 l +330 481 l +309 838 l +309 1493 l + +ce} _d +/b{1300 0 186 -29 1188 1556 sc +997 559 m +997 694 969 800 913 877 c +858 954 781 993 684 993 c +587 993 510 954 454 877 c +399 800 371 694 371 559 c +371 424 399 317 454 240 c +510 163 587 125 684 125 c +781 125 858 163 913 240 c +969 317 997 424 997 559 c + +371 950 m +410 1017 458 1066 517 1098 c +576 1131 647 1147 729 1147 c +865 1147 975 1093 1060 985 c +1145 877 1188 735 1188 559 c +1188 383 1145 241 1060 133 c +975 25 865 -29 729 -29 c +647 -29 576 -13 517 19 c +458 52 410 101 371 168 c +371 0 l +186 0 l +186 1556 l +371 1556 l +371 950 l + +ce} _d +/a{1255 0 123 -29 1069 1147 sc +702 563 m +553 563 450 546 393 512 c +336 478 307 420 307 338 c +307 273 328 221 371 182 c +414 144 473 125 547 125 c +649 125 731 161 792 233 c +854 306 885 402 885 522 c +885 563 l +702 563 l + +1069 639 m +1069 0 l +885 0 l +885 170 l +843 102 791 52 728 19 c +665 -13 589 -29 498 -29 c +383 -29 292 3 224 67 c +157 132 123 218 123 326 c +123 452 165 547 249 611 c +334 675 460 707 627 707 c +885 707 l +885 725 l +885 810 857 875 801 921 c +746 968 668 991 567 991 c +503 991 441 983 380 968 c +319 953 261 930 205 899 c +205 1069 l +272 1095 338 1114 401 1127 c +464 1140 526 1147 586 1147 c +748 1147 869 1105 949 1021 c +1029 937 1069 810 1069 639 c + +ce} _d +/e{1260 0 113 -29 1151 1147 sc +1151 606 m +1151 516 l +305 516 l +313 389 351 293 419 226 c +488 160 583 127 705 127 c +776 127 844 136 910 153 c +977 170 1043 196 1108 231 c +1108 57 l +1042 29 974 8 905 -7 c +836 -22 765 -29 694 -29 c +515 -29 374 23 269 127 c +165 231 113 372 113 549 c +113 732 162 878 261 985 c +360 1093 494 1147 662 1147 c +813 1147 932 1098 1019 1001 c +1107 904 1151 773 1151 606 c + +967 660 m +966 761 937 841 882 901 c +827 961 755 991 664 991 c +561 991 479 962 417 904 c +356 846 320 764 311 659 c +967 660 l + +ce} _d +/h{1298 0 186 0 1124 1556 sc +1124 676 m +1124 0 l +940 0 l +940 670 l +940 776 919 855 878 908 c +837 961 775 987 692 987 c +593 987 514 955 457 892 c +400 829 371 742 371 633 c +371 0 l +186 0 l +186 1556 l +371 1556 l +371 946 l +415 1013 467 1064 526 1097 c +586 1130 655 1147 733 1147 c +862 1147 959 1107 1025 1027 c +1091 948 1124 831 1124 676 c + +ce} _d +/i{569 0 193 0 377 1556 sc +193 1120 m +377 1120 l +377 0 l +193 0 l +193 1120 l + +193 1556 m +377 1556 l +377 1323 l +193 1323 l +193 1556 l + +ce} _d +/n{1298 0 186 0 1124 1147 sc +1124 676 m +1124 0 l +940 0 l +940 670 l +940 776 919 855 878 908 c +837 961 775 987 692 987 c +593 987 514 955 457 892 c +400 829 371 742 371 633 c +371 0 l +186 0 l +186 1120 l +371 1120 l +371 946 l +415 1013 467 1064 526 1097 c +586 1130 655 1147 733 1147 c +862 1147 959 1107 1025 1027 c +1091 948 1124 831 1124 676 c + +ce} _d +/r{842 0 186 0 842 1147 sc +842 948 m +821 960 799 969 774 974 c +750 980 723 983 694 983 c +590 983 510 949 454 881 c +399 814 371 717 371 590 c +371 0 l +186 0 l +186 1120 l +371 1120 l +371 946 l +410 1014 460 1064 522 1097 c +584 1130 659 1147 748 1147 c +761 1147 775 1146 790 1144 c +805 1143 822 1140 841 1137 c +842 948 l + +ce} _d +/t{803 0 55 0 754 1438 sc +375 1438 m +375 1120 l +754 1120 l +754 977 l +375 977 l +375 369 l +375 278 387 219 412 193 c +437 167 488 154 565 154 c +754 154 l +754 0 l +565 0 l +423 0 325 26 271 79 c +217 132 190 229 190 369 c +190 977 l +55 977 l +55 1120 l +190 1120 l +190 1438 l +375 1438 l + +ce} _d +/T{1251 0 -6 0 1257 1493 sc +-6 1493 m +1257 1493 l +1257 1323 l +727 1323 l +727 0 l +524 0 l +524 1323 l +-6 1323 l +-6 1493 l + +ce} _d +/w{1675 0 86 0 1589 1120 sc +86 1120 m +270 1120 l +500 246 l +729 1120 l +946 1120 l +1176 246 l +1405 1120 l +1589 1120 l +1296 0 l +1079 0 l +838 918 l +596 0 l +379 0 l +86 1120 l + +ce} _d +end readonly def + +/BuildGlyph { + exch begin + CharStrings exch + 2 copy known not {pop /.notdef} if + true 3 1 roll get exec + end +} _d + +/BuildChar { + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec +} _d + +FontName currentdict end definefont pop +%!PS-Adobe-3.0 Resource-Font +%%Creator: Converted from TrueType to Type 3 by Matplotlib. +10 dict begin +/FontName /WenQuanYiZenHei def +/PaintType 0 def +/FontMatrix [0.0009765625 0 0 0.0009765625 0 0] def +/FontBBox [-129 -304 1076 986] def +/FontType 3 def +/Encoding [/uni6C49 /uni4E2A /uni591A /uni5B57] def +/CharStrings 5 dict dup begin +/.notdef 0 def +/uni6C49{1024 0 17 -119 990 820 sc +612 211 m +536 349 488 512 468 699 c +447 699 426 698 405 697 c +407 719 407 741 405 763 c +445 761 485 760 526 760 c +871 760 l +851 534 793 348 696 202 c +769 91 867 2 990 -65 c +963 -76 942 -94 928 -119 c +819 -56 727 31 653 143 c +561 27 446 -58 307 -112 c +289 -89 268 -69 243 -53 c +392 -2 515 86 612 211 c + +535 700 m +552 534 592 391 655 271 c +735 396 782 539 796 700 c +535 700 l + +151 -118 m +123 -102 88 -93 47 -92 c +76 -38 107 24 138 93 c +169 162 215 283 274 454 c +315 433 l +199 54 l +151 -118 l + +230 457 m +166 408 l +17 544 l +80 594 l +230 457 l + +248 626 m +202 677 152 724 97 768 c +157 820 l +214 773 268 723 317 670 c +248 626 l + +ce} _d +/uni4E2A{1024 0 14 -123 980 833 sc +547 -123 m +520 -120 492 -120 464 -123 c +467 -72 468 -21 468 30 c +468 362 l +468 413 467 465 464 516 c +492 513 520 513 547 516 c +545 465 544 413 544 362 c +544 30 l +544 -21 545 -72 547 -123 c + +980 427 m +955 410 939 387 931 358 c +846 384 767 429 695 494 c +624 559 563 631 514 711 c +383 520 236 378 71 285 c +59 314 40 337 14 354 c +113 405 204 471 285 550 c +367 630 433 724 484 833 c +499 822 515 813 531 805 c +537 808 l +542 800 l +549 796 557 792 564 789 c +555 775 l +614 672 682 590 759 531 c +824 484 898 450 980 427 c + +ce} _d +/uni591A{1024 0 60 -133 928 834 sc +581 89 m +526 34 l +399 161 l +319 111 239 75 158 52 c +154 81 142 107 121 128 c +260 164 367 214 444 279 c +493 322 539 367 582 416 c +601 395 623 376 646 359 c +606 321 l +928 321 l +845 176 731 63 584 -18 c +513 -58 434 -87 348 -105 c +262 -124 174 -129 85 -120 c +82 -91 73 -64 60 -39 c +205 -66 343 -51 475 6 c +608 63 713 150 792 266 c +544 266 l +518 245 492 225 465 206 c +581 89 l + +499 513 m +441 460 l +332 581 l +267 529 201 490 132 464 c +125 493 111 517 88 536 c +210 579 301 634 360 701 c +395 743 428 787 457 834 c +480 816 504 801 530 788 c +516 769 502 751 487 734 c +824 734 l +749 610 649 507 526 424 c +403 342 268 291 119 271 c +109 298 94 322 75 344 c +199 351 314 384 421 443 c +528 503 616 582 686 679 c +438 679 l +423 662 407 647 392 632 c +499 513 l + +ce} _d +/uni5B57{1024 0 32 -132 982 872 sc +982 285 m +980 264 980 243 982 222 c +943 224 904 225 865 225 c +555 225 l +555 -29 l +555 -54 545 -76 525 -95 c +496 -120 444 -132 368 -131 c +376 -98 368 -68 344 -43 c +366 -46 392 -47 422 -47 c +452 -48 470 -46 475 -42 c +480 -38 483 -27 483 -9 c +483 225 l +148 225 l +109 225 71 224 32 222 c +34 243 34 264 32 285 c +71 283 109 282 148 282 c +483 282 l +483 355 l +648 506 l +317 506 l +278 506 239 505 200 503 c +203 524 203 545 200 566 c +239 564 278 563 317 563 c +761 563 l +769 498 l +748 493 730 483 714 469 c +555 323 l +555 282 l +865 282 l +904 282 943 283 982 285 c + +131 562 m +59 562 l +59 753 l +468 752 l +390 807 l +435 872 l +542 798 l +510 752 l +925 752 l +925 562 l +852 562 l +852 695 l +131 695 l +131 562 l + +ce} _d +end readonly def + +/BuildGlyph { + exch begin + CharStrings exch + 2 copy known not {pop /.notdef} if + true 3 1 roll get exec + end +} _d + +/BuildChar { + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec +} _d + +FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +18 180 translate +576 432 0 0 clipbox +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1.000 setgray +fill +grestore +0.000 setgray +/DejaVuSans 27.000 selectfont +gsave + +86.400000 205.200000 translate +0.000000 rotate +0.000000 0 m /T glyphshow +16.492676 0 m /h glyphshow +33.604980 0 m /e glyphshow +50.216309 0 m /r glyphshow +61.316895 0 m /e glyphshow +77.928223 0 m /space glyphshow +86.510742 0 m /a glyphshow +103.056152 0 m /r glyphshow +114.156738 0 m /e glyphshow +130.768066 0 m /space glyphshow +grestore +/WenQuanYiZenHei 27.000 selectfont +gsave + +86.400000 205.200000 translate +0.000000 rotate +139.350586 0 m /uni591A glyphshow +166.350586 0 m /uni4E2A glyphshow +193.350586 0 m /uni6C49 glyphshow +220.350586 0 m /uni5B57 glyphshow +grestore +/DejaVuSans 27.000 selectfont +gsave + +86.400000 205.200000 translate +0.000000 rotate +247.350586 0 m /space glyphshow +255.933105 0 m /i glyphshow +263.434570 0 m /n glyphshow +280.546875 0 m /space glyphshow +289.129395 0 m /b glyphshow +306.268066 0 m /e glyphshow +322.879395 0 m /t glyphshow +333.465820 0 m /w glyphshow +355.548340 0 m /e glyphshow +372.159668 0 m /e glyphshow +388.770996 0 m /n glyphshow +405.883301 0 m /exclam glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type42.eps b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type42.eps new file mode 100644 index 000000000000..c97e0bb382c1 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_backend_ps/multi_font_type42.eps @@ -0,0 +1,363 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Title: multi_font_type42.eps +%%Creator: Matplotlib v3.4.2.post1635+gbd11b22ee3.d20210813, https://matplotlib.org/ +%%CreationDate: Fri Aug 13 16:50:31 2021 +%%Orientation: portrait +%%BoundingBox: 18 180 594 612 +%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%EndComments +%%BeginProlog +/mpldict 12 dict def +mpldict begin +/_d { bind def } bind def +/m { moveto } _d +/l { lineto } _d +/r { rlineto } _d +/c { curveto } _d +/cl { closepath } _d +/ce { closepath eofill } _d +/box { + m + 1 index 0 r + 0 exch r + neg 0 r + cl + } _d +/clipbox { + box + clip + newpath + } _d +/sc { setcachedevice } _d +%!PS-TrueTypeFont-1.0-2.22937 +%%Title: unknown +%%Creator: Converted from TrueType to type 42 by PPR +15 dict begin +/FontName /DejaVuSans def +/PaintType 0 def +/FontMatrix[1 0 0 1 0 0]def +/FontBBox[-1021 -463 1793 1232]def +/FontType 42 def +/Encoding StandardEncoding def +/FontInfo 10 dict dup begin +/FamilyName (unknown) def +/FullName (unknown) def +/Weight (unknown) def +/Version (unknown) def +/ItalicAngle 0.0 def +/isFixedPitch false def +/UnderlinePosition -130 def +/UnderlineThickness 90 def +end readonly def +/sfnts[<0001000000090080000300106376742000691D390000009C000001FE6670676D +7134766A0000029C000000AB676C7966118399D500000348000007B668656164085DC286 +00000B0000000036686865610D9F077C00000B3800000024686D74783A5706B700000B5C +0000003C6C6F636108C30B6500000B98000000206D617870047C067100000BB800000020 +707265703B07F10000000BD800000568013500B800CB00CB00C100AA009C01A600B80066 +0000007100CB00A002B20085007500B800C301CB0189022D00CB00A600F000D300AA0087 +00CB03AA0400014A003300CB000000D9050200F4015400B4009C01390114013907060400 +044E04B4045204B804E704CD0037047304CD04600473013303A2055605A60556053903C5 +021200C9001F00B801DF007300BA03E9033303BC0444040E00DF03CD03AA00E503AA0404 +000000CB008F00A4007B00B80014016F007F027B0252008F00C705CD009A009A006F00CB +00CD019E01D300F000BA018300D5009803040248009E01D500C100CB00F600830354027F +00000333026600D300C700A400CD008F009A0073040005D5010A00FE022B00A400B4009C +00000062009C0000001D032D05D505D505D505F0007F007B005400A406B80614072301D3 +00B800CB00A601C301EC069300A000D3035C037103DB0185042304A80448008F01390114 +01390360008F05D5019A0614072306660179046004600460047B009C00000277046001AA +00E904600762007B00C5007F027B000000B4025205CD006600BC00660077061000CD013B +01850389008F007B0000001D00CD074A042F009C009C0000077D006F0000006F0335006A +006F007B00AE00B2002D0396008F027B00F600830354063705F6008F009C04E10266008F +018D02F600CD03440029006604EE00730000140000960000B707060504030201002C2010 +B002254964B040515820C859212D2CB002254964B040515820C859212D2C20100720B000 +50B00D7920B8FFFF5058041B0559B0051CB0032508B0042523E120B00050B00D7920B8FF +FF5058041B0559B0051CB0032508E12D2C4B505820B0FD454459212D2CB002254560442D +2C4B5358B00225B0022545445921212D2C45442D2CB00225B0022549B00525B005254960 +B0206368208A108A233A8A10653A2D00000201350000020005D5000300090035400F0700 +8304810208070501030400000A10FC4BB00B5458B90000FFC038593CEC32393931002FE4 +FCCC3001B6000B200B500B035D253315231133110323030135CBCBCB14A215FEFE05D5FD +71FE9B0165000001FFFA000004E905D50007004A400E0602950081040140031C00400508 +10D4E4FCE431002FF4EC3230014BB00A5458BD00080040000100080008FFC03811373859 +401300091F00100110021F071009400970099F09095D03211521112311210604EFFDEECB +FDEE05D5AAFAD5052B000002007BFFE3042D047B000A002500BC4027191F0B17090E00A9 +1706B90E1120861FBA1CB923B8118C170C001703180D09080B1F030814452610FCECCCD4 +EC323211393931002FC4E4F4FCF4EC10C6EE10EE11391139123930406E301D301E301F30 +20302130223F27401D401E401F402040214022501D501E501F5020502150225027702785 +1D871E871F8720872185229027A027F0271E301E301F30203021401E401F40204021501E +501F50205021601E601F60206021701E701F70207021801E801F80208021185D015D0122 +061514163332363D01371123350E01232226353436332135342623220607353E01333216 +02BEDFAC816F99B9B8B83FBC88ACCBFDFB0102A79760B65465BE5AF3F00233667B6273D9 +B4294CFD81AA6661C1A2BDC0127F8B2E2EAA2727FC00000200BAFFE304A40614000B001C +0038401903B90C0F09B918158C0FB81B971900121247180C06081A461D10FCEC3232F4EC +31002FECE4F4C4EC10C6EE30B6601E801EA01E03015D013426232206151416333236013E +01333200111002232226271523113303E5A79292A7A79292A7FD8E3AB17BCC00FFFFCC7B +B13AB9B9022FCBE7E7CBCBE7E702526461FEBCFEF8FEF8FEBC6164A8061400020071FFE3 +047F047B0014001B00704024001501098608880515A90105B90C01BB18B912B80C8C1C1B +1502081508004B02120F451C10FCECF4ECC4111239310010E4F4ECE410EE10EE10F4EE11 +12393040293F1D701DA01DD01DF01D053F003F013F023F153F1B052C072F082F092C0A6F +006F016F026F156F1B095D71015D0115211E0133323637150E0123200011100033320007 +2E0123220607047FFCB20CCDB76AC76263D06BFEF4FEC70129FCE20107B802A5889AB90E +025E5ABEC73434AE2A2C0138010A01130143FEDDC497B4AE9E00000100BA000004640614 +001300344019030900030E0106870E11B80C970A010208004E0D09080B461410FCEC32F4 +EC31002F3CECF4C4EC1112173930B2601501015D0111231134262322061511231133113E +013332160464B87C7C95ACB9B942B375C1C602A4FD5C029E9F9EBEA4FD870614FD9E6564 +EF00000200C100000179061400030007002B400E06BE04B100BC020501080400460810FC +3CEC3231002FE4FCEC30400B1009400950096009700905015D1333112311331523C1B8B8 +B8B80460FBA00614E900000100BA00000464047B001300364019030900030E0106870E11 +B80CBC0A010208004E0D09080B461410FCEC32F4EC31002F3CE4F4C4EC1112173930B460 +15CF1502015D0111231134262322061511231133153E013332160464B87C7C95ACB9B942 +B375C1C602A4FD5C029E9F9EBEA4FD870460AE6564EF000100BA0000034A047B00110030 +4014060B0700110B03870EB809BC070A06080008461210FCC4EC3231002FE4F4ECC4D4CC +11123930B450139F1302015D012E012322061511231133153E0133321617034A1F492C9C +A7B9B93ABA85132E1C03B41211CBBEFDB20460AE6663050500010037000002F2059E0013 +003840190E05080F03A9001101BC08870A0B08090204000810120E461410FC3CC4FC3CC4 +32393931002FECF43CC4EC3211393930B2AF1501015D01112115211114163B0115232226 +3511233533110177017BFE854B73BDBDD5A28787059EFEC28FFDA0894E9A9FD202608F01 +3E0000010056000006350460000C01EB404905550605090A0904550A0903550A0B0A0255 +01020B0B0A061107080705110405080807021103020C000C011100000C420A0502030603 +00BF0B080C0B0A09080605040302010B07000D10D44BB00A544BB011545B4BB012545B4B +B013545B4BB00B545B58B9000000403859014BB00C544BB00D545B4BB010545B58B90000 +FFC03859CC173931002F3CEC32321739304B5358071005ED071008ED071008ED071005ED +071008ED071005ED0705ED071008ED59220140FF050216021605220A350A49024905460A +400A5B025B05550A500A6E026E05660A79027F0279057F05870299029805940ABC02BC05 +CE02C703CF051D0502090306040B050A080B09040B050C1502190316041A051B081B0914 +0B150C2500250123022703210425052206220725082709240A210B230C39033604360839 +0C300E460248034604400442054006400740084409440A440B400E400E56005601560250 +0451055206520750085309540A550B6300640165026A0365046A056A066A076E09610B67 +0C6F0E7500750179027D0378047D057A067F067A077F07780879097F097B0A760B7D0C87 +0288058F0E97009701940293039C049B05980698079908402F960C9F0EA600A601A402A4 +03AB04AB05A906A907AB08A40CAF0EB502B103BD04BB05B809BF0EC402C303CC04CA0579 +5D005D13331B01331B013301230B012356B8E6E5D9E6E5B8FEDBD9F1F2D90460FC96036A +FC96036AFBA00396FC6A00000001000000025999D203C60C5F0F3CF5001F080000000000 +D17E0EE400000000D17E0EE4F7D6FC4C0E5909DC00000008000000000000000000010000 +076DFE1D00000EFEF7D6FA510E5900010000000000000000000000000000000F04CD0066 +0000000002AA0000028B00000335013504E3FFFA04E7007B051400BA04EC0071051200BA +023900C1051200BA034A00BA03230037068B0056000000000000000000000031006900FF +014B01B501F102190255028C02C903DB00010000000F0354002B0068000C000200100099 +000800000415021600080004B8028040FFFBFE03FA1403F92503F83203F79603F60E03F5 +FE03F4FE03F32503F20E03F19603F02503EF8A4105EFFE03EE9603ED9603ECFA03EBFA03 +EAFE03E93A03E84203E7FE03E63203E5E45305E59603E48A4105E45303E3E22F05E3FA03 +E22F03E1FE03E0FE03DF3203DE1403DD9603DCFE03DB1203DA7D03D9BB03D8FE03D68A41 +05D67D03D5D44705D57D03D44703D3D21B05D3FE03D21B03D1FE03D0FE03CFFE03CEFE03 +CD9603CCCB1E05CCFE03CB1E03CA3203C9FE03C6851105C61C03C51603C4FE03C3FE03C2 +FE03C1FE03C0FE03BFFE03BEFE03BDFE03BCFE03BBFE03BA1103B9862505B9FE03B8B7BB +05B8FE03B7B65D05B7BB03B78004B6B52505B65D40FF03B64004B52503B4FE03B39603B2 +FE03B1FE03B0FE03AFFE03AE6403AD0E03ACAB2505AC6403ABAA1205AB2503AA1203A98A +4105A9FA03A8FE03A7FE03A6FE03A51203A4FE03A3A20E05A33203A20E03A16403A08A41 +05A096039FFE039E9D0C059EFE039D0C039C9B19059C64039B9A10059B19039A1003990A +0398FE0397960D0597FE03960D03958A410595960394930E05942803930E0392FA039190 +BB0591FE03908F5D0590BB039080048F8E25058F5D038F40048E25038DFE038C8B2E058C +FE038B2E038A8625058A410389880B05891403880B038786250587640386851105862503 +85110384FE038382110583FE0382110381FE0380FE037FFE0340FF7E7D7D057EFE037D7D +037C64037B5415057B25037AFE0379FE03780E03770C03760A0375FE0374FA0373FA0372 +FA0371FA0370FE036FFE036EFE036C21036BFE036A1142056A530369FE03687D03671142 +0566FE0365FE0364FE0363FE0362FE03613A0360FA035E0C035DFE035BFE035AFE035958 +0A0559FA03580A035716190557320356FE03555415055542035415035301100553180352 +1403514A130551FE03500B034FFE034E4D10054EFE034D10034CFE034B4A13054BFE034A +4910054A1303491D0D05491003480D0347FE0346960345960344FE0343022D0543FA0342 +BB03414B0340FE033FFE033E3D12053E14033D3C0F053D12033C3B0D053C40FF0F033B0D +033AFE0339FE033837140538FA033736100537140336350B05361003350B03341E03330D +0332310B0532FE03310B03302F0B05300D032F0B032E2D09052E10032D09032C32032B2A +25052B64032A2912052A25032912032827250528410327250326250B05260F03250B0324 +FE0323FE03220F03210110052112032064031FFA031E1D0D051E64031D0D031C1142051C +FE031BFA031A42031911420519FE031864031716190517FE031601100516190315FE0314 +FE0313FE031211420512FE0311022D05114203107D030F64030EFE030D0C16050DFE030C +0110050C16030BFE030A100309FE0308022D0508FE030714030664030401100504FE0340 +1503022D0503FE0302011005022D0301100300FE0301B80164858D012B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B002B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B +2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B1D00>]def +/CharStrings 13 dict dup begin +/.notdef 0 def +/space 3 def +/exclam 4 def +/b 7 def +/a 6 def +/e 8 def +/h 9 def +/i 10 def +/n 11 def +/r 12 def +/t 13 def +/T 5 def +/w 14 def +end readonly def + +systemdict/resourcestatus known + {42 /FontType resourcestatus + {pop pop false}{true}ifelse} + {true}ifelse +{/TrueDict where{pop}{(%%[ Error: no TrueType rasterizer ]%%)= flush}ifelse +/FontType 3 def + /TrueState 271 string def + TrueDict begin sfnts save + 72 0 matrix defaultmatrix dtransform dup + mul exch dup mul add sqrt cvi 0 72 matrix + defaultmatrix dtransform dup mul exch dup + mul add sqrt cvi 3 -1 roll restore + TrueState initer end + /BuildGlyph{exch begin + CharStrings dup 2 index known + {exch}{exch pop /.notdef}ifelse + get dup xcheck + {currentdict systemdict begin begin exec end end} + {TrueDict begin /bander load cvlit exch TrueState render end} + ifelse + end}bind def + /BuildChar{ + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec + }bind def +}if + +FontName currentdict end definefont pop +%!PS-TrueTypeFont-1.0-0.58982 +%%Title: unknown +%%Creator: Converted from TrueType to type 42 by PPR +15 dict begin +/FontName /WenQuanYiZenHei def +/PaintType 0 def +/FontMatrix[1 0 0 1 0 0]def +/FontBBox[-126 -297 1051 963]def +/FontType 42 def +/Encoding StandardEncoding def +/FontInfo 10 dict dup begin +/FamilyName (unknown) def +/FullName (unknown) def +/Weight (unknown) def +/Version (unknown) def +/ItalicAngle 0.0 def +/isFixedPitch false def +/UnderlinePosition -230 def +/UnderlineThickness 51 def +end readonly def +/sfnts[<00010000000700400002003063767420002202880000007C00000004676C7966 +422AC14C000000800000028E68656164F2831BDF00000310000000366868656107EC01A3 +0000034800000024686D747805C1004E0000036C0000001A6C6F636101E4013300000388 +000000126D617870008A02690000039C00000020002202880002000DFF8503D50341000D +0024000005260736351134271637061511140106072E0127020726273E01371617371716 +17071617160223292A04042A290301B4250C80D74AC4F7122795F54C171806050B0B0958 +74627B04044D4C014C4D4D04044D4DFEB44C01D91A2B27C278FEE18B2C194DEFA3100C03 +0806050E9B5946000002003BFF8703A00343001B00360000250727060726273637363716 +170721060706042726271624372306071307270607262736373637161706072106040726 +273624372306070245377F7879061FD07349411D232801427CDC6BFEFE860514D9018D76 +F82728223A6D61670A22B759352C22271516015171FE8FDF0F1DBA014069F8171759377F +4B222C2036614049201926D97A3C370D2B2628ABAE201C013335794E272B1D40653F461B +131C1ABAF71E28210AB3921916000002001FFF7C03D60369002A00370000010617262321 +151407062736271E01363D0121220736271633213537212207362716332117060F011521 +320123350527371707211523352103D603033A3BFECA1E2B720C24215A10FEB13A3A0303 +3A3A014FA5FEB53A3B04043B3A01BC081F189F01363BFCE74801994E2D6B20019F49FD2F +011D1F2003FE261C2501322604010C1BEA03201F03499703201F0341081592290118BF01 +37414A2EBE8500050010FF8803DF03350016001B00230027002D00002526032207362716 +33210207161706072627060726273613161736370126273E011317031307273F01262737 +16170264721E201F03033C3D01591E916EB82915A46F8AD01B25E0441A5E7815FD7B2A3E +2C5E5929741F40953FA845523C564AD3CF011902212103FEADDBA66510265EA8AE512318 +4D02A4F9B4BBF2FCCE180251D0010115FE850193318832204C4234465000000000010000 +0000E666CBB292A95F0F3CF5003F040000000000C7BE78E900000000C7BE78E9FF7FFED0 +043403DA0000000800020000000000000001000003DAFED0005C0455FF7FFE7804340001 +00000000000000000000000000000005017600220000000000000000000000000400000D +003B001F00100000000000000000000000000040009D00F3014700000001000000080165 +002800D10012000200000001000100000040002E0006000200>]def +/CharStrings 5 dict dup begin +/.notdef 0 def +/uni6C49 7 def +/uni4E2A 4 def +/uni591A 5 def +/uni5B57 6 def +end readonly def + +systemdict/resourcestatus known + {42 /FontType resourcestatus + {pop pop false}{true}ifelse} + {true}ifelse +{/TrueDict where{pop}{(%%[ Error: no TrueType rasterizer ]%%)= flush}ifelse +/FontType 3 def + /TrueState 271 string def + TrueDict begin sfnts save + 72 0 matrix defaultmatrix dtransform dup + mul exch dup mul add sqrt cvi 0 72 matrix + defaultmatrix dtransform dup mul exch dup + mul add sqrt cvi 3 -1 roll restore + TrueState initer end + /BuildGlyph{exch begin + CharStrings dup 2 index known + {exch}{exch pop /.notdef}ifelse + get dup xcheck + {currentdict systemdict begin begin exec end end} + {TrueDict begin /bander load cvlit exch TrueState render end} + ifelse + end}bind def + /BuildChar{ + 1 index /Encoding get exch get + 1 index /BuildGlyph get exec + }bind def +}if + +FontName currentdict end definefont pop +end +%%EndProlog +mpldict begin +18 180 translate +576 432 0 0 clipbox +gsave +0 0 m +576 0 l +576 432 l +0 432 l +cl +1.000 setgray +fill +grestore +0.000 setgray +/DejaVuSans 27.000 selectfont +gsave + +86.400000 205.200000 translate +0.000000 rotate +0.000000 0 m /T glyphshow +16.492676 0 m /h glyphshow +33.604980 0 m /e glyphshow +50.216309 0 m /r glyphshow +61.316895 0 m /e glyphshow +77.928223 0 m /space glyphshow +86.510742 0 m /a glyphshow +103.056152 0 m /r glyphshow +114.156738 0 m /e glyphshow +130.768066 0 m /space glyphshow +grestore +/WenQuanYiZenHei 27.000 selectfont +gsave + +86.400000 205.200000 translate +0.000000 rotate +139.350586 0 m /uni591A glyphshow +166.350586 0 m /uni4E2A glyphshow +193.350586 0 m /uni6C49 glyphshow +220.350586 0 m /uni5B57 glyphshow +grestore +/DejaVuSans 27.000 selectfont +gsave + +86.400000 205.200000 translate +0.000000 rotate +247.350586 0 m /space glyphshow +255.933105 0 m /i glyphshow +263.434570 0 m /n glyphshow +280.546875 0 m /space glyphshow +289.129395 0 m /b glyphshow +306.268066 0 m /e glyphshow +322.879395 0 m /t glyphshow +333.465820 0 m /w glyphshow +355.548340 0 m /e glyphshow +372.159668 0 m /e glyphshow +388.770996 0 m /n glyphshow +405.883301 0 m /exclam glyphshow +grestore + +end +showpage diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 14573f5941f6..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 @@ -251,3 +254,15 @@ def test_draw_path_collection_error_handling(): ax.scatter([1], [1]).set_paths(path.Path([(0, 1), (2, 3)])) with pytest.raises(TypeError): fig.canvas.draw() + + +@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() + ax.text(0.25, 0.475, "There are 多个汉字 in between!") diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 74776fd048a6..80ee74e10d61 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -7,7 +7,7 @@ import matplotlib as mpl import matplotlib.pyplot as plt -from matplotlib import cbook, patheffects +from matplotlib import cbook, patheffects, font_manager as fm from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.cbook import MatplotlibDeprecationWarning @@ -234,3 +234,29 @@ def test_linedash(): fig.savefig(buf, format="ps") assert buf.tell() > 0 + + +@image_comparison(["multi_font_type3.eps"]) +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('ps', fonttype=3) + + fig = plt.figure() + fig.text(0.15, 0.475, "There are 多个汉字 in between!") + + +@image_comparison(["multi_font_type42.eps"]) +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('ps', fonttype=42) + + fig = plt.figure() + fig.text(0.15, 0.475, "There are 多个汉字 in between!") 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 fdea4c39bec0..088e23d1d703 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,15 @@ 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, bool warn = true) +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + if (glyph_index) { + return glyph_index; + } + if (warn) return ft_glyph_warn(charcode); + else return 0; +} // 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,9 +324,14 @@ 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, + PyFT2Font *py_pointer) + : image(), face(NULL) { clear(); + py_font = py_pointer; FT_Error error = FT_Open_Face(_ft2Library, &open_args, 0, &face); @@ -353,6 +363,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 +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() @@ -376,6 +393,12 @@ void FT2Font::clear() } glyphs.clear(); + glyph_to_font.clear(); + char_to_font.clear(); + + for (unsigned int i = 0; i < fallbacks.size(); i++) { + fallbacks[i]->clear(); + } } void FT2Font::set_size(double ptsize, double dpi) @@ -387,8 +410,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 (unsigned int 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) { @@ -407,8 +440,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) { + // 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. + return right_ft_object->get_kerning(left, right, mode, false); + } if (!FT_HAS_KERNING(face)) { return 0; } @@ -421,9 +465,25 @@ 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 (unsigned int i = 0; i < fallbacks.size(); i ++){ + fallbacks[i]->set_kerning_factor(factor); + } } void FT2Font::set_text( @@ -452,28 +512,35 @@ 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 = 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, + char_to_font, glyph_to_font, codepoints[n], flags, + charcode_error, glyph_error, false); + if (!was_found) { + ft_glyph_warn((FT_ULong)codepoints[n]); + + // 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; // 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); - } - // ignore errors, jump to next glyph // extract glyph image and store it in our table + FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; - 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; + 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 +556,6 @@ void FT2Font::set_text( pen.x += last_advance; previous = glyph_index; - glyphs.push_back(thisGlyph); } FT_Vector_Transform(&pen, &matrix); @@ -500,17 +566,174 @@ 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(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, + bool override = false) +{ + FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + + 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 (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); + 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) @@ -525,6 +748,28 @@ 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; + } + + // 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) +{ + FT_Glyph_Get_CBox(glyphs.back(), ft_glyph_bbox_subpixels, &bbox); +} + void FT2Font::get_width_height(long *width, long *height) { *width = advance; @@ -615,8 +860,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 0863f3450b36..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,18 +72,34 @@ 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, PyFT2Font *py_pointer); virtual ~FT2Font(); void clear(); void set_size(double ptsize, double dpi); void set_charmap(int i); + void set_fallbacks(std::vector &fallback_list); 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(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, + 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(); @@ -89,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; @@ -123,6 +160,10 @@ class FT2Font FT_Face face; 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 06cbc22dad9c..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; @@ -264,7 +265,8 @@ typedef struct Py_ssize_t shape[2]; Py_ssize_t strides[2]; Py_ssize_t suboffsets[2]; -} PyFT2Font; + std::vector fallbacks; +}; static unsigned long read_from_file_callback(FT_Stream stream, unsigned long offset, @@ -361,15 +363,17 @@ const char *PyFT2Font_init__doc__ = static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) { - PyObject *filename = NULL, *open = NULL, *data = NULL; + 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,20 @@ 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); + } + 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 +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)), + "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))); @@ -503,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); } @@ -585,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" @@ -595,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" @@ -615,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__ = @@ -655,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__ = @@ -816,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); } @@ -855,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"; @@ -863,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); } @@ -1485,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__}, pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy