aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/render/intermediate
diff options
context:
space:
mode:
authorPeter Hancock <phancock@apache.org>2012-10-08 09:24:03 +0000
committerPeter Hancock <phancock@apache.org>2012-10-08 09:24:03 +0000
commite079e8eb1900e3287f28a34a6053b62ae4b241be (patch)
treeb9e15118900d547c1d70ba2a933569c1f50d9057 /src/java/org/apache/fop/render/intermediate
parentaf57408966c6e9a525475f52bdc96add31cb0e51 (diff)
downloadxmlgraphics-fop-e079e8eb1900e3287f28a34a6053b62ae4b241be.tar.gz
xmlgraphics-fop-e079e8eb1900e3287f28a34a6053b62ae4b241be.zip
Increased unit test coverage for the Painters
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_RoundedCorners@1395490 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render/intermediate')
-rw-r--r--src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java102
-rw-r--r--src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java37
-rw-r--r--src/java/org/apache/fop/render/intermediate/BorderPainter.java998
-rw-r--r--src/java/org/apache/fop/render/intermediate/GraphicsPainter.java145
4 files changed, 793 insertions, 489 deletions
diff --git a/src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java b/src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java
new file mode 100644
index 000000000..112808f62
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/ArcToBezierCurveTransformer.java
@@ -0,0 +1,102 @@
+/*
+ * 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$ */
+
+package org.apache.fop.render.intermediate;
+
+import java.io.IOException;
+
+public class ArcToBezierCurveTransformer {
+
+ private final BezierCurvePainter bezierCurvePainter;
+
+ public ArcToBezierCurveTransformer(BezierCurvePainter bezierCurvePainter) {
+ this.bezierCurvePainter = bezierCurvePainter;
+ }
+
+ /**
+ * Draws an arc on the ellipse centered at (cx, cy) with width width and height height
+ * from start angle startAngle (with respect to the x-axis counter-clockwise)
+ * to the end angle endAngle.
+ * The ellipses major axis are assumed to coincide with the coordinate axis.
+ * The current position MUST coincide with the starting position on the ellipse.
+ * @param startAngle the start angle
+ * @param endAngle the end angle
+ * @param cx the x coordinate of the ellipse center
+ * @param cy the y coordinate of the ellipse center
+ * @param width the extent of the ellipse in the x direction
+ * @param height the extent of the ellipse in the y direction
+ * @throws IOException if an I/O error occurs
+ */
+ public void arcTo(final double startAngle, final double endAngle, final int cx, final int cy,
+ final int width, final int height) throws IOException {
+
+ // Implementation follows http://www.spaceroots.org/documents/ellipse/ -
+ // Drawing an elliptical arc using polylines, quadratic or cubic Bézier curves
+ // L. Maisonobe, July 21, 2003
+
+ // Scaling the coordinate system to represent the ellipse as a circle:
+ final double etaStart = Math.atan(Math.tan(startAngle) * width / height)
+ + quadrant(startAngle);
+ final double etaEnd = Math.atan(Math.tan(endAngle) * width / height)
+ + quadrant(endAngle);
+
+ final double sinStart = Math.sin(etaStart);
+ final double cosStart = Math.cos(etaStart);
+ final double sinEnd = Math.sin(etaEnd);
+ final double cosEnd = Math.cos(etaEnd);
+
+ final double p0x = cx + cosStart * width;
+ final double p0y = cy + sinStart * height;
+ final double p3x = cx + cosEnd * width;
+ final double p3y = cy + sinEnd * height;
+
+ double etaDiff = Math.abs(etaEnd - etaStart);
+ double tan = Math.tan((etaDiff) / 2d);
+ final double alpha = Math.sin(etaDiff) * (Math.sqrt(4d + 3d * tan * tan) - 1d) / 3d;
+
+ int order = etaEnd > etaStart ? 1 : -1;
+
+ // p1 = p0 + alpha*(-sin(startAngle), cos(startAngle))
+ final double p1x = p0x - alpha * sinStart * width * order;
+ final double p1y = p0y + alpha * cosStart * height * order;
+
+ // p1 = p3 + alpha*(sin(endAngle), -cos(endAngle))
+ final double p2x = p3x + alpha * sinEnd * width * order;
+ final double p2y = p3y - alpha * cosEnd * height * order;
+
+ //Draw the curve in original coordinate system
+ bezierCurvePainter.cubicBezierTo((int) p1x, (int) p1y, (int) p2x, (int) p2y, (int) p3x, (int) p3y);
+ }
+
+ private double quadrant(double angle) {
+ if (angle <= Math.PI) {
+ if (angle <= Math.PI / 2d) {
+ return 0;
+ } else {
+ return Math.PI;
+ }
+ } else {
+ if (angle > Math.PI * 3d / 2d) {
+ return 2d * Math.PI;
+ } else {
+ return Math.PI;
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java b/src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java
new file mode 100644
index 000000000..92eb334e0
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/BezierCurvePainter.java
@@ -0,0 +1,37 @@
+/*
+ * 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$ */
+
+package org.apache.fop.render.intermediate;
+
+import java.io.IOException;
+
+public interface BezierCurvePainter {
+ /**
+ * Draw a cubic bezier from current position to (p3x, p3y) using the control points
+ * (p1x, p1y) and (p2x, p2y)
+ * @param p1x x coordinate of the first control point
+ * @param p1y y coordinate of the first control point
+ * @param p2x x coordinate of the second control point
+ * @param p2y y coordinate of the second control point
+ * @param p3x x coordinate of the end point
+ * @param p3y y coordinate of the end point
+ * @throws IOException if an I/O error occurs
+ */
+ void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) throws IOException;
+} \ No newline at end of file
diff --git a/src/java/org/apache/fop/render/intermediate/BorderPainter.java b/src/java/org/apache/fop/render/intermediate/BorderPainter.java
index 4104f924c..51dcf5905 100644
--- a/src/java/org/apache/fop/render/intermediate/BorderPainter.java
+++ b/src/java/org/apache/fop/render/intermediate/BorderPainter.java
@@ -20,17 +20,15 @@
package org.apache.fop.render.intermediate;
import java.awt.Color;
-import java.awt.Point;
import java.awt.Rectangle;
import java.io.IOException;
import org.apache.fop.traits.BorderProps;
-import org.apache.fop.traits.RuleStyle;
/**
* This is an abstract base class for handling border painting.
*/
-public abstract class BorderPainter {
+public class BorderPainter {
/** TODO remove before integration*/
public static final String ROUNDED_CORNERS = "fop.round-corners";
@@ -42,6 +40,11 @@ public abstract class BorderPainter {
convention index of top-left, top-right, bottom-right and bottom-left border corners*/
protected static final int TOP_LEFT = 0, TOP_RIGHT = 1, BOTTOM_RIGHT = 2, BOTTOM_LEFT = 3;
+ private final GraphicsPainter graphicsPainter;
+
+ public BorderPainter(GraphicsPainter graphicsPainter) {
+ this.graphicsPainter = graphicsPainter;
+ }
/**
* Draws borders.
@@ -65,7 +68,7 @@ public abstract class BorderPainter {
}
private BorderProps sanitizeBorderProps(BorderProps bps) {
- return bps == null ? bps : bps.width == 0 ? (BorderProps)null : bps;
+ return bps == null ? bps : bps.width == 0 ? (BorderProps) null : bps;
}
/**
@@ -134,11 +137,11 @@ public abstract class BorderPainter {
int sx1a = sx1;
int ex1a = ex1;
- if (bpsTop.mode == BorderProps.COLLAPSE_OUTER) {
- if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsTop)) {
+ if (isCollapseOuter(bpsLeft)) {
sx1a -= clipw[LEFT];
}
- if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsRight)) {
ex1a += clipw[RIGHT];
}
lineTo(sx1a, outery);
@@ -165,11 +168,12 @@ public abstract class BorderPainter {
moveTo(clipx, sy1);
int sy1a = sy1;
int ey1a = ey1;
- if (bpsRight.mode == BorderProps.COLLAPSE_OUTER) {
- if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) {
+
+ if (isCollapseOuter(bpsRight)) {
+ if (isCollapseOuter(bpsTop)) {
sy1a -= clipw[TOP];
}
- if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsBottom)) {
ey1a += clipw[BOTTOM];
}
lineTo(outerx, sy1a);
@@ -196,11 +200,11 @@ public abstract class BorderPainter {
moveTo(ex1, clipy);
int sx1a = sx1;
int ex1a = ex1;
- if (bpsBottom.mode == BorderProps.COLLAPSE_OUTER) {
- if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsBottom)) {
+ if (isCollapseOuter(bpsLeft)) {
sx1a -= clipw[LEFT];
}
- if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsRight)) {
ex1a += clipw[RIGHT];
}
lineTo(ex1a, outery);
@@ -230,11 +234,11 @@ public abstract class BorderPainter {
int sy1a = sy1;
int ey1a = ey1;
- if (bpsLeft.mode == BorderProps.COLLAPSE_OUTER) {
- if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsLeft)) {
+ if (isCollapseOuter(bpsTop)) {
sy1a -= clipw[TOP];
}
- if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) {
+ if (isCollapseOuter(bpsBottom)) {
ey1a += clipw[BOTTOM];
}
lineTo(outerx, ey1a);
@@ -250,6 +254,10 @@ public abstract class BorderPainter {
}
}
+ private boolean isCollapseOuter(BorderProps bp) {
+ return bp != null && bp.isCollapseOuter();
+ }
+
/** TODO merge with drawRectangularBorders?
* @param borderRect the border rectangle
* @param bpsBefore the border specification on the before side
@@ -259,266 +267,274 @@ public abstract class BorderPainter {
* @throws IOException on io exception
* */
protected void drawRoundedBorders(Rectangle borderRect,
- BorderProps bpsBefore, BorderProps bpsAfter,
- BorderProps bpsStart, BorderProps bpsEnd) throws IOException {
-
- bpsBefore = sanitizeBorderProps(bpsBefore);
- bpsAfter = sanitizeBorderProps(bpsAfter);
- bpsStart = sanitizeBorderProps(bpsStart);
- bpsEnd = sanitizeBorderProps(bpsEnd);
-
- boolean[] b = new boolean[] {
- (bpsBefore != null), (bpsEnd != null),
- (bpsAfter != null), (bpsStart != null)};
- if (!b[TOP] && !b[RIGHT] && !b[BOTTOM] && !b[LEFT]) {
+ 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;
}
- int[] bw = new int[] {
- (b[TOP] ? bpsBefore.width : 0),
- (b[RIGHT] ? bpsEnd.width : 0),
- (b[BOTTOM] ? bpsAfter.width : 0),
- (b[LEFT] ? bpsStart.width : 0)};
- int[] clipw = new int[] {
- BorderProps.getClippedWidth(bpsBefore),
- BorderProps.getClippedWidth(bpsEnd),
- BorderProps.getClippedWidth(bpsAfter),
- BorderProps.getClippedWidth(bpsStart)};
-
- final int startx = borderRect.x + clipw[LEFT];
- final int starty = borderRect.y + clipw[TOP];
- final int width = borderRect.width - clipw[LEFT] - clipw[RIGHT];
- final int height = borderRect.height - clipw[TOP] - clipw[BOTTOM];
-
- boolean[] slant = new boolean[] {
- (b[LEFT] && b[TOP]), (b[TOP] && b[RIGHT]),
- (b[RIGHT] && b[BOTTOM]), (b[LEFT] && b[BOTTOM])};
+ 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 esf = cornerScaleFactor(width, height, bpsBefore, bpsAfter, bpsStart, bpsEnd);
+ 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);
+ }
- if (bpsBefore != null) {
+ 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 = (slant[TOP_LEFT] ? bw[LEFT] - clipw[LEFT] : 0);
+ final int sx2 = start.getWidth() - start.getClippedWidth();
final int ex1 = width;
- final int ex2 = (slant[TOP_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1);
- final int outery = -clipw[TOP];
- final int innery = outery + bw[TOP];
- final int clipy = outery + clipw[TOP];
- final int ellipseSBW = bpsStart == null ? 0 : (int)(esf * bpsStart.getRadiusStart());
- final int ellipseSBH = (int)(esf * bpsBefore.getRadiusStart());
- final int ellipseSBX = ellipseSBW;
- final int ellipseSBY = clipy + ellipseSBH;
- final int ellipseBEW = bpsEnd == null ? 0 : (int)(esf * bpsEnd.getRadiusStart());
- final int ellipseBEH = (int)(esf * bpsBefore.getRadiusEnd());
- final int ellipseBEX = ex1 - ellipseBEW;
- final int ellipseBEY = clipy + ellipseBEH;
-
+ final int ex2 = ex1 - end.getWidth() + end.getClippedWidth();
+ final int outery = -before.getClippedWidth();
+ final int innery = outery + before.getWidth();
+ final int ellipseSBRadiusX = (int) (cornerCorrectionFactor * start.getRadiusEnd());
+ final int ellipseSBRadiusY = (int) (cornerCorrectionFactor * before.getRadiusStart());
+ final int ellipseBERadiusX = (int) (cornerCorrectionFactor * end.getRadiusStart());
+ final int ellipseBERadiusY = (int) (cornerCorrectionFactor * before.getRadiusEnd());
saveGraphicsState();
- translateCoordinates(startx, starty);
- drawBorderSegment( sx2, ex1, ex2, outery, innery,
- clipw[LEFT], clipw[RIGHT],
- ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH,
- ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH,
- bpsBefore, bpsStart, bpsEnd
- );
+ 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();
}
+ }
- if (bpsStart != null) {
- //Let x increase in the AFTER->BEFORE direction
- final int sx2 = (slant[BOTTOM_LEFT] ? bw[BOTTOM] - clipw[BOTTOM] : 0);
- final int ex1 = height;
- final int ex2 = (slant[TOP_LEFT] ? ex1 - bw[TOP] + clipw[TOP] : ex1);
- final int outery = -clipw[LEFT];
- final int innery = outery + bw[LEFT];
- final int clipy = outery + clipw[LEFT];
- final int ellipseSBW = bpsAfter == null ? 0 : (int)(esf * bpsAfter.getRadiusStart());
- final int ellipseSBH = (int)(esf * bpsStart.getRadiusEnd());
- final int ellipseSBX = ellipseSBW;
- final int ellipseSBY = clipy + ellipseSBH;
- final int ellipseBEW = bpsBefore == null ? 0 : (int)(esf * bpsBefore.getRadiusStart());
- final int ellipseBEH = (int)(esf * bpsStart.getRadiusStart());
- final int ellipseBEX = ex1 - ellipseBEW;
- final int ellipseBEY = clipy + ellipseBEH;
+ private static BorderSegment borderSegmentForBefore(BorderProps before) {
+ return AbstractBorderSegment.asBorderSegment(before);
+ }
- saveGraphicsState();
- translateCoordinates(startx, starty + height);
- rotateCoordinates(Math.PI * 3d / 2d);
- drawBorderSegment( sx2, ex1, ex2, outery, innery,
- clipw[BOTTOM], clipw[TOP],
- ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH,
- ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH,
- bpsStart, bpsAfter, bpsBefore
- );
- restoreGraphicsState();
+ 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);
+ }
- if (bpsAfter != null) {
- //Let x increase in the START->END direction
- final int sx2 = (slant[BOTTOM_LEFT] ? bw[LEFT] - clipw[LEFT] : 0);
- final int ex1 = width;
- final int ex2 = (slant[BOTTOM_RIGHT] ? ex1 - bw[RIGHT] + clipw[RIGHT] : ex1);
- final int outery = -clipw[BOTTOM];
- final int innery = outery + bw[BOTTOM];
- final int clipy = outery + clipw[BOTTOM];
- final int ellipseSBW = bpsStart == null ? 0 : (int)(esf * bpsStart.getRadiusEnd());
- final int ellipseSBH = (int)(esf * bpsAfter.getRadiusStart());
- final int ellipseSBX = ellipseSBW;
- final int ellipseSBY = clipy + ellipseSBH;
- final int ellipseBEW = bpsEnd == null ? 0 : (int)(esf * bpsEnd.getRadiusEnd());
- final int ellipseBEH = (int)(esf * bpsAfter.getRadiusEnd());
- final int ellipseBEX = ex1 - ellipseBEW;
- final int ellipseBEY = clipy + ellipseBEH;
+ private interface BorderSegment {
- saveGraphicsState();
- translateCoordinates(startx, starty + height);
- scaleCoordinates(1, -1);
- drawBorderSegment( sx2, ex1, ex2, outery, innery,
- clipw[LEFT], clipw[RIGHT],
- ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH,
- ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH,
- bpsAfter, bpsStart, bpsEnd
- );
- restoreGraphicsState();
- }
+ Color getColor();
- if (bpsEnd != null) {
- //Let x increase in the BEFORE-> AFTER direction
- final int sx2 = (slant[TOP_RIGHT] ? bw[TOP] - clipw[TOP] : 0);
- final int ex1 = height;
- final int ex2 = (slant[BOTTOM_RIGHT] ? ex1 - bw[BOTTOM] + clipw[BOTTOM] : ex1);
- final int outery = -clipw[RIGHT];
- final int innery = outery + bw[RIGHT];
- final int clipy = outery + clipw[RIGHT];
- final int ellipseSBW = bpsBefore == null ? 0 : (int)(esf * bpsBefore.getRadiusEnd());
- final int ellipseSBH = (int)(esf * bpsEnd.getRadiusStart());
- final int ellipseSBX = ellipseSBW;
- final int ellipseSBY = clipy + ellipseSBH;
- final int ellipseBEW = bpsAfter == null ? 0 : (int)(esf * bpsAfter.getRadiusEnd());
- final int ellipseBEH = (int)(esf * bpsEnd.getRadiusEnd());
- final int ellipseBEX = ex1 - ellipseBEW;
- final int ellipseBEY = clipy + ellipseBEH;
+ int getStyle();
- saveGraphicsState();
- translateCoordinates(startx + width, starty);
- rotateCoordinates(Math.PI / 2d);
- drawBorderSegment( sx2, ex1, ex2, outery, innery,
- clipw[TOP], clipw[BOTTOM],
- ellipseSBX, ellipseSBY, ellipseSBW, ellipseSBH,
- ellipseBEX, ellipseBEY, ellipseBEW, ellipseBEH,
- bpsEnd, bpsBefore, bpsAfter
- );
- restoreGraphicsState();
- }
+ int getWidth();
+
+ int getClippedWidth();
+
+ int getRadiusStart();
+
+ int getRadiusEnd();
+
+ boolean isCollapseOuter();
+
+ boolean isSpecified();
}
- /** TODO collect parameters into useful data structures*/
- private void drawBorderSegment(final int sx2, final int ex1, final int ex2,
- final int outery, final int innery,
- final int clipWidthStart, final int clipWidthEnd,
- final int ellipseSBX, final int ellipseSBY,
- final int ellipseSBRadiusX, final int ellipseSBRadiusY,
- final int ellipseBEX, final int ellipseBEY,
- final int ellipseBERadiusX, final int ellipseBERadiusY,
- final BorderProps bpsThis, final BorderProps bpsStart, final BorderProps bpsEnd )
- throws IOException {
+ private abstract static class AbstractBorderSegment implements BorderSegment {
- int sx1a = 0;
- int ex1a = ex1;
+ 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);
+ }
- if (ellipseSBRadiusX != 0 && ellipseSBRadiusY != 0 ) {
+ public boolean isSpecified() {
+ return !(this instanceof NullBorderSegment);
+ }
- final double[] joinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX,
- ellipseSBRadiusY, (double)innery / sx2);
+ private static class WrappingBorderSegment extends AbstractBorderSegment {
- final double outerJoinPointX = joinMetrics[0];
- final double outerJoinPointY = joinMetrics[1];
- final double sbJoinAngle = joinMetrics[2];
+ protected final BorderProps borderProps;
- moveTo((int)outerJoinPointX, (int)outerJoinPointY);
- arcTo(Math.PI + sbJoinAngle, Math.PI * 3 / 2,
- ellipseSBX, ellipseSBY, ellipseSBRadiusX, ellipseSBRadiusY);
- } else {
+ private final int clippedWidth;
- moveTo(0, 0);
+ WrappingBorderSegment(BorderProps borderProps) {
+ this.borderProps = borderProps;
+ clippedWidth = BorderProps.getClippedWidth(borderProps);
+ }
- if (bpsThis.mode == BorderProps.COLLAPSE_OUTER) {
+ public int getStyle() {
+ return borderProps.style;
+ }
- if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) {
- sx1a -= clipWidthStart;
- }
- if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) {
- ex1a += clipWidthEnd;
- }
+ public Color getColor() {
+ return borderProps.color;
+ }
- lineTo(sx1a, outery);
- lineTo(ex1a, outery);
+ public int getWidth() {
+ return borderProps.width;
}
- }
- if (ellipseBERadiusX != 0 && ellipseBERadiusY != 0) {
+ public int getClippedWidth() {
+ return clippedWidth;
+ }
+ public boolean isCollapseOuter() {
+ return borderProps.isCollapseOuter();
+ }
- final double[] outerJoinMetrics = getCornerBorderJoinMetrics(
- ellipseBERadiusX, ellipseBERadiusY, (double)innery / (ex1 - ex2));
- final double beJoinAngle = Math.PI / 2 - outerJoinMetrics[2];
+ public int getRadiusStart() {
+ return borderProps.getRadiusStart();
+ }
- lineTo(ellipseBEX, 0);
- arcTo( Math.PI * 3 / 2 , Math.PI * 3 / 2 + beJoinAngle,
- ellipseBEX, ellipseBEY, ellipseBERadiusX, ellipseBERadiusY);
+ public int getRadiusEnd() {
+ return borderProps.getRadiusEnd();
+ }
+ }
- if (ellipseBEX < ex2 && ellipseBEY > innery) {
+ private static class FlippedBorderSegment extends WrappingBorderSegment {
- final double[] innerJoinMetrics = getCornerBorderJoinMetrics(
- (double)ex2 - ellipseBEX, (double)ellipseBEY - innery,
- (double)innery / (ex1 - ex2));
- final double innerJoinPointX = innerJoinMetrics[0];
- final double innerJoinPointY = innerJoinMetrics[1];
- final double beInnerJoinAngle = Math.PI / 2 - innerJoinMetrics[2];
+ FlippedBorderSegment(BorderProps borderProps) {
+ super(borderProps);
+ }
- 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);
+ public int getRadiusStart() {
+ return borderProps.getRadiusEnd();
}
- } else {
- lineTo(ex1, 0);
- lineTo(ex2, innery);
+ public int getRadiusEnd() {
+ return borderProps.getRadiusStart();
+ }
}
- if (ellipseSBRadiusX == 0) {
- lineTo(sx2, innery);
- } else {
- if (ellipseSBX > sx2 && ellipseSBY > innery) {
+ private static final class NullBorderSegment extends AbstractBorderSegment {
+ public static final NullBorderSegment INSTANCE = new NullBorderSegment();
- final double[] innerJoinMetrics = getCornerBorderJoinMetrics(ellipseSBRadiusX - sx2,
- ellipseSBRadiusY - innery, (double)innery / sx2);
+ private NullBorderSegment() {
+ }
- final double sbInnerJoinAngle = innerJoinMetrics[2];
+ public int getWidth() {
+ return 0;
+ }
- lineTo(ellipseSBX, innery);
- arcTo(Math.PI * 3 / 2, sbInnerJoinAngle + Math.PI,
- ellipseSBX, ellipseSBY, ellipseSBX - sx2, ellipseSBY - innery);
- } else {
- lineTo(sx2, innery);
+ public int getClippedWidth() {
+ return 0;
}
- }
- closePath();
- clip();
+ public int getRadiusStart() {
+ return 0;
+ }
- if (ellipseBERadiusY == 0 && ellipseSBRadiusY == 0) {
- drawBorderLine(sx1a, outery, ex1a, innery, true, true,
- bpsThis.style, bpsThis.color);
+ 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 {
- int innerFillY = Math.max(Math.max(ellipseBEY, ellipseSBY), innery);
- drawBorderLine(sx1a, outery, ex1a, innerFillY, true, true,
- bpsThis.style, bpsThis.color);
+ return new double[]{0, ellipseCenterY, 0};
}
}
@@ -527,8 +543,8 @@ public abstract class BorderPainter {
double x = ellipseCenterY * ellipseCenterX * (
ellipseCenterY + ellipseCenterX * borderWidthRatio
- Math.sqrt(2d * ellipseCenterX * ellipseCenterY * borderWidthRatio)
- ) / (ellipseCenterY * ellipseCenterY
- + ellipseCenterX * ellipseCenterX * borderWidthRatio * borderWidthRatio);
+ ) / (ellipseCenterY * ellipseCenterY
+ + ellipseCenterX * ellipseCenterX * borderWidthRatio * borderWidthRatio);
double y = borderWidthRatio * x;
return new double[]{x, y, Math.atan((ellipseCenterY - y) / (ellipseCenterX - x))};
}
@@ -545,331 +561,335 @@ public abstract class BorderPainter {
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();
+ }
- int fullWidth = width + ( bpsStart == null ? 0 : bpsStart.width )
- + (bpsStart == null ? 0 : bpsStart.width);
- int fullHeight = height + ( bpsBefore == null ? 0 : bpsBefore.width )
- + (bpsAfter == null ? 0 : bpsAfter.width);
-
- double esf = cornerScaleFactor( fullWidth, fullHeight, bpsBefore, bpsAfter,
- bpsStart, bpsEnd);
-
- int ellipseSS = 0;
- int ellipseBS = 0;
- int ellipseBE = 0;
- int ellipseES = 0;
- int ellipseEE = 0;
- int ellipseAE = 0;
- int ellipseAS = 0;
- int ellipseSE = 0;
- if (bpsBefore != null && bpsBefore.getRadiusStart() > 0
- && bpsStart != null && bpsStart.getRadiusStart() > 0) {
- ellipseSS = Math.max((int)(bpsStart.getRadiusStart() * esf) - bpsStart.width, 0);
- ellipseBS = Math.max((int)(bpsBefore.getRadiusStart() * esf) - bpsBefore.width, 0);
+ /**
+ * 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 */
+ BEFORE_END(Math.PI * 3 / 2, 0),
+ /** The end-after angles */
+ END_AFTER(0, Math.PI / 2),
+ /** The after-start angles*/
+ AFTER_START(Math.PI / 2, Math.PI),
+ /** 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 */
+ private final double start;
+
+ /** 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;
}
- if (bpsBefore != null && bpsBefore.getRadiusEnd() > 0
- && bpsEnd != null && bpsEnd.getRadiusStart() > 0) {
- ellipseBE = Math.max((int)(bpsBefore.getRadiusEnd() * esf) - bpsBefore.width, 0);
- ellipseES = Math.max((int)(bpsEnd.getRadiusStart() * esf) - bpsEnd.width, 0);
- }
+ }
- if (bpsEnd != null && bpsEnd.getRadiusEnd() > 0
- && bpsAfter != null && bpsAfter.getRadiusEnd() > 0) {
- ellipseEE = Math.max((int)(bpsEnd.getRadiusEnd() * esf) - bpsEnd.width, 0);
- ellipseAE = Math.max((int)(bpsAfter.getRadiusEnd() * esf) - bpsAfter.width, 0);
- }
+ private static final class Corner {
- if (bpsAfter != null && bpsAfter.getRadiusStart() > 0
- && bpsStart != null && bpsStart.getRadiusEnd() > 0) {
- ellipseAS = Math.max((int)(bpsAfter.getRadiusStart() * esf) - bpsAfter.width, 0);
- ellipseSE = Math.max((int)(bpsStart.getRadiusEnd() * esf) - bpsStart.width, 0);
- }
+ private static final Corner SQUARE = new Corner(0, 0, null, 0, 0, 0, 0);
- // Draw clipping region in the order: Before->End->After->Start
- moveTo(startx + ellipseSS, starty);
+ /** The radius of the elliptic corner in the x direction */
+ protected final int radiusX;
- lineTo(startx + width - ellipseES, starty);
+ /** The radius of the elliptic corner in the y direction */
+ protected final int radiusY;
- if (ellipseBE > 0 && ellipseES > 0) {
- arcTo(Math.PI * 3 / 2, Math.PI * 2,
- startx + width - ellipseES, starty + ellipseBE, ellipseES, ellipseBE);
- }
+ /** The start and end angles of the corner ellipse */
+ private final CornerAngles angles;
- lineTo(startx + width, starty + height - ellipseAE);
+ /** The offset in the x direction of the center of the ellipse relative to the starting point */
+ private final int centerX;
- if (ellipseEE > 0 && ellipseAE > 0) {
- arcTo(0, Math.PI / 2, startx + width - ellipseEE,
- starty + height - ellipseAE, ellipseEE, ellipseAE);
- }
+ /** The offset in the y direction of the center of the ellipse relative to the starting point */
+ private final int centerY;
- lineTo(startx + ellipseSE, starty + height);
+ /** The value in the x direction that the corner extends relative to the starting point */
+ private final int incrementX;
- if (ellipseSE > 0 && ellipseAS > 0) {
- arcTo( Math.PI / 2, Math.PI, startx + ellipseSE,
- starty + height - ellipseAS, ellipseSE, ellipseAS);
- }
+ /** The value in the y direction that the corner extends relative to the starting point */
+ private final int incrementY;
- lineTo( startx, starty + ellipseBS);
+ 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;
+ }
- if (ellipseSS > 0 && ellipseBS > 0) {
- arcTo( Math.PI, Math.PI * 3 / 2,
- startx + ellipseSS, starty + ellipseBS, ellipseSS, ellipseBS);
+ private static int extentFromRadiusStart(BorderSegment border, double correctionFactor) {
+ return extentFromRadius(border.getRadiusStart(), border, correctionFactor);
}
- clip();
+ 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);
+ }
- /**
- * TODO javadocs
- * If an ellipse radii exceed the border edge length then all ellipses must be rescaled.
- */
- protected double cornerScaleFactor(int width, int height,
- BorderProps bpsBefore, BorderProps bpsAfter,
- BorderProps bpsStart, BorderProps bpsEnd) {
- // Ellipse scale factor
- double esf = 1d;
-
- if (bpsBefore != null) {
- double ellipseExtent = (bpsStart == null ? 0 : bpsStart.getRadiusStart())
- + (bpsEnd == null ? 0 : bpsEnd.getRadiusStart());
-
- if (ellipseExtent > 0) {
- double f = width / ellipseExtent;
- if (f < esf) {
- esf = f;
- }
+ 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);
}
- if (bpsStart != null) {
- double ellipseExtent = (bpsAfter == null ? 0 : bpsAfter.getRadiusStart())
- + (bpsBefore == null ? 0 : bpsBefore.getRadiusStart());
-
- if (ellipseExtent > 0) {
- double f = height / ellipseExtent;
- if ( f < esf) {
- esf = f;
- }
+ 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);
}
- if (bpsAfter != null) {
- double ellipseExtent = (bpsStart == null ? 0 : bpsStart.getRadiusEnd())
- + (bpsEnd == null ? 0 : bpsEnd.getRadiusEnd());
-
- if (ellipseExtent > 0) {
- double f = width / ellipseExtent;
- if (f < esf) {
- esf = f;
- }
+ 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);
}
- if (bpsEnd != null) {
- double ellipseExtent = (bpsAfter == null ? 0 : bpsAfter.getRadiusEnd())
- + (bpsBefore == null ? 0 : bpsBefore.getRadiusEnd());
-
- if (ellipseExtent > 0) {
- double f = height / ellipseExtent;
- if (f < esf) {
- esf = f;
- }
+ 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);
}
-
- return esf;
}
/**
- * Draws a border line.
- * @param x1 X coordinate of the upper left corner
- * of the line's bounding rectangle (in millipoints)
- * @param y1 start Y coordinate of the upper left corner
- * of the line's bounding rectangle (in millipoints)
- * @param x2 end X coordinate of the lower right corner
- * of the line's bounding rectangle (in millipoints)
- * @param y2 end y coordinate of the lower right corner
- * of the line's bounding rectangle (in millipoints)
- * @param horz true if it is a horizontal line
- * @param startOrBefore true if the line is the start or end edge of a border box
- * @param style the border style
- * @param color the border color
- * @throws IOException if an I/O error occurs
+ * 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
*/
- protected abstract void drawBorderLine( // CSOK: ParameterNumber
- int x1, int y1, int x2, int y2,
- boolean horz, boolean startOrBefore, int style, Color color) throws IOException;
+ private final class PathPainter {
- /**
- * Draws a line/rule.
- * @param start start point (coordinates in millipoints)
- * @param end end point (coordinates in millipoints)
- * @param width width of the line
- * @param color the line color
- * @param style the rule style
- * @throws IOException if an I/O error occurs
- */
- public abstract void drawLine(Point start, Point end,
- int width, Color color, RuleStyle style) throws IOException;
+ /** Current x position */
+ private int x;
- /**
- * Moves the cursor to the given coordinate.
- * @param x the X coordinate (in millipoints)
- * @param y the Y coordinate (in millipoints)
- * @throws IOException if an I/O error occurs
- */
- protected abstract void moveTo(int x, int y) throws IOException;
+ /** Current y position */
+ private int y;
- /**
- * Draws a line from the current cursor position to the given coordinates.
- * @param x the X coordinate (in millipoints)
- * @param y the Y coordinate (in millipoints)
- * @throws IOException if an I/O error occurs
- */
- protected abstract void lineTo(int x, int y) throws IOException;
+ PathPainter(int x, int y) throws IOException {
+ moveTo(x, y);
+ }
- /**
- * Draw a cubic bezier from current position to (p3x, p3y) using the control points
- * (p1x, p1y) and (p2x, p2y)
- * @param p1x x coordinate of the first control point
- * @param p1y y coordinate of the first control point
- * @param p2x x coordinate of the second control point
- * @param p2y y coordinate of the second control point
- * @param p3x x coordinate of the end point
- * @param p3y y coordinate of the end point
- * @throws IOException if an I/O error occurs
- */
- protected abstract void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y)
- throws IOException;
+ private void moveTo(int x, int y) throws IOException {
+ this.x += x;
+ this.y += y;
+ BorderPainter.this.moveTo(this.x, this.y);
+ }
- /**
- * Draws an arc on the ellipse centered at (cx, cy) with width width and height height
- * from start angle startAngle (with respect to the x-axis counter-clockwise)
- * to the end angle endAngle.
- * The ellipses major axis are assumed to coincide with the coordinate axis.
- * The current position MUST coincide with the starting position on the ellipse.
- * @param startAngle the start angle
- * @param endAngle the end angle
- * @param cx the x coordinate of the ellipse center
- * @param cy the y coordinate of the ellipse center
- * @param width the extent of the ellipse in the x direction
- * @param height the extent of the ellipse in the y direction
- * @throws IOException if an I/O error occurs
- */
- protected void arcTo(final double startAngle, final double endAngle, final int cx, final int cy,
- final int width, final int height)
- throws IOException {
+ public PathPainter lineTo(int x, int y) throws IOException {
+ this.x += x;
+ this.y += y;
+ BorderPainter.this.lineTo(this.x, this.y);
+ return this;
+ }
- // Implementation follows http://www.spaceroots.org/documents/ellipse/ -
- // Drawing an elliptical arc using polylines, quadratic or cubic Bézier curves
- // L. Maisonobe, July 21, 2003
+ public PathPainter lineHorizTo(int x) throws IOException {
+ return lineTo(x, 0);
+ }
- // Scaling the coordinate system to represent the ellipse as a circle:
- final double etaStart = Math.atan(Math.tan(startAngle) * width / height)
- + quadrant(startAngle);
- final double etaEnd = Math.atan(Math.tan(endAngle) * width / height)
- + quadrant(endAngle);
+ public PathPainter lineVertTo(int y) throws IOException {
+ return lineTo(0, y);
+ }
- final double sinStart = Math.sin(etaStart);
- final double cosStart = Math.cos(etaStart);
- final double sinEnd = Math.sin(etaEnd);
- final double cosEnd = Math.cos(etaEnd);
+ 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;
+ }
+ }
- final double p0x = cx + cosStart * width;
- final double p0y = cy + sinStart * height;
- final double p3x = cx + cosEnd * width;
- final double p3y = cy + sinEnd * height;
+ /**
+ * 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.
+ *
+ * @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);
+ }
- double etaDiff = Math.abs(etaEnd - etaStart);
- double tan = Math.tan((etaDiff) / 2d);
- final double alpha = Math.sin(etaDiff) * (Math.sqrt(4d + 3d * tan * tan) - 1d) / 3d;
+ private static final class CornerScaleCorrectionCalculator {
+ private double correctionFactor = 1;
- int order = etaEnd > etaStart ? 1 : -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);
+ }
- // p1 = p0 + alpha*(-sin(startAngle), cos(startAngle))
- final double p1x = p0x - alpha * sinStart * width * order;
- final double p1y = p0y + alpha * cosStart * height * order;
+ 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;
+ }
- // p1 = p3 + alpha*(sin(endAngle), -cos(endAngle))
- final double p2x = p3x + alpha * sinEnd * width * order;
- final double p2y = p3y - alpha * cosEnd * height * order;
+ 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;
+ }
+ }
+ }
+ }
+ }
- //Draw the curve in original coordinate system
- cubicBezierTo((int)p1x, (int)p1y, (int)p2x, (int)p2y, (int)p3x, (int)p3y);
+ 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 double quadrant(double angle) {
- if (angle <= Math.PI ) {
- if (angle <= Math.PI / 2d) {
- return 0;
- } else {
- return Math.PI;
- }
- } else {
- if (angle > Math.PI * 3d / 2d) {
- return 2d * Math.PI;
- } else {
- return Math.PI;
- }
- }
+ private void moveTo(int x, int y) throws IOException {
+ graphicsPainter.moveTo(x, y);
}
- /**
- * Rotate the coordinate frame
- * @param angle angle in radians to rotate the coordinate frame
- * @throws IOException if an I/O error occurs
- */
- protected abstract void rotateCoordinates(double angle) throws IOException;
+ private void lineTo(int x, int y) throws IOException {
+ graphicsPainter.lineTo(x, y);
+ }
- /**
- * Translate the coordinate frame
- * @param xTranslate translation in the x direction
- * @param yTranslate translation in the y direction
- * @throws IOException if an I/O error occurs
- */
- protected abstract void translateCoordinates(int xTranslate, int yTranslate) throws IOException;
+ 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);
+ }
- /**
- * Scale the coordinate frame
- * @param xScale scale factor in the x direction
- * @param yScale scale factor in the y direction
- * @throws IOException if an I/O error occurs
- */
- protected abstract void scaleCoordinates(float xScale, float yScale) throws IOException;
+ private void rotateCoordinates(double angle) throws IOException {
+ graphicsPainter.rotateCoordinates(angle);
+ }
+ private void translateCoordinates(int xTranslate, int yTranslate) throws IOException {
+ graphicsPainter.translateCoordinates(xTranslate, yTranslate);
+ }
- /**
- * Closes the current path.
- * @throws IOException if an I/O error occurs
- */
- protected abstract void closePath() throws IOException;
+ private void closePath() throws IOException {
+ graphicsPainter.closePath();
+ }
- /**
- * Reduces the current clipping region to the current path.
- * @throws IOException if an I/O error occurs
- */
- protected abstract void clip() throws IOException;
+ private void clip() throws IOException {
+ graphicsPainter.clip();
+ }
- /**
- * Save the graphics state on the stack.
- * @throws IOException if an I/O error occurs
- */
- protected abstract void saveGraphicsState() throws IOException;
+ private void saveGraphicsState() throws IOException {
+ graphicsPainter.saveGraphicsState();
+ }
- /**
- * Restore the last graphics state from the stack.
- * @throws IOException if an I/O error occurs
- */
- protected abstract void restoreGraphicsState() throws IOException;
+ private void restoreGraphicsState() throws IOException {
+ graphicsPainter.restoreGraphicsState();
+ }
}
diff --git a/src/java/org/apache/fop/render/intermediate/GraphicsPainter.java b/src/java/org/apache/fop/render/intermediate/GraphicsPainter.java
new file mode 100644
index 000000000..369cacd43
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/GraphicsPainter.java
@@ -0,0 +1,145 @@
+/*
+ * 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$ */
+
+package org.apache.fop.render.intermediate;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.io.IOException;
+
+import org.apache.fop.traits.RuleStyle;
+
+/**
+ * Used primarily by {@link BorderPainter}, implementations are created for rendering
+ * primitive graphical operations.
+ *
+ */
+public interface GraphicsPainter {
+
+ /**
+ * Draws a border line.
+ * @param x1 X coordinate of the upper left corner
+ * of the line's bounding rectangle (in millipoints)
+ * @param y1 start Y coordinate of the upper left corner
+ * of the line's bounding rectangle (in millipoints)
+ * @param x2 end X coordinate of the lower right corner
+ * of the line's bounding rectangle (in millipoints)
+ * @param y2 end y coordinate of the lower right corner
+ * of the line's bounding rectangle (in millipoints)
+ * @param horz true if it is a horizontal line
+ * @param startOrBefore true if the line is the start or end edge of a border box
+ * @param style the border style
+ * @param color the border color
+ * @throws IOException if an I/O error occurs
+ */
+ void drawBorderLine(int x1, int y1, int x2, int y2,
+ boolean horz, boolean startOrBefore, int style, Color color) throws IOException;
+
+ /**
+ * Draws a line/rule.
+ * @param start start point (coordinates in millipoints)
+ * @param end end point (coordinates in millipoints)
+ * @param width width of the line
+ * @param color the line color
+ * @param style the rule style
+ * @throws IOException if an I/O error occurs
+ */
+ void drawLine(Point start, Point end,
+ int width, Color color, RuleStyle style) throws IOException;
+
+ /**
+ * Moves the cursor to the given coordinate.
+ * @param x the X coordinate (in millipoints)
+ * @param y the Y coordinate (in millipoints)
+ * @throws IOException if an I/O error occurs
+ */
+ void moveTo(int x, int y) throws IOException;
+
+ /**
+ * Draws a line from the current cursor position to the given coordinates.
+ * @param x the X coordinate (in millipoints)
+ * @param y the Y coordinate (in millipoints)
+ * @throws IOException if an I/O error occurs
+ */
+ void lineTo(int x, int y) throws IOException;
+
+ /**
+ * Draws an arc on the ellipse centered at (cx, cy) with width width and height height
+ * from start angle startAngle (with respect to the x-axis counter-clockwise)
+ * to the end angle endAngle.
+ * The ellipses major axis are assumed to coincide with the coordinate axis.
+ * The current position MUST coincide with the starting position on the ellipse.
+ * @param startAngle the start angle
+ * @param endAngle the end angle
+ * @param cx the x coordinate of the ellipse center
+ * @param cy the y coordinate of the ellipse center
+ * @param width the extent of the ellipse in the x direction
+ * @param height the extent of the ellipse in the y direction
+ * @throws IOException if an I/O error occurs
+ */
+ void arcTo(final double startAngle, final double endAngle, final int cx, final int cy,
+ final int width, final int height) throws IOException;
+
+ /**
+ * Rotate the coordinate frame
+ * @param angle angle in radians to rotate the coordinate frame
+ * @throws IOException if an I/O error occurs
+ */
+ void rotateCoordinates(double angle) throws IOException;
+
+ /**
+ * Translate the coordinate frame
+ * @param xTranslate translation in the x direction
+ * @param yTranslate translation in the y direction
+ * @throws IOException if an I/O error occurs
+ */
+ void translateCoordinates(int xTranslate, int yTranslate) throws IOException;
+
+ /**
+ * Scale the coordinate frame
+ * @param xScale scale factor in the x direction
+ * @param yScale scale factor in the y direction
+ * @throws IOException if an I/O error occurs
+ */
+ void scaleCoordinates(float xScale, float yScale) throws IOException;
+
+ /**
+ * Closes the current path.
+ * @throws IOException if an I/O error occurs
+ */
+ void closePath() throws IOException;
+
+ /**
+ * Reduces the current clipping region to the current path.
+ * @throws IOException if an I/O error occurs
+ */
+ void clip() throws IOException;
+
+ /**
+ * Save the graphics state on the stack.
+ * @throws IOException if an I/O error occurs
+ */
+ void saveGraphicsState() throws IOException;
+
+ /**
+ * Restore the last graphics state from the stack.
+ * @throws IOException if an I/O error occurs
+ */
+ void restoreGraphicsState() throws IOException;
+}