This implements the detection of the Blue Zones of a font. This
completes the global feature analysis. Now I head on to the local
feature analysis, where most stuff is already implemented because it's
needed for the global feature analysis too, except edge detection. And
then I'll go hinting ;-)

2006-12-14  Roman Kennke  <[EMAIL PROTECTED]>

        * gnu/java/awt/font/autofit/Latin.java
        (CAPITAL_TOP): New constant.
        (CAPITAL_BOTTOM): New constant.
        (SMALL_F_TOP): New constant.
        (SMALL_TOP): New constant.
        (SMALL_BOTTOM): New constant.
        (SMALL_MINOR): New constant.
        (BLUE_MAX): New constant.
        (IDENTITY): New constant transform.
        (MAX_TEST_CHARS): New constant.
        (TEST_CHARS): New constants.
        (initBlues): Implemented.
        (initWidths): Use constant identity transform for loading
        the test glyph.
        (isTopBlue): New helper method.
        * gnu/java/awt/font/autofit/LatinAxis.java
        (widths): Initialize in constructor.
        (blues): New field. Stores the blue zones.
        (blueCount): New field. The number of blue zones.
        (LatinAxis): New constructor.
        * gnu/java/awt/font/autofit/Utils.java
        (sort): New helper method.
        * gnu/java/awt/font/opentype/truetype/Zone.java
        (getContourEnd): New helper method.

/Roman

