Pride & Precedence
[llpp.git] / link.c
blob45744a69f768ea2679b3e9bcd3f555fc65039dd5
1 /* lots of code c&p-ed directly from mupdf */
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <inttypes.h>
6 #include <langinfo.h>
7 #include <limits.h>
8 #include <locale.h>
9 #include <math.h>
10 #include <pthread.h>
11 #include <regex.h>
12 #include <signal.h>
13 #include <spawn.h>
14 #include <stdarg.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/ioctl.h>
19 #include <sys/stat.h>
20 #include <sys/uio.h>
21 #include <unistd.h>
22 #include <wchar.h>
24 #include GL_H
26 #define CAML_NAME_SPACE
27 #include <caml/fail.h>
28 #include <caml/alloc.h>
29 #include <caml/memory.h>
30 #include <caml/unixsupport.h>
32 #pragma GCC diagnostic push
33 #pragma GCC diagnostic ignored "-Wfloat-equal"
34 #include <mupdf/fitz.h>
35 #include <mupdf/pdf.h>
36 #pragma GCC diagnostic pop
38 #pragma GCC diagnostic push
39 #ifdef __clang__
40 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
41 #endif
42 #include <ft2build.h>
43 #include FT_FREETYPE_H
44 #pragma GCC diagnostic pop
46 #include "cutils.h"
48 #define ARSERT(c) !(c) ? errx (1, "%s:%d " #c, __FILE__, __LINE__) : (void) 0
49 #define ML(d) extern value ml_##d; value ml_##d
50 #define ML0(d) extern void ml_##d; void ml_##d
51 #define STTI(st) ((unsigned int) st)
53 enum { Copen=23, Ccs, Cfreepage, Cfreetile, Csearch, Cgeometry, Creqlayout,
54 Cpage, Ctile, Ctrimset, Csettrim, Csliceh, Cinterrupt };
55 enum { FitWidth, FitProportional, FitPage };
56 enum { dir_first, dir_last };
57 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
58 enum { uuri, utext, utextannot, ufileannot, unone };
59 enum { mark_page, mark_block, mark_line, mark_word };
61 struct slice {
62 int h;
63 int texindex;
66 struct tile {
67 int w, h;
68 int slicecount;
69 int sliceheight;
70 fz_pixmap *pixmap;
71 struct slice slices[1];
74 struct pagedim {
75 int pageno;
76 int rotate;
77 int left;
78 int tctmready;
79 fz_irect bounds;
80 fz_rect pagebox;
81 fz_rect mediabox;
82 fz_matrix ctm, zoomctm, tctm;
85 struct slink {
86 enum { SLINK, SANNOT } tag;
87 fz_irect bbox;
88 union {
89 fz_link *link;
90 pdf_annot *annot;
91 } u;
94 struct annot {
95 fz_irect bbox;
96 pdf_annot *annot;
99 struct page {
100 int tgen;
101 int sgen;
102 int agen;
103 int pageno;
104 int pdimno;
105 fz_stext_page *text;
106 fz_page *fzpage;
107 fz_display_list *dlist;
108 fz_link *links;
109 int slinkcount;
110 struct slink *slinks;
111 int annotcount;
112 struct annot *annots;
113 fz_stext_char *fmark, *lmark;
116 static struct {
117 pthread_mutex_t mutex;
118 int sliceheight;
119 struct pagedim *pagedims;
120 int pagecount;
121 int pagedimcount;
122 fz_document *doc;
123 fz_context *ctx;
124 int w, h;
125 char *dcf;
126 int pfds[2];
128 struct {
129 int index, count;
130 GLuint *ids;
131 GLenum iform, form, ty;
132 struct {
133 int w, h;
134 struct slice *slice;
135 } *owners;
136 } tex;
138 fz_colorspace *colorspace;
139 float papercolor[4];
141 FT_Face face;
142 fz_pixmap *pig;
143 pthread_t thread;
144 fz_irect trimfuzz;
145 GLuint stid, boid;
146 int trimmargins, needoutline, gen, rotate, aalevel,
147 fitmodel, trimanew, csock, dirty, utf8cs;
149 GLfloat texcoords[8], vertices[16];
150 } state = { .mutex = PTHREAD_MUTEX_INITIALIZER };
152 static void lock (const char *cap)
154 int ret = pthread_mutex_lock (&state.mutex);
155 if (ret) {
156 errx (1, "%s: pthread_mutex_lock: %d(%s)", cap, ret, strerror (ret));
160 static void unlock (const char *cap)
162 int ret = pthread_mutex_unlock (&state.mutex);
163 if (ret) {
164 errx (1, "%s: pthread_mutex_unlock: %d(%s)", cap, ret, strerror (ret));
168 static int trylock (const char *cap)
170 int ret = pthread_mutex_trylock (&state.mutex);
171 if (ret && ret != EBUSY) {
172 errx (1, "%s: pthread_mutex_trylock: %d(%s)", cap, ret, strerror (ret));
174 return ret == EBUSY;
177 static int hasdata (int fd)
179 int ret, avail;
180 ret = ioctl (fd, FIONREAD, &avail);
181 if (ret) {
182 err (1, errno, "hasdata: FIONREAD error ret=%d", ret);
184 return avail > 0;
187 ML (hasdata (value fd_v))
189 CAMLparam1 (fd_v);
190 CAMLreturn (Val_bool (hasdata (Int_val (fd_v))));
193 static void readdata (int fd, void *p, int size)
195 ssize_t n;
197 again:
198 n = read (fd, p, size);
199 if (n < 0) {
200 if (errno == EINTR) {
201 goto again;
203 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size, n);
205 if (n - size) {
206 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
210 static void writedata (int fd, char *p, int size)
212 ssize_t n;
213 uint32_t size4 = size;
214 struct iovec iov[2] = {
215 { .iov_base = &size4, .iov_len = 4 },
216 { .iov_base = p, .iov_len = size }
219 again:
220 n = writev (fd, iov, 2);
221 if (n < 0) {
222 if (errno == EINTR) {
223 goto again;
225 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
227 if (n - size - 4) {
228 errx (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
232 static int readlen (int fd)
234 uint32_t u;
235 readdata (fd, &u, 4);
236 return u;
239 ML0 (wcmd (value fd_v, value bytes_v, value len_v))
241 CAMLparam3 (fd_v, bytes_v, len_v);
242 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
243 CAMLreturn0;
246 ML (rcmd (value fd_v))
248 CAMLparam1 (fd_v);
249 CAMLlocal1 (strdata_v);
250 int fd = Int_val (fd_v);
251 int len = readlen (fd);
252 strdata_v = caml_alloc_string (len);
253 readdata (fd, Bytes_val (strdata_v), len);
254 CAMLreturn (strdata_v);
257 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
259 char fbuf[64];
260 int size = sizeof (fbuf), len;
261 va_list ap;
262 char *buf = fbuf;
264 for (;;) {
265 va_start (ap, fmt);
266 len = vsnprintf (buf, size, fmt, ap);
267 va_end (ap);
269 if (len > -1) {
270 if (len < size - 4) {
271 writedata (state.csock, buf, len);
272 break;
274 else {
275 size = len + 5;
278 else {
279 err (1, errno, "vsnprintf for `%s' failed", fmt);
281 buf = realloc (buf == fbuf ? NULL : buf, size);
282 if (!buf) {
283 err (1, errno, "realloc for temp buf (%d bytes) failed", size);
286 if (buf != fbuf) {
287 free (buf);
291 static void closedoc (void)
293 if (state.doc) {
294 fz_drop_document (state.ctx, state.doc);
295 state.doc = NULL;
299 static int openxref (char *filename, char *password, int w, int h, int em)
301 for (int i = 0; i < state.tex.count; ++i) {
302 state.tex.owners[i].w = -1;
303 state.tex.owners[i].slice = NULL;
306 closedoc ();
308 state.dirty = 0;
309 if (state.pagedims) {
310 free (state.pagedims);
311 state.pagedims = NULL;
313 state.pagedimcount = 0;
315 fz_set_aa_level (state.ctx, state.aalevel);
316 state.doc = fz_open_document (state.ctx, filename);
317 if (fz_needs_password (state.ctx, state.doc)) {
318 if (password && !*password) {
319 printd ("pass");
320 return 0;
322 else {
323 int ok = fz_authenticate_password (state.ctx, state.doc, password);
324 if (!ok) {
325 printd ("pass fail");
326 return 0;
330 if (w >= 0 || h >= 0 || em >=0) {
331 fz_layout_document (state.ctx, state.doc, w, h, em);
333 state.pagecount = fz_count_pages (state.ctx, state.doc);
334 return 1;
337 static void docinfo (void)
339 struct { char *tag; char *name; } tab[] = {
340 { FZ_META_INFO_TITLE, "Title" },
341 { FZ_META_INFO_AUTHOR, "Author" },
342 { FZ_META_FORMAT, "Format" },
343 { FZ_META_ENCRYPTION, "Encryption" },
344 { FZ_META_INFO_CREATOR, "Creator" },
345 { FZ_META_INFO_PRODUCER, "Producer" },
346 { "info:CreationDate", "Creation date" },
348 int len = 0, need;
349 char *buf = NULL;
351 for (size_t i = 0; i < sizeof (tab) / sizeof (*tab); ++i) {
352 again:
353 need = fz_lookup_metadata (state.ctx, state.doc, tab[i].tag, buf, len);
354 if (need > 0) {
355 if (need <= len) {
356 printd ("info %s\t%s", tab[i].name, buf);
358 else {
359 buf = realloc (buf, need);
360 if (!buf) {
361 err (1, errno, "docinfo realloc %d", need);
363 len = need;
364 goto again;
368 free (buf);
370 printd ("infoend");
373 static void unlinktile (struct tile *tile)
375 for (int i = 0; i < tile->slicecount; ++i) {
376 struct slice *s = &tile->slices[i];
378 if (s->texindex != -1) {
379 if (state.tex.owners[s->texindex].slice == s) {
380 state.tex.owners[s->texindex].slice = NULL;
386 static void freepage (struct page *page)
388 if (page) {
389 fz_drop_stext_page (state.ctx, page->text);
390 free (page->slinks);
391 fz_drop_display_list (state.ctx, page->dlist);
392 fz_drop_page (state.ctx, page->fzpage);
393 free (page);
397 static void freetile (struct tile *tile)
399 unlinktile (tile);
400 fz_drop_pixmap (state.ctx, state.pig);
401 state.pig = tile->pixmap;
402 free (tile);
405 static void trimctm (pdf_page *page, int pindex)
407 struct pagedim *pdim = &state.pagedims[pindex];
409 if (!page) {
410 return;
412 if (!pdim->tctmready) {
413 fz_rect realbox, mediabox;
414 fz_matrix page_ctm, ctm;
416 ctm = fz_concat (fz_rotate (-pdim->rotate), fz_scale (1, -1));
417 realbox = fz_transform_rect (pdim->mediabox, ctm);
418 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
419 pdim->tctm = fz_concat (
420 fz_invert_matrix (page_ctm),
421 fz_concat (ctm, fz_translate (-realbox.x0, -realbox.y0)));
422 pdim->tctmready = 1;
426 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
428 fz_matrix ctm;
429 ptrdiff_t pdimno = pdim - state.pagedims;
431 ARSERT (pdim - state.pagedims < INT_MAX);
432 if (pdf_specifics (state.ctx, state.doc)) {
433 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
434 ctm = fz_concat (pdim->tctm, pdim->ctm);
436 else {
437 ctm = fz_concat (fz_translate (-pdim->mediabox.x0, -pdim->mediabox.y0),
438 pdim->ctm);
440 return ctm;
443 static fz_matrix pagectm (struct page *page)
445 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
448 static void *loadpage (int pageno, int pindex)
450 fz_device *dev;
451 struct page *page;
453 page = calloc (sizeof (struct page), 1);
454 if (!page) {
455 err (1, errno, "calloc page %d", pageno);
458 page->dlist = fz_new_display_list (state.ctx, fz_infinite_rect);
459 dev = fz_new_list_device (state.ctx, page->dlist);
460 fz_try (state.ctx) {
461 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
462 fz_run_page (state.ctx, page->fzpage, dev, fz_identity, NULL);
464 fz_catch (state.ctx) {
465 page->fzpage = NULL;
467 fz_close_device (state.ctx, dev);
468 fz_drop_device (state.ctx, dev);
470 page->pdimno = pindex;
471 page->pageno = pageno;
472 page->sgen = state.gen;
473 page->agen = state.gen;
474 page->tgen = state.gen;
475 return page;
478 static struct tile *alloctile (int h)
480 int slicecount;
481 size_t tilesize;
482 struct tile *tile;
484 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
485 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
486 tile = calloc (tilesize, 1);
487 if (!tile) {
488 err (1, errno, "cannot allocate tile (%zu bytes)", tilesize);
490 for (int i = 0; i < slicecount; ++i) {
491 int sh = fz_mini (h, state.sliceheight);
492 tile->slices[i].h = sh;
493 tile->slices[i].texindex = -1;
494 h -= sh;
496 tile->slicecount = slicecount;
497 tile->sliceheight = state.sliceheight;
498 return tile;
501 static struct tile *rendertile (struct page *page, int x, int y, int w, int h)
503 fz_irect bbox;
504 fz_matrix ctm;
505 fz_device *dev;
506 struct tile *tile;
507 struct pagedim *pdim;
509 tile = alloctile (h);
510 pdim = &state.pagedims[page->pdimno];
512 bbox = pdim->bounds;
513 bbox.x0 += x;
514 bbox.y0 += y;
515 bbox.x1 = bbox.x0 + w;
516 bbox.y1 = bbox.y0 + h;
518 if (state.pig) {
519 if (state.pig->w == w
520 && state.pig->h == h
521 && state.pig->colorspace == state.colorspace) {
522 tile->pixmap = state.pig;
523 tile->pixmap->x = bbox.x0;
524 tile->pixmap->y = bbox.y0;
526 else {
527 fz_drop_pixmap (state.ctx, state.pig);
529 state.pig = NULL;
531 if (!tile->pixmap) {
532 tile->pixmap = fz_new_pixmap_with_bbox (state.ctx,
533 state.colorspace,
534 bbox, NULL, 1);
537 tile->w = w;
538 tile->h = h;
539 fz_fill_pixmap_with_color (state.ctx, tile->pixmap,
540 fz_device_rgb (state.ctx),
541 state.papercolor,
542 fz_default_color_params);
544 dev = fz_new_draw_device (state.ctx, fz_identity, tile->pixmap);
545 ctm = pagectm (page);
546 fz_run_display_list (state.ctx, page->dlist, dev, ctm,
547 fz_rect_from_irect (bbox), NULL);
548 fz_close_device (state.ctx, dev);
549 fz_drop_device (state.ctx, dev);
551 return tile;
554 static void initpdims1 (void)
556 int shown = 0;
557 struct pagedim *p;
558 pdf_document *pdf;
559 fz_context *ctx = state.ctx;
560 int pageno, trim, show, cxcount;
561 fz_rect rootmediabox = fz_empty_rect;
563 fz_var (p);
564 fz_var (pdf);
565 fz_var (shown);
566 fz_var (cxcount);
568 cxcount = state.pagecount;
569 if ((pdf = pdf_specifics (ctx, state.doc))) {
570 pdf_obj *obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
571 "Root/Pages/MediaBox");
572 rootmediabox = pdf_to_rect (ctx, obj);
573 pdf_load_page_tree (ctx, pdf);
576 for (pageno = 0; pageno < cxcount; ++pageno) {
577 int rotate = 0;
578 fz_rect mediabox = fz_empty_rect;
580 fz_var (rotate);
581 if (pdf) {
582 pdf_obj *pageobj = NULL;
584 fz_var (pageobj);
585 if (pdf->rev_page_map) {
586 for (int i = 0; i < pdf->rev_page_count; ++i) {
587 if (pdf->rev_page_map[i].page == pageno) {
588 pageobj = pdf_get_xref_entry (
589 ctx, pdf, pdf->rev_page_map[i].object
590 )->obj;
591 break;
595 if (!pageobj) {
596 pageobj = pdf_lookup_page_obj (ctx, pdf, pageno);
599 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
601 if (state.trimmargins) {
602 pdf_obj *obj;
603 pdf_page *page;
605 fz_try (ctx) {
606 page = pdf_load_page (ctx, pdf, pageno);
607 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
608 trim = state.trimanew || !obj;
609 if (trim) {
610 fz_rect rect;
611 fz_device *dev;
612 fz_matrix ctm, page_ctm;
614 dev = fz_new_bbox_device (ctx, &rect);
615 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
616 ctm = fz_invert_matrix (page_ctm);
617 pdf_run_page (ctx, page, dev, fz_identity, NULL);
618 fz_close_device (ctx, dev);
619 fz_drop_device (ctx, dev);
621 rect.x0 += state.trimfuzz.x0;
622 rect.x1 += state.trimfuzz.x1;
623 rect.y0 += state.trimfuzz.y0;
624 rect.y1 += state.trimfuzz.y1;
625 rect = fz_transform_rect (rect, ctm);
626 rect = fz_intersect_rect (rect, mediabox);
628 if (!fz_is_empty_rect (rect)) {
629 mediabox = rect;
632 obj = pdf_new_array (ctx, pdf, 4);
633 pdf_array_push_real (ctx, obj, mediabox.x0);
634 pdf_array_push_real (ctx, obj, mediabox.y0);
635 pdf_array_push_real (ctx, obj, mediabox.x1);
636 pdf_array_push_real (ctx, obj, mediabox.y1);
637 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
639 else {
640 mediabox.x0 = pdf_array_get_real (ctx, obj, 0);
641 mediabox.y0 = pdf_array_get_real (ctx, obj, 1);
642 mediabox.x1 = pdf_array_get_real (ctx, obj, 2);
643 mediabox.y1 = pdf_array_get_real (ctx, obj, 3);
646 fz_drop_page (ctx, &page->super);
647 show = (pageno + 1 == state.pagecount)
648 || (trim ? pageno % 5 == 0 : pageno % 20 == 0);
649 if (show) {
650 printd ("progress %f Trimming %d",
651 (double) (pageno + 1) / state.pagecount,
652 pageno + 1);
655 fz_catch (ctx) {
656 printd ("emsg failed to load page %d", pageno);
659 else {
660 int empty = 0;
661 fz_rect cropbox;
663 mediabox =
664 pdf_to_rect (ctx,
665 pdf_dict_get_inheritable (
666 ctx,
667 pageobj,
668 PDF_NAME (MediaBox)
671 if (fz_is_empty_rect (mediabox)) {
672 mediabox.x0 = 0;
673 mediabox.y0 = 0;
674 mediabox.x1 = 612;
675 mediabox.y1 = 792;
676 empty = 1;
679 cropbox =
680 pdf_to_rect (ctx, pdf_dict_gets (ctx, pageobj, "CropBox"));
681 if (!fz_is_empty_rect (cropbox)) {
682 if (empty) {
683 mediabox = cropbox;
685 else {
686 mediabox = fz_intersect_rect (mediabox, cropbox);
689 else {
690 if (empty) {
691 if (fz_is_empty_rect (rootmediabox)) {
692 printd ("emsg cannot find size of page %d", pageno);
694 else {
695 mediabox = rootmediabox;
701 else {
702 if (state.trimmargins) {
703 fz_page *page;
705 fz_try (ctx) {
706 page = fz_load_page (ctx, state.doc, pageno);
707 mediabox = fz_bound_page (ctx, page);
708 if (state.trimmargins) {
709 fz_rect rect;
710 fz_device *dev;
712 dev = fz_new_bbox_device (ctx, &rect);
713 fz_run_page (ctx, page, dev, fz_identity, NULL);
714 fz_close_device (ctx, dev);
715 fz_drop_device (ctx, dev);
717 rect.x0 += state.trimfuzz.x0;
718 rect.x1 += state.trimfuzz.x1;
719 rect.y0 += state.trimfuzz.y0;
720 rect.y1 += state.trimfuzz.y1;
721 rect = fz_intersect_rect (rect, mediabox);
723 if (!fz_is_empty_rect (rect)) {
724 mediabox = rect;
727 fz_drop_page (ctx, page);
729 fz_catch (ctx) {
732 else {
733 fz_page *page;
734 fz_try (ctx) {
735 page = fz_load_page (ctx, state.doc, pageno);
736 mediabox = fz_bound_page (ctx, page);
737 fz_drop_page (ctx, page);
739 show = !state.trimmargins && pageno % 20 == 0;
740 if (show) {
741 shown = 1;
742 printd ("progress %f Gathering dimensions %d",
743 (double) pageno / state.pagecount, pageno);
746 fz_catch (ctx) {
747 printd ("emsg failed to load page %d", pageno);
751 if (state.pagedimcount == 0
752 || ((void) (p = &state.pagedims[state.pagedimcount-1])
753 , p->rotate != rotate)
754 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
755 size_t size;
757 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
758 state.pagedims = realloc (state.pagedims, size);
759 if (!state.pagedims) {
760 err (1, errno, "realloc pagedims to %zu (%d elems)",
761 size, state.pagedimcount + 1);
764 p = &state.pagedims[state.pagedimcount++];
765 p->rotate = rotate;
766 p->mediabox = mediabox;
767 p->pageno = pageno;
770 state.trimanew = 0;
771 if (shown) {
772 printd ("progress 1");
776 static void initpdims (void)
778 FILE *f = state.dcf ? fopen (state.dcf, "rb") : NULL;
779 if (f) {
780 size_t nread;
782 nread = fread (&state.pagedimcount, sizeof (state.pagedimcount), 1, f);
783 if (nread - 1) {
784 err (1, errno, "fread pagedim %zu", sizeof (state.pagedimcount));
786 size_t size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
787 state.pagedims = realloc (state.pagedims, size);
788 if (!state.pagedims) {
789 err (1, errno, "realloc pagedims to %zu (%d elems)",
790 size, state.pagedimcount + 1);
792 if (fread (state.pagedims,
793 sizeof (*state.pagedims),
794 state.pagedimcount+1,
795 f) - (state.pagedimcount+1)) {
796 err (1, errno, "fread pagedim data %zu %d",
797 sizeof (*state.pagedims), state.pagedimcount+1);
799 fclose (f);
802 if (!state.pagedims) {
803 initpdims1 ();
804 if (state.dcf) {
805 f = fopen (state.dcf, "wb");
806 if (!f) {
807 err (1, errno, "fopen %s for writing", state.dcf);
809 if (fwrite (&state.pagedimcount,
810 sizeof (state.pagedimcount), 1, f) - 1) {
811 err (1, errno, "fwrite pagedimcunt %zu",
812 sizeof (state.pagedimcount));
814 if (fwrite (state.pagedims, sizeof (*state.pagedims),
815 state.pagedimcount + 1, f)
816 - (state.pagedimcount + 1)) {
817 err (1, errno, "fwrite pagedim data %zu %u",
818 sizeof (*state.pagedims), state.pagedimcount+1);
820 fclose (f);
825 static void layout (void)
827 int pindex;
828 fz_rect box;
829 fz_matrix ctm;
830 struct pagedim *p = NULL;
831 float zw, w, maxw = 0.0, zoom = 1.0;
833 if (state.pagedimcount == 0) {
834 return;
837 switch (state.fitmodel) {
838 case FitProportional:
839 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
840 float x0, x1;
842 p = &state.pagedims[pindex];
843 box = fz_transform_rect (p->mediabox,
844 fz_rotate (p->rotate + state.rotate));
846 x0 = fz_min (box.x0, box.x1);
847 x1 = fz_max (box.x0, box.x1);
849 w = x1 - x0;
850 maxw = fz_max (w, maxw);
851 zoom = state.w / maxw;
853 break;
855 case FitPage:
856 maxw = state.w;
857 break;
859 case FitWidth:
860 break;
862 default:
863 ARSERT (0 && state.fitmodel);
866 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
867 p = &state.pagedims[pindex];
868 ctm = fz_rotate (state.rotate);
869 box = fz_transform_rect (p->mediabox,
870 fz_rotate (p->rotate + state.rotate));
871 w = box.x1 - box.x0;
872 switch (state.fitmodel) {
873 case FitProportional:
874 p->left = (int) (((maxw - w) * zoom) / 2.f);
875 break;
876 case FitPage:
878 float zh, h;
879 zw = maxw / w;
880 h = box.y1 - box.y0;
881 zh = state.h / h;
882 zoom = fz_min (zw, zh);
883 p->left = (int) ((maxw - (w * zoom)) / 2.f);
885 break;
886 case FitWidth:
887 p->left = 0;
888 zoom = state.w / w;
889 break;
892 p->zoomctm = fz_scale (zoom, zoom);
893 ctm = fz_concat (p->zoomctm, ctm);
895 p->pagebox = p->mediabox;
896 p->pagebox = fz_transform_rect (p->pagebox, fz_rotate (p->rotate));
897 p->pagebox.x1 -= p->pagebox.x0;
898 p->pagebox.y1 -= p->pagebox.y0;
899 p->pagebox.x0 = 0;
900 p->pagebox.y0 = 0;
901 p->bounds = fz_round_rect (fz_transform_rect (p->pagebox, ctm));
902 p->ctm = ctm;
904 ctm = fz_concat (fz_translate (0, -p->mediabox.y1),
905 fz_scale (zoom, -zoom));
906 p->tctmready = 0;
909 do {
910 printd ("pdim %u %d %d %d", p->pageno, p->left,
911 abs (p->bounds.x0 - p->bounds.x1),
912 abs (p->bounds.y0 - p->bounds.y1));
913 } while (p-- != state.pagedims);
916 static struct pagedim *pdimofpageno (int pageno)
918 struct pagedim *pdim = state.pagedims;
920 for (int i = 0; i < state.pagedimcount; ++i) {
921 if (state.pagedims[i].pageno > pageno) {
922 break;
924 pdim = &state.pagedims[i];
926 return pdim;
929 static void recurse_outline (fz_outline *outline, int level)
931 while (outline) {
932 int pageno;
933 fz_point p;
934 fz_location loc;
936 loc = fz_resolve_link (state.ctx, state.doc, String_val (outline->uri),
937 &p.x, &p.y);
938 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
939 if (pageno >= 0) {
940 struct pagedim *pdim = pdimofpageno (outline->page);
941 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
942 p = fz_transform_point (p, pdim->ctm);
943 printd ("o %d %d %d %d %s",
944 level, pageno, (int) p.y, h, outline->title);
946 else {
947 printd ("on %d %s", level, outline->title);
949 if (outline->down) {
950 recurse_outline (outline->down, level + 1);
952 outline = outline->next;
956 static void process_outline (void)
958 if (state.needoutline && state.pagedimcount) {
959 fz_outline *outline = fz_load_outline (state.ctx, state.doc);
960 state.needoutline = 0;
961 if (outline) {
962 recurse_outline (outline, 0);
963 fz_drop_outline (state.ctx, outline);
968 static char *strofline (fz_stext_line *line)
970 char *p;
971 char utf8[10];
972 fz_stext_char *ch;
973 size_t size = 0, cap = 80;
975 p = malloc (cap + 1);
976 if (!p) {
977 return NULL;
980 for (ch = line->first_char; ch; ch = ch->next) {
981 int n = fz_runetochar (utf8, ch->c);
982 if (size + n > cap) {
983 cap *= 2;
984 p = realloc (p, cap + 1);
985 if (!p) {
986 return NULL;
990 memcpy (p + size, utf8, n);
991 size += n;
993 p[size] = 0;
994 return p;
997 enum a_searchresult { Found=61, NotFound, Interrupted, Error };
999 static enum a_searchresult matchline (regex_t *re, fz_stext_line *line,
1000 int num_matches, int pageno)
1002 int ret;
1003 char *p;
1004 regmatch_t rm;
1006 p = strofline (line);
1007 if (!p) {
1008 return Error;
1011 ret = regexec (re, p, 1, &rm, 0);
1012 free (p);
1014 if (ret) {
1015 if (ret != REG_NOMATCH) {
1016 int isize;
1017 size_t size;
1018 char errbuf[80], *trail;
1020 size = regerror (ret, re, errbuf, sizeof (errbuf));
1021 if (size > 23) {
1022 isize = 23;
1023 trail = "...";
1025 else {
1026 isize = (int) size;
1027 trail = "";
1029 printd ("emsg regexec error '%*s%s'", isize, errbuf, trail);
1030 return Error;
1032 return NotFound;
1034 else {
1035 int o = 0;
1036 fz_quad s, e;
1037 fz_stext_char *ch;
1039 if (rm.rm_so == rm.rm_eo) {
1040 return Found;
1043 for (ch = line->first_char; ch; ch = ch->next) {
1044 o += fz_runelen (ch->c);
1045 if (o > rm.rm_so) {
1046 s = ch->quad;
1047 break;
1050 for (;ch; ch = ch->next) {
1051 o += fz_runelen (ch->c);
1052 if (o > rm.rm_eo) {
1053 break;
1056 e = ch->quad;
1058 printd ("match %d %d %f %f %f %f %f %f %f %f",
1059 pageno, num_matches,
1060 s.ul.x, s.ul.y,
1061 e.ur.x, s.ul.y,
1062 e.lr.x, e.lr.y,
1063 s.ul.x, e.lr.y);
1064 return Found;
1068 /* wishful thinking function */
1069 static void search (regex_t *re, int pageno, int y, int forward)
1071 fz_device *tdev;
1072 double dur, start;
1073 char *cap = "bug";
1074 struct pagedim *pdim;
1075 fz_page *page = NULL;
1076 fz_stext_block *block;
1077 fz_stext_page *text = NULL;
1078 int niters = 0, num_matches = 0;
1079 enum a_searchresult the_searchresult = NotFound;
1081 start = now ();
1082 while (pageno >= 0 && pageno < state.pagecount && num_matches == 0) {
1083 if (niters++ == 5) {
1084 niters = 0;
1085 if (hasdata (state.csock)) {
1086 fz_drop_stext_page (state.ctx, text);
1087 fz_drop_page (state.ctx, page);
1088 the_searchresult = Interrupted;
1089 break;
1091 else {
1092 printd ("progress %f searching in page %d",
1093 (double) (pageno + 1) / state.pagecount, pageno);
1096 pdim = pdimofpageno (pageno);
1097 text = fz_new_stext_page (state.ctx, pdim->mediabox);
1098 tdev = fz_new_stext_device (state.ctx, text, 0);
1100 page = fz_load_page (state.ctx, state.doc, pageno);
1101 fz_run_page (state.ctx, page, tdev, pagectm1 (page, pdim), NULL);
1103 fz_close_device (state.ctx, tdev);
1104 fz_drop_device (state.ctx, tdev);
1106 if (forward) {
1107 for (block = text->first_block; block; block = block->next) {
1108 fz_stext_line *line;
1110 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1111 continue;
1114 for (line = block->u.t.first_line; line; line = line->next) {
1115 if (line->bbox.y0 < y + 1) {
1116 continue;
1119 the_searchresult =
1120 matchline (re, line, num_matches, pageno);
1121 num_matches += the_searchresult == Found;
1125 else {
1126 for (block = text->last_block; block; block = block->prev) {
1127 fz_stext_line *line;
1129 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1130 continue;
1133 for (line = block->u.t.last_line; line; line = line->prev) {
1134 if (line->bbox.y0 < y + 1) {
1135 continue;
1138 the_searchresult =
1139 matchline (re, line, num_matches, pageno);
1140 num_matches += the_searchresult == Found;
1145 if (forward) {
1146 pageno += 1;
1147 y = 0;
1149 else {
1150 pageno -= 1;
1151 y = INT_MAX;
1153 fz_drop_stext_page (state.ctx, text);
1154 text = NULL;
1155 fz_drop_page (state.ctx, page);
1156 page = NULL;
1158 dur = now () - start;
1159 switch (the_searchresult) {
1160 case Found: case NotFound: cap = ""; break;
1161 case Error: cap = "error "; break;
1162 case Interrupted: cap = "interrupt "; break;
1164 printd ("progress 1 %sfound %d in %f sec", cap, num_matches, dur);
1165 printd ("clearrects");
1168 static void set_tex_params (int colorspace)
1170 switch (colorspace) {
1171 case 0:
1172 state.tex.iform = GL_RGBA8;
1173 state.tex.form = GL_RGBA;
1174 state.tex.ty = GL_UNSIGNED_BYTE;
1175 state.colorspace = fz_device_rgb (state.ctx);
1176 break;
1177 case 1:
1178 state.tex.iform = GL_LUMINANCE_ALPHA;
1179 state.tex.form = GL_LUMINANCE_ALPHA;
1180 state.tex.ty = GL_UNSIGNED_BYTE;
1181 state.colorspace = fz_device_gray (state.ctx);
1182 break;
1183 default:
1184 errx (1, "invalid colorspce %d", colorspace);
1188 static void realloctexts (int texcount)
1190 size_t size;
1192 if (texcount == state.tex.count) {
1193 return;
1196 if (texcount < state.tex.count) {
1197 glDeleteTextures (state.tex.count - texcount, state.tex.ids + texcount);
1200 size = texcount * (sizeof (*state.tex.ids) + sizeof (*state.tex.owners));
1201 state.tex.ids = realloc (state.tex.ids, size);
1202 if (!state.tex.ids) {
1203 err (1, errno, "realloc texs %zu", size);
1206 state.tex.owners = (void *) (state.tex.ids + texcount);
1207 if (texcount > state.tex.count) {
1208 glGenTextures (texcount - state.tex.count,
1209 state.tex.ids + state.tex.count);
1210 for (int i = state.tex.count; i < texcount; ++i) {
1211 state.tex.owners[i].w = -1;
1212 state.tex.owners[i].slice = NULL;
1215 state.tex.count = texcount;
1216 state.tex.index = 0;
1219 static char *mbtoutf8 (char *s)
1221 char *p, *r;
1222 wchar_t *tmp;
1223 size_t i, ret, len;
1225 if (state.utf8cs) {
1226 return s;
1229 len = mbstowcs (NULL, s, strlen (s));
1230 if (len == 0 || len == (size_t) -1) {
1231 if (len) {
1232 printd ("emsg mbtoutf8: mbstowcs: %d(%s)", errno, strerror (errno));
1234 return s;
1237 tmp = calloc (len, sizeof (wchar_t));
1238 if (!tmp) {
1239 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d(%s)",
1240 len, sizeof (wchar_t), errno, strerror (errno));
1241 return s;
1244 ret = mbstowcs (tmp, s, len);
1245 if (ret == (size_t) -1) {
1246 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d(%s)",
1247 len, errno, strerror (errno));
1248 free (tmp);
1249 return s;
1252 len = 0;
1253 for (i = 0; i < ret; ++i) {
1254 len += fz_runelen (tmp[i]);
1257 p = r = malloc (len + 1);
1258 if (!r) {
1259 printd ("emsg mbtoutf8: malloc(%zu)", len);
1260 free (tmp);
1261 return s;
1264 for (i = 0; i < ret; ++i) {
1265 p += fz_runetochar (p, tmp[i]);
1267 *p = 0;
1268 free (tmp);
1269 return r;
1272 ML (mbtoutf8 (value s_v))
1274 CAMLparam1 (s_v);
1275 CAMLlocal1 (ret_v);
1276 char *s, *r;
1278 s = &Byte (s_v, 0);
1279 r = mbtoutf8 (s);
1280 if (r == s) {
1281 ret_v = s_v;
1283 else {
1284 ret_v = caml_copy_string (r);
1285 free (r);
1287 CAMLreturn (ret_v);
1290 static void *mainloop (void UNUSED_ATTR *unused)
1292 char *p = NULL, c;
1293 int len, ret, oldlen = 0;
1295 fz_var (p);
1296 fz_var (oldlen);
1297 for (;;) {
1298 len = readlen (state.csock);
1299 if (len == 0) {
1300 errx (1, "readlen returned 0");
1303 if (oldlen < len) {
1304 p = realloc (p, len);
1305 if (!p) {
1306 err (1, errno, "realloc %d failed", len);
1308 oldlen = len;
1310 readdata (state.csock, p, len);
1311 c = p[len-1];
1312 p[len-1] = 0;
1314 switch (c) {
1315 case Copen: {
1316 int off, usedoccss, ok = 0;
1317 int w, h, em;
1318 char *password;
1319 char *filename;
1320 char *utf8filename;
1321 size_t filenamelen;
1323 fz_var (ok);
1324 ret = sscanf (p, "%d %d %d %d %n", &usedoccss, &w, &h, &em, &off);
1325 if (ret != 4) {
1326 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1329 filename = p + off;
1330 filenamelen = strlen (filename);
1331 password = filename + filenamelen + 1;
1333 if (password[strlen (password) + 1]) {
1334 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1337 lock ("open");
1338 fz_set_use_document_css (state.ctx, usedoccss);
1339 fz_try (state.ctx) {
1340 ok = openxref (filename, password, w, h, em);
1342 fz_catch (state.ctx) {
1343 utf8filename = mbtoutf8 (filename);
1344 printd ("emsg failed loading %s: %s",
1345 utf8filename, fz_caught_message (state.ctx));
1346 if (utf8filename != filename) {
1347 free (utf8filename);
1350 if (ok) {
1351 docinfo ();
1352 initpdims ();
1354 unlock ("open");
1355 state.needoutline = ok;
1356 break;
1358 case Ccs: {
1359 int i, colorspace;
1361 ret = sscanf (p, "%d", &colorspace);
1362 if (ret != 1) {
1363 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1365 lock ("cs");
1366 set_tex_params (colorspace);
1367 for (i = 0; i < state.tex.count; ++i) {
1368 state.tex.owners[i].w = -1;
1369 state.tex.owners[i].slice = NULL;
1371 unlock ("cs");
1372 break;
1374 case Cfreepage: {
1375 void *ptr;
1377 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1378 if (ret != 1) {
1379 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1381 lock ("freepage");
1382 freepage (ptr);
1383 unlock ("freepage");
1384 break;
1386 case Cfreetile: {
1387 void *ptr;
1389 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1390 if (ret != 1) {
1391 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1393 lock ("freetile");
1394 freetile (ptr);
1395 unlock ("freetile");
1396 break;
1398 case Csearch: {
1399 int icase, pageno, y, len2, forward;
1400 char *pattern;
1401 regex_t re;
1403 ret = sscanf (p, "%d %d %d %d,%n",
1404 &icase, &pageno, &y, &forward, &len2);
1405 if (ret != 4) {
1406 errx (1, "malformed search `%s' ret=%d", p, ret);
1409 pattern = p + len2;
1410 ret = regcomp (&re, pattern,
1411 REG_EXTENDED | (icase ? REG_ICASE : 0));
1412 if (ret) {
1413 char errbuf[80];
1414 size_t size;
1416 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1417 printd ("emsg regcomp failed `%.*s'", (int) size, errbuf);
1419 else {
1420 lock ("search");
1421 search (&re, pageno, y, forward);
1422 unlock ("search");
1423 regfree (&re);
1425 break;
1427 case Cgeometry: {
1428 int w, h, fitmodel;
1430 printd ("clear");
1431 ret = sscanf (p, "%d %d %d", &w, &h, &fitmodel);
1432 if (ret != 3) {
1433 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1436 lock ("geometry");
1437 state.h = h;
1438 if (w != state.w) {
1439 state.w = w;
1440 for (int i = 0; i < state.tex.count; ++i) {
1441 state.tex.owners[i].slice = NULL;
1444 state.fitmodel = fitmodel;
1445 layout ();
1446 process_outline ();
1448 state.gen++;
1449 unlock ("geometry");
1450 printd ("continue %d", state.pagecount);
1451 break;
1453 case Creqlayout: {
1454 char *nameddest;
1455 int rotate, off, h;
1456 int fitmodel;
1457 pdf_document *pdf;
1459 printd ("clear");
1460 ret = sscanf (p, "%d %d %d %n", &rotate, &fitmodel, &h, &off);
1461 if (ret != 3) {
1462 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1464 lock ("reqlayout");
1465 pdf = pdf_specifics (state.ctx, state.doc);
1466 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1467 state.gen += 1;
1469 state.rotate = rotate;
1470 state.fitmodel = fitmodel;
1471 state.h = h;
1472 layout ();
1473 process_outline ();
1475 nameddest = p + off;
1476 if (pdf && nameddest && *nameddest) {
1477 fz_point xy;
1478 struct pagedim *pdim;
1479 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1480 &xy.x, &xy.y);
1481 pdim = pdimofpageno (pageno);
1482 xy = fz_transform_point (xy, pdim->ctm);
1483 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1486 state.gen++;
1487 unlock ("reqlayout");
1488 printd ("continue %d", state.pagecount);
1489 break;
1491 case Cpage: {
1492 double a, b;
1493 struct page *page;
1494 int pageno, pindex;
1496 ret = sscanf (p, "%d %d", &pageno, &pindex);
1497 if (ret != 2) {
1498 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1501 lock ("page");
1502 a = now ();
1503 page = loadpage (pageno, pindex);
1504 b = now ();
1505 unlock ("page");
1507 printd ("page %" PRIxPTR " %f", (uintptr_t) page, b - a);
1508 break;
1510 case Ctile: {
1511 int x, y, w, h;
1512 struct page *page;
1513 struct tile *tile;
1514 double a, b;
1516 ret = sscanf (p, "%" SCNxPTR " %d %d %d %d",
1517 (uintptr_t *) &page, &x, &y, &w, &h);
1518 if (ret != 5) {
1519 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1522 lock ("tile");
1523 a = now ();
1524 tile = rendertile (page, x, y, w, h);
1525 b = now ();
1526 unlock ("tile");
1528 printd ("tile %d %d %" PRIxPTR " %u %f",
1529 x, y, (uintptr_t) tile,
1530 tile->w * tile->h * tile->pixmap->n, b - a);
1531 break;
1533 case Ctrimset: {
1534 fz_irect fuzz;
1535 int trimmargins;
1537 ret = sscanf (p, "%d %d %d %d %d",
1538 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1539 if (ret != 5) {
1540 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1543 lock ("trimset");
1544 state.trimmargins = trimmargins;
1545 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1546 state.trimanew = 1;
1547 state.trimfuzz = fuzz;
1549 unlock ("trimset");
1550 break;
1552 case Csettrim: {
1553 fz_irect fuzz;
1554 int trimmargins;
1556 ret = sscanf (p, "%d %d %d %d %d",
1557 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1558 if (ret != 5) {
1559 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1561 printd ("clear");
1562 lock ("settrim");
1563 state.trimmargins = trimmargins;
1564 state.needoutline = 1;
1565 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1566 state.trimanew = 1;
1567 state.trimfuzz = fuzz;
1569 state.pagedimcount = 0;
1570 free (state.pagedims);
1571 state.pagedims = NULL;
1572 initpdims ();
1573 layout ();
1574 process_outline ();
1575 unlock ("settrim");
1576 printd ("continue %d", state.pagecount);
1577 break;
1579 case Csliceh: {
1580 int h;
1582 ret = sscanf (p, "%d", &h);
1583 if (ret != 1) {
1584 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1586 if (h != state.sliceheight) {
1587 state.sliceheight = h;
1588 for (int i = 0; i < state.tex.count; ++i) {
1589 state.tex.owners[i].w = -1;
1590 state.tex.owners[i].h = -1;
1591 state.tex.owners[i].slice = NULL;
1594 break;
1596 case Cinterrupt:
1597 printd ("vmsg interrupted");
1598 break;
1599 default:
1600 errx (1, "unknown llpp ffi command - %d [%.*s]", c, len, p);
1603 return 0;
1606 ML (isexternallink (value uri_v))
1608 CAMLparam1 (uri_v);
1609 CAMLreturn (Val_bool (fz_is_external_link (state.ctx, String_val (uri_v))));
1612 ML (uritolocation (value uri_v))
1614 CAMLparam1 (uri_v);
1615 CAMLlocal1 (ret_v);
1616 fz_location loc;
1617 int pageno;
1618 fz_point xy;
1619 struct pagedim *pdim;
1621 loc = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1622 &xy.x, &xy.y);
1623 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
1624 pdim = pdimofpageno (pageno);
1625 xy = fz_transform_point (xy, pdim->ctm);
1626 ret_v = caml_alloc_tuple (3);
1627 Field (ret_v, 0) = Val_int (pageno);
1628 Field (ret_v, 1) = caml_copy_double ((double) xy.x);
1629 Field (ret_v, 2) = caml_copy_double ((double) xy.y);
1630 CAMLreturn (ret_v);
1633 ML (realloctexts (value texcount_v))
1635 CAMLparam1 (texcount_v);
1636 int ok;
1638 if (trylock (__func__)) {
1639 ok = 0;
1640 goto done;
1642 realloctexts (Int_val (texcount_v));
1643 ok = 1;
1644 unlock (__func__);
1646 done:
1647 CAMLreturn (Val_bool (ok));
1650 static void recti (int x0, int y0, int x1, int y1)
1652 GLfloat *v = state.vertices;
1654 glVertexPointer (2, GL_FLOAT, 0, v);
1655 v[0] = x0; v[1] = y0;
1656 v[2] = x1; v[3] = y0;
1657 v[4] = x0; v[5] = y1;
1658 v[6] = x1; v[7] = y1;
1659 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1662 static void showsel (struct page *page, int ox, int oy)
1664 fz_irect bbox;
1665 fz_rect rect;
1666 fz_stext_block *block;
1667 int seen = 0;
1668 unsigned char selcolor[] = {15,15,15,140};
1670 if (!page->fmark || !page->lmark) {
1671 return;
1674 glEnable (GL_BLEND);
1675 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1676 glColor4ubv (selcolor);
1678 ox += state.pagedims[page->pdimno].bounds.x0;
1679 oy += state.pagedims[page->pdimno].bounds.y0;
1681 for (block = page->text->first_block; block; block = block->next) {
1682 fz_stext_line *line;
1684 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1685 continue;
1687 for (line = block->u.t.first_line; line; line = line->next) {
1688 fz_stext_char *ch;
1690 rect = fz_empty_rect;
1691 for (ch = line->first_char; ch; ch = ch->next) {
1692 fz_rect r;
1693 if (ch == page->fmark) {
1694 seen = 1;
1696 r = fz_rect_from_quad (ch->quad);
1697 if (seen) {
1698 rect = fz_union_rect (rect, r);
1700 if (ch == page->lmark) {
1701 bbox = fz_round_rect (rect);
1702 recti (bbox.x0 + ox, bbox.y0 + oy,
1703 bbox.x1 + ox, bbox.y1 + oy);
1704 goto done;
1707 if (!fz_is_empty_rect (rect)) {
1708 bbox = fz_round_rect (rect);
1709 recti (bbox.x0 + ox, bbox.y0 + oy,
1710 bbox.x1 + ox, bbox.y1 + oy);
1714 done:
1715 glDisable (GL_BLEND);
1718 #pragma GCC diagnostic push
1719 #pragma GCC diagnostic ignored "-Wconversion"
1720 #include "glfont.c"
1721 #pragma GCC diagnostic pop
1723 static void stipplerect (fz_matrix m,
1724 fz_point p1,
1725 fz_point p2,
1726 fz_point p3,
1727 fz_point p4,
1728 GLfloat *texcoords,
1729 GLfloat *vertices)
1731 p1 = fz_transform_point (p1, m);
1732 p2 = fz_transform_point (p2, m);
1733 p3 = fz_transform_point (p3, m);
1734 p4 = fz_transform_point (p4, m);
1736 float w, h, s, t;
1738 w = p2.x - p1.x;
1739 h = p2.y - p1.y;
1740 t = hypotf (w, h) * .25f;
1742 w = p3.x - p2.x;
1743 h = p3.y - p2.y;
1744 s = hypotf (w, h) * .25f;
1746 texcoords[0] = 0; vertices[0] = p1.x; vertices[1] = p1.y;
1747 texcoords[1] = t; vertices[2] = p2.x; vertices[3] = p2.y;
1749 texcoords[2] = 0; vertices[4] = p2.x; vertices[5] = p2.y;
1750 texcoords[3] = s; vertices[6] = p3.x; vertices[7] = p3.y;
1752 texcoords[4] = 0; vertices[8] = p3.x; vertices[9] = p3.y;
1753 texcoords[5] = t; vertices[10] = p4.x; vertices[11] = p4.y;
1755 texcoords[6] = 0; vertices[12] = p4.x; vertices[13] = p4.y;
1756 texcoords[7] = s; vertices[14] = p1.x; vertices[15] = p1.y;
1758 glDrawArrays (GL_LINES, 0, 8);
1761 static void ensurelinks (struct page *page)
1763 if (!page->links) {
1764 page->links = fz_load_links (state.ctx, page->fzpage);
1768 static void highlightlinks (struct page *page, int xoff, int yoff)
1770 fz_matrix ctm;
1771 fz_link *link;
1772 GLfloat *texcoords = state.texcoords;
1773 GLfloat *vertices = state.vertices;
1775 ensurelinks (page);
1777 glEnable (GL_TEXTURE_1D);
1778 glEnable (GL_BLEND);
1779 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1780 glBindTexture (GL_TEXTURE_1D, state.stid);
1782 xoff -= state.pagedims[page->pdimno].bounds.x0;
1783 yoff -= state.pagedims[page->pdimno].bounds.y0;
1784 ctm = fz_concat (pagectm (page), fz_translate (xoff, yoff));
1786 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
1787 glVertexPointer (2, GL_FLOAT, 0, vertices);
1789 for (link = page->links; link; link = link->next) {
1790 fz_point p1, p2, p3, p4;
1792 p1.x = link->rect.x0;
1793 p1.y = link->rect.y0;
1795 p2.x = link->rect.x1;
1796 p2.y = link->rect.y0;
1798 p3.x = link->rect.x1;
1799 p3.y = link->rect.y1;
1801 p4.x = link->rect.x0;
1802 p4.y = link->rect.y1;
1804 /* TODO: different colours for different schemes */
1805 if (fz_is_external_link (state.ctx, link->uri)) {
1806 glColor3ub (0, 0, 255);
1808 else {
1809 glColor3ub (255, 0, 0);
1812 stipplerect (ctm, p1, p2, p3, p4, texcoords, vertices);
1815 for (int i = 0; i < page->annotcount; ++i) {
1816 fz_point p1, p2, p3, p4;
1817 struct annot *annot = &page->annots[i];
1819 p1.x = annot->bbox.x0;
1820 p1.y = annot->bbox.y0;
1822 p2.x = annot->bbox.x1;
1823 p2.y = annot->bbox.y0;
1825 p3.x = annot->bbox.x1;
1826 p3.y = annot->bbox.y1;
1828 p4.x = annot->bbox.x0;
1829 p4.y = annot->bbox.y1;
1831 glColor3ub (0, 0, 128);
1832 stipplerect (ctm, p1, p2, p3, p4, texcoords, vertices);
1835 glDisable (GL_BLEND);
1836 glDisable (GL_TEXTURE_1D);
1839 static int compareslinks (const void *l, const void *r)
1841 struct slink const *ls = l;
1842 struct slink const *rs = r;
1843 if (ls->bbox.y0 == rs->bbox.y0) {
1844 return ls->bbox.x0 - rs->bbox.x0;
1846 return ls->bbox.y0 - rs->bbox.y0;
1849 static void droptext (struct page *page)
1851 if (page->text) {
1852 fz_drop_stext_page (state.ctx, page->text);
1853 page->fmark = NULL;
1854 page->lmark = NULL;
1855 page->text = NULL;
1859 static void dropannots (struct page *page)
1861 if (page->annots) {
1862 free (page->annots);
1863 page->annots = NULL;
1864 page->annotcount = 0;
1868 static void ensureannots (struct page *page)
1870 int i, count = 0;
1871 pdf_annot *annot;
1872 pdf_document *pdf;
1873 pdf_page *pdfpage;
1875 pdf = pdf_specifics (state.ctx, state.doc);
1876 if (!pdf) {
1877 return;
1880 pdfpage = pdf_page_from_fz_page (state.ctx, page->fzpage);
1881 if (state.gen != page->agen) {
1882 dropannots (page);
1883 page->agen = state.gen;
1885 if (page->annots) {
1886 return;
1889 for (annot = pdf_first_annot (state.ctx, pdfpage);
1890 annot;
1891 annot = pdf_next_annot (state.ctx, annot)) {
1892 count++;
1895 if (count > 0) {
1896 page->annotcount = count;
1897 page->annots = calloc (count, sizeof (*page->annots));
1898 if (!page->annots) {
1899 err (1, errno, "calloc annots %d", count);
1902 for (annot = pdf_first_annot (state.ctx, pdfpage), i = 0;
1903 annot;
1904 annot = pdf_next_annot (state.ctx, annot), i++) {
1905 fz_rect rect;
1907 rect = pdf_bound_annot (state.ctx, annot);
1908 page->annots[i].annot = annot;
1909 page->annots[i].bbox = fz_round_rect (rect);
1914 static void dropslinks (struct page *page)
1916 if (page->slinks) {
1917 free (page->slinks);
1918 page->slinks = NULL;
1919 page->slinkcount = 0;
1921 if (page->links) {
1922 fz_drop_link (state.ctx, page->links);
1923 page->links = NULL;
1927 static void ensureslinks (struct page *page)
1929 fz_matrix ctm;
1930 int i, count;
1931 size_t slinksize = sizeof (*page->slinks);
1932 fz_link *link;
1934 ensureannots (page);
1935 if (state.gen != page->sgen) {
1936 dropslinks (page);
1937 page->sgen = state.gen;
1939 if (page->slinks) {
1940 return;
1943 ensurelinks (page);
1944 ctm = pagectm (page);
1946 count = page->annotcount;
1947 for (link = page->links; link; link = link->next) {
1948 count++;
1950 if (count > 0) {
1951 int j;
1953 page->slinkcount = count;
1954 page->slinks = calloc (count, slinksize);
1955 if (!page->slinks) {
1956 err (1, errno, "calloc slinks %d", count);
1959 for (i = 0, link = page->links; link; ++i, link = link->next) {
1960 fz_rect rect;
1962 rect = link->rect;
1963 rect = fz_transform_rect (rect, ctm);
1964 page->slinks[i].tag = SLINK;
1965 page->slinks[i].u.link = link;
1966 page->slinks[i].bbox = fz_round_rect (rect);
1968 for (j = 0; j < page->annotcount; ++j, ++i) {
1969 fz_rect rect;
1970 rect = pdf_bound_annot (state.ctx, page->annots[j].annot);
1971 rect = fz_transform_rect (rect, ctm);
1972 page->slinks[i].bbox = fz_round_rect (rect);
1974 page->slinks[i].tag = SANNOT;
1975 page->slinks[i].u.annot = page->annots[j].annot;
1977 qsort (page->slinks, count, slinksize, compareslinks);
1981 static void highlightslinks (struct page *page, int xoff, int yoff,
1982 int noff, const char *targ, unsigned int tlen,
1983 const char *chars, unsigned int clen, int hfsize)
1985 char buf[40];
1986 struct slink *slink;
1987 float x0, y0, x1, y1, w;
1989 ensureslinks (page);
1990 glColor3ub (0xc3, 0xb0, 0x91);
1991 for (int i = 0; i < page->slinkcount; ++i) {
1992 fmt_linkn (buf, chars, clen, i + noff);
1993 if (!tlen || !strncmp (targ, buf, tlen)) {
1994 slink = &page->slinks[i];
1996 x0 = slink->bbox.x0 + xoff - 5;
1997 y1 = slink->bbox.y0 + yoff - 5;
1998 y0 = y1 + 10 + hfsize;
1999 w = measure_string (state.face, hfsize, buf);
2000 x1 = x0 + w + 10;
2001 recti ((int) x0, (int) y0, (int) x1, (int) y1);
2005 glEnable (GL_BLEND);
2006 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2007 glEnable (GL_TEXTURE_2D);
2008 glColor3ub (0, 0, 0);
2009 for (int i = 0; i < page->slinkcount; ++i) {
2010 fmt_linkn (buf, chars, clen, i + noff);
2011 if (!tlen || !strncmp (targ, buf, tlen)) {
2012 slink = &page->slinks[i];
2014 x0 = slink->bbox.x0 + xoff;
2015 y0 = slink->bbox.y0 + yoff + hfsize;
2016 draw_string (state.face, hfsize, x0, y0, buf);
2019 glDisable (GL_TEXTURE_2D);
2020 glDisable (GL_BLEND);
2023 static void uploadslice (struct tile *tile, struct slice *slice)
2025 int offset;
2026 struct slice *slice1;
2027 unsigned char *texdata;
2029 offset = 0;
2030 for (slice1 = tile->slices; slice != slice1; slice1++) {
2031 offset += slice1->h * tile->w * tile->pixmap->n;
2033 if (slice->texindex != -1 && slice->texindex < state.tex.count
2034 && state.tex.owners[slice->texindex].slice == slice) {
2035 glBindTexture (TEXT_TYPE, state.tex.ids[slice->texindex]);
2037 else {
2038 int subimage = 0;
2039 int texindex = state.tex.index++ % state.tex.count;
2041 if (state.tex.owners[texindex].w == tile->w) {
2042 if (state.tex.owners[texindex].h >= slice->h) {
2043 subimage = 1;
2045 else {
2046 state.tex.owners[texindex].h = slice->h;
2049 else {
2050 state.tex.owners[texindex].h = slice->h;
2053 state.tex.owners[texindex].w = tile->w;
2054 state.tex.owners[texindex].slice = slice;
2055 slice->texindex = texindex;
2057 glBindTexture (TEXT_TYPE, state.tex.ids[texindex]);
2058 #if TEXT_TYPE == GL_TEXTURE_2D
2059 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2060 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2061 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2062 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2063 #endif
2064 texdata = tile->pixmap->samples;
2065 if (subimage) {
2066 glTexSubImage2D (TEXT_TYPE, 0, 0, 0, tile->w, slice->h,
2067 state.tex.form, state.tex.ty, texdata+offset);
2069 else {
2070 glTexImage2D (TEXT_TYPE, 0, state.tex.iform, tile->w, slice->h,
2071 0, state.tex.form, state.tex.ty, texdata+offset);
2076 ML0 (begintiles (void))
2078 glEnable (TEXT_TYPE);
2079 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2080 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2083 ML0 (endtiles (void))
2085 glDisable (TEXT_TYPE);
2088 ML0 (drawtile (value args_v, value ptr_v))
2090 CAMLparam2 (args_v, ptr_v);
2091 int dispx = Int_val (Field (args_v, 0));
2092 int dispy = Int_val (Field (args_v, 1));
2093 int dispw = Int_val (Field (args_v, 2));
2094 int disph = Int_val (Field (args_v, 3));
2095 int tilex = Int_val (Field (args_v, 4));
2096 int tiley = Int_val (Field (args_v, 5));
2097 struct tile *tile = parse_pointer (__func__, String_val (ptr_v));
2098 int slicey, firstslice;
2099 struct slice *slice;
2100 GLfloat *texcoords = state.texcoords;
2101 GLfloat *vertices = state.vertices;
2103 firstslice = tiley / tile->sliceheight;
2104 slice = &tile->slices[firstslice];
2105 slicey = tiley % tile->sliceheight;
2107 while (disph > 0) {
2108 int dh;
2110 dh = slice->h - slicey;
2111 dh = fz_mini (disph, dh);
2112 uploadslice (tile, slice);
2114 texcoords[0] = tilex; texcoords[1] = slicey;
2115 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2116 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2117 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2119 vertices[0] = dispx; vertices[1] = dispy;
2120 vertices[2] = dispx+dispw; vertices[3] = dispy;
2121 vertices[4] = dispx; vertices[5] = dispy+dh;
2122 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2124 #if TEXT_TYPE == GL_TEXTURE_2D
2125 for (int i = 0; i < 8; ++i) {
2126 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2128 #endif
2130 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2131 dispy += dh;
2132 disph -= dh;
2133 slice++;
2134 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2135 slicey = 0;
2137 CAMLreturn0;
2140 ML (postprocess (value ptr_v, value hlmask_v,
2141 value xoff_v, value yoff_v, value li_v))
2143 CAMLparam5 (ptr_v, hlmask_v, xoff_v, yoff_v, li_v);
2144 int xoff = Int_val (xoff_v);
2145 int yoff = Int_val (yoff_v);
2146 int noff = Int_val (Field (li_v, 0));
2147 const char *targ = String_val (Field (li_v, 1));
2148 mlsize_t tlen = caml_string_length (Field (li_v, 1));
2149 int hfsize = Int_val (Field (li_v, 2));
2150 const char *chars = String_val (Field (li_v, 3));
2151 mlsize_t clen = caml_string_length (Field (li_v, 3));
2152 int hlmask = Int_val (hlmask_v);
2153 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2155 if (!page->fzpage) {
2156 /* deal with loadpage failed pages */
2157 goto done;
2160 if (trylock (__func__)) {
2161 noff = -1;
2162 goto done;
2165 ensureannots (page);
2166 if (hlmask & 1) {
2167 highlightlinks (page, xoff, yoff);
2169 if (hlmask & 2) {
2170 highlightslinks (page, xoff, yoff, noff, targ, STTI (tlen),
2171 chars, STTI (clen), hfsize);
2172 noff = page->slinkcount;
2174 if (page->tgen == state.gen) {
2175 showsel (page, xoff, yoff);
2177 unlock (__func__);
2179 done:
2180 CAMLreturn (Val_int (noff));
2183 static struct annot *getannot (struct page *page, int x, int y)
2185 fz_point p;
2186 fz_matrix ctm;
2187 const fz_matrix *tctm;
2188 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2190 if (!page->annots) {
2191 return NULL;
2194 if (pdf) {
2195 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2196 tctm = &state.pagedims[page->pdimno].tctm;
2198 else {
2199 tctm = &fz_identity;
2202 p.x = x;
2203 p.y = y;
2205 ctm = fz_concat (*tctm, state.pagedims[page->pdimno].ctm);
2206 ctm = fz_invert_matrix (ctm);
2207 p = fz_transform_point (p, ctm);
2209 if (pdf) {
2210 for (int i = 0; i < page->annotcount; ++i) {
2211 struct annot *a = &page->annots[i];
2212 if (fz_is_point_inside_rect (p, pdf_bound_annot (state.ctx,
2213 a->annot))) {
2214 return a;
2218 return NULL;
2221 static fz_link *getlink (struct page *page, int x, int y)
2223 fz_link *link;
2224 fz_point p = { .x = x, .y = y };
2226 ensureslinks (page);
2227 p = fz_transform_point (p, fz_invert_matrix (pagectm (page)));
2229 for (link = page->links; link; link = link->next) {
2230 if (fz_is_point_inside_rect (p, link->rect)) {
2231 return link;
2234 return NULL;
2237 static void ensuretext (struct page *page)
2239 if (state.gen != page->tgen) {
2240 droptext (page);
2241 page->tgen = state.gen;
2243 if (!page->text) {
2244 fz_device *tdev;
2246 page->text = fz_new_stext_page (state.ctx,
2247 state.pagedims[page->pdimno].mediabox);
2248 tdev = fz_new_stext_device (state.ctx, page->text, 0);
2249 fz_run_display_list (state.ctx, page->dlist,
2250 tdev, pagectm (page), fz_infinite_rect, NULL);
2251 fz_close_device (state.ctx, tdev);
2252 fz_drop_device (state.ctx, tdev);
2256 ML (find_page_with_links (value start_page_v, value dir_v))
2258 CAMLparam2 (start_page_v, dir_v);
2259 CAMLlocal1 (ret_v);
2260 int i, dir = Int_val (dir_v);
2261 int start_page = Int_val (start_page_v);
2262 int end_page = dir > 0 ? state.pagecount : -1;
2263 pdf_document *pdf;
2265 fz_var (end_page);
2266 ret_v = Val_int (0);
2267 lock (__func__);
2268 pdf = pdf_specifics (state.ctx, state.doc);
2269 for (i = start_page + dir; i != end_page; i += dir) {
2270 int found;
2272 fz_var (found);
2273 if (pdf) {
2274 pdf_page *page = NULL;
2276 fz_var (page);
2277 fz_try (state.ctx) {
2278 page = pdf_load_page (state.ctx, pdf, i);
2279 found = !!page->links || !!page->annots;
2281 fz_catch (state.ctx) {
2282 found = 0;
2284 fz_drop_page (state.ctx, &page->super);
2286 else {
2287 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2288 fz_link *link = fz_load_links (state.ctx, page);
2289 found = !!link;
2290 fz_drop_link (state.ctx, link);
2291 fz_drop_page (state.ctx, page);
2294 if (found) {
2295 ret_v = caml_alloc_small (1, 1);
2296 Field (ret_v, 0) = Val_int (i);
2297 goto unlock;
2300 unlock:
2301 unlock (__func__);
2302 CAMLreturn (ret_v);
2305 ML (findlink (value ptr_v, value dir_v))
2307 CAMLparam2 (ptr_v, dir_v);
2308 CAMLlocal2 (ret_v, pos_v);
2309 struct page *page;
2310 int dirtag, i, slinkindex;
2311 struct slink *found = NULL ,*slink;
2313 page = parse_pointer (__func__, String_val (ptr_v));
2314 ret_v = Val_int (0);
2315 lock (__func__);
2316 ensureslinks (page);
2318 if (Is_block (dir_v)) {
2319 dirtag = Tag_val (dir_v);
2320 switch (dirtag) {
2321 case dir_first_visible:
2323 int x0, y0, dir, first_index, last_index;
2325 pos_v = Field (dir_v, 0);
2326 x0 = Int_val (Field (pos_v, 0));
2327 y0 = Int_val (Field (pos_v, 1));
2328 dir = Int_val (Field (pos_v, 2));
2330 if (dir >= 0) {
2331 dir = 1;
2332 first_index = 0;
2333 last_index = page->slinkcount;
2335 else {
2336 first_index = page->slinkcount - 1;
2337 last_index = -1;
2340 for (i = first_index; i != last_index; i += dir) {
2341 slink = &page->slinks[i];
2342 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2343 found = slink;
2344 break;
2348 break;
2350 case dir_left:
2351 slinkindex = Int_val (Field (dir_v, 0));
2352 found = &page->slinks[slinkindex];
2353 for (i = slinkindex - 1; i >= 0; --i) {
2354 slink = &page->slinks[i];
2355 if (slink->bbox.x0 < found->bbox.x0) {
2356 found = slink;
2357 break;
2360 break;
2362 case dir_right:
2363 slinkindex = Int_val (Field (dir_v, 0));
2364 found = &page->slinks[slinkindex];
2365 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2366 slink = &page->slinks[i];
2367 if (slink->bbox.x0 > found->bbox.x0) {
2368 found = slink;
2369 break;
2372 break;
2374 case dir_down:
2375 slinkindex = Int_val (Field (dir_v, 0));
2376 found = &page->slinks[slinkindex];
2377 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2378 slink = &page->slinks[i];
2379 if (slink->bbox.y0 >= found->bbox.y0) {
2380 found = slink;
2381 break;
2384 break;
2386 case dir_up:
2387 slinkindex = Int_val (Field (dir_v, 0));
2388 found = &page->slinks[slinkindex];
2389 for (i = slinkindex - 1; i >= 0; --i) {
2390 slink = &page->slinks[i];
2391 if (slink->bbox.y0 <= found->bbox.y0) {
2392 found = slink;
2393 break;
2396 break;
2399 else {
2400 dirtag = Int_val (dir_v);
2401 switch (dirtag) {
2402 case dir_first:
2403 found = page->slinks;
2404 break;
2406 case dir_last:
2407 if (page->slinks) {
2408 found = page->slinks + (page->slinkcount - 1);
2410 break;
2413 if (found) {
2414 ret_v = caml_alloc_small (2, 1);
2415 Field (ret_v, 0) = Val_int (found - page->slinks);
2418 unlock (__func__);
2419 CAMLreturn (ret_v);
2422 ML (getlink (value ptr_v, value n_v))
2424 CAMLparam2 (ptr_v, n_v);
2425 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2426 int n = Int_val (n_v);
2427 fz_link *link;
2428 struct page *page;
2429 struct slink *slink;
2431 ret_v = Val_int (0);
2432 page = parse_pointer (__func__, String_val (ptr_v));
2434 lock (__func__);
2435 ensureslinks (page);
2436 if (!page->slinkcount || n > page->slinkcount) goto unlock;
2437 slink = &page->slinks[n];
2438 if (slink->tag == SLINK) {
2439 link = slink->u.link;
2440 str_v = caml_copy_string (link->uri);
2441 ret_v = caml_alloc_small (1, uuri);
2442 Field (ret_v, 0) = str_v;
2444 else {
2445 int ty = pdf_annot_type (state.ctx, slink->u.annot)
2446 == PDF_ANNOT_FILE_ATTACHMENT ? ufileannot : utextannot;
2448 ret_v = caml_alloc_small (1, ty);
2449 tup_v = caml_alloc_tuple (2);
2450 Field (ret_v, 0) = tup_v;
2451 Field (tup_v, 0) = ptr_v;
2452 Field (tup_v, 1) = n_v;
2454 unlock:
2455 unlock (__func__);
2456 CAMLreturn (ret_v);
2459 ML (getlinkn (value ptr_v, value c_v, value n_v, value noff_v))
2461 CAMLparam4 (ptr_v, c_v, n_v, noff_v);
2462 CAMLlocal1 (ret_v);
2463 char buf[40];
2464 struct page *page;
2465 const char *c = String_val (c_v);
2466 const char *n = String_val (n_v);
2467 mlsize_t clen = caml_string_length (c_v);
2468 page = parse_pointer (__func__, String_val (ptr_v));
2470 lock (__func__);
2471 ensureslinks (page);
2473 ret_v = Val_int (-page->slinkcount);
2474 for (int i = 0; i < page->slinkcount; ++i) {
2475 fmt_linkn (buf, c, STTI (clen), i - Int_val (noff_v));
2476 if (!strncmp (buf, n, clen)) {
2477 ret_v = Val_int (i+1);
2478 break;
2482 unlock (__func__);
2483 CAMLreturn (ret_v);
2486 ML (gettextannot (value ptr_v, value n_v))
2488 CAMLparam2 (ptr_v, n_v);
2489 CAMLlocal1 (ret_v);
2490 pdf_document *pdf;
2491 const char *contents = "";
2493 lock (__func__);
2494 pdf = pdf_specifics (state.ctx, state.doc);
2495 if (pdf) {
2496 struct page *page;
2497 pdf_annot *annot;
2498 struct slink *slink;
2500 page = parse_pointer (__func__, String_val (ptr_v));
2501 slink = &page->slinks[Int_val (n_v)];
2502 annot = slink->u.annot;
2503 contents = pdf_annot_contents (state.ctx, annot);
2505 unlock (__func__);
2506 ret_v = caml_copy_string (contents);
2507 CAMLreturn (ret_v);
2510 ML (getfileannot (value ptr_v, value n_v))
2512 CAMLparam2 (ptr_v, n_v);
2513 CAMLlocal1 (ret_v);
2515 lock (__func__);
2517 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2518 struct slink *slink = &page->slinks[Int_val (n_v)];
2519 pdf_obj *fs = pdf_dict_get (state.ctx,
2520 pdf_annot_obj (state.ctx, slink->u.annot),
2521 PDF_NAME (FS));
2522 ret_v = caml_copy_string (pdf_embedded_file_name (state.ctx, fs));
2524 unlock (__func__);
2525 CAMLreturn (ret_v);
2528 ML0 (savefileannot (value ptr_v, value n_v, value path_v))
2530 CAMLparam3 (ptr_v, n_v, path_v);
2531 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2532 const char *path = String_val (path_v);
2534 lock (__func__);
2535 struct slink *slink = &page->slinks[Int_val (n_v)];
2536 fz_try (state.ctx) {
2537 pdf_obj *fs = pdf_dict_get (state.ctx,
2538 pdf_annot_obj (state.ctx, slink->u.annot),
2539 PDF_NAME (FS));
2540 fz_buffer *buf = pdf_load_embedded_file (state.ctx, fs);
2541 fz_save_buffer (state.ctx, buf, path);
2542 fz_drop_buffer (state.ctx, buf);
2543 printd ("progress 1 saved '%s'", path);
2545 fz_catch (state.ctx) {
2546 printd ("emsg saving '%s': %s", path, fz_caught_message (state.ctx));
2548 unlock (__func__);
2551 ML (getlinkrect (value ptr_v, value n_v))
2553 CAMLparam2 (ptr_v, n_v);
2554 CAMLlocal1 (ret_v);
2555 struct page *page;
2556 struct slink *slink;
2558 page = parse_pointer (__func__, String_val (ptr_v));
2559 ret_v = caml_alloc_tuple (4);
2560 lock (__func__);
2561 ensureslinks (page);
2563 slink = &page->slinks[Int_val (n_v)];
2564 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2565 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2566 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2567 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2568 unlock (__func__);
2569 CAMLreturn (ret_v);
2572 ML (whatsunder (value ptr_v, value x_v, value y_v))
2574 CAMLparam3 (ptr_v, x_v, y_v);
2575 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2576 fz_link *link;
2577 struct annot *annot;
2578 struct page *page;
2579 const char *ptr = String_val (ptr_v);
2580 int x = Int_val (x_v), y = Int_val (y_v);
2581 struct pagedim *pdim;
2583 ret_v = Val_int (0);
2584 if (trylock (__func__)) {
2585 goto done;
2588 page = parse_pointer (__func__, ptr);
2589 pdim = &state.pagedims[page->pdimno];
2590 x += pdim->bounds.x0;
2591 y += pdim->bounds.y0;
2593 annot = getannot (page, x, y);
2594 if (annot) {
2595 int i, n = -1, ty;
2597 ensureslinks (page);
2598 for (i = 0; i < page->slinkcount; ++i) {
2599 if (page->slinks[i].tag == SANNOT
2600 && page->slinks[i].u.annot == annot->annot) {
2601 n = i;
2602 break;
2605 ty = pdf_annot_type (state.ctx, annot->annot)
2606 == PDF_ANNOT_FILE_ATTACHMENT ? ufileannot : utextannot;
2608 ret_v = caml_alloc_small (1, ty);
2609 tup_v = caml_alloc_tuple (2);
2610 Field (ret_v, 0) = tup_v;
2611 Field (tup_v, 0) = ptr_v;
2612 Field (tup_v, 1) = Int_val (n);
2613 goto unlock;
2616 link = getlink (page, x, y);
2617 if (link) {
2618 str_v = caml_copy_string (link->uri);
2619 ret_v = caml_alloc_small (1, uuri);
2620 Field (ret_v, 0) = str_v;
2622 else {
2623 fz_stext_block *block;
2624 fz_point p = { .x = x, .y = y };
2626 ensuretext (page);
2628 for (block = page->text->first_block; block; block = block->next) {
2629 fz_stext_line *line;
2631 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2632 continue;
2634 if (!fz_is_point_inside_rect (p, block->bbox)) {
2635 continue;
2638 for (line = block->u.t.first_line; line; line = line->next) {
2639 fz_stext_char *ch;
2641 if (!fz_is_point_inside_rect (p, line->bbox)) {
2642 continue;
2645 for (ch = line->first_char; ch; ch = ch->next) {
2646 if (!fz_is_point_inside_quad (p, ch->quad)) {
2647 const char *n2 = fz_font_name (state.ctx, ch->font);
2648 FT_FaceRec *face = fz_font_ft_face (state.ctx,
2649 ch->font);
2651 if (!n2) {
2652 n2 = "<unknown font>";
2655 if (face && face->family_name) {
2656 char *s;
2657 char *n1 = face->family_name;
2658 size_t l1 = strlen (n1);
2659 size_t l2 = strlen (n2);
2661 if (l1 != l2 || memcmp (n1, n2, l1)) {
2662 s = malloc (l1 + l2 + 2);
2663 if (s) {
2664 memcpy (s, n2, l2);
2665 s[l2] = '=';
2666 memcpy (s + l2 + 1, n1, l1 + 1);
2667 str_v = caml_copy_string (s);
2668 free (s);
2672 if (str_v == Val_unit) {
2673 str_v = caml_copy_string (n2);
2675 ret_v = caml_alloc_small (1, utext);
2676 Field (ret_v, 0) = str_v;
2677 goto unlock;
2683 unlock:
2684 unlock (__func__);
2686 done:
2687 CAMLreturn (ret_v);
2690 ML0 (clearmark (value ptr_v))
2692 CAMLparam1 (ptr_v);
2693 struct page *page;
2695 if (trylock (__func__)) {
2696 goto done;
2699 page = parse_pointer (__func__, String_val (ptr_v));
2700 page->fmark = NULL;
2701 page->lmark = NULL;
2703 unlock (__func__);
2704 done:
2705 CAMLreturn0;
2708 static int uninteresting (int c)
2710 return isspace (c) || ispunct (c);
2713 ML (markunder (value ptr_v, value x_v, value y_v, value mark_v))
2715 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
2716 CAMLlocal1 (ret_v);
2717 struct page *page;
2718 fz_stext_line *line;
2719 fz_stext_block *block;
2720 struct pagedim *pdim;
2721 int mark = Int_val (mark_v);
2722 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2724 ret_v = Val_bool (0);
2725 if (trylock (__func__)) {
2726 goto done;
2729 page = parse_pointer (__func__, String_val (ptr_v));
2730 pdim = &state.pagedims[page->pdimno];
2732 ensuretext (page);
2734 if (mark == mark_page) {
2735 page->fmark = page->text->first_block->u.t.first_line->first_char;
2736 page->lmark = page->text->last_block->u.t.last_line->last_char;
2737 ret_v = Val_bool (1);
2738 goto unlock;
2741 p.x += pdim->bounds.x0;
2742 p.y += pdim->bounds.y0;
2744 for (block = page->text->first_block; block; block = block->next) {
2745 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2746 continue;
2748 if (!fz_is_point_inside_rect (p, block->bbox)) {
2749 continue;
2752 if (mark == mark_block) {
2753 page->fmark = block->u.t.first_line->first_char;
2754 page->lmark = block->u.t.last_line->last_char;
2755 ret_v = Val_bool (1);
2756 goto unlock;
2759 for (line = block->u.t.first_line; line; line = line->next) {
2760 fz_stext_char *ch;
2762 if (!fz_is_point_inside_rect (p, line->bbox)) {
2763 continue;
2766 if (mark == mark_line) {
2767 page->fmark = line->first_char;
2768 page->lmark = line->last_char;
2769 ret_v = Val_bool (1);
2770 goto unlock;
2773 for (ch = line->first_char; ch; ch = ch->next) {
2774 fz_stext_char *ch2, *first = NULL, *last = NULL;
2776 if (fz_is_point_inside_quad (p, ch->quad)) {
2777 for (ch2 = line->first_char; ch2 != ch; ch2 = ch2->next) {
2778 if (uninteresting (ch2->c)) {
2779 first = NULL;
2781 else {
2782 if (!first) {
2783 first = ch2;
2787 for (ch2 = ch; ch2; ch2 = ch2->next) {
2788 if (uninteresting (ch2->c)) {
2789 break;
2791 last = ch2;
2794 page->fmark = first;
2795 page->lmark = last;
2796 ret_v = Val_bool (1);
2797 goto unlock;
2802 unlock:
2803 if (!Bool_val (ret_v)) {
2804 page->fmark = NULL;
2805 page->lmark = NULL;
2807 unlock (__func__);
2809 done:
2810 CAMLreturn (ret_v);
2813 ML (rectofblock (value ptr_v, value x_v, value y_v))
2815 CAMLparam3 (ptr_v, x_v, y_v);
2816 CAMLlocal2 (ret_v, res_v);
2817 fz_rect *b = NULL;
2818 struct page *page;
2819 struct pagedim *pdim;
2820 fz_stext_block *block;
2821 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2823 ret_v = Val_int (0);
2824 if (trylock (__func__)) {
2825 goto done;
2828 page = parse_pointer (__func__, String_val (ptr_v));
2829 pdim = &state.pagedims[page->pdimno];
2830 p.x += pdim->bounds.x0;
2831 p.y += pdim->bounds.y0;
2833 ensuretext (page);
2835 for (block = page->text->first_block; block; block = block->next) {
2836 switch (block->type) {
2837 case FZ_STEXT_BLOCK_TEXT:
2838 b = &block->bbox;
2839 break;
2841 case FZ_STEXT_BLOCK_IMAGE:
2842 b = &block->bbox;
2843 break;
2845 default:
2846 continue;
2849 if (fz_is_point_inside_rect (p, *b)) {
2850 break;
2852 b = NULL;
2854 if (b) {
2855 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
2856 ret_v = caml_alloc_small (1, 1);
2857 Store_double_field (res_v, 0, (double) b->x0);
2858 Store_double_field (res_v, 1, (double) b->x1);
2859 Store_double_field (res_v, 2, (double) b->y0);
2860 Store_double_field (res_v, 3, (double) b->y1);
2861 Field (ret_v, 0) = res_v;
2863 unlock (__func__);
2865 done:
2866 CAMLreturn (ret_v);
2869 ML0 (seltext (value ptr_v, value rect_v))
2871 CAMLparam2 (ptr_v, rect_v);
2872 struct page *page;
2873 struct pagedim *pdim;
2874 int x0, x1, y0, y1;
2875 fz_stext_char *ch;
2876 fz_stext_line *line;
2877 fz_stext_block *block;
2878 fz_stext_char *fc, *lc;
2880 if (trylock (__func__)) {
2881 goto done;
2884 page = parse_pointer (__func__, String_val (ptr_v));
2885 ensuretext (page);
2887 pdim = &state.pagedims[page->pdimno];
2888 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
2889 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
2890 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
2891 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
2893 if (y0 > y1) {
2894 int t = y0;
2895 y0 = y1;
2896 y1 = t;
2897 x0 = x1;
2898 x1 = t;
2901 fc = page->fmark;
2902 lc = page->lmark;
2904 for (block = page->text->first_block; block; block = block->next) {
2905 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2906 continue;
2909 for (line = block->u.t.first_line; line; line = line->next) {
2910 for (ch = line->first_char; ch; ch = ch->next) {
2911 fz_point p0 = { .x = x0, .y = y0 }, p1 = { .x = x1, .y = y1 };
2912 if (fz_is_point_inside_quad (p0, ch->quad)) {
2913 fc = ch;
2915 if (fz_is_point_inside_quad (p1, ch->quad)) {
2916 lc = ch;
2921 if (x1 < x0 && fc == lc) {
2922 fz_stext_char *t;
2924 t = fc;
2925 fc = lc;
2926 lc = t;
2929 page->fmark = fc;
2930 page->lmark = lc;
2932 unlock (__func__);
2934 done:
2935 CAMLreturn0;
2938 static int pipechar (FILE *f, fz_stext_char *ch)
2940 char buf[4];
2941 int len;
2942 size_t ret;
2944 len = fz_runetochar (buf, ch->c);
2945 ret = fwrite (buf, len, 1, f);
2946 if (ret != 1) {
2947 printd ("emsg failed to fwrite %d bytes ret=%zu: %d(%s)",
2948 len, ret, errno, strerror (errno));
2949 return -1;
2951 return 0;
2954 ML (spawn (value command_v, value fds_v))
2956 CAMLparam2 (command_v, fds_v);
2957 CAMLlocal2 (l_v, tup_v);
2958 int ret, ret1;
2959 pid_t pid = (pid_t) -1;
2960 char *msg = NULL;
2961 value earg_v = Nothing;
2962 posix_spawnattr_t attr;
2963 posix_spawn_file_actions_t fa;
2964 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
2966 argv[2] = &Byte (command_v, 0);
2967 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
2968 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
2971 if ((ret = posix_spawnattr_init (&attr)) != 0) {
2972 msg = "posix_spawnattr_init";
2973 goto fail1;
2976 #ifdef POSIX_SPAWN_USEVFORK
2977 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
2978 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
2979 goto fail;
2981 #endif
2983 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
2984 int fd1, fd2;
2986 tup_v = Field (l_v, 0);
2987 fd1 = Int_val (Field (tup_v, 0));
2988 fd2 = Int_val (Field (tup_v, 1));
2989 if (fd2 < 0) {
2990 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
2991 msg = "posix_spawn_file_actions_addclose";
2992 earg_v = tup_v;
2993 goto fail;
2996 else {
2997 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
2998 msg = "posix_spawn_file_actions_adddup2";
2999 earg_v = tup_v;
3000 goto fail;
3005 extern char **environ;
3006 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3007 msg = "posix_spawn";
3008 goto fail;
3011 fail:
3012 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3013 printd ("emsg posix_spawnattr_destroy: %d(%s)", ret1, strerror (ret1));
3016 fail1:
3017 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3018 printd ("emsg posix_spawn_file_actions_destroy: %d(%s)",
3019 ret1, strerror (ret1));
3022 if (msg) {
3023 unix_error (ret, msg, earg_v);
3026 CAMLreturn (Val_int (pid));
3029 ML (hassel (value ptr_v))
3031 CAMLparam1 (ptr_v);
3032 CAMLlocal1 (ret_v);
3033 struct page *page;
3035 ret_v = Val_bool (0);
3036 if (trylock (__func__)) {
3037 goto done;
3040 page = parse_pointer (__func__, String_val (ptr_v));
3041 ret_v = Val_bool (page->fmark && page->lmark);
3042 unlock (__func__);
3043 done:
3044 CAMLreturn (ret_v);
3047 ML0 (copysel (value fd_v, value ptr_v))
3049 CAMLparam2 (fd_v, ptr_v);
3050 FILE *f;
3051 int seen = 0;
3052 struct page *page;
3053 fz_stext_line *line;
3054 fz_stext_block *block;
3055 int fd = Int_val (fd_v);
3057 if (trylock (__func__)) {
3058 goto done;
3061 page = parse_pointer (__func__, String_val (ptr_v));
3063 if (!page->fmark || !page->lmark) {
3064 printd ("emsg nothing to copy on page %d", page->pageno);
3065 goto unlock;
3068 f = fdopen (fd, "w");
3069 if (!f) {
3070 printd ("emsg failed to fdopen sel pipe (from fd %d): %d(%s)",
3071 fd, errno, strerror (errno));
3072 f = stdout;
3075 for (block = page->text->first_block; block; block = block->next) {
3076 if (block->type != FZ_STEXT_BLOCK_TEXT) {
3077 continue;
3080 for (line = block->u.t.first_line; line; line = line->next) {
3081 fz_stext_char *ch;
3082 for (ch = line->first_char; ch; ch = ch->next) {
3083 if (seen || ch == page->fmark) {
3084 do {
3085 if (pipechar (f, ch)) {
3086 goto close;
3088 if (ch == page->lmark) {
3089 goto close;
3091 } while ((ch = ch->next));
3092 seen = 1;
3093 break;
3096 if (seen) {
3097 fputc ('\n', f);
3101 close:
3102 if (f != stdout) {
3103 int ret = fclose (f);
3104 fd = -1;
3105 if (ret == -1) {
3106 if (errno != ECHILD) {
3107 printd ("emsg failed to close sel pipe: %d(%s)",
3108 errno, strerror (errno));
3112 unlock:
3113 unlock (__func__);
3115 done:
3116 if (fd >= 0) {
3117 if (close (fd)) {
3118 printd ("emsg failed to close sel pipe: %d(%s)",
3119 errno, strerror (errno));
3122 CAMLreturn0;
3125 ML (getpdimrect (value pagedimno_v))
3127 CAMLparam1 (pagedimno_v);
3128 CAMLlocal1 (ret_v);
3129 int pagedimno = Int_val (pagedimno_v);
3130 fz_rect box;
3132 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3133 if (trylock (__func__)) {
3134 box = fz_empty_rect;
3136 else {
3137 box = state.pagedims[pagedimno].mediabox;
3138 unlock (__func__);
3141 Store_double_field (ret_v, 0, (double) box.x0);
3142 Store_double_field (ret_v, 1, (double) box.x1);
3143 Store_double_field (ret_v, 2, (double) box.y0);
3144 Store_double_field (ret_v, 3, (double) box.y1);
3146 CAMLreturn (ret_v);
3149 ML (zoom_for_height (value winw_v, value winh_v, value dw_v, value cols_v))
3151 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3152 CAMLlocal1 (ret_v);
3153 int i;
3154 float zoom = -1.;
3155 float maxh = 0.0;
3156 struct pagedim *p;
3157 float winw = Int_val (winw_v);
3158 float winh = Int_val (winh_v);
3159 float dw = Int_val (dw_v);
3160 float cols = Int_val (cols_v);
3161 float pw = 1.0, ph = 1.0;
3163 if (trylock (__func__)) {
3164 goto done;
3167 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3168 float w = p->pagebox.x1 / cols;
3169 float h = p->pagebox.y1;
3170 if (h > maxh) {
3171 maxh = h;
3172 ph = h;
3173 if (state.fitmodel != FitProportional) {
3174 pw = w;
3177 if ((state.fitmodel == FitProportional) && w > pw) {
3178 pw = w;
3182 zoom = (((winh / ph) * pw) + dw) / winw;
3183 unlock (__func__);
3184 done:
3185 ret_v = caml_copy_double ((double) zoom);
3186 CAMLreturn (ret_v);
3189 ML (getmaxw (value unit_v))
3191 CAMLparam1 (unit_v);
3192 CAMLlocal1 (ret_v);
3193 int i;
3194 float maxw = -1.;
3195 struct pagedim *p;
3197 if (trylock (__func__)) {
3198 goto done;
3201 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3202 maxw = fz_max (maxw, p->pagebox.x1);
3205 unlock (__func__);
3206 done:
3207 ret_v = caml_copy_double ((double) maxw);
3208 CAMLreturn (ret_v);
3211 ML (draw_string (value pt_v, value x_v, value y_v, value string_v))
3213 CAMLparam4 (pt_v, x_v, y_v, string_v);
3214 CAMLlocal1 (ret_v);
3215 float w = draw_string (state.face,
3216 Int_val (pt_v), Int_val (x_v), Int_val (y_v),
3217 String_val (string_v));
3218 ret_v = caml_copy_double (w);
3219 CAMLreturn (ret_v);
3222 ML (measure_string (value pt_v, value string_v))
3224 CAMLparam2 (pt_v, string_v);
3225 CAMLlocal1 (ret_v);
3227 ret_v = caml_copy_double (
3228 measure_string (state.face, Int_val (pt_v), String_val (string_v))
3230 CAMLreturn (ret_v);
3233 ML (getpagebox (value ptr_v))
3235 CAMLparam1 (ptr_v);
3236 CAMLlocal1 (ret_v);
3237 fz_rect rect;
3238 fz_irect bbox;
3239 fz_device *dev;
3240 struct page *page = parse_pointer (__func__, String_val (ptr_v));
3242 ret_v = caml_alloc_tuple (4);
3243 dev = fz_new_bbox_device (state.ctx, &rect);
3245 fz_run_page (state.ctx, page->fzpage, dev, pagectm (page), NULL);
3247 fz_close_device (state.ctx, dev);
3248 fz_drop_device (state.ctx, dev);
3249 bbox = fz_round_rect (rect);
3250 Field (ret_v, 0) = Val_int (bbox.x0);
3251 Field (ret_v, 1) = Val_int (bbox.y0);
3252 Field (ret_v, 2) = Val_int (bbox.x1);
3253 Field (ret_v, 3) = Val_int (bbox.y1);
3255 CAMLreturn (ret_v);
3258 ML0 (setaalevel (value level_v))
3260 CAMLparam1 (level_v);
3262 state.aalevel = Int_val (level_v);
3263 CAMLreturn0;
3266 ML0 (setpapercolor (value rgba_v))
3268 CAMLparam1 (rgba_v);
3270 state.papercolor[0] = (float) Double_val (Field (rgba_v, 0));
3271 state.papercolor[1] = (float) Double_val (Field (rgba_v, 1));
3272 state.papercolor[2] = (float) Double_val (Field (rgba_v, 2));
3273 state.papercolor[3] = (float) Double_val (Field (rgba_v, 3));
3274 CAMLreturn0;
3277 value ml_keysymtoutf8 (value keysym_v);
3278 #ifndef MACOS
3279 value ml_keysymtoutf8 (value keysym_v)
3281 CAMLparam1 (keysym_v);
3282 CAMLlocal1 (str_v);
3283 unsigned short keysym = (unsigned short) Int_val (keysym_v);
3284 Rune rune;
3285 extern long keysym2ucs (unsigned short);
3286 int len;
3287 char buf[5];
3289 rune = (Rune) keysym2ucs (keysym);
3290 len = fz_runetochar (buf, rune);
3291 buf[len] = 0;
3292 str_v = caml_copy_string (buf);
3293 CAMLreturn (str_v);
3295 #else
3296 value ml_keysymtoutf8 (value keysym_v)
3298 CAMLparam1 (keysym_v);
3299 CAMLlocal1 (str_v);
3300 long ucs = Long_val (keysym_v);
3301 int len;
3302 char buf[5];
3304 len = fz_runetochar (buf, (int) ucs);
3305 buf[len] = 0;
3306 str_v = caml_copy_string (buf);
3307 CAMLreturn (str_v);
3309 #endif
3311 ML (unproject (value ptr_v, value x_v, value y_v))
3313 CAMLparam3 (ptr_v, x_v, y_v);
3314 CAMLlocal2 (ret_v, tup_v);
3315 struct page *page;
3316 int x = Int_val (x_v), y = Int_val (y_v);
3317 struct pagedim *pdim;
3318 fz_point p;
3320 page = parse_pointer (__func__, String_val (ptr_v));
3321 pdim = &state.pagedims[page->pdimno];
3323 ret_v = Val_int (0);
3324 if (trylock (__func__)) {
3325 goto done;
3328 p.x = x + pdim->bounds.x0;
3329 p.y = y + pdim->bounds.y0;
3331 p = fz_transform_point (p, fz_invert_matrix (fz_concat (pdim->tctm,
3332 pdim->ctm)));
3334 tup_v = caml_alloc_tuple (2);
3335 ret_v = caml_alloc_small (1, 1);
3336 Field (tup_v, 0) = Val_int (p.x);
3337 Field (tup_v, 1) = Val_int (p.y);
3338 Field (ret_v, 0) = tup_v;
3340 unlock (__func__);
3341 done:
3342 CAMLreturn (ret_v);
3345 ML (project (value ptr_v, value pageno_v, value pdimno_v, value x_v, value y_v))
3347 CAMLparam5 (ptr_v, pageno_v, pdimno_v, x_v, y_v);
3348 CAMLlocal1 (ret_v);
3349 struct page *page;
3350 const char *s = String_val (ptr_v);
3351 int pageno = Int_val (pageno_v);
3352 int pdimno = Int_val (pdimno_v);
3353 float x = (float) Double_val (x_v), y = (float) Double_val (y_v);
3354 struct pagedim *pdim;
3355 fz_point p;
3356 fz_matrix ctm;
3358 ret_v = Val_int (0);
3359 lock (__func__);
3361 if (!*s) {
3362 page = loadpage (pageno, pdimno);
3364 else {
3365 page = parse_pointer (__func__, String_val (ptr_v));
3367 pdim = &state.pagedims[pdimno];
3369 if (pdf_specifics (state.ctx, state.doc)) {
3370 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
3371 ctm = state.pagedims[page->pdimno].tctm;
3373 else {
3374 ctm = fz_identity;
3377 p.x = x + pdim->bounds.x0;
3378 p.y = y + pdim->bounds.y0;
3380 ctm = fz_concat (pdim->tctm, pdim->ctm);
3381 p = fz_transform_point (p, ctm);
3383 ret_v = caml_alloc_tuple (2);
3384 Field (ret_v, 0) = caml_copy_double ((double) p.x);
3385 Field (ret_v, 1) = caml_copy_double ((double) p.y);
3387 if (!*s) {
3388 freepage (page);
3390 unlock (__func__);
3391 CAMLreturn (ret_v);
3394 ML0 (addannot (value ptr_v, value x_v, value y_v, value contents_v))
3396 CAMLparam4 (ptr_v, x_v, y_v, contents_v);
3397 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3399 if (pdf) {
3400 pdf_annot *annot;
3401 struct page *page;
3402 fz_rect r;
3404 page = parse_pointer (__func__, String_val (ptr_v));
3405 annot = pdf_create_annot (state.ctx,
3406 pdf_page_from_fz_page (state.ctx,
3407 page->fzpage),
3408 PDF_ANNOT_TEXT);
3409 r.x0 = Int_val (x_v) - 10;
3410 r.y0 = Int_val (y_v) - 10;
3411 r.x1 = r.x0 + 20;
3412 r.y1 = r.y0 + 20;
3413 pdf_set_annot_contents (state.ctx, annot, String_val (contents_v));
3414 pdf_set_annot_rect (state.ctx, annot, r);
3416 state.dirty = 1;
3418 CAMLreturn0;
3421 ML0 (delannot (value ptr_v, value n_v))
3423 CAMLparam2 (ptr_v, n_v);
3424 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3426 if (pdf) {
3427 struct page *page;
3428 struct slink *slink;
3430 page = parse_pointer (__func__, String_val (ptr_v));
3431 slink = &page->slinks[Int_val (n_v)];
3432 pdf_delete_annot (state.ctx,
3433 pdf_page_from_fz_page (state.ctx, page->fzpage),
3434 (pdf_annot *) slink->u.annot);
3435 state.dirty = 1;
3437 CAMLreturn0;
3440 ML0 (modannot (value ptr_v, value n_v, value str_v))
3442 CAMLparam3 (ptr_v, n_v, str_v);
3443 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3445 if (pdf) {
3446 struct page *page;
3447 struct slink *slink;
3449 page = parse_pointer (__func__, String_val (ptr_v));
3450 slink = &page->slinks[Int_val (n_v)];
3451 pdf_set_annot_contents (state.ctx, (pdf_annot *) slink->u.annot,
3452 String_val (str_v));
3453 state.dirty = 1;
3455 CAMLreturn0;
3458 ML (hasunsavedchanges (void))
3460 return Val_bool (state.dirty);
3463 ML0 (savedoc (value path_v))
3465 CAMLparam1 (path_v);
3466 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3468 if (pdf) {
3469 pdf_save_document (state.ctx, pdf, String_val (path_v), NULL);
3471 CAMLreturn0;
3474 static void makestippletex (void)
3476 const char pixels[] = "\xff\xff\0\0";
3477 glGenTextures (1, &state.stid);
3478 glBindTexture (GL_TEXTURE_1D, state.stid);
3479 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
3480 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3481 glTexImage1D (
3482 GL_TEXTURE_1D,
3484 GL_ALPHA,
3487 GL_ALPHA,
3488 GL_UNSIGNED_BYTE,
3489 pixels
3493 ML (fz_version (void))
3495 return caml_copy_string (FZ_VERSION);
3498 ML (llpp_version (void))
3500 extern char llpp_version[];
3501 return caml_copy_string (llpp_version);
3504 static void diag_callback (void *user, const char *message)
3506 if (pthread_equal (pthread_self (), state.thread)) {
3507 printd ("emsg %s %s", (char *) user, message);
3509 else {
3510 puts (message);
3514 static fz_font *lsff (fz_context *ctx,int UNUSED_ATTR script,
3515 int UNUSED_ATTR language, int UNUSED_ATTR serif,
3516 int UNUSED_ATTR bold, int UNUSED_ATTR italic)
3518 static fz_font *font;
3519 static int done;
3521 if (!done) {
3522 char *path = getenv ("LLPP_FALLBACK_FONT");
3523 if (path) {
3524 font = fz_new_font_from_file (ctx, NULL, path, 0, 1);
3526 done = 1;
3528 return font;
3531 ML0 (setdcf (value path_v))
3533 free (state.dcf);
3534 state.dcf = NULL;
3535 const char *p = String_val (path_v);
3536 if (*p) {
3537 size_t len = caml_string_length (path_v);
3538 state.dcf = malloc (len + 1);
3539 if (!state.dcf) {
3540 err (1, errno, "malloc dimpath %zu", len + 1);
3542 memcpy (state.dcf, p, len);
3543 state.dcf[len] = 0;
3547 ML (init (value csock_v, value params_v))
3549 CAMLparam2 (csock_v, params_v);
3550 CAMLlocal2 (trim_v, fuzz_v);
3551 int ret, texcount, colorspace, mustoresize, redirstderr;
3552 const char *fontpath;
3554 #if TEXT_TYPE == GL_TEXTURE_2D
3555 if (!strstr ((const char *) glGetString (GL_EXTENSIONS),
3556 "texture_non_power_of_two")) {
3557 errx (1, "OpenGL does not support NPOT textures");
3559 #else
3560 if (!strstr ((const char *) glGetString (GL_EXTENSIONS),
3561 "texture_rectangle")) {
3562 errx (1, "OpenGL does not support rectangular textures");
3564 #endif
3565 state.csock = Int_val (csock_v);
3566 state.rotate = Int_val (Field (params_v, 0));
3567 state.fitmodel = Int_val (Field (params_v, 1));
3568 trim_v = Field (params_v, 2);
3569 texcount = Int_val (Field (params_v, 3));
3570 state.sliceheight = Int_val (Field (params_v, 4));
3571 mustoresize = Int_val (Field (params_v, 5));
3572 colorspace = Int_val (Field (params_v, 6));
3573 fontpath = String_val (Field (params_v, 7));
3574 redirstderr = Bool_val (Field (params_v, 8));
3576 if (redirstderr) {
3577 if (pipe (state.pfds)) {
3578 err (1, errno, "pipe");
3580 for (int ntries = 0; ntries < 1737; ++ntries) {
3581 if (-1 == dup2 (state.pfds[1], 2)) {
3582 if (EINTR == errno) {
3583 continue;
3585 err (1, errno, "dup2");
3587 break;
3589 } else {
3590 state.pfds[0] = 0;
3591 state.pfds[1] = 0;
3594 #ifdef MACOS
3595 state.utf8cs = 1;
3596 #else
3597 /* http://www.cl.cam.ac.uk/~mgk25/unicode.html */
3598 if (setlocale (LC_CTYPE, "")) {
3599 const char *cset = nl_langinfo (CODESET);
3600 state.utf8cs = !strcmp (cset, "UTF-8");
3602 else {
3603 err (1, errno, "setlocale");
3605 #endif
3607 state.ctx = fz_new_context (NULL, NULL, mustoresize);
3608 fz_register_document_handlers (state.ctx);
3609 if (redirstderr) {
3610 fz_set_error_callback (state.ctx, diag_callback, "[e]");
3611 fz_set_warning_callback (state.ctx, diag_callback, "[w]");
3613 fz_install_load_system_font_funcs (state.ctx, NULL, NULL, lsff);
3615 state.trimmargins = Bool_val (Field (trim_v, 0));
3616 fuzz_v = Field (trim_v, 1);
3617 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
3618 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
3619 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
3620 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
3622 set_tex_params (colorspace);
3624 if (*fontpath) {
3625 state.face = load_font (fontpath);
3627 else {
3628 int len;
3629 const unsigned char *data;
3631 data = pdf_lookup_substitute_font (state.ctx, 0, 0, 0, 0, &len);
3632 state.face = load_builtin_font (data, len);
3634 if (!state.face) {
3635 _exit (1);
3638 realloctexts (texcount);
3639 makestippletex ();
3641 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
3642 if (ret) {
3643 errx (1, "pthread_create: %d(%s)", ret, strerror (ret));
3646 CAMLreturn (Val_int (state.pfds[0]));
3649 #if FIXME || !FIXME
3650 static void UNUSED_ATTR NO_OPTIMIZE_ATTR refmacs (void) {}
3651 #endif