From b6e6d2f2b6f7e6ad64092e36806ce7475e9446ed Mon Sep 17 00:00:00 2001 From: danfickle Date: Sun, 22 Aug 2021 18:17:21 +1000 Subject: [PATCH] #364 Yet more work on correct boxing. Since we wrap generated content correctly now, one can use a border on the generated content box (such as before or after) and get Chrome matching results, rather than a border on each item in the content property list. With test proof: + Test for pseudo elements with different display values. + Test for using an image as footnote-call. + Test for using an image as footnote-marker. --- .../context/ContentFunctionFactory.java | 61 +++++- .../com/openhtmltopdf/layout/BoxBuilder.java | 200 +++++++----------- .../footnote/issue-364-image-calls.pdf | Bin 0 -> 3051 bytes .../footnote/issue-364-image-markers.pdf | Bin 0 -> 3102 bytes .../visualtest/expected/pseudo-elements.pdf | Bin 0 -> 6686 bytes .../html/footnote/issue-364-image-calls.html | 2 +- .../footnote/issue-364-image-markers.html | 2 +- .../visualtest/html/pseudo-elements.html | 64 ++++++ .../NonVisualRegressionTest.java | 5 +- .../FootnoteVisualRegressionTest.java | 10 +- .../VisualRegressionTest.java | 8 + 11 files changed, 221 insertions(+), 131 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/footnote/issue-364-image-calls.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/footnote/issue-364-image-markers.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/pseudo-elements.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/pseudo-elements.html diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/ContentFunctionFactory.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/ContentFunctionFactory.java index 4418673a6..3b462e2f7 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/ContentFunctionFactory.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/context/ContentFunctionFactory.java @@ -20,9 +20,10 @@ package com.openhtmltopdf.context; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; +import org.w3c.dom.Element; + import com.openhtmltopdf.css.constants.IdentValue; import com.openhtmltopdf.css.extend.ContentFunction; import com.openhtmltopdf.css.parser.CSSPrimitiveValue; @@ -188,7 +189,15 @@ public boolean isStatic() { @Override public String calculate(RenderingContext c, FSFunction function, InlineText text) { - String uri = text.getParent().getElement().getAttribute("href"); + // Due to how BoxBuilder::wrapGeneratedContent works, it is likely the immediate + // parent of text is an anonymous InlineLayoutBox so we have to go up another + // level to the wrapper box which contains the element. + Element hrefElement = text.getParent().getElement() == null ? + text.getParent().getParent().getElement() : + text.getParent().getElement(); + + String uri = hrefElement.getAttribute("href"); + if (uri != null && uri.startsWith("#")) { String anchor = uri.substring(1); Box target = c.getBoxById(anchor); @@ -242,7 +251,15 @@ public boolean isStatic() { @Override public String calculate(RenderingContext c, FSFunction function, InlineText text) { - String uri = text.getParent().getElement().getAttribute("href"); + // Due to how BoxBuilder::wrapGeneratedContent works, it is likely the immediate + // parent of text is an anonymous InlineLayoutBox so we have to go up another + // level to the wrapper box which contains the element. + Element hrefElement = text.getParent().getElement() == null ? + text.getParent().getParent().getElement() : + text.getParent().getElement(); + + String uri = hrefElement.getAttribute("href"); + if (uri != null && uri.startsWith("#")) { String anchor = uri.substring(1); Box target = c.getBoxById(anchor); @@ -302,16 +319,46 @@ public String calculate(RenderingContext c, FSFunction function, InlineText text // Because the leader should fill up the line, we need the correct // width and must first compute the target-counter function. boolean dynamic = false; - Iterator childIterator = lineBox.getChildIterator(); - while (childIterator.hasNext()) { - Box child = childIterator.next(); + boolean dynamic2 = false; + Box wrapperBox = iB.getParent(); + List children = null; + + // The type of wrapperBox will depend on the CSS display property + // of the pseudo element where the content property using this function exists. + // See BoxBuilder#wrapGeneratedContent. + if (wrapperBox instanceof InlineLayoutBox) { + children = ((InlineLayoutBox) wrapperBox).getInlineChildren(); + } else { + children = wrapperBox.getChildren(); + } + + for (Object child : children) { if (child == iB) { + // Don't call InlineLayoutBox::lookForDynamicFunctions on this box + // as we will arrive back here and end up as a stack overflow. + // Instead set the dynamic flag so we resolve subsequent dynamic functions. dynamic = true; - } else if (dynamic && child instanceof InlineLayoutBox) { + } else if (child instanceof InlineLayoutBox) { + // This forces the computation of dynamic functions. ((InlineLayoutBox)child).lookForDynamicFunctions(c); } } + + // We also have to evaluate subsequent dynamic functions at the line level + // in the case that we are in the ::before and subsequent functions are in ::after. + if (dynamic) { + for (Box child : lineBox.getChildren()) { + if (wrapperBox == child) { + dynamic2 = true; + } else if (dynamic2 && child instanceof InlineLayoutBox) { + ((InlineLayoutBox)child).lookForDynamicFunctions(c); + } + } + } + if (dynamic) { + // Re-calculate the width of the line after subsequent dynamic functions + // have been calculated. int totalLineWidth = InlineBoxing.positionHorizontally(c, lineBox, 0); lineBox.setContentWidth(totalLineWidth); } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java index f6fed54cd..891f385d4 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java @@ -28,7 +28,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -849,15 +848,16 @@ private static List createGeneratedContentList( Element img = doc.createElement("img"); img.setAttribute("src", value.getStringValue()); + // So we don't recurse into the element and create a duplicate box. + img.setAttribute("fs-ignore", "true"); creator.appendChild(img); - CalculatedStyle anon = new EmptyStyle().createAnonymousStyle(IdentValue.INLINE_BLOCK); + CalculatedStyle anon = style.createAnonymousStyle(IdentValue.INLINE_BLOCK); BlockBox iB = new BlockBox(); iB.setElement(img); iB.setStyle(anon); - - info.setContainsBlockLevelContent(true); + iB.setPseudoElementOrClass(peName); result.add(iB); } else if (value.getPropertyValueType() == PropertyValue.VALUE_TYPE_FUNCTION) { @@ -1027,148 +1027,116 @@ private static void insertGeneratedContent( } } + /** + * Creates generated content boxes for pseudo elements such as ::before. + * + * @param element The containing element where the pseudo element appears. + * For span::before the element would be a span. + * + * @param peName Examples include before, after, + * footnote-call and footnote-marker. + * + * @param style The child style for this pseudo element. For span::before + * this would include all the styles set explicitly on ::before as well as + * those that inherit from span following the cascade rules. + * + * @param property The values of the content CSS property. + * @param info In/out param. Whether the resultant box(es) contain block level content. + * @return The generated box(es). Typically one {@link BlockBox} or multiple inline boxes. + */ private static List createGeneratedContent( LayoutContext c, Element element, String peName, CalculatedStyle style, PropertyValue property, ChildBoxInfo info) { - if (style.isDisplayNone() || style.isIdent(CSSName.DISPLAY, IdentValue.TABLE_COLUMN) - || style.isIdent(CSSName.DISPLAY, IdentValue.TABLE_COLUMN_GROUP)) { + + if (style.isDisplayNone() + || style.isIdent(CSSName.DISPLAY, IdentValue.TABLE_COLUMN) + || style.isIdent(CSSName.DISPLAY, IdentValue.TABLE_COLUMN_GROUP) + || property.getValues() == null) { return Collections.emptyList(); } ChildBoxInfo childInfo = new ChildBoxInfo(); List values = property.getValues(); - - if (values == null) { - // content: normal or content: none - return Collections.emptyList(); - } - List result = new ArrayList<>(values.size()); - Element appendTo = element; - CalculatedStyle inlineStyle = style.createAnonymousStyle(IdentValue.INLINE); - - if ("footnote-call".equals(peName)) { - // Wrap the ::footnote-call content with an inline anchor box. - InlineBox aStart = new InlineBox(""); - - aStart.setStartsHere(true); - aStart.setEndsHere(false); - Element anchor = createFootnoteCallAnchor(c, element); + createGeneratedContentList( + c, element, values, peName, style, CONTENT_LIST_DOCUMENT, childInfo, result); - aStart.setElement(anchor); - aStart.setStyle(inlineStyle); + return wrapGeneratedContent(c, element, peName, style, info, childInfo, result); + } - appendTo = anchor; + private static List wrapGeneratedContent( + LayoutContext c, + Element element, String peName, CalculatedStyle style, + ChildBoxInfo info, ChildBoxInfo childInfo, List inlineBoxes) { - result.add(aStart); + Element wrapperElement = element; + if ("footnote-call".equals(peName)) { + wrapperElement = createFootnoteCallAnchor(c, element); } else if ("footnote-marker".equals(peName)) { - // We need a box at the start of marker to target the ::footnote-call - // link to. - Element marker = createFootnoteTarget(c, element); - - InlineBox targetStart = new InlineBox(""); - - targetStart.setStartsHere(true); - targetStart.setEndsHere(false); - targetStart.setElement(marker); - targetStart.setStyle(inlineStyle); - targetStart.setPseudoElementOrClass(peName); - - result.add(targetStart); - - appendTo = marker; + wrapperElement = createFootnoteTarget(c, element); } - createGeneratedContentList( - c, appendTo, values, peName, style, CONTENT_LIST_DOCUMENT, childInfo, result); - - if ("footnote-call".equals(peName)) { - // Wrap the ::footnote-call content with an inline anchor box. - InlineBox aEnd = new InlineBox(""); - - aEnd.setElement(appendTo); - aEnd.setStartsHere(false); - aEnd.setEndsHere(true); - aEnd.setStyle(inlineStyle); - - result.add(aEnd); - } else if ("footnote-marker".equals(peName)) { - InlineBox targetEnd = new InlineBox(""); + if (style.isInline()) { + // Because a content property like: content: counter(page) ". "; + // will generate multiple inline boxes we have to wrap them in a inline-box + // and use a child style for the generated boxes. Otherwise, if we use the + // pseudo style directly and it has a border, etc we will incorrectly get a border + // around every content box. - targetEnd.setStartsHere(false); - targetEnd.setEndsHere(true); - targetEnd.setElement(appendTo); - targetEnd.setStyle(inlineStyle); + List pseudoInlines = new ArrayList<>(inlineBoxes.size() + 2); - result.add(targetEnd); - } + InlineBox pseudoStart = new InlineBox(""); + pseudoStart.setStartsHere(true); + pseudoStart.setEndsHere(false); + pseudoStart.setStyle(style); + pseudoStart.setElement(wrapperElement); + pseudoStart.setPseudoElementOrClass(peName); - return wrapGeneratedContent(element, peName, style, info, childInfo, result); - } + pseudoInlines.add(pseudoStart); - private static List wrapGeneratedContent(Element element, String peName, CalculatedStyle style, - ChildBoxInfo info, ChildBoxInfo childInfo, List inlineBoxes) { - if (childInfo.isContainsBlockLevelContent()) { - List inlines = new ArrayList<>(); + CalculatedStyle inlineContent = style.createAnonymousStyle(IdentValue.INLINE); - CalculatedStyle anonStyle = style.isInlineBlock() ? - style : style.overrideStyle(IdentValue.INLINE_BLOCK); + for (Styleable styleable : inlineBoxes) { + if (styleable instanceof InlineBox) { + InlineBox iB = (InlineBox) styleable; - BlockBox result = createBlockBox(anonStyle, info, true); - result.setStyle(anonStyle); - result.setElement(element); - result.setChildrenContentType(BlockBox.ContentType.INLINE); - result.setPseudoElementOrClass(peName); + iB.setElement(null); + iB.setStyle(inlineContent); + iB.applyTextTransform(); + } - CalculatedStyle anon = style.createAnonymousStyle(IdentValue.INLINE); - for (Iterator i = inlineBoxes.iterator(); i.hasNext();) { - Styleable b = i.next(); - - if (b instanceof BlockBox) { - inlines.add(b); - } else { - InlineBox iB = (InlineBox) b; - - iB.setStyle(anon); - iB.applyTextTransform(); - if (!isMustKeepElement(iB)) { - iB.setElement(null); - } - - inlines.add(iB); - } + pseudoInlines.add(styleable); } - if (!inlines.isEmpty()) { - result.setInlineContent(inlines); - } + InlineBox pseudoEnd = new InlineBox(""); + pseudoEnd.setStartsHere(false); + pseudoEnd.setEndsHere(true); + pseudoEnd.setStyle(style); + pseudoEnd.setElement(wrapperElement); + pseudoEnd.setPseudoElementOrClass(peName); - return Collections.singletonList(result); - } else if (style.isInline()) { - for (Iterator i = inlineBoxes.iterator(); i.hasNext();) { - InlineBox iB = (InlineBox) i.next(); - iB.setStyle(style); - iB.applyTextTransform(); - } - return inlineBoxes; + pseudoInlines.add(pseudoEnd); + + return pseudoInlines; } else { CalculatedStyle anon = style.createAnonymousStyle(IdentValue.INLINE); - for (Iterator i = inlineBoxes.iterator(); i.hasNext();) { - InlineBox iB = (InlineBox) i.next(); - iB.setStyle(anon); - iB.applyTextTransform(); - if (!isMustKeepElement(iB)) { + for (Styleable styleable : inlineBoxes) { + if (styleable instanceof InlineBox) { + InlineBox iB = (InlineBox) styleable; + iB.setElement(null); + iB.setStyle(anon); + iB.applyTextTransform(); } } BlockBox result = createBlockBox(style, info, true); result.setStyle(style); result.setInlineContent(inlineBoxes); - result.setElement(element); + result.setElement(wrapperElement); result.setChildrenContentType(BlockBox.ContentType.INLINE); result.setPseudoElementOrClass(peName); @@ -1180,12 +1148,6 @@ private static List wrapGeneratedContent(Element element, String peNa } } - private static boolean isMustKeepElement(InlineBox iB) { - return iB.getElement() != null && - ("fs-footnote-marker".equals(iB.getElement().getNodeName()) || - iB.getElement().getAttribute("href").startsWith("#fs-footnote-")); - } - private static List createGeneratedMarginBoxContent( LayoutContext c, Element element, PropertyValue property, CalculatedStyle style, ChildBoxInfo info) { @@ -1337,9 +1299,11 @@ private static void createElementChild( } String tag = element.getNodeName(); - if ("fs-footnote-marker".equals(tag) || - ("a".equals(tag) && element.getAttribute("href").startsWith("#fs-footnote"))) { - // Don't output elements that have been artificially created to support footnotes. + if (("fs-footnote-marker".equals(tag)) || + ("a".equals(tag) && element.getAttribute("href").startsWith("#fs-footnote")) || + ("img".equals(tag) && element.getAttribute("fs-ignore").equals("true"))) { + // Don't output elements that have been artificially created to support + // footnotes and content property images. return; } diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/footnote/issue-364-image-calls.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/footnote/issue-364-image-calls.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b4b7ea5e462e61c5fa819d45f522c60c7aa4f0c1 GIT binary patch literal 3051 zcmd5;U2GIp6vm=Wya5f8sw74)(MlU+=HB_)AA|I7yQK=DY&RBdP=?*R-6^|s%gn5_ zB&0=&jXx1mL&5|4LgSl}2V;D~7{Y@wMiPDT1tfv+V4@}>5(L(B@6PPbZcFgN2d6)C z@0oMXckVskJ?D0gq|%$HVxZ3Jmv3D}6cfz%PNL15k(!;EVptupe5=GKkoq!nJ)1jN zrAi#BBi01-ums`l;`o?G48@nF zhDhDc^0w8_r|~#Z2o`^uX2O4+z!*G@SS|xswIzaI$A_thO#y_|4D)!!&A}eTYJxt; z;qZApUJInLC&D1a-^QE?zlbSK#zcgk4|`NzjQ~)OoA?0I@QaAxNkj?Wj>dO`WghKE zdJJnZ1D6p^B;}|iM|w<}QCt!l%`>52LPa!&9B?Rp+zDjpidpwWgR$^M6hPNvx}uq2 zWfVmqJt}B!Xm4@GlNcd{83|+~9TcCq!i-RDTU`Lt* zTNbr-fg+0jEK<`HQ?VskSfJb^iUUNGDpX`7TSi^kqU~XyP5ank45;SuGQ-_CJBbFf z_i!U}5`(=IZVH_Hz!2ssb>sag4(K8|;!O#a`yLwQ%19k9Q=H;xr^umV5}-|xNtq(k zPzVGmcp)4?xlg)YFP(u(vLuZH8tonkKVTDUWox}+cK#knBWA7x(`B*d) z!Q`YAL)~i>M^P<9(^|igbrh+imA$^~Kih0)A5#0^c+{L`gvasvVS#c3pAxcw83~2P zO(@-IY#_CN6zXh<81yF*SO_Ohe{25|5r*G`TCHg$Y=yykBEb!pp$!6_UGkxUsp%5* zx)jUtJP7-3-y31>054B*M|6?|QpfDPDF5JbE3+qx(5PTIz)Rd6ow9P!ZBy(GJI69Z z{Q$Dv^7cW2sx5mM+K)TM1pxNl3X8Un>63p~;hC*I`P_w>&c(~`FWz1=eD&1&%Gje% z-TCXci?gSHJax1C`^S%*(NEvD_OvhbUf4$%QJBMD|bm7`NhyUn#`0Q-Q+tr(U z7UnOlUHRC^*T>JzUzmU9*44rde(v|4k1ox2ocO%0d-@?`{o=3li%)Dl)PA;ld{y=6 zTi^J{9_-yX=iGR@`}XTcFU^`~t7}(Pt8*PIemc17t92*lKDgEK$l<}yv=Q}}E5{c2 zdt+~&JW^2OE6-o)7x~^DVV~qj=xGc?F2KQPRAK3@ zLl8W|0D(^-*_Ov7_0_ChX5OX@FIy1LpaWd6oe2z?clsR94h2z+=%p#VAn5-Q=ODCy z$+;odnrR``?9V;z_pkW!lQjpH$-=bD3ec9QfuP+75006pZsG#mXo81Ae-jUO>M|OG zQ`RCQaX6VRGEIZJ(jwDMxXm@oXpAf)i@~P0=*8np^%7(WhNfvyl$vcd9WFFWWcm_+ znh}-xu4R{)EAP@7=>E7?jnrYMz;Sr2fvB4kDU&2fnk4!L3H+L=RB|w-^(6;r67Gcw hV++2w3RJyO`ydnfkQYnK^<{>%7>%RO&cPjN^bdcT6b%3X literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/footnote/issue-364-image-markers.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/footnote/issue-364-image-markers.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b8963061b412398b71ff5f8ce9922075f9efaf1d GIT binary patch literal 3102 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 -

