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 extends Object> 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 super Box> 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