Skip to content

Commit 219d974

Browse files
committed
Merge branch 'libraqm-vector' into libraqm-full
2 parents 4c802b9 + 22e5b3d commit 219d974

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+58013
-25627
lines changed

ci/mypy-stubtest-allowlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ matplotlib\._.*
66
matplotlib\.rcsetup\._listify_validator
77
matplotlib\.rcsetup\._validate_linestyle
88
matplotlib\.ft2font\.Glyph
9+
matplotlib\.ft2font\.LayoutItem
910
matplotlib\.testing\.jpl_units\..*
1011
matplotlib\.sphinxext(\..*)?
1112

lib/matplotlib/_text_helpers.py

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,23 @@
44

55
from __future__ import annotations
66

7-
import dataclasses
7+
from collections.abc import Iterator
88

99
from . import _api
10-
from .ft2font import FT2Font, GlyphIndexType, Kerning, LoadFlags
10+
from .ft2font import FT2Font, CharacterCodeType, LayoutItem, LoadFlags
1111

1212

13-
@dataclasses.dataclass(frozen=True)
14-
class LayoutItem:
15-
ft_object: FT2Font
16-
char: str
17-
glyph_index: GlyphIndexType
18-
x: float
19-
prev_kern: float
20-
21-
22-
def warn_on_missing_glyph(codepoint, fontnames):
13+
def warn_on_missing_glyph(codepoint: CharacterCodeType, fontnames: str):
2314
_api.warn_external(
2415
f"Glyph {codepoint} "
2516
f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) "
2617
f"missing from font(s) {fontnames}.")
2718

2819

29-
def layout(string, font, *, features=None, kern_mode=Kerning.DEFAULT, language=None):
20+
def layout(string: str, font: FT2Font, *,
21+
features: tuple[str] | None = None,
22+
language: str | tuple[tuple[str, int, int], ...] | None = None
23+
) -> Iterator[LayoutItem]:
3024
"""
3125
Render *string* with *font*.
3226
@@ -41,8 +35,6 @@ def layout(string, font, *, features=None, kern_mode=Kerning.DEFAULT, language=N
4135
The font.
4236
features : tuple of str, optional
4337
The font features to apply to the text.
44-
kern_mode : Kerning
45-
A FreeType kerning mode.
4638
language : str, optional
4739
The language of the text in a format accepted by libraqm, namely `a BCP47
4840
language code <https://www.w3.org/International/articles/language-tags/>`_.
@@ -51,20 +43,8 @@ def layout(string, font, *, features=None, kern_mode=Kerning.DEFAULT, language=N
5143
------
5244
LayoutItem
5345
"""
54-
x = 0
55-
prev_glyph_index = None
56-
char_to_font = font._get_fontmap(string) # TODO: Pass in features and language.
57-
base_font = font
58-
for char in string:
59-
# This has done the fallback logic
60-
font = char_to_font.get(char, base_font)
61-
glyph_index = font.get_char_index(ord(char))
62-
kern = (
63-
base_font.get_kerning(prev_glyph_index, glyph_index, kern_mode) / 64
64-
if prev_glyph_index is not None else 0.
65-
)
66-
x += kern
67-
glyph = font.load_glyph(glyph_index, flags=LoadFlags.NO_HINTING)
68-
yield LayoutItem(font, char, glyph_index, x, kern)
69-
x += glyph.linearHoriAdvance / 65536
70-
prev_glyph_index = glyph_index
46+
for raqm_item in font._layout(string, LoadFlags.NO_HINTING,
47+
features=features, language=language):
48+
raqm_item.ft_object.load_glyph(raqm_item.glyph_index,
49+
flags=LoadFlags.NO_HINTING)
50+
yield raqm_item

lib/matplotlib/backends/_backend_pdf_ps.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ def __init__(self, subset_size: int = 0):
198198
self.glyph_maps: dict[str, GlyphMap] = {}
199199
self.subset_size = subset_size
200200

