/*
* 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.
*/
package org.apache.fop.render.intermediate;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.IOException;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import org.apache.fop.fo.Constants;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.BorderProps.Mode;
public class BorderPainterTestCase {
private static final BorderProps BORDER_PROPS = new BorderProps(Constants.EN_SOLID, 10, 50, 50,
Color.BLACK, BorderProps.Mode.SEPARATE);
@Test
public void clipBackground() throws Exception {
// Rectangular borders
test(new ClipBackgroundTester(0, 0, 10, 10));
test(new ClipBackgroundTester(5, 10, 10, 10));
test(new ClipBackgroundTester(0, 0, 10, 10).setBorderWidth(1));
test(new ClipBackgroundTester(0, 0, 10, 10).beforeBorder().setWidth(10).tester());
// Rounded corners
test(new ClipBackgroundTester(0, 0, 10, 10).setEndBefore(1, 1));
test(new ClipBackgroundTester(0, 0, 10, 10).setEndAfter(1, 1));
test(new ClipBackgroundTester(0, 0, 10, 10).setStartAfter(1, 1));
test(new ClipBackgroundTester(0, 0, 10, 10).setStartBefore(1, 1));
test(new ClipBackgroundTester(0, 0, 100, 100)
.setCornerRadii(10)
.beforeBorder().setWidth(5).tester()
.startBorder().setWidth(5).tester());
test(new ClipBackgroundTester(0, 0, 100, 100)
.setCornerRadii(10)
.beforeBorder().setWidth(10).tester()
.startBorder().setWidth(10).tester());
test(new ClipBackgroundTester(0, 0, 100, 100)
.setCornerRadii(10)
.beforeBorder().setWidth(5).tester());
test(new ClipBackgroundTester(0, 0, 100, 100)
.setCornerRadii(10)
.setStartBefore(10, 10)
.beforeBorder().setWidth(10).tester());
}
private void test(BorderPainterTester<?> tester) throws IOException {
tester.test();
}
@Test (expected = IFException.class)
public void drawBordersThrowsIFException() throws Exception {
GraphicsPainter graphicsPainter = mock(GraphicsPainter.class);
doThrow(new IOException()).when(graphicsPainter).saveGraphicsState();
new BorderPainter(graphicsPainter).drawBorders(new Rectangle(0, 0, 1000, 1000), BORDER_PROPS,
BORDER_PROPS, BORDER_PROPS, BORDER_PROPS, Color.WHITE);
}
@Test
public void testDrawRectangularBorders() throws IOException {
test(new DrawRectangularBordersTester(0, 0, 1000, 1000).setBorderWidth(10));
test(new DrawRectangularBordersTester(0, 0, 1000, 1000));
test(new DrawRectangularBordersTester(0, 0, 1000, 1000).setBorderWidth(10)
.beforeBorder().setWidth(0).tester());
}
@Test
public void testDrawRectangularBordersWithNullBorders() throws IOException, IFException {
GraphicsPainter graphicsPainter = mock(GraphicsPainter.class);
BorderProps nullBorderProps = null;
new BorderPainter(graphicsPainter).drawRectangularBorders(new Rectangle(0, 0, 1000, 1000),
nullBorderProps, nullBorderProps, nullBorderProps, nullBorderProps);
verifyZeroInteractions(graphicsPainter);
}
@Test
public void drawRoundedBorders() throws Exception {
test(new DrawRoundedBordersTester(0, 0, 10, 10).setBorderWidth(10));
test(new DrawRoundedBordersTester(0, 0, 10, 10).beforeBorder().setWidth(10).tester());
test(new DrawRoundedBordersTester(0, 0, 10, 10).setBorderWidth(10).setCornerRadii(5)
.beforeBorder().setWidth(0).tester());
test(new DrawRoundedBordersTester(0, 0, 10, 10)
.beforeBorder().setWidth(10).tester().endBorder().setWidth(10).tester());
test(new DrawRoundedBordersTester(0, 0, 100, 100).setBorderWidth(15).setCornerRadii(10));
test(new DrawRoundedBordersTester(0, 0, 100, 100).setBorderWidth(15).setCornerRadii(10)
.beforeBorder().setWidth(5).tester());
test(new DrawRoundedBordersTester(0, 0, 60, 60).setBorderWidth(4).setCornerRadii(30));
}
@Test
public void testDrawRoundedBordersWithNullBorders() throws IOException, IFException {
GraphicsPainter graphicsPainter = mock(GraphicsPainter.class);
BorderProps nullBorderProps = null;
new BorderPainter(graphicsPainter).drawRoundedBorders(new Rectangle(0, 0, 1000, 1000),
nullBorderProps, nullBorderProps, nullBorderProps, nullBorderProps);
verifyZeroInteractions(graphicsPainter);
}
@Test
public void testCalculateCornerCorrectionFactor() {
calculateCornerCorrectionFactorHelper(30000, 500000);
calculateCornerCorrectionFactorHelper(30000, 10000);
}
private void calculateCornerCorrectionFactorHelper(int radius, int rectWidth) {
BorderProps borderProps = new BorderProps(Constants.EN_SOLID, 4000, radius, radius, Color.BLACK,
BorderProps.Mode.SEPARATE);
int rectHeight = rectWidth + 100;
double expected = (2 * radius > rectWidth) ? (double) rectWidth / (2 * radius) : 1.0;
double actual = BorderPainter.calculateCornerCorrectionFactor(rectWidth, rectHeight, borderProps,
borderProps, borderProps, borderProps);
assertEquals(expected, actual, 0);
}
private abstract static class BorderPainterTester<T extends BorderPainterTester<?>> {
protected final Rectangle borderExtent;
protected BorderProps before;
protected BorderProps after;
protected BorderProps start;
protected BorderProps end;
protected final GraphicsPainter graphicsPainter;
protected final BorderPainter sut;
private final BorderPropsBuilder<T> beforeBuilder;
private final BorderPropsBuilder<T> afterBuilder;
private final BorderPropsBuilder<T> startBuilder;
private final BorderPropsBuilder<T> endBuilder;
public BorderPainterTester(int xOrigin, int yOrigin, int width, int height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Cannot test degenerate borders");
}
beforeBuilder = new BorderPropsBuilder<T>(getThis());
afterBuilder = new BorderPropsBuilder<T>(getThis());
startBuilder = new BorderPropsBuilder<T>(getThis());
endBuilder = new BorderPropsBuilder<T>(getThis());
this.borderExtent = new Rectangle(xOrigin, yOrigin, width, height);
this.graphicsPainter = mock(GraphicsPainter.class);
this.sut = new BorderPainter(graphicsPainter);
}
protected abstract T getThis();
public BorderPropsBuilder<T> beforeBorder() {
return beforeBuilder;
}
public BorderPropsBuilder<T> afterBorder() {
return afterBuilder;
}
public BorderPropsBuilder<T> startBorder() {
return startBuilder;
}
public BorderPropsBuilder<T> endBorder() {
return endBuilder;
}
public T setBorderWidth(int width) {
beforeBuilder.setWidth(width);
endBuilder.setWidth(width);
afterBuilder.setWidth(width);
startBuilder.setWidth(width);
return getThis();
}
public T setCornerRadii(int radius) {
return setCornerRadii(radius, radius);
}
public T setCornerRadii(int xRadius, int yRadius) {
setStartBefore(xRadius, yRadius);
setEndBefore(xRadius, yRadius);
setEndAfter(xRadius, yRadius);
setStartAfter(xRadius, yRadius);
return getThis();
}
public T setStartBefore(int xRadius, int yRadius) {
startBuilder.setRadiusStart(xRadius);
beforeBuilder.setRadiusStart(yRadius);
return getThis();
}
public T setEndBefore(int xRadius, int yRadius) {
endBuilder.setRadiusStart(xRadius);
beforeBuilder.setRadiusEnd(yRadius);
return getThis();
}
public T setEndAfter(int xRadius, int yRadius) {
endBuilder.setRadiusEnd(xRadius);
afterBuilder.setRadiusEnd(yRadius);
return getThis();
}
public T setStartAfter(int xRadius, int yRadius) {
startBuilder.setRadiusEnd(xRadius);
afterBuilder.setRadiusStart(yRadius);
return getThis();
}
public final void test() throws IOException {
before = beforeBuilder.build();
after = afterBuilder.build();
end = endBuilder.build();
start = startBuilder.build();
testMethod();
}
protected abstract void testMethod() throws IOException;
protected static int numberOfNonZeroBorders(BorderProps first, BorderProps... borders) {
int i = first.width == 0 ? 0 : 1;
for (BorderProps borderProp : borders) {
if (borderProp.width > 0) {
i++;
}
}
return i;
}
protected int numberOfNonZeroBorders() {
return numberOfNonZeroBorders(before, end, after, start);
}
}
private static class BorderPropsBuilder<T extends BorderPainterTester<?>> {
private final int style = 0;
private final Color color = null;
private final Mode mode = BorderProps.Mode.SEPARATE;
private int width;
private int radiusStart;
private int radiusEnd;
private final T tester;
public BorderPropsBuilder(T tester) {
this.tester = tester;
}
public T tester() {
return tester;
}
public BorderPropsBuilder<T> setWidth(int width) {
this.width = width;
return this;
}
public BorderPropsBuilder<T> setRadiusStart(int radiusStart) {
this.radiusStart = radiusStart;
return this;
}
public BorderPropsBuilder<T> setRadiusEnd(int radiusEnd) {
this.radiusEnd = radiusEnd;
return this;
}
public BorderProps build() {
return new BorderProps(style, width, radiusStart, radiusEnd, color, mode);
}
}
private static final class DrawRectangularBordersTester
extends BorderPainterTester<DrawRectangularBordersTester> {
public DrawRectangularBordersTester(int xOrigin, int yOrigin, int width, int height)
throws IOException {
super(xOrigin, yOrigin, width, height);
}
public DrawRectangularBordersTester setStartBefore(int xRadius, int yRadius) {
return notSupported();
}
public DrawRectangularBordersTester setEndBefore(int xRadius, int yRadius) {
return notSupported();
}
public DrawRectangularBordersTester setEndAfter(int xRadius, int yRadius) {
return notSupported();
}
public DrawRectangularBordersTester setStartAfter(int xRadius, int yRadius) {
return notSupported();
}
private DrawRectangularBordersTester notSupported() {
throw new UnsupportedOperationException();
}
public void testMethod() throws IOException {
sut.drawRectangularBorders(borderExtent, before, after, start, end);
verifyDrawing();
}
private void verifyDrawing() throws IOException {
final int rectX = borderExtent.x;
final int rectY = borderExtent.y;
final int rectWidth = borderExtent.width;
final int rectHeight = borderExtent.height;
if (before.width > 0) {
verify(graphicsPainter).moveTo(rectX, rectY);
verify(graphicsPainter).lineTo(rectWidth, rectY);
verify(graphicsPainter, times(numberOfNonZeroBorders(before, end)))
.lineTo(rectWidth - end.width, rectY + before.width);
verify(graphicsPainter, times(numberOfNonZeroBorders(before, start)))
.lineTo(rectX + start.width, rectY + before.width);
}
if (end.width > 0) {
verify(graphicsPainter).moveTo(rectWidth, rectY);
verify(graphicsPainter).lineTo(rectWidth, rectHeight);
verify(graphicsPainter, times(numberOfNonZeroBorders(end, after)))
.lineTo(rectWidth - end.width, rectHeight - after.width);
verify(graphicsPainter, times(numberOfNonZeroBorders(end, before)))
.lineTo(rectWidth - end.width, rectY + before.width);
}
if (after.width > 0) {
verify(graphicsPainter).moveTo(rectWidth, rectHeight);
verify(graphicsPainter).lineTo(rectX, rectHeight);
verify(graphicsPainter, times(numberOfNonZeroBorders(after, end)))
.lineTo(rectX + start.width, rectHeight - after.width);
verify(graphicsPainter, times(numberOfNonZeroBorders(after, start)))
.lineTo(rectWidth - end.width, rectHeight - after.width);
}
if (start.width > 0) {
verify(graphicsPainter).moveTo(rectX, rectHeight);
verify(graphicsPainter).lineTo(rectX, rectY);
verify(graphicsPainter, times(numberOfNonZeroBorders(start, before)))
.lineTo(rectX + start.width, rectY + before.width);
verify(graphicsPainter, times(numberOfNonZeroBorders(start, after)))
.lineTo(rectX + start.width, rectHeight - after.width);
}
int numBorders = numberOfNonZeroBorders();
verify(graphicsPainter, times(numBorders)).saveGraphicsState();
verify(graphicsPainter, times(numBorders)).closePath();
verify(graphicsPainter, times(numBorders)).restoreGraphicsState();
verify(graphicsPainter, times(numBorders)).clip();
}
@Override
protected DrawRectangularBordersTester getThis() {
return this;
}
}
private static final class DrawRoundedBordersTester extends BorderPainterTester<DrawRoundedBordersTester> {
public DrawRoundedBordersTester(int xOrigin, int yOrigin, int width, int height) throws IOException {
super(xOrigin, yOrigin, width, height);
}
public void testMethod() throws IOException {
sut.drawRoundedBorders(borderExtent, before, after, start, end);
verifyDrawing();
}
private void verifyDrawing() throws IOException {
int numBorders = numberOfNonZeroBorders();
final int rectWidth = borderExtent.width;
final int rectHeight = borderExtent.height;
if (before.width > 0) {
verify(graphicsPainter, atLeastOnce()).lineTo(rectWidth - end.getRadiusStart(), 0);
verify(graphicsPainter, atLeastOnce()).lineTo(calcLineEnd(start.width, before.width,
start.getRadiusStart(), before.getRadiusStart()), before.width);
}
if (end.width > 0) {
verify(graphicsPainter, atLeastOnce()).lineTo(rectHeight - after.getRadiusEnd(), 0);
verify(graphicsPainter, atLeastOnce()).lineTo(calcLineEnd(before.width, end.width,
before.getRadiusEnd(), end.getRadiusStart()), end.width);
}
if (after.width > 0) {
verify(graphicsPainter, atLeastOnce()).lineTo(rectWidth - start.getRadiusEnd(), 0);
verify(graphicsPainter, atLeastOnce()).lineTo(calcLineEnd(start.width, after.width,
start.getRadiusEnd(), after.getRadiusStart()), after.width);
}
if (start.width > 0) {
verify(graphicsPainter, atLeastOnce()).lineTo(rectHeight - after.getRadiusStart(), 0);
verify(graphicsPainter, atLeastOnce()).lineTo(calcLineEnd(before.width, start.width,
before.getRadiusStart(), before.getRadiusStart()), start.width);
}
// verify the drawing of the symmetric rounded corners (the ones that are a quarter of a circle)
// verification is restricted to those since it is too complex in the general case
if (before.width == end.width && before.getRadiusStart() == before.getRadiusEnd()
&& end.getRadiusStart() == end.getRadiusEnd()
&& before.getRadiusEnd() == end.getRadiusStart() && end.getRadiusStart() > 0) {
verify(graphicsPainter, atLeastOnce()).arcTo(Math.PI * 5 / 4, Math.PI * 3 / 2,
before.getRadiusStart(), end.getRadiusEnd(), before.getRadiusStart(),
end.getRadiusEnd());
}
if (end.width == after.width && end.getRadiusStart() == end.getRadiusEnd()
&& after.getRadiusStart() == after.getRadiusEnd()
&& end.getRadiusEnd() == after.getRadiusStart() && after.getRadiusStart() > 0) {
verify(graphicsPainter, atLeastOnce()).arcTo(Math.PI * 5 / 4, Math.PI * 3 / 2,
end.getRadiusStart(), after.getRadiusEnd(), end.getRadiusStart(),
after.getRadiusEnd());
}
if (after.width == start.width && after.getRadiusStart() == after.getRadiusEnd()
&& start.getRadiusStart() == start.getRadiusEnd()
&& after.getRadiusEnd() == start.getRadiusStart() && start.getRadiusStart() > 0) {
verify(graphicsPainter, atLeastOnce()).arcTo(Math.PI * 5 / 4, Math.PI * 3 / 2,
after.getRadiusStart(), start.getRadiusEnd(), after.getRadiusStart(),
start.getRadiusEnd());
}
if (start.width == before.width && start.getRadiusStart() == start.getRadiusEnd()
&& before.getRadiusStart() == before.getRadiusEnd()
&& start.getRadiusEnd() == before.getRadiusStart() && before.getRadiusStart() > 0) {
verify(graphicsPainter, atLeastOnce()).arcTo(Math.PI * 5 / 4, Math.PI * 3 / 2,
start.getRadiusStart(), before.getRadiusEnd(), start.getRadiusStart(),
before.getRadiusEnd());
}
verify(graphicsPainter, times(numBorders)).saveGraphicsState();
verify(graphicsPainter, times(numBorders)).closePath();
verify(graphicsPainter, times(numBorders)).restoreGraphicsState();
verify(graphicsPainter, times(numBorders)).clip();
}
private int calcLineEnd(int xWidth, int yWidth, int xRadius, int yRadius) {
return yWidth > yRadius ? yWidth : xWidth > 0 ? Math.max(xRadius, xWidth) : 0;
}
@Override
protected DrawRoundedBordersTester getThis() {
return this;
}
}
private static final class ClipBackgroundTester extends BorderPainterTester<ClipBackgroundTester> {
public ClipBackgroundTester(int xOrigin, int yOrigin, int width, int height) throws IOException {
super(xOrigin, yOrigin, width, height);
}
public void testMethod() throws IOException {
sut.clipBackground(borderExtent, before, after, start, end);
verifyClipping();
}
private void verifyClipping() throws IOException {
int xOrigin = borderExtent.x;
int yOrigin = borderExtent.y;
int xEnd = xOrigin + borderExtent.width;
int yEnd = yOrigin + borderExtent.height;
Corner startBeforeCorner = Corner.createStartBeforeCorner(getInnerRadiusStart(start),
getInnerRadiusStart(before));
Corner endBeforeCorner = Corner.createEndBeforeCorner(getInnerRadiusStart(end), getRadiusEnd(before));
Corner endAfterCorner = Corner.createEndAfterCorner(getRadiusEnd(end), getRadiusEnd(after));
Corner startAfterCorner = Corner.createStartAfterCorner(getRadiusEnd(start),
getInnerRadiusStart(after));
verify(graphicsPainter, times(1)).moveTo(xOrigin + startBeforeCorner.xRadius, yOrigin);
verify(graphicsPainter, times(1)).lineTo(xEnd - endBeforeCorner.xRadius, yOrigin);
endBeforeCorner.verifyCornerDrawn(graphicsPainter, xEnd - endBeforeCorner.xRadius,
yOrigin + endBeforeCorner.yRadius);
verify(graphicsPainter, times(1)).lineTo(xEnd, yEnd - endAfterCorner.yRadius);
endAfterCorner.verifyCornerDrawn(graphicsPainter, xEnd - endAfterCorner.xRadius,
yEnd - endAfterCorner.yRadius);
verify(graphicsPainter, times(1)).lineTo(xOrigin + startAfterCorner.xRadius, yEnd);
startAfterCorner.verifyCornerDrawn(graphicsPainter, xOrigin + startAfterCorner.xRadius,
yEnd - startAfterCorner.yRadius);
verify(graphicsPainter, times(1)).lineTo(xOrigin, yOrigin + startBeforeCorner.yRadius);
startBeforeCorner.verifyCornerDrawn(graphicsPainter, xOrigin + startBeforeCorner.xRadius,
yOrigin + startBeforeCorner.yRadius);
verify(graphicsPainter, times(1)).clip();
}
private int getInnerRadiusStart(BorderProps borderProps) {
return getInnerRadius(borderProps.getRadiusStart(), borderProps.width);
}
private int getRadiusEnd(BorderProps borderProps) {
return getInnerRadius(borderProps.getRadiusEnd(), borderProps.width);
}
private int getInnerRadius(int radius, int borderWidth) {
return Math.max(radius - borderWidth, 0);
}
private static class Corner {
public final int xRadius;
public final int yRadius;
private final double startAngle;
private final double endAngle;
public Corner(int xRadius, int yRadius, double startAngle, double endAngle) {
this.xRadius = xRadius;
this.yRadius = yRadius;
this.startAngle = startAngle;
this.endAngle = endAngle;
}
public static Corner createStartBeforeCorner(int xRadius, int yRadius) {
return new Corner(xRadius, yRadius, Math.PI, Math.PI * 3 / 2);
}
public static Corner createEndBeforeCorner(int xRadius, int yRadius) {
return new Corner(xRadius, yRadius, Math.PI * 3 / 2, 0);
}
public static Corner createEndAfterCorner(int xRadius, int yRadius) {
return new Corner(xRadius, yRadius, 0, Math.PI / 2);
}
public static Corner createStartAfterCorner(int xRadius, int yRadius) {
return new Corner(xRadius, yRadius, Math.PI / 2, Math.PI);
}
public void verifyCornerDrawn(GraphicsPainter graphicsPainter, int xCenter, int yCenter)
throws IOException {
if (xRadius != 0 && yRadius != 0) {
verify(graphicsPainter, times(1)).arcTo(startAngle, endAngle,
xCenter, yCenter, xRadius, yRadius);
} else {
verify(graphicsPainter, never()).arcTo(startAngle, endAngle,
xCenter, yCenter, xRadius, yRadius);
}
}
}
@Override
protected ClipBackgroundTester getThis() {
return this;
}
}
}