back

Hacking mupdf to save the page number

mupdf is the best thing since sliced bread- not only is it an incredibly fast, no-frills PDF viewer, you can also do epubs. I figured out you could change the background color only by hacking it- which I did, works great, so I made an Arch package.

In the best of these traditions, what I was really sorely missing from any PDF viewer is state saving- if I nod off reading a Neal Stephenson novel (and it only happens because they keep me up so long) I want to go right back where I left off.

Dead-tree-nostalgia has a lot of great arguments for its cause- folksy charm, unique haptics and olfactories, and the quaintness of visiting a bygone era. Nope, my industrial-looking Thinkpad workstation can't do any of those, so if I'm going to do any pleasure reading on it, it better blow me out of my socks on convenience. And it almost did! Ever since I got into using nothing but i3, a terminal and a browser for my daily work, I became a sucker for speed. mupdf certainly delivers on that- open the file, boom, it's there. The background color patch gave me the dreamy fullscreen slideshow feel- but no saving the page number. Everything perfect but won't do one thing? This is almost like it's asking for a little bit of hacking.

It was easy- mupdf got its balance between "C-style object oriented programming" (struct + a set of classes) and its pragmatism just right- it was a breeze to work with and I think it's a great example of old-school systems programming done right.

Aw- I finally get to twiddle a few bits in C, and write my own binary format. After saving, the code takes a CRC hash from the file path, adds the page number, and appends it to a state file- looking back, I implemented my own hash on-disk hash table. As a former web dev, it's really really nice to see just how much you can achieve without creating all kinds of humongous layers of protocols and storage engines and other fancy tools that have the shelf life of a Silicon Valley company. This is just going through a file 4 bytes at a time and that's that- doesn't slow anything down at all, and it works. If you have your ebooks in a file sync folder, you can hard link your ~/.mupdfstate.dat file in there as well- then you can take your state with you across computers.

Download mupdfstate.patch or view:

diff --git a/platform/x11/x11_main.c b/platform/x11/x11_main.c
index edbb9fa8..f6bde414 100644
--- a/platform/x11/x11_main.c
+++ b/platform/x11/x11_main.c
@@ -17,6 +17,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 #include <signal.h>
+#include <zlib.h>

 #define mupdf_icon_bitmap_16_width 16
 #define mupdf_icon_bitmap_16_height 16
@@ -351,10 +352,69 @@ void wincopyfile(char *source, char *target)
    fclose(in);
 }

+void savestate(pdfapp_t *app)
+{
+  FILE *f;
+  char fn[80];
+  uint32_t crc;
+  uint32_t tmp;
+  int32_t pos;
+
+  strcpy(fn,"");
+  strcat(fn,getenv("HOME"));
+  strcat(fn,"/.mupdfstate.dat");
+  crc = crc32(0, (const void *)filename, strlen(filename));
+
+  f = fopen(fn, "ab+");
+  fclose(f);
+
+  f = fopen(fn, "r+");
+  if(NULL == f) return;
+  while (fread(&tmp, 4, 1, f)) {
+    if (tmp == crc) {
+      fseek(f,-4,SEEK_CUR);
+      break;
+    }
+    fseek(f,4,SEEK_CUR);
+  }
+
+  pos = ftell(f);
+  fseek(f, pos, SEEK_SET);
+  fwrite(&crc, 4, 1, f);
+  fwrite(&app->pageno, 4, 1, f);
+
+  fclose(f);
+}
+
+void loadstate(pdfapp_t *app)
+{ 
+  FILE *f;
+  char fn[80];
+  uint32_t crc;
+  uint32_t tmp;
+  strcpy(fn,"");
+  strcat(fn,getenv("HOME"));
+  strcat(fn,"/.mupdfstate.dat");
+  crc = crc32(0, (const void *)filename, strlen(filename));
+
+  f = fopen(fn, "rb");
+  if(NULL == f) return;
+  while (fread(&tmp, 4, 1, f)) {
+    if (tmp == crc) {
+      fread(&app->pageno, 4, 1, f);
+      break;
+    }
+    fseek(f,4,SEEK_CUR);
+  }
+  fclose(f); 
+}
+
 void cleanup(pdfapp_t *app)
 {
    fz_context *ctx = app->ctx;

+        savestate(app);
+
    pdfapp_close(app);

    XDestroyWindow(xdpy, xwin);
@@ -923,6 +983,8 @@ int main(int argc, char **argv)
    gapp.scrh = DisplayHeight(xdpy, xscr);
    gapp.resolution = resolution;
    gapp.pageno = pageno;
+        
+        loadstate(&gapp);

    tmo_at.tv_sec = 0;
    tmo_at.tv_usec = 0;