Browse Source

Technocrat Pagination fixes (#171)

* Technocrat Pagination fixes

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* [TE/pagination] Add fixes from rebase to 0.4.3 previously uncommitted.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Add code to handle what appears to be a bug in Chromium.

Content that's starting to overflow gets offset to the right of the
existing content, for some strange reason. Recognise that as overflow
too.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correct a typo.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Add comparison of ranges in checking overflow equality.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remove layout repeat check - shouldn't be needed and assumes node always set.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* If we're removing all the content from a row, remove the whole row.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Ignore colgroup tags in finding overflow.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Treat break-inside:avoid-column like avoid.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remove old comment about not setting column widths.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Set the width from width attrib first if available.

Better than getBoundingClientRect result in some cases.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Clone colgroup attrib if it exists when rebuilding tree.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remember the start of the remainder separately to 'node'.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Handle siblingRangeStart also being the end of the doc.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Revert "Treat break-inside:avoid-column like avoid."

This reverts commit 045655f09852c6a88bf7af652dbc72592b0c45c7.

The commit breaks relaxtorium's sample and doesn't seem to fix
anything else anymore.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Improve logic for deciding whether an entire row is being removed.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Setting columnWidth causes text to be cut off / no bottom border sometimes

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Improve readability, correct ignoreSides logic, default false.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Disable window.scrollTo if not debugging.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Work on getting docker tests running.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Named pages re-add.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* startIsNode correction.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Handle contents that can't be paginated.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Disable setting data-split-from to make tests pass for now.

(Comment added in PR).

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Rebuild table rows taking account of row spans, initial cut.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correct comment.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Get detected overflow's non overflowing text on original page.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correct error in rowspan code.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Mathjax test needs to check math elements, not parents.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* needsBreak check adjustments.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* mustSplit DOES need to look at height, not bottom.

(This is the does-it-fit-in-a-page check).

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Reenable data-split-from - gets justification right.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Further clarification on comments.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Look for overflow from the top of the render tree.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Re-add afterOverflowRemoved event trigger.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Silence npm run test complaints.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Work so far on getting footnotes interacting with new overflow code.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* More work on footnotes.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* More footnotes work.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Looking like working apart from adding footnote overflow to last page?

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Fix handling of deferred notes.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Fix relative paths to polyfill in specs

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Work on forced breaks.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* 38 pages.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Spec polyfill path correction.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* More footnote changes.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Getting 3rd footnote on footnote-policy.html

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Go back to modified main footnote.js.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Adjust footnote code for new breakToken structure.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Add afterOverflowAdded hook and use to re-add overflowed footnotes.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Ensure we always add a last page if there's overflow & recalc bounds.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Only make page add condition, not sure about breakToken.overflow.length check

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Trying to get footnotes.html working.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Treat overflow as one if force parent height.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Don't use notePolicyDelta if < 0

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Ensure entire footnote is taken as overflow if textOffset is 0.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Prevent getStart looping when we're just rendering overflow at the end.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Fix infinite loop, but correctly?

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remove (I think wrongly) unsetting margin/padding top on split content.

(To make tests pass).

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Work on page number recto/verso odd/even correction.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correct English.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* More footnote overflow work.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Page attribute copying fix.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Re-add margin-top and padding-top to data-split-from for test passing.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Test fix

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Restore setting columnWidth (this broken TE contracts?)

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Partial fix for footnotes.html - getting underflow now.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Set max pages for now.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Get start should use breakToken.node

It's the next node to be processed.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Check loops should stop at rendered.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Use this.avoidBreakInside.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Ensure break happens for colspan with break-inside avoid.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Make avoidBreakInside look at first node too.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Don't remove rowspan attrib at the moment.

Needed for current use case but still need to check others.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* avoid-break-rowspan almost rendering right with our without break avoid.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remove a dangling TR with no content.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Add math tag to dangling child pruning.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correct textBreak.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Get rowspans working right when mixed with normal rows.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* I think the possible Chromium bug got resolved with footnotes changes.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* In getStartElement, use node that will be added next.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Don't add end overflow page if previous page empty.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Allow for hooks generating more overflow.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Get afterOverflowAdded adding footnotes correctly.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Continue named class on a footnotes only overflow page.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Break after recto testing polyfill path.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Clear MAX_PAGES and correct add page mistake.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Don't add named page classes on the basis of a node that will be in overflow.

I think we should actually look for nodes in the page with the page attrib
after overflow is removed, but let's revisit this later.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* No max pages.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Back to possible Chromium bug - footnotes.html text overflow missing o/wise.

On footnotes.html, the "existence of similar letters" overflow on p5 isn't
detected unless we look at the right bounds. Must be a better way but I
don't know what it is yet.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Prevent split-from being set for content moved entirely to the next page.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Add overflow footnotes back.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Fix split-from being incorrectly set sometimes.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Don't start by assuming we need to go back to previous row.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Modify extracting footnote overflow for footnotes-counter-reset-page.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Update image snapshots for avoid-break-rowspan.

Getting width consistent across the break now.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Update exceeding-rowspan-table image snapshots.

Not obeying break-inside:avoid for tr in main.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* splits-tables-rowspan: Main doesn't obey rowspan correctly.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* generate-content/content-none. Images looks same but spec says different.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Fix typo.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remove ignoreSides logic - no Chromium bug. It's display grid on the page.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correct typo in footnotes range set.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Improve dangling, unsplit rendered child removal.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Invoke afterOverflowRemoved after all overflow is removed.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Make indexOfTextNode handle hyphenated text.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Take account of margins in textBreak for non-block parents.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Ensure split-to gets set when it should.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Don't clear the data-split-to in textBreak, so we don't confuse ourselves.

If it is cleared and there is margin modified by the attribute, we
might think there's overflow again.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Get setting the split-to and split-from attributes right.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Use overflow_after rather than split-to as the critieria for justifying?

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Fix typo in class name addition.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* In splits, just look for the data-ref.

If text is broken, overflow-after will be on a parent but not the
lowest text node (can't be).

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Update the rebuild-table-rows test - 3 pages now.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Correction to removing an entire footnote.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* named page spec image snapshot update.

Can't see a difference but the code can.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Make isEntireNote handle startContainer is the footnote.


Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Another named page snapshot needs updating.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Update footnotes lastpage snapshot.

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>

* Remove getOverflow hook - ended up being an unneeded addition.


Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Remove window.scrollTo debugging code.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Remove debugging commented out console log.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Remove console.log.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Remove unneeded comment / addition, update tests.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Add comment explaining the removal of parent containers.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* One let ChildBounds to rule them all.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Use hyphen settings in indexOfTextNode.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Add test for copying column widths across a split (failing).

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

* Make column-width test pass.

Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>

---------

Signed-off-by: Nigel Cunningham <nigel@technocrat.com.au>
Signed-off-by: Nigel Cunningham <nigel@nigelcunningham.com.au>
main
Nigel Cunningham 10 months ago committed by GitHub
parent
commit
6c8cf0c4b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Dockerfile
  2. 2
      specs/breaks/break-after/break-after-left/break-after-left.html
  3. 2
      specs/breaks/break-after/break-after-recto/break-after-recto.html
  4. BIN
      specs/breaks/table/__image_snapshots__/avoid-break-rowspan-spec-js-breaks-table-avoid-break-rowspan-should-create-a-pdf-2-snap.png
  5. 2
      specs/counters/counter-page/counter-page.html
  6. 2
      specs/counters/counter-pages/counter-pages.html
  7. 2
      specs/counters/nested/nested.html
  8. BIN
      specs/generate-content/content-none/__image_snapshots__/content-none-spec-js-content-none-should-create-a-pdf-1-snap.png
  9. BIN
      specs/generate-content/content-none/__image_snapshots__/content-none-spec-js-content-none-should-create-a-pdf-2-snap.png
  10. 2
      specs/math/mathjax.spec.js
  11. 2
      specs/position-fixed/position-fixed.html
  12. BIN
      specs/splits/tables/__image_snapshots__/exceeding-rowspan-table-spec-js-rowspan-uneven-table-should-create-a-pdf-1-snap.png
  13. BIN
      specs/splits/tables/__image_snapshots__/exceeding-rowspan-table-spec-js-rowspan-uneven-table-should-create-a-pdf-2-snap.png
  14. BIN
      specs/splits/tables/__image_snapshots__/rowspan-table-spec-js-rowspan-table-should-create-a-pdf-1-snap.png
  15. BIN
      specs/splits/tables/__image_snapshots__/rowspan-table-spec-js-rowspan-table-should-create-a-pdf-2-snap.png
  16. BIN
      specs/tables/copy-column-widths/__image_snapshots__/copy-column-widths-spec-js-copy-column-widths-should-create-a-pdf-1-snap.png
  17. BIN
      specs/tables/copy-column-widths/__image_snapshots__/copy-column-widths-spec-js-copy-column-widths-should-create-a-pdf-2-snap.png
  18. BIN
      specs/tables/copy-column-widths/__image_snapshots__/copy-column-widths-spec-js-copy-column-widths-should-create-a-pdf-3-snap.png
  19. 203
      specs/tables/copy-column-widths/copy-column-widths.html
  20. 35
      specs/tables/copy-column-widths/copy-column-widths.spec.js
  21. BIN
      specs/tables/rebuild/__image_snapshots__/rebuild-spec-js-rebuild-table-rows-should-create-a-pdf-2-snap.png
  22. BIN
      specs/tables/rebuild/__image_snapshots__/rebuild-spec-js-rebuild-table-rows-should-create-a-pdf-3-snap.png
  23. BIN
      specs/tables/rebuild/__image_snapshots__/rebuild-spec-js-rebuild-table-rows-should-create-a-pdf-4-snap.png
  24. 5
      specs/tables/rebuild/rebuild.spec.js
  25. 73
      src/chunker/breaktoken.js
  26. 61
      src/chunker/chunker.js
  27. 1043
      src/chunker/layout.js
  28. 31
      src/chunker/overflow.js
  29. 19
      src/chunker/page.js
  30. 19
      src/modules/paged-media/atpage.js
  31. 155
      src/modules/paged-media/footnotes.js
  32. 2
      src/modules/paged-media/splits.js
  33. 263
      src/utils/dom.js

2
Dockerfile

@ -43,7 +43,7 @@ RUN apt-get update && \
apt-get install -y vim && \
rm -rf /var/lib/apt/lists/*
RUN npm install npm@latest -g
#RUN npm install npm@latest -g
RUN npm install -g node-gyp
RUN mkdir -p $DIRECTORY

2
specs/breaks/break-after/break-after-left/break-after-left.html

@ -8,7 +8,7 @@
Break after left
</title>
<script src="../../../../../dist/paged.polyfill.js"></script>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>

2
specs/breaks/break-after/break-after-recto/break-after-recto.html

@ -8,7 +8,7 @@
Break after recto
</title>
<script src="../../../../../dist/paged.polyfill.js"></script>
<script src="../../../../dist/paged.polyfill.js"></script>
<style>
:root {

BIN
specs/breaks/table/__image_snapshots__/avoid-break-rowspan-spec-js-breaks-table-avoid-break-rowspan-should-create-a-pdf-2-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

2
specs/counters/counter-page/counter-page.html

@ -7,7 +7,7 @@
Counter Pages
</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<script src="../../../dist/paged.polyfill.js"></script>
<style>

2
specs/counters/counter-pages/counter-pages.html

@ -7,7 +7,7 @@
Counter Pages
</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<script src="../../../dist/paged.polyfill.js"></script>
<style>

2
specs/counters/nested/nested.html

@ -7,7 +7,7 @@
multiple counters
</title>
<script src="../../../../dist/paged.polyfill.js"></script>
<script src="../../../dist/paged.polyfill.js"></script>
<style>

BIN
specs/generate-content/content-none/__image_snapshots__/content-none-spec-js-content-none-should-create-a-pdf-1-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
specs/generate-content/content-none/__image_snapshots__/content-none-spec-js-content-none-should-create-a-pdf-2-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

2
specs/math/mathjax.spec.js

@ -15,7 +15,7 @@ describe("default", () => {
});
it("mathjax elements should not throw an exception", async () => {
let count = await page.$$eval("mjx-container" , (r) => {
let count = await page.$$eval("math" , (r) => {
// eslint-disable-next-line no-console
return r.length;
});

2
specs/position-fixed/position-fixed.html

@ -7,7 +7,7 @@
<title>
The Project Gutenberg eBook of Auroræ: Their Characters and Spectra, by J. Rand Capron.
</title>
<script src="../../../dist/paged.polyfill.js"></script>
<script src="../../dist/paged.polyfill.js"></script>
<style>
@page {
size: letter;

BIN
specs/splits/tables/__image_snapshots__/exceeding-rowspan-table-spec-js-rowspan-uneven-table-should-create-a-pdf-1-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
specs/splits/tables/__image_snapshots__/exceeding-rowspan-table-spec-js-rowspan-uneven-table-should-create-a-pdf-2-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
specs/splits/tables/__image_snapshots__/rowspan-table-spec-js-rowspan-table-should-create-a-pdf-1-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
specs/splits/tables/__image_snapshots__/rowspan-table-spec-js-rowspan-table-should-create-a-pdf-2-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
specs/tables/copy-column-widths/__image_snapshots__/copy-column-widths-spec-js-copy-column-widths-should-create-a-pdf-1-snap.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
specs/tables/copy-column-widths/__image_snapshots__/copy-column-widths-spec-js-copy-column-widths-should-create-a-pdf-2-snap.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
specs/tables/copy-column-widths/__image_snapshots__/copy-column-widths-spec-js-copy-column-widths-should-create-a-pdf-3-snap.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

203
specs/tables/copy-column-widths/copy-column-widths.html

@ -0,0 +1,203 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="../../../dist/paged.polyfill.js"></script>
<style>
table { border-collapse: collapse; width: 100%; }
td, th { border: 1px solid gray; }
th { background: gray; }
</style>
</head>
<body>
<table>
<thead>
<th>A</th>
<th>B</th>
</thead>
<tbody>
<script>
// Make a table big enough so that the THEAD of the next table
// should be split.
for (var i = 1; i <= 38; i++) {
document.write(`<tr><td>test ${i}</td><td>m2</td></tr>`);
}
</script>
</tbody>
</table>
<table>
<thead>
<th>This table heading should be sit at the bottom of the page and want to be split. It should get moved onto the next page and repeated for the additional pages.</th>
<th>B</th>
</thead>
<tbody>
<script>
// Make a table big enough so that the THEAD of the next table
// should be split.
for (var i = 1; i <= 100; i++) {
document.write(`<tr><td>test ${i}</td><td>m2</td></tr>`);
}
</script>
</tbody>
</table>
<script>
// Repeat thead on each page (PagedJS cut tables in pieces, one for each page)
// https://gitlab.pagedmedia.org/tools/pagedjs/issues/84#note_2322
class RepeatingTableHeadersHandler extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
this.splitTablesRefs = [];
}
afterPageLayout(pageElement, page, breakToken, chunker) {
this.chunker = chunker;
this.splitTablesRefs = [];
if (breakToken) {
const node = breakToken.overflow[0].node;
const tables = this.findAllAncestors(node, "table");
if (node.tagName === "TABLE") {
tables.push(node);
}
if (tables.length > 0) {
this.splitTablesRefs = tables.map(t => t.dataset.ref);
//checks if split inside thead and if so, set breakToken to next sibling element
let thead = node.tagName === "THEAD" ? node : this.findFirstAncestor(node, "thead");
if (thead) {
let lastTheadNode = thead.hasChildNodes() ? thead.lastChild : thead;
breakToken.node = this.nodeAfter(lastTheadNode, chunker.source);
}
this.hideEmptyTables(pageElement, node);
}
}
}
hideEmptyTables(pageElement, breakTokenNode) {
this.splitTablesRefs.forEach(ref => {
let table = pageElement.querySelector("[data-ref='" + ref + "']");
if (table) {
let sourceBody = table.querySelector("tbody > tr");
if (!sourceBody || this.refEquals(sourceBody.firstElementChild, breakTokenNode)) {
table.style.visibility = "hidden";
table.style.position = "absolute";
let lineSpacer = table.nextSibling;
if (lineSpacer) {
lineSpacer.style.visibility = "hidden";
lineSpacer.style.position = "absolute";
}
}
}
});
}
refEquals(a, b) {
return a && a.dataset && b && b.dataset && a.dataset.ref === b.dataset.ref;
}
findFirstAncestor(element, selector) {
while (element.parentNode && element.parentNode.nodeType === 1) {
if (element.parentNode.matches(selector)) {
return element.parentNode;
}
element = element.parentNode;
}
return null;
}
findAllAncestors(element, selector) {
const ancestors = [];
while (element.parentNode && element.parentNode.nodeType === 1) {
if (element.parentNode.matches(selector)) {
ancestors.unshift(element.parentNode);
}
element = element.parentNode;
}
return ancestors;
}
// The addition of repeating Table Headers is done here because this hook is triggered before overflow handling
layout(rendered, layout) {
this.splitTablesRefs.forEach(ref => {
const renderedTable = rendered.querySelector("[data-ref='" + ref + "']");
if (renderedTable) {
// this event can be triggered multiple times
// added a flag repeated-headers to control when table headers already repeated in current page.
if (!renderedTable.getAttribute("repeated-headers")) {
const sourceTable = this.chunker.source.querySelector("[data-ref='" + ref + "']");
this.repeatColgroup(sourceTable, renderedTable);
this.repeatTHead(sourceTable, renderedTable);
renderedTable.setAttribute("repeated-headers", true);
}
}
});
}
repeatColgroup(sourceTable, renderedTable) {
let colgroup = sourceTable.querySelectorAll("colgroup");
let firstChild = renderedTable.firstChild;
colgroup.forEach((colgroup) => {
let clonedColgroup = colgroup.cloneNode(true);
renderedTable.insertBefore(clonedColgroup, firstChild);
});
}
repeatTHead(sourceTable, renderedTable) {
let thead = sourceTable.querySelector("thead");
if (thead) {
let clonedThead = thead.cloneNode(true);
renderedTable.insertBefore(clonedThead, renderedTable.firstChild);
}
}
// the functions below are from pagedjs utils/dom.js
nodeAfter(node, limiter) {
if (limiter && node === limiter) {
return;
}
let significantNode = this.nextSignificantNode(node);
if (significantNode) {
return significantNode;
}
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) {
return;
}
significantNode = this.nextSignificantNode(node);
if (significantNode) {
return significantNode;
}
}
}
}
nextSignificantNode(sib) {
while ((sib = sib.nextSibling)) {
if (!this.isIgnorable(sib)) return sib;
}
return null;
}
isIgnorable(node) {
return (node.nodeType === 8) || // A comment node
((node.nodeType === 3) && this.isAllWhitespace(node)); // a text node, all whitespace
}
isAllWhitespace(node) {
return !(/[^\t\n\r ]/.test(node.textContent));
}
}
Paged.registerHandlers(RepeatingTableHeadersHandler);
</script>
</html>

35
specs/tables/copy-column-widths/copy-column-widths.spec.js

@ -0,0 +1,35 @@
const TIMEOUT = 10000;
describe("copy-column-widths", () => {
let page;
beforeAll(async () => {
page = await loadPage("tables/copy-column-widths/copy-column-widths.html");
return page.rendered;
}, TIMEOUT);
afterAll(async () => {
if (!DEBUG) {
await page.close();
}
});
xit("should render 3 pages", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => {
return r.length;
});
expect(pages).toEqual(3);
});
if (!DEBUG) {
it("should create a pdf", async () => {
let pdf = await page.pdf(PDF_SETTINGS);
expect(pdf).toMatchPDFSnapshot(1);
expect(pdf).toMatchPDFSnapshot(2);
expect(pdf).toMatchPDFSnapshot(3);
});
}
}
);

BIN
specs/tables/rebuild/__image_snapshots__/rebuild-spec-js-rebuild-table-rows-should-create-a-pdf-2-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 116 KiB

BIN
specs/tables/rebuild/__image_snapshots__/rebuild-spec-js-rebuild-table-rows-should-create-a-pdf-3-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 106 KiB

BIN
specs/tables/rebuild/__image_snapshots__/rebuild-spec-js-rebuild-table-rows-should-create-a-pdf-4-snap.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

5
specs/tables/rebuild/rebuild.spec.js

@ -13,12 +13,12 @@ describe("rebuild-table-rows", () => {
}
});
xit("should render 4 pages", async () => {
xit("should render 3 pages", async () => {
let pages = await page.$$eval(".pagedjs_page", (r) => {
return r.length;
});
expect(pages).toEqual(4);
expect(pages).toEqual(3);
});
@ -29,7 +29,6 @@ describe("rebuild-table-rows", () => {
expect(pdf).toMatchPDFSnapshot(1);
expect(pdf).toMatchPDFSnapshot(2);
expect(pdf).toMatchPDFSnapshot(3);
expect(pdf).toMatchPDFSnapshot(4);
});
}
}

73
src/chunker/breaktoken.js

@ -1,57 +1,64 @@
import {
isElement
} from "../utils/dom.js";
/**
* BreakToken
* @class
*/
class BreakToken {
constructor(node, offset) {
constructor(node, overflowArray) {
this.node = node;
this.offset = offset;
this.overflow = overflowArray || [];
this.finished = false;
this.breakNeededAt = [];
}
equals(otherBreakToken) {
if (!otherBreakToken) {
if (this.node !== otherBreakToken.node) {
return false;
}
if (this["node"] && otherBreakToken["node"] &&
this["node"] !== otherBreakToken["node"]) {
if (otherBreakToken.overflow.length !== this.overflow.length) {
return false;
}
if (this["offset"] && otherBreakToken["offset"] &&
this["offset"] !== otherBreakToken["offset"]) {
return false;
for (const index in this.overflow) {
if (!this.overflow[index].equals(otherBreakToken.overflow[index])) {
return false;
}
}
let otherQueue = otherBreakToken.getForcedBreakQueue();
for (const index in this.breakNeededAt) {
if (!this.breakNeededAt[index].isEqualNode(otherQueue[index])) {
return false;
}
}
return true;
}
toJSON(hash) {
let node;
let index = 0;
if (!this.node) {
return {};
}
if (isElement(this.node) && this.node.dataset.ref) {
node = this.node.dataset.ref;
} else if (hash) {
node = this.node.parentElement.dataset.ref;
}
setFinished() {
this.finished = true;
}
if (this.node.parentElement) {
const children = Array.from(this.node.parentElement.childNodes);
index = children.indexOf(this.node);
}
isFinished() {
return this.finished;
}
addNeedsBreak(needsBreak) {
this.breakNeededAt.push(needsBreak);
}
getNextNeedsBreak() {
return this.breakNeededAt.shift();
}
return JSON.stringify({
"node": node,
"index" : index,
"offset": this.offset
});
getForcedBreakQueue() {
return this.breakNeededAt;
}
setForcedBreakQueue(queue) {
return this.breakNeededAt = queue;
}
}
export default BreakToken;
export default BreakToken;

61
src/chunker/chunker.js

@ -6,9 +6,8 @@ import Queue from "../utils/queue.js";
import {
requestIdleCallback
} from "../utils/utils.js";
import { OverflowContentError } from "./renderresult.js";
const MAX_PAGES = false;
const MAX_PAGES = null;
const MAX_LAYOUTS = false;
const TEMPLATE = `
@ -106,6 +105,7 @@ class Chunker {
this.hooks.layoutNode = new Hook(this);
this.hooks.onOverflow = new Hook(this);
this.hooks.afterOverflowRemoved = new Hook(this);
this.hooks.afterOverflowAdded = new Hook(this);
this.hooks.onBreakToken = new Hook();
this.hooks.beforeRenderResult = new Hook(this);
this.hooks.afterPageLayout = new Hook(this);
@ -332,48 +332,59 @@ class Chunker {
async *layout(content, startAt) {
let breakToken = startAt || false;
let tokens = [];
let page, prevPage, prevNumPages;
while (breakToken !== undefined && (MAX_PAGES ? this.total < MAX_PAGES : true)) {
if (breakToken && breakToken.node) {
await this.handleBreaks(breakToken.node);
} else {
await this.handleBreaks(content.firstChild);
let addedExtra = false;
let emptyBody = !page || !page.area.firstElementChild.childElementCount || !page.area.firstElementChild.firstElementChild.getBoundingClientRect().height;
let emptyFootnotes = !page || !page.footnotesArea.firstElementChild.childElementCount || !page.footnotesArea.firstElementChild.firstElementChild.getBoundingClientRect().height;
let emptyPage = (emptyBody && emptyFootnotes);
prevNumPages = this.total;
if (!page || !emptyPage) {
if (breakToken) {
if (breakToken.overflow.length && breakToken.overflow[0].node) {
// Overflow.
await this.handleBreaks(breakToken.overflow[0].node);
}
else {
await this.handleBreaks(breakToken.node);
}
} else {
await this.handleBreaks(content.firstChild);
}
}
addedExtra = this.total != prevNumPages;
// Don't add a page if we have a forced break now and we just
// did a break due to overflow but have nothing displayed on
// the current page, unless there's overflow and we're finished.
if (!page || addedExtra || !emptyPage) {
this.addPage();
}
let page = this.addPage();
page = this.pages[this.total - 1];
await this.hooks.beforePageLayout.trigger(page, content, breakToken, this);
this.emit("page", page);
// Layout content in the page, starting from the breakToken
breakToken = await page.layout(content, breakToken, this.maxChars);
if (breakToken) {
let newToken = breakToken.toJSON(true);
if (tokens.lastIndexOf(newToken) > -1) {
// loop
let err = new OverflowContentError("Layout repeated", [breakToken.node]);
console.error("Layout repeated at: ", breakToken.node);
return err;
} else {
tokens.push(newToken);
}
}
// Layout content in the page, starting from the breakToken.
breakToken = await page.layout(content, breakToken, prevPage);
await this.hooks.afterPageLayout.trigger(page.element, page, breakToken, this);
await this.hooks.finalizePage.trigger(page.element, page, undefined, this);
this.emit("renderedPage", page);
prevPage = page.wrapper;
this.recoredCharLength(page.wrapper.textContent.length);
yield breakToken;
// Stop if we get undefined, showing we have reached the end of the content
}
}
recoredCharLength(length) {

1043
src/chunker/layout.js

File diff suppressed because it is too large Load Diff

31
src/chunker/overflow.js

@ -0,0 +1,31 @@
/**
* Overflow
* @class
*/
class Overflow {
constructor(node, offset, overflowHeight, range) {
this.node = node;
this.offset = offset;
this.overflowHeight = overflowHeight;
this.range = range;
}
equals(otherOffset) {
if (!otherOffset) {
return false;
}
if (this["node"] && otherOffset["node"] &&
this["node"] !== otherOffset["node"]) {
return false;
}
if (this["offset"] && otherOffset["offset"] &&
this["offset"] !== otherOffset["offset"]) {
return false;
}
return true;
}
}
export default Overflow;

19
src/chunker/page.js

@ -15,7 +15,6 @@ class Page {
this.height = undefined;
this.hooks = hooks;
this.settings = options || {};
// this.element = this.create(this.pageTemplate);
@ -43,7 +42,6 @@ class Page {
let size = area.getBoundingClientRect();
area.style.columnWidth = Math.round(size.width) + "px";
area.style.columnGap = "calc(var(--pagedjs-margin-right) + var(--pagedjs-margin-left) + var(--pagedjs-bleed-right) + var(--pagedjs-bleed-left) + var(--pagedjs-column-gap-offset))";
// area.style.overflow = "scroll";
@ -121,22 +119,21 @@ class Page {
}
*/
async layout(contents, breakToken, maxChars) {
async layout (contents, breakToken, prevPage) {
this.clear();
this.startToken = breakToken;
let settings = this.settings;
if (!settings.maxChars && maxChars) {
settings.maxChars = maxChars;
}
this.layoutMethod = new Layout(this.area, this.hooks, settings);
this.layoutMethod = new Layout(this.area, this.hooks, this.settings);
let renderResult = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
let renderResult = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken, prevPage);
let newBreakToken = renderResult.breakToken;
if (breakToken && newBreakToken && breakToken.equals(newBreakToken)) {
return;
}
this.addListeners(contents);
this.endToken = newBreakToken;
@ -251,7 +248,7 @@ class Page {
return;
}
let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents, this.startToken);
let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents, undefined, this.startToken);
if (newBreakToken) {
this.endToken = newBreakToken;

19
src/modules/paged-media/atpage.js

@ -1694,7 +1694,8 @@ class AtPage extends Handler {
}
getStartElement(content, breakToken) {
let node = breakToken && breakToken.node;
// If we have a breaktoken, we want the first node that will be added next.
let node = breakToken && (breakToken.overflow[0]?.node || breakToken.node);
if (!content && !breakToken) {
return;
@ -1734,6 +1735,22 @@ class AtPage extends Handler {
// page.element.querySelector('.paged_area').style.color = red;
}
afterPageLayout(page, contents, breakToken, chunker) {
let thisPage = chunker.pages[chunker.pages.length - 1];
// If only footnotes were added, attribs should be like the previous page.
let emptyBody = !thisPage.area.firstElementChild || !thisPage.area.firstElementChild.childElementCount || !thisPage.area.firstElementChild.firstElementChild.getBoundingClientRect().height;
let emptyFootnotes = !thisPage.footnotesArea.firstElementChild.childElementCount || !thisPage.footnotesArea.firstElementChild.firstElementChild.getBoundingClientRect().height;
if (emptyBody && !emptyFootnotes && chunker.pages.length > 1) {
// Start element for the previous page.
let prevBreakToken = chunker.pages[chunker.pages.length - 2].startToken;
let start = this.getStartElement(contents, prevBreakToken);
if (start) {
this.addPageAttributes(thisPage, start, chunker.pages);
}
}
}
finalizePage(fragment, page, breakToken, chunker) {
for (let m in this.marginalia) {
let margin = this.marginalia[m];

155
src/modules/paged-media/footnotes.js

@ -1,5 +1,5 @@
import Handler from "../handler.js";
import { isContainer, isElement } from "../../utils/dom.js";
import { isContainer, isElement, isText } from "../../utils/dom.js";
import Layout from "../../chunker/layout.js";
import csstree from "css-tree";
@ -9,6 +9,7 @@ class Footnotes extends Handler {
this.footnotes = {};
this.needsLayout = [];
this.overflow = [];
}
onDeclaration(declaration, dItem, dList, rule) {
@ -211,47 +212,12 @@ class Footnotes extends Handler {
}
}
moveFootnote(node, pageArea, needsNoteCall) {
// let pageArea = node.closest(".pagedjs_area");
let noteArea = pageArea.querySelector(".pagedjs_footnote_area");
let noteContent = noteArea.querySelector(".pagedjs_footnote_content");
let noteInnerContent = noteContent.querySelector(".pagedjs_footnote_inner_content");
if (!isElement(node)) {
return;
}
// Add call for the note
let noteCall;
if (needsNoteCall) {
noteCall = this.createFootnoteCall(node);
}
// Remove the break before attribute for future layout
node.removeAttribute("data-break-before");
// Check if note already exists for overflow
let existing = noteInnerContent.querySelector(`[data-ref="${node.dataset.ref}"]`);
if (existing) {
// Remove the note from the flow but no need to render it again
node.remove();
return;
}
// Add the note node
noteInnerContent.appendChild(node);
recalcFootnotesHeight(node, noteContent, pageArea, noteCall, needsNoteCall) {
// Remove empty class
if (noteContent.classList.contains("pagedjs_footnote_empty")) {
noteContent.classList.remove("pagedjs_footnote_empty");
}
// Add marker
node.dataset.footnoteMarker = node.dataset.ref;
// Add Id
node.id = `note-${node.dataset.ref}`;
// Get note content size
let height = noteContent.scrollHeight;
@ -264,6 +230,7 @@ class Footnotes extends Handler {
// Check element sizes
let noteCallBounds = noteCall && noteCall.getBoundingClientRect();
let noteArea = pageArea.querySelector(".pagedjs_footnote_area");
let noteAreaBounds = noteArea.getBoundingClientRect();
// Get the @footnote margins
@ -342,17 +309,64 @@ class Footnotes extends Handler {
"--pagedjs-footnotes-height",
`${height + noteContentMargins + noteContentBorders}px`
);
} else {
} else if (notePolicyDelta > 0) {
// set height to just before note call
pageArea.style.setProperty(
"--pagedjs-footnotes-height",
`${noteAreaBounds.height + notePolicyDelta}px`
);
let noteInnerContent = noteContent.querySelector(".pagedjs_footnote_inner_content");
noteInnerContent.style.height =
noteAreaBounds.height + notePolicyDelta - total + "px";
}
}
moveFootnote(node, pageArea, needsNoteCall) {
// let pageArea = node.closest(".pagedjs_area");
let noteArea = pageArea.querySelector(".pagedjs_footnote_area");
let noteContent = noteArea.querySelector(".pagedjs_footnote_content");
let noteInnerContent = noteContent.querySelector(".pagedjs_footnote_inner_content");
if (!isElement(node)) {
return;
}
// Add call for the note but only if it's not overflow.
// If it is overflow, the parentElement will be null.
let noteCall;
if (needsNoteCall) {
if (node.parentElement) {
noteCall = this.createFootnoteCall(node);
}
else {
let ref = node.dataset['ref'];
noteCall = pageArea.querySelector(`[data-ref="${ref}"]`);
}
}
// Remove the break before attribute for future layout
node.removeAttribute("data-break-before");
// Check if note already exists for overflow
let existing = noteInnerContent.querySelector(`[data-ref="${node.dataset.ref}"]`);
if (existing) {
// Remove the note from the flow but no need to render it again
node.remove();
return;
}
// Add the note node
noteInnerContent.appendChild(node);
// Add marker
node.dataset.footnoteMarker = node.dataset.ref;
// Add Id
node.id = `note-${node.dataset.ref}`;
this.recalcFootnotesHeight(node, noteContent, pageArea, noteCall, needsNoteCall);
}
createFootnoteCall(node) {
let parentElement = node.parentElement;
let footnoteCall = document.createElement("a");
@ -391,16 +405,26 @@ class Footnotes extends Handler {
let overflow = layout.findOverflow(noteInnerContent, noteContentBounds);
if (overflow) {
let { startContainer, startOffset } = overflow;
let startIsNode;
if (isElement(startContainer)) {
let start = startContainer.childNodes[startOffset];
startIsNode = isElement(start) && start.hasAttribute("data-footnote-marker");
let { startContainer, startOffset } = overflow[0];
let extracted, parentElement = startContainer.parentElement;
let footnoteContainer = isText(startContainer) ?
startContainer.parentElement.closest('[data-footnote-marker]') :
startContainer.closest('[data-footnote-marker]');
let isEntireNote = (footnoteContainer &&
(footnoteContainer == startContainer ||
(footnoteContainer.childNodes[0] == startContainer && !startOffset)));
if (isEntireNote) {
// Adjust the range to take the entire footnote.
let range = document.createRange();
range.selectNode(footnoteContainer);
range.setEndAfter(footnoteContainer)
extracted = range.extractContents();
}
else {
// Assuming overflow is not multipart.
extracted = overflow[0].extractContents();
let extracted = overflow.extractContents();
if (!startIsNode) {
let splitChild = extracted.firstElementChild;
splitChild.dataset.splitFrom = splitChild.dataset.ref;
@ -433,20 +457,21 @@ class Footnotes extends Handler {
chunker.clonePage(page);
} else {
let breakBefore, previousBreakAfter;
let firstOverflowNode = breakToken.overflow[0]?.node;
if (
breakToken.node &&
typeof breakToken.node.dataset !== "undefined" &&
typeof breakToken.node.dataset.previousBreakAfter !== "undefined"
firstOverflowNode &&
typeof firstOverflowNode.dataset !== "undefined" &&
typeof firstOverflowNode.dataset.previousBreakAfter !== "undefined"
) {
previousBreakAfter = breakToken.node.dataset.previousBreakAfter;
previousBreakAfter = firstOverflowNode.dataset.previousBreakAfter;
}
if (
breakToken.node &&
typeof breakToken.node.dataset !== "undefined" &&
typeof breakToken.node.dataset.breakBefore !== "undefined"
firstOverflowNode &&
typeof firstOverflowNode.dataset !== "undefined" &&
typeof firstOverflowNode.dataset.breakBefore !== "undefined"
) {
breakBefore = breakToken.node.dataset.breakBefore;
breakBefore = firstOverflowNode.dataset.breakBefore;
}
if (breakBefore || previousBreakAfter) {
@ -493,6 +518,7 @@ class Footnotes extends Handler {
let call = removed.querySelector(`[data-footnote-call="${note.dataset.ref}"]`);
if (call) {
note.remove();
this.overflow.push(note);
}
}
// Hide footnote content if empty
@ -502,6 +528,27 @@ class Footnotes extends Handler {
}
}
afterOverflowAdded(rendered) {
let notes = rendered.querySelectorAll("[data-note='footnote']");
if (notes && notes.length) {
this.findVisibleFootnotes(notes, rendered);
}
let area = rendered.closest(".pagedjs_area");
let noteContent = area.querySelector(".pagedjs_footnote_content");
let notesInnerContent = area.querySelector(".pagedjs_footnote_inner_content");
if (this.overflow.length) {
this.overflow.forEach((item) => {
notesInnerContent.appendChild(item);
let call = rendered.querySelector(`[data-ref="${item.dataset['ref']}"]`)
this.recalcFootnotesHeight(item, noteContent, area, call, false);
});
this.overflow = [];
}
}
marginsHeight(element, total=true) {
let styles = window.getComputedStyle(element);
let marginTop = parseInt(styles.marginTop);

2
src/modules/paged-media/splits.js

@ -20,7 +20,7 @@ class Splits extends Handler {
let from; // Capture the last from element
splits.forEach((split) => {
let ref = split.dataset.ref;
from = prevPage.querySelector("[data-ref='"+ ref +"']:not([data-split-to])");
from = prevPage.querySelector("[data-ref='"+ ref +"']");
if (from) {
from.dataset.splitTo = ref;

263
src/utils/dom.js

@ -1,4 +1,6 @@
import { getBoundingClientRect } from "./utils.js";
export function isElement(node) {
return node && node.nodeType === 1;
}
@ -133,48 +135,236 @@ export function stackChildren(currentNode, stacked) {
return stack;
}
export function rebuildAncestors(node) {
export function rebuildTableRow(node, alreadyRendered, existingChildren) {
let currentCol = 0, maxCols = 0, nextInitialColumn = 0;
let rebuilt = node.cloneNode(false);
const initialColumns = Array.from(node.children);
// Find the max number of columns.
let earlierRow = node.parentElement.children[0];
while (earlierRow && earlierRow !== node) {
if (earlierRow.children.length > maxCols) {
maxCols = earlierRow.children.length;
}
earlierRow = earlierRow.nextElementSibling;
}
// The next td to use in each tr.
// Doesn't take account of rowspans above that might make extra columns.
let rowOffsets = Array(maxCols).fill(0);
// Duplicate rowspans and our initial columns.
while (currentCol < maxCols) {
let earlierRow = node.parentElement.children[0];
let earlierRowIndex = 0;
let rowspan, column;
// Find the nth column we'll duplicate (rowspan) or use.
while (earlierRow && earlierRow !== node) {
if (rowspan == undefined) {
column = earlierRow.children[currentCol - rowOffsets[nextInitialColumn]];
if (column && column.rowSpan !== undefined && column.rowSpan > 1) {
rowspan = column.rowSpan;
}
}
// If rowspan === 0 the entire remainder of the table row is used.
if (rowspan) {
// Tracking how many rows in the overflow.
if (rowspan < 2) {
rowspan = undefined;
}
else {
rowspan--;
}
}
earlierRow = earlierRow.nextElementSibling;
earlierRowIndex++;
}
let destColumn;
if (rowspan) {
if (!existingChildren) {
destColumn = column.cloneNode(false);
// Adjust rowspan value.
destColumn.rowSpan = !column.rowSpan ? 0 : rowspan;
}
} else {
// Fill the gap with the initial columns (if exists).
destColumn = column = initialColumns[nextInitialColumn++]?.cloneNode(false);
}
if (column && destColumn) {
if (alreadyRendered) {
let existing = findElement(column, alreadyRendered);
if (existing) {
column = existing;
}
}
let width = column.width || getBoundingClientRect(column).width + 'px';
if (width) {
destColumn.setAttribute("width", width);
}
if (destColumn) {
rebuilt.appendChild(destColumn);
}
}
currentCol++;
}
return rebuilt;
}
export function rebuildTree (node, fragment, alreadyRendered) {
let parent, ancestor;
let ancestors = [];
let added = [];
let dupSiblings = false;
let freshPage = !fragment;
let numListItems = 0;
let fragment = document.createDocumentFragment();
if (!fragment) {
fragment = document.createDocumentFragment();
}
// Gather all ancestors
let element = node;
if (!isText(node)) {
ancestors.unshift(node);
if (node.tagName == "LI") {
numListItems++;
}
}
while (element.parentNode && element.parentNode.nodeType === 1) {
ancestors.unshift(element.parentNode);
if (element.parentNode.tagName == "LI") {
numListItems++;
}
element = element.parentNode;
}
for (var i = 0; i < ancestors.length; i++) {
ancestor = ancestors[i];
let container, split;
if (added.length) {
container = added[added.length - 1];
} else {
container = fragment;
}
if (ancestor.nodeName == "TR") {
parent = findElement(ancestor, container);
if (!parent) {
parent = rebuildTableRow(ancestor, alreadyRendered, container.childElementCount);
container.appendChild(parent);
}
}
else if (dupSiblings) {
let sibling = ancestor.parentElement ? ancestor.parentElement.children[0] : ancestor;
while (sibling) {
let existing = findElement(sibling, container), siblingClone;
if (!existing) {
let split = inIndexOfRefs(ancestor, alreadyRendered);
siblingClone = cloneNodeAncestor(sibling, split);
if (alreadyRendered) {
let originalElement = findElement(sibling, alreadyRendered);
if (originalElement) {
let width = originalElement.width || getBoundingClientRect(originalElement).width + 'px';
if (width) {
siblingClone.setAttribute("width", width);
}
}
}
container.appendChild(siblingClone);
}
// Handle rowspan on table
if (node.nodeName === "TR") {
let previousRow = node.previousElementSibling;
let previousRowDistance = 1;
while (previousRow) {
// previous row has more columns, might indicate a rowspan.
if (previousRow.childElementCount > node.childElementCount) {
const initialColumns = Array.from(node.children);
while (node.firstChild) {
node.firstChild.remove();
if (sibling == ancestor) {
parent = siblingClone || existing;
}
let k = 0;
for (let j = 0; j < previousRow.children.length; j++) {
let column = previousRow.children[j];
if (column.rowSpan && column.rowSpan > previousRowDistance) {
const duplicatedColumn = column.cloneNode(true);
// Adjust rowspan value
duplicatedColumn.rowSpan = column.rowSpan - previousRowDistance;
// Add the column to the row
node.appendChild(duplicatedColumn);
} else {
// Fill the gap with the initial columns (if exists)
const initialColumn = initialColumns[k++];
// The initial column can be undefined if the newly created table has less columns than the original table
if (initialColumn) {
node.appendChild(initialColumn);
sibling = sibling.nextElementSibling;
}
} else {
parent = findElement(ancestor, container);
if (!parent) {
parent = cloneNodeAncestor(ancestor);
if (alreadyRendered) {
let originalElement = findElement(ancestor, alreadyRendered);
if (originalElement) {
let width = originalElement.width || getBoundingClientRect(originalElement).width;
if (!isNaN(width) && width) {
parent.setAttribute("width", width + "px");
}
// Colgroup to clone?
Array.from(originalElement.children).forEach(child => {
if (child.tagName == "COLGROUP") {
parent.append(child.cloneNode(true));
}
});
}
}
container.appendChild(parent);
}
previousRow = previousRow.previousElementSibling;
previousRowDistance++;
}
split = inIndexOfRefs(ancestor, alreadyRendered);
if (split) {
setSplit(split, parent);
}
dupSiblings = (ancestor.nodeName !== "TR" && ancestor.dataset.clonesiblings == true);
added.push(parent);
if (ancestor.tagName == "LI") {
numListItems--;
}
if (freshPage && (isText(node) || numListItems)) {
// Flag the first node on the page so we can suppress list styles on
// a continued item and list item numbers except the list one
// if an item number should be printed.
parent.dataset.suppressListStyle = true;
}
}
added = undefined;
return fragment;
}
function setSplit(orig, clone) {
clone.setAttribute("data-split-from", clone.getAttribute("data-ref"));
// This will let us split a table with multiple columns correctly.
orig.setAttribute("data-split-to", clone.getAttribute("data-ref"));
}
function cloneNodeAncestor (node) {
let result = node.cloneNode(false);
if (result.hasAttribute("id")) {
let dataID = result.getAttribute("id");
result.setAttribute("data-id", dataID);
result.removeAttribute("id");
}
// This is handled by css :not, but also tidied up here
if (result.hasAttribute("data-break-before")) {
result.removeAttribute("data-break-before");
}
if (result.hasAttribute("data-previous-break-after")) {
result.removeAttribute("data-previous-break-after");
}
return result;
}
export function rebuildAncestors (node) {
let parent, ancestor;
let ancestors = [];
let added = [];
let fragment = document.createDocumentFragment();
// Gather all ancestors
let element = node;
while(element.parentNode && element.parentNode.nodeType === 1) {
@ -242,7 +432,7 @@ export function split(bound, cutElement, breakAfter) {
}
// Create a fragment with rebuilt ancestors
let fragment = rebuildAncestors(cutElement);
let fragment = rebuildTree(cutElement);
// Clone cut
if (!breakAfter) {
@ -479,7 +669,14 @@ export function cloneNode(n, deep=false) {
return n.cloneNode(deep);
}
export function inIndexOfRefs(node, doc) {
if (!doc || !doc.indexOfRefs) return;
const ref = node.getAttribute("data-ref");
return doc.indexOfRefs[ref];
}
export function findElement(node, doc, forceQuery) {
if (!doc) return;
const ref = node.getAttribute("data-ref");
return findRef(ref, doc, forceQuery);
}
@ -586,11 +783,15 @@ export function hasTextContent(node) {
return false;
}
export function indexOfTextNode(node, parent) {
export function indexOfTextNode(node, parent, hyphen) {
if (!isText(node)) {
return -1;
}
let nodeTextContent = node.textContent;
// Remove hyphenation if necessary.
if (nodeTextContent.substring(nodeTextContent.length - hyphen.length) == hyphen) {
nodeTextContent = nodeTextContent.substring(0, nodeTextContent.length - hyphen.length);
}
let child;
let index = -1;
for (var i = 0; i < parent.childNodes.length; i++) {

Loading…
Cancel
Save