on 4bec310 help sign in
elk/runtime/Render/Collage.js
355 lines 11 kB history raw
2ElmRuntime.Render.Collage = function() {
4var Render = ElmRuntime.use(ElmRuntime.Render.Element);
5var Transform = Elm.Transform2D.make({});
6var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
7var newElement = Utils.newElement,
8 extract = Utils.extract, fromList = Utils.fromList,
9 fromString = Utils.fromString, addTransform = Utils.addTransform;
11function trace(ctx, path) {
12 var points = fromList(path);
13 var i = points.length - 1;
14 if (i <= 0) return;
15 ctx.moveTo(points[i]._0, points[i]._1);
16 while (i--) { ctx.lineTo(points[i]._0, points[i]._1); }
17 if (path.closed) {
18 i = points.length - 1;
19 ctx.lineTo(points[i]._0, points[i]._1);
20 }
23function line(ctx,style,path) {
24 style.dashing.ctor === 'Nil' ? trace(ctx, path) : customLineHelp(ctx, style, path);
25 ctx.scale(1,-1);
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.scale(1,-1);
111 ctx.fill();
114function drawImage(redo, ctx, form) {
115 var img = new Image();
116 img.onload = redo;
117 img.src = fromString(form._3);
118 var w = form._0,
119 h = form._1,
120 pos = form._2,
121 srcX = pos._0,
122 srcY = pos._1,
123 srcW = w,
124 srcH = h,
125 destX = -w/2,
126 destY = -h/2,
127 destW = w,
128 destH = h;
130 ctx.scale(1,-1);
131 ctx.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
134function renderForm(redo, ctx, form) {
135 ctx.save();
136 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
137 if (x !== 0 || y !== 0) ctx.translate(x, y);
138 if (theta !== 0) ctx.rotate(theta);
139 if (scale !== 1) ctx.scale(scale,scale);
140 if (form.alpha !== 1) ctx.globalAlpha = ctx.globalAlpha * form.alpha;
141 ctx.beginPath();
142 var f = form.form;
143 switch(f.ctor) {
144 case 'FPath' : drawLine(ctx, f._0, f._1); break;
145 case 'FImage': drawImage(redo, ctx, f); break;
146 case 'FShape':
147 if (f._0.ctor === 'Left') {
148 f._1.closed = true;
149 drawLine(ctx, f._0._0, f._1);
150 } else {
151 drawShape(redo, ctx, f._0._0, f._1);
152 }
153 break;
154 }
155 ctx.restore();
158function formToMatrix(form) {
159 var scale = form.scale;
160 var matrix = A6( Transform.matrix, scale, 0, 0, scale, form.x, form.y );
162 var theta = form.theta
163 if (theta !== 0)
164 matrix = A2( Transform.multiply, matrix, Transform.rotation(theta) );
166 return matrix;
169function str(n) {
170 if (n < 0.00001 && n > -0.00001) return 0;
171 return n;
174function makeTransform(w, h, form, matrices) {
175 var props = form.form._0.props;
176 var m = A6( Transform.matrix, 1, 0, 0, -1,
177 (w - props.width ) / 2,
178 (h - props.height) / 2 );
179 var len = matrices.length;
180 for (var i = 0; i < len; ++i) { m = A2( Transform.multiply, m, matrices[i] ); }
181 m = A2( Transform.multiply, m, formToMatrix(form) );
183 return 'matrix(' + str( m[0]) + ', ' + str( m[3]) + ', ' +
184 str(-m[1]) + ', ' + str(-m[4]) + ', ' +
185 str( m[2]) + ', ' + str( m[5]) + ')';
188function stepperHelp(list) {
189 var arr = fromList(list);
190 var i = 0;
191 function peekNext() {
192 return i < arr.length ? arr[i].form.ctor : '';
193 }
194 // assumes that there is a next element
195 function next() {
196 var out = arr[i];
197 ++i;
198 return out;
199 }
200 return { peekNext:peekNext, next:next };
203function formStepper(forms) {
204 var ps = [stepperHelp(forms)];
205 var matrices = [];
206 var alphas = [];
207 function peekNext() {
208 var len = ps.length;
209 var formType = '';
210 for (var i = 0; i < len; ++i ) {
211 if (formType = ps[i].peekNext()) return formType;
212 }
213 return '';
214 }
215 // assumes that there is a next element
216 function next(ctx) {
217 while (!ps[0].peekNext()) {
218 ps.shift();
219 matrices.pop();
220 alphas.shift();
221 if (ctx) { ctx.restore(); }
222 }
223 var out = ps[0].next();
224 var f = out.form;
225 if (f.ctor === 'FGroup') {
226 ps.unshift(stepperHelp(f._1));
227 var m = A2(Transform.multiply, f._0, formToMatrix(out));
228 ctx.save();
229 ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
230 matrices.push(m);
232 var alpha = (alphas[0] || 1) * out.alpha;
233 alphas.unshift(alpha);
234 ctx.globalAlpha = alpha;
235 }
236 return out;
237 }
238 function transforms() { return matrices; }
239 function alpha() { return alphas[0] || 1; }
240 return { peekNext:peekNext, next:next, transforms:transforms, alpha:alpha };
243function makeCanvas(w,h) {
244 var canvas = newElement('canvas');
245 canvas.style.width = w + 'px';
246 canvas.style.height = h + 'px';
247 canvas.style.display = "block";
248 canvas.style.position = "absolute";
249 canvas.width = w;
250 canvas.height = h;
251 return canvas;
254function render(model) {
255 var div = newElement('div');
256 div.style.overflow = 'hidden';
257 div.style.position = 'relative';
258 update(div, model, model);
259 return div;
262function nodeStepper(w,h,div) {
263 var kids = div.childNodes;
264 var i = 0;
265 function transform(transforms, ctx) {
266 ctx.translate(w/2, h/2);
267 ctx.scale(1,-1);
268 var len = transforms.length;
269 for (var i = 0; i < len; ++i) {
270 var m = transforms[i];
271 ctx.save();
272 ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
273 }
274 return ctx;
275 }
276 function nextContext(transforms) {
277 while (i < kids.length) {
278 var node = kids[i];
279 if (node.getContext) {
280 node.width = w;
281 node.height = h;
282 node.style.width = w + 'px';
283 node.style.height = h + 'px';
284 ++i;
285 return transform(transforms, node.getContext('2d'));
286 }
287 div.removeChild(node);
288 }
289 var canvas = makeCanvas(w,h);
290 div.appendChild(canvas);
291 // we have added a new node, so we must step our position
292 ++i;
293 return transform(transforms, canvas.getContext('2d'));
294 }
295 function addElement(matrices, alpha, form) {
296 var kid = kids[i];
297 var elem = form.form._0;
299 var node = (!kid || kid.getContext)
300 ? Render.render(elem)
301 : (Render.update(kid, kid.oldElement, elem), kids[i]);
303 node.style.position = 'absolute';
304 node.style.opacity = alpha * form.alpha;
305 addTransform(node.style, makeTransform(w, h, form, matrices));
306 node.oldElement = elem;
307 ++i;
308 if (!kid) {
309 div.appendChild(node);
310 } else if (kid.getContext) {
311 div.insertBefore(node, kid);
312 }
313 }
314 function clearRest() {
315 while (i < kids.length) {
316 div.removeChild(kids[i]);
317 }
318 }
319 return { nextContext:nextContext, addElement:addElement, clearRest:clearRest };
323function update(div, _, model) {
324 var w = model.w;
325 var h = model.h;
327 var forms = formStepper(model.forms);
328 var nodes = nodeStepper(w,h,div);
329 var ctx = null;
330 var formType = '';
332 while (formType = forms.peekNext()) {
333 // make sure we have context if we need it
334 if (ctx === null && formType !== 'FElement') {
335 ctx = nodes.nextContext(forms.transforms());
336 ctx.globalAlpha = forms.alpha();
337 }
339 var form = forms.next(ctx);
340 // if it is FGroup, all updates are made within formStepper when next is called.
341 if (formType === 'FElement') {
342 // update or insert an element, get a new context
343 nodes.addElement(forms.transforms(), forms.alpha(), form);
344 ctx = null;
345 } else if (formType !== 'FGroup') {
346 renderForm(function() { update(div, model, model); }, ctx, form);
347 }
348 }
349 nodes.clearRest();
350 return false;
353return { render:render, update:update };
355};