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 }