| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 1 | import { SLIDES_SELECTOR } from '../utils/constants.js' | 
|  | 2 | import { queryAll, createStyleSheet } from '../utils/util.js' | 
|  | 3 |  | 
|  | 4 | /** | 
|  | 5 | * Setups up our presentation for printing/exporting to PDF. | 
|  | 6 | */ | 
|  | 7 | export default class Print { | 
|  | 8 |  | 
|  | 9 | constructor( Reveal ) { | 
|  | 10 |  | 
|  | 11 | this.Reveal = Reveal; | 
|  | 12 |  | 
|  | 13 | } | 
|  | 14 |  | 
|  | 15 | /** | 
|  | 16 | * Configures the presentation for printing to a static | 
|  | 17 | * PDF. | 
|  | 18 | */ | 
|  | 19 | async setupPDF() { | 
|  | 20 |  | 
|  | 21 | const config = this.Reveal.getConfig(); | 
|  | 22 | const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ) | 
|  | 23 |  | 
|  | 24 | // Compute slide numbers now, before we start duplicating slides | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 25 | const injectPageNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 26 |  | 
|  | 27 | const slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight ); | 
|  | 28 |  | 
|  | 29 | // Dimensions of the PDF pages | 
|  | 30 | const pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ), | 
|  | 31 | pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) ); | 
|  | 32 |  | 
|  | 33 | // Dimensions of slides within the pages | 
|  | 34 | const slideWidth = slideSize.width, | 
|  | 35 | slideHeight = slideSize.height; | 
|  | 36 |  | 
|  | 37 | await new Promise( requestAnimationFrame ); | 
|  | 38 |  | 
|  | 39 | // Let the browser know what page size we want to print | 
|  | 40 | createStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' ); | 
|  | 41 |  | 
|  | 42 | // Limit the size of certain elements to the dimensions of the slide | 
|  | 43 | createStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' ); | 
|  | 44 |  | 
|  | 45 | document.documentElement.classList.add( 'print-pdf' ); | 
|  | 46 | document.body.style.width = pageWidth + 'px'; | 
|  | 47 | document.body.style.height = pageHeight + 'px'; | 
|  | 48 |  | 
| Christophe Dervieux | 8afae13 | 2021-12-06 15:16:42 +0100 | [diff] [blame] | 49 | const viewportElement = document.querySelector( '.reveal-viewport' ); | 
|  | 50 | let presentationBackground; | 
|  | 51 | if( viewportElement ) { | 
|  | 52 | const viewportStyles = window.getComputedStyle( viewportElement ); | 
|  | 53 | if( viewportStyles && viewportStyles.background ) { | 
|  | 54 | presentationBackground = viewportStyles.background; | 
|  | 55 | } | 
|  | 56 | } | 
|  | 57 |  | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 58 | // Make sure stretch elements fit on slide | 
|  | 59 | await new Promise( requestAnimationFrame ); | 
|  | 60 | this.Reveal.layoutSlideContents( slideWidth, slideHeight ); | 
|  | 61 |  | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 62 | // Batch scrollHeight access to prevent layout thrashing | 
|  | 63 | await new Promise( requestAnimationFrame ); | 
|  | 64 |  | 
|  | 65 | const slideScrollHeights = slides.map( slide => slide.scrollHeight ); | 
|  | 66 |  | 
|  | 67 | const pages = []; | 
|  | 68 | const pageContainer = slides[0].parentNode; | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 69 | let slideNumber = 1; | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 70 |  | 
|  | 71 | // Slide and slide background layout | 
|  | 72 | slides.forEach( function( slide, index ) { | 
|  | 73 |  | 
|  | 74 | // Vertical stacks are not centred since their section | 
|  | 75 | // children will be | 
|  | 76 | if( slide.classList.contains( 'stack' ) === false ) { | 
|  | 77 | // Center the slide inside of the page, giving the slide some margin | 
|  | 78 | let left = ( pageWidth - slideWidth ) / 2; | 
|  | 79 | let top = ( pageHeight - slideHeight ) / 2; | 
|  | 80 |  | 
|  | 81 | const contentHeight = slideScrollHeights[ index ]; | 
|  | 82 | let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 ); | 
|  | 83 |  | 
|  | 84 | // Adhere to configured pages per slide limit | 
|  | 85 | numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide ); | 
|  | 86 |  | 
|  | 87 | // Center slides vertically | 
|  | 88 | if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) { | 
|  | 89 | top = Math.max( ( pageHeight - contentHeight ) / 2, 0 ); | 
|  | 90 | } | 
|  | 91 |  | 
|  | 92 | // Wrap the slide in a page element and hide its overflow | 
|  | 93 | // so that no page ever flows onto another | 
|  | 94 | const page = document.createElement( 'div' ); | 
|  | 95 | pages.push( page ); | 
|  | 96 |  | 
|  | 97 | page.className = 'pdf-page'; | 
|  | 98 | page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px'; | 
| Christophe Dervieux | 8afae13 | 2021-12-06 15:16:42 +0100 | [diff] [blame] | 99 |  | 
|  | 100 | // Copy the presentation-wide background to each individual | 
|  | 101 | // page when printing | 
|  | 102 | if( presentationBackground ) { | 
|  | 103 | page.style.background = presentationBackground; | 
|  | 104 | } | 
|  | 105 |  | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 106 | page.appendChild( slide ); | 
|  | 107 |  | 
|  | 108 | // Position the slide inside of the page | 
|  | 109 | slide.style.left = left + 'px'; | 
|  | 110 | slide.style.top = top + 'px'; | 
|  | 111 | slide.style.width = slideWidth + 'px'; | 
|  | 112 |  | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 113 | this.Reveal.slideContent.layout( slide ); | 
| Christophe Dervieux | 8afae13 | 2021-12-06 15:16:42 +0100 | [diff] [blame] | 114 |  | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 115 | if( slide.slideBackgroundElement ) { | 
|  | 116 | page.insertBefore( slide.slideBackgroundElement, slide ); | 
|  | 117 | } | 
|  | 118 |  | 
|  | 119 | // Inject notes if `showNotes` is enabled | 
|  | 120 | if( config.showNotes ) { | 
|  | 121 |  | 
|  | 122 | // Are there notes for this slide? | 
|  | 123 | const notes = this.Reveal.getSlideNotes( slide ); | 
|  | 124 | if( notes ) { | 
|  | 125 |  | 
|  | 126 | const notesSpacing = 8; | 
|  | 127 | const notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline'; | 
|  | 128 | const notesElement = document.createElement( 'div' ); | 
|  | 129 | notesElement.classList.add( 'speaker-notes' ); | 
|  | 130 | notesElement.classList.add( 'speaker-notes-pdf' ); | 
|  | 131 | notesElement.setAttribute( 'data-layout', notesLayout ); | 
|  | 132 | notesElement.innerHTML = notes; | 
|  | 133 |  | 
|  | 134 | if( notesLayout === 'separate-page' ) { | 
|  | 135 | pages.push( notesElement ); | 
|  | 136 | } | 
|  | 137 | else { | 
|  | 138 | notesElement.style.left = notesSpacing + 'px'; | 
|  | 139 | notesElement.style.bottom = notesSpacing + 'px'; | 
|  | 140 | notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px'; | 
|  | 141 | page.appendChild( notesElement ); | 
|  | 142 | } | 
|  | 143 |  | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | } | 
|  | 147 |  | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 148 | // Inject page numbers if `slideNumbers` are enabled | 
|  | 149 | if( injectPageNumbers ) { | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 150 | const numberElement = document.createElement( 'div' ); | 
|  | 151 | numberElement.classList.add( 'slide-number' ); | 
|  | 152 | numberElement.classList.add( 'slide-number-pdf' ); | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 153 | numberElement.innerHTML = slideNumber++; | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 154 | page.appendChild( numberElement ); | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 | // Copy page and show fragments one after another | 
|  | 158 | if( config.pdfSeparateFragments ) { | 
|  | 159 |  | 
|  | 160 | // Each fragment 'group' is an array containing one or more | 
|  | 161 | // fragments. Multiple fragments that appear at the same time | 
|  | 162 | // are part of the same group. | 
|  | 163 | const fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true ); | 
|  | 164 |  | 
|  | 165 | let previousFragmentStep; | 
|  | 166 |  | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 167 | fragmentGroups.forEach( function( fragments, index ) { | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 168 |  | 
|  | 169 | // Remove 'current-fragment' from the previous group | 
|  | 170 | if( previousFragmentStep ) { | 
|  | 171 | previousFragmentStep.forEach( function( fragment ) { | 
|  | 172 | fragment.classList.remove( 'current-fragment' ); | 
|  | 173 | } ); | 
|  | 174 | } | 
|  | 175 |  | 
|  | 176 | // Show the fragments for the current index | 
|  | 177 | fragments.forEach( function( fragment ) { | 
|  | 178 | fragment.classList.add( 'visible', 'current-fragment' ); | 
|  | 179 | }, this ); | 
|  | 180 |  | 
|  | 181 | // Create a separate page for the current fragment state | 
|  | 182 | const clonedPage = page.cloneNode( true ); | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 183 |  | 
|  | 184 | // Inject unique page numbers for fragments | 
|  | 185 | if( injectPageNumbers ) { | 
|  | 186 | const numberElement = clonedPage.querySelector( '.slide-number-pdf' ); | 
|  | 187 | const fragmentNumber = index + 1; | 
|  | 188 | numberElement.innerHTML += '.' + fragmentNumber; | 
|  | 189 | } | 
|  | 190 |  | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 191 | pages.push( clonedPage ); | 
|  | 192 |  | 
|  | 193 | previousFragmentStep = fragments; | 
|  | 194 |  | 
|  | 195 | }, this ); | 
|  | 196 |  | 
|  | 197 | // Reset the first/original page so that all fragments are hidden | 
|  | 198 | fragmentGroups.forEach( function( fragments ) { | 
|  | 199 | fragments.forEach( function( fragment ) { | 
|  | 200 | fragment.classList.remove( 'visible', 'current-fragment' ); | 
|  | 201 | } ); | 
|  | 202 | } ); | 
|  | 203 |  | 
|  | 204 | } | 
|  | 205 | // Show all fragments | 
|  | 206 | else { | 
|  | 207 | queryAll( page, '.fragment:not(.fade-out)' ).forEach( function( fragment ) { | 
|  | 208 | fragment.classList.add( 'visible' ); | 
|  | 209 | } ); | 
|  | 210 | } | 
|  | 211 |  | 
|  | 212 | } | 
|  | 213 |  | 
|  | 214 | }, this ); | 
|  | 215 |  | 
|  | 216 | await new Promise( requestAnimationFrame ); | 
|  | 217 |  | 
|  | 218 | pages.forEach( page => pageContainer.appendChild( page ) ); | 
|  | 219 |  | 
| Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 220 | // Re-run JS-based content layout after the slide is added to page DOM | 
|  | 221 | this.Reveal.slideContent.layout( this.Reveal.getSlidesElement() ); | 
|  | 222 |  | 
| Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 223 | // Notify subscribers that the PDF layout is good to go | 
|  | 224 | this.Reveal.dispatchEvent({ type: 'pdf-ready' }); | 
|  | 225 |  | 
|  | 226 | } | 
|  | 227 |  | 
|  | 228 | /** | 
|  | 229 | * Checks if this instance is being used to print a PDF. | 
|  | 230 | */ | 
|  | 231 | isPrintingPDF() { | 
|  | 232 |  | 
|  | 233 | return ( /print-pdf/gi ).test( window.location.search ); | 
|  | 234 |  | 
|  | 235 | } | 
|  | 236 |  | 
|  | 237 | } |