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 }