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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy