website/blog/2020-06-11-svg-makefile-slides.md

141 lines
6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.