From 604fff0e46736bf10d5004e2eb25dfa5be2a4148 Mon Sep 17 00:00:00 2001 From: malc Date: Thu, 26 Jan 2012 19:06:34 +0400 Subject: [PATCH] What can i say... --- BUILDING | 22 +- KEYS | 18 +- Thanks | 2 +- buildall.sh | 56 +- keystoml.ml | 2 +- link.c | 1292 ++++++++++------- main.ml | 4617 ++++++++++++++++++++++++++++++++++++++--------------------- 7 files changed, 3806 insertions(+), 2203 deletions(-) diff --git a/BUILDING b/BUILDING index 515acaa..f5b57d1 100644 --- a/BUILDING +++ b/BUILDING @@ -3,14 +3,19 @@ to be built and installed to use llpp, MuPDF's README[2] describes how to build it and lists its dependencies. Snapshots of MuPDF itself can be found here[3], dependencies here[4] -llpp also depends on OCaml[5] and lablGL[6], having a C compiler -wouldn't hurt either. +Note that MuPDF is a moving target therefore bellow is the commit id +of the git version of MuPDF that this version of llpp is known to work +with: +50dbc1a356577f3df15a876f6adb716dea29bdc5 + +llpp also depends on OCaml[5], lablGL[6] and GLUT[7], having a C +compiler wouldn't hurt either. To build llpp "the easy way" one can invoke `sh buildall.sh' which -will first attempt to automatically fetch openjpeg, jbig2dec, lablGL, -mupdf and build them then, if everything goes fine, it will proceed -building llpp itself. Note that OCaml and freetype should still be -installed separately. +will first attempt to automatically fetch lablGL, mupdf, mupdf's +prerequisites and build them, then, if everything goes fine, it will +proceed building llpp itself. Note that OCaml and GLUT still need to +be installed separately. Otherwise, provided that all prereqs are already built, one should invoke `sh build.sh' from the top of llpp's directory, prior to that @@ -29,3 +34,8 @@ $ ./llpp -p password /path/to/some.password.protected.pdf [4] http://mupdf.com/download/source/mupdf-thirdparty.zip [5] http://caml.inria.fr/ [6] http://wwwfun.kurims.kyoto-u.ac.jp/soft/lsl/lablgl.html +[7] http://www.opengl.org/resources/libraries/glut/ + http://freeglut.sourceforge.net/ + +Building on windows is for masochists and left as an exercise to +the reader diff --git a/KEYS b/KEYS index f3c7c64..78c1223 100644 --- a/KEYS +++ b/KEYS @@ -3,7 +3,10 @@ j/k - .............. mouse buttons 3/4 - .............. (aka mouse wheel) when control is held - zoom left/right arrow - pan left/right (when zoomed in) +cntrol + shift up/down - set previous zoom level +control + arrows - scroll up/down, pan left/right (by half a screen widht/height) primary mouse button[1] - click on link or select text[2] +other mouse button[1] - select rectangle to zoom to escape/q - quit backspace - go back after jumping (clicking link and suchlike) [3] alt-left/right arrow - go backward/forward in history @@ -68,8 +71,10 @@ tunables -Z - set zoom (percent) -A - set auto scroll step (pixels) -t - set thumbnail width (pixels) +-T - toggle trimming of margins +-I - invert colors -f - toggle "what's under cursor" identification - (only font name currently) + (only font name of the text under cursor and link target currently) bird's eye mode Ctrl-9,F9,esc - leave bird's eye view @@ -106,8 +111,15 @@ ctrl-g,esc - cancel [2] text selection requires xsel http://www.vergenet.net/~conrad/software/xsel/ - On OSX and Windows selected text will be dumped to the terminal - Also selection can not cross page boundaries [3] if the document was previously visited initial backspace will jump to the last visited place + +====================================================================== +Caveat emptor: + +o Selection can not cross page boundaries +o On OSX(cocoa) and Windows selected text will be dumped to the terminal +o Text searching is very naive +o Most of the Ctrl keys do not work on Windows and native OSX (X11 version + is fine though) diff --git a/Thanks b/Thanks index 349c764..1a65d5c 100644 --- a/Thanks +++ b/Thanks @@ -4,7 +4,7 @@ Jacques Garrigue, Isaac Trotts, Erick Tryzelaar and Christophe Raffali Krzysztof Kowalczyk -Artifex Software Inc., Tor Andersson +Artifex Software Inc., Tor Andersson, Robin Watts Alexander Graf, Richard Henderson, Edgar E. Iglesias diff --git a/buildall.sh b/buildall.sh index 425f418..7d8ae4f 100644 --- a/buildall.sh +++ b/buildall.sh @@ -1,45 +1,27 @@ # builds "hard" prerequisites and llpp set -e -use_sumatrapdf_patched_mupdf=false - mkdir -p 3rdp cd 3rdp - root=$(pwd) -openjpeg=http://openjpeg.googlecode.com/svn/trunk/ -jbig2dec=git://git.ghostscript.com/jbig2dec.git lablgl=http://wwwfun.kurims.kyoto-u.ac.jp/soft/lsl/dist/lablgl-1.04.tar.gz mupdf=git://git.ghostscript.com/mupdf.git -sumatrapdf=http://sumatrapdf.googlecode.com/svn/trunk - -test -d openjpeg || svn -r r608 checkout $openjpeg openjpeg -test -d jbig2dec || git clone $jbig2dec jbig2dec -test -d lablGL-1.04 || (wget $lablgl && tar -xzf lablgl-1.04.tar.gz) +mupdf3p=http://mupdf.com/download/mupdf-thirdparty.zip +mupdfrev=50dbc1a356577f3df15a876f6adb716dea29bdc5 +test -d lablGL-1.04 || (wget -nc $lablgl && tar -xzf lablgl-1.04.tar.gz) if ! test -d mupdf; then - if $use_sumatrapdf_patched_mupdf; then - svn checkout $sumatrapdf/mupdf mupdf - else - git clone $mupdf - fi + git clone $mupdf + (cd mupdf; git checkout $mupdfrev) +else + : #(cd mupdf; git pull $mupdf; git checkout $mupdfrev) fi -mkdir -p $root/bin -mkdir -p $root/lib -mkdir -p $root/include +test -d mupdf/thirdparty || (wget -nc $mupdf3p && unzip -d mupdf mupdf-thirdparty.zip) make=$(gmake -v >/dev/null 2>&1 && echo gmake || echo make) -(cd openjpeg \ - && $make dist \ - && cp dist/*.h $root/include/ \ - && cp dist/*.a $root/lib/) - -(cd jbig2dec \ - && $make -f Makefile.unix install prefix=$root && rm -f $root/lib/*.so*) - (cd lablGL-1.04 \ && cat Makefile.config.linux.mdk > Makefile.config \ && $make glut glutopt \ @@ -49,9 +31,6 @@ make=$(gmake -v >/dev/null 2>&1 && echo gmake || echo make) DLLDIR=$root/lib/ocaml/stublibs \ INSTALLDIR=$root/lib/ocaml/lablGL) -export CPATH=$root/include:$root/mupdf/pdf:$root/mupdf/fitz:$CPATH:/usr/local/include -export LIBRARY_PATH=$root/lib:$root/mupdf/build/release:$LIBRARY_PATH:/usr/local/lib - (cd mupdf && $make build=release) cd .. @@ -60,9 +39,23 @@ srcpath=$(dirname $0) sh mkhelp.sh $srcpath/keystoml.ml $srcpath/KEYS > help.ml -ccopt="$(freetype-config --cflags) -O -include ft2build.h -D_GNU_SOURCE" +tp=$root/mupdf/thirdparty + +ccopt="-O" +ccopt="$ccopt -I $tp/jbig2dec" +ccopt="$ccopt -I $tp/jpeg-8c" +ccopt="$ccopt -I $tp/freetype-2.4.4/include" +ccopt="$ccopt -I $tp/openjpeg-1.4/libopenjpeg" +ccopt="$ccopt -I $tp/zlib-1.2.5" +ccopt="$ccopt -I $root/mupdf/fitz -I $root/mupdf/pdf" + +ccopt="$ccopt -include ft2build.h -D_GNU_SOURCE" + +cclib="$cclib -L$root/mupdf/build/release" +cclib="$cclib -lmupdf -lfitz -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype" + if test "$1" = "opt"; then - cclib="-lmupdf -lfitz -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype -lpthread" + cclib="$cclib -lpthread" ocamlopt -c -o link.o -ccopt "$ccopt" $srcpath/link.c ocamlopt -c -o help.cmx help.ml ocamlopt -c -o parser.cmx $srcpath/parser.ml @@ -77,7 +70,6 @@ if test "$1" = "opt"; then parser.cmx \ main.cmx else - cclib="-lmupdf -lfitz -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype" ocamlc -c -o link.o -ccopt "$ccopt" $srcpath/link.c ocamlc -c -o help.cmo help.ml ocamlc -c -o parser.cmo $srcpath/parser.ml diff --git a/keystoml.ml b/keystoml.ml index 94dfadf..2fe0b3c 100644 --- a/keystoml.ml +++ b/keystoml.ml @@ -20,7 +20,7 @@ let tabify s = let b = Buffer.create 80 in Buffer.add_substring b s 0 (nonwspos+1); Buffer.add_string b "\t"; - Buffer.add_substring b s dashpos (String.length s - dashpos); + Buffer.add_substring b s dashpos (String.length s - dashpos); Buffer.contents b ;; diff --git a/link.c b/link.c index 8db25ab..5414bbe 100644 --- a/link.c +++ b/link.c @@ -4,6 +4,8 @@ #include #include +#define PIGGYBACK + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -16,12 +18,12 @@ #else #define FMT_s "%u" #endif -#pragma warning (disable:4244) -#pragma warning (disable:4996) -#pragma warning (disable:4995) #endif #ifdef _MSC_VER +#pragma warning (disable:4244) +#pragma warning (disable:4996) +#pragma warning (disable:4995) #define NORETURN __declspec (noreturn) #define UNUSED #define OPTIMIZE @@ -35,6 +37,18 @@ #define OPTIMIZE #endif +#ifdef __MINGW32__ +/* some versions of MingW have non idempotent %p */ +#include +#define FMT_ptr PRIxPTR +#define FMT_ptr_cast(p) ((intptr_t *) (p)) +#define FMT_ptr_cast2(p) ((intptr_t) (p)) +#else +#define FMT_ptr "p" +#define FMT_ptr_cast(p) (p) +#define FMT_ptr_cast2(p) (p) +#endif + #ifdef _WIN32 static void __declspec (noreturn) sockerr (int exitcode, const char *fmt, ...) { @@ -104,6 +118,18 @@ static void NORETURN errx (int exitcode, const char *fmt, ...) #define GL_TEXTURE_RECTANGLE_ARB 0x84F5 #endif +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif + +#ifndef GL_UNSIGNED_INT_8_8_8_8 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#endif + +#ifndef GL_UNSIGNED_INT_8_8_8_8_REV +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#endif + #include #include #include @@ -127,40 +153,40 @@ static void NORETURN errx (int exitcode, const char *fmt, ...) break; \ } -struct block { - int w; - int x; - int offset; +struct slice { + int h; int texindex; }; -struct slice { - int h; - struct block *blocks; +struct tile { + int x, y, w, h; + int slicecount; + fz_pixmap *pixmap; + struct slice slices[1]; }; struct pagedim { int pageno; int rotate; int left; - fz_rect box; - fz_bbox bbox; - fz_matrix ctm, ctm1; + int tctmready; + fz_bbox bounds; + fz_rect pagebox; + fz_rect mediabox; + fz_matrix ctm, zoomctm, lctm, tctm; }; struct page { + int gen; int pageno; - int slicecount; - int blockcount; + int pdimno; fz_text_span *text; - fz_pixmap *pixmap; pdf_page *drawpage; - struct pagedim pagedim; + fz_display_list *dlist; struct mark { int i; fz_text_span *span; } fmark, lmark; - struct slice *slices; }; #if !defined _WIN32 && !defined __APPLE__ @@ -169,8 +195,7 @@ struct page { struct { int sock; - int sliceheight, blockwidth; - struct page *pig; + int sliceheight; struct pagedim *pagedims; int pagecount; int pagedimcount; @@ -183,17 +208,27 @@ struct { int texcount; GLuint *texids; + GLenum texiform; GLenum texform; GLenum texty; + fz_colorspace *colorspace; + struct { int w, h; - struct block *block; + struct slice *slice; } *texowners; int rotate; int proportional; + int trimmargins; int needoutline; + int gen; + int aalevel; + + int trimanew; + fz_bbox trimfuzz; + fz_pixmap *pig; #ifdef _WIN32 HANDLE thread; @@ -205,6 +240,22 @@ struct { FT_Face face; } state; +static void UNUSED debug_rect (const char *cap, fz_rect r) +{ + printf ("%s(rect) %.2f,%.2f,%.2f,%.2f\n", cap, r.x0, r.y0, r.x1, r.y1); +} + +static void UNUSED debug_bbox (const char *cap, fz_bbox r) +{ + printf ("%s(bbox) %d,%d,%d,%d\n", cap, r.x0, r.y0, r.x1, r.y1); +} + +static void UNUSED debug_matrix (const char *cap, fz_matrix m) +{ + printf ("%s(matrix) %.2f,%.2f,%.2f,%.2f %.2f %.2f\n", cap, + m.a, m.b, m.c, m.d, m.e, m.f); +} + #ifdef _WIN32 static CRITICAL_SECTION critsec; @@ -259,7 +310,7 @@ static void *parse_pointer (const char *cap, const char *s) int ret; void *ptr; - ret = sscanf (s, "%p", &ptr); + ret = sscanf (s, "%" FMT_ptr, FMT_ptr_cast (&ptr)); if (ret != 1) { errx (1, "%s: cannot parse pointer in `%s'", cap, s); } @@ -277,12 +328,23 @@ static int hasdata (int sock) static double now (void) { +#ifdef _WIN32 + FILETIME ft; + uint64 tmp; + + GetSystemTimeAsFileTime (&ft); + tmp = ft.dwHighDateTime; + tmp <<= 32; + tmp |= ft.dwLowDateTime; + return tmp * 1e-7; +#else struct timeval tv; if (gettimeofday (&tv, NULL)) { err (1, "gettimeofday"); } return tv.tv_sec + tv.tv_usec*1e-6; +#endif } static void readdata (int fd, char *p, int size) @@ -358,16 +420,8 @@ static void openxref (char *filename, char *password) int i; for (i = 0; i < state.texcount; ++i) { - state.texowners[i].block = NULL; - } - - if (state.cache) { - fz_free_glyph_cache (state.ctx, state.cache); - } - - state.cache = fz_new_glyph_cache (state.ctx); - if (!state.cache) { - errx (1, "fz_newglyph_cache failed"); + state.texowners[i].w = -1; + state.texowners[i].slice = NULL; } if (state.xref) { @@ -381,9 +435,14 @@ static void openxref (char *filename, char *password) } state.pagedimcount = 0; - state.xref = pdf_open_xref (state.ctx, filename, password); - pdf_load_page_tree (state.xref); - + fz_set_aa_level (state.ctx, state.aalevel); + state.xref = pdf_open_xref (state.ctx, filename); + if (pdf_needs_password (state.xref)) { + int okay = pdf_authenticate_password (state.xref, password); + if (!okay) { + errx (1, "invalid password"); + } + } state.pagecount = pdf_count_pages (state.xref); } @@ -391,28 +450,31 @@ static void pdfinfo (void) { fz_obj *infoobj; - printd (state.sock, "i PDF version\t%d.%d", + printd (state.sock, "info PDF version\t%d.%d", state.xref->version / 10, state.xref->version % 10); infoobj = fz_dict_gets (state.xref->trailer, "Info"); if (infoobj) { int i; char *s; - char *items[] = { "Title", "Creator", "Producer", "CreationDate" }; + char *items[] = { "Title", "Author", "Creator", + "Producer", "CreationDate" }; for (i = 0; i < sizeof (items) / sizeof (*items); ++i) { fz_obj *obj = fz_dict_gets (infoobj, items[i]); s = pdf_to_utf8 (state.ctx, obj); if (*s) { if (i == 0) { - printd (state.sock, "t %s", s); + printd (state.sock, "title %s", s); } - printd (state.sock, "i %s\t%s", items[i], s); + printd (state.sock, "info %s\t%s", items[i], s); } fz_free (state.ctx, s); } - printd (state.sock, "ie"); } + printd (state.sock, "info Pages\t%d", state.pagecount); + printd (state.sock, "info Dimensions\t%d", state.pagedimcount); + printd (state.sock, "infoend"); } static int readlen (int fd) @@ -429,21 +491,16 @@ static int readlen (int fd) return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } -static void unlinkpage (struct page *page) +static void unlinktile (struct tile *tile) { int i; - for (i = 0; i < page->slicecount; ++i) { - int j; - struct slice *s = &page->slices[i]; - - for (j = 0; j < page->blockcount; ++j) { - struct block *b = &s->blocks[j]; + for (i = 0; i < tile->slicecount; ++i) { + struct slice *s = &tile->slices[i]; - if (b->texindex != -1) { - if (state.texowners[b->texindex].block == b) { - state.texowners[b->texindex].block = NULL; - } + if (s->texindex != -1) { + if (state.texowners[s->texindex].slice == s) { + state.texowners[s->texindex].slice = NULL; } } } @@ -451,71 +508,26 @@ static void unlinkpage (struct page *page) static void freepage (struct page *page) { - int i; - - fz_drop_pixmap (state.ctx, page->pixmap); - - unlinkpage (page); - if (page->text) { fz_free_text_span (state.ctx, page->text); } - if (page->drawpage) { - pdf_free_page (state.ctx, page->drawpage); - } - for (i = 0; i < page->slicecount; ++i) { - free (page->slices[i].blocks); - } - free (page->slices); + pdf_free_page (state.ctx, page->drawpage); + fz_free_display_list (state.ctx, page->dlist); free (page); } -static void subdivide (struct page *p, int blockcount) +static void freetile (struct tile *tile) { - int i; - int w = p->pixmap->w; - int h = p->pixmap->h; - int offset = 0; - int tw = MIN (w, state.blockwidth); - int th = MIN (h, state.sliceheight); - - for (i = 0; i < p->slicecount; ++i) { - int j, x, offset1; - struct slice *s = &p->slices[i]; - - s->h = MIN (th, h); - - s->blocks = calloc (sizeof (struct block), p->blockcount); - if (!s->blocks) { - err (1, "calloc blocks page %d slice %d, %d", - p->pageno, i, p->blockcount); - } - - w = p->pixmap->w; - x = 0; - offset1 = offset; - - for (j = 0; j < blockcount; ++j) { - struct block *b = &s->blocks[j]; - - b->texindex = -1; - b->w = MIN (tw, w); - b->x = x; - b->offset = offset1; - w -= tw; - x += tw; - offset1 += tw * 4; - } - offset += s->h * p->pixmap->w * 4; - h -= th; + unlinktile (tile); +#ifndef PIGGYBACK + fz_drop_pixmap (state.ctx, tile->pixmap); +#else + if (state.pig) { + fz_drop_pixmap (state.ctx, state.pig); } -} - -static int compatpdims (struct pagedim *p1, struct pagedim *p2) -{ - return p1->rotate == p2->rotate - && !memcmp (&p1->bbox, &p2->bbox, sizeof (p1->bbox)) - && !memcmp (&p1->ctm, &p2->ctm, sizeof (p1->ctm)); + state.pig = tile->pixmap; +#endif + free (tile); } #ifdef __ALTIVEC__ @@ -567,78 +579,125 @@ static void OPTIMIZE (3) clearpixmap (fz_pixmap *pixmap) #define clearpixmap(p) fz_clear_pixmap_with_color (p, 0xff) #endif -static void *render (int pageno, int pindex) +static fz_matrix trimctm (pdf_page *page, int pindex) { - fz_device *idev; - double start, end; - pdf_page *drawpage; - struct pagedim *pagedim; - struct page *page = NULL; - int slicecount, blockcount; - - start = now (); - printd (state.sock, "V rendering %d", pageno); - - pagedim = &state.pagedims[pindex]; - slicecount = (pagedim->bbox.y1 - pagedim->bbox.y0 - + state.sliceheight - 1) / state.sliceheight; - slicecount += slicecount == 0; + fz_matrix ctm; + struct pagedim *pdim = &state.pagedims[pindex]; - blockcount = (pagedim->bbox.x1 - pagedim->bbox.x0 - + state.blockwidth - 1) / state.blockwidth; - blockcount += blockcount == 0; + if (!pdim->tctmready) { + if (state.trimmargins) { + fz_rect realbox; - if (state.pig) { - if (compatpdims (&state.pig->pagedim, pagedim)) { - page = state.pig; - if (page->text) { - fz_free_text_span (state.ctx, page->text); - page->text = NULL; - } - if (page->drawpage) { - pdf_free_page (state.ctx, page->drawpage); - page->drawpage = NULL; - } + ctm = fz_concat (fz_rotate (-pdim->rotate), fz_scale (1, -1)); + realbox = fz_transform_rect (ctm, pdim->mediabox); + ctm = fz_concat (ctm, fz_translate (-realbox.x0, -realbox.y0)); + ctm = fz_concat (fz_invert_matrix (page->ctm), ctm); } else { - freepage (state.pig); - } - } - if (!page) { - page = calloc (sizeof (struct page), 1); - if (!page) { - err (1, "calloc page %d", pageno); - } - page->slices = calloc (sizeof (struct slice), slicecount); - if (!page->slices) { - err (1, "calloc slices %d %d", pageno, slicecount); + ctm = fz_identity; } - page->pixmap = fz_new_pixmap_with_rect (state.ctx, fz_device_rgb, - pagedim->bbox); + pdim->tctm = ctm; + pdim->tctmready = 1; } + return pdim->tctm; +} - page->slicecount = slicecount; - page->blockcount = blockcount; +static fz_matrix pagectm (struct page *page) +{ + return fz_concat (trimctm (page->drawpage, page->pdimno), + state.pagedims[page->pdimno].ctm); +} - drawpage = pdf_load_page (state.xref, pageno - 1); +static void *loadpage (int pageno, int pindex) +{ + fz_device *dev; + struct page *page = NULL; - clearpixmap (page->pixmap); + page = calloc (sizeof (struct page), 1); + if (!page) { + err (1, "calloc page %d", pageno); + } - idev = fz_new_draw_device (state.ctx, state.cache, page->pixmap); - pdf_run_page (state.xref, drawpage, idev, pagedim->ctm); - fz_free_device (idev); + page->drawpage = pdf_load_page (state.xref, pageno); + page->dlist = fz_new_display_list (state.ctx); + dev = fz_new_list_device (state.ctx, page->dlist); + pdf_run_page (state.xref, page->drawpage, dev, fz_identity, NULL); + fz_free_device (dev); - page->drawpage = drawpage; - page->pagedim = *pagedim; + page->pdimno = pindex; page->pageno = pageno; - subdivide (page, blockcount); - end = now (); + page->gen = state.gen; - printd (state.sock, "V rendering %d took %f sec", pageno, end - start); - state.pig = NULL; return page; } +static struct tile *alloctile (int h) +{ + int i; + int slicecount; + size_t tilesize; + struct tile *tile; + + slicecount = (h + state.sliceheight - 1) / state.sliceheight; + tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice)); + tile = calloc (tilesize, 1); + if (!tile) { + err (1, "can not allocate tile (" FMT_s " bytes)", tilesize); + } + for (i = 0; i < slicecount; ++i) { + int sh = MIN (h, state.sliceheight); + tile->slices[i].h = sh; + tile->slices[i].texindex = -1; + h -= sh; + } + tile->slicecount = slicecount; + return tile; +} + +static struct tile *rendertile (struct page *page, int x, int y, int w, int h) +{ + fz_bbox bbox; + fz_device *dev; + struct tile *tile; + struct pagedim *pdim; + + tile = alloctile (h); + pdim = &state.pagedims[page->pdimno]; + + bbox = pdim->bounds; + bbox.x0 += x; + bbox.y0 += y; + bbox.x1 = bbox.x0 + w; + bbox.y1 = bbox.y0 + h; + + if (state.pig) { + if (state.pig->w == w + && state.pig->h == h + && state.pig->colorspace == state.colorspace) { + tile->pixmap = state.pig; + tile->pixmap->x = bbox.x0; + tile->pixmap->y = bbox.y0; + } + else { + fz_drop_pixmap (state.ctx, state.pig); + } + state.pig = NULL; + } + if (!tile->pixmap) { + tile->pixmap = + fz_new_pixmap_with_rect (state.ctx, state.colorspace, bbox); + } + + tile->w = w; + tile->h = h; + clearpixmap (tile->pixmap); + dev = fz_new_draw_device (state.ctx, tile->pixmap); + fz_execute_display_list (page->dlist, dev, pagectm (page), bbox, NULL); + fz_free_device (dev); + + return tile; +} + static void initpdims (void) { int pageno; @@ -647,34 +706,85 @@ static void initpdims (void) start = now (); for (pageno = 0; pageno < state.pagecount; ++pageno) { int rotate; - fz_rect box; + fz_obj *pageobj; struct pagedim *p; - fz_obj *obj, *pageobj; + fz_rect mediabox, cropbox; pageobj = state.xref->page_objs[pageno]; - obj = fz_dict_gets (pageobj, "CropBox"); - if (!fz_is_array (obj)) { - obj = fz_dict_gets (pageobj, "MediaBox"); - if (!fz_is_array (obj)) { - fz_throw (state.ctx, "cannot find page bounds %d (%d Rd)", - fz_to_num (pageobj), fz_to_gen (pageobj)); + if (state.trimmargins) { + fz_obj *obj; + pdf_page *page; + + page = pdf_load_page (state.xref, pageno); + obj = fz_dict_gets (pageobj, "llpp.TrimBox"); + if (state.trimanew || !obj) { + fz_rect rect; + fz_bbox bbox; + fz_matrix ctm; + fz_device *dev; + + dev = fz_new_bbox_device (state.ctx, &bbox); + dev->hints |= FZ_IGNORE_SHADE; + ctm = fz_invert_matrix (page->ctm); + pdf_run_page (state.xref, page, dev, fz_identity, NULL); + fz_free_device (dev); + + rect.x0 = bbox.x0 + state.trimfuzz.x0; + rect.x1 = bbox.x1 + state.trimfuzz.x1; + rect.y0 = bbox.y0 + state.trimfuzz.y0; + rect.y1 = bbox.y1 + state.trimfuzz.y1; + rect = fz_transform_rect (ctm, rect); + rect = fz_intersect_rect (rect, page->mediabox); + + if (fz_is_empty_rect (rect)) { + mediabox = page->mediabox; + } + else { + mediabox = rect; + } + + obj = fz_new_array (state.ctx, 4); + fz_array_push (obj, fz_new_real (state.ctx, mediabox.x0)); + fz_array_push (obj, fz_new_real (state.ctx, mediabox.y0)); + fz_array_push (obj, fz_new_real (state.ctx, mediabox.x1)); + fz_array_push (obj, fz_new_real (state.ctx, mediabox.y1)); + fz_dict_puts (pageobj, "llpp.TrimBox", obj); } - } - box = pdf_to_rect (state.ctx, obj); + else { + mediabox.x0 = fz_to_real (fz_array_get (obj, 0)); + mediabox.y0 = fz_to_real (fz_array_get (obj, 1)); + mediabox.x1 = fz_to_real (fz_array_get (obj, 2)); + mediabox.y1 = fz_to_real (fz_array_get (obj, 3)); + } + + rotate = page->rotate; + pdf_free_page (state.ctx, page); - obj = fz_dict_gets (pageobj, "Rotate"); - if (fz_is_int (obj)) { - rotate = fz_to_int (obj); + printd (state.sock, "progress %f Trimming %d", + (double) (pageno + 1) / state.pagecount, + pageno + 1); } - else { - rotate = 0; + else { + mediabox = pdf_to_rect (state.ctx, fz_dict_gets (pageobj, "MediaBox")); + if (fz_is_empty_rect (mediabox)) { + fprintf (stderr, "cannot find page size for page %d\n", pageno+1); + mediabox.x0 = 0; + mediabox.y0 = 0; + mediabox.x1 = 612; + mediabox.y1 = 792; + } + + cropbox = pdf_to_rect (state.ctx, fz_dict_gets (pageobj, "CropBox")); + if (!fz_is_empty_rect (cropbox)) { + mediabox = fz_intersect_rect (mediabox, cropbox); + } + rotate = fz_to_int (fz_dict_gets (pageobj, "Rotate")); } - rotate += state.rotate; - p = &state.pagedims[state.pagedimcount - 1]; - if ((state.pagedimcount == 0) - || (p->rotate != rotate || memcmp (&p->box, &box, sizeof (box)))) { + if (state.pagedimcount == 0 + || (p = &state.pagedims[state.pagedimcount-1], p->rotate != rotate) + || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) { size_t size; size = (state.pagedimcount + 1) * sizeof (*state.pagedims); @@ -683,53 +793,50 @@ static void initpdims (void) err (1, "realloc pagedims to " FMT_s " (%d elems)", size, state.pagedimcount + 1); } + p = &state.pagedims[state.pagedimcount++]; p->rotate = rotate; - p->box = box; + p->mediabox = mediabox; p->pageno = pageno; } } end = now (); - printd (state.sock, "T Processed %d pages in %f seconds", - state.pagecount, end - start); + if (state.trimmargins) { + printd (state.sock, "progress 1 Trimmed %d pages in %f seconds", + state.pagecount, end - start); + } + else { + printd (state.sock, "vmsg Processed %d pages in %f seconds", + state.pagecount, end - start); + } + state.trimanew = 0; } static void layout (void) { int pindex; + fz_rect box; fz_matrix ctm; - fz_rect box, box2; double zoom, w, maxw = 0; struct pagedim *p = state.pagedims; if (state.proportional) { for (pindex = 0; pindex < state.pagedimcount; ++pindex, ++p) { - box.x0 = MIN (p->box.x0, p->box.x1); - box.y0 = MIN (p->box.y0, p->box.y1); - box.x1 = MAX (p->box.x0, p->box.x1); - box.y1 = MAX (p->box.y0, p->box.y1); - - ctm = fz_identity; - ctm = fz_concat (ctm, fz_translate (0, -box.y1)); - ctm = fz_concat (ctm, fz_rotate (p->rotate)); - box2 = fz_transform_rect (ctm, box); - w = box2.x1 - box2.x0; + box = fz_transform_rect (fz_rotate (p->rotate + state.rotate), + p->mediabox); + w = box.x1 - box.x0; maxw = MAX (w, maxw); } } p = state.pagedims; for (pindex = 0; pindex < state.pagedimcount; ++pindex, ++p) { - box.x0 = MIN (p->box.x0, p->box.x1); - box.y0 = MIN (p->box.y0, p->box.y1); - box.x1 = MAX (p->box.x0, p->box.x1); - box.y1 = MAX (p->box.y0, p->box.y1); + fz_bbox bbox; - ctm = fz_identity; - ctm = fz_concat (ctm, fz_translate (0, -box.y1)); - ctm = fz_concat (ctm, fz_rotate (p->rotate)); - box2 = fz_transform_rect (ctm, box); - w = box2.x1 - box2.x0; + ctm = fz_rotate (state.rotate); + box = fz_transform_rect (fz_rotate (p->rotate + state.rotate), + p->mediabox); + w = box.x1 - box.x0; if (state.proportional) { double scale = w / maxw; @@ -738,47 +845,67 @@ static void layout (void) else { zoom = state.w / w; } + + p->zoomctm = fz_scale (zoom, zoom); + ctm = fz_concat (p->zoomctm, ctm); + + p->pagebox = fz_transform_rect (fz_rotate (p->rotate), p->mediabox); + p->pagebox.x1 -= p->pagebox.x0; + p->pagebox.y1 -= p->pagebox.y0; + p->pagebox.x0 = 0; + p->pagebox.y0 = 0; + bbox = fz_round_rect (fz_transform_rect (ctm, p->pagebox)); + + p->bounds = bbox; + p->left = state.proportional ? ((maxw - w) * zoom) / 2.0 : 0; + p->ctm = ctm; + ctm = fz_identity; - ctm = fz_concat (ctm, fz_translate (0, -box.y1)); + ctm = fz_concat (ctm, fz_translate (0, -p->mediabox.y1)); ctm = fz_concat (ctm, fz_scale (zoom, -zoom)); - memcpy (&p->ctm1, &ctm, sizeof (ctm)); - ctm = fz_concat (ctm, fz_rotate (p->rotate)); - p->bbox = fz_round_rect (fz_transform_rect (ctm, box)); - p->left = state.proportional ? ((maxw - w) * zoom) / 2.0 : 0; - memcpy (&p->ctm, &ctm, sizeof (ctm)); + ctm = fz_concat (ctm, fz_rotate (p->rotate + state.rotate)); + p->lctm = ctm; + + p->tctmready = 0; } while (p-- != state.pagedims) { - printd (state.sock, "l %d %d %d %d", - p->pageno, p->bbox.x1 - p->bbox.x0, p->bbox.y1 - p->bbox.y0, - p->left); + int w = p->bounds.x1 - p->bounds.x0; + int h = p->bounds.y1 - p->bounds.y0; + + printd (state.sock, "pdim %d %d %d %d", p->pageno, w, h, p->left); } } static void recurse_outline (fz_outline *outline, int level) { while (outline) { - int pageno; - int i, top = 0, h; - struct pagedim *pagedim = state.pagedims; + fz_link_dest *dest; + int i, top = 0; + struct pagedim *pdim = state.pagedims; - pageno = outline->page; + dest = &outline->dest; for (i = 0; i < state.pagedimcount; ++i) { - if (state.pagedims[i].pageno > pageno) + if (state.pagedims[i].pageno > dest->ld.gotor.page) break; - pagedim = &state.pagedims[i]; + pdim = &state.pagedims[i]; } -#ifdef HAS_PRECISE_POSITIONING - if (!outline->x_is_null && !outline->y_is_null) { - fz_point p = fz_transform_point (pagedim->ctm, outline->p); + if (dest->ld.gotor.flags & fz_link_flag_t_valid) { + fz_point p; + p.x = 0; + p.y = dest->ld.gotor.lt.y; + p = fz_transform_point (pdim->lctm, p); top = p.y; } -#endif - h = pagedim->bbox.y1 - pagedim->bbox.y0; - lprintf ("%*c%s %d %d\n", level, ' ', outline->title, pageno, top); - if (pageno > 0) { + if (dest->ld.gotor.page >= 0 && dest->ld.gotor.page < 1<<30) { + int h; + double y0, y1; + + y0 = MIN (pdim->bounds.y0, pdim->bounds.y1); + y1 = MAX (pdim->bounds.y0, pdim->bounds.y1); + h = y1 - y0; printd (state.sock, "o %d %d %d %d %s", - level, pageno, top, h, outline->title); + level, dest->ld.gotor.page, top, h, outline->title); } if (outline->down) { recurse_outline (outline->down, level + 1); @@ -830,12 +957,15 @@ static void search (regex_t *re, int pageno, int y, int forward) if (niters++ == 5) { niters = 0; if (hasdata (state.sock)) { - printd (state.sock, "T attention requested aborting search at %d", + printd (state.sock, + "progress 1 attention requested aborting search at %d", pageno); stop = 1; } else { - printd (state.sock, "T searching in page %d", pageno); + printd (state.sock, "progress %f searching in page %d", + (double) (pageno + 1) / state.pagecount, + pageno); } } pdimprev = NULL; @@ -855,11 +985,9 @@ static void search (regex_t *re, int pageno, int y, int forward) drawpage = pdf_load_page (state.xref, pageno); - ctm = fz_rotate (pdim->rotate); - text = fz_new_text_span (state.ctx); tdev = fz_new_text_device (state.ctx, text); - pdf_run_page (state.xref, drawpage, tdev, pdim->ctm1); + pdf_run_page (state.xref, drawpage, tdev, fz_identity, NULL); fz_free_device (tdev); nspans = 0; @@ -912,7 +1040,7 @@ static void search (regex_t *re, int pageno, int y, int forward) char errbuf[80]; size = regerror (ret, re, errbuf, sizeof (errbuf)); printd (state.sock, - "T regexec error `%.*s'", + "msg regexec error `%.*s'", (int) size, errbuf); fz_free_text_span (state.ctx, text); pdf_free_page (state.ctx, drawpage); @@ -921,13 +1049,9 @@ static void search (regex_t *re, int pageno, int y, int forward) } } else { - int xoff, yoff; fz_bbox *sb, *eb; fz_point p1, p2, p3, p4; - xoff = pdim->left - pdim->bbox.x0; - yoff = -pdim->bbox.y0; - sb = &span->text[rm.rm_so].bbox; eb = &span->text[rm.rm_eo - 1].bbox; @@ -940,30 +1064,36 @@ static void search (regex_t *re, int pageno, int y, int forward) p4.x = sb->x0; p4.y = eb->y1; + trimctm (drawpage, pdim - state.pagedims); + ctm = fz_concat (pdim->tctm, pdim->zoomctm); + p1 = fz_transform_point (ctm, p1); p2 = fz_transform_point (ctm, p2); p3 = fz_transform_point (ctm, p3); p4 = fz_transform_point (ctm, p4); if (!stop) { - printd (state.sock, "F %d %d %f %f %f %f %f %f %f %f", + printd (state.sock, + "firstmatch %d %d %f %f %f %f %f %f %f %f", pageno, 1, - p1.x + xoff, p1.y + yoff, - p2.x + xoff, p2.y + yoff, - p3.x + xoff, p3.y + yoff, - p4.x + xoff, p4.y + yoff); + p1.x, p1.y, + p2.x, p2.y, + p3.x, p3.y, + p4.x, p4.y); - printd (state.sock, "T found at %d `%.*s' in %f sec", - pageno, rm.rm_eo - rm.rm_so, &buf[rm.rm_so], + printd (state.sock, + "progress 1 found at %d `%.*s' in %f sec", + pageno, (int) (rm.rm_eo - rm.rm_so), &buf[rm.rm_so], now () - start); } else { - printd (state.sock, "R %d %d %f %f %f %f %f %f %f %f", + printd (state.sock, + "match %d %d %f %f %f %f %f %f %f %f", pageno, 2, - p1.x + xoff, p1.y + yoff, - p2.x + xoff, p2.y + yoff, - p3.x + xoff, p3.y + yoff, - p4.x + xoff, p4.y + yoff); + p1.x, p1.y, + p2.x, p2.y, + p3.x, p3.y, + p4.x, p4.y); } stop = 1; } @@ -982,9 +1112,37 @@ static void search (regex_t *re, int pageno, int y, int forward) } end = now (); if (!stop) { - printd (state.sock, "T no matches %f sec", end - start); + printd (state.sock, "progress 1 no matches %f sec", end - start); + } + printd (state.sock, "clearrects"); +} + +static void set_tex_params (int colorspace) +{ + switch (colorspace) { + case 0: + state.texiform = GL_RGBA8; + state.texform = GL_RGBA; + state.texty = GL_UNSIGNED_BYTE; + state.colorspace = fz_device_rgb; + break; + case 1: + state.texiform = GL_RGBA8; + state.texform = GL_BGRA; + state.texty = fz_is_big_endian () + ? GL_UNSIGNED_INT_8_8_8_8 + : GL_UNSIGNED_INT_8_8_8_8_REV; + state.colorspace = fz_device_bgr; + break; + case 2: + state.texiform = GL_LUMINANCE_ALPHA; + state.texform = GL_LUMINANCE_ALPHA; + state.texty = GL_UNSIGNED_BYTE; + state.colorspace = fz_device_gray; + break; + default: + errx (1, "invalid colorspce %d", colorspace); } - printd (state.sock, "D"); } static @@ -1023,22 +1181,44 @@ mainloop (void *unused) password = filename + filenamelen + 1; openxref (filename, password); + printd (state.sock, "msg Opened %s (press h/F1 to get help)", + filename); initpdims (); pdfinfo (); state.needoutline = 1; } - else if (!strncmp ("free", p, 4)) { + else if (!strncmp ("cs", p, 2)) { + int i, colorspace; + + ret = sscanf (p + 2, " %d", &colorspace); + if (ret != 1) { + errx (1, "malformed aa `%.*s' ret=%d", len, p, ret); + } + lock ("cs"); + set_tex_params (colorspace); + for (i = 0; i < state.texcount; ++i) { + state.texowners[i].w = -1; + state.texowners[i].slice = NULL; + } + unlock ("cs"); + } + else if (!strncmp ("freepage", p, 8)) { void *ptr; - ret = sscanf (p + 4, " %p", &ptr); + ret = sscanf (p + 8, " %" FMT_ptr, FMT_ptr_cast (&ptr)); if (ret != 1) { - errx (1, "malformed free `%.*s' ret=%d", len, p, ret); + errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret); } - unlinkpage (ptr); - if (state.pig) { - freepage (state.pig); + freepage (ptr); + } + else if (!strncmp ("freetile", p, 8)) { + void *ptr; + + ret = sscanf (p + 8, " %" FMT_ptr, FMT_ptr_cast (&ptr)); + if (ret != 1) { + errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret); } - state.pig = ptr; + freetile (ptr); } else if (!strncmp ("search", p, 6)) { int icase, pageno, y, ret, len2, forward; @@ -1059,7 +1239,7 @@ mainloop (void *unused) size_t size; size = regerror (ret, &re, errbuf, sizeof (errbuf)); - printd (state.sock, "T regcomp failed `%.*s'", + printd (state.sock, "msg regcomp failed `%.*s'", (int) size, errbuf); } else { @@ -1070,7 +1250,7 @@ mainloop (void *unused) else if (!strncmp ("geometry", p, 8)) { int w, h; - printd (state.sock, "c"); + printd (state.sock, "clear"); ret = sscanf (p + 8, " %d %d", &w, &h); if (ret != 2) { errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret); @@ -1082,63 +1262,101 @@ mainloop (void *unused) int i; state.w = w; for (i = 0; i < state.texcount; ++i) { - state.texowners[i].block = NULL; + state.texowners[i].slice = NULL; } } layout (); process_outline (); + state.gen++; unlock ("geometry"); - printd (state.sock, "C %d", state.pagecount); + printd (state.sock, "continue %d", state.pagecount); } - else if (!strncmp ("reinit", p, 6)) { - float rotate; - int proportional; + else if (!strncmp ("reqlayout", p, 9)) { + int rotate, proportional; - printd (state.sock, "c"); - ret = sscanf (p + 6, " %f %d", &rotate, &proportional); + printd (state.sock, "clear"); + ret = sscanf (p + 9, " %d %d", &rotate, &proportional); if (ret != 2) { - errx (1, "bad rotate line `%.*s' ret=%d", len, p, ret); + errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret); } - lock ("reinit"); + lock ("reqlayout"); state.rotate = rotate; state.proportional = proportional; - state.pagedimcount = 0; - free (state.pagedims); - state.pagedims = NULL; - initpdims (); layout (); - process_outline (); - if (state.pig) { - freepage (state.pig); - state.pig = NULL; - } - unlock ("reinit"); - printd (state.sock, "C %d", state.pagecount); + unlock ("reqlayout"); + printd (state.sock, "continue %d", state.pagecount); } - else if (!strncmp ("render", p, 6)) { - int pageno, pindex, w, h, ret; + else if (!strncmp ("page", p, 4)) { + double a, b; struct page *page; + int pageno, pindex, ret; - ret = sscanf (p + 6, " %d %d %d %d", &pageno, &pindex, &w, &h); - if (ret != 4) { + ret = sscanf (p + 4, " %d %d", &pageno, &pindex); + if (ret != 2) { errx (1, "bad render line `%.*s' ret=%d", len, p, ret); } - lock ("render"); - page = render (pageno, pindex); - unlock ("render"); + lock ("page"); + a = now (); + page = loadpage (pageno, pindex); + b = now (); + unlock ("page"); - printd (state.sock, "r %d %d %d %d %d %d %p", - pageno, - state.w, - state.h, - state.rotate, - state.proportional, - w * h * 4, - page); + printd (state.sock, "page %" FMT_ptr " %f", + FMT_ptr_cast2 (page), b - a); + } + else if (!strncmp ("tile", p, 4)) { + int x, y, w, h, ret; + struct page *page; + struct tile *tile; + double a, b; + + ret = sscanf (p + 4, " %" FMT_ptr " %d %d %d %d", + FMT_ptr_cast (&page), &x, &y, &w, &h); + if (ret != 5) { + errx (1, "bad tile line `%.*s' ret=%d", len, p, ret); + } + + lock ("tile"); + a = now (); + tile = rendertile (page, x, y, w, h); + b = now (); + unlock ("tile"); + + printd (state.sock, "tile %d %d %" FMT_ptr " %u %f", + x, y, + FMT_ptr_cast2 (tile), + tile->w * tile->h * tile->pixmap->n, + b - a); + } + else if (!strncmp ("settrim", p, 7)) { + int trimmargins; + fz_bbox fuzz; + + ret = sscanf (p + 7, " %d %d %d %d %d", &trimmargins, + &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1); + if (ret != 5) { + errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret); + } + printd (state.sock, "clear"); + lock ("settrim"); + state.trimmargins = trimmargins; + state.needoutline = 1; + if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) { + state.trimanew = 1; + state.trimfuzz = fuzz; + } + state.pagedimcount = 0; + free (state.pagedims); + state.pagedims = NULL; + initpdims (); + layout (); + process_outline (); + unlock ("settrim"); + printd (state.sock, "continue %d", state.pagecount); } else if (!strncmp ("interrupt", p, 9)) { - printd (state.sock, "V interrupted"); + printd (state.sock, "vmsg interrupted"); } else if (!strncmp ("quit", p, 4)) { return 0; @@ -1150,9 +1368,8 @@ mainloop (void *unused) return 0; } -static void showsel (struct page *page, int oy) +static void showsel (struct page *page, int ox, int oy) { - int ox; fz_bbox bbox; fz_text_span *span; struct mark first, last; @@ -1166,8 +1383,8 @@ static void showsel (struct page *page, int oy) glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA); glColor4f (0.5f, 0.5f, 0.0f, 0.6f); - ox = -page->pixmap->x + page->pagedim.left; - oy = -page->pixmap->y + oy; + ox -= state.pagedims[page->pdimno].bounds.x0; + oy -= state.pagedims[page->pdimno].bounds.y0; for (span = first.span; span; span = span->next) { int i, j, k; @@ -1204,22 +1421,20 @@ static void showsel (struct page *page, int oy) glDisable (GL_BLEND); } -static void highlightlinks (struct page *page, int yoff) +static void highlightlinks (struct page *page, int xoff, int yoff) { - pdf_link *link; - int xoff; + fz_link *link; + fz_matrix ctm; glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); glEnable (GL_LINE_STIPPLE); glLineStipple (0.5, 0xcccc); - xoff = page->pagedim.left - page->pixmap->x; - yoff -= page->pixmap->y; + ctm = fz_concat (pagectm (page), fz_translate (xoff, yoff)); glBegin (GL_QUADS); for (link = page->drawpage->links; link; link = link->next) { fz_point p1, p2, p3, p4; - fz_matrix ctm = page->pagedim.ctm; p1.x = link->rect.x0; p1.y = link->rect.y0; @@ -1238,16 +1453,16 @@ static void highlightlinks (struct page *page, int yoff) p3 = fz_transform_point (ctm, p3); p4 = fz_transform_point (ctm, p4); - switch (link->kind) { - case PDF_LINK_GOTO: glColor3ub (255, 0, 0); break; - case PDF_LINK_URI: glColor3ub (0, 0, 255); break; + switch (link->dest.kind) { + case FZ_LINK_GOTO: glColor3ub (255, 0, 0); break; + case FZ_LINK_URI: glColor3ub (0, 0, 255); break; default: glColor3ub (0, 0, 0); break; } - glVertex2f (p1.x + xoff, p1.y + yoff); - glVertex2f (p2.x + xoff, p2.y + yoff); - glVertex2f (p3.x + xoff, p3.y + yoff); - glVertex2f (p4.x + xoff, p4.y + yoff); + glVertex2f (p1.x, p1.y); + glVertex2f (p2.x, p2.y); + glVertex2f (p3.x, p3.y); + glVertex2f (p4.x, p4.y); } glEnd (); @@ -1255,184 +1470,155 @@ static void highlightlinks (struct page *page, int yoff) glDisable (GL_LINE_STIPPLE); } -static void upload (struct page *page, int slicenum, int blocknum) +static void uploadslice (struct tile *tile, struct slice *slice) { - double start, end; - struct slice *slice = &page->slices[slicenum]; - struct block *block = &slice->blocks[blocknum]; + int offset; + struct slice *slice1; - if (block->texindex != -1 - && state.texowners[block->texindex].block == block) { - glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[block->texindex]); + offset = 0; + for (slice1 = tile->slices; slice != slice1; slice1++) { + offset += slice1->h * tile->w * tile->pixmap->n; } - else { + if (slice->texindex != -1 + && state.texowners[slice->texindex].slice == slice) { + glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[slice->texindex]); + } + else { int subimage = 0; - int index = (state.texindex++ % state.texcount); + int texindex = state.texindex++ % state.texcount; - if (state.texowners[index].w == block->w) { - if (state.texowners[index].h >= slice->h ) { + if (state.texowners[texindex].w == tile->w) { + if (state.texowners[texindex].h >= slice->h) { subimage = 1; } else { - state.texowners[index].h = slice->h; + state.texowners[texindex].h = slice->h; } } else { - state.texowners[index].h = slice->h; + state.texowners[texindex].h = slice->h; } - state.texowners[index].w = block->w; - state.texowners[index].block = block; - block->texindex = index; - - glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[block->texindex]); - glPixelStorei (GL_UNPACK_ROW_LENGTH, page->pixmap->w); + state.texowners[texindex].w = tile->w; + state.texowners[texindex].slice = slice; + slice->texindex = texindex; - start = now (); - if (0) { - GLenum err = glGetError (); - if (err != GL_NO_ERROR) { - printf ("\033[0;31mERROR1 %d %d %#x\033[0m\n", - block->w, slice->h, err); - abort (); - } - } + glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[texindex]); if (subimage) { glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, - block->w, + tile->w, slice->h, state.texform, state.texty, - page->pixmap->samples + block->offset + tile->pixmap->samples+offset ); } else { glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, - GL_RGBA8, - block->w, + state.texiform, + tile->w, slice->h, 0, state.texform, state.texty, - page->pixmap->samples + block->offset + tile->pixmap->samples+offset ); } - if (0) { - GLenum err = glGetError (); - if (err != GL_NO_ERROR) { - printf ("\033[0;31mERROR %d %d %#x\033[0m\n", - block->w, slice->h, err); - abort (); - } - } - - end = now (); - (void) start; - (void) end; - lprintf ("%s[%d] slice=%d,%d(%d,%d) tex=%d,%d offset=%d %f sec\n", - subimage ? "sub" : "img", - page->pageno, slicenum, blocknum, - block->w, slice->h, - block->texindex, - state.texids[block->texindex], - block->offset, - end - start); } } -CAMLprim value ml_draw (value args_v, value ptr_v) +CAMLprim value ml_drawtile (value args_v, value ptr_v) { CAMLparam2 (args_v, ptr_v); - int dispy = Int_val (Field (args_v, 0)); - int h = Int_val (Field (args_v, 1)); - int py = Int_val (Field (args_v, 2)); - int hlinks = Bool_val (Field (args_v, 3)); + int dispx = Int_val (Field (args_v, 0)); + int dispy = Int_val (Field (args_v, 1)); + int dispw = Int_val (Field (args_v, 2)); + int disph = Int_val (Field (args_v, 3)); + int tilex = Int_val (Field (args_v, 4)); + int tiley = Int_val (Field (args_v, 5)); char *s = String_val (ptr_v); - int ret; - void *ptr; - struct page *page; - int slicenum; - int yoff = dispy - py; - struct slice *slice; - - ret = sscanf (s, "%p", &ptr); - if (ret != 1) { - errx (1, "cannot parse pointer `%s'", s); - } - page = ptr; - - ARSERT (h >= 0 && "ml_draw wrong h"); - ARSERT (py <= page->pixmap->h && "ml_draw wrong py"); + struct tile *tile = parse_pointer ("ml_drawtile", s); glEnable (GL_TEXTURE_RECTANGLE_ARB); + { + int slicey, firstslice; + struct slice *slice; - for (slicenum = 0, slice = page->slices; - slicenum < page->slicecount; ++slicenum, ++slice) { - if (slice->h > py) { - break; - } - py -= slice->h; - } - h = MIN (state.h, h); - - while (h) { - int th, left, blocknum; - - ARSERT (slicenum < page->slicecount && "ml_draw wrong slicenum"); + firstslice = tiley / state.sliceheight; + slice = &tile->slices[firstslice]; + slicey = tiley % state.sliceheight; - th = MIN (h, slice->h - py); - left = page->pagedim.left; + while (disph > 0) { + int dh; - for (blocknum = 0; blocknum < page->blockcount; ++blocknum) { - struct block *block = &slice->blocks[blocknum]; + dh = slice->h - slicey; + dh = MIN (disph, dh); + uploadslice (tile, slice); - upload (page, slicenum, blocknum); glBegin (GL_QUADS); { - glTexCoord2i (0, py); - glVertex2i (left, dispy); + glTexCoord2i (tilex, slicey); + glVertex2i (dispx, dispy); - glTexCoord2i (block->w, py); - glVertex2i (left + block->w, dispy); + glTexCoord2i (tilex+dispw, slicey); + glVertex2i (dispx+dispw, dispy); - glTexCoord2i (block->w, py + th); - glVertex2i (left + block->w, dispy + th); + glTexCoord2i (tilex+dispw, slicey+dh); + glVertex2i (dispx+dispw, dispy+dh); - glTexCoord2i (0, py + th); - glVertex2i (left, dispy + th); + glTexCoord2i (tilex, slicey+dh); + glVertex2i (dispx, dispy+dh); } glEnd (); - left += block->w; - } - h -= th; - py = 0; - dispy += th; - slicenum += 1; + dispy += dh; + disph -= dh; + slice++; + ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0)); + slicey = 0; + } } - glDisable (GL_TEXTURE_RECTANGLE_ARB); + CAMLreturn (Val_unit); +} + +CAMLprim value ml_postprocess (value ptr_v, value hlinks_v, + value xoff_v, value yoff_v) +{ + CAMLparam4 (ptr_v, hlinks_v, xoff_v, yoff_v); + int xoff = Int_val (xoff_v); + int yoff = Int_val (yoff_v); + char *s = String_val (ptr_v); + struct page *page = parse_pointer ("ml_postprocess", s); + + if (Bool_val (hlinks_v)) highlightlinks (page, xoff, yoff); - showsel (page, yoff); - if (hlinks) highlightlinks (page, yoff); + if (trylock ("ml_postprocess")) { + goto done; + } + showsel (page, xoff, yoff); + unlock ("ml_postprocess"); + done: CAMLreturn (Val_unit); } -static pdf_link *getlink (struct page *page, int x, int y) +static fz_link *getlink (struct page *page, int x, int y) { fz_point p; fz_matrix ctm; - pdf_link *link; + fz_link *link; - p.x = x + page->pixmap->x; - p.y = y + page->pixmap->y; + p.x = x; + p.y = y; - ctm = fz_invert_matrix (page->pagedim.ctm); + ctm = fz_concat (trimctm (page->drawpage, page->pdimno), + state.pagedims[page->pdimno].ctm); + ctm = fz_invert_matrix (ctm); p = fz_transform_point (ctm, p); for (link = page->drawpage->links; link; link = link->next) { @@ -1445,14 +1631,33 @@ static pdf_link *getlink (struct page *page, int x, int y) return NULL; } +static void droptext (struct page *page) +{ + if (page->text) { + fz_free_text_span (state.ctx, page->text); + page->fmark.i = -1; + page->lmark.i = -1; + page->fmark.span = NULL; + page->lmark.span = NULL; + page->text = NULL; + } +} + static void ensuretext (struct page *page) { + if (state.gen != page->gen) { + droptext (page); + page->gen = state.gen; + } if (!page->text) { fz_device *tdev; page->text = fz_new_text_span (state.ctx); tdev = fz_new_text_device (state.ctx, page->text); - pdf_run_page (state.xref, page->drawpage, tdev, page->pagedim.ctm); + fz_execute_display_list (page->dlist, + tdev, + pagectm (page), + fz_infinite_bbox, NULL); fz_free_device (tdev); } } @@ -1461,9 +1666,11 @@ CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v) { CAMLparam3 (ptr_v, x_v, y_v); CAMLlocal3 (ret_v, tup_v, str_v); - pdf_link *link; + fz_link *link; struct page *page; char *s = String_val (ptr_v); + int x = Int_val (x_v), y = Int_val (y_v); + struct pagedim *pdim; ret_v = Val_int (0); if (trylock ("ml_whatsunder")) { @@ -1471,42 +1678,24 @@ CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v) } page = parse_pointer ("ml_whatsunder", s); - link = getlink (page, Int_val (x_v), Int_val (y_v)); + pdim = &state.pagedims[page->pdimno]; + x += pdim->bounds.x0; + y += pdim->bounds.y0; + link = getlink (page, x, y); if (link) { - switch (link->kind) { - case PDF_LINK_GOTO: + switch (link->dest.kind) { + case FZ_LINK_GOTO: { int pageno; fz_point p; - fz_obj *obj; - pageno = -1; + pageno = link->dest.ld.gotor.page; p.x = 0; p.y = 0; - if (fz_is_array (link->dest)) { - obj = fz_array_get (link->dest, 0); - if (fz_is_indirect (obj)) { - pageno = pdf_find_page_number (state.xref, obj); - } - else if (fz_is_int (obj)) { - pageno = fz_to_int (obj); - } - - if (fz_array_len (link->dest) > 3) { - fz_obj *xo, *yo; - - xo = fz_array_get (link->dest, 2); - yo = fz_array_get (link->dest, 3); - if (!fz_is_null (xo) && !fz_is_null (yo)) { - p.x = fz_to_int (xo); - p.y = fz_to_int (yo); - p = fz_transform_point (page->pagedim.ctm, p); - } - } - } - else { - pageno = pdf_find_page_number (state.xref, link->dest); + if (link->dest.ld.gotor.flags & fz_link_flag_t_valid) { + p.y = link->dest.ld.gotor.lt.y; + p = fz_transform_point (pdim->lctm, p); } tup_v = caml_alloc_tuple (2); ret_v = caml_alloc_small (1, 1); @@ -1516,25 +1705,22 @@ CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v) } break; - case PDF_LINK_URI: - str_v = caml_copy_string (fz_to_str_buf (link->dest)); + case FZ_LINK_URI: + str_v = caml_copy_string (link->dest.ld.uri.uri); ret_v = caml_alloc_small (1, 0); Field (ret_v, 0) = str_v; break; default: - printd (state.sock, "T unhandled link kind %d", link->kind); + printd (state.sock, "msg unhandled link kind %d", link->dest.kind); break; } } else { - int i, x, y; + int i; fz_text_span *span; ensuretext (page); - x = Int_val (x_v) + page->pixmap->x; - y = Int_val (y_v) + page->pixmap->y; - for (span = page->text; span; span = span->next) { for (i = 0; i < span->len; ++i) { fz_bbox *b; @@ -1580,14 +1766,15 @@ CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v) CAMLreturn (ret_v); } -CAMLprim value ml_seltext (value ptr_v, value rect_v, value oy_v) +CAMLprim value ml_seltext (value ptr_v, value rect_v) { - CAMLparam4 (ptr_v, rect_v, oy_v, rect_v); + CAMLparam2 (ptr_v, rect_v); fz_bbox *b; struct page *page; fz_text_span *span; struct mark first, last; - int i, x0, x1, y0, y1, oy, left; + int i, x0, x1, y0, y1; + struct pagedim *pdim; char *s = String_val (ptr_v); if (trylock ("ml_seltext")) { @@ -1597,13 +1784,13 @@ CAMLprim value ml_seltext (value ptr_v, value rect_v, value oy_v) page = parse_pointer ("ml_seltext", s); ensuretext (page); - oy = Int_val (oy_v); - x0 = Int_val (Field (rect_v, 0)); - y0 = Int_val (Field (rect_v, 1)); - x1 = Int_val (Field (rect_v, 2)); - y1 = Int_val (Field (rect_v, 3)); + pdim = &state.pagedims[page->pdimno]; + + x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0; + y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0; + x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0; + y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0; - left = page->pagedim.left; if (0) { glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); glColor3ub (128, 128, 128); @@ -1611,11 +1798,6 @@ CAMLprim value ml_seltext (value ptr_v, value rect_v, value oy_v) glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); } - x0 += page->pixmap->x - left; - y0 += page->pixmap->y - oy; - x1 += page->pixmap->x - left; - y1 += page->pixmap->y - oy; - first.span = NULL; last.span = NULL; @@ -1639,7 +1821,7 @@ CAMLprim value ml_seltext (value ptr_v, value rect_v, value oy_v) if (0 && selected) { glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); glColor3ub (128, 128, 128); - glRecti (b->x0+left, b->y0, b->x1+left, b->y1); + glRecti (b->x0, b->y0, b->x1, b->y1); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); } } @@ -1692,7 +1874,7 @@ static int pipespan (FILE *f, fz_text_span *span, int a, int b) ret = fwrite (buf, len, 1, f); if (ret != 1) { - printd (state.sock, "T failed to write %d bytes ret=%d: %s", + printd (state.sock, "msg failed to write %d bytes ret=%d: %s", len, ret, strerror (errno)); return -1; } @@ -1717,7 +1899,7 @@ CAMLprim value ml_copysel (value ptr_v) if (state.xselpipe) { int ret = pclose (state.xselpipe); if (ret) { - printd (state.sock, "T failed to close xsel pipe `%s'", + printd (state.sock, "msg failed to close xsel pipe `%s'", strerror (errno)); } state.xselpipe = NULL; @@ -1732,7 +1914,7 @@ CAMLprim value ml_copysel (value ptr_v) page = parse_pointer ("ml_sopysel", s); if (!page->fmark.span || !page->lmark.span) { - printd (state.sock, "T nothing to copy"); + printd (state.sock, "msg nothing to copy"); goto unlock; } @@ -1741,7 +1923,7 @@ CAMLprim value ml_copysel (value ptr_v) if (!state.xselpipe) { state.xselpipe = popen ("xsel -i", "w"); if (!state.xselpipe) { - printd (state.sock, "T failed to open xsel pipe `%s'", + printd (state.sock, "msg failed to open xsel pipe `%s'", strerror (errno)); } else { @@ -1763,7 +1945,8 @@ CAMLprim value ml_copysel (value ptr_v) } if (span->eol) { if (putc ('\n', f) == EOF) { - printd (state.sock, "T failed break line on xsel pipe `%s'", + printd (state.sock, + "msg failed break line on xsel pipe `%s'", strerror (errno)); goto close; } @@ -1792,7 +1975,7 @@ CAMLprim value ml_getpdimrect (value pagedimno_v) box = fz_empty_rect; } else { - box = state.pagedims[pagedimno].box; + box = state.pagedims[pagedimno].mediabox; unlock ("ml_getpdimrect"); } @@ -1813,8 +1996,8 @@ static double getmaxw (void) for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) { double x0, x1, w; - x0 = MIN (p->box.x0, p->box.x1); - x1 = MAX (p->box.x0, p->box.x1); + x0 = MIN (p->mediabox.x0, p->mediabox.x1); + x1 = MAX (p->mediabox.x0, p->mediabox.x1); w = x1 - x0; maxw = MAX (w, maxw); @@ -1862,10 +2045,10 @@ CAMLprim value ml_zoom_for_height (value winw_v, value winh_v, value dw_v) for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) { double x0, x1, y0, y1, w, h, scaledh, scale; - x0 = MIN (p->box.x0, p->box.x1); - y0 = MIN (p->box.y0, p->box.y1); - x1 = MAX (p->box.x0, p->box.x1); - y1 = MAX (p->box.y0, p->box.y1); + x0 = MIN (p->mediabox.x0, p->mediabox.x1); + x1 = MAX (p->mediabox.x0, p->mediabox.x1); + y0 = MIN (p->mediabox.y0, p->mediabox.y1); + y1 = MAX (p->mediabox.y0, p->mediabox.y1); w = x1 - x0; h = y1 - y0; @@ -1924,23 +2107,136 @@ CAMLprim value ml_measure_string (value pt_v, value string_v) CAMLreturn (ret_v); } +CAMLprim value ml_getpagebox (value opaque_v) +{ + CAMLparam1 (opaque_v); + CAMLlocal1 (ret_v); + fz_bbox bbox; + fz_device *dev; + char *s = String_val (opaque_v); + struct page *page = parse_pointer ("ml_getpagebox", s); + + ret_v = caml_alloc_tuple (4); + dev = fz_new_bbox_device (state.ctx, &bbox); + dev->hints |= FZ_IGNORE_SHADE; + pdf_run_page (state.xref, page->drawpage, dev, pagectm (page), NULL); + fz_free_device (dev); + + Field (ret_v, 0) = Val_int (bbox.x0); + Field (ret_v, 1) = Val_int (bbox.y0); + Field (ret_v, 2) = Val_int (bbox.x1); + Field (ret_v, 3) = Val_int (bbox.y1); + + CAMLreturn (ret_v); +} + +CAMLprim value ml_setaalevel (value level_v) +{ + CAMLparam1 (level_v); + + state.aalevel = Int_val (level_v); + CAMLreturn (Val_unit); +} + +#if !defined _WIN32 && !defined __APPLE__ +#undef pixel +#include +#include +#include +#include + +static void set_wm_class (int hack) +{ + if (hack) { + Display *dpy; + Window win; + XClassHint hint; + char *display; + + display = getenv ("DISPLAY"); + dpy = XOpenDisplay (display); + if (!dpy) { + fprintf (stderr, "XOpenDisplay `%s' failed\n", + display ? display : "null"); + return; + } + hint.res_name = "llpp"; + hint.res_class = "llpp"; + win = glXGetCurrentDrawable (); + if (win == None) { + fprintf (stderr, "glXGetCurrentDrawable returned None\n"); + XCloseDisplay (dpy); + return; + } + XSetClassHint (dpy, win, &hint); + XCloseDisplay (dpy); + } +} +#else +#define set_wm_class(h) (void) (h) +#endif + +enum { piunknown, pilinux, piwindows, piosx, + pisun, pifreebsd, pidragonflybsd, + piopenbsd, pimingw, picygwin }; + +CAMLprim value ml_platform (value unit_v) +{ + CAMLparam1 (unit_v); + int platid = piunknown; + +#if defined __linux__ + platid = pilinux; +#elif defined __CYGWIN__ + platid = picygwin; +#elif defined __MINGW32__ + platid = pimingw; +#elif defined _WIN32 + platid = piwindows; +#elif defined __DragonFly__ + platid = pidragonflybsd; +#elif defined __FreeBSD__ + platid = pifreebsd; +#elif defined __OpenBSD__ + platid = piopenbsd; +#elif defined __sun__ + platid = pisun; +#elif defined __APPLE__ + platid = piosx; +#endif + CAMLreturn (Val_int (platid)); +} + CAMLprim value ml_init (value sock_v, value params_v) { CAMLparam2 (sock_v, params_v); + CAMLlocal2 (trim_v, fuzz_v); #ifndef _WIN32 int ret; #endif char *fontpath; + int wmclasshack; + int colorspace; - state.ctx = fz_new_context (&fz_alloc_default, 256<<20); state.rotate = Int_val (Field (params_v, 0)); state.proportional = Bool_val (Field (params_v, 1)); - state.texcount = Int_val (Field (params_v, 2)); - state.sliceheight = Int_val (Field (params_v, 3)); - state.blockwidth = Int_val (Field (params_v, 4)); - fontpath = String_val (Field (params_v, 5)); - state.texform = GL_RGBA; - state.texty = GL_UNSIGNED_BYTE; + trim_v = Field (params_v, 2); + state.texcount = Int_val (Field (params_v, 3)); + state.sliceheight = Int_val (Field (params_v, 4)); + state.ctx = fz_new_context (&fz_alloc_default, Field (params_v, 5)); + colorspace = Int_val (Field (params_v, 6)); + wmclasshack = Bool_val (Field (params_v, 7)); + fontpath = String_val (Field (params_v, 8)); + + state.trimmargins = Bool_val (Field (trim_v, 0)); + fuzz_v = Field (trim_v, 1); + state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0)); + state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1)); + state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2)); + state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3)); + + set_tex_params (colorspace); + set_wm_class (wmclasshack); if (*fontpath) { state.face = load_font (fontpath); diff --git a/main.ml b/main.ml index c6f5a30..ddd9439 100644 --- a/main.ml +++ b/main.ml @@ -9,30 +9,38 @@ let dolog fmt = Printf.kprintf prerr_endline fmt;; exception Quit;; -type params = - angle * proportional * texcount * sliceheight * blockwidth * fontpath -and pageno = int -and width = int -and height = int -and leftx = int -and opaque = string -and recttype = int -and pixmapsize = int -and angle = int -and proportional = bool +type params = (angle * proportional * trimparams + * texcount * sliceheight * memsize + * colorspace * wmclasshack * fontpath) +and pageno = int +and width = int +and height = int +and leftx = int +and opaque = string +and recttype = int +and pixmapsize = int +and angle = int +and proportional = bool +and trimmargins = bool and interpagespace = int -and texcount = int -and sliceheight = int -and blockwidth = int -and gen = int -and top = float -and fontpath = string +and texcount = int +and sliceheight = int +and gen = int +and top = float +and fontpath = string +and memsize = int +and aalevel = int +and wmclasshack = bool +and irect = (int * int * int * int) +and trimparams = (trimmargins * irect) +and colorspace = | Rgb | Bgr | Gray ;; +type platform = | Punknown | Plinux | Pwindows | Posx | Psun + | Pfreebsd | Pdragonflybsd | Popenbsd | Pmingw | Pcygwin;; + external init : Unix.file_descr -> params -> unit = "ml_init";; -external draw : (int * int * int * bool) -> string -> unit = "ml_draw";; -external seltext : string -> (int * int * int * int) -> int -> unit = - "ml_seltext";; +external seltext : string -> (int * int * int * int) -> unit = "ml_seltext";; external copysel : string -> unit = "ml_copysel";; external getpdimrect : int -> float array = "ml_getpdimrect";; external whatsunder : string -> int -> int -> under = "ml_whatsunder";; @@ -40,13 +48,48 @@ external zoomforh : int -> int -> int -> float = "ml_zoom_for_height";; external drawstr : int -> int -> int -> string -> float = "ml_draw_string";; external measurestr : int -> string -> float = "ml_measure_string";; external getmaxw : unit -> float = "ml_getmaxw";; +external postprocess : opaque -> bool -> int -> int -> unit = "ml_postprocess";; +external pagebbox : opaque -> (int * int * int * int) = "ml_getpagebox";; +external platform : unit -> platform = "ml_platform";; +external setaalevel : int -> unit = "ml_setaalevel";; + +let platform_to_string = function + | Punknown -> "unknown" + | Plinux -> "Linux" + | Pwindows -> "Windows" + | Posx -> "OSX" + | Psun -> "Sun" + | Pfreebsd -> "FreeBSD" + | Pdragonflybsd -> "DragonflyBSD" + | Popenbsd -> "OpenBSD" + | Pcygwin -> "Cygwin" + | Pmingw -> "MingW" +;; + +let platform = platform ();; + +let is_windows = + match platform with + | Pwindows | Pmingw -> true + | _ -> false +;; + +type x = int +and y = int +and tilex = int +and tiley = int +and tileparams = (x * y * width * height * tilex * tiley) +;; + +external drawtile : tileparams -> string -> unit = "ml_drawtile";; type mpos = int * int and mstate = | Msel of (mpos * mpos) | Mpan of mpos - | Mscroll + | Mscrolly | Mscrollx | Mzoom of (int * int) + | Mzoomrect of (mpos * mpos) | Mnone ;; @@ -71,6 +114,10 @@ type 'a circbuf = } ;; +let bound v minv maxv = + max minv (min maxv v); +;; + let cbnew n v = { store = Array.create n v ; rc = 0 @@ -79,6 +126,22 @@ let cbnew n v = } ;; +let drawstring size x y s = + Gl.enable `blend; + Gl.enable `texture_2d; + ignore (drawstr size x y s); + Gl.disable `blend; + Gl.disable `texture_2d; +;; + +let drawstring1 size x y s = + drawstr size x y s; +;; + +let drawstring2 size x y fmt = + Printf.kprintf (drawstring size (x+1) (y+size+1)) fmt +;; + let cbcap b = Array.length b.store;; let cbput b v = @@ -116,120 +179,182 @@ let cbgetg b circular dir = let cbget b = cbgetg b false;; let cbgetc b = cbgetg b true;; -type layout = - { pageno : int +type page = + { pageno : int ; pagedimno : int - ; pagew : int - ; pageh : int + ; pagew : int + ; pageh : int + ; pagex : int + ; pagey : int + ; pagevw : int + ; pagevh : int + ; pagedispx : int ; pagedispy : int - ; pagey : int - ; pagevh : int - ; pagex : int } ;; +let debugl l = + dolog "l %d dim=%d {" l.pageno l.pagedimno; + dolog " WxH %dx%d" l.pagew l.pageh; + dolog " vWxH %dx%d" l.pagevw l.pagevh; + dolog " pagex,y %d,%d" l.pagex l.pagey; + dolog " dispx,y %d,%d" l.pagedispx l.pagedispy; + dolog "}"; +;; + +let debugrect (x0, y0, x1, y1, x2, y2, x3, y3) = + dolog "rect {"; + dolog " x0,y0=(% f, % f)" x0 y0; + dolog " x1,y1=(% f, % f)" x1 y1; + dolog " x2,y2=(% f, % f)" x2 y2; + dolog " x3,y3=(% f, % f)" x3 y3; + dolog "}"; +;; + type conf = - { mutable scrollbw : int - ; mutable scrollh : int - ; mutable icase : bool - ; mutable preload : bool - ; mutable pagebias : int - ; mutable verbose : bool - ; mutable scrollstep : int - ; mutable maxhfit : bool - ; mutable crophack : bool + { mutable scrollbw : int + ; mutable scrollh : int + ; mutable icase : bool + ; mutable preload : bool + ; mutable pagebias : int + ; mutable verbose : bool + ; mutable debug : bool + ; mutable scrollstep : int + ; mutable maxhfit : bool + ; mutable crophack : bool ; mutable autoscrollstep : int - ; mutable showall : bool - ; mutable hlinks : bool - ; mutable underinfo : bool + ; mutable showall : bool + ; mutable hlinks : bool + ; mutable underinfo : bool ; mutable interpagespace : interpagespace - ; mutable zoom : float - ; mutable presentation : bool - ; mutable angle : angle - ; mutable winw : int - ; mutable winh : int - ; mutable savebmarks : bool - ; mutable proportional : proportional - ; mutable memlimit : int - ; mutable texcount : texcount - ; mutable sliceheight : sliceheight - ; mutable blockwidth : blockwidth - ; mutable thumbw : width - ; mutable jumpback : bool - ; mutable bgcolor : float * float * float - ; mutable bedefault : bool - ; mutable scrollbarinpm : bool + ; mutable zoom : float + ; mutable presentation : bool + ; mutable angle : angle + ; mutable winw : int + ; mutable winh : int + ; mutable savebmarks : bool + ; mutable proportional : proportional + ; mutable trimmargins : trimmargins + ; mutable trimfuzz : irect + ; mutable memlimit : memsize + ; mutable texcount : texcount + ; mutable sliceheight : sliceheight + ; mutable thumbw : width + ; mutable jumpback : bool + ; mutable bgcolor : float * float * float + ; mutable bedefault : bool + ; mutable scrollbarinpm : bool + ; mutable tilew : int + ; mutable tileh : int + ; mutable mumemlimit : memsize + ; mutable checkers : bool + ; mutable aalevel : int + ; mutable urilauncher : string + ; mutable colorspace : colorspace + ; mutable invert : bool } ;; type anchor = pageno * top;; -type outline = string * int * anchor -and outlines = - | Oarray of outline array - | Olist of outline list - | Onarrow of string * outline array * outline array -;; +type outline = string * int * anchor;; type rect = float * float * float * float * float * float * float * float;; -type pagemapkey = pageno * width * angle * proportional * gen;; +type tile = opaque * pixmapsize * elapsed +and elapsed = float;; +type pagemapkey = pageno * gen;; +type tilemapkey = pageno * gen * colorspace * angle * width * height * col * row +and row = int +and col = int;; let emptyanchor = (0, 0.0);; +class type uioh = object + method display : unit + method key : int -> uioh + method special : Glut.special_key_t -> uioh + method button : + Glut.button_t -> Glut.mouse_button_state_t -> int -> int -> uioh + method motion : int -> int -> uioh + method pmotion : int -> int -> uioh +end;; + type mode = | Birdseye of (conf * leftx * pageno * pageno * anchor) - | Outline of (bool * int * int * outline array * string * int * mode) - | Items of (int * int * item array * string * int * mode) | Textentry of (textentry * onleave) | View and onleave = leavetextentrystatus -> unit and leavetextentrystatus = | Cancel | Confirm -and item = string * int * action +and helpitem = string * int * action and action = | Noaction - | Action of (int -> int -> string -> int -> mode) + | Action of (uioh -> uioh) ;; let isbirdseye = function Birdseye _ -> true | _ -> false;; let istextentry = function Textentry _ -> true | _ -> false;; +type currently = + | Idle + | Loading of (page * gen) + | Tiling of ( + page * opaque * colorspace * angle * gen * col * row * width * height + ) + | Outlining of outline list +;; + +let nouioh : uioh = object (self) + method display = () + method key _ = self + method special _ = self + method button _ _ _ _ = self + method motion _ _ = self + method pmotion _ _ = self +end;; + type state = - { mutable csock : Unix.file_descr - ; mutable ssock : Unix.file_descr - ; mutable w : int - ; mutable x : int - ; mutable y : int - ; mutable scrollw : int - ; mutable anchor : anchor - ; mutable maxy : int - ; mutable layout : layout list - ; pagemap : (pagemapkey, (opaque * pixmapsize)) Hashtbl.t - ; pagelru : opaque Queue.t - ; mutable pdims : (pageno * width * height * leftx) list - ; mutable pagecount : int - ; mutable rendering : bool - ; mutable mstate : mstate + { mutable csock : Unix.file_descr + ; mutable ssock : Unix.file_descr + ; mutable w : int + ; mutable x : int + ; mutable y : int + ; mutable scrollw : int + ; mutable hscrollh : int + ; mutable anchor : anchor + ; mutable maxy : int + ; mutable layout : page list + ; pagemap : (pagemapkey, opaque) Hashtbl.t + ; tilemap : (tilemapkey, tile) Hashtbl.t + ; tilelru : (tilemapkey * opaque * pixmapsize) Queue.t + ; mutable pdims : (pageno * width * height * leftx) list + ; mutable pagecount : int + ; mutable currently : currently + ; mutable mstate : mstate ; mutable searchpattern : string - ; mutable rects : (pageno * recttype * rect) list - ; mutable rects1 : (pageno * recttype * rect) list - ; mutable text : string - ; mutable fullscreen : (width * height) option - ; mutable mode : mode - ; mutable outlines : outlines - ; mutable bookmarks : outline list - ; mutable path : string - ; mutable password : string - ; mutable invalidated : int - ; mutable colorscale : float - ; mutable memused : int - ; mutable gen : gen - ; mutable throttle : layout list option - ; mutable autoscroll :int option - ; mutable help : item array - ; mutable docinfo : (int * string) list - ; mutable deadline : float - ; hists : hists + ; mutable rects : (pageno * recttype * rect) list + ; mutable rects1 : (pageno * recttype * rect) list + ; mutable text : string + ; mutable fullscreen : (width * height) option + ; mutable mode : mode + ; mutable uioh : uioh + ; mutable outlines : outline array + ; mutable bookmarks : outline list + ; mutable path : string + ; mutable password : string + ; mutable invalidated : int + ; mutable colorscale : float + ; mutable memused : memsize + ; mutable gen : gen + ; mutable throttle : (page list * int) option + ; mutable autoscroll : int option + ; mutable help : helpitem array + ; mutable docinfo : (int * string) list + ; mutable deadline : float + ; mutable texid : GlTex.texture_id option + ; hists : hists + ; mutable prevzoom : float + ; mutable progress : float } and hists = { pat : string circbuf @@ -239,86 +364,136 @@ and hists = ;; let defconf = - { scrollbw = 7 - ; scrollh = 12 - ; icase = true - ; preload = true - ; pagebias = 0 - ; verbose = false - ; scrollstep = 24 - ; maxhfit = true - ; crophack = false + { scrollbw = 7 + ; scrollh = 12 + ; icase = true + ; preload = true + ; pagebias = 0 + ; verbose = false + ; debug = false + ; scrollstep = 24 + ; maxhfit = true + ; crophack = false ; autoscrollstep = 2 - ; showall = false - ; hlinks = false - ; underinfo = false + ; showall = false + ; hlinks = false + ; underinfo = false ; interpagespace = 2 - ; zoom = 1.0 - ; presentation = false - ; angle = 0 - ; winw = 900 - ; winh = 900 - ; savebmarks = true - ; proportional = true - ; memlimit = 32*1024*1024 - ; texcount = 256 - ; sliceheight = 24 - ; blockwidth = 2048 - ; thumbw = 76 - ; jumpback = false - ; bgcolor = (0.5, 0.5, 0.5) - ; bedefault = false - ; scrollbarinpm = true + ; zoom = 1.0 + ; presentation = false + ; angle = 0 + ; winw = 900 + ; winh = 900 + ; savebmarks = true + ; proportional = true + ; trimmargins = false + ; trimfuzz = (0,0,0,0) + ; memlimit = 32 lsl 20 + ; texcount = 256 + ; sliceheight = 24 + ; thumbw = 76 + ; jumpback = true + ; bgcolor = (0.5, 0.5, 0.5) + ; bedefault = false + ; scrollbarinpm = true + ; tilew = 2048 + ; tileh = 2048 + ; mumemlimit = 128 lsl 20 + ; checkers = true + ; aalevel = 8 + ; urilauncher = + (match platform with + | Plinux | Pfreebsd | Pdragonflybsd | Popenbsd | Psun -> "xdg-open \"%s\"" + | Posx -> "open \"%s\"" + | Pwindows | Pcygwin | Pmingw -> "iexplore \"%s\"" + | _ -> "") + ; colorspace = Rgb + ; invert = false } ;; let conf = { defconf with angle = defconf.angle };; +let uifontsize = ref 14;; + +let gotouri uri = + if String.length conf.urilauncher = 0 + then print_endline uri + else + let re = Str.regexp "%s" in + let command = Str.global_replace re uri conf.urilauncher in + let optic = + try Some (Unix.open_process_in command) + with exn -> + Printf.eprintf + "failed to execute `%s': %s\n" command (Printexc.to_string exn); + flush stderr; + None + in + match optic with + | Some ic -> close_in ic + | None -> () +;; + let makehelp () = let strings = ("llpp version " ^ Help.version) :: "" :: Help.keys in - Array.of_list (List.map (fun s -> s, 0, Noaction) strings); + Array.of_list ( + let r = Str.regexp "\\(http://[^ ]+\\)" in + List.map (fun s -> + if (try Str.search_forward r s 0 with Not_found -> -1) >= 0 + then + let uri = Str.matched_string s in + (s, 0, Action (fun u -> gotouri uri; u)) + else s, 0, Noaction) strings + ); ;; let state = - { csock = Unix.stdin - ; ssock = Unix.stdin - ; x = 0 - ; y = 0 - ; w = 0 - ; scrollw = 0 - ; anchor = emptyanchor - ; layout = [] - ; maxy = max_int - ; pagelru = Queue.create () - ; pagemap = Hashtbl.create 10 - ; pdims = [] - ; pagecount = 0 - ; rendering = false - ; mstate = Mnone - ; rects = [] - ; rects1 = [] - ; text = "" - ; mode = View - ; fullscreen = None + { csock = Unix.stdin + ; ssock = Unix.stdin + ; x = 0 + ; y = 0 + ; w = 0 + ; scrollw = 0 + ; hscrollh = 0 + ; anchor = emptyanchor + ; layout = [] + ; maxy = max_int + ; tilelru = Queue.create () + ; pagemap = Hashtbl.create 10 + ; tilemap = Hashtbl.create 10 + ; pdims = [] + ; pagecount = 0 + ; currently = Idle + ; mstate = Mnone + ; rects = [] + ; rects1 = [] + ; text = "" + ; mode = View + ; fullscreen = None ; searchpattern = "" - ; outlines = Olist [] - ; bookmarks = [] - ; path = "" - ; password = "" - ; invalidated = 0 - ; hists = - { nav = cbnew 100 (0, 0.0) - ; pat = cbnew 20 "" - ; pag = cbnew 10 "" + ; outlines = [||] + ; bookmarks = [] + ; path = "" + ; password = "" + ; invalidated = 0 + ; hists = + { nav = cbnew 10 (0, 0.0) + ; pat = cbnew 1 "" + ; pag = cbnew 1 "" } - ; colorscale = 1.0 - ; memused = 0 - ; gen = 0 - ; throttle = None - ; autoscroll = None - ; help = makehelp () - ; docinfo = [] - ; deadline = nan + ; colorscale = 1.0 + ; memused = 0 + ; gen = 0 + ; throttle = None + ; autoscroll = None + ; help = makehelp () + ; docinfo = [] + ; deadline = nan + ; texid = None + ; prevzoom = 1.0 + ; progress = -1.0 + ; uioh = nouioh } ;; @@ -330,6 +505,64 @@ let vlog fmt = Printf.kprintf ignore fmt ;; +module G = +struct + let postRedisplay who = + vlog "redisplay for %s" who; + Glut.postRedisplay (); + ;; +end;; + +let addchar s c = + let b = Buffer.create (String.length s + 1) in + Buffer.add_string b s; + Buffer.add_char b c; + Buffer.contents b; +;; + +let colorspace_of_string s = + match String.lowercase s with + | "rgb" -> Rgb + | "bgr" -> Bgr + | "gray" -> Gray + | _ -> failwith "invalid colorspace" +;; + +let int_of_colorspace = function + | Rgb -> 0 + | Bgr -> 1 + | Gray -> 2 +;; + +let colorspace_of_int = function + | 0 -> Rgb + | 1 -> Bgr + | 2 -> Gray + | n -> failwith ("invalid colorspace index " ^ string_of_int n) +;; + +let colorspace_to_string = function + | Rgb -> "rgb" + | Bgr -> "bgr" + | Gray -> "gray" +;; + +let intentry_with_suffix text key = + let c = Char.unsafe_chr key in + match Char.lowercase c with + | '0' .. '9' -> + let text = addchar text c in + TEcont text + + | 'k' | 'm' | 'g' -> + let text = addchar text c in + TEcont text + + | _ -> + state.text <- Printf.sprintf "invalid char (%d, `%c')" key c; + TEcont text +;; + let writecmd fd s = let len = String.length s in let n = 4 + len in @@ -449,20 +682,44 @@ let getpageyh pageno = f 0 0 0 0 state.pdims ;; +let getpagedim pageno = + let rec f ppdim l = + match l with + | (n, _, _, _) as pdim :: rest -> + if n >= pageno + then (if n = pageno then pdim else ppdim) + else f pdim rest + + | [] -> ppdim + in + f (-1, -1, -1, -1) state.pdims +;; + +let getpageh pageno = + let _, _, h, _ = getpagedim pageno in + h +;; + +let getpagew pageno = + let _, w, _, _ = getpagedim pageno in + w +;; + let getpagey pageno = fst (getpageyh pageno);; let layout y sh = + let sh = sh - state.hscrollh in let rec f ~pageno ~pdimno ~prev ~py ~dy ~pdims ~accu = - let ((w, h, ips, x) as curr), rest, pdimno, yinc = + let ((w, h, ips, xoff) as curr), rest, pdimno, yinc = match pdims with - | (pageno', w, h, x) :: rest when pageno' = pageno -> + | (pageno', w, h, xoff) :: rest when pageno' = pageno -> let ips = calcips h in let yinc = if conf.presentation || (isbirdseye state.mode && pageno = 0) then ips else 0 in - (w, h, ips, x), rest, pdimno + 1, yinc + (w, h, ips, xoff), rest, pdimno + 1, yinc | _ -> prev, pdims, pdimno, 0 in @@ -490,15 +747,32 @@ let layout y sh = let pagevh = min (sh - dy) pagevh in let off = if yinc > 0 then py - vy else 0 in let py = py + h + ips in + let pagex, dx = + let xoff = xoff + + if state.w < conf.winw - state.scrollw + then (conf.winw - state.scrollw - state.w) / 2 + else 0 + in + let dispx = xoff + state.x in + if dispx < 0 + then (-dispx, 0) + else (0, dispx) + in + let pagevw = + let lw = w - pagex in + min lw (conf.winw - state.scrollw) + in let e = { pageno = pageno ; pagedimno = pdimno ; pagew = w ; pageh = h - ; pagedispy = dy + off + ; pagex = pagex ; pagey = pagey + off + ; pagevw = pagevw ; pagevh = pagevh - off - ; pagex = x + ; pagedispx = dx + ; pagedispy = dy + off } in let accu = e :: accu in @@ -536,114 +810,343 @@ let clamp incr = ;; let getopaque pageno = - try Some (Hashtbl.find state.pagemap - (pageno, state.w, conf.angle, conf.proportional, state.gen)) + try Some (Hashtbl.find state.pagemap (pageno, state.gen)) with Not_found -> None ;; -let cache pageno opaque = - Hashtbl.replace state.pagemap - (pageno, state.w, conf.angle, conf.proportional, state.gen) opaque +let putopaque pageno opaque = + Hashtbl.replace state.pagemap (pageno, state.gen) opaque ;; -let validopaque opaque = String.length opaque > 0;; +let itertiles l f = + let tilex = l.pagex mod conf.tilew in + let tiley = l.pagey mod conf.tileh in -let render l = - match getopaque l.pageno with - | None when not state.rendering -> - state.rendering <- true; - cache l.pageno ("", -1); - wcmd "render" [`i (l.pageno + 1) - ;`i l.pagedimno - ;`i l.pagew - ;`i l.pageh]; - | _ -> () + let col = l.pagex / conf.tilew in + let row = l.pagey / conf.tileh in + + let vw = + let a = l.pagew - l.pagex in + let b = conf.winw - state.scrollw in + min a b + and vh = l.pagevh in + + let rec rowloop row y0 dispy h = + if h = 0 + then () + else ( + let dh = conf.tileh - y0 in + let dh = min h dh in + let rec colloop col x0 dispx w = + if w = 0 + then () + else ( + let dw = conf.tilew - x0 in + let dw = min w dw in + + f col row dispx dispy x0 y0 dw dh; + colloop (col+1) 0 (dispx+dw) (w-dw) + ) + in + colloop col tilex l.pagedispx vw; + rowloop (row+1) 0 (dispy+dh) (h-dh) + ) + in + if vw > 0 && vh > 0 + then rowloop row tiley l.pagedispy vh; ;; -let loadlayout layout = - let rec f all = function - | l :: ls -> - begin match getopaque l.pageno with - | None -> render l; f false ls - | Some (opaque, _) -> f (all && validopaque opaque) ls - end - | [] -> all +let gettileopaque l col row = + let key = + l.pageno, state.gen, conf.colorspace, conf.angle, l.pagew, l.pageh, col, row in - f (layout <> []) layout; + try Some (Hashtbl.find state.tilemap key) + with Not_found -> None ;; -let findpageforopaque opaque = - Hashtbl.fold - (fun k (v, s) a -> if v = opaque then Some (k, s) else a) - state.pagemap None +let puttileopaque l col row gen colorspace angle opaque size elapsed = + let key = l.pageno, gen, colorspace, angle, l.pagew, l.pageh, col, row in + Hashtbl.add state.tilemap key (opaque, size, elapsed) +;; + +let drawtiles l color = + GlDraw.color color; + let f col row x y tilex tiley w h = + match gettileopaque l col row with + | Some (opaque, _, t) -> + let params = x, y, w, h, tilex, tiley in + if conf.invert + then ( + Gl.enable `blend; + GlFunc.blend_func `zero `one_minus_src_color; + ); + drawtile params opaque; + if conf.invert + then Gl.disable `blend; + if conf.debug + then ( + let s = Printf.sprintf + "%d[%d,%d] %f sec" + l.pageno col row t + in + let w = measurestr !uifontsize s in + GlMisc.push_attrib [`current]; + GlDraw.color (0.0, 0.0, 0.0); + GlDraw.rect + (float (x-2), float (y-2)) + (float (x+2) +. w, float (y + !uifontsize + 2)); + GlDraw.color (1.0, 1.0, 1.0); + drawstring !uifontsize x (y + !uifontsize - 1) s; + GlMisc.pop_attrib (); + ); + + | _ -> + let w = + let lw = conf.winw - state.scrollw - x in + min lw w + and h = + let lh = conf.winh - y in + min lh h + in + Gl.enable `texture_2d; + begin match state.texid with + | Some id -> + GlTex.bind_texture `texture_2d id; + let x0 = float x + and y0 = float y + and x1 = float (x+w) + and y1 = float (y+h) in + + let tw = float w /. 64.0 + and th = float h /. 64.0 in + let tx0 = float tilex /. 64.0 + and ty0 = float tiley /. 64.0 in + let tx1 = tx0 +. tw + and ty1 = ty0 +. th in + GlDraw.begins `quads; + GlTex.coord2 (tx0, ty0); GlDraw.vertex2 (x0, y0); + GlTex.coord2 (tx0, ty1); GlDraw.vertex2 (x0, y1); + GlTex.coord2 (tx1, ty1); GlDraw.vertex2 (x1, y1); + GlTex.coord2 (tx1, ty0); GlDraw.vertex2 (x1, y0); + GlDraw.ends (); + + Gl.disable `texture_2d; + | None -> + GlDraw.color (1.0, 1.0, 1.0); + GlDraw.rect + (float x, float y) + (float (x+w), float (y+h)); + end; + if w > 128 && h > !uifontsize + 10 + then ( + GlDraw.color (0.0, 0.0, 0.0); + let c, r = + if conf.verbose + then (col*conf.tilew, row*conf.tileh) + else col, row + in + drawstring2 !uifontsize x y "Loading %d [%d,%d]" l.pageno c r; + ); + GlDraw.color color; + in + itertiles l f ;; let pagevisible layout n = List.exists (fun l -> l.pageno = n) layout;; -let preload () = - let oktopreload = - if conf.preload && not state.rendering - then conf.memlimit > state.memused - else false +let tilevisible1 l x y = + let ax0 = l.pagex + and ax1 = l.pagex + l.pagevw + and ay0 = l.pagey + and ay1 = l.pagey + l.pagevh in + + let bx0 = x + and by0 = y in + let bx1 = min (bx0 + conf.tilew) l.pagew + and by1 = min (by0 + conf.tileh) l.pageh in + + let rx0 = max ax0 bx0 + and ry0 = max ay0 by0 + and rx1 = min ax1 bx1 + and ry1 = min ay1 by1 in + + let nonemptyintersection = rx1 > rx0 && ry1 > ry0 in + nonemptyintersection +;; + +let tilevisible layout n x y = + let rec findpageinlayout = function + | l :: _ when l.pageno = n -> tilevisible1 l x y + | _ :: rest -> findpageinlayout rest + | [] -> false in - if oktopreload - then - let presentation = conf.presentation in - let interpagespace = conf.interpagespace in - let maxy = state.maxy in - conf.presentation <- false; - conf.interpagespace <- 0; - state.maxy <- calcheight (); - let y = - match state.layout with - | [] -> 0 - | l :: _ -> getpagey l.pageno + l.pagey - in - let y = if y < conf.winh then 0 else y - conf.winh in - let h = state.y - y + conf.winh*3 in - let pages = layout y h in - List.iter render pages; - conf.presentation <- presentation; - conf.interpagespace <- interpagespace; - state.maxy <- maxy; + findpageinlayout layout +;; + +let tileready l x y = + tilevisible1 l x y && + gettileopaque l (x/conf.tilew) (y/conf.tileh) != None +;; + +let tilepage n p layout = + let rec loop = function + | l :: rest -> + if l.pageno = n + then + let f col row _ _ _ _ _ _ = + if state.currently = Idle + then + match gettileopaque l col row with + | Some _ -> () + | None -> + let x = col*conf.tilew + and y = row*conf.tileh in + let w = + let w = l.pagew - x in + min w conf.tilew + in + let h = + let h = l.pageh - y in + min h conf.tileh + in + wcmd "tile" + [`s p + ;`i x + ;`i y + ;`i w + ;`i h + ]; + state.currently <- + Tiling ( + l, p, conf.colorspace, conf.angle, state.gen, col, row, + conf.tilew, conf.tileh + ); + in + itertiles l f; + else + loop rest + + | [] -> () + in + if state.invalidated = 0 then loop layout; +;; + +let preloadlayout visiblepages = + let presentation = conf.presentation in + let interpagespace = conf.interpagespace in + let maxy = state.maxy in + conf.presentation <- false; + conf.interpagespace <- 0; + state.maxy <- calcheight (); + let y = + match visiblepages with + | [] -> 0 + | l :: _ -> getpagey l.pageno + l.pagey + in + let y = if y < conf.winh then 0 else y - conf.winh in + let h = state.y - y + conf.winh*3 in + let pages = layout y h in + conf.presentation <- presentation; + conf.interpagespace <- interpagespace; + state.maxy <- maxy; + pages +;; + +let load pages = + let rec loop pages = + if state.currently != Idle + then () + else + match pages with + | l :: rest -> + begin match getopaque l.pageno with + | None -> + wcmd "page" [`i l.pageno; `i l.pagedimno]; + state.currently <- Loading (l, state.gen); + | Some opaque -> + tilepage l.pageno opaque pages; + loop rest + end; + | _ -> () + in + if state.invalidated = 0 then loop pages +;; + +let preload pages = + load pages; + if conf.preload && state.currently = Idle + then load (preloadlayout pages); +;; + +let layoutready layout = + let rec fold all ls = + all && match ls with + | l :: rest -> + let seen = ref false in + let allvisible = ref true in + let foo col row _ _ _ _ _ _ = + seen := true; + allvisible := !allvisible && + begin match gettileopaque l col row with + | Some _ -> true + | None -> false + end + in + itertiles l foo; + fold (!seen && !allvisible) rest + | [] -> true + in + let alltilesvisible = fold true layout in + alltilesvisible; ;; let gotoy y = - let y = max 0 y in - let y = min state.maxy y in - let pages = layout y conf.winh in - let ready = loadlayout pages in - if conf.showall + let y = bound y 0 state.maxy in + let y, layout, proceed = + if conf.showall + then + match state.throttle with + | None -> + let layout = layout y conf.winh in + let ready = layoutready layout in + if not ready + then ( + load layout; + state.throttle <- Some (layout, y); + ) + else G.postRedisplay "gotoy showall (None)"; + y, layout, ready + | Some _ -> -1, [], false + else + let layout = layout y conf.winh in + if true || layoutready layout + then G.postRedisplay "gotoy ready"; + y, layout, true + in + if proceed then ( - if ready - then ( - state.y <- y; - state.layout <- pages; - state.throttle <- None; - Glut.postRedisplay (); - ) - else ( - state.throttle <- Some pages; - ) - ) - else ( state.y <- y; - state.layout <- pages; - state.throttle <- None; - Glut.postRedisplay (); + state.layout <- layout; + begin match state.mode with + | Birdseye (conf, leftx, pageno, hooverpageno, anchor) -> + if not (pagevisible layout pageno) + then ( + match state.layout with + | [] -> () + | l :: _ -> + state.mode <- Birdseye ( + conf, leftx, l.pageno, hooverpageno, anchor + ) + ); + | _ -> () + end; + preload layout; ); - begin match state.mode with - | Birdseye (conf, leftx, pageno, hooverpageno, anchor) -> - if not (pagevisible pages pageno) - then ( - match state.layout with - | [] -> () - | l :: _ -> - state.mode <- Birdseye (conf, leftx, l.pageno, hooverpageno, anchor) - ); - | _ -> () - end; - preload (); +;; + +let conttiling pageno opaque = + tilepage pageno opaque + (if conf.preload then preloadlayout state.layout else state.layout) ;; let gotoy_and_clear_text y = @@ -693,6 +1196,23 @@ let invalidate () = state.invalidated <- state.invalidated + 1; ;; +let writeopen path password = + writecmd state.csock ("open " ^ path ^ "\000" ^ password ^ "\000"); +;; + +let opendoc path password = + invalidate (); + state.path <- path; + state.password <- password; + state.gen <- state.gen + 1; + state.docinfo <- []; + + setaalevel conf.aalevel; + writeopen path password; + Glut.setWindowTitle ("llpp " ^ Filename.basename path); + wcmd "geometry" [`i state.w; `i conf.winh]; +;; + let scalecolor c = let c = c *. state.colorscale in (c, c, c); @@ -704,6 +1224,11 @@ let scalecolor2 (r, g, b) = let represent () = state.maxy <- calcheight (); + state.hscrollh <- + if state.w <= conf.winw - state.scrollw + then 0 + else state.scrollw + ; match state.mode with | Birdseye (_, _, pageno, _, _) -> let y, h = getpageyh pageno in @@ -712,69 +1237,63 @@ let represent () = | _ -> gotoanchor state.anchor ;; -let pagematrix () = - GlMat.mode `projection; - GlMat.load_identity (); - GlMat.rotate ~x:1.0 ~angle:180.0 (); - GlMat.translate ~x:~-.1.0 ~y:~-.1.0 (); - GlMat.scale3 (2.0 /. float state.w, 2.0 /. float conf.winh, 1.0); - if state.x != 0 - then ( - GlMat.translate ~x:(float state.x) (); - ); -;; - -let winmatrix () = - GlMat.mode `projection; - GlMat.load_identity (); - GlMat.rotate ~x:1.0 ~angle:180.0 (); - GlMat.translate ~x:~-.1.0 ~y:~-.1.0 (); - GlMat.scale3 (2.0 /. float conf.winw, 2.0 /. float conf.winh, 1.0); -;; - let reshape = let firsttime = ref true in - fun ~w ~h -> - if state.invalidated = 0 && not !firsttime - then state.anchor <- getanchor (); - - firsttime := false; - conf.winw <- w; - let w = truncate (float w *. conf.zoom) - state.scrollw in - let w = max w 2 in - state.w <- w; - conf.winh <- h; - GlMat.mode `modelview; - GlMat.load_identity (); - GlClear.color (scalecolor 1.0); - GlClear.clear [`color]; - - invalidate (); - wcmd "geometry" [`i w; `i h]; -;; - -let drawstring size x y s = - Gl.enable `blend; - Gl.enable `texture_2d; - ignore (drawstr size x y s); - Gl.disable `blend; - Gl.disable `texture_2d; -;; - -let drawstring1 size x y s = - drawstr size x y s; + fun ~w ~h -> + GlDraw.viewport 0 0 w h; + if state.invalidated = 0 && not !firsttime + then state.anchor <- getanchor (); + + firsttime := false; + conf.winw <- w; + let w = truncate (float w *. conf.zoom) - state.scrollw in + let w = max w 2 in + state.w <- w; + conf.winh <- h; + GlMat.mode `modelview; + GlMat.load_identity (); + + GlMat.mode `projection; + GlMat.load_identity (); + GlMat.rotate ~x:1.0 ~angle:180.0 (); + GlMat.translate ~x:~-.1.0 ~y:~-.1.0 (); + GlMat.scale3 (2.0 /. float conf.winw, 2.0 /. float conf.winh, 1.0); + + invalidate (); + wcmd "geometry" [`i w; `i h]; ;; let enttext () = let len = String.length state.text in let drawstring s = - GlDraw.color (0.0, 0.0, 0.0); - GlDraw.rect - (0.0, float (conf.winh - 18)) - (float (conf.winw - state.scrollw - 1), float conf.winh) - ; + let hscrollh = + match state.mode with + | View -> state.hscrollh + | _ -> 0 + in + let rect x w = + GlDraw.rect + (x, float (conf.winh - (!uifontsize + 4) - hscrollh)) + (x+.w, float (conf.winh - hscrollh)) + in + + let w = float (conf.winw - state.scrollw - 1) in + if state.progress >= 0.0 && state.progress < 1.0 + then ( + GlDraw.color (0.3, 0.3, 0.3); + let w1 = w *. state.progress in + rect 0.0 w1; + GlDraw.color (0.0, 0.0, 0.0); + rect w1 (w-.w1) + ) + else ( + GlDraw.color (0.0, 0.0, 0.0); + rect 0.0 w; + ); + GlDraw.color (1.0, 1.0, 1.0); - drawstring 14 (if len > 0 then 8 else 2) (conf.winh - 5) s; + drawstring !uifontsize + (if len > 0 then 8 else 2) (conf.winh - hscrollh - 5) s; in match state.mode with | Textentry ((prefix, text, _, _, _), _) -> @@ -793,57 +1312,139 @@ let enttext () = let showtext c s = state.text <- Printf.sprintf "%c%s" c s; - Glut.postRedisplay (); + G.postRedisplay "showtext"; ;; -let act cmd = - match cmd.[0] with - | 'c' -> - state.pdims <- []; +let gctiles () = + let len = Queue.length state.tilelru in + let rec loop qpos = + if state.memused <= conf.memlimit + then () + else ( + if qpos < len + then + let (k, p, s) as lruitem = Queue.pop state.tilelru in + let n, gen, colorspace, angle, pagew, pageh, col, row = k in + if + gen = state.gen + && colorspace = conf.colorspace + && angle = conf.angle + && pagew = getpagew n + && pageh = getpageh n + && ( + let layout = + if conf.preload + then preloadlayout state.layout + else state.layout + in + let x = col*conf.tilew + and y = row*conf.tileh in + tilevisible layout n x y + ) + then Queue.push lruitem state.tilelru + else ( + wcmd "freetile" [`s p]; + state.memused <- state.memused - s; + Hashtbl.remove state.tilemap k; + ); + loop (qpos+1) + ) + in + loop 0 +;; - | 'D' -> - state.rects <- state.rects1; - Glut.postRedisplay () +let flushtiles () = + Queue.iter (fun (k, p, s) -> + wcmd "freetile" [`s p]; + state.memused <- state.memused - s; + Hashtbl.remove state.tilemap k; + ) state.tilelru; + Queue.clear state.tilelru; + load state.layout; +;; - | 'C' -> - let n = Scanf.sscanf cmd "C %u" (fun n -> n) in - state.pagecount <- n; - state.invalidated <- state.invalidated - 1; +let logcurrently = function + | Idle -> dolog "Idle" + | Loading (l, gen) -> + dolog "Loading %d gen=%d curgen=%d" l.pageno gen state.gen + | Tiling (l, pageopaque, colorspace, angle, gen, col, row, tilew, tileh) -> + dolog + "Tiling %d[%d,%d] page=%s cs=%s angle" + l.pageno col row pageopaque + (colorspace_to_string colorspace) + ; + dolog "gen=(%d,%d) (%d,%d) tile=(%d,%d) (%d,%d)" + angle gen conf.angle state.gen + tilew tileh + conf.tilew conf.tileh + ; + | Outlining _ -> + dolog "outlining" +;; + +let act cmds = + (* dolog "%S" cmds; *) + let op, args = + let spacepos = + try String.index cmds ' ' + with Not_found -> -1 + in + if spacepos = -1 + then cmds, "" + else + let l = String.length cmds in + let op = String.sub cmds 0 spacepos in + op, begin + if l - spacepos < 2 then "" + else String.sub cmds (spacepos+1) (l-spacepos-1) + end + in + match op with + | "clear" -> + state.pdims <- []; + + | "clearrects" -> + state.rects <- state.rects1; + G.postRedisplay "clearrects"; + + | "continue" -> + let n = Scanf.sscanf args "%u" (fun n -> n) in + state.pagecount <- n; + state.invalidated <- state.invalidated - 1; + begin match state.currently with + | Outlining l -> + state.currently <- Idle; + state.outlines <- Array.of_list (List.rev l) + | _ -> () + end; if state.invalidated = 0 - then represent () + then represent (); + if not conf.showall + then G.postRedisplay "continue"; - | 't' -> - let s = Scanf.sscanf cmd "t %n" - (fun n -> String.sub cmd n (String.length cmd - n)) - in - Glut.setWindowTitle s + | "title" -> + Glut.setWindowTitle args - | 'T' -> - let s = Scanf.sscanf cmd "T %n" - (fun n -> String.sub cmd n (String.length cmd - n)) - in - if istextentry state.mode - then ( - state.text <- s; - showtext ' ' s; - ) - else ( - state.text <- s; - Glut.postRedisplay (); - ) + | "msg" -> + showtext ' ' args - | 'V' -> + | "vmsg" -> if conf.verbose - then - let s = Scanf.sscanf cmd "V %n" - (fun n -> String.sub cmd n (String.length cmd - n)) - in - state.text <- s; - showtext ' ' s; + then showtext ' ' args + + | "progress" -> + let progress, text = Scanf.sscanf args "%f %n" + (fun f pos -> + f, String.sub args pos (String.length args - pos) + ) + in + state.text <- text; + state.progress <- progress; + G.postRedisplay "progress" - | 'F' -> + | "firstmatch" -> let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 = - Scanf.sscanf cmd "F %u %d %f %f %f %f %f %f %f %f" + Scanf.sscanf args "%u %d %f %f %f %f %f %f %f %f" (fun p c x0 y0 x1 y1 x2 y2 x3 y3 -> (p, c, x0, y0, x1, y1, x2, y2, x3, y3)) in @@ -852,106 +1453,158 @@ let act cmd = gotoy y; state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)] - | 'R' -> + | "match" -> let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 = - Scanf.sscanf cmd "R %u %d %f %f %f %f %f %f %f %f" + Scanf.sscanf args "%u %d %f %f %f %f %f %f %f %f" (fun p c x0 y0 x1 y1 x2 y2 x3 y3 -> (p, c, x0, y0, x1, y1, x2, y2, x3, y3)) in state.rects1 <- (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1 - | 'r' -> - let n, w, _h, r, l, s, p = - Scanf.sscanf cmd "r %u %u %u %d %d %u %s" - (fun n w h r l s p -> - (n-1, w, h, r, l != 0, s, p)) - in + | "page" -> + let pageopaque, t = Scanf.sscanf args "%s %f" (fun p t -> p, t) in + begin match state.currently with + | Loading (l, gen) -> + vlog "page %d took %f sec" l.pageno t; + Hashtbl.replace state.pagemap (l.pageno, gen) pageopaque; + begin match state.throttle with + | None -> + let preloadedpages = + if conf.preload + then preloadlayout state.layout + else state.layout + in + let evict () = + let module IntSet = + Set.Make (struct type t = int let compare = (-) end) in + let set = + List.fold_left (fun s l -> IntSet.add l.pageno s) + IntSet.empty preloadedpages + in + let evictedpages = + Hashtbl.fold (fun ((pageno, _) as key) opaque accu -> + if not (IntSet.mem pageno set) + then ( + wcmd "freepage" [`s opaque]; + key :: accu + ) + else accu + ) state.pagemap [] + in + List.iter (Hashtbl.remove state.pagemap) evictedpages; + in + evict (); + state.currently <- Idle; + if gen = state.gen + then ( + tilepage l.pageno pageopaque state.layout; + load state.layout; + load preloadedpages; + if pagevisible state.layout l.pageno + && layoutready state.layout + then G.postRedisplay "page"; + ) - state.memused <- state.memused + s; + | Some (layout, _) -> + state.currently <- Idle; + tilepage l.pageno pageopaque layout; + load state.layout + end; - let layout = - match state.throttle with - | None -> state.layout - | Some layout -> layout - in + | _ -> + dolog "Inconsistent loading state"; + logcurrently state.currently; + raise Quit; + end - let rec gc () = - if state.memused <= conf.memlimit || Queue.is_empty state.pagelru - then () - else - let opaque = Queue.peek state.pagelru in - match findpageforopaque opaque with - | None -> failwith "bug in gc" - | Some (pagekey, size) -> - let n, w, a, p, g = pagekey in - if w != state.w - || a != conf.angle - || p != conf.proportional - || g != state.gen - || not (pagevisible layout n) - then ( - ignore (Queue.pop state.pagelru); - wcmd "free" [`s opaque]; - Hashtbl.remove state.pagemap pagekey; - state.memused <- state.memused - size; - gc (); - ) + | "tile" -> + let (x, y, opaque, size, t) = + Scanf.sscanf args "%u %u %s %u %f" + (fun x y p size t -> (x, y, p, size, t)) in - gc (); - - Hashtbl.replace state.pagemap (n, w, r, l, state.gen) (p, s); - Queue.push p state.pagelru; - state.rendering <- false; + begin match state.currently with + | Tiling (l, pageopaque, cs, angle, gen, col, row, tilew, tileh) -> + vlog "tile %d [%d,%d] took %f sec" l.pageno col row t; - begin match state.throttle with - | None -> - if pagevisible state.layout n - then Glut.postRedisplay (); - let allvisible = loadlayout state.layout in - if allvisible then preload () + if tilew != conf.tilew || tileh != conf.tileh + then ( + wcmd "freetile" [`s opaque]; + state.currently <- Idle; + load state.layout; + ) + else ( + puttileopaque l col row gen cs angle opaque size t; + state.memused <- state.memused + size; + gctiles (); + Queue.push ((l.pageno, gen, cs, angle, l.pagew, l.pageh, col, row), + opaque, size) state.tilelru; + + state.currently <- Idle; + if gen = state.gen + && conf.colorspace = cs + && conf.angle = angle + && tilevisible state.layout l.pageno x y + then conttiling l.pageno pageopaque; + + begin match state.throttle with + | None -> + preload state.layout; + if gen = state.gen + && conf.colorspace = cs + && conf.angle = angle + && tilevisible state.layout l.pageno x y + then G.postRedisplay "tile nothrottle"; + + | Some (layout, y) -> + let ready = layoutready layout in + if ready + then ( + state.y <- y; + state.layout <- layout; + state.throttle <- None; + G.postRedisplay "throttle"; + ) + else load layout; + end; + ) - | Some layout -> - match layout with - | [] -> () - | l :: _ -> - let y = getpagey l.pageno + l.pagey in - gotoy y + | _ -> + dolog "Inconsistent tiling state"; + logcurrently state.currently; + raise Quit; end - | 'l' -> + | "pdim" -> let pdim = - Scanf.sscanf cmd "l %u %u %u %u" (fun n w h x -> n, w, h, x) + Scanf.sscanf args "%u %u %u %u" (fun n w h x -> n, w, h, x) in state.pdims <- pdim :: state.pdims - | 'o' -> + | "o" -> let (l, n, t, h, pos) = - Scanf.sscanf cmd "o %u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos) + Scanf.sscanf args "%u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos) in - let s = String.sub cmd pos (String.length cmd - pos) in + let s = String.sub args pos (String.length args - pos) in let outline = (s, l, (n, float t /. float h)) in - let outlines = - match state.outlines with - | Olist outlines -> Olist (outline :: outlines) - | Oarray _ -> Olist [outline] - | Onarrow _ -> Olist [outline] - in - state.outlines <- outlines + begin match state.currently with + | Outlining outlines -> + state.currently <- Outlining (outline :: outlines) + | Idle -> + state.currently <- Outlining [outline] + | currently -> + dolog "invalid outlining state"; + logcurrently currently + end + | "info" -> + state.docinfo <- (1, args) :: state.docinfo - | 'i' -> - if String.length cmd > 1 && cmd.[1] = 'e' - then - state.docinfo <- List.rev state.docinfo - else - let s = Scanf.sscanf cmd "i %n" - (fun n -> String.sub cmd n (String.length cmd - n)) - in - state.docinfo <- (1, s) :: state.docinfo + | "infoend" -> + state.docinfo <- List.rev state.docinfo | _ -> - dolog "unknown cmd `%S'" cmd + dolog "unknown cmd `%S'" cmds ;; let now = Unix.gettimeofday;; @@ -1028,8 +1681,7 @@ 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 + let text = addchar text c in TEcont text | _ -> @@ -1037,13 +1689,6 @@ let intentry text key = TEcont text ;; -let addchar s c = - let b = Buffer.create (String.length s + 1) in - Buffer.add_string b s; - Buffer.add_char b c; - Buffer.contents b; -;; - let textentry text key = let c = Char.unsafe_chr key in match c with @@ -1056,24 +1701,59 @@ let textentry text key = TEcont text ;; -let reinit angle proportional = - state.anchor <- getanchor (); - conf.angle <- angle; - conf.proportional <- proportional; +let reqlayout angle proportional = + match state.throttle with + | None -> + if state.invalidated = 0 then state.anchor <- getanchor (); + conf.angle <- angle mod 360; + conf.proportional <- proportional; + invalidate (); + wcmd "reqlayout" [`i conf.angle; `b proportional]; + | _ -> () +;; + +let settrim trimmargins trimfuzz = + if state.invalidated = 0 then state.anchor <- getanchor (); + conf.trimmargins <- trimmargins; + conf.trimfuzz <- trimfuzz; + let x0, y0, x1, y1 = trimfuzz in invalidate (); - wcmd "reinit" [`i angle; `b proportional]; + wcmd "settrim" [ + `b conf.trimmargins; + `i x0; + `i y0; + `i x1; + `i y1; + ]; + Hashtbl.iter (fun _ opaque -> + wcmd "freepage" [`s opaque]; + ) state.pagemap; + Hashtbl.clear state.pagemap; ;; let setzoom zoom = - let zoom = max 0.01 zoom in - if zoom <> conf.zoom - then ( - if zoom <= 1.0 - then state.x <- 0; - conf.zoom <- zoom; - reshape conf.winw conf.winh; - state.text <- Printf.sprintf "zoom is now %-5.1f" (zoom *. 100.0); - ); + match state.throttle with + | None -> + let zoom = max 0.01 zoom in + if zoom <> conf.zoom + then ( + state.prevzoom <- conf.zoom; + let relx = + if zoom <= 1.0 + then (state.x <- 0; 0.0) + else float state.x /. float state.w + in + conf.zoom <- zoom; + reshape conf.winw conf.winh; + if zoom > 1.0 + then ( + let x = relx *. float state.w in + state.x <- truncate x; + ); + state.text <- Printf.sprintf "zoom is now %-5.1f" (zoom *. 100.0); + ) + + | _ -> () ;; let enterbirdseye () = @@ -1136,7 +1816,7 @@ let leavebirdseye (c, leftx, pageno, _, anchor) goback = let togglebirdseye () = match state.mode with | Birdseye vals -> leavebirdseye vals true - | View | Outline _ -> enterbirdseye () + | View -> enterbirdseye () | _ -> () ;; @@ -1146,7 +1826,7 @@ let upbirdseye (conf, leftx, pageno, hooverpageno, anchor) = | [] -> gotopage1 pageno 0 | l :: _ when l.pageno = pageno -> if l.pagedispy >= 0 && l.pagey = 0 - then Glut.postRedisplay () + then G.postRedisplay "upbirdseye" else gotopage1 pageno 0 | _ :: rest -> loop rest in @@ -1165,7 +1845,7 @@ let downbirdseye (conf, leftx, pageno, hooverpageno, anchor) = | l :: _ when l.pageno = pageno -> if l.pagevh != l.pageh then gotoy (clamp (l.pageh - l.pagevh + conf.interpagespace)) - else Glut.postRedisplay () + else G.postRedisplay "downbirdseye" | _ :: rest -> loop rest in loop state.layout @@ -1209,7 +1889,7 @@ let optentry mode _ key = | 't' -> let ondone s = try - conf.thumbw <- max 2 (min 1920 (int_of_string s)); + conf.thumbw <- bound (int_of_string s) 2 4096; state.text <- Printf.sprintf "thumbnail width is set to %d" conf.thumbw; begin match mode with @@ -1233,7 +1913,7 @@ let optentry mode _ key = s (Printexc.to_string exc); None with - | Some angle -> reinit angle conf.proportional + | Some angle -> reqlayout angle conf.proportional | None -> () in TEswitch ("rotation: ", "", None, intentry, ondone) @@ -1251,6 +1931,10 @@ let optentry mode _ key = conf.verbose <- not conf.verbose; TEdone ("verbose " ^ (btos conf.verbose)) + | 'd' -> + conf.debug <- not conf.debug; + TEdone ("debug " ^ (btos conf.debug)) + | 'h' -> conf.maxhfit <- not conf.maxhfit; state.maxy <- state.maxy + (if conf.maxhfit then -conf.winh else conf.winh); @@ -1292,330 +1976,1382 @@ let optentry mode _ key = TEswitch ("vertical margin: ", "", None, intentry, ondone) | 'l' -> - reinit conf.angle (not conf.proportional); - TEdone ("proprortional display " ^ btos conf.proportional) + reqlayout conf.angle (not conf.proportional); + TEdone ("proportional display " ^ btos conf.proportional) + + | 'T' -> + settrim (not conf.trimmargins) conf.trimfuzz; + TEdone ("trim margins " ^ btos conf.trimmargins) + + | 'I' -> + conf.invert <- not conf.invert; + TEdone ("invert colors " ^ btos conf.invert) | _ -> state.text <- Printf.sprintf "bad option %d `%c'" key c; TEstop ;; -let maxoutlinerows () = (conf.winh - 31) / 16;; - -let enterselector allowdel outlines errmsg msg = - if Array.length outlines = 0 - then ( - showtext ' ' errmsg; - ) - else ( - state.text <- msg; - Glut.setCursor Glut.CURSOR_INHERIT; - let pageno = - match state.layout with - | [] -> -1 - | {pageno=pageno} :: _ -> pageno - in - let active = - let rec loop n = - if n = Array.length outlines - then 0 - else - let (_, _, (outlinepageno, _)) = outlines.(n) in - if outlinepageno >= pageno then n else loop (n+1) - in - loop 0 - in - state.mode <- Outline - (allowdel, active, max 0 (active - maxoutlinerows () / 2), outlines, "", 0, - state.mode); - Glut.postRedisplay (); - ) -;; +let maxoutlinerows () = (conf.winh - !uifontsize - 1) / (!uifontsize + 1);; + +class type lvsource = object + method getitemcount : int + method getitem : int -> (string * int) option + method hasaction : int -> bool + method exit : + uioh:uioh -> + cancel:bool -> + active:int -> + first:int -> + pan:int -> + qsearch:string -> + uioh option + method getactive : int + method getfirst : int + method getqsearch : string + method setqsearch : string -> unit + method getpan : int +end;; -let enteroutlinemode () = - let outlines, msg = - match state.outlines with - | Oarray a -> a, "" - | Olist l -> - let a = Array.of_list (List.rev l) in - state.outlines <- Oarray a; - a, "" - | Onarrow (pat, a, _) -> - a, "Outline was narrowed to `" ^ pat ^ "' (Ctrl-u to restore)" - in - enterselector false outlines "Document has no outline" msg; -;; +class virtual lvsourcebase = object + val mutable m_active = 0 + val mutable m_first = 0 + val mutable m_qsearch = "" + val mutable m_pan = 0 + method getactive = m_active + method getfirst = m_first + method getqsearch = m_qsearch + method getpan = m_pan + method setqsearch s = m_qsearch <- s +end;; -let enterbookmarkmode () = - let bookmarks = Array.of_list state.bookmarks in - enterselector true bookmarks "Document has no bookmarks (yet)" ""; +let textentryspecial key = function + | ((c, _, (Some (action, _) as onhist), onkey, ondone), mode) -> + let s = + match key with + | Glut.KEY_UP -> action HCprev + | Glut.KEY_DOWN -> action HCnext + | Glut.KEY_HOME -> action HCfirst + | Glut.KEY_END -> action HClast + | _ -> state.text + in + state.mode <- Textentry ((c, s, onhist, onkey, ondone), mode); + G.postRedisplay "special textentry"; + | _ -> () ;; -let mode_to_string mode = - let b = Buffer.create 10 in - let rec f = function - | Textentry (_, _) -> Buffer.add_string b "Textentry "; - | View -> Buffer.add_string b "View" - | Birdseye _ -> Buffer.add_string b "Birdseye" - | Items _ -> Buffer.add_string b "Items" - | Outline _ -> Buffer.add_string b "Outline" +let textentrykeyboard key ((c, text, opthist, onkey, ondone), onleave) = + let enttext te = + state.mode <- Textentry (te, onleave); + state.text <- ""; + enttext (); + G.postRedisplay "textentrykeyboard enttext"; in - f mode; - Buffer.contents b; -;; - -let color_of_string s = - Scanf.sscanf s "%d/%d/%d" (fun r g b -> - (float r /. 256.0, float g /. 256.0, float b /. 256.0) - ) -;; - -let color_to_string (r, g, b) = - let r = truncate (r *. 256.0) - and g = truncate (g *. 256.0) - and b = truncate (b *. 256.0) in - Printf.sprintf "%d/%d/%d" r g b -;; - -let enterinfomode () = - let btos = function true -> "on" | _ -> "off" in - let mode = state.mode in - let rec makeitems () = - let intp name get set = - Printf.sprintf "%s\t%d" name (get ()), 1, Action ( - fun active first _ pan -> - let ondone s = - let n = - try int_of_string s - with exn -> - state.text <- Printf.sprintf "bad integer `%s': %s" - s (Printexc.to_string exn); - max_int; - in - if n != max_int then set n; - in - let te = name ^ ": ", "", None, intentry, ondone in - state.text <- ""; - Textentry ( - te, - fun _ -> - state.mode <- Items (active, first, makeitems (), "", pan, mode) - ) - ) - and boolp name get set = - Printf.sprintf "%s\t%s" name (btos (get ())), 1, Action ( - fun active first qsearch pan -> - let v = get () in - set (not v); - Items (active, first, makeitems (), qsearch, pan, mode); + match Char.unsafe_chr key with + | '\008' -> (* backspace *) + let len = String.length text in + if len = 0 + then ( + onleave Cancel; + G.postRedisplay "textentrykeyboard after cancel"; ) - and colorp name get set = - Printf.sprintf "%s\t%s" name (color_to_string (get ())), 1, Action ( - fun active first _ pan -> - let invalid = (nan, nan, nan) in - let ondone s = - let c = - try color_of_string s - with exn -> - state.text <- Printf.sprintf "bad color `%s': %s" - s (Printexc.to_string exn); - invalid - in - if c <> invalid - then set c; - in - let te = name ^ ": ", "", None, textentry, ondone in - state.text <- ""; - Textentry ( - te, - fun _ -> - state.mode <- Items (active, first, makeitems (), "", pan, mode) - ) + else ( + let s = String.sub text 0 (len - 1) in + enttext (c, s, opthist, onkey, ondone) ) - in - let birdseye = isbirdseye mode in - let items = [ - (if birdseye then "Setup bird's eye" else "Setup"), 0, Noaction; - - boolp "presentation" - (fun () -> conf.presentation) - (fun v -> - conf.presentation <- v; - state.anchor <- getanchor (); - represent ()); - - boolp "ignore case in searches" - (fun () -> conf.icase) - (fun v -> conf.icase <- v); + | '\r' | '\n' -> + ondone text; + onleave Confirm; + G.postRedisplay "textentrykeyboard after confirm" - boolp "preload" - (fun () -> conf.preload) - (fun v -> conf.preload <- v); + | '\007' (* ctrl-g *) + | '\027' -> (* escape *) + if String.length text = 0 + then ( + begin match opthist with + | None -> () + | Some (_, onhistcancel) -> onhistcancel () + end; + onleave Cancel; + state.text <- ""; + G.postRedisplay "textentrykeyboard after cancel2" + ) + else ( + enttext (c, "", opthist, onkey, ondone) + ) - boolp "verbose" - (fun () -> conf.verbose) - (fun v -> conf.verbose <- v); + | '\127' -> () (* delete *) - boolp "max fit" - (fun () -> conf.maxhfit) - (fun v -> conf.maxhfit <- v); + | _ -> + begin match onkey text key with + | TEdone text -> + ondone text; + onleave Confirm; + G.postRedisplay "textentrykeyboard after confirm2"; - boolp "crop hack" - (fun () -> conf.crophack) - (fun v -> conf.crophack <- v); + | TEcont text -> + enttext (c, text, opthist, onkey, ondone); - boolp "throttle" - (fun () -> conf.showall) - (fun v -> conf.showall <- v); + | TEstop -> + onleave Cancel; + state.text <- ""; + G.postRedisplay "textentrykeyboard after cancel3" - boolp "highlight links" - (fun () -> conf.hlinks) - (fun v -> conf.hlinks <- v); + | TEswitch te -> + state.mode <- Textentry (te, onleave); + G.postRedisplay "textentrykeyboard switch"; + end; +;; - boolp "under info" - (fun () -> conf.underinfo) - (fun v -> conf.underinfo <- v); - boolp "persistent bookmarks" - (fun () -> conf.savebmarks) - (fun v -> conf.savebmarks <- v); +let firstof first active = + let maxrows = maxoutlinerows () in + if first > active || abs (first - active) > maxrows - 1 + then max 0 (active - (maxrows/2)) + else first +;; - boolp "proportional display" - (fun () -> conf.proportional) - (fun v -> reinit conf.angle v); +class listview ~(source:lvsource) ~trusted = + let coe s = (s :> uioh) in +object (self) + val m_pan = source#getpan + val m_first = source#getfirst + val m_active = source#getactive + val m_qsearch = source#getqsearch + val m_prev_uioh = state.uioh + + method private elemunder y = + let n = y / (!uifontsize+1) in + if m_first + n < source#getitemcount + then ( + if source#hasaction (m_first + n) + then Some (m_first + n) + else None + ) + else None + + method display = + Gl.enable `blend; + GlFunc.blend_func `src_alpha `one_minus_src_alpha; + GlDraw.color (0., 0., 0.) ~alpha:0.85; + GlDraw.rect (0., 0.) (float conf.winw, float conf.winh); + GlDraw.color (1., 1., 1.); + Gl.enable `texture_2d; + let fs = !uifontsize in + let nfs = fs + 1 in + let wx = measurestr fs "w" in + let tabx = 30.0*.wx +. float (m_pan * fs) in + let rec loop row = + if (row - m_first) * nfs > conf.winh + then () + else + match + if row >= 0 && row < source#getitemcount + then source#getitem row + else None + with + | None -> () + | Some (s, level) -> + let y = (row - m_first) * nfs in + let x = 5 + fs*(max 0 (level+m_pan)) in + if row = m_active + then ( + Gl.disable `texture_2d; + GlDraw.polygon_mode `both `line; + GlDraw.color (1., 1., 1.) ~alpha:0.9; + GlDraw.rect (1., float (y + 1)) + (float (conf.winw - 1), float (y + fs + 3)); + GlDraw.polygon_mode `both `fill; + GlDraw.color (1., 1., 1.); + Gl.enable `texture_2d; + ); - boolp "persistent location" - (fun () -> conf.jumpback) - (fun v -> conf.jumpback <- v); + let drawtabularstring x s = + if trusted + then + let tabpos = try String.index s '\t' with Not_found -> -1 in + if tabpos > 0 + then + let len = String.length s - tabpos - 1 in + let s1 = String.sub s 0 tabpos + and s2 = String.sub s (tabpos + 1) len in + let xx = wx +. drawstring1 fs x (y + !uifontsize+1) s1 in + let x = truncate (max xx tabx) in + drawstring1 nfs x (y + (!uifontsize+1)) s2 + else + drawstring1 fs x (y + nfs) s + else + drawstring1 fs x (y + nfs) s + in + let _w = drawtabularstring (x + m_pan*nfs) s in + loop (row+1) + in + loop 0; + Gl.disable `blend; + Gl.disable `texture_2d; - "", 0, Noaction; + method private key1 key = + let set active first qsearch = + coe {< m_active = active; m_first = first; m_qsearch = qsearch >} + in + let search active pattern incr = + let dosearch re = + let rec loop n = + if n >= 0 && n < source#getitemcount + then + match source#getitem n with + | None -> None + | Some (s, _) -> + if + (try ignore (Str.search_forward re s 0); true + with Not_found -> false) + then Some n + else loop (n + incr) + else None + in + loop active + in + try + let re = Str.regexp_case_fold pattern in + dosearch re + with Failure s -> + state.text <- s; + None + in + match key with + | 18 | 19 -> (* ctrl-r/ctlr-s *) + let incr = if key = 18 then -1 else 1 in + let active, first = + match search (m_active + incr) m_qsearch incr with + | None -> + state.text <- m_qsearch ^ " [not found]"; + m_active, m_first + | Some active -> + state.text <- m_qsearch; + active, firstof m_first active + in + G.postRedisplay "listview ctrl-r/s"; + set active first m_qsearch; - intp "vertical margin" - (fun () -> conf.interpagespace) - (fun n -> - conf.interpagespace <- n; - let pageno, py = - match state.layout with - | [] -> 0, 0 - | l :: _ -> - l.pageno, l.pagey - in - state.maxy <- calcheight (); - let y = getpagey pageno in - gotoy (y + py) + | 8 -> (* backspace *) + let len = String.length m_qsearch in + if len = 0 + then coe self + else ( + if len = 1 + then ( + state.text <- ""; + G.postRedisplay "listview empty qsearch"; + set m_active m_first ""; + ) + else + let qsearch = String.sub m_qsearch 0 (len - 1) in + let active, first = + match search m_active qsearch ~-1 with + | None -> + state.text <- qsearch ^ " [not found]"; + m_active, m_first + | Some active -> + state.text <- qsearch; + active, firstof m_first active + in + G.postRedisplay "listview backspace qsearch"; + set active first qsearch ); - intp "page bias" - (fun () -> conf.pagebias) - (fun v -> conf.pagebias <- v); - - intp "scroll step" - (fun () -> conf.scrollstep) - (fun n -> conf.scrollstep <- n); - - intp "auto scroll step" - (fun () -> - match state.autoscroll with - | Some step -> step - | _ -> conf.autoscrollstep) - (fun n -> - if state.autoscroll <> None - then state.autoscroll <- Some n; - conf.autoscrollstep <- n); + | _ when key >= 32 && key < 127 -> + let pattern = addchar m_qsearch (Char.chr key) in + let active, first = + match search m_active pattern 1 with + | None -> + state.text <- pattern ^ " [not found]"; + m_active, m_first + | Some active -> + state.text <- pattern; + active, firstof m_first active + in + G.postRedisplay "listview qsearch add"; + set active first pattern; - intp "zoom" - (fun () -> truncate (conf.zoom *. 100.)) - (fun v -> setzoom ((float v) /. 100.)); + | 27 -> (* escape *) + state.text <- ""; + if String.length m_qsearch = 0 + then ( + G.postRedisplay "list view escape"; + begin + match + source#exit (coe self) true m_active m_first m_pan m_qsearch + with + | None -> m_prev_uioh + | Some uioh -> uioh + end + ) + else ( + G.postRedisplay "list view kill qsearch"; + source#setqsearch ""; + coe {< m_qsearch = "" >} + ) - intp "rotation" - (fun () -> conf.angle) - (fun v -> reinit v conf.proportional); + | 13 -> (* enter *) + state.text <- ""; + let self = {< m_qsearch = "" >} in + source#setqsearch ""; + let opt = + G.postRedisplay "listview enter"; + if m_active >= 0 && m_active < source#getitemcount + then ( + source#exit (coe self) false m_active m_first m_pan ""; + ) + else ( + source#exit (coe self) true m_active m_first m_pan ""; + ); + in + begin match opt with + | None -> m_prev_uioh + | Some uioh -> uioh + end - intp "scroll bar width" - (fun () -> state.scrollw) - (fun v -> - state.scrollw <- v; - conf.scrollbw <- v; - reshape conf.winw conf.winh; - ); + | 127 -> (* delete *) + coe self - intp "scroll handle height" - (fun () -> conf.scrollh) - (fun v -> conf.scrollh <- v;); + | _ -> dolog "unknown key %d" key; coe self - intp "thumbnail width" - (fun () -> conf.thumbw) - (fun v -> - conf.thumbw <- min 1920 v; - match mode with - | Birdseye beye -> - leavebirdseye beye false; - enterbirdseye () - | _ -> () - ); + method private special1 key = + let maxrows = maxoutlinerows () in + let itemcount = source#getitemcount in + let find start incr = + let rec find i = + if i = -1 || i = itemcount + then -1 + else ( + if source#hasaction i + then i + else find (i + incr) + ) + in + find start + in + let set active first = + let first = bound first 0 (itemcount - maxrows) in + state.text <- ""; + coe {< m_active = active; m_first = first >} + in + let navigate incr = + let isvisible first n = n >= first && n - first <= maxrows in + let active, first = + let incr1 = if incr > 0 then 1 else -1 in + if isvisible m_first m_active + then + let next = + let next = m_active + incr in + let next = + if next < 0 || next >= itemcount + then -1 + else find next incr1 + in + if next = -1 || abs (m_active - next) > maxrows + then -1 + else next + in + if next = -1 + then + let first = m_first + incr in + let first = bound first 0 (itemcount - 1) in + let next = + let next = m_active + incr in + let next = bound next 0 (itemcount - 1) in + find next ~-incr1 + in + let active = if next = -1 then m_active else next in + active, first + else + let first = min next m_first in + next, first + else + let first = m_first + incr in + let first = bound first 0 (itemcount - 1) in + let active = + let next = m_active + incr in + let next = bound next 0 (itemcount - 1) in + let next = find next incr1 in + if next = -1 || abs (m_active - first) > maxrows + then m_active + else next + in + active, first + in + G.postRedisplay "listview navigate"; + set active first; + in + begin match key with + | Glut.KEY_UP -> navigate ~-1 + | Glut.KEY_DOWN -> navigate 1 + | Glut.KEY_PAGE_UP -> navigate ~-maxrows + | Glut.KEY_PAGE_DOWN -> navigate maxrows - colorp "background color" - (fun () -> conf.bgcolor) - (fun v -> conf.bgcolor <- v); + | Glut.KEY_RIGHT -> + state.text <- ""; + G.postRedisplay "listview right"; + coe {< m_pan = m_pan - 1 >} - "", 0, Noaction; - "Presentation mode", 0, Noaction; + | Glut.KEY_LEFT -> + state.text <- ""; + G.postRedisplay "listview left"; + coe {< m_pan = m_pan + 1 >} + + | Glut.KEY_HOME -> + let active = find 0 1 in + G.postRedisplay "listview home"; + set active 0; + + | Glut.KEY_END -> + let first = max 0 (itemcount - maxrows) in + let active = find (itemcount - 1) ~-1 in + G.postRedisplay "listview end"; + set active first; - boolp "scrollbar visible" - (fun () -> conf.scrollbarinpm) - (fun v -> - if v != conf.scrollbarinpm + | _ -> coe self + end; + + method key key = + match state.mode with + | Textentry te -> textentrykeyboard key te; coe self + | _ -> self#key1 key + + method special key = + match state.mode with + | Textentry te -> textentryspecial key te; coe self + | _ -> self#special1 key + + method button button bstate _ y = + let opt = + match button with + | Glut.LEFT_BUTTON when bstate = Glut.UP -> + begin match self#elemunder y with + | Some n -> + G.postRedisplay "listview click"; + source#exit (coe {< m_active = n >}) false n m_first m_pan m_qsearch + | _ -> + Some (coe self) + end + | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP -> + let len = source#getitemcount in + let first = + if m_first + maxoutlinerows () >= len + then + m_first + else + let first = m_first + (if n == 3 then -1 else 1) in + bound first 0 (len - 1) + in + G.postRedisplay "listview wheel"; + Some (coe {< m_first = first >}) + | _ -> + Some (coe self) + in + match opt with + | None -> m_prev_uioh + | Some uioh -> uioh + + method motion _ _ = coe self + + method pmotion _ y = + let n = + match self#elemunder y with + | None -> Glut.setCursor Glut.CURSOR_INHERIT; m_active + | Some n -> Glut.setCursor Glut.CURSOR_INFO; n + in + let o = + if n != m_active + then (G.postRedisplay "listview pmotion"; {< m_active = n >}) + else self + in + coe o +end;; + +class outlinelistview ~source : uioh = + let coe o = (o :> uioh) in +object + inherit listview ~source:(source :> lvsource) ~trusted:false as super + + method key key = + match key with + | 14 -> (* ctrl-n *) + source#narrow m_qsearch; + G.postRedisplay "outline ctrl-n"; + coe {< m_first = 0; m_active = 0 >} + + | 21 -> (* ctrl-u *) + source#denarrow; + G.postRedisplay "outline ctrl-u"; + coe {< m_first = 0; m_active = 0 >} + + | 12 -> (* ctrl-l *) + let first = m_active - (maxoutlinerows () / 2) in + G.postRedisplay "outline ctrl-l"; + coe {< m_first = first >} + + | 127 -> (* delete *) + source#remove m_active; + G.postRedisplay "outline delete"; + let active = max 0 (m_active-1) in + coe {< m_first = firstof m_first active; m_active = active >} + + | key -> super#key key + + method special key = + let maxrows = maxoutlinerows () in + let calcfirst first active = + if active > first + then + let rows = active - first in + if rows > maxrows then active - maxrows else first + else active + in + let navigate incr = + let active = m_active + incr in + let active = bound active 0 (source#getitemcount - 1) in + let first = calcfirst m_first active in + G.postRedisplay "special outline navigate"; + coe {< m_active = active; m_first = first >} + in + let updownlevel incr = + let len = source#getitemcount in + let curlevel = + match source#getitem m_active with + | None -> assert false + | Some (_, level) -> level + in + let rec flow i = + if i = len then i-1 else if i = -1 then 0 else + let l = + match source#getitem i with + | None -> -1 + | Some (_, l) -> l + in + if l != curlevel then i else flow (i+incr) + in + let active = flow m_active in + let first = calcfirst m_first active in + G.postRedisplay "special outline updownlevel"; + {< m_active = active; m_first = first >} + in + match key with + | Glut.KEY_UP -> navigate ~-1 + | Glut.KEY_DOWN -> navigate 1 + | Glut.KEY_PAGE_UP -> navigate ~-maxrows + | Glut.KEY_PAGE_DOWN -> navigate maxrows + + | Glut.KEY_RIGHT -> + let o = + if Glut.getModifiers () land Glut.active_ctrl != 0 + then ( + G.postRedisplay "special outline right"; + {< m_pan = m_pan + 1 >} + ) + else updownlevel 1 + in + coe o + + | Glut.KEY_LEFT -> + let o = + if Glut.getModifiers () land Glut.active_ctrl != 0 then ( - conf.scrollbarinpm <- v; - if conf.presentation + G.postRedisplay "special outline left"; + {< m_pan = m_pan - 1 >} + ) + else updownlevel ~-1 + in + coe o + + | Glut.KEY_HOME -> + G.postRedisplay "special outline home"; + coe {< m_first = 0; m_active = 0 >} + + | Glut.KEY_END -> + let active = source#getitemcount - 1 in + let first = max 0 (active - maxrows) in + G.postRedisplay "special outline end"; + coe {< m_active = active; m_first = first >} + + | _ -> super#special key +end + +let outlinesource usebookmarks = + let empty = [||] in + (object + inherit lvsourcebase + val mutable m_items = empty + val mutable m_orig_items = empty + val mutable m_prev_items = empty + val mutable m_narrow_pattern = "" + val mutable m_hadremovals = false + + method getitemcount = Array.length m_items + (if m_hadremovals then 1 else 0) + + method getitem n = + if n == Array.length m_items && m_hadremovals + then + Some ("[Confirm removal]", 0) + else + let s, n, _ = m_items.(n) in + Some (s, n) + + method exit ~uioh ~cancel ~active ~first ~pan ~qsearch = + ignore (uioh, first, pan, qsearch); + let confrimremoval = m_hadremovals && active = Array.length m_items in + let items = + if String.length m_narrow_pattern = 0 + then m_orig_items + else m_items + in + if not cancel + then ( + if not confrimremoval + then( + let _, _, anchor = m_items.(active) in + gotoanchor anchor; + m_items <- items; + ) + else ( + state.bookmarks <- Array.to_list m_items; + m_orig_items <- m_items; + ) + ) + else m_items <- items; + None + + method hasaction _ = true + + method greetmsg = + if Array.length m_items != Array.length m_orig_items + then "Narrowed to " ^ m_narrow_pattern ^ " (ctrl-u to restore)" + else "" + + method narrow pattern = + let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in + match reopt with + | None -> () + | Some re -> + let rec loop accu n = + if n = -1 then ( - state.scrollw <- if v then conf.scrollbw else 0; - reshape conf.winw conf.winh; + m_narrow_pattern <- pattern; + m_items <- Array.of_list accu ) - ); - ); + else + let (s, _, _) as o = m_items.(n) in + let accu = + if (try ignore (Str.search_forward re s 0); true + with Not_found -> false) + then o :: accu + else accu + in + loop accu (n-1) + in + loop [] (Array.length m_items - 1) - "", 0, Noaction; - "Pixmap Cache", 0, Noaction; + method denarrow = + m_orig_items <- ( + if usebookmarks + then Array.of_list state.bookmarks + else state.outlines + ); + m_items <- m_orig_items - intp "size (advisory)" - (fun () -> conf.memlimit) - (fun v -> conf.memlimit <- v); - Printf.sprintf "%s\t%d" "used" state.memused, 1, Noaction; - ] - in + method remove m = + if usebookmarks + then + if m >= 0 && m < Array.length m_items + then ( + m_hadremovals <- true; + m_items <- Array.init (Array.length m_items - 1) (fun n -> + let n = if n >= m then n+1 else n in + m_items.(n) + ) + ) + + method reset pageno items = + m_hadremovals <- false; + if m_orig_items == empty || m_prev_items != items + then ( + m_orig_items <- items; + if String.length m_narrow_pattern = 0 + then m_items <- items; + ); + m_prev_items <- items; + let active = + let rec loop n best bestd = + if n = Array.length m_items + then best + else + let (_, _, (outlinepageno, _)) = m_items.(n) in + let d = abs (outlinepageno - pageno) in + if d < bestd + then loop (n+1) n d + else loop (n+1) best bestd + in + loop 0 ~-1 max_int + in + m_active <- active; + m_first <- firstof m_first active + end) +;; - let tailer = - let trailer = - ("", 0, Noaction) - :: ("Document", 0, Noaction) - :: List.map (fun (_, s) -> (s, 1, Noaction)) state.docinfo +let enterselector usebookmarks = + let source = outlinesource usebookmarks in + fun errmsg -> + let outlines = + if usebookmarks + then Array.of_list state.bookmarks + else state.outlines + in + if Array.length outlines = 0 + then ( + showtext ' ' errmsg; + ) + else ( + state.text <- source#greetmsg; + Glut.setCursor Glut.CURSOR_INHERIT; + let pageno = + match state.layout with + | [] -> -1 + | {pageno=pageno} :: _ -> pageno in - if birdseye - then trailer + source#reset pageno outlines; + state.uioh <- new outlinelistview ~source; + G.postRedisplay "enter selector"; + ) +;; + +let enteroutlinemode = + let f = enterselector false in + fun ()-> f "Document has no outline"; +;; + +let enterbookmarkmode = + let f = enterselector true in + fun () -> f "Document has no bookmarks (yet)"; +;; + +let color_of_string s = + Scanf.sscanf s "%d/%d/%d" (fun r g b -> + (float r /. 256.0, float g /. 256.0, float b /. 256.0) + ) +;; + +let color_to_string (r, g, b) = + let r = truncate (r *. 256.0) + and g = truncate (g *. 256.0) + and b = truncate (b *. 256.0) in + Printf.sprintf "%d/%d/%d" r g b +;; + +let irect_of_string s = + Scanf.sscanf s "%d/%d/%d/%d" (fun x0 y0 x1 y1 -> (x0,y0,x1,y1)) +;; + +let irect_to_string (x0,y0,x1,y1) = + Printf.sprintf "%d/%d/%d/%d" x0 y0 x1 y1 +;; + +let makecheckers () = + (* Appropriated from lablGL-1.04/LablGlut/examples/lablGL/checker.ml which had + following to say: + converted by Issac Trotts. July 25, 2002 *) + let image_height = 64 + and image_width = 64 in + + let make_image () = + let image = + GlPix.create `ubyte ~format:`rgb ~width:image_width ~height:image_height in + for i = 0 to image_width - 1 do + for j = 0 to image_height - 1 do + Raw.sets (GlPix.to_raw image) ~pos:(3*(i*image_height+j)) + (if (i land 8 ) lxor (j land 8) = 0 + then [|255;255;255|] else [|200;200;200|]) + done + done; + image + in + let image = make_image () in + let id = GlTex.gen_texture () in + GlTex.bind_texture `texture_2d id; + GlPix.store (`unpack_alignment 1); + GlTex.image2d image; + List.iter (GlTex.parameter ~target:`texture_2d) + [ `wrap_s `repeat; + `wrap_t `repeat; + `mag_filter `nearest; + `min_filter `nearest ]; + id; +;; + +let setcheckers enabled = + match state.texid with + | None -> + if enabled then state.texid <- Some (makecheckers ()) + + | Some texid -> + if not enabled + then ( + GlTex.delete_texture texid; + state.texid <- None; + ); +;; + +let int_of_string_with_suffix s = + let l = String.length s in + let s1, shift = + if l > 1 + then + let suffix = Char.lowercase s.[l-1] in + match suffix with + | 'k' -> String.sub s 0 (l-1), 10 + | 'm' -> String.sub s 0 (l-1), 20 + | 'g' -> String.sub s 0 (l-1), 30 + | _ -> s, 0 + else s, 0 + in + let n = int_of_string s1 in + let m = n lsl shift in + if m < 0 || m < n + then raise (Failure "value too large") + else m +;; + +let string_with_suffix_of_int n = + if n = 0 + then "0" + else + let n, s = + if n = 0 + then 0, "" else ( - ("", 0, Noaction) - :: (Printf.sprintf "Save these parameters as defaults at exit (%s)" - (btos conf.bedefault), - 0, - Action ( - fun active first qsearch pan -> - conf.bedefault <- not conf.bedefault; - Items (active, first, makeitems (), qsearch, pan, mode); - ) - ) :: trailer + if n land ((1 lsl 20) - 1) = 0 + then n lsr 20, "M" + else ( + if n land ((1 lsl 10) - 1) = 0 + then n lsr 10, "K" + else n, "" + ) ) in - Array.of_list (items @ tailer) + let rec loop s n = + let h = n mod 1000 in + let n = n / 1000 in + if n = 0 + then string_of_int h ^ s + else ( + let s = Printf.sprintf "_%03d%s" h s in + loop s n + ) + in + loop "" n ^ s; +;; + +let describe_location () = + let f (fn, _) 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 maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in + let percent = + if maxy <= 0 + then 100. + else (100. *. (float state.y /. float maxy)) + 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 +;; + +let rec enterinfomode = + let btos b = if b then "\xe2\x88\x9a" else "" in + let showextended = ref false in + let leave mode = function + | Confirm -> state.mode <- mode + | Cancel -> state.mode <- mode in + let src = + (object + val mutable m_first_time = true + val mutable m_l = [] + val mutable m_a = [||] + val mutable m_prev_uioh = nouioh + val mutable m_prev_mode = View + + inherit lvsourcebase + + method reset prev_mode prev_uioh = + m_a <- Array.of_list (List.rev m_l); + m_l <- []; + m_prev_mode <- prev_mode; + m_prev_uioh <- prev_uioh; + if m_first_time + then ( + let rec loop n = + if n >= Array.length m_a + then () + else + match m_a.(n) with + | _, _, _, Action _ -> m_active <- n + | _ -> loop (n+1) + in + loop 0; + m_first_time <- false; + ) + + method int name get set = + m_l <- + (name, `int get, 1, Action ( + fun u -> + let ondone s = + try set (int_of_string s) + with exn -> + state.text <- Printf.sprintf "bad integer `%s': %s" + s (Printexc.to_string exn) + in + state.text <- ""; + let te = name ^ ": ", "", None, intentry, ondone in + state.mode <- Textentry (te, leave m_prev_mode); + u + )) :: m_l + + method int_with_suffix name get set = + m_l <- + (name, `intws get, 1, Action ( + fun u -> + let ondone s = + try set (int_of_string_with_suffix s) + with exn -> + state.text <- Printf.sprintf "bad integer `%s': %s" + s (Printexc.to_string exn) + in + state.text <- ""; + let te = + name ^ ": ", "", None, intentry_with_suffix, ondone + in + state.mode <- Textentry (te, leave m_prev_mode); + u + )) :: m_l + + method bool ?(offset=1) ?(btos=btos) name get set = + m_l <- + (name, `bool (btos, get), offset, Action ( + fun u -> + let v = get () in + set (not v); + u + )) :: m_l + + method color name get set = + m_l <- + (name, `color get, 1, Action ( + fun u -> + let invalid = (nan, nan, nan) in + let ondone s = + let c = + try color_of_string s + with exn -> + state.text <- Printf.sprintf "bad color `%s': %s" + s (Printexc.to_string exn); + invalid + in + if c <> invalid + then set c; + in + let te = name ^ ": ", "", None, textentry, ondone in + state.text <- color_to_string (get ()); + state.mode <- Textentry (te, leave m_prev_mode); + u + )) :: m_l + + method string name get set = + m_l <- + (name, `string get, 1, Action ( + fun u -> + let ondone s = set s in + let te = name ^ ": ", "", None, textentry, ondone in + state.mode <- Textentry (te, leave m_prev_mode); + u + )) :: m_l + + method colorspace name get set = + m_l <- + (name, `string get, 1, Action ( + fun _ -> + let source = + let vals = [| "rgb"; "bgr"; "gray" |] in + (object + inherit lvsourcebase + + initializer + m_active <- int_of_colorspace conf.colorspace; + m_first <- 0; + + method getitemcount = Array.length vals + method getitem n = Some (vals.(n), 0) + method exit ~uioh ~cancel ~active ~first ~pan ~qsearch = + ignore (uioh, first, pan, qsearch); + if not cancel then set active; + None + method hasaction _ = true + end) + in + state.text <- ""; + new listview ~source ~trusted:true + )) :: m_l + + method caption s offset = + m_l <- (s, `empty, offset, Noaction) :: m_l + + method caption2 s f offset = + m_l <- (s, `string f, offset, Noaction) :: m_l + + method getitemcount = Array.length m_a + + method getitem n = + let tostr = function + | `int f -> string_of_int (f ()) + | `intws f -> string_with_suffix_of_int (f ()) + | `string f -> f () + | `color f -> color_to_string (f ()) + | `bool (btos, f) -> btos (f ()) + | `empty -> "" + in + let name, t, offset, _ = m_a.(n) in + Some ( + (let s = tostr t in + if String.length s > 0 + then Printf.sprintf "%s\t%s" name s + else name), + offset + ) + + method exit ~uioh ~cancel ~active ~first ~pan ~qsearch = + let uiohopt = + if not cancel + then ( + m_qsearch <- qsearch; + let uioh = + match m_a.(active) with + | _, _, _, Action f -> f uioh + | _ -> uioh + in + Some uioh + ) + else None + in + m_active <- active; + m_first <- first; + m_pan <- pan; + uiohopt + + method hasaction n = + match m_a.(n) with + | _, _, _, Action _ -> true + | _ -> false + end) in - state.text <- ""; - state.mode <- Items (1, 0, makeitems (), "", 0, mode); - Glut.postRedisplay (); + fun () -> + let sep () = src#caption "" 0 in + let colorp name get set = + src#string name + (fun () -> color_to_string (get ())) + (fun v -> + try + let c = color_of_string v in + set c + with exn -> + state.text <- Printf.sprintf "bad color `%s': %s" + v (Printexc.to_string exn); + ) + in + let oldmode = state.mode in + let birdseye = isbirdseye state.mode in + state.text <- ""; + + src#caption (if birdseye then "Setup (Bird's eye)" else "Setup") 0; + + src#bool "presentation mode" + (fun () -> conf.presentation) + (fun v -> + conf.presentation <- v; + state.anchor <- getanchor (); + represent ()); + + src#bool "ignore case in searches" + (fun () -> conf.icase) + (fun v -> conf.icase <- v); + + src#bool "preload" + (fun () -> conf.preload) + (fun v -> conf.preload <- v); + + src#bool "throttle" + (fun () -> conf.showall) + (fun v -> conf.showall <- v); + + src#bool "highlight links" + (fun () -> conf.hlinks) + (fun v -> conf.hlinks <- v); + + src#bool "under info" + (fun () -> conf.underinfo) + (fun v -> conf.underinfo <- v); + + src#bool "persistent bookmarks" + (fun () -> conf.savebmarks) + (fun v -> conf.savebmarks <- v); + + src#bool "proportional display" + (fun () -> conf.proportional) + (fun v -> reqlayout conf.angle v); + + src#bool "trim margins" + (fun () -> conf.trimmargins) + (fun v -> settrim v conf.trimfuzz); + + src#bool "persistent location" + (fun () -> conf.jumpback) + (fun v -> conf.jumpback <- v); + + sep (); + src#int "vertical margin" + (fun () -> conf.interpagespace) + (fun n -> + conf.interpagespace <- n; + let pageno, py = + match state.layout with + | [] -> 0, 0 + | l :: _ -> + l.pageno, l.pagey + in + state.maxy <- calcheight (); + let y = getpagey pageno in + gotoy (y + py) + ); + + src#int "page bias" + (fun () -> conf.pagebias) + (fun v -> conf.pagebias <- v); + + src#int "scroll step" + (fun () -> conf.scrollstep) + (fun n -> conf.scrollstep <- n); + + src#int "auto scroll step" + (fun () -> + match state.autoscroll with + | Some step -> step + | _ -> conf.autoscrollstep) + (fun n -> + if state.autoscroll <> None + then state.autoscroll <- Some n; + conf.autoscrollstep <- n); + + src#int "zoom" + (fun () -> truncate (conf.zoom *. 100.)) + (fun v -> setzoom ((float v) /. 100.)); + + src#int "rotation" + (fun () -> conf.angle) + (fun v -> reqlayout v conf.proportional); + + src#int "scroll bar width" + (fun () -> state.scrollw) + (fun v -> + state.scrollw <- v; + conf.scrollbw <- v; + reshape conf.winw conf.winh; + ); + + src#int "scroll handle height" + (fun () -> conf.scrollh) + (fun v -> conf.scrollh <- v;); + + src#int "thumbnail width" + (fun () -> conf.thumbw) + (fun v -> + conf.thumbw <- min 4096 v; + match oldmode with + | Birdseye beye -> + leavebirdseye beye false; + enterbirdseye () + | _ -> () + ); + + sep (); + src#caption "Presentation mode" 0; + src#bool "scrollbar visible" + (fun () -> conf.scrollbarinpm) + (fun v -> + if v != conf.scrollbarinpm + then ( + conf.scrollbarinpm <- v; + if conf.presentation + then ( + state.scrollw <- if v then conf.scrollbw else 0; + reshape conf.winw conf.winh; + ) + ); + ); + + sep (); + src#caption "Pixmap cache" 0; + src#int_with_suffix "size (advisory)" + (fun () -> conf.memlimit) + (fun v -> conf.memlimit <- v); + + src#caption2 "used" + (fun () -> Printf.sprintf "%s bytes, %d tiles" + (string_with_suffix_of_int state.memused) + (Hashtbl.length state.tilemap)) 1; + + sep (); + src#caption "Layout" 0; + src#caption2 "Dimension" + (fun () -> + Printf.sprintf "%dx%d (virtual %dx%d)" + conf.winw conf.winh + state.w state.maxy) + 1; + if conf.debug + then + src#caption2 "Position" (fun () -> + Printf.sprintf "%dx%d" state.x state.y + ) 1 + else + src#caption2 "Visible" (fun () -> describe_location ()) 1 + ; + + sep (); + src#bool ~offset:0 ~btos:(fun v -> if v then "(on)" else "(off)") + "Save these parameters as global defaults at exit" + (fun () -> conf.bedefault) + (fun v -> conf.bedefault <- v) + ; + + sep (); + let btos b = if b then "\xc2\xab" else "\xc2\xbb" in + src#bool ~offset:0 ~btos "Extended parameters" + (fun () -> !showextended) + (fun v -> showextended := v; enterinfomode ()); + if !showextended + then ( + src#bool "checkers" + (fun () -> conf.checkers) + (fun v -> conf.checkers <- v; setcheckers v); + src#bool "verbose" + (fun () -> conf.verbose) + (fun v -> conf.verbose <- v); + src#bool "invert colors" + (fun () -> conf.invert) + (fun v -> conf.invert <- v); + src#bool "max fit" + (fun () -> conf.maxhfit) + (fun v -> conf.maxhfit <- v); + src#string "uri launcher" + (fun () -> conf.urilauncher) + (fun v -> conf.urilauncher <- v); + src#string "tile size" + (fun () -> Printf.sprintf "%dx%d" conf.tilew conf.tileh) + (fun v -> + try + let w, h = Scanf.sscanf v "%dx%d" (fun w h -> w, h) in + conf.tileh <- max 64 w; + conf.tilew <- max 64 h; + flushtiles (); + with exn -> + state.text <- Printf.sprintf "bad tile size `%s': %s" + v (Printexc.to_string exn)); + src#int "anti-aliasing level" + (fun () -> conf.aalevel) + (fun v -> + conf.aalevel <- bound v 0 8; + state.anchor <- getanchor (); + opendoc state.path state.password; + ); + src#int "ui font size" + (fun () -> !uifontsize) + (fun v -> uifontsize := bound v 5 100); + colorp "background color" + (fun () -> conf.bgcolor) + (fun v -> conf.bgcolor <- v); + src#bool "crop hack" + (fun () -> conf.crophack) + (fun v -> conf.crophack <- v); + src#string "trim fuzz" + (fun () -> irect_to_string conf.trimfuzz) + (fun v -> + try + conf.trimfuzz <- irect_of_string v; + if conf.trimmargins + then settrim true conf.trimfuzz; + with exn -> + state.text <- Printf.sprintf "bad irect `%s': %s" + v (Printexc.to_string exn) + ); + src#colorspace "color space" + (fun () -> colorspace_to_string conf.colorspace) + (fun v -> + conf.colorspace <- colorspace_of_int v; + wcmd "cs" [`i v]; + load state.layout; + ) + ); + + sep (); + src#caption "Document" 0; + List.iter (fun (_, s) -> src#caption s 1) state.docinfo; + + src#reset state.mode state.uioh; + let source = (src :> lvsource) in + state.uioh <- new listview ~source ~trusted:true; + G.postRedisplay "info"; ;; -let enterhelpmode () = - state.mode <- Items (-1, 0, state.help, "", 0, state.mode); - Glut.postRedisplay (); +let enterhelpmode = + let source = + (object + inherit lvsourcebase + method getitemcount = Array.length state.help + method getitem n = + let s, n, _ = state.help.(n) in + Some (s, n) + + method exit ~uioh ~cancel ~active ~first ~pan ~qsearch = + let optuioh = + if not cancel + then ( + m_qsearch <- qsearch; + match state.help.(active) with + | _, _, Action f -> Some (f uioh) + | _ -> Some (uioh) + ) + else None + in + m_active <- active; + m_first <- first; + m_pan <- pan; + optuioh + + method hasaction n = + match state.help.(n) with + | _, _, Action _ -> true + | _ -> false + + initializer + m_active <- -1 + end) + in fun () -> + state.uioh <- new listview ~source ~trusted:true; + G.postRedisplay "help"; ;; let quickbookmark ?title () = @@ -1646,34 +3382,25 @@ let doreshape w h = Glut.reshapeWindow w h; ;; -let writeopen path password = - writecmd state.csock ("open " ^ path ^ "\000" ^ password ^ "\000"); -;; - -let opendoc path password = - invalidate (); - state.path <- path; - state.password <- password; - state.gen <- state.gen + 1; - state.docinfo <- []; - - writeopen path password; - Glut.setWindowTitle ("llpp " ^ Filename.basename path); - wcmd "geometry" [`i state.w; `i conf.winh]; -;; - let viewkeyboard key = let enttext te = let mode = state.mode in state.mode <- Textentry (te, fun _ -> state.mode <- mode); state.text <- ""; enttext (); - Glut.postRedisplay () + G.postRedisplay "view:enttext" in let c = Char.chr key in match c with | '\027' | 'q' -> (* escape *) - raise Quit + begin match state.mstate with + | Mzoomrect _ -> + state.mstate <- Mnone; + Glut.setCursor Glut.CURSOR_INHERIT; + G.postRedisplay "kill zoom rect"; + | _ -> + raise Quit + end; | '\008' -> (* backspace *) let y = getnav ~-1 in @@ -1685,7 +3412,7 @@ let viewkeyboard key = | 'u' -> state.rects <- []; state.text <- ""; - Glut.postRedisplay () + G.postRedisplay "dehighlight"; | '/' | '?' -> let ondone isforw s = @@ -1725,7 +3452,7 @@ let viewkeyboard key = | '-' -> let ondone msg = state.text <- msg in enttext ( - "option [acfhilpstvAPRSZ]: ", "", None, + "option [acfhilpstvAPRSZTI]: ", "", None, optentry state.mode, ondone ) @@ -1770,7 +3497,7 @@ let viewkeyboard key = | 'l' -> conf.hlinks <- not conf.hlinks; state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off"; - Glut.postRedisplay () + G.postRedisplay "toggle highlightlinks"; | 'a' -> begin match state.autoscroll with @@ -1844,33 +3571,14 @@ let viewkeyboard key = end | '=' -> - let f (fn, _) 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 maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in - let percent = - if maxy <= 0 - then 100. - else (100. *. (float state.y /. float maxy)) 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; + showtext ' ' (describe_location ()); | 'w' -> begin match state.layout with | [] -> () | l :: _ -> doreshape (l.pagew + state.scrollw) l.pageh; - Glut.postRedisplay (); + G.postRedisplay "w" end | '\'' -> @@ -1910,12 +3618,14 @@ let viewkeyboard key = (truncate (rect.(1) -. rect.(0)), truncate (rect.(3) -. rect.(0))) in + let w = truncate ((float w)*.conf.zoom) + and h = truncate ((float h)*.conf.zoom) in if w != 0 && h != 0 then ( state.anchor <- getanchor (); doreshape (w + state.scrollw) (h + conf.interpagespace) ); - Glut.postRedisplay (); + G.postRedisplay "z"; | [] -> () end @@ -1926,13 +3636,13 @@ let viewkeyboard key = then setzoom (maxw /. float conf.winw) | '<' | '>' -> - reinit (conf.angle + (if c = '>' then 30 else -30)) conf.proportional + reqlayout (conf.angle + (if c = '>' then 30 else -30)) conf.proportional | '[' | ']' -> state.colorscale <- - max 0.0 - (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0); - Glut.postRedisplay () + bound (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 0.0 1.0 + ; + G.postRedisplay "brightness"; | 'k' -> begin match state.mode with @@ -1950,62 +3660,25 @@ let viewkeyboard key = state.anchor <- getanchor (); opendoc state.path state.password - | _ -> - vlog "huh? %d %c" key (Char.chr key); -;; - -let textentrykeyboard key ((c, text, opthist, onkey, ondone), onleave) = - let enttext te = - state.mode <- Textentry (te, onleave); - state.text <- ""; - enttext (); - Glut.postRedisplay () - in - match Char.unsafe_chr key with - | '\008' -> (* backspace *) - let len = String.length text in - if len = 0 - then ( - onleave Cancel; - Glut.postRedisplay (); - ) - else ( - let s = String.sub text 0 (len - 1) in - enttext (c, s, opthist, onkey, ondone) - ) - - | '\r' | '\n' -> - ondone text; - onleave Confirm; - Glut.postRedisplay () - - | '\007' (* ctrl-g *) - | '\027' -> (* escape *) - begin match opthist with - | None -> () - | Some (_, onhistcancel) -> onhistcancel () - end; - onleave Cancel; - Glut.postRedisplay () + | 'v' when conf.debug -> + state.rects <- []; + List.iter (fun l -> + match getopaque l.pageno with + | None -> () + | Some opaque -> + let x0, y0, x1, y1 = pagebbox opaque in + let a,b = float x0, float y0 in + let c,d = float x1, float y0 in + let e,f = float x1, float y1 in + let h,j = float x0, float y1 in + let rect = (a,b,c,d,e,f,h,j) in + debugrect rect; + state.rects <- (l.pageno, l.pageno mod 3, rect) :: state.rects; + ) state.layout; + G.postRedisplay "v"; | _ -> - begin match onkey text key with - | TEdone text -> - onleave Confirm; - ondone text; - Glut.postRedisplay () - - | TEcont text -> - enttext (c, text, opthist, onkey, ondone); - - | TEstop -> - onleave Cancel; - Glut.postRedisplay () - - | TEswitch te -> - state.mode <- Textentry (te, onleave); - Glut.postRedisplay () - end; + vlog "huh? %d %c" key (Char.chr key); ;; let birdseyekeyboard key ((_, _, pageno, _, _) as beye) = @@ -2025,334 +3698,12 @@ let birdseyekeyboard key ((_, _, pageno, _, _) as beye) = viewkeyboard key ;; -let itemskeyboard key (active, first, items, qsearch, pan, oldmode) = - let set active first qsearch = - state.mode <- Items (active, first, items, qsearch, pan, oldmode) - in - let search active pattern incr = - let dosearch re = - let rec loop n = - if n >= Array.length items || n < 0 - then None - else - let (s, _, _) = items.(n) in - if - (try ignore (Str.search_forward re s 0); true - with Not_found -> false) - then Some n - else loop (n + incr) - in - loop active - in - try - let re = Str.regexp_case_fold pattern in - dosearch re - with Failure s -> - state.text <- s; - None - in - let firstof active = max 0 (active - maxoutlinerows () / 2) in - match key with - | 18 | 19 -> (* ctrl-r/ctlr-s *) - let incr = if key = 18 then -1 else 1 in - let active, first = - match search (active + incr) qsearch incr with - | None -> - state.text <- qsearch ^ " [not found]"; - active, first - | Some active -> - state.text <- qsearch; - active, firstof active - in - set active first qsearch; - Glut.postRedisplay (); - - | 8 -> (* backspace *) - let len = String.length qsearch in - if len = 0 - then () - else ( - if len = 1 - then ( - state.text <- ""; - set active first ""; - ) - else - let qsearch = String.sub qsearch 0 (len - 1) in - let active, first = - match search active qsearch ~-1 with - | None -> - state.text <- qsearch ^ " [not found]"; - active, first - | Some active -> - state.text <- qsearch; - active, firstof active - in - set active first qsearch - ); - Glut.postRedisplay () - - | _ when key >= 32 && key < 127 -> - let pattern = addchar qsearch (Char.chr key) in - let active, first = - match search active pattern 1 with - | None -> - state.text <- pattern ^ " [not found]"; - active, first - | Some active -> - state.text <- pattern; - active, firstof active - in - set active first pattern; - Glut.postRedisplay () - - | 27 -> (* escape *) - state.text <- ""; - if String.length qsearch = 0 - then ( - state.mode <- oldmode; - ) - else ( - set active first ""; - ); - Glut.postRedisplay () - - | 13 -> (* enter *) - if active >= 0 && active < Array.length items - then ( - match items.(active) with - | _, _, Action f -> - state.mode <- f active first qsearch pan - - | _, _, Noaction -> - state.text <- ""; - state.mode <- oldmode - ) - else ( - state.text <- ""; - state.mode <- oldmode - ); - Glut.postRedisplay (); - - | _ -> dolog "unknown key %d" key -;; - -let outlinekeyboard key - (allowdel, active, first, outlines, qsearch, pan, oldmode) = - let narrow outlines pattern = - let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in - match reopt with - | None -> None - | Some re -> - let rec fold accu n = - if n = -1 - then accu - else - let (s, _, _) as o = outlines.(n) in - let accu = - if (try ignore (Str.search_forward re s 0); true - with Not_found -> false) - then (o :: accu) - else accu - in - fold accu (n-1) - in - let matched = fold [] (Array.length outlines - 1) in - if matched = [] then None else Some (Array.of_list matched) - in - let search active pattern incr = - let dosearch re = - let rec loop n = - if n = Array.length outlines || n = -1 - then None - else - let (s, _, _) = outlines.(n) in - if - (try ignore (Str.search_forward re s 0); true - with Not_found -> false) - then Some n - else loop (n + incr) - in - loop active - in - try - let re = Str.regexp_case_fold pattern in - dosearch re - with Failure s -> - state.text <- s; - None - in - let firstof active = max 0 (active - maxoutlinerows () / 2) in - match key with - | 27 -> (* escape *) - state.text <- ""; - if String.length qsearch = 0 - then ( - state.mode <- oldmode; - ) - else ( - state.mode <- Outline ( - allowdel, active, first, outlines, "", pan, oldmode - ); - ); - Glut.postRedisplay (); - - | 18 | 19 -> (* ctrl-r/ctrl-s *) - let incr = if key = 18 then -1 else 1 in - let active, first = - match search (active + incr) qsearch incr with - | None -> - state.text <- qsearch ^ " [not found]"; - active, first - | Some active -> - state.text <- qsearch; - active, firstof active - in - state.mode <- Outline ( - allowdel, active, first, outlines, qsearch, pan, oldmode - ); - Glut.postRedisplay (); - - | 8 -> (* backspace *) - let len = String.length qsearch in - if len = 0 - then () - else ( - if len = 1 - then ( - state.text <- ""; - state.mode <- Outline ( - allowdel, active, first, outlines, "", pan, oldmode - ); - ) - else - let qsearch = String.sub qsearch 0 (len - 1) in - let active, first = - match search active qsearch ~-1 with - | None -> - state.text <- qsearch ^ " [not found]"; - active, first - | Some active -> - state.text <- qsearch; - active, firstof active - in - state.mode <- Outline ( - allowdel, active, first, outlines, qsearch, pan, oldmode - ); - ); - Glut.postRedisplay () - - | 13 -> (* enter *) - if active < Array.length outlines - then ( - let (_, _, anchor) = outlines.(active) in - addnav (); - gotoanchor anchor; - ); - state.text <- ""; - if allowdel then state.bookmarks <- Array.to_list outlines; - state.mode <- oldmode; - Glut.postRedisplay (); - - | _ when key >= 32 && key < 127 -> - let pattern = addchar qsearch (Char.chr key) in - let active, first = - match search active pattern 1 with - | None -> - state.text <- pattern ^ " [not found]"; - active, first - | Some active -> - state.text <- pattern; - active, firstof active - in - state.mode <- Outline ( - allowdel, active, first, outlines, pattern, pan, oldmode - ); - Glut.postRedisplay () - - | 14 when not allowdel -> (* ctrl-n *) - if String.length qsearch > 0 - then ( - let optoutlines = narrow outlines qsearch in - begin match optoutlines with - | None -> state.text <- "can't narrow" - | Some outlines -> - state.mode <- Outline ( - allowdel, 0, 0, outlines, qsearch, pan, oldmode - ); - match state.outlines with - | Olist _ -> () - | Oarray a -> - state.outlines <- Onarrow (qsearch, outlines, a) - | Onarrow (_, _, b) -> - state.outlines <- Onarrow (qsearch, outlines, b) - end; - ); - Glut.postRedisplay () - - | 21 when not allowdel -> (* ctrl-u *) - let outline = - match state.outlines with - | Oarray a -> a - | Olist l -> - let a = Array.of_list (List.rev l) in - state.outlines <- Oarray a; - a - | Onarrow (_, _, b) -> - state.outlines <- Oarray b; - state.text <- ""; - b - in - state.mode <- Outline (allowdel, 0, 0, outline, qsearch, pan, oldmode); - Glut.postRedisplay () - - | 12 -> (* ctrl-l *) - state.mode <- Outline - (allowdel, active, firstof active, outlines, qsearch, pan, oldmode); - Glut.postRedisplay () - - | 127 when allowdel -> (* delete *) - let len = Array.length outlines - 1 in - if len = 0 - then ( - state.mode <- View; - state.bookmarks <- []; - ) - else ( - let bookmarks = Array.init len - (fun i -> - let i = if i >= active then i + 1 else i in - outlines.(i) - ) - in - state.mode <- - Outline ( - allowdel, - min active (len-1), - min first (len-1), - bookmarks, qsearch, - 0, - oldmode - ); - ); - Glut.postRedisplay () - - | _ -> dolog "unknown key %d" key -;; - let keyboard ~key ~x ~y = - ignore x; - ignore y; - if key = 7 && not (istextentry state.mode) (* ctrl-g *) - then - wcmd "interrupt" [] - else - match state.mode with - | Outline outline -> outlinekeyboard key outline - | Textentry textentry -> textentrykeyboard key textentry - | Birdseye birdseye -> birdseyekeyboard key birdseye - | View -> viewkeyboard key - | Items items -> itemskeyboard key items + ignore x; + ignore y; + if key = 7 && not (istextentry state.mode) (* ctrl-g *) + then wcmd "interrupt" [] + else state.uioh <- state.uioh#key key ;; let birdseyespecial key ((conf, leftx, _, hooverpageno, anchor) as beye) = @@ -2397,7 +3748,7 @@ let birdseyespecial key ((conf, leftx, _, hooverpageno, anchor) as beye) = Birdseye ( conf, leftx, state.pagecount - 1, hooverpageno, anchor ); - Glut.postRedisplay (); + G.postRedisplay "birdseye pagedown"; ) else gotoy (clamp (incr + conf.interpagespace*2)); @@ -2425,7 +3776,7 @@ let birdseyespecial key ((conf, leftx, _, hooverpageno, anchor) as beye) = | (_, _, h, _) :: _ -> h in gotoy (max 0 (getpagey pageno - (conf.winh - h - conf.interpagespace))) - else Glut.postRedisplay (); + else G.postRedisplay "birdseye end"; | _ -> () ;; @@ -2439,284 +3790,14 @@ let setautoscrollspeed step goingdown = let special ~key ~x ~y = ignore x; ignore y; - match state.mode with - | View | (Birdseye _) when key = Glut.KEY_F9 -> - togglebirdseye () - - | Birdseye vals -> - birdseyespecial key vals - - | View when key = Glut.KEY_F1 -> - enterhelpmode () - - | View -> - begin match state.autoscroll with - | Some step when key = Glut.KEY_DOWN || key = Glut.KEY_UP -> - setautoscrollspeed step (key = Glut.KEY_DOWN) - - | _ -> - let y = - match key with - | Glut.KEY_F3 -> search state.searchpattern true; state.y - | Glut.KEY_UP -> clamp (-conf.scrollstep) - | Glut.KEY_DOWN -> clamp conf.scrollstep - | Glut.KEY_PAGE_UP -> - if Glut.getModifiers () land Glut.active_ctrl != 0 - then - match state.layout with - | [] -> state.y - | l :: _ -> state.y - l.pagey - else - clamp (-conf.winh) - | Glut.KEY_PAGE_DOWN -> - if Glut.getModifiers () land Glut.active_ctrl != 0 - then - match List.rev state.layout with - | [] -> state.y - | l :: _ -> getpagey l.pageno - else - clamp conf.winh - | Glut.KEY_HOME -> addnav (); 0 - | Glut.KEY_END -> - addnav (); - state.maxy - (if conf.maxhfit then conf.winh else 0) - - | (Glut.KEY_RIGHT | Glut.KEY_LEFT) when - Glut.getModifiers () land Glut.active_alt != 0 -> - getnav (if key = Glut.KEY_LEFT then 1 else -1) - - | Glut.KEY_RIGHT when conf.zoom > 1.0 -> - state.x <- state.x - 10; - state.y - | Glut.KEY_LEFT when conf.zoom > 1.0 -> - state.x <- state.x + 10; - state.y - - | _ -> state.y - in - gotoy_and_clear_text y - end - - | Textentry - ((c, _, (Some (action, _) as onhist), onkey, ondone), mode) -> - let s = - match key with - | Glut.KEY_UP -> action HCprev - | Glut.KEY_DOWN -> action HCnext - | Glut.KEY_HOME -> action HCfirst - | Glut.KEY_END -> action HClast - | _ -> state.text - in - state.mode <- Textentry ((c, s, onhist, onkey, ondone), mode); - Glut.postRedisplay () - - | Textentry _ -> () - - | Items (active, first, items, qsearch, pan, oldmode) -> - let maxrows = maxoutlinerows () in - let itemcount = Array.length items in - let hasaction = function - | (_, _, Noaction) -> false - | _ -> true - in - let find start incr = - let rec find i = - if i = -1 || i = itemcount - then -1 - else ( - if hasaction items.(i) - then i - else find (i + incr) - ) - in - find start - in - let set active first = - let first = max 0 (min first (itemcount - maxrows)) in - state.mode <- Items (active, first, items, qsearch, pan, oldmode) - in - let navigate incr = - let isvisible first n = n >= first && n - first <= maxrows in - let active, first = - let incr1 = if incr > 0 then 1 else -1 in - if isvisible first active - then - let next = - let next = active + incr in - let next = - if next < 0 || next >= itemcount - then -1 - else find next incr1 - in - if next = -1 || abs (active - next) > maxrows - then -1 - else next - in - if next = -1 - then - let first = first + incr in - let first = max 0 (min first (itemcount - 1)) in - let next = - let next = active + incr in - let next = max 0 (min next (itemcount - 1)) in - find next ~-incr1 - in - let active = if next = -1 then active else next in - active, first - else - let first = min next first in - next, first - else - let first = first + incr in - let first = max 0 (min first (itemcount - 1)) in - let active = - let next = active + incr in - let next = max 0 (min next (itemcount - 1)) in - let next = find next incr1 in - if next = -1 || abs (active - first) > maxrows - then active - else next - in - active, first - in - set active first; - Glut.postRedisplay () - in - begin match key with - | Glut.KEY_UP -> navigate ~-1 - | Glut.KEY_DOWN -> navigate 1 - | Glut.KEY_PAGE_UP -> navigate ~-maxrows - | Glut.KEY_PAGE_DOWN -> navigate maxrows - - | Glut.KEY_RIGHT -> - state.mode <- Items ( - active, first, items, qsearch, min 0 (pan - 1), oldmode - ); - Glut.postRedisplay () - - | Glut.KEY_LEFT -> - state.mode <- Items ( - active, first, items, qsearch, min 0 (pan + 1), oldmode - ); - Glut.postRedisplay () - - | Glut.KEY_HOME -> - let active = find 0 1 in - set active 0; - Glut.postRedisplay () - - | Glut.KEY_END -> - let first = max 0 (itemcount - maxrows) in - let active = find (itemcount - 1) ~-1 in - set active first; - Glut.postRedisplay () - - | _ -> () - end; - - | Outline (allowdel, active, first, outlines, qsearch, pan, oldmode) -> - let maxrows = maxoutlinerows () in - let calcfirst first active = - if active > first - then - let rows = active - first in - if rows > maxrows then active - maxrows else first - else active - in - let navigate incr = - let active = active + incr in - let active = max 0 (min active (Array.length outlines - 1)) in - let first = calcfirst first active in - state.mode <- Outline ( - allowdel, active, first, outlines, qsearch, pan, oldmode - ); - Glut.postRedisplay () - in - let updownlevel incr = - let len = Array.length outlines in - let (_, curlevel, _) = outlines.(active) in - let rec flow i = - if i = len then i-1 else if i = -1 then 0 else - let (_, l, _) = outlines.(i) in - if l != curlevel then i else flow (i+incr) - in - let active = flow active in - let first = calcfirst first active in - state.mode <- Outline ( - allowdel, active, first, outlines, qsearch, pan, oldmode - ); - Glut.postRedisplay () - in - match key with - | Glut.KEY_UP -> navigate ~-1 - | Glut.KEY_DOWN -> navigate 1 - | Glut.KEY_PAGE_UP -> navigate ~-maxrows - | Glut.KEY_PAGE_DOWN -> navigate maxrows - - | Glut.KEY_RIGHT -> - if Glut.getModifiers () land Glut.active_ctrl != 0 - then ( - state.mode <- Outline ( - allowdel, active, first, outlines, - qsearch, min 0 (pan + 1), oldmode - ); - Glut.postRedisplay (); - ) - else ( - if not allowdel - then updownlevel 1 - ) - - | Glut.KEY_LEFT -> - if Glut.getModifiers () land Glut.active_ctrl != 0 - then ( - state.mode <- Outline ( - allowdel, active, first, outlines, qsearch, pan - 1, oldmode - ); - Glut.postRedisplay (); - ) - else ( - if not allowdel - then updownlevel ~-1 - ) - - | Glut.KEY_HOME -> - state.mode <- Outline ( - allowdel, 0, 0, outlines, qsearch, pan, oldmode - ); - Glut.postRedisplay () - - | Glut.KEY_END -> - let active = Array.length outlines - 1 in - let first = max 0 (active - maxrows) in - state.mode <- Outline ( - allowdel, active, first, outlines, qsearch, pan, oldmode - ); - Glut.postRedisplay () - - | _ -> () -;; - -let drawplaceholder l = - GlDraw.color (1.0, 1.0, 1.0); - let margin = state.x + (conf.winw - (state.w + state.scrollw)) / 2 in - GlDraw.rect - (float l.pagex, float l.pagedispy) - (float (l.pagew + l.pagex), float (l.pagedispy + l.pagevh)) - ; - let x = if margin < 0 then -margin else l.pagex - and y = l.pagedispy + 13 in - GlDraw.color (0.0, 0.0, 0.0); - drawstring 13 x y ("Loading " ^ string_of_int (l.pageno + 1)) + state.uioh <- state.uioh#special key ;; -let now () = Unix.gettimeofday ();; - let drawpage l = let color = match state.mode with | Textentry _ -> scalecolor 0.4 - | View | Outline _ | Items _ -> scalecolor 1.0 + | View -> scalecolor 1.0 | Birdseye (_, _, pageno, hooverpageno, _) -> if l.pageno = hooverpageno then scalecolor 0.9 @@ -2726,28 +3807,27 @@ let drawpage l = else scalecolor 0.8 ) in - GlDraw.color color; + drawtiles l color; begin match getopaque l.pageno with - | Some (opaque, _) when validopaque opaque -> - let a = now () in - draw (l.pagedispy, l.pagevh, l.pagey, conf.hlinks) opaque; - let b = now () in - let d = b-.a in - vlog "draw %d %f sec" l.pageno d; + | Some opaque -> + if tileready l l.pagex l.pagey + then + let x = l.pagedispx - l.pagex + and y = l.pagedispy - l.pagey in + postprocess opaque conf.hlinks x y; - | _ -> - drawplaceholder l; + | _ -> () end; ;; let scrollph y = let maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in - let sh = (float (maxy + conf.winh) /. float conf.winh) in + let sh = (float (maxy + conf.winh) /. float conf.winh) in let sh = float conf.winh /. sh in let sh = max sh (float conf.scrollh) in let percent = - if state.y = state.maxy + if y = state.maxy then 1.0 else float y /. float maxy in @@ -2761,12 +3841,38 @@ let scrollph y = position, sh; ;; +let scrollpw x = + let winw = conf.winw - state.scrollw - 1 in + let fwinw = float winw in + let sw = + let sw = fwinw /. float state.w in + let sw = fwinw *. sw in + max sw (float conf.scrollh) + in + let position, sw = + let f = state.w+winw in + let r = float (winw-x) /. float f in + let p = fwinw *. r in + p-.sw/.2., sw + in + let sw = + if position +. sw > fwinw + then fwinw -. position + else sw + in + position, sw; +;; + let scrollindicator () = GlDraw.color (0.64 , 0.64, 0.64); GlDraw.rect (float (conf.winw - state.scrollw), 0.) (float conf.winw, float conf.winh) ; + GlDraw.rect + (0., float (conf.winh - state.hscrollh)) + (float (conf.winw - state.scrollw - 1), float conf.winh) + ; GlDraw.color (0.0, 0.0, 0.0); let position, sh = scrollph state.y in @@ -2774,11 +3880,24 @@ let scrollindicator () = (float (conf.winw - state.scrollw), position) (float conf.winw, position +. sh) ; + let position, sw = scrollpw state.x in + GlDraw.rect + (position, float (conf.winh - state.hscrollh)) + (position +. sw, float conf.winh) + ; +;; + +let pagetranslatepoint l x y = + let dy = y - l.pagedispy in + let y = dy + l.pagey in + let dx = x - l.pagedispx in + let x = dx + l.pagex in + (x, y); ;; -let showsel margin = +let showsel () = match state.mstate with - | Mnone | Mscroll _ | Mpan _ | Mzoom _ -> + | Mnone | Mscrolly | Mscrollx | Mpan _ | Mzoom _ | Mzoomrect _ -> () | Msel ((x0, y0), (x1, y1)) -> @@ -2788,12 +3907,17 @@ let showsel margin = || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh))) then match getopaque l.pageno with - | Some (opaque, _) when validopaque opaque -> - let oy = -l.pagey + l.pagedispy in - seltext opaque - (x0 - margin - state.x, y0, - x1 - margin - state.x, y1) oy; - () + | Some opaque -> + let dx, dy = pagetranslatepoint l 0 0 in + let x0 = x0 + dx + and y0 = y0 + dy + and x1 = x1 + dx + and y1 = y1 + dy in + GlMat.mode `modelview; + GlMat.push (); + GlMat.translate ~x:(float ~-dx) ~y:(float ~-dy) (); + seltext opaque (x0, y0, x1, y1); + GlMat.pop (); | _ -> () else loop ls | [] -> () @@ -2802,23 +3926,24 @@ let showsel margin = ;; let showrects () = - let panx = float state.x in Gl.enable `blend; GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5; + GlDraw.polygon_mode `both `fill; GlFunc.blend_func `src_alpha `one_minus_src_alpha; List.iter (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) -> List.iter (fun l -> if l.pageno = pageno then ( - let d = float (l.pagedispy - l.pagey) in + let dx = float (l.pagedispx - l.pagex) in + let dy = float (l.pagedispy - l.pagey) in GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5; GlDraw.begins `quads; ( - GlDraw.vertex2 (x0+.panx, y0+.d); - GlDraw.vertex2 (x1+.panx, y1+.d); - GlDraw.vertex2 (x2+.panx, y2+.d); - GlDraw.vertex2 (x3+.panx, y3+.d); + GlDraw.vertex2 (x0+.dx, y0+.dy); + GlDraw.vertex2 (x1+.dx, y1+.dy); + GlDraw.vertex2 (x2+.dx, y2+.dy); + GlDraw.vertex2 (x3+.dx, y3+.dy); ); GlDraw.ends (); ) @@ -2828,101 +3953,23 @@ let showrects () = Gl.disable `blend; ;; -let showstrings trusted active first pan strings = - Gl.enable `blend; - GlFunc.blend_func `src_alpha `one_minus_src_alpha; - GlDraw.color (0., 0., 0.) ~alpha:0.85; - GlDraw.rect (0., 0.) (float conf.winw, float conf.winh); - GlDraw.color (1., 1., 1.); - Gl.enable `texture_2d; - - let wx = measurestr 15 "w" in - let tabx = 30.0*.wx +. float (pan*15) in - let rec loop row = - if row = Array.length strings || (row - first) * 16 > conf.winh - then () - else ( - let (s, level, _) = strings.(row) in - let y = (row - first) * 16 in - let x = 5 + 15*(max 0 (level+pan)) in - if row = active - then ( - Gl.disable `texture_2d; - GlDraw.polygon_mode `both `line; - GlDraw.color (1., 1., 1.) ~alpha:0.9; - GlDraw.rect (1., float (y + 1)) - (float (conf.winw - 1), float (y + 18)); - GlDraw.polygon_mode `both `fill; - GlDraw.color (1., 1., 1.); - Gl.enable `texture_2d; - ); - - let drawtabularstring x s = - let _ = - if trusted - then - let tabpos = try String.index s '\t' with Not_found -> -1 in - if tabpos > 0 - then - let len = String.length s - tabpos - 1 in - let s1 = String.sub s 0 tabpos - and s2 = String.sub s (tabpos + 1) len in - let xx = wx +. drawstring1 14 x (y + 16) s1 in - let x = truncate (max xx tabx) in - drawstring1 15 x (y + 16) s2 - else - drawstring1 15 x (y + 16) s - else - drawstring1 15 x (y + 16) s - in - () - in - drawtabularstring (x + pan*15) s; - loop (row+1) - ) - in - loop first; - Gl.disable `blend; - Gl.disable `texture_2d; -;; - -let showoutline (_, active, first, outlines, _, pan, _) = - showstrings false active first pan outlines; -;; - -let showitems (active, first, items, _, pan, _) = - showstrings true active first pan items; -;; - let display () = - let margin = (conf.winw - (state.w + state.scrollw)) / 2 in - GlDraw.viewport margin 0 state.w conf.winh; - pagematrix (); GlClear.color (scalecolor2 conf.bgcolor); GlClear.clear [`color]; - if conf.zoom > 1.0 - then ( - Gl.enable `scissor_test; - GlMisc.scissor 0 0 (conf.winw - state.scrollw) conf.winh; - ); List.iter drawpage state.layout; - if conf.zoom > 1.0 - then - Gl.disable `scissor_test - ; - if state.x != 0 - then ( - let x = -.float state.x in - GlMat.translate ~x (); - ); showrects (); - showsel margin; - GlDraw.viewport 0 0 conf.winw conf.winh; - winmatrix (); + showsel (); scrollindicator (); - begin match state.mode with - | Items items -> showitems items - | Outline outline -> showoutline outline + state.uioh#display; + begin match state.mstate with + | Mzoomrect ((x0, y0), (x1, y1)) -> + Gl.enable `blend; + GlDraw.color (0.3, 0.3, 0.3) ~alpha:0.5; + GlDraw.polygon_mode `both `fill; + GlFunc.blend_func `src_alpha `one_minus_src_alpha; + GlDraw.rect (float x0, float y0) + (float x1, float y1); + Gl.disable `blend; | _ -> () end; enttext (); @@ -2930,22 +3977,21 @@ let display () = ;; let getunder x y = - let margin = (conf.winw - (state.w + state.scrollw)) / 2 in - let x = x - margin - state.x in 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 + | Some opaque -> + let x0 = l.pagedispx in + let x1 = x0 + l.pagevw in + let y0 = l.pagedispy in + let y1 = y0 + l.pagevh in + if y >= y0 && y <= y1 && x >= x0 && x <= x1 then - let y = l.pagey + y in - let x = x - l.pagex in - match whatsunder opaque x y with + let px, py = pagetranslatepoint l x y in + match whatsunder opaque px py with | Unone -> f rest | under -> under - else - f rest + else f rest | _ -> f rest end @@ -2954,6 +4000,35 @@ let getunder x y = f state.layout ;; +let zoomrect x y x1 y1 = + let x0 = min x x1 + and x1 = max x x1 + and y0 = min y y1 in + gotoy (state.y + y0); + state.anchor <- getanchor (); + let zoom = (float conf.winw *. conf.zoom) /. float (x1 - x0) in + state.x <- state.x - x0; + setzoom zoom; + Glut.setCursor Glut.CURSOR_INHERIT; + state.mstate <- Mnone; +;; + +let scrollx x = + let winw = conf.winw - state.scrollw - 1 in + let s = float x /. float winw in + let destx = truncate (float (state.w + winw) *. s) in + state.x <- winw - destx; + gotoy_and_clear_text state.y; + state.mstate <- Mscrollx; +;; + +let scrolly y = + let s = float y /. float conf.winh in + let desty = truncate (float (state.maxy - conf.winh) *. s) in + gotoy_and_clear_text desty; + state.mstate <- Mscrolly; +;; + let viewmouse button bstate x y = match button with | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP -> @@ -2972,7 +4047,7 @@ let viewmouse button bstate x y = | _ -> if conf.zoom -. 0.1 < 0.1 then -0.01 else -0.1 in - let zoom = conf.zoom +. incr in + let zoom = conf.zoom -. incr in setzoom zoom; state.mstate <- Mzoom (n, 0); else @@ -3005,18 +4080,38 @@ let viewmouse button bstate x y = else state.mstate <- Mnone + | Glut.RIGHT_BUTTON -> + if bstate = Glut.DOWN + then ( + Glut.setCursor Glut.CURSOR_CYCLE; + let p = (x, y) in + state.mstate <- Mzoomrect (p, p) + ) + else ( + match state.mstate with + | Mzoomrect ((x0, y0), _) -> zoomrect x0 y0 x y + | _ -> + Glut.setCursor Glut.CURSOR_INHERIT; + state.mstate <- Mnone + ) + | Glut.LEFT_BUTTON when x > conf.winw - state.scrollw -> if bstate = Glut.DOWN then let position, sh = scrollph state.y in if y > truncate position && y < truncate (position +. sh) - then - state.mstate <- Mscroll - else - let percent = float y /. float conf.winh in - let desty = truncate (float (state.maxy - conf.winh) *. percent) in - gotoy desty; - state.mstate <- Mscroll + then state.mstate <- Mscrolly + else scrolly y + else + state.mstate <- Mnone + + | Glut.LEFT_BUTTON when y > conf.winh - state.hscrollh -> + if bstate = Glut.DOWN + then + let position, sw = scrollpw state.x in + if x > truncate position && x < truncate (position +. sw) + then state.mstate <- Mscrollx + else scrollx x else state.mstate <- Mnone @@ -3031,7 +4126,7 @@ let viewmouse button bstate x y = ) | Ulinkuri s -> - print_endline s + gotouri s | Unone when bstate = Glut.DOWN -> Glut.setCursor Glut.CURSOR_CROSSHAIR; @@ -3043,16 +4138,19 @@ let viewmouse button bstate x y = if conf.angle mod 360 = 0 then ( state.mstate <- Msel ((x, y), (x, y)); - Glut.postRedisplay () + G.postRedisplay "mouse select"; ) ) else ( match state.mstate with | Mnone -> () - | Mzoom _ | Mscroll -> + | Mzoom _ | Mscrollx | Mscrolly -> state.mstate <- Mnone + | Mzoomrect ((x0, y0), _) -> + zoomrect x0 y0 x y + | Mpan _ -> Glut.setCursor Glut.CURSOR_INHERIT; state.mstate <- Mnone @@ -3063,7 +4161,7 @@ let viewmouse button bstate x y = || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh))) then match getopaque l.pageno with - | Some (opaque, _) when validopaque opaque -> + | Some opaque -> copysel opaque | _ -> () in @@ -3098,94 +4196,223 @@ let birdseyemouse button bstate x y ;; let mouse bstate button x y = - match state.mode with - | View -> viewmouse button bstate x y - | Birdseye beye -> birdseyemouse button bstate x y beye - | Textentry _ | Outline _ | Items _ -> () + state.uioh <- state.uioh#button button bstate x y; ;; let mouse ~button ~state ~x ~y = mouse state button x y;; let motion ~x ~y = - match state.mode with - | Outline _ -> () - | _ -> - match state.mstate with - | Mzoom _ | Mnone -> () + state.uioh <- state.uioh#motion x y +;; - | Mpan (x0, y0) -> - let dx = x - x0 - and dy = y0 - y in - state.mstate <- Mpan (x, y); - if conf.zoom > 1.0 then state.x <- state.x + dx; - let y = clamp dy in - gotoy_and_clear_text y +let pmotion ~x ~y = + state.uioh <- state.uioh#pmotion x y; +;; - | Msel (a, _) -> - state.mstate <- Msel (a, (x, y)); - Glut.postRedisplay () +let uioh = object + method display = () - | Mscroll -> - let y = min conf.winh (max 0 y) in - let percent = float y /. float conf.winh in - let y = truncate (float (state.maxy - conf.winh) *. percent) in - gotoy_and_clear_text y -;; + method key key = + begin match state.mode with + | Textentry textentry -> textentrykeyboard key textentry + | Birdseye birdseye -> birdseyekeyboard key birdseye + | View -> viewkeyboard key + end; + state.uioh -let pmotion ~x ~y = - match state.mode with - | Birdseye (conf, leftx, pageno, hooverpageno, anchor) -> - let margin = (conf.winw - (state.w + state.scrollw)) / 2 in - let rec loop = function - | [] -> - if hooverpageno != -1 - then ( - state.mode <- Birdseye (conf, leftx, pageno, -1, anchor); - Glut.postRedisplay (); - ) - | l :: rest -> - if y > l.pagedispy && y < l.pagedispy + l.pagevh - && x > margin && x < margin + l.pagew - then ( - state.mode <- Birdseye (conf, leftx, pageno, l.pageno, anchor); - Glut.postRedisplay (); - ) - else loop rest - in - loop state.layout + method special key = + begin match state.mode with + | View | (Birdseye _) when key = Glut.KEY_F9 -> + togglebirdseye () - | Outline _ | Items _ | Textentry _ -> () - | View -> - match state.mstate with - | Mnone -> - begin match getunder x y with - | Unone -> Glut.setCursor Glut.CURSOR_INHERIT - | Ulinkuri uri -> - if conf.underinfo then showtext 'u' ("ri: " ^ uri); - Glut.setCursor Glut.CURSOR_INFO - | Ulinkgoto (page, _) -> - if conf.underinfo - then showtext 'p' ("age: " ^ string_of_int page); - Glut.setCursor Glut.CURSOR_INFO - | Utext s -> - if conf.underinfo then showtext 'f' ("ont: " ^ s); - Glut.setCursor Glut.CURSOR_TEXT - end + | Birdseye vals -> + birdseyespecial key vals - | Mpan _ | Msel _ | Mzoom _ | Mscroll -> - () -;; + | View when key = Glut.KEY_F1 -> + enterhelpmode () + + | View -> + begin match state.autoscroll with + | Some step when key = Glut.KEY_DOWN || key = Glut.KEY_UP -> + setautoscrollspeed step (key = Glut.KEY_DOWN) + + | _ -> + let y = + match key with + | Glut.KEY_F3 -> search state.searchpattern true; state.y + | Glut.KEY_UP -> + if Glut.getModifiers () land Glut.active_ctrl != 0 + then + if Glut.getModifiers () land Glut.active_shift != 0 + then (setzoom state.prevzoom; state.y) + else clamp (-conf.winh/2) + else clamp (-conf.scrollstep) + | Glut.KEY_DOWN -> + if Glut.getModifiers () land Glut.active_ctrl != 0 + then + if Glut.getModifiers () land Glut.active_shift != 0 + then (setzoom state.prevzoom; state.y) + else clamp (conf.winh/2) + else clamp (conf.scrollstep) + | Glut.KEY_PAGE_UP -> + if Glut.getModifiers () land Glut.active_ctrl != 0 + then + match state.layout with + | [] -> state.y + | l :: _ -> state.y - l.pagey + else + clamp (-conf.winh) + | Glut.KEY_PAGE_DOWN -> + if Glut.getModifiers () land Glut.active_ctrl != 0 + then + match List.rev state.layout with + | [] -> state.y + | l :: _ -> getpagey l.pageno + else + clamp conf.winh + | Glut.KEY_HOME -> + addnav (); + 0 + | Glut.KEY_END -> + addnav (); + state.maxy - (if conf.maxhfit then conf.winh else 0) + + | (Glut.KEY_RIGHT | Glut.KEY_LEFT) when + Glut.getModifiers () land Glut.active_alt != 0 -> + getnav (if key = Glut.KEY_LEFT then 1 else -1) + + | Glut.KEY_RIGHT when conf.zoom > 1.0 -> + let dx = + if Glut.getModifiers () land Glut.active_ctrl != 0 + then (conf.winw / 2) + else 10 + in + state.x <- state.x - dx; + state.y + | Glut.KEY_LEFT when conf.zoom > 1.0 -> + let dx = + if Glut.getModifiers () land Glut.active_ctrl != 0 + then (conf.winw / 2) + else 10 + in + state.x <- state.x + dx; + state.y + + | _ -> state.y + in + gotoy_and_clear_text y + end + + | Textentry te -> textentryspecial key te + end; + state.uioh + + method button button bstate x y = + begin match state.mode with + | View -> viewmouse button bstate x y + | Birdseye beye -> birdseyemouse button bstate x y beye + | Textentry _ -> () + end; + state.uioh + + method motion x y = + begin match state.mode with + | Textentry _ -> () + | View | Birdseye _ -> + match state.mstate with + | Mzoom _ | Mnone -> () + + | Mpan (x0, y0) -> + let dx = x - x0 + and dy = y0 - y in + state.mstate <- Mpan (x, y); + if conf.zoom > 1.0 then state.x <- state.x + dx; + let y = clamp dy in + gotoy_and_clear_text y + + | Msel (a, _) -> + state.mstate <- Msel (a, (x, y)); + G.postRedisplay "motion select"; + + | Mscrolly -> + let y = min conf.winh (max 0 y) in + scrolly y + + | Mscrollx -> + let x = min conf.winw (max 0 x) in + scrollx x + + | Mzoomrect (p0, _) -> + state.mstate <- Mzoomrect (p0, (x, y)); + G.postRedisplay "motion zoomrect"; + end; + state.uioh + + method pmotion x y = + begin match state.mode with + | Birdseye (conf, leftx, pageno, hooverpageno, anchor) -> + let margin = (conf.winw - (state.w + state.scrollw)) / 2 in + let rec loop = function + | [] -> + if hooverpageno != -1 + then ( + state.mode <- Birdseye (conf, leftx, pageno, -1, anchor); + G.postRedisplay "pmotion birdseye no hoover"; + ) + | l :: rest -> + if y > l.pagedispy && y < l.pagedispy + l.pagevh + && x > margin && x < margin + l.pagew + then ( + state.mode <- Birdseye (conf, leftx, pageno, l.pageno, anchor); + G.postRedisplay "pmotion birdseye hoover"; + ) + else loop rest + in + loop state.layout -module State = + | Textentry _ -> () + + | View -> + match state.mstate with + | Mnone -> + begin match getunder x y with + | Unone -> Glut.setCursor Glut.CURSOR_INHERIT + | Ulinkuri uri -> + if conf.underinfo then showtext 'u' ("ri: " ^ uri); + Glut.setCursor Glut.CURSOR_INFO + | Ulinkgoto (page, _) -> + if conf.underinfo + then showtext 'p' ("age: " ^ string_of_int (page+1)); + Glut.setCursor Glut.CURSOR_INFO + | Utext s -> + if conf.underinfo then showtext 'f' ("ont: " ^ s); + Glut.setCursor Glut.CURSOR_TEXT + end + + | Mpan _ | Msel _ | Mzoom _ | Mscrolly | Mscrollx | Mzoomrect _ -> + () + end; + state.uioh +end;; + +module Config = struct open Parser let fontpath = ref "";; + let wmclasshack = ref false;; + + let unent s = + let l = String.length s in + let b = Buffer.create l in + unent b s 0 l; + Buffer.contents b; + ;; let home = try - match Sys.os_type with - | "Win32" -> Sys.getenv "HOMEPATH" + match platform with + | Pwindows | Pmingw -> Sys.getenv "HOMEPATH" | _ -> Sys.getenv "HOME" with exn -> prerr_endline @@ -3215,7 +4442,7 @@ struct { c with interpagespace = max 0 (int_of_string v) } | "zoom" -> let zoom = float_of_string v /. 100. in - let zoom = max 0.01 zoom in + let zoom = max zoom 0.0 in { c with zoom = zoom } | "presentation" -> { c with presentation = bool_of_string v } | "rotation-angle" -> { c with angle = int_of_string v } @@ -3223,15 +4450,27 @@ struct | "height" -> { c with winh = max 20 (int_of_string v) } | "persistent-bookmarks" -> { c with savebmarks = bool_of_string v } | "proportional-display" -> { c with proportional = bool_of_string v } - | "pixmap-cache-size" -> { c with memlimit = max 2 (int_of_string v) } + | "pixmap-cache-size" -> + { c with memlimit = max 2 (int_of_string_with_suffix v) } | "tex-count" -> { c with texcount = max 1 (int_of_string v) } | "slice-height" -> { c with sliceheight = max 2 (int_of_string v) } - | "block-width" -> { c with blockwidth = max 2 (int_of_string v) } | "thumbnail-width" -> { c with thumbw = max 2 (int_of_string v) } | "persistent-location" -> { c with jumpback = bool_of_string v } | "background-color" -> { c with bgcolor = color_of_string v } | "scrollbar-in-presentation" -> { c with scrollbarinpm = bool_of_string v } + | "tile-width" -> { c with tilew = max 2 (int_of_string v) } + | "tile-height" -> { c with tileh = max 2 (int_of_string v) } + | "memlimit" -> + { c with mumemlimit = max 1024 (int_of_string_with_suffix v) } + | "checkers" -> { c with checkers = bool_of_string v } + | "aalevel" -> { c with aalevel = max 0 (int_of_string v) } + | "trim-margins" -> { c with trimmargins = bool_of_string v } + | "trim-fuzz" -> { c with trimfuzz = irect_of_string v } + | "wmclass-hack" -> wmclasshack := bool_of_string v; c + | "uri-launcher" -> { c with urilauncher = unent v } + | "color-space" -> { c with colorspace = colorspace_of_string v } + | "invert-colors" -> { c with invert = bool_of_string v } | _ -> c with exn -> prerr_endline ("Error processing attribute (`" ^ @@ -3304,18 +4543,20 @@ struct dst.proportional <- src.proportional; dst.texcount <- src.texcount; dst.sliceheight <- src.sliceheight; - dst.blockwidth <- src.blockwidth; dst.thumbw <- src.thumbw; dst.jumpback <- src.jumpback; dst.bgcolor <- src.bgcolor; dst.scrollbarinpm <- src.scrollbarinpm; - ;; - - let unent s = - let l = String.length s in - let b = Buffer.create l in - unent b s 0 l; - Buffer.contents b; + dst.tilew <- src.tilew; + dst.tileh <- src.tileh; + dst.mumemlimit <- src.mumemlimit; + dst.checkers <- src.checkers; + dst.aalevel <- src.aalevel; + dst.trimmargins <- src.trimmargins; + dst.trimfuzz <- src.trimfuzz; + dst.urilauncher <- src.urilauncher; + dst.colorspace <- src.colorspace; + dst.invert <- src.invert; ;; let get s = @@ -3343,7 +4584,16 @@ struct then v else { v with f = skip "defaults" (fun () -> v) } - | Vopen ("ui-font", _, closed) -> + | Vopen ("ui-font", attrs, closed) -> + let rec getsize size = function + | [] -> size + | ("size", v) :: rest -> + let size = + fromstring int_of_string spos "size" v !uifontsize in + getsize size rest + | l -> getsize size l + in + uifontsize := getsize !uifontsize attrs; if closed then v else { v with f = uifont (Buffer.create 10) } @@ -3523,6 +4773,9 @@ struct and oi s a b = if always || a != b then Printf.bprintf bb "\n %s='%d'" s a + and oI s a b = + if always || a != b + then Printf.bprintf bb "\n %s='%s'" s (string_with_suffix_of_int a) and oz s a b = if always || a <> b then Printf.bprintf bb "\n %s='%d'" s (truncate (a*.100.)) @@ -3530,6 +4783,18 @@ struct if always || a <> b then Printf.bprintf bb "\n %s='%s'" s (color_to_string a) + and oC s a b = + if always || a <> b + then + Printf.bprintf bb "\n %s='%s'" s (colorspace_to_string a) + and oR s a b = + if always || a <> b + then + Printf.bprintf bb "\n %s='%s'" s (irect_to_string a) + and os s a b = + if always || a <> b + then + Printf.bprintf bb "\n %s='%s'" s (enent a 0 (String.length a)) in let w, h = if always @@ -3568,24 +4833,44 @@ struct oi "rotation-angle" c.angle dc.angle; ob "persistent-bookmarks" c.savebmarks dc.savebmarks; ob "proportional-display" c.proportional dc.proportional; - oi "pixmap-cache-size" c.memlimit dc.memlimit; + oI "pixmap-cache-size" c.memlimit dc.memlimit; oi "tex-count" c.texcount dc.texcount; oi "slice-height" c.sliceheight dc.sliceheight; - oi "block-width" c.blockwidth dc.blockwidth; oi "thumbnail-width" c.thumbw dc.thumbw; ob "persistent-location" c.jumpback dc.jumpback; oc "background-color" c.bgcolor dc.bgcolor; ob "scrollbar-in-presentation" c.scrollbarinpm dc.scrollbarinpm; + oi "tile-width" c.tilew dc.tilew; + oi "tile-height" c.tileh dc.tileh; + oI "mupdf-memlimit" c.mumemlimit dc.mumemlimit; + ob "checkers" c.checkers dc.checkers; + oi "aalevel" c.aalevel dc.aalevel; + ob "trim-margins" c.trimmargins dc.trimmargins; + oR "trim-fuzz" c.trimfuzz dc.trimfuzz; + os "uri-launcher" c.urilauncher dc.urilauncher; + oC "color-space" c.colorspace dc.colorspace; + ob "invert-colors" c.invert dc.invert; + if always + then ob "wmclass-hack" !wmclasshack false; ;; let save () = + let uifontsize = !uifontsize in let bb = Buffer.create 32768 in let f (h, dc) = let dc = if conf.bedefault then conf else dc in Buffer.add_string bb "\n"; if String.length !fontpath > 0 - then Printf.bprintf bb "\n" !fontpath; + then + Printf.bprintf bb "\n" + uifontsize + !fontpath + else ( + if uifontsize <> 14 + then + Printf.bprintf bb "\n" uifontsize + ); Buffer.add_string bb " prerr_endline ("error while saving configuration: " ^ Printexc.to_string exn) @@ -3675,17 +4960,17 @@ let () = [("-p", Arg.String (fun s -> state.password <- s) , " Set password"); - ("-f", Arg.String (fun s -> State.fontpath := s), + ("-f", Arg.String (fun s -> Config.fontpath := s), " Set path to the user interface font"); - ("-c", Arg.String (fun s -> State.confpath := s), + ("-c", Arg.String (fun s -> Config.confpath := s), " Set path to the configuration file"); ("-v", Arg.Unit (fun () -> Printf.printf "%s\nconfiguration path: %s\n" Help.version - State.defconfpath + Config.defconfpath ; exit 0), " Print version and exit"); ] @@ -3696,15 +4981,19 @@ let () = if String.length state.path = 0 then (prerr_endline "file name missing"; exit 1); - State.load (); + Config.load (); let _ = Glut.init Sys.argv in let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in let () = Glut.initWindowSize conf.winw conf.winh in let _ = Glut.createWindow ("llpp " ^ Filename.basename state.path) in + if not (Glut.extensionSupported "GL_ARB_texture_rectangle" + || Glut.extensionSupported "GL_EXT_texture_rectangle") + then (prerr_endline "OpenGL does not suppport rectangular textures"; exit 1); + let csock, ssock = - if Sys.os_type = "Unix" + if not is_windows then Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 else @@ -3735,14 +5024,18 @@ let () = let () = Glut.motionFunc motion in let () = Glut.passiveMotionFunc pmotion in + setcheckers conf.checkers; init ssock ( - conf.angle, conf.proportional, conf.texcount, - conf.sliceheight, conf.blockwidth, !State.fontpath + conf.angle, conf.proportional, (conf.trimmargins, conf.trimfuzz), + conf.texcount, conf.sliceheight, conf.mumemlimit, conf.colorspace, + !Config.wmclasshack, !Config.fontpath ); state.csock <- csock; state.ssock <- ssock; state.text <- "Opening " ^ state.path; + setaalevel conf.aalevel; writeopen state.path state.password; + state.uioh <- uioh; while true do try @@ -3753,7 +5046,7 @@ let () = | Quit -> wcmd "quit" []; - State.save (); + Config.save (); exit 0 done; ;; -- 2.11.4.GIT