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.tools.ant.taskdefs.optional;
020:
021: // -- Ant classes ------------------------------------------------------------
022: import org.apache.tools.ant.Project;
023: import org.apache.tools.ant.BuildException;
024: import org.apache.tools.ant.Task;
025: import org.apache.tools.ant.DirectoryScanner;
026: import org.apache.tools.ant.taskdefs.MatchingTask;
027: import org.apache.tools.ant.types.EnumeratedAttribute;
028: import org.apache.tools.ant.types.FileSet;
029: import org.apache.tools.ant.util.JAXPUtils;
030:
031: // -- Batik classes ----------------------------------------------------------
032: import org.apache.batik.apps.rasterizer.SVGConverter;
033: import org.apache.batik.apps.rasterizer.DestinationType;
034: import org.apache.batik.apps.rasterizer.SVGConverterException;
035: import org.apache.batik.transcoder.image.JPEGTranscoder;
036: import org.apache.batik.util.XMLResourceDescriptor;
037:
038: // -- SAX classes ------------------------------------------------------------
039: import org.xml.sax.XMLReader;
040:
041: // -- Java SDK classes -------------------------------------------------------
042: import java.awt.geom.Rectangle2D;
043: import java.awt.Color;
044: import java.io.File;
045: import java.util.StringTokenizer;
046: import java.util.Vector;
047: import java.util.Iterator;
048: import java.util.ArrayList;
049: import java.util.List;
050:
051: /**
052: * This Ant task can be used to convert SVG images to raster images.
053: *
054: * <p>Possible result raster image formats are PNG, JPEG, TIFF, and PDF.
055: * Batik {@link SVGConverter} is used to execute the conversion. You need
056: * <em>Batik</em> to produce the first three raster image types and
057: * <em>FOP</em> to produce PDF.</p>
058: *
059: * @see SVGConverter SVGConverter
060: * @see org.apache.batik.apps.rasterizer.Main Main
061: *
062: * @author <a href="mailto:ruini@iki.fi">Henri Ruini</a>
063: * @version $Id: RasterizerTask.java 475685 2006-11-16 11:16:05Z cam $
064: */
065: public class RasterizerTask extends MatchingTask {
066:
067: // -- Constants ----------------------------------------------------------
068: /**
069: * Default quality value for JPEGs. This value is used when
070: * the user doesn't set the quality.
071: */
072: private static final float DEFAULT_QUALITY = 0.99f;
073: /**
074: * Magic string indicating that any JAXP conforming XML parser can
075: * be used.
076: */
077: private static final String JAXP_PARSER = "jaxp";
078:
079: // -- Variables ----------------------------------------------------------
080: /** Result image type. The default is PNG. */
081: protected DestinationType resultType = DestinationType.PNG;
082:
083: /** Output image height. */
084: protected float height = Float.NaN;
085: /** Output image width. */
086: protected float width = Float.NaN;
087: /** Maximum output image height. */
088: protected float maxHeight = Float.NaN;
089: /** Maximum output image width. */
090: protected float maxWidth = Float.NaN;
091: /** Output image quality. */
092: protected float quality = Float.NaN;
093: /** Output Area of Interest (AOI) area. */
094: protected String area = null;
095: /** Output background color. */
096: protected String background = null;
097: /** Media type of CSS file used to produce output images. */
098: protected String mediaType = null;
099: /** Output pixel size - dots per inch. */
100: protected float dpi = Float.NaN;
101: /** Output image language. */
102: protected String language = null;
103: /** XML parser class currently in use. */
104: protected String readerClassName = XMLResourceDescriptor
105: .getXMLParserClassName();
106:
107: /** Source image path. */
108: protected File srcFile = null;
109: /** Destination image path. */
110: protected File destFile = null;
111: /** Source directory of images. */
112: protected File srcDir = null;
113: /** Destination directory for output images. */
114: protected File destDir = null;
115: /** Contents of <code>fileset</code> elements. */
116: protected Vector filesets = new Vector();
117:
118: /** Converter object used to convert SVG images to raster images. */
119: protected SVGConverter converter;
120:
121: // -- Constructors -------------------------------------------------------
122: /**
123: * Initializes a new rasterizer task.
124: */
125: public RasterizerTask() {
126: converter = new SVGConverter(
127: new RasterizerTaskSVGConverterController(this ));
128: }
129:
130: // -- Methods required by Ant --------------------------------------------
131: /**
132: * Gets <code>result</code> attribute value.
133: *
134: * <p>See the documentation for valid values.</p>
135: *
136: * @param type Attribute value.
137: */
138: public void setResult(ValidImageTypes type) {
139: this .resultType = getResultType(type.getValue());
140: }
141:
142: /**
143: * Gets <code>height</code> attribute value.
144: *
145: * <p>The attribute is optional.</p>
146: *
147: * @param height Attribute value.
148: */
149: public void setHeight(float height) {
150: this .height = height;
151: }
152:
153: /**
154: * Gets <code>width</code> attribute value.
155: *
156: * <p>The attribute is optional.</p>
157: *
158: * @param width Attribute value.
159: */
160: public void setWidth(float width) {
161: this .width = width;
162: }
163:
164: /**
165: * Gets <code>maxheight</code> attribute value.
166: *
167: * <p>The attribute is optional.</p>
168: *
169: * @param height Attribute value.
170: */
171: public void setMaxheight(float height) {
172: this .maxHeight = height;
173: }
174:
175: /**
176: * Gets <code>maxwidth</code> attribute value.
177: *
178: * <p>The attribute is optional.</p>
179: *
180: * @param width Attribute value.
181: */
182: public void setMaxwidth(float width) {
183: this .maxWidth = width;
184: }
185:
186: /**
187: * Gets <code>quality</code> attribute value.
188: *
189: * <p>The value have to be a float between 0 and 1 excluded.
190: * The attribute is optional.</p>
191: *
192: * @param quality Attribute value.
193: */
194: public void setQuality(float quality) {
195: this .quality = quality;
196: }
197:
198: /**
199: * Gets <code>area</code> attribute value.
200: *
201: * <p>The value have to be four integers separated with whitespaces or
202: * commas.
203: * The attribute is optional.</p>
204: *
205: * @param area Attribute value.
206: */
207: public void setArea(String area) {
208: this .area = area;
209: }
210:
211: /**
212: * Gets <code>bg</code> attribute value.
213: *
214: * <p>The value have to be three or four integers separated with
215: * whitespaces or commas.
216: * The attribute is optional.</p>
217: *
218: * @param bg Attribute value.
219: */
220: public void setBg(String bg) {
221: this .background = bg;
222: }
223:
224: /**
225: * Gets <code>media</code> attribute value.
226: *
227: * <p>The value have to a media type defined in CSS2 specifications.
228: * Only visual media group is supported.</p>
229: *
230: * @param media Attribute value.
231: */
232: public void setMedia(ValidMediaTypes media) {
233: this .mediaType = media.getValue();
234: }
235:
236: /**
237: * Gets <code>dpi</code> attribute value.
238: *
239: * <p>The value have to be a positive float.
240: * The attribute is optional.</p>
241: *
242: * @param dpi Attribute value.
243: */
244: public void setDpi(float dpi) {
245: this .dpi = dpi;
246: }
247:
248: /**
249: * Gets <code>lang</code> attribute value.
250: *
251: * <p>See SVG specification for valid values.
252: * The attribute is optional.</p>
253: *
254: * @param lang Attribute value.
255: */
256: public void setLang(String language) {
257: this .language = language;
258: }
259:
260: /**
261: * Sets classname of an XML parser.
262: * The attribute is optional.
263: *
264: * @param value Java classname of an XML parser.
265: */
266: public void setClassname(String value) {
267: this .readerClassName = value;
268: }
269:
270: /**
271: * Gets <code>src</code> attribute value.
272: *
273: * <p>One of the following have to have a value: this attribute,
274: * <code>srcdir</code> attribute or <code>fileset</code> element.</p>
275: *
276: * @param file Attribute value.
277: */
278: public void setSrc(File file) {
279: this .srcFile = file;
280: }
281:
282: /**
283: * Gets <code>dest</code> attribute value.
284: *
285: * <p>The attribute have to have a value when
286: * <code>src</code> attribute has a value.</p>
287: *
288: * @param file Attribute value.
289: */
290: public void setDest(File file) {
291: this .destFile = file;
292: }
293:
294: /**
295: * Gets <code>srcdir</code> attribute value.
296: *
297: * <p>If <code>srcfile</code> attribute doesn't have a value then
298: * either this attribute have to have a value or the element have
299: * to contain <code>fileset</code> elements.</p>
300: *
301: * @param dir Attribute value.
302: */
303: public void setSrcdir(File dir) {
304: this .srcDir = dir;
305: }
306:
307: /**
308: * Gets <code>destdir</code> attribute value.
309: *
310: * <p>This attribute have to have a value if either
311: * <code>srcdir</code> attribute or <code>fileset</code> elements
312: * exists.</p>
313: *
314: * @param dir Attribute value.
315: */
316: public void setDestdir(File dir) {
317: this .destDir = dir;
318: }
319:
320: /**
321: * Reads <code>fileset</code> elements.
322: *
323: * <p><code>fileset</code> elements can be used when there are many files
324: * to be rasterized.</p>
325: *
326: * @param set <code>fileset</code> elements
327: */
328: public void addFileset(FileSet set) {
329: filesets.addElement(set);
330: }
331:
332: /**
333: * Validates and sets input values and files, then starts the conversion
334: * process.
335: *
336: * <p>See Ant documentation to find out more about the meaning of
337: * <code>execute</code> in Ant task.</p>
338: *
339: * @throws BuildException Parameters are not set correctly or the conversion fails.
340: */
341: public void execute() throws BuildException {
342:
343: String[] sources; // Array of input files.
344:
345: // Store default XML parser information and set user class.
346: String defaultParser = XMLResourceDescriptor
347: .getXMLParserClassName();
348: // Throws BuildException.
349: XMLResourceDescriptor
350: .setXMLParserClassName(getParserClassName(readerClassName));
351:
352: try {
353: // Check file and directory values.
354: if (this .srcFile != null) {
355: if (this .destFile == null) {
356: throw new BuildException(
357: "dest attribute is not set.");
358: }
359: } else {
360: if ((this .srcDir == null) && (filesets.size() == 0)) {
361: throw new BuildException(
362: "No input files! Either srcdir or fileset have to be set.");
363: }
364: if (this .destDir == null) {
365: throw new BuildException(
366: "destdir attribute is not set!");
367: }
368: }
369:
370: // Throws BuildException.
371: setRasterizingParameters();
372:
373: // Get and set source(s).
374: sources = getSourceFiles();
375: converter.setSources(sources);
376:
377: // Set destination.
378: if (this .srcFile != null) {
379: converter.setDst(this .destFile);
380: } else {
381: converter.setDst(this .destDir);
382: }
383:
384: // Input filenames are stored in the converter and
385: // everything is ready for the conversion.
386:
387: log("Rasterizing " + sources.length
388: + (sources.length == 1 ? " image " : " images ")
389: + "from SVG to " + this .resultType.toString() + ".");
390:
391: try {
392: converter.execute();
393: } catch (SVGConverterException sce) {
394: throw new BuildException(sce.getMessage());
395: }
396: } finally {
397: // Restore default XML parser for the next execute.
398: XMLResourceDescriptor.setXMLParserClassName(defaultParser);
399: }
400: }
401:
402: // -- Internal methods ---------------------------------------------------
403: /**
404: * Checks and sets parameter values to the converter.
405: *
406: * <p>Some invalid values are just swallowed and default values are
407: * used instead. This is done when the invalid value cannot
408: * be correctly differentiated from the default value and the default
409: * value doesn't cause any harm. <code>BuildException</code> is thrown
410: * if the invalid value is clearly recognized.</p>
411: *
412: * @throws BuildException Invalid parameter value.
413: */
414: protected void setRasterizingParameters() throws BuildException {
415: if (this .resultType != null) {
416: converter.setDestinationType(this .resultType);
417: } else {
418: throw new BuildException(
419: "Unknown value in result parameter.");
420: }
421: // Set size values.
422: if (!Float.isNaN(this .width)) {
423: if (this .width < 0) {
424: throw new BuildException(
425: "Value of width parameter must positive.");
426: }
427: converter.setWidth(this .width);
428: }
429: if (!Float.isNaN(this .height)) {
430: if (this .height < 0) {
431: throw new BuildException(
432: "Value of height parameter must positive.");
433: }
434: converter.setHeight(this .height);
435: }
436: // Set maximum size values.
437: if (!Float.isNaN(this .maxWidth)) {
438: if (this .maxWidth < 0) {
439: throw new BuildException(
440: "Value of maxwidth parameter must positive.");
441: }
442: converter.setMaxWidth(this .maxWidth);
443: }
444: if (!Float.isNaN(this .maxHeight)) {
445: if (this .maxHeight < 0) {
446: throw new BuildException(
447: "Value of maxheight parameter must positive.");
448: }
449: converter.setMaxHeight(this .maxHeight);
450: }
451: // The quality is just swallowed if the result type is not correct.
452: if (allowedToSetQuality(resultType)) {
453: if (!Float.isNaN(this .quality)) {
454: // Throws BuildException.
455: converter.setQuality(getQuality(this .quality));
456: } else {
457: // Set something to quiet irritating error
458: // from JPEGTranscoder.
459: converter.setQuality(DEFAULT_QUALITY);
460: }
461: }
462: if (this .area != null) {
463: // Throws BuildException.
464: converter.setArea(getAreaOfInterest(this .area));
465: }
466: if (this .background != null) {
467: // Throws BuildException.
468: converter
469: .setBackgroundColor(getBackgroundColor(this .background));
470: }
471: if (this .mediaType != null) {
472: // Ant takes care of the correct media type values.
473: converter.setMediaType(this .mediaType);
474: }
475: if (!Float.isNaN(this .dpi)) {
476: if (this .dpi < 0) {
477: throw new BuildException(
478: "Value of dpi parameter must positive.");
479: }
480: // The calculation is the same as 2.54/dpi*10 where
481: converter.setPixelUnitToMillimeter(25.4f / this .dpi);
482: }
483: if (this .language != null) {
484: converter.setLanguage(this .language);
485: }
486: }
487:
488: /**
489: * Gets source files from the task parameters and child elements,
490: * combines those to a one list, and returns the list.
491: *
492: * @return Array of source filename strings.
493: */
494: protected String[] getSourceFiles() {
495:
496: List inputFiles = new ArrayList(); // Input files in temp list.
497:
498: if (this .srcFile != null) {
499: // Only one source and destination file have been set.
500: inputFiles.add(this .srcFile.getAbsolutePath());
501: } else {
502: // Unknown number of files have to be converted. destdir
503: // attribute and either srcdir attribute or fileset element
504: // have been set.
505:
506: // Read source files from the child patterns.
507: // The value of srcdir attribute overrides the dir attribute in
508: // fileset element.
509: if (this .srcDir != null) {
510: // fileset is declared in the super class.
511: // Scan to get all the files in srcdir directory that
512: // should be in input files.
513: fileset.setDir(this .srcDir);
514: DirectoryScanner ds = fileset
515: .getDirectoryScanner(project);
516: String[] includedFiles = ds.getIncludedFiles();
517: // Add file and its path to the input file vector.
518: for (int j = 0; j < includedFiles.length; j++) {
519: File newFile = new File(srcDir.getPath(),
520: includedFiles[j]);
521: inputFiles.add(newFile.getAbsolutePath());
522: }
523: }
524: // Read source files from child filesets.
525: for (int i = 0; i < filesets.size(); i++) {
526: // Scan to get all the files in this fileset that
527: // should be in input files.
528: FileSet fs = (FileSet) filesets.elementAt(i);
529: DirectoryScanner ds = fs.getDirectoryScanner(project);
530: String[] includedFiles = ds.getIncludedFiles();
531: // Add file and its path to the input file vector.
532: for (int j = 0; j < includedFiles.length; j++) {
533: File newFile = new File(fs.getDir(project)
534: .getPath(), includedFiles[j]);
535: inputFiles.add(newFile.getAbsolutePath());
536: }
537: }
538: }
539:
540: // Convert List to array and return the array.
541: return (String[]) inputFiles.toArray(new String[0]);
542: }
543:
544: /**
545: * Returns the correct result image type object.
546: *
547: * @param type Result image type as a string.
548: *
549: * @return Result image type as an object or <code>null</code> if the parameter doesn't have corresponding object.
550: */
551: protected DestinationType getResultType(String type) {
552: if (type.equals(DestinationType.PNG_STR)) {
553: return DestinationType.PNG;
554: } else if (type.equals(DestinationType.JPEG_STR)) {
555: return DestinationType.JPEG;
556: } else if (type.equals(DestinationType.TIFF_STR)) {
557: return DestinationType.TIFF;
558: } else if (type.equals(DestinationType.PDF_STR)) {
559: return DestinationType.PDF;
560: }
561: return null;
562: }
563:
564: /**
565: * Checks if the quality value can be set. Only result image type
566: * is checked.
567: *
568: * @param type Result image type.
569: *
570: * @return <code>true</code> if the quality can be set and <code>false</code> otherwise.
571: */
572: protected boolean allowedToSetQuality(DestinationType type) {
573: if (!type.toString().equals(DestinationType.JPEG_STR)) {
574: return false;
575: }
576: return true;
577: }
578:
579: /**
580: * Returns a valid quality value.
581: *
582: * @param quality Input quality value to be tested.
583: *
584: * @return Valid quality value.
585: *
586: * @throws BuildException Input quality value is not valid.
587: */
588: protected float getQuality(float quality) throws BuildException {
589: if ((quality <= 0) || (quality >= 1)) {
590: throw new BuildException(
591: "quality parameter value have to be between 0 and 1.");
592: }
593: return quality;
594: }
595:
596: /**
597: * Returns a valid Area of Interest (AOI) as a Rectangle2D object.
598: *
599: * @param area AOI input area.
600: *
601: * @return A valid AOI rectangle.
602: *
603: * @throws BuildException AOI area is invalid.
604: */
605: protected Rectangle2D getAreaOfInterest(String area)
606: throws BuildException {
607:
608: float x; // Upper left x point value of the area.
609: float y; // Upper left y point value of the area.
610: float width; // Area width value.
611: float height; // Area height value.
612: String token; // A token from the input string.
613: StringTokenizer tokenizer = new StringTokenizer(area,
614: ", \t\n\r\f");
615: // Input string tokenizer.
616:
617: if (tokenizer.countTokens() != 4) {
618: throw new BuildException(
619: "There must be four numbers in the area parameter: x, y, width, and height.");
620: }
621: try {
622: x = Float.parseFloat(tokenizer.nextToken());
623: y = Float.parseFloat(tokenizer.nextToken());
624: width = Float.parseFloat(tokenizer.nextToken());
625: height = Float.parseFloat(tokenizer.nextToken());
626: } catch (NumberFormatException nfe) {
627: throw new BuildException("Invalid area parameter value: "
628: + nfe.toString());
629: }
630:
631: // Negative values are not allowed.
632: if ((x < 0) || (y < 0) || (width < 0) || (height < 0)) {
633: throw new BuildException(
634: "Negative values are not allowed in area parameter.");
635: }
636:
637: return new Rectangle2D.Float(x, y, width, height);
638: }
639:
640: /**
641: * Returns a valid background color object.
642: *
643: * @param argb String containing color channel values.
644: *
645: * @return A valid background color.
646: *
647: * @throws BuildException Input value is invalid.
648: */
649: protected Color getBackgroundColor(String argb)
650: throws BuildException {
651:
652: int a; // Value of the alpha channel.
653: int r; // Value of the red channel.
654: int g; // Value of the green channel.
655: int b; // Value of the blue channel.
656: String token; // A token from the input string.
657: StringTokenizer tokenizer = new StringTokenizer(argb,
658: ", \t\n\r\f");
659: // Input string tokenizer.
660:
661: try {
662: if (tokenizer.countTokens() == 3) {
663: // Default alpha channel is opaque.
664: a = 255;
665: } else if (tokenizer.countTokens() == 4) {
666: a = Integer.parseInt(tokenizer.nextToken());
667: } else {
668: throw new BuildException(
669: "There must be either three or four numbers in bg parameter: (alpha,) red, green, and blue.");
670: }
671: r = Integer.parseInt(tokenizer.nextToken());
672: g = Integer.parseInt(tokenizer.nextToken());
673: b = Integer.parseInt(tokenizer.nextToken());
674: } catch (NumberFormatException nfe) {
675: throw new BuildException("Invalid bg parameter value: "
676: + nfe.toString());
677: }
678:
679: // Check that the values are valid.
680: if ((a < 0) || (a > 255) || (r < 0) || (r > 255) || (g < 0)
681: || (g > 255) || (b < 0) || (b > 255)) {
682: throw new BuildException(
683: "bg parameter value is invalid. Numbers have to be between 0 and 255.");
684: }
685:
686: return new Color(r, g, b, a);
687: }
688:
689: /**
690: * Returns name of an XML parser.
691: * Magic string {@link #JAXP_PARSER} is also accepted.
692: *
693: * @param className Name of the XML parser class or a magic string.
694: *
695: * @return Name of an XML parser.
696: *
697: * @throws BuildException Unable to get the name of JAXP parser.
698: */
699: private String getParserClassName(final String className) {
700: String name = className;
701: if (className.equals(JAXP_PARSER)) {
702: // Set first JAXP parser.
703: // Throws BuildException.
704: XMLReader reader = JAXPUtils.getXMLReader();
705: name = reader.getClass().getName();
706: }
707:
708: log("Using class '" + name + "' to parse SVG documents.",
709: Project.MSG_VERBOSE);
710: return name;
711: }
712:
713: // -----------------------------------------------------------------------
714: // Inner classes
715: // -----------------------------------------------------------------------
716:
717: /**
718: * Defines the valid attribute values for <code>result</code> parameter.
719: *
720: * <p>See the Ant documentation for more information.</p>
721: *
722: * @author <a href="mailto:ruini@iki.fi">Henri Ruini</a>
723: * @version $Id: RasterizerTask.java 475685 2006-11-16 11:16:05Z cam $
724: */
725: public static class ValidImageTypes extends EnumeratedAttribute {
726:
727: /**
728: * Defines valid image types.
729: *
730: * @return Array of valid values as strings.
731: */
732: public String[] getValues() {
733: return new String[] { DestinationType.PNG_STR,
734: DestinationType.JPEG_STR, DestinationType.TIFF_STR,
735: DestinationType.PDF_STR };
736: }
737: }
738:
739: /**
740: * Defines the valid attribute values for a media parameter.
741: *
742: * <p>See the Ant documentation for more information.</p>
743: *
744: * @author <a href="mailto:ruini@iki.fi">Henri Ruini</a>
745: * @version $Id: RasterizerTask.java 475685 2006-11-16 11:16:05Z cam $
746: */
747: public static class ValidMediaTypes extends EnumeratedAttribute {
748:
749: /**
750: * Defines valid media types.
751: *
752: * <p>The types are defined in CSS2 specification.
753: * Only visual media group is supported.</p>
754: *
755: * @return Array of valid values as strings.
756: */
757: public String[] getValues() {
758: return new String[] { "all", "handheld", "print",
759: "projection", "screen", "tty", "tv" };
760: }
761: }
762:
763: }
|