SVG + makefile = slide deck
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é.
If you already know the secrets of make, or just want to get presenting, you can jump down to the finished makefile and start using it!
Requirements
This blog post assumes a working knowledge of the terminal/shell and editing plain text files. It’s written for Linux, macOS or other UNIX-like OSes, and assumes you’re using the shell bash. You may also get this to work under Windows with Windows Subsystem for Linux, but no guarantees there. Other requirements are GNU make and Inkscape.
File dependencies with makefile
If you think of a makefile, you may think it’s like a shell script with multiple entry points, but it 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 have been used for C programs, but you can use them for absolutely any build where you have dependencies between files. Even this website is built with a makefile. Want to 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
.
We’ll use ::=
here, whereas we used just =
above.
=
performs a recursive assignment (which is evaluated at the time it’s used), while ::=
does a simple assignment (which is evaluated at the time it’s defined).
To understand the difference better, you can read the manual on recursive and simple assignment.
SLIDES ::= $(sort $(wildcard *.svg))
SLIDES_PDF ::= $(SLIDES:%.svg=build/%.pdf)
The next rule 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 hard tab; indentation with spaces is rejected by make. (He he.)
Also note: make and spaces in file names don’t go together, so don’t bother trying to escape everything. Just make sure your file names don’t have any spaces.
build/%.pdf: %.svg
inkscape --export-pdf $@ $<
Next we tell make how to create the final output from the individual slides ($(SLIDES_PDF)
).
First they are all joined with pdftk, which is asked to write its output to its standard output, which we pipe into pspdftool’s standard input to add slide numbers in the bottom right corner.
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.
$(OUTPUT): $(SLIDES_PDF)
pdftk $+ cat output - | pspdftool 'number(x=1400pt,y=10pt,start=1,size=30)' - $@
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, the target should be marked as “phony”, which says that this rule’s name is not the file name of its output.
.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.
There are a few ways, none of them elegant.
For our simple makefile, we can just put @mkdir -p $(@D)
in each rule.
You may have noticed that 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
which is in the current directory which necessarily exists, 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)
pdftk $+ cat output - | pspdftool 'number(x=1400pt,y=10pt,start=1,size=30)' - $@
Present!
We can create another phony target to use the resulting slide deck during a presentation. I really like presenting with pdfpc since it has all the features to deliver the slides in a polished way: dual-screen with a presenter console, a timer, persistent speaker’s notes per slide, slide overview to quickly go to a slide, blanking the projected screen, simulated laser pointer, even animated transitions if that’s your cup of tea, you name it. But in a pinch, any old PDF viewer will work, of course, which is a huge advantage!
.PHONY: present
present: $(OUTPUT)
pdfpc $(OUTPUT) -R $(OUTPUT)pc
The finished makefile
A few more things: 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.
I’ve also added a constant $(INTERMEDIARIES_DIR)
to replace the hardcoded build/
.
This is the finished makefile.
You can put it in a directory together with your slides, and then, in a terminal, cd to the directory and invoke make
(or make clean
, or make present
).
INTERMEDIARIES_DIR = build
OUTPUT = slides.pdf
SLIDES ::= $(sort $(wildcard *.svg))
SLIDES_PDF ::= $(SLIDES_SVG:%.svg=$(INTERMEDIARIES_DIR)/%.pdf)
$(OUTPUT): $(SLIDES_PDF)
@mkdir -p $(@D)
pdftk $+ cat output - | pspdftool 'number(x=1400pt,y=10pt,start=1,size=30)' - $@
$(INTERMEDIARIES_DIR)/%.pdf: %.svg
@mkdir -p $(@D)
inkscape --export-pdf $@ $<
.PHONY: clean
clean:
rm -rf "$(OUTPUT)" "$(INTERMEDIARIES_DIR)"
.PHONY: present
present: $(OUTPUT)
pdfpc $(OUTPUT) -R $(OUTPUT)pc
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 on make can be accessed with the command info make
(Tab ↹ to jump to next link, ↵ Return to activate it, l to return to previous screen, ⇧ Shift+h for help).
When online you can also use the online documentation.
Parting notes on creating 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.