| /* |
| * Copyright (c) 2007, 2011, 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. |
| */ |
| 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)); |
| } |
| } |