penrose / penrose

Create beautiful diagrams just by typing notation in plain text.
https://penrose.cs.cmu.edu
MIT License
6.76k stars 283 forks source link

Provide more structured/editable SVG output #786

Open keenancrane opened 2 years ago

keenancrane commented 2 years ago

Currently our SVG output is quite a mess:

We can do a lot better! (See especially the final section below for the type of output we might want to aim for.)

There are two distinct ways we can improve it:

  1. (Less important) Improve formatting of the SVG source code, so that it's more human-readable/editable.
  2. (More important) Improve the structure of the SVG file, so that it can be more easily edited in external programs like Inkscape/Illustrator.

The first item is more or less a matter of running the output through some XML formatting package like this one. As for (2), there are several ways to improve the document structure:

These items are elaborated on below.

Clean up the garbage

Currently we emit a lot of SVG code that is unused. For instance, we repeatedly emit code for a Gaussian blur effect applied to (I think?) a path or arrowhead—see below. The idea was probably that this code needs to be available in case the Style program needs it. But we should definitely emit this code only on-demand, i.e., if it's actually used, rather than by default.

    <feOffset result="offOut" in="SourceAlpha" dx="5" dy="5"></feOffset>
       <feGaussianBlur result="blurOut" in="offOut" stdDeviation="4"></feGaussianBlur>
       <feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend>
       <feComponentTransfer>
         <feFuncA type="linear" slope="0.5"></feFuncA>
       </feComponentTransfer>
       <feMerge>
         <feMergeNode></feMergeNode>
         <feMergeNode in="SourceGraphic"></feMergeNode>
       </feMerge>

Eliminate redundant code

Things like markers are repeatedly written into the SVG file for instance; we should write these once, and then use them by reference (i.e., via instancing), which may still require that we write a per-instance transformation.

Use CSS styles

