Digital zines I: PDF

From Media Design: Networked & Lens-Based wiki
Jump to navigation Jump to search

PDF zining

Today: digital zine making with PDF (using the commandline and bash scripting)

A Zine Protocol?

In a blog post of January 2020, titled Non-realtime publishing for censorship resistance, the author, Björn Edström, ironically(?) proposes zines as a novel new technical protocol (or “class of services”) as an alternative for traditional online publishing, which he sees as fragile and too easily censored.

The main idea is that you have one or more owners. Think of an owner as a public/private keypair. The owner manipulate (offline) a collection of files (a “zine”). This collection is encrypted, signed and published periodically. It doesn’t matter where they are published, could be anywhere on the Internet, in an actual magazine, or maybe on a Tor hidden service. It doesn’t matter.

The users can perform actions on the zine by relaying (somehow) requests to the owners. The owners may then chose to apply the action and thus manipulate/update the collection, and then publish a new collection/zine for consumption by the users. In practice they will probably have a little application for manipulating the zine, like a command line tool, text editor, or something, that handles the formatting/marshalling.

Note: Marshalling, a term with a military origin, in a techincal sense is about packaging digital stuff in a way that can be transmitted and shared. source


In the post, Edström is speaking to a technical audience warning of the “centralizing” tendencies and fragility of many technical solutions, despite the fact that online publishing is often portrayed as inherently decentralized. You could see this posting as part of larger contemporary phenonenon of a backlash against many of the promises of the Web and the Internet (what could be called “Web 2.0”), as, in this case, a programmer realizes that despite the conventional wisdom, for instance: the Internet being inherhently de-centralized and thus somehow impervious to censorship, and recalls that earlier technologies/media/publishing practices may in fact already have done a better job. This movement could be termed part of what’s called “post-digital”. In the case of the blog post, what’s not clear in the end however, is whether Edström is interested in the actual history of zine publishing, and whether he thinks there might be specific lessons might be taken from this already established practice.

DIY: The rise of Lo-Fi Culture, Amy Spencer

Amy Spencer’s DIY: The rise of Lo-Fi Culture (pdf) is a telling of the history of zines, which she defines as “non-commercial, small-circulation publications which are producedand distributed by their creators”.

Tom Jennings

Tom Jennings is an interesting figure at an intersection of two histories. First, he is one of the key authors of homocore, a queer zine described by Spencer. Second, Jennings is the author of the fidonet protocol and plays an important role in the history of early bulletin board systems. Jennings is featured in “BBS documentary”, a project of Jason Scott of the project archive team

bbs: baud bbs: sysops bbs: fidonet


In addition to a certain DIY aesthetic, doing things on the commandline is quite powerful, because whatever can be accomplished once, no matter how obscure the commands or effort to get a script to work, that script can then become part of a larger program (say in a loop) and form the basis of new novel publishing workflows.

Installing tools

For the recipes on this page, you'll need some tools, namely:

  • wget
  • convert & mogrify
  • tesseract
  • pdfunite

These can be (respectively) installed with a package manager command like:

 sudo apt install wget imagemagick tesseract-ocr tesseract-ocr-eng poppler-utils


 brew install wget imagemagick tesseract-ocr poppler

Getting help on a commandline program

Try running the program with either no options:


or with "--help" or "-h"

tesseract --help

For more information check the man page:

man tesseract

BASH Loops

BASH is the so called "Bourne again" shell -- aka the command line -- and it's a scripting language.

In programming there are only 3 imporant cases: zero, one, and more than one

When you have “more than one” thing – think loop. In BASH there are loops modelled on how they work in C.

for (( c=1; c<=5; c++ ))
   echo "Welcome $c times"

You can also nest loops. It may be useful to think of a clock when nesting loops -- the outer loop is the "slower" of the two (hour hand) compared to the faster inner loop (minute hand).

for (( h=0; h<=23; h++ ))

	for (( m=0; m<=59; m++ ))
	echo "The time is: $h:$m"


Concrete example: Downloading an image from online

A useful commandline tool for downloading things that are online is wget. (See also: curl)

Let’s say we want to download all the images from homocore issue #1. They are (happily, but precariously) still available via links held in the “wayback” machine hosted by

Downloading the images:

Following the link:


try with wget:


Exercise: Adapt the loop code to call wget multiple times, using the variable in the command to download each page of the PDF.

Zero padding

