title: SVG + makefile = slide deck summary: "You don't need LibreOffice to prepare your presentation -- or: an introduction to makefiles" published: 2020-06-02 --- 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 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 `clean` to clean up. ```makefile clean: rm -rf "$(OUTPUT)" build/ ``` If you now create a file `clean` and run `make clean`, *make* will quite contently say it doesn't need to do anything: the file `clean` exists and it depends on nothing else. To avoid this, non-file targets must be marked as "phony". ```makefile .PHONY: clean ``` # Creating directories If you would 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` (tab to jump to next link, enter to activate it, l to return to previous screen, Shift+h 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.