Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 002661c

Browse files
authored
Merge pull request #29838 from anntzer/texmetrics
Switch Tfm metrics to TrueType-compatible API.
2 parents 0d11978 + 5d0adf1 commit 002661c

File tree

4 files changed

+72
-33
lines changed

4 files changed

+72
-33
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
``DviFont.widths``
22
~~~~~~~~~~~~~~~~~~
33
... is deprecated with no replacement.
4+
5+
Direct access to ``Tfm``'s ``widths``, ``heights``, ``depths`` dicts
6+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7+
... is deprecated; access a glyph's metrics with `.Tfm.get_metrics` instead.

lib/matplotlib/backends/backend_pdf.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -993,8 +993,9 @@ def _embedTeXFont(self, fontinfo):
993993
widthsObject = self.reserveObject('font widths')
994994
tfm = fontinfo.dvifont._tfm
995995
# convert from TeX's 12.20 representation to 1/1000 text space units.
996-
widths = [(1000 * tfm.width.get(char, 0)) >> 20
997-
for char in range(max(tfm.width, default=-1) + 1)]
996+
widths = [(1000 * metrics.tex_width) >> 20
997+
if (metrics := tfm.get_metrics(char)) else 0
998+
for char in range(max(tfm._glyph_metrics, default=-1) + 1)]
998999
self.writeObject(widthsObject, widths)
9991000

10001001
# Font dictionary

lib/matplotlib/dviread.py

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919

2020
from collections import namedtuple
21+
import dataclasses
2122
import enum
2223
from functools import cache, lru_cache, partial, wraps
2324
import logging
@@ -604,32 +605,30 @@ def __repr__(self):
604605

605606
def _width_of(self, char):
606607
"""Width of char in dvi units."""
607-
width = self._tfm.width.get(char, None)
608-
if width is not None:
609-
return _mul1220(width, self._scale)
610-
_log.debug('No width for char %d in font %s.', char, self.texname)
611-
return 0
608+
metrics = self._tfm.get_metrics(char)
609+
if metrics is None:
610+
_log.debug('No width for char %d in font %s.', char, self.texname)
611+
return 0
612+
return _mul1220(metrics.tex_width, self._scale)
612613

613614
def _height_depth_of(self, char):
614615
"""Height and depth of char in dvi units."""
615-
result = []
616-
for metric, name in ((self._tfm.height, "height"),
617-
(self._tfm.depth, "depth")):
618-
value = metric.get(char, None)
619-
if value is None:
620-
_log.debug('No %s for char %d in font %s',
621-
name, char, self.texname)
622-
result.append(0)
623-
else:
624-
result.append(_mul1220(value, self._scale))
616+
metrics = self._tfm.get_metrics(char)
617+
if metrics is None:
618+
_log.debug('No metrics for char %d in font %s', char, self.texname)
619+
return [0, 0]
620+
hd = [
621+
_mul1220(metrics.tex_height, self._scale),
622+
_mul1220(metrics.tex_depth, self._scale),
623+
]
625624
# cmsyXX (symbols font) glyph 0 ("minus") has a nonzero descent
626625
# so that TeX aligns equations properly
627626
# (https://tex.stackexchange.com/q/526103/)
628627
# but we actually care about the rasterization depth to align
629628
# the dvipng-generated images.
630629
if re.match(br'^cmsy\d+$', self.texname) and char == 0:
631-
result[-1] = 0
632-
return result
630+
hd[-1] = 0
631+
return hd
633632

634633

635634
class Vf(Dvi):
@@ -761,6 +760,22 @@ def _mul1220(num1, num2):
761760
return (num1*num2) >> 20
762761

763762

