1 /*
   2  * Copyright (c) 1998, 2003, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.geom;
  27 
  28 import java.util.Vector;
  29 import java.util.Enumeration;
  30 import java.util.Comparator;
  31 import java.util.Arrays;
  32 
  33 public abstract class AreaOp {
  34     public static abstract class CAGOp extends AreaOp {
  35         boolean inLeft;
  36         boolean inRight;
  37         boolean inResult;
  38 
  39         public void newRow() {
  40             inLeft = false;
  41             inRight = false;
  42             inResult = false;
  43         }
  44 
  45         public int classify(Edge e) {
  46             if (e.getCurveTag() == CTAG_LEFT) {
  47                 inLeft = !inLeft;
  48             } else {
  49                 inRight = !inRight;
  50             }
  51             boolean newClass = newClassification(inLeft, inRight);
  52             if (inResult == newClass) {
  53                 return ETAG_IGNORE;
  54             }
  55             inResult = newClass;
  56             return (newClass ? ETAG_ENTER : ETAG_EXIT);
  57         }
  58 
  59         public int getState() {
  60             return (inResult ? RSTAG_INSIDE : RSTAG_OUTSIDE);
  61         }
  62 
  63         public abstract boolean newClassification(boolean inLeft,
  64                                                   boolean inRight);
  65     }
  66 
  67     public static class AddOp extends CAGOp {
  68         public boolean newClassification(boolean inLeft, boolean inRight) {
  69             return (inLeft || inRight);
  70         }
  71     }
  72 
  73     public static class SubOp extends CAGOp {
  74         public boolean newClassification(boolean inLeft, boolean inRight) {
  75             return (inLeft && !inRight);
  76         }
  77     }
  78 
  79     public static class IntOp extends CAGOp {
  80         public boolean newClassification(boolean inLeft, boolean inRight) {
  81             return (inLeft && inRight);
  82         }
  83     }
  84 
  85     public static class XorOp extends CAGOp {
  86         public boolean newClassification(boolean inLeft, boolean inRight) {
  87             return (inLeft != inRight);
  88         }
  89     }
  90 
  91     public static class NZWindOp extends AreaOp {
  92         private int count;
  93 
  94         public void newRow() {
  95             count = 0;
  96         }
  97 
  98         public int classify(Edge e) {
  99             // Note: the right curves should be an empty set with this op...
 100             // assert(e.getCurveTag() == CTAG_LEFT);
 101             int newCount = count;
 102             int type = (newCount == 0 ? ETAG_ENTER : ETAG_IGNORE);
 103             newCount += e.getCurve().getDirection();
 104             count = newCount;
 105             return (newCount == 0 ? ETAG_EXIT : type);
 106         }
 107 
 108         public int getState() {
 109             return ((count == 0) ? RSTAG_OUTSIDE : RSTAG_INSIDE);
 110         }
 111     }
 112 
 113     public static class EOWindOp extends AreaOp {
 114         private boolean inside;
 115 
 116         public void newRow() {
 117             inside = false;
 118         }
 119 
 120         public int classify(Edge e) {
 121             // Note: the right curves should be an empty set with this op...
 122             // assert(e.getCurveTag() == CTAG_LEFT);
 123             boolean newInside = !inside;
 124             inside = newInside;
 125             return (newInside ? ETAG_ENTER : ETAG_EXIT);
 126         }
 127 
 128         public int getState() {
 129             return (inside ? RSTAG_INSIDE : RSTAG_OUTSIDE);
 130         }
 131     }
 132 
 133     private AreaOp() {
 134     }
 135 
 136     /* Constants to tag the left and right curves in the edge list */
 137     public static final int CTAG_LEFT = 0;
 138     public static final int CTAG_RIGHT = 1;
 139 
 140     /* Constants to classify edges */
 141     public static final int ETAG_IGNORE = 0;
 142     public static final int ETAG_ENTER = 1;
 143     public static final int ETAG_EXIT = -1;
 144 
 145     /* Constants used to classify result state */
 146     public static final int RSTAG_INSIDE = 1;
 147     public static final int RSTAG_OUTSIDE = -1;
 148 
 149     public abstract void newRow();
 150 
 151     public abstract int classify(Edge e);
 152 
 153     public abstract int getState();
 154 
 155     public Vector calculate(Vector left, Vector right) {
 156         Vector edges = new Vector();
 157         addEdges(edges, left, AreaOp.CTAG_LEFT);
 158         addEdges(edges, right, AreaOp.CTAG_RIGHT);
 159         edges = pruneEdges(edges);
 160         if (false) {
 161             System.out.println("result: ");
 162             int numcurves = edges.size();
 163             Curve[] curvelist = (Curve[]) edges.toArray(new Curve[numcurves]);
 164             for (int i = 0; i < numcurves; i++) {
 165                 System.out.println("curvelist["+i+"] = "+curvelist[i]);
 166             }
 167         }
 168         return edges;
 169     }
 170 
 171     private static void addEdges(Vector edges, Vector curves, int curvetag) {
 172         Enumeration enum_ = curves.elements();
 173         while (enum_.hasMoreElements()) {
 174             Curve c = (Curve) enum_.nextElement();
 175             if (c.getOrder() > 0) {
 176                 edges.add(new Edge(c, curvetag));
 177             }
 178         }
 179     }
 180 
 181     private static Comparator YXTopComparator = new Comparator() {
 182         public int compare(Object o1, Object o2) {
 183             Curve c1 = ((Edge) o1).getCurve();
 184             Curve c2 = ((Edge) o2).getCurve();
 185             double v1, v2;
 186             if ((v1 = c1.getYTop()) == (v2 = c2.getYTop())) {
 187                 if ((v1 = c1.getXTop()) == (v2 = c2.getXTop())) {
 188                     return 0;
 189                 }
 190             }
 191             if (v1 < v2) {
 192                 return -1;
 193             }
 194             return 1;
 195         }
 196     };
 197 
 198     private Vector pruneEdges(Vector edges) {
 199         int numedges = edges.size();
 200         if (numedges < 2) {
 201             return edges;
 202         }
 203         Edge[] edgelist = (Edge[]) edges.toArray(new Edge[numedges]);
 204         Arrays.sort(edgelist, YXTopComparator);
 205         if (false) {
 206             System.out.println("pruning: ");
 207             for (int i = 0; i < numedges; i++) {
 208                 System.out.println("edgelist["+i+"] = "+edgelist[i]);
 209             }
 210         }
 211         Edge e;
 212         int left = 0;
 213         int right = 0;
 214         int cur = 0;
 215         int next = 0;
 216         double yrange[] = new double[2];
 217         Vector subcurves = new Vector();
 218         Vector chains = new Vector();
 219         Vector links = new Vector();
 220         // Active edges are between left (inclusive) and right (exclusive)
 221         while (left < numedges) {
 222             double y = yrange[0];
 223             // Prune active edges that fall off the top of the active y range
 224             for (cur = next = right - 1; cur >= left; cur--) {
 225                 e = edgelist[cur];
 226                 if (e.getCurve().getYBot() > y) {
 227                     if (next > cur) {
 228                         edgelist[next] = e;
 229                     }
 230                     next--;
 231                 }
 232             }
 233             left = next + 1;
 234             // Grab a new "top of Y range" if the active edges are empty
 235             if (left >= right) {
 236                 if (right >= numedges) {
 237                     break;
 238                 }
 239                 y = edgelist[right].getCurve().getYTop();
 240                 if (y > yrange[0]) {
 241                     finalizeSubCurves(subcurves, chains);
 242                 }
 243                 yrange[0] = y;
 244             }
 245             // Incorporate new active edges that enter the active y range
 246             while (right < numedges) {
 247                 e = edgelist[right];
 248                 if (e.getCurve().getYTop() > y) {
 249                     break;
 250                 }
 251                 right++;
 252             }
 253             // Sort the current active edges by their X values and
 254             // determine the maximum valid Y range where the X ordering
 255             // is correct
 256             yrange[1] = edgelist[left].getCurve().getYBot();
 257             if (right < numedges) {
 258                 y = edgelist[right].getCurve().getYTop();
 259                 if (yrange[1] > y) {
 260                     yrange[1] = y;
 261                 }
 262             }
 263             if (false) {
 264                 System.out.println("current line: y = ["+
 265                                    yrange[0]+", "+yrange[1]+"]");
 266                 for (cur = left; cur < right; cur++) {
 267                     System.out.println("  "+edgelist[cur]);
 268                 }
 269             }
 270             // Note: We could start at left+1, but we need to make
 271             // sure that edgelist[left] has its equivalence set to 0.
 272             int nexteq = 1;
 273             for (cur = left; cur < right; cur++) {
 274                 e = edgelist[cur];
 275                 e.setEquivalence(0);
 276                 for (next = cur; next > left; next--) {
 277                     Edge prevedge = edgelist[next-1];
 278                     int ordering = e.compareTo(prevedge, yrange);
 279                     if (yrange[1] <= yrange[0]) {
 280                         throw new InternalError("backstepping to "+yrange[1]+
 281                                                 " from "+yrange[0]);
 282                     }
 283                     if (ordering >= 0) {
 284                         if (ordering == 0) {
 285                             // If the curves are equal, mark them to be
 286                             // deleted later if they cancel each other
 287                             // out so that we avoid having extraneous
 288                             // curve segments.
 289                             int eq = prevedge.getEquivalence();
 290                             if (eq == 0) {
 291                                 eq = nexteq++;
 292                                 prevedge.setEquivalence(eq);
 293                             }
 294                             e.setEquivalence(eq);
 295                         }
 296                         break;
 297                     }
 298                     edgelist[next] = prevedge;
 299                 }
 300                 edgelist[next] = e;
 301             }
 302             if (false) {
 303                 System.out.println("current sorted line: y = ["+
 304                                    yrange[0]+", "+yrange[1]+"]");
 305                 for (cur = left; cur < right; cur++) {
 306                     System.out.println("  "+edgelist[cur]);
 307                 }
 308             }
 309             // Now prune the active edge list.
 310             // For each edge in the list, determine its classification
 311             // (entering shape, exiting shape, ignore - no change) and
 312             // record the current Y range and its classification in the
 313             // Edge object for use later in constructing the new outline.
 314             newRow();
 315             double ystart = yrange[0];
 316             double yend = yrange[1];
 317             for (cur = left; cur < right; cur++) {
 318                 e = edgelist[cur];
 319                 int etag;
 320                 int eq = e.getEquivalence();
 321                 if (eq != 0) {
 322                     // Find one of the segments in the "equal" range
 323                     // with the right transition state and prefer an
 324                     // edge that was either active up until ystart
 325                     // or the edge that extends the furthest downward
 326                     // (i.e. has the most potential for continuation)
 327                     int origstate = getState();
 328                     etag = (origstate == AreaOp.RSTAG_INSIDE
 329                             ? AreaOp.ETAG_EXIT
 330                             : AreaOp.ETAG_ENTER);
 331                     Edge activematch = null;
 332                     Edge longestmatch = e;
 333                     double furthesty = yend;
 334                     do {
 335                         // Note: classify() must be called
 336                         // on every edge we consume here.
 337                         classify(e);
 338                         if (activematch == null &&
 339                             e.isActiveFor(ystart, etag))
 340                         {
 341                             activematch = e;
 342                         }
 343                         y = e.getCurve().getYBot();
 344                         if (y > furthesty) {
 345                             longestmatch = e;
 346                             furthesty = y;
 347                         }
 348                     } while (++cur < right &&
 349                              (e = edgelist[cur]).getEquivalence() == eq);
 350                     --cur;
 351                     if (getState() == origstate) {
 352                         etag = AreaOp.ETAG_IGNORE;
 353                     } else {
 354                         e = (activematch != null ? activematch : longestmatch);
 355                     }
 356                 } else {
 357                     etag = classify(e);
 358                 }
 359                 if (etag != AreaOp.ETAG_IGNORE) {
 360                     e.record(yend, etag);
 361                     links.add(new CurveLink(e.getCurve(), ystart, yend, etag));
 362                 }
 363             }
 364             // assert(getState() == AreaOp.RSTAG_OUTSIDE);
 365             if (getState() != AreaOp.RSTAG_OUTSIDE) {
 366                 System.out.println("Still inside at end of active edge list!");
 367                 System.out.println("num curves = "+(right-left));
 368                 System.out.println("num links = "+links.size());
 369                 System.out.println("y top = "+yrange[0]);
 370                 if (right < numedges) {
 371                     System.out.println("y top of next curve = "+
 372                                        edgelist[right].getCurve().getYTop());
 373                 } else {
 374                     System.out.println("no more curves");
 375                 }
 376                 for (cur = left; cur < right; cur++) {
 377                     e = edgelist[cur];
 378                     System.out.println(e);
 379                     int eq = e.getEquivalence();
 380                     if (eq != 0) {
 381                         System.out.println("  was equal to "+eq+"...");
 382                     }
 383                 }
 384             }
 385             if (false) {
 386                 System.out.println("new links:");
 387                 for (int i = 0; i < links.size(); i++) {
 388                     CurveLink link = (CurveLink) links.elementAt(i);
 389                     System.out.println("  "+link.getSubCurve());
 390                 }
 391             }
 392             resolveLinks(subcurves, chains, links);
 393             links.clear();
 394             // Finally capture the bottom of the valid Y range as the top
 395             // of the next Y range.
 396             yrange[0] = yend;
 397         }
 398         finalizeSubCurves(subcurves, chains);
 399         Vector ret = new Vector();
 400         Enumeration enum_ = subcurves.elements();
 401         while (enum_.hasMoreElements()) {
 402             CurveLink link = (CurveLink) enum_.nextElement();
 403             ret.add(link.getMoveto());
 404             CurveLink nextlink = link;
 405             while ((nextlink = nextlink.getNext()) != null) {
 406                 if (!link.absorb(nextlink)) {
 407                     ret.add(link.getSubCurve());
 408                     link = nextlink;
 409                 }
 410             }
 411             ret.add(link.getSubCurve());
 412         }
 413         return ret;
 414     }
 415 
 416     public static void finalizeSubCurves(Vector subcurves, Vector chains) {
 417         int numchains = chains.size();
 418         if (numchains == 0) {
 419             return;
 420         }
 421         if ((numchains & 1) != 0) {
 422             throw new InternalError("Odd number of chains!");
 423         }
 424         ChainEnd[] endlist = new ChainEnd[numchains];
 425         chains.toArray(endlist);
 426         for (int i = 1; i < numchains; i += 2) {
 427             ChainEnd open = endlist[i - 1];
 428             ChainEnd close = endlist[i];
 429             CurveLink subcurve = open.linkTo(close);
 430             if (subcurve != null) {
 431                 subcurves.add(subcurve);
 432             }
 433         }
 434         chains.clear();
 435     }
 436 
 437     private static CurveLink[] EmptyLinkList = new CurveLink[2];
 438     private static ChainEnd[] EmptyChainList = new ChainEnd[2];
 439 
 440     public static void resolveLinks(Vector subcurves,
 441                                     Vector chains,
 442                                     Vector links)
 443     {
 444         int numlinks = links.size();
 445         CurveLink[] linklist;
 446         if (numlinks == 0) {
 447             linklist = EmptyLinkList;
 448         } else {
 449             if ((numlinks & 1) != 0) {
 450                 throw new InternalError("Odd number of new curves!");
 451             }
 452             linklist = new CurveLink[numlinks+2];
 453             links.toArray(linklist);
 454         }
 455         int numchains = chains.size();
 456         ChainEnd[] endlist;
 457         if (numchains == 0) {
 458             endlist = EmptyChainList;
 459         } else {
 460             if ((numchains & 1) != 0) {
 461                 throw new InternalError("Odd number of chains!");
 462             }
 463             endlist = new ChainEnd[numchains+2];
 464             chains.toArray(endlist);
 465         }
 466         int curchain = 0;
 467         int curlink = 0;
 468         chains.clear();
 469         ChainEnd chain = endlist[0];
 470         ChainEnd nextchain = endlist[1];
 471         CurveLink link = linklist[0];
 472         CurveLink nextlink = linklist[1];
 473         while (chain != null || link != null) {
 474             /*
 475              * Strategy 1:
 476              * Connect chains or links if they are the only things left...
 477              */
 478             boolean connectchains = (link == null);
 479             boolean connectlinks = (chain == null);
 480 
 481             if (!connectchains && !connectlinks) {
 482                 // assert(link != null && chain != null);
 483                 /*
 484                  * Strategy 2:
 485                  * Connect chains or links if they close off an open area...
 486                  */
 487                 connectchains = ((curchain & 1) == 0 &&
 488                                  chain.getX() == nextchain.getX());
 489                 connectlinks = ((curlink & 1) == 0 &&
 490                                 link.getX() == nextlink.getX());
 491 
 492                 if (!connectchains && !connectlinks) {
 493                     /*
 494                      * Strategy 3:
 495                      * Connect chains or links if their successor is
 496                      * between them and their potential connectee...
 497                      */
 498                     double cx = chain.getX();
 499                     double lx = link.getX();
 500                     connectchains =
 501                         (nextchain != null && cx < lx &&
 502                          obstructs(nextchain.getX(), lx, curchain));
 503                     connectlinks =
 504                         (nextlink != null && lx < cx &&
 505                          obstructs(nextlink.getX(), cx, curlink));
 506                 }
 507             }
 508             if (connectchains) {
 509                 CurveLink subcurve = chain.linkTo(nextchain);
 510                 if (subcurve != null) {
 511                     subcurves.add(subcurve);
 512                 }
 513                 curchain += 2;
 514                 chain = endlist[curchain];
 515                 nextchain = endlist[curchain+1];
 516             }
 517             if (connectlinks) {
 518                 ChainEnd openend = new ChainEnd(link, null);
 519                 ChainEnd closeend = new ChainEnd(nextlink, openend);
 520                 openend.setOtherEnd(closeend);
 521                 chains.add(openend);
 522                 chains.add(closeend);
 523                 curlink += 2;
 524                 link = linklist[curlink];
 525                 nextlink = linklist[curlink+1];
 526             }
 527             if (!connectchains && !connectlinks) {
 528                 // assert(link != null);
 529                 // assert(chain != null);
 530                 // assert(chain.getEtag() == link.getEtag());
 531                 chain.addLink(link);
 532                 chains.add(chain);
 533                 curchain++;
 534                 chain = nextchain;
 535                 nextchain = endlist[curchain+1];
 536                 curlink++;
 537                 link = nextlink;
 538                 nextlink = linklist[curlink+1];
 539             }
 540         }
 541         if ((chains.size() & 1) != 0) {
 542             System.out.println("Odd number of chains!");
 543         }
 544     }
 545 
 546     /*
 547      * Does the position of the next edge at v1 "obstruct" the
 548      * connectivity between current edge and the potential
 549      * partner edge which is positioned at v2?
 550      *
 551      * Phase tells us whether we are testing for a transition
 552      * into or out of the interior part of the resulting area.
 553      *
 554      * Require 4-connected continuity if this edge and the partner
 555      * edge are both "entering into" type edges
 556      * Allow 8-connected continuity for "exiting from" type edges
 557      */
 558     public static boolean obstructs(double v1, double v2, int phase) {
 559         return (((phase & 1) == 0) ? (v1 <= v2) : (v1 < v2));
 560     }
 561 }