[
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2009-2015, Ali Gholami Rudi <ali@rudi.ir>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n* Redistributions of source code must retain the above copyright\n  notice, this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright\n  notice, this list of conditions and the following disclaimer in the\n  documentation and/or other materials provided with the distribution.\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived\n  from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "PREFIX = .\nCC = cc\nCFLAGS = -Wall -O2 -I$(PREFIX)/include\nLDFLAGS = -L$(PREFIX)/lib\n\nall: fbpdf\n\n%.o: %.c doc.h\n\t$(CC) -c $(CFLAGS) $<\nclean:\n\t-rm -f *.o fbpdf fbdjvu fbpdf2\n\n# PDF support using mupdf\nfbpdf: fbpdf.o mupdf.o draw.o\n\t$(CC) -o $@ $^ $(LDFLAGS) -lmupdf -lmupdf-third -lmupdf-pkcs7 -lmupdf-threads -lm\n\n# DjVu support\nfbdjvu: fbpdf.o djvulibre.o draw.o\n\t$(CXX) -o $@ $^ $(LDFLAGS) -ldjvulibre -ljpeg -lm -lpthread\n\n# PDF support using poppler\npoppler.o: poppler.c\n\t$(CXX) -c $(CFLAGS) `pkg-config --cflags poppler-cpp` $<\nfbpdf2: fbpdf.o poppler.o draw.o\n\t$(CXX) -o $@ $^ $(LDFLAGS) `pkg-config --libs poppler-cpp`\n"
  },
  {
    "path": "README",
    "content": "FBPDF\n=====\n\nFbpdf is a framebuffer PDF and DjVu viewer.  There are three make\ntargets:\n\n* fbpdf: uses mupdf library for rendering PDF, CBZ, and EPUB files.\n* fbpdf2: uses poppler library for rendering PDF files.\n* fbdjvu: uses djvulibre library for rendering DjVu files.\n\nThe default target is fbpdf; to build the other two, they must be\nexplicitly specified.\n\nThe following options are available in all three programs:\n\n  fbpdf [-r rotation] [-z zoom_x10] [-p page_number] file.pdf\n\nThe following table lists the commands available in fbpdf.  Most of\nthem accept a numerical prefix.  For instance, '^F' tells fbpdf to\nshow the next page while '5^F' tells it to show the fifth next page.\n\n==============\t================================================\nKEY\t\tACTION\n==============\t================================================\n^F/J\t\tnext page\n^B/K\t\tprevious page\nG\t\tgo to page (the last page if no prefix)\no\t\tset page number (for 'G' command only)\nO\t\tset page number and go to current page\nz\t\tzoom; prefix multiplied by 10 (i.e. '15z' = 150%)\nr\t\tset rotation in degrees\ni\t\tprint some information\nI\t\tinvert colors (prefix specifies black level)\nq\t\tquit\n^[/escape \tclear the numerical prefix\nmx\t\tmark page as 'x' (or any other letter)\n'x\t\tjump to the page marked as 'x'\n`x\t\tjump to the page and position marked as 'x'\nj\t\tscroll down\nk\t\tscroll up\nh\t\tscroll left\nl\t\tscroll right\n[\t\talign with the left edge of the page\n]\t\talign with the right edge of the page\n{\t\talign with the leftmost character on the page\n}\t\talign with the rightmost character on the page\nH\t\tshow page top\nM\t\tcentre the page vertically\nL\t\tshow page bottom\nC\t\tcentre the page horizontally\n^D/space\tpage down\n^U/^H/backspace\tpage up\n^L\t\tredraw\ne\t\treload current file\nf\t\tzoom to fit page height\nw\t\tzoom to fit page width\nW\t\tzoom to fit page contents horizontally\nZ\t\tset the default zoom level for 'z' command\nd\t\tsleep one second before the next command\n==============\t================================================\n"
  },
  {
    "path": "djvulibre.c",
    "content": "#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <libdjvu/ddjvuapi.h>\n#include \"doc.h\"\n\n#define MIN(a, b)\t((a) < (b) ? (a) : (b))\n\nstruct doc {\n\tddjvu_context_t *ctx;\n\tddjvu_document_t *doc;\n};\n\nint djvu_handle(struct doc *doc)\n{\n\tddjvu_message_t *msg;\n\tmsg = ddjvu_message_wait(doc->ctx);\n\twhile ((msg = ddjvu_message_peek(doc->ctx))) {\n\t\tif (msg->m_any.tag == DDJVU_ERROR) {\n\t\t\tfprintf(stderr,\"ddjvu: %s\\n\", msg->m_error.message);\n\t\t\treturn 1;\n\t\t}\n\t\tddjvu_message_pop(doc->ctx);\n\t}\n\treturn 0;\n}\n\nstatic void djvu_render(ddjvu_page_t *page, int iw, int ih, void *bitmap)\n{\n\tddjvu_format_t *fmt;\n\tddjvu_rect_t rect;\n\trect.x = 0;\n\trect.y = 0;\n\trect.w = iw;\n\trect.h = ih;\n\tfmt = ddjvu_format_create(DDJVU_FORMAT_RGB24, 0, 0);\n\tddjvu_format_set_row_order(fmt, 1);\n\tmemset(bitmap, 0, ih * iw * 3);\n\tddjvu_page_render(page, DDJVU_RENDER_COLOR,\n\t\t\t\t&rect, &rect, fmt, iw * 3, bitmap);\n\tddjvu_format_release(fmt);\n}\n\nvoid *doc_draw(struct doc *doc, int p, int zoom, int rotate, int bpp, int *rows, int *cols)\n{\n\tddjvu_page_t *page;\n\tddjvu_pageinfo_t info;\n\tint iw, ih, dpi;\n\tunsigned char *bmp;\n\tchar *pbuf;\n\tint i, j;\n\tpage = ddjvu_page_create_by_pageno(doc->doc, p - 1);\n\tif (!page)\n\t\treturn NULL;\n\twhile (!ddjvu_page_decoding_done(page))\n\t\tif (djvu_handle(doc))\n\t\t\treturn NULL;\n\tif (rotate)\n\t\tddjvu_page_set_rotation(page, (4 - (rotate / 90 % 4)) & 3);\n\tddjvu_document_get_pageinfo(doc->doc, p - 1, &info);\n\tdpi = ddjvu_page_get_resolution(page);\n\tiw = ddjvu_page_get_width(page) * zoom / dpi;\n\tih = ddjvu_page_get_height(page) * zoom / dpi;\n\tif (!(bmp = malloc(ih * iw * 3))) {\n\t\tddjvu_page_release(page);\n\t\treturn NULL;\n\t}\n\tdjvu_render(page, iw, ih, bmp);\n\tddjvu_page_release(page);\n\tif (!(pbuf = malloc(ih * iw * bpp))) {\n\t\tfree(bmp);\n\t\treturn NULL;\n\t}\n\tfor (i = 0; i < ih; i++) {\n\t\tunsigned char *s = bmp + i * iw * 3;\n\t\tchar *d = pbuf + (i * iw) * bpp;\n\t\tfor (j = 0; j < iw; j++)\n\t\t\tfb_set(d + j * bpp, s[j * 3], s[j * 3 + 1], s[j * 3 + 2]);\n\t}\n\tfree(bmp);\n\t*cols = iw;\n\t*rows = ih;\n\treturn pbuf;\n}\n\nint doc_pages(struct doc *doc)\n{\n\treturn ddjvu_document_get_pagenum(doc->doc);\n}\n\nstruct doc *doc_open(char *path)\n{\n\tstruct doc *doc = malloc(sizeof(*doc));\n\tdoc->ctx = ddjvu_context_create(\"fbpdf\");\n\tif (!doc->ctx)\n\t\tgoto fail;\n\tdoc->doc = ddjvu_document_create_by_filename(doc->ctx, path, 1);\n\tif (!doc->doc)\n\t\tgoto fail;\n\twhile (!ddjvu_document_decoding_done(doc->doc))\n\t\tif (djvu_handle(doc))\n\t\t\tgoto fail;\n\treturn doc;\nfail:\n\tdoc_close(doc);\n\treturn NULL;\n}\n\nvoid doc_close(struct doc *doc)\n{\n\tif (doc->doc)\n\t\tddjvu_context_release(doc->ctx);\n\tif (doc->ctx)\n\t\tddjvu_document_release(doc->doc);\n\tfree(doc);\n}\n"
  },
  {
    "path": "doc.h",
    "content": "struct doc *doc_open(char *path);\nint doc_pages(struct doc *doc);\nvoid *doc_draw(struct doc *doc, int page, int zoom, int rotate, int bpp, int *rows, int *cols);\nvoid doc_close(struct doc *doc);\n\nvoid fb_set(char *d, unsigned r, unsigned g, unsigned b);\n"
  },
  {
    "path": "draw.c",
    "content": "#include <fcntl.h>\n#include <linux/fb.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/ioctl.h>\n#include <sys/mman.h>\n#include <unistd.h>\n#include \"draw.h\"\n\n#define MIN(a, b)\t((a) < (b) ? (a) : (b))\n#define MAX(a, b)\t((a) > (b) ? (a) : (b))\n#define NLEVELS\t\t(1 << 8)\n\nstatic struct fb_var_screeninfo vinfo;\t/* linux-specific FB structure */\nstatic struct fb_fix_screeninfo finfo;\t/* linux-specific FB structure */\nstatic int fd;\t\t\t\t/* FB device file descriptor */\nstatic void *fb;\t\t\t/* mmap()ed FB memory */\nstatic int bpp;\t\t\t\t/* bytes per pixel */\nstatic int nr, ng, nb;\t\t\t/* color levels */\nstatic int rl, rr, gl, gr, bl, br;\t/* shifts per color */\nstatic int xres, yres, xoff, yoff;\t/* drawing region */\n\nstatic int fb_len(void)\n{\n\treturn finfo.line_length * vinfo.yres_virtual;\n}\n\nstatic void fb_cmap_save(int save)\n{\n\tstatic unsigned short red[NLEVELS], green[NLEVELS], blue[NLEVELS];\n\tstruct fb_cmap cmap;\n\tif (finfo.visual == FB_VISUAL_TRUECOLOR)\n\t\treturn;\n\tcmap.start = 0;\n\tcmap.len = MAX(nr, MAX(ng, nb));\n\tcmap.red = red;\n\tcmap.green = green;\n\tcmap.blue = blue;\n\tcmap.transp = NULL;\n\tioctl(fd, save ? FBIOGETCMAP : FBIOPUTCMAP, &cmap);\n}\n\nvoid fb_cmap(void)\n{\n\tunsigned short red[NLEVELS], green[NLEVELS], blue[NLEVELS];\n\tstruct fb_cmap cmap;\n\tint i;\n\tif (finfo.visual == FB_VISUAL_TRUECOLOR)\n\t\treturn;\n\n\tfor (i = 0; i < nr; i++)\n\t\tred[i] = (65535 / (nr - 1)) * i;\n\tfor (i = 0; i < ng; i++)\n\t\tgreen[i] = (65535 / (ng - 1)) * i;\n\tfor (i = 0; i < nb; i++)\n\t\tblue[i] = (65535 / (nb - 1)) * i;\n\n\tcmap.start = 0;\n\tcmap.len = MAX(nr, MAX(ng, nb));\n\tcmap.red = red;\n\tcmap.green = green;\n\tcmap.blue = blue;\n\tcmap.transp = NULL;\n\n\tioctl(fd, FBIOPUTCMAP, &cmap);\n}\n\nunsigned fb_mode(void)\n{\n\treturn ((rl < gl) << 22) | ((rl < bl) << 21) | ((gl < bl) << 20) |\n\t\t(bpp << 16) | (vinfo.red.length << 8) |\n\t\t(vinfo.green.length << 4) | (vinfo.blue.length);\n}\n\nstatic void init_colors(void)\n{\n\tnr = 1 << vinfo.red.length;\n\tng = 1 << vinfo.blue.length;\n\tnb = 1 << vinfo.green.length;\n\trr = 8 - vinfo.red.length;\n\trl = vinfo.red.offset;\n\tgr = 8 - vinfo.green.length;\n\tgl = vinfo.green.offset;\n\tbr = 8 - vinfo.blue.length;\n\tbl = vinfo.blue.offset;\n}\n\nint fb_init(char *dev)\n{\n\tchar *path = dev ? dev : FBDEV;\n\tchar *geom = dev ? strchr(dev, ':') : NULL;\n\tif (geom) {\n\t\t*geom = '\\0';\n\t\tsscanf(geom + 1, \"%dx%d%d%d\", &xres, &yres, &xoff, &yoff);\n\t}\n\tfd = open(path, O_RDWR);\n\tif (fd < 0)\n\t\tgoto failed;\n\tif (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0)\n\t\tgoto failed;\n\tif (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) < 0)\n\t\tgoto failed;\n\tfcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);\n\tbpp = (vinfo.bits_per_pixel + 7) >> 3;\n\tfb = mmap(NULL, fb_len(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);\n\tif (fb == MAP_FAILED)\n\t\tgoto failed;\n\tinit_colors();\n\tfb_cmap_save(1);\n\tfb_cmap();\n\treturn 0;\nfailed:\n\tperror(\"fb_init()\");\n\tclose(fd);\n\treturn 1;\n}\n\nvoid fb_free(void)\n{\n\tfb_cmap_save(0);\n\tmunmap(fb, fb_len());\n\tclose(fd);\n}\n\nint fb_rows(void)\n{\n\treturn yres ? yres : vinfo.yres;\n}\n\nint fb_cols(void)\n{\n\treturn xres ? xres : vinfo.xres;\n}\n\nvoid *fb_mem(int r)\n{\n\treturn fb + (r + vinfo.yoffset + yoff) * finfo.line_length + (vinfo.xoffset + xoff) * bpp;\n}\n\nunsigned fb_val(int r, int g, int b)\n{\n\treturn ((r >> rr) << rl) | ((g >> gr) << gl) | ((b >> br) << bl);\n}\n"
  },
  {
    "path": "draw.h",
    "content": "/* fbpad's framebuffer interface */\n#define FBDEV\t\t\"/dev/fb0\"\n\n/* fb_mode() interpretation */\n#define FBM_BPP(m)\t(((m) >> 16) & 0x0f)\t/* bytes per pixel (4 bits) */\n#define FBM_CLR(m)\t((m) & 0x0fff)\t\t/* bits per color (12 bits) */\n#define FBM_ORD(m)\t(((m) >> 20) & 0x07)\t/* color order (3 bits) */\n\n/* main functions */\nint fb_init(char *dev);\nvoid fb_free(void);\nunsigned fb_mode(void);\nvoid *fb_mem(int r);\nint fb_rows(void);\nint fb_cols(void);\nvoid fb_cmap(void);\nunsigned fb_val(int r, int g, int b);\n"
  },
  {
    "path": "fbpdf.1",
    "content": ".TH FBPDF 1 \"JUNE 2022\"\n.SH NAME\nfbpdf \\- framebuffer PDF viewer\n.SH SYNOPSIS\n.B fbpdf\n.RB [ -r\n.IR rotation ]\n.RB [ -z\n.IR zoom_x10 ]\n.RB [ -p\n.IR page_number ]\n.IR filename\n.SH DESCRIPTION\n.P\n.B fbpdf\nis a small framebuffer PDF viewer which uses the MuPDF backend.\n.P\n.B fbpdf2\nis similar to fbpdf, but uses poppler as backend.\n.P\n.B fbdjvu\nis a djvu viewer.\n.P\nThe options documented here are available in all three programs.\n.SH OPTIONS\n.TP\n.BI \"\\-r \" rotation\nRotate specified number of degrees\n.TP\n.BI \"\\-z \" zoom_x10\nZoom by a multiple of ten\n.TP\n.BI \"\\-p \" page_number\nGo to specified page\n.SH KEY BINDINGS\nMost of the key bindings accept a numerical prefix.\nFor instance, '^F' tells fbpdf to show the next page\nwhile '5^F' tells it to show the fifth next page.\n\n.TP\n.B h\nScroll left\n.TP\n.B j\nScroll down\n.TP\n.B k\nScroll up\n.TP\n.B l\nScroll right\n.TP\n.B ^d | <Space>\nPage down\n.TP\n.B ^u | <Backspace>\nPage up\n.TP\n.B ^f | J\nGo to next page\n.TP\n.B ^b | K\nGo to previous page\n.TP\n.B H\nShow page top\n.TP\n.B M\nCenter the page vertically\n.TP\n.B L\nShow page bottom\n.TP\n.B C\nCenter the page horizontally\n.TP\n.B f\nFit page height\n.TP\n.B w\nFit page width\n.TP\n.B W\nFit page contents horizontally\n.TP\n.B [\nAlign with the left edge of the page\n.TP\n.B ]\nAlign with the right edge of the page\n.TP\n.B {\nAlign with the leftmost character on the page\n.TP\n.B }\nAlign with the rightmost character on the page\n.TP\n.B <prefix>G\nGo to specified page, default to the last page if no prefix\n.TP\n.B <prefix>o\nSet page number (for 'G' command only)\n.TP\n.B <prefix>O\nSet page number and go to current page\n.TP\n.B <prefix>r\nRotate specified number of degrees\n.TP\n.B <prefix>z\nZoom by a multiple of ten in percentage (i.e. '15z' = 150%)\n.TP\n.B <prefix>Z\nSet the default zoom level for 'z' command\n.TP\n.B m<mark>\nMark page as <mark>, could be any letter\n.TP\n.B '<mark>\nJump to the page marked as <mark>\n.TP\n.B `<mark>\nJump to the page and position marked as <mark>\n.TP\n.B ^[ / <Esc>\nClear numerical prefix\n.TP\n.B <prefix>d\nSleep specified seconds before the next command\n.TP\n.B I\nInvert colors\n.TP\n.B i\nDisplay file information\n.TP\n.B e\nReload file\n.TP\n.B ^l\nRedraw screen\n.TP\n.B q\nQuit program\n.SH AUTHOR\nWritten by fbpdf contributors.\n"
  },
  {
    "path": "fbpdf.c",
    "content": "/*\n * FBPDF LINUX FRAMEBUFFER PDF VIEWER\n *\n * Copyright (C) 2009-2025 Ali Gholami Rudi <ali at rudi dot ir>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n#include <ctype.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <termios.h>\n#include <unistd.h>\n#include \"draw.h\"\n#include \"doc.h\"\n\n#define MIN(a, b)\t((a) < (b) ? (a) : (b))\n#define MAX(a, b)\t((a) > (b) ? (a) : (b))\n\n#define PAGESTEPS\t8\n#define MAXZOOM\t\t1000\n#define MARGIN\t\t1\n#define CTRLKEY(x)\t((x) - 96)\n#define ISMARK(x)\t(isalpha(x) || (x) == '\\'' || (x) == '`')\n\nstatic struct doc *doc;\nstatic char *pbuf;\t\t/* current page */\nstatic int srows, scols;\t/* screen dimentions */\nstatic int prows, pcols;\t/* current page dimensions */\nstatic int prow, pcol;\t\t/* page position */\nstatic int srow, scol;\t\t/* screen position */\nstatic int bpp;\t\t\t/* bytes per pixel */\n\nstatic struct termios termios;\nstatic char filename[256];\nstatic int mark[128];\t\t/* mark page number */\nstatic int mark_row[128];\t/* mark head position */\nstatic int num = 1;\t\t/* page number */\nstatic int numdiff;\t\t/* G command page number difference */\nstatic int zoom = 150;\nstatic int zoom_def = 150;\t/* default zoom */\nstatic int rotate;\nstatic int count;\nstatic int invert;\t\t/* invert colors? */\n\nstatic void draw(void)\n{\n\tint i;\n\tchar *rbuf = malloc(scols * bpp);\n\tfor (i = srow; i < srow + srows; i++) {\n\t\tint cbeg = MAX(scol, pcol);\n\t\tint cend = MIN(scol + scols, pcol + pcols);\n\t\tmemset(rbuf, 0, scols * bpp);\n\t\tif (i >= prow && i < prow + prows && cbeg < cend) {\n\t\t\tmemcpy(rbuf + (cbeg - scol) * bpp,\n\t\t\t\tpbuf + ((i - prow) * pcols + cbeg - pcol) * bpp,\n\t\t\t\t(cend - cbeg) * bpp);\n\t\t}\n\t\tmemcpy(fb_mem(i - srow), rbuf, scols * bpp);\n\t}\n\tfree(rbuf);\n}\n\nstatic int loadpage(int p)\n{\n\tint i;\n\tif (p < 1 || p > doc_pages(doc))\n\t\treturn 1;\n\tprows = 0;\n\tfree(pbuf);\n\tpbuf = doc_draw(doc, p, zoom, rotate, bpp, &prows, &pcols);\n\tif (invert) {\n\t\tfor (i = 0; i < prows * pcols * bpp; i++) {\n\t\t\tint val = (unsigned char) pbuf[i] ^ 0xff;\n\t\t\tpbuf[i] = val * invert / 255 + (255 - invert);\n\t\t}\n\t}\n\tprow = -prows / 2;\n\tpcol = -pcols / 2;\n\tnum = p;\n\treturn 0;\n}\n\nstatic void zoom_page(int z)\n{\n\tint _zoom = zoom;\n\tzoom = MIN(MAXZOOM, MAX(1, z));\n\tif (!loadpage(num))\n\t\tsrow = srow * zoom / _zoom;\n}\n\nstatic void setmark(int c)\n{\n\tif (ISMARK(c)) {\n\t\tmark[c] = num;\n\t\tmark_row[c] = srow * 100 / zoom;\n\t}\n}\n\nstatic void jmpmark(int c, int offset)\n{\n\tif (c == '`')\n\t\tc = '\\'';\n\tif (ISMARK(c) && mark[c]) {\n\t\tint dst = mark[c];\n\t\tint dst_row = offset ? mark_row[c] * zoom / 100 : 0;\n\t\tsetmark('\\'');\n\t\tif (!loadpage(dst))\n\t\t\tsrow = offset ? dst_row : prow;\n\t}\n}\n\nstatic int readkey(void)\n{\n\tunsigned char b;\n\tif (read(0, &b, 1) <= 0)\n\t\treturn -1;\n\treturn b;\n}\n\nstatic int getcount(int def)\n{\n\tint result = count ? count : def;\n\tcount = 0;\n\treturn result;\n}\n\nstatic void printinfo(void)\n{\n\tprintf(\"\\x1b[H\");\n\tprintf(\"FBPDF:     file:%s  page:%d(%d)  zoom:%d%% \\x1b[K\\r\",\n\t\tfilename, num, doc_pages(doc), zoom);\n\tfflush(stdout);\n}\n\nstatic void term_setup(void)\n{\n\tstruct termios newtermios;\n\ttcgetattr(0, &termios);\n\tnewtermios = termios;\n\tnewtermios.c_lflag &= ~ICANON;\n\tnewtermios.c_lflag &= ~ECHO;\n\ttcsetattr(0, TCSAFLUSH, &newtermios);\n\tprintf(\"\\x1b[?25l\");\t\t/* hide the cursor */\n\tprintf(\"\\x1b[2J\");\t\t/* clear the screen */\n\tfflush(stdout);\n}\n\nstatic void term_cleanup(void)\n{\n\ttcsetattr(0, 0, &termios);\n\tprintf(\"\\x1b[?25h\\n\");\t\t/* show the cursor */\n}\n\nstatic void sigcont(int sig)\n{\n\tterm_setup();\n}\n\nstatic int reload(void)\n{\n\tdoc_close(doc);\n\tdoc = doc_open(filename);\n\tif (!doc || !doc_pages(doc)) {\n\t\tfprintf(stderr, \"\\nfbpdf: cannot open <%s>\\n\", filename);\n\t\treturn 1;\n\t}\n\tif (!loadpage(num))\n\t\tdraw();\n\treturn 0;\n}\n\n/* this can be optimised based on framebuffer pixel format */\nvoid fb_set(char *d, unsigned r, unsigned g, unsigned b)\n{\n\tunsigned c = fb_val(r, g, b);\n\tint i;\n\tfor (i = 0; i < bpp; i++)\n\t\td[i] = (c >> (i << 3)) & 0xff;\n}\n\nstatic int iswhite(char *pix)\n{\n\tint val = 255 - invert;\n\tint i;\n\tfor (i = 0; i < 3 && i < bpp; i++)\n\t\tif (((unsigned char) pix[i]) != val)\n\t\t\treturn 0;\n\treturn 1;\n}\n\nstatic int rmargin(void)\n{\n\tint ret = 0;\n\tint i, j;\n\tfor (i = 0; i < prows; i++) {\n\t\tj = pcols - 1;\n\t\twhile (j > ret && iswhite(pbuf + (i * pcols + j) * bpp))\n\t\t\tj--;\n\t\tif (ret < j)\n\t\t\tret = j;\n\t}\n\treturn ret;\n}\n\nstatic int lmargin(void)\n{\n\tint ret = pcols;\n\tint i, j;\n\tfor (i = 0; i < prows; i++) {\n\t\tj = 0;\n\t\twhile (j < ret && iswhite(pbuf + (i * pcols + j) * bpp))\n\t\t\tj++;\n\t\tif (ret > j)\n\t\t\tret = j;\n\t}\n\treturn ret;\n}\n\nstatic void mainloop(void)\n{\n\tint step = srows / PAGESTEPS;\n\tint hstep = scols / PAGESTEPS;\n\tint c;\n\tterm_setup();\n\tsignal(SIGCONT, sigcont);\n\tloadpage(num);\n\tsrow = prow;\n\tscol = -scols / 2;\n\tdraw();\n\twhile ((c = readkey()) != -1) {\n\t\tif (c == 'q')\n\t\t\tbreak;\n\t\tif (c == 'e' && reload())\n\t\t\tbreak;\n\t\tswitch (c) {\t/* commands that do not require redrawing */\n\t\tcase 'o':\n\t\t\tnumdiff = num - getcount(num);\n\t\t\tbreak;\n\t\tcase 'Z':\n\t\t\tcount *= 10;\n\t\t\tzoom_def = getcount(zoom);\n\t\t\tbreak;\n\t\tcase 'i':\n\t\t\tprintinfo();\n\t\t\tbreak;\n\t\tcase 27:\n\t\t\tcount = 0;\n\t\t\tbreak;\n\t\tcase 'm':\n\t\t\tsetmark(readkey());\n\t\t\tbreak;\n\t\tcase 'd':\n\t\t\tsleep(getcount(1));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (isdigit(c))\n\t\t\t\tcount = count * 10 + c - '0';\n\t\t}\n\t\tswitch (c) {\t/* commands that require redrawing */\n\t\tcase CTRLKEY('f'):\n\t\tcase 'J':\n\t\t\tif (!loadpage(num + getcount(1)))\n\t\t\t\tsrow = prow;\n\t\t\tbreak;\n\t\tcase CTRLKEY('b'):\n\t\tcase 'K':\n\t\t\tif (!loadpage(num - getcount(1)))\n\t\t\t\tsrow = prow;\n\t\t\tbreak;\n\t\tcase 'G':\n\t\t\tsetmark('\\'');\n\t\t\tif (!loadpage(getcount(doc_pages(doc) - numdiff) + numdiff))\n\t\t\t\tsrow = prow;\n\t\t\tbreak;\n\t\tcase 'O':\n\t\t\tnumdiff = num - getcount(num);\n\t\t\tsetmark('\\'');\n\t\t\tif (!loadpage(num + numdiff))\n\t\t\t\tsrow = prow;\n\t\t\tbreak;\n\t\tcase 'z':\n\t\t\tcount *= 10;\n\t\t\tzoom_page(getcount(zoom_def));\n\t\t\tbreak;\n\t\tcase 'w':\n\t\t\tzoom_page(pcols ? zoom * scols / pcols : zoom);\n\t\t\tbreak;\n\t\tcase 'W':\n\t\t\tif (lmargin() < rmargin())\n\t\t\t\tzoom_page(zoom * (scols - hstep) /\n\t\t\t\t\t(rmargin() - lmargin()));\n\t\t\tbreak;\n\t\tcase 'f':\n\t\t\tzoom_page(prows ? zoom * srows / prows : zoom);\n\t\t\tbreak;\n\t\tcase 'r':\n\t\t\trotate = getcount(0);\n\t\t\tif (!loadpage(num))\n\t\t\t\tsrow = prow;\n\t\t\tbreak;\n\t\tcase '`':\n\t\tcase '\\'':\n\t\t\tjmpmark(readkey(), c == '`');\n\t\t\tbreak;\n\t\tcase 'j':\n\t\t\tsrow += step * getcount(1);\n\t\t\tbreak;\n\t\tcase 'k':\n\t\t\tsrow -= step * getcount(1);\n\t\t\tbreak;\n\t\tcase 'l':\n\t\t\tscol += hstep * getcount(1);\n\t\t\tbreak;\n\t\tcase 'h':\n\t\t\tscol -= hstep * getcount(1);\n\t\t\tbreak;\n\t\tcase 'H':\n\t\t\tsrow = prow;\n\t\t\tbreak;\n\t\tcase 'L':\n\t\t\tsrow = prow + prows - srows;\n\t\t\tbreak;\n\t\tcase 'M':\n\t\t\tsrow = prow + prows / 2 - srows / 2;\n\t\t\tbreak;\n\t\tcase 'C':\n\t\t\tscol = -scols / 2;\n\t\t\tbreak;\n\t\tcase ' ':\n\t\tcase CTRLKEY('d'):\n\t\t\tsrow += srows * getcount(1) - step;\n\t\t\tbreak;\n\t\tcase 127:\n\t\tcase CTRLKEY('u'):\n\t\t\tsrow -= srows * getcount(1) - step;\n\t\t\tbreak;\n\t\tcase '[':\n\t\t\tscol = pcol;\n\t\t\tbreak;\n\t\tcase ']':\n\t\t\tscol = pcol + pcols - scols;\n\t\t\tbreak;\n\t\tcase '{':\n\t\t\tscol = pcol + lmargin() - hstep / 2;\n\t\t\tbreak;\n\t\tcase '}':\n\t\t\tscol = pcol + rmargin() + hstep / 2 - scols;\n\t\t\tbreak;\n\t\tcase CTRLKEY('l'):\n\t\t\tbreak;\n\t\tcase 'I':\n\t\t\tinvert = count || !invert ? 255 - (getcount(48) & 0xff) : 0;\n\t\t\tloadpage(num);\n\t\t\tbreak;\n\t\tdefault:\t/* no need to redraw */\n\t\t\tcontinue;\n\t\t}\n\t\tsrow = MAX(prow - srows + MARGIN, MIN(prow + prows - MARGIN, srow));\n\t\tscol = MAX(pcol - scols + MARGIN, MIN(pcol + pcols - MARGIN, scol));\n\t\tdraw();\n\t}\n\tterm_cleanup();\n}\n\nstatic char *usage =\n\t\"usage: fbpdf [-r rotation] [-z zoom x10] [-p page] filename\";\n\nint main(int argc, char *argv[])\n{\n\tint i = 1;\n\tif (argc < 2) {\n\t\tputs(usage);\n\t\treturn 1;\n\t}\n\tstrcpy(filename, argv[argc - 1]);\n\tdoc = doc_open(filename);\n\tif (!doc || !doc_pages(doc)) {\n\t\tfprintf(stderr, \"fbpdf: cannot open <%s>\\n\", filename);\n\t\treturn 1;\n\t}\n\tfor (i = 1; i < argc && argv[i][0] == '-'; i++) {\n\t\tswitch (argv[i][1]) {\n\t\tcase 'r':\n\t\t\trotate = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);\n\t\t\tbreak;\n\t\tcase 'z':\n\t\t\tzoom = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]) * 10;\n\t\t\tbreak;\n\t\tcase 'p':\n\t\t\tnum = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);\n\t\t\tbreak;\n\t\t}\n\t}\n\tprintinfo();\n\tif (fb_init(getenv(\"FBDEV\")))\n\t\treturn 1;\n\tsrows = fb_rows();\n\tscols = fb_cols();\n\tbpp = FBM_BPP(fb_mode());\n\tmainloop();\n\tfb_free();\n\tfree(pbuf);\n\tif (doc)\n\t\tdoc_close(doc);\n\treturn 0;\n}\n"
  },
  {
    "path": "mupdf.c",
    "content": "#include <stdlib.h>\n#include <string.h>\n#include \"mupdf/fitz.h\"\n#include \"doc.h\"\n\n#define MIN_(a, b)\t((a) < (b) ? (a) : (b))\n\nstruct doc {\n\tfz_context *ctx;\n\tfz_document *pdf;\n};\n\nvoid *doc_draw(struct doc *doc, int page, int zoom, int rotate, int bpp, int *rows, int *cols)\n{\n\tfz_matrix ctm;\n\tfz_pixmap *pix;\n\tchar *pbuf;\n\tint x, y;\n\tctm = fz_scale((float) zoom / 100, (float) zoom / 100);\n\tctm = fz_pre_rotate(ctm, rotate);\n\tpix = fz_new_pixmap_from_page_number(doc->ctx, doc->pdf,\n\t\t\tpage - 1, ctm, fz_device_rgb(doc->ctx), 0);\n\tif (!pix)\n\t\treturn NULL;\n\tif (!(pbuf = malloc(pix->w * pix->h * bpp))) {\n\t\tfz_drop_pixmap(doc->ctx, pix);\n\t\treturn NULL;\n\t}\n\tfor (y = 0; y < pix->h; y++) {\n\t\tunsigned char *s = &pix->samples[y * pix->stride];\n\t\tchar *d = pbuf + (y * pix->w) * bpp;\n\t\tfor (x = 0; x < pix->w; x++)\n\t\t\tfb_set(d + x * bpp, s[x * pix->n], s[x * pix->n + 1], s[x * pix->n + 2]);\n\t}\n\tfz_drop_pixmap(doc->ctx, pix);\n\t*cols = pix->w;\n\t*rows = pix->h;\n\treturn pbuf;\n}\n\nint doc_pages(struct doc *doc)\n{\n\treturn fz_count_pages(doc->ctx, doc->pdf);\n}\n\nstruct doc *doc_open(char *path)\n{\n\tstruct doc *doc = malloc(sizeof(*doc));\n\tdoc->ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);\n\tfz_register_document_handlers(doc->ctx);\n\tfz_try (doc->ctx) {\n\t\tdoc->pdf = fz_open_document(doc->ctx, path);\n\t} fz_catch (doc->ctx) {\n\t\tfz_drop_context(doc->ctx);\n\t\tfree(doc);\n\t\treturn NULL;\n\t}\n\treturn doc;\n}\n\nvoid doc_close(struct doc *doc)\n{\n\tfz_drop_document(doc->ctx, doc->pdf);\n\tfz_drop_context(doc->ctx);\n\tfree(doc);\n}\n"
  },
  {
    "path": "poppler.c",
    "content": "#include <stdlib.h>\n#include <string.h>\n#include <poppler/cpp/poppler-document.h>\n#include <poppler/cpp/poppler-image.h>\n#include <poppler/cpp/poppler-page.h>\n#include <poppler/cpp/poppler-page-renderer.h>\n\n#define MIN(a, b)\t((a) < (b) ? (a) : (b))\n\nextern \"C\" {\n#include \"doc.h\"\n}\n\nstruct doc {\n\tpoppler::document *doc;\n};\n\nstatic poppler::rotation_enum rotation(int times)\n{\n\tif (times == 1)\n\t\treturn poppler::rotate_90;\n\tif (times == 2)\n\t\treturn poppler::rotate_180;\n\tif (times == 3)\n\t\treturn poppler::rotate_270;\n\treturn poppler::rotate_0;\n}\n\nvoid *doc_draw(struct doc *doc, int p, int zoom, int rotate, int bpp, int *rows, int *cols)\n{\n\tpoppler::page *page = doc->doc->create_page(p - 1);\n\tpoppler::page_renderer pr;\n\tint x, y;\n\tint h, w;\n\tchar *pbuf;\n\tunsigned char *dat;\n\tpr.set_render_hint(poppler::page_renderer::antialiasing, true);\n\tpr.set_render_hint(poppler::page_renderer::text_antialiasing, true);\n\tpoppler::image img = pr.render_page(page,\n\t\t\t\t(float) 72 * zoom / 100, (float) 72 * zoom / 100,\n\t\t\t\t-1, -1, -1, -1, rotation((rotate + 89) / 90));\n\th = img.height();\n\tw = img.width();\n\tdat = (unsigned char *) img.data();\n\tif (!(pbuf = (char *) malloc(h * w * bpp))) {\n\t\tdelete page;\n\t\treturn NULL;\n\t}\n\tfor (y = 0; y < h; y++) {\n\t\tunsigned char *s = dat + img.bytes_per_row() * y;\n\t\tchar *d = pbuf + (y * w) * bpp;\n\t\tfor (x = 0; x < w; x++)\n\t\t\tfb_set(d + x * bpp, s[x * 4 + 2], s[x * 4 + 1], s[x * 4]);\n\t}\n\t*rows = h;\n\t*cols = w;\n\tdelete page;\n\treturn pbuf;\n}\n\nint doc_pages(struct doc *doc)\n{\n\treturn doc->doc->pages();\n}\n\nstruct doc *doc_open(char *path)\n{\n\tstruct doc *doc = (struct doc *) malloc(sizeof(*doc));\n\tif (doc == NULL)\n\t\treturn NULL;\n\tdoc->doc = poppler::document::load_from_file(path);\n\tif (!doc->doc) {\n\t\tdoc_close(doc);\n\t\treturn NULL;\n\t}\n\treturn doc;\n}\n\nvoid doc_close(struct doc *doc)\n{\n\tdelete doc->doc;\n\tfree(doc);\n}\n"
  }
]