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 }