From 0d6294e45d7315b6b4c340d4f141ca3d458a0941 Mon Sep 17 00:00:00 2001 From: malc Date: Sat, 12 Jun 2010 21:56:57 +0400 Subject: [PATCH] More or less works now --- build.ml | 2 +- build.sh | 21 ++ link.c | 870 ++++++++++++++++++++++++++++++++++++----------------- main.ml | 1003 +++++++++++++++++++++++++++++++++++++++++++++++++------------- tbs | 7 +- 5 files changed, 1430 insertions(+), 473 deletions(-) create mode 100644 build.sh diff --git a/build.ml b/build.ml index a6b5140..7284b53 100644 --- a/build.ml +++ b/build.ml @@ -97,7 +97,7 @@ let () = cmopp ~flags:"-g -w y -I +lablGL -thread" ~dirname:srcdir "main"; "main.cmo" in - prog "lpdf" [so; main]; + prog "llpp" [so; main]; ;; let () = diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..d5610ce --- /dev/null +++ b/build.sh @@ -0,0 +1,21 @@ +srcpath=$(dirname $0) + +mupdf=/home/malc/x/rcs/svn/sumatrapdf-read-only/mupdf + +mupdflibpath=$mupdf/build/release +mupdfincpath=$mupdf + +cclib="-lmupdf -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype" + +export LIBRARY_PATH=$LIBRARY_PATH:$mupdflibpath +export CPATH=$CPATH:$mupdfincpath + +ocamlc -c -o link.o -ccopt -O $srcpath/link.c +ocamlc -c -o main.cmo -I +lablGL $srcpath/main.ml + +ocamlc -custom -o llpp \ +-I +lablGL \ +unix.cma lablgl.cma lablglut.cma \ +link.o \ +-cclib "$cclib" \ +main.cmo diff --git a/link.c b/link.c index cebea2a..7ca7684 100644 --- a/link.c +++ b/link.c @@ -1,20 +1,25 @@ +/* lot's of code c&p-ed directly from mupdf */ + #define _GNU_SOURCE -#define GL_GLEXT_PROTOTYPES #include +#include #include +#include #include #include #include #include -#include #include -#include #include -#include +#include + +/* fugly as hell and GCC specific but... */ +#ifdef _BIG_ENDIAN +#define GL_GLEXT_PROTOTYPES +#endif #include #include -#include #include #include @@ -24,54 +29,97 @@ #include "fitz/fitz.h" #include "mupdf/mupdf.h" -#include +#if 0 +#define lprintf printf +#else +#define lprintf(...) +#endif -static long pagesize; +#define ARSERT(cond) for (;;) { \ + if (!(cond)) { \ + errx (1, "%s:%d " #cond, __FILE__, __LINE__); \ + } \ + break; \ +} struct slice { int texindex; - int h; + int w, h; }; struct page { - int pagenum; + int pageno; + int slicecount; + fz_textspan *text; fz_pixmap *pixmap; - struct page2 *page2; + pdf_page *drawpage; + struct pagedim *pagedim; struct page *prev; struct slice slices[]; }; -struct page2 { +struct pagedim { + int pageno; fz_bbox bbox; fz_matrix ctm; - fz_pixmap pixmap; - int pagenum; }; struct { int sock; + int sliceheight; pthread_t thread; struct page *pages; - struct page2 *pages2; - int page; + struct pagedim *pagedims; int pagecount; + int pagedimcount; pdf_xref *xref; fz_glyphcache *cache; int w, h; - Display *dpy; - GLXContext ctx; - GLXDrawable drawable; + + int useatifs; int texindex; int texcount; - int slicecount; GLuint *texids; + + GLenum texform; + GLenum texty; + struct { - int h; + int w, h; struct slice *slice; } *texowners; } state; +static void *parse_pointer (const char *cap, const char *s) +{ + int ret; + void *ptr; + + ret = sscanf (s, "%p", &ptr); + if (ret != 1) { + errx (1, "%s: cannot parse pointer in `%s'", cap, s); + } + return ptr; +} + +static int hasdata (int sock) +{ + int ret; + struct pollfd pfd; + + pfd.fd = sock; + pfd.events = POLLIN; + ret = poll (&pfd, 1, 0); + if (ret == 0) { + return 0; + } + if (ret != 1) { + err (1, "poll"); + } + return pfd.revents & POLLIN; +} + static double now (void) { struct timeval tv; @@ -126,92 +174,32 @@ static void __attribute__ ((format (printf, 2, 3))) writedata (fd, buf, len); } -static void closexref (void); - -static void createmmap (struct page *page) +static void die (fz_error error) { - int fd, ret; - size_t size; - - size = page->pixmap->w * page->pixmap->h * 4; - - fd = open ("pdfmap", O_CREAT|O_TRUNC|O_RDWR); - if (fd == -1) { - err (1, "open"); - } - ret = unlink ("pdfmap"); - if (ret) { - err (1, "unlink"); - } - size = (size + pagesize - 1) & ~(pagesize - 1); - ret = ftruncate (fd, size); - if (ret) { - err (1, "ftruncate"); - } - page->pixmap->samples = mmap (NULL, size, PROT_READ|PROT_WRITE, - MAP_PRIVATE, fd, 0); - if (page->pixmap->samples == MAP_FAILED) { - err (1, "mmap"); - } + fz_catch (error, "aborting"); + pdf_closexref (state.xref); + exit (1); } -static void die(fz_error error) +void openxref (char *filename) { - fz_catch(error, "aborting"); - closexref(); - exit(1); -} - -void openxref(char *filename, char *password, int dieonbadpass) -{ - fz_stream *file; - int okay; int fd; - char *basename; - - basename = strrchr(filename, '/'); - if (!basename) - basename = filename; - else - basename++; + fz_stream *file; - fd = open(filename, O_BINARY | O_RDONLY, 0666); + fd = open (filename, O_BINARY | O_RDONLY, 0666); if (fd < 0) - die(fz_throw("cannot open file '%s'", filename)); + die (fz_throw ("cannot open file '%s'", filename)); - file = fz_openfile(fd); - state.xref = pdf_openxref(file); + file = fz_openfile (fd); + state.xref = pdf_openxref (file); if (!state.xref) - die(fz_throw("cannot open PDF file '%s'", basename)); - fz_dropstream(file); + die (fz_throw ("cannot open PDF file '%s'", filename)); + fz_dropstream (file); - if (pdf_needspassword(state.xref)) - { - okay = pdf_authenticatepassword(state.xref, password); - if (!okay && !dieonbadpass) - fz_warn("invalid password, attempting to continue."); - else if (!okay && dieonbadpass) - die(fz_throw("invalid password")); - } - - state.pagecount = pdf_getpagecount(state.xref); -} - -static void flushxref(void) -{ - if (state.xref) - { - pdf_flushxref(state.xref, 0); - } -} - -static void closexref(void) -{ - if (state.xref) - { - pdf_closexref(state.xref); - state.xref = nil; + if (pdf_needspassword (state.xref)) { + die (fz_throw ("password protected")); } + state.pagecount = pdf_getpagecount (state.xref); } static int readlen (int fd) @@ -239,10 +227,21 @@ static void freepage (struct page *page) break; } } - for (i = 0; i < state.slicecount; ++i) { - struct slice *s = &p->slices[i]; - state.texowners[s->texindex].slice = NULL; - state.texowners[s->texindex].h = s->h; + for (i = 0; i < page->slicecount; ++i) { + struct slice *s = &page->slices[i]; + if (s->texindex != -1) { + if (state.texowners[s->texindex].slice == s) { + state.texowners[s->texindex].slice = NULL; + ARSERT (state.texowners[s->texindex].w == s->w); + ARSERT (state.texowners[s->texindex].h >= s->h); + } + } + } + if (page->text) { + fz_freetextspan (page->text); + } + if (page->drawpage) { + pdf_droppage (page->drawpage); } free (page); } @@ -251,101 +250,107 @@ static void subdivide (struct page *p) { int i; int h = p->pixmap->h; - int th = (h + state.slicecount - 1) / state.slicecount; + int th = MIN (h, state.sliceheight); - for (i = 0; i < state.slicecount; ++i) { + for (i = 0; i < p->slicecount; ++i) { struct slice *s = &p->slices[i]; s->texindex = -1; s->h = MIN (th, h); + s->w = p->pixmap->w; h -= th; } } -static void *render (int pagenum, int pindex) +static void *render (int pageno, int pindex) { fz_error error; + int slicecount; fz_obj *pageobj; - int w, h; - float zoom; struct page *page; - struct page2 *page2; - fz_device *idev, *tdev, *mdev; - fz_displaylist *list; + double start, end; pdf_page *drawpage; + fz_displaylist *list; + fz_device *idev, *mdev; + struct pagedim *pagedim; + start = now (); + /* printd (state.sock, "T \"rendering %d\"", pageno); */ pdf_flushxref (state.xref, 0); + pagedim = &state.pagedims[pindex]; + slicecount = (pagedim->bbox.y1 - pagedim->bbox.y0 + + state.sliceheight - 1) / state.sliceheight; + slicecount += slicecount == 0; + page = calloc (sizeof (*page) - + (state.slicecount * sizeof (struct slice)), 1); + + (slicecount * sizeof (struct slice)), 1); if (!page) { - err (1, "malloc page %d\n", pagenum); + err (1, "calloc page %d\n", pageno); } + page->slicecount = slicecount; page->prev = state.pages; state.pages = page; - page2 = &state.pages2[pindex]; - - pageobj = pdf_getpageobject(state.xref, pagenum); + pageobj = pdf_getpageobject (state.xref, pageno); if (!pageobj) - die (fz_throw ("cannot retrieve info from page %d", pagenum)); + die (fz_throw ("cannot retrieve info from page %d", pageno)); - error = pdf_loadpage(&drawpage, state.xref, pageobj); + error = pdf_loadpage (&drawpage, state.xref, pageobj); if (error) - die(error); + die (error); - page->pixmap = fz_newpixmapwithrect (pdf_devicergb, page2->bbox); + page->pixmap = fz_newpixmapwithrect (pdf_devicergb, pagedim->bbox); if (error) die (error); -#if 0 - fz_free (page->pixmap->samples); - createmmap (page); -#endif - fz_clearpixmap(page->pixmap, 0xFF); + fz_clearpixmap (page->pixmap, 0xFF); list = fz_newdisplaylist (); if (!list) die (fz_throw ("fz_newdisplaylist failed")); - mdev = fz_newlistdevice(list); - error = pdf_runcontentstream(mdev, fz_identity(), state.xref, - drawpage->resources, - drawpage->contents); + mdev = fz_newlistdevice (list); + error = pdf_runcontentstream (mdev, fz_identity (), state.xref, + drawpage->resources, + drawpage->contents); if (error) die (error); - - fz_freedevice(mdev); + fz_freedevice (mdev); idev = fz_newdrawdevice (state.cache, page->pixmap); if (!idev) die (fz_throw ("fz_newdrawdevice failed")); + fz_executedisplaylist (list, idev, pagedim->ctm); + fz_freedevice (idev); - fz_executedisplaylist(list, idev, page2->ctm); - fz_freedevice(idev); - fz_freedisplaylist(list); + fz_freedisplaylist (list); - /* fz_debugpixmap (page->pixmap, "haha"); */ - pdf_droppage (drawpage); - page->page2 = page2; - page->pagenum = pagenum; + page->drawpage = drawpage; + page->pagedim = pagedim; + page->pageno = pageno; subdivide (page); + end = now (); + + /* printd (state.sock, "T \"rendering %d took %f sec\"", pageno, end - start); */ return page; } -static void layout1 (void) +static void layout (void) { - int pagenum; - double a, b; + int pageno; + double a, b, c, d; int prevrotate; fz_rect prevbox; int i, pindex; asize_t size; int64 mapsize; - struct page2 *p; + struct pagedim *p; size = 0; pindex = 0; mapsize = 0; a = now (); - for (pagenum = 1; pagenum <= state.pagecount; ++pagenum) { + c = 0.0; + printd (state.sock, "c"); + for (pageno = 1; pageno <= state.pagecount; ++pageno) { float w; float zoom; int rotate; @@ -354,29 +359,35 @@ static void layout1 (void) fz_rect box2; fz_matrix ctm; fz_bbox bbox; - fz_error error; fz_obj *pageobj; - pageobj = pdf_getpageobject (state.xref, pagenum); + if (!(pageno & 31)) { + if (!c) { + c = a; + } + d = now (); + printd (state.sock, "T \"processing page %d %f\"", pageno, d - c); + c = d; + } + pageobj = pdf_getpageobject (state.xref, pageno); if (!pageobj) - die (fz_throw ("cannot retrieve info from page %d", pagenum)); + die (fz_throw ("cannot retrieve info from page %d", pageno)); obj = fz_dictgets (pageobj, "CropBox"); - if (!fz_isarray(obj)) { + if (!fz_isarray (obj)) { obj = fz_dictgets (pageobj, "MediaBox"); if (!fz_isarray (obj)) die (fz_throw ("cannot find page bounds %d (%d R)", fz_tonum (obj), fz_togen (obj))); } box = pdf_torect (obj); - obj = fz_dictgets (pageobj, "Rotate"); if (fz_isint (obj)) rotate = fz_toint (obj); else rotate = 0; - if (pagenum != 1 + if (pageno != 1 && (prevrotate == rotate && !memcmp (&prevbox, &box, sizeof (box)))) { continue; @@ -403,31 +414,174 @@ static void layout1 (void) ctm = fz_concat (ctm, fz_rotate (rotate)); bbox = fz_roundrect (fz_transformrect (ctm, box)); - size += sizeof (*state.pages2); - state.pages2 = caml_stat_resize (state.pages2, size); + size += sizeof (*state.pagedims); + state.pagedims = caml_stat_resize (state.pagedims, size); - p = &state.pages2[pindex++]; + p = &state.pagedims[pindex++]; memcpy (&p->bbox, &bbox, sizeof (bbox)); memcpy (&p->ctm, &ctm, sizeof (ctm)); - p->pagenum = pagenum - 1; - p->pixmap.x = bbox.x0; - p->pixmap.y = bbox.y0; - p->pixmap.w = bbox.x1 - bbox.x0; - p->pixmap.h = bbox.y1 - bbox.y0; - p->pixmap.n = 4; + p->pageno = pageno - 1; } + state.pagedimcount = pindex; for (i = pindex - 1; i >= 0; --i) { - p = &state.pages2[i]; + p = &state.pagedims[i]; printd (state.sock, "l %d %d %d", - p->pagenum, p->pixmap.w, p->pixmap.h); + p->pageno, p->bbox.x1 - p->bbox.x0, p->bbox.y1 - p->bbox.y0); } b = now (); + printd (state.sock, "T \"Processed %d pages in %f secons\"", + state.pagecount, b - a); printd (state.sock, "C %d", state.pagecount); } +/* wishful thinking function */ +static void search (regex_t *re, int pageno, int y, int forward) +{ + int i; + int ret; + char *p; + char buf[256]; + fz_error error; + fz_obj *pageobj; + fz_device *tdev; + pdf_page *drawpage; + fz_textspan *text, *span; + struct pagedim *pdim, *pdimprev; + int stop = 0; + int niters = 0; + double start, end; + + start = now (); + while (pageno >= 0 && pageno < state.pagecount && !stop) { + if (niters++ == 5) { + niters = 0; + if (hasdata (state.sock)) { + printd (state.sock, "T \"attention requested aborting search at %d\"", + pageno); + stop = 1; + } + else { + printd (state.sock, "T \"searching in page %d\"", pageno); + } + } + pdimprev = NULL; + for (i = 0; i < state.pagedimcount; ++i) { + pdim = &state.pagedims[i]; + if (pdim->pageno == pageno) { + goto found; + } + if (pdim->pageno > pageno) { + pdim = pdimprev; + goto found; + } + pdimprev = pdim; + } + pdim = pdimprev; + found: + + pageobj = pdf_getpageobject (state.xref, pageno + 1); + if (!pageobj) + die (fz_throw ("cannot retrieve info from page %d", pageno)); + + error = pdf_loadpage (&drawpage, state.xref, pageobj); + if (error) + die (error); + + text = fz_newtextspan (); + tdev = fz_newtextdevice (text); + error = pdf_runcontentstream (tdev, pdim->ctm, state.xref, + drawpage->resources, + drawpage->contents); + if (error) die (error); + fz_freedevice (tdev); + + for (span = text; span; span = span->next) { + regmatch_t rm; + + p = buf; + /* XXX: spans are not sorted "visually" */ + for (i = 0; i < MIN (span->len, sizeof (buf) - 1); ++i) { + if (forward) { + if (span->text[i].bbox.y0 < y + 1) { + continue; + } + } + else { + if (span->text[i].bbox.y0 > y - 1) { + continue; + } + } + if (span->text[i].c < 256) { + *p++ = span->text[i].c; + } + else { + *p++ = '?'; + } + } + if (p == buf) { + continue; + } + *p++ = 0; + + ret = regexec (re, buf, 1, &rm, 0); + if (ret) { + if (ret != REG_NOMATCH) { + size_t size; + char errbuf[80]; + size = regerror (ret, re, errbuf, sizeof (errbuf)); + printd (state.sock, + "T \"regexec error `%.*s'\"", + (int) size, errbuf); + } + } + else { + fz_rect r; + + r.x0 = span->text[rm.rm_so].bbox.x0 - pdim->bbox.x0; + r.y0 = span->text[rm.rm_so].bbox.y0; + r.x1 = span->text[rm.rm_eo - 1].bbox.x1 - pdim->bbox.x0; + r.y1 = span->text[rm.rm_eo - 1].bbox.y1; + + if (!stop) { + printd (state.sock, "F %d %d %f %f %f %f", + pageno, 1, + r.x0, r.y0, + r.x1, r.y1); + } + else { + printd (state.sock, "R %d %d %f %f %f %f", + pageno, 2, + r.x0, r.y0, + r.x1, r.y1); + } + printd (state.sock, "T \"found at %d `%.*s' %f in %f sec\"", + pageno, rm.rm_eo - rm.rm_so, &buf[rm.rm_so], + span->text[0].bbox.y0 - drawpage->mediabox.y0, + now () - start); + stop = 1; + } + } + if (forward) { + pageno += 1; + y = 0; + } + else { + pageno -= 1; + y = INT_MAX; + } + fz_freetextspan (text); + pdf_droppage (drawpage); + } + end = now (); + if (!stop) { + printd (state.sock, "T \"no matches %f sec\"", end - start); + } + printd (state.sock, "d"); +} + static void *mainloop (void *unused) { char *p = NULL; @@ -452,26 +606,46 @@ static void *mainloop (void *unused) if (!strncmp ("open", p, 4)) { char *filename = p + 5; - openxref (filename, NULL, 1); + openxref (filename); } else if (!strncmp ("free", p, 4)) { void *ptr; - ret = sscanf(p + 4, " %p", &ptr); + ret = sscanf (p + 4, " %p", &ptr); + if (ret != 1) { + errx (1, "malformed free `%.*s' ret=%d", len, p, ret); + } freepage (ptr); printd (state.sock, "f"); } - else if (!strncmp ("layout", p, 6)) { - int y; + else if (!strncmp ("search", p, 6)) { + int icase, pageno, y, ret, len2, forward; + char *pattern; + regex_t re; - ret = sscanf (p + 6, " %d", &y); - if (ret != 1) { - errx (1, "malformed layout `%.*s' ret=%d", len, p, ret); + ret = sscanf (p + 6, " %d %d %d %d %n", + &icase, &pageno, &y, &forward, &len2); + if (ret != 4) { + errx (1, "malformed search `%s' ret=%d", p, ret); + } + + pattern = p + 6 + len2; + ret = regcomp (&re, pattern, + REG_EXTENDED | (icase ? REG_ICASE : 0)); + if (ret) { + char errbuf[80]; + size_t size; + + size = regerror (ret, &re, errbuf, sizeof (errbuf)); + printd (state.sock, "T \"regcomp failed `%.*s'\"", (int) size, errbuf); + } + else { + search (&re, pageno, y, forward); + regfree (&re); } } else if (!strncmp ("geometry", p, 8)) { int w, h; - struct page *page; ret = sscanf (p + 8, " %d %d", &w, &h); if (ret != 2) { @@ -479,37 +653,26 @@ static void *mainloop (void *unused) } state.h = h; if (w != state.w) { + int i; state.w = w; - for (page = state.pages; page; page = page->prev) { - int i; - for (i = 0; i < state.slicecount; ++i) { - int index; - - index = page->slices[i].texindex; - if (index != -1) { - state.texowners[index].h = 0; - state.texowners[index].slice = NULL; - } - page->slices[i].texindex = -1; - } + for (i = 0; i < state.texcount; ++i) { + state.texowners[i].slice = NULL; } } - layout1 (); + layout (); } else if (!strncmp ("render", p, 6)) { - int pagenum, pindex, w, h, ret; + int pageno, pindex, w, h, ret; struct page *page; - unsigned char *pix; - - ret = sscanf (p + 6, " %d %d %d %d", &pagenum, &pindex, &w, &h); + ret = sscanf (p + 6, " %d %d %d %d", &pageno, &pindex, &w, &h); if (ret != 4) { errx (1, "bad render line `%.*s' ret=%d", len, p, ret); } - page = render (pagenum, pindex); + page = render (pageno, pindex); printd (state.sock, "r %d %d %d %p\n", - pagenum, + pageno, state.w, state.h, page); @@ -531,16 +694,13 @@ static void upload2 (struct page *page, int slicenum, const char *cap) w = page->pixmap->w; h = page->pixmap->h; - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glPixelStorei(GL_UNPACK_ROW_LENGTH, w); - + ARSERT (w == slice->w); if (slice->texindex != -1 && state.texowners[slice->texindex].slice == slice) { - printf ("bind %d %d\n", page->pagenum, slicenum); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[slice->texindex]); } else { - int subimage; + int subimage = 0; int index = (state.texindex++ % state.texcount); size_t offset = 0; @@ -548,35 +708,49 @@ static void upload2 (struct page *page, int slicenum, const char *cap) offset += w * page->slices[i].h * 4; } - if (state.texowners[index].h >= slice->h ) { - subimage = 1; + if (state.texowners[index].w == slice->w) { + if (state.texowners[index].h >= slice->h ) { + subimage = 1; + } + else { + state.texowners[index].h = slice->h; + } } - else { + else { state.texowners[index].h = slice->h; - subimage = 0; } + state.texowners[index].slice = slice; + state.texowners[index].w = slice->w; slice->texindex = index; glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[slice->texindex]); - /* glFinish (); */ start = now (); if (subimage) { + { + GLenum err = glGetError (); + if (err != GL_NO_ERROR) { + printf ("\e[0;31mERROR1 %d %d %#x\e[0m\n", w, slice->h, err); + abort (); + } + } glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, w, slice->h, -#ifndef _ARCH_PPC - GL_BGRA_EXT, - GL_UNSIGNED_INT_8_8_8_8, -#else - GL_ABGR_EXT, - GL_UNSIGNED_BYTE, /* INT_8_8_8_8, */ -#endif + state.texform, + state.texty, page->pixmap->samples + offset ); + { + GLenum err = glGetError (); + if (err != GL_NO_ERROR) { + printf ("\e[0;31mERROR %d %d %#x\e[0m\n", w, slice->h, err); + abort (); + } + } } else { glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, @@ -585,22 +759,19 @@ static void upload2 (struct page *page, int slicenum, const char *cap) w, slice->h, 0, -#ifndef _ARCH_PPC - GL_BGRA_EXT, - GL_UNSIGNED_INT_8_8_8_8, -#else - GL_ABGR_EXT, - GL_UNSIGNED_BYTE, /* INT_8_8_8_8, */ -#endif + state.texform, + state.texty, page->pixmap->samples + offset ); } end = now (); - printf ("upload(%s) %d %d %f sec\n", - subimage ? "sub" : "img", - page->pagenum, slicenum, - end - start); + lprintf ("%s[%d] slice=%d(%d,%d) texid=%d %f sec\n", + subimage ? "sub" : "img", + page->pageno, slicenum, + slice->w, slice->h, + state.texids[slice->texindex], + end - start); } } @@ -611,22 +782,21 @@ CAMLprim value ml_preload (value ptr_v) void *ptr; CAMLparam1 (ptr_v); char *s = String_val (ptr_v); + struct page *page; ret = sscanf (s, "%p", &ptr); if (ret != 1) { errx (1, "cannot parse pointer `%s'", s); } - for (i = 0; i < state.slicecount; ++i) + + page = ptr; + for (i = 0; i < page->slicecount; ++i) { upload2 (ptr, i, "preload"); + } + CAMLreturn (Val_unit); } -#if 0 -#define lprintf printf -#else -#define lprintf(...) -#endif - CAMLprim value ml_draw (value dispy_v, value w_v, value h_v, value py_v, value ptr_v) { @@ -637,7 +807,6 @@ CAMLprim value ml_draw (value dispy_v, value w_v, value h_v, int py = Int_val (py_v); char *s = String_val (ptr_v); int ret; - const char *r; void *ptr; struct page *page; int slicenum = 0; @@ -650,14 +819,14 @@ CAMLprim value ml_draw (value dispy_v, value w_v, value h_v, w = page->pixmap->w; - assert (h < 0 || "ml_draw wrong h"); + ARSERT (h >= 0 && "ml_draw wrong h"); glEnable (GL_TEXTURE_RECTANGLE_ARB); -#ifdef _ARCH_PPC - glEnable (GL_FRAGMENT_SHADER_ATI); -#endif + if (state.useatifs) { + glEnable (GL_FRAGMENT_SHADER_ATI); + } - for (slicenum = 0; slicenum != state.slicecount; ++slicenum) { + for (slicenum = 0; slicenum < page->slicecount; ++slicenum) { struct slice *slice = &page->slices[slicenum]; if (slice->h > py) { break; @@ -665,13 +834,13 @@ CAMLprim value ml_draw (value dispy_v, value w_v, value h_v, py -= slice->h; } + h = MIN (state.h, h); while (h) { int th; struct slice *slice = &page->slices[slicenum]; - if (slicenum >= state.slicecount) { - abort (); - } + ARSERT (slicenum < page->slicecount && "ml_draw wrong slicenum"); + th = MIN (h, slice->h - py); upload2 (page, slicenum, "upload"); @@ -698,62 +867,220 @@ CAMLprim value ml_draw (value dispy_v, value w_v, value h_v, } glDisable (GL_TEXTURE_RECTANGLE_ARB); -#ifdef _ARCH_PPC - glDisable (GL_FRAGMENT_SHADER_ATI); -#endif + if (state.useatifs) { + glDisable (GL_FRAGMENT_SHADER_ATI); + } + CAMLreturn (Val_unit); +} + +CAMLprim value ml_checklink (value ptr_v, value x_v, value y_v) +{ + CAMLparam3 (ptr_v, x_v, y_v); + fz_point p; + fz_matrix ctm; + pdf_link *link; + int pageno = -1; + struct page *page; + char *s = String_val (ptr_v); + + p.x = Int_val (x_v); + p.y = Int_val (y_v); + + page = parse_pointer ("ml_checklink", s); + + ctm = fz_invertmatrix (page->pagedim->ctm); + p = fz_transformpoint (ctm, p); + + for (link = page->drawpage->links; link; link = link->next) { + if (p.x >= link->rect.x0 && p.x <= link->rect.x1) + if (p.y >= link->rect.y0 && p.y <= link->rect.y1) { + if (link->kind == PDF_LGOTO) { + pageno = pdf_findpageobject (state.xref, + fz_arrayget (link->dest, 0)) - 1; + /* link->dest); */ + } + break; + } + } + + CAMLreturn (Val_int (pageno)); +} + +CAMLprim value ml_gettext (value ptr_v, value rect_v, value oy_v, value rectsel_v) +{ + CAMLparam4 (ptr_v, rect_v, oy_v, rect_v); + fz_matrix ctm; + fz_point p1, p2; + struct page *page; + fz_textspan *span; + char *s = String_val (ptr_v); + int rectsel = Bool_val (rectsel_v); + int i, bx0, bx1, by0, by1, x0, x1, y0, y1, oy; + + /* stop GCC from complaining about uninitialized variables */ + int rx0 = rx0, rx1 = rx1, ry0 = ry0, ry1 = ry1; + + page = parse_pointer ("ml_gettext", s); + + oy = Int_val (oy_v); + p1.x = Int_val (Field (rect_v, 0)); + p1.y = Int_val (Field (rect_v, 1)); + p2.x = Int_val (Field (rect_v, 2)); + p2.y = Int_val (Field (rect_v, 3)); + + if (0) { + glEnable (GL_BLEND); + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glBlendFunc (GL_DST_ALPHA, GL_SRC_ALPHA); + glColor4f (0, 0, 0, 0.2); + glRecti (p1.x, p1.y, p2.x, p2.y); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glDisable (GL_BLEND); + } + + ctm = page->pagedim->ctm; + if (!page->text) { + fz_error error; + fz_device *tdev; + + page->text = fz_newtextspan (); + tdev = fz_newtextdevice (page->text); + error = pdf_runcontentstream (tdev, page->pagedim->ctm, state.xref, + page->drawpage->resources, + page->drawpage->contents); + if (error) die (error); + fz_freedevice (tdev); + } + + printf ("\ec"); + + printf ("BBox %f %f %f %f\n", p1.x, p1.y, p2.x, p2.y); + p1.x += page->pixmap->x; + p1.y += page->pixmap->y; + p2.x += page->pixmap->x; + p2.y += page->pixmap->y; + x0 = p1.x; + y0 = p1.y; + x1 = p2.x; + y1 = p2.y; + printf ("BBox %d %d %d %d %d %d\n", x0, y0, x1, y1, oy, page->pageno); + + for (span = page->text; span; span = span->next) { + int seen = 0; + + /* fz_debugtextspanxml (span); */ + for (i = 0; i < span->len; ++i) { + long c; + + bx0 = span->text[i].bbox.x0; + bx1 = span->text[i].bbox.x1; + by0 = span->text[i].bbox.y0 + oy; + by1 = span->text[i].bbox.y1 + oy; + + if ((bx1 >= x0 && bx0 <= x1 && by1 >= y0 && by0 <= y1)) { + if (!seen) { + rx0 = bx0 - page->pixmap->x; + rx1 = bx1 - page->pixmap->x; + ry0 = by0; + ry1 = by1; + } + + seen = 1; + c = span->text[i].c; + if (c < 256) { + if ((isprint (c) && !isspace (c))) { + if (!rectsel) { + bx0 -= page->pixmap->x; + bx1 -= page->pixmap->x; + glEnable (GL_BLEND); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glBlendFunc (GL_DST_ALPHA, GL_SRC_ALPHA); + glColor4f (0.5, 0.5, 0.0, 0.6); + glRecti (bx0, by0, bx1, by1); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glDisable (GL_BLEND); + } + if (isprint (c) || c ==' ') { + rx1 = bx1; + ry1 = by1; + } + } + putc (c, stdout); + } + else { + putc ('?', stdout); + } + } + } + + if (rectsel) { + if (seen) { + glEnable (GL_BLEND); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glBlendFunc (GL_DST_ALPHA, GL_SRC_ALPHA); + glColor4f (0.5, 0.5, 0.0, 0.6); + glRecti (rx0, ry0, rx1, ry1); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glDisable (GL_BLEND); + } + } + + if (seen && span->eol) { + x0 = page->pixmap->x; + putc ('\n', stdout); + } + } + CAMLreturn (Val_unit); } static void initgl (void) { - state.dpy = glXGetCurrentDisplay (); - if (!state.dpy) { - die (fz_throw ("glXGetCurrentDisplay")); - } - state.ctx = glXGetCurrentContext (); - if (!state.ctx) { - die (fz_throw ("glXGetCurrentContext")); - } - state.drawable = glXGetCurrentDrawable (); - if (!state.drawable) { - die (fz_throw ("glXGetCurrentDrawable")); - } - -#ifdef _ARCH_PPC - glBindFragmentShaderATI (1); - glBeginFragmentShaderATI (); - { - glSampleMapATI (GL_REG_0_ATI, GL_TEXTURE0_ARB, GL_SWIZZLE_STR_ATI); - - glColorFragmentOp1ATI (GL_MOV_ATI, - GL_REG_1_ATI, GL_RED_BIT_ATI, GL_NONE, - GL_REG_0_ATI, GL_BLUE, GL_NONE); - glColorFragmentOp1ATI (GL_MOV_ATI, - GL_REG_1_ATI, GL_BLUE_BIT_ATI, GL_NONE, - GL_REG_0_ATI, GL_RED, GL_NONE); - glColorFragmentOp1ATI ( - GL_MOV_ATI, - GL_REG_0_ATI, GL_RED_BIT_ATI | GL_BLUE_BIT_ATI, GL_NONE, - GL_REG_1_ATI, GL_NONE, GL_NONE - ); - } - glEndFragmentShaderATI (); +#ifdef _BIG_ENDIAN + if (strstr ((char *) glGetString (GL_EXTENSIONS), + "GL_ATI_fragment_shader")) { + /* Here, with MESA, rv280, powerpc32: BGRA(rev) is slow while + ABGR is fast, so fix things in the shader */ + state.texform = GL_ABGR_EXT; + state.texty = GL_UNSIGNED_INT_8_8_8_8; + + glBindFragmentShaderATI (1); + glBeginFragmentShaderATI (); + { + glSampleMapATI (GL_REG_0_ATI, GL_TEXTURE0_ARB, GL_SWIZZLE_STR_ATI); + + glColorFragmentOp1ATI (GL_MOV_ATI, + GL_REG_1_ATI, GL_RED_BIT_ATI, GL_NONE, + GL_REG_0_ATI, GL_BLUE, GL_NONE); + glColorFragmentOp1ATI (GL_MOV_ATI, + GL_REG_1_ATI, GL_BLUE_BIT_ATI, GL_NONE, + GL_REG_0_ATI, GL_RED, GL_NONE); + glColorFragmentOp1ATI ( + GL_MOV_ATI, + GL_REG_0_ATI, GL_RED_BIT_ATI | GL_BLUE_BIT_ATI, GL_NONE, + GL_REG_1_ATI, GL_NONE, GL_NONE + ); + } + glEndFragmentShaderATI (); + state.useatifs = 1; + } + else { + state.texform = GL_BGRA_EXT; + state.texty = GL_UNSIGNED_INT_8_8_8_8_REV; + } +#else + state.texform = GL_BGRA_EXT; + state.texty = GL_UNSIGNED_INT_8_8_8_8; #endif } CAMLprim value ml_init (value sock_v) { int ret; - fz_error error; CAMLparam1 (sock_v); - pagesize = sysconf (_SC_PAGESIZE); - if (pagesize == -1) { - err (1, "sysconf"); - } - - state.texcount = 64; - state.slicecount = 32; + state.texcount = 128; + state.sliceheight = 64; state.texids = calloc (state.texcount * sizeof (*state.texids), 1); if (!state.texids) { @@ -772,6 +1099,9 @@ CAMLprim value ml_init (value sock_v) initgl (); state.cache = fz_newglyphcache (); + if (!state.cache) { + errx (1, "fz_newglyphcache failed"); + } ret = pthread_create (&state.thread, NULL, mainloop, NULL); if (ret) { diff --git a/main.ml b/main.ml index 230f6e0..972fd01 100644 --- a/main.ml +++ b/main.ml @@ -1,51 +1,165 @@ open Format;; + +let log fmt = Printf.kprintf prerr_endline fmt;; +let dolog fmt = Printf.kprintf prerr_endline fmt;; + external init : Unix.file_descr -> unit = "ml_init";; external draw : int -> int -> int -> int -> string -> unit = "ml_draw";; external preload : string -> unit = "ml_preload";; +external gettext : string -> (int * int * int * int) -> int -> bool -> unit = + "ml_gettext";; +external checklink : string -> int -> int -> int = "ml_checklink";; + +type mstate = Msel of ((int * int) * (int * int)) | Mnone;; + + +type te = + | TEstop + | TEdone of string + | TEcont of string +;; + +type 'a circbuf = + { store : 'a array + ; mutable rc : int + ; mutable wc : int + ; mutable len : int + } +;; + +let cbnew n v = + { store = Array.create n v + ; rc = 0 + ; wc = 0 + ; len = 0 + } +;; -type ('a, 'b, 'c) g = +let cblen b = Array.length b.store;; + +let cbput b v = + let len = cblen b in + b.store.(b.wc) <- v; + b.wc <- (b.wc + 1) mod len; + b.len <- (b.len + 1) mod len; +;; + +let cbpeekw b = b.store.(b.wc);; + +let cbget b = + let v = b.store.(b.rc) in + if b.len = 0 + then + v + else ( + let rc = if b.rc = 0 then b.len - 1 else b.rc - 1 in + b.rc <- rc; + v + ) +;; + +let cbrfollowlen b = + b.rc <- b.len - 1; +;; + +type layout = + { pageno : int + ; pagedimno : int + ; pagew : int + ; pageh : int + ; pagedispy : int + ; pagey : int + ; pagevh : int + } +;; + +type conf = + { mutable scrollw : int + ; mutable rectsel : bool + ; mutable icase : bool + ; mutable preload : bool + ; mutable titletext : bool + ; mutable pagebias : int + ; mutable redispimm : bool + ; mutable verbose : bool + } +;; + +type state = { mutable csock : Unix.file_descr ; mutable ssock : Unix.file_descr ; mutable w : int ; mutable h : int ; mutable y : int + ; mutable prevy : int ; mutable maxy : int - ; mutable layout : (int * int * int * int * int * int * int) list - ; pixcache : ((int * int), string) Hashtbl.t - ; mutable pages : 'a list + ; mutable layout : layout list + ; pagemap : ((int * int), string) Hashtbl.t + ; mutable pages : (int * int * int) list ; mutable pagecount : int - ; lru : string array - ; mutable lruidx : int - ; mutable scrollw : int + ; pagecache : string circbuf + ; navhist : float circbuf ; mutable inflight : int ; mutable safe : int + ; mutable mstate : mstate + ; mutable searchpattern : string + ; mutable rects : (int * int * Gl.point2 * Gl.point2) list + ; mutable text : string + ; mutable fullscreen : (int * int) option + ; mutable textentry : + (char * string * (string -> int -> te) * (string -> unit)) option } ;; +let conf = + { scrollw = 5 + ; icase = true + ; rectsel = true + ; preload = false + ; titletext = false + ; pagebias = 0 + ; redispimm = false + ; verbose = false + } +;; + let state = { csock = Unix.stdin ; ssock = Unix.stdin ; w = 0 ; h = 0 ; y = 0 + ; prevy = 0 ; layout = [] ; maxy = max_int - ; pixcache = Hashtbl.create 10 + ; pagemap = Hashtbl.create 10 + ; pagecache = cbnew 10 "" ; pages = [] ; pagecount = 0 - ; lru = Array.create 10 "" - ; lruidx = 0 - ; scrollw = 12 ; inflight = 0 ; safe = 0 + ; mstate = Mnone + ; navhist = cbnew 100 0.0 + ; rects = [] + ; text = "" + ; fullscreen = None + ; textentry = None + ; searchpattern = "" } ;; let aincr = 17;; let aincr = 21;; +(* let aincr = 40;; *) let scrollh = 12;; -let log fmt = Printf.kprintf prerr_endline fmt;; -let dolog fmt = Printf.kprintf prerr_endline fmt;; + +let vlog fmt = + if conf.verbose + then + Printf.kprintf prerr_endline fmt + else + Printf.kprintf ignore fmt +;; let writecmd fd s = let len = String.length s in @@ -77,6 +191,11 @@ let readcmd fd = s ;; +let yratio y = + if y = state.maxy then 1.0 + else float y /. float state.maxy +;; + let wcmd s l = let b = Buffer.create 10 in Buffer.add_string b s; @@ -86,6 +205,7 @@ let wcmd s l = Buffer.add_char b ' '; let s = match x with + | `b b -> if b then "1" else "0" | `s s -> s | `i i -> string_of_int i | `f f -> string_of_float f @@ -102,29 +222,47 @@ let calcheight () = let rec f pn ph fh l = match l with | (n, _, h) :: rest -> - let fh = fh + (n - pn) * ph + h in - f (n+1) h fh rest + let fh = fh + (n - pn) * ph in + f n h fh rest | [] -> - max 0 (fh + (ph * (state.pagecount - pn)) - state.h) + let fh = fh + (ph * (state.pagecount - pn)) in + max 0 (fh - state.h) in let fh = f 0 0 0 state.pages in fh; ;; +let getpagey pageno = + let rec f pn ph y l = + match l with + | (n, _, h) :: rest -> + if n >= pageno + then + y + (pageno - pn) * ph + else + let y = y + (n - pn) * ph in + f n h y rest + + | [] -> + y + (pageno - pn) * ph + in + f 0 0 0 state.pages; +;; + let layout y sh = - let rec f pagenum pindex prev vy py dy l accu = - if pagenum = state.pagecount + let rec f pageno pdimno prev vy py dy l accu = + if pageno = state.pagecount then accu else - let ((_, w, h) as curr), rest, pindex = + let ((_, w, h) as curr), rest, pdimno = match l with - | ((pagenum', _, _) as curr) :: rest when pagenum' = pagenum -> - curr, rest, pindex + 1 + | ((pageno', _, _) as curr) :: rest when pageno' = pageno -> + curr, rest, pdimno + 1 | _ -> - prev, l, pindex + prev, l, pdimno in - let pagenum' = pagenum + 1 in + let pageno' = pageno + 1 in if py + h > vy then let py' = vy - py in @@ -136,20 +274,67 @@ let layout y sh = then accu else - let e = pagenum, pindex, w, h, dy, py', vh in + let e = + { pageno = pageno + ; pagedimno = pdimno + ; pagew = w + ; pageh = h + ; pagedispy = dy + ; pagey = py' + ; pagevh = vh + } + in e :: accu else - let e = pagenum, pindex, w, h, dy, py', vh in + let e = + { pageno = pageno + ; pagedimno = pdimno + ; pagew = w + ; pageh = h + ; pagedispy = dy + ; pagey = py' + ; pagevh = vh + } + in let accu = e :: accu in - f pagenum' pindex curr (vy + vh) (py + h) (dy + vh + 2) rest accu + f pageno' pdimno curr (vy + vh) (py + h) (dy + vh + 2) rest accu else - f pagenum' pindex curr vy (py + h) dy rest accu + f pageno' pdimno curr vy (py + h) dy rest accu in let accu = f 0 ~-1 (0,0,0) y 0 0 state.pages [] in state.maxy <- calcheight (); List.rev accu ;; +let clamp incr = + let y = state.y + incr in + let y = max 0 y in + let y = min y state.maxy in + y; +;; + +let gotoy y = + let y = max 0 y in + let y = min state.maxy y in + let pages = layout y state.h in + state.y <- y; + state.layout <- pages; + if conf.redispimm + then + Glut.postRedisplay () + ; +;; + +let addnav () = + cbput state.navhist (yratio state.y); + cbrfollowlen state.navhist; +;; + +let getnav () = + let y = cbget state.navhist in + truncate (y *. float state.maxy) +;; + let reshape ~w ~h = state.w <- w; state.h <- h; @@ -161,16 +346,55 @@ let reshape ~w ~h = GlMat.rotate ~x:1.0 ~angle:180.0 (); GlMat.translate ~x:~-.1.0 ~y:~-.1.0 (); GlMat.scale3 (2.0 /. float w, 2.0 /. float state.h, 1.0); + GlClear.color (1., 1., 1.); + GlClear.clear [`color]; + state.layout <- []; state.pages <- []; - wcmd "geometry" [`i (state.w - state.scrollw); `i h]; + state.rects <- []; + state.text <- ""; + wcmd "geometry" [`i (state.w - conf.scrollw); `i h]; +;; + +let showtext c s = + if not conf.titletext + then ( + GlDraw.color (0.0, 0.0, 0.0); + GlDraw.rect + (0.0, float (state.h - 18)) + (float (state.w - conf.scrollw - 1), float state.h) + ; + let font = Glut.BITMAP_8_BY_13 in + GlDraw.color (1.0, 1.0, 1.0); + GlPix.raster_pos ~x:0.0 ~y:(float (state.h - 5)) (); + Glut.bitmapCharacter ~font ~c:(Char.code c); + String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s + ) + else + let len = String.length s in + let dst = String.create (len + 1) in + dst.[0] <- c; + StringLabels.blit + ~src:s + ~dst + ~src_pos:0 + ~dst_pos:1 + ~len + ; + Glut.setWindowTitle ~title:dst ;; let act cmd = match cmd.[0] with + | 'c' -> + state.pages <- [] + + | 'd' -> + Glut.postRedisplay () + | 'C' -> let n = Scanf.sscanf cmd "C %d" (fun n -> n) in state.pagecount <- n; - let rely = float state.y /. float state.maxy in + let rely = yratio state.y in let maxy = calcheight () in state.y <- truncate (float maxy *. rely); let pages = layout state.y state.h in @@ -184,27 +408,48 @@ let act cmd = Glut.postRedisplay () ; + | 'T' -> + let s = Scanf.sscanf cmd "T %S" (fun s -> s) in + state.text <- s; + showtext ' ' s; + Glut.swapBuffers (); + (* Glut.postRedisplay () *) + + | 'F' -> + let pageno, c, x0, y0, x1, y1 = + Scanf.sscanf cmd "F %d %d %f %f %f %f" + (fun p c x0 y0 x1 y1 -> (p, c, x0, y0, x1, y1)) + in + let y = (getpagey pageno) + truncate y0 in + addnav (); + gotoy y; + state.rects <- [pageno, c, (x0, y0), (x1, y1)] + + | 'R' -> + let pageno, c, x0, y0, x1, y1 = + Scanf.sscanf cmd "R %d %d %f %f %f %f" + (fun pageno c x0 y0 x1 y1 -> (pageno, c, x0, y0, x1, y1)) + in + state.rects <- (pageno, c, (x0, y0), (x1, y1)) :: state.rects + | 'r' -> let n, w, h, p = Scanf.sscanf cmd "r %d %d %d %s" (fun n w h p -> (n, w, h, p)) in - Hashtbl.replace state.pixcache (n, w) p; - let idx = state.lruidx mod (Array.length state.lru) in - let s = state.lru.(idx) in - if String.length s != 0 + Hashtbl.replace state.pagemap (n, w) p; + let evicted = cbpeekw state.pagecache in + if String.length evicted > 0 then begin state.safe <- state.safe + 1; - wcmd "free" [`s s]; - let l = Hashtbl.fold (fun k s' a -> - if s = s' then k :: a else a) state.pixcache [] + wcmd "free" [`s evicted]; + let l = Hashtbl.fold (fun k p a -> + if evicted = p then k :: a else a) state.pagemap [] in - List.iter (fun k -> Hashtbl.remove state.pixcache k) l; + List.iter (fun k -> Hashtbl.remove state.pagemap k) l; end; - state.lru.(idx) <- p; - state.lruidx <- state.lruidx + 1; + cbput state.pagecache p; state.inflight <- pred state.inflight; - log "done render %d" n; Glut.postRedisplay () | 'l' -> @@ -217,196 +462,410 @@ let act cmd = log "unknown cmd `%S'" cmd ;; -let preload - ((pageno, pindex, pagewidth, pageheight, screeny, pageyoffset, screenheight) as page) = - let key = (pageno + 1, state.w - state.scrollw) in - begin try - let pixmap = Hashtbl.find state.pixcache key in - if - String.length pixmap = 0 - then ( - (* log "empty %d %d %d" (pageno+1) pagewidth pageheight; *) - (); - ) - else ( - preload pixmap - ); - with Not_found -> - if state.inflight < Array.length state.lru - then ( - state.inflight <- succ state.inflight; - Hashtbl.add state.pixcache key ""; - log "render req %d" (pageno+1); - wcmd "render" [`i (pageno + 1) - ;`i pindex - ;`i pagewidth - ;`i pageheight]; - ) - end; +let getopaque pageno = + try Some (Hashtbl.find state.pagemap (pageno + 1, state.w - conf.scrollw)) + with Not_found -> None +;; + +let cache pageno opaque = + Hashtbl.replace state.pagemap (pageno + 1, state.w - conf.scrollw) opaque +;; + +let validopaque opaque = String.length opaque > 0;; + +let preload l = + match getopaque l.pageno with + | Some opaque when validopaque opaque -> + preload opaque + + | None when state.inflight < 2+0*(cblen state.pagecache) -> + state.inflight <- succ state.inflight; + cache l.pageno ""; + wcmd "render" [`i (l.pageno + 1) + ;`i l.pagedimno + ;`i l.pagew + ;`i l.pageh]; + + | _ -> () ;; let idle () = - let r, _, _ = Unix.select [state.csock] [] [] 0.01 in - - begin match r with - | [] -> - if false then begin - let h = state.h in - let y = if state.y < state.h then 0 else state.y - state.h in - let pages = layout y (h*3) in - List.iter preload pages; - end + if not conf.redispimm && state.y != state.prevy + then ( + state.prevy <- state.y; + Glut.postRedisplay (); + ) + else + let r, _, _ = Unix.select [state.csock] [] [] 0.02 in + + begin match r with + | [] -> + if conf.preload then begin + let h = state.h in + let y = if state.y < state.h then 0 else state.y - state.h in + let pages = layout y (h*3) in + List.iter preload pages; + end; + + | _ -> + let cmd = readcmd state.csock in + act cmd; + end; +;; + +let search pattern forward = + if String.length pattern > 0 + then + let pn, py = + match state.layout with + | [] -> 0, 0 + | l :: _ -> + l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh) + in + wcmd "search" [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0) + ; `s (pattern ^ "\000")] +;; + +let intentry text key = + let c = Char.unsafe_chr key in + match c with + | '0' .. '9' -> + let s = "x" in s.[0] <- c; + let text = text ^ s in + TEcont text | _ -> - let cmd = readcmd state.csock in - act cmd; - end; + state.text <- Printf.sprintf "invalid char (%d, `%c')" key c; + TEcont text ;; -let clamp incr = - let y = state.y + incr in - let y = max 0 y in - let y = min y state.maxy in - state.y <- y; +let textentry text key = + let c = Char.unsafe_chr key in + match c with + | _ when key >= 32 && key <= 127 -> + let s = "x" in s.[0] <- c; + let text = text ^ s in + TEcont text + + | _ -> + log "unhandled key %d char `%c'" key (Char.unsafe_chr key); + TEcont text ;; -let keyboard ~key ~x ~y = - begin match Char.chr key with - | '\027' | 'q' -> exit 0 - | 'b' -> - state.scrollw <- if state.scrollw > 0 then 0 else 12; - reshape state.w state.h - - | 'n' -> - begin match List.rev state.layout with - | [] -> () - | (_, _, _, h, _, pyo, sh) :: _ -> - clamp (h-pyo); - let pages = layout state.y state.h in - state.layout <- pages; - Glut.postRedisplay (); - end +let optentry text key = + let btos b = if b then "on" else "off" in + let c = Char.unsafe_chr key in + match c with + | 'r' -> + conf.rectsel <- not conf.rectsel; + TEdone ("rectsel " ^ (btos conf.rectsel)) - | 'w' -> - begin match state.layout with - | [] -> () - | (_, _, w, h, _, _, _) :: _ -> - Glut.reshapeWindow (w + state.scrollw) h - end + | 'i' -> + conf.icase <- not conf.icase; + TEdone ("case insensitive search " ^ (btos conf.icase)) | 'p' -> - begin match state.layout with - | [] -> () - | (_, _, _, h, _, _, sh) :: _ -> - clamp ~-h; - let pages = layout state.y state.h in - state.layout <- pages; - Glut.postRedisplay (); + conf.preload <- not conf.preload; + TEdone ("preload " ^ (btos conf.preload)) + + | 't' -> + conf.titletext <- not conf.titletext; + TEdone ("titletext " ^ (btos conf.titletext)) + + | 'd' -> + conf.redispimm <- not conf.redispimm; + TEdone ("immediate redisplay " ^ (btos conf.redispimm)) + + | 'v' -> + conf.verbose <- not conf.verbose; + TEdone ("verbose " ^ (btos conf.verbose)) + + | _ -> + state.text <- Printf.sprintf "bad option %d `%c'" key c; + TEstop +;; + +let keyboard ~key ~x ~y = + match state.textentry with + | None -> + let c = Char.chr key in + begin match c with + | '\027' | 'q' -> + exit 0 + + | '\008' -> + let y = getnav () in + gotoy y + + | 'u' -> + state.rects <- []; + state.text <- ""; + Glut.postRedisplay () + + | '/' | '?' -> + let ondone isforw s = + state.searchpattern <- s; + search s isforw + in + state.textentry <- Some (c, "", textentry, ondone (c ='/')); + state.text <- ""; + Glut.postRedisplay () + + | '+' -> + let ondone s = + let n = + try int_of_string s with exc -> + state.text <- Printf.sprintf "bad integer `%s': %s" + s (Printexc.to_string exc); + max_int + in + if n != max_int + then ( + conf.pagebias <- n; + state.text <- "page bias is now " ^ string_of_int n; + ) + in + state.textentry <- Some ('+', "", intentry, ondone); + state.text <- ""; + Glut.postRedisplay () + + + | '-' -> + let ondone text = + state.text <- text; + Glut.postRedisplay (); + in + state.textentry <- Some ('-', "", optentry, ondone); + state.text <- ""; + Glut.postRedisplay () + + | '0' .. '9' -> + let ondone s = + let n = + try int_of_string s with exc -> + state.text <- Printf.sprintf "bad integer `%s': %s" + s (Printexc.to_string exc); + -1 + in + if n >= 0 + then + gotoy (getpagey (n + conf.pagebias - 1)) + in + let pageentry text key = + match Char.unsafe_chr key with + | 'g' -> TEdone text + | _ -> intentry text key + in + let text = "x" in text.[0] <- c; + state.textentry <- Some (':', text, pageentry, ondone); + state.text <- ""; + Glut.postRedisplay () + + | 'b' -> + conf.scrollw <- if conf.scrollw > 0 then 0 else 5; + reshape state.w state.h; + + | 'f' -> + begin match state.fullscreen with + | None -> + state.fullscreen <- Some (state.w, state.h); + Glut.fullScreen () + | Some (w, h) -> + state.fullscreen <- None; + Glut.reshapeWindow ~w ~h + end + + | 'n' -> + search state.searchpattern true + + | 'p' -> + search state.searchpattern false + + | 't' -> + begin match state.layout with + | [] -> () + | l :: _ -> + gotoy (state.y - l.pagey); + end + + | ' ' | 'N' -> + begin match List.rev state.layout with + | [] -> () + | l :: _ -> + gotoy (state.y + l.pageh - l.pagey) + end + + | '\127' | 'P' -> + begin match state.layout with + | [] -> () + | l :: _ -> + gotoy (state.y-l.pageh); + end + + | '=' -> + let f (fn, ln) l = + if fn = -1 then l.pageno, l.pageno else fn, l.pageno + in + let fn, ln = List.fold_left f (-1, -1) state.layout in + let s = + let percent = (100. *. yratio state.y) in + if fn = ln + then + Printf.sprintf "Page %d of %d %.2f%%" + (fn+1) state.pagecount percent + else + Printf.sprintf + "Pages %d-%d of %d %.2f%%" + (fn+1) (ln+1) state.pagecount percent + in + showtext ' ' s; + Glut.swapBuffers () + + | 'w' -> + begin match state.layout with + | [] -> () + | l :: _ -> + Glut.reshapeWindow (l.pagew + conf.scrollw) l.pageh; + Glut.postRedisplay (); + end + + | _ -> + vlog "huh? %d %c" key (Char.chr key); end - | _ -> () - end; - Glut.postRedisplay (); + + | Some (c, text, onkey, ondone) when c = '\008' -> + let len = String.length text in + let te = + if len = 0 || len = 1 + then + None + else ( + let s = String.sub text 0 (len - 1) in + Some (c, s, onkey, ondone) + ) + in + state.textentry <- te; + Glut.postRedisplay () + + | Some (c, text, onkey, ondone) -> + begin match Char.unsafe_chr key with + | '\r' | '\n' -> + state.textentry <- None; + ondone text; + + | '\008' -> + let len = String.length text in + if len < 2 + then ( + state.textentry <- None + ) + else ( + let text = String.sub text 0 (len - 1) in + state.textentry <- Some (c, text, onkey, ondone) + ) + + | '\027' -> + state.textentry <- None + + | _ -> + begin match onkey text key with + | TEdone text -> + state.textentry <- None; + ondone text; + + | TEcont text -> + state.textentry <- Some (c, text, onkey, ondone); + + | TEstop -> + state.textentry <- None; + end; + end; + Glut.postRedisplay () ;; let special ~key ~x ~y = - begin match key with - | Glut.KEY_LEFT -> () - | Glut.KEY_RIGHT -> () - | Glut.KEY_UP -> clamp ~-aincr - | Glut.KEY_DOWN -> clamp aincr - | Glut.KEY_PAGE_UP -> clamp (-state.h) - | Glut.KEY_PAGE_DOWN -> clamp state.h - | Glut.KEY_HOME -> state.y <- 0 - | Glut.KEY_END -> state.y <- state.maxy (* - state.h *) - | _ -> () - end; - let pages = layout state.y state.h in - state.layout <- pages; - Glut.postRedisplay (); + let y = + match key with + | Glut.KEY_F3 -> search state.searchpattern true; state.y + | Glut.KEY_UP -> clamp ~-aincr + | Glut.KEY_DOWN -> clamp aincr + | Glut.KEY_PAGE_UP -> clamp (-state.h) + | Glut.KEY_PAGE_DOWN -> clamp state.h + | Glut.KEY_HOME -> addnav (); 0 + | Glut.KEY_END -> addnav (); state.maxy (* - state.h *) + | _ -> state.y + in + state.text <- ""; + gotoy y ;; -let drawplaceholder (pageno, pindex, pagewidth, pageheight, - screeny, pageyoffset, screenheight) = +let drawplaceholder l = if true then ( GlDraw.color (0.2, 0.2, 0.2); GlDraw.color (1.0, 1.0, 1.0); GlDraw.rect - (0.0, float screeny) - (float pagewidth, float (screeny + screenheight)) + (0.0, float l.pagedispy) + (float l.pagew, float (l.pagedispy + l.pagevh)) ; let x = 0.0 - and y = float (screeny + screenheight) in - let font = Glut.BITMAP_8_BY_13 in (* BITMAP_HELVETICA_18 in *) + and y = float (l.pagedispy + l.pagevh - 5) in + let font = Glut.BITMAP_8_BY_13 in GlDraw.color (0.0, 0.0, 0.0); GlPix.raster_pos ~x ~y (); String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) - ("Loading " ^ string_of_int pageno ^ " ..."); + ("Loading " ^ string_of_int l.pageno); ) else ( GlDraw.begins `quads; - GlDraw.vertex2 (0.0, float screeny); - GlDraw.vertex2 (float pagewidth, float screeny); - GlDraw.vertex2 (float pagewidth, float (screeny + screenheight)); - GlDraw.vertex2 (0.0, float (screeny + screenheight)); + GlDraw.vertex2 (0.0, float l.pagedispy); + GlDraw.vertex2 (float l.pagew, float l.pagedispy); + GlDraw.vertex2 (float l.pagew, float (l.pagedispy + l.pagevh)); + GlDraw.vertex2 (0.0, float (l.pagedispy + l.pagevh)); GlDraw.ends (); ) ;; let now () = Unix.gettimeofday ();; -let drawpage i - ((pageno, pindex, pagewidth, pageheight, screeny, pageyoffset, screenheight) as page) = - let key = (pageno + 1, state.w - state.scrollw) in - begin try - let pixmap = Hashtbl.find state.pixcache key in - if - String.length pixmap = 0 - then ( - drawplaceholder page; - ) - else ( - GlDraw.color (1.0, 1.0, 1.0); - let a = now () in - if state.safe = 0 - then - draw screeny pagewidth screenheight pageyoffset pixmap - ; - let b = now () in - let d = b-.a in - if d > 0.0 (* 0.000405 *) - then - log "draw %f sec safe %d" d state.safe - ; - ); - with Not_found -> - drawplaceholder page; - if state.inflight < Array.length state.lru +let drawpage i l = + begin match getopaque l.pageno with + | Some opaque when validopaque opaque -> + GlDraw.color (1.0, 1.0, 1.0); + let a = now () in + if state.safe = 0 + then + draw l.pagedispy l.pagew l.pagevh l.pagey opaque + ; + let b = now () in + let d = b-.a in + vlog "draw %f sec safe %d" d state.safe; + + | Some _ -> + drawplaceholder l + + | None -> + if state.inflight < cblen state.pagecache then ( - Hashtbl.add state.pixcache key ""; - state.inflight <- succ state.inflight; - wcmd "render" [`i (pageno + 1) - ;`i pindex - ;`i pagewidth - ;`i pageheight]; + List.iter preload state.layout; ) else ( - log "inflight %d" state.inflight; + drawplaceholder l; + vlog "inflight %d" state.inflight; ); end; GlDraw.color (0.5, 0.5, 0.5); GlDraw.rect (0., float i) - (float (state.w - state.scrollw), float (i + (screeny - i))) + (float (state.w - conf.scrollw), float (i + (l.pagedispy - i))) ; - Gl.finish (); - screeny + screenheight; + l.pagedispy + l.pagevh; ;; let scrollindicator () = GlDraw.color (0.64 , 0.64, 0.64); GlDraw.rect - (float (state.w - state.scrollw), 0.) + (float (state.w - conf.scrollw), 0.) (float state.w, float state.h) ; GlDraw.color (0.0, 0.0, 0.0); @@ -414,10 +873,8 @@ let scrollindicator () = let sh = float state.h /. sh in let sh = max sh (float scrollh) in - let percent = float state.y /. (float state.maxy) in + let percent = yratio state.y in let position = (float state.h -. sh) *. percent in - (* log "position %f" position; *) - (* let position = sh *. percent in *) let position = if position +. sh > float state.h @@ -427,50 +884,198 @@ let scrollindicator () = position in GlDraw.rect - (float (state.w - state.scrollw), position) + (float (state.w - conf.scrollw), position) (float state.w, position +. sh) ; ;; +let showsel () = + match state.mstate with + | Mnone -> + () + + | Msel ((x0, y0), (x1, y1)) -> + let y0' = min y0 y1 + and y1 = max y0 y1 in + let y0 = y0' in + let f l = + if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh)) + || ((y1 >= l.pagedispy)) (* && y1 <= (dy + vh))) *) + then + match getopaque l.pageno with + | Some opaque when validopaque opaque -> + let oy = -l.pagey + l.pagedispy in + gettext opaque (min x0 x1, y0, max x1 x0, y1) oy conf.rectsel + | _ -> () + in + List.iter f state.layout +;; + +let showrects () = + Gl.enable `blend; + GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5; + GlFunc.blend_func `src_alpha `one_minus_src_alpha; + List.iter + (fun (pageno, c, (x0, y0), (x1, y1)) -> + List.iter (fun l -> + if l.pageno = pageno + then ( + let d = float (l.pagedispy - l.pagey) in + GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5; + GlDraw.rect (x0, y0 +. d) (x1, y1 +. d) + ) + ) state.layout + ) state.rects + ; + Gl.disable `blend; +;; + let display () = - (* GlClear.color (0.5, 0.5, 0.5) ~alpha:0.0; *) - (* GlClear.clear [`color]; *) ignore (List.fold_left drawpage 0 (state.layout)); + showrects (); scrollindicator (); + showsel (); + let len = String.length state.text in + begin match state.textentry with + | None -> + if len> 0 then showtext ' ' state.text + + | Some (c, text, _, _) -> + let s = + if len > 0 + then + text ^ " [" ^ state.text ^ "]" + else + text + in + showtext c s + end; Glut.swapBuffers (); ;; +let getlink x y = + let rec f = function + | l :: rest -> + begin match getopaque l.pageno with + | Some opaque when validopaque opaque -> + let y = y - l.pagedispy in + if y > 0 + then + let y = l.pagey + y in + let destpage = checklink opaque x y in + if destpage < 0 + then + f rest + else + destpage + else + f rest + | _ -> + f rest + end + | [] -> -1 + in + f state.layout +;; + +let gotopage n = + let y = getpagey n in + addnav (); + state.y <- y; + gotoy y; +;; + +let mouse ~button ~bstate ~x ~y = + match button with + | Glut.OTHER_BUTTON n when n == 3 || n == 4 && bstate = Glut.UP -> + let incr = + if n = 3 + then + -aincr + else + aincr + in + let incr = incr * 2 in + let y = clamp incr in + gotoy y + + | Glut.LEFT_BUTTON -> + let destpage = if bstate = Glut.DOWN then getlink x y else - 1 in + if destpage >= 0 + then + gotopage destpage + else ( + if bstate = Glut.DOWN + then ( + Glut.setCursor Glut.CURSOR_CROSSHAIR; + state.mstate <- Msel ((x, y), (x, y)); + Glut.postRedisplay () + ) + else ( + Glut.setCursor Glut.CURSOR_RIGHT_ARROW; + state.mstate <- Mnone; + ) + ) + + | _ -> + () +;; +let mouse ~button ~state ~x ~y = mouse button state x y;; + +let motion ~x ~y = + match state.mstate with + | Mnone -> () + | Msel (a, _) -> + state.mstate <- Msel (a, (x, y)); + Glut.postRedisplay () +;; + +let pmotion ~x ~y = + match state.mstate with + | Mnone -> + let pageno = getlink x y in + if pageno = -1 + then + Glut.setCursor Glut.CURSOR_RIGHT_ARROW + else + Glut.setCursor Glut.CURSOR_INFO + + | Msel (a, _) -> + () +;; + let () = - let w = 750 (* + state.scrollw *) - and h = 956 in - let w = 1080 in - let h = 1920 in + let name = ref "" in + Arg.parse [] (fun s -> name := s) "options:"; + let name = + if String.length !name = 0 + then (prerr_endline "filename missing"; exit 1) + else !name + in + + let w = 900 in + let h = 900 in let _ = Glut.init Sys.argv in let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in let () = Glut.initWindowSize w h in - let _ = Glut.createWindow "lpdf (press 'h' to get help)" in + let _ = Glut.createWindow ("llpp " ^ Filename.basename name) in let csock, ssock = Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 in + init ssock; state.w <- w; state.h <- h; state.csock <- csock; state.ssock <- ssock; - let name = - if Array.length Sys.argv = 1 - then "/home/malc/x/inc/Info_PT_Sans.pdf" - else Sys.argv.(1) - in writecmd csock ("open " ^ name ^ "\000"); - (* writecmd csock "open /home/malc/x/doc/cell/CBE_Handbook_v1.1_24APR2007_pub.pdf\000"; *) - (* writecmd csock "box 1"; *) let () = Glut.displayFunc display in let () = Glut.reshapeFunc reshape in let () = Glut.keyboardFunc keyboard in let () = Glut.specialFunc special in let () = Glut.idleFunc (Some idle) in - (* let () = Glut.mouseFunc mouse in *) - (* let () = Glut.motionFunc motion in *) + let () = Glut.mouseFunc mouse in + let () = Glut.motionFunc motion in + let () = Glut.passiveMotionFunc pmotion in Glut.mainLoop (); ;; diff --git a/tbs b/tbs index e9ba038..fcde5b1 100644 --- a/tbs +++ b/tbs @@ -24,12 +24,13 @@ fi mupdf=/home/malc/x/bld/mupdf mupdf=/home/malc/x/rcs/darcs/mupdf/mupdf/ +mupdf=/home/malc/x/rcs/svn/sumatrapdf-read-only/mupdf cc=gcc -ccopt="-Wall -Werror -I$mupdf -Wno-unused-function -Wno-unused" -targets="lpdf" #test testgl +ccopt="-Wall -Werror -I$mupdf" # -Wno-unused-function -Wno-unused" +targets="llpp" #test testgl lpath=$mupdf/build/linux-ppc-release/ -lpath=/home/malc/x/rcs/darcs/mupdf/mupdf/build/release +lpath=$mupdf/build/release ./b -O src:$h -r -O ccopt:"$ccopt" -O cc:"$cc" \ -O "mupdflibpath:$lpath" \ -- 2.11.4.GIT