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.j2d.GraphicsProperties;
029: import com.sun.perseus.j2d.RenderGraphics;
030:
031: import com.sun.perseus.util.SVGConstants;
032:
033: import org.w3c.dom.DOMException;
034:
035: import org.w3c.dom.svg.SVGPath;
036: import org.w3c.dom.svg.SVGRect;
037:
038: import com.sun.perseus.j2d.Box;
039: import com.sun.perseus.j2d.Transform;
040: import com.sun.perseus.j2d.Path;
041: import com.sun.perseus.j2d.PathSupport;
042:
043: /**
044: * A <tt>ShapesNode</tt> is an <tt>AbstractShapeNode</tt>
045: * which draws a <tt>java.awt.geom.GeneralPath</tt>.
046: * <br />
047: * Note that the <tt>paint</tt> method throws a <tt>NullPointerException</tt>
048: * when called before setting the <tt>shape</tt> property to a non-null
049: * value.
050: * <br />
051: *
052: * @version $Id: ShapeNode.java,v 1.17 2006/06/29 10:47:34 ln156897 Exp $
053: */
054: public class ShapeNode extends AbstractShapeNode {
055: /**
056: * EMPTY_PATH is used initially by the ShapeNode class.
057: * This constant is used so that a new ShapeNode, right upon creation,
058: * may be rendered. The only place where the path is mutated is in
059: * the animation code. But the animation can only work if the
060: * path has real data. Therefore, it is safe to use this final static
061: * constant to initialize all ShapeNode instances (and avoid instantiating
062: * Path objects that are always tossed away later on).
063: */
064: static final Path EMPTY_PATH = new Path();
065:
066: /**
067: * The d attribute is required on <path>
068: */
069: static final String[] PATH_REQUIRED_TRAITS = { SVGConstants.SVG_D_ATTRIBUTE };
070:
071: /**
072: * The points attribute is required on <polygon> and <polyline>
073: */
074: static final String[] POLY_ALL_REQUIRED_TRAITS = { SVGConstants.SVG_POINTS_ATTRIBUTE };
075:
076: /**
077: * The Path painted by this node. Should _never_ be set to null.
078: */
079: protected Path path = EMPTY_PATH;
080:
081: /**
082: * The path's local name. One of
083: * SVGConstants.SVG_PATH_TAG, SVGConstants.SVG_POLYGON_TAG,
084: * or SVGConstants.SVG_POLYLINE_TAG.
085: */
086: protected String localName;
087:
088: /**
089: * Constructor.
090: *
091: * @param ownerDocument this element's owner <code>DocumentNode</code>
092: */
093: public ShapeNode(final DocumentNode ownerDocument) {
094: this (ownerDocument, SVGConstants.SVG_PATH_TAG);
095:
096: // The ShapeNode initially has an empty path, so that is reflected in
097: // the canRenderState.
098: canRenderState |= CAN_RENDER_EMPTY_PATH_BIT;
099: }
100:
101: /**
102: * Constructs a new ShapeNode to represent an path, polyline,
103: * or polygon (depending on the localName value).
104: *
105: * @param ownerDocument this element's owner <code>DocumentNode</code>
106: * @param localName the element's local name. One of
107: * SVGConstants.SVG_PATH_TAG, SVGConstants.SVG_POLYGON_TAG,
108: * or SVGConstants.SVG_POLYLINE_TAG.
109: */
110: public ShapeNode(final DocumentNode ownerDocument,
111: final String localName) {
112: super (ownerDocument);
113:
114: if (SVGConstants.SVG_POLYLINE_TAG == localName
115: || SVGConstants.SVG_POLYGON_TAG == localName
116: || SVGConstants.SVG_PATH_TAG == localName) {
117: this .localName = localName;
118: } else {
119: throw new IllegalArgumentException();
120: }
121: }
122:
123: /**
124: * @return the SVGConstants.SVG_PATH_TAG value
125: */
126: public String getLocalName() {
127: return localName;
128: }
129:
130: /**
131: * Used by <code>DocumentNode</code> to create a new instance from
132: * a prototype <code>SVG</code>.
133: *
134: * @param doc the <code>DocumentNode</code> for which a new node is
135: * should be created.
136: * @return a new <code>SVG</code> for the requested document.
137: */
138: public ElementNode newInstance(final DocumentNode doc) {
139: return new ShapeNode(doc, localName);
140: }
141:
142: /**
143: * @return the Path drawn by this node
144: */
145: public Path getShape() {
146: return path;
147: }
148:
149: /**
150: * @param newPath the new path for this node
151: */
152: public void setPath(final Path newPath) {
153: if (equal(newPath, path)) {
154: return;
155: }
156:
157: modifyingNode();
158: renderingDirty();
159: if (newPath != null) {
160: this .path = newPath;
161: } else {
162: this .path = new Path();
163: }
164: computeCanRenderEmptyPathBit(this .path);
165: modifiedNode();
166: }
167:
168: /**
169: * @param rg the RenderGraphics on which to fill the shape.
170: */
171: public void fillShape(final RenderGraphics rg) {
172: rg.fill(path);
173: }
174:
175: /**
176: * @param rg the RenderGraphics on which to draw the shape.
177: */
178: public void drawShape(final RenderGraphics rg) {
179: rg.draw(path);
180: }
181:
182: /**
183: * @param x the hit point coordinate along the x-axis, in user space.
184: * @param y the hit point coordinate along the y-axis, in user space.
185: * @param fillRule the fillRule to apply when testing for containment.
186: * @return true if the hit point is contained within the shape.
187: */
188: public boolean contains(final float x, final float y,
189: final int fillRule) {
190: return PathSupport.isHit(path, fillRule, x, y);
191: }
192:
193: /**
194: * Returns the stroked shape, using the given stroke properties.
195: *
196: * @param gp the <code>GraphicsProperties</code> defining the rendering
197: * context.
198: * @return the shape's stroked path.
199: */
200: Object getStrokedPath(final GraphicsProperties gp) {
201: return PathSupport.getStrokedPath(path, gp);
202: }
203:
204: /**
205: * ShapeNode handles the 'd' trait.
206: *
207: * @param traitName the name of the trait which the element may support.
208: * @return true if this element supports the given trait in one of the
209: * trait accessor methods.
210: */
211: boolean supportsTrait(final String traitName) {
212: if (SVGConstants.SVG_PATH_TAG == localName) {
213: if (SVGConstants.SVG_D_ATTRIBUTE == traitName) {
214: return true;
215: }
216: } else if (SVGConstants.SVG_POINTS_ATTRIBUTE == traitName) {
217: // For polygon and polyline, the points trait is
218: // supported.
219: return true;
220: }
221:
222: return super .supportsTrait(traitName);
223: }
224:
225: /**
226: * @return an array of traits that are required by this element.
227: */
228: public String[] getRequiredTraits() {
229: if (SVGConstants.SVG_PATH_TAG == localName) {
230: return PATH_REQUIRED_TRAITS;
231: } else {
232: return POLY_ALL_REQUIRED_TRAITS;
233: }
234: }
235:
236: /**
237: * ShapeNode handles the 'd' trait.
238: *
239: * @param name the trait name (e.g., "d")
240: * @return the trait's value as a string (e.g., "M0,0L50,20Z")
241: *
242: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
243: * trait is not supported on this element or null.
244: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
245: * trait's computed value cannot be converted to a String (SVG Tiny only).
246: */
247: public String getTraitImpl(final String name) throws DOMException {
248: if (SVGConstants.SVG_PATH_TAG == localName) {
249: if (SVGConstants.SVG_D_ATTRIBUTE == name) {
250: return path.toString();
251: }
252: } else if (SVGConstants.SVG_POINTS_ATTRIBUTE == name) {
253: return path.toPointsString();
254: }
255: return super .getTraitImpl(name);
256: }
257:
258: /**
259: * ShapeNode handles the 'd' trait.
260: *
261: * @param name the trait's name (e.g, "d")
262: * @return the trait's SVGPath value.
263: *
264: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
265: * trait is not supported on this element or null.
266: * @throws DOMException with error code TYPE_MISMATCH_ERR if requested
267: * trait's computed value cannot be converted to {@link
268: * org.w3c.dom.svg.SVGPath SVGPath}
269: * @throws SecurityException if the application does not have the necessary
270: * privilege rights to access this (SVG) content.
271: */
272: SVGPath getPathTraitImpl(final String name) throws DOMException {
273: if (SVGConstants.SVG_PATH_TAG.equals(localName)
274: && SVGConstants.SVG_D_ATTRIBUTE.equals(name)) {
275: return new Path(path);
276: } else {
277: return super .getPathTraitImpl(name);
278: }
279: }
280:
281: /**
282: * @param traitName the trait name.
283: */
284: TraitAnim createTraitAnimImpl(final String traitName) {
285: if (SVGConstants.SVG_D_ATTRIBUTE == traitName
286: || SVGConstants.SVG_POINTS_ATTRIBUTE == traitName) {
287: return new FloatTraitAnim(this , traitName,
288: TRAIT_TYPE_SVG_PATH);
289: } else {
290: return super .createTraitAnimImpl(traitName);
291: }
292: }
293:
294: /**
295: * Set the trait value as float.
296: *
297: * @param name the trait's name.
298: * @param value the trait's value.
299: *
300: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
301: * trait is not supported on this element.
302: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
303: * trait's value cannot be specified as a float
304: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
305: * value is an invalid value for the given trait.
306: */
307: void setFloatArrayTrait(final String name, final float[][] value)
308: throws DOMException {
309: if (SVGConstants.SVG_D_ATTRIBUTE == name
310: || SVGConstants.SVG_POINTS_ATTRIBUTE == name) {
311: if (!path.equals(value)) {
312: modifyingNode();
313: renderingDirty();
314: path.setData(value[0]);
315: modifiedNode();
316: }
317: } else {
318: super .setFloatArrayTrait(name, value);
319: }
320: }
321:
322: /**
323: * Validates the input trait value.
324: *
325: * @param traitName the name of the trait to be validated.
326: * @param value the value to be validated
327: * @param reqNamespaceURI the namespace of the element requesting
328: * validation.
329: * @param reqLocalName the local name of the element requesting validation.
330: * @param reqTraitNamespace the namespace of the trait which has the values
331: * value on the requesting element.
332: * @param reqTraitName the name of the trait which has the values value on
333: * the requesting element.
334: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
335: * value is incompatible with the given trait.
336: */
337: public float[][] validateFloatArrayTrait(final String traitName,
338: final String value, final String reqNamespaceURI,
339: final String reqLocalName, final String reqTraitNamespace,
340: final String reqTraitName) throws DOMException {
341: if (SVGConstants.SVG_D_ATTRIBUTE == traitName) {
342: Path path = parsePathTrait(traitName, value);
343: return toAnimatedFloatArray(path);
344: } else if (SVGConstants.SVG_POINTS_ATTRIBUTE == traitName) {
345: Path path = parsePointsTrait(traitName, value);
346: if (SVGConstants.SVG_POLYGON_TAG == localName) {
347: path.close();
348: }
349: return toAnimatedFloatArray(path);
350: } else {
351: return super .validateFloatArrayTrait(traitName, value,
352: reqNamespaceURI, reqLocalName, reqTraitNamespace,
353: reqTraitName);
354: }
355: }
356:
357: /**
358: * ShapeNode handles the 'd' trait if it is a path and the
359: * 'points' trait if it is a polygon or a polyline.
360: *
361: * @param name the trait's name (e.g, "d")
362: * @param value the trait's new value (e.g., "M0, 0 L50, 50 Z"), using the
363: * SVG path syntax.
364: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
365: * trait is not supported on this element or null.
366: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
367: * value is an invalid value for the given trait or null.
368: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
369: * attempt is made to change readonly trait.
370: */
371: public void setTraitImpl(final String name, final String value)
372: throws DOMException {
373: if (SVGConstants.SVG_PATH_TAG == localName) {
374: if (SVGConstants.SVG_D_ATTRIBUTE == name) {
375: setPath(parsePathTrait(name, value));
376: } else {
377: super .setTraitImpl(name, value);
378: }
379: } else if (SVGConstants.SVG_POINTS_ATTRIBUTE == name) {
380: setPath(parsePointsTrait(name, value));
381:
382: // As per the specification, section 9.7 of the SVG 1.1 spec.,
383: // we need to close the path if we are dealing with a polygon.
384: // Only do so if the document does not have a delayed exception
385: // which means this element was loading and an error was detected
386: // in the points data.
387: if (ownerDocument.getDelayedException() == null) {
388: if (SVGConstants.SVG_POLYGON_TAG == localName) {
389: path.close();
390: }
391: }
392: } else {
393: super .setTraitImpl(name, value);
394: }
395: }
396:
397: /**
398: * @param name the name of the trait to convert.
399: * @param value the float trait value to convert.
400: */
401: String toStringTrait(final String name, final float[][] value) {
402: if (SVGConstants.SVG_PATH_TAG.equals(localName)
403: && SVGConstants.SVG_D_ATTRIBUTE.equals(name)) {
404: return path.toString(value[0]);
405: } else if (SVGConstants.SVG_POINTS_ATTRIBUTE.equals(name)) {
406: return path.toPointsString(value[0]);
407: } else {
408: return super .toStringTrait(name, value);
409: }
410: }
411:
412: /**
413: * ShapeNode handles the 'd' trait.
414: *
415: * @param name the trait's name (e.g., "d")
416: * @param value the trait's new SVGPath value.
417: *
418: * @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
419: * trait is not supported on this element or null.
420: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
421: * trait's value cannot be specified as an {@link org.w3c.dom.svg.SVGPath
422: * SVGPath}
423: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
424: * value is an invalid value for the given trait or null. SVGPath is
425: * invalid if it does not begin with a MOVE_TO segment.
426: */
427: void setPathTraitImpl(final String name, final SVGPath path)
428: throws DOMException {
429: // Note that here, we use equals because the strings
430: // have not been interned.
431: if (SVGConstants.SVG_PATH_TAG.equals(localName)
432: && SVGConstants.SVG_D_ATTRIBUTE.equals(name)) {
433: if (path.getNumberOfSegments() > 0
434: && path.getSegment(0) != SVGPath.MOVE_TO) {
435: // The first command _must_ be a moveTo.
436: // However, note than an empty path is accepted.
437: throw illegalTraitValue(name, path.toString());
438: }
439: setPath(new Path((Path) path));
440: } else {
441: super .setPathTraitImpl(name, path);
442: }
443: }
444:
445: /**
446: * @param bbox the bounding box to which this node's bounding box should be
447: * appended. That bounding box is in the target coordinate space. It
448: * may be null, in which case this node should create a new one.
449: * @param t the transform from the node coordinate system to the coordinate
450: * system into which the bounds should be computed.
451: * @return the bounding box of this node, in the target coordinate space,
452: */
453: Box addNodeBBox(final Box bbox, final Transform t) {
454: return addShapeBBox(bbox, path, t);
455: }
456: }
|