Skip to content

Commit 1cb76d6

Browse files
committed
PDFBOX-5660: add test, as suggested by Valery Bokov; closes #448
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1934768 13f79535-47bb-0310-9956-ffa450edef68
1 parent c8c7a23 commit 1cb76d6

1 file changed

Lines changed: 257 additions & 0 deletions

File tree

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.pdfbox.printing;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
22+
import java.awt.BasicStroke;
23+
import java.awt.Color;
24+
import java.awt.Graphics2D;
25+
import java.awt.Shape;
26+
import java.awt.Stroke;
27+
import java.awt.geom.AffineTransform;
28+
import java.awt.geom.Rectangle2D;
29+
import java.awt.image.BufferedImage;
30+
import java.awt.print.PageFormat;
31+
import java.awt.print.Paper;
32+
import java.awt.print.Printable;
33+
34+
import org.apache.pdfbox.pdmodel.PDDocument;
35+
import org.apache.pdfbox.pdmodel.PDPage;
36+
import org.apache.pdfbox.pdmodel.common.PDRectangle;
37+
38+
import org.junit.jupiter.api.Test;
39+
40+
41+
/**
42+
* Tests for {@link PDFPrintable}.
43+
*/
44+
class TestPDFPrintable
45+
{
46+
private final int IMAGE_WIDTH = 100;
47+
private final int IMAGE_HEIGHT = 100;
48+
49+
/**
50+
* Tests that the page border is drawn with Color.GRAY when showPageBorder is true.
51+
*
52+
* Without rasterization, {@code graphics} and {@code graphics2D} are the same object,
53+
* so setColor(GRAY) and drawRect() both act on the same Graphics2D. The border is drawn
54+
* correctly.
55+
*/
56+
@Test
57+
void testShowPageBorderIsGrayWithoutRasterization() throws Exception
58+
{
59+
testShowPageBorderIsGray(PDFPrintable.RASTERIZE_OFF);
60+
}
61+
62+
/**
63+
* Tests that the page border is drawn with Color.GRAY when rasterizing.
64+
*
65+
* <p>When rasterizing (dpi &gt; 0), the border is drawn on a raster image that is
66+
* larger than the page (scaled by dpiScale), then blitted down to the output.
67+
* The {@code setClip} in the showPageBorder block must use raster-pixel dimensions
68+
* (imageableWidth * scale), not raw point dimensions (imageableWidth). Otherwise
69+
* the clip is too small, the border is drawn in only a fraction of the raster image,
70+
* and the thin border line gets lost during the scale-down blit to the output.</p>
71+
*/
72+
@Test
73+
void testShowPageBorderIsGrayWithRasterization() throws Exception
74+
{
75+
testShowPageBorderIsGray(150f);
76+
}
77+
78+
/**
79+
* {@code print()} would otherwise mutate the caller's Graphics2D in several places:
80+
* translate() for imageable area and centering, scale() during rasterization,
81+
* setBackground() before the raster blit, and setColor / setStroke / setClip / setTransform
82+
* in the showPageBorder block. To isolate the caller, {@code print()} works on a private
83+
* copy obtained via {@code graphics.create()} and disposes it in the finally block, so none
84+
* of those mutations reach the caller. This test verifies that isolation by setting
85+
* distinctive state on the caller's Graphics2D before {@code print()} and asserting it is
86+
* unchanged afterwards.
87+
*/
88+
@Test
89+
void testPrinterGraphicsStateIsUnchangedAfterPrint() throws Exception
90+
{
91+
assertPrinterGraphicsStateUnchanged(PDFPrintable.RASTERIZE_OFF);
92+
}
93+
94+
@Test
95+
void testPrinterGraphicsStateIsUnchangedAfterPrintWhenRasterizing() throws Exception
96+
{
97+
assertPrinterGraphicsStateUnchanged(150f);
98+
}
99+
100+
private void assertPrinterGraphicsStateUnchanged(float dpi) throws Exception
101+
{
102+
try (PDDocument doc = new PDDocument())
103+
{
104+
doc.addPage(new PDPage(new PDRectangle(IMAGE_WIDTH, IMAGE_HEIGHT)));
105+
106+
PDFPrintable printable = new PDFPrintable(doc, Scaling.ACTUAL_SIZE, true, dpi);
107+
108+
BufferedImage output = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
109+
Graphics2D g2d = output.createGraphics();
110+
111+
// set a distinctive transform so we can detect leaks from internal translate()/scale() calls
112+
g2d.translate(7.0, 11.0);
113+
g2d.scale(1.3, 1.3);
114+
115+
Color originalColor = Color.RED;
116+
Color originalBackground = Color.BLUE;
117+
Stroke originalStroke = new BasicStroke(3.7f);
118+
g2d.setColor(originalColor);
119+
g2d.setBackground(originalBackground);
120+
g2d.setStroke(originalStroke);
121+
AffineTransform originalTransform = g2d.getTransform();
122+
Rectangle2D originalClipDeviceBounds = deviceClipBounds(g2d);
123+
124+
PageFormat pf = createPageFormat(IMAGE_WIDTH, IMAGE_HEIGHT);
125+
int result = printable.print(g2d, pf, 0);
126+
127+
assertEquals(Printable.PAGE_EXISTS, result);
128+
assertEquals(originalColor, g2d.getColor(),
129+
"color should be unchanged after print()");
130+
assertEquals(originalBackground, g2d.getBackground(),
131+
"background should be unchanged after print()");
132+
assertEquals(originalStroke, g2d.getStroke(),
133+
"stroke should be unchanged after print()");
134+
assertEquals(originalTransform, g2d.getTransform(),
135+
"transform should be unchanged after print() (translate/scale inside print() must not leak)");
136+
// device-space comparison — invariant under transform changes on the same Graphics2D
137+
assertEquals(originalClipDeviceBounds, deviceClipBounds(g2d),
138+
"clip should be unchanged after print()");
139+
140+
g2d.dispose();
141+
}
142+
}
143+
144+
/**
145+
* Returns the bounds of the current clip projected into device space via the current transform.
146+
* This is stable across transform changes on the same Graphics2D (unlike getClip().getBounds2D(),
147+
* which is in current user space).
148+
*/
149+
private static Rectangle2D deviceClipBounds(Graphics2D g2d)
150+
{
151+
Shape clip = g2d.getClip();
152+
if (clip == null)
153+
{
154+
return null;
155+
}
156+
return g2d.getTransform().createTransformedShape(clip).getBounds2D();
157+
}
158+
159+
@Test
160+
void testPrintReturnsNoSuchPageForInvalidIndex() throws Exception
161+
{
162+
try (PDDocument doc = new PDDocument())
163+
{
164+
doc.addPage(new PDPage());
165+
166+
PDFPrintable printable = new PDFPrintable(doc);
167+
168+
BufferedImage output = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
169+
Graphics2D g2d = output.createGraphics();
170+
PageFormat pf = createPageFormat(IMAGE_WIDTH, IMAGE_HEIGHT);
171+
172+
assertEquals(Printable.NO_SUCH_PAGE, printable.print(g2d, pf, -1));
173+
assertEquals(Printable.NO_SUCH_PAGE, printable.print(g2d, pf, 1));
174+
assertEquals(Printable.PAGE_EXISTS, printable.print(g2d, pf, 0));
175+
176+
g2d.dispose();
177+
}
178+
}
179+
180+
private void testShowPageBorderIsGray(float dpi) throws Exception
181+
{
182+
try (PDDocument doc = new PDDocument())
183+
{
184+
PDPage page = new PDPage(new PDRectangle(IMAGE_WIDTH, IMAGE_HEIGHT));
185+
doc.addPage(page);
186+
187+
PDFPrintable printable = new PDFPrintable(doc, Scaling.ACTUAL_SIZE, true, dpi);
188+
189+
BufferedImage output = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
190+
Graphics2D g2d = output.createGraphics();
191+
// fill with white so we can detect gray border pixels
192+
g2d.setColor(Color.WHITE);
193+
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
194+
195+
PageFormat pf = createPageFormat(IMAGE_WIDTH, IMAGE_HEIGHT);
196+
int result = printable.print(g2d, pf, 0);
197+
g2d.dispose();
198+
199+
assertEquals(Printable.PAGE_EXISTS, result);
200+
assertBorderPixelIsGray(output);
201+
}
202+
}
203+
204+
/**
205+
* Asserts that at least one pixel along the top edge of the image is gray
206+
* (R == G == B, not white and not black), proving the border was drawn with Color.GRAY.
207+
*/
208+
private static void assertBorderPixelIsGray(BufferedImage image)
209+
{
210+
boolean foundGray = false;
211+
int width = image.getWidth();
212+
// scan top row and left column where the border rect starts
213+
for (int x = 0; x < width; x++)
214+
{
215+
if (isGray(image.getRGB(x, 0)))
216+
{
217+
foundGray = true;
218+
break;
219+
}
220+
}
221+
if (!foundGray)
222+
{
223+
int height = image.getHeight();
224+
for (int y = 0; y < height; y++)
225+
{
226+
if (isGray(image.getRGB(0, y)))
227+
{
228+
foundGray = true;
229+
break;
230+
}
231+
}
232+
}
233+
assertTrue(foundGray,
234+
"Expected a gray border pixel in the top-left corner. " +
235+
"If this fails, drawRect may be called on the wrong Graphics object.");
236+
}
237+
238+
private static boolean isGray(int argb)
239+
{
240+
int a = (argb >> 24) & 0xFF;
241+
int r = (argb >> 16) & 0xFF;
242+
int g = (argb >> 8) & 0xFF;
243+
int b = argb & 0xFF;
244+
// Color.GRAY is (128, 128, 128) — allow some tolerance for antialiasing
245+
return a > 0 && r == g && g == b && r > 50 && r < 200;
246+
}
247+
248+
private static PageFormat createPageFormat(double width, double height)
249+
{
250+
Paper paper = new Paper();
251+
paper.setSize(width, height);
252+
paper.setImageableArea(0, 0, width, height);
253+
PageFormat pf = new PageFormat();
254+
pf.setPaper(paper);
255+
return pf;
256+
}
257+
}

0 commit comments

Comments
 (0)