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 }