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