? gnu/java/awt/font/autofit/LatinBlue.java
Index: gnu/java/awt/font/autofit/Latin.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/font/autofit/Latin.java,v
retrieving revision 1.2
diff -u -1 -5 -r1.2 Latin.java
--- gnu/java/awt/font/autofit/Latin.java	13 Dec 2006 22:59:43 -0000	1.2
+++ gnu/java/awt/font/autofit/Latin.java	14 Dec 2006 15:13:11 -0000
@@ -42,30 +42,52 @@
 import java.util.HashSet;
 
 import gnu.java.awt.font.opentype.OpenTypeFont;
 import gnu.java.awt.font.opentype.truetype.Point;
 import gnu.java.awt.font.opentype.truetype.Zone;
 
 /**
  * Implements Latin specific glyph handling.
  */
 class Latin
   implements Script, Constants
 {
 
   static final int MAX_WIDTHS = 16;
 
+  private final static int MAX_TEST_CHARS = 12;
+
+  /**
+   * The types of the 6 blue zones.
+   */
+  private static final int CAPITAL_TOP = 0;
+  private static final int CAPITAL_BOTTOM = 1;
+  private static final int SMALL_F_TOP = 2;
+  private static final int SMALL_TOP = 3;
+  private static final int SMALL_BOTTOM = 4;
+  private static final int SMALL_MINOR = 5;
+  static final int BLUE_MAX = 6;
+
+  /**
+   * The test chars for the blue zones.
+   *
+   * @see #initBlues(LatinMetrics, OpenTypeFont)
+   */
+  private static final String[] TEST_CHARS =
+    new String[]{"THEZOCQS", "HEZLOCUS", "fijkdbh",
+                 "xzroesc", "xzroesc", "pqgjy"};
+
   public void applyHints(GlyphHints hints, ScriptMetrics metrics)
   {
     // TODO Auto-generated method stub
 
   }
 
   public void doneMetrics(ScriptMetrics metrics)
   {
     // TODO Auto-generated method stub
 
   }
 
   /**
    * Initializes the <code>hints</code> object.
    *
@@ -111,31 +133,31 @@
    * Determines the standard stem widths.
    *
    * @param metrics the metrics to use
    * @param face the font face
    * @param ch the character that is used for getting the widths
    */
   private void initWidths(LatinMetrics metrics, OpenTypeFont face, char ch)
   {
     GlyphHints hints = new GlyphHints();
     metrics.axis[DIMENSION_HORZ].widthCount = 0;
     metrics.axis[DIMENSION_VERT].widthCount = 0;
     int glyphIndex = face.getGlyph(ch);
     // TODO: Avoid that AffineTransform constructor and change
     // getRawGlyphOutline() to accept null or remove that parameter altogether.
     // Consider this when the thing is done and we know what we need that for.
-    Zone outline = face.getRawGlyphOutline(glyphIndex, new AffineTransform());
+    Zone outline = face.getRawGlyphOutline(glyphIndex, IDENTITY);
     LatinMetrics dummy = new LatinMetrics();
     Scaler scaler = dummy.scaler;
     dummy.unitsPerEm = metrics.unitsPerEm;
     scaler.xScale = scaler.yScale = 10000;
     scaler.xDelta = scaler.yDelta = 0;
     scaler.face = face;
     hints.rescale(dummy);
     hints.reload(outline);
     for (int dim = 0; dim < DIMENSION_MAX; dim++)
       {
         LatinAxis axis = metrics.axis[dim];
         AxisHints axHints = hints.axis[dim];
         int numWidths = 0;
         computeSegments(hints, dim);
         linkSegments(hints, dim);
@@ -231,33 +253,197 @@
               }
           }
         // Uncomment to show all segments.
         // System.err.println("segment#" + i1 + ": " + seg1);
       }
   }
 
   /**
    * Initializes the blue zones of the font.
    *
    * @param metrics the metrics to use
    * @param face the font face to analyze
    */
   private void initBlues(LatinMetrics metrics, OpenTypeFont face)
   {
-    // TODO: Implement.
+    int[] flats = new int[MAX_TEST_CHARS];
+    int[] rounds = new int[MAX_TEST_CHARS];
+    int numFlats;
+    int numRounds;
+    LatinBlue blue;
+    LatinAxis axis = metrics.axis[DIMENSION_VERT];
+    // We compute the blues simply by loading each character in the test
+    // strings, then compute its topmost or bottommost points.
+    for (int bb = 0; bb < BLUE_MAX; bb++)
+      {
+        String p = TEST_CHARS[bb];
+        int blueRef;
+        int blueShoot;
+        numFlats = 0;
+        numRounds = 0;
+        for (int i = 0; i < p.length(); i++)
+          {
+            // Load the character.
+            int glyphIndex = face.getGlyph(p.charAt(i));
+            Zone glyph =
+              face.getRawGlyphOutline(glyphIndex, IDENTITY);
+
+            // Now compute the min and max points.
+            int numPoints = glyph.getSize() - 4; // 4 phantom points.
+            Point[] points = glyph.getPoints();
+            Point point = points[0];
+            int extremum = 0;
+            int index = 1;
+            if (isTopBlue(bb))
+              {
+                for (; index < numPoints; index++)
+                  {
+                    point = points[index];
+                    // We have the vertical direction swapped. The higher
+                    // points have smaller (negative) Y.
+                    if (point.getOrigY() < points[extremum].getOrigY())
+                      extremum = index;
+                  }
+              }
+            else
+              {
+                for (; index < numPoints; index++)
+                  {
+                    point = points[index];
+                    // We have the vertical direction swapped. The higher
+                    // points have smaller (negative) Y.
+                    if (point.getOrigY() > points[extremum].getOrigY())
+                      extremum = index;
+                  }
+              }
+            // Debug, prints out the maxima.
+            // System.err.println("extremum for " + bb + " / "+ p.charAt(i)
+            //                    + ": " + points[extremum]);
+
+            // Now determine if the point is part of a straight or round
+            // segment.
+            boolean round;
+            int idx = extremum;
+            int first, last, prev, next, end;
+            int dist;
+            last = -1;
+            first = 0;
+            for (int n = 0; n < glyph.getNumContours(); n++)
+              {
+                end = glyph.getContourEnd(n);
+                // System.err.println("contour end for " + n + ": " + end);
+                if (end >= idx)
+                  {
+                    last = end;
+                    break;
+                  }
+                first = end + 1;
+              }
+            // Should never happen.
+            assert last >= 0;
+
+            // Now look for the previous and next points that are not on the
+            // same Y coordinate. Threshold the 'closeness'.
+            prev = idx;
+            next = prev;
+            do
+              {
+                if (prev > first)
+                  prev--;
+                else
+                  prev = last;
+                dist = points[prev].getOrigY() - points[extremum].getOrigY();
+                if (dist < -5 || dist > 5)
+                  break;
+              } while (prev != idx);
+            do
+              {
+                if (next < last)
+                  next++;
+                else
+                  next = first;
+                dist = points[next].getOrigY() - points[extremum].getOrigY();
+                if (dist < -5 || dist > 5)
+                  break;
+              } while (next != idx);
+            round = points[prev].isControlPoint()
+                    || points[next].isControlPoint();
+
+            if (round)
+              {
+                rounds[numRounds++] = points[extremum].getOrigY();
+                // System.err.println("new round extremum: " + bb + ": "
+                //                   + points[extremum].getOrigY());
+              }
+            else
+              {
+                flats[numFlats++] = points[extremum].getOrigY();
+                // System.err.println("new flat extremum: " + bb + ": "
+                //                    + points[extremum].getOrigY());
+              }
+          }
+        // We have computed the contents of the rounds and flats tables.
+        // Now determine the reference and overshoot position of the blues --
+        // we simply take the median after a simple sort.
+        Utils.sort(numRounds, rounds);
+        Utils.sort(numFlats, flats);
+        blue = axis.blues[axis.blueCount] = new LatinBlue();
+        axis.blueCount++;
+        if (numFlats == 0)
+          {
+            blue.ref = blue.shoot = new Width(rounds[numRounds / 2]);
+          }
+        else if (numRounds == 0)
+          {
+            blue.ref = blue.shoot = new Width(flats[numFlats / 2]);
+          }
+        else
+          {
+            blue.ref = new Width(flats[numFlats / 2]);
+            blue.shoot = new Width(rounds[numRounds / 2]);
+          }
+        // There are sometimes problems:  if the overshoot position of top
+        // zones is under its reference position, or the opposite for bottom
+        // zones. We must check everything there and correct problems.
+        if (blue.shoot != blue.ref)
+          {
+            int ref = blue.ref.org;
+            int shoot = blue.shoot.org;
+            // Inversed vertical coordinates!
+            boolean overRef = shoot < ref;
+            if (isTopBlue(bb) ^ overRef)
+              {
+                blue.shoot = blue.ref = new Width((shoot + ref) / 2);
+              }
+          }
+        blue.flags = 0;
+        if (isTopBlue(bb))
+          blue.flags |= LatinBlue.FLAG_TOP;
+        // The following flag is used later to adjust y and x scales in 
+        // order to optimize the pixel grid alignment of the top small
+        // letters.
+        if (bb == SMALL_TOP)
+          {
+            blue.flags |= LatinBlue.FLAG_ADJUSTMENT;
+          }
+        // Debug: print out the blue zones.
+        System.err.println("blue zone #" + bb + ": " + blue);
+      }
   }
 
