mirror of
https://github.com/paboyle/Grid.git
synced 2025-06-13 12:47:05 +01:00
286 lines
12 KiB
JavaScript
286 lines
12 KiB
JavaScript
// PhantomJS setup
|
|
var webPage = require('webpage');
|
|
var page = webPage.create();
|
|
var system = require('system');
|
|
var waitForMath;
|
|
var noLongerExecWaitForMathJax = false;
|
|
|
|
// Usage
|
|
if (system.args.length < 2) {
|
|
console.log("Usage: phantomjs render-math-and-dump.js <filename>");
|
|
console.log("Renders <filename> and dumps the DOM to the console.");
|
|
phantom.exit();
|
|
};
|
|
|
|
// Open the page and on success set the window interval function
|
|
// Alternatives:
|
|
// page.open(encodeURI(system.args[1]), function(status) {
|
|
// page.open("file://" + encodeURI(system.args[1]), function(status) {
|
|
page.open(system.args[1], function(status) {
|
|
if (status === "success") {
|
|
// Execute the query every 20ms
|
|
waitForMath = window.setInterval(
|
|
waitForMathJax,
|
|
20
|
|
);
|
|
};
|
|
});
|
|
|
|
// This also works (to some extent) with web URLs.
|
|
// page.open(system.args[1], function(status){
|
|
// if (status === "success") {
|
|
// // In this setup a complete local MathJax installation
|
|
// // is present in MathJax/
|
|
// page.injectJs("MathJax/MathJax.js");
|
|
// page.injectJs("ConfigMathJax.js");
|
|
// // Execute the query every 20ms
|
|
// waitForMath = window.setInterval(
|
|
// waitForMathJax,
|
|
// 20
|
|
// );
|
|
// };
|
|
// });
|
|
|
|
|
|
// Removes all MathJax <script> elements from the DOM.
|
|
// This avoids a second processing in PrinceXML, which
|
|
// a) had nothing left to do (MathJax already).
|
|
// b) probably would fail, since PrinceXML at the moment still lacks MathJax support
|
|
var removeMathJaxScripts = function() {
|
|
// Does 'str' end with 'suffix'?
|
|
var strEndsWith = function(str, suffix) {
|
|
return str.indexOf(suffix, str.length - suffix.length) !== -1;
|
|
};
|
|
|
|
// Get and remove all MathJax related script elements
|
|
var MathJaxScriptFound = true;
|
|
var scripts = document.getElementsByTagName("script");
|
|
// Repeating this loop until no more matching scripts are found seems
|
|
// to be necessary because removing children from the DOM did not always
|
|
// happen immediately, some of them were still written to the output when
|
|
// dumping 'page.content'.
|
|
while (MathJaxScriptFound) {
|
|
MathJaxScriptFound = false;
|
|
for (var i = scripts.length - 1; i >= 0; i--) {
|
|
if (scripts[i].hasAttribute("id")) {
|
|
var id = scripts[i].getAttribute("id");
|
|
// Remove script if 'id' starts with 'MathJax-Element'
|
|
if (id.indexOf("MathJax-Element") === 0) {
|
|
MathJaxScriptFound = true;
|
|
scripts[i].parentNode.removeChild(scripts[i]);
|
|
};
|
|
} else if (scripts[i].hasAttribute("src")) {
|
|
var src = scripts[i].getAttribute("src");
|
|
// Remove script if 'src' ends on 'ConfigMathJax.js',
|
|
// 'MathJax.js' or 'jax.js'
|
|
if ((strEndsWith(src, "ConfigMathJax.js"))
|
|
|| (strEndsWith(src, "MathJax.js"))
|
|
|| (strEndsWith(src, "jax.js"))) {
|
|
MathJaxScriptFound = true;
|
|
scripts[i].parentNode.removeChild(scripts[i]);
|
|
};
|
|
};
|
|
};
|
|
scripts = document.getElementsByTagName("script");
|
|
};
|
|
|
|
// Remove div with ID 'MathJax_Font_Test'
|
|
var fontTest = document.getElementById("MathJax_Font_Test");
|
|
if (fontTest) {
|
|
fontTest.parentNode.removeChild(fontTest);
|
|
};
|
|
|
|
// Avoid empty HTML-Syntax elements (<meta>, <link>, <br>, <hr>, ...),
|
|
// this makes it possible to call PrinceXML in XML mode with the generated
|
|
// output from PhantomJS. In mode 'auto' this code can be disabled
|
|
// completely. Please also note that for example the resulting <br></br>
|
|
// is not compliant with HTML, so you should keep an eye on the output
|
|
// generated by PrinceXML. The same is probably true for <meta></meta>,
|
|
// <link></link> and the like.
|
|
elements = document.documentElement.getElementsByTagName("*");
|
|
for (var i = elements.length - 1; i >= 0; i--) {
|
|
if (elements[i].childNodes.length == 0) {
|
|
elements[i].appendChild(document.createTextNode(""));
|
|
};
|
|
};
|
|
};
|
|
|
|
|
|
// The problem with processing SVG formulas generated by MathJax in PrinceXML
|
|
// is the location of the SVG glyphs which are used by MathJax to construct
|
|
// formulas. MathJax creates an SVG path for every symbol/letter in a hidden
|
|
// div at the beginning of the document. Every glyph (actually an SVG path)
|
|
// has an ID. In every formula the glyphs are only referenced by this ID and
|
|
// *not* recreated every time the glyph is needed. This keeps the amount of
|
|
// markup added to the document small.
|
|
//
|
|
// Currently PrinceXML isn't able to use links to elements wich are defined
|
|
// in another SVG element (the so called SVG "islands" problem, mentioned for
|
|
// example here /forum/topic/1402/multiple-svg-regions-with-common-defs and
|
|
// here /forum/topic/2912/what-forms-does-prince-support-css-clip-path).
|
|
//
|
|
// This function collects all MathJax glyph definitions and copies them to any
|
|
// SVG formula element where they are used. The function does not copy *all*
|
|
// definitions to *all* formulas, only those needed for the current formula
|
|
// are copied.
|
|
//
|
|
// The function still has (at least) one "bug": By copying a glyph to more
|
|
// than one formula you'll end up with elements that have the same ID in the
|
|
// resulting document. For PrinceXML it doesn't seem to be a problem, but a
|
|
// validator will surely find it faulty.
|
|
var relocateMathJaxGlyphs = function () {
|
|
// Container holding all MathJax glyphs, declared here for later removal.
|
|
var mathJaxSVGGlyphContainer = null;
|
|
|
|
// Get an array of all MathJax SVG paths which form a glyph.
|
|
var getMathJaxGlyphs = function () {
|
|
// These glyphs are located inside an SVG 'defs' element and
|
|
// are later used/referenced to layout every formula.
|
|
// This code currently only checks for the presence of one
|
|
// glyph container (the first).
|
|
var glyphContainer = document.getElementById("MathJax_SVG_glyphs");
|
|
if (!glyphContainer) {
|
|
return [];
|
|
};
|
|
// Hold the container for later removal to avoid too many DOM queries.
|
|
var gcpn = glyphContainer.parentNode;
|
|
if (gcpn) {
|
|
mathJaxSVGGlyphContainer = gcpn;
|
|
};
|
|
// This could be changed to use '*' instead of 'path', but so far
|
|
// I haven't seen that MathJax uses something different than 'path'.
|
|
var paths = glyphContainer.getElementsByTagName("path");
|
|
var result = [];
|
|
for (var i = paths.length - 1; i >= 0; i--) {
|
|
var id = paths[i].getAttribute("id");
|
|
if (id) {
|
|
result.push({id:id, path:paths[i]});
|
|
};
|
|
};
|
|
return result;
|
|
};
|
|
|
|
// Get an array of all MathJax SVG display areas (formulas).
|
|
var getMathJaxSVGs = function () {
|
|
// SVG formulas are located in a span with class name 'MathJax_SVG'.
|
|
var svgContainers = document.getElementsByClassName("MathJax_SVG");
|
|
if (svgContainers.length === 0) {
|
|
return [];
|
|
};
|
|
var result = [];
|
|
for (var i = svgContainers.length - 1; i >= 0; i--) {
|
|
// Handle all embedded SVG containers (normally there should be
|
|
// only one, but it doesn't hurt to visit all).
|
|
var svgs = svgContainers[i].getElementsByTagName("svg");
|
|
for (var a = svgs.length - 1; a >= 0; a--) {
|
|
// Set missing SVG namespace
|
|
svgs[a].setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
// At this stage we can already create the <defs> element (if
|
|
// not present). The element is supposed holds copies of the
|
|
// glyphs and later will be appended to *this* SVG element.
|
|
var defs;
|
|
var defsElements = svgs[a].getElementsByTagName("defs");
|
|
if (defsElements.length === 0) {
|
|
defs = document.createElement("defs");
|
|
} else {
|
|
defs = defsElements[0];
|
|
};
|
|
result.push({svg:svgs[a], defs:defs});
|
|
};
|
|
};
|
|
return result;
|
|
};
|
|
|
|
// Find the matching glyph path by id in 'glyphs' and return a clone
|
|
var getGlyphById = function (id, glyphs) {
|
|
for (var i = glyphs.length - 1; i >= 0; i--) {
|
|
// id and path are assumed to be always non-null
|
|
if (id === glyphs[i].id) {
|
|
return glyphs[i].path.cloneNode(true);
|
|
};
|
|
};
|
|
return null;
|
|
};
|
|
|
|
// Copy every SVG glyph referenced in the SVG element into the <defs>
|
|
// element (this was already created in getMathJaxSVGs, if not present).
|
|
var copySVGGlyphsToSVGDefsElement = function (svgObject, glyphs) {
|
|
// Get all nested <use> elements
|
|
var useElements = svgObject.svg.getElementsByTagName("use");
|
|
for (var i = useElements.length - 1; i >= 0; i--) {
|
|
// First lookup the xlink:href attribute. If the attribute is
|
|
// present verbatim in this form we'll take this one.
|
|
var id = useElements[i].getAttribute("xlink:href");
|
|
if (!id) {
|
|
// Next try the ordinary href attribute. Although PhantomJS
|
|
// later writes this as 'xlink:href' it seems that it only
|
|
// can be accessed using 'href'.
|
|
id = useElements[i].getAttribute("href");
|
|
};
|
|
// No suitable id found...
|
|
if ((!id) || (id.length < 2)) {
|
|
continue;
|
|
};
|
|
// Get the matching glyph as a clone from the list of all glyps
|
|
// and append it to the <defs> element.
|
|
var glyph = getGlyphById(id.slice(1), glyphs);
|
|
if (glyph) {
|
|
svgObject.defs.appendChild(glyph);
|
|
} else {
|
|
};
|
|
};
|
|
// Finally append the <defs> element to its parent SVG element
|
|
svgObject.svg.appendChild(svgObject.defs);
|
|
};
|
|
|
|
// This is the main loop which ...
|
|
// ... gets all glyphs ...
|
|
var mathJaxGlyphs = getMathJaxGlyphs();
|
|
if (mathJaxGlyphs.length > 0) {
|
|
// ... gets all formulas ...
|
|
var mathJaxSVGs = getMathJaxSVGs();
|
|
if (mathJaxSVGs.length > 0) {
|
|
for (var i = mathJaxSVGs.length - 1; i >= 0; i--) {
|
|
// ... and copies matching glyphs into every formula.
|
|
copySVGGlyphsToSVGDefsElement(mathJaxSVGs[i], mathJaxGlyphs);
|
|
};
|
|
};
|
|
};
|
|
|
|
// Finally we can remove the glyph container from the DOM
|
|
if (mathJaxSVGGlyphContainer) {
|
|
mathJaxSVGGlyphContainer
|
|
.parentNode
|
|
.removeChild(mathJaxSVGGlyphContainer);
|
|
};
|
|
|
|
};
|
|
|
|
|
|
// Query a variable named 'MathJaxFinished' in the document,
|
|
// see also ConfigMathJax.js.
|
|
var waitForMathJax = function () {
|
|
if (noLongerExecWaitForMathJax) {
|
|
return;
|
|
};
|
|
var mjFinished = page.evaluate(function () {
|
|
return MathJaxFinished;
|
|
});
|
|
if (mjFinished) {
|
|
// waitForMathJax was called at least twice (?), even after
|
|
// window.clearInterval(). This flag prevents it (see above).
|
|
noLongerExecWaitForMathJax = true;
|
|
window.clearInterval(waitForMathJax);
|
|
|
|
// Remove the scripts
|
|
page.evaluate(removeMathJaxScripts);
|
|
|
|
// Relocate/copy all SVG glyphs (paths)
|
|
page.evaluate(relocateMathJaxGlyphs);
|
|
|
|
// Dump the DOM to the console and exit.
|
|
console.log(page.content);
|
|
phantom.exit();
|
|
};
|
|
}
|