UNPKG

nehan

Version:

Html layout engine for paged-media written in Typescript

442 lines 16.2 kB
import { Config, ContextBoxEdge, LogicalBlockNode, LogicalTableCellsNode, LogicalLineNode, LogicalCursorPos, LogicalSize, PageRootFormatContext, } from './public-api'; export class FlowFormatContext { constructor(env, parent) { this.env = env; this.parent = parent; this.name = Config.useStrictFormatContextName ? env.element.toString(true) : env.element.getNodeName(); this.cursorPos = LogicalCursorPos.zero; this.contextBoxEdge = new ContextBoxEdge(env.edge); this.suspendedGens = []; this.curChildStartMargin = 0; this.blockNodes = []; this.inlineNodes = []; this.blockNodeHistory = []; this.inlineText = ""; this.text = ""; this.progress = 0; } get inlineRoot() { let ctx = this; while (ctx) { if (ctx.env.display.isBlockLevel() || ctx.env.display.isFlowRoot()) { return ctx; } ctx = ctx.parent; } throw new Error("inline root not found!"); } get pageRoot() { let ctx = this; while (ctx) { if (ctx instanceof PageRootFormatContext) { return ctx; } ctx = ctx.parent; } throw new Error(`[${this.env.element.tagName}] root context not found!`); } get flowRoot() { let ctx = this; while (ctx) { if (ctx.env.display.isFlowRoot() || ctx instanceof PageRootFormatContext) { return ctx; } ctx = ctx.parent; } throw new Error(`[${this.env.element.tagName}] flow root context not found!`); } get parentBlock() { let ctx = this.parent; while (ctx) { if (ctx.env.display.isBlockLevel() || ctx.env.display.isFlowRoot()) { return ctx; } ctx = ctx.parent; } return undefined; } acceptLayoutReducer(visitor, ...args) { return visitor.visit(this, ...args); } get restExtent() { const parentBlock = this.parentBlock; if (parentBlock) { return parentBlock.restExtent - this.cursorPos.before; } return this.maxExtent - this.cursorPos.before; } get lastBlockNode() { return this.blockNodeHistory[this.blockNodeHistory.length - 1] || undefined; } get totalLineCount() { return this.blockNodeHistory.filter(node => node instanceof LogicalLineNode).length; } get localLineCount() { return this.blockNodes.filter(node => node instanceof LogicalLineNode).length; } get listMarkerOffset() { if (!this.listMarker) { return 0; } if (this.env.listStyle.isPositionInside()) { return 0; } return (this.totalLineCount > 0) ? this.listMarker.measure : 0; } get floatStartOffset() { if (!this.flowRoot.floatRegion) { return 0; } const startEdgeSize = this.contextBoxEdge.borderWidth.getSize("start"); const floatStartPos = this.flowRoot.floatRegion.getSpacePosFromStartBound(this.flowRootPos.before); const offset = startEdgeSize > floatStartPos ? startEdgeSize : floatStartPos - startEdgeSize; return offset; } get textAlignOffset() { if (this.env.textAlign.isEnd()) { return this.maxMeasure - this.textStartPos; } if (this.env.textAlign.isCenter()) { return Math.floor((this.maxMeasure - this.textStartPos) / 2); } return 0; } get textStartPos() { let startPos = this.cursorPos.start; startPos += this.listMarkerOffset; return startPos; } get parentInlineEdgeSize() { let parent = this.parent; let size = 0; while (parent) { size += parent.env.edge.measure; if (parent === this.flowRoot) { break; } parent = parent.parent; } return size; } get contextRestMeasure() { if (this.flowRoot.floatRegion) { const contextEdgeSize = this.contextBoxEdge.borderBoxMeasure + this.parentInlineEdgeSize; const floatSpaceSize = this.flowRoot.floatRegion.getSpaceMeasureAt(this.flowRootPos.before) - contextEdgeSize; return Math.min(floatSpaceSize, this.maxMeasure) - this.textStartPos; } return this.restMeasure; } get lineBoxStartOffset() { let offset = 0; offset += this.floatStartOffset; offset += this.listMarkerOffset; offset += this.textAlignOffset; return offset; } get restMeasure() { if (this.parent) { return Math.min(this.parent.restMeasure, this.maxMeasure) - this.cursorPos.start; } return this.maxMeasure - this.textStartPos; } get rootMeasure() { if (this.parent) { return this.parent.rootMeasure; } if (!this.env.measure) { throw new Error("root measure is not defined!"); } return this.env.measure; } get rootExtent() { if (this.parent) { return this.parent.rootExtent; } if (!this.env.extent) { throw new Error("root extent is not defined!"); } return this.env.extent; } get maxMeasure() { if (this.env.measure !== null) { return this.env.measure; } if (this.parent) { return this.parent.maxMeasure; } return this.rootMeasure; } get maxExtent() { if (this.env.extent !== null) { return this.env.extent; } return this.rootExtent; } get contentBlockSize() { const measure = this.maxMeasure; const extent = (this.env.extent || this.cursorPos.before) - this.contextBoxEdge.borderWidth.extent; return new LogicalSize({ measure, extent }); } get autoContentBlockSize() { return new LogicalSize({ measure: this.maxMeasure, extent: this.cursorPos.before - this.contextBoxEdge.borderWidth.extent }); } get contentInlineBlockSize() { const measure = (this.env.measure !== null) ? this.env.measure : Math.max(...this.blockNodes.map(block => block.measure)); const extent = ((this.env.extent !== null) ? this.env.extent : this.cursorPos.before) - this.contextBoxEdge.borderWidth.extent; return new LogicalSize({ measure, extent }); } get autoContentInlineBlockSize() { const measure = (this.env.measure !== null) ? this.env.measure : Math.max(...this.blockNodes.map(block => block.measure)); const extent = this.cursorPos.before - this.contextBoxEdge.borderWidth.extent; return new LogicalSize({ measure, extent }); } get boxPos() { return { offsetPos: this.parent ? this.parent.flowRootPos.translate({ start: this.curChildStartMargin, before: this.contextBoxEdge.margin.getSize("before") }) : this.cursorPos.cloneValue(), clientPos: { start: this.contextBoxEdge.borderBoxStartSize, before: this.contextBoxEdge.borderBoxBeforeSize } }; } get localPos() { return new LogicalCursorPos({ start: this.cursorPos.start + this.curChildStartMargin + this.contextBoxEdge.borderBoxStartSize, before: this.cursorPos.before }); } get flowRootPos() { if (!this.parent || this.env.display.isFlowRoot()) { return this.localPos; } return this.parent.flowRootPos.translate(this.localPos); } get globalPos() { if (!this.parent) { return this.localPos; } return this.parent.globalPos.translate(this.localPos); } get blockPos() { const parentBlock = this.parentBlock; return parentBlock ? parentBlock.localPos : LogicalCursorPos.zero; } get lineHeadPos() { const start = 0; const before = this.cursorPos.before - this.contextBoxEdge.borderWidth.getSize("before"); return new LogicalCursorPos({ start, before }); } addBorderBoxEdge(direction) { this.contextBoxEdge.padding.addEdge(direction); this.contextBoxEdge.borderWidth.addEdge(direction); if (direction === "before" || direction === "after") { const old = this.cursorPos.before; this.cursorPos.before += this.contextBoxEdge.padding.getSize(direction); this.cursorPos.before += this.contextBoxEdge.borderWidth.getSize(direction); if (Config.debugLayout) { console.log("[%s] addBorderBoxEdge(%s): %d -> %d", this.name, direction, old, this.cursorPos.before); } } } addInlineMarginEdge(direction, marginSize) { this.contextBoxEdge.margin.addEdge(direction); this.cursorPos.start += marginSize; } addBlockMarginEdge(direction, marginSize) { const old = this.cursorPos.before; this.contextBoxEdge.margin.addEdge(direction); this.cursorPos.before += marginSize; if (Config.debugLayout) { console.log("[%s] addBlockMarginEdge(%s): %d -> %d", this.name, direction, old, this.cursorPos.before); } } getBorderCollapseAfterSize() { const afterBorderSize = this.contextBoxEdge.borderWidth.getSize("after"); if (afterBorderSize <= 0) { return 0; } const cells = this.blockNodes.find(child => child instanceof LogicalTableCellsNode); if (cells instanceof LogicalTableCellsNode) { return Math.min(afterBorderSize, ...cells.children.map(cell => { return cell instanceof LogicalBlockNode ? cell.border.width.after : 0; })); } const lastChild = this.blockNodes[this.blockNodes.length - 1]; if (lastChild instanceof LogicalBlockNode) { return Math.min(lastChild.border.width.after, afterBorderSize); } return 0; } setFloat(block, float) { this.flowRoot.addFloat(block, float, this.maxMeasure, this.flowRootPos); } addLine(line) { if (Config.ignoreEmptyLine && line.children.length === 0) { return; } this.pushBlockNode(line); } addBlock(block) { this.pushBlockNode(block); } addBlockRe(block) { if (Config.ignoreZeroRe && block.size.hasZero()) { return; } this.pushBlockNode(block); } addTable(block) { this.pushBlockNode(block); } addTableRowGroup(block) { if (this.env.borderCollapse.isCollapse()) { this.collapseBeforeStartBorder(block); } this.pushBlockNode(block); } addTableRow(block) { if (this.env.borderCollapse.isCollapse()) { this.collapseBeforeStartBorder(block); } this.pushBlockNode(block); } addTableCells(cells) { if (this.env.borderCollapse.isCollapse() && cells.children.some(cell => { return cell instanceof LogicalBlockNode ? cell.border.width.before > 0 : false; })) { const cellBeforeBorderSizes = cells.children.map(cell => { return cell instanceof LogicalBlockNode ? cell.border.width.before : 0; }); const beforeBorderSize = this.contextBoxEdge.borderWidth.getSize("before"); if (Math.min(...cellBeforeBorderSizes) > 0 && beforeBorderSize > 0) { const collapseSize = Math.min(beforeBorderSize, ...cellBeforeBorderSizes); cells.rowPos.before -= collapseSize; this.cursorPos.before -= collapseSize; if (Config.debugLayout) { console.log("[%s] collapse before %d", this.name, collapseSize); } } } this.pushBlockNode(cells); } addBlockLink(block) { this.pushBlockNode(block); } addInlineBlock(inlineBlock) { if (Config.ignoreEmptyInline && inlineBlock.size.hasZero() && inlineBlock.children.length === 0) { return; } this.pushInlineNode(inlineBlock); } addInline(inline) { if (Config.ignoreEmptyInline && inline.children.length === 0) { return; } this.pushInlineNode(inline); } addInlineRe(inline) { if (Config.ignoreZeroRe && inline.size.hasZero()) { return; } this.pushInlineNode(inline); } addInlineLink(inline) { const id = inline.env.element.id; if (!id && Config.ignoreEmptyInline && inline.children.length === 0) { return; } if (id && inline.children.length === 0) { inline.size.extent = 0; } this.pushInlineNode(inline); } addListMarker(marker) { this.pushInlineNode(marker, false); } addText(text) { if (text.children.length === 0) { return; } this.pushInlineNode(text); } addRuby(ruby) { this.pushInlineNode(ruby); } addAnchorElement(id, node) { const anchor = this.pageRoot.getAnchor(id); if (anchor) { anchor.box = node; if (anchor.pageIndex < 0) { anchor.pageIndex = this.pageRoot.pageCount; } } } pushInlineNode(inline, hasText = true) { const old = this.cursorPos.start; this.inlineNodes.push(inline); this.cursorPos.start += inline.measure; if (hasText) { this.inlineText += inline.text; } if (inline.env.element.id) { this.addAnchorElement(inline.env.element.id, inline); } if (Config.debugLayout) { console.log("[%s] pushInlineNode:%o(%d -> %d)", this.name, inline, old, this.cursorPos.start); } } pushBlockNode(block) { const old = this.cursorPos.before; this.text += block.text; this.progress = Math.min(1, this.progress + block.progress / this.env.element.childNodes.length); this.blockNodes.push(block); this.blockNodeHistory.push(block); if (!block.env.position.isAbsolute()) { this.cursorPos.before += block.extent; } if (block.env.element.id) { this.addAnchorElement(block.env.element.id, block); } switch (block.env.element.tagName) { case "h1": case "h2": case "h3": case "h4": case "h5": case "h6": const section = this.pageRoot.getHeaderSection(block.env.element); if (section && section.pageIndex < 0) { section.pageIndex = this.pageRoot.pageCount; } break; } if (Config.debugLayout) { console.log("[%s] pushBlockNode:%o(%d -> %d)", this.name, block, old, this.cursorPos.before); } } getBorderCollapseStartSize(block) { return Math.min(this.contextBoxEdge.borderWidth.getSize("start"), block.border.width.start); } getBorderCollapseBeforeSize(block) { const lastChild = this.blockNodes[this.blockNodes.length - 1]; if (lastChild instanceof LogicalBlockNode) { return Math.min(lastChild.border.width.after, block.border.width.before); } return Math.min(this.contextBoxEdge.borderWidth.getSize("before"), block.border.width.before); } collapseBeforeStartBorder(block) { const startCollapseSize = this.getBorderCollapseStartSize(block); const beforeCollapseSize = this.getBorderCollapseBeforeSize(block); block.layoutPos.start = -startCollapseSize; block.layoutPos.before -= this.contextBoxEdge.borderWidth.getSize("before") + beforeCollapseSize; this.cursorPos.before -= beforeCollapseSize; } } //# sourceMappingURL=flow-format-context.js.map