# SVG Matrices & Layouts

> How nittygrid maps animations onto custom-shaped displays drawn as SVG, how to import a background image in Matrix Setup, and how SVGs are kept safe offline.

Real displays are rarely a neat rectangle. A car light bar curves, a wearable follows the body, a sign has gaps. nittygrid handles this by splitting the problem in two:

- **Where animations draw:** a plain grid of cells, like a tiny screen.
- **How your display looks:** an **SVG** file (a vector drawing) where each shape is one physical LED.

Each frame, nittygrid reads the color of a grid cell and paints it onto the matching SVG shape. Effects never need to know your display's shape.

---

## Add a background image

In **Matrix setup** you can import a photo or render of your product (PNG, JPG, WebP) behind your LEDs. This works for both regular grids and SVG layouts.

- **Align by dragging.** Move and resize the LED overlay on the photo until it matches the real LEDs. Grids can be stretched freely (hold **Shift** on a corner to keep proportions). SVG layouts always keep their aspect ratio.
- **Image scale vs. Preview zoom.** *Image scale* changes how big the photo is in the saved result. *Preview zoom* (or trackpad pinch) only magnifies your view while aligning.
- **It is saved with your work.** The image and alignment are stored in matrix presets and in `.ngproj` files. An `.mp4` export can include it too (**Include preview image**).

---

## How to prepare an SVG

SVG can contain almost anything. For LED layouts, **keep it simple**.

> [!IMPORTANT] **Basic shapes, with clear addresses when order matters.**
> - **One simple shape per LED.** Use `<rect>`, `<circle>`, `<ellipse>`, `<polygon>`, or a clean closed `<path>`.
> - **Label rows and columns for precise control.** Without addresses, nittygrid sorts shapes by their center points, top to bottom and then left to right. SVG layer order does not define hardware wiring.
> - **Avoid masks and effects.** Do not use `<mask>`, `<clipPath>`, `<filter>`, `<pattern>`, or `<use>` to form LEDs. In Illustrator, Figma, or Inkscape, turn them into plain shapes before exporting.

1. **Draw one shape per LED**
   Shapes inside `<defs>`, `<clipPath>`, `<mask>`, or `<symbol>` are ignored. Standalone `<use>` copies are rejected, so expand them into real shapes first.
2. **Optionally label rows and columns**
   Add `id="px-ROW-COL"` (or `data-row` and `data-col`) with numbers that start at `0` in the **top-left**:
   ```xml
   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 40">
     <rect id="px-0-0" data-led-id="px-0-0" data-row="0" data-col="0" x="4"  y="4" width="28" height="12" rx="2" />
     <rect id="px-0-1" data-led-id="px-0-1" data-row="0" data-col="1" x="44" y="4" width="28" height="12" rx="2" />
     <rect id="px-0-2" data-led-id="px-0-2" data-row="0" data-col="2" x="84" y="4" width="28" height="12" rx="2" />
   </svg>
   ```
3. **Or upload it without labels**
   nittygrid sorts shapes by their center points: ascending `y`, then ascending `x`. It assigns the resulting list to consecutive grid cells. This is a spatial fallback, not a wiring order; add row and column labels when you need a specific mapping.
4. **Split cells (optional)**
   If one cell holds two separately wired LEDs, such as an upper and a lower triangle, mark them `data-part="0"` and `data-part="1"`. They stay two separate LEDs in exports.

> [!NOTE] **Bottom-up files are fixed automatically.** If your SVG uses `id="led-r00-c000"` with row 0 at the **bottom**, nittygrid flips it to the standard top-left start on import and keeps the original name in `data-source-id`.

---

## Safety and cleanup

SVG files are XML and could in theory contain scripts or links to the internet. nittygrid cleans every SVG you upload, restore, or import from a project file.

- **No code runs.** Scripts and animations are removed. The layout is shown inside an isolated shadow root and never parsed as HTML, so its styles cannot change the app.
- **Fully offline.** External images, fonts, and stylesheets are removed, so the layout looks the same years from now.
- **Common editor exports just work.** Standard `DOCTYPE` lines, comments, and missing `xmlns` are fixed, and wrapper tags like Illustrator's `<switch>` are unwrapped.

