Skip to content

Commit dbc6ef2

Browse files
jkseppanmdboom
authored andcommitted
Fix TrueType to Type-3 font conversion
The code did not handle correctly glyph contours where the first point was off-path. This changes the conversion algorithm to a hopefully clearer one, which explicitly inserts all implicit on-path points between off-path ones as a first step before outputting the PostScript code.
1 parent 6741580 commit dbc6ef2

File tree

1 file changed

+107
-71
lines changed

1 file changed

+107
-71
lines changed

ttconv/pprdrv_tt2.cpp

+107-71
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "truetype.h"
4242
#include <algorithm>
4343
#include <stack>
44+
#include <list>
4445

4546
class GlyphToType3
4647
{
@@ -73,7 +74,10 @@ class GlyphToType3
7374
int nextoutctr(int co);
7475
int nearout(int ci);
7576
double intest(int co, int ci);
76-
void PSCurveto(TTStreamWriter& stream, FWord x, FWord y, int s, int t);
77+
void PSCurveto(TTStreamWriter& stream,
78+
FWord x0, FWord y0,
79+
FWord x1, FWord y1,
80+
FWord x2, FWord y2);
7781
void PSMoveto(TTStreamWriter& stream, int x, int y);
7882
void PSLineto(TTStreamWriter& stream, int x, int y);
7983
void do_composite(TTStreamWriter& stream, struct TTFONT *font, BYTE *glyph);
@@ -83,6 +87,18 @@ class GlyphToType3
8387
~GlyphToType3();
8488
};
8589

90+
// Each point on a TrueType contour is either on the path or off it (a
91+
// control point); here's a simple representation for building such
92+
// contours. Added by Jouni Seppänen 2012-05-27.
93+
enum Flag { ON_PATH, OFF_PATH };
94+
struct FlaggedPoint
95+
{
96+
enum Flag flag;
97+
FWord x;
98+
FWord y;
99+
FlaggedPoint(Flag flag_, FWord x_, FWord y_): flag(flag_), x(x_), y(y_) {};
100+
};
101+
86102
double area(FWord *x, FWord *y, int n);
87103
#define sqr(x) ((x)*(x))
88104

