1 /* 2 * Copyright (c) 2007, 2018, 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 @SuppressWarnings("serial") // Superclass is not serializable across versions 54 class 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 }