001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.mappaint; 003 004 import static org.openstreetmap.josm.tools.Utils.equal; 005 006 import java.awt.Color; 007 import java.awt.Font; 008 009 import org.openstreetmap.josm.data.osm.OsmPrimitive; 010 import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy; 011 import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy; 012 import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy; 013 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.TagKeyReference; 014 import org.openstreetmap.josm.tools.CheckParameterUtil; 015 import org.openstreetmap.josm.tools.Utils; 016 017 /** 018 * Represents the rendering style for a textual label placed somewhere on the map. 019 * 020 */ 021 public class TextElement implements StyleKeys { 022 static public final LabelCompositionStrategy AUTO_LABEL_COMPOSITION_STRATEGY = new DeriveLabelFromNameTagsCompositionStrategy(); 023 024 /** the strategy for building the actual label value for a given a {@link OsmPrimitive}. 025 * Check for null before accessing. 026 */ 027 public LabelCompositionStrategy labelCompositionStrategy; 028 /** the font to be used when rendering*/ 029 public Font font; 030 public int xOffset; 031 public int yOffset; 032 public Color color; 033 public Float haloRadius; 034 public Color haloColor; 035 036 /** 037 * Creates a new text element 038 * 039 * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered. 040 * If null, no label is rendered. 041 * @param font the font to be used. Must not be null. 042 * @param xOffset 043 * @param yOffset 044 * @param color the color to be used. Must not be null 045 * @param haloRadius 046 * @param haloColor 047 */ 048 public TextElement(LabelCompositionStrategy strategy, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor) { 049 CheckParameterUtil.ensureParameterNotNull(font); 050 CheckParameterUtil.ensureParameterNotNull(color); 051 labelCompositionStrategy = strategy; 052 this.font = font; 053 this.xOffset = xOffset; 054 this.yOffset = yOffset; 055 this.color = color; 056 this.haloRadius = haloRadius; 057 this.haloColor = haloColor; 058 } 059 060 /** 061 * Copy constructor 062 * 063 * @param other the other element. 064 */ 065 public TextElement(TextElement other) { 066 this.labelCompositionStrategy = other.labelCompositionStrategy; 067 this.font = other.font; 068 this.xOffset = other.xOffset; 069 this.yOffset = other.yOffset; 070 this.color = other.color; 071 this.haloColor = other.haloColor; 072 this.haloRadius = other.haloRadius; 073 } 074 075 /** 076 * Derives a suitable label composition strategy from the style properties in 077 * {@code c}. 078 * 079 * @param c the style properties 080 * @return the label composition strategy 081 */ 082 protected static LabelCompositionStrategy buildLabelCompositionStrategy(Cascade c, boolean defaultAnnotate){ 083 /* 084 * If the cascade includes a TagKeyReference we will lookup the rendered label 085 * from a tag value. 086 */ 087 TagKeyReference tkr = c.get(TEXT, null, TagKeyReference.class, true); 088 if (tkr != null) 089 return new TagLookupCompositionStrategy(tkr.key); 090 091 /* 092 * Check whether the label composition strategy is given by 093 * a keyword 094 */ 095 Keyword keyword = c.get(TEXT, null, Keyword.class, true); 096 if (equal(keyword, Keyword.AUTO)) 097 return AUTO_LABEL_COMPOSITION_STRATEGY; 098 099 /* 100 * Do we have a static text label? 101 */ 102 String text = c.get(TEXT, null, String.class, true); 103 if (text != null) 104 return new StaticLabelCompositionStrategy(text); 105 return defaultAnnotate ? AUTO_LABEL_COMPOSITION_STRATEGY : null; 106 } 107 108 /** 109 * Builds a text element from style properties in {@code c} and the 110 * default text color {@code defaultTextColor} 111 * 112 * @param c the style properties 113 * @param defaultTextColor the default text color. Must not be null. 114 * @param defaultAnnotate true, if a text label shall be rendered by default, even if the style sheet 115 * doesn't include respective style declarations 116 * @return the text element or null, if the style properties don't include 117 * properties for text rendering 118 * @throws IllegalArgumentException thrown if {@code defaultTextColor} is null 119 */ 120 public static TextElement create(Cascade c, Color defaultTextColor, boolean defaultAnnotate) throws IllegalArgumentException{ 121 CheckParameterUtil.ensureParameterNotNull(defaultTextColor); 122 123 LabelCompositionStrategy strategy = buildLabelCompositionStrategy(c, defaultAnnotate); 124 if (strategy == null) return null; 125 Font font = ElemStyle.getFont(c); 126 127 float xOffset = 0; 128 float yOffset = 0; 129 float[] offset = c.get("text-offset", null, float[].class); 130 if (offset != null) { 131 if (offset.length == 1) { 132 yOffset = offset[0]; 133 } else if (offset.length >= 2) { 134 xOffset = offset[0]; 135 yOffset = offset[1]; 136 } 137 } 138 xOffset = c.get("text-offset-x", xOffset, Float.class); 139 yOffset = c.get("text-offset-y", yOffset, Float.class); 140 141 Color color = c.get("text-color", defaultTextColor, Color.class); 142 float alpha = c.get("text-opacity", 1f, Float.class); 143 color = new Color(color.getRed(), color.getGreen(), 144 color.getBlue(), Utils.color_float2int(alpha)); 145 146 Float haloRadius = c.get("text-halo-radius", null, Float.class); 147 if (haloRadius != null && haloRadius <= 0) { 148 haloRadius = null; 149 } 150 Color haloColor = null; 151 if (haloRadius != null) { 152 haloColor = c.get("text-halo-color", Utils.complement(color), Color.class); 153 float haloAlpha = c.get("text-halo-opacity", 1f, Float.class); 154 haloColor = new Color(haloColor.getRed(), haloColor.getGreen(), 155 haloColor.getBlue(), Utils.color_float2int(haloAlpha)); 156 } 157 158 return new TextElement(strategy, font, (int) xOffset, - (int) yOffset, color, haloRadius, haloColor); 159 } 160 161 /** 162 * Replies the label to be rendered for the primitive {@code osm}. 163 * 164 * @param osm the OSM object 165 * @return the label, or null, if {@code osm} is null or if no label can be 166 * derived for {@code osm} 167 */ 168 public String getString(OsmPrimitive osm) { 169 if (labelCompositionStrategy == null) return null; 170 return labelCompositionStrategy.compose(osm); 171 } 172 173 @Override 174 public String toString() { 175 return "TextElement{" + toStringImpl() + '}'; 176 } 177 178 protected String toStringImpl() { 179 StringBuilder sb = new StringBuilder(); 180 sb.append("labelCompositionStrategy=" + labelCompositionStrategy); 181 sb.append(" font=" + font); 182 if (xOffset != 0) { 183 sb.append(" xOffset=" + xOffset); 184 } 185 if (yOffset != 0) { 186 sb.append(" yOffset=" + yOffset); 187 } 188 sb.append(" color=" + Utils.toString(color)); 189 if (haloRadius != null) { 190 sb.append(" haloRadius=" + haloRadius); 191 sb.append(" haloColor=" + haloColor); 192 } 193 return sb.toString(); 194 } 195 196 @Override 197 public int hashCode() { 198 int hash = 3; 199 hash = 79 * hash + (labelCompositionStrategy != null ? labelCompositionStrategy.hashCode() : 0); 200 hash = 79 * hash + font.hashCode(); 201 hash = 79 * hash + xOffset; 202 hash = 79 * hash + yOffset; 203 hash = 79 * hash + color.hashCode(); 204 hash = 79 * hash + (haloRadius != null ? Float.floatToIntBits(haloRadius) : 0); 205 hash = 79 * hash + (haloColor != null ? haloColor.hashCode() : 0); 206 return hash; 207 } 208 209 @Override 210 public boolean equals(Object obj) { 211 if (obj == null || getClass() != obj.getClass()) 212 return false; 213 final TextElement other = (TextElement) obj; 214 return equal(labelCompositionStrategy, other.labelCompositionStrategy) && 215 equal(font, other.font) && 216 xOffset == other.xOffset && 217 yOffset == other.yOffset && 218 equal(color, other.color) && 219 equal(haloRadius, other.haloRadius) && 220 equal(haloColor, other.haloColor); 221 } 222 }