UNPKG

60.6 kBJavaScriptView Raw
1#!/usr/bin/env node
2(function() {
3;
4var ASCIIDrawing, ASCIIMapping, CSVDrawing, CoffeeMapping, Context, DOMParser, DSVDrawing, Drawing, Drawings, DynamicSymbol, Input, JSMapping, Mapping, Mappings, SSVDrawing, SVGNS, SVGTilerException, StaticSymbol, Symbol, TSVDrawing, XLINKNS, XLSXDrawings, XMLSerializer, allBlank, attributeOrStyle, blankCells, bufferSize, convertSVG, domImplementation, escapeId, exports, extensionMap, extensionOf, fs, graphemeSplitter, help, isAuto, main, overflowBox, parseNum, path, postprocess, prettyXML, sanitize, splitIntoLines, svgBBox, svgtiler, unrecognizedSymbol, whitespace, xmldom, zIndex, zeroSizeReplacement,
5 indexOf = [].indexOf,
6 hasProp = {}.hasOwnProperty;
7
8if (typeof window === "undefined" || window === null) {
9 path = require('path');
10 fs = require('fs');
11 xmldom = require('xmldom');
12 DOMParser = xmldom.DOMParser;
13 domImplementation = new xmldom.DOMImplementation();
14 XMLSerializer = xmldom.XMLSerializer;
15 prettyXML = require('prettify-xml');
16 graphemeSplitter = new require('grapheme-splitter')();
17 svgtiler = require('../package.json');
18 require('coffeescript/register');
19} else {
20 DOMParser = window.DOMParser; // escape CoffeeScript scope
21 domImplementation = document.implementation;
22 path = {
23 extname: function(x) {
24 return /\.[^\/]+$/.exec(x)[0];
25 },
26 dirname: function(x) {
27 return /[^]*\/|/.exec(x)[0];
28 }
29 };
30}
31
32SVGNS = 'http://www.w3.org/2000/svg';
33
34XLINKNS = 'http://www.w3.org/1999/xlink';
35
36splitIntoLines = function(data) {
37 return data.replace('\r\n', '\n').replace('\r', '\n').split('\n');
38};
39
40whitespace = /[\s\uFEFF\xA0]+/; //# based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
41
42extensionOf = function(filename) {
43 return path.extname(filename).toLowerCase();
44};
45
46SVGTilerException = class SVGTilerException {
47 constructor(message) {
48 this.message = message;
49 }
50
51 toString() {
52 return `svgtiler: ${this.message}`;
53 }
54
55};
56
57overflowBox = function(xml) {
58 if (xml.documentElement.hasAttribute('overflowBox')) {
59 return xml.documentElement.getAttribute('overflowBox').split(/\s*,?\s+/).map(parseFloat);
60 } else {
61 return null;
62 }
63};
64
65parseNum = function(x) {
66 var parsed;
67 parsed = parseFloat(x);
68 if (isNaN(parsed)) {
69 return null;
70 } else {
71 return parsed;
72 }
73};
74
75svgBBox = function(xml) {
76 var recurse, viewBox;
77 //# xxx Many unsupported features!
78 //# - transformations
79 //# - used symbols/defs
80 //# - paths
81 //# - text
82 //# - line widths which extend bounding box
83 if (xml.documentElement.hasAttribute('viewBox')) {
84 viewBox = xml.documentElement.getAttribute('viewBox').split(/\s*,?\s+/).map(parseNum);
85 if (indexOf.call(viewBox, null) >= 0) {
86 return null;
87 } else {
88 return viewBox;
89 }
90 } else {
91 recurse = function(node) {
92 var child, coord, cx, cy, point, points, r, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref15, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, rx, ry, viewBoxes, x1, x2, xmax, xmin, xs, y1, y2, ymax, ymin, ys;
93 if (node.nodeType !== node.ELEMENT_NODE || ((ref = node.tagName) === 'defs' || ref === 'symbol' || ref === 'use')) {
94 return null;
95 }
96 switch (node.tagName) {
97 case 'rect':
98 case 'image':
99 //# For <image>, should autodetect image size (#42)
100 return [(ref1 = parseNum(node.getAttribute('x'))) != null ? ref1 : 0, (ref2 = parseNum(node.getAttribute('y'))) != null ? ref2 : 0, (ref3 = parseNum(node.getAttribute('width'))) != null ? ref3 : 0, (ref4 = parseNum(node.getAttribute('height'))) != null ? ref4 : 0];
101 case 'circle':
102 cx = (ref5 = parseNum(node.getAttribute('cx'))) != null ? ref5 : 0;
103 cy = (ref6 = parseNum(node.getAttribute('cy'))) != null ? ref6 : 0;
104 r = (ref7 = parseNum(node.getAttribute('r'))) != null ? ref7 : 0;
105 return [cx - r, cy - r, 2 * r, 2 * r];
106 case 'ellipse':
107 cx = (ref8 = parseNum(node.getAttribute('cx'))) != null ? ref8 : 0;
108 cy = (ref9 = parseNum(node.getAttribute('cy'))) != null ? ref9 : 0;
109 rx = (ref10 = parseNum(node.getAttribute('rx'))) != null ? ref10 : 0;
110 ry = (ref11 = parseNum(node.getAttribute('ry'))) != null ? ref11 : 0;
111 return [cx - rx, cy - ry, 2 * rx, 2 * ry];
112 case 'line':
113 x1 = (ref12 = parseNum(node.getAttribute('x1'))) != null ? ref12 : 0;
114 y1 = (ref13 = parseNum(node.getAttribute('y1'))) != null ? ref13 : 0;
115 x2 = (ref14 = parseNum(node.getAttribute('x2'))) != null ? ref14 : 0;
116 y2 = (ref15 = parseNum(node.getAttribute('y2'))) != null ? ref15 : 0;
117 xmin = Math.min(x1, x2);
118 ymin = Math.min(y1, y2);
119 return [xmin, ymin, Math.max(x1, x2) - xmin, Math.max(y1, y2) - ymin];
120 case 'polyline':
121 case 'polygon':
122 points = (function() {
123 var k, len, ref16, results;
124 ref16 = node.getAttribute('points').trim().split(/\s+/);
125 results = [];
126 for (k = 0, len = ref16.length; k < len; k++) {
127 point = ref16[k];
128 results.push((function() {
129 var l, len1, ref17, results1;
130 ref17 = point.split(/,/);
131 results1 = [];
132 for (l = 0, len1 = ref17.length; l < len1; l++) {
133 coord = ref17[l];
134 results1.push(parseFloat(coord));
135 }
136 return results1;
137 })());
138 }
139 return results;
140 })();
141 xs = (function() {
142 var k, len, results;
143 results = [];
144 for (k = 0, len = points.length; k < len; k++) {
145 point = points[k];
146 results.push(point[0]);
147 }
148 return results;
149 })();
150 ys = (function() {
151 var k, len, results;
152 results = [];
153 for (k = 0, len = points.length; k < len; k++) {
154 point = points[k];
155 results.push(point[1]);
156 }
157 return results;
158 })();
159 xmin = Math.min(...xs);
160 ymin = Math.min(...ys);
161 if (isNaN(xmin) || isNaN(ymin)) { // invalid points attribute; don't render
162 return null;
163 } else {
164 return [xmin, ymin, Math.max(...xs) - xmin, Math.max(...ys) - ymin];
165 }
166 break;
167 default:
168 viewBoxes = (function() {
169 var k, len, ref16, results;
170 ref16 = node.childNodes;
171 results = [];
172 for (k = 0, len = ref16.length; k < len; k++) {
173 child = ref16[k];
174 results.push(recurse(child));
175 }
176 return results;
177 })();
178 viewBoxes = (function() {
179 var k, len, results;
180 results = [];
181 for (k = 0, len = viewBoxes.length; k < len; k++) {
182 viewBox = viewBoxes[k];
183 if (viewBox != null) {
184 results.push(viewBox);
185 }
186 }
187 return results;
188 })();
189 xmin = Math.min(...((function() {
190 var k, len, results;
191 results = [];
192 for (k = 0, len = viewBoxes.length; k < len; k++) {
193 viewBox = viewBoxes[k];
194 results.push(viewBox[0]);
195 }
196 return results;
197 })()));
198 ymin = Math.min(...((function() {
199 var k, len, results;
200 results = [];
201 for (k = 0, len = viewBoxes.length; k < len; k++) {
202 viewBox = viewBoxes[k];
203 results.push(viewBox[1]);
204 }
205 return results;
206 })()));
207 xmax = Math.max(...((function() {
208 var k, len, results;
209 results = [];
210 for (k = 0, len = viewBoxes.length; k < len; k++) {
211 viewBox = viewBoxes[k];
212 results.push(viewBox[0] + viewBox[2]);
213 }
214 return results;
215 })()));
216 ymax = Math.max(...((function() {
217 var k, len, results;
218 results = [];
219 for (k = 0, len = viewBoxes.length; k < len; k++) {
220 viewBox = viewBoxes[k];
221 results.push(viewBox[1] + viewBox[3]);
222 }
223 return results;
224 })()));
225 return [xmin, ymin, xmax - xmin, ymax - ymin];
226 }
227 };
228 viewBox = recurse(xml.documentElement);
229 if (indexOf.call(viewBox, 2e308) >= 0 || indexOf.call(viewBox, -2e308) >= 0) {
230 return null;
231 } else {
232 return viewBox;
233 }
234 }
235};
236
237isAuto = function(xml, prop) {
238 return xml.documentElement.hasAttribute(prop) && /^\s*auto\s*$/i.test(xml.documentElement.getAttribute(prop));
239};
240
241attributeOrStyle = function(node, attr, styleKey = attr) {
242 var match, style, value;
243 if (value = node.getAttribute(attr)) {
244 return value.trim();
245 } else {
246 style = node.getAttribute('style');
247 if (style) {
248 match = /(?:^|;)\s*#{styleKey}\s*:\s*([^;\s][^;]*)/i.exec(style);
249 return match != null ? match[1] : void 0;
250 }
251 }
252};
253
254zIndex = function(node) {
255 var z;
256 //# Check whether DOM node has a specified z-index, defaulting to zero.
257 //# Note that z-index must be an integer.
258 //# 1. https://www.w3.org/Graphics/SVG/WG/wiki/Proposals/z-index suggests
259 //# a z-index="..." attribute. Check for this first.
260 //# 2. Look for style="z-index:..." as in HTML.
261 z = parseInt(attributeOrStyle(node, 'z-index'));
262 if (isNaN(z)) {
263 return 0;
264 } else {
265 return z;
266 }
267};
268
269Symbol = (function() {
270 class Symbol {
271 static parse(key, data, dirname) {
272 var extension, filename, size;
273 if (data == null) {
274 throw new SVGTilerException(`Attempt to create symbol '${key}' without data`);
275 } else if (typeof data === 'function') {
276 return new DynamicSymbol(key, data, dirname);
277 } else if (data.function != null) {
278 return new DynamicSymbol(key, data.function, dirname);
279 } else {
280 //# Render Preact virtual dom nodes (e.g. from JSX notation) into strings.
281 //# Serialization + parsing shouldn't be necessary, but this lets us
282 //# deal with one parsed format (xmldom).
283 if (typeof data === 'object' && (data.type != null) && (data.props != null)) {
284 data = require('preact-render-to-string')(data);
285 }
286 return new StaticSymbol(key, (function() {
287 if (typeof data === 'string') {
288 if (data.trim() === '') { //# Blank SVG treated as 0x0 symbol
289 return {
290 svg: '<symbol viewBox="0 0 0 0"/>'
291 };
292 } else if (data.indexOf('<') < 0) { //# No <'s -> interpret as filename
293 if (dirname != null) {
294 filename = path.join(dirname, data);
295 } else {
296 filename = data;
297 }
298 extension = extensionOf(data);
299 //# <image> tag documentation: "Conforming SVG viewers need to
300 //# support at least PNG, JPEG and SVG format files."
301 //# [https://svgwg.org/svg2-draft/embedded.html#ImageElement]
302 switch (extension) {
303 case '.png':
304 case '.jpg':
305 case '.jpeg':
306 case '.gif':
307 size = require('image-size')(filename);
308 return {
309 svg: `<image xlink:href="${encodeURI(data)}" width="${size.width}" height="${size.height}"${this.imageRendering}/>`
310 };
311 case '.svg':
312 return {
313 filename: filename,
314 svg: fs.readFileSync(filename, {
315 encoding: this.svgEncoding
316 })
317 };
318 default:
319 throw new SVGTilerException(`Unrecognized extension in filename '${data}' for symbol '${key}'`);
320 }
321 } else {
322 return {
323 svg: data
324 };
325 }
326 } else {
327 return data;
328 }
329 }).call(this));
330 }
331 }
332
333 includes(substring) {
334 return this.key.indexOf(substring) >= 0;
335 }
336
337 };
338
339 Symbol.svgEncoding = 'utf8';
340
341 Symbol.forceWidth = null; //# default: no size forcing
342
343 Symbol.forceHeight = null; //# default: no size forcing
344
345 Symbol.texText = false;
346
347 /*
348 Attempt to render pixels as pixels, as needed for old-school graphics.
349 SVG 1.1 and Inkscape define image-rendering="optimizeSpeed" for this.
350 Chrome doesn't support this, but supports a CSS3 (or SVG) specification of
351 "image-rendering:pixelated". Combining these seems to work everywhere.
352 */
353 Symbol.imageRendering = ' image-rendering="optimizeSpeed" style="image-rendering:pixelated"';
354
355 return Symbol;
356
357}).call(this);
358
359//# ECMA6: @key.includes substring
360escapeId = function(key) {
361 /*
362 id/href follows the IRI spec [https://tools.ietf.org/html/rfc3987]:
363 ifragment = *( ipchar / "/" / "?" )
364 ipchar = iunreserved / pct-encoded / sub-delims / ":" / "@"
365 iunreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" / ucschar
366 pct-encoded = "%" HEXDIG HEXDIG
367 sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
368 / "*" / "+" / "," / ";" / "="
369 ucschar = %xA0-D7FF / %xF900-FDCF / #%xFDF0-FFEF
370 / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
371 / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
372 / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
373 / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
374 / %xD0000-DFFFD / %xE1000-EFFFD
375 We also want to escape colon (:) which seems to cause trouble.
376 We use encodeURIComponent which escapes everything except
377 A-Z a-z 0-9 - _ . ! ~ * ' ( )
378 [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent]
379 Unfortunately, Inkscape seems to ignore any %-encoded symbols; see
380 https://bugs.launchpad.net/inkscape/+bug/1737778
381 So we replace '%' with '$', an allowed character that's already escaped.
382 In the special case of a blank key, we use the special $BLANK$ which cannot
383 be generated by the escaping process.
384 */
385 return (encodeURIComponent(key).replace(/%/g, '$')) || '$BLANK$';
386};
387
388zeroSizeReplacement = 1;
389
390StaticSymbol = (function() {
391 class StaticSymbol extends Symbol {
392 constructor(key1, options) {
393 var extract, key, overflow, value, warnings;
394 super();
395 this.key = key1;
396 for (key in options) {
397 if (!hasProp.call(options, key)) continue;
398 value = options[key];
399 this[key] = value;
400 }
401 //# Force SVG namespace when parsing, so nodes have correct namespaceURI.
402 //# (This is especially important on the browser, so the results can be
403 //# reparented into an HTML Document.)
404 this.svg = this.svg.replace(/^\s*<(?:[^<>'"\/]|'[^']*'|"[^"]*")*\s*(\/?\s*>)/, function(match, end) {
405 if (indexOf.call(match, 'xmlns') < 0) {
406 match = match.slice(0, match.length - end.length) + ` xmlns='${SVGNS}'` + match.slice(match.length - end.length);
407 }
408 return match;
409 });
410 this.xml = new DOMParser({
411 locator: { //# needed when specifying errorHandler
412 line: 1,
413 col: 1
414 },
415 errorHandler: (level, msg, indent = ' ') => {
416 msg = msg.replace(/^\[xmldom [^\[\]]*\]\t/, '');
417 msg = msg.replace(/@#\[line:(\d+),col:(\d+)\]$/, (match, line, col) => {
418 var lines;
419 lines = this.svg.split('\n');
420 return (line > 1 ? indent + lines[line - 2] + '\n' : '') + indent + lines[line - 1] + '\n' + indent + ' '.repeat(col - 1) + '^^^' + (line < lines.length ? '\n' + indent + lines[line] : '');
421 });
422 return console.error(`SVG parse \${level} in symbol '${this.key}': ${msg}`);
423 }
424 }).parseFromString(this.svg, 'image/svg+xml');
425 // Remove from the symbol any top-level xmlns=SVGNS possibly added above:
426 // we will have such a tag in the top-level <svg>.
427 this.xml.documentElement.removeAttribute('xmlns');
428 this.viewBox = svgBBox(this.xml);
429 this.overflowBox = overflowBox(this.xml);
430 overflow = attributeOrStyle(this.xml.documentElement, 'overflow');
431 this.overflowVisible = (overflow != null) && /^visible\b/.test(overflow);
432 this.width = this.height = null;
433 if (this.viewBox != null) {
434 this.width = this.viewBox[2];
435 this.height = this.viewBox[3];
436 /*
437 SVG's viewBox has a special rule that "A value of zero [in <width>
438 or <height>] disables rendering of the element." Avoid this.
439 [https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute]
440 */
441 if (this.overflowVisible) {
442 if (this.width === 0) {
443 this.viewBox[2] = zeroSizeReplacement;
444 }
445 if (this.height === 0) {
446 this.viewBox[3] = zeroSizeReplacement;
447 }
448 }
449 }
450 if (Symbol.forceWidth != null) {
451 this.width = Symbol.forceWidth;
452 }
453 if (Symbol.forceHeight != null) {
454 this.height = Symbol.forceHeight;
455 }
456 warnings = [];
457 if (this.width == null) {
458 warnings.push('width');
459 this.width = 0;
460 }
461 if (this.height == null) {
462 warnings.push('height');
463 this.height = 0;
464 }
465 if (warnings.length > 0) {
466 console.warn(`Failed to detect ${warnings.join(' and ')} of SVG for symbol '${this.key}'`);
467 }
468 //# Detect special `width="auto"` and/or `height="auto"` fields for future
469 //# processing, and remove them to ensure valid SVG.
470 this.autoWidth = isAuto(this.xml, 'width');
471 this.autoHeight = isAuto(this.xml, 'height');
472 if (this.autoWidth) {
473 this.xml.documentElement.removeAttribute('width');
474 }
475 if (this.autoHeight) {
476 this.xml.documentElement.removeAttribute('height');
477 }
478 this.zIndex = zIndex(this.xml.documentElement);
479 //# Optionally extract <text> nodes for LaTeX output
480 if (Symbol.texText) {
481 this.text = [];
482 extract = (node) => {
483 var child, nextChild, results;
484 if (!node.hasChildNodes()) {
485 return;
486 }
487 child = node.lastChild;
488 results = [];
489 while (child != null) {
490 nextChild = child.previousSibling;
491 if (child.nodeName === 'text') {
492 this.text.push(child);
493 node.removeChild(child);
494 } else {
495 extract(child);
496 }
497 results.push(child = nextChild);
498 }
499 return results;
500 };
501 extract(this.xml.documentElement);
502 }
503 }
504
505 id() {
506 return escapeId(this.key);
507 }
508
509 use() {
510 return this;
511 }
512
513 };
514
515 StaticSymbol.prototype.usesContext = false;
516
517 return StaticSymbol;
518
519}).call(this);
520
521DynamicSymbol = (function() {
522 class DynamicSymbol extends Symbol {
523 constructor(key1, func, dirname1) {
524 super();
525 this.key = key1;
526 this.func = func;
527 this.dirname = dirname1;
528 this.versions = {};
529 this.nversions = 0;
530 this.constructor.all.push(this);
531 }
532
533 static resetAll() {
534 var k, len, ref, results, symbol;
535 ref = this.all;
536 //# Resets all DynamicSymbol's versions to 0.
537 //# Use before starting a new SVG document.
538 results = [];
539 for (k = 0, len = ref.length; k < len; k++) {
540 symbol = ref[k];
541 symbol.versions = {};
542 results.push(symbol.nversions = 0);
543 }
544 return results;
545 }
546
547 use(context) {
548 var result, string, version;
549 result = this.func.call(context);
550 if (result == null) {
551 throw new Error(`Function for symbol ${this.key} returned ${result}`);
552 }
553 //# We use JSON serialization to detect duplicate symbols. This enables
554 //# return values like {filename: ...} and JSX virtual dom elements,
555 //# in addition to raw SVG strings.
556 string = JSON.stringify(result);
557 if (!(string in this.versions)) {
558 version = this.nversions++;
559 this.versions[string] = Symbol.parse(`${this.key}$v${version}`, result, this.dirname);
560 this.versions[string].id = () => {
561 return `${escapeId(this.key)}$v${version}`;
562 };
563 }
564 return this.versions[string];
565 }
566
567 };
568
569 DynamicSymbol.all = [];
570
571 DynamicSymbol.prototype.usesContext = true;
572
573 return DynamicSymbol;
574
575}).call(this);
576
577//# Symbol to fall back to when encountering an unrecognized symbol.
578//# Path from https://commons.wikimedia.org/wiki/File:Replacement_character.svg
579//# by Amit6, released into the public domain.
580unrecognizedSymbol = new StaticSymbol('$UNRECOGNIZED$', {
581 svg: `<symbol viewBox="0 0 200 200" preserveAspectRatio="none" width="auto" height="auto">
582 <rect width="200" height="200" fill="yellow"/>
583 <path xmlns="http://www.w3.org/2000/svg" stroke="none" fill="red" d="M 200,100 100,200 0,100 100,0 200,100 z M 135.64709,74.70585 q 0,-13.52935 -10.00006,-22.52943 -9.99999,-8.99999 -24.35289,-8.99999 -17.29415,0 -30.117661,5.29409 L 69.05879,69.52938 q 9.764731,-6.23528 21.52944,-6.23528 8.82356,0 14.58824,4.82351 5.76469,4.82351 5.76469,12.70589 0,8.5883 -9.94117,21.70588 -9.94117,13.11766 -9.94117,26.76473 l 17.88236,0 q 0,-6.3529 6.9412,-14.9412 11.76471,-14.58816 12.82351,-16.35289 6.9412,-11.05887 6.9412,-23.29417 z m -22.00003,92.11771 0,-24.70585 -27.29412,0 0,24.70585 27.29412,0 z"/>
584</symbol>`
585});
586
587unrecognizedSymbol.id = function() {
588 return '$UNRECOGNIZED$'; // cannot be output of standard id()
589};
590
591Input = (function() {
592 class Input {
593 static parseFile(filename, filedata) {
594 var input;
595 //# Generic method to parse file once we're already in the right class.
596 input = new (this)();
597 input.filename = filename;
598 if (filedata == null) {
599 filedata = fs.readFileSync(filename, {
600 encoding: this.encoding
601 });
602 }
603 input.parse(filedata);
604 return input;
605 }
606
607 static recognize(filename, filedata) {
608 var extension;
609 //# Recognize type of file and call corresponding class's `parseFile`.
610 extension = extensionOf(filename);
611 if (extension in extensionMap) {
612 return extensionMap[extension].parseFile(filename, filedata);
613 } else {
614 throw new SVGTilerException(`Unrecognized extension in filename ${filename}`);
615 }
616 }
617
618 };
619
620 Input.encoding = 'utf8';
621
622 return Input;
623
624}).call(this);
625
626Mapping = class Mapping extends Input {
627 load(data) {
628 this.map = {};
629 if (typeof data === 'function') {
630 return this.function = data;
631 } else {
632 return this.merge(data);
633 }
634 }
635
636 merge(data) {
637 var dirname, key, results, value;
638 if (this.filename != null) {
639 dirname = path.dirname(this.filename);
640 }
641 results = [];
642 for (key in data) {
643 if (!hasProp.call(data, key)) continue;
644 value = data[key];
645 if (!(value instanceof Symbol)) {
646 value = Symbol.parse(key, value, dirname);
647 }
648 results.push(this.map[key] = value);
649 }
650 return results;
651 }
652
653 lookup(key) {
654 var value;
655 key = key.toString(); //# Sometimes get a number, e.g., from XLSX
656 if (key in this.map) {
657 return this.map[key];
658 } else if (this.function != null) {
659 //# Cache return value of function so that only one Symbol generated
660 //# for each key. It still may be a DynamicSymbol, which will allow
661 //# it to make multiple versions, but keep track of which are the same.
662 value = this.function(key);
663 if (value != null) {
664 return this.map[key] = Symbol.parse(key, value);
665 } else {
666 return value;
667 }
668 } else {
669 return void 0;
670 }
671 }
672
673};
674
675ASCIIMapping = (function() {
676 class ASCIIMapping extends Mapping {
677 parse(data) {
678 var k, key, len, line, map, ref, separator;
679 map = {};
680 ref = splitIntoLines(data);
681 for (k = 0, len = ref.length; k < len; k++) {
682 line = ref[k];
683 separator = whitespace.exec(line);
684 if (separator == null) {
685 continue;
686 }
687 if (separator.index === 0) {
688 if (separator[0].length === 1) {
689 //# Single whitespace character at beginning defines blank character
690 key = '';
691 } else {
692 //# Multiple whitespace at beginning defines first whitespace character
693 key = line[0];
694 }
695 } else {
696 key = line.slice(0, separator.index);
697 }
698 map[key] = line.slice(separator.index + separator[0].length);
699 }
700 return this.load(map);
701 }
702
703 };
704
705 ASCIIMapping.title = "ASCII mapping file";
706
707 ASCIIMapping.help = "Each line is <symbol-name><space><raw SVG or filename.svg>";
708
709 return ASCIIMapping;
710
711}).call(this);
712
713JSMapping = (function() {
714 class JSMapping extends Mapping {
715 parse(data) {
716 var __filename, code;
717 ({code} = require('@babel/core').transform(data, {
718 filename: this.filename,
719 plugins: [
720 [
721 require.resolve('@babel/plugin-transform-react-jsx'),
722 {
723 useBuiltIns: true,
724 pragma: 'preact.h',
725 pragmaFrag: 'preact.Fragment',
726 throwIfNamespace: false
727 }
728 ]
729 ],
730 sourceMaps: 'inline',
731 retainLines: true
732 }));
733 if (0 <= code.indexOf('preact.')) {
734 code = `var preact = require('preact'), h = preact.h; ${code}`;
735 }
736 //# Mimick NodeJS module's __filename and __dirname variables.
737 //# Redirect require() to use paths relative to the mapping file.
738 //# xxx should probably actually create a NodeJS module when possible
739 __filename = path.resolve(this.filename);
740 code = `var __filename = ${JSON.stringify(__filename)}, __dirname = ${JSON.stringify(path.dirname(__filename))}, __require = require; require = (module) => __require(module.startsWith('.') ? __require('path').resolve(__dirname, module) : module); ${code}\n//@ sourceURL=${this.filename}`;
741 return this.load(eval(code));
742 }
743
744 };
745
746 JSMapping.title = "JavaScript mapping file";
747
748 JSMapping.help = "Object mapping symbol names to SYMBOL e.g. dot: 'dot.svg'";
749
750 return JSMapping;
751
752}).call(this);
753
754CoffeeMapping = (function() {
755 class CoffeeMapping extends JSMapping {
756 parse(data) {
757 return super.parse(require('coffeescript').compile(data, {
758 bare: true,
759 filename: this.filename,
760 sourceFiles: [this.filename],
761 inlineMap: true
762 }));
763 }
764
765 };
766
767 CoffeeMapping.title = "CoffeeScript mapping file";
768
769 CoffeeMapping.help = "Object mapping symbol names to SYMBOL e.g. dot: 'dot.svg'";
770
771 return CoffeeMapping;
772
773}).call(this);
774
775//catch err
776// throw err
777// if err.stack? and err.stack.startsWith "#{@filename}:"
778// sourceMap = require('coffeescript').compile(data,
779// bare: true
780// filename: @filename
781// sourceFiles: [@filename]
782// sourceMap: true
783// ).sourceMap
784// err.stack = err.stack.replace /:([0-9]*)/, (m, line) ->
785// ## sourceMap starts line numbers at 0, but we want to work from 1
786// for col in sourceMap?.lines[line-1]?.columns ? [] when col?.sourceLine?
787// unless sourceLine? and sourceLine < col.sourceLine
788// sourceLine = col.sourceLine
789// line = sourceLine + 1
790// ":#{line}"
791// throw err
792Mappings = class Mappings {
793 constructor(maps = []) {
794 this.maps = maps;
795 }
796
797 push(map) {
798 return this.maps.push(map);
799 }
800
801 lookup(key) {
802 var i, k, ref, value;
803 if (!this.maps.length) {
804 return;
805 }
806 for (i = k = ref = this.maps.length - 1; (ref <= 0 ? k <= 0 : k >= 0); i = ref <= 0 ? ++k : --k) {
807 value = this.maps[i].lookup(key);
808 if (value != null) {
809 return value;
810 }
811 }
812 return void 0;
813 }
814
815};
816
817blankCells = {
818 '': true,
819 ' ': true //# for ASCII art in particular
820};
821
822allBlank = function(list) {
823 var k, len, x;
824 for (k = 0, len = list.length; k < len; k++) {
825 x = list[k];
826 if ((x != null) && !(x in blankCells)) {
827 return false;
828 }
829 }
830 return true;
831};
832
833Drawing = class Drawing extends Input {
834 load(data) {
835 var cell, j, k, l, len, len1, row;
836 //# Turn strings into arrays
837 data = (function() {
838 var k, len, results;
839 results = [];
840 for (k = 0, len = data.length; k < len; k++) {
841 row = data[k];
842 results.push((function() {
843 var l, len1, results1;
844 results1 = [];
845 for (l = 0, len1 = row.length; l < len1; l++) {
846 cell = row[l];
847 results1.push(cell);
848 }
849 return results1;
850 })());
851 }
852 return results;
853 })();
854 if (!Drawing.keepMargins) {
855 //# Top margin
856 while (data.length > 0 && allBlank(data[0])) {
857 data.shift();
858 }
859 //# Bottom margin
860 while (data.length > 0 && allBlank(data[data.length - 1])) {
861 data.pop();
862 }
863 if (data.length > 0) {
864 //# Left margin
865 while (allBlank((function() {
866 var l, len1, results;
867 results = [];
868 for (l = 0, len1 = data.length; l < len1; l++) {
869 row = data[l];
870 results.push(row[0]);
871 }
872 return results;
873 })())) {
874 for (k = 0, len = data.length; k < len; k++) {
875 row = data[k];
876 row.shift();
877 }
878 }
879 //# Right margin
880 j = Math.max(...((function() {
881 var l, len1, results;
882 results = [];
883 for (l = 0, len1 = data.length; l < len1; l++) {
884 row = data[l];
885 results.push(row.length);
886 }
887 return results;
888 })()));
889 while (j >= 0 && allBlank((function() {
890 var len2, m, results;
891 results = [];
892 for (m = 0, len2 = data.length; m < len2; m++) {
893 row = data[m];
894 results.push(row[j]);
895 }
896 return results;
897 })())) {
898 for (l = 0, len1 = data.length; l < len1; l++) {
899 row = data[l];
900 if (j < row.length) {
901 row.pop();
902 }
903 }
904 j--;
905 }
906 }
907 }
908 return this.data = data;
909 }
910
911 writeSVG(mappings, filename) {
912 //# Default filename is the input filename with extension replaced by .svg
913 if (filename == null) {
914 filename = path.parse(this.filename);
915 if (filename.ext === '.svg') {
916 filename.base += '.svg';
917 } else {
918 filename.base = filename.base.slice(0, -filename.ext.length) + '.svg';
919 }
920 filename = path.format(filename);
921 }
922 console.log('->', filename);
923 fs.writeFileSync(filename, this.renderSVG(mappings));
924 return filename;
925 }
926
927 renderSVGDOM(mappings) {
928 var attribute, cell, child, colWidths, coordsRow, doc, dx, dy, i, j, k, key, l, lastSymbol, len, len1, len2, len3, len4, len5, len6, level, levelOrder, levels, m, missing, n, name, node, o, p, q, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, row, row2, rowHeight, scaleX, scaleY, svg, symbol, symbolsByKey, use, viewBox, x, y;
929 /*
930 Main rendering engine, returning an xmldom object for the whole document.
931 Also saves the table of symbols in `@symbols`, the corresponding
932 coordinates in `@coords`, and overall `@weight` and `@height`,
933 for use by `renderTeX`.
934 */
935 DynamicSymbol.resetAll();
936 doc = domImplementation.createDocument(SVGNS, 'svg');
937 svg = doc.documentElement;
938 svg.setAttribute('xmlns:xlink', XLINKNS);
939 svg.setAttribute('version', '1.1');
940 //svg.appendChild defs = doc.createElementNS SVGNS, 'defs'
941 //# Look up all symbols in the drawing.
942 missing = {};
943 this.symbols = (function() {
944 var k, len, ref, results;
945 ref = this.data;
946 results = [];
947 for (k = 0, len = ref.length; k < len; k++) {
948 row = ref[k];
949 results.push((function() {
950 var l, len1, results1;
951 results1 = [];
952 for (l = 0, len1 = row.length; l < len1; l++) {
953 cell = row[l];
954 symbol = mappings.lookup(cell);
955 if (symbol != null) {
956 results1.push(lastSymbol = symbol);
957 } else {
958 missing[cell] = true;
959 results1.push(unrecognizedSymbol);
960 }
961 }
962 return results1;
963 })());
964 }
965 return results;
966 }).call(this);
967 missing = (function() {
968 var results;
969 results = [];
970 for (key in missing) {
971 if (!hasProp.call(missing, key)) continue;
972 results.push(`'${key}'`);
973 }
974 return results;
975 })();
976 if (missing.length) {
977 console.warn("Failed to recognize symbols:", missing.join(', '));
978 }
979 //# Instantiate (.use) all (dynamic) symbols in the drawing.
980 symbolsByKey = {};
981 this.symbols = (function() {
982 var k, len, ref, results;
983 ref = this.symbols;
984 results = [];
985 for (i = k = 0, len = ref.length; k < len; i = ++k) {
986 row = ref[i];
987 results.push((function() {
988 var l, len1, results1;
989 results1 = [];
990 for (j = l = 0, len1 = row.length; l < len1; j = ++l) {
991 symbol = row[j];
992 if (symbol.usesContext) {
993 symbol = symbol.use(new Context(this, i, j));
994 } else {
995 symbol = symbol.use();
996 }
997 if (!(symbol.key in symbolsByKey)) {
998 symbolsByKey[symbol.key] = symbol;
999 } else if (symbolsByKey[symbol.key] === !symbol) {
1000 console.warn(`Multiple symbols with key ${symbol.key}`);
1001 }
1002 results1.push(symbol);
1003 }
1004 return results1;
1005 }).call(this));
1006 }
1007 return results;
1008 }).call(this);
1009//# Include all used symbols in SVG
1010 for (key in symbolsByKey) {
1011 symbol = symbolsByKey[key];
1012 if (symbol == null) {
1013 continue;
1014 }
1015 svg.appendChild(node = doc.createElementNS(SVGNS, 'symbol'));
1016 node.setAttribute('id', symbol.id());
1017 if ((ref = symbol.xml.documentElement.tagName) === 'svg' || ref === 'symbol') {
1018 ref1 = symbol.xml.documentElement.attributes;
1019 //# Remove a layer of indirection for <svg> and <symbol>
1020 for (k = 0, len = ref1.length; k < len; k++) {
1021 attribute = ref1[k];
1022 if (!(((ref2 = attribute.name) === 'version' || ref2 === 'id') || attribute.name.slice(0, 5) === 'xmlns')) {
1023 node.setAttribute(attribute.name, attribute.value);
1024 }
1025 }
1026 ref3 = symbol.xml.documentElement.childNodes;
1027 for (l = 0, len1 = ref3.length; l < len1; l++) {
1028 child = ref3[l];
1029 node.appendChild(child.cloneNode(true));
1030 }
1031 } else {
1032 node.appendChild(symbol.xml.documentElement.cloneNode(true));
1033 }
1034 //# Set/overwrite any viewbox attribute with one from symbol.
1035 if (symbol.viewBox != null) {
1036 node.setAttribute('viewBox', symbol.viewBox);
1037 }
1038 }
1039 //# Lay out the symbols in the drawing via SVG <use>.
1040 viewBox = [
1041 0,
1042 0,
1043 0,
1044 0 //# initially x-min, y-min, x-max, y-max
1045 ];
1046 levels = {};
1047 y = 0;
1048 colWidths = {};
1049 this.coords = [];
1050 ref4 = this.symbols;
1051 for (i = m = 0, len2 = ref4.length; m < len2; i = ++m) {
1052 row = ref4[i];
1053 this.coords.push(coordsRow = []);
1054 rowHeight = 0;
1055 for (n = 0, len3 = row.length; n < len3; n++) {
1056 symbol = row[n];
1057 if (!symbol.autoHeight) {
1058 if (symbol.height > rowHeight) {
1059 rowHeight = symbol.height;
1060 }
1061 }
1062 }
1063 x = 0;
1064 for (j = o = 0, len4 = row.length; o < len4; j = ++o) {
1065 symbol = row[j];
1066 coordsRow.push({x, y});
1067 if (symbol == null) {
1068 continue;
1069 }
1070 if (levels[name = symbol.zIndex] == null) {
1071 levels[name] = [];
1072 }
1073 levels[symbol.zIndex].push(use = doc.createElementNS(SVGNS, 'use'));
1074 use.setAttribute('xlink:href', '#' + symbol.id());
1075 use.setAttributeNS(SVGNS, 'x', x);
1076 use.setAttributeNS(SVGNS, 'y', y);
1077 scaleX = scaleY = 1;
1078 if (symbol.autoWidth) {
1079 if (colWidths[j] == null) {
1080 colWidths[j] = Math.max(0, ...((function() {
1081 var len5, p, ref5, results;
1082 ref5 = this.symbols;
1083 results = [];
1084 for (p = 0, len5 = ref5.length; p < len5; p++) {
1085 row2 = ref5[p];
1086 if ((row2[j] != null) && !row2[j].autoWidth) {
1087 results.push(row2[j].width);
1088 }
1089 }
1090 return results;
1091 }).call(this)));
1092 }
1093 if (symbol.width !== 0) {
1094 scaleX = colWidths[j] / symbol.width;
1095 }
1096 if (!symbol.autoHeight) {
1097 scaleY = scaleX;
1098 }
1099 }
1100 if (symbol.autoHeight) {
1101 if (symbol.height !== 0) {
1102 scaleY = rowHeight / symbol.height;
1103 }
1104 if (!symbol.autoWidth) {
1105 scaleX = scaleY;
1106 }
1107 }
1108 //# Scaling of symbol is relative to viewBox, so use that to define
1109 //# width and height attributes:
1110 use.setAttributeNS(SVGNS, 'width', ((ref5 = (ref6 = symbol.viewBox) != null ? ref6[2] : void 0) != null ? ref5 : symbol.width) * scaleX);
1111 use.setAttributeNS(SVGNS, 'height', ((ref7 = (ref8 = symbol.viewBox) != null ? ref8[3] : void 0) != null ? ref7 : symbol.height) * scaleY);
1112 if (symbol.overflowBox != null) {
1113 dx = (symbol.overflowBox[0] - symbol.viewBox[0]) * scaleX;
1114 dy = (symbol.overflowBox[1] - symbol.viewBox[1]) * scaleY;
1115 viewBox[0] = Math.min(viewBox[0], x + dx);
1116 viewBox[1] = Math.min(viewBox[1], y + dy);
1117 viewBox[2] = Math.max(viewBox[2], x + dx + symbol.overflowBox[2] * scaleX);
1118 viewBox[3] = Math.max(viewBox[3], y + dy + symbol.overflowBox[3] * scaleY);
1119 }
1120 x += symbol.width * scaleX;
1121 viewBox[2] = Math.max(viewBox[2], x);
1122 }
1123 y += rowHeight;
1124 viewBox[3] = Math.max(viewBox[3], y);
1125 }
1126 //# Change from x-min, y-min, x-max, y-max to x-min, y-min, width, height
1127 viewBox[2] = viewBox[2] - viewBox[0];
1128 viewBox[3] = viewBox[3] - viewBox[1];
1129 //# Sort by level
1130 levelOrder = ((function() {
1131 var results;
1132 results = [];
1133 for (level in levels) {
1134 results.push(level);
1135 }
1136 return results;
1137 })()).sort(function(x, y) {
1138 return x - y;
1139 });
1140 for (p = 0, len5 = levelOrder.length; p < len5; p++) {
1141 level = levelOrder[p];
1142 ref9 = levels[level];
1143 for (q = 0, len6 = ref9.length; q < len6; q++) {
1144 node = ref9[q];
1145 svg.appendChild(node);
1146 }
1147 }
1148 svg.setAttributeNS(SVGNS, 'viewBox', viewBox.join(' '));
1149 svg.setAttributeNS(SVGNS, 'width', this.width = viewBox[2]);
1150 svg.setAttributeNS(SVGNS, 'height', this.height = viewBox[3]);
1151 svg.setAttributeNS(SVGNS, 'preserveAspectRatio', 'xMinYMin meet');
1152 return doc;
1153 }
1154
1155 renderSVG(mappings) {
1156 var out;
1157 out = new XMLSerializer().serializeToString(this.renderSVGDOM(mappings));
1158 //# Parsing xlink:href in user's SVG fragments, and then serializing,
1159 //# can lead to these null namespace definitions. Remove.
1160 out = out.replace(/\sxmlns:xlink=""/g, '');
1161 out = prettyXML(out, {
1162 newline: '\n' //# force consistent line endings, not require('os').EOL
1163 });
1164 return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
1165<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
1166` + out;
1167 }
1168
1169 renderTeX(filename) {
1170 var anchor, basename, child, content, i, j, k, l, len, len1, len2, lines, m, ref, ref1, ref2, ref3, row, symbol, text, tx, ty, wrap, x, y;
1171 //# Must be called *after* `renderSVG` (or `renderSVGDOM`)
1172 filename = path.parse(filename);
1173 basename = filename.base.slice(0, -filename.ext.length);
1174 //# LaTeX based loosely on Inkscape's PDF/EPS/PS + LaTeX output extension.
1175 //# See http://tug.ctan.org/tex-archive/info/svg-inkscape/
1176 lines = [
1177 `%% Creator: svgtiler ${svgtiler.version}, https://github.com/edemaine/svgtiler
1178%% This LaTeX file includes and overlays text on top of companion file
1179%% ${basename}.pdf/.png
1180%%
1181%% Instead of \\includegraphics, include this figure via
1182%% \\input{${filename.base}}
1183%% You can scale the image by first defining \\svg{width,height,scale}:
1184%% \\def\\svgwidth{\\linewidth} % full width
1185%% or
1186%% \\def\\svgheight{5in}
1187%% or
1188%% \\def\\svgscale{0.5} % 50%
1189%% (If multiple are specified, the first in the list above takes priority.)
1190%%
1191%% If this file resides in another directory from the root .tex file,
1192%% you need to help it find its auxiliary .pdf/.png file via one of the
1193%% following options (any one will do):
1194%% 1. \\usepackage{currfile} so that this file can find its own directory.
1195%% 2. \\usepackage{import} and \\import{path/to/file/}{${filename.base}}
1196%% instead of \\import{${filename.base}}
1197%% 3. \\graphicspath{{path/to/file/}} % note extra braces and trailing slash
1198%%
1199\\begingroup
1200 \\providecommand\\color[2][]{%
1201 \\errmessage{You should load package 'color.sty' to render color in svgtiler text.}%
1202 \\renewcommand\\color[2][]{}%
1203 }%
1204 \\ifx\\currfiledir\\undefined
1205 \\def\\currfiledir{}%
1206 \\fi
1207 \\ifx\\svgwidth\\undefined
1208 \\ifx\\svgheight\\undefined
1209 \\unitlength=0.75bp\\relax % 1px (SVG unit) = 0.75bp (SVG pts)
1210 \\ifx\\svgscale\\undefined\\else
1211 \\ifx\\real\\undefined % in case calc.sty not loaded
1212 \\unitlength=\\svgscale \\unitlength
1213 \\else
1214 \\setlength{\\unitlength}{\\unitlength * \\real{\\svgscale}}%
1215 \\fi
1216 \\fi
1217 \\else
1218 \\unitlength=\\svgheight
1219 \\unitlength=${1 / this.height}\\unitlength % divide by image height
1220 \\fi
1221 \\else
1222 \\unitlength=\\svgwidth
1223 \\unitlength=${1 / this.width}\\unitlength % divide by image width
1224 \\fi
1225 \\def\\clap#1{\\hbox to 0pt{\\hss#1\\hss}}%
1226 \\begin{picture}(${this.width},${this.height})%
1227 \\put(0,0){\\includegraphics[width=${this.width}\\unitlength]{\\currfiledir ${basename}}}%`
1228 ];
1229 ref = this.symbols;
1230 for (i = k = 0, len = ref.length; k < len; i = ++k) {
1231 row = ref[i];
1232 for (j = l = 0, len1 = row.length; l < len1; j = ++l) {
1233 symbol = row[j];
1234 ({x, y} = this.coords[i][j]);
1235 ref1 = symbol.text;
1236 for (m = 0, len2 = ref1.length; m < len2; m++) {
1237 text = ref1[m];
1238 tx = (ref2 = parseNum(text.getAttribute('x'))) != null ? ref2 : 0;
1239 ty = (ref3 = parseNum(text.getAttribute('y'))) != null ? ref3 : 0;
1240 content = ((function() {
1241 var len3, n, ref4, results;
1242 ref4 = text.childNodes;
1243 // TEXT_NODE
1244 results = [];
1245 for (n = 0, len3 = ref4.length; n < len3; n++) {
1246 child = ref4[n];
1247 if (child.nodeType === 3) {
1248 results.push(child.data);
1249 }
1250 }
1251 return results;
1252 })()).join('');
1253 anchor = attributeOrStyle(text, 'text-anchor');
1254 if (/^middle\b/.test(anchor)) {
1255 wrap = '\\clap{';
1256 } else if (/^end\b/.test(anchor)) {
1257 wrap = '\\rlap{'; //if /^start\b/.test anchor # default
1258 } else {
1259 wrap = '\\llap{';
1260 }
1261 // "@height -" is to flip between y down (SVG) and y up (picture)
1262 lines.push(` \\put(${x + tx},${this.height - (y + ty)}){\\color{${attributeOrStyle(text, 'fill') || 'black'}}${wrap}${content}${wrap && '}'}}%`);
1263 }
1264 }
1265 }
1266 lines.push(` \\end{picture}%
1267\\endgroup`, ''); // trailing newline
1268 return lines.join('\n');
1269 }
1270
1271 writeTeX(filename) {
1272 /*
1273 Must be called *after* `writeSVG`.
1274 Default filename is the input filename with extension replaced by .svg_tex
1275 (analogous to .pdf_tex from Inkscape's --export-latex feature, but noting
1276 that the text is extracted from the SVG not the PDF, and that this file
1277 works with both .pdf and .png auxiliary files).
1278 */
1279 if (filename == null) {
1280 filename = path.parse(this.filename);
1281 if (filename.ext === '.svg_tex') {
1282 filename.base += '.svg_tex';
1283 } else {
1284 filename.base = filename.base.slice(0, -filename.ext.length) + '.svg_tex';
1285 }
1286 filename = path.format(filename);
1287 }
1288 console.log(' &', filename);
1289 fs.writeFileSync(filename, this.renderTeX(filename));
1290 return filename;
1291 }
1292
1293};
1294
1295ASCIIDrawing = (function() {
1296 class ASCIIDrawing extends Drawing {
1297 parse(data) {
1298 var line;
1299 return this.load((function() {
1300 var k, len, ref, results;
1301 ref = splitIntoLines(data);
1302 results = [];
1303 for (k = 0, len = ref.length; k < len; k++) {
1304 line = ref[k];
1305 results.push(graphemeSplitter.splitGraphemes(line));
1306 }
1307 return results;
1308 })());
1309 }
1310
1311 };
1312
1313 ASCIIDrawing.title = "ASCII drawing (one character per symbol)";
1314
1315 return ASCIIDrawing;
1316
1317}).call(this);
1318
1319DSVDrawing = class DSVDrawing extends Drawing {
1320 parse(data) {
1321 var ref;
1322 //# Remove trailing newline / final blank line.
1323 if (data.slice(-2) === '\r\n') {
1324 data = data.slice(0, -2);
1325 } else if ((ref = data.slice(-1)) === '\r' || ref === '\n') {
1326 data = data.slice(0, -1);
1327 }
1328 //# CSV parser.
1329 return this.load(require('csv-parse/lib/sync')(data, {
1330 delimiter: this.constructor.delimiter,
1331 relax_column_count: true
1332 }));
1333 }
1334
1335};
1336
1337SSVDrawing = (function() {
1338 class SSVDrawing extends DSVDrawing {
1339 parse(data) {
1340 //# Coallesce non-newline whitespace into single space
1341 return super.parse(data.replace(/[ \t\f\v]+/g, ' '));
1342 }
1343
1344 };
1345
1346 SSVDrawing.title = "Space-delimiter drawing (one word per symbol)";
1347
1348 SSVDrawing.delimiter = ' ';
1349
1350 return SSVDrawing;
1351
1352}).call(this);
1353
1354CSVDrawing = (function() {
1355 class CSVDrawing extends DSVDrawing {};
1356
1357 CSVDrawing.title = "Comma-separated drawing (spreadsheet export)";
1358
1359 CSVDrawing.delimiter = ',';
1360
1361 return CSVDrawing;
1362
1363}).call(this);
1364
1365TSVDrawing = (function() {
1366 class TSVDrawing extends DSVDrawing {};
1367
1368 TSVDrawing.title = "Tab-separated drawing (spreadsheet export)";
1369
1370 TSVDrawing.delimiter = '\t';
1371
1372 return TSVDrawing;
1373
1374}).call(this);
1375
1376Drawings = (function() {
1377 class Drawings extends Input {
1378 load(datas) {
1379 var data, drawing;
1380 return this.drawings = (function() {
1381 var k, len, results;
1382 results = [];
1383 for (k = 0, len = datas.length; k < len; k++) {
1384 data = datas[k];
1385 drawing = new Drawing();
1386 drawing.filename = this.filename;
1387 drawing.subname = data.subname;
1388 drawing.load(data);
1389 results.push(drawing);
1390 }
1391 return results;
1392 }).call(this);
1393 }
1394
1395 subfilename(extension, drawing) {
1396 var filename2;
1397 if (this.drawings.length > 1) {
1398 filename2 = path.parse(typeof filename !== "undefined" && filename !== null ? filename : this.filename);
1399 filename2.base = filename2.base.slice(0, -filename2.ext.length);
1400 filename2.base += this.constructor.filenameSeparator + drawing.subname;
1401 filename2.base += extension;
1402 return path.format(filename2);
1403 } else {
1404 if (drawing != null) {
1405 drawing.filename = this.filename; //# use Drawing default if not filename?
1406 }
1407 return filename;
1408 }
1409 }
1410
1411 writeSVG(mappings, filename) {
1412 var drawing, k, len, ref, results;
1413 ref = this.drawings;
1414 results = [];
1415 for (k = 0, len = ref.length; k < len; k++) {
1416 drawing = ref[k];
1417 results.push(drawing.writeSVG(mappings, this.subfilename('.svg', drawing)));
1418 }
1419 return results;
1420 }
1421
1422 writeTeX(filename) {
1423 var drawing, k, len, ref, results;
1424 ref = this.drawings;
1425 results = [];
1426 for (k = 0, len = ref.length; k < len; k++) {
1427 drawing = ref[k];
1428 results.push(drawing.writeTeX(this.subfilename('.svg_tex', drawing)));
1429 }
1430 return results;
1431 }
1432
1433 };
1434
1435 Drawings.filenameSeparator = '_';
1436
1437 return Drawings;
1438
1439}).call(this);
1440
1441XLSXDrawings = (function() {
1442 class XLSXDrawings extends Drawings {
1443 parse(data) {
1444 var rows, sheet, sheetInfo, subname, workbook, xlsx;
1445 xlsx = require('xlsx');
1446 workbook = xlsx.read(data, {
1447 type: 'binary'
1448 });
1449 //# https://www.npmjs.com/package/xlsx#common-spreadsheet-format
1450 return this.load((function() {
1451 var k, len, ref, results;
1452 ref = workbook.Workbook.Sheets;
1453 results = [];
1454 for (k = 0, len = ref.length; k < len; k++) {
1455 sheetInfo = ref[k];
1456 subname = sheetInfo.name;
1457 sheet = workbook.Sheets[subname];
1458 //# 0 = Visible, 1 = Hidden, 2 = Very Hidden
1459 //# https://sheetjs.gitbooks.io/docs/#sheet-visibility
1460 if (sheetInfo.Hidden && !Drawings.keepHidden) {
1461 continue;
1462 }
1463 if (subname.length === 31) {
1464 console.warn(`Warning: Sheet '${subname}' has length exactly 31, which may be caused by Google Sheets export truncation`);
1465 }
1466 rows = xlsx.utils.sheet_to_json(sheet, {
1467 header: 1,
1468 defval: ''
1469 });
1470 rows.subname = subname;
1471 results.push(rows);
1472 }
1473 return results;
1474 })());
1475 }
1476
1477 };
1478
1479 XLSXDrawings.encoding = 'binary';
1480
1481 XLSXDrawings.title = "Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)";
1482
1483 return XLSXDrawings;
1484
1485}).call(this);
1486
1487Context = class Context {
1488 constructor(drawing1, i1, j1) {
1489 var ref, ref1;
1490 this.drawing = drawing1;
1491 this.i = i1;
1492 this.j = j1;
1493 this.symbols = this.drawing.symbols;
1494 this.filename = this.drawing.filename;
1495 this.subname = this.drawing.subname;
1496 this.symbol = (ref = this.symbols[this.i]) != null ? ref[this.j] : void 0;
1497 this.key = (ref1 = this.symbol) != null ? ref1.key : void 0;
1498 }
1499
1500 neighbor(dj, di) {
1501 return new Context(this.drawing, this.i + di, this.j + dj);
1502 }
1503
1504 includes(...args) {
1505 return (this.symbol != null) && this.symbol.includes(...args);
1506 }
1507
1508 row(di = 0) {
1509 var i, j, k, len, ref, ref1, results, symbol;
1510 i = this.i + di;
1511 ref1 = (ref = this.symbols[i]) != null ? ref : [];
1512 results = [];
1513 for (j = k = 0, len = ref1.length; k < len; j = ++k) {
1514 symbol = ref1[j];
1515 results.push(new Context(this.drawing, i, j));
1516 }
1517 return results;
1518 }
1519
1520 column(dj = 0) {
1521 var i, j, k, len, ref, results, row;
1522 j = this.j + dj;
1523 ref = this.symbols;
1524 results = [];
1525 for (i = k = 0, len = ref.length; k < len; i = ++k) {
1526 row = ref[i];
1527 results.push(new Context(this.drawing, i, j));
1528 }
1529 return results;
1530 }
1531
1532};
1533
1534extensionMap = {
1535 '.txt': ASCIIMapping,
1536 '.js': JSMapping,
1537 '.jsx': JSMapping,
1538 '.coffee': CoffeeMapping,
1539 '.cjsx': CoffeeMapping,
1540 '.asc': ASCIIDrawing,
1541 '.ssv': SSVDrawing,
1542 '.csv': CSVDrawing,
1543 '.tsv': TSVDrawing,
1544 //# Parsable by xlsx package:
1545 '.xlsx': XLSXDrawings, //# Excel 2007+ XML Format
1546 '.xlsm': XLSXDrawings, //# Excel 2007+ Macro XML Format
1547 '.xlsb': XLSXDrawings, //# Excel 2007+ Binary Format
1548 '.xls': XLSXDrawings, //# Excel 2.0 or 2003-2004 (SpreadsheetML)
1549 '.ods': XLSXDrawings, //# OpenDocument Spreadsheet
1550 '.fods': XLSXDrawings, //# Flat OpenDocument Spreadsheet
1551 '.dif': XLSXDrawings, //# Data Interchange Format (DIF)
1552 '.prn': XLSXDrawings, //# Lotus Formatted Text
1553 '.dbf': XLSXDrawings //# dBASE II/III/IV / Visual FoxPro
1554};
1555
1556sanitize = true;
1557
1558bufferSize = 16 * 1024;
1559
1560postprocess = function(format, filename) {
1561 var buffer, e, file, fileSize, match, position, readSize, string;
1562 if (!sanitize) {
1563 return;
1564 }
1565 try {
1566 switch (format) {
1567 case 'pdf':
1568 //# Blank out /CreationDate in PDF for easier version control.
1569 //# Replace these commands with spaces to avoid in-file pointer errors.
1570 buffer = Buffer.alloc(bufferSize);
1571 fileSize = fs.statSync(filename).size;
1572 position = Math.max(0, fileSize - bufferSize);
1573 file = fs.openSync(filename, 'r+');
1574 readSize = fs.readSync(file, buffer, 0, bufferSize, position);
1575 string = buffer.toString('binary'); //# must use single-byte encoding!
1576 match = /\/CreationDate\s*\((?:[^()\\]|\\[^])*\)/.exec(string);
1577 if (match != null) {
1578 fs.writeSync(file, ' '.repeat(match[0].length), position + match.index);
1579 }
1580 return fs.closeSync(file);
1581 }
1582 } catch (error1) {
1583 e = error1;
1584 return console.log(`Failed to postprocess '${filename}': ${e}`);
1585 }
1586};
1587
1588convertSVG = function(format, svg, sync) {
1589 var args, child_process, filename, output, preprocess, result;
1590 child_process = require('child_process');
1591 filename = path.parse(svg);
1592 if (filename.ext === `.${format}`) {
1593 filename.base += `.${format}`;
1594 } else {
1595 filename.base = `${filename.base.slice(0, -filename.ext.length)}.${format}`;
1596 }
1597 output = path.format(filename);
1598 //# Workaround relative paths not working in MacOS distribution of Inkscape
1599 //# [https://bugs.launchpad.net/inkscape/+bug/181639]
1600 if (process.platform === 'darwin') {
1601 preprocess = path.resolve;
1602 } else {
1603 preprocess = function(x) {
1604 return x;
1605 };
1606 }
1607 args = ['-z', `--file=${preprocess(svg)}`, `--export-${format}=${preprocess(output)}`];
1608 if (sync) {
1609 //# In sychronous mode, we let inkscape directly output its error messages,
1610 //# and add warnings about any failures that occur.
1611 console.log('=>', output);
1612 result = child_process.spawnSync('inkscape', args, {
1613 stdio: 'inherit'
1614 });
1615 if (result.error) {
1616 return console.log(result.error.message);
1617 } else if (result.status || result.signal) {
1618 return console.log(`:-( ${output} FAILED`);
1619 } else {
1620 return postprocess(format, output);
1621 }
1622 } else {
1623 //# In asychronous mode, we capture inkscape's outputs, and print them only
1624 //# when the process has finished, along with which file failed, to avoid
1625 //# mixing up messages from parallel executions.
1626 return function(resolve) {
1627 var inkscape, out;
1628 console.log('=>', output);
1629 inkscape = require('child_process').spawn('inkscape', args);
1630 out = '';
1631 inkscape.stdout.on('data', function(buf) {
1632 return out += buf;
1633 });
1634 inkscape.stderr.on('data', function(buf) {
1635 return out += buf;
1636 });
1637 inkscape.on('error', function(error) {
1638 return console.log(error.message);
1639 });
1640 return inkscape.on('exit', function(status, signal) {
1641 if (status || signal) {
1642 console.log(`:-( ${output} FAILED:`);
1643 console.log(out);
1644 } else {
1645 postprocess(format, output);
1646 }
1647 return resolve();
1648 });
1649 };
1650 }
1651};
1652
1653help = function() {
1654 var extension, klass, ref;
1655 console.log(`svgtiler ${(ref = svgtiler.version) != null ? ref : "(web)"}
1656Usage: ${process.argv[1]} (...options and filenames...)
1657Documentation: https://github.com/edemaine/svgtiler
1658
1659Optional arguments:
1660 --help Show this help message and exit.
1661 -m / --margin Don't delete blank extreme rows/columns
1662 --hidden Process hidden sheets within spreadsheet files
1663 --tw TILE_WIDTH / --tile-width TILE_WIDTH
1664 Force all symbol tiles to have specified width
1665 --th TILE_HEIGHT / --tile-height TILE_HEIGHT
1666 Force all symbol tiles to have specified height
1667 -p / --pdf Convert output SVG files to PDF via Inkscape
1668 -P / --png Convert output SVG files to PNG via Inkscape
1669 -t / --tex Move <text> from SVG to accompanying LaTeX file.tex
1670 --no-sanitize Don't sanitize PDF output by blanking out /CreationDate
1671 -j N / --jobs N Run up to N Inkscape jobs in parallel
1672
1673Filename arguments: (mappings before drawings!)
1674`);
1675 for (extension in extensionMap) {
1676 klass = extensionMap[extension];
1677 if (extension.length < 10) {
1678 extension += ' '.repeat(10 - extension.length);
1679 }
1680 console.log(` *${extension} ${klass.title}`);
1681 if (klass.help != null) {
1682 console.log(` ${klass.help}`);
1683 }
1684 }
1685 console.log(`
1686SYMBOL specifiers: (omit the quotes in anything except .js and .coffee files)
1687
1688 'filename.svg': load SVG from specified file
1689 'filename.png': include PNG image from specified file
1690 'filename.jpg': include JPEG image from specified file
1691 '<svg>...</svg>': raw SVG
1692 -> ...@key...: function computing SVG, with \`this\` bound to Context with
1693 \`key\` (symbol name), \`i\` and \`j\` (y and x coordinates),
1694 \`filename\` (drawing filename), \`subname\` (subsheet name),
1695 and supporting \`neighbor\`/\`includes\`/\`row\`/\`column\` methods`);
1696 //object with one or more attributes
1697 return process.exit();
1698};
1699
1700main = function() {
1701 var arg, args, filename, filenames, files, format, formats, i, input, jobs, k, l, len, len1, len2, m, mappings, skip, sync;
1702 mappings = new Mappings();
1703 args = process.argv.slice(2);
1704 files = skip = 0;
1705 formats = [];
1706 jobs = [];
1707 sync = true;
1708 for (i = k = 0, len = args.length; k < len; i = ++k) {
1709 arg = args[i];
1710 if (skip) {
1711 skip--;
1712 continue;
1713 }
1714 switch (arg) {
1715 case '-h':
1716 case '--help':
1717 help();
1718 break;
1719 case '-m':
1720 case '--margin':
1721 Drawing.keepMargins = true;
1722 break;
1723 case '--hidden':
1724 Drawings.keepHidden = true;
1725 break;
1726 case '--tw':
1727 case '--tile-width':
1728 skip = 1;
1729 arg = parseFloat(args[i + 1]);
1730 if (arg) {
1731 Symbol.forceWidth = arg;
1732 } else {
1733 console.warn(`Invalid argument to --tile-width: ${args[i + 1]}`);
1734 }
1735 break;
1736 case '--th':
1737 case '--tile-height':
1738 skip = 1;
1739 arg = parseFloat(args[i + 1]);
1740 if (arg) {
1741 Symbol.forceHeight = arg;
1742 } else {
1743 console.warn(`Invalid argument to --tile-height: ${args[i + 1]}`);
1744 }
1745 break;
1746 case '-p':
1747 case '--pdf':
1748 formats.push('pdf');
1749 break;
1750 case '-P':
1751 case '--png':
1752 formats.push('png');
1753 break;
1754 case '-t':
1755 case '--tex':
1756 Symbol.texText = true;
1757 break;
1758 case '--no-sanitize':
1759 sanitize = false;
1760 break;
1761 case '-j':
1762 case '--jobs':
1763 skip = 1;
1764 arg = parseInt(args[i + 1]);
1765 if (arg) {
1766 jobs = new require('async-limiter')({
1767 concurrency: arg
1768 });
1769 sync = false;
1770 } else {
1771 console.warn(`Invalid argument to --jobs: ${args[i + 1]}`);
1772 }
1773 break;
1774 default:
1775 files++;
1776 console.log('*', arg);
1777 input = Input.recognize(arg);
1778 if (input instanceof Mapping) {
1779 mappings.push(input);
1780 } else if (input instanceof Drawing || input instanceof Drawings) {
1781 filenames = input.writeSVG(mappings);
1782 if (Symbol.texText) {
1783 input.writeTeX(mappings);
1784 }
1785 for (l = 0, len1 = formats.length; l < len1; l++) {
1786 format = formats[l];
1787 if (typeof filenames === 'string') {
1788 jobs.push(convertSVG(format, filenames, sync));
1789 } else {
1790 for (m = 0, len2 = filenames.length; m < len2; m++) {
1791 filename = filenames[m];
1792 jobs.push(convertSVG(format, filename, sync));
1793 }
1794 }
1795 }
1796 }
1797 }
1798 }
1799 if (!files) {
1800 console.log('Not enough filename arguments');
1801 return help();
1802 }
1803};
1804
1805exports = {Symbol, StaticSymbol, DynamicSymbol, unrecognizedSymbol, Mapping, ASCIIMapping, JSMapping, CoffeeMapping, Drawing, ASCIIDrawing, DSVDrawing, SSVDrawing, CSVDrawing, TSVDrawing, Drawings, XLSXDrawings, Input, Mappings, Context, SVGTilerException, SVGNS, XLINKNS, main};
1806
1807if (typeof module !== "undefined" && module !== null) {
1808 if (module.exports == null) {
1809 module.exports = exports;
1810 }
1811}
1812
1813if (typeof window !== "undefined" && window !== null) {
1814 if (window.svgtiler == null) {
1815 window.svgtiler = exports;
1816 }
1817}
1818
1819if (typeof window === "undefined" || window === null) {
1820 main();
1821}
1822
1823}).call(this);