UNPKG

contractjs

Version:

A javascript framework for generating pdf documents & contracts easily through its built in commands.

727 lines (666 loc) 23.6 kB
// Copyright (c) 2017 - Kyle Derby MacInnis // Any unauthorized distribution or transfer // of this work is strictly prohibited. // All Rights Reserved. // // REFER TO THE LICENSE FILE FOR INFORMATION REGARDING LICENSING // AUTHOR: Kyle Derby MacInnis // VERSION: 0.1.0 // DATE : FEBRUARY 7, 2017 // DESCRIPTION: // Functions for Latex Document Transpiling & Compilation (PDF + DVI) // THIRD-PARTY LIBRARIES const LTX = require('latex-ex'); // For Compiling into PDF (Requires TexLive) const toolLib = require('./library.js'); // Main Object for Export const latexEngine = (function latexEng() { // -------FLAGS AND STORAGE------- // ================================> // Header & System Generated Stuff here this._sys = []; // System related stuff goes here based on usage this._macro = []; // Blank Array for Buffer (User Driven Content Goes Here) this._latex = []; // PDF Stream (Output) this._pdf = []; // DVI Stream (Output) this._dvi = []; // Latex Stream (Output) this._ltx = []; // Export Flag for Drafting this._export = false; // Watermark Text (if-any) this._watermark = ""; // Colours (for Styling Purposes) this._colours = []; // Styles this._styles = []; // Images this._images = []; // Files this._files = []; //Packages = []; this._packages = []; // Configuration this._config = []; // System Configuration function _initSystemConfig(graphicsPath) { // Setup System this._sys.push("\\batchmode\n\\documentclass{article}\n"); // Latex Packages this._packages = [ "\\usepackage[english]{babel}", "\\usepackage[utf8]{inputenc}", "\\usepackage{color, colortbl}", "\\usepackage[letterpaper, margin=1in]{geometry}", "\\usepackage{marginnote}", "\\usepackage{parskip}", "\\usepackage{multicol}", "\\usepackage{tabularx}", "\\usepackage{ltablex}", "\\usepackage{papersign}", "\\usepackage{booktabs}", "\\usepackage{ragged2e}", "\\usepackage{ifmtarg}", "\\usepackage{etoolbox}", "\\usepackage{graphicx}", "\\usepackage{xcolor}", "\\usepackage{soul}", "\\usepackage{ifthen}" ]; // configuration this._config = [ "\\graphicspath{" + graphicsPath + "}\n", "\\setlength{\\parskip}{0em}\n", "\\keepXColumns\n", "\\newenvironment{frcseries}{\\fontfamily{pzc}\\selectfont}{}\n", "\\newcommand{\\textcur}[1]{{\\itshape\\frcseries#1}}\n", "\\newcolumntype{g}{>{\\vfill\\centering}X}\n", "\\newcolumntype{Y}{>{\\vfill\\RaggedRight\\arraybackslash}X}\n", "\\newcolumntype{Z}{>{\\vfill\\centering\\arraybackslash}X}\n", ] this._sys = this._sys.concat(this._packages).concat(this._config); }; // Macro Initialization function _initDefaultMacros() { // Setup Macros let macros = [ // Necessary Listing Macros "\n\\makeatletter", "\n\\newcommand{\\lst}{\n\\paragraph{ }\n\\hfill\\begin{minipage}{\\dimexpr\\textwidth-1cm}\n\\begin{description}\n\\setlength\\itemsep{1em}\n\\@lsti\n}\n", "\n\\newcommand\\@lsti{\n\\@ifnextchar\\stoplst{\\@lstsend}{\\@lstii}}\n", "\n\\newcommand\\@lstii[2]{\n\\@lstiii{#1}{#2}\\hfill\n\\@lsti\n}\n", "\n\\newcommand\\@lstiii[2]{\\item[#1]#2\n}\n", "\n\\newcommand\\@lstsend[1]{\n\\end{description}\n\\xdef\\tpd{\\the\\prevdepth}\n\\end{minipage}\n}\n", "\n\\makeatother" ]; this._macro = this._macro.concat(macros); // Counters this._macro.push("\\newcounter{enumcount}\n"); this._macro.push("\\newcounter{enumheadcount}\n"); }; // Setup the watermark function _setWatermark(text, colour, scale) { // TODO }; // Adds in configuration for a new colour in Latex function _addColour(name, val, type) { this._colours["_" + name] = { name: name, val: val, type: type, ltx: "\\color{" + name + "}" }; this._colours[name] = () => this._colours["_" + name].ltx // Append Latex Command for New Colour to Macros this._macro.push["\\definecolor{" + name + "}{" + type + "}{" + val + "}"]; }; // Adds in configuration for a new colour in Latex function _addStyle(name, pre, post) { this._styles["_" + name] = { name: name, pre: pre, post: post, ltx: (text) => { return "\\" + name + "{" + text + "}" } }; this._styles[name] = (text) => this._styles["_" + name].ltx(text); // Append Latex Command for New Colour to Macros this._macro.push["\\def\\" + name + "<#1>{" + pre + "#1" + post + "}"]; }; function init(ltxStream, exportFlag, graphicsPath, colours) { this._latex = ltxStream; this._export = exportFlag; if (colours && colours.length) { let i = colours.length; while (i--) { _addColour(colours[i].name, colours[i].val, colours[i].type); } } // Setup System configuration _initSystemConfig(graphicsPath); // Setup Macros _initDefaultMacros(); // Establish Styles for Use (Defaults) (extensible) _addStyle("bf", "\\textbf{", "}"); _addStyle("it", "\\emph{", "}"); _addStyle("ul", "\\ul{", "}"); _addStyle("st", "\\st{", "}"); // Establish colours available for use (Defaults) (Extensible) _addColour("text", "0, 0, 0", "rgb"); // Default Colour _addColour("new", "0.1, 0.1, 0.9", "rgb"); // Revision Highlighting _addColour("old", "0.9, 0.1, 0.1", "rgb"); // Revision Highlighting _addColour("eq", "0.1, 0.6, 0.1", "rgb"); // Equal _addColour("neq", "0.9, 0.1, 0.4", "rgb"); // Not Equal } function begin() { push("\\begin{document}"); } function end() { push("\\end{document}"); } function toPDF() { return new Promise((resolve, reject) => { let input = this._sys.concat(this._macro).concat(this._latex) // Store to this._pdf & return try { let stream = LTX(input.join("\n")); this._pdf = stream; resolve(stream); } catch (e) { reject(e); } }); } function toDVI() { // Store to this._dvi & return return new Promise((resolve, reject) => { let input = this._sys.concat(this._macro).concat(this._latex) // Store to this._pdf & return try { let stream = LTX(input.join("\n"), { format: 'dvi' }); this._dvi = stream; resolve(stream); } catch (e) { reject(e); } }); } function toLTX() { let output = this._sys.concat(this._macro).concat(this._latex) this._ltx = output.join("\n"); return Promise.resolve(output.join("\n")); } function getPDF() { return new Promise((resolve) => { let pdfData = []; try { stream.on('data', (chunk) => { pdfData.push(chunk); }); stream.on('end', () => { resolve(pdfData); }) } catch (e) { console.error(e); reject(e); } }); } function getDVI() { return new Promise((resolve) => { let pdfData = []; try { stream.on('data', (chunk) => { pdfData.push(chunk); }); stream.on('end', () => { resolve(pdfData); }) } catch (e) { console.error(e); reject(e); } }); } function getLTX() { return String(this._ltx); } function getPDFStream() { return this._pdf; } function getDVIStream() { return this._dvi; } function push(item) { this._latex.push(item); } function pushIf(condition, item) { let itm = condition ? item : ""; this._latex.push(itm); } function pushIfElse(condition, item, otheritem) { let itm = condition ? item : otheritem; this._latex.push(itm); } function cpush(item) { this._latex.push(clean(item)); } function cpushIf(condition, item) { let itm = condition ? item : ""; this._latex.push(clean(itm)); } function cpushIfElse(condition, item, otherItem) { let itm = condition ? item : otheritem; this._latex.push(clean(itm)); } function pop() { return this._latex.pop(); } function popIf(test) { if (test) { return this._latex.pop(); } } function clean(str) { return str ? String(str) .replace(/\^/g, '\\textasciicircum{}') .replace(/\~/g, '\\textasciitilde{}') .replace(/\{/g, '\\\{') .replace(/\}/g, '\\\}') .replace(/\_/g, '\\\_') .replace(/\%/g, '\\\%') .replace(/\$/g, '\\\$') .replace(/\&/g, '\\\&') .replace(/\#/g, '\\\#') .replace(/\\/g, '') : ""; } function compare(one, two) { if (one === two) { return "eq" } else if (two === "") { return "one" } else if (one === "") { return "two" } else { return "neq" } } function putIf(condition, t_str) { return condition ? t_str : ""; } function putIfElse(condition, t_str, f_str) { return condition ? t_str : f_str; } function cputIf(condition, t_str) { return condition ? clean(t_str) : ""; } function cputIfElse(condition, t_str, f_str) { return condition ? clean(t_str) : clean(f_str); } function table(columns, array) { let coltypes = ["| "]; let entries = []; for (let i = 0; i < columns.length; i++) { coltypes.push(" " + columns[i] + " | "); } for (let i = 0; i < array.length; i++) { entries.push("\\bottomrule\n") for (let j = 0; j < columns.length; j++) { j === columns.length - 1 ? entries.push(array[i][j] + "\\\\") : entries.push(array[i][j] + " & "); } entries.push("\n\\toprule\n"); if (i === 0) { entries.push("\\endhead\n"); } } let out = []; out.push("\\begin{tabularx}{\\textwidth}{" + coltypes.join("") + "}\n"); out.push(entries.join("") + "\n"); out.push("\\end{tabularx}\n\\flushleft") push(out.join("")); } function list(array) { let out = ["\\lst\n"]; for (let i = 0; i < array.length; i++) { out.push("{(" + String.fromCharCode(97 + i) + ")}{" + array[i] + "}\n"); } out.push("\\stoplst"); return out.join(""); } function section(hd) { var out = ["\n\\section*{" + hd + "}\n"]; push(out.join("")); } function subsection(hd) { var out = ["\n\\subsection*{" + hd + "}\n"]; push(out.join("")); } function definition(def, txt) { var out = ["\\paragraph{\"" + def + "\"}{" + txt + "}\n"]; push(out.join("")); } function br() { push("\\ \\linebreak\n"); } function np() { push("\\pagebreak\n"); } function plain(txt) { var out = ["\\noindent " + txt + "\n"]; push(out.join("")); } function indent(txt) { var out = ["\\indent" + txt + "\n"]; push(out.join("")); } function bf(txt) { var out = ["\\textbf{" + txt + "}\n"]; push(out.join("")); } function ul(txt) { var out = ["\\underline{" + txt + "}\n"]; push(out.join("")); } function par(bold, txt) { push("\\paragraph{" + bold + "}{" + txt + "}\n"); } function enumP(title, txt) { var out = ["\\stepcounter{enumcount}\n\\reversemarginpar", "\\marginnote{\\textbf{\\theenumcount .}}[0.9cm]\\paragraph{" + title + "}\\nonfrenchspacing " + txt + "\n" ]; push(out.join("")); } function enumHead(title) { var out = ["\\stepcounter{enumheadcount}\n", "\\subsubsection*{\\bf ARTICLE \\theenumheadcount\\ -\\ " + title + "}\n" ]; push(out.join("")); } function floatRightHead(heading) { var out = ["\\hfill\\begin{minipage}{\\dimexpr\\textwidth-8cm}\n", "\\parfillskip0pt\n\\parindent0pt\n\\fontdimen3\\font.25in\n", "\\frenchspacing\n\\bf " + heading + "\n\\end{minipage}\\ ", "\\linebreak \\ \\linebreak\\nonfrenchspacing\n" ]; push(out.join("")); } function floatLeftHead(heading) { var out = ["\\begin{minipage}{\\dimexpr\\textwidth-8cm}\n", "\\parfillskip0pt\n\\parindent0pt\n\\fontdimen3\\font.25in\n", "\\frenchspacing\n\\bf " + heading + "\n\\end{minipage}\\hfill\\ ", "\\linebreak \\ \\linebreak\\nonfrenchspacing\n" ]; push(out.join("")); } function newStyle(text, flags) { let ltx = []; // Determine Colour flags.new ? ltx.push(this._colours.new()) : ltx.push(""); flags.old ? ltx.push(this._colours.old()) : ltx.push(""); flags.eq ? ltx.push(this._colours.eq()) : ltx.push(""); flags.neq ? ltx.push(this._colours.neq()) : ltx.push(""); // Apply Formatting text = flags.cl ? clean(text) : text; text = flags.st ? this._styles.st(text) : text; text = flags.bf ? this._styles.bf(text) : text; text = flags.it ? this._styles.it(text) : text; text = flags.ul ? this._styles.ul(text) : text; // Push and Finalize ltx.push(text); ltx.push(this._colours.text()); return ltx.join(""); } function genFlags() { return { new: false, old: false, ul: false, cl: false, st: false, eq: false, neq: false, }; } function styleAs() { return { // Highlighted new: (revHistory, n) => { let index = n ? n : 0; //return push(newStyle(revHistory[index], { new: true, ul: true })); }, // Modified Style Formatting neq: (revHistory, n, m) => { let ltx = []; let newIndex = n ? n : 0; let oldIndex = m ? m : 0; ltx.push(newStyle(revHistory[oldIndex], { old: true, st: true })); ltx.push(newStyle(revHistory[newIndex], { new: true, ul: true, cl: true })); push(ltx.join("")); //return ltx.join(""); }, // Removed Style Formatting old: (revHistory, n) => { let index = n ? n : 0; //return push(newStyle(revHistory[index], { old: true, st: true })); }, // Equal (unchanged) Formatting eq: (revHistory, n) => { let index = n ? n : 0; //return push(newStyle(revHistory[index], { eq: true })); }, // New (cleaned) newClean: (revHistory, n) => { let index = n ? n : 0; return newStyle(revHistory[index], { new: true, ul: true, cl: true }); }, // Modified (Cleaned) neqClean: (revHistory, n, m) => { let ltx = []; let newIndex = n ? n : 0; let oldIndex = m ? m : 0; ltx.push(newStyle(revHistory[oldIndex], { old: true, st: true, cl: true })); ltx.push(newStyle(revHistory[newIndex], { new: true, ul: true, cl: true })); push(ltx.join("")); //return ltx.join(""); }, // Removed (Cleaned) oldClean: (revHistory, n) => { let index = n ? n : 0; //return push(newStyle(revHistory[index], { old: true, st: true, cl: true })); }, // Equal (Cleaned) eqClean: (revHistory, n) => { let index = n ? n : 0; //return push(newStyle(revHistory[index], { eq: true, cl: true })); } } } function putRev(revHistory, style, n, m) { if (m) { return styleAs()[style](revHistory, n, m); } else { return styleAs()[style](revHistory, n); } } function putRevD(rev, style) { return styleAs()[style](rev, style, 0); } return { // ----------MAIN FUNCTIONS---------- // =================================> // Initializes Stream + Setups Flags init: (ltxStream, exportFlag, graphicsPath, colours) => init.apply(this, [ltxStream, exportFlag, graphicsPath, colours]), // Begin Document Entry begin: () => begin.apply(this), // End Document Entry end: () => end.apply(this), // ---- FILE COMMAND UTILITIES ---- // ================================> // Generate PDF File toPDF: () => toPDF.apply(this), // Generate DVI File toDVI: () => toDVI.apply(this), // Generate Latex File toLTX: () => toLTX.apply(this), // Get PDF Chunk Data getPDF: () => getPDF.apply(this), // Get PDF Stream getPDFStream: () => getPDFStream.apply(this), // Get Stream Object for DVI getDVIStream: () => getDVIStream.apply(this), // Get DVI Data getDVI: () => getDVI.apply(this), getLTX: () => getLTX.apply(this), // -----STREAM BASED UTILITIES----- // ===============================> // Push Raw Text into Stream push: (item) => push.apply(this, [item]), // Push If pushIf: (condition, item) => pushIf.apply(this, [condition, item]), // Push If/Else pushIfElse: (condition, item, other) => pushIfElse.apply(this, [condition, item, other]), // Push Clean Text into Stream cpush: (item) => cpush.apply(this, [item]), // Clean & Push If cpushIf: (condition, item) => cpushIf.apply(this, [condition, item]), // Push & Clean If/Else cpushIfElse: (condition, item, other) => cpushIfElse.apply(this, [condition, item, other]), // Pop Raw Text from Stream pop: () => pop.apply(this), // Pop If popIf: (condition) => popIf.apply(this, [condition]), // ----- TEXT BASED UTILITIES ------ // =================================> // Cleans Input of all Latex Special Characters clean: (string) => clean.apply(this, [string]), // Returns comparison between two variables compare: (one, two) => compare.apply(this, [one, two]), // Conditional Put putIf: (condition, string) => putIf.apply(this, [condition, string]), // If/Else putIfElse: (condition, string, other) => putIfElse.apply(this, [condition, string, other]), // Put cputIf: (condition, string) => cputIf.apply(this, [condition, string]), // If/Else cputIfElse: (condition, string, other) => cputIfElse.apply(this, [condition, string, other]), // ----- STRUCTURES & DOCUMENT TEMPLATES ------ // ===========================================> // Generate a Dynamic Table table: (column, array) => table.apply(this, [column, array]), // List (for enumerated lists) list: (array) => list.apply(this, [array]), // Section Heading section: (text) => section.apply(this, [text]), // Subsection Heading subsection: (text) => subsection.apply(this, [text]), // Definition definition: (label, def) => definition.apply(this, [label, def]), // Line Break br: () => br.apply(this), // New Page np: () => np.apply(this), // Plain Text plain: (text) => plain.apply(this, [text]), // Indented Text indent: () => indent.apply(this), // Bold Font bf: (text) => bf.apply(this, [text]), // Underline ul: (text) => ul.apply(this, [text]), // Paragraph Entry par: (text) => par.apply(this, [text]), // Enumerated Paragraphs (uses counter) enum: (label, text) => enumP.apply(this, [label, text]), // Enumerated Heading (uses counter) enumHead: (text) => enumHead.apply(this, [text]), // Floating Title (Right) floatRightHead: (text) => floatRightHead.apply(this, [text]), // Floating Title (Left) floatLeftHead: (text) => floatLeftHead.apply(this, [text]), // ---LATEX FORMATTING & STYLE FUNCTIONS--- // =======================================> // Generate Custom Styles newStyle: (text, flags) => newStyle.apply(this, [text, flags]), // Generate Flags Object for Use (defaults) genFlags: () => genFlags.apply(this), // New Style Formatting styleAs: () => styleAs.apply(this), // Return Styled Result putRev: (revHistory, style, n, m) => putRev.apply(this, [revHistory, style, n, m]), // Return Styled Result from Direct Input putRevD: (revivion, style) => putRevD.apply(this, [revision, style]), // ----------PROPERTIES-------------- // =================================> _latex: this._latex, _export: this._export, _packages: this._packages, _macro: this._macro, _sys: this._sys, _pdf: this._pdf, _dvi: this._dvi, _ltx: this._ltx, _config: this._config, _styles: this._styles, _colours: this._colours, _files: this._files, _images: this._images, _watermark: this._watermark, } })(); module.exports = latexEngine;