001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.styleelement;
003
004import java.awt.Color;
005import java.awt.Font;
006import java.awt.geom.Point2D;
007import java.util.Objects;
008
009import org.openstreetmap.josm.data.osm.IPrimitive;
010import org.openstreetmap.josm.data.osm.OsmPrimitive;
011import org.openstreetmap.josm.gui.mappaint.Cascade;
012import org.openstreetmap.josm.gui.mappaint.Environment;
013import org.openstreetmap.josm.gui.mappaint.Keyword;
014import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.TagKeyReference;
015import org.openstreetmap.josm.gui.mappaint.StyleKeys;
016import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy;
017import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.StaticLabelCompositionStrategy;
018import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.TagLookupCompositionStrategy;
019import org.openstreetmap.josm.tools.CheckParameterUtil;
020import org.openstreetmap.josm.tools.Utils;
021
022/**
023 * Represents the rendering style for a textual label placed somewhere on the map.
024 * @since 3880
025 */
026public class TextLabel implements StyleKeys {
027    /**
028     * The default strategy to use when determining the label of a element.
029     */
030    public static final LabelCompositionStrategy AUTO_LABEL_COMPOSITION_STRATEGY = new DeriveLabelFromNameTagsCompositionStrategy();
031
032    /**
033     * The strategy for building the actual label value for a given a {@link OsmPrimitive}.
034     * Check for null before accessing.
035     */
036    public LabelCompositionStrategy labelCompositionStrategy;
037    /**
038     * the font to be used when rendering
039     */
040    public Font font;
041    /**
042     * The color to draw the text in, includes alpha.
043     */
044    public Color color;
045    /**
046     * The radius of the halo effect.
047     */
048    public Float haloRadius;
049    /**
050     * The color of the halo effect.
051     */
052    public Color haloColor;
053
054    /**
055     * Creates a new text element
056     *
057     * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered.
058     * If null, no label is rendered.
059     * @param font the font to be used. Must not be null.
060     * @param color the color to be used. Must not be null
061     * @param haloRadius halo radius
062     * @param haloColor halo color
063     */
064    protected TextLabel(LabelCompositionStrategy strategy, Font font, Color color, Float haloRadius,
065            Color haloColor) {
066        this.labelCompositionStrategy = strategy;
067        this.font = Objects.requireNonNull(font, "font");
068        this.color = Objects.requireNonNull(color, "color");
069        this.haloRadius = haloRadius;
070        this.haloColor = haloColor;
071    }
072
073    /**
074     * Copy constructor
075     *
076     * @param other the other element.
077     */
078    public TextLabel(TextLabel other) {
079        this.labelCompositionStrategy = other.labelCompositionStrategy;
080        this.font = other.font;
081        this.color = other.color;
082        this.haloColor = other.haloColor;
083        this.haloRadius = other.haloRadius;
084    }
085
086    /**
087     * Derives a suitable label composition strategy from the style properties in {@code c}.
088     *
089     * @param c the style properties
090     * @param defaultAnnotate whether to return {@link #AUTO_LABEL_COMPOSITION_STRATEGY} if not strategy is found
091     * @return the label composition strategy, or {@code null}
092     */
093    protected static LabelCompositionStrategy buildLabelCompositionStrategy(Cascade c, boolean defaultAnnotate) {
094        /*
095         * If the cascade includes a TagKeyReference we will lookup the rendered label
096         * from a tag value.
097         */
098        TagKeyReference tkr = c.get(TEXT, null, TagKeyReference.class, true);
099        if (tkr != null)
100            return new TagLookupCompositionStrategy(tkr.key);
101
102        /*
103         * Check whether the label composition strategy is given by a keyword
104         */
105        Keyword keyword = c.get(TEXT, null, Keyword.class, true);
106        if (Keyword.AUTO.equals(keyword))
107            return AUTO_LABEL_COMPOSITION_STRATEGY;
108
109        /*
110         * Do we have a static text label?
111         */
112        String text = c.get(TEXT, null, String.class, true);
113        if (text != null)
114            return new StaticLabelCompositionStrategy(text);
115        return defaultAnnotate ? AUTO_LABEL_COMPOSITION_STRATEGY : null;
116    }
117
118    /**
119     * Builds a text element from style properties in {@code c} and the
120     * default text color {@code defaultTextColor}
121     *
122     * @param env the environment
123     * @param defaultTextColor the default text color. Must not be null.
124     * @param defaultAnnotate true, if a text label shall be rendered by default, even if the style sheet
125     *   doesn't include respective style declarations
126     * @return the text element or null, if the style properties don't include
127     * properties for text rendering
128     * @throws IllegalArgumentException if {@code defaultTextColor} is null
129     */
130    public static TextLabel create(Environment env, Color defaultTextColor, boolean defaultAnnotate) {
131        CheckParameterUtil.ensureParameterNotNull(defaultTextColor);
132        Cascade c = env.mc.getCascade(env.layer);
133
134        LabelCompositionStrategy strategy = buildLabelCompositionStrategy(c, defaultAnnotate);
135        if (strategy == null) return null;
136        String s = strategy.compose(env.osm);
137        if (s == null) return null;
138        Font font = StyleElement.getFont(c, s);
139
140        Color color = c.get(TEXT_COLOR, defaultTextColor, Color.class);
141        float alpha = c.get(TEXT_OPACITY, 1f, Float.class);
142        color = Utils.alphaMultiply(color, alpha);
143
144        Float haloRadius = c.get(TEXT_HALO_RADIUS, null, Float.class);
145        if (haloRadius != null && haloRadius <= 0) {
146            haloRadius = null;
147        }
148        Color haloColor = null;
149        if (haloRadius != null) {
150            haloColor = c.get(TEXT_HALO_COLOR, Utils.complement(color), Color.class);
151            float haloAlphaFactor = c.get(TEXT_HALO_OPACITY, 1f, Float.class);
152            haloColor = Utils.alphaMultiply(haloColor, haloAlphaFactor);
153        }
154
155        return new TextLabel(strategy, font, color, haloRadius, haloColor);
156    }
157
158    /**
159     * Gets the text-offset property from a cascade
160     * @param c The cascade
161     * @return The text offset property
162     */
163    public static Point2D getTextOffset(Cascade c) {
164        float xOffset = 0;
165        float yOffset = 0;
166        float[] offset = c.get(TEXT_OFFSET, null, float[].class);
167        if (offset != null) {
168            if (offset.length == 1) {
169                yOffset = offset[0];
170            } else if (offset.length >= 2) {
171                xOffset = offset[0];
172                yOffset = offset[1];
173            }
174        }
175        xOffset = c.get(TEXT_OFFSET_X, xOffset, Float.class);
176        yOffset = c.get(TEXT_OFFSET_Y, yOffset, Float.class);
177        return new Point2D.Double(xOffset, yOffset);
178    }
179
180    /**
181     * Replies the label to be rendered for the primitive {@code osm}.
182     *
183     * @param osm the OSM object
184     * @return the label, or null, if {@code osm} is null or if no label can be
185     * derived for {@code osm}
186     */
187    public String getString(IPrimitive osm) {
188        if (labelCompositionStrategy == null) return null;
189        return labelCompositionStrategy.compose(osm);
190    }
191
192    @Override
193    public String toString() {
194        return "TextLabel{" + toStringImpl() + '}';
195    }
196
197    protected String toStringImpl() {
198        StringBuilder sb = new StringBuilder(96);
199        sb.append("labelCompositionStrategy=").append(labelCompositionStrategy)
200          .append(" font=").append(font)
201          .append(" color=").append(Utils.toString(color));
202        if (haloRadius != null) {
203            sb.append(" haloRadius=").append(haloRadius)
204              .append(" haloColor=").append(haloColor);
205        }
206        return sb.toString();
207    }
208
209    @Override
210    public int hashCode() {
211        return Objects.hash(labelCompositionStrategy, font, color, haloRadius, haloColor);
212    }
213
214    @Override
215    public boolean equals(Object obj) {
216        if (this == obj) return true;
217        if (obj == null || getClass() != obj.getClass()) return false;
218        TextLabel textLabel = (TextLabel) obj;
219        return Objects.equals(labelCompositionStrategy, textLabel.labelCompositionStrategy) &&
220                Objects.equals(font, textLabel.font) &&
221                Objects.equals(color, textLabel.color) &&
222                Objects.equals(haloRadius, textLabel.haloRadius) &&
223                Objects.equals(haloColor, textLabel.haloColor);
224    }
225}