001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.mappaint;
003    
004    import static org.openstreetmap.josm.tools.Utils.equal;
005    
006    import java.awt.Color;
007    import java.awt.Font;
008    
009    import org.openstreetmap.josm.data.osm.OsmPrimitive;
010    import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy;
011    import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy;
012    import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy;
013    import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.TagKeyReference;
014    import org.openstreetmap.josm.tools.CheckParameterUtil;
015    import org.openstreetmap.josm.tools.Utils;
016    
017    /**
018     * Represents the rendering style for a textual label placed somewhere on the map.
019     *
020     */
021    public 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    }