merge #2843 with minor tweaks

This commit is contained in:
Hakim El Hattab 2021-05-05 11:02:02 +02:00
commit 892c752a4b
6 changed files with 76 additions and 56 deletions

View File

@ -458,6 +458,9 @@ Reveal.on( 'customevent', function() {
<script src="plugin/highlight/highlight.js"></script> <script src="plugin/highlight/highlight.js"></script>
<script> <script>
console.time( 'print' )
Reveal.addEventListener( 'pdf-ready', () => console.timeEnd( 'print' ) );
// Also available as an ES module, see: // Also available as an ES module, see:
// https://revealjs.com/initialization/ // https://revealjs.com/initialization/
Reveal.initialize({ Reveal.initialize({

2
dist/reveal.esm.js vendored

File diff suppressed because one or more lines are too long

2
dist/reveal.js vendored

File diff suppressed because one or more lines are too long

View File

@ -27,8 +27,6 @@ export default class Backgrounds {
*/ */
create() { create() {
let printMode = this.Reveal.isPrintingPDF();
// Clear prior backgrounds // Clear prior backgrounds
this.element.innerHTML = ''; this.element.innerHTML = '';
this.element.classList.add( 'no-transition' ); this.element.classList.add( 'no-transition' );
@ -114,9 +112,24 @@ export default class Backgrounds {
*/ */
sync( slide ) { sync( slide ) {
let element = slide.slideBackgroundElement, const element = slide.slideBackgroundElement,
contentElement = slide.slideBackgroundContentElement; contentElement = slide.slideBackgroundContentElement;
const data = {
background: slide.getAttribute( 'data-background' ),
backgroundSize: slide.getAttribute( 'data-background-size' ),
backgroundImage: slide.getAttribute( 'data-background-image' ),
backgroundVideo: slide.getAttribute( 'data-background-video' ),
backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
backgroundColor: slide.getAttribute( 'data-background-color' ),
backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
backgroundPosition: slide.getAttribute( 'data-background-position' ),
backgroundTransition: slide.getAttribute( 'data-background-transition' ),
backgroundOpacity: slide.getAttribute( 'data-background-opacity' ),
};
const dataPreload = slide.hasAttribute( 'data-preload' );
// Reset the prior background state in case this is not the // Reset the prior background state in case this is not the
// initial sync // initial sync
slide.classList.remove( 'has-dark-background' ); slide.classList.remove( 'has-dark-background' );
@ -135,19 +148,6 @@ export default class Backgrounds {
contentElement.style.opacity = ''; contentElement.style.opacity = '';
contentElement.innerHTML = ''; contentElement.innerHTML = '';
let data = {
background: slide.getAttribute( 'data-background' ),
backgroundSize: slide.getAttribute( 'data-background-size' ),
backgroundImage: slide.getAttribute( 'data-background-image' ),
backgroundVideo: slide.getAttribute( 'data-background-video' ),
backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
backgroundColor: slide.getAttribute( 'data-background-color' ),
backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
backgroundPosition: slide.getAttribute( 'data-background-position' ),
backgroundTransition: slide.getAttribute( 'data-background-transition' ),
backgroundOpacity: slide.getAttribute( 'data-background-opacity' )
};
if( data.background ) { if( data.background ) {
// Auto-wrap image urls in url(...) // Auto-wrap image urls in url(...)
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) { if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) {
@ -179,7 +179,7 @@ export default class Backgrounds {
if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
if( slide.hasAttribute( 'data-preload' ) ) element.setAttribute( 'data-preload', '' ); if( dataPreload ) element.setAttribute( 'data-preload', '' );
// Background image options are set on the content wrapper // Background image options are set on the content wrapper
if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize; if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
@ -201,7 +201,7 @@ export default class Backgrounds {
} }
if( contrastColor ) { if( contrastColor ) {
let rgb = colorToRgb( contrastColor ); const rgb = colorToRgb( contrastColor );
// Ignore fully transparent backgrounds. Some browsers return // Ignore fully transparent backgrounds. Some browsers return
// rgba(0,0,0,0) when reading the computed background color of // rgba(0,0,0,0) when reading the computed background color of

View File

@ -16,20 +16,26 @@ export default class Print {
* Configures the presentation for printing to a static * Configures the presentation for printing to a static
* PDF. * PDF.
*/ */
setupPDF() { async setupPDF() {
let config = this.Reveal.getConfig(); const config = this.Reveal.getConfig();
const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR )
let slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight ); // 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 // Dimensions of the PDF pages
let pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ), 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 // Dimensions of slides within the pages
let slideWidth = slideSize.width, const slideWidth = slideSize.width,
slideHeight = slideSize.height; slideHeight = slideSize.height;
await new Promise( requestAnimationFrame );
// Let the browser know what page size we want to print // Let the browser know what page size we want to print
createStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' ); createStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );
@ -41,29 +47,32 @@ export default class Print {
document.body.style.height = pageHeight + 'px'; document.body.style.height = pageHeight + 'px';
// Make sure stretch elements fit on slide // Make sure stretch elements fit on slide
await new Promise( requestAnimationFrame );
this.Reveal.layoutSlideContents( slideWidth, slideHeight ); this.Reveal.layoutSlideContents( slideWidth, slideHeight );
// Compute slide numbers now, before we start duplicating slides // Re-run the slide layout so that r-fit-text is applied based on
let doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); // the printed slide size
queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( function( slide ) { slides.forEach( slide => this.Reveal.slideContent.layout( slide ) );
slide.setAttribute( 'data-slide-number', this.Reveal.slideNumber.getSlideNumber( slide ) );
}, this ); // 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 // Slide and slide background layout
queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( function( slide ) { slides.forEach( function( slide, index ) {
// Vertical stacks are not centred since their section // Vertical stacks are not centred since their section
// children will be // children will be
if( slide.classList.contains( 'stack' ) === false ) { if( slide.classList.contains( 'stack' ) === false ) {
// Center the slide inside of the page, giving the slide some margin // Center the slide inside of the page, giving the slide some margin
let left = ( pageWidth - slideWidth ) / 2, let left = ( pageWidth - slideWidth ) / 2;
top = ( pageHeight - slideHeight ) / 2; let top = ( pageHeight - slideHeight ) / 2;
// Re-run the slide layout so that r-fit-text is applied based on const contentHeight = slideScrollHeights[ index ];
// the printed slide size
this.Reveal.slideContent.layout( slide );
let contentHeight = slide.scrollHeight;
let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 ); let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
// Adhere to configured pages per slide limit // Adhere to configured pages per slide limit
@ -76,10 +85,11 @@ export default class Print {
// Wrap the slide in a page element and hide its overflow // Wrap the slide in a page element and hide its overflow
// so that no page ever flows onto another // so that no page ever flows onto another
let page = document.createElement( 'div' ); const page = document.createElement( 'div' );
pages.push( page );
page.className = 'pdf-page'; page.className = 'pdf-page';
page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px'; page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';
slide.parentNode.insertBefore( page, slide );
page.appendChild( slide ); page.appendChild( slide );
// Position the slide inside of the page // Position the slide inside of the page
@ -95,19 +105,19 @@ export default class Print {
if( config.showNotes ) { if( config.showNotes ) {
// Are there notes for this slide? // Are there notes for this slide?
let notes = this.Reveal.getSlideNotes( slide ); const notes = this.Reveal.getSlideNotes( slide );
if( notes ) { if( notes ) {
let notesSpacing = 8; const notesSpacing = 8;
let notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline'; const notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';
let notesElement = document.createElement( 'div' ); const notesElement = document.createElement( 'div' );
notesElement.classList.add( 'speaker-notes' ); notesElement.classList.add( 'speaker-notes' );
notesElement.classList.add( 'speaker-notes-pdf' ); notesElement.classList.add( 'speaker-notes-pdf' );
notesElement.setAttribute( 'data-layout', notesLayout ); notesElement.setAttribute( 'data-layout', notesLayout );
notesElement.innerHTML = notes; notesElement.innerHTML = notes;
if( notesLayout === 'separate-page' ) { if( notesLayout === 'separate-page' ) {
page.parentNode.insertBefore( notesElement, page.nextSibling ); pages.push( notesElement );
} }
else { else {
notesElement.style.left = notesSpacing + 'px'; notesElement.style.left = notesSpacing + 'px';
@ -122,10 +132,11 @@ export default class Print {
// Inject slide numbers if `slideNumbers` are enabled // Inject slide numbers if `slideNumbers` are enabled
if( doingSlideNumbers ) { if( doingSlideNumbers ) {
let numberElement = document.createElement( 'div' ); const slideNumber = index + 1;
const numberElement = document.createElement( 'div' );
numberElement.classList.add( 'slide-number' ); numberElement.classList.add( 'slide-number' );
numberElement.classList.add( 'slide-number-pdf' ); numberElement.classList.add( 'slide-number-pdf' );
numberElement.innerHTML = slide.getAttribute( 'data-slide-number' ); numberElement.innerHTML = slideNumber;
page.appendChild( numberElement ); page.appendChild( numberElement );
} }
@ -135,10 +146,9 @@ export default class Print {
// Each fragment 'group' is an array containing one or more // Each fragment 'group' is an array containing one or more
// fragments. Multiple fragments that appear at the same time // fragments. Multiple fragments that appear at the same time
// are part of the same group. // are part of the same group.
let fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true ); const fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true );
let previousFragmentStep; let previousFragmentStep;
let previousPage;
fragmentGroups.forEach( function( fragments ) { fragmentGroups.forEach( function( fragments ) {
@ -155,11 +165,10 @@ export default class Print {
}, this ); }, this );
// Create a separate page for the current fragment state // Create a separate page for the current fragment state
let clonedPage = page.cloneNode( true ); const clonedPage = page.cloneNode( true );
page.parentNode.insertBefore( clonedPage, ( previousPage || page ).nextSibling ); pages.push( clonedPage );
previousFragmentStep = fragments; previousFragmentStep = fragments;
previousPage = clonedPage;
}, this ); }, this );
@ -182,6 +191,10 @@ export default class Print {
}, this ); }, this );
await new Promise( requestAnimationFrame );
pages.forEach( page => pageContainer.appendChild( page ) );
// Notify subscribers that the PDF layout is good to go // Notify subscribers that the PDF layout is good to go
this.Reveal.dispatchEvent({ type: 'pdf-ready' }); this.Reveal.dispatchEvent({ type: 'pdf-ready' });

View File

@ -1335,7 +1335,11 @@ export default function( revealElement, options ) {
} }
// Announce the current slide contents to screen readers // Announce the current slide contents to screen readers
announceStatus( getStatusText( currentSlide ) ); // Use animation frame to prevent getComputedStyle in getStatusText
// from triggering layout mid-frame
requestAnimationFrame( () => {
announceStatus( getStatusText( currentSlide ) );
});
progress.update(); progress.update();
controls.update(); controls.update();
@ -2290,7 +2294,7 @@ export default function( revealElement, options ) {
// When looping is enabled `routes.down` is always available // When looping is enabled `routes.down` is always available
// so we need a separate check for when we've reached the // so we need a separate check for when we've reached the
// end of a stack and should move horizontally // end of a stack and should move horizontally
if( routes.down && routes.right && config.loop && isLastVerticalSlide( currentSlide ) ) { if( routes.down && routes.right && config.loop && isLastVerticalSlide() ) {
routes.down = false; routes.down = false;
} }