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