1/***************************************************************** 2 ** Author: Asvin Goel, [email protected] 3 ** 4 ** A plugin for reveal.js adding a chalkboard. 5 ** 6 ** Version: 2.3.3 7 ** 8 ** License: MIT license (see LICENSE.md) 9 ** 10 ** Credits: 11 ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard 12 ** Multi color support initially added by Kurt Rinnert https://github.com/rinnert 13 ** Compatibility with reveal.js v4 by Hakim El Hattab https://github.com/hakimel 14 ******************************************************************/ 15 16"use strict"; 17 18window.RevealChalkboard = window.RevealChalkboard || { 19 id: 'RevealChalkboard', 20 init: function ( deck ) { 21 initChalkboard.call(this, deck ); 22 }, 23 configure: function ( config ) { 24 configure( config ); 25 }, 26 toggleNotesCanvas: function () { 27 toggleNotesCanvas(); 28 }, 29 toggleChalkboard: function () { 30 toggleChalkboard(); 31 }, 32 colorIndex: function () { 33 colorIndex(); 34 }, 35 colorNext: function () { 36 colorNext(); 37 }, 38 colorPrev: function () { 39 colorPrev(); 40 }, 41 clear: function () { 42 clear(); 43 }, 44 reset: function () { 45 reset(); 46 }, 47 resetAll: function () { 48 resetAll(); 49 }, 50 updateStorage: function () { 51 updateStorage(); 52 }, 53 getData: function () { 54 return getData(); 55 }, 56 download: function () { 57 download(); 58 }, 59}; 60 61function scriptPath() { 62 // obtain plugin path from the script element 63 var src; 64 if ( document.currentScript ) { 65 src = document.currentScript.src; 66 } else { 67 var sel = document.querySelector( 'script[src$="/chalkboard/plugin.js"]' ) 68 if ( sel ) { 69 src = sel.src; 70 } 71 } 72 var path = ( src === undefined ) ? "" : src.slice( 0, src.lastIndexOf( "/" ) + 1 ); 73//console.log("Path: " + path); 74 return path; 75} 76var path = scriptPath(); 77 78const initChalkboard = function ( Reveal ) { 79//console.warn(path); 80 /* Feature detection for passive event handling*/ 81 var passiveSupported = false; 82 83 try { 84 window.addEventListener( 'test', null, Object.defineProperty( {}, 'passive', { 85 get: function () { 86 passiveSupported = true; 87 } 88 } ) ); 89 } catch ( err ) {} 90 91 92/***************************************************************** 93 ** Configuration 94 ******************************************************************/ 95 var background, pens, draw, color; 96 var grid = false; 97 var boardmarkerWidth = 3; 98 var chalkWidth = 7; 99 var chalkEffect = 1.0; 100 var rememberColor = [ true, false ]; 101 var eraser = { 102 src: path + 'img/sponge.png', 103 radius: 20 104 }; 105 var boardmarkers = [ {
106 color: 'rgba(100,100,100,1)', 107 cursor: 'url(' + path + 'img/boardmarker-black.png), auto' 108 }, 109 { 110 color: 'rgba(30,144,255, 1)', 111 cursor: 'url(' + path + 'img/boardmarker-blue.png), auto' 112 }, 113 { 114 color: 'rgba(220,20,60,1)', 115 cursor: 'url(' + path + 'img/boardmarker-red.png), auto' 116 }, 117 { 118 color: 'rgba(50,205,50,1)', 119 cursor: 'url(' + path + 'img/boardmarker-green.png), auto' 120 }, 121 { 122 color: 'rgba(255,140,0,1)', 123 cursor: 'url(' + path + 'img/boardmarker-orange.png), auto' 124 }, 125 { 126 color: 'rgba(150,0,20150,1)', 127 cursor: 'url(' + path + 'img/boardmarker-purple.png), auto' 128 }, 129 { 130 color: 'rgba(255,220,0,1)', 131 cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto' 132 } 133 ]; 134 var chalks = [ { 135 color: 'rgba(255,255,255,0.5)', 136 cursor: 'url(' + path + 'img/chalk-white.png), auto' 137 }, 138 { 139 color: 'rgba(96, 154, 244, 0.5)', 140 cursor: 'url(' + path + 'img/chalk-blue.png), auto' 141 }, 142 { 143 color: 'rgba(237, 20, 28, 0.5)', 144 cursor: 'url(' + path + 'img/chalk-red.png), auto' 145 }, 146 { 147 color: 'rgba(20, 237, 28, 0.5)', 148 cursor: 'url(' + path + 'img/chalk-green.png), auto' 149 }, 150 { 151 color: 'rgba(220, 133, 41, 0.5)', 152 cursor: 'url(' + path + 'img/chalk-orange.png), auto' 153 }, 154 { 155 color: 'rgba(220,0,220,0.5)', 156 cursor: 'url(' + path + 'img/chalk-purple.png), auto' 157 }, 158 { 159 color: 'rgba(255,220,0,0.5)', 160 cursor: 'url(' + path + 'img/chalk-yellow.png), auto' 161 } 162 ]; 163 164 var sponge = { 165 cursor: 'url(' + path + 'img/sponge.png), auto' 166 } 167 168 169 var keyBindings = { 170 toggleNotesCanvas: { 171 keyCode: 67, 172 key: 'C', 173 description: 'Toggle notes canvas' 174 }, 175 toggleChalkboard: { 176 keyCode: 66, 177 key: 'B', 178 description: 'Toggle chalkboard' 179 }, 180 clear: { 181 keyCode: 46, 182 key: 'DEL', 183 description: 'Clear drawings on slide' 184 }, 185/* 186 reset: { 187 keyCode: 173, 188 key: '-', 189 description: 'Reset drawings on slide' 190 }, 191*/ 192 resetAll: { 193 keyCode: 8, 194 key: 'BACKSPACE', 195 description: 'Reset all drawings' 196 }, 197 colorNext: { 198 keyCode: 88, 199 key: 'X', 200 description: 'Next color' 201 }, 202 colorPrev: { 203 keyCode: 89, 204 key: 'Y', 205 description: 'Previous color' 206 }, 207 download: { 208 keyCode: 68, 209 key: 'D', 210 description: 'Download drawings' 211 } 212 }; 213 214 215 var theme = 'chalkboard'; 216 var color = [ 0, 0 ]; 217 var toggleChalkboardButton = false; 218 var toggleNotesButton = false; 219 var colorButtons = true; 220 var boardHandle = true; 221 var transition = 800; 222 223 var readOnly = false; 224 var messageType = 'broadcast'; 225 226 var config = configure( Reveal.getConfig().chalkboard || {} ); 227 if ( config.keyBindings ) { 228 for ( var key in config.keyBindings ) { 229 keyBindings[ key ] = config.keyBindings[ key ]; 230 }; 231 } 232 233 function configure( config ) { 234 235 if ( config.boardmarkerWidth || config.penWidth ) boardmarkerWidth = config.boardmarkerWidth || config.penWidth; 236 if ( config.chalkWidth ) chalkWidth = config.chalkWidth; 237 if ( config.chalkEffect ) chalkEffect = config.chalkEffect; 238 if ( config.rememberColor ) rememberColor = config.rememberColor; 239 if ( config.eraser ) eraser = config.eraser; 240 if ( config.boardmarkers ) boardmarkers = config.boardmarkers; 241 if ( config.chalks ) chalks = config.chalks; 242 243 if ( config.theme ) theme = config.theme; 244 switch ( theme ) { 245 case 'whiteboard': 246 background = [ 'rgba(127,127,127,.1)', path + 'img/whiteboard.png' ]; 247 draw = [ drawWithBoardmarker, drawWithBoardmarker ]; 248 pens = [ boardmarkers, boardmarkers ]; 249 grid = { 250 color: 'rgb(127,127,255,0.1)', 251 distance: 40, 252 width: 2 253 }; 254 break; 255 case 'chalkboard': 256 default: 257 background = [ 'rgba(127,127,127,.1)', path + 'img/blackboard.png' ]; 258 draw = [ drawWithBoardmarker, drawWithChalk ]; 259 pens = [ boardmarkers, chalks ]; 260 grid = { 261 color: 'rgb(50,50,10,0.5)', 262 distance: 80, 263 width: 2 264 }; 265 } 266 267 if ( config.background ) background = config.background; 268 if ( config.grid != undefined ) grid = config.grid; 269 270 if ( config.toggleChalkboardButton != undefined ) toggleChalkboardButton = config.toggleChalkboardButton; 271 if ( config.toggleNotesButton != undefined ) toggleNotesButton = config.toggleNotesButton; 272 if ( config.colorButtons != undefined ) colorButtons = config.colorButtons; 273 if ( config.boardHandle != undefined ) boardHandle = config.boardHandle; 274 if ( config.transition ) transition = config.transition; 275 276 if ( config.readOnly != undefined ) readOnly = config.readOnly; 277 if ( config.messageType ) messageType = config.messageType; 278 279 if ( drawingCanvas && ( config.theme || config.background || config.grid ) ) { 280 var canvas = document.getElementById( drawingCanvas[ 1 ].id ); 281 canvas.style.background = 'url("' + background[ 1 ] + '") repeat'; 282 clearCanvas( 1 );
283 drawGrid(); 284 } 285 286 return config; 287 } 288/***************************************************************** 289 ** Setup 290 ******************************************************************/ 291 292 function whenReady( callback ) { 293 // wait for markdown to be parsed and code to be highlighted 294 if ( !document.querySelector( 'section[data-markdown]:not([data-markdown-parsed])' ) 295 && !document.querySelector( '[data-load]:not([data-loaded])') 296 && !document.querySelector( 'code[data-line-numbers*="|"]') 297 ) { 298 callback(); 299 } else { 300 console.log( "Wait for external sources to be loaded and code to be highlighted" ); 301 setTimeout( whenReady, 500, callback ) 302 } 303 } 304 305 function whenLoaded( callback ) { 306 // wait for drawings to be loaded and markdown to be parsed 307 if ( loaded !== null ) { 308 callback(); 309 } else { 310 console.log( "Wait for drawings to be loaded" ); 311 setTimeout( whenLoaded, 500, callback ) 312 } 313 } 314 315 var drawingCanvas = [ { 316 id: 'notescanvas' 317 }, { 318 id: 'chalkboard' 319 } ]; 320 setupDrawingCanvas( 0 ); 321 setupDrawingCanvas( 1 ); 322 323 var mode = 0; // 0: notes canvas, 1: chalkboard 324 var board = 0; // board index (only for chalkboard) 325 326 var mouseX = 0; 327 var mouseY = 0; 328 var lastX = null; 329 var lastY = null; 330 331 var drawing = false; 332 var erasing = false; 333 334 var slideStart = Date.now(); 335 var slideIndices = { 336 h: 0, 337 v: 0 338 }; 339 340 var timeouts = [ 341 [], 342 [] 343 ]; 344 var slidechangeTimeout = null; 345 var updateStorageTimeout = null; 346 var playback = false; 347 348 function changeCursor( element, tool ) { 349 element.style.cursor = tool.cursor; 350 var palette = document.querySelector('.palette[data-mode="' + mode + '"]'); 351 if ( palette ) { 352 palette.style.cursor = tool.cursor; 353 } 354 } 355 356 function createPalette( colors, length ) { 357 if ( length === true || length > colors.length ) { 358 length = colors.length; 359 } 360 var palette = document.createElement( 'div' ); 361 palette.classList.add( 'palette' ); 362 var list = document.createElement( 'ul' ); 363 // color pickers 364 for ( var i = 0; i < length; i++ ) { 365 var colorButton = document.createElement( 'li' ); 366 colorButton.setAttribute( 'data-color', i ); 367 colorButton.innerHTML = '<i class="fa fa-square"></i>'; 368 colorButton.style.color = colors[ i ].color; 369 colorButton.addEventListener( 'click', function ( e ) { 370 var element = e.target; 371 while ( !element.hasAttribute( 'data-color' ) ) { 372 element = element.parentElement; 373 } 374 colorIndex( parseInt( element.getAttribute( 'data-color' ) ) ); 375 } ); 376 colorButton.addEventListener( 'touchstart', function ( e ) { 377 var element = e.target; 378 while ( !element.hasAttribute( 'data-color' ) ) { 379 element = element.parentElement; 380 } 381 colorIndex( parseInt( element.getAttribute( 'data-color' ) ) ); 382 } ); 383 list.appendChild( colorButton ); 384 } 385 // eraser 386 var eraserButton = document.createElement( 'li' ); 387 eraserButton.setAttribute( 'data-eraser', 'true' ); 388 var spongeImg = document.createElement( 'img' ); 389 spongeImg.src = eraser.src; 390 spongeImg.height = "24"; 391 spongeImg.width = "24"; 392 spongeImg.style.marginTop = '10px'; 393 spongeImg.style.marginRight = '0'; 394 spongeImg.style.marginBottom = '0'; 395 spongeImg.style.marginLeft = '0'; 396 eraserButton.appendChild(spongeImg); 397 eraserButton.addEventListener( 'click', function ( e ) { 398 colorIndex( -1 ); 399 } ); 400 eraserButton.addEventListener( 'touchstart', function ( e ) { 401 colorIndex( -1 ); 402 } ); 403 list.appendChild( eraserButton ); 404 405 palette.appendChild( list ); 406 return palette; 407 }; 408 409 function switchBoard( boardIdx ) { 410 selectBoard( boardIdx, true ); 411 // broadcast 412 var message = new CustomEvent( messageType ); 413 message.content = { 414 sender: 'chalkboard-plugin', 415 type: 'selectboard', 416 timestamp: Date.now() - slideStart, 417 mode, 418 board 419 }; 420 document.dispatchEvent( message ); 421 } 422 423 function setupDrawingCanvas( id ) { 424 var container = document.createElement( 'div' ); 425 container.id = drawingCanvas[ id ].id; 426 container.classList.add( 'overlay' ); 427 container.setAttribute( 'data-prevent-swipe', 'true' ); 428 container.oncontextmenu = function () { 429 return false;
430 } 431 432 changeCursor( container, pens[ id ][ color[ id ] ] ); 433 434 drawingCanvas[ id ].width = window.innerWidth; 435 drawingCanvas[ id ].height = window.innerHeight; 436 drawingCanvas[ id ].scale = 1; 437 drawingCanvas[ id ].xOffset = 0; 438 drawingCanvas[ id ].yOffset = 0; 439 440 if ( id == "0" ) { 441 container.style.background = 'rgba(0,0,0,0)'; 442 container.style.zIndex = 24; 443 container.style.opacity = 1; 444 container.style.visibility = 'visible'; 445 container.style.pointerEvents = 'none'; 446 container.style['backdrop-filter'] = 'none'; 447 container.style['-webkit-backdrop-filter'] = 'none'; 448 449 var slides = document.querySelector( '.slides' ); 450 var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height; 451 if ( drawingCanvas[ id ].width > drawingCanvas[ id ].height * aspectRatio ) { 452 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - drawingCanvas[ id ].height * aspectRatio ) / 2; 453 } else if ( drawingCanvas[ id ].height > drawingCanvas[ id ].width / aspectRatio ) { 454 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - drawingCanvas[ id ].width / aspectRatio ) / 2; 455 } 456 457 if ( colorButtons ) { 458 var palette = createPalette( boardmarkers, colorButtons ); 459 palette.dataset.mode = id; 460 palette.style.visibility = 'hidden'; // only show palette in drawing mode 461 container.appendChild( palette ); 462 } 463 } else { 464 container.style.background = 'url("' + background[ id ] + '") repeat'; 465 container.style.zIndex = 26; 466 container.style.opacity = 0; 467 container.style.visibility = 'hidden'; 468 469 if ( colorButtons ) { 470 var palette = createPalette( chalks, colorButtons ); 471 palette.dataset.mode = id; 472 container.appendChild( palette ); 473 } 474 if ( boardHandle ) { 475 var handle = document.createElement( 'div' ); 476 handle.classList.add( 'boardhandle' ); 477 handle.innerHTML = '<ul><li><a id="previousboard" href="#" title="Previous board"><i class="fas fa-chevron-up"></i></a></li><li><a id="nextboard" href="#" title="Next board"><i class="fas fa-chevron-down"></i></a></li></ul>'; 478 handle.querySelector( '#previousboard' ).addEventListener( 'click', function ( e ) { 479 e.preventDefault(); 480 switchBoard( board - 1 ); 481 } ); 482 handle.querySelector( '#nextboard' ).addEventListener( 'click', function ( e ) { 483 e.preventDefault(); 484 switchBoard( board + 1 ); 485 } ); 486 handle.querySelector( '#previousboard' ).addEventListener( 'touchstart', function ( e ) { 487 e.preventDefault(); 488 switchBoard( board - 1 ); 489 } ); 490 handle.querySelector( '#nextboard' ).addEventListener( 'touchstart', function ( e ) { 491 e.preventDefault(); 492 switchBoard( board + 1 ); 493 } ); 494 495 container.appendChild( handle ); 496 } 497 } 498 499 var canvas = document.createElement( 'canvas' ); 500 canvas.width = drawingCanvas[ id ].width; 501 canvas.height = drawingCanvas[ id ].height; 502 canvas.setAttribute( 'data-chalkboard', id ); 503 changeCursor( canvas, pens[ id ][ color[ id ] ] ); 504 container.appendChild( canvas ); 505 drawingCanvas[ id ].canvas = canvas; 506 507 drawingCanvas[ id ].context = canvas.getContext( '2d' ); 508 509 setupCanvasEvents( container ); 510 511 document.querySelector( '.reveal' ).appendChild( container ); 512 drawingCanvas[ id ].container = container; 513 } 514 515 516/***************************************************************** 517 ** Storage 518 ******************************************************************/ 519 520 var storage = [ { 521 width: Reveal.getConfig().width, 522 height: Reveal.getConfig().height, 523 data: [] 524 }, 525 { 526 width: Reveal.getConfig().width, 527 height: Reveal.getConfig().height, 528 data: [] 529 } 530 ]; 531 532 var loaded = null; 533 534 if ( config.storage ) { 535 // Get chalkboard drawings from session storage 536 loaded = initStorage( sessionStorage.getItem( config.storage ) ); 537 } 538 539 if ( !loaded && config.src != null ) { 540 // Get chalkboard drawings from the given file 541 loadData( config.src ); 542 } 543 544 /** 545 * Initialize storage. 546 */ 547 function initStorage( json ) { 548 var success = false; 549 try { 550 var data = JSON.parse( json ); 551 for ( var id = 0; id < data.length; id++ ) { 552 if ( drawingCanvas[ id ].width != data[ id ].width || drawingCanvas[ id ].height != data[ id ].height ) { 553 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / data[ id ].width, drawingCanvas[ id ].height / data[ id ].height ); 554 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - data[ id ].width * drawingCanvas[ id ].scale ) / 2; 555 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - data[ id ].height * drawingCanvas[ id ].scale ) / 2; 556 } 557 if ( config.readOnly ) { 558 drawingCanvas[ id ].container.style.cursor = 'default'; 559 drawingCanvas[ id ].canvas.style.cursor = 'default'; 560 } 561 } 562 success = true; 563 storage = data; 564 } catch ( err ) { 565 console.warn( "Cannot initialise storage!" ); 566 } 567 return success; 568 } 569 570 571 /** 572 * Load data. 573 */ 574 function loadData( filename ) { 575 var xhr = new XMLHttpRequest(); 576 xhr.onload = function () { 577 if ( xhr.readyState === 4 && xhr.status != 404 ) { 578 loaded = initStorage( xhr.responseText ); 579 updateStorage(); 580 console.log( "Drawings loaded from file" ); 581 } else { 582 config.readOnly = undefined; 583 readOnly = undefined; 584 console.warn( 'Failed to get file ' + filename + '. ReadyState: ' + xhr.readyState + ', Status: ' + xhr.status ); 585 loaded = false;
586 } 587 }; 588 589 xhr.open( 'GET', filename, true ); 590 try { 591 xhr.send(); 592 } catch ( error ) { 593 config.readOnly = undefined; 594 readOnly = undefined; 595 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 ); 596 loaded = false; 597 } 598 } 599 600 601 function storageChanged( now ) { 602 if ( !now ) { 603 // create or update timer 604 if ( updateStorageTimeout ) { 605 clearTimeout( updateStorageTimeout ); 606 } 607 updateStorageTimeout = setTimeout( storageChanged, 1000, true); 608 } 609 else { 610// console.log("Update storage", updateStorageTimeout, Date.now()); 611 updateStorage(); 612 updateStorageTimeout = null; 613 } 614 } 615 616 function updateStorage() { 617 var json = JSON.stringify( storage ) 618 if ( config.storage ) { 619 sessionStorage.setItem( config.storage, json ) 620 } 621 return json; 622 } 623 624 function recordEvent( event ) { 625//console.log(event); 626 event.time = Date.now() - slideStart; 627 if ( mode == 1 ) event.board = board; 628 var slideData = getSlideData(); 629 var i = slideData.events.length; 630 while ( i > 0 && event.time < slideData.events[ i - 1 ].time ) { 631 i--; 632 } 633 slideData.events.splice( i, 0, event ); 634 slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1; 635 636 storageChanged(); 637 } 638 639 /** 640 * Get data as json string. 641 */ 642 function getData() { 643 // cleanup slide data without events 644 for ( var id = 0; id < 2; id++ ) { 645 for ( var i = storage[ id ].data.length - 1; i >= 0; i-- ) { 646 if ( storage[ id ].data[ i ].events.length == 0 ) { 647 storage[ id ].data.splice( i, 1 ); 648 } 649 } 650 } 651 652 return updateStorage(); 653 } 654 655 /** 656 * Download data. 657 */ 658 function downloadData() { 659 var a = document.createElement( 'a' ); 660 document.body.appendChild( a ); 661 try { 662 a.download = 'chalkboard.json'; 663 var blob = new Blob( [ getData() ], { 664 type: 'application/json' 665 } ); 666 a.href = window.URL.createObjectURL( blob ); 667 } catch ( error ) { 668 // https://stackoverflow.com/a/6234804 669 // escape data for proper handling of quotes and line breaks 670 // in case malicious user gets a chance to craft the exception message 671 error = String(error) 672 .replace(/&/g, "&") 673 .replace(/</g, "<") 674 .replace(/>/g, ">") 675 .replace(/"/g, """) 676 .replace(/'/g, "'"); 677 a.innerHTML += ' (' + error + ')'; 678 } 679 a.click(); 680 document.body.removeChild( a ); 681 } 682 683 /** 684 * Returns data object for the slide with the given indices. 685 */ 686 function getSlideData( indices, id ) { 687 if ( id == undefined ) id = mode; 688 if ( !indices ) indices = slideIndices; 689 var data; 690 for ( var i = 0; i < storage[ id ].data.length; i++ ) { 691 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 ) { 692 data = storage[ id ].data[ i ]; 693 return data; 694 } 695 } 696 var page = Number( Reveal.getCurrentSlide().getAttribute('data-pdf-page-number') ); 697//console.log( indices, Reveal.getCurrentSlide() ); 698 storage[ id ].data.push( { 699 slide: indices, 700 page, 701 events: [], 702 duration: 0 703 } ); 704 data = storage[ id ].data[ storage[ id ].data.length - 1 ]; 705 return data; 706 } 707 708 /** 709 * Returns maximum duration of slide playback for both modes 710 */ 711 function getSlideDuration( indices ) { 712 if ( !indices ) indices = slideIndices; 713 var duration = 0; 714 for ( var id = 0; id < 2; id++ ) { 715 for ( var i = 0; i < storage[ id ].data.length; i++ ) { 716 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 ) { 717 duration = Math.max( duration, storage[ id ].data[ i ].duration ); 718 break; 719 } 720 } 721 } 722//console.log( duration ); 723 return duration; 724 } 725 726/***************************************************************** 727 ** Print 728 ******************************************************************/ 729 var printMode = ( /print-pdf/gi ).test( window.location.search ); 730//console.log("createPrintout" + printMode) 731 732 function addPageNumbers() { 733 // determine page number for printouts with fragments serialised 734 var slides = Reveal.getSlides(); 735 var page = 0; 736 for ( var i=0; i < slides.length; i++) { 737 slides[i].setAttribute('data-pdf-page-number',page.toString()); 738 // add number of fragments without fragment indices 739 var count = slides[i].querySelectorAll('.fragment:not([data-fragment-index])').length; 740 var fragments = slides[i].querySelectorAll('.fragment[data-fragment-index]'); 741 for ( var j=0; j < fragments.length; j++) { 742 // increasenumber of fragments by highest fragment index (which start at 0) 743 if ( Number(fragments[j].getAttribute('data-fragment-index')) + 1 > count ) { 744 count = Number(fragments[j].getAttribute('data-fragment-index')) + 1; 745 } 746 } 747 page += count + 1; 748 } 749 } 750 751 function createPrintout() { 752 //console.warn(Reveal.getTotalSlides(),Reveal.getSlidesElement()); 753 if ( storage[ 1 ].data.length == 0 ) return; 754 console.log( 'Create printout(s) for ' + storage[ 1 ].data.length + " slides" ); 755 drawingCanvas[ 0 ].container.style.opacity = 0; // do not print notes canvas 756 drawingCanvas[ 0 ].container.style.visibility = 'hidden'; 757 758 var patImg = new Image(); 759 patImg.onload = function () { 760 var slides = Reveal.getSlides(); 761//console.log(slides); 762 for ( var i = storage[ 1 ].data.length - 1; i >= 0; i-- ) { 763 console.log( 'Create printout for slide ' + storage[ 1 ].data[ i ].slide.h + '.' + storage[ 1 ].data[ i ].slide.v ); 764 var slideData = getSlideData( storage[ 1 ].data[ i ].slide, 1 ); 765 var drawings = createDrawings( slideData, patImg ); 766 addDrawings( slides[storage[ 1 ].data[ i ].page], drawings ); 767 768 } 769// Reveal.sync(); 770 }; 771 patImg.src = background[ 1 ]; 772 } 773 774 775 function cloneCanvas( oldCanvas ) { 776 //create a new canvas 777 var newCanvas = document.createElement( 'canvas' ); 778 var context = newCanvas.getContext( '2d' ); 779 //set dimensions 780 newCanvas.width = oldCanvas.width; 781 newCanvas.height = oldCanvas.height; 782 //apply the old canvas to the new one 783 context.drawImage( oldCanvas, 0, 0 ); 784 //return the new canvas 785 return newCanvas; 786 } 787 788 function getCanvas( template, container, board ) { 789 var idx = container.findIndex( element => element.board === board ); 790 if ( idx === -1 ) { 791 var canvas = cloneCanvas( template ); 792 if ( !container.length ) { 793 idx = 0; 794 container.push( { 795 board, 796 canvas 797 } ); 798 } else if ( board < container[ 0 ].board ) { 799 idx = 0; 800 container.unshift( { 801 board, 802 canvas 803 } ); 804 } else if ( board > container[ container.length - 1 ].board ) { 805 idx = container.length; 806 container.push( { 807 board, 808 canvas 809 } ); 810 } 811 } 812 813 return container[ idx ].canvas; 814 } 815 816 function createDrawings( slideData, patImg ) { 817 var width = Reveal.getConfig().width; 818 var height = Reveal.getConfig().height; 819 var scale = 1; 820 var xOffset = 0; 821 var yOffset = 0; 822 if ( width != storage[ 1 ].width || height != storage[ 1 ].height ) { 823 scale = Math.min( width / storage[ 1 ].width, height / storage[ 1 ].height ); 824 xOffset = ( width - storage[ 1 ].width * scale ) / 2;
825 yOffset = ( height - storage[ 1 ].height * scale ) / 2; 826 } 827 mode = 1; 828 board = 0; 829// console.log( 'Create printout(s) for slide ', slideData ); 830 831 var drawings = []; 832 var template = document.createElement( 'canvas' ); 833 template.width = width; 834 template.height = height; 835 836 var imgCtx = template.getContext( '2d' ); 837 imgCtx.fillStyle = imgCtx.createPattern( patImg, 'repeat' ); 838 imgCtx.rect( 0, 0, width, height ); 839 imgCtx.fill(); 840 841 for ( var j = 0; j < slideData.events.length; j++ ) { 842 switch ( slideData.events[ j ].type ) { 843 case 'draw': 844 draw[ 1 ]( getCanvas( template, drawings, board ).getContext( '2d' ), 845 xOffset + slideData.events[ j ].x1 * scale, 846 yOffset + slideData.events[ j ].y1 * scale, 847 xOffset + slideData.events[ j ].x2 * scale, 848 yOffset + slideData.events[ j ].y2 * scale, 849 yOffset + slideData.events[ j ].color 850 ); 851 break; 852 case 'erase': 853 eraseWithSponge( getCanvas( template, drawings, board ).getContext( '2d' ), 854 xOffset + slideData.events[ j ].x * scale, 855 yOffset + slideData.events[ j ].y * scale 856 ); 857 break; 858 case 'selectboard': 859 selectBoard( slideData.events[ j ].board ); 860 break; 861 case 'clear': 862 getCanvas( template, drawings, board ).getContext( '2d' ).clearRect( 0, 0, width, height ); 863 getCanvas( template, drawings, board ).getContext( '2d' ).fill(); 864 break; 865 default: 866 break; 867 } 868 } 869 870 drawings = drawings.sort( ( a, b ) => a.board > b.board && 1 || -1 ); 871 872 mode = 0; 873 874 return drawings; 875 } 876 877 function addDrawings( slide, drawings ) { 878 var parent = slide.parentElement.parentElement; 879 var nextSlide = slide.parentElement.nextElementSibling; 880 881 for ( var i = 0; i < drawings.length; i++ ) { 882 var newPDFPage = document.createElement( 'div' ); 883 newPDFPage.classList.add( 'pdf-page' ); 884 newPDFPage.style.height = Reveal.getConfig().height; 885 newPDFPage.append( drawings[ i ].canvas ); 886//console.log("Add drawing", newPDFPage); 887 if ( nextSlide != null ) { 888 parent.insertBefore( newPDFPage, nextSlide ); 889 } else { 890 parent.append( newPDFPage ); 891 } 892 } 893 } 894 895 /***************************************************************** 896 ** Drawings 897 ******************************************************************/ 898 899 function drawWithBoardmarker( context, fromX, fromY, toX, toY, colorIdx ) { 900 if ( colorIdx == undefined ) colorIdx = color[ mode ]; 901 context.lineWidth = boardmarkerWidth; 902 context.lineCap = 'round'; 903 context.strokeStyle = boardmarkers[ colorIdx ].color; 904 context.beginPath(); 905 context.moveTo( fromX, fromY ); 906 context.lineTo( toX, toY ); 907 context.stroke(); 908 } 909 910 function drawWithChalk( context, fromX, fromY, toX, toY, colorIdx ) { 911 if ( colorIdx == undefined ) colorIdx = color[ mode ]; 912 var brushDiameter = chalkWidth; 913 context.lineWidth = brushDiameter; 914 context.lineCap = 'round'; 915 context.fillStyle = chalks[ colorIdx ].color; // 'rgba(255,255,255,0.5)'; 916 context.strokeStyle = chalks[ colorIdx ].color; 917 918 var opacity = 1.0; 919 context.strokeStyle = context.strokeStyle.replace( /[\d\.]+\)$/g, opacity + ')' ); 920 context.beginPath(); 921 context.moveTo( fromX, fromY ); 922 context.lineTo( toX, toY ); 923 context.stroke(); 924 // Chalk Effect 925 var length = Math.round( Math.sqrt( Math.pow( toX - fromX, 2 ) + Math.pow( toY - fromY, 2 ) ) / ( 5 / brushDiameter ) ); 926 var xUnit = ( toX - fromX ) / length; 927 var yUnit = ( toY - fromY ) / length; 928 for ( var i = 0; i < length; i++ ) { 929 if ( chalkEffect > ( Math.random() * 0.9 ) ) { 930 var xCurrent = fromX + ( i * xUnit ); 931 var yCurrent = fromY + ( i * yUnit ); 932 var xRandom = xCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2; 933 var yRandom = yCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2; 934 context.clearRect( xRandom, yRandom, Math.random() * 2 + 2, Math.random() + 1 ); 935 } 936 } 937 } 938 939 function eraseWithSponge( context, x, y ) { 940 context.save(); 941 context.beginPath(); 942 context.arc( x + eraser.radius, y + eraser.radius, eraser.radius, 0, 2 * Math.PI, false ); 943 context.clip(); 944 context.clearRect( x - 1, y - 1, eraser.radius * 2 + 2, eraser.radiu
944s * 2 + 2 ); 945 context.restore(); 946 if ( mode == 1 && grid ) { 947 redrawGrid( x + eraser.radius, y + eraser.radius, eraser.radius ); 948 } 949 } 950 951 952 /** 953 * Show an overlay for the chalkboard. 954 */ 955 function showChalkboard() { 956//console.log("showChalkboard"); 957 drawingCanvas[ 1 ].container.style.opacity = 1; 958 drawingCanvas[ 1 ].container.style.visibility = 'visible'; 959 mode = 1; 960 } 961 962 963 /** 964 * Closes open chalkboard. 965 */ 966 function closeChalkboard() { 967 drawingCanvas[ 1 ].container.style.opacity = 0; 968 drawingCanvas[ 1 ].container.style.visibility = 'hidden'; 969 lastX = null; 970 lastY = null; 971 mode = 0; 972 } 973 974 /** 975 * Clear current canvas. 976 */ 977 function clearCanvas( id ) { 978 if ( id == 0 ) clearTimeout( slidechangeTimeout ); 979 drawingCanvas[ id ].context.clearRect( 0, 0, drawingCanvas[ id ].width, drawingCanvas[ id ].height ); 980 if ( id == 1 && grid ) drawGrid(); 981 } 982 983 /** 984 * Draw grid on background 985 */ 986 function drawGrid() { 987 var context = drawingCanvas[ 1 ].context; 988 989 drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height ); 990 drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2; 991 drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2; 992 993 var scale = drawingCanvas[ 1 ].scale; 994 var xOffset = drawingCanvas[ 1 ].xOffset; 995 var yOffset = drawingCanvas[ 1 ].yOffset; 996 997 var distance = grid.distance * scale; 998 999 var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance; 1000 for ( var x = fromX; x < drawingCanvas[ 1 ].width; x += distance ) { 1001 context.beginPath(); 1002 context.lineWidth = grid.width * scale; 1003 context.lineCap = 'round'; 1004 context.fillStyle = grid.color; 1005 context.strokeStyle = grid.color; 1006 context.moveTo( x, 0 ); 1007 context.lineTo( x, drawingCanvas[ 1 ].height ); 1008 context.stroke(); 1009 } 1010 var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance; 1011 1012 for ( var y = fromY; y < drawingCanvas[ 1 ].height; y += distance ) { 1013 context.beginPath(); 1014 context.lineWidth = grid.width * scale; 1015 context.lineCap = 'round'; 1016 context.fillStyle = grid.color; 1017 context.strokeStyle = grid.color; 1018 context.moveTo( 0, y ); 1019 context.lineTo( drawingCanvas[ 1 ].width, y ); 1020 context.stroke(); 1021 } 1022 } 1023 1024 function redrawGrid( centerX, centerY, diameter ) { 1025 var context = drawingCanvas[ 1 ].context; 1026 1027 drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height ); 1028 drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2; 1029 drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2; 1030 1031 var scale = drawingCanvas[ 1 ].scale; 1032 var xOffset = drawingCanvas[ 1 ].xOffset; 1033 var yOffset = drawingCanvas[ 1 ].yOffset; 1034 1035 var distance = grid.distance * scale; 1036 1037 var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance; 1038 1039 for ( var x = fromX + distance * Math.ceil( ( centerX - diameter - fromX ) / distance ); x <= fromX + distance * Math.floor( ( centerX + diameter - fromX ) / distance ); x += distance ) { 1040 context.beginPath(); 1041 context.lineWidth = grid.width * scale; 1042 context.lineCap = 'round'; 1043 context.fillStyle = grid.color; 1044 context.strokeStyle = grid.color; 1045 context.moveTo( x, centerY - Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) ); 1046 context.lineTo( x, centerY + Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) ); 1047 context.stroke(); 1048 } 1049 var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance; 1050 for ( var y = fromY + distance * Math.ceil( ( centerY - diameter - fromY ) / distance ); y <= fromY + distance * Math.floor( ( centerY + diameter - fromY ) / distance ); y += distance ) { 1051 context.beginPath(); 1052 context.lineWidth = grid.width * scale; 1053 context.lineCap = 'round'; 1054 context.fillStyle = grid.color; 1055 context.strokeStyle = grid.color; 1056 context.moveTo( centerX - Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y ); 1057 context.lineTo( centerX + Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y ); 1058 context.stroke(); 1059 } 1060 } 1061
1062 /** 1063 * Set the color 1064 */ 1065 function setColor( index, record ) { 1066 // protect against out of bounds (this could happen when 1067 // replaying events recorded with different color settings). 1068 if ( index >= pens[ mode ].length ) index = 0; 1069 1070 color[ mode ] = index; 1071 1072 if ( color[ mode ] < 0 ) { 1073 // use eraser 1074 changeCursor( drawingCanvas[ mode ].canvas, sponge ); 1075 } 1076 else { 1077 changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] ); 1078 } 1079 } 1080 1081 /** 1082 * Set the board 1083 */ 1084 function selectBoard( boardIdx, record ) { 1085//console.log("Set board",boardIdx); 1086 if ( board == boardIdx ) return; 1087 1088 board = boardIdx; 1089 redrawChalkboard( boardIdx ); 1090 if ( record ) { 1091 recordEvent( { type: 'selectboard' } ); 1092 } 1093 } 1094 1095 function redrawChalkboard( boardIdx ) { 1096 clearCanvas( 1 ); 1097 var slideData = getSlideData( slideIndices, 1 ); 1098 var index = 0; 1099 var play = ( boardIdx == 0 ); 1100 while ( index < slideData.events.length && slideData.events[ index ].time < Date.now() - slideStart ) { 1101 if ( boardIdx == slideData.events[ index ].board ) { 1102 playEvent( 1, slideData.events[ index ], Date.now() - slideStart ); 1103 } 1104 1105 index++; 1106 } 1107 } 1108 1109 1110 /** 1111 * Forward cycle color 1112 */ 1113 function cycleColorNext() { 1114 color[ mode ] = ( color[ mode ] + 1 ) % pens[ mode ].length; 1115 return color[ mode ]; 1116 } 1117 1118 /** 1119 * Backward cycle color 1120 */ 1121 function cycleColorPrev() { 1122 color[ mode ] = ( color[ mode ] + ( pens[ mode ].length - 1 ) ) % pens[ mode ].length; 1123 return color[ mode ]; 1124 } 1125 1126/***************************************************************** 1127 ** Broadcast 1128 ******************************************************************/ 1129 1130 var eventQueue = []; 1131 1132 document.addEventListener( 'received', function ( message ) { 1133 if ( message.content && message.content.sender == 'chalkboard-plugin' ) { 1134 // add message to queue 1135 eventQueue.push( message ); 1136 console.log( JSON.stringify( message ) ); 1137 } 1138 if ( eventQueue.length == 1 ) processQueue(); 1139 } ); 1140 1141 function processQueue() { 1142 // take first message from queue 1143 var message = eventQueue.shift(); 1144 1145 // synchronize time with seminar host 1146 slideStart = Date.now() - message.content.timestamp; 1147 // set status 1148 if ( mode < message.content.mode ) { 1149 // open chalkboard 1150 showChalkboard(); 1151 } else if ( mode > message.content.mode ) { 1152 // close chalkboard 1153 closeChalkboard(); 1154 } 1155 if ( board != message.content.board ) { 1156 board = message.content.board; 1157 redrawChalkboard( board ); 1158 }; 1159 1160 switch ( message.content.type ) { 1161 case 'showChalkboard': 1162 showChalkboard(); 1163 break; 1164 case 'closeChalkboard': 1165 closeChalkboard(); 1166 break; 1167 case 'erase': 1168 erasePoint( message.content.x, message.content.y ); 1169 break; 1170 case 'draw': 1171 drawSegment( message.content.fromX, message.content.fromY, message.content.toX, message.content.toY, message.content.color ); 1172 break; 1173 case 'clear': 1174 clearSlide(); 1175 break; 1176 case 'selectboard': 1177 selectBoard( message.content.board, true ); 1178 break; 1179 case 'resetSlide': 1180 resetSlideDrawings(); 1181 break; 1182 case 'init': 1183 storage = message.content.storage; 1184 for ( var id = 0; id < 2; id++ ) { 1185 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height ); 1186 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2; 1187 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2; 1188 } 1189 clearCanvas( 0 ); 1190 clearCanvas( 1 ); 1191 if ( !playback ) { 1192 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 ); 1193 } 1194 if ( mode == 1 && message.content.mode == 0 ) { 1195 setTimeout( closeChalkboard, transition + 50 ); 1196 } 1197 if ( mode == 0 && message.content.mode == 1 ) { 1198 setTimeout( showChalkboard, transition + 50 ); 1199 } 1200 mode = message.content.mode; 1201 board = message.content.board; 1202 break; 1203 default: 1204 break; 1205 } 1206 1207 // continue with next message if queued 1208 if ( eventQueue.length > 0 ) { 1209 processQueue(); 1210 } else { 1211 storageChanged(); 1212 } 1213 } 1214 1215 document.addEventListener( 'welcome', function ( user ) { 1216 // broadcast storage 1217 var message = new CustomEvent( messageType ); 1218 message.content = { 1219 sender: 'chalkboard-plugin', 1220 recipient: user.id, 1221 type: 'init', 1222 timestamp: Date.now() - slideStart, 1223 storage: storage, 1224 mode, 1225 board 1226 }; 1227 document.dispatchEvent( message ); 1228 } ); 1229 1230 /***************************************************************** 1231 ** Playback 1232 ******************************************************************/ 1233 1234 document.addEventListener( 'seekplayback', function ( event ) { 1235//console.log('event seekplayback ' + event.timestamp); 1236 stopPlayback(); 1237 if ( !playback || event.timestamp == 0 ) { 1238 // in other cases startplayback fires after seeked 1239 startPlayback( event.timestamp ); 1240 } 1241 //console.log('seeked'); 1242 } ); 1243 1244 1245 document.addEventListener( 'startplayback', function ( event ) { 1246//console.log('event startplayback ' + event.timestamp); 1247 stopPlayback(); 1248 playback = true; 1249 startPlayback( event.timestamp ); 1250 } ); 1251 1252 document.addEventListener( 'stopplayback', function ( event ) { 1253//console.log('event stopplayback ' + (Date.now() - slideStart) ); 1254 playback = false;
1255 stopPlayback(); 1256 } ); 1257 1258 document.addEventListener( 'startrecording', function ( event ) { 1259//console.log('event startrecording ' + event.timestamp); 1260 startRecording(); 1261 } ); 1262 1263 1264 function startRecording() { 1265 resetSlide( true ); 1266 slideStart = Date.now(); 1267 } 1268 1269 function startPlayback( timestamp, finalMode ) { 1270//console.log("playback " + timestamp ); 1271 slideStart = Date.now() - timestamp; 1272 closeChalkboard(); 1273 mode = 0; 1274 board = 0; 1275 for ( var id = 0; id < 2; id++ ) { 1276 clearCanvas( id ); 1277 var slideData = getSlideData( slideIndices, id ); 1278//console.log( timestamp +" / " + JSON.stringify(slideData)); 1279 var index = 0; 1280 while ( index < slideData.events.length && slideData.events[ index ].time < ( Date.now() - slideStart ) ) { 1281 playEvent( id, slideData.events[ index ], timestamp ); 1282 index++; 1283 } 1284 1285 while ( playback && index < slideData.events.length ) { 1286 timeouts[ id ].push( setTimeout( playEvent, slideData.events[ index ].time - ( Date.now() - slideStart ), id, slideData.events[ index ], timestamp ) ); 1287 index++; 1288 } 1289 } 1290//console.log("Mode: " + finalMode + "/" + mode ); 1291 if ( finalMode != undefined ) { 1292 mode = finalMode; 1293 } 1294 if ( mode == 1 ) showChalkboard(); 1295//console.log("playback (ok)"); 1296 1297 }; 1298 1299 function stopPlayback() { 1300//console.log("stopPlayback"); 1301//console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length); 1302 for ( var id = 0; id < 2; id++ ) { 1303 for ( var i = 0; i < timeouts[ id ].length; i++ ) { 1304 clearTimeout( timeouts[ id ][ i ] ); 1305 } 1306 timeouts[ id ] = []; 1307 } 1308 }; 1309 1310 function playEvent( id, event, timestamp ) { 1311//console.log( timestamp +" / " + JSON.stringify(event)); 1312//console.log( id + ": " + timestamp +" / " + event.time +" / " + event.type +" / " + mode ); 1313 switch ( event.type ) { 1314 case 'open': 1315 if ( timestamp <= event.time ) { 1316 showChalkboard(); 1317 } else { 1318 mode = 1; 1319 } 1320 1321 break; 1322 case 'close': 1323 if ( timestamp < event.time ) { 1324 closeChalkboard(); 1325 } else { 1326 mode = 0; 1327 } 1328 break; 1329 case 'clear': 1330 clearCanvas( id ); 1331 break; 1332 case 'selectboard': 1333 selectBoard( event.board ); 1334 break; 1335 case 'draw': 1336 drawLine( id, event, timestamp ); 1337 break; 1338 case 'erase': 1339 eraseCircle( id, event, timestamp ); 1340 break; 1341 } 1342 }; 1343 1344 function drawLine( id, event, timestamp ) { 1345 var ctx = drawingCanvas[ id ].context; 1346 var scale = drawingCanvas[ id ].scale; 1347 var xOffset = drawingCanvas[ id ].xOffset; 1348 var yOffset = drawingCanvas[ id ].yOffset; 1349 draw[ id ]( ctx, xOffset + event.x1 * scale, yOffset + event.y1 * scale, xOffset + event.x2 * scale, yOffset + event.y2 * scale, event.color ); 1350 }; 1351 1352 function eraseCircle( id, event, timestamp ) { 1353 var ctx = drawingCanvas[ id ].context; 1354 var scale = drawingCanvas[ id ].scale; 1355 var xOffset = drawingCanvas[ id ].xOffset; 1356 var yOffset = drawingCanvas[ id ].yOffset; 1357 1358 eraseWithSponge( ctx, xOffset + event.x * scale, yOffset + event.y * scale ); 1359 }; 1360 1361 function startErasing( x, y ) { 1362 drawing = false; 1363 erasing = true; 1364 erasePoint( x, y ); 1365 } 1366 1367 function erasePoint( x, y ) { 1368 var ctx = drawingCanvas[ mode ].context; 1369 var scale = drawingCanvas[ mode ].scale; 1370 var xOffset = drawingCanvas[ mode ].xOffset; 1371 var yOffset = drawingCanvas[ mode ].yOffset; 1372 1373 recordEvent( { 1374 type: 'erase', 1375 x, 1376 y 1377 } ); 1378 1379 if ( 1380 x * scale + xOffset > 0 && 1381 y * scale + yOffset > 0 && 1382 x * scale + xOffset < drawingCanvas[ mode ].width && 1383 y * scale + yOffset < drawingCanvas[ mode ].height 1384 ) { 1385 eraseWithSponge( ctx, x * scale + xOffset, y * scale + yOffset ); 1386 } 1387 } 1388 1389 function stopErasing() { 1390 erasing = false; 1391 } 1392 1393 function startDrawing( x, y ) { 1394 drawing = true; 1395 1396 var ctx = drawingCanvas[ mode ].context; 1397 var scale = drawingCanvas[ mode ].scale; 1398 var xOffset = drawingCanvas[ mode ].xOffset; 1399 var yOffset = drawingCanvas[ mode ].yOffset; 1400 lastX = x * scale + xOffset; 1401 lastY = y * scale + yOffset; 1402 } 1403 1404 function drawSegment( fromX, fromY, toX, toY, colorIdx ) { 1405 var ctx = drawingCanvas[ mode ].context; 1406 var scale = drawingCanvas[ mode ].scale; 1407 var xOffset = drawingCanvas[ mode ].xOffset; 1408 var yOffset = drawingCanvas[ mode ].yOffset; 1409 1410 recordEvent( { 1411 type: 'draw', 1412 color: colorIdx, 1413 x1: fromX, 1414 y1: fromY, 1415 x2: toX, 1416 y2: toY 1417 } ); 1418 1419 if ( 1420 fromX * scale + xOffset > 0 && 1421 fromY * scale + yOffset > 0 && 1422 fromX * scale + xOffset < drawingCanvas[ mode ].width && 1423 fromY * scale + yOffset < drawingCanvas[ mode ].height && 1424 toX * scale + xOffset > 0 && 1425 toY * scale + yOffset > 0 && 1426 toX * scale + xOffset < drawingCanvas[ mode ].width && 1427 toY * scale + yOffset < drawingCanvas[ mode ].height 1428 ) { 1429 draw[ mode ]( ctx, fromX * scale + xOffset, fromY * scale + yOffset, toX * scale + xOffset, toY * scale + yOffset, colorIdx ); 1430 } 1431 } 1432 1433 function stopDrawing() { 1434 drawing = false;
1435 } 1436 1437 1438/***************************************************************** 1439 ** User interface 1440 ******************************************************************/ 1441 1442 function setupCanvasEvents( canvas ) { 1443// TODO: check all touchevents 1444 canvas.addEventListener( 'touchstart', function ( evt ) { 1445 evt.preventDefault(); 1446//console.log("Touch start"); 1447 if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) { 1448 var scale = drawingCanvas[ mode ].scale; 1449 var xOffset = drawingCanvas[ mode ].xOffset; 1450 var yOffset = drawingCanvas[ mode ].yOffset; 1451 1452 var touch = evt.touches[ 0 ]; 1453 mouseX = touch.pageX; 1454 mouseY = touch.pageY; 1455 if ( color[ mode ] < 0 ) { 1456 startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale); 1457 } 1458 else { 1459 startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); 1460 } 1461 } 1462 }, passiveSupported ? { 1463 passive: false 1464 } : false ); 1465 1466 canvas.addEventListener( 'touchmove', function ( evt ) { 1467 evt.preventDefault(); 1468//console.log("Touch move"); 1469 if ( drawing || erasing ) { 1470 var scale = drawingCanvas[ mode ].scale; 1471 var xOffset = drawingCanvas[ mode ].xOffset; 1472 var yOffset = drawingCanvas[ mode ].yOffset; 1473 1474 var touch = evt.touches[ 0 ]; 1475 mouseX = touch.pageX; 1476 mouseY = touch.pageY; 1477 1478 if ( drawing ) { 1479 drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] ); 1480 // broadcast 1481 var message = new CustomEvent( messageType ); 1482 message.content = { 1483 sender: 'chalkboard-plugin', 1484 type: 'draw', 1485 timestamp: Date.now() - slideStart, 1486 mode, 1487 board, 1488 fromX: ( lastX - xOffset ) / scale, 1489 fromY: ( lastY - yOffset ) / scale, 1490 toX: ( mouseX - xOffset ) / scale, 1491 toY: ( mouseY - yOffset ) / scale, 1492 color: color[ mode ] 1493 }; 1494 document.dispatchEvent( message ); 1495 1496 lastX = mouseX; 1497 lastY = mouseY; 1498 } else { 1499 erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); 1500 // broadcast 1501 var message = new CustomEvent( messageType ); 1502 message.content = { 1503 sender: 'chalkboard-plugin', 1504 type: 'erase', 1505 timestamp: Date.now() - slideStart, 1506 mode, 1507 board, 1508 x: ( mouseX - xOffset ) / scale, 1509 y: ( mouseY - yOffset ) / scale 1510 }; 1511 document.dispatchEvent( message ); 1512 } 1513 1514 } 1515 }, false ); 1516 1517 1518 canvas.addEventListener( 'touchend', function ( evt ) { 1519 evt.preventDefault(); 1520 stopDrawing(); 1521 stopErasing(); 1522 }, false ); 1523 1524 canvas.addEventListener( 'mousedown', function ( evt ) { 1525 evt.preventDefault(); 1526 if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) { 1527//console.log( "mousedown: " + evt.button ); 1528 var scale = drawingCanvas[ mode ].scale; 1529 var xOffset = drawingCanvas[ mode ].xOffset; 1530 var yOffset = drawingCanvas[ mode ].yOffset; 1531 1532 mouseX = evt.pageX; 1533 mouseY = evt.pageY; 1534 1535 if ( color[ mode ] < 0 || evt.button == 2 || evt.button == 1 ) { 1536 if ( color[ mode ] >= 0 ) { 1537 // show sponge 1538 changeCursor( drawingCanvas[ mode ].canvas, sponge ); 1539 } 1540 startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); 1541 // broadcast 1542 var message = new CustomEvent( messageType ); 1543 message.content = { 1544 sender: 'chalkboard-plugin', 1545 type: 'erase', 1546 timestamp: Date.now() - slideStart, 1547 mode, 1548 board, 1549 x: ( mouseX - xOffset ) / scale, 1550 y: ( mouseY - yOffset ) / scale 1551 }; 1552 document.dispatchEvent( message ); 1553 } else { 1554 startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); 1555 } 1556 } 1557 } ); 1558 1559 canvas.addEventListener( 'mousemove', function ( evt ) { 1560 evt.preventDefault(); 1561//console.log("Mouse move"); 1562 1563 var scale = drawingCanvas[ mode ].scale; 1564 var xOffset = drawingCanvas[ mode ].xOffset; 1565 var yOffset = drawingCanvas[ mode ].yOffset; 1566 1567 mouseX = evt.pageX; 1568 mouseY = evt.pageY; 1569 1570 if ( drawing || erasing ) { 1571 var scale = drawingCanvas[ mode ].scale; 1572 var xOffset = drawingCanvas[ mode ].xOffset; 1573 var yOffset = drawingCanvas[ mode ].yOffset; 1574 1575 mouseX = evt.pageX; 1576 mouseY = evt.pageY; 1577 1578 if ( drawing ) { 1579 drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] ); 1580 // broadcast 1581 var message = new CustomEvent( messageType ); 1582 message.content = { 1583 sender: 'chalkboard-plugin', 1584 type: 'draw', 1585 timestamp: Date.now() - slideStart, 1586 mode, 1587 board, 1588 fromX: ( lastX - xOffset ) / scale, 1589 fromY: ( lastY - yOffset ) / scale, 1590 toX: ( mouseX - xOffset ) / scale, 1591 toY: ( mouseY - yOffset ) / scale, 1592 color: color[ mode ] 1593 }; 1594 document.dispatchEvent( message ); 1595 1596 lastX = mouseX; 1597 lastY = mouseY; 1598 } else { 1599 erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale ); 1600 // broadcast 1601 var message = new CustomEvent( messageType ); 1602 message.content = { 1603 sender: 'chalkboard-plugin', 1604 type: 'erase', 1605 timestamp: Date.now() - slideStart, 1606 mode, 1607 board, 1608 x: ( mouseX - xOffset ) / scale, 1609 y: ( mouseY - yOffset ) / scale 1610 }; 1611 document.dispatchEvent( message ); 1612 } 1613 1614 } 1615 } ); 1616 1617 1618 canvas.addEventListener( 'mouseup', function ( evt ) { 1619 evt.preventDefault(); 1620 if ( color[ mode ] >= 0 ) { 1621 changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] ); 1622 } 1623 if ( drawing || erasing ) { 1624 stopDrawing(); 1625 stopErasing(); 1626 } 1627 } ); 1628 } 1629 1630 function resize() { 1631//console.log("resize"); 1632 // Resize the canvas and draw everything again 1633 var timestamp = Date.now() - slideStart; 1634 if ( !playback ) { 1635 timestamp = getSlideDuration(); 1636 } 1637 1638//console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/"
1638+drawingCanvas[0].yOffset ); 1639 for ( var id = 0; id < 2; id++ ) { 1640 drawingCanvas[ id ].width = window.innerWidth; 1641 drawingCanvas[ id ].height = window.innerHeight; 1642 drawingCanvas[ id ].canvas.width = drawingCanvas[ id ].width; 1643 drawingCanvas[ id ].canvas.height = drawingCanvas[ id ].height; 1644 drawingCanvas[ id ].context.canvas.width = drawingCanvas[ id ].width; 1645 drawingCanvas[ id ].context.canvas.height = drawingCanvas[ id ].height; 1646 1647 drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height ); 1648 drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2; 1649 drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2; 1650//console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset ); 1651 } 1652//console.log( window.innerWidth + "/" + window.innerHeight); 1653 startPlayback( timestamp, mode, true ); 1654 } 1655 1656 Reveal.addEventListener( 'pdf-ready', function ( evt ) { 1657// console.log( "Create printouts when ready" ); 1658 whenLoaded( createPrintout ); 1659 }); 1660 1661 Reveal.addEventListener( 'ready', function ( evt ) { 1662//console.log('ready'); 1663 if ( !printMode ) { 1664 window.addEventListener( 'resize', resize ); 1665 1666 slideStart = Date.now() - getSlideDuration(); 1667 slideIndices = Reveal.getIndices(); 1668 if ( !playback ) { 1669 startPlayback( getSlideDuration(), 0 ); 1670 } 1671 if ( Reveal.isAutoSliding() ) { 1672 var event = new CustomEvent( 'startplayback' ); 1673 event.timestamp = 0; 1674 document.dispatchEvent( event ); 1675 } 1676 updateStorage(); 1677 whenReady( addPageNumbers ); 1678 } 1679 } ); 1680 Reveal.addEventListener( 'slidechanged', function ( evt ) { 1681// clearTimeout( slidechangeTimeout ); 1682//console.log('slidechanged'); 1683 if ( !printMode ) { 1684 slideStart = Date.now() - getSlideDuration(); 1685 slideIndices = Reveal.getIndices(); 1686 closeChalkboard(); 1687 board = 0; 1688 clearCanvas( 0 ); 1689 clearCanvas( 1 ); 1690 if ( !playback ) { 1691 slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 ); 1692 } 1693 if ( Reveal.isAutoSliding() ) { 1694 var event = new CustomEvent( 'startplayback' ); 1695 event.timestamp = 0; 1696 document.dispatchEvent( event ); 1697 } 1698 } 1699 } ); 1700 Reveal.addEventListener( 'fragmentshown', function ( evt ) { 1701// clearTimeout( slidechangeTimeout ); 1702//console.log('fragmentshown'); 1703 if ( !printMode ) { 1704 slideStart = Date.now() - getSlideDuration(); 1705 slideIndices = Reveal.getIndices(); 1706 closeChalkboard(); 1707 board = 0; 1708 clearCanvas( 0 ); 1709 clearCanvas( 1 ); 1710 if ( Reveal.isAutoSliding() ) { 1711 var event = new CustomEvent( 'startplayback' ); 1712 event.timestamp = 0; 1713 document.dispatchEvent( event ); 1714 } else if ( !playback ) { 1715 startPlayback( getSlideDuration(), 0 ); 1716// closeChalkboard(); 1717 } 1718 } 1719 } ); 1720 Reveal.addEventListener( 'fragmenthidden', function ( evt ) { 1721// clearTimeout( slidechangeTimeout ); 1722//console.log('fragmenthidden'); 1723 if ( !printMode ) { 1724 slideStart = Date.now() - getSlideDuration(); 1725 slideIndices = Reveal.getIndices(); 1726 closeChalkboard(); 1727 board = 0; 1728 clearCanvas( 0 ); 1729 clearCanvas( 1 ); 1730 if ( Reveal.isAutoSliding() ) { 1731 document.dispatchEvent( new CustomEvent( 'stopplayback' ) ); 1732 } else if ( !playback ) { 1733 startPlayback( getSlideDuration() ); 1734 closeChalkboard(); 1735 } 1736 } 1737 } ); 1738 1739 Reveal.addEventListener( 'autoslideresumed', function ( evt ) { 1740//console.log('autoslideresumed'); 1741 var event = new CustomEvent( 'startplayback' ); 1742 event.timestamp = 0; 1743 document.dispatchEvent( event ); 1744 } ); 1745 Reveal.addEventListener( 'autoslidepaused', function ( evt ) { 1746//console.log('autoslidepaused'); 1747 document.dispatchEvent( new CustomEvent( 'stopplayback' ) ); 1748 1749 // advance to end of slide 1750// closeChalkboard(); 1751 startPlayback( getSlideDuration(), 0 ); 1752 } ); 1753 1754 function toggleNotesCanvas() { 1755 if ( !readOnly ) { 1756 if ( mode == 1 ) { 1757 toggleChalkboard(); 1758 notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)'; 1759 notescanvas.style.pointerEvents = 'auto'; 1760 } 1761 else { 1762 if ( notescanvas.style.pointerEvents != 'none' ) { 1763 // hide notes canvas 1764 if ( colorButtons ) { 1765 notescanvas.querySelector( '.palette' ).style.visibility = 'hidden'; 1766 } 1767 notescanvas.style.background = 'rgba(0,0,0,0)'; 1768 notescanvas.style.pointerEvents = 'none'; 1769 } 1770 else { 1771 // show notes canvas 1772 if ( colorButtons ) { 1773 notescanvas.querySelector( '.palette' ).style.visibility = 'visible'; 1774 } 1775 notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)'; 1776 notescanvas.style.pointerEvents = 'auto'; 1777 1778 var idx = 0; 1779 if ( color[ mode ] ) { 1780 idx = color[ mode ]; 1781 } 1782 1783 setColor( idx, true ); 1784 } 1785 } 1786 } 1787 }; 1788 1789 function toggleChalkboard() { 1790//console.log("toggleChalkboard " + mode); 1791 if ( mode == 1 ) { 1792 if ( !readOnly ) { 1793 recordEvent( { type: 'close' } ); 1794 } 1795 closeChalkboard(); 1796 1797 // broadcast 1798 var message = new CustomEvent( messageType ); 1799 message.content = { 1800 sender: 'chalkboard-plugin', 1801 type: 'closeChalkboard', 1802 timestamp: Date.now() - slideStart, 1803 mode: 0, 1804 board 1805 }; 1806 document.dispatchEvent( message ); 1807 1808 1809 } else { 1810 showChalkboard(); 1811 if ( !readOnly ) { 1812 recordEvent( { type: 'open' } ); 1813 // broadcast 1814 var message = new CustomEvent( messageType ); 1815 message.content = { 1816 sender: 'chalkboard-plugin', 1817 type: 'showChalkboard', 1818 timestamp: Date.now() - slideStart, 1819 mode: 1, 1820 board 1821 }; 1822 document.dispatchEvent( message ); 1823 1824 var idx = 0; 1825 1826 if ( rememberColor[ mode ] ) { 1827 idx = color[ mode ]; 1828 } 1829 1830 setColor( idx, true ); 1831 } 1832 } 1833 }; 1834 1835 function clearSlide() { 1836 recordEvent( { type: 'clear' } ); 1837 clearCanvas( mode ); 1838 } 1839 1840 function clear() { 1841 if ( !readOnly ) { 1842 clearSlide(); 1843 // broadcast 1844 var message = new CustomEvent( messageType ); 1845 message.content = { 1846 sender: 'chalkboard-plugin', 1847 type: 'clear', 1848 timestamp: Date.now() - slideStart, 1849 mode, 1850 board 1851 }; 1852 document.dispatchEvent( message ); 1853 } 1854 }; 1855 1856 function colorIndex( idx ) { 1857 if ( !readOnly ) { 1858 setColor( idx, true ); 1859 } 1860 } 1861 1862 function colorNext() { 1863 if ( !readOnly ) { 1864 let idx = cycleColorNext(); 1865 setColor( idx, true ); 1866 } 1867 } 1868 1869 function colorPrev() { 1870 if ( !readOnly ) { 1871 let idx = cycleColorPrev(); 1872 setColor( idx, true ); 1873 } 1874 } 1875 1876 function resetSlideDrawings() { 1877 slideStart = Date.now(); 1878 closeChalkboard(); 1879 1880 clearCanvas( 0 );
1881 clearCanvas( 1 ); 1882 1883 mode = 1; 1884 var slideData = getSlideData(); 1885 slideData.duration = 0; 1886 slideData.events = []; 1887 mode = 0; 1888 var slideData = getSlideData(); 1889 slideData.duration = 0; 1890 slideData.events = []; 1891 1892 updateStorage(); 1893 } 1894 1895 function resetSlide( force ) { 1896 var ok = force || confirm( "Please confirm to delete chalkboard drawings on this slide!" ); 1897 if ( ok ) { 1898//console.log("resetSlide "); 1899 stopPlayback(); 1900 resetSlideDrawings(); 1901 // broadcast 1902 var message = new CustomEvent( messageType ); 1903 message.content = { 1904 sender: 'chalkboard-plugin', 1905 type: 'resetSlide', 1906 timestamp: Date.now() - slideStart, 1907 mode, 1908 board 1909 }; 1910 document.dispatchEvent( message ); 1911 } 1912 }; 1913 1914 function resetStorage( force ) { 1915 var ok = force || confirm( "Please confirm to delete all chalkboard drawings!" ); 1916 if ( ok ) { 1917 stopPlayback(); 1918 slideStart = Date.now(); 1919 clearCanvas( 0 ); 1920 clearCanvas( 1 ); 1921 if ( mode == 1 ) { 1922 closeChalkboard(); 1923 } 1924 1925 storage = [ { 1926 width: Reveal.getConfig().width, 1927 height: Reveal.getConfig().height, 1928 data: [] 1929 }, 1930 { 1931 width: Reveal.getConfig().width, 1932 height: Reveal.getConfig().height, 1933 data: [] 1934 } 1935 ]; 1936 1937 if ( config.storage ) { 1938 sessionStorage.setItem( config.storage, null ) 1939 } 1940 // broadcast 1941 var message = new CustomEvent( messageType ); 1942 message.content = { 1943 sender: 'chalkboard-plugin', 1944 type: 'init', 1945 timestamp: Date.now() - slideStart, 1946 storage, 1947 mode, 1948 board 1949 }; 1950 document.dispatchEvent( message ); 1951 } 1952 }; 1953 1954 this.toggleNotesCanvas = toggleNotesCanvas; 1955 this.toggleChalkboard = toggleChalkboard; 1956 this.colorIndex = colorIndex; 1957 this.colorNext = colorNext; 1958 this.colorPrev = colorPrev; 1959 this.clear = clear; 1960 this.reset = resetSlide; 1961 this.resetAll = resetStorage; 1962 this.download = downloadData; 1963 this.updateStorage = updateStorage; 1964 this.getData = getData; 1965 this.configure = configure; 1966 1967 1968 for ( var key in keyBindings ) { 1969 if ( keyBindings[ key ] ) { 1970 Reveal.addKeyBinding( keyBindings[ key ], RevealChalkboard[ key ] ); 1971 } 1972 }; 1973 1974 return this; 1975};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.