From 8caff885c195fd65588c5dae6c4feaf5e57c44f7 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 3 May 2025 03:04:33 -0400 Subject: [PATCH] Remove ttconv backwards-compatibility code --- lib/matplotlib/backends/backend_pdf.py | 38 +++++---------------- src/_path.h | 47 ++++++++++---------------- 2 files changed, 25 insertions(+), 60 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 8db640d888b1..53bf10c90b48 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -617,40 +617,18 @@ def _get_pdf_charprocs(font_path, glyph_ids): procs = {} for glyph_id in glyph_ids: g = font.load_glyph(glyph_id, LoadFlags.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) + d1 = [ + round(g.horiAdvance * conv), 0, + # Round bbox corners *outwards*, so that they indeed bound the glyph. + math.floor(g.bbox[0] * conv), math.floor(g.bbox[1] * conv), + math.ceil(g.bbox[2] * conv), math.ceil(g.bbox[3] * conv), + ] 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)) + v = (v * 64 * conv).round() # Back to TrueType's internal units (1/64's). 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, + Path(v, c), None, None, False, None, 0, # no code for quad Beziers triggers auto-conversion to cubics. [b"m", b"l", b"", b"c", b"h"], True) + b"f") diff --git a/src/_path.h b/src/_path.h index c03703776760..1b54426c7e81 100644 --- a/src/_path.h +++ b/src/_path.h @@ -1066,38 +1066,25 @@ void quad2cubic(double x0, double y0, void __add_number(double val, char format_code, int precision, std::string& buffer) { - 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; - } else { - char *str = PyOS_double_to_string( - val, format_code, precision, Py_DTSF_ADD_DOT_0, nullptr); - // 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; - } + char *str = PyOS_double_to_string( + val, format_code, precision, Py_DTSF_ADD_DOT_0, nullptr); + // 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); }