Avoid leaking memory and/or reusing pages from the previous document
[llpp.git] / main.ml
blobdcd678a879c003dae447d47ff164be649fcbfb0b
1 type under =
2 | Unone
3 | Ulinkuri of string
4 | Ulinkgoto of (int * int)
5 | Utext of facename
6 and facename = string;;
8 let log fmt = Printf.kprintf prerr_endline fmt;;
9 let dolog fmt = Printf.kprintf prerr_endline fmt;;
11 type params = angle * proportional * texcount * sliceheight
12 and pageno = int
13 and width = int
14 and height = int
15 and leftx = int
16 and opaque = string
17 and recttype = int
18 and pixmapsize = int
19 and angle = int
20 and proportional = bool
21 and presentation = bool
22 and interpagespace = int
23 and texcount = int
24 and sliceheight = int
25 and zoom = float
26 and gen = int
29 external init : Unix.file_descr -> params -> unit = "ml_init";;
30 external draw : (int * int * int * int * bool) -> string -> unit = "ml_draw";;
31 external seltext : string -> (int * int * int * int) -> int -> unit =
32 "ml_seltext";;
33 external copysel : string -> unit = "ml_copysel";;
34 external getpdimrect : int -> float array = "ml_getpdimrect";;
35 external whatsunder : string -> int -> int -> under = "ml_whatsunder";;
36 external zoomforh : int -> int -> int -> float = "ml_zoom_for_height";;
38 type mpos = int * int
39 and mstate =
40 | Msel of (mpos * mpos)
41 | Mpan of mpos
42 | Mscroll
43 | Mnone
46 type 'a circbuf =
47 { store : 'a array
48 ; mutable rc : int
49 ; mutable wc : int
50 ; mutable len : int
54 type textentry = (char * string * onhist * onkey * ondone)
55 and onkey = string -> int -> te
56 and ondone = string -> unit
57 and histcancel = unit -> unit
58 and onhist = ((histcmd -> string) * histcancel) option
59 and histcmd = HCnext | HCprev | HCfirst | HClast
60 and te =
61 | TEstop
62 | TEdone of string
63 | TEcont of string
64 | TEswitch of textentry
67 let cbnew n v =
68 { store = Array.create n v
69 ; rc = 0
70 ; wc = 0
71 ; len = 0
75 let cbcap b = Array.length b.store;;
77 let cbput b v =
78 let cap = cbcap b in
79 b.store.(b.wc) <- v;
80 b.wc <- (b.wc + 1) mod cap;
81 b.rc <- b.wc;
82 b.len <- min (b.len + 1) cap;
85 let cbempty b = b.len = 0;;
87 let cbgetg b circular dir =
88 if cbempty b
89 then b.store.(0)
90 else
91 let rc = b.rc + dir in
92 let rc =
93 if circular
94 then (
95 if rc = -1
96 then b.len-1
97 else (
98 if rc = b.len
99 then 0
100 else rc
103 else max 0 (min rc (b.len-1))
105 b.rc <- rc;
106 b.store.(rc);
109 let cbget b = cbgetg b false;;
110 let cbgetc b = cbgetg b true;;
112 let cbpeek b =
113 let rc = b.wc - b.len in
114 let rc = if rc < 0 then cbcap b + rc else rc in
115 b.store.(rc);
118 let cbdecr b = b.len <- b.len - 1;;
120 type layout =
121 { pageno : int
122 ; pagedimno : int
123 ; pagew : int
124 ; pageh : int
125 ; pagedispy : int
126 ; pagey : int
127 ; pagevh : int
128 ; pagex : int
132 type conf =
133 { mutable scrollw : int
134 ; mutable scrollh : int
135 ; mutable icase : bool
136 ; mutable preload : bool
137 ; mutable pagebias : int
138 ; mutable verbose : bool
139 ; mutable scrollincr : int
140 ; mutable maxhfit : bool
141 ; mutable crophack : bool
142 ; mutable autoscroll : bool
143 ; mutable showall : bool
144 ; mutable hlinks : bool
145 ; mutable underinfo : bool
146 ; mutable interpagespace : interpagespace
147 ; mutable zoom : zoom
148 ; mutable presentation : presentation
149 ; mutable angle : angle
150 ; mutable winw : int
151 ; mutable winh : int
152 ; mutable savebmarks : bool
153 ; mutable proportional : proportional
154 ; mutable memlimit : int
155 ; mutable texcount : texcount
156 ; mutable sliceheight : sliceheight
160 type outline = string * int * int * float;;
161 type outlines =
162 | Oarray of outline array
163 | Olist of outline list
164 | Onarrow of string * outline array * outline array
167 type rect = (float * float * float * float * float * float * float * float);;
169 type pagemapkey = (pageno * width * angle * proportional * gen);;
171 type state =
172 { mutable csock : Unix.file_descr
173 ; mutable ssock : Unix.file_descr
174 ; mutable w : int
175 ; mutable x : int
176 ; mutable y : int
177 ; mutable ty : float
178 ; mutable maxy : int
179 ; mutable layout : layout list
180 ; pagemap : (pagemapkey, (opaque * pixmapsize)) Hashtbl.t
181 ; mutable pdims : (pageno * width * height * leftx) list
182 ; mutable pagecount : int
183 ; pagecache : string circbuf
184 ; mutable rendering : bool
185 ; mutable mstate : mstate
186 ; mutable searchpattern : string
187 ; mutable rects : (pageno * recttype * rect) list
188 ; mutable rects1 : (pageno * recttype * rect) list
189 ; mutable text : string
190 ; mutable fullscreen : (width * height) option
191 ; mutable birdseye : (zoom * leftx * presentation * interpagespace) option
192 ; mutable textentry : textentry option
193 ; mutable outlines : outlines
194 ; mutable outline : (bool * int * int * outline array * string) option
195 ; mutable bookmarks : outline list
196 ; mutable path : string
197 ; mutable password : string
198 ; mutable invalidated : int
199 ; mutable colorscale : float
200 ; mutable memused : int
201 ; mutable birdseyepageno : pageno
202 ; mutable gen : gen
203 ; hists : hists
205 and hists =
206 { pat : string circbuf
207 ; pag : string circbuf
208 ; nav : float circbuf
212 let defconf =
213 { scrollw = 7
214 ; scrollh = 12
215 ; icase = true
216 ; preload = true
217 ; pagebias = 0
218 ; verbose = false
219 ; scrollincr = 24
220 ; maxhfit = true
221 ; crophack = false
222 ; autoscroll = false
223 ; showall = false
224 ; hlinks = false
225 ; underinfo = false
226 ; interpagespace = 2
227 ; zoom = 1.0
228 ; presentation = false
229 ; angle = 0
230 ; winw = 900
231 ; winh = 900
232 ; savebmarks = true
233 ; proportional = true
234 ; memlimit = 32*1024*1024
235 ; texcount = 256
236 ; sliceheight = 24
240 let conf = { defconf with angle = defconf.angle };;
242 let state =
243 { csock = Unix.stdin
244 ; ssock = Unix.stdin
245 ; w = 0
246 ; y = 0
247 ; x = 0
248 ; ty = 0.0
249 ; layout = []
250 ; maxy = max_int
251 ; pagemap = Hashtbl.create 10
252 ; pagecache = cbnew 100 ""
253 ; pdims = []
254 ; pagecount = 0
255 ; rendering = false
256 ; mstate = Mnone
257 ; rects = []
258 ; rects1 = []
259 ; text = ""
260 ; fullscreen = None
261 ; birdseye = None
262 ; textentry = None
263 ; searchpattern = ""
264 ; outlines = Olist []
265 ; outline = None
266 ; bookmarks = []
267 ; path = ""
268 ; password = ""
269 ; invalidated = 0
270 ; hists =
271 { nav = cbnew 100 0.0
272 ; pat = cbnew 20 ""
273 ; pag = cbnew 10 ""
275 ; colorscale = 1.0
276 ; memused = 0
277 ; birdseyepageno = 0
278 ; gen = 0
282 let vlog fmt =
283 if conf.verbose
284 then
285 Printf.kprintf prerr_endline fmt
286 else
287 Printf.kprintf ignore fmt
290 let writecmd fd s =
291 let len = String.length s in
292 let n = 4 + len in
293 let b = Buffer.create n in
294 Buffer.add_char b (Char.chr ((len lsr 24) land 0xff));
295 Buffer.add_char b (Char.chr ((len lsr 16) land 0xff));
296 Buffer.add_char b (Char.chr ((len lsr 8) land 0xff));
297 Buffer.add_char b (Char.chr ((len lsr 0) land 0xff));
298 Buffer.add_string b s;
299 let s' = Buffer.contents b in
300 let n' = Unix.write fd s' 0 n in
301 if n' != n then failwith "write failed";
304 let readcmd fd =
305 let s = "xxxx" in
306 let n = Unix.read fd s 0 4 in
307 if n != 4 then failwith "incomplete read(len)";
308 let len = 0
309 lor (Char.code s.[0] lsl 24)
310 lor (Char.code s.[1] lsl 16)
311 lor (Char.code s.[2] lsl 8)
312 lor (Char.code s.[3] lsl 0)
314 let s = String.create len in
315 let n = Unix.read fd s 0 len in
316 if n != len then failwith "incomplete read(data)";
320 let yratio y =
321 if y = state.maxy
322 then 1.0
323 else float y /. float state.maxy
326 let makecmd s l =
327 let b = Buffer.create 10 in
328 Buffer.add_string b s;
329 let rec combine = function
330 | [] -> b
331 | x :: xs ->
332 Buffer.add_char b ' ';
333 let s =
334 match x with
335 | `b b -> if b then "1" else "0"
336 | `s s -> s
337 | `i i -> string_of_int i
338 | `f f -> string_of_float f
339 | `I f -> string_of_int (truncate f)
341 Buffer.add_string b s;
342 combine xs;
344 combine l;
347 let wcmd s l =
348 let cmd = Buffer.contents (makecmd s l) in
349 writecmd state.csock cmd;
352 let calcips h =
353 if conf.presentation
354 then
355 let d = conf.winh - h in
356 max 0 ((d + 1) / 2)
357 else
358 conf.interpagespace
361 let calcheight () =
362 let rec f pn ph pi fh l =
363 match l with
364 | (n, _, h, _) :: rest ->
365 let ips = calcips h in
366 let fh =
367 if conf.presentation
368 then fh+ips
369 else (
370 if state.birdseye <> None && pn = 0
371 then fh + ips
372 else fh
375 let fh = fh + ((n - pn) * (ph + pi)) in
376 f n h ips fh rest
378 | [] ->
379 let inc =
380 if conf.presentation || (state.birdseye <> None && pn = 0)
381 then 0
382 else -pi
384 let fh = fh + ((state.pagecount - pn) * (ph + pi)) + inc in
385 max 0 fh
387 let fh = f 0 0 0 0 state.pdims in
391 let getpageyh pageno =
392 let rec f pn ph pi y l =
393 match l with
394 | (n, _, h, _) :: rest ->
395 let ips = calcips h in
396 if n >= pageno
397 then
398 if conf.presentation && n = pageno
399 then
400 y + (pageno - pn) * (ph + pi) + pi, h
401 else
402 y + (pageno - pn) * (ph + pi), h
403 else
404 let y = y + (if conf.presentation then pi else 0) in
405 let y = y + (n - pn) * (ph + pi) in
406 f n h ips y rest
408 | [] ->
409 y + (pageno - pn) * (ph + pi), ph
411 f 0 0 0 0 state.pdims
414 let getpagey pageno = fst (getpageyh pageno);;
416 let layout y sh =
417 let rec f ~pageno ~pdimno ~prev ~py ~dy ~pdims ~cacheleft ~accu =
418 let ((w, h, ips, x) as curr), rest, pdimno, yinc =
419 match pdims with
420 | (pageno', w, h, x) :: rest when pageno' = pageno ->
421 let ips = calcips h in
422 let yinc =
423 if conf.presentation || (state.birdseye <> None && pageno = 0)
424 then ips
425 else 0
427 (w, h, ips, x), rest, pdimno + 1, yinc
428 | _ ->
429 prev, pdims, pdimno, 0
431 let dy = dy + yinc in
432 let py = py + yinc in
433 if pageno = state.pagecount || cacheleft = 0 || dy >= sh
434 then
435 accu
436 else
437 let vy = y + dy in
438 if py + h <= vy - yinc
439 then
440 let py = py + h + ips in
441 let dy = max 0 (py - y) in
442 f ~pageno:(pageno+1)
443 ~pdimno
444 ~prev:curr
447 ~pdims:rest
448 ~cacheleft
449 ~accu
450 else
451 let pagey = vy - py in
452 let pagevh = h - pagey in
453 let pagevh = min (sh - dy) pagevh in
454 let off = if yinc > 0 then py - vy else 0 in
455 let py = py + h + ips in
456 let e =
457 { pageno = pageno
458 ; pagedimno = pdimno
459 ; pagew = w
460 ; pageh = h
461 ; pagedispy = dy + off
462 ; pagey = pagey + off
463 ; pagevh = pagevh - off
464 ; pagex = x
467 let accu = e :: accu in
468 f ~pageno:(pageno+1)
469 ~pdimno
470 ~prev:curr
472 ~dy:(dy+pagevh+ips)
473 ~pdims:rest
474 ~cacheleft:(cacheleft-1)
475 ~accu
477 if state.invalidated = 0
478 then (
479 let accu =
481 ~pageno:0
482 ~pdimno:~-1
483 ~prev:(0,0,0,0)
484 ~py:0
485 ~dy:0
486 ~pdims:state.pdims
487 ~cacheleft:(cbcap state.pagecache)
488 ~accu:[]
490 List.rev accu
492 else
496 let clamp incr =
497 let y = state.y + incr in
498 let y = max 0 y in
499 let y = min y (state.maxy - (if conf.maxhfit then conf.winh else 0)) in
503 let getopaque pageno =
504 try Some (Hashtbl.find state.pagemap
505 (pageno, state.w, conf.angle, conf.proportional, state.gen))
506 with Not_found -> None
509 let cache pageno opaque =
510 Hashtbl.replace state.pagemap
511 (pageno, state.w, conf.angle, conf.proportional, state.gen) opaque
514 let validopaque opaque = String.length opaque > 0;;
516 let render l =
517 match getopaque l.pageno with
518 | None when not state.rendering ->
519 state.rendering <- true;
520 cache l.pageno ("", -1);
521 wcmd "render" [`i (l.pageno + 1)
522 ;`i l.pagedimno
523 ;`i l.pagew
524 ;`i l.pageh];
526 | _ -> ()
529 let loadlayout layout =
530 let rec f all = function
531 | l :: ls ->
532 begin match getopaque l.pageno with
533 | None -> render l; f false ls
534 | Some (opaque, _) -> f (all && validopaque opaque) ls
536 | [] -> all
538 f (layout <> []) layout;
541 let findpageforopaque opaque =
542 Hashtbl.fold
543 (fun k (v, s) a -> if v = opaque then Some (k, s) else a)
544 state.pagemap None
547 let pagevisible n = List.exists (fun l -> l.pageno = n) state.layout;;
549 let preload () =
550 if conf.preload
551 then
552 let oktopreload =
553 let opaque = cbpeek state.pagecache in
554 match findpageforopaque opaque with
555 | Some ((n, _, _, _, _), size) ->
556 not (pagevisible n) && state.memused - size <= conf.memlimit
557 | None -> false
559 if oktopreload
560 then
561 let rely = yratio state.y in
562 let presentation = conf.presentation in
563 let interpagespace = conf.interpagespace in
564 let maxy = state.maxy in
565 conf.presentation <- false;
566 conf.interpagespace <- 0;
567 state.maxy <- calcheight ();
568 let y = truncate (float state.maxy *. rely) in
569 let y = if y < conf.winh then 0 else y - conf.winh in
570 let pages = layout y (conf.winh*3) in
571 List.iter render pages;
572 conf.presentation <- presentation;
573 conf.interpagespace <- interpagespace;
574 state.maxy <- maxy;
577 let gotoy y =
578 let y = max 0 y in
579 let y = min state.maxy y in
580 let pages = layout y conf.winh in
581 let ready = loadlayout pages in
582 state.ty <- yratio y;
583 state.layout <- pages;
584 if conf.showall
585 then (
586 if ready
587 then (
588 state.y <- y;
589 Glut.postRedisplay ();
592 else (
593 state.y <- y;
594 Glut.postRedisplay ();
596 if state.birdseye <> None
597 then (
598 if not (pagevisible state.birdseyepageno)
599 then
600 match state.layout with
601 | [] -> ()
602 | l :: _ -> state.birdseyepageno <- l.pageno
604 preload ();
607 let gotoy_and_clear_text y =
608 gotoy y;
609 if not conf.verbose then state.text <- "";
612 let addnav () =
613 cbput state.hists.nav (yratio state.y);
616 let getnav () =
617 let y = cbgetc state.hists.nav ~-1 in
618 truncate (y *. float state.maxy)
621 let gotopage n top =
622 let y, h = getpageyh n in
623 addnav ();
624 gotoy_and_clear_text (y + (truncate (top *. float h)));
627 let gotopage1 n top =
628 let y = getpagey n in
629 addnav ();
630 gotoy_and_clear_text (y + top);
633 let invalidate () =
634 state.layout <- [];
635 state.pdims <- [];
636 state.rects <- [];
637 state.rects1 <- [];
638 state.invalidated <- state.invalidated + 1;
641 let scalecolor c =
642 let c = c *. state.colorscale in
643 (c, c, c);
646 let represent () =
647 let y =
648 match state.layout with
649 | [] ->
650 let rely = yratio state.y in
651 state.maxy <- calcheight ();
652 truncate (float state.maxy *. rely)
654 | l :: _ ->
655 state.maxy <- calcheight ();
656 getpagey l.pageno
658 gotoy y
661 let pagematrix () =
662 GlMat.mode `projection;
663 GlMat.load_identity ();
664 GlMat.rotate ~x:1.0 ~angle:180.0 ();
665 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
666 GlMat.scale3 (2.0 /. float state.w, 2.0 /. float conf.winh, 1.0);
669 let winmatrix () =
670 GlMat.mode `projection;
671 GlMat.load_identity ();
672 GlMat.rotate ~x:1.0 ~angle:180.0 ();
673 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
674 GlMat.scale3 (2.0 /. float conf.winw, 2.0 /. float conf.winh, 1.0);
677 let reshape ~w ~h =
678 conf.winw <- w;
679 let w = truncate (float w *. conf.zoom) - conf.scrollw in
680 let w = max w 2 in
681 state.w <- w;
682 conf.winh <- h;
683 GlMat.mode `modelview;
684 GlMat.load_identity ();
685 GlClear.color (scalecolor 1.0);
686 GlClear.clear [`color];
688 invalidate ();
689 wcmd "geometry" [`i w; `i h];
692 let showtext c s =
693 GlDraw.color (0.0, 0.0, 0.0);
694 GlDraw.rect
695 (0.0, float (conf.winh - 18))
696 (float (conf.winw - conf.scrollw - 1), float conf.winh)
698 let font = Glut.BITMAP_8_BY_13 in
699 GlDraw.color (1.0, 1.0, 1.0);
700 GlPix.raster_pos ~x:0.0 ~y:(float (conf.winh - 5)) ();
701 Glut.bitmapCharacter ~font ~c:(Char.code c);
702 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s;
705 let enttext () =
706 let len = String.length state.text in
707 match state.textentry with
708 | None ->
709 if len > 0 then showtext ' ' state.text
711 | Some (c, text, _, _, _) ->
712 let s =
713 if len > 0
714 then
715 text ^ " [" ^ state.text ^ "]"
716 else
717 text
719 showtext c s;
722 let showtext c s =
723 if true
724 then (
725 state.text <- Printf.sprintf "%c%s" c s;
726 Glut.postRedisplay ();
728 else (
729 showtext c s;
730 Glut.swapBuffers ();
734 let act cmd =
735 match cmd.[0] with
736 | 'c' ->
737 state.pdims <- [];
739 | 'D' ->
740 state.rects <- state.rects1;
741 Glut.postRedisplay ()
743 | 'C' ->
744 let n = Scanf.sscanf cmd "C %u" (fun n -> n) in
745 state.pagecount <- n;
746 state.invalidated <- state.invalidated - 1;
747 if state.invalidated = 0
748 then represent ()
750 | 't' ->
751 let s = Scanf.sscanf cmd "t %n"
752 (fun n -> String.sub cmd n (String.length cmd - n))
754 Glut.setWindowTitle s
756 | 'T' ->
757 let s = Scanf.sscanf cmd "T %n"
758 (fun n -> String.sub cmd n (String.length cmd - n))
760 if state.textentry = None
761 then (
762 state.text <- s;
763 showtext ' ' s;
765 else (
766 state.text <- s;
767 Glut.postRedisplay ();
770 | 'V' ->
771 if conf.verbose
772 then
773 let s = Scanf.sscanf cmd "V %n"
774 (fun n -> String.sub cmd n (String.length cmd - n))
776 state.text <- s;
777 showtext ' ' s;
779 | 'F' ->
780 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
781 Scanf.sscanf cmd "F %u %d %f %f %f %f %f %f %f %f"
782 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
783 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
785 let y = (getpagey pageno) + truncate y0 in
786 addnav ();
787 gotoy y;
788 state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)]
790 | 'R' ->
791 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
792 Scanf.sscanf cmd "R %u %d %f %f %f %f %f %f %f %f"
793 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
794 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
796 state.rects1 <-
797 (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
799 | 'r' ->
800 let n, w, h, r, l, s, p =
801 Scanf.sscanf cmd "r %u %u %u %u %d %u %s"
802 (fun n w h r l s p ->
803 (n-1, w, h, r, l != 0, s, p))
806 Hashtbl.replace state.pagemap (n, w, r, l, state.gen) (p, s);
807 state.memused <- state.memused + s;
809 let rec gc () =
810 if (state.memused <= conf.memlimit) || cbempty state.pagecache
811 then ()
812 else (
813 let evictedopaque = cbpeek state.pagecache in
814 match findpageforopaque evictedopaque with
815 | None -> failwith "bug in gc"
816 | Some ((evictedn, _, _, _, gen) as k, evictedsize) ->
817 if state.gen != gen || not (pagevisible evictedn)
818 then (
819 wcmd "free" [`s evictedopaque];
820 state.memused <- state.memused - evictedsize;
821 Hashtbl.remove state.pagemap k;
822 cbdecr state.pagecache;
823 gc ();
827 gc ();
829 cbput state.pagecache p;
830 state.rendering <- false;
832 if conf.showall
833 then gotoy (truncate (ceil (state.ty *. float state.maxy)))
834 else (
835 if pagevisible n
836 then gotoy state.y
837 else (ignore (loadlayout state.layout); preload ())
840 | 'l' ->
841 let (n, w, h, x) as pdim =
842 Scanf.sscanf cmd "l %u %u %u %u" (fun n w h x -> n, w, h, x)
844 state.pdims <- pdim :: state.pdims
846 | 'o' ->
847 let (l, n, t, h, pos) =
848 Scanf.sscanf cmd "o %u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos)
850 let s = String.sub cmd pos (String.length cmd - pos) in
851 let s =
852 let l = String.length s in
853 let b = Buffer.create (String.length s) in
854 let rec loop pc2 i =
855 if i = l
856 then ()
857 else
858 let pc2 =
859 match s.[i] with
860 | '\xa0' when pc2 -> Buffer.add_char b ' '; false
861 | '\xc2' -> true
862 | c ->
863 let c = if Char.code c land 0x80 = 0 then c else '?' in
864 Buffer.add_char b c;
865 false
867 loop pc2 (i+1)
869 loop false 0;
870 Buffer.contents b
872 let outline = (s, l, n, float t /. float h) in
873 let outlines =
874 match state.outlines with
875 | Olist outlines -> Olist (outline :: outlines)
876 | Oarray _ -> Olist [outline]
877 | Onarrow _ -> Olist [outline]
879 state.outlines <- outlines
881 | _ ->
882 log "unknown cmd `%S'" cmd
885 let now = Unix.gettimeofday;;
887 let idle () =
888 let rec loop delay =
889 let r, _, _ = Unix.select [state.csock] [] [] delay in
890 begin match r with
891 | [] ->
892 if conf.autoscroll
893 then begin
894 let y = state.y + conf.scrollincr in
895 let y = if y >= state.maxy then 0 else y in
896 gotoy y;
897 state.text <- "";
898 end;
900 | _ ->
901 let cmd = readcmd state.csock in
902 act cmd;
903 loop 0.0
904 end;
905 in loop 0.001
908 let onhist cb =
909 let rc = cb.rc in
910 let action = function
911 | HCprev -> cbget cb ~-1
912 | HCnext -> cbget cb 1
913 | HCfirst -> cbget cb ~-(cb.rc)
914 | HClast -> cbget cb (cb.len - 1 - cb.rc)
915 and cancel () = cb.rc <- rc
916 in (action, cancel)
919 let search pattern forward =
920 if String.length pattern > 0
921 then
922 let pn, py =
923 match state.layout with
924 | [] -> 0, 0
925 | l :: _ ->
926 l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
928 let cmd =
929 let b = makecmd "search"
930 [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0)]
932 Buffer.add_char b ',';
933 Buffer.add_string b pattern;
934 Buffer.add_char b '\000';
935 Buffer.contents b;
937 writecmd state.csock cmd;
940 let intentry text key =
941 let c = Char.unsafe_chr key in
942 match c with
943 | '0' .. '9' ->
944 let s = "x" in s.[0] <- c;
945 let text = text ^ s in
946 TEcont text
948 | _ ->
949 state.text <- Printf.sprintf "invalid char (%d, `%c')" key c;
950 TEcont text
953 let addchar s c =
954 let b = Buffer.create (String.length s + 1) in
955 Buffer.add_string b s;
956 Buffer.add_char b c;
957 Buffer.contents b;
960 let textentry text key =
961 let c = Char.unsafe_chr key in
962 match c with
963 | _ when key >= 32 && key < 127 ->
964 let text = addchar text c in
965 TEcont text
967 | _ ->
968 log "unhandled key %d char `%c'" key (Char.unsafe_chr key);
969 TEcont text
972 let reinit angle proportional =
973 conf.angle <- angle;
974 conf.proportional <- proportional;
975 invalidate ();
976 wcmd "reinit" [`i angle; `b proportional];
979 let optentry text key =
980 let btos b = if b then "on" else "off" in
981 let c = Char.unsafe_chr key in
982 match c with
983 | 's' ->
984 let ondone s =
985 try conf.scrollincr <- int_of_string s with exc ->
986 state.text <- Printf.sprintf "bad integer `%s': %s"
987 s (Printexc.to_string exc)
989 TEswitch ('#', "", None, intentry, ondone)
991 | 'R' ->
992 let ondone s =
993 match try
994 Some (int_of_string s)
995 with exc ->
996 state.text <- Printf.sprintf "bad integer `%s': %s"
997 s (Printexc.to_string exc);
998 None
999 with
1000 | Some angle -> reinit angle conf.proportional
1001 | None -> ()
1003 TEswitch ('^', "", None, intentry, ondone)
1005 | 'i' ->
1006 conf.icase <- not conf.icase;
1007 TEdone ("case insensitive search " ^ (btos conf.icase))
1009 | 'p' ->
1010 conf.preload <- not conf.preload;
1011 gotoy state.y;
1012 TEdone ("preload " ^ (btos conf.preload))
1014 | 'v' ->
1015 conf.verbose <- not conf.verbose;
1016 TEdone ("verbose " ^ (btos conf.verbose))
1018 | 'h' ->
1019 conf.maxhfit <- not conf.maxhfit;
1020 state.maxy <- state.maxy + (if conf.maxhfit then -conf.winh else conf.winh);
1021 TEdone ("maxhfit " ^ (btos conf.maxhfit))
1023 | 'c' ->
1024 conf.crophack <- not conf.crophack;
1025 TEdone ("crophack " ^ btos conf.crophack)
1027 | 'a' ->
1028 conf.showall <- not conf.showall;
1029 TEdone ("showall " ^ btos conf.showall)
1031 | 'f' ->
1032 conf.underinfo <- not conf.underinfo;
1033 TEdone ("underinfo " ^ btos conf.underinfo)
1035 | 'P' ->
1036 conf.savebmarks <- not conf.savebmarks;
1037 TEdone ("persistent bookmarks " ^ btos conf.savebmarks)
1039 | 'S' ->
1040 let ondone s =
1042 conf.interpagespace <- int_of_string s;
1043 let rely = yratio state.y in
1044 state.maxy <- calcheight ();
1045 gotoy (truncate (float state.maxy *. rely));
1046 with exc ->
1047 state.text <- Printf.sprintf "bad integer `%s': %s"
1048 s (Printexc.to_string exc)
1050 TEswitch ('%', "", None, intentry, ondone)
1052 | 'l' ->
1053 reinit conf.angle (not conf.proportional);
1054 TEdone ("proprortional display " ^ btos conf.proportional)
1056 | _ ->
1057 state.text <- Printf.sprintf "bad option %d `%c'" key c;
1058 TEstop
1061 let maxoutlinerows () = (conf.winh - 31) / 16;;
1063 let enterselector allowdel outlines errmsg msg =
1064 if Array.length outlines = 0
1065 then (
1066 showtext ' ' errmsg;
1068 else (
1069 state.text <- msg;
1070 Glut.setCursor Glut.CURSOR_INHERIT;
1071 let pageno =
1072 match state.layout with
1073 | [] -> -1
1074 | {pageno=pageno} :: rest -> pageno
1076 let active =
1077 let rec loop n =
1078 if n = Array.length outlines
1079 then 0
1080 else
1081 let (_, _, outlinepageno, _) = outlines.(n) in
1082 if outlinepageno >= pageno then n else loop (n+1)
1084 loop 0
1086 state.outline <-
1087 Some (allowdel, active,
1088 max 0 ((active - maxoutlinerows () / 2)), outlines, "");
1089 Glut.postRedisplay ();
1093 let enteroutlinemode () =
1094 let outlines, msg =
1095 match state.outlines with
1096 | Oarray a -> a, ""
1097 | Olist l ->
1098 let a = Array.of_list (List.rev l) in
1099 state.outlines <- Oarray a;
1100 a, ""
1101 | Onarrow (pat, a, b) ->
1102 a, "Outline was narrowed to `" ^ pat ^ "' (Ctrl-u to restore)"
1104 enterselector false outlines "Document has no outline" msg;
1107 let enterbookmarkmode () =
1108 let bookmarks = Array.of_list state.bookmarks in
1109 enterselector true bookmarks "Document has no bookmarks (yet)" "";
1112 let quickbookmark ?title () =
1113 match state.layout with
1114 | [] -> ()
1115 | l :: _ ->
1116 let title =
1117 match title with
1118 | None ->
1119 let sec = Unix.gettimeofday () in
1120 let tm = Unix.localtime sec in
1121 Printf.sprintf "Quick (page %d) (bookmarked at %d/%d/%d %d:%d)"
1122 (l.pageno+1)
1123 tm.Unix.tm_mday
1124 tm.Unix.tm_mon
1125 (tm.Unix.tm_year + 1900)
1126 tm.Unix.tm_hour
1127 tm.Unix.tm_min
1128 | Some title -> title
1130 state.bookmarks <-
1131 (title, 0, l.pageno, float l.pagey /. float l.pageh) :: state.bookmarks
1134 let doreshape w h =
1135 state.fullscreen <- None;
1136 Glut.reshapeWindow w h;
1139 let writeopen path password =
1140 writecmd state.csock ("open " ^ path ^ "\000" ^ state.password ^ "\000");
1143 let opendoc path password =
1144 invalidate ();
1145 state.path <- path;
1146 state.password <- password;
1147 state.gen <- state.gen + 1;
1149 writeopen path password;
1150 Glut.setWindowTitle ("llpp " ^ Filename.basename path);
1151 wcmd "geometry" [`i state.w; `i conf.winh];
1154 let birdseyeoff (zoom, x, presentation, interpagespace) =
1155 state.birdseye <- None;
1156 conf.zoom <- zoom;
1157 conf.presentation <- presentation;
1158 conf.interpagespace <- interpagespace;
1159 state.x <- x;
1160 state.text <- Printf.sprintf "birds eye mode off (zoom %3.1f%%)"
1161 (100.0*.zoom);
1164 let viewkeyboard ~key ~x ~y =
1165 let enttext te =
1166 state.textentry <- te;
1167 state.text <- "";
1168 enttext ();
1169 Glut.postRedisplay ()
1171 match state.textentry with
1172 | None ->
1173 let c = Char.chr key in
1174 begin match c with
1175 | '\027' | 'q' ->
1176 exit 0
1178 | '\008' ->
1179 let y = getnav () in
1180 gotoy_and_clear_text y
1182 | '\013' ->
1183 begin match state.birdseye with
1184 | None -> ()
1185 | Some vals ->
1186 let y = getpagey state.birdseyepageno in
1187 state.y <- y;
1188 birdseyeoff vals;
1189 reshape conf.winw conf.winh;
1190 end;
1192 | 'o' ->
1193 enteroutlinemode ()
1195 | 'u' ->
1196 state.rects <- [];
1197 state.text <- "";
1198 Glut.postRedisplay ()
1200 | '/' | '?' ->
1201 let ondone isforw s =
1202 cbput state.hists.pat s;
1203 state.searchpattern <- s;
1204 search s isforw
1206 enttext (Some (c, "", Some (onhist state.hists.pat),
1207 textentry, ondone (c ='/')))
1209 | '+' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1210 let incr = if conf.zoom +. 0.01 > 0.1 then 0.1 else 0.01 in
1211 conf.zoom <- min 2.2 (conf.zoom +. incr);
1212 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1213 reshape conf.winw conf.winh
1215 | '+' ->
1216 let ondone s =
1217 let n =
1218 try int_of_string s with exc ->
1219 state.text <- Printf.sprintf "bad integer `%s': %s"
1220 s (Printexc.to_string exc);
1221 max_int
1223 if n != max_int
1224 then (
1225 conf.pagebias <- n;
1226 state.text <- "page bias is now " ^ string_of_int n;
1229 enttext (Some ('+', "", None, intentry, ondone))
1231 | '-' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1232 let decr = if conf.zoom -. 0.1 < 0.1 then 0.01 else 0.1 in
1233 conf.zoom <- max 0.01 (conf.zoom -. decr);
1234 if conf.zoom <= 1.0 then state.x <- 0;
1235 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1236 reshape conf.winw conf.winh;
1238 | '-' ->
1239 let ondone msg =
1240 state.text <- msg;
1242 enttext (Some ('-', "", None, optentry, ondone))
1244 | '0' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1245 state.x <- 0;
1246 conf.zoom <- 1.0;
1247 state.text <- "zoom is 100%";
1248 reshape conf.winw conf.winh
1250 | '1' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1251 let zoom = zoomforh conf.winw conf.winh conf.scrollw in
1252 if zoom < 1.0
1253 then (
1254 conf.zoom <- zoom;
1255 state.x <- 0;
1256 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1257 reshape conf.winw conf.winh;
1260 | '9' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1261 begin match state.birdseye with
1262 | None ->
1263 let zoom = 50.0 /. float state.w in
1264 state.birdseye <- Some (
1265 conf.zoom,
1266 state.x,
1267 conf.presentation,
1268 conf.interpagespace;
1270 conf.zoom <- zoom;
1271 conf.presentation <- false;
1272 conf.interpagespace <- 10;
1273 state.x <- 0;
1274 state.mstate <- Mnone;
1275 Glut.setCursor Glut.CURSOR_INHERIT;
1276 state.text <- Printf.sprintf "birds eye mode on (zoom %3.1f%%)"
1277 (100.0*.zoom)
1279 | Some vals ->
1280 birdseyeoff vals;
1281 end;
1282 reshape conf.winw conf.winh
1284 | '0' .. '9' ->
1285 let ondone s =
1286 let n =
1287 try int_of_string s with exc ->
1288 state.text <- Printf.sprintf "bad integer `%s': %s"
1289 s (Printexc.to_string exc);
1292 if n >= 0
1293 then (
1294 addnav ();
1295 cbput state.hists.pag (string_of_int n);
1296 gotoy_and_clear_text (getpagey (n + conf.pagebias - 1))
1299 let pageentry text key =
1300 match Char.unsafe_chr key with
1301 | 'g' -> TEdone text
1302 | _ -> intentry text key
1304 let text = "x" in text.[0] <- c;
1305 enttext (Some (':', text, Some (onhist state.hists.pag),
1306 pageentry, ondone))
1308 | 'b' ->
1309 conf.scrollw <- if conf.scrollw > 0 then 0 else defconf.scrollw;
1310 reshape conf.winw conf.winh;
1312 | 'l' ->
1313 conf.hlinks <- not conf.hlinks;
1314 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
1315 Glut.postRedisplay ()
1317 | 'a' ->
1318 conf.autoscroll <- not conf.autoscroll
1320 | 'P' ->
1321 conf.presentation <- not conf.presentation;
1322 showtext ' ' ("presentation mode " ^
1323 if conf.presentation then "on" else "off");
1324 represent ()
1326 | 'f' ->
1327 begin match state.fullscreen with
1328 | None ->
1329 state.fullscreen <- Some (conf.winw, conf.winh);
1330 Glut.fullScreen ()
1331 | Some (w, h) ->
1332 state.fullscreen <- None;
1333 doreshape w h
1336 | 'g' ->
1337 gotoy_and_clear_text 0
1339 | 'n' ->
1340 search state.searchpattern true
1342 | 'p' | 'N' ->
1343 search state.searchpattern false
1345 | 't' ->
1346 begin match state.layout with
1347 | [] -> ()
1348 | l :: _ ->
1349 gotoy_and_clear_text (getpagey l.pageno)
1352 | ' ' ->
1353 begin match List.rev state.layout with
1354 | [] -> ()
1355 | l :: _ ->
1356 let pageno = min (l.pageno+1) (state.pagecount-1) in
1357 gotoy_and_clear_text (getpagey pageno)
1360 | '\127' ->
1361 begin match state.layout with
1362 | [] -> ()
1363 | l :: _ ->
1364 let pageno = max 0 (l.pageno-1) in
1365 gotoy_and_clear_text (getpagey pageno)
1368 | '=' ->
1369 let f (fn, ln) l =
1370 if fn = -1 then l.pageno, l.pageno else fn, l.pageno
1372 let fn, ln = List.fold_left f (-1, -1) state.layout in
1373 let s =
1374 let maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in
1375 let percent =
1376 if maxy <= 0
1377 then 100.
1378 else (100. *. (float state.y /. float maxy)) in
1379 if fn = ln
1380 then
1381 Printf.sprintf "Page %d of %d %.2f%%"
1382 (fn+1) state.pagecount percent
1383 else
1384 Printf.sprintf
1385 "Pages %d-%d of %d %.2f%%"
1386 (fn+1) (ln+1) state.pagecount percent
1388 showtext ' ' s;
1390 | 'w' ->
1391 begin match state.layout with
1392 | [] -> ()
1393 | l :: _ ->
1394 doreshape (l.pagew + conf.scrollw) l.pageh;
1395 Glut.postRedisplay ();
1398 | '\'' ->
1399 enterbookmarkmode ()
1401 | 'm' ->
1402 let ondone s =
1403 match state.layout with
1404 | l :: _ ->
1405 state.bookmarks <-
1406 (s, 0, l.pageno, float l.pagey /. float l.pageh)
1407 :: state.bookmarks
1408 | _ -> ()
1410 enttext (Some ('~', "", None, textentry, ondone))
1412 | '~' ->
1413 quickbookmark ();
1414 showtext ' ' "Quick bookmark added";
1416 | 'z' ->
1417 begin match state.layout with
1418 | l :: _ ->
1419 let rect = getpdimrect l.pagedimno in
1420 let w, h =
1421 if conf.crophack
1422 then
1423 (truncate (1.8 *. (rect.(1) -. rect.(0))),
1424 truncate (1.2 *. (rect.(3) -. rect.(0))))
1425 else
1426 (truncate (rect.(1) -. rect.(0)),
1427 truncate (rect.(3) -. rect.(0)))
1429 if w != 0 && h != 0
1430 then
1431 doreshape (w + conf.scrollw) (h + conf.interpagespace)
1433 Glut.postRedisplay ();
1435 | [] -> ()
1438 | '<' | '>' ->
1439 reinit (conf.angle + (if c = '>' then 30 else -30)) conf.proportional
1441 | '[' | ']' ->
1442 state.colorscale <-
1443 max 0.0
1444 (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0);
1445 Glut.postRedisplay ()
1447 | 'k' -> gotoy (clamp (-conf.scrollincr))
1448 | 'j' -> gotoy (clamp conf.scrollincr)
1450 | 'r' -> opendoc state.path state.password
1452 | _ ->
1453 vlog "huh? %d %c" key (Char.chr key);
1456 | Some (c, text, opthist, onkey, ondone) when key = 8 ->
1457 let len = String.length text in
1458 if len = 0
1459 then (
1460 state.textentry <- None;
1461 Glut.postRedisplay ();
1463 else (
1464 let s = String.sub text 0 (len - 1) in
1465 enttext (Some (c, s, opthist, onkey, ondone))
1468 | Some (c, text, onhist, onkey, ondone) ->
1469 begin match Char.unsafe_chr key with
1470 | '\r' | '\n' ->
1471 ondone text;
1472 state.textentry <- None;
1473 Glut.postRedisplay ()
1475 | '\027' ->
1476 begin match onhist with
1477 | None -> ()
1478 | Some (_, onhistcancel) -> onhistcancel ()
1479 end;
1480 state.textentry <- None;
1481 Glut.postRedisplay ()
1483 | _ ->
1484 begin match onkey text key with
1485 | TEdone text ->
1486 state.textentry <- None;
1487 ondone text;
1488 Glut.postRedisplay ()
1490 | TEcont text ->
1491 enttext (Some (c, text, onhist, onkey, ondone));
1493 | TEstop ->
1494 state.textentry <- None;
1495 Glut.postRedisplay ()
1497 | TEswitch te ->
1498 state.textentry <- Some te;
1499 Glut.postRedisplay ()
1500 end;
1501 end;
1504 let narrow outlines pattern =
1505 let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in
1506 match reopt with
1507 | None -> None
1508 | Some re ->
1509 let rec fold accu n =
1510 if n = -1
1511 then accu
1512 else
1513 let (s, _, _, _) as o = outlines.(n) in
1514 let accu =
1515 if (try ignore (Str.search_forward re s 0); true
1516 with Not_found -> false)
1517 then (o :: accu)
1518 else accu
1520 fold accu (n-1)
1522 let matched = fold [] (Array.length outlines - 1) in
1523 if matched = [] then None else Some (Array.of_list matched)
1526 let outlinekeyboard ~key ~x ~y (allowdel, active, first, outlines, qsearch) =
1527 let search active pattern incr =
1528 let dosearch re =
1529 let rec loop n =
1530 if n = Array.length outlines || n = -1
1531 then None
1532 else
1533 let (s, _, _, _) = outlines.(n) in
1535 (try ignore (Str.search_forward re s 0); true
1536 with Not_found -> false)
1537 then Some n
1538 else loop (n + incr)
1540 loop active
1543 let re = Str.regexp_case_fold pattern in
1544 dosearch re
1545 with Failure s ->
1546 state.text <- s;
1547 None
1549 let firstof active = max 0 (active - maxoutlinerows () / 2) in
1550 match key with
1551 | 27 ->
1552 if String.length qsearch = 0
1553 then (
1554 state.text <- "";
1555 state.outline <- None;
1556 Glut.postRedisplay ();
1558 else (
1559 state.text <- "";
1560 state.outline <- Some (allowdel, active, first, outlines, "");
1561 Glut.postRedisplay ();
1564 | 18 | 19 ->
1565 let incr = if key = 18 then -1 else 1 in
1566 let active, first =
1567 match search (active + incr) qsearch incr with
1568 | None ->
1569 state.text <- qsearch ^ " [not found]";
1570 active, first
1571 | Some active ->
1572 state.text <- qsearch;
1573 active, firstof active
1575 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1576 Glut.postRedisplay ();
1578 | 8 ->
1579 let len = String.length qsearch in
1580 if len = 0
1581 then ()
1582 else (
1583 if len = 1
1584 then (
1585 state.text <- "";
1586 state.outline <- Some (allowdel, active, first, outlines, "");
1588 else
1589 let qsearch = String.sub qsearch 0 (len - 1) in
1590 let active, first =
1591 match search active qsearch ~-1 with
1592 | None ->
1593 state.text <- qsearch ^ " [not found]";
1594 active, first
1595 | Some active ->
1596 state.text <- qsearch;
1597 active, firstof active
1599 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1601 Glut.postRedisplay ()
1603 | 13 ->
1604 if active < Array.length outlines
1605 then (
1606 let (_, _, n, t) = outlines.(active) in
1607 gotopage n t;
1609 state.text <- "";
1610 if allowdel then state.bookmarks <- Array.to_list outlines;
1611 state.outline <- None;
1612 Glut.postRedisplay ();
1614 | _ when key >= 32 && key < 127 ->
1615 let pattern = addchar qsearch (Char.chr key) in
1616 let active, first =
1617 match search active pattern 1 with
1618 | None ->
1619 state.text <- pattern ^ " [not found]";
1620 active, first
1621 | Some active ->
1622 state.text <- pattern;
1623 active, firstof active
1625 state.outline <- Some (allowdel, active, first, outlines, pattern);
1626 Glut.postRedisplay ()
1628 | 14 when not allowdel -> (* ctrl-n *)
1629 if String.length qsearch > 0
1630 then (
1631 let optoutlines = narrow outlines qsearch in
1632 begin match optoutlines with
1633 | None -> state.text <- "can't narrow"
1634 | Some outlines ->
1635 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1636 match state.outlines with
1637 | Olist l -> ()
1638 | Oarray a ->
1639 state.outlines <- Onarrow (qsearch, outlines, a)
1640 | Onarrow (pat, a, b) ->
1641 state.outlines <- Onarrow (qsearch, outlines, b)
1642 end;
1644 Glut.postRedisplay ()
1646 | 21 when not allowdel -> (* ctrl-u *)
1647 let outline =
1648 match state.outlines with
1649 | Oarray a -> a
1650 | Olist l ->
1651 let a = Array.of_list (List.rev l) in
1652 state.outlines <- Oarray a;
1654 | Onarrow (pat, a, b) ->
1655 state.outlines <- Oarray b;
1656 state.text <- "";
1659 state.outline <- Some (allowdel, 0, 0, outline, qsearch);
1660 Glut.postRedisplay ()
1662 | 12 ->
1663 state.outline <-
1664 Some (allowdel, active, firstof active, outlines, qsearch);
1665 Glut.postRedisplay ()
1667 | 127 when allowdel ->
1668 let len = Array.length outlines - 1 in
1669 if len = 0
1670 then (
1671 state.outline <- None;
1672 state.bookmarks <- [];
1674 else (
1675 let bookmarks = Array.init len
1676 (fun i ->
1677 let i = if i >= active then i + 1 else i in
1678 outlines.(i)
1681 state.outline <-
1682 Some (allowdel,
1683 min active (len-1),
1684 min first (len-1),
1685 bookmarks, qsearch)
1688 Glut.postRedisplay ()
1690 | _ -> log "unknown key %d" key
1693 let keyboard ~key ~x ~y =
1694 if key = 7
1695 then
1696 wcmd "interrupt" []
1697 else
1698 match state.outline with
1699 | None -> viewkeyboard ~key ~x ~y
1700 | Some outline -> outlinekeyboard ~key ~x ~y outline
1703 let special ~key ~x ~y =
1704 match state.outline with
1705 | None when state.birdseye <> None ->
1706 begin match key with
1707 | Glut.KEY_UP ->
1708 let pageno = max 0 (state.birdseyepageno - 1) in
1709 state.birdseyepageno <- pageno;
1710 if not (pagevisible pageno)
1711 then gotopage pageno 0.0
1712 else Glut.postRedisplay ();
1714 | Glut.KEY_DOWN ->
1715 let pageno = min (state.pagecount - 1) (state.birdseyepageno + 1) in
1716 state.birdseyepageno <- pageno;
1717 if not (pagevisible pageno)
1718 then
1719 begin match List.rev state.layout with
1720 | [] -> gotopage pageno 0.0
1721 | l :: _ ->
1722 gotoy (state.y + conf.interpagespace + l.pageh*2 - l.pagevh)
1724 else Glut.postRedisplay ();
1726 | Glut.KEY_PAGE_UP ->
1727 begin match state.layout with
1728 | l :: _ ->
1729 if l.pageno = state.birdseyepageno
1730 then (
1731 match layout (state.y - conf.winh) conf.winh with
1732 | [] -> gotoy (clamp (-conf.winh))
1733 | l :: _ ->
1734 state.birdseyepageno <- max 0 (l.pageno - 1);
1735 gotopage state.birdseyepageno 0.0
1737 else (
1738 state.birdseyepageno <- max 0 (l.pageno - 1);
1739 gotopage state.birdseyepageno 0.0
1741 | [] -> gotoy (clamp (-conf.winh))
1742 end;
1743 | Glut.KEY_PAGE_DOWN ->
1744 begin match List.rev state.layout with
1745 | l :: _ ->
1746 state.birdseyepageno <- min (state.pagecount - 1) (l.pageno + 1);
1747 gotoy (clamp (l.pagedispy + conf.interpagespace + l.pageh))
1748 | [] -> gotoy (clamp conf.winh)
1749 end;
1751 | Glut.KEY_HOME ->
1752 state.birdseyepageno <- 0;
1753 gotopage 0 0.0
1754 | Glut.KEY_END ->
1755 state.birdseyepageno <- state.pagecount - 1;
1756 if not (pagevisible state.birdseyepageno)
1757 then
1758 gotopage state.birdseyepageno 0.0
1759 else
1760 Glut.postRedisplay ()
1762 | _ -> ()
1765 | None ->
1766 begin match state.textentry with
1767 | None ->
1768 let y =
1769 match key with
1770 | Glut.KEY_F3 -> search state.searchpattern true; state.y
1771 | Glut.KEY_UP -> clamp (-conf.scrollincr)
1772 | Glut.KEY_DOWN -> clamp conf.scrollincr
1773 | Glut.KEY_PAGE_UP ->
1774 if Glut.getModifiers () land Glut.active_ctrl != 0
1775 then
1776 match state.layout with
1777 | [] -> state.y
1778 | l :: _ -> state.y - l.pagey
1779 else
1780 clamp (-conf.winh)
1781 | Glut.KEY_PAGE_DOWN ->
1782 if Glut.getModifiers () land Glut.active_ctrl != 0
1783 then
1784 match List.rev state.layout with
1785 | [] -> state.y
1786 | l :: _ -> getpagey l.pageno
1787 else
1788 clamp conf.winh
1789 | Glut.KEY_HOME -> addnav (); 0
1790 | Glut.KEY_END ->
1791 addnav ();
1792 state.maxy - (if conf.maxhfit then conf.winh else 0)
1794 | Glut.KEY_RIGHT when conf.zoom > 1.0 ->
1795 state.x <- state.x - 10;
1796 state.y
1797 | Glut.KEY_LEFT when conf.zoom > 1.0 ->
1798 state.x <- state.x + 10;
1799 state.y
1801 | _ -> state.y
1803 gotoy_and_clear_text y
1805 | Some (c, s, (Some (action, _) as onhist), onkey, ondone) ->
1806 let s =
1807 match key with
1808 | Glut.KEY_UP -> action HCprev
1809 | Glut.KEY_DOWN -> action HCnext
1810 | Glut.KEY_HOME -> action HCfirst
1811 | Glut.KEY_END -> action HClast
1812 | _ -> state.text
1814 state.textentry <- Some (c, s, onhist, onkey, ondone);
1815 Glut.postRedisplay ()
1817 | _ -> ()
1820 | Some (allowdel, active, first, outlines, qsearch) ->
1821 let maxrows = maxoutlinerows () in
1822 let calcfirst first active =
1823 if active > first
1824 then
1825 let rows = active - first in
1826 if rows > maxrows then active - maxrows else first
1827 else active
1829 let navigate incr =
1830 let active = active + incr in
1831 let active = max 0 (min active (Array.length outlines - 1)) in
1832 let first = calcfirst first active in
1833 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1834 Glut.postRedisplay ()
1836 let updownlevel incr =
1837 let len = Array.length outlines in
1838 let (_, curlevel, _, _) = outlines.(active) in
1839 let rec flow i =
1840 if i = len then i-1 else if i = -1 then 0 else
1841 let (_, l, _, _) = outlines.(i) in
1842 if l != curlevel then i else flow (i+incr)
1844 let active = flow active in
1845 let first = calcfirst first active in
1846 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1847 Glut.postRedisplay ()
1849 match key with
1850 | Glut.KEY_UP -> navigate ~-1
1851 | Glut.KEY_DOWN -> navigate 1
1852 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
1853 | Glut.KEY_PAGE_DOWN -> navigate maxrows
1855 | Glut.KEY_RIGHT when not allowdel -> updownlevel 1
1856 | Glut.KEY_LEFT when not allowdel -> updownlevel ~-1
1858 | Glut.KEY_HOME ->
1859 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1860 Glut.postRedisplay ()
1862 | Glut.KEY_END ->
1863 let active = Array.length outlines - 1 in
1864 let first = max 0 (active - maxrows) in
1865 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1866 Glut.postRedisplay ()
1868 | _ -> ()
1871 let drawplaceholder l =
1872 let margin = state.x + (conf.winw - (state.w + conf.scrollw)) / 2 in
1873 GlDraw.color (scalecolor 1.0);
1874 GlDraw.rect
1875 (float l.pagex, float l.pagedispy)
1876 (float (l.pagew + l.pagex), float (l.pagedispy + l.pagevh))
1878 let x = float (if margin < 0 then -margin else 0)
1879 and y = float (l.pagedispy + 13) in
1880 let font = Glut.BITMAP_8_BY_13 in
1881 GlDraw.color (0.0, 0.0, 0.0);
1882 GlPix.raster_pos ~x ~y ();
1883 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c))
1884 ("Loading " ^ string_of_int (l.pageno + 1));
1887 let now () = Unix.gettimeofday ();;
1889 let drawpage l =
1890 begin match getopaque l.pageno with
1891 | Some (opaque, _) when validopaque opaque ->
1892 if state.textentry = None
1893 then GlDraw.color (scalecolor 1.0)
1894 else GlDraw.color (scalecolor 0.4);
1895 let a = now () in
1896 draw (l.pagedispy, l.pagew, l.pagevh, l.pagey, conf.hlinks)
1897 opaque;
1898 let b = now () in
1899 let d = b-.a in
1900 vlog "draw %d %f sec" l.pageno d;
1902 | _ ->
1903 drawplaceholder l;
1904 end;
1905 if state.birdseye <> None && state.birdseyepageno = l.pageno
1906 then (
1907 GlDraw.polygon_mode `both `line;
1908 GlDraw.line_width 4.0;
1909 GlDraw.color (0.8, 0.0, 0.0);
1910 GlDraw.rect
1911 (float (l.pagex - 1), float (l.pagedispy - 1))
1912 (float (l.pagew + l.pagex + 1), float (l.pagedispy + l.pagevh + 1))
1914 GlDraw.line_width 1.0;
1915 GlDraw.polygon_mode `both `fill;
1919 let scrollph y =
1920 let maxy = state.maxy - (if conf.maxhfit then conf.winh else 0) in
1921 let sh = (float (maxy + conf.winh) /. float conf.winh) in
1922 let sh = float conf.winh /. sh in
1923 let sh = max sh (float conf.scrollh) in
1925 let percent =
1926 if state.y = state.maxy
1927 then 1.0
1928 else float y /. float maxy
1930 let position = (float conf.winh -. sh) *. percent in
1932 let position =
1933 if position +. sh > float conf.winh
1934 then float conf.winh -. sh
1935 else position
1937 position, sh;
1940 let scrollindicator () =
1941 GlDraw.color (0.64 , 0.64, 0.64);
1942 GlDraw.rect
1943 (float (conf.winw - conf.scrollw), 0.)
1944 (float conf.winw, float conf.winh)
1946 GlDraw.color (0.0, 0.0, 0.0);
1948 let position, sh = scrollph state.y in
1949 GlDraw.rect
1950 (float (conf.winw - conf.scrollw), position)
1951 (float conf.winw, position +. sh)
1955 let showsel margin =
1956 match state.mstate with
1957 | Mnone | Mscroll _ | Mpan _ ->
1960 | Msel ((x0, y0), (x1, y1)) ->
1961 let rec loop = function
1962 | l :: ls ->
1963 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1964 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1965 then
1966 match getopaque l.pageno with
1967 | Some (opaque, _) when validopaque opaque ->
1968 let oy = -l.pagey + l.pagedispy in
1969 seltext opaque
1970 (x0 - margin - state.x, y0,
1971 x1 - margin - state.x, y1) oy;
1973 | _ -> ()
1974 else loop ls
1975 | [] -> ()
1977 loop state.layout
1980 let showrects () =
1981 let panx = float state.x in
1982 Gl.enable `blend;
1983 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
1984 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1985 List.iter
1986 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
1987 List.iter (fun l ->
1988 if l.pageno = pageno
1989 then (
1990 let d = float (l.pagedispy - l.pagey) in
1991 GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5;
1992 GlDraw.begins `quads;
1994 GlDraw.vertex2 (x0+.panx, y0+.d);
1995 GlDraw.vertex2 (x1+.panx, y1+.d);
1996 GlDraw.vertex2 (x2+.panx, y2+.d);
1997 GlDraw.vertex2 (x3+.panx, y3+.d);
1999 GlDraw.ends ();
2001 ) state.layout
2002 ) state.rects
2004 Gl.disable `blend;
2007 let showoutline = function
2008 | None -> ()
2009 | Some (allowdel, active, first, outlines, qsearch) ->
2010 Gl.enable `blend;
2011 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2012 GlDraw.color (0., 0., 0.) ~alpha:0.85;
2013 GlDraw.rect (0., 0.) (float conf.winw, float conf.winh);
2014 Gl.disable `blend;
2016 GlDraw.color (1., 1., 1.);
2017 let font = Glut.BITMAP_9_BY_15 in
2018 let draw_string x y s =
2019 GlPix.raster_pos ~x ~y ();
2020 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
2022 let rec loop row =
2023 if row = Array.length outlines || (row - first) * 16 > conf.winh
2024 then ()
2025 else (
2026 let (s, l, _, _) = outlines.(row) in
2027 let y = (row - first) * 16 in
2028 let x = 5 + 15*l in
2029 if row = active
2030 then (
2031 Gl.enable `blend;
2032 GlDraw.polygon_mode `both `line;
2033 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
2034 GlDraw.color (1., 1., 1.) ~alpha:0.9;
2035 GlDraw.rect (0., float (y + 1))
2036 (float (conf.winw - 1), float (y + 18));
2037 GlDraw.polygon_mode `both `fill;
2038 Gl.disable `blend;
2039 GlDraw.color (1., 1., 1.);
2041 draw_string (float x) (float (y + 16)) s;
2042 loop (row+1)
2045 loop first
2048 let display () =
2049 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
2050 GlDraw.viewport margin 0 state.w conf.winh;
2051 pagematrix ();
2052 if state.birdseye <> None
2053 then
2054 GlClear.color (0.5, 0.5, 0.55)
2055 else
2056 GlClear.color (scalecolor 0.5)
2058 GlClear.clear [`color];
2059 if state.x != 0
2060 then (
2061 let x = float state.x in
2062 GlMat.translate ~x ();
2064 if conf.zoom > 1.0
2065 then (
2066 Gl.enable `scissor_test;
2067 GlMisc.scissor 0 0 (conf.winw - conf.scrollw) conf.winh;
2069 List.iter drawpage state.layout;
2070 if conf.zoom > 1.0
2071 then
2072 Gl.disable `scissor_test
2074 if state.x != 0
2075 then (
2076 let x = -.float state.x in
2077 GlMat.translate ~x ();
2079 showrects ();
2080 showsel margin;
2081 GlDraw.viewport 0 0 conf.winw conf.winh;
2082 winmatrix ();
2083 scrollindicator ();
2084 showoutline state.outline;
2085 enttext ();
2086 Glut.swapBuffers ();
2089 let getunder x y =
2090 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
2091 let x = x - margin - state.x in
2092 let rec f = function
2093 | l :: rest ->
2094 begin match getopaque l.pageno with
2095 | Some (opaque, _) when validopaque opaque ->
2096 let y = y - l.pagedispy in
2097 if y > 0
2098 then
2099 let y = l.pagey + y in
2100 let x = x - l.pagex in
2101 match whatsunder opaque x y with
2102 | Unone -> f rest
2103 | under -> under
2104 else
2105 f rest
2106 | _ ->
2107 f rest
2109 | [] -> Unone
2111 f state.layout
2114 let mouse ~button ~bstate ~x ~y =
2115 match button with
2116 | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP ->
2117 let incr =
2118 if n = 3
2119 then
2120 -conf.scrollincr
2121 else
2122 conf.scrollincr
2124 let incr = incr * 2 in
2125 let y = clamp incr in
2126 gotoy_and_clear_text y
2128 | Glut.LEFT_BUTTON when state.outline = None
2129 && Glut.getModifiers () land Glut.active_ctrl != 0 ->
2130 if bstate = Glut.DOWN
2131 then (
2132 Glut.setCursor Glut.CURSOR_CROSSHAIR;
2133 state.mstate <- Mpan (x, y)
2135 else
2136 state.mstate <- Mnone
2138 | Glut.LEFT_BUTTON
2139 when state.outline = None && x > conf.winw - conf.scrollw ->
2140 if bstate = Glut.DOWN
2141 then
2142 let position, sh = scrollph state.y in
2143 if y > truncate position && y < truncate (position +. sh)
2144 then
2145 state.mstate <- Mscroll
2146 else
2147 let percent = float y /. float conf.winh in
2148 let desty = truncate (float (state.maxy - conf.winh) *. percent) in
2149 gotoy desty;
2150 state.mstate <- Mscroll
2151 else
2152 state.mstate <- Mnone
2154 | Glut.LEFT_BUTTON when state.outline = None && state.birdseye <> None ->
2155 begin match state.birdseye with
2156 | Some vals ->
2157 let margin = (conf.winw - (state.w + conf.scrollw)) / 2 in
2158 let rec loop = function
2159 | [] -> ()
2160 | l :: rest ->
2161 if y > l.pagedispy && y < l.pagedispy + l.pagevh
2162 && x > margin && x < margin + l.pagew
2163 then (
2164 let y = getpagey l.pageno in
2165 state.y <- y;
2166 birdseyeoff vals;
2167 reshape conf.winw conf.winh;
2169 else loop rest
2171 loop state.layout;
2172 | None -> () (* impossible *)
2175 | Glut.LEFT_BUTTON when state.outline = None ->
2176 let dest = if bstate = Glut.DOWN then getunder x y else Unone in
2177 begin match dest with
2178 | Ulinkgoto (pageno, top) ->
2179 if pageno >= 0
2180 then
2181 gotopage1 pageno top
2183 | Ulinkuri s ->
2184 print_endline s
2186 | Unone when bstate = Glut.DOWN ->
2187 Glut.setCursor Glut.CURSOR_CROSSHAIR;
2188 state.mstate <- Mpan (x, y);
2190 | Unone | Utext _ ->
2191 if bstate = Glut.DOWN
2192 then (
2193 if conf.angle mod 360 = 0
2194 then (
2195 state.mstate <- Msel ((x, y), (x, y));
2196 Glut.postRedisplay ()
2199 else (
2200 match state.mstate with
2201 | Mnone -> ()
2203 | Mscroll ->
2204 state.mstate <- Mnone
2206 | Mpan _ ->
2207 Glut.setCursor Glut.CURSOR_INHERIT;
2208 state.mstate <- Mnone
2210 | Msel ((x0, y0), (x1, y1)) ->
2211 let f l =
2212 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
2213 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
2214 then
2215 match getopaque l.pageno with
2216 | Some (opaque, _) when validopaque opaque ->
2217 copysel opaque
2218 | _ -> ()
2220 List.iter f state.layout;
2221 copysel ""; (* ugly *)
2222 Glut.setCursor Glut.CURSOR_INHERIT;
2223 state.mstate <- Mnone;
2227 | _ ->
2230 let mouse ~button ~state ~x ~y = mouse button state x y;;
2232 let motion ~x ~y =
2233 if state.outline = None
2234 then
2235 match state.mstate with
2236 | Mnone -> ()
2238 | Mpan (x0, y0) ->
2239 let dx = x - x0
2240 and dy = y0 - y in
2241 state.mstate <- Mpan (x, y);
2242 if conf.zoom > 1.0 then state.x <- state.x + dx;
2243 let y = clamp dy in
2244 gotoy_and_clear_text y
2246 | Msel (a, _) ->
2247 state.mstate <- Msel (a, (x, y));
2248 Glut.postRedisplay ()
2250 | Mscroll ->
2251 let y = min conf.winh (max 0 y) in
2252 let percent = float y /. float conf.winh in
2253 let y = truncate (float (state.maxy - conf.winh) *. percent) in
2254 gotoy_and_clear_text y
2257 let pmotion ~x ~y =
2258 if state.outline = None && state.birdseye = None
2259 then
2260 match state.mstate with
2261 | Mnone ->
2262 begin match getunder x y with
2263 | Unone -> Glut.setCursor Glut.CURSOR_INHERIT
2264 | Ulinkuri uri ->
2265 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
2266 Glut.setCursor Glut.CURSOR_INFO
2267 | Ulinkgoto (page, y) ->
2268 if conf.underinfo then showtext 'p' ("age: " ^ string_of_int page);
2269 Glut.setCursor Glut.CURSOR_INFO
2270 | Utext s ->
2271 if conf.underinfo then showtext 'f' ("ont: " ^ s);
2272 Glut.setCursor Glut.CURSOR_TEXT
2275 | Mpan _ | Msel _ | Mscroll ->
2279 module State =
2280 struct
2281 open Parser
2283 let home =
2285 match Sys.os_type with
2286 | "Win32" -> Sys.getenv "HOMEPATH"
2287 | _ -> Sys.getenv "HOME"
2288 with exn ->
2289 prerr_endline
2290 ("Can not determine home directory location: " ^
2291 Printexc.to_string exn);
2295 let config_of c attrs =
2296 let apply c k v =
2298 match k with
2299 | "scroll-bar-width" -> { c with scrollw = max 0 (int_of_string v) }
2300 | "scroll-handle-height" -> { c with scrollh = max 0 (int_of_string v) }
2301 | "case-insensitive-search" -> { c with icase = bool_of_string v }
2302 | "preload" -> { c with preload = bool_of_string v }
2303 | "page-bias" -> { c with pagebias = int_of_string v }
2304 | "scroll-step" -> { c with scrollincr = max 1 (int_of_string v) }
2305 | "max-height-fit" -> { c with maxhfit = bool_of_string v }
2306 | "crop-hack" -> { c with crophack = bool_of_string v }
2307 | "throttle" -> { c with showall = bool_of_string v }
2308 | "highlight-links" -> { c with hlinks = bool_of_string v }
2309 | "under-cursor-info" -> { c with underinfo = bool_of_string v }
2310 | "vertical-margin" -> { c with interpagespace = max 0 (int_of_string v) }
2311 | "zoom" ->
2312 let zoom = float_of_string v /. 100. in
2313 let zoom = max 0.01 (min 2.2 zoom) in
2314 { c with zoom = zoom }
2315 | "presentation" -> { c with presentation = bool_of_string v }
2316 | "rotation-angle" -> { c with angle = int_of_string v }
2317 | "width" -> { c with winw = max 20 (int_of_string v) }
2318 | "height" -> { c with winh = max 20 (int_of_string v) }
2319 | "persistent-bookmarks" -> { c with savebmarks = bool_of_string v }
2320 | "proportional-display" -> { c with proportional = bool_of_string v }
2321 | "pixmap-cache-size" -> { c with memlimit = max 2 (int_of_string v) }
2322 | "tex-count" -> { c with texcount = max 1 (int_of_string v) }
2323 | "slice-height" -> { c with sliceheight = max 2 (int_of_string v) }
2324 | _ -> c
2325 with exn ->
2326 prerr_endline ("Error processing attribute (`" ^
2327 k ^ "'=`" ^ v ^ "'): " ^ Printexc.to_string exn);
2330 let rec fold c = function
2331 | [] -> c
2332 | (k, v) :: rest ->
2333 let c = apply c k v in
2334 fold c rest
2336 fold c attrs;
2339 let bookmark_of attrs =
2340 let rec fold title page rely = function
2341 | ("title", v) :: rest -> fold v page rely rest
2342 | ("page", v) :: rest -> fold title v rely rest
2343 | ("rely", v) :: rest -> fold title page v rest
2344 | _ :: rest -> fold title page rely rest
2345 | [] -> title, page, rely
2347 fold "invalid" "0" "0" attrs
2350 let setconf dst src =
2351 dst.scrollw <- src.scrollw;
2352 dst.scrollh <- src.scrollh;
2353 dst.icase <- src.icase;
2354 dst.preload <- src.preload;
2355 dst.pagebias <- src.pagebias;
2356 dst.verbose <- src.verbose;
2357 dst.scrollincr <- src.scrollincr;
2358 dst.maxhfit <- src.maxhfit;
2359 dst.crophack <- src.crophack;
2360 dst.autoscroll <- src.autoscroll;
2361 dst.showall <- src.showall;
2362 dst.hlinks <- src.hlinks;
2363 dst.underinfo <- src.underinfo;
2364 dst.interpagespace <- src.interpagespace;
2365 dst.zoom <- src.zoom;
2366 dst.presentation <- src.presentation;
2367 dst.angle <- src.angle;
2368 dst.winw <- src.winw;
2369 dst.winh <- src.winh;
2370 dst.savebmarks <- src.savebmarks;
2371 dst.memlimit <- src.memlimit;
2372 dst.proportional <- src.proportional;
2373 dst.texcount <- src.texcount;
2374 dst.sliceheight <- src.sliceheight;
2377 let unent s =
2378 let l = String.length s in
2379 let b = Buffer.create l in
2380 unent b s 0 l;
2381 Buffer.contents b;
2384 let get s =
2385 let h = Hashtbl.create 10 in
2386 let dc = { defconf with angle = defconf.angle } in
2387 let rec toplevel v t spos epos =
2388 match t with
2389 | Vdata | Vcdata | Vend -> v
2390 | Vopen ("llppconfig", attrs, closed) ->
2391 if closed
2392 then v
2393 else { v with f = llppconfig }
2394 | Vopen _ ->
2395 error "unexpected subelement at top level" s spos
2396 | Vclose tag -> error "unexpected close at top level" s spos
2398 and llppconfig v t spos epos =
2399 match t with
2400 | Vdata | Vcdata | Vend -> v
2401 | Vopen ("defaults", attrs, closed) ->
2402 let c = config_of dc attrs in
2403 setconf dc c;
2404 if closed
2405 then v
2406 else { v with f = skip "defaults" (fun () -> v) }
2408 | Vopen ("doc", attrs, closed) ->
2409 let pathent =
2411 List.assoc "path" attrs
2412 with Not_found -> error "doc is missing path attribute" s spos
2414 let path = unent pathent in
2415 let c = config_of dc attrs in
2416 let y =
2418 float_of_string (List.assoc "rely" attrs)
2419 with
2420 | Not_found -> 0.0
2421 | exn ->
2422 dolog "error while accesing rely: %s" (Printexc.to_string exn);
2425 let x =
2427 int_of_string (List.assoc "pan" attrs)
2428 with
2429 | Not_found -> 0
2430 | exn ->
2431 dolog "error while accesing rely: %s" (Printexc.to_string exn);
2434 if closed
2435 then (Hashtbl.add h path (c, [], x, y); v)
2436 else { v with f = doc path x y c [] }
2438 | Vopen (tag, _, closed) ->
2439 error "unexpected subelement in llppconfig" s spos
2441 | Vclose "llppconfig" -> { v with f = toplevel }
2442 | Vclose tag -> error "unexpected close in llppconfig" s spos
2444 and doc path x y c bookmarks v t spos epos =
2445 match t with
2446 | Vdata | Vcdata -> v
2447 | Vend -> error "unexpected end of input in doc" s spos
2448 | Vopen ("bookmarks", attrs, closed) ->
2449 { v with f = pbookmarks path x y c bookmarks }
2451 | Vopen (tag, _, _) ->
2452 error "unexpected subelement in doc" s spos
2454 | Vclose "doc" ->
2455 Hashtbl.add h path (c, List.rev bookmarks, x, y);
2456 { v with f = llppconfig }
2458 | Vclose tag -> error "unexpected close in doc" s spos
2460 and pbookmarks path x y c bookmarks v t spos epos =
2461 match t with
2462 | Vdata | Vcdata -> v
2463 | Vend -> error "unexpected end of input in bookmarks" s spos
2464 | Vopen ("item", attrs, closed) ->
2465 let titleent, spage, srely = bookmark_of attrs in
2466 let page =
2468 int_of_string spage
2469 with exn ->
2470 dolog "Failed to convert page %S to integer: %s"
2471 spage (Printexc.to_string exn);
2474 let rely =
2476 float_of_string srely
2477 with exn ->
2478 dolog "Failed to convert rely %S to real: %s"
2479 srely (Printexc.to_string exn);
2482 let bookmarks = (unent titleent, 0, page, rely) :: bookmarks in
2483 if closed
2484 then { v with f = pbookmarks path x y c bookmarks }
2485 else
2486 let f () = v in
2487 { v with f = skip "item" f }
2489 | Vopen _ ->
2490 error "unexpected subelement in bookmarks" s spos
2492 | Vclose "bookmarks" ->
2493 { v with f = doc path x y c bookmarks }
2495 | Vclose tag -> error "unexpected close in bookmarks" s spos
2497 and skip tag f v t spos epos =
2498 match t with
2499 | Vdata | Vcdata -> v
2500 | Vend ->
2501 error ("unexpected end of input in skipped " ^ tag) s spos
2502 | Vopen (tag', _, closed) ->
2503 if closed
2504 then v
2505 else
2506 let f' () = { v with f = skip tag f } in
2507 { v with f = skip tag' f' }
2508 | Vclose ctag ->
2509 if tag = ctag
2510 then f ()
2511 else error ("unexpected close in skipped " ^ tag) s spos
2514 parse { f = toplevel; accu = () } s;
2515 h, dc;
2518 let do_load f ic =
2520 let len = in_channel_length ic in
2521 let s = String.create len in
2522 really_input ic s 0 len;
2523 f s;
2524 with
2525 | Parse_error (msg, s, pos) ->
2526 let subs = subs s pos in
2527 let s = Printf.sprintf "%s: at %d [..%s..]" msg pos subs in
2528 failwith ("parse error: " ^ s)
2530 | exn ->
2531 failwith ("config load error: " ^ Printexc.to_string exn)
2534 let path =
2535 let dir =
2537 let dir = Filename.concat home ".config" in
2538 if Sys.is_directory dir then dir else home
2539 with _ -> home
2541 Filename.concat dir "llpp.conf"
2544 let load1 f =
2545 if Sys.file_exists path
2546 then
2547 match
2548 (try Some (open_in_bin path)
2549 with exn ->
2550 prerr_endline
2551 ("Error opening configuation file `" ^ path ^ "': " ^
2552 Printexc.to_string exn);
2553 None
2555 with
2556 | Some ic ->
2557 begin try
2558 f (do_load get ic)
2559 with exn ->
2560 prerr_endline
2561 ("Error loading configuation from `" ^ path ^ "': " ^
2562 Printexc.to_string exn);
2563 end;
2564 close_in ic;
2566 | None -> ()
2567 else
2568 f (Hashtbl.create 0, defconf)
2571 let load () =
2572 let f (h, dc) =
2573 let pc, pb, px, py =
2575 Hashtbl.find h state.path
2576 with Not_found -> dc, [], 0, 0.0
2578 setconf defconf dc;
2579 setconf conf pc;
2580 state.bookmarks <- pb;
2581 state.x <- px;
2582 cbput state.hists.nav py;
2584 load1 f
2587 let add_attrs bb always dc c =
2588 let ob s a b =
2589 if always || a != b
2590 then Printf.bprintf bb "\n %s='%b'" s a
2591 and oi s a b =
2592 if always || a != b
2593 then Printf.bprintf bb "\n %s='%d'" s a
2594 and oz s a b =
2595 if always || a <> b
2596 then Printf.bprintf bb "\n %s='%f'" s (a*.100.)
2598 let w, h =
2599 if always
2600 then dc.winw, dc.winh
2601 else
2602 match state.fullscreen with
2603 | Some wh -> wh
2604 | None -> c.winw, c.winh
2606 let zoom, presentation, interpagespace =
2607 if always
2608 then dc.zoom, dc.presentation, dc.interpagespace
2609 else
2610 match state.birdseye with
2611 | Some (zoom, _, presentation, interpagespace) ->
2612 (zoom, presentation, interpagespace)
2613 | None -> c.zoom, c.presentation, c.interpagespace
2615 oi "width" w dc.winw;
2616 oi "height" h dc.winh;
2617 oi "scroll-bar-width" c.scrollw dc.scrollw;
2618 oi "scroll-handle-height" c.scrollh dc.scrollh;
2619 ob "case-insensitive-search" c.icase dc.icase;
2620 ob "preload" c.preload dc.preload;
2621 oi "page-bias" c.pagebias dc.pagebias;
2622 oi "scroll-step" c.scrollincr dc.scrollincr;
2623 ob "max-height-fit" c.maxhfit dc.maxhfit;
2624 ob "crop-hack" c.crophack dc.crophack;
2625 ob "throttle" c.showall dc.showall;
2626 ob "highlight-links" c.hlinks dc.hlinks;
2627 ob "under-cursor-info" c.underinfo dc.underinfo;
2628 oi "vertical-margin" interpagespace dc.interpagespace;
2629 oz "zoom" zoom dc.zoom;
2630 ob "presentation" presentation dc.presentation;
2631 oi "rotation-angle" c.angle dc.angle;
2632 ob "persistent-bookmarks" c.savebmarks dc.savebmarks;
2633 ob "proportional-display" c.proportional dc.proportional;
2634 oi "pixmap-cache-size" c.memlimit dc.memlimit;
2635 oi "texcount" c.texcount dc.texcount;
2636 oi "slice-height" c.sliceheight dc.sliceheight;
2639 let save () =
2640 let bb = Buffer.create 32768 in
2641 let f (h, dc) =
2642 Buffer.add_string bb "<llppconfig>\n<defaults ";
2643 add_attrs bb true dc dc;
2644 Buffer.add_string bb "/>\n";
2646 let adddoc path x y c bookmarks =
2647 if bookmarks == [] && c = dc && y = 0.0
2648 then ()
2649 else (
2650 Printf.bprintf bb "<doc path='%s'"
2651 (enent path 0 (String.length path));
2653 if y <> 0.0
2654 then Printf.bprintf bb " rely='%f'" y;
2656 if x != 0
2657 then Printf.bprintf bb " pan='%d'" x;
2659 add_attrs bb false dc c;
2661 begin match bookmarks with
2662 | [] -> Buffer.add_string bb "/>\n"
2663 | _ ->
2664 Buffer.add_string bb ">\n<bookmarks>\n";
2665 List.iter (fun (title, _level, page, rely) ->
2666 Printf.bprintf bb
2667 "<item title='%s' page='%d' rely='%f'/>\n"
2668 (enent title 0 (String.length title))
2669 page
2670 rely
2671 ) bookmarks;
2672 Buffer.add_string bb "</bookmarks>\n</doc>\n";
2673 end;
2677 let x =
2678 match state.birdseye with
2679 | Some (_, x, _, _) -> x
2680 | None -> state.x
2682 adddoc state.path x (yratio state.y) conf
2683 (if conf.savebmarks then state.bookmarks else []);
2685 Hashtbl.iter (fun path (c, bookmarks, x, y) ->
2686 if path <> state.path
2687 then
2688 adddoc path x y c bookmarks
2689 ) h;
2690 Buffer.add_string bb "</llppconfig>";
2692 load1 f;
2693 if Buffer.length bb > 0
2694 then
2696 let tmp = path ^ ".tmp" in
2697 let oc = open_out_bin tmp in
2698 Buffer.output_buffer oc bb;
2699 close_out oc;
2700 Sys.rename tmp path;
2701 with exn ->
2702 prerr_endline
2703 ("error while saving configuration: " ^ Printexc.to_string exn)
2705 end;;
2707 let () =
2708 Arg.parse
2709 ["-p", Arg.String (fun s -> state.password <- s) , "password"]
2710 (fun s -> state.path <- s)
2711 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\noptions:")
2713 let path =
2714 if String.length state.path = 0
2715 then (prerr_endline "filename missing"; exit 1)
2716 else (
2717 if Filename.is_relative state.path
2718 then Filename.concat (Sys.getcwd ()) state.path
2719 else state.path
2722 state.path <- path;
2724 State.load ();
2726 let _ = Glut.init Sys.argv in
2727 let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in
2728 let () = Glut.initWindowSize conf.winw conf.winh in
2729 let _ = Glut.createWindow ("llpp " ^ Filename.basename path) in
2731 let csock, ssock =
2732 if Sys.os_type = "Unix"
2733 then
2734 Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
2735 else
2736 let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, 1337) in
2737 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2738 Unix.setsockopt sock Unix.SO_REUSEADDR true;
2739 Unix.bind sock addr;
2740 Unix.listen sock 1;
2741 let csock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2742 Unix.connect csock addr;
2743 let ssock, _ = Unix.accept sock in
2744 Unix.close sock;
2745 let opts sock =
2746 Unix.setsockopt sock Unix.TCP_NODELAY true;
2747 Unix.setsockopt_optint sock Unix.SO_LINGER None;
2749 opts ssock;
2750 opts csock;
2751 at_exit (fun () -> Unix.shutdown ssock Unix.SHUTDOWN_ALL);
2752 ssock, csock
2755 let () = Glut.displayFunc display in
2756 let () = Glut.reshapeFunc reshape in
2757 let () = Glut.keyboardFunc keyboard in
2758 let () = Glut.specialFunc special in
2759 let () = Glut.idleFunc (Some idle) in
2760 let () = Glut.mouseFunc mouse in
2761 let () = Glut.motionFunc motion in
2762 let () = Glut.passiveMotionFunc pmotion in
2764 init ssock (conf.angle, conf.proportional, conf.texcount, conf.sliceheight);
2765 state.csock <- csock;
2766 state.ssock <- ssock;
2767 state.text <- "Opening " ^ path;
2768 writeopen state.path state.password;
2770 at_exit State.save;
2772 let rec handlelablglutbug () =
2774 Glut.mainLoop ();
2775 with Glut.BadEnum "key in special_of_int" ->
2776 showtext '!' " LablGlut bug: special key not recognized";
2777 handlelablglutbug ()
2779 handlelablglutbug ();