svgtiler v3.2.1
SVG Tiler
SVG Tiler is a tool for drawing diagrams on a grid, where you draw ASCII art or spreadsheets and SVG Tiler automatically subsitutes each character or cell with a corresponding SVG symbol to make a big SVG figure (in an efficient representation that factors out repeated tiles), and optionally converts it to PDF, PNG, and/or LaTeX for LaTeX text. Here are a few examples of generated figures; see more examples below.
| Super Mario Bros. | The Witness | Chess |
|---|---|---|
![]() |
Table of Contents
- Main Concepts
- Usage
- Mapping Files: .txt, .js, .coffee, .jsx, .cjsx
- Drawing Files: .asc, .ssv, .csv, .tsv, .xlsx, .xls, .ods
- Style Files: .css, .styl
- Layout Algorithm
- z-index: Stacking Order of Tiles
- Overflow and Bounding Box
- Autosizing Tiles
- Unrecognized Tiles
<image>Processing- Converting SVG to PDF/PNG
- LaTeX Text
- Maketiles
- API
- Examples
- Installation
- Command-Line Usage
- History
Main Concepts
To use SVG Tiler, you combine at least two types of files (possibly multiple of each type):
A mapping file specifies how to map tile names (strings) to SVG content (either embedded in the same file or in separate files). Mapping files can be specified in a simple ASCII format, or as a dynamic mapping defined by JavaScript or CoffeeScript code.
A drawing file specifies a grid of tile names (strings) which, combined with one or more mapping files to define the SVG associated with each tile, compile to a single (tiled) SVG. Drawing files can be specified as ASCII art (where each tile name is limited to a single character), space-separated ASCII art (where tile names are separated by whitespace), standard CSV/TSV (comma/tab-separated) tabular formats, or standard multi-sheet spreadsheet formats XLSX/XLS/ODS supported by Google Sheets, OfficeOffice, and Excel.
An optional style file specifies global styling of SVG elements via CSS or Stylus.
Here's a simple full example from Tetris in the pixel-art style of the NES game:
T NES_level7_empty.png
O NES_level7_empty.png
I NES_level7_empty.png
J NES_level7_filled.png
S NES_level7_filled.png
L NES_level7_other.png
Z NES_level7_other.png
<rect fill="black" width="8" height="8"/>PNG image files referenced above:
I
I Z
ILJJJOO SSZZ
ILLLJOOSS Z Usage
Normally, you run svgtiler on the command line, listing
all input files (mapping, drawing, and style files) as arguments.
Mapping and style files apply to all drawing files listed later.
File types and formats are distinguished automatically by their extension,
as listed below.
For example:
svgtiler map1.txt map2.coffee drawing.asc drawings.xlsxwill generate drawing.svg using the mappings in map1.txt and map2.coffee,
and will generate drawings_<sheet>.svg for each unhidden sheet in
drawings.xlsx.
svgtiler automatically skips building when it detects all dependencies
(including the input drawing file, all style files, all mapping files,
and anything required by JavaScript/CoffeeScript mapping files)
are older than the SVG file, similar to make.
In these cases, you will see the message (SKIPPED) in the output.
In addition, svgtiler will avoid writing a .svg or .tex file
(and changing their timestamp) if the contents haven't changed;
in these cases, you will see the message (UNCHANGED) in the output.
In particular, this will prevent these files from getting
converted to PDF or PNG
(unless those files are out-of-date).
You can override this behavior via the -f/--force command-line option,
which forces all building and conversions to take place.
Alternatively, you can use the SVG Tiler API to render SVG from your own JavaScript code, e.g., converting ASCII art embedded within a webpage into SVG drawings.
Mapping Files: .txt, .js, .coffee, .jsx, .cjsx
In general, mapping files provide a partial mapping from tile names
(which are generally strings) to SVG content. Most often, your SVG
content should consist of a <symbol> or <svg> tag at the top level,
with width and height attributes (for a coordinate system of
0, width × 0, height) or with a viewBox attribute
(for a more general coordinate system e.g. starting at negative values).
You can sometimes get away with less;
see Autosizing Tiles.
In the .txt format for mapping files, each line consists of a tile name
(either having no spaces, or consisting entirely of a single space),
followed by whitespace, followed by either a block of SVG code
(such as <symbol viewBox="...">...</symbol>) or a filename containing
such a block. For example, here is a mapping of O to black squares
and both (space) and empty string to blank squares, all dimensioned
50 × 50:
O <symbol viewBox="0 0 50 50"><rect width="50" height="50"/></symbol>
<symbol viewBox="0 0 50 50"/>
<symbol viewBox="0 0 50 50"/>Here is a mapping of the same tiles to external files:
O O.svg
blank.svg
blank.svgIn the .js / .coffee / .jsx / .cjsx formats, the file consists of
JavaScript / CoffeeScript code that gets loaded as a NodeJS module.
The code specifies a mapping in one of a few ways:
export map = mappingorexport default mapping(ECMAScript modules style)exports.map = mappingorexports.default = mapping(CommonJS modules style)- Writing a top-level object, array, or function expression
at the end of the file (without e.g. being assigned to a variable),
which triggers an implicit
export default.
In any case, mapping should be one of the following types of mapping
objects:
- A plain JavaScript object whose properties map tile names to tiles
(e.g.,
{O: tile1, ' ': tile2}). - A
Mapobject mapping tile names to tiles. - A function taking two arguments — a tile name (string)
and a
Contextobject (also passed asthis) — and returning a tile. This feature allows you to parse tile names how you want, and to vary the tile depending on the context (e.g., neighboring tile names or parity of the tile location). - An
svgtiler.Mappingobject containing one of the listed formats, e.g.,new svgtiler.Mapping((key, context) => ...). Such an object can also be obtained from a mapping file (as if it were specified on the command line) viasvgtiler.require(filename). - An
svgtiler.Mappingsobject containing one or more of the listed formats, e.g.,new svgtiler.Mappings(mapping1, mapping2).Mappingslook up the specified key in each mapping in reverse sequential order until finding one that doesn't resolve tonullorundefined(so later mappings take priority over earlier mappings, as on the command line). - An
Arrayof any of the above types, meaning to run the mappings in parallel and stack the resulting tiles on top of each other, with the first non-null tile defining the tile size. (See next list for more details.)
Each tile (property of JavaScript object, value of Map object,
or return value of a function) should be specified as one of the following:
- SVG written directly in
JSX syntax, such as
<symbol viewBox=0 0 ${width} ${height}>{parity ? child1 : child2}</symbol>; or its CoffeeScript analog, such as<symbol viewBox="0 0 #{width} #{height}">{if parity then child1 else child2}</symbol>. (See e.g. the polyomino example.) - Preact (React-style) Virtual DOM elements built via
preact.hcalls. (This is what JSX notation actually gets converted into.) Be careful not to modify Preact nodes, in case you re-use them; instead usepreact.cloneElementto make a modified copy (or before modification). - A string of raw SVG code (detected by the presence of a
<character). - A filename with
.svgextension containing SVG code that gets inlined. - A filename with
.png,.jpg,.jpeg, or.gifextension containing an image, which will get processed as an<image>. - An empty string, short for the empty symbol
<symbol viewBox="0 0 0 0"/>. undefinedornull, indicating that this mapping doesn't define a tile for this tile name (and the next mapping should be checked).- Another mapping (JavaScript object,
Mapobject, function,svgtiler.Mapping, orsvgtiler.Mappings) that gets recursively evaluated as described above (with the same tile name and context). For example, a top-level JavaScript object could map some tile names to functions (when they need to be dynamic); or a top-level function could return different mappings depending on context. - A tile in one of the listed formats wrapped in a call to
svgtiler.static, e.g.,svgtiler.static(<symbol/>). This wrapper tells SVG Tiler that the tile mapping is always the same for this tile name, and does not depend onContext(e.g. adjacent tiles), enabling SVG Tiler to do more caching. This is only necessary if you use functions to define tiles; otherwise, SVG Tiler will automatically mark the tiles as static. - An
Arrayof tiles of any of the listed formats (including moreArrays, which get flattened), meaning to stack multiple tiles on top of each other, where the first non-null tile defines theviewBoxand size (but all can influence theboundingBox). Usez-indexto control stacking order. Null items in the array get ignored, and an empty array acts likenull(this mapping does not define a tile for this tile name). You can put functions inside arrays (which effectively disappear if they returnnull/undefined), or return arrays from functions, or both.
If you need to use a <marker>, <filter>, gradient, or other element
intended for <defs>, call svgtiler.def(tag), where tag
is one of the above representations of the marker, filter, gradient, etc.
This function adds the object to <defs> (if needed) at the top of the SVG,
and returns an object def with property def.id containing a unique id
string, and helper methods def.url() and def.hash() generating url(#id)
(as you'd use markers or gradients) and #id (as you'd use in <use>)
respectively.
By default, the id starts with marker, filter, etc.
according to the top-level tag of tag.
You can choose a better name by giving the tag an initial id, e.g.,
svgtiler.def(<marker id="arrow">...</marker>).
You can call svgtiler.def within a tile function (where repeated calls
with the same argument produce the same def and id) or at the global level
of your mapping file (for static defs).
See the grid-graph example for an example with markers.
Similarly, if you need to assign an id within your tile definition
(e.g., to render and then re-use an object multiple times), define the tile
with a function, and have that function call svgtiler.id(baseId)
to generate and return a unique id string starting with baseId
(which defaults to "id").
Functions get called with a Context object as both a second argument and
as this (if the function is defined via function;
=> functions
can't have this bound).
Instead of passing the Context object to other functions, they can access
the currently active Context via svgtiler.getContext().
The Context object has the following properties and methods:
context.keyis the tile name, orundefinedif theContextis out of bounds of the drawing. (This can't happen in the initial call, but can happen when you callcontext.neighbor.)context.includes(substring)computes whethercontext.keycontains the givensubstring(a shortcut forcontext.key.includes(substring)in ECMAScript 2015, but handling the case whencontext.keyisundefined).context.startsWith(substring)andcontext.endsWith(substring)are similar shortcuts, but failing whencontext.keyisundefined.context.match(regex)matchescontext.keyagainst the given regular expression (a shortcut forcontext.key.match(regex), but handling the case whencontext.keyisundefined).context.iis the row number of the cell of this tile (starting at 0).context.jis the column number of the cell of this tile (starting at 0).context.neighbor(dj, di)returns a newContextfor rowcontext.i + diand columncontext.j + dj(for relative neighbors). (Note the reversal of coordinates, so that the order passed toneighborcorresponds to x then y coordinate.) If there is no tile at that position, you will still get aContextobject but itskeyvalue will beundefinedandincludes()andmatch()will always returnfalse.context.at(j, i)returns a newContextfor rowjand columni(absolute coordinates). (Note again the reversal of coordinates to correspond to x before y.) Negative numbers count backward from the last row or column.- In particular, it's useful to call e.g.
context.neighbor(1, 0).includes('-')to check for adjacent tiles that change how this tile should be rendered. context.row(di = 0)returns an array ofContextobjects, one for each tile in rowi + di(in particular, includingcontextifdiis the default of0). For example, you can use thesomeoreverymethod on this array to do bulk tests on the row.context.column(dj = 0)returns an array ofContextobjects, one for each tile in columnj + dj.context.set(key)changes the key at the context's position to the specified value. This can be useful for changing keys that haven't been processed yet (later in reading order) to affect later processing.context.filenameis the name of the drawing file (e.g."input.xlsx").context.subnameis the name of the sheet within the spreadsheet drawing input, orundefinedif the drawing input format does allow multiple sheets.context.drawingis an instance ofDrawing(see below).- You can also add extra data to the main
contextobject given to the function, and it will be shared among all calls to all mapping/tile functions within the same drawing (but not between separate drawings). This can be useful for drawing-specific state.
The top-level code of your .js or .coffee mapping file can also export the following functions:
export initto schedule callinginit(mapping)whenever this mapping file is listed on the command line (including right after the mapping file is first loaded), and when state gets reset (e.g. via a)command-line argument). Note that each mapping file gets loaded (required) as a NodeJS module, which happens only once, so if your file uses any side effects (in particular, reading or writing to theshareobject for communication with other mapping files), it's important to put that code in aninitfunction instead of at the top level, so that SVG Tiler can correctly limit and restore these side effects in the presence of parentheses on the command line or when running multiple build rules.export preprocessto schedule callingpreprocess(render)when preparing to rendering each drawing, e.g., to initialize drawing-specific data or globally examine the drawing. Therenderargument (andthis) is set to aRenderinstance, which in particular hasdrawing,mappings, andstylesattributes. You can even modify the drawing'skeysat this stage, by modifyingrender.drawing.keys. Alternatively, loop over the cells usingrender.forEach((context) => ...)and usecontext.set(newKey). You can also add SVG content viarender.addorsvgtiler.add, e.g., add metadata likesvgtiler.add(<title>My drawing</title>); or set a default background color viarender.background(color)orsvgtiler.background(color).export postprocessto schedule callingpostprocess(render)after rendering each drawing, e.g., to modify or add to the drawing. During the callback,renderhas properties about the rendering's bounding box:xMin,xMax,yMin,yMax,width,height. You can add SVG content (overlay/underlay) viarender.addorsvgtiler.add, which you can pass a string or Preact Virtual DOM. Specify az-indexto control the stacking order relative to other symbols or overlays/underlays. SpecifyboundingBoxto increase the overall size of the rendered drawing. You can also set the final background color viarender.background(color)orsvgtiler.background(color). When used only once, this is equivalent to the postprocess step ofrender.add(<rect z-index="-Infinity" fill={fillColor} x={render.xMin} y={render.yMin} width={render.width} height={render.height}/>).
You can call svgtiler.background(color) in preprocess, postprocess,
or tile definition functions. Only the final color will end up being
rendered, as a single background <rect> beneath the whole drawing's
bounding box. You can also set a global default background color via the
--bg/--background command-line option.
Drawing objects (available via context.drawing in mapping functions
or render.drawing in preprocess and postprocess callbacks) have
the following useful attributes:
drawing.filenameis the name of the drawing file (e.g."input.xlsx").drawing.subnameis the name of the sheet within the spreadsheet drawing input, orundefinedif the drawing input format does allow multiple sheets.drawing.keysis an array of array of keys, with one array per row.drawing.get(j, i)gets the key at rowi, columnj(note reversed order), without any special processing.drawing.at(j, i)gets the key at rowi, columnj(note reversed order), with negative numbers counting back from the end.drawing.set(j, i, key)sets the key at rowi, columnj(note reversed order, without any special processing) tokey, adding rows or columns as necessary.drawing.marginsis an object specifying theleft,right,top, andbottommargins automatically removed, unless you use--margincommand-line option. This lets you adjust global parity according to the margins, for example.drawing.unevenLengthsis an array of original row lengths before rows were made to be the same length, unless you use the--unevencommand-line option. Note that these lengths do not include margins (which get removed first).
Like other NodeJS modules,
.js and .coffee files can access __dirname and __filename,
e.g., to use paths relative to the mapping file.
In addition to the preloaded module preact,
they have access to the SVG Tiler API via svgtiler, and
a global shared object share that you can add properties to
for communication between mapping files
(e.g., for one mapping file to provide settings to another mapping file).
You can also use the command-line option -s/--share to set properties
of share, as in the Mario example:
-s KEY=VALUE sets share.KEY to "VALUE", while
-s KEY sets share.KEY to undefined.
You can also use import ... from './filename' or require('./filename')
to import local modules or files relative to the mapping file.
- In particular, you can share .js/.coffee code or .json config files among mapping files.
- If you
import/requirea filename with.svgextension, you obtain a Preact Virtual DOM objectsvgrepresenting the SVG file, which you can include in a JSX template via{svg}. You can also easily manipulate the SVG before inclusion. For example,svg.props.childrenstrips off the outermost tag, allowing you to rewrap as in<symbol>{svg.props.children}</symbol>; see the Chess example. Orpreact.cloneElementlets you override certain attributes or add children; for example,preact.cloneElement(svg, {class: 'foo'}, <rect width="5" height="5"/>, svg.props.children)adds aclassattribute and prepends a<rect>child. Alternatively, usesvg.svg(thesvgattribute of the returned object) to get the SVG string (with comments removed). Note that the.svgfile can even have JSX notation in it, such as<svg width={share.width} height={share.height}>, butsvg.svgwill not interpret it specially. - If you
import/requirea filename with.png,.jpg,.jpeg, or.gifextension, you obtain a Preact Virtual DOM objectimagerepresenting an<image>tag for the file's inclusion, which you can include in a JSX template via{image}. Or if you want to inline/manipulate the SVG string, useimage.svg.
Drawing Files: .asc, .ssv, .csv, .tsv, .xlsx, .xls, .ods
The .asc format for drawing files represents traditional ASCII art:
each non-newline character represents a one-character tile name.
For example, here is a simple 5 × 5 ASCII drawing using tiles
O and (space):
OOO
O O O
OOOOO
O O
OOO.asc files can include Unicode characters encoded in UTF8. In this case, a single "character" is defined as a full "Unicode grapheme" (according to UAX #29, via the grapheme-splitter library), such as 👍🏽. See an example with Unicode.
The .ssv, .csv, and .tsv formats use
delimiter-separated values (DSV)
to specify an array of tile names. In particular,
.csv (comma-separated)
and
.tsv (tab-separated)
formats are exactly those exported by spreadsheet software such as
Google Drive, OpenOffice, or Excel, enabling you to draw in that software.
The .ssv format is similar, but where the delimiter between tile names
is arbitrary whitespace.
(Contrast this behavior with .csv which treats every comma as a delimiter.)
This format is nice to work with in a text editor, allowing you to line up
the columns by padding tile names with extra spaces.
All three formats support quoting according to the usual DSV rules:
any tile name (in particular, if it has a delimiter or double quote in it)
can be put in double quotes, and double quotes can be produced in the
tile name by putting "" (two double quotes) within the quoted string.
Thus, the one-character tile name " would be represented by """".
The .xlsx, .xlsm, .xlsb, .xls (Microsoft Excel),
.ods, .fods (OpenDocument), .dif (Data Interchange Format),
.prn (Lotus), and .dbf (dBASE/FoxPro) formats support data straight
from spreadsheet software. This format is special in that it supports
multiple sheets in one file. In this case, the output SVG files have
filenames distinguished by an underscore followed by the sheet name.
By default, hidden sheets are ignored, making it easy to "deprecate" old
drafts, but if you prefer, you can process hidden sheets via --hidden.
Currently, hidden sheet detection only works in .xls* files; see
File Format Support for
SheetJS Sheet Visibility.
Style Files: .css, .styl
Any input file in .css format gets inlined into an
SVG <style> tag.
Mixing SVG and CSS
lets you define global style rules for your SVG elements, for example,
specifying fill and stroke for every polygon of class purple:
polygon.purple { fill: hsl(276, 77%, 80%); stroke: hsl(276, 89%, 27%) }Instead of raw CSS, you can use the .styl format to write your styles in the indentation-based format Stylus. The example above could be written as follows in .styl:
polygon.purple
fill: hsl(276, 77%, 80%)
stroke: hsl(276, 89%, 27%)See the animation example for sample usage of a .css or .styl file.
If you'd rather generate a <style> tag dynamically depending on the
drawing content, you can do so in a .js or .coffee mapping file by calling
svgtiler.add during a preprocess or postprocess export.
Layout Algorithm
Given one or more mapping files and a drawing file, SVG Tiler follows a fairly
simple layout algorithm to place the SVG expansions of the tiles into a
single SVG output. Each tile has a bounding box, either specified by
the viewBox of the root <symbol> or <svg> element, or
automatically computed.
The algorithm places tiles in a single row to align their top edges,
with no horizontal space between them.
The algorithm places rows to align their left edges so that the rows' bounding
boxes touch, with the bottom of one row's bounding box equalling the top of
the next row's bounding box.
This layout algorithm works well if each row has a uniform height and each
column has a uniform width, even if different rows have different heights
and different columns have different widths. But it probably isn't what you
want if tiles have wildly differing widths or heights, so you should set
your viewBoxes accordingly.
Each unique tile gets defined just once (via SVG's
<symbol>)
and then instantiated (via SVG's
<use>)
many times, resulting in relatively small and efficient SVG outputs.
z-index: Stacking Order of Tiles
Often it is helpful to render some tiles on top of others.
Although SVG does not support a z-index
property, there was
a proposal
which SVG Tiler supports at the <symbol> level, emulated by
re-ordering tile rendering order to simulate the specified z order.
For example, the tile <symbol viewBox="0 0 10 10" z-index="2">...</symbol>
will be rendered on top of (later than) all tiles without a
z-index="..." specification (which default to a z-index of 0).
You can use a z-index="..." property or an HTML-style
style="z-index: ..." property.
The special values Infinity, +Infinity, and -Infinity are allowed
(along with variants like Inf or \infty).
Overflow and Bounding Box
By default, SVG Tiler (v1.15+) sets all tile <symbol>s to have
overflow="visible"
behavior, meaning that they can draw outside their viewBox.
If you want to override this behavior and clip symbols to their viewBox,
you have two options. At the <symbol> level, use overflow="hidden" or
style="overflow: hidden". At the global level, use the --no-overflow
command-line option (and use overflow="visible" to make some symbols
overflow).
When overflow is visible, viewBox still represents the size of the
element in the grid layout,
but allows the element's actual bounding box to be something else.
To correctly set the bounding box of the overall SVG drawing, SVG Tiler
defines an additional <symbol> attribute called boundingBox, which is like
viewBox but for specifying the actual bounding box of the content
(when they differ — boundingBox defaults to the value of viewBox).
The viewBox of the overall SVG is set to the minimum rectangle
containing all tiles' boundingBoxs.
For example,
<symbol viewBox="0 0 10 10" boundingBox="-5 -5 20 20">...</symbol>
defines a tile that gets laid out as if it occupies the 0, 10 ×
0, 10 square, but the tile can draw outside that square, and the overall
drawing bounding box will be set as if the tile occupies the
−5, 15 × −5, 15 square.
The boundingBox can also be smaller than the viewBox, in case the
viewBox needs to be larger for proper grid alignment but the particular
symbol doesn't actually use the whole space.
For example, <symbol viewBox="0 0 10 10" boundingBox="5 5 10 10"/>
allocates 10×10 of space but may shrink down to 10×5 when used on the
top edge of the diagram (if no other symbols' bounding box extend above)
or 5×10 when used on the left edge of the diagram,
or 5×5 in the top-left corner.
You can also use the special value boundingBox="none" to specify that
the symbol should not influence the drawing's viewBox at all,
or boundingBox="null -5 null 10" to just affect the vertical extent (say).
Even zero-width and zero-height <symbol>s will get rendered (unless
overflow="hidden"). This can be useful for drawing grid
outlines without affecting the overall grid layout, for example.
(SVG defines that symbols are invisible if they have zero width or
height,
so SVG Tiler automatically works around this by using slightly positive
widths and heights in the output viewBox.)
The boundingBox attribute used to be called overflowBox (prior to v3).
For backward compatibility, the old name is still supported, but in either
case it can cause both overflow and underflow relative to viewBox.
Autosizing Tiles
Normally, a tile specifies its layout size by setting the
width and height attributes or by setting the viewBox attribute
of an outermost <symbol> or <svg> tag.
But there is actually a long sequence of ways that SVG Tiler tries to
figure out the width and height of the tile:
- If the tile's top-level tag is
<symbol>or<svg>, thenwidthandheightattributes of that tag take priority. Normally these are specified in SVG units (px), but you can also use CSS units that get translated topx. - The
--tw/--tile-widthand--th/--tile-heightcommand-line options define the default width and height for all tiles that don't have an explicitwidthandheight, including if you didn't use an outermost tag of<symbol>or<svg>. viewBoxis considered next, and serves as an alternative towidthandheightif you want your coordinate system to start somewhere other than (0, 0) (as you get from the methods above). If a tile's outermost tag is<symbol>or<svg>with aviewBoxattribute, then the width and height of that attribute (the third and fourth numbers) are treated as the tile's width and height.- If none of the above are found, then SVG Tiler attempts to set the
viewBoxto the bounding box of the SVG elements in the symbol. For example, the SVG<rect x="-5" y="-5" width="10" height="10"/>will automatically get wrapped by<symbol viewBox="-5 -5 10 10">...</symbol>. However, the current computation has many limitations (see the code for details), so it is recommended to specify your ownwidth/heightorviewBoxin your own<symbol>or<svg>wrapping element, especially to control the layout bounding box which may different from the contents' bounding box.
As a special non-SVG feature, a tile <symbol> can specify width="auto"
and/or height="auto" to make their instantiated width and/or height
match their column and/or row, respectively.
In this way, multiple uses of the same symbol can appear as different sizes.
See the auto-sizing example.
If you want to nonuniformly scale the tile, you may want to also adjust
the <symbol>'s preserveAspectRatio property.
Unrecognized Tiles
Any undefined tile displays as a red-on-yellow diamond with a question mark (like the Unicode replacement character �), with automatic width and height, so that it is easy to spot. SVG Tiler also lists any unrecognized tiles at the end of its output.
If evaluating a tile raises an exception (usually from code in your .js/.coffee mapping files), the tile renders as a red-on-yellow triangle with an exclamation mark (like the Unicode warning sign ⚠️). SVG Tiler also Tiler outputs the error and stack trace, and lists any erroring tiles at the end of its output.
See the auto sizing example.
<image> Processing
<image> tags in SVG (or image filenames specified by a mapping file, which
automatically get wrapped by an <image> tag) get some special additional
processing:
The default
image-renderingis the equivalent ofpixelated, for pixel art. You can also explicitly specifyimage-rendering="pixelated"orimage-rendering="optimizeSpeed". Either way, this behavior gets achieved by a combination ofimage-rendering="optimizeSpeed"(for Inkscape) andstyle="image-rendering:pixelated"(for Chrome).If you would rather have smoothed images, set
image-rendering="auto".An omitted
widthand/orheightautomatically get filled in according to the image size (scaled if exactly one ofwidthandheightis specified).The image file contents will get inlined into the SVG document (in base64), which makes the
.svgfile a stand-alone document. If you specify the--no-inlinecommand-line option, the.svgfile will load externally linked images only if you have the auxiliary image files with the correct paths.Duplicate inlined images (with the same contents and
image-rendering, but not necessarily the samex/y/width/height) will get shared via a common SVG<symbol>. This makes for efficient SVG files when multiple keys map to the same symbol, or when multiple symbols use the same component image.
Converting SVG to PDF/PNG
SVG Tiler can automatically convert all exported SVG files (and any
.svg files specified directly on the command line) into PDF and/or PNG,
if you have Inkscape v1+ installed,
via the -p/--pdf and/or -P or --png command-line options.
For example: svgtiler -p map.coffee drawings.xls
will generate both drawings_sheet.svg and drawings_sheet.pdf.
PNG conversion is intended for pixel art; see the
Tetris example.
SVG Tiler uses svgink as an efficient
interface to Inkscape's conversion from SVG to PDF or PNG.
If you just want to convert SVG files, consider using svgink directly.
If you want to do a mix of SVG tiling and SVG export with a shared pool of
Inkscape processes, you can run svgtiler with a mix of drawings and raw
.svg files that you want to convert.
Any .svg files on the command line are passed through to svgink.
svgink has some command-line options that SVG Tiler also accepts:
- svgink automatically runs
multiple Inkscape processes to exploit multicore CPUs.
You can change the number of Inkscape processes to run
via the
-j/--jobscommand-line option. For example,svgtiler -j 4 -p map.coffee drawings.xlswill run up to four Inkscape jobs at once. - svgink automatically detects whether the PDF/PNG files are newer than
the input SVG files, in which case it skips conversion.
You can override this behavior via the
-f/--forcecommand-line option. - You can change where to put converted files via the
--op/--output-pdfand--oP/--output-pngcommand-line options. In addition, SVG Tiler supports--os/--output-svgto control where to put generated SVG files, and-o/--outputto control the default place to put all generated/converted files. - You can override the generated/converted filenames for the next drawing
using the
-O/--output-stemcommand-line option. You can also specify a stem pattern like-O prefix_*_suffix, where*represents the input stem, in which case the override applies until the next-Ooption. (In particular,-O *restores the initial behavior.) - If Inkscape isn't on your PATH, you can specify its location via
-i/--inkscape.
LaTeX Text
Using the -t command-line option, you can extract all <text> from the SVG
into a LaTeX overlay file so that your text gets rendered by LaTeX during
inclusion.
For example: svgtiler -p -t map.coffee drawings.xls
will create drawings_sheet.svg, drawings_sheet.pdf, and
drawings_sheet.svg_tex. The first two files omit the text, while the third
file is the one to include in LaTeX, via \input{drawings_sheet.svg_tex}.
The same .svg_tex file will include graphics defined by .pdf
(created with -p) or .png (created with -P).
You can control the scale of the graphics component by defining
\svgwidth, \svgheight, or \svgscale before \inputting the .svg_tex.
(If more than one is specified, the first in the list takes priority.)
For example:
\def\svgwidth{\linewidth}causes the figure to span the full width\def\svgheight{5in}makes the figure 5 inches tall\def\svgscale{0.5}makes the figure 50% of its natural size (where the SVG coordinates' unit translates to 1px = 0.75bp)
If the figure files are in a different directory from your root .tex file,
you need to help the .svg_tex file find its auxiliary .pdf/.png file
via one of the following options (any one will do):
\usepackage{currfile}to enable finding the figure's directory.\usepackage{import}and\import{path/to/file/}{filename.svg_tex}instead of\import{filename.svg_tex}.\graphicspath{{path/to/file/}}(note extra braces and trailing slash).
SVG Tiler will attempt to align <text> elements according to their
text-anchor
and
alignment-baseline
properties.
It will not respect font specification; instead, include LaTeX commands
(e.g. \footnotesize or \sf) in your text.
Maketiles
SVG Tiler has a simple Makefile-like build system for keeping track of the
svgtiler command-line arguments needed to build your tiled figures.
These Maketiles generally get run whenever you run svgtiler without
any filename arguments (including directories or glob patterns), for example,
when just running svgtiler without any arguments,
or when providing just flags like svgtiler -f.
(If you ever want to skip the Maketile behavior, just provide mapping
and drawing filename arguments like you normally would.)
Several examples of Maketiles are in the examples directory.
Maketile.args
At the simplest level, you can put the command-line arguments to svgtiler
into a file called Maketile.args (or maketile.args), and then running
svgtiler without any filename arguments will automatically
append those arguments to the command line.
Thus the .args file could specify the
mappings and drawings to render, and you can still add extra options like
--pdf or --force to the actual command line as needed.
The .args file gets parsed similar to the bash shell,
so you can write one-line comments with #,
you can use
glob expressions
like **/*.asc,
and you put quotes around filenames with spaces or other special characters.
You can also write the arguments over multiple lines
(with no need to end lines with \).
Maketile.coffee/.js
The more sophisticated system is to write a Maketile.coffee or
Maketile.js file. This system offers the entire CoffeeScript or
JavaScript programming language to express complex build rules.
The file can provide build rules in one of a few ways:
export make = ...(ESM) orexports.make = ...(CommonJS)export default ...(ESM) orexports.default = ...(CommonJS)- Writing a top-level object, array, or function expression
at the end of the file (without e.g. being assigned to a variable),
which triggers an implicit
export default.
The exported rules can be one of the following types:
- A function taking two arguments — a rule name (string)
and a
Mappingobject representing the Maketile (also passed asthis). The function should directly run build steps by callingsvgtiler(); see below. The function's return value is mostly ignored, except that a return value ofnullis interpreted as "no rule with that name". If the function takes no arguments, it is treated as just defining the default build rule""(empty string). - A plain JavaScript object whose properties rule names to rules
(e.g.,
{foo: rule1, bar: rule2, '': defaultRule}). - A
Mapobject mapping rule names to rules. - An
Arrayof any of the above types, meaning to run the rules in sequence.
If you run svgtiler with no filename arguments,
the default rule name of "" (empty string) gets run.
If you run svgtiler with one or more arguments that are valid rule names
(containing no .s or glob magic patterns like * or ?),
then instead these rules get run in sequence.
(Note that directory names, filenames with extensions, and glob patterns
take priority over Maketile rule names. So avoid naming rule names that match
directory names; other conflicts are prevented by forbidding ./*/?/etc.
in rule names.)
Rule functions can run the equivalent of an svgtiler command line by calling
the svgtiler function, e.g., svgtiler('mapping.coffee *.asc').
String arguments are parsed just like .args files: whitespace separates
arguments, # indicates comments, glob patterns get expanded, and quotes
get processed.
Array arguments are treated as already parsed argument lists, so
the previous example is equivalent to svgtiler(['mapping.coffee', '*.asc'])
(where the glob pattern still gets processed, but whitespace in filenames
would not).
Instead of strings, you can also directly pass in Mapping or Drawing or
Style objects (as arguments or as part of an array argument).
An easy way to create such objects is to call svgtiler.require(),
which loads any given filename as if it was given on the command line
(without any processing of its filename).
For example, svgtiler.require('filename with spaces and *s.asc') transforms
an ASCII file into a Drawing object.
There are also tools for manually working with glob patterns:
svgtiler.glob(pattern) returns an array of filenames that match a pattern,
and svgtiler.match(filename, pattern) checks whether a filename
matches a pattern.
These helpers use node-glob and
minimatch respectively, so follow their
glob notation.
Thus you can write your own for loops and e.g.
switch depending on what additional pattern(s) the filenames match.
For example, svgtiler('mapping.coffee *.asc') can be rewritten as
svgtiler.glob('*.asc').forEach((asc) =>
svgtiler(['mapping.coffee', asc]) or
svgtiler.glob('*.asc').forEach((asc) =>
svgtiler('mapping.coffee', svgtiler.require(asc)).
No calls to svgtiler() or other side effects should be at the top level
of the Maketile. Instead, put these build steps inside a rule function.
Directories
It usually makes sense to put Maketiles in the directory where your
figures are getting built, but sometimes that's not the directory you're
working in. For example, SVG Tiler figures may be in a figures
subdirectory or your paper's main directory. In this case, you can trigger
the Maketile within the figures directory by running svgtiler figures.
You can use this shorthand also when defining Maketiles,
to recurse into subdirectories.
For example, examples/Maketile.coffee
loops over all the subdirectories within examples and runs their Maketiles.
You can thus trigger building all examples in this repository by typing
svgtiler examples at the root directory of a checkout.
API
SVG Tiler provides an API for rendering SVG directly from your JavaScript code.
On NodeJS, you can npm install svgtiler and require('svgtiler').
On a web browser, you can include a <script> tag that points to
lib/svgtiler.js, and the interface is available via window.svgtiler,
though not all features are available or fully functional in this mode.
While the full API is still in flux and the best comments are in
svgtiler.coffee, here is a subset:
new svgtiler.Mapping({map, init, preprocess, postprocess}): Create a mapping the same as a fileexportingmap/init/preprocess/postprocess(all of which are optional). In particular,mapcan be an object,Map, or function mapping keys to SVG content, just like a JavaScript mapping file.new svgtiler.Drawing(keys): Create a drawing with the specifiedkeys, which is anArrayofArrayofStrings (or other objects), wherekeys[i][j]represents the key in the cell at rowiand columnj.new svgtiler.Style(css): Create CSS styling with the specifiedcsscontent (aString). Or usenew svgtiler.StylusStyle(styl)to parse the string as Stylus.new svgtiler.Render(drawing, [settings]): Create a rendering job for converting the specifieddrawingto SVG.- Put your mappings in
settings.mappings, which can be aMappingobject, a valid argument tonew Mapping, anArrayof the above, or aMappingsobject (a special type ofArray). - Put additional CSS styling in
settings.styles, which can be aStyleobject, a valid argument tonew Style, anArrayof the above, or aStylesobject (a special type ofArray).
- Put your mappings in
svgtiler.require(filename, [settings], [dirname])loads the specified file as if it was on thesvgtilercommand line, producing aMapping,Drawing,Drawings,Style,Args, orSVGFileaccording to the extension. The filename is relative todirname, which should (via a Babel plugin) default to the script callingsvgtiler.require. For example,svgtiler.require('./map.coffee')is equivalent tonew Mapping(require('./map.coffee')). Node onlysvgtiler.renderDOM(elts, settings): Convert drawings embedded in the DOM via elements matchingelts(which can be a query selector string like'.svgtiler', or a DOM element, or an iterable of DOM elements). Web only- Put your mappings in
settings.mappings, which can be aMappingobject, a valid argument tonew Mapping, anArrayof the above, or aMappingsobject (a special type ofArray). - Put additional CSS styling in
settings.styles, which can be aStyleobject, a valid argument tonew Style, anArrayof the above, or aStylesobject (a special type ofArray). - Each drawing can have a
data-filenameattribute to define its name and extension, which determines its format; or you can setsettingsto an object specifying a defaultfilename. The default filename is"drawing.asc", which implies ASCII art. - By default, the rendered SVG replaces the original drawing element, but
the element can specify
data-keep-parent="true"(orsettingscan specifykeepParent: true) to make it a sole child element instead; or the element can specifydata-keep-class="true"(orsettingscan specifykeepClass: true) for the rendered SVG to keep the sameclassattribute as the drawing element.
- Put your mappings in
svgtiler.getRender(): Returns the currentRenderobject for the current rendering job, which in particular hasdrawing,mappings, andstylesattributes.svgtiler.getContext(): Returns the currentContextobject for the currently rendering tile.svgtiler.version: SVG Tiler version number as a string, or'(web)'in the browser.svgtiler.needVersion(constraints): Require a specified range of version numbers (or throw an error), e.g.svgtiler.needVersion('3.x')orsvgtiler.needVersion('>=3.0 <3.1'). Put this in yourMaketile.jsorMaketile.coffee.
Drawing Class
An svgtiler.Drawing object represents a drawing as a table of keys.
Typically, each key is a string, although this is not required.
(For example, preprocess steps might want to convert strings
into other objects.)
A Drawing object has the following properties:
keys:ArrayofArrayof keys (Strings or other objects), wherekeys[i][j]represents the key in the cell at rowiand columnj.
A Drawing object has the following methods:
get(j, i): Get the key in the cell in rowiand columnj. Note that the order is flipped, to correspond tox/yorder. Returnsundefinedif out of bounds (or the key isundefined).at(j, i): Likeget, but treat negative numbers as relative to the bottom/right edge of the drawing, similar toArray.prototype.at. For example,at(-1, -1)accesses the bottom-right cell.set(j, i, key): Set thekeyin the cell in rowiand columnj.renderDOM(settings): Render drawing to SVG DOM (native in browser, xmldom in Node). Append the returned DOM to the document to render it. Shorthand fornew Render(drawing, settings).makeDOM().
Render Class
An svgtiler.Render object represents a rendering job: converting a
drawing with mappings and styles into an SVG and possibly other formats.
It is passed as the single argument (and this) to any user-defined
preprocess and postprocess functions exported from mapping files.
You can also get the currently rendering Render object
(e.g. during preprocess or postprocess stages) via svgtiler.getRender().
A Render object has the following properties:
drawing:Drawingobject of what's being rendered.mappings:Mappingsobject containing all the applicableMappings.styles:Styleobject containing all the includedStyles.xMin,xMax,yMin,yMax: Current bounding box of rendered content.
A Render object has the following methods:
forEach(callback): Callscallback(context)once per cell of the drawing, withcontextset to aContextobject (also passed asthis) includingi(row number),j(column number), andkeyattributes. This is a convenient way to iterate through a drawing, while being able to use all the context methods likeneighborandset.context(i, j): Create newContextobject at specified coordinates. Equivalent tonew svgtiler.Context(render, i, j).add(content): Add SVG content to the rendering. Equivalent tosvgtiler.add(content).id(prefix): Generate a unique-to-this-renderidstarting withprefix. The current algorithm usesprefix, thenprefix_v0, thenprefix_v1, etc. This can be useful for assigning anidto some SVG content, and then referring to it in the same rendering (e.g. duplicating via<use>). Normally you'd usesvgtiler.id(prefix)which is equivalent tocurrentRender().id(prefix)within a rendering context, and generates globally unique ids outside of a rendering context.def(tag): Adds SVG content to<defs>in the output (except when the tag doesn't need to be wrapped in<defs>, such as<marker>,<filter>,<gradient>), and assigns it a unique ID. Returns anSVGContentobjectdefwith propertydef.idcontaining a uniqueidstring, and helper methodsdef.url()anddef.hash()generatingurl(#id)(as you'd use markers or gradients) and#id(as you'd use in<use>) respectively. Normally you'd usesvgtiler.def(tag)which is equivalent tocurrentRender().def(tag)within a rendering context, and generates globally defs outside of a rendering context.background(color): Set the background color for this render. (Can later be overwritten during the rendering process.)makeDOM(): Render drawing to SVG DOM (native in browser, xmldom in Node). Append the returned DOM to the document to render it.
Examples
This repository contains several examples to help you learn SVG Tiler by inspection. Some examples aim to capture real-world games, while others are more demonstrations of particular SVG Tiler features.
Video/board games:
Demos:
- Grid graph Hamiltonicity
- Polyomino outline drawing and JSX
- Auto width/height
- Unicode
- Animations
- Escaping tests
Research using SVG Tiler:
The following research papers use SVG Tiler to generate (some of their) figures. Open an issue or pull request to add yours!
- “Who witnesses The Witness? Finding witnesses in The Witness is hard and sometimes impossible” — The Witness examples
- “Losing at Checkers is Hard”
- “Path Puzzles: Discrete Tomography with a Path Constraint is Hard”
- “Tetris is NP-hard even with O(1) Columns”
- “PSPACE-completeness of Pulling Blocks to Reach a Goal”
- “Tatamibari is NP-complete” — GitHub repo with SVG Tiler inputs
- “Recursed is not Recursive: A Jarring Result” — GitHub repo with inputs (generates both SVG Tiler inputs and actual game levels)
- “1 × 1 Rush Hour with Fixed Blocks is PSPACE-complete”
- “Complexity of Retrograde and Helpmate Chess Problems: Even Cooperative Chess is Hard” (see Chess example)
- “Cube Folding Puzzles”
- “Folding Small Polyominoes into a Unit Cube”
- “Yin-Yang Puzzles are NP-complete” — GitHub repo with SVG Tiler inputs; the associated talk directly embeds SVG Tiler into reveal.js slides via the API
Installation
After installing Node, you can install (or update) this tool via
npm install -g svgtiler@latestSVG Tiler requires Node v14+.
Command-Line Usage
The command-line arguments consist mostly of mapping and/or drawing files. The files and other arguments are processed in order, so for example a drawing can use all mapping files specified before it on the command line. If the same symbol is defined by multiple mapping files, later mappings take precedence (overwriting previous mappings).
Here is the output of svgtiler --help:
Usage: svgtiler (...options and filenames...)
Optional arguments:
-h / --help Show this help message and exit.
-p / --pdf Convert output SVG files to PDF via Inkscape
-P / --png Convert output SVG files to PNG via Inkscape
-t / --tex Move <text> from SVG to accompanying LaTeX file.svg_tex
-f / --force Force SVG/TeX/PDF/PNG creation even if deps older
-v / --verbose Log behind-the-scenes action to aid debugging
-o DIR / --output DIR Write all output files to directory DIR
-O STEM / --output-stem STEM Write next output to STEM.{svg,svg_tex,pdf,png}
(STEM can use * to refer to input stem)
--os DIR / --output-svg DIR Write all .svg files to directory DIR
--op DIR / --output-pdf DIR Write all .pdf files to directory DIR
--oP DIR / --output-png DIR Write all .png files to directory DIR
--ot DIR / --output-tex DIR Write all .svg_tex files to directory DIR
--clean Delete SVG/TeX/PDF/PNG files that would be generated
-i PATH / --inkscape PATH Specify PATH to Inkscape binary
-j N / --jobs N Run up to N Inkscape jobs in parallel
--maketile GLOB Custom Maketile file or glob pattern
-s KEY=VALUE / --share KEY=VALUE Set share.KEY to VALUE (undefined if no =)
-m / --margin Don't delete blank extreme rows/columns
--uneven Don't make all rows have same length by padding with ''
--hidden Process hidden sheets within spreadsheet files
--bg BG / --background BG Set background fill color to BG
--tw TILE_WIDTH / --tile-width TILE_WIDTH
Force all symbol tiles to have specified width
--th TILE_HEIGHT / --tile-height TILE_HEIGHT
Force all symbol tiles to have specified height
--no-inline Don't inline <image>s into output SVG
--no-overflow Don't default <symbol> overflow to "visible"
--no-sanitize Don't sanitize PDF output by blanking out /CreationDate
--use-href Use href attribute instead of xlink:href attribute
--use-data Add data-{key,i,j,k} attributes to <use> elements
( Remember settings, mappings, styles, and share values
) Restore last remembered settings/mappings/styles/share
Filename arguments: (mappings and styles before relevant drawings!)
*.txt ASCII mapping file
Each line is <symbol-name><space><raw SVG or filename.svg>
*.js JavaScript mapping file (including JSX notation)
Object mapping symbol names to SYMBOL e.g. {dot: 'dot.svg'}
*.jsx JavaScript mapping file (including JSX notation)
Object mapping symbol names to SYMBOL e.g. {dot: 'dot.svg'}
*.coffee CoffeeScript mapping file (including JSX notation)
Object mapping symbol names to SYMBOL e.g. dot: 'dot.svg'
*.cjsx CoffeeScript mapping file (including JSX notation)
Object mapping symbol names to SYMBOL e.g. dot: 'dot.svg'
*.asc ASCII drawing (one character per symbol)
*.ssv Space-delimiter drawing (one word per symbol)
*.csv Comma-separated drawing (spreadsheet export)
*.tsv Tab-separated drawing (spreadsheet export)
*.xlsx Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.xlsm Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.xlsb Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.xls Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.ods Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.fods Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.dif Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.prn Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.dbf Spreadsheet drawing(s) (Excel/OpenDocument/Lotus/dBASE)
*.css CSS style file
*.styl Stylus style file (https://stylus-lang.com/)
*.svg SVG file (convert to PDF/PNG without any tiling)
SYMBOL specifiers: (omit the quotes in anything except .js and .coffee files)
'filename.svg': load SVG from specifies file
'filename.png': include PNG image from specified file
'filename.jpg': include JPEG image from specified file
'<svg>...</svg>': raw SVG
-> ...@key...: function computing SVG, with `this` bound to Context with
`key` (symbol name), `i` and `j` (y and x coordinates),
`filename` (drawing filename), `subname` (subsheet name),
and supporting `neighbor`/`includes`/`row`/`column` methodsHistory
This take on SVG Tiler was written by Erik Demaine, in discussions with Jeffrey Bosboom and others, with the intent of subsuming his original SVG Tiler. In particular, the .txt mapping format and .asc drawing format here are nearly identical to the formats supported by the original.
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
