Ubuntu CI: make apt update before apt install
[llpp.git] / link.c
bloba4ef9dfb2582e5ab9b5fca4479368bb502c893ea
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 #ifdef LLPARANOIDP
22 #pragma GCC diagnostic error "-Weverything"
23 #pragma GCC diagnostic ignored "-Wpadded"
24 #pragma GCC diagnostic ignored "-Wsign-conversion"
25 #pragma GCC diagnostic ignored "-Wdocumentation-unknown-command"
26 #pragma GCC diagnostic ignored "-Wdocumentation"
27 #pragma GCC diagnostic ignored "-Wdouble-promotion"
28 #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion"
29 #else
30 #pragma GCC diagnostic error "-Wcast-qual"
31 #endif
33 #include GL_H
35 #define CAML_NAME_SPACE
36 #include <caml/fail.h>
37 #include <caml/alloc.h>
38 #include <caml/memory.h>
39 #include <caml/unixsupport.h>
41 #pragma GCC diagnostic push
42 #pragma GCC diagnostic ignored "-Wfloat-equal"
43 #include <mupdf/fitz.h>
44 #include <mupdf/pdf.h>
45 #pragma GCC diagnostic pop
47 #pragma GCC diagnostic push
48 #ifdef __clang__
49 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
50 #endif
51 #include <ft2build.h>
52 #include FT_FREETYPE_H
53 #pragma GCC diagnostic pop
55 #include "cutils.h"
57 #define ARSERT(c) !(c) ? errx (1, "%s:%d " #c, __FILE__, __LINE__) : (void) 0
58 #define ML(d) extern value ml_##d; value ml_##d
59 #define ML0(d) extern void ml_##d; void ml_##d
60 #define STTI(st) ((unsigned int) (st))
62 enum { Copen=23, Ccs, Cfreepage, Cfreetile, Csearch, Cgeometry, Creqlayout,
63 Cpage, Ctile, Ctrimset, Csettrim, Csliceh, Cinterrupt };
64 enum { FitWidth, FitProportional, FitPage };
65 enum { LDfirst, LDlast };
66 enum { LDfirstvisible, LDleft, LDright, LDdown, LDup };
67 enum { Uuri, Utext, Utextannot, Ufileannot, Unone };
68 enum { MarkPage, MarkBlock, MarkLine, MarkWord };
70 struct slice {
71 int h;
72 int texindex;
75 struct tile {
76 int w, h;
77 int slicecount;
78 int sliceheight;
79 fz_pixmap *pixmap;
80 struct slice slices[1];
83 struct pagedim {
84 int pageno;
85 int rotate;
86 int left;
87 int tctmready;
88 fz_irect bounds;
89 fz_rect pagebox;
90 fz_rect mediabox;
91 fz_matrix ctm, zoomctm, tctm;
94 struct slink {
95 enum { SLINK, SANNOT } tag;
96 fz_irect bbox;
97 union {
98 fz_link *link;
99 pdf_annot *annot;
100 } u;
103 struct annot {
104 fz_irect bbox;
105 pdf_annot *annot;
108 struct page {
109 int tgen;
110 int sgen;
111 int agen;
112 int pageno;
113 int pdimno;
114 fz_stext_page *text;
115 fz_page *fzpage;
116 fz_display_list *dlist;
117 fz_link *links;
118 int slinkcount;
119 struct slink *slinks;
120 int annotcount;
121 struct annot *annots;
122 fz_stext_char *fmark, *lmark;
125 static struct {
126 pthread_mutex_t mutex;
127 int sliceheight;
128 struct pagedim *pagedims;
129 int pagecount;
130 int pagedimcount;
131 fz_document *doc;
132 fz_context *ctx;
133 int w, h;
134 char *dcf;
135 int pfds[2];
137 struct {
138 int index, count;
139 GLuint *ids;
140 GLenum iform, form, ty;
141 struct {
142 int w, h;
143 struct slice *slice;
144 } *owners;
145 } tex;
147 fz_colorspace *colorspace;
148 float papercolor[4];
150 FT_Face face;
151 fz_pixmap *pig;
152 pthread_t thread;
153 fz_irect trimfuzz;
154 GLuint stid, boid;
155 int trimmargins, needoutline, gen, rotate, aalevel,
156 fitmodel, trimanew, csock, dirty, utf8cs;
158 GLfloat texcoords[8], vertices[16];
159 } state = { .mutex = PTHREAD_MUTEX_INITIALIZER };
161 static void lock (const char *cap)
163 int ret = pthread_mutex_lock (&state.mutex);
164 if (ret) {
165 errx (1, "%s: pthread_mutex_lock: %d(%s)", cap, ret, strerror (ret));
169 static void unlock (const char *cap)
171 int ret = pthread_mutex_unlock (&state.mutex);
172 if (ret) {
173 errx (1, "%s: pthread_mutex_unlock: %d(%s)", cap, ret, strerror (ret));
177 static int trylock (const char *cap)
179 int ret = pthread_mutex_trylock (&state.mutex);
180 if (ret && ret != EBUSY) {
181 errx (1, "%s: pthread_mutex_trylock: %d(%s)", cap,
182 ret, strerror (ret));
184 return ret == EBUSY;
187 static int hasdata (int fd)
189 int ret, avail;
190 ret = ioctl (fd, FIONREAD, &avail);
191 if (ret) {
192 err (1, errno, "hasdata: FIONREAD error ret=%d", ret);
194 return avail > 0;
197 ML (hasdata (value fd_v))
199 CAMLparam1 (fd_v);
200 CAMLreturn (Val_bool (hasdata (Int_val (fd_v))));
203 static void readdata (int fd, void *p, int size)
205 ssize_t n;
207 again:
208 n = read (fd, p, size);
209 if (n < 0) {
210 if (errno == EINTR) {
211 goto again;
213 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size, n);
215 if (n - size) {
216 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
220 static void writedata (int fd, char *p, int size)
222 ssize_t n;
223 uint32_t size4 = size;
224 struct iovec iov[2] = {
225 { .iov_base = &size4, .iov_len = 4 },
226 { .iov_base = p, .iov_len = size }
229 again:
230 n = writev (fd, iov, 2);
231 if (n < 0) {
232 if (errno == EINTR) {
233 goto again;
235 err (1, errno, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
237 if (n - size - 4) {
238 errx (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
242 static int readlen (int fd)
244 uint32_t u;
245 readdata (fd, &u, 4);
246 return u;
249 ML0 (wcmd (value fd_v, value bytes_v, value len_v))
251 CAMLparam3 (fd_v, bytes_v, len_v);
252 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
253 CAMLreturn0;
256 ML (rcmd (value fd_v))
258 CAMLparam1 (fd_v);
259 CAMLlocal1 (strdata_v);
260 int fd = Int_val (fd_v);
261 int len = readlen (fd);
262 strdata_v = caml_alloc_string (len);
263 readdata (fd, Bytes_val (strdata_v), len);
264 CAMLreturn (strdata_v);
267 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
269 char fbuf[64];
270 int size = sizeof (fbuf), len;
271 va_list ap;
272 char *buf = fbuf;
274 for (;;) {
275 va_start (ap, fmt);
276 len = vsnprintf (buf, size, fmt, ap);
277 va_end (ap);
279 if (len > -1) {
280 if (len < size - 4) {
281 writedata (state.csock, buf, len);
282 break;
284 else {
285 size = len + 5;
288 else {
289 err (1, errno, "vsnprintf for `%s' failed", fmt);
291 buf = realloc (buf == fbuf ? NULL : buf, size);
292 if (!buf) {
293 err (1, errno, "realloc for temp buf (%d bytes) failed", size);
296 if (buf != fbuf) {
297 free (buf);
301 static void closedoc (void)
303 if (state.doc) {
304 fz_drop_document (state.ctx, state.doc);
305 state.doc = NULL;
309 static int openxref (char *filename, char *mimetype, char *password,
310 int w, int h, int em)
312 for (int i = 0; i < state.tex.count; ++i) {
313 state.tex.owners[i].w = -1;
314 state.tex.owners[i].slice = NULL;
317 closedoc ();
319 state.dirty = 0;
320 if (state.pagedims) {
321 free (state.pagedims);
322 state.pagedims = NULL;
324 state.pagedimcount = 0;
326 fz_set_aa_level (state.ctx, state.aalevel);
327 if (mimetype) {
328 fz_stream *st = fz_open_file (state.ctx, filename);
329 state.doc = fz_open_document_with_stream (state.ctx, mimetype, st);
331 else {
332 state.doc = fz_open_document (state.ctx, filename);
334 if (fz_needs_password (state.ctx, state.doc)) {
335 if (password && !*password) {
336 printd ("pass");
337 return 0;
339 else {
340 int ok = fz_authenticate_password (state.ctx, state.doc, password);
341 if (!ok) {
342 printd ("pass fail");
343 return 0;
347 if (w >= 0 || h >= 0 || em >=0) {
348 fz_layout_document (state.ctx, state.doc, w, h, em);
350 state.pagecount = fz_count_pages (state.ctx, state.doc);
351 if (state.pagecount < 0) {
352 state.pagecount = 0;
353 return 0;
355 return 1;
358 static void docinfo (void)
360 struct { char *tag; char *name; } tab[] = {
361 { FZ_META_INFO_TITLE, "Title" },
362 { FZ_META_INFO_AUTHOR, "Author" },
363 { FZ_META_FORMAT, "Format" },
364 { FZ_META_ENCRYPTION, "Encryption" },
365 { FZ_META_INFO_CREATOR, "Creator" },
366 { FZ_META_INFO_PRODUCER, "Producer" },
367 { FZ_META_INFO_CREATIONDATE, "Creation date" },
368 { FZ_META_INFO_MODIFICATIONDATE, "Modification date"},
370 int len = 0, need;
371 char *buf = NULL;
373 for (size_t i = 0; i < sizeof (tab) / sizeof (*tab); ++i) {
374 again:
375 need = fz_lookup_metadata (state.ctx, state.doc, tab[i].tag, buf, len);
376 if (need > 0) {
377 if (need <= len) {
378 printd ("info %s\t%s", tab[i].name, buf);
380 else {
381 buf = realloc (buf, need);
382 if (!buf) {
383 err (1, errno, "docinfo realloc %d", need);
385 len = need;
386 goto again;
390 free (buf);
392 printd ("infoend");
395 static void unlinktile (struct tile *tile)
397 for (int i = 0; i < tile->slicecount; ++i) {
398 struct slice *s = &tile->slices[i];
400 if (s->texindex != -1) {
401 if (state.tex.owners[s->texindex].slice == s) {
402 state.tex.owners[s->texindex].slice = NULL;
408 static void freepage (struct page *page)
410 if (page) {
411 fz_drop_stext_page (state.ctx, page->text);
412 free (page->slinks);
413 fz_drop_display_list (state.ctx, page->dlist);
414 fz_drop_page (state.ctx, page->fzpage);
415 free (page);
419 static void freetile (struct tile *tile)
421 unlinktile (tile);
422 fz_drop_pixmap (state.ctx, state.pig);
423 state.pig = tile->pixmap;
424 free (tile);
427 static void trimctm (pdf_page *page, int pindex)
429 struct pagedim *pdim = &state.pagedims[pindex];
431 if (!page) {
432 return;
434 if (!pdim->tctmready) {
435 fz_rect realbox, mediabox;
436 fz_matrix page_ctm, ctm;
438 ctm = fz_concat (fz_rotate (-pdim->rotate), fz_scale (1, -1));
439 realbox = fz_transform_rect (pdim->mediabox, ctm);
440 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
441 pdim->tctm = fz_concat (
442 fz_invert_matrix (page_ctm),
443 fz_concat (ctm, fz_translate (-realbox.x0, -realbox.y0)));
444 pdim->tctmready = 1;
448 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
450 fz_matrix ctm;
451 ptrdiff_t pdimno = pdim - state.pagedims;
453 ARSERT (pdim - state.pagedims < INT_MAX);
454 if (pdf_specifics (state.ctx, state.doc)) {
455 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
456 ctm = fz_concat (pdim->tctm, pdim->ctm);
458 else {
459 ctm = fz_concat (fz_translate (-pdim->mediabox.x0, -pdim->mediabox.y0),
460 pdim->ctm);
462 return ctm;
465 static fz_matrix pagectm (struct page *page)
467 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
470 static void *loadpage (int pageno, int pindex)
472 fz_device *dev;
473 struct page *page;
475 page = calloc (sizeof (struct page), 1);
476 if (!page) {
477 err (1, errno, "calloc page %d", pageno);
480 page->dlist = fz_new_display_list (state.ctx, fz_infinite_rect);
481 dev = fz_new_list_device (state.ctx, page->dlist);
482 fz_try (state.ctx) {
483 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
484 fz_run_page (state.ctx, page->fzpage, dev, fz_identity, NULL);
486 fz_catch (state.ctx) {
487 page->fzpage = NULL;
489 fz_close_device (state.ctx, dev);
490 fz_drop_device (state.ctx, dev);
492 page->pdimno = pindex;
493 page->pageno = pageno;
494 page->sgen = state.gen;
495 page->agen = state.gen;
496 page->tgen = state.gen;
497 return page;
500 static struct tile *alloctile (int h)
502 int slicecount;
503 size_t tilesize;
504 struct tile *tile;
506 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
507 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
508 tile = calloc (tilesize, 1);
509 if (!tile) {
510 err (1, errno, "cannot allocate tile (%zu bytes)", tilesize);
512 for (int i = 0; i < slicecount; ++i) {
513 int sh = fz_mini (h, state.sliceheight);
514 tile->slices[i].h = sh;
515 tile->slices[i].texindex = -1;
516 h -= sh;
518 tile->slicecount = slicecount;
519 tile->sliceheight = state.sliceheight;
520 return tile;
523 static struct tile *rendertile (struct page *page, int x, int y, int w, int h)
525 fz_irect bbox;
526 fz_matrix ctm;
527 fz_device *dev;
528 struct tile *tile;
529 struct pagedim *pdim;
531 tile = alloctile (h);
532 pdim = &state.pagedims[page->pdimno];
534 bbox = pdim->bounds;
535 bbox.x0 += x;
536 bbox.y0 += y;
537 bbox.x1 = bbox.x0 + w;
538 bbox.y1 = bbox.y0 + h;
540 if (state.pig) {
541 if (state.pig->w == w
542 && state.pig->h == h
543 && state.pig->colorspace == state.colorspace) {
544 tile->pixmap = state.pig;
545 tile->pixmap->x = bbox.x0;
546 tile->pixmap->y = bbox.y0;
548 else {
549 fz_drop_pixmap (state.ctx, state.pig);
551 state.pig = NULL;
553 if (!tile->pixmap) {
554 tile->pixmap = fz_new_pixmap_with_bbox (state.ctx,
555 state.colorspace,
556 bbox, NULL, 1);
559 tile->w = w;
560 tile->h = h;
561 fz_fill_pixmap_with_color (state.ctx, tile->pixmap,
562 fz_device_rgb (state.ctx),
563 state.papercolor,
564 fz_default_color_params);
566 dev = fz_new_draw_device (state.ctx, fz_identity, tile->pixmap);
567 ctm = pagectm (page);
568 fz_run_display_list (state.ctx, page->dlist, dev, ctm,
569 fz_rect_from_irect (bbox), NULL);
570 fz_close_device (state.ctx, dev);
571 fz_drop_device (state.ctx, dev);
573 return tile;
576 static void initpdims1 (void)
578 int shown = 0;
579 struct pagedim *p;
580 pdf_document *pdf;
581 fz_context *ctx = state.ctx;
582 int pageno, trim, show, cxcount;
583 fz_rect rootmediabox = fz_empty_rect;
585 fz_var (p);
586 fz_var (pdf);
587 fz_var (shown);
588 fz_var (pageno);
589 fz_var (cxcount);
591 cxcount = state.pagecount;
592 if ((pdf = pdf_specifics (ctx, state.doc))) {
593 pdf_obj *obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
594 "Root/Pages/MediaBox");
595 rootmediabox = pdf_to_rect (ctx, obj);
596 pdf_load_page_tree (ctx, pdf);
599 for (pageno = 0; pageno < cxcount; ++pageno) {
600 int rotate = 0;
601 fz_rect mediabox = fz_empty_rect;
603 fz_var (rotate);
604 if (pdf) {
605 pdf_obj *pageobj = NULL;
607 fz_var (pageobj);
608 if (pdf->rev_page_map) {
609 for (int i = 0; i < pdf->rev_page_count; ++i) {
610 if (pdf->rev_page_map[i].page == pageno) {
611 pageobj = pdf_get_xref_entry (
612 ctx, pdf, pdf->rev_page_map[i].object
613 )->obj;
614 break;
618 if (!pageobj) {
619 pageobj = pdf_lookup_page_obj (ctx, pdf, pageno);
622 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
624 if (state.trimmargins) {
625 pdf_obj *obj;
626 pdf_page *page;
628 fz_try (ctx) {
629 page = pdf_load_page (ctx, pdf, pageno);
630 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
631 trim = state.trimanew || !obj;
632 if (trim) {
633 fz_rect rect;
634 fz_device *dev;
635 fz_matrix ctm, page_ctm;
637 dev = fz_new_bbox_device (ctx, &rect);
638 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
639 ctm = fz_invert_matrix (page_ctm);
640 pdf_run_page (ctx, page, dev, fz_identity, NULL);
641 fz_close_device (ctx, dev);
642 fz_drop_device (ctx, dev);
644 rect.x0 += state.trimfuzz.x0;
645 rect.x1 += state.trimfuzz.x1;
646 rect.y0 += state.trimfuzz.y0;
647 rect.y1 += state.trimfuzz.y1;
648 rect = fz_transform_rect (rect, ctm);
649 rect = fz_intersect_rect (rect, mediabox);
651 if (!fz_is_empty_rect (rect)) {
652 mediabox = rect;
655 obj = pdf_new_array (ctx, pdf, 4);
656 pdf_array_push_real (ctx, obj, mediabox.x0);
657 pdf_array_push_real (ctx, obj, mediabox.y0);
658 pdf_array_push_real (ctx, obj, mediabox.x1);
659 pdf_array_push_real (ctx, obj, mediabox.y1);
660 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
662 else {
663 mediabox.x0 = pdf_array_get_real (ctx, obj, 0);
664 mediabox.y0 = pdf_array_get_real (ctx, obj, 1);
665 mediabox.x1 = pdf_array_get_real (ctx, obj, 2);
666 mediabox.y1 = pdf_array_get_real (ctx, obj, 3);
669 fz_drop_page (ctx, &page->super);
670 show = (pageno + 1 == state.pagecount)
671 || (trim ? pageno % 5 == 0 : pageno % 20 == 0);
672 if (show) {
673 printd ("progress %f Trimming %d",
674 (double) (pageno + 1) / state.pagecount,
675 pageno + 1);
678 fz_catch (ctx) {
679 printd ("emsg failed to load page %d", pageno);
682 else {
683 int empty = 0;
684 fz_rect cropbox;
686 mediabox =
687 pdf_to_rect (ctx,
688 pdf_dict_get_inheritable (
689 ctx,
690 pageobj,
691 PDF_NAME (MediaBox)
694 if (fz_is_empty_rect (mediabox)) {
695 mediabox.x0 = 0;
696 mediabox.y0 = 0;
697 mediabox.x1 = 612;
698 mediabox.y1 = 792;
699 empty = 1;
702 cropbox =
703 pdf_to_rect (ctx, pdf_dict_gets (ctx, pageobj, "CropBox"));
704 if (!fz_is_empty_rect (cropbox)) {
705 if (empty) {
706 mediabox = cropbox;
708 else {
709 mediabox = fz_intersect_rect (mediabox, cropbox);
712 else {
713 if (empty) {
714 if (fz_is_empty_rect (rootmediabox)) {
715 printd ("emsg cannot find size of page %d",
716 pageno);
718 else {
719 mediabox = rootmediabox;
725 else {
726 if (state.trimmargins) {
727 fz_page *page;
729 fz_try (ctx) {
730 page = fz_load_page (ctx, state.doc, pageno);
731 mediabox = fz_bound_page (ctx, page);
732 if (state.trimmargins) {
733 fz_rect rect;
734 fz_device *dev;
736 dev = fz_new_bbox_device (ctx, &rect);
737 fz_run_page (ctx, page, dev, fz_identity, NULL);
738 fz_close_device (ctx, dev);
739 fz_drop_device (ctx, dev);
741 rect.x0 += state.trimfuzz.x0;
742 rect.x1 += state.trimfuzz.x1;
743 rect.y0 += state.trimfuzz.y0;
744 rect.y1 += state.trimfuzz.y1;
745 rect = fz_intersect_rect (rect, mediabox);
747 if (!fz_is_empty_rect (rect)) {
748 mediabox = rect;
751 fz_drop_page (ctx, page);
753 fz_catch (ctx) {
756 else {
757 fz_page *page;
758 fz_try (ctx) {
759 page = fz_load_page (ctx, state.doc, pageno);
760 mediabox = fz_bound_page (ctx, page);
761 fz_drop_page (ctx, page);
763 show = !state.trimmargins && pageno % 20 == 0;
764 if (show) {
765 shown = 1;
766 printd ("progress %f Gathering dimensions %d",
767 (double) pageno / state.pagecount, pageno);
770 fz_catch (ctx) {
771 printd ("emsg failed to load page %d", pageno);
775 if (state.pagedimcount == 0
776 || ((void) (p = &state.pagedims[state.pagedimcount-1])
777 , p->rotate != rotate)
778 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
779 size_t size;
781 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
782 state.pagedims = realloc (state.pagedims, size);
783 if (!state.pagedims) {
784 err (1, errno, "realloc pagedims to %zu (%d elems)",
785 size, state.pagedimcount + 1);
788 p = &state.pagedims[state.pagedimcount++];
789 p->rotate = rotate;
790 p->mediabox = mediabox;
791 p->pageno = pageno;
794 state.trimanew = 0;
795 if (shown) {
796 printd ("progress 1");
800 static void initpdims (void)
802 FILE *f = state.dcf ? fopen (state.dcf, "rb") : NULL;
803 if (f) {
804 size_t nread;
806 nread = fread (&state.pagedimcount, sizeof (state.pagedimcount), 1, f);
807 if (nread - 1) {
808 err (1, errno, "fread pagedim %zu", sizeof (state.pagedimcount));
810 size_t size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
811 state.pagedims = realloc (state.pagedims, size);
812 if (!state.pagedims) {
813 err (1, errno, "realloc pagedims to %zu (%d elems)",
814 size, state.pagedimcount + 1);
816 if (fread (state.pagedims,
817 sizeof (*state.pagedims),
818 state.pagedimcount+1,
819 f) - (state.pagedimcount+1)) {
820 err (1, errno, "fread pagedim data %zu %d",
821 sizeof (*state.pagedims), state.pagedimcount+1);
823 fclose (f);
826 if (!state.pagedims) {
827 initpdims1 ();
828 if (state.dcf) {
829 f = fopen (state.dcf, "wb");
830 if (!f) {
831 err (1, errno, "fopen %s for writing", state.dcf);
833 if (fwrite (&state.pagedimcount,
834 sizeof (state.pagedimcount), 1, f) - 1) {
835 err (1, errno, "fwrite pagedimcunt %zu",
836 sizeof (state.pagedimcount));
838 if (fwrite (state.pagedims, sizeof (*state.pagedims),
839 state.pagedimcount + 1, f)
840 - (state.pagedimcount + 1)) {
841 err (1, errno, "fwrite pagedim data %zu %u",
842 sizeof (*state.pagedims), state.pagedimcount+1);
844 fclose (f);
849 static void layout (void)
851 int pindex;
852 fz_rect box;
853 fz_matrix ctm;
854 struct pagedim *p = NULL;
855 float zw, w, maxw = 0.0, zoom = 1.0;
857 if (state.pagedimcount == 0) {
858 return;
861 switch (state.fitmodel) {
862 case FitProportional:
863 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
864 float x0, x1;
866 p = &state.pagedims[pindex];
867 box = fz_transform_rect (p->mediabox,
868 fz_rotate (p->rotate + state.rotate));
870 x0 = fz_min (box.x0, box.x1);
871 x1 = fz_max (box.x0, box.x1);
873 w = x1 - x0;
874 maxw = fz_max (w, maxw);
875 zoom = state.w / maxw;
877 break;
879 case FitPage:
880 maxw = state.w;
881 break;
883 case FitWidth:
884 break;
886 default:
887 ARSERT (0 && state.fitmodel);
890 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
891 p = &state.pagedims[pindex];
892 ctm = fz_rotate (state.rotate);
893 box = fz_transform_rect (p->mediabox,
894 fz_rotate (p->rotate + state.rotate));
895 w = box.x1 - box.x0;
896 switch (state.fitmodel) {
897 case FitProportional:
898 p->left = (int) (((maxw - w) * zoom) / 2.f);
899 break;
900 case FitPage:
902 float zh, h;
903 zw = maxw / w;
904 h = box.y1 - box.y0;
905 zh = state.h / h;
906 zoom = fz_min (zw, zh);
907 p->left = (int) ((maxw - (w * zoom)) / 2.f);
909 break;
910 case FitWidth:
911 p->left = 0;
912 zoom = state.w / w;
913 break;
916 p->zoomctm = fz_scale (zoom, zoom);
917 ctm = fz_concat (p->zoomctm, ctm);
919 p->pagebox = p->mediabox;
920 p->pagebox = fz_transform_rect (p->pagebox, fz_rotate (p->rotate));
921 p->pagebox.x1 -= p->pagebox.x0;
922 p->pagebox.y1 -= p->pagebox.y0;
923 p->pagebox.x0 = 0;
924 p->pagebox.y0 = 0;
925 p->bounds = fz_round_rect (fz_transform_rect (p->pagebox, ctm));
926 p->ctm = ctm;
928 ctm = fz_concat (fz_translate (0, -p->mediabox.y1),
929 fz_scale (zoom, -zoom));
930 p->tctmready = 0;
933 do {
934 printd ("pdim %u %d %d %d", p->pageno, p->left,
935 abs (p->bounds.x0 - p->bounds.x1),
936 abs (p->bounds.y0 - p->bounds.y1));
937 } while (p-- != state.pagedims);
940 static struct pagedim *pdimofpageno (int pageno)
942 struct pagedim *pdim = state.pagedims;
944 for (int i = 0; i < state.pagedimcount; ++i) {
945 if (state.pagedims[i].pageno > pageno) {
946 break;
948 pdim = &state.pagedims[i];
950 return pdim;
953 static void recurse_outline (fz_outline *outline, int level)
955 while (outline) {
956 int pageno;
957 fz_point p;
958 fz_location loc;
960 loc = fz_resolve_link (state.ctx, state.doc, String_val (outline->uri),
961 &p.x, &p.y);
962 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
963 if (pageno >= 0) {
964 struct pagedim *pdim =
965 pdimofpageno (
966 fz_page_number_from_location (state.ctx, state.doc,
967 outline->page)
969 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
970 p = fz_transform_point (p, pdim->ctm);
971 printd ("o %d %d %d %d %s",
972 level, pageno, (int) p.y, h, outline->title);
974 else {
975 printd ("on %d %s", level, outline->title);
977 if (outline->down) {
978 recurse_outline (outline->down, level + 1);
980 outline = outline->next;
984 static void process_outline (void)
986 if (state.needoutline && state.pagedimcount) {
987 fz_outline *outline = NULL;
989 fz_var (outline);
990 fz_try (state.ctx) {
991 outline = fz_load_outline (state.ctx, state.doc);
992 state.needoutline = 0;
993 if (outline) {
994 recurse_outline (outline, 0);
997 fz_always (state.ctx) {
998 if (outline) {
999 fz_drop_outline (state.ctx, outline);
1002 fz_catch (state.ctx) {
1003 printd ("emsg %s", fz_caught_message (state.ctx));
1008 static char *strofline (fz_stext_line *line)
1010 char *p;
1011 char utf8[10];
1012 fz_stext_char *ch;
1013 size_t size = 0, cap = 80;
1015 p = malloc (cap + 1);
1016 if (!p) {
1017 return NULL;
1020 for (ch = line->first_char; ch; ch = ch->next) {
1021 int n = fz_runetochar (utf8, ch->c);
1022 if (size + n > cap) {
1023 cap *= 2;
1024 p = realloc (p, cap + 1);
1025 if (!p) {
1026 return NULL;
1030 memcpy (p + size, utf8, n);
1031 size += n;
1033 p[size] = 0;
1034 return p;
1037 enum a_searchresult { Found=61, NotFound, Interrupted, Error };
1039 static enum a_searchresult matchline (regex_t *re, fz_stext_line *line,
1040 int num_matches, int pageno)
1042 int ret;
1043 char *p;
1044 regmatch_t rm;
1046 p = strofline (line);
1047 if (!p) {
1048 return Error;
1051 ret = regexec (re, p, 1, &rm, 0);
1052 free (p);
1054 if (ret) {
1055 if (ret != REG_NOMATCH) {
1056 int isize;
1057 size_t size;
1058 char errbuf[80], *trail;
1060 size = regerror (ret, re, errbuf, sizeof (errbuf));
1061 if (size > 23) {
1062 isize = 23;
1063 trail = "...";
1065 else {
1066 isize = (int) size;
1067 trail = "";
1069 printd ("emsg regexec error '%*s%s'", isize, errbuf, trail);
1070 return Error;
1072 return NotFound;
1074 else {
1075 int o = 0;
1076 fz_quad s = line->first_char->quad, e;
1077 fz_stext_char *ch;
1079 if (rm.rm_so == rm.rm_eo) {
1080 return Found;
1083 for (ch = line->first_char; ch; ch = ch->next) {
1084 o += fz_runelen (ch->c);
1085 if (o > rm.rm_so) {
1086 s = ch->quad;
1087 break;
1090 for (;ch; ch = ch->next) {
1091 o += fz_runelen (ch->c);
1092 if (o > rm.rm_eo) {
1093 break;
1096 e = ch->quad;
1098 printd ("match %d %d %f %f %f %f %f %f %f %f",
1099 pageno, num_matches,
1100 s.ul.x, s.ul.y,
1101 e.ur.x, s.ul.y,
1102 e.lr.x, e.lr.y,
1103 s.ul.x, e.lr.y);
1104 return Found;
1108 /* wishful thinking function */
1109 static void search (regex_t *re, int pageno, int y, int forward)
1111 fz_device *tdev;
1112 double dur, start;
1113 char *cap = "bug";
1114 struct pagedim *pdim;
1115 fz_page *page = NULL;
1116 fz_stext_block *block;
1117 fz_stext_page *text = NULL;
1118 int niters = 0, num_matches = 0;
1119 enum a_searchresult the_searchresult = NotFound;
1121 start = now ();
1122 while (pageno >= 0 && pageno < state.pagecount && num_matches == 0) {
1123 if (niters++ == 5) {
1124 niters = 0;
1125 if (hasdata (state.csock)) {
1126 fz_drop_stext_page (state.ctx, text);
1127 fz_drop_page (state.ctx, page);
1128 the_searchresult = Interrupted;
1129 break;
1131 else {
1132 printd ("progress %f searching in page %d",
1133 (double) (pageno + 1) / state.pagecount, pageno);
1136 pdim = pdimofpageno (pageno);
1137 text = fz_new_stext_page (state.ctx, pdim->mediabox);
1138 tdev = fz_new_stext_device (state.ctx, text, 0);
1140 page = fz_load_page (state.ctx, state.doc, pageno);
1141 fz_run_page (state.ctx, page, tdev, pagectm1 (page, pdim), NULL);
1143 fz_close_device (state.ctx, tdev);
1144 fz_drop_device (state.ctx, tdev);
1146 if (forward) {
1147 for (block = text->first_block; block; block = block->next) {
1148 fz_stext_line *line;
1150 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1151 continue;
1154 for (line = block->u.t.first_line; line; line = line->next) {
1155 if (line->bbox.y0 < y + 1) {
1156 continue;
1159 the_searchresult =
1160 matchline (re, line, num_matches, pageno);
1161 num_matches += the_searchresult == Found;
1165 else {
1166 for (block = text->last_block; block; block = block->prev) {
1167 fz_stext_line *line;
1169 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1170 continue;
1173 for (line = block->u.t.last_line; line; line = line->prev) {
1174 if (line->bbox.y0 < y + 1) {
1175 continue;
1178 the_searchresult =
1179 matchline (re, line, num_matches, pageno);
1180 num_matches += the_searchresult == Found;
1185 if (forward) {
1186 pageno += 1;
1187 y = 0;
1189 else {
1190 pageno -= 1;
1191 y = INT_MAX;
1193 fz_drop_stext_page (state.ctx, text);
1194 text = NULL;
1195 fz_drop_page (state.ctx, page);
1196 page = NULL;
1198 dur = now () - start;
1199 switch (the_searchresult) {
1200 case Found: case NotFound: cap = ""; break;
1201 case Error: cap = "error "; break;
1202 case Interrupted: cap = "interrupt "; break;
1204 if (num_matches) {
1205 printd ("progress 1 %sfound %d in %f sec", cap, num_matches, dur);
1207 else {
1208 printd ("progress 1 %sfound nothing in %f sec", cap, dur);
1210 printd ("clearrects");
1213 static void set_tex_params (int colorspace)
1215 switch (colorspace) {
1216 case 0:
1217 state.tex.iform = GL_RGBA8;
1218 state.tex.form = GL_RGBA;
1219 state.tex.ty = GL_UNSIGNED_BYTE;
1220 state.colorspace = fz_device_rgb (state.ctx);
1221 break;
1222 case 1:
1223 state.tex.iform = GL_LUMINANCE_ALPHA;
1224 state.tex.form = GL_LUMINANCE_ALPHA;
1225 state.tex.ty = GL_UNSIGNED_BYTE;
1226 state.colorspace = fz_device_gray (state.ctx);
1227 break;
1228 default:
1229 errx (1, "invalid colorspce %d", colorspace);
1233 static void realloctexts (int texcount)
1235 size_t size;
1237 if (texcount == state.tex.count) {
1238 return;
1241 if (texcount < state.tex.count) {
1242 glDeleteTextures (state.tex.count - texcount, state.tex.ids + texcount);
1245 size = texcount * (sizeof (*state.tex.ids) + sizeof (*state.tex.owners));
1246 state.tex.ids = realloc (state.tex.ids, size);
1247 if (!state.tex.ids) {
1248 err (1, errno, "realloc texs %zu", size);
1251 state.tex.owners = (void *) (state.tex.ids + texcount);
1252 if (texcount > state.tex.count) {
1253 glGenTextures (texcount - state.tex.count,
1254 state.tex.ids + state.tex.count);
1255 for (int i = state.tex.count; i < texcount; ++i) {
1256 state.tex.owners[i].w = -1;
1257 state.tex.owners[i].slice = NULL;
1260 state.tex.count = texcount;
1261 state.tex.index = 0;
1264 static char *mbtoutf8 (char *s)
1266 char *p, *r;
1267 wchar_t *tmp;
1268 size_t i, ret, len;
1270 if (state.utf8cs) {
1271 return s;
1274 len = mbstowcs (NULL, s, strlen (s));
1275 if (len == 0 || len == (size_t) -1) {
1276 if (len) {
1277 printd ("emsg mbtoutf8: mbstowcs: %d(%s)", errno, strerror (errno));
1279 return s;
1282 tmp = calloc (len, sizeof (wchar_t));
1283 if (!tmp) {
1284 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d(%s)",
1285 len, sizeof (wchar_t), errno, strerror (errno));
1286 return s;
1289 ret = mbstowcs (tmp, s, len);
1290 if (ret == (size_t) -1) {
1291 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d(%s)",
1292 len, errno, strerror (errno));
1293 free (tmp);
1294 return s;
1297 len = 0;
1298 for (i = 0; i < ret; ++i) {
1299 len += fz_runelen (tmp[i]);
1302 p = r = malloc (len + 1);
1303 if (!r) {
1304 printd ("emsg mbtoutf8: malloc(%zu)", len);
1305 free (tmp);
1306 return s;
1309 for (i = 0; i < ret; ++i) {
1310 p += fz_runetochar (p, tmp[i]);
1312 *p = 0;
1313 free (tmp);
1314 return r;
1317 ML (mbtoutf8 (value s_v))
1319 CAMLparam1 (s_v);
1320 CAMLlocal1 (ret_v);
1321 char *s, *r;
1323 s = &Byte (s_v, 0);
1324 r = mbtoutf8 (s);
1325 if (r == s) {
1326 ret_v = s_v;
1328 else {
1329 ret_v = caml_copy_string (r);
1330 free (r);
1332 CAMLreturn (ret_v);
1335 static void *mainloop (void UNUSED_ATTR *unused)
1337 char *p = NULL, c;
1338 int len, ret, oldlen = 0;
1340 fz_var (p);
1341 fz_var (oldlen);
1342 for (;;) {
1343 len = readlen (state.csock);
1344 if (len == 0) {
1345 errx (1, "readlen returned 0");
1348 if (oldlen < len) {
1349 p = realloc (p, len);
1350 if (!p) {
1351 err (1, errno, "realloc %d failed", len);
1353 oldlen = len;
1355 readdata (state.csock, p, len);
1356 c = p[len-1];
1357 p[len-1] = 0;
1359 switch (c) {
1360 case Copen: {
1361 int off, usedoccss, ok = 0;
1362 int w, h, em;
1363 char *password, *mimetype, *filename, *utf8filename;
1364 size_t filenamelen, mimetypelen;
1366 fz_var (ok);
1367 ret = sscanf (p, "%d %d %d %d %n", &usedoccss, &w, &h, &em, &off);
1368 if (ret != 4) {
1369 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1372 filename = p + off;
1373 filenamelen = strlen (filename);
1375 mimetype = filename + filenamelen + 1;
1376 mimetypelen = strlen (mimetype);
1378 password = mimetype + mimetypelen + 1;
1380 if (password[strlen (password) + 1]) {
1381 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1384 lock ("open");
1385 fz_set_use_document_css (state.ctx, usedoccss);
1386 fz_try (state.ctx) {
1387 ok = openxref (filename, mimetypelen ? mimetype : NULL,
1388 password, w, h, em);
1390 fz_catch (state.ctx) {
1391 utf8filename = mbtoutf8 (filename);
1392 printd ("emsg failed to load %s: %s", utf8filename,
1393 fz_caught_message (state.ctx));
1394 if (utf8filename != filename) {
1395 free (utf8filename);
1398 if (ok) {
1399 docinfo ();
1400 initpdims ();
1402 unlock ("open");
1403 state.needoutline = ok;
1404 break;
1406 case Ccs: {
1407 int i, colorspace;
1409 ret = sscanf (p, "%d", &colorspace);
1410 if (ret != 1) {
1411 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1413 lock ("cs");
1414 set_tex_params (colorspace);
1415 for (i = 0; i < state.tex.count; ++i) {
1416 state.tex.owners[i].w = -1;
1417 state.tex.owners[i].slice = NULL;
1419 unlock ("cs");
1420 break;
1422 case Cfreepage: {
1423 void *ptr;
1425 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1426 if (ret != 1) {
1427 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1429 lock ("freepage");
1430 freepage (ptr);
1431 unlock ("freepage");
1432 break;
1434 case Cfreetile: {
1435 void *ptr;
1437 ret = sscanf (p, "%" SCNxPTR, (uintptr_t *) &ptr);
1438 if (ret != 1) {
1439 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1441 lock ("freetile");
1442 freetile (ptr);
1443 unlock ("freetile");
1444 break;
1446 case Csearch: {
1447 int icase, pageno, y, len2, forward;
1448 regex_t re;
1450 ret = sscanf (p, "%d %d %d %d,%n",
1451 &icase, &pageno, &y, &forward, &len2);
1452 if (ret != 4) {
1453 errx (1, "malformed search `%s' ret=%d", p, ret);
1456 char *pat = p + len2;
1457 ret = regcomp (&re, pat, REG_EXTENDED | (icase ? REG_ICASE : 0));
1458 if (ret) {
1459 char errbuf[80];
1460 size_t size;
1462 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1463 printd ("emsg regcomp failed `%.*s'", (int) size, errbuf);
1465 else {
1466 lock ("search");
1467 search (&re, pageno, y, forward);
1468 unlock ("search");
1469 regfree (&re);
1471 break;
1473 case Cgeometry: {
1474 int w, h, fitmodel;
1476 printd ("clear");
1477 ret = sscanf (p, "%d %d %d", &w, &h, &fitmodel);
1478 if (ret != 3) {
1479 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1482 lock ("geometry");
1483 state.h = h;
1484 if (w != state.w) {
1485 state.w = w;
1486 for (int i = 0; i < state.tex.count; ++i) {
1487 state.tex.owners[i].slice = NULL;
1490 state.fitmodel = fitmodel;
1491 layout ();
1492 process_outline ();
1494 state.gen++;
1495 unlock ("geometry");
1496 printd ("continue %d", state.pagecount);
1497 break;
1499 case Creqlayout: {
1500 char *nameddest;
1501 int rotate, off, h;
1502 int fitmodel;
1503 pdf_document *pdf;
1505 printd ("clear");
1506 ret = sscanf (p, "%d %d %d %n", &rotate, &fitmodel, &h, &off);
1507 if (ret != 3) {
1508 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1510 lock ("reqlayout");
1511 pdf = pdf_specifics (state.ctx, state.doc);
1512 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1513 state.gen += 1;
1515 state.rotate = rotate;
1516 state.fitmodel = fitmodel;
1517 state.h = h;
1518 layout ();
1519 process_outline ();
1521 nameddest = p + off;
1522 if (pdf && nameddest && *nameddest) {
1523 fz_point xy;
1524 struct pagedim *pdim;
1525 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1526 &xy.x, &xy.y);
1527 pdim = pdimofpageno (pageno);
1528 xy = fz_transform_point (xy, pdim->ctm);
1529 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1532 state.gen++;
1533 unlock ("reqlayout");
1534 printd ("continue %d", state.pagecount);
1535 break;
1537 case Cpage: {
1538 double a, b;
1539 struct page *page;
1540 int pageno, pindex;
1542 ret = sscanf (p, "%d %d", &pageno, &pindex);
1543 if (ret != 2) {
1544 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1547 lock ("page");
1548 a = now ();
1549 page = loadpage (pageno, pindex);
1550 b = now ();
1551 unlock ("page");
1553 printd ("page %" PRIxPTR " %f", (uintptr_t) page, b - a);
1554 break;
1556 case Ctile: {
1557 int x, y, w, h;
1558 struct page *page;
1559 struct tile *tile;
1560 double a, b;
1562 ret = sscanf (p, "%" SCNxPTR " %d %d %d %d",
1563 (uintptr_t *) &page, &x, &y, &w, &h);
1564 if (ret != 5) {
1565 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1568 lock ("tile");
1569 a = now ();
1570 tile = rendertile (page, x, y, w, h);
1571 b = now ();
1572 unlock ("tile");
1574 printd ("tile %d %d %" PRIxPTR " %u %f",
1575 x, y, (uintptr_t) tile,
1576 tile->w * tile->h * tile->pixmap->n, b - a);
1577 break;
1579 case Ctrimset: {
1580 fz_irect fuzz;
1581 int trimmargins;
1583 ret = sscanf (p, "%d %d %d %d %d",
1584 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1585 if (ret != 5) {
1586 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1589 lock ("trimset");
1590 state.trimmargins = trimmargins;
1591 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1592 state.trimanew = 1;
1593 state.trimfuzz = fuzz;
1595 unlock ("trimset");
1596 break;
1598 case Csettrim: {
1599 fz_irect fuzz;
1600 int trimmargins;
1602 ret = sscanf (p, "%d %d %d %d %d", &trimmargins,
1603 &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1604 if (ret != 5) {
1605 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1607 printd ("clear");
1608 lock ("settrim");
1609 state.trimmargins = trimmargins;
1610 state.needoutline = 1;
1611 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1612 state.trimanew = 1;
1613 state.trimfuzz = fuzz;
1615 state.pagedimcount = 0;
1616 free (state.pagedims);
1617 state.pagedims = NULL;
1618 initpdims ();
1619 layout ();
1620 process_outline ();
1621 unlock ("settrim");
1622 printd ("continue %d", state.pagecount);
1623 break;
1625 case Csliceh: {
1626 int h;
1628 ret = sscanf (p, "%d", &h);
1629 if (ret != 1) {
1630 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1632 if (h != state.sliceheight) {
1633 state.sliceheight = h;
1634 for (int i = 0; i < state.tex.count; ++i) {
1635 state.tex.owners[i].w = -1;
1636 state.tex.owners[i].h = -1;
1637 state.tex.owners[i].slice = NULL;
1640 break;
1642 case Cinterrupt:
1643 printd ("vmsg interrupted");
1644 break;
1645 default:
1646 errx (1, "unknown llpp ffi command - %d [%.*s]", c, len, p);
1649 return 0;
1652 ML (isexternallink (value uri_v))
1654 CAMLparam1 (uri_v);
1655 CAMLreturn (Val_bool (fz_is_external_link (state.ctx, String_val (uri_v))));
1658 ML (uritolocation (value uri_v))
1660 CAMLparam1 (uri_v);
1661 CAMLlocal1 (ret_v);
1662 fz_location loc;
1663 int pageno;
1664 fz_point xy;
1665 struct pagedim *pdim;
1667 loc = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1668 &xy.x, &xy.y);
1669 pageno = fz_page_number_from_location (state.ctx, state.doc, loc);
1670 pdim = pdimofpageno (pageno);
1671 xy = fz_transform_point (xy, pdim->ctm);
1672 ret_v = caml_alloc_tuple (3);
1673 Field (ret_v, 0) = Val_int (pageno);
1674 Field (ret_v, 1) = caml_copy_double ((double) xy.x);
1675 Field (ret_v, 2) = caml_copy_double ((double) xy.y);
1676 CAMLreturn (ret_v);
1679 ML (realloctexts (value texcount_v))
1681 CAMLparam1 (texcount_v);
1682 int ok;
1684 if (trylock (__func__)) {
1685 ok = 0;
1686 goto done;
1688 realloctexts (Int_val (texcount_v));
1689 ok = 1;
1690 unlock (__func__);
1692 done:
1693 CAMLreturn (Val_bool (ok));
1696 static void recti (int x0, int y0, int x1, int y1)
1698 GLfloat *v = state.vertices;
1700 glVertexPointer (2, GL_FLOAT, 0, v);
1701 v[0] = x0; v[1] = y0;
1702 v[2] = x1; v[3] = y0;
1703 v[4] = x0; v[5] = y1;
1704 v[6] = x1; v[7] = y1;
1705 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1708 static void showsel (struct page *page, int ox, int oy)
1710 fz_irect bbox;
1711 fz_rect rect;
1712 fz_stext_block *block;
1713 int seen = 0;
1714 unsigned char selcolor[] = {15,15,15,140};
1716 if (!page->fmark || !page->lmark) {
1717 return;
1720 glEnable (GL_BLEND);
1721 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1722 glColor4ubv (selcolor);
1724 ox += state.pagedims[page->pdimno].bounds.x0;
1725 oy += state.pagedims[page->pdimno].bounds.y0;
1727 for (block = page->text->first_block; block; block = block->next) {
1728 fz_stext_line *line;
1730 if (block->type != FZ_STEXT_BLOCK_TEXT) {
1731 continue;
1733 for (line = block->u.t.first_line; line; line = line->next) {
1734 fz_stext_char *ch;
1736 rect = fz_empty_rect;
1737 for (ch = line->first_char; ch; ch = ch->next) {
1738 fz_rect r;
1739 if (ch == page->fmark) {
1740 seen = 1;
1742 r = fz_rect_from_quad (ch->quad);
1743 if (seen) {
1744 rect = fz_union_rect (rect, r);
1746 if (ch == page->lmark) {
1747 bbox = fz_round_rect (rect);
1748 recti (bbox.x0 + ox, bbox.y0 + oy,
1749 bbox.x1 + ox, bbox.y1 + oy);
1750 goto done;
1753 if (!fz_is_empty_rect (rect)) {
1754 bbox = fz_round_rect (rect);
1755 recti (bbox.x0 + ox, bbox.y0 + oy,
1756 bbox.x1 + ox, bbox.y1 + oy);
1760 done:
1761 glDisable (GL_BLEND);
1764 #pragma GCC diagnostic push
1765 #pragma GCC diagnostic ignored "-Wconversion"
1766 #include "glfont.c"
1767 #pragma GCC diagnostic pop
1769 static void stipplerect (fz_matrix m, fz_point p[4],
1770 GLfloat *texcoords, GLfloat *vertices)
1772 fz_point p1 = fz_transform_point (p[0], m);
1773 fz_point p2 = fz_transform_point (p[1], m);
1774 fz_point p3 = fz_transform_point (p[2], m);
1775 fz_point p4 = fz_transform_point (p[3], m);
1777 float w = p2.x - p1.x;
1778 float h = p2.y - p1.y;
1779 float t = hypotf (w, h) * .25f;
1781 w = p3.x - p2.x;
1782 h = p3.y - p2.y;
1783 float s = hypotf (w, h) * .25f;
1785 texcoords[0] = 0; vertices[0] = p1.x; vertices[1] = p1.y;
1786 texcoords[1] = t; vertices[2] = p2.x; vertices[3] = p2.y;
1788 texcoords[2] = 0; vertices[4] = p2.x; vertices[5] = p2.y;
1789 texcoords[3] = s; vertices[6] = p3.x; vertices[7] = p3.y;
1791 texcoords[4] = 0; vertices[8] = p3.x; vertices[9] = p3.y;
1792 texcoords[5] = t; vertices[10] = p4.x; vertices[11] = p4.y;
1794 texcoords[6] = 0; vertices[12] = p4.x; vertices[13] = p4.y;
1795 texcoords[7] = s; vertices[14] = p1.x; vertices[15] = p1.y;
1797 glDrawArrays (GL_LINES, 0, 8);
1800 static void ensurelinks (struct page *page)
1802 if (!page->links) {
1803 page->links = fz_load_links (state.ctx, page->fzpage);
1807 static void highlightlinks (struct page *page, int xoff, int yoff)
1809 fz_point p[4];
1810 fz_matrix ctm;
1811 fz_link *link;
1812 GLfloat *texcoords = state.texcoords;
1813 GLfloat *vertices = state.vertices;
1815 ensurelinks (page);
1817 glEnable (GL_TEXTURE_1D);
1818 glEnable (GL_BLEND);
1819 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1820 glBindTexture (GL_TEXTURE_1D, state.stid);
1822 xoff -= state.pagedims[page->pdimno].bounds.x0;
1823 yoff -= state.pagedims[page->pdimno].bounds.y0;
1824 ctm = fz_concat (pagectm (page), fz_translate (xoff, yoff));
1826 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
1827 glVertexPointer (2, GL_FLOAT, 0, vertices);
1829 for (link = page->links; link; link = link->next) {
1831 p[0].x = link->rect.x0;
1832 p[0].y = link->rect.y0;
1834 p[1].x = link->rect.x1;
1835 p[1].y = link->rect.y0;
1837 p[2].x = link->rect.x1;
1838 p[2].y = link->rect.y1;
1840 p[3].x = link->rect.x0;
1841 p[3].y = link->rect.y1;
1843 /* TODO: different colours for different schemes */
1844 if (fz_is_external_link (state.ctx, link->uri)) {
1845 glColor3ub (0, 0, 255);
1847 else {
1848 glColor3ub (255, 0, 0);
1851 stipplerect (ctm, p, texcoords, vertices);
1854 for (int i = 0; i < page->annotcount; ++i) {
1855 struct annot *annot = &page->annots[i];
1857 p[0].x = annot->bbox.x0;
1858 p[0].y = annot->bbox.y0;
1860 p[1].x = annot->bbox.x1;
1861 p[1].y = annot->bbox.y0;
1863 p[2].x = annot->bbox.x1;
1864 p[2].y = annot->bbox.y1;
1866 p[3].x = annot->bbox.x0;
1867 p[3].y = annot->bbox.y1;
1869 glColor3ub (0, 0, 128);
1870 stipplerect (ctm, p, texcoords, vertices);
1873 glDisable (GL_BLEND);
1874 glDisable (GL_TEXTURE_1D);
1877 static int compareslinks (const void *l, const void *r)
1879 struct slink const *ls = l;
1880 struct slink const *rs = r;
1881 if (ls->bbox.y0 == rs->bbox.y0) {
1882 return ls->bbox.x0 - rs->bbox.x0;
1884 return ls->bbox.y0 - rs->bbox.y0;
1887 static void droptext (struct page *page)
1889 if (page->text) {
1890 fz_drop_stext_page (state.ctx, page->text);
1891 page->fmark = NULL;
1892 page->lmark = NULL;
1893 page->text = NULL;
1897 static void dropannots (struct page *page)
1899 if (page->annots) {
1900 free (page->annots);
1901 page->annots = NULL;
1902 page->annotcount = 0;
1906 static void ensureannots (struct page *page)
1908 int i, count = 0;
1909 pdf_annot *annot;
1910 pdf_document *pdf;
1911 pdf_page *pdfpage;
1913 pdf = pdf_specifics (state.ctx, state.doc);
1914 if (!pdf) {
1915 return;
1918 pdfpage = pdf_page_from_fz_page (state.ctx, page->fzpage);
1919 if (state.gen != page->agen) {
1920 dropannots (page);
1921 page->agen = state.gen;
1923 if (page->annots) {
1924 return;
1927 for (annot = pdf_first_annot (state.ctx, pdfpage);
1928 annot;
1929 annot = pdf_next_annot (state.ctx, annot)) {
1930 count++;
1933 if (count > 0) {
1934 page->annotcount = count;
1935 page->annots = calloc (count, sizeof (*page->annots));
1936 if (!page->annots) {
1937 err (1, errno, "calloc annots %d", count);
1940 for (annot = pdf_first_annot (state.ctx, pdfpage), i = 0;
1941 annot;
1942 annot = pdf_next_annot (state.ctx, annot), i++) {
1943 fz_rect rect;
1945 rect = pdf_bound_annot (state.ctx, annot);
1946 page->annots[i].annot = annot;
1947 page->annots[i].bbox = fz_round_rect (rect);
1952 static void dropslinks (struct page *page)
1954 if (page->slinks) {
1955 free (page->slinks);
1956 page->slinks = NULL;
1957 page->slinkcount = 0;
1959 if (page->links) {
1960 fz_drop_link (state.ctx, page->links);
1961 page->links = NULL;
1965 static void ensureslinks (struct page *page)
1967 fz_matrix ctm;
1968 int i, count;
1969 size_t slinksize = sizeof (*page->slinks);
1970 fz_link *link;
1972 ensureannots (page);
1973 if (state.gen != page->sgen) {
1974 dropslinks (page);
1975 page->sgen = state.gen;
1977 if (page->slinks) {
1978 return;
1981 ensurelinks (page);
1982 ctm = pagectm (page);
1984 count = page->annotcount;
1985 for (link = page->links; link; link = link->next) {
1986 count++;
1988 if (count > 0) {
1989 int j;
1991 page->slinkcount = count;
1992 page->slinks = calloc (count, slinksize);
1993 if (!page->slinks) {
1994 err (1, errno, "calloc slinks %d", count);
1997 for (i = 0, link = page->links; link; ++i, link = link->next) {
1998 fz_rect rect;
2000 rect = link->rect;
2001 rect = fz_transform_rect (rect, ctm);
2002 page->slinks[i].tag = SLINK;
2003 page->slinks[i].u.link = link;
2004 page->slinks[i].bbox = fz_round_rect (rect);
2006 for (j = 0; j < page->annotcount; ++j, ++i) {
2007 fz_rect rect;
2008 rect = pdf_bound_annot (state.ctx, page->annots[j].annot);
2009 rect = fz_transform_rect (rect, ctm);
2010 page->slinks[i].bbox = fz_round_rect (rect);
2012 page->slinks[i].tag = SANNOT;
2013 page->slinks[i].u.annot = page->annots[j].annot;
2015 qsort (page->slinks, count, slinksize, compareslinks);
2019 static void highlightslinks (struct page *page, int xoff, int yoff,
2020 int noff, const char *targ, unsigned int tlen,
2021 const char *chars, unsigned int clen, int hfsize)
2023 char buf[40];
2024 struct slink *slink;
2025 float x0, y0, x1, y1, w;
2027 ensureslinks (page);
2028 glColor3ub (0xc3, 0xb0, 0x91);
2029 for (int i = 0; i < page->slinkcount; ++i) {
2030 fmt_linkn (buf, chars, clen, i + noff);
2031 if (!tlen || !strncmp (targ, buf, tlen)) {
2032 slink = &page->slinks[i];
2034 x0 = slink->bbox.x0 + xoff - 5;
2035 y1 = slink->bbox.y0 + yoff - 5;
2036 y0 = y1 + 10 + hfsize;
2037 w = measure_string (state.face, hfsize, buf);
2038 x1 = x0 + w + 10;
2039 recti ((int) x0, (int) y0, (int) x1, (int) y1);
2043 glEnable (GL_BLEND);
2044 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2045 glEnable (GL_TEXTURE_2D);
2046 glColor3ub (0, 0, 0);
2047 for (int i = 0; i < page->slinkcount; ++i) {
2048 fmt_linkn (buf, chars, clen, i + noff);
2049 if (!tlen || !strncmp (targ, buf, tlen)) {
2050 slink = &page->slinks[i];
2052 x0 = slink->bbox.x0 + xoff;
2053 y0 = slink->bbox.y0 + yoff + hfsize;
2054 draw_string (state.face, hfsize, x0, y0, buf);
2057 glDisable (GL_TEXTURE_2D);
2058 glDisable (GL_BLEND);
2061 static void uploadslice (struct tile *tile, struct slice *slice)
2063 int offset;
2064 struct slice *slice1;
2065 unsigned char *texdata;
2067 offset = 0;
2068 for (slice1 = tile->slices; slice != slice1; slice1++) {
2069 offset += slice1->h * tile->w * tile->pixmap->n;
2071 if (slice->texindex != -1 && slice->texindex < state.tex.count
2072 && state.tex.owners[slice->texindex].slice == slice) {
2073 glBindTexture (TEXT_TYPE, state.tex.ids[slice->texindex]);
2075 else {
2076 int subimage = 0;
2077 int texindex = state.tex.index++ % state.tex.count;
2079 if (state.tex.owners[texindex].w == tile->w) {
2080 if (state.tex.owners[texindex].h >= slice->h) {
2081 subimage = 1;
2083 else {
2084 state.tex.owners[texindex].h = slice->h;
2087 else {
2088 state.tex.owners[texindex].h = slice->h;
2091 state.tex.owners[texindex].w = tile->w;
2092 state.tex.owners[texindex].slice = slice;
2093 slice->texindex = texindex;
2095 glBindTexture (TEXT_TYPE, state.tex.ids[texindex]);
2096 #if TEXT_TYPE == GL_TEXTURE_2D
2097 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2098 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2099 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2100 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2101 #endif
2102 texdata = tile->pixmap->samples;
2103 if (subimage) {
2104 glTexSubImage2D (TEXT_TYPE, 0, 0, 0, tile->w, slice->h,
2105 state.tex.form, state.tex.ty, texdata+offset);
2107 else {
2108 glTexImage2D (TEXT_TYPE, 0, state.tex.iform, tile->w, slice->h,
2109 0, state.tex.form, state.tex.ty, texdata+offset);
2114 ML0 (begintiles (void))
2116 glEnable (TEXT_TYPE);
2117 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2118 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2121 ML0 (endtiles (void))
2123 glDisable (TEXT_TYPE);
2126 ML0 (drawtile (value args_v, value ptr_v))
2128 CAMLparam2 (args_v, ptr_v);
2129 int dispx = Int_val (Field (args_v, 0));
2130 int dispy = Int_val (Field (args_v, 1));
2131 int dispw = Int_val (Field (args_v, 2));
2132 int disph = Int_val (Field (args_v, 3));
2133 int tilex = Int_val (Field (args_v, 4));
2134 int tiley = Int_val (Field (args_v, 5));
2135 struct tile *tile = parse_pointer (__func__, String_val (ptr_v));
2136 int slicey, firstslice;
2137 struct slice *slice;
2138 GLfloat *texcoords = state.texcoords;
2139 GLfloat *vertices = state.vertices;
2141 firstslice = tiley / tile->sliceheight;
2142 slice = &tile->slices[firstslice];
2143 slicey = tiley % tile->sliceheight;
2145 while (disph > 0) {
2146 int dh;
2148 dh = slice->h - slicey;
2149 dh = fz_mini (disph, dh);
2150 uploadslice (tile, slice);
2152 texcoords[0] = tilex; texcoords[1] = slicey;
2153 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2154 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2155 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2157 vertices[0] = dispx; vertices[1] = dispy;
2158 vertices[2] = dispx+dispw; vertices[3] = dispy;
2159 vertices[4] = dispx; vertices[5] = dispy+dh;
2160 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2162 #if TEXT_TYPE == GL_TEXTURE_2D
2163 for (int i = 0; i < 8; ++i) {
2164 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2166 #endif
2168 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2169 dispy += dh;
2170 disph -= dh;
2171 slice++;
2172 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2173 slicey = 0;
2175 CAMLreturn0;
2178 ML (postprocess (value ptr_v, value hlmask_v,
2179 value xoff_v, value yoff_v, value li_v))
2181 CAMLparam5 (ptr_v, hlmask_v, xoff_v, yoff_v, li_v);
2182 int xoff = Int_val (xoff_v);
2183 int yoff = Int_val (yoff_v);
2184 int noff = Int_val (Field (li_v, 0));
2185 const char *targ = String_val (Field (li_v, 1));
2186 mlsize_t tlen = caml_string_length (Field (li_v, 1));
2187 int hfsize = Int_val (Field (li_v, 2));
2188 const char *chars = String_val (Field (li_v, 3));
2189 mlsize_t clen = caml_string_length (Field (li_v, 3));
2190 int hlmask = Int_val (hlmask_v);
2191 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2193 if (!page->fzpage) {
2194 /* deal with loadpage failed pages */
2195 goto done;
2198 if (trylock (__func__)) {
2199 noff = -1;
2200 goto done;
2203 ensureannots (page);
2204 if (hlmask & 1) {
2205 highlightlinks (page, xoff, yoff);
2207 if (hlmask & 2) {
2208 highlightslinks (page, xoff, yoff, noff, targ, STTI (tlen),
2209 chars, STTI (clen), hfsize);
2210 noff = page->slinkcount;
2212 if (page->tgen == state.gen) {
2213 showsel (page, xoff, yoff);
2215 unlock (__func__);
2217 done:
2218 CAMLreturn (Val_int (noff));
2221 static struct annot *getannot (struct page *page, int x, int y)
2223 fz_point p;
2224 fz_matrix ctm;
2225 const fz_matrix *tctm;
2226 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2228 if (!page->annots) {
2229 return NULL;
2232 if (pdf) {
2233 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2234 tctm = &state.pagedims[page->pdimno].tctm;
2236 else {
2237 tctm = &fz_identity;
2240 p.x = x;
2241 p.y = y;
2243 ctm = fz_concat (*tctm, state.pagedims[page->pdimno].ctm);
2244 ctm = fz_invert_matrix (ctm);
2245 p = fz_transform_point (p, ctm);
2247 if (pdf) {
2248 for (int i = 0; i < page->annotcount; ++i) {
2249 struct annot *a = &page->annots[i];
2250 if (fz_is_point_inside_rect (p, pdf_bound_annot (state.ctx,
2251 a->annot))) {
2252 return a;
2256 return NULL;
2259 static fz_link *getlink (struct page *page, int x, int y)
2261 fz_link *link;
2262 fz_point p = { .x = x, .y = y };
2264 ensureslinks (page);
2265 p = fz_transform_point (p, fz_invert_matrix (pagectm (page)));
2267 for (link = page->links; link; link = link->next) {
2268 if (fz_is_point_inside_rect (p, link->rect)) {
2269 return link;
2272 return NULL;
2275 static void ensuretext (struct page *page)
2277 if (state.gen != page->tgen) {
2278 droptext (page);
2279 page->tgen = state.gen;
2281 if (!page->text) {
2282 fz_device *tdev;
2284 page->text = fz_new_stext_page (state.ctx,
2285 state.pagedims[page->pdimno].mediabox);
2286 tdev = fz_new_stext_device (state.ctx, page->text, 0);
2287 fz_run_display_list (state.ctx, page->dlist,
2288 tdev, pagectm (page), fz_infinite_rect, NULL);
2289 fz_close_device (state.ctx, tdev);
2290 fz_drop_device (state.ctx, tdev);
2294 ML (find_page_with_links (value start_page_v, value dir_v))
2296 CAMLparam2 (start_page_v, dir_v);
2297 CAMLlocal1 (ret_v);
2298 int i, dir = Int_val (dir_v);
2299 int start_page = Int_val (start_page_v);
2300 int end_page = dir > 0 ? state.pagecount : -1;
2301 pdf_document *pdf;
2303 fz_var (i);
2304 fz_var (end_page);
2305 ret_v = Val_int (0);
2306 lock (__func__);
2307 pdf = pdf_specifics (state.ctx, state.doc);
2308 for (i = start_page + dir; i != end_page; i += dir) {
2309 int found;
2311 fz_var (found);
2312 if (pdf) {
2313 pdf_page *page = NULL;
2315 fz_var (page);
2316 fz_try (state.ctx) {
2317 page = pdf_load_page (state.ctx, pdf, i);
2318 found = !!page->links || !!page->annots;
2320 fz_catch (state.ctx) {
2321 found = 0;
2323 fz_drop_page (state.ctx, &page->super);
2325 else {
2326 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2327 fz_link *link = fz_load_links (state.ctx, page);
2328 found = !!link;
2329 fz_drop_link (state.ctx, link);
2330 fz_drop_page (state.ctx, page);
2333 if (found) {
2334 ret_v = caml_alloc_small (1, 1);
2335 Field (ret_v, 0) = Val_int (i);
2336 goto unlock;
2339 unlock:
2340 unlock (__func__);
2341 CAMLreturn (ret_v);
2344 ML (findlink (value ptr_v, value dir_v))
2346 CAMLparam2 (ptr_v, dir_v);
2347 CAMLlocal2 (ret_v, pos_v);
2348 struct page *page;
2349 int dirtag, i, slinkindex;
2350 struct slink *found = NULL ,*slink;
2352 page = parse_pointer (__func__, String_val (ptr_v));
2353 ret_v = Val_int (0);
2354 lock (__func__);
2355 ensureslinks (page);
2357 if (Is_block (dir_v)) {
2358 dirtag = Tag_val (dir_v);
2359 switch (dirtag) {
2360 case LDfirstvisible:
2362 int x0, y0, dir, first_index, last_index;
2364 pos_v = Field (dir_v, 0);
2365 x0 = Int_val (Field (pos_v, 0));
2366 y0 = Int_val (Field (pos_v, 1));
2367 dir = Int_val (Field (pos_v, 2));
2369 if (dir >= 0) {
2370 dir = 1;
2371 first_index = 0;
2372 last_index = page->slinkcount;
2374 else {
2375 first_index = page->slinkcount - 1;
2376 last_index = -1;
2379 for (i = first_index; i != last_index; i += dir) {
2380 slink = &page->slinks[i];
2381 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2382 found = slink;
2383 break;
2387 break;
2389 case LDleft:
2390 slinkindex = Int_val (Field (dir_v, 0));
2391 found = &page->slinks[slinkindex];
2392 for (i = slinkindex - 1; i >= 0; --i) {
2393 slink = &page->slinks[i];
2394 if (slink->bbox.x0 < found->bbox.x0) {
2395 found = slink;
2396 break;
2399 break;
2401 case LDright:
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.x0 > found->bbox.x0) {
2407 found = slink;
2408 break;
2411 break;
2413 case LDdown:
2414 slinkindex = Int_val (Field (dir_v, 0));
2415 found = &page->slinks[slinkindex];
2416 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2417 slink = &page->slinks[i];
2418 if (slink->bbox.y0 >= found->bbox.y0) {
2419 found = slink;
2420 break;
2423 break;
2425 case LDup:
2426 slinkindex = Int_val (Field (dir_v, 0));
2427 found = &page->slinks[slinkindex];
2428 for (i = slinkindex - 1; i >= 0; --i) {
2429 slink = &page->slinks[i];
2430 if (slink->bbox.y0 <= found->bbox.y0) {
2431 found = slink;
2432 break;
2435 break;
2438 else {
2439 dirtag = Int_val (dir_v);
2440 switch (dirtag) {
2441 case LDfirst:
2442 found = page->slinks;
2443 break;
2445 case LDlast:
2446 if (page->slinks) {
2447 found = page->slinks + (page->slinkcount - 1);
2449 break;
2452 if (found) {
2453 ret_v = caml_alloc_small (2, 1);
2454 Field (ret_v, 0) = Val_int (found - page->slinks);
2457 unlock (__func__);
2458 CAMLreturn (ret_v);
2461 ML (getlink (value ptr_v, value n_v))
2463 CAMLparam2 (ptr_v, n_v);
2464 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2465 int n = Int_val (n_v);
2466 fz_link *link;
2467 struct page *page;
2468 struct slink *slink;
2470 ret_v = Val_int (0);
2471 page = parse_pointer (__func__, String_val (ptr_v));
2473 lock (__func__);
2474 ensureslinks (page);
2475 if (!page->slinkcount || n > page->slinkcount) goto unlock;
2476 slink = &page->slinks[n];
2477 if (slink->tag == SLINK) {
2478 link = slink->u.link;
2479 str_v = caml_copy_string (link->uri);
2480 ret_v = caml_alloc_small (1, Uuri);
2481 Field (ret_v, 0) = str_v;
2483 else {
2484 int ty = pdf_annot_type (state.ctx, slink->u.annot)
2485 == PDF_ANNOT_FILE_ATTACHMENT ? Ufileannot : Utextannot;
2487 ret_v = caml_alloc_small (1, ty);
2488 tup_v = caml_alloc_tuple (2);
2489 Field (ret_v, 0) = tup_v;
2490 Field (tup_v, 0) = ptr_v;
2491 Field (tup_v, 1) = n_v;
2493 unlock:
2494 unlock (__func__);
2495 CAMLreturn (ret_v);
2498 ML (getlinkn (value ptr_v, value c_v, value n_v, value noff_v))
2500 CAMLparam4 (ptr_v, c_v, n_v, noff_v);
2501 CAMLlocal1 (ret_v);
2502 char buf[40];
2503 struct page *page;
2504 const char *c = String_val (c_v);
2505 const char *n = String_val (n_v);
2506 mlsize_t clen = caml_string_length (c_v);
2507 page = parse_pointer (__func__, String_val (ptr_v));
2509 lock (__func__);
2510 ensureslinks (page);
2512 ret_v = Val_int (-page->slinkcount);
2513 for (int i = 0; i < page->slinkcount; ++i) {
2514 fmt_linkn (buf, c, STTI (clen), i - Int_val (noff_v));
2515 if (!strncmp (buf, n, clen)) {
2516 ret_v = Val_int (i+1);
2517 break;
2521 unlock (__func__);
2522 CAMLreturn (ret_v);
2525 ML (gettextannot (value ptr_v, value n_v))
2527 CAMLparam2 (ptr_v, n_v);
2528 CAMLlocal1 (ret_v);
2529 pdf_document *pdf;
2530 const char *contents = "";
2532 lock (__func__);
2533 pdf = pdf_specifics (state.ctx, state.doc);
2534 if (pdf) {
2535 struct page *page;
2536 pdf_annot *annot;
2537 struct slink *slink;
2539 page = parse_pointer (__func__, String_val (ptr_v));
2540 slink = &page->slinks[Int_val (n_v)];
2541 annot = slink->u.annot;
2542 contents = pdf_annot_contents (state.ctx, annot);
2544 unlock (__func__);
2545 ret_v = caml_copy_string (contents);
2546 CAMLreturn (ret_v);
2549 ML (getfileannot (value ptr_v, value n_v))
2551 CAMLparam2 (ptr_v, n_v);
2552 CAMLlocal1 (ret_v);
2554 lock (__func__);
2556 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2557 struct slink *slink = &page->slinks[Int_val (n_v)];
2558 pdf_obj *fs = pdf_dict_get (state.ctx,
2559 pdf_annot_obj (state.ctx, slink->u.annot),
2560 PDF_NAME (FS));
2561 ret_v = caml_copy_string (pdf_embedded_file_name (state.ctx, fs));
2563 unlock (__func__);
2564 CAMLreturn (ret_v);
2567 ML0 (savefileannot (value ptr_v, value n_v, value path_v))
2569 CAMLparam3 (ptr_v, n_v, path_v);
2570 struct page *page = parse_pointer (__func__, String_val (ptr_v));
2571 const char *path = String_val (path_v);
2573 lock (__func__);
2574 struct slink *slink = &page->slinks[Int_val (n_v)];
2575 fz_try (state.ctx) {
2576 pdf_obj *fs = pdf_dict_get (state.ctx,
2577 pdf_annot_obj (state.ctx, slink->u.annot),
2578 PDF_NAME (FS));
2579 fz_buffer *buf = pdf_load_embedded_file (state.ctx, fs);
2580 fz_save_buffer (state.ctx, buf, path);
2581 fz_drop_buffer (state.ctx, buf);
2582 printd ("progress 1 saved '%s'", path);
2584 fz_catch (state.ctx) {
2585 printd ("emsg saving '%s': %s", path, fz_caught_message (state.ctx));
2587 unlock (__func__);
2590 ML (getlinkrect (value ptr_v, value n_v))
2592 CAMLparam2 (ptr_v, n_v);
2593 CAMLlocal1 (ret_v);
2594 struct page *page;
2595 struct slink *slink;
2597 page = parse_pointer (__func__, String_val (ptr_v));
2598 ret_v = caml_alloc_tuple (4);
2599 lock (__func__);
2600 ensureslinks (page);
2602 slink = &page->slinks[Int_val (n_v)];
2603 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2604 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2605 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2606 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2607 unlock (__func__);
2608 CAMLreturn (ret_v);
2611 ML (whatsunder (value ptr_v, value x_v, value y_v))
2613 CAMLparam3 (ptr_v, x_v, y_v);
2614 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2615 fz_link *link;
2616 struct annot *annot;
2617 struct page *page;
2618 const char *ptr = String_val (ptr_v);
2619 int x = Int_val (x_v), y = Int_val (y_v);
2620 struct pagedim *pdim;
2622 ret_v = Val_int (0);
2623 if (trylock (__func__)) {
2624 goto done;
2627 page = parse_pointer (__func__, ptr);
2628 pdim = &state.pagedims[page->pdimno];
2629 x += pdim->bounds.x0;
2630 y += pdim->bounds.y0;
2632 annot = getannot (page, x, y);
2633 if (annot) {
2634 int i, n = -1, ty;
2636 ensureslinks (page);
2637 for (i = 0; i < page->slinkcount; ++i) {
2638 if (page->slinks[i].tag == SANNOT
2639 && page->slinks[i].u.annot == annot->annot) {
2640 n = i;
2641 break;
2644 ty = pdf_annot_type (state.ctx, annot->annot)
2645 == PDF_ANNOT_FILE_ATTACHMENT ? Ufileannot : Utextannot;
2647 ret_v = caml_alloc_small (1, ty);
2648 tup_v = caml_alloc_tuple (2);
2649 Field (ret_v, 0) = tup_v;
2650 Field (tup_v, 0) = ptr_v;
2651 Field (tup_v, 1) = Int_val (n);
2652 goto unlock;
2655 link = getlink (page, x, y);
2656 if (link) {
2657 str_v = caml_copy_string (link->uri);
2658 ret_v = caml_alloc_small (1, Uuri);
2659 Field (ret_v, 0) = str_v;
2661 else {
2662 fz_stext_block *block;
2663 fz_point p = { .x = x, .y = y };
2665 ensuretext (page);
2667 for (block = page->text->first_block; block; block = block->next) {
2668 fz_stext_line *line;
2670 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2671 continue;
2673 if (!fz_is_point_inside_rect (p, block->bbox)) {
2674 continue;
2677 for (line = block->u.t.first_line; line; line = line->next) {
2678 fz_stext_char *ch;
2680 if (!fz_is_point_inside_rect (p, line->bbox)) {
2681 continue;
2684 for (ch = line->first_char; ch; ch = ch->next) {
2685 if (!fz_is_point_inside_quad (p, ch->quad)) {
2686 const char *n2 = fz_font_name (state.ctx, ch->font);
2687 FT_FaceRec *face = fz_font_ft_face (state.ctx,
2688 ch->font);
2690 if (!n2) {
2691 n2 = "<unknown font>";
2694 if (face && face->family_name) {
2695 char *s;
2696 char *n1 = face->family_name;
2697 size_t l1 = strlen (n1);
2698 size_t l2 = strlen (n2);
2700 if (l1 != l2 || memcmp (n1, n2, l1)) {
2701 s = malloc (l1 + l2 + 2);
2702 if (s) {
2703 memcpy (s, n2, l2);
2704 s[l2] = '=';
2705 memcpy (s + l2 + 1, n1, l1 + 1);
2706 str_v = caml_copy_string (s);
2707 free (s);
2711 if (str_v == Val_unit) {
2712 str_v = caml_copy_string (n2);
2714 ret_v = caml_alloc_small (1, Utext);
2715 Field (ret_v, 0) = str_v;
2716 goto unlock;
2722 unlock:
2723 unlock (__func__);
2725 done:
2726 CAMLreturn (ret_v);
2729 ML0 (clearmark (value ptr_v))
2731 CAMLparam1 (ptr_v);
2732 struct page *page;
2734 if (trylock (__func__)) {
2735 goto done;
2738 page = parse_pointer (__func__, String_val (ptr_v));
2739 page->fmark = NULL;
2740 page->lmark = NULL;
2742 unlock (__func__);
2743 done:
2744 CAMLreturn0;
2747 static int uninteresting (int c)
2749 return isspace (c) || ispunct (c);
2752 ML (markunder (value ptr_v, value x_v, value y_v, value mark_v))
2754 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
2755 CAMLlocal1 (ret_v);
2756 struct page *page;
2757 fz_stext_line *line;
2758 fz_stext_block *block;
2759 struct pagedim *pdim;
2760 int mark = Int_val (mark_v);
2761 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2763 ret_v = Val_bool (0);
2764 if (trylock (__func__)) {
2765 goto done;
2768 page = parse_pointer (__func__, String_val (ptr_v));
2769 pdim = &state.pagedims[page->pdimno];
2771 ensuretext (page);
2773 if (mark == MarkPage) {
2774 page->fmark = page->text->first_block->u.t.first_line->first_char;
2775 page->lmark = page->text->last_block->u.t.last_line->last_char;
2776 ret_v = Val_bool (1);
2777 goto unlock;
2780 p.x += pdim->bounds.x0;
2781 p.y += pdim->bounds.y0;
2783 for (block = page->text->first_block; block; block = block->next) {
2784 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2785 continue;
2787 if (!fz_is_point_inside_rect (p, block->bbox)) {
2788 continue;
2791 if (mark == MarkBlock) {
2792 page->fmark = block->u.t.first_line->first_char;
2793 page->lmark = block->u.t.last_line->last_char;
2794 ret_v = Val_bool (1);
2795 goto unlock;
2798 for (line = block->u.t.first_line; line; line = line->next) {
2799 fz_stext_char *ch;
2801 if (!fz_is_point_inside_rect (p, line->bbox)) {
2802 continue;
2805 if (mark == MarkLine) {
2806 page->fmark = line->first_char;
2807 page->lmark = line->last_char;
2808 ret_v = Val_bool (1);
2809 goto unlock;
2812 for (ch = line->first_char; ch; ch = ch->next) {
2813 fz_stext_char *ch2, *first = NULL, *last = NULL;
2815 if (fz_is_point_inside_quad (p, ch->quad)) {
2816 for (ch2 = line->first_char; ch2 != ch; ch2 = ch2->next) {
2817 if (uninteresting (ch2->c)) {
2818 first = NULL;
2820 else {
2821 if (!first) {
2822 first = ch2;
2826 for (ch2 = ch; ch2; ch2 = ch2->next) {
2827 if (uninteresting (ch2->c)) {
2828 break;
2830 last = ch2;
2833 page->fmark = first;
2834 page->lmark = last;
2835 ret_v = Val_bool (1);
2836 goto unlock;
2841 unlock:
2842 if (!Bool_val (ret_v)) {
2843 page->fmark = NULL;
2844 page->lmark = NULL;
2846 unlock (__func__);
2848 done:
2849 CAMLreturn (ret_v);
2852 ML (rectofblock (value ptr_v, value x_v, value y_v))
2854 CAMLparam3 (ptr_v, x_v, y_v);
2855 CAMLlocal2 (ret_v, res_v);
2856 fz_rect *b = NULL;
2857 struct page *page;
2858 struct pagedim *pdim;
2859 fz_stext_block *block;
2860 fz_point p = { .x = Int_val (x_v), .y = Int_val (y_v) };
2862 ret_v = Val_int (0);
2863 if (trylock (__func__)) {
2864 goto done;
2867 page = parse_pointer (__func__, String_val (ptr_v));
2868 pdim = &state.pagedims[page->pdimno];
2869 p.x += pdim->bounds.x0;
2870 p.y += pdim->bounds.y0;
2872 ensuretext (page);
2874 for (block = page->text->first_block; block; block = block->next) {
2875 switch (block->type) {
2876 case FZ_STEXT_BLOCK_TEXT:
2877 b = &block->bbox;
2878 break;
2880 case FZ_STEXT_BLOCK_IMAGE:
2881 b = &block->bbox;
2882 break;
2884 default:
2885 continue;
2888 if (fz_is_point_inside_rect (p, *b)) {
2889 break;
2891 b = NULL;
2893 if (b) {
2894 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
2895 ret_v = caml_alloc_small (1, 1);
2896 Store_double_field (res_v, 0, (double) b->x0);
2897 Store_double_field (res_v, 1, (double) b->x1);
2898 Store_double_field (res_v, 2, (double) b->y0);
2899 Store_double_field (res_v, 3, (double) b->y1);
2900 Field (ret_v, 0) = res_v;
2902 unlock (__func__);
2904 done:
2905 CAMLreturn (ret_v);
2908 ML0 (seltext (value ptr_v, value rect_v))
2910 CAMLparam2 (ptr_v, rect_v);
2911 struct page *page;
2912 struct pagedim *pdim;
2913 int x0, x1, y0, y1;
2914 fz_stext_char *ch;
2915 fz_stext_line *line;
2916 fz_stext_block *block;
2917 fz_stext_char *fc, *lc;
2919 if (trylock (__func__)) {
2920 goto done;
2923 page = parse_pointer (__func__, String_val (ptr_v));
2924 ensuretext (page);
2926 pdim = &state.pagedims[page->pdimno];
2927 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
2928 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
2929 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
2930 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
2932 if (y0 > y1) {
2933 int t = y0;
2934 y0 = y1;
2935 y1 = t;
2936 x0 = x1;
2937 x1 = t;
2940 fc = page->fmark;
2941 lc = page->lmark;
2943 for (block = page->text->first_block; block; block = block->next) {
2944 if (block->type != FZ_STEXT_BLOCK_TEXT) {
2945 continue;
2948 for (line = block->u.t.first_line; line; line = line->next) {
2949 for (ch = line->first_char; ch; ch = ch->next) {
2950 fz_point p0 = { .x = x0, .y = y0 }, p1 = { .x = x1, .y = y1 };
2951 if (fz_is_point_inside_quad (p0, ch->quad)) {
2952 fc = ch;
2954 if (fz_is_point_inside_quad (p1, ch->quad)) {
2955 lc = ch;
2960 if (x1 < x0 && fc == lc) {
2961 fz_stext_char *t;
2963 t = fc;
2964 fc = lc;
2965 lc = t;
2968 page->fmark = fc;
2969 page->lmark = lc;
2971 unlock (__func__);
2973 done:
2974 CAMLreturn0;
2977 static int pipechar (FILE *f, fz_stext_char *ch)
2979 char buf[4];
2980 int len;
2981 size_t ret;
2983 len = fz_runetochar (buf, ch->c);
2984 ret = fwrite (buf, len, 1, f);
2985 if (ret != 1) {
2986 printd ("emsg failed to fwrite %d bytes ret=%zu: %d(%s)",
2987 len, ret, errno, strerror (errno));
2988 return -1;
2990 return 0;
2993 ML (spawn (value command_v, value fds_v))
2995 CAMLparam2 (command_v, fds_v);
2996 CAMLlocal2 (l_v, tup_v);
2997 int ret, ret1;
2998 pid_t pid = (pid_t) -1;
2999 char *msg = NULL;
3000 value earg_v = Nothing;
3001 posix_spawnattr_t attr;
3002 posix_spawn_file_actions_t fa;
3003 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
3005 argv[2] = &Byte (command_v, 0);
3006 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
3007 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
3010 if ((ret = posix_spawnattr_init (&attr)) != 0) {
3011 msg = "posix_spawnattr_init";
3012 goto fail1;
3015 #ifdef POSIX_SPAWN_USEVFORK
3016 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
3017 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
3018 goto fail;
3020 #endif
3022 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
3023 int fd1, fd2;
3025 tup_v = Field (l_v, 0);
3026 fd1 = Int_val (Field (tup_v, 0));
3027 fd2 = Int_val (Field (tup_v, 1));
3028 if (fd2 < 0) {
3029 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
3030 msg = "posix_spawn_file_actions_addclose";
3031 earg_v = tup_v;
3032 goto fail;
3035 else {
3036 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
3037 msg = "posix_spawn_file_actions_adddup2";
3038 earg_v = tup_v;
3039 goto fail;
3044 extern char **environ;
3045 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3046 msg = "posix_spawn";
3047 goto fail;
3050 fail:
3051 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3052 printd ("emsg posix_spawnattr_destroy: %d(%s)", ret1, strerror (ret1));
3055 fail1:
3056 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3057 printd ("emsg posix_spawn_file_actions_destroy: %d(%s)",
3058 ret1, strerror (ret1));
3061 if (msg) {
3062 unix_error (ret, msg, earg_v);
3065 CAMLreturn (Val_int (pid));
3068 ML (hassel (value ptr_v))
3070 CAMLparam1 (ptr_v);
3071 CAMLlocal1 (ret_v);
3072 struct page *page;
3074 ret_v = Val_bool (0);
3075 if (trylock (__func__)) {
3076 goto done;
3079 page = parse_pointer (__func__, String_val (ptr_v));
3080 ret_v = Val_bool (page->fmark && page->lmark);
3081 unlock (__func__);
3082 done:
3083 CAMLreturn (ret_v);
3086 ML0 (copysel (value fd_v, value ptr_v))
3088 CAMLparam2 (fd_v, ptr_v);
3089 FILE *f;
3090 int seen = 0;
3091 struct page *page;
3092 fz_stext_line *line;
3093 fz_stext_block *block;
3094 int fd = Int_val (fd_v);
3096 if (trylock (__func__)) {
3097 goto done;
3100 page = parse_pointer (__func__, String_val (ptr_v));
3102 if (!page->fmark || !page->lmark) {
3103 printd ("emsg nothing to copy on page %d", page->pageno);
3104 goto unlock;
3107 f = fdopen (fd, "w");
3108 if (!f) {
3109 printd ("emsg failed to fdopen sel pipe (from fd %d): %d(%s)",
3110 fd, errno, strerror (errno));
3111 f = stdout;
3114 for (block = page->text->first_block; block; block = block->next) {
3115 if (block->type != FZ_STEXT_BLOCK_TEXT) {
3116 continue;
3119 for (line = block->u.t.first_line; line; line = line->next) {
3120 fz_stext_char *ch;
3121 for (ch = line->first_char; ch; ch = ch->next) {
3122 if (seen || ch == page->fmark) {
3123 do {
3124 if (pipechar (f, ch)) {
3125 goto close;
3127 if (ch == page->lmark) {
3128 goto close;
3130 } while ((ch = ch->next));
3131 seen = 1;
3132 break;
3135 if (seen) {
3136 fputc ('\n', f);
3140 close:
3141 if (f != stdout) {
3142 int ret = fclose (f);
3143 fd = -1;
3144 if (ret == -1) {
3145 if (errno != ECHILD) {
3146 printd ("emsg failed to close sel pipe: %d(%s)",
3147 errno, strerror (errno));
3151 unlock:
3152 unlock (__func__);
3154 done:
3155 if (fd >= 0) {
3156 if (close (fd)) {
3157 printd ("emsg failed to close sel pipe: %d(%s)",
3158 errno, strerror (errno));
3161 CAMLreturn0;
3164 ML (getpdimrect (value pagedimno_v))
3166 CAMLparam1 (pagedimno_v);
3167 CAMLlocal1 (ret_v);
3168 int pagedimno = Int_val (pagedimno_v);
3169 fz_rect box;
3171 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3172 if (trylock (__func__)) {
3173 box = fz_empty_rect;
3175 else {
3176 box = state.pagedims[pagedimno].mediabox;
3177 unlock (__func__);
3180 Store_double_field (ret_v, 0, (double) box.x0);
3181 Store_double_field (ret_v, 1, (double) box.x1);
3182 Store_double_field (ret_v, 2, (double) box.y0);
3183 Store_double_field (ret_v, 3, (double) box.y1);
3185 CAMLreturn (ret_v);
3188 ML (zoom_for_height (value winw_v, value winh_v, value dw_v, value cols_v))
3190 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3191 CAMLlocal1 (ret_v);
3192 int i;
3193 float zoom = -1.;
3194 float maxh = 0.0;
3195 struct pagedim *p;
3196 float winw = Int_val (winw_v);
3197 float winh = Int_val (winh_v);
3198 float dw = Int_val (dw_v);
3199 float cols = Int_val (cols_v);
3200 float pw = 1.0, ph = 1.0;
3202 if (trylock (__func__)) {
3203 goto done;
3206 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3207 float w = p->pagebox.x1 / cols;
3208 float h = p->pagebox.y1;
3209 if (h > maxh) {
3210 maxh = h;
3211 ph = h;
3212 if (state.fitmodel != FitProportional) {
3213 pw = w;
3216 if ((state.fitmodel == FitProportional) && w > pw) {
3217 pw = w;
3221 zoom = (((winh / ph) * pw) + dw) / winw;
3222 unlock (__func__);
3223 done:
3224 ret_v = caml_copy_double ((double) zoom);
3225 CAMLreturn (ret_v);
3228 ML (getmaxw (value unit_v))
3230 CAMLparam1 (unit_v);
3231 CAMLlocal1 (ret_v);
3232 int i;
3233 float maxw = -1.;
3234 struct pagedim *p;
3236 if (trylock (__func__)) {
3237 goto done;
3240 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3241 maxw = fz_max (maxw, p->pagebox.x1);
3244 unlock (__func__);
3245 done:
3246 ret_v = caml_copy_double ((double) maxw);
3247 CAMLreturn (ret_v);
3250 ML (draw_string (value pt_v, value x_v, value y_v, value string_v))
3252 CAMLparam4 (pt_v, x_v, y_v, string_v);
3253 CAMLlocal1 (ret_v);
3254 float w = draw_string (state.face,
3255 Int_val (pt_v), Int_val (x_v), Int_val (y_v),
3256 String_val (string_v));
3257 ret_v = caml_copy_double (w);
3258 CAMLreturn (ret_v);
3261 ML (measure_string (value pt_v, value string_v))
3263 CAMLparam2 (pt_v, string_v);
3264 CAMLlocal1 (ret_v);
3266 ret_v = caml_copy_double (
3267 measure_string (state.face, Int_val (pt_v), String_val (string_v))
3269 CAMLreturn (ret_v);
3272 ML (getpagebox (value ptr_v))
3274 CAMLparam1 (ptr_v);
3275 CAMLlocal1 (ret_v);
3276 fz_rect rect;
3277 fz_irect bbox;
3278 fz_device *dev;
3279 struct page *page = parse_pointer (__func__, String_val (ptr_v));
3281 ret_v = caml_alloc_tuple (4);
3282 dev = fz_new_bbox_device (state.ctx, &rect);
3284 fz_run_page (state.ctx, page->fzpage, dev, pagectm (page), NULL);
3286 fz_close_device (state.ctx, dev);
3287 fz_drop_device (state.ctx, dev);
3288 bbox = fz_round_rect (rect);
3289 Field (ret_v, 0) = Val_int (bbox.x0);
3290 Field (ret_v, 1) = Val_int (bbox.y0);
3291 Field (ret_v, 2) = Val_int (bbox.x1);
3292 Field (ret_v, 3) = Val_int (bbox.y1);
3294 CAMLreturn (ret_v);
3297 ML0 (setaalevel (value level_v))
3299 CAMLparam1 (level_v);
3301 state.aalevel = Int_val (level_v);
3302 CAMLreturn0;
3305 ML0 (setpapercolor (value rgba_v))
3307 CAMLparam1 (rgba_v);
3309 state.papercolor[0] = (float) Double_val (Field (rgba_v, 0));
3310 state.papercolor[1] = (float) Double_val (Field (rgba_v, 1));
3311 state.papercolor[2] = (float) Double_val (Field (rgba_v, 2));
3312 state.papercolor[3] = (float) Double_val (Field (rgba_v, 3));
3313 CAMLreturn0;
3316 value ml_keysymtoutf8 (value keysym_v);
3317 #ifndef MACOS
3318 value ml_keysymtoutf8 (value keysym_v)
3320 CAMLparam1 (keysym_v);
3321 CAMLlocal1 (str_v);
3322 unsigned short keysym = (unsigned short) Int_val (keysym_v);
3323 Rune rune;
3324 extern long keysym2ucs (unsigned short);
3325 int len;
3326 char buf[5];
3328 rune = (Rune) keysym2ucs (keysym);
3329 len = fz_runetochar (buf, rune);
3330 buf[len] = 0;
3331 str_v = caml_copy_string (buf);
3332 CAMLreturn (str_v);
3334 #else
3335 value ml_keysymtoutf8 (value keysym_v)
3337 CAMLparam1 (keysym_v);
3338 CAMLlocal1 (str_v);
3339 long ucs = Long_val (keysym_v);
3340 int len;
3341 char buf[5];
3343 len = fz_runetochar (buf, (int) ucs);
3344 buf[len] = 0;
3345 str_v = caml_copy_string (buf);
3346 CAMLreturn (str_v);
3348 #endif
3350 ML (unproject (value ptr_v, value x_v, value y_v))
3352 CAMLparam3 (ptr_v, x_v, y_v);
3353 CAMLlocal2 (ret_v, tup_v);
3354 struct page *page;
3355 int x = Int_val (x_v), y = Int_val (y_v);
3356 struct pagedim *pdim;
3357 fz_point p;
3359 page = parse_pointer (__func__, String_val (ptr_v));
3360 pdim = &state.pagedims[page->pdimno];
3362 ret_v = Val_int (0);
3363 if (trylock (__func__)) {
3364 goto done;
3367 p.x = x + pdim->bounds.x0;
3368 p.y = y + pdim->bounds.y0;
3370 p = fz_transform_point (p, fz_invert_matrix (fz_concat (pdim->tctm,
3371 pdim->ctm)));
3373 tup_v = caml_alloc_tuple (2);
3374 ret_v = caml_alloc_small (1, 1);
3375 Field (tup_v, 0) = Val_int (p.x);
3376 Field (tup_v, 1) = Val_int (p.y);
3377 Field (ret_v, 0) = tup_v;
3379 unlock (__func__);
3380 done:
3381 CAMLreturn (ret_v);
3384 ML (project (value ptr_v, value pageno_v, value pdimno_v, value x_v, value y_v))
3386 CAMLparam5 (ptr_v, pageno_v, pdimno_v, x_v, y_v);
3387 CAMLlocal1 (ret_v);
3388 struct page *page;
3389 const char *s = String_val (ptr_v);
3390 int pageno = Int_val (pageno_v);
3391 int pdimno = Int_val (pdimno_v);
3392 float x = (float) Double_val (x_v), y = (float) Double_val (y_v);
3393 struct pagedim *pdim;
3394 fz_point p;
3395 fz_matrix ctm;
3397 ret_v = Val_int (0);
3398 lock (__func__);
3400 if (!*s) {
3401 page = loadpage (pageno, pdimno);
3403 else {
3404 page = parse_pointer (__func__, String_val (ptr_v));
3406 pdim = &state.pagedims[pdimno];
3408 if (pdf_specifics (state.ctx, state.doc)) {
3409 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
3410 ctm = state.pagedims[page->pdimno].tctm;
3412 else {
3413 ctm = fz_identity;
3416 p.x = x + pdim->bounds.x0;
3417 p.y = y + pdim->bounds.y0;
3419 ctm = fz_concat (pdim->tctm, pdim->ctm);
3420 p = fz_transform_point (p, ctm);
3422 ret_v = caml_alloc_tuple (2);
3423 Field (ret_v, 0) = caml_copy_double ((double) p.x);
3424 Field (ret_v, 1) = caml_copy_double ((double) p.y);
3426 if (!*s) {
3427 freepage (page);
3429 unlock (__func__);
3430 CAMLreturn (ret_v);
3433 ML0 (addannot (value ptr_v, value x_v, value y_v, value contents_v))
3435 CAMLparam4 (ptr_v, x_v, y_v, contents_v);
3436 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3438 if (pdf) {
3439 pdf_annot *annot;
3440 struct page *page;
3441 fz_rect r;
3443 page = parse_pointer (__func__, String_val (ptr_v));
3444 annot = pdf_create_annot (state.ctx,
3445 pdf_page_from_fz_page (state.ctx,
3446 page->fzpage),
3447 PDF_ANNOT_TEXT);
3448 r.x0 = Int_val (x_v) - 10;
3449 r.y0 = Int_val (y_v) - 10;
3450 r.x1 = r.x0 + 20;
3451 r.y1 = r.y0 + 20;
3452 pdf_set_annot_contents (state.ctx, annot, String_val (contents_v));
3453 pdf_set_annot_rect (state.ctx, annot, r);
3455 state.dirty = 1;
3457 CAMLreturn0;
3460 ML0 (delannot (value ptr_v, value n_v))
3462 CAMLparam2 (ptr_v, n_v);
3463 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3465 if (pdf) {
3466 struct page *page;
3467 struct slink *slink;
3469 page = parse_pointer (__func__, String_val (ptr_v));
3470 slink = &page->slinks[Int_val (n_v)];
3471 pdf_delete_annot (state.ctx,
3472 pdf_page_from_fz_page (state.ctx, page->fzpage),
3473 (pdf_annot *) slink->u.annot);
3474 state.dirty = 1;
3476 CAMLreturn0;
3479 ML0 (modannot (value ptr_v, value n_v, value str_v))
3481 CAMLparam3 (ptr_v, n_v, str_v);
3482 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3484 if (pdf) {
3485 struct page *page;
3486 struct slink *slink;
3488 page = parse_pointer (__func__, String_val (ptr_v));
3489 slink = &page->slinks[Int_val (n_v)];
3490 pdf_set_annot_contents (state.ctx, (pdf_annot *) slink->u.annot,
3491 String_val (str_v));
3492 state.dirty = 1;
3494 CAMLreturn0;
3497 ML (hasunsavedchanges (void))
3499 return Val_bool (state.dirty);
3502 ML0 (savedoc (value path_v))
3504 CAMLparam1 (path_v);
3505 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
3507 if (pdf) {
3508 pdf_save_document (state.ctx, pdf, String_val (path_v), NULL);
3510 CAMLreturn0;
3513 static void makestippletex (void)
3515 const char pixels[] = "\xff\xff\0\0";
3516 glGenTextures (1, &state.stid);
3517 glBindTexture (GL_TEXTURE_1D, state.stid);
3518 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
3519 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3520 glTexImage1D (
3521 GL_TEXTURE_1D,
3523 GL_ALPHA,
3526 GL_ALPHA,
3527 GL_UNSIGNED_BYTE,
3528 pixels
3532 ML (fz_version (void))
3534 return caml_copy_string (FZ_VERSION);
3537 ML (llpp_version (void))
3539 extern char llpp_version[];
3540 return caml_copy_string (llpp_version);
3543 static void diag_callback (void *user, const char *message)
3545 if (pthread_equal (pthread_self (), state.thread)) {
3546 printd ("emsg %s %s", (char *) user, message);
3548 else {
3549 puts (message);
3553 static fz_font *lsff (fz_context *ctx,int UNUSED_ATTR script,
3554 int UNUSED_ATTR language, int UNUSED_ATTR serif,
3555 int UNUSED_ATTR bold, int UNUSED_ATTR italic)
3557 static fz_font *font;
3558 static int done;
3560 if (!done) {
3561 char *path = getenv ("LLPP_FALLBACK_FONT");
3562 if (path) {
3563 font = fz_new_font_from_file (ctx, NULL, path, 0, 1);
3565 done = 1;
3567 return font;
3570 ML0 (setdcf (value path_v))
3572 free (state.dcf);
3573 state.dcf = NULL;
3574 const char *p = String_val (path_v);
3575 if (*p) {
3576 size_t len = caml_string_length (path_v);
3577 state.dcf = malloc (len + 1);
3578 if (!state.dcf) {
3579 err (1, errno, "malloc dimpath %zu", len + 1);
3581 memcpy (state.dcf, p, len);
3582 state.dcf[len] = 0;
3586 ML (init (value csock_v, value params_v))
3588 CAMLparam2 (csock_v, params_v);
3589 CAMLlocal2 (trim_v, fuzz_v);
3590 int ret, texcount, colorspace, mustoresize, redirstderr;
3591 const char *fontpath;
3592 const char *ext = TEXT_TYPE == GL_TEXTURE_2D
3593 ? "texture_non_power_of_two"
3594 : "texture_rectangle";
3596 if (!strstr ((const char *) glGetString (GL_EXTENSIONS), ext)) {
3597 errx (1, "OpenGL does not support '%s' extension", ext);
3599 state.csock = Int_val (csock_v);
3600 state.rotate = Int_val (Field (params_v, 0));
3601 state.fitmodel = Int_val (Field (params_v, 1));
3602 trim_v = Field (params_v, 2);
3603 texcount = Int_val (Field (params_v, 3));
3604 state.sliceheight = Int_val (Field (params_v, 4));
3605 mustoresize = Int_val (Field (params_v, 5));
3606 colorspace = Int_val (Field (params_v, 6));
3607 fontpath = String_val (Field (params_v, 7));
3608 redirstderr = Bool_val (Field (params_v, 8));
3610 if (redirstderr) {
3611 if (pipe (state.pfds)) {
3612 err (1, errno, "pipe");
3614 for (int ntries = 0; ntries < 1737; ++ntries) {
3615 if (-1 == dup2 (state.pfds[1], 2)) {
3616 if (EINTR == errno) {
3617 continue;
3619 err (1, errno, "dup2");
3621 break;
3623 } else {
3624 state.pfds[0] = 0;
3625 state.pfds[1] = 0;
3628 #ifdef MACOS
3629 state.utf8cs = 1;
3630 #else
3631 /* http://www.cl.cam.ac.uk/~mgk25/unicode.html */
3632 if (setlocale (LC_CTYPE, "")) {
3633 const char *cset = nl_langinfo (CODESET);
3634 state.utf8cs = !strcmp (cset, "UTF-8");
3636 else {
3637 err (1, errno, "setlocale");
3639 #endif
3641 state.ctx = fz_new_context (NULL, NULL, mustoresize);
3642 fz_register_document_handlers (state.ctx);
3643 if (redirstderr) {
3644 fz_set_error_callback (state.ctx, diag_callback, "[e]");
3645 fz_set_warning_callback (state.ctx, diag_callback, "[w]");
3647 fz_install_load_system_font_funcs (state.ctx, NULL, NULL, lsff);
3649 state.trimmargins = Bool_val (Field (trim_v, 0));
3650 fuzz_v = Field (trim_v, 1);
3651 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
3652 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
3653 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
3654 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
3656 set_tex_params (colorspace);
3658 if (*fontpath) {
3659 state.face = load_font (fontpath);
3661 else {
3662 int len;
3663 const unsigned char *data;
3665 data = pdf_lookup_substitute_font (state.ctx, 0, 0, 0, 0, &len);
3666 state.face = load_builtin_font (data, len);
3668 if (!state.face) {
3669 _exit (1);
3672 realloctexts (texcount);
3673 makestippletex ();
3675 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
3676 if (ret) {
3677 errx (1, "pthread_create: %d(%s)", ret, strerror (ret));
3680 CAMLreturn (Val_int (state.pfds[0]));