diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java index d239b7008..188206c81 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java @@ -468,6 +468,15 @@ public void reset(LayoutContext c) { if (_markerData != null) { _markerData.restorePreviousReferenceLine(this); } + + if (hasFootnotes()) { + // Reset usually happens when satisfying widows and orphans. + // Reset means we and our descendants are about to be layed out again + // so we have to remove footnotes as they will be added again, possible on + // a new page. + c.getRootLayer().getFirstPage(c, this).removeFootnoteBodies(c, getReferencedFootnoteBodies()); + } + super.reset(c); } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/DescendantBoxSpliterator.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/DescendantBoxSpliterator.java new file mode 100644 index 000000000..fbee30808 --- /dev/null +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/DescendantBoxSpliterator.java @@ -0,0 +1,180 @@ +package com.openhtmltopdf.util; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + +import com.openhtmltopdf.render.BlockBox; +import com.openhtmltopdf.render.Box; +import com.openhtmltopdf.render.InlineLayoutBox; + +/** + * A spliterator that can be used to create a stream of descendant boxes in + * breadth first order. + *

+ * NOTE: This is likely slower than simply creating a box list recursively for small + * number of descendants but gets relatively faster (and uses less memory) + * as the list of descendants increases in size. + */ +public class DescendantBoxSpliterator implements Spliterator { + // A queue of boxes that we have to process their children. + private final Deque unprocessed = new ArrayDeque<>(); + + private State curState = State.BOX_CHILDREN; + private int childIndex = 0; + + private enum State { + BOX_CHILDREN, + BLOCK_INLINE_CONTENT, + ILB_INLINE_CHILDREN, + DONE; + } + + public DescendantBoxSpliterator(Box parent) { + unprocessed.add(parent); + } + + private boolean hasChildren(Box bx) { + return + bx.getChildCount() > 0 || + (bx instanceof BlockBox && !((BlockBox) bx).getInlineContent().isEmpty()) || + (bx instanceof InlineLayoutBox && ((InlineLayoutBox) bx).getInlineChildCount() > 0); + } + + private void add(Box bx) { + unprocessed.add(bx); + } + + private void next() { + setState(State.BOX_CHILDREN); + unprocessed.remove(); + } + + private boolean hasNext() { + return !unprocessed.isEmpty(); + } + + private Box current() { + return unprocessed.peek(); + } + + private void setState(State newState) { + curState = newState; + childIndex = 0; + } + + private Box getNext() { + Box box = current(); + + if (box == null) { + return null; + } + + switch (curState) { + case BOX_CHILDREN: { + if (childIndex < box.getChildCount()) { + Box next = box.getChild(childIndex); + childIndex++; + + if (hasChildren(next)) { + add(next); + } + + return next; + } + + setState(State.BLOCK_INLINE_CONTENT); + // FALL-THRU + } + + case BLOCK_INLINE_CONTENT: { + if (box instanceof BlockBox) { + BlockBox block = (BlockBox) box; + Box next = getNextInlineChild(block.getInlineContent()); + + if (next != null) { + return next; + } + } + + setState(State.ILB_INLINE_CHILDREN); + // FALL-THRU + } + + case ILB_INLINE_CHILDREN: { + if (box instanceof InlineLayoutBox) { + InlineLayoutBox ilb = (InlineLayoutBox) box; + Box next = getNextInlineChild(ilb.getInlineChildren()); + + if (next != null) { + return next; + } + } + + setState(State.DONE); + // FALL-THRU + } + + default: + next(); + return null; + } + } + + private Box getNextInlineChild(List inlineChilds) { + if (inlineChilds == null) { + return null; + } + + while (childIndex < inlineChilds.size()) { + Object inlineChild = inlineChilds.get(childIndex); + childIndex++; + + if (inlineChild instanceof Box) { + if (hasChildren((Box) inlineChild)) { + add((Box) inlineChild); + } + return (Box) inlineChild; + } + } + + return null; + } + + @Override + public boolean tryAdvance(Consumer action) { + Box next = getNext(); + + if (next != null) { + action.accept(next); + return true; + } else { + while (hasNext()) { + Box next2 = getNext(); + if (next2 != null) { + action.accept(next2); + return true; + } + } + } + + return false; + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + @Override + public int characteristics() { + return Spliterator.IMMUTABLE | Spliterator.NONNULL; + } +} diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java index 20cfd1388..4d6c7d640 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java @@ -6,10 +6,10 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import com.openhtmltopdf.layout.Layer; import com.openhtmltopdf.render.Box; -import com.openhtmltopdf.render.InlineLayoutBox; public class LambdaUtil { private LambdaUtil() { } @@ -28,35 +28,25 @@ public static Stream ancestors(Box bx) { return list.stream(); } - private static void descendants(Box bx, List out) { - if (bx != null && bx.getChildren() != null) { - out.addAll(bx.getChildren()); - for (Box box : bx.getChildren()) { - descendants(box, out); - - if (box instanceof InlineLayoutBox) { - List inlineChilds = ((InlineLayoutBox) box).getInlineChildren(); - - if (inlineChilds != null) { - for (Object obj : inlineChilds) { - if (obj instanceof Box) { - out.add((Box) obj); - descendants((Box) obj, out); - } - } - } - } - } - } + /** + * A stream of all descendant boxes not including + * InlineText or InlineBox objects. + * + * This would usually only be called after layout is concluded + * as InlineBox objects are converted to one or more InlineLayoutBox + * during layout. + * + * Should be in breadth first order. + */ + public static Stream descendants(Box parent) { + return StreamSupport.stream(new DescendantBoxSpliterator(parent), false); } /** - * A stream of all descendant boxes not including InlineText objects. + * See {@link #descendants(Box)} */ - public static Stream descendants(Box bx) { - List list = new ArrayList<>(); - descendants(bx, list); - return list.stream(); + public static List descendantsList(Box parent) { + return descendants(parent).collect(Collectors.toList()); } /**