mirror of
https://github.com/paboyle/Grid.git
synced 2025-06-14 13:57:07 +01:00
Adding pdf generation capabilities
This commit is contained in:
73
assets/js/ConfigMathJax.js
Normal file
73
assets/js/ConfigMathJax.js
Normal file
@ -0,0 +1,73 @@
|
||||
// A trigger which is queried by render-math-and-dump.js in PhantomJS
|
||||
var MathJaxFinished = false;
|
||||
|
||||
// This function will be called when MathJax has finished *all* rendering
|
||||
MathJax.Hub.Queue(
|
||||
function () {
|
||||
MathJaxFinished = true;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// A sample MathJax setup for SVG
|
||||
MathJax.Hub.Config({
|
||||
displayAlign: "left",
|
||||
displayIndent: "2em",
|
||||
extensions: ["tex2jax.js"],
|
||||
// Tex input, SVG output
|
||||
jax: ["input/TeX", "output/SVG"],
|
||||
imageFont: null,
|
||||
messageStyle: "none",
|
||||
showProcessingMessages: false,
|
||||
showMathMenu: false,
|
||||
delayStartupUntil: "onload",
|
||||
"HTML-CSS": { availableFonts: ["TeX"] },
|
||||
tex2jax: {
|
||||
ignoreClass: "tex2jax_ignore",
|
||||
processClass: "math",
|
||||
inlineMath: [ ["$","$"], ["\\(","\\)"] ],
|
||||
displayMath: [ ["$$","$$"], ["\\[","\\]"] ],
|
||||
processEscapes: false,
|
||||
preview: "none"
|
||||
},
|
||||
TeX: {
|
||||
extensions: ["AMSmath.js","AMSsymbols.js", "color.js"],
|
||||
//TagSide: "right",
|
||||
//TagIndent: "10em",
|
||||
MultLineWidth: "85%",
|
||||
equationNumbers: {
|
||||
autoNumber: "AMS"
|
||||
},
|
||||
unicode: {
|
||||
fonts: "STIXGeneral,'Arial Unicode MS'"
|
||||
}
|
||||
},
|
||||
SVG: {
|
||||
font: "Latin-Modern",
|
||||
scale: 90,
|
||||
minScaleAdjust: 50,
|
||||
undefinedFamily: "STIXGeneral, 'Arial Unicode MS', serif",
|
||||
linebreaks: {
|
||||
automatic: true
|
||||
},
|
||||
styles: {
|
||||
// Avoid rectangles with a black surrounding stroke in PrinceXML
|
||||
".mjx-svg-href" : {
|
||||
fill: "none",
|
||||
stroke: "none"
|
||||
},
|
||||
// Links to other formulas in black
|
||||
".MathJax_ref" : {
|
||||
fill: "#000000",
|
||||
stroke: "none"
|
||||
},
|
||||
".MathJax_SVG": {
|
||||
"vertical-align": "baseline"
|
||||
//"vertical-align": "-1px"
|
||||
}
|
||||
},
|
||||
matchFontHeight: false,
|
||||
mtextFontInherit: false,
|
||||
addMMLclasses: false
|
||||
}
|
||||
});
|
285
assets/js/render-math-and-dump.js
Normal file
285
assets/js/render-math-and-dump.js
Normal file
@ -0,0 +1,285 @@
|
||||
// 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();
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user