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;
}
}