| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge; |
| |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| |
| import org.apache.batik.css.engine.SVGCSSEngine; |
| import org.apache.batik.css.engine.value.Value; |
| import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit; |
| import org.apache.batik.ext.awt.image.renderable.Filter; |
| import org.apache.batik.gvt.CompositeGraphicsNode; |
| import org.apache.batik.gvt.GraphicsNode; |
| import org.apache.batik.gvt.Marker; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * Bridge class for the <marker> element. |
| * |
| * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a> |
| * @version $Id$ |
| */ |
| public class SVGMarkerElementBridge extends AnimatableGenericSVGBridge |
| implements MarkerBridge, ErrorConstants { |
| |
| /** |
| * Constructs a new bridge for the <marker> element. |
| */ |
| protected SVGMarkerElementBridge() {} |
| |
| /** |
| * Returns 'marker'. |
| */ |
| public String getLocalName() { |
| return SVG_MARKER_TAG; |
| } |
| |
| /** |
| * Creates a <tt>Marker</tt> according to the specified parameters. |
| * |
| * @param ctx the bridge context to use |
| * @param markerElement the element that represents the marker |
| * @param paintedElement the element that references the marker element |
| */ |
| public Marker createMarker(BridgeContext ctx, |
| Element markerElement, |
| Element paintedElement) { |
| |
| GVTBuilder builder = ctx.getGVTBuilder(); |
| |
| CompositeGraphicsNode markerContentNode |
| = new CompositeGraphicsNode(); |
| |
| // build the GVT tree that represents the marker |
| boolean hasChildren = false; |
| for(Node n = markerElement.getFirstChild(); |
| n != null; |
| n = n.getNextSibling()) { |
| |
| // check if the node is a valid Element |
| if (n.getNodeType() != Node.ELEMENT_NODE) { |
| continue; |
| } |
| Element child = (Element)n; |
| GraphicsNode markerNode = builder.build(ctx, child) ; |
| // check if a GVT node has been created |
| if (markerNode == null) { |
| continue; // skip element as <marker> can contain <defs>... |
| } |
| hasChildren = true; |
| markerContentNode.getChildren().add(markerNode); |
| } |
| if (!hasChildren) { |
| return null; // no marker content defined |
| } |
| |
| String s; |
| UnitProcessor.Context uctx |
| = UnitProcessor.createContext(ctx, paintedElement); |
| |
| // 'markerWidth' attribute - default is 3 |
| float markerWidth = 3; |
| s = markerElement.getAttributeNS(null, SVG_MARKER_WIDTH_ATTRIBUTE); |
| if (s.length() != 0) { |
| markerWidth = UnitProcessor.svgHorizontalLengthToUserSpace |
| (s, SVG_MARKER_WIDTH_ATTRIBUTE, uctx); |
| } |
| if (markerWidth == 0) { |
| // A value of zero disables rendering of the element. |
| return null; |
| } |
| |
| // 'markerHeight' attribute - default is 3 |
| float markerHeight = 3; |
| s = markerElement.getAttributeNS(null, SVG_MARKER_HEIGHT_ATTRIBUTE); |
| if (s.length() != 0) { |
| markerHeight = UnitProcessor.svgVerticalLengthToUserSpace |
| (s, SVG_MARKER_HEIGHT_ATTRIBUTE, uctx); |
| } |
| if (markerHeight == 0) { |
| // A value of zero disables rendering of the element. |
| return null; |
| } |
| |
| // 'orient' attribute - default is '0' |
| double orient; |
| s = markerElement.getAttributeNS(null, SVG_ORIENT_ATTRIBUTE); |
| if (s.length() == 0) { |
| orient = 0; |
| } else if (SVG_AUTO_VALUE.equals(s)) { |
| orient = Double.NaN; |
| } else { |
| try { |
| orient = SVGUtilities.convertSVGNumber(s); |
| } catch (NumberFormatException nfEx ) { |
| throw new BridgeException |
| (ctx, markerElement, nfEx, ERR_ATTRIBUTE_VALUE_MALFORMED, |
| new Object [] {SVG_ORIENT_ATTRIBUTE, s}); |
| } |
| } |
| |
| // 'stroke-width' property |
| Value val = CSSUtilities.getComputedStyle |
| (paintedElement, SVGCSSEngine.STROKE_WIDTH_INDEX); |
| float strokeWidth = val.getFloatValue(); |
| |
| // 'markerUnits' attribute - default is 'strokeWidth' |
| short unitsType; |
| s = markerElement.getAttributeNS(null, SVG_MARKER_UNITS_ATTRIBUTE); |
| if (s.length() == 0) { |
| unitsType = SVGUtilities.STROKE_WIDTH; |
| } else { |
| unitsType = SVGUtilities.parseMarkerCoordinateSystem |
| (markerElement, SVG_MARKER_UNITS_ATTRIBUTE, s, ctx); |
| } |
| |
| // |
| // |
| // |
| |
| // compute an additional transform for 'strokeWidth' coordinate system |
| AffineTransform markerTxf; |
| if (unitsType == SVGUtilities.STROKE_WIDTH) { |
| markerTxf = new AffineTransform(); |
| markerTxf.scale(strokeWidth, strokeWidth); |
| } else { |
| markerTxf = new AffineTransform(); |
| } |
| |
| // 'viewBox' and 'preserveAspectRatio' attributes |
| // viewBox -> viewport(0, 0, markerWidth, markerHeight) |
| AffineTransform preserveAspectRatioTransform |
| = ViewBox.getPreserveAspectRatioTransform(markerElement, |
| markerWidth, |
| markerHeight, ctx); |
| if (preserveAspectRatioTransform == null) { |
| // disable the rendering of the element |
| return null; |
| } else { |
| markerTxf.concatenate(preserveAspectRatioTransform); |
| } |
| // now we can set the transform to the 'markerContentNode' |
| markerContentNode.setTransform(markerTxf); |
| |
| // 'overflow' property |
| if (CSSUtilities.convertOverflow(markerElement)) { // overflow:hidden |
| Rectangle2D markerClip; |
| float [] offsets = CSSUtilities.convertClip(markerElement); |
| if (offsets == null) { // clip:auto |
| markerClip |
| = new Rectangle2D.Float(0, |
| 0, |
| strokeWidth * markerWidth, |
| strokeWidth * markerHeight); |
| } else { // clip:rect(<x>, <y>, <w>, <h>) |
| // offsets[0] = top |
| // offsets[1] = right |
| // offsets[2] = bottom |
| // offsets[3] = left |
| markerClip = new Rectangle2D.Float |
| (offsets[3], |
| offsets[0], |
| strokeWidth * markerWidth - offsets[1] - offsets[3], |
| strokeWidth * markerHeight - offsets[2] - offsets[0]); |
| } |
| |
| CompositeGraphicsNode comp = new CompositeGraphicsNode(); |
| comp.getChildren().add(markerContentNode); |
| Filter clipSrc = comp.getGraphicsNodeRable(true); |
| comp.setClip(new ClipRable8Bit(clipSrc, markerClip)); |
| markerContentNode = comp; |
| } |
| |
| // 'refX' attribute - default is 0 |
| float refX = 0; |
| s = markerElement.getAttributeNS(null, SVG_REF_X_ATTRIBUTE); |
| if (s.length() != 0) { |
| refX = UnitProcessor.svgHorizontalCoordinateToUserSpace |
| (s, SVG_REF_X_ATTRIBUTE, uctx); |
| } |
| |
| // 'refY' attribute - default is 0 |
| float refY = 0; |
| s = markerElement.getAttributeNS(null, SVG_REF_Y_ATTRIBUTE); |
| if (s.length() != 0) { |
| refY = UnitProcessor.svgVerticalCoordinateToUserSpace |
| (s, SVG_REF_Y_ATTRIBUTE, uctx); |
| } |
| |
| // TK: Warning at this time, refX and refY are relative to the |
| // paintedElement's coordinate system. We need to move the |
| // reference point to the marker's coordinate system |
| |
| // Watch out: the reference point is defined a little weirdly in the |
| // SVG spec., but the bottom line is that the marker content should |
| // not be translated. Rather, the reference point should be computed |
| // in viewport space (this is what the following transform |
| // does) and used when placing the marker. |
| // |
| float[] ref = {refX, refY}; |
| markerTxf.transform(ref, 0, ref, 0, 1); |
| Marker marker = new Marker(markerContentNode, |
| new Point2D.Float(ref[0], ref[1]), |
| orient); |
| |
| return marker; |
| } |
| } |