001    /*
002     * Copyright 2007 - 2008 JEuclid, http://jeuclid.sf.net
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    /* $Id: Converter.java,v 0bed7fec74b0 2010/08/06 15:59:34 max $ */
018    
019    package net.sourceforge.jeuclid.converter;
020    
021    import java.awt.Color;
022    import java.awt.Dimension;
023    import java.awt.Graphics2D;
024    import java.awt.Image;
025    import java.awt.image.BufferedImage;
026    import java.io.BufferedOutputStream;
027    import java.io.File;
028    import java.io.FileOutputStream;
029    import java.io.IOException;
030    import java.io.OutputStream;
031    
032    import javax.xml.parsers.ParserConfigurationException;
033    
034    import net.sourceforge.jeuclid.LayoutContext;
035    import net.sourceforge.jeuclid.MathMLParserSupport;
036    import net.sourceforge.jeuclid.MutableLayoutContext;
037    import net.sourceforge.jeuclid.context.LayoutContextImpl;
038    import net.sourceforge.jeuclid.converter.ConverterPlugin.DocumentWithDimension;
039    import net.sourceforge.jeuclid.layout.JEuclidView;
040    
041    import org.apache.commons.logging.Log;
042    import org.apache.commons.logging.LogFactory;
043    import org.w3c.dom.Document;
044    import org.w3c.dom.Node;
045    import org.xml.sax.SAXException;
046    
047    /**
048     * Generic converter which uses the registry to do its conversions.
049     * 
050     * @version $Revision: 0bed7fec74b0 $
051     */
052    public final class Converter {
053    
054        /**
055         * Mime type for SVG.
056         */
057        public static final String TYPE_SVG = "image/svg+xml";
058    
059        /**
060         * File extension for SVG.
061         */
062        public static final String EXTENSION_SVG = "svg";
063    
064        private static final String UNSUPPORTED_OUTPUT_TYPE = "Unsupported output type: ";
065    
066        private static final int MAX_RGB_VALUE = 255;
067    
068        private static final class SingletonHolder {
069            private static final Converter INSTANCE = new Converter();
070    
071            private SingletonHolder() {
072            }
073        }
074    
075        /**
076         * Logger for this class
077         */
078        private static final Log LOGGER = LogFactory.getLog(Converter.class);
079    
080        /**
081         * Default constructor.
082         */
083        protected Converter() {
084            // Empty on purpose.
085        }
086    
087        /**
088         * Retrieve an instance of the converter singleton class.
089         * 
090         * @return a Converter object.
091         */
092        public static Converter getInstance() {
093            return Converter.SingletonHolder.INSTANCE;
094        }
095    
096        /**
097         * @return Converter instance
098         * @deprecated use {@link #getInstance()} instead.
099         */
100        @Deprecated
101        public static Converter getConverter() {
102            return Converter.getInstance();
103        }
104    
105        /**
106         * Converts an existing file from MathML or ODF to the given type.
107         * 
108         * @param inFile
109         *            input file.
110         * @param outFile
111         *            output file.
112         * @param outFileType
113         *            mimetype for the output file.
114         * @return Dimension of converted image upon success, null otherwise
115         * @throws IOException
116         *             if an I/O error occurred during read or write.
117         */
118        public Dimension convert(final File inFile, final File outFile,
119                final String outFileType) throws IOException {
120            final MutableLayoutContext params = new LayoutContextImpl(
121                    LayoutContextImpl.getDefaultLayoutContext());
122            return this.convert(inFile, outFile, outFileType, params);
123        }
124    
125        /**
126         * Converts an existing file from MathML or ODF to the given type.
127         * 
128         * @param inFile
129         *            input file.
130         * @param outFile
131         *            output file.
132         * @param outFileType
133         *            mimetype for the output file.
134         * @param params
135         *            rendering parameters.
136         * @return Dimension of converted image upon success, null otherwise
137         * @throws IOException
138         *             if an I/O error occurred during read or write.
139         */
140        public Dimension convert(final File inFile, final File outFile,
141                final String outFileType, final LayoutContext params)
142                throws IOException {
143            Document doc;
144            try {
145                doc = MathMLParserSupport.parseFile(inFile);
146                return this.convert(doc, outFile, outFileType, params);
147            } catch (final SAXException e) {
148                Converter.LOGGER.error("Failed to parse file:" + inFile, e);
149                return null;
150            }
151        }
152    
153        /**
154         * Converts an existing document from MathML to the given type and store it
155         * in a file.
156         * 
157         * @param doc
158         *            input document. See
159         *            {@link net.sourceforge.jeuclid.DOMBuilder#DOMBuilder(Node, MathBase)}
160         *            for the list of valid node types.
161         * @param outFile
162         *            output file.
163         * @param outFileType
164         *            mimetype for the output file.
165         * @param params
166         *            parameter set to use for conversion.
167         * @return Dimension of converted image upon success, null otherwise
168         * @throws IOException
169         *             if an I/O error occurred during read or write.
170         */
171        public Dimension convert(final Node doc, final File outFile,
172                final String outFileType, final LayoutContext params)
173                throws IOException {
174    
175            final OutputStream outStream = new BufferedOutputStream(
176                    new FileOutputStream(outFile));
177            final Dimension result = this.convert(doc, outStream, outFileType,
178                    params);
179            if (result == null) {
180                if (!outFile.delete()) {
181                    Converter.LOGGER.debug("Could not delete " + outFile);
182                }
183            } else {
184                // should be closed by wrapper image streams, but just in case...
185                try {
186                    outStream.close();
187                } catch (final IOException e) {
188                    Converter.LOGGER.debug(e);
189                }
190            }
191            return result;
192        }
193    
194        /**
195         * Converts an existing document from MathML to the given XML based type and
196         * store it in a DOM document.
197         * 
198         * @param doc
199         *            input document. See
200         *            {@link net.sourceforge.jeuclid.DOMBuilder#DOMBuilder(Node, MathBase)}
201         *            for the list of valid node types.
202         * @param outFileType
203         *            mimetype for the output file.
204         * @param params
205         *            parameter set to use for conversion.
206         * @return an instance of Document, or the appropriate subtype for this
207         *         format (e.g. SVGDocument). If conversion is not supported to this
208         *         type, it may return null.
209         */
210        public DocumentWithDimension convert(final Node doc,
211                final String outFileType, final LayoutContext params) {
212            final ConverterPlugin plugin = ConverterRegistry.getInstance()
213                    .getConverter(outFileType);
214            DocumentWithDimension result = null;
215            if (plugin != null) {
216                result = plugin.convert(doc, params);
217            }
218            if (result == null) {
219                Converter.LOGGER.fatal(Converter.UNSUPPORTED_OUTPUT_TYPE
220                        + outFileType);
221            }
222            return result;
223        }
224    
225        /**
226         * Converts an existing document from MathML to the given XML based type and
227         * writes it to the provided output stream.
228         * 
229         * @param doc
230         *            input document. See
231         *            {@link net.sourceforge.jeuclid.DOMBuilder#DOMBuilder(Node, MathBase)}
232         *            for the list of valid node types.
233         * @param outStream
234         *            output stream.
235         * @param outFileType
236         *            mimetype for the output file.
237         * @param params
238         *            parameter set to use for conversion.
239         * @return Dimension of converted image upon success, null otherwise
240         * @throws IOException
241         *             if an I/O error occurred during read or write.
242         */
243        public Dimension convert(final Node doc, final OutputStream outStream,
244                final String outFileType, final LayoutContext params)
245                throws IOException {
246            final ConverterPlugin plugin = ConverterRegistry.getInstance()
247                    .getConverter(outFileType);
248            Dimension result = null;
249            if (plugin == null) {
250                Converter.LOGGER.fatal(Converter.UNSUPPORTED_OUTPUT_TYPE
251                        + outFileType);
252            } else {
253                try {
254                    result = plugin.convert(doc, params, outStream);
255                } catch (final IOException ex) {
256                    Converter.LOGGER.fatal("Failed to process: " + ex.getMessage(),
257                            ex);
258                }
259            }
260            return result;
261        }
262    
263        /**
264         * Converts an XML string from MathML to the given XML based type and writes
265         * it to the provided output stream.
266         * 
267         * @param docString
268         *            XML string representing a valid document
269         * @param outStream
270         *            output stream.
271         * @param outFileType
272         *            mimetype for the output file.
273         * @param params
274         *            parameter set to use for conversion.
275         * @return Dimension of converted image upon success, null otherwise
276         * @throws IOException
277         *             if an I/O error occurred during read or write.
278         */
279        public Dimension convert(final String docString,
280                final OutputStream outStream, final String outFileType,
281                final LayoutContext params) throws IOException {
282    
283            Dimension result = null;
284    
285            try {
286                final Document doc = MathMLParserSupport.parseString(docString);
287                result = this.convert(doc, outStream, outFileType, params);
288            } catch (final SAXException e) {
289                Converter.LOGGER.error("SAXException converting:" + docString, e);
290                result = null;
291            } catch (final ParserConfigurationException e) {
292                Converter.LOGGER.error("ParserConfigurationException converting:"
293                        + docString, e);
294                result = null;
295            }
296    
297            return result;
298        }
299    
300        /**
301         * Renders a document into an image.
302         * 
303         * @param node
304         *            Document / Node to render
305         * @param context
306         *            LayoutContext to use.
307         * @return the rendered image
308         * @throws IOException
309         *             if an I/O error occurred.
310         */
311        public BufferedImage render(final Node node, final LayoutContext context)
312                throws IOException {
313            return this.render(node, context, BufferedImage.TYPE_INT_ARGB);
314        }
315    
316        /**
317         * Renders a document into an image.
318         * 
319         * @param node
320         *            Document / Node to render
321         * @param context
322         *            LayoutContext to use.
323         * @param imageType
324         *            ImageType as defined by {@link BufferedImage}
325         * @return the rendered image
326         * @throws IOException
327         *             if an I/O error occurred.
328         */
329        public BufferedImage render(final Node node, final LayoutContext context,
330                final int imageType) throws IOException {
331            final Image tempimage = new BufferedImage(1, 1, imageType);
332            final Graphics2D tempg = (Graphics2D) tempimage.getGraphics();
333    
334            final JEuclidView view = new JEuclidView(node, context, tempg);
335    
336            final int width = Math.max(1, (int) Math.ceil(view.getWidth()));
337            final int ascent = (int) Math.ceil(view.getAscentHeight());
338            final int height = Math.max(1, (int) Math.ceil(view.getDescentHeight())
339                    + ascent);
340    
341            final BufferedImage image = new BufferedImage(width, height, imageType);
342            final Graphics2D g = image.createGraphics();
343    
344            final Color background;
345            if (image.getColorModel().hasAlpha()) {
346                background = new Color(Converter.MAX_RGB_VALUE,
347                        Converter.MAX_RGB_VALUE, Converter.MAX_RGB_VALUE, 0);
348            } else {
349                background = Color.WHITE;
350            }
351            g.setColor(background);
352            g.fillRect(0, 0, width, height);
353            g.setColor(Color.black);
354    
355            view.draw(g, 0, ascent);
356            return image;
357        }
358    }