Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3b576e6

Browse filesBrowse files
authored
Merge pull request #18517 from anntzer/pdfkern
Include kerning when outputting pdf strings.
2 parents 03a542e + 1e1883b commit 3b576e6
Copy full SHA for 3b576e6

File tree

Expand file treeCollapse file tree

6 files changed

+49
-27
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+49
-27
lines changed

‎lib/matplotlib/_text_layout.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_text_layout.py
+11-5Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
Text layouting utilities.
33
"""
44

5+
import dataclasses
6+
57
from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
68

79

10+
LayoutItem = dataclasses.make_dataclass(
11+
"LayoutItem", ["char", "glyph_idx", "x", "prev_kern"])
12+
13+
814
def layout(string, font, *, kern_mode=KERNING_DEFAULT):
915
"""
1016
Render *string* with *font*. For each character in *string*, yield a
@@ -26,13 +32,13 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT):
2632
x_position : float
2733
"""
2834
x = 0
29-
last_glyph_idx = None
35+
prev_glyph_idx = None
3036
for char in string:
3137
glyph_idx = font.get_char_index(ord(char))
32-
kern = (font.get_kerning(last_glyph_idx, glyph_idx, kern_mode)
33-
if last_glyph_idx is not None else 0) / 64
38+
kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
39+
if prev_glyph_idx is not None else 0.)
3440
x += kern
3541
glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING)
36-
yield glyph_idx, x
42+
yield LayoutItem(char, glyph_idx, x, kern)
3743
x += glyph.linearHoriAdvance / 65536
38-
last_glyph_idx = glyph_idx
44+
prev_glyph_idx = glyph_idx

‎lib/matplotlib/backends/backend_pdf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pdf.py
+24-17Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2281,21 +2281,23 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
22812281
# complication is avoided, but of course, those fonts can not be
22822282
# subsetted.)
22832283
else:
2284-
singlebyte_chunks = [] # List of (start_x, list-of-1-byte-chars).
2285-
multibyte_glyphs = [] # List of (start_x, glyph_index).
2286-
prev_was_singlebyte = False
2287-
for char, (glyph_idx, glyph_x) in zip(
2288-
s,
2289-
_text_layout.layout(s, font, kern_mode=KERNING_UNFITTED)):
2290-
if ord(char) <= 255:
2291-
if prev_was_singlebyte:
2292-
singlebyte_chunks[-1][1].append(char)
2293-
else:
2294-
singlebyte_chunks.append((glyph_x, [char]))
2295-
prev_was_singlebyte = True
2284+
# List of (start_x, [prev_kern, char, char, ...]), w/o zero kerns.
2285+
singlebyte_chunks = []
2286+
# List of (start_x, glyph_index).
2287+
multibyte_glyphs = []
2288+
prev_was_multibyte = True
2289+
for item in _text_layout.layout(
2290+
s, font, kern_mode=KERNING_UNFITTED):
2291+
if ord(item.char) <= 255:
2292+
if prev_was_multibyte:
2293+
singlebyte_chunks.append((item.x, []))
2294+
if item.prev_kern:
2295+
singlebyte_chunks[-1][1].append(item.prev_kern)
2296+
singlebyte_chunks[-1][1].append(item.char)
2297+
prev_was_multibyte = False
22962298
else:
2297-
multibyte_glyphs.append((glyph_x, glyph_idx))
2298-
prev_was_singlebyte = False
2299+
multibyte_glyphs.append((item.x, item.glyph_idx))
2300+
prev_was_multibyte = True
22992301
# Do the rotation and global translation as a single matrix
23002302
# concatenation up front
23012303
self.file.output(Op.gsave)
@@ -2307,10 +2309,15 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23072309
self.file.output(Op.begin_text,
23082310
self.file.fontName(prop), fontsize, Op.selectfont)
23092311
prev_start_x = 0
2310-
for start_x, chars in singlebyte_chunks:
2312+
for start_x, kerns_or_chars in singlebyte_chunks:
23112313
self._setup_textpos(start_x, 0, 0, prev_start_x, 0, 0)
2312-
self.file.output(self.encode_string(''.join(chars), fonttype),
2313-
Op.show)
2314+
self.file.output(
2315+
# See pdf spec "Text space details" for the 1000/fontsize
2316+
# (aka. 1000/T_fs) factor.
2317+
[-1000 * next(group) / fontsize if tp == float # a kern
2318+
else self.encode_string("".join(group), fonttype)
2319+
for tp, group in itertools.groupby(kerns_or_chars, type)],
2320+
Op.showkern)
23142321
prev_start_x = start_x
23152322
self.file.output(Op.end_text)
23162323
# Then emit all the multibyte characters, one at a time.

‎lib/matplotlib/backends/backend_ps.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_ps.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,8 +584,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
584584
self.set_font(ps_name, prop.get_size_in_points())
585585

586586
thetext = '\n'.join(
587-
'%f 0 m /%s glyphshow' % (x, font.get_glyph_name(glyph_idx))
588-
for glyph_idx, x in _text_layout.layout(s, font))
587+
'{:f} 0 m /{:s} glyphshow'
588+
.format(item.x, font.get_glyph_name(item.glyph_idx))
589+
for item in _text_layout.layout(s, font))
589590
self._pswriter.write(f"""\
590591
gsave
591592
{x:f} {y:f} translate
Binary file not shown.

‎lib/matplotlib/tests/test_backend_pdf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_backend_pdf.py
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,11 @@ def test_empty_rasterized():
271271
fig, ax = plt.subplots()
272272
ax.plot([], [], rasterized=True)
273273
fig.savefig(io.BytesIO(), format="pdf")
274+
275+
276+
@image_comparison(['kerning.pdf'])
277+
def test_kerning():
278+
fig = plt.figure()
279+
s = "AVAVAVAVAVAVAVAV€AAVV"
280+
fig.text(0, .25, s, size=5)
281+
fig.text(0, .75, s, size=20)

‎lib/matplotlib/textpath.py

Copy file name to clipboardExpand all lines: lib/matplotlib/textpath.py
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ def get_glyphs_with_font(self, font, s, glyph_map=None,
149149

150150
xpositions = []
151151
glyph_ids = []
152-
for char, (_, x) in zip(s, _text_layout.layout(s, font)):
153-
char_id = self._get_char_id(font, ord(char))
152+
for item in _text_layout.layout(s, font):
153+
char_id = self._get_char_id(font, ord(item.char))
154154
glyph_ids.append(char_id)
155-
xpositions.append(x)
155+
xpositions.append(item.x)
156156
if char_id not in glyph_map:
157157
glyph_map_new[char_id] = font.get_path()
158158

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.