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 static void DoHStems(HintVal* sLst1);
14 static void DoVStems(HintVal* sLst);
15 
16 static bool CounterFailed;
17 
18 void
InitAll(int32_t reason)19 InitAll(int32_t reason)
20 {
21     InitData(reason); /* must be first */
22     InitFix(reason);
23     InitGen(reason);
24     InitPick(reason);
25 }
26 
27 static int32_t
PtLstLen(HintPoint * lst)28 PtLstLen(HintPoint* lst)
29 {
30     int32_t cnt = 0;
31     while (lst != NULL) {
32         cnt++;
33         lst = lst->next;
34     }
35     return cnt;
36 }
37 
38 static int32_t
PointListCheck(HintPoint * new,HintPoint * lst)39 PointListCheck(HintPoint* new, HintPoint* lst)
40 {
41     /* -1 means not a member, 1 means already a member, 0 means conflicts */
42     Fixed l1 = 0, l2 = 0, n1 = 0, n2 = 0, tmp, halfMargin;
43     char ch = new->c;
44     halfMargin = FixHalfMul(gBandMargin);
45     halfMargin = FixHalfMul(halfMargin);
46     /* DEBUG 8 BIT. In the previous version, with 7 bit fraction coordinates
47     instead of the current
48     8 bit, bandMargin is declared as 30, but scaled by half to match the 7 bit
49     fraction coordinate -> a value of 15.
50     In the current version this scaling doesn't happen. However, in this part of
51     the code, the hint values are scaled up to 8 bits of fraction even in the
52     original version, but topBand is applied without correcting for the scaling
53     difference. In this version  I need to divide by half again in order to get
54     to the same value. I think the original is a bug, but it has been working
55     for 30 years, so I am not going to change the test now.
56      */
57     switch (ch) {
58         case 'y':
59         case 'm': {
60             n1 = new->x0;
61             n2 = new->x1;
62             break;
63         }
64         case 'b':
65         case 'v': {
66             n1 = new->y0;
67             n2 = new->y1;
68             break;
69         }
70         default: {
71             LogMsg(LOGERROR, NONFATALERROR, "Illegal character in point list.");
72         }
73     }
74     if (n1 > n2) {
75         tmp = n1;
76         n1 = n2;
77         n2 = tmp;
78     }
79     while (true) {
80         if (lst == NULL) {
81             return -1;
82         }
83         if (lst->c == ch) { /* same kind of hint */
84             switch (ch) {
85                 case 'y':
86                 case 'm': {
87                     l1 = lst->x0;
88                     l2 = lst->x1;
89                     break;
90                 }
91                 case 'b':
92                 case 'v': {
93                     l1 = lst->y0;
94                     l2 = lst->y1;
95                     break;
96                 }
97             }
98             if (l1 > l2) {
99                 tmp = l1;
100                 l1 = l2;
101                 l2 = tmp;
102             }
103             if (l1 == n1 && l2 == n2) {
104                 return 1;
105             }
106             /* Add this extra margin to the band to fix a problem in
107              TimesEuropa/Italic/v,w,y where a main hstem hint was
108              being merged with newhints. This main hstem caused
109              problems in rasterization so it shouldn't be included. */
110             l1 -= halfMargin;
111             l2 += halfMargin;
112             if (l1 <= n2 && n1 <= l2) {
113                 return 0;
114             }
115         }
116         lst = lst->next;
117     }
118 }
119 
120 static bool
SameHintLists(HintPoint * lst1,HintPoint * lst2)121 SameHintLists(HintPoint* lst1, HintPoint* lst2)
122 {
123     if (PtLstLen(lst1) != PtLstLen(lst2)) {
124         return false;
125     }
126     while (lst1 != NULL) { /* go through lst1 */
127         if (PointListCheck(lst1, lst2) != 1) {
128             return false;
129         }
130         lst1 = lst1->next;
131     }
132     return true;
133 }
134 
135 bool
SameHints(int32_t cn1,int32_t cn2)136 SameHints(int32_t cn1, int32_t cn2)
137 {
138     if (cn1 == cn2) {
139         return true;
140     }
141     return SameHintLists(gPtLstArray[cn1], gPtLstArray[cn2]);
142 }
143 
144 void
MergeFromMainHints(char ch)145 MergeFromMainHints(char ch)
146 {
147     HintPoint* lst;
148     for (lst = gPtLstArray[0]; lst != NULL; lst = lst->next) {
149         if (lst->c != ch) {
150             continue;
151         }
152         if (PointListCheck(lst, gPointList) == -1) {
153             if (ch == 'b') {
154                 AddHintPoint(0, lst->y0, 0, lst->y1, ch, lst->p0, lst->p1);
155             } else {
156                 AddHintPoint(lst->x0, 0, lst->x1, 0, ch, lst->p0, lst->p1);
157             }
158         }
159     }
160 }
161 
162 void
AddHintPoint(Fixed x0,Fixed y0,Fixed x1,Fixed y1,char ch,PathElt * p0,PathElt * p1)163 AddHintPoint(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch, PathElt* p0,
164              PathElt* p1)
165 {
166     HintPoint* pt;
167     int32_t chk;
168     pt = (HintPoint*)Alloc(sizeof(HintPoint));
169     pt->x0 = x0;
170     pt->y0 = y0;
171     pt->x1 = x1;
172     pt->y1 = y1;
173     pt->c = ch;
174     pt->done = false;
175     pt->next = NULL;
176     pt->p0 = p0;
177     pt->p1 = p1;
178     chk = PointListCheck(pt, gPointList);
179     if (chk == 0) {
180         ReportHintConflict(x0, y0, x1, y1, ch);
181     }
182     if (chk == -1) {
183         pt->next = gPointList;
184         gPointList = pt;
185         LogHintInfo(gPointList);
186     }
187 }
188 
189 static void
CopyHintFromLst(char hint,HintPoint * lst)190 CopyHintFromLst(char hint, HintPoint* lst)
191 {
192     bool bvflg = (hint == 'b' || hint == 'v');
193     while (lst != NULL) {
194         if (lst->c == hint) {
195             if (bvflg) {
196                 AddHintPoint(0, lst->y0, 0, lst->y1, hint, lst->p0, lst->p1);
197             } else {
198                 AddHintPoint(lst->x0, 0, lst->x1, 0, hint, lst->p0, lst->p1);
199             }
200         }
201         lst = lst->next;
202     }
203 }
204 
205 void
CopyMainV(void)206 CopyMainV(void)
207 {
208     CopyHintFromLst('m', gPtLstArray[0]);
209 }
210 
211 void
CopyMainH(void)212 CopyMainH(void)
213 {
214     CopyHintFromLst('v', gPtLstArray[0]);
215 }
216 
217 void
AddHPair(HintVal * v,char ch)218 AddHPair(HintVal* v, char ch)
219 {
220     Fixed bot, top;
221     PathElt *p0, *p1, *p;
222     bot = -v->vLoc1;
223     top = -v->vLoc2;
224     p0 = v->vBst->vSeg1->sElt;
225     p1 = v->vBst->vSeg2->sElt;
226     if (top < bot) {
227         Fixed tmp = top;
228         top = bot;
229         bot = tmp;
230         p = p0;
231         p0 = p1;
232         p1 = p;
233     }
234     if (v->vGhst) {
235         if (v->vSeg1->sType == sGHOST) {
236             bot = top;
237             p0 = p1;
238             p1 = NULL;
239             top = bot - FixInt(20); /* width == -20 iff bottom seg is ghost */
240         } else {
241             top = bot;
242             p1 = p0;
243             p0 = NULL;
244             bot = top + FixInt(21); /* width == -21 iff top seg is ghost */
245         }
246     }
247     AddHintPoint(0, bot, 0, top, ch, p0, p1);
248 }
249 
250 void
AddVPair(HintVal * v,char ch)251 AddVPair(HintVal* v, char ch)
252 {
253     Fixed lft, rght;
254     PathElt *p0, *p1, *p;
255     lft = v->vLoc1;
256     rght = v->vLoc2;
257     p0 = v->vBst->vSeg1->sElt;
258     p1 = v->vBst->vSeg2->sElt;
259     if (lft > rght) {
260         Fixed tmp = lft;
261         lft = rght;
262         rght = tmp;
263         p = p0;
264         p0 = p1;
265         p1 = p;
266     }
267     AddHintPoint(lft, 0, rght, 0, ch, p0, p1);
268 }
269 
270 static bool
UseCounter(HintVal * sLst,bool mhint)271 UseCounter(HintVal* sLst, bool mhint)
272 {
273     int32_t cnt = 0;
274     Fixed minLoc, midLoc, maxLoc, prevBstVal, bestVal;
275     Fixed minDelta, midDelta, maxDelta, th;
276     HintVal *lst, *newLst;
277     minLoc = midLoc = maxLoc = FixInt(20000);
278     minDelta = midDelta = maxDelta = 0;
279     lst = sLst;
280     while (lst != NULL) {
281         cnt++;
282         lst = lst->vNxt;
283     }
284     if (cnt < 3) {
285         return false;
286     }
287     cnt -= 3;
288     prevBstVal = 0;
289     while (cnt > 0) {
290         cnt--;
291         if (cnt == 0) {
292             prevBstVal = sLst->vVal;
293         }
294         sLst = sLst->vNxt;
295     }
296     bestVal = sLst->vVal;
297     if (prevBstVal > FixInt(1000) || bestVal < prevBstVal * 10) {
298         return false;
299     }
300     newLst = sLst;
301     while (sLst != NULL) {
302         Fixed loc = sLst->vLoc1;
303         Fixed delta = sLst->vLoc2 - loc;
304         loc += FixHalfMul(delta);
305         if (loc < minLoc) {
306             maxLoc = midLoc;
307             maxDelta = midDelta;
308             midLoc = minLoc;
309             midDelta = minDelta;
310             minLoc = loc;
311             minDelta = delta;
312         } else if (loc < midLoc) {
313             maxLoc = midLoc;
314             maxDelta = midDelta;
315             midLoc = loc;
316             midDelta = delta;
317         } else {
318             maxLoc = loc;
319             maxDelta = delta;
320         }
321         sLst = sLst->vNxt;
322     }
323     th = FixInt(5) / 100;
324     if (abs(minDelta - maxDelta) < th &&
325         abs((maxLoc - midLoc) - (midLoc - minLoc)) < th) {
326         if (mhint) {
327             gVHinting = newLst;
328         } else {
329             gHHinting = newLst;
330         }
331         return true;
332     }
333     if (abs(minDelta - maxDelta) < FixInt(3) &&
334         abs((maxLoc - midLoc) - (midLoc - minLoc)) < FixInt(3)) {
335         LogMsg(INFO, OK,
336                mhint ? "Near miss for using V counter hinting."
337                      : "Near miss for using H counter hinting.");
338     }
339     return false;
340 }
341 
342 static void
GetNewPtLst(void)343 GetNewPtLst(void)
344 {
345     if (gNumPtLsts >= gMaxPtLsts) { /* increase size */
346         HintPoint** newArray;
347         int32_t i;
348         int32_t newSize = gMaxPtLsts * 2;
349         newArray = (HintPoint**)Alloc(newSize * sizeof(HintPoint*));
350         for (i = 0; i < gMaxPtLsts; i++) {
351             newArray[i] = gPtLstArray[i];
352         }
353         gPtLstArray = newArray;
354         gMaxPtLsts = newSize;
355     }
356     gPtLstIndex = gNumPtLsts;
357     gNumPtLsts++;
358     gPointList = NULL;
359     gPtLstArray[gPtLstIndex] = NULL;
360 }
361 
362 void
XtraHints(PathElt * e)363 XtraHints(PathElt* e)
364 {
365     /* this can be simplified for standalone hinting */
366     gPtLstArray[gPtLstIndex] = gPointList;
367     if (e->newhints == 0) {
368         GetNewPtLst();
369         e->newhints = (int16_t)gPtLstIndex;
370     }
371     gPtLstIndex = e->newhints;
372     gPointList = gPtLstArray[gPtLstIndex];
373 }
374 
375 static void
Blues(unsigned char * links)376 Blues(unsigned char* links)
377 {
378     Fixed pv = 0, pd = 0, pc = 0, pb = 0, pa = 0;
379     HintVal* sLst;
380 
381     /*
382      * Top alignment zones are in the global 'gTopBands', bottom in
383      * 'gBotBands'.
384      *
385      * This function looks through the path, as defined by the linked list of
386      * PathElt's, starting at the global 'gPathStart', and adds to several
387      * lists.  Coordinates are stored in the PathElt.(x,y) as (original
388      * value)/2.0, aka right shifted by 1 bit from the original 24.8 Fixed. I
389      * suspect that is to allow a larger integer portion - when this program
390      * was written, an int was 16 bits.
391      *
392      * 'gHStems' and 'gVStems' are global arrays of Fixed 24.8 numbers..
393      *
394      * 'gSegLists' is an array of 4 HintSeg linked lists. List 0 and 1 are
395      * respectively up and down vertical segments. Lists 2 and 3 are
396      * respectively left pointing and right pointing horizontal segments. On a
397      * counter-clockwise path, this is the same as selecting top and bottom
398      * stem locations.
399      *
400      * NoBlueGlyph() consults a hard-coded list of glyph names, if the glyph is
401      * in this list, set the alignment zones ('gTopBands' and 'gBotBands') to
402      * empty.
403      *
404      * 1) gen.c:GenHPts()
405      *    Builds the raw list of stem segments in global
406      *    'topList' and 'botList'. It steps through the liked list of path
407      *    segments, starting at 'gPathStart'. It decides if a path is mostly H,
408      *    and if so, adds it to a linked list of hstem candidates in gSegLists,
409      *    by calling gen.c:AddHSegment(). This calls ReportAddHSeg() (useful in
410      *    debugging), and then gen.c:AddSegment().
411      *
412      *    If the path segment is in fact entirely vertical and is followed by a
413      *    sharp bend, gen.c:GenHPts() adds two new path segments just 1 unit
414      *    long, after the segment end point, called H/VBends (segment type
415      *    sBend=1). I have no idea what these are for.
416      *
417      *    AddSegment() is pretty simple. It creates a new hint segment
418      *    (HintSeg) for the parent PathElt, fills it in, adds it to appropriate
419      *    list of the 4 gSegLists, and then sorts by hstem location. seg->sElt
420      *    is the parent PathElt, seg->sType is the type, seg->sLoc is the
421      *    location in Fixed 18.14: right shift 7 to get integer value.
422      *
423      *    If the current PathElt is a Closepath, It also calls LinkSegment() to
424      *    add the current stem segment to the list of stem segments referenced
425      *    by this elt's e->Hs/Vs.
426      *
427      *    Note that a hint segment is created for each nearly vertical or
428      *    horizontal PathElt. This means that in an H, there will be two hint
429      *    segments created for the bottom and top of the H, as there are two
430      *    horizontal paths with the same Y at the top and bottom of the H.
431      *
432      *    Assign the top and bottom Hstem location lists.
433      *    topList = segLists[2]
434      *    botList = segLists[3];
435      *
436      * 2) eval.c::EvalH()
437      *    Evaluates every combination of botList and topList, and assign a
438      *    priority value and a 'Q' value.
439      *
440      *    For each bottom stem
441      *    for each top stem
442      *    1) assign priority (spc) and weight (val) values with EvalHPair()
443      *    2) report stem near misses  in the 'HStems' list with HStemMiss()
444      *    3) decide whether to add pair to 'HStems' list with AddHValue()
445      *
446      *     Add ghost hints.
447      *     For each bottom stem segment and then for each top stem segment:
448      *     if it is in an alignment zone, make a ghost hint segment and add it
449      *     with AddHValue().
450      *
451      *     EvalHPair() sets priority (spc) and weight (val) values.
452      *       Omit pair by setting value to 0 if:
453      *         bottom is in bottom alignment zone, and top is in top alignment
454      *         zone. (otherwise, these will override the ghost hints).
455      *
456      *       Boost priority by +2 if either the bot or top segment is in an
457      *       alignment zone.
458      *
459      *       dy = stem width ( top - bot)
460      *
461      *       Calculate dist. Dist is set to a fudge factor * dy.
462      *         if bottom segment xo->x1 overlaps top x0->x1, the fudge factor is
463      *         1.0. The less the overlap, the larger the fduge factor.
464      *         if bottom segment xo->x1 overlaps top x0->x1:.
465      *           if  top and bottom overlap exactly, dist = dy
466      *           if they barely overlap, dist = 1.4*dy
467      *           in between, interpolate.
468      *         else, look at closest ends betwen bottom and top segments.
469      *           dx = min X separation between top and bottom segments.
470      *           dist = 1.4 *dy
471      *           dist += dx*dx
472      *           if dx > dy:
473      *             dist *= dx / dy;
474      *
475      *       Look through the gHStems global list. For each match to dy, boost
476      *       priority by +1.
477      *
478      *       Calculate weight with gen.c:AdjustVal()
479      *         if dy is more than twice the 1.1.5* the largest hint in gHStems,
480      *         set weight to 0.
481      *         Calculate weight as related to length of the segments squared
482      *         divided by the distance squared.
483      *         Basically, the greater the ratio segment overlap to stem width,
484      *         the higher the value.
485      *         if dy is greater than the largest stem hint in gHStems, decrease
486      *         the value scale weight by  of * (largest stem hint in
487      *         gHStems)/dy)**3.
488      *
489      *     AddHValue() decides whether add a (bottom, top)  pair of hint segments.
490      *     Do not add the pair if:
491      *     if weight (val) is 0,
492      *     if both are sBEND segments
493      *     if neither are a ghost hint, and weight <= pruneD and priority (spc)
494      *     is <= 0:
495      *     if either is an sBEND: skip
496      *     if the BBox for one segment is the same or inside the BBox for the
497      *     other: skip
498      *
499      *     else add it with eval.c:InsertHValue()
500      *     add new HintVal to global valList.
501      *     item->vVal = val; # weight
502      *     item->initVal = val; # originl weight from EvalHPair()
503      *     item->vSpc = spc; # priority
504      *     item->vLoc1 = bot; # bottom Y value in Fixed 18.14
505      *     item->vLoc2 = top; # top Y value in Fixed 18.14
506      *     item->vSeg1 = bSeg; # bottom hint segment
507      *     item->vSeg2 = tSeg; # top hint segment
508      *     item->vGhst = ghst; # if it is a ghost segment.
509      *     The new item is inserted after the first element where vlist->vLoc2 >= top
510      *    and vlist->vLoc1 >= bottom
511      *
512      * 3) merge.c:PruneHVals();
513      *
514      *    item2 in the list knocks out item1 if:
515      *    1) (item2 val is more than 3* greater than item1 val) and
516      *       (val 1 is less than FixedInt(100)) and
517      *       (item2 top and bottom is within item 1 top and bottom) and
518      *       (if val1 is more than 50* less than val2 and either top
519      *        seg1 is close to top seg 2, or bottom seg1 is close to
520      *        bottom seg 2) and
521      *       (val 1 < FixInt(16)) or
522      *       ((item1 top not in blue zone, or top1 = top2) and
523      *        (item1 bottom not in blue zone, or top1 = bottom2))
524      *    "Close to" for the bottom segment means you can get to the bottom elt for
525      *    item 2 from bottom elt for 1 within the same path, by
526      *     stepping either forward or back from item 1's elt, and without going
527      *    outside the bounds between
528      *     location 1 and location 2. Same for top segments.
529      *
530      * 4) pick.c:FindBestHVals();
531      * When a hint segment
532      */
533 
534     LogMsg(LOGDEBUG, OK, "generate blues");
535     if (NoBlueGlyph()) {
536         gLenTopBands = gLenBotBands = 0;
537     }
538     GenHPts();
539     LogMsg(LOGDEBUG, OK, "evaluate");
540     if (!CounterFailed && HHintGlyph()) {
541         pv = gPruneValue;
542         gPruneValue = (Fixed)gMinVal;
543         pa = gPruneA;
544         gPruneA = (Fixed)gMinVal;
545         pd = gPruneD;
546         gPruneD = (Fixed)gMinVal;
547         pc = gPruneC;
548         gPruneC = (Fixed)gMaxVal;
549         pb = gPruneB;
550         gPruneB = (Fixed)gMinVal;
551     }
552     EvalH();
553     PruneHVals();
554     FindBestHVals();
555     MergeVals(false);
556 
557     ShowHVals(gValList);
558     LogMsg(LOGDEBUG, OK, "pick best");
559     MarkLinks(gValList, true, links);
560     CheckVals(gValList, false);
561 
562     /* Report stems and alignment zones, if this has been requested. */
563     if (gDoAligns || gDoStems)
564         DoHStems(gValList);
565 
566     /* Moves best HintVal items from valList to Hhinting list.
567      * (? Choose from set of HintVals for the samte stem values.) */
568     PickHVals(gValList);
569 
570     if (!CounterFailed && HHintGlyph()) {
571         gPruneValue = pv;
572         gPruneD = pd;
573         gPruneC = pc;
574         gPruneB = pb;
575         gPruneA = pa;
576         gUseH = UseCounter(gHHinting, false);
577         if (!gUseH) { /* try to fix */
578             AddBBoxHV(true, true);
579             gUseH = UseCounter(gHHinting, false);
580             if (!gUseH) { /* still bad news */
581                 LogMsg(INFO, OK,
582                        "Glyph is in list for using H counter hints, "
583                        "but didn't find any candidates.");
584                 CounterFailed = true;
585             }
586         }
587     } else {
588         gUseH = false;
589     }
590     if (gHHinting == NULL) {
591         AddBBoxHV(true, false);
592     }
593     LogMsg(LOGDEBUG, OK, "results");
594     LogMsg(LOGDEBUG, OK, gUseH ? "rv" : "rb");
595     ShowHVals(gHHinting);
596     if (gUseH) {
597         LogMsg(INFO, OK, "Using H counter hints.");
598     }
599     sLst = gHHinting;
600     while (sLst != NULL) {
601         AddHPair(sLst, gUseH ? 'v' : 'b'); /* actually adds hint */
602         sLst = sLst->vNxt;
603     }
604 }
605 
606 static void
DoHStems(HintVal * sLst1)607 DoHStems(HintVal* sLst1)
608 {
609     Fixed glyphTop = INT32_MIN, glyphBot = INT32_MAX;
610     bool curved;
611 
612     while (sLst1 != NULL) {
613         Fixed bot = -sLst1->vLoc1;
614         Fixed top = -sLst1->vLoc2;
615         if (top < bot) {
616             Fixed tmp = top;
617             top = bot;
618             bot = tmp;
619         }
620         if (top > glyphTop)
621             glyphTop = top;
622         if (bot < glyphBot)
623             glyphBot = bot;
624 
625         /* skip if ghost or not a line on top or bottom */
626         if (!sLst1->vGhst) {
627             curved = !FindLineSeg(sLst1->vLoc1, botList) &&
628                      !FindLineSeg(sLst1->vLoc2, topList);
629             AddHStem(top, bot, curved);
630             if (top != INT32_MIN || bot != INT32_MAX)
631                 AddStemExtremes(bot, top);
632         }
633 
634         sLst1 = sLst1->vNxt;
635     }
636 
637     if (glyphTop != INT32_MIN || glyphBot != INT32_MAX)
638         AddGlyphExtremes(glyphBot, glyphTop);
639 }
640 
641 static void
Yellows(unsigned char * links)642 Yellows(unsigned char* links)
643 {
644     Fixed pv = 0, pd = 0, pc = 0, pb = 0, pa = 0;
645     HintVal* sLst;
646     LogMsg(LOGDEBUG, OK, "generate yellows");
647     GenVPts(SpecialGlyphType());
648     LogMsg(LOGDEBUG, OK, "evaluate");
649     if (!CounterFailed && VHintGlyph()) {
650         pv = gPruneValue;
651         gPruneValue = (Fixed)gMinVal;
652         pa = gPruneA;
653         gPruneA = (Fixed)gMinVal;
654         pd = gPruneD;
655         gPruneD = (Fixed)gMinVal;
656         pc = gPruneC;
657         gPruneC = (Fixed)gMaxVal;
658         pb = gPruneB;
659         gPruneB = (Fixed)gMinVal;
660     }
661     EvalV();
662     PruneVVals();
663     FindBestVVals();
664     MergeVals(true);
665     ShowVVals(gValList);
666     LogMsg(LOGDEBUG, OK, "pick best");
667     MarkLinks(gValList, false, links);
668     CheckVals(gValList, true);
669 
670     if (gDoAligns || gDoStems)
671         DoVStems(gValList);
672 
673     PickVVals(gValList);
674     if (!CounterFailed && VHintGlyph()) {
675         gPruneValue = pv;
676         gPruneD = pd;
677         gPruneC = pc;
678         gPruneB = pb;
679         gPruneA = pa;
680         gUseV = UseCounter(gVHinting, true);
681         if (!gUseV) { /* try to fix */
682             AddBBoxHV(false, true);
683             gUseV = UseCounter(gVHinting, true);
684             if (!gUseV) { /* still bad news */
685                 LogMsg(INFO, OK,
686                        "Glyph is in list for using V counter hints, "
687                        "but didn't find any candidates.");
688                 CounterFailed = true;
689             }
690         }
691     } else {
692         gUseV = false;
693     }
694     if (gVHinting == NULL) {
695         AddBBoxHV(false, false);
696     }
697     LogMsg(LOGDEBUG, OK, "results");
698     LogMsg(LOGDEBUG, OK, gUseV ? "rm" : "ry");
699     ShowVVals(gVHinting);
700     if (gUseV) {
701         LogMsg(INFO, OK, "Using V counter hints.");
702     }
703     sLst = gVHinting;
704     while (sLst != NULL) {
705         AddVPair(sLst, gUseV ? 'm' : 'y');
706         sLst = sLst->vNxt;
707     }
708 }
709 
710 static void
DoVStems(HintVal * sLst)711 DoVStems(HintVal* sLst)
712 {
713     while (sLst != NULL) {
714         Fixed lft, rght;
715         bool curved;
716         curved = !FindLineSeg(sLst->vLoc1, leftList) &&
717                  !FindLineSeg(sLst->vLoc2, rightList);
718         lft = sLst->vLoc1;
719         rght = sLst->vLoc2;
720         if (lft > rght) {
721             Fixed tmp = lft;
722             lft = rght;
723             rght = tmp;
724         }
725         AddVStem(rght, lft, curved);
726         sLst = sLst->vNxt;
727     }
728 }
729 
730 static void
RemoveRedundantFirstHints(void)731 RemoveRedundantFirstHints(void)
732 {
733     PathElt* e;
734     if (gNumPtLsts < 2 || !SameHints(0, 1)) {
735         return;
736     }
737     e = gPathStart;
738     while (e != NULL) {
739         if (e->newhints == 1) {
740             e->newhints = 0;
741             return;
742         }
743         e = e->next;
744     }
745 }
746 
747 static void
AddHintsSetup(void)748 AddHintsSetup(void)
749 {
750     int i;
751     gVBigDist = 0;
752     for (i = 0; i < gNumVStems; i++) {
753         if (gVStems[i] > gVBigDist) {
754             gVBigDist = gVStems[i];
755         }
756     }
757     if (gVBigDist < gInitBigDist) {
758         gVBigDist = gInitBigDist;
759     }
760     gVBigDist = (gVBigDist * 23) / 20;
761     acfixtopflt(gVBigDist, &gVBigDistR);
762     gHBigDist = 0;
763     for (i = 0; i < gNumHStems; i++) {
764         if (gHStems[i] > gHBigDist) {
765             gHBigDist = gHStems[i];
766         }
767     }
768     gHBigDist = abs(gHBigDist);
769     if (gHBigDist < gInitBigDist) {
770         gHBigDist = gInitBigDist;
771     }
772     gHBigDist = (gHBigDist * 23) / 20;
773     acfixtopflt(gHBigDist, &gHBigDistR);
774     if (gRoundToInt) {
775         RoundPathCoords();
776     }
777     CheckForMultiMoveTo();
778     /* PreCheckForSolEol(); */
779 }
780 
781 /* If extrahint is true then it is ok to have multi-level
782  hinting. */
783 static void
AddHintsInnerLoop(const char * srcglyph,bool extrahint)784 AddHintsInnerLoop(const char* srcglyph, bool extrahint)
785 {
786     int32_t retryHinting = 0;
787     unsigned char* links;
788 
789     while (true) {
790         PreGenPts();
791         CheckSmooth();
792         links = InitShuffleSubpaths();
793         Blues(links);
794         if (!gDoAligns) {
795             Yellows(links);
796         }
797         if (gEditGlyph) {
798             DoShuffleSubpaths(links);
799         }
800         gHPrimary = CopyHints(gHHinting);
801         gVPrimary = CopyHints(gVHinting);
802         PruneElementHintSegs();
803         ListHintInfo();
804         if (extrahint) {
805             AutoExtraHints(MoveToNewHints());
806         }
807         gPtLstArray[gPtLstIndex] = gPointList;
808         retryHinting++;
809         /* we want to retry hinting if
810          `1) CounterFailed or
811          2) doFixes changed something, but in both cases, only on the first
812          pass.
813          */
814         if (CounterFailed && retryHinting == 1) {
815             goto retry;
816         }
817         if (retryHinting > 1) {
818             break;
819         }
820     retry:
821         /* if we are doing the stem and zones reporting, we need to discard the
822          * reported. */
823         if (gReportRetryCB != NULL) {
824             gReportRetryCB(gReportRetryUserData);
825         }
826         if (gPathStart == NULL || gPathStart == gPathEnd) {
827             LogMsg(LOGERROR, NONFATALERROR, "No glyph path.");
828         }
829 
830         /* SaveFile(); SaveFile is always called in AddHintsCleanup, so this is
831          * a duplciate */
832         InitAll(RESTART);
833         if (gWriteHintedBez && !ReadGlyph(srcglyph, false, false)) {
834             break;
835         }
836         AddHintsSetup();
837         if (!PreCheckForHinting()) {
838             break;
839         }
840         if (gFlexOK) {
841             gHasFlex = false;
842             AutoAddFlex();
843         }
844     }
845 }
846 
847 static void
AddHintsCleanup(void)848 AddHintsCleanup(void)
849 {
850     RemoveRedundantFirstHints();
851     if (gWriteHintedBez) {
852 
853         if (gPathStart == NULL || gPathStart == gPathEnd) {
854             LogMsg(LOGERROR, NONFATALERROR,
855                    "The glyph path vanished while adding hints.");
856         } else {
857             SaveFile();
858         }
859     }
860     InitAll(RESTART);
861 }
862 
863 static void
AddHints(const char * srcglyph,bool extrahint)864 AddHints(const char* srcglyph, bool extrahint)
865 {
866     if (gPathStart == NULL || gPathStart == gPathEnd) {
867         LogMsg(INFO, OK, "No glyph path, so no hints.");
868         SaveFile(); /* make sure it gets saved with no hinting */
869         return;
870     }
871     CounterFailed = gBandError = false;
872     CheckPathBBox();
873     CheckForDups();
874     AddHintsSetup();
875     if (!PreCheckForHinting()) {
876         return;
877     }
878     if (gFlexOK) {
879         gHasFlex = false;
880         AutoAddFlex();
881     }
882     AddHintsInnerLoop(srcglyph, extrahint);
883     AddHintsCleanup();
884 }
885 
886 bool
AutoHintGlyph(const char * srcglyph,bool extrahint)887 AutoHintGlyph(const char* srcglyph, bool extrahint)
888 {
889     int32_t lentop = gLenTopBands, lenbot = gLenBotBands;
890     if (!ReadGlyph(srcglyph, false, false)) {
891         LogMsg(LOGERROR, NONFATALERROR, "Cannot parse glyph.");
892     }
893     AddHints(srcglyph, extrahint);
894     gLenTopBands = lentop;
895     gLenBotBands = lenbot;
896     return true;
897 }
898