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