1 /*
2 * Copyright 2007 - 2008 JEuclid, http://jeuclid.sf.net
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /* $Id: Converter.java,v 0bed7fec74b0 2010/08/06 15:59:34 max $ */
18
19 package net.sourceforge.jeuclid.converter;
20
21 import java.awt.Color;
22 import java.awt.Dimension;
23 import java.awt.Graphics2D;
24 import java.awt.Image;
25 import java.awt.image.BufferedImage;
26 import java.io.BufferedOutputStream;
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31
32 import javax.xml.parsers.ParserConfigurationException;
33
34 import net.sourceforge.jeuclid.LayoutContext;
35 import net.sourceforge.jeuclid.MathMLParserSupport;
36 import net.sourceforge.jeuclid.MutableLayoutContext;
37 import net.sourceforge.jeuclid.context.LayoutContextImpl;
38 import net.sourceforge.jeuclid.converter.ConverterPlugin.DocumentWithDimension;
39 import net.sourceforge.jeuclid.layout.JEuclidView;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.Node;
45 import org.xml.sax.SAXException;
46
47 /**
48 * Generic converter which uses the registry to do its conversions.
49 *
50 * @version $Revision: 0bed7fec74b0 $
51 */
52 public final class Converter {
53
54 /**
55 * Mime type for SVG.
56 */
57 public static final String TYPE_SVG = "image/svg+xml";
58
59 /**
60 * File extension for SVG.
61 */
62 public static final String EXTENSION_SVG = "svg";
63
64 private static final String UNSUPPORTED_OUTPUT_TYPE = "Unsupported output type: ";
65
66 private static final int MAX_RGB_VALUE = 255;
67
68 private static final class SingletonHolder {
69 private static final Converter INSTANCE = new Converter();
70
71 private SingletonHolder() {
72 }
73 }
74
75 /**
76 * Logger for this class
77 */
78 private static final Log LOGGER = LogFactory.getLog(Converter.class);
79
80 /**
81 * Default constructor.
82 */
83 protected Converter() {
84 // Empty on purpose.
85 }
86
87 /**
88 * Retrieve an instance of the converter singleton class.
89 *
90 * @return a Converter object.
91 */
92 public static Converter getInstance() {
93 return Converter.SingletonHolder.INSTANCE;
94 }
95
96 /**
97 * @return Converter instance
98 * @deprecated use {@link #getInstance()} instead.
99 */
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 }