blob: e56f33778528f13ee4d00eb5e59ded402c461a1d [file] [log] [blame]
/*
* 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();
}
}
}