Add U+2192
[llpp.git] / link.c
blob19703dc2b69413b85b1a64c8393ba6d224b4e22e
1 /* lots of code c&p-ed directly from mupdf */
2 #ifdef __clang__
3 #include "diag.h"
4 #endif
6 #define CAML_NAME_SPACE
7 #define FIXME 0
9 extern char **environ;
11 #include <errno.h>
12 #include <stdio.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <signal.h>
18 #include <math.h>
19 #include <wchar.h>
20 #include <locale.h>
21 #include <langinfo.h>
23 #include <unistd.h>
24 #include <pthread.h>
25 #include <sys/uio.h>
26 #include <sys/time.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <sys/types.h>
30 #include <sys/ioctl.h>
31 #include <sys/utsname.h>
33 #include <spawn.h>
35 #include <regex.h>
36 #include <stdarg.h>
37 #include <limits.h>
38 #include <inttypes.h>
40 #ifdef __COCOA__
41 #include <CoreFoundation/CoreFoundation.h>
42 #endif
44 #ifdef __APPLE__
45 #include <OpenGL/gl.h>
46 #else
47 #include <GL/gl.h>
48 #endif
50 #pragma GCC diagnostic push
51 #ifdef __clang__
52 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
53 #endif
54 #pragma GCC diagnostic ignored "-Wpedantic"
55 #include <caml/fail.h>
56 #include <caml/alloc.h>
57 #include <caml/memory.h>
58 #include <caml/unixsupport.h>
60 #if __GNUC__ < 5 && !defined __clang__
61 /* At least gcc (Gentoo 4.9.3 p1.0, pie-0.6.2) 4.9.3 emits erroneous
62 clobbered diagnostics */
63 #pragma GCC diagnostic ignored "-Wclobbered"
64 #endif
66 #include <mupdf/fitz.h>
67 #include <mupdf/pdf.h>
69 #include <ft2build.h>
70 #include FT_FREETYPE_H
71 #pragma GCC diagnostic pop
73 #define PIGGYBACK
74 #define CACHE_PAGEREFS
76 #if defined __GNUC__
77 #define NORETURN_ATTR __attribute__ ((noreturn))
78 #define UNUSED_ATTR __attribute__ ((unused))
79 #if !defined __clang__
80 #define OPTIMIZE_ATTR(n) __attribute__ ((optimize ("O"#n)))
81 #else
82 #define OPTIMIZE_ATTR(n)
83 #endif
84 #define GCC_FMT_ATTR(a, b) __attribute__ ((format (printf, a, b)))
85 #else
86 #define NORETURN_ATTR
87 #define UNUSED_ATTR
88 #define OPTIMIZE_ATTR(n)
89 #define GCC_FMT_ATTR(a, b)
90 #endif
92 #define FMT_s "zu"
94 #define FMT_ptr PRIxPTR
95 #define SCN_ptr SCNxPTR
96 #define FMT_ptr_cast(p) ((uintptr_t) (p))
97 #define SCN_ptr_cast(p) ((uintptr_t *) (p))
99 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
100 err (int exitcode, const char *fmt, ...)
102 va_list ap;
103 int savederrno;
105 savederrno = errno;
106 va_start (ap, fmt);
107 vfprintf (stderr, fmt, ap);
108 va_end (ap);
109 fprintf (stderr, ": %s\n", strerror (savederrno));
110 fflush (stderr);
111 _exit (exitcode);
114 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
115 errx (int exitcode, const char *fmt, ...)
117 va_list ap;
119 va_start (ap, fmt);
120 vfprintf (stderr, fmt, ap);
121 va_end (ap);
122 fputc ('\n', stderr);
123 fflush (stderr);
124 _exit (exitcode);
127 #ifndef GL_TEXTURE_RECTANGLE_ARB
128 #define GL_TEXTURE_RECTANGLE_ARB 0x84F5
129 #endif
131 #ifdef USE_NPOT
132 #define TEXT_TYPE GL_TEXTURE_2D
133 #else
134 #define TEXT_TYPE GL_TEXTURE_RECTANGLE_ARB
135 #endif
137 #ifndef GL_BGRA
138 #define GL_BGRA 0x80E1
139 #endif
141 #ifndef GL_UNSIGNED_INT_8_8_8_8
142 #define GL_UNSIGNED_INT_8_8_8_8 0x8035
143 #endif
145 #ifndef GL_UNSIGNED_INT_8_8_8_8_REV
146 #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
147 #endif
149 #if 0
150 #define lprintf printf
151 #else
152 #define lprintf(...)
153 #endif
155 #define ARSERT(cond) for (;;) { \
156 if (!(cond)) { \
157 errx (1, "%s:%d " #cond, __FILE__, __LINE__); \
159 break; \
162 struct slice {
163 int h;
164 int texindex;
167 struct tile {
168 int w, h;
169 int slicecount;
170 int sliceheight;
171 struct bo *pbo;
172 fz_pixmap *pixmap;
173 struct slice slices[1];
176 struct pagedim {
177 int pageno;
178 int rotate;
179 int left;
180 int tctmready;
181 fz_irect bounds;
182 fz_rect pagebox;
183 fz_rect mediabox;
184 fz_matrix ctm, zoomctm, tctm;
187 struct slink {
188 enum { SLINK, SANNOT } tag;
189 fz_irect bbox;
190 union {
191 fz_link *link;
192 fz_annot *annot;
193 } u;
196 struct annot {
197 fz_irect bbox;
198 fz_annot *annot;
201 struct page {
202 int tgen;
203 int sgen;
204 int agen;
205 int pageno;
206 int pdimno;
207 fz_stext_page *text;
208 fz_page *fzpage;
209 fz_display_list *dlist;
210 fz_link *links;
211 int slinkcount;
212 struct slink *slinks;
213 int annotcount;
214 struct annot *annots;
215 struct mark {
216 fz_stext_char *ch;
217 } fmark, lmark;
220 enum { FitWidth, FitProportional, FitPage };
222 static struct {
223 int sliceheight;
224 struct pagedim *pagedims;
225 int pagecount;
226 int pagedimcount;
227 fz_document *doc;
228 fz_context *ctx;
229 int w, h;
231 int texindex;
232 int texcount;
233 GLuint *texids;
235 GLenum texiform;
236 GLenum texform;
237 GLenum texty;
239 fz_colorspace *colorspace;
241 struct {
242 int w, h;
243 struct slice *slice;
244 } *texowners;
246 int rotate;
247 int fitmodel;
248 int trimmargins;
249 int needoutline;
250 int gen;
251 int aalevel;
253 int trimanew;
254 fz_irect trimfuzz;
255 fz_pixmap *pig;
257 pthread_t thread;
258 int csock;
259 FT_Face face;
261 char *trimcachepath;
262 int dirty;
264 GLuint stid;
266 int bo_usable;
267 GLuint boid;
269 void (*glBindBufferARB) (GLenum, GLuint);
270 GLboolean (*glUnmapBufferARB) (GLenum);
271 void *(*glMapBufferARB) (GLenum, GLenum);
272 void (*glBufferDataARB) (GLenum, GLsizei, void *, GLenum);
273 void (*glGenBuffersARB) (GLsizei, GLuint *);
274 void (*glDeleteBuffersARB) (GLsizei, GLuint *);
276 GLfloat texcoords[8];
277 GLfloat vertices[16];
279 #ifdef CACHE_PAGEREFS
280 struct {
281 int idx;
282 int count;
283 pdf_obj **objs;
284 pdf_document *pdf;
285 } pdflut;
286 #endif
287 int utf8cs;
288 } state;
290 struct bo {
291 GLuint id;
292 void *ptr;
293 size_t size;
296 #pragma GCC diagnostic ignored "-Wdouble-promotion"
297 static void UNUSED_ATTR debug_rect (const char *cap, fz_rect r)
299 printf ("%s(rect) %.2f,%.2f,%.2f,%.2f\n", cap, r.x0, r.y0, r.x1, r.y1);
302 static void UNUSED_ATTR debug_bbox (const char *cap, fz_irect r)
304 printf ("%s(bbox) %d,%d,%d,%d\n", cap, r.x0, r.y0, r.x1, r.y1);
307 static void UNUSED_ATTR debug_matrix (const char *cap, fz_matrix m)
309 printf ("%s(matrix) %.2f,%.2f,%.2f,%.2f %.2f %.2f\n", cap,
310 m.a, m.b, m.c, m.d, m.e, m.f);
312 #pragma GCC diagnostic error "-Wdouble-promotion"
314 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
316 static void lock (const char *cap)
318 int ret = pthread_mutex_lock (&mutex);
319 if (ret) {
320 errx (1, "%s: pthread_mutex_lock: %s", cap, strerror (ret));
324 static void unlock (const char *cap)
326 int ret = pthread_mutex_unlock (&mutex);
327 if (ret) {
328 errx (1, "%s: pthread_mutex_unlock: %s", cap, strerror (ret));
332 static int trylock (const char *cap)
334 int ret = pthread_mutex_trylock (&mutex);
335 if (ret && ret != EBUSY) {
336 errx (1, "%s: pthread_mutex_trylock: %s", cap, strerror (ret));
338 return ret == EBUSY;
341 static void *parse_pointer (const char *cap, const char *s)
343 int ret;
344 void *ptr;
346 ret = sscanf (s, "%" SCN_ptr, SCN_ptr_cast (&ptr));
347 if (ret != 1) {
348 errx (1, "%s: cannot parse pointer in `%s'", cap, s);
350 return ptr;
353 static double now (void)
355 struct timeval tv;
357 if (gettimeofday (&tv, NULL)) {
358 err (1, "gettimeofday");
360 return tv.tv_sec + tv.tv_usec*1e-6;
363 static int hasdata (void)
365 int ret, avail;
366 ret = ioctl (state.csock, FIONREAD, &avail);
367 if (ret) err (1, "hasdata: FIONREAD error ret=%d", ret);
368 return avail > 0;
371 CAMLprim value ml_hasdata (value fd_v)
373 CAMLparam1 (fd_v);
374 int ret, avail;
376 ret = ioctl (Int_val (fd_v), FIONREAD, &avail);
377 if (ret) uerror ("ioctl (FIONREAD)", Nothing);
378 CAMLreturn (Val_bool (avail > 0));
381 static void readdata (int fd, void *p, int size)
383 ssize_t n;
385 again:
386 n = read (fd, p, size);
387 if (n - size) {
388 if (n < 0 && errno == EINTR) goto again;
389 if (!n) errx (1, "EOF while reading");
390 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
394 static void writedata (int fd, char *p, int size)
396 ssize_t n;
397 uint32_t size4 = size;
398 struct iovec iov[2] = {
399 { .iov_base = &size4, .iov_len = 4 },
400 { .iov_base = p, .iov_len = size }
403 again:
404 n = writev (fd, iov, 2);
405 if (n < 0 && errno == EINTR) goto again;
406 if (n - size - 4) {
407 if (!n) errx (1, "EOF while writing data");
408 err (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
412 static int readlen (int fd)
414 uint32_t u;
415 readdata (fd, &u, 4);
416 return u;
419 CAMLprim void ml_wcmd (value fd_v, value bytes_v, value len_v)
421 CAMLparam3 (fd_v, bytes_v, len_v);
422 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
423 CAMLreturn0;
426 CAMLprim value ml_rcmd (value fd_v)
428 CAMLparam1 (fd_v);
429 CAMLlocal1 (strdata_v);
430 int fd = Int_val (fd_v);
431 int len = readlen (fd);
432 strdata_v = caml_alloc_string (len);
433 readdata (fd, String_val (strdata_v), len);
434 CAMLreturn (strdata_v);
437 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
439 char fbuf[64];
440 int size = sizeof (fbuf), len;
441 va_list ap;
442 char *buf = fbuf;
444 for (;;) {
445 va_start (ap, fmt);
446 len = vsnprintf (buf, size, fmt, ap);
447 va_end (ap);
449 if (len > -1) {
450 if (len < size - 4) {
451 writedata (state.csock, buf, len);
452 break;
454 else size = len + 5;
456 else {
457 err (1, "vsnprintf for `%s' failed", fmt);
459 buf = realloc (buf == fbuf ? NULL : buf, size);
460 if (!buf) err (1, "realloc for temp buf (%d bytes) failed", size);
462 if (buf != fbuf) free (buf);
465 static void closedoc (void)
467 #ifdef CACHE_PAGEREFS
468 if (state.pdflut.objs) {
469 for (int i = 0; i < state.pdflut.count; ++i) {
470 pdf_drop_obj (state.ctx, state.pdflut.objs[i]);
472 free (state.pdflut.objs);
473 state.pdflut.objs = NULL;
474 state.pdflut.idx = 0;
476 #endif
477 if (state.doc) {
478 fz_drop_document (state.ctx, state.doc);
479 state.doc = NULL;
483 static int openxref (char *filename, char *password, int layouth)
485 for (int i = 0; i < state.texcount; ++i) {
486 state.texowners[i].w = -1;
487 state.texowners[i].slice = NULL;
490 closedoc ();
492 state.dirty = 0;
493 if (state.pagedims) {
494 free (state.pagedims);
495 state.pagedims = NULL;
497 state.pagedimcount = 0;
499 fz_set_aa_level (state.ctx, state.aalevel);
500 state.doc = fz_open_document (state.ctx, filename);
501 if (fz_needs_password (state.ctx, state.doc)) {
502 if (password && !*password) {
503 printd ("pass");
504 return 0;
506 else {
507 int ok = fz_authenticate_password (state.ctx, state.doc, password);
508 if (!ok) {
509 printd ("pass fail");
510 return 0;
514 if (layouth >= 0)
515 fz_layout_document (state.ctx, state.doc, 460, layouth, 12);
516 state.pagecount = fz_count_pages (state.ctx, state.doc);
517 return 1;
520 static void pdfinfo (void)
522 struct { char *tag; char *name; } metatbl[] = {
523 { FZ_META_INFO_TITLE, "Title" },
524 { FZ_META_INFO_AUTHOR, "Author" },
525 { FZ_META_FORMAT, "Format" },
526 { FZ_META_ENCRYPTION, "Encryption" },
527 { "info:Creator", "Creator" },
528 { "info:Producer", "Producer" },
529 { "info:CreationDate", "Creation date" },
531 int len = 0;
532 char *buf = NULL;
534 for (size_t i = 0; i < sizeof (metatbl) / sizeof (metatbl[1]); ++i) {
535 int need;
536 again:
537 need = fz_lookup_metadata (state.ctx, state.doc,
538 metatbl[i].tag, buf, len);
539 if (need > 0) {
540 if (need <= len) {
541 printd ("info %s\t%s", metatbl[i].name, buf);
543 else {
544 buf = realloc (buf, need + 1);
545 if (!buf) err (1, "pdfinfo realloc %d", need + 1);
546 len = need + 1;
547 goto again;
551 free (buf);
553 printd ("infoend");
556 static void unlinktile (struct tile *tile)
558 for (int i = 0; i < tile->slicecount; ++i) {
559 struct slice *s = &tile->slices[i];
561 if (s->texindex != -1) {
562 if (state.texowners[s->texindex].slice == s) {
563 state.texowners[s->texindex].slice = NULL;
569 static void freepage (struct page *page)
571 if (!page) return;
572 if (page->text) {
573 fz_drop_stext_page (state.ctx, page->text);
575 if (page->slinks) {
576 free (page->slinks);
578 fz_drop_display_list (state.ctx, page->dlist);
579 fz_drop_page (state.ctx, page->fzpage);
580 free (page);
583 static void freetile (struct tile *tile)
585 unlinktile (tile);
586 if (!tile->pbo) {
587 #ifndef PIGGYBACK
588 fz_drop_pixmap (state.ctx, tile->pixmap);
589 #else
590 if (state.pig) {
591 fz_drop_pixmap (state.ctx, state.pig);
593 state.pig = tile->pixmap;
594 #endif
596 else {
597 free (tile->pbo);
598 fz_drop_pixmap (state.ctx, tile->pixmap);
600 free (tile);
603 #ifdef __ALTIVEC__
604 #include <stdint.h>
605 #include <altivec.h>
607 static int cacheline32bytes;
609 static void __attribute__ ((constructor)) clcheck (void)
611 char **envp = environ;
612 unsigned long *auxv;
614 while (*envp++);
616 for (auxv = (unsigned long *) envp; *auxv != 0; auxv += 2) {
617 if (*auxv == 19) {
618 cacheline32bytes = auxv[1] == 32;
619 return;
624 static void OPTIMIZE_ATTR (3) clearpixmap (fz_pixmap *pixmap)
626 size_t size = pixmap->w * pixmap->h * pixmap->n;
627 if (cacheline32bytes && size > 32) {
628 intptr_t a1, a2, diff;
629 size_t sizea, i;
630 vector unsigned char v = vec_splat_u8 (-1);
631 vector unsigned char *p;
633 a1 = a2 = (intptr_t) pixmap->samples;
634 a2 = (a1 + 31) & ~31;
635 diff = a2 - a1;
636 sizea = size - diff;
637 p = (void *) a2;
639 while (a1 != a2) *(char *) a1++ = 0xff;
640 for (i = 0; i < (sizea & ~31); i += 32) {
641 __asm volatile ("dcbz %0, %1"::"b"(a2),"r"(i));
642 vec_st (v, i, p);
643 vec_st (v, i + 16, p);
645 while (i < sizea) *((char *) a1 + i++) = 0xff;
647 else fz_clear_pixmap_with_value (state.ctx, pixmap, 0xff);
649 #else
650 #define clearpixmap(p) fz_clear_pixmap_with_value (state.ctx, p, 0xff)
651 #endif
653 static void trimctm (pdf_page *page, int pindex)
655 fz_matrix ctm;
656 struct pagedim *pdim = &state.pagedims[pindex];
658 if (!page) return;
659 if (!pdim->tctmready) {
660 fz_rect realbox, mediabox;
661 fz_matrix rm, sm, tm, im, ctm1, page_ctm;
663 fz_rotate (&rm, -pdim->rotate);
664 fz_scale (&sm, 1, -1);
665 fz_concat (&ctm, &rm, &sm);
666 realbox = pdim->mediabox;
667 fz_transform_rect (&realbox, &ctm);
668 fz_translate (&tm, -realbox.x0, -realbox.y0);
669 fz_concat (&ctm1, &ctm, &tm);
670 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
671 fz_invert_matrix (&im, &page_ctm);
672 fz_concat (&ctm, &im, &ctm1);
673 pdim->tctm = ctm;
674 pdim->tctmready = 1;
678 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
680 fz_matrix ctm, tm;
681 ptrdiff_t pdimno = pdim - state.pagedims;
683 ARSERT (pdim - state.pagedims < INT_MAX);
684 if (pdf_specifics (state.ctx, state.doc)) {
685 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
686 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
688 else {
689 fz_translate (&tm, -pdim->mediabox.x0, -pdim->mediabox.y0);
690 fz_concat (&ctm, &tm, &pdim->ctm);
692 return ctm;
695 static fz_matrix pagectm (struct page *page)
697 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
700 static void *loadpage (int pageno, int pindex)
702 fz_device *dev;
703 struct page *page;
705 page = calloc (sizeof (struct page), 1);
706 if (!page) {
707 err (1, "calloc page %d", pageno);
710 page->dlist = fz_new_display_list (state.ctx, NULL);
711 dev = fz_new_list_device (state.ctx, page->dlist);
712 fz_try (state.ctx) {
713 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
714 fz_run_page (state.ctx, page->fzpage, dev,
715 &fz_identity, NULL);
717 fz_catch (state.ctx) {
718 page->fzpage = NULL;
720 fz_close_device (state.ctx, dev);
721 fz_drop_device (state.ctx, dev);
723 page->pdimno = pindex;
724 page->pageno = pageno;
725 page->sgen = state.gen;
726 page->agen = state.gen;
727 page->tgen = state.gen;
728 return page;
731 static struct tile *alloctile (int h)
733 int slicecount;
734 size_t tilesize;
735 struct tile *tile;
737 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
738 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
739 tile = calloc (tilesize, 1);
740 if (!tile) {
741 err (1, "cannot allocate tile (%" FMT_s " bytes)", tilesize);
743 for (int i = 0; i < slicecount; ++i) {
744 int sh = fz_mini (h, state.sliceheight);
745 tile->slices[i].h = sh;
746 tile->slices[i].texindex = -1;
747 h -= sh;
749 tile->slicecount = slicecount;
750 tile->sliceheight = state.sliceheight;
751 return tile;
754 static struct tile *rendertile (struct page *page, int x, int y, int w, int h,
755 struct bo *pbo)
757 fz_rect rect;
758 fz_irect bbox;
759 fz_matrix ctm;
760 fz_device *dev;
761 struct tile *tile;
762 struct pagedim *pdim;
764 tile = alloctile (h);
765 pdim = &state.pagedims[page->pdimno];
767 bbox = pdim->bounds;
768 bbox.x0 += x;
769 bbox.y0 += y;
770 bbox.x1 = bbox.x0 + w;
771 bbox.y1 = bbox.y0 + h;
773 if (state.pig) {
774 if (state.pig->w == w
775 && state.pig->h == h
776 && state.pig->colorspace == state.colorspace) {
777 tile->pixmap = state.pig;
778 tile->pixmap->x = bbox.x0;
779 tile->pixmap->y = bbox.y0;
781 else {
782 fz_drop_pixmap (state.ctx, state.pig);
784 state.pig = NULL;
786 if (!tile->pixmap) {
787 if (pbo) {
788 tile->pixmap =
789 fz_new_pixmap_with_bbox_and_data (state.ctx, state.colorspace,
790 &bbox, NULL, 1, pbo->ptr);
791 tile->pbo = pbo;
793 else {
794 tile->pixmap =
795 fz_new_pixmap_with_bbox (state.ctx, state.colorspace, &bbox,
796 NULL, 1);
800 tile->w = w;
801 tile->h = h;
802 clearpixmap (tile->pixmap);
804 dev = fz_new_draw_device (state.ctx, NULL, tile->pixmap);
805 ctm = pagectm (page);
806 fz_rect_from_irect (&rect, &bbox);
807 fz_run_display_list (state.ctx, page->dlist, dev, &ctm, &rect, NULL);
808 fz_close_device (state.ctx, dev);
809 fz_drop_device (state.ctx, dev);
811 return tile;
814 #ifdef CACHE_PAGEREFS
815 /* modified mupdf/source/pdf/pdf-page.c:pdf_lookup_page_loc_imp
816 thanks to Robin Watts */
817 static void
818 pdf_collect_pages(pdf_document *doc, pdf_obj *node)
820 fz_context *ctx = state.ctx; /* doc->ctx; */
821 pdf_obj *kids;
822 int len;
824 if (state.pdflut.idx == state.pagecount) return;
826 kids = pdf_dict_gets (ctx, node, "Kids");
827 len = pdf_array_len (ctx, kids);
829 if (len == 0)
830 fz_throw (ctx, FZ_ERROR_GENERIC, "malformed pages tree");
832 if (pdf_mark_obj (ctx, node))
833 fz_throw (ctx, FZ_ERROR_GENERIC, "cycle in page tree");
834 for (int i = 0; i < len; i++) {
835 pdf_obj *kid = pdf_array_get (ctx, kids, i);
836 const char *type = pdf_to_name (ctx, pdf_dict_gets (ctx, kid, "Type"));
837 if (*type
838 ? !strcmp (type, "Pages")
839 : pdf_dict_gets (ctx, kid, "Kids")
840 && !pdf_dict_gets (ctx, kid, "MediaBox")) {
841 pdf_collect_pages (doc, kid);
843 else {
844 if (*type
845 ? strcmp (type, "Page") != 0
846 : !pdf_dict_gets (ctx, kid, "MediaBox"))
847 fz_warn (ctx, "non-page object in page tree (%s)", type);
848 state.pdflut.objs[state.pdflut.idx++] = pdf_keep_obj (ctx, kid);
851 pdf_unmark_obj (ctx, node);
854 static void
855 pdf_load_page_objs (pdf_document *doc)
857 pdf_obj *root = pdf_dict_gets (state.ctx,
858 pdf_trailer (state.ctx, doc), "Root");
859 pdf_obj *node = pdf_dict_gets (state.ctx, root, "Pages");
861 if (!node)
862 fz_throw (state.ctx, FZ_ERROR_GENERIC, "cannot find page tree");
864 state.pdflut.idx = 0;
865 pdf_collect_pages (doc, node);
867 #endif
869 static void initpdims (void)
871 double start, end;
872 FILE *trimf = NULL;
873 fz_rect rootmediabox = fz_empty_rect;
874 int pageno, trim, show;
875 int trimw = 0, cxcount;
876 fz_context *ctx = state.ctx;
877 pdf_document *pdf = pdf_specifics (ctx, state.doc);
879 fz_var (trimw);
880 fz_var (trimf);
881 fz_var (cxcount);
882 start = now ();
884 if (state.trimmargins && state.trimcachepath) {
885 trimf = fopen (state.trimcachepath, "rb");
886 if (!trimf) {
887 trimf = fopen (state.trimcachepath, "wb");
888 trimw = 1;
892 if (state.trimmargins || pdf)
893 cxcount = state.pagecount;
894 else
895 cxcount = fz_mini (state.pagecount, 1);
897 if (pdf) {
898 pdf_obj *obj;
899 obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
900 "Root/Pages/MediaBox");
901 pdf_to_rect (ctx, obj, &rootmediabox);
904 #ifdef CACHE_PAGEREFS
905 if (pdf && (!state.pdflut.objs || state.pdflut.pdf != pdf)) {
906 state.pdflut.objs = calloc (sizeof (*state.pdflut.objs), cxcount);
907 if (!state.pdflut.objs) {
908 err (1, "malloc pageobjs %zu %d %zu failed",
909 sizeof (*state.pdflut.objs), cxcount,
910 sizeof (*state.pdflut.objs) * cxcount);
912 state.pdflut.count = cxcount;
913 pdf_load_page_objs (pdf);
914 state.pdflut.pdf = pdf;
916 #endif
918 for (pageno = 0; pageno < cxcount; ++pageno) {
919 int rotate = 0;
920 struct pagedim *p;
921 fz_rect mediabox = fz_empty_rect;
923 fz_var (rotate);
924 if (pdf) {
925 pdf_obj *pageref, *pageobj;
927 #ifdef CACHE_PAGEREFS
928 pageref = state.pdflut.objs[pageno];
929 #else
930 pageref = pdf_lookup_page_obj (ctx, pdf, pageno);
931 #endif
932 pageobj = pdf_resolve_indirect (ctx, pageref);
933 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
935 if (state.trimmargins) {
936 pdf_obj *obj;
937 pdf_page *page;
939 fz_try (ctx) {
940 page = pdf_load_page (ctx, pdf, pageno);
941 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
942 trim = state.trimanew || !obj;
943 if (trim) {
944 fz_rect rect;
945 fz_device *dev;
946 fz_matrix ctm, page_ctm;
948 dev = fz_new_bbox_device (ctx, &rect);
949 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
950 fz_invert_matrix (&ctm, &page_ctm);
951 pdf_run_page (ctx, page, dev, &fz_identity, NULL);
952 fz_close_device (ctx, dev);
953 fz_drop_device (ctx, dev);
955 rect.x0 += state.trimfuzz.x0;
956 rect.x1 += state.trimfuzz.x1;
957 rect.y0 += state.trimfuzz.y0;
958 rect.y1 += state.trimfuzz.y1;
959 fz_transform_rect (&rect, &ctm);
960 fz_intersect_rect (&rect, &mediabox);
962 if (!fz_is_empty_rect (&rect)) {
963 mediabox = rect;
966 obj = pdf_new_array (ctx, pdf, 4);
967 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
968 mediabox.x0));
969 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
970 mediabox.y0));
971 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
972 mediabox.x1));
973 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
974 mediabox.y1));
975 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
977 else {
978 mediabox.x0 = pdf_to_real (ctx,
979 pdf_array_get (ctx, obj, 0));
980 mediabox.y0 = pdf_to_real (ctx,
981 pdf_array_get (ctx, obj, 1));
982 mediabox.x1 = pdf_to_real (ctx,
983 pdf_array_get (ctx, obj, 2));
984 mediabox.y1 = pdf_to_real (ctx,
985 pdf_array_get (ctx, obj, 3));
988 fz_drop_page (ctx, &page->super);
989 show = trim ? pageno % 5 == 0 : pageno % 20 == 0;
990 if (show) {
991 printd ("progress %f Trimming %d",
992 (double) (pageno + 1) / state.pagecount,
993 pageno + 1);
996 fz_catch (ctx) {
997 printd ("emsg failed to load page %d", pageno);
1000 else {
1001 int empty = 0;
1002 fz_rect cropbox;
1004 pdf_to_rect (ctx,
1005 pdf_dict_gets (ctx, pageobj, "MediaBox"),
1006 &mediabox);
1007 if (fz_is_empty_rect (&mediabox)) {
1008 mediabox.x0 = 0;
1009 mediabox.y0 = 0;
1010 mediabox.x1 = 612;
1011 mediabox.y1 = 792;
1012 empty = 1;
1015 pdf_to_rect (ctx,
1016 pdf_dict_gets (ctx, pageobj, "CropBox"),
1017 &cropbox);
1018 if (!fz_is_empty_rect (&cropbox)) {
1019 if (empty) {
1020 mediabox = cropbox;
1022 else {
1023 fz_intersect_rect (&mediabox, &cropbox);
1026 else {
1027 if (empty) {
1028 if (fz_is_empty_rect (&rootmediabox)) {
1029 printd ("emsg cannot find page size for page %d",
1030 pageno);
1032 else {
1033 mediabox = rootmediabox;
1039 else {
1040 if (state.trimmargins && trimw) {
1041 fz_page *page;
1043 fz_try (ctx) {
1044 page = fz_load_page (ctx, state.doc, pageno);
1045 fz_bound_page (ctx, page, &mediabox);
1046 if (state.trimmargins) {
1047 fz_rect rect;
1048 fz_device *dev;
1050 dev = fz_new_bbox_device (ctx, &rect);
1051 fz_run_page (ctx, page, dev, &fz_identity, NULL);
1052 fz_close_device (ctx, dev);
1053 fz_drop_device (ctx, dev);
1055 rect.x0 += state.trimfuzz.x0;
1056 rect.x1 += state.trimfuzz.x1;
1057 rect.y0 += state.trimfuzz.y0;
1058 rect.y1 += state.trimfuzz.y1;
1059 fz_intersect_rect (&rect, &mediabox);
1061 if (!fz_is_empty_rect (&rect)) {
1062 mediabox = rect;
1065 fz_drop_page (ctx, page);
1067 fz_catch (ctx) {
1069 if (trimf) {
1070 size_t n = fwrite (&mediabox, sizeof (mediabox), 1, trimf);
1071 if (n - 1) {
1072 err (1, "fwrite trim mediabox");
1076 else {
1077 if (trimf) {
1078 size_t n = fread (&mediabox, sizeof (mediabox), 1, trimf);
1079 if (n - 1) {
1080 err (1, "fread trim mediabox %d", pageno);
1083 else {
1084 fz_page *page;
1085 fz_try (ctx) {
1086 page = fz_load_page (ctx, state.doc, pageno);
1087 fz_bound_page (ctx, page, &mediabox);
1088 fz_drop_page (ctx, page);
1090 show = !state.trimmargins && pageno % 20 == 0;
1091 if (show) {
1092 printd ("progress %f Gathering dimensions %d",
1093 (double) (pageno) / state.pagecount,
1094 pageno);
1097 fz_catch (ctx) {
1098 printd ("emsg failed to load page %d", pageno);
1104 if (state.pagedimcount == 0
1105 || ((void) (p = &state.pagedims[state.pagedimcount-1])
1106 , p->rotate != rotate)
1107 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
1108 size_t size;
1110 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
1111 state.pagedims = realloc (state.pagedims, size);
1112 if (!state.pagedims) {
1113 err (1, "realloc pagedims to %" FMT_s " (%d elems)",
1114 size, state.pagedimcount + 1);
1117 p = &state.pagedims[state.pagedimcount++];
1118 p->rotate = rotate;
1119 p->mediabox = mediabox;
1120 p->pageno = pageno;
1123 end = now ();
1124 printd ("progress 1 %s %d pages in %f seconds",
1125 state.trimmargins ? "Trimmed" : "Processed",
1126 state.pagecount, end - start);
1127 state.trimanew = 0;
1128 if (trimf) {
1129 if (fclose (trimf)) {
1130 err (1, "fclose");
1135 static void layout (void)
1137 int pindex;
1138 fz_rect box;
1139 fz_matrix ctm, rm;
1140 struct pagedim *p = NULL;
1141 float zw, w, maxw = 0.0, zoom = 1.0;
1143 if (state.pagedimcount == 0) return;
1145 switch (state.fitmodel) {
1146 case FitProportional:
1147 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1148 float x0, x1;
1150 p = &state.pagedims[pindex];
1151 fz_rotate (&rm, p->rotate + state.rotate);
1152 box = p->mediabox;
1153 fz_transform_rect (&box, &rm);
1155 x0 = fz_min (box.x0, box.x1);
1156 x1 = fz_max (box.x0, box.x1);
1158 w = x1 - x0;
1159 maxw = fz_max (w, maxw);
1160 zoom = state.w / maxw;
1162 break;
1164 case FitPage:
1165 maxw = state.w;
1166 break;
1168 case FitWidth:
1169 break;
1171 default:
1172 ARSERT (0 && state.fitmodel);
1175 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1176 fz_rect rect;
1177 fz_matrix tm, sm;
1179 p = &state.pagedims[pindex];
1180 fz_rotate (&ctm, state.rotate);
1181 fz_rotate (&rm, p->rotate + state.rotate);
1182 box = p->mediabox;
1183 fz_transform_rect (&box, &rm);
1184 w = box.x1 - box.x0;
1185 switch (state.fitmodel) {
1186 case FitProportional:
1187 p->left = (int) (((maxw - w) * zoom) / 2.f);
1188 break;
1189 case FitPage:
1191 float zh, h;
1192 zw = maxw / w;
1193 h = box.y1 - box.y0;
1194 zh = state.h / h;
1195 zoom = fz_min (zw, zh);
1196 p->left = (int) ((maxw - (w * zoom)) / 2.f);
1198 break;
1199 case FitWidth:
1200 p->left = 0;
1201 zoom = state.w / w;
1202 break;
1205 fz_scale (&p->zoomctm, zoom, zoom);
1206 fz_concat (&ctm, &p->zoomctm, &ctm);
1208 fz_rotate (&rm, p->rotate);
1209 p->pagebox = p->mediabox;
1210 fz_transform_rect (&p->pagebox, &rm);
1211 p->pagebox.x1 -= p->pagebox.x0;
1212 p->pagebox.y1 -= p->pagebox.y0;
1213 p->pagebox.x0 = 0;
1214 p->pagebox.y0 = 0;
1215 rect = p->pagebox;
1216 fz_transform_rect (&rect, &ctm);
1217 fz_round_rect (&p->bounds, &rect);
1218 p->ctm = ctm;
1220 fz_translate (&tm, 0, -p->mediabox.y1);
1221 fz_scale (&sm, zoom, -zoom);
1222 fz_concat (&ctm, &tm, &sm);
1224 p->tctmready = 0;
1227 do {
1228 int x0 = fz_mini (p->bounds.x0, p->bounds.x1);
1229 int y0 = fz_mini (p->bounds.y0, p->bounds.y1);
1230 int x1 = fz_maxi (p->bounds.x0, p->bounds.x1);
1231 int y1 = fz_maxi (p->bounds.y0, p->bounds.y1);
1232 int boundw = x1 - x0;
1233 int boundh = y1 - y0;
1235 printd ("pdim %d %d %d %d", p->pageno, boundw, boundh, p->left);
1236 } while (p-- != state.pagedims);
1239 struct pagedim *pdimofpageno (int pageno)
1241 struct pagedim *pdim = state.pagedims;
1243 for (int i = 0; i < state.pagedimcount; ++i) {
1244 if (state.pagedims[i].pageno > pageno)
1245 break;
1246 pdim = &state.pagedims[i];
1248 return pdim;
1251 static void recurse_outline (fz_outline *outline, int level)
1253 while (outline) {
1254 if (outline->page >= 0) {
1255 fz_point p = {.x = outline->x, .y = outline->y};
1256 struct pagedim *pdim = pdimofpageno (outline->page);
1257 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
1258 fz_transform_point (&p, &pdim->ctm);
1259 printd ("o %d %d %d %d %s",
1260 level, outline->page, (int) p.y, h, outline->title);
1262 else {
1263 printd ("on %d %s", level, outline->title);
1265 if (outline->down) {
1266 recurse_outline (outline->down, level + 1);
1268 outline = outline->next;
1272 static void process_outline (void)
1274 fz_outline *outline;
1276 if (!state.needoutline || !state.pagedimcount) return;
1278 state.needoutline = 0;
1279 outline = fz_load_outline (state.ctx, state.doc);
1280 if (outline) {
1281 recurse_outline (outline, 0);
1282 fz_drop_outline (state.ctx, outline);
1286 static char *strofline (fz_stext_line *line)
1288 char *p;
1289 char utf8[10];
1290 fz_stext_char *ch;
1291 size_t size = 0, cap = 80;
1293 p = malloc (cap + 1);
1294 if (!p) return NULL;
1296 for (ch = line->first_char; ch; ch = ch->next) {
1297 int n = fz_runetochar (utf8, ch->c);
1298 if (size + n > cap) {
1299 cap *= 2;
1300 p = realloc (p, cap + 1);
1301 if (!p) return NULL;
1304 memcpy (p + size, utf8, n);
1305 size += n;
1307 p[size] = 0;
1308 return p;
1311 static int matchline (regex_t *re, fz_stext_line *line,
1312 int stop, int pageno, double start)
1314 int ret;
1315 char *p;
1316 regmatch_t rm;
1318 p = strofline (line);
1319 if (!p) return -1;
1321 ret = regexec (re, p, 1, &rm, 0);
1322 if (ret) {
1323 free (p);
1324 if (ret != REG_NOMATCH) {
1325 size_t size;
1326 char errbuf[80];
1327 size = regerror (ret, re, errbuf, sizeof (errbuf));
1328 printd ("msg regexec error `%.*s'",
1329 (int) size, errbuf);
1330 return -1;
1332 return 0;
1334 else {
1335 fz_point p1, p2, p3, p4;
1336 fz_rect s = {0,0,0,0}, e;
1337 fz_stext_char *ch;
1338 int o = 0;
1340 for (ch = line->first_char; ch; ch = ch->next) {
1341 o += fz_runelen (ch->c);
1342 if (o > rm.rm_so) {
1343 s = ch->bbox;
1344 break;
1347 for (;ch; ch = ch->next) {
1348 o += fz_runelen (ch->c);
1349 if (o > rm.rm_eo) break;
1351 e = ch->bbox;
1353 p1.x = s.x0;
1354 p1.y = s.y0;
1355 p2.x = e.x1;
1356 p2.y = s.y0;
1357 p3.x = e.x1;
1358 p3.y = e.y1;
1359 p4.x = s.x0;
1360 p4.y = e.y1;
1362 #pragma GCC diagnostic ignored "-Wdouble-promotion"
1363 if (!stop) {
1364 printd ("firstmatch %d %d %f %f %f %f %f %f %f %f",
1365 pageno, 1,
1366 p1.x, p1.y,
1367 p2.x, p2.y,
1368 p3.x, p3.y,
1369 p4.x, p4.y);
1371 printd ("progress 1 found at %d `%.*s' in %f sec",
1372 pageno + 1, (int) (rm.rm_eo - rm.rm_so), &p[rm.rm_so],
1373 now () - start);
1375 else {
1376 printd ("match %d %d %f %f %f %f %f %f %f %f",
1377 pageno, 2,
1378 p1.x, p1.y,
1379 p2.x, p2.y,
1380 p3.x, p3.y,
1381 p4.x, p4.y);
1383 #pragma GCC diagnostic error "-Wdouble-promotion"
1384 free (p);
1385 return 1;
1389 /* wishful thinking function */
1390 static void search (regex_t *re, int pageno, int y, int forward)
1392 fz_device *tdev;
1393 fz_stext_page *text;
1394 struct pagedim *pdim;
1395 int stop = 0, niters = 0;
1396 double start, end;
1397 fz_page *page;
1398 fz_stext_block *block;
1400 start = now ();
1401 while (pageno >= 0 && pageno < state.pagecount && !stop) {
1402 if (niters++ == 5) {
1403 niters = 0;
1404 if (hasdata ()) {
1405 printd ("progress 1 attention requested aborting search at %d",
1406 pageno);
1407 stop = 1;
1409 else {
1410 printd ("progress %f searching in page %d",
1411 (double) (pageno + 1) / state.pagecount,
1412 pageno);
1415 pdim = pdimofpageno (pageno);
1416 text = fz_new_stext_page (state.ctx, &pdim->mediabox);
1417 tdev = fz_new_stext_device (state.ctx, text, 0);
1419 page = fz_load_page (state.ctx, state.doc, pageno);
1421 fz_matrix ctm = pagectm1 (page, pdim);
1422 fz_run_page (state.ctx, page, tdev, &ctm, NULL);
1425 fz_close_device (state.ctx, tdev);
1426 fz_drop_device (state.ctx, tdev);
1428 if (forward) {
1429 for (block = text->first_block; block; block = block->next) {
1430 fz_stext_line *line;
1432 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
1433 for (line = block->u.t.first_line; line; line = line->next) {
1434 if (line->bbox.y0 < y + 1) continue;
1436 switch (matchline (re, line, stop, pageno, start)) {
1437 case 0: break;
1438 case 1: stop = 1; break;
1439 case -1: stop = 1; goto endloop;
1444 else {
1445 for (block = text->last_block; block; block = block->prev) {
1446 fz_stext_line *line;
1448 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
1449 for (line = block->u.t.last_line; line; line = line->prev) {
1450 if (line->bbox.y0 < y + 1) continue;
1452 switch (matchline (re, line, stop, pageno, start)) {
1453 case 0: break;
1454 case 1: stop = 1; break;
1455 case -1: stop = 1; goto endloop;
1461 if (forward) {
1462 pageno += 1;
1463 y = 0;
1465 else {
1466 pageno -= 1;
1467 y = INT_MAX;
1469 endloop:
1470 fz_drop_stext_page (state.ctx, text);
1471 fz_drop_page (state.ctx, page);
1473 end = now ();
1474 if (!stop) {
1475 printd ("progress 1 no matches %f sec", end - start);
1477 printd ("clearrects");
1480 static void set_tex_params (int colorspace)
1482 switch (colorspace) {
1483 case 0:
1484 state.texiform = GL_RGBA8;
1485 state.texform = GL_RGBA;
1486 state.texty = GL_UNSIGNED_BYTE;
1487 state.colorspace = fz_device_rgb (state.ctx);
1488 break;
1489 case 1:
1490 state.texiform = GL_RGBA8;
1491 state.texform = GL_BGRA;
1492 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1493 state.texty = GL_UNSIGNED_INT_8_8_8_8_REV;
1494 #else
1495 state.texty = GL_UNSIGNED_INT_8_8_8_8;
1496 #endif
1497 state.colorspace = fz_device_bgr (state.ctx);
1498 break;
1499 case 2:
1500 state.texiform = GL_LUMINANCE_ALPHA;
1501 state.texform = GL_LUMINANCE_ALPHA;
1502 state.texty = GL_UNSIGNED_BYTE;
1503 state.colorspace = fz_device_gray (state.ctx);
1504 break;
1505 default:
1506 errx (1, "invalid colorspce %d", colorspace);
1510 static void realloctexts (int texcount)
1512 size_t size;
1514 if (texcount == state.texcount) return;
1516 if (texcount < state.texcount) {
1517 glDeleteTextures (state.texcount - texcount,
1518 state.texids + texcount);
1521 size = texcount * (sizeof (*state.texids) + sizeof (*state.texowners));
1522 state.texids = realloc (state.texids, size);
1523 if (!state.texids) {
1524 err (1, "realloc texs %" FMT_s, size);
1527 state.texowners = (void *) (state.texids + texcount);
1528 if (texcount > state.texcount) {
1529 glGenTextures (texcount - state.texcount,
1530 state.texids + state.texcount);
1531 for (int i = state.texcount; i < texcount; ++i) {
1532 state.texowners[i].w = -1;
1533 state.texowners[i].slice = NULL;
1536 state.texcount = texcount;
1537 state.texindex = 0;
1540 static char *mbtoutf8 (char *s)
1542 char *p, *r;
1543 wchar_t *tmp;
1544 size_t i, ret, len;
1546 if (state.utf8cs) {
1547 return s;
1550 len = mbstowcs (NULL, s, strlen (s));
1551 if (len == 0) {
1552 return s;
1554 else {
1555 if (len == (size_t) -1) {
1556 printd ("emsg mbtoutf8: mbstowcs: %d:%s", errno, strerror (errno));
1557 return s;
1561 tmp = calloc (len, sizeof (wchar_t));
1562 if (!tmp) {
1563 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d:%s",
1564 len, sizeof (wchar_t), errno, strerror (errno));
1565 return s;
1568 ret = mbstowcs (tmp, s, len);
1569 if (ret == (size_t) -1) {
1570 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d:%s",
1571 len, errno, strerror (errno));
1572 free (tmp);
1573 return s;
1576 len = 0;
1577 for (i = 0; i < ret; ++i) {
1578 len += fz_runelen (tmp[i]);
1581 p = r = malloc (len + 1);
1582 if (!r) {
1583 printd ("emsg mbtoutf8: malloc(%zu)", len);
1584 free (tmp);
1585 return s;
1588 for (i = 0; i < ret; ++i) {
1589 p += fz_runetochar (p, tmp[i]);
1591 *p = 0;
1592 free (tmp);
1593 return r;
1596 CAMLprim value ml_mbtoutf8 (value s_v)
1598 CAMLparam1 (s_v);
1599 CAMLlocal1 (ret_v);
1600 char *s, *r;
1602 s = String_val (s_v);
1603 r = mbtoutf8 (s);
1604 if (r == s) {
1605 ret_v = s_v;
1607 else {
1608 ret_v = caml_copy_string (r);
1609 free (r);
1611 CAMLreturn (ret_v);
1614 static void * mainloop (void UNUSED_ATTR *unused)
1616 char *p = NULL;
1617 int len, ret, oldlen = 0;
1619 fz_var (p);
1620 fz_var (oldlen);
1621 for (;;) {
1622 len = readlen (state.csock);
1623 if (len == 0) {
1624 errx (1, "readlen returned 0");
1627 if (oldlen < len + 1) {
1628 p = realloc (p, len + 1);
1629 if (!p) {
1630 err (1, "realloc %d failed", len + 1);
1632 oldlen = len + 1;
1634 readdata (state.csock, p, len);
1635 p[len] = 0;
1637 if (!strncmp ("open", p, 4)) {
1638 int off, usedoccss, ok = 0, layouth;
1639 char *password;
1640 char *filename;
1641 char *utf8filename;
1642 size_t filenamelen;
1644 fz_var (ok);
1645 ret = sscanf (p + 5, " %d %d %n", &usedoccss, &layouth, &off);
1646 if (ret != 2) {
1647 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1650 filename = p + 5 + off;
1651 filenamelen = strlen (filename);
1652 password = filename + filenamelen + 1;
1654 if (password[strlen (password) + 1]) {
1655 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1658 lock ("open");
1659 fz_set_use_document_css (state.ctx, usedoccss);
1660 fz_try (state.ctx) {
1661 ok = openxref (filename, password, layouth);
1663 fz_catch (state.ctx) {
1664 utf8filename = mbtoutf8 (filename);
1665 printd ("msg Could not open %s", utf8filename);
1666 if (utf8filename != filename) {
1667 free (utf8filename);
1670 if (ok) {
1671 pdfinfo ();
1672 initpdims ();
1674 unlock ("open");
1676 if (ok) {
1677 utf8filename = mbtoutf8 (filename);
1678 printd ("msg Opened %s (press h/F1 to get help)", utf8filename);
1679 if (utf8filename != filename) {
1680 free (utf8filename);
1682 state.needoutline = 1;
1685 else if (!strncmp ("cs", p, 2)) {
1686 int i, colorspace;
1688 ret = sscanf (p + 2, " %d", &colorspace);
1689 if (ret != 1) {
1690 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1692 lock ("cs");
1693 set_tex_params (colorspace);
1694 for (i = 0; i < state.texcount; ++i) {
1695 state.texowners[i].w = -1;
1696 state.texowners[i].slice = NULL;
1698 unlock ("cs");
1700 else if (!strncmp ("freepage", p, 8)) {
1701 void *ptr;
1703 ret = sscanf (p + 8, " %" SCN_ptr, SCN_ptr_cast (&ptr));
1704 if (ret != 1) {
1705 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1707 lock ("freepage");
1708 freepage (ptr);
1709 unlock ("freepage");
1711 else if (!strncmp ("freetile", p, 8)) {
1712 void *ptr;
1714 ret = sscanf (p + 8, " %" SCN_ptr, SCN_ptr_cast (&ptr));
1715 if (ret != 1) {
1716 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1718 lock ("freetile");
1719 freetile (ptr);
1720 unlock ("freetile");
1722 else if (!strncmp ("search", p, 6)) {
1723 int icase, pageno, y, len2, forward;
1724 char *pattern;
1725 regex_t re;
1727 ret = sscanf (p + 6, " %d %d %d %d,%n",
1728 &icase, &pageno, &y, &forward, &len2);
1729 if (ret != 4) {
1730 errx (1, "malformed search `%s' ret=%d", p, ret);
1733 pattern = p + 6 + len2;
1734 ret = regcomp (&re, pattern,
1735 REG_EXTENDED | (icase ? REG_ICASE : 0));
1736 if (ret) {
1737 char errbuf[80];
1738 size_t size;
1740 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1741 printd ("msg regcomp failed `%.*s'", (int) size, errbuf);
1743 else {
1744 search (&re, pageno, y, forward);
1745 regfree (&re);
1748 else if (!strncmp ("geometry", p, 8)) {
1749 int w, h, fitmodel;
1751 printd ("clear");
1752 ret = sscanf (p + 8, " %d %d %d", &w, &h, &fitmodel);
1753 if (ret != 3) {
1754 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1757 lock ("geometry");
1758 state.h = h;
1759 if (w != state.w) {
1760 state.w = w;
1761 for (int i = 0; i < state.texcount; ++i) {
1762 state.texowners[i].slice = NULL;
1765 state.fitmodel = fitmodel;
1766 layout ();
1767 process_outline ();
1769 state.gen++;
1770 unlock ("geometry");
1771 printd ("continue %d", state.pagecount);
1773 else if (!strncmp ("reqlayout", p, 9)) {
1774 char *nameddest;
1775 int rotate, off, h;
1776 int fitmodel;
1777 pdf_document *pdf;
1779 printd ("clear");
1780 ret = sscanf (p + 9, " %d %d %d %n",
1781 &rotate, &fitmodel, &h, &off);
1782 if (ret != 3) {
1783 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1785 lock ("reqlayout");
1786 pdf = pdf_specifics (state.ctx, state.doc);
1787 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1788 state.gen += 1;
1790 state.rotate = rotate;
1791 state.fitmodel = fitmodel;
1792 state.h = h;
1793 layout ();
1794 process_outline ();
1796 nameddest = p + 9 + off;
1797 if (pdf && nameddest && *nameddest) {
1798 fz_point xy;
1799 struct pagedim *pdim;
1800 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1801 &xy.x, &xy.y);
1802 pdim = pdimofpageno (pageno);
1803 fz_transform_point (&xy, &pdim->ctm);
1804 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1807 state.gen++;
1808 unlock ("reqlayout");
1809 printd ("continue %d", state.pagecount);
1811 else if (!strncmp ("page", p, 4)) {
1812 double a, b;
1813 struct page *page;
1814 int pageno, pindex;
1816 ret = sscanf (p + 4, " %d %d", &pageno, &pindex);
1817 if (ret != 2) {
1818 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1821 lock ("page");
1822 a = now ();
1823 page = loadpage (pageno, pindex);
1824 b = now ();
1825 unlock ("page");
1827 printd ("page %" FMT_ptr " %f", FMT_ptr_cast (page), b - a);
1829 else if (!strncmp ("tile", p, 4)) {
1830 int x, y, w, h;
1831 struct page *page;
1832 struct tile *tile;
1833 double a, b;
1834 void *data;
1836 ret = sscanf (p + 4, " %" SCN_ptr " %d %d %d %d %" SCN_ptr,
1837 SCN_ptr_cast (&page), &x, &y, &w, &h,
1838 SCN_ptr_cast (&data));
1839 if (ret != 6) {
1840 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1843 lock ("tile");
1844 a = now ();
1845 tile = rendertile (page, x, y, w, h, data);
1846 b = now ();
1847 unlock ("tile");
1849 printd ("tile %d %d %" FMT_ptr " %u %f",
1850 x, y,
1851 FMT_ptr_cast (tile),
1852 tile->w * tile->h * tile->pixmap->n,
1853 b - a);
1855 else if (!strncmp ("trimset", p, 7)) {
1856 fz_irect fuzz;
1857 int trimmargins;
1859 ret = sscanf (p + 7, " %d %d %d %d %d",
1860 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1861 if (ret != 5) {
1862 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1864 lock ("trimset");
1865 state.trimmargins = trimmargins;
1866 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1867 state.trimanew = 1;
1868 state.trimfuzz = fuzz;
1870 unlock ("trimset");
1872 else if (!strncmp ("settrim", p, 7)) {
1873 fz_irect fuzz;
1874 int trimmargins;
1876 ret = sscanf (p + 7, " %d %d %d %d %d",
1877 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1878 if (ret != 5) {
1879 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1881 printd ("clear");
1882 lock ("settrim");
1883 state.trimmargins = trimmargins;
1884 state.needoutline = 1;
1885 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1886 state.trimanew = 1;
1887 state.trimfuzz = fuzz;
1889 state.pagedimcount = 0;
1890 free (state.pagedims);
1891 state.pagedims = NULL;
1892 initpdims ();
1893 layout ();
1894 process_outline ();
1895 unlock ("settrim");
1896 printd ("continue %d", state.pagecount);
1898 else if (!strncmp ("sliceh", p, 6)) {
1899 int h;
1901 ret = sscanf (p + 6, " %d", &h);
1902 if (ret != 1) {
1903 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1905 if (h != state.sliceheight) {
1906 state.sliceheight = h;
1907 for (int i = 0; i < state.texcount; ++i) {
1908 state.texowners[i].w = -1;
1909 state.texowners[i].h = -1;
1910 state.texowners[i].slice = NULL;
1914 else if (!strncmp ("interrupt", p, 9)) {
1915 printd ("vmsg interrupted");
1917 else {
1918 errx (1, "unknown command %.*s", len, p);
1921 return 0;
1924 CAMLprim value ml_isexternallink (value uri_v)
1926 CAMLparam1 (uri_v);
1927 int ext = fz_is_external_link (state.ctx, String_val (uri_v));
1928 CAMLreturn (Val_bool (ext));
1931 CAMLprim value ml_uritolocation (value uri_v)
1933 CAMLparam1 (uri_v);
1934 CAMLlocal1 (ret_v);
1935 int pageno;
1936 fz_point xy;
1937 struct pagedim *pdim;
1939 pageno = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1940 &xy.x, &xy.y);
1941 pdim = pdimofpageno (pageno);
1942 fz_transform_point (&xy, &pdim->ctm);
1943 ret_v = caml_alloc_tuple (3);
1944 Field (ret_v, 0) = Val_int (pageno);
1945 Field (ret_v, 1) = caml_copy_double ((double) xy.x);
1946 Field (ret_v, 2) = caml_copy_double ((double) xy.y);
1947 CAMLreturn (ret_v);
1950 CAMLprim value ml_realloctexts (value texcount_v)
1952 CAMLparam1 (texcount_v);
1953 int ok;
1955 if (trylock (__func__)) {
1956 ok = 0;
1957 goto done;
1959 realloctexts (Int_val (texcount_v));
1960 ok = 1;
1961 unlock (__func__);
1963 done:
1964 CAMLreturn (Val_bool (ok));
1967 static void recti (int x0, int y0, int x1, int y1)
1969 GLfloat *v = state.vertices;
1971 glVertexPointer (2, GL_FLOAT, 0, v);
1972 v[0] = x0; v[1] = y0;
1973 v[2] = x1; v[3] = y0;
1974 v[4] = x0; v[5] = y1;
1975 v[6] = x1; v[7] = y1;
1976 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1979 static void showsel (struct page *page, int ox, int oy)
1981 fz_irect bbox;
1982 fz_rect rect;
1983 fz_stext_block *block;
1984 int seen = 0;
1985 unsigned char selcolor[] = {15,15,15,140};
1987 if (!page->fmark.ch || !page->lmark.ch) return;
1989 glEnable (GL_BLEND);
1990 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1991 glColor4ubv (selcolor);
1993 ox += state.pagedims[page->pdimno].bounds.x0;
1994 oy += state.pagedims[page->pdimno].bounds.y0;
1996 for (block = page->text->first_block; block; block = block->next) {
1997 fz_stext_line *line;
1999 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
2000 for (line = block->u.t.first_line; line; line = line->next) {
2001 fz_stext_char *ch;
2003 rect = fz_empty_rect;
2004 for (ch = line->first_char; ch; ch = ch->next) {
2005 if (ch == page->fmark.ch) seen = 1;
2006 if (seen) fz_union_rect (&rect, &ch->bbox);
2007 if (ch == page->lmark.ch) {
2008 fz_round_rect (&bbox, &rect);
2009 recti (bbox.x0 + ox, bbox.y0 + oy,
2010 bbox.x1 + ox, bbox.y1 + oy);
2011 goto done;
2014 fz_round_rect (&bbox, &rect);
2015 recti (bbox.x0 + ox, bbox.y0 + oy,
2016 bbox.x1 + ox, bbox.y1 + oy);
2019 done:
2020 glDisable (GL_BLEND);
2023 #pragma GCC diagnostic push
2024 #pragma GCC diagnostic ignored "-Wdouble-promotion"
2025 #pragma GCC diagnostic ignored "-Wconversion"
2026 #include "glfont.c"
2027 #pragma GCC diagnostic pop
2029 static void stipplerect (fz_matrix *m,
2030 fz_point *p1,
2031 fz_point *p2,
2032 fz_point *p3,
2033 fz_point *p4,
2034 GLfloat *texcoords,
2035 GLfloat *vertices)
2037 fz_transform_point (p1, m);
2038 fz_transform_point (p2, m);
2039 fz_transform_point (p3, m);
2040 fz_transform_point (p4, m);
2042 float w, h, s, t;
2044 w = p2->x - p1->x;
2045 h = p2->y - p1->y;
2046 t = hypotf (w, h) * .25f;
2048 w = p3->x - p2->x;
2049 h = p3->y - p2->y;
2050 s = hypotf (w, h) * .25f;
2052 texcoords[0] = 0; vertices[0] = p1->x; vertices[1] = p1->y;
2053 texcoords[1] = t; vertices[2] = p2->x; vertices[3] = p2->y;
2055 texcoords[2] = 0; vertices[4] = p2->x; vertices[5] = p2->y;
2056 texcoords[3] = s; vertices[6] = p3->x; vertices[7] = p3->y;
2058 texcoords[4] = 0; vertices[8] = p3->x; vertices[9] = p3->y;
2059 texcoords[5] = t; vertices[10] = p4->x; vertices[11] = p4->y;
2061 texcoords[6] = 0; vertices[12] = p4->x; vertices[13] = p4->y;
2062 texcoords[7] = s; vertices[14] = p1->x; vertices[15] = p1->y;
2064 glDrawArrays (GL_LINES, 0, 8);
2067 static void solidrect (fz_matrix *m,
2068 fz_point *p1,
2069 fz_point *p2,
2070 fz_point *p3,
2071 fz_point *p4,
2072 GLfloat *vertices)
2074 fz_transform_point (p1, m);
2075 fz_transform_point (p2, m);
2076 fz_transform_point (p3, m);
2077 fz_transform_point (p4, m);
2078 vertices[0] = p1->x; vertices[1] = p1->y;
2079 vertices[2] = p2->x; vertices[3] = p2->y;
2081 vertices[4] = p3->x; vertices[5] = p3->y;
2082 vertices[6] = p4->x; vertices[7] = p4->y;
2083 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
2086 static void ensurelinks (struct page *page)
2088 if (!page->links)
2089 page->links = fz_load_links (state.ctx, page->fzpage);
2092 static void highlightlinks (struct page *page, int xoff, int yoff)
2094 fz_matrix ctm, tm, pm;
2095 fz_link *link;
2096 GLfloat *texcoords = state.texcoords;
2097 GLfloat *vertices = state.vertices;
2099 ensurelinks (page);
2101 glEnable (GL_TEXTURE_1D);
2102 glEnable (GL_BLEND);
2103 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2104 glBindTexture (GL_TEXTURE_1D, state.stid);
2106 xoff -= state.pagedims[page->pdimno].bounds.x0;
2107 yoff -= state.pagedims[page->pdimno].bounds.y0;
2108 fz_translate (&tm, xoff, yoff);
2109 pm = pagectm (page);
2110 fz_concat (&ctm, &pm, &tm);
2112 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
2113 glVertexPointer (2, GL_FLOAT, 0, vertices);
2115 for (link = page->links; link; link = link->next) {
2116 fz_point p1, p2, p3, p4;
2118 p1.x = link->rect.x0;
2119 p1.y = link->rect.y0;
2121 p2.x = link->rect.x1;
2122 p2.y = link->rect.y0;
2124 p3.x = link->rect.x1;
2125 p3.y = link->rect.y1;
2127 p4.x = link->rect.x0;
2128 p4.y = link->rect.y1;
2130 /* TODO: different colours for different schemes */
2131 if (fz_is_external_link (state.ctx, link->uri)) glColor3ub (0, 0, 255);
2132 else glColor3ub (255, 0, 0);
2134 stipplerect (&ctm, &p1, &p2, &p3, &p4, texcoords, vertices);
2137 for (int i = 0; i < page->annotcount; ++i) {
2138 fz_point p1, p2, p3, p4;
2139 struct annot *annot = &page->annots[i];
2141 p1.x = annot->bbox.x0;
2142 p1.y = annot->bbox.y0;
2144 p2.x = annot->bbox.x1;
2145 p2.y = annot->bbox.y0;
2147 p3.x = annot->bbox.x1;
2148 p3.y = annot->bbox.y1;
2150 p4.x = annot->bbox.x0;
2151 p4.y = annot->bbox.y1;
2153 glColor3ub (0, 0, 128);
2154 stipplerect (&ctm, &p1, &p2, &p3, &p4, texcoords, vertices);
2157 glDisable (GL_BLEND);
2158 glDisable (GL_TEXTURE_1D);
2161 static int compareslinks (const void *l, const void *r)
2163 struct slink const *ls = l;
2164 struct slink const *rs = r;
2165 if (ls->bbox.y0 == rs->bbox.y0) {
2166 return rs->bbox.x0 - rs->bbox.x0;
2168 return ls->bbox.y0 - rs->bbox.y0;
2171 static void droptext (struct page *page)
2173 if (page->text) {
2174 fz_drop_stext_page (state.ctx, page->text);
2175 page->fmark.ch = NULL;
2176 page->lmark.ch = NULL;
2177 page->text = NULL;
2181 static void dropannots (struct page *page)
2183 if (page->annots) {
2184 free (page->annots);
2185 page->annots = NULL;
2186 page->annotcount = 0;
2190 static void ensureannots (struct page *page)
2192 int i, count = 0;
2193 size_t annotsize = sizeof (*page->annots);
2194 fz_annot *annot;
2196 if (state.gen != page->agen) {
2197 dropannots (page);
2198 page->agen = state.gen;
2200 if (page->annots) return;
2202 for (annot = fz_first_annot (state.ctx, page->fzpage);
2203 annot;
2204 annot = fz_next_annot (state.ctx, annot)) {
2205 count++;
2208 if (count > 0) {
2209 page->annotcount = count;
2210 page->annots = calloc (count, annotsize);
2211 if (!page->annots) {
2212 err (1, "calloc annots %d", count);
2215 for (annot = fz_first_annot (state.ctx, page->fzpage), i = 0;
2216 annot;
2217 annot = fz_next_annot (state.ctx, annot), i++) {
2218 fz_rect rect;
2220 fz_bound_annot (state.ctx, annot, &rect);
2221 page->annots[i].annot = annot;
2222 fz_round_rect (&page->annots[i].bbox, &rect);
2227 static void dropslinks (struct page *page)
2229 if (page->slinks) {
2230 free (page->slinks);
2231 page->slinks = NULL;
2232 page->slinkcount = 0;
2234 if (page->links) {
2235 fz_drop_link (state.ctx, page->links);
2236 page->links = NULL;
2240 static void ensureslinks (struct page *page)
2242 fz_matrix ctm;
2243 int i, count;
2244 size_t slinksize = sizeof (*page->slinks);
2245 fz_link *link;
2247 ensureannots (page);
2248 if (state.gen != page->sgen) {
2249 dropslinks (page);
2250 page->sgen = state.gen;
2252 if (page->slinks) return;
2254 ensurelinks (page);
2255 ctm = pagectm (page);
2257 count = page->annotcount;
2258 for (link = page->links; link; link = link->next) {
2259 count++;
2261 if (count > 0) {
2262 int j;
2264 page->slinkcount = count;
2265 page->slinks = calloc (count, slinksize);
2266 if (!page->slinks) {
2267 err (1, "calloc slinks %d", count);
2270 for (i = 0, link = page->links; link; ++i, link = link->next) {
2271 fz_rect rect;
2273 rect = link->rect;
2274 fz_transform_rect (&rect, &ctm);
2275 page->slinks[i].tag = SLINK;
2276 page->slinks[i].u.link = link;
2277 fz_round_rect (&page->slinks[i].bbox, &rect);
2279 for (j = 0; j < page->annotcount; ++j, ++i) {
2280 fz_rect rect;
2281 fz_bound_annot (state.ctx, page->annots[j].annot, &rect);
2282 fz_transform_rect (&rect, &ctm);
2283 fz_round_rect (&page->slinks[i].bbox, &rect);
2285 page->slinks[i].tag = SANNOT;
2286 page->slinks[i].u.annot = page->annots[j].annot;
2288 qsort (page->slinks, count, slinksize, compareslinks);
2292 #pragma GCC diagnostic push
2293 #pragma GCC diagnostic ignored "-Wconversion"
2294 /* slightly tweaked fmt_ulong by D.J. Bernstein */
2295 static void fmt_linkn (char *s, unsigned int u)
2297 unsigned int len; unsigned int q;
2298 unsigned int zma = 'z' - 'a' + 1;
2299 len = 1; q = u;
2300 while (q > zma - 1) { ++len; q /= zma; }
2301 if (s) {
2302 s += len;
2303 do { *--s = 'a' + (u % zma) - (u < zma && len > 1); u /= zma; } while(u);
2304 /* handles u == 0 */
2306 s[len] = 0;
2308 #pragma GCC diagnostic pop
2310 static void highlightslinks (struct page *page, int xoff, int yoff,
2311 int noff, char *targ, mlsize_t tlen, int hfsize)
2313 char buf[40];
2314 struct slink *slink;
2315 float x0, y0, x1, y1, w;
2317 ensureslinks (page);
2318 glColor3ub (0xc3, 0xb0, 0x91);
2319 for (int i = 0; i < page->slinkcount; ++i) {
2320 fmt_linkn (buf, i + noff);
2321 if (!tlen || !strncmp (targ, buf, tlen)) {
2322 slink = &page->slinks[i];
2324 x0 = slink->bbox.x0 + xoff - 5;
2325 y1 = slink->bbox.y0 + yoff - 5;
2326 y0 = y1 + 10 + hfsize;
2327 w = measure_string (state.face, hfsize, buf);
2328 x1 = x0 + w + 10;
2329 recti ((int) x0, (int) y0, (int) x1, (int) y1);
2333 glEnable (GL_BLEND);
2334 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2335 glEnable (GL_TEXTURE_2D);
2336 glColor3ub (0, 0, 0);
2337 for (int i = 0; i < page->slinkcount; ++i) {
2338 fmt_linkn (buf, i + noff);
2339 if (!tlen || !strncmp (targ, buf, tlen)) {
2340 slink = &page->slinks[i];
2342 x0 = slink->bbox.x0 + xoff;
2343 y0 = slink->bbox.y0 + yoff + hfsize;
2344 draw_string (state.face, hfsize, x0, y0, buf);
2347 glDisable (GL_TEXTURE_2D);
2348 glDisable (GL_BLEND);
2351 static void uploadslice (struct tile *tile, struct slice *slice)
2353 int offset;
2354 struct slice *slice1;
2355 unsigned char *texdata;
2357 offset = 0;
2358 for (slice1 = tile->slices; slice != slice1; slice1++) {
2359 offset += slice1->h * tile->w * tile->pixmap->n;
2361 if (slice->texindex != -1 && slice->texindex < state.texcount
2362 && state.texowners[slice->texindex].slice == slice) {
2363 glBindTexture (TEXT_TYPE, state.texids[slice->texindex]);
2365 else {
2366 int subimage = 0;
2367 int texindex = state.texindex++ % state.texcount;
2369 if (state.texowners[texindex].w == tile->w) {
2370 if (state.texowners[texindex].h >= slice->h) {
2371 subimage = 1;
2373 else {
2374 state.texowners[texindex].h = slice->h;
2377 else {
2378 state.texowners[texindex].h = slice->h;
2381 state.texowners[texindex].w = tile->w;
2382 state.texowners[texindex].slice = slice;
2383 slice->texindex = texindex;
2385 glBindTexture (TEXT_TYPE, state.texids[texindex]);
2386 #if TEXT_TYPE == GL_TEXTURE_2D
2387 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2388 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2389 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2390 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2391 #endif
2392 if (tile->pbo) {
2393 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
2394 texdata = 0;
2396 else {
2397 texdata = tile->pixmap->samples;
2399 if (subimage) {
2400 glTexSubImage2D (TEXT_TYPE,
2404 tile->w,
2405 slice->h,
2406 state.texform,
2407 state.texty,
2408 texdata+offset
2411 else {
2412 glTexImage2D (TEXT_TYPE,
2414 state.texiform,
2415 tile->w,
2416 slice->h,
2418 state.texform,
2419 state.texty,
2420 texdata+offset
2423 if (tile->pbo) {
2424 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
2429 CAMLprim void ml_begintiles (value unit_v)
2431 CAMLparam1 (unit_v);
2432 glEnable (TEXT_TYPE);
2433 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2434 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2435 CAMLreturn0;
2438 CAMLprim void ml_endtiles (value unit_v)
2440 CAMLparam1 (unit_v);
2441 glDisable (TEXT_TYPE);
2442 CAMLreturn0;
2445 CAMLprim void ml_drawtile (value args_v, value ptr_v)
2447 CAMLparam2 (args_v, ptr_v);
2448 int dispx = Int_val (Field (args_v, 0));
2449 int dispy = Int_val (Field (args_v, 1));
2450 int dispw = Int_val (Field (args_v, 2));
2451 int disph = Int_val (Field (args_v, 3));
2452 int tilex = Int_val (Field (args_v, 4));
2453 int tiley = Int_val (Field (args_v, 5));
2454 char *s = String_val (ptr_v);
2455 struct tile *tile = parse_pointer (__func__, s);
2456 int slicey, firstslice;
2457 struct slice *slice;
2458 GLfloat *texcoords = state.texcoords;
2459 GLfloat *vertices = state.vertices;
2461 firstslice = tiley / tile->sliceheight;
2462 slice = &tile->slices[firstslice];
2463 slicey = tiley % tile->sliceheight;
2465 while (disph > 0) {
2466 int dh;
2468 dh = slice->h - slicey;
2469 dh = fz_mini (disph, dh);
2470 uploadslice (tile, slice);
2472 texcoords[0] = tilex; texcoords[1] = slicey;
2473 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2474 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2475 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2477 vertices[0] = dispx; vertices[1] = dispy;
2478 vertices[2] = dispx+dispw; vertices[3] = dispy;
2479 vertices[4] = dispx; vertices[5] = dispy+dh;
2480 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2482 #if TEXT_TYPE == GL_TEXTURE_2D
2483 for (int i = 0; i < 8; ++i) {
2484 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2486 #endif
2488 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2489 dispy += dh;
2490 disph -= dh;
2491 slice++;
2492 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2493 slicey = 0;
2495 CAMLreturn0;
2498 static void drawprect (struct page *page, int xoff, int yoff, value rects_v)
2500 fz_matrix ctm, tm, pm;
2501 fz_point p1, p2, p3, p4;
2502 GLfloat *vertices = state.vertices;
2503 double *v = (double *) rects_v;
2505 xoff -= state.pagedims[page->pdimno].bounds.x0;
2506 yoff -= state.pagedims[page->pdimno].bounds.y0;
2507 fz_translate (&tm, xoff, yoff);
2508 pm = pagectm (page);
2509 fz_concat (&ctm, &pm, &tm);
2511 glEnable (GL_BLEND);
2512 glVertexPointer (2, GL_FLOAT, 0, vertices);
2514 glColor4dv (v);
2515 p1.x = (float) v[4];
2516 p1.y = (float) v[5];
2518 p2.x = (float) v[6];
2519 p2.y = (float) v[5];
2521 p3.x = (float) v[6];
2522 p3.y = (float) v[7];
2524 p4.x = (float) v[4];
2525 p4.y = (float) v[7];
2526 solidrect (&ctm, &p1, &p2, &p3, &p4, vertices);
2527 glDisable (GL_BLEND);
2530 CAMLprim value ml_postprocess (value ptr_v, value hlinks_v,
2531 value xoff_v, value yoff_v,
2532 value li_v)
2534 CAMLparam5 (ptr_v, hlinks_v, xoff_v, yoff_v, li_v);
2535 int xoff = Int_val (xoff_v);
2536 int yoff = Int_val (yoff_v);
2537 int noff = Int_val (Field (li_v, 0));
2538 char *targ = String_val (Field (li_v, 1));
2539 mlsize_t tlen = caml_string_length (Field (li_v, 1));
2540 int hfsize = Int_val (Field (li_v, 2));
2541 char *s = String_val (ptr_v);
2542 int hlmask = Int_val (hlinks_v);
2543 struct page *page = parse_pointer (__func__, s);
2545 if (!page->fzpage) {
2546 /* deal with loadpage failed pages */
2547 goto done;
2550 if (trylock (__func__)) {
2551 noff = -1;
2552 goto done;
2555 ensureannots (page);
2556 if (hlmask & 1) highlightlinks (page, xoff, yoff);
2557 if (hlmask & 2) {
2558 highlightslinks (page, xoff, yoff, noff, targ, tlen, hfsize);
2559 noff = page->slinkcount;
2561 if (page->tgen == state.gen) {
2562 showsel (page, xoff, yoff);
2564 unlock (__func__);
2566 done:
2567 CAMLreturn (Val_int (noff));
2570 CAMLprim void ml_drawprect (value ptr_v, value xoff_v, value yoff_v,
2571 value rects_v)
2573 CAMLparam4 (ptr_v, xoff_v, yoff_v, rects_v);
2574 int xoff = Int_val (xoff_v);
2575 int yoff = Int_val (yoff_v);
2576 char *s = String_val (ptr_v);
2577 struct page *page = parse_pointer (__func__, s);
2579 drawprect (page, xoff, yoff, rects_v);
2580 CAMLreturn0;
2583 static struct annot *getannot (struct page *page, int x, int y)
2585 fz_point p;
2586 fz_matrix ctm;
2587 const fz_matrix *tctm;
2588 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2590 if (!page->annots) return NULL;
2592 if (pdf) {
2593 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2594 tctm = &state.pagedims[page->pdimno].tctm;
2596 else {
2597 tctm = &fz_identity;
2600 p.x = x;
2601 p.y = y;
2603 fz_concat (&ctm, tctm, &state.pagedims[page->pdimno].ctm);
2604 fz_invert_matrix (&ctm, &ctm);
2605 fz_transform_point (&p, &ctm);
2607 if (pdf) {
2608 for (int i = 0; i < page->annotcount; ++i) {
2609 struct annot *a = &page->annots[i];
2610 fz_rect rect;
2612 fz_bound_annot (state.ctx, a->annot, &rect);
2613 if (p.x >= rect.x0 && p.x <= rect.x1) {
2614 if (p.y >= rect.y0 && p.y <= rect.y1)
2615 return a;
2619 return NULL;
2622 static fz_link *getlink (struct page *page, int x, int y)
2624 fz_point p;
2625 fz_matrix ctm;
2626 fz_link *link;
2628 ensureslinks (page);
2630 p.x = x;
2631 p.y = y;
2633 ctm = pagectm (page);
2634 fz_invert_matrix (&ctm, &ctm);
2635 fz_transform_point (&p, &ctm);
2637 for (link = page->links; link; link = link->next) {
2638 if (p.x >= link->rect.x0 && p.x <= link->rect.x1) {
2639 if (p.y >= link->rect.y0 && p.y <= link->rect.y1) {
2640 return link;
2644 return NULL;
2647 static void ensuretext (struct page *page)
2649 if (state.gen != page->tgen) {
2650 droptext (page);
2651 page->tgen = state.gen;
2653 if (!page->text) {
2654 fz_matrix ctm;
2655 fz_device *tdev;
2657 page->text = fz_new_stext_page (state.ctx,
2658 &state.pagedims[page->pdimno].mediabox);
2659 tdev = fz_new_stext_device (state.ctx, page->text, 0);
2660 ctm = pagectm (page);
2661 fz_run_display_list (state.ctx, page->dlist,
2662 tdev, &ctm, &fz_infinite_rect, NULL);
2663 fz_close_device (state.ctx, tdev);
2664 fz_drop_device (state.ctx, tdev);
2668 CAMLprim value ml_find_page_with_links (value start_page_v, value dir_v)
2670 CAMLparam2 (start_page_v, dir_v);
2671 CAMLlocal1 (ret_v);
2672 int i, dir = Int_val (dir_v);
2673 int start_page = Int_val (start_page_v);
2674 int end_page = dir > 0 ? state.pagecount : -1;
2675 pdf_document *pdf;
2677 fz_var (end_page);
2678 ret_v = Val_int (0);
2679 lock (__func__);
2680 pdf = pdf_specifics (state.ctx, state.doc);
2681 for (i = start_page + dir; i != end_page; i += dir) {
2682 int found;
2684 fz_var (found);
2685 if (pdf) {
2686 pdf_page *page = NULL;
2688 fz_var (page);
2689 fz_try (state.ctx) {
2690 page = pdf_load_page (state.ctx, pdf, i);
2691 found = !!page->links || !!page->annots;
2693 fz_catch (state.ctx) {
2694 found = 0;
2696 if (page) {
2697 fz_drop_page (state.ctx, &page->super);
2700 else {
2701 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2702 fz_link *link = fz_load_links (state.ctx, page);
2703 found = !!link;
2704 fz_drop_link (state.ctx, link);
2705 fz_drop_page (state.ctx, page);
2708 if (found) {
2709 ret_v = caml_alloc_small (1, 1);
2710 Field (ret_v, 0) = Val_int (i);
2711 goto unlock;
2714 unlock:
2715 unlock (__func__);
2716 CAMLreturn (ret_v);
2719 enum { dir_first, dir_last };
2720 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
2722 CAMLprim value ml_findlink (value ptr_v, value dir_v)
2724 CAMLparam2 (ptr_v, dir_v);
2725 CAMLlocal2 (ret_v, pos_v);
2726 struct page *page;
2727 int dirtag, i, slinkindex;
2728 struct slink *found = NULL ,*slink;
2729 char *s = String_val (ptr_v);
2731 page = parse_pointer (__func__, s);
2732 ret_v = Val_int (0);
2733 lock (__func__);
2734 ensureslinks (page);
2736 if (Is_block (dir_v)) {
2737 dirtag = Tag_val (dir_v);
2738 switch (dirtag) {
2739 case dir_first_visible:
2741 int x0, y0, dir, first_index, last_index;
2743 pos_v = Field (dir_v, 0);
2744 x0 = Int_val (Field (pos_v, 0));
2745 y0 = Int_val (Field (pos_v, 1));
2746 dir = Int_val (Field (pos_v, 2));
2748 if (dir >= 0) {
2749 dir = 1;
2750 first_index = 0;
2751 last_index = page->slinkcount;
2753 else {
2754 first_index = page->slinkcount - 1;
2755 last_index = -1;
2758 for (i = first_index; i != last_index; i += dir) {
2759 slink = &page->slinks[i];
2760 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2761 found = slink;
2762 break;
2766 break;
2768 case dir_left:
2769 slinkindex = Int_val (Field (dir_v, 0));
2770 found = &page->slinks[slinkindex];
2771 for (i = slinkindex - 1; i >= 0; --i) {
2772 slink = &page->slinks[i];
2773 if (slink->bbox.x0 < found->bbox.x0) {
2774 found = slink;
2775 break;
2778 break;
2780 case dir_right:
2781 slinkindex = Int_val (Field (dir_v, 0));
2782 found = &page->slinks[slinkindex];
2783 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2784 slink = &page->slinks[i];
2785 if (slink->bbox.x0 > found->bbox.x0) {
2786 found = slink;
2787 break;
2790 break;
2792 case dir_down:
2793 slinkindex = Int_val (Field (dir_v, 0));
2794 found = &page->slinks[slinkindex];
2795 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2796 slink = &page->slinks[i];
2797 if (slink->bbox.y0 >= found->bbox.y0) {
2798 found = slink;
2799 break;
2802 break;
2804 case dir_up:
2805 slinkindex = Int_val (Field (dir_v, 0));
2806 found = &page->slinks[slinkindex];
2807 for (i = slinkindex - 1; i >= 0; --i) {
2808 slink = &page->slinks[i];
2809 if (slink->bbox.y0 <= found->bbox.y0) {
2810 found = slink;
2811 break;
2814 break;
2817 else {
2818 dirtag = Int_val (dir_v);
2819 switch (dirtag) {
2820 case dir_first:
2821 found = page->slinks;
2822 break;
2824 case dir_last:
2825 if (page->slinks) {
2826 found = page->slinks + (page->slinkcount - 1);
2828 break;
2831 if (found) {
2832 ret_v = caml_alloc_small (2, 1);
2833 Field (ret_v, 0) = Val_int (found - page->slinks);
2836 unlock (__func__);
2837 CAMLreturn (ret_v);
2840 enum { uuri, utext, uannot };
2842 CAMLprim value ml_getlink (value ptr_v, value n_v)
2844 CAMLparam2 (ptr_v, n_v);
2845 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2846 fz_link *link;
2847 struct page *page;
2848 char *s = String_val (ptr_v);
2849 struct slink *slink;
2851 ret_v = Val_int (0);
2852 page = parse_pointer (__func__, s);
2854 lock (__func__);
2855 ensureslinks (page);
2856 slink = &page->slinks[Int_val (n_v)];
2857 if (slink->tag == SLINK) {
2858 link = slink->u.link;
2859 str_v = caml_copy_string (link->uri);
2860 ret_v = caml_alloc_small (1, uuri);
2861 Field (ret_v, 0) = str_v;
2863 else {
2864 ret_v = caml_alloc_small (1, uannot);
2865 tup_v = caml_alloc_tuple (2);
2866 Field (ret_v, 0) = tup_v;
2867 Field (tup_v, 0) = ptr_v;
2868 Field (tup_v, 1) = n_v;
2870 unlock (__func__);
2872 CAMLreturn (ret_v);
2875 CAMLprim value ml_getannotcontents (value ptr_v, value n_v)
2877 CAMLparam2 (ptr_v, n_v);
2878 CAMLlocal1 (ret_v);
2879 pdf_document *pdf;
2880 char *contents = NULL;
2882 lock (__func__);
2883 pdf = pdf_specifics (state.ctx, state.doc);
2884 if (pdf) {
2885 char *s = String_val (ptr_v);
2886 struct page *page;
2887 struct slink *slink;
2889 page = parse_pointer (__func__, s);
2890 slink = &page->slinks[Int_val (n_v)];
2891 contents = pdf_copy_annot_contents (state.ctx,
2892 (pdf_annot *) slink->u.annot);
2894 unlock (__func__);
2895 if (contents) {
2896 ret_v = caml_copy_string (contents);
2897 fz_free (state.ctx, contents);
2899 else {
2900 ret_v = caml_copy_string ("");
2902 CAMLreturn (ret_v);
2905 CAMLprim value ml_getlinkcount (value ptr_v)
2907 CAMLparam1 (ptr_v);
2908 struct page *page;
2909 char *s = String_val (ptr_v);
2911 page = parse_pointer (__func__, s);
2912 CAMLreturn (Val_int (page->slinkcount));
2915 CAMLprim value ml_getlinkrect (value ptr_v, value n_v)
2917 CAMLparam2 (ptr_v, n_v);
2918 CAMLlocal1 (ret_v);
2919 struct page *page;
2920 struct slink *slink;
2921 char *s = String_val (ptr_v);
2923 page = parse_pointer (__func__, s);
2924 ret_v = caml_alloc_tuple (4);
2925 lock (__func__);
2926 ensureslinks (page);
2928 slink = &page->slinks[Int_val (n_v)];
2929 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2930 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2931 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2932 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2933 unlock (__func__);
2934 CAMLreturn (ret_v);
2937 CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v)
2939 CAMLparam3 (ptr_v, x_v, y_v);
2940 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2941 fz_link *link;
2942 struct annot *annot;
2943 struct page *page;
2944 char *ptr = String_val (ptr_v);
2945 int x = Int_val (x_v), y = Int_val (y_v);
2946 struct pagedim *pdim;
2948 ret_v = Val_int (0);
2949 if (trylock (__func__)) {
2950 goto done;
2953 page = parse_pointer (__func__, ptr);
2954 pdim = &state.pagedims[page->pdimno];
2955 x += pdim->bounds.x0;
2956 y += pdim->bounds.y0;
2959 annot = getannot (page, x, y);
2960 if (annot) {
2961 int i, n = -1;
2963 ensureslinks (page);
2964 for (i = 0; i < page->slinkcount; ++i) {
2965 if (page->slinks[i].tag == SANNOT
2966 && page->slinks[i].u.annot == annot->annot) {
2967 n = i;
2968 break;
2971 ret_v = caml_alloc_small (1, uannot);
2972 tup_v = caml_alloc_tuple (2);
2973 Field (ret_v, 0) = tup_v;
2974 Field (tup_v, 0) = ptr_v;
2975 Field (tup_v, 1) = Val_int (n);
2976 goto unlock;
2980 link = getlink (page, x, y);
2981 if (link) {
2982 str_v = caml_copy_string (link->uri);
2983 ret_v = caml_alloc_small (1, uuri);
2984 Field (ret_v, 0) = str_v;
2986 else {
2987 fz_rect *b;
2988 fz_stext_block *block;
2990 ensuretext (page);
2992 for (block = page->text->first_block; block; block = block->next) {
2993 fz_stext_line *line;
2995 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
2996 b = &block->bbox;
2997 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
2998 continue;
3000 for (line = block->u.t.first_line; line; line = line->next) {
3001 fz_stext_char *ch;
3003 b = &line->bbox;
3004 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3005 continue;
3007 for (ch = line->first_char; ch; ch = ch->next) {
3008 b = &ch->bbox;
3010 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1) {
3011 const char *n2 = fz_font_name (state.ctx, ch->font);
3012 FT_FaceRec *face = fz_font_ft_face (state.ctx,
3013 ch->font);
3015 if (!n2) n2 = "<unknown font>";
3017 if (face && face->family_name) {
3018 char *s;
3019 char *n1 = face->family_name;
3020 size_t l1 = strlen (n1);
3021 size_t l2 = strlen (n2);
3023 if (l1 != l2 || memcmp (n1, n2, l1)) {
3024 s = malloc (l1 + l2 + 2);
3025 if (s) {
3026 memcpy (s, n2, l2);
3027 s[l2] = '=';
3028 memcpy (s + l2 + 1, n1, l1 + 1);
3029 str_v = caml_copy_string (s);
3030 free (s);
3034 if (str_v == Val_unit) {
3035 str_v = caml_copy_string (n2);
3037 ret_v = caml_alloc_small (1, utext);
3038 Field (ret_v, 0) = str_v;
3039 goto unlock;
3045 unlock:
3046 unlock (__func__);
3048 done:
3049 CAMLreturn (ret_v);
3052 enum { mark_page, mark_block, mark_line, mark_word };
3054 static int uninteresting (int c)
3056 return c == ' ' || c == '\n' || c == '\t' || c == '\n' || c == '\r'
3057 || ispunct (c);
3060 CAMLprim void ml_clearmark (value ptr_v)
3062 CAMLparam1 (ptr_v);
3063 char *s = String_val (ptr_v);
3064 struct page *page;
3066 if (trylock (__func__)) {
3067 goto done;
3070 page = parse_pointer (__func__, s);
3071 page->fmark.ch = NULL;
3072 page->lmark.ch = NULL;
3074 unlock (__func__);
3075 done:
3076 CAMLreturn0;
3079 CAMLprim value ml_markunder (value ptr_v, value x_v, value y_v, value mark_v)
3081 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
3082 CAMLlocal1 (ret_v);
3083 fz_rect *b;
3084 struct page *page;
3085 fz_stext_line *line;
3086 fz_stext_block *block;
3087 struct pagedim *pdim;
3088 int mark = Int_val (mark_v);
3089 char *s = String_val (ptr_v);
3090 int x = Int_val (x_v), y = Int_val (y_v);
3092 ret_v = Val_bool (0);
3093 if (trylock (__func__)) {
3094 goto done;
3097 page = parse_pointer (__func__, s);
3098 pdim = &state.pagedims[page->pdimno];
3100 ensuretext (page);
3102 if (mark == mark_page) {
3103 page->fmark.ch = page->text->first_block->u.t.first_line->first_char;
3104 page->lmark.ch = page->text->last_block->u.t.last_line->last_char;
3105 ret_v = Val_bool (1);
3106 goto unlock;
3109 x += pdim->bounds.x0;
3110 y += pdim->bounds.y0;
3112 for (block = page->text->first_block; block; block = block->next) {
3113 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
3114 b = &block->bbox;
3115 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3116 continue;
3118 if (mark == mark_block) {
3119 page->fmark.ch = block->u.t.first_line->first_char;
3120 page->lmark.ch = block->u.t.last_line->last_char;
3121 ret_v = Val_bool (1);
3122 goto unlock;
3125 for (line = block->u.t.first_line; line; line = line->next) {
3126 fz_stext_char *ch;
3128 b = &line->bbox;
3129 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3130 continue;
3132 if (mark == mark_line) {
3133 page->fmark.ch = line->first_char;
3134 page->lmark.ch = line->last_char;
3135 ret_v = Val_bool (1);
3136 goto unlock;
3139 for (ch = line->first_char; ch; ch = ch->next) {
3140 fz_stext_char *ch2, *first = NULL, *last = NULL;
3141 b = &ch->bbox;
3142 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1) {
3143 for (ch2 = line->first_char; ch2 != ch; ch2 = ch2->next) {
3144 if (uninteresting (ch2->c)) first = NULL;
3145 else if (!first) first = ch2;
3147 for (ch2 = ch; ch2; ch2 = ch2->next) {
3148 if (uninteresting (ch2->c)) break;
3149 last = ch2;
3152 page->fmark.ch = first;
3153 page->lmark.ch = last;
3154 ret_v = Val_bool (1);
3155 goto unlock;
3160 unlock:
3161 if (!Bool_val (ret_v)) {
3162 page->fmark.ch = NULL;
3163 page->lmark.ch = NULL;
3165 unlock (__func__);
3167 done:
3168 CAMLreturn (ret_v);
3171 CAMLprim value ml_rectofblock (value ptr_v, value x_v, value y_v)
3173 CAMLparam3 (ptr_v, x_v, y_v);
3174 CAMLlocal2 (ret_v, res_v);
3175 fz_rect *b = NULL;
3176 struct page *page;
3177 struct pagedim *pdim;
3178 fz_stext_block *block;
3179 char *s = String_val (ptr_v);
3180 int x = Int_val (x_v), y = Int_val (y_v);
3182 ret_v = Val_int (0);
3183 if (trylock (__func__)) {
3184 goto done;
3187 page = parse_pointer (__func__, s);
3188 pdim = &state.pagedims[page->pdimno];
3189 x += pdim->bounds.x0;
3190 y += pdim->bounds.y0;
3192 ensuretext (page);
3194 for (block = page->text->first_block; block; block = block->next) {
3195 switch (block->type) {
3196 case FZ_STEXT_BLOCK_TEXT:
3197 b = &block->bbox;
3198 break;
3200 case FZ_STEXT_BLOCK_IMAGE:
3201 b = &block->bbox;
3202 break;
3204 default:
3205 continue;
3208 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1)
3209 break;
3210 b = NULL;
3212 if (b) {
3213 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3214 ret_v = caml_alloc_small (1, 1);
3215 Store_double_field (res_v, 0, (double) b->x0);
3216 Store_double_field (res_v, 1, (double) b->x1);
3217 Store_double_field (res_v, 2, (double) b->y0);
3218 Store_double_field (res_v, 3, (double) b->y1);
3219 Field (ret_v, 0) = res_v;
3221 unlock (__func__);
3223 done:
3224 CAMLreturn (ret_v);
3227 CAMLprim void ml_seltext (value ptr_v, value rect_v)
3229 CAMLparam2 (ptr_v, rect_v);
3230 fz_rect b;
3231 struct page *page;
3232 struct pagedim *pdim;
3233 char *s = String_val (ptr_v);
3234 int x0, x1, y0, y1;
3235 fz_stext_char *ch;
3236 fz_stext_line *line;
3237 fz_stext_block *block;
3238 fz_stext_char *fc, *lc;
3240 if (trylock (__func__)) {
3241 goto done;
3244 page = parse_pointer (__func__, s);
3245 ensuretext (page);
3247 pdim = &state.pagedims[page->pdimno];
3248 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
3249 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
3250 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
3251 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
3253 if (y0 > y1) {
3254 int t = y0;
3255 y0 = y1;
3256 y1 = t;
3257 x0 = x1;
3258 x1 = t;
3261 fc = page->fmark.ch;
3262 lc = page->lmark.ch;
3264 for (block = page->text->first_block; block; block = block->next) {
3265 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
3266 for (line = block->u.t.first_line; line; line = line->next) {
3267 for (ch = line->first_char; ch; ch = ch->next) {
3268 b = ch->bbox;
3269 if (x0 >= b.x0 && x0 <= b.x1 && y0 >= b.y0 && y0 <= b.y1) {
3270 fc = ch;
3272 if (x1 >= b.x0 && x1 <= b.x1 && y1 >= b.y0 && y1 <= b.y1) {
3273 lc = ch;
3278 if (x1 < x0 && fc == lc) {
3279 fz_stext_char *t;
3281 t = fc;
3282 fc = lc;
3283 lc = t;
3286 page->fmark.ch = fc;
3287 page->lmark.ch = lc;
3289 unlock (__func__);
3291 done:
3292 CAMLreturn0;
3295 static int pipechar (FILE *f, fz_stext_char *ch)
3297 char buf[4];
3298 int len;
3299 size_t ret;
3301 len = fz_runetochar (buf, ch->c);
3302 ret = fwrite (buf, len, 1, f);
3303 if (ret != 1) {
3304 printd ("emsg failed to write %d bytes ret=%zu: %d:%s",
3305 len, ret, errno, strerror (errno));
3306 return -1;
3308 return 0;
3311 CAMLprim value ml_spawn (value command_v, value fds_v)
3313 CAMLparam2 (command_v, fds_v);
3314 CAMLlocal2 (l_v, tup_v);
3315 int ret, ret1;
3316 pid_t pid = (pid_t) -1;
3317 char *msg = NULL;
3318 value earg_v = Nothing;
3319 posix_spawnattr_t attr;
3320 posix_spawn_file_actions_t fa;
3321 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
3323 argv[2] = String_val (command_v);
3325 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
3326 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
3329 if ((ret = posix_spawnattr_init (&attr)) != 0) {
3330 msg = "posix_spawnattr_init";
3331 goto fail1;
3334 #ifdef POSIX_SPAWN_USEVFORK
3335 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
3336 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
3337 goto fail;
3339 #endif
3341 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
3342 int fd1, fd2;
3344 tup_v = Field (l_v, 0);
3345 fd1 = Int_val (Field (tup_v, 0));
3346 fd2 = Int_val (Field (tup_v, 1));
3347 if (fd2 < 0) {
3348 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
3349 msg = "posix_spawn_file_actions_addclose";
3350 earg_v = tup_v;
3351 goto fail;
3354 else {
3355 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
3356 msg = "posix_spawn_file_actions_adddup2";
3357 earg_v = tup_v;
3358 goto fail;
3363 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3364 msg = "posix_spawn";
3365 goto fail;
3368 fail:
3369 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3370 printd ("emsg posix_spawnattr_destroy: %d:%s", ret1, strerror (ret1));
3373 fail1:
3374 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3375 printd ("emsg posix_spawn_file_actions_destroy: %d:%s",
3376 ret1, strerror (ret1));
3379 if (msg)
3380 unix_error (ret, msg, earg_v);
3382 CAMLreturn (Val_int (pid));
3385 CAMLprim value ml_hassel (value ptr_v)
3387 CAMLparam1 (ptr_v);
3388 CAMLlocal1 (ret_v);
3389 struct page *page;
3390 char *s = String_val (ptr_v);
3392 ret_v = Val_bool (0);
3393 if (trylock (__func__)) {
3394 goto done;
3397 page = parse_pointer (__func__, s);
3398 ret_v = Val_bool (page->fmark.ch && page->lmark.ch);
3399 unlock (__func__);
3400 done:
3401 CAMLreturn (ret_v);
3404 CAMLprim void ml_copysel (value fd_v, value ptr_v)
3406 CAMLparam2 (fd_v, ptr_v);
3407 FILE *f;
3408 int seen = 0;
3409 struct page *page;
3410 fz_stext_line *line;
3411 fz_stext_block *block;
3412 int fd = Int_val (fd_v);
3413 char *s = String_val (ptr_v);
3415 if (trylock (__func__)) {
3416 goto done;
3419 page = parse_pointer (__func__, s);
3421 if (!page->fmark.ch || !page->lmark.ch) {
3422 printd ("emsg nothing to copy on page %d", page->pageno);
3423 goto unlock;
3426 f = fdopen (fd, "w");
3427 if (!f) {
3428 printd ("emsg failed to fdopen sel pipe (from fd %d): %d:%s",
3429 fd, errno, strerror (errno));
3430 f = stdout;
3433 for (block = page->text->first_block; block; block = block->next) {
3434 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
3435 for (line = block->u.t.first_line; line; line = line->next) {
3436 fz_stext_char *ch;
3437 for (ch = line->first_char; ch; ch = ch->next) {
3438 if (seen || ch == page->fmark.ch) {
3439 do {
3440 pipechar (f, ch);
3441 if (ch == page->lmark.ch) goto close;
3442 } while ((ch = ch->next));
3443 seen = 1;
3444 break;
3447 if (seen) fputc ('\n', f);
3450 close:
3451 if (f != stdout) {
3452 int ret = fclose (f);
3453 fd = -1;
3454 if (ret == -1) {
3455 if (errno != ECHILD) {
3456 printd ("emsg failed to close sel pipe: %d:%s",
3457 errno, strerror (errno));
3461 unlock:
3462 unlock (__func__);
3464 done:
3465 if (fd >= 0) {
3466 if (close (fd)) {
3467 printd ("emsg failed to close sel pipe: %d:%s",
3468 errno, strerror (errno));
3471 CAMLreturn0;
3474 CAMLprim value ml_getpdimrect (value pagedimno_v)
3476 CAMLparam1 (pagedimno_v);
3477 CAMLlocal1 (ret_v);
3478 int pagedimno = Int_val (pagedimno_v);
3479 fz_rect box;
3481 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3482 if (trylock (__func__)) {
3483 box = fz_empty_rect;
3485 else {
3486 box = state.pagedims[pagedimno].mediabox;
3487 unlock (__func__);
3490 Store_double_field (ret_v, 0, (double) box.x0);
3491 Store_double_field (ret_v, 1, (double) box.x1);
3492 Store_double_field (ret_v, 2, (double) box.y0);
3493 Store_double_field (ret_v, 3, (double) box.y1);
3495 CAMLreturn (ret_v);
3498 CAMLprim value ml_zoom_for_height (value winw_v, value winh_v,
3499 value dw_v, value cols_v)
3501 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3502 CAMLlocal1 (ret_v);
3503 int i;
3504 float zoom = -1.;
3505 float maxh = 0.0;
3506 struct pagedim *p;
3507 float winw = Int_val (winw_v);
3508 float winh = Int_val (winh_v);
3509 float dw = Int_val (dw_v);
3510 float cols = Int_val (cols_v);
3511 float pw = 1.0, ph = 1.0;
3513 if (trylock (__func__)) {
3514 goto done;
3517 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3518 float w = p->pagebox.x1 / cols;
3519 float h = p->pagebox.y1;
3520 if (h > maxh) {
3521 maxh = h;
3522 ph = h;
3523 if (state.fitmodel != FitProportional) pw = w;
3525 if ((state.fitmodel == FitProportional) && w > pw) pw = w;
3528 zoom = (((winh / ph) * pw) + dw) / winw;
3529 unlock (__func__);
3530 done:
3531 ret_v = caml_copy_double ((double) zoom);
3532 CAMLreturn (ret_v);
3535 CAMLprim value ml_getmaxw (value unit_v)
3537 CAMLparam1 (unit_v);
3538 CAMLlocal1 (ret_v);
3539 int i;
3540 float maxw = -1.;
3541 struct pagedim *p;
3543 if (trylock (__func__)) {
3544 goto done;
3547 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3548 float w = p->pagebox.x1;
3549 maxw = fz_max (maxw, w);
3552 unlock (__func__);
3553 done:
3554 ret_v = caml_copy_double ((double) maxw);
3555 CAMLreturn (ret_v);
3558 CAMLprim value ml_draw_string (value pt_v, value x_v, value y_v, value string_v)
3560 CAMLparam4 (pt_v, x_v, y_v, string_v);
3561 CAMLlocal1 (ret_v);
3562 int pt = Int_val(pt_v);
3563 int x = Int_val (x_v);
3564 int y = Int_val (y_v);
3565 double w;
3567 w = (double) draw_string (state.face, pt, x, y, String_val (string_v));
3568 ret_v = caml_copy_double (w);
3569 CAMLreturn (ret_v);
3572 CAMLprim value ml_measure_string (value pt_v, value string_v)
3574 CAMLparam2 (pt_v, string_v);
3575 CAMLlocal1 (ret_v);
3576 int pt = Int_val (pt_v);
3577 double w;
3579 w = (double) measure_string (state.face, pt, String_val (string_v));
3580 ret_v = caml_copy_double (w);
3581 CAMLreturn (ret_v);
3584 CAMLprim value ml_getpagebox (value opaque_v)
3586 CAMLparam1 (opaque_v);
3587 CAMLlocal1 (ret_v);
3588 fz_rect rect;
3589 fz_irect bbox;
3590 fz_matrix ctm;
3591 fz_device *dev;
3592 char *s = String_val (opaque_v);
3593 struct page *page = parse_pointer (__func__, s);
3595 ret_v = caml_alloc_tuple (4);
3596 dev = fz_new_bbox_device (state.ctx, &rect);
3598 ctm = pagectm (page);
3599 fz_run_page (state.ctx, page->fzpage, dev, &ctm, NULL);
3601 fz_close_device (state.ctx, dev);
3602 fz_drop_device (state.ctx, dev);
3603 fz_round_rect (&bbox, &rect);
3604 Field (ret_v, 0) = Val_int (bbox.x0);
3605 Field (ret_v, 1) = Val_int (bbox.y0);
3606 Field (ret_v, 2) = Val_int (bbox.x1);
3607 Field (ret_v, 3) = Val_int (bbox.y1);
3609 CAMLreturn (ret_v);
3612 CAMLprim void ml_setaalevel (value level_v)
3614 CAMLparam1 (level_v);
3616 state.aalevel = Int_val (level_v);
3617 CAMLreturn0;
3620 #ifndef __COCOA__
3621 #pragma GCC diagnostic push
3622 #pragma GCC diagnostic ignored "-Wvariadic-macros"
3623 #include <X11/Xlib.h>
3624 #include <X11/cursorfont.h>
3625 #pragma GCC diagnostic pop
3627 #ifdef USE_EGL
3628 #include <EGL/egl.h>
3629 #else
3630 #include <GL/glx.h>
3631 #endif
3633 static const int shapes[] = {
3634 XC_left_ptr, XC_hand2, XC_exchange, XC_fleur, XC_xterm
3637 #define CURS_COUNT (sizeof (shapes) / sizeof (shapes[0]))
3639 static struct {
3640 Window wid;
3641 Display *dpy;
3642 #ifdef USE_EGL
3643 EGLContext ctx;
3644 EGLConfig conf;
3645 EGLSurface win;
3646 EGLDisplay *edpy;
3647 #else
3648 GLXContext ctx;
3649 #endif
3650 XVisualInfo *visual;
3651 Cursor curs[CURS_COUNT];
3652 } glx;
3655 static void initcurs (void)
3657 for (size_t n = 0; n < CURS_COUNT; ++n) {
3658 glx.curs[n] = XCreateFontCursor (glx.dpy, shapes[n]);
3662 #ifdef USE_EGL
3663 CAMLprim value ml_glxinit (value display_v, value wid_v, value screen_v)
3665 CAMLparam3 (display_v, wid_v, screen_v);
3666 int major, minor;
3667 int num_conf;
3668 EGLint visid;
3669 EGLint attribs[] = {
3670 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
3671 EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
3672 EGL_NONE
3674 EGLConfig conf;
3676 glx.dpy = XOpenDisplay (String_val (display_v));
3677 if (!glx.dpy) {
3678 caml_failwith ("XOpenDisplay");
3681 eglBindAPI (EGL_OPENGL_API);
3683 glx.edpy = eglGetDisplay (glx.dpy);
3684 if (glx.edpy == EGL_NO_DISPLAY) {
3685 caml_failwith ("eglGetDisplay");
3688 if (!eglInitialize (glx.edpy, &major, &minor)) {
3689 caml_failwith ("eglInitialize");
3692 if (!eglChooseConfig (glx.edpy, attribs, &conf, 1, &num_conf) ||
3693 !num_conf) {
3694 caml_failwith ("eglChooseConfig");
3697 if (!eglGetConfigAttrib (glx.edpy, conf, EGL_NATIVE_VISUAL_ID, &visid)) {
3698 caml_failwith ("eglGetConfigAttrib");
3701 glx.conf = conf;
3702 initcurs ();
3704 glx.wid = Int_val (wid_v);
3705 CAMLreturn (Val_int (visid));
3708 CAMLprim void ml_glxcompleteinit (value unit_v)
3710 CAMLparam1 (unit_v);
3712 glx.ctx = eglCreateContext (glx.edpy, glx.conf, EGL_NO_CONTEXT, NULL);
3713 if (!glx.ctx) {
3714 caml_failwith ("eglCreateContext");
3717 glx.win = eglCreateWindowSurface (glx.edpy, glx.conf,
3718 glx.wid, NULL);
3719 if (glx.win == EGL_NO_SURFACE) {
3720 caml_failwith ("eglCreateWindowSurface");
3723 if (!eglMakeCurrent (glx.edpy, glx.win, glx.win, glx.ctx)) {
3724 glx.ctx = NULL;
3725 caml_failwith ("eglMakeCurrent");
3727 CAMLreturn0;
3729 #else
3730 CAMLprim value ml_glxinit (value display_v, value wid_v, value screen_v)
3732 CAMLparam3 (display_v, wid_v, screen_v);
3734 glx.dpy = XOpenDisplay (String_val (display_v));
3735 if (!glx.dpy) {
3736 caml_failwith ("XOpenDisplay");
3739 int attribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
3740 glx.visual = glXChooseVisual (glx.dpy, Int_val (screen_v), attribs);
3741 if (!glx.visual) {
3742 XCloseDisplay (glx.dpy);
3743 caml_failwith ("glXChooseVisual");
3746 initcurs ();
3748 glx.wid = Int_val (wid_v);
3749 CAMLreturn (Val_int (glx.visual->visualid));
3752 CAMLprim void ml_glxcompleteinit (value unit_v)
3754 CAMLparam1 (unit_v);
3756 glx.ctx = glXCreateContext (glx.dpy, glx.visual, NULL, True);
3757 if (!glx.ctx) {
3758 caml_failwith ("glXCreateContext");
3761 XFree (glx.visual);
3762 glx.visual = NULL;
3764 if (!glXMakeCurrent (glx.dpy, glx.wid, glx.ctx)) {
3765 glXDestroyContext (glx.dpy, glx.ctx);
3766 glx.ctx = NULL;
3767 caml_failwith ("glXMakeCurrent");
3769 CAMLreturn0;
3771 #endif
3773 CAMLprim void ml_setcursor (value cursor_v)
3775 CAMLparam1 (cursor_v);
3776 size_t cursn = Int_val (cursor_v);
3778 if (cursn >= CURS_COUNT) caml_failwith ("cursor index out of range");
3779 XDefineCursor (glx.dpy, glx.wid, glx.curs[cursn]);
3780 XFlush (glx.dpy);
3781 CAMLreturn0;
3784 CAMLprim void ml_swapb (value unit_v)
3786 CAMLparam1 (unit_v);
3787 #ifdef USE_EGL
3788 if (!eglSwapBuffers (glx.edpy, glx.win)) {
3789 caml_failwith ("eglSwapBuffers");
3791 #else
3792 glXSwapBuffers (glx.dpy, glx.wid);
3793 #endif
3794 CAMLreturn0;
3797 long keysym2ucs (KeySym);
3798 CAMLprim value ml_keysymtoutf8 (value keysym_v)
3800 CAMLparam1 (keysym_v);
3801 CAMLlocal1 (str_v);
3802 KeySym keysym = Int_val (keysym_v);
3803 Rune rune;
3804 int len;
3805 char buf[5];
3807 rune = (Rune) keysym2ucs (keysym);
3808 len = fz_runetochar (buf, rune);
3809 buf[len] = 0;
3810 str_v = caml_copy_string (buf);
3811 CAMLreturn (str_v);
3813 #else
3814 CAMLprim value ml_keysymtoutf8 (value keysym_v)
3816 CAMLparam1 (keysym_v);
3817 CAMLlocal1 (str_v);
3818 long ucs_v = Long_val (keysym_v);
3819 int len;
3820 char buf[5];
3822 len = fz_runetochar (buf, (int) ucs_v);
3823 buf[len] = 0;
3824 str_v = caml_copy_string (buf);
3825 CAMLreturn (str_v);
3827 #endif
3829 enum { piunknown, pilinux, piosx, pisun, pibsd };
3831 CAMLprim value ml_platform (value unit_v)
3833 CAMLparam1 (unit_v);
3834 CAMLlocal2 (tup_v, arr_v);
3835 int platid = piunknown;
3836 struct utsname buf;
3838 #if defined __linux__
3839 platid = pilinux;
3840 #elif defined __DragonFly__ || defined __FreeBSD__
3841 || defined __OpenBSD__ || defined __NetBSD__
3842 platid = pibsd;
3843 #elif defined __sun__
3844 platid = pisun;
3845 #elif defined __APPLE__
3846 platid = piosx;
3847 #endif
3848 if (uname (&buf)) err (1, "uname");
3850 tup_v = caml_alloc_tuple (2);
3852 char const *sar[] = {
3853 buf.sysname,
3854 buf.release,
3855 buf.version,
3856 buf.machine,
3857 NULL
3859 arr_v = caml_copy_string_array (sar);
3861 Field (tup_v, 0) = Val_int (platid);
3862 Field (tup_v, 1) = arr_v;
3863 CAMLreturn (tup_v);
3866 CAMLprim void ml_cloexec (value fd_v)
3868 CAMLparam1 (fd_v);
3869 int fd = Int_val (fd_v);
3871 if (fcntl (fd, F_SETFD, FD_CLOEXEC, 1)) {
3872 uerror ("fcntl", Nothing);
3874 CAMLreturn0;
3877 CAMLprim value ml_getpbo (value w_v, value h_v, value cs_v)
3879 CAMLparam2 (w_v, h_v);
3880 CAMLlocal1 (ret_v);
3881 struct bo *pbo;
3882 int w = Int_val (w_v);
3883 int h = Int_val (h_v);
3884 int cs = Int_val (cs_v);
3886 if (state.bo_usable) {
3887 pbo = calloc (sizeof (*pbo), 1);
3888 if (!pbo) {
3889 err (1, "calloc pbo");
3892 switch (cs) {
3893 case 0:
3894 case 1:
3895 pbo->size = w*h*4;
3896 break;
3897 case 2:
3898 pbo->size = w*h*2;
3899 break;
3900 default:
3901 errx (1, "%s: invalid colorspace %d", __func__, cs);
3904 state.glGenBuffersARB (1, &pbo->id);
3905 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, pbo->id);
3906 state.glBufferDataARB (GL_PIXEL_UNPACK_BUFFER_ARB, (GLsizei) pbo->size,
3907 NULL, GL_STREAM_DRAW);
3908 pbo->ptr = state.glMapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB,
3909 GL_READ_WRITE);
3910 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
3911 if (!pbo->ptr) {
3912 printd ("emsg glMapBufferARB failed: %#x", glGetError ());
3913 state.glDeleteBuffersARB (1, &pbo->id);
3914 free (pbo);
3915 ret_v = caml_copy_string ("0");
3917 else {
3918 int res;
3919 char *s;
3921 res = snprintf (NULL, 0, "%" FMT_ptr, FMT_ptr_cast (pbo));
3922 if (res < 0) {
3923 err (1, "snprintf %" FMT_ptr " failed", FMT_ptr_cast (pbo));
3925 s = malloc (res+1);
3926 if (!s) {
3927 err (1, "malloc %d bytes failed", res+1);
3929 res = sprintf (s, "%" FMT_ptr, FMT_ptr_cast (pbo));
3930 if (res < 0) {
3931 err (1, "sprintf %" FMT_ptr " failed", FMT_ptr_cast (pbo));
3933 ret_v = caml_copy_string (s);
3934 free (s);
3937 else {
3938 ret_v = caml_copy_string ("0");
3940 CAMLreturn (ret_v);
3943 CAMLprim void ml_freepbo (value s_v)
3945 CAMLparam1 (s_v);
3946 char *s = String_val (s_v);
3947 struct tile *tile = parse_pointer (__func__, s);
3949 if (tile->pbo) {
3950 state.glDeleteBuffersARB (1, &tile->pbo->id);
3951 tile->pbo->id = -1;
3952 tile->pbo->ptr = NULL;
3953 tile->pbo->size = -1;
3955 CAMLreturn0;
3958 CAMLprim void ml_unmappbo (value s_v)
3960 CAMLparam1 (s_v);
3961 char *s = String_val (s_v);
3962 struct tile *tile = parse_pointer (__func__, s);
3964 if (tile->pbo) {
3965 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
3966 if (state.glUnmapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB) == GL_FALSE) {
3967 errx (1, "glUnmapBufferARB failed: %#x\n", glGetError ());
3969 tile->pbo->ptr = NULL;
3970 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
3972 CAMLreturn0;
3975 static void setuppbo (void)
3977 #ifdef __clang__
3978 #pragma GCC diagnostic ignored "-Wunused-macros"
3979 #endif
3980 #ifdef __COCOA__
3981 static CFBundleRef framework = NULL;
3982 if (framework == NULL)
3983 framework = CFBundleGetBundleWithIdentifier (CFSTR ("com.apple.opengl"));
3984 #define GGPA(n) (&state.n = CFBundleGetFunctionPointerForName (framework, CFSTR (#n)))
3985 #else
3986 #ifdef USE_EGL
3987 #define GGPA(n) (*(void (**) (void)) &state.n = eglGetProcAddress (#n))
3988 #else
3989 #define GGPA(n) (*(void (**) (void)) &state.n = glXGetProcAddress ((GLubyte *) #n))
3990 #endif
3991 state.bo_usable = GGPA (glBindBufferARB)
3992 && GGPA (glUnmapBufferARB)
3993 && GGPA (glMapBufferARB)
3994 && GGPA (glBufferDataARB)
3995 && GGPA (glGenBuffersARB)
3996 && GGPA (glDeleteBuffersARB);
3997 #endif
3998 #undef GGPA
4001 CAMLprim value ml_bo_usable (value unit_v)
4003 CAMLparam1 (unit_v);
4004 CAMLreturn (Val_bool (state.bo_usable));
4007 CAMLprim value ml_unproject (value ptr_v, value x_v, value y_v)
4009 CAMLparam3 (ptr_v, x_v, y_v);
4010 CAMLlocal2 (ret_v, tup_v);
4011 struct page *page;
4012 char *s = String_val (ptr_v);
4013 int x = Int_val (x_v), y = Int_val (y_v);
4014 struct pagedim *pdim;
4015 fz_point p;
4016 fz_matrix ctm;
4018 page = parse_pointer (__func__, s);
4019 pdim = &state.pagedims[page->pdimno];
4021 ret_v = Val_int (0);
4022 if (trylock (__func__)) {
4023 goto done;
4026 if (pdf_specifics (state.ctx, state.doc)) {
4027 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
4028 ctm = state.pagedims[page->pdimno].tctm;
4030 else {
4031 ctm = fz_identity;
4033 p.x = x + pdim->bounds.x0;
4034 p.y = y + pdim->bounds.y0;
4036 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
4037 fz_invert_matrix (&ctm, &ctm);
4038 fz_transform_point (&p, &ctm);
4040 tup_v = caml_alloc_tuple (2);
4041 ret_v = caml_alloc_small (1, 1);
4042 Field (tup_v, 0) = Val_int (p.x);
4043 Field (tup_v, 1) = Val_int (p.y);
4044 Field (ret_v, 0) = tup_v;
4046 unlock (__func__);
4047 done:
4048 CAMLreturn (ret_v);
4051 CAMLprim value ml_project (value ptr_v, value pageno_v, value pdimno_v,
4052 value x_v, value y_v)
4054 CAMLparam5 (ptr_v, pageno_v, pdimno_v, x_v, y_v);
4055 CAMLlocal1 (ret_v);
4056 struct page *page;
4057 char *s = String_val (ptr_v);
4058 int pageno = Int_val (pageno_v);
4059 int pdimno = Int_val (pdimno_v);
4060 float x = (float) Double_val (x_v), y = (float) Double_val (y_v);
4061 struct pagedim *pdim;
4062 fz_point p;
4063 fz_matrix ctm;
4065 ret_v = Val_int (0);
4066 lock (__func__);
4068 if (!*s) {
4069 page = loadpage (pageno, pdimno);
4071 else {
4072 page = parse_pointer (__func__, s);
4074 pdim = &state.pagedims[pdimno];
4076 if (pdf_specifics (state.ctx, state.doc)) {
4077 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
4078 ctm = state.pagedims[page->pdimno].tctm;
4080 else {
4081 ctm = fz_identity;
4083 p.x = x + pdim->bounds.x0;
4084 p.y = y + pdim->bounds.y0;
4086 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
4087 fz_transform_point (&p, &ctm);
4089 ret_v = caml_alloc_tuple (2);
4090 Field (ret_v, 0) = caml_copy_double ((double) p.x);
4091 Field (ret_v, 1) = caml_copy_double ((double) p.y);
4093 if (!*s) {
4094 freepage (page);
4096 unlock (__func__);
4097 CAMLreturn (ret_v);
4100 CAMLprim void ml_addannot (value ptr_v, value x_v, value y_v,
4101 value contents_v)
4103 CAMLparam4 (ptr_v, x_v, y_v, contents_v);
4104 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4106 if (pdf) {
4107 pdf_annot *annot;
4108 struct page *page;
4109 fz_point p;
4110 char *s = String_val (ptr_v);
4112 page = parse_pointer (__func__, s);
4113 annot = pdf_create_annot (state.ctx,
4114 pdf_page_from_fz_page (state.ctx,
4115 page->fzpage),
4116 PDF_ANNOT_TEXT);
4117 p.x = Int_val (x_v);
4118 p.y = Int_val (y_v);
4119 pdf_set_annot_contents (state.ctx, annot, String_val (contents_v));
4120 pdf_set_text_annot_position (state.ctx, annot, p);
4121 state.dirty = 1;
4123 CAMLreturn0;
4126 CAMLprim void ml_delannot (value ptr_v, value n_v)
4128 CAMLparam2 (ptr_v, n_v);
4129 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4131 if (pdf) {
4132 struct page *page;
4133 char *s = String_val (ptr_v);
4134 struct slink *slink;
4136 page = parse_pointer (__func__, s);
4137 slink = &page->slinks[Int_val (n_v)];
4138 pdf_delete_annot (state.ctx,
4139 pdf_page_from_fz_page (state.ctx, page->fzpage),
4140 (pdf_annot *) slink->u.annot);
4141 state.dirty = 1;
4143 CAMLreturn0;
4146 CAMLprim void ml_modannot (value ptr_v, value n_v, value str_v)
4148 CAMLparam3 (ptr_v, n_v, str_v);
4149 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4151 if (pdf) {
4152 struct page *page;
4153 char *s = String_val (ptr_v);
4154 struct slink *slink;
4156 page = parse_pointer (__func__, s);
4157 slink = &page->slinks[Int_val (n_v)];
4158 pdf_set_annot_contents (state.ctx, (pdf_annot *) slink->u.annot,
4159 String_val (str_v));
4160 state.dirty = 1;
4162 CAMLreturn0;
4165 CAMLprim value ml_hasunsavedchanges (value unit_v)
4167 CAMLparam1 (unit_v);
4168 CAMLreturn (Val_bool (state.dirty));
4171 CAMLprim void ml_savedoc (value path_v)
4173 CAMLparam1 (path_v);
4174 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4176 if (pdf) {
4177 pdf_save_document (state.ctx, pdf, String_val (path_v), NULL);
4179 CAMLreturn0;
4182 static void makestippletex (void)
4184 const char pixels[] = "\xff\xff\0\0";
4185 glGenTextures (1, &state.stid);
4186 glBindTexture (GL_TEXTURE_1D, state.stid);
4187 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
4188 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
4189 glTexImage1D (
4190 GL_TEXTURE_1D,
4192 GL_ALPHA,
4195 GL_ALPHA,
4196 GL_UNSIGNED_BYTE,
4197 pixels
4201 CAMLprim value ml_fz_version (value UNUSED_ATTR unit_v)
4203 return caml_copy_string (FZ_VERSION);
4206 static char *ystrdup (const char *s)
4208 size_t len = strlen (s);
4209 if (len > 0) {
4210 char *r = malloc (len+1);
4211 if (!r) errx (1, "malloc %zu", len+1);
4212 memcpy (r, s, len+1);
4213 return r;
4215 return NULL;
4218 CAMLprim void ml_init (value csock_v, value params_v)
4220 CAMLparam2 (csock_v, params_v);
4221 CAMLlocal2 (trim_v, fuzz_v);
4222 int ret;
4223 int texcount;
4224 char *fontpath;
4225 int colorspace;
4226 int mustoresize;
4227 int haspboext;
4229 state.csock = Int_val (csock_v);
4230 state.rotate = Int_val (Field (params_v, 0));
4231 state.fitmodel = Int_val (Field (params_v, 1));
4232 trim_v = Field (params_v, 2);
4233 texcount = Int_val (Field (params_v, 3));
4234 state.sliceheight = Int_val (Field (params_v, 4));
4235 mustoresize = Int_val (Field (params_v, 5));
4236 colorspace = Int_val (Field (params_v, 6));
4237 fontpath = String_val (Field (params_v, 7));
4239 /* http://www.cl.cam.ac.uk/~mgk25/unicode.html */
4240 if (setlocale (LC_CTYPE, "")) {
4241 const char *cset = nl_langinfo (CODESET);
4242 state.utf8cs = !strcmp (cset, "UTF-8");
4244 else {
4245 printd ("emsg setlocale: %d:%s", errno, strerror (errno));
4248 if (caml_string_length (Field (params_v, 8)) > 0) {
4249 state.trimcachepath = ystrdup (String_val (Field (params_v, 8)));
4251 if (!state.trimcachepath) {
4252 printd ("emsg failed to strdup trimcachepath: %d:%s",
4253 errno, strerror (errno));
4257 haspboext = Bool_val (Field (params_v, 9));
4259 state.ctx = fz_new_context (NULL, NULL, mustoresize);
4260 fz_register_document_handlers (state.ctx);
4262 state.trimmargins = Bool_val (Field (trim_v, 0));
4263 fuzz_v = Field (trim_v, 1);
4264 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
4265 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
4266 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
4267 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
4269 set_tex_params (colorspace);
4271 if (*fontpath) {
4272 state.face = load_font (fontpath);
4274 else {
4275 int len;
4276 const unsigned char *data;
4278 data = pdf_lookup_substitute_font (state.ctx, 0, 0, 0, 0, &len);
4279 state.face = load_builtin_font (data, len);
4281 if (!state.face) _exit (1);
4283 realloctexts (texcount);
4285 if (haspboext) {
4286 setuppbo ();
4289 makestippletex ();
4291 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
4292 if (ret) {
4293 errx (1, "pthread_create: %s", strerror (ret));
4296 CAMLreturn0;
4299 #if FIXME || !FIXME
4300 static void OPTIMIZE_ATTR (0) UNUSED_ATTR refmacs (void) {}
4301 #endif