1/*
2 * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.text.html;
26
27import java.awt.Color;
28import java.awt.Component;
29import java.awt.Graphics;
30import java.awt.Graphics2D;
31import java.awt.Insets;
32import java.awt.Polygon;
33import java.awt.Rectangle;
34import java.awt.Shape;
35import java.util.HashMap;
36import java.util.Map;
37import javax.swing.border.AbstractBorder;
38import javax.swing.text.AttributeSet;
39import javax.swing.text.View;
40import javax.swing.text.html.CSS.Attribute;
41import javax.swing.text.html.CSS.BorderStyle;
42import javax.swing.text.html.CSS.BorderWidthValue;
43import javax.swing.text.html.CSS.ColorValue;
44import javax.swing.text.html.CSS.CssValue;
45import javax.swing.text.html.CSS.LengthValue;
46import javax.swing.text.html.CSS.Value;
47
48/**
49 * CSS-style borders for HTML elements.
50 *
51 * @author Sergey Groznyh
52 */
53@SuppressWarnings("serial") // Superclass is not serializable across versions
54class CSSBorder extends AbstractBorder {
55
56    /** Indices for the attribute groups.  */
57    static final int COLOR = 0, STYLE = 1, WIDTH = 2;
58
59    /** Indices for the box sides within the attribute group.  */
60    static final int TOP = 0, RIGHT = 1, BOTTOM = 2, LEFT = 3;
61
62    /** The attribute groups.  */
63    static final Attribute[][] ATTRIBUTES = {
64        { Attribute.BORDER_TOP_COLOR, Attribute.BORDER_RIGHT_COLOR,
65          Attribute.BORDER_BOTTOM_COLOR, Attribute.BORDER_LEFT_COLOR, },
66        { Attribute.BORDER_TOP_STYLE, Attribute.BORDER_RIGHT_STYLE,
67          Attribute.BORDER_BOTTOM_STYLE, Attribute.BORDER_LEFT_STYLE, },
68        { Attribute.BORDER_TOP_WIDTH, Attribute.BORDER_RIGHT_WIDTH,
69          Attribute.BORDER_BOTTOM_WIDTH, Attribute.BORDER_LEFT_WIDTH, },
70    };
71
72    /** Parsers for the border properties.  */
73    static final CssValue PARSERS[] = {
74        new ColorValue(), new BorderStyle(), new BorderWidthValue(null, 0),
75    };
76
77    /** Default values for the border properties.  */
78    static final Object[] DEFAULTS = {
79        Attribute.BORDER_COLOR, // marker: value will be computed on request
80        PARSERS[1].parseCssValue(Attribute.BORDER_STYLE.getDefaultValue()),
81        PARSERS[2].parseCssValue(Attribute.BORDER_WIDTH.getDefaultValue()),
82    };
83
84    /** Attribute set containing border properties.  */
85    final AttributeSet attrs;
86
87    /**
88     * Initialize the attribute set.
89     */
90    CSSBorder(AttributeSet attrs) {
91        this.attrs = attrs;
92    }
93
94    /**
95     * Return the border color for the given side.
96     */
97    private Color getBorderColor(int side) {
98        Object o = attrs.getAttribute(ATTRIBUTES[COLOR][side]);
99        ColorValue cv;
100        if (o instanceof ColorValue) {
101            cv = (ColorValue) o;
102        } else {
103            // Marker for the default value.  Use 'color' property value as the
104            // computed value of the 'border-color' property (CSS2 8.5.2)
105            cv = (ColorValue) attrs.getAttribute(Attribute.COLOR);
106            if (cv == null) {
107                cv = (ColorValue) PARSERS[COLOR].parseCssValue(
108                                            Attribute.COLOR.getDefaultValue());
109            }
110        }
111        return cv.getValue();
112    }
113
114    /**
115     * Return the border width for the given side.
116     */
117    private int getBorderWidth(int side) {
118        int width = 0;
119        BorderStyle bs = (BorderStyle) attrs.getAttribute(
120                                                    ATTRIBUTES[STYLE][side]);
121        if ((bs != null) && (bs.getValue() != Value.NONE)) {
122            // The 'border-style' value of "none" forces the computed value
123            // of 'border-width' to be 0 (CSS2 8.5.3)
124            LengthValue bw = (LengthValue) attrs.getAttribute(
125                                                    ATTRIBUTES[WIDTH][side]);
126            if (bw == null) {
127                bw = (LengthValue) DEFAULTS[WIDTH];
128            }
129            width = (int) bw.getValue(true);
130        }
131        return width;
132    }
133
134    /**
135     * Return an array of border widths in the TOP, RIGHT, BOTTOM, LEFT order.
136     */
137    private int[] getWidths() {
138        int[] widths = new int[4];
139        for (int i = 0; i < widths.length; i++) {
140            widths[i] = getBorderWidth(i);
141        }
142        return widths;
143    }
144
145    /**
146     * Return the border style for the given side.
147     */
148    private Value getBorderStyle(int side) {
149        BorderStyle style =
150                    (BorderStyle) attrs.getAttribute(ATTRIBUTES[STYLE][side]);
151        if (style == null) {
152            style = (BorderStyle) DEFAULTS[STYLE];
153        }
154        return style.getValue();
155    }
156
157    /**
158     * Return border shape for {@code side} as if the border has zero interior
159     * length.  Shape start is at (0,0); points are added clockwise.
160     */
161    private Polygon getBorderShape(int side) {
162        Polygon shape = null;
163        int[] widths = getWidths();
164        if (widths[side] != 0) {
165            shape = new Polygon(new int[4], new int[4], 0);
166            shape.addPoint(0, 0);
167            shape.addPoint(-widths[(side + 3) % 4], -widths[side]);
168            shape.addPoint(widths[(side + 1) % 4], -widths[side]);
169            shape.addPoint(0, 0);
170        }
171        return shape;
172    }
173
174    /**
175     * Return the border painter appropriate for the given side.
176     */
177    private BorderPainter getBorderPainter(int side) {
178        Value style = getBorderStyle(side);
179        return borderPainters.get(style);
180    }
181
182    /**
183     * Return the color with brightness adjusted by the specified factor.
184     *
185     * The factor values are between 0.0 (no change) and 1.0 (turn into white).
186     * Negative factor values decrease brigthness (ie, 1.0 turns into black).
187     */
188    static Color getAdjustedColor(Color c, double factor) {
189        double f = 1 - Math.min(Math.abs(factor), 1);
190        double inc = (factor > 0 ? 255 * (1 - f) : 0);
191        return new Color((int) (c.getRed() * f + inc),
192                         (int) (c.getGreen() * f + inc),
193                         (int) (c.getBlue() * f + inc));
194    }
195
196
197    /* The javax.swing.border.Border methods.  */
198
199    public Insets getBorderInsets(Component c, Insets insets) {
200        int[] widths = getWidths();
201        insets.set(widths[TOP], widths[LEFT], widths[BOTTOM], widths[RIGHT]);
202        return insets;
203    }
204
205    public void paintBorder(Component c, Graphics g,
206                                        int x, int y, int width, int height) {
207        if (!(g instanceof Graphics2D)) {
208            return;
209        }
210
211        Graphics2D g2 = (Graphics2D) g.create();
212
213        int[] widths = getWidths();
214
215        // Position and size of the border interior.
216        int intX = x + widths[LEFT];
217        int intY = y + widths[TOP];
218        int intWidth = width - (widths[RIGHT] + widths[LEFT]);
219        int intHeight = height - (widths[TOP] + widths[BOTTOM]);
220
221        // Coordinates of the interior corners, from NW clockwise.
222        int[][] intCorners = {
223            { intX, intY },
224            { intX + intWidth, intY },
225            { intX + intWidth, intY + intHeight },
226            { intX, intY + intHeight, },
227        };
228
229        // Draw the borders for all sides.
230        for (int i = 0; i < 4; i++) {
231            Value style = getBorderStyle(i);
232            Polygon shape = getBorderShape(i);
233            if ((style != Value.NONE) && (shape != null)) {
234                int sideLength = (i % 2 == 0 ? intWidth : intHeight);
235
236                // "stretch" the border shape by the interior area dimension
237                shape.xpoints[2] += sideLength;
238                shape.xpoints[3] += sideLength;
239                Color color = getBorderColor(i);
240                BorderPainter painter = getBorderPainter(i);
241
242                double angle = i * Math.PI / 2;
243                g2.setClip(g.getClip()); // Restore initial clip
244                g2.translate(intCorners[i][0], intCorners[i][1]);
245                g2.rotate(angle);
246                g2.clip(shape);
247                painter.paint(shape, g2, color, i);
248                g2.rotate(-angle);
249                g2.translate(-intCorners[i][0], -intCorners[i][1]);
250            }
251        }
252        g2.dispose();
253    }
254
255
256    /* Border painters.  */
257
258    interface BorderPainter {
259        /**
260         * The painter should paint the border as if it were at the top and the
261         * coordinates of the NW corner of the interior area is (0, 0).  The
262         * caller is responsible for the appropriate affine transformations.
263         *
264         * Clip is set by the caller to the exact border shape so it's safe to
265         * simply draw into the shape's bounding rectangle.
266         */
267        void paint(Polygon shape, Graphics g, Color color, int side);
268    }
269
270    /**
271     * Painter for the "none" and "hidden" CSS border styles.
272     */
273    static class NullPainter implements BorderPainter {
274        public void paint(Polygon shape, Graphics g, Color color, int side) {
275            // Do nothing.
276        }
277    }
278
279    /**
280     * Painter for the "solid" CSS border style.
281     */
282    static class SolidPainter implements BorderPainter {
283        public void paint(Polygon shape, Graphics g, Color color, int side) {
284            g.setColor(color);
285            g.fillPolygon(shape);
286        }
287    }
288
289    /**
290     * Defines a method for painting strokes in the specified direction using
291     * the given length and color patterns.
292     */
293    abstract static class StrokePainter implements BorderPainter {
294        /**
295         * Paint strokes repeatedly using the given length and color patterns.
296         */
297        void paintStrokes(Rectangle r, Graphics g, int axis,
298                                int[] lengthPattern, Color[] colorPattern) {
299            boolean xAxis = (axis == View.X_AXIS);
300            int start = 0;
301            int end = (xAxis ? r.width : r.height);
302            while (start < end) {
303                for (int i = 0; i < lengthPattern.length; i++) {
304                    if (start >= end) {
305                        break;
306                    }
307                    int length = lengthPattern[i];
308                    Color c = colorPattern[i];
309                    if (c != null) {
310                        int x = r.x + (xAxis ? start : 0);
311                        int y = r.y + (xAxis ? 0 : start);
312                        int width = xAxis ? length : r.width;
313                        int height = xAxis ? r.height : length;
314                        g.setColor(c);
315                        g.fillRect(x, y, width, height);
316                    }
317                    start += length;
318                }
319            }
320        }
321    }
322
323    /**
324     * Painter for the "double" CSS border style.
325     */
326    static class DoublePainter extends StrokePainter {
327        public void paint(Polygon shape, Graphics g, Color color, int side) {
328            Rectangle r = shape.getBounds();
329            int length = Math.max(r.height / 3, 1);
330            int[] lengthPattern = { length, length };
331            Color[] colorPattern = { color, null };
332            paintStrokes(r, g, View.Y_AXIS, lengthPattern, colorPattern);
333        }
334    }
335
336    /**
337     * Painter for the "dotted" and "dashed" CSS border styles.
338     */
339    static class DottedDashedPainter extends StrokePainter {
340        final int factor;
341
342        DottedDashedPainter(int factor) {
343            this.factor = factor;
344        }
345
346        public void paint(Polygon shape, Graphics g, Color color, int side) {
347            Rectangle r = shape.getBounds();
348            int length = r.height * factor;
349            int[] lengthPattern = { length, length };
350            Color[] colorPattern = { color, null };
351            paintStrokes(r, g, View.X_AXIS, lengthPattern, colorPattern);
352        }
353    }
354
355    /**
356     * Painter that defines colors for "shadow" and "light" border sides.
357     */
358    abstract static class ShadowLightPainter extends StrokePainter {
359        /**
360         * Return the "shadow" border side color.
361         */
362        static Color getShadowColor(Color c) {
363            return CSSBorder.getAdjustedColor(c, -0.3);
364        }
365
366        /**
367         * Return the "light" border side color.
368         */
369        static Color getLightColor(Color c) {
370            return CSSBorder.getAdjustedColor(c, 0.7);
371        }
372    }
373
374    /**
375     * Painter for the "groove" and "ridge" CSS border styles.
376     */
377    static class GrooveRidgePainter extends ShadowLightPainter {
378        final Value type;
379
380        GrooveRidgePainter(Value type) {
381            this.type = type;
382        }
383
384        public void paint(Polygon shape, Graphics g, Color color, int side) {
385            Rectangle r = shape.getBounds();
386            int length = Math.max(r.height / 2, 1);
387            int[] lengthPattern = { length, length };
388            Color[] colorPattern =
389                             ((side + 1) % 4 < 2) == (type == Value.GROOVE) ?
390                new Color[] { getShadowColor(color), getLightColor(color) } :
391                new Color[] { getLightColor(color), getShadowColor(color) };
392            paintStrokes(r, g, View.Y_AXIS, lengthPattern, colorPattern);
393        }
394    }
395
396    /**
397     * Painter for the "inset" and "outset" CSS border styles.
398     */
399    static class InsetOutsetPainter extends ShadowLightPainter {
400        Value type;
401
402        InsetOutsetPainter(Value type) {
403            this.type = type;
404        }
405
406        public void paint(Polygon shape, Graphics g, Color color, int side) {
407            g.setColor(((side + 1) % 4 < 2) == (type == Value.INSET) ?
408                                getShadowColor(color) : getLightColor(color));
409            g.fillPolygon(shape);
410        }
411    }
412
413    /**
414     * Add the specified painter to the painters map.
415     */
416    static void registerBorderPainter(Value style, BorderPainter painter) {
417        borderPainters.put(style, painter);
418    }
419
420    /** Map the border style values to the border painter objects.  */
421    static Map<Value, BorderPainter> borderPainters =
422                                        new HashMap<Value, BorderPainter>();
423
424    /* Initialize the border painters map with the pre-defined values.  */
425    static {
426        registerBorderPainter(Value.NONE, new NullPainter());
427        registerBorderPainter(Value.HIDDEN, new NullPainter());
428        registerBorderPainter(Value.SOLID, new SolidPainter());
429        registerBorderPainter(Value.DOUBLE, new DoublePainter());
430        registerBorderPainter(Value.DOTTED, new DottedDashedPainter(1));
431        registerBorderPainter(Value.DASHED, new DottedDashedPainter(3));
432        registerBorderPainter(Value.GROOVE, new GrooveRidgePainter(Value.GROOVE));
433        registerBorderPainter(Value.RIDGE, new GrooveRidgePainter(Value.RIDGE));
434        registerBorderPainter(Value.INSET, new InsetOutsetPainter(Value.INSET));
435        registerBorderPainter(Value.OUTSET, new InsetOutsetPainter(Value.OUTSET));
436    }
437}
438