// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.mappaint.styleelement; import java.awt.Color; import java.awt.Font; import java.util.Objects; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.gui.mappaint.Cascade; import org.openstreetmap.josm.gui.mappaint.Environment; import org.openstreetmap.josm.gui.mappaint.Keyword; import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.TagKeyReference; import org.openstreetmap.josm.gui.mappaint.StyleKeys; import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy; import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.StaticLabelCompositionStrategy; import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.TagLookupCompositionStrategy; import org.openstreetmap.josm.gui.mappaint.styleelement.placement.CompletelyInsideAreaStrategy; import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.Utils; /** * Represents the rendering style for a textual label placed somewhere on the map. * @since 3880 */ public class TextLabel implements StyleKeys { public static final LabelCompositionStrategy AUTO_LABEL_COMPOSITION_STRATEGY = new DeriveLabelFromNameTagsCompositionStrategy(); /** * The strategy for building the actual label value for a given a {@link OsmPrimitive}. * Check for null before accessing. */ public LabelCompositionStrategy labelCompositionStrategy; /** * the font to be used when rendering */ public Font font; /** * The x offset of the text. */ public int xOffset; /** * The y offset of the text. */ public int yOffset; /** * The color to draw the text in, includes alpha. */ public Color color; /** * The radius of the halo effect. */ public Float haloRadius; /** * The color of the halo effect. */ public Color haloColor; /** * The position strategy for this text label. */ private final PositionForAreaStrategy labelPositionStrategy; /** * Creates a new text element * * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered. * If null, no label is rendered. * @param font the font to be used. Must not be null. * @param xOffset x offset * @param yOffset y offset * @param color the color to be used. Must not be null * @param haloRadius halo radius * @param haloColor halo color * @deprecated since 11722, To be removed in mid-2017 */ @Deprecated public TextLabel(LabelCompositionStrategy strategy, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor) { this(strategy, font, xOffset, yOffset, color, haloRadius, haloColor, new CompletelyInsideAreaStrategy()); } /** * Creates a new text element * * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered. * If null, no label is rendered. * @param font the font to be used. Must not be null. * @param xOffset x offset * @param yOffset y offset * @param color the color to be used. Must not be null * @param haloRadius halo radius * @param haloColor halo color * @param labelPositionStrategy The position in the area. */ protected TextLabel(LabelCompositionStrategy strategy, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor, PositionForAreaStrategy labelPositionStrategy) { this.labelCompositionStrategy = strategy; this.font = Objects.requireNonNull(font, "font"); this.xOffset = xOffset; this.yOffset = yOffset; this.color = Objects.requireNonNull(color, "color"); this.haloRadius = haloRadius; this.haloColor = haloColor; this.labelPositionStrategy = Objects.requireNonNull(labelPositionStrategy, "labelPositionStrategy"); } /** * Copy constructor * * @param other the other element. */ public TextLabel(TextLabel other) { this.labelCompositionStrategy = other.labelCompositionStrategy; this.font = other.font; this.xOffset = other.xOffset; this.yOffset = other.yOffset; this.color = other.color; this.haloColor = other.haloColor; this.haloRadius = other.haloRadius; this.labelPositionStrategy = other.labelPositionStrategy; } /** * Copy constructor that changes the position strategy. * * @param other the other element. * @param labelPositionStrategy the position */ private TextLabel(TextLabel other, PositionForAreaStrategy labelPositionStrategy) { this.labelCompositionStrategy = other.labelCompositionStrategy; this.font = other.font; this.xOffset = other.xOffset; this.yOffset = other.yOffset; this.color = other.color; this.haloColor = other.haloColor; this.haloRadius = other.haloRadius; this.labelPositionStrategy = labelPositionStrategy; } /** * Derives a suitable label composition strategy from the style properties in {@code c}. * * @param c the style properties * @param defaultAnnotate whether to return {@link #AUTO_LABEL_COMPOSITION_STRATEGY} if not strategy is found * @return the label composition strategy, or {@code null} */ protected static LabelCompositionStrategy buildLabelCompositionStrategy(Cascade c, boolean defaultAnnotate) { /* * If the cascade includes a TagKeyReference we will lookup the rendered label * from a tag value. */ TagKeyReference tkr = c.get(TEXT, null, TagKeyReference.class, true); if (tkr != null) return new TagLookupCompositionStrategy(tkr.key); /* * Check whether the label composition strategy is given by a keyword */ Keyword keyword = c.get(TEXT, null, Keyword.class, true); if (Keyword.AUTO.equals(keyword)) return AUTO_LABEL_COMPOSITION_STRATEGY; /* * Do we have a static text label? */ String text = c.get(TEXT, null, String.class, true); if (text != null) return new StaticLabelCompositionStrategy(text); return defaultAnnotate ? AUTO_LABEL_COMPOSITION_STRATEGY : null; } /** * Builds a text element from style properties in {@code c} and the * default text color {@code defaultTextColor} * * @param env the environment * @param defaultTextColor the default text color. Must not be null. * @param defaultAnnotate true, if a text label shall be rendered by default, even if the style sheet * doesn't include respective style declarations * @return the text element or null, if the style properties don't include * properties for text rendering * @throws IllegalArgumentException if {@code defaultTextColor} is null */ public static TextLabel create(Environment env, Color defaultTextColor, boolean defaultAnnotate) { CheckParameterUtil.ensureParameterNotNull(defaultTextColor); Cascade c = env.mc.getCascade(env.layer); LabelCompositionStrategy strategy = buildLabelCompositionStrategy(c, defaultAnnotate); if (strategy == null) return null; String s = strategy.compose(env.osm); if (s == null) return null; Font font = StyleElement.getFont(c, s); float xOffset = 0; float yOffset = 0; float[] offset = c.get(TEXT_OFFSET, null, float[].class); if (offset != null) { if (offset.length == 1) { yOffset = offset[0]; } else if (offset.length >= 2) { xOffset = offset[0]; yOffset = offset[1]; } } xOffset = c.get(TEXT_OFFSET_X, xOffset, Float.class); yOffset = c.get(TEXT_OFFSET_Y, yOffset, Float.class); Color color = c.get(TEXT_COLOR, defaultTextColor, Color.class); float alpha = c.get(TEXT_OPACITY, 1f, Float.class); color = Utils.alphaMultiply(color, alpha); Float haloRadius = c.get(TEXT_HALO_RADIUS, null, Float.class); if (haloRadius != null && haloRadius <= 0) { haloRadius = null; } Color haloColor = null; if (haloRadius != null) { haloColor = c.get(TEXT_HALO_COLOR, Utils.complement(color), Color.class); float haloAlphaFactor = c.get(TEXT_HALO_OPACITY, 1f, Float.class); haloColor = Utils.alphaMultiply(haloColor, haloAlphaFactor); } Keyword positionKeyword = c.get(AreaElement.TEXT_POSITION, null, Keyword.class); PositionForAreaStrategy position = PositionForAreaStrategy.forKeyword(positionKeyword); return new TextLabel(strategy, font, (int) xOffset, -(int) yOffset, color, haloRadius, haloColor, position); } /** * Replies the label to be rendered for the primitive {@code osm}. * * @param osm the OSM object * @return the label, or null, if {@code osm} is null or if no label can be * derived for {@code osm} */ public String getString(OsmPrimitive osm) { if (labelCompositionStrategy == null) return null; return labelCompositionStrategy.compose(osm); } /** * Gets the strategy that defines where to place the label. * @return The strategy. Never null. * @since 11722 */ public PositionForAreaStrategy getLabelPositionStrategy() { return labelPositionStrategy; } public TextLabel withPosition(PositionForAreaStrategy labelPositionStrategy) { return new TextLabel(this, labelPositionStrategy); } @Override public String toString() { return "TextElement{" + toStringImpl() + '}'; } protected String toStringImpl() { StringBuilder sb = new StringBuilder(96); sb.append("labelCompositionStrategy=").append(labelCompositionStrategy) .append(" font=").append(font); if (xOffset != 0) { sb.append(" xOffset=").append(xOffset); } if (yOffset != 0) { sb.append(" yOffset=").append(yOffset); } sb.append(" color=").append(Utils.toString(color)); if (haloRadius != null) { sb.append(" haloRadius=").append(haloRadius) .append(" haloColor=").append(haloColor); } return sb.toString(); } @Override public int hashCode() { return Objects.hash(labelCompositionStrategy, font, xOffset, yOffset, color, haloRadius, haloColor); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; TextLabel textLabel = (TextLabel) obj; return xOffset == textLabel.xOffset && yOffset == textLabel.yOffset && Objects.equals(labelCompositionStrategy, textLabel.labelCompositionStrategy) && Objects.equals(font, textLabel.font) && Objects.equals(color, textLabel.color) && Objects.equals(haloRadius, textLabel.haloRadius) && Objects.equals(haloColor, textLabel.haloColor); } }