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