/** * This file Copyright (c) 2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.templating.jsp.taglib; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.ItemType; import info.magnolia.cms.util.ContentUtil; import java.awt.FontFormatException; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.StringTokenizer; import javax.imageio.ImageIO; import javax.jcr.AccessDeniedException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import org.apache.commons.lang.SystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tldgen.annotations.BodyContent; import org.tldgen.annotations.Tag; /** * Tag that converts text into PNG images, and outputs a div element containing a set of img elements. The font face, * text color, text size and background color can be set via the tag attributes. <br /> * <br /> * The images are saved under the node specified by the attribute parentContentNodeName. Under this parent node, a new * node is created, with the name specified by the attribute imageContentNodeName. Under this node, each image will have * it own node. The names of the image node are based on the text that they contain. (Special characters such as &, /, * chinese characters, etc. are replaces by codes to ensure that these names are legal.) <br /> * <br /> * If the images for the specified text do not exist in the repository under the specified parent node, then the this * tag will create the images and save them. If the images for the text already exist, then they will not be recreated. * <br /> * <br /> * The text to be converted into images can be split in three ways. If the attribute textSplit is null or is set to * TEXT_SPLIT_NONE, then a single image will be created of the text on one line. If textSplit is set to * TEXT_SPLIT_WORDS, then the text is plit into words (i.e. wherever there is a space). Finally, if textSplit is set to * TEXT_SPLIT_CHARACTERS, then a seperate image is created for each letter. <br /> * <br /> * The tag outputs a div that contains one or more img's. The CSS class applied to the div is specified by the divCSS * attribute. The CSS applied to the images depends on how the text was split. For text that was not split, the CSS * applied is set to CSS_TEXT_IMAGE, for words it is CSS_WORD_IMAGE, and for characters it is CSS_CHARACTER_IMAGE. Any * spacing that is required between images will need to be set in your css stylesheet. <br /> * <br /> * The textFontFace attribute may either be a font name of a font installed on the server, or it may be a path to a TTF * file. The class to generate PNG images from TrueType font strings is originally by Philip McCarthy - * http://chimpen.com (http://chimpen.com/things/archives/001139.php). I have made a couple of small changes. <br /> * <br /> * * @jsp.tag name="txt2img" body-content="empty" * * @author Patrick Janssen * @author Fabrizio Giustina * @version 1.0 */ @Tag(name="txt2img", bodyContent=BodyContent.EMPTY) public class TextToImageTag extends BaseImageTag { /** * The image that is created can first be created at a larger size, and then scaled down. This overcomes kerning * problems on the Windows platform, which results in very irregular spacing between characters. If you are not * using Windows, this can be set to 1. */ private static final double SCALE_FACTOR = SystemUtils.IS_OS_WINDOWS ? 4 : 1; /** * The CSS class applied to images of individual characters. */ private static final String CSS_CHARACTER_IMAGE = "character-image"; /** * The CSS class appled to images of words. */ private static final String CSS_WORD_IMAGE = "word-image"; /** * The CSS class applied to images of whole sentances, or other text. */ private static final String CSS_TEXT_IMAGE = "text-image"; /** * The text will not be split. */ private static final String TEXT_SPLIT_NONE = "none"; /** * The text will be split into words. */ private static final String TEXT_SPLIT_WORDS = "words"; /** * The text will be split into characters. */ private static final String TEXT_SPLIT_CHARACTERS = "characters"; private static final Logger log = LoggerFactory.getLogger(BaseImageTag.class); private String text; private String textFontFace; private int textFontSize; private String textFontColor; private String textBackColor; private String textSplit; private String divCSS; /** * The text to be converted. * @jsp.attribute required="true" rtexprvalue="true" */ public void setText(String text) { this.text = text; } /** * The name of the new contentNode to create, under which all image nodes will be saved. * @jsp.attribute required="true" rtexprvalue="true" */ @Override public void setImageContentNodeName(String imageContentNodeName) { this.imageContentNodeName = imageContentNodeName; } /** * The name of the parent of the new contentNode. * @jsp.attribute required="false" rtexprvalue="true" */ @Override public void setParentContentNodeName(String parentContentNodeName) { this.parentContentNodeName = parentContentNodeName; } /** * The font face of the text, e.g. 'Helvetica'. Default is 'Helvetica'. * @jsp.attribute required="false" rtexprvalue="true" */ public void setTextFontFace(String textFontFace) { this.textFontFace = textFontFace; } /** * The size of the text, in points, e.g. 12. Default is '12'. * @jsp.attribute required="false" rtexprvalue="true" type="int" */ public void setTextFontSize(int textFontSize) { this.textFontSize = textFontSize; } /** * The color of the text in hexadecimal format, e.g. 'ff0044'. Default is '000000' (black). * @jsp.attribute required="false" rtexprvalue="true" */ public void setTextFontColor(String textFontColor) { this.textFontColor = textFontColor; } /** * The color of the background in hexadecimal format, e.g. 'ff0044'. Default is 'ffffff' (white). * @jsp.attribute required="false" rtexprvalue="true" */ public void setTextBackColor(String textBackColor) { this.textBackColor = textBackColor; } /** * The method used to split the text into sub-strings: 'none', 'words', or 'characters'. Default is 'none'. * @jsp.attribute required="false" rtexprvalue="true" */ public void setTextSplit(String textSplit) { this.textSplit = textSplit; } /** * The CSS class that will be applied to the div that contains these text images. * Defaults to "text-box". * @jsp.attribute required="false" rtexprvalue="true" */ public void setDivCSS(String divCSS) { this.divCSS = divCSS; } /** * @see info.magnolia.cms.taglibs.util.BaseImageTag#getFilename() */ @Override protected String getFilename() { return "textImage"; } /** * Initialize settings. */ public void setUp() { // check that all the necessary attributes are set if (this.text == null) { this.text = "Test Test Test"; } if (this.textFontFace == null) { this.textFontFace = SystemUtils.IS_OS_WINDOWS ? "Arial" : "Helvetica"; } if (this.textFontSize == 0) { this.textFontSize = 12; } if (this.textFontColor == null) { this.textFontColor = "000000"; } if (this.textBackColor == null) { this.textBackColor = "ffffff"; } if (this.textSplit == null) { this.textSplit = TEXT_SPLIT_NONE; } else if (!((this.textSplit.equals(TEXT_SPLIT_WORDS)) || (this.textSplit.equals(TEXT_SPLIT_CHARACTERS)))) { this.textSplit = TEXT_SPLIT_NONE; } if (this.divCSS == null) { this.divCSS = "text-box"; } } @Override public void doTag() throws JspException { this.setUp(); try { Content imageContentNode = ContentUtil.asContent(getImageContentNode()); String[] subStrings = this.getTextSubStrings(this.text); String[] imageURIs = this.getImageURIs( subStrings, (HttpServletRequest) ((PageContext) this.getJspContext()).getRequest(), imageContentNode); this.drawTextImages(imageURIs, subStrings); } catch (PathNotFoundException e) { log.error("PathNotFoundException occured during text-to-image conversion: " + e.getMessage(), e); } catch (AccessDeniedException e) { log.error("AccessDeniedException occured during text-to-image conversion: " + e.getMessage(), e); } catch (RepositoryException e) { log.error("RepositoryException occured during text-to-image conversion: " + e.getMessage(), e); } catch (FileNotFoundException e) { log.error("FileNotFoundException occured during text-to-image conversion: " + e.getMessage(), e); } catch (IOException e) { log.error("IOException occured during text-to-image conversion: " + e.getMessage(), e); } catch (FontFormatException e) { log.error("FontFormatException occured during text-to-image conversion: " + e.getMessage(), e); } this.cleanUp(); } /** * Set objects to null. */ public void cleanUp() { this.parentContentNodeName = null; this.imageContentNodeName = null; this.text = null; this.textFontFace = null; this.textFontSize = 0; this.textFontColor = null; this.textBackColor = null; this.textSplit = null; this.divCSS = null; } /** * Draws a div box that contains the text images. * @param imageURLs an array of urls * @param subStrings an array of strings * @throws IOException jspwriter exception */ private void drawTextImages(String[] imageURIs, String[] subStrings) throws IOException { JspWriter out = this.getJspContext().getOut(); if (this.divCSS != null) { out.print("<div class=\""); out.print(this.divCSS); out.print("\">"); } for (int i = 0; i < imageURIs.length; i++) { out.print("<img class=\""); if (this.textSplit.equals(TEXT_SPLIT_CHARACTERS)) { out.print(CSS_CHARACTER_IMAGE); } else if (this.textSplit.equals(TEXT_SPLIT_WORDS)) { out.print(CSS_WORD_IMAGE); } else { out.print(CSS_TEXT_IMAGE); } out.print("\" src=\""); out.print(imageURIs[i]); out.print("\" alt=\""); out.print(subStrings[i]); out.print("\" />"); } if (this.divCSS != null) { out.print("</div>"); } } /** * Splits a string into words or characters, depending on the textSplit attribute. For words, spaces at either end * are removed. * @param The string to split * @return An array of words */ private String[] getTextSubStrings(String text) { String[] subStrings = null; if (this.textSplit.equals(TEXT_SPLIT_CHARACTERS)) { subStrings = new String[text.length()]; for (int i = 0; i < text.length(); i++) { subStrings[i] = text.substring(i, i + 1); } } else if (this.textSplit.equals(TEXT_SPLIT_WORDS)) { StringTokenizer st = new StringTokenizer(text, " "); // Split sentence into words subStrings = new String[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++) { subStrings[i] = st.nextToken().trim(); } } else { subStrings = new String[]{text}; } return subStrings; } /** * Get an array of image URIs, one URI for each text string. * @param The array of text strings. * @return An array of URIs pointing to the images. */ private String[] getImageURIs(String[] subStrings, HttpServletRequest req, Content imageContentNode) throws PathNotFoundException, AccessDeniedException, RepositoryException, FileNotFoundException, IOException, FontFormatException { String[] imageURIs = new String[subStrings.length]; for (int i = 0; i < subStrings.length; i++) { // Create a unique image node name String tmpImgNodeName = subStrings[i] + this.textBackColor + this.textFontColor + this.textFontFace + this.textFontSize; String imageNodeName = this.convertToSimpleString(tmpImgNodeName); // If the image node with this name does not exist, then create it. if (!imageContentNode.hasContent(imageNodeName)) { File image = createImage(subStrings[i]); // Create the node that will contain the image Content imageNode = imageContentNode.createContent(imageNodeName, ItemType.CONTENTNODE); this.createImageNode(image, imageNode); } // Save the URI for this image in the array String contextPath = req.getContextPath(); String handle = imageContentNode.getHandle(); String imageURI = contextPath + handle + "/" + imageNodeName + "/" + getFilename() + "." + PROPERTIES_EXTENSION_VALUE; imageURIs[i] = imageURI; } return imageURIs; } /** * Creates an image from a word. The file is saved in the location specified by TEMP_IMAGE_PATH. * @param subString The text. * @return An input stream. */ private File createImage(String subString) throws FileNotFoundException, IOException, FontFormatException { // Create file File imageFile = File.createTempFile(getClass().getName(), "png"); imageFile.createNewFile(); // create the image // due to kerning problems, the image is being created 4 times to big // then being scaled down to the right size Text2PngFactory tpf = new Text2PngFactory(); tpf.setFontFace(this.textFontFace); tpf.setFontSize((int) (this.textFontSize * SCALE_FACTOR)); int[] textRGB = this.convertHexToRGB(this.textFontColor); int[] backRGB = this.convertHexToRGB(this.textBackColor); tpf.setTextRGB(textRGB[0], textRGB[1], textRGB[2]); tpf.setBackgroundRGB(backRGB[0], backRGB[1], backRGB[2]); tpf.setText(subString); BufferedImage bigImgBuff = (BufferedImage) tpf.createImage(); if (SCALE_FACTOR != 1) { BufferedImage smallImgBuff = this.scaleImage(bigImgBuff, (1.0 / SCALE_FACTOR)); ImageIO.write(smallImgBuff, "png", imageFile); smallImgBuff = null; } else { ImageIO.write(bigImgBuff, "png", imageFile); } bigImgBuff = null; return imageFile; } /** * Create an image file that is a scaled version of the original image. * @param the original BufferedImage * @param the scale factor * @return the new BufferedImage */ private BufferedImage scaleImage(BufferedImage oriImgBuff, double scaleFactor) { // get the dimesnions of the original image int oriWidth = oriImgBuff.getWidth(); int oriHeight = oriImgBuff.getHeight(); // get the width and height of the new image int newWidth = new Double(oriWidth * scaleFactor).intValue(); int newHeight = new Double(oriHeight * scaleFactor).intValue(); // create the thumbnail as a buffered image Image newImg = oriImgBuff.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING); BufferedImage newImgBuff = new BufferedImage( newImg.getWidth(null), newImg.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D g = newImgBuff.createGraphics(); g.drawImage(newImg, 0, 0, null); g.dispose(); // return the newImgBuff return newImgBuff; } }