diff options
Diffstat (limited to 'presentation/js/controllers/print.js')
-rw-r--r-- | presentation/js/controllers/print.js | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/presentation/js/controllers/print.js b/presentation/js/controllers/print.js new file mode 100644 index 0000000..5bedeae --- /dev/null +++ b/presentation/js/controllers/print.js @@ -0,0 +1,212 @@ +import { SLIDES_SELECTOR } from '../utils/constants.js' +import { queryAll, createStyleSheet } from '../utils/util.js' + +/** + * Setups up our presentation for printing/exporting to PDF. + */ +export default class Print { + + constructor( Reveal ) { + + this.Reveal = Reveal; + + } + + /** + * Configures the presentation for printing to a static + * PDF. + */ + async setupPDF() { + + const config = this.Reveal.getConfig(); + const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ) + + // Compute slide numbers now, before we start duplicating slides + const doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); + + const slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight ); + + // Dimensions of the PDF pages + const pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ), + pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) ); + + // Dimensions of slides within the pages + const slideWidth = slideSize.width, + slideHeight = slideSize.height; + + await new Promise( requestAnimationFrame ); + + // Let the browser know what page size we want to print + createStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' ); + + // 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'; + + // Make sure stretch elements fit on slide + await new Promise( requestAnimationFrame ); + this.Reveal.layoutSlideContents( slideWidth, slideHeight ); + + // Re-run the slide layout so that r-fit-text is applied based on + // the printed slide size + slides.forEach( slide => this.Reveal.slideContent.layout( slide ) ); + + // Batch scrollHeight access to prevent layout thrashing + await new Promise( requestAnimationFrame ); + + const slideScrollHeights = slides.map( slide => slide.scrollHeight ); + + const pages = []; + const pageContainer = slides[0].parentNode; + + // Slide and slide background layout + slides.forEach( function( slide, index ) { + + // Vertical stacks are not centred since their section + // children will be + if( slide.classList.contains( 'stack' ) === false ) { + // Center the slide inside of the page, giving the slide some margin + let left = ( pageWidth - slideWidth ) / 2; + let top = ( pageHeight - slideHeight ) / 2; + + const contentHeight = slideScrollHeights[ index ]; + let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 ); + + // Adhere to configured pages per slide limit + numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide ); + + // Center slides vertically + if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) { + top = Math.max( ( pageHeight - contentHeight ) / 2, 0 ); + } + + // Wrap the slide in a page element and hide its overflow + // so that no page ever flows onto another + const page = document.createElement( 'div' ); + pages.push( page ); + + page.className = 'pdf-page'; + page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px'; + page.appendChild( slide ); + + // Position the slide inside of the page + slide.style.left = left + 'px'; + slide.style.top = top + 'px'; + slide.style.width = slideWidth + 'px'; + + if( slide.slideBackgroundElement ) { + page.insertBefore( slide.slideBackgroundElement, slide ); + } + + // Inject notes if `showNotes` is enabled + if( config.showNotes ) { + + // Are there notes for this slide? + const notes = this.Reveal.getSlideNotes( slide ); + if( notes ) { + + const notesSpacing = 8; + const notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline'; + const notesElement = document.createElement( 'div' ); + notesElement.classList.add( 'speaker-notes' ); + notesElement.classList.add( 'speaker-notes-pdf' ); + notesElement.setAttribute( 'data-layout', notesLayout ); + notesElement.innerHTML = notes; + + if( notesLayout === 'separate-page' ) { + pages.push( notesElement ); + } + else { + notesElement.style.left = notesSpacing + 'px'; + notesElement.style.bottom = notesSpacing + 'px'; + notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px'; + page.appendChild( notesElement ); + } + + } + + } + + // Inject slide numbers if `slideNumbers` are enabled + if( doingSlideNumbers ) { + const slideNumber = index + 1; + const numberElement = document.createElement( 'div' ); + numberElement.classList.add( 'slide-number' ); + numberElement.classList.add( 'slide-number-pdf' ); + numberElement.innerHTML = slideNumber; + page.appendChild( numberElement ); + } + + // Copy page and show fragments one after another + if( config.pdfSeparateFragments ) { + + // Each fragment 'group' is an array containing one or more + // fragments. Multiple fragments that appear at the same time + // are part of the same group. + const fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true ); + + let previousFragmentStep; + + fragmentGroups.forEach( function( fragments ) { + + // Remove 'current-fragment' from the previous group + if( previousFragmentStep ) { + previousFragmentStep.forEach( function( fragment ) { + fragment.classList.remove( 'current-fragment' ); + } ); + } + + // Show the fragments for the current index + fragments.forEach( function( fragment ) { + fragment.classList.add( 'visible', 'current-fragment' ); + }, this ); + + // Create a separate page for the current fragment state + const clonedPage = page.cloneNode( true ); + pages.push( clonedPage ); + + previousFragmentStep = fragments; + + }, this ); + + // Reset the first/original page so that all fragments are hidden + fragmentGroups.forEach( function( fragments ) { + fragments.forEach( function( fragment ) { + fragment.classList.remove( 'visible', 'current-fragment' ); + } ); + } ); + + } + // Show all fragments + else { + queryAll( page, '.fragment:not(.fade-out)' ).forEach( function( fragment ) { + fragment.classList.add( 'visible' ); + } ); + } + + } + + }, this ); + + await new Promise( requestAnimationFrame ); + + pages.forEach( page => pageContainer.appendChild( page ) ); + + // 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. + */ + isPrintingPDF() { + + return ( /print-pdf/gi ).test( window.location.search ); + + } + +} |