1 /* 2 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 /* 26 * 27 * (C) Copyright IBM Corp. 1998, All Rights Reserved 28 */ 29 30 package sun.font; 31 32 import java.awt.BasicStroke; 33 import java.awt.Graphics2D; 34 import java.awt.Shape; 35 import java.awt.Stroke; 36 37 import java.awt.geom.GeneralPath; 38 import java.awt.geom.Line2D; 39 40 import java.awt.font.TextAttribute; 41 42 import java.util.concurrent.ConcurrentHashMap; 43 44 /** 45 * This class provides drawing and bounds-measurement of 46 * underlines. Additionally, it has a factory method for 47 * obtaining underlines from values of underline attributes. 48 */ 49 50 abstract class Underline { 51 52 /** 53 * Draws the underline into g2d. The thickness should be obtained 54 * from a LineMetrics object. Note that some underlines ignore the 55 * thickness parameter. 56 * The underline is drawn from (x1, y) to (x2, y). 57 */ 58 abstract void drawUnderline(Graphics2D g2d, 59 float thickness, 60 float x1, 61 float x2, 62 float y); 63 64 /** 65 * Returns the bottom of the bounding rectangle for this underline. 66 */ 67 abstract float getLowerDrawLimit(float thickness); 68 69 /** 70 * Returns a Shape representing the underline. The thickness should be obtained 71 * from a LineMetrics object. Note that some underlines ignore the 72 * thickness parameter. 73 */ 74 abstract Shape getUnderlineShape(float thickness, 75 float x1, 76 float x2, 77 float y); 78 79 // Implementation of underline for standard and Input Method underlines. 80 // These classes are private. 81 82 // IM Underlines ignore thickness param, and instead use 83 // DEFAULT_THICKNESS 84 private static final float DEFAULT_THICKNESS = 1.0f; 85 86 // StandardUnderline's constructor takes a boolean param indicating 87 // whether to override the default thickness. These values clarify 88 // the semantics of the parameter. 89 private static final boolean USE_THICKNESS = true; 90 private static final boolean IGNORE_THICKNESS = false; 91 92 // Implementation of standard underline and all input method underlines 93 // except UNDERLINE_LOW_GRAY. 94 private static final class StandardUnderline extends Underline { 95 96 // the amount by which to move the underline 97 private float shift; 98 99 // the actual line thickness is this value times 100 // the requested thickness 101 private float thicknessMultiplier; 102 103 // if non-null, underline is drawn with a BasicStroke 104 // with this dash pattern 105 private float[] dashPattern; 106 107 // if false, all underlines are DEFAULT_THICKNESS thick 108 // if true, use thickness param 109 private boolean useThickness; 110 111 // cached BasicStroke 112 private BasicStroke cachedStroke; 113 114 StandardUnderline(float shift, 115 float thicknessMultiplier, 116 float[] dashPattern, 117 boolean useThickness) { 118 119 this.shift = shift; 120 this.thicknessMultiplier = thicknessMultiplier; 121 this.dashPattern = dashPattern; 122 this.useThickness = useThickness; 123 this.cachedStroke = null; 124 } 125 126 private BasicStroke createStroke(float lineThickness) { 127 128 if (dashPattern == null) { 129 return new BasicStroke(lineThickness, 130 BasicStroke.CAP_BUTT, 131 BasicStroke.JOIN_MITER); 132 } 133 else { 134 return new BasicStroke(lineThickness, 135 BasicStroke.CAP_BUTT, 136 BasicStroke.JOIN_MITER, 137 10.0f, 138 dashPattern, 139 0); 140 } 141 } 142 143 private float getLineThickness(float thickness) { 144 145 if (useThickness) { 146 return thickness * thicknessMultiplier; 147 } 148 else { 149 return DEFAULT_THICKNESS * thicknessMultiplier; 150 } 151 } 152 153 private Stroke getStroke(float thickness) { 154 155 float lineThickness = getLineThickness(thickness); 156 BasicStroke stroke = cachedStroke; 157 if (stroke == null || 158 stroke.getLineWidth() != lineThickness) { 159 160 stroke = createStroke(lineThickness); 161 cachedStroke = stroke; 162 } 163 164 return stroke; 165 } 166 167 void drawUnderline(Graphics2D g2d, 168 float thickness, 169 float x1, 170 float x2, 171 float y) { 172 173 174 Stroke saveStroke = g2d.getStroke(); 175 g2d.setStroke(getStroke(thickness)); 176 g2d.draw(new Line2D.Float(x1, y + shift, x2, y + shift)); 177 g2d.setStroke(saveStroke); 178 } 179 180 float getLowerDrawLimit(float thickness) { 181 182 return shift + getLineThickness(thickness); 183 } 184 185 Shape getUnderlineShape(float thickness, 186 float x1, 187 float x2, 188 float y) { 189 190 Stroke ulStroke = getStroke(thickness); 191 Line2D line = new Line2D.Float(x1, y + shift, x2, y + shift); 192 return ulStroke.createStrokedShape(line); 193 } 194 } 195 196 // Implementation of UNDERLINE_LOW_GRAY. 197 private static class IMGrayUnderline extends Underline { 198 199 private BasicStroke stroke; 200 201 IMGrayUnderline() { 202 stroke = new BasicStroke(DEFAULT_THICKNESS, 203 BasicStroke.CAP_BUTT, 204 BasicStroke.JOIN_MITER, 205 10.0f, 206 new float[] {1, 1}, 207 0); 208 } 209 210 void drawUnderline(Graphics2D g2d, 211 float thickness, 212 float x1, 213 float x2, 214 float y) { 215 216 Stroke saveStroke = g2d.getStroke(); 217 g2d.setStroke(stroke); 218 219 Line2D.Float drawLine = new Line2D.Float(x1, y, x2, y); 220 g2d.draw(drawLine); 221 222 drawLine.y1 += DEFAULT_THICKNESS; 223 drawLine.y2 += DEFAULT_THICKNESS; 224 drawLine.x1 += DEFAULT_THICKNESS; 225 226 g2d.draw(drawLine); 227 228 g2d.setStroke(saveStroke); 229 } 230 231 float getLowerDrawLimit(float thickness) { 232 233 return DEFAULT_THICKNESS * 2; 234 } 235 236 Shape getUnderlineShape(float thickness, 237 float x1, 238 float x2, 239 float y) { 240 241 GeneralPath gp = new GeneralPath(); 242 243 Line2D.Float line = new Line2D.Float(x1, y, x2, y); 244 gp.append(stroke.createStrokedShape(line), false); 245 246 line.y1 += DEFAULT_THICKNESS; 247 line.y2 += DEFAULT_THICKNESS; 248 line.x1 += DEFAULT_THICKNESS; 249 250 gp.append(stroke.createStrokedShape(line), false); 251 252 return gp; 253 } 254 } 255 256 // Keep a map of underlines, one for each type 257 // of underline. The Underline objects are Flyweights 258 // (shared across multiple clients), so they should be immutable. 259 // If this implementation changes then clone underline 260 // instances in getUnderline before returning them. 261 private static final ConcurrentHashMap<Object, Underline> 262 UNDERLINES = new ConcurrentHashMap<Object, Underline>(6); 263 private static final Underline[] UNDERLINE_LIST; 264 265 static { 266 Underline[] uls = new Underline[6]; 267 268 uls[0] = new StandardUnderline(0, 1, null, USE_THICKNESS); 269 UNDERLINES.put(TextAttribute.UNDERLINE_ON, uls[0]); 270 271 uls[1] = new StandardUnderline(1, 1, null, IGNORE_THICKNESS); 272 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_ONE_PIXEL, uls[1]); 273 274 uls[2] = new StandardUnderline(1, 2, null, IGNORE_THICKNESS); 275 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_TWO_PIXEL, uls[2]); 276 277 uls[3] = new StandardUnderline(1, 1, new float[] { 1, 1 }, IGNORE_THICKNESS); 278 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DOTTED, uls[3]); 279 280 uls[4] = new IMGrayUnderline(); 281 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_GRAY, uls[4]); 282 283 uls[5] = new StandardUnderline(1, 1, new float[] { 4, 4 }, IGNORE_THICKNESS); 284 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DASHED, uls[5]); 285 286 UNDERLINE_LIST = uls; 287 } 288 289 /** 290 * Return the Underline for the given value of 291 * TextAttribute.INPUT_METHOD_UNDERLINE or 292 * TextAttribute.UNDERLINE. 293 * If value is not an input method underline value or 294 * TextAttribute.UNDERLINE_ON, null is returned. 295 */ 296 static Underline getUnderline(Object value) { 297 298 if (value == null) { 299 return null; 300 } 301 302 return UNDERLINES.get(value); 303 } 304 305 static Underline getUnderline(int index) { 306 return index < 0 ? null : UNDERLINE_LIST[index]; 307 } 308 }