diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 10063bd9a7b3..d4cde3155af4 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2236,6 +2236,20 @@ def encode_string(self, s, fonttype): return s.encode('cp1252', 'replace') return s.encode('utf-16be', 'replace') + @staticmethod + def _font_supports_char(fonttype, char): + """ + Returns True if the font is able to provided the char in a PDF + + For a Type 3 font, this method returns True only for single-byte + chars. For Type 42 fonts this method always returns True. + """ + if fonttype == 3: + return ord(char) <= 255 + if fonttype == 42: + return True + raise NotImplementedError() + def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # docstring inherited @@ -2270,26 +2284,27 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): } self.file._annotations[-1][1].append(link_annotation) - # If fonttype != 3 emit the whole string at once without manual - # kerning. - if fonttype != 3: + # If fonttype is neither 3 nor 42, emit the whole string at once + # without manual kerning. + if fonttype not in [3, 42]: self.file.output(Op.begin_text, self.file.fontName(prop), fontsize, Op.selectfont) self._setup_textpos(x, y, angle) self.file.output(self.encode_string(s, fonttype), Op.show, Op.end_text) - # There is no way to access multibyte characters of Type 3 fonts, as - # they cannot have a CIDMap. Therefore, in this case we break the - # string into chunks, where each chunk contains either a string of - # consecutive 1-byte characters or a single multibyte character. - # A sequence of 1-byte characters is broken into multiple chunks to - # adjust the kerning between adjacent chunks. Each chunk is emitted - # with a separate command: 1-byte characters use the regular text show - # command (TJ) with appropriate kerning between chunks, whereas - # multibyte characters use the XObject command (Do). (If using Type - # 42 fonts, all of this complication is avoided, but of course, - # subsetting those fonts is complex/hard to implement.) + # A sequence of characters is broken into multiple chunks. The chunking + # serves two purposes: + # - For Type 3 fonts, there is no way to access multibyte characters, + # as they cannot have a CIDMap. Therefore, in this case we break + # the string into chunks, where each chunk contains either a string + # of consecutive 1-byte characters or a single multibyte character. + # - A sequence of 1-byte characters is split into chunks to allow for + # kerning adjustments between consecutive chunks. + # + # Each chunk is emitted with a separate command: 1-byte characters use + # 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. singlebyte_chunks = [] @@ -2298,7 +2313,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): - if ord(item.char) <= 255: + if self._font_supports_char(fonttype, item.char): if prev_was_multibyte: singlebyte_chunks.append((item.x, [])) if item.prev_kern: diff --git a/lib/matplotlib/tests/baseline_images/test_text/text_pdf_font42_kerning.pdf b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_font42_kerning.pdf new file mode 100644 index 000000000000..a8ce9fca346c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_text/text_pdf_font42_kerning.pdf differ diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index abefe3c3ab04..8c575a7be1a6 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -741,3 +741,10 @@ def test_parse_math(): ax.text(0, 0, r"$ \wrong{math} $", parse_math=True) with pytest.raises(ValueError, match='Unknown symbol'): fig.canvas.draw() + + +@image_comparison(['text_pdf_font42_kerning.pdf'], style='mpl20') +def test_pdf_font42_kerning(): + plt.rcParams['pdf.fonttype'] = 42 + plt.figure() + plt.figtext(0.1, 0.5, "ATAVATAVATAVATAVATA", size=30)