From b42d9bd0103819dee40076e9d70540c41ee2def8 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Thu, 29 Sep 2022 13:01:17 -0400 Subject: [PATCH] svgtiler.add in place of returning content from afterRender (fix #94) * Return value from `svgtiler.afterRender` content is now ignored. * Instead, use `render.add` or `svgtiler.add` to add content explicitly. * Can now add content during `svgtiler.beforeRender`. * Suitable for adding metadata (#94), given that we no longer wrap --- README.md | 22 +++-- examples/chess/board-immortal.svg | 1 + examples/chess/board-init.svg | 1 + examples/chess/board-kasparov-immortal.svg | 1 + examples/chess/graph-board-immortal.svg | 5 +- examples/chess/graph-board-init.svg | 1 + .../chess/graph-board-kasparov-immortal.svg | 5 +- examples/chess/graph.coffee | 2 +- examples/chess/map.coffee | 7 +- examples/chess/map.jsx | 9 ++- src/svgtiler.coffee | 80 ++++++++++++------- 11 files changed, 88 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 932b0a3..0c9d75d 100644 --- a/README.md +++ b/README.md @@ -337,21 +337,24 @@ The top-level code of your .js or .coffee mapping file can also call: which in particular has `drawing`, `mappings`, and `styles` attributes. You can even modify the drawing's `keys` at this stage, by modifying `render.drawing.keys`. + You can also add SVG content via `render.add` or `svgtiler.add`, + e.g., add metadata like `svgtiler.add(My drawing)` + (see below for more details). * `svgtiler.afterRender(callback)` to schedule calling `callback(render)` - after rendering each drawing. In particular, - to render an overlay or underlay, return the content to render - as a string or Preact VDOM (ideally in an `` wrapper). + after rendering each drawing. + During the callback, `render` has properties about the rendering's + bounding box: `xMin`, `xMax`, `yMin`, `yMax`, `width`, `height`. + You can add SVG content (overlay/underlay) via `render.add` or + `svgtiler.add`, which you can pass a string or Preact Virtual DOM. Specify a [`z-index`](#z-index-stacking-order-of-tiles) to control the stacking order relative to other symbols or overlays/underlays. Specify [`overflowBox`](#overflow-and-bounding-box) to increase the overall size of the rendered drawing. - During the callback, `render` has properties about the rendering's - bounding box: `xMin`, `xMax`, `yMin`, `yMax`, `width`, `height`. * `svgtiler.background(fillColor)` to set the default background color for the SVG drawing (implemented via a `` underneath the bounding box). Equivalent to - `svgtiler.afterRender((render) => `. + `svgtiler.afterRender((render) => render.add())`. You can also call `svgtiler.background` within a tile definition function or a `beforeRender`/`afterRender` callback to set the background dynamically, or set the global default via the `--bg`/`--background` command-line option. @@ -470,6 +473,11 @@ polygon.purple See the [animation example](examples/anim) for sample usage of a .css or .styl file. +If you'd rather generate a ` \ No newline at end of file diff --git a/examples/chess/graph-board-init.svg b/examples/chess/graph-board-init.svg index 3c36450..5ee0e6d 100644 --- a/examples/chess/graph-board-init.svg +++ b/examples/chess/graph-board-init.svg @@ -128,6 +128,7 @@ + Chess diagram board-init.asc diff --git a/examples/chess/graph-board-kasparov-immortal.svg b/examples/chess/graph-board-kasparov-immortal.svg index b3d87f0..6ed75c9 100644 --- a/examples/chess/graph-board-kasparov-immortal.svg +++ b/examples/chess/graph-board-kasparov-immortal.svg @@ -128,6 +128,7 @@ + Chess diagram board-kasparov-immortal.asc @@ -186,10 +187,10 @@ - + - + \ No newline at end of file diff --git a/examples/chess/graph.coffee b/examples/chess/graph.coffee index c645556..174969a 100644 --- a/examples/chess/graph.coffee +++ b/examples/chess/graph.coffee @@ -71,4 +71,4 @@ svgtiler.afterRender (render) -> stroke={graphColor} stroke-width="10" stroke-linecap="round" /> - {edges} + render.add {edges} diff --git a/examples/chess/map.coffee b/examples/chess/map.coffee index bb5018d..a82c376 100644 --- a/examples/chess/map.coffee +++ b/examples/chess/map.coffee @@ -20,10 +20,13 @@ read = (filename) -> # {dom.props.children} # +svgtiler.beforeRender ({drawing}) -> + svgtiler.add Chess diagram {drawing.filename} + svgtiler.background 'white' ## Equivalent: -#svgtiler.afterRender (render) -> -# render.add \ +# [ diff --git a/examples/chess/map.jsx b/examples/chess/map.jsx index 663afeb..a643e51 100644 --- a/examples/chess/map.jsx +++ b/examples/chess/map.jsx @@ -11,12 +11,15 @@ function read(filename) { return dom.props.children; } +svgtiler.beforeRender(({drawing}) => svgtiler.add( + Chess diagram {drawing.filename})); + svgtiler.background('white'); // Equivalent: -//svgtiler.afterRender((render) => -// render.add( +// -//); +//)); (key, context) => { // Map blanks to empty string diff --git a/src/svgtiler.coffee b/src/svgtiler.coffee index da87a16..9f7d412 100755 --- a/src/svgtiler.coffee +++ b/src/svgtiler.coffee @@ -230,6 +230,17 @@ parseNum = (x) -> else parsed +## Allow Infinity, \infty, Inf with optional sign prefix +infinityRegExp = /^\s*([+-]?)\\?infi?n?i?t?y?\s*$/i +parseNumOrInfinity = (x) -> + if (match = infinityRegExp.exec x)? + if match[1] == '-' + -Infinity + else + Infinity + else + parseNum x + ## Conversion from arbitrary unit to px (SVG units), ## from https://www.w3.org/TR/css-values-3/#absolute-lengths units = @@ -364,12 +375,13 @@ getHref = (node) -> extractZIndex = (node) -> ## Check whether DOM node has a specified z-index, defaulting to zero. + ## Special values of Infinity or -Infinity are allowed. ## Also remove z-index attribute, so output is valid SVG. ## Note that z-index must be an integer. ## 1. https://www.w3.org/Graphics/SVG/WG/wiki/Proposals/z-index suggests ## a z-index="..." attribute. Check for this first. ## 2. Look for style="z-index:..." as in HTML. - z = parseNum attributeOrStyle node, 'z-index' + z = parseNumOrInfinity attributeOrStyle node, 'z-index' removeAttributeOrStyle node, 'z-index' z ? 0 @@ -545,7 +557,7 @@ getContextString = -> else if currentRender? "render of '#{currentRender.drawing.filename}'" else - '' + 'unknown context' getSettings = -> ### @@ -1296,12 +1308,12 @@ class Mappings extends ArrayWrapper value = @[i].lookup key, context return value if value? undefined - doBeforeRender: (render, callback) -> + doBeforeRender: (render, onResult) -> for mapping in @ - mapping.doBeforeRender render, callback - doAfterRender: (render, callback) -> + mapping.doBeforeRender render, onResult + doAfterRender: (render, onResult) -> for mapping in @ - mapping.doAfterRender render, callback + mapping.doAfterRender render, onResult blankCells = new Set [ '' @@ -1555,6 +1567,9 @@ class Render extends HasSettings @mappings = new Mappings @getSetting 'mappings' @styles = new Styles @getSetting 'styles' @defs = [] + ## Accumulated rendering, in case add() called before makeDOM(): + @xMin = @yMin = @xMax = @yMax = 0 + @layers = {} hrefAttr: -> hrefAttr @settings id: (key) -> #, noEscape) -> ## Generate unique ID starting with an escaped version of `key`. @@ -1598,6 +1613,23 @@ class Render extends HasSettings ## Returns current background fill. @backgroundFill = fill unless fill == undefined @backgroundFill + add: (content, prepend) -> + unless content instanceof SVGContent + content = new SVGContent \ + "svgtiler.add content from #{getContextString()}", content, @settings + dom = content.makeDOM() + return if content.isEmpty + if (box = content.overflowBox ? content.viewBox)? + @xMin = Math.min @xMin, box[0] + @yMin = Math.min @yMin, box[1] + @xMax = Math.max @xMax, box[0] + box[2] + @yMax = Math.max @yMax, box[1] + box[3] + @updateSize() + @layers[content.zIndex] ?= [] + if prepend + @layers[content.zIndex].unshift dom.documentElement + else + @layers[content.zIndex].push dom.documentElement updateSize: -> @width = @xMax - @xMin @height = @yMax - @yMin @@ -1666,11 +1698,8 @@ class Render extends HasSettings console.warn "Errors during tiles:", errored.join ', ' ## Lay out the tiles in the drawing via SVG . - @xMin = @yMin = @xMax = @yMax = 0 - @layers = {} y = 0 colWidths = {} - @coords = [] for row, i in @tiles rowHeight = 0 for tiles in row @@ -1740,28 +1769,13 @@ class Render extends HasSettings ## afterRender callbacks, which may use (and update) @width/@height @updateSize() - @mappings.doAfterRender @, (out, mapping) => - return unless out - overlay = new SVGContent "afterRender content from '#{mapping.filename}'", - out, @settings - dom = overlay.makeDOM() - return if overlay.isEmpty - if (box = overlay.overflowBox ? overlay.viewBox)? - @xMin = Math.min @xMin, box[0] - @yMin = Math.min @yMin, box[1] - @xMax = Math.max @xMax, box[0] + box[2] - @yMax = Math.max @yMax, box[1] + box[3] - @updateSize() - @layers[overlay.zIndex] ?= [] - @layers[overlay.zIndex].push dom.documentElement + @mappings.doAfterRender @ ## Background fill, now that size has settled if (@backgroundFill ?= @getSetting 'background')? - @layers['-Infinity'] ?= [] - @layers['-Infinity'].unshift (new SVGContent "background", - """""", - @settings - ).makeDOM() + @add new SVGContent("background", + """""", + @settings), true # prepend ## Check for global used by the symbols so far. usedIds = new Set @@ -2047,6 +2061,12 @@ globalBackground = (fill) -> else throw new SVGTilerError "svgtiler.background called outside of render of mapping context" +globalAdd = (content, ...opts) -> + if currentRender? + currentRender.add content, ...opts + else + throw new SVGTilerError "svgtiler.add called outside of render context" + class Context constructor: (@render, i, j) -> @drawing = @render.drawing @@ -2373,7 +2393,7 @@ main = (args = process.argv[2..]) -> svgtiler = { SVGContent, SVGWrapped, SVGSymbol, unrecognizedSymbol, Mapping, Mappings, ASCIIMapping, JSMapping, CoffeeMapping, - getMapping, runWithMapping, + getMapping, runWithMapping, static: wrapStatic, Drawing, AutoDrawing, ASCIIDrawing, DSVDrawing, SSVDrawing, CSVDrawing, TSVDrawing, Drawings, XLSXDrawings, @@ -2382,7 +2402,7 @@ svgtiler = { extensionMap, Input, DummyInput, ArrayWrapper, Render, getRender, runWithRender, beforeRender, afterRender, id: globalId, def: globalDef, background: globalBackground, - static: wrapStatic, + add: globalAdd, Context, getContext, getContextString, runWithContext, SVGTilerError, SVGNS, XLINKNS, escapeId, main, renderDOM, convert, require: inputRequire,