Skip to content

Vector (SVG) export, for figures that survive a journal #3

Description

@bshepp

PNG is the only image export. The audience for this is a mathematics mailing list, and a raster screenshot is the wrong artefact for a paper: it cannot be rescaled, the hairline strokes this site defaults to alias badly at print resolution, and a reviewer cannot zoom into it.

Why it is not a one-liner

Every visualizer draws straight to a Canvas 2D context and keeps no scene graph, so there is nothing to serialise. viz.render(seq, params, ctx, size) is the whole contract, and the drawing exists only as the side effects of those calls.

The workable approach is a recording context: an object implementing the Canvas 2D subset the visualizers actually use, which records the calls and emits SVG. Passed to viz.render in place of the real context, no visualizer changes at all.

The subset that has to be implemented

Counted across src/viz/ and src/ui/, the complete surface in use today is:

Methods: beginPath, moveTo, lineTo, closePath, rect, arc, stroke, fill, fillRect, strokeRect, fillText, measureText, save, restore, translate, scale, setTransform, setLineDash, clip, drawImage

Properties: fillStyle, strokeStyle, font, lineWidth, globalAlpha, textBaseline, lineJoin, lineCap

That is the entire list, and it is small. measureText is the awkward one - it has to return a real width, and there is no text metrics engine off-canvas. Simplest honest answer is to keep a hidden real canvas purely for measurement.

drawImage is used once and may be avoidable in the export path.

Things that will silently produce a wrong file

Worth listing because an SVG export fails quietly - it produces a file, and the file looks plausible until someone opens it at A4:

  • The viewport transform. Pan and zoom are applied around viz.render and inverted for hit-testing. The exporter has to bake them or deliberately drop them.
  • Per-panel palettes. withCanvas swaps a module-level palette around each panel, so colours depend on when a call is made, not just on what it draws.
  • Clipping. Side-by-side clips each panel to its half; SVG needs a real clipPath, and getting it wrong lets one panel bleed over the other.
  • Split viewports. The two panels can now sit at different zoom and pan.
  • devicePixelRatio. The canvas multiplies by dpr; an SVG must not.
  • The overlays. Panel captions, endpoint marks and the pinned marker are drawn after the viewport is restored, so they are in a different coordinate space from the drawing.

Verification

Not "a file downloads". Render the same view to canvas and to SVG at identical size, rasterise the SVG back, and compare. Anything else will miss a transform being applied twice or not at all, which is exactly the failure this invites.

Scope notes

  • Text: keep as <text> rather than outlines, so the sequence name and captions stay selectable and searchable.
  • Strokes should stay strokes, not be converted to filled outlines, or the file balloons and line width stops being editable.
  • The existing PNG attribution band should carry over - the OEIS licence line has to be in the exported file, not only on the page.
  • A 2001-term b-file digit walk is 418,487 points. An SVG with that many path segments is a very large file; worth measuring before deciding whether to cap or to warn.

Unrelated to #1 (3D views) and #2 (responsive layout).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions