com.adobe.fontengine.font.cff.NonOverlappingOutlineConsumer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
package com.adobe.fontengine.font.cff;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Collections;
import java.util.Comparator;
import com.adobe.fontengine.font.Matrix;
import com.adobe.fontengine.font.OutlineConsumer;
import com.adobe.fontengine.math.Math2;
/**
* A class that consumes an outline and generates an outline with overlapping paths removed
*/
public class NonOverlappingOutlineConsumer implements OutlineConsumer {
private static final int MAX_NUM_SUBPATHS=1000;
private static final int MAX_NUM_SEGMENTS=1000;
//Segment flags
private static final int SEG_DELETE = (1<<0); //Segment deleted (must be bit zero)
private static final int SEG_ISECT = (1<<1); // Segment intersected
private static final int SEG_WIND_TEST =(1<<2); // Segment winding tested
// Scale to ten-thousandths of an em
private static float milliem(float unitsPerEm, float v) {
return (float) (unitsPerEm*(v)/1000.0);
}
// Exception specific to removal of overlapping paths
private static class ISectException extends Exception {
static final long serialVersionUID = 1;
public ISectException () {
super ();
}
public ISectException (String message) {
super (message);
}
public ISectException (String message, Throwable cause) {
super (message, cause);
}
public ISectException (Throwable cause) {
super (cause);
}
}
// private class representing a point
private static class Point
{
float x, y;
Point(float x, float y) {
this.x = x;
this.y = y;
}
Point() {}
Point(Point p) {
this.x = p.x;
this.y = p.y;
}
public String toString() {
return "(" + x + "," + y + ")";
}
public boolean equals(Point p) {
return (Math2.epsilonEquals(this.x, p.x) && Math2.epsilonEquals(this.y, p.y));
}
}
// private class representing a rectangle
private static class Rect {
float left, bottom, right, top;
Rect(float l, float b, float r, float t) {
left = l;
bottom = b;
right = r;
top = t;
}
private void grow(Rect r1) {
if (this.left > r1.left)
this.left = r1.left;
if (this.bottom > r1.bottom)
this.bottom = r1.bottom;
if (this.right < r1.right)
this.right = r1.right;
if (this.top < r1.top)
this.top = r1.top;
}
private boolean overlap(Rect r1) {
boolean doesNotOverlap =
((this.right < r1.left) || (this.left > r1.right) ||
(this.top < r1.bottom) || (this.bottom > r1.top));
return !doesNotOverlap;
}
private Rect iSect(Rect r1) {
float l, r, t, b;
l = Math.max(this.left, r1.left);
r = Math.min(right, r1.right);
t = Math.min(top, r1.top);
b = Math.max(bottom, r1.bottom);
return new Rect(l, b, r, t);
}
public String toString() {
return "left:" + left + " right:" + right + "bottom: " + bottom + "top: " + top;
}
}
// private helper class representing an interception and winding in order to compute
// if a segment is inside of outside of path
private static class ICept {
float ic;
int winding;
ICept(float ic, int winding) {
this.ic = ic;
this.winding = winding;
}
}
// private class representing a line segment
private static class Line extends Segment
{
Point p0;
Point p1;
float t0, t1; //start and end of split
Rect bounds;
int id;
Line(float x0, float y0, float x1, float y1)
{
p0 = new Point(x0, y0);
p1 = new Point(x1, y1);
t0 = 0;
t1 = 1;
setBounds();
}
Line(Point start, Point end)
{
p0 = new Point(start);
p1 = new Point(end);
t0 = 0;
t1 = 1;
setBounds();
}
public String toString() {
String flagStr = ((this.flags & SEG_DELETE) != 0) ? "Delete," : "";
flagStr = ((this.flags & SEG_WIND_TEST) != 0) ? flagStr+" WindTested" : flagStr;
String s = "Point p0:" + p0.toString() + " Point p1:" + p1.toString() + "t0:" + t0 + " t1:" + t1 + "flag:" + flagStr +" bounds:" + bounds.toString();
return s;
}
int getID() {
return id;
}
Point getFirstPoint() {
return p0;
}
Point getLastPoint() {
return p1;
}
boolean horizontal() {
return (Math.abs(p1.x - p0.x) >
Math.abs(p1.y - p0.y));
}
//compute bounds for the line segment
void setBounds() {
float left, bottom, right, top;
if (p0.x < p1.x) {
left = p0.x;
right = p1.x;
}
else
{
right = p0.x;
left = p1.x;
}
if (p0.y < p1.y) {
bottom = p0.y;
top = p1.y;
}
else
{
top = p0.y;
bottom = p1.y;
}
bounds = new Rect(left, bottom, right, top);
}
void roundControlPoints(float unitsPerEm) {
p0.x = Math.round(p0.x);
p0.y = Math.round(p0.y);
p1.x = Math.round(p1.x);
p1.y = Math.round(p1.y);
}
Rect getBounds() {
if (bounds != null)
return bounds;
setBounds();
return bounds;
}
boolean equalControlPoints(Segment s) {
if (s instanceof Line) {
Line l = (Line) s;
return (Math2.epsilonEquals(p0.x, l.p0.x) && Math2.epsilonEquals(p0.y, l.p0.y) &&
Math2.epsilonEquals(p1.x, l.p1.x) && Math2.epsilonEquals(p1.y, l.p1.y));
}
return false;
}
boolean reverseControlPoints(Segment s) {
if (s instanceof Line) {
Line l = (Line) s;
return (Math2.epsilonEquals(p0.x, l.p1.x) && Math2.epsilonEquals(p0.y, l.p1.y) &&
Math2.epsilonEquals(p1.x, l.p0.x) && Math2.epsilonEquals(p1.y, l.p0.y));
}
return false;
}
Segment copySegment() {
Line newl = new Line(p0, p1);
newl.t0 = t0;
newl.t1 = t1;
newl.id = id;
newl.flags = flags;
newl.origSeg = origSeg;
return newl;
}
// compute intersection point
private Point computeISectPoint(float t) {
float x = p0.x + t*(p1.x - p0.x);
float y = p0.y + t*(p1.y - p0.y);
return new Point(x, y);
}
// Check if line u0->u1 intersects with rectangle r. Return true if intersection
// else false. Cohen-Sutherland algorithm.
private boolean iSectRect(Rect r) {
final int CLIP_CODE_LEFT = 1;
final int CLIP_CODE_RIGHT = 2;
final int CLIP_CODE_BOTTOM = 4;
final int CLIP_CODE_TOP = 8;
int c0;
int c1;
if (p0.x < r.left)
c0 = CLIP_CODE_LEFT;
else if (p0.x > r.right)
c0 = CLIP_CODE_RIGHT;
else
c0 = 0;
if (p0.y > r.top)
c0 |= CLIP_CODE_TOP;
else if (p0.y < r.bottom)
c0 |= CLIP_CODE_BOTTOM;
if (c0 == 0)
return true;
if (p1.x < r.left)
c1 = CLIP_CODE_LEFT;
else if (p1.x > r.right)
c1 = CLIP_CODE_RIGHT;
else
c1 = 0;
if (p1.y > r.top)
c1 |= CLIP_CODE_TOP;
else if (p1.y < r.bottom)
c1 |= CLIP_CODE_BOTTOM;
if (c1 == 0)
return true;
return (c0 & c1) == 0;
}
// Intersect lines. Returns as a list of 2 t values per line
private List iSectLine(Line l) {
float a0 = p1.y - p0.y;
float b0 = p1.x - p0.x;
float a1 = l.p1.y - l.p0.y;
float b1 = l.p1.x - l.p0.x;
float d = a1*b0 - b1*a0;
float j = p0.y - l.p0.y;
float k = p0.x - l.p0.x;
float n0 = b1*j - a1*k;
List iSects = new ArrayList();
if (d == 0)
{
// Lines are parallel
if (n0 == 0)
{
// Lines are coincident; compute intersection rect
Rect lBounds = l.getBounds();
if (!bounds.overlap(lBounds))
return iSects;
Rect r = bounds.iSect(lBounds);
if (r.left == r.right && r.bottom == r.top)
{
float[] t = new float[2];
// Lines touch at end points
if (b0 != 0)
{
// Compute t horizontally
t[0] = (Math2.epsilonEquals(p1.x, r.left)) ? 1 : 0;
t[1] = (Math2.epsilonEquals(l.p1.x, r.left)) ? 1 : 0;
}
else
{
// Compute t vertically
t[0] = (Math2.epsilonEquals(p1.y, r.bottom)) ? 1 : 0;
t[1] = (Math2.epsilonEquals(l.p1.y, r.bottom)) ? 1 : 0;
}
iSects.add(t);
return iSects;
}
else if (r.right - r.left > r.top - r.bottom)
{
// Compute t horizontally
float[] t = new float[2];
float[] t1 = new float[2];
t[0] = (r.left - p0.x)/b0;
t[1] = (r.left - l.p0.x)/b1;
t1[0] = (r.right - p0.x)/b0;
t1[1] = (r.right - l.p0.x)/b1;
iSects.add(t);
iSects.add(t1);
return iSects;
}
else
{
// Compute t vertically
float[] t = new float[2];
float[] t1 = new float[2];
t[0] = (r.bottom - p0.y)/a0;
t[1] = (r.bottom - l.p0.y)/a1;
t1[0] = (r.top - p0.y)/a0;
t1[1] = (r.top - l.p0.y)/a1;
iSects.add(t);
iSects.add(t1);
return iSects;
}
}
else
return iSects;
}
// Non-parallel lines; check they both have intercepts
float[] t = new float[2];
t[0] = n0/d;
if (t[0] < 0 || t[0] > 1)
return iSects;
t[1] = (b0*j - a0*k)/d;
if (t[1] < 0 || t[1] > 1)
return iSects;
iSects.add(t);
return iSects;
}
List solveAtX(float x) {
float value = p0.y + (x-p0.x)*(p1.y - p0.y)/(p1.x - p0.x);
ICept icept = new ICept(value, getWindingX());
List icList = new ArrayList();
icList.add(icept);
return icList;
}
List solveAtY(float y) {
float value = p0.x + (y-p0.y)*(p1.x - p0.x)/(p1.y - p0.y);
ICept icept = new ICept(value, getWindingY());
List icList = new ArrayList();
icList.add(icept);
return icList;
}
int getWindingX() {
return getWinding(p0.x, p1.x);
}
int getWindingY() {
return getWinding(p0.y, p1.y);
}
}
// private class representing a curve segment
private static class Curve extends Segment
{
Point p0, p1, p2, p3;
float t0, t1; //split start and end
long depth; //recursion depth
Rect bounds;
int id;
private static class Limit {
float min, max;
Limit(float minimum, float maximum) {
min = minimum;
max = maximum;
}
}
Curve(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3)
{
p0 = new Point(x0, y0);
p1 = new Point(x1, y1);
p2 = new Point(x2, y2);
p3 = new Point(x3, y3);
t0 = 0;
t1 = 1;
depth = 0;
setBounds();
}
Curve(Point pnt0, Point pnt1, Point pnt2, Point pnt3)
{
p0 = new Point(pnt0);
p1 = new Point(pnt1);
p2 = new Point(pnt2);
p3 = new Point(pnt3);
t0 = 0;
t1 = 1;
depth = 0;
setBounds();
}
public String toString() {
String flagStr = ((this.flags & SEG_DELETE) != 0) ? "Delete," : "";
flagStr = ((this.flags & SEG_WIND_TEST) != 0) ? flagStr+" WindTested" : flagStr;
String s = "Point p0:" + p0.toString() + " Point p1:" + p1.toString() + "Point p2:" + p2.toString() + " Point p3:" + p3.toString() + "t0:" + t0 + " t1:" + t1 + "flag:" + flagStr+" bounds:" + bounds.toString();
return s;
}
int getID() {
return id;
}
private Limit getBezLimit(float p0, float p1, float p2, float p3, float min, float max)
{
float t[] = new float[2];
int i = 0;
float a = p3 - 3*(p2 - p1) - p0;
float b = p2 - 2*p1 + p0;
float c = p1 - p0;
float minimum = min;
float maximum = max;
if (a == 0)
{
if (b != 0)
t[i++] = -c/(2*b);
}
else
{
float r = b*b - a*c;
if (r >= 0)
{
r = (float)Math.sqrt(r);
t[i++] = (-b + r)/a;
t[i++] = (-b - r)/a;
}
}
for (i--; i >=0 ; i--) {
if (t[i] > 0 && t[i] < 1)
{
float limit = t[i]*(t[i]*(t[i]*a + 3*b) + 3*c) + p0;
if (limit < min)
minimum = limit;
else if (limit > max)
maximum = limit;
}
}
Limit l = new Limit(minimum, maximum);
return l;
}
Point getFirstPoint() {
return p0;
}
Point getLastPoint() {
return p3;
}
boolean horizontal() {
return (Math.abs(p3.x - p0.x) >
Math.abs(p3.y - p0.y));
}
boolean equalControlPoints(Segment s) {
if (s instanceof Curve) {
Curve c = (Curve) s;
return (Math2.epsilonEquals(p0.x, c.p0.x) && Math2.epsilonEquals(p0.y, c.p0.y) &&
Math2.epsilonEquals(p1.x, c.p1.x) && Math2.epsilonEquals(p1.y, c.p1.y) &&
Math2.epsilonEquals(p2.x, c.p2.x) && Math2.epsilonEquals(p2.y, c.p2.y) &&
Math2.epsilonEquals(p3.x, c.p3.x) && Math2.epsilonEquals(p3.y, c.p3.y));
}
return false;
}
boolean reverseControlPoints(Segment s) {
if (s instanceof Curve) {
Curve c = (Curve) s;
return (Math2.epsilonEquals(p0.x, c.p3.x) && Math2.epsilonEquals(p0.y, c.p3.y) &&
Math2.epsilonEquals(p1.x, c.p2.x) && Math2.epsilonEquals(p1.y, c.p2.y) &&
Math2.epsilonEquals(p2.x, c.p1.x) && Math2.epsilonEquals(p2.y, c.p1.y) &&
Math2.epsilonEquals(p3.x, c.p0.x) && Math2.epsilonEquals(p3.y, c.p0.y));
}
return false;
}
Segment copySegment() {
Curve newc = new Curve(p0, p1, p2, p3);
newc.t0 = t0;
newc.t1 = t1;
newc.id = id;
newc.flags = flags;
newc.origSeg = origSeg;
return newc;
}
void roundControlPoints(float unitsPerEm) {
p0.x = Math.round(p0.x);
p0.y = Math.round(p0.y);
p1.x = Math.round(p1.x);
p1.y = Math.round(p1.y);
p2.x = Math.round(p2.x);
p2.y = Math.round(p2.y);
p3.x = Math.round(p3.x);
p3.y = Math.round(p3.y);
if (isLine(unitsPerEm))
makeLine();
}
//compute bounds for the bezier curve
void setBounds() {
//set initial bounds from end points
Line l = new Line(p0, p3);
Rect r = l.getBounds();
if ((p1.x < r.left) || (p1.x > r.right) || (p2.x < r.left) || (p2.x > r.right)) {
Limit lim = getBezLimit(p0.x, p1.x, p2.x, p3.x, r.left, r.right);
r.left = lim.min;
r.right = lim.max;
}
if ((p1.y < r.bottom) || (p1.y > r.top) || (p2.y < r.bottom) || (p2.y > r.top)) {
Limit lim = getBezLimit(p0.y, p1.y, p2.y, p3.y, r.bottom, r.top);
r.bottom = lim.min;
r.top = lim.max;
}
bounds = r;
}
Rect getBounds() {
if (bounds != null)
return bounds;
setBounds();
return bounds;
}
private Point computeISectPoint(float t) {
float x = t*(t*(t*(p3.x - 3*(p2.x - p1.x) - p0.x) +
3*(p2.x - 2*p1.x + p0.x)) +
3*(p1.x - p0.x)) + p0.x;
float y = t*(t*(t*(p3.y - 3*(p2.y - p1.y) - p0.y) +
3*(p2.y - 2*p1.y + p0.y)) +
3*(p1.y - p0.y)) + p0.y;
return new Point(x, y);
}
private boolean isLine(float unitsPerEm) {
float a = p3.y - p0.y;
float b = p3.x - p0.x;
if (Math.abs(a) <= 1 && Math.abs(b) <= 1)
return true; // Segment within unit square
else
{
float s = a*(p1.x - p0.x) + b*(p0.y - p1.y);
float t = a*(p2.x - p0.x) + b*(p0.y - p2.y);
float tol = milliem(unitsPerEm, 1);
return (s*s + t*t) < tol*tol*(a*a + b*b);
}
}
private Line makeLine() {
Line l = new Line(p0.x, p0.y, p3.x, p3.y);
l.id = id;
l.t0 = t0;
l.t1 = t1;
l.origSeg = this.origSeg;
return l;
}
private List splitAtT(float t) {
List curveList = new ArrayList(2);
float ap0x = p0.x;
float ap0y = p0.y;
float ap1x = t*(p1.x - p0.x) + ap0x;
float ap1y = t*(p1.y - p0.y) + ap0y;
float ap2x = t*t*(p2.x - 2*p1.x + p0.x) + 2*ap1x - ap0x;
float ap2y = t*t*(p2.y - 2*p1.y + p0.y) + 2*ap1y - ap0y;
float ap3x = t*t*t*(p3.x - 3*(p2.x - p1.x) - p0.x) +
3*(ap2x - ap1x) + ap0x;
float ap3y = t*t*t*(p3.y - 3*(p2.y - p1.y) - p0.y) +
3*(ap2y - ap1y) + ap0y;
Curve a = new Curve(ap0x, ap0y, ap1x, ap1y, ap2x, ap2y, ap3x, ap3y);
a.t0 = 0;
a.t1 = t;
a.origSeg = this.origSeg;
curveList.add(a);
t = 1 - t;
float bp3x = p3.x;
float bp3y = p3.y;
float bp2x = t*(p2.x - p3.x) + bp3x;
float bp2y = t*(p2.y - p3.y) + bp3y;;
float bp1x = t*t*(p1.x - 2*p2.x + p3.x) + 2*bp2x - bp3x;
float bp1y = t*t*(p1.y - 2*p2.y + p3.y) + 2*bp2y - bp3y;
float bp0x = ap3x;
float bp0y = ap3y;
Curve b = new Curve(bp0x, bp0y, bp1x, bp1y, bp2x, bp2y, bp3x, bp3y);
b.t0 = t;
b.t1 = 1;
b.origSeg = this.origSeg;
curveList.add(b);
return curveList;
}
// Split Bezier curve a at the midpoint into Bezier curves b and a.
private List splitMid() {
Point s = new Point();
Point t = new Point();
Point u = new Point();
Point c = new Point();
float ct;
List curveList = new ArrayList(2);
// Compute intermediate points
s.x = (p2.x + p3.x)/2;
s.y = (p2.y + p3.y)/2;
t.x = (p1.x + p2.x)/2;
t.y = (p1.y + p2.y)/2;
u.x = (p0.x + p1.x)/2;
u.y = (p0.y + p1.y)/2;
c.x = (s.x + 2*t.x + u.x)/4;
c.y = (s.y + 2*t.y + u.y)/4;
ct = (t0 + t1)/2;
// Compute first half of split (curve b)
Curve curve = new Curve(p0.x, p0.y, u.x, u.y, (t.x + u.x)/2, (t.y + u.y)/2, c.x, c.y);
curve.t0 = t0;
curve.t1 = ct;
curve.depth = depth+1;
curve.origSeg = this.origSeg;
curveList.add(curve);
// Compute second half of split (curve a)
curve = new Curve(c.x, c.y, (s.x + t.x)/2, (s.y + t.y)/2, s.x, s.y, p3.x, p3.y);
curve.t0 = ct;
curve.t1 = t1;
curve.depth = depth;
curve.origSeg = this.origSeg;
curveList.add(curve);
return curveList;
}
// Return true if curve flat enough else false.
//Adapted from the flatten.c in Type 1 rasterizer.
private boolean isFlatEnough(float unitsPerEm) {
if (depth == 6)
// Split 6 times; bbox < .002 em; don't split
return true;
if ((Math.abs(bounds.right - bounds.left) > milliem(unitsPerEm, 127)) ||
(Math.abs(bounds.top - bounds.bottom) > milliem(unitsPerEm, 127))) {
// BBox eighth em or bigger, split
depth = 0;
return false;
}
else if (((p0.x > p1.x || p1.x > p2.x || p2.x > p3.x) &&
(p3.x > p2.x || p2.x > p1.x || p1.x > p0.x)) ||
((p0.y > p1.y || p1.y > p2.y || p2.y > p3.y) &&
(p3.y > p2.y || p2.y > p1.y || p1.y > p0.y)))
// Points not monotonic in x and y; split
return false;
else
{
// Split if control points not spaced evenly within .003 em
float eps3 = milliem(unitsPerEm, 9);
float c1x = Math.abs(p1.x - p0.x);
float c3x = Math.abs(p3.x - p0.x);
if (Math.abs(c3x - 3*c1x) > eps3)
return false;
else
{
float c2x = Math.abs(p2.x - p0.x);
if (Math.abs(2*(c3x - c2x) - c2x) > eps3)
return false;
else
{
float c1y = Math.abs(p1.y - p0.y);
float c3y = Math.abs(p3.y - p0.y);
if (Math.abs(c3y - 3*c1y) > eps3)
return false;
else
{
float c2y = Math.abs(p2.y - p0.y);
if (Math.abs(2*(c3y - c2y) - c2y) > eps3)
return false;
}
}
}
}
return true;
}
// Returns a list of float t values that are the extrema
private List findExtrema(float a0, float a1, float a2, float a3) {
float t[] = new float[2];
int i = 0;
int j = 0;
float a = a3 - 3*(a2 - a1) - a0;
float b = a2 - 2*a1 + a0;
float c = a1 - a0;
// Solve dy/dx=0
if (a == 0)
{
if (b != 0)
t[i++] = -c/(2*b);
}
else
{
float r = b*b - a*c;
if (r >= 0)
{
r = (float)Math.sqrt(r);
t[i++] = (-b + r)/a;
t[i++] = (-b - r)/a;
}
}
// Select solutions in range 0 < t and t < 1
float solns[] = new float[2];
for (i--; i >= 0; i--) {
if (0 < t[i] && t[i] < 1)
solns[j++] = t[i];
}
// Sort solutions
if (j == 2 && solns[0] > solns[1])
{
solns[0] = t[1];
solns[1] = t[0];
}
List solnsList = new ArrayList();
for (i = 0; i < j; i++) {
Float d = new Float(solns[i]);
solnsList.add(d);
}
return solnsList;
}
// Find t value that yeilds the target value from Bezier equation.
private float solveAtValue(float value, float a3, float a2, float a1, float a0) {
float a = a3 - 3*(a2 - a1) - a0;
float b = 3*(a2 - 2*a1 + a0);
float c = 3*(a1 - a0);
float lo = 0;
float hi = 1;
float t;
// Binary search for solution
for (;;)
{
float delta;
t = (lo + hi)/2;
delta = t*(t*(t*a + b) + c) + a0 - value;
if (Math.abs(delta) < 0.01)
break;
else if (delta < 0)
lo = t;
else
hi = t;
}
return t;
}
// Solve segment for x value(s) at given y.
List solveAtY(float y)
{
// Find extrema
List tList = findExtrema(p3.y, p2.y, p1.y, p0.y);
// Split curve(s)
List T0CurveList, T1CurveList;
Float t0, t1;
int cnt = tList.size();
Curve[] curves = new Curve[cnt+1];
if (cnt > 0) {
t0 = (Float) tList.get(0);
T0CurveList = splitAtT(t0.floatValue());
curves[0] = (Curve) T0CurveList.get(0);
curves[1] = (Curve) T0CurveList.get(1);
if (cnt > 1) {
t1 = (Float) tList.get(1);
float t = (t1.floatValue() - t0.floatValue())/(1 - t0.floatValue());
T1CurveList = curves[1].splitAtT(t);
curves[1] = (Curve) T1CurveList.get(0);
curves[2] = (Curve) T0CurveList.get(1);
}
}
else {
//copy curve
curves[0] = new Curve(p0, p1, p2, p3);
}
List iceptList = new ArrayList();
for (int i = 0; i <= cnt; i++)
{
Point pnt0;
Point pnt1;
Point pnt2;
Point pnt3;
// Orient curve
if (curves[i].p3.y > curves[i].p0.y)
{
pnt0 = curves[i].p0;
pnt1 = curves[i].p1;
pnt2 = curves[i].p2;
pnt3 = curves[i].p3;
}
else
{
pnt0 = curves[i].p3;
pnt1 = curves[i].p2;
pnt2 = curves[i].p1;
pnt3 = curves[i].p0;
}
int wind = curves[i].getWindingY();
float icpnt;
if (y < pnt0.y || y > pnt3.y)
continue;
else if (Math2.epsilonEquals(y, pnt0.y))
icpnt = pnt0.x;
else if (Math2.epsilonEquals(y, pnt3.y))
icpnt = pnt3.x;
else {
float t = solveAtValue(y, pnt3.y, pnt2.y, pnt1.y, pnt0.y);
icpnt = t*(t*(t*(pnt3.x - 3*(pnt2.x - pnt1.x) - pnt0.x) +
3*(pnt2.x - 2*pnt1.x + pnt0.x)) +
3*(pnt1.x - pnt0.x)) + pnt0.x;
}
ICept ic = new ICept(icpnt, wind);
iceptList.add(ic);
}
return iceptList;
}
// Solve segment for y value(s) at given x.
List solveAtX(float x)
{
// Find extrema
List tList = findExtrema(p3.x, p2.x, p1.x, p0.x);
// Split curve(s)
List T0CurveList, T1CurveList;
Float t0, t1;
int cnt = tList.size();
Curve[] curves = new Curve[cnt+1];
if (cnt > 0) {
t0 = (Float) tList.get(0);
T0CurveList = splitAtT(t0.floatValue());
curves[0] = (Curve) T0CurveList.get(0);
curves[1] = (Curve) T0CurveList.get(1);
if (cnt > 1) {
t1 = (Float) tList.get(1);
float t = (t1.floatValue() - t0.floatValue())/(1 - t0.floatValue());
T1CurveList = curves[1].splitAtT(t);
curves[1] = (Curve) T1CurveList.get(0);
curves[2] = (Curve) T0CurveList.get(1);
}
}
else {
//copy curve
curves[0] = new Curve(p0, p1, p2, p3);
}
List iceptList = new ArrayList();
for (int i = 0; i <= cnt; i++)
{
Point pnt0;
Point pnt1;
Point pnt2;
Point pnt3;
// Orient curve
if (curves[i].p3.x > curves[i].p0.x)
{
pnt0 = curves[i].p0;
pnt1 = curves[i].p1;
pnt2 = curves[i].p2;
pnt3 = curves[i].p3;
}
else
{
pnt0 = curves[i].p3;
pnt1 = curves[i].p2;
pnt2 = curves[i].p1;
pnt3 = curves[i].p0;
}
int wind = curves[i].getWindingX();
float icpnt;
if (x < pnt0.x || x > pnt3.x)
continue;
else if (Math2.epsilonEquals(x, pnt0.x))
icpnt = pnt0.y;
else if (Math2.epsilonEquals(x, pnt3.x))
icpnt = pnt3.y;
else {
float t = solveAtValue(x, pnt3.x, pnt2.x, pnt1.x, pnt0.x);
icpnt = t*(t*(t*(pnt3.y - 3*(pnt2.y - pnt1.y) - pnt0.y) +
3*(pnt2.y - 2*pnt1.y + pnt0.y)) +
3*(pnt1.y - pnt0.y)) + pnt0.y;
}
ICept ic = new ICept(icpnt, wind);
iceptList.add(ic);
}
return iceptList;
}
int getWindingX() {
return getWinding(p0.x, p3.x);
}
int getWindingY() {
return getWinding(p0.y, p3.y);
}
}
private static abstract class Segment
{
SubPath subPath;
int flags;
Segment nextSegment;
Segment prevSegment;
Segment origSeg;
abstract Rect getBounds();
abstract void setBounds();
abstract Point getLastPoint();
abstract Point getFirstPoint();
abstract int getID();
abstract void roundControlPoints(float unitsPerEm);
abstract boolean horizontal();
abstract int getWindingX();
abstract int getWindingY();
abstract boolean equalControlPoints(Segment s);
abstract boolean reverseControlPoints(Segment s);
abstract Segment copySegment();
abstract List solveAtX(float val);
abstract List solveAtY(float val);
abstract public String toString();
private Segment nextSeg() {
return nextSegment;
}
private Segment prevSeg() {
return prevSegment;
}
private void link(Segment nextSeg)
{
if (nextSeg == this) {
//starting a subpath
this.nextSegment = this;
this.prevSegment = this;
} else {
nextSeg.nextSegment = this.nextSegment;
this.nextSegment.prevSegment = nextSeg;
nextSeg.prevSegment = this;
this.nextSegment = nextSeg;
}
}
private void relink(Segment nextSeg)
{
this.nextSegment = nextSeg;
nextSeg.prevSegment = this;
}
private void replace(Segment newSeg) {
newSeg.nextSegment = this.nextSegment;
newSeg.prevSegment = this.prevSegment;
}
private void remove() {
this.prevSegment.nextSegment = this.nextSegment;
this.nextSegment.prevSegment = this.prevSegment;
}
// Round points in segment to nearest integer.
private void round(float unitsPerEm)
{
roundControlPoints(unitsPerEm);
setBounds();
}
// Get winding for beg and end values.
static int getWinding(float beg, float end) {
return (beg > end)? 1: -1;
}
static int getWindingAtValue(float beg, float end, float value) {
if ((value < beg && value < end) || (value > beg && value > end))
return 0;
else
return (beg > end)? 1: -1;
}
}
private static class SegComparator implements Comparator, Serializable
{
private static final long serialVersionUID = 1L;
public int compare(Object o1, Object o2) {
Intersect i1 = (Intersect) o1;
Intersect i2 = (Intersect) o2;
return i1.cmpSegs(i2);
}
boolean equals(Object o1, Object o2) {
Intersect i1 = (Intersect) o1;
Intersect i2 = (Intersect) o2;
return (i1.cmpSegs(i2) == 0);
}
}
private static class IDComparator implements Comparator, Serializable
{
private static final long serialVersionUID = 1L;
public int compare(Object o1, Object o2) {
Intersect i1 = (Intersect) o1;
Intersect i2 = (Intersect) o2;
return i1.cmpIds(i2);
}
boolean equals(Object o1, Object o2) {
Intersect i1 = (Intersect) o1;
Intersect i2 = (Intersect) o2;
return (i1.cmpIds(i2) == 0);
}
}
// private class representing an intersection
private class Intersect {
float t; // t value for intercept on segment
Point p; // intersection point
Segment seg; // original segment
Segment splitSeg; // split segment
long id; // Pair identifier
Intersect(float t, Point p, Segment seg, Segment splitSeg, long id) {
this.p = p;
this.t = t;
this.seg = seg;
this.splitSeg = splitSeg;
this.id = id;
}
public String toString() {
String s = ("Isect point:" + p.toString() + ") t: " + t + "\n");
if (seg != null) s= s+ "Segment:" + seg.toString() +"\n";
if (splitSeg != null) s=s+"Split Segment:" + splitSeg.toString() + "\n";
return s;
}
// Compare intersections by id.
private int cmpIds(Intersect i2) {
if (id < i2.id)
return -1;
else if (id > i2.id)
return 1;
else
return 0;
}
// Compare intersections by segments
private int cmpSegs(Intersect i2) {
int iSeg=segList.indexOf(this.seg);
int i2Seg = segList.indexOf(i2.seg);
if (iSeg < i2Seg)
return -1;
else if (iSeg > i2Seg)
return 1;
else if (this.t < i2.t)
return -1;
else if (this.t > i2.t)
return 1;
else
return 0;
}
}
// private class representing a subpath
private class SubPath {
Rect bounds;
Point startPoint;
boolean pathISected;
Segment firstSegment;
Segment lastSegment;
int numSegments;
SubPath nextSubPath;
SubPath() {
bounds = new Rect(0,0,0,0);
startPoint = null;
firstSegment = null;
lastSegment = null;
numSegments = 0;
nextSubPath = null;
pathISected = false;
}
SubPath(float x, float y) {
bounds = new Rect(x,y,x,y);
startPoint = new Point(x, y);
firstSegment = null;
lastSegment = null;
numSegments = 0;
nextSubPath = null;
pathISected = false;
}
public String toString() {
String str = "Num Segments:" + numSegments;
if (numSegments == 0) return str;
Segment s = firstSegment;
do {
str = str + s.toString() + "\n";
s = s.nextSeg();
} while (s != firstSegment);
return str;
}
private int getNumSegments() {
return numSegments;
}
private void addSegment(Segment s, boolean addToSegList) {
if (addToSegList)
segList.add(s);
s.subPath = this;
if (lastSegment == null) {
firstSegment = s;
lastSegment = s;
s.link(s);
}
else {
lastSegment.link(s);
lastSegment = s;
}
bounds.grow(s.getBounds());
numSegments++;
}
private void copySubPath(SubPath sp) throws ISectException {
Segment n = sp.firstSegment;;
if (sp.firstSegment.subPath == this) return;
int totalNumSegments = segList.size();
do {
Segment newseg = n.copySegment();
n = n.nextSeg();
addSegment(newseg, true);
if (this.numSegments > totalNumSegments) {
//System.out.println("Exception: In AddSegments - inifinite loop");
throw(new ISectException("error - infinite loop"));
}
} while (n != sp.firstSegment);
}
// add segments s and all its subsequent segments
private void addSegments(Segment s) throws ISectException {
Segment n;
int totalNumSegments = segList.size();
if (s.subPath == this) return;
n = s;
do {
n = s.nextSeg();
addSegment(s, false);
if (this.numSegments > totalNumSegments) {
//System.out.println("Exception: In AddSegments - inifinite loop");
throw(new ISectException("error - infinite loop"));
}
s = n;
} while (s.subPath != this);
}
private void splitSegment(Segment seg, Segment newSeg) {
segList.add(newSeg);
newSeg.subPath = this;
seg.link(newSeg);
numSegments++;
}
private void replaceSegment(Segment seg, Segment newSeg) {
segList.add(newSeg);
newSeg.subPath = this;
seg.replace(newSeg);
}
private void removeSegment(Segment seg) throws ISectException {
if (seg.subPath == this) {
if (numSegments == 1) {
if (firstSegment == seg) {
firstSegment = null;
lastSegment = null;
} else {
//System.out.println("Exception: In removeSegment - error");
throw(new ISectException("error - cant handle"));
}
}
if (seg == firstSegment) {
firstSegment = seg.nextSeg();
}
if (seg == lastSegment) {
lastSegment = seg.prevSeg();
}
seg.remove();
numSegments--;
}
}
private void closepath(float cpx, float cpy) {
// draw a line from last point of last segment to start point
if (Math2.epsilonEquals(startPoint.x, cpx) && Math2.epsilonEquals(startPoint.y, cpy)) return;
Point lastPnt = new Point(cpx, cpy);
Line l = new Line(lastPnt, startPoint);
addSegment(l, true);
}
}
float cpx, cpy;
private SubPath currentSubPath;
private List /* SubPath */ path;
private List /* new SubPaths */ newPath;
private List /* new SubPaths */ savedPath;
private List /*Intersect */ isectList;
private List /*Segments */ segList;
private float unitsPerEm;
boolean selfIsectFlag;
OutlineConsumer oc;
public NonOverlappingOutlineConsumer (OutlineConsumer oc, double upem) {
this.oc = oc;
currentSubPath = null;
path = new ArrayList();
segList = new ArrayList();
cpx = 0;
cpy = 0;
unitsPerEm = (float) upem;
//debug params
debugNumGlyphsWithOverlaps = 0;
debugNumFailures = 0;
}
// Save intersection.
private void saveIsect(Point p, Segment segment, float t, long id)
{
Intersect newISect;
// Search for insertion position
ListIterator iSectIter = isectList.listIterator();
Point u = new Point(Math.round(p.x), Math.round(p.y));
float vx, vy;
Intersect isect = null;
if (!selfIsectFlag) {
while (iSectIter.hasNext()) {
isect = (Intersect) iSectIter.next();
SubPath iSubPath = isect.seg.subPath;
//long iPath = h->segs.array[isect->iSeg].iPath;
if (iSubPath == segment.subPath) {
vx = Math.round(isect.p.x);
if (Math2.epsilonEquals(vx, u.x)) {
vy = Math.round(isect.p.y);
if (Math2.epsilonEquals(vy, u.y)) {
return; // Matches previous intersection; ignore
}
}
}
}
}
// Insert new record
newISect = new Intersect(t, p, segment, null, id);
isectList.add(newISect);
// Mark path as intersected
segment.subPath.pathISected = true;
}
// Save intersection pair.
private void saveIsectPair(Segment seg0, float t0, Segment seg1, float t1)
{
Point p;
long id = isectList.size();
// Compute intersection point
if (t0 == 0)
p = new Point(seg0.getFirstPoint());
else if (t1 == 0)
p = new Point(seg1.getFirstPoint());
else if (t0 == 1)
p = new Point(seg0.getLastPoint());
else if (t1 == 1)
p = new Point(seg1.getLastPoint());
else if (seg0 instanceof Line) {
Line l = (Line) seg0;
p = l.computeISectPoint(t0);
}
else if (seg1 instanceof Line) {
Line l = (Line) seg1;
p = l.computeISectPoint(t1);
}else {
Curve c = (Curve) seg0;
p = c.computeISectPoint(t0);
}
// If intersection is very close to end of segment, force it to end
long x = Math.round(p.x);
long y = Math.round(p.y);
if (0 < t0 && t0 < 1) {
long segFirstpx = Math.round(seg0.getFirstPoint().x);
long segFirstpy = Math.round(seg0.getFirstPoint().y);
long segLastpx = Math.round(seg0.getLastPoint().x);
long segLastpy = Math.round(seg0.getLastPoint().y);
if ((x == segFirstpx && y == segFirstpy) ||
(x == segLastpx && y == segLastpy))
t0 = Math.round(t0);
}
if (0 < t1 && t1 < 1) {
long segFirstpx = Math.round(seg1.getFirstPoint().x);
long segFirstpy = Math.round(seg1.getFirstPoint().y);
long segLastpx = Math.round(seg1.getLastPoint().x);
long segLastpy = Math.round(seg1.getLastPoint().y);
if ((x == segFirstpx && y == segFirstpy) ||
(x == segLastpx && y == segLastpy))
t1 = Math.round(t1);
}
saveIsect(p, seg0, t0, id);
saveIsect(p, seg1, t1, id+1);
}
// Intersect a line/line segments.
private void isectLineLineSegs(Segment s0, Segment s1)
{
Line l0, l1;
if (s0 instanceof Line)
l0 = (Line) s0;
else
return;
if (s1 instanceof Line)
l1 = (Line) s1;
else
return;
List iSects = l0.iSectLine(l1);
if (iSects.size() == 2) {
float[] t = (float[]) iSects.get(1);
saveIsectPair(s0, t[0], s1, t[1]);
t = (float[]) iSects.get(0);
saveIsectPair(s0, t[0], s1, t[1]);
} else if (iSects.size() == 1) {
float[] t = (float[]) iSects.get(0);
saveIsectPair(s0, t[0], s1, t[1]);
}
}
// Intersect line/curve by recursive subdivision of curve.
private void isectLineCurveSegs(Segment s0, Segment s1) {
Line l;
Curve c;
if (s0 instanceof Line)
l = (Line) s0;
else
return;
if (s1 instanceof Curve)
c = (Curve) s1;
else
return;
for (;;) {
if (c.isFlatEnough(unitsPerEm)) {
Line newLine = c.makeLine();
List iSects = newLine.iSectLine(l);
if (iSects.size() == 2) {
float[] t = (float[]) iSects.get(1);
saveIsectPair(s1.origSeg,
c.t0 + t[0]*(c.t1 - c.t0),
s0.origSeg,
l.t0 + t[1]*(l.t1 - l.t0));
t = (float[]) iSects.get(0);
saveIsectPair(s1.origSeg,
c.t0 + t[0]*(c.t1 - c.t0),
s0.origSeg,
l.t0 + t[1]*(l.t1 - l.t0));
return;
}
else if (iSects.size() == 1) {
float[] t = (float[]) iSects.get(0);
saveIsectPair(s1.origSeg,
c.t0 + t[0]*(c.t1 - c.t0),
s0.origSeg,
l.t0 + t[1]*(l.t1 - l.t0));
return;
} else
return;
}
else if (l.iSectRect(c.getBounds())) {
// Split a into b and a
List curves = c.splitMid();
Curve b = (Curve) curves.get(0);
c = (Curve) curves.get(1);
isectLineCurveSegs(l,b);
}
else
return;
}
}
// Intersect curve/curve by alternate recursive subdivision of curves.
private void isectCurveCurve(Curve a, Curve b)
{
if (!a.getBounds().overlap(b.getBounds()))
return;
else if (a.isFlatEnough(unitsPerEm)) {
Line l = a.makeLine();
isectLineCurveSegs(l, b);
}
else
{
// Split a into c and a
List curves = a.splitMid();
Curve c = (Curve) curves.get(0); //first half
a = (Curve) curves.get(1); //second half
isectCurveCurve(b, c);
isectCurveCurve(b, a);
}
}
// Intersect a curve/curve segments.
private void isectCurveCurveSegs(Segment s0, Segment s1)
{
Curve c0, c1;
if (s0 instanceof Curve)
c0 = (Curve) s0;
else
return;
if (s1 instanceof Curve)
c1 = (Curve) s1;
else
return;
if (c0.equalControlPoints(c1))
{
// Coincident identical curves; intersect endpoints
saveIsectPair(s0, 0, s1, 0);
saveIsectPair(s0, 1, s1, 1);
}
else if (c0.reverseControlPoints(c1))
{
// Reversed coincident identical curves; intersect endpoints
saveIsectPair(s0, 0, s1, 1);
saveIsectPair(s0, 1, s1, 0);
}
else
{
// Complex intersection
isectCurveCurve(c0, c1);
}
}
// Intersect pair of segments.
private void isectSegPair(Segment s0, Segment s1)
{
s0.origSeg = s0;
s1.origSeg = s1;
if (s0 instanceof Line)
{
if (s1 instanceof Line)
isectLineLineSegs(s0, s1);
else
isectLineCurveSegs(s0, s1);
}
else
{
if (s1 instanceof Line)
isectLineCurveSegs(s1, s0);
else
isectCurveCurveSegs(s0, s1);
}
}
// Intersect pair of paths.
private void isectPathPair(SubPath p0, SubPath p1)
{
Segment s0 = p0.firstSegment;
if (s0 == null) return;
do {
Segment s1 = p1.firstSegment;
if (s1 == null) return;
do {
if (s0.getBounds().overlap(s1.getBounds()))
isectSegPair(s0, s1);
s1 = s1.nextSeg();
} while (s1 != p1.firstSegment);
s0 = s0.nextSeg();
} while (s0 != p0.firstSegment);
}
// Split segment at intersection point.
private void splitSegment(Intersect last, Intersect isect)
{
float t = isect.t;
Segment seg = isect.seg;
Segment newSeg;
if (last != null && last.seg == isect.seg)
{
// Second or subsequent intercept on same segment
if (t == last.t || t == 1) {
isect.splitSeg = last.splitSeg;
return;
}
else if (last.splitSeg != null)
{
t = (t - last.t)/(1 - last.t);
seg = last.splitSeg;
//isect.seg = seg;
}
}
else if (t == 0 || t == 1)
return;
// Split segment; allocate new segment
if (seg instanceof Line)
{
// Split line segment
Line l = (Line) seg;
Line newl = new Line(isect.p, l.p1);
l.p1.x = isect.p.x;
l.p1.y = isect.p.y;
newSeg = newl;
}
else
{
// Split curve segment
Curve c = (Curve) seg;
Point p0 = new Point(c.p0);
Point p1 = new Point(c.p1);
Point p2 = new Point(c.p2);
Point p3 = new Point(c.p3);
c.p1.x = Math.round(t*(p1.x - p0.x) + c.p0.x);
c.p1.y = Math.round(t*(p1.y - p0.y) + c.p0.y);
c.p2.x = Math.round(t*t*(p2.x - 2*p1.x + p0.x) + 2*c.p1.x - c.p0.x);
c.p2.y = Math.round(t*t*(p2.y - 2*p1.y + p0.y) + 2*c.p1.y - c.p0.y);
c.p3.x = Math.round(isect.p.x);
c.p3.y = Math.round(isect.p.y);
if (c.isLine(unitsPerEm)) {
Line l = c.makeLine();
seg.subPath.replaceSegment(seg, l);
}
t = 1 - t;
Point newp3 = new Point(p3);
Point newp2 = new Point(Math.round(t*(p2.x - p3.x) + newp3.x),
Math.round(t*(p2.y - p3.y) + newp3.y));
Point newp1 = new Point(Math.round(t*t*(p1.x - 2*p2.x + p3.x) + 2*newp2.x - newp3.x),
Math.round(t*t*(p1.y - 2*p2.y + p3.y) + 2*newp2.y - newp3.y));
Point newp0 = new Point(c.p3);
Curve newc = new Curve(newp0, newp1, newp2, newp3);
newSeg = newc;
if (newc.isLine(unitsPerEm)) {
Line l = newc.makeLine();
newSeg = l;
}
}
// Link new segment
SubPath sp = seg.subPath;
sp.splitSegment(seg, newSeg);
isect.splitSeg = newSeg;
}
// Split intersecting segments.
private void splitIsectSegs()
{
// Sort list by segment
SegComparator segCompare = new SegComparator();
Collections.sort(isectList, segCompare);
// Split segments
Intersect last = null;
Iterator iter = isectList.iterator();
while (iter.hasNext()) {
Intersect isect = (Intersect) iter.next();
splitSegment(last, isect);
last = isect;
}
// Update connection of multi-split segments
Object[] isectArray = isectList.toArray();
for (int i = isectArray.length - 1; i > 0; i--)
{
last = (Intersect) isectArray[i - 1];
Intersect isect = (Intersect) isectArray[i];
if (isect.splitSeg != null &&
last != null && last.seg == isect.seg && last.splitSeg != null)
{
isect.seg = last.splitSeg;
isect.splitSeg =isect.seg.nextSeg();
}
}
// Round split segments and update unsplit segment connections
iter = isectList.iterator();
while (iter.hasNext()) {
Intersect isect = (Intersect) iter.next();
Segment seg = isect.seg;
if (isect.splitSeg == null) {
isect.splitSeg = (isect.t == 0)? seg.prevSeg(): seg.nextSeg();
}
else
{
seg.round(unitsPerEm);
isect.splitSeg.round(unitsPerEm);
}
// Mark segments as intersected
seg.flags |= SEG_ISECT;
isect.splitSeg.flags |= SEG_ISECT;
}
}
// Test segment winding and if non-zero delete it.
private void windTestSeg(Segment target)
{
Point p = new Point();
if ((target.flags & (SEG_WIND_TEST|SEG_DELETE)) != 0)
return; // Ignore checked or deleted segments
// Choose target point in middle of this segment
if (target instanceof Line)
{
Line l = (Line) target;
p.x = (l.p0.x + l.p1.x)/2;
p.y = (l.p0.y + l.p1.y)/2;
}
else
{
Curve c = (Curve) target;
p.x = (c.p3.x + 3*(c.p2.x + c.p1.x) + c.p0.x)/8;
p.y = (c.p3.y + 3*(c.p2.y + c.p1.y) + c.p0.y)/8;
}
int windtotal = 0;
int windtarget;
boolean testvert = target.horizontal();
if (testvert)
windtarget = target.getWindingX();
else
windtarget = target.getWindingY();
SubPath sp = target.subPath;
do
{
// Ignore paths that can't affect winding
if (testvert)
{
if (sp.bounds.top < p.y ||
sp.bounds.left > p.x ||
sp.bounds.right <= p.x) {
sp = sp.nextSubPath;
continue;
}
}
else
{
if (sp.bounds.left > p.x ||
sp.bounds.bottom > p.y ||
sp.bounds.top <= p.y) {
sp = sp.nextSubPath;
continue;
}
}
Segment seg = sp.firstSegment;
do
{
if (seg == target) {
;
} else if (testvert)
{
/* Consider only non-horizontal segments that are to the left
of the target point, and cross, or touch from above, the
horizontal line through the target point. */
Rect segBounds = seg.getBounds();
if (segBounds.top < p.y ||
segBounds.left > p.x ||
segBounds.right <= p.x ||
segBounds.left == segBounds.right) {
;
}else if (segBounds.bottom > p.y) {
windtotal += Segment.getWindingAtValue(seg.getFirstPoint().x, seg.getLastPoint().x, p.x);
} else if (seg.reverseControlPoints(target))
{
// Coincident in opposite direction
target.flags |= (SEG_DELETE|SEG_WIND_TEST);
return;
}
else if (seg.equalControlPoints(target))
{
// Coincident in same direction
if (path.indexOf(seg.subPath) < path.indexOf(target.subPath)) //is this the right things to do?
target.flags |= SEG_DELETE;
target.flags |= SEG_WIND_TEST;
return;
}
else
{
List iceptList = seg.solveAtX(p.x);
Iterator iter = iceptList.iterator();
while (iter.hasNext()) {
ICept icept = (ICept) iter.next();
if (icept.ic > p.y) {
windtotal += icept.winding;
}
}
}
}
else
{
/* Consider only non-vertical segments that are to the left
of the target point, and cross, or touch from above, the
horizontal line through the target point. */
Rect segBounds = seg.getBounds();
if (segBounds.left > p.x ||
segBounds.bottom > p.y ||
segBounds.top <= p.y ||
segBounds.top == segBounds.bottom) {
;
} else if (segBounds.right < p.x) {
windtotal += Segment.getWindingAtValue(seg.getFirstPoint().y, seg.getLastPoint().y, p.y);
} else if (seg.reverseControlPoints(target))
{
// Coincident in opposite direction
target.flags |= (SEG_DELETE|SEG_WIND_TEST);
return;
}
else if (seg.equalControlPoints(target))
{
// Coincident in same direction
if (path.indexOf(seg.subPath) < path.indexOf(target.subPath))
target.flags |= SEG_DELETE;
target.flags |= SEG_WIND_TEST;
return;
}
else
{
List iceptList = seg.solveAtY(p.y);
Iterator iter = iceptList.iterator();
while (iter.hasNext()) {
ICept icept = (ICept) iter.next();
if (icept.ic < p.x) {
windtotal += icept.winding;
}
}
}
}
seg = seg.nextSeg();
} while (seg != sp.firstSegment);
sp = sp.nextSubPath;
} while (sp != target.subPath);
if ((windtotal != 0) && (windtotal + windtarget)!= 0)
target.flags |= SEG_DELETE;
target.flags |= SEG_WIND_TEST;
}
// Delete overlapped segments or those with bad winding.
private void deleteBadSegs() throws ISectException
{
if ((isectList.size() % 2) != 0) { //has to be even
//System.out.println("Exception: In deleteBadSegs - error - uneven intersects");
throw(new ISectException("error - uneven intersects"));
}
// Sort list by id
IDComparator idCompare = new IDComparator();
Collections.sort(isectList, idCompare);
// Delete badly wound segments from each intersection
Iterator iter = isectList.iterator();
while (iter.hasNext()) {
Intersect isect = (Intersect) iter.next();
windTestSeg(isect.seg);
windTestSeg(isect.splitSeg);
}
// Link non-deleted segments from each intersection
iter = isectList.iterator();
while (iter.hasNext()) {
Segment abeg, aend, bbeg, bend;
Intersect a = (Intersect) iter.next();
Intersect b = (Intersect) iter.next();
// Order intersection path "a" from beg to end
if (a.seg.nextSeg() == a.splitSeg)
{
abeg = a.seg;
aend = a.splitSeg;
}
else
{
abeg = a.splitSeg;
aend = a.seg;
}
/* Order intersection path "b" from beg to end */
if (b.seg.nextSeg() == b.splitSeg)
{
bbeg = b.seg;
bend = b.splitSeg;
}
else
{
bbeg = b.splitSeg;
bend = b.seg;
}
/* Link overlapping paths. There are 4 segments on 2 paths surrounding
an intersection. Some, or all, of these segments may have been
deleted because of bad winding. This switch determines how to link
the remaining undeleted segments. Apart from cases 0, 3, 12, and 15
which require no action the only other senisible cases are 6 and 9.
However, the remaining cases may be encountered if the winding test
fails due the numeric instability, which although rare, must be
handled. Fortunately, it is possible to determine where the failure
occured and fix it, yeilding a correct result. */
switch ((abeg.flags & SEG_DELETE)<<3 |
(aend.flags & SEG_DELETE)<<2 |
(bbeg.flags & SEG_DELETE)<<1 |
(bend.flags & SEG_DELETE)<<0) {
case 0:
case 3:
case 12:
case 15:
// Various cases requiring no action
break;
case 5:
case 10:
// These cases shouldn't happen; give up
//System.out.println("Exception: illegal segment flags");
throw(new ISectException("error - illegal segment flags"));
case 1:
abeg.flags |= SEG_DELETE;
bbeg.relink(aend);
break;
case 2:
aend.flags |= SEG_DELETE;
abeg.relink(bend);
break;
case 4:
bbeg.flags |= SEG_DELETE;
abeg.relink(bend);
break;
case 6:
abeg.relink(bend);
break;
case 7:
if (abeg.getLastPoint().equals(bend.getFirstPoint())) {
bend.flags &= ~SEG_DELETE;
abeg.relink(bend);
}
break;
case 8:
bend.flags |= SEG_DELETE;
bbeg.relink(aend);
break;
case 9:
bbeg.relink(aend);
break;
case 11:
if (bbeg.getLastPoint().equals(aend.getFirstPoint())) {
bbeg.flags &= ~SEG_DELETE;
bbeg.relink(aend);
}
break;
case 13:
if (bbeg.getLastPoint().equals(aend.getFirstPoint())) {
aend.flags &= ~SEG_DELETE;
bbeg.relink(aend);
}
break;
case 14:
if (abeg.getLastPoint().equals(bend.getFirstPoint())) {
abeg.flags &= ~SEG_DELETE;
abeg.relink(bend);
}
break;
}
}
}
/* Create new path starting from seg. Path bounds and links must be set by
caller. Returns pointer to new sub path. */
private SubPath newSubPath(Segment seg) throws ISectException
{
SubPath sp = new SubPath();
sp.addSegments(seg);
return sp;
}
// Build new sub path from intersection segment.
private void buildSubPath(Segment seg) throws ISectException
{
if (((seg.flags & SEG_DELETE) != 0) || // Segment deleted
newPath.contains(seg.subPath)) // Segment already added to new path
return;
SubPath subPath = newSubPath(seg);
addPath(newPath, subPath);
}
// Build new path list.
private void buildNewPaths() throws ISectException
{
newPath = new ArrayList();
// Copy non-intersected paths to new list
Iterator iter = path.iterator();
while (iter.hasNext()) {
SubPath sp = (SubPath) (iter.next());
if (!(sp.pathISected)) {
addPath(newPath, sp);
}
}
// Build new paths from intersection data
iter = isectList.iterator();
while (iter.hasNext()) {
Intersect isect = (Intersect) iter.next();
buildSubPath(isect.seg);
buildSubPath(isect.splitSeg);
}
// Use original path start points with new paths if possible
iter = path.iterator();
while (iter.hasNext()) {
SubPath sp = (SubPath) (iter.next());
if ((sp.pathISected)) {
Segment seg = sp.firstSegment;
SubPath segsp = seg.subPath;
if (newPath.contains(segsp) && (seg.flags & SEG_DELETE) != 1) {
segsp.firstSegment = seg;
segsp.lastSegment = seg.prevSegment;
}
}
}
// Coalesce co-linear line segments
iter = newPath.iterator();
while (iter.hasNext()) {
SubPath sp = (SubPath) iter.next();
int lastdir = 0;
int segcnt = 0;
Segment seg = sp.firstSegment;
do {
int thisdir = 0;
if (seg instanceof Line) {
Line l = (Line) seg;
if (Math2.epsilonEquals(l.p0.x, l.p1.x))
thisdir = 1;
else if (Math2.epsilonEquals(l.p0.y, l.p1.y))
thisdir = -1;
else {
if (++segcnt > segList.size()) {
//System.out.println("Exception: In buildNewPath - segcnt > segList.size() detected infinite loop condition");
// Infinite loop!
throw(new ISectException("Infinite loop condition!"));
}
lastdir = thisdir;
seg = seg.nextSeg();
continue;
}
if (seg != sp.firstSegment && thisdir == lastdir)
{
// Remove this segment from path
Point p = new Point(l.p1);
Segment lastSeg = seg.prevSegment;
sp.removeSegment(seg);
if (lastSeg instanceof Line) {
Line ll = (Line) lastSeg;
ll.p1 = p;
ll.setBounds();
} else {
Curve c = (Curve) lastSeg;
c.p3 = p;
c.setBounds();
}
}
if (++segcnt > segList.size()) {
//System.out.println("Exception: In buildNewPath - detected infinite loop condition");
// Infinite loop!
throw(new ISectException("Infinite loop condition!"));
}
}
lastdir = thisdir;
seg = seg.nextSeg();
} while (seg != sp.firstSegment);
}
}
// Self-intersect path segments
private void selfIsectPath(SubPath subPath) throws ISectException {
if (subPath.getNumSegments() > MAX_NUM_SEGMENTS) {
//System.out.println("Exception: In selfISectPath - too many segments");
throw(new ISectException("error - too many segments in subpath"));
}
if (subPath.getNumSegments() < 3) return;
for (Segment s0 = subPath.firstSegment; s0.nextSeg() != subPath.lastSegment; s0 = s0.nextSeg())
{
Segment s1 = s0.nextSeg();
do {
s1 = s1.nextSeg();
if (s1.nextSeg() != s0 && s0.getBounds().overlap(s1.getBounds()))
isectSegPair(s0, s1);
} while (s1 != subPath.lastSegment);
}
}
// intersect glyph paths
private void isectPath() throws ISectException {
List subPathList = path;
if (subPathList == null) return;
if (subPathList.size() > MAX_NUM_SUBPATHS) {
//System.out.println("Exception: In isectPath - too many subpaths");
throw(new ISectException("error - too many subpaths"));
}
isectList = new ArrayList();
//look for self-intersecting paths
Iterator subPathListIter = subPathList.iterator();
selfIsectFlag = true;
while (subPathListIter.hasNext()) {
SubPath subPath = (SubPath) subPathListIter.next();
selfIsectPath(subPath);
}
//look for intersections between paths
selfIsectFlag = false;
Object[] subPathArray = subPathList.toArray();
for (int i = 0; i < subPathArray.length; i++) {
SubPath subPathi = (SubPath) subPathArray[i];
for (int j = i+1; j < subPathArray.length; j++) {
SubPath subPathj = (SubPath) subPathArray[j];
if (subPathj.bounds.overlap(subPathi.bounds))
isectPathPair(subPathi, subPathj);
}
}
if (isectList.size() > 0) {
// paths contained intersecting paths
savedPath = copyPath(path);
splitIsectSegs();
deleteBadSegs();
buildNewPaths();
debugNumGlyphsWithOverlaps++;
} else
newPath = path;
//debugPrint("number of intersecting glyphs:" + debugNumGlyphsWithOverlaps);
}
private void addPath(List pathList, SubPath subPath) {
int size = pathList.size();
if (size > 0) {
SubPath lastsp = (SubPath) pathList.get(size - 1);
subPath.nextSubPath = lastsp.nextSubPath;
lastsp.nextSubPath = subPath;
}
else
{
//add as first element of path - keep as circular list
subPath.nextSubPath = subPath;
}
pathList.add(subPath);
}
private List copyPath(List pathList) throws ISectException {
List copiedPathList = new ArrayList();
Iterator pathIter = pathList.iterator();
while (pathIter.hasNext()) {
SubPath sp = (SubPath) pathIter.next();
SubPath newsp = new SubPath();
newsp.copySubPath(sp);
copiedPathList.add(newsp);
}
return copiedPathList;
}
private void generateOutline(List path) {
if (path == null) return;
Iterator pathListIter = path.iterator();
if (savedPath != null) {
boolean foundLoopInPath = false;
int maxNumSegments = segList.size();
while (pathListIter.hasNext() && !foundLoopInPath){
SubPath subPath = (SubPath) pathListIter.next();
if (subPath == null) continue;
Segment pathSegment = subPath.firstSegment;
if (pathSegment == null) return;
int i = 0;
do {
i++;
if (i > maxNumSegments) {
path = savedPath;
foundLoopInPath = true;
}
pathSegment = pathSegment.nextSeg();
} while ((pathSegment != subPath.firstSegment) && (!foundLoopInPath));
}
}
pathListIter = path.iterator();
while (pathListIter.hasNext()) {
SubPath subPath = (SubPath) pathListIter.next();
if (subPath == null) continue;
Segment pathSegment = subPath.firstSegment;
if (pathSegment == null) return;
boolean move = true;
do {
if (pathSegment instanceof Line) {
Line l = (Line) pathSegment;
if (move) {
oc.moveto(l.p0.x, l.p0.y);
move = false;
}
if (pathSegment != subPath.lastSegment) {//ignore last one; it was added to close the path
oc.lineto(l.p1.x, l.p1.y);
}
} else if (pathSegment instanceof Curve) {
Curve c = (Curve) pathSegment;
if (move) {
oc.moveto(c.p0.x, c.p0.y);
move = false;
}
oc.curveto(c.p1.x, c.p1.y, c.p2.x, c.p2.y, c.p3.x, c.p3.y);
}
pathSegment = pathSegment.nextSeg();
} while (pathSegment != subPath.firstSegment);
}
}
public void setMatrix(Matrix newMatrix) {
oc.setMatrix(newMatrix);
}
public void moveto (double x, double y) {
if ((currentSubPath != null) && (currentSubPath.getNumSegments() > 0)) {
currentSubPath.closepath(cpx, cpy);
addPath(path, currentSubPath);
}
currentSubPath = new SubPath((float)x, (float)y);
cpx = (float)x;
cpy = (float)y;
}
public void lineto (double x, double y) {
if ((x == cpx) && (y == cpy)) return; // ignore 0 length lines
if (currentSubPath == null)
moveto(0,0);
Line l = new Line(cpx, cpy, (float)x,(float)y);
currentSubPath.addSegment(l, true);
cpx = (float) x;
cpy = (float) y;
}
public void curveto (double x1, double y1, double x2, double y2) {
curveto (Math.round ((cpx + 2*x1)/3.0), Math.round ((cpy + 2*y1)/3.0),
Math.round ((2*x1 + x2)/3.0), Math.round ((2*y1 + y2)/3.0),
Math.round(x2), Math.round(y2));
}
public void curveto (double x2, double y2, double x3, double y3, double x4, double y4) {
if (currentSubPath == null)
moveto(0,0);
Curve c = new Curve(cpx, cpy, (float)x2, (float)y2, (float)x3, (float)y3, (float)x4,(float) y4);
currentSubPath.addSegment(c, true);
cpx = (float)x4;
cpy = (float)y4;
}
public void reset()
{
currentSubPath = null;
path.clear();
savedPath = null;
newPath = null;
isectList = null;
segList.clear();
cpx = 0;
cpy = 0;
}
public void endchar()
{
if ((currentSubPath != null) && (currentSubPath.getNumSegments() > 0)) {
currentSubPath.closepath(cpx, cpy);
addPath(path, currentSubPath);
}
try {
isectPath();
} catch (ISectException e) {
debugNumFailures++;
newPath = savedPath;
//debugPrint("number of failures:" + debugNumFailures);
}
generateOutline(newPath);
oc.endchar();
reset();
}
//debug parameters
int debugNumGlyphsWithOverlaps;
int debugNumFailures;
public int getNumErrors() {
return debugNumFailures;
}
/*
* DEBUGGING SUPPORT
*/
/*
private static boolean DEBUG = true;
private void debugPrint(String str) {
if (DEBUG)
System.out.println(str);
}
private void debugPrintPath(List path) {
if (!DEBUG) return;
System.out.println("Path - number of subpaths:" + path.size());
Iterator pathIter = path.iterator();
while (pathIter.hasNext()) {
SubPath subPath = (SubPath) pathIter.next();
System.out.println("Subpath segment nums:" + subPath.getNumSegments());
System.out.println(subPath.toString());
System.out.println("--------");
}
System.out.println("------------------------------------");
}
private void debugPrintISect() {
if (!DEBUG) return;
System.out.println("Number of ISects:" + isectList.size());
Iterator iter = isectList.iterator();
while (iter.hasNext()) {
Intersect isect = (Intersect) iter.next();
System.out.println(isect.toString());
}
System.out.println("---------------");
}
*/
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy