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 "ac.h"
11 #include "bbox.h"
12 
13 #define MAXF (1 << 15)
14 static void
AdjustVal(Fixed * pv,Fixed l1,Fixed l2,Fixed dist,Fixed d,bool hFlg)15 AdjustVal(Fixed* pv, Fixed l1, Fixed l2, Fixed dist, Fixed d, bool hFlg)
16 {
17     float v, q, r1, r2, rd;
18     /* DEBUG 8 BIT. To get the saem result as the old auothint, had to change
19      from FixedOne to FixedTwo. Since the returned weight is proportional to the
20      square of l1 and l2,
21      these need to be clamped to twice the old clamped value, else when the
22      clamped values are used, the weight comes out as 1/4 of the original value.
23      */
24     if (dist < FixTwo)
25         dist = FixTwo;
26     if (l1 < FixTwo)
27         l1 = FixTwo;
28     if (l2 < FixTwo)
29         l2 = FixTwo;
30     if (abs(l1) < MAXF)
31         r1 = (float)(l1 * l1);
32     else {
33         r1 = (float)l1;
34         r1 = r1 * r1;
35     }
36     if (abs(l2) < MAXF)
37         r2 = (float)(l2 * l2);
38     else {
39         r2 = (float)l2;
40         r2 = r2 * r2;
41     }
42     if (abs(dist) < MAXF)
43         q = (float)(dist * dist);
44     else {
45         q = (float)dist;
46         q = q * q;
47     }
48     v = (float)((1000.0f * r1 * r2) / (q * q));
49     if (d <= (hFlg ? gHBigDist : gVBigDist))
50         goto done;
51     acfixtopflt(d, &rd);
52     q = (hFlg ? gHBigDistR : gVBigDistR) / rd; /* 0 < q < 1.0 */
53     if (q <= 0.5f) {
54         v = 0.0;
55         goto done;
56     }
57     q *= q;
58     q *= q;
59     q *= q;    /* raise q to 8th power */
60     v = v * q; /* if d is twice bigDist, value goes down by factor of 256 */
61 done:
62     if (v > gMaxVal)
63         v = gMaxVal;
64     else if (v > 0.0f && v < gMinVal)
65         v = gMinVal;
66     *pv = acpflttofix(&v);
67 }
68 
69 static Fixed
CalcOverlapDist(Fixed d,Fixed overlaplen,Fixed minlen)70 CalcOverlapDist(Fixed d, Fixed overlaplen, Fixed minlen)
71 {
72     float r = (float)d, ro = (float)overlaplen, rm = (float)minlen;
73     r = r * ((float)(1.0f + 0.4f * (1.0f - ro / rm)));
74     d = (Fixed)r;
75     return d;
76 }
77 
78 #define GapDist(d)                                                             \
79     (((d) < FixInt(127)) ? FTrunc(((d) * (d)) / 40)                            \
80                          : ((int32_t)(((double)(d)) * (d) / (40 * 256))))
81 /* if d is >= 127.0 Fixed, then d*d will overflow the signed int 16 bit value.
82  */
83 /* DEBUG 8 BIT. No idea why d*d was divided by 20, but we need to divide it by 2
84  more to get a dist that is only 2* the old autohint value.
85  With the 8.8 fixed coordinate system, we still overflow a int32_t with
86  d*(d/40), so rather than casting this to a int32_t and then doing >>8, we need
87  to divide by 256, then cast to int32_t.
88  I also fail to understand why the original used FTrunc, which right shifts by
89  256. For the current coordinate space, which has a fractional part of 8 bits,
90  you do need to divide by 256 after doing a simple int multiply, but the
91  previous coordinate space
92     has a 7 bit Fixed fraction, and should be dividing by 128. I suspect that
93  there was a yet earlier version which used a 8 bit fraction, and this is a bug.
94  */
95 
96 static void
EvalHPair(HintSeg * botSeg,HintSeg * topSeg,Fixed * pspc,Fixed * pv)97 EvalHPair(HintSeg* botSeg, HintSeg* topSeg, Fixed* pspc, Fixed* pv)
98 {
99     Fixed brght, blft, bloc, tloc, trght, tlft;
100     Fixed mndist, dist, dy;
101     bool inBotBand, inTopBand;
102     *pspc = 0;
103     brght = botSeg->sMax;
104     blft = botSeg->sMin;
105     trght = topSeg->sMax;
106     tlft = topSeg->sMin;
107     bloc = botSeg->sLoc;
108     tloc = topSeg->sLoc;
109     dy = abs(bloc - tloc);
110     if (dy < gMinDist) {
111         *pv = 0;
112         return;
113     }
114     inBotBand = InBlueBand(bloc, gLenBotBands, gBotBands);
115     inTopBand = InBlueBand(tloc, gLenTopBands, gTopBands);
116     if (inBotBand && inTopBand) { /* delete these */
117         *pv = 0;
118         return;
119     }
120     if (inBotBand || inTopBand) /* up the priority of these */
121         *pspc = FixInt(2);
122     /* left is always < right */
123     if ((tlft <= brght) && (trght >= blft)) { /* overlap */
124         Fixed overlaplen = NUMMIN(trght, brght) - NUMMAX(tlft, blft);
125         Fixed minlen = NUMMIN(trght - tlft, brght - blft);
126         if (minlen == overlaplen)
127             dist = dy;
128         else
129             dist = CalcOverlapDist(dy, overlaplen, minlen);
130     } else { /* no overlap; take closer ends */
131         Fixed dx;
132         Fixed ldst = abs(tlft - brght);
133         Fixed rdst = abs(trght - blft);
134         dx = NUMMIN(ldst, rdst);
135         dist = GapDist(dx);
136         /* extra penalty for nonoverlap changed from 7/5 to 12/5 for
137          * Perpetua/Regular/ n, r ,m and other lowercase serifs; undid change
138          * for Berthold/AkzidenzGrotesk 9/16/91; this did not make Perpetua any
139          * worse. */
140         dist += (7 * dy) / 5;
141         DEBUG_ROUND(dist) /* DEBUG 8 BIT */
142         if (dx > dy)
143             dist *= dx / dy;
144     }
145     mndist = FixTwoMul(gMinDist);
146     dist = NUMMAX(dist, mndist);
147     if (gNumHStems > 0) {
148         int i;
149         Fixed w = abs(dy);
150         for (i = 0; i < gNumHStems; i++)
151             if (w == gHStems[i]) {
152                 *pspc += FixOne;
153                 break;
154             }
155     }
156     AdjustVal(pv, brght - blft, trght - tlft, dist, dy, true);
157 }
158 
159 static void
HStemMiss(HintSeg * botSeg,HintSeg * topSeg)160 HStemMiss(HintSeg* botSeg, HintSeg* topSeg)
161 {
162     Fixed brght, blft, bloc, tloc, trght, tlft;
163     Fixed mndist, dist, dy;
164     Fixed b, t, minDiff, minW, w;
165     int i;
166     if (gNumHStems == 0)
167         return;
168     brght = botSeg->sMax;
169     blft = botSeg->sMin;
170     trght = topSeg->sMax;
171     tlft = topSeg->sMin;
172     bloc = botSeg->sLoc;
173     tloc = topSeg->sLoc;
174     dy = abs(bloc - tloc);
175     if (dy < gMinDist)
176         return;
177     /* left is always < right */
178     if ((tlft <= brght) && (trght >= blft)) { /* overlap */
179         Fixed overlaplen = NUMMIN(trght, brght) - NUMMAX(tlft, blft);
180         Fixed minlen = NUMMIN(trght - tlft, brght - blft);
181         if (minlen == overlaplen)
182             dist = dy;
183         else
184             dist = CalcOverlapDist(dy, overlaplen, minlen);
185     } else
186         return;
187     mndist = FixTwoMul(gMinDist);
188     if (dist < mndist)
189         return;
190     minDiff = FixInt(1000);
191     minW = 0;
192     b = -bloc;
193     t = -tloc;
194     w = t - b;
195     /* don't check ghost bands for near misses */
196     if (((w = t - b) == botGhst) || (w == topGhst))
197         return;
198     w = abs(w);
199     for (i = 0; i < gNumHStems; i++) {
200         Fixed sw = gHStems[i];
201         Fixed diff = abs(sw - w);
202         if (diff == 0)
203             return;
204         if (diff < minDiff) {
205             minDiff = diff;
206             minW = sw;
207         }
208     }
209     if (minDiff > FixInt(2))
210         return;
211     ReportStemNearMiss(false, w, minW, b, t,
212                        (botSeg->sType == sCURVE) || (topSeg->sType == sCURVE));
213 }
214 
215 static void
EvalVPair(HintSeg * leftSeg,HintSeg * rightSeg,Fixed * pspc,Fixed * pv)216 EvalVPair(HintSeg* leftSeg, HintSeg* rightSeg, Fixed* pspc, Fixed* pv)
217 {
218     Fixed ltop, lbot, lloc, rloc, rtop, rbot;
219     Fixed mndist, dx, dist;
220     Fixed bonus, lbonus, rbonus;
221     *pspc = 0;
222     ltop = leftSeg->sMax;
223     lbot = leftSeg->sMin;
224     rtop = rightSeg->sMax;
225     rbot = rightSeg->sMin;
226     lloc = leftSeg->sLoc;
227     rloc = rightSeg->sLoc;
228     dx = abs(lloc - rloc);
229     if (dx < gMinDist) {
230         *pv = 0;
231         return;
232     }
233     /* top is always > bot, independent of YgoesUp */
234     if ((ltop >= rbot) && (lbot <= rtop)) { /* overlap */
235         Fixed overlaplen = NUMMIN(ltop, rtop) - NUMMAX(lbot, rbot);
236         Fixed minlen = NUMMIN(ltop - lbot, rtop - rbot);
237         if (minlen == overlaplen)
238             dist = dx;
239         else
240             dist = CalcOverlapDist(dx, overlaplen, minlen);
241     } else { /* no overlap; take closer ends */
242         Fixed tdst = abs(ltop - rbot);
243         Fixed bdst = abs(lbot - rtop);
244         Fixed dy = NUMMIN(tdst, bdst);
245         dist = (7 * dx) / 5 + GapDist(dy); /* extra penalty for nonoverlap */
246         DEBUG_ROUND(dist)                  /* DEBUG 8 BIT */
247         if (dy > dx)
248             dist *= dy / dx;
249     }
250     mndist = FixTwoMul(gMinDist);
251     dist = NUMMAX(dist, mndist);
252     lbonus = leftSeg->sBonus;
253     rbonus = rightSeg->sBonus;
254     bonus = NUMMIN(lbonus, rbonus);
255     *pspc = (bonus > 0) ? FixInt(2) : 0; /* this is for sol-eol characters */
256     if (gNumVStems > 0) {
257         int i;
258         Fixed w = abs(dx);
259         for (i = 0; i < gNumVStems; i++)
260             if (w == gVStems[i]) {
261                 *pspc = *pspc + FixOne;
262                 break;
263             }
264     }
265     AdjustVal(pv, ltop - lbot, rtop - rbot, dist, dx, false);
266 }
267 
268 static void
VStemMiss(HintSeg * leftSeg,HintSeg * rightSeg)269 VStemMiss(HintSeg* leftSeg, HintSeg* rightSeg)
270 {
271     Fixed ltop, lbot, lloc, rloc, rtop, rbot;
272     Fixed dx, l, r, minDiff, minW, w;
273     int i;
274     if (gNumVStems == 0)
275         return;
276     ltop = leftSeg->sMax;
277     lbot = leftSeg->sMin;
278     rtop = rightSeg->sMax;
279     rbot = rightSeg->sMin;
280     lloc = leftSeg->sLoc;
281     rloc = rightSeg->sLoc;
282     dx = abs(lloc - rloc);
283     if (dx < gMinDist)
284         return;
285     /* top is always > bot, independent of YgoesUp */
286     if ((ltop < rbot) || (lbot > rtop))  /* does not overlap */
287         return;
288 
289     l = lloc;
290     r = rloc;
291     w = abs(r - l);
292     minDiff = FixInt(1000);
293     minW = 0;
294     for (i = 0; i < gNumVStems; i++) {
295         Fixed sw = gVStems[i];
296         Fixed diff = abs(sw - w);
297         if (diff < minDiff) {
298             minDiff = diff;
299             minW = sw;
300         }
301         if (minDiff == 0)
302             return;
303     }
304     if (minDiff > FixInt(2))
305         return;
306     ReportStemNearMiss(true, w, minW, l, r,
307                        (leftSeg->sType == sCURVE) ||
308                          (rightSeg->sType == sCURVE));
309 }
310 
311 static void
InsertVValue(Fixed lft,Fixed rght,Fixed val,Fixed spc,HintSeg * lSeg,HintSeg * rSeg)312 InsertVValue(Fixed lft, Fixed rght, Fixed val, Fixed spc, HintSeg* lSeg,
313              HintSeg* rSeg)
314 {
315     HintVal *item, *vlist, *vprev;
316     item = (HintVal*)Alloc(sizeof(HintVal));
317     item->vVal = val;
318     item->initVal = val;
319     item->vLoc1 = lft;
320     item->vLoc2 = rght;
321     item->vSpc = spc;
322     item->vSeg1 = lSeg;
323     item->vSeg2 = rSeg;
324     item->vGhst = false;
325     vlist = gValList;
326     vprev = NULL;
327     while (vlist != NULL) {
328         if (vlist->vLoc1 >= lft)
329             break;
330         vprev = vlist;
331         vlist = vlist->vNxt;
332     }
333     while (vlist != NULL && vlist->vLoc1 == lft) {
334         if (vlist->vLoc2 >= rght)
335             break;
336         vprev = vlist;
337         vlist = vlist->vNxt;
338     }
339     if (vprev == NULL)
340         gValList = item;
341     else
342         vprev->vNxt = item;
343     item->vNxt = vlist;
344     ReportAddVVal(item);
345 }
346 
347 #define LePruneValue(val) ((val) < FixOne && ((val) << 10) <= gPruneValue)
348 
349 static void
AddVValue(Fixed lft,Fixed rght,Fixed val,Fixed spc,HintSeg * lSeg,HintSeg * rSeg)350 AddVValue(Fixed lft, Fixed rght, Fixed val, Fixed spc, HintSeg* lSeg,
351           HintSeg* rSeg)
352 {
353     if (val == 0)
354         return;
355     if (LePruneValue(val) && spc <= 0)
356         return;
357     if (lSeg != NULL && lSeg->sType == sBEND && rSeg != NULL &&
358         rSeg->sType == sBEND)
359         return;
360     if (val <= gPruneD && spc <= 0 && lSeg != NULL && rSeg != NULL) {
361         if (lSeg->sType == sBEND || rSeg->sType == sBEND ||
362             !CheckBBoxes(lSeg->sElt, rSeg->sElt))
363             return;
364     }
365     if (rSeg == NULL)
366         return;
367     InsertVValue(lft, rght, val, spc, lSeg, rSeg);
368 }
369 
370 static void
InsertHValue(Fixed bot,Fixed top,Fixed val,Fixed spc,HintSeg * bSeg,HintSeg * tSeg,bool ghst)371 InsertHValue(Fixed bot, Fixed top, Fixed val, Fixed spc, HintSeg* bSeg,
372              HintSeg* tSeg, bool ghst)
373 {
374     HintVal *item, *vlist, *vprev, *vl;
375     vlist = gValList;
376     vprev = NULL;
377     while (vlist != NULL) {
378         if (vlist->vLoc2 >= top)
379             break;
380         vprev = vlist;
381         vlist = vlist->vNxt;
382     }
383     while (vlist != NULL && vlist->vLoc2 == top) {
384         if (vlist->vLoc1 >= bot)
385             break;
386         vprev = vlist;
387         vlist = vlist->vNxt;
388     }
389     /* prune ghost pair that is same as non ghost pair for same segment
390  only if val for ghost is less than an existing val with same
391  top and bottom segment (vl) */
392     vl = vlist;
393     while (ghst && vl != NULL && vl->vLoc2 == top && vl->vLoc1 == bot) {
394         if (!vl->vGhst && (vl->vSeg1 == bSeg || vl->vSeg2 == tSeg) &&
395             vl->vVal > val)
396             return;
397         vl = vl->vNxt;
398     }
399     item = (HintVal*)Alloc(sizeof(HintVal));
400     item->vVal = val;
401     item->initVal = val;
402     item->vSpc = spc;
403     item->vLoc1 = bot;
404     item->vLoc2 = top;
405     item->vSeg1 = bSeg;
406     item->vSeg2 = tSeg;
407     item->vGhst = ghst;
408     if (vprev == NULL)
409         gValList = item;
410     else
411         vprev->vNxt = item;
412     item->vNxt = vlist;
413     ReportAddHVal(item);
414 }
415 
416 static void
AddHValue(Fixed bot,Fixed top,Fixed val,Fixed spc,HintSeg * bSeg,HintSeg * tSeg)417 AddHValue(Fixed bot, Fixed top, Fixed val, Fixed spc, HintSeg* bSeg,
418           HintSeg* tSeg)
419 {
420     bool ghst;
421     if (val == 0)
422         return;
423     if (LePruneValue(val) && spc <= 0)
424         return;
425     if (bSeg->sType == sBEND && tSeg->sType == sBEND)
426         return;
427     ghst = bSeg->sType == sGHOST || tSeg->sType == sGHOST;
428     if (!ghst && val <= gPruneD && spc <= 0) {
429         if (bSeg->sType == sBEND || tSeg->sType == sBEND ||
430             !CheckBBoxes(bSeg->sElt, tSeg->sElt))
431             return;
432     }
433     InsertHValue(bot, top, val, spc, bSeg, tSeg, ghst);
434 }
435 
436 static float
mfabs(float in)437 mfabs(float in)
438 {
439     if (in > 0)
440         return in;
441     return -in;
442 }
443 
444 static Fixed
CombVals(Fixed v1,Fixed v2)445 CombVals(Fixed v1, Fixed v2)
446 {
447     int32_t i;
448     float r1, r2;
449     float x, a, xx = 0;
450     acfixtopflt(v1, &r1);
451     acfixtopflt(v2, &r2);
452     /* home brew sqrt */
453     a = r1 * r2;
454     x = a;
455     for (i = 0; i < 16; i++) {
456         xx = ((float)0.5) * (x + a / x);
457         if (i >= 8 && mfabs(xx - x) <= mfabs(xx) * 0.0000001f)
458             break;
459         x = xx;
460     }
461     r1 += r2 + ((float)2.0) * xx;
462     if (r1 > gMaxVal)
463         r1 = gMaxVal;
464     else if (r1 > 0 && r1 < gMinVal)
465         r1 = gMinVal;
466     return acpflttofix(&r1);
467 }
468 
469 static void
CombineValues(void)470 CombineValues(void)
471 { /* works for both H and V */
472     HintVal* vlist = gValList;
473     while (vlist != NULL) {
474         HintVal* v1 = vlist->vNxt;
475         Fixed loc1 = vlist->vLoc1;
476         Fixed loc2 = vlist->vLoc2;
477         Fixed val = vlist->vVal;
478         bool match = false;
479         while (v1 != NULL && v1->vLoc1 == loc1 && v1->vLoc2 == loc2) {
480             if (v1->vGhst)
481                 val = v1->vVal;
482             else
483                 val = CombVals(val, v1->vVal);
484             /* increase value to compensate for length squared effect */
485             match = true;
486             v1 = v1->vNxt;
487         }
488         if (match) {
489             while (vlist != v1) {
490                 vlist->vVal = val;
491                 vlist = vlist->vNxt;
492             }
493         } else
494             vlist = v1;
495     }
496 }
497 
498 void
EvalV(void)499 EvalV(void)
500 {
501     HintSeg *lList, *rList;
502     Fixed lft, rght;
503     Fixed val, spc;
504     gValList = NULL;
505     lList = leftList;
506     while (lList != NULL) {
507         rList = rightList;
508         while (rList != NULL) {
509             lft = lList->sLoc;
510             rght = rList->sLoc;
511             if (lft < rght) {
512                 EvalVPair(lList, rList, &spc, &val);
513                 VStemMiss(lList, rList);
514                 AddVValue(lft, rght, val, spc, lList, rList);
515             }
516             rList = rList->sNxt;
517         }
518         lList = lList->sNxt;
519     }
520     CombineValues();
521 }
522 
523 void
EvalH(void)524 EvalH(void)
525 {
526     HintSeg *bList, *tList, *lst, *ghostSeg;
527     Fixed lstLoc, tempLoc, cntr;
528     Fixed val, spc;
529     gValList = NULL;
530     bList = botList;
531     while (bList != NULL) {
532         tList = topList;
533         while (tList != NULL) {
534             Fixed bot, top;
535             bot = bList->sLoc;
536             top = tList->sLoc;
537             if (bot > top) {
538                 EvalHPair(bList, tList, &spc, &val);
539                 HStemMiss(bList, tList);
540                 AddHValue(bot, top, val, spc, bList, tList);
541             }
542             tList = tList->sNxt;
543         }
544         bList = bList->sNxt;
545     }
546     ghostSeg = (HintSeg*)Alloc(sizeof(HintSeg));
547     ghostSeg->sType = sGHOST;
548     ghostSeg->sElt = NULL;
549     if (gLenBotBands < 2 && gLenTopBands < 2)
550         goto done;
551     lst = botList;
552     while (lst != NULL) {
553         lstLoc = lst->sLoc;
554         if (InBlueBand(lstLoc, gLenBotBands, gBotBands)) {
555             tempLoc = lstLoc - gGhostWidth;
556             ghostSeg->sLoc = tempLoc;
557             cntr = (lst->sMax + lst->sMin) / 2;
558             ghostSeg->sMax = cntr + gGhostLength / 2;
559             ghostSeg->sMin = cntr - gGhostLength / 2;
560             DEBUG_ROUND(ghostSeg->sMax) /* DEBUG 8 BIT */
561             DEBUG_ROUND(ghostSeg->sMin) /* DEBUG 8 BIT */
562             spc = FixInt(2);
563             val = FixInt(20);
564             AddHValue(lstLoc, tempLoc, val, spc, lst, ghostSeg);
565         }
566         lst = lst->sNxt;
567     }
568     lst = topList;
569     while (lst != NULL) {
570         lstLoc = lst->sLoc;
571         if (InBlueBand(lstLoc, gLenTopBands, gTopBands)) {
572             tempLoc = lstLoc + gGhostWidth;
573             ghostSeg->sLoc = tempLoc;
574             cntr = (lst->sMin + lst->sMax) / 2;
575             ghostSeg->sMax = cntr + gGhostLength / 2;
576             ghostSeg->sMin = cntr - gGhostLength / 2;
577             spc = FixInt(2);
578             val = FixInt(20);
579             AddHValue(tempLoc, lstLoc, val, spc, ghostSeg, lst);
580         }
581         lst = lst->sNxt;
582     }
583 done:
584     CombineValues();
585 }
586