Remove bogus(?) assert
[llpp.git] / link.c
blob8c4817ccb4f4cacbb8c564fadc69e8f10e2698dd
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 <locale.h>
8 #include <math.h>
9 #include <pthread.h>
10 #include <regex.h>
11 #include <spawn.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/ioctl.h>
16 #include <sys/stat.h>
17 #include <sys/uio.h>
18 #include <unistd.h>
19 #include <wchar.h>
21 #include GL_H
23 #define CAML_NAME_SPACE
24 #include <caml/fail.h>
25 #include <caml/alloc.h>
26 #include <caml/memory.h>
27 #include <caml/unixsupport.h>
29 #pragma GCC diagnostic push
30 #pragma GCC diagnostic ignored "-Wfloat-equal"
31 #include <mupdf/fitz.h>
32 #include <mupdf/pdf.h>
33 #pragma GCC diagnostic pop
35 #pragma GCC diagnostic push
36 #ifdef __clang__
37 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
38 #endif
39 #include <ft2build.h>
40 #include FT_FREETYPE_H
41 #pragma GCC diagnostic pop
43 #include "cutils.h"
45 #define ARSERT(c) !(c) ? errx (1, "%s:%d " #c, __FILE__, __LINE__) : (void) 0
46 #define ML(d) extern value ml_##d; value ml_##d
47 #define ML0(d) extern void ml_##d; void ml_##d
48 #define STTI(st) ((unsigned int) (st))
50 enum { Copen=23, Ccs, Cfreepage, Cfreetile, Csearch, Cgeometry, Creqlayout,
51 Cpage, Ctile, Ctrimset, Csettrim, Csliceh, Cinterrupt };
52 enum { FitWidth, FitProportional, FitPage };
53 enum { LDfirst, LDlast };
54 enum { LDfirstvisible, LDleft, LDright, LDdown, LDup };
55 enum { Uuri, Utext, Utextannot, Ufileannot, Unone };
56 enum { MarkPage, MarkBlock, MarkLine, MarkWord };
58 struct slice {
59 int h;
60 int texindex;
63 struct tile {
64 int w, h;
65 int slicecount;
66 int sliceheight;
67 fz_pixmap *pixmap;
68 struct slice slices[1];
71 struct pagedim {
72 int pageno;
73 int rotate;
74 int left;
75 int tctmready;
76 fz_irect bounds;
77 fz_rect pagebox;
78 fz_rect mediabox;
79 fz_matrix ctm, zoomctm, tctm;
82 struct slink {
83 enum { SLINK, SANNOT } tag;
84 fz_irect bbox;
85 union {
86 fz_link *link;
87 pdf_annot *annot;
88 } u;
91 struct annot {
92 fz_irect bbox;
93 pdf_annot *annot;
96 struct page {
97 int tgen;
98 int sgen;
99 int agen;
100 int pageno;
101 int pdimno;
102 fz_stext_page *text;
103 fz_page *fzpage;
104 fz_display_list *dlist;
105 fz_link *links;
106 int slinkcount;
107 struct slink *slinks;
108 int annotcount;
109 struct annot *annots;
110 fz_stext_char *fmark, *lmark;
113 static struct {
114 pthread_mutex_t mutex;
115 int sliceheight;
116 struct pagedim *pagedims;
117 int pagecount;
118 int pagedimcount;
119 fz_document *doc;
120 fz_context *ctx;
121 int w, h;
122 char *dcf;
123 int pfds[2];
125 struct {
126 int index, count;
127 GLuint *ids;
128 GLenum iform, form, ty;
129 struct {
130 int w, h;
131 struct slice *slice;
132 } *owners;
133 } tex;
135 fz_colorspace *colorspace;
136 float papercolor[4];
138 FT_Face face;
139 fz_pixmap *pig;
140 pthread_t thread;
141 fz_irect trimfuzz;
142 GLuint stid, boid;
143 int trimmargins, needoutline, gen, rotate, aalevel,
144 fitmodel, trimanew, csock, dirty, utf8cs;
146 GLfloat texcoords[8], vertices[16];
147 } state = { .mutex = PTHREAD_MUTEX_INITIALIZER };
149 static void lock (const char *cap)
151 int ret = pthread_mutex_lock (&state.mutex);
152 if (ret) {
153 errx (1, "%s: pthread_mutex_lock: %d(%s)", cap, ret, strerror (ret));
157 static void unlock (const char *cap)
159 int ret = pthread_mutex_unlock (&state.mutex);
160 if (ret) {
161 errx (1, "%s: pthread_mutex_unlock: %d(%s)", cap, ret, strerror (ret));
165 static int trylock (const char *cap)
167 int ret = pthread_mutex_trylock (&state.mutex);
168 if (ret && ret != EBUSY) {
169 errx (1, "%s: pthread_mutex_trylock: %d(%s)", cap,
170 ret, strerror (ret));
172 return ret == EBUSY;
175 static int hasdata (int fd)
177 int ret, avail;
178 ret = ioctl (fd, FIONREAD, &avail);
179 if (ret) {
180 err (1, errno, "hasdata: FIONREAD error ret=%d", ret);
182 return avail > 0;
185 ML (hasdata (value fd_v))
187 CAMLparam1 (fd_v);
188 CAMLreturn (Val_bool (hasdata (Int_val (fd_v))));
191 static void readdata (int fd, void *p, int size)
193 ssize_t n;
195 again:
196 n = read (fd, p, size);
197 if (n < 0) {
198 if (errno == EINTR) {
199 goto again;
201 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size, n);
203 if (n - size) {
204 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
208 static void writedata (int fd, char *p, int size)
210 ssize_t n;
211 uint32_t size4 = size;
212 struct iovec iov[2] = {
213 { .iov_base = &size4, .iov_len = 4 },
214 { .iov_base = p, .iov_len = size }
217 again:
218 n = writev (fd, iov, 2);
219 if (n < 0) {
220 if (errno == EINTR) {
221 goto again;
223 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
225 if (n - size - 4) {
226 errx (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
230 static int readlen (int fd)
232 uint32_t u;
233 readdata (fd, &u, 4);
234 return u;
237 ML0 (wcmd (value fd_v, value bytes_v, value len_v))
239 CAMLparam3 (fd_v, bytes_v, len_v);
240 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
241 CAMLreturn0;
244 ML (rcmd (value fd_v))
246 CAMLparam1 (fd_v);
247 CAMLlocal1 (strdata_v);
248 int fd = Int_val (fd_v);
249 int len = readlen (fd);
250 strdata_v = caml_alloc_string (len);
251 readdata (fd, Bytes_val (strdata_v), len);
252 CAMLreturn (strdata_v);
255 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
257 char fbuf[64];
258 int size = sizeof (fbuf), len;
259 va_list ap;
260 char *buf = fbuf;
262 for (;;) {
263 va_start (ap, fmt);
264 len = vsnprintf (buf, size, fmt, ap);
265 va_end (ap);
267 if (len > -1) {
268 if (len < size - 4) {
269 writedata (state.csock, buf, len);
270 break;
272 else {
273 size = len + 5;
276 else {
277 err (1, errno, "vsnprintf for `%s' failed", fmt);
279 buf = realloc (buf == fbuf ? NULL : buf, size);
280 if (!buf) {
281 err (1, errno, "realloc for temp buf (%d bytes) failed", size);
284 if (buf != fbuf) {
285 free (buf);
289 static void closedoc (void)
291 if (state.doc) {
292 fz_drop_document (state.ctx, state.doc);
293 state.doc = NULL;
297 static int openxref (char *filename, char *mimetype, char *password,
298 int w, int h, int em)
300 for (int i = 0; i < state.tex.count; ++i) {
301 state.tex.owners[i].w = -1;
302 state.tex.owners[i].slice = NULL;
305 closedoc ();
307 state.dirty = 0;
308 if (state.pagedims) {
309 free (state.pagedims);
310 state.pagedims = NULL;
312 state.pagedimcount = 0;
314 fz_set_aa_level (state.ctx, state.aalevel);
315 if (mimetype) {
316 fz_stream *st = fz_open_file (state.ctx, filename);
317 state.doc = fz_open_document_with_stream (state.ctx, mimetype, st);
319 else {
320 state.doc = fz_open_document (state.ctx, filename);
322 if (fz_needs_password (state.ctx, state.doc)) {
323 if (password && !*password) {
324 printd ("pass");
325 return 0;
327 else {
328 int ok = fz_authenticate_password (state.ctx, state.doc, password);
329 if (!ok) {
330 printd ("pass fail");
331 return 0;
335 if (w >= 0 || h >= 0 || em >=0) {
336 fz_layout_document (state.ctx, state.doc, w, h, em);
338 state.pagecount = fz_count_pages (state.ctx, state.doc);
339 if (state.pagecount < 0) {
340 state.pagecount = 0;
341 return 0;
343 return 1;
346 static void docinfo (void)
348 struct { char *tag; char *name; } tab[] = {
349 { FZ_META_INFO_TITLE, "Title" },
350 { FZ_META_INFO_AUTHOR, "Author" },
351 { FZ_META_FORMAT, "Format" },
352 { FZ_META_ENCRYPTION, "Encryption" },
353 { FZ_META_INFO_CREATOR, "Creator" },
354 { FZ_META_INFO_PRODUCER, "Producer" },
355 { FZ_META_INFO_CREATIONDATE, "Creation date" },
356 { FZ_META_INFO_MODIFICATIONDATE, "Modification date"},
358 int len = 0, need;
359 char *buf = NULL;
361 for (size_t i = 0; i < sizeof (tab) / sizeof (*tab); ++i) {
362 again:
363 need = fz_lookup_metadata (state.ctx, state.doc, tab[i].tag, buf, len);
364 if (need > 0) {
365 if (need <= len) {
366 printd ("info %s\t%s", tab[i].name, buf);
368 else {
369 buf = realloc (buf, need);
370 if (!buf) {
371 err (1, errno, "docinfo realloc %d", need);
373 len = need;
374 goto again;
378 free (buf);
380 printd ("infoend");
383 static void unlinktile (struct tile *tile)
385 for (int i = 0; i < tile->slicecount; ++i) {
386 struct slice *s = &tile->slices[i];
388 if (s->texindex != -1) {
389 if (state.tex.owners[s->texindex].slice == s) {
390 state.tex.owners[s->texindex].slice = NULL;
396 static void freepage (struct page *page)
398 if (page) {
399 fz_drop_stext_page (state.ctx, page->text);
400 free (page->slinks);
401 fz_drop_display_list (state.ctx, page->dlist);
402 fz_drop_page (state.ctx, page->fzpage);
403 free (page);
407 static void freetile (struct tile *tile)
409 unlinktile (tile);
410 fz_drop_pixmap (state.ctx, state.pig);
411 state.pig = tile->pixmap;
412 free (tile);
415 static void trimctm (pdf_page *page, int pindex)
417 struct pagedim *pdim = &state.pagedims[pindex];
419 if (!page) {
420 return;
422 if (!pdim->tctmready) {
423 fz_rect realbox, mediabox;
424 fz_matrix page_ctm, ctm;
426 ctm = fz_concat (fz_rotate (-pdim->rotate), fz_scale (1, -1));
427 realbox = fz_transform_rect (pdim->mediabox, ctm);
428 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
429 pdim->tctm = fz_concat (
430 fz_invert_matrix (page_ctm),
431 fz_concat (ctm, fz_translate (-realbox.x0, -realbox.y0)));
432 pdim->tctmready = 1;
436 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
438 fz_matrix ctm;
439 ptrdiff_t pdimno = pdim - state.pagedims;
441 ARSERT (pdim - state.pagedims < INT_MAX);
442 if (pdf_specifics (state.ctx, state.doc)) {
443 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
444 ctm = fz_concat (pdim->tctm, pdim->ctm);
446 else {
447 ctm = fz_concat (fz_translate (-pdim->mediabox.x0, -pdim->mediabox.y0),
448 pdim->ctm);
450 return ctm;
453 static fz_matrix pagectm (struct page *page)
455 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
458 static void *loadpage (int pageno, int pindex)
460 fz_device *dev;
461 struct page *page;
463 page = calloc (sizeof (struct page), 1);
464 if (!page) {
465 err (1, errno, "calloc page %d", pageno);
468 page->dlist = fz_new_display_list (state.ctx, fz_infinite_rect);
469 dev = fz_new_list_device (state.ctx, page->dlist);
470 fz_try (state.ctx) {
471 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
472 fz_run_page (state.ctx, page->fzpage, dev, fz_identity, NULL);
474 fz_catch (state.ctx) {
475 page->fzpage = NULL;
477 fz_close_device (state.ctx, dev);
478 fz_drop_device (state.ctx, dev);
480 page->pdimno = pindex;
481 page->pageno = pageno;
482 page->sgen = state.gen;
483 page->agen = state.gen;
484 page->tgen = state.gen;
485 return page;
488 static struct tile *alloctile (int h)
490 int slicecount;
491 size_t tilesize;
492 struct tile *tile;
494 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
495 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
496 tile = calloc (tilesize, 1);
497 if (!tile) {
498 err (1, errno, "cannot allocate tile (%zu bytes)", tilesize);
500 for (int i = 0; i < slicecount; ++i) {
501 int sh = fz_mini (h, state.sliceheight);
502 tile->slices[i].h = sh;
503 tile->slices[i].texindex = -1;
504 h -= sh;
506 tile->slicecount = slicecount;
507 tile->sliceheight = state.sliceheight;
508 return tile;
511 static struct tile *rendertile (struct page *page, int x, int y, int w, int h)
513 fz_irect bbox;
514 fz_matrix ctm;
515 fz_device *dev;
516 struct tile *tile;
517 struct pagedim *pdim;
519 tile = alloctile (h);
520 pdim = &state.pagedims[page->pdimno];
522 bbox = pdim->bounds;
523 bbox.x0 += x;
524 bbox.y0 += y;
525 bbox.x1 = bbox.x0 + w;
526 bbox.y1 = bbox.y0 + h;
528 if (state.pig) {
529 if (state.pig->w == w
530 && state.pig->h == h
531 && state.pig->colorspace == state.colorspace) {
532 tile->pixmap = state.pig;
533 tile->pixmap->x = bbox.x0;
534 tile->pixmap->y = bbox.y0;
536 else {
537 fz_drop_pixmap (state.ctx, state.pig);
539 state.pig = NULL;
541 if (!tile->pixmap) {
542 tile->pixmap = fz_new_pixmap_with_bbox (state.ctx,
543 state.colorspace,
544 bbox, NULL, 1);
547 tile->w = w;
548 tile->h = h;
549 fz_fill_pixmap_with_color (state.ctx, tile->pixmap,
550 fz_device_rgb (state.ctx),
551 state.papercolor,
552 fz_default_color_params);
554 dev = fz_new_draw_device (state.ctx, fz_identity, tile->pixmap);
555 ctm = pagectm (page);
556 fz_run_display_list (state.ctx, page->dlist, dev, ctm,
557 fz_rect_from_irect (bbox), NULL);
558 fz_close_device (state.ctx, dev);
559 fz_drop_device (state.ctx, dev);
561 return tile;
564 static void initpdims1 (void)
566 int shown = 0;
567 struct pagedim *p;
568 pdf_document *pdf;
569 fz_context *ctx = state.ctx;
570 int pageno, trim, show, cxcount;
571 fz_rect rootmediabox = fz_empty_rect;
573 fz_var (p);
574 fz_var (pdf);
575 fz_var (shown);
576 fz_var (pageno);
577 fz_var (cxcount);
579 cxcount = state.pagecount;
580 if ((pdf = pdf_specifics (ctx, state.doc))) {
581 pdf_obj *obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
582 "Root/Pages/MediaBox");
583 rootmediabox = pdf_to_rect (ctx, obj);
584 pdf_load_page_tree (ctx, pdf);
587 for (pageno = 0; pageno < cxcount; ++pageno) {
588 int rotate = 0;
589 fz_rect mediabox = fz_empty_rect;
591 fz_var (rotate);
592 if (pdf) {
593 pdf_obj *pageobj = NULL;
595 fz_var (pageobj);
596 if (pdf->rev_page_map) {
597 for (int i = 0; i < pdf->rev_page_count; ++i) {
598 if (pdf->rev_page_map[i].page == pageno) {
599 pageobj = pdf_get_xref_entry (
600 ctx, pdf, pdf->rev_page_map[i].object
601 )->obj;
602 break;
606 if (!pageobj) {
607 pageobj = pdf_lookup_page_obj (ctx, pdf, pageno);
610 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
612 if (state.trimmargins) {
613 pdf_obj *obj;
614 pdf_page *page;
616 fz_try (ctx) {
617 page = pdf_load_page (ctx, pdf, pageno);
618 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
619 trim = state.trimanew || !obj;
620 if (trim) {
621 fz_rect rect;
622 fz_device *dev;
623 fz_matrix ctm, page_ctm;
625 dev = fz_new_bbox_device (ctx, &rect);
626 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
627 ctm = fz_invert_matrix (page_ctm);
628 pdf_run_page (ctx, page, dev, fz_identity, NULL);
629 fz_close_device (ctx, dev);
630 fz_drop_device (ctx, dev);
632 rect.x0 += state.trimfuzz.x0;
633 rect.x1 += state.trimfuzz.x1;
634 rect.y0 += state.trimfuzz.y0;
635 rect.y1 += state.trimfuzz.y1;
636 rect = fz_transform_rect (rect, ctm);
637 rect = fz_intersect_rect (rect, mediabox);
639 if (!fz_is_empty_rect (rect)) {
640 mediabox = rect;
643 obj = pdf_new_array (ctx, pdf, 4);
644 pdf_array_push_real (ctx, obj, mediabox.x0);
645 pdf_array_push_real (ctx, obj, mediabox.y0);
646 pdf_array_push_real (ctx, obj, mediabox.x1);
647 pdf_array_push_real (ctx, obj, mediabox.y1);
648 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
650 else {
651 mediabox.x0 = pdf_array_get_real (ctx, obj, 0);
652 mediabox.y0 = pdf_array_get_real (ctx, obj, 1);
653 mediabox.x1 = pdf_array_get_real (ctx, obj, 2);
654 mediabox.y1 = pdf_array_get_real (ctx, obj, 3);
657 fz_drop_page (ctx, &page->super);
658 show = (pageno + 1 == state.pagecount)
659 || (trim ? pageno % 5 == 0 : pageno % 20 == 0);
660 if (show) {
661 printd ("progress %f Trimming %d",
662 (double) (pageno + 1) / state.pagecount,
663 pageno + 1);
666 fz_catch (ctx) {
667 printd ("emsg failed to load page %d", pageno);
670 else {
671 int empty = 0;
672 fz_rect cropbox;
674 mediabox =
675 pdf_to_rect (ctx,
676 pdf_dict_get_inheritable (
677 ctx,
678 pageobj,
679 PDF_NAME (MediaBox)
682 if (fz_is_empty_rect (mediabox)) {
683 mediabox.x0 = 0;
684 mediabox.y0 = 0;
685 mediabox.x1 = 612;
686 mediabox.y1 = 792;
687 empty = 1;
690 cropbox =
691 pdf_to_rect (ctx, pdf_dict_gets (ctx, pageobj, "CropBox"));
692 if (!fz_is_empty_rect (cropbox)) {
693 if (empty) {
694 mediabox = cropbox;
696 else {
697 mediabox = fz_intersect_rect (mediabox, cropbox);
700 else {
701 if (empty) {
702 if (fz_is_empty_rect (rootmediabox)) {
703 printd ("emsg cannot find size of page %d",
704 pageno);
706 else {
707 mediabox = rootmediabox;
713 else {
714 if (state.trimmargins) {
715 fz_page *page;
717 fz_try (ctx) {
718 page = fz_load_page (ctx, state.doc, pageno);
719 mediabox = fz_bound_page (ctx, page);
720 if (state.trimmargins) {
721 fz_rect rect;
722 fz_device *dev;
724 dev = fz_new_bbox_device (ctx, &rect);
725 fz_run_page (ctx, page, dev, fz_identity, NULL);
726 fz_close_device (ctx, dev);
727 fz_drop_device (ctx, dev);
729 rect.x0 += state.trimfuzz.x0;
730 rect.x1 += state.trimfuzz.x1;
731 rect.y0 += state.trimfuzz.y0;
732 rect.y1 += state.trimfuzz.y1;
733 rect = fz_intersect_rect (rect, mediabox);
735 if (!fz_is_empty_rect (rect)) {
736 mediabox = rect;
739 fz_drop_page (ctx, page);
741 fz_catch (ctx) {
744 else {
745 fz_page *page;
746 fz_try (ctx) {
747 page = fz_load_page (ctx, state.doc, pageno);
748 mediabox = fz_bound_page (ctx, page);
749 fz_drop_page (ctx, page);
751 show = !state.trimmargins && pageno % 20 == 0;
752 if (show) {
753 shown = 1;
754 printd ("progress %f Gathering dimensions %d",
755 (double) pageno / state.pagecount, pageno);
758 fz_catch (ctx) {
759 printd ("emsg failed to load page %d", pageno);
763 if (state.pagedimcount == 0
764 || ((void) (p = &state.pagedims[state.pagedimcount-1])
765 , p->rotate != rotate)
766 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
767 size_t size;
769 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
770 state.pagedims = realloc (state.pagedims, size);
771 if (!state.pagedims) {
772 err (1, errno, "realloc pagedims to %zu (%d elems)",
773 size, state.pagedimcount + 1);
776 p = &state.pagedims[state.pagedimcount++];
777 p->rotate = rotate;
778 p->mediabox = mediabox;
779 p->pageno = pageno;
782 state.trimanew = 0;
783 if (shown) {
784 printd ("progress 1");
788 static void initpdims (void)
790 FILE *f = state.dcf ? fopen (state.dcf, "rb") : NULL;
791 if (f) {
792 size_t nread;
794 nread = fread (&state.pagedimcount, sizeof (state.pagedimcount), 1, f);
795 if (nread - 1) {
796 err (1, errno, "fread pagedim %zu", sizeof (state.pagedimcount));
798 size_t size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
799 state.pagedims = realloc (state.pagedims, size);
800 if (!state.pagedims) {
801 err (1, errno, "realloc pagedims to %zu (%d elems)",
802 size, state.pagedimcount + 1);
804 if (fread (state.pagedims,
805 sizeof (*state.pagedims),
806 state.pagedimcount+1,
807 f) - (state.pagedimcount+1)) {
808 err (1, errno, "fread pagedim data %zu %d",
809 sizeof (*state.pagedims), state.pagedimcount+1);
811 fclose (f);
814 if (!state.pagedims) {
815 initpdims1 ();
816 if (state.dcf) {
817 f = fopen (state.dcf, "wb");
818 if (!f) {
819 err (1, errno, "fopen %s for writing", state.dcf);
821 if (fwrite (&state.pagedimcount,
822 sizeof (state.pagedimcount), 1, f) - 1) {
823 err (1, errno, "fwrite pagedimcunt %zu",
824 sizeof (state.pagedimcount));
826 if (fwrite (state.pagedims, sizeof (*state.pagedims),
827 state.pagedimcount + 1, f)
828 - (state.pagedimcount + 1)) {
829 err (1, errno, "fwrite pagedim data %zu %u",
830 sizeof (*state.pagedims), state.pagedimcount+1);
832 fclose (f);
837 static void layout (void)
839 int pindex;
840 fz_rect box;
841 fz_matrix ctm;
842 struct pagedim *p = NULL;
843 float zw, w, maxw = 0.0, zoom = 1.0;
845 if (state.pagedimcount == 0) {
846 return;
849 switch (state.fitmodel) {
850 case FitProportional:
851 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
852 float x0, x1;
854 p = &state.pagedims[pindex];
855 box = fz_transform_rect (p->mediabox,
856 fz_rotate (p->rotate + state.rotate));
858 x0 = fz_min (box.x0, box.x1);
859 x1 = fz_max (box.x0, box.x1);
861 w = x1 - x0;
862 maxw = fz_max (w, maxw);
863 zoom = state.w / maxw;
865 break;
867 case FitPage:
868 maxw = state.w;
869 break;
871 case FitWidth:
872 break;
874 default:
875 ARSERT (0 && state.fitmodel);
878 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
879 p = &state.pagedims[pindex];
880 ctm = fz_rotate (state.rotate);
881 box = fz_transform_rect (p->mediabox,
882 fz_rotate (p->rotate + state.rotate));
883 w = box.x1 - box.x0;
884 switch (state.fitmodel) {
885 case FitProportional:
886 p->left = (int) (((maxw - w) * zoom) / 2.f);
887 break;
888 case FitPage:
890 float zh, h;
891 zw = maxw / w;
892 h = box.y1 - box.y0;
893 zh = state.h / h;
894 zoom = fz_min (zw, zh);
895 p->left = (int) ((maxw - (w * zoom)) / 2.f);
897 break;
898 case FitWidth:
899 p->left = 0;
900 zoom = state.w / w;
901 break;
904 p->zoomctm = fz_scale (zoom, zoom);
905 ctm = fz_concat (p->zoomctm, ctm);
907 p->pagebox = p->mediabox;
908 p->pagebox = fz_transform_rect (p->pagebox, fz_rotate (p->rotate));
909 p->pagebox.x1 -= p->pagebox.x0;
910 p->pagebox.y1 -= p->pagebox.y0;
911 p->pagebox.x0 = 0;
912 p->pagebox.y0 = 0;
913 p->bounds = fz_round_rect (fz_transform_rect (p->pagebox, ctm));
914 p->ctm = ctm;
916 ctm = fz_concat (fz_translate (0, -p->mediabox.y1),
917 fz_scale (zoom, -zoom));
918 p->tctmready = 0;
921 do {
922 printd ("pdim %u %d %d %d", p->pageno, p->left,
923 abs (p->bounds.x0 - p->bounds.x1),
924 abs (p->bounds.y0 - p->bounds.y1));
925 } while (p-- != state.pagedims);
928 static struct pagedim *pdimofpageno (int pageno)
930 struct pagedim *pdim = state.pagedims;
932 for (int i = 0; i < state.pagedimcount; ++i) {
933 if (state.pagedims[i].pageno > pageno) {
934 break;
936 pdim = &state.pagedims[i];
938 return pdim;
941 static void recurse_outline (fz_outline *outline, int level)
943 while (outline) {
944 int pageno;
945 fz_point p;
946 fz_location loc;
948 loc = fz_resolve_link (state.ctx, state.doc, String_val (outline->uri),
949 &p.x, &p.y);
950 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
951 if (pageno >= 0) {
952 struct pagedim *pdim =
953 pdimofpageno (
954 fz_page_number_from_location (state.ctx, state.doc,
955 outline->page)
957 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
958 p = fz_transform_point (p, pdim->ctm);
959 printd ("o %d %d %d %d %s",
960 level, pageno, (int) p.y, h, outline->title);
962 else {
963 printd ("on %d %s", level, outline->title);
965 if (outline->down) {
966 recurse_outline (outline->down, level + 1);
968 outline = outline->next;
972 static void process_outline (void)
974 if (state.needoutline && state.pagedimcount) {
975 fz_outline *outline = NULL;
977 fz_var (outline);
978 fz_try (state.ctx) {
979 outline = fz_load_outline (state.ctx, state.doc);
980 state.needoutline = 0;
981 if (outline) {
982 recurse_outline (outline, 0);
985 fz_always (state.ctx) {
986 if (outline) {
987 fz_drop_outline (state.ctx, outline);
990 fz_catch (state.ctx) {
991 printd ("emsg %s", fz_caught_message (state.ctx));
996 static char *strofline (fz_stext_line *line)
998 char *p;
999 char utf8[10];
1000 fz_stext_char *ch;
1001 size_t size = 0, cap = 80;
1003 p = malloc (cap + 1);
1004 if (!p) {
1005 return NULL;
1008 for (ch = line->first_char; ch; ch = ch->next) {
1009 int n = fz_runetochar (utf8, ch->c);
1010 if (size + n > cap) {
1011 cap *= 2;
1012 p = realloc (p, cap + 1);
1013 if (!p) {
1014 return NULL;
1018 memcpy (p + size, utf8, n);
1019 size += n;
1021 p[size] = 0;
1022 return p;
1025 enum a_searchresult { Found=61, NotFound, Interrupted, Error };
1027 static enum a_searchresult matchline (regex_t *re, fz_stext_line *line,
1028 int num_matches, int pageno)
1030 int ret;
1031 char *p;
1032 regmatch_t rm;
1034 p = strofline (line);
1035 if (!p) {
1036 return Error;
1039 ret = regexec (re, p, 1, &rm, 0);
1040 free (p);
1042 if (ret) {
1043 if (ret != REG_NOMATCH) {
1044 int isize;
1045 size_t size;
1046 char errbuf[80], *trail;
1048 size = regerror (ret, re, errbuf, sizeof (errbuf));
1049 if (size > 23) {
1050 isize = 23;
1051 trail = "...";
1053 else {
1054 isize = (int) size;
1055 trail = "";
1057 printd ("emsg regexec error '%*s%s'", isize, errbuf, trail);
1058 return Error;
1060 return NotFound;
1062 else {
1063 int o = 0;
1064 fz_quad s = line->first_char->quad, e;
1065 fz_stext_char *ch;
1067 if (rm.rm_so == rm.rm_eo) {
1068 return Found;
1071 for (ch = line->first_char; ch; ch = ch->next) {
1072 o += fz_runelen (ch->c);
1073 if (o > rm.rm_so) {
1074 s = ch->quad;
1075 break;
1078 for (;ch; ch = ch->next) {
1079 o += fz_runelen (ch->c);
1080 if (o > rm.rm_eo) {
1081 break;
1084 e = ch->quad;
1086 printd ("match %d %d %f %f %f %f %f %f %f %f",
1087 pageno, num_matches,
1088 s.ul.x, s.ul.y,
1089 e.ur.x, s.ul.y,
1090 e.lr.x, e.lr.y,
1091 s.ul.x, e.lr.y);
1092 return Found;
1096 /* wishful thinking function */
1097 static void search (regex_t *re, int pageno, int y, int forward)
1099 fz_device *tdev;
1100 double dur, start;
1101 char *cap = "bug";
1102 struct pagedim *pdim;
1103 fz_page *page = NULL;
1104 fz_stext_block *block;
1105 fz_stext_page *text = NULL;
1106 int niters = 0, num_matches = 0;
1107 enum a_searchresult the_searchresult = NotFound;
1109 start = now ();
1110 while (pageno >= 0 && pageno < state.pagecount && num_matches == 0) {
1111 if (niters++ == 5) {
1112 niters = 0;
1113 if (hasdata (state.csock)) {
1114 fz_drop_stext_page (state.ctx, text);
1115 fz_drop_page (state.ctx, page);
1116 the_searchresult = Interrupted;
1117 break;
1119 else {
1120 printd ("progress %f searching in page %d",
1121 (double) (pageno + 1) / state.pagecount, pageno);
1124 pdim = pdimofpageno (pageno);
1125 text = fz_new_stext_page (state.ctx, pdim->mediabox);
1126 tdev = fz_new_stext_device (state.ctx, text, 0);
1128 page = fz_load_page (state.ctx, state.doc, pageno);
1129 fz_run_page (state.ctx, page, tdev, pagectm1 (page, pdim), NULL);
1131 fz_close_device (state.ctx, tdev);
1132 fz_drop_device (state.ctx, tdev);
1134 if (forward) {
1135 for (block = text->first_block; block; block = block->next) {
1136 fz_stext_line *line;
1138 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1139 continue;
1142 for (line = block->u.t.first_line; line; line = line->next) {
1143 if (line->bbox.y0 < y + 1) {
1144 continue;
1147 the_searchresult =
1148 matchline (re, line, num_matches, pageno);
1149 num_matches += the_searchresult == Found;
1153 else {
1154 for (block = text->last_block; block; block = block->prev) {
1155 fz_stext_line *line;
1157 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1158 continue;
1161 for (line = block->u.t.last_line; line; line = line->prev) {
1162 if (line->bbox.y0 < y + 1) {
1163 continue;
1166 the_searchresult =
1167 matchline (re, line, num_matches, pageno);
1168 num_matches += the_searchresult == Found;
1173 if (forward) {
1174 pageno += 1;
1175 y = 0;
1177 else {
1178 pageno -= 1;
1179 y = INT_MAX;
1181 fz_drop_stext_page (state.ctx, text);
1182 text = NULL;
1183 fz_drop_page (state.ctx, page);
1184 page = NULL;
1186 dur = now () - start;
1187 switch (the_searchresult) {
1188 case Found: case NotFound: cap = ""; break;
1189 case Error: cap = "error "; break;
1190 case Interrupted: cap = "interrupt "; break;
1192 if (num_matches) {
1193 printd ("progress 1 %sfound %d in %f sec", cap, num_matches, dur);
1195 else {
1196 printd ("progress 1 %sfound nothing in %f sec", cap, dur);
1198 printd ("clearrects");
1201 static void set_tex_params (int colorspace)
1203 switch (colorspace) {
1204 case 0:
1205 state.tex.iform = GL_RGBA8;
1206 state.tex.form = GL_RGBA;
1207 state.tex.ty = GL_UNSIGNED_BYTE;
1208 state.colorspace = fz_device_rgb (state.ctx);
1209 break;
1210 case 1:
1211 state.tex.iform = GL_LUMINANCE_ALPHA;
1212 state.tex.form = GL_LUMINANCE_ALPHA;
1213 state.tex.ty = GL_UNSIGNED_BYTE;
1214 state.colorspace = fz_device_gray (state.ctx);
1215 break;
1216 default:
1217 errx (1, "invalid colorspce %d", colorspace);
1221 static void realloctexts (int texcount)
1223 size_t size;
1225 if (texcount == state.tex.count) {
1226 return;
1229 if (texcount < state.tex.count) {
1230 glDeleteTextures (state.tex.count - texcount, state.tex.ids + texcount);
1233 size = texcount * (sizeof (*state.tex.ids) + sizeof (*state.tex.owners));
1234 state.tex.ids = realloc (state.tex.ids, size);
1235 if (!state.tex.ids) {
1236 err (1, errno, "realloc texs %zu", size);
1239 state.tex.owners = (void *) (state.tex.ids + texcount);
1240 if (texcount > state.tex.count) {
1241 glGenTextures (texcount - state.tex.count,
1242 state.tex.ids + state.tex.count);
1243 for (int i = state.tex.count; i < texcount; ++i) {
1244 state.tex.owners[i].w = -1;
1245 state.tex.owners[i].slice = NULL;
1248 state.tex.count = texcount;
1249 state.tex.index = 0;
1252 static char *mbtoutf8 (char *s)
1254 char *p, *r;
1255 wchar_t *tmp;
1256 size_t i, ret, len;
1258 if (state.utf8cs) {
1259 return s;
1262 len = mbstowcs (NULL, s, strlen (s));
1263 if (len == 0 || len == (size_t) -1) {
1264 if (len) {
1265 printd ("emsg mbtoutf8: mbstowcs: %d(%s)", errno, strerror (errno));
1267 return s;
1270 tmp = calloc (len, sizeof (wchar_t));
1271 if (!tmp) {
1272 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d(%s)",
1273 len, sizeof (wchar_t), errno, strerror (errno));
1274 return s;
1277 ret = mbstowcs (tmp, s, len);
1278 if (ret == (size_t) -1) {
1279 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d(%s)",
1280 len, errno, strerror (errno));
1281 free (tmp);
1282 return s;
1285 len = 0;
1286 for (i = 0; i < ret; ++i) {
1287 len += fz_runelen (tmp[i]);
1290 p = r = malloc (len + 1);
1291 if (!r) {
1292 printd ("emsg mbtoutf8: malloc(%zu)", len);
1293 free (tmp);
1294 return s;
1297 for (i = 0; i < ret; ++i) {
1298 p += fz_runetochar (p, tmp[i]);
1300 *p = 0;
1301 free (tmp);
1302 return r;
1305 ML (mbtoutf8 (value s_v))
1307 CAMLparam1 (s_v);
1308 CAMLlocal1 (ret_v);
1309 char *s, *r;
1311 s = &Byte (s_v, 0);
1312 r = mbtoutf8 (s);
1313 if (r == s) {
1314 ret_v = s_v;
1316 else {
1317 ret_v = caml_copy_string (r);
1318 free (r);
1320 CAMLreturn (ret_v);
1323 static void *mainloop (void UNUSED_ATTR *unused)
1325 char *p = NULL, c;
1326 int len, ret, oldlen = 0;
1328 fz_var (p);
1329 fz_var (oldlen);
1330 for (;;) {
1331 len = readlen (state.csock);
1332 if (len == 0) {
1333 errx (1, "readlen returned 0");
1336 if (oldlen < len) {
1337 p = realloc (p, len);
1338 if (!p) {
1339 err (1, errno, "realloc %d failed", len);
1341 oldlen = len;
1343 readdata (state.csock, p, len);
1344 c = p[len-1];
1345 p[len-1] = 0;
1347 switch (c) {
1348 case Copen: {
1349 int off, usedoccss, ok = 0;
1350 int w, h, em;
1351 char *password, *mimetype, *filename, *utf8filename;
1352 size_t filenamelen, mimetypelen;
1354 fz_var (ok);
1355 ret = sscanf (p, "%d %d %d %d %n", &usedoccss, &w, &h, &em, &off);
1356 if (ret != 4) {
1357 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1360 filename = p + off;
1361 filenamelen = strlen (filename);
1363 mimetype = filename + filenamelen + 1;
1364 mimetypelen = strlen (mimetype);
1366 password = mimetype + mimetypelen + 1;
1368 if (password[strlen (password) + 1]) {
1369 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1372 lock ("open");
1373 fz_set_use_document_css (state.ctx, usedoccss);
1374 fz_try (state.ctx) {
1375 ok = openxref (filename, mimetypelen ? mimetype : NULL,
1376 password, w, h, em);
1378 fz_catch (state.ctx) {
1379 utf8filename = mbtoutf8 (filename);
1380 printd ("emsg failed to load %s: %s", utf8filename,
1381 fz_caught_message (state.ctx));
1382 if (utf8filename != filename) {
1383 free (utf8filename);
1386 if (ok) {
1387 docinfo ();
1388 initpdims ();
1390 unlock ("open");
1391 state.needoutline = ok;
1392 break;
1394 case Ccs: {
1395 int i, colorspace;
1397 ret = sscanf (p, "%d", &colorspace);
1398 if (ret != 1) {
1399 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1401 lock ("cs");
1402 set_tex_params (colorspace);
1403 for (i = 0; i < state.tex.count; ++i) {
1404 state.tex.owners[i].w = -1;
1405 state.tex.owners[i].slice = NULL;
1407 unlock ("cs");
1408 break;
1410 case Cfreepage: {
1411 void *ptr;
1413 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1414 if (ret != 1) {
1415 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1417 lock ("freepage");
1418 freepage (ptr);
1419 unlock ("freepage");
1420 break;
1422 case Cfreetile: {
1423 void *ptr;
1425 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1426 if (ret != 1) {
1427 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1429 lock ("freetile");
1430 freetile (ptr);
1431 unlock ("freetile");
1432 break;
1434 case Csearch: {
1435 int icase, pageno, y, len2, forward;
1436 regex_t re;
1438 ret = sscanf (p, "%d %d %d %d,%n",
1439 &icase, &pageno, &y, &forward, &len2);
1440 if (ret != 4) {
1441 errx (1, "malformed search `%s' ret=%d", p, ret);
1444 char *pat = p + len2;
1445 ret = regcomp (&re, pat, REG_EXTENDED | (icase ? REG_ICASE : 0));
1446 if (ret) {
1447 char errbuf[80];
1448 size_t size;
1450 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1451 printd ("emsg regcomp failed `%.*s'", (int) size, errbuf);
1453 else {
1454 lock ("search");
1455 search (&re, pageno, y, forward);
1456 unlock ("search");
1457 regfree (&re);
1459 break;
1461 case Cgeometry: {
1462 int w, h, fitmodel;
1464 printd ("clear");
1465 ret = sscanf (p, "%d %d %d", &w, &h, &fitmodel);
1466 if (ret != 3) {
1467 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1470 lock ("geometry");
1471 state.h = h;
1472 if (w != state.w) {
1473 state.w = w;
1474 for (int i = 0; i < state.tex.count; ++i) {
1475 state.tex.owners[i].slice = NULL;
1478 state.fitmodel = fitmodel;
1479 layout ();
1480 process_outline ();
1482 state.gen++;
1483 unlock ("geometry");
1484 printd ("continue %d", state.pagecount);
1485 break;
1487 case Creqlayout: {
1488 char *nameddest;
1489 int rotate, off, h;
1490 int fitmodel;
1491 pdf_document *pdf;
1493 printd ("clear");
1494 ret = sscanf (p, "%d %d %d %n", &rotate, &fitmodel, &h, &off);
1495 if (ret != 3) {
1496 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1498 lock ("reqlayout");
1499 pdf = pdf_specifics (state.ctx, state.doc);
1500 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1501 state.gen += 1;
1503 state.rotate = rotate;
1504 state.fitmodel = fitmodel;
1505 state.h = h;
1506 layout ();
1507 process_outline ();
1509 nameddest = p + off;
1510 if (pdf && nameddest && *nameddest) {
1511 fz_point xy;
1512 struct pagedim *pdim;
1513 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1514 &xy.x, &xy.y);
1515 pdim = pdimofpageno (pageno);
1516 xy = fz_transform_point (xy, pdim->ctm);
1517 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1520 state.gen++;
1521 unlock ("reqlayout");
1522 printd ("continue %d", state.pagecount);
1523 break;
1525 case Cpage: {
1526 double a, b;
1527 struct page *page;
1528 int pageno, pindex;
1530 ret = sscanf (p, "%d %d", &pageno, &pindex);
1531 if (ret != 2) {
1532 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1535 lock ("page");
1536 a = now ();
1537 page = loadpage (pageno, pindex);
1538 b = now ();
1539 unlock ("page");
1541 printd ("page %" PRIxPTR " %f", (uintptr_t) page, b - a);
1542 break;
1544 case Ctile: {
1545 int x, y, w, h;
1546 struct page *page;
1547 struct tile *tile;
1548 double a, b;
1550 ret = sscanf (p, "%" SCNxPTR " %d %d %d %d",
1551 (uintptr_t *) &page, &x, &y, &w, &h);
1552 if (ret != 5) {
1553 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1556 lock ("tile");
1557 a = now ();
1558 tile = rendertile (page, x, y, w, h);
1559 b = now ();
1560 unlock ("tile");
1562 printd ("tile %d %d %" PRIxPTR " %u %f",
1563 x, y, (uintptr_t) tile,
1564 tile->w * tile->h * tile->pixmap->n, b - a);
1565 break;
1567 case Ctrimset: {
1568 fz_irect fuzz;
1569 int trimmargins;
1571 ret = sscanf (p, "%d %d %d %d %d",
1572 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1573 if (ret != 5) {
1574 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1577 lock ("trimset");
1578 state.trimmargins = trimmargins;
1579 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1580 state.trimanew = 1;
1581 state.trimfuzz = fuzz;
1583 unlock ("trimset");
1584 break;
1586 case Csettrim: {
1587 fz_irect fuzz;
1588 int trimmargins;
1590 ret = sscanf (p, "%d %d %d %d %d", &trimmargins,
1591 &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1592 if (ret != 5) {
1593 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1595 printd ("clear");
1596 lock ("settrim");
1597 state.trimmargins = trimmargins;
1598 state.needoutline = 1;
1599 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1600 state.trimanew = 1;
1601 state.trimfuzz = fuzz;
1603 state.pagedimcount = 0;
1604 free (state.pagedims);
1605 state.pagedims = NULL;
1606 initpdims ();
1607 layout ();
1608 process_outline ();
1609 unlock ("settrim");
1610 printd ("continue %d", state.pagecount);
1611 break;
1613 case Csliceh: {
1614 int h;
1616 ret = sscanf (p, "%d", &h);
1617 if (ret != 1) {
1618 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1620 if (h != state.sliceheight) {
1621 state.sliceheight = h;
1622 for (int i = 0; i < state.tex.count; ++i) {
1623 state.tex.owners[i].w = -1;
1624 state.tex.owners[i].h = -1;
1625 state.tex.owners[i].slice = NULL;
1628 break;
1630 case Cinterrupt:
1631 printd ("vmsg interrupted");
1632 break;
1633 default:
1634 errx (1, "unknown llpp ffi command - %d [%.*s]", c, len, p);
1637 return 0;
1640 ML (isexternallink (value uri_v))
1642 CAMLparam1 (uri_v);
1643 CAMLreturn (Val_bool (fz_is_external_link (state.ctx, String_val (uri_v))));
1646 ML (uritolocation (value uri_v))
1648 CAMLparam1 (uri_v);
1649 CAMLlocal1 (ret_v);
1650 fz_location loc;
1651 int pageno;
1652 fz_point xy;
1653 struct pagedim *pdim;
1655 loc = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1656 &xy.x, &xy.y);
1657 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
1658 pdim = pdimofpageno (pageno);
1659 xy = fz_transform_point (xy, pdim->ctm);
1660 ret_v = caml_alloc_tuple (3);
1661 Field (ret_v, 0) = Val_int (pageno);
1662 Field (ret_v, 1) = caml_copy_double ((double) xy.x);
1663 Field (ret_v, 2) = caml_copy_double ((double) xy.y);
1664 CAMLreturn (ret_v);
1667 ML (realloctexts (value texcount_v))
1669 CAMLparam1 (texcount_v);
1670 int ok;
1672 if (trylock (__func__)) {
1673 ok = 0;
1674 goto done;
1676 realloctexts (Int_val (texcount_v));
1677 ok = 1;
1678 unlock (__func__);
1680 done:
1681 CAMLreturn (Val_bool (ok));
1684 static void recti (int x0, int y0, int x1, int y1)
1686 GLfloat *v = state.vertices;
1688 glVertexPointer (2, GL_FLOAT, 0, v);
1689 v[0] = x0; v[1] = y0;
1690 v[2] = x1; v[3] = y0;
1691 v[4] = x0; v[5] = y1;
1692 v[6] = x1; v[7] = y1;
1693 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1696 static void showsel (struct page *page, int ox, int oy)
1698 fz_irect bbox;
1699 fz_rect rect;
1700 fz_stext_block *block;
1701 int seen = 0;
1702 unsigned char selcolor[] = {15,15,15,140};
1704 if (!page->fmark || !page->lmark) {
1705 return;
1708 glEnable (GL_BLEND);
1709 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1710 glColor4ubv (selcolor);
1712 ox += state.pagedims[page->pdimno].bounds.x0;
1713 oy += state.pagedims[page->pdimno].bounds.y0;
1715 for (block = page->text->first_block; block; block = block->next) {
1716 fz_stext_line *line;
1718 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1719 continue;
1721 for (line = block->u.t.first_line; line; line = line->next) {
1722 fz_stext_char *ch;
1724 rect = fz_empty_rect;
1725 for (ch = line->first_char; ch; ch = ch->next) {
1726 fz_rect r;
1727 if (ch == page->fmark) {
1728 seen = 1;
1730 r = fz_rect_from_quad (ch->quad);
1731 if (seen) {
1732 rect = fz_union_rect (rect, r);
1734 if (ch == page->lmark) {
1735 bbox = fz_round_rect (rect);
1736 recti (bbox.x0 + ox, bbox.y0 + oy,
1737 bbox.x1 + ox, bbox.y1 + oy);
1738 goto done;
1741 if (!fz_is_empty_rect (rect)) {
1742 bbox = fz_round_rect (rect);
1743 recti (bbox.x0 + ox, bbox.y0 + oy,
1744 bbox.x1 + ox, bbox.y1 + oy);
1748 done:
1749 glDisable (GL_BLEND);
1752 #pragma GCC diagnostic push
1753 #pragma GCC diagnostic ignored "-Wconversion"
1754 #include "glfont.c"
1755 #pragma GCC diagnostic pop
1757 static void stipplerect (fz_matrix m, fz_point p[4],
1758 GLfloat *texcoords, GLfloat *vertices)
1760 fz_point p1 = fz_transform_point (p[0], m);
1761 fz_point p2 = fz_transform_point (p[1], m);
1762 fz_point p3 = fz_transform_point (p[2], m);
1763 fz_point p4 = fz_transform_point (p[3], m);
1765 float w = p2.x - p1.x;
1766 float h = p2.y - p1.y;
1767 float t = hypotf (w, h) * .25f;
1769 w = p3.x - p2.x;
1770 h = p3.y - p2.y;
1771 float s = hypotf (w, h) * .25f;
1773 texcoords[0] = 0; vertices[0] = p1.x; vertices[1] = p1.y;
1774 texcoords[1] = t; vertices[2] = p2.x; vertices[3] = p2.y;
1776 texcoords[2] = 0; vertices[4] = p2.x; vertices[5] = p2.y;
1777 texcoords[3] = s; vertices[6] = p3.x; vertices[7] = p3.y;
1779 texcoords[4] = 0; vertices[8] = p3.x; vertices[9] = p3.y;
1780 texcoords[5] = t; vertices[10] = p4.x; vertices[11] = p4.y;
1782 texcoords[6] = 0; vertices[12] = p4.x; vertices[13] = p4.y;
1783 texcoords[7] = s; vertices[14] = p1.x; vertices[15] = p1.y;
1785 glDrawArrays (GL_LINES, 0, 8);
1788 static void ensurelinks (struct page *page)
1790 if (!page->links) {
1791 page->links = fz_load_links (state.ctx, page->fzpage);
1795 static void highlightlinks (struct page *page, int xoff, int yoff)
1797 fz_point p[4];
1798 fz_matrix ctm;
1799 fz_link *link;
1800 GLfloat *texcoords = state.texcoords;
1801 GLfloat *vertices = state.vertices;
1803 ensurelinks (page);
1805 glEnable (GL_TEXTURE_1D);
1806 glEnable (GL_BLEND);
1807 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1808 glBindTexture (GL_TEXTURE_1D, state.stid);
1810 xoff -= state.pagedims[page->pdimno].bounds.x0;
1811 yoff -= state.pagedims[page->pdimno].bounds.y0;
1812 ctm = fz_concat (pagectm (page), fz_translate (xoff, yoff));
1814 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
1815 glVertexPointer (2, GL_FLOAT, 0, vertices);
1817 for (link = page->links; link; link = link->next) {
1819 p[0].x = link->rect.x0;
1820 p[0].y = link->rect.y0;
1822 p[1].x = link->rect.x1;
1823 p[1].y = link->rect.y0;
1825 p[2].x = link->rect.x1;
1826 p[2].y = link->rect.y1;
1828 p[3].x = link->rect.x0;
1829 p[3].y = link->rect.y1;
1831 /* TODO: different colours for different schemes */
1832 if (fz_is_external_link (state.ctx, link->uri)) {
1833 glColor3ub (0, 0, 255);
1835 else {
1836 glColor3ub (255, 0, 0);
1839 stipplerect (ctm, p, texcoords, vertices);
1842 for (int i = 0; i < page->annotcount; ++i) {
1843 struct annot *annot = &page->annots[i];
1845 p[0].x = annot->bbox.x0;
1846 p[0].y = annot->bbox.y0;
1848 p[1].x = annot->bbox.x1;
1849 p[1].y = annot->bbox.y0;
1851 p[2].x = annot->bbox.x1;
1852 p[2].y = annot->bbox.y1;
1854 p[3].x = annot->bbox.x0;
1855 p[3].y = annot->bbox.y1;
1857 glColor3ub (0, 0, 128);
1858 stipplerect (ctm, p, texcoords, vertices);
1861 glDisable (GL_BLEND);
1862 glDisable (GL_TEXTURE_1D);
1865 static int compareslinks (const void *l, const void *r)
1867 struct slink const *ls = l;
1868 struct slink const *rs = r;
1869 if (ls->bbox.y0 == rs->bbox.y0) {
1870 return ls->bbox.x0 - rs->bbox.x0;
1872 return ls->bbox.y0 - rs->bbox.y0;
1875 static void droptext (struct page *page)
1877 if (page->text) {
1878 fz_drop_stext_page (state.ctx, page->text);
1879 page->fmark = NULL;
1880 page->lmark = NULL;
1881 page->text = NULL;
1885 static void dropannots (struct page *page)
1887 if (page->annots) {
1888 free (page->annots);
1889 page->annots = NULL;
1890 page->annotcount = 0;
1894 static void ensureannots (struct page *page)
1896 int i, count = 0;
1897 pdf_annot *annot;
1898 pdf_document *pdf;
1899 pdf_page *pdfpage;
1901 pdf = pdf_specifics (state.ctx, state.doc);
1902 if (!pdf) {
1903 return;
1906 pdfpage = pdf_page_from_fz_page (state.ctx, page->fzpage);
1907 if (state.gen != page->agen) {
1908 dropannots (page);
1909 page->agen = state.gen;
1911 if (page->annots) {
1912 return;
1915 for (annot = pdf_first_annot (state.ctx, pdfpage);
1916 annot;
1917 annot = pdf_next_annot (state.ctx, annot)) {
1918 count++;
1921 if (count > 0) {
1922 page->annotcount = count;
1923 page->annots = calloc (count, sizeof (*page->annots));
1924 if (!page->annots) {
1925 err (1, errno, "calloc annots %d", count);
1928 for (annot = pdf_first_annot (state.ctx, pdfpage), i = 0;
1929 annot;
1930 annot = pdf_next_annot (state.ctx, annot), i++) {
1931 fz_rect rect;
1933 rect = pdf_bound_annot (state.ctx, annot);
1934 page->annots[i].annot = annot;
1935 page->annots[i].bbox = fz_round_rect (rect);
1940 static void dropslinks (struct page *page)
1942 if (page->slinks) {
1943 free (page->slinks);
1944 page->slinks = NULL;
1945 page->slinkcount = 0;
1947 if (page->links) {
1948 fz_drop_link (state.ctx, page->links);
1949 page->links = NULL;
1953 static void ensureslinks (struct page *page)
1955 fz_matrix ctm;
1956 int i, count;
1957 size_t slinksize = sizeof (*page->slinks);
1958 fz_link *link;
1960 ensureannots (page);
1961 if (state.gen != page->sgen) {
1962 dropslinks (page);
1963 page->sgen = state.gen;
1965 if (page->slinks) {
1966 return;
1969 ensurelinks (page);
1970 ctm = pagectm (page);
1972 count = page->annotcount;
1973 for (link = page->links; link; link = link->next) {
1974 count++;
1976 if (count > 0) {
1977 int j;
1979 page->slinkcount = count;
1980 page->slinks = calloc (count, slinksize);
1981 if (!page->slinks) {
1982 err (1, errno, "calloc slinks %d", count);
1985 for (i = 0, link = page->links; link; ++i, link = link->next) {
1986 fz_rect rect;
1988 rect = link->rect;
1989 rect = fz_transform_rect (rect, ctm);
1990 page->slinks[i].tag = SLINK;
1991 page->slinks[i].u.link = link;
1992 page->slinks[i].bbox = fz_round_rect (rect);
1994 for (j = 0; j < page->annotcount; ++j, ++i) {
1995 fz_rect rect;
1996 rect = pdf_bound_annot (state.ctx, page->annots[j].annot);
1997 rect = fz_transform_rect (rect, ctm);
1998 page->slinks[i].bbox = fz_round_rect (rect);
2000 page->slinks[i].tag = SANNOT;
2001 page->slinks[i].u.annot = page->annots[j].annot;
2003 qsort (page->slinks, count, slinksize, compareslinks);
2007 static void highlightslinks (struct page *page, int xoff, int yoff,
2008 int noff, const char *targ, unsigned int tlen,
2009 const char *chars, unsigned int clen, int hfsize)
2011 char buf[40];
2012 struct slink *slink;
2013 float x0, y0, x1, y1, w;
2015 ensureslinks (page);
2016 glColor3ub (0xc3, 0xb0, 0x91);
2017 for (int i = 0; i < page->slinkcount; ++i) {
2018 fmt_linkn (buf, chars, clen, i + noff);
2019 if (!tlen || !strncmp (targ, buf, tlen)) {
2020 slink = &page->slinks[i];
2022 x0 = slink->bbox.x0 + xoff - 5;
2023 y1 = slink->bbox.y0 + yoff - 5;
2024 y0 = y1 + 10 + hfsize;
2025 w = measure_string (state.face, hfsize, buf);
2026 x1 = x0 + w + 10;
2027 recti ((int) x0, (int) y0, (int) x1, (int) y1);
2031 glEnable (GL_BLEND);
2032 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2033 glEnable (GL_TEXTURE_2D);
2034 glColor3ub (0, 0, 0);
2035 for (int i = 0; i < page->slinkcount; ++i) {
2036 fmt_linkn (buf, chars, clen, i + noff);
2037 if (!tlen || !strncmp (targ, buf, tlen)) {
2038 slink = &page->slinks[i];
2040 x0 = slink->bbox.x0 + xoff;
2041 y0 = slink->bbox.y0 + yoff + hfsize;
2042 draw_string (state.face, hfsize, x0, y0, buf);
2045 glDisable (GL_TEXTURE_2D);
2046 glDisable (GL_BLEND);
2049 static void uploadslice (struct tile *tile, struct slice *slice)
2051 int offset;
2052 struct slice *slice1;
2053 unsigned char *texdata;
2055 offset = 0;
2056 for (slice1 = tile->slices; slice != slice1; slice1++) {
2057 offset += slice1->h * tile->w * tile->pixmap->n;
2059 if (slice->texindex != -1 && slice->texindex < state.tex.count
2060 && state.tex.owners[slice->texindex].slice == slice) {
2061 glBindTexture (TEXT_TYPE, state.tex.ids[slice->texindex]);
2063 else {
2064 int subimage = 0;
2065 int texindex = state.tex.index++ % state.tex.count;
2067 if (state.tex.owners[texindex].w == tile->w) {
2068 if (state.tex.owners[texindex].h >= slice->h) {
2069 subimage = 1;
2071 else {
2072 state.tex.owners[texindex].h = slice->h;
2075 else {
2076 state.tex.owners[texindex].h = slice->h;
2079 state.tex.owners[texindex].w = tile->w;
2080 state.tex.owners[texindex].slice = slice;
2081 slice->texindex = texindex;
2083 glBindTexture (TEXT_TYPE, state.tex.ids[texindex]);
2084 #if TEXT_TYPE == GL_TEXTURE_2D
2085 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2086 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2087 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2088 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2089 #endif
2090 texdata = tile->pixmap->samples;
2091 if (subimage) {
2092 glTexSubImage2D (TEXT_TYPE, 0, 0, 0, tile->w, slice->h,
2093 state.tex.form, state.tex.ty, texdata+offset);
2095 else {
2096 glTexImage2D (TEXT_TYPE, 0, state.tex.iform, tile->w, slice->h,
2097 0, state.tex.form, state.tex.ty, texdata+offset);
2102 ML0 (begintiles (void))
2104 glEnable (TEXT_TYPE);
2105 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2106 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2109 ML0 (endtiles (void))
2111 glDisable (TEXT_TYPE);
2114 ML0 (drawtile (value args_v, value ptr_v))
2116 CAMLparam2 (args_v, ptr_v);
2117 int dispx = Int_val (Field (args_v, 0));
2118 int dispy = Int_val (Field (args_v, 1));
2119 int dispw = Int_val (Field (args_v, 2));
2120 int disph = Int_val (Field (args_v, 3));
2121 int tilex = Int_val (Field (args_v, 4));
2122 int tiley = Int_val (Field (args_v, 5));
2123 struct tile *tile = parse_pointer (__func__, String_val (ptr_v));
2124 int slicey, firstslice;
2125 struct slice *slice;
2126 GLfloat *texcoords = state.texcoords;
2127 GLfloat *vertices = state.vertices;
2129 firstslice = tiley / tile->sliceheight;
2130 slice = &tile->slices[firstslice];
2131 slicey = tiley % tile->sliceheight;
2133 while (disph > 0) {
2134 int dh;
2136 dh = slice->h - slicey;
2137 dh = fz_mini (disph, dh);
2138 uploadslice (tile, slice);
2140 texcoords[0] = tilex; texcoords[1] = slicey;
2141 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2142 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2143 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2145 vertices[0] = dispx; vertices[1] = dispy;
2146 vertices[2] = dispx+dispw; vertices[3] = dispy;
2147 vertices[4] = dispx; vertices[5] = dispy+dh;
2148 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2150 #if TEXT_TYPE == GL_TEXTURE_2D
2151 for (int i = 0; i < 8; ++i) {
2152 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2154 #endif
2156 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2157 dispy += dh;
2158 disph -= dh;
2159 slice++;
2160 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2161 slicey = 0;
2163 CAMLreturn0;
2166 ML (postprocess (value ptr_v, value hlmask_v,
2167 value xoff_v, value yoff_v, value li_v))
2169 CAMLparam5 (ptr_v, hlmask_v, xoff_v, yoff_v, li_v);
2170 int xoff = Int_val (xoff_v);
2171 int yoff = Int_val (yoff_v);
2172 int noff = Int_val (Field (li_v, 0));
2173 const char *targ = String_val (Field (li_v, 1));
2174 mlsize_t tlen = caml_string_length (Field (li_v, 1));
2175 int hfsize = Int_val (Field (li_v, 2));
2176 const char *chars = String_val (Field (li_v, 3));
2177 mlsize_t clen = caml_string_length (Field (li_v, 3));
2178 int hlmask = Int_val (hlmask_v);
2179 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2181 if (!page->fzpage) {
2182 /* deal with loadpage failed pages */
2183 goto done;
2186 if (trylock (__func__)) {
2187 noff = -1;
2188 goto done;
2191 ensureannots (page);
2192 if (hlmask & 1) {
2193 highlightlinks (page, xoff, yoff);
2195 if (hlmask & 2) {
2196 highlightslinks (page, xoff, yoff, noff, targ, STTI (tlen),
2197 chars, STTI (clen), hfsize);
2198 noff = page->slinkcount;
2200 if (page->tgen == state.gen) {
2201 showsel (page, xoff, yoff);
2203 unlock (__func__);
2205 done:
2206 CAMLreturn (Val_int (noff));
2209 static struct annot *getannot (struct page *page, int x, int y)
2211 fz_point p;
2212 fz_matrix ctm;
2213 const fz_matrix *tctm;
2214 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2216 if (!page->annots) {
2217 return NULL;
2220 if (pdf) {
2221 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2222 tctm = &state.pagedims[page->pdimno].tctm;
2224 else {
2225 tctm = &fz_identity;
2228 p.x = x;
2229 p.y = y;
2231 ctm = fz_concat (*tctm, state.pagedims[page->pdimno].ctm);
2232 ctm = fz_invert_matrix (ctm);
2233 p = fz_transform_point (p, ctm);
2235 if (pdf) {
2236 for (int i = 0; i < page->annotcount; ++i) {
2237 struct annot *a = &page->annots[i];
2238 if (fz_is_point_inside_rect (p, pdf_bound_annot (state.ctx,
2239 a->annot))) {
2240 return a;
2244 return NULL;
2247 static fz_link *getlink (struct page *page, int x, int y)
2249 fz_link *link;
2250 fz_point p = { .x = x, .y = y };
2252 ensureslinks (page);
2253 p = fz_transform_point (p, fz_invert_matrix (pagectm (page)));
2255 for (link = page->links; link; link = link->next) {
2256 if (fz_is_point_inside_rect (p, link->rect)) {
2257 return link;
2260 return NULL;
2263 static void ensuretext (struct page *page)
2265 if (state.gen != page->tgen) {
2266 droptext (page);
2267 page->tgen = state.gen;
2269 if (!page->text) {
2270 fz_device *tdev;
2272 page->text = fz_new_stext_page (state.ctx,
2273 state.pagedims[page->pdimno].mediabox);
2274 tdev = fz_new_stext_device (state.ctx, page->text, 0);
2275 fz_run_display_list (state.ctx, page->dlist,
2276 tdev, pagectm (page), fz_infinite_rect, NULL);
2277 fz_close_device (state.ctx, tdev);
2278 fz_drop_device (state.ctx, tdev);
2282 ML (find_page_with_links (value start_page_v, value dir_v))
2284 CAMLparam2 (start_page_v, dir_v);
2285 CAMLlocal1 (ret_v);
2286 int i, dir = Int_val (dir_v);
2287 int start_page = Int_val (start_page_v);
2288 int end_page = dir > 0 ? state.pagecount : -1;
2289 pdf_document *pdf;
2291 fz_var (i);
2292 fz_var (end_page);
2293 ret_v = Val_int (0);
2294 lock (__func__);
2295 pdf = pdf_specifics (state.ctx, state.doc);
2296 for (i = start_page + dir; i != end_page; i += dir) {
2297 int found;
2299 fz_var (found);
2300 if (pdf) {
2301 pdf_page *page = NULL;
2303 fz_var (page);
2304 fz_try (state.ctx) {
2305 page = pdf_load_page (state.ctx, pdf, i);
2306 found = !!page->links || !!page->annots;
2308 fz_catch (state.ctx) {
2309 found = 0;
2311 fz_drop_page (state.ctx, &page->super);
2313 else {
2314 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2315 fz_link *link = fz_load_links (state.ctx, page);
2316 found = !!link;
2317 fz_drop_link (state.ctx, link);
2318 fz_drop_page (state.ctx, page);
2321 if (found) {
2322 ret_v = caml_alloc_small (1, 1);
2323 Field (ret_v, 0) = Val_int (i);
2324 goto unlock;
2327 unlock:
2328 unlock (__func__);
2329 CAMLreturn (ret_v);
2332 ML (findlink (value ptr_v, value dir_v))
2334 CAMLparam2 (ptr_v, dir_v);
2335 CAMLlocal2 (ret_v, pos_v);
2336 struct page *page;
2337 int dirtag, i, slinkindex;
2338 struct slink *found = NULL ,*slink;
2340 page = parse_pointer (__func__, String_val (ptr_v));
2341 ret_v = Val_int (0);
2342 lock (__func__);
2343 ensureslinks (page);
2345 if (Is_block (dir_v)) {
2346 dirtag = Tag_val (dir_v);
2347 switch (dirtag) {
2348 case LDfirstvisible:
2350 int x0, y0, dir, first_index, last_index;
2352 pos_v = Field (dir_v, 0);
2353 x0 = Int_val (Field (pos_v, 0));
2354 y0 = Int_val (Field (pos_v, 1));
2355 dir = Int_val (Field (pos_v, 2));
2357 if (dir >= 0) {
2358 dir = 1;
2359 first_index = 0;
2360 last_index = page->slinkcount;
2362 else {
2363 first_index = page->slinkcount - 1;
2364 last_index = -1;
2367 for (i = first_index; i != last_index; i += dir) {
2368 slink = &page->slinks[i];
2369 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2370 found = slink;
2371 break;
2375 break;
2377 case LDleft:
2378 slinkindex = Int_val (Field (dir_v, 0));
2379 found = &page->slinks[slinkindex];
2380 for (i = slinkindex - 1; i >= 0; --i) {
2381 slink = &page->slinks[i];
2382 if (slink->bbox.x0 < found->bbox.x0) {
2383 found = slink;
2384 break;
2387 break;
2389 case LDright:
2390 slinkindex = Int_val (Field (dir_v, 0));
2391 found = &page->slinks[slinkindex];
2392 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2393 slink = &page->slinks[i];
2394 if (slink->bbox.x0 > found->bbox.x0) {
2395 found = slink;
2396 break;
2399 break;
2401 case LDdown:
2402 slinkindex = Int_val (Field (dir_v, 0));
2403 found = &page->slinks[slinkindex];
2404 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2405 slink = &page->slinks[i];
2406 if (slink->bbox.y0 >= found->bbox.y0) {
2407 found = slink;
2408 break;
2411 break;
2413 case LDup:
2414 slinkindex = Int_val (Field (dir_v, 0));
2415 found = &page->slinks[slinkindex];
2416 for (i = slinkindex - 1; i >= 0; --i) {
2417 slink = &page->slinks[i];
2418 if (slink->bbox.y0 <= found->bbox.y0) {
2419 found = slink;
2420 break;
2423 break;
2426 else {
2427 dirtag = Int_val (dir_v);
2428 switch (dirtag) {
2429 case LDfirst:
2430 found = page->slinks;
2431 break;
2433 case LDlast:
2434 if (page->slinks) {
2435 found = page->slinks + (page->slinkcount - 1);
2437 break;
2440 if (found) {
2441 ret_v = caml_alloc_small (2, 1);
2442 Field (ret_v, 0) = Val_int (found - page->slinks);
2445 unlock (__func__);
2446 CAMLreturn (ret_v);
2449 ML (getlink (value ptr_v, value n_v))
2451 CAMLparam2 (ptr_v, n_v);
2452 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2453 int n = Int_val (n_v);
2454 fz_link *link;
2455 struct page *page;
2456 struct slink *slink;
2458 ret_v = Val_int (0);
2459 page = parse_pointer (__func__, String_val (ptr_v));
2461 lock (__func__);
2462 ensureslinks (page);
2463 if (!page->slinkcount || n > page->slinkcount) goto unlock;
2464 slink = &page->slinks[n];
2465 if (slink->tag == SLINK) {
2466 link = slink->u.link;
2467 str_v = caml_copy_string (link->uri);
2468 ret_v = caml_alloc_small (1, Uuri);
2469 Field (ret_v, 0) = str_v;
2471 else {
2472 int ty = pdf_annot_type (state.ctx, slink->u.annot)
2473 == PDF_ANNOT_FILE_ATTACHMENT ? Ufileannot : Utextannot;
2475 ret_v = caml_alloc_small (1, ty);
2476 tup_v = caml_alloc_tuple (2);
2477 Field (ret_v, 0) = tup_v;
2478 Field (tup_v, 0) = ptr_v;
2479 Field (tup_v, 1) = n_v;
2481 unlock:
2482 unlock (__func__);
2483 CAMLreturn (ret_v);
2486 ML (getlinkn (value ptr_v, value c_v, value n_v, value noff_v))
2488 CAMLparam4 (ptr_v, c_v, n_v, noff_v);
2489 CAMLlocal1 (ret_v);
2490 char buf[40];
2491 struct page *page;
2492 const char *c = String_val (c_v);
2493 const char *n = String_val (n_v);
2494 mlsize_t clen = caml_string_length (c_v);
2495 page = parse_pointer (__func__, String_val (ptr_v));
2497 lock (__func__);
2498 ensureslinks (page);
2500 ret_v = Val_int (-page->slinkcount);
2501 for (int i = 0; i < page->slinkcount; ++i) {
2502 fmt_linkn (buf, c, STTI (clen), i - Int_val (noff_v));
2503 if (!strncmp (buf, n, clen)) {
2504 ret_v = Val_int (i+1);
2505 break;
2509 unlock (__func__);
2510 CAMLreturn (ret_v);
2513 ML (gettextannot (value ptr_v, value n_v))
2515 CAMLparam2 (ptr_v, n_v);
2516 CAMLlocal1 (ret_v);
2517 pdf_document *pdf;
2518 const char *contents = "";
2520 lock (__func__);
2521 pdf = pdf_specifics (state.ctx, state.doc);
2522 if (pdf) {
2523 struct page *page;
2524 pdf_annot *annot;
2525 struct slink *slink;
2527 page = parse_pointer (__func__, String_val (ptr_v));
2528 slink = &page->slinks[Int_val (n_v)];
2529 annot = slink->u.annot;
2530 contents = pdf_annot_contents (state.ctx, annot);
2532 unlock (__func__);
2533 ret_v = caml_copy_string (contents);
2534 CAMLreturn (ret_v);
2537 ML (getfileannot (value ptr_v, value n_v))
2539 CAMLparam2 (ptr_v, n_v);
2540 CAMLlocal1 (ret_v);
2542 lock (__func__);
2544 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2545 struct slink *slink = &page->slinks[Int_val (n_v)];
2546 pdf_obj *fs = pdf_dict_get (state.ctx,
2547 pdf_annot_obj (state.ctx, slink->u.annot),
2548 PDF_NAME (FS));
2549 ret_v = caml_copy_string (pdf_embedded_file_name (state.ctx, fs));
2551 unlock (__func__);
2552 CAMLreturn (ret_v);
2555 ML0 (savefileannot (value ptr_v, value n_v, value path_v))
2557 CAMLparam3 (ptr_v, n_v, path_v);
2558 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2559 const char *path = String_val (path_v);
2561 lock (__func__);
2562 struct slink *slink = &page->slinks[Int_val (n_v)];
2563 fz_try (state.ctx) {
2564 pdf_obj *fs = pdf_dict_get (state.ctx,
2565 pdf_annot_obj (state.ctx, slink->u.annot),
2566 PDF_NAME (FS));
2567 fz_buffer *buf = pdf_load_embedded_file (state.ctx, fs);
2568 fz_save_buffer (state.ctx, buf, path);
2569 fz_drop_buffer (state.ctx, buf);
2570 printd ("progress 1 saved '%s'", path);
2572 fz_catch (state.ctx) {
2573 printd ("emsg saving '%s': %s", path, fz_caught_message (state.ctx));
2575 unlock (__func__);
2578 ML (getlinkrect (value ptr_v, value n_v))
2580 CAMLparam2 (ptr_v, n_v);
2581 CAMLlocal1 (ret_v);
2582 struct page *page;
2583 struct slink *slink;
2585 page = parse_pointer (__func__, String_val (ptr_v));
2586 ret_v = caml_alloc_tuple (4);
2587 lock (__func__);
2588 ensureslinks (page);
2590 slink = &page->slinks[Int_val (n_v)];
2591 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2592 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2593 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2594 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2595 unlock (__func__);
2596 CAMLreturn (ret_v);
2599 ML (whatsunder (value ptr_v, value x_v, value y_v))
2601 CAMLparam3 (ptr_v, x_v, y_v);
2602 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2603 fz_link *link;
2604 struct annot *annot;
2605 struct page *page;
2606 const char *ptr = String_val (ptr_v);
2607 int x = Int_val (x_v), y = Int_val (y_v);
2608 struct pagedim *pdim;
2610 ret_v = Val_int (0);
2611 if (trylock (__func__)) {
2612 goto done;
2615 page = parse_pointer (__func__, ptr);
2616 pdim = &state.pagedims[page->pdimno];
2617 x += pdim->bounds.x0;
2618 y += pdim->bounds.y0;
2620 annot = getannot (page, x, y);
2621 if (annot) {
2622 int i, n = -1, ty;
2624 ensureslinks (page);
2625 for (i = 0; i < page->slinkcount; ++i) {
2626 if (page->slinks[i].tag == SANNOT
2627 && page->slinks[i].u.annot == annot->annot) {
2628 n = i;
2629 break;
2632 ty = pdf_annot_type (state.ctx, annot->annot)
2633 == PDF_ANNOT_FILE_ATTACHMENT ? Ufileannot : Utextannot;
2635 ret_v = caml_alloc_small (1, ty);
2636 tup_v = caml_alloc_tuple (2);
2637 Field (ret_v, 0) = tup_v;
2638 Field (tup_v, 0) = ptr_v;
2639 Field (tup_v, 1) = Int_val (n);
2640 goto unlock;
2643 link = getlink (page, x, y);
2644 if (link) {
2645 str_v = caml_copy_string (link->uri);
2646 ret_v = caml_alloc_small (1, Uuri);
2647 Field (ret_v, 0) = str_v;
2649 else {
2650 fz_stext_block *block;
2651 fz_point p = { .x = x, .y = y };
2653 ensuretext (page);
2655 for (block = page->text->first_block; block; block = block->next) {
2656 fz_stext_line *line;
2658 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2659 continue;
2661 if (!fz_is_point_inside_rect (p, block->bbox)) {
2662 continue;
2665 for (line = block->u.t.first_line; line; line = line->next) {
2666 fz_stext_char *ch;
2668 if (!fz_is_point_inside_rect (p, line->bbox)) {
2669 continue;
2672 for (ch = line->first_char; ch; ch = ch->next) {
2673 if (!fz_is_point_inside_quad (p, ch->quad)) {
2674 const char *n2 = fz_font_name (state.ctx, ch->font);
2675 FT_FaceRec *face = fz_font_ft_face (state.ctx,
2676 ch->font);
2678 if (!n2) {
2679 n2 = "<unknown font>";
2682 if (face && face->family_name) {
2683 char *s;
2684 char *n1 = face->family_name;
2685 size_t l1 = strlen (n1);
2686 size_t l2 = strlen (n2);
2688 if (l1 != l2 || memcmp (n1, n2, l1)) {
2689 s = malloc (l1 + l2 + 2);
2690 if (s) {
2691 memcpy (s, n2, l2);
2692 s[l2] = '=';
2693 memcpy (s + l2 + 1, n1, l1 + 1);
2694 str_v = caml_copy_string (s);
2695 free (s);
2699 if (str_v == Val_unit) {
2700 str_v = caml_copy_string (n2);
2702 ret_v = caml_alloc_small (1, Utext);
2703 Field (ret_v, 0) = str_v;
2704 goto unlock;
2710 unlock:
2711 unlock (__func__);
2713 done:
2714 CAMLreturn (ret_v);
2717 ML0 (clearmark (value ptr_v))
2719 CAMLparam1 (ptr_v);
2720 struct page *page;
2722 if (trylock (__func__)) {
2723 goto done;
2726 page = parse_pointer (__func__, String_val (ptr_v));
2727 page->fmark = NULL;
2728 page->lmark = NULL;
2730 unlock (__func__);
2731 done:
2732 CAMLreturn0;
2735 static int uninteresting (int c)
2737 return isspace (c) || ispunct (c);
2740 ML (markunder (value ptr_v, value x_v, value y_v, value mark_v))
2742 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
2743 CAMLlocal1 (ret_v);
2744 struct page *page;
2745 fz_stext_line *line;
2746 fz_stext_block *block;
2747 struct pagedim *pdim;
2748 int mark = Int_val (mark_v);
2749 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2751 ret_v = Val_bool (0);
2752 if (trylock (__func__)) {
2753 goto done;
2756 page = parse_pointer (__func__, String_val (ptr_v));
2757 pdim = &state.pagedims[page->pdimno];
2759 ensuretext (page);
2761 if (mark == MarkPage) {
2762 page->fmark = page->text->first_block->u.t.first_line->first_char;
2763 page->lmark = page->text->last_block->u.t.last_line->last_char;
2764 ret_v = Val_bool (1);
2765 goto unlock;
2768 p.x += pdim->bounds.x0;
2769 p.y += pdim->bounds.y0;
2771 for (block = page->text->first_block; block; block = block->next) {
2772 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2773 continue;
2775 if (!fz_is_point_inside_rect (p, block->bbox)) {
2776 continue;
2779 if (mark == MarkBlock) {
2780 page->fmark = block->u.t.first_line->first_char;
2781 page->lmark = block->u.t.last_line->last_char;
2782 ret_v = Val_bool (1);
2783 goto unlock;
2786 for (line = block->u.t.first_line; line; line = line->next) {
2787 fz_stext_char *ch;
2789 if (!fz_is_point_inside_rect (p, line->bbox)) {
2790 continue;
2793 if (mark == MarkLine) {
2794 page->fmark = line->first_char;
2795 page->lmark = line->last_char;
2796 ret_v = Val_bool (1);
2797 goto unlock;
2800 for (ch = line->first_char; ch; ch = ch->next) {
2801 fz_stext_char *ch2, *first = NULL, *last = NULL;
2803 if (fz_is_point_inside_quad (p, ch->quad)) {
2804 for (ch2 = line->first_char; ch2 != ch; ch2 = ch2->next) {
2805 if (uninteresting (ch2->c)) {
2806 first = NULL;
2808 else {
2809 if (!first) {
2810 first = ch2;
2814 for (ch2 = ch; ch2; ch2 = ch2->next) {
2815 if (uninteresting (ch2->c)) {
2816 break;
2818 last = ch2;
2821 page->fmark = first;
2822 page->lmark = last;
2823 ret_v = Val_bool (1);
2824 goto unlock;
2829 unlock:
2830 if (!Bool_val (ret_v)) {
2831 page->fmark = NULL;
2832 page->lmark = NULL;
2834 unlock (__func__);
2836 done:
2837 CAMLreturn (ret_v);
2840 ML (rectofblock (value ptr_v, value x_v, value y_v))
2842 CAMLparam3 (ptr_v, x_v, y_v);
2843 CAMLlocal2 (ret_v, res_v);
2844 fz_rect *b = NULL;
2845 struct page *page;
2846 struct pagedim *pdim;
2847 fz_stext_block *block;
2848 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2850 ret_v = Val_int (0);
2851 if (trylock (__func__)) {
2852 goto done;
2855 page = parse_pointer (__func__, String_val (ptr_v));
2856 pdim = &state.pagedims[page->pdimno];
2857 p.x += pdim->bounds.x0;
2858 p.y += pdim->bounds.y0;
2860 ensuretext (page);
2862 for (block = page->text->first_block; block; block = block->next) {
2863 switch (block->type) {
2864 case FZ_STEXT_BLOCK_TEXT:
2865 b = &block->bbox;
2866 break;
2868 case FZ_STEXT_BLOCK_IMAGE:
2869 b = &block->bbox;
2870 break;
2872 default:
2873 continue;
2876 if (fz_is_point_inside_rect (p, *b)) {
2877 break;
2879 b = NULL;
2881 if (b) {
2882 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
2883 ret_v = caml_alloc_small (1, 1);
2884 Store_double_field (res_v, 0, (double) b->x0);
2885 Store_double_field (res_v, 1, (double) b->x1);
2886 Store_double_field (res_v, 2, (double) b->y0);
2887 Store_double_field (res_v, 3, (double) b->y1);
2888 Field (ret_v, 0) = res_v;
2890 unlock (__func__);
2892 done:
2893 CAMLreturn (ret_v);
2896 ML0 (seltext (value ptr_v, value rect_v))
2898 CAMLparam2 (ptr_v, rect_v);
2899 struct page *page;
2900 struct pagedim *pdim;
2901 int x0, x1, y0, y1;
2902 fz_stext_char *ch;
2903 fz_stext_line *line;
2904 fz_stext_block *block;
2905 fz_stext_char *fc, *lc;
2907 if (trylock (__func__)) {
2908 goto done;
2911 page = parse_pointer (__func__, String_val (ptr_v));
2912 ensuretext (page);
2914 pdim = &state.pagedims[page->pdimno];
2915 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
2916 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
2917 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
2918 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
2920 if (y0 > y1) {
2921 int t = y0;
2922 y0 = y1;
2923 y1 = t;
2924 x0 = x1;
2925 x1 = t;
2928 fc = page->fmark;
2929 lc = page->lmark;
2931 for (block = page->text->first_block; block; block = block->next) {
2932 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2933 continue;
2936 for (line = block->u.t.first_line; line; line = line->next) {
2937 for (ch = line->first_char; ch; ch = ch->next) {
2938 fz_point p0 = { .x = x0, .y = y0 }, p1 = { .x = x1, .y = y1 };
2939 if (fz_is_point_inside_quad (p0, ch->quad)) {
2940 fc = ch;
2942 if (fz_is_point_inside_quad (p1, ch->quad)) {
2943 lc = ch;
2948 if (x1 < x0 && fc == lc) {
2949 fz_stext_char *t;
2951 t = fc;
2952 fc = lc;
2953 lc = t;
2956 page->fmark = fc;
2957 page->lmark = lc;
2959 unlock (__func__);
2961 done:
2962 CAMLreturn0;
2965 static int pipechar (FILE *f, fz_stext_char *ch)
2967 char buf[4];
2968 int len;
2969 size_t ret;
2971 len = fz_runetochar (buf, ch->c);
2972 ret = fwrite (buf, len, 1, f);
2973 if (ret != 1) {
2974 printd ("emsg failed to fwrite %d bytes ret=%zu: %d(%s)",
2975 len, ret, errno, strerror (errno));
2976 return -1;
2978 return 0;
2981 ML (spawn (value command_v, value fds_v))
2983 CAMLparam2 (command_v, fds_v);
2984 CAMLlocal2 (l_v, tup_v);
2985 int ret, ret1;
2986 pid_t pid = (pid_t) -1;
2987 char *msg = NULL;
2988 value earg_v = Nothing;
2989 posix_spawnattr_t attr;
2990 posix_spawn_file_actions_t fa;
2991 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
2993 argv[2] = &Byte (command_v, 0);
2994 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
2995 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
2998 if ((ret = posix_spawnattr_init (&attr)) != 0) {
2999 msg = "posix_spawnattr_init";
3000 goto fail1;
3003 #ifdef POSIX_SPAWN_USEVFORK
3004 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
3005 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
3006 goto fail;
3008 #endif
3010 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
3011 int fd1, fd2;
3013 tup_v = Field (l_v, 0);
3014 fd1 = Int_val (Field (tup_v, 0));
3015 fd2 = Int_val (Field (tup_v, 1));
3016 if (fd2 < 0) {
3017 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
3018 msg = "posix_spawn_file_actions_addclose";
3019 earg_v = tup_v;
3020 goto fail;
3023 else {
3024 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
3025 msg = "posix_spawn_file_actions_adddup2";
3026 earg_v = tup_v;
3027 goto fail;
3032 extern char **environ;
3033 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3034 msg = "posix_spawn";
3035 goto fail;
3038 fail:
3039 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3040 printd ("emsg posix_spawnattr_destroy: %d(%s)", ret1, strerror (ret1));
3043 fail1:
3044 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3045 printd ("emsg posix_spawn_file_actions_destroy: %d(%s)",
3046 ret1, strerror (ret1));
3049 if (msg) {
3050 unix_error (ret, msg, earg_v);
3053 CAMLreturn (Val_int (pid));
3056 ML (hassel (value ptr_v))
3058 CAMLparam1 (ptr_v);
3059 CAMLlocal1 (ret_v);
3060 struct page *page;
3062 ret_v = Val_bool (0);
3063 if (trylock (__func__)) {
3064 goto done;
3067 page = parse_pointer (__func__, String_val (ptr_v));
3068 ret_v = Val_bool (page->fmark && page->lmark);
3069 unlock (__func__);
3070 done:
3071 CAMLreturn (ret_v);
3074 ML0 (copysel (value fd_v, value ptr_v))
3076 CAMLparam2 (fd_v, ptr_v);
3077 FILE *f;
3078 int seen = 0;
3079 struct page *page;
3080 fz_stext_line *line;
3081 fz_stext_block *block;
3082 int fd = Int_val (fd_v);
3084 if (trylock (__func__)) {
3085 goto done;
3088 page = parse_pointer (__func__, String_val (ptr_v));
3090 if (!page->fmark || !page->lmark) {
3091 printd ("emsg nothing to copy on page %d", page->pageno);
3092 goto unlock;
3095 f = fdopen (fd, "w");
3096 if (!f) {
3097 printd ("emsg failed to fdopen sel pipe (from fd %d): %d(%s)",
3098 fd, errno, strerror (errno));
3099 f = stdout;
3102 for (block = page->text->first_block; block; block = block->next) {
3103 if (block->type != FZ_STEXT_BLOCK_TEXT) {
3104 continue;
3107 for (line = block->u.t.first_line; line; line = line->next) {
3108 fz_stext_char *ch;
3109 for (ch = line->first_char; ch; ch = ch->next) {
3110 if (seen || ch == page->fmark) {
3111 do {
3112 if (pipechar (f, ch)) {
3113 goto close;
3115 if (ch == page->lmark) {
3116 goto close;
3118 } while ((ch = ch->next));
3119 seen = 1;
3120 break;
3123 if (seen) {
3124 fputc ('\n', f);
3128 close:
3129 if (f != stdout) {
3130 int ret = fclose (f);
3131 fd = -1;
3132 if (ret == -1) {
3133 if (errno != ECHILD) {
3134 printd ("emsg failed to close sel pipe: %d(%s)",
3135 errno, strerror (errno));
3139 unlock:
3140 unlock (__func__);
3142 done:
3143 if (fd >= 0) {
3144 if (close (fd)) {
3145 printd ("emsg failed to close sel pipe: %d(%s)",
3146 errno, strerror (errno));
3149 CAMLreturn0;
3152 ML (getpdimrect (value pagedimno_v))
3154 CAMLparam1 (pagedimno_v);
3155 CAMLlocal1 (ret_v);
3156 int pagedimno = Int_val (pagedimno_v);
3157 fz_rect box;
3159 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3160 if (trylock (__func__)) {
3161 box = fz_empty_rect;
3163 else {
3164 box = state.pagedims[pagedimno].mediabox;
3165 unlock (__func__);
3168 Store_double_field (ret_v, 0, (double) box.x0);
3169 Store_double_field (ret_v, 1, (double) box.x1);
3170 Store_double_field (ret_v, 2, (double) box.y0);
3171 Store_double_field (ret_v, 3, (double) box.y1);
3173 CAMLreturn (ret_v);
3176 ML (zoom_for_height (value winw_v, value winh_v, value dw_v, value cols_v))
3178 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3179 CAMLlocal1 (ret_v);
3180 int i;
3181 float zoom = -1.;
3182 float maxh = 0.0;
3183 struct pagedim *p;
3184 float winw = Int_val (winw_v);
3185 float winh = Int_val (winh_v);
3186 float dw = Int_val (dw_v);
3187 float cols = Int_val (cols_v);
3188 float pw = 1.0, ph = 1.0;
3190 if (trylock (__func__)) {
3191 goto done;
3194 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3195 float w = p->pagebox.x1 / cols;
3196 float h = p->pagebox.y1;
3197 if (h > maxh) {
3198 maxh = h;
3199 ph = h;
3200 if (state.fitmodel != FitProportional) {
3201 pw = w;
3204 if ((state.fitmodel == FitProportional) && w > pw) {
3205 pw = w;
3209 zoom = (((winh / ph) * pw) + dw) / winw;
3210 unlock (__func__);
3211 done:
3212 ret_v = caml_copy_double ((double) zoom);
3213 CAMLreturn (ret_v);
3216 ML (getmaxw (value unit_v))
3218 CAMLparam1 (unit_v);
3219 CAMLlocal1 (ret_v);
3220 int i;
3221 float maxw = -1.;
3222 struct pagedim *p;
3224 if (trylock (__func__)) {
3225 goto done;
3228 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3229 maxw = fz_max (maxw, p->pagebox.x1);
3232 unlock (__func__);
3233 done:
3234 ret_v = caml_copy_double ((double) maxw);
3235 CAMLreturn (ret_v);
3238 ML (draw_string (value pt_v, value x_v, value y_v, value string_v))
3240 CAMLparam4 (pt_v, x_v, y_v, string_v);
3241 CAMLlocal1 (ret_v);
3242 float w = draw_string (state.face,
3243 Int_val (pt_v), Int_val (x_v), Int_val (y_v),
3244 String_val (string_v));
3245 ret_v = caml_copy_double (w);
3246 CAMLreturn (ret_v);
3249 ML (measure_string (value pt_v, value string_v))
3251 CAMLparam2 (pt_v, string_v);
3252 CAMLlocal1 (ret_v);
3254 ret_v = caml_copy_double (
3255 measure_string (state.face, Int_val (pt_v), String_val (string_v))
3257 CAMLreturn (ret_v);
3260 ML (getpagebox (value ptr_v))
3262 CAMLparam1 (ptr_v);
3263 CAMLlocal1 (ret_v);
3264 fz_rect rect;
3265 fz_irect bbox;
3266 fz_device *dev;
3267 struct page *page = parse_pointer (__func__, String_val (ptr_v));
3269 ret_v = caml_alloc_tuple (4);
3270 dev = fz_new_bbox_device (state.ctx, &rect);
3272 fz_run_page (state.ctx, page->fzpage, dev, pagectm (page), NULL);
3274 fz_close_device (state.ctx, dev);
3275 fz_drop_device (state.ctx, dev);
3276 bbox = fz_round_rect (rect);
3277 Field (ret_v, 0) = Val_int (bbox.x0);
3278 Field (ret_v, 1) = Val_int (bbox.y0);
3279 Field (ret_v, 2) = Val_int (bbox.x1);
3280 Field (ret_v, 3) = Val_int (bbox.y1);
3282 CAMLreturn (ret_v);
3285 ML0 (setaalevel (value level_v))
3287 CAMLparam1 (level_v);
3289 state.aalevel = Int_val (level_v);
3290 CAMLreturn0;
3293 ML0 (setpapercolor (value rgba_v))
3295 CAMLparam1 (rgba_v);
3297 state.papercolor[0] = (float) Double_val (Field (rgba_v, 0));
3298 state.papercolor[1] = (float) Double_val (Field (rgba_v, 1));
3299 state.papercolor[2] = (float) Double_val (Field (rgba_v, 2));
3300 state.papercolor[3] = (float) Double_val (Field (rgba_v, 3));
3301 CAMLreturn0;
3304 value ml_keysymtoutf8 (value keysym_v);
3305 #ifndef MACOS
3306 value ml_keysymtoutf8 (value keysym_v)
3308 CAMLparam1 (keysym_v);
3309 CAMLlocal1 (str_v);
3310 unsigned short keysym = (unsigned short) Int_val (keysym_v);
3311 Rune rune;
3312 extern long keysym2ucs (unsigned short);
3313 int len;
3314 char buf[5];
3316 rune = (Rune) keysym2ucs (keysym);
3317 len = fz_runetochar (buf, rune);
3318 buf[len] = 0;
3319 str_v = caml_copy_string (buf);
3320 CAMLreturn (str_v);
3322 #else
3323 value ml_keysymtoutf8 (value keysym_v)
3325 CAMLparam1 (keysym_v);
3326 CAMLlocal1 (str_v);
3327 long ucs = Long_val (keysym_v);
3328 int len;
3329 char buf[5];
3331 len = fz_runetochar (buf, (int) ucs);
3332 buf[len] = 0;
3333 str_v = caml_copy_string (buf);
3334 CAMLreturn (str_v);
3336 #endif
3338 ML (unproject (value ptr_v, value x_v, value y_v))
3340 CAMLparam3 (ptr_v, x_v, y_v);
3341 CAMLlocal2 (ret_v, tup_v);
3342 struct page *page;
3343 int x = Int_val (x_v), y = Int_val (y_v);
3344 struct pagedim *pdim;
3345 fz_point p;
3347 page = parse_pointer (__func__, String_val (ptr_v));
3348 pdim = &state.pagedims[page->pdimno];
3350 ret_v = Val_int (0);
3351 if (trylock (__func__)) {
3352 goto done;
3355 p.x = x + pdim->bounds.x0;
3356 p.y = y + pdim->bounds.y0;
3358 p = fz_transform_point (p, fz_invert_matrix (fz_concat (pdim->tctm,
3359 pdim->ctm)));
3361 tup_v = caml_alloc_tuple (2);
3362 ret_v = caml_alloc_small (1, 1);
3363 Field (tup_v, 0) = Val_int (p.x);
3364 Field (tup_v, 1) = Val_int (p.y);
3365 Field (ret_v, 0) = tup_v;
3367 unlock (__func__);
3368 done:
3369 CAMLreturn (ret_v);
3372 ML (project (value ptr_v, value pageno_v, value pdimno_v, value x_v, value y_v))
3374 CAMLparam5 (ptr_v, pageno_v, pdimno_v, x_v, y_v);
3375 CAMLlocal1 (ret_v);
3376 struct page *page;
3377 const char *s = String_val (ptr_v);
3378 int pageno = Int_val (pageno_v);
3379 int pdimno = Int_val (pdimno_v);
3380 float x = (float) Double_val (x_v), y = (float) Double_val (y_v);
3381 struct pagedim *pdim;
3382 fz_point p;
3383 fz_matrix ctm;
3385 ret_v = Val_int (0);
3386 lock (__func__);
3388 if (!*s) {
3389 page = loadpage (pageno, pdimno);
3391 else {
3392 page = parse_pointer (__func__, String_val (ptr_v));
3394 pdim = &state.pagedims[pdimno];
3396 if (pdf_specifics (state.ctx, state.doc)) {
3397 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
3398 ctm = state.pagedims[page->pdimno].tctm;
3400 else {
3401 ctm = fz_identity;
3404 p.x = x + pdim->bounds.x0;
3405 p.y = y + pdim->bounds.y0;
3407 ctm = fz_concat (pdim->tctm, pdim->ctm);
3408 p = fz_transform_point (p, ctm);
3410 ret_v = caml_alloc_tuple (2);
3411 Field (ret_v, 0) = caml_copy_double ((double) p.x);
3412 Field (ret_v, 1) = caml_copy_double ((double) p.y);
3414 if (!*s) {
3415 freepage (page);
3417 unlock (__func__);
3418 CAMLreturn (ret_v);
3421 ML0 (addannot (value ptr_v, value x_v, value y_v, value contents_v))
3423 CAMLparam4 (ptr_v, x_v, y_v, contents_v);
3424 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3426 if (pdf) {
3427 pdf_annot *annot;
3428 struct page *page;
3429 fz_rect r;
3431 page = parse_pointer (__func__, String_val (ptr_v));
3432 annot = pdf_create_annot (state.ctx,
3433 pdf_page_from_fz_page (state.ctx,
3434 page->fzpage),
3435 PDF_ANNOT_TEXT);
3436 r.x0 = Int_val (x_v) - 10;
3437 r.y0 = Int_val (y_v) - 10;
3438 r.x1 = r.x0 + 20;
3439 r.y1 = r.y0 + 20;
3440 pdf_set_annot_contents (state.ctx, annot, String_val (contents_v));
3441 pdf_set_annot_rect (state.ctx, annot, r);
3443 state.dirty = 1;
3445 CAMLreturn0;
3448 ML0 (delannot (value ptr_v, value n_v))
3450 CAMLparam2 (ptr_v, n_v);
3451 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3453 if (pdf) {
3454 struct page *page;
3455 struct slink *slink;
3457 page = parse_pointer (__func__, String_val (ptr_v));
3458 slink = &page->slinks[Int_val (n_v)];
3459 pdf_delete_annot (state.ctx,
3460 pdf_page_from_fz_page (state.ctx, page->fzpage),
3461 (pdf_annot *) slink->u.annot);
3462 state.dirty = 1;
3464 CAMLreturn0;
3467 ML0 (modannot (value ptr_v, value n_v, value str_v))
3469 CAMLparam3 (ptr_v, n_v, str_v);
3470 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3472 if (pdf) {
3473 struct page *page;
3474 struct slink *slink;
3476 page = parse_pointer (__func__, String_val (ptr_v));
3477 slink = &page->slinks[Int_val (n_v)];
3478 pdf_set_annot_contents (state.ctx, (pdf_annot *) slink->u.annot,
3479 String_val (str_v));
3480 state.dirty = 1;
3482 CAMLreturn0;
3485 ML (hasunsavedchanges (void))
3487 return Val_bool (state.dirty);
3490 ML0 (savedoc (value path_v))
3492 CAMLparam1 (path_v);
3493 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3495 if (pdf) {
3496 pdf_save_document (state.ctx, pdf, String_val (path_v), NULL);
3498 CAMLreturn0;
3501 static void makestippletex (void)
3503 const char pixels[] = "\xff\xff\0\0";
3504 glGenTextures (1, &state.stid);
3505 glBindTexture (GL_TEXTURE_1D, state.stid);
3506 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
3507 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3508 glTexImage1D (
3509 GL_TEXTURE_1D,
3511 GL_ALPHA,
3514 GL_ALPHA,
3515 GL_UNSIGNED_BYTE,
3516 pixels
3520 ML (fz_version (void))
3522 return caml_copy_string (FZ_VERSION);
3525 ML (llpp_version (void))
3527 extern char llpp_version[];
3528 return caml_copy_string (llpp_version);
3531 static void diag_callback (void *user, const char *message)
3533 if (pthread_equal (pthread_self (), state.thread)) {
3534 printd ("emsg %s %s", (char *) user, message);
3536 else {
3537 puts (message);
3541 static fz_font *lsff (fz_context *ctx,int UNUSED_ATTR script,
3542 int UNUSED_ATTR language, int UNUSED_ATTR serif,
3543 int UNUSED_ATTR bold, int UNUSED_ATTR italic)
3545 static fz_font *font;
3546 static int done;
3548 if (!done) {
3549 char *path = getenv ("LLPP_FALLBACK_FONT");
3550 if (path) {
3551 font = fz_new_font_from_file (ctx, NULL, path, 0, 1);
3553 done = 1;
3555 return font;
3558 ML0 (setdcf (value path_v))
3560 free (state.dcf);
3561 state.dcf = NULL;
3562 const char *p = String_val (path_v);
3563 if (*p) {
3564 size_t len = caml_string_length (path_v);
3565 state.dcf = malloc (len + 1);
3566 if (!state.dcf) {
3567 err (1, errno, "malloc dimpath %zu", len + 1);
3569 memcpy (state.dcf, p, len);
3570 state.dcf[len] = 0;
3574 ML (init (value csock_v, value params_v))
3576 CAMLparam2 (csock_v, params_v);
3577 CAMLlocal2 (trim_v, fuzz_v);
3578 int ret, texcount, colorspace, mustoresize, redirstderr;
3579 const char *fontpath;
3580 const char *ext = TEXT_TYPE == GL_TEXTURE_2D
3581 ? "texture_non_power_of_two"
3582 : "texture_rectangle";
3584 if (!strstr ((const char *) glGetString (GL_EXTENSIONS), ext)) {
3585 errx (1, "OpenGL does not support '%s' extension", ext);
3587 state.csock = Int_val (csock_v);
3588 state.rotate = Int_val (Field (params_v, 0));
3589 state.fitmodel = Int_val (Field (params_v, 1));
3590 trim_v = Field (params_v, 2);
3591 texcount = Int_val (Field (params_v, 3));
3592 state.sliceheight = Int_val (Field (params_v, 4));
3593 mustoresize = Int_val (Field (params_v, 5));
3594 colorspace = Int_val (Field (params_v, 6));
3595 fontpath = String_val (Field (params_v, 7));
3596 redirstderr = Bool_val (Field (params_v, 8));
3598 if (redirstderr) {
3599 if (pipe (state.pfds)) {
3600 err (1, errno, "pipe");
3602 for (int ntries = 0; ntries < 1737; ++ntries) {
3603 if (-1 == dup2 (state.pfds[1], 2)) {
3604 if (EINTR == errno) {
3605 continue;
3607 err (1, errno, "dup2");
3609 break;
3611 } else {
3612 state.pfds[0] = 0;
3613 state.pfds[1] = 0;
3616 #ifdef MACOS
3617 state.utf8cs = 1;
3618 #else
3619 /* http://www.cl.cam.ac.uk/~mgk25/unicode.html */
3620 if (setlocale (LC_CTYPE, "")) {
3621 const char *cset = nl_langinfo (CODESET);
3622 state.utf8cs = !strcmp (cset, "UTF-8");
3624 else {
3625 err (1, errno, "setlocale");
3627 #endif
3629 state.ctx = fz_new_context (NULL, NULL, mustoresize);
3630 fz_register_document_handlers (state.ctx);
3631 if (redirstderr) {
3632 fz_set_error_callback (state.ctx, diag_callback, "[e]");
3633 fz_set_warning_callback (state.ctx, diag_callback, "[w]");
3635 fz_install_load_system_font_funcs (state.ctx, NULL, NULL, lsff);
3637 state.trimmargins = Bool_val (Field (trim_v, 0));
3638 fuzz_v = Field (trim_v, 1);
3639 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
3640 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
3641 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
3642 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
3644 set_tex_params (colorspace);
3646 if (*fontpath) {
3647 state.face = load_font (fontpath);
3649 else {
3650 int len;
3651 const unsigned char *data;
3653 data = pdf_lookup_substitute_font (state.ctx, 0, 0, 0, 0, &len);
3654 state.face = load_builtin_font (data, len);
3656 if (!state.face) {
3657 _exit (1);
3660 realloctexts (texcount);
3661 makestippletex ();
3663 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
3664 if (ret) {
3665 errx (1, "pthread_create: %d(%s)", ret, strerror (ret));
3668 CAMLreturn (Val_int (state.pfds[0]));
3671 #if FIXME || !FIXME
3672 static void UNUSED_ATTR NO_OPTIMIZE_ATTR refmacs (void) {}
3673 #endif