| /* |
| * 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. 1999-2003, All Rights Reserved |
| * |
| */ |
| |
| package sun.font; |
| |
| import java.util.Map; |
| |
| import java.awt.BasicStroke; |
| import java.awt.Color; |
| import java.awt.Graphics2D; |
| import java.awt.Paint; |
| import java.awt.RenderingHints; |
| import java.awt.Shape; |
| import java.awt.Stroke; |
| |
| import java.awt.font.TextAttribute; |
| |
| import java.awt.geom.Area; |
| import java.awt.geom.Line2D; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.geom.GeneralPath; |
| |
| import static sun.font.AttributeValues.*; |
| import static sun.font.EAttribute.*; |
| |
| /** |
| * This class handles underlining, strikethrough, and foreground and |
| * background styles on text. Clients simply acquire instances |
| * of this class and hand them off to ExtendedTextLabels or GraphicComponents. |
| */ |
| public class Decoration { |
| |
| /** |
| * This interface is implemented by clients that use Decoration. |
| * Unfortunately, interface methods have to public; ideally these |
| * would be package-private. |
| */ |
| public interface Label { |
| CoreMetrics getCoreMetrics(); |
| Rectangle2D getLogicalBounds(); |
| |
| void handleDraw(Graphics2D g2d, float x, float y); |
| Rectangle2D handleGetCharVisualBounds(int index); |
| Rectangle2D handleGetVisualBounds(); |
| Shape handleGetOutline(float x, float y); |
| } |
| |
| private Decoration() { |
| } |
| |
| /** |
| * Return a Decoration which does nothing. |
| */ |
| public static Decoration getPlainDecoration() { |
| |
| return PLAIN; |
| } |
| |
| private static final int VALUES_MASK = |
| AttributeValues.getMask(EFOREGROUND, EBACKGROUND, ESWAP_COLORS, |
| ESTRIKETHROUGH, EUNDERLINE, EINPUT_METHOD_HIGHLIGHT, |
| EINPUT_METHOD_UNDERLINE); |
| |
| public static Decoration getDecoration(AttributeValues values) { |
| if (values == null || !values.anyDefined(VALUES_MASK)) { |
| return PLAIN; |
| } |
| |
| values = values.applyIMHighlight(); |
| |
| return new DecorationImpl(values.getForeground(), |
| values.getBackground(), |
| values.getSwapColors(), |
| values.getStrikethrough(), |
| Underline.getUnderline(values.getUnderline()), |
| Underline.getUnderline(values.getInputMethodUnderline())); |
| } |
| |
| /** |
| * Return a Decoration appropriate for the the given Map. |
| * @param attributes the Map used to determine the Decoration |
| */ |
| public static Decoration getDecoration(Map attributes) { |
| if (attributes == null) { |
| return PLAIN; |
| } |
| return getDecoration(AttributeValues.fromMap(attributes)); |
| } |
| |
| public void drawTextAndDecorations(Label label, |
| Graphics2D g2d, |
| float x, |
| float y) { |
| |
| label.handleDraw(g2d, x, y); |
| } |
| |
| public Rectangle2D getVisualBounds(Label label) { |
| |
| return label.handleGetVisualBounds(); |
| } |
| |
| public Rectangle2D getCharVisualBounds(Label label, int index) { |
| |
| return label.handleGetCharVisualBounds(index); |
| } |
| |
| Shape getOutline(Label label, |
| float x, |
| float y) { |
| |
| return label.handleGetOutline(x, y); |
| } |
| |
| private static final Decoration PLAIN = new Decoration(); |
| |
| private static final class DecorationImpl extends Decoration { |
| |
| private Paint fgPaint = null; |
| private Paint bgPaint = null; |
| private boolean swapColors = false; |
| private boolean strikethrough = false; |
| private Underline stdUnderline = null; // underline from TextAttribute.UNDERLINE_ON |
| private Underline imUnderline = null; // input method underline |
| |
| DecorationImpl(Paint foreground, |
| Paint background, |
| boolean swapColors, |
| boolean strikethrough, |
| Underline stdUnderline, |
| Underline imUnderline) { |
| |
| fgPaint = (Paint) foreground; |
| bgPaint = (Paint) background; |
| |
| this.swapColors = swapColors; |
| this.strikethrough = strikethrough; |
| |
| this.stdUnderline = stdUnderline; |
| this.imUnderline = imUnderline; |
| } |
| |
| private static boolean areEqual(Object lhs, Object rhs) { |
| |
| if (lhs == null) { |
| return rhs == null; |
| } |
| else { |
| return lhs.equals(rhs); |
| } |
| } |
| |
| public boolean equals(Object rhs) { |
| |
| if (rhs == this) { |
| return true; |
| } |
| if (rhs == null) { |
| return false; |
| } |
| |
| DecorationImpl other = null; |
| try { |
| other = (DecorationImpl) rhs; |
| } |
| catch(ClassCastException e) { |
| return false; |
| } |
| |
| if (!(swapColors == other.swapColors && |
| strikethrough == other.strikethrough)) { |
| return false; |
| } |
| |
| if (!areEqual(stdUnderline, other.stdUnderline)) { |
| return false; |
| } |
| if (!areEqual(fgPaint, other.fgPaint)) { |
| return false; |
| } |
| if (!areEqual(bgPaint, other.bgPaint)) { |
| return false; |
| } |
| return areEqual(imUnderline, other.imUnderline); |
| } |
| |
| public int hashCode() { |
| |
| int hc = 1; |
| if (strikethrough) { |
| hc |= 2; |
| } |
| if (swapColors) { |
| hc |= 4; |
| } |
| if (stdUnderline != null) { |
| hc += stdUnderline.hashCode(); |
| } |
| return hc; |
| } |
| |
| /** |
| * Return the bottom of the Rectangle which encloses pixels |
| * drawn by underlines. |
| */ |
| private float getUnderlineMaxY(CoreMetrics cm) { |
| |
| float maxY = 0; |
| if (stdUnderline != null) { |
| |
| float ulBottom = cm.underlineOffset; |
| ulBottom += stdUnderline.getLowerDrawLimit(cm.underlineThickness); |
| maxY = Math.max(maxY, ulBottom); |
| } |
| |
| if (imUnderline != null) { |
| |
| float ulBottom = cm.underlineOffset; |
| ulBottom += imUnderline.getLowerDrawLimit(cm.underlineThickness); |
| maxY = Math.max(maxY, ulBottom); |
| } |
| |
| return maxY; |
| } |
| |
| private void drawTextAndEmbellishments(Label label, |
| Graphics2D g2d, |
| float x, |
| float y) { |
| |
| label.handleDraw(g2d, x, y); |
| |
| if (!strikethrough && stdUnderline == null && imUnderline == null) { |
| return; |
| } |
| |
| float x1 = x; |
| float x2 = x1 + (float)label.getLogicalBounds().getWidth(); |
| |
| CoreMetrics cm = label.getCoreMetrics(); |
| if (strikethrough) { |
| Stroke savedStroke = g2d.getStroke(); |
| g2d.setStroke(new BasicStroke(cm.strikethroughThickness, |
| BasicStroke.CAP_BUTT, |
| BasicStroke.JOIN_MITER)); |
| float strikeY = y + cm.strikethroughOffset; |
| g2d.draw(new Line2D.Float(x1, strikeY, x2, strikeY)); |
| g2d.setStroke(savedStroke); |
| } |
| |
| float ulOffset = cm.underlineOffset; |
| float ulThickness = cm.underlineThickness; |
| |
| if (stdUnderline != null) { |
| stdUnderline.drawUnderline(g2d, ulThickness, x1, x2, y + ulOffset); |
| } |
| |
| if (imUnderline != null) { |
| imUnderline.drawUnderline(g2d, ulThickness, x1, x2, y + ulOffset); |
| } |
| } |
| |
| public void drawTextAndDecorations(Label label, |
| Graphics2D g2d, |
| float x, |
| float y) { |
| |
| if (fgPaint == null && bgPaint == null && swapColors == false) { |
| drawTextAndEmbellishments(label, g2d, x, y); |
| } |
| else { |
| Paint savedPaint = g2d.getPaint(); |
| Paint foreground, background; |
| |
| if (swapColors) { |
| background = fgPaint==null? savedPaint : fgPaint; |
| if (bgPaint == null) { |
| if (background instanceof Color) { |
| Color bg = (Color)background; |
| // 30/59/11 is standard weights, tweaked a bit |
| int brightness = 33 * bg.getRed() |
| + 53 * bg.getGreen() |
| + 14 * bg.getBlue(); |
| foreground = brightness > 18500 ? Color.BLACK : Color.WHITE; |
| } else { |
| foreground = Color.WHITE; |
| } |
| } else { |
| foreground = bgPaint; |
| } |
| } |
| else { |
| foreground = fgPaint==null? savedPaint : fgPaint; |
| background = bgPaint; |
| } |
| |
| if (background != null) { |
| |
| Rectangle2D bgArea = label.getLogicalBounds(); |
| bgArea = new Rectangle2D.Float(x + (float)bgArea.getX(), |
| y + (float)bgArea.getY(), |
| (float)bgArea.getWidth(), |
| (float)bgArea.getHeight()); |
| |
| g2d.setPaint(background); |
| g2d.fill(bgArea); |
| } |
| |
| g2d.setPaint(foreground); |
| drawTextAndEmbellishments(label, g2d, x, y); |
| g2d.setPaint(savedPaint); |
| } |
| } |
| |
| public Rectangle2D getVisualBounds(Label label) { |
| |
| Rectangle2D visBounds = label.handleGetVisualBounds(); |
| |
| if (swapColors || bgPaint != null || strikethrough |
| || stdUnderline != null || imUnderline != null) { |
| |
| float minX = 0; |
| Rectangle2D lb = label.getLogicalBounds(); |
| |
| float minY = 0, maxY = 0; |
| |
| if (swapColors || bgPaint != null) { |
| |
| minY = (float)lb.getY(); |
| maxY = minY + (float)lb.getHeight(); |
| } |
| |
| maxY = Math.max(maxY, getUnderlineMaxY(label.getCoreMetrics())); |
| |
| Rectangle2D ab = new Rectangle2D.Float(minX, minY, (float)lb.getWidth(), maxY-minY); |
| visBounds.add(ab); |
| } |
| |
| return visBounds; |
| } |
| |
| Shape getOutline(Label label, |
| float x, |
| float y) { |
| |
| if (!strikethrough && stdUnderline == null && imUnderline == null) { |
| return label.handleGetOutline(x, y); |
| } |
| |
| CoreMetrics cm = label.getCoreMetrics(); |
| |
| // NOTE: The performace of the following code may |
| // be very poor. |
| float ulThickness = cm.underlineThickness; |
| float ulOffset = cm.underlineOffset; |
| |
| Rectangle2D lb = label.getLogicalBounds(); |
| float x1 = x; |
| float x2 = x1 + (float)lb.getWidth(); |
| |
| Area area = null; |
| |
| if (stdUnderline != null) { |
| Shape ul = stdUnderline.getUnderlineShape(ulThickness, |
| x1, x2, y+ulOffset); |
| area = new Area(ul); |
| } |
| |
| if (strikethrough) { |
| Stroke stStroke = new BasicStroke(cm.strikethroughThickness, |
| BasicStroke.CAP_BUTT, |
| BasicStroke.JOIN_MITER); |
| float shiftY = y + cm.strikethroughOffset; |
| Line2D line = new Line2D.Float(x1, shiftY, x2, shiftY); |
| Area slArea = new Area(stStroke.createStrokedShape(line)); |
| if(area == null) { |
| area = slArea; |
| } else { |
| area.add(slArea); |
| } |
| } |
| |
| if (imUnderline != null) { |
| Shape ul = imUnderline.getUnderlineShape(ulThickness, |
| x1, x2, y+ulOffset); |
| Area ulArea = new Area(ul); |
| if (area == null) { |
| area = ulArea; |
| } |
| else { |
| area.add(ulArea); |
| } |
| } |
| |
| // area won't be null here, since at least one underline exists. |
| area.add(new Area(label.handleGetOutline(x, y))); |
| |
| return new GeneralPath(area); |
| } |
| |
| |
| public String toString() { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(super.toString()); |
| buf.append("["); |
| if (fgPaint != null) buf.append("fgPaint: " + fgPaint); |
| if (bgPaint != null) buf.append(" bgPaint: " + bgPaint); |
| if (swapColors) buf.append(" swapColors: true"); |
| if (strikethrough) buf.append(" strikethrough: true"); |
| if (stdUnderline != null) buf.append(" stdUnderline: " + stdUnderline); |
| if (imUnderline != null) buf.append(" imUnderline: " + imUnderline); |
| buf.append("]"); |
| return buf.toString(); |
| } |
| } |
| } |