/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.formula.internal; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Vector; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.component.annotation.Component; import org.xwiki.component.phase.Initializable; import org.xwiki.component.phase.InitializationException; import org.xwiki.environment.Environment; import org.xwiki.formula.AbstractFormulaRenderer; import org.xwiki.formula.FormulaRenderer; import org.xwiki.formula.ImageData; /** * Implementation of the {@link FormulaRenderer} component, which uses console commands to build a formula image. Best * results, but requires the presence of external programs, and involves a slight overhead for starting new processes * and for working with the disk. * <p> * Required commands: latex, dvips and convert (ImageMagick) * </p> * <p> * Performance tip: Try to mount a RAM drive/tmpfs on the folder where this component creates its temporary files * ([webapp work dir]/formulae/). * </p> * * @version $Id: c8844afea8093f71fcefc27012715fbe2facaf0e $ * @since 2.0M3 */ @Component @Named("native") @Singleton public class NativeFormulaRenderer extends AbstractFormulaRenderer implements Initializable { /** Logging helper object. */ private static final Logger LOGGER = LoggerFactory.getLogger(NativeFormulaRenderer.class); /** Application container, needed for retrieving the work directory where temporary files can be created. */ @Inject private Environment environment; /** Temporary parent directory for storing files created during the image rendering process. */ private File tempDirectory; @Override public void initialize() throws InitializationException { this.tempDirectory = new File(this.environment.getTemporaryDirectory(), "formulae"); this.tempDirectory.mkdir(); } @Override protected ImageData renderImage(String formula, boolean inline, FormulaRenderer.FontSize size, FormulaRenderer.Type type) throws IllegalArgumentException, IOException { File tmpDirectory = null; try { String texContent = "\\documentclass[10pt]{article}\n" + "\\usepackage[paperheight=1000in]{geometry}\n" + "\\usepackage{amsmath}\n" + "\\usepackage{amsfonts}\n" + "\\usepackage{amssymb}\n" + "\\usepackage{pst-plot}\n" + "\\usepackage{color}\n" + "\\pagestyle{empty}\n" + "\\begin{document}\n" + size.getCommand() + "\n" + wrapFormula(formula, inline) + "\\end{document}\n"; do { tmpDirectory = new File(this.tempDirectory, RandomStringUtils.randomAlphanumeric(8)); } while (tmpDirectory.exists()); tmpDirectory.mkdir(); final String baseName = tmpDirectory.getAbsolutePath() + "/file"; final String texFileName = baseName + ".tex"; final String dviFileName = baseName + ".dvi"; final String psFileName = baseName + ".ps"; final String imageFileName = baseName + type.getExtension(); // Write the formula in a tex file FileWriter fw = new FileWriter(texFileName); fw.write(texContent); fw.close(); // TeX to DVI String[] commandLine = new String[] {"latex", "--interaction=nonstopmode", texFileName}; executeCommand(commandLine, tmpDirectory); // DVI to PS commandLine = new String[] {"dvips", "-E", dviFileName, "-o", psFileName}; executeCommand(commandLine, tmpDirectory); // PS to image commandLine = new String[] {"convert", "-density", "120", psFileName, imageFileName}; executeCommand(commandLine, tmpDirectory); return new ImageData(FileUtils.readFileToByteArray(new File(imageFileName)), type); } finally { FileUtils.deleteQuietly(tmpDirectory); } } /** * Execute a system command. * * @param commandLine the command and its arguments * @param cwd the directory to use as the current working directory for the executed process * @return {@code true} if the command succeeded (return code 0), {@code false} otherwise * @throws IOException if the process failed to start */ private boolean executeCommand(String[] commandLine, File cwd) throws IOException { List<String> commandList = new Vector<String>(commandLine.length); Collections.addAll(commandList, commandLine); ProcessBuilder processBuilder = new ProcessBuilder(commandList); processBuilder.directory(cwd); Process process = processBuilder.start(); IOUtils.copy(process.getInputStream(), new NullOutputStream()); try { process.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } if (process.exitValue() != 0) { LOGGER.debug("Error generating image: " + IOUtils.toString(process.getErrorStream())); } return process.exitValue() == 0; } }