nehan
Version:
Html layout engine for paged-media written in Typescript
103 lines • 3.82 kB
JavaScript
import { Config, NehanDocument, LogicalNodeGenerator, LayoutOutlineEvaluator, ImageLoader, ImageLoaderContext, DomCallbackEffector, } from './public-api';
export class PagedNehanDocument extends NehanDocument {
constructor(src, options = { styleSheets: [] }) {
super(src, options);
this.generator = options.generator || LogicalNodeGenerator.createRoot(this.body);
this.evaluator = options.evaluator || this.generator.context.pageRoot.createLogicalNodeEvaluator();
this.effector = new DomCallbackEffector(this.generator.context.pageRoot);
this.pages = [];
this.timestamp = 0;
}
addPageNode(node) {
const index = this.pages.length;
const dom = (index === 0) ? this.evalNode(node, true) : undefined;
const prevPage = this.pages[index - 1];
const progress = node.progress;
const charCount = node.text.length;
const acmCharCount = prevPage ? prevPage.acmCharCount + charCount : charCount;
const pagedNode = {
node,
dom,
index,
progress,
charCount,
acmCharCount,
};
this.pages.push(pagedNode);
return pagedNode;
}
get pageCount() {
return this.pages.length;
}
getAnchor(anchorName) {
return this.generator.context.pageRoot.getAnchor(anchorName);
}
getAnchorPage(anchorName) {
const anchor = this.getAnchor(anchorName);
if (!anchor) {
throw new Error(`anchor(${anchorName}) is not found!`);
}
return this.getPage(anchor.pageIndex);
}
evalNode(node, useEffector = true) {
const dom = node.acceptEvaluator(this.evaluator);
if (useEffector) {
node.acceptEffector(this.effector);
}
return dom;
}
getPage(index) {
const page = this.pages[index];
if (!page) {
throw new Error(`page ${index} is not found!`);
}
if (page.dom) {
return page;
}
page.dom = this.evalNode(page.node, true);
return page;
}
createOutline(outlineEvaluator) {
const evaluator = outlineEvaluator || new LayoutOutlineEvaluator();
return this.generator.context.flowRoot.createOutline(evaluator);
}
getSectionAt(pageIndex) {
return this.generator.context.flowRoot.getSectionAt(pageIndex);
}
render(options = {}) {
const images = this.querySelectorAll("img").concat(this.querySelectorAll("video"));
const context = new ImageLoaderContext(images.length);
new ImageLoader(images, context).load(options).then(_ => {
this.timestamp = performance.now();
this.renderAsync(options);
});
return this;
}
renderAsync(options) {
requestAnimationFrame(() => {
const result = this.generator.getNext();
if (!result) {
const time = performance.now() - this.timestamp;
const pageCount = this.pages.length;
if (options.onComplete) {
options.onComplete({ caller: this, time, pageCount });
}
return;
}
const node = result.getBodyAsBlockNode();
if (node.children.some(child => child.extent > 0)) {
const page = this.addPageNode(node);
if (options.onPage) {
options.onPage({ caller: this, page });
}
}
if (this.pages.length > Config.maxPageCount) {
console.error("too many pages, abort.");
return;
}
this.renderAsync(options);
});
return this;
}
}
//# sourceMappingURL=paged-nehan-document.js.map