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