nehan
Version:
Html layout engine for paged-media written in Typescript
442 lines • 16.2 kB
JavaScript
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