---
estimated_steps: 15
estimated_files: 4
skills_used: []
---

# T03: Build export service with SVG composition, validation, and DXF API client

This task creates the core export logic as pure utility functions: (1) `composeCanvasSVG()` renders visible canvas objects into an SVG string with correct coordinate space and real-world unit dimensions, (2) `validateForExport()` checks for unconverted text objects (blocking error), raster-only images (warning), and missing artboard (blocking error), (3) `exportDxf()` in the API client POSTs composed SVG to `/engine/simplify?output_format=dxf` with `units` and `scale_factor` params and returns a Blob, (4) `triggerDownload()` creates a hidden anchor + blob URL to trigger browser download with proper cleanup.

SVG composition details:
- The SVG viewBox matches artboard dimensions in pixels
- `width`/`height` attributes use real-world units (e.g., `width="4in"` or `width="101.6mm"`)
- RectObject → `<rect>`, CircleObject → `<circle>`, EllipseObject → `<ellipse>`, LineObject → `<polyline>`
- ImageObject with SVG blob src → inline the SVG content (extract path data from blob URL)
- ImageObject with raster src → skip (validation warns about this)
- TextObject → error (validation blocks this — must be converted to paths first)
- Objects with `visible: false` are skipped
- All coordinates are in the artboard's pixel space (the engine handles conversion via scale_factor)

DXF API client:
- New function `exportAsDxf(svgContent: string, units: 'inches' | 'mm', scaleFactor: number, signal?: AbortSignal): Promise<Blob>`
- Uses FormData with file as Blob, output_format=dxf, units, scale_factor
- Returns `response.blob()` not `response.json()`

Unit tests cover: SVG composition with known objects produces correct SVG elements, validation catches text objects, validation warns on raster images, coordinate space is correct.

## Inputs

- ``app/src/types/canvas.ts` — CanvasObject union type (rect, circle, ellipse, line, image, text), ArtboardConfig`
- ``app/src/utils/artboardShapes.ts` — toPx(), fromPx(), PPI constant (96)`
- ``app/src/api/engine.ts` — existing API client with traceImage() and simplifyVector() patterns`
- ``app/src/App.tsx` — updated in T02 with lifted canvas state (provides the state shape export service consumes)`

## Expected Output

- ``app/src/utils/exportService.ts` — composeCanvasSVG(), validateForExport(), triggerDownload() functions`
- ``app/src/utils/__tests__/exportService.test.ts` — tests for SVG composition, validation logic, download trigger`
- ``app/src/api/engine.ts` — new exportAsDxf() function for binary DXF response handling`
- ``app/src/api/__tests__/engine.test.ts` — test for exportAsDxf() with mocked fetch returning blob`

## Verification

cd app && npx vitest run src/utils/__tests__/exportService.test.ts src/api/__tests__/engine.test.ts && npx tsc -b --noEmit