201-
def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
201+
def track(self, font: FT2Font, s: str,
202+
features: tuple[str, ...] | None = ...,
203+
language: str | tuple[tuple[str, int, int], ...] | None = None
204+
) -> list[tuple[int, CharacterCodeType]]:
202205
"""
203206
Record that string *s* is being typeset using font *font*.
204207
@@ -208,6 +211,14 @@ def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
208211
A font that is being used for the provided string.
209212
s : str
210213
The string that should be marked as tracked by the provided font.
214+
features : tuple[str, ...], optional
215+
The font feature tags to use for the font.
216+
217+
Available font feature tags may be found at
218+
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
219+
language : str, optional
220+
The language of the text in a format accepted by libraqm, namely `a BCP47
221+
language code <https://www.w3.org/International/articles/language-tags/>`_.
211222
212223
Returns
213224
-------
@@ -219,8 +230,9 @@ def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
219230
and the character codes will be returned from the string unchanged.
220231
"""
221232
return [
222-
self.track_glyph(f, ord(c), f.get_char_index(ord(c)))
223-
for c, f in font._get_fontmap(s).items()
233+
self.track_glyph(raqm_item.ft_object, raqm_item.char, raqm_item.glyph_index)
234+
for raqm_item in font._layout(s, ft2font.LoadFlags.NO_HINTING,
235+
features=features, language=language)
224236
]
225237

