Minimal sketch — auto-render mode
import algorithmic.typography.*;
AlgorithmicTypography at;
void setup() {
size(800, 800);
at = new AlgorithmicTypography(this);
at.loadConfiguration("config.json");
at.initialize();
}
void draw() {
// auto-renders — nothing to add
}
Programmatic config (no JSON file)
Configuration cfg = new Configuration.Builder()
.initialTiles(12, 12)
.changedTiles(6, 6)
.changeTime(5000)
.character("@")
.build();
at.setConfiguration(cfg);
at.initialize();
Restart & keyboard
void keyPressed() {
if (key == 'r') at.restart();
if (key == 's') at.toggleFrameSaving();
}
| Method | Returns | Description |
|---|---|---|
| loadConfiguration(file) | this | Load JSON config |
| setConfiguration(cfg) | this | Set Configuration object |
| getConfiguration() | Configuration | Get current config |
| initialize() | this | Build grid & start |
| render() | void | Draw full-window grid |
| renderAt(x,y,w,h) | void | Draw into rectangle |
| restart() | void | Reset animation timer |
| setAutoRender(bool) | this | Enable/disable auto draw |
| isAutoRender() | boolean | Check auto-render state |
| isRunning() | boolean | Animation still playing? |
| getProgress() | float | 0.0 → 1.0 progress |
| getFrameCount() | int | Current frame index |
| getTotalFrames() | int | Total frames in anim |
| toggleFrameSaving() | this | Toggle frame export |
| setFrameSaving(bool) | this | Set frame export on/off |
| getFramesDirectory() | String | Output folder path |
| dispose() | void | Clean up resources |
{
"canvas": { "width": 1080, "height": 1080 },
"animation": {
"duration": 18,
"fps": 30,
"changeTime": 6000,
"secondChangeTime": 12000,
"fadeDuration": 2000,
"character": "A", // or a word: "TYPE"
"textScale": 0.8,
"waveSpeed": 1.0,
"waveAngle": 45,
"waveType": "SINE",
"waveMultiplierMin": 0.0,
"waveMultiplierMax": 2.0,
"saveFrames": false
},
"grid": {
"initialTilesX": 16,
"initialTilesY": 16,
"changedTilesX": 8,
"changedTilesY": 8,
"finalTilesX": 4,
"finalTilesY": 4
},
"colors": {
"hueMin": 0,
"hueMax": 0,
"saturationMin": 0,
"saturationMax": 0,
"brightnessMin": 50,
"brightnessMax": 255,
"waveAmplitudeMin": -200,
"waveAmplitudeMax": 200,
"backgroundR": 0,
"backgroundG": 0,
"backgroundB": 0
},
"cellBorder": {
"sides": 0,
"r": 255,
"g": 255,
"b": 255,
"weight": 1.0,
"colorMode": 0
},
"cellMotion": {
"style": "perlin",
"radius": 12,
"speed": 1.0
},
"glyphOutline": {
"style": "none",
"r": 255, "g": 255, "b": 255,
"weight": 1.5,
"dashLength": 8.0,
"gapLength": 4.0
}
}
Wave presets
at.setWaveType(WavePresets.Type.SINE);
// SINE · TANGENT · SQUARE · TRIANGLE · SAWTOOTH
at.setWaveType("SQUARE"); // string overload
// or via config.json: "waveType": "TANGENT"
Custom wave function (lambda)
at.setWaveFunction(
(x, y, time, angle) -> {
return (float)Math.sin(
x * 0.1 + time
);
}
);
Design system presets
at.useDesignSystem(
DesignSystem.SystemType.SWISS
);
// SWISS · BAUHAUS · JAPANESE
// BRUTALIST · MINIMAL
Vibe coding — natural language
at.setVibe("calm ocean waves");
at.setVibe("chaos energy");
at.setVibe("geometric minimal");
Glyph motion (v0.2.1 – v0.2.4)
// Perlin noise wander
cfg.setCellMotion(new PerlinMotion(12, 1.0f));
// Circular orbital (CW / CCW)
cfg.setCellMotion(new CircularMotion(12, 1.0f, true));
// Lissajous figure-8 (1:2) or knot (3:2)
cfg.setCellMotion(new LissajousMotion(10, 1.0f));
// Spring-damped glyphs
cfg.setCellMotion(new SpringMotion(0.35f, 0.12f));
// Gravity + bounce (kick() for impulse)
cfg.setCellMotion(new GravityMotion(0.4f, 0.7f));
// Mouse repel/attract field
cfg.setCellMotion(new MagneticMotion(this));
// ── v0.2.3 ──────────────────────────────────
// Ripple: click-triggered concentric rings
RippleMotion r = new RippleMotion(14, 1.0f);
r.setTileGrid(width, height, tilesX, tilesY);
r.trigger(mouseX, mouseY); // in mousePressed()
// FlowField: Perlin vector field
cfg.setCellMotion(new FlowFieldMotion(12, 1.0f));
// Orbital: constellation orbit patterns
cfg.setCellMotion(new OrbitalMotion(10, 1.0f));
// Disable
cfg.setCellMotion(null);
// ── JSON alternative (v0.2.4) ────────────────
// Any style above can be set in config.json:
// "cellMotion": { "style": "gravity",
// "radius": 14, "speed": 1.0,
// "gravity": 0.5, "restitution": 0.8 }
Three-stage grid progression
| Stage | Grid Size | Trigger | Default |
|---|---|---|---|
| 1 Initial | initialTilesX × Y | 0 ms | 16 × 16 |
| 2 Changed | changedTilesX × Y | changeTime | 8 × 8 @ 6 s |
| 3 Final | finalTilesX × Y | secondChangeTime | 4 × 4 @ 12 s |
fadeDuration Cross-fade duration between stages (default 2000 ms)
secondChangeTime Set to 0 to
disable the third stage
Reading grid state at runtime
Configuration cfg = at.getConfiguration();
cfg.getInitialTilesX(); // stage 1 cols
cfg.getInitialTilesY(); // stage 1 rows
cfg.getChangedTilesX(); // stage 2 cols
cfg.getChangedTilesY(); // stage 2 rows
cfg.getFinalTilesX(); // stage 3 cols
cfg.getFinalTilesY(); // stage 3 rows
cfg.getChangeTime(); // ms → stage 2
cfg.getSecondChangeTime();// ms → stage 3
cfg.getFadeDuration(); // crossfade ms
Determining current stage manually
int elapsed = millis() - startTime;
int cols, rows;
if (elapsed > cfg.getSecondChangeTime()) {
cols = cfg.getFinalTilesX();
rows = cfg.getFinalTilesY();
} else if (elapsed > cfg.getChangeTime()) {
cols = cfg.getChangedTilesX();
rows = cfg.getChangedTilesY();
} else {
cols = cfg.getInitialTilesX();
rows = cfg.getInitialTilesY();
}
| Method | Type | Default |
|---|---|---|
| getCanvasWidth() | int | 1080 |
| getCanvasHeight() | int | 1080 |
| getAnimationDuration() | int | 18 |
| getAnimationFPS() | int | 30 |
| getChangeTime() | int | 6000 |
| getSecondChangeTime() | int | 12000 |
| getFadeDuration() | int | 2000 |
| getCharacter() | String | "A" |
| getTextScale() | float | 0.8 |
| getWaveSpeed() | float | 1.0 |
| getWaveAngle() | float | 45.0 |
| getWaveType() | String | "SINE" |
| getWaveMultiplierMin() | float | 0.0 |
| getWaveMultiplierMax() | float | 2.0 |
| getInitialTilesX/Y() | int | 16 |
| getChangedTilesX/Y() | int | 8 |
| getFinalTilesX/Y() | int | 4 |
| getHueMin/Max() | float | 0 / 0 |
| getSaturationMin/Max() | float | 0 / 0 |
| getBrightnessMin/Max() | float | 50 / 255 |
| getWaveAmplitudeMin/Max() | float | -200 / 200 |
| getBackgroundRed/Green/Blue() | int | 0 |
| isSaveFrames() | boolean | false |
| getCellBorderSides() | int | 0 |
| getCellBorderRed/Green/Blue() | int | 255 |
| getCellBorderWeight() | float | 1.0 |
| getCellBorderColorMode() | int | 0 = static, 1 = wave |
| getGlyphOutlineStyle() | int | 0=none, 1=solid, 2=dashed, 3=dashedOnly |
| getGlyphOutlineRed/Green/Blue() | int | 255 |
| getGlyphOutlineWeight() | float | 1.5 |
| getGlyphOutlineDashLength() | float | 8.0 (dashed only) |
| getGlyphOutlineGapLength() | float | 4.0 (dashed only) |
Auto Default — library clears & draws each frame
at.initialize();
// draw() is empty — auto-render handles it
Manual You control what goes on the canvas
at.setAutoRender(false);
at.initialize();
void draw() {
background(0);
// … your drawing code …
at.renderAt(0, 0, width, height);
}
Multi Several instances side by side
AlgorithmicTypography a, b;
void setup() {
size(1600, 800);
a = new AlgorithmicTypography(this);
b = new AlgorithmicTypography(this);
a.setAutoRender(false); b.setAutoRender(false);
a.loadConfiguration("a.json"); b.loadConfiguration("b.json");
a.initialize(); b.initialize();
}
void draw() {
a.renderAt(0,0,width/2,height);
b.renderAt(width/2,0,width/2,height);
}
Save frames as PNG sequence
// Enable in config.json
"saveFrames": true
// Or toggle at runtime
at.toggleFrameSaving();
at.setFrameSaving(true);
// Check output directory
println(at.getFramesDirectory());
HSB Colours render correctly in PDF &
SVG — the library switches colorMode(HSB) before
every draw call and restores RGB after. Hue-driven
palettes look identical on screen and in export.
Save vector SVG
import processing.svg.*;
void keyPressed() {
if (key == 's') {
beginRecord(SVG, "output.svg");
at.render();
endRecord();
println("SVG saved!");
}
}
Common keyboard shortcuts
| Key | Action |
|---|---|
| R | Restart animation |
| S | Toggle frame saving / save SVG |
Render typography on top of a photograph — place the image in the sketch's data/ folder.
AlgorithmicTypography at;
PImage bg;
void setup() {
size(800, 800);
bg = loadImage("background.png");
bg.resize(width, height);
at = new AlgorithmicTypography(this);
at.loadConfiguration("config.json");
at.setAutoRender(false);
at.initialize();
}
void draw() {
image(bg, 0, 0); // draw photo first
at.renderAt(0, 0, width, height); // overlay grid
}
Different photo behind each grid cell. Place
photo-01.png … photo-16.png in
data/
// setup(): load photos into PImage[] photos
for (int i = 1; i <= 16; i++) {
PImage img = loadImage("photo-"+nf(i,2)+".png");
if (img != null && img.width > 0)
photos[photoCount++] = img;
}
// draw(): tile photos, then overlay glyphs
float cw = (float)width/cols, ch = (float)height/rows;
for (int gx=0; gx<cols; gx++)
for (int gy=0; gy<rows; gy++) {
int idx = (gy*cols+gx) % photoCount;
image(photos[idx], gx*cw, gy*ch, cw, ch);
}
at.renderAt(0, 0, width, height);
See MultiPhoto example for centre-crop & 3-stage tracking.
glyphOutline
JSON block. O cycles 4 styles: None → Solid → Dashed → Dashed Only
(fill suppressed); left/right arrows cycle 16 test chars; S saves
PNG. (v0.2.5)
interpolateTo(): large central
morph, five-thumbnail timeline strip, four styles (Outline / Filled
/ Dots / Dual), mouse-X scrub (v0.2.3).
Designer-oriented glyph geometry access
import algorithmic.typography.render.GlyphExtractor;
import java.util.List;
GlyphExtractor g = new GlyphExtractor(this, "Helvetica", 72);
g.setFlatness(0.3); // curve resolution
// Fluent GlyphBuilder (v0.2.3)
GlyphExtractor.of('A', 400)
.fillPoints(500)
.hatch(45, 8)
.colourByPosition()
.hueRange(180, 270)
.draw(this, x, y);
| Method | Returns | Notes |
|---|---|---|
| extractChar(ch, size) | PShape | filled shape |
| extractDeformed(ch, sz, …) | PShape | wave-distorted |
| getContours(ch, size) | List<PVector[]> | all contours |
| getOuterContour(ch, size) | PVector[] | largest area |
| getInnerContours(ch, size) | List<PVector[]> | counter-forms |
| fillWithPoints(ch, size, n) | PVector[] | interior scatter |
| distributeAlongOutline(ch, sz, n) | PVector[] | arc-length spaced |
| getBounds(ch, size) | float[4] | x,y,w,h |
| fillWithLines(ch, sz, ang, gap) | float[][] | hatch segments |
| offsetOutline(ch, size, dist) | PVector[] | inline / outline |
| interpolateTo(c1, c2, sz, t) | PVector[] | morph at t∈[0,1] |
| getMedialAxis(ch, size, n) | PVector[] | spine approx. |
| sampleAlongPath(ch, size, t) | PVector | point at arc t |
| getBoundingContour(chs[], sz, f) | PVector[] | union outline |
| of(ch, size) → GlyphBuilder | GlyphBuilder | fluent API |
| centerOf(str, sz, cx, cy) | PVector | word centering offset |
| getDashedOutline(str, sz, dash, gap) | float[][] | word dashed outline |
v0.2.3 rows
highlighted in accent colour.
See Page 6 for v0.2.5 additions: boolean ops,
path utilities & Type DNA.
Bottom two rows: v0.2.5 word-support String overloads — pass a
word wherever a char was accepted.
| Package | Key classes |
|---|---|
| algorithmic.typography | AlgorithmicTypography, Configuration, HeadlessRenderer |
| …typography.core | WaveEngine, WavePresets, CellMotion, all Motion types, TemporalTrail |
| …typography.render | GridRenderer, GlyphExtractor, GlyphBuilder, TypeDNAProfile, GlyphPhysics, ExportController, VectorExporter |
| …typography.audio | AudioBridge (FFT, beat detection) |
| …typography.style | ColorPalette, Serendipity |
| …typography.system | DesignSystem, VibePreset |
| …typography.text | RitaBridge (RiTa integration) |
| …typography.ui | ControlPanel, Slider, ProgressBar, OSCBridge |
| …typography.ml | AIWaveFunction |
| …typography.net | WebSocketServer |
CellMotion Types
| Class | Since | Description |
|---|---|---|
| CircularMotion | 0.2.1 | CW / CCW orbital |
| PerlinMotion | 0.2.1 | noise wandering |
| LissajousMotion | 0.2.1 | figure-8 / knot |
| SpringMotion | 0.2.1 | spring-damped chase |
| GravityMotion | 0.2.1 | fall + bounce + kick() |
| MagneticMotion | 0.2.2 | mouse repel / attract |
| RippleMotion | 0.2.3 | concentric click rings |
| FlowFieldMotion | 0.2.3 | Perlin vector field |
| OrbitalMotion | 0.2.3 | constellation orbit |
AWT Area boolean algebra on native font outlines.
Both PShape (draw with shape()) and
PVector[] (physics / custom render) return variants.
import algorithmic.typography.render.*;
GlyphExtractor g = new GlyphExtractor(this, "Helvetica", 72);
// PShape overloads — ready to draw with shape()
PShape u = g.union('O', 'C', 400);
PShape i = g.intersect('B', 'E', 400);
PShape s = g.subtract('A', 'V', 400);
shape(u, x, y);
// PVector[] overloads — for physics / custom draw
PVector[] uc = g.getUnionContour('O', 'C', 400);
PVector[] ic = g.getIntersectContour('B', 'E', 400);
PVector[] sc = g.getSubtractContour('A', 'V', 400);
// Arc-length-resampled dashed outline
float[][] dash = g.getDashedOutline('O', 400, 10, 5);
for (float[] seg : dash)
line(seg[0], seg[1], seg[2], seg[3]);
| Method | Returns | Description |
|---|---|---|
| union(c1,c2,sz) | PShape | merged outline |
| getUnionContour(c1,c2,sz) | PVector[] | merged contour pts |
| intersect(c1,c2,sz) | PShape | overlap region |
| getIntersectContour(c1,c2,sz) | PVector[] | overlap contour pts |
| subtract(c1,c2,sz) | PShape | c1 minus c2 |
| getSubtractContour(c1,c2,sz) | PVector[] | knockout contour |
| getDashedOutline(ch,sz,dash,gap) | float[][] | {x1,y1,x2,y2}/seg; per-contour |
| getDashedOutline(str,sz,dash,gap) | float[][] | word variant; per-contour |
Config-driven: add
"glyphOutline": {"style":"dashedOnly", …} to
config.json — AT renders outlines automatically. Four
styles: OUTLINE_NONE, OUTLINE_SOLID,
OUTLINE_DASHED,
OUTLINE_DASHED_ONLY (fill suppressed). See
GlyphOutline: O cycles all 4; ←/→=char;
S=save.
// Unit tangent direction at arc-length t ∈ [0,1]
PVector tan = g.getTangent('A', 400, 0.25f);
float angle = atan2(tan.y, tan.x);
// Orient 48 ornaments along an outline
PVector[] path = g.getOuterContour('O', 600);
for (int k = 0; k < 48; k++) {
float t = (float) k / 48;
PVector pos = /* samplePath(path, t) */;
PVector dir = g.getTangent('O', 600, t);
pushMatrix(); translate(pos.x, pos.y);
rotate(atan2(dir.y, dir.x));
text("›", 0, 0); popMatrix();
}
// Extra tessellation density for deformation
PVector[] dense = g.subdivide('O', 400, 6);
// Lay a string along any contour
PVector[] outer = g.getOuterContour('O', 600);
g.textOnPath("Around the letterform", outer, 24);
| Method | Returns | Notes |
|---|---|---|
| getTangent(ch, size, t) | PVector | unit dir at arc t∈[0,1] |
| subdivide(ch, size, n) | PVector[] | n pts per segment |
| textOnPath(text, path, sz) | PShape | glyph group on contour |
| textOnPath(text, path, sz, spacing) | PShape | + extra px between chars |
See TextOnPath: mode 1 text on outer contour; mode 2 tangent-oriented ornaments; SPACE cycles chars, T cycles text, S saves.
Font outlines drive the animation. Switch typefaces and the animation changes character automatically.
import algorithmic.typography.render.*;
GlyphExtractor g =
new GlyphExtractor(this, "Garamond", 600);
// Individual metrics
float axis = g.getStressAxis('A', 600); // °
PVector centre = g.getOpticalCentroid('A', 600);
float ratio = g.getCounterRatio('O', 600); // 0–1
float weight = g.getStrokeWeight('A', 600); // px
// Aggregate into a reusable profile
TypeDNAProfile dna =
g.buildTypeDNAProfile('A', 600);
// One-call preset — returns this for chaining
at.applyTypeDNA(dna).initialize();
// JSON round-trip — save & share presets
saveJSONObject(dna.toJSON(), "garamond.json");
TypeDNAProfile p =
TypeDNAProfile.fromJSON(
loadJSONObject("garamond.json"));
at.applyTypeDNA(p).initialize();
applyTypeDNA maps the profile:
| DNA field | Config parameter set |
|---|---|
| stressAxis | waveAngle |
| counterRatio | waveAmplitudeRange |
| strokeWeight | brightnessRange |
| Method | Returns | Use as… |
|---|---|---|
| getStressAxis(ch,sz) | float ° | waveAngle |
| getOpticalCentroid(ch,sz) | PVector | orbit / pivot origin |
| getCounterRatio(ch,sz) | float 0–1 | amplitude / breathing |
| getStrokeWeight(ch,sz) | float px | brightness / mass |
| buildTypeDNAProfile(ch,sz) | TypeDNAProfile | full fingerprint |
| # | Tip |
|---|---|
| 1 |
Always call initialize() after loading or setting
a configuration.
|
| 2 |
Use setAutoRender(false) whenever you draw
backgrounds or composite.
|
| 3 |
renderAt(x, y, w, h) places the grid anywhere —
split layouts, insets, etc.
|
| 4 |
Track your own startTime = millis() — the
library's start time is private.
|
| 5 |
Call restart() + reset
startTime together when restarting.
|
| 6 |
Set secondChangeTime to 0 in JSON to
use only two grid stages.
|
| # | Tip |
|---|---|
| 7 | Colours are HSB: hue 0–360, saturation 0–255, brightness 0–255. |
| 8 |
Place images and JSON files in the sketch's
data/ folder.
|
| 9 |
Builder pattern:
new Configuration.Builder().initialTiles(8,8).build()
|
| 10 |
Javadoc is in reference/ — open
index.html in a browser.
|
| 11 |
RippleMotion.trigger(x,y) must be called from
mousePressed(); call
setTileGrid() once in setup().
|
| 12 |
All GlyphExtractor methods have String overloads
— pass a word (e.g. "TYPE") wherever a single
char was accepted.
|