mirror of git://gcc.gnu.org/git/gcc.git
641 lines
19 KiB
Java
641 lines
19 KiB
Java
/* GlyphHints.java -- Data and methods for actual hinting
|
|
Copyright (C) 2006 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA.
|
|
|
|
Linking this library statically or dynamically with other modules is
|
|
making a combined work based on this library. Thus, the terms and
|
|
conditions of the GNU General Public License cover the whole
|
|
combination.
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
permission to link this library with independent modules to produce an
|
|
executable, regardless of the license terms of these independent
|
|
modules, and to copy and distribute the resulting executable under
|
|
terms of your choice, provided that you also meet, for each linked
|
|
independent module, the terms and conditions of the license of that
|
|
module. An independent module is a module which is not derived from
|
|
or based on this library. If you modify this library, you may extend
|
|
this exception to your version of the library, but you are not
|
|
obligated to do so. If you do not wish to do so, delete this
|
|
exception statement from your version. */
|
|
|
|
|
|
package gnu.java.awt.font.autofit;
|
|
|
|
import gnu.java.awt.font.FontDelegate;
|
|
import gnu.java.awt.font.opentype.truetype.Fixed;
|
|
import gnu.java.awt.font.opentype.truetype.Point;
|
|
import gnu.java.awt.font.opentype.truetype.Zone;
|
|
|
|
/**
|
|
* The data and methods used for the actual hinting process.
|
|
*/
|
|
class GlyphHints
|
|
implements Constants
|
|
{
|
|
|
|
int xScale;
|
|
int xDelta;
|
|
int yScale;
|
|
int yDelta;
|
|
|
|
AxisHints[] axis;
|
|
|
|
Point[] points;
|
|
int numPoints;
|
|
int maxPoints;
|
|
|
|
Point[] contours;
|
|
int numContours;
|
|
int maxContours;
|
|
|
|
ScriptMetrics metrics;
|
|
|
|
int flags;
|
|
|
|
GlyphHints()
|
|
{
|
|
axis = new AxisHints[Constants.DIMENSION_MAX];
|
|
axis[Constants.DIMENSION_VERT] = new AxisHints();
|
|
axis[Constants.DIMENSION_HORZ] = new AxisHints();
|
|
|
|
xScale = Fixed.ONE;
|
|
yScale = Fixed.ONE;
|
|
}
|
|
|
|
void rescale(ScriptMetrics m)
|
|
{
|
|
metrics = m;
|
|
// TODO: Copy scalerFlags.
|
|
}
|
|
|
|
void reload(Zone outline)
|
|
{
|
|
numPoints = 0;
|
|
numContours = 0;
|
|
axis[0].numSegments = 0;
|
|
axis[0].numEdges = 0;
|
|
axis[1].numSegments = 0;
|
|
axis[1].numEdges = 0;
|
|
|
|
// Create/reallocate the contours array.
|
|
int newMax = outline.getNumContours();
|
|
if (newMax > maxContours || contours == null)
|
|
{
|
|
newMax = (newMax + 3) & ~3; // Taken from afhints.c .
|
|
Point[] newContours = new Point[newMax];
|
|
if (contours != null)
|
|
{
|
|
System.arraycopy(contours, 0, newContours, 0, maxContours);
|
|
}
|
|
contours = newContours;
|
|
maxContours = newMax;
|
|
}
|
|
|
|
// Create/reallocate the points array.
|
|
newMax = outline.getSize() + 2;
|
|
if (newMax > maxPoints || points == null)
|
|
{
|
|
newMax = (newMax + 2 + 7) & ~7; // Taken from afhints.c .
|
|
Point[] newPoints = new Point[newMax];
|
|
if (points != null)
|
|
{
|
|
System.arraycopy(points, 0, newPoints, 0, maxPoints);
|
|
}
|
|
points = newPoints;
|
|
maxPoints = newMax;
|
|
}
|
|
|
|
numPoints = outline.getSize() - 4; // 4 phantom points.
|
|
numContours = outline.getNumContours();
|
|
|
|
// Set major direction. We don't handle Type 1 fonts yet.
|
|
axis[DIMENSION_HORZ].majorDir = DIR_UP;
|
|
axis[DIMENSION_VERT].majorDir = DIR_LEFT;
|
|
|
|
// TODO: Freetype seems to scale and translate the glyph at that point.
|
|
// I suppose that this is not really needed.
|
|
// The scales are scaling from font units to 1/64 device pixels.
|
|
xScale = Fixed.valueOf16(outline.scaleX * 64);
|
|
yScale = Fixed.valueOf16(outline.scaleY * 64);
|
|
|
|
// FIXME: What is that xDelta and yDelta used for?
|
|
System.arraycopy(outline.getPoints(), 0, points, 0, numPoints);
|
|
|
|
// Setup prev and next and contours array.
|
|
// TODO: Probably cache this.
|
|
contours = new Point[numContours];
|
|
Point currentContour = points[0];
|
|
for (int i = 0, cIndex = 0; i < numPoints; i++)
|
|
{
|
|
// Start new contour when the last point has been a contour end.
|
|
if (outline.isContourEnd(i))
|
|
{
|
|
// Connect the contour end point to the start point.
|
|
points[i].setNext(currentContour);
|
|
currentContour.setPrev(points[i]);
|
|
contours[cIndex] = currentContour;
|
|
cIndex++;
|
|
currentContour = i < numPoints - 1 ? points[i + 1] : null;
|
|
}
|
|
else
|
|
{
|
|
// Connect the current and the previous point.
|
|
points[i].setNext(points[i + 1]);
|
|
points[i + 1].setPrev(points[i]);
|
|
}
|
|
}
|
|
// Compute directions of in and out vectors of all points as well
|
|
// as the weak point flag.
|
|
for (int i = 0; i < numPoints; i++)
|
|
{
|
|
// Compute in and out dir.
|
|
Point p = points[i];
|
|
Point prev = p.getPrev();
|
|
int inX = p.getOrigX() - prev.getOrigX();
|
|
int inY = p.getOrigY() - prev.getOrigY();
|
|
p.setInDir(Utils.computeDirection(inX, inY));
|
|
Point next = p.getNext();
|
|
int outX = next.getOrigX() - p.getOrigX();
|
|
int outY = next.getOrigY() - p.getOrigY();
|
|
p.setOutDir(Utils.computeDirection(outX, outY));
|
|
|
|
if (p.isControlPoint())
|
|
{
|
|
setWeakPoint(p);
|
|
}
|
|
else if (p.getOutDir() == p.getInDir())
|
|
{
|
|
if (p.getOutDir() != DIR_NONE)
|
|
setWeakPoint(p);
|
|
else
|
|
{
|
|
int angleIn = Utils.atan(inY, inX);
|
|
int angleOut = Utils.atan(outY, outX);
|
|
int delta = Utils.angleDiff(angleIn, angleOut);
|
|
if (delta < 2 && delta > -2)
|
|
setWeakPoint(p);
|
|
}
|
|
}
|
|
else if (p.getInDir() == - p.getOutDir())
|
|
{
|
|
setWeakPoint(p);
|
|
}
|
|
}
|
|
computeInflectionPoints();
|
|
}
|
|
|
|
private void setWeakPoint(Point p)
|
|
{
|
|
p.setFlags((byte) (p.getFlags() | Point.FLAG_WEAK_INTERPOLATION));
|
|
}
|
|
|
|
/**
|
|
* Computes the inflection points for a glyph.
|
|
*/
|
|
private void computeInflectionPoints()
|
|
{
|
|
// Do each contour separately.
|
|
contours : for (int c = 0; c < contours.length; c++)
|
|
{
|
|
Point point = contours[c];
|
|
Point first = point;
|
|
Point start = point;
|
|
Point end = point;
|
|
do
|
|
{
|
|
end = end.getNext();
|
|
if (end == first)
|
|
continue contours;
|
|
} while (end.getOrigX() == first.getOrigX()
|
|
&& end.getOrigY() == first.getOrigY());
|
|
|
|
// Extend segment start whenever possible.
|
|
Point before = start;
|
|
int angleIn;
|
|
int angleSeg = Utils.atan(end.getOrigX() - start.getOrigX(),
|
|
end.getOrigY() - start.getOrigY());
|
|
do
|
|
{
|
|
do
|
|
{
|
|
start = before;
|
|
before = before.getPrev();
|
|
if (before == first)
|
|
continue contours;
|
|
} while (before.getOrigX() == start.getOrigX()
|
|
&& before.getOrigY() == start.getOrigY());
|
|
angleIn = Utils.atan(start.getOrigX() - before.getOrigX(),
|
|
start.getOrigY() - before.getOrigY());
|
|
} while (angleIn == angleSeg);
|
|
|
|
first = start;
|
|
int diffIn = Utils.angleDiff(angleIn, angleSeg);
|
|
// Now, process all segments in the contour.
|
|
Point after;
|
|
boolean finished = false;
|
|
int angleOut, diffOut;
|
|
do
|
|
{
|
|
// First, extend the current segment's end whenever possible.
|
|
after = end;
|
|
do
|
|
{
|
|
do
|
|
{
|
|
end = after;
|
|
after = after.getNext();
|
|
if (after == first)
|
|
finished = true;
|
|
} while (end.getOrigX() == after.getOrigX()
|
|
&& end.getOrigY() == after.getOrigY());
|
|
angleOut = Utils.atan(after.getOrigX() - end.getOrigX(),
|
|
after.getOrigY() - end.getOrigY());
|
|
} while (angleOut == angleSeg);
|
|
diffOut = Utils.angleDiff(angleSeg, angleOut);
|
|
if ((diffIn ^ diffOut) < 0)
|
|
{
|
|
// diffIn and diffOut have different signs, we have
|
|
// inflection points here.
|
|
do
|
|
{
|
|
start.addFlags(Point.FLAG_INFLECTION);
|
|
start = start.getNext();
|
|
} while (start != end);
|
|
start.addFlags(Point.FLAG_INFLECTION);
|
|
}
|
|
start = end;
|
|
end = after;
|
|
angleSeg = angleOut;
|
|
diffIn = diffOut;
|
|
} while (! finished);
|
|
}
|
|
}
|
|
|
|
boolean doHorizontal()
|
|
{
|
|
return (flags & FontDelegate.FLAG_NO_HINT_HORIZONTAL) == 0;
|
|
}
|
|
|
|
boolean doVertical()
|
|
{
|
|
return (flags & FontDelegate.FLAG_NO_HINT_VERTICAL) == 0;
|
|
}
|
|
|
|
void alignWeakPoints(int dim)
|
|
{
|
|
short touchFlag;
|
|
Point point;
|
|
// PASS 1 : Move segments to edge positions.
|
|
if (dim == DIMENSION_HORZ)
|
|
{
|
|
touchFlag = Point.FLAG_DONE_X;
|
|
for (int p = 0; p < numPoints; p++)
|
|
{
|
|
point = points[p];
|
|
point.setU(point.getX());
|
|
point.setV(point.getScaledX());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
touchFlag = Point.FLAG_DONE_Y;
|
|
for (int p = 0; p < numPoints; p++)
|
|
{
|
|
point = points[p];
|
|
point.setU(point.getY());
|
|
point.setV(point.getScaledY());
|
|
}
|
|
}
|
|
point = points[0];
|
|
for (int c = 0; c < numContours; c++)
|
|
{
|
|
point = contours[c];
|
|
int idx = getPointIndex(point);
|
|
Point endPoint = point.getPrev();
|
|
int endIdx = getPointIndex(endPoint);
|
|
int firstIdx = idx;
|
|
while (idx <= endIdx
|
|
&& (point.getFlags() & touchFlag) == 0)
|
|
{
|
|
idx++;
|
|
point = points[idx];
|
|
}
|
|
if (idx <= endIdx)
|
|
{
|
|
int firstTouched = idx;
|
|
int curTouched = idx;
|
|
idx++;
|
|
point = points[idx];
|
|
while (idx <= endIdx)
|
|
{
|
|
if ((point.getFlags() & touchFlag) != 0)
|
|
{
|
|
// We found two successive touch points. We interpolate
|
|
// all contour points between them.
|
|
iupInterp(curTouched + 1, idx - 1, curTouched, idx);
|
|
curTouched = idx;
|
|
}
|
|
idx++;
|
|
point = points[idx];
|
|
}
|
|
if (curTouched == firstTouched)
|
|
{
|
|
// This is a special case: Only one point was touched in the
|
|
// contour. We thus simply shift the whole contour.
|
|
iupShift(firstIdx, endIdx, curTouched);
|
|
}
|
|
else
|
|
{
|
|
// Now interpolate after the last touched point to the end
|
|
// of the contour.
|
|
iupInterp(curTouched + 1, endIdx, curTouched, firstTouched);
|
|
// If the first contour point isn't touched, interpolate
|
|
// from the contour start to the first touched point.
|
|
if (firstTouched > 0)
|
|
{
|
|
iupInterp(firstIdx, firstTouched - 1, curTouched,
|
|
firstTouched);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Now store the values back.
|
|
if (dim == DIMENSION_HORZ)
|
|
{
|
|
for (int p = 0; p < numPoints; p++)
|
|
{
|
|
point = points[p];
|
|
point.setX(point.getU());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int p = 0; p < numPoints; p++)
|
|
{
|
|
point = points[p];
|
|
point.setY(point.getU());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void iupShift(int p1, int p2, int ref)
|
|
{
|
|
int delta = points[ref].getU() - points[ref].getV();
|
|
for (int p = p1; p < ref; p++)
|
|
{
|
|
points[p].setU(points[p].getV() + delta);
|
|
}
|
|
for (int p = ref + 1; p <= p2; p++)
|
|
{
|
|
points[p].setU(points[p].getV() + delta);
|
|
}
|
|
}
|
|
|
|
private void iupInterp(int p1, int p2, int ref1, int ref2)
|
|
{
|
|
int v1 = points[ref1].getV();
|
|
int v2 = points[ref2].getV();
|
|
int d1 = points[ref1].getU() - v1;
|
|
int d2 = points[ref2].getU() - v2;
|
|
if (p1 > p2)
|
|
return;
|
|
if (v1 == v2)
|
|
{
|
|
for (int p = p1; p <= p2; p++)
|
|
{
|
|
int u = points[p].getV();
|
|
if (u <= v1)
|
|
u += d1;
|
|
else
|
|
u += d2;
|
|
points[p].setU(u);
|
|
}
|
|
}
|
|
else if (v1 < v2)
|
|
{
|
|
for (int p = p1; p <= p2; p++)
|
|
{
|
|
int u = points[p].getV();
|
|
if (u <= v1)
|
|
u += d1;
|
|
else if (u >= v2)
|
|
u += d2;
|
|
else
|
|
{
|
|
u = points[ref1].getU() + Utils.mulDiv(u - v1,
|
|
points[ref2].getU()
|
|
- points[ref1].getU(),
|
|
v2 - v1);
|
|
}
|
|
points[p].setU(u);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int p = p1; p <= p2; p++)
|
|
{
|
|
int u = points[p].getV();
|
|
if (u <= v2)
|
|
u += d2;
|
|
else if (u >= v1)
|
|
u += d1;
|
|
else
|
|
{
|
|
u = points[ref1].getU() + Utils.mulDiv(u - v1,
|
|
points[ref2].getU()
|
|
- points[ref1].getU(),
|
|
v2 - v1);
|
|
}
|
|
points[p].setU(u);
|
|
}
|
|
}
|
|
}
|
|
|
|
void alignStrongPoints(int dim)
|
|
{
|
|
AxisHints ax = axis[dim];
|
|
Edge[] edges = ax.edges;
|
|
int numEdges = ax.numEdges;
|
|
short touchFlag;
|
|
if (dim == DIMENSION_HORZ)
|
|
touchFlag = Point.FLAG_DONE_X;
|
|
else
|
|
touchFlag = Point.FLAG_DONE_Y;
|
|
|
|
if (numEdges > 0)
|
|
{
|
|
for (int p = 0; p < numPoints; p++)
|
|
{
|
|
Point point = points[p];
|
|
if ((point.getFlags() & touchFlag) != 0)
|
|
continue;
|
|
// If this point is a candidate for weak interpolation, we
|
|
// interpolate it after all strong points have been processed.
|
|
if ((point.getFlags() & Point.FLAG_WEAK_INTERPOLATION) != 0
|
|
&& (point.getFlags() & Point.FLAG_INFLECTION) == 0)
|
|
continue;
|
|
|
|
int u, ou, fu, delta;
|
|
if (dim == DIMENSION_VERT)
|
|
{
|
|
u = point.getOrigY();
|
|
ou = point.getScaledY();
|
|
}
|
|
else
|
|
{
|
|
u = point.getOrigX();
|
|
ou = point.getScaledX();
|
|
}
|
|
fu = u;
|
|
// Is the point before the first edge?
|
|
Edge edge = edges[0];
|
|
// Inversed vertical dimension.
|
|
delta = edge.fpos - u;
|
|
if (delta >= 0)
|
|
{
|
|
u = edge.pos - (edge.opos - ou);
|
|
storePoint(point, u, dim, touchFlag);
|
|
}
|
|
else
|
|
{
|
|
// Is the point after the last edge?
|
|
edge = edges[numEdges - 1];
|
|
delta = u - edge.fpos;
|
|
if (delta >= 0)
|
|
{
|
|
u = edge.pos + (ou - edge.opos);
|
|
storePoint(point, u, dim, touchFlag);
|
|
}
|
|
else
|
|
{
|
|
// Find enclosing edges.
|
|
int min = 0;
|
|
int max = numEdges;
|
|
int mid, fpos;
|
|
boolean found = false;
|
|
while (min < max)
|
|
{
|
|
mid = (max + min) / 2;
|
|
edge = edges[mid];
|
|
fpos = edge.fpos;
|
|
if (u < fpos)
|
|
max = mid;
|
|
else if (u > fpos)
|
|
min = mid + 1;
|
|
else
|
|
{
|
|
// Directly on the edge.
|
|
u = edge.pos;
|
|
storePoint(point, u, dim, touchFlag);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (! found)
|
|
{
|
|
Edge before = edges[min - 1];
|
|
Edge after = edges[min];
|
|
if (before.scale == 0)
|
|
{
|
|
before.scale = Fixed.div16(after.pos - before.pos,
|
|
after.fpos - before.fpos);
|
|
}
|
|
u = before.pos + Fixed.mul16(fu - before.fpos,
|
|
before.scale);
|
|
}
|
|
storePoint(point, u, dim, touchFlag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void storePoint(Point p, int u, int dim, short touchFlag)
|
|
{
|
|
if (dim == DIMENSION_HORZ)
|
|
p.setX(u);
|
|
else
|
|
p.setY(u);
|
|
p.addFlags(touchFlag);
|
|
}
|
|
|
|
void alignEdgePoints(int dim)
|
|
{
|
|
AxisHints ax = axis[dim];
|
|
Edge[] edges = ax.edges;
|
|
int numEdges = ax.numEdges;
|
|
for (int e = 0; e < numEdges; e++)
|
|
{
|
|
Edge edge = edges[e];
|
|
Segment seg = edge.first;
|
|
do
|
|
{
|
|
Point point = seg.first;
|
|
while (true)
|
|
{
|
|
if (dim == DIMENSION_HORZ)
|
|
{
|
|
point.setX(edge.pos);
|
|
point.addFlags(Point.FLAG_DONE_X);
|
|
}
|
|
else
|
|
{
|
|
point.setY(edge.pos);
|
|
point.addFlags(Point.FLAG_DONE_Y);
|
|
}
|
|
if (point == seg.last)
|
|
break;
|
|
point = point.getNext();
|
|
}
|
|
seg = seg.edgeNext;
|
|
} while (seg != edge.first);
|
|
}
|
|
}
|
|
|
|
private int getPointIndex(Point p)
|
|
{
|
|
int idx = -1;
|
|
for (int i = 0; i < numPoints; i++)
|
|
{
|
|
if (p == points[i])
|
|
{
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
public boolean doAlignEdgePoints()
|
|
{
|
|
return (flags & FontDelegate.FLAG_NO_HINT_EDGE_POINTS) == 0;
|
|
}
|
|
|
|
public boolean doAlignStrongPoints()
|
|
{
|
|
return (flags & FontDelegate.FLAG_NO_HINT_STRONG_POINTS) == 0;
|
|
}
|
|
|
|
public boolean doAlignWeakPoints()
|
|
{
|
|
return (flags & FontDelegate.FLAG_NO_HINT_WEAK_POINTS) == 0;
|
|
}
|
|
}
|