Remove unintended change
[llpp.git] / main.ml
blobe40e974e153acd274ef11c7c1d41dcb25e19394c
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 getpdimrect : int -> float array = "ml_getpdimrect";;
17 external whatsunder : string -> int -> int -> under = "ml_whatsunder";;
19 type mpos = int * int
20 and mstate =
21 | Msel of (mpos * mpos)
22 | Mpan of mpos
23 | Mscroll
24 | Mnone
27 type 'a circbuf =
28 { store : 'a array
29 ; mutable rc : int
30 ; mutable wc : int
31 ; mutable len : int
35 type textentry = (char * string * onhist option * onkey * ondone)
36 and onkey = string -> int -> te
37 and ondone = string -> unit
38 and onhist = histcmd -> string
39 and histcmd = HCnext | HCprev | HCfirst | HClast
40 and te =
41 | TEstop
42 | TEdone of string
43 | TEcont of string
44 | TEswitch of textentry
47 let cbnew n v =
48 { store = Array.create n v
49 ; rc = 0
50 ; wc = 0
51 ; len = 0
55 let cblen b = Array.length b.store;;
57 let cbput b v =
58 let len = cblen b in
59 b.store.(b.wc) <- v;
60 b.wc <- (b.wc + 1) mod len;
61 b.len <- min (b.len + 1) len;
64 let cbpeekw b = b.store.(b.wc);;
66 let cbget b dir =
67 if b.len = 0
68 then b.store.(0)
69 else
70 let rc = b.rc + dir in
71 let rc = if rc = -1 then b.len - 1 else rc in
72 let rc = if rc = b.len then 0 else rc in
73 b.rc <- rc;
74 b.store.(rc);
77 let cbrfollowlen b =
78 b.rc <- b.len;
81 let cbclear b v =
82 b.len <- 0;
83 Array.fill b.store 0 (Array.length b.store) v;
86 type layout =
87 { pageno : int
88 ; pagedimno : int
89 ; pagew : int
90 ; pageh : int
91 ; pagedispy : int
92 ; pagey : int
93 ; pagevh : int
97 type conf =
98 { mutable scrollw : int
99 ; mutable scrollh : int
100 ; mutable icase : bool
101 ; mutable preload : bool
102 ; mutable pagebias : int
103 ; mutable verbose : bool
104 ; mutable scrollincr : int
105 ; mutable maxhfit : bool
106 ; mutable crophack : bool
107 ; mutable autoscroll : bool
108 ; mutable showall : bool
109 ; mutable hlinks : bool
110 ; mutable underinfo : bool
111 ; mutable interpagespace : int
112 ; mutable zoom : float
113 ; mutable presentation : bool
114 ; mutable angle : int
118 type outline = string * int * int * float;;
119 type outlines =
120 | Oarray of outline array
121 | Olist of outline list
122 | Onarrow of outline array * outline array
125 type rect = (float * float * float * float * float * float * float * float);;
127 type state =
128 { mutable csock : Unix.file_descr
129 ; mutable ssock : Unix.file_descr
130 ; mutable w : int
131 ; mutable h : int
132 ; mutable winw : int
133 ; mutable x : int
134 ; mutable y : int
135 ; mutable ty : float
136 ; mutable maxy : int
137 ; mutable layout : layout list
138 ; pagemap : ((int * int * int), string) Hashtbl.t
139 ; mutable pdims : (int * int * int) list
140 ; mutable pagecount : int
141 ; pagecache : string circbuf
142 ; mutable rendering : bool
143 ; mutable mstate : mstate
144 ; mutable searchpattern : string
145 ; mutable rects : (int * int * rect) list
146 ; mutable rects1 : (int * int * rect) list
147 ; mutable text : string
148 ; mutable fullscreen : (int * int) option
149 ; mutable textentry : textentry option
150 ; mutable outlines : outlines
151 ; mutable outline : (bool * int * int * outline array * string) option
152 ; mutable bookmarks : outline list
153 ; mutable path : string
154 ; mutable password : string
155 ; mutable invalidated : int
156 ; mutable colorscale : float
157 ; hists : hists
159 and hists =
160 { pat : string circbuf
161 ; pag : string circbuf
162 ; nav : float circbuf
166 let conf =
167 { scrollw = 7
168 ; scrollh = 12
169 ; icase = true
170 ; preload = true
171 ; pagebias = 0
172 ; verbose = false
173 ; scrollincr = 24
174 ; maxhfit = true
175 ; crophack = false
176 ; autoscroll = false
177 ; showall = false
178 ; hlinks = false
179 ; underinfo = false
180 ; interpagespace = 2
181 ; zoom = 1.0
182 ; presentation = false
183 ; angle = 0
187 let state =
188 { csock = Unix.stdin
189 ; ssock = Unix.stdin
190 ; w = 900
191 ; h = 900
192 ; winw = 900
193 ; y = 0
194 ; x = 0
195 ; ty = 0.0
196 ; layout = []
197 ; maxy = max_int
198 ; pagemap = Hashtbl.create 10
199 ; pagecache = cbnew 10 ""
200 ; pdims = []
201 ; pagecount = 0
202 ; rendering = false
203 ; mstate = Mnone
204 ; rects = []
205 ; rects1 = []
206 ; text = ""
207 ; fullscreen = None
208 ; textentry = None
209 ; searchpattern = ""
210 ; outlines = Olist []
211 ; outline = None
212 ; bookmarks = []
213 ; path = ""
214 ; password = ""
215 ; invalidated = 0
216 ; hists =
217 { nav = cbnew 100 0.0
218 ; pat = cbnew 20 ""
219 ; pag = cbnew 10 ""
221 ; colorscale = 1.0
225 let vlog fmt =
226 if conf.verbose
227 then
228 Printf.kprintf prerr_endline fmt
229 else
230 Printf.kprintf ignore fmt
233 let writecmd fd s =
234 let len = String.length s in
235 let n = 4 + len in
236 let b = Buffer.create n in
237 Buffer.add_char b (Char.chr ((len lsr 24) land 0xff));
238 Buffer.add_char b (Char.chr ((len lsr 16) land 0xff));
239 Buffer.add_char b (Char.chr ((len lsr 8) land 0xff));
240 Buffer.add_char b (Char.chr ((len lsr 0) land 0xff));
241 Buffer.add_string b s;
242 let s' = Buffer.contents b in
243 let n' = Unix.write fd s' 0 n in
244 if n' != n then failwith "write failed";
247 let readcmd fd =
248 let s = "xxxx" in
249 let n = Unix.read fd s 0 4 in
250 if n != 4 then failwith "incomplete read(len)";
251 let len = 0
252 lor (Char.code s.[0] lsl 24)
253 lor (Char.code s.[1] lsl 16)
254 lor (Char.code s.[2] lsl 8)
255 lor (Char.code s.[3] lsl 0)
257 let s = String.create len in
258 let n = Unix.read fd s 0 len in
259 if n != len then failwith "incomplete read(data)";
263 let yratio y =
264 if y = state.maxy
265 then 1.0
266 else float y /. float state.maxy
269 let makecmd s l =
270 let b = Buffer.create 10 in
271 Buffer.add_string b s;
272 let rec combine = function
273 | [] -> b
274 | x :: xs ->
275 Buffer.add_char b ' ';
276 let s =
277 match x with
278 | `b b -> if b then "1" else "0"
279 | `s s -> s
280 | `i i -> string_of_int i
281 | `f f -> string_of_float f
282 | `I f -> string_of_int (truncate f)
284 Buffer.add_string b s;
285 combine xs;
287 combine l;
290 let wcmd s l =
291 let cmd = Buffer.contents (makecmd s l) in
292 writecmd state.csock cmd;
295 let calcips h =
296 if conf.presentation
297 then
298 let d = state.h - h in
299 max 0 ((d + 1) / 2)
300 else
301 conf.interpagespace
304 let calcheight () =
305 let rec f pn ph pi fh l =
306 match l with
307 | (n, _, h) :: rest ->
308 let ips = calcips h in
309 let fh =
310 if conf.presentation
311 then fh+ips
312 else fh
314 let fh = fh + ((n - pn) * (ph + pi)) in
315 f n h ips fh rest
317 | [] ->
318 let inc =
319 if conf.presentation
320 then 0
321 else -pi
323 let fh = fh + ((state.pagecount - pn) * (ph + pi)) + inc in
324 max 0 fh
326 let fh = f 0 0 0 0 state.pdims in
330 let getpageyh pageno =
331 let rec f pn ph pi y l =
332 match l with
333 | (n, _, h) :: rest ->
334 let ips = calcips h in
335 if n >= pageno
336 then
337 if conf.presentation && n = pageno
338 then
339 y + (pageno - pn) * (ph + pi) + pi, h
340 else
341 y + (pageno - pn) * (ph + pi), h
342 else
343 let y = y + (if conf.presentation then pi else 0) in
344 let y = y + (n - pn) * (ph + pi) in
345 f n h ips y rest
347 | [] ->
348 y + (pageno - pn) * (ph + pi), ph
350 f 0 0 0 0 state.pdims
353 let getpagey pageno = fst (getpageyh pageno);;
355 let layout y sh =
356 let rec f ~pageno ~pdimno ~prev ~py ~dy ~pdims ~cacheleft ~accu =
357 let ((w, h, ips) as curr), rest, pdimno, yinc =
358 match pdims with
359 | (pageno', w, h) :: rest when pageno' = pageno ->
360 let ips = calcips h in
361 let yinc = if conf.presentation then ips else 0 in
362 (w, h, ips), rest, pdimno + 1, yinc
363 | _ ->
364 prev, pdims, pdimno, 0
366 let dy = dy + yinc in
367 let py = py + yinc in
368 if pageno = state.pagecount || cacheleft = 0 || dy >= sh
369 then
370 accu
371 else
372 let vy = y + dy in
373 if py + h <= vy - yinc
374 then
375 let py = py + h + ips in
376 let dy = max 0 (py - y) in
377 f ~pageno:(pageno+1)
378 ~pdimno
379 ~prev:curr
382 ~pdims:rest
383 ~cacheleft
384 ~accu
385 else
386 let pagey = vy - py in
387 let pagevh = h - pagey in
388 let pagevh = min (sh - dy) pagevh in
389 let off = if yinc > 0 then py - vy else 0
391 let py = py + h + ips in
392 let e =
393 { pageno = pageno
394 ; pagedimno = pdimno
395 ; pagew = w
396 ; pageh = h
397 ; pagedispy = dy + off
398 ; pagey = pagey + off
399 ; pagevh = pagevh - off
402 let accu = e :: accu in
403 f ~pageno:(pageno+1)
404 ~pdimno
405 ~prev:curr
407 ~dy:(dy+pagevh+ips)
408 ~pdims:rest
409 ~cacheleft:(cacheleft-1)
410 ~accu
412 if state.invalidated = 0
413 then (
414 let accu =
416 ~pageno:0
417 ~pdimno:~-1
418 ~prev:(0,0,0)
419 ~py:0
420 ~dy:0
421 ~pdims:state.pdims
422 ~cacheleft:(cblen state.pagecache)
423 ~accu:[]
425 List.rev accu
427 else
431 let clamp incr =
432 let y = state.y + incr in
433 let y = max 0 y in
434 let y = min y (state.maxy - (if conf.maxhfit then state.h else 0)) in
438 let getopaque pageno =
439 try Some (Hashtbl.find state.pagemap (pageno + 1, state.w, conf.angle))
440 with Not_found -> None
443 let cache pageno opaque =
444 Hashtbl.replace state.pagemap (pageno + 1, state.w, conf.angle) opaque
447 let validopaque opaque = String.length opaque > 0;;
449 let render l =
450 match getopaque l.pageno with
451 | None when not state.rendering ->
452 state.rendering <- true;
453 cache l.pageno "";
454 wcmd "render" [`i (l.pageno + 1)
455 ;`i l.pagedimno
456 ;`i l.pagew
457 ;`i l.pageh];
459 | _ -> ()
462 let loadlayout layout =
463 let rec f all = function
464 | l :: ls ->
465 begin match getopaque l.pageno with
466 | None -> render l; f false ls
467 | Some opaque -> f (all && validopaque opaque) ls
469 | [] -> all
471 f (layout <> []) layout;
474 let preload () =
475 if conf.preload
476 then
477 let evictedvisible =
478 let evictedopaque = cbpeekw state.pagecache in
479 List.exists (fun l ->
480 match getopaque l.pageno with
481 | Some opaque when validopaque opaque ->
482 evictedopaque = opaque
483 | otherwise -> false
484 ) state.layout
486 if not evictedvisible
487 then
488 let rely = yratio state.y in
489 let presentation = conf.presentation in
490 let interpagespace = conf.interpagespace in
491 let maxy = state.maxy in
492 conf.presentation <- false;
493 conf.interpagespace <- 0;
494 state.maxy <- calcheight ();
495 let y = truncate (float state.maxy *. rely) in
496 let y = if y < state.h then 0 else y - state.h in
497 let pages = layout y (state.h*3) in
498 List.iter render pages;
499 conf.presentation <- presentation;
500 conf.interpagespace <- interpagespace;
501 state.maxy <- maxy;
504 let gotoy y =
505 let y = max 0 y in
506 let y = min state.maxy y in
507 let pages = layout y state.h in
508 let ready = loadlayout pages in
509 state.ty <- yratio y;
510 if conf.showall
511 then (
512 if ready
513 then (
514 state.layout <- pages;
515 state.y <- y;
516 Glut.postRedisplay ();
519 else (
520 state.layout <- pages;
521 state.y <- y;
522 Glut.postRedisplay ();
524 preload ();
527 let gotoy_and_clear_text y =
528 gotoy y;
529 if not conf.verbose then state.text <- "";
532 let addnav () =
533 cbput state.hists.nav (yratio state.y);
534 cbrfollowlen state.hists.nav;
537 let getnav () =
538 let y = cbget state.hists.nav ~-1 in
539 truncate (y *. float state.maxy)
542 let gotopage n top =
543 let y, h = getpageyh n in
544 addnav ();
545 gotoy_and_clear_text (y + (truncate (top *. float h)));
548 let gotopage1 n top =
549 let y = getpagey n in
550 addnav ();
551 gotoy_and_clear_text (y + top);
554 let invalidate () =
555 state.layout <- [];
556 state.pdims <- [];
557 state.rects <- [];
558 state.rects1 <- [];
559 state.invalidated <- state.invalidated + 1;
562 let scalecolor c =
563 let c = c *. state.colorscale in
564 (c, c, c);
567 let represent () =
568 let y =
569 match state.layout with
570 | [] ->
571 let rely = yratio state.y in
572 state.maxy <- calcheight ();
573 truncate (float state.maxy *. rely)
575 | l :: _ ->
576 state.maxy <- calcheight ();
577 getpagey l.pageno
579 gotoy y
582 let pagematrix () =
583 GlMat.mode `projection;
584 GlMat.load_identity ();
585 GlMat.rotate ~x:1.0 ~angle:180.0 ();
586 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
587 GlMat.scale3 (2.0 /. float state.w, 2.0 /. float state.h, 1.0);
590 let winmatrix () =
591 GlMat.mode `projection;
592 GlMat.load_identity ();
593 GlMat.rotate ~x:1.0 ~angle:180.0 ();
594 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
595 GlMat.scale3 (2.0 /. float state.winw, 2.0 /. float state.h, 1.0);
598 let reshape ~w ~h =
599 state.winw <- w;
600 let w = truncate (float w *. conf.zoom) - conf.scrollw in
601 state.w <- w;
602 state.h <- h;
603 GlMat.mode `modelview;
604 GlMat.load_identity ();
605 GlClear.color (scalecolor 1.0);
606 GlClear.clear [`color];
608 invalidate ();
609 wcmd "geometry" [`i w; `i h];
612 let showtext c s =
613 GlDraw.color (0.0, 0.0, 0.0);
614 GlDraw.rect
615 (0.0, float (state.h - 18))
616 (float (state.winw - conf.scrollw - 1), float state.h)
618 let font = Glut.BITMAP_8_BY_13 in
619 GlDraw.color (1.0, 1.0, 1.0);
620 GlPix.raster_pos ~x:0.0 ~y:(float (state.h - 5)) ();
621 Glut.bitmapCharacter ~font ~c:(Char.code c);
622 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s;
625 let enttext () =
626 let len = String.length state.text in
627 match state.textentry with
628 | None ->
629 if len > 0 then showtext ' ' state.text
631 | Some (c, text, _, _, _) ->
632 let s =
633 if len > 0
634 then
635 text ^ " [" ^ state.text ^ "]"
636 else
637 text
639 showtext c s;
642 let showtext c s =
643 if true
644 then (
645 state.text <- Printf.sprintf "%c%s" c s;
646 Glut.postRedisplay ();
648 else (
649 showtext c s;
650 Glut.swapBuffers ();
654 let act cmd =
655 match cmd.[0] with
656 | 'c' ->
657 state.pdims <- [];
659 | 'D' ->
660 state.rects <- state.rects1;
661 Glut.postRedisplay ()
663 | 'C' ->
664 let n = Scanf.sscanf cmd "C %d" (fun n -> n) in
665 state.pagecount <- n;
666 state.invalidated <- state.invalidated - 1;
667 if state.invalidated = 0
668 then represent ()
670 | 't' ->
671 let s = Scanf.sscanf cmd "t %n"
672 (fun n -> String.sub cmd n (String.length cmd - n))
674 Glut.setWindowTitle s
676 | 'T' ->
677 let s = Scanf.sscanf cmd "T %n"
678 (fun n -> String.sub cmd n (String.length cmd - n))
680 if state.textentry = None
681 then (
682 state.text <- s;
683 showtext ' ' s;
685 else (
686 state.text <- s;
687 Glut.postRedisplay ();
690 | 'V' ->
691 if conf.verbose
692 then
693 let s = Scanf.sscanf cmd "V %n"
694 (fun n -> String.sub cmd n (String.length cmd - n))
696 state.text <- s;
697 showtext ' ' s;
699 | 'F' ->
700 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
701 Scanf.sscanf cmd "F %d %d %f %f %f %f %f %f %f %f"
702 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
703 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
705 let y = (getpagey pageno) + truncate y0 in
706 addnav ();
707 gotoy y;
708 state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)]
710 | 'R' ->
711 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
712 Scanf.sscanf cmd "R %d %d %f %f %f %f %f %f %f %f"
713 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
714 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
716 state.rects1 <-
717 (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
719 | 'r' ->
720 let n, w, h, r, p =
721 Scanf.sscanf cmd "r %d %d %d %d %s"
722 (fun n w h r p -> (n, w, h, r, p))
724 Hashtbl.replace state.pagemap (n, w, r) p;
725 let opaque = cbpeekw state.pagecache in
726 if validopaque opaque
727 then (
728 let k =
729 Hashtbl.fold
730 (fun k v a -> if v = opaque then k else a)
731 state.pagemap (-1, -1, -1)
733 wcmd "free" [`s opaque];
734 Hashtbl.remove state.pagemap k
736 cbput state.pagecache p;
737 state.rendering <- false;
738 if conf.showall
739 then gotoy (truncate (ceil (state.ty *. float state.maxy)))
740 else (
741 let visible = List.exists (fun l -> l.pageno + 1 = n) state.layout in
742 if visible
743 then gotoy state.y
744 else (ignore (loadlayout state.layout); preload ())
747 | 'l' ->
748 let (n, w, h) as pdim =
749 Scanf.sscanf cmd "l %d %d %d" (fun n w h -> n, w, h)
751 state.pdims <- pdim :: state.pdims
753 | 'o' ->
754 let (l, n, t, h, pos) =
755 Scanf.sscanf cmd "o %d %d %d %d %n" (fun l n t h pos -> l, n, t, h, pos)
757 let s = String.sub cmd pos (String.length cmd - pos) in
758 let s =
759 let l = String.length s in
760 let b = Buffer.create (String.length s) in
761 let rec loop pc2 i =
762 if i = l
763 then ()
764 else
765 let pc2 =
766 match s.[i] with
767 | '\xa0' when pc2 -> Buffer.add_char b ' '; false
768 | '\xc2' -> true
769 | c ->
770 let c = if Char.code c land 0x80 = 0 then c else '?' in
771 Buffer.add_char b c;
772 false
774 loop pc2 (i+1)
776 loop false 0;
777 Buffer.contents b
779 let outline = (s, l, n, float t /. float h) in
780 let outlines =
781 match state.outlines with
782 | Olist outlines -> Olist (outline :: outlines)
783 | Oarray _ -> Olist [outline]
784 | Onarrow _ -> Olist [outline]
786 state.outlines <- outlines
788 | _ ->
789 log "unknown cmd `%S'" cmd
792 let now = Unix.gettimeofday;;
794 let idle () =
795 let rec loop delay =
796 let r, _, _ = Unix.select [state.csock] [] [] delay in
797 begin match r with
798 | [] ->
799 if conf.autoscroll
800 then begin
801 let y = state.y + conf.scrollincr in
802 let y = if y >= state.maxy then 0 else y in
803 gotoy y;
804 state.text <- "";
805 end;
807 | _ ->
808 let cmd = readcmd state.csock in
809 act cmd;
810 loop 0.0
811 end;
812 in loop 0.001
815 let onhist cb = function
816 | HCprev -> cbget cb ~-1
817 | HCnext -> cbget cb 1
818 | HCfirst -> cbget cb ~-(cb.rc)
819 | HClast -> cbget cb (cb.len - 1 - cb.rc)
822 let search pattern forward =
823 if String.length pattern > 0
824 then
825 let pn, py =
826 match state.layout with
827 | [] -> 0, 0
828 | l :: _ ->
829 l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
831 let cmd =
832 let b = makecmd "search"
833 [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0)]
835 Buffer.add_char b ',';
836 Buffer.add_string b pattern;
837 Buffer.add_char b '\000';
838 Buffer.contents b;
840 writecmd state.csock cmd;
843 let intentry text key =
844 let c = Char.unsafe_chr key in
845 match c with
846 | '0' .. '9' ->
847 let s = "x" in s.[0] <- c;
848 let text = text ^ s in
849 TEcont text
851 | _ ->
852 state.text <- Printf.sprintf "invalid char (%d, `%c')" key c;
853 TEcont text
856 let addchar s c =
857 let b = Buffer.create (String.length s + 1) in
858 Buffer.add_string b s;
859 Buffer.add_char b c;
860 Buffer.contents b;
863 let textentry text key =
864 let c = Char.unsafe_chr key in
865 match c with
866 | _ when key >= 32 && key < 127 ->
867 let text = addchar text c in
868 TEcont text
870 | _ ->
871 log "unhandled key %d char `%c'" key (Char.unsafe_chr key);
872 TEcont text
875 let rotate angle =
876 conf.angle <- angle;
877 invalidate ();
878 wcmd "rotate" [`i angle];
881 let optentry text key =
882 let btos b = if b then "on" else "off" in
883 let c = Char.unsafe_chr key in
884 match c with
885 | 's' ->
886 let ondone s =
887 try conf.scrollincr <- int_of_string s with exc ->
888 state.text <- Printf.sprintf "bad integer `%s': %s"
889 s (Printexc.to_string exc)
891 TEswitch ('#', "", None, intentry, ondone)
893 | 'R' ->
894 let ondone s =
895 match try
896 Some (int_of_string s)
897 with exc ->
898 state.text <- Printf.sprintf "bad integer `%s': %s"
899 s (Printexc.to_string exc);
900 None
901 with
902 | Some angle -> rotate angle
903 | None -> ()
905 TEswitch ('^', "", None, intentry, ondone)
907 | 'i' ->
908 conf.icase <- not conf.icase;
909 TEdone ("case insensitive search " ^ (btos conf.icase))
911 | 'p' ->
912 conf.preload <- not conf.preload;
913 gotoy state.y;
914 TEdone ("preload " ^ (btos conf.preload))
916 | 'v' ->
917 conf.verbose <- not conf.verbose;
918 TEdone ("verbose " ^ (btos conf.verbose))
920 | 'h' ->
921 conf.maxhfit <- not conf.maxhfit;
922 state.maxy <- state.maxy + (if conf.maxhfit then -state.h else state.h);
923 TEdone ("maxhfit " ^ (btos conf.maxhfit))
925 | 'c' ->
926 conf.crophack <- not conf.crophack;
927 TEdone ("crophack " ^ btos conf.crophack)
929 | 'a' ->
930 conf.showall <- not conf.showall;
931 TEdone ("showall " ^ btos conf.showall)
933 | 'f' ->
934 conf.underinfo <- not conf.underinfo;
935 TEdone ("underinfo " ^ btos conf.underinfo)
937 | 'S' ->
938 let ondone s =
940 conf.interpagespace <- int_of_string s;
941 let rely = yratio state.y in
942 state.maxy <- calcheight ();
943 gotoy (truncate (float state.maxy *. rely));
944 with exc ->
945 state.text <- Printf.sprintf "bad integer `%s': %s"
946 s (Printexc.to_string exc)
948 TEswitch ('%', "", None, intentry, ondone)
950 | _ ->
951 state.text <- Printf.sprintf "bad option %d `%c'" key c;
952 TEstop
955 let maxoutlinerows () = (state.h - 31) / 16;;
957 let enterselector allowdel outlines errmsg =
958 if Array.length outlines = 0
959 then (
960 showtext ' ' errmsg;
962 else (
963 state.text <- "";
964 Glut.setCursor Glut.CURSOR_INHERIT;
965 let pageno =
966 match state.layout with
967 | [] -> -1
968 | {pageno=pageno} :: rest -> pageno
970 let active =
971 let rec loop n =
972 if n = Array.length outlines
973 then 0
974 else
975 let (_, _, outlinepageno, _) = outlines.(n) in
976 if outlinepageno >= pageno then n else loop (n+1)
978 loop 0
980 state.outline <-
981 Some (allowdel, active,
982 max 0 ((active - maxoutlinerows () / 2)), outlines, "");
983 Glut.postRedisplay ();
987 let enteroutlinemode () =
988 let outlines =
989 match state.outlines with
990 | Oarray a -> a
991 | Olist l ->
992 let a = Array.of_list (List.rev l) in
993 state.outlines <- Oarray a;
995 | Onarrow (a, b) -> a
997 enterselector false outlines "Document has no outline";
1000 let enterbookmarkmode () =
1001 let bookmarks = Array.of_list state.bookmarks in
1002 enterselector true bookmarks "Document has no bookmarks (yet)";
1005 let quickbookmark ?title () =
1006 match state.layout with
1007 | [] -> ()
1008 | l :: _ ->
1009 let title =
1010 match title with
1011 | None ->
1012 let sec = Unix.gettimeofday () in
1013 let tm = Unix.localtime sec in
1014 Printf.sprintf "Quick %d visited (%d/%d/%d %d:%d)"
1015 l.pageno
1016 tm.Unix.tm_mday
1017 tm.Unix.tm_mon
1018 (tm.Unix.tm_year + 1900)
1019 tm.Unix.tm_hour
1020 tm.Unix.tm_min
1021 | Some title -> title
1023 state.bookmarks <-
1024 (title, 0, l.pageno, float l.pagey /. float l.pageh) :: state.bookmarks
1027 let doreshape w h =
1028 state.fullscreen <- None;
1029 Glut.reshapeWindow w h;
1032 let opendoc path password =
1033 invalidate ();
1034 state.path <- path;
1035 state.password <- password;
1036 Hashtbl.clear state.pagemap;
1038 writecmd state.csock ("open " ^ path ^ "\000" ^ password ^ "\000");
1039 Glut.setWindowTitle ("llpp " ^ Filename.basename path);
1040 wcmd "geometry" [`i state.w; `i state.h];
1043 let viewkeyboard ~key ~x ~y =
1044 let enttext te =
1045 state.textentry <- te;
1046 state.text <- "";
1047 enttext ();
1048 Glut.postRedisplay ()
1050 match state.textentry with
1051 | None ->
1052 let c = Char.chr key in
1053 begin match c with
1054 | '\027' | 'q' ->
1055 exit 0
1057 | '\008' ->
1058 let y = getnav () in
1059 gotoy_and_clear_text y
1061 | 'o' ->
1062 enteroutlinemode ()
1064 | 'u' ->
1065 state.rects <- [];
1066 state.text <- "";
1067 Glut.postRedisplay ()
1069 | '/' | '?' ->
1070 let ondone isforw s =
1071 cbput state.hists.pat s;
1072 cbrfollowlen state.hists.pat;
1073 state.searchpattern <- s;
1074 search s isforw
1076 enttext (Some (c, "", Some (onhist state.hists.pat),
1077 textentry, ondone (c ='/')))
1079 | '+' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1080 conf.zoom <- min 2.2 (conf.zoom +. 0.1);
1081 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1082 reshape state.winw state.h
1084 | '+' ->
1085 let ondone s =
1086 let n =
1087 try int_of_string s with exc ->
1088 state.text <- Printf.sprintf "bad integer `%s': %s"
1089 s (Printexc.to_string exc);
1090 max_int
1092 if n != max_int
1093 then (
1094 conf.pagebias <- n;
1095 state.text <- "page bias is now " ^ string_of_int n;
1098 enttext (Some ('+', "", None, intentry, ondone))
1100 | '-' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1101 conf.zoom <- max 0.1 (conf.zoom -. 0.1);
1102 if conf.zoom <= 1.0 then state.x <- 0;
1103 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1104 reshape state.winw state.h;
1106 | '-' ->
1107 let ondone msg =
1108 state.text <- msg;
1110 enttext (Some ('-', "", None, optentry, ondone))
1112 | '0' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1113 state.x <- 0;
1114 conf.zoom <- 1.0;
1115 state.text <- "zoom is 100%";
1116 reshape state.winw state.h
1118 | '1' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1119 let n =
1120 let rec find n maxh nformaxh = function
1121 | (_, _, h) :: rest ->
1122 if h > maxh
1123 then find (n+1) h n rest
1124 else find (n+1) maxh nformaxh rest
1125 | [] -> nformaxh
1127 find 0 0 0 state.pdims
1130 let rect = getpdimrect n in
1131 let pw = rect.(1) -. rect.(0) in
1132 let ph = rect.(3) -. rect.(2) in
1134 let num = (float state.h *. pw) +. (ph *. float conf.scrollw) in
1135 let den = ph *. float state.winw in
1136 let zoom = num /. den in
1138 if zoom < 1.0
1139 then (
1140 conf.zoom <- zoom;
1141 state.x <- 0;
1142 state.text <- Printf.sprintf "zoom is %3.1f%%" (100.0*.conf.zoom);
1143 reshape state.winw state.h;
1146 | '0' .. '9' ->
1147 let ondone s =
1148 let n =
1149 try int_of_string s with exc ->
1150 state.text <- Printf.sprintf "bad integer `%s': %s"
1151 s (Printexc.to_string exc);
1154 if n >= 0
1155 then (
1156 addnav ();
1157 cbput state.hists.pag (string_of_int n);
1158 cbrfollowlen state.hists.pag;
1159 gotoy_and_clear_text (getpagey (n + conf.pagebias - 1))
1162 let pageentry text key =
1163 match Char.unsafe_chr key with
1164 | 'g' -> TEdone text
1165 | _ -> intentry text key
1167 let text = "x" in text.[0] <- c;
1168 enttext (Some (':', text, Some (onhist state.hists.pag),
1169 pageentry, ondone))
1171 | 'b' ->
1172 conf.scrollw <- if conf.scrollw > 0 then 0 else 7;
1173 reshape state.winw state.h;
1175 | 'l' ->
1176 conf.hlinks <- not conf.hlinks;
1177 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
1178 Glut.postRedisplay ()
1180 | 'a' ->
1181 conf.autoscroll <- not conf.autoscroll
1183 | 'P' ->
1184 conf.presentation <- not conf.presentation;
1185 showtext ' ' ("presentation mode " ^
1186 if conf.presentation then "on" else "off");
1187 represent ()
1189 | 'f' ->
1190 begin match state.fullscreen with
1191 | None ->
1192 state.fullscreen <- Some (state.winw, state.h);
1193 Glut.fullScreen ()
1194 | Some (w, h) ->
1195 state.fullscreen <- None;
1196 doreshape w h
1199 | 'g' ->
1200 gotoy_and_clear_text 0
1202 | 'n' ->
1203 search state.searchpattern true
1205 | 'p' | 'N' ->
1206 search state.searchpattern false
1208 | 't' ->
1209 begin match state.layout with
1210 | [] -> ()
1211 | l :: _ ->
1212 gotoy_and_clear_text (getpagey l.pageno)
1215 | ' ' ->
1216 begin match List.rev state.layout with
1217 | [] -> ()
1218 | l :: _ ->
1219 let pageno = min (l.pageno+1) (state.pagecount-1) in
1220 gotoy_and_clear_text (getpagey pageno)
1223 | '\127' ->
1224 begin match state.layout with
1225 | [] -> ()
1226 | l :: _ ->
1227 let pageno = max 0 (l.pageno-1) in
1228 gotoy_and_clear_text (getpagey pageno)
1231 | '=' ->
1232 let f (fn, ln) l =
1233 if fn = -1 then l.pageno, l.pageno else fn, l.pageno
1235 let fn, ln = List.fold_left f (-1, -1) state.layout in
1236 let s =
1237 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1238 let percent =
1239 if maxy <= 0
1240 then 100.
1241 else (100. *. (float state.y /. float maxy)) in
1242 if fn = ln
1243 then
1244 Printf.sprintf "Page %d of %d %.2f%%"
1245 (fn+1) state.pagecount percent
1246 else
1247 Printf.sprintf
1248 "Pages %d-%d of %d %.2f%%"
1249 (fn+1) (ln+1) state.pagecount percent
1251 showtext ' ' s;
1253 | 'w' ->
1254 begin match state.layout with
1255 | [] -> ()
1256 | l :: _ ->
1257 doreshape (l.pagew + conf.scrollw) l.pageh;
1258 Glut.postRedisplay ();
1261 | '\'' ->
1262 enterbookmarkmode ()
1264 | 'm' ->
1265 let ondone s =
1266 match state.layout with
1267 | l :: _ ->
1268 state.bookmarks <-
1269 (s, 0, l.pageno, float l.pagey /. float l.pageh)
1270 :: state.bookmarks
1271 | _ -> ()
1273 enttext (Some ('~', "", None, textentry, ondone))
1275 | '~' ->
1276 quickbookmark ();
1277 showtext ' ' "Quick bookmark added";
1279 | 'z' ->
1280 begin match state.layout with
1281 | l :: _ ->
1282 let rect = getpdimrect l.pagedimno in
1283 let w, h =
1284 if conf.crophack
1285 then
1286 (truncate (1.8 *. (rect.(1) -. rect.(0))),
1287 truncate (1.2 *. (rect.(3) -. rect.(0))))
1288 else
1289 (truncate (rect.(1) -. rect.(0)),
1290 truncate (rect.(3) -. rect.(0)))
1292 doreshape (w + conf.scrollw) (h + conf.interpagespace);
1293 Glut.postRedisplay ();
1295 | [] -> ()
1298 | '<' | '>' ->
1299 rotate (conf.angle + (if c = '>' then 30 else -30));
1301 | '[' | ']' ->
1302 state.colorscale <-
1303 max 0.0
1304 (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0);
1305 Glut.postRedisplay ()
1307 | 'k' -> gotoy (clamp (-conf.scrollincr))
1308 | 'j' -> gotoy (clamp conf.scrollincr)
1310 | 'r' -> opendoc state.path state.password
1312 | _ ->
1313 vlog "huh? %d %c" key (Char.chr key);
1316 | Some (c, text, onhist, onkey, ondone) when key = 8 ->
1317 let len = String.length text in
1318 if len = 0
1319 then (
1320 state.textentry <- None;
1321 Glut.postRedisplay ();
1323 else (
1324 let s = String.sub text 0 (len - 1) in
1325 enttext (Some (c, s, onhist, onkey, ondone))
1328 | Some (c, text, onhist, onkey, ondone) ->
1329 begin match Char.unsafe_chr key with
1330 | '\r' | '\n' ->
1331 ondone text;
1332 state.textentry <- None;
1333 Glut.postRedisplay ()
1335 | '\027' ->
1336 state.textentry <- None;
1337 Glut.postRedisplay ()
1339 | _ ->
1340 begin match onkey text key with
1341 | TEdone text ->
1342 state.textentry <- None;
1343 ondone text;
1344 Glut.postRedisplay ()
1346 | TEcont text ->
1347 enttext (Some (c, text, onhist, onkey, ondone));
1349 | TEstop ->
1350 state.textentry <- None;
1351 Glut.postRedisplay ()
1353 | TEswitch te ->
1354 state.textentry <- Some te;
1355 Glut.postRedisplay ()
1356 end;
1357 end;
1360 let narrow outlines pattern =
1361 let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in
1362 match reopt with
1363 | None -> None
1364 | Some re ->
1365 let rec fold accu n =
1366 if n = -1
1367 then accu
1368 else
1369 let (s, _, _, _) as o = outlines.(n) in
1370 let accu =
1371 if (try ignore (Str.search_forward re s 0); true
1372 with Not_found -> false)
1373 then (o :: accu)
1374 else accu
1376 fold accu (n-1)
1378 let matched = fold [] (Array.length outlines - 1) in
1379 if matched = [] then None else Some (Array.of_list matched)
1382 let outlinekeyboard ~key ~x ~y (allowdel, active, first, outlines, qsearch) =
1383 let search active pattern incr =
1384 let dosearch re =
1385 let rec loop n =
1386 if n = Array.length outlines || n = -1
1387 then None
1388 else
1389 let (s, _, _, _) = outlines.(n) in
1391 (try ignore (Str.search_forward re s 0); true
1392 with Not_found -> false)
1393 then Some n
1394 else loop (n + incr)
1396 loop active
1399 let re = Str.regexp_case_fold pattern in
1400 dosearch re
1401 with Failure s ->
1402 state.text <- s;
1403 None
1405 let firstof active = max 0 (active - maxoutlinerows () / 2) in
1406 match key with
1407 | 27 ->
1408 if String.length qsearch = 0
1409 then (
1410 state.text <- "";
1411 state.outline <- None;
1412 Glut.postRedisplay ();
1414 else (
1415 state.text <- "";
1416 state.outline <- Some (allowdel, active, first, outlines, "");
1417 Glut.postRedisplay ();
1420 | 18 | 19 ->
1421 let incr = if key = 18 then -1 else 1 in
1422 let active, first =
1423 match search (active + incr) qsearch incr with
1424 | None ->
1425 state.text <- qsearch ^ " [not found]";
1426 active, first
1427 | Some active ->
1428 state.text <- qsearch;
1429 active, firstof active
1431 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1432 Glut.postRedisplay ();
1434 | 8 ->
1435 let len = String.length qsearch in
1436 if len = 0
1437 then ()
1438 else (
1439 if len = 1
1440 then (
1441 state.text <- "";
1442 state.outline <- Some (allowdel, active, first, outlines, "");
1444 else
1445 let qsearch = String.sub qsearch 0 (len - 1) in
1446 let active, first =
1447 match search active qsearch ~-1 with
1448 | None ->
1449 state.text <- qsearch ^ " [not found]";
1450 active, first
1451 | Some active ->
1452 state.text <- qsearch;
1453 active, firstof active
1455 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1457 Glut.postRedisplay ()
1459 | 13 ->
1460 if active < Array.length outlines
1461 then (
1462 let (_, _, n, t) = outlines.(active) in
1463 gotopage n t;
1465 state.text <- "";
1466 if allowdel then state.bookmarks <- Array.to_list outlines;
1467 state.outline <- None;
1468 Glut.postRedisplay ();
1470 | _ when key >= 32 && key < 127 ->
1471 let pattern = addchar qsearch (Char.chr key) in
1472 let active, first =
1473 match search active pattern 1 with
1474 | None ->
1475 state.text <- pattern ^ " [not found]";
1476 active, first
1477 | Some active ->
1478 state.text <- pattern;
1479 active, firstof active
1481 state.outline <- Some (allowdel, active, first, outlines, pattern);
1482 Glut.postRedisplay ()
1484 | 14 when not allowdel ->
1485 let optoutlines = narrow outlines qsearch in
1486 begin match optoutlines with
1487 | None -> state.text <- "can't narrow"
1488 | Some outlines ->
1489 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1490 match state.outlines with
1491 | Olist l -> ()
1492 | Oarray a -> state.outlines <- Onarrow (outlines, a)
1493 | Onarrow (a, b) -> state.outlines <- Onarrow (outlines, b)
1494 end;
1495 Glut.postRedisplay ()
1497 | 21 when not allowdel ->
1498 let outline =
1499 match state.outlines with
1500 | Oarray a -> a
1501 | Olist l ->
1502 let a = Array.of_list (List.rev l) in
1503 state.outlines <- Oarray a;
1505 | Onarrow (a, b) ->
1506 state.outlines <- Oarray b;
1509 state.outline <- Some (allowdel, 0, 0, outline, qsearch);
1510 Glut.postRedisplay ()
1512 | 12 ->
1513 state.outline <-
1514 Some (allowdel, active, firstof active, outlines, qsearch);
1515 Glut.postRedisplay ()
1517 | 127 when allowdel ->
1518 let len = Array.length outlines - 1 in
1519 if len = 0
1520 then (
1521 state.outline <- None;
1522 state.bookmarks <- [];
1524 else (
1525 let bookmarks = Array.init len
1526 (fun i ->
1527 let i = if i >= active then i + 1 else i in
1528 outlines.(i)
1531 state.outline <-
1532 Some (allowdel,
1533 min active (len-1),
1534 min first (len-1),
1535 bookmarks, qsearch)
1538 Glut.postRedisplay ()
1540 | _ -> log "unknown key %d" key
1543 let keyboard ~key ~x ~y =
1544 if key = 7
1545 then
1546 wcmd "interrupt" []
1547 else
1548 match state.outline with
1549 | None -> viewkeyboard ~key ~x ~y
1550 | Some outline -> outlinekeyboard ~key ~x ~y outline
1553 let special ~key ~x ~y =
1554 match state.outline with
1555 | None ->
1556 begin match state.textentry with
1557 | None ->
1558 let y =
1559 match key with
1560 | Glut.KEY_F3 -> search state.searchpattern true; state.y
1561 | Glut.KEY_UP -> clamp (-conf.scrollincr)
1562 | Glut.KEY_DOWN -> clamp conf.scrollincr
1563 | Glut.KEY_PAGE_UP ->
1564 if Glut.getModifiers () land Glut.active_ctrl != 0
1565 then
1566 match state.layout with
1567 | [] -> state.y
1568 | l :: _ -> state.y - l.pagey
1569 else
1570 clamp (-state.h)
1571 | Glut.KEY_PAGE_DOWN ->
1572 if Glut.getModifiers () land Glut.active_ctrl != 0
1573 then
1574 match List.rev state.layout with
1575 | [] -> state.y
1576 | l :: _ -> getpagey l.pageno
1577 else
1578 clamp state.h
1579 | Glut.KEY_HOME -> addnav (); 0
1580 | Glut.KEY_END ->
1581 addnav ();
1582 state.maxy - (if conf.maxhfit then state.h else 0)
1584 | Glut.KEY_RIGHT when conf.zoom > 1.0 ->
1585 state.x <- state.x - 10;
1586 state.y
1587 | Glut.KEY_LEFT when conf.zoom > 1.0 ->
1588 state.x <- state.x + 10;
1589 state.y
1591 | _ -> state.y
1593 gotoy_and_clear_text y
1595 | Some (c, s, Some onhist, onkey, ondone) ->
1596 let s =
1597 match key with
1598 | Glut.KEY_UP -> onhist HCprev
1599 | Glut.KEY_DOWN -> onhist HCnext
1600 | Glut.KEY_HOME -> onhist HCfirst
1601 | Glut.KEY_END -> onhist HClast
1602 | _ -> state.text
1604 state.textentry <- Some (c, s, Some onhist, onkey, ondone);
1605 Glut.postRedisplay ()
1607 | _ -> ()
1610 | Some (allowdel, active, first, outlines, qsearch) ->
1611 let maxrows = maxoutlinerows () in
1612 let navigate incr =
1613 let active = active + incr in
1614 let active = max 0 (min active (Array.length outlines - 1)) in
1615 let first =
1616 if active > first
1617 then
1618 let rows = active - first in
1619 if rows > maxrows then active - maxrows else first
1620 else active
1622 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1623 Glut.postRedisplay ()
1625 match key with
1626 | Glut.KEY_UP -> navigate ~-1
1627 | Glut.KEY_DOWN -> navigate 1
1628 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
1629 | Glut.KEY_PAGE_DOWN -> navigate maxrows
1631 | Glut.KEY_HOME ->
1632 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1633 Glut.postRedisplay ()
1635 | Glut.KEY_END ->
1636 let active = Array.length outlines - 1 in
1637 let first = max 0 (active - maxrows) in
1638 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1639 Glut.postRedisplay ()
1641 | _ -> ()
1644 let drawplaceholder l =
1645 GlDraw.color (scalecolor 1.0);
1646 GlDraw.rect
1647 (0.0, float l.pagedispy)
1648 (float l.pagew, float (l.pagedispy + l.pagevh))
1650 let x = 0.0
1651 and y = float (l.pagedispy + 13) in
1652 let font = Glut.BITMAP_8_BY_13 in
1653 GlDraw.color (0.0, 0.0, 0.0);
1654 GlPix.raster_pos ~x ~y ();
1655 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c))
1656 ("Loading " ^ string_of_int (l.pageno + 1));
1659 let now () = Unix.gettimeofday ();;
1661 let drawpage i l =
1662 begin match getopaque l.pageno with
1663 | Some opaque when validopaque opaque ->
1664 if state.textentry = None
1665 then GlDraw.color (scalecolor 1.0)
1666 else GlDraw.color (scalecolor 0.4);
1667 let a = now () in
1668 draw (l.pagedispy, l.pagew, l.pagevh, l.pagey, conf.hlinks)
1669 opaque;
1670 let b = now () in
1671 let d = b-.a in
1672 vlog "draw %d %f sec" l.pageno d;
1674 | _ ->
1675 drawplaceholder l;
1676 end;
1677 l.pagedispy + l.pagevh;
1680 let scrollph y =
1681 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1682 let sh = (float (maxy + state.h) /. float state.h) in
1683 let sh = float state.h /. sh in
1684 let sh = max sh (float conf.scrollh) in
1686 let percent =
1687 if state.y = state.maxy
1688 then 1.0
1689 else float y /. float maxy
1691 let position = (float state.h -. sh) *. percent in
1693 let position =
1694 if position +. sh > float state.h
1695 then float state.h -. sh
1696 else position
1698 position, sh;
1701 let scrollindicator () =
1702 GlDraw.color (0.64 , 0.64, 0.64);
1703 GlDraw.rect
1704 (float (state.winw - conf.scrollw), 0.)
1705 (float state.winw, float state.h)
1707 GlDraw.color (0.0, 0.0, 0.0);
1709 let position, sh = scrollph state.y in
1710 GlDraw.rect
1711 (float (state.winw - conf.scrollw), position)
1712 (float state.winw, position +. sh)
1716 let showsel margin =
1717 match state.mstate with
1718 | Mnone | Mscroll _ | Mpan _ ->
1721 | Msel ((x0, y0), (x1, y1)) ->
1722 let rec loop = function
1723 | l :: ls ->
1724 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1725 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1726 then
1727 match getopaque l.pageno with
1728 | Some opaque when validopaque opaque ->
1729 let oy = -l.pagey + l.pagedispy in
1730 seltext opaque
1731 (x0 - margin - state.x, y0,
1732 x1 - margin - state.x, y1) oy;
1734 | _ -> ()
1735 else loop ls
1736 | [] -> ()
1738 loop state.layout
1741 let showrects () =
1742 let panx = float state.x in
1743 Gl.enable `blend;
1744 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
1745 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1746 List.iter
1747 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
1748 List.iter (fun l ->
1749 if l.pageno = pageno
1750 then (
1751 let d = float (l.pagedispy - l.pagey) in
1752 GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5;
1753 GlDraw.begins `quads;
1755 GlDraw.vertex2 (x0+.panx, y0+.d);
1756 GlDraw.vertex2 (x1+.panx, y1+.d);
1757 GlDraw.vertex2 (x2+.panx, y2+.d);
1758 GlDraw.vertex2 (x3+.panx, y3+.d);
1760 GlDraw.ends ();
1762 ) state.layout
1763 ) state.rects
1765 Gl.disable `blend;
1768 let showoutline = function
1769 | None -> ()
1770 | Some (allowdel, active, first, outlines, qsearch) ->
1771 Gl.enable `blend;
1772 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1773 GlDraw.color (0., 0., 0.) ~alpha:0.85;
1774 GlDraw.rect (0., 0.) (float state.w, float state.h);
1775 Gl.disable `blend;
1777 GlDraw.color (1., 1., 1.);
1778 let font = Glut.BITMAP_9_BY_15 in
1779 let draw_string x y s =
1780 GlPix.raster_pos ~x ~y ();
1781 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
1783 let rec loop row =
1784 if row = Array.length outlines || (row - first) * 16 > state.h
1785 then ()
1786 else (
1787 let (s, l, _, _) = outlines.(row) in
1788 let y = (row - first) * 16 in
1789 let x = 5 + 15*l in
1790 if row = active
1791 then (
1792 Gl.enable `blend;
1793 GlDraw.polygon_mode `both `line;
1794 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1795 GlDraw.color (1., 1., 1.) ~alpha:0.9;
1796 GlDraw.rect (0., float (y + 1))
1797 (float (state.w - 1), float (y + 18));
1798 GlDraw.polygon_mode `both `fill;
1799 Gl.disable `blend;
1800 GlDraw.color (1., 1., 1.);
1802 draw_string (float x) (float (y + 16)) s;
1803 loop (row+1)
1806 loop first
1809 let display () =
1810 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1811 GlDraw.viewport margin 0 state.w state.h;
1812 pagematrix ();
1813 GlClear.color (scalecolor 0.5);
1814 GlClear.clear [`color];
1815 if state.x != 0
1816 then (
1817 let x = float state.x in
1818 GlMat.translate ~x ();
1820 if conf.zoom > 1.0
1821 then (
1822 Gl.enable `scissor_test;
1823 GlMisc.scissor 0 0 (state.winw - conf.scrollw) state.h;
1825 let _lasty = List.fold_left drawpage 0 (state.layout) in
1826 if conf.zoom > 1.0
1827 then
1828 Gl.disable `scissor_test
1830 if state.x != 0
1831 then (
1832 let x = -.float state.x in
1833 GlMat.translate ~x ();
1835 showrects ();
1836 showsel margin;
1837 GlDraw.viewport 0 0 state.winw state.h;
1838 winmatrix ();
1839 scrollindicator ();
1840 showoutline state.outline;
1841 enttext ();
1842 Glut.swapBuffers ();
1845 let getunder x y =
1846 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1847 let x = x - margin - state.x in
1848 let rec f = function
1849 | l :: rest ->
1850 begin match getopaque l.pageno with
1851 | Some opaque when validopaque opaque ->
1852 let y = y - l.pagedispy in
1853 if y > 0
1854 then
1855 let y = l.pagey + y in
1856 match whatsunder opaque x y with
1857 | Unone -> f rest
1858 | under -> under
1859 else
1860 f rest
1861 | _ ->
1862 f rest
1864 | [] -> Unone
1866 f state.layout
1869 let mouse ~button ~bstate ~x ~y =
1870 match button with
1871 | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP ->
1872 let incr =
1873 if n = 3
1874 then
1875 -conf.scrollincr
1876 else
1877 conf.scrollincr
1879 let incr = incr * 2 in
1880 let y = clamp incr in
1881 gotoy_and_clear_text y
1883 | Glut.LEFT_BUTTON when state.outline = None
1884 && Glut.getModifiers () land Glut.active_ctrl != 0 ->
1885 if bstate = Glut.DOWN
1886 then (
1887 Glut.setCursor Glut.CURSOR_CROSSHAIR;
1888 state.mstate <- Mpan (x, y)
1890 else
1891 state.mstate <- Mnone
1893 | Glut.LEFT_BUTTON
1894 when state.outline = None && x > state.winw - conf.scrollw ->
1895 if bstate = Glut.DOWN
1896 then
1897 let position, sh = scrollph state.y in
1898 if y > truncate position && y < truncate (position +. sh)
1899 then
1900 state.mstate <- Mscroll
1901 else
1902 let percent = float y /. float state.h in
1903 let desty = truncate (float (state.maxy - state.h) *. percent) in
1904 gotoy desty;
1905 state.mstate <- Mscroll
1906 else
1907 state.mstate <- Mnone
1909 | Glut.LEFT_BUTTON when state.outline = None ->
1910 let dest = if bstate = Glut.DOWN then getunder x y else Unone in
1911 begin match dest with
1912 | Ulinkgoto (pageno, top) ->
1913 if pageno >= 0
1914 then
1915 gotopage1 pageno top
1917 | Ulinkuri s ->
1918 print_endline s
1920 | Unone when bstate = Glut.DOWN ->
1921 Glut.setCursor Glut.CURSOR_CROSSHAIR;
1922 state.mstate <- Mpan (x, y);
1924 | Unone | Utext _ ->
1925 if bstate = Glut.DOWN
1926 then (
1927 if conf.angle mod 360 = 0
1928 then (
1929 state.mstate <- Msel ((x, y), (x, y));
1930 Glut.postRedisplay ()
1933 else (
1934 match state.mstate with
1935 | Mnone -> ()
1937 | Mscroll ->
1938 state.mstate <- Mnone
1940 | Mpan _ ->
1941 Glut.setCursor Glut.CURSOR_INHERIT;
1942 state.mstate <- Mnone
1944 | Msel ((x0, y0), (x1, y1)) ->
1945 let f l =
1946 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1947 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1948 then
1949 match getopaque l.pageno with
1950 | Some opaque when validopaque opaque ->
1951 copysel opaque
1952 | _ -> ()
1954 List.iter f state.layout;
1955 copysel ""; (* ugly *)
1956 Glut.setCursor Glut.CURSOR_INHERIT;
1957 state.mstate <- Mnone;
1961 | _ ->
1964 let mouse ~button ~state ~x ~y = mouse button state x y;;
1966 let motion ~x ~y =
1967 if state.outline = None
1968 then
1969 match state.mstate with
1970 | Mnone -> ()
1972 | Mpan (x0, y0) ->
1973 let dx = x - x0
1974 and dy = y0 - y in
1975 state.mstate <- Mpan (x, y);
1976 if conf.zoom > 1.0 then state.x <- state.x + dx;
1977 let y = clamp dy in
1978 gotoy_and_clear_text y
1980 | Msel (a, _) ->
1981 state.mstate <- Msel (a, (x, y));
1982 Glut.postRedisplay ()
1984 | Mscroll ->
1985 let y = min state.h (max 0 y) in
1986 let percent = float y /. float state.h in
1987 let y = truncate (float (state.maxy - state.h) *. percent) in
1988 gotoy_and_clear_text y
1991 let pmotion ~x ~y =
1992 if state.outline = None
1993 then
1994 match state.mstate with
1995 | Mnone ->
1996 begin match getunder x y with
1997 | Unone -> Glut.setCursor Glut.CURSOR_INHERIT
1998 | Ulinkuri uri ->
1999 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
2000 Glut.setCursor Glut.CURSOR_INFO
2001 | Ulinkgoto (page, y) ->
2002 if conf.underinfo then showtext 'p' ("age: " ^ string_of_int page);
2003 Glut.setCursor Glut.CURSOR_INFO
2004 | Utext s ->
2005 if conf.underinfo then showtext 'f' ("ont: " ^ s);
2006 Glut.setCursor Glut.CURSOR_TEXT
2009 | Mpan _ | Msel _ | Mscroll ->
2013 let () =
2014 let statepath =
2015 let home =
2016 if Sys.os_type = "Win32"
2017 then
2018 try Sys.getenv "HOMEPATH" with Not_found -> ""
2019 else
2020 try Filename.concat (Sys.getenv "HOME") ".config" with Not_found -> ""
2022 Filename.concat home "llpp"
2024 let pstate =
2026 let ic = open_in_bin statepath in
2027 let hash = input_value ic in
2028 close_in ic;
2029 hash
2030 with exn ->
2031 if false
2032 then
2033 prerr_endline ("Error loading state " ^ Printexc.to_string exn)
2035 Hashtbl.create 1
2037 let savestate () =
2039 let w, h =
2040 match state.fullscreen with
2041 | None -> state.winw, state.h
2042 | Some wh -> wh
2044 Hashtbl.replace pstate state.path (state.bookmarks, w, h);
2045 let oc = open_out_bin statepath in
2046 output_value oc pstate
2047 with exn ->
2048 if false
2049 then
2050 prerr_endline ("Error saving state " ^ Printexc.to_string exn)
2053 let setstate () =
2055 let statebookmarks, statew, stateh = Hashtbl.find pstate state.path in
2056 state.w <- statew;
2057 state.h <- stateh;
2058 state.bookmarks <- statebookmarks;
2059 with Not_found -> ()
2060 | exn ->
2061 prerr_endline ("Error setting state " ^ Printexc.to_string exn)
2064 Arg.parse
2065 ["-p", Arg.String (fun s -> state.password <- s) , "password"]
2066 (fun s -> state.path <- s)
2067 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\noptions:")
2069 let name =
2070 if String.length state.path = 0
2071 then (prerr_endline "filename missing"; exit 1)
2072 else state.path
2075 setstate ();
2076 let _ = Glut.init Sys.argv in
2077 let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in
2078 let () = Glut.initWindowSize state.w state.h in
2079 let _ = Glut.createWindow ("llpp " ^ Filename.basename name) in
2081 let csock, ssock =
2082 if Sys.os_type = "Unix"
2083 then
2084 Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
2085 else
2086 let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, 1337) in
2087 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2088 Unix.setsockopt sock Unix.SO_REUSEADDR true;
2089 Unix.bind sock addr;
2090 Unix.listen sock 1;
2091 let csock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2092 Unix.connect csock addr;
2093 let ssock, _ = Unix.accept sock in
2094 Unix.close sock;
2095 let opts sock =
2096 Unix.setsockopt sock Unix.TCP_NODELAY true;
2097 Unix.setsockopt_optint sock Unix.SO_LINGER None;
2099 opts ssock;
2100 opts csock;
2101 at_exit (fun () -> Unix.shutdown ssock Unix.SHUTDOWN_ALL);
2102 ssock, csock
2105 let () = Glut.displayFunc display in
2106 let () = Glut.reshapeFunc reshape in
2107 let () = Glut.keyboardFunc keyboard in
2108 let () = Glut.specialFunc special in
2109 let () = Glut.idleFunc (Some idle) in
2110 let () = Glut.mouseFunc mouse in
2111 let () = Glut.motionFunc motion in
2112 let () = Glut.passiveMotionFunc pmotion in
2114 init ssock;
2115 state.csock <- csock;
2116 state.ssock <- ssock;
2117 state.text <- "Opening " ^ name;
2118 writecmd state.csock ("open " ^ state.path ^ "\000" ^ state.password ^ "\000");
2120 at_exit savestate;
2122 let rec handlelablglutbug () =
2124 Glut.mainLoop ();
2125 with Glut.BadEnum "key in special_of_int" ->
2126 showtext '!' " LablGlut bug: special key not recognized";
2127 handlelablglutbug ()
2129 handlelablglutbug ();