Skip to content

Commit eb6664f

Browse files
authored
Merge pull request #14872 from anntzer/text_layout
Unify text layout paths.
2 parents 6aec225 + 959eeae commit eb6664f

File tree

4 files changed

+94
-104
lines changed

4 files changed

+94
-104
lines changed

lib/matplotlib/_text_layout.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
Text layouting utilities.
3+
"""
4+
5+
from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
6+
7+
8+
def layout(string, font, *, x0=0, kern_mode=KERNING_DEFAULT):
9+
"""
10+
Render *string* with *font*. For each character in *string*, yield a
11+
(character-index, x-position) pair. When such a pair is yielded, the
12+
font's glyph is set to the corresponding character.
13+
14+
Parameters
15+
----------
16+
string : str
17+
The string to be rendered.
18+
font : FT2Font
19+
The font.
20+
x0 : float
21+
The initial x-value
22+
kern_mode : int
23+
A FreeType kerning mode.
24+
25+
Yields
26+
------
27+
character_index : int
28+
x_position : float
29+
"""
30+
x = x0
31+
last_char_idx = None
32+
for char in string:
33+
char_idx = font.get_char_index(ord(char))
34+
kern = (font.get_kerning(last_char_idx, char_idx, kern_mode)
35+
if last_char_idx is not None else 0) / 64
36+
x += kern
37+
glyph = font.load_glyph(char_idx, flags=LOAD_NO_HINTING)
38+
yield char_idx, x
39+
x += glyph.linearHoriAdvance / 65536
40+
last_char_idx = char_idx

lib/matplotlib/backends/backend_pdf.py

+41-47
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import numpy as np
2323

24-
from matplotlib import cbook, __version__, rcParams
24+
from matplotlib import _text_layout, cbook, __version__, rcParams
2525
from matplotlib._pylab_helpers import Gcf
2626
from matplotlib.backend_bases import (
2727
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
@@ -2176,52 +2176,46 @@ def draw_text_woven(chunks):
21762176
-math.sin(a), math.cos(a),
21772177
x, y, Op.concat_matrix)
21782178

2179-
# Output all the 1-byte characters in a BT/ET group, then
2180-
# output all the 2-byte characters.
2181-
for mode in (1, 2):
2182-
newx = oldx = 0
2183-
# Output a 1-byte character chunk
2184-
if mode == 1:
2185-
self.file.output(Op.begin_text,
2186-
self.file.fontName(prop),
2187-
fontsize,
2188-
Op.selectfont)
2189-
2190-
for chunk_type, chunk in chunks:
2191-
if mode == 1 and chunk_type == 1:
2192-
self._setup_textpos(newx, 0, 0, oldx, 0, 0)
2193-
self.file.output(self.encode_string(chunk, fonttype),
2194-
Op.show)
2195-
oldx = newx
2196-
2197-
lastgind = None
2198-
for c in chunk:
2199-
ccode = ord(c)
2200-
gind = font.get_char_index(ccode)
2201-
if mode == 2 and chunk_type == 2:
2202-
glyph_name = font.get_glyph_name(gind)
2203-
self.file.output(Op.gsave)
2204-
self.file.output(0.001 * fontsize, 0,
2205-
0, 0.001 * fontsize,
2206-
newx, 0, Op.concat_matrix)
2207-
name = self.file._get_xobject_symbol_name(
2208-
font.fname, glyph_name)
2209-
self.file.output(Name(name), Op.use_xobject)
2210-
self.file.output(Op.grestore)
2211-
2212-
# Move the pointer based on the character width
2213-
# and kerning
2214-
glyph = font.load_char(ccode, flags=LOAD_NO_HINTING)
2215-
if lastgind is not None:
2216-
kern = font.get_kerning(
2217-
lastgind, gind, KERNING_UNFITTED)
2218-
else:
2219-
kern = 0
2220-
lastgind = gind
2221-
newx += kern / 64 + glyph.linearHoriAdvance / 65536
2222-
2223-
if mode == 1:
2224-
self.file.output(Op.end_text)
2179+
# Output all the 1-byte characters in a BT/ET group.
2180+
self.file.output(Op.begin_text,
2181+
self.file.fontName(prop),
2182+
fontsize,
2183+
Op.selectfont)
2184+
newx = oldx = 0
2185+
for chunk_type, chunk in chunks:
2186+
if chunk_type == 1:
2187+
self._setup_textpos(newx, 0, 0, oldx, 0, 0)
2188+
self.file.output(self.encode_string(chunk, fonttype),
2189+
Op.show)
2190+
oldx = newx
2191+
# Update newx to include the advance from this chunk,
2192+
# regardless of its mode...
2193+
for char_idx, char_x in _text_layout.layout(
2194+
chunk, font, x0=newx, kern_mode=KERNING_UNFITTED):
2195+
pass
2196+
newx = char_x + ( # ... including the last character's advance
2197+
font.load_glyph(char_idx, flags=LOAD_NO_HINTING)
2198+
.linearHoriAdvance / 65536)
2199+
self.file.output(Op.end_text)
2200+
2201+
# Then output all the 2-byte characters.
2202+
newx = 0
2203+
for chunk_type, chunk in chunks:
2204+
for char_idx, char_x in _text_layout.layout(
2205+
chunk, font, x0=newx, kern_mode=KERNING_UNFITTED):
2206+
if chunk_type == 2:
2207+
glyph_name = font.get_glyph_name(char_idx)
2208+
self.file.output(Op.gsave)
2209+
self.file.output(0.001 * fontsize, 0,
2210+
0, 0.001 * fontsize,
2211+
char_x, 0, Op.concat_matrix)
2212+
name = self.file._get_xobject_symbol_name(
2213+
font.fname, glyph_name)
2214+
self.file.output(Name(name), Op.use_xobject)
2215+
self.file.output(Op.grestore)
2216+
newx = char_x + ( # ... including the last character's advance
2217+
font.load_glyph(char_idx, flags=LOAD_NO_HINTING)
2218+
.linearHoriAdvance / 65536)
22252219

22262220
self.file.output(Op.grestore)
22272221

lib/matplotlib/backends/backend_ps.py

+5-22
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
import matplotlib as mpl
2121
from matplotlib import (
2222
cbook, _path, __version__, rcParams, checkdep_ghostscript)
23+
from matplotlib import _text_layout
2324
from matplotlib.backend_bases import (
2425
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
2526
RendererBase)
2627
from matplotlib.cbook import (get_realpath_and_stat, is_writable_file_like,
2728
file_requires_unicode)
2829
from matplotlib.font_manager import is_opentype_cff_font, get_font
29-
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
30+
from matplotlib.ft2font import LOAD_NO_HINTING
3031
from matplotlib.ttconv import convert_ttf_to_ps
3132
from matplotlib.mathtext import MathTextParser
3233
from matplotlib._mathtext_data import uni2type1
@@ -623,27 +624,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
623624
.encode('ascii', 'replace').decode('ascii'))
624625
self.set_font(ps_name, prop.get_size_in_points())
625626

626-
lastgind = None
627-
lines = []
628-
thisx = 0
629-
thisy = 0
630-
for c in s:
631-
ccode = ord(c)
632-
gind = font.get_char_index(ccode)
633-
name = font.get_glyph_name(gind)
634-
glyph = font.load_char(ccode, flags=LOAD_NO_HINTING)
635-
636-
if lastgind is not None:
637-
kern = font.get_kerning(lastgind, gind, KERNING_DEFAULT)
638-
else:
639-
kern = 0
640-
lastgind = gind
641-
thisx += kern / 64
642-
643-
lines.append('%f %f m /%s glyphshow' % (thisx, thisy, name))
644-
thisx += glyph.linearHoriAdvance / 65536
645-
646-
thetext = '\n'.join(lines)
627+
thetext = '\n'.join(
628+
'%f 0 m /%s glyphshow' % (x, font.get_glyph_name(char_idx))
629+
for char_idx, x in _text_layout.layout(s, font))
647630
self._pswriter.write(f"""\
648631
gsave
649632
{x:f} {y:f} translate

