/***************************************************************** ** Author: Asvin Goel, goel@telematique.eu ** ** A plugin for reveal.js adding a chalkboard. ** ** Version: 0.6 ** ** License: MIT license (see LICENSE.md) ** ** Credits: ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard ******************************************************************/ var RevealChalkboard = window.RevealChalkboard || (function(){ var path = scriptPath(); function scriptPath() { // obtain plugin path from the script element var src; if (document.currentScript) { src = document.currentScript.src; } else { var sel = document.querySelector('script[src$="/chalkboard.js"]') if (sel) { src = sel.src; } } var path = typeof src === undefined ? src : src.slice(0, src.lastIndexOf("/") + 1); //console.log("Path: " + path); return path; } /***************************************************************** ** Configuration ******************************************************************/ var config = Reveal.getConfig().chalkboard || {}; var background, pen, draw, color; var theme = config.theme || "chalkboard"; switch ( theme ) { case "whiteboard": background = [ 'rgba(127,127,127,.1)' , path + 'img/whiteboard.png' ]; pen = [ 'url(' + path + 'img/boardmarker.png), auto', 'url(' + path + 'img/boardmarker.png), auto' ]; draw = [ drawWithPen , drawWithPen ]; color = [ 'rgba(0,0,255,1)', 'rgba(0,0,255,1)' ]; break; default: background = [ 'rgba(127,127,127,.1)' , path + 'img/blackboard.png' ]; pen = [ 'url(' + path + 'img/boardmarker.png), auto', 'url(' + path + 'img/chalk.png), auto' ]; draw = [ drawWithPen , drawWithChalk ]; color = [ 'rgba(0,0,255,1)', 'rgba(255,255,255,0.5)' ]; } if ( config.background ) background = config.background; if ( config.pen ) pen = config.pen; if ( config.draw ) draw = config.draw; if ( config.color ) color = config.color; var toggleChalkboardButton = config.toggleChalkboardButton == undefined ? true : config.toggleChalkboardButton; var toggleNotesButton = config.toggleNotesButton == undefined ? true : config.toggleNotesButton; var transition = config.transition || 800; var readOnly = config.readOnly; var legacyFileSupport = config.legacyFileSupport; if ( legacyFileSupport ) { console.warn("Legacy file support is deprecated and may be removed in future versions!") } /***************************************************************** ** Setup ******************************************************************/ function whenReady( callback ) { // wait for drawings to be loaded and markdown to be parsed if ( loaded == null || document.querySelector('section[data-markdown]:not([data-markdown-parsed])') ) { setTimeout( whenReady, 100, callback ) } else { callback(); } } var eraserDiameter = 20; if ( toggleChalkboardButton ) { //console.log("toggleChalkboardButton") var button = document.createElement( 'div' ); button.className = "chalkboard-button"; button.id = "toggle-chalkboard"; button.style.visibility = "visible"; button.style.position = "absolute"; button.style.zIndex = 30; button.style.fontSize = "24px"; button.style.left = toggleChalkboardButton.left || "30px"; button.style.bottom = toggleChalkboardButton.bottom || "30px"; button.style.top = toggleChalkboardButton.top || "auto"; button.style.right = toggleChalkboardButton.right || "auto"; button.innerHTML = '' document.querySelector(".reveal").appendChild( button ); } if ( toggleNotesButton ) { //console.log("toggleNotesButton") var button = document.createElement( 'div' ); button.className = "chalkboard-button"; button.id = "toggle-notes"; button.style.position = "absolute"; button.style.zIndex = 30; button.style.fontSize = "24px"; button.style.left = toggleNotesButton.left || "70px"; button.style.bottom = toggleNotesButton.bottom || "30px"; button.style.top = toggleNotesButton.top || "auto"; button.style.right = toggleNotesButton.right || "auto"; button.innerHTML = '' document.querySelector(".reveal").appendChild( button ); } //alert("Buttons"); var drawingCanvas = [ {id: "notescanvas" }, {id: "chalkboard" } ]; setupDrawingCanvas(0); setupDrawingCanvas(1); var mode = 0; // 0: notes canvas, 1: chalkboard var mouseX = 0; var mouseY = 0; var xLast = null; var yLast = null; var slideStart = Date.now(); var slideIndices = { h:0, v:0 }; var event = null; var timeouts = [ [], [] ]; var touchTimeout = null; var slidechangeTimeout = null; var playback = false; function setupDrawingCanvas( id ) { var container = document.createElement( 'div' ); container.id = drawingCanvas[id].id; container.classList.add( 'overlay' ); container.setAttribute( 'data-prevent-swipe', '' ); container.oncontextmenu = function() { return false; } container.style.cursor = pen[ id ]; drawingCanvas[id].width = window.innerWidth; drawingCanvas[id].height = window.innerHeight; drawingCanvas[id].scale = 1; drawingCanvas[id].xOffset = 0; drawingCanvas[id].yOffset = 0; if ( id == "0" ) { container.style.background = 'rgba(0,0,0,0)'; container.style.zIndex = "24"; container.classList.add( 'visible' ) container.style.pointerEvents = "none"; var slides = document.querySelector(".slides"); var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height; if ( drawingCanvas[id].width > drawingCanvas[id].height*aspectRatio ) { drawingCanvas[id].xOffset = (drawingCanvas[id].width - drawingCanvas[id].height*aspectRatio) / 2; } else if ( drawingCanvas[id].height > drawingCanvas[id].width/aspectRatio ) { drawingCanvas[id].yOffset = ( drawingCanvas[id].height - drawingCanvas[id].width/aspectRatio ) / 2; } } else { container.style.background = 'url("' + background[id] + '") repeat'; container.style.zIndex = "26"; } var sponge = document.createElement( 'img' ); sponge.src = path + 'img/sponge.png'; sponge.id = "sponge"; sponge.style.visibility = "hidden"; sponge.style.position = "absolute"; container.appendChild( sponge ); drawingCanvas[id].sponge = sponge; var canvas = document.createElement( 'canvas' ); canvas.width = drawingCanvas[id].width; canvas.height = drawingCanvas[id].height; canvas.setAttribute( 'data-chalkboard', id ); canvas.style.cursor = pen[ id ]; container.appendChild( canvas ); drawingCanvas[id].canvas = canvas; drawingCanvas[id].context = canvas.getContext("2d"); document.querySelector( '.reveal' ).appendChild( container ); drawingCanvas[id].container = container; } /***************************************************************** ** Storage ******************************************************************/ var storage = [ { width: drawingCanvas[0].width - 2 * drawingCanvas[0].xOffset, height: drawingCanvas[0].height - 2 * drawingCanvas[0].yOffset, data: []}, { width: drawingCanvas[1].width, height: drawingCanvas[1].height, data: []} ]; //console.log( JSON.stringify(storage)); var loaded = null; if ( config.src != null ) { loadData( config.src ); } /** * Load data. */ function loadData( filename ) { var xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.readyState === 4 && xhr.status != 404 ) { storage = JSON.parse(xhr.responseText); for (var id = 0; id < storage.length; id++) { if ( drawingCanvas[id].width != storage[id].width || drawingCanvas[id].height != storage[id].height ) { drawingCanvas[id].scale = Math.min( drawingCanvas[id].width/storage[id].width, drawingCanvas[id].height/storage[id].height); drawingCanvas[id].xOffset = (drawingCanvas[id].width - storage[id].width * drawingCanvas[id].scale)/2; drawingCanvas[id].yOffset = (drawingCanvas[id].height - storage[id].height * drawingCanvas[id].scale)/2; } if ( config.readOnly ) { drawingCanvas[id].container.style.cursor = 'default'; drawingCanvas[id].canvas.style.cursor = 'default'; } } loaded = true; //console.log("Drawings loaded"); } else { config.readOnly = undefined; readOnly = undefined; console.warn( 'Failed to get file ' + filename +". ReadyState: " + xhr.readyState + ", Status: " + xhr.status); loaded = false; } }; xhr.open( 'GET', filename, true ); try { xhr.send(); } catch ( error ) { config.readOnly = undefined; readOnly = undefined; console.warn( 'Failed to get file ' + filename + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + error ); loaded = false; } } /** * Download data. */ function downloadData() { var a = document.createElement('a'); document.body.appendChild(a); try { // cleanup slide data without events for (var id = 0; id < 2; id++) { for (var i = storage[id].data.length-1; i >= 0; i--) { if (storage[id].data[i].events.length == 0) { storage[id].data.splice(i, 1); } } } a.download = "chalkboard.json"; var blob = new Blob( [ JSON.stringify( storage ) ], { type: "application/json"} ); a.href = window.URL.createObjectURL( blob ); } catch( error ) { a.innerHTML += " (" + error + ")"; } a.click(); document.body.removeChild(a); } /** * Returns data object for the slide with the given indices. */ function getSlideData( indices, id ) { if ( id == undefined ) id = mode; if (!indices) indices = slideIndices; var data; for (var i = 0; i < storage[id].data.length; i++) { if (storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v === indices.v && storage[id].data[i].slide.f === indices.f ) { data = storage[id].data[i]; return data; } if ( !legacyFileSupport && ( storage[id].data[i].slide.h > indices.h || ( storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v > indices.v ) || ( storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v === indices.v && storage[id].data[i].slide.f > indices.f ) ) ) { storage[id].data.splice( i, 0, { slide: indices, events: [], duration: 0 } ); data = storage[id].data[i]; return data; } } storage[id].data.push( { slide: indices, events: [], duration: 0 } ); data = storage[id].data[storage[id].data.length-1]; return data; } /** * Returns maximum duration of slide playback for both modes */ function getSlideDuration( indices ) { if (!indices) indices = slideIndices; var duration = 0; for (var id = 0; id < 2; id++) { for (var i = 0; i < storage[id].data.length; i++) { if (storage[id].data[i].slide.h === indices.h && storage[id].data[i].slide.v === indices.v && storage[id].data[i].slide.f === indices.f ) { duration = Math.max( duration, storage[id].data[i].duration ); break; } } } //console.log( duration ); return duration; } /***************************************************************** ** Print ******************************************************************/ var printMode = ( /print-pdf/gi ).test( window.location.search ); //console.log("createPrintout" + printMode) function createPrintout( ) { //console.log( 'Create printout for ' + storage[1].data.length + " slides"); drawingCanvas[0].container.classList.remove( 'visible' ); // do not print notes canvas var patImg = new Image(); patImg.onload = function () { var nextSlide = []; var width = Reveal.getConfig().width; var height = Reveal.getConfig().height; var scale = 1; var xOffset = 0; var yOffset = 0; if ( width != storage[1].width || height != storage[1].height ) { scale = Math.min( width/storage[1].width, height/storage[1].height); xOffset = (width - storage[1].width * scale)/2; yOffset = (height - storage[1].height * scale)/2; } for (var i = 0; i < storage[1].data.length; i++) { var slide = Reveal.getSlide( storage[1].data[i].slide.h, storage[1].data[i].slide.v ); nextSlide.push( slide.nextSibling ); } for (var i = 0; i < storage[1].data.length; i++) { console.log( 'Create printout for slide ' + storage[1].data[i].slide.h + "." + storage[1].data[i].slide.v ); var parent = Reveal.getSlide( storage[1].data[i].slide.h, storage[1].data[i].slide.v ).parentElement; var slideData = getSlideData( storage[1].data[i].slide, 1 ); var imgCanvas = document.createElement('canvas'); imgCanvas.width = width; imgCanvas.height = height; var imgCtx = imgCanvas.getContext("2d"); imgCtx.fillStyle = imgCtx.createPattern( patImg ,'repeat'); imgCtx.rect(0,0,imgCanvas.width,imgCanvas.height); imgCtx.fill(); for (var j = 0; j < slideData.events.length; j++) { switch ( slideData.events[j].type ) { case "draw": for (var k = 1; k < slideData.events[j].curve.length; k++) { draw[1]( imgCtx, xOffset + slideData.events[j].curve[k-1].x*scale, yOffset + slideData.events[j].curve[k-1].y*scale, xOffset + slideData.events[j].curve[k].x*scale, yOffset + slideData.events[j].curve[k].y*scale ); } break; case "erase": for (var k = 0; k < slideData.events[j].curve.length; k++) { eraseWithSponge( imgCtx, xOffset + slideData.events[j].curve[k].x*scale, yOffset + slideData.events[j].curve[k].y*scale ); } break; case "clear": addPrintout( parent, nextSlide[i], imgCanvas, patImg ); imgCtx.clearRect(0,0,imgCanvas.width,imgCanvas.height); imgCtx.fill(); break; default: break; } } if ( slideData.events.length ) { addPrintout( parent, nextSlide[i], imgCanvas, patImg ); } } Reveal.sync(); }; patImg.src = background[1]; } function addPrintout( parent, nextSlide, imgCanvas, patImg ) { var slideCanvas = document.createElement('canvas'); slideCanvas.width = Reveal.getConfig().width; slideCanvas.height = Reveal.getConfig().height; var ctx = slideCanvas.getContext("2d"); ctx.fillStyle = ctx.createPattern( patImg ,'repeat'); ctx.rect(0,0,slideCanvas.width,slideCanvas.height); ctx.fill(); ctx.drawImage(imgCanvas, 0, 0); var newSlide = document.createElement( 'section' ); newSlide.classList.add( 'present' ); newSlide.innerHTML = '

Drawing

'; newSlide.setAttribute("data-background-size", '100% 100%' ); newSlide.setAttribute("data-background-repeat", 'norepeat' ); newSlide.setAttribute("data-background", 'url("' + slideCanvas.toDataURL("image/png") +'")' ); if ( nextSlide != null ) { parent.insertBefore( newSlide, nextSlide ); } else { parent.append( newSlide ); } } /***************************************************************** ** Drawings ******************************************************************/ function drawWithPen(context,fromX,fromY,toX,toY){ context.lineWidth = 3; context.lineCap = 'round'; context.strokeStyle = color[0]; context.beginPath(); context.moveTo(fromX, fromY); context.lineTo(toX, toY); context.stroke(); } function drawWithChalk(context,fromX,fromY,toX,toY) { var brushDiameter = 7; context.lineWidth = brushDiameter; context.lineCap = 'round'; context.fillStyle = color[1]; // 'rgba(255,255,255,0.5)'; context.strokeStyle = color[1]; var opacity = Math.min(0.8, Math.max(0,color[1].replace(/^.*,(.+)\)/,'$1') - 0.1)) + Math.random()*0.2; context.strokeStyle = context.strokeStyle.replace(/[\d\.]+\)$/g, opacity + ')'); context.beginPath(); context.moveTo(fromX, fromY); context.lineTo(toX, toY); context.stroke(); // Chalk Effect var length = Math.round(Math.sqrt(Math.pow(toX-fromX,2)+Math.pow(toY-fromY,2))/(5/brushDiameter)); var xUnit = (toX-fromX)/length; var yUnit = (toY-fromY)/length; for(var i=0; i