UNPKG

nehan

Version:

Html layout engine for paged-media written in Typescript

283 lines 13 kB
import { LogicalSize, LogicalCursorPos, LayoutResult, LogicalTextNode, LogicalBlockNode, LogicalInlineBlockNode, LogicalTableCellsNode, LogicalInlineNode, LogicalLineNode, LogicalRubyNode, LogicalBlockReNode, LogicalInlineReNode, } from './public-api'; export class TextReducer { constructor() { } visit(context, indent = false) { const measure = context.cursorPos.start; const extent = context.env.font.size; const size = new LogicalSize({ measure, extent }); const text = context.text; const children = context.characters; const skipBr = !context.lexer.hasNext() && indent; const textNode = new LogicalTextNode(context.env, context.boxPos, size, text, skipBr, children); context.characters = []; context.text = ""; if (indent) { context.cursorPos.start = 0; } return LayoutResult.logicalNode('text', textNode); } } TextReducer.instance = new TextReducer(); export class InlineReducer { constructor(type) { this.type = type; } visit(context, indent) { const measure = context.cursorPos.start; const extent = Math.max(context.env.font.size, ...context.inlineNodes.map(node => node.extent)); const children = context.inlineNodes; const text = context.inlineText; const size = new LogicalSize({ measure, extent }); const edge = context.contextBoxEdge.currentMarginBoxEdge; const inlineNode = new LogicalInlineNode(context.env, context.boxPos, size, text, edge, children); context.contextBoxEdge.clear(); context.inlineNodes = []; context.inlineText = ""; if (indent) { context.cursorPos.start = 0; } return LayoutResult.logicalNode(this.type, inlineNode); } } InlineReducer.instance = new InlineReducer('inline'); export class RubyReducer { constructor() { } visit(context, rubyGroup) { const rb = rubyGroup.rb; const rt = rubyGroup.rt; const measure = Math.max(rb.size.measure, rt.size.measure); const extent = rb.size.extent + rt.size.extent; const size = new LogicalSize({ measure, extent }); const text = rb.text; const rubyNode = new LogicalRubyNode(context.env, context.boxPos, size, text, rb, rt); return LayoutResult.logicalNode('ruby', rubyNode); } } RubyReducer.instance = new RubyReducer(); export class RubyBaseReducer extends InlineReducer { } RubyBaseReducer.instance = new RubyBaseReducer('ruby-base'); export class RubyTextReducer extends InlineReducer { } RubyTextReducer.instance = new RubyTextReducer('ruby-text'); export class ListMarkerReducer extends InlineReducer { } ListMarkerReducer.instance = new ListMarkerReducer('list-marker'); export class LineReducer { constructor() { } isDecoratedText(node) { return !node.env.textEmphasis.isNone() || node instanceof LogicalRubyNode; } getDecoratedExtent(node) { if (!node.env.textEmphasis.isNone()) { return node.env.font.size * 2; } if (node instanceof LogicalRubyNode) { return node.extent; } return 0; } isReChild(node) { if (node instanceof LogicalInlineReNode) { return true; } if (node instanceof LogicalInlineNode) { return node.children.some(child => this.isReChild(child)); } return false; } isEmptyLine(children, lineText) { if (children.length === 0) { return true; } if (children[0].text.trim() !== "") { return false; } return lineText.trim() === ""; } visit(context, isBr = false) { const pos = context.lineHeadPos; const measure = context.env.display.isInlineBlockFlow() ? context.cursorPos.start : context.maxMeasure; const children = context.inlineNodes; const reChildren = children.filter(node => this.isReChild(node)); const iblockChildren = children.filter(node => node instanceof LogicalInlineBlockNode); const decoratedChildren = children.filter(node => this.isDecoratedText(node)); const maxFont = children.reduce((acm, node) => node.env.font.size > acm.size ? node.env.font : acm, context.env.font); const minChildExtent = Math.min(...children.map(child => child.extent)); const maxDecoratedExtent = Math.max(...decoratedChildren.map(node => this.getDecoratedExtent(node))); const maxReExtent = Math.max(...reChildren.map(node => node.extent)); const maxIblockExtent = Math.max(...iblockChildren.map(node => node.extent)); const maxNonTextExtent = Math.max(maxReExtent, maxIblockExtent); const baseLineExtent = Math.max(maxFont.size, maxDecoratedExtent, maxNonTextExtent); const maxFontLineExtent = maxFont.lineExtent; const maxChildExtent = Math.max(maxFontLineExtent, ...children.map(node => node.extent)); const lineBodyExtent = Math.max(maxFontLineExtent, maxChildExtent); const textBodyExtent = Math.max(maxFont.size, maxNonTextExtent); const baseLineOffset = maxFontLineExtent - maxFont.size; const isNonTextLine = children.length === (iblockChildren.length + reChildren.length); let extent = (lineBodyExtent === maxNonTextExtent && context.restExtent >= baseLineOffset) ? lineBodyExtent + baseLineOffset : lineBodyExtent; extent = Math.min(extent, context.rootExtent); const size = new LogicalSize({ measure, extent }); const text = context.inlineText; const blockOffset = Math.floor(baseLineOffset / 2); const baseline = { size: new LogicalSize({ measure, extent: baseLineExtent }), textBodySize: new LogicalSize({ measure: context.cursorPos.start, extent: textBodyExtent }), startOffset: context.lineBoxStartOffset, blockOffset, }; const autoMeasure = baseline.startOffset + context.cursorPos.start; const autoSize = new LogicalSize({ measure: autoMeasure, extent }); if (children.length === 0 || (minChildExtent < context.env.font.size && isNonTextLine)) { const shrinkExtent = (isNonTextLine && children.length > 0) ? minChildExtent : context.env.font.size; size.extent = autoSize.extent = baseline.size.extent = baseline.textBodySize.extent = shrinkExtent; baseline.blockOffset = 0; } const lineNode = new LogicalLineNode(context.env, context.boxPos, pos, size, autoSize, text, children, baseline); if (!isBr && this.isEmptyLine(children, text)) { lineNode.size.extent = baseline.size.extent = 0; } context.cursorPos.start = 0; context.inlineNodes = []; context.inlineText = ""; return LayoutResult.logicalNode('line', lineNode); } } LineReducer.instance = new LineReducer(); export class BlockReducer { constructor(type) { this.type = type; } visit(context) { const pos = context.blockPos; const size = context.contentBlockSize; const autoSize = context.autoContentBlockSize; const border = context.contextBoxEdge.currentBorder; const text = context.text; const children = context.blockNodes; if (context.env.borderCollapse.isCollapse()) { size.extent -= context.getBorderCollapseAfterSize(); } const blockNode = new LogicalBlockNode(context.env, context.boxPos, pos, size, autoSize, text, border, children, context.progress); context.text = ""; context.blockNodes = []; context.cursorPos = LogicalCursorPos.zero; context.contextBoxEdge.clearBlock(); return LayoutResult.logicalNode(this.type, blockNode); } } BlockReducer.instance = new BlockReducer('block'); export class RootBlockReducer { constructor(type) { this.type = type; } visit(context) { const pos = context.blockPos; const size = context.contentBlockSize; const autoSize = context.autoContentBlockSize; if (context.floatRegion) { const maxFloatedExtent = context.floatRegion.maxRegionExtent; size.extent = Math.max(size.extent, maxFloatedExtent); autoSize.extent = Math.max(autoSize.extent, maxFloatedExtent); } const border = context.contextBoxEdge.currentBorder; const text = context.text; const children = context.floatNodes ? context.blockNodes.concat(context.floatNodes) : context.blockNodes; const blockNode = new LogicalBlockNode(context.env, context.boxPos, pos, size, autoSize, text, border, children, context.progress); context.text = ""; context.blockNodes = []; context.floatNodes = []; context.cursorPos = LogicalCursorPos.zero; context.contextBoxEdge.clearBlock(); if (context.floatRegion) { delete context.floatRegion; context.floatRegion = undefined; } context.pageCount++; return LayoutResult.logicalNode(this.type, blockNode); } } RootBlockReducer.instance = new RootBlockReducer('block'); export class InlineBlockReducer { visit(context) { const pos = context.blockPos; const size = context.contentInlineBlockSize; if (context.floatRegion) { size.extent = Math.max(size.extent, context.floatRegion.maxRegionExtent); } const autoSize = context.autoContentInlineBlockSize; const border = context.contextBoxEdge.currentBorder; const edge = context.env.edge.clone(); edge.border = border; const text = context.text; const children = context.floatNodes ? context.blockNodes.concat(context.floatNodes) : context.blockNodes; const iblockNode = new LogicalInlineBlockNode(context.env, context.boxPos, pos, size, autoSize, text, edge, children); context.text = ""; context.blockNodes = []; context.floatNodes = []; context.cursorPos = LogicalCursorPos.zero; context.contextBoxEdge.clearBlock(); if (context.floatRegion) { delete context.floatRegion; context.floatRegion = undefined; } return LayoutResult.logicalNode("inline-block", iblockNode); } } InlineBlockReducer.instance = new InlineBlockReducer(); export class TableCellReducer extends RootBlockReducer { } TableCellReducer.instance = new TableCellReducer("table-cell"); export class TableCellsReducer { constructor() { } visit(context) { const measure = context.maxMeasure; const extent = Math.max(...context.cells.map(cell => cell.extent)); const size = new LogicalSize({ measure, extent }); const pos = LogicalCursorPos.zero; const text = context.cells.reduce((acm, cell) => acm + cell.text, ""); const block = new LogicalTableCellsNode(context.env, context.boxPos, size, pos, text, context.cells); context.contextBoxEdge.clearBlock(); context.cursorPos = LogicalCursorPos.zero; return LayoutResult.logicalNode("table-cells", block); } } TableCellsReducer.instance = new TableCellsReducer(); export class TableReducer extends BlockReducer { } TableReducer.instance = new TableReducer("table"); export class TableRowGroupReducer extends BlockReducer { } TableRowGroupReducer.instance = new TableRowGroupReducer("table-row-group"); export class TableRowReducer extends BlockReducer { } TableRowReducer.instance = new TableRowReducer("table-row"); export class BlockLinkReducer extends BlockReducer { } BlockLinkReducer.instance = new BlockReducer("block-link"); export class InlineLinkReducer extends InlineReducer { } InlineLinkReducer.instance = new InlineReducer("inline-link"); export class ReReducer { constructor() { } visitBlock(context, logicalSize, physicalSize) { const edge = context.env.edge; const pos = context.parent ? context.parent.localPos : LogicalCursorPos.zero; const text = `(${context.env.element.tagName})`; const re = new LogicalBlockReNode(context.env, context.boxPos, logicalSize, physicalSize, edge, pos, text); return LayoutResult.logicalNode('re-block', re); } visitInline(context, logicalSize, physicalSize) { const edge = context.env.edge; const text = `(${context.env.element.tagName})`; const re = new LogicalInlineReNode(context.env, context.boxPos, logicalSize, physicalSize, edge, text); return LayoutResult.logicalNode('re-inline', re); } visit(context, logicalSize, physicalSize) { return context.env.display.isBlockLevel() ? this.visitBlock(context, logicalSize, physicalSize) : this.visitInline(context, logicalSize, physicalSize); } } ReReducer.instance = new ReReducer(); //# sourceMappingURL=layout-reducer.js.map