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

6 KiB
Raw Blame History

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.

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.

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.

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?

$(OUTPUT): $(SLIDES_PDF)
	pdfjoin $+ -o "$@"

It is common to create a target clean to delete output and intermediary files.

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".

.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 makes single biggest shortcoming.

There are a few ways, 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.

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). 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.

This is the finished makefile:

TODO verify up to date
TODO verify actually works

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.

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. But any old PDF viewer that has a fullscreen mode will work, of course.