--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Abstract base class for converters.
+ */
+public abstract class AbstractBitmapProducer implements BitmapProducer {
+
+ /** Logger */
+ protected static Log log = LogFactory.getLog(AbstractBitmapProducer.class);
+
+ /**
+ * Returns a new JAXP Transformer based on information in the ProducerContext.
+ * @param context context information for the process
+ * @return a new Transformer instance (identity or set up with a stylesheet)
+ * @throws TransformerConfigurationException in case creating the Transformer fails.
+ */
+ protected Transformer getTransformer(ProducerContext context)
+ throws TransformerConfigurationException {
+ if (context.getTemplates() != null) {
+ return context.getTemplates().newTransformer();
+ } else {
+ return context.getTransformerFactory().newTransformer();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.MessageFormat;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.commons.io.IOUtils;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+
+/**
+ * BitmapProducer implementation that uses the PS or PDF renderer and an external converter
+ * to create bitmaps.
+ * <p>
+ * Here's what the configuration element looks like for the class:
+ * <p>
+ * <pre>
+ * <producer classname="<concrete class>">
+ * <converter>mypdf2bmp {0} {1} {2}</converter>
+ * <delete-temp-files>false</delete-temp-files>
+ * </producer>
+ * </pre>
+ * <p>
+ * You will notice the three parameters in curly braces (java.util.MessageFormat style) to the
+ * converter call:
+ * <ul>
+ * <li>0: the input file
+ * <li>1: the output bitmap file
+ * <li>2: the requested resolution in dpi for the generated bitmap
+ * </ul>
+ * <p>
+ * The "delete-temp-files" element is optional and defaults to true.
+ */
+public abstract class AbstractPSPDFBitmapProducer extends AbstractBitmapProducer
+ implements Configurable {
+
+ private String converter;
+ private boolean deleteTempFiles;
+
+ /** @see org.apache.avalon.framework.configuration.Configurable */
+ public void configure(Configuration cfg) throws ConfigurationException {
+ this.converter = cfg.getChild("converter").getValue();
+ this.deleteTempFiles = cfg.getChild("delete-temp-files").getValueAsBoolean(true);
+ }
+
+ /**
+ * Calls an external converter to convert the file generated by FOP to a bitmap.
+ * @param inFile the generated output file to be converted
+ * @param outFile the target filename for the bitmap
+ * @param context context information (required bitmap resolution etc.)
+ * @throws IOException in case the conversion fails
+ */
+ public void convert(File inFile, File outFile, ProducerContext context) throws IOException {
+ outFile.delete();
+
+ //Build command-line
+ String cmd = MessageFormat.format(converter,
+ new Object[] {inFile.toString(), outFile.toString(),
+ Integer.toString(context.getResolution())});
+ ConvertUtils.convert(cmd, null, null, log);
+
+ if (!outFile.exists()) {
+ throw new IOException("The target file has not been generated");
+ }
+ }
+
+ /**
+ * @return the native extension generated by the output format, ex. "ps" or "pdf".
+ */
+ protected abstract String getTargetExtension();
+
+ /**
+ * @return the output format constant for the FOP renderer, i.e. one of Constants.RENDER_*.
+ */
+ protected abstract int getTargetFormat();
+
+ /** @see org.apache.fop.visual.BitmapProducer */
+ public BufferedImage produce(File src, ProducerContext context) {
+ try {
+ FOUserAgent userAgent = new FOUserAgent();
+ userAgent.setResolution(context.getResolution());
+ userAgent.setBaseURL(src.getParentFile().toURL().toString());
+
+ File tempOut = new File(context.getTargetDir(),
+ src.getName() + "." + getTargetExtension());
+ File tempPNG = new File(context.getTargetDir(),
+ src.getName() + "." + getTargetExtension() + ".png");
+ try {
+ OutputStream out = new FileOutputStream(tempOut);
+ out = new BufferedOutputStream(out);
+ try {
+ Fop fop = new Fop(getTargetFormat(), userAgent);
+ fop.setOutputStream(out);
+ SAXResult res = new SAXResult(fop.getDefaultHandler());
+
+ Transformer transformer = getTransformer(context);
+ transformer.transform(new StreamSource(src), res);
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
+
+ convert(tempOut, tempPNG, context);
+ BufferedImage img = BitmapComparator.getImage(tempPNG);
+ return img;
+ } finally {
+ if (deleteTempFiles) {
+ if (!tempOut.delete()) {
+ log.warn("Can't delete " + tempOut);
+ tempOut.deleteOnExit();
+ }
+ if (!tempPNG.delete()) {
+ log.warn("Can't delete " + tempPNG);
+ tempPNG.deleteOnExit();
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.error(e);
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+/**
+ * Abstract base implementation for a RedirectorLineHandler which provides empty notifyStart()
+ * and notifyEnd() methods.
+ */
+public abstract class AbstractRedirectorLineHandler
+ implements RedirectorLineHandler {
+
+ /** @see org.apache.fop.visual.RedirectorLineHandler#notifyStart() */
+ public void notifyStart() {
+ //nop
+ }
+
+ /** @see org.apache.fop.visual.RedirectorLineHandler#notifyEnd() */
+ public void notifyEnd() {
+ //nop
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
+import org.apache.avalon.framework.container.ContainerUtil;
+
+import org.apache.batik.ext.awt.image.codec.PNGEncodeParam;
+import org.apache.batik.ext.awt.image.codec.PNGImageEncoder;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.io.filefilter.SuffixFileFilter;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.layoutengine.LayoutEngineTestSuite;
+
+import org.xml.sax.SAXException;
+
+/**
+ * This class is used to visually diff bitmap images created through various sources.
+ * <p>
+ * Here's what the configuration format looks like:
+ * <p>
+ * <pre>
+ * <batch-diff>
+ * <source-directory>C:/Dev/FOP/trunk/test/layoutengine</source-directory>
+ * <filter-disabled>false</filter-disabled>
+ * <max-files>10</max-files>
+ * <target-directory>C:/Temp/diff-out</target-directory>
+ * <resolution>100</resolution>
+ * <stylesheet>C:/Dev/FOP/trunk/test/layoutengine/testcase2fo.xsl</stylesheet>
+ * <producers>
+ * <producer classname="org.apache.fop.visual.BitmapProducerJava2D">
+ * <delete-temp-files>false</delete-temp-files>
+ * </producer>
+ * <producer classname="org.apache.fop.visual.ReferenceBitmapLoader">
+ * <directory>C:/Temp/diff-bitmaps</directory>
+ * </producer>
+ * </producers>
+ * </batch-diff>
+ * </pre>
+ * <p>
+ * The optional "filter-disabled" element determines whether the source files should be filtered
+ * using the same "disabled-testcases.txt" file used for the layout engine tests. Default: true
+ * <p>
+ * The optional "max-files" element controls how many files at maximum should be processed.
+ * Default is to process all the files found.
+ * <p>
+ * The optional "resolution" element controls the requested bitmap resolution in dpi for the
+ * generated bitmaps. Defaults to 72dpi.
+ * <p>
+ * The optional "stylesheet" element allows you to supply an XSLT stylesheet to preprocess all
+ * source files with. Default: no stylesheet, identity transform.
+ * <p>
+ * The "producers" element contains a list of producer implementations with configuration.
+ * The "classname" attribute specifies the fully qualified class name for the implementation.
+ */
+public class BatchDiffer {
+
+ /** Logger */
+ protected static Log log = LogFactory.getLog(BatchDiffer.class);
+
+ /**
+ * Prints the usage of this app to stdout.
+ */
+ public static void printUsage() {
+ System.out.println("Usage:");
+ System.out.println("java " + BatchDiffer.class.getName() + " <cfgfile>");
+ System.out.println();
+ System.out.println("<cfgfile>: Path to an XML file with the configuration"
+ + " for the batch run.");
+ }
+
+ /**
+ * Saves a BufferedImage as a PNG file.
+ * @param bitmap the bitmap to encode
+ * @param outputFile the target file
+ * @throws IOException in case of an I/O problem
+ */
+ public static void saveAsPNG(BufferedImage bitmap, File outputFile) throws IOException {
+ OutputStream out = new FileOutputStream(outputFile);
+ try {
+ PNGImageEncoder encoder = new PNGImageEncoder(
+ out,
+ PNGEncodeParam.getDefaultEncodeParam(bitmap));
+ encoder.encode(bitmap);
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
+ }
+
+ /**
+ * Runs the batch.
+ * @param cfgFile configuration file to use
+ * @throws ConfigurationException In case of a problem with the configuration
+ * @throws SAXException In case of a problem during SAX processing
+ * @throws IOException In case of a I/O problem
+ */
+ public void runBatch(File cfgFile)
+ throws ConfigurationException, SAXException, IOException {
+ DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
+ Configuration cfg = cfgBuilder.buildFromFile(cfgFile);
+ runBatch(cfg);
+ }
+
+ /**
+ * Runs the batch
+ * @param cfg Configuration for the batch
+ * @throws TransformerConfigurationException
+ */
+ public void runBatch(Configuration cfg) {
+ try {
+ ProducerContext context = new ProducerContext();
+ context.setResolution(cfg.getChild("resolution").getValueAsInteger(72));
+ String xslt = cfg.getChild("stylesheet").getValue(null);
+ if (xslt != null) {
+ try {
+ context.setTemplates(context.getTransformerFactory().newTemplates(
+ new StreamSource(xslt)));
+ } catch (TransformerConfigurationException e) {
+ throw new RuntimeException("Error setting up stylesheet", e);
+ }
+ }
+ BitmapProducer[] producers = getProducers(cfg.getChild("producers"));
+
+ //Set up directories
+ File srcDir = new File(cfg.getChild("source-directory").getValue());
+ if (!srcDir.exists()) {
+ throw new RuntimeException("source-directory does not exist: " + srcDir);
+ }
+ File targetDir = new File(cfg.getChild("target-directory").getValue());
+ targetDir.mkdirs();
+ if (!targetDir.exists()) {
+ throw new RuntimeException("target-directory is invalid: " + targetDir);
+ }
+ context.setTargetDir(targetDir);
+
+ //RUN!
+ BufferedImage[] bitmaps = new BufferedImage[producers.length];
+
+ IOFileFilter filter = new SuffixFileFilter(new String[] {".xml", ".fo"});
+ //Same filtering as in layout engine tests
+ if (cfg.getChild("filter-disabled").getValueAsBoolean(true)) {
+ filter = LayoutEngineTestSuite.decorateWithDisabledList(filter);
+ }
+
+ int maxfiles = cfg.getChild("max-files").getValueAsInteger(-1);
+ Collection files = FileUtils.listFiles(srcDir, filter, null);
+ Iterator i = files.iterator();
+ while (i.hasNext()) {
+ File f = (File)i.next();
+ log.info("---=== " + f + " ===---");
+ for (int j = 0; j < producers.length; j++) {
+ bitmaps[j] = producers[j].produce(f, context);
+ }
+ //Create combined image
+ if (bitmaps[0] == null) {
+ throw new RuntimeException("First producer didn't return a bitmap."
+ + " Cannot continue.");
+ }
+ BufferedImage combined = BitmapComparator.buildCompareImage(bitmaps);
+
+ //Save combined bitmap as PNG file
+ File outputFile = new File(targetDir, f.getName() + "._combined.png");
+ saveAsPNG(combined, outputFile);
+
+ for (int k = 1; k < bitmaps.length; k++) {
+ BufferedImage diff = BitmapComparator.buildDiffImage(bitmaps[0], bitmaps[k]);
+ outputFile = new File(targetDir, f.getName() + "._diff" + k + ".png");
+ saveAsPNG(diff, outputFile);
+ }
+ maxfiles = (maxfiles < 0 ? maxfiles : maxfiles - 1);
+ if (maxfiles == 0) {
+ break;
+ }
+ }
+ } catch (IOException ioe) {
+ log.error("I/O problem while processing", ioe);
+ throw new RuntimeException("I/O problem: " + ioe.getMessage());
+ } catch (ConfigurationException e) {
+ log.error("Error while configuring BatchDiffer", e);
+ throw new RuntimeException("Error while configuring BatchDiffer: " + e.getMessage());
+ }
+ }
+
+ private BitmapProducer[] getProducers(Configuration cfg) {
+ Configuration[] children = cfg.getChildren("producer");
+ BitmapProducer[] producers = new BitmapProducer[children.length];
+ for (int i = 0; i < children.length; i++) {
+ try {
+ Class clazz = Class.forName(children[i].getAttribute("classname"));
+ producers[i] = (BitmapProducer)clazz.newInstance();
+ ContainerUtil.configure(producers[i], children[i]);
+ } catch (Exception e) {
+ throw new RuntimeException("Error while setting up producers", e);
+ }
+ }
+ return producers;
+ }
+
+ /**
+ * Main method.
+ * @param args command-line arguments
+ */
+ public static void main(String[] args) {
+ try {
+ if (args.length == 0) {
+ System.err.println("Configuration file is missing!");
+ printUsage();
+ System.exit(-1);
+ }
+ File cfgFile = new File(args[0]);
+ if (!cfgFile.exists()) {
+ System.err.println("Configuration file cannot be found: " + args[0]);
+ printUsage();
+ System.exit(-1);
+ }
+
+ BatchDiffer differ = new BatchDiffer();
+ differ.runBatch(cfgFile);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.batik.ext.awt.image.GraphicsUtil;
+import org.apache.batik.ext.awt.image.renderable.Filter;
+import org.apache.batik.ext.awt.image.spi.ImageTagRegistry;
+import org.apache.batik.util.ParsedURL;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Helper class to visually compare two bitmap images.
+ * <p>
+ * This class was created by extracting reusable code from
+ * org.apache.batik.test.util.ImageCompareText (Apache Batik)
+ * into this separate class.
+ * <p>
+ * TODO Move as utility class to XML Graphics Commons when possible
+ */
+public class BitmapComparator {
+
+ /**
+ * Builds a new BufferedImage that is the difference between the two input images
+ * @param ref the reference bitmap
+ * @param gen the newly generated bitmap
+ * @return the diff bitmap
+ */
+ public static BufferedImage buildDiffImage(BufferedImage ref,
+ BufferedImage gen) {
+ BufferedImage diff = new BufferedImage(ref.getWidth(),
+ ref.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ WritableRaster refWR = ref.getRaster();
+ WritableRaster genWR = gen.getRaster();
+ WritableRaster dstWR = diff.getRaster();
+
+ boolean refPre = ref.isAlphaPremultiplied();
+ if (!refPre) {
+ ColorModel cm = ref.getColorModel();
+ cm = GraphicsUtil.coerceData(refWR, cm, true);
+ ref = new BufferedImage(cm, refWR, true, null);
+ }
+ boolean genPre = gen.isAlphaPremultiplied();
+ if (!genPre) {
+ ColorModel cm = gen.getColorModel();
+ cm = GraphicsUtil.coerceData(genWR, cm, true);
+ gen = new BufferedImage(cm, genWR, true, null);
+ }
+
+
+ int w = ref.getWidth();
+ int h = ref.getHeight();
+
+ int y, i, val;
+ int [] refPix = null;
+ int [] genPix = null;
+ for (y = 0; y < h; y++) {
+ refPix = refWR.getPixels (0, y, w, 1, refPix);
+ genPix = genWR.getPixels (0, y, w, 1, genPix);
+ for (i = 0; i < refPix.length; i++) {
+ // val = ((genPix[i] - refPix[i]) * 5) + 128;
+ val = ((refPix[i] - genPix[i]) * 10) + 128;
+ if ((val & 0xFFFFFF00) != 0) {
+ if ((val & 0x80000000) != 0) {
+ val = 0;
+ } else {
+ val = 255;
+ }
+ }
+ genPix[i] = val;
+ }
+ dstWR.setPixels(0, y, w, 1, genPix);
+ }
+
+ if (!genPre) {
+ ColorModel cm = gen.getColorModel();
+ cm = GraphicsUtil.coerceData(genWR, cm, false);
+ }
+
+ if (!refPre) {
+ ColorModel cm = ref.getColorModel();
+ cm = GraphicsUtil.coerceData(refWR, cm, false);
+ }
+
+ return diff;
+ }
+
+ /**
+ * Builds a combined image that places a number of images next to each other for
+ * manual, visual comparison.
+ * @param images the array of bitmaps
+ * @return the combined image
+ */
+ public static BufferedImage buildCompareImage(BufferedImage[] images) {
+ BufferedImage cmp = new BufferedImage(
+ images[0].getWidth() * images.length,
+ images[0].getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g = cmp.createGraphics();
+ g.setPaint(Color.white);
+ g.fillRect(0, 0, cmp.getWidth(), cmp.getHeight());
+ int lastWidth = 0;
+ for (int i = 0; i < images.length; i++) {
+ if (lastWidth > 0) {
+ g.translate(lastWidth, 0);
+ }
+ if (images[i] != null) {
+ g.drawImage(images[i], 0, 0, null);
+ lastWidth = images[i].getWidth();
+ } else {
+ lastWidth = 20; //Maybe add a special placeholder image instead later
+ }
+ }
+ g.dispose();
+
+ return cmp;
+ }
+
+ /**
+ * Builds a combined image that places two images next to each other for
+ * manual, visual comparison.
+ * @param ref the reference image
+ * @param gen the actual image
+ * @return the combined image
+ */
+ public static BufferedImage buildCompareImage(BufferedImage ref,
+ BufferedImage gen) {
+ return buildCompareImage(new BufferedImage[] {ref, gen});
+ }
+
+ /**
+ * Loads an image from a URL
+ * @param url the URL to the image
+ * @return the bitmap as BufferedImage
+ * TODO This method doesn't close the InputStream opened by the URL.
+ */
+ public static BufferedImage getImage(URL url) {
+ ImageTagRegistry reg = ImageTagRegistry.getRegistry();
+ Filter filt = reg.readURL(new ParsedURL(url));
+ if (filt == null) {
+ return null;
+ }
+
+ RenderedImage red = filt.createDefaultRendering();
+ if (red == null) {
+ return null;
+ }
+
+ BufferedImage img = new BufferedImage(red.getWidth(),
+ red.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ red.copyData(img.getRaster());
+
+ return img;
+ }
+
+ /**
+ * Loads an image from a URL
+ * @param bitmapFile the bitmap file
+ * @return the bitmap as BufferedImage
+ */
+ public static BufferedImage getImage(File bitmapFile) {
+ try {
+ InputStream in = new java.io.FileInputStream(bitmapFile);
+ try {
+ in = new java.io.BufferedInputStream(in);
+
+ ImageTagRegistry reg = ImageTagRegistry.getRegistry();
+ Filter filt = reg.readStream(in);
+ if (filt == null) {
+ return null;
+ }
+
+ RenderedImage red = filt.createDefaultRendering();
+ if (red == null) {
+ return null;
+ }
+
+ BufferedImage img = new BufferedImage(red.getWidth(),
+ red.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ red.copyData(img.getRaster());
+ return img;
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+/**
+ * Interface for a converter.
+ */
+public interface BitmapProducer {
+
+ /**
+ * Produces a BufferedImage from the source file by invoking the FO processor and
+ * converting the generated output file to a bitmap image if necessary.
+ * @param src the source FO or XML file
+ * @param context context information for the conversion
+ * @return the generated BufferedImage
+ */
+ BufferedImage produce(File src, ProducerContext context);
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.commons.io.IOUtils;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+import org.apache.fop.fo.Constants;
+
+/**
+ * BitmapProducer implementation that uses the Java2DRenderer to create bitmaps.
+ * <p>
+ * Here's what the configuration element looks like for the class:
+ * <p>
+ * <pre>
+ * <producer classname="org.apache.fop.visual.BitmapProducerJava2D">
+ * <delete-temp-files>false</delete-temp-files>
+ * </producer>
+ * </pre>
+ * <p>
+ * The "delete-temp-files" element is optional and defaults to true.
+ */
+public class BitmapProducerJava2D extends AbstractBitmapProducer implements Configurable {
+
+ private boolean deleteTempFiles;
+
+ /** @see org.apache.avalon.framework.configuration.Configurable */
+ public void configure(Configuration cfg) throws ConfigurationException {
+ this.deleteTempFiles = cfg.getChild("delete-temp-files").getValueAsBoolean(true);
+ }
+
+ /** @see org.apache.fop.visual.BitmapProducer */
+ public BufferedImage produce(File src, ProducerContext context) {
+ try {
+ FOUserAgent userAgent = new FOUserAgent();
+ userAgent.setResolution(context.getResolution());
+ userAgent.setBaseURL(src.getParentFile().toURL().toString());
+
+ File outputFile = new File(context.getTargetDir(), src.getName() + ".java2d.png");
+ OutputStream out = new FileOutputStream(outputFile);
+ out = new BufferedOutputStream(out);
+ try {
+ Fop fop = new Fop(Constants.RENDER_PNG, userAgent);
+ fop.setOutputStream(out);
+ SAXResult res = new SAXResult(fop.getDefaultHandler());
+
+ Transformer transformer = getTransformer(context);
+ transformer.transform(new StreamSource(src), res);
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
+
+ BufferedImage img = BitmapComparator.getImage(outputFile);
+ if (deleteTempFiles) {
+ if (!outputFile.delete()) {
+ log.warn("Cannot delete " + outputFile);
+ outputFile.deleteOnExit();
+ }
+ }
+ return img;
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.error(e);
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import org.apache.fop.fo.Constants;
+
+/**
+ * BitmapProducer implementation that uses the PDFRenderer and an external converter
+ * to create bitmaps.
+ * <p>
+ * See the superclass' javadoc for info on the configuration format.
+ */
+public class BitmapProducerPDF extends AbstractPSPDFBitmapProducer {
+
+ /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetExtension() */
+ protected String getTargetExtension() {
+ return "pdf";
+ }
+
+ /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetFormat() */
+ protected int getTargetFormat() {
+ return Constants.RENDER_PDF;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import org.apache.fop.fo.Constants;
+
+/**
+ * BitmapProducer implementation that uses the PSRenderer and an external converter
+ * to create bitmaps.
+ * <p>
+ * See the superclass' javadoc for info on the configuration format.
+ */
+public class BitmapProducerPS extends AbstractPSPDFBitmapProducer {
+
+ /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetExtension() */
+ protected String getTargetExtension() {
+ return "ps";
+ }
+
+ /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetFormat() */
+ protected int getTargetFormat() {
+ return Constants.RENDER_PS;
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Utilities for converting files with external converters.
+ */
+public class ConvertUtils {
+
+ /**
+ * Calls an external converter application (GhostScript, for example).
+ * @param cmd the full command
+ * @param envp array of strings, each element of which has environment variable settings
+ * in format name=value.
+ * @param workDir the working directory of the subprocess, or null if the subprocess should
+ * inherit the working directory of the current process.
+ * @param log the logger to log output by the external application to
+ * @throws IOException in case the external call fails
+ */
+ public static void convert(String cmd, String[] envp, File workDir, final Log log)
+ throws IOException {
+ log.debug(cmd);
+
+ Process process = null;
+ try {
+ process = Runtime.getRuntime().exec(cmd, envp, null);
+
+ //Redirect stderr output
+ RedirectorLineHandler errorHandler = new AbstractRedirectorLineHandler() {
+ public void handleLine(String line) {
+ log.error("ERR> " + line);
+ }
+ };
+ StreamRedirector errorRedirector
+ = new StreamRedirector(process.getErrorStream(), errorHandler);
+
+ //Redirect stdout output
+ RedirectorLineHandler outputHandler = new AbstractRedirectorLineHandler() {
+ public void handleLine(String line) {
+ log.debug("OUT> " + line);
+ }
+ };
+ StreamRedirector outputRedirector
+ = new StreamRedirector(process.getInputStream(), outputHandler);
+ new Thread(errorRedirector).start();
+ new Thread(outputRedirector).start();
+
+ process.waitFor();
+ } catch (java.lang.InterruptedException ie) {
+ throw new IOException("The call to the external converter failed: " + ie.getMessage());
+ } catch (java.io.IOException ioe) {
+ throw new IOException("The call to the external converter failed: " + ioe.getMessage());
+ }
+
+ int exitValue = process.exitValue();
+ if (exitValue != 0) {
+ throw new IOException("The call to the external converter failed. Result: "
+ + exitValue);
+ }
+
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.io.File;
+
+import javax.xml.transform.Templates;
+import javax.xml.transform.TransformerFactory;
+
+/**
+ * Context object for the bitmap production.
+ */
+public class ProducerContext {
+
+ private TransformerFactory tFactory;
+ private Templates templates;
+ private int resolution;
+ private File targetDir;
+
+ /**
+ * @return the TransformerFactory to be used.
+ */
+ public TransformerFactory getTransformerFactory() {
+ if (tFactory == null) {
+ tFactory = TransformerFactory.newInstance();
+ }
+ return tFactory;
+ }
+
+ /**
+ * @return the requested bitmap resolution in dpi for all bitmaps.
+ */
+ public int getResolution() {
+ return resolution;
+ }
+
+ /**
+ * Sets the requested bitmap resolution in dpi for all bitmaps.
+ * @param resolution the resolution in dpi
+ */
+ public void setResolution(int resolution) {
+ this.resolution = resolution;
+ }
+
+ /**
+ * @return the XSLT stylesheet to preprocess the input files with.
+ */
+ public Templates getTemplates() {
+ return templates;
+ }
+
+ /**
+ * Sets an optional XSLT stylesheet which is used to preprocess all input files with.
+ * @param templates the XSLT stylesheet
+ */
+ public void setTemplates(Templates templates) {
+ this.templates = templates;
+ }
+
+ /**
+ * @return the target directory for all produced bitmaps
+ */
+ public File getTargetDir() {
+ return targetDir;
+ }
+
+ /**
+ * Sets the target directory for all produced bitmaps.
+ * @param targetDir the target directory
+ */
+ public void setTargetDir(File targetDir) {
+ this.targetDir = targetDir;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+/**
+ * This interface is used to redirect output from an external application elsewhere.
+ */
+public interface RedirectorLineHandler {
+
+ /**
+ * Called before the first handleLine() call.
+ */
+ void notifyStart();
+
+ /**
+ * Called for each line of output to be processed.
+ * @param line a line of application output
+ */
+ void handleLine(String line);
+
+ /**
+ * Called after the last handleLine() call.
+ */
+ void notifyEnd();
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+
+/**
+ * BitmapProducer implementation that simply loads preproduced reference bitmaps from a
+ * certain directory.
+ * <p>
+ * Here's what the configuration element looks like for the class:
+ * <p>
+ * <pre>
+ * <producer classname="org.apache.fop.visual.ReferenceBitmapLoader">
+ * <directory>C:/Temp/ref-bitmaps</directory>
+ * </producer>
+ * </pre>
+ */
+public class ReferenceBitmapLoader extends AbstractBitmapProducer implements Configurable {
+
+ private File bitmapDirectory;
+
+ /** @see org.apache.avalon.framework.configuration.Configurable */
+ public void configure(Configuration cfg) throws ConfigurationException {
+ this.bitmapDirectory = new File(cfg.getChild("directory").getValue(null));
+ if (!bitmapDirectory.exists()) {
+ throw new ConfigurationException("Directory could not be found: " + bitmapDirectory);
+ }
+ }
+
+ /** @see org.apache.fop.visual.BitmapProducer */
+ public BufferedImage produce(File src, ProducerContext context) {
+ try {
+ File bitmap = new File(bitmapDirectory, src.getName() + ".png");
+ if (bitmap.exists()) {
+ return BitmapComparator.getImage(bitmap);
+ } else {
+ return null;
+ }
+ } catch (Exception e) {
+ log.error(e);
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.visual;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Redirects the content coming in through an InputStream using a separate thread to a
+ * RedirectorLineHandler instance. The default text encoding is used.
+ */
+public class StreamRedirector implements Runnable {
+
+ private InputStream in;
+ private RedirectorLineHandler handler;
+ private Exception exception;
+
+ /**
+ * @param in the InputStream to read the content from
+ * @param handler the handler that receives all the lines
+ */
+ public StreamRedirector(InputStream in, RedirectorLineHandler handler) {
+ this.in = in;
+ this.handler = handler;
+ }
+
+ /**
+ * @return true if the run() method was terminated by an exception.
+ */
+ public boolean hasFailed() {
+ return (this.exception != null);
+ }
+
+ /**
+ * @return the exception if the run() method was terminated by an exception, or null
+ */
+ public Exception getException() {
+ return this.exception;
+ }
+
+ /** @see java.lang.Runnable#run() */
+ public void run() {
+ this.exception = null;
+ try {
+ Reader inr = new java.io.InputStreamReader(in);
+ BufferedReader br = new BufferedReader(inr);
+ if (handler != null) {
+ handler.notifyStart();
+ }
+ String line = null;
+ while ((line = br.readLine()) != null) {
+ if (handler != null) {
+ handler.handleLine(line);
+ }
+ }
+ if (handler != null) {
+ handler.notifyStart();
+ }
+ } catch (IOException ioe) {
+ this.exception = ioe;
+ }
+ }
+}
\ No newline at end of file