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