10000 Merge pull request #27891 from QuLogic/ft2font-cleanup · matplotlib/matplotlib@3eda5a3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3eda5a3

Browse files
authored
Merge pull request #27891 from QuLogic/ft2font-cleanup
Refactor some parts of ft2font extension
2 parents aa70f69 + 276fade commit 3eda5a3

File tree

6 files changed

+149
-261
lines changed

6 files changed

+149
-261
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ft2font classes are now final
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The ft2font classes `.ft2font.FT2Font`, and `.ft2font.FT2Image` are now final
5+
and can no longer be subclassed.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``ft2font.FT2Image.draw_rect`` and ``ft2font.FT2Font.get_xys``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
... have been removed as they are unused.

lib/matplotlib/ft2font.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import BinaryIO, Literal, TypedDict, overload
1+
from typing import BinaryIO, Literal, TypedDict, final, overload
22

33
import numpy as np
44
from numpy.typing import NDArray
@@ -158,6 +158,7 @@ class _SfntPcltDict(TypedDict):
158158
widthType: int
159159
serifStyle: int
160160

161+
@final
161162
class FT2Font:
162163
ascender: int
163164
bbox: tuple[int, int, int, int]
@@ -223,7 +224,6 @@ class FT2Font:
223224
@overload
224225
def get_sfnt_table(self, name: Literal["pclt"]) -> _SfntPcltDict | None: ...
225226
def get_width_height(self) -> tuple[int, int]: ...
226-
def get_xys(self, antialiased: bool = ...) -> NDArray[np.float64]: ...
227227
def load_char(self, charcode: int, flags: int = ...) -> Glyph: ...
228228
def load_glyph(self, glyphindex: int, flags: int = ...) -> Glyph: ...
229229
def select_charmap(self, i: int) -> None: ...
@@ -233,11 +233,12 @@ class FT2Font:
233233
self, string: str, angle: float = ..., flags: int = ...
234234
) -> NDArray[np.float64]: ...
235235

236+
@final
236237
class FT2Image: # TODO: When updating mypy>=1.4, subclass from Buffer.
237238
def __init__(self, width: float, height: float) -> None: ...
238-
def draw_rect(self, x0: float, y0: float, x1: float, y1: float) -> None: ...
239239
def draw_rect_filled(self, x0: float, y0: float, x1: float, y1: float) -> None: ...
240240

241+
@final
241242
class Glyph:
242243
width: int
243244
height: int

src/ft2font.cpp

Lines changed: 69 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
/* -*- mode: c++; c-basic-offset: 4 -*- */
22

3-
#define NO_IMPORT_ARRAY
4-
53
#include <algorithm>
4+
#include <charconv>
65
#include <iterator>
76
#include <set>
87
#include <sstream>
98
#include <stdexcept>
109
#include <string>
10+
#include <vector>
1111

1212
#include "ft2font.h"
1313
#include "mplutils.h"
14-
#include "numpy_cpp.h"
15-
#include "py_exceptions.h"
1614

1715
#ifndef M_PI
1816
#define M_PI 3.14159265358979323846264338328
@@ -65,12 +63,12 @@ void throw_ft_error(std::string message, FT_Error error) {
6563
throw std::runtime_error(os.str());
6664
}
6765

68-
FT2Image::FT2Image() : m_dirty(true), m_buffer(NULL), m_width(0), m_height(0)
66+
FT2Image::FT2Image() : m_buffer(NULL), m_width(0), m_height(0)
6967
{
7068
}
7169

7270
FT2Image::FT2Image(unsigned long width, unsigned long height)
73-
: m_dirty(true), m_buffer(NULL), m_width(0), m_height(0)
71+
: m_buffer(NULL), m_width(0), m_height(0)
7472
{
7573
resize(width, height);
7674
}
@@ -104,8 +102,6 @@ void FT2Image::resize(long width, long height)
104102
if (numBytes && m_buffer) {
105103
memset(m_buffer, 0, numBytes);
106104
}
107-
108-
m_dirty = true;
109105
}
110106

