Skip to main content

Warp overview

<x:warp field="…"> wraps arbitrary child geometry and applies a deformation field to it, non-destructively. SVG's transform is affine-only, so perspective and envelope distortions cannot ride on vector geometry — instead xsvg bakes the field into deformed <path>s at compile time. The children stay editable in the source; the output is exact static SVG.

Compiling…
Envelope presets baked to plain pathsOpen in viewer ↗Edit in Playground →
Eight Make-with-Warp presets applied to a rect + outlined text; the dashed box is the unwarped source.

Syntax

<x:warp field="flag" bend="60">
<rect x="0" y="0" width="240" height="80" fill="#14532d"/>
<x:textbox x="0" y="0" width="240" height="80" align="center" valign="middle"
outline="true">WAVING</x:textbox>
</x:warp>

The bake

Every warp is the same three steps, parameterized only by the field and a tolerance:

flatten → map → refit. Flatten each source path to a polyline within a Hausdorff tolerance; map every vertex through the field; refit the mapped polyline back to <path> segments.

Bézier curves are affine-invariant, so an affine field could map control points directly — but a non-affine field must go through flatten → map → refit (mapping control points alone is wrong). Chords are subdivided adaptively: a segment splits while its mapped midpoint deviates from the mapped chord by more than tolerance, so long straight edges curve smoothly under a nonlinear field while line-preserving fields (perspective) emit zero waste.

The tolerance is the graded quality knob: the quality profile sets it (shapes bake at looser tolerances than glyph runs), so tightening the profile trades path size for fidelity. It is the only approximation step — the emitted <path> is exact SVG. Coordinates quantize to a decimal grid and serialize as relative commands with implicit repetition, so dense glyph runs stay compact.

What can be warped

Children first lower to pure <path> geometry:

  • Basic shapes convert directly — <rect> (including a rounded <rect>, whose corners lower to arcs), <circle>, <ellipse>, <polygon>, <polyline>, and <path> as-is.
  • xsvg text participates through its outlined form — set outline="true" on <x:textbox>, or use <x:textpath> output. Live <text> cannot be warped.
  • Nested <x:warp>s bake innermost-first.

The pre-warp union bbox of that geometry is the envelope frame, so a preset scales with the art it warps. A child that cannot become path geometry (live <text>, <line>, <image>, <use>) is skipped with a marker comment — a warp never silently emits unwarped content.

The field families

FamilyFieldsPage
Envelope presetsarch · flag · rise · wave · fisheye · inflate · squeeze · twistEnvelope presets
Arc & shellarc · arc-lower · arc-upper · bulge · fish · shell-lower · shell-upperEnvelope presets
Corner-drivenperspective · free, plus distort-h / distort-vPerspective & free distort
Spine & noisebend · roughenBend & roughen

Attributes

AttributeValuesDefaultDescription
fieldone of the fields aboveselects the deformation field
bendnumber, −100…100 (%)0preset strength; clamped
axish | vhthe bend axis; ignored by radial/rotational presets
corners"x0,y0 x1,y1 x2,y2 x3,y3" (TL TR BR BL)target corners for perspective / free
distort-h, distort-vnumber, −100…100 (%)0projective taper composed after the field
in#idthe spine path for bend
align, startas type-on-a-pathstart, 0place the bend envelope along the spine
detailnumber > 010roughen ridge frequency (ridges per 100 user units)

An unknown or absent field, or no usable geometry, emits the children unwarped behind a marker. A path that fails to bake keeps its original geometry, and non-finite coordinates never reach output.

How it lowers

The compiler emits the baked <path>s inside a single <g> that carries the element's paint and its affine transform (affine, so it composes after the bake for free). At the current profile grades the bake emits the tolerance-graded polyline (M/L/Z), which stays within tolerance of the true mapped geometry by construction.

See also