| /* |
| * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| /* |
| * |
| * (C) Copyright IBM Corp. 1998, All Rights Reserved |
| */ |
| |
| package sun.font; |
| |
| import java.awt.BasicStroke; |
| import java.awt.Graphics2D; |
| import java.awt.Shape; |
| import java.awt.Stroke; |
| |
| import java.awt.geom.GeneralPath; |
| import java.awt.geom.Line2D; |
| |
| import java.awt.font.TextAttribute; |
| |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * This class provides drawing and bounds-measurement of |
| * underlines. Additionally, it has a factory method for |
| * obtaining underlines from values of underline attributes. |
| */ |
| |
| abstract class Underline { |
| |
| /** |
| * Draws the underline into g2d. The thickness should be obtained |
| * from a LineMetrics object. Note that some underlines ignore the |
| * thickness parameter. |
| * The underline is drawn from (x1, y) to (x2, y). |
| */ |
| abstract void drawUnderline(Graphics2D g2d, |
| float thickness, |
| float x1, |
| float x2, |
| float y); |
| |
| /** |
| * Returns the bottom of the bounding rectangle for this underline. |
| */ |
| abstract float getLowerDrawLimit(float thickness); |
| |
| /** |
| * Returns a Shape representing the underline. The thickness should be obtained |
| * from a LineMetrics object. Note that some underlines ignore the |
| * thickness parameter. |
| */ |
| abstract Shape getUnderlineShape(float thickness, |
| float x1, |
| float x2, |
| float y); |
| |
| // Implementation of underline for standard and Input Method underlines. |
| // These classes are private. |
| |
| // IM Underlines ignore thickness param, and instead use |
| // DEFAULT_THICKNESS |
| private static final float DEFAULT_THICKNESS = 1.0f; |
| |
| // StandardUnderline's constructor takes a boolean param indicating |
| // whether to override the default thickness. These values clarify |
| // the semantics of the parameter. |
| private static final boolean USE_THICKNESS = true; |
| private static final boolean IGNORE_THICKNESS = false; |
| |
| // Implementation of standard underline and all input method underlines |
| // except UNDERLINE_LOW_GRAY. |
| private static final class StandardUnderline extends Underline { |
| |
| // the amount by which to move the underline |
| private float shift; |
| |
| // the actual line thickness is this value times |
| // the requested thickness |
| private float thicknessMultiplier; |
| |
| // if non-null, underline is drawn with a BasicStroke |
| // with this dash pattern |
| private float[] dashPattern; |
| |
| // if false, all underlines are DEFAULT_THICKNESS thick |
| // if true, use thickness param |
| private boolean useThickness; |
| |
| // cached BasicStroke |
| private BasicStroke cachedStroke; |
| |
| StandardUnderline(float shift, |
| float thicknessMultiplier, |
| float[] dashPattern, |
| boolean useThickness) { |
| |
| this.shift = shift; |
| this.thicknessMultiplier = thicknessMultiplier; |
| this.dashPattern = dashPattern; |
| this.useThickness = useThickness; |
| this.cachedStroke = null; |
| } |
| |
| private BasicStroke createStroke(float lineThickness) { |
| |
| if (dashPattern == null) { |
| return new BasicStroke(lineThickness, |
| BasicStroke.CAP_BUTT, |
| BasicStroke.JOIN_MITER); |
| } |
| else { |
| return new BasicStroke(lineThickness, |
| BasicStroke.CAP_BUTT, |
| BasicStroke.JOIN_MITER, |
| 10.0f, |
| dashPattern, |
| 0); |
| } |
| } |
| |
| private float getLineThickness(float thickness) { |
| |
| if (useThickness) { |
| return thickness * thicknessMultiplier; |
| } |
| else { |
| return DEFAULT_THICKNESS * thicknessMultiplier; |
| } |
| } |
| |
| private Stroke getStroke(float thickness) { |
| |
| float lineThickness = getLineThickness(thickness); |
| BasicStroke stroke = cachedStroke; |
| if (stroke == null || |
| stroke.getLineWidth() != lineThickness) { |
| |
| stroke = createStroke(lineThickness); |
| cachedStroke = stroke; |
| } |
| |
| return stroke; |
| } |
| |
| void drawUnderline(Graphics2D g2d, |
| float thickness, |
| float x1, |
| float x2, |
| float y) { |
| |
| |
| Stroke saveStroke = g2d.getStroke(); |
| g2d.setStroke(getStroke(thickness)); |
| g2d.draw(new Line2D.Float(x1, y + shift, x2, y + shift)); |
| g2d.setStroke(saveStroke); |
| } |
| |
| float getLowerDrawLimit(float thickness) { |
| |
| return shift + getLineThickness(thickness); |
| } |
| |
| Shape getUnderlineShape(float thickness, |
| float x1, |
| float x2, |
| float y) { |
| |
| Stroke ulStroke = getStroke(thickness); |
| Line2D line = new Line2D.Float(x1, y + shift, x2, y + shift); |
| return ulStroke.createStrokedShape(line); |
| } |
| } |
| |
| // Implementation of UNDERLINE_LOW_GRAY. |
| private static class IMGrayUnderline extends Underline { |
| |
| private BasicStroke stroke; |
| |
| IMGrayUnderline() { |
| stroke = new BasicStroke(DEFAULT_THICKNESS, |
| BasicStroke.CAP_BUTT, |
| BasicStroke.JOIN_MITER, |
| 10.0f, |
| new float[] {1, 1}, |
| 0); |
| } |
| |
| void drawUnderline(Graphics2D g2d, |
| float thickness, |
| float x1, |
| float x2, |
| float y) { |
| |
| Stroke saveStroke = g2d.getStroke(); |
| g2d.setStroke(stroke); |
| |
| Line2D.Float drawLine = new Line2D.Float(x1, y, x2, y); |
| g2d.draw(drawLine); |
| |
| drawLine.y1 += DEFAULT_THICKNESS; |
| drawLine.y2 += DEFAULT_THICKNESS; |
| drawLine.x1 += DEFAULT_THICKNESS; |
| |
| g2d.draw(drawLine); |
| |
| g2d.setStroke(saveStroke); |
| } |
| |
| float getLowerDrawLimit(float thickness) { |
| |
| return DEFAULT_THICKNESS * 2; |
| } |
| |
| Shape getUnderlineShape(float thickness, |
| float x1, |
| float x2, |
| float y) { |
| |
| GeneralPath gp = new GeneralPath(); |
| |
| Line2D.Float line = new Line2D.Float(x1, y, x2, y); |
| gp.append(stroke.createStrokedShape(line), false); |
| |
| line.y1 += DEFAULT_THICKNESS; |
| line.y2 += DEFAULT_THICKNESS; |
| line.x1 += DEFAULT_THICKNESS; |
| |
| gp.append(stroke.createStrokedShape(line), false); |
| |
| return gp; |
| } |
| } |
| |
| // Keep a map of underlines, one for each type |
| // of underline. The Underline objects are Flyweights |
| // (shared across multiple clients), so they should be immutable. |
| // If this implementation changes then clone underline |
| // instances in getUnderline before returning them. |
| private static final ConcurrentHashMap<Object, Underline> |
| UNDERLINES = new ConcurrentHashMap<Object, Underline>(6); |
| private static final Underline[] UNDERLINE_LIST; |
| |
| static { |
| Underline[] uls = new Underline[6]; |
| |
| uls[0] = new StandardUnderline(0, 1, null, USE_THICKNESS); |
| UNDERLINES.put(TextAttribute.UNDERLINE_ON, uls[0]); |
| |
| uls[1] = new StandardUnderline(1, 1, null, IGNORE_THICKNESS); |
| UNDERLINES.put(TextAttribute.UNDERLINE_LOW_ONE_PIXEL, uls[1]); |
| |
| uls[2] = new StandardUnderline(1, 2, null, IGNORE_THICKNESS); |
| UNDERLINES.put(TextAttribute.UNDERLINE_LOW_TWO_PIXEL, uls[2]); |
| |
| uls[3] = new StandardUnderline(1, 1, new float[] { 1, 1 }, IGNORE_THICKNESS); |
| UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DOTTED, uls[3]); |
| |
| uls[4] = new IMGrayUnderline(); |
| UNDERLINES.put(TextAttribute.UNDERLINE_LOW_GRAY, uls[4]); |
| |
| uls[5] = new StandardUnderline(1, 1, new float[] { 4, 4 }, IGNORE_THICKNESS); |
| UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DASHED, uls[5]); |
| |
| UNDERLINE_LIST = uls; |
| } |
| |
| /** |
| * Return the Underline for the given value of |
| * TextAttribute.INPUT_METHOD_UNDERLINE or |
| * TextAttribute.UNDERLINE. |
| * If value is not an input method underline value or |
| * TextAttribute.UNDERLINE_ON, null is returned. |
| */ |
| static Underline getUnderline(Object value) { |
| |
| if (value == null) { |
| return null; |
| } |
| |
| return UNDERLINES.get(value); |
| } |
| |
| static Underline getUnderline(int index) { |
| return index < 0 ? null : UNDERLINE_LIST[index]; |
| } |
| } |