111107
void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y)
@@ -143,29 +139,6 @@ void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int y)
143139
} else {
144140
throw std::runtime_error("Unknown pixel mode");
145141
}
146-
147-
m_dirty = true;
148-
}
149-
150-
void FT2Image::draw_rect(unsigned long x0, unsigned long y0, unsigned long x1, unsigned long y1)
151-
{
152-
if (x0 > m_width || x1 > m_width || y0 > m_height || y1 > m_height) {
153-
throw std::runtime_error("Rect coords outside image bounds");
154-
}
155-
156-
size_t top = y0 * m_width;
157-
size_t bottom = y1 * m_width;
158-
for (size_t i = x0; i < x1 + 1; ++i) {
159-
m_buffer[i + top] = 255;
160-
m_buffer[i + bottom] = 255;
161-
}
162-
163-
for (size_t j = y0 + 1; j < y1; ++j) {
164-
m_buffer[x0 + j * m_width] = 255;
165-
m_buffer[x1 + j * m_width] = 255;
166-
}
167-
168-
m_dirty = true;
169142
}
170143

171144
void
@@ -181,92 +154,51 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1,
181154
m_buffer[i + j * m_width] = 255;
182155
}
183156
}
184-
185-
m_dirty = true;
186-
}
187-
188-
static void ft_glyph_warn(FT_ULong charcode, std::set<FT_String*> family_names)
189-
{
190-
PyObject *text_helpers = NULL, *tmp = NULL;
191-
std::set<FT_String*>::iterator it = family_names.begin();
192-
std::stringstream ss;
193-
ss<<*it;
194-
while(++it != family_names.end()){
195-
ss<<", "<<*it;
196-
}
197-
198-
if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) ||
199-
!(tmp = PyObject_CallMethod(text_helpers,
200-
"warn_on_missing_glyph", "(k, s)",
201-
charcode, ss.str().c_str()))) {
202-
goto exit;
203-
}
204-
exit:
205-
Py_XDECREF(text_helpers);
206-
Py_XDECREF(tmp);
207-
if (PyErr_Occurred()) {
208-
throw mpl::exception();
209-
}
210157
}
211158

212-
// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the
213-
// first pass, vertices and codes are set to NULL, and index is simply
214-
// incremented for each vertex that should be inserted, so that it is set, at
215-
// the end, to the total number of vertices. On a second pass, vertices and
216-
// codes should point to correctly sized arrays, and index set again to zero,
217-
// to get fill vertices and codes with the outline decomposition.
159+
// ft_outline_decomposer should be passed to FT_Outline_Decompose.
218160
struct ft_outline_decomposer
219161
{
220-
int index;
221-
double* vertices;
222-
unsigned char* codes;
162+
std::vector<double> &vertices;
163+
std::vector<unsigned char> &codes;
223164
};
224165

225166
static int
226167
ft_outline_move_to(FT_Vector const* to, void* user)
227168
{
228169
ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
229-
if (d->codes) {
230-
if (d->index) {
231-
// Appending CLOSEPOLY is important to make patheffects work.
232-
*(d->vertices++) = 0;
233-
*(d->vertices++) = 0;
234-
*(d->codes++) = CLOSEPOLY;
235-
}
236-
*(d->vertices++) = to->x * (1. / 64.);
237-
*(d->vertices++) = to->y * (1. / 64.);
238-
*(d->codes++) = MOVETO;
239-
}
240-
d->index += d->index ? 2 : 1;
170+
if (!d->vertices.empty()) {
171+
// Appending CLOSEPOLY is important to make patheffects work.
172+
d->vertices.push_back(0);
173+
d->vertices.push_back(0);
174+
d->codes.push_back(CLOSEPOLY);
175+
}
176+
d->vertices.push_back(to->x * (1. / 64.));
177+
d->vertices.push_back(to->y * (1. / 64.));
178+
d->codes.push_back(MOVETO);
241179
return 0;
242180
}
243181

244182
static int
245183
ft_outline_line_to(FT_Vector const* to, void* user)
246184
{
247185
ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
248-
if (d->codes) {
249-
*(d->vertices++) = to->x * (1. / 64.);
250-
*(d->vertices++) = to->y * (1. / 64.);
251-
*(d->codes++) = LINETO;
252-
}
253-
d->index++;
186+
d->vertices.push_back(to->x * (1. / 64.));
187+
d->vertices.push_back(to->y * (1. / 64.));
188+
d->codes.push_back(LINETO);
254189
return 0;
255190
}
256191