@@ -150,8 +166,7 @@ double area(FWord *x, FWord *y, int n)
150166
*/
151167
void GlyphToType3::PSConvert(TTStreamWriter& stream)
152168
{
153-
int i,j,k,fst,start_offpt;
154-
int end_offpt = 0;
169+
int i,j,k;
155170

156171
assert(area_ctr == NULL);
157172
area_ctr=(double*)calloc(num_ctr, sizeof(double));
@@ -191,56 +206,79 @@ void GlyphToType3::PSConvert(TTStreamWriter& stream)
191206
i=j=k=0;
192207
while ( i < num_ctr )
193208
{
194-
fst = j = (k==0) ? 0 : (epts_ctr[k-1]+1);
209+
// A TrueType contour consists of on-path and off-path points.
210+
// Two consecutive on-path points are to be joined with a
211+
// line; off-path points between on-path points indicate a
212+
// quadratic spline, where the off-path point is the control
213+
// point. Two consecutive off-path points have an implicit
214+
// on-path point midway between them.
215+
std::list<FlaggedPoint> points;
195216

196-
/* Move to the first point on the contour. */
197-
stack(stream, 3);
198-
PSMoveto(stream,xcoor[j],ycoor[j]);
199-
200-
start_offpt = 0; /* No off curve points yet. */
201-
202-
/* Step thru the remaining points of this contour. */
203-
for (j++; j <= epts_ctr[k]; j++)
217+
// Represent flags and x/y coordinates as a C++ list
218+
for (; j <= epts_ctr[k]; j++)
204219
{
205-
if (!(tt_flags[j]&1)) /* Off curve */
206-
{
207-
if (!start_offpt)
208-
{
209-
start_offpt = end_offpt = j;
210-
}
211-
else
212-
{
213-
end_offpt++;
214-
}
220+
if (!(tt_flags[j] & 1)) {
221+
points.push_back(FlaggedPoint(OFF_PATH, xcoor[j], ycoor[j]));
222+
} else {
223+
points.push_back(FlaggedPoint(ON_PATH, xcoor[j], ycoor[j]));
215224
}
216-
else
225+
}
226+
227+
// For any two consecutive off-path points, insert the implied
228+
// on-path point.
229+
FlaggedPoint prev = points.back();
230+
for (std::list<FlaggedPoint>::iterator it = points.begin();
231+
it != points.end();
232+
it++)
233+
{
234+
if (prev.flag == OFF_PATH && it->flag == OFF_PATH)
217235
{
218-
/* On Curve */
219-
if (start_offpt)
220-
{
221-
stack(stream, 7);
222-
PSCurveto(stream, xcoor[j],ycoor[j],start_offpt,end_offpt);
223-
start_offpt = 0;
224-
}
225-
else
226-
{
227-
stack(stream, 3);
228-
PSLineto(stream, xcoor[j], ycoor[j]);
229-
}
236+
points.insert(it,
237+
FlaggedPoint(ON_PATH,
238+
(prev.x + it->x) / 2,
239+
(prev.y + it->y) / 2));
230240
}
241+
prev = *it;
231242
}
232-
233-
/* Do the final curve or line */
234-
/* of this coutour. */
235-
if (start_offpt)
243+
// Handle the wrap-around: insert a point either at the beginning
244+
// or at the end that has the same coordinates as the opposite point.
245+
// This also ensures that the initial point is ON_PATH.
246+
if (points.front().flag == OFF_PATH)
236247
{
237-
stack(stream, 7);
238-
PSCurveto(stream, xcoor[fst],ycoor[fst],start_offpt,end_offpt);
248+
assert(points.back().flag == ON_PATH);
249+
points.insert(points.begin(), points.back());
239250
}
240251
else
241252
{
242-
stack(stream, 3);
243-
PSLineto(stream, xcoor[fst],ycoor[fst]);
253+
assert(points.front().flag == ON_PATH);
254+
points.push_back(points.front());
255+
}
256+
257+
// For output, a vector is more convenient than a list.
258+
std::vector<FlaggedPoint> points_v(points.begin(), points.end());
259+
// The first point
260+
stack(stream, 3);
261+
PSMoveto(stream, points_v.front().x, points_v.front().y);
262+
263+
// Step through the remaining points
264+
for (size_t p = 1; p < points_v.size(); )
265+
{
266+
const FlaggedPoint& point = points_v.at(p);
267+
if (point.flag == ON_PATH)
268+
{
269+
stack(stream, 3);
270+
PSLineto(stream, point.x, point.y);
271+
p++;
272+
} else {
273+
assert(points_v.at(p-1).flag == ON_PATH);
274+
assert(points_v.at(p+1).flag == ON_PATH);
275+
stack(stream, 7);
276+
PSCurveto(stream,
277+
points_v.at(p-1).x, points_v.at(p-1).y,
278+
point.x, point.y,
279+
points_v.at(p+1).x, points_v.at(p+1).y);
280+
p += 2;
281+
}
244282
}
245283

246284
k=nextinctr(i,k);
@@ -392,36 +430,34 @@ void GlyphToType3::PSLineto(TTStreamWriter& stream, int x, int y)
392430
}
393431

394432
/*
395-
** Emmit a PostScript "curveto" command.
433+
** Emit a PostScript "curveto" command, assuming the current point
434+
** is (x0, y0), the control point of a quadratic spline is (x1, y1),
435+
** and the endpoint is (x2, y2). Note that this requires a conversion,
436+
** since PostScript splines are cubic.
396437
*/
397-
void GlyphToType3::PSCurveto(TTStreamWriter& stream, FWord x, FWord y, int s, int t)
438+
void GlyphToType3::PSCurveto(TTStreamWriter& stream,
439+
FWord x0, FWord y0,
440+
FWord x1, FWord y1,
441+
FWord x2, FWord y2)
398442
{
399-
int N, i;
400-
double sx[3], sy[3], cx[4], cy[4];
401-
402-
N = t-s+2;
403-
for (i=0; i<N-1; i++)
404-
{
405-
sx[0] = i==0?xcoor[s-1]:(xcoor[i+s]+xcoor[i+s-1])/2;
406-
sy[0] = i==0?ycoor[s-1]:(ycoor[i+s]+ycoor[i+s-1])/2;
407-
sx[1] = xcoor[s+i];
408-
sy[1] = ycoor[s+i];
409-
sx[2] = i==N-2?x:(xcoor[s+i]+xcoor[s+i+1])/2;
410-
sy[2] = i==N-2?y:(ycoor[s+i]+ycoor[s+i+1])/2;
411-
cx[3] = sx[2];
412-
cy[3] = sy[2];
413-
cx[1] = (2*sx[1]+sx[0])/3;
414-
cy[1] = (2*sy[1]+sy[0])/3;
415-
cx[2] = (sx[2]+2*sx[1])/3;
416-
cy[2] = (sy[2]+2*sy[1])/3;
417-
418-
stream.printf(pdf_mode ?
419-
"%d %d %d %d %d %d c\n" :
420-
"%d %d %d %d %d %d _c\n",
421-
(int)cx[1], (int)cy[1], (int)cx[2], (int)cy[2],
422-
(int)cx[3], (int)cy[3]);
423-
}
424-
} /* end of PSCurveto() */
443+
double sx[3], sy[3], cx[3], cy[3];
444+
445+
sx[0] = x0;
446+
sy[0] = y0;
447+
sx[1] = x1;
448+
sy[1] = y1;
449+
sx[2] = x2;
450+
sy[2] = y2;
451+
cx[0] = (2*sx[1]+sx[0])/3;
452+
cy[0] = (2*sy[1]+sy[0])/3;
453+
cx[1] = (sx[2]+2*sx[1])/3;
454+
cy[1] = (sy[2]+2*sy[1])/3;
455+
cx[2] = sx[2];
456+
cy[2] = sy[2];
457+
stream.printf("%d %d %d %d %d %d %s\n",
458+
(int)cx[0], (int)cy[0], (int)cx[1], (int)cy[1],
459+
(int)cx[2], (int)cy[2], pdf_mode ? "c" : "_c");
460+
}
425461

426462
/*
427463
** Deallocate the structures which stored

0 commit comments

Comments
 (0)