8000 TYP: Make glyph indices distinct from character codes by QuLogic · Pull Request #30143 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

TYP: Make glyph indices distinct from character codes #30143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: text-overhaul
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/development/30143-ES.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Glyph indices now typed distinctly from character codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Previously, character codes and glyph indices were both typed as `int`, which means you
could mix and match them erroneously. While the character code can't be made a distinct
type (because it's used for `chr`/`ord`), typing glyph indices as a distinct type means
these can't be fully swapped.
19 changes: 11 additions & 8 deletions lib/matplotlib/_afm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
import inspect
import logging
import re
from typing import BinaryIO, NamedTuple, TypedDict
from typing import BinaryIO, NamedTuple, TypedDict, cast

from ._mathtext_data import uni2type1
from .ft2font import CharacterCodeType, GlyphIndexType


_log = logging.getLogger(__name__)
Expand Down Expand Up @@ -197,7 +198,7 @@
The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*)."""


def _parse_char_metrics(fh: BinaryIO) -> tuple[dict[int, CharMetrics],
def _parse_char_metrics(fh: BinaryIO) -> tuple[dict[CharacterCodeType, CharMetrics],
dict[str, CharMetrics]]:
"""
Parse the given filehandle for character metrics information.
Expand All @@ -218,7 +219,7 @@
"""
required_keys = {'C', 'WX', 'N', 'B'}

ascii_d: dict[int, CharMetrics] = {}
ascii_d: dict[CharacterCodeType, CharMetrics] = {}
name_d: dict[str, CharMetrics] = {}
for bline in fh:
# We are defensively letting values be utf8. The spec requires
Expand Down Expand Up @@ -409,19 +410,21 @@

return left, miny, total_width, maxy - miny, -miny

def get_glyph_name(self, glyph_ind: int) -> str: # For consistency with FT2Font.
def get_glyph_name(self, # For consistency with FT2Font.
glyph_ind: GlyphIndexType) -> str:
"""Get the name of the glyph, i.e., ord(';') is 'semicolon'."""
return self._metrics[glyph_ind].name
return self._metrics[cast(CharacterCodeType, glyph_ind)].name

Check warning on line 416 in lib/matplotlib/_afm.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/_afm.py#L416

Added line #L416 was not covered by tests

def get_char_index(self, c: int) -> int: # For consistency with FT2Font.
def get_char_index(self, # For consistency with FT2Font.
c: CharacterCodeType) -> GlyphIndexType:
"""
Return the glyph index corresponding to a character code point.

Note, for AFM fonts, we treat the glyph index the same as the codepoint.
"""
return c
return cast(GlyphIndexType, c)

Check warning on line 425 in lib/matplotlib/_afm.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/_afm.py#L425

Added line #L425 was not covered by tests

def get_width_char(self, c: int) -> float:
def get_width_char(self, c: CharacterCodeType) -> float:
"""Get the width of the character code from the character metric WX field."""
return self._metrics[c].width

Expand Down
32 changes: 17 additions & 15 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@

if T.TYPE_CHECKING:
from collections.abc import Iterable
from .ft2font import Glyph
from .ft2font import CharacterCodeType, Glyph

Check warning on line 40 in lib/matplotlib/_mathtext.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/_mathtext.py#L40

Added line #L40 was not covered by tests


ParserElement.enable_packrat()
_log = logging.getLogger("matplotlib.mathtext")
Expand All @@ -47,7 +48,7 @@
# FONTS


def get_unicode_index(symbol: str) -> int: # Publicly exported.
def get_unicode_index(symbol: str) -> CharacterCodeType: # Publicly exported.
r"""
Return the integer index (from the Unicode table) of *symbol*.

Expand Down Expand Up @@ -85,7 +86,7 @@
width: float
height: float
depth: float
glyphs: list[tuple[FT2Font, float, int, float, float]]
glyphs: list[tuple[FT2Font, float, CharacterCodeType, float, float]]
rects: list[tuple[float, float, float, float]]

VectorParse.__module__ = "matplotlib.mathtext"
Expand Down Expand Up @@ -212,7 +213,7 @@
fontsize: float
postscript_name: str
metrics: FontMetrics
num: int
num: CharacterCodeType
glyph: Glyph
offset: float

Expand Down Expand Up @@ -365,7 +366,7 @@
return 0.

def _get_glyph(self, fontname: str, font_class: str,
sym: str) -> tuple[FT2Font, int, bool]:
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
raise NotImplementedError

