jdk/src/share/classes/javax/swing/text/html/CSSBorder.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/text/html/CSSBorder.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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) {
+        assert (g instanceof Graphics2D) : "need Graphics2D instanse";
+        Graphics2D g2 = (Graphics2D) g;
+        Color savedColor = g2.getColor();
+        Shape savedClip = g2.getClip();
+
+        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.translate(intCorners[i][0], intCorners[i][1]);
+                g2.rotate(angle);
+                g2.setClip(shape);
+                painter.paint(shape, g, color, i);
+                g2.rotate(-angle);
+                g2.translate(-intCorners[i][0], -intCorners[i][1]);
+            }
+        }
+        g2.setColor(savedColor);
+        g2.setClip(savedClip);
+    }
+
+
+    /* 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));
+    }
+}