Cosmetics
[llpp.git] / link.c
blobc43297ca26073f73ee827c4e296008d4bcd06ac8
1 /* lots of code c&p-ed directly from mupdf */
2 #include <errno.h>
3 #include <stdio.h>
4 #include <ctype.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <signal.h>
8 #include <wchar.h>
10 #include <unistd.h>
11 #include <pthread.h>
12 #include <sys/uio.h>
13 #include <sys/time.h>
14 #include <sys/types.h>
15 #include <sys/ioctl.h>
17 #ifdef __CYGWIN__
18 #include <cygwin/socket.h> /* FIONREAD */
19 #else
20 #include <spawn.h>
21 #endif
23 #include <regex.h>
24 #include <stdarg.h>
25 #include <limits.h>
27 #include <GL/gl.h>
29 #include <caml/fail.h>
30 #include <caml/alloc.h>
31 #include <caml/memory.h>
32 #include <caml/unixsupport.h>
34 #include <mupdf/fitz.h>
35 #include <mupdf/cbz.h>
36 #include <mupdf/pdf.h>
37 #include <mupdf/xps.h>
39 #include FT_FREETYPE_H
41 #define PIGGYBACK
43 #ifndef __USE_GNU
44 extern char **environ;
45 #endif
47 #define MIN(a,b) ((a) < (b) ? (a) : (b))
48 #define MAX(a,b) ((a) > (b) ? (a) : (b))
50 #if defined __GNUC__
51 #define NORETURN_ATTR __attribute__ ((noreturn))
52 #define UNUSED_ATTR __attribute__ ((unused))
53 #define OPTIMIZE_ATTR(n) __attribute__ ((optimize ("O"#n)))
54 #define GCC_FMT_ATTR(a, b) __attribute__ ((format (printf, a, b)))
55 #else
56 #define NORETURN_ATTR
57 #define UNUSED_ATTR
58 #define OPTIMIZE_ATTR(n)
59 #define GCC_FMT_ATTR(a, b)
60 #endif
62 #define FMT_s "zu"
64 #define FMT_ptr "p"
65 #define FMT_ptr_cast(p) (p)
66 #define FMT_ptr_cast2(p) (p)
68 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
69 err (int exitcode, const char *fmt, ...)
71 va_list ap;
72 int savederrno;
74 savederrno = errno;
75 va_start (ap, fmt);
76 vfprintf (stderr, fmt, ap);
77 va_end (ap);
78 fprintf (stderr, ": %s\n", strerror (savederrno));
79 fflush (stderr);
80 _exit (exitcode);
83 static void NORETURN_ATTR GCC_FMT_ATTR (2, 3)
84 errx (int exitcode, const char *fmt, ...)
86 va_list ap;
88 va_start (ap, fmt);
89 vfprintf (stderr, fmt, ap);
90 va_end (ap);
91 fputc ('\n', stderr);
92 fflush (stderr);
93 _exit (exitcode);
96 #ifndef GL_TEXTURE_RECTANGLE_ARB
97 #define GL_TEXTURE_RECTANGLE_ARB 0x84F5
98 #endif
100 #ifndef GL_BGRA
101 #define GL_BGRA 0x80E1
102 #endif
104 #ifndef GL_UNSIGNED_INT_8_8_8_8
105 #define GL_UNSIGNED_INT_8_8_8_8 0x8035
106 #endif
108 #ifndef GL_UNSIGNED_INT_8_8_8_8_REV
109 #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
110 #endif
112 #if 0
113 #define lprintf printf
114 #else
115 #define lprintf(...)
116 #endif
118 #define ARSERT(cond) for (;;) { \
119 if (!(cond)) { \
120 errx (1, "%s:%d " #cond, __FILE__, __LINE__); \
122 break; \
125 struct slice {
126 int h;
127 int texindex;
130 struct tile {
131 int w, h;
132 int slicecount;
133 int sliceheight;
134 struct pbo *pbo;
135 fz_pixmap *pixmap;
136 struct slice slices[1];
139 struct pagedim {
140 int pageno;
141 int rotate;
142 int left;
143 int tctmready;
144 fz_irect bounds;
145 fz_rect pagebox;
146 fz_rect mediabox;
147 fz_matrix ctm, zoomctm, lctm, tctm;
150 struct slink {
151 fz_irect bbox;
152 fz_link *link;
155 enum { DPDF, DXPS, DCBZ };
157 struct page {
158 int tgen;
159 int sgen;
160 int type;
161 int pageno;
162 int pdimno;
163 fz_text_page *text;
164 fz_text_sheet *sheet;
165 union {
166 void *ptr;
167 pdf_page *pdfpage;
168 xps_page *xpspage;
169 cbz_page *cbzpage;
170 } u;
171 fz_display_list *dlist;
172 int slinkcount;
173 struct slink *slinks;
174 struct mark {
175 int i;
176 fz_text_span *span;
177 } fmark, lmark;
178 void (*freepage) (void *);
181 struct {
182 int type;
183 int sliceheight;
184 struct pagedim *pagedims;
185 int pagecount;
186 int pagedimcount;
187 union {
188 pdf_document *pdf;
189 xps_document *xps;
190 cbz_document *cbz;
191 } u;
192 fz_context *ctx;
193 int w, h;
195 int texindex;
196 int texcount;
197 GLuint *texids;
199 GLenum texiform;
200 GLenum texform;
201 GLenum texty;
203 fz_colorspace *colorspace;
205 struct {
206 int w, h;
207 struct slice *slice;
208 } *texowners;
210 int rotate;
211 enum { FitWidth, FitProportional, FitPage } fitmodel;
212 int trimmargins;
213 int needoutline;
214 int gen;
215 int aalevel;
217 int trimanew;
218 fz_irect trimfuzz;
219 fz_pixmap *pig;
221 pthread_t thread;
222 int cr, cw;
223 FT_Face face;
225 void (*closedoc) (void);
226 void (*freepage) (void *);
228 char *trimcachepath;
229 int cxack;
231 GLuint stid;
233 int pbo_usable;
234 void (*glBindBufferARB) (GLenum, GLuint);
235 GLboolean (*glUnmapBufferARB) (GLenum);
236 void *(*glMapBufferARB) (GLenum, GLenum);
237 void (*glBufferDataARB) (GLenum, GLsizei, void *, GLenum);
238 void (*glGenBuffersARB) (GLsizei, GLuint *);
239 void (*glDeleteBuffersARB) (GLsizei, GLuint *);
240 } state;
242 struct pbo {
243 GLuint id;
244 void *ptr;
245 size_t size;
248 static void UNUSED_ATTR debug_rect (const char *cap, fz_rect r)
250 printf ("%s(rect) %.2f,%.2f,%.2f,%.2f\n", cap, r.x0, r.y0, r.x1, r.y1);
253 static void UNUSED_ATTR debug_bbox (const char *cap, fz_irect r)
255 printf ("%s(bbox) %d,%d,%d,%d\n", cap, r.x0, r.y0, r.x1, r.y1);
258 static void UNUSED_ATTR debug_matrix (const char *cap, fz_matrix m)
260 printf ("%s(matrix) %.2f,%.2f,%.2f,%.2f %.2f %.2f\n", cap,
261 m.a, m.b, m.c, m.d, m.e, m.f);
264 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
266 static void lock (const char *cap)
268 int ret = pthread_mutex_lock (&mutex);
269 if (ret) {
270 errx (1, "%s: pthread_mutex_lock: %s", cap, strerror (ret));
274 static void unlock (const char *cap)
276 int ret = pthread_mutex_unlock (&mutex);
277 if (ret) {
278 errx (1, "%s: pthread_mutex_unlock: %s", cap, strerror (ret));
282 static int trylock (const char *cap)
284 int ret = pthread_mutex_trylock (&mutex);
285 if (ret && ret != EBUSY) {
286 errx (1, "%s: pthread_mutex_trylock: %s", cap, strerror (ret));
288 return ret == EBUSY;
291 static void *parse_pointer (const char *cap, const char *s)
293 int ret;
294 void *ptr;
296 ret = sscanf (s, "%" FMT_ptr, FMT_ptr_cast (&ptr));
297 if (ret != 1) {
298 errx (1, "%s: cannot parse pointer in `%s'", cap, s);
300 return ptr;
303 static double now (void)
305 struct timeval tv;
307 if (gettimeofday (&tv, NULL)) {
308 err (1, "gettimeofday");
310 return tv.tv_sec + tv.tv_usec*1e-6;
313 static int hasdata (void)
315 int ret, avail;
316 ret = ioctl (state.cr, FIONREAD, &avail);
317 if (ret) err (1, "hasdata: FIONREAD error ret=%d", ret);
318 return avail > 0;
321 CAMLprim value ml_hasdata (value fd_v)
323 CAMLparam1 (fd_v);
324 int ret, avail;
326 ret = ioctl (Int_val (fd_v), FIONREAD, &avail);
327 if (ret) uerror ("ioctl (FIONREAD)", Nothing);
328 CAMLreturn (Val_bool (avail > 0));
331 static void readdata (void *p, int size)
333 ssize_t n;
335 again:
336 n = read (state.cr, p, size);
337 if (n < 0) {
338 if (errno == EINTR) goto again;
339 err (1, "read (req %d, ret %zd)", size, n);
341 if (n - size) {
342 if (!n) errx (1, "EOF while reading");
343 errx (1, "read (req %d, ret %zd)", size, n);
347 static void writedata (char *p, int size)
349 ssize_t n;
350 char buf[4];
351 struct iovec iovec[2];
353 buf[0] = (size >> 24) & 0xff;
354 buf[1] = (size >> 16) & 0xff;
355 buf[2] = (size >> 8) & 0xff;
356 buf[3] = (size >> 0) & 0xff;
358 iovec[0].iov_base = buf;
359 iovec[0].iov_len = 4;
360 iovec[1].iov_base = p;
361 iovec[1].iov_len = size;
363 n = writev (state.cw, iovec, 2);
364 if (n - size - 4) {
365 if (!n) errx (1, "EOF while writing data");
366 err (1, "write (req %d, ret %zd)", size + 4, n);
370 static int readlen (void)
372 unsigned char p[4];
374 readdata (p, 4);
375 return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
378 static void GCC_FMT_ATTR (1, 2) printd (const char *fmt, ...)
380 int size = 200, len;
381 va_list ap;
382 char *buf;
384 buf = malloc (size);
385 for (;;) {
386 if (!buf) err (1, "malloc for temp buf (%d bytes) failed", size);
388 va_start (ap, fmt);
389 len = vsnprintf (buf, size, fmt, ap);
390 va_end (ap);
392 if (len > -1 && len < size) {
393 writedata (buf, len);
394 break;
397 if (len > -1) {
398 size = len + 1;
400 else {
401 size *= 2;
403 buf = realloc (buf, size);
405 free (buf);
408 static void closepdf (void)
410 if (state.u.pdf) {
411 pdf_close_document (state.u.pdf);
412 state.u.pdf = NULL;
416 static void closexps (void)
418 if (state.u.xps) {
419 xps_close_document (state.u.xps);
420 state.u.xps = NULL;
424 static void closecbz (void)
426 if (state.u.cbz) {
427 cbz_close_document (state.u.cbz);
428 state.u.cbz = NULL;
432 static void freepdfpage (void *ptr)
434 pdf_free_page (state.u.pdf, ptr);
437 static void freeemptyxxxpage (void *ptr)
439 (void) ptr;
442 static void freexpspage (void *ptr)
444 xps_free_page (state.u.xps, ptr);
447 static void freecbzpage (void *ptr)
449 cbz_free_page (state.u.cbz, ptr);
452 static void openxref (char *filename, char *password)
454 int i, len;
456 for (i = 0; i < state.texcount; ++i) {
457 state.texowners[i].w = -1;
458 state.texowners[i].slice = NULL;
461 if (state.closedoc) state.closedoc ();
463 len = strlen (filename);
465 state.type = DPDF;
466 if (len > 4) {
467 char ext[4];
469 ext[0] = tolower ((int) filename[len-3]);
470 ext[1] = tolower ((int) filename[len-2]);
471 ext[2] = tolower ((int) filename[len-1]);
473 /**/ if (ext[0] == 'x' && ext[1] == 'p' && ext[2] == 's') {
474 state.type = DXPS;
476 else if (ext[0] == 'c' && ext[1] == 'b' && ext[2] == 'z') {
477 state.type = DCBZ;
481 if (state.pagedims) {
482 free (state.pagedims);
483 state.pagedims = NULL;
485 state.pagedimcount = 0;
487 fz_set_aa_level (state.ctx, state.aalevel);
488 switch (state.type) {
489 case DPDF:
490 state.u.pdf = pdf_open_document (state.ctx, filename);
491 if (pdf_needs_password (state.u.pdf)) {
492 int okay = pdf_authenticate_password (state.u.pdf, password);
493 if (!okay) {
494 errx (1, "invalid password");
497 state.pagecount = pdf_count_pages (state.u.pdf);
498 state.closedoc = closepdf;
499 state.freepage = freepdfpage;
500 break;
502 case DXPS:
503 state.u.xps = xps_open_document (state.ctx, filename);
504 state.pagecount = xps_count_pages (state.u.xps);
505 state.closedoc = closexps;
506 state.freepage = freexpspage;
507 break;
509 case DCBZ:
510 state.u.cbz = cbz_open_document (state.ctx, filename);
511 state.pagecount = cbz_count_pages (state.u.cbz);
512 state.closedoc = closecbz;
513 state.freepage = freecbzpage;
514 break;
518 static void pdfinfo (void)
520 if (state.type == DPDF) {
521 pdf_obj *infoobj;
523 printd ("info PDF version\t%d.%d",
524 state.u.pdf->version / 10, state.u.pdf->version % 10);
526 infoobj = pdf_dict_gets (pdf_trailer (state.u.pdf), "Info");
527 if (infoobj) {
528 int i;
529 char *s;
530 char *items[] = { "Title", "Author", "Creator",
531 "Producer", "CreationDate" };
533 for (i = 0; i < sizeof (items) / sizeof (*items); ++i) {
534 pdf_obj *obj = pdf_dict_gets (infoobj, items[i]);
535 s = pdf_to_utf8 (state.u.pdf, obj);
536 if (*s) {
537 if (i == 0) {
538 printd ("title %s", s);
540 printd ("info %s\t%s", items[i], s);
542 fz_free (state.ctx, s);
545 printd ("infoend");
549 static void unlinktile (struct tile *tile)
551 int i;
553 for (i = 0; i < tile->slicecount; ++i) {
554 struct slice *s = &tile->slices[i];
556 if (s->texindex != -1) {
557 if (state.texowners[s->texindex].slice == s) {
558 state.texowners[s->texindex].slice = NULL;
564 static void freepage (struct page *page)
566 if (page->text) {
567 fz_free_text_page (state.ctx, page->text);
569 if (page->sheet) {
570 fz_free_text_sheet (state.ctx, page->sheet);
572 if (page->slinks) {
573 free (page->slinks);
575 fz_drop_display_list (state.ctx, page->dlist);
576 page->freepage (page->u.ptr);
577 free (page);
580 static void freetile (struct tile *tile)
582 unlinktile (tile);
583 if (!tile->pbo) {
584 #ifndef PIGGYBACK
585 fz_drop_pixmap (state.ctx, tile->pixmap);
586 #else
587 if (state.pig) {
588 fz_drop_pixmap (state.ctx, state.pig);
590 state.pig = tile->pixmap;
591 #endif
593 else {
594 free (tile->pbo);
595 fz_drop_pixmap (state.ctx, tile->pixmap);
597 free (tile);
600 #ifdef __ALTIVEC__
601 #include <stdint.h>
602 #include <altivec.h>
604 static int cacheline32bytes;
606 static void __attribute__ ((constructor)) clcheck (void)
608 char **envp = environ;
609 unsigned long *auxv;
611 while (*envp++);
613 for (auxv = (unsigned long *) envp; *auxv != 0; auxv += 2) {
614 if (*auxv == 19) {
615 cacheline32bytes = auxv[1] == 32;
616 return;
621 static void OPTIMIZE_ATTR (3) clearpixmap (fz_pixmap *pixmap)
623 size_t size = pixmap->w * pixmap->h * pixmap->n;
624 if (cacheline32bytes && size > 32) {
625 intptr_t a1, a2, diff;
626 size_t sizea, i;
627 vector unsigned char v = vec_splat_u8 (-1);
628 vector unsigned char *p;
630 a1 = a2 = (intptr_t) pixmap->samples;
631 a2 = (a1 + 31) & ~31;
632 diff = a2 - a1;
633 sizea = size - diff;
634 p = (void *) a2;
636 while (a1 != a2) *(char *) a1++ = 0xff;
637 for (i = 0; i < (sizea & ~31); i += 32) {
638 __asm volatile ("dcbz %0, %1"::"b"(a2),"r"(i));
639 vec_st (v, i, p);
640 vec_st (v, i + 16, p);
642 while (i < sizea) *((char *) a1 + i++) = 0xff;
644 else fz_clear_pixmap_with_value (state.ctx, pixmap, 0xff);
646 #else
647 #define clearpixmap(p) fz_clear_pixmap_with_value (state.ctx, p, 0xff)
648 #endif
650 static void trimctm (pdf_page *page, int pindex)
652 fz_matrix ctm;
653 struct pagedim *pdim = &state.pagedims[pindex];
655 if (!pdim->tctmready) {
656 if (state.trimmargins) {
657 fz_rect realbox;
658 fz_matrix rm, sm, tm, im, ctm1;
660 fz_rotate (&rm, -pdim->rotate);
661 fz_scale (&sm, 1, -1);
662 fz_concat (&ctm, &rm, &sm);
663 realbox = pdim->mediabox;
664 fz_transform_rect (&realbox, &ctm);
665 fz_translate (&tm, -realbox.x0, -realbox.y0);
666 fz_concat (&ctm1, &ctm, &tm);
667 fz_invert_matrix (&im, &page->ctm);
668 fz_concat (&ctm, &im, &ctm1);
670 else {
671 ctm = fz_identity;
673 pdim->tctm = ctm;
674 pdim->tctmready = 1;
678 static fz_matrix pagectm (struct page *page)
680 fz_matrix ctm, tm;
682 if (page->type == DPDF) {
683 trimctm (page->u.pdfpage, page->pdimno);
684 fz_concat (&ctm,
685 &state.pagedims[page->pdimno].tctm,
686 &state.pagedims[page->pdimno].ctm);
688 else {
689 struct pagedim *pdim = &state.pagedims[page->pdimno];
691 fz_translate (&tm, -pdim->mediabox.x0, -pdim->mediabox.y0);
692 fz_concat (&ctm, &tm, &state.pagedims[page->pdimno].ctm);
694 return ctm;
697 static void *loadpage (int pageno, int pindex)
699 fz_device *dev;
700 struct page *page;
702 page = calloc (sizeof (struct page), 1);
703 if (!page) {
704 err (1, "calloc page %d", pageno);
707 page->dlist = fz_new_display_list (state.ctx);
708 dev = fz_new_list_device (state.ctx, page->dlist);
709 fz_try (state.ctx) {
710 switch (state.type) {
711 case DPDF:
712 page->u.pdfpage = pdf_load_page (state.u.pdf, pageno);
713 pdf_run_page (state.u.pdf, page->u.pdfpage, dev,
714 &fz_identity, NULL);
715 page->freepage = freepdfpage;
716 break;
718 case DXPS:
719 page->u.xpspage = xps_load_page (state.u.xps, pageno);
720 xps_run_page (state.u.xps, page->u.xpspage, dev,
721 &fz_identity, NULL);
722 page->freepage = freexpspage;
723 break;
725 case DCBZ:
726 page->u.cbzpage = cbz_load_page (state.u.cbz, pageno);
727 cbz_run_page (state.u.cbz, page->u.cbzpage, dev,
728 &fz_identity, NULL);
729 page->freepage = freecbzpage;
730 break;
733 fz_catch (state.ctx) {
734 page->u.ptr = NULL;
735 page->freepage = freeemptyxxxpage;
737 fz_free_device (dev);
739 page->pdimno = pindex;
740 page->pageno = pageno;
741 page->sgen = state.gen;
742 page->tgen = state.gen;
743 page->type = state.type;
745 return page;
748 static struct tile *alloctile (int h)
750 int i;
751 int slicecount;
752 size_t tilesize;
753 struct tile *tile;
755 slicecount = (h + state.sliceheight - 1) / state.sliceheight;
756 tilesize = sizeof (*tile) + ((slicecount - 1) * sizeof (struct slice));
757 tile = calloc (tilesize, 1);
758 if (!tile) {
759 err (1, "can not allocate tile (%" FMT_s " bytes)", tilesize);
761 for (i = 0; i < slicecount; ++i) {
762 int sh = MIN (h, state.sliceheight);
763 tile->slices[i].h = sh;
764 tile->slices[i].texindex = -1;
765 h -= sh;
767 tile->slicecount = slicecount;
768 tile->sliceheight = state.sliceheight;
769 return tile;
772 #ifdef OBSCURED_OPT
773 struct obs {
774 int cured;
775 fz_irect b;
778 static void obs_fill_image (fz_device *dev, fz_image *image,
779 const fz_matrix *ctm, float alpha)
781 struct obs *obs = dev->user;
783 if (!obs->cured && fabs (1.0 - alpha) < 1e6) {
784 fz_irect b;
785 fz_rect rect = fz_unit_rect;
787 fz_transform_rect (&rect, ctm);
788 fz_round_rect (&b, &rect);
789 fz_intersect_irect (&b, &obs->b);
790 obs->cured = b.x0 == obs->b.x0
791 && b.x1 == obs->b.x1
792 && b.y0 == obs->b.y0
793 && b.y1 == obs->b.y1;
797 static int obscured (struct page *page, fz_irect bbox)
799 fz_rect rect;
800 fz_matrix ctm;
801 fz_device dev;
802 struct obs obs;
804 memset (&dev, 0, sizeof (dev));
805 memset (&obs, 0, sizeof (obs));
806 dev.hints = 0;
807 dev.flags = 0;
808 dev.user = &obs;
809 dev.ctx = state.ctx;
810 dev.fill_image = obs_fill_image;
811 obs.b = bbox;
812 fz_rect_from_irect (&rect, &bbox);
813 ctm = pagectm (page);
814 fz_run_display_list (page->dlist, &dev, &ctm, &rect, NULL);
815 return obs.cured;
817 #define OBSCURED obscured
818 #else
819 #define OBSCURED(a, b) 0
820 #endif
822 static struct tile *rendertile (struct page *page, int x, int y, int w, int h,
823 struct pbo *pbo)
825 fz_rect rect;
826 fz_irect bbox;
827 fz_matrix ctm;
828 fz_device *dev;
829 struct tile *tile;
830 struct pagedim *pdim;
832 tile = alloctile (h);
833 pdim = &state.pagedims[page->pdimno];
835 bbox = pdim->bounds;
836 bbox.x0 += x;
837 bbox.y0 += y;
838 bbox.x1 = bbox.x0 + w;
839 bbox.y1 = bbox.y0 + h;
841 if (state.pig) {
842 if (state.pig->w == w
843 && state.pig->h == h
844 && state.pig->colorspace == state.colorspace) {
845 tile->pixmap = state.pig;
846 tile->pixmap->x = bbox.x0;
847 tile->pixmap->y = bbox.y0;
849 else {
850 fz_drop_pixmap (state.ctx, state.pig);
852 state.pig = NULL;
854 if (!tile->pixmap) {
855 if (pbo) {
856 tile->pixmap =
857 fz_new_pixmap_with_bbox_and_data (state.ctx, state.colorspace,
858 &bbox, pbo->ptr);
859 tile->pbo = pbo;
861 else {
862 tile->pixmap =
863 fz_new_pixmap_with_bbox (state.ctx, state.colorspace, &bbox);
867 tile->w = w;
868 tile->h = h;
869 if (!page->u.ptr || ((w < 128 && h < 128) || !OBSCURED (page, bbox))) {
870 clearpixmap (tile->pixmap);
872 dev = fz_new_draw_device (state.ctx, tile->pixmap);
873 ctm = pagectm (page);
874 fz_rect_from_irect (&rect, &bbox);
875 fz_run_display_list (page->dlist, dev, &ctm, &rect, NULL);
876 fz_free_device (dev);
878 return tile;
881 static void initpdims (void)
883 double start, end;
884 int pageno, trim, show;
885 FILE *trimf = NULL;
886 int trimw = 0, cxcount;
888 start = now ();
890 if (state.trimmargins && state.trimcachepath) {
891 trimf = fopen (state.trimcachepath, "rb");
892 if (!trimf) {
893 trimf = fopen (state.trimcachepath, "wb");
894 trimw = 1;
898 if (state.trimmargins || state.type == DPDF || !state.cxack)
899 cxcount = state.pagecount;
900 else
901 cxcount = MIN (state.pagecount, 1);
903 for (pageno = 0; pageno < cxcount; ++pageno) {
904 int rotate = 0;
905 struct pagedim *p;
906 fz_rect mediabox;
908 switch (state.type) {
909 case DPDF: {
910 pdf_obj *pageref = pdf_lookup_page_obj (state.u.pdf, pageno);
911 pdf_obj *pageobj = pdf_resolve_indirect (pageref);
913 if (state.trimmargins) {
914 pdf_obj *obj;
915 pdf_page *page;
917 fz_try (state.ctx) {
918 page = pdf_load_page (state.u.pdf, pageno);
919 obj = pdf_dict_gets (pageobj, "llpp.TrimBox");
920 trim = state.trimanew || !obj;
921 if (trim) {
922 fz_rect rect;
923 fz_matrix ctm;
924 fz_device *dev;
926 dev = fz_new_bbox_device (state.ctx, &rect);
927 dev->hints |= FZ_IGNORE_SHADE;
928 fz_invert_matrix (&ctm, &page->ctm);
929 pdf_run_page (state.u.pdf, page, dev,
930 &fz_identity, NULL);
931 fz_free_device (dev);
933 rect.x0 += state.trimfuzz.x0;
934 rect.x1 += state.trimfuzz.x1;
935 rect.y0 += state.trimfuzz.y0;
936 rect.y1 += state.trimfuzz.y1;
937 fz_transform_rect (&rect, &ctm);
938 fz_intersect_rect (&rect, &page->mediabox);
940 if (fz_is_empty_rect (&rect)) {
941 mediabox = page->mediabox;
943 else {
944 mediabox = rect;
947 obj = pdf_new_array (state.u.pdf, 4);
948 pdf_array_push (obj, pdf_new_real (state.u.pdf,
949 mediabox.x0));
950 pdf_array_push (obj, pdf_new_real (state.u.pdf,
951 mediabox.y0));
952 pdf_array_push (obj, pdf_new_real (state.u.pdf,
953 mediabox.x1));
954 pdf_array_push (obj, pdf_new_real (state.u.pdf,
955 mediabox.y1));
956 pdf_dict_puts (pageobj, "llpp.TrimBox", obj);
958 else {
959 mediabox.x0 = pdf_to_real (pdf_array_get (obj, 0));
960 mediabox.y0 = pdf_to_real (pdf_array_get (obj, 1));
961 mediabox.x1 = pdf_to_real (pdf_array_get (obj, 2));
962 mediabox.y1 = pdf_to_real (pdf_array_get (obj, 3));
965 rotate = page->rotate;
966 pdf_free_page (state.u.pdf, page);
968 show = trim ? pageno % 5 == 0 : pageno % 20 == 0;
969 if (show) {
970 printd ("progress %f Trimming %d",
971 (double) (pageno + 1) / state.pagecount,
972 pageno + 1);
975 fz_catch (state.ctx) {
976 fprintf (stderr, "failed to load page %d\n", pageno+1);
979 else {
980 int empty = 0;
981 fz_rect cropbox;
983 pdf_to_rect (state.ctx, pdf_dict_gets (pageobj, "MediaBox"),
984 &mediabox);
985 if (fz_is_empty_rect (&mediabox)) {
986 mediabox.x0 = 0;
987 mediabox.y0 = 0;
988 mediabox.x1 = 612;
989 mediabox.y1 = 792;
990 empty = 1;
993 pdf_to_rect (state.ctx, pdf_dict_gets (pageobj, "CropBox"),
994 &cropbox);
995 if (!fz_is_empty_rect (&cropbox)) {
996 fz_intersect_rect (&mediabox, &cropbox);
998 else {
999 if (empty) {
1000 fprintf (stderr, "cannot find page size for page %d\n",
1001 pageno+1);
1004 rotate = pdf_to_int (pdf_dict_gets (pageobj, "Rotate"));
1006 break;
1009 case DXPS:
1011 xps_page *page;
1013 fz_try (state.ctx) {
1014 page = xps_load_page (state.u.xps, pageno);
1015 xps_bound_page (state.u.xps, page, &mediabox);
1016 rotate = 0;
1017 if (state.trimmargins) {
1018 fz_rect rect;
1019 fz_device *dev;
1021 dev = fz_new_bbox_device (state.ctx, &rect);
1022 dev->hints |= FZ_IGNORE_SHADE;
1023 xps_run_page (state.u.xps, page, dev,
1024 &fz_identity, NULL);
1025 fz_free_device (dev);
1027 rect.x0 += state.trimfuzz.x0;
1028 rect.x1 += state.trimfuzz.x1;
1029 rect.y0 += state.trimfuzz.y0;
1030 rect.y1 += state.trimfuzz.y1;
1031 fz_intersect_rect (&rect, &mediabox);
1033 if (!fz_is_empty_rect (&rect)) {
1034 mediabox = rect;
1037 xps_free_page (state.u.xps, page);
1038 if (!state.cxack) {
1039 printd ("progress %f loading %d",
1040 (double) (pageno + 1) / state.pagecount,
1041 pageno + 1);
1044 fz_catch (state.ctx) {
1047 break;
1049 case DCBZ:
1051 rotate = 0;
1052 mediabox.x0 = 0;
1053 mediabox.y0 = 0;
1054 mediabox.x1 = 900;
1055 mediabox.y1 = 900;
1056 if (state.trimmargins && trimw) {
1057 cbz_page *page;
1059 fz_try (state.ctx) {
1060 page = cbz_load_page (state.u.cbz, pageno);
1061 cbz_bound_page (state.u.cbz, page, &mediabox);
1062 cbz_free_page (state.u.cbz, page);
1063 printd ("progress %f Trimming %d",
1064 (double) (pageno + 1) / state.pagecount,
1065 pageno + 1);
1067 fz_catch (state.ctx) {
1068 fprintf (stderr, "failed to load page %d\n", pageno+1);
1070 if (trimf) {
1071 int n = fwrite (&mediabox, sizeof (mediabox), 1, trimf);
1072 if (n - 1) {
1073 err (1, "fwrite trim mediabox");
1077 else {
1078 if (trimf) {
1079 int n = fread (&mediabox, sizeof (mediabox), 1, trimf);
1080 if (n - 1) {
1081 err (1, "fread trim mediabox");
1084 else {
1085 if (state.cxack) {
1086 cbz_page *page;
1087 fz_try (state.ctx) {
1088 page = cbz_load_page (state.u.cbz, pageno);
1089 cbz_bound_page (state.u.cbz, page, &mediabox);
1090 cbz_free_page (state.u.cbz, page);
1092 fz_catch (state.ctx) {
1093 fprintf (stderr, "failed to load cbz page\n");
1099 break;
1101 default:
1102 ARSERT (0 && state.type);
1105 if (state.pagedimcount == 0
1106 || (p = &state.pagedims[state.pagedimcount-1], p->rotate != rotate)
1107 || memcmp (&p->mediabox, &mediabox, sizeof (mediabox))) {
1108 size_t size;
1110 size = (state.pagedimcount + 1) * sizeof (*state.pagedims);
1111 state.pagedims = realloc (state.pagedims, size);
1112 if (!state.pagedims) {
1113 err (1, "realloc pagedims to %" FMT_s " (%d elems)",
1114 size, state.pagedimcount + 1);
1117 p = &state.pagedims[state.pagedimcount++];
1118 p->rotate = rotate;
1119 p->mediabox = mediabox;
1120 p->pageno = pageno;
1123 end = now ();
1124 if (state.trimmargins) {
1125 printd ("progress 1 Trimmed %d pages in %f seconds",
1126 state.pagecount, end - start);
1128 else {
1129 printd ("vmsg Processed %d pages in %f seconds",
1130 state.pagecount, end - start);
1132 state.trimanew = 0;
1133 if (trimf) {
1134 if (fclose (trimf)) {
1135 err (1, "fclose");
1140 static void layout (void)
1142 int pindex;
1143 fz_rect box;
1144 fz_matrix ctm, rm;
1145 struct pagedim *p = p;
1146 double zw, w, maxw = 0.0, zoom = zoom;
1148 if (state.pagedimcount == 0) return;
1150 switch (state.fitmodel) {
1151 case FitProportional:
1152 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1153 double x0, x1;
1155 p = &state.pagedims[pindex];
1156 fz_rotate (&rm, p->rotate + state.rotate);
1157 box = p->mediabox;
1158 fz_transform_rect (&box, &rm);
1160 x0 = MIN (box.x0, box.x1);
1161 x1 = MAX (box.x0, box.x1);
1163 w = x1 - x0;
1164 maxw = MAX (w, maxw);
1165 zoom = state.w / maxw;
1167 break;
1169 case FitPage:
1170 maxw = state.w;
1171 break;
1173 case FitWidth:
1174 break;
1176 default:
1177 ARSERT (0 && state.fitmodel);
1180 for (pindex = 0; pindex < state.pagedimcount; ++pindex) {
1181 fz_rect rect;
1182 fz_matrix tm, sm;
1184 p = &state.pagedims[pindex];
1185 fz_rotate (&ctm, state.rotate);
1186 fz_rotate (&rm, p->rotate + state.rotate);
1187 box = p->mediabox;
1188 fz_transform_rect (&box, &rm);
1189 w = box.x1 - box.x0;
1190 switch (state.fitmodel) {
1191 case FitProportional:
1192 p->left = ((maxw - w) * zoom) / 2.0;
1193 break;
1194 case FitPage:
1196 double zh, h;
1197 zw = maxw / w;
1198 h = box.y1 - box.y0;
1199 zh = state.h / h;
1200 zoom = MIN (zw, zh);
1201 p->left = (maxw - (w * zoom)) / 2.0;
1203 break;
1204 case FitWidth:
1205 p->left = 0;
1206 zoom = state.w / w;
1207 break;
1210 fz_scale (&p->zoomctm, zoom, zoom);
1211 fz_concat (&ctm, &p->zoomctm, &ctm);
1213 fz_rotate (&rm, p->rotate);
1214 p->pagebox = p->mediabox;
1215 fz_transform_rect (&p->pagebox, &rm);
1216 p->pagebox.x1 -= p->pagebox.x0;
1217 p->pagebox.y1 -= p->pagebox.y0;
1218 p->pagebox.x0 = 0;
1219 p->pagebox.y0 = 0;
1220 rect = p->pagebox;
1221 fz_transform_rect (&rect, &ctm);
1222 fz_round_rect (&p->bounds, &rect);
1223 p->ctm = ctm;
1225 fz_translate (&tm, 0, -p->mediabox.y1);
1226 fz_scale (&sm, zoom, -zoom);
1227 fz_concat (&ctm, &tm, &sm);
1228 fz_concat (&p->lctm, &ctm, &rm);
1230 p->tctmready = 0;
1233 do {
1234 int x0 = MIN (p->bounds.x0, p->bounds.x1);
1235 int y0 = MIN (p->bounds.y0, p->bounds.y1);
1236 int x1 = MAX (p->bounds.x0, p->bounds.x1);
1237 int y1 = MAX (p->bounds.y0, p->bounds.y1);
1238 int w = x1 - x0;
1239 int h = y1 - y0;
1241 printd ("pdim %d %d %d %d", p->pageno, w, h, p->left);
1242 } while (p-- != state.pagedims);
1245 static
1246 struct anchor { int n; int x; int y; int w; int h; }
1247 desttoanchor (fz_link_dest *dest)
1249 int i;
1250 struct anchor a;
1251 struct pagedim *pdim = state.pagedims;
1253 a.n = -1;
1254 a.x = 0;
1255 a.y = 0;
1256 for (i = 0; i < state.pagedimcount; ++i) {
1257 if (state.pagedims[i].pageno > dest->ld.gotor.page)
1258 break;
1259 pdim = &state.pagedims[i];
1261 if (dest->ld.gotor.flags & fz_link_flag_t_valid) {
1262 fz_point p;
1263 if (dest->ld.gotor.flags & fz_link_flag_l_valid)
1264 p.x = dest->ld.gotor.lt.x;
1265 else
1266 p.x = 0.0;
1267 p.y = dest->ld.gotor.lt.y;
1268 fz_transform_point (&p, &pdim->lctm);
1269 a.y = p.y;
1270 a.x = p.x;
1272 if (dest->ld.gotor.page >= 0 && dest->ld.gotor.page < 1<<30) {
1273 double x0, x1, y0, y1;
1275 x0 = MIN (pdim->bounds.x0, pdim->bounds.x1);
1276 x1 = MAX (pdim->bounds.x0, pdim->bounds.x1);
1277 a.w = x1 - x0;
1278 y0 = MIN (pdim->bounds.y0, pdim->bounds.y1);
1279 y1 = MAX (pdim->bounds.y0, pdim->bounds.y1);
1280 a.h = y1 - y0;
1281 a.n = dest->ld.gotor.page;
1283 return a;
1286 static void recurse_outline (fz_outline *outline, int level)
1288 while (outline) {
1289 if (outline->dest.kind == FZ_LINK_GOTO) {
1290 struct anchor a = desttoanchor (&outline->dest);
1292 if (a.n >= 0) {
1293 printd ("o %d %d %d %d %s",
1294 level, a.n, a.y, a.h, outline->title);
1297 else {
1298 printd ("emsg Unhandled outline kind %d for %s\n",
1299 outline->dest.kind, outline->title);
1301 if (outline->down) {
1302 recurse_outline (outline->down, level + 1);
1304 outline = outline->next;
1308 static void process_outline (void)
1310 fz_outline *outline;
1312 if (!state.needoutline) return;
1314 state.needoutline = 0;
1315 switch (state.type) {
1316 case DPDF:
1317 outline = pdf_load_outline (state.u.pdf);
1318 break;
1319 case DXPS:
1320 outline = xps_load_outline (state.u.xps);
1321 break;
1322 default:
1323 outline = NULL;
1324 break;
1326 if (outline) {
1327 recurse_outline (outline, 0);
1328 fz_free_outline (state.ctx, outline);
1332 static char *strofspan (fz_text_span *span)
1334 char *p;
1335 char utf8[10];
1336 fz_text_char *ch;
1337 size_t size = 0, cap = 80;
1339 p = malloc (cap + 1);
1340 if (!p) return NULL;
1342 for (ch = span->text; ch < span->text + span->len; ++ch) {
1343 int n = fz_runetochar (utf8, ch->c);
1344 if (size + n > cap) {
1345 cap *= 2;
1346 p = realloc (p, cap + 1);
1347 if (!p) return NULL;
1350 memcpy (p + size, utf8, n);
1351 size += n;
1353 p[size] = 0;
1354 return p;
1357 static int matchspan (regex_t *re, fz_text_page *page,
1358 fz_text_span *span, fz_matrix ctm,
1359 int stop, int pageno, double start)
1361 int ret;
1362 char *p;
1363 regmatch_t rm;
1364 int a, b, c;
1365 fz_rect sb, eb;
1366 fz_point p1, p2, p3, p4;
1368 p = strofspan (span);
1369 if (!p) return -1;
1371 ret = regexec (re, p, 1, &rm, 0);
1372 if (ret) {
1373 free (p);
1374 if (ret != REG_NOMATCH) {
1375 size_t size;
1376 char errbuf[80];
1377 size = regerror (ret, re, errbuf, sizeof (errbuf));
1378 printd ("msg regexec error `%.*s'",
1379 (int) size, errbuf);
1380 return -1;
1382 return 0;
1384 else {
1385 int l = span->len;
1387 for (a = 0, c = 0; c < rm.rm_so && a < l; a++) {
1388 c += fz_runelen (span->text[a].c);
1390 for (b = a; c < rm.rm_eo - 1 && b < l; b++) {
1391 c += fz_runelen (span->text[b].c);
1394 if (fz_runelen (span->text[b].c) > 1) {
1395 b = MAX (0, b-1);
1398 fz_text_char_bbox (&sb, span, a);
1399 fz_text_char_bbox (&eb, span, b);
1401 p1.x = sb.x0;
1402 p1.y = sb.y0;
1403 p2.x = eb.x1;
1404 p2.y = sb.y0;
1405 p3.x = eb.x1;
1406 p3.y = eb.y1;
1407 p4.x = sb.x0;
1408 p4.y = eb.y1;
1410 if (!stop) {
1411 printd ("firstmatch %d %d %f %f %f %f %f %f %f %f",
1412 pageno, 1,
1413 p1.x, p1.y,
1414 p2.x, p2.y,
1415 p3.x, p3.y,
1416 p4.x, p4.y);
1418 printd ("progress 1 found at %d `%.*s' in %f sec",
1419 pageno + 1, (int) (rm.rm_eo - rm.rm_so), &p[rm.rm_so],
1420 now () - start);
1422 else {
1423 printd ("match %d %d %f %f %f %f %f %f %f %f",
1424 pageno, 2,
1425 p1.x, p1.y,
1426 p2.x, p2.y,
1427 p3.x, p3.y,
1428 p4.x, p4.y);
1430 free (p);
1431 return 1;
1435 static int compareblocks (const void *l, const void *r)
1437 fz_text_block const *ls = l;
1438 fz_text_block const* rs = r;
1439 return ls->bbox.y0 - rs->bbox.y0;
1442 /* wishful thinking function */
1443 static void search (regex_t *re, int pageno, int y, int forward)
1445 int i, j;
1446 fz_matrix ctm;
1447 fz_device *tdev;
1448 union { void *ptr; pdf_page *pdfpage; xps_page *xpspage; } u;
1449 fz_text_page *text;
1450 fz_text_sheet *sheet;
1451 struct pagedim *pdim, *pdimprev;
1452 int stop = 0, niters = 0;
1453 double start, end;
1455 if (!(state.type == DPDF || state.type == DXPS))
1456 return;
1458 start = now ();
1459 while (pageno >= 0 && pageno < state.pagecount && !stop) {
1460 if (niters++ == 5) {
1461 niters = 0;
1462 if (hasdata ()) {
1463 printd ("progress 1 attention requested aborting search at %d",
1464 pageno);
1465 stop = 1;
1467 else {
1468 printd ("progress %f searching in page %d",
1469 (double) (pageno + 1) / state.pagecount,
1470 pageno);
1473 pdimprev = NULL;
1474 for (i = 0; i < state.pagedimcount; ++i) {
1475 pdim = &state.pagedims[i];
1476 if (pdim->pageno == pageno) {
1477 goto found;
1479 if (pdim->pageno > pageno) {
1480 pdim = pdimprev;
1481 goto found;
1483 pdimprev = pdim;
1485 pdim = pdimprev;
1486 found:
1488 sheet = fz_new_text_sheet (state.ctx);
1489 text = fz_new_text_page (state.ctx);
1490 tdev = fz_new_text_device (state.ctx, sheet, text);
1492 switch (state.type) {
1493 case DPDF:
1494 u.ptr = NULL;
1495 fz_try (state.ctx) {
1496 u.pdfpage = pdf_load_page (state.u.pdf, pageno);
1497 trimctm (u.pdfpage, pdim - state.pagedims);
1498 fz_concat (&ctm, &pdim->tctm, &pdim->zoomctm);
1499 fz_begin_page (tdev, &fz_infinite_rect, &ctm);
1500 pdf_run_page (state.u.pdf, u.pdfpage, tdev, &ctm, NULL);
1501 fz_end_page (tdev);
1503 fz_catch (state.ctx) {
1504 fz_free_device (tdev);
1505 u.ptr = NULL;
1506 goto nextiter;
1508 break;
1510 case DXPS:
1511 u.xpspage = xps_load_page (state.u.xps, pageno);
1512 xps_run_page (state.u.xps, u.xpspage, tdev, &pdim->ctm, NULL);
1513 break;
1515 default:
1516 ARSERT (0 && state.type);
1519 qsort (text->blocks, text->len, sizeof (*text->blocks), compareblocks);
1520 fz_free_device (tdev);
1522 for (j = 0; j < text->len; ++j) {
1523 int k;
1524 fz_page_block *pb;
1525 fz_text_block *block;
1527 pb = &text->blocks[forward ? j : text->len - 1 - j];
1528 if (pb->type != FZ_PAGE_BLOCK_TEXT) continue;
1529 block = pb->u.text;
1531 for (k = 0; k < block->len; ++k) {
1532 fz_text_line *line;
1533 fz_text_span *span;
1535 if (forward) {
1536 line = &block->lines[k];
1537 if (line->bbox.y0 < y + 1) continue;
1539 else {
1540 line = &block->lines[block->len - 1 - k];
1541 if (line->bbox.y0 > y - 1) continue;
1544 for (span = line->first_span; span; span = span->next) {
1545 switch (matchspan (re, text, span, ctm,
1546 stop, pageno, start)) {
1547 case 0: break;
1548 case 1: stop = 1; break;
1549 case -1: stop = 1; goto endloop;
1554 nextiter:
1555 if (forward) {
1556 pageno += 1;
1557 y = 0;
1559 else {
1560 pageno -= 1;
1561 y = INT_MAX;
1563 endloop:
1564 fz_free_text_page (state.ctx, text);
1565 fz_free_text_sheet (state.ctx, sheet);
1566 if (u.ptr) {
1567 state.freepage (u.ptr);
1570 end = now ();
1571 if (!stop) {
1572 printd ("progress 1 no matches %f sec", end - start);
1574 printd ("clearrects");
1577 static void set_tex_params (int colorspace)
1579 union {
1580 unsigned char b;
1581 unsigned int s;
1582 } endianness = {1};
1584 switch (colorspace) {
1585 case 0:
1586 state.texiform = GL_RGBA8;
1587 state.texform = GL_RGBA;
1588 state.texty = GL_UNSIGNED_BYTE;
1589 state.colorspace = fz_device_rgb (state.ctx);
1590 break;
1591 case 1:
1592 state.texiform = GL_RGBA8;
1593 state.texform = GL_BGRA;
1594 state.texty = endianness.s > 1
1595 ? GL_UNSIGNED_INT_8_8_8_8
1596 : GL_UNSIGNED_INT_8_8_8_8_REV;
1597 state.colorspace = fz_device_bgr (state.ctx);
1598 break;
1599 case 2:
1600 state.texiform = GL_LUMINANCE_ALPHA;
1601 state.texform = GL_LUMINANCE_ALPHA;
1602 state.texty = GL_UNSIGNED_BYTE;
1603 state.colorspace = fz_device_gray (state.ctx);
1604 break;
1605 default:
1606 errx (1, "invalid colorspce %d", colorspace);
1610 static void realloctexts (int texcount)
1612 size_t size;
1614 if (texcount == state.texcount) return;
1616 if (texcount < state.texcount) {
1617 glDeleteTextures (state.texcount - texcount,
1618 state.texids + texcount);
1621 size = texcount * sizeof (*state.texids);
1622 state.texids = realloc (state.texids, size);
1623 if (!state.texids) {
1624 err (1, "realloc texids %" FMT_s, size);
1627 size = texcount * sizeof (*state.texowners);
1628 state.texowners = realloc (state.texowners, size);
1629 if (!state.texowners) {
1630 err (1, "realloc texowners %" FMT_s, size);
1632 if (texcount > state.texcount) {
1633 int i;
1635 glGenTextures (texcount - state.texcount,
1636 state.texids + state.texcount);
1637 for (i = state.texcount; i < texcount; ++i) {
1638 state.texowners[i].w = -1;
1639 state.texowners[i].slice = NULL;
1642 state.texcount = texcount;
1643 state.texindex = 0;
1646 static char *mbtoutf8 (char *s)
1648 char *p, *r;
1649 wchar_t *tmp;
1650 size_t i, ret, len;
1652 len = mbstowcs (NULL, s, strlen (s));
1653 if (len == 0) {
1654 return s;
1656 else {
1657 if (len == (size_t) -1) {
1658 return s;
1662 tmp = malloc (len * sizeof (wchar_t));
1663 if (!tmp) {
1664 return s;
1667 ret = mbstowcs (tmp, s, len);
1668 if (ret == (size_t) -1) {
1669 free (tmp);
1670 return s;
1673 len = 0;
1674 for (i = 0; i < ret; ++i) {
1675 len += fz_runelen (tmp[i]);
1678 p = r = malloc (len + 1);
1679 if (!r) {
1680 free (tmp);
1681 return s;
1684 for (i = 0; i < ret; ++i) {
1685 p += fz_runetochar (p, tmp[i]);
1687 *p = 0;
1688 free (tmp);
1689 return r;
1692 CAMLprim value ml_mbtoutf8 (value s_v)
1694 CAMLparam1 (s_v);
1695 CAMLlocal1 (ret_v);
1696 char *s, *r;
1698 s = String_val (s_v);
1699 r = mbtoutf8 (s);
1700 if (r == s) {
1701 ret_v = s_v;
1703 else {
1704 ret_v = caml_copy_string (r);
1705 free (r);
1707 CAMLreturn (ret_v);
1710 static void * mainloop (void *unused)
1712 char *p = NULL;
1713 int len, ret, oldlen = 0;
1715 for (;;) {
1716 len = readlen ();
1717 if (len == 0) {
1718 errx (1, "readlen returned 0");
1721 if (oldlen < len + 1) {
1722 p = realloc (p, len + 1);
1723 if (!p) {
1724 err (1, "realloc %d failed", len + 1);
1726 oldlen = len + 1;
1728 readdata (p, len);
1729 p[len] = 0;
1731 if (!strncmp ("open", p, 4)) {
1732 int wthack, off;
1733 char *password;
1734 char *filename;
1735 char *utf8filename;
1736 size_t filenamelen;
1738 ret = sscanf (p + 5, " %d %d %n", &wthack, &state.cxack, &off);
1739 if (ret != 2) {
1740 errx (1, "malformed open `%.*s' ret=%d", len, p, ret);
1743 filename = p + 5 + off;
1744 filenamelen = strlen (filename);
1745 password = filename + filenamelen + 1;
1747 lock ("open");
1748 openxref (filename, password);
1749 pdfinfo ();
1750 initpdims ();
1751 unlock ("open");
1753 if (!wthack) {
1754 utf8filename = mbtoutf8 (filename);
1755 printd ("msg Opened %s (press h/F1 to get help)", utf8filename);
1756 if (utf8filename != filename) {
1757 free (utf8filename);
1760 state.needoutline = 1;
1762 else if (!strncmp ("cs", p, 2)) {
1763 int i, colorspace;
1765 ret = sscanf (p + 2, " %d", &colorspace);
1766 if (ret != 1) {
1767 errx (1, "malformed cs `%.*s' ret=%d", len, p, ret);
1769 lock ("cs");
1770 set_tex_params (colorspace);
1771 for (i = 0; i < state.texcount; ++i) {
1772 state.texowners[i].w = -1;
1773 state.texowners[i].slice = NULL;
1775 unlock ("cs");
1777 else if (!strncmp ("freepage", p, 8)) {
1778 void *ptr;
1780 ret = sscanf (p + 8, " %" FMT_ptr, FMT_ptr_cast (&ptr));
1781 if (ret != 1) {
1782 errx (1, "malformed freepage `%.*s' ret=%d", len, p, ret);
1784 freepage (ptr);
1786 else if (!strncmp ("freetile", p, 8)) {
1787 void *ptr;
1789 ret = sscanf (p + 8, " %" FMT_ptr, FMT_ptr_cast (&ptr));
1790 if (ret != 1) {
1791 errx (1, "malformed freetile `%.*s' ret=%d", len, p, ret);
1793 freetile (ptr);
1795 else if (!strncmp ("search", p, 6)) {
1796 int icase, pageno, y, ret, len2, forward;
1797 char *pattern;
1798 regex_t re;
1800 ret = sscanf (p + 6, " %d %d %d %d,%n",
1801 &icase, &pageno, &y, &forward, &len2);
1802 if (ret != 4) {
1803 errx (1, "malformed search `%s' ret=%d", p, ret);
1806 pattern = p + 6 + len2;
1807 ret = regcomp (&re, pattern,
1808 REG_EXTENDED | (icase ? REG_ICASE : 0));
1809 if (ret) {
1810 char errbuf[80];
1811 size_t size;
1813 size = regerror (ret, &re, errbuf, sizeof (errbuf));
1814 printd ("msg regcomp failed `%.*s'", (int) size, errbuf);
1816 else {
1817 search (&re, pageno, y, forward);
1818 regfree (&re);
1821 else if (!strncmp ("geometry", p, 8)) {
1822 int w, h, fitmodel;
1824 printd ("clear");
1825 ret = sscanf (p + 8, " %d %d %d", &w, &h, &fitmodel);
1826 if (ret != 3) {
1827 errx (1, "malformed geometry `%.*s' ret=%d", len, p, ret);
1830 lock ("geometry");
1831 state.h = h;
1832 if (w != state.w) {
1833 int i;
1834 state.w = w;
1835 for (i = 0; i < state.texcount; ++i) {
1836 state.texowners[i].slice = NULL;
1839 state.fitmodel = fitmodel;
1840 layout ();
1841 process_outline ();
1843 state.gen++;
1844 unlock ("geometry");
1845 printd ("continue %d", state.pagecount);
1847 else if (!strncmp ("reqlayout", p, 9)) {
1848 char *nameddest;
1849 int rotate, fitmodel, off, h;
1851 printd ("clear");
1852 ret = sscanf (p + 9, " %d %d %d %n",
1853 &rotate, &fitmodel, &h, &off);
1854 if (ret != 3) {
1855 errx (1, "bad reqlayout line `%.*s' ret=%d", len, p, ret);
1857 lock ("reqlayout");
1858 if (state.rotate != rotate || state.fitmodel != fitmodel) {
1859 state.gen += 1;
1861 state.rotate = rotate;
1862 state.fitmodel = fitmodel;
1863 state.h = h;
1864 layout ();
1865 process_outline ();
1867 nameddest = p + 9 + off;
1868 if (state.type == DPDF && nameddest && *nameddest) {
1869 struct anchor a;
1870 fz_link_dest dest;
1871 pdf_obj *needle, *obj;
1873 needle = pdf_new_string (state.u.pdf, nameddest,
1874 strlen (nameddest));
1875 obj = pdf_lookup_dest (state.u.pdf, needle);
1876 if (obj) {
1877 dest = pdf_parse_link_dest (state.u.pdf, FZ_LINK_GOTO, obj);
1879 a = desttoanchor (&dest);
1880 if (a.n >= 0) {
1881 printd ("a %d %d %d", a.n, a.x, a.y);
1883 else {
1884 printd ("emsg failed to parse destination `%s'\n",
1885 nameddest);
1888 else {
1889 printd ("emsg destination `%s' not found\n",
1890 nameddest);
1892 pdf_drop_obj (needle);
1895 state.gen++;
1896 unlock ("reqlayout");
1897 printd ("continue %d", state.pagecount);
1899 else if (!strncmp ("page", p, 4)) {
1900 double a, b;
1901 struct page *page;
1902 int pageno, pindex, ret;
1904 ret = sscanf (p + 4, " %d %d", &pageno, &pindex);
1905 if (ret != 2) {
1906 errx (1, "bad page line `%.*s' ret=%d", len, p, ret);
1909 lock ("page");
1910 a = now ();
1911 page = loadpage (pageno, pindex);
1912 b = now ();
1913 unlock ("page");
1915 printd ("page %" FMT_ptr " %f", FMT_ptr_cast2 (page), b - a);
1917 else if (!strncmp ("tile", p, 4)) {
1918 int x, y, w, h, ret;
1919 struct page *page;
1920 struct tile *tile;
1921 double a, b;
1922 void *data;
1924 ret = sscanf (p + 4, " %" FMT_ptr " %d %d %d %d %" FMT_ptr,
1925 FMT_ptr_cast (&page), &x, &y, &w, &h, &data);
1926 if (ret != 6) {
1927 errx (1, "bad tile line `%.*s' ret=%d", len, p, ret);
1930 lock ("tile");
1931 a = now ();
1932 tile = rendertile (page, x, y, w, h, data);
1933 b = now ();
1934 unlock ("tile");
1936 printd ("tile %d %d %" FMT_ptr " %u %f",
1937 x, y,
1938 FMT_ptr_cast2 (tile),
1939 tile->w * tile->h * tile->pixmap->n,
1940 b - a);
1942 else if (!strncmp ("settrim", p, 7)) {
1943 fz_irect fuzz;
1944 int trimmargins;
1946 ret = sscanf (p + 7, " %d %d %d %d %d",
1947 &trimmargins, &fuzz.x0, &fuzz.y0, &fuzz.x1, &fuzz.y1);
1948 if (ret != 5) {
1949 errx (1, "malformed settrim `%.*s' ret=%d", len, p, ret);
1951 printd ("clear");
1952 lock ("settrim");
1953 state.trimmargins = trimmargins;
1954 state.needoutline = 1;
1955 if (memcmp (&fuzz, &state.trimfuzz, sizeof (fuzz))) {
1956 state.trimanew = 1;
1957 state.trimfuzz = fuzz;
1959 state.pagedimcount = 0;
1960 free (state.pagedims);
1961 state.pagedims = NULL;
1962 initpdims ();
1963 layout ();
1964 process_outline ();
1965 unlock ("settrim");
1966 printd ("continue %d", state.pagecount);
1968 else if (!strncmp ("sliceh", p, 6)) {
1969 int h;
1971 ret = sscanf (p + 6, " %d", &h);
1972 if (ret != 1) {
1973 errx (1, "malformed sliceh `%.*s' ret=%d", len, p, ret);
1975 if (h != state.sliceheight) {
1976 int i;
1978 state.sliceheight = h;
1979 for (i = 0; i < state.texcount; ++i) {
1980 state.texowners[i].w = -1;
1981 state.texowners[i].h = -1;
1982 state.texowners[i].slice = NULL;
1986 else if (!strncmp ("interrupt", p, 9)) {
1987 printd ("vmsg interrupted");
1989 else {
1990 errx (1, "unknown command %.*s", len, p);
1993 return 0;
1996 CAMLprim value ml_realloctexts (value texcount_v)
1998 CAMLparam1 (texcount_v);
1999 int ok;
2001 if (trylock ("ml_realloctexts")) {
2002 ok = 0;
2003 goto done;
2005 realloctexts (Int_val (texcount_v));
2006 ok = 1;
2007 unlock ("ml_realloctexts");
2009 done:
2010 CAMLreturn (Val_bool (ok));
2013 static void showsel (struct page *page, int ox, int oy)
2015 int seen = 0;
2016 fz_irect bbox;
2017 fz_rect rect;
2018 fz_text_line *line;
2019 fz_page_block *pageb;
2020 fz_text_block *block;
2021 struct mark first, last;
2023 first = page->fmark;
2024 last = page->lmark;
2026 if (!first.span || !last.span) return;
2028 glEnable (GL_BLEND);
2029 glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA);
2030 glColor4f (0.5f, 0.5f, 0.0f, 0.6f);
2032 ox += state.pagedims[page->pdimno].bounds.x0;
2033 oy += state.pagedims[page->pdimno].bounds.y0;
2034 for (pageb = page->text->blocks;
2035 pageb < page->text->blocks + page->text->len;
2036 ++pageb) {
2037 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
2038 block = pageb->u.text;
2040 for (line = block->lines;
2041 line < block->lines + block->len;
2042 ++line) {
2043 fz_text_span *span;
2044 rect = fz_empty_rect;
2046 for (span = line->first_span; span; span = span->next) {
2047 int i, j, k;
2048 bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0;
2050 j = 0;
2051 k = span->len - 1;
2053 if (span == page->fmark.span && span == page->lmark.span) {
2054 seen = 1;
2055 j = MIN (first.i, last.i);
2056 k = MAX (first.i, last.i);
2058 else if (span == first.span) {
2059 seen = 1;
2060 j = first.i;
2062 else if (span == last.span) {
2063 seen = 1;
2064 k = last.i;
2067 if (seen) {
2068 for (i = j; i <= k; ++i) {
2069 fz_rect bbox;
2070 fz_union_rect (&rect,
2071 fz_text_char_bbox (&bbox, span, i));
2073 fz_round_rect (&bbox, &rect);
2074 lprintf ("%d %d %d %d oy=%d ox=%d\n",
2075 bbox.x0,
2076 bbox.y0,
2077 bbox.x1,
2078 bbox.y1,
2079 oy, ox);
2081 glRecti (bbox.x0 + ox, bbox.y0 + oy,
2082 bbox.x1 + ox, bbox.y1 + oy);
2083 if (span == last.span) {
2084 goto done;
2090 done:
2091 glDisable (GL_BLEND);
2094 #include "glfont.c"
2096 static void highlightlinks (struct page *page, int xoff, int yoff)
2098 fz_matrix ctm, tm, pm;
2099 fz_link *link, *links;
2101 switch (page->type) {
2102 case DPDF:
2103 links = page->u.pdfpage->links;
2104 break;
2106 case DXPS:
2107 links = page->u.xpspage->links;
2108 break;
2110 default:
2111 return;
2114 glEnable (GL_TEXTURE_1D);
2115 glEnable (GL_BLEND);
2116 glBindTexture (GL_TEXTURE_1D, state.stid);
2118 xoff -= state.pagedims[page->pdimno].bounds.x0;
2119 yoff -= state.pagedims[page->pdimno].bounds.y0;
2120 fz_translate (&tm, xoff, yoff);
2121 pm = pagectm (page);
2122 fz_concat (&ctm, &pm, &tm);
2124 glBegin (GL_LINES);
2125 for (link = links; link; link = link->next) {
2126 fz_point p1, p2, p3, p4;
2128 p1.x = link->rect.x0;
2129 p1.y = link->rect.y0;
2131 p2.x = link->rect.x1;
2132 p2.y = link->rect.y0;
2134 p3.x = link->rect.x1;
2135 p3.y = link->rect.y1;
2137 p4.x = link->rect.x0;
2138 p4.y = link->rect.y1;
2140 fz_transform_point (&p1, &ctm);
2141 fz_transform_point (&p2, &ctm);
2142 fz_transform_point (&p3, &ctm);
2143 fz_transform_point (&p4, &ctm);
2145 switch (link->dest.kind) {
2146 case FZ_LINK_GOTO: glColor3ub (255, 0, 0); break;
2147 case FZ_LINK_URI: glColor3ub (0, 0, 255); break;
2148 case FZ_LINK_LAUNCH: glColor3ub (0, 255, 0); break;
2149 default: glColor3ub (0, 0, 0); break;
2153 float w, h, s, t;
2155 w = p2.x - p1.x;
2156 h = p2.y - p1.y;
2157 t = sqrtf (w*w + h*h) * .25f;
2159 w = p3.x - p2.x;
2160 h = p3.y - p2.y;
2161 s = sqrtf (w*w + h*h) * .25f;
2163 glTexCoord1i (0);
2164 glVertex2f (p1.x, p1.y);
2165 glTexCoord1f (t);
2166 glVertex2f (p2.x, p2.y);
2168 glTexCoord1i (0);
2169 glVertex2f (p2.x, p2.y);
2170 glTexCoord1f (s);
2171 glVertex2f (p3.x, p3.y);
2173 glTexCoord1i (0);
2174 glVertex2f (p3.x, p3.y);
2175 glTexCoord1f (t);
2176 glVertex2f (p4.x, p4.y);
2178 glTexCoord1i (0);
2179 glVertex2f (p4.x, p4.y);
2180 glTexCoord1f (s);
2181 glVertex2f (p1.x, p1.y);
2184 glEnd ();
2186 glDisable (GL_BLEND);
2187 glDisable (GL_TEXTURE_1D);
2190 static int compareslinks (const void *l, const void *r)
2192 struct slink const *ls = l;
2193 struct slink const *rs = r;
2194 if (ls->bbox.y0 == rs->bbox.y0) {
2195 return rs->bbox.x0 - rs->bbox.x0;
2197 return ls->bbox.y0 - rs->bbox.y0;
2200 static void droptext (struct page *page)
2202 if (page->text) {
2203 fz_free_text_page (state.ctx, page->text);
2204 page->fmark.i = -1;
2205 page->lmark.i = -1;
2206 page->fmark.span = NULL;
2207 page->lmark.span = NULL;
2208 page->text = NULL;
2210 if (page->sheet) {
2211 fz_free_text_sheet (state.ctx, page->sheet);
2212 page->sheet = NULL;
2216 static void dropslinks (struct page *page)
2218 if (page->slinks) {
2219 free (page->slinks);
2220 page->slinks = NULL;
2221 page->slinkcount = 0;
2225 static void ensureslinks (struct page *page)
2227 fz_matrix ctm;
2228 int i, count = 0;
2229 size_t slinksize = sizeof (*page->slinks);
2230 fz_link *link, *links;
2232 if (state.gen != page->sgen) {
2233 dropslinks (page);
2234 page->sgen = state.gen;
2236 if (page->slinks) return;
2238 switch (page->type) {
2239 case DPDF:
2240 links = page->u.pdfpage->links;
2241 trimctm (page->u.pdfpage, page->pdimno);
2242 fz_concat (&ctm,
2243 &state.pagedims[page->pdimno].tctm,
2244 &state.pagedims[page->pdimno].ctm);
2245 break;
2247 case DXPS:
2248 links = page->u.xpspage->links;
2249 ctm = state.pagedims[page->pdimno].ctm;
2250 break;
2252 default:
2253 return;
2256 for (link = links; link; link = link->next) {
2257 count++;
2259 if (count > 0) {
2260 page->slinkcount = count;
2261 page->slinks = calloc (count, slinksize);
2262 if (!page->slinks) {
2263 err (1, "realloc slinks %d", count);
2266 for (i = 0, link = links; link; ++i, link = link->next) {
2267 fz_rect rect;
2269 rect = link->rect;
2270 fz_transform_rect (&rect, &ctm);
2271 page->slinks[i].link = link;
2272 fz_round_rect (&page->slinks[i].bbox, &rect);
2274 qsort (page->slinks, count, slinksize, compareslinks);
2278 /* slightly tweaked fmt_ulong by D.J. Bernstein */
2279 static void fmt_linkn (char *s, unsigned int u)
2281 unsigned int len; unsigned int q;
2282 int zma = 'z' - 'a' + 1;
2283 len = 1; q = u;
2284 while (q > zma - 1) { ++len; q /= zma; }
2285 if (s) {
2286 s += len;
2287 do { *--s = 'a' + (u % zma) - (u < zma && len > 1); u /= zma; } while(u);
2288 /* handles u == 0 */
2290 s[len] = 0;
2293 static void highlightslinks (struct page *page, int xoff, int yoff,
2294 int noff, char *targ, int tlen, int hfsize)
2296 int i;
2297 char buf[40];
2298 struct slink *slink;
2299 double x0, y0, x1, y1, w;
2301 ensureslinks (page);
2302 glColor3ub (0xc3, 0xb0, 0x91);
2303 for (i = 0; i < page->slinkcount; ++i) {
2304 fmt_linkn (buf, i + noff);
2305 if (!tlen || !strncmp (targ, buf, tlen)) {
2306 slink = &page->slinks[i];
2308 x0 = slink->bbox.x0 + xoff - 5;
2309 y1 = slink->bbox.y0 + yoff - 5;
2310 y0 = y1 + 10 + hfsize;
2311 w = measure_string (state.face, hfsize, buf);
2312 x1 = x0 + w + 10;
2313 glRectd (x0, y0, x1, y1);
2317 glEnable (GL_BLEND);
2318 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2319 glEnable (GL_TEXTURE_2D);
2320 glColor3ub (0, 0, 0);
2321 for (i = 0; i < page->slinkcount; ++i) {
2322 fmt_linkn (buf, i + noff);
2323 if (!tlen || !strncmp (targ, buf, tlen)) {
2324 slink = &page->slinks[i];
2326 x0 = slink->bbox.x0 + xoff;
2327 y0 = slink->bbox.y0 + yoff + hfsize;
2328 draw_string (state.face, hfsize, x0, y0, buf);
2331 glDisable (GL_TEXTURE_2D);
2332 glDisable (GL_BLEND);
2336 static void uploadslice (struct tile *tile, struct slice *slice)
2338 int offset;
2339 struct slice *slice1;
2340 unsigned char *texdata;
2342 offset = 0;
2343 for (slice1 = tile->slices; slice != slice1; slice1++) {
2344 offset += slice1->h * tile->w * tile->pixmap->n;
2346 if (slice->texindex != -1 && slice->texindex < state.texcount
2347 && state.texowners[slice->texindex].slice == slice) {
2348 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[slice->texindex]);
2350 else {
2351 int subimage = 0;
2352 int texindex = state.texindex++ % state.texcount;
2354 if (state.texowners[texindex].w == tile->w) {
2355 if (state.texowners[texindex].h >= slice->h) {
2356 subimage = 1;
2358 else {
2359 state.texowners[texindex].h = slice->h;
2362 else {
2363 state.texowners[texindex].h = slice->h;
2366 state.texowners[texindex].w = tile->w;
2367 state.texowners[texindex].slice = slice;
2368 slice->texindex = texindex;
2370 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, state.texids[texindex]);
2371 if (tile->pbo) {
2372 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
2373 texdata = 0;
2375 else {
2376 texdata = tile->pixmap->samples;
2378 if (subimage) {
2379 glTexSubImage2D (GL_TEXTURE_RECTANGLE_ARB,
2383 tile->w,
2384 slice->h,
2385 state.texform,
2386 state.texty,
2387 texdata+offset
2390 else {
2391 glTexImage2D (GL_TEXTURE_RECTANGLE_ARB,
2393 state.texiform,
2394 tile->w,
2395 slice->h,
2397 state.texform,
2398 state.texty,
2399 texdata+offset
2402 if (tile->pbo) {
2403 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
2408 CAMLprim value ml_drawtile (value args_v, value ptr_v)
2410 CAMLparam2 (args_v, ptr_v);
2411 int dispx = Int_val (Field (args_v, 0));
2412 int dispy = Int_val (Field (args_v, 1));
2413 int dispw = Int_val (Field (args_v, 2));
2414 int disph = Int_val (Field (args_v, 3));
2415 int tilex = Int_val (Field (args_v, 4));
2416 int tiley = Int_val (Field (args_v, 5));
2417 char *s = String_val (ptr_v);
2418 struct tile *tile = parse_pointer ("ml_drawtile", s);
2420 glEnable (GL_TEXTURE_RECTANGLE_ARB);
2422 int slicey, firstslice;
2423 struct slice *slice;
2425 firstslice = tiley / tile->sliceheight;
2426 slice = &tile->slices[firstslice];
2427 slicey = tiley % tile->sliceheight;
2429 while (disph > 0) {
2430 int dh;
2432 dh = slice->h - slicey;
2433 dh = MIN (disph, dh);
2434 uploadslice (tile, slice);
2436 glBegin (GL_QUADS);
2438 glTexCoord2i (tilex, slicey);
2439 glVertex2i (dispx, dispy);
2441 glTexCoord2i (tilex+dispw, slicey);
2442 glVertex2i (dispx+dispw, dispy);
2444 glTexCoord2i (tilex+dispw, slicey+dh);
2445 glVertex2i (dispx+dispw, dispy+dh);
2447 glTexCoord2i (tilex, slicey+dh);
2448 glVertex2i (dispx, dispy+dh);
2450 glEnd ();
2452 dispy += dh;
2453 disph -= dh;
2454 slice++;
2455 ARSERT (!(slice - tile->slices >= tile->slicecount && disph > 0));
2456 slicey = 0;
2459 glDisable (GL_TEXTURE_RECTANGLE_ARB);
2460 CAMLreturn (Val_unit);
2463 CAMLprim value ml_postprocess (value ptr_v, value hlinks_v,
2464 value xoff_v, value yoff_v,
2465 value li_v)
2467 CAMLparam5 (ptr_v, hlinks_v, xoff_v, yoff_v, li_v);
2468 int xoff = Int_val (xoff_v);
2469 int yoff = Int_val (yoff_v);
2470 int noff = Int_val (Field (li_v, 0));
2471 char *targ = String_val (Field (li_v, 1));
2472 int tlen = caml_string_length (Field (li_v, 1));
2473 int hfsize = Int_val (Field (li_v, 2));
2474 char *s = String_val (ptr_v);
2475 int hlmask = Int_val (hlinks_v);
2476 struct page *page = parse_pointer ("ml_postprocess", s);
2478 if (!page->u.ptr) {
2479 /* deal with loadpage failed pages */
2480 goto done;
2483 if (hlmask & 1) highlightlinks (page, xoff, yoff);
2484 if (trylock ("ml_postprocess")) {
2485 noff = 0;
2486 goto done;
2488 if (hlmask & 2) {
2489 highlightslinks (page, xoff, yoff, noff, targ, tlen, hfsize);
2490 noff = page->slinkcount;
2492 if (page->tgen == state.gen) {
2493 showsel (page, xoff, yoff);
2495 unlock ("ml_postprocess");
2497 done:
2498 CAMLreturn (Val_int (noff));
2501 static fz_link *getlink (struct page *page, int x, int y)
2503 fz_point p;
2504 fz_matrix ctm;
2505 const fz_matrix *tctm;
2506 fz_link *link, *links;
2508 switch (page->type) {
2509 case DPDF:
2510 trimctm (page->u.pdfpage, page->pdimno);
2511 tctm = &state.pagedims[page->pdimno].tctm;
2512 links = page->u.pdfpage->links;
2513 break;
2515 case DXPS:
2516 tctm = &fz_identity;
2517 links = page->u.xpspage->links;
2518 break;
2520 default:
2521 return NULL;
2523 p.x = x;
2524 p.y = y;
2526 fz_concat (&ctm, tctm, &state.pagedims[page->pdimno].ctm);
2527 fz_invert_matrix (&ctm, &ctm);
2528 fz_transform_point (&p, &ctm);
2530 for (link = links; link; link = link->next) {
2531 if (p.x >= link->rect.x0 && p.x <= link->rect.x1) {
2532 if (p.y >= link->rect.y0 && p.y <= link->rect.y1) {
2533 return link;
2537 return NULL;
2540 static void ensuretext (struct page *page)
2542 if (state.gen != page->tgen) {
2543 droptext (page);
2544 page->tgen = state.gen;
2546 if (!page->text) {
2547 fz_matrix ctm;
2548 fz_device *tdev;
2550 page->text = fz_new_text_page (state.ctx);
2551 page->sheet = fz_new_text_sheet (state.ctx);
2552 tdev = fz_new_text_device (state.ctx, page->sheet, page->text);
2553 ctm = pagectm (page);
2554 fz_begin_page (tdev, &fz_infinite_rect, &ctm);
2555 fz_run_display_list (page->dlist, tdev, &ctm, &fz_infinite_rect, NULL);
2556 qsort (page->text->blocks, page->text->len,
2557 sizeof (*page->text->blocks), compareblocks);
2558 fz_end_page (tdev);
2559 fz_free_device (tdev);
2563 CAMLprim value ml_find_page_with_links (value start_page_v, value dir_v)
2565 CAMLparam2 (start_page_v, dir_v);
2566 CAMLlocal1 (ret_v);
2567 int i, dir = Int_val (dir_v);
2568 int start_page = Int_val (start_page_v);
2569 int end_page = dir > 0 ? state.pagecount : -1;
2571 ret_v = Val_int (0);
2572 if (!(state.type == DPDF || state.type == DXPS)) {
2573 goto done;
2576 lock ("ml_findpage_with_links");
2577 for (i = start_page + dir; i != end_page; i += dir) {
2578 int found;
2580 switch (state.type) {
2581 case DPDF:
2583 pdf_page *page = NULL;
2585 fz_try (state.ctx) {
2586 page = pdf_load_page (state.u.pdf, i);
2587 found = !!page->links;
2589 fz_catch (state.ctx) {
2590 found = 0;
2592 if (page) {
2593 freepdfpage (page);
2596 break;
2597 case DXPS:
2599 xps_page *page = xps_load_page (state.u.xps, i);
2600 found = !!page->links;
2601 freexpspage (page);
2603 break;
2605 default:
2606 ARSERT (0 && "invalid document type");
2609 if (found) {
2610 ret_v = caml_alloc_small (1, 1);
2611 Field (ret_v, 0) = Val_int (i);
2612 goto unlock;
2615 unlock:
2616 unlock ("ml_findpage_with_links");
2618 done:
2619 CAMLreturn (ret_v);
2622 enum { dir_first, dir_last};
2623 enum { dir_first_visible, dir_left, dir_right, dir_down, dir_up };
2625 CAMLprim value ml_findlink (value ptr_v, value dir_v)
2627 CAMLparam2 (ptr_v, dir_v);
2628 CAMLlocal2 (ret_v, pos_v);
2629 struct page *page;
2630 int dirtag, i, slinkindex;
2631 struct slink *found = NULL ,*slink;
2632 char *s = String_val (ptr_v);
2634 page = parse_pointer ("ml_findlink", s);
2635 ret_v = Val_int (0);
2636 if (trylock ("ml_findlink")) {
2637 goto done;
2640 ensureslinks (page);
2642 if (Is_block (dir_v)) {
2643 dirtag = Tag_val (dir_v);
2644 switch (dirtag) {
2645 case dir_first_visible:
2647 int x0, y0, dir, first_index, last_index;
2649 pos_v = Field (dir_v, 0);
2650 x0 = Int_val (Field (pos_v, 0));
2651 y0 = Int_val (Field (pos_v, 1));
2652 dir = Int_val (Field (pos_v, 2));
2654 if (dir >= 0) {
2655 dir = 1;
2656 first_index = 0;
2657 last_index = page->slinkcount;
2659 else {
2660 first_index = page->slinkcount - 1;
2661 last_index = -1;
2664 for (i = first_index; i != last_index; i += dir) {
2665 slink = &page->slinks[i];
2666 if (slink->bbox.y0 >= y0 && slink->bbox.x0 >= x0) {
2667 found = slink;
2668 break;
2672 break;
2674 case dir_left:
2675 slinkindex = Int_val (Field (dir_v, 0));
2676 found = &page->slinks[slinkindex];
2677 for (i = slinkindex - 1; i >= 0; --i) {
2678 slink = &page->slinks[i];
2679 if (slink->bbox.x0 < found->bbox.x0) {
2680 found = slink;
2681 break;
2684 break;
2686 case dir_right:
2687 slinkindex = Int_val (Field (dir_v, 0));
2688 found = &page->slinks[slinkindex];
2689 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2690 slink = &page->slinks[i];
2691 if (slink->bbox.x0 > found->bbox.x0) {
2692 found = slink;
2693 break;
2696 break;
2698 case dir_down:
2699 slinkindex = Int_val (Field (dir_v, 0));
2700 found = &page->slinks[slinkindex];
2701 for (i = slinkindex + 1; i < page->slinkcount; ++i) {
2702 slink = &page->slinks[i];
2703 if (slink->bbox.y0 >= found->bbox.y0) {
2704 found = slink;
2705 break;
2708 break;
2710 case dir_up:
2711 slinkindex = Int_val (Field (dir_v, 0));
2712 found = &page->slinks[slinkindex];
2713 for (i = slinkindex - 1; i >= 0; --i) {
2714 slink = &page->slinks[i];
2715 if (slink->bbox.y0 <= found->bbox.y0) {
2716 found = slink;
2717 break;
2720 break;
2723 else {
2724 dirtag = Int_val (dir_v);
2725 switch (dirtag) {
2726 case dir_first:
2727 found = page->slinks;
2728 break;
2730 case dir_last:
2731 if (page->slinks) {
2732 found = page->slinks + (page->slinkcount - 1);
2734 break;
2737 if (found) {
2738 ret_v = caml_alloc_small (2, 1);
2739 Field (ret_v, 0) = Val_int (found - page->slinks);
2742 unlock ("ml_findlink");
2743 done:
2744 CAMLreturn (ret_v);
2747 enum { uuri, ugoto, utext, uunexpected,
2748 ulaunch, unamed, uremote, uremotedest };
2750 #define LINKTOVAL \
2752 int pageno; \
2754 switch (link->dest.kind) { \
2755 case FZ_LINK_GOTO: \
2757 fz_point p; \
2759 pageno = link->dest.ld.gotor.page; \
2760 p.x = 0; \
2761 p.y = 0; \
2763 if (link->dest.ld.gotor.flags & fz_link_flag_t_valid) { \
2764 p.y = link->dest.ld.gotor.lt.y; \
2765 fz_transform_point (&p, &pdim->lctm); \
2767 tup_v = caml_alloc_tuple (2); \
2768 ret_v = caml_alloc_small (1, ugoto); \
2769 Field (tup_v, 0) = Val_int (pageno); \
2770 Field (tup_v, 1) = Val_int (p.y); \
2771 Field (ret_v, 0) = tup_v; \
2773 break; \
2775 case FZ_LINK_URI: \
2776 str_v = caml_copy_string (link->dest.ld.uri.uri); \
2777 ret_v = caml_alloc_small (1, uuri); \
2778 Field (ret_v, 0) = str_v; \
2779 break; \
2781 case FZ_LINK_LAUNCH: \
2782 str_v = caml_copy_string (link->dest.ld.launch.file_spec); \
2783 ret_v = caml_alloc_small (1, ulaunch); \
2784 Field (ret_v, 0) = str_v; \
2785 break; \
2787 case FZ_LINK_NAMED: \
2788 str_v = caml_copy_string (link->dest.ld.named.named); \
2789 ret_v = caml_alloc_small (1, unamed); \
2790 Field (ret_v, 0) = str_v; \
2791 break; \
2793 case FZ_LINK_GOTOR: \
2795 int rty; \
2797 str_v = caml_copy_string (link->dest.ld.gotor.file_spec); \
2798 pageno = link->dest.ld.gotor.page; \
2799 if (pageno == -1) { \
2800 gr_v = caml_copy_string (link->dest.ld.gotor.dest); \
2801 rty = uremotedest; \
2803 else { \
2804 gr_v = Val_int (pageno); \
2805 rty = uremote; \
2807 tup_v = caml_alloc_tuple (2); \
2808 ret_v = caml_alloc_small (1, rty); \
2809 Field (tup_v, 0) = str_v; \
2810 Field (tup_v, 1) = gr_v; \
2811 Field (ret_v, 0) = tup_v; \
2813 break; \
2815 default: \
2817 char buf[80]; \
2819 snprintf (buf, sizeof (buf), \
2820 "unhandled link kind %d", link->dest.kind); \
2821 str_v = caml_copy_string (buf); \
2822 ret_v = caml_alloc_small (1, uunexpected); \
2823 Field (ret_v, 0) = str_v; \
2825 break; \
2829 CAMLprim value ml_getlink (value ptr_v, value n_v)
2831 CAMLparam2 (ptr_v, n_v);
2832 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2833 fz_link *link;
2834 struct page *page;
2835 struct pagedim *pdim;
2836 char *s = String_val (ptr_v);
2838 ret_v = Val_int (0);
2839 if (trylock ("ml_getlink")) {
2840 goto done;
2843 page = parse_pointer ("ml_getlink", s);
2844 ensureslinks (page);
2845 pdim = &state.pagedims[page->pdimno];
2846 link = page->slinks[Int_val (n_v)].link;
2847 LINKTOVAL;
2849 unlock ("ml_getlink");
2850 done:
2851 CAMLreturn (ret_v);
2854 CAMLprim value ml_getlinkcount (value ptr_v)
2856 CAMLparam1 (ptr_v);
2857 struct page *page;
2858 char *s = String_val (ptr_v);
2860 page = parse_pointer ("ml_getlinkcount", s);
2861 CAMLreturn (Val_int (page->slinkcount));
2864 CAMLprim value ml_getlinkrect (value ptr_v, value n_v)
2866 CAMLparam2 (ptr_v, n_v);
2867 CAMLlocal1 (ret_v);
2868 struct page *page;
2869 struct slink *slink;
2870 char *s = String_val (ptr_v);
2872 page = parse_pointer ("ml_getlinkrect", s);
2873 ret_v = caml_alloc_tuple (4);
2874 if (trylock ("ml_getlinkrect")) {
2875 Field (ret_v, 0) = Val_int (0);
2876 Field (ret_v, 1) = Val_int (0);
2877 Field (ret_v, 2) = Val_int (0);
2878 Field (ret_v, 3) = Val_int (0);
2879 goto done;
2881 ensureslinks (page);
2883 slink = &page->slinks[Int_val (n_v)];
2884 Field (ret_v, 0) = Val_int (slink->bbox.x0);
2885 Field (ret_v, 1) = Val_int (slink->bbox.y0);
2886 Field (ret_v, 2) = Val_int (slink->bbox.x1);
2887 Field (ret_v, 3) = Val_int (slink->bbox.y1);
2888 unlock ("ml_getlinkrect");
2890 done:
2891 CAMLreturn (ret_v);
2894 CAMLprim value ml_whatsunder (value ptr_v, value x_v, value y_v)
2896 CAMLparam3 (ptr_v, x_v, y_v);
2897 CAMLlocal4 (ret_v, tup_v, str_v, gr_v);
2898 fz_link *link;
2899 struct page *page;
2900 char *s = String_val (ptr_v);
2901 int x = Int_val (x_v), y = Int_val (y_v);
2902 struct pagedim *pdim;
2904 ret_v = Val_int (0);
2905 if (trylock ("ml_whatsunder")) {
2906 goto done;
2909 page = parse_pointer ("ml_whatsunder", s);
2910 pdim = &state.pagedims[page->pdimno];
2911 x += pdim->bounds.x0;
2912 y += pdim->bounds.y0;
2913 link = getlink (page, x, y);
2914 if (link) {
2915 LINKTOVAL;
2917 else {
2918 fz_rect *b;
2919 fz_page_block *pageb;
2920 fz_text_block *block;
2922 ensuretext (page);
2923 for (pageb = page->text->blocks;
2924 pageb < page->text->blocks + page->text->len;
2925 ++pageb) {
2926 fz_text_line *line;
2927 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
2928 block = pageb->u.text;
2930 b = &block->bbox;
2931 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
2932 continue;
2934 for (line = block->lines;
2935 line < block->lines + block->len;
2936 ++line) {
2937 fz_text_span *span;
2939 b = &line->bbox;
2940 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
2941 continue;
2943 for (span = line->first_span; span; span = span->next) {
2944 int charnum;
2946 b = &span->bbox;
2947 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
2948 continue;
2950 for (charnum = 0; charnum < span->len; ++charnum) {
2951 fz_rect bbox;
2952 fz_text_char_bbox (&bbox, span, charnum);
2953 b = &bbox;
2955 if (x >= b->x0 && x <= b->x1
2956 && y >= b->y0 && y <= b->y1) {
2957 fz_text_style *style = span->text->style;
2958 const char *n2 =
2959 style->font && style->font->name
2960 ? style->font->name
2961 : "Span has no font name"
2963 FT_FaceRec *face = style->font->ft_face;
2964 if (face && face->family_name) {
2965 char *s;
2966 char *n1 = face->family_name;
2967 size_t l1 = strlen (n1);
2968 size_t l2 = strlen (n2);
2970 if (l1 != l2 || memcmp (n1, n2, l1)) {
2971 s = malloc (l1 + l2 + 2);
2972 if (s) {
2973 memcpy (s, n2, l2);
2974 s[l2] = '=';
2975 memcpy (s + l2 + 1, n1, l1 + 1);
2976 str_v = caml_copy_string (s);
2977 free (s);
2981 if (str_v == 0) {
2982 str_v = caml_copy_string (n2);
2984 ret_v = caml_alloc_small (1, utext);
2985 Field (ret_v, 0) = str_v;
2986 goto unlock;
2993 unlock:
2994 unlock ("ml_whatsunder");
2996 done:
2997 CAMLreturn (ret_v);
3000 enum { mark_page, mark_block, mark_line, mark_word };
3002 static int uninteresting (int c)
3004 return c == ' ' || c == '\n' || c == '\t' || c == '\n' || c == '\r'
3005 || ispunct (c);
3008 CAMLprim value ml_markunder (value ptr_v, value x_v, value y_v, value mark_v)
3010 CAMLparam4 (ptr_v, x_v, y_v, mark_v);
3011 CAMLlocal1 (ret_v);
3012 fz_rect *b;
3013 struct page *page;
3014 fz_text_line *line;
3015 fz_page_block *pageb;
3016 fz_text_block *block;
3017 struct pagedim *pdim;
3018 int mark = Int_val (mark_v);
3019 char *s = String_val (ptr_v);
3020 int x = Int_val (x_v), y = Int_val (y_v);
3022 ret_v = Val_bool (0);
3023 if (trylock ("ml_markunder")) {
3024 goto done;
3027 page = parse_pointer ("ml_markunder", s);
3028 pdim = &state.pagedims[page->pdimno];
3029 x += pdim->bounds.x0;
3030 y += pdim->bounds.y0;
3032 ensuretext (page);
3034 if (mark == mark_page) {
3035 int i;
3037 for (i = page->text->len - 1; i >= 0; --i) {
3038 pageb = &page->text->blocks[i];
3039 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3041 if (i < 0) goto unlock;
3043 block = pageb->u.text;
3045 page->fmark.i = 0;
3046 page->fmark.span = block->lines->first_span;
3048 line = &block->lines[block->len - 1];
3049 page->lmark.i = line->last_span->len - 1;
3050 page->lmark.span = line->last_span;
3051 ret_v = Val_bool (1);
3052 goto unlock;
3055 for (pageb = page->text->blocks;
3056 pageb < page->text->blocks + page->text->len;
3057 ++pageb) {
3058 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3059 block = pageb->u.text;
3061 b = &block->bbox;
3062 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3063 continue;
3065 if (mark == mark_block) {
3066 page->fmark.i = 0;
3067 page->fmark.span = block->lines->first_span;
3069 line = &block->lines[block->len - 1];
3070 page->lmark.i = line->last_span->len - 1;
3071 page->lmark.span = line->last_span;
3072 ret_v = Val_bool (1);
3073 goto unlock;
3076 for (line = block->lines;
3077 line < block->lines + block->len;
3078 ++line) {
3079 fz_text_span *span;
3081 b = &line->bbox;
3082 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3083 continue;
3085 if (mark == mark_line) {
3086 page->fmark.i = 0;
3087 page->fmark.span = line->first_span;
3089 page->lmark.i = line->last_span->len - 1;
3090 page->lmark.span = line->last_span;
3091 ret_v = Val_bool (1);
3092 goto unlock;
3095 for (span = line->first_span; span; span = span->next) {
3096 int charnum;
3098 b = &span->bbox;
3099 if (!(x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1))
3100 continue;
3102 for (charnum = 0; charnum < span->len; ++charnum) {
3103 fz_rect bbox;
3104 fz_text_char_bbox (&bbox, span, charnum);
3105 b = &bbox;
3107 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1) {
3108 /* unicode ftw */
3109 int charnum2, charnum3 = -1, charnum4 = -1;
3111 if (uninteresting (span->text[charnum].c)) goto unlock;
3113 for (charnum2 = charnum; charnum2 >= 0; --charnum2) {
3114 if (uninteresting (span->text[charnum2].c)) {
3115 charnum3 = charnum2 + 1;
3116 break;
3119 if (charnum3 == -1) charnum3 = 0;
3121 for (charnum2 = charnum + 1;
3122 charnum2 < span->len;
3123 ++charnum2) {
3124 if (uninteresting (span->text[charnum2].c)) break;
3125 charnum4 = charnum2;
3127 if (charnum4 == -1) goto unlock;
3129 page->fmark.i = charnum3;
3130 page->fmark.span = span;
3132 page->lmark.i = charnum4;
3133 page->lmark.span = span;
3134 ret_v = Val_bool (1);
3135 goto unlock;
3141 unlock:
3142 if (!Bool_val (ret_v)) {
3143 page->fmark.span = NULL;
3144 page->lmark.span = NULL;
3145 page->fmark.i = 0;
3146 page->lmark.i = 0;
3148 unlock ("ml_markunder");
3150 done:
3151 CAMLreturn (ret_v);
3154 CAMLprim value ml_rectofblock (value ptr_v, value x_v, value y_v)
3156 CAMLparam3 (ptr_v, x_v, y_v);
3157 CAMLlocal2 (ret_v, res_v);
3158 fz_rect *b = NULL;
3159 struct page *page;
3160 fz_page_block *pageb;
3161 struct pagedim *pdim;
3162 char *s = String_val (ptr_v);
3163 int x = Int_val (x_v), y = Int_val (y_v);
3165 ret_v = Val_int (0);
3166 if (trylock ("ml_rectofblock")) {
3167 goto done;
3170 page = parse_pointer ("ml_rectofblock", s);
3171 pdim = &state.pagedims[page->pdimno];
3172 x += pdim->bounds.x0;
3173 y += pdim->bounds.y0;
3175 ensuretext (page);
3177 for (pageb = page->text->blocks;
3178 pageb < page->text->blocks + page->text->len;
3179 ++pageb) {
3180 switch (pageb->type) {
3181 case FZ_PAGE_BLOCK_TEXT:
3182 b = &pageb->u.text->bbox;
3183 break;
3185 case FZ_PAGE_BLOCK_IMAGE:
3186 b = &pageb->u.image->bbox;
3187 break;
3189 default:
3190 continue;
3193 if (x >= b->x0 && x <= b->x1 && y >= b->y0 && y <= b->y1)
3194 break;
3195 b = NULL;
3197 if (b) {
3198 res_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3199 ret_v = caml_alloc_small (1, 1);
3200 Store_double_field (res_v, 0, b->x0);
3201 Store_double_field (res_v, 1, b->x1);
3202 Store_double_field (res_v, 2, b->y0);
3203 Store_double_field (res_v, 3, b->y1);
3204 Field (ret_v, 0) = res_v;
3206 unlock ("ml_rectofblock");
3208 done:
3209 CAMLreturn (ret_v);
3212 CAMLprim value ml_seltext (value ptr_v, value rect_v)
3214 CAMLparam2 (ptr_v, rect_v);
3215 fz_rect b;
3216 struct page *page;
3217 struct pagedim *pdim;
3218 int i, x0, x1, y0, y1;
3219 char *s = String_val (ptr_v);
3220 int fi = 0, li = 0;
3221 fz_page_block *pageb;
3222 fz_text_block *block;
3223 fz_text_span *span, *fspan, *lspan;
3224 fz_text_line *line, *fline = NULL, *lline = NULL;
3226 if (trylock ("ml_seltext")) {
3227 goto done;
3230 page = parse_pointer ("ml_seltext", s);
3231 ensuretext (page);
3233 pdim = &state.pagedims[page->pdimno];
3234 x0 = Int_val (Field (rect_v, 0)) + pdim->bounds.x0;;
3235 y0 = Int_val (Field (rect_v, 1)) + pdim->bounds.y0;
3236 x1 = Int_val (Field (rect_v, 2)) + pdim->bounds.x0;
3237 y1 = Int_val (Field (rect_v, 3)) + pdim->bounds.y0;
3239 if (0) {
3240 glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
3241 glColor3ub (128, 128, 128);
3242 glRecti (x0, y0, x1, y1);
3243 glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
3246 fspan = lspan = NULL;
3248 for (pageb= page->text->blocks;
3249 pageb < page->text->blocks + page->text->len;
3250 ++pageb) {
3251 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3252 block = pageb->u.text;
3253 for (line = block->lines;
3254 line < block->lines + block->len;
3255 ++line) {
3256 fz_text_span *span;
3258 for (span = line->first_span; span; span = span->next) {
3259 for (i = 0; i < span->len; ++i) {
3260 int selected = 0;
3262 fz_text_char_bbox (&b, span, i);
3264 if (x0 >= b.x0 && x0 <= b.x1
3265 && y0 >= b.y0 && y0 <= b.y1) {
3266 fspan = span;
3267 fline = line;
3268 fi = i;
3269 selected = 1;
3271 if (x1 >= b.x0 && x1 <= b.x1
3272 && y1 >= b.y0 && y1 <= b.y1) {
3273 lspan = span;
3274 lline = line;
3275 li = i;
3276 selected = 1;
3278 if (0 && selected) {
3279 glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
3280 glColor3ub (128, 128, 128);
3281 glRecti (b.x0, b.y0, b.x1, b.y1);
3282 glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
3288 if (y1 < y0 || x1 < x0) {
3289 int swap = 0;
3291 if (fspan == lspan) {
3292 swap = 1;
3294 else {
3295 if (y1 < y0) {
3296 if (fline != lline) {
3297 swap = 1;
3302 if (swap) {
3303 i = fi;
3304 span = fspan;
3306 fi = li;
3307 fspan = lspan;
3309 li = i;
3310 lspan = span;
3314 page->fmark.i = fi;
3315 page->fmark.span = fspan;
3317 page->lmark.i = li;
3318 page->lmark.span = lspan;
3320 unlock ("ml_seltext");
3322 done:
3323 CAMLreturn (Val_unit);
3326 static int UNUSED_ATTR pipespan (FILE *f, fz_text_span *span, int a, int b)
3328 char buf[4];
3329 int i, len, ret;
3331 for (i = a; i <= b; ++i) {
3332 len = fz_runetochar (buf, span->text[i].c);
3333 ret = fwrite (buf, len, 1, f);
3335 if (ret != 1) {
3336 fprintf (stderr, "failed to write %d bytes ret=%d: %s\n",
3337 len, ret, strerror (errno));
3338 return -1;
3341 return 0;
3344 #ifdef __CYGWIN__
3345 value ml_popen (value UNUSED_ATTR u1, value UNUSED_ATTR u2)
3347 caml_failwith ("ml_popen not implemented under Cygwin");
3349 #else
3350 CAMLprim value ml_popen (value command_v, value fds_v)
3352 CAMLparam2 (command_v, fds_v);
3353 CAMLlocal2 (l_v, tup_v);
3354 int ret;
3355 char *msg = NULL;
3356 value earg_v = Nothing;
3357 posix_spawnattr_t attr;
3358 posix_spawn_file_actions_t fa;
3359 char *argv[] = { "/bin/sh", "-c", String_val (command_v), NULL };
3361 if ((ret = posix_spawn_file_actions_init (&fa)) != 0) {
3362 unix_error (ret, "posix_spawn_file_actions_init", Nothing);
3365 if ((ret = posix_spawnattr_init (&attr)) != 0) {
3366 msg = "posix_spawnattr_init";
3367 goto fail1;
3370 #ifdef POSIX_SPAWN_USEVFORK
3371 if ((ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK)) != 0) {
3372 msg = "posix_spawnattr_setflags POSIX_SPAWN_USEVFORK";
3373 goto fail;
3375 #endif
3377 for (l_v = fds_v; l_v != Val_int (0); l_v = Field (l_v, 1)) {
3378 int fd1, fd2;
3380 tup_v = Field (l_v, 0);
3381 fd1 = Int_val (Field (tup_v, 0));
3382 fd2 = Int_val (Field (tup_v, 1));
3383 if (fd2 < 0) {
3384 if ((ret = posix_spawn_file_actions_addclose (&fa, fd1)) != 0) {
3385 msg = "posix_spawn_file_actions_addclose";
3386 earg_v = tup_v;
3387 goto fail;
3390 else {
3391 if ((ret = posix_spawn_file_actions_adddup2 (&fa, fd1, fd2)) != 0) {
3392 msg = "posix_spawn_file_actions_adddup2";
3393 earg_v = tup_v;
3394 goto fail;
3399 if ((ret = posix_spawn (NULL, "/bin/sh", &fa, &attr, argv, environ))) {
3400 msg = "posix_spawn";
3401 goto fail;
3404 fail:
3405 if ((ret = posix_spawnattr_destroy (&attr)) != 0) {
3406 fprintf (stderr, "posix_spawnattr_destroy: %s\n", strerror (ret));
3409 fail1:
3410 if ((ret = posix_spawn_file_actions_destroy (&fa)) != 0) {
3411 fprintf (stderr, "posix_spawn_file_actions_destroy: %s\n",
3412 strerror (ret));
3415 if (msg)
3416 unix_error (ret, msg, earg_v);
3418 CAMLreturn (Val_unit);
3420 #endif
3422 CAMLprim value ml_copysel (value fd_v, value ptr_v, value clear_v)
3424 CAMLparam3 (fd_v, ptr_v, clear_v);
3425 FILE *f;
3426 int seen = 0;
3427 struct page *page;
3428 fz_text_line *line;
3429 fz_page_block *pageb;
3430 fz_text_block *block;
3431 int fd = Int_val (fd_v);
3432 char *s = String_val (ptr_v);
3433 int clear = Bool_val (clear_v);
3435 if (trylock ("ml_copysel")) {
3436 goto done;
3439 page = parse_pointer ("ml_sopysel", s);
3441 if (!page->fmark.span || !page->lmark.span) {
3442 fprintf (stderr, "nothing to copy\n");
3443 goto unlock;
3446 f = fdopen (fd, "w");
3447 if (!f) {
3448 fprintf (stderr, "failed to fopen sel pipe: %s\n",
3449 strerror (errno));
3450 f = stdout;
3453 for (pageb = page->text->blocks;
3454 pageb < page->text->blocks + page->text->len;
3455 ++pageb) {
3456 if (pageb->type != FZ_PAGE_BLOCK_TEXT) continue;
3457 block = pageb->u.text;
3458 for (line = block->lines;
3459 line < block->lines + block->len;
3460 ++line) {
3461 fz_text_span *span;
3463 for (span = line->first_span; span; span = span->next) {
3464 int a, b;
3466 seen |= span == page->fmark.span || span == page->lmark.span;
3467 a = span == page->fmark.span ? page->fmark.i : 0;
3468 b = span == page->lmark.span ? page->lmark.i : span->len - 1;
3470 if (seen) {
3471 if (pipespan (f, span, a, b)) {
3472 goto close;
3474 if (span == line->last_span) {
3475 if (putc ('\n', f) == EOF) {
3476 fprintf (stderr,
3477 "failed break line on sel pipe: %s\n",
3478 strerror (errno));
3479 goto close;
3482 if (span == page->lmark.span) {
3483 goto endloop;
3489 endloop:
3490 if (clear) {
3491 page->lmark.span = NULL;
3492 page->fmark.span = NULL;
3495 close:
3496 if (f != stdout) {
3497 int ret = fclose (f);
3498 fd = -1;
3499 if (ret == -1) {
3500 if (errno != ECHILD) {
3501 fprintf (stderr, "failed to close sel pipe: %s\n",
3502 strerror (errno));
3506 unlock:
3507 unlock ("ml_copysel");
3509 done:
3510 if (fd >= 0) {
3511 if (close (fd)) {
3512 fprintf (stderr, "failed to close sel pipe: %s\n",
3513 strerror (errno));
3516 CAMLreturn (Val_unit);
3519 CAMLprim value ml_getpdimrect (value pagedimno_v)
3521 CAMLparam1 (pagedimno_v);
3522 CAMLlocal1 (ret_v);
3523 int pagedimno = Int_val (pagedimno_v);
3524 fz_rect box;
3526 ret_v = caml_alloc_small (4 * Double_wosize, Double_array_tag);
3527 if (trylock ("ml_getpdimrect")) {
3528 box = fz_empty_rect;
3530 else {
3531 box = state.pagedims[pagedimno].mediabox;
3532 unlock ("ml_getpdimrect");
3535 Store_double_field (ret_v, 0, box.x0);
3536 Store_double_field (ret_v, 1, box.x1);
3537 Store_double_field (ret_v, 2, box.y0);
3538 Store_double_field (ret_v, 3, box.y1);
3540 CAMLreturn (ret_v);
3543 CAMLprim value ml_zoom_for_height (value winw_v, value winh_v,
3544 value dw_v, value cols_v)
3546 CAMLparam3 (winw_v, winh_v, dw_v);
3547 CAMLlocal1 (ret_v);
3548 int i;
3549 double zoom = -1.;
3550 double maxh = 0.0;
3551 struct pagedim *p;
3552 double winw = Int_val (winw_v);
3553 double winh = Int_val (winh_v);
3554 double dw = Int_val (dw_v);
3555 double cols = Int_val (cols_v);
3556 double pw = 1.0, ph = 1.0;
3558 if (trylock ("ml_zoom_for_height")) {
3559 goto done;
3562 for (i = 0, p = state.pagedims; i < state.pagedimcount; ++i, ++p) {
3563 double w = p->pagebox.x1 / cols;
3564 double h = p->pagebox.y1;
3565 if (h > maxh) {
3566 maxh = h;
3567 ph = h;
3568 if (state.fitmodel != FitProportional) pw = w;
3570 if ((state.fitmodel == FitProportional) && w > pw) pw = w;
3573 zoom = (((winh / ph) * pw) + dw) / winw;
3574 unlock ("ml_zoom_for_height");
3575 done:
3576 ret_v = caml_copy_double (zoom);
3577 CAMLreturn (ret_v);
3580 CAMLprim value ml_draw_string (value pt_v, value x_v, value y_v, value string_v)
3582 CAMLparam4 (pt_v, x_v, y_v, string_v);
3583 CAMLlocal1 (ret_v);
3584 int pt = Int_val(pt_v);
3585 int x = Int_val (x_v);
3586 int y = Int_val (y_v);
3587 double w;
3589 w = draw_string (state.face, pt, x, y, String_val (string_v));
3590 ret_v = caml_copy_double (w);
3591 CAMLreturn (ret_v);
3594 CAMLprim value ml_measure_string (value pt_v, value string_v)
3596 CAMLparam2 (pt_v, string_v);
3597 CAMLlocal1 (ret_v);
3598 int pt = Int_val (pt_v);
3599 double w;
3601 w = measure_string (state.face, pt, String_val (string_v));
3602 ret_v = caml_copy_double (w);
3603 CAMLreturn (ret_v);
3606 CAMLprim value ml_getpagebox (value opaque_v)
3608 CAMLparam1 (opaque_v);
3609 CAMLlocal1 (ret_v);
3610 fz_rect rect;
3611 fz_irect bbox;
3612 fz_matrix ctm;
3613 fz_device *dev;
3614 char *s = String_val (opaque_v);
3615 struct page *page = parse_pointer ("ml_getpagebox", s);
3617 ret_v = caml_alloc_tuple (4);
3618 dev = fz_new_bbox_device (state.ctx, &rect);
3619 dev->hints |= FZ_IGNORE_SHADE;
3621 switch (page->type) {
3622 case DPDF:
3623 ctm = pagectm (page);
3624 pdf_run_page (state.u.pdf, page->u.pdfpage, dev, &ctm, NULL);
3625 break;
3627 case DXPS:
3628 ctm = pagectm (page);
3629 xps_run_page (state.u.xps, page->u.xpspage, dev, &ctm, NULL);
3630 break;
3632 default:
3633 rect = fz_infinite_rect;
3634 break;
3637 fz_free_device (dev);
3638 fz_round_rect (&bbox, &rect);
3639 Field (ret_v, 0) = Val_int (bbox.x0);
3640 Field (ret_v, 1) = Val_int (bbox.y0);
3641 Field (ret_v, 2) = Val_int (bbox.x1);
3642 Field (ret_v, 3) = Val_int (bbox.y1);
3644 CAMLreturn (ret_v);
3647 CAMLprim value ml_setaalevel (value level_v)
3649 CAMLparam1 (level_v);
3651 state.aalevel = Int_val (level_v);
3652 CAMLreturn (Val_unit);
3655 #undef pixel
3656 #include <X11/Xlib.h>
3657 #include <GL/glx.h>
3659 static struct {
3660 Display *dpy;
3661 GLXContext ctx;
3662 GLXDrawable drawable;
3663 } glx;
3665 #include "keysym2ucs.c"
3667 CAMLprim value ml_keysymtoutf8 (value keysym_v)
3669 CAMLparam1 (keysym_v);
3670 CAMLlocal1 (str_v);
3671 KeySym keysym = Int_val (keysym_v);
3672 Rune rune;
3673 int len;
3674 char buf[5];
3676 rune = keysym2ucs (keysym);
3677 len = fz_runetochar (buf, rune);
3678 buf[len] = 0;
3679 str_v = caml_copy_string (buf);
3680 CAMLreturn (str_v);
3683 CAMLprim value ml_glx (value win_v)
3685 CAMLparam1 (win_v);
3686 XVisualInfo *visual;
3687 int screen, wid = Int_val (win_v);
3688 int attributes[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
3690 glx.dpy = XOpenDisplay (NULL);
3691 if (!glx.dpy) {
3692 caml_failwith ("XOpenDisplay");
3695 screen = DefaultScreen (glx.dpy);
3696 visual = glXChooseVisual (glx.dpy, screen, attributes);
3697 if (!visual) {
3698 XCloseDisplay (glx.dpy);
3699 glx.dpy = NULL;
3700 caml_failwith ("glXChooseVisual");
3703 glx.ctx = glXCreateContext (glx.dpy, visual, NULL, True);
3704 XFree (visual);
3705 if (!glx.ctx) {
3706 XCloseDisplay (glx.dpy);
3707 glx.dpy = NULL;
3708 caml_failwith ("glXCreateContext");
3711 if (!glXMakeCurrent (glx.dpy, wid, glx.ctx)) {
3712 glXDestroyContext (glx.dpy, glx.ctx);
3713 XCloseDisplay (glx.dpy);
3714 glx.dpy = NULL;
3715 glx.ctx = NULL;
3716 caml_failwith ("glXMakeCurrent");
3718 glx.drawable = wid;
3719 CAMLreturn (Val_unit);
3722 CAMLprim value ml_swapb (value unit_v)
3724 CAMLparam1 (unit_v);
3725 glXSwapBuffers (glx.dpy, glx.drawable);
3726 CAMLreturn (Val_unit);
3729 CAMLprim value ml_glxsync (value unit_v)
3731 CAMLparam1 (unit_v);
3732 if (glx.dpy && glx.ctx) {
3733 glXWaitX ();
3734 glXWaitGL ();
3736 CAMLreturn (Val_unit);
3739 enum { piunknown, pilinux, piosx, pisun, pifreebsd,
3740 pidragonflybsd, piopenbsd, pinetbsd, picygwin };
3742 CAMLprim value ml_platform (value unit_v)
3744 CAMLparam1 (unit_v);
3745 int platid = piunknown;
3747 #if defined __linux__
3748 platid = pilinux;
3749 #elif defined __CYGWIN__
3750 platid = picygwin;
3751 #elif defined __DragonFly__
3752 platid = pidragonflybsd;
3753 #elif defined __FreeBSD__
3754 platid = pifreebsd;
3755 #elif defined __OpenBSD__
3756 platid = piopenbsd;
3757 #elif defined __NetBSD__
3758 platid = pinetbsd;
3759 #elif defined __sun__
3760 platid = pisun;
3761 #elif defined __APPLE__
3762 platid = piosx;
3763 #endif
3764 CAMLreturn (Val_int (platid));
3767 CAMLprim value ml_cloexec (value fd_v)
3769 CAMLparam1 (fd_v);
3770 int fd = Int_val (fd_v);
3772 if (fcntl (fd, F_SETFD, FD_CLOEXEC, 1)) {
3773 uerror ("fcntl", Nothing);
3775 CAMLreturn (Val_unit);
3778 CAMLprim value ml_getpbo (value w_v, value h_v, value cs_v)
3780 CAMLparam2 (w_v, h_v);
3781 CAMLlocal1 (ret_v);
3782 struct pbo *pbo;
3783 int w = Int_val (w_v);
3784 int h = Int_val (h_v);
3785 int cs = Int_val (cs_v);
3787 if (state.pbo_usable) {
3788 pbo = calloc (sizeof (*pbo), 1);
3789 if (!pbo) {
3790 err (1, "calloc pbo");
3793 switch (cs) {
3794 case 0:
3795 case 1:
3796 pbo->size = w*h*4;
3797 break;
3798 case 2:
3799 pbo->size = w*h*2;
3800 break;
3801 default:
3802 errx (1, "ml_getpbo: invalid colorspace %d", cs);
3805 state.glGenBuffersARB (1, &pbo->id);
3806 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, pbo->id);
3807 state.glBufferDataARB (GL_PIXEL_UNPACK_BUFFER_ARB, pbo->size,
3808 NULL, GL_STREAM_DRAW);
3809 pbo->ptr = state.glMapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB,
3810 GL_READ_WRITE);
3811 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
3812 if (!pbo->ptr) {
3813 fprintf (stderr, "glMapBufferARB failed: %#x\n", glGetError ());
3814 state.glDeleteBuffersARB (1, &pbo->id);
3815 free (pbo);
3816 ret_v = caml_copy_string ("0");
3818 else {
3819 int res;
3820 char *s;
3822 res = snprintf (NULL, 0, "%" FMT_ptr, pbo);
3823 if (res < 0) {
3824 err (1, "snprintf %" FMT_ptr " failed", pbo);
3826 s = malloc (res+1);
3827 if (!s) {
3828 err (1, "malloc %d bytes failed", res+1);
3830 res = sprintf (s, "%" FMT_ptr, pbo);
3831 if (res < 0) {
3832 err (1, "sprintf %" FMT_ptr " failed", pbo);
3834 ret_v = caml_copy_string (s);
3835 free (s);
3838 else {
3839 ret_v = caml_copy_string ("0");
3841 CAMLreturn (ret_v);
3844 CAMLprim value ml_freepbo (value s_v)
3846 CAMLparam1 (s_v);
3847 char *s = String_val (s_v);
3848 struct tile *tile = parse_pointer ("ml_freepbo", s);
3850 if (tile->pbo) {
3851 state.glDeleteBuffersARB (1, &tile->pbo->id);
3852 tile->pbo->id = -1;
3853 tile->pbo->ptr = NULL;
3854 tile->pbo->size = -1;
3856 CAMLreturn (Val_unit);
3859 CAMLprim value ml_unmappbo (value s_v)
3861 CAMLparam1 (s_v);
3862 char *s = String_val (s_v);
3863 struct tile *tile = parse_pointer ("ml_unmappbo", s);
3865 if (tile->pbo) {
3866 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, tile->pbo->id);
3867 if (state.glUnmapBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB) == GL_FALSE) {
3868 errx (1, "glUnmapBufferARB failed: %#x\n", glGetError ());
3870 tile->pbo->ptr = NULL;
3871 state.glBindBufferARB (GL_PIXEL_UNPACK_BUFFER_ARB, 0);
3873 CAMLreturn (Val_unit);
3876 static void setuppbo (void)
3878 #define GGPA(n) (*(void (**) ()) &state.n = glXGetProcAddress ((GLubyte *) #n))
3879 state.pbo_usable = GGPA (glBindBufferARB)
3880 && GGPA (glUnmapBufferARB)
3881 && GGPA (glMapBufferARB)
3882 && GGPA (glBufferDataARB)
3883 && GGPA (glGenBuffersARB)
3884 && GGPA (glDeleteBuffersARB);
3885 #undef GGPA
3888 CAMLprim value ml_pbo_usable (value unit_v)
3890 CAMLparam1 (unit_v);
3891 CAMLreturn (Val_bool (state.pbo_usable));
3894 CAMLprim value ml_unproject (value ptr_v, value x_v, value y_v)
3896 CAMLparam3 (ptr_v, x_v, y_v);
3897 CAMLlocal2 (ret_v, tup_v);
3898 struct page *page;
3899 char *s = String_val (ptr_v);
3900 int x = Int_val (x_v), y = Int_val (y_v);
3901 struct pagedim *pdim;
3902 fz_point p;
3903 fz_matrix ctm;
3905 page = parse_pointer ("ml_unproject", s);
3906 pdim = &state.pagedims[page->pdimno];
3908 ret_v = Val_int (0);
3909 if (trylock ("ml_unproject")) {
3910 goto done;
3913 switch (page->type) {
3914 case DPDF:
3915 trimctm (page->u.pdfpage, page->pdimno);
3916 break;
3918 default:
3919 break;
3921 p.x = x;
3922 p.y = y;
3924 fz_concat (&ctm, &pdim->tctm, &pdim->ctm);
3925 fz_invert_matrix (&ctm, &ctm);
3926 fz_transform_point (&p, &ctm);
3928 tup_v = caml_alloc_tuple (2);
3929 ret_v = caml_alloc_small (1, 1);
3930 Field (tup_v, 0) = Val_int (p.x);
3931 Field (tup_v, 1) = Val_int (p.y);
3932 Field (ret_v, 0) = tup_v;
3934 unlock ("ml_unproject");
3935 done:
3936 CAMLreturn (ret_v);
3939 static void makestippletex (void)
3941 const char pixels[] = "\xff\xff\0\0";
3942 glGenTextures (1, &state.stid);
3943 glBindTexture (GL_TEXTURE_1D, state.stid);
3944 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
3945 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3946 glTexImage1D (
3947 GL_TEXTURE_1D,
3949 GL_ALPHA,
3952 GL_ALPHA,
3953 GL_UNSIGNED_BYTE,
3954 pixels
3958 CAMLprim value ml_init (value pipe_v, value params_v)
3960 CAMLparam2 (pipe_v, params_v);
3961 CAMLlocal2 (trim_v, fuzz_v);
3962 int ret;
3963 int texcount;
3964 char *fontpath;
3965 int colorspace;
3966 int mustoresize;
3967 int haspboext;
3968 struct sigaction sa;
3970 state.cr = Int_val (Field (pipe_v, 0));
3971 state.cw = Int_val (Field (pipe_v, 1));
3972 state.rotate = Int_val (Field (params_v, 0));
3973 state.fitmodel = Int_val (Field (params_v, 1));
3974 trim_v = Field (params_v, 2);
3975 texcount = Int_val (Field (params_v, 3));
3976 state.sliceheight = Int_val (Field (params_v, 4));
3977 mustoresize = Int_val (Field (params_v, 5));
3978 colorspace = Int_val (Field (params_v, 6));
3979 fontpath = String_val (Field (params_v, 7));
3981 state.trimcachepath = strdup (String_val (Field (params_v, 8)));
3982 if (!state.trimcachepath) {
3983 fprintf (stderr, "failed to strdup trimcachepath: %s\n",
3984 strerror (errno));
3986 haspboext = Bool_val (Field (params_v, 9));
3988 state.ctx = fz_new_context (NULL, NULL, mustoresize);
3990 state.trimmargins = Bool_val (Field (trim_v, 0));
3991 fuzz_v = Field (trim_v, 1);
3992 state.trimfuzz.x0 = Int_val (Field (fuzz_v, 0));
3993 state.trimfuzz.y0 = Int_val (Field (fuzz_v, 1));
3994 state.trimfuzz.x1 = Int_val (Field (fuzz_v, 2));
3995 state.trimfuzz.y1 = Int_val (Field (fuzz_v, 3));
3997 set_tex_params (colorspace);
3999 if (*fontpath) {
4000 state.face = load_font (fontpath);
4002 else {
4003 unsigned int len;
4004 void *base = pdf_lookup_substitute_font (0, 0, 0, 0, &len);
4006 state.face = load_builtin_font (base, len);
4008 if (!state.face) _exit (1);
4010 realloctexts (texcount);
4012 if (haspboext) {
4013 setuppbo ();
4016 makestippletex ();
4017 #ifdef __CYGWIN__
4018 sa.sa_handler = SIG_IGN;
4019 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
4020 #else
4021 sa.sa_handler = SIG_DFL;
4022 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT;
4023 #endif
4024 if (sigemptyset (&sa.sa_mask)) {
4025 err (1, "sigemptyset");
4027 if (sigaction (SIGCHLD, &sa, NULL)) {
4028 err (1, "sigaction");
4031 ret = pthread_create (&state.thread, NULL, mainloop, NULL);
4032 if (ret) {
4033 errx (1, "pthread_create: %s", strerror (ret));
4036 CAMLreturn (Val_unit);