           for row in data
             row.shift()
         ## Right margin
-        j = Math.max (row.length for row in data)...
+        j = Math.max ...(row.length for row in data)
         while j >= 0 and allBlank (row[j] for row in data)
           for row in data
             if j < row.length
               row.pop()
           j--
-    @data = data
-  renderDOM: (settings = @settings) ->
-    new Render @, settings
-    .makeDOM()
-  render: (settings = @settings) ->
-    ## Writes SVG and optionally TeX file.
-    ## Returns output SVG filename.
-    r = new Render @, settings
-    filename = r.writeSVG()
-    r.writeTeX() if getSetting settings, 'texText'
-    filename
-
-class ASCIIDrawing extends Drawing
+    super data
+
+class ASCIIDrawing extends AutoDrawing
   @title: "ASCII drawing (one character per tile)"
   parse: (data) ->
-    @load(
+    super(
       for line in splitIntoLines data
         graphemeSplitter.splitGraphemes line
     )
 
-class DSVDrawing extends Drawing
+class DSVDrawing extends AutoDrawing
+  ###
+  Abstract base class for all Delimiter-Separator Value (DSV) drawings.
+  Each subclass must define `@delimiter` class property.
+  ###
   parse: (data) ->
     ## Remove trailing newline / final blank line.
     if data[-2..] == '\r\n'

###

class Drawing extends Input
  load: (data) ->
    ## Turn strings into arrays, and turn numbers (e.g. from XLSX) into strings.
    data = for row in data
             for cell in row
               String cell
    unless @getSetting 'keepMargins'
      ## Top margin
      while data.length > 0 and allBlank data[0]

    ## `@value` can be a string or Preact VDOM.
    super()
    unless @value?
      throw new SVGTilerError "Attempt to create tile '#{@key}' without content"

  makeSVG: ->
    return if @svg?
