Skip to content
105 changes: 105 additions & 0 deletions contributor_docs/p5.svg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!-- An overview of the goals of p5's native SVG export, import, and vector shape recording system. -->

# p5.svg Overview

`p5.svg` is an experimental native vector graphics system provided in p5.js starting from version 2.4. It allows you to create, import, and export vector graphics directly in p5.js without needing external addons. With `p5.svg`, artists, designers, and educators can generate graphics that scale smoothly for high-DPI displays, print, pen plotters, CNC routers, laser cutters, and embroidery machines using familiar p5.js drawing functions.

The specifics of these APIs are currently experimental and subject to evolution based on community feedback. A valuable contribution to the project is testing these APIs, reporting edge cases, and sharing feedback on usability and performance!

## Project Goals

`p5.svg` addresses several key goals:

- **Vector Graphics**: `p5.svg` allows p5.js drawings to be saved as vector graphics that can scale to different sizes without losing sharpness.
- **Familiar p5 Drawing Workflow**: To create SVG output, the only thing you need to do differently is use `createShape()` and `buildShape()`. Inside these functions, use the standard drawing API (`rect`, `circle`, `path`, `fill`, `stroke`, `translate`, `rotate`, etc.) as you usually would.
- **Import and Export SVG Files**: `p5.svg` allows you to load existing SVG files with `loadSVG()` and save p5.js drawings as SVG files using `saveSVG()` or `getSVG()`.

## What Needs Feedback

The main ways you can help develop `p5.svg` are:

- **Shape API**: Test vector shape functions like `createSVG()`, `loadSVG()`, `buildShape()`, `createShape()`, `shape()`, `getSVG()`, and `saveSVG()`. We want feedback on whether the API feels familiar to someone learning p5.js, whether the functions are easy to understand for beginners, and whether they are easy to use when teaching p5.js. Let us know if any choices feel confusing or tricky for students, or if anything feels inconsistent with the rest of p5.js.
- **SVG Path Parsing**: Test importing SVGs generated by vector design tools (Adobe Illustrator, Inkscape, Figma). Report any unsupported path commands (`M`, `L`, `H`, `V`, `C`, `S`, `Q`, `T`, `A`, `Z`) or malformed elements.
- **Styling and CSS Properties**: Help verify that stroke weights, colors, fill rules, opacities, gradients, and transform stacks behave consistently across export and import pipelines.
- **Performance & Memory**: Test large or complex SVG scenes to help identify bottlenecks in DOM parsing, AST construction, or XML string generation.

## Technical Overview

Behind the scenes, `p5.svg` operates in three main layers:

1. **Shape Recording (`src/shape/svg/svg_recorder.js`)**:
- Intercepts 2D drawing calls (`rect`, `ellipse`, `line`, `beginShape`/`endShape`, `fill`, `stroke`, `push`, `pop`, `translate`, `rotate`, `scale`, `applyMatrix`).
- Builds an Abstract Syntax Tree (AST) composed of node instances (`ScopeNode`, `ShapeNode`, `BackgroundNode`, `ClearNode`, `ImageNode`).
- Tracks coordinate transformations using an internal `TransformStack`.

2. **SVG Export & Visitor (`src/shape/svg/svg_export.js`)**:
- Implements `SVGExportAddon` and `SVGVisitor` (extending `p5.PrimitiveVisitor`).
- Traverses the shape AST to output standard SVG 2.0 XML markup string via `getSVG()` or triggers browser file downloads via `saveSVG()`.

3. **SVG Import & Parsing (`src/shape/svg/svg_import.js`)**:
- Implements `SVGImportAddon` to parse external SVG DOM trees.
- Tokenizes path data commands (`PATH_COMMANDS`) and converts SVG elements (`<path>`, `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<polyline>`, `<polygon>`, `<g>`) into internal p5 `RecordedShape` structures ready for playback via `shape()`.

## Usage Example

### Exporting an SVG

```js
function setup() {
createCanvas(400, 400);

// Record drawing commands into a vector shape
const record = buildShape(() => {
background(245);
fill(99, 102, 241);
stroke(0);
strokeWeight(2);
circle(200, 200, 150);
});

// Save as vector file
saveSVG(record, 'my-vector.svg');
}
```

### Importing and Replaying an SVG

```js
let botLogo;

async function setup() {
createCanvas(500, 500);

try {
// loadSVG returns a promise; await the resolved RecordedShape
botLogo = await loadSVG('assets/robot.svg');
console.log('SVG Loaded successfully!');
} catch (err) {
console.error('Failed to load SVG:', err);
}
}

function draw() {
background(255);

// Render the SVG once it is fully loaded
if (botLogo) {
shape(botLogo, 100, 100);
} else {
fill(100);
text('Loading SVG...', 20, 30);
}
}
```

## Contributing

We welcome contributions to `p5.svg`! You can get involved by:

* **Testing existing SVG workflows** and reporting bugs or unexpected behavior on GitHub.
* **Proposing and implementing new SVG features**, such as expanded SVG element support, clipping paths, gradients, filters, and custom SVG attributes.
* **Improving SVG import and parsing**, including support for additional path commands and CSS/SVG attributes.
* **Adding tests and examples** to validate new functionality and demonstrate real-world SVG workflows.
* **Creating tutorials and creative coding examples** that showcase how `p5.svg` can be used in p5.js projects.
* **Reviewing and providing feedback on experimental APIs** to help improve their usability, consistency, and performance.

2 changes: 2 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ import shader from './webgl/p5.Shader';
p5.registerAddon(shader);
import strands from './strands/p5.strands';
p5.registerAddon(strands);
import svg from './shape/svg/p5.svg';
p5.registerAddon(svg);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💟


import { waitForDocumentReady, _globalInit } from './core/init';
waitForDocumentReady().then(_globalInit);
Expand Down
1 change: 1 addition & 0 deletions src/core/experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { FES } from '../friendly_errors/fes';
const experimentalMessages = {
webgpu: 'WEBGPU mode is experimental, so its functions and constants may change in future versions. You can get involved by giving feedback to help direct its development!',
'p5.strands': 'p5.strands shaders are experimental, so functions for building shaders and the hooks available within them may change in future versions. You can get involved by giving feedback to help direct its development!',
'p5.svg': 'SVG features are experimental, so SVG export, import, and shape recording functions may change in future versions. You can get involved by giving feedback to help direct its development!'
};

// Just in case it's not possible to get access to the p5 instance from something,
Expand Down
41 changes: 41 additions & 0 deletions src/shape/svg/p5.svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @module SVG
* @submodule p5.svg
* @for p5
*/

import { SVGExportAddon } from './svg_export.js';
import { SVGImportAddon } from './svg_import.js';
import { markExperimental } from '../../core/experimental.js';

// Initializes the p5.js SVG module by combining export and import functionality.
// Registers public APIs on p5.prototype and marks experimental features with
// warning decorators to inform users about API stability during the 2.x lifecycle.
function svg(p5, fn, lifecycles) {
// Register core export (shape recording, vector output) and import (SVG parser) extensions.
SVGExportAddon(p5, fn, lifecycles);
SVGImportAddon(p5, fn, lifecycles);

// List of user-facing SVG methods marked as experimental.
// Decorators log friendly error warnings when these methods are invoked in user sketches.
const experimentalMethods = [
'createSVG',
'loadSVG',
'createShape',
'buildShape',
'getSVG',
'shape',
'saveSVG'
];

for (const method of experimentalMethods) {
if (fn[method]) {
p5.registerDecorator(
`p5.prototype.${method}`,
markExperimental('p5.svg', p5)
);
}
}
}

export default svg;
Loading
Loading