UNPKG

@andatoshiki/toshiki-cdn

Version:
270 lines (227 loc) 7.22 kB
/** * These objects store the data about the DOM nodes we create, as well as some * extra data. They can then be transformed into real DOM nodes with the * `toNode` function or HTML markup using `toMarkup`. They are useful for both * storing extra properties on the nodes, as well as providing a way to easily * work with the DOM. * * Similar functions for working with MathML nodes exist in mathMLTree.js. */ var utils = require("./utils"); /** * Create an HTML className based on a list of classes. In addition to joining * with spaces, we also remove null or empty classes. */ var createClass = function(classes) { classes = classes.slice(); for (var i = classes.length - 1; i >= 0; i--) { if (!classes[i]) { classes.splice(i, 1); } } return classes.join(" "); }; /** * This node represents a span node, with a className, a list of children, and * an inline style. It also contains information about its height, depth, and * maxFontSize. */ function span(classes, children, height, depth, maxFontSize, style) { this.classes = classes || []; this.children = children || []; this.height = height || 0; this.depth = depth || 0; this.maxFontSize = maxFontSize || 0; this.style = style || {}; this.attributes = {}; } /** * Sets an arbitrary attribute on the span. Warning: use this wisely. Not all * browsers support attributes the same, and having too many custom attributes * is probably bad. */ span.prototype.setAttribute = function(attribute, value) { this.attributes[attribute] = value; }; /** * Convert the span into an HTML node */ span.prototype.toNode = function() { var span = document.createElement("span"); // Apply the class span.className = createClass(this.classes); // Apply inline styles for (var style in this.style) { if (Object.prototype.hasOwnProperty.call(this.style, style)) { span.style[style] = this.style[style]; } } // Apply attributes for (var attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { span.setAttribute(attr, this.attributes[attr]); } } // Append the children, also as HTML nodes for (var i = 0; i < this.children.length; i++) { span.appendChild(this.children[i].toNode()); } return span; }; /** * Convert the span into an HTML markup string */ span.prototype.toMarkup = function() { var markup = "<span"; // Add the class if (this.classes.length) { markup += " class=\""; markup += utils.escape(createClass(this.classes)); markup += "\""; } var styles = ""; // Add the styles, after hyphenation for (var style in this.style) { if (this.style.hasOwnProperty(style)) { styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; } } if (styles) { markup += " style=\"" + utils.escape(styles) + "\""; } // Add the attributes for (var attr in this.attributes) { if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { markup += " " + attr + "=\""; markup += utils.escape(this.attributes[attr]); markup += "\""; } } markup += ">"; // Add the markup of the children, also as markup for (var i = 0; i < this.children.length; i++) { markup += this.children[i].toMarkup(); } markup += "</span>"; return markup; }; /** * This node represents a document fragment, which contains elements, but when * placed into the DOM doesn't have any representation itself. Thus, it only * contains children and doesn't have any HTML properties. It also keeps track * of a height, depth, and maxFontSize. */ function documentFragment(children, height, depth, maxFontSize) { this.children = children || []; this.height = height || 0; this.depth = depth || 0; this.maxFontSize = maxFontSize || 0; } /** * Convert the fragment into a node */ documentFragment.prototype.toNode = function() { // Create a fragment var frag = document.createDocumentFragment(); // Append the children for (var i = 0; i < this.children.length; i++) { frag.appendChild(this.children[i].toNode()); } return frag; }; /** * Convert the fragment into HTML markup */ documentFragment.prototype.toMarkup = function() { var markup = ""; // Simply concatenate the markup for the children together for (var i = 0; i < this.children.length; i++) { markup += this.children[i].toMarkup(); } return markup; }; /** * A symbol node contains information about a single symbol. It either renders * to a single text node, or a span with a single text node in it, depending on * whether it has CSS classes, styles, or needs italic correction. */ function symbolNode(value, height, depth, italic, skew, classes, style) { this.value = value || ""; this.height = height || 0; this.depth = depth || 0; this.italic = italic || 0; this.skew = skew || 0; this.classes = classes || []; this.style = style || {}; this.maxFontSize = 0; } /** * Creates a text node or span from a symbol node. Note that a span is only * created if it is needed. */ symbolNode.prototype.toNode = function() { var node = document.createTextNode(this.value); var span = null; if (this.italic > 0) { span = document.createElement("span"); span.style.marginRight = this.italic + "em"; } if (this.classes.length > 0) { span = span || document.createElement("span"); span.className = createClass(this.classes); } for (var style in this.style) { if (this.style.hasOwnProperty(style)) { span = span || document.createElement("span"); span.style[style] = this.style[style]; } } if (span) { span.appendChild(node); return span; } else { return node; } }; /** * Creates markup for a symbol node. */ symbolNode.prototype.toMarkup = function() { // TODO(alpert): More duplication than I'd like from // span.prototype.toMarkup and symbolNode.prototype.toNode... var needsSpan = false; var markup = "<span"; if (this.classes.length) { needsSpan = true; markup += " class=\""; markup += utils.escape(createClass(this.classes)); markup += "\""; } var styles = ""; if (this.italic > 0) { styles += "margin-right:" + this.italic + "em;"; } for (var style in this.style) { if (this.style.hasOwnProperty(style)) { styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; } } if (styles) { needsSpan = true; markup += " style=\"" + utils.escape(styles) + "\""; } var escaped = utils.escape(this.value); if (needsSpan) { markup += ">"; markup += escaped; markup += "</span>"; return markup; } else { return escaped; } }; module.exports = { span: span, documentFragment: documentFragment, symbolNode: symbolNode };