1var svgCache;
2var svgDefs;
3var svgGradients;
4var svgNS = "http://www.w3.org/2000/svg";
5var svgRoot;
6
7function displaySvg(displayList) {
8    for (var index = 0; index < displayList.length; ++index) {
9        drawToSvg(displayList[index]);
10    }
11}
12
13function drawToSvg(display) {
14    assert('string' == typeof(display.ref));
15    var cache;
16    if (display.ref in svgCache) {
17        cache = svgCache[display.ref];
18        if (display.drawDirty) {
19            switch (cache.spec) {
20                case "paths":
21                    svgSetPathData(cache.element, display.draw);
22                    break;
23                case "pictures":
24                    svgSetPictureData(cache.element, display.draw);
25                    break;
26                case "text":
27                    svgCreateText(cache.element, display.draw);
28                    break;
29                default:
30                    assert(0);
31            }
32        }
33    } else {
34        cache = {};
35        cache.action = display;
36        cache.spec = display.drawSpec;
37        var dot = cache.spec.indexOf(".");
38        if (dot > 0) {
39            cache.spec = cache.spec.substring(0, dot);
40        }
41        switch (cache.spec) {
42            case "paths":
43                cache.element = svgCreatePath(display.ref, display.draw);
44                break;
45            case "pictures":
46                cache.element = svgCreatePicture(display.ref, display.draw);
47                break;
48            case "text":
49                cache.element = svgCreateText(display.ref, display.draw);
50                break;
51            default:
52                assert(0);
53        }
54    }
55    display.drawDirty = false;
56    if (display.paintDirty) {
57        svgSetPaintData(cache.element, display.paint);
58        var opacity = svg_opacity(display.paint.color);
59        cache.element.setAttribute("fill-opacity", opacity);
60        cache.element.setAttribute("stroke-opacity", opacity);
61        display.paintDirty = false;
62    }
63    assert('object' == typeof(cache));
64    if (!(display.ref in svgCache)) {
65        svgRoot.appendChild(cache.element);
66        svgCache[display.ref] = cache;
67    }
68}
69
70function setupSvg() {
71    svgCache = { "paths":{}, "pictures":{}, "text":{} };
72    svgDefs = document.createElementNS(svgNS, "defs");
73    svgGradients = {};
74    svgRoot = document.getElementById("svg");
75    while (svgRoot.lastChild) {
76        svgRoot.removeChild(svgRoot.lastChild);
77    }
78    svgRoot.appendChild(svgDefs);
79}
80
81function svg_rbg(color) {
82    return "rgb(" + ((color >> 16) & 0xFF)
83            + "," + ((color >>  8) & 0xFF)
84            + "," + ((color >>  0) & 0xFF) + ")";
85}
86
87function svg_opacity(color) {
88    return ((color >> 24) & 0xFF) / 255.0;
89}
90
91function svgCreatePath(key, path) {
92    var svgPath = document.createElementNS(svgNS, "path");
93    svgPath.setAttribute("id", key);
94    svgSetPathData(svgPath, path);
95    return svgPath;
96}
97
98function svgCreatePicture(key, picture) {
99    var svgPicture = document.createElementNS(svgNS, "g");
100    svgPicture.setAttribute("id", key);
101    svgSetPictureData(svgPicture, picture);
102    return svgPicture;
103}
104
105function svgCreateRadialGradient(key) {
106    var g = gradients[key];
107    var e = document.createElementNS(svgNS, "radialGradient");
108    e.setAttribute("id", key);
109    e.setAttribute("cx", g.cx);
110    e.setAttribute("cy", g.cy);
111    e.setAttribute("r", g.r);
112    e.setAttribute("gradientUnits", "userSpaceOnUse");
113    var stopLen = g.stops.length;
114    for (var index = 0; index < stopLen; ++index) {
115        var stop = g.stops[index];
116        var color = svg_rbg(stop.color);
117        var s = document.createElementNS(svgNS, 'stop');
118        s.setAttribute("offset", stop.offset);
119        var style = "stop-color:" + svg_rbg(stop.color) + "; stop-opacity:"
120                + svg_opacity(stop.color);
121        s.setAttribute("style", style);
122        e.appendChild(s);
123    }
124    svgGradients[key] = e;
125    svgDefs.appendChild(e);
126}
127
128function svgCreateText(key, text) {
129    var svgText = document.createElementNS(svgNS, "text");
130    svgText.setAttribute("id", key);
131    var textNode = document.createTextNode(text.string);
132    svgText.appendChild(textNode);
133    svgSetTextData(svgText, text);
134    return svgText;
135}
136
137function svgSetPathData(svgPath, path) {
138    var dString = "";
139    for (var cIndex = 0; cIndex < path.length; ++cIndex) {
140        var curveKey = Object.keys(path[cIndex])[0];
141        var v = path[cIndex][curveKey];
142        switch (curveKey) {
143            case 'arcTo':
144                var clockwise = 1; // to do; work in general case
145                dString += " A" + v[4] + "," + v[4] + " 0 0," + clockwise + " "
146                        + v[2] + "," + v[3];
147                break;
148            case 'close':
149                dString += " z";
150                break;
151            case 'cubic':
152                dString += " M" + v[0] + "," + v[1];
153                dString += " C" + v[2] + "," + v[3]
154                          + " " + v[4] + "," + v[5]
155                          + " " + v[6] + "," + v[7];
156                break;
157            case 'line':
158                dString += " M" + v[0] + "," + v[1];
159                dString += " L" + v[2] + "," + v[3];
160                break;
161            case 'quad':
162                dString += " M" + v[0] + "," + v[1];
163                dString += " Q" + v[2] + "," + v[3]
164                          + " " + v[4] + "," + v[5];
165                break;
166            default:
167                assert(0);
168        }
169    }
170    svgPath.setAttribute("d", dString);
171}
172
173function svgSetPaintData(svgElement, paint) {
174    var color;
175    var inPicture = 'string' == typeof(paint);
176    if (inPicture) {
177        paint = (new Function("return " + paint))();
178        assert('object' == typeof(paint) && !isArray(paint));
179    }
180    if ('gradient' in paint) {
181        var gradient = paint.gradient.split('.');
182        var gradName = gradient[1];
183        if (!svgGradients[gradName]) {
184            svgCreateRadialGradient(gradName);
185        }
186        color = "url(#" + gradName + ")";
187    } else {
188        color = svg_rbg(paint.color);
189    }
190    svgElement.setAttribute("fill", 'fill' == paint.style ? color : "none");
191    if ('stroke' == paint.style) {
192        svgElement.setAttribute("stroke", color);
193    }
194    if ('strokeWidth' in paint) {
195        svgElement.setAttribute("stroke-width", paint.strokeWidth);
196    }
197    if ('typeface' in paint) {
198        var typeface = typefaces[paint.typeface];
199        var font = typeface.style;
200        if ('textSize' in paint) {
201            svgElement.setAttribute("font-size", paint.textSize);
202        }
203        if ('family' in typeface) {
204            svgElement.setAttribute("font-family", typeface.family);
205        }
206        if ('textAlign' in paint) {
207            svgElement.setAttribute("text-anchor", paint.textAlign == "right" ? "end" : assert(0));
208        }
209        if ('textBaseline' in paint) {
210            svgElement.setAttribute("alignment-baseline", paint.textBaseline);
211        }
212    }
213}
214
215function svgSetPictureData(svgPicture, picture) {
216    while (svgPicture.lastChild) {
217        svgPicture.removeChild(svgPicture.lastChild);
218    }
219    for (var index = 0; index < picture.length; ++index) {
220        var entry = picture[index];
221        var drawObj = (new Function("return " + entry.draw))();
222        var drawSpec = entry.draw.split('.');
223        var svgElement;
224        switch (drawSpec[0]) {
225            case 'paths':
226                svgElement = svgCreatePath(drawSpec[1], drawObj);
227                break;
228            case 'pictures':
229                svgElement = svgCreatePicture(drawSpec[1], drawObj);
230                break;
231            case 'text':
232                svgElement = svgCreateText(drawSpec[1], drawObj);
233                break;
234            default:
235                assert(0);
236        }
237        var paintObj = (new Function("return " + entry.paint))();
238        svgSetPaintData(svgElement, paintObj);
239        svgPicture.appendChild(svgElement);
240    }
241}
242
243function svgSetTextData(svgElement, text) {
244    svgElement.setAttribute('x', text.x);
245    svgElement.setAttribute('y', text.y);
246}
247