From 6aa1eae796a8b9e32ae26d960152035a7bd61a65 Mon Sep 17 00:00:00 2001 From: Hakim El Hattab Date: Tue, 12 Sep 2023 17:00:56 +0200 Subject: [PATCH] foundation for reader mode, activate via 'mode=reader/print' config param --- css/print/pdf.scss | 2 +- css/reveal.scss | 137 +++++++++++++++++++++++++ js/controllers/notes.js | 4 +- js/controllers/{print.js => reader.js} | 72 ++++++++++--- js/reveal.js | 41 +++++--- 5 files changed, 224 insertions(+), 32 deletions(-) rename js/controllers/{print.js => reader.js} (81%) diff --git a/css/print/pdf.scss b/css/print/pdf.scss index 308f202c..3ff04b40 100644 --- a/css/print/pdf.scss +++ b/css/print/pdf.scss @@ -5,7 +5,7 @@ * https://revealjs.com/pdf-export/ */ -html.print-pdf { +html.reveal-print { * { -webkit-print-color-adjust: exact; } diff --git a/css/reveal.scss b/css/reveal.scss index 6eb7caa4..0af020e0 100644 --- a/css/reveal.scss +++ b/css/reveal.scss @@ -1864,6 +1864,143 @@ $notesWidthPercent: 25%; } +/********************************************* + * READER MODE + *********************************************/ +html.reveal-reader { + width: 100%; + height: 100%; + overflow: visible; + + .reveal-viewport, body { + margin: 0 auto !important; + overflow: auto; + } + + .reveal .controls, + .reveal .progress, + .reveal .playback { + display: none !important; + } + + .reveal { + display: flex; + justify-content: center; + width: auto !important; + height: auto !important; + overflow: visible !important; + } + .reveal .slides { + position: static; + zoom: 1 !important; + pointer-events: initial; + transform-origin: 50% 0; + + left: auto; + top: auto; + margin: 0 !important; + padding: 0 !important; + + overflow: visible; + display: block; + + perspective: none; + perspective-origin: 50% 50%; + } + + .reveal .slides .reader-page { + display: grid; + place-items: center; + position: relative; + overflow: hidden; + z-index: 1; + + page-break-after: always; + } + + .reveal .slides .reader-page section { + visibility: visible !important; + display: block !important; + position: relative !important; + + margin: 0 !important; + padding: 0 !important; + box-sizing: border-box !important; + min-height: 1px; + + opacity: 1 !important; + + transform-style: flat !important; + transform: none !important; + } + + .reveal section.stack { + position: relative !important; + margin: 0 !important; + padding: 0 !important; + page-break-after: avoid !important; + height: auto !important; + min-height: auto !important; + } + + /* Slide backgrounds are nested inside of the page in reader mode */ + .reveal .backgrounds { + display: none; + } + + .reveal .slide-background { + display: block !important; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: auto !important; + visibility: visible; + opacity: 1; + } + + /* Display slide speaker notes when 'showNotes' is enabled */ + .reveal.show-notes { + max-width: none; + max-height: none; + } + .reveal .speaker-notes-pdf { + display: block; + width: 100%; + height: auto; + max-height: none; + top: auto; + right: auto; + bottom: auto; + left: auto; + z-index: 100; + } + + /* Layout option which makes notes appear on a separate page */ + .reveal .speaker-notes-pdf[data-layout="separate-page"] { + position: relative; + color: inherit; + background-color: transparent; + padding: 20px; + page-break-after: always; + border: 0; + } + + /* Display slide numbers when 'slideNumber' is enabled */ + .reveal .slide-number-pdf { + display: block; + position: absolute; + font-size: 14px; + } + + /* This accessibility tool is not useful in PDF and breaks it visually */ + .aria-status { + display: none; + } +} + + /********************************************* * PRINT STYLES *********************************************/ diff --git a/js/controllers/notes.js b/js/controllers/notes.js index 7256425e..03e09d53 100644 --- a/js/controllers/notes.js +++ b/js/controllers/notes.js @@ -38,7 +38,7 @@ export default class Notes { */ update() { - if( this.Reveal.getConfig().showNotes && this.element && this.Reveal.getCurrentSlide() && !this.Reveal.print.isPrintingPDF() ) { + if( this.Reveal.getConfig().showNotes && this.element && this.Reveal.getCurrentSlide() && !this.Reveal.reader.isActive() ) { this.element.innerHTML = this.getSlideNotes() || 'No notes on this slide.'; @@ -54,7 +54,7 @@ export default class Notes { */ updateVisibility() { - if( this.Reveal.getConfig().showNotes && this.hasNotes() && !this.Reveal.print.isPrintingPDF() ) { + if( this.Reveal.getConfig().showNotes && this.hasNotes() && !this.Reveal.reader.isActive() ) { this.Reveal.getRevealElement().classList.add( 'show-notes' ); } else { diff --git a/js/controllers/print.js b/js/controllers/reader.js similarity index 81% rename from js/controllers/print.js rename to js/controllers/reader.js index 4214519f..6784bfb7 100644 --- a/js/controllers/print.js +++ b/js/controllers/reader.js @@ -4,7 +4,7 @@ import { queryAll, createStyleSheet } from '../utils/util.js' /** * Setups up our presentation for printing/exporting to PDF. */ -export default class Print { +export default class Reader { constructor( Reveal ) { @@ -13,10 +13,11 @@ export default class Print { } /** - * Configures the presentation for printing to a static - * PDF. + * Configures the presentation for printing to a static. */ - async setupPDF() { + async setup() { + + const printing = this.isPrintMode(); const config = this.Reveal.getConfig(); const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ) @@ -28,11 +29,11 @@ export default class Print { // Dimensions of the PDF pages const pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ), - pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) ); + pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) ); // Dimensions of slides within the pages const slideWidth = slideSize.width, - slideHeight = slideSize.height; + slideHeight = slideSize.height; await new Promise( requestAnimationFrame ); @@ -42,9 +43,14 @@ export default class Print { // Limit the size of certain elements to the dimensions of the slide createStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' ); - document.documentElement.classList.add( 'print-pdf' ); - document.body.style.width = pageWidth + 'px'; - document.body.style.height = pageHeight + 'px'; + if( printing ) { + document.documentElement.classList.add( 'reveal-print', 'print-pdf' ); + document.body.style.width = pageWidth + 'px'; + document.body.style.height = pageHeight + 'px'; + } + else { + document.documentElement.classList.add( 'reveal-reader' ); + } const viewportElement = document.querySelector( '.reveal-viewport' ); let presentationBackground; @@ -94,7 +100,7 @@ export default class Print { const page = document.createElement( 'div' ); pages.push( page ); - page.className = 'pdf-page'; + page.className = printing ? 'pdf-page' : 'reader-page'; page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px'; // Copy the presentation-wide background to each individual @@ -106,8 +112,11 @@ export default class Print { page.appendChild( slide ); // Position the slide inside of the page - slide.style.left = left + 'px'; - slide.style.top = top + 'px'; + if( printing ) { + slide.style.left = left + 'px'; + slide.style.top = top + 'px'; + } + slide.style.width = slideWidth + 'px'; this.Reveal.slideContent.layout( slide ); @@ -213,6 +222,9 @@ export default class Print { }, this ); + // Remove leftover stacks + queryAll( pageContainer, '.reveal .stack' ).forEach( stack => stack.remove() ); + await new Promise( requestAnimationFrame ); pages.forEach( page => pageContainer.appendChild( page ) ); @@ -220,17 +232,43 @@ export default class Print { // Re-run JS-based content layout after the slide is added to page DOM this.Reveal.slideContent.layout( this.Reveal.getSlidesElement() ); - // Notify subscribers that the PDF layout is good to go - this.Reveal.dispatchEvent({ type: 'pdf-ready' }); + if( printing ) { + // Notify subscribers that the PDF layout is good to go + this.Reveal.dispatchEvent({ type: 'pdf-ready' }); + } } /** - * Checks if this instance is being used to print a PDF. + * Checks if reveal.js was initialized in printing mode. */ - isPrintingPDF() { + isPrintMode() { - return ( /print-pdf/gi ).test( window.location.search ); + if( typeof this._isPrintMode === 'undefined' ) { + this._isPrintMode = this.Reveal.getConfig().mode === 'pdf' || + ( /print-pdf/gi ).test( window.location.search ); + } + + return this._isPrintMode; + + } + + /** + * Checks if reveal.js was initialized in reader mode. + */ + isReaderMode() { + + if( typeof this._isReaderMode === 'undefined' ) { + this._isReaderMode = this.Reveal.getConfig().mode === 'reader'; + } + + return this._isReaderMode; + + } + + isActive() { + + return this.isPrintMode() || this.isReaderMode(); } diff --git a/js/reveal.js b/js/reveal.js index 1d191164..58c2f6f3 100644 --- a/js/reveal.js +++ b/js/reveal.js @@ -11,7 +11,7 @@ import Controls from './controllers/controls.js' import Progress from './controllers/progress.js' import Pointer from './controllers/pointer.js' import Plugins from './controllers/plugins.js' -import Print from './controllers/print.js' +import Reader from './controllers/reader.js' import Touch from './controllers/touch.js' import Focus from './controllers/focus.js' import Notes from './controllers/notes.js' @@ -113,7 +113,7 @@ export default function( revealElement, options ) { progress = new Progress( Reveal ), pointer = new Pointer( Reveal ), plugins = new Plugins( Reveal ), - print = new Print( Reveal ), + reader = new Reader( Reveal ), focus = new Focus( Reveal ), touch = new Touch( Reveal ), notes = new Notes( Reveal ); @@ -225,18 +225,25 @@ export default function( revealElement, options ) { }); }, 1 ); - // Special setup and config is required when printing to PDF - if( print.isPrintingPDF() ) { + // Special setup and config is required when initializing a deck + // to be read or printed linearly + if( reader.isPrintMode() || reader.isReaderMode() ) { + removeEventListeners(); + window.addEventListener( 'resize', onWindowResize, false ); + + // Avoid content flickering during layout + revealElement.style.visibility = 'hidden'; + // The document needs to have loaded for the PDF layout // measurements to be accurate if( document.readyState === 'complete' ) { - print.setupPDF(); + reader.setup().then( () => layout() ); } else { window.addEventListener( 'load', () => { - print.setupPDF(); + reader.setup().then( () => layout() ); } ); } } @@ -861,7 +868,7 @@ export default function( revealElement, options ) { */ function layout() { - if( dom.wrapper && !print.isPrintingPDF() ) { + if( dom.wrapper && !reader.isPrintMode() ) { if( !config.disableLayout ) { @@ -901,6 +908,15 @@ export default function( revealElement, options ) { dom.slides.style.right = ''; transformSlides( { layout: '' } ); } + else if( reader.isActive() ) { + dom.slides.style.zoom = ''; + dom.slides.style.left = 'auto'; + dom.slides.style.top = 'auto'; + dom.slides.style.bottom = 'auto'; + dom.slides.style.right = 'auto'; + dom.slides.style.height = 'auto'; + transformSlides( { layout: 'scale('+ scale +')' } ); + } else { dom.slides.style.zoom = ''; dom.slides.style.left = '50%'; @@ -921,7 +937,7 @@ export default function( revealElement, options ) { continue; } - if( config.center || slide.classList.contains( 'center' ) ) { + if( ( config.center || slide.classList.contains( 'center' ) ) && !reader.isActive() ) { // Vertical stacks are not centred since their section // children will be if( slide.classList.contains( 'stack' ) ) { @@ -1597,7 +1613,7 @@ export default function( revealElement, options ) { let slides = Util.queryAll( dom.wrapper, selector ), slidesLength = slides.length; - let printMode = print.isPrintingPDF(); + let printMode = reader.isActive(); let loopedForwards = false; let loopedBackwards = false; @@ -1757,7 +1773,7 @@ export default function( revealElement, options ) { } // All slides need to be visible when exporting to PDF - if( print.isPrintingPDF() ) { + if( reader.isPrintMode() || reader.isReaderMode() ) { viewDistance = Number.MAX_VALUE; } @@ -2696,7 +2712,8 @@ export default function( revealElement, options ) { isSpeakerNotes: notes.isSpeakerNotesWindow.bind( notes ), isOverview: overview.isActive.bind( overview ), isFocused: focus.isFocused.bind( focus ), - isPrintingPDF: print.isPrintingPDF.bind( print ), + isReaderMode: reader.isReaderMode.bind( reader ), + isPrintingPDF: reader.isPrintMode.bind( reader ), // Checks if reveal.js has been loaded and is ready for use isReady: () => ready, @@ -2816,8 +2833,8 @@ export default function( revealElement, options ) { getStatusText, // Controllers - print, focus, + reader, progress, controls, location,