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

Skip to content
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
45 changes: 37 additions & 8 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"""

from contextlib import nullcontext
import math
from math import radians, cos, sin

import numpy as np
Expand All @@ -31,6 +32,7 @@
from matplotlib import _api, cbook
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
from matplotlib.dviread import Dvi
from matplotlib.font_manager import fontManager as _fontManager, get_font
from matplotlib.ft2font import LoadFlags
from matplotlib.mathtext import MathTextParser
Expand Down Expand Up @@ -219,7 +221,8 @@ def get_text_width_height_descent(self, s, prop, ismath):

_api.check_in_list(["TeX", True, False], ismath=ismath)
if ismath == "TeX":
return super().get_text_width_height_descent(s, prop, ismath)
return [*map(
math.ceil, super().get_text_width_height_descent(s, prop, ismath))]

if ismath:
ox, oy, width, height, descent, font_image = \
Expand All @@ -238,19 +241,45 @@ def get_text_width_height_descent(self, s, prop, ismath):
def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
# docstring inherited
# todo, handle props, angle, origins
size = prop.get_size_in_points()

texmanager = self.get_texmanager()

Z = texmanager.get_grey(s, size, self.dpi)
Z = np.array(Z * 255.0, np.uint8)
size = prop.get_size_in_points()
dvifile = self.get_texmanager().make_dvi(s, size)
with Dvi(dvifile, self.dpi) as dvi:
page, = dvi
w = math.ceil(page.width)
h = math.ceil(page.height)
d = math.ceil(page.descent)

image = np.zeros((h + d, w), np.uint8)

for text in page.text:
hf = mpl.rcParams["text.hinting_factor"]
font = get_font(text.font_path)
font.set_size(text.font_size, self.dpi)
slant = text.font_effects.get("slant", 0)
extend = text.font_effects.get("extend", 1)
matrix = [
[round(65536 * extend / hf), round(65536 * extend * slant)],
[0, 65536],
]
font._set_transform(matrix, [0, 0])
glyph = font.load_glyph(text.index)
# text.y is upwards from baseline, _draw_glyph_at wants upwards from bottom.
font._draw_glyph_at(image, text.x, d + text.y, glyph,
antialiased=gc.get_antialiased())

for box in page.boxes:
x0 = round(box.x)
x1 = x0 + max(round(box.width), 1)
y1 = round(h - box.y)
y0 = y1 - max(round(box.height), 1)
image[y0:y1, x0:x1] = 0xff

w, h, d = self.get_text_width_height_descent(s, prop, ismath="TeX")
xd = d * sin(radians(angle))
yd = d * cos(radians(angle))
x = round(x + xd)
y = round(y + yd)
self._renderer.draw_text_image(Z, x, y, angle, gc)
self._renderer.draw_text_image(image, x, y, angle, gc)

def get_canvas_width_height(self):
# docstring inherited
Expand Down
Binary file modified lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_usetex/rotation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
834 changes: 417 additions & 417 deletions lib/matplotlib/tests/baseline_images/test_usetex/rotation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_usetex/test_usetex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/ft2font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,17 @@ void FT2Font::set_size(double ptsize, double dpi)
}
}

void FT2Font::_set_transform(
std::array<std::array<FT_Fixed, 2>, 2> matrix, std::array<FT_Fixed, 2> delta)
{
FT_Matrix m = {matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1]};
FT_Vector d = {delta[0], delta[1]};
FT_Set_Transform(face, &m, &d);
for (auto & fallback : fallbacks) {
fallback->_set_transform(matrix, delta);
}
}

void FT2Font::set_charmap(int i)
{
if (i >= face->num_charmaps) {
Expand Down Expand Up @@ -696,6 +707,24 @@ void FT2Font::draw_glyph_to_bitmap(
draw_bitmap(im, &bitmap->bitmap, x + bitmap->left, y);
}

void FT2Font::_draw_glyph_at(
py::array_t<uint8_t, py::array::c_style> im,
double x, double y, size_t glyphInd, bool antialiased)
{
if (glyphInd >= glyphs.size()) {
throw std::runtime_error("glyph num is out of range");
}
FT_Vector sub_offset = {FT_Fixed(x * 64 + .5), FT_Fixed(y * 64 + .5)};
FT_CHECK(
FT_Glyph_To_Bitmap,
&glyphs[glyphInd],
antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO,
&sub_offset, // additional translation
1); // destroy image
FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd];
draw_bitmap(im, &bitmap->bitmap, bitmap->left, im.shape(0) - bitmap->top);
}

std::string FT2Font::get_glyph_name(unsigned int glyph_number)
{
std::string buffer;
Expand Down
5 changes: 5 additions & 0 deletions src/ft2font.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class FT2Font
void close();
void clear();
void set_size(double ptsize, double dpi);
void _set_transform(
std::array<std::array<FT_Fixed, 2>, 2> matrix, std::array<FT_Fixed, 2> delta);
void set_charmap(int i);
void select_charmap(unsigned long i);
void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
Expand All @@ -132,6 +134,9 @@ class FT2Font
void draw_glyph_to_bitmap(
py::array_t<uint8_t, py::array::c_style> im,
int x, int y, size_t glyphInd, bool antialiased);
void _draw_glyph_at(
py::array_t<uint8_t, py::array::c_style> im,
double x, double y, size_t glyphInd, bool antialiased);
std::string get_glyph_name(unsigned int glyph_number);
long get_name_index(char *name);
FT_UInt get_char_index(FT_ULong charcode, bool fallback);
Expand Down
48 changes: 47 additions & 1 deletion src/ft2font_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,18 @@ const char *PyFT2Font_set_size__doc__ = R"""(
The DPI used for rendering the text.
)""";

const char *PyFT2Font__set_transform__doc__ = R"""(
Set the transform of the text.

This is a low-level function directly taking inputs in 26.6 format. Refer
to the FreeType docs of FT_Set_Transform for further description.

Parameters
----------
matrix : (2, 2) array of int
delta : (2,) array of int
)""";

const char *PyFT2Font_set_charmap__doc__ = R"""(
Make the i-th charmap current.

Expand Down Expand Up @@ -912,7 +924,7 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(
image : 2d array of uint8
The image buffer on which to draw the glyph.
x, y : int
The pixel location at which to draw the glyph.
The position of the glyph's top left corner.
glyph : Glyph
The glyph to draw.
antialiased : bool, default: True
Expand All @@ -936,6 +948,36 @@ PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image,
xd, yd, glyph->glyphInd, antialiased);
}

