You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
15 KiB
437 lines
15 KiB
/*
|
|
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package javax.swing.text.html;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Component;
|
|
import java.awt.Graphics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.Insets;
|
|
import java.awt.Polygon;
|
|
import java.awt.Rectangle;
|
|
import java.awt.Shape;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import javax.swing.border.AbstractBorder;
|
|
import javax.swing.text.AttributeSet;
|
|
import javax.swing.text.View;
|
|
import javax.swing.text.html.CSS.Attribute;
|
|
import javax.swing.text.html.CSS.BorderStyle;
|
|
import javax.swing.text.html.CSS.BorderWidthValue;
|
|
import javax.swing.text.html.CSS.ColorValue;
|
|
import javax.swing.text.html.CSS.CssValue;
|
|
import javax.swing.text.html.CSS.LengthValue;
|
|
import javax.swing.text.html.CSS.Value;
|
|
|
|
/**
|
|
* CSS-style borders for HTML elements.
|
|
*
|
|
* @author Sergey Groznyh
|
|
*/
|
|
class CSSBorder extends AbstractBorder {
|
|
|
|
/** Indices for the attribute groups. */
|
|
final static int COLOR = 0, STYLE = 1, WIDTH = 2;
|
|
|
|
/** Indices for the box sides within the attribute group. */
|
|
final static int TOP = 0, RIGHT = 1, BOTTOM = 2, LEFT = 3;
|
|
|
|
/** The attribute groups. */
|
|
final static Attribute[][] ATTRIBUTES = {
|
|
{ Attribute.BORDER_TOP_COLOR, Attribute.BORDER_RIGHT_COLOR,
|
|
Attribute.BORDER_BOTTOM_COLOR, Attribute.BORDER_LEFT_COLOR, },
|
|
{ Attribute.BORDER_TOP_STYLE, Attribute.BORDER_RIGHT_STYLE,
|
|
Attribute.BORDER_BOTTOM_STYLE, Attribute.BORDER_LEFT_STYLE, },
|
|
{ Attribute.BORDER_TOP_WIDTH, Attribute.BORDER_RIGHT_WIDTH,
|
|
Attribute.BORDER_BOTTOM_WIDTH, Attribute.BORDER_LEFT_WIDTH, },
|
|
};
|
|
|
|
/** Parsers for the border properties. */
|
|
final static CssValue PARSERS[] = {
|
|
new ColorValue(), new BorderStyle(), new BorderWidthValue(null, 0),
|
|
};
|
|
|
|
/** Default values for the border properties. */
|
|
final static Object[] DEFAULTS = {
|
|
Attribute.BORDER_COLOR, // marker: value will be computed on request
|
|
PARSERS[1].parseCssValue(Attribute.BORDER_STYLE.getDefaultValue()),
|
|
PARSERS[2].parseCssValue(Attribute.BORDER_WIDTH.getDefaultValue()),
|
|
};
|
|
|
|
/** Attribute set containing border properties. */
|
|
final AttributeSet attrs;
|
|
|
|
/**
|
|
* Initialize the attribute set.
|
|
*/
|
|
CSSBorder(AttributeSet attrs) {
|
|
this.attrs = attrs;
|
|
}
|
|
|
|
/**
|
|
* Return the border color for the given side.
|
|
*/
|
|
private Color getBorderColor(int side) {
|
|
Object o = attrs.getAttribute(ATTRIBUTES[COLOR][side]);
|
|
ColorValue cv;
|
|
if (o instanceof ColorValue) {
|
|
cv = (ColorValue) o;
|
|
} else {
|
|
// Marker for the default value. Use 'color' property value as the
|
|
// computed value of the 'border-color' property (CSS2 8.5.2)
|
|
cv = (ColorValue) attrs.getAttribute(Attribute.COLOR);
|
|
if (cv == null) {
|
|
cv = (ColorValue) PARSERS[COLOR].parseCssValue(
|
|
Attribute.COLOR.getDefaultValue());
|
|
}
|
|
}
|
|
return cv.getValue();
|
|
}
|
|
|
|
/**
|
|
* Return the border width for the given side.
|
|
*/
|
|
private int getBorderWidth(int side) {
|
|
int width = 0;
|
|
BorderStyle bs = (BorderStyle) attrs.getAttribute(
|
|
ATTRIBUTES[STYLE][side]);
|
|
if ((bs != null) && (bs.getValue() != Value.NONE)) {
|
|
// The 'border-style' value of "none" forces the computed value
|
|
// of 'border-width' to be 0 (CSS2 8.5.3)
|
|
LengthValue bw = (LengthValue) attrs.getAttribute(
|
|
ATTRIBUTES[WIDTH][side]);
|
|
if (bw == null) {
|
|
bw = (LengthValue) DEFAULTS[WIDTH];
|
|
}
|
|
width = (int) bw.getValue(true);
|
|
}
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* Return an array of border widths in the TOP, RIGHT, BOTTOM, LEFT order.
|
|
*/
|
|
private int[] getWidths() {
|
|
int[] widths = new int[4];
|
|
for (int i = 0; i < widths.length; i++) {
|
|
widths[i] = getBorderWidth(i);
|
|
}
|
|
return widths;
|
|
}
|
|
|
|
/**
|
|
* Return the border style for the given side.
|
|
*/
|
|
private Value getBorderStyle(int side) {
|
|
BorderStyle style =
|
|
(BorderStyle) attrs.getAttribute(ATTRIBUTES[STYLE][side]);
|
|
if (style == null) {
|
|
style = (BorderStyle) DEFAULTS[STYLE];
|
|
}
|
|
return style.getValue();
|
|
}
|
|
|
|
/**
|
|
* Return border shape for {@code side} as if the border has zero interior
|
|
* length. Shape start is at (0,0); points are added clockwise.
|
|
*/
|
|
private Polygon getBorderShape(int side) {
|
|
Polygon shape = null;
|
|
int[] widths = getWidths();
|
|
if (widths[side] != 0) {
|
|
shape = new Polygon(new int[4], new int[4], 0);
|
|
shape.addPoint(0, 0);
|
|
shape.addPoint(-widths[(side + 3) % 4], -widths[side]);
|
|
shape.addPoint(widths[(side + 1) % 4], -widths[side]);
|
|
shape.addPoint(0, 0);
|
|
}
|
|
return shape;
|
|
}
|
|
|
|
/**
|
|
* Return the border painter appropriate for the given side.
|
|
*/
|
|
private BorderPainter getBorderPainter(int side) {
|
|
Value style = getBorderStyle(side);
|
|
return borderPainters.get(style);
|
|
}
|
|
|
|
/**
|
|
* Return the color with brightness adjusted by the specified factor.
|
|
*
|
|
* The factor values are between 0.0 (no change) and 1.0 (turn into white).
|
|
* Negative factor values decrease brigthness (ie, 1.0 turns into black).
|
|
*/
|
|
static Color getAdjustedColor(Color c, double factor) {
|
|
double f = 1 - Math.min(Math.abs(factor), 1);
|
|
double inc = (factor > 0 ? 255 * (1 - f) : 0);
|
|
return new Color((int) (c.getRed() * f + inc),
|
|
(int) (c.getGreen() * f + inc),
|
|
(int) (c.getBlue() * f + inc));
|
|
}
|
|
|
|
|
|
/* The javax.swing.border.Border methods. */
|
|
|
|
public Insets getBorderInsets(Component c, Insets insets) {
|
|
int[] widths = getWidths();
|
|
insets.set(widths[TOP], widths[LEFT], widths[BOTTOM], widths[RIGHT]);
|
|
return insets;
|
|
}
|
|
|
|
public void paintBorder(Component c, Graphics g,
|
|
int x, int y, int width, int height) {
|
|
if (!(g instanceof Graphics2D)) {
|
|
return;
|
|
}
|
|
|
|
Graphics2D g2 = (Graphics2D) g.create();
|
|
|
|
int[] widths = getWidths();
|
|
|
|
// Position and size of the border interior.
|
|
int intX = x + widths[LEFT];
|
|
int intY = y + widths[TOP];
|
|
int intWidth = width - (widths[RIGHT] + widths[LEFT]);
|
|
int intHeight = height - (widths[TOP] + widths[BOTTOM]);
|
|
|
|
// Coordinates of the interior corners, from NW clockwise.
|
|
int[][] intCorners = {
|
|
{ intX, intY },
|
|
{ intX + intWidth, intY },
|
|
{ intX + intWidth, intY + intHeight },
|
|
{ intX, intY + intHeight, },
|
|
};
|
|
|
|
// Draw the borders for all sides.
|
|
for (int i = 0; i < 4; i++) {
|
|
Value style = getBorderStyle(i);
|
|
Polygon shape = getBorderShape(i);
|
|
if ((style != Value.NONE) && (shape != null)) {
|
|
int sideLength = (i % 2 == 0 ? intWidth : intHeight);
|
|
|
|
// "stretch" the border shape by the interior area dimension
|
|
shape.xpoints[2] += sideLength;
|
|
shape.xpoints[3] += sideLength;
|
|
Color color = getBorderColor(i);
|
|
BorderPainter painter = getBorderPainter(i);
|
|
|
|
double angle = i * Math.PI / 2;
|
|
g2.setClip(g.getClip()); // Restore initial clip
|
|
g2.translate(intCorners[i][0], intCorners[i][1]);
|
|
g2.rotate(angle);
|
|
g2.clip(shape);
|
|
painter.paint(shape, g2, color, i);
|
|
g2.rotate(-angle);
|
|
g2.translate(-intCorners[i][0], -intCorners[i][1]);
|
|
}
|
|
}
|
|
g2.dispose();
|
|
}
|
|
|
|
|
|
/* Border painters. */
|
|
|
|
interface BorderPainter {
|
|
/**
|
|
* The painter should paint the border as if it were at the top and the
|
|
* coordinates of the NW corner of the interior area is (0, 0). The
|
|
* caller is responsible for the appropriate affine transformations.
|
|
*
|
|
* Clip is set by the caller to the exact border shape so it's safe to
|
|
* simply draw into the shape's bounding rectangle.
|
|
*/
|
|
void paint(Polygon shape, Graphics g, Color color, int side);
|
|
}
|
|
|
|
/**
|
|
* Painter for the "none" and "hidden" CSS border styles.
|
|
*/
|
|
static class NullPainter implements BorderPainter {
|
|
public void paint(Polygon shape, Graphics g, Color color, int side) {
|
|
// Do nothing.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Painter for the "solid" CSS border style.
|
|
*/
|
|
static class SolidPainter implements BorderPainter {
|
|
public void paint(Polygon shape, Graphics g, Color color, int side) {
|
|
g.setColor(color);
|
|
g.fillPolygon(shape);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Defines a method for painting strokes in the specified direction using
|
|
* the given length and color patterns.
|
|
*/
|
|
abstract static class StrokePainter implements BorderPainter {
|
|
/**
|
|
* Paint strokes repeatedly using the given length and color patterns.
|
|
*/
|
|
void paintStrokes(Rectangle r, Graphics g, int axis,
|
|
int[] lengthPattern, Color[] colorPattern) {
|
|
boolean xAxis = (axis == View.X_AXIS);
|
|
int start = 0;
|
|
int end = (xAxis ? r.width : r.height);
|
|
while (start < end) {
|
|
for (int i = 0; i < lengthPattern.length; i++) {
|
|
if (start >= end) {
|
|
break;
|
|
}
|
|
int length = lengthPattern[i];
|
|
Color c = colorPattern[i];
|
|
if (c != null) {
|
|
int x = r.x + (xAxis ? start : 0);
|
|
int y = r.y + (xAxis ? 0 : start);
|
|
int width = xAxis ? length : r.width;
|
|
int height = xAxis ? r.height : length;
|
|
g.setColor(c);
|
|
g.fillRect(x, y, width, height);
|
|
}
|
|
start += length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Painter for the "double" CSS border style.
|
|
*/
|
|
static class DoublePainter extends StrokePainter {
|
|
public void paint(Polygon shape, Graphics g, Color color, int side) {
|
|
Rectangle r = shape.getBounds();
|
|
int length = Math.max(r.height / 3, 1);
|
|
int[] lengthPattern = { length, length };
|
|
Color[] colorPattern = { color, null };
|
|
paintStrokes(r, g, View.Y_AXIS, lengthPattern, colorPattern);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Painter for the "dotted" and "dashed" CSS border styles.
|
|
*/
|
|
static class DottedDashedPainter extends StrokePainter {
|
|
final int factor;
|
|
|
|
DottedDashedPainter(int factor) {
|
|
this.factor = factor;
|
|
}
|
|
|
|
public void paint(Polygon shape, Graphics g, Color color, int side) {
|
|
Rectangle r = shape.getBounds();
|
|
int length = r.height * factor;
|
|
int[] lengthPattern = { length, length };
|
|
Color[] colorPattern = { color, null };
|
|
paintStrokes(r, g, View.X_AXIS, lengthPattern, colorPattern);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Painter that defines colors for "shadow" and "light" border sides.
|
|
*/
|
|
abstract static class ShadowLightPainter extends StrokePainter {
|
|
/**
|
|
* Return the "shadow" border side color.
|
|
*/
|
|
static Color getShadowColor(Color c) {
|
|
return CSSBorder.getAdjustedColor(c, -0.3);
|
|
}
|
|
|
|
/**
|
|
* Return the "light" border side color.
|
|
*/
|
|
static Color getLightColor(Color c) {
|
|
return CSSBorder.getAdjustedColor(c, 0.7);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Painter for the "groove" and "ridge" CSS border styles.
|
|
*/
|
|
static class GrooveRidgePainter extends ShadowLightPainter {
|
|
final Value type;
|
|
|
|
GrooveRidgePainter(Value type) {
|
|
this.type = type;
|
|
}
|
|
|
|
public void paint(Polygon shape, Graphics g, Color color, int side) {
|
|
Rectangle r = shape.getBounds();
|
|
int length = Math.max(r.height / 2, 1);
|
|
int[] lengthPattern = { length, length };
|
|
Color[] colorPattern =
|
|
((side + 1) % 4 < 2) == (type == Value.GROOVE) ?
|
|
new Color[] { getShadowColor(color), getLightColor(color) } :
|
|
new Color[] { getLightColor(color), getShadowColor(color) };
|
|
paintStrokes(r, g, View.Y_AXIS, lengthPattern, colorPattern);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Painter for the "inset" and "outset" CSS border styles.
|
|
*/
|
|
static class InsetOutsetPainter extends ShadowLightPainter {
|
|
Value type;
|
|
|
|
InsetOutsetPainter(Value type) {
|
|
this.type = type;
|
|
}
|
|
|
|
public void paint(Polygon shape, Graphics g, Color color, int side) {
|
|
g.setColor(((side + 1) % 4 < 2) == (type == Value.INSET) ?
|
|
getShadowColor(color) : getLightColor(color));
|
|
g.fillPolygon(shape);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the specified painter to the painters map.
|
|
*/
|
|
static void registerBorderPainter(Value style, BorderPainter painter) {
|
|
borderPainters.put(style, painter);
|
|
}
|
|
|
|
/** Map the border style values to the border painter objects. */
|
|
static Map<Value, BorderPainter> borderPainters =
|
|
new HashMap<Value, BorderPainter>();
|
|
|
|
/* Initialize the border painters map with the pre-defined values. */
|
|
static {
|
|
registerBorderPainter(Value.NONE, new NullPainter());
|
|
registerBorderPainter(Value.HIDDEN, new NullPainter());
|
|
registerBorderPainter(Value.SOLID, new SolidPainter());
|
|
registerBorderPainter(Value.DOUBLE, new DoublePainter());
|
|
registerBorderPainter(Value.DOTTED, new DottedDashedPainter(1));
|
|
registerBorderPainter(Value.DASHED, new DottedDashedPainter(3));
|
|
registerBorderPainter(Value.GROOVE, new GrooveRidgePainter(Value.GROOVE));
|
|
registerBorderPainter(Value.RIDGE, new GrooveRidgePainter(Value.RIDGE));
|
|
registerBorderPainter(Value.INSET, new InsetOutsetPainter(Value.INSET));
|
|
registerBorderPainter(Value.OUTSET, new InsetOutsetPainter(Value.OUTSET));
|
|
}
|
|
}
|