Switch to generic example
[llpp.git] / link.c
blobcd877fbb78eb828790a3b2199c6d5d61d8ce2ea7
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 #pragma GCC diagnostic ignored "-Wunused-parameter"
35 #include <mupdf/fitz.h>
36 #include <mupdf/pdf.h>
37 #pragma GCC diagnostic pop
39 #pragma GCC diagnostic push
40 #ifdef __clang__
41 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
42 #endif
43 #include <ft2build.h>
44 #include FT_FREETYPE_H
45 #pragma GCC diagnostic pop
47 #include "cutils.h"
49 #define ARSERT(c) !(c) ? errx (1, "%s:%d " #c, __FILE__, __LINE__) : (void) 0
50 #define ML(d) extern value ml_##d; value ml_##d
51 #define ML0(d) extern void ml_##d; void ml_##d
52 #define STTI(st) ((unsigned int) st)
54 enum { Copen=23, Ccs, Cfreepage, Cfreetile, Csearch, Cgeometry, Creqlayout,
55 Cpage, Ctile, Ctrimset, Csettrim, Csliceh, Cinterrupt };
56 enum { FitWidth, FitProportional, FitPage };
57 enum { dir_first, dir_last };
58 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
59 enum { uuri, utext, utextannot, ufileannot, unone };
60 enum { mark_page, mark_block, mark_line, mark_word };
62 struct slice {
63 int h;
64 int texindex;
67 struct tile {
68 int w, h;
69 int slicecount;
70 int sliceheight;
71 fz_pixmap *pixmap;
72 struct slice slices[1];
75 struct pagedim {
76 int pageno;
77 int rotate;
78 int left;
79 int tctmready;
80 fz_irect bounds;
81 fz_rect pagebox;
82 fz_rect mediabox;
83 fz_matrix ctm, zoomctm, tctm;
86 struct slink {
87 enum { SLINK, SANNOT } tag;
88 fz_irect bbox;
89 union {
90 fz_link *link;
91 pdf_annot *annot;
92 } u;
95 struct annot {
96 fz_irect bbox;
97 pdf_annot *annot;
100 struct page {
101 int tgen;
102 int sgen;
103 int agen;
104 int pageno;
105 int pdimno;
106 fz_stext_page *text;
107 fz_page *fzpage;
108 fz_display_list *dlist;
109 fz_link *links;
110 int slinkcount;
111 struct slink *slinks;
112 int annotcount;
113 struct annot *annots;
114 fz_stext_char *fmark, *lmark;
117 static struct {
118 pthread_mutex_t mutex;
119 int sliceheight;
120 struct pagedim *pagedims;
121 int pagecount;
122 int pagedimcount;
123 fz_document *doc;
124 fz_context *ctx;
125 int w, h;
126 char *dcf;
127 int pfds[2];
129 struct {
130 int index, count;
131 GLuint *ids;
132 GLenum iform, form, ty;
133 struct {
134 int w, h;
135 struct slice *slice;
136 } *owners;
137 } tex;
139 fz_colorspace *colorspace;
140 float papercolor[4];
142 FT_Face face;
143 fz_pixmap *pig;
144 pthread_t thread;
145 fz_irect trimfuzz;
146 GLuint stid, boid;
147 int trimmargins, needoutline, gen, rotate, aalevel,
148 fitmodel, trimanew, csock, dirty, utf8cs;
150 GLfloat texcoords[8], vertices[16];
151 } state = { .mutex = PTHREAD_MUTEX_INITIALIZER };
153 static void lock (const char *cap)
155 int ret = pthread_mutex_lock (&state.mutex);
156 if (ret) {
157 errx (1, "%s: pthread_mutex_lock: %d(%s)", cap, ret, strerror (ret));
161 static void unlock (const char *cap)
163 int ret = pthread_mutex_unlock (&state.mutex);
164 if (ret) {
165 errx (1, "%s: pthread_mutex_unlock: %d(%s)", cap, ret, strerror (ret));
169 static int trylock (const char *cap)
171 int ret = pthread_mutex_trylock (&state.mutex);
172 if (ret && ret != EBUSY) {
173 errx (1, "%s: pthread_mutex_trylock: %d(%s)", cap, ret, strerror (ret));
175 return ret == EBUSY;
178 static int hasdata (int fd)
180 int ret, avail;
181 ret = ioctl (fd, FIONREAD, &avail);
182 if (ret) {
183 err (1, errno, "hasdata: FIONREAD error ret=%d", ret);
185 return avail > 0;
188 ML (hasdata (value fd_v))
190 CAMLparam1 (fd_v);
191 CAMLreturn (Val_bool (hasdata (Int_val (fd_v))));
194 static void readdata (int fd, void *p, int size)
196 ssize_t n;
198 again:
199 n = read (fd, p, size);
200 if (n < 0) {
201 if (errno == EINTR) {
202 goto again;
204 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size, n);
206 if (n - size) {
207 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
211 static void writedata (int fd, char *p, int size)
213 ssize_t n;
214 uint32_t size4 = size;
215 struct iovec iov[2] = {
216 { .iov_base = &size4, .iov_len = 4 },
217 { .iov_base = p, .iov_len = size }
220 again:
221 n = writev (fd, iov, 2);
222 if (n < 0) {
223 if (errno == EINTR) {
224 goto again;
226 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
228 if (n - size - 4) {
229 errx (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
233 static int readlen (int fd)
235 uint32_t u;
236 readdata (fd, &u, 4);
237 return u;
240 ML0 (wcmd (value fd_v, value bytes_v, value len_v))
242 CAMLparam3 (fd_v, bytes_v, len_v);
243 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
244 CAMLreturn0;
247 ML (rcmd (value fd_v))
249 CAMLparam1 (fd_v);
250 CAMLlocal1 (strdata_v);
251 int fd = Int_val (fd_v);
252 int len = readlen (fd);
253 strdata_v = caml_alloc_string (len);
254 readdata (fd, Bytes_val (strdata_v), len);
255 CAMLreturn (strdata_v);
258 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
260 char fbuf[64];
261 int size = sizeof (fbuf), len;
262 va_list ap;
263 char *buf = fbuf;
265 for (;;) {
266 va_start (ap, fmt);
267 len = vsnprintf (buf, size, fmt, ap);
268 va_end (ap);
270 if (len > -1) {
271 if (len < size - 4) {
272 writedata (state.csock, buf, len);
273 break;
275 else {
276 size = len + 5;
279 else {
280 err (1, errno, "vsnprintf for `%s' failed", fmt);
282 buf = realloc (buf == fbuf ? NULL : buf, size);
283 if (!buf) {
284 err (1, errno, "realloc for temp buf (%d bytes) failed", size);
287 if (buf != fbuf) {
288 free (buf);
292 static void closedoc (void)
294 if (state.doc) {
295 fz_drop_document (state.ctx, state.doc);
296 state.doc = NULL;
300 static int openxref (char *filename, char *password, int w, int h, int em)
302 for (int i = 0; i < state.tex.count; ++i) {
303 state.tex.owners[i].w = -1;
304 state.tex.owners[i].slice = NULL;
307 closedoc ();
309 state.dirty = 0;
310 if (state.pagedims) {
311 free (state.pagedims);
312 state.pagedims = NULL;
314 state.pagedimcount = 0;
316 fz_set_aa_level (state.ctx, state.aalevel);
317 state.doc = fz_open_document (state.ctx, filename);
318 if (fz_needs_password (state.ctx, state.doc)) {
319 if (password && !*password) {
320 printd ("pass");
321 return 0;
323 else {
324 int ok = fz_authenticate_password (state.ctx, state.doc, password);
325 if (!ok) {
326 printd ("pass fail");
327 return 0;
331 if (w >= 0 || h >= 0 || em >=0) {
332 fz_layout_document (state.ctx, state.doc, w, h, em);
334 state.pagecount = fz_count_pages (state.ctx, state.doc);
335 return 1;
338 static void docinfo (void)
340 struct { char *tag; char *name; } tab[] = {
341 { FZ_META_INFO_TITLE, "Title" },
342 { FZ_META_INFO_AUTHOR, "Author" },
343 { FZ_META_FORMAT, "Format" },
344 { FZ_META_ENCRYPTION, "Encryption" },
345 { FZ_META_INFO_CREATOR, "Creator" },
346 { FZ_META_INFO_PRODUCER, "Producer" },
347 { "info:CreationDate", "Creation date" },
349 int len = 0, need;
350 char *buf = NULL;
352 for (size_t i = 0; i < sizeof (tab) / sizeof (*tab); ++i) {
353 again:
354 need = fz_lookup_metadata (state.ctx, state.doc, tab[i].tag, buf, len);
355 if (need > 0) {
356 if (need <= len) {
357 printd ("info %s\t%s", tab[i].name, buf);
359 else {
360 buf = realloc (buf, need);
361 if (!buf) {
362 err (1, errno, "docinfo realloc %d", need);
364 len = need;
365 goto again;
369 free (buf);
371 printd ("infoend");
374 static void unlinktile (struct tile *tile)
376 for (int i = 0; i < tile->slicecount; ++i) {
377 struct slice *s = &tile->slices[i];
379 if (s->texindex != -1) {
380 if (state.tex.owners[s->texindex].slice == s) {
381 state.tex.owners[s->texindex].slice = NULL;
387 static void freepage (struct page *page)
389 if (page) {
390 fz_drop_stext_page (state.ctx, page->text);
391 free (page->slinks);
392 fz_drop_display_list (state.ctx, page->dlist);
393 fz_drop_page (state.ctx, page->fzpage);
394 free (page);
398 static void freetile (struct tile *tile)
400 unlinktile (tile);
401 fz_drop_pixmap (state.ctx, state.pig);
402 state.pig = tile->pixmap;
403 free (tile);
406 static void trimctm (pdf_page *page, int pindex)
408 struct pagedim *pdim = &state.pagedims[pindex];
410 if (!page) {
411 return;
413 if (!pdim->tctmready) {
414 fz_rect realbox, mediabox;
415 fz_matrix page_ctm, ctm;
417 ctm = fz_concat (fz_rotate (-pdim->rotate), fz_scale (1, -1));
418 realbox = fz_transform_rect (pdim->mediabox, ctm);
419 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
420 pdim->tctm = fz_concat (
421 fz_invert_matrix (page_ctm),
422 fz_concat (ctm, fz_translate (-realbox.x0, -realbox.y0)));
423 pdim->tctmready = 1;
427 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
429 fz_matrix ctm;
430 ptrdiff_t pdimno = pdim - state.pagedims;
432 ARSERT (pdim - state.pagedims < INT_MAX);
433 if (pdf_specifics (state.ctx, state.doc)) {
434 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
435 ctm = fz_concat (pdim->tctm, pdim->ctm);
437 else {
438 ctm = fz_concat (fz_translate (-pdim->mediabox.x0, -pdim->mediabox.y0),
439 pdim->ctm);
441 return ctm;
444 static fz_matrix pagectm (struct page *page)
446 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
449 static void *loadpage (int pageno, int pindex)
451 fz_device *dev;
452 struct page *page;
454 page = calloc (sizeof (struct page), 1);
455 if (!page) {
456 err (1, errno, "calloc page %d", pageno);
459 page->dlist = fz_new_display_list (state.ctx, fz_infinite_rect);
460 dev = fz_new_list_device (state.ctx, page->dlist);
461 fz_try (state.ctx) {
462 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
463 fz_run_page (state.ctx, page->fzpage, dev, fz_identity, NULL);
465 fz_catch (state.ctx) {
466 page->fzpage = NULL;
468 fz_close_device (state.ctx, dev);
469 fz_drop_device (state.ctx, dev);
471 page->pdimno = pindex;
472 page->pageno = pageno;
473 page->sgen = state.gen;
474 page->agen = state.gen;
475 page->tgen = state.gen;
476 return page;
479 static struct tile *alloctile (int h)
481 int slicecount;
482 size_t tilesize;
483 struct tile *tile;
485 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
486 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
487 tile = calloc (tilesize, 1);
488 if (!tile) {
489 err (1, errno, "cannot allocate tile (%zu bytes)", tilesize);
491 for (int i = 0; i < slicecount; ++i) {
492 int sh = fz_mini (h, state.sliceheight);
493 tile->slices[i].h = sh;
494 tile->slices[i].texindex = -1;
495 h -= sh;
497 tile->slicecount = slicecount;
498 tile->sliceheight = state.sliceheight;
499 return tile;
502 static struct tile *rendertile (struct page *page, int x, int y, int w, int h)
504 fz_irect bbox;
505 fz_matrix ctm;
506 fz_device *dev;
507 struct tile *tile;
508 struct pagedim *pdim;
510 tile = alloctile (h);
511 pdim = &state.pagedims[page->pdimno];
513 bbox = pdim->bounds;
514 bbox.x0 += x;
515 bbox.y0 += y;
516 bbox.x1 = bbox.x0 + w;
517 bbox.y1 = bbox.y0 + h;
519 if (state.pig) {
520 if (state.pig->w == w
521 && state.pig->h == h
522 && state.pig->colorspace == state.colorspace) {
523 tile->pixmap = state.pig;
524 tile->pixmap->x = bbox.x0;
525 tile->pixmap->y = bbox.y0;
527 else {
528 fz_drop_pixmap (state.ctx, state.pig);
530 state.pig = NULL;
532 if (!tile->pixmap) {
533 tile->pixmap = fz_new_pixmap_with_bbox (state.ctx,
534 state.colorspace,
535 bbox, NULL, 1);
538 tile->w = w;
539 tile->h = h;
540 fz_fill_pixmap_with_color (state.ctx, tile->pixmap,
541 fz_device_rgb (state.ctx),
542 state.papercolor,
543 fz_default_color_params);
545 dev = fz_new_draw_device (state.ctx, fz_identity, tile->pixmap);
546 ctm = pagectm (page);
547 fz_run_display_list (state.ctx, page->dlist, dev, ctm,
548 fz_rect_from_irect (bbox), NULL);
549 fz_close_device (state.ctx, dev);
550 fz_drop_device (state.ctx, dev);
552 return tile;
555 static void initpdims1 (void)
557 int shown = 0;
558 struct pagedim *p;
559 pdf_document *pdf;
560 fz_context *ctx = state.ctx;
561 int pageno, trim, show, cxcount;
562 fz_rect rootmediabox = fz_empty_rect;
564 fz_var (p);
565 fz_var (pdf);
566 fz_var (shown);
567 fz_var (cxcount);
569 cxcount = state.pagecount;
570 if ((pdf = pdf_specifics (ctx, state.doc))) {
571 pdf_obj *obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
572 "Root/Pages/MediaBox");
573 rootmediabox = pdf_to_rect (ctx, obj);
574 pdf_load_page_tree (ctx, pdf);
577 for (pageno = 0; pageno < cxcount; ++pageno) {
578 int rotate = 0;
579 fz_rect mediabox = fz_empty_rect;
581 fz_var (rotate);
582 if (pdf) {
583 pdf_obj *pageobj = NULL;
585 fz_var (pageobj);
586 if (pdf->rev_page_map) {
587 for (int i = 0; i < pdf->rev_page_count; ++i) {
588 if (pdf->rev_page_map[i].page == pageno) {
589 pageobj = pdf_get_xref_entry (
590 ctx, pdf, pdf->rev_page_map[i].object
591 )->obj;
592 break;
596 if (!pageobj) {
597 pageobj = pdf_lookup_page_obj (ctx, pdf, pageno);
600 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
602 if (state.trimmargins) {
603 pdf_obj *obj;
604 pdf_page *page;
606 fz_try (ctx) {
607 page = pdf_load_page (ctx, pdf, pageno);
608 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
609 trim = state.trimanew || !obj;
610 if (trim) {
611 fz_rect rect;
612 fz_device *dev;
613 fz_matrix ctm, page_ctm;
615 dev = fz_new_bbox_device (ctx, &rect);
616 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
617 ctm = fz_invert_matrix (page_ctm);
618 pdf_run_page (ctx, page, dev, fz_identity, NULL);
619 fz_close_device (ctx, dev);
620 fz_drop_device (ctx, dev);
622 rect.x0 += state.trimfuzz.x0;
623 rect.x1 += state.trimfuzz.x1;
624 rect.y0 += state.trimfuzz.y0;
625 rect.y1 += state.trimfuzz.y1;
626 rect = fz_transform_rect (rect, ctm);
627 rect = fz_intersect_rect (rect, mediabox);
629 if (!fz_is_empty_rect (rect)) {
630 mediabox = rect;
633 obj = pdf_new_array (ctx, pdf, 4);
634 pdf_array_push_real (ctx, obj, mediabox.x0);
635 pdf_array_push_real (ctx, obj, mediabox.y0);
636 pdf_array_push_real (ctx, obj, mediabox.x1);
637 pdf_array_push_real (ctx, obj, mediabox.y1);
638 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
640 else {
641 mediabox.x0 = pdf_array_get_real (ctx, obj, 0);
642 mediabox.y0 = pdf_array_get_real (ctx, obj, 1);
643 mediabox.x1 = pdf_array_get_real (ctx, obj, 2);
644 mediabox.y1 = pdf_array_get_real (ctx, obj, 3);
647 fz_drop_page (ctx, &page->super);
648 show = (pageno + 1 == state.pagecount)
649 || (trim ? pageno % 5 == 0 : pageno % 20 == 0);
650 if (show) {
651 printd ("progress %f Trimming %d",
652 (double) (pageno + 1) / state.pagecount,
653 pageno + 1);
656 fz_catch (ctx) {
657 printd ("emsg failed to load page %d", pageno);
660 else {
661 int empty = 0;
662 fz_rect cropbox;
664 mediabox =
665 pdf_to_rect (ctx,
666 pdf_dict_get_inheritable (
667 ctx,
668 pageobj,
669 PDF_NAME (MediaBox)
672 if (fz_is_empty_rect (mediabox)) {
673 mediabox.x0 = 0;
674 mediabox.y0 = 0;
675 mediabox.x1 = 612;
676 mediabox.y1 = 792;
677 empty = 1;
680 cropbox =
681 pdf_to_rect (ctx, pdf_dict_gets (ctx, pageobj, "CropBox"));
682 if (!fz_is_empty_rect (cropbox)) {
683 if (empty) {
684 mediabox = cropbox;
686 else {
687 mediabox = fz_intersect_rect (mediabox, cropbox);
690 else {
691 if (empty) {
692 if (fz_is_empty_rect (rootmediabox)) {
693 printd ("emsg cannot find size of page %d", pageno);
695 else {
696 mediabox = rootmediabox;
702 else {
703 if (state.trimmargins) {
704 fz_page *page;
706 fz_try (ctx) {
707 page = fz_load_page (ctx, state.doc, pageno);
708 mediabox = fz_bound_page (ctx, page);
709 if (state.trimmargins) {
710 fz_rect rect;
711 fz_device *dev;
713 dev = fz_new_bbox_device (ctx, &rect);
714 fz_run_page (ctx, page, dev, fz_identity, NULL);
715 fz_close_device (ctx, dev);
716 fz_drop_device (ctx, dev);
718 rect.x0 += state.trimfuzz.x0;
719 rect.x1 += state.trimfuzz.x1;
720 rect.y0 += state.trimfuzz.y0;
721 rect.y1 += state.trimfuzz.y1;
722 rect = fz_intersect_rect (rect, mediabox);
724 if (!fz_is_empty_rect (rect)) {
725 mediabox = rect;
728 fz_drop_page (ctx, page);
730 fz_catch (ctx) {
733 else {
734 fz_page *page;
735 fz_try (ctx) {
736 page = fz_load_page (ctx, state.doc, pageno);
737 mediabox = fz_bound_page (ctx, page);
738 fz_drop_page (ctx, page);
740 show = !state.trimmargins && pageno % 20 == 0;
741 if (show) {
742 shown = 1;
743 printd ("progress %f Gathering dimensions %d",
744 (double) pageno / state.pagecount, pageno);
747 fz_catch (ctx) {
748 printd ("emsg failed to load page %d", pageno);
752 if (state.pagedimcount == 0
753 || ((void) (p = &state.pagedims[state.pagedimcount-1])
754 , p->rotate != rotate)
755 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
756 size_t size;
758 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
759 state.pagedims = realloc (state.pagedims, size);
760 if (!state.pagedims) {
761 err (1, errno, "realloc pagedims to %zu (%d elems)",
762 size, state.pagedimcount + 1);
765 p = &state.pagedims[state.pagedimcount++];
766 p->rotate = rotate;
767 p->mediabox = mediabox;
768 p->pageno = pageno;
771 state.trimanew = 0;
772 if (shown) {
773 printd ("progress 1");
777 static void initpdims (void)
779 FILE *f = state.dcf ? fopen (state.dcf, "rb") : NULL;
780 if (f) {
781 size_t nread;
783 nread = fread (&state.pagedimcount, sizeof (state.pagedimcount), 1, f);
784 if (nread - 1) {
785 err (1, errno, "fread pagedim %zu", sizeof (state.pagedimcount));
787 size_t size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
788 state.pagedims = realloc (state.pagedims, size);
789 if (!state.pagedims) {
790 err (1, errno, "realloc pagedims to %zu (%d elems)",
791 size, state.pagedimcount + 1);
793 if (fread (state.pagedims,
794 sizeof (*state.pagedims),
795 state.pagedimcount+1,
796 f) - (state.pagedimcount+1)) {
797 err (1, errno, "fread pagedim data %zu %d",
798 sizeof (*state.pagedims), state.pagedimcount+1);
800 fclose (f);
803 if (!state.pagedims) {
804 initpdims1 ();
805 if (state.dcf) {
806 f = fopen (state.dcf, "wb");
807 if (!f) {
808 err (1, errno, "fopen %s for writing", state.dcf);
810 if (fwrite (&state.pagedimcount,
811 sizeof (state.pagedimcount), 1, f) - 1) {
812 err (1, errno, "fwrite pagedimcunt %zu",
813 sizeof (state.pagedimcount));
815 if (fwrite (state.pagedims, sizeof (*state.pagedims),
816 state.pagedimcount + 1, f)
817 - (state.pagedimcount + 1)) {
818 err (1, errno, "fwrite pagedim data %zu %u",
819 sizeof (*state.pagedims), state.pagedimcount+1);
821 fclose (f);
826 static void layout (void)
828 int pindex;
829 fz_rect box;
830 fz_matrix ctm;
831 struct pagedim *p = NULL;
832 float zw, w, maxw = 0.0, zoom = 1.0;
834 if (state.pagedimcount == 0) {
835 return;
838 switch (state.fitmodel) {
839 case FitProportional:
840 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
841 float x0, x1;
843 p = &state.pagedims[pindex];
844 box = fz_transform_rect (p->mediabox,
845 fz_rotate (p->rotate + state.rotate));
847 x0 = fz_min (box.x0, box.x1);
848 x1 = fz_max (box.x0, box.x1);
850 w = x1 - x0;
851 maxw = fz_max (w, maxw);
852 zoom = state.w / maxw;
854 break;
856 case FitPage:
857 maxw = state.w;
858 break;
860 case FitWidth:
861 break;
863 default:
864 ARSERT (0 && state.fitmodel);
867 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
868 p = &state.pagedims[pindex];
869 ctm = fz_rotate (state.rotate);
870 box = fz_transform_rect (p->mediabox,
871 fz_rotate (p->rotate + state.rotate));
872 w = box.x1 - box.x0;
873 switch (state.fitmodel) {
874 case FitProportional:
875 p->left = (int) (((maxw - w) * zoom) / 2.f);
876 break;
877 case FitPage:
879 float zh, h;
880 zw = maxw / w;
881 h = box.y1 - box.y0;
882 zh = state.h / h;
883 zoom = fz_min (zw, zh);
884 p->left = (int) ((maxw - (w * zoom)) / 2.f);
886 break;
887 case FitWidth:
888 p->left = 0;
889 zoom = state.w / w;
890 break;
893 p->zoomctm = fz_scale (zoom, zoom);
894 ctm = fz_concat (p->zoomctm, ctm);
896 p->pagebox = p->mediabox;
897 p->pagebox = fz_transform_rect (p->pagebox, fz_rotate (p->rotate));
898 p->pagebox.x1 -= p->pagebox.x0;
899 p->pagebox.y1 -= p->pagebox.y0;
900 p->pagebox.x0 = 0;
901 p->pagebox.y0 = 0;
902 p->bounds = fz_round_rect (fz_transform_rect (p->pagebox, ctm));
903 p->ctm = ctm;
905 ctm = fz_concat (fz_translate (0, -p->mediabox.y1),
906 fz_scale (zoom, -zoom));
907 p->tctmready = 0;
910 do {
911 printd ("pdim %u %d %d %d", p->pageno, p->left,
912 abs (p->bounds.x0 - p->bounds.x1),
913 abs (p->bounds.y0 - p->bounds.y1));
914 } while (p-- != state.pagedims);
917 static struct pagedim *pdimofpageno (int pageno)
919 struct pagedim *pdim = state.pagedims;
921 for (int i = 0; i < state.pagedimcount; ++i) {
922 if (state.pagedims[i].pageno > pageno) {
923 break;
925 pdim = &state.pagedims[i];
927 return pdim;
930 static void recurse_outline (fz_outline *outline, int level)
932 while (outline) {
933 int pageno;
934 fz_point p;
935 fz_location loc;
937 loc = fz_resolve_link (state.ctx, state.doc, String_val (outline->uri),
938 &p.x, &p.y);
939 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
940 if (pageno >= 0) {
941 struct pagedim *pdim = pdimofpageno (outline->page);
942 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
943 p = fz_transform_point (p, pdim->ctm);
944 printd ("o %d %d %d %d %s",
945 level, pageno, (int) p.y, h, outline->title);
947 else {
948 printd ("on %d %s", level, outline->title);
950 if (outline->down) {
951 recurse_outline (outline->down, level + 1);
953 outline = outline->next;
957 static void process_outline (void)
959 if (state.needoutline && state.pagedimcount) {
960 fz_outline *outline = fz_load_outline (state.ctx, state.doc);
961 state.needoutline = 0;
962 if (outline) {
963 recurse_outline (outline, 0);
964 fz_drop_outline (state.ctx, outline);
969 static char *strofline (fz_stext_line *line)
971 char *p;
972 char utf8[10];
973 fz_stext_char *ch;
974 size_t size = 0, cap = 80;
976 p = malloc (cap + 1);
977 if (!p) {
978 return NULL;
981 for (ch = line->first_char; ch; ch = ch->next) {
982 int n = fz_runetochar (utf8, ch->c);
983 if (size + n > cap) {
984 cap *= 2;
985 p = realloc (p, cap + 1);
986 if (!p) {
987 return NULL;
991 memcpy (p + size, utf8, n);
992 size += n;
994 p[size] = 0;
995 return p;
998 enum a_searchresult { Found=61, NotFound, Interrupted, Error };
1000 static enum a_searchresult matchline (regex_t *re, fz_stext_line *line,
1001 int num_matches, int pageno)
1003 int ret;
1004 char *p;
1005 regmatch_t rm;
1007 p = strofline (line);
1008 if (!p) {
1009 return Error;
1012 ret = regexec (re, p, 1, &rm, 0);
1013 free (p);
1015 if (ret) {
1016 if (ret != REG_NOMATCH) {
1017 int isize;
1018 size_t size;
1019 char errbuf[80], *trail;
1021 size = regerror (ret, re, errbuf, sizeof (errbuf));
1022 if (size > 23) {
1023 isize = 23;
1024 trail = "...";
1026 else {
1027 isize = (int) size;
1028 trail = "";
1030 printd ("emsg regexec error '%*s%s'", isize, errbuf, trail);
1031 return Error;
1033 return NotFound;
1035 else {
1036 int o = 0;
1037 fz_quad s, e;
1038 fz_stext_char *ch;
1040 if (rm.rm_so == rm.rm_eo) {
1041 return Found;
1044 for (ch = line->first_char; ch; ch = ch->next) {
1045 o += fz_runelen (ch->c);
1046 if (o > rm.rm_so) {
1047 s = ch->quad;
1048 break;
1051 for (;ch; ch = ch->next) {
1052 o += fz_runelen (ch->c);
1053 if (o > rm.rm_eo) {
1054 break;
1057 e = ch->quad;
1059 printd ("match %d %d %f %f %f %f %f %f %f %f",
1060 pageno, num_matches,
1061 s.ul.x, s.ul.y,
1062 e.ur.x, s.ul.y,
1063 e.lr.x, e.lr.y,
1064 s.ul.x, e.lr.y);
1065 return Found;
1069 /* wishful thinking function */
1070 static void search (regex_t *re, int pageno, int y, int forward)
1072 fz_device *tdev;
1073 double dur, start;
1074 char *cap = "bug";
1075 struct pagedim *pdim;
1076 fz_page *page = NULL;
1077 fz_stext_block *block;
1078 fz_stext_page *text = NULL;
1079 int niters = 0, num_matches = 0;
1080 enum a_searchresult the_searchresult = NotFound;
1082 start = now ();
1083 while (pageno >= 0 && pageno < state.pagecount && num_matches == 0) {
1084 if (niters++ == 5) {
1085 niters = 0;
1086 if (hasdata (state.csock)) {
1087 fz_drop_stext_page (state.ctx, text);
1088 fz_drop_page (state.ctx, page);
1089 the_searchresult = Interrupted;
1090 break;
1092 else {
1093 printd ("progress %f searching in page %d",
1094 (double) (pageno + 1) / state.pagecount, pageno);
1097 pdim = pdimofpageno (pageno);
1098 text = fz_new_stext_page (state.ctx, pdim->mediabox);
1099 tdev = fz_new_stext_device (state.ctx, text, 0);
1101 page = fz_load_page (state.ctx, state.doc, pageno);
1102 fz_run_page (state.ctx, page, tdev, pagectm1 (page, pdim), NULL);
1104 fz_close_device (state.ctx, tdev);
1105 fz_drop_device (state.ctx, tdev);
1107 if (forward) {
1108 for (block = text->first_block; block; block = block->next) {
1109 fz_stext_line *line;
1111 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1112 continue;
1115 for (line = block->u.t.first_line; line; line = line->next) {
1116 if (line->bbox.y0 < y + 1) {
1117 continue;
1120 the_searchresult =
1121 matchline (re, line, num_matches, pageno);
1122 num_matches += the_searchresult == Found;
1126 else {
1127 for (block = text->last_block; block; block = block->prev) {
1128 fz_stext_line *line;
1130 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1131 continue;
1134 for (line = block->u.t.last_line; line; line = line->prev) {
1135 if (line->bbox.y0 < y + 1) {
1136 continue;
1139 the_searchresult =
1140 matchline (re, line, num_matches, pageno);
1141 num_matches += the_searchresult == Found;
1146 if (forward) {
1147 pageno += 1;
1148 y = 0;
1150 else {
1151 pageno -= 1;
1152 y = INT_MAX;
1154 fz_drop_stext_page (state.ctx, text);
1155 text = NULL;
1156 fz_drop_page (state.ctx, page);
1157 page = NULL;
1159 dur = now () - start;
1160 switch (the_searchresult) {
1161 case Found: case NotFound: cap = ""; break;
1162 case Error: cap = "error "; break;
1163 case Interrupted: cap = "interrupt "; break;
1165 printd ("progress 1 %sfound %d in %f sec", cap, num_matches, dur);
1166 printd ("clearrects");
1169 static void set_tex_params (int colorspace)
1171 switch (colorspace) {
1172 case 0:
1173 state.tex.iform = GL_RGBA8;
1174 state.tex.form = GL_RGBA;
1175 state.tex.ty = GL_UNSIGNED_BYTE;
1176 state.colorspace = fz_device_rgb (state.ctx);
1177 break;
1178 case 1:
1179 state.tex.iform = GL_LUMINANCE_ALPHA;
1180 state.tex.form = GL_LUMINANCE_ALPHA;
1181 state.tex.ty = GL_UNSIGNED_BYTE;
1182 state.colorspace = fz_device_gray (state.ctx);
1183 break;
1184 default:
1185 errx (1, "invalid colorspce %d", colorspace);
1189 static void realloctexts (int texcount)
1191 size_t size;
1193 if (texcount == state.tex.count) {
1194 return;
1197 if (texcount < state.tex.count) {
1198 glDeleteTextures (state.tex.count - texcount, state.tex.ids + texcount);
1201 size = texcount * (sizeof (*state.tex.ids) + sizeof (*state.tex.owners));
1202 state.tex.ids = realloc (state.tex.ids, size);
1203 if (!state.tex.ids) {
1204 err (1, errno, "realloc texs %zu", size);
1207 state.tex.owners = (void *) (state.tex.ids + texcount);
1208 if (texcount > state.tex.count) {
1209 glGenTextures (texcount - state.tex.count,
1210 state.tex.ids + state.tex.count);
1211 for (int i = state.tex.count; i < texcount; ++i) {
1212 state.tex.owners[i].w = -1;
1213 state.tex.owners[i].slice = NULL;
1216 state.tex.count = texcount;
1217 state.tex.index = 0;
1220 static char *mbtoutf8 (char *s)
1222 char *p, *r;
1223 wchar_t *tmp;
1224 size_t i, ret, len;
1226 if (state.utf8cs) {
1227 return s;
1230 len = mbstowcs (NULL, s, strlen (s));
1231 if (len == 0 || len == (size_t) -1) {
1232 if (len) {
1233 printd ("emsg mbtoutf8: mbstowcs: %d(%s)", errno, strerror (errno));
1235 return s;
1238 tmp = calloc (len, sizeof (wchar_t));
1239 if (!tmp) {
1240 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d(%s)",
1241 len, sizeof (wchar_t), errno, strerror (errno));
1242 return s;
1245 ret = mbstowcs (tmp, s, len);
1246 if (ret == (size_t) -1) {
1247 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d(%s)",
1248 len, errno, strerror (errno));
1249 free (tmp);
1250 return s;
1253 len = 0;
1254 for (i = 0; i < ret; ++i) {
1255 len += fz_runelen (tmp[i]);
1258 p = r = malloc (len + 1);
1259 if (!r) {
1260 printd ("emsg mbtoutf8: malloc(%zu)", len);
1261 free (tmp);
1262 return s;
1265 for (i = 0; i < ret; ++i) {
1266 p += fz_runetochar (p, tmp[i]);
1268 *p = 0;
1269 free (tmp);
1270 return r;
1273 ML (mbtoutf8 (value s_v))
1275 CAMLparam1 (s_v);
1276 CAMLlocal1 (ret_v);
1277 char *s, *r;
1279 s = &Byte (s_v, 0);
1280 r = mbtoutf8 (s);
1281 if (r == s) {
1282 ret_v = s_v;
1284 else {
1285 ret_v = caml_copy_string (r);
1286 free (r);
1288 CAMLreturn (ret_v);
1291 static void *mainloop (void UNUSED_ATTR *unused)
1293 char *p = NULL, c;
1294 int len, ret, oldlen = 0;
1296 fz_var (p);
1297 fz_var (oldlen);
1298 for (;;) {
1299 len = readlen (state.csock);
1300 if (len == 0) {
1301 errx (1, "readlen returned 0");
1304 if (oldlen < len) {
1305 p = realloc (p, len);
1306 if (!p) {
1307 err (1, errno, "realloc %d failed", len);
1309 oldlen = len;
1311 readdata (state.csock, p, len);
1312 c = p[len-1];
1313 p[len-1] = 0;
1315 switch (c) {
1316 case Copen: {
1317 int off, usedoccss, ok = 0;
1318 int w, h, em;
1319 char *password;
1320 char *filename;
1321 char *utf8filename;
1322 size_t filenamelen;
1324 fz_var (ok);
1325 ret = sscanf (p, "%d %d %d %d %n", &usedoccss, &w, &h, &em, &off);
1326 if (ret != 4) {
1327 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1330 filename = p + off;
1331 filenamelen = strlen (filename);
1332 password = filename + filenamelen + 1;
1334 if (password[strlen (password) + 1]) {
1335 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1338 lock ("open");
1339 fz_set_use_document_css (state.ctx, usedoccss);
1340 fz_try (state.ctx) {
1341 ok = openxref (filename, password, w, h, em);
1343 fz_catch (state.ctx) {
1344 utf8filename = mbtoutf8 (filename);
1345 printd ("emsg failed loading %s: %s",
1346 utf8filename, fz_caught_message (state.ctx));
1347 if (utf8filename != filename) {
1348 free (utf8filename);
1351 if (ok) {
1352 docinfo ();
1353 initpdims ();
1355 unlock ("open");
1356 state.needoutline = ok;
1357 break;
1359 case Ccs: {
1360 int i, colorspace;
1362 ret = sscanf (p, "%d", &colorspace);
1363 if (ret != 1) {
1364 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1366 lock ("cs");
1367 set_tex_params (colorspace);
1368 for (i = 0; i < state.tex.count; ++i) {
1369 state.tex.owners[i].w = -1;
1370 state.tex.owners[i].slice = NULL;
1372 unlock ("cs");
1373 break;
1375 case Cfreepage: {
1376 void *ptr;
1378 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1379 if (ret != 1) {
1380 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1382 lock ("freepage");
1383 freepage (ptr);
1384 unlock ("freepage");
1385 break;
1387 case Cfreetile: {
1388 void *ptr;
1390 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1391 if (ret != 1) {
1392 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1394 lock ("freetile");
1395 freetile (ptr);
1396 unlock ("freetile");
1397 break;
1399 case Csearch: {
1400 int icase, pageno, y, len2, forward;
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 char *pat = p + len2;
1410 ret = regcomp (&re, pat, REG_EXTENDED | (icase ? REG_ICASE : 0));
1411 if (ret) {
1412 char errbuf[80];
1413 size_t size;
1415 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1416 printd ("emsg regcomp failed `%.*s'", (int) size, errbuf);
1418 else {
1419 lock ("search");
1420 search (&re, pageno, y, forward);
1421 unlock ("search");
1422 regfree (&re);
1424 break;
1426 case Cgeometry: {
1427 int w, h, fitmodel;
1429 printd ("clear");
1430 ret = sscanf (p, "%d %d %d", &w, &h, &fitmodel);
1431 if (ret != 3) {
1432 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1435 lock ("geometry");
1436 state.h = h;
1437 if (w != state.w) {
1438 state.w = w;
1439 for (int i = 0; i < state.tex.count; ++i) {
1440 state.tex.owners[i].slice = NULL;
1443 state.fitmodel = fitmodel;
1444 layout ();
1445 process_outline ();
1447 state.gen++;
1448 unlock ("geometry");
1449 printd ("continue %d", state.pagecount);
1450 break;
1452 case Creqlayout: {
1453 char *nameddest;
1454 int rotate, off, h;
1455 int fitmodel;
1456 pdf_document *pdf;
1458 printd ("clear");
1459 ret = sscanf (p, "%d %d %d %n", &rotate, &fitmodel, &h, &off);
1460 if (ret != 3) {
1461 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1463 lock ("reqlayout");
1464 pdf = pdf_specifics (state.ctx, state.doc);
1465 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1466 state.gen += 1;
1468 state.rotate = rotate;
1469 state.fitmodel = fitmodel;
1470 state.h = h;
1471 layout ();
1472 process_outline ();
1474 nameddest = p + off;
1475 if (pdf && nameddest && *nameddest) {
1476 fz_point xy;
1477 struct pagedim *pdim;
1478 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1479 &xy.x, &xy.y);
1480 pdim = pdimofpageno (pageno);
1481 xy = fz_transform_point (xy, pdim->ctm);
1482 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1485 state.gen++;
1486 unlock ("reqlayout");
1487 printd ("continue %d", state.pagecount);
1488 break;
1490 case Cpage: {
1491 double a, b;
1492 struct page *page;
1493 int pageno, pindex;
1495 ret = sscanf (p, "%d %d", &pageno, &pindex);
1496 if (ret != 2) {
1497 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1500 lock ("page");
1501 a = now ();
1502 page = loadpage (pageno, pindex);
1503 b = now ();
1504 unlock ("page");
1506 printd ("page %" PRIxPTR " %f", (uintptr_t) page, b - a);
1507 break;
1509 case Ctile: {
1510 int x, y, w, h;
1511 struct page *page;
1512 struct tile *tile;
1513 double a, b;
1515 ret = sscanf (p, "%" SCNxPTR " %d %d %d %d",
1516 (uintptr_t *) &page, &x, &y, &w, &h);
1517 if (ret != 5) {
1518 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1521 lock ("tile");
1522 a = now ();
1523 tile = rendertile (page, x, y, w, h);
1524 b = now ();
1525 unlock ("tile");
1527 printd ("tile %d %d %" PRIxPTR " %u %f",
1528 x, y, (uintptr_t) tile,
1529 tile->w * tile->h * tile->pixmap->n, b - a);
1530 break;
1532 case Ctrimset: {
1533 fz_irect fuzz;
1534 int trimmargins;
1536 ret = sscanf (p, "%d %d %d %d %d",
1537 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1538 if (ret != 5) {
1539 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1542 lock ("trimset");
1543 state.trimmargins = trimmargins;
1544 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1545 state.trimanew = 1;
1546 state.trimfuzz = fuzz;
1548 unlock ("trimset");
1549 break;
1551 case Csettrim: {
1552 fz_irect fuzz;
1553 int trimmargins;
1555 ret = sscanf (p, "%d %d %d %d %d",
1556 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1557 if (ret != 5) {
1558 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1560 printd ("clear");
1561 lock ("settrim");
1562 state.trimmargins = trimmargins;
1563 state.needoutline = 1;
1564 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1565 state.trimanew = 1;
1566 state.trimfuzz = fuzz;
1568 state.pagedimcount = 0;
1569 free (state.pagedims);
1570 state.pagedims = NULL;
1571 initpdims ();
1572 layout ();
1573 process_outline ();
1574 unlock ("settrim");
1575 printd ("continue %d", state.pagecount);
1576 break;
1578 case Csliceh: {
1579 int h;
1581 ret = sscanf (p, "%d", &h);
1582 if (ret != 1) {
1583 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1585 if (h != state.sliceheight) {
1586 state.sliceheight = h;
1587 for (int i = 0; i < state.tex.count; ++i) {
1588 state.tex.owners[i].w = -1;
1589 state.tex.owners[i].h = -1;
1590 state.tex.owners[i].slice = NULL;
1593 break;
1595 case Cinterrupt:
1596 printd ("vmsg interrupted");
1597 break;
1598 default:
1599 errx (1, "unknown llpp ffi command - %d [%.*s]", c, len, p);
1602 return 0;
1605 ML (isexternallink (value uri_v))
1607 CAMLparam1 (uri_v);
1608 CAMLreturn (Val_bool (fz_is_external_link (state.ctx, String_val (uri_v))));
1611 ML (uritolocation (value uri_v))
1613 CAMLparam1 (uri_v);
1614 CAMLlocal1 (ret_v);
1615 fz_location loc;
1616 int pageno;
1617 fz_point xy;
1618 struct pagedim *pdim;
1620 loc = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1621 &xy.x, &xy.y);
1622 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
1623 pdim = pdimofpageno (pageno);
1624 xy = fz_transform_point (xy, pdim->ctm);
1625 ret_v = caml_alloc_tuple (3);
1626 Field (ret_v, 0) = Val_int (pageno);
1627 Field (ret_v, 1) = caml_copy_double ((double) xy.x);
1628 Field (ret_v, 2) = caml_copy_double ((double) xy.y);
1629 CAMLreturn (ret_v);
1632 ML (realloctexts (value texcount_v))
1634 CAMLparam1 (texcount_v);
1635 int ok;
1637 if (trylock (__func__)) {
1638 ok = 0;
1639 goto done;
1641 realloctexts (Int_val (texcount_v));
1642 ok = 1;
1643 unlock (__func__);
1645 done:
1646 CAMLreturn (Val_bool (ok));
1649 static void recti (int x0, int y0, int x1, int y1)
1651 GLfloat *v = state.vertices;
1653 glVertexPointer (2, GL_FLOAT, 0, v);
1654 v[0] = x0; v[1] = y0;
1655 v[2] = x1; v[3] = y0;
1656 v[4] = x0; v[5] = y1;
1657 v[6] = x1; v[7] = y1;
1658 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1661 static void showsel (struct page *page, int ox, int oy)
1663 fz_irect bbox;
1664 fz_rect rect;
1665 fz_stext_block *block;
1666 int seen = 0;
1667 unsigned char selcolor[] = {15,15,15,140};
1669 if (!page->fmark || !page->lmark) {
1670 return;
1673 glEnable (GL_BLEND);
1674 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1675 glColor4ubv (selcolor);
1677 ox += state.pagedims[page->pdimno].bounds.x0;
1678 oy += state.pagedims[page->pdimno].bounds.y0;
1680 for (block = page->text->first_block; block; block = block->next) {
1681 fz_stext_line *line;
1683 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1684 continue;
1686 for (line = block->u.t.first_line; line; line = line->next) {
1687 fz_stext_char *ch;
1689 rect = fz_empty_rect;
1690 for (ch = line->first_char; ch; ch = ch->next) {
1691 fz_rect r;
1692 if (ch == page->fmark) {
1693 seen = 1;
1695 r = fz_rect_from_quad (ch->quad);
1696 if (seen) {
1697 rect = fz_union_rect (rect, r);
1699 if (ch == page->lmark) {
1700 bbox = fz_round_rect (rect);
1701 recti (bbox.x0 + ox, bbox.y0 + oy,
1702 bbox.x1 + ox, bbox.y1 + oy);
1703 goto done;
1706 if (!fz_is_empty_rect (rect)) {
1707 bbox = fz_round_rect (rect);
1708 recti (bbox.x0 + ox, bbox.y0 + oy,
1709 bbox.x1 + ox, bbox.y1 + oy);
1713 done:
1714 glDisable (GL_BLEND);
1717 #pragma GCC diagnostic push
1718 #pragma GCC diagnostic ignored "-Wconversion"
1719 #include "glfont.c"
1720 #pragma GCC diagnostic pop
1722 static void stipplerect (fz_matrix m,
1723 fz_point p1,
1724 fz_point p2,
1725 fz_point p3,
1726 fz_point p4,
1727 GLfloat *texcoords,
1728 GLfloat *vertices)
1730 p1 = fz_transform_point (p1, m);
1731 p2 = fz_transform_point (p2, m);
1732 p3 = fz_transform_point (p3, m);
1733 p4 = fz_transform_point (p4, m);
1735 float w, h, s, t;
1737 w = p2.x - p1.x;
1738 h = p2.y - p1.y;
1739 t = hypotf (w, h) * .25f;
1741 w = p3.x - p2.x;
1742 h = p3.y - p2.y;
1743 s = hypotf (w, h) * .25f;
1745 texcoords[0] = 0; vertices[0] = p1.x; vertices[1] = p1.y;
1746 texcoords[1] = t; vertices[2] = p2.x; vertices[3] = p2.y;
1748 texcoords[2] = 0; vertices[4] = p2.x; vertices[5] = p2.y;
1749 texcoords[3] = s; vertices[6] = p3.x; vertices[7] = p3.y;
1751 texcoords[4] = 0; vertices[8] = p3.x; vertices[9] = p3.y;
1752 texcoords[5] = t; vertices[10] = p4.x; vertices[11] = p4.y;
1754 texcoords[6] = 0; vertices[12] = p4.x; vertices[13] = p4.y;
1755 texcoords[7] = s; vertices[14] = p1.x; vertices[15] = p1.y;
1757 glDrawArrays (GL_LINES, 0, 8);
1760 static void ensurelinks (struct page *page)
1762 if (!page->links) {
1763 page->links = fz_load_links (state.ctx, page->fzpage);
1767 static void highlightlinks (struct page *page, int xoff, int yoff)
1769 fz_matrix ctm;
1770 fz_link *link;
1771 GLfloat *texcoords = state.texcoords;
1772 GLfloat *vertices = state.vertices;
1774 ensurelinks (page);
1776 glEnable (GL_TEXTURE_1D);
1777 glEnable (GL_BLEND);
1778 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1779 glBindTexture (GL_TEXTURE_1D, state.stid);
1781 xoff -= state.pagedims[page->pdimno].bounds.x0;
1782 yoff -= state.pagedims[page->pdimno].bounds.y0;
1783 ctm = fz_concat (pagectm (page), fz_translate (xoff, yoff));
1785 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
1786 glVertexPointer (2, GL_FLOAT, 0, vertices);
1788 for (link = page->links; link; link = link->next) {
1789 fz_point p1, p2, p3, p4;
1791 p1.x = link->rect.x0;
1792 p1.y = link->rect.y0;
1794 p2.x = link->rect.x1;
1795 p2.y = link->rect.y0;
1797 p3.x = link->rect.x1;
1798 p3.y = link->rect.y1;
1800 p4.x = link->rect.x0;
1801 p4.y = link->rect.y1;
1803 /* TODO: different colours for different schemes */
1804 if (fz_is_external_link (state.ctx, link->uri)) {
1805 glColor3ub (0, 0, 255);
1807 else {
1808 glColor3ub (255, 0, 0);
1811 stipplerect (ctm, p1, p2, p3, p4, texcoords, vertices);
1814 for (int i = 0; i < page->annotcount; ++i) {
1815 fz_point p1, p2, p3, p4;
1816 struct annot *annot = &page->annots[i];
1818 p1.x = annot->bbox.x0;
1819 p1.y = annot->bbox.y0;
1821 p2.x = annot->bbox.x1;
1822 p2.y = annot->bbox.y0;
1824 p3.x = annot->bbox.x1;
1825 p3.y = annot->bbox.y1;
1827 p4.x = annot->bbox.x0;
1828 p4.y = annot->bbox.y1;
1830 glColor3ub (0, 0, 128);
1831 stipplerect (ctm, p1, p2, p3, p4, texcoords, vertices);
1834 glDisable (GL_BLEND);
1835 glDisable (GL_TEXTURE_1D);
1838 static int compareslinks (const void *l, const void *r)
1840 struct slink const *ls = l;
1841 struct slink const *rs = r;
1842 if (ls->bbox.y0 == rs->bbox.y0) {
1843 return ls->bbox.x0 - rs->bbox.x0;
1845 return ls->bbox.y0 - rs->bbox.y0;
1848 static void droptext (struct page *page)
1850 if (page->text) {
1851 fz_drop_stext_page (state.ctx, page->text);
1852 page->fmark = NULL;
1853 page->lmark = NULL;
1854 page->text = NULL;
1858 static void dropannots (struct page *page)
1860 if (page->annots) {
1861 free (page->annots);
1862 page->annots = NULL;
1863 page->annotcount = 0;
1867 static void ensureannots (struct page *page)
1869 int i, count = 0;
1870 pdf_annot *annot;
1871 pdf_document *pdf;
1872 pdf_page *pdfpage;
1874 pdf = pdf_specifics (state.ctx, state.doc);
1875 if (!pdf) {
1876 return;
1879 pdfpage = pdf_page_from_fz_page (state.ctx, page->fzpage);
1880 if (state.gen != page->agen) {
1881 dropannots (page);
1882 page->agen = state.gen;
1884 if (page->annots) {
1885 return;
1888 for (annot = pdf_first_annot (state.ctx, pdfpage);
1889 annot;
1890 annot = pdf_next_annot (state.ctx, annot)) {
1891 count++;
1894 if (count > 0) {
1895 page->annotcount = count;
1896 page->annots = calloc (count, sizeof (*page->annots));
1897 if (!page->annots) {
1898 err (1, errno, "calloc annots %d", count);
1901 for (annot = pdf_first_annot (state.ctx, pdfpage), i = 0;
1902 annot;
1903 annot = pdf_next_annot (state.ctx, annot), i++) {
1904 fz_rect rect;
1906 rect = pdf_bound_annot (state.ctx, annot);
1907 page->annots[i].annot = annot;
1908 page->annots[i].bbox = fz_round_rect (rect);
1913 static void dropslinks (struct page *page)
1915 if (page->slinks) {
1916 free (page->slinks);
1917 page->slinks = NULL;
1918 page->slinkcount = 0;
1920 if (page->links) {
1921 fz_drop_link (state.ctx, page->links);
1922 page->links = NULL;
1926 static void ensureslinks (struct page *page)
1928 fz_matrix ctm;
1929 int i, count;
1930 size_t slinksize = sizeof (*page->slinks);
1931 fz_link *link;
1933 ensureannots (page);
1934 if (state.gen != page->sgen) {
1935 dropslinks (page);
1936 page->sgen = state.gen;
1938 if (page->slinks) {
1939 return;
1942 ensurelinks (page);
1943 ctm = pagectm (page);
1945 count = page->annotcount;
1946 for (link = page->links; link; link = link->next) {
1947 count++;
1949 if (count > 0) {
1950 int j;
1952 page->slinkcount = count;
1953 page->slinks = calloc (count, slinksize);
1954 if (!page->slinks) {
1955 err (1, errno, "calloc slinks %d", count);
1958 for (i = 0, link = page->links; link; ++i, link = link->next) {
1959 fz_rect rect;
1961 rect = link->rect;
1962 rect = fz_transform_rect (rect, ctm);
1963 page->slinks[i].tag = SLINK;
1964 page->slinks[i].u.link = link;
1965 page->slinks[i].bbox = fz_round_rect (rect);
1967 for (j = 0; j < page->annotcount; ++j, ++i) {
1968 fz_rect rect;
1969 rect = pdf_bound_annot (state.ctx, page->annots[j].annot);
1970 rect = fz_transform_rect (rect, ctm);
1971 page->slinks[i].bbox = fz_round_rect (rect);
1973 page->slinks[i].tag = SANNOT;
1974 page->slinks[i].u.annot = page->annots[j].annot;
1976 qsort (page->slinks, count, slinksize, compareslinks);
1980 static void highlightslinks (struct page *page, int xoff, int yoff,
1981 int noff, const char *targ, unsigned int tlen,
1982 const char *chars, unsigned int clen, int hfsize)
1984 char buf[40];
1985 struct slink *slink;
1986 float x0, y0, x1, y1, w;
1988 ensureslinks (page);
1989 glColor3ub (0xc3, 0xb0, 0x91);
1990 for (int i = 0; i < page->slinkcount; ++i) {
1991 fmt_linkn (buf, chars, clen, i + noff);
1992 if (!tlen || !strncmp (targ, buf, tlen)) {
1993 slink = &page->slinks[i];
1995 x0 = slink->bbox.x0 + xoff - 5;
1996 y1 = slink->bbox.y0 + yoff - 5;
1997 y0 = y1 + 10 + hfsize;
1998 w = measure_string (state.face, hfsize, buf);
1999 x1 = x0 + w + 10;
2000 recti ((int) x0, (int) y0, (int) x1, (int) y1);
2004 glEnable (GL_BLEND);
2005 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2006 glEnable (GL_TEXTURE_2D);
2007 glColor3ub (0, 0, 0);
2008 for (int i = 0; i < page->slinkcount; ++i) {
2009 fmt_linkn (buf, chars, clen, i + noff);
2010 if (!tlen || !strncmp (targ, buf, tlen)) {
2011 slink = &page->slinks[i];
2013 x0 = slink->bbox.x0 + xoff;
2014 y0 = slink->bbox.y0 + yoff + hfsize;
2015 draw_string (state.face, hfsize, x0, y0, buf);
2018 glDisable (GL_TEXTURE_2D);
2019 glDisable (GL_BLEND);
2022 static void uploadslice (struct tile *tile, struct slice *slice)
2024 int offset;
2025 struct slice *slice1;
2026 unsigned char *texdata;
2028 offset = 0;
2029 for (slice1 = tile->slices; slice != slice1; slice1++) {
2030 offset += slice1->h * tile->w * tile->pixmap->n;
2032 if (slice->texindex != -1 && slice->texindex < state.tex.count
2033 && state.tex.owners[slice->texindex].slice == slice) {
2034 glBindTexture (TEXT_TYPE, state.tex.ids[slice->texindex]);
2036 else {
2037 int subimage = 0;
2038 int texindex = state.tex.index++ % state.tex.count;
2040 if (state.tex.owners[texindex].w == tile->w) {
2041 if (state.tex.owners[texindex].h >= slice->h) {
2042 subimage = 1;
2044 else {
2045 state.tex.owners[texindex].h = slice->h;
2048 else {
2049 state.tex.owners[texindex].h = slice->h;
2052 state.tex.owners[texindex].w = tile->w;
2053 state.tex.owners[texindex].slice = slice;
2054 slice->texindex = texindex;
2056 glBindTexture (TEXT_TYPE, state.tex.ids[texindex]);
2057 #if TEXT_TYPE == GL_TEXTURE_2D
2058 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2059 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2060 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2061 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2062 #endif
2063 texdata = tile->pixmap->samples;
2064 if (subimage) {
2065 glTexSubImage2D (TEXT_TYPE, 0, 0, 0, tile->w, slice->h,
2066 state.tex.form, state.tex.ty, texdata+offset);
2068 else {
2069 glTexImage2D (TEXT_TYPE, 0, state.tex.iform, tile->w, slice->h,
2070 0, state.tex.form, state.tex.ty, texdata+offset);
2075 ML0 (begintiles (void))
2077 glEnable (TEXT_TYPE);
2078 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2079 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2082 ML0 (endtiles (void))
2084 glDisable (TEXT_TYPE);
2087 ML0 (drawtile (value args_v, value ptr_v))
2089 CAMLparam2 (args_v, ptr_v);
2090 int dispx = Int_val (Field (args_v, 0));
2091 int dispy = Int_val (Field (args_v, 1));
2092 int dispw = Int_val (Field (args_v, 2));
2093 int disph = Int_val (Field (args_v, 3));
2094 int tilex = Int_val (Field (args_v, 4));
2095 int tiley = Int_val (Field (args_v, 5));
2096 struct tile *tile = parse_pointer (__func__, String_val (ptr_v));
2097 int slicey, firstslice;
2098 struct slice *slice;
2099 GLfloat *texcoords = state.texcoords;
2100 GLfloat *vertices = state.vertices;
2102 firstslice = tiley / tile->sliceheight;
2103 slice = &tile->slices[firstslice];
2104 slicey = tiley % tile->sliceheight;
2106 while (disph > 0) {
2107 int dh;
2109 dh = slice->h - slicey;
2110 dh = fz_mini (disph, dh);
2111 uploadslice (tile, slice);
2113 texcoords[0] = tilex; texcoords[1] = slicey;
2114 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2115 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2116 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2118 vertices[0] = dispx; vertices[1] = dispy;
2119 vertices[2] = dispx+dispw; vertices[3] = dispy;
2120 vertices[4] = dispx; vertices[5] = dispy+dh;
2121 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2123 #if TEXT_TYPE == GL_TEXTURE_2D
2124 for (int i = 0; i < 8; ++i) {
2125 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2127 #endif
2129 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2130 dispy += dh;
2131 disph -= dh;
2132 slice++;
2133 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2134 slicey = 0;
2136 CAMLreturn0;
2139 ML (postprocess (value ptr_v, value hlmask_v,
2140 value xoff_v, value yoff_v, value li_v))
2142 CAMLparam5 (ptr_v, hlmask_v, xoff_v, yoff_v, li_v);
2143 int xoff = Int_val (xoff_v);
2144 int yoff = Int_val (yoff_v);
2145 int noff = Int_val (Field (li_v, 0));
2146 const char *targ = String_val (Field (li_v, 1));
2147 mlsize_t tlen = caml_string_length (Field (li_v, 1));
2148 int hfsize = Int_val (Field (li_v, 2));
2149 const char *chars = String_val (Field (li_v, 3));
2150 mlsize_t clen = caml_string_length (Field (li_v, 3));
2151 int hlmask = Int_val (hlmask_v);
2152 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2154 if (!page->fzpage) {
2155 /* deal with loadpage failed pages */
2156 goto done;
2159 if (trylock (__func__)) {
2160 noff = -1;
2161 goto done;
2164 ensureannots (page);
2165 if (hlmask & 1) {
2166 highlightlinks (page, xoff, yoff);
2168 if (hlmask & 2) {
2169 highlightslinks (page, xoff, yoff, noff, targ, STTI (tlen),
2170 chars, STTI (clen), hfsize);
2171 noff = page->slinkcount;
2173 if (page->tgen == state.gen) {
2174 showsel (page, xoff, yoff);
2176 unlock (__func__);
2178 done:
2179 CAMLreturn (Val_int (noff));
2182 static struct annot *getannot (struct page *page, int x, int y)
2184 fz_point p;
2185 fz_matrix ctm;
2186 const fz_matrix *tctm;
2187 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2189 if (!page->annots) {
2190 return NULL;
2193 if (pdf) {
2194 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2195 tctm = &state.pagedims[page->pdimno].tctm;
2197 else {
2198 tctm = &fz_identity;
2201 p.x = x;
2202 p.y = y;
2204 ctm = fz_concat (*tctm, state.pagedims[page->pdimno].ctm);
2205 ctm = fz_invert_matrix (ctm);
2206 p = fz_transform_point (p, ctm);
2208 if (pdf) {
2209 for (int i = 0; i < page->annotcount; ++i) {
2210 struct annot *a = &page->annots[i];
2211 if (fz_is_point_inside_rect (p, pdf_bound_annot (state.ctx,
2212 a->annot))) {
2213 return a;
2217 return NULL;
2220 static fz_link *getlink (struct page *page, int x, int y)
2222 fz_link *link;
2223 fz_point p = { .x = x, .y = y };
2225 ensureslinks (page);
2226 p = fz_transform_point (p, fz_invert_matrix (pagectm (page)));
2228 for (link = page->links; link; link = link->next) {
2229 if (fz_is_point_inside_rect (p, link->rect)) {
2230 return link;
2233 return NULL;
2236 static void ensuretext (struct page *page)
2238 if (state.gen != page->tgen) {
2239 droptext (page);
2240 page->tgen = state.gen;
2242 if (!page->text) {
2243 fz_device *tdev;
2245 page->text = fz_new_stext_page (state.ctx,
2246 state.pagedims[page->pdimno].mediabox);
2247 tdev = fz_new_stext_device (state.ctx, page->text, 0);
2248 fz_run_display_list (state.ctx, page->dlist,
2249 tdev, pagectm (page), fz_infinite_rect, NULL);
2250 fz_close_device (state.ctx, tdev);
2251 fz_drop_device (state.ctx, tdev);
2255 ML (find_page_with_links (value start_page_v, value dir_v))
2257 CAMLparam2 (start_page_v, dir_v);
2258 CAMLlocal1 (ret_v);
2259 int i, dir = Int_val (dir_v);
2260 int start_page = Int_val (start_page_v);
2261 int end_page = dir > 0 ? state.pagecount : -1;
2262 pdf_document *pdf;
2264 fz_var (end_page);
2265 ret_v = Val_int (0);
2266 lock (__func__);
2267 pdf = pdf_specifics (state.ctx, state.doc);
2268 for (i = start_page + dir; i != end_page; i += dir) {
2269 int found;
2271 fz_var (found);
2272 if (pdf) {
2273 pdf_page *page = NULL;
2275 fz_var (page);
2276 fz_try (state.ctx) {
2277 page = pdf_load_page (state.ctx, pdf, i);
2278 found = !!page->links || !!page->annots;
2280 fz_catch (state.ctx) {
2281 found = 0;
2283 fz_drop_page (state.ctx, &page->super);
2285 else {
2286 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2287 fz_link *link = fz_load_links (state.ctx, page);
2288 found = !!link;
2289 fz_drop_link (state.ctx, link);
2290 fz_drop_page (state.ctx, page);
2293 if (found) {
2294 ret_v = caml_alloc_small (1, 1);
2295 Field (ret_v, 0) = Val_int (i);
2296 goto unlock;
2299 unlock:
2300 unlock (__func__);
2301 CAMLreturn (ret_v);
2304 ML (findlink (value ptr_v, value dir_v))
2306 CAMLparam2 (ptr_v, dir_v);
2307 CAMLlocal2 (ret_v, pos_v);
2308 struct page *page;
2309 int dirtag, i, slinkindex;
2310 struct slink *found = NULL ,*slink;
2312 page = parse_pointer (__func__, String_val (ptr_v));
2313 ret_v = Val_int (0);
2314 lock (__func__);
2315 ensureslinks (page);
2317 if (Is_block (dir_v)) {
2318 dirtag = Tag_val (dir_v);
2319 switch (dirtag) {
2320 case dir_first_visible:
2322 int x0, y0, dir, first_index, last_index;
2324 pos_v = Field (dir_v, 0);
2325 x0 = Int_val (Field (pos_v, 0));
2326 y0 = Int_val (Field (pos_v, 1));
2327 dir = Int_val (Field (pos_v, 2));
2329 if (dir >= 0) {
2330 dir = 1;
2331 first_index = 0;
2332 last_index = page->slinkcount;
2334 else {
2335 first_index = page->slinkcount - 1;
2336 last_index = -1;
2339 for (i = first_index; i != last_index; i += dir) {
2340 slink = &page->slinks[i];
2341 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2342 found = slink;
2343 break;
2347 break;
2349 case dir_left:
2350 slinkindex = Int_val (Field (dir_v, 0));
2351 found = &page->slinks[slinkindex];
2352 for (i = slinkindex - 1; i >= 0; --i) {
2353 slink = &page->slinks[i];
2354 if (slink->bbox.x0 < found->bbox.x0) {
2355 found = slink;
2356 break;
2359 break;
2361 case dir_right:
2362 slinkindex = Int_val (Field (dir_v, 0));
2363 found = &page->slinks[slinkindex];
2364 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2365 slink = &page->slinks[i];
2366 if (slink->bbox.x0 > found->bbox.x0) {
2367 found = slink;
2368 break;
2371 break;
2373 case dir_down:
2374 slinkindex = Int_val (Field (dir_v, 0));
2375 found = &page->slinks[slinkindex];
2376 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2377 slink = &page->slinks[i];
2378 if (slink->bbox.y0 >= found->bbox.y0) {
2379 found = slink;
2380 break;
2383 break;
2385 case dir_up:
2386 slinkindex = Int_val (Field (dir_v, 0));
2387 found = &page->slinks[slinkindex];
2388 for (i = slinkindex - 1; i >= 0; --i) {
2389 slink = &page->slinks[i];
2390 if (slink->bbox.y0 <= found->bbox.y0) {
2391 found = slink;
2392 break;
2395 break;
2398 else {
2399 dirtag = Int_val (dir_v);
2400 switch (dirtag) {
2401 case dir_first:
2402 found = page->slinks;
2403 break;
2405 case dir_last:
2406 if (page->slinks) {
2407 found = page->slinks + (page->slinkcount - 1);
2409 break;
2412 if (found) {
2413 ret_v = caml_alloc_small (2, 1);
2414 Field (ret_v, 0) = Val_int (found - page->slinks);
2417 unlock (__func__);
2418 CAMLreturn (ret_v);
2421 ML (getlink (value ptr_v, value n_v))
2423 CAMLparam2 (ptr_v, n_v);
2424 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2425 int n = Int_val (n_v);
2426 fz_link *link;
2427 struct page *page;
2428 struct slink *slink;
2430 ret_v = Val_int (0);
2431 page = parse_pointer (__func__, String_val (ptr_v));
2433 lock (__func__);
2434 ensureslinks (page);
2435 if (!page->slinkcount || n > page->slinkcount) goto unlock;
2436 slink = &page->slinks[n];
2437 if (slink->tag == SLINK) {
2438 link = slink->u.link;
2439 str_v = caml_copy_string (link->uri);
2440 ret_v = caml_alloc_small (1, uuri);
2441 Field (ret_v, 0) = str_v;
2443 else {
2444 int ty = pdf_annot_type (state.ctx, slink->u.annot)
2445 == PDF_ANNOT_FILE_ATTACHMENT ? ufileannot : utextannot;
2447 ret_v = caml_alloc_small (1, ty);
2448 tup_v = caml_alloc_tuple (2);
2449 Field (ret_v, 0) = tup_v;
2450 Field (tup_v, 0) = ptr_v;
2451 Field (tup_v, 1) = n_v;
2453 unlock:
2454 unlock (__func__);
2455 CAMLreturn (ret_v);
2458 ML (getlinkn (value ptr_v, value c_v, value n_v, value noff_v))
2460 CAMLparam4 (ptr_v, c_v, n_v, noff_v);
2461 CAMLlocal1 (ret_v);
2462 char buf[40];
2463 struct page *page;
2464 const char *c = String_val (c_v);
2465 const char *n = String_val (n_v);
2466 mlsize_t clen = caml_string_length (c_v);
2467 page = parse_pointer (__func__, String_val (ptr_v));
2469 lock (__func__);
2470 ensureslinks (page);
2472 ret_v = Val_int (-page->slinkcount);
2473 for (int i = 0; i < page->slinkcount; ++i) {
2474 fmt_linkn (buf, c, STTI (clen), i - Int_val (noff_v));
2475 if (!strncmp (buf, n, clen)) {
2476 ret_v = Val_int (i+1);
2477 break;
2481 unlock (__func__);
2482 CAMLreturn (ret_v);
2485 ML (gettextannot (value ptr_v, value n_v))
2487 CAMLparam2 (ptr_v, n_v);
2488 CAMLlocal1 (ret_v);
2489 pdf_document *pdf;
2490 const char *contents = "";
2492 lock (__func__);
2493 pdf = pdf_specifics (state.ctx, state.doc);
2494 if (pdf) {
2495 struct page *page;
2496 pdf_annot *annot;
2497 struct slink *slink;
2499 page = parse_pointer (__func__, String_val (ptr_v));
2500 slink = &page->slinks[Int_val (n_v)];
2501 annot = slink->u.annot;
2502 contents = pdf_annot_contents (state.ctx, annot);
2504 unlock (__func__);
2505 ret_v = caml_copy_string (contents);
2506 CAMLreturn (ret_v);
2509 ML (getfileannot (value ptr_v, value n_v))
2511 CAMLparam2 (ptr_v, n_v);
2512 CAMLlocal1 (ret_v);
2514 lock (__func__);
2516 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2517 struct slink *slink = &page->slinks[Int_val (n_v)];
2518 pdf_obj *fs = pdf_dict_get (state.ctx,
2519 pdf_annot_obj (state.ctx, slink->u.annot),
2520 PDF_NAME (FS));
2521 ret_v = caml_copy_string (pdf_embedded_file_name (state.ctx, fs));
2523 unlock (__func__);
2524 CAMLreturn (ret_v);
2527 ML0 (savefileannot (value ptr_v, value n_v, value path_v))
2529 CAMLparam3 (ptr_v, n_v, path_v);
2530 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2531 const char *path = String_val (path_v);
2533 lock (__func__);
2534 struct slink *slink = &page->slinks[Int_val (n_v)];
2535 fz_try (state.ctx) {
2536 pdf_obj *fs = pdf_dict_get (state.ctx,
2537 pdf_annot_obj (state.ctx, slink->u.annot),
2538 PDF_NAME (FS));
2539 fz_buffer *buf = pdf_load_embedded_file (state.ctx, fs);
2540 fz_save_buffer (state.ctx, buf, path);
2541 fz_drop_buffer (state.ctx, buf);
2542 printd ("progress 1 saved '%s'", path);
2544 fz_catch (state.ctx) {
2545 printd ("emsg saving '%s': %s", path, fz_caught_message (state.ctx));
2547 unlock (__func__);
2550 ML (getlinkrect (value ptr_v, value n_v))
2552 CAMLparam2 (ptr_v, n_v);
2553 CAMLlocal1 (ret_v);
2554 struct page *page;
2555 struct slink *slink;
2557 page = parse_pointer (__func__, String_val (ptr_v));
2558 ret_v = caml_alloc_tuple (4);
2559 lock (__func__);
2560 ensureslinks (page);
2562 slink = &page->slinks[Int_val (n_v)];
2563 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2564 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2565 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2566 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2567 unlock (__func__);
2568 CAMLreturn (ret_v);
2571 ML (whatsunder (value ptr_v, value x_v, value y_v))
2573 CAMLparam3 (ptr_v, x_v, y_v);
2574 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2575 fz_link *link;
2576 struct annot *annot;
2577 struct page *page;
2578 const char *ptr = String_val (ptr_v);
2579 int x = Int_val (x_v), y = Int_val (y_v);
2580 struct pagedim *pdim;
2582 ret_v = Val_int (0);
2583 if (trylock (__func__)) {
2584 goto done;
2587 page = parse_pointer (__func__, ptr);
2588 pdim = &state.pagedims[page->pdimno];
2589 x += pdim->bounds.x0;
2590 y += pdim->bounds.y0;
2592 annot = getannot (page, x, y);
2593 if (annot) {
2594 int i, n = -1, ty;
2596 ensureslinks (page);
2597 for (i = 0; i < page->slinkcount; ++i) {
2598 if (page->slinks[i].tag == SANNOT
2599 && page->slinks[i].u.annot == annot->annot) {
2600 n = i;
2601 break;
2604 ty = pdf_annot_type (state.ctx, annot->annot)
2605 == PDF_ANNOT_FILE_ATTACHMENT ? ufileannot : utextannot;
2607 ret_v = caml_alloc_small (1, ty);
2608 tup_v = caml_alloc_tuple (2);
2609 Field (ret_v, 0) = tup_v;
2610 Field (tup_v, 0) = ptr_v;
2611 Field (tup_v, 1) = Int_val (n);
2612 goto unlock;
2615 link = getlink (page, x, y);
2616 if (link) {
2617 str_v = caml_copy_string (link->uri);
2618 ret_v = caml_alloc_small (1, uuri);
2619 Field (ret_v, 0) = str_v;
2621 else {
2622 fz_stext_block *block;
2623 fz_point p = { .x = x, .y = y };
2625 ensuretext (page);
2627 for (block = page->text->first_block; block; block = block->next) {
2628 fz_stext_line *line;
2630 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2631 continue;
2633 if (!fz_is_point_inside_rect (p, block->bbox)) {
2634 continue;
2637 for (line = block->u.t.first_line; line; line = line->next) {
2638 fz_stext_char *ch;
2640 if (!fz_is_point_inside_rect (p, line->bbox)) {
2641 continue;
2644 for (ch = line->first_char; ch; ch = ch->next) {
2645 if (!fz_is_point_inside_quad (p, ch->quad)) {
2646 const char *n2 = fz_font_name (state.ctx, ch->font);
2647 FT_FaceRec *face = fz_font_ft_face (state.ctx,
2648 ch->font);
2650 if (!n2) {
2651 n2 = "<unknown font>";
2654 if (face && face->family_name) {
2655 char *s;
2656 char *n1 = face->family_name;
2657 size_t l1 = strlen (n1);
2658 size_t l2 = strlen (n2);
2660 if (l1 != l2 || memcmp (n1, n2, l1)) {
2661 s = malloc (l1 + l2 + 2);
2662 if (s) {
2663 memcpy (s, n2, l2);
2664 s[l2] = '=';
2665 memcpy (s + l2 + 1, n1, l1 + 1);
2666 str_v = caml_copy_string (s);
2667 free (s);
2671 if (str_v == Val_unit) {
2672 str_v = caml_copy_string (n2);
2674 ret_v = caml_alloc_small (1, utext);
2675 Field (ret_v, 0) = str_v;
2676 goto unlock;
2682 unlock:
2683 unlock (__func__);
2685 done:
2686 CAMLreturn (ret_v);
2689 ML0 (clearmark (value ptr_v))
2691 CAMLparam1 (ptr_v);
2692 struct page *page;
2694 if (trylock (__func__)) {
2695 goto done;
2698 page = parse_pointer (__func__, String_val (ptr_v));
2699 page->fmark = NULL;
2700 page->lmark = NULL;
2702 unlock (__func__);
2703 done:
2704 CAMLreturn0;
2707 static int uninteresting (int c)
2709 return isspace (c) || ispunct (c);
2712 ML (markunder (value ptr_v, value x_v, value y_v, value mark_v))
2714 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
2715 CAMLlocal1 (ret_v);
2716 struct page *page;
2717 fz_stext_line *line;
2718 fz_stext_block *block;
2719 struct pagedim *pdim;
2720 int mark = Int_val (mark_v);
2721 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2723 ret_v = Val_bool (0);
2724 if (trylock (__func__)) {
2725 goto done;
2728 page = parse_pointer (__func__, String_val (ptr_v));
2729 pdim = &state.pagedims[page->pdimno];
2731 ensuretext (page);
2733 if (mark == mark_page) {
2734 page->fmark = page->text->first_block->u.t.first_line->first_char;
2735 page->lmark = page->text->last_block->u.t.last_line->last_char;
2736 ret_v = Val_bool (1);
2737 goto unlock;
2740 p.x += pdim->bounds.x0;
2741 p.y += pdim->bounds.y0;
2743 for (block = page->text->first_block; block; block = block->next) {
2744 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2745 continue;
2747 if (!fz_is_point_inside_rect (p, block->bbox)) {
2748 continue;
2751 if (mark == mark_block) {
2752 page->fmark = block->u.t.first_line->first_char;
2753 page->lmark = block->u.t.last_line->last_char;
2754 ret_v = Val_bool (1);
2755 goto unlock;
2758 for (line = block->u.t.first_line; line; line = line->next) {
2759 fz_stext_char *ch;
2761 if (!fz_is_point_inside_rect (p, line->bbox)) {
2762 continue;
2765 if (mark == mark_line) {
2766 page->fmark = line->first_char;
2767 page->lmark = line->last_char;
2768 ret_v = Val_bool (1);
2769 goto unlock;
2772 for (ch = line->first_char; ch; ch = ch->next) {
2773 fz_stext_char *ch2, *first = NULL, *last = NULL;
2775 if (fz_is_point_inside_quad (p, ch->quad)) {
2776 for (ch2 = line->first_char; ch2 != ch; ch2 = ch2->next) {
2777 if (uninteresting (ch2->c)) {
2778 first = NULL;
2780 else {
2781 if (!first) {
2782 first = ch2;
2786 for (ch2 = ch; ch2; ch2 = ch2->next) {
2787 if (uninteresting (ch2->c)) {
2788 break;
2790 last = ch2;
2793 page->fmark = first;
2794 page->lmark = last;
2795 ret_v = Val_bool (1);
2796 goto unlock;
2801 unlock:
2802 if (!Bool_val (ret_v)) {
2803 page->fmark = NULL;
2804 page->lmark = NULL;
2806 unlock (__func__);
2808 done:
2809 CAMLreturn (ret_v);
2812 ML (rectofblock (value ptr_v, value x_v, value y_v))
2814 CAMLparam3 (ptr_v, x_v, y_v);
2815 CAMLlocal2 (ret_v, res_v);
2816 fz_rect *b = NULL;
2817 struct page *page;
2818 struct pagedim *pdim;
2819 fz_stext_block *block;
2820 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2822 ret_v = Val_int (0);
2823 if (trylock (__func__)) {
2824 goto done;
2827 page = parse_pointer (__func__, String_val (ptr_v));
2828 pdim = &state.pagedims[page->pdimno];
2829 p.x += pdim->bounds.x0;
2830 p.y += pdim->bounds.y0;
2832 ensuretext (page);
2834 for (block = page->text->first_block; block; block = block->next) {
2835 switch (block->type) {
2836 case FZ_STEXT_BLOCK_TEXT:
2837 b = &block->bbox;
2838 break;
2840 case FZ_STEXT_BLOCK_IMAGE:
2841 b = &block->bbox;
2842 break;
2844 default:
2845 continue;
2848 if (fz_is_point_inside_rect (p, *b)) {
2849 break;
2851 b = NULL;
2853 if (b) {
2854 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
2855 ret_v = caml_alloc_small (1, 1);
2856 Store_double_field (res_v, 0, (double) b->x0);
2857 Store_double_field (res_v, 1, (double) b->x1);
2858 Store_double_field (res_v, 2, (double) b->y0);
2859 Store_double_field (res_v, 3, (double) b->y1);
2860 Field (ret_v, 0) = res_v;
2862 unlock (__func__);
2864 done:
2865 CAMLreturn (ret_v);
2868 ML0 (seltext (value ptr_v, value rect_v))
2870 CAMLparam2 (ptr_v, rect_v);
2871 struct page *page;
2872 struct pagedim *pdim;
2873 int x0, x1, y0, y1;
2874 fz_stext_char *ch;
2875 fz_stext_line *line;
2876 fz_stext_block *block;
2877 fz_stext_char *fc, *lc;
2879 if (trylock (__func__)) {
2880 goto done;
2883 page = parse_pointer (__func__, String_val (ptr_v));
2884 ensuretext (page);
2886 pdim = &state.pagedims[page->pdimno];
2887 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
2888 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
2889 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
2890 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
2892 if (y0 > y1) {
2893 int t = y0;
2894 y0 = y1;
2895 y1 = t;
2896 x0 = x1;
2897 x1 = t;
2900 fc = page->fmark;
2901 lc = page->lmark;
2903 for (block = page->text->first_block; block; block = block->next) {
2904 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2905 continue;
2908 for (line = block->u.t.first_line; line; line = line->next) {
2909 for (ch = line->first_char; ch; ch = ch->next) {
2910 fz_point p0 = { .x = x0, .y = y0 }, p1 = { .x = x1, .y = y1 };
2911 if (fz_is_point_inside_quad (p0, ch->quad)) {
2912 fc = ch;
2914 if (fz_is_point_inside_quad (p1, ch->quad)) {
2915 lc = ch;
2920 if (x1 < x0 && fc == lc) {
2921 fz_stext_char *t;
2923 t = fc;
2924 fc = lc;
2925 lc = t;
2928 page->fmark = fc;
2929 page->lmark = lc;
2931 unlock (__func__);
2933 done:
2934 CAMLreturn0;
2937 static int pipechar (FILE *f, fz_stext_char *ch)
2939 char buf[4];
2940 int len;
2941 size_t ret;
2943 len = fz_runetochar (buf, ch->c);
2944 ret = fwrite (buf, len, 1, f);
2945 if (ret != 1) {
2946 printd ("emsg failed to fwrite %d bytes ret=%zu: %d(%s)",
2947 len, ret, errno, strerror (errno));
2948 return -1;
2950 return 0;
2953 ML (spawn (value command_v, value fds_v))
2955 CAMLparam2 (command_v, fds_v);
2956 CAMLlocal2 (l_v, tup_v);
2957 int ret, ret1;
2958 pid_t pid = (pid_t) -1;
2959 char *msg = NULL;
2960 value earg_v = Nothing;
2961 posix_spawnattr_t attr;
2962 posix_spawn_file_actions_t fa;
2963 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
2965 argv[2] = &Byte (command_v, 0);
2966 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
2967 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
2970 if ((ret = posix_spawnattr_init (&attr)) != 0) {
2971 msg = "posix_spawnattr_init";
2972 goto fail1;
2975 #ifdef POSIX_SPAWN_USEVFORK
2976 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
2977 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
2978 goto fail;
2980 #endif
2982 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
2983 int fd1, fd2;
2985 tup_v = Field (l_v, 0);
2986 fd1 = Int_val (Field (tup_v, 0));
2987 fd2 = Int_val (Field (tup_v, 1));
2988 if (fd2 < 0) {
2989 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
2990 msg = "posix_spawn_file_actions_addclose";
2991 earg_v = tup_v;
2992 goto fail;
2995 else {
2996 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
2997 msg = "posix_spawn_file_actions_adddup2";
2998 earg_v = tup_v;
2999 goto fail;
3004 extern char **environ;
3005 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3006 msg = "posix_spawn";
3007 goto fail;
3010 fail:
3011 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3012 printd ("emsg posix_spawnattr_destroy: %d(%s)", ret1, strerror (ret1));
3015 fail1:
3016 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3017 printd ("emsg posix_spawn_file_actions_destroy: %d(%s)",
3018 ret1, strerror (ret1));
3021 if (msg) {
3022 unix_error (ret, msg, earg_v);
3025 CAMLreturn (Val_int (pid));
3028 ML (hassel (value ptr_v))
3030 CAMLparam1 (ptr_v);
3031 CAMLlocal1 (ret_v);
3032 struct page *page;
3034 ret_v = Val_bool (0);
3035 if (trylock (__func__)) {
3036 goto done;
3039 page = parse_pointer (__func__, String_val (ptr_v));
3040 ret_v = Val_bool (page->fmark && page->lmark);
3041 unlock (__func__);
3042 done:
3043 CAMLreturn (ret_v);
3046 ML0 (copysel (value fd_v, value ptr_v))
3048 CAMLparam2 (fd_v, ptr_v);
3049 FILE *f;
3050 int seen = 0;
3051 struct page *page;
3052 fz_stext_line *line;
3053 fz_stext_block *block;
3054 int fd = Int_val (fd_v);
3056 if (trylock (__func__)) {
3057 goto done;
3060 page = parse_pointer (__func__, String_val (ptr_v));
3062 if (!page->fmark || !page->lmark) {
3063 printd ("emsg nothing to copy on page %d", page->pageno);
3064 goto unlock;
3067 f = fdopen (fd, "w");
3068 if (!f) {
3069 printd ("emsg failed to fdopen sel pipe (from fd %d): %d(%s)",
3070 fd, errno, strerror (errno));
3071 f = stdout;
3074 for (block = page->text->first_block; block; block = block->next) {
3075 if (block->type != FZ_STEXT_BLOCK_TEXT) {
3076 continue;
3079 for (line = block->u.t.first_line; line; line = line->next) {
3080 fz_stext_char *ch;
3081 for (ch = line->first_char; ch; ch = ch->next) {
3082 if (seen || ch == page->fmark) {
3083 do {
3084 if (pipechar (f, ch)) {
3085 goto close;
3087 if (ch == page->lmark) {
3088 goto close;
3090 } while ((ch = ch->next));
3091 seen = 1;
3092 break;
3095 if (seen) {
3096 fputc ('\n', f);
3100 close:
3101 if (f != stdout) {
3102 int ret = fclose (f);
3103 fd = -1;
3104 if (ret == -1) {
3105 if (errno != ECHILD) {
3106 printd ("emsg failed to close sel pipe: %d(%s)",
3107 errno, strerror (errno));
3111 unlock:
3112 unlock (__func__);
3114 done:
3115 if (fd >= 0) {
3116 if (close (fd)) {
3117 printd ("emsg failed to close sel pipe: %d(%s)",
3118 errno, strerror (errno));
3121 CAMLreturn0;
3124 ML (getpdimrect (value pagedimno_v))
3126 CAMLparam1 (pagedimno_v);
3127 CAMLlocal1 (ret_v);
3128 int pagedimno = Int_val (pagedimno_v);
3129 fz_rect box;
3131 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3132 if (trylock (__func__)) {
3133 box = fz_empty_rect;
3135 else {
3136 box = state.pagedims[pagedimno].mediabox;
3137 unlock (__func__);
3140 Store_double_field (ret_v, 0, (double) box.x0);
3141 Store_double_field (ret_v, 1, (double) box.x1);
3142 Store_double_field (ret_v, 2, (double) box.y0);
3143 Store_double_field (ret_v, 3, (double) box.y1);
3145 CAMLreturn (ret_v);
3148 ML (zoom_for_height (value winw_v, value winh_v, value dw_v, value cols_v))
3150 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3151 CAMLlocal1 (ret_v);
3152 int i;
3153 float zoom = -1.;
3154 float maxh = 0.0;
3155 struct pagedim *p;
3156 float winw = Int_val (winw_v);
3157 float winh = Int_val (winh_v);
3158 float dw = Int_val (dw_v);
3159 float cols = Int_val (cols_v);
3160 float pw = 1.0, ph = 1.0;
3162 if (trylock (__func__)) {
3163 goto done;
3166 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3167 float w = p->pagebox.x1 / cols;
3168 float h = p->pagebox.y1;
3169 if (h > maxh) {
3170 maxh = h;
3171 ph = h;
3172 if (state.fitmodel != FitProportional) {
3173 pw = w;
3176 if ((state.fitmodel == FitProportional) && w > pw) {
3177 pw = w;
3181 zoom = (((winh / ph) * pw) + dw) / winw;
3182 unlock (__func__);
3183 done:
3184 ret_v = caml_copy_double ((double) zoom);
3185 CAMLreturn (ret_v);
3188 ML (getmaxw (value unit_v))
3190 CAMLparam1 (unit_v);
3191 CAMLlocal1 (ret_v);
3192 int i;
3193 float maxw = -1.;
3194 struct pagedim *p;
3196 if (trylock (__func__)) {
3197 goto done;
3200 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3201 maxw = fz_max (maxw, p->pagebox.x1);
3204 unlock (__func__);
3205 done:
3206 ret_v = caml_copy_double ((double) maxw);
3207 CAMLreturn (ret_v);
3210 ML (draw_string (value pt_v, value x_v, value y_v, value string_v))
3212 CAMLparam4 (pt_v, x_v, y_v, string_v);
3213 CAMLlocal1 (ret_v);
3214 float w = draw_string (state.face,
3215 Int_val (pt_v), Int_val (x_v), Int_val (y_v),
3216 String_val (string_v));
3217 ret_v = caml_copy_double (w);
3218 CAMLreturn (ret_v);
3221 ML (measure_string (value pt_v, value string_v))
3223 CAMLparam2 (pt_v, string_v);
3224 CAMLlocal1 (ret_v);
3226 ret_v = caml_copy_double (
3227 measure_string (state.face, Int_val (pt_v), String_val (string_v))
3229 CAMLreturn (ret_v);
3232 ML (getpagebox (value ptr_v))
3234 CAMLparam1 (ptr_v);
3235 CAMLlocal1 (ret_v);
3236 fz_rect rect;
3237 fz_irect bbox;
3238 fz_device *dev;
3239 struct page *page = parse_pointer (__func__, String_val (ptr_v));
3241 ret_v = caml_alloc_tuple (4);
3242 dev = fz_new_bbox_device (state.ctx, &rect);
3244 fz_run_page (state.ctx, page->fzpage, dev, pagectm (page), NULL);
3246 fz_close_device (state.ctx, dev);
3247 fz_drop_device (state.ctx, dev);
3248 bbox = fz_round_rect (rect);
3249 Field (ret_v, 0) = Val_int (bbox.x0);
3250 Field (ret_v, 1) = Val_int (bbox.y0);
3251 Field (ret_v, 2) = Val_int (bbox.x1);
3252 Field (ret_v, 3) = Val_int (bbox.y1);
3254 CAMLreturn (ret_v);
3257 ML0 (setaalevel (value level_v))
3259 CAMLparam1 (level_v);
3261 state.aalevel = Int_val (level_v);
3262 CAMLreturn0;
3265 ML0 (setpapercolor (value rgba_v))
3267 CAMLparam1 (rgba_v);
3269 state.papercolor[0] = (float) Double_val (Field (rgba_v, 0));
3270 state.papercolor[1] = (float) Double_val (Field (rgba_v, 1));
3271 state.papercolor[2] = (float) Double_val (Field (rgba_v, 2));
3272 state.papercolor[3] = (float) Double_val (Field (rgba_v, 3));
3273 CAMLreturn0;
3276 value ml_keysymtoutf8 (value keysym_v);
3277 #ifndef MACOS
3278 value ml_keysymtoutf8 (value keysym_v)
3280 CAMLparam1 (keysym_v);
3281 CAMLlocal1 (str_v);
3282 unsigned short keysym = (unsigned short) Int_val (keysym_v);
3283 Rune rune;
3284 extern long keysym2ucs (unsigned short);
3285 int len;
3286 char buf[5];
3288 rune = (Rune) keysym2ucs (keysym);
3289 len = fz_runetochar (buf, rune);
3290 buf[len] = 0;
3291 str_v = caml_copy_string (buf);
3292 CAMLreturn (str_v);
3294 #else
3295 value ml_keysymtoutf8 (value keysym_v)
3297 CAMLparam1 (keysym_v);
3298 CAMLlocal1 (str_v);
3299 long ucs = Long_val (keysym_v);
3300 int len;
3301 char buf[5];
3303 len = fz_runetochar (buf, (int) ucs);
3304 buf[len] = 0;
3305 str_v = caml_copy_string (buf);
3306 CAMLreturn (str_v);
3308 #endif
3310 ML (unproject (value ptr_v, value x_v, value y_v))
3312 CAMLparam3 (ptr_v, x_v, y_v);
3313 CAMLlocal2 (ret_v, tup_v);
3314 struct page *page;
3315 int x = Int_val (x_v), y = Int_val (y_v);
3316 struct pagedim *pdim;
3317 fz_point p;
3319 page = parse_pointer (__func__, String_val (ptr_v));
3320 pdim = &state.pagedims[page->pdimno];
3322 ret_v = Val_int (0);
3323 if (trylock (__func__)) {
3324 goto done;
3327 p.x = x + pdim->bounds.x0;
3328 p.y = y + pdim->bounds.y0;
3330 p = fz_transform_point (p, fz_invert_matrix (fz_concat (pdim->tctm,
3331 pdim->ctm)));
3333 tup_v = caml_alloc_tuple (2);
3334 ret_v = caml_alloc_small (1, 1);
3335 Field (tup_v, 0) = Val_int (p.x);
3336 Field (tup_v, 1) = Val_int (p.y);
3337 Field (ret_v, 0) = tup_v;
3339 unlock (__func__);
3340 done:
3341 CAMLreturn (ret_v);
3344 ML (project (value ptr_v, value pageno_v, value pdimno_v, value x_v, value y_v))
3346 CAMLparam5 (ptr_v, pageno_v, pdimno_v, x_v, y_v);
3347 CAMLlocal1 (ret_v);
3348 struct page *page;
3349 const char *s = String_val (ptr_v);
3350 int pageno = Int_val (pageno_v);
3351 int pdimno = Int_val (pdimno_v);
3352 float x = (float) Double_val (x_v), y = (float) Double_val (y_v);
3353 struct pagedim *pdim;
3354 fz_point p;
3355 fz_matrix ctm;
3357 ret_v = Val_int (0);
3358 lock (__func__);
3360 if (!*s) {
3361 page = loadpage (pageno, pdimno);
3363 else {
3364 page = parse_pointer (__func__, String_val (ptr_v));
3366 pdim = &state.pagedims[pdimno];
3368 if (pdf_specifics (state.ctx, state.doc)) {
3369 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
3370 ctm = state.pagedims[page->pdimno].tctm;
3372 else {
3373 ctm = fz_identity;
3376 p.x = x + pdim->bounds.x0;
3377 p.y = y + pdim->bounds.y0;
3379 ctm = fz_concat (pdim->tctm, pdim->ctm);
3380 p = fz_transform_point (p, ctm);
3382 ret_v = caml_alloc_tuple (2);
3383 Field (ret_v, 0) = caml_copy_double ((double) p.x);
3384 Field (ret_v, 1) = caml_copy_double ((double) p.y);
3386 if (!*s) {
3387 freepage (page);
3389 unlock (__func__);
3390 CAMLreturn (ret_v);
3393 ML0 (addannot (value ptr_v, value x_v, value y_v, value contents_v))
3395 CAMLparam4 (ptr_v, x_v, y_v, contents_v);
3396 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3398 if (pdf) {
3399 pdf_annot *annot;
3400 struct page *page;
3401 fz_rect r;
3403 page = parse_pointer (__func__, String_val (ptr_v));
3404 annot = pdf_create_annot (state.ctx,
3405 pdf_page_from_fz_page (state.ctx,
3406 page->fzpage),
3407 PDF_ANNOT_TEXT);
3408 r.x0 = Int_val (x_v) - 10;
3409 r.y0 = Int_val (y_v) - 10;
3410 r.x1 = r.x0 + 20;
3411 r.y1 = r.y0 + 20;
3412 pdf_set_annot_contents (state.ctx, annot, String_val (contents_v));
3413 pdf_set_annot_rect (state.ctx, annot, r);
3415 state.dirty = 1;
3417 CAMLreturn0;
3420 ML0 (delannot (value ptr_v, value n_v))
3422 CAMLparam2 (ptr_v, n_v);
3423 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3425 if (pdf) {
3426 struct page *page;
3427 struct slink *slink;
3429 page = parse_pointer (__func__, String_val (ptr_v));
3430 slink = &page->slinks[Int_val (n_v)];
3431 pdf_delete_annot (state.ctx,
3432 pdf_page_from_fz_page (state.ctx, page->fzpage),
3433 (pdf_annot *) slink->u.annot);
3434 state.dirty = 1;
3436 CAMLreturn0;
3439 ML0 (modannot (value ptr_v, value n_v, value str_v))
3441 CAMLparam3 (ptr_v, n_v, str_v);
3442 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3444 if (pdf) {
3445 struct page *page;
3446 struct slink *slink;
3448 page = parse_pointer (__func__, String_val (ptr_v));
3449 slink = &page->slinks[Int_val (n_v)];
3450 pdf_set_annot_contents (state.ctx, (pdf_annot *) slink->u.annot,
3451 String_val (str_v));
3452 state.dirty = 1;
3454 CAMLreturn0;
3457 ML (hasunsavedchanges (void))
3459 return Val_bool (state.dirty);
3462 ML0 (savedoc (value path_v))
3464 CAMLparam1 (path_v);
3465 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3467 if (pdf) {
3468 pdf_save_document (state.ctx, pdf, String_val (path_v), NULL);
3470 CAMLreturn0;
3473 static void makestippletex (void)
3475 const char pixels[] = "\xff\xff\0\0";
3476 glGenTextures (1, &state.stid);
3477 glBindTexture (GL_TEXTURE_1D, state.stid);
3478 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
3479 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3480 glTexImage1D (
3481 GL_TEXTURE_1D,
3483 GL_ALPHA,
3486 GL_ALPHA,
3487 GL_UNSIGNED_BYTE,
3488 pixels
3492 ML (fz_version (void))
3494 return caml_copy_string (FZ_VERSION);
3497 ML (llpp_version (void))
3499 extern char llpp_version[];
3500 return caml_copy_string (llpp_version);
3503 static void diag_callback (void *user, const char *message)
3505 if (pthread_equal (pthread_self (), state.thread)) {
3506 printd ("emsg %s %s", (char *) user, message);
3508 else {
3509 puts (message);
3513 static fz_font *lsff (fz_context *ctx,int UNUSED_ATTR script,
3514 int UNUSED_ATTR language, int UNUSED_ATTR serif,
3515 int UNUSED_ATTR bold, int UNUSED_ATTR italic)
3517 static fz_font *font;
3518 static int done;
3520 if (!done) {
3521 char *path = getenv ("LLPP_FALLBACK_FONT");
3522 if (path) {
3523 font = fz_new_font_from_file (ctx, NULL, path, 0, 1);
3525 done = 1;
3527 return font;
3530 ML0 (setdcf (value path_v))
3532 free (state.dcf);
3533 state.dcf = NULL;
3534 const char *p = String_val (path_v);
3535 if (*p) {
3536 size_t len = caml_string_length (path_v);
3537 state.dcf = malloc (len + 1);
3538 if (!state.dcf) {
3539 err (1, errno, "malloc dimpath %zu", len + 1);
3541 memcpy (state.dcf, p, len);
3542 state.dcf[len] = 0;
3546 ML (init (value csock_v, value params_v))
3548 CAMLparam2 (csock_v, params_v);
3549 CAMLlocal2 (trim_v, fuzz_v);
3550 int ret, texcount, colorspace, mustoresize, redirstderr;
3551 const char *fontpath;
3552 const char *ext = TEXT_TYPE == GL_TEXTURE_2D
3553 ? "texture_non_power_of_two"
3554 : "texture_rectangle";
3556 if (!strstr ((const char *) glGetString (GL_EXTENSIONS), ext)) {
3557 errx (1, "OpenGL does not support '%s' extension", ext);
3559 state.csock = Int_val (csock_v);
3560 state.rotate = Int_val (Field (params_v, 0));
3561 state.fitmodel = Int_val (Field (params_v, 1));
3562 trim_v = Field (params_v, 2);
3563 texcount = Int_val (Field (params_v, 3));
3564 state.sliceheight = Int_val (Field (params_v, 4));
3565 mustoresize = Int_val (Field (params_v, 5));
3566 colorspace = Int_val (Field (params_v, 6));
3567 fontpath = String_val (Field (params_v, 7));
3568 redirstderr = Bool_val (Field (params_v, 8));
3570 if (redirstderr) {
3571 if (pipe (state.pfds)) {
3572 err (1, errno, "pipe");
3574 for (int ntries = 0; ntries < 1737; ++ntries) {
3575 if (-1 == dup2 (state.pfds[1], 2)) {
3576 if (EINTR == errno) {
3577 continue;
3579 err (1, errno, "dup2");
3581 break;
3583 } else {
3584 state.pfds[0] = 0;
3585 state.pfds[1] = 0;
3588 #ifdef MACOS
3589 state.utf8cs = 1;
3590 #else
3591 /* http://www.cl.cam.ac.uk/~mgk25/unicode.html */
3592 if (setlocale (LC_CTYPE, "")) {
3593 const char *cset = nl_langinfo (CODESET);
3594 state.utf8cs = !strcmp (cset, "UTF-8");
3596 else {
3597 err (1, errno, "setlocale");
3599 #endif
3601 state.ctx = fz_new_context (NULL, NULL, mustoresize);
3602 fz_register_document_handlers (state.ctx);
3603 if (redirstderr) {
3604 fz_set_error_callback (state.ctx, diag_callback, "[e]");
3605 fz_set_warning_callback (state.ctx, diag_callback, "[w]");
3607 fz_install_load_system_font_funcs (state.ctx, NULL, NULL, lsff);
3609 state.trimmargins = Bool_val (Field (trim_v, 0));
3610 fuzz_v = Field (trim_v, 1);
3611 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
3612 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
3613 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
3614 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
3616 set_tex_params (colorspace);
3618 if (*fontpath) {
3619 state.face = load_font (fontpath);
3621 else {
3622 int len;
3623 const unsigned char *data;
3625 data = pdf_lookup_substitute_font (state.ctx, 0, 0, 0, 0, &len);
3626 state.face = load_builtin_font (data, len);
3628 if (!state.face) {
3629 _exit (1);
3632 realloctexts (texcount);
3633 makestippletex ();
3635 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
3636 if (ret) {
3637 errx (1, "pthread_create: %d(%s)", ret, strerror (ret));
3640 CAMLreturn (Val_int (state.pfds[0]));
3643 #if FIXME || !FIXME
3644 static void UNUSED_ATTR NO_OPTIMIZE_ATTR refmacs (void) {}
3645 #endif