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