1 /*
2  * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/).
3  * All Rights Reserved.
4  *
5  * This software is licensed as OpenSource, under the Apache License, Version
6  * 2.0.
7  * This license is available at: http://opensource.org/licenses/Apache-2.0.
8  */
9 
10 #include <math.h>
11 
12 #include "ac.h"
13 
14 static bool g_xflat, g_yflat, g_xdone, g_ydone, g_bbquit;
15 static int32_t g_xstate, g_ystate, g_xstart, g_ystart;
16 static Fixed g_x0, g_cy0, g_x1, g_cy1, g_xloc, g_yloc;
17 static Fixed g_x, g_y, g_xnxt, g_ynxt;
18 static Fixed g_yflatstartx, g_yflatstarty, g_yflatendx, g_yflatendy;
19 static Fixed g_xflatstarty, g_xflatstartx, g_xflatendx, g_xflatendy;
20 static bool g_vert, g_started, g_reCheckSmooth;
21 static Fixed g_loc, g_frst, g_lst, g_fltnvalue;
22 static PathElt* g_e;
23 static bool g_forMultiMaster = false, g_inflPtFound = false;
24 
25 #define STARTING (0)
26 #define goingUP (1)
27 #define goingDOWN (2)
28 
29 /* DEBUG 8 BIT. The SDELTA value must tbe increased by 2 due to change in
30  * coordinate system from 7 to 8 bit FIXED fraction. */
31 
32 #define SDELTA (FixInt(8))
33 #define SDELTA3 (FixInt(10))
34 
35 static void
chkBad(void)36 chkBad(void)
37 {
38     g_reCheckSmooth = ResolveConflictBySplit(g_e, false, NULL, NULL);
39     ;
40 }
41 
42 #define GrTan(n, d) (abs(n) * 100 > abs(d) * gSCurveTan)
43 #define LsTan(n, d) (abs(n) * 100 < abs(d) * gSCurveTan)
44 
45 static void
chkYDIR(void)46 chkYDIR(void)
47 {
48     if (g_y > g_yloc) { /* going up */
49         if (g_ystate == goingUP)
50             return;
51         if (g_ystate == STARTING)
52             g_ystart = g_ystate = goingUP;
53         else /*if (ystate == goingDOWN)*/ {
54             if (g_ystart == goingUP) {
55                 g_yflatendx = g_xloc;
56                 g_yflatendy = g_yloc;
57             } else if (!g_yflat) {
58                 g_yflatstartx = g_xloc;
59                 g_yflatstarty = g_yloc;
60                 g_yflat = true;
61             }
62             g_ystate = goingUP;
63         }
64     } else if (g_y < g_yloc) { /* going down */
65         if (g_ystate == goingDOWN)
66             return;
67         if (g_ystate == STARTING)
68             g_ystart = g_ystate = goingDOWN;
69         else /*if (ystate == goingUP)*/ {
70             if (g_ystart == goingDOWN) {
71                 g_yflatendx = g_xloc;
72                 g_yflatendy = g_yloc;
73             } else if (!g_yflat) {
74                 g_yflatstartx = g_xloc;
75                 g_yflatstarty = g_yloc;
76                 g_yflat = true;
77             }
78             g_ystate = goingDOWN;
79         }
80     }
81 }
82 
83 static void
chkYFLAT(void)84 chkYFLAT(void)
85 {
86     if (!g_yflat) {
87         if (LsTan(g_y - g_yloc, g_x - g_xloc)) {
88             g_yflat = true;
89             g_yflatstartx = g_xloc;
90             g_yflatstarty = g_yloc;
91         }
92         return;
93     }
94     if (g_ystate != g_ystart)
95         return;
96     if (GrTan(g_y - g_yloc, g_x - g_xloc)) {
97         g_yflatendx = g_xloc;
98         g_yflatendy = g_yloc;
99         g_ydone = true;
100     }
101 }
102 
103 static void
chkXFLAT(void)104 chkXFLAT(void)
105 {
106     if (!g_xflat) {
107         if (LsTan(g_x - g_xloc, g_y - g_yloc)) {
108             g_xflat = true;
109             g_xflatstartx = g_xloc;
110             g_xflatstarty = g_yloc;
111         }
112         return;
113     }
114     if (g_xstate != g_xstart)
115         return;
116     if (GrTan(g_x - g_xloc, g_y - g_yloc)) {
117         g_xflatendx = g_xloc;
118         g_xflatendy = g_yloc;
119         g_xdone = true;
120     }
121 }
122 
123 static void
chkXDIR(void)124 chkXDIR(void)
125 {
126     if (g_x > g_xloc) { /* going up */
127         if (g_xstate == goingUP)
128             return;
129         if (g_xstate == STARTING)
130             g_xstart = g_xstate = goingUP;
131         else /*if (xstate == goingDOWN)*/ {
132             if (g_xstart == goingUP) {
133                 g_xflatendx = g_xloc;
134                 g_xflatendy = g_yloc;
135             } else if (!g_xflat) {
136                 g_xflatstartx = g_xloc;
137                 g_xflatstarty = g_yloc;
138                 g_xflat = true;
139             }
140             g_xstate = goingUP;
141         }
142     } else if (g_x < g_xloc) {
143         if (g_xstate == goingDOWN)
144             return;
145         if (g_xstate == STARTING)
146             g_xstart = g_xstate = goingDOWN;
147         else /*if (xstate == goingUP)*/ {
148             if (g_xstart == goingDOWN) {
149                 g_xflatendx = g_xloc;
150                 g_xflatendy = g_yloc;
151             } else if (!g_xflat) {
152                 g_xflatstartx = g_xloc;
153                 g_xflatstarty = g_yloc;
154                 g_xflat = true;
155             }
156             g_xstate = goingDOWN;
157         }
158     }
159 }
160 
161 static void
chkDT(Cd c)162 chkDT(Cd c)
163 {
164     Fixed loc;
165 
166     g_x = c.x;
167     g_y = c.y;
168     g_ynxt = g_y;
169     g_xnxt = g_x;
170     if (!g_ydone) {
171         chkYDIR();
172         chkYFLAT();
173         if (g_ydone && g_yflat && abs(g_yflatstarty - g_cy0) > SDELTA &&
174             abs(g_cy1 - g_yflatendy) > SDELTA) {
175             if ((g_ystart == goingUP && g_yflatstarty - g_yflatendy > SDELTA) ||
176                 (g_ystart == goingDOWN && g_yflatendy - g_yflatstarty > SDELTA)) {
177                 if (gEditGlyph && !g_forMultiMaster)
178                     chkBad();
179                 return;
180             }
181             if (abs(g_yflatstartx - g_yflatendx) > SDELTA3) {
182                 DEBUG_ROUND(g_yflatstartx);
183                 DEBUG_ROUND(g_yflatendx);
184                 DEBUG_ROUND(g_yflatstarty);
185                 DEBUG_ROUND(g_yflatendy);
186 
187                 loc = (g_yflatstarty + g_yflatendy) / 2;
188                 DEBUG_ROUND(loc);
189 
190                 if (!g_forMultiMaster) {
191                     AddHSegment(g_yflatstartx, g_yflatendx, loc, g_e, NULL, sCURVE,
192                                 13);
193                 } else {
194                     g_inflPtFound = true;
195                     g_fltnvalue = -loc;
196                 }
197             }
198         }
199     }
200     if (!g_xdone) {
201         chkXDIR();
202         chkXFLAT();
203         if (g_xdone && g_xflat && abs(g_xflatstartx - g_x0) > SDELTA &&
204             abs(g_x1 - g_xflatendx) > SDELTA) {
205             if ((g_xstart == goingUP && g_xflatstartx - g_xflatendx > SDELTA) ||
206                 (g_xstart == goingDOWN && g_xflatendx - g_xflatstartx > SDELTA)) {
207                 if (gEditGlyph && !g_forMultiMaster)
208                     chkBad();
209                 return;
210             }
211             if (abs(g_xflatstarty - g_xflatendy) > SDELTA3) {
212                 DEBUG_ROUND(g_xflatstarty);
213                 DEBUG_ROUND(g_xflatendy);
214                 DEBUG_ROUND(g_xflatstartx);
215                 DEBUG_ROUND(g_xflatendx);
216 
217                 loc = (g_xflatstartx + g_xflatendx) / 2;
218                 DEBUG_ROUND(loc);
219 
220                 if (!g_forMultiMaster)
221 
222                 {
223                     AddVSegment(g_xflatstarty, g_xflatendy, loc, g_e, NULL, sCURVE,
224                                 13);
225                 } else {
226                     g_inflPtFound = true;
227                     g_fltnvalue = loc;
228                 }
229             }
230         }
231     }
232     g_xloc = g_xnxt;
233     g_yloc = g_ynxt;
234 }
235 
236 #define FQ(x) ((int32_t)((x) >> 6))
237 static int32_t
CPDirection(Fixed x1,Fixed cy1,Fixed x2,Fixed y2,Fixed x3,Fixed y3)238 CPDirection(Fixed x1, Fixed cy1, Fixed x2, Fixed y2, Fixed x3, Fixed y3)
239 {
240     int32_t q, q1, q2, q3;
241     q1 = FQ(x2) * FQ(y3 - cy1);
242     q2 = FQ(x1) * FQ(y2 - y3);
243     q3 = FQ(x3) * FQ(cy1 - y2);
244     q = q1 + q2 + q3;
245     if (q > 0)
246         return 1;
247     if (q < 0)
248         return -1;
249     return 0;
250 }
251 
252 void
RMovePoint(Fixed dx,Fixed dy,int32_t whichcp,PathElt * e)253 RMovePoint(Fixed dx, Fixed dy, int32_t whichcp, PathElt* e)
254 {
255     if (whichcp == cpStart) {
256         e = e->prev;
257         whichcp = cpEnd;
258     }
259     if (whichcp == cpEnd) {
260         if (e->type == CLOSEPATH)
261             e = GetDest(e);
262         if (e->type == CURVETO) {
263             e->x3 += dx;
264             e->y3 += dy;
265         } else {
266             e->x += dx;
267             e->y += dy;
268         }
269         return;
270     }
271     if (whichcp == cpCurve1) {
272         e->x1 += dx;
273         e->y1 += dy;
274         return;
275     }
276     if (whichcp == cpCurve2) {
277         e->x2 += dx;
278         e->y2 += dy;
279         return;
280     }
281     LogMsg(LOGERROR, NONFATALERROR, "Malformed path list.");
282 }
283 
284 void
Delete(PathElt * e)285 Delete(PathElt* e)
286 {
287     PathElt *nxt, *prv;
288     nxt = e->next;
289     prv = e->prev;
290     if (nxt != NULL)
291         nxt->prev = prv;
292     else
293         gPathEnd = prv;
294     if (prv != NULL)
295         prv->next = nxt;
296     else
297         gPathStart = nxt;
298 }
299 
300 /* This procedure is called from BuildFont when adding hints
301  to base designs of a multi-master font. */
302 bool
GetInflectionPoint(Fixed px,Fixed py,Fixed px1,Fixed pcy1,Fixed px2,Fixed py2,Fixed px3,Fixed py3,Fixed * inflPt)303 GetInflectionPoint(Fixed px, Fixed py, Fixed px1, Fixed pcy1, Fixed px2,
304                    Fixed py2, Fixed px3, Fixed py3, Fixed* inflPt)
305 {
306     FltnRec fltnrec;
307     Cd c0, c1, c2, c3;
308 
309     fltnrec.report = chkDT;
310     c0.x = px;
311     c0.y = -py;
312     c1.x = px1;
313     c1.y = -pcy1;
314     c2.x = px2;
315     c2.y = -py2;
316     c3.x = px3;
317     c3.y = -py3;
318     g_xstate = g_ystate = STARTING;
319     g_xdone = g_ydone = g_xflat = g_yflat = g_inflPtFound = false;
320     g_x0 = c0.x;
321     g_cy0 = c0.y;
322     g_x1 = c3.x;
323     g_cy1 = c3.y;
324     g_xloc = g_x0;
325     g_yloc = g_cy0;
326     g_forMultiMaster = true;
327     FltnCurve(c0, c1, c2, c3, &fltnrec);
328     if (g_inflPtFound)
329         *inflPt = g_fltnvalue;
330     return g_inflPtFound;
331 }
332 
333 static void
CheckSCurve(PathElt * ee)334 CheckSCurve(PathElt* ee)
335 {
336     FltnRec fr;
337     Cd c0, c1, c2, c3;
338     if (ee->type != CURVETO) {
339         LogMsg(LOGERROR, NONFATALERROR, "Malformed path list.");
340     }
341 
342     GetEndPoint(ee->prev, &c0.x, &c0.y);
343     fr.report = chkDT;
344     c1.x = ee->x1;
345     c1.y = ee->y1;
346     c2.x = ee->x2;
347     c2.y = ee->y2;
348     c3.x = ee->x3;
349     c3.y = ee->y3;
350     g_xstate = g_ystate = STARTING;
351     g_xdone = g_ydone = g_xflat = g_yflat = false;
352     g_x0 = c0.x;
353     g_cy0 = c0.y;
354     g_x1 = c3.x;
355     g_cy1 = c3.y;
356     g_xloc = g_x0;
357     g_yloc = g_cy0;
358     g_e = ee;
359     g_forMultiMaster = false;
360     FltnCurve(c0, c1, c2, c3, &fr);
361 }
362 
363 static void
CheckZeroLength(void)364 CheckZeroLength(void)
365 {
366     PathElt *e, *NxtE;
367     Fixed x0, cy0, x1, cy1, x2, y2, x3, y3;
368     if ((!gEditGlyph) || g_forMultiMaster)
369     {
370         /* Do not change topology when hinting MM fonts,
371          and do not edit glyphs if not requested */
372         return;
373     }
374     e = gPathStart;
375     while (e != NULL) { /* delete zero length elements */
376         NxtE = e->next;
377         GetEndPoints(e, &x0, &cy0, &x1, &cy1);
378         if (e->type == LINETO && x0 == x1 && cy0 == cy1) {
379             Delete(e);
380             goto Nxt1;
381         }
382         if (e->type == CURVETO) {
383             x2 = e->x1;
384             y2 = e->y1;
385             x3 = e->x2;
386             y3 = e->y2;
387             if (x0 == x1 && cy0 == cy1 && x2 == x1 && x3 == x1 && y2 == cy1 &&
388                 y3 == cy1) {
389                 Delete(e);
390                 goto Nxt1;
391             }
392         }
393     Nxt1:
394         e = NxtE;
395     }
396 }
397 
398 void
CheckSmooth(void)399 CheckSmooth(void)
400 {
401     PathElt *e, *nxt, *NxtE;
402     bool recheck;
403     Fixed x0, cy0, x1, cy1, x2, y2, x3, y3, smdiff, xx, yy;
404     CheckZeroLength();
405 restart:
406     g_reCheckSmooth = false;
407     recheck = false;
408     e = gPathStart;
409     while (e != NULL) {
410         NxtE = e->next;
411         if (e->type == MOVETO || IsTiny(e) || e->isFlex)
412             goto Nxt;
413         GetEndPoint(e, &x1, &cy1);
414         if (e->type == CURVETO) {
415             int32_t cpd0, cpd1;
416             x2 = e->x1;
417             y2 = e->y1;
418             x3 = e->x2;
419             y3 = e->y2;
420             GetEndPoint(e->prev, &x0, &cy0);
421             cpd0 = CPDirection(x0, cy0, x2, y2, x3, y3);
422             cpd1 = CPDirection(x2, y2, x3, y3, x1, cy1);
423             if (ProdLt0(cpd0, cpd1))
424                 CheckSCurve(e);
425         }
426         nxt = NxtForBend(e, &x2, &y2, &xx, &yy);
427         if (nxt->isFlex)
428             goto Nxt;
429         PrvForBend(nxt, &x0, &cy0);
430         if (!CheckSmoothness(x0, cy0, x1, cy1, x2, y2, &smdiff))
431             LogMsg(INFO, OK, "Junction at %g %g may need smoothing.",
432                    FixToDbl(x1), FixToDbl(-cy1));
433         if (smdiff > FixInt(160))
434             LogMsg(INFO, OK, "Too sharp angle at %g %g has been clipped.",
435                    FixToDbl(x1), FixToDbl(-cy1));
436     Nxt:
437         e = NxtE;
438     }
439     if (g_reCheckSmooth)
440         goto restart;
441     if (!recheck)
442         return;
443     CheckZeroLength();
444     /* in certain cases clip sharp point can produce a zero length line */
445 }
446 
447 #define BBdist                                                                 \
448     (FixInt(20)) /* DEBUG 8 BIT. DOuble value from 10 to 20 for change in      \
449                     coordinate system. */
450 
451 static void
chkBBDT(Cd c)452 chkBBDT(Cd c)
453 {
454     Fixed x = c.x, y = c.y;
455     if (g_bbquit)
456         return;
457     if (g_vert) {
458         g_lst = y;
459         if (!g_started && abs(x - g_loc) <= BBdist) {
460             g_started = true;
461             g_frst = y;
462         } else if (g_started && abs(x - g_loc) > BBdist)
463             g_bbquit = true;
464     } else {
465         g_lst = x;
466         if (!g_started && abs(y - g_loc) <= BBdist) {
467             g_started = true;
468             g_frst = x;
469         } else if (g_started && abs(y - g_loc) > BBdist)
470             g_bbquit = true;
471     }
472 }
473 
474 void
CheckForMultiMoveTo(void)475 CheckForMultiMoveTo(void)
476 {
477     PathElt* e = gPathStart;
478     bool moveto;
479     moveto = false;
480     while (e != NULL) {
481         if (e->type != MOVETO)
482             moveto = false;
483         else if (!moveto)
484             moveto = true;
485         else
486             Delete(e->prev); /* delete previous moveto */
487         e = e->next;
488     }
489 }
490 
491 void
CheckBBoxEdge(PathElt * e,bool vrt,Fixed lc,Fixed * pf,Fixed * pl)492 CheckBBoxEdge(PathElt* e, bool vrt, Fixed lc, Fixed* pf, Fixed* pl)
493 {
494     FltnRec fr;
495     Cd c0, c1, c2, c3;
496     if (e->type != CURVETO) {
497         LogMsg(LOGERROR, NONFATALERROR, "Malformed path list.");
498     }
499 
500     GetEndPoint(e->prev, &c0.x, &c0.y);
501     fr.report = chkBBDT;
502     g_bbquit = false;
503     c1.x = e->x1;
504     c1.y = e->y1;
505     c2.x = e->x2;
506     c2.y = e->y2;
507     c3.x = e->x3;
508     c3.y = e->y3;
509     g_loc = lc;
510     g_vert = vrt;
511     g_started = false;
512     chkBBDT(c0);
513     FltnCurve(c0, c1, c2, c3, &fr);
514     *pf = g_frst;
515     *pl = g_lst;
516 }
517 
518 static void
MakeColinear(Fixed tx,Fixed ty,Fixed x0,Fixed cy0,Fixed x1,Fixed cy1,Fixed * xptr,Fixed * yptr)519 MakeColinear(Fixed tx, Fixed ty, Fixed x0, Fixed cy0, Fixed x1, Fixed cy1,
520              Fixed* xptr, Fixed* yptr)
521 {
522     Fixed dx, dy;
523     float rdx, rdy, dxdy, dxsq, dysq, dsq, xi, yi, rx, ry, rx0, ry0;
524     dx = x1 - x0;
525     dy = cy1 - cy0;
526     if (dx == 0 && dy == 0) {
527         *xptr = tx;
528         *yptr = ty;
529         return;
530     }
531     if (dx == 0) {
532         *xptr = x0;
533         *yptr = ty;
534         return;
535     }
536     if (dy == 0) {
537         *xptr = tx;
538         *yptr = cy0;
539         return;
540     }
541     acfixtopflt(dx, &rdx);
542     acfixtopflt(dy, &rdy);
543     acfixtopflt(x0, &rx0);
544     acfixtopflt(cy0, &ry0);
545     acfixtopflt(tx, &rx);
546     acfixtopflt(ty, &ry);
547     dxdy = rdx * rdy;
548     dxsq = rdx * rdx;
549     dysq = rdy * rdy;
550     dsq = dxsq + dysq;
551     xi = (rx * dxsq + rx0 * dysq + (ry - ry0) * dxdy) / dsq;
552     yi = ry0 + ((xi - rx0) * rdy) / rdx;
553     *xptr = acpflttofix(&xi);
554     *yptr = acpflttofix(&yi);
555 }
556 
557 #define DEG(x) ((x)*57.29577951308232088)
558 static Fixed
ATan(Fixed a,Fixed b)559 ATan(Fixed a, Fixed b)
560 {
561     float aa, bb, cc;
562     acfixtopflt(a, &aa);
563     acfixtopflt(b, &bb);
564     cc = (float)DEG(atan2((double)aa, (double)bb));
565     while (cc < 0)
566         cc += 360.0f;
567     return acpflttofix(&cc);
568 }
569 
570 bool
CheckSmoothness(Fixed x0,Fixed cy0,Fixed x1,Fixed cy1,Fixed x2,Fixed y2,Fixed * pd)571 CheckSmoothness(Fixed x0, Fixed cy0, Fixed x1, Fixed cy1, Fixed x2, Fixed y2,
572                 Fixed* pd)
573 {
574     Fixed dx, dy, smdiff, smx, smy, at0, at1;
575     dx = x0 - x1;
576     dy = cy0 - cy1;
577     *pd = 0;
578     if (dx == 0 && dy == 0)
579         return true;
580     at0 = ATan(dx, dy);
581     dx = x1 - x2;
582     dy = cy1 - y2;
583     if (dx == 0 && dy == 0)
584         return true;
585     at1 = ATan(dx, dy);
586     smdiff = at0 - at1;
587     if (smdiff < 0)
588         smdiff = -smdiff;
589     if (smdiff >= FixInt(180))
590         smdiff = FixInt(360) - smdiff;
591     *pd = smdiff;
592     if (smdiff == 0 || smdiff > FixInt(30))
593         return true;
594     MakeColinear(x1, cy1, x0, cy0, x2, y2, &smx, &smy);
595     smx = FHalfRnd(smx);
596     smy = FHalfRnd(smy);
597     /* DEBUG 8 BIT. Double hard coded distance values, for change from 7 to 8
598      * bits for fractions. */
599     return abs(smx - x1) < FixInt(4) && abs(smy - cy1) < FixInt(4);
600 }
601 
602 void
CheckForDups(void)603 CheckForDups(void)
604 {
605     PathElt *ob, *nxt;
606     Fixed x, y;
607     ob = gPathStart;
608     while (ob != NULL) {
609         nxt = ob->next;
610         if (ob->type == MOVETO) {
611             x = ob->x;
612             y = ob->y;
613             ob = nxt;
614             while (ob != NULL) {
615                 if (ob->type == MOVETO && x == ob->x && y == ob->y)
616                     goto foundMatch;
617                 ob = ob->next;
618             }
619         }
620         ob = nxt;
621     }
622     return;
623 foundMatch:
624     y = -y;
625     ReportDuplicates(x, y);
626 }
627 
628 void
MoveSubpathToEnd(PathElt * e)629 MoveSubpathToEnd(PathElt* e)
630 {
631     PathElt *subEnd, *subStart, *subNext, *subPrev;
632     subEnd = (e->type == CLOSEPATH) ? e : GetClosedBy(e);
633     subStart = GetDest(subEnd);
634     if (subEnd == gPathEnd)
635         return; /* already at end */
636     subNext = subEnd->next;
637     if (subStart == gPathStart) {
638         gPathStart = subNext;
639         subNext->prev = NULL;
640     } else {
641         subPrev = subStart->prev;
642         subPrev->next = subNext;
643         subNext->prev = subPrev;
644     }
645     gPathEnd->next = subStart;
646     subStart->prev = gPathEnd;
647     subEnd->next = NULL;
648     gPathEnd = subEnd;
649 }
650