diff --git a/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java b/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java index 78486e78d..5a5961b8c 100644 --- a/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java +++ b/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java @@ -13,13 +13,19 @@ import java.io.StringWriter; import java.nio.file.Files; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.imageio.ImageIO; import com.openhtmltopdf.util.Diagnostic; + +import org.apache.pdfbox.io.IOUtils; import org.w3c.dom.Element; import com.openhtmltopdf.bidi.support.ICUBidiReorderer; @@ -30,8 +36,11 @@ import com.openhtmltopdf.extend.FSTextBreaker; import com.openhtmltopdf.extend.OutputDevice; import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.TextDirection; +import com.openhtmltopdf.pdfboxout.visualtester.PdfVisualTester; +import com.openhtmltopdf.pdfboxout.visualtester.PdfVisualTester.PdfCompareResult; import com.openhtmltopdf.render.RenderingContext; import com.openhtmltopdf.svgsupport.BatikSVGDrawer; +import com.openhtmltopdf.testcases.TestcaseRunner; import com.openhtmltopdf.util.XRLogger; import com.openhtmltopdf.visualtest.Java2DVisualTester.Java2DBuilderConfig; import com.openhtmltopdf.visualtest.VisualTester.BuilderConfig; @@ -230,4 +239,42 @@ public boolean isReplacedObject(Element e) { } public static final BuilderConfig WITH_SHAPES_DRAWER = (builder) -> { builder.useObjectDrawerFactory(new ShapesObjectDrawerFactory()); }; + + public static boolean comparePdfs(byte[] actualPdfBytes, String resource) throws IOException { + File outputPath = new File("target/test/visual-tests/test-output/"); + + byte[] expectedPdfBytes; + try (InputStream expectedIs = TestcaseRunner.class.getResourceAsStream("/visualtest/expected/" + resource + ".pdf")) { + expectedPdfBytes = IOUtils.toByteArray(expectedIs); + } + + List problems = PdfVisualTester.comparePdfDocuments(expectedPdfBytes, actualPdfBytes, resource, false); + + if (!problems.isEmpty()) { + System.err.println("Found problems with test case (" + resource + "):"); + System.err.println(problems.stream().map(p -> p.logMessage).collect(Collectors.joining("\n ", "[\n ", "\n]"))); + + File outPdf = new File(outputPath, resource + "---actual.pdf"); + Files.write(outPdf.toPath(), actualPdfBytes); + } + + if (problems.stream().anyMatch(p -> p.testImages != null)) { + System.err.println("For test case (" + resource + ") writing diff images to '" + outputPath + "'"); + } + + for (PdfCompareResult result : problems) { + if (result.testImages != null) { + File output = new File(outputPath, resource + "---" + result.pageNumber + "---diff.png"); + ImageIO.write(result.testImages.createDiff(), "png", output); + + output = new File(outputPath, resource + "---" + result.pageNumber + "---actual.png"); + ImageIO.write(result.testImages.getActual(), "png", output); + + output = new File(outputPath, resource + "---" + result.pageNumber + "---expected.png"); + ImageIO.write(result.testImages.getExpected(), "png", output); + } + } + + return problems.isEmpty(); + } } diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-427-body-page-positions.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-427-body-page-positions.pdf new file mode 100644 index 000000000..7b1862801 Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/issue-427-body-page-positions.pdf differ diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-427-body-page-positions.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-427-body-page-positions.html new file mode 100644 index 000000000..9a9d98817 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/issue-427-body-page-positions.html @@ -0,0 +1,33 @@ + + + + + + +
Page one
+This is a test! + +
Fixed layer
+ +
This is page two.
+ +
Absolute layer on page two
+ +
This is page three.
+ +
+ Relative +
Abs in rel
+
+ + + 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 cc0c5bf60..a23dc6599 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java @@ -6,9 +6,11 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; +import java.awt.Color; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -24,6 +26,8 @@ import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo; import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; @@ -42,7 +46,10 @@ import org.junit.Assert; import org.junit.Test; +import com.openhtmltopdf.layout.Layer; import com.openhtmltopdf.outputdevice.helper.ExternalResourceControlPriority; +import com.openhtmltopdf.pdfboxout.PagePosition; +import com.openhtmltopdf.pdfboxout.PdfBoxRenderer; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.testcases.TestcaseRunner; import com.openhtmltopdf.util.Diagnostic; @@ -71,20 +78,30 @@ private static void render(String fileName, String html, BuilderConfig config) t System.err.println("Failed to render resource (" + fileName + ")"); e.printStackTrace(); } - + + writePdfToFile(fileName, actual); + } + + private static void writePdfToFile(String fileName, ByteArrayOutputStream actual) throws IOException { FileUtils.writeByteArrayToFile(new File(OUT_PATH, fileName + ".pdf"), actual.toByteArray()); } - - private static PDDocument run(String fileName, BuilderConfig config) throws IOException { + + private static String loadHtml(String fileName) throws IOException { String absResPath = RES_PATH + fileName + ".html"; - - byte[] htmlBytes = IOUtils - .toByteArray(TestcaseRunner.class.getResourceAsStream(absResPath)); - - String html = new String(htmlBytes, Charsets.UTF_8); + + try (InputStream is = TestcaseRunner.class.getResourceAsStream(absResPath)) { + byte[] htmlBytes = IOUtils + .toByteArray(is); + + return new String(htmlBytes, Charsets.UTF_8); + } + } + + private static PDDocument run(String fileName, BuilderConfig config) throws IOException { + String html = loadHtml(fileName); render(fileName, html, config); - + return load(fileName); } @@ -1093,6 +1110,65 @@ public void testIssue508FileEmbed() throws IOException { } } + /** + * Tests the PdfBoxRenderer::getPagePositions and + * PdfBoxRenderer::getLastYPositionOfContent apis. + * It does this by drawing a rect around each layer and comparing + * with the expected document. + */ + @Test + public void testIssue427GetBodyPagePositions() throws IOException { + String filename = "issue-427-body-page-positions"; + String html = loadHtml(filename); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + PdfRendererBuilder builder = new PdfRendererBuilder(); + + builder.withHtmlContent(html, null); + builder.useFastMode(); + builder.toStream(os); + + float lastContentLine; + + try (PdfBoxRenderer renderer = builder.buildPdfRenderer()) { + renderer.createPDFWithoutClosing(); + + List> posList = renderer.getLayersPositions(); + lastContentLine = renderer.getLastContentBottom(); + + int i = -1; + PDPageContentStream stream = null; + for (PagePosition pos : posList) { + if (i != pos.getPageNo()) { + if (stream != null) { + stream.close(); + } + + stream = new PDPageContentStream(renderer.getPdfDocument(), + renderer.getPdfDocument().getPage(pos.getPageNo()), AppendMode.APPEND, false, false); + stream.setLineWidth(1f); + stream.setStrokingColor(Color.ORANGE); + + i = pos.getPageNo(); + } + + + stream.addRect(pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight()); + stream.stroke(); + } + + if (stream != null) { + stream.close(); + } + + renderer.getPdfDocument().save(os); + writePdfToFile(filename, os); + } + + assertTrue(TestSupport.comparePdfs(os.toByteArray(), filename)); + assertEquals(111.48, lastContentLine, 0.5); + } + // TODO: // + More form controls. // + Custom meta info. diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PagePosition.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PagePosition.java index 3b86fd819..f4af94342 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PagePosition.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PagePosition.java @@ -19,59 +19,57 @@ */ package com.openhtmltopdf.pdfboxout; -public class PagePosition { - private String _id; - private int _pageNo; - private float _x; - private float _width; - private float _y; - private float _height; - +public class PagePosition { + private final String _id; + private final T _element; + private final int _pageNo; + private final float _x; + private final float _y; + private final float _width; + private final float _height; + + public PagePosition(String id, T element, int pageNo, float x, float y, float width, float height) { + this._id = id; + this._element = element; + this._pageNo = pageNo; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + } + public int getPageNo() { return _pageNo; } - - public void setPageNo(int pageNo) { - _pageNo = pageNo; - } - + public float getX() { return _x; } - - public void setX(float x) { - _x = x; - } - - public float getWidth() { - return _width; - } - - public void setWidth(float width) { - _width = width; - } - + public float getY() { return _y; } - - public void setY(float y) { - _y = y; + + public float getWidth() { + return _width; } - + public float getHeight() { return _height; } - - public void setHeight(float height) { - _height = height; - } public String getId() { return _id; } - public void setId(String id) { - _id = id; + public T getElement() { + return _element; + } + + @Override + public String toString() { + return String.format( + "PagePosition [_id=%s, _element=%s, _pageNo=%s, _x=%s, _y=%s, _width=%s, _height=%s]", + _id, _element, _pageNo, _x, _y, _width, _height); } } diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java index 157b2eb02..0dc93b0c1 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java @@ -53,8 +53,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; -import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; -import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.shading.PDShading; import org.apache.pdfbox.pdmodel.graphics.state.RenderingMode; @@ -71,6 +69,7 @@ import java.util.Map.Entry; import java.util.logging.Level; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class PdfBoxFastOutputDevice extends AbstractOutputDevice implements OutputDevice, PdfBoxOutputDevice { // @@ -1182,34 +1181,23 @@ protected PDFont mapFont(Font font, IFontTextDrawerEnv env) { } } - public List findPagePositionsByID(CssContext c, Pattern pattern) { + public List> findPagePositionsByID(CssContext c, Pattern pattern) { Map idMap = _sharedContext.getIdMap(); if (idMap == null) { return Collections.emptyList(); } - List result = new ArrayList<>(); - for (Entry entry : idMap.entrySet()) { - String id = (String) entry.getKey(); - if (pattern.matcher(id).find()) { - Box box = (Box) entry.getValue(); - PagePosition pos = calcPDFPagePosition(c, id, box); - if (pos != null) { - result.add(pos); - } - } - } - - Collections.sort(result, new Comparator() { - public int compare(PagePosition p1, PagePosition p2) { - return p1.getPageNo() - p2.getPageNo(); - } - }); - - return result; + return + idMap.entrySet() + .stream() + .filter(entry -> pattern.matcher(entry.getKey()).find()) + .map(entry -> calcPDFPagePosition(c, entry.getKey(), entry.getValue())) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(PagePosition::getPageNo)) + .collect(Collectors.toList()); } - private PagePosition calcPDFPagePosition(CssContext c, String id, Box box) { + private PagePosition calcPDFPagePosition(CssContext c, String id, Box box) { PageBox page = _root.getLayer().getLastPage(c, box); if (page == null) { return null; @@ -1220,15 +1208,8 @@ private PagePosition calcPDFPagePosition(CssContext c, String id, Box box) { x /= _dotsPerPoint; y /= _dotsPerPoint; - PagePosition result = new PagePosition(); - result.setId(id); - result.setPageNo(page.getPageNo()); - result.setX(x); - result.setY(y); - result.setWidth(box.getEffectiveWidth() / _dotsPerPoint); - result.setHeight(box.getHeight() / _dotsPerPoint); - - return result; + return new PagePosition( + id, box, page.getPageNo(), x, y, box.getEffectiveWidth() / _dotsPerPoint, box.getHeight() / _dotsPerPoint); } @Override diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java index ead654683..320adab2b 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java @@ -183,7 +183,7 @@ void drawStringFast(String s, float x, float y, JustificationInfo info, void drawWithGraphics(float x, float y, float width, float height, OutputDeviceGraphicsDrawer renderer); - List findPagePositionsByID(CssContext c, Pattern pattern); + List> findPagePositionsByID(CssContext c, Pattern pattern); void setRenderingContext(RenderingContext result); diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java index 28ebfdb77..c09d54933 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java @@ -35,23 +35,23 @@ import com.openhtmltopdf.outputdevice.helper.ExternalResourceControlPriority; import com.openhtmltopdf.outputdevice.helper.ExternalResourceType; import com.openhtmltopdf.extend.FSDOMMutator; -import com.openhtmltopdf.outputdevice.helper.NullUserInterface; import com.openhtmltopdf.outputdevice.helper.PageDimensions; import com.openhtmltopdf.outputdevice.helper.UnicodeImplementation; import com.openhtmltopdf.pdfboxout.PdfBoxSlowOutputDevice.Metadata; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder.CacheStore; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder.PdfAConformance; import com.openhtmltopdf.render.BlockBox; +import com.openhtmltopdf.render.Box; import com.openhtmltopdf.render.PageBox; import com.openhtmltopdf.render.RenderingContext; import com.openhtmltopdf.render.ViewportBox; import com.openhtmltopdf.render.displaylist.DisplayListCollector; import com.openhtmltopdf.render.displaylist.DisplayListContainer; import com.openhtmltopdf.render.displaylist.DisplayListPainter; +import com.openhtmltopdf.render.displaylist.PagedBoxCollector; import com.openhtmltopdf.render.displaylist.DisplayListContainer.DisplayListPageContainer; import com.openhtmltopdf.resource.XMLResource; import com.openhtmltopdf.simple.extend.XhtmlNamespaceHandler; -import com.openhtmltopdf.util.Configuration; import com.openhtmltopdf.util.LogMessageId; import com.openhtmltopdf.util.ThreadCtx; import com.openhtmltopdf.util.XRLog; @@ -83,10 +83,15 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.logging.Level; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class PdfBoxRenderer implements Closeable, PageSupplier { // See discussion of units at top of PdfBoxOutputDevice. @@ -1037,7 +1042,7 @@ public float getDotsPerPoint() { return _dotsPerPoint; } - public List findPagePositionsByID(Pattern pattern) { + public List> findPagePositionsByID(Pattern pattern) { return _outputDevice.findPagePositionsByID(newLayoutContext(), pattern); } @@ -1108,4 +1113,185 @@ public PDPage requestPage(PDDocument doc, float pageWidth, float pageHeight, int doc.addPage(page); return page; } + + /** + * Start page to end page and then top to bottom on page. + */ + private final Comparator> PAGE_POSITION_COMPARATOR = + Comparator.comparingInt(PagePosition::getPageNo) + .thenComparing(Comparator.comparingDouble(PagePosition::getY).reversed()); + + /** + * Returns the bottom Y postion in bottom-up PDF units + * on the last page of content. + * + * WARNING: NOT transform aware. + */ + public float getLastContentBottom() { + List> positions = getLayersPositions(); + + if (positions.isEmpty()) { + return 0; + } + + return positions.get(positions.size() - 1).getY(); + } + + /** + * Returns a list of page positions for all layers in the document. + * The page positions are sorted from first page to last and then top to bottom. + * The page position values are in bottom-up PDF units. + * + * WARNING: NOT transform aware. Transformed layers will return page + * positions that are not correct. + */ + public List> getLayersPositions() { + if (getRootBox() == null) { + this.layout(); + } + + Layer rootLayer = getRootBox().getLayer(); + + int[] whiches = new int[] { Layer.NEGATIVE, Layer.AUTO, Layer.ZERO, Layer.POSITIVE }; + + List layers = + Arrays.stream(whiches) + .mapToObj(rootLayer::collectLayers) + .flatMap(List::stream) + .collect(Collectors.toList()); + + RenderingContext ctx = newRenderingContext(); + List pages = rootLayer.getPages(); + + List> ret = new ArrayList<>(); + + ret.addAll(getLayerPagePositions(rootLayer, pages, ctx)); + + layers.stream() + .map(layer -> getLayerPagePositions(layer, pages, ctx)) + .forEach(ret::addAll); + + Collections.sort(ret, PAGE_POSITION_COMPARATOR); + + return ret; + } + + /** + * Returns a list of page positions for a single layer. + * The page positions are sorted from first page to last and then top to bottom. + * The page position values are in bottom-up PDF units. + * + * Compare to {@link #getLayersPositions()} which will return page + * positions for all layers. + * + * WARNING: NOT transform aware. A transformed layer will return page + * positions that are not correct. + */ + public List> getLayerPositions(Layer layer) { + RenderingContext ctx = newRenderingContext(); + List pages = layer.getPages(); + + List> ret = getLayerPagePositions(layer, pages, ctx); + + Collections.sort(ret, PAGE_POSITION_COMPARATOR); + + return ret; + } + + private List> getLayerPagePositions( + Layer layer, List pages, RenderingContext ctx) { + + // FIXME: This method is not transform aware. + + Box box = layer.getMaster(); + + int start = findStartPage(ctx, layer, pages); + int end = findEndPage(ctx, layer, pages); + + if (box.getStyle().isFixed()) { + PageBox page = pages.get(start); + + float x = box.getAbsX() + page.getMarginBorderPadding(ctx, CalculatedStyle.LEFT); + float w = box.getEffectiveWidth(); + float y = page.getMarginBorderPadding(ctx, CalculatedStyle.BOTTOM) + + (page.getPaintingBottom() - box.getAbsY() - box.getHeight()); + float h = box.getHeight(); + + return IntStream.range(0, pages.size()) + .mapToObj(pageNo -> createPagePosition(null, layer, pageNo, x, y, w, h)) + .collect(Collectors.toList()); + } + + List> ret = new ArrayList<>((end - start) + 1); + + for (int i = start; i <= end; i++) { + PageBox page = pages.get(i); + + float x = box.getAbsX() + page.getMarginBorderPadding(ctx, CalculatedStyle.LEFT); + float w = box.getEffectiveWidth(); + + float y; + float h; + + if (start != end) { + if (i != start && i != end) { + y = page.getMarginBorderPadding(ctx, CalculatedStyle.BOTTOM); + h = page.getContentHeight(ctx); + } else if (i == end) { + h = (box.getAbsY() + box.getHeight()) - page.getPaintingTop(); + y = page.getMarginBorderPadding(ctx, CalculatedStyle.BOTTOM) + + page.getContentHeight(ctx) - h; + } else { + assert i == start; + y = page.getMarginBorderPadding(ctx, CalculatedStyle.BOTTOM); + h = page.getPaintingBottom() - box.getAbsY(); + } + } else { + y = page.getMarginBorderPadding(ctx, CalculatedStyle.BOTTOM) + + (page.getPaintingBottom() - box.getAbsY() - box.getHeight()); + h = box.getHeight(); + } + + PagePosition pos = createPagePosition(null, layer, i, x, y, w, h); + + ret.add(pos); + } + + return ret; + } + + private PagePosition createPagePosition( + String id, T element, int pageNo, float x, float y, float w, float h) { + + return new PagePosition<>( + id, element, pageNo, x / _dotsPerPoint, y / _dotsPerPoint, w / _dotsPerPoint, h / _dotsPerPoint); + } + + /** + * Returns the start page for a layer. Transform aware. + */ + private int findStartPage(RenderingContext c, Layer layer, List pages) { + int start = PagedBoxCollector.findStartPage(c, layer.getMaster(), pages); + + // Floats maybe outside the master box. + for (BlockBox floater : layer.getFloats()) { + start = Math.min(start, PagedBoxCollector.findStartPage(c, floater, pages)); + } + + return start; + } + + /** + * Returns the end page number for a layer. Transform aware. + */ + private int findEndPage(RenderingContext c, Layer layer, List pages) { + int end = PagedBoxCollector.findEndPage(c, layer.getMaster(), pages); + + // Floats may be outside the master box. + for (BlockBox floater : layer.getFloats()) { + end = Math.max(end, PagedBoxCollector.findEndPage(c, floater, pages)); + } + + return end; + } } diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java index ffeb754e0..2156e3bb4 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java @@ -1326,26 +1326,26 @@ protected PDFont mapFont(Font font, IFontTextDrawerEnv env) { } } - public List findPagePositionsByID(CssContext c, Pattern pattern) { + public List> findPagePositionsByID(CssContext c, Pattern pattern) { Map idMap = _sharedContext.getIdMap(); if (idMap == null) { return Collections.emptyList(); } - List result = new ArrayList<>(); + List> result = new ArrayList<>(); for (Entry entry : idMap.entrySet()) { String id = (String) entry.getKey(); if (pattern.matcher(id).find()) { Box box = (Box) entry.getValue(); - PagePosition pos = calcPDFPagePosition(c, id, box); + PagePosition pos = calcPDFPagePosition(c, id, box); if (pos != null) { result.add(pos); } } } - Collections.sort(result, new Comparator() { - public int compare(PagePosition p1, PagePosition p2) { + Collections.sort(result, new Comparator>() { + public int compare(PagePosition p1, PagePosition p2) { return p1.getPageNo() - p2.getPageNo(); } }); @@ -1353,7 +1353,7 @@ public int compare(PagePosition p1, PagePosition p2) { return result; } - private PagePosition calcPDFPagePosition(CssContext c, String id, Box box) { + private PagePosition calcPDFPagePosition(CssContext c, String id, Box box) { PageBox page = _root.getLayer().getLastPage(c, box); if (page == null) { return null; @@ -1364,15 +1364,8 @@ private PagePosition calcPDFPagePosition(CssContext c, String id, Box box) { x /= _dotsPerPoint; y /= _dotsPerPoint; - PagePosition result = new PagePosition(); - result.setId(id); - result.setPageNo(page.getPageNo()); - result.setX(x); - result.setY(y); - result.setWidth(box.getEffectiveWidth() / _dotsPerPoint); - result.setHeight(box.getHeight() / _dotsPerPoint); - - return result; + return new PagePosition<>( + id, box, page.getPageNo(), x, y, box.getEffectiveWidth() / _dotsPerPoint, box.getHeight() / _dotsPerPoint); } public void setRenderingContext(RenderingContext result) {