# The return value of _get_info is cached per-instance.
Expand Down Expand Up @@ -425,7 +426,9 @@
info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
font = info1.font
return font.get_kerning(info1.num, info2.num, Kerning.DEFAULT) / 64
return font.get_kerning(font.get_char_index(info1.num),
font.get_char_index(info2.num),
Kerning.DEFAULT) / 64
return super().get_kern(font1, fontclass1, sym1, fontsize1,
font2, fontclass2, sym2, fontsize2, dpi)

Expand Down Expand Up @@ -459,7 +462,7 @@
_slanted_symbols = set(r"\int \oint".split())

def _get_glyph(self, fontname: str, font_class: str,
sym: str) -> tuple[FT2Font, int, bool]:
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
font = None
if fontname in self.fontmap and sym in latex_to_bakoma:
basename, num = latex_to_bakoma[sym]
Expand Down Expand Up @@ -551,7 +554,7 @@
# Some glyphs are not present in the `cmr10` font, and must be brought in
# from `cmsy10`. Map the Unicode indices of those glyphs to the indices at
# which they are found in `cmsy10`.
_cmr10_substitutions = {
_cmr10_substitutions: dict[CharacterCodeType, CharacterCodeType] = {
0x00D7: 0x00A3, # Multiplication sign.
0x2212: 0x00A1, # Minus sign.
}
Expand Down Expand Up @@ -594,11 +597,11 @@
_slanted_symbols = set(r"\int \oint".split())

def _map_virtual_font(self, fontname: str, font_class: str,
uniindex: int) -> tuple[str, int]:
uniindex: CharacterCodeType) -> tuple[str, CharacterCodeType]:
return fontname, uniindex

def _get_glyph(self, fontname: str, font_class: str,
sym: str) -> tuple[FT2Font, int, bool]:
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
try:
uniindex = get_unicode_index(sym)
found_symbol = True
Expand All @@ -607,8 +610,7 @@
found_symbol = False
_log.warning("No TeX to Unicode mapping for %a.", sym)

fontname, uniindex = self._map_virtual_font(
fontname, font_class, uniindex)
fontname, uniindex = self._map_virtual_font(fontname, font_class, uniindex)

new_fontname = fontname

Expand Down Expand Up @@ -693,7 +695,7 @@
self.fontmap[name] = fullpath

def _get_glyph(self, fontname: str, font_class: str,
sym: str) -> tuple[FT2Font, int, bool]:
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
# Override prime symbol to use Bakoma.
if sym == r'\prime':
return self.bakoma._get_glyph(fontname, font_class, sym)
Expand Down Expand Up @@ -783,7 +785,7 @@
self.fontmap[name] = fullpath

def _map_virtual_font(self, fontname: str, font_class: str,
uniindex: int) -> tuple[str, int]:
uniindex: CharacterCodeType) -> tuple[str, CharacterCodeType]:
# Handle these "fonts" that are actually embedded in
# other fonts.
font_mapping = stix_virtual_fonts.get(fontname)
Expand Down Expand Up @@ -1170,7 +1172,7 @@
self.glue_sign = 0 # 0: normal, -1: shrinking, 1: stretching
self.glue_order = 0 # The order of infinity (0 - 3) for the glue

