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