on 89efcef help sign in
elk/runtime/Render/Collage.js
345 lines 10 kB history raw
2ElmRuntime.Render.Collage = function() {
3'use strict';
5var Render = ElmRuntime.use(ElmRuntime.Render.Element);
6var Matrix = Elm.Matrix2D({});
7var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
8var newElement = Utils.newElement,
9 extract = Utils.extract, fromList = Utils.fromList,
10 fromString = Utils.fromString, addTransform = Utils.addTransform;
12function trace(ctx, path) {
13 var points = fromList(path);
14 var i = points.length - 1;
15 if (i <= 0) return;
16 ctx.moveTo(points[i]._0, points[i]._1);
17 while (i--) { ctx.lineTo(points[i]._0, points[i]._1); }
18 if (path.closed) {
19 i = points.length - 1;
20 ctx.lineTo(points[i]._0, points[i]._1);
21 }
24function line(ctx,style,path) {
25 style.dashing.ctor === 'Nil' ? trace(ctx, path) : customLineHelp(ctx, style, path);
26 ctx.stroke();
29function customLineHelp(ctx, style, path) {
30 var points = fromList(path);
31 if (path.closed) points.push(points[0]);
32 var pattern = fromList(style.dashing);
33 var i = points.length - 1;
34 if (i <= 0) return;
35 var x0 = points[i]._0, y0 = points[i]._1;
36 var x1=0, y1=0, dx=0, dy=0, remaining=0, nx=0, ny=0;
37 var pindex = 0, plen = pattern.length;
38 var draw = true, segmentLength = pattern[0];
39 ctx.moveTo(x0,y0);
40 while (i--) {
41 x1 = points[i]._0; y1 = points[i]._1;
42 dx = x1 - x0; dy = y1 - y0;
43 remaining = Math.sqrt(dx * dx + dy * dy);
44 while (segmentLength <= remaining) {
45 x0 += dx * segmentLength / remaining;
46 y0 += dy * segmentLength / remaining;
47 ctx[draw ? 'lineTo' : 'moveTo'](x0, y0);
48 // update starting position
49 dx = x1 - x0; dy = y1 - y0;
50 remaining = Math.sqrt(dx * dx + dy * dy);
51 // update pattern
52 draw = !draw;
53 pindex = (pindex + 1) % plen;
54 segmentLength = pattern[pindex];
55 }
56 if (remaining > 0) {
57 ctx[draw ? 'lineTo' : 'moveTo'](x1, y1);
58 segmentLength -= remaining;
59 }
60 x0 = x1; y0 = y1;
61 }
64function drawLine(ctx, style, path) {
65 ctx.lineWidth = style.width;
66 var cap = style.cap.ctor;
67 ctx.lineCap = cap === 'Flat' ? 'butt' :
68 cap === 'Round' ? 'round' : 'square';
69 var join = style.join.ctor;
70 ctx.lineJoin = join === 'Smooth' ? 'round' :
71 join === 'Sharp' ? 'miter' : 'bevel';
72 ctx.miterLimit = style.join._0 || 10;
73 ctx.strokeStyle = extract(style.color);
74 return line(ctx, style, path);
77function texture(redo, ctx, src) {
78 var img = new Image();
79 img.src = fromString(src);
80 img.onload = redo;
81 return ctx.createPattern(img, 'repeat');
84function gradient(ctx, grad) {
85 var g;
86 var stops = [];
87 if (grad.ctor === 'Linear') {
88 var p0 = grad._0, p1 = grad._1;
89 g = ctx.createLinearGradient(p0._0, -p0._1, p1._0, -p1._1);
90 stops = fromList(grad._2);
91 } else {
92 var p0 = grad._0, p2 = grad._2;
93 g = ctx.createRadialGradient(p0._0, -p0._1, grad._1, p2._0, -p2._1, grad._3);
94 stops = fromList(grad._4);
95 }
96 var len = stops.length;
97 for (var i = 0; i < len; ++i) {
98 var stop = stops[i];
99 g.addColorStop(stop._0, extract(stop._1));
100 }
101 return g;
104function drawShape(redo, ctx, style, path) {
105 trace(ctx, path);
106 var sty = style.ctor;
107 ctx.fillStyle =
108 sty === 'Solid' ? extract(style._0) :
109 sty === 'Texture' ? texture(redo, ctx, style._0) : gradient(ctx, style._0);
110 ctx.fill();
113function drawImage(redo, ctx, form) {
114 var img = new Image();
115 img.onload = redo;
116 img.src = fromString(form._3);
117 var w = form._0,
118 h = form._1,
119 pos = form._2,
120 srcX = pos._0,
121 srcY = pos._1,
122 srcW = w,
123 srcH = h,
124 destX = -w/2,
125 destY = -h/2,
126 destW = w,
127 destH = h;
129 ctx.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
132function renderForm(redo, ctx, form) {
133 ctx.save();
134 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
135 if (x !== 0 || y !== 0) ctx.translate(x, y);
136 if (theta !== 0) ctx.rotate(theta);
137 ctx.scale(scale,-scale);
138 ctx.beginPath();
139 var f = form.form;
140 switch(f.ctor) {
141 case 'FPath' : drawLine(ctx, f._0, f._1); break;
142 case 'FImage': drawImage(redo, ctx, f); break;
143 case 'FShape':
144 if (f._0.ctor === 'Left') {
145 f._1.closed = true;
146 drawLine(ctx, f._0._0, f._1);
147 } else {
148 drawShape(redo, ctx, f._0._0, f._1);
149 }
150 break;
151 }
152 ctx.restore();
155function formToMatrix(form) {
156 var scale = form.scale;
157 var matrix = A6( Matrix.matrix, scale, 0, 0, scale, scale * form.x, scale * form.y );
159 var theta = form.theta
160 if (theta !== 0)
161 matrix = A2( Matrix.multiply, matrix, Matrix.rotation(theta) );
163 return matrix;
166function makeTransform(w, h, form, matrices) {
167 var props = form.form._0.props;
168 var m = A6( Matrix.matrix, 1, 0, 0, 1,
169 (w - props.width)/2,
170 (h - props.height)/2 );
171 var len = matrices.length;
172 for (var i = 0; i < len; ++i) { m = A2( Matrix.multiply, m, matrices[i] ); }
173 m = A2( Matrix.multiply, m, formToMatrix(form) );
175 return 'matrix(' + m[0] + ',' + m[3] + ',' +
176 (-m[1]) + ',' + (-m[4]) + ',' +
177 m[2] + ',' + m[5] + ')';
180function stepperHelp(list) {
181 var arr = fromList(list);
182 var i = 0;
183 function peekNext() {
184 return i < arr.length ? arr[i].form.ctor : '';
185 }
186 // assumes that there is a next element
187 function next() {
188 var out = arr[i];
189 ++i;
190 return out;
191 }
192 return { peekNext:peekNext, next:next };
195function stepper(forms) {
196 var ps = [stepperHelp(forms)];
197 var matrices = [];
198 function peekNext() {
199 var len = ps.length;
200 var formType = '';
201 for (var i = 0; i < len; ++i ) {
202 if (formType = ps[i].peekNext()) return formType;
203 }
204 return '';
205 }
206 // assumes that there is a next element
207 function next(ctx) {
208 while (!ps[0].peekNext()) { ps.shift(); matrices.pop(); ctx.restore(); }
209 var out = ps[0].next();
210 var f = out.form;
211 if (f.ctor === 'FGroup') {
212 ps.unshift(stepperHelp(f._1));
213 var m = A2( Matrix.multiply, f._0, formToMatrix(out));
214 ctx.save();
215 ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
216 matrices.push(m);
217 }
218 return out;
219 }
220 function transforms() { return matrices; }
221 return { peekNext:peekNext, next:next, transforms:transforms };
224function makeCanvas(w,h) {
225 var canvas = newElement('canvas');
226 canvas.style.width = w + 'px';
227 canvas.style.height = h + 'px';
228 canvas.style.display = "block";
229 canvas.style.position = "absolute";
230 canvas.width = w;
231 canvas.height = h;
232 return canvas;
235function render(model) {
236 var div = newElement('div');
237 update(div, model, model);
238 return div;
241function updateTracker(w,h,div) {
242 var kids = div.childNodes;
243 var i = 0;
244 function transform(transforms, ctx) {
245 ctx.translate(w/2, h/2);
246 var len = transforms.length;
247 for (var i = 0; i < len; ++i) {
248 var m = transforms[i];
249 ctx.save();
250 ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
251 }
252 return ctx;
253 }
254 function getContext(transforms) {
255 while (i < kids.length) {
256 var node = kids[i++];
257 if (node.getContext) {
258 node.width = w;
259 node.height = h;
260 node.style.width = w + 'px';
261 node.style.height = h + 'px';
262 return transform(transforms, node.getContext('2d'));
263 }
264 div.removeChild(node);
265 }
266 var canvas = makeCanvas(w,h);
267 div.appendChild(canvas);
268 // we have added a new node, so we must step our position
269 ++i;
270 return transform(transforms, canvas.getContext('2d'));
271 }
272 function element(matrices, form) {
273 var container = kids[i];
274 if (!container || container.getContext) {
275 container = newElement('div');
276 container.style.overflow = 'hidden';
277 container.style.position = 'absolute';
278 addTransform(container.style, 'scaleY(-1)');
279 if (!container) {
280 div.appendChild(container);
281 } else {
282 var kid = kids[i];
283 if (kid) {
284 div.insertBefore(container, kid);
285 } else {
286 div.appendChild(container);
287 }
288 }
289 }
290 // we have added a new node, so we must step our position
291 ++i;
293 container.style.width = w + 'px';
294 container.style.height = h + 'px';
296 var elem = form.form._0;
297 var node = container.firstChild;
298 if (node) {
299 Render.update(node, node.oldElement, elem);
300 node = container.firstChild;
301 } else {
302 node = Render.render(elem);
303 container.appendChild(node);
304 }
305 node.oldElement = elem;
306 addTransform(node.style, makeTransform(w, h, form, matrices));
307 }
308 return { getContext:getContext, element:element };
312function update(div, _, model) {
313 var w = model.w;
314 var h = model.h;
315 div.style.width = w + 'px';
316 div.style.height = h + 'px';
317 if (model.forms.ctor === 'Nil') {
318 while (div.hasChildNodes()) {
319 div.removeChild(div.lastChild);
320 }
321 }
322 var stpr = stepper(model.forms);
323 var tracker = updateTracker(w,h,div);
324 var ctx = null;
325 var formType = '';
327 while (formType = stpr.peekNext()) {
328 if (ctx === null && formType !== 'FElement') {
329 ctx = tracker.getContext(stpr.transforms());
330 ctx.scale(1,-1);
331 }
332 var form = stpr.next(ctx);
333 if (formType === 'FElement') {
334 tracker.element(stpr.transforms(), form);
335 ctx = null;
336 } else if (formType !== 'FGroup') {
337 renderForm(function() { update(div, model, model); }, ctx, form);
338 }
339 }
340 return div;
343return { render:render, update:update };
345};