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 abstract static 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<Curve> calculate(Vector<Curve> left, Vector<Curve> right) {
 156         Vector<Edge> edges = new Vector<>();
 157         addEdges(edges, left, AreaOp.CTAG_LEFT);
 158         addEdges(edges, right, AreaOp.CTAG_RIGHT);
 159         Vector<Curve> curves = pruneEdges(edges);
 160         if (false) {
 161             System.out.println("result: ");
 162             int numcurves = curves.size();
 163             Curve[] curvelist = curves.toArray(new Curve[numcurves]);
 164             for (int i = 0; i < numcurves; i++) {
 165                 System.out.println("curvelist["+i+"] = "+curvelist[i]);
 166             }
 167         }
 168         return curves;
 169     }
 170 
 171     private static void addEdges(Vector<Edge> edges, Vector<Curve> curves, int curvetag) {
 172         Enumeration<Curve> enum_ = curves.elements();
 173         while (enum_.hasMoreElements()) {
 174             Curve c = enum_.nextElement();
 175             if (c.getOrder() > 0) {
 176                 edges.add(new Edge(c, curvetag));
 177             }
 178         }
 179     }
 180 
 181     private static Comparator<Edge> YXTopComparator = new Comparator<Edge>() {
 182         public int compare(Edge o1, Edge o2) {
 183             Curve c1 = o1.getCurve();
 184             Curve c2 = 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<Curve> pruneEdges(Vector<Edge> edges) {
 199         int numedges = edges.size();
 200         if (numedges < 2) {
 201             // empty vector is expected with less than 2 edges
 202             return new Vector<>();
 203         }
 204         Edge[] edgelist = edges.toArray(new Edge[numedges]);
 205         Arrays.sort(edgelist, YXTopComparator);
 206         if (false) {
 207             System.out.println("pruning: ");
 208             for (int i = 0; i < numedges; i++) {
 209                 System.out.println("edgelist["+i+"] = "+edgelist[i]);
 210             }
 211         }
 212         Edge e;
 213         int left = 0;
 214         int right = 0;
 215         int cur = 0;
 216         int next = 0;
 217         double yrange[] = new double[2];
 218         Vector<CurveLink> subcurves = new Vector<>();
 219         Vector<ChainEnd> chains = new Vector<>();
 220         Vector<CurveLink> links = new Vector<>();
 221         // Active edges are between left (inclusive) and right (exclusive)
 222         while (left < numedges) {
 223             double y = yrange[0];
 224             // Prune active edges that fall off the top of the active y range
 225             for (cur = next = right - 1; cur >= left; cur--) {
 226                 e = edgelist[cur];
 227                 if (e.getCurve().getYBot() > y) {
 228                     if (next > cur) {
 229                         edgelist[next] = e;
 230                     }
 231                     next--;
 232                 }
 233             }
 234             left = next + 1;
 235             // Grab a new "top of Y range" if the active edges are empty
 236             if (left >= right) {
 237                 if (right >= numedges) {
 238                     break;
 239                 }
 240                 y = edgelist[right].getCurve().getYTop();
 241                 if (y > yrange[0]) {
 242                     finalizeSubCurves(subcurves, chains);
 243                 }
 244                 yrange[0] = y;
 245             }
 246             // Incorporate new active edges that enter the active y range
 247             while (right < numedges) {
 248                 e = edgelist[right];
 249                 if (e.getCurve().getYTop() > y) {
 250                     break;
 251                 }
 252                 right++;
 253             }
 254             // Sort the current active edges by their X values and
 255             // determine the maximum valid Y range where the X ordering
 256             // is correct
 257             yrange[1] = edgelist[left].getCurve().getYBot();
 258             if (right < numedges) {
 259                 y = edgelist[right].getCurve().getYTop();
 260                 if (yrange[1] > y) {
 261                     yrange[1] = y;
 262                 }
 263             }
 264             if (false) {
 265                 System.out.println("current line: y = ["+
 266                                    yrange[0]+", "+yrange[1]+"]");
 267                 for (cur = left; cur < right; cur++) {
 268                     System.out.println("  "+edgelist[cur]);
 269                 }
 270             }
 271             // Note: We could start at left+1, but we need to make
 272             // sure that edgelist[left] has its equivalence set to 0.
 273             int nexteq = 1;
 274             for (cur = left; cur < right; cur++) {
 275                 e = edgelist[cur];
 276                 e.setEquivalence(0);
 277                 for (next = cur; next > left; next--) {
 278                     Edge prevedge = edgelist[next-1];
 279                     int ordering = e.compareTo(prevedge, yrange);
 280                     if (yrange[1] <= yrange[0]) {
 281                         throw new InternalError("backstepping to "+yrange[1]+
 282                                                 " from "+yrange[0]);
 283                     }
 284                     if (ordering >= 0) {
 285                         if (ordering == 0) {
 286                             // If the curves are equal, mark them to be
 287                             // deleted later if they cancel each other
 288                             // out so that we avoid having extraneous
 289                             // curve segments.
 290                             int eq = prevedge.getEquivalence();
 291                             if (eq == 0) {
 292                                 eq = nexteq++;
 293                                 prevedge.setEquivalence(eq);
 294                             }
 295                             e.setEquivalence(eq);
 296                         }
 297                         break;
 298                     }
 299                     edgelist[next] = prevedge;
 300                 }
 301                 edgelist[next] = e;
 302             }
 303             if (false) {
 304                 System.out.println("current sorted line: y = ["+
 305                                    yrange[0]+", "+yrange[1]+"]");
 306                 for (cur = left; cur < right; cur++) {
 307                     System.out.println("  "+edgelist[cur]);
 308                 }
 309             }
 310             // Now prune the active edge list.
 311             // For each edge in the list, determine its classification
 312             // (entering shape, exiting shape, ignore - no change) and
 313             // record the current Y range and its classification in the
 314             // Edge object for use later in constructing the new outline.
 315             newRow();
 316             double ystart = yrange[0];
 317             double yend = yrange[1];
 318             for (cur = left; cur < right; cur++) {
 319                 e = edgelist[cur];
 320                 int etag;
 321                 int eq = e.getEquivalence();
 322                 if (eq != 0) {
 323                     // Find one of the segments in the "equal" range
 324                     // with the right transition state and prefer an
 325                     // edge that was either active up until ystart
 326                     // or the edge that extends the furthest downward
 327                     // (i.e. has the most potential for continuation)
 328                     int origstate = getState();
 329                     etag = (origstate == AreaOp.RSTAG_INSIDE
 330                             ? AreaOp.ETAG_EXIT
 331                             : AreaOp.ETAG_ENTER);
 332                     Edge activematch = null;
 333                     Edge longestmatch = e;
 334                     double furthesty = yend;
 335                     do {
 336                         // Note: classify() must be called
 337                         // on every edge we consume here.
 338                         classify(e);
 339                         if (activematch == null &&
 340                             e.isActiveFor(ystart, etag))
 341                         {
 342                             activematch = e;
 343                         }
 344                         y = e.getCurve().getYBot();
 345                         if (y > furthesty) {
 346                             longestmatch = e;
 347                             furthesty = y;
 348                         }
 349                     } while (++cur < right &&
 350                              (e = edgelist[cur]).getEquivalence() == eq);
 351                     --cur;
 352                     if (getState() == origstate) {
 353                         etag = AreaOp.ETAG_IGNORE;
 354                     } else {
 355                         e = (activematch != null ? activematch : longestmatch);
 356                     }
 357                 } else {
 358                     etag = classify(e);
 359                 }
 360                 if (etag != AreaOp.ETAG_IGNORE) {
 361                     e.record(yend, etag);
 362                     links.add(new CurveLink(e.getCurve(), ystart, yend, etag));
 363                 }
 364             }
 365             // assert(getState() == AreaOp.RSTAG_OUTSIDE);
 366             if (getState() != AreaOp.RSTAG_OUTSIDE) {
 367                 System.out.println("Still inside at end of active edge list!");
 368                 System.out.println("num curves = "+(right-left));
 369                 System.out.println("num links = "+links.size());
 370                 System.out.println("y top = "+yrange[0]);
 371                 if (right < numedges) {
 372                     System.out.println("y top of next curve = "+
 373                                        edgelist[right].getCurve().getYTop());
 374                 } else {
 375                     System.out.println("no more curves");
 376                 }
 377                 for (cur = left; cur < right; cur++) {
 378                     e = edgelist[cur];
 379                     System.out.println(e);
 380                     int eq = e.getEquivalence();
 381                     if (eq != 0) {
 382                         System.out.println("  was equal to "+eq+"...");
 383                     }
 384                 }
 385             }
 386             if (false) {
 387                 System.out.println("new links:");
 388                 for (int i = 0; i < links.size(); i++) {
 389                     CurveLink link = links.elementAt(i);
 390                     System.out.println("  "+link.getSubCurve());
 391                 }
 392             }
 393             resolveLinks(subcurves, chains, links);
 394             links.clear();
 395             // Finally capture the bottom of the valid Y range as the top
 396             // of the next Y range.
 397             yrange[0] = yend;
 398         }
 399         finalizeSubCurves(subcurves, chains);
 400         Vector<Curve> ret = new Vector<>();
 401         Enumeration<CurveLink> enum_ = subcurves.elements();
 402         while (enum_.hasMoreElements()) {
 403             CurveLink link = enum_.nextElement();
 404             ret.add(link.getMoveto());
 405             CurveLink nextlink = link;
 406             while ((nextlink = nextlink.getNext()) != null) {
 407                 if (!link.absorb(nextlink)) {
 408                     ret.add(link.getSubCurve());
 409                     link = nextlink;
 410                 }
 411             }
 412             ret.add(link.getSubCurve());
 413         }
 414         return ret;
 415     }
 416 
 417     public static void finalizeSubCurves(Vector<CurveLink> subcurves,
 418                                          Vector<ChainEnd> chains) {
 419         int numchains = chains.size();
 420         if (numchains == 0) {
 421             return;
 422         }
 423         if ((numchains & 1) != 0) {
 424             throw new InternalError("Odd number of chains!");
 425         }
 426         ChainEnd[] endlist = new ChainEnd[numchains];
 427         chains.toArray(endlist);
 428         for (int i = 1; i < numchains; i += 2) {
 429             ChainEnd open = endlist[i - 1];
 430             ChainEnd close = endlist[i];
 431             CurveLink subcurve = open.linkTo(close);
 432             if (subcurve != null) {
 433                 subcurves.add(subcurve);
 434             }
 435         }
 436         chains.clear();
 437     }
 438 
 439     private static CurveLink[] EmptyLinkList = new CurveLink[2];
 440     private static ChainEnd[] EmptyChainList = new ChainEnd[2];
 441 
 442     public static void resolveLinks(Vector<CurveLink> subcurves,
 443                                     Vector<ChainEnd> chains,
 444                                     Vector<CurveLink> links)
 445     {
 446         int numlinks = links.size();
 447         CurveLink[] linklist;
 448         if (numlinks == 0) {
 449             linklist = EmptyLinkList;
 450         } else {
 451             if ((numlinks & 1) != 0) {
 452                 throw new InternalError("Odd number of new curves!");
 453             }
 454             linklist = new CurveLink[numlinks+2];
 455             links.toArray(linklist);
 456         }
 457         int numchains = chains.size();
 458         ChainEnd[] endlist;
 459         if (numchains == 0) {
 460             endlist = EmptyChainList;
 461         } else {
 462             if ((numchains & 1) != 0) {
 463                 throw new InternalError("Odd number of chains!");
 464             }
 465             endlist = new ChainEnd[numchains+2];
 466             chains.toArray(endlist);
 467         }
 468         int curchain = 0;
 469         int curlink = 0;
 470         chains.clear();
 471         ChainEnd chain = endlist[0];
 472         ChainEnd nextchain = endlist[1];
 473         CurveLink link = linklist[0];
 474         CurveLink nextlink = linklist[1];
 475         while (chain != null || link != null) {
 476             /*
 477              * Strategy 1:
 478              * Connect chains or links if they are the only things left...
 479              */
 480             boolean connectchains = (link == null);
 481             boolean connectlinks = (chain == null);
 482 
 483             if (!connectchains && !connectlinks) {
 484                 // assert(link != null && chain != null);
 485                 /*
 486                  * Strategy 2:
 487                  * Connect chains or links if they close off an open area...
 488                  */
 489                 connectchains = ((curchain & 1) == 0 &&
 490                                  chain.getX() == nextchain.getX());
 491                 connectlinks = ((curlink & 1) == 0 &&
 492                                 link.getX() == nextlink.getX());
 493 
 494                 if (!connectchains && !connectlinks) {
 495                     /*
 496                      * Strategy 3:
 497                      * Connect chains or links if their successor is
 498                      * between them and their potential connectee...
 499                      */
 500                     double cx = chain.getX();
 501                     double lx = link.getX();
 502                     connectchains =
 503                         (nextchain != null && cx < lx &&
 504                          obstructs(nextchain.getX(), lx, curchain));
 505                     connectlinks =
 506                         (nextlink != null && lx < cx &&
 507                          obstructs(nextlink.getX(), cx, curlink));
 508                 }
 509             }
 510             if (connectchains) {
 511                 CurveLink subcurve = chain.linkTo(nextchain);
 512                 if (subcurve != null) {
 513                     subcurves.add(subcurve);
 514                 }
 515                 curchain += 2;
 516                 chain = endlist[curchain];
 517                 nextchain = endlist[curchain+1];
 518             }
 519             if (connectlinks) {
 520                 ChainEnd openend = new ChainEnd(link, null);
 521                 ChainEnd closeend = new ChainEnd(nextlink, openend);
 522                 openend.setOtherEnd(closeend);
 523                 chains.add(openend);
 524                 chains.add(closeend);
 525                 curlink += 2;
 526                 link = linklist[curlink];
 527                 nextlink = linklist[curlink+1];
 528             }
 529             if (!connectchains && !connectlinks) {
 530                 // assert(link != null);
 531                 // assert(chain != null);
 532                 // assert(chain.getEtag() == link.getEtag());
 533                 chain.addLink(link);
 534                 chains.add(chain);
 535                 curchain++;
 536                 chain = nextchain;
 537                 nextchain = endlist[curchain+1];
 538                 curlink++;
 539                 link = nextlink;
 540                 nextlink = linklist[curlink+1];
 541             }
 542         }
 543         if ((chains.size() & 1) != 0) {
 544             System.out.println("Odd number of chains!");
 545         }
 546     }
 547 
 548     /*
 549      * Does the position of the next edge at v1 "obstruct" the
 550      * connectivity between current edge and the potential
 551      * partner edge which is positioned at v2?
 552      *
 553      * Phase tells us whether we are testing for a transition
 554      * into or out of the interior part of the resulting area.
 555      *
 556      * Require 4-connected continuity if this edge and the partner
 557      * edge are both "entering into" type edges
 558      * Allow 8-connected continuity for "exiting from" type edges
 559      */
 560     public static boolean obstructs(double v1, double v2, int phase) {
 561         return (((phase & 1) == 0) ? (v1 <= v2) : (v1 < v2));
 562     }
 563 }