Hey everyone I've been working on a new project with an e-paper display, it's a DIY e-Reader.
It's got an SD Card to hold the books - so it's quite easy to add new ones and you can hold an awful
lot of books on it. There's a simple UI to navigate through the files and we can open a book and read
it. I've built mine around a 4.7-inch e-paper display from LilyGo but you should be able to
get the software up and running on any ESP32 based e-paper board - provided it has PSRAM built-in and
a way to connect an SD Card. Lots of boards come with an SD Card reader but, if yours is missing one
then you can easily connect one using SPI following the instructions in this video. I've put
a link in the description. You'll also need three buttons for navigation. We need UP, DOWN and SELECT.
The code is pretty flexible, so these can be defined as either active low or active high
depending on your board and your wiring. We'll run through how this all works after this quick thanks
to PCBWay for sponsoring the channel. They've been supporting me for a while now and they've
been really helpful. I've made quite a few boards with them and the service has always been great.
They also do CNC and 3D printing which we'll be trying out in some future videos. Check out the
link in the description. So how does it all work? Well let's start off with a quick rough overview
of the ePUB format. This is a free and open format for eBooks that is compatible with most popular
eBook readers. This means there are a lot of free books available that you can download and read.
Now, I've only investigated the format sufficiently to get a basic reader up and running but there
is a detailed spec with a lot more information online. If we look at the first few bytes of an
ePUB file we can see that it's actually a ZIP archive. We can easily change the extension of
the file to zip and then decompress it using unzip to see what's inside. The most interesting file is
the content.opf file this XML file contains three sections that are of interest to us. The metadata
section contains useful information such as the title of the book along with the content to use
for the cover page image. The manifest contains a list of all the items contained in the ePUB file
and finally, the spine section lists the items in the order they should be displayed to the reader.
There is some other useful information but for a basic e-reader, we can safely ignore it.
If we look at the individual items that make up the book, we can see that we have some images
and some HTML files. Now the ePUB specification dictates that the HTML files should all be valid
XHTML - this will come in handy later when we try to parse them. So there are several challenges to solve:
we need to get some ePUB files onto our device, we need a ZIP library that will run on the ESP32 and
let us read files from the ePUB archive, we need to parse the content.opf file using an XML parser
and finally, we need to take the XHTML items parse them and lay them out so they can be rendered on
the e-paper display. To make the first part simple I've attached an SD Card to the board.
This is a slightly hacked together contraption there is an official adapter for the LilyGo board,
but using what we've learned in the previous video I've got an SD Card attached to some spare
GPIO pins. Unfortunately, these are the only spare GPIO pins on the board so we can't use
the touchscreen interface but, this is probably a good thing as buttons mean we can take advantage
of deep sleep and save battery power. With a bunch of ePUB files copied onto the SD Card it's pretty
straightforward to list the contents using a couple of C functions. We can filter out the ePUB
files by checking the extension and ignore any hidden files that start with a dot. Now there is
a nasty gotcha here for the unwary by default the file system code in the ESP-IDF doesn't
support long file names. So you need to enable this option using menuconfig. If you don't do this then
you'll end up with some very weird and wacky names showing up when you list the directory contents.
With the list of ePUB file names collected, we need to open them up and parse the content.opf
file. There's a nice library we can use called miniz which will read ZIP archives. I've encapsulated
this in a very simple wrapper that will either extract files to memory or extract them to a file
on the SD Card. To extract the contents.opf file we just use these couple of lines. The file we're
extracting is generally quite small so we can extract it to memory without any problems. To parse
the XML file I'm using a library called TinyXML2 this is a nice C++ library that is lightweight
and works really nicely on the ESP32. With the XML parsed it's quite simple to extract out the
information we need about the book. We can get the title and we can get the item that should be used
for the cover image. We use this to drive our book selection UI. To process the manifest and spine
we first read in the items from the manifest and create a map from the ID of the item to the item
path in the ZIP archive. We then run through the spine and collect the items in the required order.
So that's our ePUB structure pretty much parsed. We now have enough information to show a list of
books with a thumbnail and title and for each book we know what order to render the HTML files in
This leads us nicely onto the next problem we need to parse and render the HTML and CSS files.
Doing this properly on an ESP32 would be pretty difficult even with the extra PSRAM.
So, we will need to cheat slightly. Electronic books don't really have that much styling
so we can pretty much ignore the CSS files. They're also designed to be displayed on various different
devices and screen resolutions - so the structure of the HTML tends to be fairly straightforward.
We're really only interested in a few HTML tags. We're looking for headers, divs and paragraph tags.
We'll treat these tags as containing blocks of text. We also want to pick up bold and italic text
to get some formatting and we also want the image tags so we can show the inline images in the book.
Since we know that we'll be getting valid XHTML we can use the TinyXML2 parser on the files.
We parse the XML and then we visit each tag in the document picking up any opening tags that
are of interest to us. If we see a new block tag then we start collecting text making sure we flag
any sections that are bold or italic. We do this until we hit the end of the block tag. If we hit
an image tag then we end the current block, add the image, and then create a new block. At the end of
this processing we have a list of text and image blocks in the order they appear in the HTML file.
We now need to fit these to the width of our display and calculate the height of each block so
we can work out where the page breaks should be. For images this is pretty simple: we get the dimensions
of the image and then scale them so they fit within the screen bounds. For the text blocks, it's
a bit more complicated - we need to layout the text so it fits nicely within the width of the screen.
We need to work out where the line breaks should be. There's a very nice dynamic programming
way of doing this that should ensure we end up with nicely laid out text without huge gaps or
really tiny gaps. You can watch a really interesting talk on dynamic programming here that
I have linked to in the description that describes the algorithm in detail. With our text blocks
broken into individual lines and our image heights all calculated we can now simply assign the lines
of text and images to pages. We start assigning text and images to a page and when we run out
of space, we just start a new page. When it comes to rendering the pages it's now pretty simple.
We just draw the images and the words where they've been laid out. All the heavy lifting
has already been done so our rendering is easy. To make the device portable I've got a battery
attached and the code goes into deep sleep after 30 seconds of user inactivity. I've got a video
that dives into deep sleep that's definitely worth watching. All the code is on GitHub there's
huge scope for improving what's there the project is by no means done and still needs a
considerable amount of work. So please contribute if you've got an ESP32 and an e-paper display
then let me know if you get it to work. I'm really excited to see what can be done. Good luck!