Skip to content

Commit

Permalink
Emit Type 42 text with TJ and kerning in PDF
Browse files Browse the repository at this point in the history
The commit applies the same kerning algorithm used for Type 3 fonts also to Type
42 fonts. A string is split into chunks with kerning between the chunks.
  • Loading branch information
sauerburger committed Jul 14, 2021
1 parent 76b3b6e commit 67aea08
Showing 1 changed file with 30 additions and 15 deletions.
45 changes: 30 additions & 15 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = []
Expand All @@ -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:
Expand Down

0 comments on commit 67aea08

Please sign in to comment.