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