1 /*
2 * Copyright (c) 1998, 2006, 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
80 * which encloses no area (even when "closed") produces an
81 * empty <code>Area</code>. A common example of this issue
82 * is that producing an <code>Area</code> from a line will
83 * be empty since the line encloses no area. An empty
84 * <code>Area</code> will iterate no geometry in its
85 * <code>PathIterator</code> objects.
86 * <li>A self-intersecting <code>Shape</code> may be split into
87 * two (or more) sub-paths each enclosing one of the
88 * non-intersecting portions of the original path.
89 * <li>An <code>Area</code> may take more path segments to
90 * describe the same geometry even when the original
91 * outline is simple and obvious. The analysis that the
92 * <code>Area</code> class must perform on the path may
93 * not reflect the same concepts of "simple and obvious"
94 * as a human being perceives.
95 * </ul>
96 *
97 * @since 1.2
98 */
99 public class Area implements Shape, Cloneable {
100 private static Vector EmptyCurves = new Vector();
101
102 private Vector curves;
103
104 /**
105 * Default constructor which creates an empty area.
106 * @since 1.2
107 */
108 public Area() {
109 curves = EmptyCurves;
110 }
111
112 /**
113 * The <code>Area</code> class creates an area geometry from the
114 * specified {@link Shape} object. The geometry is explicitly
115 * closed, if the <code>Shape</code> is not already closed. The
116 * fill rule (even-odd or winding) specified by the geometry of the
117 * <code>Shape</code> is used to determine the resulting enclosed area.
118 * @param s the <code>Shape</code> from which the area is constructed
119 * @throws NullPointerException if <code>s</code> is null
120 * @since 1.2
121 */
122 public Area(Shape s) {
123 if (s instanceof Area) {
124 curves = ((Area) s).curves;
125 } else {
126 curves = pathToCurves(s.getPathIterator(null));
127 }
128 }
129
130 private static Vector pathToCurves(PathIterator pi) {
131 Vector curves = new Vector();
132 int windingRule = pi.getWindingRule();
133 // coords array is big enough for holding:
134 // coordinates returned from currentSegment (6)
135 // OR
136 // two subdivided quadratic curves (2+4+4=10)
137 // AND
138 // 0-1 horizontal splitting parameters
139 // OR
140 // 2 parametric equation derivative coefficients
141 // OR
142 // three subdivided cubic curves (2+6+6+6=20)
143 // AND
144 // 0-2 horizontal splitting parameters
145 // OR
146 // 3 parametric equation derivative coefficients
147 double coords[] = new double[23];
148 double movx = 0, movy = 0;
149 double curx = 0, cury = 0;
150 double newx, newy;
151 while (!pi.isDone()) {
317 * ###### ###### ###### ######
318 * #### #### #### ####
319 * ## ## ## ##
320 * </pre>
321 * @param rhs the <code>Area</code> to be exclusive ORed with this
322 * <code>Area</code>.
323 * @throws NullPointerException if <code>rhs</code> is null
324 * @since 1.2
325 */
326 public void exclusiveOr(Area rhs) {
327 curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
328 invalidateBounds();
329 }
330
331 /**
332 * Removes all of the geometry from this <code>Area</code> and
333 * restores it to an empty area.
334 * @since 1.2
335 */
336 public void reset() {
337 curves = new Vector();
338 invalidateBounds();
339 }
340
341 /**
342 * Tests whether this <code>Area</code> object encloses any area.
343 * @return <code>true</code> if this <code>Area</code> object
344 * represents an empty area; <code>false</code> otherwise.
345 * @since 1.2
346 */
347 public boolean isEmpty() {
348 return (curves.size() == 0);
349 }
350
351 /**
352 * Tests whether this <code>Area</code> consists entirely of
353 * straight edged polygonal geometry.
354 * @return <code>true</code> if the geometry of this
355 * <code>Area</code> consists entirely of line segments;
356 * <code>false</code> otherwise.
357 * @since 1.2
358 */
359 public boolean isPolygonal() {
360 Enumeration enum_ = curves.elements();
361 while (enum_.hasMoreElements()) {
362 if (((Curve) enum_.nextElement()).getOrder() > 1) {
363 return false;
364 }
365 }
366 return true;
367 }
368
369 /**
370 * Tests whether this <code>Area</code> is rectangular in shape.
371 * @return <code>true</code> if the geometry of this
372 * <code>Area</code> is rectangular in shape; <code>false</code>
373 * otherwise.
374 * @since 1.2
375 */
376 public boolean isRectangular() {
377 int size = curves.size();
378 if (size == 0) {
379 return true;
380 }
381 if (size > 3) {
382 return false;
383 }
384 Curve c1 = (Curve) curves.get(1);
385 Curve c2 = (Curve) curves.get(2);
386 if (c1.getOrder() != 1 || c2.getOrder() != 1) {
387 return false;
388 }
389 if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
390 return false;
391 }
392 if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
393 // One might be able to prove that this is impossible...
394 return false;
395 }
396 return true;
397 }
398
399 /**
400 * Tests whether this <code>Area</code> is comprised of a single
401 * closed subpath. This method returns <code>true</code> if the
402 * path contains 0 or 1 subpaths, or <code>false</code> if the path
403 * contains more than 1 subpath. The subpaths are counted by the
404 * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments
405 * that appear in the path.
406 * @return <code>true</code> if the <code>Area</code> is comprised
407 * of a single basic geometry; <code>false</code> otherwise.
408 * @since 1.2
409 */
410 public boolean isSingular() {
411 if (curves.size() < 3) {
412 return true;
413 }
414 Enumeration enum_ = curves.elements();
415 enum_.nextElement(); // First Order0 "moveto"
416 while (enum_.hasMoreElements()) {
417 if (((Curve) enum_.nextElement()).getOrder() == 0) {
418 return false;
419 }
420 }
421 return true;
422 }
423
424 private Rectangle2D cachedBounds;
425 private void invalidateBounds() {
426 cachedBounds = null;
427 }
428 private Rectangle2D getCachedBounds() {
429 if (cachedBounds != null) {
430 return cachedBounds;
431 }
432 Rectangle2D r = new Rectangle2D.Double();
433 if (curves.size() > 0) {
434 Curve c = (Curve) curves.get(0);
435 // First point is always an order 0 curve (moveto)
436 r.setRect(c.getX0(), c.getY0(), 0, 0);
437 for (int i = 1; i < curves.size(); i++) {
438 ((Curve) curves.get(i)).enlarge(r);
439 }
440 }
441 return (cachedBounds = r);
442 }
443
444 /**
445 * Returns a high precision bounding {@link Rectangle2D} that
446 * completely encloses this <code>Area</code>.
447 * <p>
448 * The Area class will attempt to return the tightest bounding
449 * box possible for the Shape. The bounding box will not be
450 * padded to include the control points of curves in the outline
451 * of the Shape, but should tightly fit the actual geometry of
452 * the outline itself.
453 * @return the bounding <code>Rectangle2D</code> for the
454 * <code>Area</code>.
455 * @since 1.2
456 */
457 public Rectangle2D getBounds2D() {
458 return getCachedBounds().getBounds2D();
490 /**
491 * Tests whether the geometries of the two <code>Area</code> objects
492 * are equal.
493 * This method will return false if the argument is null.
494 * @param other the <code>Area</code> to be compared to this
495 * <code>Area</code>
496 * @return <code>true</code> if the two geometries are equal;
497 * <code>false</code> otherwise.
498 * @since 1.2
499 */
500 public boolean equals(Area other) {
501 // REMIND: A *much* simpler operation should be possible...
502 // Should be able to do a curve-wise comparison since all Areas
503 // should evaluate their curves in the same top-down order.
504 if (other == this) {
505 return true;
506 }
507 if (other == null) {
508 return false;
509 }
510 Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves);
511 return c.isEmpty();
512 }
513
514 /**
515 * Transforms the geometry of this <code>Area</code> using the specified
516 * {@link AffineTransform}. The geometry is transformed in place, which
517 * permanently changes the enclosed area defined by this object.
518 * @param t the transformation used to transform the area
519 * @throws NullPointerException if <code>t</code> is null
520 * @since 1.2
521 */
522 public void transform(AffineTransform t) {
523 if (t == null) {
524 throw new NullPointerException("transform must not be null");
525 }
526 // REMIND: A simpler operation can be performed for some types
527 // of transform.
528 curves = pathToCurves(getPathIterator(t));
529 invalidateBounds();
530 }
538 * the new <code>Area</code>
539 * @throws NullPointerException if <code>t</code> is null
540 * @return a new <code>Area</code> object representing the transformed
541 * geometry.
542 * @since 1.2
543 */
544 public Area createTransformedArea(AffineTransform t) {
545 Area a = new Area(this);
546 a.transform(t);
547 return a;
548 }
549
550 /**
551 * {@inheritDoc}
552 * @since 1.2
553 */
554 public boolean contains(double x, double y) {
555 if (!getCachedBounds().contains(x, y)) {
556 return false;
557 }
558 Enumeration enum_ = curves.elements();
559 int crossings = 0;
560 while (enum_.hasMoreElements()) {
561 Curve c = (Curve) enum_.nextElement();
562 crossings += c.crossingsFor(x, y);
563 }
564 return ((crossings & 1) == 1);
565 }
566
567 /**
568 * {@inheritDoc}
569 * @since 1.2
570 */
571 public boolean contains(Point2D p) {
572 return contains(p.getX(), p.getY());
573 }
574
575 /**
576 * {@inheritDoc}
577 * @since 1.2
578 */
579 public boolean contains(double x, double y, double w, double h) {
580 if (w < 0 || h < 0) {
581 return false;
641 * object is unchanged.
642 * @param at an optional <code>AffineTransform</code> to be
643 * applied to the coordinates as they are returned in the
644 * iteration, or <code>null</code> if untransformed coordinates
645 * are desired
646 * @param flatness the maximum amount that the control points
647 * for a given curve can vary from colinear before a subdivided
648 * curve is replaced by a straight line connecting the end points
649 * @return the <code>PathIterator</code> object that returns the
650 * geometry of the outline of this <code>Area</code>, one segment
651 * at a time.
652 * @since 1.2
653 */
654 public PathIterator getPathIterator(AffineTransform at, double flatness) {
655 return new FlatteningPathIterator(getPathIterator(at), flatness);
656 }
657 }
658
659 class AreaIterator implements PathIterator {
660 private AffineTransform transform;
661 private Vector curves;
662 private int index;
663 private Curve prevcurve;
664 private Curve thiscurve;
665
666 public AreaIterator(Vector curves, AffineTransform at) {
667 this.curves = curves;
668 this.transform = at;
669 if (curves.size() >= 1) {
670 thiscurve = (Curve) curves.get(0);
671 }
672 }
673
674 public int getWindingRule() {
675 // REMIND: Which is better, EVEN_ODD or NON_ZERO?
676 // The paths calculated could be classified either way.
677 //return WIND_EVEN_ODD;
678 return WIND_NON_ZERO;
679 }
680
681 public boolean isDone() {
682 return (prevcurve == null && thiscurve == null);
683 }
684
685 public void next() {
686 if (prevcurve != null) {
687 prevcurve = null;
688 } else {
689 prevcurve = thiscurve;
690 index++;
691 if (index < curves.size()) {
692 thiscurve = (Curve) curves.get(index);
693 if (thiscurve.getOrder() != 0 &&
694 prevcurve.getX1() == thiscurve.getX0() &&
695 prevcurve.getY1() == thiscurve.getY0())
696 {
697 prevcurve = null;
698 }
699 } else {
700 thiscurve = null;
701 }
702 }
703 }
704
705 public int currentSegment(float coords[]) {
706 double dcoords[] = new double[6];
707 int segtype = currentSegment(dcoords);
708 int numpoints = (segtype == SEG_CLOSE ? 0
709 : (segtype == SEG_QUADTO ? 2
710 : (segtype == SEG_CUBICTO ? 3
711 : 1)));
712 for (int i = 0; i < numpoints * 2; i++) {
|
1 /*
2 * Copyright (c) 1998, 2014, 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
80 * which encloses no area (even when "closed") produces an
81 * empty <code>Area</code>. A common example of this issue
82 * is that producing an <code>Area</code> from a line will
83 * be empty since the line encloses no area. An empty
84 * <code>Area</code> will iterate no geometry in its
85 * <code>PathIterator</code> objects.
86 * <li>A self-intersecting <code>Shape</code> may be split into
87 * two (or more) sub-paths each enclosing one of the
88 * non-intersecting portions of the original path.
89 * <li>An <code>Area</code> may take more path segments to
90 * describe the same geometry even when the original
91 * outline is simple and obvious. The analysis that the
92 * <code>Area</code> class must perform on the path may
93 * not reflect the same concepts of "simple and obvious"
94 * as a human being perceives.
95 * </ul>
96 *
97 * @since 1.2
98 */
99 public class Area implements Shape, Cloneable {
100 private static Vector<Curve> EmptyCurves = new Vector<>();
101
102 private Vector<Curve> curves;
103
104 /**
105 * Default constructor which creates an empty area.
106 * @since 1.2
107 */
108 public Area() {
109 curves = EmptyCurves;
110 }
111
112 /**
113 * The <code>Area</code> class creates an area geometry from the
114 * specified {@link Shape} object. The geometry is explicitly
115 * closed, if the <code>Shape</code> is not already closed. The
116 * fill rule (even-odd or winding) specified by the geometry of the
117 * <code>Shape</code> is used to determine the resulting enclosed area.
118 * @param s the <code>Shape</code> from which the area is constructed
119 * @throws NullPointerException if <code>s</code> is null
120 * @since 1.2
121 */
122 public Area(Shape s) {
123 if (s instanceof Area) {
124 curves = ((Area) s).curves;
125 } else {
126 curves = pathToCurves(s.getPathIterator(null));
127 }
128 }
129
130 private static Vector<Curve> pathToCurves(PathIterator pi) {
131 Vector<Curve> curves = new Vector<>();
132 int windingRule = pi.getWindingRule();
133 // coords array is big enough for holding:
134 // coordinates returned from currentSegment (6)
135 // OR
136 // two subdivided quadratic curves (2+4+4=10)
137 // AND
138 // 0-1 horizontal splitting parameters
139 // OR
140 // 2 parametric equation derivative coefficients
141 // OR
142 // three subdivided cubic curves (2+6+6+6=20)
143 // AND
144 // 0-2 horizontal splitting parameters
145 // OR
146 // 3 parametric equation derivative coefficients
147 double coords[] = new double[23];
148 double movx = 0, movy = 0;
149 double curx = 0, cury = 0;
150 double newx, newy;
151 while (!pi.isDone()) {
317 * ###### ###### ###### ######
318 * #### #### #### ####
319 * ## ## ## ##
320 * </pre>
321 * @param rhs the <code>Area</code> to be exclusive ORed with this
322 * <code>Area</code>.
323 * @throws NullPointerException if <code>rhs</code> is null
324 * @since 1.2
325 */
326 public void exclusiveOr(Area rhs) {
327 curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
328 invalidateBounds();
329 }
330
331 /**
332 * Removes all of the geometry from this <code>Area</code> and
333 * restores it to an empty area.
334 * @since 1.2
335 */
336 public void reset() {
337 curves = new Vector<>();
338 invalidateBounds();
339 }
340
341 /**
342 * Tests whether this <code>Area</code> object encloses any area.
343 * @return <code>true</code> if this <code>Area</code> object
344 * represents an empty area; <code>false</code> otherwise.
345 * @since 1.2
346 */
347 public boolean isEmpty() {
348 return (curves.size() == 0);
349 }
350
351 /**
352 * Tests whether this <code>Area</code> consists entirely of
353 * straight edged polygonal geometry.
354 * @return <code>true</code> if the geometry of this
355 * <code>Area</code> consists entirely of line segments;
356 * <code>false</code> otherwise.
357 * @since 1.2
358 */
359 public boolean isPolygonal() {
360 Enumeration<Curve> enum_ = curves.elements();
361 while (enum_.hasMoreElements()) {
362 if (enum_.nextElement().getOrder() > 1) {
363 return false;
364 }
365 }
366 return true;
367 }
368
369 /**
370 * Tests whether this <code>Area</code> is rectangular in shape.
371 * @return <code>true</code> if the geometry of this
372 * <code>Area</code> is rectangular in shape; <code>false</code>
373 * otherwise.
374 * @since 1.2
375 */
376 public boolean isRectangular() {
377 int size = curves.size();
378 if (size == 0) {
379 return true;
380 }
381 if (size > 3) {
382 return false;
383 }
384 Curve c1 = curves.get(1);
385 Curve c2 = curves.get(2);
386 if (c1.getOrder() != 1 || c2.getOrder() != 1) {
387 return false;
388 }
389 if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
390 return false;
391 }
392 if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
393 // One might be able to prove that this is impossible...
394 return false;
395 }
396 return true;
397 }
398
399 /**
400 * Tests whether this <code>Area</code> is comprised of a single
401 * closed subpath. This method returns <code>true</code> if the
402 * path contains 0 or 1 subpaths, or <code>false</code> if the path
403 * contains more than 1 subpath. The subpaths are counted by the
404 * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments
405 * that appear in the path.
406 * @return <code>true</code> if the <code>Area</code> is comprised
407 * of a single basic geometry; <code>false</code> otherwise.
408 * @since 1.2
409 */
410 public boolean isSingular() {
411 if (curves.size() < 3) {
412 return true;
413 }
414 Enumeration<Curve> enum_ = curves.elements();
415 enum_.nextElement(); // First Order0 "moveto"
416 while (enum_.hasMoreElements()) {
417 if (enum_.nextElement().getOrder() == 0) {
418 return false;
419 }
420 }
421 return true;
422 }
423
424 private Rectangle2D cachedBounds;
425 private void invalidateBounds() {
426 cachedBounds = null;
427 }
428 private Rectangle2D getCachedBounds() {
429 if (cachedBounds != null) {
430 return cachedBounds;
431 }
432 Rectangle2D r = new Rectangle2D.Double();
433 if (curves.size() > 0) {
434 Curve c = curves.get(0);
435 // First point is always an order 0 curve (moveto)
436 r.setRect(c.getX0(), c.getY0(), 0, 0);
437 for (int i = 1; i < curves.size(); i++) {
438 curves.get(i).enlarge(r);
439 }
440 }
441 return (cachedBounds = r);
442 }
443
444 /**
445 * Returns a high precision bounding {@link Rectangle2D} that
446 * completely encloses this <code>Area</code>.
447 * <p>
448 * The Area class will attempt to return the tightest bounding
449 * box possible for the Shape. The bounding box will not be
450 * padded to include the control points of curves in the outline
451 * of the Shape, but should tightly fit the actual geometry of
452 * the outline itself.
453 * @return the bounding <code>Rectangle2D</code> for the
454 * <code>Area</code>.
455 * @since 1.2
456 */
457 public Rectangle2D getBounds2D() {
458 return getCachedBounds().getBounds2D();
490 /**
491 * Tests whether the geometries of the two <code>Area</code> objects
492 * are equal.
493 * This method will return false if the argument is null.
494 * @param other the <code>Area</code> to be compared to this
495 * <code>Area</code>
496 * @return <code>true</code> if the two geometries are equal;
497 * <code>false</code> otherwise.
498 * @since 1.2
499 */
500 public boolean equals(Area other) {
501 // REMIND: A *much* simpler operation should be possible...
502 // Should be able to do a curve-wise comparison since all Areas
503 // should evaluate their curves in the same top-down order.
504 if (other == this) {
505 return true;
506 }
507 if (other == null) {
508 return false;
509 }
510 Vector<Curve> c = new AreaOp.XorOp().calculate(this.curves, other.curves);
511 return c.isEmpty();
512 }
513
514 /**
515 * Transforms the geometry of this <code>Area</code> using the specified
516 * {@link AffineTransform}. The geometry is transformed in place, which
517 * permanently changes the enclosed area defined by this object.
518 * @param t the transformation used to transform the area
519 * @throws NullPointerException if <code>t</code> is null
520 * @since 1.2
521 */
522 public void transform(AffineTransform t) {
523 if (t == null) {
524 throw new NullPointerException("transform must not be null");
525 }
526 // REMIND: A simpler operation can be performed for some types
527 // of transform.
528 curves = pathToCurves(getPathIterator(t));
529 invalidateBounds();
530 }
538 * the new <code>Area</code>
539 * @throws NullPointerException if <code>t</code> is null
540 * @return a new <code>Area</code> object representing the transformed
541 * geometry.
542 * @since 1.2
543 */
544 public Area createTransformedArea(AffineTransform t) {
545 Area a = new Area(this);
546 a.transform(t);
547 return a;
548 }
549
550 /**
551 * {@inheritDoc}
552 * @since 1.2
553 */
554 public boolean contains(double x, double y) {
555 if (!getCachedBounds().contains(x, y)) {
556 return false;
557 }
558 Enumeration<Curve> enum_ = curves.elements();
559 int crossings = 0;
560 while (enum_.hasMoreElements()) {
561 Curve c = enum_.nextElement();
562 crossings += c.crossingsFor(x, y);
563 }
564 return ((crossings & 1) == 1);
565 }
566
567 /**
568 * {@inheritDoc}
569 * @since 1.2
570 */
571 public boolean contains(Point2D p) {
572 return contains(p.getX(), p.getY());
573 }
574
575 /**
576 * {@inheritDoc}
577 * @since 1.2
578 */
579 public boolean contains(double x, double y, double w, double h) {
580 if (w < 0 || h < 0) {
581 return false;
641 * object is unchanged.
642 * @param at an optional <code>AffineTransform</code> to be
643 * applied to the coordinates as they are returned in the
644 * iteration, or <code>null</code> if untransformed coordinates
645 * are desired
646 * @param flatness the maximum amount that the control points
647 * for a given curve can vary from colinear before a subdivided
648 * curve is replaced by a straight line connecting the end points
649 * @return the <code>PathIterator</code> object that returns the
650 * geometry of the outline of this <code>Area</code>, one segment
651 * at a time.
652 * @since 1.2
653 */
654 public PathIterator getPathIterator(AffineTransform at, double flatness) {
655 return new FlatteningPathIterator(getPathIterator(at), flatness);
656 }
657 }
658
659 class AreaIterator implements PathIterator {
660 private AffineTransform transform;
661 private Vector<Curve> curves;
662 private int index;
663 private Curve prevcurve;
664 private Curve thiscurve;
665
666 public AreaIterator(Vector<Curve> curves, AffineTransform at) {
667 this.curves = curves;
668 this.transform = at;
669 if (curves.size() >= 1) {
670 thiscurve = curves.get(0);
671 }
672 }
673
674 public int getWindingRule() {
675 // REMIND: Which is better, EVEN_ODD or NON_ZERO?
676 // The paths calculated could be classified either way.
677 //return WIND_EVEN_ODD;
678 return WIND_NON_ZERO;
679 }
680
681 public boolean isDone() {
682 return (prevcurve == null && thiscurve == null);
683 }
684
685 public void next() {
686 if (prevcurve != null) {
687 prevcurve = null;
688 } else {
689 prevcurve = thiscurve;
690 index++;
691 if (index < curves.size()) {
692 thiscurve = curves.get(index);
693 if (thiscurve.getOrder() != 0 &&
694 prevcurve.getX1() == thiscurve.getX0() &&
695 prevcurve.getY1() == thiscurve.getY0())
696 {
697 prevcurve = null;
698 }
699 } else {
700 thiscurve = null;
701 }
702 }
703 }
704
705 public int currentSegment(float coords[]) {
706 double dcoords[] = new double[6];
707 int segtype = currentSegment(dcoords);
708 int numpoints = (segtype == SEG_CLOSE ? 0
709 : (segtype == SEG_QUADTO ? 2
710 : (segtype == SEG_CUBICTO ? 3
711 : 1)));
712 for (int i = 0; i < numpoints * 2; i++) {
|