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