lib/matplotlib/textpath.py

+8-35
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55

66
import numpy as np
77

8-
from matplotlib import cbook, dviread, font_manager, rcParams
8+
from matplotlib import _text_layout, cbook, dviread, font_manager, rcParams
99
from matplotlib.font_manager import FontProperties, get_font
10-
from matplotlib.ft2font import (
11-
KERNING_DEFAULT, LOAD_NO_HINTING, LOAD_TARGET_LIGHT)
10+
from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_TARGET_LIGHT
1211
from matplotlib.mathtext import MathTextParser
1312
from matplotlib.path import Path
1413
from matplotlib.transforms import Affine2D
@@ -162,14 +161,6 @@ def get_glyphs_with_font(self, font, s, glyph_map=None,
162161
Convert string *s* to vertices and codes using the provided ttf font.
163162
"""
164163

165-
# Mostly copied from backend_svg.py.
166-
167-
lastgind = None
168-
169-
currx = 0
170-
xpositions = []
171-
glyph_ids = []
172-
173164
if glyph_map is None:
174165
glyph_map = OrderedDict()
175166

@@ -178,33 +169,15 @@ def get_glyphs_with_font(self, font, s, glyph_map=None,
178169
else:
179170
glyph_map_new = glyph_map
180171

181-
# I'm not sure if I get kernings right. Needs to be verified. -JJL
182-
183-
for c in s:
184-
ccode = ord(c)
185-
gind = font.get_char_index(ccode)
186-
187-
if lastgind is not None:
188-
kern = font.get_kerning(lastgind, gind, KERNING_DEFAULT)
189-
else:
190-
kern = 0
191-
192-
glyph = font.load_char(ccode, flags=LOAD_NO_HINTING)
193-
horiz_advance = glyph.linearHoriAdvance / 65536
194-
195-
char_id = self._get_char_id(font, ccode)
172+
xpositions = []
173+
glyph_ids = []
174+
for char, (_, x) in zip(s, _text_layout.layout(s, font)):
175+
char_id = self._get_char_id(font, ord(char))
176+
glyph_ids.append(char_id)
177+
xpositions.append(x)
196178
if char_id not in glyph_map:
197179
glyph_map_new[char_id] = font.get_path()
198180

199-
currx += kern / 64
200-
201-
xpositions.append(currx)
202-
glyph_ids.append(char_id)
203-
204-
currx += horiz_advance
205-
206-
lastgind = gind
207-
208181
ypositions = [0] * len(xpositions)
209182
sizes = [1.] * len(xpositions)
210183

0 commit comments

Comments
 (0)