Zero padding (or adding extra 0's to the start of a numeric filename) is a good practice as it allows numerically named files to be lexically sorted (ie alphabetically) and be in the same order as if they were numerically sorted. In other words, without zero padding, the default ordering of files (alphabetical) places "10.jpg" before "1.jpg", while "01.jpg" and "10.jpg" will sort (alphabetically) as expected.

   printf %04d 5



Command substitution

Like variable substitution, but with a command. You tend to see the `backticks` version more (it's less typing ;)

There are two forms:





So, for example, instead of:

echo Hello $USER

you could use the *whoami* command for the same:

echo Hello `whoami`

Zero padding files after the fact

If you are unable to avoid pooly named filenames with the above, then you can fix them with the following command:

This uses the *rename* utility and regular expressions:

rename 's/\d+/sprintf("%04d",$&)/e' foo*



In addition to shell scripts and command substitutions, creating Pipelines is another way to make novel workflows by composing multiple tools/commands. See also: stdout/stdin /stderr

Remember that:

  • > myfile ... Means Save stdout as myfile...
  • >2 myfile .... Means Save stderr as myfile...

So using ">2 /dev/null" means send stderr to "dev/null" (in other words, don't do anything with it) to hide the output of a program (such as wget).


See: ImageMagick

BASH tests

A looping downloader with zero-padded filenames

for (( i=1; i<=7; i++ ))
	echo Starting to download issue $i...
	mkdir -p `printf %02d $i`
	for (( p=1; p<=100; p++ ))
	   outname=`printf %02d $i`/`printf %02d $i`_`printf %02d $p`.jpg
	   echo "Downloading $outname"
	   wget$i/$p.JPG -O $outname 2> /dev/null
	   if [ ! -s $outname ]
	       echo No more pages...
               rm $outname

Resize all the images to be smaller

In the case that your images are too high resolution (which isn't actually the case with the images from the homocore zine), you can use imagemagick to resize them.

NB: the Mogrify command changes images in place (ie it destroys the originals). Just make a copy of your images first…

mkdir originals
cp *.JPG originals

then you could:

mogrify -resize 120x *.JPG

Alternatively, you could use convert in a loop:

mkdir -p icons
for i in *.JPG
	echo making a thumbnail of $i
	convert $i -resize 200x200 icons/$i

Make a single PDF of all the images

You can “bind” all the images together into a single (image only) PDF file with imagemagick:

convert *.JPG zine.pdf


Create a pdf with 100 numbered pages.


Use tesseract to convert a single jpeg into a pdf with searchable text

Tesseract has a particular way of being run,

 tesseract imagename outputbase [options...] [configfile...]

In particular, rather than giving an output filename, you need to give a output "base" name (the first part of the output file name), and then separately a "configuration" which basically defines what kind(s) of output you want to produce. So for instance to convert the single JPEG (01_01.jpg) into a PDF named (01_01.pdf), you would use the command:

 tesseract 01_01.jpg 01_01 pdf

NB: There is a SPACE between 01_01 and pdf, if you would type:

 tesseract 01_01.jpg 01_01.pdf     # this is not right!

It's confusing because tesseract then uses as output "basename" 01_01.pdf, and defaults to text output, producing a text file output named:


Joining pdfs (and preserving searchability)

Unforunately using imagemagick to join different pdf files together removes any non-image material (such as tesseracts OCR output). Luckily pdfunite (part of the poppler package) is a program that can join searchable pdfs and preserves this information!

To check pdfunite on just two particular files, you might try:

  pdfunite 01_01.pdf 01_02.pdf output.pdf

Then finally, you can use a wildcard to join all the single page pdfs together:

  pdfunite 01_*.pdf output.pdf

ocr script

Tesseract can only process one image at a time. This is not a bad thing. Following the command-line aesthetic of "doing one thing well" it does it's thing. It's then up to the intrepid shell scripter (that's you) to put tesseract commands into a loop to process a whole bunch of input files.

So to combine the different steps above in a loop... and finally joining the different pdfs into a single one (still with searchable text) (using pdfunite)...

mkdir -p icons
for i in *.jpg
	echo ocring $i...
	convert $i -resize 200x200 icons/$i
	tesseract $i `basename -s .jpg $i` pdf
pdfunite 01_*.pdf 01.pdf


In the end we didn't use this, but pdfsandwich is a command that just calls a number of other commands to turn an input pdf (without text information) into a pdf with text (aka searchable). It makes use of:

  • imagemagick (convert): to extract images from a source PDF
  • unpaper: to "fix" / clean up a scanned image to work
  • tesseract: to do ocr and produce a single page pdf
  • pdfunite: to "rebind" the single page pdfs back into a multi-page pdf.

When you run the command in "verbose" mode, the script outputs a "trace" of the commands it's using (a bit like using the -x option on the bash command):

pdfsandwich -verbose test.pdf
pdfsandwich version 0.1.7
Checking for convert:
convert -version
Version: ImageMagick 6.9.10-23 Q16 x86_64 20190101
Copyright: © 1999-2019 ImageMagick Studio LLC
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib djvu fftw fontconfig freetype heic jbig jng jp2 jpeg lcms lqr ltdl lzma openexr pangocairo png tiff webp wmf x xml zlib
Checking for unpaper:
unpaper -V
Checking for tesseract:
tesseract -v
tesseract 4.0.0
  libgif 5.1.4 : libjpeg 6b (libjpeg-turbo 1.5.2) : libpng 1.6.36 : libtiff 4.0.10 : zlib 1.2.11 : libwebp 0.6.1 : libopenjp2 2.3.0
 Found AVX2
 Found AVX
 Found SSE
Checking for gs:
gs -v
GPL Ghostscript 9.27 (2019-04-04)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
Checking for pdfinfo:
pdfinfo -v
Checking for pdfunite:
pdfunite -v
Input file: "zine.pdf"
Output file: "zine_ocr.pdf"
Number of pages in inputfile: 2
More threads than pages. Using 2 threads instead.

Parallel processing with 2 threads started.
Processing page order may differ from original page order.

Processing page 2.
Processing page 1.
identify -format "%w\n%h\n"  "/tmp/pdfsandwich_tmpbd9242/pdfsandwich_inputfile6e1885.pdf[0]" 
identify -format "%w\n%h\n"  "/tmp/pdfsandwich_tmpbd9242/pdfsandwich_inputfile6e1885.pdf[1]" 
convert -units PixelsPerInch  -type Bilevel -density 300x300  "/tmp/pdfsandwich_tmpbd9242/pdfsandwich_inputfile6e1885.pdf[1]" /tmp/pdfsandwich_tmpbd9242/pdfsandwich8228d4.pbm
convert -units PixelsPerInch  -type Bilevel -density 300x300  "/tmp/pdfsandwich_tmpbd9242/pdfsandwich_inputfile6e1885.pdf[0]" /tmp/pdfsandwich_tmpbd9242/pdfsandwich3d8ad8.pbm
unpaper --overwrite  --no-grayfilter --layout none /tmp/pdfsandwich_tmpbd9242/pdfsandwich8228d4.pbm /tmp/pdfsandwich_tmpbd9242/pdfsandwich104659_unpaper.pbm
unpaper --overwrite  --no-grayfilter --layout none /tmp/pdfsandwich_tmpbd9242/pdfsandwich3d8ad8.pbm /tmp/pdfsandwich_tmpbd9242/pdfsandwich7570dd_unpaper.pbm
Processing sheet #1: /tmp/pdfsandwich_tmpbd9242/pdfsandwich8228d4.pbm -> /tmp/pdfsandwich_tmpbd9242/pdfsandwich104659_unpaper.pbm
convert -units PixelsPerInch -density 300x300 /tmp/pdfsandwich_tmpbd9242/pdfsandwich104659_unpaper.pbm /tmp/pdfsandwich_tmpbd9242/pdfsandwiche7f914.tif
OMP_THREAD_LIMIT=1 tesseract /tmp/pdfsandwich_tmpbd9242/pdfsandwiche7f914.tif /tmp/pdfsandwich_tmpbd9242/pdfsandwich8ddf62  -l eng pdf 
Processing sheet #1: /tmp/pdfsandwich_tmpbd9242/pdfsandwich3d8ad8.pbm -> /tmp/pdfsandwich_tmpbd9242/pdfsandwich7570dd_unpaper.pbm
convert -units PixelsPerInch -density 300x300 /tmp/pdfsandwich_tmpbd9242/pdfsandwich7570dd_unpaper.pbm /tmp/pdfsandwich_tmpbd9242/pdfsandwich33bb2b.tif
OMP_THREAD_LIMIT=1 tesseract /tmp/pdfsandwich_tmpbd9242/pdfsandwich33bb2b.tif /tmp/pdfsandwich_tmpbd9242/pdfsandwich70ba74  -l eng pdf 
OCR pdf generated. Renaming output file to /tmp/pdfsandwich_tmpbd9242/pdfsandwichd39e00.pdf

OCR pdf generated. Renaming output file to /tmp/pdfsandwich_tmpbd9242/pdfsandwichbea9bf.pdf

OCR done. Writing "zine_ocr.pdf"
pdfunite /tmp/pdfsandwich_tmpbd9242/pdfsandwichd39e00.pdf /tmp/pdfsandwich_tmpbd9242/pdfsandwichbea9bf.pdf /tmp/pdfsandwich_tmpbd9242/pdfsandwich_outputc93999.pdf

zine_ocr.pdf generated.


Other sources of scanned materials to choose from


Followed by: Digital zines II: HTML and friends