141 lines
6 KiB
Markdown
141 lines
6 KiB
Markdown
title: SVG + makefile = slide deck
|
||
summary: "You don't need LibreOffice to prepare your presentation -- or: an introduction to makefiles"
|
||
|
||
---
|
||
|
||
Text-based slides are boring and inefficient.
|
||
While preparing a presentation for my intermediary thesis' defence, I wanted to use graphics instead of words so my audience wouldn't have to read and listen at the same time.
|
||
Rather than fiddling with shapes and images in traditional office software, I knew I'd be less frustrated just using the SVG editor Inkscape.
|
||
And a makefile would be nice to stitch the SVGs together to create a PDF file that I could use during my exposé.
|
||
|
||
## File dependencies with makefile
|
||
Makefiles are commonly used as a shell script with multiple entry points, but they can do much more than that!
|
||
You give it a filename, and it will build that file if its dependencies have been changed.
|
||
Traditionally makefiles are used for C programs, but you can use them for absolutely any build where you have dependencies between files.
|
||
Even this website is built by a makefile.
|
||
Want to join me and write one together?
|
||
|
||
Putting the name of our output file in a variable will make it easy to change it later, if we'd ever want that.
|
||
```makefile
|
||
OUTPUT = slides.pdf
|
||
```
|
||
|
||
We'll make a list of SVGs in the current directory, and convert it into a list of PDF targets.
|
||
Sorting is necessary to put your slides in the order you want.
|
||
The `$(:=)` construct is used to go from `%.svg` to `build/%.pdf`.
|
||
```makefile
|
||
SLIDES ::= $(sort $(wildcard *.svg))
|
||
SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf)
|
||
```
|
||
|
||
I love this next rule.
|
||
It teaches *make* that it should use Inkscape if it needs to create a PDF.
|
||
Inkscape's `--export-pdf filename` option exports to PDF without opening the GUI.
|
||
|
||
`$@` refers to the target (`build/….pdf`), and `$<` refers to the first dependency (`….svg`).
|
||
|
||
Do note, the indentation *must* be a tab; spaces are not allowed.
|
||
|
||
```makefile
|
||
build/%.pdf: %.svg
|
||
inkscape --export-pdf "$@" "$<"
|
||
```
|
||
|
||
Next we tell *make* that the final output can be created from the individual slides (`$(SLIDES_PDF)`) by calling pdfjoin.
|
||
|
||
*Make* will recursively make sure that all dependencies are up to date.
|
||
We already declared the corresponding SVG file as a dependency for each slide's PDF, so *make* will know that it should rebuild them if the SVG was changed.
|
||
And if any PDF was updated, the end result will be, too.
|
||
But if there was no change, it won't; nothing is rebuilt unnecessarily!
|
||
|
||
`$+` means all prerequisites, retaining duplicate entries.
|
||
|
||
**TODO: Do we need to fix sh's space disease here?**
|
||
```makefile
|
||
$(OUTPUT): $(SLIDES_PDF)
|
||
pdfjoin $+ -o "$@"
|
||
```
|
||
|
||
It is common to create a target `clean` to delete output and intermediary files.
|
||
```makefile
|
||
clean:
|
||
rm -rf "$(OUTPUT)" build/
|
||
```
|
||
|
||
If you now create a file named `clean` and run `make clean`, *make* will quite contently say it doesn't need to do anything: the file `clean` exists and has no dependencies that could have been modified.
|
||
To avoid this, non-file targets must be marked as "phony".
|
||
```makefile
|
||
.PHONY: clean
|
||
```
|
||
|
||
# Creating directories
|
||
If you were to use our makefile up to now, Inkscape would fail, complaining that `build/` doesn't exist.
|
||
This is to be expected, since directories have to be created before you can place files in them.
|
||
Much to my dismay, there seems to be no clean, standard way to create the necessary directories in *make*.
|
||
In my opinion, this is *make*’s single biggest shortcoming.
|
||
|
||
There [are a few ways](https://www.cmcrossroads.com/article/making-directories-gnu-make), none of them elegant.
|
||
For our simple makefile, we can just put `@mkdir -p $(@D)` in each rule.
|
||
It's not ideal for performance, **TODO and it might give race conditions if there are more directories and rules, should think how to word this and stuff when less tired**
|
||
You'll have noticed by default *make* prints the commands it executes; the `@` in front of this trivial command suppresses this.
|
||
`$(@D)` refers to the parent directory of the current target.
|
||
|
||
`$(OUTPUT)` is currently defined as `slides.pdf`, but we might change it to another directory later.
|
||
So just for good measure its rule too gets the special `mkdir` treatment.
|
||
|
||
```makefile
|
||
build/%.pdf: %.svg
|
||
@mkdir -p $(@D)
|
||
inkscape --export-pdf "$@" "$<"
|
||
|
||
$(OUTPUT): $(SLIDES_PDF)
|
||
@mkdir -p $(@D)
|
||
pdfjoin $+ -o "$@"
|
||
```
|
||
|
||
# The final makefile
|
||
One more thing: the default target, for when you call *make* without arguments, is the first one in the makefile (with [some caveats][gmake_docs_targets]).
|
||
You could override this with `.DEFAULT_GOAL := your_target_name_here`, but it's just as easy to reorder the rules, which is what I've done in the summary below.
|
||
|
||
[gmake_docs_targets]: https://www.gnu.org/software/make/manual/html_node/Goals.html "Goals – GNU make documentation"
|
||
|
||
This is the finished makefile:
|
||
|
||
**TODO verify up to date**
|
||
**TODO verify actually works**
|
||
|
||
```makefile
|
||
OUTPUT = slides.pdf
|
||
|
||
SLIDES ::= $(sort $(wildcard *.svg))
|
||
SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf)
|
||
|
||
$(OUTPUT): $(SLIDES_PDF)
|
||
@mkdir -p $(@D)
|
||
pdfjoin $+ -o "$@"
|
||
|
||
build/%.pdf: %.svg
|
||
@mkdir -p $(@D)
|
||
inkscape --export-pdf "$@" "$<"
|
||
|
||
.PHONY: clean
|
||
clean:
|
||
rm -rf "$(OUTPUT)" build/
|
||
```
|
||
|
||
**TODO: split into 2 posts?**
|
||
|
||
## Further help with *make*
|
||
I'm using *GNU make* and I'll admit I don't know if this works in other flavours.
|
||
GNU's documentation is installed on my system and accessible with `info make` (<kbd>tab</kbd> to jump to next link, <kbd>enter</kbd> to activate it, <kbd>l</kbd> to return to previous screen, <kbd>Shift+h</kbd> for help).
|
||
When online you can also use the [online documentation][gmake_docs].
|
||
|
||
[gmake_docs]: https://www.gnu.org/software/make/manual/html_node/index.html "GNU make documentation"
|
||
|
||
## Creating and presenting slides
|
||
Create one SVG per slide, you can do that in Inkscape.
|
||
Set the document size to something with the desired aspect ratio.
|
||
In my experience this is 16:9 for newer projectors and 4:3 for older models.
|
||
|
||
To use the resulting slide deck during your presentation, I can recommend [pdfpc](https://pdfpc.github.io/).
|
||
But any old PDF viewer that has a fullscreen mode will work, of course.
|