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

Skip to content

Commit 89343dc

Browse files
committed
Rasterize dvi files without dvipng.
This patch drops the reliance on dvipng to rasterize dvi files prior to inclusion by agg, instead performing the rasterization ourselves (as a consequence, the rasterization output also becomes dependent of the freetype version used). Note that this approach will be needed anyways to support xetex and luatex, as dvipng doesn't support dvi files generated by these engines. Baseline images change slightly, for the better or the worse. The top-left blue cross text in test_rotation.py ("Myrt0") seems to be better top-aligned against the blue line (the old version overshot a bit); the bounding box of the formulas in test_usetex.py seems a bit worse.
1 parent 9b4233b commit 89343dc

File tree

7 files changed

+129
-13
lines changed

7 files changed

+129
-13
lines changed

lib/matplotlib/backends/backend_agg.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"""
2323

2424
from contextlib import nullcontext
25+
import math
2526
from math import radians, cos, sin
2627

2728
import numpy as np
@@ -31,8 +32,9 @@
3132
from matplotlib import _api, cbook
3233
from matplotlib.backend_bases import (
3334
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
35+
from matplotlib.dviread import Dvi
3436
from matplotlib.font_manager import fontManager as _fontManager, get_font
35-
from matplotlib.ft2font import LoadFlags
37+
from matplotlib.ft2font import FT2Image, LoadFlags
3638
from matplotlib.mathtext import MathTextParser
3739
from matplotlib.path import Path
3840
from matplotlib.transforms import Bbox, BboxBase
@@ -209,7 +211,8 @@ def get_text_width_height_descent(self, s, prop, ismath):
209211

210212
_api.check_in_list(["TeX", True, False], ismath=ismath)
211213
if ismath == "TeX":
212-
return super().get_text_width_height_descent(s, prop, ismath)
214+
return [*map(
215+
math.ceil, super().get_text_width_height_descent(s, prop, ismath))]
213216

214217
if ismath:
215218
ox, oy, width, height, descent, font_image = \
@@ -228,19 +231,45 @@ def get_text_width_height_descent(self, s, prop, ismath):
228231
def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
229232
# docstring inherited
230233
# todo, handle props, angle, origins
231-
size = prop.get_size_in_points()
232-
233-
texmanager = self.get_texmanager()
234234

235-
Z = texmanager.get_grey(s, size, self.dpi)
236-
Z = np.array(Z * 255.0, np.uint8)
235+
size = prop.get_size_in_points()
236+
dvifile = self.get_texmanager().make_dvi(s, size)
237+
with Dvi(dvifile, self.dpi) as dvi:
238+
page, = dvi
239+
w = math.ceil(page.width)
240+
h = math.ceil(page.height)
241+
d = math.ceil(page.descent)
242+
243+
image = np.zeros((h + d, w), np.uint8)
244+
245+
for text in page.text:
246+
hf = mpl.rcParams["text.hinting_factor"]
247+
font = get_font(text.font_path)
248+
font.set_size(text.font_size, self.dpi)
249+
slant = text.font_effects.get("slant", 0)
250+
extend = text.font_effects.get("extend", 1)
251+
matrix = [
252+
[round(65536 * extend / hf), round(65536 * extend * slant)],
253+
[0, 65536],
254+
]
255+
font._set_transform(matrix, [0, 0])
256+
glyph = font.load_glyph(text.index)
257+
# text.y is upwards from baseline, _draw_glyph_at wants upwards from bottom.
258+
font._draw_glyph_at(image, text.x, d + text.y, glyph,
259+
antialiased=gc.get_antialiased())
260+
261+
for box in page.boxes:
262+
x0 = round(box.x)
263+
x1 = x0 + max(round(box.width), 1)
264+
y1 = round(h - box.y)
265+
y0 = y1 - max(round(box.height), 1)
266+
image[y0:y1, x0:x1] = 0xff
237267

238-
w, h, d = self.get_text_width_height_descent(s, prop, ismath="TeX")
239268
xd = d * sin(radians(angle))
240269
yd = d * cos(radians(angle))
241270
x = round(x + xd)
242271
y = round(y + yd)
243-
self._renderer.draw_text_image(Z, x, y, angle, gc)
272+
self._renderer.draw_text_image(image, x, y, angle, gc)
244273

245274
def get_canvas_width_height(self):
246275
# docstring inherited
603 Bytes
Loading
3.59 KB
Loading
2.19 KB
Loading

src/ft2font.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,17 @@ void FT2Font::set_size(double ptsize, double dpi)
274274
}
275275
}
276276

277+
void FT2Font::set_transform(
278+
std::array<std::array<FT_Fixed, 2>, 2> matrix, std::array<FT_Fixed, 2> delta)
279+
{
280+
FT_Matrix m = {matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1]};
281+
FT_Vector d = {delta[0], delta[1]};
282+
FT_Set_Transform(face, &m, &d);
283+
for (auto & fallback : fallbacks) {
284+
fallback->set_transform(matrix, delta);
285+
}
286+
}
287+
277288
void FT2Font::set_charmap(int i)
278289
{
279290
if (i >= face->num_charmaps) {
@@ -644,6 +655,27 @@ void FT2Font::draw_glyph_to_bitmap(
644655
draw_bitmap(im, &bitmap->bitmap, x + bitmap->left, y);
645656
}
646657

658+
void FT2Font::draw_glyph_at(
659+
py::array_t<uint8_t, py::array::c_style> im,
660+
double x, double y, size_t glyphInd, bool antialiased)
661+
{
662+
if (glyphInd >= glyphs.size()) {
663+
throw std::runtime_error("glyph num is out of range");
664+
}
665+
FT_Vector sub_offset = {FT_Fixed(x * 64 + .5), FT_Fixed(y * 64 + .5)};
666+
FT_Error error = FT_Glyph_To_Bitmap(
667+
&glyphs[glyphInd],
668+
antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO,
669+
&sub_offset, // additional translation
670+
1 // destroy image
671+
);
672+
if (error) {
673+
throw_ft_error("Could not convert glyph to bitmap", error);
674+
}
675+
FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[glyphInd];
676+
draw_bitmap(im, &bitmap->bitmap, bitmap->left, im.shape(0) - bitmap->top);
677+
}
678+
647679
void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer,
648680
bool fallback = false)
649681
{

src/ft2font.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ class FT2Font
105105
virtual ~FT2Font();
106106
void clear();
107107
void set_size(double ptsize, double dpi);
108+
void set_transform(
109+
std::array<std::array<FT_Fixed, 2>, 2> matrix, std::array<FT_Fixed, 2> delta);
108110
void set_charmap(int i);
109111
void select_charmap(unsigned long i);
110112
void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
@@ -133,6 +135,9 @@ class FT2Font
133135
void draw_glyph_to_bitmap(
134136
py::array_t<uint8_t, py::array::c_style> im,
135137
int x, int y, size_t glyphInd, bool antialiased);
138+
void draw_glyph_at(
139+
py::array_t<uint8_t, py::array::c_style> im,
140+
double x, double y, size_t glyphInd, bool antialiased);
136141
void get_glyph_name(unsigned int glyph_number, std::string &buffer, bool fallback);
137142
long get_name_index(char *name);
138143
FT_UInt get_char_index(FT_ULong charcode, bool fallback);

src/ft2font_wrapper.cpp

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,25 @@ PyFT2Font_set_size(PyFT2Font *self, double ptsize, double dpi)
541541
self->x->set_size(ptsize, dpi);
542542
}
543543

544+
const char *PyFT2Font__set_transform__doc__ = R"""(
545+
Set the transform of the text.
546+
547+
This is a low-level function directly taking inputs in 26.6 format. Refer
548+
to the FreeType docs of FT_Set_Transform for further description.
549+
550+
Parameters
551+
----------
552+
matrix : (2, 2) array of int
553+
delta : (2,) array of int
554+
)""";
555+
556+
static void
557+
PyFT2Font__set_transform(
558+
PyFT2Font *self, std::array<std::array<FT_Fixed, 2>, 2> matrix, std::array<FT_Fixed, 2> delta)
559+
{
560+
self->x->set_transform(matrix, delta);
561+
}
562+
544563
const char *PyFT2Font_set_charmap__doc__ = R"""(
545564
Make the i-th charmap current.
546565
@@ -971,7 +990,7 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(
971990
image : 2d array of uint8
972991
The image buffer on which to draw the glyph.
973992
x, y : int
974-
The pixel location at which to draw the glyph.
993+
The position of the glyph's top left corner.
975994
glyph : Glyph
976995
The glyph to draw.
977996
antialiased : bool, default: True
@@ -990,9 +1009,36 @@ PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image,
9901009
auto xd = _double_to_<int>("x", vxd);
9911010
auto yd = _double_to_<int>("y", vyd);
9921011

993-
self->x->draw_glyph_to_bitmap(
994-
py::array_t<uint8_t, py::array::c_style>{image},
995-
xd, yd, glyph->glyphInd, antialiased);
1012+
self->x->draw_glyph_to_bitmap(image, xd, yd, glyph->glyphInd, antialiased);
1013+
}
1014+
1015+
const char *PyFT2Font__draw_glyph_at__doc__ = R"""(
1016+
Draw a single glyph to the bitmap at pixel locations x, y.
1017+
1018+
Parameters
1019+
----------
1020+
image : FT2Image
1021+
The image buffer on which to draw the glyph. If the buffer is too
1022+
small, the glyph will be cropped.
1023+
x, y : float
1024+
The position of the glyph's origin.
1025+
glyph : Glyph
1026+
The glyph to draw.
1027+
antialiased : bool, default: True
1028+
Whether to render glyphs 8-bit antialiased or in pure black-and-white.
1029+
1030+
See Also
1031+
--------
1032+
.draw_glyphs_to_bitmap
1033+
)""";
1034+
1035+
static void
1036+
PyFT2Font__draw_glyph_at(
1037+
PyFT2Font *self,
1038+
py::buffer &image, double x, double y, PyGlyph *glyph,
1039+
bool antialiased = true)
1040+
{
1041+
self->x->draw_glyph_at(image, x, y, glyph->glyphInd, antialiased);
9961042
}
9971043

9981044
const char *PyFT2Font_get_glyph_name__doc__ = R"""(
@@ -1615,6 +1661,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16151661
.def("clear", &PyFT2Font_clear, PyFT2Font_clear__doc__)
16161662
.def("set_size", &PyFT2Font_set_size, "ptsize"_a, "dpi"_a,
16171663
PyFT2Font_set_size__doc__)
1664+
.def("_set_transform", &PyFT2Font__set_transform, "matrix"_a, "delta"_a)
16181665
.def("set_charmap", &PyFT2Font_set_charmap, "i"_a,
16191666
PyFT2Font_set_charmap__doc__)
16201667
.def("select_charmap", &PyFT2Font_select_charmap, "i"_a,
@@ -1654,6 +1701,9 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16541701
PyFT2Font_draw_glyph_to_bitmap__doc__);
16551702
}
16561703
cls
1704+
.def("_draw_glyph_at", &PyFT2Font__draw_glyph_at,
1705+
"image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true,
1706+
PyFT2Font__draw_glyph_at__doc__)
16571707
.def("get_glyph_name", &PyFT2Font_get_glyph_name, "index"_a,
16581708
PyFT2Font_get_glyph_name__doc__)
16591709
.def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__)

0 commit comments

Comments
 (0)