blob: efe78985da8209c92e785490f8585e92b6e84b6a [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25/*
26 *
27 * (C) Copyright IBM Corp. 1998, All Rights Reserved
28 */
29
30package sun.font;
31
32import java.awt.BasicStroke;
33import java.awt.Graphics2D;
34import java.awt.Shape;
35import java.awt.Stroke;
36
37import java.awt.geom.GeneralPath;
38import java.awt.geom.Line2D;
39
40import java.awt.font.TextAttribute;
41
42import 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
50abstract 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 }
131 else {
132 return new BasicStroke(lineThickness,
133 BasicStroke.CAP_BUTT,
134 BasicStroke.JOIN_MITER,
135 10.0f,
136 dashPattern,
137 0);
138 }
139 }
140
141 private float getLineThickness(float thickness) {
142
143 if (useThickness) {
144 return thickness * thicknessMultiplier;
145 }
146 else {
147 return DEFAULT_THICKNESS * thicknessMultiplier;
148 }
149 }
150
151 private Stroke getStroke(float thickness) {
152
153 float lineThickness = getLineThickness(thickness);
154 BasicStroke stroke = cachedStroke;
155 if (stroke == null ||
156 stroke.getLineWidth() != lineThickness) {
157
158 stroke = createStroke(lineThickness);
159 cachedStroke = stroke;
160 }
161
162 return stroke;
163 }
164
165 void drawUnderline(Graphics2D g2d,
166 float thickness,
167 float x1,
168 float x2,
169 float y) {
170
171
172 Stroke saveStroke = g2d.getStroke();
173 g2d.setStroke(getStroke(thickness));
174 g2d.draw(new Line2D.Float(x1, y + shift, x2, y + shift));
175 g2d.setStroke(saveStroke);
176 }
177
178 float getLowerDrawLimit(float thickness) {
179
180 return shift + getLineThickness(thickness);
181 }
182
183 Shape getUnderlineShape(float thickness,
184 float x1,
185 float x2,
186 float y) {
187
188 Stroke ulStroke = getStroke(thickness);
189 Line2D line = new Line2D.Float(x1, y + shift, x2, y + shift);
190 return ulStroke.createStrokedShape(line);
191 }
192 }
193
194 // Implementation of UNDERLINE_LOW_GRAY.
195 private static class IMGrayUnderline extends Underline {
196
197 private BasicStroke stroke;
198
199 IMGrayUnderline() {
200 stroke = new BasicStroke(DEFAULT_THICKNESS,
201 BasicStroke.CAP_BUTT,
202 BasicStroke.JOIN_MITER,
203 10.0f,
204 new float[] {1, 1},
205 0);
206 }
207
208 void drawUnderline(Graphics2D g2d,
209 float thickness,
210 float x1,
211 float x2,
212 float y) {
213
214 Stroke saveStroke = g2d.getStroke();
215 g2d.setStroke(stroke);
216
217 Line2D.Float drawLine = new Line2D.Float(x1, y, x2, y);
218 g2d.draw(drawLine);
219
220 drawLine.y1 += DEFAULT_THICKNESS;
221 drawLine.y2 += DEFAULT_THICKNESS;
222 drawLine.x1 += DEFAULT_THICKNESS;
223
224 g2d.draw(drawLine);
225
226 g2d.setStroke(saveStroke);
227 }
228
229 float getLowerDrawLimit(float thickness) {
230
231 return DEFAULT_THICKNESS * 2;
232 }
233
234 Shape getUnderlineShape(float thickness,
235 float x1,
236 float x2,
237 float y) {
238
239 GeneralPath gp = new GeneralPath();
240
241 Line2D.Float line = new Line2D.Float(x1, y, x2, y);
242 gp.append(stroke.createStrokedShape(line), false);
243
244 line.y1 += DEFAULT_THICKNESS;
245 line.y2 += DEFAULT_THICKNESS;
246 line.x1 += DEFAULT_THICKNESS;
247
248 gp.append(stroke.createStrokedShape(line), false);
249
250 return gp;
251 }
252 }
253
254 // Keep a map of underlines, one for each type
255 // of underline. The Underline objects are Flyweights
256 // (shared across multiple clients), so they should be immutable.
257 // If this implementation changes then clone underline
258 // instances in getUnderline before returning them.
259 private static final ConcurrentHashMap<Object, Underline>
260 UNDERLINES = new ConcurrentHashMap<Object, Underline>(6);
261 private static final Underline[] UNDERLINE_LIST;
262
263 static {
264 Underline[] uls = new Underline[6];
265
266 uls[0] = new StandardUnderline(0, 1, null, USE_THICKNESS);
267 UNDERLINES.put(TextAttribute.UNDERLINE_ON, uls[0]);
268
269 uls[1] = new StandardUnderline(1, 1, null, IGNORE_THICKNESS);
270 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_ONE_PIXEL, uls[1]);
271
272 uls[2] = new StandardUnderline(1, 2, null, IGNORE_THICKNESS);
273 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_TWO_PIXEL, uls[2]);
274
275 uls[3] = new StandardUnderline(1, 1, new float[] { 1, 1 }, IGNORE_THICKNESS);
276 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DOTTED, uls[3]);
277
278 uls[4] = new IMGrayUnderline();
279 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_GRAY, uls[4]);
280
281 uls[5] = new StandardUnderline(1, 1, new float[] { 4, 4 }, IGNORE_THICKNESS);
282 UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DASHED, uls[5]);
283
284 UNDERLINE_LIST = uls;
285 }
286
287 /**
288 * Return the Underline for the given value of
289 * TextAttribute.INPUT_METHOD_UNDERLINE or
290 * TextAttribute.UNDERLINE.
291 * If value is not an input method underline value or
292 * TextAttribute.UNDERLINE_ON, null is returned.
293 */
294 static Underline getUnderline(Object value) {
295
296 if (value == null) {
297 return null;
298 }
299
300 return (Underline) UNDERLINES.get(value);
301 }
302
303 static Underline getUnderline(int index) {
304 return index < 0 ? null : UNDERLINE_LIST[index];
305 }
306}