+  private static final AffineTransform IDENTITY = new AffineTransform();
+
   private int constant(LatinMetrics metrics, int c)
   {
     return c * (metrics.unitsPerEm / 2048);
   }
 
   private void computeSegments(GlyphHints hints, int dim)
   {
     Point[] points = hints.points;
     if (dim == DIMENSION_HORZ)
       {
         for (int i = 0; i < hints.numPoints; i++)
           {
             points[i].setU(points[i].getOrigX());
             points[i].setV(points[i].getOrigY());
           }
@@ -352,16 +538,21 @@
                 segment.flags = Segment.FLAG_EDGE_NORMAL;
                 minPos = maxPos = point.getU();
                 segment.first = point;
                 segment.last = point;
                 segment.contour = contours[i];
                 segment.score = 32000;
                 segment.len = 0;
                 segment.link = null;
                 onEdge = true;
               }
             point = point.getNext();
           }
       }
     
   }
+
+  private boolean isTopBlue(int b)
+  {
+    return b == CAPITAL_TOP || b == SMALL_F_TOP || b == SMALL_TOP;
+  }
 }
Index: gnu/java/awt/font/autofit/LatinAxis.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/font/autofit/LatinAxis.java,v
retrieving revision 1.2
diff -u -1 -5 -r1.2 LatinAxis.java
--- gnu/java/awt/font/autofit/LatinAxis.java	13 Dec 2006 22:59:43 -0000	1.2
+++ gnu/java/awt/font/autofit/LatinAxis.java	14 Dec 2006 15:13:11 -0000
@@ -36,18 +36,25 @@
 exception statement from your version. */
 
 
 package gnu.java.awt.font.autofit;
 
 /**
  * Some axis specific data.
  */
 class LatinAxis
 {
 
   int scale;
   int delta;
 
   int widthCount;
-  Width[] widths = new Width[Latin.MAX_WIDTHS];
+  Width[] widths;
   float edgeDistanceTreshold;
+  LatinBlue[] blues;
+  int blueCount;
+  LatinAxis()
+  {
+    widths = new Width[Latin.MAX_WIDTHS];
+    blues = new LatinBlue[Latin.BLUE_MAX];
+  }
 }
