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.util.StringTokenizer;
023:
024: import org.apache.batik.dom.svg.LiveAttributeException;
025: import org.apache.batik.parser.AWTTransformProducer;
026: import org.apache.batik.parser.FragmentIdentifierHandler;
027: import org.apache.batik.parser.FragmentIdentifierParser;
028: import org.apache.batik.parser.ParseException;
029: import org.apache.batik.parser.PreserveAspectRatioParser;
030: import org.apache.batik.util.SVGConstants;
031:
032: import org.w3c.dom.Document;
033: import org.w3c.dom.Element;
034: import org.w3c.dom.Node;
035: import org.w3c.dom.svg.SVGAnimatedPreserveAspectRatio;
036: import org.w3c.dom.svg.SVGPreserveAspectRatio;
037:
038: /**
039: * This class provides convenient methods to handle viewport.
040: *
041: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
042: * @version $Id: ViewBox.java 501922 2007-01-31 17:47:47Z dvholten $
043: */
044: public abstract class ViewBox implements SVGConstants, ErrorConstants {
045:
046: /**
047: * No instance of this class is required.
048: */
049: protected ViewBox() {
050: }
051:
052: /**
053: * Parses the specified reference (from a URI) and returns the appropriate
054: * transform.
055: *
056: * @param ref the reference of the URI that may specify additional attribute
057: * values such as the viewBox, preserveAspectRatio or a transform
058: * @param e the element interested in its view transform
059: * @param w the width of the effective viewport
060: * @param h The height of the effective viewport
061: * @param ctx The BridgeContext to use for error information
062: * @exception BridgeException if an error occured while computing the
063: * preserveAspectRatio transform
064: */
065: public static AffineTransform getViewTransform(String ref,
066: Element e, float w, float h, BridgeContext ctx) {
067:
068: // no reference has been specified, no extra viewBox is defined
069: if (ref == null || ref.length() == 0) {
070: return getPreserveAspectRatioTransform(e, w, h, ctx);
071: }
072:
073: ViewHandler vh = new ViewHandler();
074: FragmentIdentifierParser p = new FragmentIdentifierParser();
075: p.setFragmentIdentifierHandler(vh);
076: p.parse(ref);
077:
078: Element attrDefElement = e; // the element that defines the attributes
079: if (vh.hasId) {
080: Document document = e.getOwnerDocument();
081: attrDefElement = document.getElementById(vh.id);
082: }
083: if (attrDefElement == null) {
084: throw new BridgeException(ctx, e, ERR_URI_MALFORMED,
085: new Object[] { ref });
086: }
087: // if the referenced element is not a view, the attribute
088: // values to use are those defined on the enclosed svg element
089: if (!(attrDefElement.getNamespaceURI()
090: .equals(SVG_NAMESPACE_URI) && attrDefElement
091: .getLocalName().equals(SVG_VIEW_TAG))) {
092: attrDefElement = getClosestAncestorSVGElement(e);
093: }
094:
095: // 'viewBox'
096: float[] vb;
097: if (vh.hasViewBox) {
098: vb = vh.viewBox;
099: } else {
100: String viewBoxStr = attrDefElement.getAttributeNS(null,
101: SVG_VIEW_BOX_ATTRIBUTE);
102: vb = parseViewBoxAttribute(attrDefElement, viewBoxStr, ctx);
103: }
104:
105: // 'preserveAspectRatio'
106: short align;
107: boolean meet;
108: if (vh.hasPreserveAspectRatio) {
109: align = vh.align;
110: meet = vh.meet;
111: } else {
112: String aspectRatio = attrDefElement.getAttributeNS(null,
113: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE);
114: PreserveAspectRatioParser pp = new PreserveAspectRatioParser();
115: ViewHandler ph = new ViewHandler();
116: pp.setPreserveAspectRatioHandler(ph);
117: try {
118: pp.parse(aspectRatio);
119: } catch (ParseException pEx) {
120: throw new BridgeException(ctx, attrDefElement, pEx,
121: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
122: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE,
123: aspectRatio, pEx });
124: }
125: align = ph.align;
126: meet = ph.meet;
127: }
128:
129: // the additional transform that may appear on the URI
130: AffineTransform transform = getPreserveAspectRatioTransform(vb,
131: align, meet, w, h);
132: if (vh.hasTransform) {
133: transform.concatenate(vh.getAffineTransform());
134: }
135: return transform;
136: }
137:
138: /**
139: * Returns the closest svg element ancestor of the specified element.
140: *
141: * @param e the element on which to start the svg element lookup
142: */
143: private static Element getClosestAncestorSVGElement(Element e) {
144: for (Node n = e; n != null
145: && n.getNodeType() == Node.ELEMENT_NODE; n = n
146: .getParentNode()) {
147: Element tmp = (Element) n;
148: if (tmp.getNamespaceURI().equals(SVG_NAMESPACE_URI)
149: && tmp.getLocalName().equals(SVG_SVG_TAG)) {
150: return tmp;
151: }
152: }
153: return null;
154: }
155:
156: /**
157: * Returns the transformation matrix to apply to initalize a viewport or
158: * null if the specified viewBox disables the rendering of the element.
159: *
160: * @deprecated Replaced by {@link
161: * #getPreserveAspectRatioTransform(Element,float,float,BridgeContext)},
162: * which has more accurate error reporting.
163: * @param e the element with a viewbox
164: * @param w the width of the effective viewport
165: * @param h The height of the effective viewport
166: */
167: public static AffineTransform getPreserveAspectRatioTransform(
168: Element e, float w, float h) {
169: return getPreserveAspectRatioTransform(e, w, h, null);
170: }
171:
172: /**
173: * Returns the transformation matrix to apply to initalize a viewport or
174: * null if the specified viewBox disables the rendering of the element.
175: *
176: * @param e the element with a viewbox
177: * @param w the width of the effective viewport
178: * @param h The height of the effective viewport
179: * @param ctx The BridgeContext to use for error information
180: */
181: public static AffineTransform getPreserveAspectRatioTransform(
182: Element e, float w, float h, BridgeContext ctx) {
183: String viewBox = e.getAttributeNS(null, SVG_VIEW_BOX_ATTRIBUTE);
184:
185: String aspectRatio = e.getAttributeNS(null,
186: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE);
187:
188: return getPreserveAspectRatioTransform(e, viewBox, aspectRatio,
189: w, h, ctx);
190: }
191:
192: /**
193: * Returns the transformation matrix to apply to initalize a viewport or
194: * null if the specified viewBox disables the rendering of the element.
195: *
196: * @param e the element with a viewbox
197: * @param viewBox the viewBox definition
198: * @param w the width of the effective viewport
199: * @param h The height of the effective viewport
200: * @param ctx The BridgeContext to use for error information
201: */
202: public static AffineTransform getPreserveAspectRatioTransform(
203: Element e, String viewBox, String aspectRatio, float w,
204: float h, BridgeContext ctx) {
205:
206: // no viewBox specified
207: if (viewBox.length() == 0) {
208: return new AffineTransform();
209: }
210: float[] vb = parseViewBoxAttribute(e, viewBox, ctx);
211:
212: // 'preserveAspectRatio' attribute
213: PreserveAspectRatioParser p = new PreserveAspectRatioParser();
214: ViewHandler ph = new ViewHandler();
215: p.setPreserveAspectRatioHandler(ph);
216: try {
217: p.parse(aspectRatio);
218: } catch (ParseException pEx) {
219: throw new BridgeException(ctx, e, pEx,
220: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
221: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE,
222: aspectRatio, pEx });
223: }
224:
225: return getPreserveAspectRatioTransform(vb, ph.align, ph.meet,
226: w, h);
227: }
228:
229: /**
230: * Returns the transformation matrix to apply to initalize a viewport or
231: * null if the specified viewBox disables the rendering of the element.
232: *
233: * @param e the element with a viewbox
234: * @param vb the viewBox definition as float
235: * @param w the width of the effective viewport
236: * @param h The height of the effective viewport
237: * @param ctx The BridgeContext to use for error information
238: */
239: public static AffineTransform getPreserveAspectRatioTransform(
240: Element e, float[] vb, float w, float h, BridgeContext ctx) {
241:
242: String aspectRatio = e.getAttributeNS(null,
243: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE);
244:
245: // 'preserveAspectRatio' attribute
246: PreserveAspectRatioParser p = new PreserveAspectRatioParser();
247: ViewHandler ph = new ViewHandler();
248: p.setPreserveAspectRatioHandler(ph);
249: try {
250: p.parse(aspectRatio);
251: } catch (ParseException pEx) {
252: throw new BridgeException(ctx, e, pEx,
253: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
254: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE,
255: aspectRatio, pEx });
256: }
257:
258: return getPreserveAspectRatioTransform(vb, ph.align, ph.meet,
259: w, h);
260: }
261:
262: /**
263: * Returns the transformation matrix to apply to initalize a viewport or
264: * null if the specified viewBox disables the rendering of the element.
265: *
266: * @param e the element with a viewbox
267: * @param vb the viewBox definition as float
268: * @param w the width of the effective viewport
269: * @param h The height of the effective viewport
270: * @param aPAR The animated preserveAspectRatio value
271: * @param ctx The BridgeContext to use for error information
272: */
273: public static AffineTransform getPreserveAspectRatioTransform(
274: Element e, float[] vb, float w, float h,
275: SVGAnimatedPreserveAspectRatio aPAR, BridgeContext ctx) {
276:
277: // 'preserveAspectRatio' attribute
278: try {
279: SVGPreserveAspectRatio pAR = aPAR.getAnimVal();
280: short align = pAR.getAlign();
281: boolean meet = pAR.getMeetOrSlice() == SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET;
282: return getPreserveAspectRatioTransform(vb, align, meet, w,
283: h);
284: } catch (LiveAttributeException ex) {
285: throw new BridgeException(ctx, ex);
286: }
287: }
288:
289: /**
290: * Parses a viewBox attribute.
291: *
292: * @param e the element whose viewBox attribute value is being parsed
293: * @param value the viewBox
294: * @param ctx the BridgeContext to use for error information
295: * @return The 4 viewbox components or null.
296: */
297: public static float[] parseViewBoxAttribute(Element e,
298: String value, BridgeContext ctx) {
299: if (value.length() == 0) {
300: return null;
301: }
302: int i = 0;
303: float[] vb = new float[4];
304: StringTokenizer st = new StringTokenizer(value, " ,");
305: try {
306: while (i < 4 && st.hasMoreTokens()) {
307: vb[i] = Float.parseFloat(st.nextToken());
308: i++;
309: }
310: } catch (NumberFormatException nfEx) {
311: throw new BridgeException(ctx, e, nfEx,
312: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
313: SVG_VIEW_BOX_ATTRIBUTE, value, nfEx });
314: }
315: if (i != 4) {
316: throw new BridgeException(ctx, e,
317: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
318: SVG_VIEW_BOX_ATTRIBUTE, value });
319: }
320: // A negative value for <width> or <height> is an error
321: if (vb[2] < 0 || vb[3] < 0) {
322: throw new BridgeException(ctx, e,
323: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
324: SVG_VIEW_BOX_ATTRIBUTE, value });
325: }
326: // A value of zero for width or height disables rendering of the element
327: if (vb[2] == 0 || vb[3] == 0) {
328: return null; // <!> FIXME : must disable !
329: }
330: return vb;
331: }
332:
333: /**
334: * Returns the preserveAspectRatio transform according to the specified
335: * parameters.
336: *
337: * @param vb the viewBox definition
338: * @param align the alignment definition
339: * @param meet true means 'meet', false means 'slice'
340: * @param w the width of the region in which the document has to fit into
341: * @param h the height of the region in which the document has to fit into
342: */
343: public static AffineTransform getPreserveAspectRatioTransform(
344: float[] vb, short align, boolean meet, float w, float h) {
345: if (vb == null) {
346: return new AffineTransform();
347: }
348:
349: AffineTransform result = new AffineTransform();
350: float vpar = vb[2] / vb[3];
351: float svgar = w / h;
352:
353: if (align == SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_NONE) {
354: result.scale(w / vb[2], h / vb[3]);
355: result.translate(-vb[0], -vb[1]);
356: } else if (vpar < svgar && meet || vpar >= svgar && !meet) {
357: float sf = h / vb[3];
358: result.scale(sf, sf);
359: switch (align) {
360: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN:
361: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID:
362: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMAX:
363: result.translate(-vb[0], -vb[1]);
364: break;
365: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN:
366: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID:
367: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMAX:
368: result.translate(-vb[0] - (vb[2] - w * vb[3] / h) / 2,
369: -vb[1]);
370: break;
371: default:
372: result.translate(-vb[0] - (vb[2] - w * vb[3] / h),
373: -vb[1]);
374: }
375: } else {
376: float sf = w / vb[2];
377: result.scale(sf, sf);
378: switch (align) {
379: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN:
380: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN:
381: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN:
382: result.translate(-vb[0], -vb[1]);
383: break;
384: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID:
385: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID:
386: case SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID:
387: result.translate(-vb[0], -vb[1]
388: - (vb[3] - h * vb[2] / w) / 2);
389: break;
390: default:
391: result.translate(-vb[0], -vb[1]
392: - (vb[3] - h * vb[2] / w));
393: }
394: }
395: return result;
396: }
397:
398: /**
399: * This class can be used to store the value of the attribute viewBox or can
400: * also be used to store the various attribute value that can be specified
401: * on a SVG URI fragments.
402: */
403: protected static class ViewHandler extends AWTTransformProducer
404: implements FragmentIdentifierHandler {
405:
406: /**
407: * Constructs a new <tt>ViewHandler</tt> instance.
408: */
409: protected ViewHandler() {
410: }
411:
412: //////////////////////////////////////////////////////////////////////
413: // TransformListHandler
414: //////////////////////////////////////////////////////////////////////
415:
416: public boolean hasTransform;
417:
418: public void endTransformList() throws ParseException {
419: super .endTransformList();
420: hasTransform = true;
421: }
422:
423: //////////////////////////////////////////////////////////////////////
424: // FragmentIdentifierHandler
425: //////////////////////////////////////////////////////////////////////
426:
427: public boolean hasId;
428: public boolean hasViewBox;
429: public boolean hasViewTargetParams;
430: public boolean hasZoomAndPanParams;
431:
432: public String id;
433: public float[] viewBox;
434: public String viewTargetParams;
435: public boolean isMagnify;
436:
437: /**
438: * Invoked when the fragment identifier starts.
439: * @exception ParseException if an error occured while processing the
440: * fragment identifier
441: */
442: public void startFragmentIdentifier() throws ParseException {
443: }
444:
445: /**
446: * Invoked when an ID has been parsed.
447: * @param s The string that represents the parsed ID.
448: * @exception ParseException if an error occured while processing the
449: * fragment identifier
450: */
451: public void idReference(String s) throws ParseException {
452: id = s;
453: hasId = true;
454: }
455:
456: /**
457: * Invoked when 'viewBox(x,y,width,height)' has been parsed.
458: * @param x the viewbox x coordinate
459: * @param y the viewbox y coordinate
460: * @param width the viewbox width
461: * @param height the viewbox height
462: * @exception ParseException if an error occured while processing the
463: * fragment identifier
464: */
465: public void viewBox(float x, float y, float width, float height)
466: throws ParseException {
467:
468: hasViewBox = true;
469: viewBox = new float[4];
470: viewBox[0] = x;
471: viewBox[1] = y;
472: viewBox[2] = width;
473: viewBox[3] = height;
474: }
475:
476: /**
477: * Invoked when a view target specification starts.
478: * @exception ParseException if an error occured while processing the
479: * fragment identifier
480: */
481: public void startViewTarget() throws ParseException {
482: }
483:
484: /**
485: * Invoked when a identifier has been parsed within a view target
486: * specification.
487: * @param name the target name.
488: * @exception ParseException if an error occured while processing the
489: * fragment identifier
490: */
491: public void viewTarget(String name) throws ParseException {
492: viewTargetParams = name;
493: hasViewTargetParams = true;
494: }
495:
496: /**
497: * Invoked when a view target specification ends.
498: * @exception ParseException if an error occured while processing the
499: * fragment identifier
500: */
501: public void endViewTarget() throws ParseException {
502: }
503:
504: /**
505: * Invoked when a 'zoomAndPan' specification has been parsed.
506: * @param magnify true if 'magnify' has been parsed.
507: * @exception ParseException if an error occured while processing the
508: * fragment identifier
509: */
510: public void zoomAndPan(boolean magnify) {
511: isMagnify = magnify;
512: hasZoomAndPanParams = true;
513: }
514:
515: /**
516: * Invoked when the fragment identifier ends.
517: * @exception ParseException if an error occured while processing the
518: * fragment identifier
519: */
520: public void endFragmentIdentifier() throws ParseException {
521: }
522:
523: //////////////////////////////////////////////////////////////////////
524: // PreserveAspectRatioHandler
525: //////////////////////////////////////////////////////////////////////
526:
527: public boolean hasPreserveAspectRatio;
528:
529: public short align;
530: public boolean meet = true;
531:
532: /**
533: * Invoked when the PreserveAspectRatio parsing starts.
534: * @exception ParseException if an error occured while processing
535: * the transform
536: */
537: public void startPreserveAspectRatio() throws ParseException {
538: }
539:
540: /**
541: * Invoked when 'none' been parsed.
542: * @exception ParseException if an error occured while processing
543: * the transform
544: */
545: public void none() throws ParseException {
546: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_NONE;
547: }
548:
549: /**
550: * Invoked when 'xMaxYMax' has been parsed.
551: * @exception ParseException if an error occured while processing
552: * the transform
553: */
554: public void xMaxYMax() throws ParseException {
555: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMAX;
556: }
557:
558: /**
559: * Invoked when 'xMaxYMid' has been parsed.
560: * @exception ParseException if an error occured while processing
561: * the transform
562: */
563: public void xMaxYMid() throws ParseException {
564: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID;
565: }
566:
567: /**
568: * Invoked when 'xMaxYMin' has been parsed.
569: * @exception ParseException if an error occured while processing
570: * the transform
571: */
572: public void xMaxYMin() throws ParseException {
573: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN;
574: }
575:
576: /**
577: * Invoked when 'xMidYMax' has been parsed.
578: * @exception ParseException if an error occured while processing
579: * the transform
580: */
581: public void xMidYMax() throws ParseException {
582: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMAX;
583: }
584:
585: /**
586: * Invoked when 'xMidYMid' has been parsed.
587: * @exception ParseException if an error occured while processing
588: * the transform
589: */
590: public void xMidYMid() throws ParseException {
591: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID;
592: }
593:
594: /**
595: * Invoked when 'xMidYMin' has been parsed.
596: * @exception ParseException if an error occured while processing
597: * the transform
598: */
599: public void xMidYMin() throws ParseException {
600: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN;
601: }
602:
603: /**
604: * Invoked when 'xMinYMax' has been parsed.
605: * @exception ParseException if an error occured while processing
606: * the transform
607: */
608: public void xMinYMax() throws ParseException {
609: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMAX;
610: }
611:
612: /**
613: * Invoked when 'xMinYMid' has been parsed.
614: * @exception ParseException if an error occured while processing
615: * the transform
616: */
617: public void xMinYMid() throws ParseException {
618: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID;
619: }
620:
621: /**
622: * Invoked when 'xMinYMin' has been parsed.
623: * @exception ParseException if an error occured while processing
624: * the transform
625: */
626: public void xMinYMin() throws ParseException {
627: align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN;
628: }
629:
630: /**
631: * Invoked when 'meet' has been parsed.
632: * @exception ParseException if an error occured while processing
633: * the transform
634: */
635: public void meet() throws ParseException {
636: meet = true;
637: }
638:
639: /**
640: * Invoked when 'slice' has been parsed.
641: * @exception ParseException if an error occured while processing
642: * the transform
643: */
644: public void slice() throws ParseException {
645: meet = false;
646: }
647:
648: /**
649: * Invoked when the PreserveAspectRatio parsing ends.
650: * @exception ParseException if an error occured while processing
651: * the transform
652: */
653: public void endPreserveAspectRatio() throws ParseException {
654: hasPreserveAspectRatio = true;
655: }
656: }
657: }
|