p5.gysin has three library files
p5.jsis the creative-coding foundation. Load it when you wantGysinPlotto draw the generated paths on a canvas.p5.gysin.min.jsis the core library. It providesGysinPlot, drawing methods such astext(), and SVG, JSON, and HPGL export.p5.gysin.text.min.jsis the text addon. It providesGysinText.permute(),GysinText.weave(),plot.chant(), andplot.weave().p5.gysin.underwood.min.jsis the typewriter addon. It providesplot.underwood().
Load what you need, in this order - with p5.js first when you draw to a canvas:
<script src="https://cdn.jsdelivr.net/gh/seb-prjcts-be/p5.gysin@v1.2.0/p5.gysin.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/seb-prjcts-be/p5.gysin@v1.2.0/p5.gysin.text.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/seb-prjcts-be/p5.gysin@v1.2.0/p5.gysin.underwood.min.js"></script>
The addons are optional. The pure GysinText operations run without p5.js or GysinPlot; the plot.* methods attach when the core is present.
Basic Use
let plot;
function setup() {
createCanvas(800, 800);
plot = new GysinPlot({ seed: 1960 });
plot.text("FIRST TRACE", 80, 180, {
size: 72,
breathe: 1.2,
dropout: 0.06
});
plot.line(80, 260, 720, 280, {
breathe: 1,
dropout: 0.05,
overshoot: 8
});
plot.draw();
}
Constructor and Instance Mode
new GysinPlot(options) accepts seed, width, height, page, and default human, style, and export objects. In p5 instance mode, create the plot after createCanvas():
const plot = p.createGysinPlot({ seed: 1960 });The helper links drawing and automatic export dimensions to that p5 instance. Capture methods return a shape id. Lookup and mutation methods return detached snapshots, so use update() instead of modifying data returned by get().
Shape Methods
plot.line(x1, y1, x2, y2, options)plot.rect(x, y, w, h, options)plot.circle(x, y, diameter, options)plot.polygon(points, options)plot.path(points, options)plot.text(value, x, y, options)plot.textCutup(value, x, y, options)
2D Primitives shows line(), rect(), circle(), polygon(), and path() side by side with their clean defaults.
Fields and Marks
These methods fill a region instead of drawing one isolated shape. They return shape ids or cell rectangles that can be addressed later.
const cells = plot.grid(40, 40, 480, 320, 3, 2); // frame grid; returns cell rects
plot.letters("RUB OUT", 60, 60, 200, 120); // a decaying letter field
plot.symbols(280, 60, 200, 120, { set: "+#&" }); // typewriter operator texture
plot.asemic(60, 220, 160, 120); // a cursive, wordless tangle
plot.grid(x, y, w, h, cols, rows, options)draws an ink-bled frame grid and returns its cell rectangles ({ x, y, w, h, col, row }) so a composition can place fields declaratively. Options:gap,outer: false(skip the outer frame),cells: false(return rectangles without drawing them), andframe- an options object passed to every drawn rect, includingangle/pivot.plot.letters(source, x, y, w, h, options)fills the box with rows of letters drawn from the source phrase, clustered by repetition so words dissolve into a letter field. Returns one id per row.plot.symbols(x, y, w, h, options)fills the box with typewriter operator glyphs.set(default+"#&/) chooses the glyphs;cluster: truerepeats them in runs.plot.asemic(x, y, w, h, options)draws a compact, handwriting-like tangle - writing with no words.loopsanddetailset the density; every otherpath()option passes through.- All four are auto-seeded by call order, so redrawing the same composition reproduces the same fields; pass
seedto pin one. Both field methods acceptsize,lineHeight,letterSpacing, and every trace option.
Shape, Text, Style, and Export Options
path: setclosed: truefor an exact closed contour.text:size,font,sampleFactor,lineHeight, andletterSpacing.textCutup: a rare horizontal contour disturbance; all text options plusslices,sliceOffset, andsliceDropout.- Screen and generic-SVG style:
stroke, positivestrokeWeight, and clampedalpha. The plotter-SVG route keeps the pen grouping fromstrokebut discards the two screen-only properties. - Export per shape:
simplify,minSegmentLength, andlayer.
Invalid geometry, duplicate ids, excessive sampling, more than 1,000 repeats, and route optimization above 2,000 traces raise an error before export.
Text
Word: isolate one word or short name and treat it as a drawn body with text() or rub(). Scale, repetition, wear, and position carry the gesture.
Sentence: preserve enough reading time for order, sequence, and source to matter. chant(), weave(), and underwood() therefore default to readable lines rather than automatic slicing.
Surface: textCutup() shifts horizontal bands of an existing letter contour. It is a graphical after-effect, not a linguistic cut-up operation; use it explicitly and sparingly.
When the supplied p5 font exposes its outline path, p5.gysin keeps separate glyph contours and counters. Otherwise it uses font.textToPoints(); without a font, it falls back to a simple vector alphabet.
For p5.js 2.x, load fonts with async setup() and await loadFont(...) before passing the font to plot.text().
Trace Parameters
density: positive point sampling density; default1.breathe: the hand tremor that keeps a line alive - maximum point displacement in pixels; default0.dropout: clamped 0–1 probability that a point interrupts a path.hesitate: clamped 0–1 probability of small mechanical hesitations.repeat: whole number from 1 through 1000; default1.drift: non-negative global offset between repeats.bleed: clamped 0–1 share of written contour length selected for additive ink accumulation.bleedPasses: maximum extra passages per selected fragment, from 1 through 3; default2.bleedSpread: coherent offset between bleed passages; at least0.1when bleed is active.bleedCluster: positive target length for contiguous selected fragments; default18.rubout: clamped 0–1 strength of generated erase zones.fray: non-negative amount of loose edge strokes.pressure: clamped 0–1 screen weight and alpha variation; never a physical pen-pressure setting and discarded by plotter SVG.overshoot: non-negative extension of open path endpoints.segmentLength: positive target spacing; default8.glyphJitter: non-negative per-glyph variation (rotation, shift, scale) so no two rendered letters are identical; default0.35,0is mechanically exact.fill:"hatch"(parallel strokes) or"cross"(two perpendicular hatchings) fills closed contours; even-odd, so letter counters stay open. Fill strokes carryrole: "fill",stats()reportsfillPathsandfillLength, andtool: "blade"leaves fill out of the export. House use: letterforms via a realfont- not empty tone planes.hatchSpacing: distance between fill strokes; default2, minimum0.25.hatchAngle: rotation of the hatching in degrees; default0.seed: optional deterministic seed for one shape.
Ink accumulation
dropout and rubout remove material. bleed is their additive counterpart: it selects seeded, contiguous trace fragments and adds a bounded number of coherently offset passages. It does not repeat the complete glyph or shape.
plot.text("RUB OUT THE WORD", 80, 180, {
bleed: 0.22,
bleedPasses: 2,
bleedSpread: 0.8,
bleedCluster: 18
});Generated bleed traces carry data-role="bleed" and data-pass in SVG. Statistics expose bleedPaths, bleedLength, overdrawRatio, and maxLocalPasses.
Composed Methods
Composed methods combine existing capture methods and return the ids they create. Their options can replace or adjust the built-in composition.
plot.rub(text, x, y, options) draws a word worn away in three copies - legible, weathered, nearly gone - then lets an asemic scribble bury the last one. Its default composition uses text and asemic; a custom stage may still select textCutup explicitly.
plot.rub("RUB OUT", 46, 248);
plot.rub("RUB OUT", 46, 248, { decay: 2 });
plot.rub("RUB OUT", 46, 248, { font: outlineFont });
decay: scales breathe/drift/dropout/rubout/fray across every copy at once; default1,0draws clean.size(default46),font(fills the legible head),stroke.tail: the asemic burial of the last copy; defaulttrue, setfalseto leave the word unburied.stepX/stepY: the drift between copies.stages/tanglesreplace the built-in recipe wholesale.
rub() returns the id of every copy and tangle, so each can be frozen, rerolled, updated, or exported like any other shape.
The Turned Sheet
Every shape takes angle (degrees) and pivot. This is the physical gesture of the studio - write, turn the paper, write again - as shape geometry, not as an export afterthought. The turn runs before breathe, dropout, bleed and hatching, so every disturbance lives on the turned line, and update() can turn any standing shape.
plot.text("RUB OUT", 80, 120, { angle: 90 }); // about its pen-down anchor
plot.text("RUB OUT", 80, 120, { angle: 45, pivot: "center" }); // about its own middle
plot.rect(60, 60, 200, 90, { angle: 30, pivot: { x: 60, y: 60 } }); // about an explicit pin
plot.update(id, { angle: 180 });pivot: "anchor"(default): text turns about its pen-down point, rect and circle about their centre, path and polygon about their centroid, line about its midpoint.pivot: "center"turns about the sampled bounds; an{ x, y }object sets the pin explicitly.angle: 0stays byte-identical to a shape without the option. Page-levelpage.rotationis unchanged and still means the whole exported sheet.
plot.lattice(text, x, y, w, h, options) writes a phrase in rows that fill a field, then writes across it at each requested turn. Each row cycles the word order by one; later passes can carry more wear.
plot.lattice("RUB OUT THE WORD", 60, 60, 480, 480); // two passes, 0 and 90
plot.lattice("JUNK IS NO GOOD BABY", 60, 60, 480, 480, {
turns: [0, 90, 180],
size: 16,
wear: 1.6
});turns(default[0, 90]): the sheet's angles, one written pass per entry. Near-perpendicular passes write along the field's other axis so their rows still fill it.wear(default1): one knob that scales breathe/dropout/drift per pass;0writes every pass clean.size(default14),lineHeight,letterSpacing,stroke,font, screen-onlyalpha,layer(default"lattice");extrapasses any other trace option to every row.- Returns the id of every written row, pass by pass. Freeze one pass to keep it unchanged while later passes reroll.
Optional Text Operations
p5.gysin.text.min.js provides pure text operations and two matching plot methods.
<script src="https://cdn.jsdelivr.net/gh/seb-prjcts-be/p5.gysin@v1.2.0/p5.gysin.text.min.js"></script>
const lines = GysinText.permute("I LOVE YOU", {
seed: 1960,
limit: 6,
order: "walk"
});
lines.forEach((line, index) => {
plot.text(line, 40, 65 + index * 55, { size: 28 });
});
Orders: walk, random, lexical, and rotate. The source line stays first, duplicate words produce only unique lines, and limit is capped at 1,000.
plot.chant(text, x, y, options) draws the result of permute() as readable text() lines. The changing word order is the composition. It is added to each plot when the addon is loaded.
plot.chant("CUT ARRANGE GLUE", 60, 90);
plot.chant("CUT ARRANGE GLUE", 60, 90, { order: "rotate" });
plot.chant("CUT ARRANGE GLUE", 60, 90, { slices: 5 }); // explicit surface effect
lines(default5): how many permuted lines return;orderandseedpass topermute().sizeandleadingset the readable lines; ordinary trace options such asbreathe,dropout,bleed, andlayerpass totext().- Passing
slices,sliceOffset,sliceDropout, ordescentexplicitly opts the whole chant intotextCutup(). Once active,descentdefaults to1and deepens the horizontal shift line by line. This compatibility route is intentionally not the default.
Weave
const sources = [
{ id: "left", text: "The first source." },
{ id: "right", text: "The second source." }
];
const woven = GysinText.weave(sources, {
seed: 1960,
unit: "phrase",
lines: 4,
fragments: 3
});
const ids = plot.weave(sources, 60, 90, {
seed: 1960,
unit: "clause",
maxWidth: 560
});GysinText.weave(sources, options) cuts and recombines fragments from two to eight sources without drawing. A source is a string or { id, text }; each text may contain at most 20,000 characters and source ids must be unique. The method does not fetch files or URLs.
Options are seed (default 1), unit (word, phrase, or clause; default phrase), lines (1–100; default 4), and fragments (2–6; default 3). The result is { seed, unit, lines }. Each output line contains text and a fragments array; every fragment records source, sourceIndex, start, end, and text. Offsets refer to the trimmed input text.
plot.weave(sources, x, y, options) draws the result as text and returns the generated ids. It defaults to the plot seed, size: 26, and leading: 42. Set a positive maxWidth to keep the group inside the available width from x: when one line is too long, every line receives the same smaller type size. It never enlarges a group. Provenance is retained in each shape’s params.weave and in JSON export. SVG and HPGL retain shape ids and paths, not source text metadata.
reroll() changes only the drawn trace. To make a new weave, call weave() again with a new seed. freeze() and thaw() behave as they do for other captured shapes.
Optional Typewriter (underwood)
p5.gysin.underwood.min.js adds plot.underwood(text, x, y, options): single-stroke letters at a fixed pitch of ten characters and six lines per inch. The bundled face includes capitals, lowercase, and marks such as + # &.
plot.underwood("KICK THAT HABIT MAN", 60, 90);
plot.underwood("HEADING", 60, 130, { underline: 1 });
plot.underwood("BOLD?", 60, 160, { bold: true }); // struck twice
plot.underwood("the small voice.", 60, 190);
bold: true double-strikes each letter, underline draws one to three rules, and wear (0 and up) scales strike variation. Every glyph is a single pen line.
Low-level access is available through the frozen GysinUnderwood object. GysinUnderwood.glyphPolylines(d) parses one bundled glyph path into polylines; GysinUnderwood.render(plot, text, x, y, options) calls the renderer used by plot.underwood(). The glyph dataset is exposed as GysinUnderwood.face.
Addressing
const titleId = plot.text("RUB OUT", 80, 180, { breathe: 2 });
plot.freeze(titleId);
plot.update(titleId, { stroke: "#b5362b" });
plot.thaw(titleId);
plot.regenerate(titleId);
plot.reroll(titleId); // explicit new seeded variation
plot.remove(titleId);
plot.select(titleId); // id-less calls now target this shape
plot.freeze(); // same as plot.freeze(titleId)
plot.setSeed(1961); // re-seed and regenerate every unpinned shape
plot.clear(); // empty the sheet: shapes, ids, and selection
get(id) returns a detached snapshot. regenerate() recalculates with the same seed; reroll() assigns a new deterministic variation. select() sets the default target for id-less addressing calls; setSeed() changes the plot seed and regenerates every shape without an explicit seed of its own; clear() removes all shapes and the selection.
Public Method Index
Core creation and drawing: new GysinPlot(), p.createGysinPlot(), plot.line(), plot.rect(), plot.circle(), plot.polygon(), plot.path(), plot.text(), plot.textCutup(), plot.asemic(), plot.grid(), plot.letters(), plot.symbols(), plot.rub(), plot.lattice(), and plot.draw().
Core addressing: plot.get(), plot.select(), plot.freeze(), plot.thaw(), plot.regenerate(), plot.reroll(), plot.remove(), plot.update(), plot.setSeed(), and plot.clear().
Core output: plot.exportSVG(), plot.exportPlotterSVG(), plot.exportJSON(), plot.exportHPGL(), plot.downloadSVG(), plot.downloadPlotterSVG(), plot.downloadJSON(), plot.downloadHPGL(), and plot.stats().
Text addon: GysinText.permute(), GysinText.weave(), plot.chant(), and plot.weave(). Typewriter addon: plot.underwood(), GysinUnderwood.glyphPolylines(), and GysinUnderwood.render().
Export
Plotter-safe SVG
The methods were introduced in v1.1.0. Version 1.2.0 adds the ISO page presets used below.
const page = "A3";
const penMap = { frame: 1, type: 2 };
const svg = plot.exportPlotterSVG({ page, penMap });
const json = plot.exportJSON();
const stats = plot.stats({ page, drawSpeed: 20, travelSpeed: 60 });
plot.downloadPlotterSVG("gysin.svg", { page, penMap });
plot.downloadJSON("gysin.json");
exportPlotterSVG() and downloadPlotterSVG() are the safe physical-output route. Built-in pages "A5", "A4", "A3", and "A2" use their exact ISO millimetre dimensions, a 10 mm margin, top-left placement, and a width-fitting scale derived from the canvas. A custom page object with explicit width, height, and mm, cm, or in units remains available for advanced output. The route clips actual path geometry to the page; optimizes travel inside each physical pen group; and writes those groups directly below the SVG root as true Inkscape layers. Each visible layer name starts with its physical number from penMap, for example 1 frame, 2 type; without a map the groups receive a deterministic one-based order. Layers with the same opening number use the same physical pen. In Inkscape, show every layer beginning with 1, install pen 1 and plot; then hide those layers and repeat for 2, 3, and so on. A semantic layer with several stroke colours is split into separate pen groups automatically. The generic exportSVG() and downloadSVG() keep their original screen-oriented defaults and byte-compatible group structure.
page: "A4" is an export option, not a separate A4() drawing function. It leaves the canvas composition unchanged and maps its traces onto the selected physical sheet. Only Plotter Calibration keeps a custom page object because its canvas is a millimetre measuring instrument.
Plotter SVG route optimization is on by default. Pass optimize: false only when physical drawing order matters more than pen-up travel, such as a deliberate wet-ink sequence. Clipping stays mandatory in this route. One pen group may contain at most 2,000 traces during optimization; independent groups no longer share that limit.
Plotter SVG is a centre-line file. It omits per-path opacity and varying stroke widths, and uses one uniform 0.1mm SVG hairline only to keep paths visible. Physical coverage and width come from the installed pen. Plotter metadata always lists alpha, strokeWeight, and pressure-derived screen styling as ignored; they are not plotter settings. The route warns only about actionable physical mapping, such as splitting one semantic layer into several colour groups.
The route never deduplicates geometry: tool: "pen" preserves bleed and explicit repeats, while tool: "blade" removes second and later passages. Repetition is physical geometry, but its ink-darkening effect depends on the actual pen, paper, drying time, and machine.
The Plotter Export poster separates four concerns: canvas colours, the artwork, semantic SVG layers, and physical export. Its black and red layers map to pen 1 and pen 2. Choosing A5, A4, A3, or A2 applies the built-in 10 mm margin, centre-line model, geometric clipping, optimized travel, and width-fitting scale; the 720 × 900 canvas composition does not move.
The Plotter Calibration sheet likewise contains only physical tests: millimetre scale, hatch spacing, geometric disturbance, and one through six real pen passages. It no longer presents alpha, pressure, or SVG stroke width as machine controls.
HPGL
plot.exportHPGL(options) returns the current traces as an HPGL string. plot.downloadHPGL(filename, options) passes the same options to that exporter and downloads the result as a .hpgl file. Both methods are part of the core library; no text or typewriter addon is required.
const hpglOptions = {
page: "A4", // physical page + 10 mm margin
optimize: true, // reduce pen-up travel per semantic layer
penMap: { black: 1, red: 2 },
speed: 20
};
const hpgl = plot.exportHPGL(hpglOptions); // HPGL text
plot.downloadHPGL("drawing.hpgl", hpglOptions); // browser download
For physical output, pass both an explicit physical page and optimize: true. HPGL uses the generic export route: without page it falls back to the plot or canvas dimensions, and without optimize: true it preserves drawing order. A preset such as page: "A4" supplies exact millimetres, a 10 mm margin, width-fitting scale, top-left origin, and geometric clipping. Unlike exportPlotterSVG(), HPGL does not require those safeguards or switch optimization on automatically.
penMap maps a semantic layer name or an exact stroke colour to a positive physical pen number. A layer match wins over a colour match; an unmapped trace uses pen 1. The exporter writes SP commands when that number changes. It does not preserve SVG layer names.
page:"A5","A4","A3","A2", or a custom page object with dimensions, units, margins, origin, rotation, scale, and optional clipping.optimize:truesorts travel inside each semantic layer; it remains opt-in and supports at most 2,000 traces per optimized layer.penMap: maps layer names or stroke colours to HPGL pen numbers.speed: emits a roundedVScommand. Its physical interpretation depends on the plotter or controller.scale: overrides coordinate conversion. Defaults are 40 plotter units per mm, 400 per cm, and 1,016 per inch.offsetX: shifts horizontal coordinates in the selected page units before HPGL scaling.offsetY: shifts vertical coordinates in the selected page units before HPGL scaling.maxPointsPerCommand: limits eachPDcommand; the default is 100 points.tool:"pen"preserves fill, bleed, and repeated passages;"blade"removes fill and later passages.
HPGL stores coordinates, pen selections, and optional speed—not SVG colours, opacity, varying stroke width, layer names, or page metadata. Physical line width and density therefore come from the installed pen and from repeated geometry. The file starts with initialization and pen selection, draws with PU/PD, and ends by deselecting the pen. It creates a file only: sending that file to a specific machine remains the responsibility of the plotter software or controller.
Shared page model
The page model applies units, margins, origin, rotation, scale, and optional clipping to generic SVG, HPGL, and statistics. Generic route optimization remains opt-in so existing drawing order stays unchanged.
Supported units are px, mm, cm, and in. Origins are top-left and bottom-left. SVG precision accepts decimals from 0 through 12.
Compatibility
Version 1.2.0 targets p5.js 2.x in global and instance mode. The vector and export core is framework-independent, but p5.js 1.x is not part of the tested support contract.
Input safety
Geometry and sampling values must be finite. Invalid dimensions, duplicate shape ids, generated output above the point budget, and paths that would produce more than 100,000 sampled points raise a clear error; probability values are clamped to the 0–1 range. Active bleed rejects a spread below 0.1 to prevent exact duplicate fragments.