001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.perseus.model;
027:
028: import com.sun.perseus.platform.MathSupport;
029:
030: import com.sun.perseus.j2d.Box;
031: import com.sun.perseus.j2d.PathSupport;
032: import com.sun.perseus.j2d.Transform;
033: import com.sun.perseus.j2d.GraphicsProperties;
034: import com.sun.perseus.j2d.RenderGraphics;
035:
036: import com.sun.perseus.util.SVGConstants;
037:
038: import org.w3c.dom.DOMException;
039:
040: /**
041: * An <code>Ellipse</code> node models an SVG <code><ellipse></code>
042: * or an <code><circle></code> element.
043: * <br />
044: * A negative radius along the x or y axis is illegal. A null radius
045: * along the x or y axis disables rendering of the ellipes.
046: * <br />
047: * If the <code>Ellipse</code> is a circle, then setting the x radius
048: * also sets the y radius (to the same value) and setting the y radius
049: * also sets the x radius (to the same value).
050: *
051: * @version $Id: Ellipse.java,v 1.11 2006/06/29 10:47:31 ln156897 Exp $
052: */
053: public class Ellipse extends AbstractShapeNode {
054: /**
055: * The rx and ry attributes are required for an <ellipse>
056: */
057: static final String[] ELLIPSE_REQUIRED_TRAITS = {
058: SVGConstants.SVG_RX_ATTRIBUTE,
059: SVGConstants.SVG_RY_ATTRIBUTE };
060:
061: /**
062: * The r trait is required for a <circle>
063: */
064: static final String[] CIRCLE_REQUIRED_TRAITS = { SVGConstants.SVG_R_ATTRIBUTE };
065:
066: /**
067: * If true, the x and y radii are constrained to always be the
068: * same value.
069: */
070: protected boolean isCircle = false;
071:
072: /**
073: * This ellipe's origin along the x-axis
074: */
075: protected float x;
076:
077: /**
078: * The ellipse's origin along the y-axis
079: */
080: protected float y;
081:
082: /**
083: * The ellipse's width
084: */
085: protected float width;
086:
087: /**
088: * The ellipse's height.
089: */
090: protected float height;
091:
092: /**
093: * Constructor.
094: *
095: * @param ownerDocument this element's owner <code>DocumentNode</code>
096: */
097: public Ellipse(final DocumentNode ownerDocument) {
098: this (ownerDocument, false);
099: }
100:
101: /**
102: * Constructor.
103: *
104: * @param ownerDocument this element's owner <code>DocumentNode</code>
105: * @param isCircle if true, the x and y radii will be constrained to
106: * always have the same value.
107: */
108: public Ellipse(final DocumentNode ownerDocument,
109: final boolean isCircle) {
110: super (ownerDocument);
111: this .isCircle = isCircle;
112:
113: // Initially, the ellipse's width and height are zero, so we
114: // set the corresponding bits accordingly.
115: canRenderState |= CAN_RENDER_ZERO_WIDTH_BIT;
116: canRenderState |= CAN_RENDER_ZERO_HEIGHT_BIT;
117: }
118:
119: /**
120: * @return the SVGConstants.SVG_CIRCLE_TAG if isCircle is true,
121: * SVGConstants.SVG_ELLIPSE_TAG otherwise.
122: */
123:
124: public String getLocalName() {
125: if (isCircle) {
126: return SVGConstants.SVG_CIRCLE_TAG;
127: } else {
128: return SVGConstants.SVG_ELLIPSE_TAG;
129: }
130: }
131:
132: /**
133: * Used by <code>DocumentNode</code> to create a new instance from
134: * a prototype <code>Ellipse</code>.
135: *
136: * @param doc the <code>DocumentNode</code> for which a new node is
137: * should be created.
138: * @return a new <code>Ellipse</code> for the requested document.
139: */
140: public ElementNode newInstance(final DocumentNode doc) {
141: return new Ellipse(doc, isCircle);
142: }
143:
144: /**
145: * @return this ellipse's x-axis center
146: */
147: public float getCx() {
148: return x + width / 2;
149: }
150:
151: /**
152: * @return this ellipse's y-axis center
153: */
154: public float getCy() {
155: return y + height / 2;
156: }
157:
158: /**
159: * @return this ellipse's x-axis radius
160: */
161: public float getRx() {
162: return width / 2;
163: }
164:
165: /**
166: * @return this ellipse's y-axis radius
167: */
168: public float getRy() {
169: return height / 2;
170: }
171:
172: /**
173: * @param cx x-axis ellipse center coordinate
174: */
175: public void setCx(final float cx) {
176: float newCx = cx - width / 2;
177: if (newCx == x) {
178: return;
179: }
180: modifyingNode();
181: x = newCx;
182: renderingDirty();
183: modifiedNode();
184: }
185:
186: /**
187: * @param cy y-axis ellipse coordinate
188: */
189: public void setCy(final float cy) {
190: float newCy = cy - height / 2;
191: if (y == newCy) {
192: return;
193: }
194: modifyingNode();
195: y = newCy;
196: renderingDirty();
197: modifiedNode();
198: }
199:
200: /**
201: * @param rx the new x-axis radius
202: */
203: public void setRx(final float rx) {
204: if (rx < 0) {
205: throw new IllegalArgumentException();
206: }
207:
208: if (width == rx * 2) {
209: return;
210: }
211:
212: modifyingNode();
213: renderingDirty();
214:
215: float cx = getCx();
216: width = rx * 2;
217: x = cx - rx;
218:
219: computeCanRenderWidthBit(width);
220:
221: if (isCircle) {
222: float cy = getCy();
223: height = rx * 2;
224: y = cy - rx;
225: computeCanRenderHeightBit(width);
226: }
227:
228: modifiedNode();
229: }
230:
231: /**
232: * @param ry the new y-axis radius
233: */
234: public void setRy(final float ry) {
235: if (ry < 0) {
236: throw new IllegalArgumentException();
237: }
238:
239: if (height == ry * 2) {
240: return;
241: }
242:
243: modifyingNode();
244: renderingDirty();
245:
246: float cy = getCy();
247: height = ry * 2;
248: y = cy - ry;
249:
250: if (isCircle) {
251: float cx = getCx();
252: width = ry * 2;
253: x = cx - ry;
254: }
255:
256: computeCanRenderHeightBit(height);
257: modifiedNode();
258: }
259:
260: /**
261: * @param rg the RenderGraphics on which to fill the shape.
262: */
263: public void fillShape(final RenderGraphics rg) {
264: rg.fillOval(x, y, width, height);
265: }
266:
267: /**
268: * @param rg the RenderGraphics on which to draw the shape.
269: */
270: public void drawShape(final RenderGraphics rg) {
271: rg.drawOval(x, y, width, height);
272: }
273:
274: /**
275: * @param x the hit point coordinate along the x-axis, in user space.
276: * @param y the hit point coordinate along the y-axis, in user space.
277: * @param fillRule the fillRule to apply when testing for containment.
278: * @return true if the hit point is contained within the shape.
279: */
280: public boolean contains(final float x, final float y,
281: final int fillRule) {
282: // Normalize the coordinates compared to the ellipse
283: // having a center at 0,0 and a radius of 0.5.
284: float normx = (x - this .x) / width - 0.5f;
285: float normy = (y - this .y) / height - 0.5f;
286: return (normx * normx + normy * normy) < 0.25f;
287: }
288:
289: /**
290: * Returns the stroked shape, using the given stroke properties.
291: *
292: * @param gp the <code>GraphicsProperties</code> defining the rendering
293: * context.
294: * @return the shape's stroked path.
295: */
296: Object getStrokedPath(final GraphicsProperties gp) {
297: return PathSupport.getStrokedEllipse(x, y, width, height, gp);
298: }
299:
300: /**
301: * Supported traits: cx, cy, rx, ry
302: *
303: * @param traitName the name of the trait which the element may support.
304: * @return true if this element supports the given trait in one of the
305: * trait accessor methods.
306: */
307: boolean supportsTrait(final String traitName) {
308: if (SVGConstants.SVG_CX_ATTRIBUTE == traitName
309: || SVGConstants.SVG_CY_ATTRIBUTE == traitName) {
310: return true;
311: }
312:
313: if (isCircle && SVGConstants.SVG_R_ATTRIBUTE == traitName) {
314: return true;
315: } else if (!isCircle
316: && (SVGConstants.SVG_RX_ATTRIBUTE == traitName || SVGConstants.SVG_RY_ATTRIBUTE == traitName)) {
317: return true;
318: } else {
319: return super .supportsTrait(traitName);
320: }
321: }
322:
323: /**
324: * @return an array of traits that are required by this element.
325: */
326: public String[] getRequiredTraits() {
327: if (isCircle) {
328: return CIRCLE_REQUIRED_TRAITS;
329: } else {
330: return ELLIPSE_REQUIRED_TRAITS;
331: }
332: }
333:
334: /**
335: * Supported traits: cx, cy, r, rx, ry
336: *
337: * @param name the requested trait name.
338: * @return the requested trait value, as a string.
339: *
340: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
341: * trait is not supported on this element or null.
342: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
343: * trait's computed value cannot be converted to a String (SVG Tiny only).
344: */
345: public String getTraitImpl(String name) throws DOMException {
346: if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
347: return Float.toString(getCx());
348: } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
349: return Float.toString(getCy());
350: } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name)
351: || (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
352: return Float.toString(getRx());
353: } else if (!isCircle && SVGConstants.SVG_RY_ATTRIBUTE == name) {
354: return Float.toString(getRy());
355: } else {
356: return super .getTraitImpl(name);
357: }
358: }
359:
360: /**
361: * Supported traits: cx, cy, rx, ry
362: *
363: * @param name the requested trait name.
364: * @param return the requested trait value.
365: *
366: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
367: * trait is not supported on this element or null.
368: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
369: * trait's computed value cannot be converted to a float
370: * @throws SecurityException if the application does not have the necessary
371: * privilege rights to access this (SVG) content.
372: */
373: float getFloatTraitImpl(final String name) throws DOMException {
374: if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
375: return getCx();
376: } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
377: return getCy();
378: } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name)
379: || (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
380: return getRx();
381: } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
382: return getRy();
383: } else {
384: return super .getFloatTraitImpl(name);
385: }
386: }
387:
388: /**
389: * @param traitName the trait name.
390: */
391: TraitAnim createTraitAnimImpl(final String traitName) {
392: if (SVGConstants.SVG_CX_ATTRIBUTE == traitName
393: || SVGConstants.SVG_CY_ATTRIBUTE == traitName
394: || SVGConstants.SVG_RX_ATTRIBUTE == traitName
395: || SVGConstants.SVG_RY_ATTRIBUTE == traitName
396: || SVGConstants.SVG_R_ATTRIBUTE == traitName) {
397: return new FloatTraitAnim(this , traitName, TRAIT_TYPE_FLOAT);
398: } else {
399: return super .createTraitAnimImpl(traitName);
400: }
401: }
402:
403: /**
404: * Set the trait value as float.
405: *
406: * @param name the trait's name.
407: * @param value the trait's value.
408: *
409: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
410: * trait is not supported on this element.
411: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
412: * trait's value cannot be specified as a float
413: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
414: * value is an invalid value for the given trait.
415: */
416: void setFloatArrayTrait(final String name, final float[][] value)
417: throws DOMException {
418: if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
419: setCx(value[0][0]);
420: } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
421: setCy(value[0][0]);
422: } else if (SVGConstants.SVG_RX_ATTRIBUTE == name) {
423: checkPositive(name, value[0][0]);
424: setRx(value[0][0]);
425: } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
426: checkPositive(name, value[0][0]);
427: setRy(value[0][0]);
428: } else if (SVGConstants.SVG_R_ATTRIBUTE == name) {
429: checkPositive(name, value[0][0]);
430: setRx(value[0][0]);
431: } else {
432: super .setFloatArrayTrait(name, value);
433: }
434: }
435:
436: /**
437: * Validates the input trait value.
438: *
439: * @param traitName the name of the trait to be validated.
440: * @param value the value to be validated
441: * @param reqNamespaceURI the namespace of the element requesting
442: * validation.
443: * @param reqLocalName the local name of the element requesting validation.
444: * @param reqTraitNamespace the namespace of the trait which has the values
445: * value on the requesting element.
446: * @param reqTraitName the name of the trait which has the values value on
447: * the requesting element.
448: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
449: * value is incompatible with the given trait.
450: */
451: public float[][] validateFloatArrayTrait(final String traitName,
452: final String value, final String reqNamespaceURI,
453: final String reqLocalName, final String reqTraitNamespace,
454: final String reqTraitName) throws DOMException {
455: if (SVGConstants.SVG_CX_ATTRIBUTE == traitName
456: || SVGConstants.SVG_CY_ATTRIBUTE == traitName) {
457: return toAnimatedFloatArray(parseFloatTrait(traitName,
458: value));
459: } else if (SVGConstants.SVG_RX_ATTRIBUTE == traitName
460: || SVGConstants.SVG_R_ATTRIBUTE == traitName
461: || SVGConstants.SVG_RY_ATTRIBUTE == traitName) {
462: return toAnimatedFloatArray(parsePositiveFloatTrait(
463: traitName, value));
464: } else {
465: return super .validateFloatArrayTrait(traitName, value,
466: reqNamespaceURI, reqLocalName, reqTraitNamespace,
467: reqTraitName);
468: }
469: }
470:
471: /**
472: * Supported traits: cx, cy, rx, ry
473: *
474: * @param name the trait name.
475: * @param value the trait's string value.
476: *
477: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
478: * trait is not supported on this element or null.
479: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
480: * trait's value cannot be specified as a String
481: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
482: * value is an invalid value for the given trait or null.
483: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
484: * attempt is made to change readonly trait.
485: */
486: public void setTraitImpl(final String name, final String value)
487: throws DOMException {
488: if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
489: setCx(parseFloatTrait(name, value));
490: } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
491: setCy(parseFloatTrait(name, value));
492: } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name)
493: || (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
494: setRx(parsePositiveFloatTrait(name, value));
495: } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
496: setRy(parsePositiveFloatTrait(name, value));
497: } else {
498: super .setTraitImpl(name, value);
499: }
500: }
501:
502: /**
503: * Supported traits: cx, cy, rx, ry
504: *
505: * @param name the trait name.
506: * @param value the trait float value.
507: *
508: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
509: * trait is not supported on this element.
510: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
511: * trait's value cannot be specified as a float
512: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
513: * value is an invalid value for the given trait.
514: * @throws SecurityException if the application does not have the necessary
515: * privilege rights to access this (SVG) content.
516: */
517: public void setFloatTraitImpl(final String name, final float value)
518: throws DOMException {
519: if (SVGConstants.SVG_CX_ATTRIBUTE == name) {
520: setCx(value);
521: } else if (SVGConstants.SVG_CY_ATTRIBUTE == name) {
522: setCy(value);
523: } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name)
524: || (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
525: checkPositive(name, value);
526: setRx(value);
527: } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
528: checkPositive(name, value);
529: setRy(value);
530: } else {
531: super .setFloatTraitImpl(name, value);
532: }
533: }
534:
535: /**
536: * @param name the name of the trait to convert.
537: * @param value the float trait value to convert.
538: */
539: String toStringTrait(final String name, final float[][] value) {
540: if (SVGConstants.SVG_CX_ATTRIBUTE == name
541: || SVGConstants.SVG_CY_ATTRIBUTE == name) {
542: return Float.toString(value[0][0]);
543: } else if ((!isCircle && SVGConstants.SVG_RX_ATTRIBUTE == name)
544: || (isCircle && SVGConstants.SVG_R_ATTRIBUTE == name)) {
545: return Float.toString(value[0][0]);
546: } else if (SVGConstants.SVG_RY_ATTRIBUTE == name) {
547: return Float.toString(value[0][0]);
548: } else {
549: return super .toStringTrait(name, value);
550: }
551: }
552:
553: /**
554: * @param bbox the bounding box to which this node's bounding box should be
555: * appended. That bounding box is in the target coordinate space. It
556: * may be null, in which case this node should create a new one.
557: * @param t the transform from the node coordinate system to the coordinate
558: * system into which the bounds should be computed.
559: * @return the bounding box of this node, in the target coordinate space,
560: */
561: Box addNodeBBox(final Box bbox, final Transform t) {
562:
563: float rx = getRx();
564: float ry = getRy();
565:
566: if (t == null
567: || (t.getComponent(1) == 0 && t.getComponent(2) == 0)
568: || width == 0 || height == 0) {
569: // If we are dealing with no transform or if the
570: // transform is a simple zoom/pan
571: return addTransformedBBox(bbox, x, y, width, height, t);
572: }
573:
574: //
575: // The ellipse's equations are:
576: //
577: // x = cx + rx * cos (t)
578: // y = cy + ry * sin (t)
579: //
580: // When transformed through t, the equation becomes:
581: //
582: // [x'] [m0 m2 m4] [x]
583: // [y'] = [m1 m3 m5] * [y]
584: // [1 ] [ 0 0 1] [1]
585: //
586: // x' = m0 * x + m2 * y + m4
587: // y' = m1 * x + m3 * y + m5
588: //
589: // x' = m0 * cx + m0 * rx * cos(t) + m2 * cy + m2 * ry * sin(t) + m4
590: // y' = m1 * cx + m1 * rx * cos(t) + m3 * cy + m3 * ry * sin(t) + m5
591: //
592: // x' = m0 * cx + m2 * cy + m4 + m0 * rx * cos(t) + m2 * ry * sin(t)
593: // y' = m1 * cx + m3 * cy + m5 + m1 * rx * cos(t) + m3 * ry * sin(t)
594: //
595: // fx = m0 * cx + m2 * cy + m4
596: // fy = m1 * cx + m3 * cy + m5
597: //
598: // vx(t) = m0 * rx * cos(t) + m2 * ry * sin(t)
599: // vy(t) = m1 * rx * cos(t) + m3 * ry * sin(t)
600: //
601: // The maximum and minimum are computed by the derivative functions:
602: //
603: // vx(t)' = -m0 * rx * sin(t) + m2 * ry * cos(t)
604: // vy(t)' = -m1 * rx * sin(t) + m3 * ry * cos(t)
605: //
606: // vx(t)' = 0 for tan(t) = sin(t) / cos(t)
607: // = m2 * ry / m0 * rx
608: // vy(t)' = 0 for tan(t) = sin(t) / cos(t)
609: // = m3 * ry / m1 * rx
610: //
611: float m0 = t.getComponent(0);
612: float m1 = t.getComponent(1);
613: float m2 = t.getComponent(2);
614: float m3 = t.getComponent(3);
615: float m4 = t.getComponent(4);
616: float m5 = t.getComponent(5);
617: float cx = getCx();
618: float cy = getCy();
619:
620: float m0rx = m0 * rx;
621: float m2ry = m2 * ry;
622: float m1rx = m1 * rx;
623: float m3ry = m3 * ry;
624:
625: float theta = MathSupport.atan2(m2ry, m0rx);
626: float theta2 = theta + MathSupport.PI;
627: float cost = MathSupport.cos(theta);
628: float sint = MathSupport.sin(theta);
629: float cost2 = MathSupport.cos(theta2);
630: float sint2 = MathSupport.sin(theta2);
631:
632: float maxX = m0rx * cost + m2ry * sint;
633: float minX = m0rx * cost2 + m2ry * sint2;
634: float width = maxX - minX;
635:
636: if (minX > maxX) {
637: minX = maxX;
638: width = -width;
639: }
640:
641: theta = MathSupport.atan2(m3ry, m1rx);
642: theta2 = theta + MathSupport.PI;
643: cost = MathSupport.cos(theta);
644: sint = MathSupport.sin(theta);
645: cost2 = MathSupport.cos(theta2);
646: sint2 = MathSupport.sin(theta2);
647:
648: float maxY = m1rx * cost + m3ry * sint;
649: float minY = m1rx * cost2 + m3ry * sint2;
650: float height = maxY - minY;
651:
652: if (minY > maxY) {
653: minY = maxY;
654: height = -height;
655: }
656:
657: float fx = m0 * cx + m2 * cy + m4;
658: float fy = m1 * cx + m3 * cy + m5;
659:
660: // (fx, fy) is the upper left corner of the bounding box
661: return addBBox(bbox, fx + minX, fy + minY, width, height);
662: }
663:
664: }
|