Currently many shape attributes are repeatedly defined inline, in the shape tags themselves. SVG allows one to include a CSS block at the beginning that defines a collection of styles, then refer to these styles from individual shape tags. Consider for example this illustration, and corresponding SVG code:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 1920 1080" style="enable-background:new 0 0 1920 1080;" xml:space="preserve">
<style type="text/css">
    .st0{fill:#FF0000;}
    .st1{fill:#0019FF;}
</style>
<circle class="st0" cx="737.8717651" cy="423.7435913" r="105.1282043"/>
<circle class="st0" cx="995.5640869" cy="251.9487152" r="105.1282043"/>
<circle class="st0" cx="1253.2564697" cy="341.6923218" r="105.1282043"/>
<circle class="st1" cx="984.0256348" cy="601.9487305" r="105.1282043"/>
<circle class="st1" cx="1253.2564697" cy="625.0256348" r="105.1282043"/>
</svg>

Re-using styles becomes more valuable as the styles get more attributes (color, stroke color, stroke width, mitering, etc.).

Moreover, encapsulating styles in CSS enables users to modify many style attributes without recompiling/re-running Penrose.. For instance, in the example above the color of many circles can be updated by changing just a single color string. This ability makes Penrose diagrams even more useful, e.g., in scenarios where re-running Penrose is impossible (say, due to lack of support on a given platform) or infeasible (say, due to real time constraints).

Hierarchically group elements / Give elements meaningful names.

Related to #783, our current SVGs are just a linear dump of shapes, whereas SVG allows hierarchical grouping of shapes via the <g> tag.. Both automatically organizing shapes into groups (e.g., grouping shapes that come from a common Style rule) and/or giving Style programmers the ability to specify grouping (a la #783) would go a long way toward improving SVG structure.

Likewise, adding meaningful names (derived from things like Domain names and notation, Substance identifiers, Style GPI names, and so on) would make it much easier to inspect and edit files either at the source code level, or in an external tool.

A good example of both nice grouping and nice naming is this file generated by a little home-brewed utility called obj2svg (which converts a 3D Wavefront OBJ mesh file into a 2D SVG drawing):

<svg>
   <style type="text/css">
      .edge{fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:10;}
      .dualEdge{fill:none;stroke:#aaaaff;stroke-width:1;stroke-miterlimit:10;}
      .face{fill:#1b1f8a;opacity:0.2;stroke:none;}
      .vertex{fill:#000000;stroke:none;}
      .dualVertex{fill:#ffffff;stroke:#000000;}
      .vector{fill:none;stroke:#ff6600;stroke-width:1;stroke-miterlimit:10;}
      .boundaryCurve{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
      .parameterCurve{fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:10;}
      .latitude{fill:none;stroke:#0000ff;stroke-width:1;stroke-miterlimit:10;}
      .longitude{fill:none;stroke:#ff0000;stroke-width:1;stroke-miterlimit:10;}
      .circumcircle{fill:none;stroke:#aaaaff;stroke-width:.5;stroke-miterlimit:10;}
      .silhouetteEdge{fill:none;stroke:#333333;stroke-width:1.5;stroke-miterlimit:10;}
   </style>

   <g id="faces">
      <polygon points="-312.638,-493.803 1422.79,-129.975 -740.611,-955.815 " style="fill:#bebfdd;" />
      <polygon points="-510.61,1358.88 -312.638,-493.803 -740.611,-955.815 " style="fill:#1b1f8a;" />
      <polygon points="-312.638,-493.803 -510.61,1358.88 1422.79,-129.975 " style="fill:#6164ae;" />
   </g>
   <g id="edges">
      <g id="edgesFront">
         <line class="edge" x1="-740.611" y1="-955.815" x2="-510.61" y2="1358.88" />
         <line class="edge" x1="1422.79" y1="-129.975" x2="-740.611" y2="-955.815" />
         <line class="edge" x1="1422.79" y1="-129.975" x2="-510.61" y2="1358.88" />
      </g>
      <g id="edgesBack">
         <line class="edge" x1="-312.638" y1="-493.803" x2="-740.611" y2="-955.815" />
         <line class="edge" x1="-740.611" y1="-955.815" x2="-510.61" y2="1358.88" />
         <line class="edge" x1="-510.61" y1="1358.88" x2="-312.638" y2="-493.803" />
         <line class="edge" x1="1422.79" y1="-129.975" x2="-740.611" y2="-955.815" />
         <line class="edge" x1="-312.638" y1="-493.803" x2="1422.79" y2="-129.975" />
         <line class="edge" x1="1422.79" y1="-129.975" x2="-510.61" y2="1358.88" />
      </g>
   </g>
   <g id="vertices">
      <g id="verticesFront">
      </g>
      <g id="verticesBack">
         <circle class="vertex" cx="-312.638" cy="-493.803" r="10" />
         <circle class="vertex" cx="1422.79" cy="-129.975" r="10" />
         <circle class="vertex" cx="-740.611" cy="-955.815" r="10" />
         <circle class="vertex" cx="-510.61" cy="1358.88" r="10" />
      </g>
   </g>
   <g id="boundaryCurves">
   </g>
</svg>

Here's what this file looks like when you load it up into Illustrator:

Notice in particular how elements are nicely grouped and nicely named—making it easy to perform high-level edits. Here for example I selected the set of front-facing edges and colored them pink (originally gray in the SVG file).

maxkrieger commented 2 years ago

Related: #541

maxkrieger commented 2 years ago

For the formatting/prettyprinting, could walk the dom tree with https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker

wodeni commented 1 year ago

Related: #1129

samestep commented 1 year ago

Also, as of #864, we use Prettier to format the SVG files.

maxkrieger commented 1 year ago

@samestep that's not bundled right, just the automator? because we really just need indentation and newlines, definitely shouldn't put prettier in the browser.

samestep commented 1 year ago

@maxkrieger Ah yes, you're right.

wodeni commented 1 year ago

svgo is a library that optimizes SVG trees. Although it claims to be node-based, looks like other made it work in the browser: https://github.com/jakearchibald/svgomg.

wodeni commented 1 year ago

One other note on CSS classes: when you have multiple images on the page, the CSS classes like st0 can conflict with other images. See #1307.