/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.decoration;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.wms.WMSMapContent;
import org.geotools.geometry.jts.TransformedShape;
import org.geotools.renderer.style.FontCache;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.StringModel;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
/**
* A map decoration showing a text message driven by a Freemarker template
*
* @author Andrea Aime - GeoSolutions
*/
public class TextDecoration implements MapDecoration {
/** A logger for this class. */
private static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geoserver.wms.responses");
private static Font DEFAULT_FONT = new java.awt.Font("Serif", java.awt.Font.PLAIN, 12);
String fontFamily;
boolean fontBold;
boolean fontItalic;
float fontSize;
float haloRadius;
Color haloColor;
String messageTemplate;
Color fontColor;
@Override
public void loadOptions(Map<String, String> options) throws Exception {
// message
this.messageTemplate = options.get("message");
if(messageTemplate == null) {
messageTemplate = "You forgot to set the 'message' option";
}
// font
this.fontFamily = options.get("font-family");
if (options.get("font-italic") != null) {
this.fontItalic = Boolean.parseBoolean(options.get("font-italic"));
}
if (options.get("font-bold") != null) {
this.fontBold = Boolean.parseBoolean(options.get("font-bold"));
}
if (options.get("font-size") != null) {
try {
this.fontSize = Float.parseFloat(options.get("font-size"));
} catch (Exception e) {
LOGGER.log(Level.WARNING, "'font-size' must be a float.", e);
}
}
if (options.get("font-color") != null) {
try {
this.fontColor = MapDecorationLayout.parseColor(options.get("font-color"));
} catch (Exception e) {
LOGGER.log(Level.WARNING, "'font-color' must be a color in #RRGGBB[AA] format.", e);
}
}
if(fontColor == null) {
fontColor = Color.BLACK;
}
// halo
if (options.get("halo-radius") != null) {
try {
this.haloRadius = Float.parseFloat(options.get("halo-radius"));
} catch (Exception e) {
LOGGER.log(Level.WARNING, "'halo-radius' must be a float.", e);
}
}
if (options.get("halo-color") != null) {
try {
this.haloColor = MapDecorationLayout.parseColor(options.get("halo-color"));
} catch (Exception e) {
LOGGER.log(Level.WARNING, "'halo-color' must be a color in #RRGGBB[AA] format.", e);
}
}
if(haloRadius > 0 && haloColor == null) {
haloColor = Color.WHITE;
}
}
Font getFont() {
Font font = DEFAULT_FONT;
if(fontFamily != null) {
font = FontCache.getDefaultInstance().getFont(fontFamily);
if(font == null) {
LOGGER.log(Level.WARNING, "Font " + fontFamily + " not found, falling back on the default");
font = DEFAULT_FONT;
}
}
if(fontSize > 0) {
font = font.deriveFont(fontSize);
}
if(fontItalic) {
font = font.deriveFont(Font.ITALIC);
}
if(fontBold) {
font = font.deriveFont(Font.BOLD);
}
return font;
}
String evaluateMessage(WMSMapContent content) throws IOException, TemplateException {
final Map env = content.getRequest().getEnv();
Template t = new Template("name", new StringReader(messageTemplate),
new Configuration());
final BeansWrapper bw = new BeansWrapper();
return FreeMarkerTemplateUtils.processTemplateIntoString(t, new TemplateHashModel() {
@Override
public boolean isEmpty() throws TemplateModelException {
return env.isEmpty();
}
@Override
public TemplateModel get(String key) throws TemplateModelException {
String value = (String) env.get(key);
if(value != null) {
return new StringModel(value, bw);
} else {
return null;
}
}
});
}
@Override
public Dimension findOptimalSize(Graphics2D g2d, WMSMapContent mapContent) throws Exception {
Font font = getFont();
String message = evaluateMessage(mapContent);
GlyphVector gv = font.createGlyphVector(g2d.getFontRenderContext(), message.toCharArray());
Shape outline = gv.getOutline();
Rectangle2D bounds = outline.getBounds2D();
double width = bounds.getWidth() + haloRadius * 2;
double height = bounds.getHeight() + haloRadius * 2;
return new Dimension((int) Math.ceil(width), (int) Math.ceil(height));
}
@Override
public void paint(Graphics2D g2d, Rectangle paintArea, WMSMapContent mapContent) throws Exception {
Font font = getFont();
String message = evaluateMessage(mapContent);
Font oldFont = g2d.getFont();
Color oldColor = g2d.getColor();
Stroke oldStroke = g2d.getStroke();
try {
// extract the glyph vector outline (paint like the labelling system does)
GlyphVector gv = font.createGlyphVector(g2d.getFontRenderContext(), message.toCharArray());
AffineTransform at = AffineTransform.getTranslateInstance(paintArea.x + haloRadius,
paintArea.y + paintArea.height - haloRadius);
Shape outline = gv.getOutline();
outline = at.createTransformedShape(outline);
// draw the halo if necessary
if(haloRadius > 0) {
g2d.setColor(haloColor);
g2d.setStroke(new BasicStroke(2 * haloRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(outline);
}
// draw the string
g2d.setFont(font);
g2d.setColor(fontColor);
g2d.fill(outline);
} finally {
g2d.setColor(oldColor);
g2d.setFont(oldFont);
g2d.setStroke(oldStroke);
}
}
}