| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
22 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,7 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | 10 | ||
| 11 | 11 | LayoutItem = dataclasses.make_dataclass( | |
| 12 | - "LayoutItem", ["char", "glyph_idx", "x", "prev_kern"]) | ||
| 12 | + "LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"]) | ||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | 15 | def warn_on_missing_glyph(codepoint): | |
@@ -57,12 +57,18 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): | |||
| 57 | 57 | """ | |
| 58 | 58 | x = 0 | |
| 59 | 59 | prev_glyph_idx = None | |
| 60 | + char_to_font = font._get_fontmap(string) | ||
| 61 | + base_font = font | ||
| 60 | 62 | for char in string: | |
| 63 | + # This has done the fallback logic | ||
| 64 | + font = char_to_font.get(char, base_font) | ||
| 61 | 65 | glyph_idx = font.get_char_index(ord(char)) | |
| 62 | - kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64 | ||
| 63 | - if prev_glyph_idx is not None else 0.) | ||
| 66 | + kern = ( | ||
| 67 | + base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64 | ||
| 68 | + if prev_glyph_idx is not None else 0. | ||
| 69 | + ) | ||
| 64 | 70 | x += kern | |
| 65 | 71 | glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING) | |
| 66 | - yield LayoutItem(char, glyph_idx, x, kern) | ||
| 72 | + yield LayoutItem(font, char, glyph_idx, x, kern) | ||
| 67 | 73 | x += glyph.linearHoriAdvance / 65536 | |
| 68 | 74 | prev_glyph_idx = glyph_idx | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -65,7 +65,9 @@ def __init__(self): | |||
| 65 | 65 | ||
| 66 | 66 | def track(self, font, s): | |
| 67 | 67 | """Record that string *s* is being typeset using font *font*.""" | |
| 68 | - self.used.setdefault(font.fname, set()).update(map(ord, s)) | ||
| 68 | + char_to_font = font._get_fontmap(s) | ||
| 69 | + for _c, _f in char_to_font.items(): | ||
| 70 | + self.used.setdefault(_f.fname, set()).add(ord(_c)) | ||
| 69 | 71 | ||
| 70 | 72 | def track_glyph(self, font, glyph): | |
| 71 | 73 | """Record that codepoint *glyph* is being typeset using font *font*.""" | |
@@ -135,8 +137,8 @@ def _get_font_afm(self, prop): | |||
| 135 | 137 | return _cached_get_afm_from_fname(fname) | |
| 136 | 138 | ||
| 137 | 139 | def _get_font_ttf(self, prop): | |
| 138 | - fname = font_manager.findfont(prop) | ||
| 139 | - font = font_manager.get_font(fname) | ||
| 140 | + fnames = font_manager.fontManager._find_fonts_by_props(prop) | ||
| 141 | + font = font_manager.get_font(fnames) | ||
| 140 | 142 | font.clear() | |
| 141 | 143 | font.set_size(prop.get_size_in_points(), 72) | |
| 142 | 144 | return font | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,7 +32,9 @@ | |||
| 32 | 32 | RendererBase) | |
| 33 | 33 | from matplotlib.backends.backend_mixed import MixedModeRenderer | |
| 34 | 34 | from matplotlib.figure import Figure | |
| 35 | - from matplotlib.font_manager import findfont, get_font | ||
| 35 | + from matplotlib.font_manager import ( | ||
| 36 | + findfont, get_font, fontManager as _fontManager | ||
| 37 | + ) | ||
| 36 | 38 | from matplotlib._afm import AFM | |
| 37 | 39 | from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, | |
| 38 | 40 | LOAD_NO_HINTING, KERNING_UNFITTED, FT2Font) | |
@@ -925,20 +927,28 @@ def fontName(self, fontprop): | |||
| 925 | 927 | """ | |
| 926 | 928 | ||
| 927 | 929 | if isinstance(fontprop, str): | |
| 928 | - filename = fontprop | ||
| 930 | + filenames = [fontprop] | ||
| 929 | 931 | elif mpl.rcParams['pdf.use14corefonts']: | |
| 930 | - filename = findfont( | ||
| 931 | - fontprop, fontext='afm', directory=RendererPdf._afm_font_dir) | ||
| 932 | + filenames = _fontManager._find_fonts_by_props( | ||
| 933 | + fontprop, fontext='afm', directory=RendererPdf._afm_font_dir | ||
| 934 | + ) | ||
| 932 | 935 | else: | |
| 933 | - filename = findfont(fontprop) | ||
| 934 | - | ||
| 935 | - Fx = self.fontNames.get(filename) | ||
| 936 | - if Fx is None: | ||
| 937 | - Fx = next(self._internal_font_seq) | ||
| 938 | - self.fontNames[filename] = Fx | ||
| 939 | - _log.debug('Assigning font %s = %r', Fx, filename) | ||
| 940 | - | ||
| 941 | - return Fx | ||
| 936 | + filenames = _fontManager._find_fonts_by_props(fontprop) | ||
| 937 | + first_Fx = None | ||
| 938 | + for fname in filenames: | ||
| 939 | + Fx = self.fontNames.get(fname) | ||
| 940 | + if not first_Fx: | ||
| 941 | + first_Fx = Fx | ||
| 942 | + if Fx is None: | ||
| 943 | + Fx = next(self._internal_font_seq) | ||
| 944 | + self.fontNames[fname] = Fx | ||
| 945 | + _log.debug('Assigning font %s = %r', Fx, fname) | ||
| 946 | + if not first_Fx: | ||
| 947 | + first_Fx = Fx | ||
| 948 | + | ||
| 949 | + # find_fontsprop's first value always adheres to | ||
| 950 | + # findfont's value, so technically no behaviour change | ||
| 951 | + return first_Fx | ||
| 942 | 952 | ||
| 943 | 953 | def dviFontName(self, dvifont): | |
| 944 | 954 | """ | |
@@ -1204,7 +1214,6 @@ def get_char_width(charcode): | |||
| 1204 | 1214 | width = font.load_char( | |
| 1205 | 1215 | s, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance | |
| 1206 | 1216 | return cvt(width) | |
| 1207 | - | ||
| 1208 | 1217 | with warnings.catch_warnings(): | |
| 1209 | 1218 | # Ignore 'Required glyph missing from current font' warning | |
| 1210 | 1219 | # from ft2font: here we're just building the widths table, but | |
@@ -2389,22 +2398,27 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): | |||
| 2389 | 2398 | # the regular text show command (TJ) with appropriate kerning between | |
| 2390 | 2399 | # chunks, whereas multibyte characters use the XObject command (Do). | |
| 2391 | 2400 | else: | |
| 2392 | - # List of (start_x, [prev_kern, char, char, ...]), w/o zero kerns. | ||
| 2401 | + # List of (ft_object, start_x, [prev_kern, char, char, ...]), | ||
| 2402 | + # w/o zero kerns. | ||
| 2393 | 2403 | singlebyte_chunks = [] | |
| 2394 | - # List of (start_x, glyph_index). | ||
| 2404 | + # List of (ft_object, start_x, glyph_index). | ||
| 2395 | 2405 | multibyte_glyphs = [] | |
| 2396 | 2406 | prev_was_multibyte = True | |
| 2407 | + prev_font = font | ||
| 2397 | 2408 | for item in _text_helpers.layout( | |
| 2398 | 2409 | s, font, kern_mode=KERNING_UNFITTED): | |
| 2399 | 2410 | if _font_supports_glyph(fonttype, ord(item.char)): | |
| 2400 | - if prev_was_multibyte: | ||
| 2401 | - singlebyte_chunks.append((item.x, [])) | ||
| 2411 | + if prev_was_multibyte or item.ft_object != prev_font: | ||
| 2412 | + singlebyte_chunks.append((item.ft_object, item.x, [])) | ||
| 2413 | + prev_font = item.ft_object | ||
| 2402 | 2414 | if item.prev_kern: | |
| 2403 | - singlebyte_chunks[-1][1].append(item.prev_kern) | ||
| 2404 | - singlebyte_chunks[-1][1].append(item.char) | ||
| 2415 | + singlebyte_chunks[-1][2].append(item.prev_kern) | ||
| 2416 | + singlebyte_chunks[-1][2].append(item.char) | ||
| 2405 | 2417 | prev_was_multibyte = False | |
| 2406 | 2418 | else: | |
| 2407 | - multibyte_glyphs.append((item.x, item.glyph_idx)) | ||
| 2419 | + multibyte_glyphs.append( | ||
| 2420 | + (item.ft_object, item.x, item.glyph_idx) | ||
| 2421 | + ) | ||
| 2408 | 2422 | prev_was_multibyte = True | |
| 2409 | 2423 | # Do the rotation and global translation as a single matrix | |
| 2410 | 2424 | # concatenation up front | |
@@ -2414,10 +2428,12 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): | |||
| 2414 | 2428 | -math.sin(a), math.cos(a), | |
| 2415 | 2429 | x, y, Op.concat_matrix) | |
| 2416 | 2430 | # Emit all the 1-byte characters in a BT/ET group. | |
| 2417 | - self.file.output(Op.begin_text, | ||
| 2418 | - self.file.fontName(prop), fontsize, Op.selectfont) | ||
| 2431 | + | ||
| 2432 | + self.file.output(Op.begin_text) | ||
| 2419 | 2433 | prev_start_x = 0 | |
| 2420 | - for start_x, kerns_or_chars in singlebyte_chunks: | ||
| 2434 | + for ft_object, start_x, kerns_or_chars in singlebyte_chunks: | ||
| 2435 | + ft_name = self.file.fontName(ft_object.fname) | ||
| 2436 | + self.file.output(ft_name, fontsize, Op.selectfont) | ||
| 2421 | 2437 | self._setup_textpos(start_x, 0, 0, prev_start_x, 0, 0) | |
| 2422 | 2438 | self.file.output( | |
| 2423 | 2439 | # See pdf spec "Text space details" for the 1000/fontsize | |
@@ -2429,8 +2445,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): | |||
| 2429 | 2445 | prev_start_x = start_x | |
| 2430 | 2446 | self.file.output(Op.end_text) | |
| 2431 | 2447 | # Then emit all the multibyte characters, one at a time. | |
| 2432 | - for start_x, glyph_idx in multibyte_glyphs: | ||
| 2433 | - self._draw_xobject_glyph(font, fontsize, glyph_idx, start_x, 0) | ||
| 2448 | + for ft_object, start_x, glyph_idx in multibyte_glyphs: | ||
| 2449 | + self._draw_xobject_glyph( | ||
| 2450 | + ft_object, fontsize, glyph_idx, start_x, 0 | ||
| 2451 | + ) | ||
| 2434 | 2452 | self.file.output(Op.grestore) | |
| 2435 | 2453 | ||
| 2436 | 2454 | def _draw_xobject_glyph(self, font, fontsize, glyph_idx, x, y): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -631,7 +631,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): | |||
| 631 | 631 | if mpl.rcParams['ps.useafm']: | |
| 632 | 632 | font = self._get_font_afm(prop) | |
| 633 | 633 | scale = 0.001 * prop.get_size_in_points() | |
| 634 | - | ||
| 634 | + stream = [] | ||
| 635 | 635 | thisx = 0 | |
| 636 | 636 | last_name = None # kerns returns 0 for None. | |
| 637 | 637 | xs_names = [] | |
@@ -647,21 +647,36 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): | |||
| 647 | 647 | thisx += kern * scale | |
| 648 | 648 | xs_names.append((thisx, name)) | |
| 649 | 649 | thisx += width * scale | |
| 650 | + ps_name = (font.postscript_name | ||
| 651 | + .encode("ascii", "replace").decode("ascii")) | ||
| 652 | + stream.append((ps_name, xs_names)) | ||
| 650 | 653 | ||
| 651 | 654 | else: | |
| 652 | 655 | font = self._get_font_ttf(prop) | |
| 653 | - font.set_text(s, 0, flags=LOAD_NO_HINTING) | ||
| 654 | 656 | self._character_tracker.track(font, s) | |
| 655 | - xs_names = [(item.x, font.get_glyph_name(item.glyph_idx)) | ||
| 656 | - for item in _text_helpers.layout(s, font)] | ||
| 657 | + stream = [] | ||
| 658 | + prev_font = curr_stream = None | ||
| 659 | + for item in _text_helpers.layout(s, font): | ||
| 660 | + ps_name = (item.ft_object.postscript_name | ||
| 661 | + .encode("ascii", "replace").decode("ascii")) | ||
| 662 | + if item.ft_object is not prev_font: | ||
| 663 | + if curr_stream: | ||
| 664 | + stream.append(curr_stream) | ||
| 665 | + prev_font = item.ft_object | ||
| 666 | + curr_stream = [ps_name, []] | ||
| 667 | + curr_stream[1].append( | ||
| 668 | + (item.x, item.ft_object.get_glyph_name(item.glyph_idx)) | ||
| 669 | + ) | ||
| 670 | + # append the last entry | ||
| 671 | + stream.append(curr_stream) | ||
| 657 | 672 | ||
| 658 | 673 | self.set_color(*gc.get_rgb()) | |
| 659 | - ps_name = (font.postscript_name | ||
| 660 | - .encode("ascii", "replace").decode("ascii")) | ||
| 661 | - self.set_font(ps_name, prop.get_size_in_points()) | ||
| 662 | - thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow" | ||
| 663 | - for x, name in xs_names) | ||
| 664 | - self._pswriter.write(f"""\ | ||
| 674 | + | ||
| 675 | + for ps_name, xs_names in stream: | ||
| 676 | + self.set_font(ps_name, prop.get_size_in_points(), False) | ||
| 677 | + thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow" | ||
| 678 | + for x, name in xs_names) | ||
| 679 | + self._pswriter.write(f"""\ | ||
| 665 | 680 | gsave | |
| 666 | 681 | {self._get_clip_cmd(gc)} | |
| 667 | 682 | {x:g} {y:g} translate | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1130,7 +1130,8 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): | |||
| 1130 | 1130 | font_parts.append(f'{weight}') | |
| 1131 | 1131 | font_parts.extend([ | |
| 1132 | 1132 | f'{_short_float_fmt(prop.get_size())}px', | |
| 1133 | - f'{prop.get_family()[0]!r}', # ensure quoting | ||
| 1133 | + # ensure quoting | ||
| 1134 | + f'{", ".join(repr(f) for f in prop.get_family())}', | ||
| 1134 | 1135 | ]) | |
| 1135 | 1136 | style['font'] = ' '.join(font_parts) | |
| 1136 | 1137 | if prop.get_stretch() != 'normal': | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ | |||
| 8 | 8 | import pytest | |
| 9 | 9 | ||
| 10 | 10 | import matplotlib.testing | |
| 11 | + import matplotlib.testing.compare | ||
| 11 | 12 | from matplotlib import _get_executable_info, ExecutableNotFoundError | |
| 12 | 13 | ||
| 13 | 14 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments