Make variable height pages work in presnetation mode
[llpp.git] / main.ml
blob0db24f924c9e4675909067c48758e18c2fbf623c
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 mpos = int * int
20 type mstate = Msel of (mpos * mpos) | Mpan of mpos | Mnone;;
22 type 'a circbuf =
23 { store : 'a array
24 ; mutable rc : int
25 ; mutable wc : int
26 ; mutable len : int
30 type textentry = (char * string * onhist option * onkey * ondone)
31 and onkey = string -> int -> te
32 and ondone = string -> unit
33 and onhist = histcmd -> string
34 and histcmd = HCnext | HCprev | HCfirst | HClast
35 and te =
36 | TEstop
37 | TEdone of string
38 | TEcont of string
39 | TEswitch of textentry
42 let cbnew n v =
43 { store = Array.create n v
44 ; rc = 0
45 ; wc = 0
46 ; len = 0
50 let cblen b = Array.length b.store;;
52 let cbput b v =
53 let len = cblen b in
54 b.store.(b.wc) <- v;
55 b.wc <- (b.wc + 1) mod len;
56 b.len <- min (b.len + 1) len;
59 let cbpeekw b = b.store.(b.wc);;
61 let cbget b dir =
62 if b.len = 0
63 then b.store.(0)
64 else
65 let rc = b.rc + dir in
66 let rc = if rc = -1 then b.len - 1 else rc in
67 let rc = if rc = b.len then 0 else rc in
68 b.rc <- rc;
69 b.store.(rc);
72 let cbrfollowlen b =
73 b.rc <- b.len;
76 let cbclear b v =
77 b.len <- 0;
78 Array.fill b.store 0 (Array.length b.store) v;
81 type layout =
82 { pageno : int
83 ; pagedimno : int
84 ; pagew : int
85 ; pageh : int
86 ; pagedispy : int
87 ; pagey : int
88 ; pagevh : int
92 type conf =
93 { mutable scrollw : int
94 ; mutable scrollh : int
95 ; mutable icase : bool
96 ; mutable preload : bool
97 ; mutable pagebias : int
98 ; mutable verbose : bool
99 ; mutable scrollincr : int
100 ; mutable maxhfit : bool
101 ; mutable crophack : bool
102 ; mutable autoscroll : bool
103 ; mutable showall : bool
104 ; mutable hlinks : bool
105 ; mutable underinfo : bool
106 ; mutable interpagespace : int
107 ; mutable zoom : float
108 ; mutable presentation : bool
112 type outline = string * int * int * float;;
113 type outlines =
114 | Oarray of outline array
115 | Olist of outline list
116 | Onarrow of outline array * outline array
119 type rect = (float * float * float * float * float * float * float * float);;
121 type state =
122 { mutable csock : Unix.file_descr
123 ; mutable ssock : Unix.file_descr
124 ; mutable w : int
125 ; mutable h : int
126 ; mutable winw : int
127 ; mutable rotate : int
128 ; mutable x : int
129 ; mutable y : int
130 ; mutable ty : float
131 ; mutable maxy : int
132 ; mutable layout : layout list
133 ; pagemap : ((int * int * int), string) Hashtbl.t
134 ; mutable pages : (int * int * int) list
135 ; mutable pagecount : int
136 ; pagecache : string circbuf
137 ; mutable rendering : bool
138 ; mutable mstate : mstate
139 ; mutable searchpattern : string
140 ; mutable rects : (int * int * rect) list
141 ; mutable rects1 : (int * int * rect) list
142 ; mutable text : string
143 ; mutable fullscreen : (int * int) option
144 ; mutable textentry : textentry option
145 ; mutable outlines : outlines
146 ; mutable outline : (bool * int * int * outline array * string) option
147 ; mutable bookmarks : outline list
148 ; mutable path : string
149 ; mutable password : string
150 ; mutable invalidated : int
151 ; mutable colorscale : float
152 ; hists : hists
154 and hists =
155 { pat : string circbuf
156 ; pag : string circbuf
157 ; nav : float circbuf
161 let conf =
162 { scrollw = 5
163 ; scrollh = 12
164 ; icase = true
165 ; preload = true
166 ; pagebias = 0
167 ; verbose = false
168 ; scrollincr = 24
169 ; maxhfit = true
170 ; crophack = false
171 ; autoscroll = false
172 ; showall = false
173 ; hlinks = false
174 ; underinfo = false
175 ; interpagespace = 2
176 ; zoom = 1.0
177 ; presentation = false
181 let state =
182 { csock = Unix.stdin
183 ; ssock = Unix.stdin
184 ; w = 900
185 ; h = 900
186 ; winw = 900
187 ; rotate = 0
188 ; y = 0
189 ; x = 0
190 ; ty = 0.0
191 ; layout = []
192 ; maxy = max_int
193 ; pagemap = Hashtbl.create 10
194 ; pagecache = cbnew 10 ""
195 ; pages = []
196 ; pagecount = 0
197 ; rendering = false
198 ; mstate = Mnone
199 ; rects = []
200 ; rects1 = []
201 ; text = ""
202 ; fullscreen = None
203 ; textentry = None
204 ; searchpattern = ""
205 ; outlines = Olist []
206 ; outline = None
207 ; bookmarks = []
208 ; path = ""
209 ; password = ""
210 ; invalidated = 0
211 ; hists =
212 { nav = cbnew 100 0.0
213 ; pat = cbnew 20 ""
214 ; pag = cbnew 10 ""
216 ; colorscale = 1.0
220 let vlog fmt =
221 if conf.verbose
222 then
223 Printf.kprintf prerr_endline fmt
224 else
225 Printf.kprintf ignore fmt
228 let writecmd fd s =
229 let len = String.length s in
230 let n = 4 + len in
231 let b = Buffer.create n in
232 Buffer.add_char b (Char.chr ((len lsr 24) land 0xff));
233 Buffer.add_char b (Char.chr ((len lsr 16) land 0xff));
234 Buffer.add_char b (Char.chr ((len lsr 8) land 0xff));
235 Buffer.add_char b (Char.chr ((len lsr 0) land 0xff));
236 Buffer.add_string b s;
237 let s' = Buffer.contents b in
238 let n' = Unix.write fd s' 0 n in
239 if n' != n then failwith "write failed";
242 let readcmd fd =
243 let s = "xxxx" in
244 let n = Unix.read fd s 0 4 in
245 if n != 4 then failwith "incomplete read(len)";
246 let len = 0
247 lor (Char.code s.[0] lsl 24)
248 lor (Char.code s.[1] lsl 16)
249 lor (Char.code s.[2] lsl 8)
250 lor (Char.code s.[3] lsl 0)
252 let s = String.create len in
253 let n = Unix.read fd s 0 len in
254 if n != len then failwith "incomplete read(data)";
258 let yratio y =
259 if y = state.maxy
260 then 1.0
261 else float y /. float state.maxy
264 let makecmd s l =
265 let b = Buffer.create 10 in
266 Buffer.add_string b s;
267 let rec combine = function
268 | [] -> b
269 | x :: xs ->
270 Buffer.add_char b ' ';
271 let s =
272 match x with
273 | `b b -> if b then "1" else "0"
274 | `s s -> s
275 | `i i -> string_of_int i
276 | `f f -> string_of_float f
277 | `I f -> string_of_int (truncate f)
279 Buffer.add_string b s;
280 combine xs;
282 combine l;
285 let wcmd s l =
286 let cmd = Buffer.contents (makecmd s l) in
287 writecmd state.csock cmd;
290 let calcips h =
291 if conf.presentation
292 then
293 let d = state.h - h in
294 max 0 ((d + 1) / 2)
295 else
296 conf.interpagespace
299 let calcheight () =
300 let rec f pn ph pi fh l =
301 match l with
302 | (n, _, h) :: rest ->
303 let ips = calcips h in
304 let fh =
305 if conf.presentation
306 then
307 fh+ips
308 else
311 let fh = fh + ((n - pn) * (ph + pi)) in
312 f n h ips fh rest
314 | [] ->
315 let inc =
316 if conf.presentation
317 then 0
318 else -pi
320 let fh = fh + ((state.pagecount - pn) * (ph + pi)) + inc in
321 max 0 fh
323 let fh = f 0 0 0 0 state.pages in
327 let getpageyh pageno =
328 let rec f pn ph pi y l =
329 match l with
330 | (n, _, h) :: rest ->
331 let ips = calcips h in
332 if n >= pageno
333 then
334 if conf.presentation && n = pageno
335 then
336 y + (pageno - pn) * (ph + pi) + pi, h
337 else
338 y + (pageno - pn) * (ph + pi), h
339 else
340 let y = y + (if conf.presentation then pi else 0) in
341 let y = y + (n - pn) * (ph + pi) in
342 f n h ips y rest
344 | [] ->
345 y + (pageno - pn) * (ph + pi), ph
347 f 0 0 0 0 state.pages
350 let getpagey pageno = fst (getpageyh pageno);;
352 let layout y sh =
353 let rec f ~pageno ~pdimno ~prev ~py ~dy ~pdims ~cacheleft ~accu =
354 let ((w, h, ips) as curr), rest, pdimno, yinc =
355 match pdims with
356 | (pageno', w, h) :: rest when pageno' = pageno ->
357 let ips = calcips h in
358 (w, h, ips), rest, pdimno + 1,
359 if conf.presentation then ips else 0
360 | _ ->
361 prev, pdims, pdimno, 0
363 let dy = dy + yinc in
364 let py = py + yinc in
365 if pageno = state.pagecount || cacheleft = 0 || dy >= sh
366 then
367 accu
368 else
369 let vy = y + dy in
370 if py + h <= vy - yinc
371 then
372 let py = py + h + ips in
373 let dy = max 0 (py - y) in
374 f ~pageno:(pageno+1)
375 ~pdimno
376 ~prev:curr
379 ~pdims:rest
380 ~cacheleft
381 ~accu
382 else
383 let pagey = vy - py in
384 let pagevh = h - pagey in
385 let pagevh = min (sh - dy) pagevh in
386 let off =
387 if yinc > 0
388 then
389 py - vy
390 else
393 let py = py + h + ips in
394 let e =
395 { pageno = pageno
396 ; pagedimno = pdimno
397 ; pagew = w
398 ; pageh = h
399 ; pagedispy = dy + off
400 ; pagey = pagey + off
401 ; pagevh = pagevh - off
404 let accu = e :: accu in
405 f ~pageno:(pageno+1)
406 ~pdimno
407 ~prev:curr
409 ~dy:(dy+pagevh+ips)
410 ~pdims:rest
411 ~cacheleft:(cacheleft-1)
412 ~accu
414 if state.invalidated = 0
415 then (
416 let accu =
418 ~pageno:0
419 ~pdimno:~-1
420 ~prev:(0,0,0)
421 ~py:0
422 ~dy:0
423 ~pdims:state.pages
424 ~cacheleft:(cblen state.pagecache)
425 ~accu:[]
427 List.rev accu
429 else
433 let clamp incr =
434 let y = state.y + incr in
435 let y = max 0 y in
436 let y = min y (state.maxy - (if conf.maxhfit then state.h else 0)) in
440 let getopaque pageno =
441 try Some (Hashtbl.find state.pagemap (pageno + 1, state.w, state.rotate))
442 with Not_found -> None
445 let cache pageno opaque =
446 Hashtbl.replace state.pagemap (pageno + 1, state.w, state.rotate) opaque
449 let validopaque opaque = String.length opaque > 0;;
451 let render l =
452 match getopaque l.pageno with
453 | None when not state.rendering ->
454 state.rendering <- true;
455 cache l.pageno "";
456 wcmd "render" [`i (l.pageno + 1)
457 ;`i l.pagedimno
458 ;`i l.pagew
459 ;`i l.pageh];
461 | _ -> ()
464 let loadlayout layout =
465 let rec f all = function
466 | l :: ls ->
467 begin match getopaque l.pageno with
468 | None -> render l; f false ls
469 | Some opaque -> f (all && validopaque opaque) ls
471 | [] -> all
473 f (layout <> []) layout;
476 let preload () =
477 if conf.preload
478 then
479 let evictedvisible =
480 let evictedopaque = cbpeekw state.pagecache in
481 List.exists (fun l ->
482 match getopaque l.pageno with
483 | Some opaque when validopaque opaque ->
484 evictedopaque = opaque
485 | otherwise -> false
486 ) state.layout
488 if not evictedvisible
489 then
490 let rely = yratio state.y in
491 let presentation = conf.presentation in
492 let interpagespace = conf.interpagespace in
493 let maxy = state.maxy in
494 conf.presentation <- false;
495 conf.interpagespace <- 0;
496 state.maxy <- calcheight ();
497 let y = truncate (float state.maxy *. rely) in
498 let y = if y < state.h then 0 else y - state.h in
499 let pages = layout y (state.h*3) in
500 List.iter render pages;
501 conf.presentation <- presentation;
502 conf.interpagespace <- interpagespace;
503 state.maxy <- maxy;
506 let gotoy y =
507 let y = max 0 y in
508 let y = min state.maxy y in
509 let pages = layout y state.h in
510 let ready = loadlayout pages in
511 state.ty <- yratio y;
512 if conf.showall
513 then (
514 if ready
515 then (
516 state.layout <- pages;
517 state.y <- y;
518 Glut.postRedisplay ();
521 else (
522 state.layout <- pages;
523 state.y <- y;
524 Glut.postRedisplay ();
526 preload ();
529 let addnav () =
530 cbput state.hists.nav (yratio state.y);
531 cbrfollowlen state.hists.nav;
534 let getnav () =
535 let y = cbget state.hists.nav ~-1 in
536 truncate (y *. float state.maxy)
539 let gotopage n top =
540 let y, h = getpageyh n in
541 addnav ();
542 gotoy (y + (truncate (top *. float h)));
545 let gotopage1 n top =
546 let y = getpagey n in
547 addnav ();
548 gotoy (y + top);
551 let invalidate () =
552 state.layout <- [];
553 state.pages <- [];
554 state.rects <- [];
555 state.rects1 <- [];
556 state.invalidated <- state.invalidated + 1;
559 let scalecolor c =
560 let c = c *. state.colorscale in
561 (c, c, c);
564 let represent () =
565 let y =
566 match state.layout with
567 | [] ->
568 let rely = yratio state.y in
569 state.maxy <- calcheight ();
570 truncate (float state.maxy *. rely)
572 | l :: _ ->
573 state.maxy <- calcheight ();
574 getpagey l.pageno
576 gotoy y
579 let reshape ~w ~h =
580 let margin = truncate (0.5 *. (float w -. float w *. conf.zoom)) in
581 state.winw <- w;
582 let w = w - margin * 2 - conf.scrollw in
583 state.w <- w;
584 state.h <- h;
585 GlMat.mode `modelview;
586 GlMat.load_identity ();
587 GlMat.mode `projection;
588 GlMat.load_identity ();
589 GlMat.rotate ~x:1.0 ~angle:180.0 ();
590 GlMat.translate ~x:~-.1.0 ~y:~-.1.0 ();
591 GlMat.scale3 (2.0 /. float w, 2.0 /. float state.h, 1.0);
592 GlClear.color (scalecolor 1.0);
593 GlClear.clear [`color];
595 invalidate ();
596 wcmd "geometry" [`i state.w; `i h];
599 let showtext c s =
600 GlDraw.viewport 0 0 state.winw state.h;
601 GlDraw.color (0.0, 0.0, 0.0);
602 let sw = float (conf.scrollw - 1) *. float state.w /. float state.winw in
603 GlDraw.rect
604 (0.0, float (state.h - 18))
605 (float state.w -. sw, float state.h)
607 let font = Glut.BITMAP_8_BY_13 in
608 GlDraw.color (1.0, 1.0, 1.0);
609 GlPix.raster_pos ~x:0.0 ~y:(float (state.h - 5)) ();
610 Glut.bitmapCharacter ~font ~c:(Char.code c);
611 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s;
614 let enttext () =
615 let len = String.length state.text in
616 match state.textentry with
617 | None ->
618 if len > 0 then showtext ' ' state.text
620 | Some (c, text, _, _, _) ->
621 let s =
622 if len > 0
623 then
624 text ^ " [" ^ state.text ^ "]"
625 else
626 text
628 showtext c s;
631 let showtext c s =
632 if true
633 then (
634 state.text <- Printf.sprintf "%c%s" c s;
635 Glut.postRedisplay ();
637 else (
638 showtext c s;
639 Glut.swapBuffers ();
643 let act cmd =
644 match cmd.[0] with
645 | 'c' ->
646 state.pages <- [];
648 | 'D' ->
649 state.rects <- state.rects1;
650 Glut.postRedisplay ()
652 | 'C' ->
653 let n = Scanf.sscanf cmd "C %d" (fun n -> n) in
654 state.pagecount <- n;
655 state.invalidated <- state.invalidated - 1;
656 if state.invalidated = 0
657 then represent ()
659 | 't' ->
660 let s = Scanf.sscanf cmd "t %n"
661 (fun n -> String.sub cmd n (String.length cmd - n))
663 Glut.setWindowTitle s
665 | 'T' ->
666 let s = Scanf.sscanf cmd "T %n"
667 (fun n -> String.sub cmd n (String.length cmd - n))
669 if state.textentry = None
670 then (
671 state.text <- s;
672 showtext ' ' s;
674 else (
675 state.text <- s;
676 Glut.postRedisplay ();
679 | 'V' ->
680 if conf.verbose
681 then
682 let s = Scanf.sscanf cmd "V %n"
683 (fun n -> String.sub cmd n (String.length cmd - n))
685 state.text <- s;
686 showtext ' ' s;
688 | 'F' ->
689 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
690 Scanf.sscanf cmd "F %d %d %f %f %f %f %f %f %f %f"
691 (fun p c x0 y0 x1 y1 x2 y2 x3 y3 ->
692 (p, c, x0, y0, x1, y1, x2, y2, x3, y3))
694 let y = (getpagey pageno) + truncate y0 in
695 addnav ();
696 gotoy y;
697 state.rects1 <- [pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)]
699 | 'R' ->
700 let pageno, c, x0, y0, x1, y1, x2, y2, x3, y3 =
701 Scanf.sscanf cmd "R %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 state.rects1 <-
706 (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) :: state.rects1
708 | 'r' ->
709 let n, w, h, r, p =
710 Scanf.sscanf cmd "r %d %d %d %d %s"
711 (fun n w h r p -> (n, w, h, r, p))
713 Hashtbl.replace state.pagemap (n, w, r) p;
714 let opaque = cbpeekw state.pagecache in
715 if validopaque opaque
716 then (
717 let k =
718 Hashtbl.fold
719 (fun k v a -> if v = opaque then k else a)
720 state.pagemap (-1, -1, -1)
722 wcmd "free" [`s opaque];
723 Hashtbl.remove state.pagemap k
725 cbput state.pagecache p;
726 state.rendering <- false;
727 if conf.showall
728 then gotoy (truncate (ceil (state.ty *. float state.maxy)))
729 else (
730 let visible = List.exists (fun l -> l.pageno + 1 = n) state.layout in
731 if visible
732 then gotoy state.y
733 else (ignore (loadlayout state.layout); preload ())
736 | 'l' ->
737 let (n, w, h) as pagelayout =
738 Scanf.sscanf cmd "l %d %d %d" (fun n w h -> n, w, h)
740 state.pages <- pagelayout :: state.pages
742 | 'o' ->
743 let (l, n, t, h, pos) =
744 Scanf.sscanf cmd "o %d %d %d %d %n" (fun l n t h pos -> l, n, t, h, pos)
746 let s = String.sub cmd pos (String.length cmd - pos) in
747 let s =
748 let l = String.length s in
749 let b = Buffer.create (String.length s) in
750 let rec loop pc2 i =
751 if i = l
752 then ()
753 else
754 let pc2 =
755 match s.[i] with
756 | '\xa0' when pc2 -> Buffer.add_char b ' '; false
757 | '\xc2' -> true
758 | c ->
759 let c = if Char.code c land 0x80 = 0 then c else '?' in
760 Buffer.add_char b c;
761 false
763 loop pc2 (i+1)
765 loop false 0;
766 Buffer.contents b
768 let outline = (s, l, n, float t /. float h) in
769 let outlines =
770 match state.outlines with
771 | Olist outlines -> Olist (outline :: outlines)
772 | Oarray _ -> Olist [outline]
773 | Onarrow _ -> Olist [outline]
775 state.outlines <- outlines
777 | _ ->
778 log "unknown cmd `%S'" cmd
781 let now = Unix.gettimeofday;;
783 let idle () =
784 let rec loop delay =
785 let r, _, _ = Unix.select [state.csock] [] [] delay in
786 begin match r with
787 | [] ->
788 if conf.autoscroll
789 then begin
790 let y = state.y + conf.scrollincr in
791 let y = if y >= state.maxy then 0 else y in
792 gotoy y;
793 state.text <- "";
794 end;
796 | _ ->
797 let cmd = readcmd state.csock in
798 act cmd;
799 loop 0.0
800 end;
801 in loop 0.001
804 let onhist cb = function
805 | HCprev -> cbget cb ~-1
806 | HCnext -> cbget cb 1
807 | HCfirst -> cbget cb ~-(cb.rc)
808 | HClast -> cbget cb (cb.len - 1 - cb.rc)
811 let search pattern forward =
812 if String.length pattern > 0
813 then
814 let pn, py =
815 match state.layout with
816 | [] -> 0, 0
817 | l :: _ ->
818 l.pageno, (l.pagey + if forward then 0 else 0*l.pagevh)
820 let cmd =
821 let b = makecmd "search"
822 [`b conf.icase; `i pn; `i py; `i (if forward then 1 else 0)]
824 Buffer.add_char b ',';
825 Buffer.add_string b pattern;
826 Buffer.add_char b '\000';
827 Buffer.contents b;
829 writecmd state.csock cmd;
832 let intentry text key =
833 let c = Char.unsafe_chr key in
834 match c with
835 | '0' .. '9' ->
836 let s = "x" in s.[0] <- c;
837 let text = text ^ s in
838 TEcont text
840 | _ ->
841 state.text <- Printf.sprintf "invalid char (%d, `%c')" key c;
842 TEcont text
845 let addchar s c =
846 let b = Buffer.create (String.length s + 1) in
847 Buffer.add_string b s;
848 Buffer.add_char b c;
849 Buffer.contents b;
852 let textentry text key =
853 let c = Char.unsafe_chr key in
854 match c with
855 | _ when key >= 32 && key < 127 ->
856 let text = addchar text c in
857 TEcont text
859 | _ ->
860 log "unhandled key %d char `%c'" key (Char.unsafe_chr key);
861 TEcont text
864 let rotate angle =
865 state.rotate <- angle;
866 invalidate ();
867 wcmd "rotate" [`i angle];
870 let optentry text key =
871 let btos b = if b then "on" else "off" in
872 let c = Char.unsafe_chr key in
873 match c with
874 | 's' ->
875 let ondone s =
876 try conf.scrollincr <- int_of_string s with exc ->
877 state.text <- Printf.sprintf "bad integer `%s': %s"
878 s (Printexc.to_string exc)
880 TEswitch ('#', "", None, intentry, ondone)
882 | 'R' ->
883 let ondone s =
884 match try
885 Some (int_of_string s)
886 with exc ->
887 state.text <- Printf.sprintf "bad integer `%s': %s"
888 s (Printexc.to_string exc);
889 None
890 with
891 | Some angle -> rotate angle
892 | None -> ()
894 TEswitch ('^', "", None, intentry, ondone)
896 | 'i' ->
897 conf.icase <- not conf.icase;
898 TEdone ("case insensitive search " ^ (btos conf.icase))
900 | 'p' ->
901 conf.preload <- not conf.preload;
902 gotoy state.y;
903 TEdone ("preload " ^ (btos conf.preload))
905 | 'v' ->
906 conf.verbose <- not conf.verbose;
907 TEdone ("verbose " ^ (btos conf.verbose))
909 | 'h' ->
910 conf.maxhfit <- not conf.maxhfit;
911 state.maxy <- state.maxy + (if conf.maxhfit then -state.h else state.h);
912 TEdone ("maxhfit " ^ (btos conf.maxhfit))
914 | 'c' ->
915 conf.crophack <- not conf.crophack;
916 TEdone ("crophack " ^ btos conf.crophack)
918 | 'a' ->
919 conf.showall <- not conf.showall;
920 TEdone ("showall " ^ btos conf.showall)
922 | 'f' ->
923 conf.underinfo <- not conf.underinfo;
924 TEdone ("underinfo " ^ btos conf.underinfo)
926 | 'S' ->
927 let ondone s =
929 conf.interpagespace <- int_of_string s;
930 let rely = yratio state.y in
931 state.maxy <- calcheight ();
932 gotoy (truncate (float state.maxy *. rely));
933 with exc ->
934 state.text <- Printf.sprintf "bad integer `%s': %s"
935 s (Printexc.to_string exc)
937 TEswitch ('%', "", None, intentry, ondone)
939 | _ ->
940 state.text <- Printf.sprintf "bad option %d `%c'" key c;
941 TEstop
944 let maxoutlinerows () = (state.h - 31) / 16;;
946 let enterselector allowdel outlines errmsg =
947 if Array.length outlines = 0
948 then (
949 showtext ' ' errmsg;
951 else (
952 Glut.setCursor Glut.CURSOR_INHERIT;
953 let pageno =
954 match state.layout with
955 | [] -> -1
956 | {pageno=pageno} :: rest -> pageno
958 let active =
959 let rec loop n =
960 if n = Array.length outlines
961 then 0
962 else
963 let (_, _, outlinepageno, _) = outlines.(n) in
964 if outlinepageno >= pageno then n else loop (n+1)
966 loop 0
968 state.outline <-
969 Some (allowdel, active,
970 max 0 ((active - maxoutlinerows () / 2)), outlines, "");
971 Glut.postRedisplay ();
975 let enteroutlinemode () =
976 let outlines =
977 match state.outlines with
978 | Oarray a -> a
979 | Olist l ->
980 let a = Array.of_list (List.rev l) in
981 state.outlines <- Oarray a;
983 | Onarrow (a, b) -> a
985 enterselector false outlines "Document has no outline";
988 let enterbookmarkmode () =
989 let bookmarks = Array.of_list state.bookmarks in
990 enterselector true bookmarks "Document has no bookmarks (yet)";
994 let quickbookmark ?title () =
995 match state.layout with
996 | [] -> ()
997 | l :: _ ->
998 let title =
999 match title with
1000 | None ->
1001 let sec = Unix.gettimeofday () in
1002 let tm = Unix.localtime sec in
1003 Printf.sprintf "Quick %d visited (%d/%d/%d %d:%d)"
1004 l.pageno
1005 tm.Unix.tm_mday
1006 tm.Unix.tm_mon
1007 (tm.Unix.tm_year + 1900)
1008 tm.Unix.tm_hour
1009 tm.Unix.tm_min
1010 | Some title -> title
1012 state.bookmarks <-
1013 (title, 0, l.pageno, float l.pagey /. float l.pageh) :: state.bookmarks
1016 let doreshape w h =
1017 state.fullscreen <- None;
1018 Glut.reshapeWindow w h;
1021 let opendoc path password =
1022 invalidate ();
1023 state.path <- path;
1024 state.password <- password;
1025 Hashtbl.clear state.pagemap;
1027 writecmd state.csock ("open " ^ path ^ "\000" ^ password ^ "\000");
1028 Glut.setWindowTitle ("llpp " ^ Filename.basename path);
1029 wcmd "geometry" [`i state.w; `i state.h];
1032 let viewkeyboard ~key ~x ~y =
1033 let enttext te =
1034 state.textentry <- te;
1035 state.text <- "";
1036 enttext ();
1037 Glut.postRedisplay ()
1039 match state.textentry with
1040 | None ->
1041 let c = Char.chr key in
1042 begin match c with
1043 | '\027' | 'q' ->
1044 exit 0
1046 | '\008' ->
1047 let y = getnav () in
1048 gotoy y
1050 | 'o' ->
1051 enteroutlinemode ()
1053 | 'u' ->
1054 state.rects <- [];
1055 state.text <- "";
1056 Glut.postRedisplay ()
1058 | '/' | '?' ->
1059 let ondone isforw s =
1060 cbput state.hists.pat s;
1061 cbrfollowlen state.hists.pat;
1062 state.searchpattern <- s;
1063 search s isforw
1065 enttext (Some (c, "", Some (onhist state.hists.pat),
1066 textentry, ondone (c ='/')))
1068 | '+' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1069 conf.zoom <- min 10.0 (conf.zoom +. 0.1);
1070 reshape state.winw state.h
1072 | '+' ->
1073 let ondone s =
1074 let n =
1075 try int_of_string s with exc ->
1076 state.text <- Printf.sprintf "bad integer `%s': %s"
1077 s (Printexc.to_string exc);
1078 max_int
1080 if n != max_int
1081 then (
1082 conf.pagebias <- n;
1083 state.text <- "page bias is now " ^ string_of_int n;
1086 enttext (Some ('+', "", None, intentry, ondone))
1088 | '-' when Glut.getModifiers () land Glut.active_ctrl != 0 ->
1089 conf.zoom <- max 0.1 (conf.zoom -. 0.1);
1090 if conf.zoom <= 1.0 then state.x <- 0;
1091 reshape state.winw state.h;
1093 | '-' ->
1094 let ondone msg =
1095 state.text <- msg;
1097 enttext (Some ('-', "", None, optentry, ondone))
1099 | '0' when (Glut.getModifiers () land Glut.active_ctrl != 0) ->
1100 state.x <- 0;
1101 conf.zoom <- 1.0;
1102 reshape state.winw state.h
1104 | '0' .. '9' ->
1105 let ondone s =
1106 let n =
1107 try int_of_string s with exc ->
1108 state.text <- Printf.sprintf "bad integer `%s': %s"
1109 s (Printexc.to_string exc);
1112 if n >= 0
1113 then (
1114 addnav ();
1115 cbput state.hists.pag (string_of_int n);
1116 cbrfollowlen state.hists.pag;
1117 gotoy (getpagey (n + conf.pagebias - 1))
1120 let pageentry text key =
1121 match Char.unsafe_chr key with
1122 | 'g' -> TEdone text
1123 | _ -> intentry text key
1125 let text = "x" in text.[0] <- c;
1126 enttext (Some (':', text, Some (onhist state.hists.pag),
1127 pageentry, ondone))
1129 | 'b' ->
1130 conf.scrollw <- if conf.scrollw > 0 then 0 else 5;
1131 reshape state.winw state.h;
1133 | 'l' ->
1134 conf.hlinks <- not conf.hlinks;
1135 state.text <- "highlightlinks " ^ if conf.hlinks then "on" else "off";
1136 Glut.postRedisplay ()
1138 | 'a' ->
1139 conf.autoscroll <- not conf.autoscroll
1141 | 'P' ->
1142 conf.presentation <- not conf.presentation;
1143 showtext ' ' ("Presnetation mode " ^
1144 if conf.presentation then "on" else "off");
1145 represent ()
1147 | 'f' ->
1148 begin match state.fullscreen with
1149 | None ->
1150 state.fullscreen <- Some (state.w, state.h);
1151 Glut.fullScreen ()
1152 | Some (w, h) ->
1153 state.fullscreen <- None;
1154 doreshape w h
1157 | 'g' ->
1158 gotoy 0
1160 | 'n' ->
1161 search state.searchpattern true
1163 | 'p' | 'N' ->
1164 search state.searchpattern false
1166 | 't' ->
1167 begin match state.layout with
1168 | [] -> ()
1169 | l :: _ ->
1170 gotoy (getpagey l.pageno)
1173 | ' ' ->
1174 begin match List.rev state.layout with
1175 | [] -> ()
1176 | l :: _ ->
1177 let pageno = min (l.pageno+1) (state.pagecount-1) in
1178 gotoy (getpagey pageno)
1181 | '\127' ->
1182 begin match state.layout with
1183 | [] -> ()
1184 | l :: _ ->
1185 let pageno = max 0 (l.pageno-1) in
1186 gotoy (getpagey pageno)
1189 | '=' ->
1190 let f (fn, ln) l =
1191 if fn = -1 then l.pageno, l.pageno else fn, l.pageno
1193 let fn, ln = List.fold_left f (-1, -1) state.layout in
1194 let s =
1195 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1196 let percent =
1197 if maxy <= 0
1198 then 100.
1199 else (100. *. (float state.y /. float maxy)) in
1200 if fn = ln
1201 then
1202 Printf.sprintf "Page %d of %d %.2f%%"
1203 (fn+1) state.pagecount percent
1204 else
1205 Printf.sprintf
1206 "Pages %d-%d of %d %.2f%%"
1207 (fn+1) (ln+1) state.pagecount percent
1209 showtext ' ' s;
1211 | 'w' ->
1212 begin match state.layout with
1213 | [] -> ()
1214 | l :: _ ->
1215 doreshape l.pagew l.pageh;
1216 Glut.postRedisplay ();
1219 | '\'' ->
1220 enterbookmarkmode ()
1222 | 'm' ->
1223 let ondone s =
1224 match state.layout with
1225 | l :: _ ->
1226 state.bookmarks <-
1227 (s, 0, l.pageno, float l.pagey /. float l.pageh)
1228 :: state.bookmarks
1229 | _ -> ()
1231 enttext (Some ('~', "", None, textentry, ondone))
1233 | '~' ->
1234 quickbookmark ();
1235 showtext ' ' "Quick bookmark added";
1237 | 'z' ->
1238 begin match state.layout with
1239 | l :: _ ->
1240 let a = getpagewh l.pagedimno in
1241 let w, h =
1242 if conf.crophack
1243 then
1244 (truncate (1.8 *. (a.(1) -. a.(0))),
1245 truncate (1.2 *. (a.(3) -. a.(0))))
1246 else
1247 (truncate (a.(1) -. a.(0)),
1248 truncate (a.(3) -. a.(0)))
1250 doreshape w h;
1251 Glut.postRedisplay ();
1253 | [] -> ()
1256 | '<' | '>' ->
1257 rotate (state.rotate + (if c = '>' then 30 else -30));
1259 | '[' | ']' ->
1260 state.colorscale <-
1261 max 0.0
1262 (min (state.colorscale +. (if c = ']' then 0.1 else -0.1)) 1.0);
1263 Glut.postRedisplay ()
1265 | 'k' -> gotoy (clamp (-conf.scrollincr))
1266 | 'j' -> gotoy (clamp conf.scrollincr)
1268 | 'r' -> opendoc state.path state.password
1270 | _ ->
1271 vlog "huh? %d %c" key (Char.chr key);
1274 | Some (c, text, onhist, onkey, ondone) when key = 8 ->
1275 let len = String.length text in
1276 if len = 0
1277 then (
1278 state.textentry <- None;
1279 Glut.postRedisplay ();
1281 else (
1282 let s = String.sub text 0 (len - 1) in
1283 enttext (Some (c, s, onhist, onkey, ondone))
1286 | Some (c, text, onhist, onkey, ondone) ->
1287 begin match Char.unsafe_chr key with
1288 | '\r' | '\n' ->
1289 ondone text;
1290 state.textentry <- None;
1291 Glut.postRedisplay ()
1293 | '\027' ->
1294 state.textentry <- None;
1295 Glut.postRedisplay ()
1297 | _ ->
1298 begin match onkey text key with
1299 | TEdone text ->
1300 state.textentry <- None;
1301 ondone text;
1302 Glut.postRedisplay ()
1304 | TEcont text ->
1305 enttext (Some (c, text, onhist, onkey, ondone));
1307 | TEstop ->
1308 state.textentry <- None;
1309 Glut.postRedisplay ()
1311 | TEswitch te ->
1312 state.textentry <- Some te;
1313 Glut.postRedisplay ()
1314 end;
1315 end;
1318 let narrow outlines pattern =
1319 let reopt = try Some (Str.regexp_case_fold pattern) with _ -> None in
1320 match reopt with
1321 | None -> None
1322 | Some re ->
1323 let rec fold accu n =
1324 if n = -1
1325 then accu
1326 else
1327 let (s, _, _, _) as o = outlines.(n) in
1328 let accu =
1329 if (try ignore (Str.search_forward re s 0); true
1330 with Not_found -> false)
1331 then (o :: accu)
1332 else accu
1334 fold accu (n-1)
1336 let matched = fold [] (Array.length outlines - 1) in
1337 if matched = [] then None else Some (Array.of_list matched)
1340 let outlinekeyboard ~key ~x ~y (allowdel, active, first, outlines, qsearch) =
1341 let search active pattern incr =
1342 let dosearch re =
1343 let rec loop n =
1344 if n = Array.length outlines || n = -1
1345 then None
1346 else
1347 let (s, _, _, _) = outlines.(n) in
1349 (try ignore (Str.search_forward re s 0); true
1350 with Not_found -> false)
1351 then Some n
1352 else loop (n + incr)
1354 loop active
1357 let re = Str.regexp_case_fold pattern in
1358 dosearch re
1359 with Failure s ->
1360 state.text <- s;
1361 None
1363 let firstof active = max 0 (active - maxoutlinerows () / 2) in
1364 match key with
1365 | 27 ->
1366 if String.length qsearch = 0
1367 then (
1368 state.text <- "";
1369 state.outline <- None;
1370 Glut.postRedisplay ();
1372 else (
1373 state.text <- "";
1374 state.outline <- Some (allowdel, active, first, outlines, "");
1375 Glut.postRedisplay ();
1378 | 18 | 19 ->
1379 let incr = if key = 18 then -1 else 1 in
1380 let active, first =
1381 match search (active + incr) qsearch incr with
1382 | None ->
1383 state.text <- qsearch ^ " [not found]";
1384 active, first
1385 | Some active ->
1386 state.text <- qsearch;
1387 active, firstof active
1389 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1390 Glut.postRedisplay ();
1392 | 8 ->
1393 let len = String.length qsearch in
1394 if len = 0
1395 then ()
1396 else (
1397 if len = 1
1398 then (
1399 state.text <- "";
1400 state.outline <- Some (allowdel, active, first, outlines, "");
1402 else
1403 let qsearch = String.sub qsearch 0 (len - 1) in
1404 let active, first =
1405 match search active qsearch ~-1 with
1406 | None ->
1407 state.text <- qsearch ^ " [not found]";
1408 active, first
1409 | Some active ->
1410 state.text <- qsearch;
1411 active, firstof active
1413 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1415 Glut.postRedisplay ()
1417 | 13 ->
1418 if active < Array.length outlines
1419 then (
1420 let (_, _, n, t) = outlines.(active) in
1421 gotopage n t;
1423 state.text <- "";
1424 if allowdel then state.bookmarks <- Array.to_list outlines;
1425 state.outline <- None;
1426 Glut.postRedisplay ();
1428 | _ when key >= 32 && key < 127 ->
1429 let pattern = addchar qsearch (Char.chr key) in
1430 let active, first =
1431 match search active pattern 1 with
1432 | None ->
1433 state.text <- pattern ^ " [not found]";
1434 active, first
1435 | Some active ->
1436 state.text <- pattern;
1437 active, firstof active
1439 state.outline <- Some (allowdel, active, first, outlines, pattern);
1440 Glut.postRedisplay ()
1442 | 14 when not allowdel ->
1443 let optoutlines = narrow outlines qsearch in
1444 begin match optoutlines with
1445 | None -> state.text <- "can't narrow"
1446 | Some outlines ->
1447 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1448 match state.outlines with
1449 | Olist l -> ()
1450 | Oarray a -> state.outlines <- Onarrow (outlines, a)
1451 | Onarrow (a, b) -> state.outlines <- Onarrow (outlines, b)
1452 end;
1453 Glut.postRedisplay ()
1455 | 21 when not allowdel ->
1456 let outline =
1457 match state.outlines with
1458 | Oarray a -> a
1459 | Olist l ->
1460 let a = Array.of_list (List.rev l) in
1461 state.outlines <- Oarray a;
1463 | Onarrow (a, b) ->
1464 state.outlines <- Oarray b;
1467 state.outline <- Some (allowdel, 0, 0, outline, qsearch);
1468 Glut.postRedisplay ()
1470 | 12 ->
1471 state.outline <-
1472 Some (allowdel, active, firstof active, outlines, qsearch);
1473 Glut.postRedisplay ()
1475 | 127 when allowdel ->
1476 let len = Array.length outlines - 1 in
1477 if len = 0
1478 then (
1479 state.outline <- None;
1480 state.bookmarks <- [];
1482 else (
1483 let bookmarks = Array.init len
1484 (fun i ->
1485 let i = if i >= active then i + 1 else i in
1486 outlines.(i)
1489 state.outline <-
1490 Some (allowdel,
1491 min active (len-1),
1492 min first (len-1),
1493 bookmarks, qsearch)
1496 Glut.postRedisplay ()
1498 | _ -> log "unknown key %d" key
1501 let keyboard ~key ~x ~y =
1502 if key = 7
1503 then
1504 wcmd "interrupt" []
1505 else
1506 match state.outline with
1507 | None -> viewkeyboard ~key ~x ~y
1508 | Some outline -> outlinekeyboard ~key ~x ~y outline
1511 let special ~key ~x ~y =
1512 match state.outline with
1513 | None ->
1514 begin match state.textentry with
1515 | None ->
1516 let y =
1517 match key with
1518 | Glut.KEY_F3 -> search state.searchpattern true; state.y
1519 | Glut.KEY_UP -> clamp (-conf.scrollincr)
1520 | Glut.KEY_DOWN -> clamp conf.scrollincr
1521 | Glut.KEY_PAGE_UP ->
1522 if Glut.getModifiers () land Glut.active_ctrl != 0
1523 then
1524 match state.layout with
1525 | [] -> state.y
1526 | l :: _ -> state.y - l.pagey
1527 else
1528 clamp (-state.h)
1529 | Glut.KEY_PAGE_DOWN ->
1530 if Glut.getModifiers () land Glut.active_ctrl != 0
1531 then
1532 match List.rev state.layout with
1533 | [] -> state.y
1534 | l :: _ -> getpagey l.pageno
1535 else
1536 clamp state.h
1537 | Glut.KEY_HOME -> addnav (); 0
1538 | Glut.KEY_END ->
1539 addnav ();
1540 state.maxy - (if conf.maxhfit then state.h else 0)
1542 | Glut.KEY_RIGHT when conf.zoom > 1.0 ->
1543 state.x <- state.x - 10;
1544 state.y
1545 | Glut.KEY_LEFT when conf.zoom > 1.0 ->
1546 state.x <- state.x + 10;
1547 state.y
1549 | _ -> state.y
1551 if not conf.verbose then state.text <- "";
1552 gotoy y
1554 | Some (c, s, Some onhist, onkey, ondone) ->
1555 let s =
1556 match key with
1557 | Glut.KEY_UP -> onhist HCprev
1558 | Glut.KEY_DOWN -> onhist HCnext
1559 | Glut.KEY_HOME -> onhist HCfirst
1560 | Glut.KEY_END -> onhist HClast
1561 | _ -> state.text
1563 state.textentry <- Some (c, s, Some onhist, onkey, ondone);
1564 Glut.postRedisplay ()
1566 | _ -> ()
1569 | Some (allowdel, active, first, outlines, qsearch) ->
1570 let maxrows = maxoutlinerows () in
1571 let navigate incr =
1572 let active = active + incr in
1573 let active = max 0 (min active (Array.length outlines - 1)) in
1574 let first =
1575 if active > first
1576 then
1577 let rows = active - first in
1578 if rows > maxrows then active - maxrows else first
1579 else active
1581 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1582 Glut.postRedisplay ()
1584 match key with
1585 | Glut.KEY_UP -> navigate ~-1
1586 | Glut.KEY_DOWN -> navigate 1
1587 | Glut.KEY_PAGE_UP -> navigate ~-maxrows
1588 | Glut.KEY_PAGE_DOWN -> navigate maxrows
1590 | Glut.KEY_HOME ->
1591 state.outline <- Some (allowdel, 0, 0, outlines, qsearch);
1592 Glut.postRedisplay ()
1594 | Glut.KEY_END ->
1595 let active = Array.length outlines - 1 in
1596 let first = max 0 (active - maxrows) in
1597 state.outline <- Some (allowdel, active, first, outlines, qsearch);
1598 Glut.postRedisplay ()
1600 | _ -> ()
1603 let drawplaceholder l =
1604 GlDraw.color (scalecolor 1.0);
1605 GlDraw.rect
1606 (0.0, float l.pagedispy)
1607 (float l.pagew, float (l.pagedispy + l.pagevh))
1609 let x = 0.0
1610 and y = float (l.pagedispy + 13) in
1611 let font = Glut.BITMAP_8_BY_13 in
1612 GlDraw.color (0.0, 0.0, 0.0);
1613 GlPix.raster_pos ~x ~y ();
1614 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c))
1615 ("Loading " ^ string_of_int l.pageno);
1618 let now () = Unix.gettimeofday ();;
1620 let drawpage i l =
1621 begin match getopaque l.pageno with
1622 | Some opaque when validopaque opaque ->
1623 if state.textentry = None
1624 then GlDraw.color (scalecolor 1.0)
1625 else GlDraw.color (scalecolor 0.4);
1626 let a = now () in
1627 draw (l.pagedispy, l.pagew, l.pagevh, l.pagey, conf.hlinks)
1628 opaque;
1629 let b = now () in
1630 let d = b-.a in
1631 vlog "draw %d %f sec" l.pageno d;
1633 | _ ->
1634 drawplaceholder l;
1635 end;
1636 l.pagedispy + l.pagevh;
1639 let scrollindicator () =
1640 let maxy = state.maxy - (if conf.maxhfit then state.h else 0) in
1641 GlDraw.color (0.64 , 0.64, 0.64);
1642 GlDraw.rect
1643 (0., 0.)
1644 (float conf.scrollw, float state.h)
1646 GlDraw.color (0.0, 0.0, 0.0);
1647 let sh = (float (maxy + state.h) /. float state.h) in
1648 let sh = float state.h /. sh in
1649 let sh = max sh (float conf.scrollh) in
1651 let percent =
1652 if state.y = state.maxy
1653 then 1.0
1654 else float state.y /. float maxy
1656 let position = (float state.h -. sh) *. percent in
1658 let position =
1659 if position +. sh > float state.h
1660 then
1661 float state.h -. sh
1662 else
1663 position
1665 GlDraw.rect
1666 (0.0, position)
1667 (float conf.scrollw, position +. sh)
1671 let showsel margin =
1672 match state.mstate with
1673 | Mnone | Mpan _ ->
1676 | Msel ((x0, y0), (x1, y1)) ->
1677 let rec loop = function
1678 | l :: ls ->
1679 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1680 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1681 then
1682 match getopaque l.pageno with
1683 | Some opaque when validopaque opaque ->
1684 let oy = -l.pagey + l.pagedispy in
1685 seltext opaque
1686 (x0 - margin - state.x, y0,
1687 x1 - margin - state.x, y1) oy;
1689 | _ -> ()
1690 else loop ls
1691 | [] -> ()
1693 loop state.layout
1696 let showrects () =
1697 let panx = float state.x in
1698 Gl.enable `blend;
1699 GlDraw.color (0.0, 0.0, 1.0) ~alpha:0.5;
1700 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1701 List.iter
1702 (fun (pageno, c, (x0, y0, x1, y1, x2, y2, x3, y3)) ->
1703 List.iter (fun l ->
1704 if l.pageno = pageno
1705 then (
1706 let d = float (l.pagedispy - l.pagey) in
1707 GlDraw.color (0.0, 0.0, 1.0 /. float c) ~alpha:0.5;
1708 GlDraw.begins `quads;
1710 GlDraw.vertex2 (x0+.panx, y0+.d);
1711 GlDraw.vertex2 (x1+.panx, y1+.d);
1712 GlDraw.vertex2 (x2+.panx, y2+.d);
1713 GlDraw.vertex2 (x3+.panx, y3+.d);
1715 GlDraw.ends ();
1717 ) state.layout
1718 ) state.rects
1720 Gl.disable `blend;
1723 let showoutline = function
1724 | None -> ()
1725 | Some (allowdel, active, first, outlines, qsearch) ->
1726 Gl.enable `blend;
1727 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1728 GlDraw.color (0., 0., 0.) ~alpha:0.85;
1729 GlDraw.rect (0., 0.) (float state.w, float state.h);
1730 Gl.disable `blend;
1732 GlDraw.color (1., 1., 1.);
1733 let font = Glut.BITMAP_9_BY_15 in
1734 let draw_string x y s =
1735 GlPix.raster_pos ~x ~y ();
1736 String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s
1738 let rec loop row =
1739 if row = Array.length outlines || (row - first) * 16 > state.h
1740 then ()
1741 else (
1742 let (s, l, _, _) = outlines.(row) in
1743 let y = (row - first) * 16 in
1744 let x = 5 + 15*l in
1745 if row = active
1746 then (
1747 Gl.enable `blend;
1748 GlDraw.polygon_mode `both `line;
1749 GlFunc.blend_func `src_alpha `one_minus_src_alpha;
1750 GlDraw.color (1., 1., 1.) ~alpha:0.9;
1751 let sw = float (conf.scrollw - 1) *. float state.w /. float state.winw in
1752 GlDraw.rect (0., float (y + 1))
1753 ((float state.w -. sw), float (y + 18));
1754 GlDraw.polygon_mode `both `fill;
1755 Gl.disable `blend;
1756 GlDraw.color (1., 1., 1.);
1758 draw_string (float x) (float (y + 16)) s;
1759 loop (row+1)
1762 loop first
1765 let display () =
1766 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1767 GlDraw.viewport margin 0 state.w state.h;
1768 GlClear.color (scalecolor 0.5);
1769 GlClear.clear [`color];
1770 if state.x != 0
1771 then (
1772 let x = float state.x in
1773 GlMat.translate ~x ();
1775 if conf.zoom > 1.0
1776 then (
1777 Gl.enable `scissor_test;
1778 GlMisc.scissor 0 0 (state.winw - conf.scrollw) state.h;
1780 let _lasty = List.fold_left drawpage 0 (state.layout) in
1781 if conf.zoom > 1.0
1782 then
1783 Gl.disable `scissor_test
1785 if state.x != 0
1786 then (
1787 let x = -.float state.x in
1788 GlMat.translate ~x ();
1790 showrects ();
1791 GlDraw.viewport (state.winw - conf.scrollw) 0 state.winw state.h;
1792 scrollindicator ();
1793 showsel margin;
1794 GlDraw.viewport 0 0 state.winw state.h;
1795 showoutline state.outline;
1796 enttext ();
1797 Glut.swapBuffers ();
1800 let getunder x y =
1801 let margin = (state.winw - (state.w + conf.scrollw)) / 2 in
1802 let x = x - margin - state.x in
1803 let rec f = function
1804 | l :: rest ->
1805 begin match getopaque l.pageno with
1806 | Some opaque when validopaque opaque ->
1807 let y = y - l.pagedispy in
1808 if y > 0
1809 then
1810 let y = l.pagey + y in
1811 match whatsunder opaque x y with
1812 | Unone -> f rest
1813 | under -> under
1814 else
1815 f rest
1816 | _ ->
1817 f rest
1819 | [] -> Unone
1821 f state.layout
1824 let mouse ~button ~bstate ~x ~y =
1825 match button with
1826 | Glut.OTHER_BUTTON n when (n == 3 || n == 4) && bstate = Glut.UP ->
1827 let incr =
1828 if n = 3
1829 then
1830 -conf.scrollincr
1831 else
1832 conf.scrollincr
1834 let incr = incr * 2 in
1835 let y = clamp incr in
1836 gotoy y
1838 | Glut.LEFT_BUTTON when state.outline = None
1839 && Glut.getModifiers () land Glut.active_ctrl != 0 ->
1840 if bstate = Glut.DOWN
1841 then
1842 state.mstate <- Mpan (x, y)
1843 else
1844 state.mstate <- Mnone
1846 | Glut.LEFT_BUTTON when state.outline = None ->
1847 let dest = if bstate = Glut.DOWN then getunder x y else Unone in
1848 begin match dest with
1849 | Ulinkgoto (pageno, top) ->
1850 if pageno >= 0
1851 then
1852 gotopage1 pageno top
1854 | Ulinkuri s ->
1855 print_endline s
1857 | Unone when bstate = Glut.DOWN ->
1858 Glut.setCursor Glut.CURSOR_CROSSHAIR;
1859 state.mstate <- Mpan (x, y);
1861 | Unone | Utext _ ->
1862 if bstate = Glut.DOWN
1863 then (
1864 if state.rotate mod 360 = 0
1865 then (
1866 state.mstate <- Msel ((x, y), (x, y));
1867 Glut.postRedisplay ()
1870 else (
1871 match state.mstate with
1872 | Mnone -> ()
1874 | Mpan _ ->
1875 Glut.setCursor Glut.CURSOR_INHERIT;
1876 state.mstate <- Mnone
1878 | Msel ((x0, y0), (x1, y1)) ->
1879 let f l =
1880 if (y0 >= l.pagedispy && y0 <= (l.pagedispy + l.pagevh))
1881 || ((y1 >= l.pagedispy && y1 <= (l.pagedispy + l.pagevh)))
1882 then
1883 match getopaque l.pageno with
1884 | Some opaque when validopaque opaque ->
1885 copysel opaque
1886 | _ -> ()
1888 List.iter f state.layout;
1889 copysel ""; (* ugly *)
1890 Glut.setCursor Glut.CURSOR_INHERIT;
1891 state.mstate <- Mnone;
1895 | _ ->
1898 let mouse ~button ~state ~x ~y = mouse button state x y;;
1900 let motion ~x ~y =
1901 if state.outline = None
1902 then
1903 match state.mstate with
1904 | Mnone -> ()
1906 | Mpan (x0, y0) ->
1907 let dx = x - x0
1908 and dy = y0 - y in
1909 state.mstate <- Mpan (x, y);
1910 if conf.zoom > 1.0 then state.x <- state.x + dx;
1911 let y = clamp dy in
1912 gotoy y
1914 | Msel (a, _) ->
1915 state.mstate <- Msel (a, (x, y));
1916 Glut.postRedisplay ()
1919 let pmotion ~x ~y =
1920 if state.outline = None
1921 then
1922 match state.mstate with
1923 | Mnone ->
1924 begin match getunder x y with
1925 | Unone -> Glut.setCursor Glut.CURSOR_INHERIT
1926 | Ulinkuri uri ->
1927 if conf.underinfo then showtext 'u' ("ri: " ^ uri);
1928 Glut.setCursor Glut.CURSOR_INFO
1929 | Ulinkgoto (page, y) ->
1930 if conf.underinfo then showtext 'p' ("age: " ^ string_of_int page);
1931 Glut.setCursor Glut.CURSOR_INFO
1932 | Utext s ->
1933 if conf.underinfo then showtext 'f' ("ont: " ^ s);
1934 Glut.setCursor Glut.CURSOR_TEXT
1937 | Mpan _ | Msel _ ->
1941 let () =
1942 let statepath =
1943 let home =
1944 if Sys.os_type = "Win32"
1945 then
1946 try Sys.getenv "HOMEPATH" with Not_found -> ""
1947 else
1948 try Filename.concat (Sys.getenv "HOME") ".config" with Not_found -> ""
1950 Filename.concat home "llpp"
1952 let pstate =
1954 let ic = open_in_bin statepath in
1955 let hash = input_value ic in
1956 close_in ic;
1957 hash
1958 with exn ->
1959 if false
1960 then
1961 prerr_endline ("Error loading state " ^ Printexc.to_string exn)
1963 Hashtbl.create 1
1965 let savestate () =
1967 let w, h =
1968 match state.fullscreen with
1969 | None -> state.winw, state.h
1970 | Some wh -> wh
1972 Hashtbl.replace pstate state.path (state.bookmarks, w, h);
1973 let oc = open_out_bin statepath in
1974 output_value oc pstate
1975 with exn ->
1976 if false
1977 then
1978 prerr_endline ("Error saving state " ^ Printexc.to_string exn)
1981 let setstate () =
1983 let statebookmarks, statew, stateh = Hashtbl.find pstate state.path in
1984 state.w <- statew;
1985 state.h <- stateh;
1986 state.bookmarks <- statebookmarks;
1987 with Not_found -> ()
1988 | exn ->
1989 prerr_endline ("Error setting state " ^ Printexc.to_string exn)
1992 Arg.parse
1993 ["-p", Arg.String (fun s -> state.password <- s) , "password"]
1994 (fun s -> state.path <- s)
1995 ("Usage: " ^ Sys.argv.(0) ^ " [options] some.pdf\noptions:")
1997 let name =
1998 if String.length state.path = 0
1999 then (prerr_endline "filename missing"; exit 1)
2000 else state.path
2003 setstate ();
2004 let _ = Glut.init Sys.argv in
2005 let () = Glut.initDisplayMode ~depth:false ~double_buffer:true () in
2006 let () = Glut.initWindowSize state.w state.h in
2007 let _ = Glut.createWindow ("llpp " ^ Filename.basename name) in
2009 let csock, ssock =
2010 if Sys.os_type = "Unix"
2011 then
2012 Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
2013 else
2014 let addr = Unix.ADDR_INET (Unix.inet_addr_loopback, 1337) in
2015 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2016 Unix.setsockopt sock Unix.SO_REUSEADDR true;
2017 Unix.bind sock addr;
2018 Unix.listen sock 1;
2019 let csock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
2020 Unix.connect csock addr;
2021 let ssock, _ = Unix.accept sock in
2022 Unix.close sock;
2023 let opts sock =
2024 Unix.setsockopt sock Unix.TCP_NODELAY true;
2025 Unix.setsockopt_optint sock Unix.SO_LINGER None;
2027 opts ssock;
2028 opts csock;
2029 at_exit (fun () -> Unix.shutdown ssock Unix.SHUTDOWN_ALL);
2030 ssock, csock
2033 let () = Glut.displayFunc display in
2034 let () = Glut.reshapeFunc reshape in
2035 let () = Glut.keyboardFunc keyboard in
2036 let () = Glut.specialFunc special in
2037 let () = Glut.idleFunc (Some idle) in
2038 let () = Glut.mouseFunc mouse in
2039 let () = Glut.motionFunc motion in
2040 let () = Glut.passiveMotionFunc pmotion in
2042 init ssock;
2043 state.csock <- csock;
2044 state.ssock <- ssock;
2045 state.text <- "Opening " ^ name;
2046 writecmd state.csock ("open " ^ state.path ^ "\000" ^ state.password ^ "\000");
2048 at_exit savestate;
2050 let rec handlelablglutbug () =
2052 Glut.mainLoop ();
2053 with Glut.BadEnum "key in special_of_int" ->
2054 showtext '!' " LablGlut bug: special key not recognized";
2055 handlelablglutbug ()
2057 handlelablglutbug ();