001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004import java.awt.Font;
005import java.util.HashMap;
006import java.util.Map;
007
008import org.openstreetmap.josm.Main;
009import org.openstreetmap.josm.data.osm.OsmPrimitive;
010import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
011import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
012import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
013
014public abstract class ElemStyle implements StyleKeys {
015
016    protected static final String[] ICON_KEYS = {"icon-image", "icon-width", "icon-height", "icon-opacity"};
017    protected static final String[] REPEAT_IMAGE_KEYS = {"repeat-image", "repeat-image-width", "repeat-image-height", "repeat-image-opacity"};
018
019    public float major_z_index;
020    public float z_index;
021    public float object_z_index;
022    public boolean isModifier;  // false, if style can serve as main style for the
023    // primitive; true, if it is a highlight or modifier
024
025    public ElemStyle(float major_z_index, float z_index, float object_z_index, boolean isModifier) {
026        this.major_z_index = major_z_index;
027        this.z_index = z_index;
028        this.object_z_index = object_z_index;
029        this.isModifier = isModifier;
030    }
031
032    protected ElemStyle(Cascade c, float default_major_z_index) {
033        major_z_index = c.get("major-z-index", default_major_z_index, Float.class);
034        z_index = c.get(Z_INDEX, 0f, Float.class);
035        object_z_index = c.get(OBJECT_Z_INDEX, 0f, Float.class);
036        isModifier = c.get(MODIFIER, false, Boolean.class);
037    }
038
039    /**
040     * draws a primitive
041     * @param primitive
042     * @param paintSettings
043     * @param painter
044     * @param selected true, if primitive is selected
045     * @param member true, if primitive is not selected and member of a selected relation
046     */
047    public abstract void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member);
048
049    public boolean isProperLineStyle() {
050        return false;
051    }
052
053    /**
054     * Get a property value of type Width
055     * @param c the cascade
056     * @param key property key for the width value
057     * @param relativeTo reference width. Only needed, when relative width syntax
058     *              is used, e.g. "+4".
059     */
060    protected static Float getWidth(Cascade c, String key, Float relativeTo) {
061        Float width = c.get(key, null, Float.class, true);
062        if (width != null) {
063            if (width > 0)
064                return width;
065        } else {
066            Keyword widthKW = c.get(key, null, Keyword.class, true);
067            if (Keyword.THINNEST.equals(widthKW))
068                return 0f;
069            if (Keyword.DEFAULT.equals(widthKW))
070                return (float) MapPaintSettings.INSTANCE.getDefaultSegmentWidth();
071            if (relativeTo != null) {
072                RelativeFloat width_rel = c.get(key, null, RelativeFloat.class, true);
073                if (width_rel != null)
074                    return relativeTo + width_rel.val;
075            }
076        }
077        return null;
078    }
079
080    /* ------------------------------------------------------------------------------- */
081    /* cached values                                                                   */
082    /* ------------------------------------------------------------------------------- */
083    /*
084     * Two preference values and the set of created fonts are cached in order to avoid
085     * expensive lookups and to avoid too many font objects
086     *
087     * FIXME: cached preference values are not updated if the user changes them during
088     * a JOSM session. Should have a listener listening to preference changes.
089     */
090    private static volatile String DEFAULT_FONT_NAME = null;
091    private static volatile Float DEFAULT_FONT_SIZE = null;
092    private static final Object lock = new Object();
093
094    // thread save access (double-checked locking)
095    private static Float getDefaultFontSize() {
096        Float s = DEFAULT_FONT_SIZE;
097        if (s == null) {
098            synchronized (lock) {
099                s = DEFAULT_FONT_SIZE;
100                if (s == null) {
101                    DEFAULT_FONT_SIZE = s = (float) Main.pref.getInteger("mappaint.fontsize", 8);
102                }
103            }
104        }
105        return s;
106    }
107
108    private static String getDefaultFontName() {
109        String n = DEFAULT_FONT_NAME;
110        if (n == null) {
111            synchronized (lock) {
112                n = DEFAULT_FONT_NAME;
113                if (n == null) {
114                    DEFAULT_FONT_NAME = n = Main.pref.get("mappaint.font", "Droid Sans");
115                }
116            }
117        }
118        return n;
119    }
120
121    private static class FontDescriptor {
122        public String name;
123        public int style;
124        public int size;
125
126        public FontDescriptor(String name, int style, int size){
127            this.name = name;
128            this.style = style;
129            this.size = size;
130        }
131
132        @Override
133        public int hashCode() {
134            final int prime = 31;
135            int result = 1;
136            result = prime * result + ((name == null) ? 0 : name.hashCode());
137            result = prime * result + size;
138            result = prime * result + style;
139            return result;
140        }
141        @Override
142        public boolean equals(Object obj) {
143            if (this == obj)
144                return true;
145            if (obj == null)
146                return false;
147            if (getClass() != obj.getClass())
148                return false;
149            FontDescriptor other = (FontDescriptor) obj;
150            if (name == null) {
151                if (other.name != null)
152                    return false;
153            } else if (!name.equals(other.name))
154                return false;
155            if (size != other.size)
156                return false;
157            if (style != other.style)
158                return false;
159            return true;
160        }
161    }
162
163    private static final Map<FontDescriptor, Font> FONT_MAP = new HashMap<>();
164    private static Font getCachedFont(FontDescriptor fd) {
165        Font f = FONT_MAP.get(fd);
166        if (f != null) return f;
167        f = new Font(fd.name, fd.style, fd.size);
168        FONT_MAP.put(fd, f);
169        return f;
170    }
171
172    private static Font getCachedFont(String name, int style, int size){
173        return getCachedFont(new FontDescriptor(name, style, size));
174    }
175
176    protected static Font getFont(Cascade c, String s) {
177        String name = c.get("font-family", getDefaultFontName(), String.class);
178        float size = c.get("font-size", getDefaultFontSize(), Float.class);
179        int weight = Font.PLAIN;
180        if ("bold".equalsIgnoreCase(c.get("font-weight", null, String.class))) {
181            weight = Font.BOLD;
182        }
183        int style = Font.PLAIN;
184        if ("italic".equalsIgnoreCase(c.get("font-style", null, String.class))) {
185            style = Font.ITALIC;
186        }
187        Font f = getCachedFont(name, style | weight, Math.round(size));
188        if (f.canDisplayUpTo(s) == -1)
189            return f;
190        else {
191            // fallback if the string contains characters that cannot be
192            // rendered by the selected font
193            return getCachedFont("SansSerif", style | weight, Math.round(size));
194        }
195    }
196
197    @Override
198    public boolean equals(Object o) {
199        if (!(o instanceof ElemStyle))
200            return false;
201        ElemStyle s = (ElemStyle) o;
202        return major_z_index == s.major_z_index &&
203                z_index == s.z_index &&
204                object_z_index == s.object_z_index &&
205                isModifier == s.isModifier;
206    }
207
208    @Override
209    public int hashCode() {
210        int hash = 5;
211        hash = 41 * hash + Float.floatToIntBits(this.major_z_index);
212        hash = 41 * hash + Float.floatToIntBits(this.z_index);
213        hash = 41 * hash + Float.floatToIntBits(this.object_z_index);
214        hash = 41 * hash + (isModifier ? 1 : 0);
215        return hash;
216    }
217
218    @Override
219    public String toString() {
220        return String.format("z_idx=[%s/%s/%s] ", major_z_index, z_index, object_z_index) + (isModifier ? "modifier " : "");
221    }
222}