build.bash: adapt compilation flags to OCaml 4.13
[llpp.git] / link.c
blobc920fef1ca7725583635700a6bb8add6672e297d
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 {
54 Copen=23, Ccs, Cfreepage, Cfreetile, Csearch, Cgeometry, Creqlayout,
55 Cpage, Ctile, Ctrimset, Csettrim, Csliceh, Cinterrupt
57 enum { FitWidth, FitProportional, FitPage };
58 enum { dir_first, dir_last };
59 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
60 enum { uuri, utext, utextannot, ufileannot, unone };
61 enum { mark_page, mark_block, mark_line, mark_word };
63 struct slice {
64 int h;
65 int texindex;
68 struct tile {
69 int w, h;
70 int slicecount;
71 int sliceheight;
72 fz_pixmap *pixmap;
73 struct slice slices[1];
76 struct pagedim {
77 int pageno;
78 int rotate;
79 int left;
80 int tctmready;
81 fz_irect bounds;
82 fz_rect pagebox;
83 fz_rect mediabox;
84 fz_matrix ctm, zoomctm, tctm;
87 struct slink {
88 enum { SLINK, SANNOT } tag;
89 fz_irect bbox;
90 union {
91 fz_link *link;
92 pdf_annot *annot;
93 } u;
96 struct annot {
97 fz_irect bbox;
98 pdf_annot *annot;
101 struct page {
102 int tgen;
103 int sgen;
104 int agen;
105 int pageno;
106 int pdimno;
107 fz_stext_page *text;
108 fz_page *fzpage;
109 fz_display_list *dlist;
110 fz_link *links;
111 int slinkcount;
112 struct slink *slinks;
113 int annotcount;
114 struct annot *annots;
115 fz_stext_char *fmark, *lmark;
118 static struct {
119 pthread_mutex_t mutex;
120 int sliceheight;
121 struct pagedim *pagedims;
122 int pagecount;
123 int pagedimcount;
124 fz_document *doc;
125 fz_context *ctx;
126 int w, h;
127 char *dcf;
128 int pfds[2];
130 struct {
131 int index, count;
132 GLuint *ids;
133 GLenum iform, form, ty;
134 struct {
135 int w, h;
136 struct slice *slice;
137 } *owners;
138 } tex;
140 fz_colorspace *colorspace;
141 float papercolor[4];
143 FT_Face face;
144 fz_pixmap *pig;
145 pthread_t thread;
146 fz_irect trimfuzz;
147 GLuint stid, boid;
148 int trimmargins, needoutline, gen, rotate, aalevel,
149 fitmodel, trimanew, csock, dirty, utf8cs;
151 GLfloat texcoords[8], vertices[16];
152 } state = { .mutex = PTHREAD_MUTEX_INITIALIZER };
154 static void lock (const char *cap)
156 int ret = pthread_mutex_lock (&state.mutex);
157 if (ret) {
158 errx (1, "%s: pthread_mutex_lock: %d(%s)", cap, ret, strerror (ret));
162 static void unlock (const char *cap)
164 int ret = pthread_mutex_unlock (&state.mutex);
165 if (ret) {
166 errx (1, "%s: pthread_mutex_unlock: %d(%s)", cap, ret, strerror (ret));
170 static int trylock (const char *cap)
172 int ret = pthread_mutex_trylock (&state.mutex);
173 if (ret && ret != EBUSY) {
174 errx (1, "%s: pthread_mutex_trylock: %d(%s)", cap, ret, strerror (ret));
176 return ret == EBUSY;
179 static int hasdata (int fd)
181 int ret, avail;
182 ret = ioctl (fd, FIONREAD, &avail);
183 if (ret) {
184 err (1, errno, "hasdata: FIONREAD error ret=%d", ret);
186 return avail > 0;
189 ML (hasdata (value fd_v))
191 CAMLparam1 (fd_v);
192 CAMLreturn (Val_bool (hasdata (Int_val (fd_v))));
195 static void readdata (int fd, void *p, int size)
197 ssize_t n;
199 again:
200 n = read (fd, p, size);
201 if (n < 0) {
202 if (errno == EINTR) {
203 goto again;
205 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size, n);
207 if (n - size) {
208 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
212 static void writedata (int fd, char *p, int size)
214 ssize_t n;
215 uint32_t size4 = size;
216 struct iovec iov[2] = {
217 { .iov_base = &size4, .iov_len = 4 },
218 { .iov_base = p, .iov_len = size }
221 again:
222 n = writev (fd, iov, 2);
223 if (n < 0) {
224 if (errno == EINTR) {
225 goto again;
227 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
229 if (n - size - 4) {
230 errx (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
234 static int readlen (int fd)
236 uint32_t u;
237 readdata (fd, &u, 4);
238 return u;
241 ML0 (wcmd (value fd_v, value bytes_v, value len_v))
243 CAMLparam3 (fd_v, bytes_v, len_v);
244 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
245 CAMLreturn0;
248 ML (rcmd (value fd_v))
250 CAMLparam1 (fd_v);
251 CAMLlocal1 (strdata_v);
252 int fd = Int_val (fd_v);
253 int len = readlen (fd);
254 strdata_v = caml_alloc_string (len);
255 readdata (fd, Bytes_val (strdata_v), len);
256 CAMLreturn (strdata_v);
259 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
261 char fbuf[64];
262 int size = sizeof (fbuf), len;
263 va_list ap;
264 char *buf = fbuf;
266 for (;;) {
267 va_start (ap, fmt);
268 len = vsnprintf (buf, size, fmt, ap);
269 va_end (ap);
271 if (len > -1) {
272 if (len < size - 4) {
273 writedata (state.csock, buf, len);
274 break;
276 else {
277 size = len + 5;
280 else {
281 err (1, errno, "vsnprintf for `%s' failed", fmt);
283 buf = realloc (buf == fbuf ? NULL : buf, size);
284 if (!buf) {
285 err (1, errno, "realloc for temp buf (%d bytes) failed", size);
288 if (buf != fbuf) {
289 free (buf);
293 static void closedoc (void)
295 if (state.doc) {
296 fz_drop_document (state.ctx, state.doc);
297 state.doc = NULL;
301 static int openxref (char *filename, char *password, int w, int h, int em)
303 for (int i = 0; i < state.tex.count; ++i) {
304 state.tex.owners[i].w = -1;
305 state.tex.owners[i].slice = NULL;
308 closedoc ();
310 state.dirty = 0;
311 if (state.pagedims) {
312 free (state.pagedims);
313 state.pagedims = NULL;
315 state.pagedimcount = 0;
317 fz_set_aa_level (state.ctx, state.aalevel);
318 state.doc = fz_open_document (state.ctx, filename);
319 if (fz_needs_password (state.ctx, state.doc)) {
320 if (password && !*password) {
321 printd ("pass");
322 return 0;
324 else {
325 int ok = fz_authenticate_password (state.ctx, state.doc, password);
326 if (!ok) {
327 printd ("pass fail");
328 return 0;
332 if (w >= 0 || h >= 0 || em >=0) {
333 fz_layout_document (state.ctx, state.doc, w, h, em);
335 state.pagecount = fz_count_pages (state.ctx, state.doc);
336 return 1;
339 static void docinfo (void)
341 struct { char *tag; char *name; } tab[] = {
342 { FZ_META_INFO_TITLE, "Title" },
343 { FZ_META_INFO_AUTHOR, "Author" },
344 { FZ_META_FORMAT, "Format" },
345 { FZ_META_ENCRYPTION, "Encryption" },
346 { FZ_META_INFO_CREATOR, "Creator" },
347 { FZ_META_INFO_PRODUCER, "Producer" },
348 { "info:CreationDate", "Creation date" },
350 int len = 0, need;
351 char *buf = NULL;
353 for (size_t i = 0; i < sizeof (tab) / sizeof (*tab); ++i) {
354 again:
355 need = fz_lookup_metadata (state.ctx, state.doc, tab[i].tag, buf, len);
356 if (need > 0) {
357 if (need <= len) {
358 printd ("info %s\t%s", tab[i].name, buf);
360 else {
361 buf = realloc (buf, need);
362 if (!buf) {
363 err (1, errno, "docinfo realloc %d", need);
365 len = need;
366 goto again;
370 free (buf);
372 printd ("infoend");
375 static void unlinktile (struct tile *tile)
377 for (int i = 0; i < tile->slicecount; ++i) {
378 struct slice *s = &tile->slices[i];
380 if (s->texindex != -1) {
381 if (state.tex.owners[s->texindex].slice == s) {
382 state.tex.owners[s->texindex].slice = NULL;
388 static void freepage (struct page *page)
390 if (page) {
391 fz_drop_stext_page (state.ctx, page->text);
392 free (page->slinks);
393 fz_drop_display_list (state.ctx, page->dlist);
394 fz_drop_page (state.ctx, page->fzpage);
395 free (page);
399 static void freetile (struct tile *tile)
401 unlinktile (tile);
402 fz_drop_pixmap (state.ctx, state.pig);
403 state.pig = tile->pixmap;
404 free (tile);
407 static void trimctm (pdf_page *page, int pindex)
409 struct pagedim *pdim = &state.pagedims[pindex];
411 if (!page) {
412 return;
414 if (!pdim->tctmready) {
415 fz_rect realbox, mediabox;
416 fz_matrix page_ctm, ctm;
418 ctm = fz_concat (fz_rotate (-pdim->rotate), fz_scale (1, -1));
419 realbox = fz_transform_rect (pdim->mediabox, ctm);
420 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
421 pdim->tctm = fz_concat (
422 fz_invert_matrix (page_ctm),
423 fz_concat (ctm, fz_translate (-realbox.x0, -realbox.y0)));
424 pdim->tctmready = 1;
428 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
430 fz_matrix ctm;
431 ptrdiff_t pdimno = pdim - state.pagedims;
433 ARSERT (pdim - state.pagedims < INT_MAX);
434 if (pdf_specifics (state.ctx, state.doc)) {
435 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
436 ctm = fz_concat (pdim->tctm, pdim->ctm);
438 else {
439 ctm = fz_concat (fz_translate (-pdim->mediabox.x0, -pdim->mediabox.y0),
440 pdim->ctm);
442 return ctm;
445 static fz_matrix pagectm (struct page *page)
447 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
450 static void *loadpage (int pageno, int pindex)
452 fz_device *dev;
453 struct page *page;
455 page = calloc (sizeof (struct page), 1);
456 if (!page) {
457 err (1, errno, "calloc page %d", pageno);
460 page->dlist = fz_new_display_list (state.ctx, fz_infinite_rect);
461 dev = fz_new_list_device (state.ctx, page->dlist);
462 fz_try (state.ctx) {
463 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
464 fz_run_page (state.ctx, page->fzpage, dev, fz_identity, NULL);
466 fz_catch (state.ctx) {
467 page->fzpage = NULL;
469 fz_close_device (state.ctx, dev);
470 fz_drop_device (state.ctx, dev);
472 page->pdimno = pindex;
473 page->pageno = pageno;
474 page->sgen = state.gen;
475 page->agen = state.gen;
476 page->tgen = state.gen;
477 return page;
480 static struct tile *alloctile (int h)
482 int slicecount;
483 size_t tilesize;
484 struct tile *tile;
486 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
487 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
488 tile = calloc (tilesize, 1);
489 if (!tile) {
490 err (1, errno, "cannot allocate tile (%zu bytes)", tilesize);
492 for (int i = 0; i < slicecount; ++i) {
493 int sh = fz_mini (h, state.sliceheight);
494 tile->slices[i].h = sh;
495 tile->slices[i].texindex = -1;
496 h -= sh;
498 tile->slicecount = slicecount;
499 tile->sliceheight = state.sliceheight;
500 return tile;
503 static struct tile *rendertile (struct page *page, int x, int y, int w, int h)
505 fz_irect bbox;
506 fz_matrix ctm;
507 fz_device *dev;
508 struct tile *tile;
509 struct pagedim *pdim;
511 tile = alloctile (h);
512 pdim = &state.pagedims[page->pdimno];
514 bbox = pdim->bounds;
515 bbox.x0 += x;
516 bbox.y0 += y;
517 bbox.x1 = bbox.x0 + w;
518 bbox.y1 = bbox.y0 + h;
520 if (state.pig) {
521 if (state.pig->w == w
522 && state.pig->h == h
523 && state.pig->colorspace == state.colorspace) {
524 tile->pixmap = state.pig;
525 tile->pixmap->x = bbox.x0;
526 tile->pixmap->y = bbox.y0;
528 else {
529 fz_drop_pixmap (state.ctx, state.pig);
531 state.pig = NULL;
533 if (!tile->pixmap) {
534 tile->pixmap = fz_new_pixmap_with_bbox (state.ctx,
535 state.colorspace,
536 bbox, NULL, 1);
539 tile->w = w;
540 tile->h = h;
541 fz_fill_pixmap_with_color (state.ctx, tile->pixmap,
542 fz_device_rgb (state.ctx),
543 state.papercolor,
544 fz_default_color_params);
546 dev = fz_new_draw_device (state.ctx, fz_identity, tile->pixmap);
547 ctm = pagectm (page);
548 fz_run_display_list (state.ctx, page->dlist, dev, ctm,
549 fz_rect_from_irect (bbox), NULL);
550 fz_close_device (state.ctx, dev);
551 fz_drop_device (state.ctx, dev);
553 return tile;
556 static void initpdims1 (void)
558 int shown = 0;
559 struct pagedim *p;
560 pdf_document *pdf;
561 fz_context *ctx = state.ctx;
562 int pageno, trim, show, cxcount;
563 fz_rect rootmediabox = fz_empty_rect;
565 fz_var (p);
566 fz_var (pdf);
567 fz_var (shown);
568 fz_var (cxcount);
570 cxcount = state.pagecount;
571 if ((pdf = pdf_specifics (ctx, state.doc))) {
572 pdf_obj *obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
573 "Root/Pages/MediaBox");
574 rootmediabox = pdf_to_rect (ctx, obj);
575 pdf_load_page_tree (ctx, pdf);
578 for (pageno = 0; pageno < cxcount; ++pageno) {
579 int rotate = 0;
580 fz_rect mediabox = fz_empty_rect;
582 fz_var (rotate);
583 if (pdf) {
584 pdf_obj *pageobj = NULL;
586 fz_var (pageobj);
587 if (pdf->rev_page_map) {
588 for (int i = 0; i < pdf->rev_page_count; ++i) {
589 if (pdf->rev_page_map[i].page == pageno) {
590 pageobj = pdf_get_xref_entry (
591 ctx, pdf, pdf->rev_page_map[i].object
592 )->obj;
593 break;
597 if (!pageobj) {
598 pageobj = pdf_lookup_page_obj (ctx, pdf, pageno);
601 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
603 if (state.trimmargins) {
604 pdf_obj *obj;
605 pdf_page *page;
607 fz_try (ctx) {
608 page = pdf_load_page (ctx, pdf, pageno);
609 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
610 trim = state.trimanew || !obj;
611 if (trim) {
612 fz_rect rect;
613 fz_device *dev;
614 fz_matrix ctm, page_ctm;
616 dev = fz_new_bbox_device (ctx, &rect);
617 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
618 ctm = fz_invert_matrix (page_ctm);
619 pdf_run_page (ctx, page, dev, fz_identity, NULL);
620 fz_close_device (ctx, dev);
621 fz_drop_device (ctx, dev);
623 rect.x0 += state.trimfuzz.x0;
624 rect.x1 += state.trimfuzz.x1;
625 rect.y0 += state.trimfuzz.y0;
626 rect.y1 += state.trimfuzz.y1;
627 rect = fz_transform_rect (rect, ctm);
628 rect = fz_intersect_rect (rect, mediabox);
630 if (!fz_is_empty_rect (rect)) {
631 mediabox = rect;
634 obj = pdf_new_array (ctx, pdf, 4);
635 pdf_array_push_real (ctx, obj, mediabox.x0);
636 pdf_array_push_real (ctx, obj, mediabox.y0);
637 pdf_array_push_real (ctx, obj, mediabox.x1);
638 pdf_array_push_real (ctx, obj, mediabox.y1);
639 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
641 else {
642 mediabox.x0 = pdf_array_get_real (ctx, obj, 0);
643 mediabox.y0 = pdf_array_get_real (ctx, obj, 1);
644 mediabox.x1 = pdf_array_get_real (ctx, obj, 2);
645 mediabox.y1 = pdf_array_get_real (ctx, obj, 3);
648 fz_drop_page (ctx, &page->super);
649 show = (pageno + 1 == state.pagecount)
650 || (trim ? pageno % 5 == 0 : pageno % 20 == 0);
651 if (show) {
652 printd ("progress %f Trimming %d",
653 (double) (pageno + 1) / state.pagecount,
654 pageno + 1);
657 fz_catch (ctx) {
658 printd ("emsg failed to load page %d", pageno);
661 else {
662 int empty = 0;
663 fz_rect cropbox;
665 mediabox =
666 pdf_to_rect (ctx,
667 pdf_dict_get_inheritable (
668 ctx,
669 pageobj,
670 PDF_NAME (MediaBox)
673 if (fz_is_empty_rect (mediabox)) {
674 mediabox.x0 = 0;
675 mediabox.y0 = 0;
676 mediabox.x1 = 612;
677 mediabox.y1 = 792;
678 empty = 1;
681 cropbox =
682 pdf_to_rect (ctx, pdf_dict_gets (ctx, pageobj, "CropBox"));
683 if (!fz_is_empty_rect (cropbox)) {
684 if (empty) {
685 mediabox = cropbox;
687 else {
688 mediabox = fz_intersect_rect (mediabox, cropbox);
691 else {
692 if (empty) {
693 if (fz_is_empty_rect (rootmediabox)) {
694 printd ("emsg cannot find size of page %d", pageno);
696 else {
697 mediabox = rootmediabox;
703 else {
704 if (state.trimmargins) {
705 fz_page *page;
707 fz_try (ctx) {
708 page = fz_load_page (ctx, state.doc, pageno);
709 mediabox = fz_bound_page (ctx, page);
710 if (state.trimmargins) {
711 fz_rect rect;
712 fz_device *dev;
714 dev = fz_new_bbox_device (ctx, &rect);
715 fz_run_page (ctx, page, dev, fz_identity, NULL);
716 fz_close_device (ctx, dev);
717 fz_drop_device (ctx, dev);
719 rect.x0 += state.trimfuzz.x0;
720 rect.x1 += state.trimfuzz.x1;
721 rect.y0 += state.trimfuzz.y0;
722 rect.y1 += state.trimfuzz.y1;
723 rect = fz_intersect_rect (rect, mediabox);
725 if (!fz_is_empty_rect (rect)) {
726 mediabox = rect;
729 fz_drop_page (ctx, page);
731 fz_catch (ctx) {
734 else {
735 fz_page *page;
736 fz_try (ctx) {
737 page = fz_load_page (ctx, state.doc, pageno);
738 mediabox = fz_bound_page (ctx, page);
739 fz_drop_page (ctx, page);
741 show = !state.trimmargins && pageno % 20 == 0;
742 if (show) {
743 shown = 1;
744 printd ("progress %f Gathering dimensions %d",
745 (double) pageno / state.pagecount, pageno);
748 fz_catch (ctx) {
749 printd ("emsg failed to load page %d", pageno);
753 if (state.pagedimcount == 0
754 || ((void) (p = &state.pagedims[state.pagedimcount-1])
755 , p->rotate != rotate)
756 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
757 size_t size;
759 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
760 state.pagedims = realloc (state.pagedims, size);
761 if (!state.pagedims) {
762 err (1, errno, "realloc pagedims to %zu (%d elems)",
763 size, state.pagedimcount + 1);
766 p = &state.pagedims[state.pagedimcount++];
767 p->rotate = rotate;
768 p->mediabox = mediabox;
769 p->pageno = pageno;
772 state.trimanew = 0;
773 if (shown) {
774 printd ("progress 1");
778 static void initpdims (void)
780 FILE *f = state.dcf ? fopen (state.dcf, "rb") : NULL;
781 if (f) {
782 size_t nread;
784 nread = fread (&state.pagedimcount, sizeof (state.pagedimcount), 1, f);
785 if (nread - 1) {
786 err (1, errno, "fread pagedim %zu", sizeof (state.pagedimcount));
788 size_t size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
789 state.pagedims = realloc (state.pagedims, size);
790 if (!state.pagedims) {
791 err (1, errno, "realloc pagedims to %zu (%d elems)",
792 size, state.pagedimcount + 1);
794 if (fread (state.pagedims,
795 sizeof (*state.pagedims),
796 state.pagedimcount+1,
797 f) - (state.pagedimcount+1)) {
798 err (1, errno, "fread pagedim data %zu %d",
799 sizeof (*state.pagedims), state.pagedimcount+1);
801 fclose (f);
804 if (!state.pagedims) {
805 initpdims1 ();
806 if (state.dcf) {
807 f = fopen (state.dcf, "wb");
808 if (!f) {
809 err (1, errno, "fopen %s for writing", state.dcf);
811 if (fwrite (&state.pagedimcount,
812 sizeof (state.pagedimcount), 1, f) - 1) {
813 err (1, errno, "fwrite pagedimcunt %zu",
814 sizeof (state.pagedimcount));
816 if (fwrite (state.pagedims, sizeof (*state.pagedims),
817 state.pagedimcount + 1, f)
818 - (state.pagedimcount + 1)) {
819 err (1, errno, "fwrite pagedim data %zu %u",
820 sizeof (*state.pagedims), state.pagedimcount+1);
822 fclose (f);
827 static void layout (void)
829 int pindex;
830 fz_rect box;
831 fz_matrix ctm;
832 struct pagedim *p = NULL;
833 float zw, w, maxw = 0.0, zoom = 1.0;
835 if (state.pagedimcount == 0) {
836 return;
839 switch (state.fitmodel) {
840 case FitProportional:
841 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
842 float x0, x1;
844 p = &state.pagedims[pindex];
845 box = fz_transform_rect (p->mediabox,
846 fz_rotate (p->rotate + state.rotate));
848 x0 = fz_min (box.x0, box.x1);
849 x1 = fz_max (box.x0, box.x1);
851 w = x1 - x0;
852 maxw = fz_max (w, maxw);
853 zoom = state.w / maxw;
855 break;
857 case FitPage:
858 maxw = state.w;
859 break;
861 case FitWidth:
862 break;
864 default:
865 ARSERT (0 && state.fitmodel);
868 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
869 p = &state.pagedims[pindex];
870 ctm = fz_rotate (state.rotate);
871 box = fz_transform_rect (p->mediabox,
872 fz_rotate (p->rotate + state.rotate));
873 w = box.x1 - box.x0;
874 switch (state.fitmodel) {
875 case FitProportional:
876 p->left = (int) (((maxw - w) * zoom) / 2.f);
877 break;
878 case FitPage:
880 float zh, h;
881 zw = maxw / w;
882 h = box.y1 - box.y0;
883 zh = state.h / h;
884 zoom = fz_min (zw, zh);
885 p->left = (int) ((maxw - (w * zoom)) / 2.f);
887 break;
888 case FitWidth:
889 p->left = 0;
890 zoom = state.w / w;
891 break;
894 p->zoomctm = fz_scale (zoom, zoom);
895 ctm = fz_concat (p->zoomctm, ctm);
897 p->pagebox = p->mediabox;
898 p->pagebox = fz_transform_rect (p->pagebox, fz_rotate (p->rotate));
899 p->pagebox.x1 -= p->pagebox.x0;
900 p->pagebox.y1 -= p->pagebox.y0;
901 p->pagebox.x0 = 0;
902 p->pagebox.y0 = 0;
903 p->bounds = fz_round_rect (fz_transform_rect (p->pagebox, ctm));
904 p->ctm = ctm;
906 ctm = fz_concat (fz_translate (0, -p->mediabox.y1),
907 fz_scale (zoom, -zoom));
908 p->tctmready = 0;
911 do {
912 printd ("pdim %u %d %d %d", p->pageno, p->left,
913 abs (p->bounds.x0 - p->bounds.x1),
914 abs (p->bounds.y0 - p->bounds.y1));
915 } while (p-- != state.pagedims);
918 static struct pagedim *pdimofpageno (int pageno)
920 struct pagedim *pdim = state.pagedims;
922 for (int i = 0; i < state.pagedimcount; ++i) {
923 if (state.pagedims[i].pageno > pageno) {
924 break;
926 pdim = &state.pagedims[i];
928 return pdim;
931 static void recurse_outline (fz_outline *outline, int level)
933 while (outline) {
934 int pageno;
935 fz_point p;
936 fz_location loc;
938 loc = fz_resolve_link (state.ctx, state.doc, String_val (outline->uri),
939 &p.x, &p.y);
940 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
941 if (pageno >= 0) {
942 struct pagedim *pdim = pdimofpageno (outline->page);
943 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
944 p = fz_transform_point (p, pdim->ctm);
945 printd ("o %d %d %d %d %s",
946 level, pageno, (int) p.y, h, outline->title);
948 else {
949 printd ("on %d %s", level, outline->title);
951 if (outline->down) {
952 recurse_outline (outline->down, level + 1);
954 outline = outline->next;
958 static void process_outline (void)
960 if (state.needoutline && state.pagedimcount) {
961 fz_outline *outline = fz_load_outline (state.ctx, state.doc);
962 state.needoutline = 0;
963 if (outline) {
964 recurse_outline (outline, 0);
965 fz_drop_outline (state.ctx, outline);
970 static char *strofline (fz_stext_line *line)
972 char *p;
973 char utf8[10];
974 fz_stext_char *ch;
975 size_t size = 0, cap = 80;
977 p = malloc (cap + 1);
978 if (!p) {
979 return NULL;
982 for (ch = line->first_char; ch; ch = ch->next) {
983 int n = fz_runetochar (utf8, ch->c);
984 if (size + n > cap) {
985 cap *= 2;
986 p = realloc (p, cap + 1);
987 if (!p) {
988 return NULL;
992 memcpy (p + size, utf8, n);
993 size += n;
995 p[size] = 0;
996 return p;
999 enum a_searchresult { Found=61, NotFound, Interrupted, Error };
1001 static enum a_searchresult matchline (regex_t *re, fz_stext_line *line,
1002 int num_matches, int pageno)
1004 int ret;
1005 char *p;
1006 regmatch_t rm;
1008 p = strofline (line);
1009 if (!p) {
1010 return Error;
1013 ret = regexec (re, p, 1, &rm, 0);
1014 free (p);
1016 if (ret) {
1017 if (ret != REG_NOMATCH) {
1018 int isize;
1019 size_t size;
1020 char errbuf[80], *trail;
1022 size = regerror (ret, re, errbuf, sizeof (errbuf));
1023 if (size > 23) {
1024 isize = 23;
1025 trail = "...";
1027 else {
1028 isize = (int) size;
1029 trail = "";
1031 printd ("emsg regexec error '%*s%s'", isize, errbuf, trail);
1032 return Error;
1034 return NotFound;
1036 else {
1037 int o = 0;
1038 fz_quad s, e;
1039 fz_stext_char *ch;
1041 if (rm.rm_so == rm.rm_eo) {
1042 return Found;
1045 for (ch = line->first_char; ch; ch = ch->next) {
1046 o += fz_runelen (ch->c);
1047 if (o > rm.rm_so) {
1048 s = ch->quad;
1049 break;
1052 for (;ch; ch = ch->next) {
1053 o += fz_runelen (ch->c);
1054 if (o > rm.rm_eo) {
1055 break;
1058 e = ch->quad;
1060 printd ("match %d %d %f %f %f %f %f %f %f %f",
1061 pageno, num_matches,
1062 s.ul.x, s.ul.y,
1063 e.ur.x, s.ul.y,
1064 e.lr.x, e.lr.y,
1065 s.ul.x, e.lr.y);
1066 return Found;
1070 /* wishful thinking function */
1071 static void search (regex_t *re, int pageno, int y, int forward)
1073 fz_device *tdev;
1074 double dur, start;
1075 char *cap = "bug";
1076 struct pagedim *pdim;
1077 fz_page *page = NULL;
1078 fz_stext_block *block;
1079 fz_stext_page *text = NULL;
1080 int niters = 0, num_matches = 0;
1081 enum a_searchresult the_searchresult = NotFound;
1083 start = now ();
1084 while (pageno >= 0 && pageno < state.pagecount && num_matches == 0) {
1085 if (niters++ == 5) {
1086 niters = 0;
1087 if (hasdata (state.csock)) {
1088 fz_drop_stext_page (state.ctx, text);
1089 fz_drop_page (state.ctx, page);
1090 the_searchresult = Interrupted;
1091 break;
1093 else {
1094 printd ("progress %f searching in page %d",
1095 (double) (pageno + 1) / state.pagecount, pageno);
1098 pdim = pdimofpageno (pageno);
1099 text = fz_new_stext_page (state.ctx, pdim->mediabox);
1100 tdev = fz_new_stext_device (state.ctx, text, 0);
1102 page = fz_load_page (state.ctx, state.doc, pageno);
1103 fz_run_page (state.ctx, page, tdev, pagectm1 (page, pdim), NULL);
1105 fz_close_device (state.ctx, tdev);
1106 fz_drop_device (state.ctx, tdev);
1108 if (forward) {
1109 for (block = text->first_block; block; block = block->next) {
1110 fz_stext_line *line;
1112 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1113 continue;
1116 for (line = block->u.t.first_line; line; line = line->next) {
1117 if (line->bbox.y0 < y + 1) {
1118 continue;
1121 the_searchresult =
1122 matchline (re, line, num_matches, pageno);
1123 num_matches += the_searchresult == Found;
1127 else {
1128 for (block = text->last_block; block; block = block->prev) {
1129 fz_stext_line *line;
1131 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1132 continue;
1135 for (line = block->u.t.last_line; line; line = line->prev) {
1136 if (line->bbox.y0 < y + 1) {
1137 continue;
1140 the_searchresult =
1141 matchline (re, line, num_matches, pageno);
1142 num_matches += the_searchresult == Found;
1147 if (forward) {
1148 pageno += 1;
1149 y = 0;
1151 else {
1152 pageno -= 1;
1153 y = INT_MAX;
1155 fz_drop_stext_page (state.ctx, text);
1156 text = NULL;
1157 fz_drop_page (state.ctx, page);
1158 page = NULL;
1160 dur = now () - start;
1161 switch (the_searchresult) {
1162 case Found: case NotFound: cap = ""; break;
1163 case Error: cap = "error "; break;
1164 case Interrupted: cap = "interrupt "; break;
1166 printd ("progress 1 %sfound %d in %f sec", cap, num_matches, dur);
1167 printd ("clearrects");
1170 static void set_tex_params (int colorspace)
1172 switch (colorspace) {
1173 case 0:
1174 state.tex.iform = GL_RGBA8;
1175 state.tex.form = GL_RGBA;
1176 state.tex.ty = GL_UNSIGNED_BYTE;
1177 state.colorspace = fz_device_rgb (state.ctx);
1178 break;
1179 case 1:
1180 state.tex.iform = GL_LUMINANCE_ALPHA;
1181 state.tex.form = GL_LUMINANCE_ALPHA;
1182 state.tex.ty = GL_UNSIGNED_BYTE;
1183 state.colorspace = fz_device_gray (state.ctx);
1184 break;
1185 default:
1186 errx (1, "invalid colorspce %d", colorspace);
1190 static void realloctexts (int texcount)
1192 size_t size;
1194 if (texcount == state.tex.count) {
1195 return;
1198 if (texcount < state.tex.count) {
1199 glDeleteTextures (state.tex.count - texcount, state.tex.ids + texcount);
1202 size = texcount * (sizeof (*state.tex.ids) + sizeof (*state.tex.owners));
1203 state.tex.ids = realloc (state.tex.ids, size);
1204 if (!state.tex.ids) {
1205 err (1, errno, "realloc texs %zu", size);
1208 state.tex.owners = (void *) (state.tex.ids + texcount);
1209 if (texcount > state.tex.count) {
1210 glGenTextures (texcount - state.tex.count,
1211 state.tex.ids + state.tex.count);
1212 for (int i = state.tex.count; i < texcount; ++i) {
1213 state.tex.owners[i].w = -1;
1214 state.tex.owners[i].slice = NULL;
1217 state.tex.count = texcount;
1218 state.tex.index = 0;
1221 static char *mbtoutf8 (char *s)
1223 char *p, *r;
1224 wchar_t *tmp;
1225 size_t i, ret, len;
1227 if (state.utf8cs) {
1228 return s;
1231 len = mbstowcs (NULL, s, strlen (s));
1232 if (len == 0 || len == (size_t) -1) {
1233 if (len) {
1234 printd ("emsg mbtoutf8: mbstowcs: %d(%s)", errno, strerror (errno));
1236 return s;
1239 tmp = calloc (len, sizeof (wchar_t));
1240 if (!tmp) {
1241 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d(%s)",
1242 len, sizeof (wchar_t), errno, strerror (errno));
1243 return s;
1246 ret = mbstowcs (tmp, s, len);
1247 if (ret == (size_t) -1) {
1248 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d(%s)",
1249 len, errno, strerror (errno));
1250 free (tmp);
1251 return s;
1254 len = 0;
1255 for (i = 0; i < ret; ++i) {
1256 len += fz_runelen (tmp[i]);
1259 p = r = malloc (len + 1);
1260 if (!r) {
1261 printd ("emsg mbtoutf8: malloc(%zu)", len);
1262 free (tmp);
1263 return s;
1266 for (i = 0; i < ret; ++i) {
1267 p += fz_runetochar (p, tmp[i]);
1269 *p = 0;
1270 free (tmp);
1271 return r;
1274 ML (mbtoutf8 (value s_v))
1276 CAMLparam1 (s_v);
1277 CAMLlocal1 (ret_v);
1278 char *s, *r;
1280 s = &Byte (s_v, 0);
1281 r = mbtoutf8 (s);
1282 if (r == s) {
1283 ret_v = s_v;
1285 else {
1286 ret_v = caml_copy_string (r);
1287 free (r);
1289 CAMLreturn (ret_v);
1292 static void *mainloop (void UNUSED_ATTR *unused)
1294 char *p = NULL, c;
1295 int len, ret, oldlen = 0;
1297 fz_var (p);
1298 fz_var (oldlen);
1299 for (;;) {
1300 len = readlen (state.csock);
1301 if (len == 0) {
1302 errx (1, "readlen returned 0");
1305 if (oldlen < len) {
1306 p = realloc (p, len);
1307 if (!p) {
1308 err (1, errno, "realloc %d failed", len);
1310 oldlen = len;
1312 readdata (state.csock, p, len);
1313 c = p[len-1];
1314 p[len-1] = 0;
1316 switch (c) {
1317 case Copen: {
1318 int off, usedoccss, ok = 0;
1319 int w, h, em;
1320 char *password;
1321 char *filename;
1322 char *utf8filename;
1323 size_t filenamelen;
1325 fz_var (ok);
1326 ret = sscanf (p, "%d %d %d %d %n", &usedoccss, &w, &h, &em, &off);
1327 if (ret != 4) {
1328 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1331 filename = p + off;
1332 filenamelen = strlen (filename);
1333 password = filename + filenamelen + 1;
1335 if (password[strlen (password) + 1]) {
1336 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1339 lock ("open");
1340 fz_set_use_document_css (state.ctx, usedoccss);
1341 fz_try (state.ctx) {
1342 ok = openxref (filename, password, w, h, em);
1344 fz_catch (state.ctx) {
1345 utf8filename = mbtoutf8 (filename);
1346 printd ("emsg failed loading %s: %s",
1347 utf8filename, fz_caught_message (state.ctx));
1348 if (utf8filename != filename) {
1349 free (utf8filename);
1352 if (ok) {
1353 docinfo ();
1354 initpdims ();
1356 unlock ("open");
1357 state.needoutline = ok;
1358 break;
1360 case Ccs: {
1361 int i, colorspace;
1363 ret = sscanf (p, "%d", &colorspace);
1364 if (ret != 1) {
1365 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1367 lock ("cs");
1368 set_tex_params (colorspace);
1369 for (i = 0; i < state.tex.count; ++i) {
1370 state.tex.owners[i].w = -1;
1371 state.tex.owners[i].slice = NULL;
1373 unlock ("cs");
1374 break;
1376 case Cfreepage: {
1377 void *ptr;
1379 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1380 if (ret != 1) {
1381 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1383 lock ("freepage");
1384 freepage (ptr);
1385 unlock ("freepage");
1386 break;
1388 case Cfreetile: {
1389 void *ptr;
1391 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1392 if (ret != 1) {
1393 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1395 lock ("freetile");
1396 freetile (ptr);
1397 unlock ("freetile");
1398 break;
1400 case Csearch: {
1401 int icase, pageno, y, len2, forward;
1402 regex_t re;
1404 ret = sscanf (p, "%d %d %d %d,%n",
1405 &icase, &pageno, &y, &forward, &len2);
1406 if (ret != 4) {
1407 errx (1, "malformed search `%s' ret=%d", p, ret);
1410 char *pat = p + len2;
1411 ret = regcomp (&re, pat, 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;
3553 const char *ext = TEXT_TYPE == GL_TEXTURE_2D
3554 ? "texture_non_power_of_two"
3555 : "texture_rectangle";
3557 if (!strstr ((const char *) glGetString (GL_EXTENSIONS), ext)) {
3558 errx (1, "OpenGL does not support '%s' extension", ext);
3560 state.csock = Int_val (csock_v);
3561 state.rotate = Int_val (Field (params_v, 0));
3562 state.fitmodel = Int_val (Field (params_v, 1));
3563 trim_v = Field (params_v, 2);
3564 texcount = Int_val (Field (params_v, 3));
3565 state.sliceheight = Int_val (Field (params_v, 4));
3566 mustoresize = Int_val (Field (params_v, 5));
3567 colorspace = Int_val (Field (params_v, 6));
3568 fontpath = String_val (Field (params_v, 7));
3569 redirstderr = Bool_val (Field (params_v, 8));
3571 if (redirstderr) {
3572 if (pipe (state.pfds)) {
3573 err (1, errno, "pipe");
3575 for (int ntries = 0; ntries < 1737; ++ntries) {
3576 if (-1 == dup2 (state.pfds[1], 2)) {
3577 if (EINTR == errno) {
3578 continue;
3580 err (1, errno, "dup2");
3582 break;
3584 } else {
3585 state.pfds[0] = 0;
3586 state.pfds[1] = 0;
3589 #ifdef MACOS
3590 state.utf8cs = 1;
3591 #else
3592 /* http://www.cl.cam.ac.uk/~mgk25/unicode.html */
3593 if (setlocale (LC_CTYPE, "")) {
3594 const char *cset = nl_langinfo (CODESET);
3595 state.utf8cs = !strcmp (cset, "UTF-8");
3597 else {
3598 err (1, errno, "setlocale");
3600 #endif
3602 state.ctx = fz_new_context (NULL, NULL, mustoresize);
3603 fz_register_document_handlers (state.ctx);
3604 if (redirstderr) {
3605 fz_set_error_callback (state.ctx, diag_callback, "[e]");
3606 fz_set_warning_callback (state.ctx, diag_callback, "[w]");
3608 fz_install_load_system_font_funcs (state.ctx, NULL, NULL, lsff);
3610 state.trimmargins = Bool_val (Field (trim_v, 0));
3611 fuzz_v = Field (trim_v, 1);
3612 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
3613 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
3614 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
3615 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
3617 set_tex_params (colorspace);
3619 if (*fontpath) {
3620 state.face = load_font (fontpath);
3622 else {
3623 int len;
3624 const unsigned char *data;
3626 data = pdf_lookup_substitute_font (state.ctx, 0, 0, 0, 0, &len);
3627 state.face = load_builtin_font (data, len);
3629 if (!state.face) {
3630 _exit (1);
3633 realloctexts (texcount);
3634 makestippletex ();
3636 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
3637 if (ret) {
3638 errx (1, "pthread_create: %d(%s)", ret, strerror (ret));
3641 CAMLreturn (Val_int (state.pfds[0]));
3644 #if FIXME || !FIXME
3645 static void UNUSED_ATTR NO_OPTIMIZE_ATTR refmacs (void) {}
3646 #endif