slidev
slidev copied to clipboard
feat: PDF handout (slides on top, notes on bottom of page)
This pull request introduces a new option for slide export. It allows for the creation of an A4 page with the slide on the top and the speaker notes on the bottom.
The footer of the pages is customizable using a custom component handout-bottom.vue
, similar to global-bottom.vue
for slides.
Optionally, it can create cover pages by passing the --cover
option, which then renders another custom component handout-cover.vue
. Both of these components need to be in your theme or project root.
I have added some example components to the starter.
Example Usage
cd demo/starter
node node_modules/@slidev/cli/bin/slidev.js export --handout --cover
The code creates 2 temporary files:
- A cover PDF (also accessible via /cover)
- A temporary handout PDF (also accessible via /handout), which forms the bottom part of the final handout
It then prepends the cover and merges slides and handouts. In this way, we can reuse the slide generation code. However, this breaks hyperlinks. Therefore, I have ensured to perform all necessary calculations to make them clickable also in the final PDF.
The final file will be: handout-slides-export.pdf
Caveat
~~This feature requires the inclusion of this pull request to work properly: https://github.com/slidevjs/slidev/pull/1419 Without it, each click exports a slide, which then desynchronizes the merge with the notes causing errors.~~
Possible enhancements.
Terminology
Since the term "notes" was already taken, I called the API "handout", but it might be confusing, as /handout is just a helper page to generate the lower part of the final handout and will be merged with the slides to create the final handout.
Fix pageNumber
param
Somehow the pageNumber param does not get passed to HandoutBottom here: <HandoutBottom :pageNumber="index + 100" /> in PrintHandout.vue. This may be due to the way the component is imported. It would be nice to have, because for now, the page numbering is hardcoded in Slidev and not customizable.
Configure output name
Allow configuring the output name of the handout
Deploy Preview for slidev ready!
Name | Link |
---|---|
Latest commit | 3e20b974e39405d5b9b180758cabb45f7d67e1c7 |
Latest deploy log | https://app.netlify.com/sites/slidev/deploys/661e1f5074cf8900082f8394 |
Deploy Preview | https://deploy-preview-1421--slidev.netlify.app |
Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. |
To edit notification comments on pull requests, go to your Netlify site configuration.
Thanks for your contribution! I think this feature is quite useful. btw, the commits I pushed earlier contain no actual function-related changes.
Thanks for having a look at it and cleaning it up. I really appreciate it. I fetched your changes and seems to work.
Hello, there is an almost finished PR #1513 which allows users to provide their own print templates. Would that help?
@KermanX I synced the current pull request with main. Unfortunately, I did not get around yet to implement the changes you mentioned.
Hello, there is an almost finished PR #1513 which allows users to provide their own print templates. Would that help?
Ok, thanks for the info, I will have a look how to implement it using this PR.
Hello, there is an almost finished PR #1513 which allows users to provide their own print templates. Would that help?
So I am starting to use this the mentioned PR, works but I have one question and two issue I am stuck with.
-
How can I debug export easier, before I could go to /handout in the browser (with my code) but now that the --template parameter was introduced I don't understand how to do live debugging, as --template is only accepted when exporting and not when running in dev mode (so /print in dev mode is always the default print), so one always has to do a new export run to see the result. Makes development a bit painful.
-
This is a template I am working on, I am stuck on how to properly scale the slide to fit to the A4 page with margins etc. it always ends up looking bad as content is overflowing. I am sure I use the wrong way to do that (
slideWidth.value
,provideLocal(injectionSlideScale, computed(() => 0.7))
.
<script setup lang="ts">
import { computed } from 'vue'
import { useNav } from '../../composables/useNav'
import NoteDisplay from '../../internals/NoteDisplay.vue'
import { usePrintStyle } from '../../composables/usePrintStyle'
import PrintSlide from '../../internals/PrintSlide.vue'
import { provideLocal } from '@vueuse/core'
import { injectionSlideScale } from '../../constants'
import { useStyleTag } from '@vueuse/core'
const { slidesToPrint, total } = useNav()
import { slideWidth } from '../../env'
slideWidth.value = 740
provideLocal(injectionSlideScale, computed(() => 1))
</script>
<template>
<div class="w-full px-[1px]"> <!--1px helps to avoid cutting off a tiny bit of content by margins-->
<div v-for="(slide, index) of slidesToPrint" :key="slide.no"
class="mx-auto w-full flex flex-col gap-4 break-inside-avoid-page break-after-page">
<div>
<div class="relative border-1 border-red w-full">
<PrintSlide :key="slide.no" :route="slide" class="mx-auto w-full border-0.5 border-black" />
</div>
<div class="flex flex-col w-full mt-12">
<div class="h-124 border-red border-2">
<NoteDisplay v-if="slide.meta.slide.noteHTML" :note-html="slide.meta.slide.noteHTML"
class="max-w-full h-full" />
</div>
<div class="w-full flex flex-col gap-2">
<div class="h-[1px] bg-black w-full" />
<div class="mt-2 relative">
<div class="top-3.0 absolute left-0 !text-[11px]">© {{ new Date().getFullYear() }} Company
</div>
<div class="top-3.0 absolute right-0 !text-[11px]">Title - Version</div>
</div>
<div class="text-right text-[11px] mt-6">
{{ index + 1 }} / {{ total }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style>
@page {
size: A4;
margin-top: 1.5cm;
margin-bottom: 1cm;
}
/* overwrite the class in PrintSlideClick.vue, we want to break every slide
* 794px fits A4
* 1 px border (2x 0.5)
* 26 px margin
* 740 px slide width
*/
.print-slide-container {
@apply relative overflow-hidden !break-after-avoid translate-0;
}
html.print,
html.print body,
html.print #app {
height: auto;
overflow: auto;
}
html.print #page-root {
height: auto;
overflow: hidden;
}
html.print * {
-webkit-print-color-adjust: exact;
}
html.print {
width: 100%;
height: 100%;
overflow: visible;
}
html.print body {
margin: 0 auto;
border: 0;
padding: 0;
float: none;
overflow: visible;
}
@page {
size: A4;
margin-top: 1.5cm;
margin-bottom: 1.0cm;
margin-left: 1cm;
margin-right: 1cm;
}
</style>
- I also need to overwrite
.print-slide-container
to avoid page breaks after slides. Works but is a bit awkward to do it like that. Maybe this needs to be restructured as well
.print-slide-container {
@apply relative overflow-hidden !break-after-avoid translate-0;
}