continuous pages
[fbpdf.git] / fbpdf.c
blobd447c2e3a8d092f1d2b8899c301aa56fdbb9b649
1 /*
2 * fbpdf - a small framebuffer pdf viewer using mupdf
4 * Copyright (C) 2009-2014 Ali Gholami Rudi <ali at rudi dot ir>
6 * This program is released under the Modified BSD license.
7 */
8 #include <ctype.h>
9 #include <signal.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <pty.h>
15 #include "draw.h"
16 #include "doc.h"
18 #define MIN(a, b) ((a) < (b) ? (a) : (b))
19 #define MAX(a, b) ((a) > (b) ? (a) : (b))
21 #define PAGESTEPS 8
22 #define CTRLKEY(x) ((x) - 96)
23 #define ISMARK(x) (isalpha(x) || (x) == '\'' || (x) == '`')
24 #define MAXWIDTH 2
25 #define MAXHEIGHT 3
26 #define PDFCOLS (1 << 11)
27 #define PDFROWS (1 << 12)
28 #define MAXZOOM (100)
29 #define NBUFS 4
31 static fbval_t pbuf[NBUFS][PDFROWS * PDFCOLS]; /* rendered pages */
32 static int prows[NBUFS]; /* buf heights */
33 static int pcols[NBUFS]; /* buf widths */
34 static int ppage[NBUFS]; /* buf page number */
35 static struct doc *doc;
37 static struct termios termios;
38 static char filename[256];
39 static int mark[128]; /* mark page number */
40 static int mark_head[128]; /* mark head position */
41 static int num = 1; /* page number */
42 static int numdiff; /* G command page number difference */
43 static int zoom = 15;
44 static int zoom_def = 15; /* default zoom */
45 static int rotate;
46 static int head;
47 static int left;
48 static int count;
50 static void draw(void)
52 int i, j;
53 int soff = 0; /* screen position */
54 int poff = head; /* page position */
55 for (j = 0; j < NBUFS && ppage[j] && soff < PDFROWS; j++) {
56 for (i = poff; soff + i - poff < fb_rows() && i < prows[j]; i++)
57 fb_set(soff + i - poff, 0, pbuf[j] + i * PDFCOLS + left, fb_cols());
58 soff += prows[j] - poff;
59 poff = 0;
61 /* blanking the rest of the screen */
62 for (; soff < fb_rows(); soff++)
63 fb_set(soff, 0, pbuf[0] + (prows[0] - 1) * PDFCOLS, fb_cols());
66 static int showpage(int p, int h)
68 int i;
69 int rows = 0;
70 if (p < 1 || p > doc_pages(doc))
71 return 0;
72 memset(ppage, 0, sizeof(ppage));
73 for (i = 0; i < NBUFS && p + i <= doc_pages(doc); i++) {
74 prows[i] = PDFROWS;
75 pcols[i] = PDFCOLS;
76 ppage[i] = p + i;
77 memset(pbuf[i], 0x00, sizeof(pbuf[i]));
78 doc_draw(doc, p + i, zoom, rotate, pbuf[i], &prows[i], &pcols[i]);
79 /* leaving one blank row */
80 if (prows[i] < PDFROWS)
81 prows[i]++;
82 rows += prows[i];
83 if (rows >= PDFROWS || rows - prows[0] >= fb_rows())
84 break;
86 num = p;
87 head = h;
88 draw();
89 return 0;
93 static void zoom_page(int z)
95 int _zoom = zoom;
96 zoom = MIN(MAXZOOM, MAX(1, z));
97 showpage(num, MIN(PDFROWS - fb_rows(), head * zoom / _zoom));
100 static void setmark(int c)
102 if (ISMARK(c)) {
103 mark[c] = num;
104 mark_head[c] = head / zoom;
108 static void jmpmark(int c, int offset)
110 if (c == '`')
111 c = '\'';
112 if (ISMARK(c) && mark[c]) {
113 int dst = mark[c];
114 int dst_head = offset ? mark_head[c] * zoom : 0;
115 setmark('\'');
116 showpage(dst, dst_head);
120 static void scroll(int hdiff)
122 int pdiff, nhead;
123 if ((hdiff < 0 && -hdiff <= head) || (hdiff >= 0 && head + hdiff < prows[0])) {
124 head += hdiff;
125 draw();
126 } else {
127 if (hdiff > 0) {
128 pdiff = (hdiff + head) / prows[0];
129 nhead = (hdiff + head) % prows[0];
130 } else {
131 pdiff = (hdiff + head) / prows[0] - 1;
132 nhead = (prows[0] * -pdiff + hdiff + head) % prows[0];
134 showpage(num + pdiff, nhead);
138 static int readkey(void)
140 unsigned char b;
141 if (read(0, &b, 1) <= 0)
142 return -1;
143 return b;
146 static int getcount(int def)
148 int result = count ? count : def;
149 count = 0;
150 return result;
153 static void printinfo(void)
155 printf("\x1b[H");
156 printf("FBPDF: file:%s page:%d(%d) zoom:%d%% \x1b[K\r",
157 filename, num, doc_pages(doc), zoom * 10);
158 fflush(stdout);
161 static void term_setup(void)
163 struct termios newtermios;
164 tcgetattr(0, &termios);
165 newtermios = termios;
166 newtermios.c_lflag &= ~ICANON;
167 newtermios.c_lflag &= ~ECHO;
168 tcsetattr(0, TCSAFLUSH, &newtermios);
169 printf("\x1b[?25l"); /* hide the cursor */
170 printf("\x1b[2J"); /* clear the screen */
171 fflush(stdout);
174 static void term_cleanup(void)
176 tcsetattr(0, 0, &termios);
177 printf("\x1b[?25h\n"); /* show the cursor */
180 static void sigcont(int sig)
182 term_setup();
185 static int reload(void)
187 doc_close(doc);
188 doc = doc_open(filename);
189 if (!doc) {
190 fprintf(stderr, "\nfbpdf: cannot open <%s>\n", filename);
191 return 1;
193 showpage(num, head);
194 return 0;
197 static int rightmost(int cont)
199 int ret = 0;
200 int i, j;
201 for (i = 0; i < prows[0]; i++) {
202 j = PDFCOLS - 1;
203 while (j > ret && pbuf[0][i * PDFCOLS + j] == FB_VAL(0, 0, 0))
204 j--;
205 while (cont && j > ret &&
206 pbuf[0][i * PDFCOLS + j] == FB_VAL(255, 255, 255))
207 j--;
208 if (ret < j)
209 ret = j;
211 return ret;
214 static int leftmost(int cont)
216 int ret = PDFCOLS;
217 int i, j;
218 for (i = 0; i < prows[0]; i++) {
219 j = 0;
220 while (j < ret && pbuf[0][i * PDFCOLS + j] == FB_VAL(0, 0, 0))
221 j++;
222 while (cont && j < ret &&
223 pbuf[0][i * PDFCOLS + j] == FB_VAL(255, 255, 255))
224 j++;
225 if (ret > j)
226 ret = j;
228 return ret;
231 static void mainloop(void)
233 int step = fb_rows() / PAGESTEPS;
234 int hstep = fb_cols() / PAGESTEPS;
235 int c;
236 int hdiff;
237 term_setup();
238 signal(SIGCONT, sigcont);
239 showpage(num, 0);
240 while ((c = readkey()) != -1) {
241 if (c == 'q')
242 break;
243 if (c == 'e' && reload())
244 break;
245 switch (c) {
246 case CTRLKEY('f'):
247 case 'J':
248 showpage(num + getcount(1), 0);
249 break;
250 case CTRLKEY('b'):
251 case 'K':
252 showpage(num - getcount(1), 0);
253 break;
254 case 'G':
255 setmark('\'');
256 showpage(getcount(doc_pages(doc) - numdiff) + numdiff, 0);
257 break;
258 case 'O':
259 numdiff = num - getcount(num);
260 setmark('\'');
261 showpage(num + numdiff, 0);
262 break;
263 case 'o':
264 numdiff = num - getcount(num);
265 break;
266 case 'z':
267 zoom_page(getcount(zoom_def));
268 break;
269 case 'Z':
270 zoom_def = getcount(zoom);
271 break;
272 case 'w':
273 zoom_page(zoom * fb_cols() / pcols[0]);
274 break;
275 case 'W':
276 if (leftmost(1) < rightmost(1))
277 zoom_page(zoom * (fb_cols() - hstep) /
278 (rightmost(1) - leftmost(1)));
279 break;
280 case 'f':
281 zoom_page(zoom * fb_rows() / prows[0]);
282 break;
283 case 'r':
284 rotate = getcount(0);
285 showpage(num, 0);
286 break;
287 case 'i':
288 printinfo();
289 break;
290 case 27:
291 count = 0;
292 break;
293 case 'm':
294 setmark(readkey());
295 break;
296 case '`':
297 case '\'':
298 jmpmark(readkey(), c == '`');
299 break;
300 case 'd':
301 sleep(getcount(1));
302 break;
303 default:
304 if (isdigit(c))
305 count = count * 10 + c - '0';
307 hdiff = 0;
308 switch (c) {
309 case 'j':
310 hdiff = step * getcount(1);
311 break;
312 case 'k':
313 hdiff = -step * getcount(1);
314 break;
315 case 'l':
316 left += hstep * getcount(1);
317 break;
318 case 'h':
319 left -= hstep * getcount(1);
320 break;
321 case 'H':
322 hdiff = -head;
323 break;
324 case 'L':
325 hdiff = prows[0] - fb_rows() - head;
326 break;
327 case 'M':
328 hdiff = prows[0] / 2 - head;
329 break;
330 case 'C':
331 left = (PDFCOLS - fb_cols()) / 2;
332 break;
333 case ' ':
334 case CTRL('d'):
335 hdiff = fb_rows() * getcount(1) - step;
336 break;
337 case 127:
338 case CTRL('u'):
339 hdiff = -(fb_rows() * getcount(1) - step);
340 break;
341 case '[':
342 left = leftmost(0);
343 break;
344 case ']':
345 left = rightmost(0) - fb_cols();
346 break;
347 case '{':
348 left = leftmost(1) - hstep / 2;
349 break;
350 case '}':
351 left = rightmost(1) + hstep / 2 - fb_cols();
352 break;
353 case CTRLKEY('l'):
354 break;
355 default:
356 /* no need to redraw */
357 continue;
359 left = MAX(0, MIN(PDFCOLS - fb_cols(), left));
360 scroll(hdiff);
362 term_cleanup();
365 static char *usage =
366 "usage: fbpdf [-r rotation] [-z zoom x10] [-p page] filename\n";
368 int main(int argc, char *argv[])
370 int i = 1;
371 if (argc < 2) {
372 printf(usage);
373 return 1;
375 strcpy(filename, argv[argc - 1]);
376 doc = doc_open(filename);
377 if (!doc) {
378 fprintf(stderr, "fbpdf: cannot open <%s>\n", filename);
379 return 1;
381 for (i = 1; i < argc && argv[i][0] == '-'; i++) {
382 switch (argv[i][1]) {
383 case 'r':
384 rotate = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
385 break;
386 case 'z':
387 zoom = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
388 break;
389 case 'p':
390 num = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
391 break;
394 printinfo();
395 if (fb_init())
396 return 1;
397 left = (PDFCOLS - fb_cols()) / 2;
398 if (FBM_BPP(fb_mode()) != sizeof(fbval_t))
399 fprintf(stderr, "fbpdf: fbval_t doesn't match fb depth\n");
400 else
401 mainloop();
402 fb_free();
403 if (doc)
404 doc_close(doc);
405 return 0;