/* * 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; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base class for all implementations of the {@link FormulaRenderer} component. Provides all the common functionalities * (caching, storage, and retrieval), so that the only responsibility of each implementation remains just to actually * transform the formula into an image. * * @version $Id: 2ff66bb941eeb6e5d1783688388e29fd889a773f $ * @since 2.0M3 */ public abstract class AbstractFormulaRenderer implements FormulaRenderer { /** Logging helper object. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFormulaRenderer.class); /** A storage system for rendered images, for reuse in subsequent requests. */ @Inject private ImageStorage storage; @Override public String process(String formula, boolean inline, FontSize size, Type type) throws IllegalArgumentException, IOException { // Only render the image if it is not already in the cache String cacheKey = computeImageID(formula, inline, size, type); if (this.storage.get(cacheKey) == null) { ImageData image = renderImage(formula, inline, size, type); this.storage.put(cacheKey, image); } return cacheKey; } @Override public ImageData getImage(String imageID) { return this.storage.get(imageID); } /** * Renders a mathematical formula into an image. * * @param formula a string representation of the formula, in LaTeX syntax, without any commands that specify the * environment (such as $$ .. $$, \begin{math} ... \end{math}, etc) * @param inline specifies if the rendered formula will be displayed inline in the text, or as a separate block * @param size the font size used for displaying the formula * @param type the format in which the formula is rendered * @return the rendered image, as an {@link ImageData} instance * @throws IllegalArgumentException if the LaTeX syntax of the formula is incorrect and the error is unrecoverable * @throws IOException in case of a renderer execution error */ protected abstract ImageData renderImage(String formula, boolean inline, FontSize size, Type type) throws IllegalArgumentException, IOException; /** * Computes the identifier under which the rendered formula will be stored for later reuse. * * @param formula a string representation of the formula, in LaTeX syntax, without any commands that specify the * environment (such as $$ .. $$, \begin{math} ... \end{math}, etc) * @param inline specifies if the rendered formula will be displayed inline in the text, or as a separate block * @param size the font size used for displaying the formula * @param type the format in which the formula is rendered * @return a string representation of the hash code for the four information items */ protected String computeImageID(String formula, boolean inline, FontSize size, Type type) { // Try computing a long hash try { MessageDigest hashAlgorithm = MessageDigest.getInstance("SHA-256"); hashAlgorithm.update(inline ? (byte) 't' : (byte) 'f'); hashAlgorithm.update((byte) size.ordinal()); hashAlgorithm.update((byte) type.ordinal()); hashAlgorithm.update(formula.getBytes()); return String.valueOf(org.apache.commons.codec.binary.Hex.encodeHex(hashAlgorithm.digest())); } catch (NoSuchAlgorithmException ex) { LOGGER.error("No MD5 hash algorithm implementation", ex); } catch (NullPointerException ex) { LOGGER.error("Error hashing image name", ex); } // Fallback to a simple hashcode final int prime = 37; int result = 1; result = prime * result + formula.hashCode(); result = prime * result + (inline ? 0 : 1); result = prime * result + size.hashCode(); result = prime * result + type.hashCode(); result = prime * result + this.getClass().getCanonicalName().hashCode(); return result + ""; } /** * Prepares the mathematical formula for rendering by wrapping it in the proper math environment. * * @param formula the mathematical formula that needs to be rendered * @param inline a boolean that specifies if the rendered formula will be displayed inline in the text, or as a * separate block * @return the formula, surrounded by "\begin{math}" / "\end{math}" if it is supposed to be displayed inline, and by * "\begin{displaymath}" / \end{displaymath} if it should be displayed as a block. */ protected String wrapFormula(String formula, boolean inline) { return (inline ? "\\begin{math}" : "\\begin{displaymath}") + "\n{ " + formula + " }\n" + (inline ? "\\end{math}" : "\\end{displaymath}"); } }