Spaces:
Running
Running
| /** | |
| * 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, | |
| }; | |