def __repr__(self):
def __repr__(self) -> str:
return "{}<w={:.02f} h={:.02f} d={:.02f} s={:.02f}>[{}]".format(
super().__repr__(),
self.width, self.height,
Expand Down
18 changes: 11 additions & 7 deletions lib/matplotlib/_mathtext_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
"""

from __future__ import annotations
from typing import overload
from typing import TypeAlias, overload

latex_to_bakoma = {
from .ft2font import CharacterCodeType


latex_to_bakoma: dict[str, tuple[str, CharacterCodeType]] = {
'\\__sqrt__' : ('cmex10', 0x70),
'\\bigcap' : ('cmex10', 0x5c),
'\\bigcup' : ('cmex10', 0x5b),
Expand Down Expand Up @@ -241,7 +244,7 @@

# Automatically generated.

type12uni = {
type12uni: dict[str, CharacterCodeType] = {
'aring' : 229,
'quotedblright' : 8221,
'V' : 86,
Expand Down Expand Up @@ -475,7 +478,7 @@
# for key in sd:
# print("{0:24} : {1: <s},".format("'" + key + "'", sd[key]))

tex2uni = {
tex2uni: dict[str, CharacterCodeType] = {
'#' : 0x23,
'$' : 0x24,
'%' : 0x25,
Expand Down Expand Up @@ -1113,8 +1116,9 @@
# Each element is a 4-tuple of the form:
# src_start, src_end, dst_font, dst_start

_EntryTypeIn = tuple[str, str, str, str | int]
_EntryTypeOut = tuple[int, int, str, int]
_EntryTypeIn: TypeAlias = tuple[str, str, str, str | CharacterCodeType]
_EntryTypeOut: TypeAlias = tuple[CharacterCodeType, CharacterCodeType, str,
CharacterCodeType]

_stix_virtual_fonts: dict[str, dict[str, list[_EntryTypeIn]] | list[_EntryTypeIn]] = {
'bb': {
Expand Down Expand Up @@ -1735,7 +1739,7 @@ def _normalize_stix_fontcodes(d):
del _stix_virtual_fonts

# Fix some incorrect glyphs.
stix_glyph_fixes = {
stix_glyph_fixes: dict[CharacterCodeType, CharacterCodeType] = {
# Cap and Cup glyphs are swapped.
0x22d2: 0x22d3,
0x22d3: 0x22d2,
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/_text_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import dataclasses

from . import _api
from .ft2font import FT2Font, Kerning, LoadFlags
from .ft2font import FT2Font, GlyphIndexType, Kerning, LoadFlags


@dataclasses.dataclass(frozen=True)
class LayoutItem:
ft_object: FT2Font
char: str
glyph_idx: int
glyph_idx: GlyphIndexType
x: float
prev_kern: float

Expand Down
7 changes: 5 additions & 2 deletions lib/matplotlib/dviread.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ from collections.abc import Generator
from typing import NamedTuple
from typing_extensions import Self # < Py 3.11

from .ft2font import GlyphIndexType


class _dvistate(Enum):
pre = ...
outer = ...
Expand Down Expand Up @@ -41,9 +44,9 @@ class Text(NamedTuple):
@property
def font_effects(self) -> dict[str, float]: ...
@property
def index(self) -> int: ... # type: ignore[override]
def index(self) -> GlyphIndexType: ... # type: ignore[override]
@property
def glyph_name_or_index(self) -> int | str: ...
def glyph_name_or_index(self) -> GlyphIndexType | str: ...

class Dvi:
file: io.BufferedReader
Expand Down
23 changes: 15 additions & 8 deletions lib/matplotlib/ft2font.pyi
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
from enum import Enum, Flag
import sys
from typing import BinaryIO, Literal, TypedDict, final, overload, cast
from typing import BinaryIO, Literal, NewType, TypeAlias, TypedDict, final, overload, cast
from typing_extensions import Buffer # < Py 3.12

import numpy as np
from numpy.typing import NDArray


__freetype_build_type__: str
__freetype_version__: str

# We can't change the type hints for standard library chr/ord, so character codes are a
# simple type alias.
CharacterCodeType: TypeAlias = int
# But glyph indices are internal, so use a distinct type hint.
GlyphIndexType = NewType('GlyphIndexType', int)

class FaceFlags(Flag):
SCALABLE = cast(int, ...)
FIXED_SIZES = cast(int, ...)
Expand Down Expand Up @@ -202,13 +209,13 @@ class FT2Font(Buffer):
) -> None: ...
def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ...
def get_bitmap_offset(self) -> tuple[int, int]: ...
def get_char_index(self, codepoint: int) -> int: ...
def get_charmap(self) -> dict[int, int]: ...
def get_char_index(self, codepoint: CharacterCodeType) -> GlyphIndexType: ...
def get_charmap(self) -> dict[CharacterCodeType, GlyphIndexType]: ...
def get_descent(self) -> int: ...
def get_glyph_name(self, index: int) -> str: ...
def get_glyph_name(self, index: GlyphIndexType) -> str: ...
def get_image(self) -> NDArray[np.uint8]: ...
def get_kerning(self, left: int, right: int, mode: Kerning) -> int: ...
def get_name_index(self, name: str) -> int: ...
def get_kerning(self, left: GlyphIndexType, right: GlyphIndexType, mode: Kerning) -> int: ...
def get_name_index(self, name: str) -> GlyphIndexType: ...
def get_num_glyphs(self) -> int: ...
def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ...
def get_ps_font_info(
Expand All @@ -230,8 +237,8 @@ class FT2Font(Buffer):
@overload
def get_sfnt_table(self, name: Literal["pclt"]) -> _SfntPcltDict | None: ...
def get_width_height(self) -> tuple[int, int]: ...
def load_char(self, charcode: int, flags: LoadFlags = ...) -> Glyph: ...
def load_glyph(self, glyphindex: int, flags: LoadFlags = ...) -> Glyph: ...
def load_char(self, charcode: CharacterCodeType, flags: LoadFlags = ...) -> Glyph: ...
def load_glyph(self, glyphindex: GlyphIndexType, flags: LoadFlags = ...) -> Glyph: ...
def select_charmap(self, i: int) -> None: ...
def set_charmap(self, i: int) -> None: ...
def set_size(self, ptsize: float, dpi: float) -> None: ...
Expand Down
Loading
Loading
0