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