pax_global_header00006660000000000000000000000064141667026420014522gustar00rootroot0000000000000052 comment=dcf222c3d2c9b6de89f7fb507caaa0d3207b4fbb aasvg-0.1.8/000077500000000000000000000000001416670264200126315ustar00rootroot00000000000000aasvg-0.1.8/.gitignore000066400000000000000000000000111416670264200146110ustar00rootroot00000000000000*~ *.swp aasvg-0.1.8/LICENSE000066400000000000000000000024301416670264200136350ustar00rootroot00000000000000Copyright 2021, Martin Thomson Copyright 2015-2021, Morgan McGuire Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. aasvg-0.1.8/README.md000066400000000000000000000035141416670264200141130ustar00rootroot00000000000000# aasvg Convert ASCII art diagrams into SVG. This is inspired by [goat](https://github.com/blampe/goat) but rather than a reimplementation, this code uses the original [markdeep](https://casual-effects.com/markdeep/) code. ## Usage Install with `npm install -g aasvg`. Feed `aasvg` an image and it will write an SVG. For example: ``` $ aasvg < example.txt > example.svg ``` ![example](./example.svg) ## Character Placement By default, this does not place text characters on a grid one-by-one as the original markdeep code did. The `--spaces` command-line argument controls how text is combined. Use either `--spaces=0` or `--stretch` to provide precise text placement. `--spaces=0` ensures that every character is placed separately; which is precise and avoids text distortion, but makes for a larger SVG that is harder to search. `--stretch` can be used with `--spaces` set to any value. `--stretch` stretches text to fit, which is less widely implemented in viewers (generally you don't have to worry about this unless you are using an [insane profile](https://datatracker.ietf.org/doc/html/rfc7996)) and might distort the text a tiny bit because the metrics for the font used (the generic "monospace") cannot be exactly controlled. ## Dark Backgrounds This tool draws black lines and text on a transparent background. In certain settings (such as GitHub when a dark mode is enabled), this produces unfortunate results. If you inline the resulting SVG, you can perform [tricks with CSS](https://github.com/martinthomson/i-d-template/blob/3c960b652a0708a291c01f186511fd0b39eeb8b4/v3.css#L982-L993), but if you are using ``, that isn't possible. Use the `--backdrop` switch to generate a mostly-white backdrop for the image. aasvg-0.1.8/example.svg000066400000000000000000000350141416670264200150100ustar00rootroot00000000000000 A Box Round Mixed Rounded Diagonals & Square Corners Search Interior Diag line if (a > b) Curved line obj->fcn() Done? Join Curved Vertical 3 not:line 'quotes' A || B *bold* Not a dot A dash--is not a line Nor/is this. aasvg-0.1.8/example.txt000066400000000000000000000040361416670264200150300ustar00rootroot00000000000000+-------------------+ ^ .---. | A Box |__.--.__ __.--> | .-. | | | | '--' v | * |<--- | | +-------------------+ '-' | | Round *---(-. | .-----------------. .-------. .----------. .-------. | | | | Mixed Rounded | | | / Diagonals \ | | | | | | | & Square Corners | '--. .--' / \ |---+---| '-)-' .--------. '--+------------+-' .--. | '-------+--------' | | | | / Search / | | | | '---. | '-------' | '-+------' |<---------->| | | | v Interior | ^ ' <---' '----' .-----------. ---. .--- v | .------------------. Diag line | .-------. +---. \ / . | | if (a > b) +---. .--->| | | | | Curved line \ / / \ | | obj->fcn() | \ / | '-------' |<--' + / \ | '------------------' '--' '--+--------' .--. .--. | .-. +Done?+-' .---+-----. | ^ |\ | | /| .--+ | | \ / | | | Join \|/ | | Curved | \| |/ | | \ | \ / | | +----> ◌ --◍-- '-' Vertical '--' '--' '-- '--' + .---. <--+---+-----' | /|\ | | 3 | v not:line 'quotes' .-' '---' .-. .---+--------. / A || B *bold* | ^ | | | Not a dot | <---+---<-- A dash--is not a line v | '-' '---------+--' / Nor/is this. --- aasvg-0.1.8/main.js000077500000000000000000000050631416670264200141220ustar00rootroot00000000000000#!/usr/bin/env node const { diagramToSVG } = require('./markdeep-diagram.js'); const VERSION = "aasvg 0.1.7"; function usage() { console.warn("Turn ASCII art into SVG"); console.warn(); console.warn("Usage: aasvg [options] < > "); console.warn(); console.warn(" --disable-text Disable simple text"); console.warn(" --grid Draw a grid (debugging)"); console.warn(" --spaces= Split text after spaces"); console.warn(" (0 means place every character separately)"); console.warn(" --stretch Stretch text to better fit it") console.warn(" (use with --spaces > 0; uses advanced SVG)"); console.warn(" --backdrop Draw a backdrop"); console.warn(" --source Draw an overlay with source text"); console.warn(" --= Set SVG attribute to "); console.warn(" --version Show the version and exit"); process.exit(2); } async function read() { let input = ''; process.stdin.setEncoding('utf8'); process.stdin.on('readable', () => { let chunk; // Use a loop to make sure we read all available data. while ((chunk = process.stdin.read()) !== null) { input += chunk.replace(/\r/g, ''); } }); return await new Promise((resolve, reject) => { process.stdin.on('end', () => { resolve(input); }); process.stdin.on('error', e => { reject(e); }); }); } (async function main() { let options = { style: {} }; process.argv.slice(2).forEach(a => { if (a === '--disable-text') { options.disableText = true; } else if (a === '--grid') { options.grid = true; } else if (a === '--stretch') { options.stretch = true; } else if (a === '--backdrop') { options.backdrop = true; } else if (a === '--source') { options.source = true; } else if (a === "--version") { console.log(VERSION); process.exit(); } else if (a.startsWith("--spaces=")) { options.spaces = parseInt(a.substring(9), 10); } else { let s = a.substring(2).split('='); if (a.substring(0, 2) === '--' && s.length === 2) { options.style[s[0]] = s[1]; } else { usage(); } } }) const txt = await read(); const svg = diagramToSVG(txt, options); console.log(svg); })(); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aasvg-0.1.8/markdeep-diagram.js���������������������������������������������������������������������0000664�0000000�0000000�00000167330�14166702642�0016373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** Markdeep diagrams; extracted from Markdeep.js Version 1.13 Refined for use with nodejs Copyright 2015-2021, Morgan McGuire, https://casual-effects.com All rights reserved. ------------------------------------------------------------- You may use, extend, and redistribute this code under the terms of the BSD license at https://opensource.org/licenses/BSD-2-Clause. */ 'use strict'; // Mappings and constants used by markdeep. const STROKE_WIDTH = 2; const ARROW_COLOR = ' fill="black"'; // + ' stroke="none"', but xml2rfc doesn't like that. const STROKE_COLOR = ' fill="none" stroke="black"'; const TEXT_COLOR = ' stroke="black"'; ['min', 'max', 'abs', 'sign'].forEach(f => { global[f] = Math[f]; }); String.prototype.ss = String.prototype.substring; String.prototype.rp = String.prototype.replace; function escapeHTMLEntities(str) { return String(str).rp(/&/g, '&').rp(//g, '>').rp(/"/g, '"'); } function strToArray(s) { return Array.from(s); } /** Adds whitespace at the end of each line of str, so that all lines have equal length in unicode characters (which is not the same as JavaScript characters when high-index/escape characters are present). */ function equalizeLineLengths(str) { var lineArray = str.split('\n'); if ((lineArray.length > 0) && (lineArray[lineArray.length - 1] === '')) { // Remove the empty last line generated by split on a trailing newline lineArray.pop(); } var longest = 0; lineArray.forEach(function (line) { longest = max(longest, strToArray(line).length); }); // Worst case spaces needed for equalizing lengths // http://stackoverflow.com/questions/1877475/repeat-character-n-times var spaces = Array(longest + 1).join(' '); var result = ''; lineArray.forEach(function (line) { // Append the needed number of spaces onto each line, and // reconstruct the output with newlines result += line + spaces.ss(strToArray(line).length) + '\n'; }); return result; } /** Finds the longest common whitespace prefix of all non-empty lines and then removes it */ function removeLeadingSpace(str) { var lineArray = str.split('\n'); var minimum = Infinity; lineArray.forEach(function (line) { if (line.trim() !== '') { // This is a non-empty line var spaceArray = line.match(/^([ \t]*)/); if (spaceArray) { minimum = min(minimum, spaceArray[0].length); } } }); if (minimum === 0) { // No leading space return str; } var result = ''; lineArray.forEach(function (line) { // Strip the common spaces result += line.ss(minimum) + '\n'; }); return result; } /** Returns true if this character is a "letter" under the ASCII definition */ function isASCIILetter(c) { var code = c.charCodeAt(0); return ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122)); } const MARKERS = { 'o': '\ue004', 'v': '\ue005', 'V': '\ue006' }; function hideChar(s, i) { let r = new Array(3); r.fill('[a-zA-Z' + Object.values(MARKERS).join('') + ']'); r[i] = '[' + Object.keys(MARKERS).join('') + ']'; return s.replace(new RegExp(r.join(''), 'g'), v => v.substring(0, i) + MARKERS[v.charAt(i)] + v.substring(i + 1)); } function unhideMarkers(s) { Object.keys(MARKERS).forEach(k => { s = s.rp(new RegExp(MARKERS[k], 'g'), k); }); return s; } function hideMarkers(s) { s = hideChar(s, 0); s = hideChar(s, 1); s = hideChar(s, 2); // Unhide strings that only contain 'o' or 'v'. // Note: Using \B as \ue00? is a non-word character. const allHidden = '\\B[' + Object.values(MARKERS).join('') + ']{3,}\\B'; return s.replace(new RegExp(allHidden, 'g'), unhideMarkers); } /** Converts diagramString, which is a Markdeep diagram without the surrounding asterisks, to SVG (HTML). Lines may have ragged lengths. `options` is a dictionary with the following keys: backdrop (default: false) will add a white element as a backdrop so that the background of the image is not transparent disableText (default: false) will disable passing text showGrid (default: false) will display a debug grid spaces (default: 2) the number of spaces between different strings style (default: {}) a dictionary of attributes to attach to the element */ function diagramToSVG(diagramString, options) { // Clean up diagramString diagramString = equalizeLineLengths(removeLeadingSpace(diagramString)); options = options || {}; if (!Number.isInteger(options.spaces)) { options.spaces = 2; } // 0 is valid so falsy tests fail. // Temporarily replace 'o', 'v', and 'V' if they are surrounded by other // text. Use another character to avoid processing them as decorations. // These will be swapped back in the final SVG. diagramString = hideMarkers(diagramString); /** Pixels per character */ var SCALE = 8; /** Multiply Y coordinates by this when generating the final SVG result to account for the aspect ratio of text files. */ var ASPECT = 2; var DIAGONAL_ANGLE = Math.atan(1.0 / ASPECT) * 180 / Math.PI; var EPSILON = 1e-6; // The order of the following is based on rotation angles // and is used for ArrowSet.toSVG var ARROW_HEAD_CHARACTERS = '>v<^'; var POINT_CHARACTERS = 'o*◌○◍●'; var JUMP_CHARACTERS = '()'; var UNDIRECTED_VERTEX_CHARACTERS = "+"; var VERTEX_CHARACTERS = UNDIRECTED_VERTEX_CHARACTERS + ".',`"; // GRAY[i] is the Unicode block character for (i+1)/4 level gray var GRAY_CHARACTERS = '\u2591\u2592\u2593\u2588'; // TRI[i] is a right-triangle rotated by 90*i var TRI_CHARACTERS = '\u25E2\u25E3\u25E4\u25E5'; var DECORATION_CHARACTERS = ARROW_HEAD_CHARACTERS + POINT_CHARACTERS + JUMP_CHARACTERS + GRAY_CHARACTERS + TRI_CHARACTERS; function isUndirectedVertex(c) { return UNDIRECTED_VERTEX_CHARACTERS.indexOf(c) + 1; } function isVertex(c) { return VERTEX_CHARACTERS.indexOf(c) !== -1; } function isTopVertex(c) { return isUndirectedVertex(c) || (c === '.') || (c === ','); } function isBottomVertex(c) { return isUndirectedVertex(c) || (c === "'") || (c === '`'); } function isVertexOrLeftDecoration(c) { return isVertex(c) || (c === '<') || isPoint(c); } function isVertexOrRightDecoration(c) { return isVertex(c) || (c === '>') || isPoint(c); } function isGray(c) { return GRAY_CHARACTERS.indexOf(c) + 1; } function isTri(c) { return TRI_CHARACTERS.indexOf(c) + 1; } // "D" = Diagonal slash (/), "B" = diagonal Backslash (\) // Characters that may appear anywhere on a solid line function isSolidHLine(c) { return (c === '-') || isUndirectedVertex(c) || isJump(c); } function isSolidVLineOrJumpOrPoint(c) { return isSolidVLine(c) || isJump(c) || isPoint(c); } function isSolidVLine(c) { return (c === '|') || isUndirectedVertex(c); } function isSolidDLine(c) { return (c === '/') || isUndirectedVertex(c) } function isSolidBLine(c) { return (c === '\\') || isUndirectedVertex(c); } function isJump(c) { return JUMP_CHARACTERS.indexOf(c) + 1; } function isPoint(c) { return POINT_CHARACTERS.indexOf(c) + 1; } function isDecoration(c) { return DECORATION_CHARACTERS.indexOf(c) + 1; } /////////////////////////////////////////////////////////////////////////////// // Math library /** Invoke as new Vec2(v) to clone or new Vec2(x, y) to create from coordinates. Can also invoke without new for brevity. */ function Vec2(x, y) { // Detect when being run without new if (!(this instanceof Vec2)) { return new Vec2(x, y); } if (y === undefined) { if (x === undefined) { x = y = 0; } else if (x instanceof Vec2) { y = x.y; x = x.x; } else { throw new Error("Vec2 requires one Vec2 or (x, y) as an argument"); } } this.x = x; this.y = y; Object.seal(this); } /** Returns coordinates */ Vec2.prototype.coords = function () { function s(x) { return x.toFixed(5).replace(/\.?0*$/, ''); } return s((this.x + 1) * SCALE) + ',' + s((this.y + 1) * SCALE * ASPECT); } /** Returns an SVG representation, with a trailing space */ Vec2.prototype.toString = Vec2.prototype.toSVG = function () { return this.coords() + ' '; }; /** Converts a "rectangular" string defined by newlines into 2D array of characters. Grids are immutable. */ function makeGrid(str) { /** Returns ' ' for out of bounds values */ var grid = function (x, y) { if (y === undefined) { if (x instanceof Vec2) { y = x.y; x = x.x; } else { throw new Error('grid requires either a Vec2 or (x, y)'); } } return ((x >= 0) && (x < grid.width) && (y >= 0) && (y < grid.height)) ? str[y * (grid.width + 1) + x] : ' '; }; // Elements are true when consumed grid._used = []; grid.height = str.split('\n').length; if (str[str.length - 1] === '\n') { --grid.height; } // Convert the string to an array to better handle greater-than 16-bit unicode // characters, which JavaScript does not process correctly with indices. Do this after // the above string processing. str = strToArray(str); grid.width = str.indexOf('\n'); grid.v = function (x, y) { return ((x >= 0) && (x < grid.width) && (y >= 0) && (y < grid.height)) ? str[y * (grid.width + 1) + x] : ' '; }; /** Mark this location. Takes a Vec2 or (x, y) */ grid.setUsed = function (x, y) { if (y === undefined) { if (x instanceof Vec2) { y = x.y; x = x.x; } else { throw new Error('grid requires either a Vec2 or (x, y)'); } } if ((x >= 0) && (x < grid.width) && (y >= 0) && (y < grid.height)) { // Match the source string indexing grid._used[y * (grid.width + 1) + x] = true; } }; grid.isUsed = function (x, y) { if (y === undefined) { if (x instanceof Vec2) { y = x.y; x = x.x; } else { throw new Error('grid requires either a Vec2 or (x, y)'); } } return (this._used[y * (this.width + 1) + x] === true); }; /** Returns the x offset of the next run of text on the line; returns this.width if there is no text. */ grid.textStart = function (x, y) { for (; x < this.width; ++x) { if (this.isUsed(x, y)) { continue; } if (this(x, y) !== ' ') { break; } } return x; } /** Returns the text at the given location, marking these locations as used. */ grid.text = function (x, y) { let spaces = 0; let end = x; for (; end < this.width; ++end) { if (this.isUsed(end, y)) { break; } if (this(end, y) === ' ') { spaces++; } else { spaces = 0; } if (spaces >= options.spaces) { end++; break; } } end -= spaces; for (let i = x; i < end; ++i) { this._used[y * (this.width + 1) + i] = true; } return str.slice(y * (this.width + 1) + x, y * (this.width + 1) + end); } /** Returns true if there is a solid vertical line passing through (x, y) */ grid.isSolidVLineAt = function (x, y) { if (y === undefined) { y = x.x; x = x.x; } var up = grid(x, y - 1); var c = grid(x, y); var dn = grid(x, y + 1); var uprt = grid(x + 1, y - 1); var uplt = grid(x - 1, y - 1); if (isSolidVLine(c)) { // Looks like a vertical line...does it continue? return (isTopVertex(up) || (up === '^') || isSolidVLine(up) || isJump(up) || isBottomVertex(dn) || (dn === 'v') || isSolidVLine(dn) || isJump(dn) || isPoint(up) || isPoint(dn) || (grid(x, y - 1) === '_') || (uplt === '_') || (uprt === '_') || // Special case of 1-high vertical on two curved corners ((isTopVertex(uplt) || isTopVertex(uprt)) && (isBottomVertex(grid(x - 1, y + 1)) || isBottomVertex(grid(x + 1, y + 1))))); } else if (isTopVertex(c) || (c === '^')) { // May be the top of a vertical line return isSolidVLine(dn) || (isJump(dn) && (c !== '.')); } else if (isBottomVertex(c) || (c === 'v' || c === 'V')) { return isSolidVLine(up) || (isJump(up) && (c !== "'")); } else if (isPoint(c)) { return isSolidVLine(up) || isSolidVLine(dn); } return false; }; /** Returns true if there is a solid middle (---) horizontal line passing through (x, y). Ignores underscores. */ grid.isSolidHLineAt = function (x, y) { if (y === undefined) { y = x.x; x = x.x; } var ltlt = grid(x - 2, y); var lt = grid(x - 1, y); var c = grid(x + 0, y); var rt = grid(x + 1, y); var rtrt = grid(x + 2, y); if (isSolidHLine(c) || (isSolidHLine(lt) && isJump(c))) { // Looks like a horizontal line...does it continue? We need three in a row. if (isSolidHLine(lt)) { return isSolidHLine(rt) || isVertexOrRightDecoration(rt) || isSolidHLine(ltlt) || isVertexOrLeftDecoration(ltlt); } else if (isVertexOrLeftDecoration(lt)) { return isSolidHLine(rt); } else { return isSolidHLine(rt) && (isSolidHLine(rtrt) || isVertexOrRightDecoration(rtrt)); } } else if (c === '<') { return isSolidHLine(rt) && isSolidHLine(rtrt) } else if (c === '>') { return isSolidHLine(lt) && isSolidHLine(ltlt); } else if (isVertex(c)) { return ((isSolidHLine(lt) && isSolidHLine(ltlt)) || (isSolidHLine(rt) && isSolidHLine(rtrt))); } return false; }; /** Returns true if there is a solid backslash line passing through (x, y) */ grid.isSolidBLineAt = function (x, y) { if (y === undefined) { y = x.x; x = x.x; } var c = grid(x, y); var lt = grid(x - 1, y - 1); var rt = grid(x + 1, y + 1); if (c === '\\') { // Looks like a diagonal line...does it continue? We need two in a row. return (isSolidBLine(rt) || isBottomVertex(rt) || isPoint(rt) || (rt === 'v' || rt === 'V') || isSolidBLine(lt) || isTopVertex(lt) || isPoint(lt) || (lt === '^') || (grid(x, y - 1) === '/') || (grid(x, y + 1) === '/') || (rt === '_') || (lt === '_') || (grid(x + 1, y) === '/' && grid(x - 1, y - 1) === '_') || (grid(x - 1, y) === '/' && grid(x + 1, y) === '_')); } else if (c === '.') { return (rt === '\\'); } else if (c === "'") { return (lt === '\\'); } else if (c === '^') { return rt === '\\'; } else if (c === 'v' || c === 'V') { return lt === '\\'; } else if (isVertex(c) || isPoint(c) || (c === '|')) { return isSolidBLine(lt) || isSolidBLine(rt); } }; /** Returns true if there is a solid diagonal line passing through (x, y) */ grid.isSolidDLineAt = function (x, y) { if (y === undefined) { y = x.x; x = x.x; } var c = grid(x, y); var lt = grid(x - 1, y + 1); var rt = grid(x + 1, y - 1); if (c === '/' && ((grid(x, y - 1) === '\\') || (grid(x, y + 1) === '\\'))) { // Special case of tiny hexagon corner return true; } else if (c === '/' && ((grid(x + 1, y) === '\\' && grid(x - 1, y) === '_') || (grid(x - 1, y) === '\\' && grid(x + 1, y - 1) === '_'))) { // _/\ or _ // \/ return true; } else if (isSolidDLine(c)) { // Looks like a diagonal line...does it continue? We need two in a row. return (isSolidDLine(rt) || isTopVertex(rt) || isPoint(rt) || (rt === '^') || (rt === '_') || isSolidDLine(lt) || isBottomVertex(lt) || isPoint(lt) || (lt === 'v' || lt === 'V') || (lt === '_')); } else if (c === '.' || c === ',') { return (lt === '/'); } else if (c === "'") { return (rt === '/'); } else if (c === '^') { return lt === '/'; } else if (c === 'v' || c === 'V') { return rt === '/'; } else if (isVertex(c) || isPoint(c) || (c === '|')) { return isSolidDLine(lt) || isSolidDLine(rt); } return false; }; grid.toString = function () { return str; }; return Object.freeze(grid); } /** A 1D curve. If C is specified, the result is a bezier with that as the tangent control point */ function Path(A, B, C, D, dashed) { if (!((A instanceof Vec2) && (B instanceof Vec2))) { throw new Error('Path constructor requires at least two Vec2s'); } this.A = A; this.B = B; if (C) { this.C = C; if (D) { this.D = D; } else { this.D = C; } } this.dashed = dashed || false; Object.freeze(this); } var _ = Path.prototype; _.isVertical = function () { return this.B.x === this.A.x; }; _.isHorizontal = function () { return this.B.y === this.A.y; }; /** Diagonal lines look like: / See also backDiagonal */ _.isDiagonal = function () { var dx = this.B.x - this.A.x; var dy = this.B.y - this.A.y; return (abs(dy + dx) < EPSILON); }; _.isBackDiagonal = function () { var dx = this.B.x - this.A.x; var dy = this.B.y - this.A.y; return (abs(dy - dx) < EPSILON); }; _.isCurved = function () { return this.C !== undefined; }; /** Does this path have any end at (x, y) */ _.endsAt = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return ((this.A.x === x) && (this.A.y === y)) || ((this.B.x === x) && (this.B.y === y)); }; /** Does this path have an up end at (x, y) */ _.upEndsAt = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return this.isVertical() && (this.A.x === x) && (min(this.A.y, this.B.y) === y); }; /** Does this path have an up end at (x, y) */ _.diagonalUpEndsAt = function (x, y) { if (!this.isDiagonal()) { return false; } if (y === undefined) { y = x.y; x = x.x; } if (this.A.y < this.B.y) { return (this.A.x === x) && (this.A.y === y); } else { return (this.B.x === x) && (this.B.y === y); } }; /** Does this path have a down end at (x, y) */ _.diagonalDownEndsAt = function (x, y) { if (!this.isDiagonal()) { return false; } if (y === undefined) { y = x.y; x = x.x; } if (this.B.y < this.A.y) { return (this.A.x === x) && (this.A.y === y); } else { return (this.B.x === x) && (this.B.y === y); } }; /** Does this path have an up end at (x, y) */ _.backDiagonalUpEndsAt = function (x, y) { if (!this.isBackDiagonal()) { return false; } if (y === undefined) { y = x.y; x = x.x; } if (this.A.y < this.B.y) { return (this.A.x === x) && (this.A.y === y); } else { return (this.B.x === x) && (this.B.y === y); } }; /** Does this path have a down end at (x, y) */ _.backDiagonalDownEndsAt = function (x, y) { if (!this.isBackDiagonal()) { return false; } if (y === undefined) { y = x.y; x = x.x; } if (this.B.y < this.A.y) { return (this.A.x === x) && (this.A.y === y); } else { return (this.B.x === x) && (this.B.y === y); } }; /** Does this path have a down end at (x, y) */ _.downEndsAt = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return this.isVertical() && (this.A.x === x) && (max(this.A.y, this.B.y) === y); }; /** Does this path have a left end at (x, y) */ _.leftEndsAt = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return this.isHorizontal() && (this.A.y === y) && (min(this.A.x, this.B.x) === x); }; /** Does this path have a right end at (x, y) */ _.rightEndsAt = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return this.isHorizontal() && (this.A.y === y) && (max(this.A.x, this.B.x) === x); }; _.verticalPassesThrough = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return this.isVertical() && (this.A.x === x) && (min(this.A.y, this.B.y) <= y) && (max(this.A.y, this.B.y) >= y); } _.horizontalPassesThrough = function (x, y) { if (y === undefined) { y = x.y; x = x.x; } return this.isHorizontal() && (this.A.y === y) && (min(this.A.x, this.B.x) <= x) && (max(this.A.x, this.B.x) >= x); } /** Returns a string suitable for inclusion in an SVG tag */ _.toSVG = function () { var svg = ') insert(vec, type, ) angle is the angle in degrees to rotate the result */ DS.insert = function (x, y, type, angle) { if (type === undefined) { type = y; y = x.y; x = x.x; } if (!isDecoration(type)) { throw new Error('Illegal decoration character: ' + type); } var d = { C: Vec2(x, y), type: type, angle: angle || 0 }; // Put arrows at the front and points at the back so that // arrows always draw under points if (isPoint(type)) { this._decorationArray.push(d); } else { this._decorationArray.unshift(d); } }; DS.toSVG = function () { var svg = ''; for (var i = 0; i < this._decorationArray.length; ++i) { var decoration = this._decorationArray[i]; var C = decoration.C; if (isJump(decoration.type)) { // Slide jumps var dx = (decoration.type === ')') ? +0.75 : -0.75; var up = Vec2(C.x, C.y - 0.5); var dn = Vec2(C.x, C.y + 0.5); var cup = Vec2(C.x + dx, C.y - 0.5); var cdn = Vec2(C.x + dx, C.y + 0.5); svg += ''; } else if (isPoint(decoration.type)) { const CLASSES = { '*': 'closed', 'o': 'open', '◌': 'dotted', '○': 'open', '◍': 'shaded', '●': 'closed' }; const FILL = { 'closed': 'black', 'open': 'white', 'dotted': 'white', 'shaded': '#666' }; const STROKE = { 'closed': '', 'open': ' stroke="black"', 'dotted': ' stroke="black" stroke-dasharray="1,1"', 'shaded': ' stroke="black"' }; var cls = CLASSES[decoration.type]; svg += '\n'; } else if (isGray(decoration.type)) { var shade = Math.round((3 - GRAY_CHARACTERS.indexOf(decoration.type)) * 63.75); svg += '\n'; } else if (isTri(decoration.type)) { // 30-60-90 triangle var index = TRI_CHARACTERS.indexOf(decoration.type); var xs = 0.5 - (index & 1); var ys = 0.5 - (index >> 1); xs *= sign(ys); var tip = Vec2(C.x + xs, C.y - ys); var up = Vec2(C.x + xs, C.y + ys); var dn = Vec2(C.x - xs, C.y + ys); svg += '\n'; } else { // Arrow head var tip = Vec2(C.x + 1, C.y); var up = Vec2(C.x - 0.5, C.y - 0.35); var dn = Vec2(C.x - 0.5, C.y + 0.35); svg += '\n'; } } return svg; }; //////////////////////////////////////////////////////////////////////////// function findPaths(grid, pathSet) { // Does the line from A to B contain at least one c? function lineContains(A, B, c) { var dx = sign(B.x - A.x); var dy = sign(B.y - A.y); var x, y; for (x = A.x, y = A.y; (x !== B.x) || (y !== B.y); x += dx, y += dy) { if (grid(x, y) === c) { return true; } } // Last point return (grid(x, y) === c); } // Find all solid vertical lines. Iterate horizontally // so that we never hit the same line twice for (var x = 0; x < grid.width; ++x) { for (var y = 0; y < grid.height; ++y) { if (grid.isSolidVLineAt(x, y)) { // This character begins a vertical line...now, find the end var A = Vec2(x, y); do { grid.setUsed(x, y); ++y; } while (grid.isSolidVLineAt(x, y)); var B = Vec2(x, y - 1); var up = grid(A); var upup = grid(A.x, A.y - 1); if (!isVertex(up) && ((upup === '-') || (upup === '_') || (upup === '┳') || (grid(A.x - 1, A.y - 1) === '_') || (grid(A.x + 1, A.y - 1) === '_') || isBottomVertex(upup)) || isJump(upup)) { // Stretch up to almost reach the line above (if there is a decoration, // it will finish the gap) A.y -= 0.5; } var dn = grid(B); var dndn = grid(B.x, B.y + 1); if (!isVertex(dn) && ((dndn === '-') || (dndn === '┻') || isTopVertex(dndn)) || isJump(dndn) || (grid(B.x - 1, B.y) === '_') || (grid(B.x + 1, B.y) === '_')) { // Stretch down to almost reach the line below B.y += 0.5; } // Don't insert degenerate lines if ((A.x !== B.x) || (A.y !== B.y)) { pathSet.insert(new Path(A, B)); } // Continue the search from the end value y+1 } // Some very special patterns for the short lines needed on // circuit diagrams. Only invoke these if not also on a curve // _ _ // -' '- -' else if ((grid(x, y) === "'") && (((grid(x - 1, y) === '-') && (grid(x + 1, y - 1) === '_') && !isSolidVLineOrJumpOrPoint(grid(x - 1, y - 1))) || ((grid(x - 1, y - 1) === '_') && (grid(x + 1, y) === '-') && !isSolidVLineOrJumpOrPoint(grid(x + 1, y - 1))))) { pathSet.insert(new Path(Vec2(x, y - 0.5), Vec2(x, y))); } // _.- -._ else if ((grid(x, y) === '.') && (((grid(x - 1, y) === '_') && (grid(x + 1, y) === '-') && !isSolidVLineOrJumpOrPoint(grid(x + 1, y + 1))) || ((grid(x - 1, y) === '-') && (grid(x + 1, y) === '_') && !isSolidVLineOrJumpOrPoint(grid(x - 1, y + 1))))) { pathSet.insert(new Path(Vec2(x, y), Vec2(x, y + 0.5))); } // For drawing resistors: -.╱ else if ((grid(x, y) === '.') && (grid(x - 1, y) === '-') && (grid(x + 1, y) === '╱')) { pathSet.insert(new Path(Vec2(x, y), Vec2(x + 0.5, y + 0.5))); } // For drawing resistors: ╱'- else if ((grid(x, y) === "'") && (grid(x + 1, y) === '-') && (grid(x - 1, y) === '╱')) { pathSet.insert(new Path(Vec2(x, y), Vec2(x - 0.5, y - 0.5))); } } // y } // x // Find all solid horizontal lines for (var y = 0; y < grid.height; ++y) { for (var x = 0; x < grid.width; ++x) { if (grid.isSolidHLineAt(x, y)) { // Begins a line...find the end var A = Vec2(x, y); do { grid.setUsed(x, y); ++x; } while (grid.isSolidHLineAt(x, y)); var B = Vec2(x - 1, y); // Detect adjacent box-drawing characters and lengthen the edge if (grid(B.x + 1, B.y) === '┫') { B.x += 0.5; } if (grid(A.x - 1, A.y) === '┣') { A.x -= 0.5; } // Detect curves and shorten the edge if (!isVertex(grid(A.x - 1, A.y)) && ((isTopVertex(grid(A)) && isSolidVLineOrJumpOrPoint(grid(A.x - 1, A.y + 1))) || (isBottomVertex(grid(A)) && isSolidVLineOrJumpOrPoint(grid(A.x - 1, A.y - 1))))) { ++A.x; } if (!isVertex(grid(B.x + 1, B.y)) && ((isTopVertex(grid(B)) && isSolidVLineOrJumpOrPoint(grid(B.x + 1, B.y + 1))) || (isBottomVertex(grid(B)) && isSolidVLineOrJumpOrPoint(grid(B.x + 1, B.y - 1))))) { --B.x; } // Only insert non-degenerate lines if ((A.x !== B.x) || (A.y !== B.y)) { pathSet.insert(new Path(A, B)); } // Continue the search from the end x+1 } } } // y // Find all solid left-to-right downward diagonal lines (BACK DIAGONAL) for (var i = -grid.height; i < grid.width; ++i) { for (var x = i, y = 0; y < grid.height; ++y, ++x) { if (grid.isSolidBLineAt(x, y)) { // Begins a line...find the end var A = Vec2(x, y); do { ++x; ++y; } while (grid.isSolidBLineAt(x, y)); var B = Vec2(x - 1, y - 1); // Ensure that the entire line wasn't just vertices if (lineContains(A, B, '\\')) { for (var j = A.x; j <= B.x; ++j) { grid.setUsed(j, A.y + (j - A.x)); } var top = grid(A); var up = grid(A.x, A.y - 1); var uplt = grid(A.x - 1, A.y - 1); if ((up === '/') || (uplt === '_') || (up === '_') || (!isVertex(top) && (isSolidHLine(uplt) || isSolidVLine(uplt)))) { // Continue half a cell more to connect for: // ___ ___ // \ \ / ---- | // \ \ \ ^ |^ A.x -= 0.5; A.y -= 0.5; } else if (isPoint(uplt)) { // Continue 1/4 cell more to connect for: // // o // ^ // \ A.x -= 0.25; A.y -= 0.25; } else if (top === '\\' && grid.isSolidDLineAt(A.x - 1, A.y)) { // Cap a sharp vertex: // \ / \ _ // \/ \/ A.x -= 0.5; A.y -= 0.5; } var bottom = grid(B); var dnrt = grid(B.x + 1, B.y + 1); if ((grid(B.x, B.y + 1) === '/') || (grid(B.x + 1, B.y) === '_') || (grid(B.x - 1, B.y) === '_') || (!isVertex(grid(B)) && (isSolidHLine(dnrt) || isSolidVLine(dnrt)))) { // Continue half a cell more to connect for: // \ \ | // \ \ \ v v| // \__ __\ / ---- | B.x += 0.5; B.y += 0.5; } else if (isPoint(dnrt)) { // Continue 1/4 cell more to connect for: // // \ // v // o B.x += 0.25; B.y += 0.25; } else if (bottom === '\\' && grid.isSolidDLineAt(B.x + 1, B.y)) { // Cap a sharp vertex: // /\ _/\ // / \ \ B.x += 0.5; B.y += 0.5; } pathSet.insert(new Path(A, B)); // Continue the search from the end x+1,y+1 } // lineContains } } } // i // Find all solid left-to-right upward diagonal lines (DIAGONAL) for (var i = -grid.height; i < grid.width; ++i) { for (var x = i, y = grid.height - 1; y >= 0; --y, ++x) { if (grid.isSolidDLineAt(x, y)) { // Begins a line...find the end var A = Vec2(x, y); do { ++x; --y; } while (grid.isSolidDLineAt(x, y)); var B = Vec2(x - 1, y + 1); if (lineContains(A, B, '/')) { // This is definitely a line. Commit the characters on it for (var j = A.x; j <= B.x; ++j) { grid.setUsed(j, A.y - (j - A.x)); } var up = grid(B.x, B.y - 1); var uprt = grid(B.x + 1, B.y - 1); var bottom = grid(B); if ((up === '\\') || (up === '_') || (uprt === '_') || (!isVertex(grid(B)) && (isSolidHLine(uprt) || isSolidVLine(uprt)))) { // Continue half a cell more to connect at: // __ __ --- | // / / ^ ^| // / / / / | B.x += 0.5; B.y -= 0.5; } else if (isPoint(uprt)) { // Continue 1/4 cell more to connect at: // // o // ^ // / B.x += 0.25; B.y -= 0.25; } if (bottom === '/' && grid.isSolidBLineAt(B.x + 1, B.y)) { // Cap a sharp vertex: // \ / \ _ // \/ \/ B.x += 0.5; B.y -= 0.5; } var dnlt = grid(A.x - 1, A.y + 1); var top = grid(A); if ((grid(A.x, A.y + 1) === '\\') || (grid(A.x - 1, A.y) === '_') || (grid(A.x + 1, A.y) === '_') || (!isVertex(grid(A)) && (isSolidHLine(dnlt) || isSolidVLine(dnlt)))) { // Continue half a cell more to connect at: // / \ | // / / v v| // __/ /__ ---- | A.x -= 0.5; A.y += 0.5; } else if (isPoint(dnlt)) { // Continue 1/4 cell more to connect at: // // / // v // o A.x -= 0.25; A.y += 0.25; } else if (top === '/' && grid.isSolidBLineAt(A.x - 1, A.y)) { // Cap a sharp vertex: // \ / _ / // \/ \/ A.x -= 0.5; A.y += 0.5; } pathSet.insert(new Path(A, B)); // Continue the search from the end x+1,y-1 } // lineContains } } } // y // Now look for curved corners. The syntax constraints require // that these can always be identified by looking at three // horizontally-adjacent characters. for (var y = 0; y < grid.height; ++y) { for (var x = 0; x < grid.width; ++x) { const CURVE = 0.551915024494; // https://spencermortensen.com/articles/bezier-circle/ const CURVE_X = 2 * CURVE; const CURVE_Y = CURVE; var c = grid(x, y); // Note that because of undirected vertices, the // following cases are not exclusive if (isTopVertex(c)) { // -. // | if (isSolidHLine(grid(x - 1, y)) && isSolidVLine(grid(x + 1, y + 1))) { grid.setUsed(x - 1, y); grid.setUsed(x, y); grid.setUsed(x + 1, y + 1); pathSet.insert(new Path(Vec2(x - 1, y), Vec2(x + 1, y + 1), Vec2(x - 1 + CURVE_X, y), Vec2(x + 1, y + 1 - CURVE_Y))); } // .- // | if (isSolidHLine(grid(x + 1, y)) && isSolidVLine(grid(x - 1, y + 1))) { grid.setUsed(x - 1, y + 1); grid.setUsed(x, y); grid.setUsed(x + 1, y); pathSet.insert(new Path(Vec2(x + 1, y), Vec2(x - 1, y + 1), Vec2(x + 1 - CURVE_X, y), Vec2(x - 1, y + 1 - CURVE_Y))); } } // Special case patterns: // . . . . // ( o ) o // ' . ' ' if (((c === ')') || isPoint(c)) && (grid(x - 1, y - 1) === '.') && (grid(x - 1, y + 1) === "\'")) { grid.setUsed(x, y); grid.setUsed(x - 1, y - 1); grid.setUsed(x - 1, y + 1); pathSet.insert(new Path(Vec2(x - 2, y - 1), Vec2(x - 2, y + 1), Vec2(x + 0.6, y - 1), Vec2(x + 0.6, y + 1))); } if (((c === '(') || isPoint(c)) && (grid(x + 1, y - 1) === '.') && (grid(x + 1, y + 1) === "\'")) { grid.setUsed(x, y); grid.setUsed(x + 1, y - 1); grid.setUsed(x + 1, y + 1); pathSet.insert(new Path(Vec2(x + 2, y - 1), Vec2(x + 2, y + 1), Vec2(x - 0.6, y - 1), Vec2(x - 0.6, y + 1))); } if (isBottomVertex(c)) { // | // -' if (isSolidHLine(grid(x - 1, y)) && isSolidVLine(grid(x + 1, y - 1))) { grid.setUsed(x - 1, y); grid.setUsed(x, y); grid.setUsed(x + 1, y - 1); pathSet.insert(new Path(Vec2(x - 1, y), Vec2(x + 1, y - 1), Vec2(x - 1 + CURVE_X, y), Vec2(x + 1, y - 1 + CURVE_Y))); } // | // '- if (isSolidHLine(grid(x + 1, y)) && isSolidVLine(grid(x - 1, y - 1))) { grid.setUsed(x - 1, y - 1); grid.setUsed(x, y); grid.setUsed(x + 1, y); pathSet.insert(new Path(Vec2(x + 1, y), Vec2(x - 1, y - 1), Vec2(x + 1 - CURVE_X, y), Vec2(x - 1, y - 1 + CURVE_Y))); } } } // for x } // for y // Find low horizontal lines marked with underscores. These // are so simple compared to the other cases that we process // them directly here without a helper function. Process these // from top to bottom and left to right so that we can read // them in a single sweep. // // Exclude the special case of double underscores going right // into an ASCII character, which could be a source code // identifier such as __FILE__ embedded in the diagram. for (var y = 0; y < grid.height; ++y) { for (var x = 0; x < grid.width - 2; ++x) { var lt = grid(x - 1, y); if ((grid(x, y) === '_') && (grid(x + 1, y) === '_') && (!isASCIILetter(grid(x + 2, y)) || (lt === '_')) && (!isASCIILetter(lt) || (grid(x + 2, y) === '_'))) { var ltlt = grid(x - 2, y); var A = Vec2(x - 0.5, y + 0.5); if ((lt === '|') || (grid(x - 1, y + 1) === '|') || (lt === '.') || (grid(x - 1, y + 1) === "'")) { // Extend to meet adjacent vertical A.x -= 0.5; // Very special case of overrunning into the side of a curve, // needed for logic gate diagrams if ((lt === '.') && ((ltlt === '-') || (ltlt === '.')) && (grid(x - 2, y + 1) === '(')) { A.x -= 0.5; } } else if (lt === '/') { A.x -= 1.0; } // Detect overrun of a tight double curve if ((lt === '(') && (ltlt === '(') && (grid(x, y + 1) === "'") && (grid(x, y - 1) === '.')) { A.x += 0.5; } lt = ltlt = undefined; do { grid.setUsed(x, y); ++x; } while (grid(x, y) === '_'); var B = Vec2(x - 0.5, y + 0.5); var c = grid(x, y); var rt = grid(x + 1, y); var dn = grid(x, y + 1); if ((c === '|') || (dn === '|') || (c === '.') || (dn === "'")) { // Extend to meet adjacent vertical B.x += 0.5; // Very special case of overrunning into the side of a curve, // needed for logic gate diagrams if ((c === '.') && ((rt === '-') || (rt === '.')) && (grid(x + 1, y + 1) === ')')) { B.x += 0.5; } } else if ((c === '\\')) { B.x += 1.0; } // Detect overrun of a tight double curve if ((c === ')') && (rt === ')') && (grid(x - 1, y + 1) === "'") && (grid(x - 1, y - 1) === '.')) { B.x += -0.5; } pathSet.insert(new Path(A, B)); } } // for x } // for y } // findPaths function findDecorations(grid, pathSet, decorationSet) { function isEmptyOrVertex(c) { return (c === ' ') || /[^a-zA-Z0-9]|[ov]/.test(c); } /** Is the point in the center of these values on a line? Allow points that are vertically adjacent but not horizontally--they wouldn't fit anyway, and might be text. */ function onLine(up, dn, lt, rt) { return ((isEmptyOrVertex(dn) || isPoint(dn)) && (isEmptyOrVertex(up) || isPoint(up)) && isEmptyOrVertex(rt) && isEmptyOrVertex(lt)); } for (var x = 0; x < grid.width; ++x) { for (var j = 0; j < grid.height; ++j) { var c = grid(x, j); var y = j; if (isJump(c)) { // Ensure that this is really a jump and not a stray character if (pathSet.downEndsAt(x, y - 0.5) && pathSet.upEndsAt(x, y + 0.5)) { decorationSet.insert(x, y, c); grid.setUsed(x, y); } } else if (isPoint(c)) { var up = grid(x, y - 1); var dn = grid(x, y + 1); var lt = grid(x - 1, y); var rt = grid(x + 1, y); var llt = grid(x - 2, y); var rrt = grid(x + 2, y); if (pathSet.rightEndsAt(x - 1, y) || // Must be at the end of a line... pathSet.leftEndsAt(x + 1, y) || // or completely isolated NSEW pathSet.downEndsAt(x, y - 1) || pathSet.upEndsAt(x, y + 1) || pathSet.upEndsAt(x, y) || // For points on vertical lines pathSet.downEndsAt(x, y) || // that are surrounded by other characters onLine(up, dn, lt, rt)) { decorationSet.insert(x, y, c); grid.setUsed(x, y); } } else if (isGray(c)) { decorationSet.insert(x, y, c); grid.setUsed(x, y); } else if (isTri(c)) { decorationSet.insert(x, y, c); grid.setUsed(x, y); } else { // Arrow heads // If we find one, ensure that it is really an // arrow head and not a stray character by looking // for a connecting line. var dx = 0; if ((c === '>') && (pathSet.rightEndsAt(x, y) || pathSet.horizontalPassesThrough(x, y))) { if (isPoint(grid(x + 1, y))) { // Back up if connecting to a point so as to not // overlap it dx = -0.5; } decorationSet.insert(x + dx, y, '>', 0); grid.setUsed(x, y); } else if ((c === '<') && (pathSet.leftEndsAt(x, y) || pathSet.horizontalPassesThrough(x, y))) { if (isPoint(grid(x - 1, y))) { // Back up if connecting to a point so as to not // overlap it dx = 0.5; } decorationSet.insert(x + dx, y, '>', 180); grid.setUsed(x, y); } else if (c === '^') { // Because of the aspect ratio, we need to look // in two slots for the end of the previous line if (pathSet.upEndsAt(x, y - 0.5)) { decorationSet.insert(x, y - 0.5, '>', 270); grid.setUsed(x, y); } else if (pathSet.upEndsAt(x, y)) { decorationSet.insert(x, y, '>', 270); grid.setUsed(x, y); } else if (pathSet.diagonalUpEndsAt(x + 0.5, y - 0.5)) { decorationSet.insert(x + 0.5, y - 0.5, '>', 270 + DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.diagonalUpEndsAt(x + 0.25, y - 0.25)) { decorationSet.insert(x + 0.25, y - 0.25, '>', 270 + DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.diagonalUpEndsAt(x, y)) { decorationSet.insert(x, y, '>', 270 + DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.backDiagonalUpEndsAt(x, y)) { decorationSet.insert(x, y, c, 270 - DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.backDiagonalUpEndsAt(x - 0.5, y - 0.5)) { decorationSet.insert(x - 0.5, y - 0.5, c, 270 - DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.backDiagonalUpEndsAt(x - 0.25, y - 0.25)) { decorationSet.insert(x - 0.25, y - 0.25, c, 270 - DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.verticalPassesThrough(x, y)) { // Only try this if all others failed decorationSet.insert(x, y - 0.5, '>', 270); grid.setUsed(x, y); } } else if (c === 'v' || c === 'V') { if (pathSet.downEndsAt(x, y + 0.5)) { decorationSet.insert(x, y + 0.5, '>', 90); grid.setUsed(x, y); } else if (pathSet.downEndsAt(x, y)) { decorationSet.insert(x, y, '>', 90); grid.setUsed(x, y); } else if (pathSet.diagonalDownEndsAt(x, y)) { decorationSet.insert(x, y, '>', 90 + DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.diagonalDownEndsAt(x - 0.5, y + 0.5)) { decorationSet.insert(x - 0.5, y + 0.5, '>', 90 + DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.diagonalDownEndsAt(x - 0.25, y + 0.25)) { decorationSet.insert(x - 0.25, y + 0.25, '>', 90 + DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.backDiagonalDownEndsAt(x, y)) { decorationSet.insert(x, y, '>', 90 - DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.backDiagonalDownEndsAt(x + 0.5, y + 0.5)) { decorationSet.insert(x + 0.5, y + 0.5, '>', 90 - DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.backDiagonalDownEndsAt(x + 0.25, y + 0.25)) { decorationSet.insert(x + 0.25, y + 0.25, '>', 90 - DIAGONAL_ANGLE); grid.setUsed(x, y); } else if (pathSet.verticalPassesThrough(x, y)) { // Only try this if all others failed decorationSet.insert(x, y + 0.5, '>', 90); grid.setUsed(x, y); } } // arrow heads } // decoration type } // y } // x } // findArrowHeads // Cases where we want to redraw at graphical unicode character // to adjust its weight or shape for a conventional application // in constructing a diagram. function findReplacementCharacters(grid, pathSet) { for (var x = 0; x < grid.width; ++x) { for (var y = 0; y < grid.height; ++y) { if (grid.isUsed(x, y)) continue; var c = grid(x, y); switch (c) { case '╱': pathSet.insert(new Path(Vec2(x - 0.5, y + 0.5), Vec2(x + 0.5, y - 0.5))); grid.setUsed(x, y); break; case '╲': pathSet.insert(new Path(Vec2(x - 0.5, y - 0.5), Vec2(x + 0.5, y + 0.5))); grid.setUsed(x, y); break; } } } } // findReplacementCharacters var grid = makeGrid(diagramString); var pathSet = new PathSet(); var decorationSet = new DecorationSet(); findPaths(grid, pathSet); findReplacementCharacters(grid, pathSet); findDecorations(grid, pathSet, decorationSet); let width = (grid.width + 1) * SCALE; let height = (grid.height + 1) * SCALE * ASPECT; let attrs = options.style; attrs.xmlns = 'http://www.w3.org/2000/svg'; attrs.version = '1.1'; attrs.height = height.toString(); attrs.width = width.toString(); attrs.viewBox = '0 0 ' + width + ' ' + height; // These attributes can be overridden: const DEFAULT_ATTRS = { 'class': 'diagram', 'text-anchor': 'middle', 'font-family': 'monospace', 'font-size': (SCALE * 13 / 8).toString() + 'px', }; Object.keys(DEFAULT_ATTRS).forEach(k => { if (!attrs[k]) { attrs[k] = DEFAULT_ATTRS[k]; } }); let svg = ' typeof attrs[k] === 'string') .map(k => k + '="' + escapeHTMLEntities(attrs[k]) + '"').join(' ') + '>\n'; if (options.backdrop) { svg += '\n'; } if (options.grid) { svg += '\n'; for (var x = 0; x < grid.width; ++x) { for (var y = 0; y < grid.height; ++y) { svg += '\n'; } } svg += '\n'; } svg += pathSet.toSVG(); svg += decorationSet.toSVG(); // Convert any remaining characters if (!options.disableText) { svg += '\n'; // Enlarge hexagons so that they fill a grid for (var y = 0; y < grid.height; ++y) { for (var x = 0; x < grid.width; ++x) { var c = grid(x, y); if (/[\u2B22\u2B21]/.test(c)) { svg += '' + escapeHTMLEntities(c) + '\n'; grid.setUsed(x, y); } } // x } // y for (var y = 0; y < grid.height; ++y) { let x = grid.textStart(0, y); while (x < grid.width) { let t = grid.text(x, y); let s = t.join(''); svg += '' + escapeHTMLEntities(s) + '\n'; x = grid.textStart(x + t.length, y); } } // y svg += '\n'; } if (options.source) { svg += '\n'; for (var y = 0; y < grid.height; ++y) { for (var x = 0; x < grid.width; ++x) { var c = grid(x, y); if (c !== ' ') { // Offset the characters by 2 for easier viewing svg += '' + escapeHTMLEntities(c) + ''; } // if } // x } // y svg += ''; } // if svg += ''; return unhideMarkers(svg); } module.exports = { diagramToSVG }; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aasvg-0.1.8/package-lock.json�����������������������������������������������������������������������0000664�0000000�0000000�00000000104�14166702642�0016040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "name": "aasvg", "version": "0.1.0", "lockfileVersion": 1 } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aasvg-0.1.8/package.json����������������������������������������������������������������������������0000664�0000000�0000000�00000001070�14166702642�0015115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "name": "aasvg", "version": "0.1.8", "description": "Turn ASCII art diagrams into SVG", "bin": "./main.js", "repository": { "type": "git", "url": "https://github.com/martinthomson/aasvg" }, "keywords": [ "ascii", "diagram", "goat", "svg" ], "author": "Martin Thomson ", "license": "BSD", "bugs": { "url": "https://github.com/martinthomson/aasvg/issues" }, "homepage": "https://github.com/martinthomson/aasvg#readme", "engines": { "node": ">=10" }, "files": [ "*.js", "README.md" ] } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������