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      */
drawUnderline(Graphics2D g2d, float thickness, float x1, float x2, float y)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      */
getLowerDrawLimit(float thickness)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      */
getUnderlineShape(float thickness, float x1, float x2, float y)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 
StandardUnderline(float shift, float thicknessMultiplier, float[] dashPattern, boolean useThickness)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 
createStroke(float lineThickness)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 
getLineThickness(float thickness)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 
getStroke(float thickness)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 
drawUnderline(Graphics2D g2d, float thickness, float x1, float x2, float y)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 
getLowerDrawLimit(float thickness)180         float getLowerDrawLimit(float thickness) {
181 
182             return shift + getLineThickness(thickness);
183         }
184 
getUnderlineShape(float thickness, float x1, float x2, float y)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 
IMGrayUnderline()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 
drawUnderline(Graphics2D g2d, float thickness, float x1, float x2, float y)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 
getLowerDrawLimit(float thickness)231         float getLowerDrawLimit(float thickness) {
232 
233             return DEFAULT_THICKNESS * 2;
234         }
235 
getUnderlineShape(float thickness, float x1, float x2, float y)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);
UNDERLINES.put(TextAttribute.UNDERLINE_ON, uls[0])269         UNDERLINES.put(TextAttribute.UNDERLINE_ON, uls[0]);
270 
271         uls[1] = new StandardUnderline(1, 1, null, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_ONE_PIXEL, uls[1])272         UNDERLINES.put(TextAttribute.UNDERLINE_LOW_ONE_PIXEL, uls[1]);
273 
274         uls[2] = new StandardUnderline(1, 2, null, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_TWO_PIXEL, uls[2])275         UNDERLINES.put(TextAttribute.UNDERLINE_LOW_TWO_PIXEL, uls[2]);
276 
277         uls[3] = new StandardUnderline(1, 1, new float[] { 1, 1 }, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DOTTED, uls[3])278         UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DOTTED, uls[3]);
279 
280         uls[4] = new IMGrayUnderline();
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_GRAY, uls[4])281         UNDERLINES.put(TextAttribute.UNDERLINE_LOW_GRAY, uls[4]);
282 
283         uls[5] = new StandardUnderline(1, 1, new float[] { 4, 4 }, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DASHED, uls[5])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      */
getUnderline(Object value)296     static Underline getUnderline(Object value) {
297 
298         if (value == null) {
299             return null;
300         }
301 
302         return UNDERLINES.get(value);
303     }
304 
getUnderline(int index)305     static Underline getUnderline(int index) {
306         return index < 0 ? null : UNDERLINE_LIST[index];
307     }
308 }
309