Index: gnu/java/awt/font/autofit/Utils.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/font/autofit/Utils.java,v
retrieving revision 1.1
diff -u -1 -5 -r1.1 Utils.java
--- gnu/java/awt/font/autofit/Utils.java	13 Dec 2006 22:59:43 -0000	1.1
+++ gnu/java/awt/font/autofit/Utils.java	14 Dec 2006 15:13:11 -0000
@@ -184,16 +184,32 @@
     if (angle > Math.PI)
       angle -= ANGLE_2PI;
     return angle;
   }
 
   public static int angleDiff(int ang1, int ang2)
   {
     int delta = ang2 - ang1;
     delta %= ANGLE_2PI;
     if (delta < 0)
       delta += ANGLE_2PI;
     if (delta > ANGLE_2PI)
       delta -= ANGLE_2PI;
     return delta;
   }
+
+  static void sort(int num, int[] array)
+  {
+    int swap;
+    for (int i = 1; i < num; i++)
+      {
+        for (int j = i; j > 0; j--)
+          {
+            if (array[j] > array[j - 1])
+              break;
+            swap = array[j];
+            array[j] = array[j - 1];
+            array[j - 1] = swap;
+          }
+      }
+  }
 }
Index: gnu/java/awt/font/opentype/truetype/Zone.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/font/opentype/truetype/Zone.java,v
retrieving revision 1.3
diff -u -1 -5 -r1.3 Zone.java
--- gnu/java/awt/font/opentype/truetype/Zone.java	13 Dec 2006 22:59:43 -0000	1.3
+++ gnu/java/awt/font/opentype/truetype/Zone.java	14 Dec 2006 15:13:11 -0000
@@ -231,20 +231,37 @@
    * Returns the number of contours in this outline.
    *
    * @return the number of contours in this outline
    */
   public int getNumContours()
   {
     int num = 0;
     for (int i = 0; i < numPoints; i++)
       {
         if (isContourEnd(i))
           num++;
       }
     return num;
   }
 
+  public int getContourEnd(int n)
+  {
+    int idx = -1;
+    int num = 0;
+    for (int i = 0; i < numPoints; i++)
+      {
+        if (isContourEnd(i))
+          {
+            idx = i;
+            if (num == n)
+              break;
+            num++;
+          }
+      }
+    return idx;
+  }
+
   public Point[] getPoints()
   {
     return points;
   }
 }

Reply via email to