Restore info wrt. dates
[llpp.git] / link.c
blobd98b65588b1edfedc41609e310264f3d42742639
1 /* lots of code c&p-ed directly from mupdf */
2 #define CAML_NAME_SPACE
3 #define FIXME 0
5 #include <errno.h>
6 #include <stdio.h>
7 #include <ctype.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <signal.h>
11 #include <wchar.h>
13 #include <unistd.h>
14 #include <pthread.h>
15 #include <sys/uio.h>
16 #include <sys/time.h>
17 #include <sys/types.h>
18 #include <sys/ioctl.h>
19 #include <sys/utsname.h>
21 #ifdef __CYGWIN__
22 #include <cygwin/socket.h> /* FIONREAD */
23 #else
24 #include <spawn.h>
25 #endif
27 #include <regex.h>
28 #include <stdarg.h>
29 #include <limits.h>
30 #include <inttypes.h>
32 #include <GL/gl.h>
34 #include <caml/fail.h>
35 #include <caml/alloc.h>
36 #include <caml/memory.h>
37 #include <caml/unixsupport.h>
39 #if __GNUC__ < 5 && !defined __clang__
40 /* At least gcc (Gentoo 4.9.3 p1.0, pie-0.6.2) 4.9.3 emits erroneous
41 clobbered diagnostics */
42 #pragma GCC diagnostic ignored "-Wclobbered"
43 #endif
45 #include <mupdf/fitz.h>
46 #include <mupdf/pdf.h>
48 #include <ft2build.h>
49 #include FT_FREETYPE_H
51 #ifdef USE_FONTCONFIG
52 #include <fontconfig/fontconfig.h>
53 #endif
55 #define PIGGYBACK
56 #define CACHE_PAGEREFS
58 #ifndef __USE_GNU
59 extern char **environ;
60 #endif
62 #if defined __GNUC__
63 #define NORETURN_ATTR __attribute__ ((noreturn))
64 #define UNUSED_ATTR __attribute__ ((unused))
65 #if !defined __clang__
66 #define OPTIMIZE_ATTR(n) __attribute__ ((optimize ("O"#n)))
67 #else
68 #define OPTIMIZE_ATTR(n)
69 #endif
70 #define GCC_FMT_ATTR(a, b) __attribute__ ((format (printf, a, b)))
71 #else
72 #define NORETURN_ATTR
73 #define UNUSED_ATTR
74 #define OPTIMIZE_ATTR(n)
75 #define GCC_FMT_ATTR(a, b)
76 #endif
78 #define FMT_s "zu"
80 #define FMT_ptr PRIxPTR
81 #define SCN_ptr SCNxPTR
82 #define FMT_ptr_cast(p) ((uintptr_t) (p))
83 #define SCN_ptr_cast(p) ((uintptr_t *) (p))
85 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
86 err (int exitcode, const char *fmt, ...)
88 va_list ap;
89 int savederrno;
91 savederrno = errno;
92 va_start (ap, fmt);
93 vfprintf (stderr, fmt, ap);
94 va_end (ap);
95 fprintf (stderr, ": %s\n", strerror (savederrno));
96 fflush (stderr);
97 _exit (exitcode);
100 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
101 errx (int exitcode, const char *fmt, ...)
103 va_list ap;
105 va_start (ap, fmt);
106 vfprintf (stderr, fmt, ap);
107 va_end (ap);
108 fputc ('\n', stderr);
109 fflush (stderr);
110 _exit (exitcode);
113 #ifndef GL_TEXTURE_RECTANGLE_ARB
114 #define GL_TEXTURE_RECTANGLE_ARB 0x84F5
115 #endif
117 #ifdef USE_NPOT
118 #define TEXT_TYPE GL_TEXTURE_2D
119 #else
120 #define TEXT_TYPE GL_TEXTURE_RECTANGLE_ARB
121 #endif
123 #ifndef GL_BGRA
124 #define GL_BGRA 0x80E1
125 #endif
127 #ifndef GL_UNSIGNED_INT_8_8_8_8
128 #define GL_UNSIGNED_INT_8_8_8_8 0x8035
129 #endif
131 #ifndef GL_UNSIGNED_INT_8_8_8_8_REV
132 #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
133 #endif
135 #if 0
136 #define lprintf printf
137 #else
138 #define lprintf(...)
139 #endif
141 #define ARSERT(cond) for (;;) { \
142 if (!(cond)) { \
143 errx (1, "%s:%d " #cond, __FILE__, __LINE__); \
145 break; \
148 struct slice {
149 int h;
150 int texindex;
153 struct tile {
154 int w, h;
155 int slicecount;
156 int sliceheight;
157 struct bo *pbo;
158 fz_pixmap *pixmap;
159 struct slice slices[1];
162 struct pagedim {
163 int pageno;
164 int rotate;
165 int left;
166 int tctmready;
167 fz_irect bounds;
168 fz_rect pagebox;
169 fz_rect mediabox;
170 fz_matrix ctm, zoomctm, tctm;
173 struct slink {
174 enum { SLINK, SANNOT } tag;
175 fz_irect bbox;
176 union {
177 fz_link *link;
178 fz_annot *annot;
179 } u;
182 struct annot {
183 fz_irect bbox;
184 fz_annot *annot;
187 struct page {
188 int tgen;
189 int sgen;
190 int agen;
191 int pageno;
192 int pdimno;
193 fz_stext_page *text;
194 fz_stext_sheet *sheet;
195 fz_page *fzpage;
196 fz_display_list *dlist;
197 int slinkcount;
198 struct slink *slinks;
199 int annotcount;
200 struct annot *annots;
201 struct mark {
202 int i;
203 fz_stext_span *span;
204 } fmark, lmark;
207 struct {
208 int sliceheight;
209 struct pagedim *pagedims;
210 int pagecount;
211 int pagedimcount;
212 fz_document *doc;
213 fz_context *ctx;
214 int w, h;
216 int texindex;
217 int texcount;
218 GLuint *texids;
220 GLenum texiform;
221 GLenum texform;
222 GLenum texty;
224 fz_colorspace *colorspace;
226 struct {
227 int w, h;
228 struct slice *slice;
229 } *texowners;
231 int rotate;
232 enum { FitWidth, FitProportional, FitPage } fitmodel;
233 int trimmargins;
234 int needoutline;
235 int gen;
236 int aalevel;
238 int trimanew;
239 fz_irect trimfuzz;
240 fz_pixmap *pig;
242 pthread_t thread;
243 int csock;
244 FT_Face face;
246 char *trimcachepath;
247 int cxack;
248 int dirty;
250 GLuint stid;
252 int bo_usable;
253 GLuint boid;
255 void (*glBindBufferARB) (GLenum, GLuint);
256 GLboolean (*glUnmapBufferARB) (GLenum);
257 void *(*glMapBufferARB) (GLenum, GLenum);
258 void (*glBufferDataARB) (GLenum, GLsizei, void *, GLenum);
259 void (*glGenBuffersARB) (GLsizei, GLuint *);
260 void (*glDeleteBuffersARB) (GLsizei, GLuint *);
262 GLfloat texcoords[8];
263 GLfloat vertices[16];
265 #ifdef CACHE_PAGEREFS
266 struct {
267 int idx;
268 int count;
269 pdf_obj **objs;
270 pdf_document *pdf;
271 } pdflut;
272 #endif
273 } state;
275 struct bo {
276 GLuint id;
277 void *ptr;
278 size_t size;
281 static void UNUSED_ATTR debug_rect (const char *cap, fz_rect r)
283 printf ("%s(rect) %.2f,%.2f,%.2f,%.2f\n", cap, r.x0, r.y0, r.x1, r.y1);
286 static void UNUSED_ATTR debug_bbox (const char *cap, fz_irect r)
288 printf ("%s(bbox) %d,%d,%d,%d\n", cap, r.x0, r.y0, r.x1, r.y1);
291 static void UNUSED_ATTR debug_matrix (const char *cap, fz_matrix m)
293 printf ("%s(matrix) %.2f,%.2f,%.2f,%.2f %.2f %.2f\n", cap,
294 m.a, m.b, m.c, m.d, m.e, m.f);
297 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
299 static void lock (const char *cap)
301 int ret = pthread_mutex_lock (&mutex);
302 if (ret) {
303 errx (1, "%s: pthread_mutex_lock: %s", cap, strerror (ret));
307 static void unlock (const char *cap)
309 int ret = pthread_mutex_unlock (&mutex);
310 if (ret) {
311 errx (1, "%s: pthread_mutex_unlock: %s", cap, strerror (ret));
315 static int trylock (const char *cap)
317 int ret = pthread_mutex_trylock (&mutex);
318 if (ret && ret != EBUSY) {
319 errx (1, "%s: pthread_mutex_trylock: %s", cap, strerror (ret));
321 return ret == EBUSY;
324 static void *parse_pointer (const char *cap, const char *s)
326 int ret;
327 void *ptr;
329 ret = sscanf (s, "%" SCN_ptr, SCN_ptr_cast (&ptr));
330 if (ret != 1) {
331 errx (1, "%s: cannot parse pointer in `%s'", cap, s);
333 return ptr;
336 static double now (void)
338 struct timeval tv;
340 if (gettimeofday (&tv, NULL)) {
341 err (1, "gettimeofday");
343 return tv.tv_sec + tv.tv_usec*1e-6;
346 static int hasdata (void)
348 int ret, avail;
349 ret = ioctl (state.csock, FIONREAD, &avail);
350 if (ret) err (1, "hasdata: FIONREAD error ret=%d", ret);
351 return avail > 0;
354 CAMLprim value ml_hasdata (value fd_v)
356 CAMLparam1 (fd_v);
357 int ret, avail;
359 ret = ioctl (Int_val (fd_v), FIONREAD, &avail);
360 if (ret) uerror ("ioctl (FIONREAD)", Nothing);
361 CAMLreturn (Val_bool (avail > 0));
364 static void readdata (int fd, void *p, int size)
366 ssize_t n;
368 again:
369 n = read (fd, p, size);
370 if (n - size) {
371 if (n < 0 && errno == EINTR) goto again;
372 if (!n) errx (1, "EOF while reading");
373 errx (1, "read (fd %d, req %d, ret %zd)", fd, size, n);
377 static void writedata (int fd, char *p, int size)
379 ssize_t n;
380 uint32_t size4 = size;
381 struct iovec iov[2] = {
382 { .iov_base = &size4, .iov_len = 4 },
383 { .iov_base = p, .iov_len = size }
386 again:
387 n = writev (fd, iov, 2);
388 if (n < 0 && errno == EINTR) goto again;
389 if (n - size - 4) {
390 if (!n) errx (1, "EOF while writing data");
391 err (1, "writev (fd %d, req %d, ret %zd)", fd, size + 4, n);
395 static int readlen (int fd)
397 /* Type punned unions here. Why? Less code (Adjusted by more comments).
398 https://en.wikipedia.org/wiki/Type_punning */
399 union { uint32_t len; char raw[4]; } buf;
400 readdata (fd, buf.raw, 4);
401 return buf.len;
404 CAMLprim void ml_wcmd (value fd_v, value bytes_v, value len_v)
406 CAMLparam3 (fd_v, bytes_v, len_v);
407 writedata (Int_val (fd_v), &Byte (bytes_v, 0), Int_val (len_v));
408 CAMLreturn0;
411 CAMLprim value ml_rcmd (value fd_v)
413 CAMLparam1 (fd_v);
414 CAMLlocal1 (strdata_v);
415 int fd = Int_val (fd_v);
416 int len = readlen (fd);
417 strdata_v = caml_alloc_string (len);
418 readdata (fd, String_val (strdata_v), len);
419 CAMLreturn (strdata_v);
422 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
424 int size = 64, len;
425 va_list ap;
426 char fbuf[size];
427 char *buf = fbuf;
429 for (;;) {
430 va_start (ap, fmt);
431 len = vsnprintf (buf, size, fmt, ap);
432 va_end (ap);
434 if (len > -1) {
435 if (len < size - 4) {
436 writedata (state.csock, buf, len);
437 break;
439 else size = len + 5;
441 else {
442 err (1, "vsnprintf for `%s' failed", fmt);
444 buf = realloc (buf == fbuf ? NULL : buf, size);
445 if (!buf) err (1, "realloc for temp buf (%d bytes) failed", size);
447 if (buf != fbuf) free (buf);
450 static void closedoc (void)
452 #ifdef CACHE_PAGEREFS
453 if (state.pdflut.objs) {
454 for (int i = 0; i < state.pdflut.count; ++i) {
455 pdf_drop_obj (state.ctx, state.pdflut.objs[i]);
457 free (state.pdflut.objs);
458 state.pdflut.objs = NULL;
459 state.pdflut.idx = 0;
461 #endif
462 if (state.doc) {
463 fz_drop_document (state.ctx, state.doc);
464 state.doc = NULL;
468 static int openxref (char *filename, char *password)
470 for (int i = 0; i < state.texcount; ++i) {
471 state.texowners[i].w = -1;
472 state.texowners[i].slice = NULL;
475 closedoc ();
477 state.dirty = 0;
478 if (state.pagedims) {
479 free (state.pagedims);
480 state.pagedims = NULL;
482 state.pagedimcount = 0;
484 fz_set_aa_level (state.ctx, state.aalevel);
485 state.doc = fz_open_document (state.ctx, filename);
486 if (fz_needs_password (state.ctx, state.doc)) {
487 if (password && !*password) {
488 printd ("pass");
489 return 0;
491 else {
492 int ok = fz_authenticate_password (state.ctx, state.doc, password);
493 if (!ok) {
494 printd ("pass fail");
495 return 0;
499 state.pagecount = fz_count_pages (state.ctx, state.doc);
500 return 1;
503 static void pdfinfo (void)
505 struct { char *tag; char *name; } metatbl[] = {
506 { FZ_META_INFO_TITLE, "Title" },
507 { FZ_META_INFO_AUTHOR, "Author" },
508 { FZ_META_FORMAT, "Format" },
509 { FZ_META_ENCRYPTION, "Encryption" },
510 { "info:Creator", "Creator" },
511 { "info:Producer", "Producer" },
512 { "info:CreationDate", "Creation date" },
515 for (size_t i = 0; i < sizeof (metatbl) / sizeof (metatbl[1]); ++i) {
516 char buf[256];
517 if (fz_lookup_metadata (state.ctx, state.doc,
518 metatbl[i].tag, buf, sizeof buf) > 0)
519 printd ("info %s\t%s", metatbl[i].name, buf);
522 printd ("infoend");
525 static void unlinktile (struct tile *tile)
527 for (int i = 0; i < tile->slicecount; ++i) {
528 struct slice *s = &tile->slices[i];
530 if (s->texindex != -1) {
531 if (state.texowners[s->texindex].slice == s) {
532 state.texowners[s->texindex].slice = NULL;
538 static void freepage (struct page *page)
540 if (!page) return;
541 if (page->text) {
542 fz_drop_stext_page (state.ctx, page->text);
544 if (page->sheet) {
545 fz_drop_stext_sheet (state.ctx, page->sheet);
547 if (page->slinks) {
548 free (page->slinks);
550 fz_drop_display_list (state.ctx, page->dlist);
551 fz_drop_page (state.ctx, page->fzpage);
552 free (page);
555 static void freetile (struct tile *tile)
557 unlinktile (tile);
558 if (!tile->pbo) {
559 #ifndef PIGGYBACK
560 fz_drop_pixmap (state.ctx, tile->pixmap);
561 #else
562 if (state.pig) {
563 fz_drop_pixmap (state.ctx, state.pig);
565 state.pig = tile->pixmap;
566 #endif
568 else {
569 free (tile->pbo);
570 fz_drop_pixmap (state.ctx, tile->pixmap);
572 free (tile);
575 #ifdef __ALTIVEC__
576 #include <stdint.h>
577 #include <altivec.h>
579 static int cacheline32bytes;
581 static void __attribute__ ((constructor)) clcheck (void)
583 char **envp = environ;
584 unsigned long *auxv;
586 while (*envp++);
588 for (auxv = (unsigned long *) envp; *auxv != 0; auxv += 2) {
589 if (*auxv == 19) {
590 cacheline32bytes = auxv[1] == 32;
591 return;
596 static void OPTIMIZE_ATTR (3) clearpixmap (fz_pixmap *pixmap)
598 size_t size = pixmap->w * pixmap->h * pixmap->n;
599 if (cacheline32bytes && size > 32) {
600 intptr_t a1, a2, diff;
601 size_t sizea, i;
602 vector unsigned char v = vec_splat_u8 (-1);
603 vector unsigned char *p;
605 a1 = a2 = (intptr_t) pixmap->samples;
606 a2 = (a1 + 31) & ~31;
607 diff = a2 - a1;
608 sizea = size - diff;
609 p = (void *) a2;
611 while (a1 != a2) *(char *) a1++ = 0xff;
612 for (i = 0; i < (sizea & ~31); i += 32) {
613 __asm volatile ("dcbz %0, %1"::"b"(a2),"r"(i));
614 vec_st (v, i, p);
615 vec_st (v, i + 16, p);
617 while (i < sizea) *((char *) a1 + i++) = 0xff;
619 else fz_clear_pixmap_with_value (state.ctx, pixmap, 0xff);
621 #else
622 #define clearpixmap(p) fz_clear_pixmap_with_value (state.ctx, p, 0xff)
623 #endif
625 static void trimctm (pdf_page *page, int pindex)
627 fz_matrix ctm;
628 struct pagedim *pdim = &state.pagedims[pindex];
630 if (!page) return;
631 if (!pdim->tctmready) {
632 fz_rect realbox, mediabox;
633 fz_matrix rm, sm, tm, im, ctm1, page_ctm;
635 fz_rotate (&rm, -pdim->rotate);
636 fz_scale (&sm, 1, -1);
637 fz_concat (&ctm, &rm, &sm);
638 realbox = pdim->mediabox;
639 fz_transform_rect (&realbox, &ctm);
640 fz_translate (&tm, -realbox.x0, -realbox.y0);
641 fz_concat (&ctm1, &ctm, &tm);
642 pdf_page_transform (state.ctx, page, &mediabox, &page_ctm);
643 fz_invert_matrix (&im, &page_ctm);
644 fz_concat (&ctm, &im, &ctm1);
645 pdim->tctm = ctm;
646 pdim->tctmready = 1;
650 static fz_matrix pagectm1 (fz_page *fzpage, struct pagedim *pdim)
652 fz_matrix ctm, tm;
653 int pdimno = pdim - state.pagedims;
655 if (pdf_specifics (state.ctx, state.doc)) {
656 trimctm (pdf_page_from_fz_page (state.ctx, fzpage), pdimno);
657 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
659 else {
660 fz_translate (&tm, -pdim->mediabox.x0, -pdim->mediabox.y0);
661 fz_concat (&ctm, &tm, &pdim->ctm);
663 return ctm;
666 static fz_matrix pagectm (struct page *page)
668 return pagectm1 (page->fzpage, &state.pagedims[page->pdimno]);
671 static void *loadpage (int pageno, int pindex)
673 fz_device *dev;
674 struct page *page;
676 page = calloc (sizeof (struct page), 1);
677 if (!page) {
678 err (1, "calloc page %d", pageno);
681 page->dlist = fz_new_display_list (state.ctx, NULL);
682 dev = fz_new_list_device (state.ctx, page->dlist);
683 fz_try (state.ctx) {
684 page->fzpage = fz_load_page (state.ctx, state.doc, pageno);
685 fz_run_page (state.ctx, page->fzpage, dev,
686 &fz_identity, NULL);
688 fz_catch (state.ctx) {
689 page->fzpage = NULL;
691 fz_close_device (state.ctx, dev);
692 fz_drop_device (state.ctx, dev);
694 page->pdimno = pindex;
695 page->pageno = pageno;
696 page->sgen = state.gen;
697 page->agen = state.gen;
698 page->tgen = state.gen;
699 return page;
702 static struct tile *alloctile (int h)
704 int slicecount;
705 size_t tilesize;
706 struct tile *tile;
708 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
709 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
710 tile = calloc (tilesize, 1);
711 if (!tile) {
712 err (1, "cannot allocate tile (%" FMT_s " bytes)", tilesize);
714 for (int i = 0; i < slicecount; ++i) {
715 int sh = fz_mini (h, state.sliceheight);
716 tile->slices[i].h = sh;
717 tile->slices[i].texindex = -1;
718 h -= sh;
720 tile->slicecount = slicecount;
721 tile->sliceheight = state.sliceheight;
722 return tile;
725 static struct tile *rendertile (struct page *page, int x, int y, int w, int h,
726 struct bo *pbo)
728 fz_rect rect;
729 fz_irect bbox;
730 fz_matrix ctm;
731 fz_device *dev;
732 struct tile *tile;
733 struct pagedim *pdim;
735 tile = alloctile (h);
736 pdim = &state.pagedims[page->pdimno];
738 bbox = pdim->bounds;
739 bbox.x0 += x;
740 bbox.y0 += y;
741 bbox.x1 = bbox.x0 + w;
742 bbox.y1 = bbox.y0 + h;
744 if (state.pig) {
745 if (state.pig->w == w
746 && state.pig->h == h
747 && state.pig->colorspace == state.colorspace) {
748 tile->pixmap = state.pig;
749 tile->pixmap->x = bbox.x0;
750 tile->pixmap->y = bbox.y0;
752 else {
753 fz_drop_pixmap (state.ctx, state.pig);
755 state.pig = NULL;
757 if (!tile->pixmap) {
758 if (pbo) {
759 tile->pixmap =
760 fz_new_pixmap_with_bbox_and_data (state.ctx, state.colorspace,
761 &bbox, 1, pbo->ptr);
762 tile->pbo = pbo;
764 else {
765 tile->pixmap =
766 fz_new_pixmap_with_bbox (state.ctx, state.colorspace, &bbox, 1);
770 tile->w = w;
771 tile->h = h;
772 clearpixmap (tile->pixmap);
774 dev = fz_new_draw_device (state.ctx, NULL, tile->pixmap);
775 ctm = pagectm (page);
776 fz_rect_from_irect (&rect, &bbox);
777 fz_run_display_list (state.ctx, page->dlist, dev, &ctm, &rect, NULL);
778 fz_close_device (state.ctx, dev);
779 fz_drop_device (state.ctx, dev);
781 return tile;
784 #ifdef CACHE_PAGEREFS
785 /* modified mupdf/source/pdf/pdf-page.c:pdf_lookup_page_loc_imp
786 thanks to Robin Watts */
787 static void
788 pdf_collect_pages(pdf_document *doc, pdf_obj *node)
790 fz_context *ctx = state.ctx; /* doc->ctx; */
791 pdf_obj *kids;
792 int len;
794 if (state.pdflut.idx == state.pagecount) return;
796 kids = pdf_dict_gets (ctx, node, "Kids");
797 len = pdf_array_len (ctx, kids);
799 if (len == 0)
800 fz_throw (ctx, FZ_ERROR_GENERIC, "malformed pages tree");
802 if (pdf_mark_obj (ctx, node))
803 fz_throw (ctx, FZ_ERROR_GENERIC, "cycle in page tree");
804 for (int i = 0; i < len; i++) {
805 pdf_obj *kid = pdf_array_get (ctx, kids, i);
806 char *type = pdf_to_name (ctx, pdf_dict_gets (ctx, kid, "Type"));
807 if (*type
808 ? !strcmp (type, "Pages")
809 : pdf_dict_gets (ctx, kid, "Kids")
810 && !pdf_dict_gets (ctx, kid, "MediaBox")) {
811 pdf_collect_pages (doc, kid);
813 else {
814 if (*type
815 ? strcmp (type, "Page") != 0
816 : !pdf_dict_gets (ctx, kid, "MediaBox"))
817 fz_warn (ctx, "non-page object in page tree (%s)", type);
818 state.pdflut.objs[state.pdflut.idx++] = pdf_keep_obj (ctx, kid);
821 pdf_unmark_obj (ctx, node);
824 static void
825 pdf_load_page_objs (pdf_document *doc)
827 pdf_obj *root = pdf_dict_gets (state.ctx,
828 pdf_trailer (state.ctx, doc), "Root");
829 pdf_obj *node = pdf_dict_gets (state.ctx, root, "Pages");
831 if (!node)
832 fz_throw (state.ctx, FZ_ERROR_GENERIC, "cannot find page tree");
834 state.pdflut.idx = 0;
835 pdf_collect_pages (doc, node);
837 #endif
839 static void initpdims (int wthack)
841 double start, end;
842 FILE *trimf = NULL;
843 fz_rect rootmediabox;
844 int pageno, trim, show;
845 int trimw = 0, cxcount;
846 fz_context *ctx = state.ctx;
847 pdf_document *pdf = pdf_specifics (ctx, state.doc);
849 fz_var (trimw);
850 fz_var (trimf);
851 fz_var (cxcount);
852 start = now ();
854 if (state.trimmargins && state.trimcachepath) {
855 trimf = fopen (state.trimcachepath, "rb");
856 if (!trimf) {
857 trimf = fopen (state.trimcachepath, "wb");
858 trimw = 1;
862 if (state.trimmargins || pdf || !state.cxack)
863 cxcount = state.pagecount;
864 else
865 cxcount = fz_mini (state.pagecount, 1);
867 if (pdf) {
868 pdf_obj *obj;
869 obj = pdf_dict_getp (ctx, pdf_trailer (ctx, pdf),
870 "Root/Pages/MediaBox");
871 pdf_to_rect (ctx, obj, &rootmediabox);
874 #ifdef CACHE_PAGEREFS
875 if (pdf && (!state.pdflut.objs || state.pdflut.pdf != pdf)) {
876 state.pdflut.objs = calloc (sizeof (*state.pdflut.objs), cxcount);
877 if (!state.pdflut.objs) {
878 err (1, "malloc pageobjs %zu %d %zu failed",
879 sizeof (*state.pdflut.objs), cxcount,
880 sizeof (*state.pdflut.objs) * cxcount);
882 state.pdflut.count = cxcount;
883 pdf_load_page_objs (pdf);
884 state.pdflut.pdf = pdf;
886 #endif
888 for (pageno = 0; pageno < cxcount; ++pageno) {
889 int rotate = 0;
890 struct pagedim *p;
891 fz_rect mediabox;
893 fz_var (rotate);
894 if (pdf) {
895 pdf_obj *pageref, *pageobj;
897 #ifdef CACHE_PAGEREFS
898 pageref = state.pdflut.objs[pageno];
899 #else
900 pageref = pdf_lookup_page_obj (ctx, pdf, pageno);
901 #endif
902 pageobj = pdf_resolve_indirect (ctx, pageref);
903 rotate = pdf_to_int (ctx, pdf_dict_gets (ctx, pageobj, "Rotate"));
905 if (state.trimmargins) {
906 pdf_obj *obj;
907 pdf_page *page;
909 fz_try (ctx) {
910 page = pdf_load_page (ctx, pdf, pageno);
911 obj = pdf_dict_gets (ctx, pageobj, "llpp.TrimBox");
912 trim = state.trimanew || !obj;
913 if (trim) {
914 fz_rect rect;
915 fz_device *dev;
916 fz_matrix ctm, page_ctm;
918 dev = fz_new_bbox_device (ctx, &rect);
919 dev->hints |= FZ_IGNORE_SHADE;
920 pdf_page_transform (ctx, page, &mediabox, &page_ctm);
921 fz_invert_matrix (&ctm, &page_ctm);
922 pdf_run_page (ctx, page, dev, &fz_identity, NULL);
923 fz_close_device (ctx, dev);
924 fz_drop_device (ctx, dev);
926 rect.x0 += state.trimfuzz.x0;
927 rect.x1 += state.trimfuzz.x1;
928 rect.y0 += state.trimfuzz.y0;
929 rect.y1 += state.trimfuzz.y1;
930 fz_transform_rect (&rect, &ctm);
931 fz_intersect_rect (&rect, &mediabox);
933 if (!fz_is_empty_rect (&rect)) {
934 mediabox = rect;
937 obj = pdf_new_array (ctx, pdf, 4);
938 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
939 mediabox.x0));
940 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
941 mediabox.y0));
942 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
943 mediabox.x1));
944 pdf_array_push (ctx, obj, pdf_new_real (ctx, pdf,
945 mediabox.y1));
946 pdf_dict_puts (ctx, pageobj, "llpp.TrimBox", obj);
948 else {
949 mediabox.x0 = pdf_to_real (ctx,
950 pdf_array_get (ctx, obj, 0));
951 mediabox.y0 = pdf_to_real (ctx,
952 pdf_array_get (ctx, obj, 1));
953 mediabox.x1 = pdf_to_real (ctx,
954 pdf_array_get (ctx, obj, 2));
955 mediabox.y1 = pdf_to_real (ctx,
956 pdf_array_get (ctx, obj, 3));
959 fz_drop_page (ctx, &page->super);
960 show = trim ? pageno % 5 == 0 : pageno % 20 == 0;
961 if (show) {
962 printd ("progress %f Trimming %d",
963 (double) (pageno + 1) / state.pagecount,
964 pageno + 1);
967 fz_catch (ctx) {
968 fprintf (stderr, "failed to load page %d\n", pageno+1);
971 else {
972 int empty = 0;
973 fz_rect cropbox;
975 pdf_to_rect (ctx,
976 pdf_dict_gets (ctx, pageobj, "MediaBox"),
977 &mediabox);
978 if (fz_is_empty_rect (&mediabox)) {
979 mediabox.x0 = 0;
980 mediabox.y0 = 0;
981 mediabox.x1 = 612;
982 mediabox.y1 = 792;
983 empty = 1;
986 pdf_to_rect (ctx,
987 pdf_dict_gets (ctx, pageobj, "CropBox"),
988 &cropbox);
989 if (!fz_is_empty_rect (&cropbox)) {
990 if (empty) {
991 mediabox = cropbox;
993 else {
994 fz_intersect_rect (&mediabox, &cropbox);
997 else {
998 if (empty) {
999 if (fz_is_empty_rect (&rootmediabox)) {
1000 fprintf (stderr,
1001 "cannot find page size for page %d\n",
1002 pageno+1);
1004 else {
1005 mediabox = rootmediabox;
1011 else {
1012 if (state.trimmargins && trimw) {
1013 fz_page *page;
1015 fz_try (ctx) {
1016 page = fz_load_page (ctx, state.doc, pageno);
1017 fz_bound_page (ctx, page, &mediabox);
1018 if (state.trimmargins) {
1019 fz_rect rect;
1020 fz_device *dev;
1022 dev = fz_new_bbox_device (ctx, &rect);
1023 dev->hints |= FZ_IGNORE_SHADE;
1024 fz_run_page (ctx, page, dev, &fz_identity, NULL);
1025 fz_close_device (ctx, dev);
1026 fz_drop_device (ctx, dev);
1028 rect.x0 += state.trimfuzz.x0;
1029 rect.x1 += state.trimfuzz.x1;
1030 rect.y0 += state.trimfuzz.y0;
1031 rect.y1 += state.trimfuzz.y1;
1032 fz_intersect_rect (&rect, &mediabox);
1034 if (!fz_is_empty_rect (&rect)) {
1035 mediabox = rect;
1038 fz_drop_page (ctx, page);
1039 if (!state.cxack) {
1040 printd ("progress %f loading %d",
1041 (double) (pageno + 1) / state.pagecount,
1042 pageno + 1);
1045 fz_catch (ctx) {
1047 if (trimf) {
1048 int n = fwrite (&mediabox, sizeof (mediabox), 1, trimf);
1049 if (n - 1) {
1050 err (1, "fwrite trim mediabox");
1054 else {
1055 if (trimf) {
1056 int n = fread (&mediabox, sizeof (mediabox), 1, trimf);
1057 if (n - 1) {
1058 err (1, "fread trim mediabox %d", pageno);
1061 else {
1062 fz_page *page;
1063 fz_try (ctx) {
1064 page = fz_load_page (ctx, state.doc, pageno);
1065 fz_bound_page (ctx, page, &mediabox);
1066 fz_drop_page (ctx, page);
1068 show = !state.trimmargins && pageno % 20 == 0;
1069 if (show) {
1070 printd ("progress %f Gathering dimensions %d",
1071 (double) (pageno) / state.pagecount,
1072 pageno);
1075 fz_catch (ctx) {
1076 fprintf (stderr, "failed to load page %d\n", pageno);
1082 if (state.pagedimcount == 0
1083 || (p = &state.pagedims[state.pagedimcount-1], p->rotate != rotate)
1084 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
1085 size_t size;
1087 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
1088 state.pagedims = realloc (state.pagedims, size);
1089 if (!state.pagedims) {
1090 err (1, "realloc pagedims to %" FMT_s " (%d elems)",
1091 size, state.pagedimcount + 1);
1094 p = &state.pagedims[state.pagedimcount++];
1095 p->rotate = rotate;
1096 p->mediabox = mediabox;
1097 p->pageno = pageno;
1100 end = now ();
1101 if (!wthack) {
1102 printd ("progress 1 %s %d pages in %f seconds",
1103 state.trimmargins ? "Trimmed" : "Processed",
1104 state.pagecount, end - start);
1106 state.trimanew = 0;
1107 if (trimf) {
1108 if (fclose (trimf)) {
1109 err (1, "fclose");
1114 static void layout (void)
1116 int pindex;
1117 fz_rect box;
1118 fz_matrix ctm, rm;
1119 struct pagedim *p = p;
1120 double zw, w, maxw = 0.0, zoom = zoom;
1122 if (state.pagedimcount == 0) return;
1124 switch (state.fitmodel) {
1125 case FitProportional:
1126 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1127 double x0, x1;
1129 p = &state.pagedims[pindex];
1130 fz_rotate (&rm, p->rotate + state.rotate);
1131 box = p->mediabox;
1132 fz_transform_rect (&box, &rm);
1134 x0 = fz_min (box.x0, box.x1);
1135 x1 = fz_max (box.x0, box.x1);
1137 w = x1 - x0;
1138 maxw = fz_max (w, maxw);
1139 zoom = state.w / maxw;
1141 break;
1143 case FitPage:
1144 maxw = state.w;
1145 break;
1147 case FitWidth:
1148 break;
1150 default:
1151 ARSERT (0 && state.fitmodel);
1154 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1155 fz_rect rect;
1156 fz_matrix tm, sm;
1158 p = &state.pagedims[pindex];
1159 fz_rotate (&ctm, state.rotate);
1160 fz_rotate (&rm, p->rotate + state.rotate);
1161 box = p->mediabox;
1162 fz_transform_rect (&box, &rm);
1163 w = box.x1 - box.x0;
1164 switch (state.fitmodel) {
1165 case FitProportional:
1166 p->left = ((maxw - w) * zoom) / 2.0;
1167 break;
1168 case FitPage:
1170 double zh, h;
1171 zw = maxw / w;
1172 h = box.y1 - box.y0;
1173 zh = state.h / h;
1174 zoom = fz_min (zw, zh);
1175 p->left = (maxw - (w * zoom)) / 2.0;
1177 break;
1178 case FitWidth:
1179 p->left = 0;
1180 zoom = state.w / w;
1181 break;
1184 fz_scale (&p->zoomctm, zoom, zoom);
1185 fz_concat (&ctm, &p->zoomctm, &ctm);
1187 fz_rotate (&rm, p->rotate);
1188 p->pagebox = p->mediabox;
1189 fz_transform_rect (&p->pagebox, &rm);
1190 p->pagebox.x1 -= p->pagebox.x0;
1191 p->pagebox.y1 -= p->pagebox.y0;
1192 p->pagebox.x0 = 0;
1193 p->pagebox.y0 = 0;
1194 rect = p->pagebox;
1195 fz_transform_rect (&rect, &ctm);
1196 fz_round_rect (&p->bounds, &rect);
1197 p->ctm = ctm;
1199 fz_translate (&tm, 0, -p->mediabox.y1);
1200 fz_scale (&sm, zoom, -zoom);
1201 fz_concat (&ctm, &tm, &sm);
1203 p->tctmready = 0;
1206 do {
1207 int x0 = fz_mini (p->bounds.x0, p->bounds.x1);
1208 int y0 = fz_mini (p->bounds.y0, p->bounds.y1);
1209 int x1 = fz_maxi (p->bounds.x0, p->bounds.x1);
1210 int y1 = fz_maxi (p->bounds.y0, p->bounds.y1);
1211 int boundw = x1 - x0;
1212 int boundh = y1 - y0;
1214 printd ("pdim %d %d %d %d", p->pageno, boundw, boundh, p->left);
1215 } while (p-- != state.pagedims);
1218 struct pagedim *pdimofpageno (int pageno)
1220 struct pagedim *pdim = state.pagedims;
1222 for (int i = 0; i < state.pagedimcount; ++i) {
1223 if (state.pagedims[i].pageno > pageno)
1224 break;
1225 pdim = &state.pagedims[i];
1227 return pdim;
1230 static
1231 struct anchor { int n; int x; int y; int w; int h; }
1232 uritoanchor (const char *uri)
1234 fz_point p;
1235 struct anchor a;
1237 a.n = -1;
1238 a.n = fz_resolve_link (state.ctx, state.doc, uri, &p.x, &p.y);
1239 if (a.n >= 0) {
1240 struct pagedim *pdim = pdimofpageno (a.n);
1241 fz_transform_point (&p, &pdim->ctm);
1242 a.x = p.x;
1243 a.y = p.y;
1244 a.h = fz_maxi (fz_absi (pdim->bounds.y1 - pdim->bounds.y0), 0);
1246 return a;
1249 static void recurse_outline (fz_outline *outline, int level)
1251 while (outline) {
1252 struct anchor a = uritoanchor (outline->uri);
1253 if (a.n >= 0) {
1254 printd ("o %d %d %d %d %s", level, a.n, a.y, a.h, outline->title);
1256 else {
1257 printd ("on %d %s", level, outline->title);
1259 if (outline->down) {
1260 recurse_outline (outline->down, level + 1);
1262 outline = outline->next;
1266 static void process_outline (void)
1268 fz_outline *outline;
1270 if (!state.needoutline || !state.pagedimcount) return;
1272 state.needoutline = 0;
1273 outline = fz_load_outline (state.ctx, state.doc);
1274 if (outline) {
1275 recurse_outline (outline, 0);
1276 fz_drop_outline (state.ctx, outline);
1280 static char *strofspan (fz_stext_span *span)
1282 char *p;
1283 char utf8[10];
1284 fz_stext_char *ch;
1285 size_t size = 0, cap = 80;
1287 p = malloc (cap + 1);
1288 if (!p) return NULL;
1290 for (ch = span->text; ch < span->text + span->len; ++ch) {
1291 int n = fz_runetochar (utf8, ch->c);
1292 if (size + n > cap) {
1293 cap *= 2;
1294 p = realloc (p, cap + 1);
1295 if (!p) return NULL;
1298 memcpy (p + size, utf8, n);
1299 size += n;
1301 p[size] = 0;
1302 return p;
1305 static int matchspan (regex_t *re, fz_stext_span *span,
1306 int stop, int pageno, double start)
1308 int ret;
1309 char *p;
1310 regmatch_t rm;
1311 int a, b, c;
1312 fz_rect sb, eb;
1313 fz_point p1, p2, p3, p4;
1315 p = strofspan (span);
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 int l = span->len;
1334 for (a = 0, c = 0; c < rm.rm_so && a < l; a++) {
1335 c += fz_runelen (span->text[a].c);
1337 for (b = a; c < rm.rm_eo - 1 && b < l; b++) {
1338 c += fz_runelen (span->text[b].c);
1341 if (fz_runelen (span->text[b].c) > 1) {
1342 b = fz_maxi (0, b-1);
1345 fz_stext_char_bbox (state.ctx, &sb, span, a);
1346 fz_stext_char_bbox (state.ctx, &eb, span, b);
1348 p1.x = sb.x0;
1349 p1.y = sb.y0;
1350 p2.x = eb.x1;
1351 p2.y = sb.y0;
1352 p3.x = eb.x1;
1353 p3.y = eb.y1;
1354 p4.x = sb.x0;
1355 p4.y = eb.y1;
1357 if (!stop) {
1358 printd ("firstmatch %d %d %f %f %f %f %f %f %f %f",
1359 pageno, 1,
1360 p1.x, p1.y,
1361 p2.x, p2.y,
1362 p3.x, p3.y,
1363 p4.x, p4.y);
1365 printd ("progress 1 found at %d `%.*s' in %f sec",
1366 pageno + 1, (int) (rm.rm_eo - rm.rm_so), &p[rm.rm_so],
1367 now () - start);
1369 else {
1370 printd ("match %d %d %f %f %f %f %f %f %f %f",
1371 pageno, 2,
1372 p1.x, p1.y,
1373 p2.x, p2.y,
1374 p3.x, p3.y,
1375 p4.x, p4.y);
1377 free (p);
1378 return 1;
1382 static int compareblocks (const void *l, const void *r)
1384 fz_stext_block const *ls = l;
1385 fz_stext_block const *rs = r;
1386 return ls->bbox.y0 - rs->bbox.y0;
1389 /* wishful thinking function */
1390 static void search (regex_t *re, int pageno, int y, int forward)
1392 int j;
1393 fz_device *tdev;
1394 fz_stext_page *text;
1395 fz_stext_sheet *sheet;
1396 struct pagedim *pdim;
1397 int stop = 0, niters = 0;
1398 double start, end;
1399 fz_page *page;
1401 start = now ();
1402 while (pageno >= 0 && pageno < state.pagecount && !stop) {
1403 if (niters++ == 5) {
1404 niters = 0;
1405 if (hasdata ()) {
1406 printd ("progress 1 attention requested aborting search at %d",
1407 pageno);
1408 stop = 1;
1410 else {
1411 printd ("progress %f searching in page %d",
1412 (double) (pageno + 1) / state.pagecount,
1413 pageno);
1416 pdim = pdimofpageno (pageno);
1417 sheet = fz_new_stext_sheet (state.ctx);
1418 text = fz_new_stext_page (state.ctx, &pdim->mediabox);
1419 tdev = fz_new_stext_device (state.ctx, sheet, text, 0);
1421 page = fz_load_page (state.ctx, state.doc, pageno);
1423 fz_matrix ctm = pagectm1 (page, pdim);
1424 fz_run_page (state.ctx, page, tdev, &ctm, NULL);
1427 qsort (text->blocks, text->len, sizeof (*text->blocks), compareblocks);
1428 fz_close_device (state.ctx, tdev);
1429 fz_drop_device (state.ctx, tdev);
1431 for (j = 0; j < text->len; ++j) {
1432 int k;
1433 fz_page_block *pb;
1434 fz_stext_block *block;
1436 pb = &text->blocks[forward ? j : text->len - 1 - j];
1437 if (pb->type != FZ_PAGE_BLOCK_TEXT) continue;
1438 block = pb->u.text;
1440 for (k = 0; k < block->len; ++k) {
1441 fz_stext_line *line;
1442 fz_stext_span *span;
1444 if (forward) {
1445 line = &block->lines[k];
1446 if (line->bbox.y0 < y + 1) continue;
1448 else {
1449 line = &block->lines[block->len - 1 - k];
1450 if (line->bbox.y0 > y - 1) continue;
1453 for (span = line->first_span; span; span = span->next) {
1454 switch (matchspan (re, span, stop, pageno, start)) {
1455 case 0: break;
1456 case 1: stop = 1; break;
1457 case -1: stop = 1; goto endloop;
1462 if (forward) {
1463 pageno += 1;
1464 y = 0;
1466 else {
1467 pageno -= 1;
1468 y = INT_MAX;
1470 endloop:
1471 fz_drop_stext_page (state.ctx, text);
1472 fz_drop_stext_sheet (state.ctx, sheet);
1473 fz_drop_page (state.ctx, page);
1475 end = now ();
1476 if (!stop) {
1477 printd ("progress 1 no matches %f sec", end - start);
1479 printd ("clearrects");
1482 static void set_tex_params (int colorspace)
1484 union {
1485 unsigned char b;
1486 unsigned int s;
1487 } endianness = {1};
1489 switch (colorspace) {
1490 case 0:
1491 state.texiform = GL_RGBA8;
1492 state.texform = GL_RGBA;
1493 state.texty = GL_UNSIGNED_BYTE;
1494 state.colorspace = fz_device_rgb (state.ctx);
1495 break;
1496 case 1:
1497 state.texiform = GL_RGBA8;
1498 state.texform = GL_BGRA;
1499 state.texty = endianness.s > 1
1500 ? GL_UNSIGNED_INT_8_8_8_8
1501 : GL_UNSIGNED_INT_8_8_8_8_REV;
1502 state.colorspace = fz_device_bgr (state.ctx);
1503 break;
1504 case 2:
1505 state.texiform = GL_LUMINANCE_ALPHA;
1506 state.texform = GL_LUMINANCE_ALPHA;
1507 state.texty = GL_UNSIGNED_BYTE;
1508 state.colorspace = fz_device_gray (state.ctx);
1509 break;
1510 default:
1511 errx (1, "invalid colorspce %d", colorspace);
1515 static void realloctexts (int texcount)
1517 size_t size;
1519 if (texcount == state.texcount) return;
1521 if (texcount < state.texcount) {
1522 glDeleteTextures (state.texcount - texcount,
1523 state.texids + texcount);
1526 size = texcount * sizeof (*state.texids);
1527 state.texids = realloc (state.texids, size);
1528 if (!state.texids) {
1529 err (1, "realloc texids %" FMT_s, size);
1532 size = texcount * sizeof (*state.texowners);
1533 state.texowners = realloc (state.texowners, size);
1534 if (!state.texowners) {
1535 err (1, "realloc texowners %" FMT_s, size);
1537 if (texcount > state.texcount) {
1538 glGenTextures (texcount - state.texcount,
1539 state.texids + state.texcount);
1540 for (int i = state.texcount; i < texcount; ++i) {
1541 state.texowners[i].w = -1;
1542 state.texowners[i].slice = NULL;
1545 state.texcount = texcount;
1546 state.texindex = 0;
1549 static char *mbtoutf8 (char *s)
1551 char *p, *r;
1552 wchar_t *tmp;
1553 size_t i, ret, len;
1555 len = mbstowcs (NULL, s, strlen (s));
1556 if (len == 0) {
1557 return s;
1559 else {
1560 if (len == (size_t) -1) {
1561 return s;
1565 tmp = malloc (len * sizeof (wchar_t));
1566 if (!tmp) {
1567 return s;
1570 ret = mbstowcs (tmp, s, len);
1571 if (ret == (size_t) -1) {
1572 free (tmp);
1573 return s;
1576 len = 0;
1577 for (i = 0; i < ret; ++i) {
1578 len += fz_runelen (tmp[i]);
1581 p = r = malloc (len + 1);
1582 if (!r) {
1583 free (tmp);
1584 return s;
1587 for (i = 0; i < ret; ++i) {
1588 p += fz_runetochar (p, tmp[i]);
1590 *p = 0;
1591 free (tmp);
1592 return r;
1595 CAMLprim value ml_mbtoutf8 (value s_v)
1597 CAMLparam1 (s_v);
1598 CAMLlocal1 (ret_v);
1599 char *s, *r;
1601 s = String_val (s_v);
1602 r = mbtoutf8 (s);
1603 if (r == s) {
1604 ret_v = s_v;
1606 else {
1607 ret_v = caml_copy_string (r);
1608 free (r);
1610 CAMLreturn (ret_v);
1613 static void * mainloop (void UNUSED_ATTR *unused)
1615 char *p = NULL;
1616 int len, ret, oldlen = 0;
1618 fz_var (p);
1619 fz_var (oldlen);
1620 for (;;) {
1621 len = readlen (state.csock);
1622 if (len == 0) {
1623 errx (1, "readlen returned 0");
1626 if (oldlen < len + 1) {
1627 p = realloc (p, len + 1);
1628 if (!p) {
1629 err (1, "realloc %d failed", len + 1);
1631 oldlen = len + 1;
1633 readdata (state.csock, p, len);
1634 p[len] = 0;
1636 if (!strncmp ("open", p, 4)) {
1637 int wthack, off, usedoccss, ok = 0;
1638 char *password;
1639 char *filename;
1640 char *utf8filename;
1641 size_t filenamelen;
1643 fz_var (ok);
1644 ret = sscanf (p + 5, " %d %d %d %n",
1645 &wthack, &state.cxack, &usedoccss, &off);
1646 if (ret != 3) {
1647 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1650 filename = p + 5 + off;
1651 filenamelen = strlen (filename);
1652 password = filename + filenamelen + 1;
1654 if (password[strlen (password) + 1]) {
1655 fz_set_user_css (state.ctx, password + strlen (password) + 1);
1658 lock ("open");
1659 fz_set_use_document_css (state.ctx, usedoccss);
1660 fz_try (state.ctx) {
1661 ok = openxref (filename, password);
1663 fz_catch (state.ctx) {
1664 utf8filename = mbtoutf8 (filename);
1665 printd ("msg Could not open %s", utf8filename);
1667 if (ok) {
1668 pdfinfo ();
1669 initpdims (wthack);
1671 unlock ("open");
1673 if (ok) {
1674 if (!wthack) {
1675 utf8filename = mbtoutf8 (filename);
1676 printd ("msg Opened %s (press h/F1 to get help)",
1677 utf8filename);
1678 if (utf8filename != filename) {
1679 free (utf8filename);
1682 state.needoutline = 1;
1685 else if (!strncmp ("cs", p, 2)) {
1686 int i, colorspace;
1688 ret = sscanf (p + 2, " %d", &colorspace);
1689 if (ret != 1) {
1690 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1692 lock ("cs");
1693 set_tex_params (colorspace);
1694 for (i = 0; i < state.texcount; ++i) {
1695 state.texowners[i].w = -1;
1696 state.texowners[i].slice = NULL;
1698 unlock ("cs");
1700 else if (!strncmp ("freepage", p, 8)) {
1701 void *ptr;
1703 ret = sscanf (p + 8, " %" SCN_ptr, SCN_ptr_cast (&ptr));
1704 if (ret != 1) {
1705 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1707 freepage (ptr);
1709 else if (!strncmp ("freetile", p, 8)) {
1710 void *ptr;
1712 ret = sscanf (p + 8, " %" SCN_ptr, SCN_ptr_cast (&ptr));
1713 if (ret != 1) {
1714 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1716 freetile (ptr);
1718 else if (!strncmp ("search", p, 6)) {
1719 int icase, pageno, y, len2, forward;
1720 char *pattern;
1721 regex_t re;
1723 ret = sscanf (p + 6, " %d %d %d %d,%n",
1724 &icase, &pageno, &y, &forward, &len2);
1725 if (ret != 4) {
1726 errx (1, "malformed search `%s' ret=%d", p, ret);
1729 pattern = p + 6 + len2;
1730 ret = regcomp (&re, pattern,
1731 REG_EXTENDED | (icase ? REG_ICASE : 0));
1732 if (ret) {
1733 char errbuf[80];
1734 size_t size;
1736 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1737 printd ("msg regcomp failed `%.*s'", (int) size, errbuf);
1739 else {
1740 search (&re, pageno, y, forward);
1741 regfree (&re);
1744 else if (!strncmp ("geometry", p, 8)) {
1745 int w, h, fitmodel;
1747 printd ("clear");
1748 ret = sscanf (p + 8, " %d %d %d", &w, &h, &fitmodel);
1749 if (ret != 3) {
1750 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1753 lock ("geometry");
1754 state.h = h;
1755 if (w != state.w) {
1756 state.w = w;
1757 for (int i = 0; i < state.texcount; ++i) {
1758 state.texowners[i].slice = NULL;
1761 state.fitmodel = fitmodel;
1762 layout ();
1763 process_outline ();
1765 state.gen++;
1766 unlock ("geometry");
1767 printd ("continue %d", state.pagecount);
1769 else if (!strncmp ("reqlayout", p, 9)) {
1770 char *nameddest;
1771 int rotate, off, h;
1772 unsigned int fitmodel;
1773 pdf_document *pdf;
1775 printd ("clear");
1776 ret = sscanf (p + 9, " %d %u %d %n",
1777 &rotate, &fitmodel, &h, &off);
1778 if (ret != 3) {
1779 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1781 lock ("reqlayout");
1782 pdf = pdf_specifics (state.ctx, state.doc);
1783 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1784 state.gen += 1;
1786 state.rotate = rotate;
1787 state.fitmodel = fitmodel;
1788 state.h = h;
1789 layout ();
1790 process_outline ();
1792 nameddest = p + 9 + off;
1793 if (pdf && nameddest && *nameddest) {
1794 fz_point xy;
1795 struct pagedim *pdim;
1796 int pageno = pdf_lookup_anchor (state.ctx, pdf, nameddest,
1797 &xy.x, &xy.y);
1798 pdim = pdimofpageno (pageno);
1799 fz_transform_point (&xy, &pdim->ctm);
1800 printd ("a %d %d %d", pageno, (int) xy.x, (int) xy.y);
1803 state.gen++;
1804 unlock ("reqlayout");
1805 printd ("continue %d", state.pagecount);
1807 else if (!strncmp ("page", p, 4)) {
1808 double a, b;
1809 struct page *page;
1810 int pageno, pindex;
1812 ret = sscanf (p + 4, " %d %d", &pageno, &pindex);
1813 if (ret != 2) {
1814 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1817 lock ("page");
1818 a = now ();
1819 page = loadpage (pageno, pindex);
1820 b = now ();
1821 unlock ("page");
1823 printd ("page %" FMT_ptr " %f", FMT_ptr_cast (page), b - a);
1825 else if (!strncmp ("tile", p, 4)) {
1826 int x, y, w, h;
1827 struct page *page;
1828 struct tile *tile;
1829 double a, b;
1830 void *data;
1832 ret = sscanf (p + 4, " %" SCN_ptr " %d %d %d %d %" SCN_ptr,
1833 SCN_ptr_cast (&page), &x, &y, &w, &h,
1834 SCN_ptr_cast (&data));
1835 if (ret != 6) {
1836 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1839 lock ("tile");
1840 a = now ();
1841 tile = rendertile (page, x, y, w, h, data);
1842 b = now ();
1843 unlock ("tile");
1845 printd ("tile %d %d %" FMT_ptr " %u %f",
1846 x, y,
1847 FMT_ptr_cast (tile),
1848 tile->w * tile->h * tile->pixmap->n,
1849 b - a);
1851 else if (!strncmp ("trimset", p, 7)) {
1852 fz_irect fuzz;
1853 int trimmargins;
1855 ret = sscanf (p + 7, " %d %d %d %d %d",
1856 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1857 if (ret != 5) {
1858 errx (1, "malformed trimset `%.*s' ret=%d", len, p, ret);
1860 lock ("trimset");
1861 state.trimmargins = trimmargins;
1862 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1863 state.trimanew = 1;
1864 state.trimfuzz = fuzz;
1866 unlock ("trimset");
1868 else if (!strncmp ("settrim", p, 7)) {
1869 fz_irect fuzz;
1870 int trimmargins;
1872 ret = sscanf (p + 7, " %d %d %d %d %d",
1873 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1874 if (ret != 5) {
1875 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1877 printd ("clear");
1878 lock ("settrim");
1879 state.trimmargins = trimmargins;
1880 state.needoutline = 1;
1881 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1882 state.trimanew = 1;
1883 state.trimfuzz = fuzz;
1885 state.pagedimcount = 0;
1886 free (state.pagedims);
1887 state.pagedims = NULL;
1888 initpdims (0);
1889 layout ();
1890 process_outline ();
1891 unlock ("settrim");
1892 printd ("continue %d", state.pagecount);
1894 else if (!strncmp ("sliceh", p, 6)) {
1895 int h;
1897 ret = sscanf (p + 6, " %d", &h);
1898 if (ret != 1) {
1899 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1901 if (h != state.sliceheight) {
1902 state.sliceheight = h;
1903 for (int i = 0; i < state.texcount; ++i) {
1904 state.texowners[i].w = -1;
1905 state.texowners[i].h = -1;
1906 state.texowners[i].slice = NULL;
1910 else if (!strncmp ("interrupt", p, 9)) {
1911 printd ("vmsg interrupted");
1913 else {
1914 errx (1, "unknown command %.*s", len, p);
1917 return 0;
1920 CAMLprim value ml_isexternallink (value uri_v)
1922 CAMLparam1 (uri_v);
1923 int ext = fz_is_external_link (state.ctx, String_val (uri_v));
1924 CAMLreturn (Val_bool (ext));
1927 CAMLprim value ml_uritolocation (value uri_v)
1929 CAMLparam1 (uri_v);
1930 CAMLlocal1 (ret_v);
1931 int pageno;
1932 fz_point xy;
1933 struct pagedim *pdim;
1935 pageno = fz_resolve_link (state.ctx, state.doc, String_val (uri_v),
1936 &xy.x, &xy.y);
1937 pdim = pdimofpageno (pageno);
1938 fz_transform_point (&xy, &pdim->ctm);
1939 ret_v = caml_alloc_tuple (3);
1940 Field (ret_v, 0) = Val_int (pageno);
1941 Field (ret_v, 1) = caml_copy_double (xy.x);
1942 Field (ret_v, 2) = caml_copy_double (xy.y);
1943 CAMLreturn (ret_v);
1946 CAMLprim value ml_realloctexts (value texcount_v)
1948 CAMLparam1 (texcount_v);
1949 int ok;
1951 if (trylock (__func__)) {
1952 ok = 0;
1953 goto done;
1955 realloctexts (Int_val (texcount_v));
1956 ok = 1;
1957 unlock (__func__);
1959 done:
1960 CAMLreturn (Val_bool (ok));
1963 static void recti (int x0, int y0, int x1, int y1)
1965 GLfloat *v = state.vertices;
1967 glVertexPointer (2, GL_FLOAT, 0, v);
1968 v[0] = x0; v[1] = y0;
1969 v[2] = x1; v[3] = y0;
1970 v[4] = x0; v[5] = y1;
1971 v[6] = x1; v[7] = y1;
1972 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
1975 static void showsel (struct page *page, int ox, int oy)
1977 int seen = 0;
1978 fz_irect bbox;
1979 fz_rect rect;
1980 fz_stext_line *line;
1981 fz_page_block *pageb;
1982 fz_stext_block *block;
1983 struct mark first, last;
1984 unsigned char selcolor[] = {15,15,15,140};
1986 first = page->fmark;
1987 last = page->lmark;
1989 if (!first.span || !last.span) return;
1991 glEnable (GL_BLEND);
1992 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
1993 glColor4ubv (selcolor);
1995 ox += state.pagedims[page->pdimno].bounds.x0;
1996 oy += state.pagedims[page->pdimno].bounds.y0;
1997 for (pageb = page->text->blocks;
1998 pageb < page->text->blocks + page->text->len;
1999 ++pageb) {
2000 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
2001 block = pageb->u.text;
2003 for (line = block->lines;
2004 line < block->lines + block->len;
2005 ++line) {
2006 fz_stext_span *span;
2007 rect = fz_empty_rect;
2009 for (span = line->first_span; span; span = span->next) {
2010 int i, j, k;
2011 bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0;
2013 j = 0;
2014 k = span->len - 1;
2016 if (span == page->fmark.span && span == page->lmark.span) {
2017 seen = 1;
2018 j = fz_mini (first.i, last.i);
2019 k = fz_maxi (first.i, last.i);
2021 else {
2022 if (span == first.span) {
2023 seen = 1;
2024 j = first.i;
2026 else if (span == last.span) {
2027 seen = 1;
2028 k = last.i;
2032 if (seen) {
2033 for (i = j; i <= k; ++i) {
2034 fz_rect bbox1;
2035 fz_union_rect (&rect,
2036 fz_stext_char_bbox (state.ctx, &bbox1,
2037 span, i));
2039 fz_round_rect (&bbox, &rect);
2040 lprintf ("%d %d %d %d oy=%d ox=%d\n",
2041 bbox.x0,
2042 bbox.y0,
2043 bbox.x1,
2044 bbox.y1,
2045 oy, ox);
2047 recti (bbox.x0 + ox, bbox.y0 + oy,
2048 bbox.x1 + ox, bbox.y1 + oy);
2049 if (span == last.span) {
2050 goto done;
2052 rect = fz_empty_rect;
2057 done:
2058 glDisable (GL_BLEND);
2061 #include "glfont.c"
2063 static void stipplerect (fz_matrix *m,
2064 fz_point *p1,
2065 fz_point *p2,
2066 fz_point *p3,
2067 fz_point *p4,
2068 GLfloat *texcoords,
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);
2076 float w, h, s, t;
2078 w = p2->x - p1->x;
2079 h = p2->y - p1->y;
2080 t = hypotf (w, h) * .25f;
2082 w = p3->x - p2->x;
2083 h = p3->y - p2->y;
2084 s = hypotf (w, h) * .25f;
2086 texcoords[0] = 0; vertices[0] = p1->x; vertices[1] = p1->y;
2087 texcoords[1] = t; vertices[2] = p2->x; vertices[3] = p2->y;
2089 texcoords[2] = 0; vertices[4] = p2->x; vertices[5] = p2->y;
2090 texcoords[3] = s; vertices[6] = p3->x; vertices[7] = p3->y;
2092 texcoords[4] = 0; vertices[8] = p3->x; vertices[9] = p3->y;
2093 texcoords[5] = t; vertices[10] = p4->x; vertices[11] = p4->y;
2095 texcoords[6] = 0; vertices[12] = p4->x; vertices[13] = p4->y;
2096 texcoords[7] = s; vertices[14] = p1->x; vertices[15] = p1->y;
2098 glDrawArrays (GL_LINES, 0, 8);
2101 static void solidrect (fz_matrix *m,
2102 fz_point *p1,
2103 fz_point *p2,
2104 fz_point *p3,
2105 fz_point *p4,
2106 GLfloat *vertices)
2108 fz_transform_point (p1, m);
2109 fz_transform_point (p2, m);
2110 fz_transform_point (p3, m);
2111 fz_transform_point (p4, m);
2112 vertices[0] = p1->x; vertices[1] = p1->y;
2113 vertices[2] = p2->x; vertices[3] = p2->y;
2115 vertices[4] = p3->x; vertices[5] = p3->y;
2116 vertices[6] = p4->x; vertices[7] = p4->y;
2117 glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
2120 static void highlightlinks (struct page *page, int xoff, int yoff)
2122 fz_matrix ctm, tm, pm;
2123 fz_link *link, *links;
2124 GLfloat *texcoords = state.texcoords;
2125 GLfloat *vertices = state.vertices;
2127 links = fz_load_links (state.ctx, page->fzpage);
2129 glEnable (GL_TEXTURE_1D);
2130 glEnable (GL_BLEND);
2131 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2132 glBindTexture (GL_TEXTURE_1D, state.stid);
2134 xoff -= state.pagedims[page->pdimno].bounds.x0;
2135 yoff -= state.pagedims[page->pdimno].bounds.y0;
2136 fz_translate (&tm, xoff, yoff);
2137 pm = pagectm (page);
2138 fz_concat (&ctm, &pm, &tm);
2140 glTexCoordPointer (1, GL_FLOAT, 0, texcoords);
2141 glVertexPointer (2, GL_FLOAT, 0, vertices);
2143 for (link = links; link; link = link->next) {
2144 fz_point p1, p2, p3, p4;
2146 p1.x = link->rect.x0;
2147 p1.y = link->rect.y0;
2149 p2.x = link->rect.x1;
2150 p2.y = link->rect.y0;
2152 p3.x = link->rect.x1;
2153 p3.y = link->rect.y1;
2155 p4.x = link->rect.x0;
2156 p4.y = link->rect.y1;
2158 /* TODO: different colours for different schemes */
2159 if (fz_is_external_link (state.ctx, link->uri)) glColor3ub (0, 0, 255);
2160 else glColor3ub (255, 0, 0);
2162 stipplerect (&ctm, &p1, &p2, &p3, &p4, texcoords, vertices);
2165 for (int i = 0; i < page->annotcount; ++i) {
2166 fz_point p1, p2, p3, p4;
2167 struct annot *annot = &page->annots[i];
2169 p1.x = annot->bbox.x0;
2170 p1.y = annot->bbox.y0;
2172 p2.x = annot->bbox.x1;
2173 p2.y = annot->bbox.y0;
2175 p3.x = annot->bbox.x1;
2176 p3.y = annot->bbox.y1;
2178 p4.x = annot->bbox.x0;
2179 p4.y = annot->bbox.y1;
2181 glColor3ub (0, 0, 128);
2182 stipplerect (&ctm, &p1, &p2, &p3, &p4, texcoords, vertices);
2185 glDisable (GL_BLEND);
2186 glDisable (GL_TEXTURE_1D);
2189 static int compareslinks (const void *l, const void *r)
2191 struct slink const *ls = l;
2192 struct slink const *rs = r;
2193 if (ls->bbox.y0 == rs->bbox.y0) {
2194 return rs->bbox.x0 - rs->bbox.x0;
2196 return ls->bbox.y0 - rs->bbox.y0;
2199 static void droptext (struct page *page)
2201 if (page->text) {
2202 fz_drop_stext_page (state.ctx, page->text);
2203 page->fmark.i = -1;
2204 page->lmark.i = -1;
2205 page->fmark.span = NULL;
2206 page->lmark.span = NULL;
2207 page->text = NULL;
2209 if (page->sheet) {
2210 fz_drop_stext_sheet (state.ctx, page->sheet);
2211 page->sheet = NULL;
2215 static void dropannots (struct page *page)
2217 if (page->annots) {
2218 free (page->annots);
2219 page->annots = NULL;
2220 page->annotcount = 0;
2224 static void ensureannots (struct page *page)
2226 int i, count = 0;
2227 size_t annotsize = sizeof (*page->annots);
2228 fz_annot *annot;
2230 if (state.gen != page->agen) {
2231 dropannots (page);
2232 page->agen = state.gen;
2234 if (page->annots) return;
2236 for (annot = fz_first_annot (state.ctx, page->fzpage);
2237 annot;
2238 annot = fz_next_annot (state.ctx, annot)) {
2239 count++;
2242 if (count > 0) {
2243 page->annotcount = count;
2244 page->annots = calloc (count, annotsize);
2245 if (!page->annots) {
2246 err (1, "calloc annots %d", count);
2249 for (annot = fz_first_annot (state.ctx, page->fzpage), i = 0;
2250 annot;
2251 annot = fz_next_annot (state.ctx, annot), i++) {
2252 fz_rect rect;
2254 fz_bound_annot (state.ctx, annot, &rect);
2255 page->annots[i].annot = annot;
2256 fz_round_rect (&page->annots[i].bbox, &rect);
2261 static void dropslinks (struct page *page)
2263 if (page->slinks) {
2264 free (page->slinks);
2265 page->slinks = NULL;
2266 page->slinkcount = 0;
2270 static void ensureslinks (struct page *page)
2272 fz_matrix ctm;
2273 int i, count;
2274 size_t slinksize = sizeof (*page->slinks);
2275 fz_link *link, *links;
2277 ensureannots (page);
2278 if (state.gen != page->sgen) {
2279 dropslinks (page);
2280 page->sgen = state.gen;
2282 if (page->slinks) return;
2284 links = fz_load_links (state.ctx, page->fzpage);
2285 ctm = pagectm (page);
2287 count = page->annotcount;
2288 for (link = links; link; link = link->next) {
2289 count++;
2291 if (count > 0) {
2292 int j;
2294 page->slinkcount = count;
2295 page->slinks = calloc (count, slinksize);
2296 if (!page->slinks) {
2297 err (1, "calloc slinks %d", count);
2300 for (i = 0, link = links; link; ++i, link = link->next) {
2301 fz_rect rect;
2303 rect = link->rect;
2304 fz_transform_rect (&rect, &ctm);
2305 page->slinks[i].tag = SLINK;
2306 page->slinks[i].u.link = link;
2307 fz_round_rect (&page->slinks[i].bbox, &rect);
2309 for (j = 0; j < page->annotcount; ++j, ++i) {
2310 fz_rect rect;
2311 fz_bound_annot (state.ctx, page->annots[j].annot, &rect);
2312 fz_transform_rect (&rect, &ctm);
2313 fz_round_rect (&page->slinks[i].bbox, &rect);
2315 page->slinks[i].tag = SANNOT;
2316 page->slinks[i].u.annot = page->annots[j].annot;
2318 qsort (page->slinks, count, slinksize, compareslinks);
2322 /* slightly tweaked fmt_ulong by D.J. Bernstein */
2323 static void fmt_linkn (char *s, unsigned int u)
2325 unsigned int len; unsigned int q;
2326 unsigned int zma = 'z' - 'a' + 1;
2327 len = 1; q = u;
2328 while (q > zma - 1) { ++len; q /= zma; }
2329 if (s) {
2330 s += len;
2331 do { *--s = 'a' + (u % zma) - (u < zma && len > 1); u /= zma; } while(u);
2332 /* handles u == 0 */
2334 s[len] = 0;
2337 static void highlightslinks (struct page *page, int xoff, int yoff,
2338 int noff, char *targ, int tlen, int hfsize)
2340 char buf[40];
2341 struct slink *slink;
2342 double x0, y0, x1, y1, w;
2344 ensureslinks (page);
2345 glColor3ub (0xc3, 0xb0, 0x91);
2346 for (int i = 0; i < page->slinkcount; ++i) {
2347 fmt_linkn (buf, i + noff);
2348 if (!tlen || !strncmp (targ, buf, tlen)) {
2349 slink = &page->slinks[i];
2351 x0 = slink->bbox.x0 + xoff - 5;
2352 y1 = slink->bbox.y0 + yoff - 5;
2353 y0 = y1 + 10 + hfsize;
2354 w = measure_string (state.face, hfsize, buf);
2355 x1 = x0 + w + 10;
2356 recti (x0, y0, x1, y1);
2360 glEnable (GL_BLEND);
2361 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2362 glEnable (GL_TEXTURE_2D);
2363 glColor3ub (0, 0, 0);
2364 for (int i = 0; i < page->slinkcount; ++i) {
2365 fmt_linkn (buf, i + noff);
2366 if (!tlen || !strncmp (targ, buf, tlen)) {
2367 slink = &page->slinks[i];
2369 x0 = slink->bbox.x0 + xoff;
2370 y0 = slink->bbox.y0 + yoff + hfsize;
2371 draw_string (state.face, hfsize, x0, y0, buf);
2374 glDisable (GL_TEXTURE_2D);
2375 glDisable (GL_BLEND);
2378 static void uploadslice (struct tile *tile, struct slice *slice)
2380 int offset;
2381 struct slice *slice1;
2382 unsigned char *texdata;
2384 offset = 0;
2385 for (slice1 = tile->slices; slice != slice1; slice1++) {
2386 offset += slice1->h * tile->w * tile->pixmap->n;
2388 if (slice->texindex != -1 && slice->texindex < state.texcount
2389 && state.texowners[slice->texindex].slice == slice) {
2390 glBindTexture (TEXT_TYPE, state.texids[slice->texindex]);
2392 else {
2393 int subimage = 0;
2394 int texindex = state.texindex++ % state.texcount;
2396 if (state.texowners[texindex].w == tile->w) {
2397 if (state.texowners[texindex].h >= slice->h) {
2398 subimage = 1;
2400 else {
2401 state.texowners[texindex].h = slice->h;
2404 else {
2405 state.texowners[texindex].h = slice->h;
2408 state.texowners[texindex].w = tile->w;
2409 state.texowners[texindex].slice = slice;
2410 slice->texindex = texindex;
2412 glBindTexture (TEXT_TYPE, state.texids[texindex]);
2413 #if TEXT_TYPE == GL_TEXTURE_2D
2414 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2415 glTexParameteri (TEXT_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2416 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
2417 glTexParameteri (TEXT_TYPE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2418 #endif
2419 if (tile->pbo) {
2420 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
2421 texdata = 0;
2423 else {
2424 texdata = tile->pixmap->samples;
2426 if (subimage) {
2427 glTexSubImage2D (TEXT_TYPE,
2431 tile->w,
2432 slice->h,
2433 state.texform,
2434 state.texty,
2435 texdata+offset
2438 else {
2439 glTexImage2D (TEXT_TYPE,
2441 state.texiform,
2442 tile->w,
2443 slice->h,
2445 state.texform,
2446 state.texty,
2447 texdata+offset
2450 if (tile->pbo) {
2451 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
2456 CAMLprim value ml_begintiles (value unit_v)
2458 CAMLparam1 (unit_v);
2459 glEnable (TEXT_TYPE);
2460 glTexCoordPointer (2, GL_FLOAT, 0, state.texcoords);
2461 glVertexPointer (2, GL_FLOAT, 0, state.vertices);
2462 CAMLreturn (unit_v);
2465 CAMLprim value ml_endtiles (value unit_v)
2467 CAMLparam1 (unit_v);
2468 glDisable (TEXT_TYPE);
2469 CAMLreturn (unit_v);
2472 CAMLprim void ml_drawtile (value args_v, value ptr_v)
2474 CAMLparam2 (args_v, ptr_v);
2475 int dispx = Int_val (Field (args_v, 0));
2476 int dispy = Int_val (Field (args_v, 1));
2477 int dispw = Int_val (Field (args_v, 2));
2478 int disph = Int_val (Field (args_v, 3));
2479 int tilex = Int_val (Field (args_v, 4));
2480 int tiley = Int_val (Field (args_v, 5));
2481 char *s = String_val (ptr_v);
2482 struct tile *tile = parse_pointer (__func__, s);
2483 int slicey, firstslice;
2484 struct slice *slice;
2485 GLfloat *texcoords = state.texcoords;
2486 GLfloat *vertices = state.vertices;
2488 firstslice = tiley / tile->sliceheight;
2489 slice = &tile->slices[firstslice];
2490 slicey = tiley % tile->sliceheight;
2492 while (disph > 0) {
2493 int dh;
2495 dh = slice->h - slicey;
2496 dh = fz_mini (disph, dh);
2497 uploadslice (tile, slice);
2499 texcoords[0] = tilex; texcoords[1] = slicey;
2500 texcoords[2] = tilex+dispw; texcoords[3] = slicey;
2501 texcoords[4] = tilex; texcoords[5] = slicey+dh;
2502 texcoords[6] = tilex+dispw; texcoords[7] = slicey+dh;
2504 vertices[0] = dispx; vertices[1] = dispy;
2505 vertices[2] = dispx+dispw; vertices[3] = dispy;
2506 vertices[4] = dispx; vertices[5] = dispy+dh;
2507 vertices[6] = dispx+dispw; vertices[7] = dispy+dh;
2509 #if TEXT_TYPE == GL_TEXTURE_2D
2510 for (int i = 0; i < 8; ++i) {
2511 texcoords[i] /= ((i & 1) == 0 ? tile->w : slice->h);
2513 #endif
2515 glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
2516 dispy += dh;
2517 disph -= dh;
2518 slice++;
2519 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2520 slicey = 0;
2522 CAMLreturn0;
2525 static void drawprect (struct page *page, int xoff, int yoff, value rects_v)
2527 fz_matrix ctm, tm, pm;
2528 fz_point p1, p2, p3, p4;
2529 GLfloat *vertices = state.vertices;
2530 double *v = (double *) rects_v;
2532 xoff -= state.pagedims[page->pdimno].bounds.x0;
2533 yoff -= state.pagedims[page->pdimno].bounds.y0;
2534 fz_translate (&tm, xoff, yoff);
2535 pm = pagectm (page);
2536 fz_concat (&ctm, &pm, &tm);
2538 glEnable (GL_BLEND);
2539 glVertexPointer (2, GL_FLOAT, 0, vertices);
2541 glColor4dv (v);
2542 p1.x = v[4];
2543 p1.y = v[5];
2545 p2.x = v[6];
2546 p2.y = v[5];
2548 p3.x = v[6];
2549 p3.y = v[7];
2551 p4.x = v[4];
2552 p4.y = v[7];
2553 solidrect (&ctm, &p1, &p2, &p3, &p4, vertices);
2554 glDisable (GL_BLEND);
2557 CAMLprim value ml_postprocess (value ptr_v, value hlinks_v,
2558 value xoff_v, value yoff_v,
2559 value li_v)
2561 CAMLparam5 (ptr_v, hlinks_v, xoff_v, yoff_v, li_v);
2562 int xoff = Int_val (xoff_v);
2563 int yoff = Int_val (yoff_v);
2564 int noff = Int_val (Field (li_v, 0));
2565 char *targ = String_val (Field (li_v, 1));
2566 int tlen = caml_string_length (Field (li_v, 1));
2567 int hfsize = Int_val (Field (li_v, 2));
2568 char *s = String_val (ptr_v);
2569 int hlmask = Int_val (hlinks_v);
2570 struct page *page = parse_pointer (__func__, s);
2572 if (!page->fzpage) {
2573 /* deal with loadpage failed pages */
2574 goto done;
2577 if (trylock (__func__)) {
2578 noff = -1;
2579 goto done;
2582 ensureannots (page);
2583 if (hlmask & 1) highlightlinks (page, xoff, yoff);
2584 if (hlmask & 2) {
2585 highlightslinks (page, xoff, yoff, noff, targ, tlen, hfsize);
2586 noff = page->slinkcount;
2588 if (page->tgen == state.gen) {
2589 showsel (page, xoff, yoff);
2591 unlock (__func__);
2593 done:
2594 CAMLreturn (Val_int (noff));
2597 CAMLprim void ml_drawprect (value ptr_v, value xoff_v, value yoff_v,
2598 value rects_v)
2600 CAMLparam4 (ptr_v, xoff_v, yoff_v, rects_v);
2601 int xoff = Int_val (xoff_v);
2602 int yoff = Int_val (yoff_v);
2603 char *s = String_val (ptr_v);
2604 struct page *page = parse_pointer (__func__, s);
2606 drawprect (page, xoff, yoff, rects_v);
2607 CAMLreturn0;
2610 static struct annot *getannot (struct page *page, int x, int y)
2612 fz_point p;
2613 fz_matrix ctm;
2614 const fz_matrix *tctm;
2615 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
2617 if (!page->annots) return NULL;
2619 if (pdf) {
2620 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
2621 tctm = &state.pagedims[page->pdimno].tctm;
2623 else {
2624 tctm = &fz_identity;
2627 p.x = x;
2628 p.y = y;
2630 fz_concat (&ctm, tctm, &state.pagedims[page->pdimno].ctm);
2631 fz_invert_matrix (&ctm, &ctm);
2632 fz_transform_point (&p, &ctm);
2634 if (pdf) {
2635 for (int i = 0; i < page->annotcount; ++i) {
2636 struct annot *a = &page->annots[i];
2637 fz_rect rect;
2639 fz_bound_annot (state.ctx, a->annot, &rect);
2640 if (p.x >= rect.x0 && p.x <= rect.x1) {
2641 if (p.y >= rect.y0 && p.y <= rect.y1)
2642 return a;
2646 return NULL;
2649 static fz_link *getlink (struct page *page, int x, int y)
2651 fz_point p;
2652 fz_matrix ctm;
2653 fz_link *link, *links;
2655 links = fz_load_links (state.ctx, page->fzpage);
2657 p.x = x;
2658 p.y = y;
2660 ctm = pagectm (page);
2661 fz_invert_matrix (&ctm, &ctm);
2662 fz_transform_point (&p, &ctm);
2664 for (link = links; link; link = link->next) {
2665 if (p.x >= link->rect.x0 && p.x <= link->rect.x1) {
2666 if (p.y >= link->rect.y0 && p.y <= link->rect.y1) {
2667 return link;
2671 return NULL;
2674 static void ensuretext (struct page *page)
2676 if (state.gen != page->tgen) {
2677 droptext (page);
2678 page->tgen = state.gen;
2680 if (!page->text) {
2681 fz_matrix ctm;
2682 fz_device *tdev;
2684 page->text = fz_new_stext_page (state.ctx,
2685 &state.pagedims[page->pdimno].mediabox);
2686 page->sheet = fz_new_stext_sheet (state.ctx);
2687 tdev = fz_new_stext_device (state.ctx, page->sheet, page->text, 0);
2688 ctm = pagectm (page);
2689 fz_run_display_list (state.ctx, page->dlist,
2690 tdev, &ctm, &fz_infinite_rect, NULL);
2691 qsort (page->text->blocks, page->text->len,
2692 sizeof (*page->text->blocks), compareblocks);
2693 fz_close_device (state.ctx, tdev);
2694 fz_drop_device (state.ctx, tdev);
2698 CAMLprim value ml_find_page_with_links (value start_page_v, value dir_v)
2700 CAMLparam2 (start_page_v, dir_v);
2701 CAMLlocal1 (ret_v);
2702 int i, dir = Int_val (dir_v);
2703 int start_page = Int_val (start_page_v);
2704 int end_page = dir > 0 ? state.pagecount : -1;
2705 pdf_document *pdf;
2707 fz_var (end_page);
2708 ret_v = Val_int (0);
2709 lock (__func__);
2710 pdf = pdf_specifics (state.ctx, state.doc);
2711 for (i = start_page + dir; i != end_page; i += dir) {
2712 int found;
2714 fz_var (found);
2715 if (pdf) {
2716 pdf_page *page = NULL;
2718 fz_var (page);
2719 fz_try (state.ctx) {
2720 page = pdf_load_page (state.ctx, pdf, i);
2721 found = !!page->links || !!page->annots;
2723 fz_catch (state.ctx) {
2724 found = 0;
2726 if (page) {
2727 fz_drop_page (state.ctx, &page->super);
2730 else {
2731 fz_page *page = fz_load_page (state.ctx, state.doc, i);
2732 found = !!fz_load_links (state.ctx, page);
2733 fz_drop_page (state.ctx, page);
2736 if (found) {
2737 ret_v = caml_alloc_small (1, 1);
2738 Field (ret_v, 0) = Val_int (i);
2739 goto unlock;
2742 unlock:
2743 unlock (__func__);
2744 CAMLreturn (ret_v);
2747 enum { dir_first, dir_last };
2748 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
2750 CAMLprim value ml_findlink (value ptr_v, value dir_v)
2752 CAMLparam2 (ptr_v, dir_v);
2753 CAMLlocal2 (ret_v, pos_v);
2754 struct page *page;
2755 int dirtag, i, slinkindex;
2756 struct slink *found = NULL ,*slink;
2757 char *s = String_val (ptr_v);
2759 page = parse_pointer (__func__, s);
2760 ret_v = Val_int (0);
2761 lock (__func__);
2762 ensureslinks (page);
2764 if (Is_block (dir_v)) {
2765 dirtag = Tag_val (dir_v);
2766 switch (dirtag) {
2767 case dir_first_visible:
2769 int x0, y0, dir, first_index, last_index;
2771 pos_v = Field (dir_v, 0);
2772 x0 = Int_val (Field (pos_v, 0));
2773 y0 = Int_val (Field (pos_v, 1));
2774 dir = Int_val (Field (pos_v, 2));
2776 if (dir >= 0) {
2777 dir = 1;
2778 first_index = 0;
2779 last_index = page->slinkcount;
2781 else {
2782 first_index = page->slinkcount - 1;
2783 last_index = -1;
2786 for (i = first_index; i != last_index; i += dir) {
2787 slink = &page->slinks[i];
2788 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2789 found = slink;
2790 break;
2794 break;
2796 case dir_left:
2797 slinkindex = Int_val (Field (dir_v, 0));
2798 found = &page->slinks[slinkindex];
2799 for (i = slinkindex - 1; i >= 0; --i) {
2800 slink = &page->slinks[i];
2801 if (slink->bbox.x0 < found->bbox.x0) {
2802 found = slink;
2803 break;
2806 break;
2808 case dir_right:
2809 slinkindex = Int_val (Field (dir_v, 0));
2810 found = &page->slinks[slinkindex];
2811 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2812 slink = &page->slinks[i];
2813 if (slink->bbox.x0 > found->bbox.x0) {
2814 found = slink;
2815 break;
2818 break;
2820 case dir_down:
2821 slinkindex = Int_val (Field (dir_v, 0));
2822 found = &page->slinks[slinkindex];
2823 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2824 slink = &page->slinks[i];
2825 if (slink->bbox.y0 >= found->bbox.y0) {
2826 found = slink;
2827 break;
2830 break;
2832 case dir_up:
2833 slinkindex = Int_val (Field (dir_v, 0));
2834 found = &page->slinks[slinkindex];
2835 for (i = slinkindex - 1; i >= 0; --i) {
2836 slink = &page->slinks[i];
2837 if (slink->bbox.y0 <= found->bbox.y0) {
2838 found = slink;
2839 break;
2842 break;
2845 else {
2846 dirtag = Int_val (dir_v);
2847 switch (dirtag) {
2848 case dir_first:
2849 found = page->slinks;
2850 break;
2852 case dir_last:
2853 if (page->slinks) {
2854 found = page->slinks + (page->slinkcount - 1);
2856 break;
2859 if (found) {
2860 ret_v = caml_alloc_small (2, 1);
2861 Field (ret_v, 0) = Val_int (found - page->slinks);
2864 unlock (__func__);
2865 CAMLreturn (ret_v);
2868 enum { uuri, utext, uannot };
2870 CAMLprim value ml_getlink (value ptr_v, value n_v)
2872 CAMLparam2 (ptr_v, n_v);
2873 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2874 fz_link *link;
2875 struct page *page;
2876 char *s = String_val (ptr_v);
2877 struct slink *slink;
2879 ret_v = Val_int (0);
2880 page = parse_pointer (__func__, s);
2882 lock (__func__);
2883 ensureslinks (page);
2884 slink = &page->slinks[Int_val (n_v)];
2885 if (slink->tag == SLINK) {
2886 link = slink->u.link;
2887 str_v = caml_copy_string (link->uri);
2888 ret_v = caml_alloc_small (1, uuri);
2889 Field (ret_v, 0) = str_v;
2891 else {
2892 ret_v = caml_alloc_small (1, uannot);
2893 tup_v = caml_alloc_tuple (2);
2894 Field (ret_v, 0) = tup_v;
2895 Field (tup_v, 0) = ptr_v;
2896 Field (tup_v, 1) = n_v;
2898 unlock (__func__);
2900 CAMLreturn (ret_v);
2903 CAMLprim value ml_getannotcontents (value ptr_v, value n_v)
2905 CAMLparam2 (ptr_v, n_v);
2906 pdf_document *pdf;
2907 const char *contents = "";
2909 lock (__func__);
2910 pdf = pdf_specifics (state.ctx, state.doc);
2911 if (pdf) {
2912 char *s = String_val (ptr_v);
2913 struct page *page;
2914 struct slink *slink;
2916 page = parse_pointer (__func__, s);
2917 slink = &page->slinks[Int_val (n_v)];
2918 contents = pdf_annot_contents (state.ctx,
2919 (pdf_annot *) slink->u.annot);
2921 unlock (__func__);
2922 CAMLreturn (caml_copy_string (contents));
2925 CAMLprim value ml_getlinkcount (value ptr_v)
2927 CAMLparam1 (ptr_v);
2928 struct page *page;
2929 char *s = String_val (ptr_v);
2931 page = parse_pointer (__func__, s);
2932 CAMLreturn (Val_int (page->slinkcount));
2935 CAMLprim value ml_getlinkrect (value ptr_v, value n_v)
2937 CAMLparam2 (ptr_v, n_v);
2938 CAMLlocal1 (ret_v);
2939 struct page *page;
2940 struct slink *slink;
2941 char *s = String_val (ptr_v);
2943 page = parse_pointer (__func__, s);
2944 ret_v = caml_alloc_tuple (4);
2945 lock (__func__);
2946 ensureslinks (page);
2948 slink = &page->slinks[Int_val (n_v)];
2949 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2950 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2951 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2952 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2953 unlock (__func__);
2954 CAMLreturn (ret_v);
2957 CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v)
2959 CAMLparam3 (ptr_v, x_v, y_v);
2960 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2961 fz_link *link;
2962 struct annot *annot;
2963 struct page *page;
2964 char *ptr = String_val (ptr_v);
2965 int x = Int_val (x_v), y = Int_val (y_v);
2966 struct pagedim *pdim;
2968 ret_v = Val_int (0);
2969 if (trylock (__func__)) {
2970 goto done;
2973 page = parse_pointer (__func__, ptr);
2974 pdim = &state.pagedims[page->pdimno];
2975 x += pdim->bounds.x0;
2976 y += pdim->bounds.y0;
2979 annot = getannot (page, x, y);
2980 if (annot) {
2981 int i, n = -1;
2983 ensureslinks (page);
2984 for (i = 0; i < page->slinkcount; ++i) {
2985 if (page->slinks[i].tag == SANNOT
2986 && page->slinks[i].u.annot == annot->annot) {
2987 n = i;
2988 break;
2991 ret_v = caml_alloc_small (1, uannot);
2992 tup_v = caml_alloc_tuple (2);
2993 Field (ret_v, 0) = tup_v;
2994 Field (tup_v, 0) = ptr_v;
2995 Field (tup_v, 1) = Val_int (n);
2996 goto unlock;
3000 link = getlink (page, x, y);
3001 if (link) {
3002 str_v = caml_copy_string (link->uri);
3003 ret_v = caml_alloc_small (1, uuri);
3004 Field (ret_v, 0) = str_v;
3006 else {
3007 fz_rect *b;
3008 fz_page_block *pageb;
3009 fz_stext_block *block;
3011 ensuretext (page);
3012 for (pageb = page->text->blocks;
3013 pageb < page->text->blocks + page->text->len;
3014 ++pageb) {
3015 fz_stext_line *line;
3016 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3017 block = pageb->u.text;
3019 b = &block->bbox;
3020 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3021 continue;
3023 for (line = block->lines;
3024 line < block->lines + block->len;
3025 ++line) {
3026 fz_stext_span *span;
3028 b = &line->bbox;
3029 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3030 continue;
3032 for (span = line->first_span; span; span = span->next) {
3033 int charnum;
3035 b = &span->bbox;
3036 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3037 continue;
3039 for (charnum = 0; charnum < span->len; ++charnum) {
3040 fz_rect bbox;
3041 fz_stext_char_bbox (state.ctx, &bbox, span, charnum);
3042 b = &bbox;
3044 if (x >= b->x0 && x <= b->x1
3045 && y >= b->y0 && y <= b->y1) {
3046 fz_stext_style *style = span->text->style;
3047 const char *n2 =
3048 style->font
3049 ? fz_font_name (state.ctx, style->font)
3050 : "Span has no font name"
3052 FT_FaceRec *face = fz_font_ft_face (state.ctx,
3053 style->font);
3054 if (face && face->family_name) {
3055 char *s;
3056 char *n1 = face->family_name;
3057 size_t l1 = strlen (n1);
3058 size_t l2 = strlen (n2);
3060 if (l1 != l2 || memcmp (n1, n2, l1)) {
3061 s = malloc (l1 + l2 + 2);
3062 if (s) {
3063 memcpy (s, n2, l2);
3064 s[l2] = '=';
3065 memcpy (s + l2 + 1, n1, l1 + 1);
3066 str_v = caml_copy_string (s);
3067 free (s);
3071 if (str_v == Val_unit) {
3072 str_v = caml_copy_string (n2);
3074 ret_v = caml_alloc_small (1, utext);
3075 Field (ret_v, 0) = str_v;
3076 goto unlock;
3083 unlock:
3084 unlock (__func__);
3086 done:
3087 CAMLreturn (ret_v);
3090 enum { mark_page, mark_block, mark_line, mark_word };
3092 static int uninteresting (int c)
3094 return c == ' ' || c == '\n' || c == '\t' || c == '\n' || c == '\r'
3095 || ispunct (c);
3098 CAMLprim void ml_clearmark (value ptr_v)
3100 CAMLparam1 (ptr_v);
3101 char *s = String_val (ptr_v);
3102 struct page *page;
3104 if (trylock (__func__)) {
3105 goto done;
3108 page = parse_pointer (__func__, s);
3109 page->fmark.span = NULL;
3110 page->lmark.span = NULL;
3111 page->fmark.i = 0;
3112 page->lmark.i = 0;
3114 unlock (__func__);
3115 done:
3116 CAMLreturn0;
3119 CAMLprim value ml_markunder (value ptr_v, value x_v, value y_v, value mark_v)
3121 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
3122 CAMLlocal1 (ret_v);
3123 fz_rect *b;
3124 struct page *page;
3125 fz_stext_line *line;
3126 fz_page_block *pageb;
3127 fz_stext_block *block;
3128 struct pagedim *pdim;
3129 int mark = Int_val (mark_v);
3130 char *s = String_val (ptr_v);
3131 int x = Int_val (x_v), y = Int_val (y_v);
3133 ret_v = Val_bool (0);
3134 if (trylock (__func__)) {
3135 goto done;
3138 page = parse_pointer (__func__, s);
3139 pdim = &state.pagedims[page->pdimno];
3141 ensuretext (page);
3143 if (mark == mark_page) {
3144 int i;
3145 fz_page_block *pb1 = NULL, *pb2 = NULL;
3147 for (i = 0; i < page->text->len; ++i) {
3148 if (page->text->blocks[i].type == FZ_PAGE_BLOCK_TEXT) {
3149 pb1 = &page->text->blocks[i];
3150 break;
3153 if (!pb1) goto unlock;
3155 for (i = page->text->len - 1; i >= 0; --i) {
3156 if (page->text->blocks[i].type == FZ_PAGE_BLOCK_TEXT) {
3157 pb2 = &page->text->blocks[i];
3158 break;
3161 if (!pb2) goto unlock;
3163 block = pb1->u.text;
3165 page->fmark.i = 0;
3166 page->fmark.span = block->lines->first_span;
3168 block = pb2->u.text;
3169 line = &block->lines[block->len - 1];
3170 page->lmark.i = line->last_span->len - 1;
3171 page->lmark.span = line->last_span;
3172 ret_v = Val_bool (1);
3173 goto unlock;
3176 x += pdim->bounds.x0;
3177 y += pdim->bounds.y0;
3179 for (pageb = page->text->blocks;
3180 pageb < page->text->blocks + page->text->len;
3181 ++pageb) {
3182 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3183 block = pageb->u.text;
3185 b = &block->bbox;
3186 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3187 continue;
3189 if (mark == mark_block) {
3190 page->fmark.i = 0;
3191 page->fmark.span = block->lines->first_span;
3193 line = &block->lines[block->len - 1];
3194 page->lmark.i = line->last_span->len - 1;
3195 page->lmark.span = line->last_span;
3196 ret_v = Val_bool (1);
3197 goto unlock;
3200 for (line = block->lines;
3201 line < block->lines + block->len;
3202 ++line) {
3203 fz_stext_span *span;
3205 b = &line->bbox;
3206 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3207 continue;
3209 if (mark == mark_line) {
3210 page->fmark.i = 0;
3211 page->fmark.span = line->first_span;
3213 page->lmark.i = line->last_span->len - 1;
3214 page->lmark.span = line->last_span;
3215 ret_v = Val_bool (1);
3216 goto unlock;
3219 for (span = line->first_span; span; span = span->next) {
3220 int charnum;
3222 b = &span->bbox;
3223 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3224 continue;
3226 for (charnum = 0; charnum < span->len; ++charnum) {
3227 fz_rect bbox;
3228 fz_stext_char_bbox (state.ctx, &bbox, span, charnum);
3229 b = &bbox;
3231 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1) {
3232 /* unicode ftw */
3233 int charnum2, charnum3 = -1, charnum4 = -1;
3235 if (uninteresting (span->text[charnum].c)) goto unlock;
3237 for (charnum2 = charnum; charnum2 >= 0; --charnum2) {
3238 if (uninteresting (span->text[charnum2].c)) {
3239 charnum3 = charnum2 + 1;
3240 break;
3243 if (charnum3 == -1) charnum3 = 0;
3245 charnum4 = charnum;
3246 for (charnum2 = charnum + 1;
3247 charnum2 < span->len;
3248 ++charnum2) {
3249 if (uninteresting (span->text[charnum2].c)) break;
3250 charnum4 = charnum2;
3253 page->fmark.i = charnum3;
3254 page->fmark.span = span;
3256 page->lmark.i = charnum4;
3257 page->lmark.span = span;
3258 ret_v = Val_bool (1);
3259 goto unlock;
3265 unlock:
3266 if (!Bool_val (ret_v)) {
3267 page->fmark.span = NULL;
3268 page->lmark.span = NULL;
3269 page->fmark.i = 0;
3270 page->lmark.i = 0;
3272 unlock (__func__);
3274 done:
3275 CAMLreturn (ret_v);
3278 CAMLprim value ml_rectofblock (value ptr_v, value x_v, value y_v)
3280 CAMLparam3 (ptr_v, x_v, y_v);
3281 CAMLlocal2 (ret_v, res_v);
3282 fz_rect *b = NULL;
3283 struct page *page;
3284 fz_page_block *pageb;
3285 struct pagedim *pdim;
3286 char *s = String_val (ptr_v);
3287 int x = Int_val (x_v), y = Int_val (y_v);
3289 ret_v = Val_int (0);
3290 if (trylock (__func__)) {
3291 goto done;
3294 page = parse_pointer (__func__, s);
3295 pdim = &state.pagedims[page->pdimno];
3296 x += pdim->bounds.x0;
3297 y += pdim->bounds.y0;
3299 ensuretext (page);
3301 for (pageb = page->text->blocks;
3302 pageb < page->text->blocks + page->text->len;
3303 ++pageb) {
3304 switch (pageb->type) {
3305 case FZ_PAGE_BLOCK_TEXT:
3306 b = &pageb->u.text->bbox;
3307 break;
3309 case FZ_PAGE_BLOCK_IMAGE:
3310 b = &pageb->u.image->bbox;
3311 break;
3313 default:
3314 continue;
3317 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1)
3318 break;
3319 b = NULL;
3321 if (b) {
3322 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3323 ret_v = caml_alloc_small (1, 1);
3324 Store_double_field (res_v, 0, b->x0);
3325 Store_double_field (res_v, 1, b->x1);
3326 Store_double_field (res_v, 2, b->y0);
3327 Store_double_field (res_v, 3, b->y1);
3328 Field (ret_v, 0) = res_v;
3330 unlock (__func__);
3332 done:
3333 CAMLreturn (ret_v);
3336 CAMLprim void ml_seltext (value ptr_v, value rect_v)
3338 CAMLparam2 (ptr_v, rect_v);
3339 fz_rect b;
3340 struct page *page;
3341 struct pagedim *pdim;
3342 char *s = String_val (ptr_v);
3343 int i, x0, x1, y0, y1, fi, li;
3344 fz_stext_line *line;
3345 fz_page_block *pageb;
3346 fz_stext_block *block;
3347 fz_stext_span *span, *fspan, *lspan;
3349 if (trylock (__func__)) {
3350 goto done;
3353 page = parse_pointer (__func__, s);
3354 ensuretext (page);
3356 pdim = &state.pagedims[page->pdimno];
3357 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;
3358 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
3359 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
3360 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
3362 if (y0 > y1) {
3363 int t = y0;
3364 y0 = y1;
3365 y1 = t;
3366 x0 = x1;
3367 x1 = t;
3370 fi = page->fmark.i;
3371 fspan = page->fmark.span;
3373 li = page->lmark.i;
3374 lspan = page->lmark.span;
3376 for (pageb = page->text->blocks;
3377 pageb < page->text->blocks + page->text->len;
3378 ++pageb) {
3379 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3380 block = pageb->u.text;
3381 for (line = block->lines;
3382 line < block->lines + block->len;
3383 ++line) {
3385 for (span = line->first_span; span; span = span->next) {
3386 for (i = 0; i < span->len; ++i) {
3387 fz_stext_char_bbox (state.ctx, &b, span, i);
3389 if (x0 >= b.x0 && x0 <= b.x1
3390 && y0 >= b.y0 && y0 <= b.y1) {
3391 fspan = span;
3392 fi = i;
3394 if (x1 >= b.x0 && x1 <= b.x1
3395 && y1 >= b.y0 && y1 <= b.y1) {
3396 lspan = span;
3397 li = i;
3403 if (x1 < x0 && fspan == lspan) {
3404 i = fi;
3405 span = fspan;
3407 fi = li;
3408 fspan = lspan;
3410 li = i;
3411 lspan = span;
3414 page->fmark.i = fi;
3415 page->fmark.span = fspan;
3417 page->lmark.i = li;
3418 page->lmark.span = lspan;
3420 unlock (__func__);
3422 done:
3423 CAMLreturn0;
3426 static int UNUSED_ATTR pipespan (FILE *f, fz_stext_span *span, int a, int b)
3428 char buf[4];
3429 int i, len, ret;
3431 for (i = a; i <= b; ++i) {
3432 len = fz_runetochar (buf, span->text[i].c);
3433 ret = fwrite (buf, len, 1, f);
3435 if (ret != 1) {
3436 fprintf (stderr, "failed to write %d bytes ret=%d: %s\n",
3437 len, ret, strerror (errno));
3438 return -1;
3441 return 0;
3444 #ifdef __CYGWIN__
3445 CAMLprim value ml_spawn (value UNUSED_ATTR u1, value UNUSED_ATTR u2)
3447 caml_failwith ("ml_popen not implemented under Cygwin");
3449 #else
3450 CAMLprim value ml_spawn (value command_v, value fds_v)
3452 CAMLparam2 (command_v, fds_v);
3453 CAMLlocal2 (l_v, tup_v);
3454 int ret, ret1;
3455 pid_t pid;
3456 char *msg = NULL;
3457 value earg_v = Nothing;
3458 posix_spawnattr_t attr;
3459 posix_spawn_file_actions_t fa;
3460 char *argv[] = { "/bin/sh", "-c", NULL, NULL };
3462 argv[2] = String_val (command_v);
3464 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
3465 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
3468 if ((ret = posix_spawnattr_init (&attr)) != 0) {
3469 msg = "posix_spawnattr_init";
3470 goto fail1;
3473 #ifdef POSIX_SPAWN_USEVFORK
3474 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
3475 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
3476 goto fail;
3478 #endif
3480 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
3481 int fd1, fd2;
3483 tup_v = Field (l_v, 0);
3484 fd1 = Int_val (Field (tup_v, 0));
3485 fd2 = Int_val (Field (tup_v, 1));
3486 if (fd2 < 0) {
3487 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
3488 msg = "posix_spawn_file_actions_addclose";
3489 earg_v = tup_v;
3490 goto fail;
3493 else {
3494 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
3495 msg = "posix_spawn_file_actions_adddup2";
3496 earg_v = tup_v;
3497 goto fail;
3502 if ((ret = posix_spawn (&pid, "/bin/sh", &fa, &attr, argv, environ))) {
3503 msg = "posix_spawn";
3504 goto fail;
3507 fail:
3508 if ((ret1 = posix_spawnattr_destroy (&attr)) != 0) {
3509 fprintf (stderr, "posix_spawnattr_destroy: %s\n", strerror (ret1));
3512 fail1:
3513 if ((ret1 = posix_spawn_file_actions_destroy (&fa)) != 0) {
3514 fprintf (stderr, "posix_spawn_file_actions_destroy: %s\n",
3515 strerror (ret1));
3518 if (msg)
3519 unix_error (ret, msg, earg_v);
3521 CAMLreturn (Val_int (pid));
3523 #endif
3525 CAMLprim value ml_hassel (value ptr_v)
3527 CAMLparam1 (ptr_v);
3528 CAMLlocal1 (ret_v);
3529 struct page *page;
3530 char *s = String_val (ptr_v);
3532 ret_v = Val_bool (0);
3533 if (trylock (__func__)) {
3534 goto done;
3537 page = parse_pointer (__func__, s);
3538 ret_v = Val_bool (page->fmark.span && page->lmark.span);
3539 unlock (__func__);
3540 done:
3541 CAMLreturn (ret_v);
3544 CAMLprim void ml_copysel (value fd_v, value ptr_v)
3546 CAMLparam2 (fd_v, ptr_v);
3547 FILE *f;
3548 int seen = 0;
3549 struct page *page;
3550 fz_stext_line *line;
3551 fz_page_block *pageb;
3552 fz_stext_block *block;
3553 int fd = Int_val (fd_v);
3554 char *s = String_val (ptr_v);
3556 if (trylock (__func__)) {
3557 goto done;
3560 page = parse_pointer (__func__, s);
3562 if (!page->fmark.span || !page->lmark.span) {
3563 fprintf (stderr, "nothing to copy on page %d\n", page->pageno);
3564 goto unlock;
3567 f = fdopen (fd, "w");
3568 if (!f) {
3569 fprintf (stderr, "failed to fdopen sel pipe (from fd %d): %s\n",
3570 fd, strerror (errno));
3571 f = stdout;
3574 for (pageb = page->text->blocks;
3575 pageb < page->text->blocks + page->text->len;
3576 ++pageb) {
3577 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3578 block = pageb->u.text;
3579 for (line = block->lines;
3580 line < block->lines + block->len;
3581 ++line) {
3582 fz_stext_span *span;
3584 for (span = line->first_span; span; span = span->next) {
3585 int a, b;
3587 seen |= span == page->fmark.span || span == page->lmark.span;
3588 a = span == page->fmark.span ? page->fmark.i : 0;
3589 b = span == page->lmark.span ? page->lmark.i : span->len - 1;
3591 if (seen) {
3592 if (pipespan (f, span, a, b)) {
3593 goto close;
3595 if (span == page->lmark.span) {
3596 goto close;
3598 if (span == line->last_span) {
3599 if (putc ('\n', f) == EOF) {
3600 fprintf (stderr,
3601 "failed break line on sel pipe: %s\n",
3602 strerror (errno));
3603 goto close;
3610 close:
3611 if (f != stdout) {
3612 int ret = fclose (f);
3613 fd = -1;
3614 if (ret == -1) {
3615 if (errno != ECHILD) {
3616 fprintf (stderr, "failed to close sel pipe: %s\n",
3617 strerror (errno));
3621 unlock:
3622 unlock (__func__);
3624 done:
3625 if (fd >= 0) {
3626 if (close (fd)) {
3627 fprintf (stderr, "failed to close sel pipe: %s\n",
3628 strerror (errno));
3631 CAMLreturn0;
3634 CAMLprim value ml_getpdimrect (value pagedimno_v)
3636 CAMLparam1 (pagedimno_v);
3637 CAMLlocal1 (ret_v);
3638 int pagedimno = Int_val (pagedimno_v);
3639 fz_rect box;
3641 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3642 if (trylock (__func__)) {
3643 box = fz_empty_rect;
3645 else {
3646 box = state.pagedims[pagedimno].mediabox;
3647 unlock (__func__);
3650 Store_double_field (ret_v, 0, box.x0);
3651 Store_double_field (ret_v, 1, box.x1);
3652 Store_double_field (ret_v, 2, box.y0);
3653 Store_double_field (ret_v, 3, box.y1);
3655 CAMLreturn (ret_v);
3658 CAMLprim value ml_zoom_for_height (value winw_v, value winh_v,
3659 value dw_v, value cols_v)
3661 CAMLparam4 (winw_v, winh_v, dw_v, cols_v);
3662 CAMLlocal1 (ret_v);
3663 int i;
3664 double zoom = -1.;
3665 double maxh = 0.0;
3666 struct pagedim *p;
3667 double winw = Int_val (winw_v);
3668 double winh = Int_val (winh_v);
3669 double dw = Int_val (dw_v);
3670 double cols = Int_val (cols_v);
3671 double pw = 1.0, ph = 1.0;
3673 if (trylock (__func__)) {
3674 goto done;
3677 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3678 double w = p->pagebox.x1 / cols;
3679 double h = p->pagebox.y1;
3680 if (h > maxh) {
3681 maxh = h;
3682 ph = h;
3683 if (state.fitmodel != FitProportional) pw = w;
3685 if ((state.fitmodel == FitProportional) && w > pw) pw = w;
3688 zoom = (((winh / ph) * pw) + dw) / winw;
3689 unlock (__func__);
3690 done:
3691 ret_v = caml_copy_double (zoom);
3692 CAMLreturn (ret_v);
3695 CAMLprim value ml_getmaxw (value unit_v)
3697 CAMLparam1 (unit_v);
3698 CAMLlocal1 (ret_v);
3699 int i;
3700 double maxw = -1.;
3701 struct pagedim *p;
3703 if (trylock (__func__)) {
3704 goto done;
3707 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3708 double w = p->pagebox.x1;
3709 maxw = fz_max (maxw, w);
3712 unlock (__func__);
3713 done:
3714 ret_v = caml_copy_double (maxw);
3715 CAMLreturn (ret_v);
3718 CAMLprim value ml_draw_string (value pt_v, value x_v, value y_v, value string_v)
3720 CAMLparam4 (pt_v, x_v, y_v, string_v);
3721 CAMLlocal1 (ret_v);
3722 int pt = Int_val(pt_v);
3723 int x = Int_val (x_v);
3724 int y = Int_val (y_v);
3725 double w;
3727 w = draw_string (state.face, pt, x, y, String_val (string_v));
3728 ret_v = caml_copy_double (w);
3729 CAMLreturn (ret_v);
3732 CAMLprim value ml_measure_string (value pt_v, value string_v)
3734 CAMLparam2 (pt_v, string_v);
3735 CAMLlocal1 (ret_v);
3736 int pt = Int_val (pt_v);
3737 double w;
3739 w = measure_string (state.face, pt, String_val (string_v));
3740 ret_v = caml_copy_double (w);
3741 CAMLreturn (ret_v);
3744 CAMLprim value ml_getpagebox (value opaque_v)
3746 CAMLparam1 (opaque_v);
3747 CAMLlocal1 (ret_v);
3748 fz_rect rect;
3749 fz_irect bbox;
3750 fz_matrix ctm;
3751 fz_device *dev;
3752 char *s = String_val (opaque_v);
3753 struct page *page = parse_pointer (__func__, s);
3755 ret_v = caml_alloc_tuple (4);
3756 dev = fz_new_bbox_device (state.ctx, &rect);
3757 dev->hints |= FZ_IGNORE_SHADE;
3759 ctm = pagectm (page);
3760 fz_run_page (state.ctx, page->fzpage, dev, &ctm, NULL);
3762 fz_close_device (state.ctx, dev);
3763 fz_drop_device (state.ctx, dev);
3764 fz_round_rect (&bbox, &rect);
3765 Field (ret_v, 0) = Val_int (bbox.x0);
3766 Field (ret_v, 1) = Val_int (bbox.y0);
3767 Field (ret_v, 2) = Val_int (bbox.x1);
3768 Field (ret_v, 3) = Val_int (bbox.y1);
3770 CAMLreturn (ret_v);
3773 CAMLprim void ml_setaalevel (value level_v)
3775 CAMLparam1 (level_v);
3777 state.aalevel = Int_val (level_v);
3778 CAMLreturn0;
3781 #pragma GCC diagnostic push
3782 #pragma GCC diagnostic ignored "-Wvariadic-macros"
3783 #include <X11/Xlib.h>
3784 #include <X11/cursorfont.h>
3785 #pragma GCC diagnostic pop
3787 #ifdef USE_EGL
3788 #include <EGL/egl.h>
3789 #else
3790 #include <GL/glx.h>
3791 #endif
3793 static const int shapes[] = {
3794 XC_left_ptr, XC_hand2, XC_exchange, XC_fleur, XC_xterm
3797 #define CURS_COUNT (sizeof (shapes) / sizeof (shapes[0]))
3799 static struct {
3800 Window wid;
3801 Display *dpy;
3802 #ifdef USE_EGL
3803 EGLContext ctx;
3804 EGLConfig conf;
3805 EGLSurface win;
3806 EGLDisplay *edpy;
3807 #else
3808 GLXContext ctx;
3809 #endif
3810 XVisualInfo *visual;
3811 Cursor curs[CURS_COUNT];
3812 } glx;
3815 static void initcurs (void)
3817 for (size_t n = 0; n < CURS_COUNT; ++n) {
3818 glx.curs[n] = XCreateFontCursor (glx.dpy, shapes[n]);
3822 CAMLprim void ml_setbgcol (value color_v)
3824 CAMLparam1 (color_v);
3825 XSetWindowBackground (glx.dpy, glx.wid, Int_val (color_v));
3826 CAMLreturn0;
3829 #ifdef USE_EGL
3830 CAMLprim value ml_glxinit (value display_v, value wid_v, value screen_v)
3832 CAMLparam3 (display_v, wid_v, screen_v);
3833 int major, minor;
3834 int num_conf;
3835 EGLint visid;
3836 EGLint attribs[] = {
3837 EGL_DEPTH_SIZE, 24,
3838 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
3839 EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
3840 EGL_NONE
3842 EGLConfig conf;
3844 glx.dpy = XOpenDisplay (String_val (display_v));
3845 if (!glx.dpy) {
3846 caml_failwith ("XOpenDisplay");
3849 eglBindAPI (EGL_OPENGL_API);
3851 glx.edpy = eglGetDisplay (glx.dpy);
3852 if (glx.edpy == EGL_NO_DISPLAY) {
3853 caml_failwith ("eglGetDisplay");
3856 if (!eglInitialize (glx.edpy, &major, &minor)) {
3857 caml_failwith ("eglInitialize");
3860 if (!eglChooseConfig (glx.edpy, attribs, &conf, 1, &num_conf) ||
3861 !num_conf) {
3862 caml_failwith ("eglChooseConfig");
3865 if (!eglGetConfigAttrib (glx.edpy, conf, EGL_NATIVE_VISUAL_ID, &visid)) {
3866 caml_failwith ("eglGetConfigAttrib");
3869 glx.conf = conf;
3870 initcurs ();
3872 glx.wid = Int_val (wid_v);
3873 CAMLreturn (Val_int (visid));
3876 CAMLprim value ml_glxcompleteinit (value unit_v)
3878 CAMLparam1 (unit_v);
3880 glx.ctx = eglCreateContext (glx.edpy, glx.conf, EGL_NO_CONTEXT, NULL);
3881 if (!glx.ctx) {
3882 caml_failwith ("eglCreateContext");
3885 glx.win = eglCreateWindowSurface (glx.edpy, glx.conf,
3886 glx.wid, NULL);
3887 if (glx.win == EGL_NO_SURFACE) {
3888 caml_failwith ("eglCreateWindowSurface");
3891 XFree (glx.visual);
3892 if (!eglMakeCurrent (glx.edpy, glx.win, glx.win, glx.ctx)) {
3893 glx.ctx = NULL;
3894 caml_failwith ("eglMakeCurrent");
3896 CAMLreturn (unit_v);
3898 #else
3899 CAMLprim value ml_glxinit (value display_v, value wid_v, value screen_v)
3901 CAMLparam3 (display_v, wid_v, screen_v);
3903 glx.dpy = XOpenDisplay (String_val (display_v));
3904 if (!glx.dpy) {
3905 caml_failwith ("XOpenDisplay");
3908 int attribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
3909 glx.visual = glXChooseVisual (glx.dpy, Int_val (screen_v), attribs);
3910 if (!glx.visual) {
3911 XCloseDisplay (glx.dpy);
3912 caml_failwith ("glXChooseVisual");
3915 initcurs ();
3917 glx.wid = Int_val (wid_v);
3918 CAMLreturn (Val_int (glx.visual->visualid));
3921 CAMLprim value ml_glxcompleteinit (value unit_v)
3923 CAMLparam1 (unit_v);
3925 glx.ctx = glXCreateContext (glx.dpy, glx.visual, NULL, True);
3926 if (!glx.ctx) {
3927 caml_failwith ("glXCreateContext");
3930 XFree (glx.visual);
3931 glx.visual = NULL;
3933 if (!glXMakeCurrent (glx.dpy, glx.wid, glx.ctx)) {
3934 glXDestroyContext (glx.dpy, glx.ctx);
3935 glx.ctx = NULL;
3936 caml_failwith ("glXMakeCurrent");
3938 CAMLreturn (unit_v);
3940 #endif
3942 CAMLprim void ml_setcursor (value cursor_v)
3944 CAMLparam1 (cursor_v);
3945 size_t cursn = Int_val (cursor_v);
3947 if (cursn >= CURS_COUNT) caml_failwith ("cursor index out of range");
3948 XDefineCursor (glx.dpy, glx.wid, glx.curs[cursn]);
3949 XFlush (glx.dpy);
3950 CAMLreturn0;
3953 CAMLprim value ml_swapb (value unit_v)
3955 CAMLparam1 (unit_v);
3956 #ifdef USE_EGL
3957 if (!eglSwapBuffers (glx.edpy, glx.win)) {
3958 caml_failwith ("eglSwapBuffers");
3960 #else
3961 glXSwapBuffers (glx.dpy, glx.wid);
3962 #endif
3963 CAMLreturn (unit_v);
3966 #include "keysym2ucs.c"
3968 CAMLprim value ml_keysymtoutf8 (value keysym_v)
3970 CAMLparam1 (keysym_v);
3971 CAMLlocal1 (str_v);
3972 KeySym keysym = Int_val (keysym_v);
3973 Rune rune;
3974 int len;
3975 char buf[5];
3977 rune = keysym2ucs (keysym);
3978 len = fz_runetochar (buf, rune);
3979 buf[len] = 0;
3980 str_v = caml_copy_string (buf);
3981 CAMLreturn (str_v);
3984 enum { piunknown, pilinux, piosx, pisun, pibsd, picygwin };
3986 CAMLprim value ml_platform (value unit_v)
3988 CAMLparam1 (unit_v);
3989 CAMLlocal2 (tup_v, arr_v);
3990 int platid = piunknown;
3991 struct utsname buf;
3993 #if defined __linux__
3994 platid = pilinux;
3995 #elif defined __CYGWIN__
3996 platid = picygwin;
3997 #elif defined __DragonFly__ || defined __FreeBSD__
3998 || defined __OpenBSD__ || defined __NetBSD__
3999 platid = pibsd;
4000 #elif defined __sun__
4001 platid = pisun;
4002 #elif defined __APPLE__
4003 platid = piosx;
4004 #endif
4005 if (uname (&buf)) err (1, "uname");
4007 tup_v = caml_alloc_tuple (2);
4009 char const *sar[] = {
4010 buf.sysname,
4011 buf.release,
4012 buf.version,
4013 buf.machine,
4014 NULL
4016 arr_v = caml_copy_string_array (sar);
4018 Field (tup_v, 0) = Val_int (platid);
4019 Field (tup_v, 1) = arr_v;
4020 CAMLreturn (tup_v);
4023 CAMLprim void ml_cloexec (value fd_v)
4025 CAMLparam1 (fd_v);
4026 int fd = Int_val (fd_v);
4028 if (fcntl (fd, F_SETFD, FD_CLOEXEC, 1)) {
4029 uerror ("fcntl", Nothing);
4031 CAMLreturn0;
4034 CAMLprim value ml_getpbo (value w_v, value h_v, value cs_v)
4036 CAMLparam2 (w_v, h_v);
4037 CAMLlocal1 (ret_v);
4038 struct bo *pbo;
4039 int w = Int_val (w_v);
4040 int h = Int_val (h_v);
4041 int cs = Int_val (cs_v);
4043 if (state.bo_usable) {
4044 pbo = calloc (sizeof (*pbo), 1);
4045 if (!pbo) {
4046 err (1, "calloc pbo");
4049 switch (cs) {
4050 case 0:
4051 case 1:
4052 pbo->size = w*h*4;
4053 break;
4054 case 2:
4055 pbo->size = w*h*2;
4056 break;
4057 default:
4058 errx (1, "%s: invalid colorspace %d", __func__, cs);
4061 state.glGenBuffersARB (1, &pbo->id);
4062 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, pbo->id);
4063 state.glBufferDataARB (GL_PIXEL_UNPACK_BUFFER_ARB, pbo->size,
4064 NULL, GL_STREAM_DRAW);
4065 pbo->ptr = state.glMapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB,
4066 GL_READ_WRITE);
4067 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
4068 if (!pbo->ptr) {
4069 fprintf (stderr, "glMapBufferARB failed: %#x\n", glGetError ());
4070 state.glDeleteBuffersARB (1, &pbo->id);
4071 free (pbo);
4072 ret_v = caml_copy_string ("0");
4074 else {
4075 int res;
4076 char *s;
4078 res = snprintf (NULL, 0, "%" FMT_ptr, FMT_ptr_cast (pbo));
4079 if (res < 0) {
4080 err (1, "snprintf %" FMT_ptr " failed", FMT_ptr_cast (pbo));
4082 s = malloc (res+1);
4083 if (!s) {
4084 err (1, "malloc %d bytes failed", res+1);
4086 res = sprintf (s, "%" FMT_ptr, FMT_ptr_cast (pbo));
4087 if (res < 0) {
4088 err (1, "sprintf %" FMT_ptr " failed", FMT_ptr_cast (pbo));
4090 ret_v = caml_copy_string (s);
4091 free (s);
4094 else {
4095 ret_v = caml_copy_string ("0");
4097 CAMLreturn (ret_v);
4100 CAMLprim void ml_freepbo (value s_v)
4102 CAMLparam1 (s_v);
4103 char *s = String_val (s_v);
4104 struct tile *tile = parse_pointer (__func__, s);
4106 if (tile->pbo) {
4107 state.glDeleteBuffersARB (1, &tile->pbo->id);
4108 tile->pbo->id = -1;
4109 tile->pbo->ptr = NULL;
4110 tile->pbo->size = -1;
4112 CAMLreturn0;
4115 CAMLprim void ml_unmappbo (value s_v)
4117 CAMLparam1 (s_v);
4118 char *s = String_val (s_v);
4119 struct tile *tile = parse_pointer (__func__, s);
4121 if (tile->pbo) {
4122 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
4123 if (state.glUnmapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB) == GL_FALSE) {
4124 errx (1, "glUnmapBufferARB failed: %#x\n", glGetError ());
4126 tile->pbo->ptr = NULL;
4127 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
4129 CAMLreturn0;
4132 static void setuppbo (void)
4134 #ifdef USE_EGL
4135 #define GGPA(n) (*(void (**) ()) &state.n = eglGetProcAddress (#n))
4136 #else
4137 #define GGPA(n) (*(void (**) ()) &state.n = glXGetProcAddress ((GLubyte *) #n))
4138 #endif
4139 state.bo_usable = GGPA (glBindBufferARB)
4140 && GGPA (glUnmapBufferARB)
4141 && GGPA (glMapBufferARB)
4142 && GGPA (glBufferDataARB)
4143 && GGPA (glGenBuffersARB)
4144 && GGPA (glDeleteBuffersARB);
4145 #undef GGPA
4148 CAMLprim value ml_bo_usable (value unit_v)
4150 CAMLparam1 (unit_v);
4151 CAMLreturn (Val_bool (state.bo_usable));
4154 CAMLprim value ml_unproject (value ptr_v, value x_v, value y_v)
4156 CAMLparam3 (ptr_v, x_v, y_v);
4157 CAMLlocal2 (ret_v, tup_v);
4158 struct page *page;
4159 char *s = String_val (ptr_v);
4160 int x = Int_val (x_v), y = Int_val (y_v);
4161 struct pagedim *pdim;
4162 fz_point p;
4163 fz_matrix ctm;
4165 page = parse_pointer (__func__, s);
4166 pdim = &state.pagedims[page->pdimno];
4168 ret_v = Val_int (0);
4169 if (trylock (__func__)) {
4170 goto done;
4173 if (pdf_specifics (state.ctx, state.doc)) {
4174 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
4175 ctm = state.pagedims[page->pdimno].tctm;
4177 else {
4178 ctm = fz_identity;
4180 p.x = x + pdim->bounds.x0;
4181 p.y = y + pdim->bounds.y0;
4183 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
4184 fz_invert_matrix (&ctm, &ctm);
4185 fz_transform_point (&p, &ctm);
4187 tup_v = caml_alloc_tuple (2);
4188 ret_v = caml_alloc_small (1, 1);
4189 Field (tup_v, 0) = Val_int (p.x);
4190 Field (tup_v, 1) = Val_int (p.y);
4191 Field (ret_v, 0) = tup_v;
4193 unlock (__func__);
4194 done:
4195 CAMLreturn (ret_v);
4198 CAMLprim value ml_project (value ptr_v, value pageno_v, value pdimno_v,
4199 value x_v, value y_v)
4201 CAMLparam5 (ptr_v, pageno_v, pdimno_v, x_v, y_v);
4202 CAMLlocal1 (ret_v);
4203 struct page *page;
4204 char *s = String_val (ptr_v);
4205 int pageno = Int_val (pageno_v);
4206 int pdimno = Int_val (pdimno_v);
4207 double x = Double_val (x_v), y = Double_val (y_v);
4208 struct pagedim *pdim;
4209 fz_point p;
4210 fz_matrix ctm;
4212 ret_v = Val_int (0);
4213 lock (__func__);
4215 if (!*s) {
4216 page = loadpage (pageno, pdimno);
4218 else {
4219 page = parse_pointer (__func__, s);
4221 pdim = &state.pagedims[pdimno];
4223 if (pdf_specifics (state.ctx, state.doc)) {
4224 trimctm (pdf_page_from_fz_page (state.ctx, page->fzpage), page->pdimno);
4225 ctm = state.pagedims[page->pdimno].tctm;
4227 else {
4228 ctm = fz_identity;
4230 p.x = x + pdim->bounds.x0;
4231 p.y = y + pdim->bounds.y0;
4233 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
4234 fz_transform_point (&p, &ctm);
4236 ret_v = caml_alloc_tuple (2);
4237 Field (ret_v, 0) = caml_copy_double (p.x);
4238 Field (ret_v, 1) = caml_copy_double (p.y);
4240 if (!*s) {
4241 freepage (page);
4243 unlock (__func__);
4244 CAMLreturn (ret_v);
4247 CAMLprim void ml_addannot (value ptr_v, value x_v, value y_v,
4248 value contents_v)
4250 CAMLparam4 (ptr_v, x_v, y_v, contents_v);
4251 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4253 if (pdf) {
4254 pdf_annot *annot;
4255 struct page *page;
4256 fz_point p;
4257 char *s = String_val (ptr_v);
4259 page = parse_pointer (__func__, s);
4260 annot = pdf_create_annot (state.ctx,
4261 pdf_page_from_fz_page (state.ctx,
4262 page->fzpage),
4263 PDF_ANNOT_TEXT);
4264 p.x = Int_val (x_v);
4265 p.y = Int_val (y_v);
4266 pdf_set_annot_contents (state.ctx, annot, String_val (contents_v));
4267 pdf_set_text_annot_position (state.ctx, annot, p);
4268 state.dirty = 1;
4270 CAMLreturn0;
4273 CAMLprim void ml_delannot (value ptr_v, value n_v)
4275 CAMLparam2 (ptr_v, n_v);
4276 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4278 if (pdf) {
4279 struct page *page;
4280 char *s = String_val (ptr_v);
4281 struct slink *slink;
4283 page = parse_pointer (__func__, s);
4284 slink = &page->slinks[Int_val (n_v)];
4285 pdf_delete_annot (state.ctx,
4286 pdf_page_from_fz_page (state.ctx, page->fzpage),
4287 (pdf_annot *) slink->u.annot);
4288 state.dirty = 1;
4290 CAMLreturn0;
4293 CAMLprim void ml_modannot (value ptr_v, value n_v, value str_v)
4295 CAMLparam3 (ptr_v, n_v, str_v);
4296 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4298 if (pdf) {
4299 struct page *page;
4300 char *s = String_val (ptr_v);
4301 struct slink *slink;
4303 page = parse_pointer (__func__, s);
4304 slink = &page->slinks[Int_val (n_v)];
4305 pdf_set_annot_contents (state.ctx, (pdf_annot *) slink->u.annot,
4306 String_val (str_v));
4307 state.dirty = 1;
4309 CAMLreturn0;
4312 CAMLprim value ml_hasunsavedchanges (value unit_v)
4314 CAMLparam1 (unit_v);
4315 CAMLreturn (Val_bool (state.dirty));
4318 CAMLprim void ml_savedoc (value path_v)
4320 CAMLparam1 (path_v);
4321 pdf_document *pdf = pdf_specifics (state.ctx, state.doc);
4323 if (pdf) {
4324 pdf_save_document (state.ctx, pdf, String_val (path_v), NULL);
4326 CAMLreturn0;
4329 static void makestippletex (void)
4331 const char pixels[] = "\xff\xff\0\0";
4332 glGenTextures (1, &state.stid);
4333 glBindTexture (GL_TEXTURE_1D, state.stid);
4334 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
4335 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
4336 glTexImage1D (
4337 GL_TEXTURE_1D,
4339 GL_ALPHA,
4342 GL_ALPHA,
4343 GL_UNSIGNED_BYTE,
4344 pixels
4348 CAMLprim value ml_fz_version (value UNUSED_ATTR unit_v)
4350 return caml_copy_string (FZ_VERSION);
4353 #ifdef USE_FONTCONFIG
4354 static struct {
4355 int inited;
4356 FcConfig *config;
4357 } fc;
4359 static fz_font *fc_load_system_font_func (fz_context *ctx,
4360 const char *name,
4361 int bold,
4362 int italic,
4363 int UNUSED_ATTR needs_exact_metrics)
4365 char *buf;
4366 size_t i, size;
4367 fz_font *font;
4368 FcChar8 *path;
4369 FcResult result;
4370 FcPattern *pat, *pat1;
4372 lprintf ("looking up %s bold:%d italic:%d needs_exact_metrics:%d\n",
4373 name, bold, italic, needs_exact_metrics);
4374 if (!fc.inited) {
4375 fc.inited = 1;
4376 fc.config = FcInitLoadConfigAndFonts ();
4377 if (!fc.config) {
4378 lprintf ("FcInitLoadConfigAndFonts failed\n");
4379 return NULL;
4382 if (!fc.config) return NULL;
4384 size = strlen (name);
4385 if (bold) size += sizeof (":bold") - 1;
4386 if (italic) size += sizeof (":italic") - 1;
4387 size += 1;
4389 buf = malloc (size);
4390 if (!buf) {
4391 err (1, "malloc %zu failed", size);
4394 strcpy (buf, name);
4395 if (bold && italic) {
4396 strcat (buf, ":bold:italic");
4398 else {
4399 if (bold) strcat (buf, ":bold");
4400 if (italic) strcat (buf, ":italic");
4402 for (i = 0; i < size; ++i) {
4403 if (buf[i] == ',' || buf[i] == '-') buf[i] = ':';
4406 lprintf ("fcbuf=%s\n", buf);
4407 pat = FcNameParse ((FcChar8 *) buf);
4408 if (!pat) {
4409 printd ("emsg FcNameParse failed\n");
4410 free (buf);
4411 return NULL;
4414 if (!FcConfigSubstitute (fc.config, pat, FcMatchPattern)) {
4415 printd ("emsg FcConfigSubstitute failed\n");
4416 free (buf);
4417 return NULL;
4419 FcDefaultSubstitute (pat);
4421 pat1 = FcFontMatch (fc.config, pat, &result);
4422 if (!pat1) {
4423 printd ("emsg FcFontMatch failed\n");
4424 FcPatternDestroy (pat);
4425 free (buf);
4426 return NULL;
4429 if (FcPatternGetString (pat1, FC_FILE, 0, &path) != FcResultMatch) {
4430 printd ("emsg FcPatternGetString failed\n");
4431 FcPatternDestroy (pat);
4432 FcPatternDestroy (pat1);
4433 free (buf);
4434 return NULL;
4437 #if 0
4438 printd ("emsg name=%s path=%s\n", name, path);
4439 #endif
4440 font = fz_new_font_from_file (ctx, name, (char *) path, 0, 0);
4441 FcPatternDestroy (pat);
4442 FcPatternDestroy (pat1);
4443 free (buf);
4444 return font;
4446 #endif
4448 CAMLprim void ml_init (value csock_v, value params_v)
4450 CAMLparam2 (csock_v, params_v);
4451 CAMLlocal2 (trim_v, fuzz_v);
4452 int ret;
4453 int texcount;
4454 char *fontpath;
4455 int colorspace;
4456 int mustoresize;
4457 int haspboext;
4459 state.csock = Int_val (csock_v);
4460 state.rotate = Int_val (Field (params_v, 0));
4461 state.fitmodel = Int_val (Field (params_v, 1));
4462 trim_v = Field (params_v, 2);
4463 texcount = Int_val (Field (params_v, 3));
4464 state.sliceheight = Int_val (Field (params_v, 4));
4465 mustoresize = Int_val (Field (params_v, 5));
4466 colorspace = Int_val (Field (params_v, 6));
4467 fontpath = String_val (Field (params_v, 7));
4469 if (caml_string_length (Field (params_v, 8)) > 0) {
4470 state.trimcachepath = strdup (String_val (Field (params_v, 8)));
4472 if (!state.trimcachepath) {
4473 fprintf (stderr, "failed to strdup trimcachepath: %s\n",
4474 strerror (errno));
4478 haspboext = Bool_val (Field (params_v, 9));
4480 state.ctx = fz_new_context (NULL, NULL, mustoresize);
4481 fz_register_document_handlers (state.ctx);
4483 #ifdef USE_FONTCONFIG
4484 if (Bool_val (Field (params_v, 11))) {
4485 fz_install_load_system_font_funcs (
4486 state.ctx, fc_load_system_font_func, NULL
4489 #endif
4491 state.trimmargins = Bool_val (Field (trim_v, 0));
4492 fuzz_v = Field (trim_v, 1);
4493 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
4494 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
4495 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
4496 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
4498 set_tex_params (colorspace);
4500 if (*fontpath) {
4501 #ifndef USE_FONTCONFIG
4502 state.face = load_font (fontpath);
4503 #else
4504 FcChar8 *path;
4505 FcResult result;
4506 char *buf = fontpath;
4507 FcPattern *pat, *pat1;
4509 fc.inited = 1;
4510 fc.config = FcInitLoadConfigAndFonts ();
4511 if (!fc.config) {
4512 errx (1, "FcInitLoadConfigAndFonts failed");
4515 pat = FcNameParse ((FcChar8 *) buf);
4516 if (!pat) {
4517 errx (1, "FcNameParse failed");
4520 if (!FcConfigSubstitute (fc.config, pat, FcMatchPattern)) {
4521 errx (1, "FcConfigSubstitute failed");
4523 FcDefaultSubstitute (pat);
4525 pat1 = FcFontMatch (fc.config, pat, &result);
4526 if (!pat1) {
4527 errx (1, "FcFontMatch failed");
4530 if (FcPatternGetString (pat1, FC_FILE, 0, &path) != FcResultMatch) {
4531 errx (1, "FcPatternGetString failed");
4534 state.face = load_font ((char *) path);
4535 FcPatternDestroy (pat);
4536 FcPatternDestroy (pat1);
4537 #endif
4539 else {
4540 int len;
4541 const char *data = pdf_lookup_substitute_font (state.ctx, 0, 0,
4542 0, 0, &len);
4543 state.face = load_builtin_font (data, len);
4545 if (!state.face) _exit (1);
4547 realloctexts (texcount);
4549 if (haspboext) {
4550 setuppbo ();
4553 makestippletex ();
4555 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
4556 if (ret) {
4557 errx (1, "pthread_create: %s", strerror (ret));
4560 CAMLreturn0;