Add ability to zoom out (set horizontal margins)
[llpp.git] / main.ml
blob302fe6945350bcb53648b7f991e8cebb7172d173
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 external init : Unix.file_descr -> unit = "ml_init";;
12 external draw : (int * int * int * int * bool) -> string -> unit = "ml_draw";;
13 external seltext : string -> (int * int * int * int) -> int -> unit =
14 "ml_seltext";;
15 external copysel : string -> unit = "ml_copysel";;
16 external getpagewh : int -> float array = "ml_getpagewh";;
17 external whatsunder : string -> int -> int -> under = "ml_whatsunder";;
19 type mstate = Msel of ((int * int) * (int * int)) | Mnone;;
21 type 'a circbuf =
22 { store : 'a array
23 ; mutable rc : int
24 ; mutable wc : int
25 ; mutable len : int
29 type textentry = (char * string * onhist option * onkey * ondone)
30 and onkey = string -> int -> te
31 and ondone = string -> unit
32 and onhist = histcmd -> string
33 and histcmd = HCnext | HCprev | HCfirst | HClast
34 and te =
35 | TEstop
36 | TEdone of string
37 | TEcont of string
38 | TEswitch of textentry
41 let cbnew n v =
42 { store = Array.create n v
43 ; rc = 0
44 ; wc = 0
45 ; len = 0
49 let cblen b = Array.length b.store;;
51 let cbput b v =
52 let len = cblen b in
53 b.store.(b.wc) <- v;
54 b.wc <- (b.wc + 1) mod len;
55 b.len <- min (b.len + 1) len;
58 let cbpeekw b = b.store.(b.wc);;
60 let cbget b dir =
61 if b.len = 0
62 then b.store.(0)
63 else
64 let rc = b.rc + dir in
65 let rc = if rc = -1 then b.len - 1 else rc in
66 let rc = if rc = b.len then 0 else rc in
67 b.rc <- rc;
68 b.store.(rc);
71 let cbrfollowlen b =
72 b.rc <- b.len;
75 let cbclear b v =
76 b.len <- 0;
77 Array.fill b.store 0 (Array.length b.store) v;
80 type layout =
81 { pageno : int
82 ; pagedimno : int
83 ; pagew : int
84 ; pageh : int
85 ; pagedispy : int
86 ; pagey : int
87 ; pagevh : int
91 type conf =
92 { mutable scrollw : int
93 ; mutable scrollh : int
94 ; mutable icase : bool
95 ; mutable preload : bool
96 ; mutable pagebias : int
97 ; mutable verbose : bool
98 ; mutable scrollincr : int
99 ; mutable maxhfit : bool
100 ; mutable crophack : bool
101 ; mutable autoscroll : bool
102 ; mutable showall : bool
103 ; mutable hlinks : bool
104 ; mutable underinfo : bool
105 ; mutable interpagespace : int
106 ; mutable margin : int
110 type outline = string * int * int * float;;
111 type outlines =
112 | Oarray of outline array
113 | Olist of outline list
114 | Onarrow of outline array * outline array
117 type rect = (float * float * float * float * float * float * float * float);;
119 type state =
120 { mutable csock : Unix.file_descr
121 ; mutable ssock : Unix.file_descr
122 ; mutable w : int
123 ; mutable h : int
124 ; mutable winw : int
125 ; mutable rotate : int
126 ; mutable y : int
127 ; mutable ty : float
128 ; mutable maxy : int
129 ; mutable layout : layout list
130 ; pagemap : ((int * int * int), string) Hashtbl.t
131 ; mutable pages : (int * int * int) list
132 ; mutable pagecount : int
133 ; pagecache : string circbuf
134 ; mutable rendering : bool
135 ; mutable mstate : mstate
136 ; mutable searchpattern : string
137 ; mutable rects : (int * int * rect) list
138 ; mutable rects1 : (int * int * rect) list
139 ; mutable text : string
140 ; mutable fullscreen : (int * int) option
141 ; mutable textentry : textentry option
142 ; mutable outlines : outlines
143 ; mutable outline : (bool * int * int * outline array * string) option
144 ; mutable bookmarks : outline list
145 ; mutable path : string
146 ; mutable password : string
147 ; mutable invalidated : int
148 ; mutable colorscale : float
149 ; hists : hists
151 and hists =
152 { pat : string circbuf
153 ; pag : string circbuf
154 ; nav : float circbuf
158 let conf =
159 { scrollw = 5
160 ; scrollh = 12
161 ; icase = true
162 ; preload = true
163 ; pagebias = 0
164 ; verbose = false
165 ; scrollincr = 24
166 ; maxhfit = true
167 ; crophack = false
168 ; autoscroll = false
169 ; showall = false
170 ; hlinks = false
171 ; underinfo = false
172 ; interpagespace = 2
173 ; margin = 0
177 let state =
178 { csock = Unix.stdin
179 ; ssock = Unix.stdin
180 ; w = 900
181 ; h = 900
182 ; winw = 900
183 ; rotate = 0
184 ; y = 0
185 ; ty = 0.0
186 ; layout = []
187 ; maxy = max_int
188 ; pagemap = Hashtbl.create 10
189 ; pagecache = cbnew 10 ""
190 ; pages = []
191 ; pagecount = 0
192 ; rendering = false
193 ; mstate = Mnone
194 ; rects = []
195 ; rects1 = []
196 ; text = ""
197 ; fullscreen = None
198 ; textentry = None
199 ; searchpattern = ""
200 ; outlines = Olist []
201 ; outline = None
202 ; bookmarks = []
203 ; path = ""
204 ; password = ""
205 ; invalidated = 0
206 ; hists =
207 { nav = cbnew 100 0.0
208 ; pat = cbnew 20 ""
209 ; pag = cbnew 10 ""
211 ; colorscale = 1.0
215 let vlog fmt =
216 if conf.verbose
217 then
218 Printf.kprintf prerr_endline fmt
219 else
220 Printf.kprintf ignore fmt
223 let writecmd fd s =
224 let len = String.length s in
225 let n = 4 + len in
226 let b = Buffer.create n in
227 Buffer.add_char b (Char.chr ((len lsr 24) land 0xff));
228 Buffer.add_char b (Char.chr ((len lsr 16) land 0xff));
229 Buffer.add_char b (Char.chr ((len lsr 8) land 0xff));
230 Buffer.add_char b (Char.chr ((len lsr 0) land 0xff));
231 Buffer.add_string b s;
232 let s' = Buffer.contents b in
233 let n' = Unix.write fd s' 0 n in
234 if n' != n then failwith "write failed";
237 let readcmd fd =
238 let s = "xxxx" in
239 let n = Unix.read fd s 0 4 in
240 if n != 4 then failwith "incomplete read(len)";
241 let len = 0
242 lor (Char.code s.[0] lsl 24)
243 lor (Char.code s.[1] lsl 16)
244 lor (Char.code s.[2] lsl 8)
245 lor (Char.code s.[3] lsl 0)
247 let s = String.create len in
248 let n = Unix.read fd s 0 len in
249 if n != len then failwith "incomplete read(data)";
253 let yratio y =
254 if y = state.maxy
255 then 1.0
256 else float y /. float state.maxy
259 let makecmd s l =
260 let b = Buffer.create 10 in
261 Buffer.add_string b s;
262 let rec combine = function
263 | [] -> b
264 | x :: xs ->
265 Buffer.add_char b ' ';
266 let s =
267 match x with
268 | `b b -> if b then "1" else "0"
269 | `s s -> s
270 | `i i -> string_of_int i
271 | `f f -> string_of_float f
272 | `I f -> string_of_int (truncate f)
274 Buffer.add_string b s;
275 combine xs;
277 combine l;
280 let wcmd s l =
281 let cmd = Buffer.contents (makecmd s l) in
282 writecmd state.csock cmd;
285 let calcheight () =
286 let rec f pn ph fh l =
287 match l with
288 | (n, _, h) :: rest ->
289 let fh = fh + (n - pn) * (ph + conf.interpagespace) in
290 f n h fh rest
292 | [] ->
293 let fh = fh + ((ph + conf.interpagespace) * (state.pagecount - pn)) in
294 max 0 fh
296 let fh = f 0 0 0 state.pages in
300 let getpageyh pageno =
301 let rec f pn ph y l =
302 match l with
303 | (n, _, h) :: rest ->
304 if n >= pageno
305 then
306 y + (pageno - pn) * (ph + conf.interpagespace), h
307 else
308 let y = y + (n - pn) * (ph + conf.interpagespace) in
309 f n h y rest
311 | [] ->
312 y + (pageno - pn) * (ph + conf.interpagespace), ph
314 f 0 0 0 state.pages;
317 let getpagey pageno = fst (getpageyh pageno);;
319 let layout y sh =
320 let ips = conf.interpagespace in
321 let rec f ~pageno ~pdimno ~prev ~vy ~py ~dy ~pdims ~cacheleft ~accu =
322 if pageno = state.pagecount || cacheleft = 0
323 then accu
324 else
325 let ((_, w, h) as curr), rest, pdimno =
326 match pdims with
327 | ((pageno', _, _) as curr) :: rest when pageno' = pageno ->
328 curr, rest, pdimno + 1
329 | _ ->
330 prev, pdims, pdimno
332 let pageno' = pageno + 1 in
333 if py + h > vy
334 then
335 let py' = vy - py in
336 let vh = h - py' in
337 if dy + vh > sh
338 then
339 let vh = sh - dy in
340 if vh <= 0
341 then
342 accu
343 else
344 let e =
345 { pageno = pageno
346 ; pagedimno = pdimno
347 ; pagew = w
348 ; pageh = h
349 ; pagedispy = dy
350 ; pagey = py'
351 ; pagevh = vh
354 e :: accu
355 else
356 let e =
357 { pageno = pageno
358 ; pagedimno = pdimno
359 ; pagew = w
360 ; pageh = h
361 ; pagedispy = dy
362 ; pagey = py'
363 ; pagevh = vh
366 let accu = e :: accu in
367 f ~pageno:pageno'
368 ~pdimno
369 ~prev:curr
370 ~vy:(vy + vh)
371 ~py:(py + h)
372 ~dy:(dy + vh + ips)
373 ~pdims:rest
374 ~cacheleft:(pred cacheleft)
375 ~accu
376 else (
377 let py' = vy - py in
378 let vh = h - py' in
379 let t = ips + vh in
380 let dy, py = if t < 0 then 0, py + h + ips else t, py + h - vh in
381 f ~pageno:pageno'
382 ~pdimno
383 ~prev:curr
387 ~pdims:rest
388 ~cacheleft
389 ~accu
392 if state.invalidated = 0
393 then
394 let accu =
396 ~pageno:0
397 ~pdimno:~-1
398 ~prev:(0,0,0)
399 ~vy:y
400 ~py:0
401 ~dy:0
402 ~pdims:state.pages
403 ~cacheleft:(cblen state.pagecache)
404 ~accu:[]
406 state.maxy <- calcheight ();
407 List.rev accu
408 else
412 let clamp incr =
413 let y = state.y + incr in
414 let y = max 0 y in
415 let y = min y (state.maxy - (if conf.maxhfit then state.h else 0)) in
419 let getopaque pageno =
420 try Some (Hashtbl.find state.pagemap (pageno + 1, state.w, state.rotate))
421 with Not_found -> None
424 let cache pageno opaque =
425 Hashtbl.replace state.pagemap (pageno + 1, state.w, state.rotate) opaque
428 let validopaque opaque = String.length opaque > 0;;
430 let render l =
431 match getopaque l.pageno with
432 | None when not state.rendering ->
433 state.rendering <- true;
434 cache l.pageno "";
435 wcmd "render" [`i (l.pageno + 1)
436 ;`i l.pagedimno
437 ;`i l.pagew
438 ;`i l.pageh];
440 | _ -> ()
443 let loadlayout layout =
444 let rec f all = function
445 | l :: ls ->
446 begin match getopaque l.pageno with
447 | None -> render l; f false ls
448 | Some opaque -> f (all && validopaque opaque) ls
450 | [] -> all
452 f (layout <> []) layout;
455 let preload () =
456 if conf.preload
457 then
458 let evictedvisible =
459 let evictedopaque = cbpeekw state.pagecache in
460 List.exists (fun l ->
461 match getopaque l.pageno with
462 | Some opaque when validopaque opaque ->
463 evictedopaque = opaque
464 | otherwise -> false
465 ) state.layout
467 if not evictedvisible
468 then
469 let y = if state.y < state.h then 0 else state.y - state.h in
470 let pages = layout y (state.h*3) in
471 List.iter render pages;
474 let gotoy y =
475 let y = max 0 y in
476 let y = min state.maxy y in
477 let pages = layout y state.h in
478 let ready = loadlayout pages in
479 state.ty <- yratio y;
480 if conf.showall
481 then (
482 if ready
483 then (
484 state.layout <- pages;
485 state.y <- y;
486 Glut.postRedisplay ();
489 else (
490 state.layout <- pages;
491 state.y <- y;
492 Glut.postRedisplay ();
494 preload ();
497 let addnav () =
498 cbput state.hists.nav (yratio state.y);
499 cbrfollowlen state.hists.nav;
502 let getnav () =
503 let y = cbget state.hists.nav ~-1 in
504 truncate (y *. float state.maxy)
507 let gotopage n top =
508 let y, h = getpageyh n in
509 addnav ();
510 gotoy (y + (truncate (top *. float h)));
513 let gotopage1 n top =
514 let y = getpagey n in
515 addnav ();
516 gotoy (y + top);
519 let invalidate () =
520 state.layout <- [];
521 state.pages <- [];
522 state.rects <- [];
523 state.rects1 <- [];
524 state.invalidated <- state.invalidated + 1;
527 let scalecolor c =
528 let c = c *. state.colorscale in
529 (c, c, c);
532 let reshape ~w ~h =
533 let margin =
534 let m = float conf.margin in
535 let m = m *. (float w /. 20.) in
536 let m = truncate m in
537 if m*2 > (w - conf.scrollw) then 0 else m
539 state.winw <- w;
540 let w = w - margin * 2 - conf.scrollw in
541 state.w <- w;
542 state.h <- h;
543 GlMat.mode `modelview;
544 GlMat.load_identity ();
545 GlMat.mode `projection;
546 GlMat.load_identity ();
547 GlMat.rotate ~x:1.0 ~angle:180.0 ();
548 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
549 GlMat.scale3 (2.0 /. float w, 2.0 /. float state.h, 1.0);
550 GlClear.color (scalecolor 1.0);
551 GlClear.clear [`color];
553 invalidate ();
554 wcmd "geometry" [`i state.w; `i h];
557 let showtext c s =
558 GlDraw.color (0.0, 0.0, 0.0);
559 GlDraw.rect
560 (0.0, float (state.h - 18))
561 (float (state.winw - conf.scrollw - 1), float state.h)
563 let font = Glut.BITMAP_8_BY_13 in
564 GlDraw.color (1.0, 1.0, 1.0);
565 GlPix.raster_pos ~x:0.0 ~y:(float (state.h - 5)) ();
566 Glut.bitmapCharacter ~font ~c:(Char.code c);
567 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s;
570 let enttext () =
571 let len = String.length state.text in
572 match state.textentry with
573 | None ->
574 if len > 0 then showtext ' ' state.text
576 | Some (c, text, _, _, _) ->
577 let s =
578 if len > 0
579 then
580 text ^ " [" ^ state.text ^ "]"
581 else
582 text
584 showtext c s;
587 let showtext c s =
588 if true
589 then (
590 state.text <- Printf.sprintf "%c%s" c s;
591 Glut.postRedisplay ();
593 else (
594 showtext c s;
595 Glut.swapBuffers ();
599 let act cmd =
600 match cmd.[0] with
601 | 'c' ->
602 state.pages <- [];
604 | 'D' ->
605 state.rects <- state.rects1;
606 Glut.postRedisplay ()
608 | 'C' ->
609 let n = Scanf.sscanf cmd "C %d" (fun n -> n) in
610 state.pagecount <- n;
611 state.invalidated <- state.invalidated - 1;
612 if state.invalidated = 0
613 then (
614 let rely = yratio state.y in
615 state.maxy <- calcheight ();
616 gotoy (truncate (float state.maxy *. rely));
619 | 't' ->
620 let s = Scanf.sscanf cmd "t %n"
621 (fun n -> String.sub cmd n (String.length cmd - n))
623 Glut.setWindowTitle s
625 | 'T' ->
626 let s = Scanf.sscanf cmd "T %n"
627 (fun n -> String.sub cmd n (String.length cmd - n))
629 if state.textentry = None
630 then (
631 state.text <- s;
632 showtext ' ' s;
634 else (
635 state.text <- s;
636 Glut.postRedisplay ();
639 | 'V' ->
640 if conf.verbose
641 then
642 let s = Scanf.sscanf cmd "V %n"
643 (fun n -> String.sub cmd n (String.length cmd - n))
645 state.text <- s;
646 showtext ' ' s;
648 | 'F' ->
649 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
650 Scanf.sscanf cmd "F %d %d %f %f %f %f %f %f %f %f"
651 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
652 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
654 let y = (getpagey pageno) + truncate y0 in
655 addnav ();
656 gotoy y;
657 state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)]
659 | 'R' ->
660 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
661 Scanf.sscanf cmd "R %d %d %f %f %f %f %f %f %f %f"
662 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
663 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
665 state.rects1 <-
666 (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
668 | 'r' ->
669 let n, w, h, r, p =
670 Scanf.sscanf cmd "r %d %d %d %d %s"
671 (fun n w h r p -> (n, w, h, r, p))
673 Hashtbl.replace state.pagemap (n, w, r) p;
674 let opaque = cbpeekw state.pagecache in
675 if validopaque opaque
676 then (
677 let k =
678 Hashtbl.fold
679 (fun k v a -> if v = opaque then k else a)
680 state.pagemap (-1, -1, -1)
682 wcmd "free" [`s opaque];
683 Hashtbl.remove state.pagemap k
685 cbput state.pagecache p;
686 state.rendering <- false;
687 if conf.showall
688 then gotoy (truncate (ceil (state.ty *. float state.maxy)))
689 else (
690 let visible = List.exists (fun l -> l.pageno + 1 = n) state.layout in
691 if visible
692 then gotoy state.y
693 else (ignore (loadlayout state.layout); preload ())
696 | 'l' ->
697 let (n, w, h) as pagelayout =
698 Scanf.sscanf cmd "l %d %d %d" (fun n w h -> n, w, h)
700 state.pages <- pagelayout :: state.pages
702 | 'o' ->
703 let (l, n, t, h, pos) =
704 Scanf.sscanf cmd "o %d %d %d %d %n" (fun l n t h pos -> l, n, t, h, pos)
706 let s = String.sub cmd pos (String.length cmd - pos) in
707 let s =
708 let l = String.length s in
709 let b = Buffer.create (String.length s) in
710 let rec loop pc2 i =
711 if i = l
712 then ()
713 else
714 let pc2 =
715 match s.[i] with
716 | '\xa0' when pc2 -> Buffer.add_char b ' '; false
717 | '\xc2' -> true
718 | c ->
719 let c = if Char.code c land 0x80 = 0 then c else '?' in
720 Buffer.add_char b c;
721 false
723 loop pc2 (i+1)
725 loop false 0;
726 Buffer.contents b
728 let outline = (s, l, n, float t /. float h) in
729 let outlines =
730 match state.outlines with
731 | Olist outlines -> Olist (outline :: outlines)
732 | Oarray _ -> Olist [outline]
733 | Onarrow _ -> Olist [outline]
735 state.outlines <- outlines
737 | _ ->
738 log "unknown cmd `%S'" cmd
741 let now = Unix.gettimeofday;;
743 let idle () =
744 let rec loop delay =
745 let r, _, _ = Unix.select [state.csock] [] [] delay in
746 begin match r with
747 | [] ->
748 if conf.autoscroll
749 then begin
750 let y = state.y + conf.scrollincr in
751 let y = if y >= state.maxy then 0 else y in
752 gotoy y;
753 state.text <- "";
754 end;
756 | _ ->
757 let cmd = readcmd state.csock in
758 act cmd;
759 loop 0.0
760 end;
761 in loop 0.001
764 let onhist cb = function
765 | HCprev -> cbget cb ~-1
766 | HCnext -> cbget cb 1
767 | HCfirst -> cbget cb ~-(cb.rc)
768 | HClast -> cbget cb (cb.len - 1 - cb.rc)
771 let search pattern forward =
772 if String.length pattern > 0
773 then
774 let pn, py =
775 match state.layout with
776 | [] -> 0, 0
777 | l :: _ ->
778 l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
780 let cmd =
781 let b = makecmd "search"
782 [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0)]
784 Buffer.add_char b ',';
785 Buffer.add_string b pattern;
786 Buffer.add_char b '\000';
787 Buffer.contents b;
789 writecmd state.csock cmd;
792 let intentry text key =
793 let c = Char.unsafe_chr key in
794 match c with
795 | '0' .. '9' ->
796 let s = "x" in s.[0] <- c;
797 let text = text ^ s in
798 TEcont text
800 | _ ->
801 state.text <- Printf.sprintf "invalid char (%d, `%c')" key c;
802 TEcont text
805 let addchar s c =
806 let b = Buffer.create (String.length s + 1) in
807 Buffer.add_string b s;
808 Buffer.add_char b c;
809 Buffer.contents b;
812 let textentry text key =
813 let c = Char.unsafe_chr key in
814 match c with
815 | _ when key >= 32 && key < 127 ->
816 let text = addchar text c in
817 TEcont text
819 | _ ->
820 log "unhandled key %d char `%c'" key (Char.unsafe_chr key);
821 TEcont text
824 let rotate angle =
825 state.rotate <- angle;
826 invalidate ();
827 wcmd "rotate" [`i angle];
830 let optentry text key =
831 let btos b = if b then "on" else "off" in
832 let c = Char.unsafe_chr key in
833 match c with
834 | 's' ->
835 let ondone s =
836 try conf.scrollincr <- int_of_string s with exc ->
837 state.text <- Printf.sprintf "bad integer `%s': %s"
838 s (Printexc.to_string exc)
840 TEswitch ('#', "", None, intentry, ondone)
842 | 'R' ->
843 let ondone s =
844 match try
845 Some (int_of_string s)
846 with exc ->
847 state.text <- Printf.sprintf "bad integer `%s': %s"
848 s (Printexc.to_string exc);
849 None
850 with
851 | Some angle -> rotate angle
852 | None -> ()
854 TEswitch ('^', "", None, intentry, ondone)
856 | 'i' ->
857 conf.icase <- not conf.icase;
858 TEdone ("case insensitive search " ^ (btos conf.icase))
860 | 'p' ->
861 conf.preload <- not conf.preload;
862 gotoy state.y;
863 TEdone ("preload " ^ (btos conf.preload))
865 | 'v' ->
866 conf.verbose <- not conf.verbose;
867 TEdone ("verbose " ^ (btos conf.verbose))
869 | 'h' ->
870 conf.maxhfit <- not conf.maxhfit;
871 state.maxy <- state.maxy + (if conf.maxhfit then -state.h else state.h);
872 TEdone ("maxhfit " ^ (btos conf.maxhfit))
874 | 'c' ->
875 conf.crophack <- not conf.crophack;
876 TEdone ("crophack " ^ btos conf.crophack)
878 | 'a' ->
879 conf.showall <- not conf.showall;
880 TEdone ("showall " ^ btos conf.showall)
882 | 'f' ->
883 conf.underinfo <- not conf.underinfo;
884 TEdone ("underinfo " ^ btos conf.underinfo)
886 | 'S' ->
887 let ondone s =
889 conf.interpagespace <- int_of_string s;
890 let rely = yratio state.y in
891 state.maxy <- calcheight ();
892 gotoy (truncate (float state.maxy *. rely));
893 with exc ->
894 state.text <- Printf.sprintf "bad integer `%s': %s"
895 s (Printexc.to_string exc)
897 TEswitch ('%', "", None, intentry, ondone)
899 | _ ->
900 state.text <- Printf.sprintf "bad option %d `%c'" key c;
901 TEstop
904 let maxoutlinerows () = (state.h - 31) / 16;;
906 let enterselector allowdel outlines errmsg =
907 if Array.length outlines = 0
908 then (
909 showtext ' ' errmsg;
911 else (
912 Glut.setCursor Glut.CURSOR_INHERIT;
913 let pageno =
914 match state.layout with
915 | [] -> -1
916 | {pageno=pageno} :: rest -> pageno
918 let active =
919 let rec loop n =
920 if n = Array.length outlines
921 then 0
922 else
923 let (_, _, outlinepageno, _) = outlines.(n) in
924 if outlinepageno >= pageno then n else loop (n+1)
926 loop 0
928 state.outline <-
929 Some (allowdel, active,
930 max 0 ((active - maxoutlinerows () / 2)), outlines, "");
931 Glut.postRedisplay ();
935 let enteroutlinemode () =
936 let outlines =
937 match state.outlines with
938 | Oarray a -> a
939 | Olist l ->
940 let a = Array.of_list (List.rev l) in
941 state.outlines <- Oarray a;
943 | Onarrow (a, b) -> a
945 enterselector false outlines "Document has no outline";
948 let enterbookmarkmode () =
949 let bookmarks = Array.of_list state.bookmarks in
950 enterselector true bookmarks "Document has no bookmarks (yet)";
954 let quickbookmark ?title () =
955 match state.layout with
956 | [] -> ()
957 | l :: _ ->
958 let title =
959 match title with
960 | None ->
961 let sec = Unix.gettimeofday () in
962 let tm = Unix.localtime sec in
963 Printf.sprintf "Quick %d visited (%d/%d/%d %d:%d)"
964 l.pageno
965 tm.Unix.tm_mday
966 tm.Unix.tm_mon
967 (tm.Unix.tm_year + 1900)
968 tm.Unix.tm_hour
969 tm.Unix.tm_min
970 | Some title -> title
972 state.bookmarks <-
973 (title, 0, l.pageno, float l.pagey /. float l.pageh) :: state.bookmarks
976 let doreshape w h =
977 state.fullscreen <- None;
978 Glut.reshapeWindow w h;
981 let opendoc path password =
982 invalidate ();
983 state.path <- path;
984 state.password <- password;
985 Hashtbl.clear state.pagemap;
987 writecmd state.csock ("open " ^ path ^ "\000" ^ password ^ "\000");
988 Glut.setWindowTitle ("llpp " ^ Filename.basename path);
989 wcmd "geometry" [`i state.w; `i state.h];
992 let viewkeyboard ~key ~x ~y =
993 let enttext te =
994 state.textentry <- te;
995 state.text <- "";
996 enttext ();
997 Glut.postRedisplay ()
999 match state.textentry with
1000 | None ->
1001 let c = Char.chr key in
1002 begin match c with
1003 | '\027' | 'q' ->
1004 exit 0
1006 | '\008' ->
1007 let y = getnav () in
1008 gotoy y
1010 | 'o' ->
1011 enteroutlinemode ()
1013 | 'u' ->
1014 state.rects <- [];
1015 state.text <- "";
1016 Glut.postRedisplay ()
1018 | '/' | '?' ->
1019 let ondone isforw s =
1020 cbput state.hists.pat s;
1021 cbrfollowlen state.hists.pat;
1022 state.searchpattern <- s;
1023 search s isforw
1025 enttext (Some (c, "", Some (onhist state.hists.pat),
1026 textentry, ondone (c ='/')))
1028 | '+' ->
1029 if Glut.getModifiers () land Glut.active_ctrl != 0
1030 then (
1031 let margin = min 8 (conf.margin + 1) in
1032 conf.margin <- margin;
1033 reshape state.winw state.h;
1035 else
1036 let ondone s =
1037 let n =
1038 try int_of_string s with exc ->
1039 state.text <- Printf.sprintf "bad integer `%s': %s"
1040 s (Printexc.to_string exc);
1041 max_int
1043 if n != max_int
1044 then (
1045 conf.pagebias <- n;
1046 state.text <- "page bias is now " ^ string_of_int n;
1049 enttext (Some ('+', "", None, intentry, ondone))
1051 | '-' ->
1052 if Glut.getModifiers () land Glut.active_ctrl != 0
1053 then (
1054 let margin = max 0 (conf.margin - 1) in
1055 conf.margin <- margin;
1056 reshape state.winw state.h;
1058 else
1059 let ondone msg =
1060 state.text <- msg;
1062 enttext (Some ('-', "", None, optentry, ondone))
1064 | '0' .. '9' ->
1065 let ondone s =
1066 let n =
1067 try int_of_string s with exc ->
1068 state.text <- Printf.sprintf "bad integer `%s': %s"
1069 s (Printexc.to_string exc);
1072 if n >= 0
1073 then (
1074 addnav ();
1075 cbput state.hists.pag (string_of_int n);
1076 cbrfollowlen state.hists.pag;
1077 gotoy (getpagey (n + conf.pagebias - 1))
1080 let pageentry text key =
1081 match Char.unsafe_chr key with
1082 | 'g' -> TEdone text
1083 | _ -> intentry text key
1085 let text = "x" in text.[0] <- c;
1086 enttext (Some (':', text, Some (onhist state.hists.pag),
1087 pageentry, ondone))
1089 | 'b' ->
1090 conf.scrollw <- if conf.scrollw > 0 then 0 else 5;
1091 reshape state.winw state.h;
1093 | 'l' ->
1094 conf.hlinks <- not conf.hlinks;
1095 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
1096 Glut.postRedisplay ()
1098 | 'a' ->
1099 conf.autoscroll <- not conf.autoscroll
1101 | 'P' ->
1102 begin match state.layout with
1103 | [] -> ()
1104 | l :: _ ->
1105 let ips =
1106 let d = state.h - l.pageh in
1107 max 0 (d / 2)
1109 let rely = yratio state.y in
1110 conf.interpagespace <- ips;
1111 state.maxy <- calcheight ();
1112 gotoy (truncate (float state.maxy *. rely));
1113 end;
1115 | 'f' ->
1116 begin match state.fullscreen with
1117 | None ->
1118 state.fullscreen <- Some (state.w, state.h);
1119 Glut.fullScreen ()
1120 | Some (w, h) ->
1121 state.fullscreen <- None;
1122 doreshape w h
1125 | 'g' ->
1126 gotoy 0
1128 | 'n' ->
1129 search state.searchpattern true
1131 | 'p' | 'N' ->
1132 search state.searchpattern false
1134 | 't' ->
1135 begin match state.layout with
1136 | [] -> ()
1137 | l :: _ ->
1138 gotoy (state.y - l.pagey - conf.interpagespace);
1141 | ' ' ->
1142 begin match List.rev state.layout with
1143 | [] -> ()
1144 | l :: _ ->
1145 gotoy (clamp (l.pageh - l.pagey + conf.interpagespace))
1148 | '\127' ->
1149 begin match state.layout with
1150 | [] -> ()
1151 | l :: _ ->
1152 gotoy (clamp (-l.pageh - conf.interpagespace));
1155 | '=' ->
1156 let f (fn, ln) l =
1157 if fn = -1 then l.pageno, l.pageno else fn, l.pageno
1159 let fn, ln = List.fold_left f (-1, -1) state.layout in
1160 let s =
1161 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1162 let percent =
1163 if maxy <= 0
1164 then 100.
1165 else (100. *. (float state.y /. float maxy)) in
1166 if fn = ln
1167 then
1168 Printf.sprintf "Page %d of %d %.2f%%"
1169 (fn+1) state.pagecount percent
1170 else
1171 Printf.sprintf
1172 "Pages %d-%d of %d %.2f%%"
1173 (fn+1) (ln+1) state.pagecount percent
1175 showtext ' ' s;
1177 | 'w' ->
1178 begin match state.layout with
1179 | [] -> ()
1180 | l :: _ ->
1181 doreshape l.pagew l.pageh;
1182 Glut.postRedisplay ();
1185 | '\'' ->
1186 enterbookmarkmode ()
1188 | 'm' ->
1189 let ondone s =
1190 match state.layout with
1191 | l :: _ ->
1192 state.bookmarks <-
1193 (s, 0, l.pageno, float l.pagey /. float l.pageh)
1194 :: state.bookmarks
1195 | _ -> ()
1197 enttext (Some ('~', "", None, textentry, ondone))
1199 | '~' ->
1200 quickbookmark ();
1201 showtext ' ' "Quick bookmark added";
1203 | 'z' ->
1204 begin match state.layout with
1205 | l :: _ ->
1206 let a = getpagewh l.pagedimno in
1207 let w, h =
1208 if conf.crophack
1209 then
1210 (truncate (1.8 *. (a.(1) -. a.(0))),
1211 truncate (1.2 *. (a.(3) -. a.(0))))
1212 else
1213 (truncate (a.(1) -. a.(0)),
1214 truncate (a.(3) -. a.(0)))
1216 doreshape w h;
1217 Glut.postRedisplay ();
1219 | [] -> ()
1222 | '<' | '>' ->
1223 rotate (state.rotate + (if c = '>' then 30 else -30));
1225 | '[' | ']' ->
1226 state.colorscale <-
1227 max 0.0
1228 (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0);
1229 Glut.postRedisplay ()
1231 | 'k' -> gotoy (clamp (-conf.scrollincr))
1232 | 'j' -> gotoy (clamp conf.scrollincr)
1234 | 'r' -> opendoc state.path state.password
1236 | _ ->
1237 vlog "huh? %d %c" key (Char.chr key);
1240 | Some (c, text, onhist, onkey, ondone) when key = 8 ->
1241 let len = String.length text in
1242 if len = 0
1243 then (
1244 state.textentry <- None;
1245 Glut.postRedisplay ();
1247 else (
1248 let s = String.sub text 0 (len - 1) in
1249 enttext (Some (c, s, onhist, onkey, ondone))
1252 | Some (c, text, onhist, onkey, ondone) ->
1253 begin match Char.unsafe_chr key with
1254 | '\r' | '\n' ->
1255 ondone text;
1256 state.textentry <- None;
1257 Glut.postRedisplay ()
1259 | '\027' ->
1260 state.textentry <- None;
1261 Glut.postRedisplay ()
1263 | _ ->
1264 begin match onkey text key with
1265 | TEdone text ->
1266 state.textentry <- None;
1267 ondone text;
1268 Glut.postRedisplay ()
1270 | TEcont text ->
1271 enttext (Some (c, text, onhist, onkey, ondone));
1273 | TEstop ->
1274 state.textentry <- None;
1275 Glut.postRedisplay ()
1277 | TEswitch te ->
1278 state.textentry <- Some te;
1279 Glut.postRedisplay ()
1280 end;
1281 end;
1284 let narrow outlines pattern =
1285 let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in
1286 match reopt with
1287 | None -> None
1288 | Some re ->
1289 let rec fold accu n =
1290 if n = -1
1291 then accu
1292 else
1293 let (s, _, _, _) as o = outlines.(n) in
1294 let accu =
1295 if (try ignore (Str.search_forward re s 0); true
1296 with Not_found -> false)
1297 then (o :: accu)
1298 else accu
1300 fold accu (n-1)
1302 let matched = fold [] (Array.length outlines - 1) in
1303 if matched = [] then None else Some (Array.of_list matched)
1306 let outlinekeyboard ~key ~x ~y (allowdel, active, first, outlines, qsearch) =
1307 let search active pattern incr =
1308 let dosearch re =
1309 let rec loop n =
1310 if n = Array.length outlines || n = -1
1311 then None
1312 else
1313 let (s, _, _, _) = outlines.(n) in
1315 (try ignore (Str.search_forward re s 0); true
1316 with Not_found -> false)
1317 then Some n
1318 else loop (n + incr)
1320 loop active
1323 let re = Str.regexp_case_fold pattern in
1324 dosearch re
1325 with Failure s ->
1326 state.text <- s;
1327 None
1329 let firstof active = max 0 (active - maxoutlinerows () / 2) in
1330 match key with
1331 | 27 ->
1332 if String.length qsearch = 0
1333 then (
1334 state.text <- "";
1335 state.outline <- None;
1336 Glut.postRedisplay ();
1338 else (
1339 state.text <- "";
1340 state.outline <- Some (allowdel, active, first, outlines, "");
1341 Glut.postRedisplay ();
1344 | 18 | 19 ->
1345 let incr = if key = 18 then -1 else 1 in
1346 let active, first =
1347 match search (active + incr) qsearch incr with
1348 | None ->
1349 state.text <- qsearch ^ " [not found]";
1350 active, first
1351 | Some active ->
1352 state.text <- qsearch;
1353 active, firstof active
1355 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1356 Glut.postRedisplay ();
1358 | 8 ->
1359 let len = String.length qsearch in
1360 if len = 0
1361 then ()
1362 else (
1363 if len = 1
1364 then (
1365 state.text <- "";
1366 state.outline <- Some (allowdel, active, first, outlines, "");
1368 else
1369 let qsearch = String.sub qsearch 0 (len - 1) in
1370 let active, first =
1371 match search active qsearch ~-1 with
1372 | None ->
1373 state.text <- qsearch ^ " [not found]";
1374 active, first
1375 | Some active ->
1376 state.text <- qsearch;
1377 active, firstof active
1379 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1381 Glut.postRedisplay ()
1383 | 13 ->
1384 if active < Array.length outlines
1385 then (
1386 let (_, _, n, t) = outlines.(active) in
1387 gotopage n t;
1389 state.text <- "";
1390 if allowdel then state.bookmarks <- Array.to_list outlines;
1391 state.outline <- None;
1392 Glut.postRedisplay ();
1394 | _ when key >= 32 && key < 127 ->
1395 let pattern = addchar qsearch (Char.chr key) in
1396 let active, first =
1397 match search active pattern 1 with
1398 | None ->
1399 state.text <- pattern ^ " [not found]";
1400 active, first
1401 | Some active ->
1402 state.text <- pattern;
1403 active, firstof active
1405 state.outline <- Some (allowdel, active, first, outlines, pattern);
1406 Glut.postRedisplay ()
1408 | 14 when not allowdel ->
1409 let optoutlines = narrow outlines qsearch in
1410 begin match optoutlines with
1411 | None -> state.text <- "can't narrow"
1412 | Some outlines ->
1413 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1414 match state.outlines with
1415 | Olist l -> ()
1416 | Oarray a -> state.outlines <- Onarrow (outlines, a)
1417 | Onarrow (a, b) -> state.outlines <- Onarrow (outlines, b)
1418 end;
1419 Glut.postRedisplay ()
1421 | 21 when not allowdel ->
1422 let outline =
1423 match state.outlines with
1424 | Oarray a -> a
1425 | Olist l ->
1426 let a = Array.of_list (List.rev l) in
1427 state.outlines <- Oarray a;
1429 | Onarrow (a, b) ->
1430 state.outlines <- Oarray b;
1433 state.outline <- Some (allowdel, 0, 0, outline, qsearch);
1434 Glut.postRedisplay ()
1436 | 12 ->
1437 state.outline <-
1438 Some (allowdel, active, firstof active, outlines, qsearch);
1439 Glut.postRedisplay ()
1441 | 127 when allowdel ->
1442 let len = Array.length outlines - 1 in
1443 if len = 0
1444 then (
1445 state.outline <- None;
1446 state.bookmarks <- [];
1448 else (
1449 let bookmarks = Array.init len
1450 (fun i ->
1451 let i = if i >= active then i + 1 else i in
1452 outlines.(i)
1455 state.outline <-
1456 Some (allowdel,
1457 min active (len-1),
1458 min first (len-1),
1459 bookmarks, qsearch)
1462 Glut.postRedisplay ()
1464 | _ -> log "unknown key %d" key
1467 let keyboard ~key ~x ~y =
1468 if key = 7
1469 then
1470 wcmd "interrupt" []
1471 else
1472 match state.outline with
1473 | None -> viewkeyboard ~key ~x ~y
1474 | Some outline -> outlinekeyboard ~key ~x ~y outline
1477 let special ~key ~x ~y =
1478 match state.outline with
1479 | None ->
1480 begin match state.textentry with
1481 | None ->
1482 let y =
1483 match key with
1484 | Glut.KEY_F3 -> search state.searchpattern true; state.y
1485 | Glut.KEY_UP -> clamp (-conf.scrollincr)
1486 | Glut.KEY_DOWN -> clamp conf.scrollincr
1487 | Glut.KEY_PAGE_UP ->
1488 if Glut.getModifiers () land Glut.active_ctrl != 0
1489 then
1490 match state.layout with
1491 | [] -> state.y
1492 | l :: _ -> state.y - l.pagey
1493 else
1494 clamp (-state.h)
1495 | Glut.KEY_PAGE_DOWN ->
1496 if Glut.getModifiers () land Glut.active_ctrl != 0
1497 then
1498 match List.rev state.layout with
1499 | [] -> state.y
1500 | l :: _ -> getpagey l.pageno
1501 else
1502 clamp state.h
1503 | Glut.KEY_HOME -> addnav (); 0
1504 | Glut.KEY_END ->
1505 addnav ();
1506 state.maxy - (if conf.maxhfit then state.h else 0)
1507 | _ -> state.y
1509 if not conf.verbose then state.text <- "";
1510 gotoy y
1512 | Some (c, s, Some onhist, onkey, ondone) ->
1513 let s =
1514 match key with
1515 | Glut.KEY_UP -> onhist HCprev
1516 | Glut.KEY_DOWN -> onhist HCnext
1517 | Glut.KEY_HOME -> onhist HCfirst
1518 | Glut.KEY_END -> onhist HClast
1519 | _ -> state.text
1521 state.textentry <- Some (c, s, Some onhist, onkey, ondone);
1522 Glut.postRedisplay ()
1524 | _ -> ()
1527 | Some (allowdel, active, first, outlines, qsearch) ->
1528 let maxrows = maxoutlinerows () in
1529 let navigate incr =
1530 let active = active + incr in
1531 let active = max 0 (min active (Array.length outlines - 1)) in
1532 let first =
1533 if active > first
1534 then
1535 let rows = active - first in
1536 if rows > maxrows then active - maxrows else first
1537 else active
1539 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1540 Glut.postRedisplay ()
1542 match key with
1543 | Glut.KEY_UP -> navigate ~-1
1544 | Glut.KEY_DOWN -> navigate 1
1545 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
1546 | Glut.KEY_PAGE_DOWN -> navigate maxrows
1548 | Glut.KEY_HOME ->
1549 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1550 Glut.postRedisplay ()
1552 | Glut.KEY_END ->
1553 let active = Array.length outlines - 1 in
1554 let first = max 0 (active - maxrows) in
1555 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1556 Glut.postRedisplay ()
1558 | _ -> ()
1561 let drawplaceholder l =
1562 GlDraw.color (scalecolor 1.0);
1563 GlDraw.rect
1564 (0.0, float l.pagedispy)
1565 (float l.pagew, float (l.pagedispy + l.pagevh))
1567 let x = 0.0
1568 and y = float (l.pagedispy + 13) in
1569 let font = Glut.BITMAP_8_BY_13 in
1570 GlDraw.color (0.0, 0.0, 0.0);
1571 GlPix.raster_pos ~x ~y ();
1572 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c))
1573 ("Loading " ^ string_of_int l.pageno);
1576 let now () = Unix.gettimeofday ();;
1578 let drawpage i l =
1579 begin match getopaque l.pageno with
1580 | Some opaque when validopaque opaque ->
1581 if state.textentry = None
1582 then GlDraw.color (scalecolor 1.0)
1583 else GlDraw.color (scalecolor 0.4);
1584 let a = now () in
1585 draw (l.pagedispy, l.pagew, l.pagevh, l.pagey, conf.hlinks)
1586 opaque;
1587 let b = now () in
1588 let d = b-.a in
1589 vlog "draw %f sec" d;
1591 | _ ->
1592 drawplaceholder l;
1593 end;
1594 l.pagedispy + l.pagevh;
1597 let scrollindicator () =
1598 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1599 GlDraw.color (0.64 , 0.64, 0.64);
1600 GlDraw.rect
1601 (0., 0.)
1602 (float conf.scrollw, float state.h)
1604 GlDraw.color (0.0, 0.0, 0.0);
1605 let sh = (float (maxy + state.h) /. float state.h) in
1606 let sh = float state.h /. sh in
1607 let sh = max sh (float conf.scrollh) in
1609 let percent =
1610 if state.y = state.maxy
1611 then 1.0
1612 else float state.y /. float maxy
1614 let position = (float state.h -. sh) *. percent in
1616 let position =
1617 if position +. sh > float state.h
1618 then
1619 float state.h -. sh
1620 else
1621 position
1623 GlDraw.rect
1624 (0.0, position)
1625 (float conf.scrollw, position +. sh)
1629 let showsel margin =
1630 match state.mstate with
1631 | Mnone ->
1634 | Msel ((x0, y0), (x1, y1)) ->
1635 let rec loop = function
1636 | l :: ls ->
1637 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1638 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1639 then
1640 match getopaque l.pageno with
1641 | Some opaque when validopaque opaque ->
1642 let oy = -l.pagey + l.pagedispy in
1643 seltext opaque (x0 - margin, y0, x1 - margin, y1) oy;
1645 | _ -> ()
1646 else loop ls
1647 | [] -> ()
1649 loop state.layout
1652 let showrects () =
1653 Gl.enable `blend;
1654 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
1655 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1656 List.iter
1657 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
1658 List.iter (fun l ->
1659 if l.pageno = pageno
1660 then (
1661 let d = float (l.pagedispy - l.pagey) in
1662 GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5;
1663 GlDraw.begins `quads;
1665 GlDraw.vertex2 (x0, y0+.d);
1666 GlDraw.vertex2 (x1, y1+.d);
1667 GlDraw.vertex2 (x2, y2+.d);
1668 GlDraw.vertex2 (x3, y3+.d);
1670 GlDraw.ends ();
1672 ) state.layout
1673 ) state.rects
1675 Gl.disable `blend;
1678 let showoutline = function
1679 | None -> ()
1680 | Some (allowdel, active, first, outlines, qsearch) ->
1681 Gl.enable `blend;
1682 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1683 GlDraw.color (0., 0., 0.) ~alpha:0.85;
1684 GlDraw.rect (0., 0.) (float state.w, float state.h);
1685 Gl.disable `blend;
1687 GlDraw.color (1., 1., 1.);
1688 let font = Glut.BITMAP_9_BY_15 in
1689 let draw_string x y s =
1690 GlPix.raster_pos ~x ~y ();
1691 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
1693 let rec loop row =
1694 if row = Array.length outlines || (row - first) * 16 > state.h
1695 then ()
1696 else (
1697 let (s, l, _, _) = outlines.(row) in
1698 let y = (row - first) * 16 in
1699 let x = 5 + 15*l in
1700 if row = active
1701 then (
1702 Gl.enable `blend;
1703 GlDraw.polygon_mode `both `line;
1704 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1705 GlDraw.color (1., 1., 1.) ~alpha:0.9;
1706 GlDraw.rect (0., float (y + 1))
1707 (float (state.winw - conf.scrollw - 1), float (y + 18));
1708 GlDraw.polygon_mode `both `fill;
1709 Gl.disable `blend;
1710 GlDraw.color (1., 1., 1.);
1712 draw_string (float x) (float (y + 16)) s;
1713 loop (row+1)
1716 loop first
1719 let display () =
1720 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1721 GlDraw.viewport margin 0 state.w state.h;
1722 GlClear.color (scalecolor 0.5);
1723 GlClear.clear [`color];
1724 let lasty = List.fold_left drawpage 0 (state.layout) in
1725 showrects ();
1726 GlDraw.viewport (state.winw - conf.scrollw) 0 state.winw state.h;
1727 scrollindicator ();
1728 showsel margin;
1729 GlDraw.viewport 0 0 state.winw state.h;
1730 showoutline state.outline;
1731 enttext ();
1732 Glut.swapBuffers ();
1735 let getunder x y =
1736 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1737 let x = x - margin in
1738 let rec f = function
1739 | l :: rest ->
1740 begin match getopaque l.pageno with
1741 | Some opaque when validopaque opaque ->
1742 let y = y - l.pagedispy in
1743 if y > 0
1744 then
1745 let y = l.pagey + y in
1746 match whatsunder opaque x y with
1747 | Unone -> f rest
1748 | under -> under
1749 else
1750 f rest
1751 | _ ->
1752 f rest
1754 | [] -> Unone
1756 f state.layout
1759 let mouse ~button ~bstate ~x ~y =
1760 match button with
1761 | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP ->
1762 let incr =
1763 if n = 3
1764 then
1765 -conf.scrollincr
1766 else
1767 conf.scrollincr
1769 let incr = incr * 2 in
1770 let y = clamp incr in
1771 gotoy y
1773 | Glut.LEFT_BUTTON when state.outline = None ->
1774 let dest = if bstate = Glut.DOWN then getunder x y else Unone in
1775 begin match dest with
1776 | Ulinkgoto (pageno, top) ->
1777 if pageno >= 0
1778 then
1779 gotopage1 pageno top
1781 | Ulinkuri s ->
1782 print_endline s
1784 | Unone when bstate = Glut.DOWN ->
1785 Glut.setCursor Glut.CURSOR_INHERIT;
1786 state.mstate <- Mnone
1788 | Unone | Utext _ ->
1789 if bstate = Glut.DOWN
1790 then (
1791 if state.rotate mod 360 = 0
1792 then (
1793 state.mstate <- Msel ((x, y), (x, y));
1794 Glut.postRedisplay ()
1797 else (
1798 match state.mstate with
1799 | Mnone -> ()
1800 | Msel ((x0, y0), (x1, y1)) ->
1801 let f l =
1802 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1803 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1804 then
1805 match getopaque l.pageno with
1806 | Some opaque when validopaque opaque ->
1807 copysel opaque
1808 | _ -> ()
1810 List.iter f state.layout;
1811 copysel ""; (* ugly *)
1812 Glut.setCursor Glut.CURSOR_INHERIT;
1813 state.mstate <- Mnone;
1817 | _ ->
1820 let mouse ~button ~state ~x ~y = mouse button state x y;;
1822 let motion ~x ~y =
1823 if state.outline = None
1824 then
1825 match state.mstate with
1826 | Mnone -> ()
1827 | Msel (a, _) ->
1828 state.mstate <- Msel (a, (x, y));
1829 Glut.postRedisplay ()
1832 let pmotion ~x ~y =
1833 if state.outline = None
1834 then
1835 match state.mstate with
1836 | Mnone ->
1837 begin match getunder x y with
1838 | Unone -> Glut.setCursor Glut.CURSOR_INHERIT
1839 | Ulinkuri uri ->
1840 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
1841 Glut.setCursor Glut.CURSOR_INFO
1842 | Ulinkgoto (page, y) ->
1843 if conf.underinfo then showtext 'p' ("age: " ^ string_of_int page);
1844 Glut.setCursor Glut.CURSOR_INFO
1845 | Utext s ->
1846 if conf.underinfo then showtext 'f' ("ont: " ^ s);
1847 Glut.setCursor Glut.CURSOR_TEXT
1850 | Msel (a, _) ->
1854 let () =
1855 let statepath =
1856 let home =
1857 if Sys.os_type = "Win32"
1858 then
1859 try Sys.getenv "HOMEPATH" with Not_found -> ""
1860 else
1861 try Filename.concat (Sys.getenv "HOME") ".config" with Not_found -> ""
1863 Filename.concat home "llpp"
1865 let pstate =
1867 let ic = open_in_bin statepath in
1868 let hash = input_value ic in
1869 close_in ic;
1870 hash
1871 with exn ->
1872 if false
1873 then
1874 prerr_endline ("Error loading state " ^ Printexc.to_string exn)
1876 Hashtbl.create 1
1878 let savestate () =
1880 let w, h =
1881 match state.fullscreen with
1882 | None -> state.winw, state.h
1883 | Some wh -> wh
1885 Hashtbl.replace pstate state.path (state.bookmarks, w, h);
1886 let oc = open_out_bin statepath in
1887 output_value oc pstate
1888 with exn ->
1889 if false
1890 then
1891 prerr_endline ("Error saving state " ^ Printexc.to_string exn)
1894 let setstate () =
1896 let statebookmarks, statew, stateh = Hashtbl.find pstate state.path in
1897 state.w <- statew;
1898 state.h <- stateh;
1899 state.bookmarks <- statebookmarks;
1900 with Not_found -> ()
1901 | exn ->
1902 prerr_endline ("Error setting state " ^ Printexc.to_string exn)
1905 Arg.parse
1906 ["-p", Arg.String (fun s -> state.password <- s) , "password"]
1907 (fun s -> state.path <- s)
1908 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\noptions:")
1910 let name =
1911 if String.length state.path = 0
1912 then (prerr_endline "filename missing"; exit 1)
1913 else state.path
1916 setstate ();
1917 let _ = Glut.init Sys.argv in
1918 let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in
1919 let () = Glut.initWindowSize state.w state.h in
1920 let _ = Glut.createWindow ("llpp " ^ Filename.basename name) in
1922 let csock, ssock =
1923 if Sys.os_type = "Unix"
1924 then
1925 Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
1926 else
1927 let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, 1337) in
1928 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
1929 Unix.setsockopt sock Unix.SO_REUSEADDR true;
1930 Unix.bind sock addr;
1931 Unix.listen sock 1;
1932 let csock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
1933 Unix.connect csock addr;
1934 let ssock, _ = Unix.accept sock in
1935 Unix.close sock;
1936 let opts sock =
1937 Unix.setsockopt sock Unix.TCP_NODELAY true;
1938 Unix.setsockopt_optint sock Unix.SO_LINGER None;
1940 opts ssock;
1941 opts csock;
1942 at_exit (fun () -> Unix.shutdown ssock Unix.SHUTDOWN_ALL);
1943 ssock, csock
1946 let () = Glut.displayFunc display in
1947 let () = Glut.reshapeFunc reshape in
1948 let () = Glut.keyboardFunc keyboard in
1949 let () = Glut.specialFunc special in
1950 let () = Glut.idleFunc (Some idle) in
1951 let () = Glut.mouseFunc mouse in
1952 let () = Glut.motionFunc motion in
1953 let () = Glut.passiveMotionFunc pmotion in
1955 init ssock;
1956 state.csock <- csock;
1957 state.ssock <- ssock;
1958 state.text <- "Opening " ^ name;
1959 writecmd state.csock ("open " ^ state.path ^ "\000" ^ state.password ^ "\000");
1961 at_exit savestate;
1963 let rec handlelablglutbug () =
1965 Glut.mainLoop ();
1966 with Glut.BadEnum "key in special_of_int" ->
1967 showtext '!' " LablGlut bug: special key not recognized";
1968 handlelablglutbug ()
1970 handlelablglutbug ();