package com.revolsys.swing.map.layer.record.renderer; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.measure.Measure; import javax.measure.quantity.Length; import javax.measure.unit.Unit; import javax.swing.Icon; import javax.swing.ImageIcon; import com.revolsys.collection.map.MapEx; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.impl.PointDoubleXYOrientation; import com.revolsys.io.BaseCloseable; import com.revolsys.record.Record; import com.revolsys.swing.Icons; import com.revolsys.swing.component.Form; import com.revolsys.swing.map.Viewport2D; import com.revolsys.swing.map.layer.AbstractLayer; import com.revolsys.swing.map.layer.LayerRenderer; import com.revolsys.swing.map.layer.record.AbstractRecordLayer; import com.revolsys.swing.map.layer.record.LayerRecord; import com.revolsys.swing.map.layer.record.style.TextStyle; import com.revolsys.swing.map.layer.record.style.panel.TextStylePanel; import com.revolsys.util.Property; public class TextStyleRenderer extends AbstractRecordLayerRenderer { private static final Pattern FIELD_PATTERN = Pattern.compile("\\[([\\w.]+)\\]"); private static final Icon ICON = Icons.getIcon("style_text"); public static final AffineTransform NOOP_TRANSFORM = AffineTransform.getTranslateInstance(0, 0); public static String getLabel(final Record record, final TextStyle style) { if (record == null) { return "Text"; } else { final StringBuffer label = new StringBuffer(); final String labelPattern = style.getTextName(); final Matcher matcher = FIELD_PATTERN.matcher(labelPattern); while (matcher.find()) { final String propertyName = matcher.group(1); String text = ""; try { final Object value = record.getValueByPath(propertyName); if (value != null) { text = DataTypes.toString(value); } } catch (final Throwable e) { } matcher.appendReplacement(label, text); } matcher.appendTail(label); return label.toString().trim(); } } public static final void renderText(final Viewport2D viewport, final Graphics2D graphics, final Record record, final Geometry geometry, final TextStyle style) { final String label = getLabel(record, style); if (geometry != null) { for (final Geometry part : geometry.geometries()) { renderText(viewport, graphics, label, part, style); } } } public static void renderText(final Viewport2D viewport, final Graphics2D graphics, final String label, final Geometry geometry, final TextStyle style) { if (Property.hasValue(label) && geometry != null || viewport == null) { final String textPlacementType = style.getTextPlacementType(); final PointDoubleXYOrientation point = getPointWithOrientation(viewport, geometry, textPlacementType); if (point != null) { double orientation; final String orientationType = style.getTextOrientationType(); if ("none".equals(orientationType)) { orientation = 0; } else { orientation = point.getOrientation(); if (orientation > 270) { orientation -= 360; } } orientation += style.getTextOrientation(); final Paint paint = graphics.getPaint(); final Composite composite = graphics.getComposite(); try { graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); final double x = point.getX(); final double y = point.getY(); final double[] location; if (viewport == null) { location = new double[] { x, y }; } else { location = viewport.toViewCoordinates(x, y); } final AffineTransform savedTransform = graphics.getTransform(); style.setTextStyle(viewport, graphics); final Measure<Length> textDx = style.getTextDx(); double dx = Viewport2D.toDisplayValue(viewport, textDx); final Measure<Length> textDy = style.getTextDy(); double dy = -Viewport2D.toDisplayValue(viewport, textDy); final FontMetrics fontMetrics = graphics.getFontMetrics(); double maxWidth = 0; final String[] lines = label.split("[\\r\\n]"); for (final String line : lines) { final Rectangle2D bounds = fontMetrics.getStringBounds(line, graphics); final double width = bounds.getWidth(); maxWidth = Math.max(width, maxWidth); } final int descent = fontMetrics.getDescent(); final int ascent = fontMetrics.getAscent(); final int leading = fontMetrics.getLeading(); final double maxHeight = lines.length * (ascent + descent) + (lines.length - 1) * leading; final String verticalAlignment = style.getTextVerticalAlignment(); if ("top".equals(verticalAlignment)) { } else if ("middle".equals(verticalAlignment)) { dy -= maxHeight / 2; } else { dy -= maxHeight; } String horizontalAlignment = style.getTextHorizontalAlignment(); double screenX = location[0]; double screenY = location[1]; final String textPlacement = textPlacementType; if ("auto".equals(textPlacement) && viewport != null) { if (screenX < 0) { screenX = 1; dx = 0; horizontalAlignment = "left"; } final int viewWidth = viewport.getViewWidthPixels(); if (screenX + maxWidth > viewWidth) { screenX = (int)(viewWidth - maxWidth - 1); dx = 0; horizontalAlignment = "left"; } if (screenY < maxHeight) { screenY = 1; dy = 0; } final int viewHeight = viewport.getViewHeightPixels(); if (screenY > viewHeight) { screenY = viewHeight - 1 - maxHeight; dy = 0; } } graphics.translate(screenX, screenY); if (orientation != 0) { graphics.rotate(-Math.toRadians(orientation), 0, 0); } graphics.translate(dx, dy); for (final String line : lines) { graphics.translate(0, ascent); final AffineTransform lineTransform = graphics.getTransform(); final Rectangle2D bounds = fontMetrics.getStringBounds(line, graphics); final double width = bounds.getWidth(); final double height = bounds.getHeight(); if ("right".equals(horizontalAlignment)) { graphics.translate(-width, 0); } else if ("center".equals(horizontalAlignment) || "auto".equals(horizontalAlignment)) { graphics.translate(-width / 2, 0); } graphics.translate(dx, 0); graphics.scale(1, 1); if (Math.abs(orientation) > 90) { graphics.rotate(Math.PI, maxWidth / 2, -height / 4); } final int textBoxOpacity = style.getTextBoxOpacity(); final Color textBoxColor = style.getTextBoxColor(); if (textBoxOpacity > 0 && textBoxColor != null) { graphics.setPaint(textBoxColor); final double cornerSize = Math.max(height / 2, 5); final RoundRectangle2D.Double box = new RoundRectangle2D.Double(bounds.getX() - 3, bounds.getY() - 1, width + 6, height + 2, cornerSize, cornerSize); graphics.fill(box); } final double radius = style.getTextHaloRadius(); final Unit<Length> unit = style.getTextSizeUnit(); final double textHaloRadius = Viewport2D.toDisplayValue(viewport, Measure.valueOf(radius, unit)); if (textHaloRadius > 0) { final Stroke savedStroke = graphics.getStroke(); final Stroke outlineStroke = new BasicStroke((float)(textHaloRadius + 1), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); graphics.setColor(style.getTextHaloFill()); graphics.setStroke(outlineStroke); final Font font = graphics.getFont(); final FontRenderContext fontRenderContext = graphics.getFontRenderContext(); final TextLayout textLayout = new TextLayout(line, font, fontRenderContext); final Shape outlineShape = textLayout.getOutline(NOOP_TRANSFORM); graphics.draw(outlineShape); graphics.setStroke(savedStroke); } graphics.setColor(style.getTextFill()); if (textBoxOpacity > 0 && textBoxOpacity < 255) { graphics.setComposite(AlphaComposite.SrcOut); graphics.drawString(line, (float)0, (float)0); graphics.setComposite(AlphaComposite.DstOver); graphics.drawString(line, (float)0, (float)0); } else { graphics.setComposite(AlphaComposite.SrcOver); graphics.drawString(line, (float)0, (float)0); } graphics.setTransform(lineTransform); graphics.translate(0, leading + descent); } graphics.setTransform(savedTransform); } finally { graphics.setPaint(paint); graphics.setComposite(composite); } } } } private TextStyle style = new TextStyle(); public TextStyleRenderer(final AbstractRecordLayer layer, final LayerRenderer<?> parent) { super("textStyle", "Text Style", layer, parent); setIcon(newIcon()); } public TextStyleRenderer(final AbstractRecordLayer layer, final TextStyle textStyle) { super("textStyle", "Text Style"); setStyle(textStyle); } public TextStyleRenderer(final Map<String, ? extends Object> properties) { super("textStyle", "Text Style"); setIcon(ICON); setProperties(properties); } @Override public TextStyleRenderer clone() { final TextStyleRenderer clone = (TextStyleRenderer)super.clone(); clone.setStyle(this.style.clone()); return clone; } @Override public Icon getIcon() { Icon icon = super.getIcon(); if (icon == ICON) { icon = newIcon(); setIcon(icon); } return icon; } public TextStyle getStyle() { return this.style; } @Override public Icon newIcon() { final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); final Graphics2D graphics = image.createGraphics(); this.style.drawTextIcon(graphics, 12); graphics.dispose(); final Icon icon = new ImageIcon(image); return icon; } @Override public Form newStylePanel() { return new TextStylePanel(this); } @Override public void propertyChange(final PropertyChangeEvent event) { final Object source = event.getSource(); if (source == this.style) { refreshIcon(); } super.propertyChange(event); } @Override public void renderRecord(final Viewport2D viewport, final BoundingBox visibleArea, final AbstractLayer layer, final LayerRecord record) { final Geometry geometry = record.getGeometry(); if (Property.hasValue(geometry)) { try ( BaseCloseable transformClosable = viewport.setUseModelCoordinates(false)) { viewport.drawText(record, geometry, this.style); } } } @Override public void setProperties(final Map<String, ? extends Object> properties) { super.setProperties(properties); if (this.style != null) { this.style.setProperties(properties); } } public void setStyle(final TextStyle style) { if (this.style != null) { this.style.removePropertyChangeListener(this); } this.style = style; if (this.style != null) { this.style.addPropertyChangeListener(this); } refreshIcon(); } @Override public MapEx toMap() { final MapEx map = super.toMap(); if (this.style != null) { final Map<String, Object> styleMap = this.style.toMap(); map.putAll(styleMap); } return map; } }