763+
@dataclasses.dataclass(frozen=True, kw_only=True)
764+
class TexMetrics:
765+
"""
766+
Metrics of a glyph, with TeX semantics.
767+
768+
TeX metrics have different semantics from FreeType metrics: tex_width
769+
corresponds to FreeType's ``advance`` (i.e., including whitespace padding);
770+
tex_height to ``bearingY`` (how much the glyph extends over the baseline);
771+
tex_depth to ``height - bearingY`` (how much the glyph extends under the
772+
baseline, as a positive number).
773+
"""
774+
tex_width: int
775+
tex_height: int
776+
tex_depth: int
777+
778+
764779
class Tfm:
765780
"""
766781
A TeX Font Metric file.
@@ -778,12 +793,7 @@ class Tfm:
778793
design_size : int
779794
Design size of the font (in 12.20 TeX points); unused because it is
780795
overridden by the scale factor specified in the dvi file.
781-
width, height, depth : dict
782-
Dimensions of each character, need to be scaled by the factor
783-
specified in the dvi file. These are dicts because indexing may
784-
not start from 0.
785796
"""
786-
__slots__ = ('checksum', 'design_size', 'width', 'height', 'depth')
787797

788798
def __init__(self, filename):
789799
_log.debug('opening tfm file %s', filename)
@@ -799,15 +809,26 @@ def __init__(self, filename):
799809
widths = struct.unpack(f'!{nw}i', file.read(4*nw))
800810
heights = struct.unpack(f'!{nh}i', file.read(4*nh))
801811
depths = struct.unpack(f'!{nd}i', file.read(4*nd))
802-
self.width = {}
803-
self.height = {}
804-
self.depth = {}
812+
self._glyph_metrics = {}
805813
for idx, char in enumerate(range(bc, ec+1)):
806814
byte0 = char_info[4*idx]
807815
byte1 = char_info[4*idx+1]
808-
self.width[char] = widths[byte0]
809-
self.height[char] = heights[byte1 >> 4]
810-
self.depth[char] = depths[byte1 & 0xf]
816+
self._glyph_metrics[char] = TexMetrics(
817+
tex_width=widths[byte0],
818+
tex_height=heights[byte1 >> 4],
819+
tex_depth=depths[byte1 & 0xf],
820+
)
821+
822+
def get_metrics(self, idx):
823+
"""Return a glyph's TexMetrics, or None if unavailable."""
824+
return self._glyph_metrics.get(idx)
825+
826+
width = _api.deprecated("3.11", alternative="get_metrics")(
827+
property(lambda self: {c: m.tex_width for c, m in self._glyph_metrics}))
828+
height = _api.deprecated("3.11", alternative="get_metrics")(
829+
property(lambda self: {c: m.tex_height for c, m in self._glyph_metrics}))
830+
depth = _api.deprecated("3.11", alternative="get_metrics")(
831+
property(lambda self: {c: m.tex_depth for c, m in self._glyph_metrics}))
811832

812833

813834
PsFont = namedtuple('PsFont', 'texname psname effects encoding filename')

lib/matplotlib/dviread.pyi

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import dataclasses
12
from pathlib import Path
23
import io
34
import os
@@ -68,13 +69,25 @@ class Vf(Dvi):
6869
def __init__(self, filename: str | os.PathLike) -> None: ...
6970
def __getitem__(self, code: int) -> Page: ...
7071

72+
@dataclasses.dataclass(frozen=True, kw_only=True)
73+
class TexMetrics:
74+
tex_width: int
75+
tex_height: int
76+
tex_depth: int
77+
# work around mypy not respecting kw_only=True in stub files
78+
__match_args__ = ()
79+
7180
class Tfm:
7281
checksum: int
7382
design_size: int
74-
width: dict[int, int]
75-
height: dict[int, int]
76-
depth: dict[int, int]
7783
def __init__(self, filename: str | os.PathLike) -> None: ...
84+
def get_metrics(self, idx: int) -> TexMetrics | None: ...
85+
@property
86+
def width(self) -> dict[int, int]: ...
87+
@property
88+
def height(self) -> dict[int, int]: ...
89+
@property
90+
def depth(self) -> dict[int, int]: ...
7891

7992
class PsFont(NamedTuple):
8093
texname: bytes

0 commit comments

Comments
 (0)