Shapes
Business rules of the shapes of a symbol: model, rotation, corner radius, creation tools, booleans, strokes and static drawings.
1. Shape model
Processing chain: typed numbers → integer model → computed geometry → <path> (ADR-0005).
- A shape = one or more closed contours (the first is the outside, the next ones are holes, produced by booleans).
- A contour = ordered list of vertices: coordinates stored by a free shape (ADR-0032, ADR-0033: integers when typed, fractional as a conversion leaves them), fractional when they are derived from it (a regular polygon, later booleans), never written back (ADR-0003).
- Each vertex carries an integer corner radius (0 = sharp corner); a free shape keeps it with its point, as a corner (ADR-0033), a regular polygon in its list
radii(ADR-0029); their number is the number of vertices, otherwise the shape is refused. - The shape is rendered as a single
<path>(ADR-0002). - Geometric primitives: segments and circular arcs only (ADR-0001).
Consequence: every shape is described by numbers. The drawing is determined, not drawn by hand.
Contour orientation (Q11)
- Contours are listed clockwise on screen (SVG frame, y pointing down), starting at the top-left vertex; a regular polygon starts at the right end of its flat base, then its left end, so that the base keeps points 1 and 2 when the number of corners changes (Q18 amended, F08).
- For other shapes, where no vertex is both topmost and leftmost, the contour starts at the topmost vertex, the leftmost on a tie (Q18): the start is a natural one, not a strict rule; only the clockwise order matters to the geometry.
- Point numbers never change once a shape exists: point 1 is the first point of the path's
d, the others follow in its order, wherever its points move or however it turns (F08, F04): points are never renumbered. Only adding or deleting a point shifts the numbers after it by one (interaction.md§3). The top-left, clockwise start holds at creation only. - A contour is cyclic: the last vertex joins the first; every per-vertex computation (corner radius, clamping) wraps around, so the first vertex sees the last edge and the last vertex sees the first edge.
- Once its corners are rounded, the top-left vertex is no longer on the drawn outline: the written path starts where the first corner's arc ends and closes with that arc, keeping the clockwise order (
DERIV-fillet-arcstep 7).
Size (Q20)
- No upper limit chosen in the model nor in the calculations: a size is any safe integer ≥ 0 (up to 2⁵³ − 1, the largest integer a double holds exactly); coordinates lose precision long before that bound.
- Good practice: one user unit is one screen pixel. Draw a view at the size of its screen — even a wall of 8K screens stays far below 100 000 pixels — and let the SVG scale for a screen seen from far.
- The interface caps a width, height or radius typed above 100 000 at 100 000 (US-008).
EPSILONstays absolute. Rounding errors are about 1e-16 × the size: 1e-11 at 100 000, 1e-10 at a million, so underEPSILON= 1e-9 up to a few million units. Beyond, tiny segments of rounding may remain, and near the largest safe integer an output coordinate can be off by a few units (0.5 measured in AUD-002, 2 for a square of side 2⁵³ − 1 in AUD-003): a relative tolerance is not needed for real screens.
Precision (Q10)
SVG_DECIMALS = 5: derived coordinates are written with at most 5 decimals, in their shortest form: no trailing zeros (2.5, not2.50000), never a negative zero (-0.000001is written0).EPSILON = 1e-9: tolerance of floating-point comparisons.- The user only ever enters integers; decimals only appear in derived geometry and output.
Rotation of shapes
- In the Symbol Editor, a shape may be rotated by any integer angle in degrees (e.g. a rectangle tilted by 45°). The angle is stored; the rotated vertices are derived (ADR-0003).
- A shape turns about the center of its box; only shapes turn, never a single point (Product Owner, 2026-10-11, F04).
- A positive angle turns clockwise on screen; the angle is kept from 0 to 359: 370 becomes 10, −90 becomes 270 (Product Owner, 2026-10-11, F04).
- Turning keeps the shape, as in Figma (Product Owner's rule): its width and height stay those before it turned, its corner radii, anchor (the box turns with its content) and point numbers do not change; back to 0 the shape is straight again. In Points mode, x and y are those of the turned point in the SVG (Q27). A turned polygon converted to a free shape keeps its angle (F04).
- Two boxes (Product Owner, 2026-10-11, F04): the turned box, the shape's box turned with it, shown as its selection (a turned square does not seem to grow); the straight box, the smallest upright rectangle around the drawn shape, rounded corners included, as a browser's
getBBox(DERIV-arc-bounding-box), which grows at 45°; it is shown discreetly, with its x, y, width and height. The playground's view stays put, the origin at its top-left: a turned shape turns about its center on screen and may leave the canvas (Product Owner, VAL-007: a shape leaving the view is acceptable). - Position: every shape has one, shown and typed as the x and y of the top-left of its straight box, as in Inkscape; the model stores the top-left of its box before it turns, so that changing a radius does not move a turned shape (ADR-0035). Turning does not move the center of the box, so a square turned by 45° has smaller x and y. A width typed on a turned shape keeps its turned top-left corner in place, as Figma; a point typed leaves the others where they are (F04, US-019); so do a point added in the middle of an edge and a deletion, and a turned polygon converted keeps its drawing.
- Instances in the View Editor only rotate by quarter turns (ADR-0008).
2. Corner radius
Reference behavior: Figma.
- Radius entered numerically, never with the mouse.
- Radius per vertex: select one or more vertices (node editing) → type a value (F03).
- Global radius: in shape mode, the radius field applies to every vertex of the shape. When the vertices have different radii it shows Mixed and is empty; typing a value gives it to every vertex (Product Owner, 2026-10-11, as in Figma).
- Changing the number of corners of a regular polygon keeps the radii by vertex number: vertex 1 keeps vertex 1's radius; removed vertices take their radius away; new vertices have radius 0 (Product Owner, 2026-10-11).
- No "independent corners" switch: the radius of one corner is set in node editing only.
- Geometry: arc tangent to both edges; setback
d = r / tan(θ/2)(DERIV-fillet-setback).
Radius clamping (ADR-0007)
Rule: local proportional reduction per edge (DERIV-local-radius-clamp).
- Two corners with the same radius on a too-short edge → each stops at the middle.
- A single corner pushed to the maximum → the arc covers the whole edge, and goes no further.
- Never an "anti-corner": the arcs of one edge never overlap.
- No error message: the effective value simply caps.
- Only the vertices adjacent to a conflicting edge are reduced.
- A spike — a vertex where the contour turns back on itself — is consumed by its fillet: rounding may eat length (Q16, settled).
- Where no corner can be rounded (a size of 0, an aligned vertex), the radius stays on the vertex and the interface shows it "as requested": it applies as soon as the shape grows (Q17, settled).
- Known limit: on a concave contour, an arc might touch a non-adjacent edge; the clamp only checks adjacent edges. Concave contours exist since F08 (node editing); the case is not detected yet.
Storing the value (Q8, settled):
- The model keeps the requested radius (e.g. 1000).
- The effective radius is derived at every evaluation (e.g. 100 on a 100 square), never stored (ADR-0003).
- Enlarging the shape makes the rounding grow up to the requested value.
- Interface: show the requested value and signal the effective value when they differ.
Useful special case
- Square of side
c+ radiusc/2on all 4 corners = circle. The circle is therefore not a primitive: it is a rounded square. A round-shape tool will draw it without a radius field: a circle in a square box, an ellipse otherwise, which needs curves outside ADR-0001 (F10, moved to E14 by the Product Owner on 2026-10-12, Q4). A capsule is a rectangle whose radius is half its smaller side. - Same for a regular polygon with maximal radius: every fillet stops at the middle of its edges, so the polygon becomes exactly its inscribed circle.
3. Creation tools
Rectangle
The rectangle is a free shape of 4 points made by the rectangle tool (Product Owner, 2026-10-11, F09): underneath, a rectangle is always a free shape. Width and height of any free shape stretch its points, the top-left of its box fixed; a rectangle stays a rectangle; stretching is without loss (0 then back gives the same shape). The eight handles of the box, the opposite side fixed as in Inkscape, come with E02.
Model (ADR-0032, ADR-0033) — a free shape stores:
- its corners, each a point in the shape's own box and its radius (integer ≥ 0); at least 3, spanning a box of positive width and height (contour order Q11);
- its
width,height,angleand positionx,y(ADR-0035).
Numbers: finite; integers when typed; fractional after a conversion (Q27) or an edit (ADR-0034); on a turned shape, integers in the drawing only, the point being stored un-turned (ADR-0035).
Drawing: the stored points stretched from the top-left of their box (
DERIV-free-shape-stretch), moved to the position, then turned by the angle (DERIV-turned-shape). The rectangle tool stores the unit square.A point typed in the SVG's coordinates: the corners are kept as drawn, that point replaced, the box fitted around them; on a turned shape the box is placed so that the other points stay where they are drawn (
DERIV-turned-shapestep 5).Rectangle tool parameters:
width,height(integers ≥ 0; negative forbidden, 0 allowed — Q15),angle(0 to 359),x,y(integers, ADR-0003), andradii, the requested corner radius of each of its 4 vertices in contour order (integers ≥ 0; 0 = sharp corner; ADR-0029). They are creation values: the free shape the tool makes is what is edited afterwards, and its position may become fractional (ADR-0035). A radius larger than the rectangle is clamped, not refused (ADR-0007); the requested value is kept (Q8).Result: contour of 4 vertices.
Regular polygon
Functional reference: Inkscape's Star/Polygon tool.
- Parameters: number of corners, an integer
3 ≤ n ≤ 12(Q19);widthandheightof the box, integers ≥ 0 (negative forbidden, 0 allowed — Q15);radii, the requested corner radius of each of itsnvertices in contour order, integers ≥ 0 (0 = sharp corner, ADR-0029), clamped rather than refused (ADR-0007), the requested values kept (Q8). - Examples:
n=3equilateral triangle,n=4square,n=5pentagon,n=6hexagon. - Rule: the shape fills as much of the
width × heightbox as possible without exceeding it. - Derived vertices (cos/sin): not integers. They are computed, not stored (ADR-0003).
n,width,height, the requestedradii, theanchor, thestretchedoption (ADR-0037), theangleand the positionx,yof its box (ADR-0035) are stored (RegularPolygon).
Chosen mode: uniform (DERIV-regular-polygon-fit).
- The shape stays regular.
- It touches the width, the height, or both, depending on
nand the ratiow/h. - Default orientation: flat base (horizontal bottom edge).
n=4gives a square, not a diamond. - Fitted on the sharp-cornered polygon; the corner radius then rounds inwards, clamped exactly as for the rectangle (same geometry, no rule of its own). At the maximal radius the shape is its inscribed circle and may no longer touch the box (a hexagon in 100 × 100 becomes a circle of diameter 86.6): accepted by the Product Owner (2026-10-09) — removing the radius makes it touch again; a shape filling its box whatever its rounding would be a separate "fill the parent" option.
- Number of corners: integer
3 ≤ n ≤ 12(Q19): process and electrical symbols use triangles, squares, diamonds, hexagons and one octagon (note 0004); beyond 12 sides a polygon is hardly told from a circle, and the circle is already a rounded square. - Other orientations (a diamond is a square turned by 45°, the IEC hexagon stands on a vertex: 30°) come from rotation (F04), applied to the flat-based polygon.
Stretched option (F06, Product Owner 2026-10-12):
- A "stretched" option of the regular polygon, off by default: the polygon then touches the four sides of its box, its base flat at the bottom, symmetric about the vertical axis of the box. Stretching makes some sides longer than others; the growth is spread over all the sides so that their lengths stay as even as possible. The rule (Product Owner, 2026-10-12, from research note 0011, rule C): all sides equal as long as every corner turns at least half of its regular turn (
π/n); past that, that limit polygon is stretched on each axis to the box. The triangle stays isosceles and the square a rectangle: they have no freedom. - In the code:
stretchedPolygon(DERIV-stretched-polygon); the scale of rule C is found by bisection (stretchScale). - It stays a polygon: its number of corners, radii (one per corner, the effective ones may differ since its corners no longer share one angle), angle and position work as for any polygon. Its anchor has no effect: it fills its box. Converted to a free shape, it does not move.
- Figma's polygon does not fill its box (the Product Owner tried it): no functional reference.
Anchor (F07, Product Owner 2026-10-10 and 2026-10-11):
- The polygon is placed in the room its box leaves by an anchor, one of 9 places on a 3 × 3 grid: a horizontal alignment (left, center, right) and a vertical one (top, center, bottom), stored as
min,mid,max(SVG'spreserveAspectRatio,DERIV-regular-polygon-fitstep 5). Default: center on both axes, F02's placement. minputs the polygon's smallest coordinate on the box's (left or top),maxits largest (right or bottom): a triangle in 100 × 100 anchored at the bottom has its base written at y = 100, its apex at 13.39746. In floating point the vertices of an anchored edge are on it within a rounding error, and written as the integer edge (Q10).- The anchor is an intention, always stored: on the axis the polygon fills, the three alignments give the same drawing, and the chosen one is still kept and shown.
- The anchor places the sharp-cornered polygon; the corner radius then rounds inwards without moving it: a flat face on the anchored side keeps touching it, a rounded vertex leaves it (a triangle anchored at the top with radius 10 has its top at y = 10). No "always touch" option, which would push a rounded polygon until it touches its box (Product Owner, 2026-10-11, confirmed at
VAL-003): a polygon is used to attach things to the face that touches its box, and a rounded polygon pushed off its sharp place would put that face, and the measures taken from it, at meaningless positions. - The grid keeps its nine places (Product Owner, 2026-10-11, left to the agent at
VAL-003). In a given box only one axis has room, so only three places give different drawings. A corner place is not a duplicate: it holds both alignments, and the one that counts follows the proportions of the box. A triangle anchored bottom left sits at the bottom of a 100 × 100 box; a square anchored bottom left sits on the left of a 100 × 50 box. The layout box (Q21) uses the same grid for elements with room on both axes. - Rotation (F04) comes after: the box turns with its content.
Text
- Exception to "everything is a path": native
<text>(ADR-0002). - Parameters: content, font, integer size, text anchor (start / middle / end, SVG's
text-anchor; not the anchor of a shape in its box), color. - Reason: converting text to paths requires reading font files, hence a library or a heavy home-made parser.
- Text has its own epic, E15 (Product Owner, 2026-10-12): native text or a
foreignObject, fonts and measured size are studied first (Q31).
Layout box (Q21)
Settled with the Product Owner (2026-10-10, refining F07): a box that places the elements inside it, as a Figma frame with a layout does, made simple for synoptic views rather than following CSS. Delivered by epic E13, with the shape tree; "layout" is a working name, kept only if no better term is found.
The box is a rectangle only, with a corner radius at most: its vertices cannot be edited, otherwise placing its children becomes too complex.
Its children are placed by anchor (the 3 × 3 grid of F07, top-left … bottom-right), or stacked in a row or a column with gaps; the box may have padding. Both modes exist.
Any child may override the anchor and still be laid out by the box: a child may choose the bottom-left anchor where the box places the others top right. This is what Figma lacks: its child in absolute position no longer follows the parent's layout.
The box does not resize to its children (unlike Figma's hug): its size is the one the designer typed.
Children: shapes, text, later symbols. The polygon in its own box (F07) is its single-child case.
A child given its own anchor stays in the box (same group) but leaves the stack completely: the stack follows the box's default layout, the child its own. Overlaps are the designer's business: the option exists, using it well is up to them.
Stacks are a drawing aid, fixed at run time: they live in the Symbol Editor (still there when the symbol is edited again); once the symbol is built, every position is static. An element hidden by an animation leaves its place empty: the stack never closes the gap: only that element, at that place, is hidden.
Out of scope (symbols)
- Pen and freeform drawing in the Symbol Editor: contrary to the "logical" principle.
- Bézier curves, Figma's Bend tool, handle mirroring in symbols (ADR-0001) — until E14 decides otherwise (Q4).
- Corner smoothing (Figma squircle smoothing): produces Béziers.
Static drawings of the View Editor follow other rules (§8).
4. Node editing
See interaction.md §3.
5. Boolean operations
Functional reference: Inkscape's Path menu (REF-INKSCAPE-BOOL).
| Operation | French | Result |
|---|---|---|
| Union | Union | area covered by at least one shape |
| Difference | Différence | bottom shape minus top shape |
| Intersection | Intersection | common area |
| Exclusion | Exclusion | area covered by exactly one shape (XOR) |
| Division | Division | bottom shape cut into pieces by the top outline |
| Cut path | Découpe de chemin | outlines cut at intersections, without fill |
Reference algorithms: REF-MARTINEZ-2009 (sweep line, O((n+k) log n), holes and multiple shapes), REF-GREINER-HORMANN (simpler, tricky degenerate cases). An extension to circular arcs is needed.
Chosen mode: non-destructive (ADR-0006).
- An operation creates a boolean node
{ operation, operands[] }; the operands stay integer and editable. - The result (fractional vertices) only exists in the derived geometry, never in the model.
- The operands' corner radius is applied before the operation.
- Shape Builder tool (reference: Inkscape 1.3,
REF-INKSCAPE-SHAPEBUILDER): non-destructive version, a region is stored as "inside A, outside B…". - No destructive flattening in v1.
- Corner radius on the new vertices of a result: not available in v1 (Q9).
6. Strokes
The stroke is computed as a path, not through the SVG stroke property (ADR-0002).
| Property | Values | Note |
|---|---|---|
| Width | integer ≥ 0 | — |
| Alignment | inner, center, outer | model of the W3C stroke-alignment draft (REF-SVG-STROKES) |
| Join | miter, round, bevel | + miter limit, as in SVG (REF-SVG2-PAINT) |
| Cap (open contours) | butt, round, square | — |
| Dashes | pattern of integers | exact arc length: r × θ |
Rules:
- Open contour: alignment ignored (no inside), as in design tools (
REF-SVGWG-957). - Advantage of the segments + arcs scope: the offset of a circular arc is a concentric circular arc. The offset is therefore exact, without approximation.
- Geometry: offset of the contour at ±width, then the ring between both contours.
7. Shape tree
See interaction.md §6.
8. Static drawings
- Made by business users in the View Editor, with more freedom than symbols but few options.
- A drawing has no parameter and no animation. Selecting a group and saving it creates a drawing.
- Drawings go into a drawing library shared between projects, so that the same drawings are reused from one synoptic view to another.
- Curves are allowed only in drawings (Q12, ADR-0018): a Figma-like pen places points, and dragging a point pulls Bézier handles. Symbols keep ADR-0001 (segments and arcs only).