UNPKG

nehan

Version:

Html layout engine for paged-media written in Typescript

366 lines 17.7 kB
import { Config, MixChar, DualChar, LogicalInlineNode, LogicalInlineBlockNode, LogicalInlineReNode, LogicalBoxEdge, } from './public-api'; export class VertLogicalNodeEvaluator { constructor(pageRoot, cssVisitor, textJustifier) { this.pageRoot = pageRoot; this.cssVisitor = cssVisitor; this.textJustifier = textJustifier; } isReOrIblock(node) { if (node instanceof LogicalInlineReNode || node instanceof LogicalInlineBlockNode) { return true; } if (node instanceof LogicalInlineNode) { return node.children.some(child => this.isReOrIblock(child)); } return false; } visitChar(char) { return document.createTextNode(char.text); } visitCharEmpha(char, empha) { const node = document.createElement("div"); const emphaNode = document.createElement("div"); const textNode = document.createElement("div"); textNode.appendChild(document.createTextNode(char.text)); if (char instanceof DualChar) { textNode.style.writingMode = "vertical-rl"; } emphaNode.appendChild(document.createTextNode(empha.text)); if (empha.scale < 1.0) { emphaNode.style.fontSize = String(empha.scale) + "em"; emphaNode.style.paddingLeft = String(empha.scale / 2) + "em"; } if (char instanceof MixChar) { emphaNode.style.marginLeft = "-1em"; } node.style.textAlign = "center"; node.style.display = "flex"; node.style.alignItems = "center"; node.appendChild(textNode); node.appendChild(emphaNode); return node; } visitRefChar(refChar) { return document.createTextNode(refChar.text); } visitRefCharEmpha(refChar, empha) { return this.visitCharEmpha(refChar, empha); } visitSpaceChar(spaceChar) { const node = document.createElement("div"); node.style.height = spaceChar.size.measure + "px"; return node; } visitHalfChar(halfChar) { const node = document.createElement("div"); node.appendChild(document.createTextNode(halfChar.text)); node.style.textAlign = "center"; node.style.width = "1em"; return node; } visitMixChar(mixChar) { const node = document.createElement("div"); node.appendChild(document.createTextNode(mixChar.text)); return node; } visitDualChar(dualChar) { const node = document.createElement("div"); node.style.writingMode = "vertical-rl"; node.appendChild(document.createTextNode(dualChar.text)); return node; } visitDualCharKern(dualChar) { const node = document.createElement("div"); node.style.writingMode = "vertical-rl"; node.style.marginTop = "-0.5em"; node.appendChild(document.createTextNode(dualChar.text)); return node; } visitSmpUniChar(uniChar) { return document.createTextNode(uniChar.text); } visitTcy(tcy) { const node = document.createElement("div"); node.style.writingMode = "vertical-rl"; node.style.textCombineUpright = "all"; node.style.setProperty("-webkit-text-combine", "horizontal"); node.appendChild(document.createTextNode(tcy.text)); return node; } visitWord(word) { const node = document.createElement("div"); node.style.writingMode = "vertical-rl"; node.style.textOrientation = "sideways"; node.appendChild(document.createTextNode(word.text)); return node; } visitText(textNode) { const node = document.createElement("div"); node.className = "nehan-text"; node.style.lineHeight = "1"; textNode.children.forEach(char => { const charNode = char.acceptEvaluator(this); node.appendChild(charNode); if (charNode instanceof Text) { node.appendChild(document.createElement("br")); } if (char.spacing) { const spacingNode = document.createElement("div"); spacingNode.style.height = char.spacing + "px"; spacingNode.appendChild(document.createTextNode(" ")); node.appendChild(spacingNode); } }); node.normalize(); return node; } visitRuby(rubyNode) { const node = document.createElement("div"); node.className = "nehan-ruby"; const rbNode = document.createElement("div").appendChild(rubyNode.rb.acceptEvaluator(this)); const rtNode = document.createElement("div").appendChild(rubyNode.rt.acceptEvaluator(this)); node.appendChild(rbNode); node.appendChild(rtNode); node.style.display = "flex"; node.style.textAlign = "center"; node.style.alignItems = "center"; rubyNode.size.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); rubyNode.env.color.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); rubyNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitLine(lineNode) { const writingMode = lineNode.env.writingMode; const beforeProp = writingMode.isVerticalLr() ? "left" : "right"; const node = document.createElement("div"); node.className = "nehan-line"; node.style.boxSizing = "content-box"; node.style.position = "absolute"; node.style.overflow = "visible"; node.style[beforeProp] = lineNode.linePos.before + "px"; lineNode.env.font.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); lineNode.env.color.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); lineNode.size.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); const baseLineNode = document.createElement("div"); baseLineNode.className = "nehan-baseline"; baseLineNode.style.position = "absolute"; baseLineNode.style.top = (lineNode.linePos.start + lineNode.baseline.startOffset) + "px"; baseLineNode.style.height = "auto"; baseLineNode.style.width = lineNode.baseline.size.extent + "px"; baseLineNode.style.left = lineNode.baseline.blockOffset + "px"; node.appendChild(baseLineNode); if (lineNode.env.textAlign.isJustify()) { this.textJustifier.justify(lineNode); } lineNode.children.forEach(child => { const childNode = child.acceptEvaluator(this); const textBodyExtent = this.isReOrIblock(child) ? child.extent : child.env.font.size; const baselineGap = Math.floor((lineNode.baseline.textBodySize.extent - textBodyExtent) / 2); if (baselineGap === 0) { baseLineNode.appendChild(childNode); } else { const offsetNode = document.createElement("div"); offsetNode.style.marginLeft = Math.max(0, baselineGap) + "px"; offsetNode.appendChild(childNode); baseLineNode.appendChild(offsetNode); } }); return node; } visitInline(inlineNode) { const node = this.pageRoot.createElement("div", ["inline"], inlineNode); node.style.marginTop = inlineNode.edge.margin.start + "px"; node.style.marginBottom = inlineNode.edge.margin.end + "px"; inlineNode.env.font.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); inlineNode.env.color.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); inlineNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); inlineNode.children.forEach(child => { node.appendChild(child.acceptEvaluator(this)); }); return node; } visitInlineEmpha(inlineNode) { const node = this.pageRoot.createElement("div", ["inline", "empha"], inlineNode); node.style.marginTop = inlineNode.edge.margin.start + "px"; node.style.marginBottom = inlineNode.edge.margin.end + "px"; inlineNode.env.font.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); inlineNode.env.color.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); inlineNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); inlineNode.children.forEach(child => { node.appendChild(child.acceptEvaluator(this)); }); return node; } visitInlineBlock(iblockNode) { const node = this.pageRoot.createElement("div", ["iblock"], iblockNode); node.style.boxSizing = "content-box"; node.style.position = "relative"; node.style.paddingTop = iblockNode.env.edge.padding.start + "px"; node.style.paddingBottom = iblockNode.env.edge.padding.end + "px"; node.style.marginTop = iblockNode.env.edge.margin.start + "px"; node.style.marginBottom = iblockNode.env.edge.margin.end + "px"; iblockNode.size.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); iblockNode.edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); iblockNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); iblockNode.children.forEach(child => { node.appendChild(child.acceptEvaluator(this)); }); return node; } visitBlock(blockNode) { const node = this.pageRoot.createElement("div", ["block"], blockNode); node.style.boxSizing = "content-box"; node.style.position = (blockNode.env.element.tagName === Config.pageRootTagName) ? "relative" : "absolute"; node.style.paddingTop = blockNode.env.edge.padding.start + "px"; node.style.paddingBottom = blockNode.env.edge.padding.end + "px"; if (blockNode.env.position.isAbsolute()) { blockNode.env.absPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } else { blockNode.layoutPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } blockNode.size.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); blockNode.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); blockNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); blockNode.children.forEach(child => { node.appendChild(child.acceptEvaluator(this)); }); return node; } visitRootBlock(rootBlockNode) { const node = this.pageRoot.createElement("div", ["root"], rootBlockNode); const edge = LogicalBoxEdge.loadBoxEdge(rootBlockNode.env.element); node.style.width = rootBlockNode.extent + edge.extent + "px"; node.style.height = rootBlockNode.measure + edge.measure + "px"; edge.margin.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); edge.padding.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); rootBlockNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitTableCells(tableCellsNode) { const node = this.pageRoot.createElement("div", ["table-cells"], tableCellsNode); node.style.boxSizing = "content-box"; node.style.position = "absolute"; tableCellsNode.rowPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); tableCellsNode.size.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); tableCellsNode.children.forEach(child => { node.appendChild(child.acceptEvaluator(this)); }); return node; } visitBlockImage(imgNode) { const node = this.pageRoot.createElement("img", ["block"], imgNode); node.style.position = "absolute"; node.style.display = "block"; node.style.width = imgNode.physicalSize.width + "px"; node.style.height = imgNode.physicalSize.height + "px"; node.setAttribute("src", imgNode.env.element.getAttribute("src") || ""); imgNode.edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); if (imgNode.env.position.isAbsolute()) { imgNode.env.absPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } else { imgNode.layoutPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } imgNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitInlineImage(imgNode) { const node = this.pageRoot.createElement("img", ["inline"], imgNode); node.style.display = "block"; node.style.width = imgNode.physicalSize.width + "px"; node.style.height = imgNode.physicalSize.height + "px"; node.style.marginTop = imgNode.edge.margin.start + "px"; node.style.marginBottom = imgNode.edge.margin.end + "px"; node.setAttribute("src", imgNode.env.element.getAttribute("src") || ""); imgNode.edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); imgNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitBlockVideo(videoNode) { const node = videoNode.env.element.$node.cloneNode(true); node.setAttribute("width", String(videoNode.physicalSize.width)); node.setAttribute("height", String(videoNode.physicalSize.height)); node.removeAttribute("style"); node.style.position = "absolute"; node.style.marginTop = videoNode.edge.margin.start + "px"; node.style.marginBottom = videoNode.edge.margin.end + "px"; videoNode.layoutPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); videoNode.edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); videoNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitInlineVideo(videoNode) { const node = this.pageRoot.createElement("div", ["inline"], videoNode); node.style.display = "none"; node.style.width = "0"; node.style.height = "0"; node.innerHTML = "Sorry, inline video is not supported yet!"; return node; } visitBlockReFixed(reNodeFixed, fixedDOM) { const node = this.pageRoot.createElement("div", ["block"], reNodeFixed); node.style.position = "absolute"; node.style.width = reNodeFixed.physicalSize.width + "px"; node.style.height = reNodeFixed.physicalSize.height + "px"; node.appendChild(fixedDOM); reNodeFixed.edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); if (reNodeFixed.env.position.isAbsolute()) { reNodeFixed.env.absPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } else { reNodeFixed.layoutPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } reNodeFixed.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitInlineReFixed(reNodeFixed, fixedDOM) { const node = this.pageRoot.createElement("div", ["inline"], reNodeFixed); node.style.display = "block"; node.style.width = reNodeFixed.physicalSize.width + "px"; node.style.height = reNodeFixed.physicalSize.height + "px"; node.style.marginLeft = reNodeFixed.edge.margin.start + "px"; node.style.marginRight = reNodeFixed.edge.margin.end + "px"; node.appendChild(fixedDOM); reNodeFixed.edge.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); reNodeFixed.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); return node; } visitInlineLink(linkNode) { const node = this.pageRoot.createElement("a", ["inline"], linkNode); node.style.textDecoration = "none"; node.style.marginTop = linkNode.edge.margin.start + "px"; node.style.marginBottom = linkNode.edge.margin.end + "px"; linkNode.env.font.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); linkNode.env.color.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); linkNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); linkNode.children.forEach(child => { node.appendChild(child.acceptEvaluator(this)); }); return node; } visitBlockLink(linkNode) { const node = this.pageRoot.createElement("a", ["block"], linkNode); node.style.boxSizing = "content-box"; node.style.position = "absolute"; node.style.paddingTop = linkNode.env.edge.padding.start + "px"; node.style.paddingBottom = linkNode.env.edge.padding.end + "px"; if (linkNode.env.position.isAbsolute()) { linkNode.env.absPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } else { linkNode.layoutPos.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); } linkNode.size.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); linkNode.border.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); linkNode.env.element.style.acceptCssEvaluator(this.cssVisitor).applyTo(node.style); linkNode.children.forEach(child => { const childNode = child.acceptEvaluator(this); node.appendChild(childNode); }); return node; } } //# sourceMappingURL=vert-logical-node-evaluator.js.map