+

This text needs some footnotes.

This is a footnote. diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/footnote/issue-364-image-markers.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/footnote/issue-364-image-markers.html index fea98b2a9..70208dddc 100644 --- a/openhtmltopdf-examples/src/main/resources/visualtest/html/footnote/issue-364-image-markers.html +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/footnote/issue-364-image-markers.html @@ -40,7 +40,7 @@ -

+

This text needs some footnotes.

This is a footnote. diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/pseudo-elements.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/pseudo-elements.html new file mode 100644 index 000000000..86caaf2e8 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/pseudo-elements.html @@ -0,0 +1,64 @@ + + + + + +
Hello
+ World! + +
One
+
Two
+ Three + +
Four
+ Five + +
Six
+ Seven + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java index 4d15e796c..dee39a666 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java @@ -451,9 +451,8 @@ public void testIssue364FootnoteCallLink() throws IOException { List annots0 = doc.getPage(0).getAnnotations(); List annots1 = doc.getPage(1).getAnnotations(); - // FIXME: There should only be one annot on each page. - assertEquals(2, annots0.size()); - assertEquals(2, annots1.size()); + assertEquals(1, annots0.size()); + assertEquals(1, annots1.size()); PDAnnotationLink link0 = (PDAnnotationLink) annots0.get(0); PDAnnotationLink link1 = (PDAnnotationLink) annots1.get(0); diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/FootnoteVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/FootnoteVisualRegressionTest.java index 475f1a363..ad621e895 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/FootnoteVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/FootnoteVisualRegressionTest.java @@ -15,7 +15,6 @@ import com.openhtmltopdf.visualtest.TestSupport; import com.openhtmltopdf.visualtest.VisualTester; -@Ignore @RunWith(PrintingRunner.class) public class FootnoteVisualRegressionTest { private VisualTester vt; @@ -43,6 +42,7 @@ public void configureTester() { * Tests that we support CSS footnotes. */ @Test + @Ignore public void testIssue364FootnotesBasicExample() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-basic")); } @@ -51,6 +51,7 @@ public void testIssue364FootnotesBasicExample() throws IOException { * Tests that a line of text can contain two multi-page footnotes. */ @Test + @Ignore public void testIssue364FootnotesMultiPage() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-multi-page")); } @@ -60,6 +61,7 @@ public void testIssue364FootnotesMultiPage() throws IOException { * do not fit on a single page. */ @Test + @Ignore public void testIssue364FootnotesTooLarge() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-too-large")); } @@ -70,6 +72,7 @@ public void testIssue364FootnotesTooLarge() throws IOException { * with the footnote area. */ @Test + @Ignore public void testIssue364FootnotesBlocks() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-blocks")); } @@ -79,6 +82,7 @@ public void testIssue364FootnotesBlocks() throws IOException { * presence of footnotes. */ @Test + @Ignore public void testIssue364FootnotesPaginatedTable() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-paginated-table")); } @@ -88,6 +92,7 @@ public void testIssue364FootnotesPaginatedTable() throws IOException { * above footnotes. */ @Test + @Ignore public void testIssue364FootnotesPositionedContent() throws IOException { assertTrue(vt.runTest("issue-364-footnotes-positioned-content")); } @@ -103,6 +108,7 @@ public void testIssue364FootnotesPositionedContent() throws IOException { * */ @Test + @Ignore public void testIssue364PositionedInsideFootnotes() throws IOException { assertTrue(vt.runTest("issue-364-positioned-inside-footnotes")); } @@ -119,6 +125,7 @@ public void testIssue364PositionedInsideFootnotes() throws IOException { * */ @Test + @Ignore public void testIssue364Floats() throws IOException { assertTrue(vt.runTest("issue-364-floats")); } @@ -127,6 +134,7 @@ public void testIssue364Floats() throws IOException { * Tests images in footnotes, as footnotes and intersecting with footnotes. */ @Test + @Ignore public void testIssue364Images() throws IOException { assertTrue(vt.runTest("issue-364-images")); } diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 782e9b90c..18aec8735 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -1433,6 +1433,14 @@ public void testIssue732NoBlockFormattingContext() throws IOException { assertTrue(vt.runTest("issue-732-no-bfc")); } + /** + * Tests that pseudo elements such as ::before are boxed correctly. + */ + @Test + public void testPseudoElements() throws IOException { + assertTrue(vt.runTest("pseudo-elements")); + } + /** * Tests a now-fixed near-infinite loop when page-break-inside: avoid * is used on heavily nested content.