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}