436 lines · 15.5 KB · Java
RawDownload
/* * 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));    }}