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.PaintServer;
030: import com.sun.perseus.j2d.PaintTarget;
031: import com.sun.perseus.j2d.PathSupport;
032: import com.sun.perseus.j2d.RenderGraphics;
033: import com.sun.perseus.j2d.Tile;
034:
035: import com.sun.perseus.util.SVGConstants;
036:
037: import org.w3c.dom.DOMException;
038:
039: import org.w3c.dom.svg.SVGMatrix;
040:
041: import com.sun.perseus.j2d.Transform;
042:
043: /**
044: * All nodes which represent geometry (complex or simple shapes)
045: * are represented by descendants of this class.
046: *
047: * @version $Id: AbstractShapeNode.java,v 1.16 2006/06/29 10:47:29 ln156897 Exp $
048: */
049: public abstract class AbstractShapeNode extends AbstractRenderingNode {
050: /**
051: * Constructor.
052: *
053: * @param ownerDocument this element's owner <code>DocumentNode</code>
054: */
055: public AbstractShapeNode(final DocumentNode ownerDocument) {
056: super (ownerDocument);
057: }
058:
059: /**
060: * @return an adequate <code>ElementNodeProxy</code> for this node.
061: */
062: ElementNodeProxy buildProxy() {
063: return new AbstractShapeNodeProxy(this );
064: }
065:
066: /**
067: * @param rg the RenderGraphics on which to fill the shape.
068: */
069: public abstract void fillShape(final RenderGraphics rg);
070:
071: /**
072: * @param rg the RenderGraphics on which to draw the shape.
073: */
074: public abstract void drawShape(final RenderGraphics rg);
075:
076: /**
077: * @param x the hit point coordinate along the x-axis, in user space.
078: * @param y the hit point coordinate along the y-axis, in user space.
079: * @param fillRule the fillRule to apply when testing for containment.
080: * @return true if the hit point is contained within the shape.
081: */
082: public abstract boolean contains(final float x, final float y,
083: final int fillRule);
084:
085: /**
086: * @param x the hit point coordinate along the x-axis, in user space.
087: * @param y the hit point coordinate along the y-axis, in user space.
088: * @param gp the <code>GraphicsProperties</code> instance defining the
089: * rendering context.
090: * @return true if the hit point is contained within the shape.
091: */
092: public final boolean strokedContains(final float x, final float y,
093: final GraphicsProperties gp) {
094: Object strokedPath = getStrokedPath(this );
095: return PathSupport.isStrokedPathHit(strokedPath, gp
096: .getFillRule(), x, y);
097: }
098:
099: /**
100: * Returns the stroked shape, using the given stroke properties.
101: *
102: * @param gp the <code>GraphicsProperties</code> defining the rendering
103: * context.
104: * @return the shape's stroked path.
105: */
106: abstract Object getStrokedPath(final GraphicsProperties gp);
107:
108: /**
109: * Computes the rendering tile for the given set of GraphicsProperties.
110: *
111: * @param tile the Tile instance whose bounds should be set.
112: * @param t the Transform to the requested tile space, from this node's user
113: * space.
114: * @param gp the <code>GraphicsProperties</code> for which the tile
115: * should be computed.
116: * @return the screen bounding box when this node is rendered with the
117: * given render context.
118: */
119: protected void computeRenderingTile(final Tile tile,
120: final Transform t, final GraphicsProperties gp) {
121: if (gp.getStroke() == null) {
122: // No stroking on the shape, we can use the geometrical bounding
123: // box.
124: tile.snapBox(addNodeBBox(null, t));
125: } else {
126: // Need to account for stroking, with a more costly operation to
127: // compute the stroked bounds.
128: Object strokedPath = getStrokedPath(gp);
129: PathSupport.computeStrokedPathTile(tile, strokedPath, t);
130: }
131: }
132:
133: /**
134: * Paints this node into the input RenderGraphics, assuming the node
135: * is rendered.
136: *
137: * @param rg the <code>RenderGraphics</code> where the node should paint
138: * itself.
139: * @param gp the <code>GraphicsProperties</code> controlling the operation's
140: * rendering
141: * @param pt the <code>PaintTarget</code> for the paint operation.
142: * @param txf the <code>Transform</code> from user space to device space for
143: * the paint operation.
144: */
145: protected void paintRendered(final RenderGraphics rg,
146: final GraphicsProperties gp, final PaintTarget pt,
147: final Transform tx) {
148: if (!gp.getVisibility()) {
149: return;
150: }
151:
152: rg.setPaintTarget(pt);
153: rg.setPaintTransform(tx);
154: rg.setTransform(tx);
155:
156: // Fill the shape. Only apply the fill property
157: if (gp.getFill() != null) {
158: rg.setFillRule(gp.getFillRule());
159: rg.setFill(gp.getFill());
160: rg.setFillOpacity(gp.getFillOpacity());
161: fillShape(rg);
162: }
163:
164: // Stroke the shape. Only apply the stroke properties
165: if (gp.getStroke() != null) {
166: rg.setStroke(gp.getStroke());
167: rg.setStrokeOpacity(gp.getStrokeOpacity());
168: rg.setStrokeWidth(gp.getStrokeWidth());
169: rg.setStrokeLineCap(gp.getStrokeLineCap());
170: rg.setStrokeLineJoin(gp.getStrokeLineJoin());
171: rg.setStrokeDashArray(gp.getStrokeDashArray());
172: rg.setStrokeMiterLimit(gp.getStrokeMiterLimit());
173: rg.setStrokeDashOffset(gp.getStrokeDashOffset());
174: drawShape(rg);
175: }
176: }
177:
178: /**
179: * Returns true if this node is hit by the input point. The input point
180: * is in viewport space.
181: *
182: * For an <tt>AbstractShapeNode</tt> this method returns true if:
183: * <ul>
184: * <li>the node is visible <b>and</b><ul>
185: * <li>the node's fill is not NONE and the associated shape contains the
186: * input point, <b>or</b></li>
187: * <li>the node's stroke is not NONE and the associated stroked shape
188: * contains the input point.</li></ul></li>
189: * </ul>
190: * This implements the equivalent of the visiblePainted value for the
191: * pointerEvents attribute. That attribute is not part of SVG Tiny,
192: * but the default behavior in SVG Tiny is that of visiblePainted.
193: *
194: * @param pt the x/y coordinate. Should never be null and be
195: * of size two. If not, the behavior is unspecified.
196: * The x/y coordinate is in viewport space.
197: *
198: * @return true if the node is hit by the input point.
199: * @see #nodeHitAt
200: */
201: protected boolean isHitVP(float[] pt) {
202: // Node has to be visible to be a hit target
203: if (!getVisibility() || (fill == null && stroke == null)) {
204: return false;
205: }
206:
207: getInverseTransformState()
208: .transformPoint(pt, ownerDocument.upt);
209: pt = ownerDocument.upt;
210:
211: // If the node is filled, see if the shape is hit
212: if (fill != null) {
213: if (contains(pt[0], pt[1], getFillRule())) {
214: return true;
215: }
216: }
217:
218: // Test detection on the edge if the stroke color
219: // is set.
220: if (stroke != null) {
221: if (strokedContains(pt[0], pt[1], this )) {
222: return true;
223: }
224: }
225:
226: return false;
227: }
228:
229: /**
230: * Returns true if this proxy node is hit by the input point. The input
231: * point is in viewport space.
232: *
233: * @param pt the x/y coordinate. Should never be null and be
234: * of size two. If not, the behavior is unspecified.
235: * The x/y coordinate is in viewport space.
236: * @param proxy the tested ElementNodeProxy.
237: * @return true if the node is hit by the input point.
238: * @see #isHitVP
239: */
240: protected boolean isProxyHitVP(float[] pt,
241: final AbstractRenderingNodeProxy proxy) {
242: // Node has to be visible to be a hit target
243: if (!proxy.getVisibility()
244: || (proxy.fill == null && proxy.stroke == null)) {
245: return false;
246: }
247:
248: proxy.getInverseTransformState().transformPoint(pt,
249: ownerDocument.upt);
250: pt = ownerDocument.upt;
251:
252: // If the node is filled, see if the shape is hit
253: if (proxy.fill != null) {
254: if (contains(pt[0], pt[1], proxy.getFillRule())) {
255: return true;
256: }
257: }
258:
259: // Test detection on the edge if the stroke color
260: // is set.
261: if (((AbstractShapeNodeProxy) proxy).stroke != null) {
262: if (strokedContains(pt[0], pt[1], proxy)) {
263: return true;
264: }
265: }
266:
267: return false;
268: }
269:
270: /**
271: * @param newFill the new computed fill property.
272: */
273: void setComputedFill(final PaintServer newFill) {
274: this .fill = newFill;
275: renderingDirty();
276: }
277:
278: /**
279: * @param newStroke the new computed stroke property.
280: */
281: void setComputedStroke(final PaintServer newStroke) {
282: this .stroke = newStroke;
283: renderingDirty();
284: }
285:
286: /**
287: * @param newStrokeWidth the new computed stroke-width property value.
288: */
289: void setComputedStrokeWidth(final float newStrokeWidth) {
290: strokeWidth = newStrokeWidth;
291:
292: // Only dirty rendering if the object is actually stroked.
293: if (stroke != null) {
294: renderingDirty();
295: }
296: }
297:
298: /**
299: * @param newStrokeLineJoin the new computed value for stroke-line-join
300: */
301: void setComputedStrokeLineJoin(final int newStrokeLineJoin) {
302: super .setComputedStrokeLineJoin(newStrokeLineJoin);
303:
304: if (stroke != null) {
305: renderingDirty();
306: }
307: }
308:
309: /**
310: * @param newStrokeLineCap the new value for the stroke-linecap property.
311: */
312: void setComputedStrokeLineCap(final int newStrokeLineCap) {
313: super .setComputedStrokeLineCap(newStrokeLineCap);
314:
315: if (stroke != null) {
316: renderingDirty();
317: }
318: }
319:
320: /**
321: * @param newStrokeMiterLimit the new computed stroke-miterlimit property.
322: */
323: void setComputedStrokeMiterLimit(final float newStrokeMiterLimit) {
324: strokeMiterLimit = newStrokeMiterLimit;
325:
326: if (stroke != null && getStrokeLineJoin() == JOIN_MITER) {
327: renderingDirty();
328: }
329: }
330:
331: /**
332: * @param newStrokeDashArray the new computed stroke-dasharray property
333: * value.
334: */
335: void setComputedStrokeDashArray(final float[] newStrokeDashArray) {
336: strokeDashArray = newStrokeDashArray;
337:
338: if (stroke != null) {
339: renderingDirty();
340: }
341: }
342:
343: /**
344: * @param newStrokeDashOffset the new stroke-dashoffset computed property
345: * value.
346: */
347: void setComputedStrokeDashOffset(final float newStrokeDashOffset) {
348: strokeDashOffset = newStrokeDashOffset;
349:
350: if (stroke != null && strokeDashArray != null) {
351: renderingDirty();
352: }
353: }
354:
355: /**
356: * @param newFillOpacity the new computed value for the fill opacity
357: * property.
358: */
359: void setComputedFillOpacity(final float newFillOpacity) {
360: super .setComputedFillOpacity(newFillOpacity);
361:
362: if (fill != null) {
363: renderingDirty();
364: }
365: }
366:
367: /**
368: * @param newStrokeOpacity the new computed stroke-opacity property.
369: */
370: void setComputedStrokeOpacity(final float newStrokeOpacity) {
371: super.setComputedStrokeOpacity(newStrokeOpacity);
372:
373: if (stroke != null) {
374: renderingDirty();
375: }
376: }
377:
378: }
|