diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index e8e8ece1310c..bdd55eeeb8b4 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -42,7 +42,6 @@ from matplotlib.path import Path from matplotlib.dates import UTC from matplotlib import _path -from matplotlib import _ttconv from . import _backend_pdf_ps _log = logging.getLogger(__name__) @@ -556,6 +555,52 @@ def _flush(self): self.compressobj = None +def _get_pdf_charprocs(font_path, glyph_ids): + font = get_font(font_path, hinting_factor=1) + conv = 1000 / font.units_per_EM # Conversion to PS units (1/1000's). + procs = {} + for glyph_id in glyph_ids: + g = font.load_glyph(glyph_id, LOAD_NO_SCALE) + # NOTE: We should be using round(), but instead use + # "(x+.5).astype(int)" to keep backcompat with the old ttconv code + # (this is different for negative x's). + d1 = (np.array([g.horiAdvance, 0, *g.bbox]) * conv + .5).astype(int) + v, c = font.get_path() + v = (v * 64).astype(int) # Back to TrueType's internal units (1/64's). + # Backcompat with old ttconv code: control points between two quads are + # omitted if they are exactly at the midpoint between the control of + # the quad before and the quad after, but ttconv used to interpolate + # *after* conversion to PS units, causing floating point errors. Here + # we reproduce ttconv's logic, detecting these "implicit" points and + # re-interpolating them. Note that occasionally (e.g. with DejaVu Sans + # glyph "0") a point detected as "implicit" is actually explicit, and + # will thus be shifted by 1. + quads, = np.nonzero(c == 3) + quads_on = quads[1::2] + quads_mid_on = np.array( + sorted({*quads_on} & {*(quads - 1)} & {*(quads + 1)}), int) + implicit = quads_mid_on[ + (v[quads_mid_on] # As above, use astype(int), not // division + == ((v[quads_mid_on - 1] + v[quads_mid_on + 1]) / 2).astype(int)) + .all(axis=1)] + if (font.postscript_name, glyph_id) in [ + ("DejaVuSerif-Italic", 77), # j + ("DejaVuSerif-Italic", 135), # \AA + ]: + v[:, 0] -= 1 # Hard-coded backcompat (FreeType shifts glyph by 1). + v = (v * conv + .5).astype(int) # As above re: truncation vs rounding. + v[implicit] = (( # Fix implicit points; again, truncate. + (v[implicit - 1] + v[implicit + 1]) / 2).astype(int)) + procs[font.get_glyph_name(glyph_id)] = ( + " ".join(map(str, d1)).encode("ascii") + b" d1\n" + + _path.convert_to_string( + Path(v, c), None, None, False, None, -1, + # no code for quad Beziers triggers auto-conversion to cubics. + [b"m", b"l", b"", b"c", b"h"], True) + + b"f") + return procs + + class PdfFile: """PDF file object.""" @@ -1089,15 +1134,8 @@ def get_char_width(charcode): differencesArray.append(Name(name)) last_c = c - # Make the charprocs array (using ttconv to generate the - # actual outlines) - try: - rawcharprocs = _ttconv.get_pdf_charprocs( - os.fsencode(filename), glyph_ids) - except RuntimeError: - _log.warning("The PDF backend does not currently support the " - "selected font.") - raise + # Make the charprocs array. + rawcharprocs = _get_pdf_charprocs(filename, glyph_ids) charprocs = {} for charname in sorted(rawcharprocs): stream = rawcharprocs[charname] diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.pdf b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.pdf deleted file mode 100644 index c1763f68be19..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_69.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_69.svg index 7a63312d15d4..21c6e291cdbd 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_69.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_69.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:33.473355 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,124 +30,128 @@ z - - + + - + - + - - - - - +" id="STIXGeneral-Italic-c5" transform="scale(0.015625)"/> + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_69.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_69.svg index 18d953ca5660..9a3e21d549e9 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_69.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_69.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:42.291943 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,94 +30,98 @@ z - - + + - + - + - - - - - +" id="DejaVuSerif-Italic-c5" transform="scale(0.015625)"/> + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_05.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_05.svg index 926dea2b92e5..c13a83ae034c 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_05.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_05.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:34.129013 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,220 +30,231 @@ z - - - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - +M 4077 768 +L 307 768 +L 307 1190 +L 4077 1190 +L 4077 768 +z +" id="STIXGeneral-Regular-3d" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_26.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_26.svg index 34c8efe0cd47..adc8bed4cb06 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_26.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_26.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:34.626436 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,264 +30,282 @@ z - - + + - - - - - - - - - + - - + - - - +" id="STIXGeneral-Regular-301" transform="scale(0.015625)"/> + + + + + + + + + + + - + - + - - - + + + - + - + - + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_69.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_69.svg index 25e468a1f39c..9738cc21a69b 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_69.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_69.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:35.788326 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,107 +30,111 @@ z - - + + - + - + - - - - - +" id="STIXGeneral-Italic-c5" transform="scale(0.015625)"/> + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_05.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_05.svg index 98b9bd65970b..f4634ce76c62 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_05.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_05.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:36.331853 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,172 +30,183 @@ z - - - - + + - + - + - - + - + - - - - - - - - - - - - - - - - - - - - +" id="STIXGeneral-Regular-3c" transform="scale(0.015625)"/> + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_26.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_26.svg index 01a95a9c3782..5a1ed4923774 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_26.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_26.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:36.792928 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,243 +30,261 @@ z - - + + - - - + - - - - - - + - + - - - - +M 1203 1875 +L 2579 1875 +Q 2605 1978 2605 2086 +Q 2605 2509 2093 2509 +Q 1837 2509 1587 2349 +Q 1338 2189 1203 1875 +z +" id="STIXGeneral-Italic-1d626" transform="scale(0.015625)"/> + + + + + + + + + + - + - + - + - + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_28.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_28.svg index 6b002332b5dc..65a11fcbd59d 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_28.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_28.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:36.857412 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,172 +30,184 @@ z - - + + - - + - - - + + - - - - - - - - - - - +" id="STIXGeneral-Regular-1d7e8" transform="scale(0.015625)"/> + + + + + + + + + + + + - + + + + + 2020-08-05T13:43:37.056437 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,508 +30,539 @@ z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + 2020-08-05T13:43:37.190526 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,235 +30,253 @@ z - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_41.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_41.svg index ac4809cf0045..8b097236395d 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_41.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_41.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:37.223168 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,597 +30,628 @@ z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_69.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_69.svg index 6f13a8fd413f..8242b10930c7 100644 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_69.svg +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_69.svg @@ -1,12 +1,23 @@ - + + + + + 2020-08-05T13:43:37.975730 + image/svg+xml + + + Matplotlib v3.3.0rc1.post530.dev0+g4269804ce, https://matplotlib.org/ + + + + + - + @@ -19,94 +30,98 @@ z - - + + - + - + - - - - - +" id="STIXGeneral-Italic-c5" transform="scale(0.015625)"/> + + + + diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index a3af6ee1b58f..295a6c80452c 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -18,7 +18,7 @@ import pytest -@image_comparison(['figure_align_labels'], +@image_comparison(['figure_align_labels'], extensions=['png', 'svg'], tol=0 if platform.machine() == 'x86_64' else 0.01) def test_align_labels(): fig = plt.figure(tight_layout=True) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 0edfe8c7d242..74af2a7fd570 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -122,8 +122,7 @@ def test_find_ttc(): ax.text(.5, .5, "\N{KANGXI RADICAL DRAGON}", fontproperties=fp) fig.savefig(BytesIO(), format="raw") fig.savefig(BytesIO(), format="svg") - with pytest.raises(RuntimeError): - fig.savefig(BytesIO(), format="pdf") + fig.savefig(BytesIO(), format="pdf") with pytest.raises(RuntimeError): fig.savefig(BytesIO(), format="ps") diff --git a/src/_path.h b/src/_path.h index f1acbb937753..784a6457807c 100644 --- a/src/_path.h +++ b/src/_path.h @@ -1089,35 +1089,38 @@ void quad2cubic(double x0, double y0, void __add_number(double val, char format_code, int precision, std::string& buffer) { - char *str = PyOS_double_to_string(val, format_code, precision, 0, NULL); - - // Delete trailing zeros and decimal point - char *q = str; - for (; *q != 0; ++q) { - // Find the end of the string - } - - --q; - for (; q >= str && *q == '0'; --q) { - // Rewind through all the zeros - } - - // If the end is a decimal point, delete that too - if (q >= str && *q == '.') { - --q; - } - - // Truncate the string - ++q; - *q = 0; - - try { + if (precision == -1) { + // Special-case for compat with old ttconv code, which *truncated* + // values with a cast to int instead of rounding them as printf + // would do. The only point where non-integer values arise is from + // quad2cubic conversion (as we already perform a first truncation + // on Python's side), which can introduce additional floating point + // error (by adding 2/3 delta-x and then 1/3 delta-x), so compensate by + // first rounding to the closest 1/3 and then truncating. + char str[255]; + PyOS_snprintf(str, 255, "%d", (int)(round(val * 3)) / 3); buffer += str; - } catch (std::bad_alloc& e) { + } else { + char *str = PyOS_double_to_string( + val, format_code, precision, Py_DTSF_ADD_DOT_0, NULL); + // Delete trailing zeros and decimal point + char *c = str + strlen(str) - 1; // Start at last character. + // Rewind through all the zeros and, if present, the trailing decimal + // point. Py_DTSF_ADD_DOT_0 ensures we won't go past the start of str. + while (*c == '0') { + --c; + } + if (*c == '.') { + --c; + } + try { + buffer.append(str, c + 1); + } catch (std::bad_alloc& e) { + PyMem_Free(str); + throw e; + } PyMem_Free(str); - throw e; } - PyMem_Free(str); } diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 817289945f72..720d8a622df5 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -9,6 +9,7 @@ #include "ft2font.h" #include "mplutils.h" +#include "numpy_cpp.h" #include "py_exceptions.h" #ifndef M_PI @@ -168,11 +169,6 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, m_dirty = true; } -inline double conv(long v) -{ - return v / 64.; -} - static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); @@ -188,325 +184,133 @@ static FT_UInt ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode) } -int FT2Font::get_path_count() +// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the +// first pass, vertices and codes are set to NULL, and index is simply +// incremented for each vertex that should be inserted, so that it is set, at +// the end, to the total number of vertices. On a second pass, vertices and +// codes should point to correctly sized arrays, and index set again to zero, +// to get fill vertices and codes with the outline decomposition. +struct ft_outline_decomposer { - // get the glyph as a path, a list of (COMMAND, *args) as described in matplotlib.path - // this code is from agg's decompose_ft_outline with minor modifications - - if (!face->glyph) { - throw std::runtime_error("No glyph loaded"); - } - - FT_Outline &outline = face->glyph->outline; - - FT_Vector v_last; - FT_Vector v_control; - FT_Vector v_start; - - FT_Vector *point; - FT_Vector *limit; - char *tags; - - int n; // index of contour in outline - int first; // index of first point in contour - char tag; // current point's state - int count; - - count = 0; - first = 0; - for (n = 0; n < outline.n_contours; n++) { - int last; // index of last point in contour - bool starts_with_last; - - last = outline.contours[n]; - limit = outline.points + last; - - v_start = outline.points[first]; - v_last = outline.points[last]; - - v_control = v_start; - - point = outline.points + first; - tags = outline.tags + first; - tag = FT_CURVE_TAG(tags[0]); - - // A contour cannot start with a cubic control point! - if (tag == FT_CURVE_TAG_CUBIC) { - throw std::runtime_error("A contour cannot start with a cubic control point"); - } else if (tag == FT_CURVE_TAG_CONIC) { - starts_with_last = true; - } else { - starts_with_last = false; - } - - count++; - - while (point < limit) { - if (!starts_with_last) { - point++; - tags++; - } - starts_with_last = false; - - tag = FT_CURVE_TAG(tags[0]); - switch (tag) { - case FT_CURVE_TAG_ON: // emit a single line_to - { - count++; - continue; - } - - case FT_CURVE_TAG_CONIC: // consume conic arcs - { - Count_Do_Conic: - if (point < limit) { - point++; - tags++; - tag = FT_CURVE_TAG(tags[0]); - - if (tag == FT_CURVE_TAG_ON) { - count += 2; - continue; - } - - if (tag != FT_CURVE_TAG_CONIC) { - throw std::runtime_error("Invalid font"); - } - - count += 2; - - goto Count_Do_Conic; - } - - count += 2; - - goto Count_Close; - } - - default: // FT_CURVE_TAG_CUBIC - { - if (point + 1 > limit || FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC) { - throw std::runtime_error("Invalid font"); - } - - point += 2; - tags += 2; - - if (point <= limit) { - count += 3; - continue; - } + int index; + double* vertices; + unsigned char* codes; +}; - count += 3; - - goto Count_Close; - } - } +static int +ft_outline_move_to(FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast(user); + if (d->codes) { + if (d->index) { + // Appending ENDPOLY is important to make patheffects work. + *(d->vertices++) = 0; + *(d->vertices++) = 0; + *(d->codes++) = ENDPOLY; } - - Count_Close: - count++; - first = last + 1; + *(d->vertices++) = to->x / 64.; + *(d->vertices++) = to->y / 64.; + *(d->codes++) = MOVETO; } - - return count; + d->index += d->index ? 2 : 1; + return 0; } -void FT2Font::get_path(double *outpoints, unsigned char *outcodes) +static int +ft_outline_line_to(FT_Vector const* to, void* user) { - FT_Outline &outline = face->glyph->outline; - bool flip_y = false; // todo, pass me as kwarg - - FT_Vector v_last; - FT_Vector v_control; - FT_Vector v_start; - - FT_Vector *point; - FT_Vector *limit; - char *tags; - - int n; // index of contour in outline - int first; // index of first point in contour - char tag; // current point's state - - first = 0; - for (n = 0; n < outline.n_contours; n++) { - int last; // index of last point in contour - bool starts_with_last; - - last = outline.contours[n]; - limit = outline.points + last; - - v_start = outline.points[first]; - v_last = outline.points[last]; - - v_control = v_start; - - point = outline.points + first; - tags = outline.tags + first; - tag = FT_CURVE_TAG(tags[0]); - - double x, y; - if (tag != FT_CURVE_TAG_ON) { - x = conv(v_last.x); - y = flip_y ? -conv(v_last.y) : conv(v_last.y); - starts_with_last = true; - } else { - x = conv(v_start.x); - y = flip_y ? -conv(v_start.y) : conv(v_start.y); - starts_with_last = false; - } - - *(outpoints++) = x; - *(outpoints++) = y; - *(outcodes++) = MOVETO; + ft_outline_decomposer* d = reinterpret_cast(user); + if (d->codes) { + *(d->vertices++) = to->x / 64.; + *(d->vertices++) = to->y / 64.; + *(d->codes++) = LINETO; + } + d->index++; + return 0; +} - while (point < limit) { - if (!starts_with_last) { - point++; - tags++; - } - starts_with_last = false; - - tag = FT_CURVE_TAG(tags[0]); - switch (tag) { - case FT_CURVE_TAG_ON: // emit a single line_to - { - double x = conv(point->x); - double y = flip_y ? -conv(point->y) : conv(point->y); - *(outpoints++) = x; - *(outpoints++) = y; - *(outcodes++) = LINETO; - continue; - } +static int +ft_outline_conic_to(FT_Vector const* control, FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast(user); + if (d->codes) { + *(d->vertices++) = control->x / 64.; + *(d->vertices++) = control->y / 64.; + *(d->vertices++) = to->x / 64.; + *(d->vertices++) = to->y / 64.; + *(d->codes++) = CURVE3; + *(d->codes++) = CURVE3; + } + d->index += 2; + return 0; +} - case FT_CURVE_TAG_CONIC: // consume conic arcs - { - v_control.x = point->x; - v_control.y = point->y; - - Do_Conic: - if (point < limit) { - FT_Vector vec; - FT_Vector v_middle; - - point++; - tags++; - tag = FT_CURVE_TAG(tags[0]); - - vec.x = point->x; - vec.y = point->y; - - if (tag == FT_CURVE_TAG_ON) { - double xctl = conv(v_control.x); - double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y); - double xto = conv(vec.x); - double yto = flip_y ? -conv(vec.y) : conv(vec.y); - *(outpoints++) = xctl; - *(outpoints++) = yctl; - *(outpoints++) = xto; - *(outpoints++) = yto; - *(outcodes++) = CURVE3; - *(outcodes++) = CURVE3; - continue; - } - - v_middle.x = (v_control.x + vec.x) / 2; - v_middle.y = (v_control.y + vec.y) / 2; - - double xctl = conv(v_control.x); - double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y); - double xto = conv(v_middle.x); - double yto = flip_y ? -conv(v_middle.y) : conv(v_middle.y); - *(outpoints++) = xctl; - *(outpoints++) = yctl; - *(outpoints++) = xto; - *(outpoints++) = yto; - *(outcodes++) = CURVE3; - *(outcodes++) = CURVE3; - - v_control = vec; - goto Do_Conic; - } - double xctl = conv(v_control.x); - double yctl = flip_y ? -conv(v_control.y) : conv(v_control.y); - double xto = conv(v_start.x); - double yto = flip_y ? -conv(v_start.y) : conv(v_start.y); - - *(outpoints++) = xctl; - *(outpoints++) = yctl; - *(outpoints++) = xto; - *(outpoints++) = yto; - *(outcodes++) = CURVE3; - *(outcodes++) = CURVE3; - - goto Close; - } +static int +ft_outline_cubic_to( + FT_Vector const* c1, FT_Vector const* c2, FT_Vector const* to, void* user) +{ + ft_outline_decomposer* d = reinterpret_cast(user); + if (d->codes) { + *(d->vertices++) = c1->x / 64.; + *(d->vertices++) = c1->y / 64.; + *(d->vertices++) = c2->x / 64.; + *(d->vertices++) = c2->y / 64.; + *(d->vertices++) = to->x / 64.; + *(d->vertices++) = to->y / 64.; + *(d->codes++) = CURVE4; + *(d->codes++) = CURVE4; + *(d->codes++) = CURVE4; + } + d->index += 3; + return 0; +} - default: // FT_CURVE_TAG_CUBIC - { - FT_Vector vec1, vec2; - - vec1.x = point[0].x; - vec1.y = point[0].y; - vec2.x = point[1].x; - vec2.y = point[1].y; - - point += 2; - tags += 2; - - if (point <= limit) { - FT_Vector vec; - - vec.x = point->x; - vec.y = point->y; - - double xctl1 = conv(vec1.x); - double yctl1 = flip_y ? -conv(vec1.y) : conv(vec1.y); - double xctl2 = conv(vec2.x); - double yctl2 = flip_y ? -conv(vec2.y) : conv(vec2.y); - double xto = conv(vec.x); - double yto = flip_y ? -conv(vec.y) : conv(vec.y); - - (*outpoints++) = xctl1; - (*outpoints++) = yctl1; - (*outpoints++) = xctl2; - (*outpoints++) = yctl2; - (*outpoints++) = xto; - (*outpoints++) = yto; - (*outcodes++) = CURVE4; - (*outcodes++) = CURVE4; - (*outcodes++) = CURVE4; - continue; - } - - double xctl1 = conv(vec1.x); - double yctl1 = flip_y ? -conv(vec1.y) : conv(vec1.y); - double xctl2 = conv(vec2.x); - double yctl2 = flip_y ? -conv(vec2.y) : conv(vec2.y); - double xto = conv(v_start.x); - double yto = flip_y ? -conv(v_start.y) : conv(v_start.y); - (*outpoints++) = xctl1; - (*outpoints++) = yctl1; - (*outpoints++) = xctl2; - (*outpoints++) = yctl2; - (*outpoints++) = xto; - (*outpoints++) = yto; - (*outcodes++) = CURVE4; - (*outcodes++) = CURVE4; - (*outcodes++) = CURVE4; - - goto Close; - } - } - } +static FT_Outline_Funcs ft_outline_funcs = { + ft_outline_move_to, + ft_outline_line_to, + ft_outline_conic_to, + ft_outline_cubic_to}; - Close: - (*outpoints++) = 0.0; - (*outpoints++) = 0.0; - (*outcodes++) = ENDPOLY; - first = last + 1; - } +PyObject* +FT2Font::get_path() +{ + if (!face->glyph) { + PyErr_SetString(PyExc_RuntimeError, "No glyph loaded"); + return NULL; + } + ft_outline_decomposer decomposer = {}; + if (FT_Error error = + FT_Outline_Decompose( + &face->glyph->outline, &ft_outline_funcs, &decomposer)) { + PyErr_Format(PyExc_RuntimeError, + "FT_Outline_Decompose failed with error 0x%x", error); + return NULL; + } + if (!decomposer.index) { // Don't append ENDPOLY to null glyphs. + npy_intp vertices_dims[2] = { 0, 2 }; + numpy::array_view vertices(vertices_dims); + npy_intp codes_dims[1] = { 0 }; + numpy::array_view codes(codes_dims); + return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); + } + npy_intp vertices_dims[2] = { decomposer.index + 1, 2 }; + numpy::array_view vertices(vertices_dims); + npy_intp codes_dims[1] = { decomposer.index + 1 }; + numpy::array_view codes(codes_dims); + decomposer.index = 0; + decomposer.vertices = vertices.data(); + decomposer.codes = codes.data(); + if (FT_Error error = + FT_Outline_Decompose( + &face->glyph->outline, &ft_outline_funcs, &decomposer)) { + PyErr_Format(PyExc_RuntimeError, + "FT_Outline_Decompose failed with error 0x%x", error); + return NULL; + } + *(decomposer.vertices++) = 0; + *(decomposer.vertices++) = 0; + *(decomposer.codes++) = ENDPOLY; + return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); } FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face(NULL) diff --git a/src/ft2font.h b/src/ft2font.h index ea48e7f49bae..0863f3450b36 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -10,11 +10,15 @@ extern "C" { #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_OUTLINE_H #include FT_SFNT_NAMES_H #include FT_TYPE1_TABLES_H #include FT_TRUETYPE_TABLES_H } +#define PY_SSIZE_T_CLEAN +#include + /* By definition, FT_FIXED as 2 16bit values stored in a single long. */ @@ -87,8 +91,7 @@ class FT2Font void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased); void get_glyph_name(unsigned int glyph_number, char *buffer); long get_name_index(char *name); - int get_path_count(); - void get_path(double *outpoints, unsigned char *outcodes); + PyObject* get_path(); FT_Face &get_face() { diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index ba361ac0279a..ea8d1e19237a 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1370,19 +1370,7 @@ const char *PyFT2Font_get_path__doc__ = static PyObject *PyFT2Font_get_path(PyFT2Font *self, PyObject *args, PyObject *kwds) { - int count; - - CALL_CPP("get_path", (count = self->x->get_path_count())); - - npy_intp vertices_dims[2] = { count, 2 }; - numpy::array_view vertices(vertices_dims); - - npy_intp codes_dims[1] = { count }; - numpy::array_view codes(codes_dims); - - self->x->get_path(vertices.data(), codes.data()); - - return Py_BuildValue("NN", vertices.pyobj(), codes.pyobj()); + CALL_CPP("get_path", return self->x->get_path()); } const char *PyFT2Font_get_image__doc__ =