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