Commit b77cb92e authored by Fred Chasen's avatar Fred Chasen

Update overflow methods

parent fdb31dcd
......@@ -70,6 +70,7 @@ Handlers have methods that correspond to the hooks for the parsing, layout and r
```js
// Chunker
beforeParsed(content)
afterParsed(parsed)
beforePageLayout(page)
afterPageLayout(pageElement, page, breakToken)
......@@ -82,6 +83,10 @@ onAtPage(atPageNode)
onRule(ruleNode)
onDeclaration(declarationNode, ruleNode)
onContent(contentNode, declarationNode, ruleNode)
// Previewer
beforePolishing(stylesheets)
beforeChunking(contents)
```
## Setup
......
This diff is collapsed.
......@@ -6,7 +6,7 @@ import {
needsBreakBefore,
needsBreakAfter
} from "../utils/dom";
const MAX_PAGES = false;
const MAX_PAGES = 10;
const TEMPLATE = `<div class="pagedjs_page">
<div class="pagedjs_margin-top-left-corner-holder">
......@@ -59,6 +59,7 @@ class Chunker {
// this.preview = preview;
this.hooks = {};
this.hooks.beforeParsed = new Hook(this);
this.hooks.afterParsed = new Hook(this);
this.hooks.beforePageLayout = new Hook(this);
this.hooks.layout = new Hook(this);
......@@ -93,7 +94,13 @@ class Chunker {
}
async flow(content, renderTo) {
let parsed = new ContentParser(content);
let parsed;
await this.hooks.beforeParsed.trigger(content, this);
parsed = new ContentParser(content);
this.source = parsed;
this.setup(renderTo);
......@@ -179,7 +186,7 @@ class Chunker {
if (page) {
await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this);
this.emit("page", page);
await this.hooks.layout.trigger(page.element, page, undefined, this);
// await this.hooks.layout.trigger(page.element, page, undefined, this);
await this.hooks.afterPageLayout.trigger(page.element, page, undefined, this);
this.emit("renderedPage", page);
}
......@@ -204,7 +211,7 @@ class Chunker {
// Layout content in the page, starting from the breakToken
breakToken = page.layout(content, breakToken);
await this.hooks.layout.trigger(page.element, page, breakToken, this);
// await this.hooks.layout.trigger(page.element, page, breakToken, this);
await this.hooks.afterPageLayout.trigger(page.element, page, breakToken, this);
this.emit("renderedPage", page);
......@@ -230,24 +237,26 @@ class Chunker {
if (!blank) {
// Listen for page overflow
page.onOverflow((overflow) => {
_requestIdleCallback(() => {
let index = this.pages.indexOf(page) + 1;
if (index < this.pages.length &&
(this.pages[index].breakBefore || this.pages[index].previousBreakAfter)) {
let newPage = this.insertPage(index - 1);
newPage.prepend(overflow);
} else if (index < this.pages.length) {
this.pages[index].prepend(overflow);
} else {
let newPage = this.addPage();
newPage.prepend(overflow);
}
})
page.onOverflow((overflowToken) => {
// console.log("overflow on", page.id, overflowToken);
let index = this.pages.indexOf(page) + 1;
if (index < this.pages.length &&
(this.pages[index].breakBefore || this.pages[index].previousBreakAfter)) {
let newPage = this.insertPage(index - 1);
newPage.layout(this.source, overflowToken);
} else if (index < this.pages.length) {
this.pages[index].layout(this.source, overflowToken);
} else {
let newPage = this.addPage();
newPage.layout(this.source, overflowToken);
}
});
page.onUnderflow(() => {
// console.log("underflow on", page.id);
page.onUnderflow((overflowToken) => {
// console.log("underflow on", page.id, overflowToken);
// page.append(this.source, overflowToken);
});
}
......@@ -274,15 +283,13 @@ class Chunker {
if (!blank) {
// Listen for page overflow
page.onOverflow((overflow) => {
_requestIdleCallback(() => {
if (total < this.pages.length) {
this.pages[total].prepend(overflow);
} else {
let newPage = this.addPage();
newPage.prepend(overflow);
}
})
page.onOverflow((overflowToken) => {
if (total < this.pages.length) {
this.pages[total].layout(this.source, overflowToken);
} else {
let newPage = this.addPage();
newPage.layout(this.source, overflowToken);
}
});
page.onUnderflow(() => {
......
This diff is collapsed.
......@@ -52,15 +52,17 @@ class Page {
this.element = page;
this.area = area;
return page;
}
createWrapper() {
let wrapper = document.createElement("div");
// wrapper.setAttribute("contenteditable", true);
// wrapper.style.outline = "none";
this.area.appendChild(wrapper);
this.wrapper = wrapper;
return page;
return wrapper;
}
index(pgnum) {
......@@ -111,18 +113,24 @@ class Page {
layout(contents, breakToken) {
// console.log("layout page", this.id);
let size = this.area.getBoundingClientRect();
this.l = new Layout(this.area, this.wrapper, this.hooks);
this.clear();
this.l.onOverflow((overflow) => {
this._onOverflow && this._onOverflow(overflow);
});
this.layoutMethod = new Layout(this.area, this.hooks);
this.l.onUnderflow((overflow) => {
this._onUnderflow && this._onUnderflow(overflow);
});
breakToken = this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
breakToken = this.l.layout(size, contents, {}, {}, breakToken);
this.addListeners(contents);
return breakToken;
}
append(contents, breakToken) {
if (!this.layoutMethod) {
return this.layout(contents, breakToken);
}
breakToken = this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
return breakToken;
}
......@@ -146,29 +154,104 @@ class Page {
this._onUnderflow = func;
}
prepend(fragment) {
if (!this.l) {
this.l = new Layout(this.area, this.wrapper, this.hooks);
clear() {
this.removeListeners();
this.wrapper && this.wrapper.remove();
this.createWrapper();
}
addListeners(contents) {
if (typeof ResizeObserver !== "undefined") {
this.addResizeObserver(contents);
} else {
this.element.addEventListener("overflow", this.checkOverflowAfterResize.bind(this, contents), false);
this.element.addEventListener("underflow", this.checkOverflowAfterResize.bind(this, contents), false);
}
// TODO: fall back to mutation observer?
this.l.onOverflow((overflow) => {
this._onOverflow && this._onOverflow(overflow);
});
// Key scroll width from changing
this.element.addEventListener("scroll", () => {
if(this.listening) {
this.element.scrollLeft = 0;
}
});
this.listening = true;
return true;
}
this.l.onUnderflow((overflow) => {
this._onUnderflow && this._onUnderflow(overflow);
});
removeListeners() {
this.listening = false;
// clearTimeout(this.timeoutAfterResize);
if (this.element) {
this.element.removeEventListener("overflow", this.checkOverflowAfterResize.bind(this), false);
this.element.removeEventListener("underflow", this.checkOverflowAfterResize.bind(this), false);
}
if (this.ro) {
this.ro.disconnect();
}
}
addResizeObserver(contents) {
let wrapper = this.wrapper;
let prevHeight = wrapper.getBoundingClientRect().height;
this.ro = new ResizeObserver( entries => {
if (!this.listening) {
return;
}
for (let entry of entries) {
const cr = entry.contentRect;
if (cr.height > prevHeight) {
this.checkOverflowAfterResize(contents);
prevHeight = wrapper.getBoundingClientRect().height;
} else if (cr.height < prevHeight ) { // TODO: calc line height && (prevHeight - cr.height) >= 22
this.checkUnderflowAfterResize(contents);
prevHeight = cr.height;
}
}
});
this.l.prepend(fragment);
this.ro.observe(wrapper);
}
append() {
checkOverflowAfterResize(contents) {
if (!this.listening || !this.layoutMethod) {
return;
}
let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents);
if (newBreakToken) {
this._onOverflow && this._onOverflow(newBreakToken);
}
}
checkUnderflowAfterResize(contents) {
if (!this.listening || !this.layoutMethod) {
return;
}
let endToken = this.layoutMethod.findEndToken(this.wrapper, contents);
// let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents);
if (endToken) {
this._onUnderflow && this._onUnderflow(endToken);
}
}
destroy() {
this.removeListeners();
this.element = undefined;
this.wrapper = undefined;
}
}
......
......@@ -54,15 +54,19 @@ class ContentParser {
let node;
while(node = treeWalker.nextNode()) {
let uuid = UUID();
node.setAttribute("data-ref", uuid);
if (!node.hasAttribute("data-ref")) {
let uuid = UUID();
node.setAttribute("data-ref", uuid);
}
if (node.id) {
node.setAttribute("data-id", node.id);
}
node.setAttribute("data-children", node.childNodes.length);
node.setAttribute("data-text", node.textContent.trim().length);
// node.setAttribute("data-children", node.childNodes.length);
// node.setAttribute("data-text", node.textContent.trim().length);
}
}
......
......@@ -2,7 +2,7 @@ import EventEmitter from "event-emitter";
class Handler {
constructor(chunker, polisher, caller) {
let hooks = Object.assign({}, chunker && chunker.hooks, polisher && polisher.hooks);
let hooks = Object.assign({}, chunker && chunker.hooks, polisher && polisher.hooks, caller && caller.hooks);
this.chunker = chunker;
this.polisher = polisher;
this.caller = caller;
......
......@@ -908,7 +908,7 @@ class AtPage extends Handler {
marginGroup.style["grid-template-columns"] = leftWidth + " 1fr " + leftWidth;
}
}else{
if(rightWidth !== "none" && rightWidth !== "auto"){
if(rightWidth !== "none" && rightWidth !== "auto"){
marginGroup.style["grid-template-columns"] = rightWidth + " 1fr " + rightWidth;
}else{
marginGroup.style["grid-template-columns"] = "auto auto 1fr";
......@@ -946,7 +946,7 @@ class AtPage extends Handler {
right.style["white-space"] = "normal";
center.style["white-space"] = "normal";
}
}
}
}else if(centerWidth !== "none" && centerWidth !== "auto"){
if(leftContent && leftWidth !== "none" && leftWidth !== "auto"){
marginGroup.style["grid-template-columns"] = leftWidth + " " + centerWidth + " 1fr";
......@@ -961,14 +961,14 @@ class AtPage extends Handler {
}else{
if(leftContent){
if(!rightContent){
marginGroup.style["grid-template-columns"] = "1fr 0 0";
marginGroup.style["grid-template-columns"] = "1fr 0 0";
}else{
if(leftWidth !== "none" && leftWidth !== "auto"){
if(rightWidth !== "none" && rightWidth !== "auto"){
marginGroup.style["grid-template-columns"] = leftWidth + " 1fr " + rightWidth;
}else{
marginGroup.style["grid-template-columns"] = leftWidth + " 0 1fr";
}
}
}else{
if(rightWidth !== "none" && rightWidth !== "auto"){
marginGroup.style["grid-template-columns"] = "1fr 0 " + rightWidth;
......@@ -983,16 +983,16 @@ class AtPage extends Handler {
marginGroup.style["grid-template-columns"] = "minmax(16.66%, " + newLeftWidth + "%) 0 1fr";
left.style["white-space"] = "normal";
right.style["white-space"] = "normal";
}
}
}
}
}
}else{
if(rightWidth !== "none" && rightWidth !== "auto"){
marginGroup.style["grid-template-columns"] = "1fr 0 " + rightWidth;
}else{
marginGroup.style["grid-template-columns"] = "0 0 1fr";
}
}
}
}
}
});
......@@ -1033,7 +1033,7 @@ class AtPage extends Handler {
marginGroup.style["grid-template-rows"] = topHeight + " calc(100% - " + topHeight + "*2) " + topHeight;
}
}else{
if(bottomHeight !== "none" && bottomHeight !== "auto"){
if(bottomHeight !== "none" && bottomHeight !== "auto"){
marginGroup.style["grid-template-rows"] = bottomHeight + " calc(100% - " + bottomHeight + "*2) " + bottomHeight;
}
}
......@@ -1042,7 +1042,7 @@ class AtPage extends Handler {
if(bottomHeight !== "none" && bottomHeight !== "auto"){
marginGroup.style["grid-template-rows"] = bottomHeight + " calc(100% - " + bottomHeight + "*2) " + bottomHeight;
}
}
}
}else{
if(topContent && topHeight !== "none" && topHeight !== "auto"){
marginGroup.style["grid-template-rows"] = topHeight +" " + middleHeight + " calc(100% - (" + topHeight + " + " + middleHeight + "))";
......@@ -1057,33 +1057,33 @@ class AtPage extends Handler {
}else{
if(topContent){
if(!bottomContent){
marginGroup.style["grid-template-rows"] = "1fr 0 0";
marginGroup.style["grid-template-rows"] = "1fr 0 0";
}else{
if(topHeight !== "none" && topHeight !== "auto"){
if(bottomHeight !== "none" && bottomHeight !== "auto"){
marginGroup.style["grid-template-rows"] = topHeight + " 1fr " + bottomHeight;
}else{
marginGroup.style["grid-template-rows"] = topHeight + " 0 1fr";
}
}
}else{
if(bottomHeight !== "none" && bottomHeight !== "auto"){
marginGroup.style["grid-template-rows"] = "1fr 0 " + bottomHeight;
}else{
marginGroup.style["grid-template-rows"] = "1fr 0 1fr";
}
}
}
}
}
}else{
if(bottomHeight !== "none" && bottomHeight !== "auto"){
marginGroup.style["grid-template-rows"] = "1fr 0 " + bottomHeight;
}else{
marginGroup.style["grid-template-rows"] = "0 0 1fr";
}
}
}
}
}
});
}
......
import Handler from "../handler";
import csstree from 'css-tree';
import { split, rebuildAncestors, elementAfter } from "../../utils/dom";
import { rebuildAncestors, elementAfter } from "../../utils/dom";
class Breaks extends Handler {
constructor(chunker, polisher, caller) {
......@@ -147,7 +147,7 @@ class Breaks extends Handler {
}
}
layout(pageElement, page) {
afterLayout(pageElement, page) {
this.addBreakAttributes(page);
}
}
......
import EventEmitter from "event-emitter";
import Hook from "../utils/hook";
import Chunker from '../chunker/chunker';
import Polisher from '../polisher/polisher';
......@@ -15,6 +16,11 @@ class Previewer {
// Chunk contents
this.chunker = new Chunker();
// Hooks
this.hooks = {};
this.hooks.beforePolishing = new Hook(this);
this.hooks.beforeChunking = new Hook(this);
// default size
this.size = {
width: {
......@@ -110,28 +116,32 @@ class Previewer {
}
async preview(content, stylesheets, renderTo) {
if (!stylesheets) {
stylesheets = this.removeStyles();
}
if (!content) {
content = this.wrapContent();
}
if (!stylesheets) {
stylesheets = this.removeStyles();
}
this.polisher.setup();
let handlers = this.initializeHandlers();
await this.hooks.beforePolishing.trigger(stylesheets, this);
let styleText = await this.polisher.add(...stylesheets);
let startTime = performance.now();
await this.hooks.beforeChunking.trigger(content, this);
// Render flow
let flow = await this.chunker.flow(content, renderTo);
let endTime = performance.now();
let msg = "Rendering " + flow.total + " pages took " + (endTime - startTime) + " milliseconds.";
console.log(msg);
this.emit("rendered", msg, this.size.width && this.size.width.value + this.size.width.unit, this.size.height && this.size.height.value + this.size.height.unit, this.size.orientation, this.size.format);
if (typeof window.onPagesRendered !== "undefined") {
window.onPagesRendered(msg, this.size.width && this.size.width.value + this.size.width.unit, this.size.height && this.size.height.value + this.size.height.unit, this.size.orientation, this.size.format);
......
import { UUID } from "../utils/utils";
export function isElement(node) {
return node && node.nodeType === 1;
}
export function isText(node) {
return node && node.nodeType === 3;
}
export function *walk(start, limiter) {
let node = start;
......@@ -164,9 +172,10 @@ export function rebuildAncestors(node) {
return fragment;
}
/*
export function split(bound, cutElement, breakAfter) {
let needsRemoval = [];
let index = Array.prototype.indexOf.call(cutElement.parentNode.children, cutElement);
let index = indexOf(cutElement);
if (!breakAfter && index === 0) {
return;
......@@ -210,6 +219,7 @@ export function split(bound, cutElement, breakAfter) {
bound.parentNode.insertBefore(fragment, bound.nextSibling);
return [bound, bound.nextSibling];
}
*/
export function needsBreakBefore(node) {
if( typeof node !== "undefined" &&
......@@ -272,3 +282,208 @@ export function needsPageBreak(node) {
return false;
}
export function *words(node) {
let currentText = node.nodeValue;
let max = currentText.length;
let currentOffset = 0;
let currentLetter;
let range;
while(currentOffset < max) {
currentLetter = currentText[currentOffset];
if (/^\S$/.test(currentLetter)) {
if (!range) {
range = document.createRange();
range.setStart(node, currentOffset);
}
} else {
if (range) {
range.setEnd(node, currentOffset);
yield range;
range = undefined;
}
}
currentOffset += 1;
}
if (range) {
range.setEnd(node, currentOffset);
yield range;
range = undefined;
}
}
export function *letters(wordRange) {
let currentText = wordRange.startContainer;
let max = currentText.length;
let currentOffset = wordRange.startOffset;
let currentLetter;
let range;
while(currentOffset < max) {
currentLetter = currentText[currentOffset];
range = document.createRange();
range.setStart(currentText, currentOffset);
range.setEnd(currentText, currentOffset+1);
yield range;
currentOffset += 1;
}
}
export function isContainer(node) {
let container;
if (typeof node.tagName === "undefined") {
return true;
}
if (node.style.display === "none") {
return false;
}
switch (node.tagName) {
// Inline
case "A":
case "ABBR":
case "ACRONYM":
case "B":
case "BDO":
case "BIG":
case "BR":
case "BUTTON":
case "CITE":
case "CODE":
case "DFN":
case "EM":
case "I":
case "IMG":
case "INPUT":
case "KBD":
case "LABEL":
case "MAP":
case "OBJECT":
case "Q":
case "SAMP":
case "SCRIPT":
case "SELECT":
case "SMALL":
case "SPAN":
case "STRONG":
case "SUB":
case "SUP":
case "TEXTAREA":
case "TIME":
case "TT":
case "VAR":
// Content
case "P":
case "H1":
case "H2":
case "H3":
case "H4":
case "H5":
case "H6":
case "FIGCAPTION":
case "BLOCKQUOTE":
case "PRE":
case "LI":
case "TR":
case "DT":
case "DD":
case "VIDEO":
case "CANVAS":
container = false;
break;
default:
container = true;
}
return container;
}
export function cloneNode(n, deep=false) {
return n.cloneNode(deep);
}
export function findElement(node, doc) {
const ref = node.getAttribute("data-ref");
return findRef(ref, doc);
}
export function findRef(ref, doc) {
return doc.querySelector(`[data-ref='${ref}']`);
}
export function validNode(node) {
if (isText(node)) {
return true;
}
if (isElement(node) && node.dataset.ref) {
return true;
}
return false;
}
export function prevValidNode(node) {
while (!validNode(node)) {
if (node.previousSibling) {
node = node.previousSibling;
} else {
node = node.parentNode;
}
if (!node) {
break;
}
}
return node;
}