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