257192
static int
258193
ft_outline_conic_to(FT_Vector const* control, FT_Vector const* to, void* user)
259194
{
260195
ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
261-
if (d->codes) {
262-
*(d->vertices++) = control->x * (1. / 64.);
263-
*(d->vertices++) = control->y * (1. / 64.);
264-
*(d->vertices++) = to->x * (1. / 64.);
265-
*(d->vertices++) = to->y * (1. / 64.);
266-
*(d->codes++) = CURVE3;
267-
*(d->codes++) = CURVE3;
268-
}
269-
d->index += 2;
196+
d->vertices.push_back(control->x * (1. / 64.));
197+
d->vertices.push_back(control->y * (1. / 64.));
198+
d->vertices.push_back(to->x * (1. / 64.));
199+
d->vertices.push_back(to->y * (1. / 64.));
200+
d->codes.push_back(CURVE3);
201+
d->codes.push_back(CURVE3);
270202
return 0;
271203
}
272204

@@ -275,18 +207,15 @@ ft_outline_cubic_to(
275207
FT_Vector const* c1, FT_Vector const* c2, FT_Vector const* to, void* user)
276208
{
277209
ft_outline_decomposer* d = reinterpret_cast<ft_outline_decomposer*>(user);
278-
if (d->codes) {
279-
*(d->vertices++) = c1->x * (1. / 64.);
280-
*(d->vertices++) = c1->y * (1. / 64.);
281-
*(d->vertices++) = c2->x * (1. / 64.);
282-
*(d->vertices++) = c2->y * (1. / 64.);
283-
*(d->vertices++) = to->x * (1. / 64.);
284-
*(d->vertices++) = to->y * (1. / 64.);
285-
*(d->codes++) = CURVE4;
286-
*(d->codes++) = CURVE4;
287-
*(d->codes++) = CURVE4;
288-
}
289-
d->index += 3;
210+
d->vertices.push_back(c1->x * (1. / 64.));
211+
d->vertices.push_back(c1->y * (1. / 64.));
212+
d->vertices.push_back(c2->x * (1. / 64.));
213+
d->vertices.push_back(c2->y * (1. / 64.));
214+
d->vertices.push_back(to->x * (1. / 64.));
215+
d->vertices.push_back(to->y * (1. / 64.));
216+
d->codes.push_back(CURVE4);
217+
d->codes.push_back(CURVE4);
218+
d->codes.push_back(CURVE4);
290219
return 0;
291220
}
292221

@@ -296,52 +225,41 @@ static FT_Outline_Funcs ft_outline_funcs = {
296225
ft_outline_conic_to,
297226
ft_outline_cubic_to};
298227

299-
PyObject*
300-
FT2Font::get_path()
228+
void
229+
FT2Font::get_path(std::vector<double> &vertices, std::vector<unsigned char> &codes)
301230
{
302231
if (!face->glyph) {
303-
PyErr_SetString(PyExc_RuntimeError, "No glyph loaded");
304-
return NULL;
305-
}
306-
ft_outline_decomposer decomposer = {};
307-
if (FT_Error error =
308-
FT_Outline_Decompose(
309-
&face->glyph->outline, &ft_outline_funcs, &decomposer)) {
310-
PyErr_Format(PyExc_RuntimeError,
311-
"FT_Outline_Decompose failed with error 0x%x", error);
312-
return NULL;
313-
}
314-
if (!decomposer.index) { // Don't append CLOSEPOLY to null glyphs.
315-
npy_intp vertices_dims[2] = { 0, 2 };
316-
numpy::array_view<double, 2> vertices(vertices_dims);
317-
npy_intp codes_dims[1] = { 0 };
318-
numpy::array_view<unsigned char, 1> codes(codes_dims);
319-
return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj());
320-
}
321-
npy_intp vertices_dims[2] = { decomposer.index + 1, 2 };
322-
numpy::array_view<double, 2> vertices(vertices_dims);
323-
npy_intp codes_dims[1] = { decomposer.index + 1 };
324-
numpy::array_view<unsigned char, 1> codes(codes_dims);
325-
decomposer.index = 0;
326-
decomposer.vertices = vertices.data();
327-
decomposer.codes = codes.data();
328-
if (FT_Error error =
329-
FT_Outline_Decompose(
330-
&face->glyph->outline, &ft_outline_funcs, &decomposer)) {
331-
PyErr_Format(PyExc_RuntimeError,
332-
"FT_Outline_Decompose failed with error 0x%x", error);
333-
return NULL;
334-
}
335-
*(decomposer.vertices++) = 0;
336-
*(decomposer.vertices++) = 0;
337-
*(decomposer.codes++) = CLOSEPOLY;
338-
return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj());
232+
throw std::runtime_error("No glyph loaded");
233+
}
234+
ft_outline_decomposer decomposer = {
235+
vertices,
236+
codes,
237+
};
238+
// We can make a close-enough estimate based on number of points and number of
239+
// contours (which produce a MOVETO each), though it's slightly underestimating due
240+
// to higher-order curves.
241+
size_t estimated_points = static_cast<size_t>(face->glyph->outline.n_contours) +
242+
static_cast<size_t>(face->glyph->outline.n_points);
243+
vertices.reserve(2 * estimated_points);
244+
codes.reserve(estimated_points);
245+
if (FT_Error error = FT_Outline_Decompose(
246+
&face->glyph->outline, &ft_outline_funcs, &decomposer)) {
247+
throw std::runtime_error("FT_Outline_Decompose failed with error " +
248+
std::to_string(error));
249+
}
250+
if (vertices.empty()) { // Don't append CLOSEPOLY to null glyphs.
251+
return;
252+
}
253+
vertices.push_back(0);
254+
vertices.push_back(0);
255+
codes.push_back(CLOSEPOLY);
339256
}
340257

