UNPKG

mrscheme

Version:

Scheme didactic subset interpreter in JavaScript.

788 lines (705 loc) 22 kB
/*! jCanvas v3.6 Copyright 2011, Caleb Evans Licensed under the MIT license http://calebevans.me/projects/jcanvas/license.html */ export function installJCanvas ($) { $.extend = Object.assign; $.fn = {}; $.event = {}; //(function($, document, Math, Image, undefined) { // jCanvas function function jC(args, defaults) { // Reset to defaults if nothing is passed if (args === undefined) { jC.prefs = $.extend({}, jC.defaults); // Merge arguments with defaults } else if (defaults === true) { jC.defaults = $.extend({}, jC.defaults, args); jC.prefs = $.extend({}, jC.defaults); // Merge arguments with preferences } else { jC.prefs = $.extend({}, jC.prefs, args); } return this; } // Set jCanvas default properties jC.defaults = { width: 0, height: 0, cornerRadius: 0, fillStyle: 'transparent', strokeStyle: 'transparent', strokeWidth: 1, strokeCap: 'butt', strokeJoin: 'miter', rounded: false, shadowX: 0, shadowY: 0, shadowBlur: 3, shadowColor: 'transparent', opacity: 1, compositing: 'source-over', mask: false, x: 0, y: 0, x1: 0, y1: 0, x2: 0, y2: 0, radius: 0, start: 0, end: 360, ccw: false, inDegrees: true, fromCenter: true, closed: false, sides: 3, points: 5, angle: 0, text: '', font: 'normal 12pt sans-serif', align: 'center', baseline: 'middle', source: '', repeat: 'repeat' }; // Merge defaults with preferences jC.prefs = $.extend({}, jC.defaults); jC.retro = false; // Set global properties jC.setGlobals = function(ctx, params) { ctx.fillStyle = params.fillColor || params.fillStyle; ctx.strokeStyle = params.strokeColor || params.strokeStyle; ctx.lineWidth = params.strokeWidth; ctx.lineCap = params.strokeCap; ctx.lineJoin = params.strokeJoin; // Set rounded corners for paths if (params.rounded === true) { ctx.lineCap = 'round'; ctx.lineJoin = 'round'; } ctx.shadowOffsetX = params.shadowX; ctx.shadowOffsetY = params.shadowY; ctx.shadowBlur = params.shadowBlur; ctx.shadowColor = params.shadowColor; ctx.globalAlpha = params.globalAlpha || params.opacity; ctx.globalCompositeOperation = params.compositing; }; // Close path if chosen jC.closePath = function(ctx, params) { // Mask if chosen if (params.mask === true) { ctx.save(); ctx.clip(); } if (params.closed === true) { ctx.closePath(); ctx.fill(); ctx.stroke(); } else { ctx.fill(); ctx.stroke(); ctx.closePath(); } }; // Measure angles in correct units jC.checkUnits = function(params) { if (params.inDegrees === true) { return Math.PI / 180; } else { return 1; } }; // Rotate shape jC.rotate = function(ctx, params, width, height) { // Always rotate from center if (params.fromCenter === false) { params.x += width/2; params.y += height/2; } params.toRad = jC.checkUnits(params); ctx.save(); ctx.translate(params.x, params.y); ctx.rotate(params.angle*params.toRad); ctx.translate(-params.x, -params.y); }; // Load canvas $.fn.loadCanvas = function(ctx) { return this[0].getContext(ctx || '2d'); }; // Draw on canvas manually $.fn.draw = function(callback) { var ctx, e; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); callback.call(this[e], ctx); } }; // Create gradient $.fn.gradient = function(args) { var ctx = this.loadCanvas(), params = $.extend({}, jC.prefs, args), gradient, percent, stops = 0, i = 1; // Create radial gradient if chosen if (params.r1 !== undefined && params.r2 !== undefined) { gradient = ctx.createRadialGradient(params.x1, params.y1, params.r1, params.x2, params.y2, params.r2); } else { gradient = ctx.createLinearGradient(params.x1, params.y1, params.x2, params.y2); } // Count number of color stops while (params['c' + i] !== undefined) { stops += 1; i += 1; } // Calculate color stop percentages if absent for (i=1; i<=stops; i+=1) { percent = Math.round((100 / (stops-1)) * (i-1)) / 100; if (params['s' + i] === undefined) { params['s' + i] = percent; } gradient.addColorStop(params['s' + i], params['c' + i]); } return gradient; }; // Create pattern $.fn.pattern = function(args) { var ctx = this.loadCanvas(), params = $.extend({}, jC.prefs, args), img = new Image(), pattern; img.src = params.source; // Create pattern function create() { if (img.complete === true) { // Create pattern pattern = ctx.createPattern(img, params.repeat); return true; } else { return false; } } function onload() { create(); // Run callback function if (params.load) { params.load.call(this[0], pattern); } } // Draw when image is loaded if (params.load) { img.onload = onload; } else { // Check if image is loaded if (create() === false) { img.onload = onload; } } return pattern; }; // Clear canvas $.fn.clearCanvas = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.rotate(ctx, params, params.width, params.height); // Clear entire canvas if (args === undefined) { ctx.clearRect(0, 0, this[e].width, this[e].height); } else { ctx.clearRect(params.x-params.width/2, params.y-params.height/2, params.width, params.height); } } return this; }; // Save canvas $.fn.saveCanvas = function() { var ctx, e; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); ctx.save(); } return this; }; // Restore canvas $.fn.restoreCanvas = function() { var ctx, e; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); ctx.restore(); } return this; }; // Symetrize canvas $.fn.symetrizeCanvas = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); //window.lastctx = ctx; // DEBUG let matrix = ctx.currentTransform || ctx.mozCurrentTransform; if ( params.symetrizeX ) { ctx.setTransform( // flipX -matrix[0], matrix[1], matrix[2], matrix[3], params.width, matrix[5] ); } if ( params.symetrizeY ) { ctx.setTransform( // flipY matrix[0], matrix[1], matrix[2], -matrix[3], matrix[4], params.height ); } params.innerDraw(); ctx.setTransform( matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5] ); } return this; }; /* Tests for symetrize (triangle -1 -1 1 0 0 -1) (quart-de-tour-gauche (triangle -1 -1 1 0 0 -1)) (quarter-turn-right (triangle -1 -1 1 0 0 -1)) (symetrique (quart-de-tour-gauche (triangle -1 -1 1 0 0 -1))) (symetrique (quarter-turn-right (triangle -1 -1 1 0 0 -1))) (quart-de-tour-gauche (symetrique (triangle -1 -1 1 0 0 -1))) ; KO (quarter-turn-right (symetrique (triangle -1 -1 1 0 0 -1))) ; KO (superposition ; KO (triangle -1 -1 1 0 0 -1) (quart-de-tour-gauche (symetrique (triangle -1 -1 1 0 0 -1))) (symetrique (triangle -1 1 0 0 -1 0.75)) ) */ // Scale canvas $.fn.scaleCanvas = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); ctx.save(); ctx.translate(params.x, params.y); ctx.scale(params.width, params.height); ctx.translate(-params.x, -params.y); } return this; }; // Translate canvas $.fn.translateCanvas = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); ctx.save(); ctx.translate(params.x, params.y); } return this; }; // Rotate canvas $.fn.rotateCanvas = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); params.toRad = jC.checkUnits(params); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); ctx.save(); ctx.translate(params.x, params.y); ctx.rotate(params.angle*params.toRad); ctx.translate(-params.x, -params.y); } return this; }; // Draw rectangle $.fn.drawRect = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args), x1, y1, x2, y2, r; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); jC.rotate(ctx, params, params.width, params.height); // Draw rounded rectangle if chosen if (params.cornerRadius > 0) { x1 = params.x - params.width/2; y1 = params.y - params.height/2; x2 = params.x + params.width/2; y2 = params.y + params.height/2; r = params.cornerRadius; // Prevent over-rounded corners if ((x2 - x1) - (2 * r) < 0) { r = (x2 - x1) / 2; } if ((y2 - y1) - (2 * r) < 0) { r = (y2 - y1) / 2; } ctx.beginPath(); ctx.moveTo(x1+r,y1); ctx.lineTo(x2-r,y1); ctx.arc(x2-r, y1+r, r, 3*Math.PI/2, Math.PI*2, false); ctx.lineTo(x2,y2-r); ctx.arc(x2-r, y2-r, r, 0, Math.PI/2, false); ctx.lineTo(x1+r,y2); ctx.arc(x1+r, y2-r, r, Math.PI/2, Math.PI, false); ctx.lineTo(x1,y1+r); ctx.arc(x1+r, y1+r, r, Math.PI, 3*Math.PI/2, false); ctx.fill(); ctx.stroke(); ctx.closePath(); } else { ctx.beginPath(); ctx.rect(params.x-params.width/2, params.y-params.height/2, params.width, params.height); ctx.restore(); jC.closePath(ctx, params); } } return this; }; // Draw arc $.fn.drawArc = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); // Change default end angle to radians if needed if (params.inDegrees === false && params.end === 360) { params.end = Math.PI * 2; } for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); jC.rotate(ctx, params, params.radius, params.radius); // Draw arc ctx.beginPath(); ctx.arc(params.x, params.y, params.radius, (params.start*params.toRad)-(Math.PI/2), (params.end*params.toRad)-(Math.PI/2), params.ccw); // Close path if chosen ctx.restore(); jC.closePath(ctx, params); } return this; }; // Draw ellipse $.fn.drawEllipse = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args), controlW = params.width * (4/3); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); jC.rotate(ctx, params, params.width, params.height); // Create ellipse ctx.beginPath(); ctx.moveTo(params.x, params.y-params.height/2); // Left side ctx.bezierCurveTo(params.x-controlW/2,params.y-params.height/2, params.x-controlW/2,params.y+params.height/2, params.x,params.y+params.height/2); // Right side ctx.bezierCurveTo(params.x+controlW/2,params.y+params.height/2, params.x+controlW/2,params.y-params.height/2, params.x,params.y-params.height/2); ctx.restore(); jC.closePath(ctx, params); } return this; }; // Draw line $.fn.drawLine = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args), l = 2, lx = 0, ly = 0; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); // Draw each point ctx.beginPath(); ctx.moveTo(params.x1, params.y1); while (1) { lx = params['x' + l]; ly = params['y' + l]; if (lx !== undefined && ly !== undefined) { ctx.lineTo(lx, ly); } else { break; } l += 1; } // Close path if chosen jC.closePath(ctx, params); } return this; }; // Draw quadratic curve $.fn.drawQuad = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args), l = 2, lx = 0, ly = 0, lcx = 0, lcy = 0; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); // Draw each point ctx.beginPath(); ctx.moveTo(params.x1, params.y1); while (1) { lx = params['x' + l]; ly = params['y' + l]; lcx = params['cx' + (l-1)]; lcy = params['cy' + (l-1)]; if (lx !== undefined && ly !== undefined && lcx !== undefined && lcy !== undefined) { ctx.quadraticCurveTo(lcx, lcy, lx, ly); } else { break; } l += 1; } // Close path if chosen jC.closePath(ctx, params); } return this; }; // Draw Bezier curve $.fn.drawBezier = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args), l = 2, lc = 1, lx = 0, ly = 0, lcx1 = 0, lcy1 = 0, lcx2 = 0, lcy2 = 0; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); // Draw each point ctx.beginPath(); ctx.moveTo(params.x1, params.y1); while (1) { lx = params['x' + l]; ly = params['y' + l]; lcx1 = params['cx' + lc]; lcy1 = params['cy' + lc]; lcx2 = params['cx' + (lc+1)]; lcy2 = params['cy' + (lc+1)]; if (lx !== undefined && ly !== undefined && lcx1 !== undefined && lcy1 !== undefined && lcx2 !== undefined && lcy2 !== undefined) { ctx.bezierCurveTo(lcx1, lcy1, lcx2, lcy2, lx, ly); } else { break; } l += 1; lc += 2; } // Close path if chosen jC.closePath(ctx, params); } return this; }; // Draw text $.fn.drawText = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args); for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); // Set text-specific properties ctx.textBaseline = params.baseline; ctx.textAlign = params.align; ctx.font = params.font; ctx.strokeText(params.text, params.x, params.y); ctx.fillText(params.text, params.x, params.y); ctx.restore(); } return this; }; // Draw image $.fn.drawImage = function(args) { var ctx, elem, e, params = $.extend({}, jC.prefs, args), // Define image source img = new Image(), scaleFac; img.src = params.source; // Draw image function function draw(ctx) { if (img.complete === true) { scaleFac = img.width / img.height; // If width/height are specified if (params.width && params.height) { img.width = params.width; img.height = params.height; // If width is specified } else if (params.width && !params.height) { img.width = params.width; img.height = img.width / scaleFac; // If height is specified } else if (!params.width && params.height) { img.height = params.height; img.width = img.height * scaleFac; } // Draw image jC.rotate(ctx, params, img.width, img.height); ctx.drawImage(img, params.x-img.width/2, params.y-img.height/2, img.width, img.height); ctx.restore(); return true; } else { return false; } } // On load function function onload() { draw(ctx); // Run callback function if (params.load) { params.load.call(elem); } } // Draw image if already loaded for (e=0; e<this.length; e+=1) { elem = this[e]; ctx = elem.getContext('2d'); jC.setGlobals(ctx, params); // Draw when image is loaded if (params.load) { img.onload = onload; } else { // Check if image is loaded if (draw(ctx) === false) { img.onload = onload; } } } return this; }; // Draw polygon $.fn.drawPolygon = function(args) { var ctx, e, params = $.extend({}, jC.prefs, args), inner = Math.PI / params.sides, theta = (Math.PI/2) + inner, dtheta = (Math.PI*2) / params.sides, x1, y1, x2, y2, i; params.apothem = Math.cos(dtheta/2) * params.radius; params.closed = true; if (params.sides >= 3) { for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); jC.setGlobals(ctx, params); // Calculate points and draw jC.rotate(ctx, params, params.radius, params.radius); ctx.beginPath(); for (i=0; i<params.sides; i+=1) { x1 = Math.round(params.x + (params.radius * Math.cos(theta))); y1 = Math.round(params.y + (params.radius * Math.sin(theta))); x2 = Math.round(params.y + ((params.apothem+params.apothem*params.projection) * Math.cos(theta+inner))); y2 = Math.round(params.y + ((params.apothem+params.apothem*params.projection) * Math.sin(theta+inner))); // Draw path if (i === 0) { ctx.moveTo(x1, y1); } else { ctx.lineTo(x1, y1); } // Project if chosen if (params.projection !== undefined) { ctx.lineTo(x2, y2); } theta += dtheta; } ctx.restore(); jC.closePath(ctx, params); } } return this; }; // Get pixels on the canvas $.fn.setPixels = function(args) { var ctx, elem, e, i, params = $.extend({}, jC.prefs, args), imgData, data, len, px; for (e=0; e<this.length; e+=1) { elem = this[e]; ctx = elem.getContext('2d'); if (!params.x && !params.y && !params.width && !params.height) { params.width = elem.width; params.height = elem.height; params.x = params.width/2; params.y = params.height/2; } jC.rotate(ctx, params, params.width, params.height); imgData = ctx.getImageData(params.x-params.width/2, params.y-params.height/2, params.width, params.height); data = imgData.data; len = data.length; px = []; // Loop through pixels with "each" method if (params.each !== undefined) { for (i=0; i<len; i+=4) { px = params.each.call(elem, data[i], data[i+1], data[i+2], data[i+3]); data[i] = px[0]; data[i+1] = px[1]; data[i+2] = px[2]; data[i+3] = px[3]; } } // Put pixels on canvas ctx.putImageData(imgData, params.x-params.width/2, params.y-params.height/2); ctx.restore(); } return this; }; // Create jCanvas queue jC.layers = []; // Create layer jC.addLayer = function(args) { var params = $.extend({}, jC.prefs, args); jC.layers.push(params); return params; }; // Draw jCanvas layers $.fn.drawLayers = function(clear) { var ctx, items = jC.layers.length, params, e, i; for (e=0; e<this.length; e+=1) { ctx = this[e].getContext('2d'); // Optionally clear canvas if (clear === true) { ctx.clearRect(0, 0, this[e].width, this[e].height); } // Draw items on queue for (i=0; i<items; i+=1) { params = jC.layers[i]; if (params.fn) { $.fn[params.fn].call(this.eq(e), params); } } } return this; }; // Normalize layerX/layerY for jQuery mouse events var fix = $.event.fix; $.event.fix = function(event) { event = fix.call($.event, event); // Use offsetX/offsetY for Opera if (event.layerX === undefined && event.layerY === undefined) { event.layerX = event.offsetX; event.layerY = event.offsetY; } return event; }; // Enable backward compatibility jC.retrofit = function() { jC.retro = true; $.fn.drawQuadCurve = $.fn.drawQuad; $.fn.drawBezierCurve = $.fn.drawBezier; $.fn.canvasDefaults = jC; $.fn.canvas = jC; jC.queue = jC.layers; jC.create = jC.addLayer; $.fn.drawQueue = $.fn.drawLayers; return $; }; return ($.jCanvas = jC); } //}(jQuery, document, Math, Image));