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 }