1 /* Latin.java -- Latin specific glyph handling
2    Copyright (C) 2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package gnu.java.awt.font.autofit;
40 
41 import java.awt.geom.AffineTransform;
42 import java.util.HashSet;
43 
44 import gnu.java.awt.font.opentype.OpenTypeFont;
45 import gnu.java.awt.font.opentype.truetype.Fixed;
46 import gnu.java.awt.font.opentype.truetype.Point;
47 import gnu.java.awt.font.opentype.truetype.Zone;
48 
49 /**
50  * Implements Latin specific glyph handling.
51  */
52 class Latin
53   implements Script, Constants
54 {
55 
56   static final int MAX_WIDTHS = 16;
57 
58   private final static int MAX_TEST_CHARS = 12;
59 
60   /**
61    * The types of the 6 blue zones.
62    */
63   private static final int CAPITAL_TOP = 0;
64   private static final int CAPITAL_BOTTOM = 1;
65   private static final int SMALL_F_TOP = 2;
66   private static final int SMALL_TOP = 3;
67   private static final int SMALL_BOTTOM = 4;
68   private static final int SMALL_MINOR = 5;
69   static final int BLUE_MAX = 6;
70 
71   /**
72    * The test chars for the blue zones.
73    *
74    * @see #initBlues(LatinMetrics, OpenTypeFont)
75    */
76   private static final String[] TEST_CHARS =
77     new String[]{"THEZOCQS", "HEZLOCUS", "fijkdbh",
78                  "xzroesc", "xzroesc", "pqgjy"};
79 
applyHints(GlyphHints hints, Zone outline, ScriptMetrics metrics)80   public void applyHints(GlyphHints hints, Zone outline, ScriptMetrics metrics)
81   {
82     hints.reload(outline);
83     hints.rescale(metrics);
84     if (hints.doHorizontal())
85       {
86         detectFeatures(hints, DIMENSION_HORZ);
87       }
88     if (hints.doVertical())
89       {
90         detectFeatures(hints, DIMENSION_VERT);
91         computeBlueEdges(hints, (LatinMetrics) metrics);
92       }
93     // Grid-fit the outline.
94     for (int dim = 0; dim < DIMENSION_MAX; dim++)
95       {
96         if (dim == DIMENSION_HORZ && hints.doHorizontal()
97             || dim == DIMENSION_VERT && hints.doVertical())
98           {
99             hintEdges(hints, dim);
100             if (hints.doAlignEdgePoints())
101               hints.alignEdgePoints(dim);
102             if (hints.doAlignStrongPoints())
103               hints.alignStrongPoints(dim);
104             if (hints.doAlignWeakPoints())
105               hints.alignWeakPoints(dim);
106 
107          }
108       }
109     // FreeType does a save call here. I guess that's not needed as we operate
110     // on the live glyph data anyway.
111   }
112 
hintEdges(GlyphHints hints, int dim)113   private void hintEdges(GlyphHints hints, int dim)
114   {
115     AxisHints axis = hints.axis[dim];
116     Edge[] edges = axis.edges;
117     int numEdges = axis.numEdges;
118     Edge anchor = null;
119     int hasSerifs = 0;
120 
121     // We begin by aligning all stems relative to the blue zone if
122     // needed -- that's only for horizontal edges.
123     if (dim == DIMENSION_VERT)
124       {
125         for (int e = 0; e < numEdges; e++)
126           {
127             Edge edge = edges[e];
128             if ((edge.flags & Segment.FLAG_EDGE_DONE) != 0)
129               continue;
130 
131             Width blue = edge.blueEdge;
132             Edge edge1 = null;
133             Edge edge2 = edge.link;
134             if (blue != null)
135               {
136                 edge1 = edge;
137               }
138             else if (edge2 != null && edge2.blueEdge != null)
139               {
140                 blue = edge2.blueEdge;
141                 edge1 = edge2;
142                 edge2 = edge;
143               }
144             if (edge1 == null)
145               continue;
146 
147             edge1.pos = blue.fit;
148             edge1.flags |= Segment.FLAG_EDGE_DONE;
149 
150             if (edge2 != null && edge2.blueEdge == null)
151               {
152                 alignLinkedEdge(hints, dim, edge1, edge2);
153                 edge2.flags |= Segment.FLAG_EDGE_DONE;
154               }
155             if (anchor == null)
156               anchor = edge;
157           }
158       }
159 
160     // Now we will align all stem edges, trying to maintain the
161     // relative order of stems in the glyph.
162     for (int e = 0; e < numEdges; e++)
163       {
164         Edge edge = edges[e];
165         if ((edge.flags & Segment.FLAG_EDGE_DONE) != 0)
166           continue;
167         Edge edge2 = edge.link;
168         if (edge2 == null)
169           {
170             hasSerifs++;
171             continue;
172           }
173         // Now align the stem.
174         // This should not happen, but it's better to be safe.
175         if (edge2.blueEdge != null || axis.getEdgeIndex(edge2) < e)
176           {
177             alignLinkedEdge(hints, dim, edge2, edge);
178             edge.flags |= Segment.FLAG_EDGE_DONE;
179             continue;
180           }
181 
182         if (anchor == null)
183           {
184             int orgLen = edge2.opos - edge.opos;
185             int curLen = computeStemWidth(hints, dim, orgLen, edge.flags,
186                                           edge2.flags);
187             int uOff, dOff, orgCenter, curPos1, error1, error2;
188             if (curLen <= 64) // < 1 Pixel.
189               {
190                 uOff = 32;
191                 dOff = 32;
192               }
193             else
194               {
195                 uOff = 38;
196                 dOff = 26;
197               }
198             if (curLen < 96)
199               {
200                 orgCenter = edge.opos + (orgLen >> 1);
201                 curPos1 = Utils.pixRound(orgCenter);
202                 error1 = orgCenter - (curPos1 - uOff);
203                 if (error1 < 0)
204                   error1 = -error1;
205                 error2 = orgCenter - (curPos1 + dOff);
206                 if (error2 < 0)
207                   error2 = -error2;
208                 if (error1 < error2)
209                   {
210                     curPos1 -= uOff;
211                   }
212                 else
213                   {
214                     curPos1 += dOff;
215                   }
216                 edge.pos = curPos1 - curLen / 2;
217                 edge2.pos = curPos1 + curLen / 2;
218               }
219             else
220               {
221                 edge.pos = Utils.pixRound(edge.opos);
222               }
223             anchor = edge;
224             edge.flags |= Segment.FLAG_EDGE_DONE;
225             alignLinkedEdge(hints, dim, edge, edge2);
226           }
227         else
228           {
229             int aDiff = edge.opos - anchor.opos;
230             int orgPos = anchor.pos + aDiff;
231             int orgLen = edge2.opos - edge.opos;
232             int orgCenter = orgPos + (orgLen >> 1);
233             int curLen = computeStemWidth(hints, dim, orgLen, edge.flags,
234                                           edge2.flags);
235             //System.err.println("stem width: " + curLen);
236             if (curLen < 96)
237               {
238                 int uOff, dOff;
239                 int curPos1 = Utils.pixRound(orgCenter);
240                 if (curLen <= 64)
241                   {
242                     uOff = 32;
243                     dOff = 32;
244                   }
245                 else
246                   {
247                     uOff = 38;
248                     dOff = 26;
249                   }
250                 int delta1 = orgCenter - (curPos1 - uOff);
251                 if (delta1 < 0)
252                   delta1 = -delta1;
253                 int delta2 = orgCenter - (curPos1 + dOff);
254                 if (delta2 < 0)
255                   delta2 = -delta2;
256                 if (delta1 < delta2)
257                   {
258                     curPos1 -= uOff;
259                   }
260                 else
261                   {
262                     curPos1 += dOff;
263                   }
264                 edge.pos = curPos1 - curLen / 2;
265                 edge2.pos = curPos1 + curLen / 2;
266               }
267             else
268               {
269                 orgPos = anchor.pos + (edge.opos - anchor.opos);
270                 orgLen = edge2.opos - edge.opos;
271                 orgCenter = orgPos + (orgLen >> 1);
272                 curLen = computeStemWidth(hints, dim, orgLen, edge.flags,
273                                           edge2.flags);
274                 int curPos1 = Utils.pixRound(orgPos);
275                 int delta1 = curPos1 + (curLen >> 1) - orgCenter;
276                 if (delta1 < 0)
277                   delta1 = -delta1;
278                 int curPos2 = Utils.pixRound(orgPos + orgLen) - curLen;
279                 int delta2 = curPos2 + (curLen >> 1) - orgCenter;
280                 if (delta2 < 0)
281                   delta2 = -delta2;
282                 edge.pos = (delta1 < delta2) ? curPos1 : curPos2;
283                 edge2.pos = edge.pos + curLen;
284               }
285             edge.flags |= Segment.FLAG_EDGE_DONE;
286             edge2.flags |= Segment.FLAG_EDGE_DONE;
287 
288             if (e > 0 && edge.pos < edges[e - 1].pos)
289               {
290                 edge.pos = edges[e - 1].pos;
291               }
292           }
293       }
294     // TODO: Implement the lowercase m symmetry thing.
295 
296     // Now we hint the remaining edges (serifs and singles) in order
297     // to complete our processing.
298     if (hasSerifs > 0 || anchor == null)
299       {
300         for (int e = 0; e < numEdges; e++)
301           {
302             Edge edge = edges[e];
303             if ((edge.flags & Segment.FLAG_EDGE_DONE) != 0)
304               continue;
305             if (edge.serif != null)
306               {
307                 alignSerifEdge(hints, edge.serif, edge);
308               }
309             else if (anchor == null)
310               {
311                 edge.pos = Utils.pixRound(edge.opos);
312                 anchor = edge;
313               }
314             else
315               {
316                 edge.pos = anchor.pos
317                            + Utils.pixRound(edge.opos - anchor.opos);
318               }
319             edge.flags |= Segment.FLAG_EDGE_DONE;
320 
321             if (e > 0 && edge.pos < edges[e - 1].pos)
322               {
323                 edge.pos = edges[e - 1].pos;
324               }
325             if (e + 1 < numEdges
326                 && (edges[e + 1].flags & Segment.FLAG_EDGE_DONE) != 0
327                 && edge.pos > edges[e + 1].pos)
328               {
329                 edge.pos = edges[e + 1].pos;
330               }
331           }
332       }
333 
334     // Debug: print all hinted edges.
335     // System.err.println("hinted edges: " );
336     // for (int i = 0; i < numEdges; i++)
337     //   {
338     //     System.err.println("edge#" + i + ": " + edges[i]);
339     //   }
340   }
341 
alignSerifEdge(GlyphHints hints, Edge base, Edge serif)342   private void alignSerifEdge(GlyphHints hints, Edge base, Edge serif)
343   {
344     serif.pos = base.pos + (serif.opos - base.opos);
345   }
346 
computeStemWidth(GlyphHints hints, int dim, int width, int baseFlags, int stemFlags)347   private int computeStemWidth(GlyphHints hints, int dim, int width,
348                                int baseFlags, int stemFlags)
349   {
350     LatinMetrics metrics = (LatinMetrics) hints.metrics;
351     LatinAxis axis = metrics.axis[dim];
352     int dist = width;
353     int sign = 0;
354     boolean vertical = dim == DIMENSION_VERT;
355     if (! doStemAdjust(hints))
356       return width;
357     if (dist < 0)
358       {
359         dist = -width;
360         sign = 1;
361       }
362     if ((vertical && ! doVertSnap(hints)) || ! vertical && ! doHorzSnap(hints))
363       {
364         // Smooth hinting process. Very lightly quantize the stem width.
365         // Leave the widths of serifs alone.
366         if ((stemFlags & Segment.FLAG_EDGE_SERIF) != 0 && vertical
367             && dist < 3 * 64)
368           {
369             return doneWidth(dist, sign);
370           }
371         else if ((baseFlags & Segment.FLAG_EDGE_ROUND) != 0)
372           {
373             if (dist < 80)
374               dist = 64;
375           }
376         else if (dist < 56)
377           {
378             dist = 56;
379           }
380         if (axis.widthCount > 0)
381           {
382             int delta;
383             if (axis.widthCount > 0)
384               {
385                 delta = dist - axis.widths[0].cur;
386                 if (delta < 0)
387                   {
388                     delta = -delta;
389                   }
390                 if (delta < 40)
391                   {
392                     dist = axis.widths[0].cur;
393                     if (dist < 48)
394                       dist = 48;
395                     return doneWidth(dist, sign);
396                   }
397               }
398             if (dist < 3 * 64) // < 3 pixels.
399               {
400                 delta = dist & 63;
401                 dist &= -64;
402                 if (delta < 10)
403                   dist += delta;
404                 else if (delta < 32)
405                   dist += 10;
406                 else if (delta < 54)
407                   dist += 54;
408                 else
409                   dist += delta;
410 
411               }
412             else
413               {
414                 dist = (dist + 32) & ~63;
415               }
416           }
417       }
418     else
419       {
420         // Strong hinting process: Snap the stem width to integer pixels.
421         dist = snapWidth(axis.widths, axis.widthCount, dist);
422         if (vertical)
423           {
424             // In the case of vertical hinting, always round
425             // the stem heights to integer pixels.
426             if (dist >= 64)
427               dist = (dist + 16) & ~63;
428             else
429               dist = 64;
430           }
431         else
432           {
433             if (doMono(hints))
434               {
435                 // Monochrome horizontal hinting: Snap widths to integer pixels
436                 // with a different threshold.
437                 if (dist < 64)
438                   dist = 64;
439                 else
440                   dist = (dist + 32) & ~63;
441               }
442             else
443               {
444                 // For anti-aliased hinting, we adopt a more subtle
445                 // approach: We strengthen small stems, round those stems
446                 // whose size is between 1 and 2 pixels to an integer,
447                 // otherwise nothing.
448                 if (dist < 48)
449                   dist = (dist + 64) >> 1;
450                 else if (dist < 128)
451                   dist = (dist + 22) & ~63;
452                 else
453                   // Round otherwise to prevent color fringes in LCD mode.
454                   dist = (dist + 32) & ~63;
455               }
456           }
457       }
458     return doneWidth(dist, sign);
459   }
460 
doMono(GlyphHints hints)461   private boolean doMono(GlyphHints hints)
462   {
463     return true;
464   }
465 
snapWidth(Width[] widths, int count, int width)466   private int snapWidth(Width[] widths, int count, int width)
467   {
468     int best = 64 + 32 + 2;
469     int reference = width;
470     for (int n = 0; n < count; n++)
471       {
472         int w = widths[n].cur;
473         int dist = width - w;
474         if (dist < 0)
475           dist = -dist;
476         if (dist < best)
477           {
478             best = dist;
479             reference = w;
480           }
481       }
482     int scaled = Utils.pixRound(reference);
483     if (width >= reference)
484       {
485         if (width < scaled + 48)
486           width = reference;
487       }
488     else
489       {
490         if (width > scaled + 48)
491           width = reference;
492       }
493     return width;
494   }
495 
doneWidth(int w, int s)496   private int doneWidth(int w, int s)
497   {
498     if (s == 1)
499       w = -w;
500     return w;
501   }
502 
doVertSnap(GlyphHints hints)503   private boolean doVertSnap(GlyphHints hints)
504   {
505     // TODO Auto-generated method stub
506     return true;
507   }
508 
doHorzSnap(GlyphHints hints)509   private boolean doHorzSnap(GlyphHints hints)
510   {
511     // TODO Auto-generated method stub
512     return true;
513   }
514 
doStemAdjust(GlyphHints hints)515   private boolean doStemAdjust(GlyphHints hints)
516   {
517     // TODO Auto-generated method stub
518     return true;
519   }
520 
alignLinkedEdge(GlyphHints hints, int dim, Edge base, Edge stem)521   private void alignLinkedEdge(GlyphHints hints, int dim, Edge base, Edge stem)
522   {
523     int dist = stem.opos - base.opos;
524     int fitted = computeStemWidth(hints, dim, dist, base.flags, stem.flags);
525     stem.pos = base.pos + fitted;
526   }
527 
doneMetrics(ScriptMetrics metrics)528   public void doneMetrics(ScriptMetrics metrics)
529   {
530     // TODO Auto-generated method stub
531 
532   }
533 
534   /**
535    * Initializes the <code>hints</code> object.
536    *
537    * @param hints the hints to initialize
538    * @param metrics the metrics to use
539    */
initHints(GlyphHints hints, ScriptMetrics metrics)540   public void initHints(GlyphHints hints, ScriptMetrics metrics)
541   {
542     hints.rescale(metrics);
543     LatinMetrics lm = (LatinMetrics) metrics;
544     hints.xScale = lm.axis[DIMENSION_HORZ].scale;
545     hints.xDelta = lm.axis[DIMENSION_HORZ].delta;
546     hints.yScale = lm.axis[DIMENSION_VERT].scale;
547     hints.yDelta = lm.axis[DIMENSION_VERT].delta;
548     // TODO: Set the scaler and other flags.
549   }
550 
551   /**
552    * Initializes the script metrics.
553    *
554    * @param metrics the script metrics to initialize
555    * @param face the font
556    */
initMetrics(ScriptMetrics metrics, OpenTypeFont face)557   public void initMetrics(ScriptMetrics metrics, OpenTypeFont face)
558   {
559     assert metrics instanceof LatinMetrics;
560     LatinMetrics lm = (LatinMetrics) metrics;
561     lm.unitsPerEm = face.unitsPerEm;
562 
563     // TODO: Check for latin charmap.
564 
565     initWidths(lm, face, 'o');
566     initBlues(lm, face);
567   }
568 
scaleMetrics(ScriptMetrics metrics, HintScaler scaler)569   public void scaleMetrics(ScriptMetrics metrics, HintScaler scaler)
570   {
571     LatinMetrics lm = (LatinMetrics) metrics;
572     lm.scaler.renderMode = scaler.renderMode;
573     lm.scaler.face = scaler.face;
574     scaleMetricsDim(lm, scaler, DIMENSION_HORZ);
575     scaleMetricsDim(lm, scaler, DIMENSION_VERT);
576   }
577 
scaleMetricsDim(LatinMetrics lm, HintScaler scaler, int dim)578   private void scaleMetricsDim(LatinMetrics lm, HintScaler scaler, int dim)
579   {
580     int scale;
581     int delta;
582     if (dim == DIMENSION_HORZ)
583       {
584         scale = scaler.xScale;
585         delta = scaler.xDelta;
586       }
587     else
588       {
589         scale = scaler.yScale;
590         delta = scaler.yDelta;
591       }
592     LatinAxis axis = lm.axis[dim];
593     if (axis.orgScale == scale && axis.orgDelta == delta)
594       // No change, no need to adjust.
595       return;
596     axis.orgScale = scale;
597     axis.orgDelta = delta;
598 
599     // Correct X and Y scale to optimize the alignment of the top small
600     // letters to the pixel grid.
601     LatinAxis axis2 = lm.axis[DIMENSION_VERT];
602     LatinBlue blue = null;
603 //    for (int nn = 0; nn < axis2.blueCount; nn++)
604 //      {
605 //        if ((axis2.blues[nn].flags & LatinBlue.FLAG_ADJUSTMENT) != 0)
606 //          {
607 //            blue = axis2.blues[nn];
608 //            break;
609 //          }
610 //      }
611 //    if (blue != null)
612 //      {
613 //        int scaled = Fixed.mul16(blue.shoot.org, scaler.yScale);
614 //        int fitted = Utils.pixRound(scaled);
615 //        if (scaled != fitted)
616 //          {
617 //            if (dim == DIMENSION_HORZ)
618 //              {
619 //                if (fitted < scaled)
620 //                  {
621 //                    scale -= scale / 50;
622 //                  }
623 //              }
624 //            else
625 //              {
626 //                scale = Utils.mulDiv(scale, fitted, scaled);
627 //              }
628 //          }
629 //      }
630     axis.scale = scale;
631     axis.delta = delta;
632     if (dim == DIMENSION_HORZ)
633       {
634         lm.scaler.xScale = scale;
635         lm.scaler.xDelta = delta;
636       }
637     else
638       {
639         lm.scaler.yScale = scale;
640         lm.scaler.yDelta = delta;
641       }
642     // Scale the standard widths.
643     for (int nn = 0; nn < axis.widthCount; nn++)
644       {
645         Width w = axis.widths[nn];
646         w.cur = Fixed.mul16(w.org, scale);
647         w.fit = w.cur;
648       }
649     // Scale blue zones.
650     if (dim == DIMENSION_VERT)
651       {
652         for (int nn = 0; nn < axis.blueCount; nn++)
653           {
654             blue = axis.blues[nn];
655             blue.ref.cur = Fixed.mul16(blue.ref.org, scale) + delta;
656             blue.ref.fit = blue.ref.cur;
657             blue.shoot.cur = Fixed.mul16(blue.ref.org, scale) + delta;
658             blue.flags &= ~LatinBlue.FLAG_BLUE_ACTIVE;
659             // A blue zone is only active if it is less than 3/4 pixels tall.
660             int dist = Fixed.mul16(blue.ref.org - blue.shoot.org, scale);
661             if (dist <= 48 && dist >= -48)
662               {
663                 int delta1 = blue.shoot.org - blue.ref.org;
664                 int delta2 = delta1;
665                 if (delta1 < 0)
666                   delta2 = -delta2;
667                 delta2 = Fixed.mul16(delta2, scale);
668                 if (delta2 < 32)
669                   delta2 = 0;
670                 else if (delta2 < 64)
671                   delta2 = 32 + (((delta2 - 32) + 16) & ~31);
672                 else
673                   delta2 = Utils.pixRound(delta2);
674                 if (delta1 < 0)
675                   delta2 = -delta2;
676                 blue.ref.fit = Utils.pixRound(blue.ref.cur);
677                 blue.shoot.fit = blue.ref.fit + delta2;
678                 blue.flags |= LatinBlue.FLAG_BLUE_ACTIVE;
679               }
680           }
681       }
682   }
683 
684   /**
685    * Determines the standard stem widths.
686    *
687    * @param metrics the metrics to use
688    * @param face the font face
689    * @param ch the character that is used for getting the widths
690    */
initWidths(LatinMetrics metrics, OpenTypeFont face, char ch)691   private void initWidths(LatinMetrics metrics, OpenTypeFont face, char ch)
692   {
693     GlyphHints hints = new GlyphHints();
694     metrics.axis[DIMENSION_HORZ].widthCount = 0;
695     metrics.axis[DIMENSION_VERT].widthCount = 0;
696     int glyphIndex = face.getGlyph(ch);
697     Zone outline = face.getRawGlyphOutline(glyphIndex, IDENTITY);
698     LatinMetrics dummy = new LatinMetrics();
699     HintScaler scaler = dummy.scaler;
700     dummy.unitsPerEm = metrics.unitsPerEm;
701     scaler.xScale = scaler.yScale = 10000;
702     scaler.xDelta = scaler.yDelta = 0;
703     scaler.face = face;
704     hints.rescale(dummy);
705     hints.reload(outline);
706     for (int dim = 0; dim < DIMENSION_MAX; dim++)
707       {
708         LatinAxis axis = metrics.axis[dim];
709         AxisHints axHints = hints.axis[dim];
710         int numWidths = 0;
711         computeSegments(hints, dim);
712         linkSegments(hints, dim);
713         Segment[] segs = axHints.segments;
714         HashSet<Segment> touched = new HashSet<Segment>();
715         for (int i = 0; i < segs.length; i++)
716           {
717             Segment seg = segs[i];
718             Segment link = seg.link;
719             if (link != null && link.link == seg && ! touched.contains(link))
720               {
721                 int dist = Math.abs(seg.pos - link.pos);
722                 if (numWidths < MAX_WIDTHS)
723                   axis.widths[numWidths++] = new Width(dist);
724               }
725             touched.add(seg);
726           }
727         Utils.sort(numWidths, axis.widths);
728         axis.widthCount = numWidths;
729       }
730     for (int dim = 0; dim < DIMENSION_MAX; dim++)
731       {
732         LatinAxis axis = metrics.axis[dim];
733         int stdw = axis.widthCount > 0 ? axis.widths[0].org
734                                        : constant(metrics, 50);
735         axis.edgeDistanceTreshold= stdw / 5;
736       }
737   }
738 
linkSegments(GlyphHints hints, int dim)739   void linkSegments(GlyphHints hints, int dim)
740   {
741     AxisHints axis = hints.axis[dim];
742     Segment[] segments = axis.segments;
743     int numSegs = axis.numSegments;
744     int majorDir = axis.majorDir;
745     int lenThreshold = constant((LatinMetrics) hints.metrics, 8);
746     lenThreshold = Math.min(1, lenThreshold);
747     int lenScore = constant((LatinMetrics) hints.metrics, 3000);
748     for (int i1 = 0; i1 < numSegs; i1++)
749       {
750         Segment seg1 = segments[i1];
751         // The fake segments are introduced to hint the metrics.
752         // Never link them to anything.
753         if (seg1.first == seg1.last || seg1.dir != majorDir)
754           continue;
755         for (int i2 = 0; i2 < numSegs; i2++)
756           {
757             Segment seg2 = segments[i2];
758             if (seg2 != seg1 && seg1.dir + seg2.dir == 0)
759               {
760                 int pos1 = seg1.pos;
761                 int pos2 = seg2.pos;
762                 // The vertical coords are swapped compared to how FT handles
763                 // this.
764                 int dist = dim == DIMENSION_VERT ? pos1 - pos2 : pos2 - pos1;
765                 if (dist >= 0)
766                   {
767                     int min = seg1.minPos;
768                     int max = seg1.maxPos;
769                     int len, score;
770                     if (min < seg2.minPos)
771                       min = seg2.minPos;
772                     if (max > seg2.maxPos)
773                       max = seg2.maxPos;
774                     len = max - min;
775                     if (len > lenThreshold)
776                       {
777                         score = dist + lenScore / len;
778                         if (score < seg1.score)
779                           {
780                             seg1.score = score;
781                             seg1.link = seg2;
782                           }
783                         if (score < seg2.score)
784                           {
785                             seg2.score = score;
786                             seg2.link = seg1;
787                           }
788                       }
789                   }
790               }
791           }
792       }
793     for (int i1 = 0; i1 < numSegs; i1++)
794       {
795         Segment seg1 = segments[i1];
796         Segment seg2 = seg1.link;
797         if (seg2 != null)
798           {
799             seg2.numLinked++;
800             if (seg2.link != seg1)
801               {
802                 seg1.link = null;
803                 seg1.serif = seg2.link;
804               }
805           }
806         // Uncomment to show all segments.
807         // System.err.println("segment#" + i1 + ": " + seg1);
808       }
809   }
810 
811   /**
812    * Initializes the blue zones of the font.
813    *
814    * @param metrics the metrics to use
815    * @param face the font face to analyze
816    */
initBlues(LatinMetrics metrics, OpenTypeFont face)817   private void initBlues(LatinMetrics metrics, OpenTypeFont face)
818   {
819     int[] flats = new int[MAX_TEST_CHARS];
820     int[] rounds = new int[MAX_TEST_CHARS];
821     int numFlats;
822     int numRounds;
823     LatinBlue blue;
824     LatinAxis axis = metrics.axis[DIMENSION_VERT];
825     // We compute the blues simply by loading each character in the test
826     // strings, then compute its topmost or bottommost points.
827     for (int bb = 0; bb < BLUE_MAX; bb++)
828       {
829         String p = TEST_CHARS[bb];
830         int blueRef;
831         int blueShoot;
832         numFlats = 0;
833         numRounds = 0;
834         for (int i = 0; i < p.length(); i++)
835           {
836             // Load the character.
837             int glyphIndex = face.getGlyph(p.charAt(i));
838             Zone glyph =
839               face.getRawGlyphOutline(glyphIndex, IDENTITY);
840 
841             // Now compute the min and max points.
842             int numPoints = glyph.getSize() - 4; // 4 phantom points.
843             Point[] points = glyph.getPoints();
844             Point point = points[0];
845             int extremum = 0;
846             int index = 1;
847             if (isTopBlue(bb))
848               {
849                 for (; index < numPoints; index++)
850                   {
851                     point = points[index];
852                     // We have the vertical direction swapped. The higher
853                     // points have smaller (negative) Y.
854                     if (point.getOrigY() < points[extremum].getOrigY())
855                       extremum = index;
856                   }
857               }
858             else
859               {
860                 for (; index < numPoints; index++)
861                   {
862                     point = points[index];
863                     // We have the vertical direction swapped. The higher
864                     // points have smaller (negative) Y.
865                     if (point.getOrigY() > points[extremum].getOrigY())
866                       extremum = index;
867                   }
868               }
869             // Debug, prints out the maxima.
870             // System.err.println("extremum for " + bb + " / "+ p.charAt(i)
871             //                    + ": " + points[extremum]);
872 
873             // Now determine if the point is part of a straight or round
874             // segment.
875             boolean round;
876             int idx = extremum;
877             int first, last, prev, next, end;
878             int dist;
879             last = -1;
880             first = 0;
881             for (int n = 0; n < glyph.getNumContours(); n++)
882               {
883                 end = glyph.getContourEnd(n);
884                 // System.err.println("contour end for " + n + ": " + end);
885                 if (end >= idx)
886                   {
887                     last = end;
888                     break;
889                   }
890                 first = end + 1;
891               }
892             // Should never happen.
893             assert last >= 0;
894 
895             // Now look for the previous and next points that are not on the
896             // same Y coordinate. Threshold the 'closeness'.
897             prev = idx;
898             next = prev;
899             do
900               {
901                 if (prev > first)
902                   prev--;
903                 else
904                   prev = last;
905                 dist = points[prev].getOrigY() - points[extremum].getOrigY();
906                 if (dist < -5 || dist > 5)
907                   break;
908               } while (prev != idx);
909             do
910               {
911                 if (next < last)
912                   next++;
913                 else
914                   next = first;
915                 dist = points[next].getOrigY() - points[extremum].getOrigY();
916                 if (dist < -5 || dist > 5)
917                   break;
918               } while (next != idx);
919             round = points[prev].isControlPoint()
920                     || points[next].isControlPoint();
921 
922             if (round)
923               {
924                 rounds[numRounds++] = points[extremum].getOrigY();
925                 // System.err.println("new round extremum: " + bb + ": "
926                 //                   + points[extremum].getOrigY());
927               }
928             else
929               {
930                 flats[numFlats++] = points[extremum].getOrigY();
931                 // System.err.println("new flat extremum: " + bb + ": "
932                 //                    + points[extremum].getOrigY());
933               }
934           }
935         // We have computed the contents of the rounds and flats tables.
936         // Now determine the reference and overshoot position of the blues --
937         // we simply take the median after a simple sort.
938         Utils.sort(numRounds, rounds);
939         Utils.sort(numFlats, flats);
940         blue = axis.blues[axis.blueCount] = new LatinBlue();
941         axis.blueCount++;
942         if (numFlats == 0)
943           {
944             blue.ref = blue.shoot = new Width(rounds[numRounds / 2]);
945           }
946         else if (numRounds == 0)
947           {
948             blue.ref = blue.shoot = new Width(flats[numFlats / 2]);
949           }
950         else
951           {
952             blue.ref = new Width(flats[numFlats / 2]);
953             blue.shoot = new Width(rounds[numRounds / 2]);
954           }
955         // There are sometimes problems:  if the overshoot position of top
956         // zones is under its reference position, or the opposite for bottom
957         // zones. We must check everything there and correct problems.
958         if (blue.shoot != blue.ref)
959           {
960             int ref = blue.ref.org;
961             int shoot = blue.shoot.org;
962             // Inversed vertical coordinates!
963             boolean overRef = shoot < ref;
964             if (isTopBlue(bb) ^ overRef)
965               {
966                 blue.shoot = blue.ref = new Width((shoot + ref) / 2);
967               }
968           }
969         blue.flags = 0;
970         if (isTopBlue(bb))
971           blue.flags |= LatinBlue.FLAG_TOP;
972         // The following flag is used later to adjust y and x scales in
973         // order to optimize the pixel grid alignment of the top small
974         // letters.
975         if (bb == SMALL_TOP)
976           {
977             blue.flags |= LatinBlue.FLAG_ADJUSTMENT;
978           }
979         // Debug: print out the blue zones.
980         // System.err.println("blue zone #" + bb + ": " + blue);
981       }
982   }
983 
984   private static final AffineTransform IDENTITY = new AffineTransform();
985 
986   private int constant(LatinMetrics metrics, int c)
987   {
988     return c * (metrics.unitsPerEm / 2048);
989   }
990 
991   private void computeSegments(GlyphHints hints, int dim)
992   {
993     Point[] points = hints.points;
994     if (dim == DIMENSION_HORZ)
995       {
996         for (int i = 0; i < hints.numPoints; i++)
997           {
998             points[i].setU(points[i].getOrigX());
999             points[i].setV(points[i].getOrigY());
1000           }
1001       }
1002     else
1003       {
1004         for (int i = 0; i < hints.numPoints; i++)
1005           {
1006             points[i].setU(points[i].getOrigY());
1007             points[i].setV(points[i].getOrigX());
1008           }
1009       }
1010     // Now look at each contour.
1011     AxisHints axis = hints.axis[dim];
1012     int majorDir = Math.abs(axis.majorDir);
1013     int segmentDir = majorDir;
1014     Point[] contours = hints.contours;
1015     int numContours = hints.numContours;
1016     Segment segment = null;
1017     for (int i = 0; i < numContours; i++)
1018       {
1019         int minPos = 32000;
1020         int maxPos = -32000;
1021 
1022         Point point = contours[i];
1023         Point last = point.getPrev();
1024         if (point == last) // Skip singletons.
1025           continue;
1026         if (Math.abs(last.getOutDir()) == majorDir
1027             && Math.abs(point.getOutDir()) == majorDir)
1028           {
1029             // We are already on an edge. Locate its start.
1030             last = point;
1031             while (true)
1032               {
1033                 point = point.getPrev();
1034                 if (Math.abs(point.getOutDir()) != majorDir)
1035                   {
1036                     point = point.getNext();
1037                     break;
1038                   }
1039                 if (point == last)
1040                   break;
1041               }
1042           }
1043         last = point;
1044         boolean passed = false;
1045         boolean onEdge = false;
1046         while (true)
1047           {
1048             int u, v;
1049             if (onEdge)
1050               {
1051                 u = point.getU();
1052                 if (u < minPos)
1053                   minPos = u;
1054                 if (u > maxPos)
1055                   maxPos = u;
1056                 if (point.getOutDir() != segmentDir || point == last)
1057                   {
1058                     // Leaving an edge. Record new segment.
1059                     segment.last = point;
1060                     // (minPos + maxPos) / 2.
1061                     segment.pos = (minPos + maxPos) >> 1;
1062                     if (segment.first.isControlPoint()
1063                         || point.isControlPoint())
1064                       segment.flags |= Segment.FLAG_EDGE_ROUND;
1065                     minPos = maxPos = point.getV();
1066                     v = segment.first.getV();
1067                     if (v < minPos)
1068                       minPos = v;
1069                     if (v > maxPos)
1070                       maxPos = v;
1071                     segment.minPos = minPos;
1072                     segment.maxPos = maxPos;
1073                     onEdge = false;
1074                     segment = null;
1075                   }
1076               }
1077             if (point == last)
1078               {
1079                 if (passed)
1080                   break;
1081                 passed = true;
1082               }
1083             if (! onEdge && Math.abs(point.getOutDir()) == majorDir)
1084               {
1085                 // This is the start of a new segment.
1086                 segmentDir = point.getOutDir();
1087                 segment = axis.newSegment();
1088                 segment.dir = segmentDir;
1089                 segment.flags = Segment.FLAG_EDGE_NORMAL;
1090                 minPos = maxPos = point.getU();
1091                 segment.first = point;
1092                 segment.last = point;
1093                 segment.contour = contours[i];
1094                 segment.score = 32000;
1095                 segment.len = 0;
1096                 segment.link = null;
1097                 onEdge = true;
1098               }
1099             point = point.getNext();
1100           }
1101       }
1102 
1103   }
1104 
1105   private boolean isTopBlue(int b)
1106   {
1107     return b == CAPITAL_TOP || b == SMALL_F_TOP || b == SMALL_TOP;
1108   }
1109 
1110   private void detectFeatures(GlyphHints hints, int dim)
1111   {
1112     computeSegments(hints, dim);
1113     linkSegments(hints, dim);
1114     computeEdges(hints, dim);
1115   }
1116 
1117   private void computeEdges(GlyphHints hints, int dim)
1118   {
1119     AxisHints axis = hints.axis[dim];
1120     LatinAxis laxis = ((LatinMetrics) hints.metrics).axis[dim];
1121     Segment[] segments = axis.segments;
1122     int numSegments = axis.numSegments;
1123     Segment seg;
1124     int upDir;
1125     int scale;
1126     int edgeDistanceThreshold;
1127     axis.numEdges = 0;
1128     scale = dim == DIMENSION_HORZ ? hints.xScale : hints.yScale;
1129     upDir = dim == DIMENSION_HORZ ? DIR_UP : DIR_RIGHT;
1130 
1131     // We will begin by generating a sorted table of edges for the
1132     // current direction. To do so, we simply scan each segment and try
1133     // to find an edge in our table that corresponds to its position.
1134     //
1135     // If no edge is found, we create one and insert a new edge in the
1136     // sorted table. Otherwise, we simply add the segment to the egde's
1137     // list which will be processed in the second step to compute the
1138     // edge's properties.
1139     //
1140     // Note that the edge table is sorted along the segment/edge
1141     // position.
1142 
1143     edgeDistanceThreshold = Fixed.mul16(laxis.edgeDistanceTreshold, scale);
1144     if (edgeDistanceThreshold > 64 / 4)
1145       edgeDistanceThreshold = 64 / 4;
1146     edgeDistanceThreshold = Fixed.div16(edgeDistanceThreshold, scale);
1147     for (int i = 0; i < numSegments; i++)
1148       {
1149         seg = segments[i];
1150         Edge found = null;
1151         for (int ee = 0; ee < axis.numEdges; ee++)
1152           {
1153             Edge edge = axis.edges[ee];
1154             int dist = seg.pos - edge.fpos;
1155             if (dist < 0)
1156               dist = -dist;
1157             if (dist < edgeDistanceThreshold)
1158               {
1159                 found = edge;
1160                 break;
1161               }
1162           }
1163         if (found == null)
1164           {
1165             // Insert new edge in the list and sort according to
1166             // the position.
1167             Edge edge = axis.newEdge(seg.pos);
1168             edge.first = seg;
1169             edge.last = seg;
1170             edge.fpos = seg.pos;
1171             edge.opos = edge.pos = Fixed.mul16(seg.pos, scale);
1172             seg.edgeNext = seg;
1173             seg.edge = edge;
1174           }
1175         else
1176           {
1177             seg.edgeNext = found.first;
1178             found.last.edgeNext = seg;
1179             found.last = seg;
1180             seg.edge = found;
1181           }
1182       }
1183     // Good. We will now compute each edge's properties according to
1184     // segments found on its position. Basically these are:
1185     // - Edge's main direction.
1186     // - Stem edge, serif edge, or both (which defaults to stem edge).
1187     // - Rounded edge, straight or both (which defaults to straight).
1188     // - Link for edge.
1189 
1190     // Now, compute each edge properties.
1191     for (int e = 0; e < axis.numEdges; e++)
1192       {
1193         Edge edge = axis.edges[e];
1194         // Does it contain round segments?
1195         int isRound = 0;
1196         // Does it contain straight segments?
1197         int isStraight = 0;
1198         // Number of upward segments.
1199         int ups = 0;
1200         // Number of downward segments.
1201         int downs = 0;
1202 
1203         seg = edge.first;
1204         do
1205           {
1206             // Check for roundness of segment.
1207             if ((seg.flags & Segment.FLAG_EDGE_ROUND) != 0)
1208               isRound++;
1209             else
1210               isStraight++;
1211 
1212             // Check for segment direction.
1213             if (seg.dir == upDir)
1214               ups += seg.maxPos - seg.minPos;
1215             else
1216               downs += seg.maxPos - seg.minPos;
1217 
1218             // Check for links. If seg.serif is set, then seg.link must
1219             // be ignored.
1220             boolean isSerif = seg.serif != null && seg.serif.edge != edge;
1221             if (seg.link != null || isSerif)
1222               {
1223                 Edge edge2 = edge.link;
1224                 Segment seg2 = seg.link;
1225                 if (isSerif)
1226                   {
1227                     seg2 = seg.serif;
1228                     edge2 = edge.serif;
1229                   }
1230                 if (edge2 != null)
1231                   {
1232                     int edgeDelta = edge.fpos - edge2.fpos;
1233                     if (edgeDelta < 0)
1234                       edgeDelta = -edgeDelta;
1235                     int segDelta = seg.pos - seg2.pos;
1236                     if (segDelta < 0)
1237                       segDelta = -segDelta;
1238                     if (segDelta < edgeDelta)
1239                       edge2 = seg2.edge;
1240                   }
1241                 else
1242                   {
1243                     edge2 = seg2.edge;
1244                   }
1245                 if (isSerif)
1246                   {
1247                     edge.serif = edge2;
1248                     edge2.flags |= Segment.FLAG_EDGE_SERIF;
1249                   }
1250                 else
1251                   {
1252                     edge.link = edge2;
1253                   }
1254               }
1255             seg = seg.edgeNext;
1256           } while (seg != edge.first);
1257         edge.flags = Segment.FLAG_EDGE_NORMAL;
1258         if (isRound > 0 && isRound > isStraight)
1259           edge.flags |= Segment.FLAG_EDGE_ROUND;
1260 
1261         // Set the edge's main direction.
1262         edge.dir = DIR_NONE;
1263         if (ups > downs)
1264           edge.dir = upDir;
1265         else if (ups < downs)
1266           edge.dir = -upDir;
1267         else if (ups == downs)
1268           edge.dir = 0;
1269 
1270         // Gets rid of serif if link is set. This gets rid of many
1271         // unpleasant artifacts.
1272         if (edge.serif != null && edge.link != null)
1273           {
1274             edge.serif = null;
1275           }
1276 
1277         // Debug: Print out all edges.
1278         // System.err.println("edge# " + e + ": " + edge);
1279       }
1280   }
1281 
1282   private void computeBlueEdges(GlyphHints hints, LatinMetrics metrics)
1283   {
1284     AxisHints axis = hints.axis[DIMENSION_VERT];
1285     Edge[] edges = axis.edges;
1286     int numEdges = axis.numEdges;
1287     LatinAxis latin = metrics.axis[DIMENSION_VERT];
1288     int scale = latin.scale;
1289 
1290     // Compute which blue zones are active. I.e. have their scaled
1291     // size < 3/4 pixels.
1292 
1293     // For each horizontal edge search the blue zone that is closest.
1294     for (int e = 0; e < numEdges; e++)
1295       {
1296         Edge edge = edges[e];
1297         // System.err.println("checking edge: " + edge);
1298         Width bestBlue = null;
1299         int bestDist = Fixed.mul16(metrics.unitsPerEm / 40, scale);
1300 
1301         if (bestDist > 64 / 2)
1302           bestDist = 64 / 2;
1303         for (int bb = 0; bb < BLUE_MAX; bb++)
1304           {
1305             LatinBlue blue = latin.blues[bb];
1306             // System.err.println("checking blue: " + blue);
1307             // Skip inactive blue zones, i.e. those that are too small.
1308             if ((blue.flags & LatinBlue.FLAG_BLUE_ACTIVE) == 0)
1309               continue;
1310             // If it is a top zone, check for right edges. If it is a bottom
1311             // zone, check for left edges.
1312             boolean isTopBlue = (blue.flags & LatinBlue.FLAG_TOP) != 0;
1313             boolean isMajorDir = edge.dir == axis.majorDir;
1314 
1315             // If it is a top zone, the edge must be against the major
1316             // direction. If it is a bottom zone it must be in the major
1317             // direction.
1318             if (isTopBlue ^ isMajorDir)
1319               {
1320                 int dist = edge.fpos - blue.ref.org;
1321                 if (dist < 0)
1322                   dist = -dist;
1323                 dist = Fixed.mul16(dist, scale);
1324                 if (dist < bestDist)
1325                   {
1326                     bestDist = dist;
1327                     bestBlue = blue.ref;
1328                   }
1329 
1330                 // Now, compare it to the overshoot position if the edge is
1331                 // rounded, and if the edge is over the reference position of
1332                 // a top zone, or under the reference position of a bottom
1333                 // zone.
1334                 if ((edge.flags & Segment.FLAG_EDGE_ROUND) != 0 && dist != 0)
1335                   {
1336                     // Inversed vertical coordinates!
1337                     boolean isUnderRef = edge.fpos > blue.ref.org;
1338                     if (isTopBlue ^ isUnderRef)
1339                       {
1340                         blue = latin.blues[bb]; // Needed?
1341                         dist = edge.fpos - blue.shoot.org;
1342                         if (dist < 0)
1343                           dist = -dist;
1344                         dist = Fixed.mul16(dist, scale);
1345                         if (dist < bestDist)
1346                           {
1347                             bestDist = dist;
1348                             bestBlue = blue.shoot;
1349                           }
1350                       }
1351                   }
1352 
1353               }
1354           }
1355         if (bestBlue != null)
1356           {
1357             edge.blueEdge = bestBlue;
1358             // Debug: Print out the blue edges.
1359             // System.err.println("blue edge for: " + edge + ": " + bestBlue);
1360           }
1361       }
1362   }
1363 }
1364