Skip to content

Commit 44787f7

Browse files
authored
Merge pull request #653 from danfickle/fix_642_inline_block_layer_double_paint
Fixes #642 Solution for double ups of inline-blocks
2 parents 76b86d9 + 84803bc commit 44787f7

File tree

11 files changed

+415
-8
lines changed

11 files changed

+415
-8
lines changed

openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/displaylist/DisplayListPainter.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,15 @@ private void paintInlineContent(RenderingContext c, List<DisplayListItem> inline
120120
OperatorSetClip setClip = (OperatorSetClip) dli;
121121
setClip(c, setClip);
122122
} else if (dli instanceof BlockBox) {
123-
// Inline blocks need to be painted as a layer.
124-
BlockBox bb = (BlockBox) dli;
125-
List<PageBox> pageBoxes = bb.getContainingLayer().getPages();
126-
DisplayListCollector dlCollector = new DisplayListCollector(pageBoxes);
127-
DisplayListPageContainer pageInstructions = dlCollector.collectInlineBlock(c, bb, EnumSet.noneOf(CollectFlags.class), c.getShadowPageNumber());
128-
129-
paint(c, pageInstructions);
123+
// Inline blocks need to be painted as a layer, if not already done so.
124+
BlockBox bb = (BlockBox) dli;
125+
if (!bb.getStyle().requiresLayer()) {
126+
List<PageBox> pageBoxes = bb.getContainingLayer().getPages();
127+
DisplayListCollector dlCollector = new DisplayListCollector(pageBoxes);
128+
DisplayListPageContainer pageInstructions = dlCollector.collectInlineBlock(c, bb, EnumSet.noneOf(CollectFlags.class), c.getShadowPageNumber());
129+
130+
paint(c, pageInstructions);
131+
}
130132
} else {
131133
InlinePaintable paintable = (InlinePaintable) dli;
132134
Object token = c.getOutputDevice().startStructure(StructureType.INLINE, (Box) dli);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<html>
2+
<head>
3+
<style>
4+
@page {
5+
size: 150px 200px;
6+
margin: 0;
7+
margin-top: 20px;
8+
9+
@top-left {
10+
content: element(runner);
11+
}
12+
}
13+
@page:first {
14+
size: 150px 500px;
15+
@top-left {
16+
content: none;
17+
}
18+
margin-top: 0;
19+
}
20+
body {
21+
margin: 10px;
22+
max-width: 130px;
23+
}
24+
</style>
25+
</head>
26+
<body>
27+
<!-- inline block running element -->
28+
<div style="display: inline-block; position: running(runner);">
29+
SIX<span style="display: inline-block; transform: rotate(-20deg);">TEEN</span>
30+
</div>
31+
<!-- fixed inline block -->
32+
<div style="position:fixed;top:0;left:0;display:inline-block;">ONE</div>
33+
<!-- inline block inside fixed -->
34+
<div style="position:fixed;top:50px;left:0;">TWO <span style="display:inline-block;">THREE</span></div>
35+
<!-- absolute inline block -->
36+
<div style="position:absolute;top:100px;left:0;display:inline-block;">FOUR</div>
37+
<!-- inline block inside absolute -->
38+
<div style="position:absolute;top:150px;left:0;">FIVE <span style="display:inline-block;">SIX</span></div>
39+
<div></div>
40+
<div style="margin-top: 200px;">SPACER</div>
41+
<!-- inline block inside table -->
42+
<table><tr><td><span style="display: inline-block;">SEVEN</span></td></tr></table>
43+
<!-- floating inline block -->
44+
<span style="display: inline-block;float: left;">EIGHT</span>
45+
<!-- cleared inline block -->
46+
<span style="display: block; clear: both;"><span style="display: inline-block;">NINE</span></span>
47+
<!-- absolute inside inline block -->
48+
<span style="display: inline-block; position: relative; width: 90%;">TEN <div style="position: absolute; top: 0; right: 0;">ELEVEN</div></span>
49+
<!-- transformed inline block inside transformed inline block -->
50+
<span style="display: inline-block; transform: scale(2, 1); margin-left: 30px;">TWELVE<span style="display: inline-block; transform: rotate(180deg);">THIRTEEN</span></span>
51+
<div style="page-break-before: always; margin-top: 100px;">NEW PAGE</div>
52+
<!-- inline block on new page -->
53+
<span style="display: inline-block; transform: scale(2, 1); margin-left: 30px;">FOURTEEN<span style="display: inline-block; transform: rotate(180deg);">FIFTEEN</span></span>
54+
55+
</body>
56+
</html>

openhtmltopdf-examples/src/main/resources/visualtest/html/issue-642-transform-inline.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,8 @@
99
</head>
1010
<body>
1111
<div><span style="transform: scale(2, 2); display: inline-block;">O N E</span></div>
12+
<div><span style="display: inline-block;">T W O</span></div>
13+
<div> <span style="display: inline-block;">T H R E E</span> </div>
14+
<div> <span style="transform: scale(2, 2); display: inline-block;">F O U R</span> </div>
1215
</body>
1316
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package com.openhtmltopdf.nonvisualregressiontests;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.fail;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.HashSet;
14+
import java.util.List;
15+
import java.util.Set;
16+
import java.util.function.Predicate;
17+
import java.util.regex.Pattern;
18+
import java.util.stream.Collectors;
19+
20+
import org.apache.commons.io.FileUtils;
21+
import org.apache.pdfbox.io.IOUtils;
22+
import org.apache.pdfbox.pdmodel.PDDocument;
23+
import org.apache.pdfbox.text.PDFTextStripper;
24+
import org.apache.pdfbox.util.Charsets;
25+
import org.junit.Test;
26+
27+
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
28+
import com.openhtmltopdf.testcases.TestcaseRunner;
29+
import com.openhtmltopdf.visualtest.VisualTester.BuilderConfig;
30+
31+
public class RepeatContentRegressionTest {
32+
private static final String RES_PATH = "/repeated-content-tests/";
33+
private static final String OUT_PATH = "target/test/visual-tests/test-output/";
34+
35+
interface StringMatcher {
36+
List<String> problems(String expected, String actual);
37+
void doAssert(String expected, String actual, String fileName, List<String> problems);
38+
}
39+
40+
/**
41+
* Simple tests with content all in one layer
42+
* (ie. no z-index, absolute, relative, fixed, transform)
43+
* should generally use an ordered matcher.
44+
*/
45+
private static class OrderedMatcher implements StringMatcher {
46+
public List<String> problems(String expected, String actual) {
47+
if (!expected.equals(actual)) {
48+
return Collections.singletonList("Mismatched ordered");
49+
}
50+
51+
return Collections.emptyList();
52+
}
53+
54+
public void doAssert(String expected, String actual, String fileName, List<String> problems) {
55+
assertEquals("Mismatched: " + fileName, expected, actual);
56+
}
57+
}
58+
59+
/**
60+
* Layers have to follow a specific painting order so they may be out of order.
61+
*/
62+
private static class UnOrderedMatcher implements StringMatcher {
63+
public List<String> problems(String expected, String actual) {
64+
String[] expWords = expected.split("\\s");
65+
String[] actWords = actual.split("\\s");
66+
67+
Predicate<String> filter = w -> !w.trim().isEmpty();
68+
69+
Set<String> exp = Arrays.stream(expWords)
70+
.filter(filter)
71+
.collect(Collectors.toSet());
72+
73+
List<String> act = Arrays.stream(actWords)
74+
.filter(filter)
75+
.collect(Collectors.toList());
76+
77+
Set<String> seen = new HashSet<>();
78+
79+
List<String> problems = new ArrayList<>();
80+
81+
for (String word : act) {
82+
if (seen.contains(word)) {
83+
problems.add("Repeat content: " + word);
84+
}
85+
86+
seen.add(word);
87+
88+
if (!exp.contains(word)) {
89+
problems.add("Unexpected content: " + word);
90+
}
91+
}
92+
93+
for (String word : exp) {
94+
if (!act.contains(word)) {
95+
problems.add("Missing: " + word);
96+
}
97+
}
98+
99+
return problems;
100+
}
101+
102+
public void doAssert(String expected, String actual, String fileName, List<String> problems) {
103+
fail(problems.stream().collect(Collectors.joining("\n")));
104+
}
105+
}
106+
107+
private static final StringMatcher ORDERED = new OrderedMatcher();
108+
private static final StringMatcher UNORDERED = new UnOrderedMatcher();
109+
110+
private static void render(String fileName, String html, BuilderConfig config, String proof, StringMatcher matcher) throws IOException {
111+
ByteArrayOutputStream actual = new ByteArrayOutputStream();
112+
113+
PdfRendererBuilder builder = new PdfRendererBuilder();
114+
builder.withHtmlContent(html, NonVisualRegressionTest.class.getResource(RES_PATH).toString());
115+
builder.toStream(actual);
116+
builder.useFastMode();
117+
builder.testMode(true);
118+
config.configure(builder);
119+
120+
try {
121+
builder.run();
122+
} catch (IOException e) {
123+
System.err.println("Failed to render resource (" + fileName + ")");
124+
e.printStackTrace();
125+
throw e;
126+
}
127+
128+
byte[] pdfBytes = actual.toByteArray();
129+
130+
try (PDDocument doc = PDDocument.load(pdfBytes)) {
131+
PDFTextStripper stripper = new PDFTextStripper();
132+
stripper.setSuppressDuplicateOverlappingText(false);
133+
stripper.setLineSeparator("\n");
134+
135+
String text = stripper.getText(doc).trim();
136+
String expected = proof.trim().replace("\r\n", "\n");
137+
138+
List<String> problems = matcher.problems(expected, text);
139+
140+
if (!problems.isEmpty()) {
141+
FileUtils.writeByteArrayToFile(new File(OUT_PATH, fileName + ".pdf"), pdfBytes);
142+
143+
matcher.doAssert(expected, text, fileName, problems);
144+
}
145+
}
146+
}
147+
148+
private static void run(String fileName, BuilderConfig config, StringMatcher matcher) throws IOException {
149+
String absResPath = RES_PATH + fileName + ".html";
150+
151+
try (InputStream is = TestcaseRunner.class.getResourceAsStream(absResPath)) {
152+
byte[] htmlBytes = IOUtils.toByteArray(is);
153+
String htmlWithProof = new String(htmlBytes, Charsets.UTF_8);
154+
155+
String[] parts = htmlWithProof.split(Pattern.quote("======="));
156+
String html = parts[0];
157+
String proof = parts[1];
158+
159+
render(fileName, html, config, proof, matcher);
160+
}
161+
}
162+
163+
private static void runOrdered(String fileName) throws IOException {
164+
run(fileName, builder -> { }, ORDERED);
165+
}
166+
167+
private static void runUnOrdered(String fileName) throws IOException {
168+
run(fileName, builder -> { }, UNORDERED);
169+
}
170+
171+
/**
172+
* inline-blocks/relative/absolute with z-index.
173+
*/
174+
@Test
175+
public void testInlineBlockZIndex() throws IOException {
176+
runOrdered("inline-block-z-index");
177+
}
178+
179+
/**
180+
* Multiple in-flow inline-blocks on the one page.
181+
*/
182+
@Test
183+
public void testInlineBlockMultiple() throws IOException {
184+
runUnOrdered("inline-block-multiple");
185+
}
186+
187+
/**
188+
* Multiple in-flow inline-blocks across pages with large page margin.
189+
*/
190+
@Test
191+
public void testInlineBlockPages() throws IOException {
192+
runUnOrdered("inline-block-pages");
193+
}
194+
195+
/**
196+
* Float that goes over two pages.
197+
*/
198+
@Test
199+
public void testFloatsPages() throws IOException {
200+
runUnOrdered("floats-pages");
201+
}
202+
203+
}

openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,14 +1367,23 @@ public void testExternalAccessControl() throws IOException {
13671367
/**
13681368
* Transforms on inline-block or inline elements output twice,
13691369
* once as the non-transformed output and once as transformed.
1370+
* Also affected inline-blocks with a z-index.
13701371
* https://github.com/danfickle/openhtmltopdf/issues/642
13711372
*/
13721373
@Test
1373-
@Ignore // Not debugged yet.
13741374
public void testIssue642TransformInline() throws IOException {
13751375
assertTrue(vt.runTest("issue-642-transform-inline"));
13761376
}
13771377

1378+
/**
1379+
* Combinations of inline-blocks with other display and positioned
1380+
* content.
1381+
*/
1382+
@Test
1383+
public void testIssue642InlineBlocks() throws IOException {
1384+
assertTrue(vt.runTest("issue-642-inline-blocks"));
1385+
}
1386+
13781387
/**
13791388
* Tests that the background-image property allows multiple values.
13801389
*/
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<html>
2+
<head>
3+
<style>
4+
@page {
5+
size: 180px 100px;
6+
margin: 20px;
7+
}
8+
body {
9+
font-size: 14px;
10+
margin: 0;
11+
}
12+
.flt-l {
13+
float: left;
14+
border: 1px solid red;
15+
margin: 3px;
16+
}
17+
.flt-r {
18+
float: right;
19+
border: 1px solid green;
20+
width: 60%;
21+
}
22+
</style>
23+
</head>
24+
<body>
25+
<div class="flt-l">one two three</div>
26+
four five six seven eleven
27+
<div class="flt-r">eight nine ten sixteen seventeen eighteen</div>
28+
twelve thirteen fourteen
29+
fifteen
30+
</body>
31+
</html>
32+
=======
33+
one
34+
two
35+
three
36+
four
37+
five
38+
six
39+
seven
40+
eight
41+
nine
42+
ten
43+
eleven
44+
twelve
45+
thirteen
46+
fourteen
47+
fifteen
48+
sixteen
49+
seventeen
50+
eighteen
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<html>
2+
<head>
3+
<style>
4+
span {
5+
display: inline-block;
6+
}
7+
</style>
8+
</head>
9+
<body>
10+
<div><span>one</span></div>
11+
<div> <span>two</span> </div>
12+
<span>three</span>
13+
<span>four</span>
14+
<div><span>five</span> <span>six</span></div>
15+
<div> seven <span>eight</span> nine <span>ten</span></div>
16+
</body>
17+
</html>
18+
=======
19+
one
20+
two
21+
three
22+
four
23+
five
24+
six
25+
seven
26+
eight
27+
nine
28+
ten

0 commit comments

Comments
 (0)