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