├── .gitignore
├── CONTRIBUTING.md
├── images
├── studio_logo.png
├── bantam_tools_logo.png
├── cmu_school_of_art_logo.png
└── bantam_artframe_plotter.jpg
├── examples
├── plotSvg_hershey_text
│ ├── style.css
│ ├── hershey_text.png
│ ├── hershey_text_sm.png
│ ├── index.html
│ └── sketch.js
├── plotSvg_post_grouping
│ ├── style.css
│ ├── post_grouping.png
│ ├── post_grouping_sm.png
│ ├── index.html
│ ├── sketch.js
│ ├── README.md
│ └── post_grouping.svg
├── plotSvg_svg_font_text
│ ├── style.css
│ ├── svg_font_text.png
│ ├── svg_font_text_sm.png
│ ├── index.html
│ ├── README.md
│ └── sketch.js
├── plotSvg_generative
│ ├── plotSvg_generative.png
│ ├── plotSvg_generative_sm.png
│ ├── index.html
│ ├── sketch.js
│ ├── README.md
│ └── plotSvg_generative.svg
├── plotSvg_smorgasbord
│ ├── plotSvg_smorgasbord.png
│ ├── plotSvg_smorgasbord_sm.png
│ ├── index.html
│ ├── README.md
│ └── sketch.js
├── plotSvg_hello_static
│ ├── plotSvg_hello_static.png
│ ├── plotSvg_hello_static_sm.png
│ ├── index.html
│ ├── plotSvg_hello_static.svg
│ ├── sketch.js
│ └── README.md
├── plotSvg_instancemode
│ ├── plotSvg_instancemode.png
│ ├── plotSvg_instancemode_sm.png
│ ├── index.html
│ ├── sketch.js
│ ├── README.md
│ └── plotSvg_instancemode.svg
├── plotSvg_face_flipbook
│ ├── plotSvg_face_flipbook.gif
│ ├── README.md
│ ├── index.html
│ └── sketch.js
├── plotSvg_hatched_shapes
│ ├── plotSvg_hatched_shapes.png
│ ├── plotSvg_hatched_shapes_sm.png
│ ├── index.html
│ ├── sketch.js
│ ├── README.md
│ └── plotSvg_hatched_shapes.svg
├── plotSvg_particle_paths
│ ├── plotSvg_particle_paths.png
│ ├── plotSvg_particle_paths_sm.png
│ ├── index.html
│ ├── sketch.js
│ └── README.md
├── plotSvg_hello_animating
│ ├── plotSvg_hello_animating.png
│ ├── plotSvg_hello_animating_sm.png
│ ├── index.html
│ ├── sketch.js
│ ├── README.md
│ └── plotSvg_hello_animating.svg
├── plotSvg_drawing_recorder
│ ├── plotSvg_drawing_recorder.png
│ ├── plotSvg_drawing_recorder_sm.png
│ ├── index.html
│ ├── plotSvg_drawing_recorder.svg
│ ├── sketch.js
│ └── README.md
├── plotSvg_powerstroke_WIP
│ ├── README.md
│ ├── index.html
│ ├── svg
│ │ ├── example_powerstroke_svgs
│ │ │ ├── powerstroke_drawing_simplest.svg
│ │ │ └── powerstroke_drawing_curve.svg
│ │ └── plotSvg_powerstroke_wavy.svg
│ ├── BezierSegment.js
│ ├── sketchUI.js
│ └── sketch.js
└── README.md
├── SECURITY.md
├── package.json
├── LICENSE
├── CODE_OF_CONDUCT.md
└── documentation.md
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | We welcome your contributions! ❤️🤖🎨
2 |
3 |
--------------------------------------------------------------------------------
/images/studio_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/images/studio_logo.png
--------------------------------------------------------------------------------
/images/bantam_tools_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/images/bantam_tools_logo.png
--------------------------------------------------------------------------------
/images/cmu_school_of_art_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/images/cmu_school_of_art_logo.png
--------------------------------------------------------------------------------
/images/bantam_artframe_plotter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/images/bantam_artframe_plotter.jpg
--------------------------------------------------------------------------------
/examples/plotSvg_hershey_text/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | canvas {
6 | display: block;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/plotSvg_post_grouping/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | canvas {
6 | display: block;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/plotSvg_svg_font_text/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | canvas {
6 | display: block;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/plotSvg_hershey_text/hershey_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hershey_text/hershey_text.png
--------------------------------------------------------------------------------
/examples/plotSvg_post_grouping/post_grouping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_post_grouping/post_grouping.png
--------------------------------------------------------------------------------
/examples/plotSvg_svg_font_text/svg_font_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_svg_font_text/svg_font_text.png
--------------------------------------------------------------------------------
/examples/plotSvg_generative/plotSvg_generative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_generative/plotSvg_generative.png
--------------------------------------------------------------------------------
/examples/plotSvg_hershey_text/hershey_text_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hershey_text/hershey_text_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_generative/plotSvg_generative_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_generative/plotSvg_generative_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_post_grouping/post_grouping_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_post_grouping/post_grouping_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_smorgasbord/plotSvg_smorgasbord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_smorgasbord/plotSvg_smorgasbord.png
--------------------------------------------------------------------------------
/examples/plotSvg_svg_font_text/svg_font_text_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_svg_font_text/svg_font_text_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_hello_static/plotSvg_hello_static.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hello_static/plotSvg_hello_static.png
--------------------------------------------------------------------------------
/examples/plotSvg_instancemode/plotSvg_instancemode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_instancemode/plotSvg_instancemode.png
--------------------------------------------------------------------------------
/examples/plotSvg_smorgasbord/plotSvg_smorgasbord_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_smorgasbord/plotSvg_smorgasbord_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_face_flipbook/plotSvg_face_flipbook.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_face_flipbook/plotSvg_face_flipbook.gif
--------------------------------------------------------------------------------
/examples/plotSvg_hatched_shapes/plotSvg_hatched_shapes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hatched_shapes/plotSvg_hatched_shapes.png
--------------------------------------------------------------------------------
/examples/plotSvg_hello_static/plotSvg_hello_static_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hello_static/plotSvg_hello_static_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_instancemode/plotSvg_instancemode_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_instancemode/plotSvg_instancemode_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_particle_paths/plotSvg_particle_paths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_particle_paths/plotSvg_particle_paths.png
--------------------------------------------------------------------------------
/examples/plotSvg_hello_animating/plotSvg_hello_animating.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hello_animating/plotSvg_hello_animating.png
--------------------------------------------------------------------------------
/examples/plotSvg_drawing_recorder/plotSvg_drawing_recorder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_drawing_recorder/plotSvg_drawing_recorder.png
--------------------------------------------------------------------------------
/examples/plotSvg_hatched_shapes/plotSvg_hatched_shapes_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hatched_shapes/plotSvg_hatched_shapes_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_hello_animating/plotSvg_hello_animating_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_hello_animating/plotSvg_hello_animating_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_particle_paths/plotSvg_particle_paths_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_particle_paths/plotSvg_particle_paths_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_drawing_recorder/plotSvg_drawing_recorder_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/golanlevin/p5.plotSvg/HEAD/examples/plotSvg_drawing_recorder/plotSvg_drawing_recorder_sm.png
--------------------------------------------------------------------------------
/examples/plotSvg_powerstroke_WIP/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_powerstroke Example
2 |
3 | **WORK IN PROGRESS — NOTHING TO SEE HERE YET**
4 |
5 | ---
6 |
7 | Todo:
8 |
9 | * Wacom
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/plotSvg_face_flipbook/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_face_flipbook Example
2 |
3 | 
4 |
5 | This example exports a tiny flipbook, using faces recording from Google's MediaPipe face-tracker. Code is available:
6 |
7 | * [here](sketch.js)
8 | * [@openProcessing](https://openprocessing.org/sketch/2488219)
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | The following versions of this project would be supported with security updates if any were necessary.
6 |
7 | | Version | Supported |
8 | | ------- | ------------------ |
9 | | 0.1.x | :white_check_mark: |
10 |
11 | ## Reporting a Vulnerability
12 |
13 | I can't imagine how there would be a security vulnerability with this project, but if you find one, please file an issue or otherwise notify @golanlevin.
14 |
--------------------------------------------------------------------------------
/examples/plotSvg_hershey_text/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Inlined Hershey Font Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/plotSvg_post_grouping/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | p5.plotSvg Post Grouping Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/plotSvg_svg_font_text/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | p5.plotSvg SVG Font Text Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/plotSvg_generative/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Generative Art Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/plotSvg_instancemode/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Instance Mode Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/plotSvg_face_flipbook/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Drawing Recorder Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/plotSvg_smorgasbord/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Smorgasbord Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Press 's' to save SVG.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/plotSvg_hello_animating/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Hello Animating Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Press 's' to save SVG.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/plotSvg_hello_static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Hello Static Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Saves an SVG when the sketch is loaded.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/plotSvg_hatched_shapes/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Hatched Shapes Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Click to randomize. Press 's' to save SVG.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/plotSvg_particle_paths/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Particle Paths Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Saves an SVG after 100 frames have elapsed.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "p5.plotsvg",
3 | "version": "0.1.7",
4 | "description": "A Plotter-Oriented SVG Exporter for p5.js",
5 | "main": "lib/p5.plotSvg.js",
6 | "directories": {
7 | "example": "examples",
8 | "lib": "lib"
9 | },
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/golanlevin/p5.plotSvg.git"
16 | },
17 | "keywords": [
18 | "plotters",
19 | "p5.js",
20 | "SVG",
21 | "#plotterTwitter",
22 | "creativecoding",
23 | "generativeart"
24 | ],
25 | "author": "Golan Levin",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/golanlevin/p5.plotSvg/issues"
29 | },
30 | "homepage": "https://github.com/golanlevin/p5.plotSvg#readme"
31 | }
32 |
--------------------------------------------------------------------------------
/examples/plotSvg_hello_static/plotSvg_hello_static.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_hello_static/sketch.js:
--------------------------------------------------------------------------------
1 | // https://github.com/golanlevin/p5.plotSvg (v.0.1.0)
2 | // A Plotter-Oriented SVG Exporter for p5.js
3 | // Golan Levin, November 2024
4 | //
5 | // Extremely simple demo of using p5.plotSvg to export SVG files.
6 | // Requires https://unpkg.com/p5.plotsvg@0.1.0/lib/p5.plotSvg.js or
7 | // https://cdn.jsdelivr.net/npm/p5.plotsvg@latest/lib/p5.plotSvg.js
8 | //
9 | // Note 1: This sketch will save an SVG at the very moment when you run it.
10 | // Note 2: This sketch issues many warnings; this line quiets them:
11 | p5.disableFriendlyErrors = true;
12 |
13 | function setup() {
14 | createCanvas(576, 384); // 6"x4" at 96 dpi
15 | background(245);
16 | noFill();
17 |
18 | beginRecordSvg(this, "plotSvg_hello1.svg");
19 | circle(width/2, height/2, 300);
20 | ellipse(width/2-60, height/2-40, 30, 50);
21 | ellipse(width/2+60, height/2-40, 30, 50);
22 | arc(width/2, height/2+30, 150, 100, 0, PI);
23 | endRecordSvg();
24 | }
--------------------------------------------------------------------------------
/examples/plotSvg_drawing_recorder/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-2025 Golan Levin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/plotSvg_powerstroke_WIP/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | p5.plotSvg Inkscape PowerStroke Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Press 's' to save SVG.
26 |
27 |
28 |
--------------------------------------------------------------------------------
/examples/plotSvg_hello_animating/sketch.js:
--------------------------------------------------------------------------------
1 | // https://github.com/golanlevin/p5.plotSvg (v.0.1.x)
2 | // A Plotter-Oriented SVG Exporter for p5.js
3 | // Golan Levin, November 2024
4 | //
5 | // This sketch demonstrates how to use the p5.plotSvg library
6 | // to export SVG files during interaction. Press 's' to export an SVG.
7 |
8 | // This line of code disables the p5.js "Friendly Error System" (FES),
9 | // in order to prevent several distracting warnings:
10 | p5.disableFriendlyErrors = true;
11 |
12 | let bDoExportSvg = false;
13 | function setup() {
14 | createCanvas(576, 384); // Postcard size: 6"x4" at 96 dpi
15 | }
16 |
17 | function keyPressed(){
18 | if (key == 's'){
19 | // Initiate SVG exporting
20 | bDoExportSvg = true;
21 | }
22 | }
23 |
24 | function draw(){
25 | background(245);
26 | strokeWeight(1);
27 | stroke(0);
28 | noFill();
29 |
30 | if (bDoExportSvg){
31 | // Begin exporting, if requested
32 | beginRecordSvg(this, "plotSvg_hello_animating.svg");
33 | }
34 |
35 |
36 | // Draw your artwork here.
37 | push();
38 | translate(width/2, height/2);
39 | beginShape();
40 | for (let i=0; i<=400; i++){
41 | let val = noise(i/100 + millis()/1000) - 0.5;
42 | vertex(i-200, 200*val);
43 | }
44 | endShape();
45 | rectMode(CENTER);
46 | rect(0,0, 400,300);
47 | pop();
48 |
49 |
50 | if (bDoExportSvg){
51 | // End exporting, if doing so
52 | endRecordSvg();
53 | bDoExportSvg = false;
54 | }
55 | }
--------------------------------------------------------------------------------
/examples/plotSvg_hello_static/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_hello_static Example
2 |
3 | The `plotSvg_hello_static` example shows the simplest possible use of the p5.plotSvg library. The program runs in the p5.js "static mode", and the SVG is exported at the conclusion of `setup()`. There is no interactivity or animation.
4 |
5 | 
6 |
7 | Code:
8 |
9 | * At editor.p5js.org: [https://editor.p5js.org/golan/sketches/AW8GI36fA](https://editor.p5js.org/golan/sketches/AW8GI36fA)
10 | * At openprocessing.org: [https://openprocessing.org/sketch/2455362](https://openprocessing.org/sketch/2455362)
11 | * At GitHub: [sketch.js](https://raw.githubusercontent.com/golanlevin/p5.plotSvg/refs/heads/main/examples/plotSvg_hello_static/sketch.js)
12 |
13 | ```js
14 | // plotSvg_hello_static Example
15 | // Extremely simple demo of using p5.plotSvg to export SVG files.
16 | // Requires https://cdn.jsdelivr.net/npm/p5.plotsvg@latest/lib/p5.plotSvg.js
17 | //
18 | // Note 1: This sketch will export an SVG at the very moment when you run it.
19 | // Note 2: This sketch issues many warnings; the following line quiets them:
20 | p5.disableFriendlyErrors = true;
21 |
22 | function setup() {
23 | createCanvas(576, 384); // Postcard size: 6"x4" at 96 dpi
24 | background(245);
25 | noFill();
26 |
27 | beginRecordSvg(this, "plotSvg_hello_static.svg");
28 | circle(width/2, height/2, 300);
29 | ellipse(width/2-60, height/2-40, 30, 50);
30 | ellipse(width/2+60, height/2-40, 30, 50);
31 | arc(width/2, height/2+30, 150, 100, 0, PI);
32 | endRecordSvg();
33 | }
34 | ```
35 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # p5.plotSvg Contributor Code of Conduct
2 |
3 | As contributors and maintainers of the p5.plotSvg project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4 |
5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6 |
7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8 |
9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10 |
11 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
12 |
13 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
14 |
15 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
--------------------------------------------------------------------------------
/examples/plotSvg_svg_font_text/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_svg_font_text Example
2 |
3 | 
4 |
5 | This example loads a single-line SVG 1.1 font, and uses p5.plotSVG to plot it. Code is available:
6 |
7 | * [here, in this repo](sketch.js)
8 | * [at Editor.p5js.org](https://editor.p5js.org/golan/sketches/rIsRh01Vj)
9 | * [at openProcessing](https://openprocessing.org/sketch/2684135)
10 |
11 | ---
12 |
13 | ### Additional Information
14 |
15 | p5.plotSVG already supports standard (outline) typography executed using p5.js, as demonstrated in the [Smorgasbord example](../plotSvg_smorgasbord/README.md). In many plotter-oriented applications, however, it is desirable to use specialized ***single-line*** fonts (also called single-stroke or monoline fonts) instead of outline fonts.
16 |
17 | [This example](sketch.js) provides a workflow for loading [SVG 1.1 Fonts](https://www.w3.org/TR/SVG11/fonts.html), a lesser-known open standard that allows for single-line vector fonts. Here is an [**archive of single-line SVG 1.1 fonts**](https://github.com/golanlevin/p5-single-line-font-resources/blob/main/p5_single_line_svg_fonts/single_line_svg_fonts/README.md), including various p5.js code for loading and displaying them. More generally, here is a [large repository of single-line fonts and related code](https://github.com/golanlevin/p5-single-line-font-resources/blob/main/README.md) in various formats.
18 |
19 | A [simplified example is also provided here](../plotSvg_hershey_text), in which a basic single-line font is reformatted and directly *inlined* (i.e. hardcoded) into a p5.plotSvg project, eliminating the need to load an external font file.
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/plotSvg_particle_paths/sketch.js:
--------------------------------------------------------------------------------
1 | // plotSvg_particle_paths Example
2 | // Golan Levin, November 2024
3 | //
4 | // Accumulates the positions of particles as they move over time.
5 | // (Particles are influenced by a Perlin Noise flow field.)
6 | // Exports these traces as an SVG after 100 frames.
7 | // (Note: it would be smarter to store the paths!)
8 | //
9 | // Requires: https://cdn.jsdelivr.net/npm/p5.plotsvg@latest/lib/p5.plotSvg.js
10 | // See: https://github.com/golanlevin/p5.plotSvg
11 | p5.disableFriendlyErrors = true;
12 |
13 | let bDoExportSvg = false;
14 | let nRecordingFrames = 100;
15 | let particles = []; // current positions of the particles
16 |
17 | function setup() {
18 | // Postcard size: 6"x4" at 96 dpi
19 | createCanvas(576, 384);
20 | background(245);
21 | strokeWeight(1);
22 | stroke(0);
23 | noFill();
24 |
25 | // Initiate SVG exporting in setup()
26 | bDoExportSvg = true;
27 | if (bDoExportSvg){
28 | setSvgCoordinatePrecision(2); // keep file size small
29 | beginRecordSvg(this, "plotSvg_particle_paths.svg");
30 | }
31 |
32 | for (let i=0; i<100; i++){
33 | let rx = random(0,width);
34 | let ry = random(0,height);
35 | particles.push(createVector(rx,ry));
36 | }
37 | }
38 |
39 | function draw(){
40 | // Accrete each of the stored marks onto the canvas
41 | if (bDoExportSvg){
42 | for (let i=0; i
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_post_grouping/sketch.js:
--------------------------------------------------------------------------------
1 | // plotSvg_post_grouping: lines grouped by their color.
2 | // Click to re-generate design; press button to export SVG.
3 | // Demonstrates post-hoc merging of groups, using
4 | // setSvgMergeNamedGroups(true). This function is
5 | // useful for grouping together lines that are computed
6 | // at different times, but should be executed with the
7 | // same drawing implement on the plotter.
8 | // Requires p5.plotSvg 0.1.5+
9 |
10 | p5.disableFriendlyErrors = true; // hush, p5
11 | let bDoExportSvg = false;
12 | let myRandomSeed = 12345;
13 |
14 | function setup(){
15 | createCanvas(6 * 96, 4 * 96); // 6x4 inches at 96dpi
16 | setSvgMergeNamedGroups(true); // Groups the lines!
17 |
18 | let saveButton = createButton("Save SVG");
19 | saveButton.position(10, 10);
20 | saveButton.mousePressed((event) => {
21 | event.stopPropagation();
22 | bDoExportSvg = true;
23 | // save("post_grouping.png");
24 | });
25 | }
26 |
27 | function mousePressed(){
28 | myRandomSeed = millis();
29 | }
30 |
31 | function draw(){
32 | randomSeed(myRandomSeed);
33 | background(245);
34 | strokeWeight(1);
35 |
36 | if (bDoExportSvg){
37 | beginRecordSvg(this, "post_grouping.svg");
38 | }
39 |
40 | // Do a drunk walk, alternating horizonal and vertical moves.
41 | // Horizonal lines are red, vertical lines are blue.
42 | // setSvgMergeNamedGroups(true) ensures that lines inside
43 | // the same group are (eventually) grouped together.
44 | let px = width/2;
45 | let py = height/2;
46 | for (let i=0; i<75; i++){
47 | let qx = px;
48 | let qy = py;
49 |
50 | if (i%2 == 0){
51 | qx += 40 * random(-1,1);
52 | beginSvgGroup("horizontalLines");
53 | stroke('red');
54 | line(px,py, qx,qy);
55 | endSvgGroup();
56 |
57 | } else {
58 | qy += 30 * random(-1,1);
59 | beginSvgGroup("verticalLines");
60 | stroke('blue');
61 | line(px,py, qx,qy);
62 | endSvgGroup();
63 | }
64 |
65 | px = qx;
66 | py = qy;
67 | }
68 |
69 | if (bDoExportSvg){
70 | endRecordSvg();
71 | bDoExportSvg = false;
72 | }
73 | }
--------------------------------------------------------------------------------
/examples/plotSvg_hello_animating/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_hello_animating Example
2 |
3 | The `plotSvg_hello_animating ` example shows basic use of the p5.plotSvg library, for a p5.js sketch in the familiar "animating/looping" mode, which uses both `setup()` and `draw()` functions. Note that the global boolean variable `bDoExportSvg` is used as a latch to export an SVG file only when the user presses the `s` key.
4 |
5 | 
6 |
7 | Code:
8 |
9 | * At editor.p5js.org: [https://editor.p5js.org/golan/sketches/JA-ty5j83](https://editor.p5js.org/golan/sketches/JA-ty5j83)
10 | * At openprocessing.org: [https://openprocessing.org/sketch/2455390](https://openprocessing.org/sketch/2455390)
11 | * At Github: [sketch.js](https://raw.githubusercontent.com/golanlevin/p5.plotSvg/refs/heads/main/examples/plotSvg_hello_animating/sketch.js)
12 |
13 |
14 | ```js
15 | // plotSvg_hello_animating Example
16 | // This sketch demonstrates how to use the p5.plotSvg library
17 | // to export SVG files during interaction. Press 's' to export an SVG.
18 |
19 | // This line of code disables the p5.js "Friendly Error System" (FES),
20 | // in order to prevent several distracting warnings:
21 | p5.disableFriendlyErrors = true;
22 |
23 | let bDoExportSvg = false;
24 | function setup() {
25 | // Postcard size: 6"x4" at 96 dpi
26 | createCanvas(576, 384);
27 | }
28 |
29 | function keyPressed(){
30 | if (key == 's'){
31 | // Initiate SVG exporting
32 | bDoExportSvg = true;
33 | }
34 | }
35 |
36 | function draw(){
37 | background(245);
38 | strokeWeight(1);
39 | stroke(0);
40 | noFill();
41 |
42 | if (bDoExportSvg){
43 | // Begin exporting, if requested
44 | beginRecordSvg(this, "plotSvg_hello_animating.svg");
45 | }
46 |
47 |
48 | // Draw your artwork here.
49 | push();
50 | translate(width/2, height/2);
51 | beginShape();
52 | for (let i=0; i<=400; i++){
53 | let val = noise(i/100 + millis()/1000) - 0.5;
54 | vertex(i-200, 200*val);
55 | }
56 | endShape();
57 | rectMode(CENTER);
58 | rect(0,0, 400,300);
59 | pop();
60 |
61 |
62 | if (bDoExportSvg){
63 | // End exporting, if doing so
64 | endRecordSvg();
65 | bDoExportSvg = false;
66 | }
67 | }
68 | ```
69 |
--------------------------------------------------------------------------------
/examples/plotSvg_smorgasbord/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_smorgasbord Example
2 |
3 | The `plotSvg_smorgasbord` example is intended as a comprehensive test, to ensure that all path-based p5.js drawing commands are successfully exporting vector paths to an SVG file.
4 |
5 | ### Code:
6 |
7 | * At editor.p5js.org: [https://editor.p5js.org/golan/sketches/QReF_9ss2](https://editor.p5js.org/golan/sketches/QReF_9ss2)
8 | * At openprocessing.org: [https://openprocessing.org/sketch/2455426](https://openprocessing.org/sketch/2455426)
9 | * At Github: [sketch.js](https://raw.githubusercontent.com/golanlevin/p5.plotSvg/refs/heads/main/examples/plotSvg_smorgasbord/sketch.js)
10 |
11 | 
12 |
13 | `plotSvg_smorgasbord` includes tests of the following p5.js drawing commands and options:
14 |
15 | * `arc()` — `OPEN, CHORD, PIE`
16 | * `bezier()`
17 | * `circle()`
18 | * `curve()`
19 | * `ellipse()`
20 | * `line()`
21 | * `point()`
22 | * `quad()`
23 | * `rect()`
24 | * `square()`
25 | * `stroke()`
26 | * `text()`
27 | * `triangle()`
28 | * `beginShape()` — `TRIANGLE_STRIP, TRIANGLES, QUAD_STRIP, QUADS, TRIANGLE_FAN, POINTS, LINES`
29 | * `vertex()`, `quadraticVertex()`, `bezierVertex()`, `curveVertex()`¹
30 | * `endShape()` — `CLOSE`
31 |
32 | In addition, this example also tests the following drawing-state modifiers:
33 |
34 | * `stroke()`
35 | * `curveTightness()`
36 | * `ellipseMode()` — `CORNER, CORNERS, CENTER, RADIUS`
37 | * `rectMode()` — `CORNER, CORNERS, CENTER, RADIUS`
38 | * `push()`, `pop()`
39 | * `translate()`, `rotate()`, `scale()`, `shearX()`, `shearY()`
40 | * `textSize()`
41 | * `textFont()`
42 | * `textStyle()`
43 | * `textAlign()`²
44 |
45 | ---
46 |
47 | ### Known Issues and Bugs:
48 |
49 | 1. In p5.plotSvg v.0.1.x, there is a small discrepancy in the SVG output of polylines rendered with curveVertex(). Specifically, there is an error with the starting orientation of the first point of the polyline.
50 | 2. In p5.plotSvg v.0.1.x, non-default vertical `textAlign()` settings are not yet supported; only BASELINE currently works correctly.
51 | 3. In p5.plotSvg v.0.1.x, *multi-contour* shapes (made with `beginContour()` / `endContour()`etc.) are not yet unsupported. For the time being, encode each contour in its own `beginShape()` / `endShape()` block instead.
52 |
53 |
--------------------------------------------------------------------------------
/examples/plotSvg_generative/sketch.js:
--------------------------------------------------------------------------------
1 | // Demonstrates how to use the p5.plotSvg library to export
2 | // SVG files from a "generative art" sketch in p5.js.
3 |
4 | // This line of code disables the p5.js "Friendly Error System" (FES),
5 | // to prevent some distracting warnings. Feel free to comment this out.
6 | p5.disableFriendlyErrors = true;
7 |
8 | let bDoExportSvg = false;
9 | let myRandomSeed = 12345;
10 | let regenerateButton;
11 | let exportSvgButton;
12 |
13 | //------------------------------------------------------------
14 | function setup() {
15 | createCanvas(576, 384); // 6"x4" at 96 dpi
16 |
17 | regenerateButton = createButton('Regenerate');
18 | regenerateButton.position(0, height);
19 | regenerateButton.mousePressed(regenerate);
20 |
21 | exportSvgButton = createButton('Export SVG');
22 | exportSvgButton.position(100, height);
23 | exportSvgButton.mousePressed(initiateSvgExport);
24 | }
25 |
26 | //------------------------------------------------------------
27 | // Make a new random seed when the "Regenerate" button is pressed
28 | function regenerate(){
29 | myRandomSeed = round(millis());
30 | }
31 | // Set the SVG to be exported when the "Export SVG" button is pressed
32 | function initiateSvgExport(){
33 | bDoExportSvg = true;
34 | }
35 |
36 | //------------------------------------------------------------
37 | function draw(){
38 | randomSeed(myRandomSeed);
39 | background(245);
40 | strokeWeight(1);
41 | stroke(0);
42 | noFill();
43 |
44 | if (bDoExportSvg){
45 | beginRecordSvg(this, "plotSvg_generative_" + myRandomSeed + ".svg");
46 | }
47 |
48 |
49 | // Set the SVG group by stroke color to `true`, so that strokes
50 | // of the same color are grouped together in the SVG file.
51 | setSvgGroupByStrokeColor(true);
52 |
53 | // Draw 100 random lines: some red, some black.
54 | let nLines = 100;
55 | for (let i=0; i
2 |
3 |
4 |
84 |
--------------------------------------------------------------------------------
/examples/plotSvg_drawing_recorder/sketch.js:
--------------------------------------------------------------------------------
1 | // This sketch records a series of marks drawn by the user
2 | // and exports an SVG file when the button is pressed.
3 | // This sketch is mobile-friendly.
4 | //
5 | // Uses https://github.com/golanlevin/p5.plotSvg (v.0.1.x)
6 | // A Plotter-Oriented SVG Exporter for p5.js
7 | // Golan Levin, November 2024
8 |
9 | p5.disableFriendlyErrors = true;
10 | let exportSvgButton;
11 | let clearButton;
12 | let bDoExportSvg = false;
13 | let marks = [];
14 | let currentMark = [];
15 |
16 | function setup() {
17 | createCanvas(375, 500); // phone-safe size
18 | exportSvgButton = createButton('Export SVG');
19 | exportSvgButton.position(10, 10);
20 | exportSvgButton.mousePressed(() => bDoExportSvg = true);
21 | clearButton = createButton('Clear');
22 | clearButton.position(100, 10);
23 | clearButton.mousePressed(() => {
24 | marks = [];
25 | currentMark = [];
26 | });
27 | }
28 |
29 | //-------------------------------------
30 | function keyPressed(){
31 | if (key == 's'){
32 | // Another way to initiate SVG exporting
33 | bDoExportSvg = true;
34 | } else if (key == ' '){
35 | // Clear recordings with spacebar
36 | marks = [];
37 | currentMark = [];
38 | }
39 | }
40 |
41 | //-------------------------------------
42 | function mousePressed(){
43 | currentMark = [];
44 | currentMark.push(createVector(mouseX, mouseY));
45 | }
46 | function mouseDragged(){
47 | currentMark.push(createVector(mouseX, mouseY));
48 | }
49 | function mouseReleased(){
50 | if (currentMark){
51 | marks.push(currentMark);
52 | }
53 | }
54 | function touchStarted(event) {
55 | if (event.target.tagName === 'CANVAS') {
56 | mousePressed();
57 | return false;
58 | }
59 | }
60 | function touchMoved(event) {
61 | if (event.target.tagName === 'CANVAS') {
62 | mouseDragged();
63 | return false;
64 | }
65 | }
66 | function touchEnded(event) {
67 | if (event.target.tagName === 'CANVAS') {
68 | mouseReleased();
69 | return false;
70 | }
71 | }
72 |
73 | //-------------------------------------
74 | function draw(){
75 | background(245);
76 | strokeWeight(1);
77 | stroke(0);
78 | noFill();
79 |
80 | if (bDoExportSvg){
81 | let svgFilename = "plotSvg_recording_" + frameCount;
82 | beginRecordSvg(this, svgFilename + ".svg");
83 | }
84 |
85 | // Draw each of the stored marks
86 | for (let j=0; j
2 |
3 |
4 |
68 |
--------------------------------------------------------------------------------
/examples/plotSvg_post_grouping/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_post_grouping Example
2 |
3 | The `plotSvg_post_grouping` example demonstrates how to use the `setSvgMergeNamedGroups()` function to merge groups of SVG paths, even if the paths were computed at different times, *so long as the SVG groups have the same name*. This is useful for grouping together paths that should be plotted with (e.g.) the same color pen. The implementation of this functionality is thanks to the contribution of Lionel Ringenbach ([@Ucodia](https://github.com/ucodia)) and is supported in p5.plotSvg 0.1.5+.
4 |
5 |
6 | 
7 |
8 | Code:
9 |
10 | * At editor.p5js.org: [https://editor.p5js.org/golan/sketches/aWfRPvVfT](https://editor.p5js.org/golan/sketches/aWfRPvVfT)
11 | * At Github: [sketch.js](https://raw.githubusercontent.com/golanlevin/p5.plotSvg/refs/heads/main/examples/plotSvg_post_grouping/sketch.js)
12 | * A related example by @Ucodia can be found [here](https://editor.p5js.org/golan/sketches/k3xWBNTND)
13 |
14 | In the example provided here, a `for` loop in the `draw()` function accumulates 75 steps of a "drunk walk", alternately pushing a tracer at `(px,py)` horizontally and vertically by random amounts. Horizontal movements are shown with a red line, and vertical movements are shown with a blue line. In the [resulting SVG](post_grouping.svg), the horizontal and vertical lines are placed into separate SVG groups, e.g.:
15 |
16 | ```xml
17 |
18 |
19 |
20 |
21 | ...
22 | ```
23 |
24 | Program code:
25 |
26 | ```js
27 | // plotSvg_post_grouping: lines grouped by their color.
28 | // Click to re-generate design; press 's' to export SVG.
29 | // Demonstrates post-hoc merging of groups, using
30 | // setSvgMergeNamedGroups(true). This function is
31 | // useful for grouping together lines that are computed
32 | // at different times, but should be executed with the
33 | // same drawing implement on the plotter.
34 | // Requires p5.plotSvg 0.1.5+
35 |
36 | p5.disableFriendlyErrors = true; // hush, p5
37 | let bDoExportSvg = false;
38 | let myRandomSeed = 12345;
39 |
40 | function setup(){
41 | createCanvas(600, 400);
42 | setSvgMergeNamedGroups(true); // Groups the lines!
43 | }
44 |
45 | function mousePressed(){
46 | myRandomSeed = millis();
47 | }
48 |
49 | function keyPressed(){
50 | if (key == 's'){
51 | bDoExportSvg = true;
52 | }
53 | }
54 |
55 | function draw(){
56 | randomSeed(myRandomSeed);
57 | background(255);
58 |
59 | if (bDoExportSvg){
60 | beginRecordSvg(this, "post_grouping.svg");
61 | }
62 |
63 | // Do a drunk walk, alternating horizonal and vertical moves.
64 | // Horizonal lines are red, vertical lines are blue.
65 | // Ensure that like-colored lines are grouped together.
66 | let px = width/2;
67 | let py = height/2;
68 | for (let i=0; i<75; i++){
69 | let qx = px;
70 | let qy = py;
71 |
72 | if (i%2 == 0){
73 | qx += 40 * random(-1,1);
74 | beginSvgGroup("horizontalLines");
75 | stroke('red');
76 | line(px,py, qx,qy);
77 | endSvgGroup();
78 |
79 | } else {
80 | qy += 30 * random(-1,1);
81 | beginSvgGroup("verticalLines");
82 | stroke('blue');
83 | line(px,py, qx,qy);
84 | endSvgGroup();
85 | }
86 |
87 | px = qx;
88 | py = qy;
89 | }
90 |
91 | if (bDoExportSvg){
92 | endRecordSvg();
93 | bDoExportSvg = false;
94 | }
95 | }
96 | ```
97 |
--------------------------------------------------------------------------------
/examples/plotSvg_drawing_recorder/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_drawing_recorder Example
2 |
3 | The `plotSvg_drawing_recorder` example records a series of marks drawn by the user, and exports an SVG file when a button is pressed.
4 |
5 | 
6 |
7 | Code:
8 |
9 | * At editor.p5js.org: [https://editor.p5js.org/golan/sketches/bQDM5IQdv](https://editor.p5js.org/golan/sketches/bQDM5IQdv)
10 | * At openprocessing.org: [https://openprocessing.org/sketch/2478914](https://openprocessing.org/sketch/2478914)
11 | * At Github: [sketch.js](https://raw.githubusercontent.com/golanlevin/p5.plotSvg/refs/heads/main/examples/plotSvg_drawing_recorder/sketch.js)
12 |
13 |
14 | ```js
15 | // This sketch records a series of marks drawn by the user
16 | // and exports an SVG file when the 'Export SVG' button is pressed.
17 | // This sketch is mobile-friendly.
18 | //
19 | // Uses https://github.com/golanlevin/p5.plotSvg (v.0.1.x)
20 | // A Plotter-Oriented SVG Exporter for p5.js
21 | // Golan Levin, November 2024
22 |
23 | p5.disableFriendlyErrors = true;
24 | let exportSvgButton;
25 | let clearButton;
26 | let bDoExportSvg = false;
27 | let marks = [];
28 | let currentMark = [];
29 |
30 | function setup() {
31 | createCanvas(375, 500); // phone-safe size
32 | exportSvgButton = createButton('Export SVG');
33 | exportSvgButton.position(10, 10);
34 | exportSvgButton.mousePressed(() => bDoExportSvg = true);
35 | clearButton = createButton('Clear');
36 | clearButton.position(100, 10);
37 | clearButton.mousePressed(() => {
38 | marks = [];
39 | currentMark = [];
40 | });
41 | }
42 |
43 | //-------------------------------------
44 | function keyPressed(){
45 | if (key == 's'){
46 | // Another way to initiate SVG exporting
47 | bDoExportSvg = true;
48 | } else if (key == ' '){
49 | // Clear recordings with spacebar
50 | marks = [];
51 | currentMark = [];
52 | }
53 | }
54 |
55 | //-------------------------------------
56 | function mousePressed(){
57 | currentMark = [];
58 | currentMark.push(createVector(mouseX, mouseY));
59 | }
60 | function mouseDragged(){
61 | currentMark.push(createVector(mouseX, mouseY));
62 | }
63 | function mouseReleased(){
64 | if (currentMark){
65 | marks.push(currentMark);
66 | }
67 | }
68 | function touchStarted(event) {
69 | if (event.target.tagName === 'CANVAS') {
70 | mousePressed();
71 | return false;
72 | }
73 | }
74 | function touchMoved(event) {
75 | if (event.target.tagName === 'CANVAS') {
76 | mouseDragged();
77 | return false;
78 | }
79 | }
80 | function touchEnded(event) {
81 | if (event.target.tagName === 'CANVAS') {
82 | mouseReleased();
83 | return false;
84 | }
85 | }
86 |
87 | //-------------------------------------
88 | function draw(){
89 | background(245);
90 | strokeWeight(1);
91 | stroke(0);
92 | noFill();
93 |
94 | if (bDoExportSvg){
95 | let svgFilename = "plotSvg_recording_" + frameCount;
96 | beginRecordSvg(this, svgFilename + ".svg");
97 | }
98 |
99 | // Draw each of the stored marks
100 | for (let j=0; j ({
103 | t: i * stepsInv,
104 | s: s / total,
105 | }));
106 | }
107 |
108 | // --- Lookup: Given s in [0,1], find t such that arcLen(t) ≈ s ---
109 | getTForS(s) {
110 | if (s <= 0) return 0;
111 | if (s >= 1) return 1;
112 |
113 | const lut = this.lut;
114 | let lo = 0, hi = lut.length - 1;
115 | while (hi - lo > 1) {
116 | const mid = Math.floor((lo + hi) / 2);
117 | if (lut[mid].s < s) lo = mid;
118 | else hi = mid;
119 | }
120 | const a = lut[lo], b = lut[hi];
121 | const alph = (s - a.s) / (b.s - a.s);
122 | return a.t + alph * (b.t - a.t);
123 | }
124 |
125 | eval(t) {
126 | return fcbezier.q(this.points, t);
127 | }
128 |
129 | getPointAtS(s) {
130 | if (s <= 0) return fcbezier.q(this.points, 0); // this.eval(0);
131 | if (s >= 1) return fcbezier.q(this.points, 1); // this.eval(1);
132 |
133 | const lut = this.lut;
134 | let lo = 0, hi = lut.length - 1;
135 |
136 | // Binary search for s
137 | while (hi - lo > 1) {
138 | const mid = Math.floor((lo + hi) / 2);
139 | if (lut[mid].s < s) lo = mid;
140 | else hi = mid;
141 | }
142 |
143 | // Linear interpolation of t
144 | const a = lut[lo], b = lut[hi];
145 | const alph = (s - a.s) / (b.s - a.s);
146 | const t = a.t + alph * (b.t - a.t);
147 | return fcbezier.q(this.points, t); // this.eval(t);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Index of p5.plotSvg Examples
2 |
3 | These examples show how to generate plotter-friendly SVGs from p5.js using p5.plotSvg. All examples are mirrored in collections at [editor.p5js.org](https://editor.p5js.org/golan/collections/MCA5RvDFX) and [openProcessing.org](https://openprocessing.org/curation/88363).
4 |
5 |
6 | ---
7 |
8 | ### ⭐ [plotSvg_smorgasbord](plotSvg_smorgasbord/README.md) ⭐
9 |
10 | **Full demonstration of all p5.js drawing primitives exported to SVG.**
[here](plotSvg_smorgasbord/README.md) • [@editor](https://editor.p5js.org/golan/sketches/QReF_9ss2) • [@openProcessing](https://openprocessing.org/sketch/2455426)
11 |
12 | [](plotSvg_smorgasbord/README.md)
13 |
14 | ---
15 |
16 | ### [plotSvg_hello_static](plotSvg_hello_static/README.md)
17 |
18 | Simplest possible demo, in "static" mode; all art is in `setup()` only.
[here](plotSvg_hello_static/README.md) • [@editor](https://editor.p5js.org/golan/sketches/AW8GI36fA) • [@openProcessing](https://openprocessing.org/sketch/2455362)
19 |
20 | [](plotSvg_hello_static/README.md)
21 |
22 | ---
23 |
24 | ### [plotSvg_hello_animating](plotSvg_hello_animating/README.md)
25 |
26 | Simple demo; uses `setup()`, `draw()` and a keypress to capture an SVG during animation.
[here](plotSvg_hello_animating/README.md) • [@editor](https://editor.p5js.org/golan/sketches/JA-ty5j83) • [@openProcessing](https://openprocessing.org/sketch/2455390)
27 |
28 | [](plotSvg_hello_animating/README.md)
29 |
30 | ---
31 |
32 | ### [plotSvg_generative](plotSvg_generative/README.md)
33 |
34 | Simple "generative artwork"; press a button to export.
[here](plotSvg_generative/README.md) • [@editor](https://editor.p5js.org/golan/sketches/LRTXmDg2q) • [@openProcessing](https://openprocessing.org/sketch/2455399)
35 |
36 | [](plotSvg_generative/README.md)
37 |
38 | ---
39 |
40 | ### [plotSvg_drawing_recorder](plotSvg_drawing_recorder/README.md)
41 |
42 | Records a series of marks drawn by the user.
[here](plotSvg_drawing_recorder/README.md) • [@editor](https://editor.p5js.org/golan/sketches/bQDM5IQdv) • [@openProcessing](https://openprocessing.org/sketch/2478914)
43 |
44 | [](plotSvg_drawing_recorder/README.md)
45 |
46 | ---
47 |
48 | ### [plotSvg_particle_paths](plotSvg_particle_paths/README.md)
49 |
50 | Accumulates the traces of some particles over time.
[here](plotSvg_particle_paths/README.md) • [@editor](https://editor.p5js.org/golan/sketches/1Toe-pMZH) • [@openProcessing](https://openprocessing.org/sketch/2478945)
51 |
52 | [](plotSvg_particle_paths/README.md)
53 |
54 | ---
55 |
56 | ### [plotSvg_hatched_shapes](plotSvg_hatched_shapes/README.md)
57 |
58 | A trick for exporting hatched ("filled") SVG shapes.
[here](plotSvg_hatched_shapes/README.md) • [@editor](https://editor.p5js.org/golan/sketches/b75oVci5f) • [@openProcessing](https://openprocessing.org/sketch/2479519)
59 |
60 | [](plotSvg_hatched_shapes/README.md)
61 |
62 | ---
63 |
64 | ### [plotSvg_instancemode](plotSvg_instancemode/README.md)
65 |
66 | Using p5.plotSvg in p5's [instance mode](https://github.com/processing/p5.js/wiki/Global-and-instance-mode).
[here](plotSvg_instancemode/README.md) • [@editor](https://editor.p5js.org/golan/sketches/Ib_myDs3s)
67 |
68 | [](plotSvg_instancemode/README.md)
69 |
70 | ---
71 |
72 | ### [plotSvg_post_grouping](plotSvg_post_grouping/README.md)
73 |
74 | Merge groups of paths computed at different times. Demonstrates per-color "layers".
[here](plotSvg_post_grouping/README.md) • [@editor](https://editor.p5js.org/golan/sketches/aWfRPvVfT) • [@openProcessing](https://openprocessing.org/sketch/2684018)
75 |
76 | 
77 |
78 | ---
79 |
80 | ### [plotSvg_svg_font_text](plotSvg_svg_font_text/README.md)
81 |
82 | Use monoline SVG fonts in your exported designs.
83 | [here](plotSvg_svg_font_text/README.md) • [@editor](https://editor.p5js.org/golan/sketches/rIsRh01Vj) • [@openProcessing](https://openprocessing.org/sketch/2684135)
84 |
85 | 
86 |
87 | ---
88 |
89 | ### [plotSvg_face_flipbook](https://openprocessing.org/sketch/2488219)
90 |
91 | Exports a tiny flipbook recording from a face-tracker.
[here](plotSvg_face_flipbook/sketch.js) • [@openProcessing](https://openprocessing.org/sketch/2488219)
92 |
93 | 
94 |
95 |
--------------------------------------------------------------------------------
/examples/plotSvg_hello_animating/plotSvg_hello_animating.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_face_flipbook/sketch.js:
--------------------------------------------------------------------------------
1 | // Software which exports an SVG flipbook showing a
2 | // two-second recording from a face-tracker, using
3 | // p5.js v.1.11.10 + MediaPipe v.0.10.17 + p5.plotSvg v.0.1.5
4 |
5 | //------------------------------------------
6 | let myFaceLandmarker;
7 | let faceLandmarks;
8 | let myCapture;
9 | let lastVideoTime = -1;
10 | let bDoExportSvg = false;
11 |
12 | const DPI = 96;
13 | const margin = DPI*0.5;
14 | const nPageCols = 6;
15 | const nPageRows = 9;
16 | const nPages = nPageCols * nPageRows;
17 |
18 | let faces = [];
19 | let currentFrameIndex = 0;
20 | p5.disableFriendlyErrors = true;
21 |
22 | //------------------------------------------
23 | const trackingConfig = {
24 | doAcquireFaceMetrics: true,
25 | cpuOrGpuString: "GPU", /* "GPU" or "CPU" */
26 | maxNumFaces: 1,
27 | };
28 | //------------------------------------------
29 | async function preload() {
30 | const mediapipe_module = await import('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.js');
31 | FaceLandmarker = mediapipe_module.FaceLandmarker;
32 | FilesetResolver = mediapipe_module.FilesetResolver;
33 | const vision = await FilesetResolver.forVisionTasks(
34 | "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.17/wasm"
35 | );
36 | // Face Landmark Tracking:
37 | // https://developers.google.com/mediapipe/solutions/vision/face_landmarker
38 | myFaceLandmarker = await FaceLandmarker.createFromOptions(vision, {
39 | numFaces: trackingConfig.maxNumFaces,
40 | runningMode: "VIDEO",
41 | outputFaceBlendshapes:trackingConfig.doAcquireFaceMetrics,
42 | baseOptions: {
43 | delegate: trackingConfig.cpuOrGpuString,
44 | modelAssetPath:
45 | "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task",
46 | },
47 | });
48 | }
49 |
50 | //------------------------------------------
51 | async function predictWebcam() {
52 | let startTimeMs = performance.now();
53 | if (lastVideoTime !== myCapture.elt.currentTime) {
54 | if (myFaceLandmarker) {
55 | faceLandmarks = myFaceLandmarker.detectForVideo(myCapture.elt,startTimeMs);
56 | }
57 | lastVideoTime = myCapture.elt.currentTime;
58 | }
59 | window.requestAnimationFrame(predictWebcam);
60 | }
61 |
62 | //------------------------------------------
63 | function keyPressed(){
64 | bDoExportSvg = false;
65 | currentFrameIndex = 0;
66 | faces = [];
67 | }
68 |
69 | //------------------------------------------
70 | function setup() {
71 | createCanvas(11*DPI, 8.5*DPI);
72 | myCapture = createCapture(VIDEO);
73 | myCapture.size(320, 240);
74 | myCapture.hide();
75 | setSvgDefaultStrokeWeight(0.25);
76 | }
77 |
78 | //------------------------------------------
79 | function draw() {
80 | background(245);
81 | noFill();
82 | stroke("black");
83 | strokeWeight(0.5);
84 |
85 | predictWebcam();
86 | storeCurrentFace();
87 | text("Press a key to restart recording.",10,30);
88 |
89 | if (bDoExportSvg){
90 | beginRecordSvg(this, "plotSvg_face_flipbook.svg");
91 | }
92 |
93 | drawVideoBackground();
94 | drawFlipbookPageBoundaries();
95 | drawFlipbookPages();
96 |
97 | if (bDoExportSvg){
98 | endRecordSvg();
99 | bDoExportSvg = false;
100 | }
101 | }
102 |
103 | //------------------------------------------
104 | function drawFlipbookPageBoundaries(){
105 | let rw = (width-2*margin)/nPageCols;
106 | let rh = (height-2*margin)/nPageRows;
107 | let f = 0;
108 | noFill();
109 | stroke(0);
110 | for (let col=0; col 0) {
165 | for (let f = 0; f < nFaces; f++) {
166 | let aFace = faceLandmarks.faceLandmarks[f];
167 | if (aFace) {
168 | let nFrames = nPageCols*nPageRows;
169 | if (faces.length < nFrames){
170 | faces.push(aFace);
171 | if (faces.length == nFrames){
172 | bDoExportSvg = true;
173 | }
174 | }
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | //------------------------------------------
182 | function drawFace(rx,ry, rw,rh, aFace){
183 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE);
184 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_RIGHT_EYEBROW);
185 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_LEFT_EYE);
186 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_LEFT_EYEBROW);
187 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_FACE_OVAL);
188 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_LIPS);
189 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS);
190 | drawConnectors(rx,ry, rw,rh, aFace, FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS);
191 | drawConnectors(rx,ry, rw,rh, aFace, FACELANDMARKER_NOSE);
192 | }
193 |
194 | //------------------------------------------
195 | function drawConnectors(rx,ry,rw,rh, landmarks, connectorSet) {
196 | if (landmarks) {
197 | let nConnectors = connectorSet.length;
198 | for (let i=0; i
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_hatched_shapes/sketch.js:
--------------------------------------------------------------------------------
1 | // plotSvg_hatched_shapes Hatched Shapes Example
2 | // Requires https://cdn.jsdelivr.net/npm/p5.plotsvg@latest/lib/p5.plotSvg.js
3 | // Golan Levin, December 2024
4 | //
5 | // This sketch presents a hack for hatching SVG shapes.
6 | // Note: This method uses pixel analysis and is resolution-dependent.
7 | //
8 | // Click mouse or press ' ' to get a new composition
9 | // Press 'd' to toggle debug view.
10 | // Press 's' to export SVG.
11 |
12 | let bDoExportSvg = false;
13 | let bShowDebug = false;
14 |
15 | let HATCH_INTERVAL = 3; // the hatch spacing
16 | let HATCH_ANGLE = 0;
17 |
18 | let W, H;
19 | let shapes = [];
20 | let hatchBuffer;
21 | let hatchLines;
22 | let exportCount = 0;
23 | let myRandomSeed = 12345678;
24 |
25 | p5.disableFriendlyErrors = true;
26 | //======================================
27 | function setup() {
28 | createCanvas(6 * 96, 4 * 96); // Postcard, 6"x4" @96dpi
29 | W = width;
30 | H = height;
31 | hatchBuffer = createGraphics(W*2, H*2, P2D);
32 | hatchBuffer.pixelDensity(1);
33 | hatchLines = [];
34 | makeThreeNewShapes();
35 |
36 | // Set values for our SVG export:
37 | setSvgCoordinatePrecision(4);
38 | setSvgIndent(SVG_INDENT_SPACES, 2);
39 | setSvgDefaultStrokeColor('black');
40 | setSvgDefaultStrokeWeight(1);
41 | }
42 |
43 |
44 | //======================================
45 | function makeFilledShape() {
46 | // Note: shapes must not overlap edge of canvas
47 | // or else significant extra effort must be made.
48 | let pointsX = [];
49 | let pointsY = [];
50 | let nPts = int(round(random(5, 8)));
51 |
52 | let cx = random(0.25, 0.75) * W;
53 | let cy = random(0.25, 0.75) * H;
54 | for (let i = 0; i < nPts; i++) {
55 | let t = map(i, 0, nPts, 0, TWO_PI);
56 | let rx = random(0.10, 0.25) * W;
57 | let ry = random(0.10, 0.25) * H;
58 | let px = cx + rx * cos(t);
59 | let py = cy + ry * sin(t);
60 | pointsX[i] = px;
61 | pointsY[i] = py;
62 | }
63 | shapes.push([pointsX, pointsY]);
64 | }
65 |
66 |
67 | //======================================
68 | function makeThreeNewShapes(){
69 | randomSeed(myRandomSeed);
70 | shapes = [];
71 | makeFilledShape();
72 | makeFilledShape();
73 | makeFilledShape();
74 | }
75 |
76 |
77 | //======================================
78 | function draw() {
79 | background(245);
80 |
81 | if (bDoExportSvg) {
82 | let svgFilename = "plotSvg_hatched_shapes_" + nf(exportCount,3) + ".svg";
83 | beginRecordSvg(this, svgFilename);
84 | }
85 |
86 | stroke(0);
87 | drawShapeOutlines();
88 | drawShapeHatchlines();
89 |
90 | if (bDoExportSvg) {
91 | endRecordSvg();
92 | save("plotSvg_hatched_shapes_" + nf(exportCount,3) + ".png");
93 | bDoExportSvg = false;
94 | exportCount++;
95 | }
96 | }
97 |
98 |
99 | //======================================
100 | function drawShapeOutlines(){
101 | for (let s = 0; s < shapes.length; s++) {
102 | let pointsX = shapes[s][0];
103 | let pointsY = shapes[s][1];
104 |
105 | noFill();
106 | stroke(0);
107 | beginShape();
108 | for (let i = 0; i < pointsX.length; i++) {
109 | let px = pointsX[i];
110 | let py = pointsY[i];
111 | vertex(px, py);
112 | }
113 | endShape(CLOSE);
114 | }
115 | }
116 |
117 |
118 | //======================================
119 | function drawShapeHatchlines(){
120 | for (let s=0; s= 3) {
159 | hatchBuffer.background(0, 0, 0);
160 | hatchBuffer.fill(255);
161 | hatchBuffer.noStroke();
162 | hatchBuffer.push();
163 | hatchBuffer.translate(cx, cy);
164 | hatchBuffer.rotate(HATCH_ANGLE);
165 | hatchBuffer.translate(-cx, -cy);
166 |
167 | hatchBuffer.push();
168 | hatchBuffer.translate(W/2, H/2);
169 | hatchBuffer.beginShape();
170 | for (let i=0; i= 128 && prevR < 128) {
196 | hatchLines.push(createVector(x + 1, y)); // line start
197 | bActive = true;
198 | } else if (currR < 128 && prevR >= 128 && bActive) {
199 | hatchLines.push(createVector(x - 1, y)); // line end
200 | bActive = false;
201 | }
202 | }
203 | prevR = currR;
204 | }
205 | }
206 |
207 | // 3. Un-rotate the hatch lines.
208 | for (let i = 0; i < hatchLines.length; i += 2) {
209 | let st = hatchLines[i]; // start
210 | let en = hatchLines[i+1]; // end
211 | let sxo = st.x - cx;
212 | let syo = st.y - cy;
213 | let sxr = sxo * Math.cos(-HATCH_ANGLE) - syo * Math.sin(-HATCH_ANGLE) + cx;
214 | let syr = syo * Math.cos(-HATCH_ANGLE) + sxo * Math.sin(-HATCH_ANGLE) + cy;
215 | let exo = en.x - cx;
216 | let eyo = en.y - cy;
217 | let exr = exo * Math.cos(-HATCH_ANGLE) - eyo * Math.sin(-HATCH_ANGLE) + cx;
218 | let eyr = eyo * Math.cos(-HATCH_ANGLE) + exo * Math.sin(-HATCH_ANGLE) + cy;
219 | hatchLines[i].set(sxr, syr);
220 | hatchLines[i+1].set(exr, eyr);
221 | }
222 | for (let i = 0; i < hatchLines.length; i++) {
223 | let px = hatchLines[i].x - W/2;
224 | let py = hatchLines[i].y - H/2;
225 | hatchLines[i].set(px, py);
226 | }
227 | }
228 |
229 |
230 | //======================================
231 | function mousePressed(){
232 | myRandomSeed = round(millis());
233 | makeThreeNewShapes();
234 | }
235 | function keyPressed() {
236 | if (key == " ") {
237 | makeThreeNewShapes();
238 | } else if (key == "s") {
239 | bDoExportSvg = true;
240 | } else if (key == "d") {
241 | bShowDebug = !bShowDebug;
242 | }
243 | }
--------------------------------------------------------------------------------
/examples/plotSvg_powerstroke_WIP/sketchUI.js:
--------------------------------------------------------------------------------
1 | //=========================================================
2 | // USER INTERFACE
3 | let spineModeRadio, spineModeRadioLabel;
4 | let envModeRadio, envModeRadioLabel;
5 | let maxThicknessSlider, maxThicknessSliderLabel;
6 | let fitCurveSlider, fitCurveSliderLabel;
7 | let betaSlider, betaSliderLabel;
8 | let envEmphasisSlider, envEmphasisSliderLabel;
9 | let envContrastSlider, envContrastSliderLabel;
10 | let envScaleSlider, envScaleSliderLabel;
11 | let envOffsetSlider, envOffsetSliderLabel;
12 | let envStepSlider, envStepSliderLabel;
13 | let filterButton, clearButton, resetButton;
14 |
15 | function updateSliders(){
16 | maxThicknessSliderLabel.html("Width Multiplier: " + nf(maxThicknessSlider.value(), 1, 3));
17 | fitCurveSliderLabel.html("Spine Fit Error: " + nf(fitCurveSlider.value(), 1, 3));
18 | betaSliderLabel.html("Envelope Smoothness: " + nf(betaSlider.value(), 1, 3));
19 | envStepSliderLabel.html("Envelope Step: " + nf(envStepSlider.value(), 1, 3));
20 | envEmphasisSliderLabel.html("Emphasis: " + nf(envEmphasisSlider.value(), 1, 3));
21 | envContrastSliderLabel.html("Contrast: " + nf(envContrastSlider.value(), 1, 3));
22 | envScaleSliderLabel.html("Scale: " + nf(envScaleSlider.value(), 1, 3));
23 | envOffsetSliderLabel.html("Offset: " + nf(envOffsetSlider.value(), 1, 3));
24 | }
25 |
26 | function createUserInterface(){
27 | createSliders();
28 | createSpineModeRadio();
29 | createEnvelopeModeRadio();
30 | createSpineModeRadio();
31 | createButtons();
32 | }
33 |
34 | //--------------------
35 | function createSpineModeRadio(){
36 | spineModeRadio = createRadio();
37 | spineModeRadio.class('p5-radio');
38 | spineModeRadio.position(7, 10);
39 | spineModeRadio.size(300);
40 | spineModeRadio.option(SPINE_MODE_LINEAR, 'Polyline');
41 | spineModeRadio.option(SPINE_MODE_BEZIER, 'PolyBezier');
42 | spineModeRadio.selected(String(SPINE_MODE_LINEAR));
43 | spineModeRadio.elt.addEventListener('mousedown', (e) => e.stopPropagation());
44 | spineModeRadio.changed(() => {
45 | SPINE_MODE = Number(spineModeRadio.value());
46 | fitCurveSlider.elt.disabled = (SPINE_MODE != SPINE_MODE_BEZIER);
47 | });
48 | fitCurveSlider.elt.disabled = (SPINE_MODE != SPINE_MODE_BEZIER);
49 | spineModeRadioLabel = createDiv('Spine Construction');
50 | spineModeRadioLabel.position(220, 12);
51 | }
52 |
53 | //--------------------
54 | function createEnvelopeModeRadio() {
55 | envModeRadio = createRadio();
56 | envModeRadio.class('p5-radio');
57 | envModeRadio.position(7, 30);
58 | envModeRadio.size(300);
59 | envModeRadio.option(ENV_INTERP_LINEAR, 'Linear');
60 | envModeRadio.option(ENV_INTERP_BEZIER_JOHAN, 'CubicBezierJohan');
61 | envModeRadio.selected(String(ENV_INTERP_LINEAR));
62 | envModeRadio.elt.addEventListener('mousedown', (e) => e.stopPropagation());
63 | envModeRadio.changed(() => {
64 | ENV_INTERP_MODE = Number(envModeRadio.value());
65 | betaSlider.elt.disabled = (ENV_INTERP_MODE != ENV_INTERP_BEZIER_JOHAN);
66 | });
67 | betaSlider.elt.disabled = (ENV_INTERP_MODE != ENV_INTERP_BEZIER_JOHAN);
68 | envModeRadioLabel = createDiv('Envelope Smoothing Type');
69 | envModeRadioLabel.position(220, 32);
70 | }
71 |
72 |
73 | function setRadioOption(radio, valueToDisable, torf) {
74 | const inputs = radio.elt.querySelectorAll('input[type="radio"]');
75 | for (let input of inputs) {
76 | if (input.value === String(valueToDisable)) {
77 | input.disabled = !torf;
78 | break;
79 | }
80 | }
81 | }
82 |
83 | //--------------------
84 | function createSliders(){
85 | let sy = 110;
86 | const dy = 20;
87 |
88 | envStepSlider = createSlider(0.25,20, 3, 0.25);
89 | envStepSlider.position(10, sy);
90 | envStepSlider.size(200);
91 | envStepSlider.elt.addEventListener(
92 | 'mousedown', (e) => e.stopPropagation());
93 | envStepSliderLabel = createDiv('Envelope Step');
94 | envStepSliderLabel.position(220, sy);
95 | sy += dy;
96 |
97 | maxThicknessSlider = createSlider(1,100, 50, 1);
98 | maxThicknessSlider.position(10, sy);
99 | maxThicknessSlider.size(200);
100 | maxThicknessSlider.elt.addEventListener(
101 | 'mousedown', (e) => e.stopPropagation());
102 | maxThicknessSliderLabel = createDiv('Width Multiplier');
103 | maxThicknessSliderLabel.position(220, sy);
104 | sy += dy;
105 |
106 | fitCurveSlider = createSlider(0.1,20, 4, 0.1);
107 | fitCurveSlider.position(10, sy);
108 | fitCurveSlider.size(200);
109 | fitCurveSlider.elt.addEventListener(
110 | 'mousedown', (e) => e.stopPropagation());
111 | fitCurveSliderLabel = createDiv('Spine Fit Error');
112 | fitCurveSliderLabel.position(220, sy);
113 | sy += dy;
114 |
115 | betaSlider = createSlider(0,1, 0.25, 0.005);
116 | betaSlider.position(10, sy);
117 | betaSlider.size(200);
118 | betaSlider.elt.addEventListener(
119 | 'mousedown', (e) => e.stopPropagation());
120 | betaSliderLabel = createDiv('Envelope Smoothness');
121 | betaSliderLabel.position(220, sy);
122 | sy += dy;
123 |
124 | envEmphasisSlider = createSlider(-1,1, 0.0, 0.005);
125 | envEmphasisSlider.position(10, sy);
126 | envEmphasisSlider.size(200);
127 | envEmphasisSlider.elt.addEventListener(
128 | 'mousedown', (e) => e.stopPropagation());
129 | envEmphasisSliderLabel = createDiv('Emphasis');
130 | envEmphasisSliderLabel.position(220, sy);
131 | sy += dy;
132 |
133 | envContrastSlider = createSlider(-1,1, 0.0, 0.005);
134 | envContrastSlider.position(10, sy);
135 | envContrastSlider.size(200);
136 | envContrastSlider.elt.addEventListener(
137 | 'mousedown', (e) => e.stopPropagation());
138 | envContrastSliderLabel = createDiv('Contrast');
139 | envContrastSliderLabel.position(220, sy);
140 | sy += dy;
141 |
142 | envScaleSlider = createSlider(0,2, 1, 0.005);
143 | envScaleSlider.position(10, sy);
144 | envScaleSlider.size(200);
145 | envScaleSlider.elt.addEventListener(
146 | 'mousedown', (e) => e.stopPropagation());
147 | envScaleSliderLabel = createDiv('Scale');
148 | envScaleSliderLabel.position(220, sy);
149 | sy += dy;
150 |
151 | envOffsetSlider = createSlider(-1,1, 0, 0.005);
152 | envOffsetSlider.position(10, sy);
153 | envOffsetSlider.size(200);
154 | envOffsetSlider.elt.addEventListener(
155 | 'mousedown', (e) => e.stopPropagation());
156 | envOffsetSliderLabel = createDiv('Offset');
157 | envOffsetSliderLabel.position(220, sy);
158 | sy += dy;
159 | }
160 |
161 | //--------------------
162 | function createButtons(){
163 | let filterButton = createButton('Filter');
164 | let clearButton = createButton('Clear');
165 | let resetButton = createButton('Reset');
166 | filterButton.position(10, 80);
167 | clearButton.position(60, 80);
168 | resetButton.position(110, 80);
169 |
170 |
171 | filterButton.elt.addEventListener(
172 | 'mousedown', (e) => e.stopPropagation());
173 | clearButton.elt.addEventListener(
174 | 'mousedown', (e) => e.stopPropagation());
175 | resetButton.elt.addEventListener(
176 | 'mousedown', (e) => e.stopPropagation());
177 |
178 | filterButton.mousePressed(() => {
179 | filterCurrentPowerStroke();
180 | });
181 | clearButton.mousePressed(() => {
182 | clearCurrentPowerStroke();
183 | });
184 | resetButton.mousePressed(() => {
185 | envStepSlider.value(3);
186 | maxThicknessSlider.value(50);
187 | fitCurveSlider.value(4);
188 | betaSlider.value(0.25);
189 | envEmphasisSlider.value(0);
190 | envContrastSlider.value(0);
191 | envScaleSlider.value(1);
192 | envOffsetSlider.value(0);
193 | });
194 | }
195 |
196 |
--------------------------------------------------------------------------------
/examples/plotSvg_hatched_shapes/README.md:
--------------------------------------------------------------------------------
1 | # plotSvg_hatched_shapes Example
2 |
3 | The `plotSvg_hatched_shapes` example presents a hack for hatching SVG shapes, i.e. approximating a simple "fill" using a pen plotter. The hatching method allows for both concave and convex shapes. For more ideas about how to "fill" (hatch) shapes for a pen-plotter, consider [these notes](https://github.com/golanlevin/DrawingWithMachines/blob/main/assignments/05_tone/README.md).
4 |
5 | **Note**: this method uses pixel analysis to implement the hatching, and is resolution-dependent. The method works by tracing scanlines in an offscreen buffer. To make the hatching denser or sparser, change the integer variable `HATCH_INTERVAL`.
6 |
7 | 
8 |
9 | Code:
10 |
11 | * At editor.p5js.org: [https://editor.p5js.org/golan/sketches/b75oVci5f](https://editor.p5js.org/golan/sketches/b75oVci5f)
12 | * At openprocessing.org: [https://openprocessing.org/sketch/2479519](https://openprocessing.org/sketch/2479519)
13 | * At GitHub: [sketch.js](https://raw.githubusercontent.com/golanlevin/p5.plotSvg/refs/heads/main/examples/plotSvg_hatched_shapes/sketch.js)
14 |
15 | ```js
16 | // plotSvg_hatched_shapes Hatched Shapes Example
17 | // Requires https://cdn.jsdelivr.net/npm/p5.plotsvg@latest/lib/p5.plotSvg.js
18 | // Golan Levin, December 2024
19 | //
20 | // This sketch presents a hack for hatching SVG shapes.
21 | // Note: This method uses pixel analysis and is resolution-dependent.
22 | //
23 | // Click mouse or press ' ' to get a new composition
24 | // Press 'd' to toggle debug view.
25 | // Press 's' to export SVG.
26 |
27 | let bDoExportSvg = false;
28 | let bShowDebug = false;
29 |
30 | let HATCH_INTERVAL = 3; // the hatch spacing. Must be an integer.
31 | let HATCH_ANGLE = 0;
32 |
33 | let W, H;
34 | let shapes = [];
35 | let hatchBuffer;
36 | let hatchLines;
37 | let exportCount = 0;
38 |
39 | p5.disableFriendlyErrors = true;
40 | //======================================
41 | function setup() {
42 | createCanvas(6 * 96, 4 * 96); // Postcard, 6"x4" @96dpi
43 | W = width;
44 | H = height;
45 | hatchBuffer = createGraphics(W*2, H*2, P2D);
46 | hatchBuffer.pixelDensity(1);
47 | hatchLines = [];
48 | makeThreeNewShapes();
49 |
50 | // Set values for our SVG export:
51 | setSvgCoordinatePrecision(4);
52 | setSvgIndent(SVG_INDENT_SPACES, 2);
53 | setSvgDefaultStrokeColor('black');
54 | setSvgDefaultStrokeWeight(1);
55 | }
56 |
57 |
58 | //======================================
59 | function makeFilledShape() {
60 | // Note: shapes must not overlap edge of canvas
61 | // or else significant extra effort must be made.
62 | let pointsX = [];
63 | let pointsY = [];
64 | let nPts = int(round(random(5, 8)));
65 |
66 | let cx = random(0.25, 0.75) * W;
67 | let cy = random(0.25, 0.75) * H;
68 | for (let i = 0; i < nPts; i++) {
69 | let t = map(i, 0, nPts, 0, TWO_PI);
70 | let rx = random(0.10, 0.25) * W;
71 | let ry = random(0.10, 0.25) * H;
72 | let px = cx + rx * cos(t);
73 | let py = cy + ry * sin(t);
74 | pointsX[i] = px;
75 | pointsY[i] = py;
76 | }
77 | shapes.push([pointsX, pointsY]);
78 | }
79 |
80 |
81 | //======================================
82 | function makeThreeNewShapes(){
83 | shapes = [];
84 | makeFilledShape();
85 | makeFilledShape();
86 | makeFilledShape();
87 | }
88 |
89 |
90 | //======================================
91 | function draw() {
92 | background(245);
93 |
94 | if (bDoExportSvg) {
95 | let svgFilename = "plotSvg_hatched_shapes" + nf(exportCount,3) + ".svg";
96 | beginRecordSvg(this, svgFilename);
97 | exportCount++;
98 | }
99 |
100 | stroke(0);
101 | drawShapeOutlines();
102 | drawShapeHatchlines();
103 |
104 | if (bDoExportSvg) {
105 | endRecordSvg();
106 | bDoExportSvg = false;
107 | }
108 | }
109 |
110 |
111 | //======================================
112 | function drawShapeOutlines(){
113 | for (let s = 0; s < shapes.length; s++) {
114 | let pointsX = shapes[s][0];
115 | let pointsY = shapes[s][1];
116 |
117 | noFill();
118 | stroke(0);
119 | beginShape();
120 | for (let i = 0; i < pointsX.length; i++) {
121 | let px = pointsX[i];
122 | let py = pointsY[i];
123 | vertex(px, py);
124 | }
125 | endShape(CLOSE);
126 | }
127 | }
128 |
129 |
130 | //======================================
131 | function drawShapeHatchlines(){
132 | for (let s=0; s= 3) {
169 | hatchBuffer.background(0, 0, 0);
170 | hatchBuffer.fill(255);
171 | hatchBuffer.noStroke();
172 | hatchBuffer.push();
173 | hatchBuffer.translate(cx, cy);
174 | hatchBuffer.rotate(HATCH_ANGLE);
175 | hatchBuffer.translate(-cx, -cy);
176 |
177 | hatchBuffer.push();
178 | hatchBuffer.translate(W/2, H/2);
179 | hatchBuffer.beginShape();
180 | for (let i=0; i= 128 && prevR < 128) {
206 | hatchLines.push(createVector(x + 1, y)); // line start
207 | bActive = true;
208 | } else if (currR < 128 && prevR >= 128 && bActive) {
209 | hatchLines.push(createVector(x - 1, y)); // line end
210 | bActive = false;
211 | }
212 | }
213 | prevR = currR;
214 | }
215 | }
216 |
217 | // 3. Un-rotate the hatch lines.
218 | for (let i = 0; i < hatchLines.length; i += 2) {
219 | let st = hatchLines[i]; // start
220 | let en = hatchLines[i+1]; // end
221 | let sxo = st.x - cx;
222 | let syo = st.y - cy;
223 | let sxr = sxo * Math.cos(-HATCH_ANGLE) - syo * Math.sin(-HATCH_ANGLE) + cx;
224 | let syr = syo * Math.cos(-HATCH_ANGLE) + sxo * Math.sin(-HATCH_ANGLE) + cy;
225 | let exo = en.x - cx;
226 | let eyo = en.y - cy;
227 | let exr = exo * Math.cos(-HATCH_ANGLE) - eyo * Math.sin(-HATCH_ANGLE) + cx;
228 | let eyr = eyo * Math.cos(-HATCH_ANGLE) + exo * Math.sin(-HATCH_ANGLE) + cy;
229 | hatchLines[i].set(sxr, syr);
230 | hatchLines[i+1].set(exr, eyr);
231 | }
232 | for (let i = 0; i < hatchLines.length; i++) {
233 | let px = hatchLines[i].x - W/2;
234 | let py = hatchLines[i].y - H/2;
235 | hatchLines[i].set(px, py);
236 | }
237 | }
238 |
239 |
240 | //======================================
241 | function mousePressed(){
242 | makeThreeNewShapes();
243 | }
244 | function keyPressed() {
245 | if (key == " ") {
246 | makeThreeNewShapes();
247 | } else if (key == "s") {
248 | bDoExportSvg = true;
249 | save(); // also save a PNG
250 | } else if (key == "d") {
251 | bShowDebug = !bShowDebug;
252 | }
253 | }
254 | ```
255 |
--------------------------------------------------------------------------------
/examples/plotSvg_generative/plotSvg_generative.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_powerstroke_WIP/sketch.js:
--------------------------------------------------------------------------------
1 | // WORK IN PROGRESS; NOTHING TO SEE HERE YET
2 |
3 | /*
4 | Todo:
5 | * If bezier spine is selected, when exporting, clobber spinePts with resampled bezier points.
6 | * Demonstrate pre-hoc / post-hoc offset point addition modes.
7 |
8 | */
9 |
10 | p5.disableFriendlyErrors = true;
11 | let bDoExportSvg = false;
12 | let myPowerStrokes = []; // Array to hold loaded PowerStroke instances
13 | let currentPowerStrokeIndex = null; // Currently selected PowerStroke for editing
14 |
15 | //---------------------------------------------------------------
16 | function setup() {
17 | createCanvas(1280, 800);
18 | createUserInterface();
19 |
20 | // DEMO 1.
21 | // Load a readymade PowerStroke instance from an SVG file.
22 | // This may end up being a long-running operation, so we do it asynchronously.
23 | // As a result, the loaded PowerStroke may appear at the end of the array.
24 | let bLoadFromFile = true; // Set to true to load from SVG file
25 | if (bLoadFromFile) {
26 | loadPowerStrokesFromSvgFile("svg/plotSvg_powerstroke_wavy.svg")
27 | .then(result => {
28 | let loadedCount = 0;
29 | if (Array.isArray(result)) {
30 | for (let i = 0; i < result.length; i++) {
31 | myPowerStrokes.push(result[i]);
32 | loadedCount++;
33 | }
34 | }
35 | })
36 | .catch(err => {
37 | console.error("⚠️ Error loading PowerStrokes:", err);
38 | });
39 | }
40 |
41 | // DEMO 2.
42 | // Create a PowerStroke in which the offset points are added afterwards
43 | // using addOffsetPt(), which wants indexes (0...N-1) as t-values.
44 | let powerStroke0 = new PowerStroke(OFFSET_POINT_ADD_ASYNC);
45 | powerStroke0.setPowerStrokeWeight(40.0);
46 | powerStroke0.setEnvInterpolatorType("Linear");
47 | powerStroke0.addSpinePt(100, 650);
48 | powerStroke0.addSpinePt(200, 550);
49 | powerStroke0.addSpinePt(350, 560);
50 | powerStroke0.addSpinePt(400, 660);
51 | powerStroke0.addSpinePt(500, 650);
52 | powerStroke0.addOffsetPt(0.00, 1.00);
53 | powerStroke0.addOffsetPt(0.60, 0.30);
54 | powerStroke0.addOffsetPt(1.00, 0.60);
55 | powerStroke0.addOffsetPt(2.00, 0.20);
56 | powerStroke0.addOffsetPt(3.50, 0.40);
57 | powerStroke0.addOffsetPt(4.00, 0.80);
58 | myPowerStrokes.push(powerStroke0); // Add the PowerStroke to the array
59 |
60 |
61 | // DEMO 3.
62 | // Create a PowerStroke in which the offset points are added afterwards
63 | // using addNormalizedOffsetPt(), which wants t-values in [0,1] as normalized values.
64 | let powerStroke1 = new PowerStroke(OFFSET_POINT_ADD_ASYNC);
65 | powerStroke1.setPowerStrokeWeight(40.0);
66 | powerStroke1.setEnvInterpolatorType("Linear");
67 | let nSpinePts1 = 50;
68 | for (let i = 0; i < nSpinePts1; i++) {
69 | let px = map(pow(map(i, 0,nSpinePts1-1, 0,1), 2.0), 0,1, 500,900);
70 | let py = 200 + 50 * sin(px/60.0);
71 | powerStroke1.addSpinePt(px, py); // Add spine
72 | }
73 | noiseSeed(1234);
74 | let nOffsetPts1 = 9; // Number of offset points to add
75 | for (let j=0; j= 0 && currentPowerStrokeIndex < myPowerStrokes.length) {
100 | let myPowerStroke = myPowerStrokes[currentPowerStrokeIndex];
101 | // myPowerStroke.addSpinePt(mouseX, mouseY);
102 | let px = mouseX;
103 | let py = mouseY;
104 | let pr = 0;
105 | myPowerStroke.addSpineAndOffsetPt(px, py, pr);
106 | mx = mouseX;
107 | my = mouseY;
108 | oldP = 0;
109 | }
110 | }
111 |
112 | function mouseDragged() {
113 | if (currentPowerStrokeIndex >= 0 && currentPowerStrokeIndex < myPowerStrokes.length) {
114 | let myPowerStroke = myPowerStrokes[currentPowerStrokeIndex];
115 | // myPowerStroke.addSpinePt(mouseX, mouseY);
116 |
117 | let px = mx; // Previous mouse position
118 | let py = my; // Previous mouse position
119 | mx = 0.75 * mx + 0.25 * mouseX; // Smooth mouse movement
120 | my = 0.75 * my + 0.25 * mouseY;
121 |
122 | let vel = 2*dist(px, py, mx, my);
123 | var sca = maxThicknessSlider.value();
124 | var minP = 0.02;
125 | let damp = 5.0;
126 | let dampInv = 1.0 / damp;
127 | let damp1 = damp - 1;
128 | let th = ((minP + max(0, 1.0 - vel/sca)) + (damp1 * oldP)) * dampInv;
129 | oldP = th;
130 |
131 | let pr = th;
132 | // pr = map(sin(millis()/50.0), -1,1, 0.2, 1.0); // Example radius based on time
133 | myPowerStroke.addSpineAndOffsetPt(px, py, pr);
134 | }
135 | }
136 |
137 |
138 |
139 |
140 | function keyPressed(){
141 | if (key == 's'){ // Initiate SVG exporting
142 | bDoExportSvg = true;
143 | }
144 | if (key == 'f'){ // Filter the current PowerStroke
145 | filterCurrentPowerStroke();
146 | }
147 | if (key == ' '){ // Clear the current PowerStroke
148 | clearCurrentPowerStroke();
149 | }
150 | if (key == 'b'){
151 | if (currentPowerStrokeIndex >= 0 && currentPowerStrokeIndex < myPowerStrokes.length) {
152 | let myPowerStroke = myPowerStrokes[currentPowerStrokeIndex];
153 | let step = envStepSlider.value() || 3; // Default step value if slider is not set
154 | myPowerStroke.resamplePolylineFromBezierAndReindexOffsetPts(step);
155 | }
156 | }
157 | }
158 |
159 | //---------------------------------------------------------------
160 | function draw(){
161 | background(245);
162 | updateSliders();
163 | updatePowerStrokes(); // Update the PowerStroke instances based on slider values
164 |
165 | if (bDoExportSvg){
166 | beginRecordSvg(this, "plotSvg_powerstroke.svg");
167 | }
168 |
169 | fill(200,200,200, 120);
170 | stroke(0,0,255, 80);
171 |
172 | for (let i=0; i < myPowerStrokes.length; i++){
173 | let aPowerStroke = myPowerStrokes[i];
174 |
175 | // Draw a visual representation of the PowerStroke.
176 | // If bEmitDebugViewToSvg is true, this will also emit a debug view to the SVG.
177 | // NOTE THAT EMITTING THE DEBUGVIEW TO SVG IS NOT THE SAME THING AS SAVING THE POWERSTROKE TO SVG.
178 | aPowerStroke.drawDebugView (
179 | true, /* bEmitDebugViewToSvg */
180 | true, /* bDrawEnvelope */
181 | true, /* bDrawEnvelopeSpans */
182 | true, /* bDrawSpineLine */
183 | true, /* bDrawSpinePts */
184 | true, /* bDrawShapedOffsetPts */
185 | );
186 |
187 | // Save the actual PowerStroke to the current SVG, as a PowerStroke element.
188 | aPowerStroke.addToCurrentSvg();
189 | }
190 |
191 | if (bDoExportSvg){
192 | endRecordSvg();
193 | bDoExportSvg = false;
194 | }
195 | }
196 |
197 | //---------------------------------------------------------------
198 | function updatePowerStrokes() {
199 | for (let i = 0; i < myPowerStrokes.length; i++) {
200 | let aPowerStroke = myPowerStrokes[i];
201 | aPowerStroke.setPowerStrokeWeight(maxThicknessSlider.value());
202 | aPowerStroke.setFitCurveMaxError(fitCurveSlider.value());
203 | aPowerStroke.setBezierInterpBeta(betaSlider.value());
204 | aPowerStroke.setSpineMode(SPINE_MODE);
205 |
206 | const step = envStepSlider.value();
207 | aPowerStroke.computeRadii(step);
208 | aPowerStroke.computeEnvelope();
209 |
210 | let e = envEmphasisSlider.value();
211 | let c = envContrastSlider.value();
212 | let s = envScaleSlider.value();
213 | let o = envOffsetSlider.value();
214 | aPowerStroke.setShapingParams(e,c,s,o);
215 |
216 | if (ENV_INTERP_MODE == ENV_INTERP_LINEAR){
217 | aPowerStroke.setEnvInterpolatorType("Linear");
218 | } else {
219 | aPowerStroke.setEnvInterpolatorType("CubicBezierJohan")
220 | }
221 | }
222 | }
223 |
224 |
225 | function filterCurrentPowerStroke() {
226 | if (currentPowerStrokeIndex >= 0 && currentPowerStrokeIndex < myPowerStrokes.length) {
227 | let myPowerStroke = myPowerStrokes[currentPowerStrokeIndex];
228 | myPowerStroke.filterSpine();
229 | }
230 | }
231 |
232 | function clearCurrentPowerStroke() {
233 | if (currentPowerStrokeIndex >= 0 && currentPowerStrokeIndex < myPowerStrokes.length) {
234 | let myPowerStroke = myPowerStrokes[currentPowerStrokeIndex];
235 | myPowerStroke.clear();
236 | }
237 | }
238 |
239 |
--------------------------------------------------------------------------------
/examples/plotSvg_instancemode/plotSvg_instancemode.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_powerstroke_WIP/svg/plotSvg_powerstroke_wavy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_svg_font_text/sketch.js:
--------------------------------------------------------------------------------
1 | // Load, display, and export SVG designs with SVG 1.1 Fonts,
2 | // as specified in https://www.w3.org/TR/SVG11/fonts.html
3 |
4 | p5.disableFriendlyErrors = true; // hush, p5
5 | let bDoExportSvg = false;
6 |
7 | let mySvgFont;
8 | function preload() {
9 |
10 | //// Here are some SVG fonts to try:
11 | mySvgFont = new SvgFont("HersheyScript1.svg");
12 | // mySvgFont = new SvgFont("HersheySans1.svg");
13 |
14 | // Lots more SVG single-line fonts can be found at:
15 | // https://github.com/golanlevin/p5-single-line-font-resources/tree/main/p5_single_line_svg_fonts
16 | // https://gitlab.com/oskay/svg-fonts
17 | // https://github.com/isdat-type/Relief-SingleLine
18 | }
19 |
20 |
21 | function setup() {
22 | createCanvas(640, 320);
23 |
24 | let saveButton = createButton("Save SVG");
25 | saveButton.position(10, 10);
26 | saveButton.mousePressed((event) => {
27 | event.stopPropagation();
28 | bDoExportSvg = true;
29 | });
30 | }
31 |
32 |
33 | function draw() {
34 | background(245);
35 | stroke(0);
36 |
37 | if (bDoExportSvg){
38 | beginRecordSvg(this, "svg_font_text.svg");
39 | }
40 |
41 | let sca = 30;
42 | mySvgFont.drawString("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 40, 80, sca);
43 | mySvgFont.drawString("abcdefghijklmnopqrstuvwxyz", 40, 120, sca);
44 | mySvgFont.drawString("1234567890", 40, 160, sca);
45 | mySvgFont.drawString("!@#$%^&*,.?/;:'-+_", 40, 200, sca);
46 | mySvgFont.drawString("()[]{}<>|\u00A9\u00AE\u20AC", 40, 240, sca);
47 | mySvgFont.drawString("Hello World!", 40, 280, sca);
48 |
49 | if (bDoExportSvg){
50 | endRecordSvg();
51 | bDoExportSvg = false;
52 | }
53 | }
54 |
55 |
56 | function keyPressed() {
57 | if ((key === 's') || (key === 'S')) {
58 | bDoExportSvg = true;
59 | }
60 | }
61 |
62 | //=====================================================
63 | //=====================================================
64 | // Class to handle SVG font parsing and rendering
65 | class SvgFont {
66 | constructor(filePath) {
67 | this.glyphs = {};
68 | this.unitsPerEm = 1000;
69 | this.ready = false;
70 |
71 | // Load the SVG font file
72 | loadStrings(filePath, (strings) => {
73 | this.loadData(strings.join("\n"));
74 | this.ready = true;
75 | });
76 | }
77 |
78 | isReady() {
79 | return this.ready;
80 | }
81 |
82 | //---------------------------------------------------------
83 | // Load and parse the SVG font data
84 | loadData(svgData) {
85 | const parser = new DOMParser();
86 | const svgDoc = parser.parseFromString(svgData, "text/xml");
87 |
88 | // Parse the glyphs
89 | const glyphElements = svgDoc.querySelectorAll("glyph");
90 | glyphElements.forEach((glyph) => {
91 | const unicode = glyph.getAttribute("unicode");
92 | if (unicode !== null) { // Ensure glyph has a valid unicode attribute
93 | const pathData = glyph.getAttribute("d");
94 | const horizAdvX = parseFloat(glyph.getAttribute("horiz-adv-x") || 0);
95 | this.glyphs[unicode] = { d: pathData, horizAdvX };
96 | }
97 | });
98 |
99 | // Parse font-face for scale metrics
100 | const fontFace = svgDoc.querySelector("font-face");
101 | if (fontFace) {
102 | this.unitsPerEm = parseFloat(fontFace.getAttribute("units-per-em") || 1000);
103 | }
104 | }
105 |
106 | //---------------------------------------------------------
107 | // Draw a single glyph at the specified position and scale
108 | drawGlyph(pathData, x, y, sca) {
109 | const commands = pathData.match(/[A-Za-z][^A-Za-z]*/g) || [];
110 | const nCommands = commands.length;
111 | let currentX = x;
112 | let currentY = y;
113 | let prevControlX = null;
114 | let prevControlY = null;
115 |
116 | for (let i=0; i
6 |
7 | # p5.plotSvg Documentation
8 |
9 | ### Table of Contents
10 |
11 | * [beginRecordSvg](#beginrecordsvg)
12 | * [pauseRecordSvg](#pauserecordsvg)
13 | * [endRecordSvg](#endrecordsvg)
14 | * [setSvgDocumentSize](#setsvgdocumentsize)
15 | * [setSvgResolutionDPI](#setsvgresolutiondpi)
16 | * [setSvgResolutionDPCM](#setsvgresolutiondpcm)
17 | * [setSvgDefaultStrokeWeight](#setsvgdefaultstrokeweight)
18 | * [setSvgDefaultStrokeColor](#setsvgdefaultstrokecolor)
19 | * [setSvgBackgroundColor](#setsvgbackgroundcolor)
20 | * [setSvgIndent](#setsvgindent)
21 | * [setSvgFlattenTransforms](#setsvgflattentransforms)
22 | * [setSvgCoordinatePrecision](#setsvgcoordinateprecision)
23 | * [setSvgTransformPrecision](#setsvgtransformprecision)
24 | * [setSvgPointRadius](#setsvgpointradius)
25 | * [setSvgGroupByStrokeColor](#setsvggroupbystrokecolor)
26 | * [setSvgMergeNamedGroups](#setsvgmergenamedgroups)
27 | * [beginSvgGroup](#beginsvggroup)
28 | * [endSvgGroup](#endsvggroup)
29 | * [getDefaultStrokeColor](#getdefaultstrokecolor)
30 | * [isRecordingSVG](#isRecordingSVG)
31 |
32 |
33 |
38 |
39 |
40 | ---
41 |
42 |
43 | ## beginRecordSvg
44 |
45 | Begins recording SVG output for a p5.js sketch.
46 | Initializes recording state, validates and sets the output filename,
47 | and overrides p5.js drawing functions to capture drawing commands for SVG export.
48 |
49 | #### Parameters
50 | * `p5Instance` **[object][26]** A reference to the current p5.js sketch (e.g. `this`).
51 | * `fn` **[string][27]?** Optional filename for the output SVG file. The *explicit* use of `null` will prevent a file from being saved. Behavior:
52 | * `beginRecordSvg(this, "file.svg"); // saves to "file.svg"`
53 | * `beginRecordSvg(this); // saves to "output.svg" (default)`
54 | * `beginRecordSvg(this, null); // DOES NOT save any file!`
55 |
56 |
57 | ## pauseRecordSvg
58 |
59 | Pauses or unpauses recording of SVG output for a p5.js sketch,
60 | depending on whether the boolean `bPause` argument is `true` or `false`.
61 |
62 | #### Parameters
63 | * `bPause ` **[boolean][29]**
64 |
65 |
66 | ## endRecordSvg
67 |
68 | Ends recording of SVG output for a p5.js sketch. Calls the export function to generate the SVG output and restores the original p5.js functions. Returns the complete text of the SVG file as a string.
69 |
70 |
71 | ## setSvgDocumentSize
72 |
73 | Sets the dimensions of the SVG document in pixels/dots.
74 | Note that graphics are not scaled to fit this size; they may extend beyond the specified dimensions.
75 | If this is not set, the system will default to the main canvas dimensions (i.e. from `createCanvas()`).
76 |
77 | #### Parameters
78 | * `w` **[number][28]** The SVG document width in pixels/dots. Must be a positive number.
79 | * `h` **[number][28]** The SVG document height in pixels/dots. Must be a positive number.
80 |
81 |
82 | ## setSvgResolutionDPI
83 |
84 | Sets the resolution for the exported SVG file in dots per inch (DPI).
85 | This value is used to determine the scaling of units (pixels to physical dimensions) in the SVG output. The default is 96 dpi.
86 |
87 | #### Parameters
88 | * `dpi` **[number][28]** The resolution in dots per inch. Must be a positive number.
89 |
90 |
91 | ## setSvgResolutionDPCM
92 |
93 | Sets the resolution for the exported SVG file in dots per centimeter (DPCM).
94 | This value is used to determine the scaling of units (pixels to physical dimensions) in the SVG output. The default resolution is 243.84 dpcm (equivalent to 96 dpi).
95 |
96 | #### Parameters
97 | * `dpcm` **[number][28]** The resolution in dots per centimeter. Must be a positive number.
98 |
99 |
100 | ## setSvgDefaultStrokeWeight
101 |
102 | Sets the default stroke weight for SVG elements.
103 |
104 | #### Parameters
105 | * `wei` **[number][28]** The stroke weight to set.
106 |
107 |
108 | ## setSvgDefaultStrokeColor
109 |
110 | Sets the default stroke color for SVG elements.
111 |
112 | #### Parameters
113 | * `col` **[string][27]** The stroke color to set, in valid CSS color format.
114 |
115 |
116 | ## setSvgBackgroundColor
117 |
118 | Sets an optional background color (as a CSS style) for the SVG. This is independent of the `background()` color of the p5 sketch.
119 | This color does not interfere with plotter output and is purely for visualization. Note that this color may not be visible in all SVG viewers. If this function is not called, no background color style is specified in the SVG.
120 |
121 | #### Parameters
122 | * `col` **[string][27]** The background color to set, in valid CSS color format.
123 |
124 |
125 | ## setSvgIndent
126 |
127 | Sets the type and amount of indentation used for formatting SVG output.
128 | The function allows for spaces, tabs, or no indentation.
129 |
130 | #### Parameters
131 | * `itype` **[string][27]** The type of indentation to use. Valid values are
132 | 'SVG\_INDENT\_SPACES', 'SVG\_INDENT\_TABS', or 'SVG\_INDENT\_NONE'.
133 | * `inum` **[number][28]?** Optional number of spaces or tabs to use for indentation.
134 | Must be a non-negative integer if provided. Defaults to 2 for spaces and 1 for tabs.
135 |
136 |
137 | ## setSvgFlattenTransforms
138 |
139 | Set whether or not to use a stack to encode matrix transforms.
140 |
141 | * `setSvgFlattenTransforms(true)` -- larger SVG files, greater fidelity to original
142 | * `setSvgFlattenTransforms(false)` -- smaller SVG files, potentially less fidelity
143 |
144 | #### Parameters
145 | * `b` **[boolean][29]** Whether or not to flatten geometric transforms
146 |
147 |
148 | ## setSvgCoordinatePrecision
149 |
150 | Sets the output precision for graphics coordinates in SVGs by adjusting
151 | the number of decimal digits used when formatting values. Default is 4 digits.
152 |
153 | #### Parameters
154 | * `p` **[number][28]** The desired number of decimal digits for coordinates.
155 | Must be a non-negative integer. If an invalid value is provided, a warning is issued.
156 |
157 |
158 | ## setSvgTransformPrecision
159 |
160 | Sets the output precision for matrix-transform values in SVGs by adjusting
161 | the number of decimal digits used when formatting rotations, translations, etc. Default is 6 digits.
162 |
163 | #### Parameters
164 | * `p` **[number][28]** The desired number of decimal digits for matrix values.
165 | Must be a non-negative integer. If an invalid value is provided, a warning is issued.
166 |
167 |
168 | ## setSvgPointRadius
169 |
170 | Sets the radius for "points" (which are rendered as tiny circles) in the SVG output. Default is 0.25 pixels.
171 |
172 | #### Parameters
173 | * `radius` **[number][28]** The desired radius for points, specified as a positive number.
174 | If an invalid value (non-positive or non-number) is provided, a warning is issued.
175 |
176 |
177 | ## setSvgGroupByStrokeColor
178 |
179 | Sets whether or not to group SVG elements by stroke color. When true, elements with the same stroke color, at the same level, will be grouped together.
180 |
181 | #### Parameters
182 |
183 | * `bEnabled` **[boolean][29]** Enable or disables grouping of elements by stroke color. The default is `false`.
184 |
185 |
186 | ## setSvgMergeNamedGroups
187 |
188 | Sets whether or not to merge user-defined SVG groups that have the same name.
189 | Useful for grouping paths that might be computed at different times, but which are part of the same compositional design element, and should be plotted with the same drawing tool. The default is `true`, meaning that groups with the same name (which are at the same hierarchical level) will be merged.
190 |
191 | #### Parameters
192 | * `bEnabled` **[boolean][29]** Whether or not groups with the same name should be merged.
193 |
194 |
195 |
204 |
205 |
206 |
220 |
221 | ## beginSvgGroup
222 |
223 | Begins a new user-defined grouping of SVG elements.
224 | Optionally associates a group name to the SVG group.
225 | Be sure to call `endSvgGroup()` later or the SVG file will report errors.
226 |
227 | #### Parameters
228 | * `gname` **[string][27]?** Optional group name used as an ID for the SVG group.
229 |
230 |
231 |
232 | ## endSvgGroup
233 |
234 | Ends the current user-defined group of SVG elements.
235 |
236 |
237 | ## getDefaultStrokeColor
238 |
239 | Retrieves the default stroke color used for SVG rendering.
240 | Returns **[string][27]** The default stroke color (in hex, RGB, or named CSS color format).
241 |
242 |
243 | ## isRecordingSVG
244 |
245 | Retrieves whether or not SVG recording is active.
246 | Returns **[boolean][29]** True if SVG recording is active, false otherwise.
247 |
248 |
249 |
259 |
260 |
270 |
271 |
272 | [26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
273 |
274 | [27]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
275 |
276 | [28]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
277 |
278 | [29]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
--------------------------------------------------------------------------------
/examples/plotSvg_hatched_shapes/plotSvg_hatched_shapes.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/plotSvg_hershey_text/sketch.js:
--------------------------------------------------------------------------------
1 | // Display an inlined Hershey font in p5.js from
2 | // https://github.com/techninja/hersheytextjs
3 | // Adapted from "hershey font json example" by Allison Parrish,
4 | // https://editor.p5js.org/allison.parrish/sketches/SJv2DCYpQ
5 |
6 | p5.disableFriendlyErrors = true; // hush, p5
7 | let bDoExportSvg = false;
8 | let charCount = 0;
9 |
10 | function setup() {
11 | createCanvas(640, 280);
12 | setSvgMergeNamedGroups(false); // don't group paths from like letters
13 |
14 | let saveButton = createButton("Save SVG");
15 | saveButton.position(10, 10);
16 | saveButton.mousePressed((event) => {
17 | event.stopPropagation();
18 | bDoExportSvg = true;
19 | });
20 | }
21 |
22 |
23 | function draw(){
24 | background(245);
25 | stroke(0);
26 |
27 | if (bDoExportSvg){
28 | beginRecordSvg(this, "hershey_text.svg");
29 | charCount = 0;
30 | }
31 |
32 | drawString("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 50,70, 1.0);
33 | drawString("abcdefghijklmnopqrstuvwxyz", 50,110, 1.0);
34 | drawString("1234567890", 50,150, 1.0);
35 | drawString("!@#$%^&*()?{}[]()<>,.;:'\"", 50,190, 1.0);
36 | drawString("Hello World", 50,230, 1.0);
37 |
38 | if (bDoExportSvg){
39 | endRecordSvg();
40 | bDoExportSvg = false;
41 | }
42 | }
43 |
44 |
45 | function keyPressed() {
46 | if ((key === 's') || (key === 'S')) {
47 | bDoExportSvg = true;
48 | }
49 | }
50 |
51 |
52 |
53 | //===================================================================
54 | // convert a hersheytextjs SVG path to a list of opentype-esque commands
55 | // Adapted from code by Allison Parrish
56 |
57 | function drawString(str, x, y, sca){
58 | drawPath(getPathCommandsForText(str), x,y, sca);
59 | }
60 |
61 | function drawChar(c, bCentered=false){
62 | // Draws a character at the origin.
63 | // It's up to you to scale, translate, rotate it, etc.
64 | let cidx = c.charCodeAt(0) - 33;
65 | if (cidx >= 0) {
66 | let dx = bCentered ? 0-hersheyFutural.futural.chars[cidx].o : 0;
67 | let dy = 0;
68 | drawPath(getPathCommandsForText(c), dx,dy, 1.0);
69 | }
70 | }
71 |
72 | //--------------------------------------------
73 | // produce an array of commands for the given string
74 | function getPathCommandsForText(s) {
75 | const font = hersheyFutural.futural;
76 | let commands = [];
77 | let offset = 0;
78 | for (let i = 0; i < s.length; i++) {
79 | let ithChar = s[i];
80 | let cidx = s.charCodeAt(i) - 33;
81 | if (cidx >= 0) {
82 | Array.prototype.push.apply(
83 | commands, getPathCommandsForChar(ithChar, font.chars[cidx].d, offset));
84 | offset += int(font.chars[cidx].o) * 2;
85 | }
86 | else {
87 | offset += 10;
88 | }
89 | }
90 | return commands;
91 | }
92 |
93 | //--------------------------------------------
94 | function getPathCommandsForChar(c, s, offset) {
95 | let instr = [];
96 | let mode = '';
97 |
98 | instr.push({type: 'B', 'x': 0, 'y': 0, 'c':c}); // beginSvgGroup
99 | for (let t of s.split(' ')) {
100 | if (t.charAt(0) == 'M' || t.charAt(0) == 'L') {
101 | mode = t.charAt(0);
102 | t = t.substr(1);
103 | }
104 | let coords = t.split(',');
105 | if (mode == 'M') {
106 | instr.push({type: 'M', 'x': int(coords[0])+offset, 'y': int(coords[1])});
107 | }
108 | else if (mode == 'L') {
109 | instr.push({type: 'L', 'x': int(coords[0])+offset, 'y': int(coords[1])});
110 | }
111 | }
112 | instr.push({type: 'E', 'x': 0, 'y': 0}); // endSvgGroup
113 | return instr;
114 | }
115 |
116 | //--------------------------------------------
117 | // draw commands.
118 | // Note, this has been modified to use p5.plotSvg's SVG group commands.
119 | function drawPath(cmds, x,y, sca) {
120 | // current pen position
121 | let cx = 0;
122 | let cy = 0;
123 | // start position of current contour
124 | let startX = 0;
125 | let startY = 0;
126 | const dy = 22;
127 | for (let cmd of cmds) {
128 | switch (cmd.type) {
129 | case 'B':
130 | beginSvgGroup(charToId(cmd.c));
131 | break;
132 | case 'E':
133 | endSvgGroup();
134 | break;
135 | case 'M':
136 | startX = cmd.x;
137 | startY = cmd.y;
138 | cx = cmd.x;
139 | cy = cmd.y;
140 | break;
141 | case 'L':
142 | let x0 = x + cx*sca;
143 | let y0 = y + (cy - dy)*sca;
144 | let x1 = x + cmd.x*sca;
145 | let y1 = y + (cmd.y - dy)*sca;
146 | line(x0,y0, x1,y1);
147 | cx = cmd.x;
148 | cy = cmd.y;
149 | break;
150 | }
151 | }
152 | }
153 |
154 | function charToId(c) {
155 | // Wraps the character safely for the group-name
156 | const code = c.codePointAt(0).toString(16).toUpperCase().padStart(4, '0');
157 | return `char_${charCount++}_U${code}`;
158 | }
159 |
160 |
161 | const hersheyFutural = {"futural":{
162 | "name":"Sans 1-stroke",
163 | "chars":[
164 | {"d":"M5,1 L5,15 M5,20 L4,21 5,22 6,21 5,20","o":5},
165 | {"d":"M4,1 L4,8 M12,1 L12,8","o":8},
166 | {"d":"M11,-3 L4,29 M17,-3 L10,29 M4,10 L18,10 M3,16 L17,16","o":11},
167 | {"d":"M8,-3 L8,26 M12,-3 L12,26 M17,4 L15,2 12,1 8,1 5,2 3,4 3,6 4,8 5,9 7,10 13,12 15,13 16,14 17,16 17,19 15,21 12,22 8,22 5,21 3,19","o":10},
168 | {"d":"M21,1 L3,22 M8,1 L10,3 10,5 9,7 7,8 5,8 3,6 3,4 4,2 6,1 8,1 10,2 13,3 16,3 19,2 21,1 M17,15 L15,16 14,18 14,20 16,22 18,22 20,21 21,19 21,17 19,15 17,15","o":12},
169 | {"d":"M23,10 L23,9 22,8 21,8 20,9 19,11 17,16 15,19 13,21 11,22 7,22 5,21 4,20 3,18 3,16 4,14 5,13 12,9 13,8 14,6 14,4 13,2 11,1 9,2 8,4 8,6 9,9 11,12 16,19 18,21 20,22 22,22 23,21 23,20","o":13},
170 | {"d":"M5,3 L4,2 5,1 6,2 6,4 5,6 4,7","o":5},
171 | {"d":"M11,-3 L9,-1 7,2 5,6 4,11 4,15 5,20 7,24 9,27 11,29","o":7},
172 | {"d":"M3,-3 L5,-1 7,2 9,6 10,11 10,15 9,20 7,24 5,27 3,29","o":7},
173 | {"d":"M8,7 L8,19 M3,10 L13,16 M13,10 L3,16","o":8},
174 | {"d":"M13,4 L13,22 M4,13 L22,13","o":13},
175 | {"d":"M5,18 L4,19 3,18 4,17 5,18 5,20 3,22","o":4},
176 | {"d":"M4,13 L22,13","o":13},
177 | {"d":"M4,17 L3,18 4,19 5,18 4,17","o":4},
178 | {"d":"M20,-3 L2,29","o":11},
179 | {"d":"M9,1 L6,2 4,5 3,10 3,13 4,18 6,21 9,22 11,22 14,21 16,18 17,13 17,10 16,5 14,2 11,1 9,1","o":10},
180 | {"d":"M6,5 L8,4 11,1 11,22","o":10},
181 | {"d":"M4,6 L4,5 5,3 6,2 8,1 12,1 14,2 15,3 16,5 16,7 15,9 13,12 3,22 17,22","o":10},
182 | {"d":"M5,1 L16,1 10,9 13,9 15,10 16,11 17,14 17,16 16,19 14,21 11,22 8,22 5,21 4,20 3,18","o":10},
183 | {"d":"M13,1 L3,15 18,15 M13,1 L13,22","o":10},
184 | {"d":"M15,1 L5,1 4,10 5,9 8,8 11,8 14,9 16,11 17,14 17,16 16,19 14,21 11,22 8,22 5,21 4,20 3,18","o":10},
185 | {"d":"M16,4 L15,2 12,1 10,1 7,2 5,5 4,10 4,15 5,19 7,21 10,22 11,22 14,21 16,19 17,16 17,15 16,12 14,10 11,9 10,9 7,10 5,12 4,15","o":10},
186 | {"d":"M17,1 L7,22 M3,1 L17,1","o":10},
187 | {"d":"M8,1 L5,2 4,4 4,6 5,8 7,9 11,10 14,11 16,13 17,15 17,18 16,20 15,21 12,22 8,22 5,21 4,20 3,18 3,15 4,13 6,11 9,10 13,9 15,8 16,6 16,4 15,2 12,1 8,1","o":10},
188 | {"d":"M16,8 L15,11 13,13 10,14 9,14 6,13 4,11 3,8 3,7 4,4 6,2 9,1 10,1 13,2 15,4 16,8 16,13 15,18 13,21 10,22 8,22 5,21 4,19","o":10},
189 | {"d":"M4,10 L3,11 4,12 5,11 4,10 M4,17 L3,18 4,19 5,18 4,17","o":4},
190 | {"d":"M4,10 L3,11 4,12 5,11 4,10 M5,18 L4,19 3,18 4,17 5,18 5,20 3,22","o":4},
191 | {"d":"M20,4 L4,13 20,22","o":12},
192 | {"d":"M4,10 L22,10 M4,16 L22,16","o":13},
193 | {"d":"M4,4 L20,13 4,22","o":12},
194 | {"d":"M3,6 L3,5 4,3 5,2 7,1 11,1 13,2 14,3 15,5 15,7 14,9 13,10 9,12 9,15 M9,20 L8,21 9,22 10,21 9,20","o":9},
195 | {"d":"M18,9 L17,7 15,6 12,6 10,7 9,8 8,11 8,14 9,16 11,17 14,17 16,16 17,14 M12,6 L10,8 9,11 9,14 10,16 11,17 M18,6 L17,14 17,16 19,17 21,17 23,15 24,12 24,10 23,7 22,5 20,3 18,2 15,1 12,1 9,2 7,3 5,5 4,7 3,10 3,13 4,16 5,18 7,20 9,21 12,22 15,22 18,21 20,20 21,19 M19,6 L18,14 18,16 19,17","o":14},
196 | {"d":"M9,1 L1,22 M9,1 L17,22 M4,15 L14,15","o":9},
197 | {"d":"M4,1 L4,22 M4,1 L13,1 16,2 17,3 18,5 18,7 17,9 16,10 13,11 M4,11 L13,11 16,12 17,13 18,15 18,18 17,20 16,21 13,22 4,22","o":10},
198 | {"d":"M18,6 L17,4 15,2 13,1 9,1 7,2 5,4 4,6 3,9 3,14 4,17 5,19 7,21 9,22 13,22 15,21 17,19 18,17","o":11},
199 | {"d":"M4,1 L4,22 M4,1 L11,1 14,2 16,4 17,6 18,9 18,14 17,17 16,19 14,21 11,22 4,22","o":10},
200 | {"d":"M4,1 L4,22 M4,1 L17,1 M4,11 L12,11 M4,22 L17,22","o":9},
201 | {"d":"M4,1 L4,22 M4,1 L17,1 M4,11 L12,11","o":8},
202 | {"d":"M18,6 L17,4 15,2 13,1 9,1 7,2 5,4 4,6 3,9 3,14 4,17 5,19 7,21 9,22 13,22 15,21 17,19 18,17 18,14 M13,14 L18,14","o":11},
203 | {"d":"M4,1 L4,22 M18,1 L18,22 M4,11 L18,11","o":11},
204 | {"d":"M4,1 L4,22","o":4},
205 | {"d":"M12,1 L12,17 11,20 10,21 8,22 6,22 4,21 3,20 2,17 2,15","o":8},
206 | {"d":"M4,1 L4,22 M18,1 L4,15 M9,10 L18,22","o":10},
207 | {"d":"M4,1 L4,22 M4,22 L16,22","o":7},
208 | {"d":"M4,1 L4,22 M4,1 L12,22 M20,1 L12,22 M20,1 L20,22","o":12},
209 | {"d":"M4,1 L4,22 M4,1 L18,22 M18,1 L18,22","o":11},
210 | {"d":"M9,1 L7,2 5,4 4,6 3,9 3,14 4,17 5,19 7,21 9,22 13,22 15,21 17,19 18,17 19,14 19,9 18,6 17,4 15,2 13,1 9,1","o":11},
211 | {"d":"M4,1 L4,22 M4,1 L13,1 16,2 17,3 18,5 18,8 17,10 16,11 13,12 4,12","o":10},
212 | {"d":"M9,1 L7,2 5,4 4,6 3,9 3,14 4,17 5,19 7,21 9,22 13,22 15,21 17,19 18,17 19,14 19,9 18,6 17,4 15,2 13,1 9,1 M12,18 L18,24","o":11},
213 | {"d":"M4,1 L4,22 M4,1 L13,1 16,2 17,3 18,5 18,7 17,9 16,10 13,11 4,11 M11,11 L18,22","o":10},
214 | {"d":"M17,4 L15,2 12,1 8,1 5,2 3,4 3,6 4,8 5,9 7,10 13,12 15,13 16,14 17,16 17,19 15,21 12,22 8,22 5,21 3,19","o":10},
215 | {"d":"M8,1 L8,22 M1,1 L15,1","o":8},
216 | {"d":"M4,1 L4,16 5,19 7,21 10,22 12,22 15,21 17,19 18,16 18,1","o":11},
217 | {"d":"M1,1 L9,22 M17,1 L9,22","o":9},
218 | {"d":"M2,1 L7,22 M12,1 L7,22 M12,1 L17,22 M22,1 L17,22","o":12},
219 | {"d":"M3,1 L17,22 M17,1 L3,22","o":10},
220 | {"d":"M1,1 L9,11 9,22 M17,1 L9,11","o":9},
221 | {"d":"M17,1 L3,22 M3,1 L17,1 M3,22 L17,22","o":10},
222 | {"d":"M4,-3 L4,29 M5,-3 L5,29 M4,-3 L11,-3 M4,29 L11,29","o":7},
223 | {"d":"M0,1 L14,25","o":7},
224 | {"d":"M9,-3 L9,29 M10,-3 L10,29 M3,-3 L10,-3 M3,29 L10,29","o":7},
225 | {"d":"M8,-1 L0,13 M8,-1 L16,13","o":8},
226 | {"d":"M0,29 L18,29","o":9},
227 | {"d":"M5,6 L3,8 3,10 4,11 5,10 4,9 3,10","o":4},
228 | {"d":"M15,8 L15,22 M15,11 L13,9 11,8 8,8 6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19","o":10},
229 | {"d":"M4,1 L4,22 M4,11 L6,9 8,8 11,8 13,9 15,11 16,14 16,16 15,19 13,21 11,22 8,22 6,21 4,19","o":9},
230 | {"d":"M15,11 L13,9 11,8 8,8 6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19","o":9},
231 | {"d":"M15,1 L15,22 M15,11 L13,9 11,8 8,8 6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19","o":10},
232 | {"d":"M3,14 L15,14 15,12 14,10 13,9 11,8 8,8 6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19","o":9},
233 | {"d":"M10,1 L8,1 6,2 5,5 5,22 M2,8 L9,8","o":7},
234 | {"d":"M15,8 L15,24 14,27 13,28 11,29 8,29 6,28 M15,11 L13,9 11,8 8,8 6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19","o":10},
235 | {"d":"M4,1 L4,22 M4,12 L7,9 9,8 12,8 14,9 15,12 15,22","o":10},
236 | {"d":"M3,1 L4,2 5,1 4,0 3,1 M4,8 L4,22","o":4},
237 | {"d":"M5,1 L6,2 7,1 6,0 5,1 M6,8 L6,25 5,28 3,29 1,29","o":5},
238 | {"d":"M4,1 L4,22 M14,8 L4,18 M8,14 L15,22","o":8},
239 | {"d":"M4,1 L4,22","o":4},
240 | {"d":"M4,8 L4,22 M4,12 L7,9 9,8 12,8 14,9 15,12 15,22 M15,12 L18,9 20,8 23,8 25,9 26,12 26,22","o":15},
241 | {"d":"M4,8 L4,22 M4,12 L7,9 9,8 12,8 14,9 15,12 15,22","o":10},
242 | {"d":"M8,8 L6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19 16,16 16,14 15,11 13,9 11,8 8,8","o":10},
243 | {"d":"M4,8 L4,29 M4,11 L6,9 8,8 11,8 13,9 15,11 16,14 16,16 15,19 13,21 11,22 8,22 6,21 4,19","o":9},
244 | {"d":"M15,8 L15,29 M15,11 L13,9 11,8 8,8 6,9 4,11 3,14 3,16 4,19 6,21 8,22 11,22 13,21 15,19","o":10},
245 | {"d":"M4,8 L4,22 M4,14 L5,11 7,9 9,8 12,8","o":6},
246 | {"d":"M14,11 L13,9 10,8 7,8 4,9 3,11 4,13 6,14 11,15 13,16 14,18 14,19 13,21 10,22 7,22 4,21 3,19","o":9},
247 | {"d":"M5,1 L5,18 6,21 8,22 10,22 M2,8 L9,8","o":7},
248 | {"d":"M4,8 L4,18 5,21 7,22 10,22 12,21 15,18 M15,8 L15,22","o":10},
249 | {"d":"M2,8 L8,22 M14,8 L8,22","o":8},
250 | {"d":"M3,8 L7,22 M11,8 L7,22 M11,8 L15,22 M19,8 L15,22","o":11},
251 | {"d":"M3,8 L14,22 M14,8 L3,22","o":9},
252 | {"d":"M2,8 L8,22 M14,8 L8,22 6,26 4,28 2,29 1,29","o":8},
253 | {"d":"M14,8 L3,22 M3,8 L14,8 M3,22 L14,22","o":9},
254 | {"d":"M9,-3 L7,-2 6,-1 5,1 5,3 6,5 7,6 8,8 8,10 6,12 M7,-2 L6,0 6,2 7,4 8,5 9,7 9,9 8,11 4,13 8,15 9,17 9,19 8,21 7,22 6,24 6,26 7,28 M6,14 L8,16 8,18 7,20 6,21 5,23 5,25 6,27 7,28 9,29","o":7},
255 | {"d":"M4,-3 L4,29","o":4},
256 | {"d":"M5,-3 L7,-2 8,-1 9,1 9,3 8,5 7,6 6,8 6,10 8,12 M7,-2 L8,0 8,2 7,4 6,5 5,7 5,9 6,11 10,13 6,15 5,17 5,19 6,21 7,22 8,24 8,26 7,28 M8,14 L6,16 6,18 7,20 8,21 9,23 9,25 8,27 7,28 5,29","o":7},
257 | {"d":"M3,16 L3,14 4,11 6,10 8,10 10,11 14,14 16,15 18,15 20,14 21,12 M3,14 L4,12 6,11 8,11 10,12 14,15 16,16 18,16 20,15 21,12 21,10","o":12},
258 | {"d":"M0,1 L0,22 1,22 1,1 2,1 2,22 3,22 3,1 4,1 4,22 5,22 5,1 6,1 6,22 7,22 7,1 8,1 8,22 9,22 9,1 10,1 10,22 11,22 11,1 12,1 12,22 13,22 13,1 14,1 14,22 15,22 15,1 16,1 16,22","o":8}
259 | ]
260 | }};
--------------------------------------------------------------------------------