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