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