This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/papercraft/openjscad/node_modules/sylvester/doc/gen/latex.js

154 lines
4.2 KiB
JavaScript

'use strict';
const Bluebird = require('bluebird'); // eslint-disable-line import/order
const path = require('path');
const os = require('os');
const cp = require('child_process');
const fs = Bluebird.promisifyAll(require('fs'));
const rimraf = Bluebird.promisify(require('rimraf'));
const tmpFileBaseName = '';
function exec(command, options) {
return new Promise((resolve, reject) => {
cp.exec(command, options, (err, stdout, stderr) => {
if (err) {
reject(new Error(`error executing ${command}: ${stderr.toString() || stdout.toString()}`));
} else {
resolve(stdout);
}
});
});
}
function leftPad(str, length, padder) {
str = String(str);
while (str.length < length) {
str = padder + str;
}
return str;
}
function hasPrecisionGreaterThan(number, amount) {
const raised = Math.pow(10, amount) * number;
return Math.abs(Math.floor(raised) - raised) > 1e-6;
}
function anyHavePrecisionGreaterThan(numbers, amount) {
if (typeof numbers === 'number') {
return hasPrecisionGreaterThan(numbers, amount);
}
if (numbers instanceof Array) {
return numbers.some(n => anyHavePrecisionGreaterThan(n, amount));
}
return false;
}
function symbolToTex(data, rounding) {
if (typeof data === 'string') {
return data;
}
if (typeof data === 'number') {
return hasPrecisionGreaterThan(data, rounding) ? data.toFixed(rounding) : String(data);
}
if (data === null || typeof data === 'boolean') {
return `\\mathbf{${data}}`;
}
if (data instanceof Array) {
if (data[0] instanceof Array) {
data = data.map(row => row.map(r => symbolToTex(r, rounding)).join(' & '));
}
return `\\begin{bmatrix}
${data.map(r => symbolToTex(r, rounding)).join('\\\\\n')}
\\end{bmatrix}`;
}
return '\\left\\{' +
Object.keys(data)
.map(key => `\\mathrm{${key}\\!:}\\,${symbolToTex(data[key], rounding)}`)
.join(', ') +
'\\right\\}';
}
const startBlock = `\\begin{sylvEquation}`;
const endBlock = `\\end{sylvEquation}`;
class Renderer {
constructor() {
this._equations = [];
this._prefix = 'equation-';
this._format = 'png';
this._precision = 2;
}
/**
* Adds a new equation to be rendered and returns the filename it'll be
* rendered to in the renderer's base directory.
* @param {Object[]} example
* @return {String}
*/
push(example) {
this._equations.push(
example
.map(eg => {
const approx = anyHavePrecisionGreaterThan(eg.retValue, this._precision);
return symbolToTex(eg.callee) +
`\\!\\!{.}\\mathrm{${symbolToTex(eg.method)}}` +
`(${eg.args.map(symbolToTex).join(', ')})&` +
(approx ? '\\approx' : '=') +
symbolToTex(eg.retValue, this._precision);
})
.join('\\\\\n')
);
return `${this._prefix}${leftPad(this.length() - 1, 4, 0)}.${this._format}`;
}
/**
* Returns the number of equations to be rendered.
* @return {Number}
*/
length() {
return this._equations.length;
}
/**
* Renders all equations into the target directory. Internals of this method
* are inspired by http://tex.stackexchange.com/a/287501.
* @param {String} dir
* @return {Promise}
*/
render(dir) {
if (this._equations.length === 0) {
return Bluebird.resolve();
}
const tmpdir = path.join(os.tmpdir(), `${tmpFileBaseName}${Math.random()}`);
const targetFmt = path.join(dir, `${this._prefix}%04d.${this._format}`);
const content = `
\\documentclass[multi={sylvEquation}]{standalone}
\\usepackage{amsmath}
\\newenvironment{sylvEquation}
{$\\displaystyle\\begin{aligned}}
{\\end{aligned}$}
\\begin{document}
${startBlock}${this._equations.join(endBlock + startBlock)}${endBlock}
\\end{document}
`;
return fs.mkdirAsync(tmpdir)
.then(() => fs.writeFileAsync(path.join(tmpdir, 'input.tex'), content))
.then(() => exec('pdflatex -interaction nonstopmode input.tex', { cwd: tmpdir }))
.then(() => exec(`convert -density 144 -trim input.pdf -quality 100 ${targetFmt}`, { cwd: tmpdir }))
.finally(() => rimraf(tmpdir));
}
}
exports.Renderer = Renderer;