001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.bridge;
020:
021: import java.awt.geom.AffineTransform;
022: import java.awt.geom.Point2D;
023: import java.awt.geom.Rectangle2D;
024:
025: import org.apache.batik.css.engine.SVGCSSEngine;
026: import org.apache.batik.css.engine.value.Value;
027: import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit;
028: import org.apache.batik.ext.awt.image.renderable.Filter;
029: import org.apache.batik.gvt.CompositeGraphicsNode;
030: import org.apache.batik.gvt.GraphicsNode;
031: import org.apache.batik.gvt.Marker;
032: import org.w3c.dom.Element;
033: import org.w3c.dom.Node;
034:
035: /**
036: * Bridge class for the <marker> element.
037: *
038: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
039: * @version $Id: SVGMarkerElementBridge.java 501922 2007-01-31 17:47:47Z dvholten $
040: */
041: public class SVGMarkerElementBridge extends AnimatableGenericSVGBridge
042: implements MarkerBridge, ErrorConstants {
043:
044: /**
045: * Constructs a new bridge for the <marker> element.
046: */
047: protected SVGMarkerElementBridge() {
048: }
049:
050: /**
051: * Returns 'marker'.
052: */
053: public String getLocalName() {
054: return SVG_MARKER_TAG;
055: }
056:
057: /**
058: * Creates a <tt>Marker</tt> according to the specified parameters.
059: *
060: * @param ctx the bridge context to use
061: * @param markerElement the element that represents the marker
062: * @param paintedElement the element that references the marker element
063: */
064: public Marker createMarker(BridgeContext ctx,
065: Element markerElement, Element paintedElement) {
066:
067: GVTBuilder builder = ctx.getGVTBuilder();
068:
069: CompositeGraphicsNode markerContentNode = new CompositeGraphicsNode();
070:
071: // build the GVT tree that represents the marker
072: boolean hasChildren = false;
073: for (Node n = markerElement.getFirstChild(); n != null; n = n
074: .getNextSibling()) {
075:
076: // check if the node is a valid Element
077: if (n.getNodeType() != Node.ELEMENT_NODE) {
078: continue;
079: }
080: Element child = (Element) n;
081: GraphicsNode markerNode = builder.build(ctx, child);
082: // check if a GVT node has been created
083: if (markerNode == null) {
084: continue; // skip element as <marker> can contain <defs>...
085: }
086: hasChildren = true;
087: markerContentNode.getChildren().add(markerNode);
088: }
089: if (!hasChildren) {
090: return null; // no marker content defined
091: }
092:
093: String s;
094: UnitProcessor.Context uctx = UnitProcessor.createContext(ctx,
095: paintedElement);
096:
097: // 'markerWidth' attribute - default is 3
098: float markerWidth = 3;
099: s = markerElement.getAttributeNS(null,
100: SVG_MARKER_WIDTH_ATTRIBUTE);
101: if (s.length() != 0) {
102: markerWidth = UnitProcessor.svgHorizontalLengthToUserSpace(
103: s, SVG_MARKER_WIDTH_ATTRIBUTE, uctx);
104: }
105: if (markerWidth == 0) {
106: // A value of zero disables rendering of the element.
107: return null;
108: }
109:
110: // 'markerHeight' attribute - default is 3
111: float markerHeight = 3;
112: s = markerElement.getAttributeNS(null,
113: SVG_MARKER_HEIGHT_ATTRIBUTE);
114: if (s.length() != 0) {
115: markerHeight = UnitProcessor.svgVerticalLengthToUserSpace(
116: s, SVG_MARKER_HEIGHT_ATTRIBUTE, uctx);
117: }
118: if (markerHeight == 0) {
119: // A value of zero disables rendering of the element.
120: return null;
121: }
122:
123: // 'orient' attribute - default is '0'
124: double orient;
125: s = markerElement.getAttributeNS(null, SVG_ORIENT_ATTRIBUTE);
126: if (s.length() == 0) {
127: orient = 0;
128: } else if (SVG_AUTO_VALUE.equals(s)) {
129: orient = Double.NaN;
130: } else {
131: try {
132: orient = SVGUtilities.convertSVGNumber(s);
133: } catch (NumberFormatException nfEx) {
134: throw new BridgeException(ctx, markerElement, nfEx,
135: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
136: SVG_ORIENT_ATTRIBUTE, s });
137: }
138: }
139:
140: // 'stroke-width' property
141: Value val = CSSUtilities.getComputedStyle(paintedElement,
142: SVGCSSEngine.STROKE_WIDTH_INDEX);
143: float strokeWidth = val.getFloatValue();
144:
145: // 'markerUnits' attribute - default is 'strokeWidth'
146: short unitsType;
147: s = markerElement.getAttributeNS(null,
148: SVG_MARKER_UNITS_ATTRIBUTE);
149: if (s.length() == 0) {
150: unitsType = SVGUtilities.STROKE_WIDTH;
151: } else {
152: unitsType = SVGUtilities.parseMarkerCoordinateSystem(
153: markerElement, SVG_MARKER_UNITS_ATTRIBUTE, s, ctx);
154: }
155:
156: //
157: //
158: //
159:
160: // compute an additional transform for 'strokeWidth' coordinate system
161: AffineTransform markerTxf;
162: if (unitsType == SVGUtilities.STROKE_WIDTH) {
163: markerTxf = new AffineTransform();
164: markerTxf.scale(strokeWidth, strokeWidth);
165: } else {
166: markerTxf = new AffineTransform();
167: }
168:
169: // 'viewBox' and 'preserveAspectRatio' attributes
170: // viewBox -> viewport(0, 0, markerWidth, markerHeight)
171: AffineTransform preserveAspectRatioTransform = ViewBox
172: .getPreserveAspectRatioTransform(markerElement,
173: markerWidth, markerHeight, ctx);
174: if (preserveAspectRatioTransform == null) {
175: // disable the rendering of the element
176: return null;
177: } else {
178: markerTxf.concatenate(preserveAspectRatioTransform);
179: }
180: // now we can set the transform to the 'markerContentNode'
181: markerContentNode.setTransform(markerTxf);
182:
183: // 'overflow' property
184: if (CSSUtilities.convertOverflow(markerElement)) { // overflow:hidden
185: Rectangle2D markerClip;
186: float[] offsets = CSSUtilities.convertClip(markerElement);
187: if (offsets == null) { // clip:auto
188: markerClip = new Rectangle2D.Float(0, 0, strokeWidth
189: * markerWidth, strokeWidth * markerHeight);
190: } else { // clip:rect(<x>, <y>, <w>, <h>)
191: // offsets[0] = top
192: // offsets[1] = right
193: // offsets[2] = bottom
194: // offsets[3] = left
195: markerClip = new Rectangle2D.Float(offsets[3],
196: offsets[0], strokeWidth * markerWidth
197: - offsets[1] - offsets[3], strokeWidth
198: * markerHeight - offsets[2]
199: - offsets[0]);
200: }
201:
202: CompositeGraphicsNode comp = new CompositeGraphicsNode();
203: comp.getChildren().add(markerContentNode);
204: Filter clipSrc = comp.getGraphicsNodeRable(true);
205: comp.setClip(new ClipRable8Bit(clipSrc, markerClip));
206: markerContentNode = comp;
207: }
208:
209: // 'refX' attribute - default is 0
210: float refX = 0;
211: s = markerElement.getAttributeNS(null, SVG_REF_X_ATTRIBUTE);
212: if (s.length() != 0) {
213: refX = UnitProcessor.svgHorizontalCoordinateToUserSpace(s,
214: SVG_REF_X_ATTRIBUTE, uctx);
215: }
216:
217: // 'refY' attribute - default is 0
218: float refY = 0;
219: s = markerElement.getAttributeNS(null, SVG_REF_Y_ATTRIBUTE);
220: if (s.length() != 0) {
221: refY = UnitProcessor.svgVerticalCoordinateToUserSpace(s,
222: SVG_REF_Y_ATTRIBUTE, uctx);
223: }
224:
225: // TK: Warning at this time, refX and refY are relative to the
226: // paintedElement's coordinate system. We need to move the
227: // reference point to the marker's coordinate system
228:
229: // Watch out: the reference point is defined a little weirdly in the
230: // SVG spec., but the bottom line is that the marker content should
231: // not be translated. Rather, the reference point should be computed
232: // in viewport space (this is what the following transform
233: // does) and used when placing the marker.
234: //
235: float[] ref = { refX, refY };
236: markerTxf.transform(ref, 0, ref, 0, 1);
237: Marker marker = new Marker(markerContentNode,
238: new Point2D.Float(ref[0], ref[1]), orient);
239:
240: return marker;
241: }
242: }
|