562 lines
21 KiB
Java
562 lines
21 KiB
Java
/*
|
|
* Copyright (c) 1998, 2003, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code 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
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.awt.geom;
|
|
|
|
import java.util.Vector;
|
|
import java.util.Enumeration;
|
|
import java.util.Comparator;
|
|
import java.util.Arrays;
|
|
|
|
public abstract class AreaOp {
|
|
public static abstract class CAGOp extends AreaOp {
|
|
boolean inLeft;
|
|
boolean inRight;
|
|
boolean inResult;
|
|
|
|
public void newRow() {
|
|
inLeft = false;
|
|
inRight = false;
|
|
inResult = false;
|
|
}
|
|
|
|
public int classify(Edge e) {
|
|
if (e.getCurveTag() == CTAG_LEFT) {
|
|
inLeft = !inLeft;
|
|
} else {
|
|
inRight = !inRight;
|
|
}
|
|
boolean newClass = newClassification(inLeft, inRight);
|
|
if (inResult == newClass) {
|
|
return ETAG_IGNORE;
|
|
}
|
|
inResult = newClass;
|
|
return (newClass ? ETAG_ENTER : ETAG_EXIT);
|
|
}
|
|
|
|
public int getState() {
|
|
return (inResult ? RSTAG_INSIDE : RSTAG_OUTSIDE);
|
|
}
|
|
|
|
public abstract boolean newClassification(boolean inLeft,
|
|
boolean inRight);
|
|
}
|
|
|
|
public static class AddOp extends CAGOp {
|
|
public boolean newClassification(boolean inLeft, boolean inRight) {
|
|
return (inLeft || inRight);
|
|
}
|
|
}
|
|
|
|
public static class SubOp extends CAGOp {
|
|
public boolean newClassification(boolean inLeft, boolean inRight) {
|
|
return (inLeft && !inRight);
|
|
}
|
|
}
|
|
|
|
public static class IntOp extends CAGOp {
|
|
public boolean newClassification(boolean inLeft, boolean inRight) {
|
|
return (inLeft && inRight);
|
|
}
|
|
}
|
|
|
|
public static class XorOp extends CAGOp {
|
|
public boolean newClassification(boolean inLeft, boolean inRight) {
|
|
return (inLeft != inRight);
|
|
}
|
|
}
|
|
|
|
public static class NZWindOp extends AreaOp {
|
|
private int count;
|
|
|
|
public void newRow() {
|
|
count = 0;
|
|
}
|
|
|
|
public int classify(Edge e) {
|
|
// Note: the right curves should be an empty set with this op...
|
|
// assert(e.getCurveTag() == CTAG_LEFT);
|
|
int newCount = count;
|
|
int type = (newCount == 0 ? ETAG_ENTER : ETAG_IGNORE);
|
|
newCount += e.getCurve().getDirection();
|
|
count = newCount;
|
|
return (newCount == 0 ? ETAG_EXIT : type);
|
|
}
|
|
|
|
public int getState() {
|
|
return ((count == 0) ? RSTAG_OUTSIDE : RSTAG_INSIDE);
|
|
}
|
|
}
|
|
|
|
public static class EOWindOp extends AreaOp {
|
|
private boolean inside;
|
|
|
|
public void newRow() {
|
|
inside = false;
|
|
}
|
|
|
|
public int classify(Edge e) {
|
|
// Note: the right curves should be an empty set with this op...
|
|
// assert(e.getCurveTag() == CTAG_LEFT);
|
|
boolean newInside = !inside;
|
|
inside = newInside;
|
|
return (newInside ? ETAG_ENTER : ETAG_EXIT);
|
|
}
|
|
|
|
public int getState() {
|
|
return (inside ? RSTAG_INSIDE : RSTAG_OUTSIDE);
|
|
}
|
|
}
|
|
|
|
private AreaOp() {
|
|
}
|
|
|
|
/* Constants to tag the left and right curves in the edge list */
|
|
public static final int CTAG_LEFT = 0;
|
|
public static final int CTAG_RIGHT = 1;
|
|
|
|
/* Constants to classify edges */
|
|
public static final int ETAG_IGNORE = 0;
|
|
public static final int ETAG_ENTER = 1;
|
|
public static final int ETAG_EXIT = -1;
|
|
|
|
/* Constants used to classify result state */
|
|
public static final int RSTAG_INSIDE = 1;
|
|
public static final int RSTAG_OUTSIDE = -1;
|
|
|
|
public abstract void newRow();
|
|
|
|
public abstract int classify(Edge e);
|
|
|
|
public abstract int getState();
|
|
|
|
public Vector calculate(Vector left, Vector right) {
|
|
Vector edges = new Vector();
|
|
addEdges(edges, left, AreaOp.CTAG_LEFT);
|
|
addEdges(edges, right, AreaOp.CTAG_RIGHT);
|
|
edges = pruneEdges(edges);
|
|
if (false) {
|
|
System.out.println("result: ");
|
|
int numcurves = edges.size();
|
|
Curve[] curvelist = (Curve[]) edges.toArray(new Curve[numcurves]);
|
|
for (int i = 0; i < numcurves; i++) {
|
|
System.out.println("curvelist["+i+"] = "+curvelist[i]);
|
|
}
|
|
}
|
|
return edges;
|
|
}
|
|
|
|
private static void addEdges(Vector edges, Vector curves, int curvetag) {
|
|
Enumeration enum_ = curves.elements();
|
|
while (enum_.hasMoreElements()) {
|
|
Curve c = (Curve) enum_.nextElement();
|
|
if (c.getOrder() > 0) {
|
|
edges.add(new Edge(c, curvetag));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Comparator YXTopComparator = new Comparator() {
|
|
public int compare(Object o1, Object o2) {
|
|
Curve c1 = ((Edge) o1).getCurve();
|
|
Curve c2 = ((Edge) o2).getCurve();
|
|
double v1, v2;
|
|
if ((v1 = c1.getYTop()) == (v2 = c2.getYTop())) {
|
|
if ((v1 = c1.getXTop()) == (v2 = c2.getXTop())) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (v1 < v2) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
private Vector pruneEdges(Vector edges) {
|
|
int numedges = edges.size();
|
|
if (numedges < 2) {
|
|
return edges;
|
|
}
|
|
Edge[] edgelist = (Edge[]) edges.toArray(new Edge[numedges]);
|
|
Arrays.sort(edgelist, YXTopComparator);
|
|
if (false) {
|
|
System.out.println("pruning: ");
|
|
for (int i = 0; i < numedges; i++) {
|
|
System.out.println("edgelist["+i+"] = "+edgelist[i]);
|
|
}
|
|
}
|
|
Edge e;
|
|
int left = 0;
|
|
int right = 0;
|
|
int cur = 0;
|
|
int next = 0;
|
|
double yrange[] = new double[2];
|
|
Vector subcurves = new Vector();
|
|
Vector chains = new Vector();
|
|
Vector links = new Vector();
|
|
// Active edges are between left (inclusive) and right (exclusive)
|
|
while (left < numedges) {
|
|
double y = yrange[0];
|
|
// Prune active edges that fall off the top of the active y range
|
|
for (cur = next = right - 1; cur >= left; cur--) {
|
|
e = edgelist[cur];
|
|
if (e.getCurve().getYBot() > y) {
|
|
if (next > cur) {
|
|
edgelist[next] = e;
|
|
}
|
|
next--;
|
|
}
|
|
}
|
|
left = next + 1;
|
|
// Grab a new "top of Y range" if the active edges are empty
|
|
if (left >= right) {
|
|
if (right >= numedges) {
|
|
break;
|
|
}
|
|
y = edgelist[right].getCurve().getYTop();
|
|
if (y > yrange[0]) {
|
|
finalizeSubCurves(subcurves, chains);
|
|
}
|
|
yrange[0] = y;
|
|
}
|
|
// Incorporate new active edges that enter the active y range
|
|
while (right < numedges) {
|
|
e = edgelist[right];
|
|
if (e.getCurve().getYTop() > y) {
|
|
break;
|
|
}
|
|
right++;
|
|
}
|
|
// Sort the current active edges by their X values and
|
|
// determine the maximum valid Y range where the X ordering
|
|
// is correct
|
|
yrange[1] = edgelist[left].getCurve().getYBot();
|
|
if (right < numedges) {
|
|
y = edgelist[right].getCurve().getYTop();
|
|
if (yrange[1] > y) {
|
|
yrange[1] = y;
|
|
}
|
|
}
|
|
if (false) {
|
|
System.out.println("current line: y = ["+
|
|
yrange[0]+", "+yrange[1]+"]");
|
|
for (cur = left; cur < right; cur++) {
|
|
System.out.println(" "+edgelist[cur]);
|
|
}
|
|
}
|
|
// Note: We could start at left+1, but we need to make
|
|
// sure that edgelist[left] has its equivalence set to 0.
|
|
int nexteq = 1;
|
|
for (cur = left; cur < right; cur++) {
|
|
e = edgelist[cur];
|
|
e.setEquivalence(0);
|
|
for (next = cur; next > left; next--) {
|
|
Edge prevedge = edgelist[next-1];
|
|
int ordering = e.compareTo(prevedge, yrange);
|
|
if (yrange[1] <= yrange[0]) {
|
|
throw new InternalError("backstepping to "+yrange[1]+
|
|
" from "+yrange[0]);
|
|
}
|
|
if (ordering >= 0) {
|
|
if (ordering == 0) {
|
|
// If the curves are equal, mark them to be
|
|
// deleted later if they cancel each other
|
|
// out so that we avoid having extraneous
|
|
// curve segments.
|
|
int eq = prevedge.getEquivalence();
|
|
if (eq == 0) {
|
|
eq = nexteq++;
|
|
prevedge.setEquivalence(eq);
|
|
}
|
|
e.setEquivalence(eq);
|
|
}
|
|
break;
|
|
}
|
|
edgelist[next] = prevedge;
|
|
}
|
|
edgelist[next] = e;
|
|
}
|
|
if (false) {
|
|
System.out.println("current sorted line: y = ["+
|
|
yrange[0]+", "+yrange[1]+"]");
|
|
for (cur = left; cur < right; cur++) {
|
|
System.out.println(" "+edgelist[cur]);
|
|
}
|
|
}
|
|
// Now prune the active edge list.
|
|
// For each edge in the list, determine its classification
|
|
// (entering shape, exiting shape, ignore - no change) and
|
|
// record the current Y range and its classification in the
|
|
// Edge object for use later in constructing the new outline.
|
|
newRow();
|
|
double ystart = yrange[0];
|
|
double yend = yrange[1];
|
|
for (cur = left; cur < right; cur++) {
|
|
e = edgelist[cur];
|
|
int etag;
|
|
int eq = e.getEquivalence();
|
|
if (eq != 0) {
|
|
// Find one of the segments in the "equal" range
|
|
// with the right transition state and prefer an
|
|
// edge that was either active up until ystart
|
|
// or the edge that extends the furthest downward
|
|
// (i.e. has the most potential for continuation)
|
|
int origstate = getState();
|
|
etag = (origstate == AreaOp.RSTAG_INSIDE
|
|
? AreaOp.ETAG_EXIT
|
|
: AreaOp.ETAG_ENTER);
|
|
Edge activematch = null;
|
|
Edge longestmatch = e;
|
|
double furthesty = yend;
|
|
do {
|
|
// Note: classify() must be called
|
|
// on every edge we consume here.
|
|
classify(e);
|
|
if (activematch == null &&
|
|
e.isActiveFor(ystart, etag))
|
|
{
|
|
activematch = e;
|
|
}
|
|
y = e.getCurve().getYBot();
|
|
if (y > furthesty) {
|
|
longestmatch = e;
|
|
furthesty = y;
|
|
}
|
|
} while (++cur < right &&
|
|
(e = edgelist[cur]).getEquivalence() == eq);
|
|
--cur;
|
|
if (getState() == origstate) {
|
|
etag = AreaOp.ETAG_IGNORE;
|
|
} else {
|
|
e = (activematch != null ? activematch : longestmatch);
|
|
}
|
|
} else {
|
|
etag = classify(e);
|
|
}
|
|
if (etag != AreaOp.ETAG_IGNORE) {
|
|
e.record(yend, etag);
|
|
links.add(new CurveLink(e.getCurve(), ystart, yend, etag));
|
|
}
|
|
}
|
|
// assert(getState() == AreaOp.RSTAG_OUTSIDE);
|
|
if (getState() != AreaOp.RSTAG_OUTSIDE) {
|
|
System.out.println("Still inside at end of active edge list!");
|
|
System.out.println("num curves = "+(right-left));
|
|
System.out.println("num links = "+links.size());
|
|
System.out.println("y top = "+yrange[0]);
|
|
if (right < numedges) {
|
|
System.out.println("y top of next curve = "+
|
|
edgelist[right].getCurve().getYTop());
|
|
} else {
|
|
System.out.println("no more curves");
|
|
}
|
|
for (cur = left; cur < right; cur++) {
|
|
e = edgelist[cur];
|
|
System.out.println(e);
|
|
int eq = e.getEquivalence();
|
|
if (eq != 0) {
|
|
System.out.println(" was equal to "+eq+"...");
|
|
}
|
|
}
|
|
}
|
|
if (false) {
|
|
System.out.println("new links:");
|
|
for (int i = 0; i < links.size(); i++) {
|
|
CurveLink link = (CurveLink) links.elementAt(i);
|
|
System.out.println(" "+link.getSubCurve());
|
|
}
|
|
}
|
|
resolveLinks(subcurves, chains, links);
|
|
links.clear();
|
|
// Finally capture the bottom of the valid Y range as the top
|
|
// of the next Y range.
|
|
yrange[0] = yend;
|
|
}
|
|
finalizeSubCurves(subcurves, chains);
|
|
Vector ret = new Vector();
|
|
Enumeration enum_ = subcurves.elements();
|
|
while (enum_.hasMoreElements()) {
|
|
CurveLink link = (CurveLink) enum_.nextElement();
|
|
ret.add(link.getMoveto());
|
|
CurveLink nextlink = link;
|
|
while ((nextlink = nextlink.getNext()) != null) {
|
|
if (!link.absorb(nextlink)) {
|
|
ret.add(link.getSubCurve());
|
|
link = nextlink;
|
|
}
|
|
}
|
|
ret.add(link.getSubCurve());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public static void finalizeSubCurves(Vector subcurves, Vector chains) {
|
|
int numchains = chains.size();
|
|
if (numchains == 0) {
|
|
return;
|
|
}
|
|
if ((numchains & 1) != 0) {
|
|
throw new InternalError("Odd number of chains!");
|
|
}
|
|
ChainEnd[] endlist = new ChainEnd[numchains];
|
|
chains.toArray(endlist);
|
|
for (int i = 1; i < numchains; i += 2) {
|
|
ChainEnd open = endlist[i - 1];
|
|
ChainEnd close = endlist[i];
|
|
CurveLink subcurve = open.linkTo(close);
|
|
if (subcurve != null) {
|
|
subcurves.add(subcurve);
|
|
}
|
|
}
|
|
chains.clear();
|
|
}
|
|
|
|
private static CurveLink[] EmptyLinkList = new CurveLink[2];
|
|
private static ChainEnd[] EmptyChainList = new ChainEnd[2];
|
|
|
|
public static void resolveLinks(Vector subcurves,
|
|
Vector chains,
|
|
Vector links)
|
|
{
|
|
int numlinks = links.size();
|
|
CurveLink[] linklist;
|
|
if (numlinks == 0) {
|
|
linklist = EmptyLinkList;
|
|
} else {
|
|
if ((numlinks & 1) != 0) {
|
|
throw new InternalError("Odd number of new curves!");
|
|
}
|
|
linklist = new CurveLink[numlinks+2];
|
|
links.toArray(linklist);
|
|
}
|
|
int numchains = chains.size();
|
|
ChainEnd[] endlist;
|
|
if (numchains == 0) {
|
|
endlist = EmptyChainList;
|
|
} else {
|
|
if ((numchains & 1) != 0) {
|
|
throw new InternalError("Odd number of chains!");
|
|
}
|
|
endlist = new ChainEnd[numchains+2];
|
|
chains.toArray(endlist);
|
|
}
|
|
int curchain = 0;
|
|
int curlink = 0;
|
|
chains.clear();
|
|
ChainEnd chain = endlist[0];
|
|
ChainEnd nextchain = endlist[1];
|
|
CurveLink link = linklist[0];
|
|
CurveLink nextlink = linklist[1];
|
|
while (chain != null || link != null) {
|
|
/*
|
|
* Strategy 1:
|
|
* Connect chains or links if they are the only things left...
|
|
*/
|
|
boolean connectchains = (link == null);
|
|
boolean connectlinks = (chain == null);
|
|
|
|
if (!connectchains && !connectlinks) {
|
|
// assert(link != null && chain != null);
|
|
/*
|
|
* Strategy 2:
|
|
* Connect chains or links if they close off an open area...
|
|
*/
|
|
connectchains = ((curchain & 1) == 0 &&
|
|
chain.getX() == nextchain.getX());
|
|
connectlinks = ((curlink & 1) == 0 &&
|
|
link.getX() == nextlink.getX());
|
|
|
|
if (!connectchains && !connectlinks) {
|
|
/*
|
|
* Strategy 3:
|
|
* Connect chains or links if their successor is
|
|
* between them and their potential connectee...
|
|
*/
|
|
double cx = chain.getX();
|
|
double lx = link.getX();
|
|
connectchains =
|
|
(nextchain != null && cx < lx &&
|
|
obstructs(nextchain.getX(), lx, curchain));
|
|
connectlinks =
|
|
(nextlink != null && lx < cx &&
|
|
obstructs(nextlink.getX(), cx, curlink));
|
|
}
|
|
}
|
|
if (connectchains) {
|
|
CurveLink subcurve = chain.linkTo(nextchain);
|
|
if (subcurve != null) {
|
|
subcurves.add(subcurve);
|
|
}
|
|
curchain += 2;
|
|
chain = endlist[curchain];
|
|
nextchain = endlist[curchain+1];
|
|
}
|
|
if (connectlinks) {
|
|
ChainEnd openend = new ChainEnd(link, null);
|
|
ChainEnd closeend = new ChainEnd(nextlink, openend);
|
|
openend.setOtherEnd(closeend);
|
|
chains.add(openend);
|
|
chains.add(closeend);
|
|
curlink += 2;
|
|
link = linklist[curlink];
|
|
nextlink = linklist[curlink+1];
|
|
}
|
|
if (!connectchains && !connectlinks) {
|
|
// assert(link != null);
|
|
// assert(chain != null);
|
|
// assert(chain.getEtag() == link.getEtag());
|
|
chain.addLink(link);
|
|
chains.add(chain);
|
|
curchain++;
|
|
chain = nextchain;
|
|
nextchain = endlist[curchain+1];
|
|
curlink++;
|
|
link = nextlink;
|
|
nextlink = linklist[curlink+1];
|
|
}
|
|
}
|
|
if ((chains.size() & 1) != 0) {
|
|
System.out.println("Odd number of chains!");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Does the position of the next edge at v1 "obstruct" the
|
|
* connectivity between current edge and the potential
|
|
* partner edge which is positioned at v2?
|
|
*
|
|
* Phase tells us whether we are testing for a transition
|
|
* into or out of the interior part of the resulting area.
|
|
*
|
|
* Require 4-connected continuity if this edge and the partner
|
|
* edge are both "entering into" type edges
|
|
* Allow 8-connected continuity for "exiting from" type edges
|
|
*/
|
|
public static boolean obstructs(double v1, double v2, int phase) {
|
|
return (((phase & 1) == 0) ? (v1 <= v2) : (v1 < v2));
|
|
}
|
|
}
|