/*
* 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.rendering.internal.macro.formula;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.xwiki.bridge.DocumentAccessBridge;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.formula.FormulaRenderer;
import org.xwiki.formula.FormulaRenderer.FontSize;
import org.xwiki.formula.FormulaRenderer.Type;
import org.xwiki.model.reference.AttachmentReference;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.ImageBlock;
import org.xwiki.rendering.block.ParagraphBlock;
import org.xwiki.rendering.block.WordBlock;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;
import org.xwiki.rendering.macro.AbstractMacro;
import org.xwiki.rendering.macro.MacroExecutionException;
import org.xwiki.rendering.macro.descriptor.DefaultContentDescriptor;
import org.xwiki.rendering.macro.formula.FormulaMacroConfiguration;
import org.xwiki.rendering.macro.formula.FormulaMacroParameters;
import org.xwiki.rendering.transformation.MacroTransformationContext;
/**
* Displays a formula, in LaTeX syntax, as an image.
*
* @version $Id: aff07810bbe7469589ae4e1372c8e2b76ed27a9d $
* @since 2.0M3
*/
@Component
@Named("formula")
@Singleton
public class FormulaMacro extends AbstractMacro<FormulaMacroParameters>
{
/** Predefined error message: empty formula. */
public static final String CONTENT_MISSING_ERROR = "The mandatory formula text is missing.";
/** Predefined error message: invalid formula. */
public static final String WRONG_CONTENT_ERROR = "The formula text is not valid, please correct it.";
/** The description of the macro. */
private static final String DESCRIPTION = "Displays a mathematical formula.";
/** The description of the macro content. */
private static final String CONTENT_DESCRIPTION = "The mathematical formula, in LaTeX syntax";
/** Component manager, needed for retrieving the selected formula renderer. */
@Inject
private ComponentManager manager;
/** Defines from where to read the rendering configuration data. */
@Inject
private FormulaMacroConfiguration configuration;
/** Needed for computing the URL for accessing the rendered image. */
@Inject
private DocumentAccessBridge dab;
/**
* The logger to log.
*/
@Inject
private Logger logger;
/**
* Create and initialize the descriptor of the macro.
*/
public FormulaMacro()
{
super("Formula", DESCRIPTION, new DefaultContentDescriptor(CONTENT_DESCRIPTION), FormulaMacroParameters.class);
setDefaultCategory(DEFAULT_CATEGORY_CONTENT);
}
@Override
public List<Block> execute(FormulaMacroParameters parameters, String content, MacroTransformationContext context)
throws MacroExecutionException
{
if (StringUtils.isEmpty(content)) {
throw new MacroExecutionException(CONTENT_MISSING_ERROR);
}
String rendererHint = this.configuration.getRenderer();
FontSize size = parameters.getFontSize();
Type type = parameters.getImageType();
Block result;
try {
result = render(content, context.isInline(), size, type, rendererHint);
} catch (MacroExecutionException ex) {
this.logger.debug("Failed to render content with the [{}] renderer. Falling back to the safe renderer.",
rendererHint, ex);
try {
result = render(content, context.isInline(), size, type, this.configuration.getSafeRenderer());
} catch (IllegalArgumentException ex2) {
throw new MacroExecutionException(WRONG_CONTENT_ERROR);
}
} catch (IllegalArgumentException ex) {
throw new MacroExecutionException(WRONG_CONTENT_ERROR);
}
// If no image was generated, just return the original text
if (result == null) {
result = new WordBlock(content);
}
// Block level formulae should be wrapped in a paragraph element
if (!context.isInline()) {
result = new ParagraphBlock(Collections.<Block> singletonList(result));
}
return Collections.singletonList(result);
}
/**
* Renders the formula using the specified renderer.
*
* @param formula the formula text
* @param inline is the formula supposed to be used inline or as a block-level element
* @param fontSize the specified font size
* @param imageType the specified resulting image type
* @param rendererHint the hint for the renderer to use
* @return the resulting block holding the generated image, or {@code null} in case of an error.
* @throws MacroExecutionException if no renderer exists for the passed hint or if that rendered failed to render
* the formula
* @throws IllegalArgumentException if the formula is not valid, according to the LaTeX syntax
*/
private Block render(String formula, boolean inline, FontSize fontSize, Type imageType, String rendererHint)
throws MacroExecutionException, IllegalArgumentException
{
try {
FormulaRenderer renderer = this.manager.getInstance(FormulaRenderer.class, rendererHint);
String imageName = renderer.process(formula, inline, fontSize, imageType);
// TODO: HACK!!
// We're going through the getAttachmentURL() API so that when the PdfURLFactory is used, the generated
// image is saved and then embedded in the exported PDF thanks to PDFURIResolver. In the future we need
// to remove this hack by introduce a proper Resource for generated image (say TemporaryResource),
// implement a TemporaryResourceSerializer<URL> and introduce a ResourceLoader interface and have it
// implemented for TemporaryResource...
AttachmentReference attachmentReference =
new AttachmentReference(imageName, this.dab.getCurrentDocumentReference());
String url = this.dab.getAttachmentURL(attachmentReference, false);
// Note that we have to replace the download action by the tex action since the getAttachmentURL() API
// will use the "download" action but when the generated URL is called by the browser it needs to point to
// the TexAction...
url = url.replace("/download/", "/tex/");
// TODO: end HACK!!
ResourceReference imageReference = new ResourceReference(url, ResourceType.URL);
ImageBlock result = new ImageBlock(imageReference, false);
// Set the alternative text for the image to be the original formula
result.setParameter("alt", formula);
return result;
} catch (Exception e) {
throw new MacroExecutionException(
String.format("Failed to render formula using the [%s] renderer", rendererHint), e);
}
}
@Override
public boolean supportsInlineMode()
{
return true;
}
}