diff --git a/lib/matplotlib/tests/baseline_images/test_ttconv/truetype-conversion.pdf b/lib/matplotlib/tests/baseline_images/test_ttconv/truetype-conversion.pdf new file mode 100644 index 000000000000..0649058c6200 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_ttconv/truetype-conversion.pdf differ diff --git a/lib/matplotlib/tests/mpltest.ttf b/lib/matplotlib/tests/mpltest.ttf new file mode 100644 index 000000000000..b8133a6c7baf Binary files /dev/null and b/lib/matplotlib/tests/mpltest.ttf differ diff --git a/lib/matplotlib/tests/test_ttconv.py b/lib/matplotlib/tests/test_ttconv.py new file mode 100644 index 000000000000..21f9b79ccb1c --- /dev/null +++ b/lib/matplotlib/tests/test_ttconv.py @@ -0,0 +1,18 @@ +import matplotlib +from matplotlib.font_manager import FontProperties +from matplotlib.testing.decorators import image_comparison +import matplotlib.pyplot as plt +import os.path + +@image_comparison(baseline_images=["truetype-conversion"], + extensions=["pdf"]) +def test_truetype_conversion(): + fontname = os.path.join(os.path.dirname(__file__), 'mpltest.ttf') + fontname = os.path.abspath(fontname) + fontprop = FontProperties(fname=fontname, size=80) + matplotlib.rcParams['pdf.fonttype'] = 3 + fig = plt.figure() + ax = fig.add_subplot(111) + ax.text(0, 0, "ABCDE", fontproperties=fontprop) + ax.set_xticks([]) + ax.set_yticks([]) diff --git a/setup.py b/setup.py index 5c54c9565116..8f4b0d1977c0 100644 --- a/setup.py +++ b/setup.py @@ -112,6 +112,7 @@ def chop_package(fname): return result baseline_images = [chop_package(f) for f in baseline_images] package_data['matplotlib'].extend(baseline_images) + package_data['matplotlib'].append('tests/mpltest.ttf') if not check_for_numpy(__version__numpy__): sys.exit(1) diff --git a/ttconv/pprdrv_tt2.cpp b/ttconv/pprdrv_tt2.cpp index f8c8cef66fdb..13023822a53e 100644 --- a/ttconv/pprdrv_tt2.cpp +++ b/ttconv/pprdrv_tt2.cpp @@ -41,6 +41,7 @@ #include "truetype.h" #include #include +#include class GlyphToType3 { @@ -73,7 +74,10 @@ class GlyphToType3 int nextoutctr(int co); int nearout(int ci); double intest(int co, int ci); - void PSCurveto(TTStreamWriter& stream, FWord x, FWord y, int s, int t); + void PSCurveto(TTStreamWriter& stream, + FWord x0, FWord y0, + FWord x1, FWord y1, + FWord x2, FWord y2); void PSMoveto(TTStreamWriter& stream, int x, int y); void PSLineto(TTStreamWriter& stream, int x, int y); void do_composite(TTStreamWriter& stream, struct TTFONT *font, BYTE *glyph); @@ -83,6 +87,18 @@ class GlyphToType3 ~GlyphToType3(); }; +// Each point on a TrueType contour is either on the path or off it (a +// control point); here's a simple representation for building such +// contours. Added by Jouni Seppänen 2012-05-27. +enum Flag { ON_PATH, OFF_PATH }; +struct FlaggedPoint +{ + enum Flag flag; + FWord x; + FWord y; + FlaggedPoint(Flag flag_, FWord x_, FWord y_): flag(flag_), x(x_), y(y_) {}; +}; + double area(FWord *x, FWord *y, int n); #define sqr(x) ((x)*(x)) @@ -150,8 +166,7 @@ double area(FWord *x, FWord *y, int n) */ void GlyphToType3::PSConvert(TTStreamWriter& stream) { - int i,j,k,fst,start_offpt; - int end_offpt = 0; + int i,j,k; assert(area_ctr == NULL); area_ctr=(double*)calloc(num_ctr, sizeof(double)); @@ -191,56 +206,79 @@ void GlyphToType3::PSConvert(TTStreamWriter& stream) i=j=k=0; while ( i < num_ctr ) { - fst = j = (k==0) ? 0 : (epts_ctr[k-1]+1); + // A TrueType contour consists of on-path and off-path points. + // Two consecutive on-path points are to be joined with a + // line; off-path points between on-path points indicate a + // quadratic spline, where the off-path point is the control + // point. Two consecutive off-path points have an implicit + // on-path point midway between them. + std::list points; - /* Move to the first point on the contour. */ - stack(stream, 3); - PSMoveto(stream,xcoor[j],ycoor[j]); - - start_offpt = 0; /* No off curve points yet. */ - - /* Step thru the remaining points of this contour. */ - for (j++; j <= epts_ctr[k]; j++) + // Represent flags and x/y coordinates as a C++ list + for (; j <= epts_ctr[k]; j++) { - if (!(tt_flags[j]&1)) /* Off curve */ - { - if (!start_offpt) - { - start_offpt = end_offpt = j; - } - else - { - end_offpt++; - } + if (!(tt_flags[j] & 1)) { + points.push_back(FlaggedPoint(OFF_PATH, xcoor[j], ycoor[j])); + } else { + points.push_back(FlaggedPoint(ON_PATH, xcoor[j], ycoor[j])); } - else + } + + // For any two consecutive off-path points, insert the implied + // on-path point. + FlaggedPoint prev = points.back(); + for (std::list::iterator it = points.begin(); + it != points.end(); + it++) + { + if (prev.flag == OFF_PATH && it->flag == OFF_PATH) { - /* On Curve */ - if (start_offpt) - { - stack(stream, 7); - PSCurveto(stream, xcoor[j],ycoor[j],start_offpt,end_offpt); - start_offpt = 0; - } - else - { - stack(stream, 3); - PSLineto(stream, xcoor[j], ycoor[j]); - } + points.insert(it, + FlaggedPoint(ON_PATH, + (prev.x + it->x) / 2, + (prev.y + it->y) / 2)); } + prev = *it; } - - /* Do the final curve or line */ - /* of this coutour. */ - if (start_offpt) + // Handle the wrap-around: insert a point either at the beginning + // or at the end that has the same coordinates as the opposite point. + // This also ensures that the initial point is ON_PATH. + if (points.front().flag == OFF_PATH) { - stack(stream, 7); - PSCurveto(stream, xcoor[fst],ycoor[fst],start_offpt,end_offpt); + assert(points.back().flag == ON_PATH); + points.insert(points.begin(), points.back()); } else { - stack(stream, 3); - PSLineto(stream, xcoor[fst],ycoor[fst]); + assert(points.front().flag == ON_PATH); + points.push_back(points.front()); + } + + // For output, a vector is more convenient than a list. + std::vector points_v(points.begin(), points.end()); + // The first point + stack(stream, 3); + PSMoveto(stream, points_v.front().x, points_v.front().y); + + // Step through the remaining points + for (size_t p = 1; p < points_v.size(); ) + { + const FlaggedPoint& point = points_v.at(p); + if (point.flag == ON_PATH) + { + stack(stream, 3); + PSLineto(stream, point.x, point.y); + p++; + } else { + assert(points_v.at(p-1).flag == ON_PATH); + assert(points_v.at(p+1).flag == ON_PATH); + stack(stream, 7); + PSCurveto(stream, + points_v.at(p-1).x, points_v.at(p-1).y, + point.x, point.y, + points_v.at(p+1).x, points_v.at(p+1).y); + p += 2; + } } k=nextinctr(i,k); @@ -392,36 +430,34 @@ void GlyphToType3::PSLineto(TTStreamWriter& stream, int x, int y) } /* -** Emmit a PostScript "curveto" command. +** Emit a PostScript "curveto" command, assuming the current point +** is (x0, y0), the control point of a quadratic spline is (x1, y1), +** and the endpoint is (x2, y2). Note that this requires a conversion, +** since PostScript splines are cubic. */ -void GlyphToType3::PSCurveto(TTStreamWriter& stream, FWord x, FWord y, int s, int t) +void GlyphToType3::PSCurveto(TTStreamWriter& stream, + FWord x0, FWord y0, + FWord x1, FWord y1, + FWord x2, FWord y2) { - int N, i; - double sx[3], sy[3], cx[4], cy[4]; - - N = t-s+2; - for (i=0; i