/* * 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; } } }