341258
FT2Font::FT2Font(FT_Open_Args &open_args,
342259
long hinting_factor_,
343-
std::vector<FT2Font *> &fallback_list)
344-
: image(), face(NULL)
260+
std::vector<FT2Font *> &fallback_list,
261+
FT2Font::WarnFunc warn)
262+
: ft_glyph_warn(warn), image(), face(NULL)
345263
{
346264
clear();
347265

@@ -771,29 +689,6 @@ void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
771689
}
772690
}
773691

774-
void FT2Font::get_xys(bool antialiased, std::vector<double> &xys)
775-
{
776-
for (size_t n = 0; n < glyphs.size(); n++) {
777-
778-
FT_Error error = FT_Glyph_To_Bitmap(
779-
&glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
780-
if (error) {
781-
throw_ft_error("Could not convert glyph to bitmap", error);
782-
}
783-
784-
FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n];
785-
786-
// bitmap left and top in pixel, string bbox in subpixel
787-
FT_Int x = (FT_Int)(bitmap->left - bbox.xMin * (1. / 64.));
788-
FT_Int y = (FT_Int)(bbox.yMax * (1. / 64.) - bitmap->top + 1);
789-
// make sure the index is non-neg
790-
x = x < 0 ? 0 : x;
791-
y = y < 0 ? 0 : y;
792-
xys.push_back(x);
793-
xys.push_back(y);
794-
}
795-
}
796-
797692
void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased)
798693
{
799694
FT_Vector sub_offset;
@@ -819,7 +714,8 @@ void FT2Font::draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd,
819714
im.draw_bitmap(&bitmap->bitmap, x + bitmap->left, y);
820715
}
821716

822-
void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer, bool fallback = false)
717+
void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer,
718+
bool fallback = false)
823719
{
824720
if (fallback && glyph_to_font.find(glyph_number) != glyph_to_font.end()) {
825721
// cache is only for parent FT2Font
@@ -830,9 +726,11 @@ void FT2Font::get_glyph_name(unsigned int glyph_number, char *buffer, bool fallb
830726
if (!FT_HAS_GLYPH_NAMES(face)) {
831727
/* Note that this generated name must match the name that
832728
is generated by ttconv in ttfont_CharStrings_getname. */
833-
PyOS_snprintf(buffer, 128, "uni%08x", glyph_number);
729+
buffer.replace(0, 3, "uni");
730+
std::to_chars(buffer.data() + 3, buffer.data() + buffer.size(),
731+
glyph_number, 16);
834732
} else {
835-
if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer, 128)) {
733+
if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer.data(), buffer.size())) {
836734
throw_ft_error("Could not get glyph names", error);
837735
}
838736
}

0 commit comments

Comments
 (0)
0