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