# SVG Editor

Vector graphics editor with raster-to-SVG auto-tracing, element manipulation, zoom control, and export to SVG or high-resolution PNG.

## Live Demo

<iframe id="se-demo" src="/online/webapp/svg-editor" width="100%" height="500" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>

<script>window._wsConnect('se-demo', 'seSocket');</script>

## Embed

```html
<iframe src="https://sgapps.io/online/webapp/svg-editor"
    width="100%" height="600" frameborder="0"></iframe>
```

### Open with an SVG File

```html
<iframe src="https://sgapps.io/online/webapp/svg-editor/url/aHR0cHM6Ly9leGFtcGxlLmNvbS9pY29uLnN2Zw=="
    width="100%" height="600" frameborder="0"></iframe>
```

---

## Events Reference

---

### `loadSvgImage` -- Load SVG Source

Loads an SVG document from a string into the editor. Use this to programmatically set the SVG content.

| Arg | Type | Description |
|-----|------|-------------|
| `svgString` | string | Raw SVG markup |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "loadSvgImage",
    '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
    '<circle cx="100" cy="100" r="80" fill="coral" />' +
    '<text x="100" y="110" text-anchor="middle" fill="white" font-size="24">Hello</text>' +
    '</svg>',
    function (err) { console.log("SVG loaded"); }
);
```

<button onclick="window._ws('seSocket')&&window.seSocket.fire('webapp::instance::request','loadSvgImage','<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'300\' height=\'200\'><rect width=\'300\' height=\'200\' fill=\'#4a6cf7\' rx=\'10\'/><circle cx=\'150\' cy=\'100\' r=\'60\' fill=\'#fff3\'/><text x=\'150\' y=\'108\' text-anchor=\'middle\' fill=\'white\' font-size=\'20\'>SGApps</text></svg>')">Try: Load Sample SVG</button>

---

### `loadRasterImage` -- Import Raster Image

Imports a raster image (PNG, JPEG, etc.) and auto-traces it to SVG vectors using the ImageTracer algorithm. The result replaces the current editor content.

| Arg | Type | Description |
|-----|------|-------------|
| `imageBase64` | string | Base64 image data |
| `callback` | function | `(err)` |
| `options` | object (optional) | `{ numberofcolors, mincolorratio, linefilter }` |

```js
socket.fire("webapp::instance::request", "loadRasterImage",
    "data:image/png;base64,iVBORw0KGgo...",
    function (err) { console.log("Traced"); },
    { numberofcolors: 16, mincolorratio: 3, linefilter: true }
);
```

---

### `getSVGSource` -- Get SVG Source

Returns the current SVG source as a string. This is the most common way to extract the edited content.

```js
socket.fire("webapp::instance::request", "getSVGSource", function (err, svgString) {
    console.log("SVG source:", svgString.length, "bytes");
});
```

<button onclick="window._ws('seSocket')&&window.seSocket.fire('webapp::instance::request','getSVGSource',function(e,s){if(s)alert('SVG length: '+s.length+' bytes\n\n'+s.substring(0,300)+'...')})">Try: Get SVG Source</button>

### `getSVGDynamicSource`, `getSVGAbsoluteSource` -- Alternative Export

- `getSVGDynamicSource` -- returns SVG with dynamic (relative) coordinates
- `getSVGAbsoluteSource` -- returns SVG with all coordinates converted to absolute

```js
socket.fire("webapp::instance::request", "getSVGAbsoluteSource", function (err, svg) {
    // SVG with absolute path coordinates
});
```

---

### `generateRasterImage` -- Export as PNG

Renders the current SVG to a high-resolution PNG (scaled to 3500px) and returns it as a base64 data URL.

```js
socket.fire("webapp::instance::request", "generateRasterImage", function (err, pngDataUrl) {
    var img = document.createElement('img');
    img.src = pngDataUrl;
    document.body.appendChild(img);
});
```

<button onclick="window._ws('seSocket')&&window.seSocket.fire('webapp::instance::request','generateRasterImage',function(e,d){if(d){var i=document.createElement('img');i.src=d;i.style.maxHeight='120px';document.getElementById('se-png-result').innerHTML='';document.getElementById('se-png-result').appendChild(i);}})">Try: Export PNG</button>
<div id="se-png-result" style="margin:8px 0; min-height:20px;"></div>

---

### `editor:currentElement:getAttribute` -- Get Element Attributes

Reads attributes from the currently selected SVG element.

| Arg | Type | Description |
|-----|------|-------------|
| `attrs` | Array | Attribute names to read (e.g., `["fill", "stroke", "stroke-width"]`) |
| `callback` | function | `(err, values: object)` |

```js
socket.fire("webapp::instance::request", "editor:currentElement:getAttribute",
    ["fill", "stroke", "opacity"],
    function (err, attrs) { console.log(attrs); }
);
```

### `editor:currentElement:setAttribute` -- Set Element Attributes

Sets attributes on the selected element. Pass `null` as a value to remove an attribute.

| Arg | Type | Description |
|-----|------|-------------|
| `attrs` | object | `{ attributeName: value }` |
| `callback` | function | `(err)` |

```js
socket.fire("webapp::instance::request", "editor:currentElement:setAttribute",
    { fill: "#ff6600", "stroke-width": "3", stroke: "#333" }
);
```

---

### `editor:currentElement:set-zoom`, `editor:currentElement:get-zoom` -- Zoom

```js
// Set zoom to 150%
socket.fire("webapp::instance::request", "editor:currentElement:set-zoom", 1.5);

// Get current zoom
socket.fire("webapp::instance::request", "editor:currentElement:get-zoom",
    function (err, zoom) { console.log("Zoom:", zoom); }
);
```

---

## Complete Example

```html
<!DOCTYPE html>
<html>
<head><title>SVG Editor Widget</title></head>
<body>
    <div style="display:flex;gap:8px;padding:10px;flex-wrap:wrap;">
        <button id="load-svg">Load Circle</button>
        <button id="get-svg">Get SVG</button>
        <button id="export-png">Export PNG</button>
    </div>

    <iframe id="editor" src="https://sgapps.io/online/webapp/svg-editor"
        width="100%" height="500" frameborder="0"></iframe>
    <pre id="svg-output" style="background:#f6f8fa;padding:8px;max-height:200px;overflow:auto;"></pre>

    <script src="https://sgapps.io/components/window-socket/index.js"></script>
    <script>
        var socket = new WindowSocket(document.getElementById('editor').contentWindow);
        socket.start();

        socket.on("webapp::connection::ping", function () {
            socket.fire("webapp::instance::embed-mode", true);
        });

        document.getElementById('load-svg').onclick = function () {
            socket.fire("webapp::instance::request", "loadSvgImage",
                '<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">' +
                '<circle cx="150" cy="150" r="120" fill="#4a6cf7" />' +
                '<circle cx="150" cy="150" r="80" fill="#fff" opacity="0.3" />' +
                '</svg>');
        };

        document.getElementById('get-svg').onclick = function () {
            socket.fire("webapp::instance::request", "getSVGSource",
                function (err, svg) {
                    document.getElementById('svg-output').textContent = svg;
                });
        };

        document.getElementById('export-png').onclick = function () {
            socket.fire("webapp::instance::request", "generateRasterImage",
                function (err, data) {
                    var a = document.createElement('a');
                    a.href = data;
                    a.download = 'export.png';
                    a.click();
                });
        };
    </script>
</body>
</html>
```
