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.BasicStroke;
022: import java.awt.Color;
023: import java.awt.Paint;
024: import java.awt.Shape;
025: import java.awt.Stroke;
026:
027: import org.apache.batik.css.engine.SVGCSSEngine;
028: import org.apache.batik.css.engine.value.Value;
029: import org.apache.batik.css.engine.value.svg.ICCColor;
030: import org.apache.batik.ext.awt.color.ICCColorSpaceExt;
031: import org.apache.batik.gvt.CompositeShapePainter;
032: import org.apache.batik.gvt.FillShapePainter;
033: import org.apache.batik.gvt.GraphicsNode;
034: import org.apache.batik.gvt.Marker;
035: import org.apache.batik.gvt.MarkerShapePainter;
036: import org.apache.batik.gvt.ShapeNode;
037: import org.apache.batik.gvt.ShapePainter;
038: import org.apache.batik.gvt.StrokeShapePainter;
039: import org.apache.batik.util.CSSConstants;
040: import org.apache.batik.util.SVGConstants;
041: import org.w3c.dom.Element;
042: import org.w3c.dom.css.CSSPrimitiveValue;
043: import org.w3c.dom.css.CSSValue;
044:
045: /**
046: * A collection of utility methods to deliver <tt>java.awt.Paint</tt>,
047: * <tt>java.awt.Stroke</tt> objects that could be used to paint a
048: * shape. This class also provides additional methods the deliver SVG
049: * Paint using the ShapePainter interface.
050: *
051: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
052: * @version $Id: PaintServer.java 498740 2007-01-22 18:35:57Z dvholten $
053: */
054: public abstract class PaintServer implements SVGConstants,
055: CSSConstants, ErrorConstants {
056:
057: /**
058: * No instance of this class is required.
059: */
060: protected PaintServer() {
061: }
062:
063: /////////////////////////////////////////////////////////////////////////
064: // 'marker-start', 'marker-mid', 'marker-end' delegates to the PaintServer
065: /////////////////////////////////////////////////////////////////////////
066:
067: /**
068: * Returns a <tt>ShapePainter</tt> defined on the specified
069: * element and for the specified shape node.
070: *
071: * @param e the element with the marker CSS properties
072: * @param node the shape node
073: * @param ctx the bridge context
074: */
075: public static ShapePainter convertMarkers(Element e,
076: ShapeNode node, BridgeContext ctx) {
077: Value v;
078: v = CSSUtilities.getComputedStyle(e,
079: SVGCSSEngine.MARKER_START_INDEX);
080: Marker startMarker = convertMarker(e, v, ctx);
081: v = CSSUtilities.getComputedStyle(e,
082: SVGCSSEngine.MARKER_MID_INDEX);
083: Marker midMarker = convertMarker(e, v, ctx);
084: v = CSSUtilities.getComputedStyle(e,
085: SVGCSSEngine.MARKER_END_INDEX);
086: Marker endMarker = convertMarker(e, v, ctx);
087:
088: if (startMarker != null || midMarker != null
089: || endMarker != null) {
090: MarkerShapePainter p = new MarkerShapePainter(node
091: .getShape());
092: p.setStartMarker(startMarker);
093: p.setMiddleMarker(midMarker);
094: p.setEndMarker(endMarker);
095: return p;
096: } else {
097: return null;
098: }
099: }
100:
101: /////////////////////////////////////////////////////////////////////////
102: // org.apache.batik.gvt.Marker
103: /////////////////////////////////////////////////////////////////////////
104:
105: /**
106: * Returns a <tt>Marker</tt> defined on the specified element by
107: * the specified value, and for the specified shape node.
108: *
109: * @param e the painted element
110: * @param v the CSS value describing the marker to construct
111: * @param ctx the bridge context
112: */
113: public static Marker convertMarker(Element e, Value v,
114: BridgeContext ctx) {
115:
116: if (v.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) {
117: return null; // 'none'
118: } else {
119: String uri = v.getStringValue();
120: Element markerElement = ctx.getReferencedElement(e, uri);
121: Bridge bridge = ctx.getBridge(markerElement);
122: if (bridge == null || !(bridge instanceof MarkerBridge)) {
123: throw new BridgeException(ctx, e,
124: ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
125: }
126: return ((MarkerBridge) bridge).createMarker(ctx,
127: markerElement, e);
128: }
129: }
130:
131: /////////////////////////////////////////////////////////////////////////
132: // 'stroke', 'fill' ... converts to ShapePainter
133: /////////////////////////////////////////////////////////////////////////
134:
135: /**
136: * Returns a <tt>ShapePainter</tt> defined on the specified element and
137: * for the specified shape node, and using the specified bridge
138: * context.
139: *
140: * @param e the element interested in a shape painter
141: * @param node the shape node
142: * @param ctx the bridge context
143: */
144: public static ShapePainter convertFillAndStroke(Element e,
145: ShapeNode node, BridgeContext ctx) {
146: Shape shape = node.getShape();
147: if (shape == null)
148: return null;
149:
150: Paint fillPaint = convertFillPaint(e, node, ctx);
151: FillShapePainter fp = new FillShapePainter(shape);
152: fp.setPaint(fillPaint);
153:
154: Stroke stroke = convertStroke(e);
155: if (stroke == null)
156: return fp;
157:
158: Paint strokePaint = convertStrokePaint(e, node, ctx);
159: StrokeShapePainter sp = new StrokeShapePainter(shape);
160: sp.setStroke(stroke);
161: sp.setPaint(strokePaint);
162:
163: CompositeShapePainter cp = new CompositeShapePainter(shape);
164: cp.addShapePainter(fp);
165: cp.addShapePainter(sp);
166: return cp;
167: }
168:
169: public static ShapePainter convertStrokePainter(Element e,
170: ShapeNode node, BridgeContext ctx) {
171: Shape shape = node.getShape();
172: if (shape == null)
173: return null;
174:
175: Stroke stroke = convertStroke(e);
176: if (stroke == null)
177: return null;
178:
179: Paint strokePaint = convertStrokePaint(e, node, ctx);
180: StrokeShapePainter sp = new StrokeShapePainter(shape);
181: sp.setStroke(stroke);
182: sp.setPaint(strokePaint);
183: return sp;
184: }
185:
186: /////////////////////////////////////////////////////////////////////////
187: // java.awt.Paint
188: /////////////////////////////////////////////////////////////////////////
189:
190: /**
191: * Converts for the specified element, its stroke paint properties
192: * to a Paint object.
193: *
194: * @param strokedElement the element interested in a Paint
195: * @param strokedNode the graphics node to stroke
196: * @param ctx the bridge context
197: */
198: public static Paint convertStrokePaint(Element strokedElement,
199: GraphicsNode strokedNode, BridgeContext ctx) {
200: Value v = CSSUtilities.getComputedStyle(strokedElement,
201: SVGCSSEngine.STROKE_OPACITY_INDEX);
202: float opacity = convertOpacity(v);
203: v = CSSUtilities.getComputedStyle(strokedElement,
204: SVGCSSEngine.STROKE_INDEX);
205:
206: return convertPaint(strokedElement, strokedNode, v, opacity,
207: ctx);
208: }
209:
210: /**
211: * Converts for the specified element, its fill paint properties
212: * to a Paint object.
213: *
214: * @param filledElement the element interested in a Paint
215: * @param filledNode the graphics node to fill
216: * @param ctx the bridge context
217: */
218: public static Paint convertFillPaint(Element filledElement,
219: GraphicsNode filledNode, BridgeContext ctx) {
220: Value v = CSSUtilities.getComputedStyle(filledElement,
221: SVGCSSEngine.FILL_OPACITY_INDEX);
222: float opacity = convertOpacity(v);
223: v = CSSUtilities.getComputedStyle(filledElement,
224: SVGCSSEngine.FILL_INDEX);
225:
226: return convertPaint(filledElement, filledNode, v, opacity, ctx);
227: }
228:
229: /**
230: * Converts a Paint definition to a concrete <tt>java.awt.Paint</tt>
231: * instance according to the specified parameters.
232: *
233: * @param paintedElement the element interested in a Paint
234: * @param paintedNode the graphics node to paint (objectBoundingBox)
235: * @param paintDef the paint definition
236: * @param opacity the opacity to consider for the Paint
237: * @param ctx the bridge context
238: */
239: public static Paint convertPaint(Element paintedElement,
240: GraphicsNode paintedNode, Value paintDef, float opacity,
241: BridgeContext ctx) {
242: if (paintDef.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
243: switch (paintDef.getPrimitiveType()) {
244: case CSSPrimitiveValue.CSS_IDENT:
245: return null; // none
246:
247: case CSSPrimitiveValue.CSS_RGBCOLOR:
248: return convertColor(paintDef, opacity);
249:
250: case CSSPrimitiveValue.CSS_URI:
251: return convertURIPaint(paintedElement, paintedNode,
252: paintDef, opacity, ctx);
253:
254: default:
255: throw new IllegalArgumentException(
256: "Paint argument is not an appropriate CSS value");
257: }
258: } else { // List
259: Value v = paintDef.item(0);
260: switch (v.getPrimitiveType()) {
261: case CSSPrimitiveValue.CSS_RGBCOLOR:
262: return convertRGBICCColor(paintedElement, v,
263: (ICCColor) paintDef.item(1), opacity, ctx);
264:
265: case CSSPrimitiveValue.CSS_URI: {
266: Paint result = silentConvertURIPaint(paintedElement,
267: paintedNode, v, opacity, ctx);
268: if (result != null)
269: return result;
270:
271: v = paintDef.item(1);
272: switch (v.getPrimitiveType()) {
273: case CSSPrimitiveValue.CSS_IDENT:
274: return null; // none
275:
276: case CSSPrimitiveValue.CSS_RGBCOLOR:
277: if (paintDef.getLength() == 2) {
278: return convertColor(v, opacity);
279: } else {
280: return convertRGBICCColor(paintedElement, v,
281: (ICCColor) paintDef.item(2), opacity,
282: ctx);
283: }
284: default:
285: throw new IllegalArgumentException(
286: "Paint argument is not an appropriate CSS value");
287: }
288: }
289: default:
290: // can't be reached
291: throw new IllegalArgumentException(
292: "Paint argument is not an appropriate CSS value");
293: }
294: }
295: }
296:
297: /**
298: * Converts a Paint specified by URI without sending any error.
299: * if a problem occured while processing the URI, it just returns
300: * null (same effect as 'none')
301: *
302: * @param paintedElement the element interested in a Paint
303: * @param paintedNode the graphics node to paint (objectBoundingBox)
304: * @param paintDef the paint definition
305: * @param opacity the opacity to consider for the Paint
306: * @param ctx the bridge context
307: * @return the paint object or null when impossible
308: */
309: public static Paint silentConvertURIPaint(Element paintedElement,
310: GraphicsNode paintedNode, Value paintDef, float opacity,
311: BridgeContext ctx) {
312: Paint paint = null;
313: try {
314: paint = convertURIPaint(paintedElement, paintedNode,
315: paintDef, opacity, ctx);
316: } catch (BridgeException ex) {
317: }
318: return paint;
319: }
320:
321: /**
322: * Converts a Paint specified as a URI.
323: *
324: * @param paintedElement the element interested in a Paint
325: * @param paintedNode the graphics node to paint (objectBoundingBox)
326: * @param paintDef the paint definition
327: * @param opacity the opacity to consider for the Paint
328: * @param ctx the bridge context
329: */
330: public static Paint convertURIPaint(Element paintedElement,
331: GraphicsNode paintedNode, Value paintDef, float opacity,
332: BridgeContext ctx) {
333:
334: String uri = paintDef.getStringValue();
335: Element paintElement = ctx.getReferencedElement(paintedElement,
336: uri);
337:
338: Bridge bridge = ctx.getBridge(paintElement);
339: if (bridge == null || !(bridge instanceof PaintBridge)) {
340: throw new BridgeException(ctx, paintedElement,
341: ERR_CSS_URI_BAD_TARGET, new Object[] { uri });
342: }
343: return ((PaintBridge) bridge).createPaint(ctx, paintElement,
344: paintedElement, paintedNode, opacity);
345: }
346:
347: /**
348: * Returns a Color object that corresponds to the input Paint's
349: * ICC color value or an RGB color if the related color profile
350: * could not be used or loaded for any reason.
351: *
352: * @param paintedElement the element using the color
353: * @param colorDef the color definition
354: * @param iccColor the ICC color definition
355: * @param opacity the opacity
356: * @param ctx the bridge context to use
357: */
358: public static Color convertRGBICCColor(Element paintedElement,
359: Value colorDef, ICCColor iccColor, float opacity,
360: BridgeContext ctx) {
361: Color color = null;
362: if (iccColor != null) {
363: color = convertICCColor(paintedElement, iccColor, opacity,
364: ctx);
365: }
366: if (color == null) {
367: color = convertColor(colorDef, opacity);
368: }
369: return color;
370: }
371:
372: /**
373: * Returns a Color object that corresponds to the input Paint's
374: * ICC color value or null if the related color profile could not
375: * be used or loaded for any reason.
376: *
377: * @param e the element using the color
378: * @param c the ICC color definition
379: * @param opacity the opacity
380: * @param ctx the bridge context to use
381: */
382: public static Color convertICCColor(Element e, ICCColor c,
383: float opacity, BridgeContext ctx) {
384: // Get ICC Profile's name
385: String iccProfileName = c.getColorProfile();
386: if (iccProfileName == null) {
387: return null;
388: }
389: // Ask the bridge to map the ICC profile name to an ICC_Profile object
390: SVGColorProfileElementBridge profileBridge = (SVGColorProfileElementBridge) ctx
391: .getBridge(SVG_NAMESPACE_URI, SVG_COLOR_PROFILE_TAG);
392: if (profileBridge == null) {
393: return null; // no bridge for color profile
394: }
395:
396: ICCColorSpaceExt profileCS = profileBridge
397: .createICCColorSpaceExt(ctx, e, iccProfileName);
398: if (profileCS == null) {
399: return null; // no profile
400: }
401:
402: // Now, convert the colors to an array of floats
403: int n = c.getNumberOfColors();
404: float[] colorValue = new float[n];
405: if (n == 0) {
406: return null;
407: }
408: for (int i = 0; i < n; i++) {
409: colorValue[i] = c.getColor(i);
410: }
411:
412: // Convert values to RGB
413: float[] rgb = profileCS.intendedToRGB(colorValue);
414: return new Color(rgb[0], rgb[1], rgb[2], opacity);
415: }
416:
417: /**
418: * Converts the given Value and opacity to a Color object.
419: * @param c The CSS color to convert.
420: * @param opacity The opacity value (0 <= o <= 1).
421: */
422: public static Color convertColor(Value c, float opacity) {
423: int r = resolveColorComponent(c.getRed());
424: int g = resolveColorComponent(c.getGreen());
425: int b = resolveColorComponent(c.getBlue());
426: return new Color(r, g, b, Math.round(opacity * 255f));
427: }
428:
429: /////////////////////////////////////////////////////////////////////////
430: // java.awt.stroke
431: /////////////////////////////////////////////////////////////////////////
432:
433: /**
434: * Converts a <tt>Stroke</tt> object defined on the specified element.
435: *
436: * @param e the element on which the stroke is specified
437: */
438: public static Stroke convertStroke(Element e) {
439: Value v;
440: v = CSSUtilities.getComputedStyle(e,
441: SVGCSSEngine.STROKE_WIDTH_INDEX);
442: float width = v.getFloatValue();
443: if (width == 0.0f)
444: return null; // Stop here no stroke should be painted.
445:
446: v = CSSUtilities.getComputedStyle(e,
447: SVGCSSEngine.STROKE_LINECAP_INDEX);
448: int linecap = convertStrokeLinecap(v);
449: v = CSSUtilities.getComputedStyle(e,
450: SVGCSSEngine.STROKE_LINEJOIN_INDEX);
451: int linejoin = convertStrokeLinejoin(v);
452: v = CSSUtilities.getComputedStyle(e,
453: SVGCSSEngine.STROKE_MITERLIMIT_INDEX);
454: float miterlimit = convertStrokeMiterlimit(v);
455: v = CSSUtilities.getComputedStyle(e,
456: SVGCSSEngine.STROKE_DASHARRAY_INDEX);
457: float[] dasharray = convertStrokeDasharray(v);
458:
459: float dashoffset = 0;
460: if (dasharray != null) {
461: v = CSSUtilities.getComputedStyle(e,
462: SVGCSSEngine.STROKE_DASHOFFSET_INDEX);
463: dashoffset = v.getFloatValue();
464:
465: // make the dashoffset positive since BasicStroke cannot handle
466: // negative values
467: if (dashoffset < 0) {
468: float dashpatternlength = 0;
469: for (int i = 0; i < dasharray.length; i++) {
470: dashpatternlength += dasharray[i];
471: }
472: // if the dash pattern consists of an odd number of elements,
473: // the pattern length must be doubled
474: if ((dasharray.length % 2) != 0)
475: dashpatternlength *= 2;
476:
477: if (dashpatternlength == 0) {
478: dashoffset = 0;
479: } else {
480: while (dashoffset < 0)
481: dashoffset += dashpatternlength;
482: }
483: }
484: }
485: return new BasicStroke(width, linecap, linejoin, miterlimit,
486: dasharray, dashoffset);
487: }
488:
489: /////////////////////////////////////////////////////////////////////////
490: // Stroke utility methods
491: /////////////////////////////////////////////////////////////////////////
492:
493: /**
494: * Converts the 'stroke-dasharray' property to a list of float
495: * number in user units.
496: *
497: * @param v the CSS value describing the dasharray property
498: */
499: public static float[] convertStrokeDasharray(Value v) {
500: float[] dasharray = null;
501: if (v.getCssValueType() == CSSValue.CSS_VALUE_LIST) {
502: int length = v.getLength();
503: dasharray = new float[length];
504: float sum = 0;
505: for (int i = 0; i < dasharray.length; ++i) {
506: dasharray[i] = v.item(i).getFloatValue();
507: sum += dasharray[i];
508: }
509: if (sum == 0) {
510: /* 11.4 - If the sum of the <length>'s is zero, then
511: * the stroke is rendered as if a value of none were specified.
512: */
513: dasharray = null;
514: }
515: }
516: return dasharray;
517: }
518:
519: /**
520: * Converts the 'miterlimit' property to the appropriate float number.
521: * @param v the CSS value describing the miterlimit property
522: */
523: public static float convertStrokeMiterlimit(Value v) {
524: float miterlimit = v.getFloatValue();
525: return (miterlimit < 1.0f) ? 1.0f : miterlimit;
526: }
527:
528: /**
529: * Converts the 'linecap' property to the appropriate BasicStroke constant.
530: * @param v the CSS value describing the linecap property
531: */
532: public static int convertStrokeLinecap(Value v) {
533: String s = v.getStringValue();
534: switch (s.charAt(0)) {
535: case 'b':
536: return BasicStroke.CAP_BUTT;
537: case 'r':
538: return BasicStroke.CAP_ROUND;
539: case 's':
540: return BasicStroke.CAP_SQUARE;
541: default:
542: throw new IllegalArgumentException(
543: "Linecap argument is not an appropriate CSS value");
544: }
545: }
546:
547: /**
548: * Converts the 'linejoin' property to the appropriate BasicStroke
549: * constant.
550: * @param v the CSS value describing the linejoin property
551: */
552: public static int convertStrokeLinejoin(Value v) {
553: String s = v.getStringValue();
554: switch (s.charAt(0)) {
555: case 'm':
556: return BasicStroke.JOIN_MITER;
557: case 'r':
558: return BasicStroke.JOIN_ROUND;
559: case 'b':
560: return BasicStroke.JOIN_BEVEL;
561: default:
562: throw new IllegalArgumentException(
563: "Linejoin argument is not an appropriate CSS value");
564: }
565: }
566:
567: /////////////////////////////////////////////////////////////////////////
568: // Paint utility methods
569: /////////////////////////////////////////////////////////////////////////
570:
571: /**
572: * Returns the value of one color component (0 <= result <= 255).
573: * @param v the value that defines the color component
574: */
575: public static int resolveColorComponent(Value v) {
576: float f;
577: switch (v.getPrimitiveType()) {
578: case CSSPrimitiveValue.CSS_PERCENTAGE:
579: f = v.getFloatValue();
580: f = (f > 100f) ? 100f : (f < 0f) ? 0f : f;
581: return Math.round(255f * f / 100f);
582: case CSSPrimitiveValue.CSS_NUMBER:
583: f = v.getFloatValue();
584: f = (f > 255f) ? 255f : (f < 0f) ? 0f : f;
585: return Math.round(f);
586: default:
587: throw new IllegalArgumentException(
588: "Color component argument is not an appropriate CSS value");
589: }
590: }
591:
592: /**
593: * Returns the opacity represented by the specified CSSValue.
594: * @param v the value that represents the opacity
595: * @return the opacity between 0 and 1
596: */
597: public static float convertOpacity(Value v) {
598: float r = v.getFloatValue();
599: return (r < 0f) ? 0f : (r > 1.0f) ? 1.0f : r;
600: }
601: }
|