Bootstraping
[llpp.git] / link.c
blob804d1a4e1c53dc7747fd36b658a562d35e25e8a8
1 /* lots of code c&p-ed directly from mupdf */
2 #define _GNU_SOURCE
3 #define CAML_NAME_SPACE
4 #define FIXME 0
6 extern char **environ;
8 #include <errno.h>
9 #include <stdio.h>
10 #include <ctype.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <signal.h>
15 #include <math.h>
16 #include <wchar.h>
17 #include <locale.h>
18 #include <langinfo.h>
20 #include <unistd.h>
21 #include <pthread.h>
22 #include <sys/uio.h>
23 #include <sys/time.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <sys/types.h>
27 #include <sys/ioctl.h>
28 #include <sys/utsname.h>
30 #include <spawn.h>
32 #include <regex.h>
33 #include <stdarg.h>
34 #include <limits.h>
35 #include <inttypes.h>
37 #ifdef __COCOA__
38 #include <CoreFoundation/CoreFoundation.h>
39 #endif
41 #ifdef __APPLE__
42 #include <OpenGL/gl.h>
43 #else
44 #include <GL/gl.h>
45 #endif
47 #pragma GCC diagnostic push
48 #ifdef __clang__
49 #pragma GCC diagnostic ignored "-Wreserved-id-macro"
50 #endif
51 #pragma GCC diagnostic ignored "-Wpedantic"
52 #include <caml/fail.h>
53 #include <caml/alloc.h>
54 #include <caml/memory.h>
55 #include <caml/unixsupport.h>
57 #if __GNUC__ < 5 && !defined __clang__
58 /* At least gcc (Gentoo 4.9.3 p1.0, pie-0.6.2) 4.9.3 emits erroneous
59 clobbered diagnostics */
60 #pragma GCC diagnostic ignored "-Wclobbered"
61 #endif
63 #include <mupdf/fitz.h>
64 #include <mupdf/pdf.h>
66 #include <ft2build.h>
67 #include FT_FREETYPE_H
68 #pragma GCC diagnostic pop
70 #define PIGGYBACK
71 #define CACHE_PAGEREFS
73 #if defined __GNUC__
74 #define NORETURN_ATTR __attribute__ ((noreturn))
75 #define UNUSED_ATTR __attribute__ ((unused))
76 #if !defined __clang__
77 #define OPTIMIZE_ATTR(n) __attribute__ ((optimize ("O"#n)))
78 #else
79 #define OPTIMIZE_ATTR(n)
80 #endif
81 #define GCC_FMT_ATTR(a, b) __attribute__ ((format (printf, a, b)))
82 #else
83 #define NORETURN_ATTR
84 #define UNUSED_ATTR
85 #define OPTIMIZE_ATTR(n)
86 #define GCC_FMT_ATTR(a, b)
87 #endif
89 #define FMT_s "zu"
91 #define FMT_ptr PRIxPTR
92 #define SCN_ptr SCNxPTR
93 #define FMT_ptr_cast(p) ((uintptr_t) (p))
94 #define SCN_ptr_cast(p) ((uintptr_t *) (p))
96 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
97 err (int exitcode, const char *fmt, ...)
99 va_list ap;
100 int savederrno;
102 savederrno = errno;
103 va_start (ap, fmt);
104 vfprintf (stderr, fmt, ap);
105 va_end (ap);
106 fprintf (stderr, ": %s\n", strerror (savederrno));
107 fflush (stderr);
108 _exit (exitcode);
111 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
112 errx (int exitcode, const char *fmt, ...)
114 va_list ap;
116 va_start (ap, fmt);
117 vfprintf (stderr, fmt, ap);
118 va_end (ap);
119 fputc ('\n', stderr);
120 fflush (stderr);
121 _exit (exitcode);
124 #ifndef GL_TEXTURE_RECTANGLE_ARB
125 #define GL_TEXTURE_RECTANGLE_ARB 0x84F5
126 #endif
128 #ifdef USE_NPOT
129 #define TEXT_TYPE GL_TEXTURE_2D
130 #else
131 #define TEXT_TYPE GL_TEXTURE_RECTANGLE_ARB
132 #endif
134 #ifndef GL_BGRA
135 #define GL_BGRA 0x80E1
136 #endif
138 #ifndef GL_UNSIGNED_INT_8_8_8_8
139 #define GL_UNSIGNED_INT_8_8_8_8 0x8035
140 #endif
142 #ifndef GL_UNSIGNED_INT_8_8_8_8_REV
143 #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
144 #endif
146 #if 0
147 #define lprintf printf
148 #else
149 #define lprintf(...)
150 #endif
152 #define ARSERT(cond) for (;;) { \
153 if (!(cond)) { \
154 errx (1, "%s:%d " #cond, __FILE__, __LINE__); \
156 break; \
159 struct slice {
160 int h;
161 int texindex;
164 struct tile {
165 int w, h;
166 int slicecount;
167 int sliceheight;
168 struct bo *pbo;
169 fz_pixmap *pixmap;
170 struct slice slices[1];
173 struct pagedim {
174 int pageno;
175 int rotate;
176 int left;
177 int tctmready;
178 fz_irect bounds;
179 fz_rect pagebox;
180 fz_rect mediabox;
181 fz_matrix ctm, zoomctm, tctm;
184 struct slink {
185 enum { SLINK, SANNOT } tag;
186 fz_irect bbox;
187 union {
188 fz_link *link;
189 fz_annot *annot;
190 } u;
193 struct annot {
194 fz_irect bbox;
195 fz_annot *annot;
198 struct page {
199 int tgen;
200 int sgen;
201 int agen;
202 int pageno;
203 int pdimno;
204 fz_stext_page *text;
205 fz_page *fzpage;
206 fz_display_list *dlist;
207 fz_link *links;
208 int slinkcount;
209 struct slink *slinks;
210 int annotcount;
211 struct annot *annots;
212 struct mark {
213 fz_stext_char *ch;
214 } fmark, lmark;
217 enum { FitWidth, FitProportional, FitPage };
219 static struct {
220 int sliceheight;
221 struct pagedim *pagedims;
222 int pagecount;
223 int pagedimcount;
224 fz_document *doc;
225 fz_context *ctx;
226 int w, h;
228 int texindex;
229 int texcount;
230 GLuint *texids;
232 GLenum texiform;
233 GLenum texform;
234 GLenum texty;
236 fz_colorspace *colorspace;
238 struct {
239 int w, h;
240 struct slice *slice;
241 } *texowners;
243 int rotate;
244 int fitmodel;
245 int trimmargins;
246 int needoutline;
247 int gen;
248 int aalevel;
250 int trimanew;
251 fz_irect trimfuzz;
252 fz_pixmap *pig;
254 pthread_t thread;
255 int csock;
256 FT_Face face;
258 char *trimcachepath;
259 int dirty;
261 GLuint stid;
263 int bo_usable;
264 GLuint boid;
266 void (*glBindBufferARB) (GLenum, GLuint);
267 GLboolean (*glUnmapBufferARB) (GLenum);
268 void *(*glMapBufferARB) (GLenum, GLenum);
269 void (*glBufferDataARB) (GLenum, GLsizei, void *, GLenum);
270 void (*glGenBuffersARB) (GLsizei, GLuint *);
271 void (*glDeleteBuffersARB) (GLsizei, GLuint *);
273 GLfloat texcoords[8];
274 GLfloat vertices[16];
276 #ifdef CACHE_PAGEREFS
277 struct {
278 int idx;
279 int count;
280 pdf_obj **objs;
281 pdf_document *pdf;
282 } pdflut;
283 #endif
284 int utf8cs;
285 } state;
287 struct bo {
288 GLuint id;
289 void *ptr;
290 size_t size;
293 #pragma GCC diagnostic ignored "-Wdouble-promotion"
294 static void UNUSED_ATTR debug_rect (const char *cap, fz_rect r)
296 printf ("%s(rect) %.2f,%.2f,%.2f,%.2f\n", cap, r.x0, r.y0, r.x1, r.y1);
299 static void UNUSED_ATTR debug_bbox (const char *cap, fz_irect r)
301 printf ("%s(bbox) %d,%d,%d,%d\n", cap, r.x0, r.y0, r.x1, r.y1);
304 static void UNUSED_ATTR debug_matrix (const char *cap, fz_matrix m)
306 printf ("%s(matrix) %.2f,%.2f,%.2f,%.2f %.2f %.2f\n", cap,
307 m.a, m.b, m.c, m.d, m.e, m.f);
309 #pragma GCC diagnostic error "-Wdouble-promotion"
311 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
313 static void lock (const char *cap)
315 int ret = pthread_mutex_lock (&mutex);
316 if (ret) {
317 errx (1, "%s: pthread_mutex_lock: %s", cap, strerror (ret));
321 static void unlock (const char *cap)
323 int ret = pthread_mutex_unlock (&mutex);
324 if (ret) {
325 errx (1, "%s: pthread_mutex_unlock: %s", cap, strerror (ret));
329 static int trylock (const char *cap)
331 int ret = pthread_mutex_trylock (&mutex);
332 if (ret && ret != EBUSY) {
333 errx (1, "%s: pthread_mutex_trylock: %s", cap, strerror (ret));
335 return ret == EBUSY;
338 static void *parse_pointer (const char *cap, const char *s)
340 int ret;
341 void *ptr;
343 ret = sscanf (s, "%" SCN_ptr, SCN_ptr_cast (&ptr));
344 if (ret != 1) {
345 errx (1, "%s: cannot parse pointer in `%s'", cap, s);
347 return ptr;
350 static double now (void)
352 struct timeval tv;
354 if (gettimeofday (&tv, NULL)) {
355 err (1, "gettimeofday");
357 return tv.tv_sec + tv.tv_usec*1e-6;
360 static int hasdata (void)
362 int ret, avail;
363 ret = ioctl (state.csock, FIONREAD, &avail);
364 if (ret) err (1, "hasdata: FIONREAD error ret=%d", ret);
365 return avail > 0;
368 CAMLprim value ml_hasdata (value fd_v)
370 CAMLparam1 (fd_v);
371 int ret, avail;
373 ret = ioctl (Int_val (fd_v), FIONREAD, &avail);
374 if (ret) uerror ("ioctl (FIONREAD)", Nothing);
375 CAMLreturn (Val_bool (avail > 0));
378 static void readdata (int fd, void *p, int size)
380 ssize_t n;
382 again:
383 n = read (fd, p, size);
384 if (n - size) {
385 if (n < 0 && errno == EINTR) goto again;
386 if (!n) errx (1, "EOF while reading");
387 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
391 static void writedata (int fd, char *p, int size)
393 ssize_t n;
394 uint32_t size4 = size;
395 struct iovec iov[2] = {
396 { .iov_base = &size4, .iov_len = 4 },
397 { .iov_base = p, .iov_len = size }
400 again:
401 n = writev (fd, iov, 2);
402 if (n < 0 && errno == EINTR) goto again;
403 if (n - size - 4) {
404 if (!n) errx (1, "EOF while writing data");
405 err (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
409 static int readlen (int fd)
411 uint32_t u;
412 readdata (fd, &u, 4);
413 return u;
416 CAMLprim void ml_wcmd (value fd_v, value bytes_v, value len_v)
418 CAMLparam3 (fd_v, bytes_v, len_v);
419 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
420 CAMLreturn0;
423 CAMLprim value ml_rcmd (value fd_v)
425 CAMLparam1 (fd_v);
426 CAMLlocal1 (strdata_v);
427 int fd = Int_val (fd_v);
428 int len = readlen (fd);
429 strdata_v = caml_alloc_string (len);
430 readdata (fd, String_val (strdata_v), len);
431 CAMLreturn (strdata_v);
434 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
436 char fbuf[64];
437 int size = sizeof (fbuf), len;
438 va_list ap;
439 char *buf = fbuf;
441 for (;;) {
442 va_start (ap, fmt);
443 len = vsnprintf (buf, size, fmt, ap);
444 va_end (ap);
446 if (len > -1) {
447 if (len < size - 4) {
448 writedata (state.csock, buf, len);
449 break;
451 else size = len + 5;
453 else {
454 err (1, "vsnprintf for `%s' failed", fmt);
456 buf = realloc (buf == fbuf ? NULL : buf, size);
457 if (!buf) err (1, "realloc for temp buf (%d bytes) failed", size);
459 if (buf != fbuf) free (buf);
462 static void closedoc (void)
464 #ifdef CACHE_PAGEREFS
465 if (state.pdflut.objs) {
466 for (int i = 0; i < state.pdflut.count; ++i) {
467 pdf_drop_obj (state.ctx, state.pdflut.objs[i]);
469 free (state.pdflut.objs);
470 state.pdflut.objs = NULL;
471 state.pdflut.idx = 0;
473 #endif
474 if (state.doc) {
475 fz_drop_document (state.ctx, state.doc);
476 state.doc = NULL;
480 static int openxref (char *filename, char *password, int layouth)
482 for (int i = 0; i < state.texcount; ++i) {
483 state.texowners[i].w = -1;
484 state.texowners[i].slice = NULL;
487 closedoc ();
489 state.dirty = 0;
490 if (state.pagedims) {
491 free (state.pagedims);
492 state.pagedims = NULL;
494 state.pagedimcount = 0;
496 fz_set_aa_level (state.ctx, state.aalevel);
497 state.doc = fz_open_document (state.ctx, filename);
498 if (fz_needs_password (state.ctx, state.doc)) {
499 if (password && !*password) {
500 printd ("pass");
501 return 0;
503 else {
504 int ok = fz_authenticate_password (state.ctx, state.doc, password);
505 if (!ok) {
506 printd ("pass fail");
507 return 0;
511 if (layouth >= 0)
512 fz_layout_document (state.ctx, state.doc, 460, layouth, 12);
513 state.pagecount = fz_count_pages (state.ctx, state.doc);
514 return 1;
517 static void pdfinfo (void)
519 struct { char *tag; char *name; } metatbl[] = {
520 { FZ_META_INFO_TITLE, "Title" },
521 { FZ_META_INFO_AUTHOR, "Author" },
522 { FZ_META_FORMAT, "Format" },
523 { FZ_META_ENCRYPTION, "Encryption" },
524 { "info:Creator", "Creator" },
525 { "info:Producer", "Producer" },
526 { "info:CreationDate", "Creation date" },
528 int len = 0;
529 char *buf = NULL;
531 for (size_t i = 0; i < sizeof (metatbl) / sizeof (metatbl[1]); ++i) {
532 int need;
533 again:
534 need = fz_lookup_metadata (state.ctx, state.doc,
535 metatbl[i].tag, buf, len);
536 if (need > 0) {
537 if (need <= len) {
538 printd ("info %s\t%s", metatbl[i].name, buf);
540 else {
541 buf = realloc (buf, need + 1);
542 if (!buf) err (1, "pdfinfo realloc %d", need + 1);
543 len = need + 1;
544 goto again;
548 free (buf);
550 printd ("infoend");
553 static void unlinktile (struct tile *tile)
555 for (int i = 0; i < tile->slicecount; ++i) {
556 struct slice *s = &tile->slices[i];
558 if (s->texindex != -1) {
559 if (state.texowners[s->texindex].slice == s) {
560 state.texowners[s->texindex].slice = NULL;
566 static void freepage (struct page *page)
568 if (!page) return;
569 if (page->text) {
570 fz_drop_stext_page (state.ctx, page->text);
572 if (page->slinks) {
573 free (page->slinks);
575 fz_drop_display_list (state.ctx, page->dlist);
576 fz_drop_page (state.ctx, page->fzpage);
577 free (page);
580 static void freetile (struct tile *tile)
582 unlinktile (tile);
583 if (!tile->pbo) {
584 #ifndef PIGGYBACK
585 fz_drop_pixmap (state.ctx, tile->pixmap);
586 #else
587 if (state.pig) {
588 fz_drop_pixmap (state.ctx, state.pig);
590 state.pig = tile->pixmap;
591 #endif
593 else {
594 free (tile->pbo);
595 fz_drop_pixmap (state.ctx, tile->pixmap);
597 free (tile);
600 #ifdef __ALTIVEC__
601 #include <stdint.h>
602 #include <altivec.h>
604 static int cacheline32bytes;
606 static void __attribute__ ((constructor)) clcheck (void)
608 char **envp = environ;
609 unsigned long *auxv;
611 while (*envp++);
613 for (auxv = (unsigned long *) envp; *auxv != 0; auxv += 2) {
614 if (*auxv == 19) {
615 cacheline32bytes = auxv[1] == 32;
616 return;
621 static void OPTIMIZE_ATTR (3) clearpixmap (fz_pixmap *pixmap)
623 size_t size = pixmap->w * pixmap->h * pixmap->n;
624 if (cacheline32bytes && size > 32) {
625 intptr_t a1, a2, diff;
626 size_t sizea, i;
627 vector unsigned char v = vec_splat_u8 (-1);
628 vector unsigned char *p;
630 a1 = a2 = (intptr_t) pixmap->samples;
631 a2 = (a1 + 31) & ~31;
632 diff = a2 - a1;
633 sizea = size - diff;
634 p = (void *) a2;
636 while (a1 != a2) *(char *) a1++ = 0xff;
637 for (i = 0; i < (sizea & ~31); i += 32) {
638 __asm volatile ("dcbz %0, %1"::"b"(a2),"r"(i));
639 vec_st (v, i, p);
640 vec_st (v, i + 16, p);
642 while (i < sizea) *((char *) a1 + i++) = 0xff;
644 else fz_clear_pixmap_with_value (state.ctx, pixmap, 0xff);
646 #else
647 #define clearpixmap(p) fz_clear_pixmap_with_value (state.ctx, p, 0xff)
648 #endif
650 static void trimctm (pdf_page *page, int pindex)
652 fz_matrix ctm;
653 struct pagedim *pdim = &state.pagedims[pindex];
655 if (!page) return;
656 if (!pdim->tctmready) {
657 fz_rect realbox, mediabox;
658 fz_matrix rm, sm, tm, im, ctm1, page_ctm;
660 fz_rotate (&rm, -pdim->rotate);
661 fz_scale (&sm, 1, -1);
662 fz_concat (&ctm, &rm, &sm);
663 realbox = pdim->mediabox;
664 fz_transform_rect (&realbox, &ctm);
665 fz_translate (&tm, -realbox.x0, -realbox.y0);
666 fz_concat (&ctm1, &ctm, &tm);
667 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
668 fz_invert_matrix (&im, &page_ctm);
669 fz_concat (&ctm, &im, &ctm1);
670 pdim->tctm = ctm;
671 pdim->tctmready = 1;
675 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
677 fz_matrix ctm, tm;
678 ptrdiff_t pdimno = pdim - state.pagedims;
680 ARSERT (pdim - state.pagedims < INT_MAX);
681 if (pdf_specifics (state.ctx, state.doc)) {
682 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), (int) pdimno);
683 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
685 else {
686 fz_translate (&tm, -pdim->mediabox.x0, -pdim->mediabox.y0);
687 fz_concat (&ctm, &tm, &pdim->ctm);
689 return ctm;
692 static fz_matrix pagectm (struct page *page)
694 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
697 static void *loadpage (int pageno, int pindex)
699 fz_device *dev;
700 struct page *page;
702 page = calloc (sizeof (struct page), 1);
703 if (!page) {
704 err (1, "calloc page %d", pageno);
707 page->dlist = fz_new_display_list (state.ctx, NULL);
708 dev = fz_new_list_device (state.ctx, page->dlist);
709 fz_try (state.ctx) {
710 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
711 fz_run_page (state.ctx, page->fzpage, dev,
712 &fz_identity, NULL);
714 fz_catch (state.ctx) {
715 page->fzpage = NULL;
717 fz_close_device (state.ctx, dev);
718 fz_drop_device (state.ctx, dev);
720 page->pdimno = pindex;
721 page->pageno = pageno;
722 page->sgen = state.gen;
723 page->agen = state.gen;
724 page->tgen = state.gen;
725 return page;
728 static struct tile *alloctile (int h)
730 int slicecount;
731 size_t tilesize;
732 struct tile *tile;
734 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
735 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
736 tile = calloc (tilesize, 1);
737 if (!tile) {
738 err (1, "cannot allocate tile (%" FMT_s " bytes)", tilesize);
740 for (int i = 0; i < slicecount; ++i) {
741 int sh = fz_mini (h, state.sliceheight);
742 tile->slices[i].h = sh;
743 tile->slices[i].texindex = -1;
744 h -= sh;
746 tile->slicecount = slicecount;
747 tile->sliceheight = state.sliceheight;
748 return tile;
751 static struct tile *rendertile (struct page *page, int x, int y, int w, int h,
752 struct bo *pbo)
754 fz_rect rect;
755 fz_irect bbox;
756 fz_matrix ctm;
757 fz_device *dev;
758 struct tile *tile;
759 struct pagedim *pdim;
761 tile = alloctile (h);
762 pdim = &state.pagedims[page->pdimno];
764 bbox = pdim->bounds;
765 bbox.x0 += x;
766 bbox.y0 += y;
767 bbox.x1 = bbox.x0 + w;
768 bbox.y1 = bbox.y0 + h;
770 if (state.pig) {
771 if (state.pig->w == w
772 && state.pig->h == h
773 && state.pig->colorspace == state.colorspace) {
774 tile->pixmap = state.pig;
775 tile->pixmap->x = bbox.x0;
776 tile->pixmap->y = bbox.y0;
778 else {
779 fz_drop_pixmap (state.ctx, state.pig);
781 state.pig = NULL;
783 if (!tile->pixmap) {
784 if (pbo) {
785 tile->pixmap =
786 fz_new_pixmap_with_bbox_and_data (state.ctx, state.colorspace,
787 &bbox, NULL, 1, pbo->ptr);
788 tile->pbo = pbo;
790 else {
791 tile->pixmap =
792 fz_new_pixmap_with_bbox (state.ctx, state.colorspace, &bbox,
793 NULL, 1);
797 tile->w = w;
798 tile->h = h;
799 clearpixmap (tile->pixmap);
801 dev = fz_new_draw_device (state.ctx, NULL, tile->pixmap);
802 ctm = pagectm (page);
803 fz_rect_from_irect (&rect, &bbox);
804 fz_run_display_list (state.ctx, page->dlist, dev, &ctm, &rect, NULL);
805 fz_close_device (state.ctx, dev);
806 fz_drop_device (state.ctx, dev);
808 return tile;
811 #ifdef CACHE_PAGEREFS
812 /* modified mupdf/source/pdf/pdf-page.c:pdf_lookup_page_loc_imp
813 thanks to Robin Watts */
814 static void
815 pdf_collect_pages(pdf_document *doc, pdf_obj *node)
817 fz_context *ctx = state.ctx; /* doc->ctx; */
818 pdf_obj *kids;
819 int len;
821 if (state.pdflut.idx == state.pagecount) return;
823 kids = pdf_dict_gets (ctx, node, "Kids");
824 len = pdf_array_len (ctx, kids);
826 if (len == 0)
827 fz_throw (ctx, FZ_ERROR_GENERIC, "malformed pages tree");
829 if (pdf_mark_obj (ctx, node))
830 fz_throw (ctx, FZ_ERROR_GENERIC, "cycle in page tree");
831 for (int i = 0; i < len; i++) {
832 pdf_obj *kid = pdf_array_get (ctx, kids, i);
833 const char *type = pdf_to_name (ctx, pdf_dict_gets (ctx, kid, "Type"));
834 if (*type
835 ? !strcmp (type, "Pages")
836 : pdf_dict_gets (ctx, kid, "Kids")
837 && !pdf_dict_gets (ctx, kid, "MediaBox")) {
838 pdf_collect_pages (doc, kid);
840 else {
841 if (*type
842 ? strcmp (type, "Page") != 0
843 : !pdf_dict_gets (ctx, kid, "MediaBox"))
844 fz_warn (ctx, "non-page object in page tree (%s)", type);
845 state.pdflut.objs[state.pdflut.idx++] = pdf_keep_obj (ctx, kid);
848 pdf_unmark_obj (ctx, node);
851 static void
852 pdf_load_page_objs (pdf_document *doc)
854 pdf_obj *root = pdf_dict_gets (state.ctx,
855 pdf_trailer (state.ctx, doc), "Root");
856 pdf_obj *node = pdf_dict_gets (state.ctx, root, "Pages");
858 if (!node)
859 fz_throw (state.ctx, FZ_ERROR_GENERIC, "cannot find page tree");
861 state.pdflut.idx = 0;
862 pdf_collect_pages (doc, node);
864 #endif
866 static void initpdims (void)
868 double start, end;
869 FILE *trimf = NULL;
870 fz_rect rootmediabox = fz_empty_rect;
871 int pageno, trim, show;
872 int trimw = 0, cxcount;
873 fz_context *ctx = state.ctx;
874 pdf_document *pdf = pdf_specifics (ctx, state.doc);
876 fz_var (trimw);
877 fz_var (trimf);
878 fz_var (cxcount);
879 start = now ();
881 if (state.trimmargins && state.trimcachepath) {
882 trimf = fopen (state.trimcachepath, "rb");
883 if (!trimf) {
884 trimf = fopen (state.trimcachepath, "wb");
885 trimw = 1;
889 if (state.trimmargins || pdf)
890 cxcount = state.pagecount;
891 else
892 cxcount = fz_mini (state.pagecount, 1);
894 if (pdf) {
895 pdf_obj *obj;
896 obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
897 "Root/Pages/MediaBox");
898 pdf_to_rect (ctx, obj, &rootmediabox);
901 #ifdef CACHE_PAGEREFS
902 if (pdf && (!state.pdflut.objs || state.pdflut.pdf != pdf)) {
903 state.pdflut.objs = calloc (sizeof (*state.pdflut.objs), cxcount);
904 if (!state.pdflut.objs) {
905 err (1, "malloc pageobjs %zu %d %zu failed",
906 sizeof (*state.pdflut.objs), cxcount,
907 sizeof (*state.pdflut.objs) * cxcount);
909 state.pdflut.count = cxcount;
910 pdf_load_page_objs (pdf);
911 state.pdflut.pdf = pdf;
913 #endif
915 for (pageno = 0; pageno < cxcount; ++pageno) {
916 int rotate = 0;
917 struct pagedim *p;
918 fz_rect mediabox = fz_empty_rect;
920 fz_var (rotate);
921 if (pdf) {
922 pdf_obj *pageref, *pageobj;
924 #ifdef CACHE_PAGEREFS
925 pageref = state.pdflut.objs[pageno];
926 #else
927 pageref = pdf_lookup_page_obj (ctx, pdf, pageno);
928 #endif
929 pageobj = pdf_resolve_indirect (ctx, pageref);
930 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
932 if (state.trimmargins) {
933 pdf_obj *obj;
934 pdf_page *page;
936 fz_try (ctx) {
937 page = pdf_load_page (ctx, pdf, pageno);
938 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
939 trim = state.trimanew || !obj;
940 if (trim) {
941 fz_rect rect;
942 fz_device *dev;
943 fz_matrix ctm, page_ctm;
945 dev = fz_new_bbox_device (ctx, &rect);
946 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
947 fz_invert_matrix (&ctm, &page_ctm);
948 pdf_run_page (ctx, page, dev, &fz_identity, NULL);
949 fz_close_device (ctx, dev);
950 fz_drop_device (ctx, dev);
952 rect.x0 += state.trimfuzz.x0;
953 rect.x1 += state.trimfuzz.x1;
954 rect.y0 += state.trimfuzz.y0;
955 rect.y1 += state.trimfuzz.y1;
956 fz_transform_rect (&rect, &ctm);
957 fz_intersect_rect (&rect, &mediabox);
959 if (!fz_is_empty_rect (&rect)) {
960 mediabox = rect;
963 obj = pdf_new_array (ctx, pdf, 4);
964 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
965 mediabox.x0));
966 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
967 mediabox.y0));
968 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
969 mediabox.x1));
970 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
971 mediabox.y1));
972 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
974 else {
975 mediabox.x0 = pdf_to_real (ctx,
976 pdf_array_get (ctx, obj, 0));
977 mediabox.y0 = pdf_to_real (ctx,
978 pdf_array_get (ctx, obj, 1));
979 mediabox.x1 = pdf_to_real (ctx,
980 pdf_array_get (ctx, obj, 2));
981 mediabox.y1 = pdf_to_real (ctx,
982 pdf_array_get (ctx, obj, 3));
985 fz_drop_page (ctx, &page->super);
986 show = trim ? pageno % 5 == 0 : pageno % 20 == 0;
987 if (show) {
988 printd ("progress %f Trimming %d",
989 (double) (pageno + 1) / state.pagecount,
990 pageno + 1);
993 fz_catch (ctx) {
994 printd ("emsg failed to load page %d", pageno);
997 else {
998 int empty = 0;
999 fz_rect cropbox;
1001 pdf_to_rect (ctx,
1002 pdf_dict_gets (ctx, pageobj, "MediaBox"),
1003 &mediabox);
1004 if (fz_is_empty_rect (&mediabox)) {
1005 mediabox.x0 = 0;
1006 mediabox.y0 = 0;
1007 mediabox.x1 = 612;
1008 mediabox.y1 = 792;
1009 empty = 1;
1012 pdf_to_rect (ctx,
1013 pdf_dict_gets (ctx, pageobj, "CropBox"),
1014 &cropbox);
1015 if (!fz_is_empty_rect (&cropbox)) {
1016 if (empty) {
1017 mediabox = cropbox;
1019 else {
1020 fz_intersect_rect (&mediabox, &cropbox);
1023 else {
1024 if (empty) {
1025 if (fz_is_empty_rect (&rootmediabox)) {
1026 printd ("emsg cannot find page size for page %d",
1027 pageno);
1029 else {
1030 mediabox = rootmediabox;
1036 else {
1037 if (state.trimmargins && trimw) {
1038 fz_page *page;
1040 fz_try (ctx) {
1041 page = fz_load_page (ctx, state.doc, pageno);
1042 fz_bound_page (ctx, page, &mediabox);
1043 if (state.trimmargins) {
1044 fz_rect rect;
1045 fz_device *dev;
1047 dev = fz_new_bbox_device (ctx, &rect);
1048 fz_run_page (ctx, page, dev, &fz_identity, NULL);
1049 fz_close_device (ctx, dev);
1050 fz_drop_device (ctx, dev);
1052 rect.x0 += state.trimfuzz.x0;
1053 rect.x1 += state.trimfuzz.x1;
1054 rect.y0 += state.trimfuzz.y0;
1055 rect.y1 += state.trimfuzz.y1;
1056 fz_intersect_rect (&rect, &mediabox);
1058 if (!fz_is_empty_rect (&rect)) {
1059 mediabox = rect;
1062 fz_drop_page (ctx, page);
1064 fz_catch (ctx) {
1066 if (trimf) {
1067 size_t n = fwrite (&mediabox, sizeof (mediabox), 1, trimf);
1068 if (n - 1) {
1069 err (1, "fwrite trim mediabox");
1073 else {
1074 if (trimf) {
1075 size_t n = fread (&mediabox, sizeof (mediabox), 1, trimf);
1076 if (n - 1) {
1077 err (1, "fread trim mediabox %d", pageno);
1080 else {
1081 fz_page *page;
1082 fz_try (ctx) {
1083 page = fz_load_page (ctx, state.doc, pageno);
1084 fz_bound_page (ctx, page, &mediabox);
1085 fz_drop_page (ctx, page);
1087 show = !state.trimmargins && pageno % 20 == 0;
1088 if (show) {
1089 printd ("progress %f Gathering dimensions %d",
1090 (double) (pageno) / state.pagecount,
1091 pageno);
1094 fz_catch (ctx) {
1095 printd ("emsg failed to load page %d", pageno);
1101 if (state.pagedimcount == 0
1102 || ((void) (p = &state.pagedims[state.pagedimcount-1])
1103 , p->rotate != rotate)
1104 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
1105 size_t size;
1107 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
1108 state.pagedims = realloc (state.pagedims, size);
1109 if (!state.pagedims) {
1110 err (1, "realloc pagedims to %" FMT_s " (%d elems)",
1111 size, state.pagedimcount + 1);
1114 p = &state.pagedims[state.pagedimcount++];
1115 p->rotate = rotate;
1116 p->mediabox = mediabox;
1117 p->pageno = pageno;
1120 end = now ();
1121 printd ("progress 1 %s %d pages in %f seconds",
1122 state.trimmargins ? "Trimmed" : "Processed",
1123 state.pagecount, end - start);
1124 state.trimanew = 0;
1125 if (trimf) {
1126 if (fclose (trimf)) {
1127 err (1, "fclose");
1132 static void layout (void)
1134 int pindex;
1135 fz_rect box;
1136 fz_matrix ctm, rm;
1137 struct pagedim *p = NULL;
1138 float zw, w, maxw = 0.0, zoom = 1.0;
1140 if (state.pagedimcount == 0) return;
1142 switch (state.fitmodel) {
1143 case FitProportional:
1144 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1145 float x0, x1;
1147 p = &state.pagedims[pindex];
1148 fz_rotate (&rm, p->rotate + state.rotate);
1149 box = p->mediabox;
1150 fz_transform_rect (&box, &rm);
1152 x0 = fz_min (box.x0, box.x1);
1153 x1 = fz_max (box.x0, box.x1);
1155 w = x1 - x0;
1156 maxw = fz_max (w, maxw);
1157 zoom = state.w / maxw;
1159 break;
1161 case FitPage:
1162 maxw = state.w;
1163 break;
1165 case FitWidth:
1166 break;
1168 default:
1169 ARSERT (0 && state.fitmodel);
1172 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1173 fz_rect rect;
1174 fz_matrix tm, sm;
1176 p = &state.pagedims[pindex];
1177 fz_rotate (&ctm, state.rotate);
1178 fz_rotate (&rm, p->rotate + state.rotate);
1179 box = p->mediabox;
1180 fz_transform_rect (&box, &rm);
1181 w = box.x1 - box.x0;
1182 switch (state.fitmodel) {
1183 case FitProportional:
1184 p->left = (int) (((maxw - w) * zoom) / 2.f);
1185 break;
1186 case FitPage:
1188 float zh, h;
1189 zw = maxw / w;
1190 h = box.y1 - box.y0;
1191 zh = state.h / h;
1192 zoom = fz_min (zw, zh);
1193 p->left = (int) ((maxw - (w * zoom)) / 2.f);
1195 break;
1196 case FitWidth:
1197 p->left = 0;
1198 zoom = state.w / w;
1199 break;
1202 fz_scale (&p->zoomctm, zoom, zoom);
1203 fz_concat (&ctm, &p->zoomctm, &ctm);
1205 fz_rotate (&rm, p->rotate);
1206 p->pagebox = p->mediabox;
1207 fz_transform_rect (&p->pagebox, &rm);
1208 p->pagebox.x1 -= p->pagebox.x0;
1209 p->pagebox.y1 -= p->pagebox.y0;
1210 p->pagebox.x0 = 0;
1211 p->pagebox.y0 = 0;
1212 rect = p->pagebox;
1213 fz_transform_rect (&rect, &ctm);
1214 fz_round_rect (&p->bounds, &rect);
1215 p->ctm = ctm;
1217 fz_translate (&tm, 0, -p->mediabox.y1);
1218 fz_scale (&sm, zoom, -zoom);
1219 fz_concat (&ctm, &tm, &sm);
1221 p->tctmready = 0;
1224 do {
1225 int x0 = fz_mini (p->bounds.x0, p->bounds.x1);
1226 int y0 = fz_mini (p->bounds.y0, p->bounds.y1);
1227 int x1 = fz_maxi (p->bounds.x0, p->bounds.x1);
1228 int y1 = fz_maxi (p->bounds.y0, p->bounds.y1);
1229 int boundw = x1 - x0;
1230 int boundh = y1 - y0;
1232 printd ("pdim %d %d %d %d", p->pageno, boundw, boundh, p->left);
1233 } while (p-- != state.pagedims);
1236 struct pagedim *pdimofpageno (int pageno)
1238 struct pagedim *pdim = state.pagedims;
1240 for (int i = 0; i < state.pagedimcount; ++i) {
1241 if (state.pagedims[i].pageno > pageno)
1242 break;
1243 pdim = &state.pagedims[i];
1245 return pdim;
1248 static void recurse_outline (fz_outline *outline, int level)
1250 while (outline) {
1251 if (outline->page >= 0) {
1252 fz_point p = {.x = outline->x, .y = outline->y};
1253 struct pagedim *pdim = pdimofpageno (outline->page);
1254 int h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
1255 fz_transform_point (&p, &pdim->ctm);
1256 printd ("o %d %d %d %d %s",
1257 level, outline->page, (int) p.y, h, outline->title);
1259 else {
1260 printd ("on %d %s", level, outline->title);
1262 if (outline->down) {
1263 recurse_outline (outline->down, level + 1);
1265 outline = outline->next;
1269 static void process_outline (void)
1271 fz_outline *outline;
1273 if (!state.needoutline || !state.pagedimcount) return;
1275 state.needoutline = 0;
1276 outline = fz_load_outline (state.ctx, state.doc);
1277 if (outline) {
1278 recurse_outline (outline, 0);
1279 fz_drop_outline (state.ctx, outline);
1283 static char *strofline (fz_stext_line *line)
1285 char *p;
1286 char utf8[10];
1287 fz_stext_char *ch;
1288 size_t size = 0, cap = 80;
1290 p = malloc (cap + 1);
1291 if (!p) return NULL;
1293 for (ch = line->first_char; ch; ch = ch->next) {
1294 int n = fz_runetochar (utf8, ch->c);
1295 if (size + n > cap) {
1296 cap *= 2;
1297 p = realloc (p, cap + 1);
1298 if (!p) return NULL;
1301 memcpy (p + size, utf8, n);
1302 size += n;
1304 p[size] = 0;
1305 return p;
1308 static int matchline (regex_t *re, fz_stext_line *line,
1309 int stop, int pageno, double start)
1311 int ret;
1312 char *p;
1313 regmatch_t rm;
1315 p = strofline (line);
1316 if (!p) return -1;
1318 ret = regexec (re, p, 1, &rm, 0);
1319 if (ret) {
1320 free (p);
1321 if (ret != REG_NOMATCH) {
1322 size_t size;
1323 char errbuf[80];
1324 size = regerror (ret, re, errbuf, sizeof (errbuf));
1325 printd ("msg regexec error `%.*s'",
1326 (int) size, errbuf);
1327 return -1;
1329 return 0;
1331 else {
1332 fz_point p1, p2, p3, p4;
1333 fz_rect s = {0,0,0,0}, e;
1334 fz_stext_char *ch;
1335 int o = 0;
1337 for (ch = line->first_char; ch; ch = ch->next) {
1338 o += fz_runelen (ch->c);
1339 if (o > rm.rm_so) {
1340 s = ch->bbox;
1341 break;
1344 for (;ch; ch = ch->next) {
1345 o += fz_runelen (ch->c);
1346 if (o > rm.rm_eo) break;
1348 e = ch->bbox;
1350 p1.x = s.x0;
1351 p1.y = s.y0;
1352 p2.x = e.x1;
1353 p2.y = s.y0;
1354 p3.x = e.x1;
1355 p3.y = e.y1;
1356 p4.x = s.x0;
1357 p4.y = e.y1;
1359 #pragma GCC diagnostic ignored "-Wdouble-promotion"
1360 if (!stop) {
1361 printd ("firstmatch %d %d %f %f %f %f %f %f %f %f",
1362 pageno, 1,
1363 p1.x, p1.y,
1364 p2.x, p2.y,
1365 p3.x, p3.y,
1366 p4.x, p4.y);
1368 printd ("progress 1 found at %d `%.*s' in %f sec",
1369 pageno + 1, (int) (rm.rm_eo - rm.rm_so), &p[rm.rm_so],
1370 now () - start);
1372 else {
1373 printd ("match %d %d %f %f %f %f %f %f %f %f",
1374 pageno, 2,
1375 p1.x, p1.y,
1376 p2.x, p2.y,
1377 p3.x, p3.y,
1378 p4.x, p4.y);
1380 #pragma GCC diagnostic error "-Wdouble-promotion"
1381 free (p);
1382 return 1;
1386 /* wishful thinking function */
1387 static void search (regex_t *re, int pageno, int y, int forward)
1389 fz_device *tdev;
1390 fz_stext_page *text;
1391 struct pagedim *pdim;
1392 int stop = 0, niters = 0;
1393 double start, end;
1394 fz_page *page;
1395 fz_stext_block *block;
1397 start = now ();
1398 while (pageno >= 0 && pageno < state.pagecount && !stop) {
1399 if (niters++ == 5) {
1400 niters = 0;
1401 if (hasdata ()) {
1402 printd ("progress 1 attention requested aborting search at %d",
1403 pageno);
1404 stop = 1;
1406 else {
1407 printd ("progress %f searching in page %d",
1408 (double) (pageno + 1) / state.pagecount,
1409 pageno);
1412 pdim = pdimofpageno (pageno);
1413 text = fz_new_stext_page (state.ctx, &pdim->mediabox);
1414 tdev = fz_new_stext_device (state.ctx, text, 0);
1416 page = fz_load_page (state.ctx, state.doc, pageno);
1418 fz_matrix ctm = pagectm1 (page, pdim);
1419 fz_run_page (state.ctx, page, tdev, &ctm, NULL);
1422 fz_close_device (state.ctx, tdev);
1423 fz_drop_device (state.ctx, tdev);
1425 if (forward) {
1426 for (block = text->first_block; block; block = block->next) {
1427 fz_stext_line *line;
1429 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
1430 for (line = block->u.t.first_line; line; line = line->next) {
1431 if (line->bbox.y0 < y + 1) continue;
1433 switch (matchline (re, line, stop, pageno, start)) {
1434 case 0: break;
1435 case 1: stop = 1; break;
1436 case -1: stop = 1; goto endloop;
1441 else {
1442 for (block = text->last_block; block; block = block->prev) {
1443 fz_stext_line *line;
1445 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
1446 for (line = block->u.t.last_line; line; line = line->prev) {
1447 if (line->bbox.y0 < y + 1) continue;
1449 switch (matchline (re, line, stop, pageno, start)) {
1450 case 0: break;
1451 case 1: stop = 1; break;
1452 case -1: stop = 1; goto endloop;
1458 if (forward) {
1459 pageno += 1;
1460 y = 0;
1462 else {
1463 pageno -= 1;
1464 y = INT_MAX;
1466 endloop:
1467 fz_drop_stext_page (state.ctx, text);
1468 fz_drop_page (state.ctx, page);
1470 end = now ();
1471 if (!stop) {
1472 printd ("progress 1 no matches %f sec", end - start);
1474 printd ("clearrects");
1477 static void set_tex_params (int colorspace)
1479 switch (colorspace) {
1480 case 0:
1481 state.texiform = GL_RGBA8;
1482 state.texform = GL_RGBA;
1483 state.texty = GL_UNSIGNED_BYTE;
1484 state.colorspace = fz_device_rgb (state.ctx);
1485 break;
1486 case 1:
1487 state.texiform = GL_RGBA8;
1488 state.texform = GL_BGRA;
1489 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1490 state.texty = GL_UNSIGNED_INT_8_8_8_8_REV;
1491 #else
1492 state.texty = GL_UNSIGNED_INT_8_8_8_8;
1493 #endif
1494 state.colorspace = fz_device_bgr (state.ctx);
1495 break;
1496 case 2:
1497 state.texiform = GL_LUMINANCE_ALPHA;
1498 state.texform = GL_LUMINANCE_ALPHA;
1499 state.texty = GL_UNSIGNED_BYTE;
1500 state.colorspace = fz_device_gray (state.ctx);
1501 break;
1502 default:
1503 errx (1, "invalid colorspce %d", colorspace);
1507 static void realloctexts (int texcount)
1509 size_t size;
1511 if (texcount == state.texcount) return;
1513 if (texcount < state.texcount) {
1514 glDeleteTextures (state.texcount - texcount,
1515 state.texids + texcount);
1518 size = texcount * (sizeof (*state.texids) + sizeof (*state.texowners));
1519 state.texids = realloc (state.texids, size);
1520 if (!state.texids) {
1521 err (1, "realloc texs %" FMT_s, size);
1524 state.texowners = (void *) (state.texids + texcount);
1525 if (texcount > state.texcount) {
1526 glGenTextures (texcount - state.texcount,
1527 state.texids + state.texcount);
1528 for (int i = state.texcount; i < texcount; ++i) {
1529 state.texowners[i].w = -1;
1530 state.texowners[i].slice = NULL;
1533 state.texcount = texcount;
1534 state.texindex = 0;
1537 static char *mbtoutf8 (char *s)
1539 char *p, *r;
1540 wchar_t *tmp;
1541 size_t i, ret, len;
1543 if (state.utf8cs) {
1544 return s;
1547 len = mbstowcs (NULL, s, strlen (s));
1548 if (len == 0) {
1549 return s;
1551 else {
1552 if (len == (size_t) -1) {
1553 printd ("emsg mbtoutf8: mbstowcs: %d:%s", errno, strerror (errno));
1554 return s;
1558 tmp = calloc (len, sizeof (wchar_t));
1559 if (!tmp) {
1560 printd ("emsg mbtoutf8: calloc(%zu, %zu): %d:%s",
1561 len, sizeof (wchar_t), errno, strerror (errno));
1562 return s;
1565 ret = mbstowcs (tmp, s, len);
1566 if (ret == (size_t) -1) {
1567 printd ("emsg mbtoutf8: mbswcs %zu characters failed: %d:%s",
1568 len, errno, strerror (errno));
1569 free (tmp);
1570 return s;
1573 len = 0;
1574 for (i = 0; i < ret; ++i) {
1575 len += fz_runelen (tmp[i]);
1578 p = r = malloc (len + 1);
1579 if (!r) {
1580 printd ("emsg mbtoutf8: malloc(%zu)", len);
1581 free (tmp);
1582 return s;
1585 for (i = 0; i < ret; ++i) {
1586 p += fz_runetochar (p, tmp[i]);
1588 *p = 0;
1589 free (tmp);
1590 return r;
1593 CAMLprim value ml_mbtoutf8 (value s_v)
1595 CAMLparam1 (s_v);
1596 CAMLlocal1 (ret_v);
1597 char *s, *r;
1599 s = String_val (s_v);
1600 r = mbtoutf8 (s);
1601 if (r == s) {
1602 ret_v = s_v;
1604 else {
1605 ret_v = caml_copy_string (r);
1606 free (r);
1608 CAMLreturn (ret_v);
1611 static void * mainloop (void UNUSED_ATTR *unused)
1613 char *p = NULL;
1614 int len, ret, oldlen = 0;
1616 fz_var (p);
1617 fz_var (oldlen);
1618 for (;;) {
1619 len = readlen (state.csock);
1620 if (len == 0) {
1621 errx (1, "readlen returned 0");
1624 if (oldlen < len + 1) {
1625 p = realloc (p, len + 1);
1626 if (!p) {
1627 err (1, "realloc %d failed", len + 1);
1629 oldlen = len + 1;
1631 readdata (state.csock, p, len);
1632 p[len] = 0;
1634 if (!strncmp ("open", p, 4)) {
1635 int off, usedoccss, ok = 0, layouth;
1636 char *password;
1637 char *filename;
1638 char *utf8filename;
1639 size_t filenamelen;
1641 fz_var (ok);
1642 ret = sscanf (p + 5, " %d %d %n", &usedoccss, &layouth, &off);
1643 if (ret != 2) {
1644 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1647 filename = p + 5 + off;
1648 filenamelen = strlen (filename);
1649 password = filename + filenamelen + 1;
1651 if (password[strlen (password) + 1]) {
1652 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1655 lock ("open");
1656 fz_set_use_document_css (state.ctx, usedoccss);
1657 fz_try (state.ctx) {
1658 ok = openxref (filename, password, layouth);
1660 fz_catch (state.ctx) {
1661 utf8filename = mbtoutf8 (filename);
1662 printd ("msg Could not open %s", utf8filename);
1663 if (utf8filename != filename) {
1664 free (utf8filename);
1667 if (ok) {
1668 pdfinfo ();
1669 initpdims ();
1671 unlock ("open");
1673 if (ok) {
1674 utf8filename = mbtoutf8 (filename);
1675 printd ("msg Opened %s (press h/F1 to get help)", utf8filename);
1676 if (utf8filename != filename) {
1677 free (utf8filename);
1679 state.needoutline = 1;
1682 else if (!strncmp ("cs", p, 2)) {
1683 int i, colorspace;
1685 ret = sscanf (p + 2, " %d", &colorspace);
1686 if (ret != 1) {
1687 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1689 lock ("cs");
1690 set_tex_params (colorspace);
1691 for (i = 0; i < state.texcount; ++i) {
1692 state.texowners[i].w = -1;
1693 state.texowners[i].slice = NULL;
1695 unlock ("cs");
1697 else if (!strncmp ("freepage", p, 8)) {
1698 void *ptr;
1700 ret = sscanf (p + 8, " %" SCN_ptr, SCN_ptr_cast (&ptr));
1701 if (ret != 1) {
1702 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1704 lock ("freepage");
1705 freepage (ptr);
1706 unlock ("freepage");
1708 else if (!strncmp ("freetile", p, 8)) {
1709 void *ptr;
1711 ret = sscanf (p + 8, " %" SCN_ptr, SCN_ptr_cast (&ptr));
1712 if (ret != 1) {
1713 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1715 lock ("freetile");
1716 freetile (ptr);
1717 unlock ("freetile");
1719 else if (!strncmp ("search", p, 6)) {
1720 int icase, pageno, y, len2, forward;
1721 char *pattern;
1722 regex_t re;
1724 ret = sscanf (p + 6, " %d %d %d %d,%n",
1725 &icase, &pageno, &y, &forward, &len2);
1726 if (ret != 4) {
1727 errx (1, "malformed search `%s' ret=%d", p, ret);
1730 pattern = p + 6 + len2;
1731 ret = regcomp (&re, pattern,
1732 REG_EXTENDED | (icase ? REG_ICASE : 0));
1733 if (ret) {
1734 char errbuf[80];
1735 size_t size;
1737 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1738 printd ("msg regcomp failed `%.*s'", (int) size, errbuf);
1740 else {
1741 search (&re, pageno, y, forward);
1742 regfree (&re);
1745 else if (!strncmp ("geometry", p, 8)) {
1746 int w, h, fitmodel;
1748 printd ("clear");
1749 ret = sscanf (p + 8, " %d %d %d", &w, &h, &fitmodel);
1750 if (ret != 3) {
1751 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1754 lock ("geometry");
1755 state.h = h;
1756 if (w != state.w) {
1757 state.w = w;
1758 for (int i = 0; i < state.texcount; ++i) {
1759 state.texowners[i].slice = NULL;
1762 state.fitmodel = fitmodel;
1763 layout ();
1764 process_outline ();
1766 state.gen++;
1767 unlock ("geometry");
1768 printd ("continue %d", state.pagecount);
1770 else if (!strncmp ("reqlayout", p, 9)) {
1771 char *nameddest;
1772 int rotate, off, h;
1773 int fitmodel;
1774 pdf_document *pdf;
1776 printd ("clear");
1777 ret = sscanf (p + 9, " %d %d %d %n",
1778 &rotate, &fitmodel, &h, &off);
1779 if (ret != 3) {
1780 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1782 lock ("reqlayout");
1783 pdf = pdf_specifics (state.ctx, state.doc);
1784 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1785 state.gen += 1;
1787 state.rotate = rotate;
1788 state.fitmodel = fitmodel;
1789 state.h = h;
1790 layout ();
1791 process_outline ();
1793 nameddest = p + 9 + off;
1794 if (pdf && nameddest && *nameddest) {
1795 fz_point xy;
1796 struct pagedim *pdim;
1797 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1798 &xy.x, &xy.y);
1799 pdim = pdimofpageno (pageno);
1800 fz_transform_point (&xy, &pdim->ctm);
1801 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1804 state.gen++;
1805 unlock ("reqlayout");
1806 printd ("continue %d", state.pagecount);
1808 else if (!strncmp ("page", p, 4)) {
1809 double a, b;
1810 struct page *page;
1811 int pageno, pindex;
1813 ret = sscanf (p + 4, " %d %d", &pageno, &pindex);
1814 if (ret != 2) {
1815 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1818 lock ("page");
1819 a = now ();
1820 page = loadpage (pageno, pindex);
1821 b = now ();
1822 unlock ("page");
1824 printd ("page %" FMT_ptr " %f", FMT_ptr_cast (page), b - a);
1826 else if (!strncmp ("tile", p, 4)) {
1827 int x, y, w, h;
1828 struct page *page;
1829 struct tile *tile;
1830 double a, b;
1831 void *data;
1833 ret = sscanf (p + 4, " %" SCN_ptr " %d %d %d %d %" SCN_ptr,
1834 SCN_ptr_cast (&page), &x, &y, &w, &h,
1835 SCN_ptr_cast (&data));
1836 if (ret != 6) {
1837 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1840 lock ("tile");
1841 a = now ();
1842 tile = rendertile (page, x, y, w, h, data);
1843 b = now ();
1844 unlock ("tile");
1846 printd ("tile %d %d %" FMT_ptr " %u %f",
1847 x, y,
1848 FMT_ptr_cast (tile),
1849 tile->w * tile->h * tile->pixmap->n,
1850 b - a);
1852 else if (!strncmp ("trimset", p, 7)) {
1853 fz_irect fuzz;
1854 int trimmargins;
1856 ret = sscanf (p + 7, " %d %d %d %d %d",
1857 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1858 if (ret != 5) {
1859 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1861 lock ("trimset");
1862 state.trimmargins = trimmargins;
1863 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1864 state.trimanew = 1;
1865 state.trimfuzz = fuzz;
1867 unlock ("trimset");
1869 else if (!strncmp ("settrim", p, 7)) {
1870 fz_irect fuzz;
1871 int trimmargins;
1873 ret = sscanf (p + 7, " %d %d %d %d %d",
1874 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1875 if (ret != 5) {
1876 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1878 printd ("clear");
1879 lock ("settrim");
1880 state.trimmargins = trimmargins;
1881 state.needoutline = 1;
1882 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1883 state.trimanew = 1;
1884 state.trimfuzz = fuzz;
1886 state.pagedimcount = 0;
1887 free (state.pagedims);
1888 state.pagedims = NULL;
1889 initpdims ();
1890 layout ();
1891 process_outline ();
1892 unlock ("settrim");
1893 printd ("continue %d", state.pagecount);
1895 else if (!strncmp ("sliceh", p, 6)) {
1896 int h;
1898 ret = sscanf (p + 6, " %d", &h);
1899 if (ret != 1) {
1900 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1902 if (h != state.sliceheight) {
1903 state.sliceheight = h;
1904 for (int i = 0; i < state.texcount; ++i) {
1905 state.texowners[i].w = -1;
1906 state.texowners[i].h = -1;
1907 state.texowners[i].slice = NULL;
1911 else if (!strncmp ("interrupt", p, 9)) {
1912 printd ("vmsg interrupted");
1914 else {
1915 errx (1, "unknown command %.*s", len, p);
1918 return 0;
1921 CAMLprim value ml_isexternallink (value uri_v)
1923 CAMLparam1 (uri_v);
1924 int ext = fz_is_external_link (state.ctx, String_val (uri_v));
1925 CAMLreturn (Val_bool (ext));
1928 CAMLprim value ml_uritolocation (value uri_v)
1930 CAMLparam1 (uri_v);
1931 CAMLlocal1 (ret_v);
1932 int pageno;
1933 fz_point xy;
1934 struct pagedim *pdim;
1936 pageno = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1937 &xy.x, &xy.y);
1938 pdim = pdimofpageno (pageno);
1939 fz_transform_point (&xy, &pdim->ctm);
1940 ret_v = caml_alloc_tuple (3);
1941 Field (ret_v, 0) = Val_int (pageno);
1942 Field (ret_v, 1) = caml_copy_double ((double) xy.x);
1943 Field (ret_v, 2) = caml_copy_double ((double) xy.y);
1944 CAMLreturn (ret_v);
1947 CAMLprim value ml_realloctexts (value texcount_v)
1949 CAMLparam1 (texcount_v);
1950 int ok;
1952 if (trylock (__func__)) {
1953 ok = 0;
1954 goto done;
1956 realloctexts (Int_val (texcount_v));
1957 ok = 1;
1958 unlock (__func__);
1960 done:
1961 CAMLreturn (Val_bool (ok));
1964 static void recti (int x0, int y0, int x1, int y1)
1966 GLfloat *v = state.vertices;
1968 glVertexPointer (2, GL_FLOAT, 0, v);
1969 v[0] = x0; v[1] = y0;
1970 v[2] = x1; v[3] = y0;
1971 v[4] = x0; v[5] = y1;
1972 v[6] = x1; v[7] = y1;
1973 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1976 static void showsel (struct page *page, int ox, int oy)
1978 fz_irect bbox;
1979 fz_rect rect;
1980 fz_stext_block *block;
1981 int seen = 0;
1982 unsigned char selcolor[] = {15,15,15,140};
1984 if (!page->fmark.ch || !page->lmark.ch) return;
1986 glEnable (GL_BLEND);
1987 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1988 glColor4ubv (selcolor);
1990 ox += state.pagedims[page->pdimno].bounds.x0;
1991 oy += state.pagedims[page->pdimno].bounds.y0;
1993 for (block = page->text->first_block; block; block = block->next) {
1994 fz_stext_line *line;
1996 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
1997 for (line = block->u.t.first_line; line; line = line->next) {
1998 fz_stext_char *ch;
2000 rect = fz_empty_rect;
2001 for (ch = line->first_char; ch; ch = ch->next) {
2002 if (ch == page->fmark.ch) seen = 1;
2003 if (seen) fz_union_rect (&rect, &ch->bbox);
2004 if (ch == page->lmark.ch) {
2005 fz_round_rect (&bbox, &rect);
2006 recti (bbox.x0 + ox, bbox.y0 + oy,
2007 bbox.x1 + ox, bbox.y1 + oy);
2008 goto done;
2011 fz_round_rect (&bbox, &rect);
2012 recti (bbox.x0 + ox, bbox.y0 + oy,
2013 bbox.x1 + ox, bbox.y1 + oy);
2016 done:
2017 glDisable (GL_BLEND);
2020 #pragma GCC diagnostic push
2021 #pragma GCC diagnostic ignored "-Wdouble-promotion"
2022 #pragma GCC diagnostic ignored "-Wconversion"
2023 #include "glfont.c"
2024 #pragma GCC diagnostic pop
2026 static void stipplerect (fz_matrix *m,
2027 fz_point *p1,
2028 fz_point *p2,
2029 fz_point *p3,
2030 fz_point *p4,
2031 GLfloat *texcoords,
2032 GLfloat *vertices)
2034 fz_transform_point (p1, m);
2035 fz_transform_point (p2, m);
2036 fz_transform_point (p3, m);
2037 fz_transform_point (p4, m);
2039 float w, h, s, t;
2041 w = p2->x - p1->x;
2042 h = p2->y - p1->y;
2043 t = hypotf (w, h) * .25f;
2045 w = p3->x - p2->x;
2046 h = p3->y - p2->y;
2047 s = hypotf (w, h) * .25f;
2049 texcoords[0] = 0; vertices[0] = p1->x; vertices[1] = p1->y;
2050 texcoords[1] = t; vertices[2] = p2->x; vertices[3] = p2->y;
2052 texcoords[2] = 0; vertices[4] = p2->x; vertices[5] = p2->y;
2053 texcoords[3] = s; vertices[6] = p3->x; vertices[7] = p3->y;
2055 texcoords[4] = 0; vertices[8] = p3->x; vertices[9] = p3->y;
2056 texcoords[5] = t; vertices[10] = p4->x; vertices[11] = p4->y;
2058 texcoords[6] = 0; vertices[12] = p4->x; vertices[13] = p4->y;
2059 texcoords[7] = s; vertices[14] = p1->x; vertices[15] = p1->y;
2061 glDrawArrays (GL_LINES, 0, 8);
2064 static void solidrect (fz_matrix *m,
2065 fz_point *p1,
2066 fz_point *p2,
2067 fz_point *p3,
2068 fz_point *p4,
2069 GLfloat *vertices)
2071 fz_transform_point (p1, m);
2072 fz_transform_point (p2, m);
2073 fz_transform_point (p3, m);
2074 fz_transform_point (p4, m);
2075 vertices[0] = p1->x; vertices[1] = p1->y;
2076 vertices[2] = p2->x; vertices[3] = p2->y;
2078 vertices[4] = p3->x; vertices[5] = p3->y;
2079 vertices[6] = p4->x; vertices[7] = p4->y;
2080 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
2083 static void ensurelinks (struct page *page)
2085 if (!page->links)
2086 page->links = fz_load_links (state.ctx, page->fzpage);
2089 static void highlightlinks (struct page *page, int xoff, int yoff)
2091 fz_matrix ctm, tm, pm;
2092 fz_link *link;
2093 GLfloat *texcoords = state.texcoords;
2094 GLfloat *vertices = state.vertices;
2096 ensurelinks (page);
2098 glEnable (GL_TEXTURE_1D);
2099 glEnable (GL_BLEND);
2100 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2101 glBindTexture (GL_TEXTURE_1D, state.stid);
2103 xoff -= state.pagedims[page->pdimno].bounds.x0;
2104 yoff -= state.pagedims[page->pdimno].bounds.y0;
2105 fz_translate (&tm, xoff, yoff);
2106 pm = pagectm (page);
2107 fz_concat (&ctm, &pm, &tm);
2109 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
2110 glVertexPointer (2, GL_FLOAT, 0, vertices);
2112 for (link = page->links; link; link = link->next) {
2113 fz_point p1, p2, p3, p4;
2115 p1.x = link->rect.x0;
2116 p1.y = link->rect.y0;
2118 p2.x = link->rect.x1;
2119 p2.y = link->rect.y0;
2121 p3.x = link->rect.x1;
2122 p3.y = link->rect.y1;
2124 p4.x = link->rect.x0;
2125 p4.y = link->rect.y1;
2127 /* TODO: different colours for different schemes */
2128 if (fz_is_external_link (state.ctx, link->uri)) glColor3ub (0, 0, 255);
2129 else glColor3ub (255, 0, 0);
2131 stipplerect (&ctm, &p1, &p2, &p3, &p4, texcoords, vertices);
2134 for (int i = 0; i < page->annotcount; ++i) {
2135 fz_point p1, p2, p3, p4;
2136 struct annot *annot = &page->annots[i];
2138 p1.x = annot->bbox.x0;
2139 p1.y = annot->bbox.y0;
2141 p2.x = annot->bbox.x1;
2142 p2.y = annot->bbox.y0;
2144 p3.x = annot->bbox.x1;
2145 p3.y = annot->bbox.y1;
2147 p4.x = annot->bbox.x0;
2148 p4.y = annot->bbox.y1;
2150 glColor3ub (0, 0, 128);
2151 stipplerect (&ctm, &p1, &p2, &p3, &p4, texcoords, vertices);
2154 glDisable (GL_BLEND);
2155 glDisable (GL_TEXTURE_1D);
2158 static int compareslinks (const void *l, const void *r)
2160 struct slink const *ls = l;
2161 struct slink const *rs = r;
2162 if (ls->bbox.y0 == rs->bbox.y0) {
2163 return rs->bbox.x0 - rs->bbox.x0;
2165 return ls->bbox.y0 - rs->bbox.y0;
2168 static void droptext (struct page *page)
2170 if (page->text) {
2171 fz_drop_stext_page (state.ctx, page->text);
2172 page->fmark.ch = NULL;
2173 page->lmark.ch = NULL;
2174 page->text = NULL;
2178 static void dropannots (struct page *page)
2180 if (page->annots) {
2181 free (page->annots);
2182 page->annots = NULL;
2183 page->annotcount = 0;
2187 static void ensureannots (struct page *page)
2189 int i, count = 0;
2190 size_t annotsize = sizeof (*page->annots);
2191 fz_annot *annot;
2193 if (state.gen != page->agen) {
2194 dropannots (page);
2195 page->agen = state.gen;
2197 if (page->annots) return;
2199 for (annot = fz_first_annot (state.ctx, page->fzpage);
2200 annot;
2201 annot = fz_next_annot (state.ctx, annot)) {
2202 count++;
2205 if (count > 0) {
2206 page->annotcount = count;
2207 page->annots = calloc (count, annotsize);
2208 if (!page->annots) {
2209 err (1, "calloc annots %d", count);
2212 for (annot = fz_first_annot (state.ctx, page->fzpage), i = 0;
2213 annot;
2214 annot = fz_next_annot (state.ctx, annot), i++) {
2215 fz_rect rect;
2217 fz_bound_annot (state.ctx, annot, &rect);
2218 page->annots[i].annot = annot;
2219 fz_round_rect (&page->annots[i].bbox, &rect);
2224 static void dropslinks (struct page *page)
2226 if (page->slinks) {
2227 free (page->slinks);
2228 page->slinks = NULL;
2229 page->slinkcount = 0;
2231 if (page->links) {
2232 fz_drop_link (state.ctx, page->links);
2233 page->links = NULL;
2237 static void ensureslinks (struct page *page)
2239 fz_matrix ctm;
2240 int i, count;
2241 size_t slinksize = sizeof (*page->slinks);
2242 fz_link *link;
2244 ensureannots (page);
2245 if (state.gen != page->sgen) {
2246 dropslinks (page);
2247 page->sgen = state.gen;
2249 if (page->slinks) return;
2251 ensurelinks (page);
2252 ctm = pagectm (page);
2254 count = page->annotcount;
2255 for (link = page->links; link; link = link->next) {
2256 count++;
2258 if (count > 0) {
2259 int j;
2261 page->slinkcount = count;
2262 page->slinks = calloc (count, slinksize);
2263 if (!page->slinks) {
2264 err (1, "calloc slinks %d", count);
2267 for (i = 0, link = page->links; link; ++i, link = link->next) {
2268 fz_rect rect;
2270 rect = link->rect;
2271 fz_transform_rect (&rect, &ctm);
2272 page->slinks[i].tag = SLINK;
2273 page->slinks[i].u.link = link;
2274 fz_round_rect (&page->slinks[i].bbox, &rect);
2276 for (j = 0; j < page->annotcount; ++j, ++i) {
2277 fz_rect rect;
2278 fz_bound_annot (state.ctx, page->annots[j].annot, &rect);
2279 fz_transform_rect (&rect, &ctm);
2280 fz_round_rect (&page->slinks[i].bbox, &rect);
2282 page->slinks[i].tag = SANNOT;
2283 page->slinks[i].u.annot = page->annots[j].annot;
2285 qsort (page->slinks, count, slinksize, compareslinks);
2289 #pragma GCC diagnostic push
2290 #pragma GCC diagnostic ignored "-Wconversion"
2291 /* slightly tweaked fmt_ulong by D.J. Bernstein */
2292 static void fmt_linkn (char *s, unsigned int u)
2294 unsigned int len; unsigned int q;
2295 unsigned int zma = 'z' - 'a' + 1;
2296 len = 1; q = u;
2297 while (q > zma - 1) { ++len; q /= zma; }
2298 if (s) {
2299 s += len;
2300 do { *--s = 'a' + (u % zma) - (u < zma && len > 1); u /= zma; } while(u);
2301 /* handles u == 0 */
2303 s[len] = 0;
2305 #pragma GCC diagnostic pop
2307 static void highlightslinks (struct page *page, int xoff, int yoff,
2308 int noff, char *targ, mlsize_t tlen, int hfsize)
2310 char buf[40];
2311 struct slink *slink;
2312 float x0, y0, x1, y1, w;
2314 ensureslinks (page);
2315 glColor3ub (0xc3, 0xb0, 0x91);
2316 for (int i = 0; i < page->slinkcount; ++i) {
2317 fmt_linkn (buf, i + noff);
2318 if (!tlen || !strncmp (targ, buf, tlen)) {
2319 slink = &page->slinks[i];
2321 x0 = slink->bbox.x0 + xoff - 5;
2322 y1 = slink->bbox.y0 + yoff - 5;
2323 y0 = y1 + 10 + hfsize;
2324 w = measure_string (state.face, hfsize, buf);
2325 x1 = x0 + w + 10;
2326 recti ((int) x0, (int) y0, (int) x1, (int) y1);
2330 glEnable (GL_BLEND);
2331 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2332 glEnable (GL_TEXTURE_2D);
2333 glColor3ub (0, 0, 0);
2334 for (int i = 0; i < page->slinkcount; ++i) {
2335 fmt_linkn (buf, i + noff);
2336 if (!tlen || !strncmp (targ, buf, tlen)) {
2337 slink = &page->slinks[i];
2339 x0 = slink->bbox.x0 + xoff;
2340 y0 = slink->bbox.y0 + yoff + hfsize;
2341 draw_string (state.face, hfsize, x0, y0, buf);
2344 glDisable (GL_TEXTURE_2D);
2345 glDisable (GL_BLEND);
2348 static void uploadslice (struct tile *tile, struct slice *slice)
2350 int offset;
2351 struct slice *slice1;
2352 unsigned char *texdata;
2354 offset = 0;
2355 for (slice1 = tile->slices; slice != slice1; slice1++) {
2356 offset += slice1->h * tile->w * tile->pixmap->n;
2358 if (slice->texindex != -1 && slice->texindex < state.texcount
2359 && state.texowners[slice->texindex].slice == slice) {
2360 glBindTexture (TEXT_TYPE, state.texids[slice->texindex]);
2362 else {
2363 int subimage = 0;
2364 int texindex = state.texindex++ % state.texcount;
2366 if (state.texowners[texindex].w == tile->w) {
2367 if (state.texowners[texindex].h >= slice->h) {
2368 subimage = 1;
2370 else {
2371 state.texowners[texindex].h = slice->h;
2374 else {
2375 state.texowners[texindex].h = slice->h;
2378 state.texowners[texindex].w = tile->w;
2379 state.texowners[texindex].slice = slice;
2380 slice->texindex = texindex;
2382 glBindTexture (TEXT_TYPE, state.texids[texindex]);
2383 #if TEXT_TYPE == GL_TEXTURE_2D
2384 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2385 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2386 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2387 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2388 #endif
2389 if (tile->pbo) {
2390 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
2391 texdata = 0;
2393 else {
2394 texdata = tile->pixmap->samples;
2396 if (subimage) {
2397 glTexSubImage2D (TEXT_TYPE,
2401 tile->w,
2402 slice->h,
2403 state.texform,
2404 state.texty,
2405 texdata+offset
2408 else {
2409 glTexImage2D (TEXT_TYPE,
2411 state.texiform,
2412 tile->w,
2413 slice->h,
2415 state.texform,
2416 state.texty,
2417 texdata+offset
2420 if (tile->pbo) {
2421 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
2426 CAMLprim void ml_begintiles (value unit_v)
2428 CAMLparam1 (unit_v);
2429 glEnable (TEXT_TYPE);
2430 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2431 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2432 CAMLreturn0;
2435 CAMLprim void ml_endtiles (value unit_v)
2437 CAMLparam1 (unit_v);
2438 glDisable (TEXT_TYPE);
2439 CAMLreturn0;
2442 CAMLprim void ml_drawtile (value args_v, value ptr_v)
2444 CAMLparam2 (args_v, ptr_v);
2445 int dispx = Int_val (Field (args_v, 0));
2446 int dispy = Int_val (Field (args_v, 1));
2447 int dispw = Int_val (Field (args_v, 2));
2448 int disph = Int_val (Field (args_v, 3));
2449 int tilex = Int_val (Field (args_v, 4));
2450 int tiley = Int_val (Field (args_v, 5));
2451 char *s = String_val (ptr_v);
2452 struct tile *tile = parse_pointer (__func__, s);
2453 int slicey, firstslice;
2454 struct slice *slice;
2455 GLfloat *texcoords = state.texcoords;
2456 GLfloat *vertices = state.vertices;
2458 firstslice = tiley / tile->sliceheight;
2459 slice = &tile->slices[firstslice];
2460 slicey = tiley % tile->sliceheight;
2462 while (disph > 0) {
2463 int dh;
2465 dh = slice->h - slicey;
2466 dh = fz_mini (disph, dh);
2467 uploadslice (tile, slice);
2469 texcoords[0] = tilex; texcoords[1] = slicey;
2470 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2471 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2472 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2474 vertices[0] = dispx; vertices[1] = dispy;
2475 vertices[2] = dispx+dispw; vertices[3] = dispy;
2476 vertices[4] = dispx; vertices[5] = dispy+dh;
2477 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2479 #if TEXT_TYPE == GL_TEXTURE_2D
2480 for (int i = 0; i < 8; ++i) {
2481 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2483 #endif
2485 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2486 dispy += dh;
2487 disph -= dh;
2488 slice++;
2489 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2490 slicey = 0;
2492 CAMLreturn0;
2495 static void drawprect (struct page *page, int xoff, int yoff, value rects_v)
2497 fz_matrix ctm, tm, pm;
2498 fz_point p1, p2, p3, p4;
2499 GLfloat *vertices = state.vertices;
2500 double *v = (double *) rects_v;
2502 xoff -= state.pagedims[page->pdimno].bounds.x0;
2503 yoff -= state.pagedims[page->pdimno].bounds.y0;
2504 fz_translate (&tm, xoff, yoff);
2505 pm = pagectm (page);
2506 fz_concat (&ctm, &pm, &tm);
2508 glEnable (GL_BLEND);
2509 glVertexPointer (2, GL_FLOAT, 0, vertices);
2511 glColor4dv (v);
2512 p1.x = (float) v[4];
2513 p1.y = (float) v[5];
2515 p2.x = (float) v[6];
2516 p2.y = (float) v[5];
2518 p3.x = (float) v[6];
2519 p3.y = (float) v[7];
2521 p4.x = (float) v[4];
2522 p4.y = (float) v[7];
2523 solidrect (&ctm, &p1, &p2, &p3, &p4, vertices);
2524 glDisable (GL_BLEND);
2527 CAMLprim value ml_postprocess (value ptr_v, value hlinks_v,
2528 value xoff_v, value yoff_v,
2529 value li_v)
2531 CAMLparam5 (ptr_v, hlinks_v, xoff_v, yoff_v, li_v);
2532 int xoff = Int_val (xoff_v);
2533 int yoff = Int_val (yoff_v);
2534 int noff = Int_val (Field (li_v, 0));
2535 char *targ = String_val (Field (li_v, 1));
2536 mlsize_t tlen = caml_string_length (Field (li_v, 1));
2537 int hfsize = Int_val (Field (li_v, 2));
2538 char *s = String_val (ptr_v);
2539 int hlmask = Int_val (hlinks_v);
2540 struct page *page = parse_pointer (__func__, s);
2542 if (!page->fzpage) {
2543 /* deal with loadpage failed pages */
2544 goto done;
2547 if (trylock (__func__)) {
2548 noff = -1;
2549 goto done;
2552 ensureannots (page);
2553 if (hlmask & 1) highlightlinks (page, xoff, yoff);
2554 if (hlmask & 2) {
2555 highlightslinks (page, xoff, yoff, noff, targ, tlen, hfsize);
2556 noff = page->slinkcount;
2558 if (page->tgen == state.gen) {
2559 showsel (page, xoff, yoff);
2561 unlock (__func__);
2563 done:
2564 CAMLreturn (Val_int (noff));
2567 CAMLprim void ml_drawprect (value ptr_v, value xoff_v, value yoff_v,
2568 value rects_v)
2570 CAMLparam4 (ptr_v, xoff_v, yoff_v, rects_v);
2571 int xoff = Int_val (xoff_v);
2572 int yoff = Int_val (yoff_v);
2573 char *s = String_val (ptr_v);
2574 struct page *page = parse_pointer (__func__, s);
2576 drawprect (page, xoff, yoff, rects_v);
2577 CAMLreturn0;
2580 static struct annot *getannot (struct page *page, int x, int y)
2582 fz_point p;
2583 fz_matrix ctm;
2584 const fz_matrix *tctm;
2585 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2587 if (!page->annots) return NULL;
2589 if (pdf) {
2590 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2591 tctm = &state.pagedims[page->pdimno].tctm;
2593 else {
2594 tctm = &fz_identity;
2597 p.x = x;
2598 p.y = y;
2600 fz_concat (&ctm, tctm, &state.pagedims[page->pdimno].ctm);
2601 fz_invert_matrix (&ctm, &ctm);
2602 fz_transform_point (&p, &ctm);
2604 if (pdf) {
2605 for (int i = 0; i < page->annotcount; ++i) {
2606 struct annot *a = &page->annots[i];
2607 fz_rect rect;
2609 fz_bound_annot (state.ctx, a->annot, &rect);
2610 if (p.x >= rect.x0 && p.x <= rect.x1) {
2611 if (p.y >= rect.y0 && p.y <= rect.y1)
2612 return a;
2616 return NULL;
2619 static fz_link *getlink (struct page *page, int x, int y)
2621 fz_point p;
2622 fz_matrix ctm;
2623 fz_link *link;
2625 ensureslinks (page);
2627 p.x = x;
2628 p.y = y;
2630 ctm = pagectm (page);
2631 fz_invert_matrix (&ctm, &ctm);
2632 fz_transform_point (&p, &ctm);
2634 for (link = page->links; link; link = link->next) {
2635 if (p.x >= link->rect.x0 && p.x <= link->rect.x1) {
2636 if (p.y >= link->rect.y0 && p.y <= link->rect.y1) {
2637 return link;
2641 return NULL;
2644 static void ensuretext (struct page *page)
2646 if (state.gen != page->tgen) {
2647 droptext (page);
2648 page->tgen = state.gen;
2650 if (!page->text) {
2651 fz_matrix ctm;
2652 fz_device *tdev;
2654 page->text = fz_new_stext_page (state.ctx,
2655 &state.pagedims[page->pdimno].mediabox);
2656 tdev = fz_new_stext_device (state.ctx, page->text, 0);
2657 ctm = pagectm (page);
2658 fz_run_display_list (state.ctx, page->dlist,
2659 tdev, &ctm, &fz_infinite_rect, NULL);
2660 fz_close_device (state.ctx, tdev);
2661 fz_drop_device (state.ctx, tdev);
2665 CAMLprim value ml_find_page_with_links (value start_page_v, value dir_v)
2667 CAMLparam2 (start_page_v, dir_v);
2668 CAMLlocal1 (ret_v);
2669 int i, dir = Int_val (dir_v);
2670 int start_page = Int_val (start_page_v);
2671 int end_page = dir > 0 ? state.pagecount : -1;
2672 pdf_document *pdf;
2674 fz_var (end_page);
2675 ret_v = Val_int (0);
2676 lock (__func__);
2677 pdf = pdf_specifics (state.ctx, state.doc);
2678 for (i = start_page + dir; i != end_page; i += dir) {
2679 int found;
2681 fz_var (found);
2682 if (pdf) {
2683 pdf_page *page = NULL;
2685 fz_var (page);
2686 fz_try (state.ctx) {
2687 page = pdf_load_page (state.ctx, pdf, i);
2688 found = !!page->links || !!page->annots;
2690 fz_catch (state.ctx) {
2691 found = 0;
2693 if (page) {
2694 fz_drop_page (state.ctx, &page->super);
2697 else {
2698 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2699 fz_link *link = fz_load_links (state.ctx, page);
2700 found = !!link;
2701 fz_drop_link (state.ctx, link);
2702 fz_drop_page (state.ctx, page);
2705 if (found) {
2706 ret_v = caml_alloc_small (1, 1);
2707 Field (ret_v, 0) = Val_int (i);
2708 goto unlock;
2711 unlock:
2712 unlock (__func__);
2713 CAMLreturn (ret_v);
2716 enum { dir_first, dir_last };
2717 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
2719 CAMLprim value ml_findlink (value ptr_v, value dir_v)
2721 CAMLparam2 (ptr_v, dir_v);
2722 CAMLlocal2 (ret_v, pos_v);
2723 struct page *page;
2724 int dirtag, i, slinkindex;
2725 struct slink *found = NULL ,*slink;
2726 char *s = String_val (ptr_v);
2728 page = parse_pointer (__func__, s);
2729 ret_v = Val_int (0);
2730 lock (__func__);
2731 ensureslinks (page);
2733 if (Is_block (dir_v)) {
2734 dirtag = Tag_val (dir_v);
2735 switch (dirtag) {
2736 case dir_first_visible:
2738 int x0, y0, dir, first_index, last_index;
2740 pos_v = Field (dir_v, 0);
2741 x0 = Int_val (Field (pos_v, 0));
2742 y0 = Int_val (Field (pos_v, 1));
2743 dir = Int_val (Field (pos_v, 2));
2745 if (dir >= 0) {
2746 dir = 1;
2747 first_index = 0;
2748 last_index = page->slinkcount;
2750 else {
2751 first_index = page->slinkcount - 1;
2752 last_index = -1;
2755 for (i = first_index; i != last_index; i += dir) {
2756 slink = &page->slinks[i];
2757 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2758 found = slink;
2759 break;
2763 break;
2765 case dir_left:
2766 slinkindex = Int_val (Field (dir_v, 0));
2767 found = &page->slinks[slinkindex];
2768 for (i = slinkindex - 1; i >= 0; --i) {
2769 slink = &page->slinks[i];
2770 if (slink->bbox.x0 < found->bbox.x0) {
2771 found = slink;
2772 break;
2775 break;
2777 case dir_right:
2778 slinkindex = Int_val (Field (dir_v, 0));
2779 found = &page->slinks[slinkindex];
2780 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2781 slink = &page->slinks[i];
2782 if (slink->bbox.x0 > found->bbox.x0) {
2783 found = slink;
2784 break;
2787 break;
2789 case dir_down:
2790 slinkindex = Int_val (Field (dir_v, 0));
2791 found = &page->slinks[slinkindex];
2792 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2793 slink = &page->slinks[i];
2794 if (slink->bbox.y0 >= found->bbox.y0) {
2795 found = slink;
2796 break;
2799 break;
2801 case dir_up:
2802 slinkindex = Int_val (Field (dir_v, 0));
2803 found = &page->slinks[slinkindex];
2804 for (i = slinkindex - 1; i >= 0; --i) {
2805 slink = &page->slinks[i];
2806 if (slink->bbox.y0 <= found->bbox.y0) {
2807 found = slink;
2808 break;
2811 break;
2814 else {
2815 dirtag = Int_val (dir_v);
2816 switch (dirtag) {
2817 case dir_first:
2818 found = page->slinks;
2819 break;
2821 case dir_last:
2822 if (page->slinks) {
2823 found = page->slinks + (page->slinkcount - 1);
2825 break;
2828 if (found) {
2829 ret_v = caml_alloc_small (2, 1);
2830 Field (ret_v, 0) = Val_int (found - page->slinks);
2833 unlock (__func__);
2834 CAMLreturn (ret_v);
2837 enum { uuri, utext, uannot };
2839 CAMLprim value ml_getlink (value ptr_v, value n_v)
2841 CAMLparam2 (ptr_v, n_v);
2842 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2843 fz_link *link;
2844 struct page *page;
2845 char *s = String_val (ptr_v);
2846 struct slink *slink;
2848 ret_v = Val_int (0);
2849 page = parse_pointer (__func__, s);
2851 lock (__func__);
2852 ensureslinks (page);
2853 slink = &page->slinks[Int_val (n_v)];
2854 if (slink->tag == SLINK) {
2855 link = slink->u.link;
2856 str_v = caml_copy_string (link->uri);
2857 ret_v = caml_alloc_small (1, uuri);
2858 Field (ret_v, 0) = str_v;
2860 else {
2861 ret_v = caml_alloc_small (1, uannot);
2862 tup_v = caml_alloc_tuple (2);
2863 Field (ret_v, 0) = tup_v;
2864 Field (tup_v, 0) = ptr_v;
2865 Field (tup_v, 1) = n_v;
2867 unlock (__func__);
2869 CAMLreturn (ret_v);
2872 CAMLprim value ml_getannotcontents (value ptr_v, value n_v)
2874 CAMLparam2 (ptr_v, n_v);
2875 CAMLlocal1 (ret_v);
2876 pdf_document *pdf;
2877 char *contents = NULL;
2879 lock (__func__);
2880 pdf = pdf_specifics (state.ctx, state.doc);
2881 if (pdf) {
2882 char *s = String_val (ptr_v);
2883 struct page *page;
2884 struct slink *slink;
2886 page = parse_pointer (__func__, s);
2887 slink = &page->slinks[Int_val (n_v)];
2888 contents = pdf_copy_annot_contents (state.ctx,
2889 (pdf_annot *) slink->u.annot);
2891 unlock (__func__);
2892 if (contents) {
2893 ret_v = caml_copy_string (contents);
2894 fz_free (state.ctx, contents);
2896 else {
2897 ret_v = caml_copy_string ("");
2899 CAMLreturn (ret_v);
2902 CAMLprim value ml_getlinkcount (value ptr_v)
2904 CAMLparam1 (ptr_v);
2905 struct page *page;
2906 char *s = String_val (ptr_v);
2908 page = parse_pointer (__func__, s);
2909 CAMLreturn (Val_int (page->slinkcount));
2912 CAMLprim value ml_getlinkrect (value ptr_v, value n_v)
2914 CAMLparam2 (ptr_v, n_v);
2915 CAMLlocal1 (ret_v);
2916 struct page *page;
2917 struct slink *slink;
2918 char *s = String_val (ptr_v);
2920 page = parse_pointer (__func__, s);
2921 ret_v = caml_alloc_tuple (4);
2922 lock (__func__);
2923 ensureslinks (page);
2925 slink = &page->slinks[Int_val (n_v)];
2926 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2927 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2928 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2929 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2930 unlock (__func__);
2931 CAMLreturn (ret_v);
2934 CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v)
2936 CAMLparam3 (ptr_v, x_v, y_v);
2937 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2938 fz_link *link;
2939 struct annot *annot;
2940 struct page *page;
2941 char *ptr = String_val (ptr_v);
2942 int x = Int_val (x_v), y = Int_val (y_v);
2943 struct pagedim *pdim;
2945 ret_v = Val_int (0);
2946 if (trylock (__func__)) {
2947 goto done;
2950 page = parse_pointer (__func__, ptr);
2951 pdim = &state.pagedims[page->pdimno];
2952 x += pdim->bounds.x0;
2953 y += pdim->bounds.y0;
2956 annot = getannot (page, x, y);
2957 if (annot) {
2958 int i, n = -1;
2960 ensureslinks (page);
2961 for (i = 0; i < page->slinkcount; ++i) {
2962 if (page->slinks[i].tag == SANNOT
2963 && page->slinks[i].u.annot == annot->annot) {
2964 n = i;
2965 break;
2968 ret_v = caml_alloc_small (1, uannot);
2969 tup_v = caml_alloc_tuple (2);
2970 Field (ret_v, 0) = tup_v;
2971 Field (tup_v, 0) = ptr_v;
2972 Field (tup_v, 1) = Val_int (n);
2973 goto unlock;
2977 link = getlink (page, x, y);
2978 if (link) {
2979 str_v = caml_copy_string (link->uri);
2980 ret_v = caml_alloc_small (1, uuri);
2981 Field (ret_v, 0) = str_v;
2983 else {
2984 fz_rect *b;
2985 fz_stext_block *block;
2987 ensuretext (page);
2989 for (block = page->text->first_block; block; block = block->next) {
2990 fz_stext_line *line;
2992 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
2993 b = &block->bbox;
2994 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
2995 continue;
2997 for (line = block->u.t.first_line; line; line = line->next) {
2998 fz_stext_char *ch;
3000 b = &line->bbox;
3001 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3002 continue;
3004 for (ch = line->first_char; ch; ch = ch->next) {
3005 b = &ch->bbox;
3007 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1) {
3008 const char *n2 = fz_font_name (state.ctx, ch->font);
3009 FT_FaceRec *face = fz_font_ft_face (state.ctx,
3010 ch->font);
3012 if (!n2) n2 = "<unknown font>";
3014 if (face && face->family_name) {
3015 char *s;
3016 char *n1 = face->family_name;
3017 size_t l1 = strlen (n1);
3018 size_t l2 = strlen (n2);
3020 if (l1 != l2 || memcmp (n1, n2, l1)) {
3021 s = malloc (l1 + l2 + 2);
3022 if (s) {
3023 memcpy (s, n2, l2);
3024 s[l2] = '=';
3025 memcpy (s + l2 + 1, n1, l1 + 1);
3026 str_v = caml_copy_string (s);
3027 free (s);
3031 if (str_v == Val_unit) {
3032 str_v = caml_copy_string (n2);
3034 ret_v = caml_alloc_small (1, utext);
3035 Field (ret_v, 0) = str_v;
3036 goto unlock;
3042 unlock:
3043 unlock (__func__);
3045 done:
3046 CAMLreturn (ret_v);
3049 enum { mark_page, mark_block, mark_line, mark_word };
3051 static int uninteresting (int c)
3053 return c == ' ' || c == '\n' || c == '\t' || c == '\n' || c == '\r'
3054 || ispunct (c);
3057 CAMLprim void ml_clearmark (value ptr_v)
3059 CAMLparam1 (ptr_v);
3060 char *s = String_val (ptr_v);
3061 struct page *page;
3063 if (trylock (__func__)) {
3064 goto done;
3067 page = parse_pointer (__func__, s);
3068 page->fmark.ch = NULL;
3069 page->lmark.ch = NULL;
3071 unlock (__func__);
3072 done:
3073 CAMLreturn0;
3076 CAMLprim value ml_markunder (value ptr_v, value x_v, value y_v, value mark_v)
3078 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
3079 CAMLlocal1 (ret_v);
3080 fz_rect *b;
3081 struct page *page;
3082 fz_stext_line *line;
3083 fz_stext_block *block;
3084 struct pagedim *pdim;
3085 int mark = Int_val (mark_v);
3086 char *s = String_val (ptr_v);
3087 int x = Int_val (x_v), y = Int_val (y_v);
3089 ret_v = Val_bool (0);
3090 if (trylock (__func__)) {
3091 goto done;
3094 page = parse_pointer (__func__, s);
3095 pdim = &state.pagedims[page->pdimno];
3097 ensuretext (page);
3099 if (mark == mark_page) {
3100 page->fmark.ch = page->text->first_block->u.t.first_line->first_char;
3101 page->lmark.ch = page->text->last_block->u.t.last_line->last_char;
3102 ret_v = Val_bool (1);
3103 goto unlock;
3106 x += pdim->bounds.x0;
3107 y += pdim->bounds.y0;
3109 for (block = page->text->first_block; block; block = block->next) {
3110 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
3111 b = &block->bbox;
3112 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3113 continue;
3115 if (mark == mark_block) {
3116 page->fmark.ch = block->u.t.first_line->first_char;
3117 page->lmark.ch = block->u.t.last_line->last_char;
3118 ret_v = Val_bool (1);
3119 goto unlock;
3122 for (line = block->u.t.first_line; line; line = line->next) {
3123 fz_stext_char *ch;
3125 b = &line->bbox;
3126 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3127 continue;
3129 if (mark == mark_line) {
3130 page->fmark.ch = line->first_char;
3131 page->lmark.ch = line->last_char;
3132 ret_v = Val_bool (1);
3133 goto unlock;
3136 for (ch = line->first_char; ch; ch = ch->next) {
3137 fz_stext_char *ch2, *first = NULL, *last = NULL;
3138 b = &ch->bbox;
3139 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1) {
3140 for (ch2 = line->first_char; ch2 != ch; ch2 = ch2->next) {
3141 if (uninteresting (ch2->c)) first = NULL;
3142 else if (!first) first = ch2;
3144 for (ch2 = ch; ch2; ch2 = ch2->next) {
3145 if (uninteresting (ch2->c)) break;
3146 last = ch2;
3149 page->fmark.ch = first;
3150 page->lmark.ch = last;
3151 ret_v = Val_bool (1);
3152 goto unlock;
3157 unlock:
3158 if (!Bool_val (ret_v)) {
3159 page->fmark.ch = NULL;
3160 page->lmark.ch = NULL;
3162 unlock (__func__);
3164 done:
3165 CAMLreturn (ret_v);
3168 CAMLprim value ml_rectofblock (value ptr_v, value x_v, value y_v)
3170 CAMLparam3 (ptr_v, x_v, y_v);
3171 CAMLlocal2 (ret_v, res_v);
3172 fz_rect *b = NULL;
3173 struct page *page;
3174 struct pagedim *pdim;
3175 fz_stext_block *block;
3176 char *s = String_val (ptr_v);
3177 int x = Int_val (x_v), y = Int_val (y_v);
3179 ret_v = Val_int (0);
3180 if (trylock (__func__)) {
3181 goto done;
3184 page = parse_pointer (__func__, s);
3185 pdim = &state.pagedims[page->pdimno];
3186 x += pdim->bounds.x0;
3187 y += pdim->bounds.y0;
3189 ensuretext (page);
3191 for (block = page->text->first_block; block; block = block->next) {
3192 switch (block->type) {
3193 case FZ_STEXT_BLOCK_TEXT:
3194 b = &block->bbox;
3195 break;
3197 case FZ_STEXT_BLOCK_IMAGE:
3198 b = &block->bbox;
3199 break;
3201 default:
3202 continue;
3205 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1)
3206 break;
3207 b = NULL;
3209 if (b) {
3210 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3211 ret_v = caml_alloc_small (1, 1);
3212 Store_double_field (res_v, 0, (double) b->x0);
3213 Store_double_field (res_v, 1, (double) b->x1);
3214 Store_double_field (res_v, 2, (double) b->y0);
3215 Store_double_field (res_v, 3, (double) b->y1);
3216 Field (ret_v, 0) = res_v;
3218 unlock (__func__);
3220 done:
3221 CAMLreturn (ret_v);
3224 CAMLprim void ml_seltext (value ptr_v, value rect_v)
3226 CAMLparam2 (ptr_v, rect_v);
3227 fz_rect b;
3228 struct page *page;
3229 struct pagedim *pdim;
3230 char *s = String_val (ptr_v);
3231 int x0, x1, y0, y1;
3232 fz_stext_char *ch;
3233 fz_stext_line *line;
3234 fz_stext_block *block;
3235 fz_stext_char *fc, *lc;
3237 if (trylock (__func__)) {
3238 goto done;
3241 page = parse_pointer (__func__, s);
3242 ensuretext (page);
3244 pdim = &state.pagedims[page->pdimno];
3245 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
3246 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
3247 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
3248 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
3250 if (y0 > y1) {
3251 int t = y0;
3252 y0 = y1;
3253 y1 = t;
3254 x0 = x1;
3255 x1 = t;
3258 fc = page->fmark.ch;
3259 lc = page->lmark.ch;
3261 for (block = page->text->first_block; block; block = block->next) {
3262 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
3263 for (line = block->u.t.first_line; line; line = line->next) {
3264 for (ch = line->first_char; ch; ch = ch->next) {
3265 b = ch->bbox;
3266 if (x0 >= b.x0 && x0 <= b.x1 && y0 >= b.y0 && y0 <= b.y1) {
3267 fc = ch;
3269 if (x1 >= b.x0 && x1 <= b.x1 && y1 >= b.y0 && y1 <= b.y1) {
3270 lc = ch;
3275 if (x1 < x0 && fc == lc) {
3276 fz_stext_char *t;
3278 t = fc;
3279 fc = lc;
3280 lc = t;
3283 page->fmark.ch = fc;
3284 page->lmark.ch = lc;
3286 unlock (__func__);
3288 done:
3289 CAMLreturn0;
3292 static int pipechar (FILE *f, fz_stext_char *ch)
3294 char buf[4];
3295 int len;
3296 size_t ret;
3298 len = fz_runetochar (buf, ch->c);
3299 ret = fwrite (buf, len, 1, f);
3300 if (ret != 1) {
3301 printd ("emsg failed to write %d bytes ret=%zu: %d:%s",
3302 len, ret, errno, strerror (errno));
3303 return -1;
3305 return 0;
3308 CAMLprim value ml_spawn (value command_v, value fds_v)
3310 CAMLparam2 (command_v, fds_v);
3311 CAMLlocal2 (l_v, tup_v);
3312 int ret, ret1;
3313 pid_t pid = (pid_t) -1;
3314 char *msg = NULL;
3315 value earg_v = Nothing;
3316 posix_spawnattr_t attr;
3317 posix_spawn_file_actions_t fa;
3318 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
3320 argv[2] = String_val (command_v);
3322 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
3323 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
3326 if ((ret = posix_spawnattr_init (&attr)) != 0) {
3327 msg = "posix_spawnattr_init";
3328 goto fail1;
3331 #ifdef POSIX_SPAWN_USEVFORK
3332 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
3333 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
3334 goto fail;
3336 #endif
3338 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
3339 int fd1, fd2;
3341 tup_v = Field (l_v, 0);
3342 fd1 = Int_val (Field (tup_v, 0));
3343 fd2 = Int_val (Field (tup_v, 1));
3344 if (fd2 < 0) {
3345 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
3346 msg = "posix_spawn_file_actions_addclose";
3347 earg_v = tup_v;
3348 goto fail;
3351 else {
3352 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
3353 msg = "posix_spawn_file_actions_adddup2";
3354 earg_v = tup_v;
3355 goto fail;
3360 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3361 msg = "posix_spawn";
3362 goto fail;
3365 fail:
3366 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3367 printd ("emsg posix_spawnattr_destroy: %d:%s", ret1, strerror (ret1));
3370 fail1:
3371 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3372 printd ("emsg posix_spawn_file_actions_destroy: %d:%s",
3373 ret1, strerror (ret1));
3376 if (msg)
3377 unix_error (ret, msg, earg_v);
3379 CAMLreturn (Val_int (pid));
3382 CAMLprim value ml_hassel (value ptr_v)
3384 CAMLparam1 (ptr_v);
3385 CAMLlocal1 (ret_v);
3386 struct page *page;
3387 char *s = String_val (ptr_v);
3389 ret_v = Val_bool (0);
3390 if (trylock (__func__)) {
3391 goto done;
3394 page = parse_pointer (__func__, s);
3395 ret_v = Val_bool (page->fmark.ch && page->lmark.ch);
3396 unlock (__func__);
3397 done:
3398 CAMLreturn (ret_v);
3401 CAMLprim void ml_copysel (value fd_v, value ptr_v)
3403 CAMLparam2 (fd_v, ptr_v);
3404 FILE *f;
3405 int seen = 0;
3406 struct page *page;
3407 fz_stext_line *line;
3408 fz_stext_block *block;
3409 int fd = Int_val (fd_v);
3410 char *s = String_val (ptr_v);
3412 if (trylock (__func__)) {
3413 goto done;
3416 page = parse_pointer (__func__, s);
3418 if (!page->fmark.ch || !page->lmark.ch) {
3419 printd ("emsg nothing to copy on page %d", page->pageno);
3420 goto unlock;
3423 f = fdopen (fd, "w");
3424 if (!f) {
3425 printd ("emsg failed to fdopen sel pipe (from fd %d): %d:%s",
3426 fd, errno, strerror (errno));
3427 f = stdout;
3430 for (block = page->text->first_block; block; block = block->next) {
3431 if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
3432 for (line = block->u.t.first_line; line; line = line->next) {
3433 fz_stext_char *ch;
3434 for (ch = line->first_char; ch; ch = ch->next) {
3435 if (seen || ch == page->fmark.ch) {
3436 do {
3437 pipechar (f, ch);
3438 if (ch == page->lmark.ch) goto close;
3439 } while ((ch = ch->next));
3440 seen = 1;
3441 break;
3444 if (seen) fputc ('\n', f);
3447 close:
3448 if (f != stdout) {
3449 int ret = fclose (f);
3450 fd = -1;
3451 if (ret == -1) {
3452 if (errno != ECHILD) {
3453 printd ("emsg failed to close sel pipe: %d:%s",
3454 errno, strerror (errno));
3458 unlock:
3459 unlock (__func__);
3461 done:
3462 if (fd >= 0) {
3463 if (close (fd)) {
3464 printd ("emsg failed to close sel pipe: %d:%s",
3465 errno, strerror (errno));
3468 CAMLreturn0;
3471 CAMLprim value ml_getpdimrect (value pagedimno_v)
3473 CAMLparam1 (pagedimno_v);
3474 CAMLlocal1 (ret_v);
3475 int pagedimno = Int_val (pagedimno_v);
3476 fz_rect box;
3478 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3479 if (trylock (__func__)) {
3480 box = fz_empty_rect;
3482 else {
3483 box = state.pagedims[pagedimno].mediabox;
3484 unlock (__func__);
3487 Store_double_field (ret_v, 0, (double) box.x0);
3488 Store_double_field (ret_v, 1, (double) box.x1);
3489 Store_double_field (ret_v, 2, (double) box.y0);
3490 Store_double_field (ret_v, 3, (double) box.y1);
3492 CAMLreturn (ret_v);
3495 CAMLprim value ml_zoom_for_height (value winw_v, value winh_v,
3496 value dw_v, value cols_v)
3498 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3499 CAMLlocal1 (ret_v);
3500 int i;
3501 float zoom = -1.;
3502 float maxh = 0.0;
3503 struct pagedim *p;
3504 float winw = Int_val (winw_v);
3505 float winh = Int_val (winh_v);
3506 float dw = Int_val (dw_v);
3507 float cols = Int_val (cols_v);
3508 float pw = 1.0, ph = 1.0;
3510 if (trylock (__func__)) {
3511 goto done;
3514 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3515 float w = p->pagebox.x1 / cols;
3516 float h = p->pagebox.y1;
3517 if (h > maxh) {
3518 maxh = h;
3519 ph = h;
3520 if (state.fitmodel != FitProportional) pw = w;
3522 if ((state.fitmodel == FitProportional) && w > pw) pw = w;
3525 zoom = (((winh / ph) * pw) + dw) / winw;
3526 unlock (__func__);
3527 done:
3528 ret_v = caml_copy_double ((double) zoom);
3529 CAMLreturn (ret_v);
3532 CAMLprim value ml_getmaxw (value unit_v)
3534 CAMLparam1 (unit_v);
3535 CAMLlocal1 (ret_v);
3536 int i;
3537 float maxw = -1.;
3538 struct pagedim *p;
3540 if (trylock (__func__)) {
3541 goto done;
3544 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3545 float w = p->pagebox.x1;
3546 maxw = fz_max (maxw, w);
3549 unlock (__func__);
3550 done:
3551 ret_v = caml_copy_double ((double) maxw);
3552 CAMLreturn (ret_v);
3555 CAMLprim value ml_draw_string (value pt_v, value x_v, value y_v, value string_v)
3557 CAMLparam4 (pt_v, x_v, y_v, string_v);
3558 CAMLlocal1 (ret_v);
3559 int pt = Int_val(pt_v);
3560 int x = Int_val (x_v);
3561 int y = Int_val (y_v);
3562 double w;
3564 w = (double) draw_string (state.face, pt, x, y, String_val (string_v));
3565 ret_v = caml_copy_double (w);
3566 CAMLreturn (ret_v);
3569 CAMLprim value ml_measure_string (value pt_v, value string_v)
3571 CAMLparam2 (pt_v, string_v);
3572 CAMLlocal1 (ret_v);
3573 int pt = Int_val (pt_v);
3574 double w;
3576 w = (double) measure_string (state.face, pt, String_val (string_v));
3577 ret_v = caml_copy_double (w);
3578 CAMLreturn (ret_v);
3581 CAMLprim value ml_getpagebox (value opaque_v)
3583 CAMLparam1 (opaque_v);
3584 CAMLlocal1 (ret_v);
3585 fz_rect rect;
3586 fz_irect bbox;
3587 fz_matrix ctm;
3588 fz_device *dev;
3589 char *s = String_val (opaque_v);
3590 struct page *page = parse_pointer (__func__, s);
3592 ret_v = caml_alloc_tuple (4);
3593 dev = fz_new_bbox_device (state.ctx, &rect);
3595 ctm = pagectm (page);
3596 fz_run_page (state.ctx, page->fzpage, dev, &ctm, NULL);
3598 fz_close_device (state.ctx, dev);
3599 fz_drop_device (state.ctx, dev);
3600 fz_round_rect (&bbox, &rect);
3601 Field (ret_v, 0) = Val_int (bbox.x0);
3602 Field (ret_v, 1) = Val_int (bbox.y0);
3603 Field (ret_v, 2) = Val_int (bbox.x1);
3604 Field (ret_v, 3) = Val_int (bbox.y1);
3606 CAMLreturn (ret_v);
3609 CAMLprim void ml_setaalevel (value level_v)
3611 CAMLparam1 (level_v);
3613 state.aalevel = Int_val (level_v);
3614 CAMLreturn0;
3617 #ifndef __COCOA__
3618 #pragma GCC diagnostic push
3619 #pragma GCC diagnostic ignored "-Wvariadic-macros"
3620 #include <X11/Xlib.h>
3621 #include <X11/cursorfont.h>
3622 #pragma GCC diagnostic pop
3624 #ifdef USE_EGL
3625 #include <EGL/egl.h>
3626 #else
3627 #include <GL/glx.h>
3628 #endif
3630 static const int shapes[] = {
3631 XC_left_ptr, XC_hand2, XC_exchange, XC_fleur, XC_xterm
3634 #define CURS_COUNT (sizeof (shapes) / sizeof (shapes[0]))
3636 static struct {
3637 Window wid;
3638 Display *dpy;
3639 #ifdef USE_EGL
3640 EGLContext ctx;
3641 EGLConfig conf;
3642 EGLSurface win;
3643 EGLDisplay *edpy;
3644 #else
3645 GLXContext ctx;
3646 #endif
3647 XVisualInfo *visual;
3648 Cursor curs[CURS_COUNT];
3649 } glx;
3652 static void initcurs (void)
3654 for (size_t n = 0; n < CURS_COUNT; ++n) {
3655 glx.curs[n] = XCreateFontCursor (glx.dpy, shapes[n]);
3659 #ifdef USE_EGL
3660 CAMLprim value ml_glxinit (value display_v, value wid_v, value screen_v)
3662 CAMLparam3 (display_v, wid_v, screen_v);
3663 int major, minor;
3664 int num_conf;
3665 EGLint visid;
3666 EGLint attribs[] = {
3667 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
3668 EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
3669 EGL_NONE
3671 EGLConfig conf;
3673 glx.dpy = XOpenDisplay (String_val (display_v));
3674 if (!glx.dpy) {
3675 caml_failwith ("XOpenDisplay");
3678 eglBindAPI (EGL_OPENGL_API);
3680 glx.edpy = eglGetDisplay (glx.dpy);
3681 if (glx.edpy == EGL_NO_DISPLAY) {
3682 caml_failwith ("eglGetDisplay");
3685 if (!eglInitialize (glx.edpy, &major, &minor)) {
3686 caml_failwith ("eglInitialize");
3689 if (!eglChooseConfig (glx.edpy, attribs, &conf, 1, &num_conf) ||
3690 !num_conf) {
3691 caml_failwith ("eglChooseConfig");
3694 if (!eglGetConfigAttrib (glx.edpy, conf, EGL_NATIVE_VISUAL_ID, &visid)) {
3695 caml_failwith ("eglGetConfigAttrib");
3698 glx.conf = conf;
3699 initcurs ();
3701 glx.wid = Int_val (wid_v);
3702 CAMLreturn (Val_int (visid));
3705 CAMLprim void ml_glxcompleteinit (value unit_v)
3707 CAMLparam1 (unit_v);
3709 glx.ctx = eglCreateContext (glx.edpy, glx.conf, EGL_NO_CONTEXT, NULL);
3710 if (!glx.ctx) {
3711 caml_failwith ("eglCreateContext");
3714 glx.win = eglCreateWindowSurface (glx.edpy, glx.conf,
3715 glx.wid, NULL);
3716 if (glx.win == EGL_NO_SURFACE) {
3717 caml_failwith ("eglCreateWindowSurface");
3720 if (!eglMakeCurrent (glx.edpy, glx.win, glx.win, glx.ctx)) {
3721 glx.ctx = NULL;
3722 caml_failwith ("eglMakeCurrent");
3724 CAMLreturn0;
3726 #else
3727 CAMLprim value ml_glxinit (value display_v, value wid_v, value screen_v)
3729 CAMLparam3 (display_v, wid_v, screen_v);
3731 glx.dpy = XOpenDisplay (String_val (display_v));
3732 if (!glx.dpy) {
3733 caml_failwith ("XOpenDisplay");
3736 int attribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
3737 glx.visual = glXChooseVisual (glx.dpy, Int_val (screen_v), attribs);
3738 if (!glx.visual) {
3739 XCloseDisplay (glx.dpy);
3740 caml_failwith ("glXChooseVisual");
3743 initcurs ();
3745 glx.wid = Int_val (wid_v);
3746 CAMLreturn (Val_int (glx.visual->visualid));
3749 CAMLprim void ml_glxcompleteinit (value unit_v)
3751 CAMLparam1 (unit_v);
3753 glx.ctx = glXCreateContext (glx.dpy, glx.visual, NULL, True);
3754 if (!glx.ctx) {
3755 caml_failwith ("glXCreateContext");
3758 XFree (glx.visual);
3759 glx.visual = NULL;
3761 if (!glXMakeCurrent (glx.dpy, glx.wid, glx.ctx)) {
3762 glXDestroyContext (glx.dpy, glx.ctx);
3763 glx.ctx = NULL;
3764 caml_failwith ("glXMakeCurrent");
3766 CAMLreturn0;
3768 #endif
3770 CAMLprim void ml_setcursor (value cursor_v)
3772 CAMLparam1 (cursor_v);
3773 size_t cursn = Int_val (cursor_v);
3775 if (cursn >= CURS_COUNT) caml_failwith ("cursor index out of range");
3776 XDefineCursor (glx.dpy, glx.wid, glx.curs[cursn]);
3777 XFlush (glx.dpy);
3778 CAMLreturn0;
3781 CAMLprim void ml_swapb (value unit_v)
3783 CAMLparam1 (unit_v);
3784 #ifdef USE_EGL
3785 if (!eglSwapBuffers (glx.edpy, glx.win)) {
3786 caml_failwith ("eglSwapBuffers");
3788 #else
3789 glXSwapBuffers (glx.dpy, glx.wid);
3790 #endif
3791 CAMLreturn0;
3794 #pragma GCC diagnostic push
3795 #ifdef __clang__
3796 #pragma GCC diagnostic ignored "-Wmissing-variable-declarations"
3797 #endif
3798 #include "keysym2ucs.c"
3799 #pragma GCC diagnostic pop
3801 CAMLprim value ml_keysymtoutf8 (value keysym_v)
3803 CAMLparam1 (keysym_v);
3804 CAMLlocal1 (str_v);
3805 KeySym keysym = Int_val (keysym_v);
3806 Rune rune;
3807 int len;
3808 char buf[5];
3810 rune = (Rune) keysym2ucs (keysym);
3811 len = fz_runetochar (buf, rune);
3812 buf[len] = 0;
3813 str_v = caml_copy_string (buf);
3814 CAMLreturn (str_v);
3816 #else
3817 CAMLprim value ml_keysymtoutf8 (value keysym_v)
3819 CAMLparam1 (keysym_v);
3820 CAMLlocal1 (str_v);
3821 long ucs_v = Long_val (keysym_v);
3822 int len;
3823 char buf[5];
3825 len = fz_runetochar (buf, ucs_v);
3826 buf[len] = 0;
3827 str_v = caml_copy_string (buf);
3828 CAMLreturn (str_v);
3830 #endif
3832 enum { piunknown, pilinux, piosx, pisun, pibsd };
3834 CAMLprim value ml_platform (value unit_v)
3836 CAMLparam1 (unit_v);
3837 CAMLlocal2 (tup_v, arr_v);
3838 int platid = piunknown;
3839 struct utsname buf;
3841 #if defined __linux__
3842 platid = pilinux;
3843 #elif defined __DragonFly__ || defined __FreeBSD__
3844 || defined __OpenBSD__ || defined __NetBSD__
3845 platid = pibsd;
3846 #elif defined __sun__
3847 platid = pisun;
3848 #elif defined __APPLE__
3849 platid = piosx;
3850 #endif
3851 if (uname (&buf)) err (1, "uname");
3853 tup_v = caml_alloc_tuple (2);
3855 char const *sar[] = {
3856 buf.sysname,
3857 buf.release,
3858 buf.version,
3859 buf.machine,
3860 NULL
3862 arr_v = caml_copy_string_array (sar);
3864 Field (tup_v, 0) = Val_int (platid);
3865 Field (tup_v, 1) = arr_v;
3866 CAMLreturn (tup_v);
3869 CAMLprim void ml_cloexec (value fd_v)
3871 CAMLparam1 (fd_v);
3872 int fd = Int_val (fd_v);
3874 if (fcntl (fd, F_SETFD, FD_CLOEXEC, 1)) {
3875 uerror ("fcntl", Nothing);
3877 CAMLreturn0;
3880 CAMLprim value ml_getpbo (value w_v, value h_v, value cs_v)
3882 CAMLparam2 (w_v, h_v);
3883 CAMLlocal1 (ret_v);
3884 struct bo *pbo;
3885 int w = Int_val (w_v);
3886 int h = Int_val (h_v);
3887 int cs = Int_val (cs_v);
3889 if (state.bo_usable) {
3890 pbo = calloc (sizeof (*pbo), 1);
3891 if (!pbo) {
3892 err (1, "calloc pbo");
3895 switch (cs) {
3896 case 0:
3897 case 1:
3898 pbo->size = w*h*4;
3899 break;
3900 case 2:
3901 pbo->size = w*h*2;
3902 break;
3903 default:
3904 errx (1, "%s: invalid colorspace %d", __func__, cs);
3907 state.glGenBuffersARB (1, &pbo->id);
3908 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, pbo->id);
3909 state.glBufferDataARB (GL_PIXEL_UNPACK_BUFFER_ARB, (GLsizei) pbo->size,
3910 NULL, GL_STREAM_DRAW);
3911 pbo->ptr = state.glMapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB,
3912 GL_READ_WRITE);
3913 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
3914 if (!pbo->ptr) {
3915 printd ("emsg glMapBufferARB failed: %#x", glGetError ());
3916 state.glDeleteBuffersARB (1, &pbo->id);
3917 free (pbo);
3918 ret_v = caml_copy_string ("0");
3920 else {
3921 int res;
3922 char *s;
3924 res = snprintf (NULL, 0, "%" FMT_ptr, FMT_ptr_cast (pbo));
3925 if (res < 0) {
3926 err (1, "snprintf %" FMT_ptr " failed", FMT_ptr_cast (pbo));
3928 s = malloc (res+1);
3929 if (!s) {
3930 err (1, "malloc %d bytes failed", res+1);
3932 res = sprintf (s, "%" FMT_ptr, FMT_ptr_cast (pbo));
3933 if (res < 0) {
3934 err (1, "sprintf %" FMT_ptr " failed", FMT_ptr_cast (pbo));
3936 ret_v = caml_copy_string (s);
3937 free (s);
3940 else {
3941 ret_v = caml_copy_string ("0");
3943 CAMLreturn (ret_v);
3946 CAMLprim void ml_freepbo (value s_v)
3948 CAMLparam1 (s_v);
3949 char *s = String_val (s_v);
3950 struct tile *tile = parse_pointer (__func__, s);
3952 if (tile->pbo) {
3953 state.glDeleteBuffersARB (1, &tile->pbo->id);
3954 tile->pbo->id = -1;
3955 tile->pbo->ptr = NULL;
3956 tile->pbo->size = -1;
3958 CAMLreturn0;
3961 CAMLprim void ml_unmappbo (value s_v)
3963 CAMLparam1 (s_v);
3964 char *s = String_val (s_v);
3965 struct tile *tile = parse_pointer (__func__, s);
3967 if (tile->pbo) {
3968 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
3969 if (state.glUnmapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB) == GL_FALSE) {
3970 errx (1, "glUnmapBufferARB failed: %#x\n", glGetError ());
3972 tile->pbo->ptr = NULL;
3973 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
3975 CAMLreturn0;
3978 static void setuppbo (void)
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