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 * @since 3880 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 x offset 042 * @param yOffset y offset 043 * @param color the color to be used. Must not be null 044 * @param haloRadius halo radius 045 * @param haloColor halo color 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 {@code c}. 076 * 077 * @param c the style properties 078 * @return the label composition strategy 079 */ 080 protected static LabelCompositionStrategy buildLabelCompositionStrategy(Cascade c, boolean defaultAnnotate) { 081 /* 082 * If the cascade includes a TagKeyReference we will lookup the rendered label 083 * from a tag value. 084 */ 085 TagKeyReference tkr = c.get(TEXT, null, TagKeyReference.class, true); 086 if (tkr != null) 087 return new TagLookupCompositionStrategy(tkr.key); 088 089 /* 090 * Check whether the label composition strategy is given by a keyword 091 */ 092 Keyword keyword = c.get(TEXT, null, Keyword.class, true); 093 if (Keyword.AUTO.equals(keyword)) 094 return AUTO_LABEL_COMPOSITION_STRATEGY; 095 096 /* 097 * Do we have a static text label? 098 */ 099 String text = c.get(TEXT, null, String.class, true); 100 if (text != null) 101 return new StaticLabelCompositionStrategy(text); 102 return defaultAnnotate ? AUTO_LABEL_COMPOSITION_STRATEGY : null; 103 } 104 105 /** 106 * Builds a text element from style properties in {@code c} and the 107 * default text color {@code defaultTextColor} 108 * 109 * @param env the environment 110 * @param defaultTextColor the default text color. Must not be null. 111 * @param defaultAnnotate true, if a text label shall be rendered by default, even if the style sheet 112 * doesn't include respective style declarations 113 * @return the text element or null, if the style properties don't include 114 * properties for text rendering 115 * @throws IllegalArgumentException if {@code defaultTextColor} is null 116 */ 117 public static TextElement create(Environment env, Color defaultTextColor, boolean defaultAnnotate) { 118 CheckParameterUtil.ensureParameterNotNull(defaultTextColor); 119 Cascade c = env.mc.getCascade(env.layer); 120 121 LabelCompositionStrategy strategy = buildLabelCompositionStrategy(c, defaultAnnotate); 122 if (strategy == null) return null; 123 String s = strategy.compose(env.osm); 124 if (s == null) return null; 125 Font font = ElemStyle.getFont(c, s); 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(96); 180 sb.append("labelCompositionStrategy=").append(labelCompositionStrategy) 181 .append(" font=").append(font); 182 if (xOffset != 0) { 183 sb.append(" xOffset=").append(xOffset); 184 } 185 if (yOffset != 0) { 186 sb.append(" yOffset=").append(yOffset); 187 } 188 sb.append(" color=").append(Utils.toString(color)); 189 if (haloRadius != null) { 190 sb.append(" haloRadius=").append(haloRadius) 191 .append(" haloColor=").append(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 Objects.equals(labelCompositionStrategy, other.labelCompositionStrategy) && 215 Objects.equals(font, other.font) && 216 xOffset == other.xOffset && 217 yOffset == other.yOffset && 218 Objects.equals(color, other.color) && 219 Objects.equals(haloRadius, other.haloRadius) && 220 Objects.equals(haloColor, other.haloColor); 221 } 222}