/* * {{{ header & license * Copyright (c) 2004, 2005 Joshua Marinacci * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * }}} */ package org.xhtmlrenderer.render; import java.awt.BasicStroke; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import org.xhtmlrenderer.css.constants.IdentValue; import org.xhtmlrenderer.css.parser.FSRGBColor; import org.xhtmlrenderer.css.style.derived.BorderPropertySet; import org.xhtmlrenderer.extend.OutputDevice; public class BorderPainter { public static final int TOP = 1; public static final int LEFT = 2; public static final int BOTTOM = 4; public static final int RIGHT = 8; public static final int ALL = TOP + LEFT + BOTTOM + RIGHT; /** * @param xOffset for determining starting point for patterns */ public static void paint( Rectangle bounds, int sides, BorderPropertySet border, RenderingContext ctx, int xOffset, boolean bevel) { if ((sides & BorderPainter.TOP) == BorderPainter.TOP && border.noTop()) { sides -= BorderPainter.TOP; } if ((sides & BorderPainter.LEFT) == BorderPainter.LEFT && border.noLeft()) { sides -= BorderPainter.LEFT; } if ((sides & BorderPainter.BOTTOM) == BorderPainter.BOTTOM && border.noBottom()) { sides -= BorderPainter.BOTTOM; } if ((sides & BorderPainter.RIGHT) == BorderPainter.RIGHT && border.noRight()) { sides -= BorderPainter.RIGHT; } //Now paint! if ((sides & BorderPainter.TOP) == BorderPainter.TOP && border.topColor() != FSRGBColor.TRANSPARENT) { paintBorderSide(ctx.getOutputDevice(), border, bounds, sides, BorderPainter.TOP, border.topStyle(), xOffset, bevel); } if ((sides & BorderPainter.LEFT) == BorderPainter.LEFT && border.leftColor() != FSRGBColor.TRANSPARENT) { paintBorderSide(ctx.getOutputDevice(), border, bounds, sides, BorderPainter.LEFT, border.leftStyle(), xOffset, bevel); } if ((sides & BorderPainter.BOTTOM) == BorderPainter.BOTTOM && border.bottomColor() != FSRGBColor.TRANSPARENT) { paintBorderSide(ctx.getOutputDevice(), border, bounds, sides, BorderPainter.BOTTOM, border.bottomStyle(), xOffset, bevel); } if ((sides & BorderPainter.RIGHT) == BorderPainter.RIGHT && border.rightColor() != FSRGBColor.TRANSPARENT) { paintBorderSide(ctx.getOutputDevice(), border, bounds, sides, BorderPainter.RIGHT, border.rightStyle(), xOffset, bevel); } } private static Rectangle shrinkRect(final Rectangle rect, final BorderPropertySet border, int sides) { Rectangle r2 = new Rectangle(); r2.x = rect.x + ((sides & BorderPainter.LEFT) == 0 ? 0 : (int) border.left()); r2.width = rect.width - ((sides & BorderPainter.LEFT) == 0 ? 0 : (int) border.left()) - ((sides & BorderPainter.RIGHT) == 0 ? 0 : (int) border.right()); r2.y = rect.y + ((sides & BorderPainter.TOP) == 0 ? 0 : (int) border.top()); r2.height = rect.height - ((sides & BorderPainter.TOP) == 0 ? 0 : (int) border.top()) - ((sides & BorderPainter.BOTTOM) == 0 ? 0 : (int) border.bottom()); return r2; } private static void paintBorderSide(OutputDevice outputDevice, final BorderPropertySet border, final Rectangle bounds, final int sides, int currentSide, final IdentValue borderSideStyle, int xOffset, boolean bevel) { if (borderSideStyle == IdentValue.RIDGE || borderSideStyle == IdentValue.GROOVE) { BorderPropertySet bd2 = new BorderPropertySet((int) (border.top() / 2), (int) (border.right() / 2), (int) (border.bottom() / 2), (int) (border.left() / 2)); if (borderSideStyle == IdentValue.RIDGE) { paintBorderSidePolygon( outputDevice, bounds, border, border.darken(borderSideStyle), border.lighten(borderSideStyle), sides, currentSide, bevel); paintBorderSidePolygon( outputDevice, bounds, bd2, border.lighten(borderSideStyle), border.darken(borderSideStyle), sides, currentSide, bevel); } else { paintBorderSidePolygon( outputDevice, bounds, border, border.lighten(borderSideStyle), border.darken(borderSideStyle), sides, currentSide, bevel); paintBorderSidePolygon( outputDevice, bounds, bd2, border.darken(borderSideStyle), border.lighten(borderSideStyle), sides, currentSide, bevel); } } else if (borderSideStyle == IdentValue.OUTSET) { paintBorderSidePolygon(outputDevice, bounds, border, border.lighten(borderSideStyle), border.darken(borderSideStyle), sides, currentSide, bevel); } else if (borderSideStyle == IdentValue.INSET) { paintBorderSidePolygon(outputDevice, bounds, border, border.darken(borderSideStyle), border.lighten(borderSideStyle), sides, currentSide, bevel); } else if (borderSideStyle == IdentValue.SOLID) { paintSolid(outputDevice, bounds, border, border, sides, currentSide, bevel); } else if (borderSideStyle == IdentValue.DOUBLE) { paintDoubleBorder(outputDevice, border, bounds, sides, currentSide, bevel); } else { int thickness = 0; if (currentSide == BorderPainter.TOP) thickness = (int) border.top(); if (currentSide == BorderPainter.BOTTOM) thickness = (int) border.bottom(); if (currentSide == BorderPainter.RIGHT) thickness = (int) border.right(); if (currentSide == BorderPainter.LEFT) thickness = (int) border.left(); if (borderSideStyle == IdentValue.DASHED) { outputDevice.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); paintPatternedRect(outputDevice, bounds, border, border, new float[]{8.0f + thickness * 2, 4.0f + thickness}, sides, currentSide, xOffset); outputDevice.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } if (borderSideStyle == IdentValue.DOTTED) { // turn off anti-aliasing or the dots will be all blurry outputDevice.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); paintPatternedRect(outputDevice, bounds, border, border, new float[]{thickness, thickness}, sides, currentSide, xOffset); outputDevice.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } } } private static DoubleBorderInfo calcDoubleBorderInfo(int width) { DoubleBorderInfo result = new DoubleBorderInfo(); if (width == 1) { result.setOuter(1); } else if (width == 2) { result.setOuter(1); result.setInner(1); } else { int extra = width % 3; switch (extra) { case 0: result.setOuter(width / 3); result.setCenter(width / 3); result.setInner(width / 3); break; case 1: result.setOuter((width + 2) / 3 - 1); result.setCenter((width + 2) / 3); result.setInner((width + 2) / 3 - 1); break; case 2: result.setOuter((width + 1) / 3); result.setCenter((width + 1) / 3 - 1); result.setInner((width + 1) / 3); break; } } return result; } private static void paintDoubleBorder( OutputDevice outputDevice, BorderPropertySet border, Rectangle bounds, int sides, int currentSide, boolean bevel) { DoubleBorderInfo topBorderInfo = calcDoubleBorderInfo((int)border.top()); DoubleBorderInfo rightBorderInfo = calcDoubleBorderInfo((int)border.right()); DoubleBorderInfo bottomBorderInfo = calcDoubleBorderInfo((int)border.bottom()); DoubleBorderInfo leftBorderInfo = calcDoubleBorderInfo((int)border.left()); BorderPropertySet outer = new BorderPropertySet( topBorderInfo.getOuter(), rightBorderInfo.getOuter(), bottomBorderInfo.getOuter(), leftBorderInfo.getOuter()); BorderPropertySet center = new BorderPropertySet( topBorderInfo.getCenter(), rightBorderInfo.getCenter(), bottomBorderInfo.getCenter(), leftBorderInfo.getCenter()); BorderPropertySet inner = new BorderPropertySet( topBorderInfo.getInner(), rightBorderInfo.getInner(), bottomBorderInfo.getInner(), leftBorderInfo.getInner()); Rectangle b2 = shrinkRect(bounds, outer, bevel ? sides : currentSide); b2 = shrinkRect(b2, center, bevel ? sides : currentSide); // draw outer border paintSolid(outputDevice, bounds, outer, border, sides, currentSide, bevel); // draw inner border paintSolid(outputDevice, b2, inner, border, sides, currentSide, bevel); } /** * Gets the polygon to be filled for the border */ private static Polygon getBorderSidePolygon( final Rectangle bounds, final BorderPropertySet border, final int sides, int currentSide, boolean bevel) { int rightCorner = 0; int leftCorner = 0; int topCorner = 0; int bottomCorner = 0; if (bevel) { rightCorner = (((sides & BorderPainter.RIGHT) == BorderPainter.RIGHT) ? (int) border.right() : 0); leftCorner = (((sides & BorderPainter.LEFT) == BorderPainter.LEFT) ? (int) border.left() : 0); topCorner = (((sides & BorderPainter.TOP) == BorderPainter.TOP) ? (int) border.top() : 0); bottomCorner = (((sides & BorderPainter.BOTTOM) == BorderPainter.BOTTOM) ? (int) border.bottom() : 0); } Polygon poly = null; if (currentSide == BorderPainter.TOP) { if ((int) border.top() != 1) { // use polygons for borders over 1px wide poly = new Polygon(); poly.addPoint(bounds.x, bounds.y); poly.addPoint(bounds.x + bounds.width, bounds.y); poly.addPoint(bounds.x + bounds.width - rightCorner, bounds.y + (int) border.top() - 0); poly.addPoint(bounds.x + leftCorner, bounds.y + (int) border.top() - 0); } } else if (currentSide == BorderPainter.BOTTOM) { if ((int) border.bottom() != 1) { poly = new Polygon(); // upper right poly.addPoint(bounds.x + bounds.width - rightCorner, bounds.y + bounds.height - (int) border.bottom()); // upper left poly.addPoint(bounds.x + leftCorner, bounds.y + bounds.height - (int) border.bottom()); // lower left poly.addPoint(bounds.x, bounds.y + bounds.height); // lower right poly.addPoint(bounds.x + bounds.width, bounds.y + bounds.height - 0); } } else if (currentSide == BorderPainter.RIGHT) { if ((int) border.right() != 1) { poly = new Polygon(); poly.addPoint(bounds.x + bounds.width, bounds.y); poly.addPoint(bounds.x + bounds.width - (int) border.right(), bounds.y + topCorner); poly.addPoint(bounds.x + bounds.width - (int) border.right(), bounds.y + bounds.height - bottomCorner); poly.addPoint(bounds.x + bounds.width, bounds.y + bounds.height); } } else if (currentSide == BorderPainter.LEFT) { if ((int) border.left() != 1) { poly = new Polygon(); poly.addPoint(bounds.x, bounds.y); poly.addPoint(bounds.x + (int) border.left(), bounds.y + topCorner); poly.addPoint(bounds.x + (int) border.left(), bounds.y + bounds.height - bottomCorner); poly.addPoint(bounds.x, bounds.y + bounds.height); } } return poly; } /** * @param xOffset for inline borders, to determine dash_phase of top and bottom */ private static void paintPatternedRect(OutputDevice outputDevice, final Rectangle bounds, final BorderPropertySet border, final BorderPropertySet color, final float[] pattern, final int sides, final int currentSide, int xOffset) { Stroke old_stroke = outputDevice.getStroke(); if (currentSide == BorderPainter.TOP) { outputDevice.setColor(color.topColor()); outputDevice.setStroke(new BasicStroke((int) border.top(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, pattern, xOffset)); outputDevice.drawBorderLine( bounds, BorderPainter.TOP, (int)border.top(), false); } else if (currentSide == BorderPainter.LEFT) { outputDevice.setColor(color.leftColor()); outputDevice.setStroke(new BasicStroke((int) border.left(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, pattern, 0)); outputDevice.drawBorderLine( bounds, BorderPainter.LEFT, (int)border.left(), false); } else if (currentSide == BorderPainter.RIGHT) { outputDevice.setColor(color.rightColor()); outputDevice.setStroke(new BasicStroke((int) border.right(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, pattern, 0)); outputDevice.drawBorderLine( bounds, BorderPainter.RIGHT, (int)border.right(), false); } else if (currentSide == BorderPainter.BOTTOM) { outputDevice.setColor(color.bottomColor()); outputDevice.setStroke(new BasicStroke((int) border.bottom(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, pattern, xOffset)); outputDevice.drawBorderLine( bounds, BorderPainter.BOTTOM, (int)border.bottom(), false); } outputDevice.setStroke(old_stroke); } private static void paintBorderSidePolygon(OutputDevice outputDevice, final Rectangle bounds, final BorderPropertySet border, final BorderPropertySet high, final BorderPropertySet low, final int sides, int currentSide, boolean bevel) { if (currentSide == BorderPainter.TOP) { paintSolid(outputDevice, bounds, border, high, sides, currentSide, bevel); } else if (currentSide == BorderPainter.BOTTOM) { paintSolid(outputDevice, bounds, border, low, sides, currentSide, bevel); } else if (currentSide == BorderPainter.RIGHT) { paintSolid(outputDevice, bounds, border, low, sides, currentSide, bevel); } else if (currentSide == BorderPainter.LEFT) { paintSolid(outputDevice, bounds, border, high, sides, currentSide, bevel); } } private static void paintSolid(OutputDevice outputDevice, final Rectangle bounds, final BorderPropertySet border, final BorderPropertySet bcolor, final int sides, int currentSide, boolean bevel) { Polygon poly = getBorderSidePolygon(bounds, border, sides, currentSide, bevel); if (currentSide == BorderPainter.TOP) { outputDevice.setColor(bcolor.topColor()); // draw a 1px border with a line instead of a polygon if ((int) border.top() == 1) { outputDevice.drawBorderLine(bounds, BorderPainter.TOP, (int)border.top(), true); } else { // use polygons for borders over 1px wide outputDevice.fill(poly); } } else if (currentSide == BorderPainter.BOTTOM) { outputDevice.setColor(bcolor.bottomColor()); if ((int) border.bottom() == 1) { outputDevice.drawBorderLine(bounds, BorderPainter.BOTTOM, (int)border.bottom(), true); } else { outputDevice.fill(poly); } } else if (currentSide == BorderPainter.RIGHT) { outputDevice.setColor(bcolor.rightColor()); if ((int) border.right() == 1) { outputDevice.drawBorderLine(bounds, BorderPainter.RIGHT, (int)border.right(), true); } else { outputDevice.fill(poly); } } else if (currentSide == BorderPainter.LEFT) { outputDevice.setColor(bcolor.leftColor()); if ((int) border.left() == 1) { outputDevice.drawBorderLine(bounds, BorderPainter.LEFT, (int)border.left(), true); } else { outputDevice.fill(poly); } } } private static class DoubleBorderInfo { private int _outer; private int _center; private int _inner; public int getCenter() { return _center; } public void setCenter(int center) { _center = center; } public int getInner() { return _inner; } public void setInner(int inner) { _inner = inner; } public int getOuter() { return _outer; } public void setOuter(int outer) { _outer = outer; } } } /* * $Id: BorderPainter.java,v 1.48 2008/07/27 00:21:47 peterbrant Exp $ * * $Log: BorderPainter.java,v $ * Revision 1.48 2008/07/27 00:21:47 peterbrant * Implement CMYK color support for PDF output, starting with patch from Mykola Gurov / Banish java.awt.Color from FS core layout classes * * Revision 1.47 2007/04/24 17:04:31 peterbrant * Method name improvements * * Revision 1.46 2007/03/01 18:00:10 peterbrant * Fix rounding problems with double borders / Light BorderPainter cleanup (more needed) / Don't bevel collapsed table borders * * Revision 1.45 2007/02/07 16:33:25 peterbrant * Initial commit of rewritten table support and associated refactorings * * Revision 1.44 2007/01/17 17:50:54 peterbrant * Clean out unused code * * Revision 1.43 2006/02/21 18:09:17 peterbrant * Fix call to BorderPropertySet constructor * * Revision 1.42 2006/02/01 01:30:14 peterbrant * Initial commit of PDF work * * Revision 1.41 2006/01/27 01:15:36 peterbrant * Start on better support for different output devices * * Revision 1.40 2005/12/15 20:04:13 peterbrant * Don't paint transparent borders * * Revision 1.39 2005/11/08 20:02:14 peterbrant * Fix off by one errors for borders with an odd width * * Revision 1.38 2005/10/27 00:09:02 tobega * Sorted out Context into RenderingContext and LayoutContext * * Revision 1.37 2005/10/21 18:49:44 pdoubleya * Fixed border painting bug. * * Revision 1.36 2005/10/21 18:10:52 pdoubleya * Support for cachable borders. Still buggy on some pages, but getting there. * * Revision 1.35 2005/06/22 23:48:45 tobega * Refactored the css package to allow a clean separation from the core. * * Revision 1.34 2005/06/04 16:04:12 tobega * just playing with border colors a bit more * * Revision 1.33 2005/06/04 14:47:43 tobega * Just for fun: took more control over darkening/brightening colors. Looks nice, though. * * Revision 1.32 2005/06/03 01:08:58 tobega * Fixed bug in painting double borders * * Revision 1.31 2005/05/29 23:49:15 tobega * Did it right, this time, so that inline borders also look nice * * Revision 1.30 2005/05/29 23:43:28 tobega * Removed tendency for white diagonal line in corners * * Revision 1.29 2005/05/29 20:13:20 tobega * Cleaned up duplicate code * * Revision 1.28 2005/05/29 19:37:58 tobega * Fixed up using different style borders. * Fixed patterned borders to work right. * * Revision 1.27 2005/05/16 08:07:09 tobega * Border painting for inlines works beautifully (tested only solid borders) * * Revision 1.26 2005/05/13 11:49:59 tobega * Started to fix up borders on inlines. Got caught up in refactoring. * Boxes shouldn't cache borders and stuff unless necessary. Started to remove unnecessary references. * Hover is not working completely well now, might get better when I'm done. * * Revision 1.25 2005/05/13 08:46:17 tobega * A line is drawn to the right and below the coordinate. Needed to adjust when drawing lines for 1-pixel borders * * Revision 1.24 2005/05/12 06:24:16 joshy * more very minor border and background tweaks * Issue number: * Obtained from: * Submitted by: * Reviewed by: * * Revision 1.23 2005/05/12 04:55:57 joshy * fix for issues 76 * Issue number: * Obtained from: * Submitted by: * Reviewed by: * * Revision 1.22 2005/05/08 14:36:58 tobega * Refactored away the need for having a context in a CalculatedStyle * * Revision 1.21 2005/01/29 20:24:23 pdoubleya * Clean/reformat code. Removed commented blocks, checked copyright. * * Revision 1.20 2005/01/25 10:56:56 pdoubleya * Added warning on possible duplicate code. * * Revision 1.19 2005/01/25 10:55:15 pdoubleya * Added warning on possible duplicate code. * * Revision 1.18 2005/01/24 22:46:42 pdoubleya * Added support for ident-checks using IdentValue instead of string comparisons. * * Revision 1.17 2005/01/24 14:36:34 pdoubleya * Mass commit, includes: updated for changes to property declaration instantiation, and new use of DerivedValue. Removed any references to older XR... classes (e.g. XRProperty). Cleaned imports. * * Revision 1.16 2005/01/09 15:22:49 tobega * Prepared improved handling of margins, borders and padding. * * Revision 1.15 2004/12/29 10:39:34 tobega * Separated current state Context into LayoutContext and the rest into SharedContext. * * Revision 1.14 2004/12/27 09:40:48 tobega * Moved more styling to render stage. Now inlines have backgrounds and borders again. * * Revision 1.13 2004/12/27 07:43:32 tobega * Cleaned out border from box, it can be gotten from current style. Is it maybe needed for dynamic stuff? * * Revision 1.12 2004/12/13 02:12:53 tobega * Borders are working again * * Revision 1.11 2004/12/12 04:18:57 tobega * Now the core compiles at least. Now we must make it work right. Table layout is one point that really needs to be looked over * * Revision 1.10 2004/12/12 03:33:00 tobega * Renamed x and u to avoid confusing IDE. But that got cvs in a twist. See if this does it * * Revision 1.9 2004/12/11 23:36:49 tobega * Progressing on cleaning up layout and boxes. Still broken, won't even compile at the moment. Working hard to fix it, though. * * Revision 1.8 2004/12/05 00:48:58 tobega * Cleaned up so that now all property-lookups use the CalculatedStyle. Also added support for relative values of top, left, width, etc. * * Revision 1.7 2004/11/09 15:53:49 joshy * initial support for hover (currently disabled) * moved justification code into it's own class in a new subpackage for inline * layout (because it's so blooming complicated) * * Issue number: * Obtained from: * Submitted by: * Reviewed by: * * Revision 1.6 2004/11/07 16:23:18 joshy * added support for lighten and darken to bordercolor * added support for different colored sides * * Issue number: * Obtained from: * Submitted by: * Reviewed by: * * Revision 1.5 2004/11/06 22:49:52 joshy * cleaned up alice * initial support for inline borders and backgrounds * moved all of inlinepainter back into inlinerenderer, where it belongs. * * * * Issue number: * Obtained from: * Submitted by: * Reviewed by: * * Revision 1.4 2004/11/02 17:14:00 joshy * implemented double borders * * * Issue number: * Obtained from: * Submitted by: * Reviewed by: * * Revision 1.3 2004/10/23 13:50:26 pdoubleya * Re-formatted using JavaStyle tool. * Cleaned imports to resolve wildcards except for common packages (java.io, java.util, etc). * Added CVS log comments at bottom. * * */