### Technical reference: the exact rules

Generated from the app's own SVG import code, so it always matches what nittygrid does. This is for technicians and AI agents.

| Rule Category | Specification |
| :--- | :--- |
| **Eligible LED geometry (`SHAPE_TAGS`)** | `<path>`, `<circle>`, `<rect>`, `<ellipse>`, `<polygon>`, `<polyline>`, `<line>` |
| **Preserved geometry attributes** | `d`, `points`, `x`, `y`, `width`, `height`, `cx`, `cy`, `r`, `rx`, `ry`, `x1`, `y1`, `x2`, `y2`, `transform`, `fill-rule`, `clip-rule`, `pathLength` |
| **Excluded definition contexts & `<use>` policy** | Shapes inside `<defs>`, `<clipPath>`, `<mask>`, `<marker>`, `<pattern>`, or `<symbol>` are presentation definitions, never LEDs. Standalone `<use>` instances are rejected—expand instances into independent shapes before importing. |
| **Canonical LED metadata & group inheritance** | `data-led-id` (stable portable LED identity), `id="px-ROW-COL"`, `data-row="ROW"`, `data-col="COL"`, optional `data-part="PART"` (0-based top-left origin). Logical addresses on ancestor `<g>` or `<svg>` elements are inherited during normalization; unusable or duplicate metadata attributes are stripped cleanly on import instead of rejecting the file. |
| **Untagged sampling order & identity** | Without sampling addresses, LEDs are sorted by center `cy`, then `cx` and sampled from consecutive grid cells. Addressed layouts sort by sampling `y`, then `x`. When a row/col/part address lands on a cell another LED already uses, explicit `data-x`/`data-y` addresses keep their cell and the other LED moves to a spare row below the grid. `data-parts-per-cell` counts only as a positive whole number. IDs remain paired with their source shapes; SVG element order is not a hardware wiring order. |
| **Accepted import aliases & editor cleanup** | `id="led-rNN-cNNN"` (automatically flipped from bottom-left to top-left origin on import). Standard DOCTYPEs, XML comments, and CDATA markers are stripped; missing SVG namespaces are added; unsupported containers like Illustrator `<switch>` are unwrapped. |
| **Shadow DOM isolation & removed active elements** | Layouts render inside an isolated shadow root imported from the XML parse (never re-parsed as HTML, and SVG `<style>` rules cannot restyle the app). Strips `<script>`, `<foreignObject>`, `<iframe>`, `<embed>`, `<object>`, `<audio>`, `<video>`, SMIL animation tags, `<handler>`, and `<listener>`. |
| **Offline link, raster & buffer budgets** | External `href` / `xlink:href` / CSS `url(...)` and `@import` rules are stripped. Only local `#...` fragments and header-verified `data:image/(png\|jpeg\|gif\|webp);base64` rasters (≤ 4 MiB, ≤ 4 MP, ≤ 4096 px/side) are allowed. Sampling addresses are capped at a `2048 × 2048` render buffer (`≤ 8 MiB` UTF-8 SVG for portable export). |

### What each LED stores

Each LED in an SVG layout keeps four separate pieces of information:

1. **Identity (`data-led-id`):** its permanent, unique name across saves and `.ngrender.json` / `.ngrender.zip` exports.
2. **Logical address (`row`, `col`, `part`):** optional 0-based grid position, starting top-left.
3. **Sample position (`x`, `y`):** the integer slot in the `W × H` grid that the renderer reads its color from.
4. **Shape (`tag`, `attrs`, `cx`, `cy`):** the actual outline and center point inside the SVG `viewBox`.

LED identities are assigned before sorting and remain paired with their original shapes. For addressed layouts, the pixel list is sorted by sampling `y`, then `x`; for unaddressed layouts, it is sorted by center `cy`, then `cx`. Exported frame colors follow the explicit LED list in the animation JSON. Consumers should use that list, not the SVG element order, to interpret the frames.
