/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: BorderPainter.java 1805173 2017-08-16 10:50:04Z ssteiner $ */

package org.apache.fop.render.intermediate;

import java.awt.Color;
import java.awt.Rectangle;
import java.io.IOException;

import org.apache.fop.traits.BorderProps;

This is an abstract base class for handling border painting.
/** * This is an abstract base class for handling border painting. */
public class BorderPainter { // TODO Use a class to model border instead of an array
Convention index of before top
/** Convention index of before top */
protected static final int TOP = 0;
Convention index of right border
/** Convention index of right border */
protected static final int RIGHT = 1;
Convention index of bottom border
/** Convention index of bottom border */
protected static final int BOTTOM = 2;
Convention index of left border
/** Convention index of left border */
protected static final int LEFT = 3; // TODO Use a class to model border corners instead of an array
Convention index of top-left border corners
/** Convention index of top-left border corners */
protected static final int TOP_LEFT = 0;
Convention index of top-right-end border corners
/** Convention index of top-right-end border corners */
protected static final int TOP_RIGHT = 1;
Convention index of bottom-right border corners
/** Convention index of bottom-right border corners */
protected static final int BOTTOM_RIGHT = 2;
Convention index of bottom-left border corners
/** Convention index of bottom-left border corners */
protected static final int BOTTOM_LEFT = 3;
The ratio between a solid dash and the white-space in a dashed-border
/** The ratio between a solid dash and the white-space in a dashed-border */
public static final float DASHED_BORDER_SPACE_RATIO = 0.5f;
The length of the dash as a factor of the border width i.e. 2 -> dashWidth = 2*borderWidth
/** The length of the dash as a factor of the border width i.e. 2 -> dashWidth = 2*borderWidth */
protected static final float DASHED_BORDER_LENGTH_FACTOR = 2.0f; private final GraphicsPainter graphicsPainter; public BorderPainter(GraphicsPainter graphicsPainter) { this.graphicsPainter = graphicsPainter; }
Draws borders.
Params:
  • borderRect – the border rectangle
  • bpsTop – the border specification on the top side
  • bpsBottom – the border specification on the bottom side
  • bpsLeft – the border specification on the left side
  • bpsRight – the border specification on the end side
  • innerBackgroundColor – the inner background color
Throws:
  • IFException – if an error occurs while drawing the borders
/** * Draws borders. * @param borderRect the border rectangle * @param bpsTop the border specification on the top side * @param bpsBottom the border specification on the bottom side * @param bpsLeft the border specification on the left side * @param bpsRight the border specification on the end side * @param innerBackgroundColor the inner background color * @throws IFException if an error occurs while drawing the borders */
public void drawBorders(Rectangle borderRect, BorderProps bpsTop, BorderProps bpsBottom, BorderProps bpsLeft, BorderProps bpsRight, Color innerBackgroundColor) throws IFException { try { drawRoundedBorders(borderRect, bpsTop, bpsBottom, bpsLeft, bpsRight); } catch (IOException ioe) { throw new IFException("IO error drawing borders", ioe); } } private BorderProps sanitizeBorderProps(BorderProps bps) { return bps == null ? bps : bps.width == 0 ? (BorderProps) null : bps; }
TODO merge with drawRoundedBorders()?
Params:
  • borderRect – the border rectangle
  • bpsTop – the border specification on the top side
  • bpsBottom – the border specification on the bottom side
  • bpsLeft – the border specification on the left side
  • bpsRight – the border specification on the end side
Throws:
/** * TODO merge with drawRoundedBorders()? * @param borderRect the border rectangle * @param bpsTop the border specification on the top side * @param bpsBottom the border specification on the bottom side * @param bpsLeft the border specification on the left side * @param bpsRight the border specification on the end side * @throws IOException */
protected void drawRectangularBorders(Rectangle borderRect, BorderProps bpsTop, BorderProps bpsBottom, BorderProps bpsLeft, BorderProps bpsRight) throws IOException { bpsTop = sanitizeBorderProps(bpsTop); bpsBottom = sanitizeBorderProps(bpsBottom); bpsLeft = sanitizeBorderProps(bpsLeft); bpsRight = sanitizeBorderProps(bpsRight); int startx = borderRect.x; int starty = borderRect.y; int width = borderRect.width; int height = borderRect.height; boolean[] b = new boolean[] { (bpsTop != null), (bpsRight != null), (bpsBottom != null), (bpsLeft != null)}; if (!b[TOP] && !b[RIGHT] && !b[BOTTOM] && !b[LEFT]) { return; } int[] bw = new int[] { (b[TOP] ? bpsTop.width : 0), (b[RIGHT] ? bpsRight.width : 0), (b[BOTTOM] ? bpsBottom.width : 0), (b[LEFT] ? bpsLeft.width : 0)}; int[] clipw = new int[] { BorderProps.getClippedWidth(bpsTop), BorderProps.getClippedWidth(bpsRight), BorderProps.getClippedWidth(bpsBottom), BorderProps.getClippedWidth(bpsLeft)}; starty += clipw[TOP]; height -= clipw[TOP]; height -= clipw[BOTTOM]; startx += clipw[LEFT]; width -= clipw[LEFT]; width -= clipw[RIGHT]; boolean[] slant = new boolean[] { (b[LEFT] && b[TOP]), (b[TOP] && b[RIGHT]), (b[RIGHT] && b[BOTTOM]), (b[BOTTOM] && b[LEFT])}; if (bpsTop != null) { int sx1 = startx; int sx2 = (slant[TOP_LEFT] ? sx1 + bw[LEFT] - clipw[LEFT] : sx1); int ex1 = startx + width; int ex2 = (slant[TOP_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1); int outery = starty - clipw[TOP]; int clipy = outery + clipw[TOP]; int innery = outery + bw[TOP]; saveGraphicsState(); moveTo(sx1, clipy); int sx1a = sx1; int ex1a = ex1; if (isCollapseOuter(bpsTop)) { if (isCollapseOuter(bpsLeft)) { sx1a -= clipw[LEFT]; } if (isCollapseOuter(bpsRight)) { ex1a += clipw[RIGHT]; } lineTo(sx1a, outery); lineTo(ex1a, outery); } lineTo(ex1, clipy); lineTo(ex2, innery); lineTo(sx2, innery); closePath(); clip(); drawBorderLine(sx1a, outery, ex1a, innery, true, true, bpsTop.style, bpsTop.color); restoreGraphicsState(); } if (bpsRight != null) { int sy1 = starty; int sy2 = (slant[TOP_RIGHT] ? sy1 + bw[TOP] - clipw[TOP] : sy1); int ey1 = starty + height; int ey2 = (slant[BOTTOM_RIGHT] ? ey1 - bw[BOTTOM] + clipw[BOTTOM] : ey1); int outerx = startx + width + clipw[RIGHT]; int clipx = outerx - clipw[RIGHT]; int innerx = outerx - bw[RIGHT]; saveGraphicsState(); moveTo(clipx, sy1); int sy1a = sy1; int ey1a = ey1; if (isCollapseOuter(bpsRight)) { if (isCollapseOuter(bpsTop)) { sy1a -= clipw[TOP]; } if (isCollapseOuter(bpsBottom)) { ey1a += clipw[BOTTOM]; } lineTo(outerx, sy1a); lineTo(outerx, ey1a); } lineTo(clipx, ey1); lineTo(innerx, ey2); lineTo(innerx, sy2); closePath(); clip(); drawBorderLine(innerx, sy1a, outerx, ey1a, false, false, bpsRight.style, bpsRight.color); restoreGraphicsState(); } if (bpsBottom != null) { int sx1 = startx; int sx2 = (slant[BOTTOM_LEFT] ? sx1 + bw[LEFT] - clipw[LEFT] : sx1); int ex1 = startx + width; int ex2 = (slant[BOTTOM_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1); int outery = starty + height + clipw[BOTTOM]; int clipy = outery - clipw[BOTTOM]; int innery = outery - bw[BOTTOM]; saveGraphicsState(); moveTo(ex1, clipy); int sx1a = sx1; int ex1a = ex1; if (isCollapseOuter(bpsBottom)) { if (isCollapseOuter(bpsLeft)) { sx1a -= clipw[LEFT]; } if (isCollapseOuter(bpsRight)) { ex1a += clipw[RIGHT]; } lineTo(ex1a, outery); lineTo(sx1a, outery); } lineTo(sx1, clipy); lineTo(sx2, innery); lineTo(ex2, innery); closePath(); clip(); drawBorderLine(sx1a, innery, ex1a, outery, true, false, bpsBottom.style, bpsBottom.color); restoreGraphicsState(); } if (bpsLeft != null) { int sy1 = starty; int sy2 = (slant[TOP_LEFT] ? sy1 + bw[TOP] - clipw[TOP] : sy1); int ey1 = sy1 + height; int ey2 = (slant[BOTTOM_LEFT] ? ey1 - bw[BOTTOM] + clipw[BOTTOM] : ey1); int outerx = startx - clipw[LEFT]; int clipx = outerx + clipw[LEFT]; int innerx = outerx + bw[LEFT]; saveGraphicsState(); moveTo(clipx, ey1); int sy1a = sy1; int ey1a = ey1; if (isCollapseOuter(bpsLeft)) { if (isCollapseOuter(bpsTop)) { sy1a -= clipw[TOP]; } if (isCollapseOuter(bpsBottom)) { ey1a += clipw[BOTTOM]; } lineTo(outerx, ey1a); lineTo(outerx, sy1a); } lineTo(clipx, sy1); lineTo(innerx, sy2); lineTo(innerx, ey2); closePath(); clip(); drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsLeft.style, bpsLeft.color); restoreGraphicsState(); } } private boolean isCollapseOuter(BorderProps bp) { return bp != null && bp.isCollapseOuter(); }
This method calculates the length of the "dash" in a dashed border. The dash satisfies the condition that corners start on a dash and end with a dash (rather than ending with a white space).
Params:
  • borderLength – The length of the border.
  • borderWidth – The width/thickness of the border.
Returns:returns the length of the dash such that it fits the criteria above.
/** * This method calculates the length of the "dash" in a dashed border. The dash satisfies the * condition that corners start on a dash and end with a dash (rather than ending with a white space). * @param borderLength The length of the border. * @param borderWidth The width/thickness of the border. * @return returns the length of the dash such that it fits the criteria above. */
public static float dashWidthCalculator(float borderLength, float borderWidth) { float dashWidth = DASHED_BORDER_LENGTH_FACTOR * borderWidth; if (borderWidth < 3) { dashWidth = (DASHED_BORDER_LENGTH_FACTOR * 3) * borderWidth; } int period = (int) ((borderLength - dashWidth) / dashWidth / (1.0f + DASHED_BORDER_SPACE_RATIO)); period = period < 0 ? 0 : period; return borderLength / (period * (1.0f + DASHED_BORDER_SPACE_RATIO) + 1.0f); }
TODO merge with drawRectangularBorders?
Params:
  • borderRect – the border rectangle
Throws:
/** TODO merge with drawRectangularBorders? * @param borderRect the border rectangle * @throws IOException on io exception * */
protected void drawRoundedBorders(Rectangle borderRect, BorderProps beforeBorderProps, BorderProps afterBorderProps, BorderProps startBorderProps, BorderProps endBorderProps) throws IOException { BorderSegment before = borderSegmentForBefore(beforeBorderProps); BorderSegment after = borderSegmentForAfter(afterBorderProps); BorderSegment start = borderSegmentForStart(startBorderProps); BorderSegment end = borderSegmentForEnd(endBorderProps); if (before.getWidth() == 0 && after.getWidth() == 0 && start.getWidth() == 0 && end.getWidth() == 0) { return; } final int startx = borderRect.x + start.getClippedWidth(); final int starty = borderRect.y + before.getClippedWidth(); final int width = borderRect.width - start.getClippedWidth() - end.getClippedWidth(); final int height = borderRect.height - before.getClippedWidth() - after.getClippedWidth(); //Determine scale factor if any adjacent elliptic corners overlap double cornerCorrectionFactor = calculateCornerScaleCorrection(width, height, before, after, start, end); drawBorderSegment(start, before, end, 0, width, startx, starty, cornerCorrectionFactor); drawBorderSegment(before, end, after, 1, height, startx + width, starty, cornerCorrectionFactor); drawBorderSegment(end, after, start, 2, width, startx + width, starty + height, cornerCorrectionFactor); drawBorderSegment(after, start, before, 3, height, startx, starty + height, cornerCorrectionFactor); } private void drawBorderSegment(BorderSegment start, BorderSegment before, BorderSegment end, int orientation, int width, int x, int y, double cornerCorrectionFactor) throws IOException { if (before.getWidth() != 0) { //Let x increase in the START->END direction final int sx2 = start.getWidth() - start.getClippedWidth(); final int ex1 = width; final int ex2 = ex1 - end.getWidth() + end.getClippedWidth(); final int outery = -before.getClippedWidth(); final int innery = outery + before.getWidth(); final int ellipseSBRadiusX = correctRadius(cornerCorrectionFactor, start.getRadiusEnd()); final int ellipseSBRadiusY = correctRadius(cornerCorrectionFactor, before.getRadiusStart()); final int ellipseBERadiusX = correctRadius(cornerCorrectionFactor, end.getRadiusStart()); final int ellipseBERadiusY = correctRadius(cornerCorrectionFactor, before.getRadiusEnd()); saveGraphicsState(); translateCoordinates(x, y); if (orientation != 0) { rotateCoordinates(Math.PI * orientation / 2d); } final int ellipseSBX = ellipseSBRadiusX; final int ellipseSBY = ellipseSBRadiusY; final int ellipseBEX = ex1 - ellipseBERadiusX; final int ellipseBEY = ellipseBERadiusY; int sx1a = 0; int ex1a = ex1; if (ellipseSBRadiusX != 0 && ellipseSBRadiusY != 0) { final double[] joinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX, ellipseSBRadiusY, sx2, innery); final double outerJoinPointX = joinMetrics[0]; final double outerJoinPointY = joinMetrics[1]; final double sbJoinAngle = joinMetrics[2]; moveTo((int) outerJoinPointX, (int) outerJoinPointY); arcTo(Math.PI + sbJoinAngle, Math.PI * 3 / 2, ellipseSBX, ellipseSBY, ellipseSBRadiusX, ellipseSBRadiusY); } else { moveTo(0, 0); if (before.isCollapseOuter()) { if (start.isCollapseOuter()) { sx1a -= start.getClippedWidth(); } if (end.isCollapseOuter()) { ex1a += end.getClippedWidth(); } lineTo(sx1a, outery); lineTo(ex1a, outery); } } if (ellipseBERadiusX != 0 && ellipseBERadiusY != 0) { final double[] outerJoinMetrics = getCornerBorderJoinMetrics( ellipseBERadiusX, ellipseBERadiusY, ex1 - ex2, innery); final double beJoinAngle = ex1 == ex2 ? Math.PI / 2 : Math.PI / 2 - outerJoinMetrics[2]; lineTo(ellipseBEX, 0); arcTo(Math.PI * 3 / 2 , Math.PI * 3 / 2 + beJoinAngle, ellipseBEX, ellipseBEY, ellipseBERadiusX, ellipseBERadiusY); if (ellipseBEX < ex2 && ellipseBEY > innery) { final double[] innerJoinMetrics = getCornerBorderJoinMetrics( (double) ex2 - ellipseBEX, (double) ellipseBEY - innery, ex1 - ex2, innery); final double innerJoinPointX = innerJoinMetrics[0]; final double innerJoinPointY = innerJoinMetrics[1]; final double beInnerJoinAngle = Math.PI / 2 - innerJoinMetrics[2]; lineTo((int) (ex2 - innerJoinPointX), (int) (innerJoinPointY + innery)); arcTo(beInnerJoinAngle + Math.PI * 3 / 2, Math.PI * 3 / 2, ellipseBEX, ellipseBEY, ex2 - ellipseBEX, ellipseBEY - innery); } else { lineTo(ex2, innery); } } else { lineTo(ex1, 0); lineTo(ex2, innery); } if (ellipseSBRadiusX == 0) { lineTo(sx2, innery); } else { if (ellipseSBX > sx2 && ellipseSBY > innery) { final double[] innerJoinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX - sx2, ellipseSBRadiusY - innery, sx2, innery); final double sbInnerJoinAngle = innerJoinMetrics[2]; lineTo(ellipseSBX, innery); arcTo(Math.PI * 3 / 2, sbInnerJoinAngle + Math.PI, ellipseSBX, ellipseSBY, ellipseSBX - sx2, ellipseSBY - innery); } else { lineTo(sx2, innery); } } closePath(); clip(); if (ellipseBERadiusY == 0 && ellipseSBRadiusY == 0) { drawBorderLine(sx1a, outery, ex1a, innery, true, true, before.getStyle(), before.getColor()); } else { int innerFillY = Math.max(Math.max(ellipseBEY, ellipseSBY), innery); drawBorderLine(sx1a, outery, ex1a, innerFillY, true, true, before.getStyle(), before.getColor()); } restoreGraphicsState(); } } private static int correctRadius(double cornerCorrectionFactor, int radius) { return (int) (Math.round(cornerCorrectionFactor * radius)); } private static BorderSegment borderSegmentForBefore(BorderProps before) { return AbstractBorderSegment.asBorderSegment(before); } private static BorderSegment borderSegmentForAfter(BorderProps after) { return AbstractBorderSegment.asFlippedBorderSegment(after); } private static BorderSegment borderSegmentForStart(BorderProps start) { return AbstractBorderSegment.asFlippedBorderSegment(start); } private static BorderSegment borderSegmentForEnd(BorderProps end) { return AbstractBorderSegment.asBorderSegment(end); } private interface BorderSegment { Color getColor(); int getStyle(); int getWidth(); int getClippedWidth(); int getRadiusStart(); int getRadiusEnd(); boolean isCollapseOuter(); boolean isSpecified(); } private abstract static class AbstractBorderSegment implements BorderSegment { private static BorderSegment asBorderSegment(BorderProps borderProps) { return borderProps == null ? NullBorderSegment.INSTANCE : new WrappingBorderSegment(borderProps); } private static BorderSegment asFlippedBorderSegment(BorderProps borderProps) { return borderProps == null ? NullBorderSegment.INSTANCE : new FlippedBorderSegment(borderProps); } public boolean isSpecified() { return !(this instanceof NullBorderSegment); } private static class WrappingBorderSegment extends AbstractBorderSegment { protected final BorderProps borderProps; private final int clippedWidth; WrappingBorderSegment(BorderProps borderProps) { this.borderProps = borderProps; clippedWidth = BorderProps.getClippedWidth(borderProps); } public int getStyle() { return borderProps.style; } public Color getColor() { return borderProps.color; } public int getWidth() { return borderProps.width; } public int getClippedWidth() { return clippedWidth; } public boolean isCollapseOuter() { return borderProps.isCollapseOuter(); } public int getRadiusStart() { return borderProps.getRadiusStart(); } public int getRadiusEnd() { return borderProps.getRadiusEnd(); } } private static class FlippedBorderSegment extends WrappingBorderSegment { FlippedBorderSegment(BorderProps borderProps) { super(borderProps); } public int getRadiusStart() { return borderProps.getRadiusEnd(); } public int getRadiusEnd() { return borderProps.getRadiusStart(); } } private static final class NullBorderSegment extends AbstractBorderSegment { public static final NullBorderSegment INSTANCE = new NullBorderSegment(); private NullBorderSegment() { } public int getWidth() { return 0; } public int getClippedWidth() { return 0; } public int getRadiusStart() { return 0; } public int getRadiusEnd() { return 0; } public boolean isCollapseOuter() { return false; } public Color getColor() { throw new UnsupportedOperationException(); } public int getStyle() { throw new UnsupportedOperationException(); } public boolean isSpecified() { return false; } } } private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, double xWidth, double yWidth) { if (xWidth > 0) { return getCornerBorderJoinMetrics(ellipseCenterX, ellipseCenterY, yWidth / xWidth); } else { return new double[]{0, ellipseCenterY, 0}; } } private double[] getCornerBorderJoinMetrics(double ellipseCenterX, double ellipseCenterY, double borderWidthRatio) { double x = ellipseCenterY * ellipseCenterX * ( ellipseCenterY + ellipseCenterX * borderWidthRatio - Math.sqrt(2d * ellipseCenterX * ellipseCenterY * borderWidthRatio) ) / (ellipseCenterY * ellipseCenterY + ellipseCenterX * ellipseCenterX * borderWidthRatio * borderWidthRatio); double y = borderWidthRatio * x; return new double[]{x, y, Math.atan((ellipseCenterY - y) / (ellipseCenterX - x))}; }
Clip the background to the inner border
Params:
  • rect – clipping rectangle
  • bpsBefore – before border
  • bpsAfter – after border
  • bpsStart – start border
  • bpsEnd – end border
Throws:
/** * Clip the background to the inner border * @param rect clipping rectangle * @param bpsBefore before border * @param bpsAfter after border * @param bpsStart start border * @param bpsEnd end border * @throws IOException if an I/O error occurs */
public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IOException { BorderSegment before = borderSegmentForBefore(bpsBefore); BorderSegment after = borderSegmentForAfter(bpsAfter); BorderSegment start = borderSegmentForStart(bpsStart); BorderSegment end = borderSegmentForEnd(bpsEnd); int startx = rect.x; int starty = rect.y; int width = rect.width; int height = rect.height; double correctionFactor = calculateCornerCorrectionFactor(width + start.getWidth() + end.getWidth(), height + before.getWidth() + after.getWidth(), bpsBefore, bpsAfter, bpsStart, bpsEnd); Corner cornerBeforeEnd = Corner.createBeforeEndCorner(before, end, correctionFactor); Corner cornerEndAfter = Corner.createEndAfterCorner(end, after, correctionFactor); Corner cornerAfterStart = Corner.createAfterStartCorner(after, start, correctionFactor); Corner cornerStartBefore = Corner.createStartBeforeCorner(start, before, correctionFactor); new PathPainter(startx + cornerStartBefore.radiusX, starty) .lineHorizTo(width - cornerStartBefore.radiusX - cornerBeforeEnd.radiusX) .drawCorner(cornerBeforeEnd) .lineVertTo(height - cornerBeforeEnd.radiusY - cornerEndAfter.radiusY) .drawCorner(cornerEndAfter) .lineHorizTo(cornerEndAfter.radiusX + cornerAfterStart.radiusX - width) .drawCorner(cornerAfterStart) .lineVertTo(cornerAfterStart.radiusY + cornerStartBefore.radiusY - height) .drawCorner(cornerStartBefore); clip(); }
The four corners SB - Start-Before BE - Before-End EA - End-After AS - After-Start 0 --> x | v y SB BE *----* | | | | *----* AS EA
/** * The four corners * SB - Start-Before * BE - Before-End * EA - End-After * AS - After-Start * * 0 --> x * | * v * y * * SB BE * *----* * | | * | | * *----* * AS EA * */
private enum CornerAngles {
The before-end angles
/** The before-end angles */
BEFORE_END(Math.PI * 3 / 2, 0),
The end-after angles
/** The end-after angles */
END_AFTER(0, Math.PI / 2),
The after-start angles
/** The after-start angles*/
AFTER_START(Math.PI / 2, Math.PI),
The start-before angles
/** The start-before angles */
START_BEFORE(Math.PI, Math.PI * 3 / 2);
Angle of the start of the corner arch relative to the x-axis in the counter-clockwise direction
/** Angle of the start of the corner arch relative to the x-axis in the counter-clockwise direction */
private final double start;
Angle of the end of the corner arch relative to the x-axis in the counter-clockwise direction
/** Angle of the end of the corner arch relative to the x-axis in the counter-clockwise direction */
private final double end; CornerAngles(double start, double end) { this.start = start; this.end = end; } } private static final class Corner { private static final Corner SQUARE = new Corner(0, 0, null, 0, 0, 0, 0);
The radius of the elliptic corner in the x direction
/** The radius of the elliptic corner in the x direction */
private final int radiusX;
The radius of the elliptic corner in the y direction
/** The radius of the elliptic corner in the y direction */
private final int radiusY;
The start and end angles of the corner ellipse
/** The start and end angles of the corner ellipse */
private final CornerAngles angles;
The offset in the x direction of the center of the ellipse relative to the starting point
/** The offset in the x direction of the center of the ellipse relative to the starting point */
private final int centerX;
The offset in the y direction of the center of the ellipse relative to the starting point
/** The offset in the y direction of the center of the ellipse relative to the starting point */
private final int centerY;
The value in the x direction that the corner extends relative to the starting point
/** The value in the x direction that the corner extends relative to the starting point */
private final int incrementX;
The value in the y direction that the corner extends relative to the starting point
/** The value in the y direction that the corner extends relative to the starting point */
private final int incrementY; private Corner(int radiusX, int radiusY, CornerAngles angles, int ellipseOffsetX, int ellipseOffsetY, int incrementX, int incrementY) { this.radiusX = radiusX; this.radiusY = radiusY; this.angles = angles; this.centerX = ellipseOffsetX; this.centerY = ellipseOffsetY; this.incrementX = incrementX; this.incrementY = incrementY; } private static int extentFromRadiusStart(BorderSegment border, double correctionFactor) { return extentFromRadius(border.getRadiusStart(), border, correctionFactor); } private static int extentFromRadiusEnd(BorderSegment border, double correctionFactor) { return extentFromRadius(border.getRadiusEnd(), border, correctionFactor); } private static int extentFromRadius(int radius, BorderSegment border, double correctionFactor) { return Math.max((int) (radius * correctionFactor) - border.getWidth(), 0); } public static Corner createBeforeEndCorner(BorderSegment before, BorderSegment end, double correctionFactor) { int width = end.getRadiusStart(); int height = before.getRadiusEnd(); if (width == 0 || height == 0) { return SQUARE; } int x = extentFromRadiusStart(end, correctionFactor); int y = extentFromRadiusEnd(before, correctionFactor); return new Corner(x, y, CornerAngles.BEFORE_END, 0, y, x, y); } public static Corner createEndAfterCorner(BorderSegment end, BorderSegment after, double correctionFactor) { int width = end.getRadiusEnd(); int height = after.getRadiusStart(); if (width == 0 || height == 0) { return SQUARE; } int x = extentFromRadiusEnd(end, correctionFactor); int y = extentFromRadiusStart(after, correctionFactor); return new Corner(x, y, CornerAngles.END_AFTER, -x, 0, -x, y); } public static Corner createAfterStartCorner(BorderSegment after, BorderSegment start, double correctionFactor) { int width = start.getRadiusStart(); int height = after.getRadiusEnd(); if (width == 0 || height == 0) { return SQUARE; } int x = extentFromRadiusStart(start, correctionFactor); int y = extentFromRadiusEnd(after, correctionFactor); return new Corner(x, y, CornerAngles.AFTER_START, 0, -y, -x, -y); } public static Corner createStartBeforeCorner(BorderSegment start, BorderSegment before, double correctionFactor) { int width = start.getRadiusEnd(); int height = before.getRadiusStart(); if (width == 0 || height == 0) { return SQUARE; } int x = extentFromRadiusEnd(start, correctionFactor); int y = extentFromRadiusStart(before, correctionFactor); return new Corner(x, y, CornerAngles.START_BEFORE, x, 0, x, -y); } }
This is a helper class for constructing curves composed of move, line and arc operations. Coordinates are relative to the terminal point of the previous operation
/** * This is a helper class for constructing curves composed of move, line and arc operations. Coordinates * are relative to the terminal point of the previous operation */
private final class PathPainter {
Current x position
/** Current x position */
private int x;
Current y position
/** Current y position */
private int y; PathPainter(int x, int y) throws IOException { moveTo(x, y); } private void moveTo(int x, int y) throws IOException { this.x += x; this.y += y; BorderPainter.this.moveTo(this.x, this.y); } public PathPainter lineTo(int x, int y) throws IOException { this.x += x; this.y += y; BorderPainter.this.lineTo(this.x, this.y); return this; } public PathPainter lineHorizTo(int x) throws IOException { return lineTo(x, 0); } public PathPainter lineVertTo(int y) throws IOException { return lineTo(0, y); } PathPainter drawCorner(Corner corner) throws IOException { if (corner.radiusX == 0 && corner.radiusY == 0) { return this; } if (corner.radiusX == 0 || corner.radiusY == 0) { x += corner.incrementX; y += corner.incrementY; BorderPainter.this.lineTo(x, y); return this; } BorderPainter.this.arcTo(corner.angles.start, corner.angles.end, x + corner.centerX, y + corner.centerY, corner.radiusX, corner.radiusY); x += corner.incrementX; y += corner.incrementY; return this; } }
Calculate the correction factor to handle over-sized elliptic corner radii.
Params:
  • width – the border width
  • height – the border height
  • before – the before border properties
  • after – the after border properties
  • start – the start border properties
  • end – the end border properties
/** * Calculate the correction factor to handle over-sized elliptic corner radii. * * @param width the border width * @param height the border height * @param before the before border properties * @param after the after border properties * @param start the start border properties * @param end the end border properties * */
protected static double calculateCornerCorrectionFactor(int width, int height, BorderProps before, BorderProps after, BorderProps start, BorderProps end) { return calculateCornerScaleCorrection(width, height, borderSegmentForBefore(before), borderSegmentForAfter(after), borderSegmentForStart(start), borderSegmentForEnd(end)); }
Calculate the scaling factor to handle over-sized elliptic corner radii.
Params:
  • width – the border width
  • height – the border height
  • before – the before border segment
  • after – the after border segment
  • start – the start border segment
  • end – the end border segment
/** * Calculate the scaling factor to handle over-sized elliptic corner radii. * * @param width the border width * @param height the border height * @param before the before border segment * @param after the after border segment * @param start the start border segment * @param end the end border segment */
protected static double calculateCornerScaleCorrection(int width, int height, BorderSegment before, BorderSegment after, BorderSegment start, BorderSegment end) { return CornerScaleCorrectionCalculator.calculate(width, height, before, after, start, end); } private static final class CornerScaleCorrectionCalculator { private double correctionFactor = 1; private CornerScaleCorrectionCalculator(int width, int height, BorderSegment before, BorderSegment after, BorderSegment start, BorderSegment end) { calculateForSegment(width, start, before, end); calculateForSegment(height, before, end, after); calculateForSegment(width, end, after, start); calculateForSegment(height, after, start, before); } public static double calculate(int width, int height, BorderSegment before, BorderSegment after, BorderSegment start, BorderSegment end) { return new CornerScaleCorrectionCalculator(width, height, before, after, start, end) .correctionFactor; } private void calculateForSegment(int width, BorderSegment bpsStart, BorderSegment bpsBefore, BorderSegment bpsEnd) { if (bpsBefore.isSpecified()) { double ellipseExtent = bpsStart.getRadiusEnd() + bpsEnd.getRadiusStart(); if (ellipseExtent > 0) { double thisCorrectionFactor = width / ellipseExtent; if (thisCorrectionFactor < correctionFactor) { correctionFactor = thisCorrectionFactor; } } } } } private void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, boolean startOrBefore, int style, Color color) throws IOException { graphicsPainter.drawBorderLine(x1, y1, x2, y2, horz, startOrBefore, style, color); } private void moveTo(int x, int y) throws IOException { graphicsPainter.moveTo(x, y); } private void lineTo(int x, int y) throws IOException { graphicsPainter.lineTo(x, y); } private void arcTo(final double startAngle, final double endAngle, final int cx, final int cy, final int width, final int height) throws IOException { graphicsPainter.arcTo(startAngle, endAngle, cx, cy, width, height); } private void rotateCoordinates(double angle) throws IOException { graphicsPainter.rotateCoordinates(angle); } private void translateCoordinates(int xTranslate, int yTranslate) throws IOException { graphicsPainter.translateCoordinates(xTranslate, yTranslate); } private void closePath() throws IOException { graphicsPainter.closePath(); } private void clip() throws IOException { graphicsPainter.clip(); } private void saveGraphicsState() throws IOException { graphicsPainter.saveGraphicsState(); } private void restoreGraphicsState() throws IOException { graphicsPainter.restoreGraphicsState(); } }