226238
def track_glyph(self, font: FT2Font, chars: str | CharacterCodeType,

lib/matplotlib/backends/backend_pdf.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from matplotlib.figure import Figure
3535
from matplotlib.font_manager import FontPath, get_font, fontManager as _fontManager
3636
from matplotlib._afm import AFM
37-
from matplotlib.ft2font import FT2Font, FaceFlags, Kerning, LoadFlags, StyleFlags
37+
from matplotlib.ft2font import FT2Font, FaceFlags, LoadFlags, StyleFlags
3838
from matplotlib.transforms import Affine2D, BboxBase
3939
from matplotlib.path import Path
4040
from matplotlib.dates import UTC
@@ -469,6 +469,7 @@ class Op(Enum):
469469
textpos = b'Td'
470470
selectfont = b'Tf'
471471
textmatrix = b'Tm'
472+
textrise = b'Ts'
472473
show = b'Tj'
473474
showkern = b'TJ'
474475
setlinewidth = b'w'
@@ -2288,6 +2289,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
22882289
# If fonttype is neither 3 nor 42, emit the whole string at once
22892290
# without manual kerning.
22902291
if fonttype not in [3, 42]:
2292+
if not mpl.rcParams['pdf.use14corefonts']:
2293+
self.file._character_tracker.track(font, s,
2294+
features=features, language=language)
22912295
self.file.output(Op.begin_text,
22922296
self.file.fontName(prop), fontsize, Op.selectfont)
22932297
self._setup_textpos(x, y, angle)
@@ -2308,13 +2312,16 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23082312
# kerning between chunks.
23092313
else:
23102314
def output_singlebyte_chunk(kerns_or_chars):
2315+
if not kerns_or_chars:
2316+
return
23112317
self.file.output(
23122318
# See pdf spec "Text space details" for the 1000/fontsize
23132319
# (aka. 1000/T_fs) factor.
23142320
[(-1000 * next(group) / fontsize) if tp == float # a kern
23152321
else self._encode_glyphs(group, fonttype)
23162322
for tp, group in itertools.groupby(kerns_or_chars, type)],
23172323
Op.showkern)
2324+
kerns_or_chars.clear()
23182325
# Do the rotation and global translation as a single matrix
23192326
# concatenation up front
23202327
self.file.output(Op.gsave)
@@ -2329,26 +2336,28 @@ def output_singlebyte_chunk(kerns_or_chars):
23292336
# Emit all the characters in a BT/ET group.
23302337
self.file.output(Op.begin_text)
23312338
for item in _text_helpers.layout(s, font, features=features,
2332-
kern_mode=Kerning.UNFITTED,
23332339
language=language):
23342340
subset, charcode = self.file._character_tracker.track_glyph(
23352341
item.ft_object, item.char, item.glyph_index)
23362342
if (item.ft_object, subset) != prev_font:
2337-
if singlebyte_chunk:
2338-
output_singlebyte_chunk(singlebyte_chunk)
2343+
output_singlebyte_chunk(singlebyte_chunk)
23392344
font_path = FontPath(item.ft_object.fname,
23402345
item.ft_object.face_index)
23412346
ft_name = self.file.fontName(font_path, subset)
23422347
self.file.output(ft_name, fontsize, Op.selectfont)
23432348
self._setup_textpos(item.x, 0, 0, prev_start_x, 0, 0)
2344-
singlebyte_chunk = []
23452349
prev_font = (item.ft_object, subset)
23462350
prev_start_x = item.x
2351+
if item.y:
2352+
output_singlebyte_chunk(singlebyte_chunk)
2353+
self.file.output(item.y, Op.textrise)
23472354
if item.prev_kern:
23482355
singlebyte_chunk.append(item.prev_kern)
23492356
singlebyte_chunk.append(charcode)
2350-
if singlebyte_chunk:
2351-
output_singlebyte_chunk(singlebyte_chunk)
2357+
if item.y:
2358+
output_singlebyte_chunk(singlebyte_chunk)
2359+
self.file.output(0, Op.textrise)
2360+
output_singlebyte_chunk(singlebyte_chunk)
23522361
self.file.output(Op.end_text)
23532362
self.file.output(Op.grestore)
23542363

lib/matplotlib/backends/backend_ps.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
772772
if ismath:
773773
return self.draw_mathtext(gc, x, y, s, prop, angle)
774774

775-
stream = [] # list of (ps_name, x, char_name)
775+
stream = [] # list of (ps_name, x, y, char_name)
776776

777777
if mpl.rcParams['ps.useafm']:
778778
font = self._get_font_afm(prop)
@@ -790,7 +790,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
790790
kern = font.get_kern_dist_from_name(last_name, name)
791791
last_name = name
792792
thisx += kern * scale
793-
stream.append((ps_name, thisx, name))
793+
stream.append((ps_name, thisx, 0, name))
794794
thisx += width * scale
795795

796796
else:
@@ -810,14 +810,13 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
810810
ps_name = (item.ft_object.postscript_name
811811
.encode("ascii", "replace").decode("ascii"))
812812
glyph_name = item.ft_object.get_glyph_name(item.glyph_index)
813-
stream.append((f'{ps_name}-{subset}', item.x, glyph_name))
813+
stream.append((f'{ps_name}-{subset}', item.x, item.y, glyph_name))
814814
self.set_color(*gc.get_rgb())
815815

816-
for ps_name, group in itertools. \
817-
groupby(stream, lambda entry: entry[0]):
816+
for ps_name, group in itertools.groupby(stream, lambda entry: entry[0]):
818817
self.set_font(ps_name, prop.get_size_in_points(), False)
819-
thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow"
820-
for _, x, name in group)
818+
thetext = "\n".join(f"{x:g} {y:g} m /{name:s} glyphshow"
819+
for _, x, y, name in group)
821820
self._pswriter.write(f"""\
822821
gsave
823822
{self._get_clip_cmd(gc)}

lib/matplotlib/backends/backend_svg.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,11 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
10481048
text2path = self._text2path
10491049
color = rgb2hex(gc.get_rgb())
10501050
fontsize = prop.get_size_in_points()
1051+
if mtext is not None:
1052+
features = mtext.get_fontfeatures()
1053+
language = mtext.get_language()
1054+
else:
1055+
features = language = None
10511056

10521057
style = {}
10531058
if color != '#000000':
@@ -1068,7 +1073,8 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
10681073
if not ismath:
10691074
font = text2path._get_font(prop)
10701075
glyph_info, glyph_map_new, rects = text2path.get_glyphs_with_font(
1071-
font, s, glyph_map=glyph_map, return_new_glyphs_only=True)
1076+
font, s, features=features, language=language,
1077+
glyph_map=glyph_map, return_new_glyphs_only=True)
10721078
self._update_glyph_map_defs(glyph_map_new)
10731079

10741080
for glyph_repr, xposition, yposition, scale in glyph_info:

lib/matplotlib/ft2font.pyi

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,22 @@ class _SfntPcltDict(TypedDict):
199199
widthType: int
200200
serifStyle: int
201201

202+
@final
203+
class LayoutItem:
204+
@property
205+
def ft_object(self) -> FT2Font: ...
206+
@property
207+
def char(self) -> str: ...
208+
@property
209+
def glyph_index(self) -> GlyphIndexType: ...
210+
@property
211+
def x(self) -> float: ...
212+
@property
213+
def y(self) -> float: ...
214+
@property
215+
def prev_kern(self) -> float: ...
216+
def __str__(self) -> str: ...
217+
202218
@final
203219
class FT2Font(Buffer):
204220
def __init__(
@@ -212,7 +228,13 @@ class FT2Font(Buffer):
212228
) -> None: ...
213229
if sys.version_info[:2] >= (3, 12):
214230
def __buffer__(self, flags: int) -> memoryview: ...
215-
def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
231+
def _layout(
232+
self,
233+
text: str,
234+
flags: LoadFlags,
235+
features: tuple[str, ...] | None = ...,
236+
language: str | tuple[tuple[str, int, int], ...] | None = ...,
237+
) -> list[LayoutItem]: ...
216238
def clear(self) -> None: ...
217239
def draw_glyph_to_bitmap(
218240
self, image: NDArray[np.uint8], x: int, y: int, glyph: Glyph, antialiased: bool = ...
-723 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)