Thin-Plate Spline Typography, Safely
Warp an open geometric glyph with landmark pairs, solve a thin-plate spline, measure interpolation and bending, detect crossings, and export reproducible SVG type.
Thin-plate spline typography answers how a few landmark pairs can bend an entire sampled glyph while keeping the original outline and deformation receipt. This tutorial solves the radial-basis field, checks landmark residuals and bending energy, detects sampled segment crossings, and exports editable SVG.
Thin-plate spline typography starts from an open glyph
Use a simple geometric glyph whose outline and license are known. Preserve the original path and sample it at a declared spacing without converting it into an anonymous cloud. The deformation should remain reversible: source path, samples, landmark pairs, solver settings, and exported path all belong in one edition receipt.
Bookstein's principal warps paper describes the landmark-based thin-plate spline framework. Thin-plate spline typography borrows that interpolation model as a creative coding instrument. It does not guarantee font quality, outline validity, hinting, readable words, or freedom from self-intersection.
The constructed landmark fixture uses an original open geometric A-like polyline defined in code, not a copied font glyph. It solves a small landmark system, reports residuals and sampled crossings, and emits SVG path samples. Its numerical results belong only to that bounded local geometry.
Normalize the source into a stable coordinate box while preserving an inverse transform. Solver conditioning improves, and the export can still return to typographic units.
Pair source and target landmarks deliberately
Each source landmark needs one target coordinate. Place landmarks on meaningful anchors such as apex, baseline corners, crossbar joins, or counter boundaries, then add a few surrounding control points if the exterior field needs stability. Too few points make broad unintended motion; too many nearly coincident points can make the system ill-conditioned.
Keep point IDs stable and visualize arrows from source to target. Reject duplicates, non-finite coordinates, and target lists with different length. A glyph deformation interface should distinguish fixed points, moved points, and optional boundary anchors without relying on color alone. Thin-plate spline typography becomes art-directable when the landmark constellation reads as a compositional score.
Start with restrained moves and save named variations. A control-point performance can be expressive, but the archive still needs the exact final coordinates. The anamorphic typography article is adjacent when the transformation comes from a viewing geometry rather than landmark interpolation.
Add exterior anchors only with a stated purpose, such as protecting a margin or baseline region. Invisible controls can otherwise make the deformation feel mysteriously resistant.
- Each landmark has one source and one target coordinate.
- The thin-plate spline interpolates those pairs exactly within numerical tolerance.
- The surrounding grid reveals deformation far from the outline.
| Signal | Interpretation |
|---|---|
| Landmark constellation and warped grid | Source and target landmark pairs pull a square grid through a smooth radial-basis deformation. |
Solve affine and radial terms together
The two-dimensional warp combines an affine function with weighted radial basis terms centered at the source landmarks. For the classic two-dimensional thin-plate spline, the radial kernel uses squared distance times the logarithm of distance, with a defined zero limit at the center. Solve separate coefficient vectors for output x and y under the standard side constraints.
Use a reviewed linear solver with pivoting and expose singular or poorly conditioned systems. Do not invert a matrix by handwritten formula in production code. The local fixture includes a compact Gaussian elimination routine only for its small deterministic case. Principal warps provide a way to interpret deformation modes, while the tutorial focuses on one direct interpolation solve.
Thin-plate spline typography needs a regularization policy if exact interpolation is relaxed. Name the parameter and its effect rather than quietly adding epsilon until the solver stops throwing. Exact landmarks and smoothed landmarks are different artistic contracts.
Compare solver residuals against a tolerance scaled to the normalized domain. A raw decimal threshold becomes meaningless when the glyph coordinate system changes, even if the radial basis warp is otherwise identical.
Warp samples, then rebuild SVG paths
Evaluate the solved field at every outline sample and at diagnostic grid points. Preserve contour order and open or closed status. A polyline export is easiest to audit; a later curve-fitting step may reduce points but introduces approximation error and must retain corners intentionally. The SVG 2 path specification defines the output geometry.
For cubic source paths, sampling by roughly uniform arc length gives more stable detail than equal parameter steps. Store the sampling tolerance and fitting tolerance in document units. SVG typography distortion should not change unpredictably when the viewBox scales. Normalize before solving, then transform back for export.
The SVG path morphing workflow differs because it establishes compatible path topology between keyframes. A thin-plate spline provides a spatial field that can warp any sampled point, including guides and decoration, but it does not create correspondence between unrelated glyph outlines.
Retain corner samples before curve fitting. A smoother approximation that rounds a deliberate apex may reduce point count while weakening the letter's voice.
- The affine term carries translation, rotation, scale, and shear.
- Radial terms add the non-affine bend around landmarks.
- Energy is a comparative diagnostic, not a typographic quality score.
| Signal | Interpretation |
|---|---|
| Bending-energy field behind a glyph | Nested heat contours sit behind a warped geometric glyph outline, with high-curvature zones marked. |
The typography solver pairs coefficient and residual evidence with detailed safe and rejected crossing records in one namespaced SVG.
Runnable artifact — thin-plate-type-fixture.mjs
import assert from "node:assert/strict";
const controls=[[[0,0],[0,0]],[[1,0],[1.08,.08]],[[0,1],[-.08,1.02]],[[1,1],[1.02,1.14]],[[.5,.5],[.46,.58]]],kernel=r2=>r2===0?0:r2*Math.log(r2),n=controls.length,system=Array.from({length:controls.length+3},()=>Array(controls.length+3).fill(0));for(let i=0;i<n;i++){for(let j=0;j<n;j++){const dx=controls[i][0][0]-controls[j][0][0],dy=controls[i][0][1]-controls[j][0][1];system[i][j]=kernel(dx*dx+dy*dy)}system[i][n]=1;system[i][n+1]=controls[i][0][0];system[i][n+2]=controls[i][0][1];system[n][i]=1;system[n+1][i]=controls[i][0][0];system[n+2][i]=controls[i][0][1]}
const solve=rhs=>{const matrix=system.map((row,index)=>[...row,rhs[index]]);for(let column=0;column<matrix.length;column++){let pivot=column;for(let row=column+1;row<matrix.length;row++)if(Math.abs(matrix[row][column])>Math.abs(matrix[pivot][column]))pivot=row;assert.ok(Math.abs(matrix[pivot][column])>1e-12);[matrix[column],matrix[pivot]]=[matrix[pivot],matrix[column]];const divisor=matrix[column][column];for(let cell=column;cell<=matrix.length;cell++)matrix[column][cell]/=divisor;for(let row=0;row<matrix.length;row++){if(row===column)continue;const factor=matrix[row][column];for(let cell=column;cell<=matrix.length;cell++)matrix[row][cell]-=factor*matrix[column][cell]}}return matrix.map(row=>row.at(-1))},xCoefficients=solve([...controls.map(pair=>pair[1][0]),0,0,0]),yCoefficients=solve([...controls.map(pair=>pair[1][1]),0,0,0]),evaluate=(point,coefficients)=>{let value=coefficients[n]+coefficients[n+1]*point[0]+coefficients[n+2]*point[1];for(let i=0;i<n;i++){const dx=point[0]-controls[i][0][0],dy=point[1]-controls[i][0][1];value+=coefficients[i]*kernel(dx*dx+dy*dy)}return value},warp=point=>[evaluate(point,xCoefficients),evaluate(point,yCoefficients)];
const residuals=controls.map(([source,target])=>Math.hypot(warp(source)[0]-target[0],warp(source)[1]-target[1])),maxResidual=Math.max(...residuals),energyFor=coefficients=>{let total=0;for(let i=0;i<n;i++)for(let j=0;j<n;j++)total+=coefficients[i]*system[i][j]*coefficients[j];return total},bendingEnergy=energyFor(xCoefficients)+energyFor(yCoefficients),safeSource=[[.2,.9],[.5,.08],[.8,.9],[.66,.56],[.34,.56]],safeGlyph=safeSource.map(warp),rejectedGlyph=[[0,0],[1,1],[0,1],[1,0]];
const round=value=>Number(value.toFixed(6)),intersection=(a,b,c,d)=>{const r=[b[0]-a[0],b[1]-a[1]],s=[d[0]-c[0],d[1]-c[1]],denominator=r[0]*s[1]-r[1]*s[0];if(Math.abs(denominator)<1e-12)return null;const delta=[c[0]-a[0],c[1]-a[1]],t=(delta[0]*s[1]-delta[1]*s[0])/denominator,u=(delta[0]*r[1]-delta[1]*r[0])/denominator;if(t<=0||t>=1||u<=0||u>=1)return null;return[a[0]+t*r[0],a[1]+t*r[1]].map(round)},findCrossings=points=>{const details=[];for(let first=0;first<points.length-1;first++)for(let second=first+2;second<points.length-1;second++){const point=intersection(points[first],points[first+1],points[second],points[second+1]);if(point)details.push({segments:[first,second],point})}return details},safeCrossings=findCrossings(safeGlyph),rejectedCrossings=findCrossings(rejectedGlyph);assert.ok(maxResidual<1e-9);assert.ok(Number.isFinite(bendingEnergy)&&bendingEnergy>=-1e-10);assert.deepEqual(safeCrossings,[]);assert.deepEqual(rejectedCrossings,[{segments:[0,2],point:[.5,.5]}]);
const path=points=>"M"+points.map(point=>point.map(round).join(" ")).join("L"),shifted=rejectedGlyph.map(([x,y])=>[x+1.5,y]),marker=rejectedCrossings[0],svg='<svg xmlns="http://www.w3.org/2000/svg" id="tps-a3-export" viewBox="-0.2 -0.2 3 1.6" aria-labelledby="tps-a3-title tps-a3-desc"><title id="tps-a3-title">Safe and rejected thin-plate spline crossing cases</title><desc id="tps-a3-desc">The safe solved glyph has no crossings. The rejected fixture marks segments 0 and 2 at coordinate 0.5, 0.5.</desc><g id="tps-a3-safe"><path fill="none" d="'+path(safeGlyph)+'"/></g><g id="tps-a3-rejected"><path fill="none" d="'+path(shifted)+'"/><circle cx="'+round(marker.point[0]+1.5)+'" cy="'+marker.point[1]+'" r="0.04"/><text x="1.45" y="1.2">segments 0 × 2 at 0.5, 0.5</text></g></svg>',cases=[{id:"safe-tps-warp",crossings:safeCrossings},{id:"deliberate-crossing",crossings:rejectedCrossings}];assert.match(svg,/tps-a3-safe/);assert.match(svg,/tps-a3-rejected/);assert.match(svg,/segments 0 × 2 at 0.5, 0.5/);console.log(JSON.stringify({maxResidual,bendingEnergy,cases,coefficients:{x:xCoefficients,y:yCoefficients},svg}));console.log("PASS: safe and crossing TPS cases emit detailed SVG evidence");
Run node thin-plate-type-fixture.mjs. Expected receipt: PASS: safe and crossing TPS cases emit detailed SVG evidence.
Read bending energy as a comparative diagnostic
The thin-plate model minimizes a bending-energy functional under its interpolation constraints. Compute or approximate energy consistently to compare landmark arrangements for the same source and scale. Higher energy can indicate sharper localized strain, but lower energy is not automatically better typography. A nearly affine warp can have low energy and still destroy the intended letter.
Visualize field derivatives or a sampled strain proxy behind the glyph. Keep the scale and normalization visible when comparing editions. Thin-plate spline typography uses the energy map to ask where the surface works hardest, then returns the decision to typographic judgment: weight, countershape, rhythm, silhouette, and reading context.
The article's heat contours are explanatory. The runnable artifact prints a bounded coefficient-based proxy derived from its frozen system. It does not claim a perceptual threshold or results from reader research. Any usability claim would need committed human evidence.
Use the energy view to compare nearby landmark edits, not unrelated glyphs or scales. The metric gains meaning only under a controlled deformation context.
Detect crossings and collapsed segments
Exact landmark interpolation does not preserve topology. Sampled outline segments can cross, reverse, collapse, or create extreme cusps between control points. Test all non-adjacent segment pairs within each contour, respecting shared endpoints and closed-contour adjacency. Also flag very short segments, orientation reversals, and large local scale changes.
A crossing detector proves only what it tests at the chosen sample density. Increase sampling around curvature and suspicious regions, then inspect visually. Thin-plate spline typography should label an unsafe edition instead of silently filling it with an ambiguous winding rule. Keep the original outline available for side-by-side comparison.
The runnable artifact emits two explicit branches: the solved glyph reports an empty crossing-detail array, while a deliberately crossed polyline reports segments zero and two at coordinate 0.5, 0.5. Its namespaced SVG renders both cases and marks the rejected intersection with a circle and text, not color alone. Crossing-free output is necessary for many uses but still not sufficient for legibility or font engineering.
Crossing tests should run again after curve fitting, because a fitted cubic can overshoot even when the warped sample polyline was safe.
| Diagnostic | Meaning |
|---|---|
| Interpolation residual | landmarks reached? |
| Bending energy | relative field strain |
| Segment crossing | sampled outline may self-intersect |
| Signal | Interpretation |
|---|---|
| Crossing detector in the glyph playground | A sampled outline shows safe segments in solid strokes and one detected self-intersection with explicit markers. |
Preserve typographic judgment outside the solver
Inspect cap height, baseline, stroke distribution, joins, counters, spacing, and word rhythm at final size. A mathematical warp moves points smoothly but knows nothing about optical correction. After the field establishes the main gesture, allow a documented manual refinement layer or add landmarks that encode the missing decision. Do not rewrite the source silently.
The variable fonts article offers a production type system when designed axes and interpolation masters are required. Thin-plate spline typography is better treated as an edition, display experiment, or exploratory deformation unless a type designer completes the broader engineering work.
Use open paths, shapes, or self-authored glyphs during experimentation. Preserve license and provenance for any typeface source. The output should credit its source even when the deformation is substantial, according to the applicable license and ethical context.
Place the deformed glyph beside an undistorted word and neutral shapes. Context exposes weight and spacing damage that an isolated dramatic letter can conceal.
Export the deformation as a recoverable edition
Write namespaced SVG IDs, title, description, source outline, warped outline, optional grid, landmark arrows, and a semantic transcript. Store solver version, kernel, normalization, landmarks, regularization, sample spacing, coefficients or reproducible inputs, residual maximum, energy measure, crossing list, and source license. A static fallback should retain the same visual argument.
Test translation, rotation, uniform affine targets, repeated points, collinear or unstable landmarks, restrained warp, extreme warp, open path, closed contour, and multi-contour glyphs. Thin-plate spline typography passes when landmarks are reached within declared numerical tolerance and every detected topology risk remains visible to the artist.
The Lissajous letterforms practice offers a generative alternative built from oscillation rather than deformation. For this method, warp one open glyph and archive its landmarks before composing a word. That keeps the mathematical field and the typographic decision equally inspectable.
Archive rejected editions too when they teach a boundary. Their landmarks and crossing receipts become a useful negative corpus for future interaction design. Label why each was rejected—crossing, collapsed counter, excessive bend, or lost rhythm—without presenting that artistic judgment as an objective solver metric or universal typographic rule.