const char *PyFT2Font__draw_glyph_at__doc__ = R"""(
Draw a single glyph to the bitmap at pixel locations x, y.

Parameters
----------
image : FT2Image
The image buffer on which to draw the glyph. If the buffer is too
small, the glyph will be cropped.
x, y : float
The position of the glyph's origin.
glyph : Glyph
The glyph to draw.
antialiased : bool, default: True
Whether to render glyphs 8-bit antialiased or in pure black-and-white.

See Also
--------
.draw_glyphs_to_bitmap
)""";

static void
PyFT2Font__draw_glyph_at(PyFT2Font *self, py::buffer &image,
double x, double y,
PyGlyph *glyph, bool antialiased = true)
{
self->_draw_glyph_at(
py::array_t<uint8_t, py::array::c_style>{image},
x, y, glyph->glyphInd, antialiased);
}

const char *PyFT2Font_get_glyph_name__doc__ = R"""(
Retrieve the ASCII name of a given glyph *index* in a face.

Expand Down Expand Up @@ -1527,6 +1569,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
.def("clear", &PyFT2Font::clear, PyFT2Font_clear__doc__)
.def("set_size", &PyFT2Font::set_size, "ptsize"_a, "dpi"_a,
PyFT2Font_set_size__doc__)
.def("_set_transform", &PyFT2Font::_set_transform, "matrix"_a, "delta"_a)
.def("set_charmap", &PyFT2Font::set_charmap, "i"_a,
PyFT2Font_set_charmap__doc__)
.def("select_charmap", &PyFT2Font::select_charmap, "i"_a,
Expand Down Expand Up @@ -1567,6 +1610,9 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
PyFT2Font_draw_glyph_to_bitmap__doc__);
}
cls
.def("_draw_glyph_at", &PyFT2Font__draw_glyph_at,
"image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true,
PyFT2Font__draw_glyph_at__doc__)
Comment on lines +1613 to +1615
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this correlate with #30059?

Copy link
Contributor Author

@anntzer anntzer Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed redundant with the _render_glyph method of #30059; when both PRs are in I'll rewrite this in terms of the #30059 API.
However #30059 seems a bit further away from merging, both because of the API design choices at #30059 (comment), and because of the maybe-off-by-1 positioning issues mentioned at #30059 (comment) (actually I haven't revisited that at all since then, so that may need a bit of re-checking). So if this goes in first I think _draw_glyph_at is fine as temporary API.

.def("get_glyph_name", &PyFT2Font::get_glyph_name, "index"_a,
PyFT2Font_get_glyph_name__doc__)
.def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__)
Expand Down
Loading