summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/cbz/mucbz.c435
-rw-r--r--source/fitz/bbox-device.c231
-rw-r--r--source/fitz/bitmap.c123
-rw-r--r--source/fitz/buffer.c390
-rw-r--r--source/fitz/colorspace.c1277
-rw-r--r--source/fitz/compressed-buffer.c75
-rw-r--r--source/fitz/context.c210
-rw-r--r--source/fitz/crypt-aes.c569
-rw-r--r--source/fitz/crypt-arc4.c98
-rw-r--r--source/fitz/crypt-md5.c272
-rw-r--r--source/fitz/crypt-sha2.c393
-rw-r--r--source/fitz/device.c388
-rw-r--r--source/fitz/document.c274
-rw-r--r--source/fitz/draw-affine.c755
-rw-r--r--source/fitz/draw-blend.c636
-rw-r--r--source/fitz/draw-device.c2126
-rw-r--r--source/fitz/draw-edge.c972
-rw-r--r--source/fitz/draw-glyph.c241
-rw-r--r--source/fitz/draw-imp.h46
-rw-r--r--source/fitz/draw-mesh.c273
-rw-r--r--source/fitz/draw-paint.c479
-rw-r--r--source/fitz/draw-path.c831
-rw-r--r--source/fitz/draw-scale-simple.c1509
-rw-r--r--source/fitz/draw-scale.c1541
-rw-r--r--source/fitz/draw-unpack.c242
-rw-r--r--source/fitz/error.c155
-rw-r--r--source/fitz/filter-basic.c662
-rw-r--r--source/fitz/filter-dct.c256
-rw-r--r--source/fitz/filter-fax.c776
-rw-r--r--source/fitz/filter-flate.c117
-rw-r--r--source/fitz/filter-jbig2.c108
-rw-r--r--source/fitz/filter-lzw.c224
-rw-r--r--source/fitz/filter-predict.c256
-rw-r--r--source/fitz/font.c1094
-rw-r--r--source/fitz/function.c48
-rw-r--r--source/fitz/geometry.c483
-rw-r--r--source/fitz/getopt.c66
-rw-r--r--source/fitz/halftone.c202
-rw-r--r--source/fitz/hash.c357
-rw-r--r--source/fitz/image.c493
-rw-r--r--source/fitz/link.c65
-rw-r--r--source/fitz/list-device.c851
-rw-r--r--source/fitz/load-jpeg.c111
-rw-r--r--source/fitz/load-jpx.c253
-rw-r--r--source/fitz/load-png.c599
-rw-r--r--source/fitz/load-tiff.c867
-rw-r--r--source/fitz/memento.c1535
-rw-r--r--source/fitz/memory.c402
-rw-r--r--source/fitz/outline.c62
-rw-r--r--source/fitz/output-pcl.c856
-rw-r--r--source/fitz/output-pwg.c318
-rw-r--r--source/fitz/output.c100
-rw-r--r--source/fitz/path.c507
-rw-r--r--source/fitz/pixmap.c1062
-rw-r--r--source/fitz/shade.c1096
-rw-r--r--source/fitz/stext-device.c1027
-rw-r--r--source/fitz/stext-output.c400
-rw-r--r--source/fitz/stext-paragraph.c1500
-rw-r--r--source/fitz/stext-search.c279
-rw-r--r--source/fitz/store.c638
-rw-r--r--source/fitz/stream-open.c210
-rw-r--r--source/fitz/stream-read.c219
-rw-r--r--source/fitz/string.c264
-rw-r--r--source/fitz/svg-device.c619
-rw-r--r--source/fitz/text.c154
-rw-r--r--source/fitz/time.c144
-rw-r--r--source/fitz/trace-device.c339
-rw-r--r--source/fitz/transition.c165
-rw-r--r--source/fitz/ucdn.c281
-rw-r--r--source/fitz/ucdn.h288
-rw-r--r--source/fitz/unicodedata_db.h4753
-rw-r--r--source/fitz/xml.c460
-rw-r--r--source/img/muimage.c148
-rw-r--r--source/pdf/js/pdf-js-none.c36
-rw-r--r--source/pdf/js/pdf-js.c919
-rw-r--r--source/pdf/js/pdf-jsimp-cpp.c225
-rw-r--r--source/pdf/js/pdf-jsimp-cpp.h29
-rw-r--r--source/pdf/js/pdf-jsimp-v8.cpp476
-rw-r--r--source/pdf/js/pdf-util.js875
-rw-r--r--source/pdf/pdf-annot.c1200
-rw-r--r--source/pdf/pdf-cmap-load.c141
-rw-r--r--source/pdf/pdf-cmap-parse.c344
-rw-r--r--source/pdf/pdf-cmap-table.c183
-rw-r--r--source/pdf/pdf-cmap.c518
-rw-r--r--source/pdf/pdf-colorspace.c338
-rw-r--r--source/pdf/pdf-crypt.c1010
-rw-r--r--source/pdf/pdf-device.c1263
-rw-r--r--source/pdf/pdf-encoding.c82
-rw-r--r--source/pdf/pdf-encodings.h215
-rw-r--r--source/pdf/pdf-event.c144
-rw-r--r--source/pdf/pdf-field.c56
-rw-r--r--source/pdf/pdf-font.c1263
-rw-r--r--source/pdf/pdf-fontfile.c153
-rw-r--r--source/pdf/pdf-form.c2876
-rw-r--r--source/pdf/pdf-function.c1718
-rw-r--r--source/pdf/pdf-glyphlist.h1461
-rw-r--r--source/pdf/pdf-image.c285
-rw-r--r--source/pdf/pdf-interpret.c3111
-rw-r--r--source/pdf/pdf-lex.c553
-rw-r--r--source/pdf/pdf-metrics.c141
-rw-r--r--source/pdf/pdf-nametree.c166
-rw-r--r--source/pdf/pdf-object.c1576
-rw-r--r--source/pdf/pdf-outline.c72
-rw-r--r--source/pdf/pdf-page.c489
-rw-r--r--source/pdf/pdf-parse.c611
-rw-r--r--source/pdf/pdf-pattern.c83
-rw-r--r--source/pdf/pdf-pkcs7.c400
-rw-r--r--source/pdf/pdf-repair.c587
-rw-r--r--source/pdf/pdf-shade.c498
-rw-r--r--source/pdf/pdf-store.c76
-rw-r--r--source/pdf/pdf-stream.c564
-rw-r--r--source/pdf/pdf-type3.c190
-rw-r--r--source/pdf/pdf-unicode.c77
-rw-r--r--source/pdf/pdf-write.c2363
-rw-r--r--source/pdf/pdf-xobject.c232
-rw-r--r--source/pdf/pdf-xref-aux.c39
-rw-r--r--source/pdf/pdf-xref.c1552
-rw-r--r--source/tools/mudraw.c1090
-rw-r--r--source/tools/mutool.c93
-rw-r--r--source/tools/pdfclean.c237
-rw-r--r--source/tools/pdfextract.c231
-rw-r--r--source/tools/pdfinfo.c1032
-rw-r--r--source/tools/pdfposter.c183
-rw-r--r--source/tools/pdfshow.c251
-rw-r--r--source/xps/xps-common.c311
-rw-r--r--source/xps/xps-doc.c534
-rw-r--r--source/xps/xps-glyphs.c623
-rw-r--r--source/xps/xps-gradient.c525
-rw-r--r--source/xps/xps-image.c128
-rw-r--r--source/xps/xps-outline.c152
-rw-r--r--source/xps/xps-path.c1053
-rw-r--r--source/xps/xps-resource.c172
-rw-r--r--source/xps/xps-tile.c390
-rw-r--r--source/xps/xps-util.c165
-rw-r--r--source/xps/xps-zip.c697
135 files changed, 77312 insertions, 0 deletions
diff --git a/source/cbz/mucbz.c b/source/cbz/mucbz.c
new file mode 100644
index 00000000..bbc8a5fe
--- /dev/null
+++ b/source/cbz/mucbz.c
@@ -0,0 +1,435 @@
+#include "mupdf/cbz.h"
+
+#include <zlib.h>
+
+#include <ctype.h> /* for tolower */
+
+#define ZIP_LOCAL_FILE_SIG 0x04034b50
+#define ZIP_CENTRAL_DIRECTORY_SIG 0x02014b50
+#define ZIP_END_OF_CENTRAL_DIRECTORY_SIG 0x06054b50
+
+#define DPI 72.0f
+
+static void cbz_init_document(cbz_document *doc);
+
+static const char *cbz_ext_list[] = {
+ ".jpg", ".jpeg", ".png",
+ ".JPG", ".JPEG", ".PNG",
+ NULL
+};
+
+struct cbz_page_s
+{
+ fz_image *image;
+};
+
+typedef struct cbz_entry_s cbz_entry;
+
+struct cbz_entry_s
+{
+ char *name;
+ int offset;
+};
+
+struct cbz_document_s
+{
+ fz_document super;
+
+ fz_context *ctx;
+ fz_stream *file;
+ int entry_count;
+ cbz_entry *entry;
+ int page_count;
+ int *page;
+};
+
+static inline int getshort(fz_stream *file)
+{
+ int a = fz_read_byte(file);
+ int b = fz_read_byte(file);
+ return a | b << 8;
+}
+
+static inline int getlong(fz_stream *file)
+{
+ int a = fz_read_byte(file);
+ int b = fz_read_byte(file);
+ int c = fz_read_byte(file);
+ int d = fz_read_byte(file);
+ return a | b << 8 | c << 16 | d << 24;
+}
+
+static void *
+cbz_zip_alloc_items(void *ctx, unsigned int items, unsigned int size)
+{
+ return fz_malloc_array(ctx, items, size);
+}
+
+static void
+cbz_zip_free(void *ctx, void *ptr)
+{
+ fz_free(ctx, ptr);
+}
+
+static unsigned char *
+cbz_read_zip_entry(cbz_document *doc, int offset, int *sizep)
+{
+ fz_context *ctx = doc->ctx;
+ fz_stream *file = doc->file;
+ int sig, method, namelength, extralength;
+ unsigned long csize, usize;
+ unsigned char *cdata;
+ int code;
+
+ fz_seek(file, offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP_LOCAL_FILE_SIG)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong zip local file signature (0x%x)", sig);
+
+ (void) getshort(doc->file); /* version */
+ (void) getshort(doc->file); /* general */
+ method = getshort(doc->file);
+ (void) getshort(doc->file); /* file time */
+ (void) getshort(doc->file); /* file date */
+ (void) getlong(doc->file); /* crc-32 */
+ csize = getlong(doc->file); /* csize */
+ usize = getlong(doc->file); /* usize */
+ namelength = getshort(doc->file);
+ extralength = getshort(doc->file);
+
+ fz_seek(file, namelength + extralength, 1);
+
+ cdata = fz_malloc(ctx, csize);
+ fz_try(ctx)
+ {
+ fz_read(file, cdata, csize);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, cdata);
+ fz_rethrow(ctx);
+ }
+
+ if (method == 0)
+ {
+ *sizep = usize;
+ return cdata;
+ }
+
+ if (method == 8)
+ {
+ unsigned char *udata = fz_malloc(ctx, usize);
+ z_stream stream;
+
+ memset(&stream, 0, sizeof stream);
+ stream.zalloc = cbz_zip_alloc_items;
+ stream.zfree = cbz_zip_free;
+ stream.opaque = ctx;
+ stream.next_in = cdata;
+ stream.avail_in = csize;
+ stream.next_out = udata;
+ stream.avail_out = usize;
+
+ fz_try(ctx)
+ {
+ code = inflateInit2(&stream, -15);
+ if (code != Z_OK)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflateInit2 error: %s", stream.msg);
+ code = inflate(&stream, Z_FINISH);
+ if (code != Z_STREAM_END) {
+ inflateEnd(&stream);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflate error: %s", stream.msg);
+ }
+ code = inflateEnd(&stream);
+ if (code != Z_OK)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflateEnd error: %s", stream.msg);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, cdata);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, udata);
+ fz_rethrow(ctx);
+ }
+
+ *sizep = usize;
+ return udata;
+ }
+
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown zip method: %d", method);
+ return NULL; /* not reached */
+}
+
+static int
+cbz_compare_entries(const void *a_, const void *b_)
+{
+ const cbz_entry *a = a_;
+ const cbz_entry *b = b_;
+ return strcmp(a->name, b->name);
+}
+
+static void
+cbz_read_zip_dir_imp(cbz_document *doc, int startoffset)
+{
+ fz_context *ctx = doc->ctx;
+ fz_stream *file = doc->file;
+ int sig, offset, count;
+ int namesize, metasize, commentsize;
+ int i, k;
+
+ fz_seek(file, startoffset, 0);
+
+ sig = getlong(file);
+ if (sig != ZIP_END_OF_CENTRAL_DIRECTORY_SIG)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong zip end of central directory signature (0x%x)", sig);
+
+ (void) getshort(file); /* this disk */
+ (void) getshort(file); /* start disk */
+ (void) getshort(file); /* entries in this disk */
+ count = getshort(file); /* entries in central directory disk */
+ (void) getlong(file); /* size of central directory */
+ offset = getlong(file); /* offset to central directory */
+
+ doc->entry = fz_calloc(ctx, count, sizeof(cbz_entry));
+ doc->entry_count = count;
+
+ fz_seek(file, offset, 0);
+
+ for (i = 0; i < count; i++)
+ {
+ cbz_entry *entry = doc->entry + i;
+
+ sig = getlong(doc->file);
+ if (sig != ZIP_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip central directory signature (0x%x)", sig);
+
+ (void) getshort(file); /* version made by */
+ (void) getshort(file); /* version to extract */
+ (void) getshort(file); /* general */
+ (void) getshort(file); /* method */
+ (void) getshort(file); /* last mod file time */
+ (void) getshort(file); /* last mod file date */
+ (void) getlong(file); /* crc-32 */
+ (void) getlong(file); /* csize */
+ (void) getlong(file); /* usize */
+ namesize = getshort(file);
+ metasize = getshort(file);
+ commentsize = getshort(file);
+ (void) getshort(file); /* disk number start */
+ (void) getshort(file); /* int file atts */
+ (void) getlong(file); /* ext file atts */
+ entry->offset = getlong(file);
+
+ entry->name = fz_malloc(ctx, namesize + 1);
+ fz_read(file, (unsigned char *)entry->name, namesize);
+ entry->name[namesize] = 0;
+
+ fz_seek(file, metasize, 1);
+ fz_seek(file, commentsize, 1);
+ }
+
+ qsort(doc->entry, count, sizeof(cbz_entry), cbz_compare_entries);
+
+ doc->page_count = 0;
+ doc->page = fz_malloc_array(ctx, count, sizeof(int));
+
+ for (i = 0; i < count; i++)
+ for (k = 0; cbz_ext_list[k]; k++)
+ if (strstr(doc->entry[i].name, cbz_ext_list[k]))
+ doc->page[doc->page_count++] = i;
+}
+
+static void
+cbz_read_zip_dir(cbz_document *doc)
+{
+ fz_stream *file = doc->file;
+ unsigned char buf[512];
+ int filesize, back, maxback;
+ int i, n;
+
+ fz_seek(file, 0, 2);
+ filesize = fz_tell(file);
+
+ maxback = fz_mini(filesize, 0xFFFF + sizeof buf);
+ back = fz_mini(maxback, sizeof buf);
+
+ while (back < maxback)
+ {
+ fz_seek(file, filesize - back, 0);
+ n = fz_read(file, buf, sizeof buf);
+ for (i = n - 4; i > 0; i--)
+ {
+ if (!memcmp(buf + i, "PK\5\6", 4))
+ {
+ cbz_read_zip_dir_imp(doc, filesize - back + i);
+ return;
+ }
+ }
+ back += sizeof buf - 4;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find end of central directory");
+}
+
+cbz_document *
+cbz_open_document_with_stream(fz_context *ctx, fz_stream *file)
+{
+ cbz_document *doc;
+
+ doc = fz_malloc_struct(ctx, cbz_document);
+ cbz_init_document(doc);
+ doc->ctx = ctx;
+ doc->file = fz_keep_stream(file);
+ doc->entry_count = 0;
+ doc->entry = NULL;
+ doc->page_count = 0;
+ doc->page = NULL;
+
+ fz_try(ctx)
+ {
+ cbz_read_zip_dir(doc);
+ }
+ fz_catch(ctx)
+ {
+ cbz_close_document(doc);
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+cbz_document *
+cbz_open_document(fz_context *ctx, const char *filename)
+{
+ fz_stream *file;
+ cbz_document *doc;
+
+ file = fz_open_file(ctx, filename);
+ if (!file)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ fz_try(ctx)
+ {
+ doc = cbz_open_document_with_stream(ctx, file);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+void
+cbz_close_document(cbz_document *doc)
+{
+ int i;
+ fz_context *ctx = doc->ctx;
+ for (i = 0; i < doc->entry_count; i++)
+ fz_free(ctx, doc->entry[i].name);
+ fz_free(ctx, doc->entry);
+ fz_free(ctx, doc->page);
+ fz_close(doc->file);
+ fz_free(ctx, doc);
+}
+
+int
+cbz_count_pages(cbz_document *doc)
+{
+ return doc->page_count;
+}
+
+cbz_page *
+cbz_load_page(cbz_document *doc, int number)
+{
+ fz_context *ctx = doc->ctx;
+ unsigned char *data;
+ cbz_page *page = NULL;
+ int size;
+
+ if (number < 0 || number >= doc->page_count)
+ return NULL;
+
+ number = doc->page[number];
+
+ fz_var(data);
+ fz_var(page);
+ fz_try(ctx)
+ {
+ page = fz_malloc_struct(ctx, cbz_page);
+ page->image = NULL;
+
+ data = cbz_read_zip_entry(doc, doc->entry[number].offset, &size);
+
+ page->image = fz_new_image_from_data(ctx, data, size);
+ }
+ fz_catch(ctx)
+ {
+ cbz_free_page(doc, page);
+ fz_rethrow(ctx);
+ }
+
+ return page;
+}
+
+void
+cbz_free_page(cbz_document *doc, cbz_page *page)
+{
+ if (!page)
+ return;
+ fz_drop_image(doc->ctx, page->image);
+ fz_free(doc->ctx, page);
+}
+
+fz_rect *
+cbz_bound_page(cbz_document *doc, cbz_page *page, fz_rect *bbox)
+{
+ fz_image *image = page->image;
+ bbox->x0 = bbox->y0 = 0;
+ bbox->x1 = image->w * DPI / image->xres;
+ bbox->y1 = image->h * DPI / image->yres;
+ return bbox;
+}
+
+void
+cbz_run_page(cbz_document *doc, cbz_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ fz_matrix local_ctm = *ctm;
+ fz_image *image = page->image;
+ float w = image->w * DPI / image->xres;
+ float h = image->h * DPI / image->yres;
+ fz_pre_scale(&local_ctm, w, h);
+ fz_fill_image(dev, image, &local_ctm, 1);
+}
+
+static int
+cbz_meta(cbz_document *doc, int key, void *ptr, int size)
+{
+ switch (key)
+ {
+ case FZ_META_FORMAT_INFO:
+ sprintf((char *)ptr, "CBZ");
+ return FZ_META_OK;
+ default:
+ return FZ_META_UNKNOWN_KEY;
+ }
+}
+
+static void
+cbz_init_document(cbz_document *doc)
+{
+ doc->super.close = (void*)cbz_close_document;
+ doc->super.count_pages = (void*)cbz_count_pages;
+ doc->super.load_page = (void*)cbz_load_page;
+ doc->super.bound_page = (void*)cbz_bound_page;
+ doc->super.run_page_contents = (void*)cbz_run_page;
+ doc->super.free_page = (void*)cbz_free_page;
+ doc->super.meta = (void*)cbz_meta;
+}
diff --git a/source/fitz/bbox-device.c b/source/fitz/bbox-device.c
new file mode 100644
index 00000000..9cb2a27e
--- /dev/null
+++ b/source/fitz/bbox-device.c
@@ -0,0 +1,231 @@
+#include "mupdf/fitz.h"
+
+#define STACK_SIZE 96
+
+typedef struct fz_bbox_data_s
+{
+ fz_rect *result;
+ int top;
+ fz_rect stack[STACK_SIZE];
+ /* mask content and tiles are ignored */
+ int ignore;
+} fz_bbox_data;
+
+static void
+fz_bbox_add_rect(fz_device *dev, const fz_rect *rect, int clip)
+{
+ fz_bbox_data *data = dev->user;
+ fz_rect r = *rect;
+
+ if (0 < data->top && data->top <= STACK_SIZE)
+ {
+ fz_intersect_rect(&r, &data->stack[data->top-1]);
+ }
+ if (!clip && data->top <= STACK_SIZE && !data->ignore)
+ {
+ fz_union_rect(data->result, &r);
+ }
+ if (clip && ++data->top <= STACK_SIZE)
+ {
+ data->stack[data->top-1] = r;
+ }
+}
+
+static void
+fz_bbox_fill_path(fz_device *dev, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_path(dev->ctx, path, NULL, ctm, &r), 0);
+}
+
+static void
+fz_bbox_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke,
+ const fz_matrix *ctm, fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_path(dev->ctx, path, stroke, ctm, &r), 0);
+}
+
+static void
+fz_bbox_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_text(dev->ctx, text, NULL, ctm, &r), 0);
+}
+
+static void
+fz_bbox_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke,
+ const fz_matrix *ctm, fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_text(dev->ctx, text, stroke, ctm, &r), 0);
+}
+
+static void
+fz_bbox_fill_shade(fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_shade(dev->ctx, shade, ctm, &r), 0);
+}
+
+static void
+fz_bbox_fill_image(fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ fz_rect r = fz_unit_rect;
+ fz_bbox_add_rect(dev, fz_transform_rect(&r, ctm), 0);
+}
+
+static void
+fz_bbox_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_rect r = fz_unit_rect;
+ fz_bbox_add_rect(dev, fz_transform_rect(&r, ctm), 0);
+}
+
+static void
+fz_bbox_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_path(dev->ctx, path, NULL, ctm, &r), 1);
+}
+
+static void
+fz_bbox_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_path(dev->ctx, path, stroke, ctm, &r), 1);
+}
+
+static void
+fz_bbox_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ fz_rect r = fz_infinite_rect;
+ if (accumulate)
+ fz_bbox_add_rect(dev, &r, accumulate != 2);
+ else
+ fz_bbox_add_rect(dev, fz_bound_text(dev->ctx, text, NULL, ctm, &r), 1);
+}
+
+static void
+fz_bbox_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_rect r;
+ fz_bbox_add_rect(dev, fz_bound_text(dev->ctx, text, stroke, ctm, &r), 1);
+}
+
+static void
+fz_bbox_clip_image_mask(fz_device *dev, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ fz_rect r = *rect;
+ fz_bbox_add_rect(dev, fz_transform_rect(&r, ctm), 1);
+}
+
+static void
+fz_bbox_pop_clip(fz_device *dev)
+{
+ fz_bbox_data *data = dev->user;
+ if (data->top > 0)
+ data->top--;
+ else
+ fz_warn(dev->ctx, "unexpected pop clip");
+}
+
+static void
+fz_bbox_begin_mask(fz_device *dev, const fz_rect *rect, int luminosity, fz_colorspace *colorspace, float *color)
+{
+ fz_bbox_data *data = dev->user;
+ fz_bbox_add_rect(dev, rect, 1);
+ data->ignore++;
+}
+
+static void
+fz_bbox_end_mask(fz_device *dev)
+{
+ fz_bbox_data *data = dev->user;
+ assert(data->ignore > 0);
+ data->ignore--;
+}
+
+static void
+fz_bbox_begin_group(fz_device *dev, const fz_rect *rect, int isolated, int knockout, int blendmode, float alpha)
+{
+ fz_bbox_add_rect(dev, rect, 1);
+}
+
+static void
+fz_bbox_end_group(fz_device *dev)
+{
+ fz_bbox_pop_clip(dev);
+}
+
+static int
+fz_bbox_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ fz_bbox_data *data = dev->user;
+ fz_rect r = *area;
+ fz_bbox_add_rect(dev, fz_transform_rect(&r, ctm), 0);
+ data->ignore++;
+ return 0;
+}
+
+static void
+fz_bbox_end_tile(fz_device *dev)
+{
+ fz_bbox_data *data = dev->user;
+ assert(data->ignore > 0);
+ data->ignore--;
+}
+
+static void
+fz_bbox_free_user(fz_device *dev)
+{
+ fz_bbox_data *data = dev->user;
+ if (data->top > 0)
+ fz_warn(dev->ctx, "items left on stack in bbox device: %d", data->top);
+ fz_free(dev->ctx, dev->user);
+}
+
+fz_device *
+fz_new_bbox_device(fz_context *ctx, fz_rect *result)
+{
+ fz_device *dev;
+
+ fz_bbox_data *user = fz_malloc_struct(ctx, fz_bbox_data);
+ user->result = result;
+ user->top = 0;
+ user->ignore = 0;
+ dev = fz_new_device(ctx, user);
+ dev->free_user = fz_bbox_free_user;
+
+ dev->fill_path = fz_bbox_fill_path;
+ dev->stroke_path = fz_bbox_stroke_path;
+ dev->clip_path = fz_bbox_clip_path;
+ dev->clip_stroke_path = fz_bbox_clip_stroke_path;
+
+ dev->fill_text = fz_bbox_fill_text;
+ dev->stroke_text = fz_bbox_stroke_text;
+ dev->clip_text = fz_bbox_clip_text;
+ dev->clip_stroke_text = fz_bbox_clip_stroke_text;
+
+ dev->fill_shade = fz_bbox_fill_shade;
+ dev->fill_image = fz_bbox_fill_image;
+ dev->fill_image_mask = fz_bbox_fill_image_mask;
+ dev->clip_image_mask = fz_bbox_clip_image_mask;
+
+ dev->pop_clip = fz_bbox_pop_clip;
+
+ dev->begin_mask = fz_bbox_begin_mask;
+ dev->end_mask = fz_bbox_end_mask;
+ dev->begin_group = fz_bbox_begin_group;
+ dev->end_group = fz_bbox_end_group;
+
+ dev->begin_tile = fz_bbox_begin_tile;
+ dev->end_tile = fz_bbox_end_tile;
+
+ *result = fz_empty_rect;
+
+ return dev;
+}
diff --git a/source/fitz/bitmap.c b/source/fitz/bitmap.c
new file mode 100644
index 00000000..6357f7d7
--- /dev/null
+++ b/source/fitz/bitmap.c
@@ -0,0 +1,123 @@
+#include "mupdf/fitz.h"
+
+fz_bitmap *
+fz_new_bitmap(fz_context *ctx, int w, int h, int n, int xres, int yres)
+{
+ fz_bitmap *bit;
+
+ bit = fz_malloc_struct(ctx, fz_bitmap);
+ bit->refs = 1;
+ bit->w = w;
+ bit->h = h;
+ bit->n = n;
+ bit->xres = xres;
+ bit->yres = yres;
+ /* Span is 32 bit aligned. We may want to make this 64 bit if we
+ * use SSE2 etc. */
+ bit->stride = ((n * w + 31) & ~31) >> 3;
+
+ bit->samples = fz_malloc_array(ctx, h, bit->stride);
+
+ return bit;
+}
+
+fz_bitmap *
+fz_keep_bitmap(fz_context *ctx, fz_bitmap *bit)
+{
+ if (bit)
+ bit->refs++;
+ return bit;
+}
+
+void
+fz_drop_bitmap(fz_context *ctx, fz_bitmap *bit)
+{
+ if (bit && --bit->refs == 0)
+ {
+ fz_free(ctx, bit->samples);
+ fz_free(ctx, bit);
+ }
+}
+
+void
+fz_clear_bitmap(fz_context *ctx, fz_bitmap *bit)
+{
+ memset(bit->samples, 0, bit->stride * bit->h);
+}
+
+/*
+ * Write bitmap to PBM file
+ */
+
+void
+fz_write_pbm(fz_context *ctx, fz_bitmap *bitmap, char *filename)
+{
+ FILE *fp;
+ unsigned char *p;
+ int h, bytestride;
+
+ fp = fopen(filename, "wb");
+ if (!fp)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ assert(bitmap->n == 1);
+
+ fprintf(fp, "P4\n%d %d\n", bitmap->w, bitmap->h);
+
+ p = bitmap->samples;
+
+ h = bitmap->h;
+ bytestride = (bitmap->w + 7) >> 3;
+ while (h--)
+ {
+ fwrite(p, 1, bytestride, fp);
+ p += bitmap->stride;
+ }
+
+ fclose(fp);
+}
+
+fz_colorspace *fz_pixmap_colorspace(fz_context *ctx, fz_pixmap *pix)
+{
+ if (!pix)
+ return NULL;
+ return pix->colorspace;
+}
+
+int fz_pixmap_components(fz_context *ctx, fz_pixmap *pix)
+{
+ if (!pix)
+ return 0;
+ return pix->n;
+}
+
+unsigned char *fz_pixmap_samples(fz_context *ctx, fz_pixmap *pix)
+{
+ if (!pix)
+ return NULL;
+ return pix->samples;
+}
+
+void fz_bitmap_details(fz_bitmap *bit, int *w, int *h, int *n, int *stride)
+{
+ if (!bit)
+ {
+ if (w)
+ *w = 0;
+ if (h)
+ *h = 0;
+ if (n)
+ *n = 0;
+ if (stride)
+ *stride = 0;
+ return;
+ }
+ if (w)
+ *w = bit->w;
+ if (h)
+ *h = bit->h;
+ if (n)
+ *n = bit->n;
+ if (stride)
+ *stride = bit->stride;
+}
diff --git a/source/fitz/buffer.c b/source/fitz/buffer.c
new file mode 100644
index 00000000..388e0461
--- /dev/null
+++ b/source/fitz/buffer.c
@@ -0,0 +1,390 @@
+#include "mupdf/fitz.h"
+
+fz_buffer *
+fz_new_buffer(fz_context *ctx, int size)
+{
+ fz_buffer *b;
+
+ size = size > 1 ? size : 16;
+
+ b = fz_malloc_struct(ctx, fz_buffer);
+ b->refs = 1;
+ fz_try(ctx)
+ {
+ b->data = fz_malloc(ctx, size);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, b);
+ fz_rethrow(ctx);
+ }
+ b->cap = size;
+ b->len = 0;
+ b->unused_bits = 0;
+
+ return b;
+}
+
+fz_buffer *
+fz_new_buffer_from_data(fz_context *ctx, unsigned char *data, int size)
+{
+ fz_buffer *b;
+
+ b = fz_malloc_struct(ctx, fz_buffer);
+ b->refs = 1;
+ b->data = data;
+ b->cap = size;
+ b->len = size;
+ b->unused_bits = 0;
+
+ return b;
+}
+
+fz_buffer *
+fz_keep_buffer(fz_context *ctx, fz_buffer *buf)
+{
+ if (buf)
+ {
+ if (buf->refs == 1 && buf->cap > buf->len+1)
+ fz_resize_buffer(ctx, buf, buf->len);
+ buf->refs ++;
+ }
+
+ return buf;
+}
+
+void
+fz_drop_buffer(fz_context *ctx, fz_buffer *buf)
+{
+ if (!buf)
+ return;
+ if (--buf->refs == 0)
+ {
+ fz_free(ctx, buf->data);
+ fz_free(ctx, buf);
+ }
+}
+
+void
+fz_resize_buffer(fz_context *ctx, fz_buffer *buf, int size)
+{
+ buf->data = fz_resize_array(ctx, buf->data, size, 1);
+ buf->cap = size;
+ if (buf->len > buf->cap)
+ buf->len = buf->cap;
+}
+
+void
+fz_grow_buffer(fz_context *ctx, fz_buffer *buf)
+{
+ int newsize = (buf->cap * 3) / 2;
+ if (newsize == 0)
+ newsize = 256;
+ fz_resize_buffer(ctx, buf, newsize);
+}
+
+static void
+fz_ensure_buffer(fz_context *ctx, fz_buffer *buf, int min)
+{
+ int newsize = buf->cap;
+ while (newsize < min)
+ {
+ newsize = (newsize * 3) / 2;
+ }
+ fz_resize_buffer(ctx, buf, newsize);
+}
+
+void
+fz_trim_buffer(fz_context *ctx, fz_buffer *buf)
+{
+ if (buf->cap > buf->len+1)
+ fz_resize_buffer(ctx, buf, buf->len);
+}
+
+int
+fz_buffer_storage(fz_context *ctx, fz_buffer *buf, unsigned char **datap)
+{
+ if (datap)
+ *datap = (buf ? buf->data : NULL);
+ return (buf ? buf->len : 0);
+}
+
+void
+fz_buffer_cat(fz_context *ctx, fz_buffer *buf, fz_buffer *extra)
+{
+ if (buf->cap - buf->len < extra->len)
+ {
+ buf->data = fz_resize_array(ctx, buf->data, buf->len + extra->len, 1);
+ buf->cap = buf->len + extra->len;
+ }
+
+ memcpy(buf->data + buf->len, extra->data, extra->len);
+ buf->len += extra->len;
+}
+
+void fz_write_buffer(fz_context *ctx, fz_buffer *buf, const void *data, int len)
+{
+ if (buf->len + len > buf->cap)
+ fz_ensure_buffer(ctx, buf, buf->len + len);
+ memcpy(buf->data + buf->len, data, len);
+ buf->len += len;
+ buf->unused_bits = 0;
+}
+
+void fz_write_buffer_byte(fz_context *ctx, fz_buffer *buf, int val)
+{
+ if (buf->len > buf->cap)
+ fz_grow_buffer(ctx, buf);
+ buf->data[buf->len++] = val;
+ buf->unused_bits = 0;
+}
+
+void fz_write_buffer_rune(fz_context *ctx, fz_buffer *buf, int c)
+{
+ char data[10];
+ int len = fz_runetochar(data, c);
+ if (buf->len + len > buf->cap)
+ fz_ensure_buffer(ctx, buf, buf->len + len);
+ memcpy(buf->data + buf->len, data, len);
+ buf->len += len;
+ buf->unused_bits = 0;
+}
+
+void fz_write_buffer_bits(fz_context *ctx, fz_buffer *buf, int val, int bits)
+{
+ int shift;
+
+ /* Throughout this code, the invariant is that we need to write the
+ * bottom 'bits' bits of 'val' into the stream. On entry we assume
+ * that val & ((1<<bits)-1) == val, but we do not rely on this after
+ * having written the first partial byte. */
+
+ if (bits == 0)
+ return;
+
+ /* buf->len always covers all the bits in the buffer, including
+ * any unused ones in the last byte, which will always be 0.
+ * buf->unused_bits = the number of unused bits in the last byte.
+ */
+
+ /* Find the amount we need to shift val up by so that it will be in
+ * the correct position to be inserted into any existing data byte. */
+ shift = (buf->unused_bits - bits);
+
+ /* Extend the buffer as required before we start; that way we never
+ * fail part way during writing. If shift < 0, then we'll need -shift
+ * more bits. */
+ if (shift < 0)
+ {
+ int extra = (7-shift)>>3; /* Round up to bytes */
+ fz_ensure_buffer(ctx, buf, buf->len + extra);
+ }
+
+ /* Write any bits that will fit into the existing byte */
+ if (buf->unused_bits)
+ {
+ buf->data[buf->len-1] |= (shift >= 0 ? (((unsigned int)val)<<shift) : (((unsigned int)val)>>-shift));
+ if (shift >= 0)
+ {
+ /* If we were shifting up, we're done. */
+ buf->unused_bits -= bits;
+ return;
+ }
+ /* The number of bits left to write is the number that didn't
+ * fit in this first byte. */
+ bits = -shift;
+ }
+
+ /* Write any whole bytes */
+ while (bits >= 8)
+ {
+ bits -= 8;
+ buf->data[buf->len++] = val>>bits;
+ }
+
+ /* Write trailing bits (with 0's in unused bits) */
+ if (bits > 0)
+ {
+ bits = 8-bits;
+ buf->data[buf->len++] = val<<bits;
+ }
+ buf->unused_bits = bits;
+}
+
+void fz_write_buffer_pad(fz_context *ctx, fz_buffer *buf)
+{
+ buf->unused_bits = 0;
+}
+
+int
+fz_buffer_printf(fz_context *ctx, fz_buffer *buffer, const char *fmt, ...)
+{
+ int ret;
+ va_list args;
+ va_start(args, fmt);
+
+ ret = fz_buffer_vprintf(ctx, buffer, fmt, args);
+
+ va_end(args);
+
+ return ret;
+}
+
+int
+fz_buffer_vprintf(fz_context *ctx, fz_buffer *buffer, const char *fmt, va_list old_args)
+{
+ int len;
+
+ do
+ {
+ int slack = buffer->cap - buffer->len;
+
+ if (slack > 0)
+ {
+ va_list args;
+#ifdef _MSC_VER /* Microsoft Visual C */
+ args = old_args;
+#else
+ va_copy(args, old_args);
+#endif
+ len = vsnprintf((char *)buffer->data + buffer->len, slack, fmt, args);
+#ifndef _MSC_VER
+ va_end(args);
+#endif
+ /* len = number of chars written, not including the terminating
+ * NULL, so len+1 > slack means "truncated". MSVC differs here
+ * and returns -1 for truncated. */
+ if (len >= 0 && len+1 <= slack)
+ break;
+ }
+ /* Grow the buffer and retry */
+ fz_grow_buffer(ctx, buffer);
+ }
+ while (1);
+
+ buffer->len += len;
+
+ return len;
+}
+
+void
+fz_buffer_cat_pdf_string(fz_context *ctx, fz_buffer *buffer, const char *text)
+{
+ int len = 2;
+ const char *s = text;
+ char *d;
+ char c;
+
+ while ((c = *s++) != 0)
+ {
+ switch (c)
+ {
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\b':
+ case '\f':
+ case '(':
+ case ')':
+ case '\\':
+ len++;
+ break;
+ }
+ len++;
+ }
+
+ while(buffer->cap - buffer->len < len)
+ fz_grow_buffer(ctx, buffer);
+
+ s = text;
+ d = (char *)buffer->data + buffer->len;
+ *d++ = '(';
+ while ((c = *s++) != 0)
+ {
+ switch (c)
+ {
+ case '\n':
+ *d++ = '\\';
+ *d++ = 'n';
+ break;
+ case '\r':
+ *d++ = '\\';
+ *d++ = 'r';
+ break;
+ case '\t':
+ *d++ = '\\';
+ *d++ = 't';
+ break;
+ case '\b':
+ *d++ = '\\';
+ *d++ = 'b';
+ break;
+ case '\f':
+ *d++ = '\\';
+ *d++ = 'f';
+ break;
+ case '(':
+ *d++ = '\\';
+ *d++ = '(';
+ break;
+ case ')':
+ *d++ = '\\';
+ *d++ = ')';
+ break;
+ case '\\':
+ *d++ = '\\';
+ *d++ = '\\';
+ break;
+ default:
+ *d++ = c;
+ }
+ }
+ *d++ = ')';
+ buffer->len += len;
+}
+
+#ifdef TEST_BUFFER_WRITE
+
+#define TEST_LEN 1024
+
+void
+fz_test_buffer_write(fz_context *ctx)
+{
+ fz_buffer *master = fz_new_buffer(ctx, TEST_LEN);
+ fz_buffer *copy = fz_new_buffer(ctx, TEST_LEN);
+ fz_stream *stm;
+ int i, j, k;
+
+ /* Make us a dummy buffer */
+ for (i = 0; i < TEST_LEN; i++)
+ {
+ master->data[i] = rand();
+ }
+ master->len = TEST_LEN;
+
+ /* Now copy that buffer several times, checking it for validity */
+ stm = fz_open_buffer(ctx, master);
+ for (i = 0; i < 256; i++)
+ {
+ memset(copy->data, i, TEST_LEN);
+ copy->len = 0;
+ j = TEST_LEN * 8;
+ do
+ {
+ k = (rand() & 31)+1;
+ if (k > j)
+ k = j;
+ fz_write_buffer_bits(ctx, copy, fz_read_bits(stm, k), k);
+ j -= k;
+ }
+ while (j);
+
+ if (memcmp(copy->data, master->data, TEST_LEN) != 0)
+ fprintf(stderr, "Copied buffer is different!\n");
+ fz_seek(stm, 0, 0);
+ }
+ fz_close(stm);
+ fz_drop_buffer(ctx, master);
+ fz_drop_buffer(ctx, copy);
+}
+#endif
diff --git a/source/fitz/colorspace.c b/source/fitz/colorspace.c
new file mode 100644
index 00000000..07da5a95
--- /dev/null
+++ b/source/fitz/colorspace.c
@@ -0,0 +1,1277 @@
+#include "mupdf/fitz.h"
+
+#define SLOWCMYK
+
+void
+fz_free_colorspace_imp(fz_context *ctx, fz_storable *cs_)
+{
+ fz_colorspace *cs = (fz_colorspace *)cs_;
+
+ if (cs->free_data && cs->data)
+ cs->free_data(ctx, cs);
+ fz_free(ctx, cs);
+}
+
+fz_colorspace *
+fz_new_colorspace(fz_context *ctx, char *name, int n)
+{
+ fz_colorspace *cs = fz_malloc(ctx, sizeof(fz_colorspace));
+ FZ_INIT_STORABLE(cs, 1, fz_free_colorspace_imp);
+ cs->size = sizeof(fz_colorspace);
+ fz_strlcpy(cs->name, name, sizeof cs->name);
+ cs->n = n;
+ cs->to_rgb = NULL;
+ cs->from_rgb = NULL;
+ cs->free_data = NULL;
+ cs->data = NULL;
+ return cs;
+}
+
+fz_colorspace *
+fz_keep_colorspace(fz_context *ctx, fz_colorspace *cs)
+{
+ return (fz_colorspace *)fz_keep_storable(ctx, &cs->storable);
+}
+
+void
+fz_drop_colorspace(fz_context *ctx, fz_colorspace *cs)
+{
+ fz_drop_storable(ctx, &cs->storable);
+}
+
+/* Device colorspace definitions */
+
+static void gray_to_rgb(fz_context *ctx, fz_colorspace *cs, float *gray, float *rgb)
+{
+ rgb[0] = gray[0];
+ rgb[1] = gray[0];
+ rgb[2] = gray[0];
+}
+
+static void rgb_to_gray(fz_context *ctx, fz_colorspace *cs, float *rgb, float *gray)
+{
+ float r = rgb[0];
+ float g = rgb[1];
+ float b = rgb[2];
+ gray[0] = r * 0.3f + g * 0.59f + b * 0.11f;
+}
+
+static void rgb_to_rgb(fz_context *ctx, fz_colorspace *cs, float *rgb, float *xyz)
+{
+ xyz[0] = rgb[0];
+ xyz[1] = rgb[1];
+ xyz[2] = rgb[2];
+}
+
+static void bgr_to_rgb(fz_context *ctx, fz_colorspace *cs, float *bgr, float *rgb)
+{
+ rgb[0] = bgr[2];
+ rgb[1] = bgr[1];
+ rgb[2] = bgr[0];
+}
+
+static void rgb_to_bgr(fz_context *ctx, fz_colorspace *cs, float *rgb, float *bgr)
+{
+ bgr[0] = rgb[2];
+ bgr[1] = rgb[1];
+ bgr[2] = rgb[0];
+}
+
+static void cmyk_to_rgb(fz_context *ctx, fz_colorspace *cs, float *cmyk, float *rgb)
+{
+#ifdef SLOWCMYK /* from poppler */
+ float c = cmyk[0], m = cmyk[1], y = cmyk[2], k = cmyk[3];
+ float r, g, b, x;
+ float cm = c * m;
+ float c1m = m - cm;
+ float cm1 = c - cm;
+ float c1m1 = 1 - m - cm1;
+ float c1m1y = c1m1 * y;
+ float c1m1y1 = c1m1 - c1m1y;
+ float c1my = c1m * y;
+ float c1my1 = c1m - c1my;
+ float cm1y = cm1 * y;
+ float cm1y1 = cm1 - cm1y;
+ float cmy = cm * y;
+ float cmy1 = cm - cmy;
+
+ /* this is a matrix multiplication, unrolled for performance */
+ x = c1m1y1 * k; /* 0 0 0 1 */
+ r = g = b = c1m1y1 - x; /* 0 0 0 0 */
+ r += 0.1373 * x;
+ g += 0.1216 * x;
+ b += 0.1255 * x;
+
+ x = c1m1y * k; /* 0 0 1 1 */
+ r += 0.1098 * x;
+ g += 0.1020 * x;
+ x = c1m1y - x; /* 0 0 1 0 */
+ r += x;
+ g += 0.9490 * x;
+
+ x = c1my1 * k; /* 0 1 0 1 */
+ r += 0.1412 * x;
+ x = c1my1 - x; /* 0 1 0 0 */
+ r += 0.9255 * x;
+ b += 0.5490 * x;
+
+ x = c1my * k; /* 0 1 1 1 */
+ r += 0.1333 * x;
+ x = c1my - x; /* 0 1 1 0 */
+ r += 0.9294 * x;
+ g += 0.1098 * x;
+ b += 0.1412 * x;
+
+ x = cm1y1 * k; /* 1 0 0 1 */
+ g += 0.0588 * x;
+ b += 0.1412 * x;
+ x = cm1y1 - x; /* 1 0 0 0 */
+ g += 0.6784 * x;
+ b += 0.9373 * x;
+
+ x = cm1y * k; /* 1 0 1 1 */
+ g += 0.0745 * x;
+ x = cm1y - x; /* 1 0 1 0 */
+ g += 0.6510 * x;
+ b += 0.3137 * x;
+
+ x = cmy1 * k; /* 1 1 0 1 */
+ b += 0.0078 * x;
+ x = cmy1 - x; /* 1 1 0 0 */
+ r += 0.1804 * x;
+ g += 0.1922 * x;
+ b += 0.5725 * x;
+
+ x = cmy * (1-k); /* 1 1 1 0 */
+ r += 0.2118 * x;
+ g += 0.2119 * x;
+ b += 0.2235 * x;
+ rgb[0] = fz_clamp(r, 0, 1);
+ rgb[1] = fz_clamp(g, 0, 1);
+ rgb[2] = fz_clamp(b, 0, 1);
+#else
+ rgb[0] = 1 - fz_min(1, cmyk[0] + cmyk[3]);
+ rgb[1] = 1 - fz_min(1, cmyk[1] + cmyk[3]);
+ rgb[2] = 1 - fz_min(1, cmyk[2] + cmyk[3]);
+#endif
+}
+
+static void rgb_to_cmyk(fz_context *ctx, fz_colorspace *cs, float *rgb, float *cmyk)
+{
+ float c, m, y, k;
+ c = 1 - rgb[0];
+ m = 1 - rgb[1];
+ y = 1 - rgb[2];
+ k = fz_min(c, fz_min(m, y));
+ cmyk[0] = c - k;
+ cmyk[1] = m - k;
+ cmyk[2] = y - k;
+ cmyk[3] = k;
+}
+
+static fz_colorspace k_default_gray = { {-1, fz_free_colorspace_imp}, 0, "DeviceGray", 1, gray_to_rgb, rgb_to_gray };
+static fz_colorspace k_default_rgb = { {-1, fz_free_colorspace_imp}, 0, "DeviceRGB", 3, rgb_to_rgb, rgb_to_rgb };
+static fz_colorspace k_default_bgr = { {-1, fz_free_colorspace_imp}, 0, "DeviceRGB", 3, bgr_to_rgb, rgb_to_bgr };
+static fz_colorspace k_default_cmyk = { {-1, fz_free_colorspace_imp}, 0, "DeviceCMYK", 4, cmyk_to_rgb, rgb_to_cmyk };
+
+static fz_colorspace *fz_default_gray = &k_default_gray;
+static fz_colorspace *fz_default_rgb = &k_default_rgb;
+static fz_colorspace *fz_default_bgr = &k_default_bgr;
+static fz_colorspace *fz_default_cmyk = &k_default_cmyk;
+
+struct fz_colorspace_context_s
+{
+ int ctx_refs;
+ fz_colorspace *gray, *rgb, *bgr, *cmyk;
+};
+
+void fz_new_colorspace_context(fz_context *ctx)
+{
+ ctx->colorspace = fz_malloc_struct(ctx, fz_colorspace_context);
+ ctx->colorspace->ctx_refs = 1;
+ ctx->colorspace->gray = fz_default_gray;
+ ctx->colorspace->rgb = fz_default_rgb;
+ ctx->colorspace->bgr = fz_default_bgr;
+ ctx->colorspace->cmyk = fz_default_cmyk;
+}
+
+fz_colorspace_context *
+fz_keep_colorspace_context(fz_context *ctx)
+{
+ if (!ctx || !ctx->colorspace)
+ return NULL;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ ctx->colorspace->ctx_refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return ctx->colorspace;
+}
+
+void fz_drop_colorspace_context(fz_context *ctx)
+{
+ int drop;
+ if (!ctx || !ctx->colorspace)
+ return;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = --ctx->colorspace->ctx_refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop == 0)
+ fz_free(ctx, ctx->colorspace);
+}
+
+fz_colorspace *
+fz_device_gray(fz_context *ctx)
+{
+ return ctx->colorspace->gray;
+}
+
+fz_colorspace *
+fz_device_rgb(fz_context *ctx)
+{
+ return ctx->colorspace->rgb;
+}
+
+fz_colorspace *
+fz_device_bgr(fz_context *ctx)
+{
+ return ctx->colorspace->bgr;
+}
+
+fz_colorspace *
+fz_device_cmyk(fz_context *ctx)
+{
+ return ctx->colorspace->cmyk;
+}
+
+fz_colorspace *
+fz_lookup_device_colorspace(fz_context *ctx, char *name)
+{
+ if (!strcmp(name, "DeviceGray"))
+ return fz_device_gray(ctx);
+ if (!strcmp(name, "DeviceRGB"))
+ return fz_device_rgb(ctx);
+ if (!strcmp(name, "DeviceBGR"))
+ return fz_device_bgr(ctx);
+ if (!strcmp(name, "DeviceCMYK"))
+ return fz_device_cmyk(ctx);
+ assert(!"unknown device colorspace");
+ return NULL;
+}
+
+void
+fz_set_device_gray(fz_context *ctx, fz_colorspace *cs)
+{
+ fz_drop_colorspace(ctx, ctx->colorspace->gray);
+ ctx->colorspace->gray = fz_keep_colorspace(ctx, cs);
+}
+
+void
+fz_set_device_rgb(fz_context *ctx, fz_colorspace *cs)
+{
+ fz_drop_colorspace(ctx, ctx->colorspace->rgb);
+ ctx->colorspace->rgb = fz_keep_colorspace(ctx, cs);
+}
+
+void
+fz_set_device_bgr(fz_context *ctx, fz_colorspace *cs)
+{
+ fz_drop_colorspace(ctx, ctx->colorspace->bgr);
+ ctx->colorspace->bgr = fz_keep_colorspace(ctx, cs);
+}
+
+void
+fz_set_device_cmyk(fz_context *ctx, fz_colorspace *cs)
+{
+ fz_drop_colorspace(ctx, ctx->colorspace->cmyk);
+ ctx->colorspace->cmyk = fz_keep_colorspace(ctx, cs);
+}
+
+int
+fz_colorspace_is_indexed(fz_colorspace *cs)
+{
+ return (cs && !strcmp(cs->name, "Indexed"));
+}
+
+/* Fast pixmap color conversions */
+
+static void fast_gray_to_rgb(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ d[0] = s[0];
+ d[1] = s[0];
+ d[2] = s[0];
+ d[3] = s[1];
+ s += 2;
+ d += 4;
+ }
+}
+
+static void fast_gray_to_cmyk(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ d[0] = 0;
+ d[1] = 0;
+ d[2] = 0;
+ d[3] = s[0];
+ d[4] = s[1];
+ s += 2;
+ d += 5;
+ }
+}
+
+static void fast_rgb_to_gray(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ d[0] = ((s[0]+1) * 77 + (s[1]+1) * 150 + (s[2]+1) * 28) >> 8;
+ d[1] = s[3];
+ s += 4;
+ d += 2;
+ }
+}
+
+static void fast_bgr_to_gray(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ d[0] = ((s[0]+1) * 28 + (s[1]+1) * 150 + (s[2]+1) * 77) >> 8;
+ d[1] = s[3];
+ s += 4;
+ d += 2;
+ }
+}
+
+static void fast_rgb_to_cmyk(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ unsigned char c = 255 - s[0];
+ unsigned char m = 255 - s[1];
+ unsigned char y = 255 - s[2];
+ unsigned char k = (unsigned char)fz_mini(c, fz_mini(m, y));
+ d[0] = c - k;
+ d[1] = m - k;
+ d[2] = y - k;
+ d[3] = k;
+ d[4] = s[3];
+ s += 4;
+ d += 5;
+ }
+}
+
+static void fast_bgr_to_cmyk(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ unsigned char c = 255 - s[2];
+ unsigned char m = 255 - s[1];
+ unsigned char y = 255 - s[0];
+ unsigned char k = (unsigned char)fz_mini(c, fz_mini(m, y));
+ d[0] = c - k;
+ d[1] = m - k;
+ d[2] = y - k;
+ d[3] = k;
+ d[4] = s[3];
+ s += 4;
+ d += 5;
+ }
+}
+
+static void fast_cmyk_to_gray(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ unsigned char c = fz_mul255(s[0], 77);
+ unsigned char m = fz_mul255(s[1], 150);
+ unsigned char y = fz_mul255(s[2], 28);
+ d[0] = 255 - (unsigned char)fz_mini(c + m + y + s[3], 255);
+ d[1] = s[4];
+ s += 5;
+ d += 2;
+ }
+}
+
+#ifdef ARCH_ARM
+static void
+fast_cmyk_to_rgb_ARM(unsigned char *dst, unsigned char *src, int n)
+__attribute__((naked));
+
+static void
+fast_cmyk_to_rgb_ARM(unsigned char *dst, unsigned char *src, int n)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = n \n"
+ "mov r12, #0 @ r12= CMYK = 0 \n"
+ "b 2f @ enter loop \n"
+ "1: @ White or Black \n"
+ "@ Cunning trick: On entry r11 = 0 if black, r11 = FF if white \n"
+ "ldrb r7, [r1],#1 @ r8 = s[4] \n"
+ "strb r11,[r0],#1 @ d[0] = r \n"
+ "strb r11,[r0],#1 @ d[1] = g \n"
+ "strb r11,[r0],#1 @ d[2] = b \n"
+ "strb r7, [r0],#1 @ d[3] = s[4] \n"
+ "subs r2, r2, #1 @ r2 = n-- \n"
+ "beq 9f \n"
+ "2: @ Main loop starts here \n"
+ "ldrb r3, [r1], #4 @ r3 = c \n"
+ "ldrb r6, [r1, #-1] @ r6 = k \n"
+ "ldrb r5, [r1, #-2] @ r5 = y \n"
+ "ldrb r4, [r1, #-3] @ r4 = m \n"
+ "eors r11,r6, #0xFF @ if (k == 255) \n"
+ "beq 1b @ goto black \n"
+ "orr r7, r3, r4, LSL #8 \n"
+ "orr r14,r5, r6, LSL #8 \n"
+ "orrs r7, r7, r14,LSL #16 @ r7 = cmyk \n"
+ "beq 1b @ if (cmyk == 0) white \n"
+ "@ At this point, we have to decode a new pixel \n"
+ "@ r0 = dst r1 = src r2 = n r7 = cmyk \n"
+ "3: @ unmatched \n"
+ "stmfd r13!,{r0-r1,r7} @ stash regs for space \n"
+ "add r3, r3, r3, LSR #7 @ r3 = c += c>>7 \n"
+ "add r4, r4, r4, LSR #7 @ r4 = m += m>>7 \n"
+ "add r5, r5, r5, LSR #7 @ r5 = y += y>>7 \n"
+ "add r6, r6, r6, LSR #7 @ r6 = k += k>>7 \n"
+ "mov r5, r5, LSR #1 @ sacrifice 1 bit of Y \n"
+ "mul r8, r3, r4 @ r8 = cm = c * m \n"
+ "rsb r9, r8, r4, LSL #8 @ r9 = c1m = (m<<8) - cm \n"
+ "rsb r3, r8, r3, LSL #8 @ r3 = cm1 = (c<<8) - cm \n"
+ "rsb r4, r4, #0x100 @ r4 = 256-m \n"
+ "rsb r4, r3, r4, LSL #8 @ r4 = c1m1 =((256-m)<<8)-cm1 \n"
+ "mul r7, r4, r5 @ r7 = c1m1y = c1m1 * y \n"
+ "rsb r4, r7, r4, LSL #7 @ r4 = c1m1y1 = (c1m1<<7)-c1m1y \n"
+ "mul r10,r9, r5 @ r10= c1my = c1m * y \n"
+ "rsb r9, r10,r9, LSL #7 @ r9 = c1my1 = (c1m<<7) - c1my \n"
+ "mul r11,r3, r5 @ r11= cm1y = cm1 * y \n"
+ "rsb r3, r11,r3, LSL #7 @ r3 = cm1y1 = (cm1<<7) - cm1y \n"
+ "mul r5, r8, r5 @ r5 = cmy = cm * y \n"
+ "rsb r8, r5, r8, LSL #7 @ r8 = cmy1 = (cm<<7) - cmy \n"
+ "@ Register recap: \n"
+ "@ r3 = cm1y1 \n"
+ "@ r4 = c1m1y1 \n"
+ "@ r5 = cmy \n"
+ "@ r6 = k \n"
+ "@ r7 = c1m1y \n"
+ "@ r8 = cmy1 \n"
+ "@ r9 = c1my1 \n"
+ "@ r10= c1my \n"
+ "@ r11= cm1y \n"
+ "@ The actual matrix multiplication \n"
+ "mul r14,r4, r6 @ r14= x1 = c1m1y1 * k \n"
+ "rsb r4, r14,r4, LSL #8 @ r4 = x0 = (c1m1y1<<8) - x1 \n"
+ "add r4, r4, r14,LSR #8-5 @ r4 = b = x0 + 32*(x1>>8) \n"
+ "sub r1, r4, r14,LSR #8 @ r1 = g = x0 + 31*(x1>>8) \n"
+ "add r0, r1, r14,LSR #8-2 @ r0 = r = x0 + 35*(x1>>8) \n"
+ " \n"
+ "mul r14,r7, r6 @ r14= x1 = c1m1y * k \n"
+ "rsb r7, r14,r7, LSL #8 @ r7 = x0 = (c1m1y<<8) - x1 \n"
+ "add r0, r0, r7 @ r0 = r += x0 \n"
+ "add r1, r1, r7 @ r1 = g += (x0>>8 * 256) \n"
+ "sub r1, r1, r7, LSR #8-3 @ 248 \n"
+ "sub r1, r1, r7, LSR #8-2 @ 244 \n"
+ "sub r1, r1, r7, LSR #8 @ 243 \n"
+ "sub r7, r14,r14,LSR #3 @ r7 = 28*(x1>>5) \n"
+ "add r0, r0, r7, LSR #8-5 @ r0 = r += 28 * x1 \n"
+ "sub r7, r7, r14,LSR #4 @ r7 = 26*(x1>>5) \n"
+ "add r1, r1, r7, LSR #8-5 @ r1 = g += 26 * x1 \n"
+ " \n"
+ "mul r14,r9, r6 @ r14= x1 = c1my1 * k \n"
+ "sub r9, r9, r14,LSR #8 @ r9 = x0>>8 = c1my1 - (x1>>8) \n"
+ "add r0, r0, r14,LSR #8-5 @ r0 = r += (x1>>8)*32 \n"
+ "add r0, r0, r14,LSR #8-2 @ r0 = r += (x1>>8)*36 \n"
+ "mov r14,#237 @ r14= 237 \n"
+ "mla r0,r14,r9,r0 @ r14= r += x0*237 \n"
+ "mov r14,#141 @ r14= 141 \n"
+ "mla r4,r14,r9,r4 @ r14= b += x0*141 \n"
+ " \n"
+ "mul r14,r10,r6 @ r14= x1 = c1my * k \n"
+ "sub r10,r10,r14,LSR #8 @ r10= x0>>8 = c1my - (x1>>8) \n"
+ "add r0, r0, r14,LSR #8-5 @ r0 = r += 32 * x1 \n"
+ "add r0, r0, r14,LSR #8-1 @ r0 = r += 34 * x1 \n"
+ "mov r14,#238 @ r14= 238 \n"
+ "mla r0,r14,r10,r0 @ r0 = r += 238 * x0 \n"
+ "mov r14,#28 @ r14= 28 \n"
+ "mla r1,r14,r10,r1 @ r1 = g += 28 * x0 \n"
+ "mov r14,#36 @ r14= 36 \n"
+ "mla r4,r14,r10,r4 @ r4 = b += 36 * x0 \n"
+ " \n"
+ "mul r14,r3, r6 @ r14= x1 = cm1y1 * k \n"
+ "sub r3, r3, r14,LSR #8 @ r3 = x1>>8 = cm1y1 - (x1>>8) \n"
+ "add r1, r1, r14,LSR #8-4 @ r1 = g += 16*x1 \n"
+ "sub r1, r1, r14,LSR #8 @ 15*x1 \n"
+ "add r4, r4, r14,LSR #8-5 @ r4 = b += 32*x1 \n"
+ "add r4, r4, r14,LSR #8-2 @ 36*x1 \n"
+ "mov r14,#174 @ r14= 174 \n"
+ "mla r1, r14,r3, r1 @ r1 = g += 174 * x0 \n"
+ "mov r14,#240 @ r14= 240 \n"
+ "mla r4, r14,r3, r4 @ r4 = b += 240 * x0 \n"
+ " \n"
+ "mul r14,r11,r6 @ r14= x1 = cm1y * k \n"
+ "sub r11,r11,r14,LSR #8 @ r11= x0>>8 = cm1y - (x1>>8) \n"
+ "add r1, r1, r14,LSR #8-4 @ r1 = g += x1 * 16 \n"
+ "add r1, r1, r14,LSR #8 @ x1 * 17 \n"
+ "add r1, r1, r14,LSR #8-1 @ x1 * 19 \n"
+ "mov r14,#167 @ r14 = 167 \n"
+ "mla r1, r14,r11,r1 @ r1 = g += 167 * x0 \n"
+ "mov r14,#80 @ r14 = 80 \n"
+ "mla r4, r14,r11,r4 @ r4 = b += 80 * x0 \n"
+ " \n"
+ "mul r14,r8, r6 @ r14= x1 = cmy1 * k \n"
+ "sub r8, r8, r14,LSR #8 @ r8 = x0>>8 = cmy1 - (x1>>8) \n"
+ "add r4, r4, r14,LSR #8-1 @ r4 = b += x1 * 2 \n"
+ "mov r14,#46 @ r14=46 \n"
+ "mla r0, r14,r8, r0 @ r0 = r += 46 * x0 \n"
+ "mov r14,#49 @ r14=49 \n"
+ "mla r1, r14,r8, r1 @ r1 = g += 49 * x0 \n"
+ "mov r14,#147 @ r14=147 \n"
+ "mla r4, r14,r8, r4 @ r4 = b += 147 * x0 \n"
+ " \n"
+ "rsb r6, r6, #256 @ r6 = k = 256-k \n"
+ "mul r14,r5, r6 @ r14= x0 = cmy * (256-k) \n"
+ "mov r11,#54 @ r11= 54 \n"
+ "mov r14,r14,LSR #8 @ r14= (x0>>8) \n"
+ "mov r8,#57 @ r8 = 57 \n"
+ "mla r0,r14,r11,r0 @ r0 = r += 54*x0 \n"
+ "mla r1,r14,r11,r1 @ r1 = g += 54*x0 \n"
+ "mla r4,r14,r8, r4 @ r4 = b += 57*x0 \n"
+ " \n"
+ "sub r8, r0, r0, LSR #8 @ r8 = r -= (r>>8) \n"
+ "sub r9, r1, r1, LSR #8 @ r9 = g -= (r>>8) \n"
+ "sub r10,r4, r4, LSR #8 @ r10= b -= (r>>8) \n"
+ "ldmfd r13!,{r0-r1,r12} \n"
+ "mov r8, r8, LSR #23 @ r8 = r>>23 \n"
+ "mov r9, r9, LSR #23 @ r9 = g>>23 \n"
+ "mov r10,r10,LSR #23 @ r10= b>>23 \n"
+ "ldrb r14,[r1],#1 @ r8 = s[4] \n"
+ "strb r8, [r0],#1 @ d[0] = r \n"
+ "strb r9, [r0],#1 @ d[1] = g \n"
+ "strb r10,[r0],#1 @ d[2] = b \n"
+ "strb r14,[r0],#1 @ d[3] = s[4] \n"
+ "subs r2, r2, #1 @ r2 = n-- \n"
+ "beq 9f \n"
+ "@ At this point, we've just decoded a pixel \n"
+ "@ r0 = dst r1 = src r2 = n r8 = r r9 = g r10= b r12= CMYK \n"
+ "4: \n"
+ "ldrb r3, [r1], #4 @ r3 = c \n"
+ "ldrb r6, [r1, #-1] @ r6 = k \n"
+ "ldrb r5, [r1, #-2] @ r5 = y \n"
+ "ldrb r4, [r1, #-3] @ r4 = m \n"
+ "eors r11,r6, #0xFF @ if (k == 255) \n"
+ "beq 1b @ goto black \n"
+ "orr r7, r3, r4, LSL #8 \n"
+ "orr r14,r5, r6, LSL #8 \n"
+ "orrs r7, r7, r14,LSL #16 @ r7 = cmyk \n"
+ "beq 1b @ if (cmyk == 0) white \n"
+ "cmp r7, r12 @ if (cmyk != CMYK) \n"
+ "bne 3b @ not the same, loop \n"
+ "@ If we get here, we just matched a pixel we have just decoded \n"
+ "ldrb r3, [r1],#1 @ r8 = s[4] \n"
+ "strb r8, [r0],#1 @ d[0] = r \n"
+ "strb r9, [r0],#1 @ d[1] = g \n"
+ "strb r10,[r0],#1 @ d[2] = b \n"
+ "strb r3, [r0],#1 @ d[3] = s[4] \n"
+ "subs r2, r2, #1 @ r2 = n-- \n"
+ "bne 4b \n"
+ "9: \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+#endif
+
+static void fast_cmyk_to_rgb(fz_context *ctx, fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+#ifdef ARCH_ARM
+ fast_cmyk_to_rgb_ARM(d, s, n);
+#else
+ unsigned int C,M,Y,K,r,g,b;
+
+ C = 0;
+ M = 0;
+ Y = 0;
+ K = 0;
+ r = 255;
+ g = 255;
+ b = 255;
+
+ while (n--)
+ {
+#ifdef SLOWCMYK
+ unsigned int c = s[0];
+ unsigned int m = s[1];
+ unsigned int y = s[2];
+ unsigned int k = s[3];
+ unsigned int cm, c1m, cm1, c1m1, c1m1y, c1m1y1, c1my, c1my1, cm1y, cm1y1, cmy, cmy1;
+ unsigned int x0, x1;
+
+ if (c == C && m == M && y == Y && k == K)
+ {
+ /* Nothing to do */
+ }
+ else if (k == 0 && c == 0 && m == 0 && y == 0)
+ {
+ r = g = b = 255;
+ }
+ else if (k == 255)
+ {
+ r = g = b = 0;
+ }
+ else
+ {
+ c += c>>7;
+ m += m>>7;
+ y += y>>7;
+ k += k>>7;
+ y >>= 1; /* Ditch 1 bit of Y to avoid overflow */
+ cm = c * m;
+ c1m = (m<<8) - cm;
+ cm1 = (c<<8) - cm;
+ c1m1 = ((256 - m)<<8) - cm1;
+ c1m1y = c1m1 * y;
+ c1m1y1 = (c1m1<<7) - c1m1y;
+ c1my = c1m * y;
+ c1my1 = (c1m<<7) - c1my;
+ cm1y = cm1 * y;
+ cm1y1 = (cm1<<7) - cm1y;
+ cmy = cm * y;
+ cmy1 = (cm<<7) - cmy;
+
+ /* this is a matrix multiplication, unrolled for performance */
+ x1 = c1m1y1 * k; /* 0 0 0 1 */
+ x0 = (c1m1y1<<8) - x1; /* 0 0 0 0 */
+ x1 = x1>>8; /* From 23 fractional bits to 15 */
+ r = g = b = x0;
+ r += 35 * x1; /* 0.1373 */
+ g += 31 * x1; /* 0.1216 */
+ b += 32 * x1; /* 0.1255 */
+
+ x1 = c1m1y * k; /* 0 0 1 1 */
+ x0 = (c1m1y<<8) - x1; /* 0 0 1 0 */
+ x1 >>= 8; /* From 23 fractional bits to 15 */
+ r += 28 * x1; /* 0.1098 */
+ g += 26 * x1; /* 0.1020 */
+ r += x0;
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ g += 243 * x0; /* 0.9490 */
+
+ x1 = c1my1 * k; /* 0 1 0 1 */
+ x0 = (c1my1<<8) - x1; /* 0 1 0 0 */
+ x1 >>= 8; /* From 23 fractional bits to 15 */
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ r += 36 * x1; /* 0.1412 */
+ r += 237 * x0; /* 0.9255 */
+ b += 141 * x0; /* 0.5490 */
+
+ x1 = c1my * k; /* 0 1 1 1 */
+ x0 = (c1my<<8) - x1; /* 0 1 1 0 */
+ x1 >>= 8; /* From 23 fractional bits to 15 */
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ r += 34 * x1; /* 0.1333 */
+ r += 238 * x0; /* 0.9294 */
+ g += 28 * x0; /* 0.1098 */
+ b += 36 * x0; /* 0.1412 */
+
+ x1 = cm1y1 * k; /* 1 0 0 1 */
+ x0 = (cm1y1<<8) - x1; /* 1 0 0 0 */
+ x1 >>= 8; /* From 23 fractional bits to 15 */
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ g += 15 * x1; /* 0.0588 */
+ b += 36 * x1; /* 0.1412 */
+ g += 174 * x0; /* 0.6784 */
+ b += 240 * x0; /* 0.9373 */
+
+ x1 = cm1y * k; /* 1 0 1 1 */
+ x0 = (cm1y<<8) - x1; /* 1 0 1 0 */
+ x1 >>= 8; /* From 23 fractional bits to 15 */
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ g += 19 * x1; /* 0.0745 */
+ g += 167 * x0; /* 0.6510 */
+ b += 80 * x0; /* 0.3137 */
+
+ x1 = cmy1 * k; /* 1 1 0 1 */
+ x0 = (cmy1<<8) - x1; /* 1 1 0 0 */
+ x1 >>= 8; /* From 23 fractional bits to 15 */
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ b += 2 * x1; /* 0.0078 */
+ r += 46 * x0; /* 0.1804 */
+ g += 49 * x0; /* 0.1922 */
+ b += 147 * x0; /* 0.5725 */
+
+ x0 = cmy * (256-k); /* 1 1 1 0 */
+ x0 >>= 8; /* From 23 fractional bits to 15 */
+ r += 54 * x0; /* 0.2118 */
+ g += 54 * x0; /* 0.2119 */
+ b += 57 * x0; /* 0.2235 */
+
+ r -= (r>>8);
+ g -= (g>>8);
+ b -= (b>>8);
+ r = r>>23;
+ g = g>>23;
+ b = b>>23;
+ C = c;
+ M = m;
+ Y = y;
+ K = k;
+ }
+ d[0] = r;
+ d[1] = g;
+ d[2] = b;
+#else
+ d[0] = 255 - (unsigned char)fz_mini(s[0] + s[3], 255);
+ d[1] = 255 - (unsigned char)fz_mini(s[1] + s[3], 255);
+ d[2] = 255 - (unsigned char)fz_mini(s[2] + s[3], 255);
+#endif
+ d[3] = s[4];
+ s += 5;
+ d += 4;
+ }
+#endif
+}
+
+static void fast_cmyk_to_bgr(fz_context *ctx, fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+#ifdef SLOWCMYK
+ float cmyk[4], rgb[3];
+ cmyk[0] = s[0] / 255.0f;
+ cmyk[1] = s[1] / 255.0f;
+ cmyk[2] = s[2] / 255.0f;
+ cmyk[3] = s[3] / 255.0f;
+ cmyk_to_rgb(ctx, NULL, cmyk, rgb);
+ d[0] = rgb[2] * 255;
+ d[1] = rgb[1] * 255;
+ d[2] = rgb[0] * 255;
+#else
+ d[0] = 255 - (unsigned char)fz_mini(s[2] + s[3], 255);
+ d[1] = 255 - (unsigned char)fz_mini(s[1] + s[3], 255);
+ d[2] = 255 - (unsigned char)fz_mini(s[0] + s[3], 255);
+#endif
+ d[3] = s[4];
+ s += 5;
+ d += 4;
+ }
+}
+
+static void fast_rgb_to_bgr(fz_pixmap *dst, fz_pixmap *src)
+{
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+ int n = src->w * src->h;
+ while (n--)
+ {
+ d[0] = s[2];
+ d[1] = s[1];
+ d[2] = s[0];
+ d[3] = s[3];
+ s += 4;
+ d += 4;
+ }
+}
+
+static void
+fz_std_conv_pixmap(fz_context *ctx, fz_pixmap *dst, fz_pixmap *src)
+{
+ float srcv[FZ_MAX_COLORS];
+ float dstv[FZ_MAX_COLORS];
+ int srcn, dstn;
+ int k, i;
+ unsigned int xy;
+
+ fz_colorspace *ss = src->colorspace;
+ fz_colorspace *ds = dst->colorspace;
+
+ unsigned char *s = src->samples;
+ unsigned char *d = dst->samples;
+
+ assert(src->w == dst->w && src->h == dst->h);
+ assert(src->n == ss->n + 1);
+ assert(dst->n == ds->n + 1);
+
+ srcn = ss->n;
+ dstn = ds->n;
+
+ xy = (unsigned int)(src->w * src->h);
+
+ /* Special case for Lab colorspace (scaling of components to float) */
+ if (!strcmp(ss->name, "Lab") && srcn == 3)
+ {
+ fz_color_converter cc;
+
+ fz_lookup_color_converter(&cc, ctx, ds, ss);
+ for (; xy > 0; xy--)
+ {
+ srcv[0] = *s++ / 255.0f * 100;
+ srcv[1] = *s++ - 128;
+ srcv[2] = *s++ - 128;
+
+ cc.convert(&cc, dstv, srcv);
+
+ for (k = 0; k < dstn; k++)
+ *d++ = dstv[k] * 255;
+
+ *d++ = *s++;
+ }
+ }
+
+ /* Brute-force for small images */
+ else if (xy < 256)
+ {
+ fz_color_converter cc;
+
+ fz_lookup_color_converter(&cc, ctx, ds, ss);
+ for (; xy > 0; xy--)
+ {
+ for (k = 0; k < srcn; k++)
+ srcv[k] = *s++ / 255.0f;
+
+ cc.convert(&cc, dstv, srcv);
+
+ for (k = 0; k < dstn; k++)
+ *d++ = dstv[k] * 255;
+
+ *d++ = *s++;
+ }
+ }
+
+ /* 1-d lookup table for separation and similar colorspaces */
+ else if (srcn == 1)
+ {
+ unsigned char lookup[FZ_MAX_COLORS * 256];
+ fz_color_converter cc;
+
+ fz_lookup_color_converter(&cc, ctx, ds, ss);
+ for (i = 0; i < 256; i++)
+ {
+ srcv[0] = i / 255.0f;
+ cc.convert(&cc, dstv, srcv);
+ for (k = 0; k < dstn; k++)
+ lookup[i * dstn + k] = dstv[k] * 255;
+ }
+
+ for (; xy > 0; xy--)
+ {
+ i = *s++;
+ for (k = 0; k < dstn; k++)
+ *d++ = lookup[i * dstn + k];
+ *d++ = *s++;
+ }
+ }
+
+ /* Memoize colors using a hash table for the general case */
+ else
+ {
+ fz_hash_table *lookup;
+ unsigned char *color;
+ unsigned char dummy = s[0] ^ 255;
+ unsigned char *sold = &dummy;
+ fz_color_converter cc;
+
+ fz_lookup_color_converter(&cc, ctx, ds, ss);
+ lookup = fz_new_hash_table(ctx, 509, srcn, -1);
+
+ for (; xy > 0; xy--)
+ {
+ if (*s == *sold && memcmp(sold,s,srcn) == 0)
+ {
+ sold = s;
+ memcpy(d, d-dstn-1, dstn);
+ d += dstn;
+ s += srcn;
+ *d++ = *s++;
+ }
+ else
+ {
+ sold = s;
+ color = fz_hash_find(ctx, lookup, s);
+ if (color)
+ {
+ memcpy(d, color, dstn);
+ s += srcn;
+ d += dstn;
+ *d++ = *s++;
+ }
+ else
+ {
+ for (k = 0; k < srcn; k++)
+ srcv[k] = *s++ / 255.0f;
+ cc.convert(&cc, dstv, srcv);
+ for (k = 0; k < dstn; k++)
+ *d++ = dstv[k] * 255;
+
+ fz_hash_insert(ctx, lookup, s - srcn, d - dstn);
+
+ *d++ = *s++;
+ }
+ }
+ }
+
+ fz_free_hash(ctx, lookup);
+ }
+}
+
+void
+fz_convert_pixmap(fz_context *ctx, fz_pixmap *dp, fz_pixmap *sp)
+{
+ fz_colorspace *ss = sp->colorspace;
+ fz_colorspace *ds = dp->colorspace;
+
+ assert(ss && ds);
+
+ dp->interpolate = sp->interpolate;
+
+ if (ss == fz_default_gray)
+ {
+ if (ds == fz_default_rgb) fast_gray_to_rgb(dp, sp);
+ else if (ds == fz_default_bgr) fast_gray_to_rgb(dp, sp); /* bgr == rgb here */
+ else if (ds == fz_default_cmyk) fast_gray_to_cmyk(dp, sp);
+ else fz_std_conv_pixmap(ctx, dp, sp);
+ }
+
+ else if (ss == fz_default_rgb)
+ {
+ if (ds == fz_default_gray) fast_rgb_to_gray(dp, sp);
+ else if (ds == fz_default_bgr) fast_rgb_to_bgr(dp, sp);
+ else if (ds == fz_default_cmyk) fast_rgb_to_cmyk(dp, sp);
+ else fz_std_conv_pixmap(ctx, dp, sp);
+ }
+
+ else if (ss == fz_default_bgr)
+ {
+ if (ds == fz_default_gray) fast_bgr_to_gray(dp, sp);
+ else if (ds == fz_default_rgb) fast_rgb_to_bgr(dp, sp); /* bgr = rgb here */
+ else if (ds == fz_default_cmyk) fast_bgr_to_cmyk(sp, dp);
+ else fz_std_conv_pixmap(ctx, dp, sp);
+ }
+
+ else if (ss == fz_default_cmyk)
+ {
+ if (ds == fz_default_gray) fast_cmyk_to_gray(dp, sp);
+ else if (ds == fz_default_bgr) fast_cmyk_to_bgr(ctx, dp, sp);
+ else if (ds == fz_default_rgb) fast_cmyk_to_rgb(ctx, dp, sp);
+ else fz_std_conv_pixmap(ctx, dp, sp);
+ }
+
+ else fz_std_conv_pixmap(ctx, dp, sp);
+}
+
+/* Convert a single color */
+
+static void
+std_conv_color(fz_color_converter *cc, float *dstv, float *srcv)
+{
+ float rgb[3];
+ int i;
+ fz_colorspace *srcs = cc->ss;
+ fz_colorspace *dsts = cc->ds;
+ fz_context *ctx = cc->ctx;
+
+ if (srcs != dsts)
+ {
+ assert(srcs->to_rgb && dsts->from_rgb);
+ srcs->to_rgb(ctx, srcs, srcv, rgb);
+ dsts->from_rgb(ctx, dsts, rgb, dstv);
+ for (i = 0; i < dsts->n; i++)
+ dstv[i] = fz_clamp(dstv[i], 0, 1);
+ }
+ else
+ {
+ for (i = 0; i < srcs->n; i++)
+ dstv[i] = srcv[i];
+ }
+}
+
+static void
+g2rgb(fz_color_converter *cc, float *dv, float *sv)
+{
+ dv[0] = sv[0];
+ dv[1] = sv[0];
+ dv[2] = sv[0];
+}
+
+static void
+g2cmyk(fz_color_converter *cc, float *dv, float *sv)
+{
+ dv[0] = 0;
+ dv[1] = 0;
+ dv[2] = 0;
+ dv[3] = sv[0];
+}
+
+static void
+rgb2g(fz_color_converter *cc, float *dv, float *sv)
+{
+ dv[0] = sv[0] * 0.3f + sv[1] * 0.59f + sv[2] * 0.11f;
+}
+
+static void
+rgb2bgr(fz_color_converter *cc, float *dv, float *sv)
+{
+ dv[0] = sv[2];
+ dv[1] = sv[1];
+ dv[2] = sv[0];
+}
+
+static void
+rgb2cmyk(fz_color_converter *cc, float *dv, float *sv)
+{
+ float c = 1 - sv[0];
+ float m = 1 - sv[1];
+ float y = 1 - sv[2];
+ float k = fz_min(c, fz_min(m, y));
+ dv[0] = c - k;
+ dv[1] = m - k;
+ dv[2] = y - k;
+ dv[3] = k;
+}
+
+static void
+bgr2g(fz_color_converter *cc, float *dv, float *sv)
+{
+ dv[0] = sv[0] * 0.11f + sv[1] * 0.59f + sv[2] * 0.3f;
+}
+
+static void
+bgr2cmyk(fz_color_converter *cc, float *dv, float *sv)
+{
+ float c = 1 - sv[2];
+ float m = 1 - sv[1];
+ float y = 1 - sv[0];
+ float k = fz_min(c, fz_min(m, y));
+ dv[0] = c - k;
+ dv[1] = m - k;
+ dv[2] = y - k;
+ dv[3] = k;
+}
+
+static void
+cmyk2g(fz_color_converter *cc, float *dv, float *sv)
+{
+ float c = sv[0] * 0.3f;
+ float m = sv[1] * 0.59f;
+ float y = sv[2] * 0.11f;
+ dv[0] = 1 - fz_min(c + m + y + sv[3], 1);
+}
+
+static void
+cmyk2rgb(fz_color_converter *cc, float *dv, float *sv)
+{
+#ifdef SLOWCMYK
+ cmyk_to_rgb(cc->ctx, NULL, sv, dv);
+#else
+ dv[0] = 1 - fz_min(sv[0] + sv[3], 1);
+ dv[1] = 1 - fz_min(sv[1] + sv[3], 1);
+ dv[2] = 1 - fz_min(sv[2] + sv[3], 1);
+#endif
+}
+
+static void
+cmyk2bgr(fz_color_converter *cc, float *dv, float *sv)
+{
+#ifdef SLOWCMYK
+ float rgb[3];
+ cmyk_to_rgb(cc->ctx, NULL, sv, rgb);
+ dv[0] = rgb[2];
+ dv[1] = rgb[1];
+ dv[2] = rgb[0];
+#else
+ dv[0] = 1 - fz_min(sv[2] + sv[3], 1);
+ dv[1] = 1 - fz_min(sv[1] + sv[3], 1);
+ dv[2] = 1 - fz_min(sv[0] + sv[3], 1);
+#endif
+}
+
+void fz_lookup_color_converter(fz_color_converter *cc, fz_context *ctx, fz_colorspace *ds, fz_colorspace *ss)
+{
+ cc->ctx = ctx;
+ cc->ds = ds;
+ cc->ss = ss;
+ if (ss == fz_default_gray)
+ {
+ if ((ds == fz_default_rgb) || (ds == fz_default_bgr))
+ cc->convert = g2rgb;
+ else if (ds == fz_default_cmyk)
+ cc->convert = g2cmyk;
+ else
+ cc->convert = std_conv_color;
+ }
+
+ else if (ss == fz_default_rgb)
+ {
+ if (ds == fz_default_gray)
+ cc->convert = rgb2g;
+ else if (ds == fz_default_bgr)
+ cc->convert = rgb2bgr;
+ else if (ds == fz_default_cmyk)
+ cc->convert = rgb2cmyk;
+ else
+ cc->convert = std_conv_color;
+ }
+
+ else if (ss == fz_default_bgr)
+ {
+ if (ds == fz_default_gray)
+ cc->convert = bgr2g;
+ else if (ds == fz_default_rgb)
+ cc->convert = rgb2bgr;
+ else if (ds == fz_default_cmyk)
+ cc->convert = bgr2cmyk;
+ else
+ cc->convert = std_conv_color;
+ }
+
+ else if (ss == fz_default_cmyk)
+ {
+ if (ds == fz_default_gray)
+ cc->convert = cmyk2g;
+ else if (ds == fz_default_rgb)
+ cc->convert = cmyk2rgb;
+ else if (ds == fz_default_bgr)
+ cc->convert = cmyk2bgr;
+ else
+ cc->convert = std_conv_color;
+ }
+
+ else
+ cc->convert = std_conv_color;
+}
+
+void
+fz_convert_color(fz_context *ctx, fz_colorspace *ds, float *dv, fz_colorspace *ss, float *sv)
+{
+ fz_color_converter cc;
+
+ fz_lookup_color_converter(&cc, ctx, ds, ss);
+ cc.convert(&cc, dv, sv);
+}
+
+/* Indexed */
+
+struct indexed
+{
+ fz_colorspace *base;
+ int high;
+ unsigned char *lookup;
+};
+
+static void
+indexed_to_rgb(fz_context *ctx, fz_colorspace *cs, float *color, float *rgb)
+{
+ struct indexed *idx = cs->data;
+ float alt[FZ_MAX_COLORS];
+ int i, k;
+ i = color[0] * 255;
+ i = fz_clampi(i, 0, idx->high);
+ for (k = 0; k < idx->base->n; k++)
+ alt[k] = idx->lookup[i * idx->base->n + k] / 255.0f;
+ idx->base->to_rgb(ctx, idx->base, alt, rgb);
+}
+
+static void
+free_indexed(fz_context *ctx, fz_colorspace *cs)
+{
+ struct indexed *idx = cs->data;
+ if (idx->base)
+ fz_drop_colorspace(ctx, idx->base);
+ fz_free(ctx, idx->lookup);
+ fz_free(ctx, idx);
+}
+
+fz_colorspace *
+fz_new_indexed_colorspace(fz_context *ctx, fz_colorspace *base, int high, unsigned char *lookup)
+{
+ fz_colorspace *cs;
+ struct indexed *idx;
+
+ idx = fz_malloc_struct(ctx, struct indexed);
+ idx->lookup = lookup;
+ idx->base = base;
+ idx->high = high;
+
+ fz_try(ctx)
+ {
+ cs = fz_new_colorspace(ctx, "Indexed", 1);
+ cs->to_rgb = indexed_to_rgb;
+ cs->free_data = free_indexed;
+ cs->data = idx;
+ cs->size += sizeof(*idx) + (base->n * (idx->high + 1)) + base->size;
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, idx);
+ fz_rethrow_message(ctx, "failed to create indexed colorspace");
+ }
+ return cs;
+}
+
+fz_pixmap *
+fz_expand_indexed_pixmap(fz_context *ctx, fz_pixmap *src)
+{
+ struct indexed *idx;
+ fz_pixmap *dst;
+ unsigned char *s, *d;
+ int y, x, k, n, high;
+ unsigned char *lookup;
+ fz_irect bbox;
+
+ assert(src->colorspace->to_rgb == indexed_to_rgb);
+ assert(src->n == 2);
+
+ idx = src->colorspace->data;
+ high = idx->high;
+ lookup = idx->lookup;
+ n = idx->base->n;
+
+ dst = fz_new_pixmap_with_bbox(ctx, idx->base, fz_pixmap_bbox(ctx, src, &bbox));
+ s = src->samples;
+ d = dst->samples;
+
+ for (y = 0; y < src->h; y++)
+ {
+ for (x = 0; x < src->w; x++)
+ {
+ int v = *s++;
+ int a = *s++;
+ v = fz_mini(v, high);
+ for (k = 0; k < n; k++)
+ *d++ = fz_mul255(lookup[v * n + k], a);
+ *d++ = a;
+ }
+ }
+
+ dst->interpolate = src->interpolate;
+
+ return dst;
+}
diff --git a/source/fitz/compressed-buffer.c b/source/fitz/compressed-buffer.c
new file mode 100644
index 00000000..acdf2747
--- /dev/null
+++ b/source/fitz/compressed-buffer.c
@@ -0,0 +1,75 @@
+#include "mupdf/fitz.h"
+
+/* This code needs to be kept out of stm_buffer.c to avoid it being
+ * pulled into cmapdump.c */
+
+void
+fz_free_compressed_buffer(fz_context *ctx, fz_compressed_buffer *buf)
+{
+ if (!buf)
+ return;
+
+ fz_drop_buffer(ctx, buf->buffer);
+ fz_free(ctx, buf);
+}
+
+fz_stream *
+fz_open_image_decomp_stream(fz_context *ctx, fz_compressed_buffer *buffer, int *l2factor)
+{
+ fz_stream *chain = fz_open_buffer(ctx, buffer->buffer);
+ fz_compression_params *params = &buffer->params;
+
+ switch (params->type)
+ {
+ case FZ_IMAGE_FAX:
+ *l2factor = 0;
+ return fz_open_faxd(chain,
+ params->u.fax.k,
+ params->u.fax.end_of_line,
+ params->u.fax.encoded_byte_align,
+ params->u.fax.columns,
+ params->u.fax.rows,
+ params->u.fax.end_of_block,
+ params->u.fax.black_is_1);
+ case FZ_IMAGE_JPEG:
+ if (*l2factor > 3)
+ *l2factor = 3;
+ return fz_open_resized_dctd(chain, params->u.jpeg.color_transform, *l2factor);
+ case FZ_IMAGE_RLD:
+ *l2factor = 0;
+ return fz_open_rld(chain);
+ case FZ_IMAGE_FLATE:
+ *l2factor = 0;
+ chain = fz_open_flated(chain);
+ if (params->u.flate.predictor > 1)
+ chain = fz_open_predict(chain, params->u.flate.predictor, params->u.flate.columns, params->u.flate.colors, params->u.flate.bpc);
+ return chain;
+ case FZ_IMAGE_LZW:
+ *l2factor = 0;
+ chain = fz_open_lzwd(chain, params->u.lzw.early_change);
+ if (params->u.lzw.predictor > 1)
+ chain = fz_open_predict(chain, params->u.lzw.predictor, params->u.lzw.columns, params->u.lzw.colors, params->u.lzw.bpc);
+ return chain;
+ default:
+ *l2factor = 0;
+ break;
+ }
+
+ return chain;
+}
+
+fz_stream *
+fz_open_compressed_buffer(fz_context *ctx, fz_compressed_buffer *buffer)
+{
+ int l2factor = 0;
+
+ return fz_open_image_decomp_stream(ctx, buffer, &l2factor);
+}
+
+unsigned int
+fz_compressed_buffer_size(fz_compressed_buffer *buffer)
+{
+ if (!buffer || !buffer->buffer)
+ return 0;
+ return (unsigned int)buffer->buffer->cap;
+}
diff --git a/source/fitz/context.c b/source/fitz/context.c
new file mode 100644
index 00000000..c65377a8
--- /dev/null
+++ b/source/fitz/context.c
@@ -0,0 +1,210 @@
+#include "mupdf/fitz.h"
+
+struct fz_id_context_s
+{
+ int refs;
+ int id;
+};
+
+static void
+fz_drop_id_context(fz_context *ctx)
+{
+ int refs;
+ fz_id_context *id = ctx->id;
+
+ if (id == NULL)
+ return;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ refs = --id->refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (refs == 0)
+ fz_free(ctx, id);
+}
+
+static void
+fz_new_id_context(fz_context *ctx)
+{
+ ctx->id = fz_malloc_struct(ctx, fz_id_context);
+ ctx->id->refs = 1;
+ ctx->id->id = 0;
+}
+
+static fz_id_context *
+fz_keep_id_context(fz_context *ctx)
+{
+ fz_id_context *id = ctx->id;
+
+ if (id == NULL)
+ return NULL;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ ++id->refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return id;
+}
+
+void
+fz_free_context(fz_context *ctx)
+{
+ if (!ctx)
+ return;
+
+ /* Other finalisation calls go here (in reverse order) */
+ fz_drop_glyph_cache_context(ctx);
+ fz_drop_store_context(ctx);
+ fz_free_aa_context(ctx);
+ fz_drop_colorspace_context(ctx);
+ fz_drop_font_context(ctx);
+ fz_drop_id_context(ctx);
+
+ if (ctx->warn)
+ {
+ fz_flush_warnings(ctx);
+ fz_free(ctx, ctx->warn);
+ }
+
+ if (ctx->error)
+ {
+ assert(ctx->error->top == -1);
+ fz_free(ctx, ctx->error);
+ }
+
+ /* Free the context itself */
+ ctx->alloc->free(ctx->alloc->user, ctx);
+}
+
+/* Allocate new context structure, and initialise allocator, and sections
+ * that aren't shared between contexts.
+ */
+static fz_context *
+new_context_phase1(fz_alloc_context *alloc, fz_locks_context *locks)
+{
+ fz_context *ctx;
+
+ ctx = alloc->malloc(alloc->user, sizeof(fz_context));
+ if (!ctx)
+ return NULL;
+ memset(ctx, 0, sizeof *ctx);
+ ctx->alloc = alloc;
+ ctx->locks = locks;
+
+ ctx->glyph_cache = NULL;
+
+ ctx->error = fz_malloc_no_throw(ctx, sizeof(fz_error_context));
+ if (!ctx->error)
+ goto cleanup;
+ ctx->error->top = -1;
+ ctx->error->errcode = FZ_ERROR_NONE;
+ ctx->error->message[0] = 0;
+
+ ctx->warn = fz_malloc_no_throw(ctx, sizeof(fz_warn_context));
+ if (!ctx->warn)
+ goto cleanup;
+ ctx->warn->message[0] = 0;
+ ctx->warn->count = 0;
+
+ /* New initialisation calls for context entries go here */
+ fz_try(ctx)
+ {
+ fz_new_aa_context(ctx);
+ }
+ fz_catch(ctx)
+ {
+ goto cleanup;
+ }
+
+ return ctx;
+
+cleanup:
+ fprintf(stderr, "cannot create context (phase 1)\n");
+ fz_free_context(ctx);
+ return NULL;
+}
+
+fz_context *
+fz_new_context(fz_alloc_context *alloc, fz_locks_context *locks, unsigned int max_store)
+{
+ fz_context *ctx;
+
+ if (!alloc)
+ alloc = &fz_alloc_default;
+
+ if (!locks)
+ locks = &fz_locks_default;
+
+ ctx = new_context_phase1(alloc, locks);
+ if (!ctx)
+ return NULL;
+
+ /* Now initialise sections that are shared */
+ fz_try(ctx)
+ {
+ fz_new_store_context(ctx, max_store);
+ fz_new_glyph_cache_context(ctx);
+ fz_new_colorspace_context(ctx);
+ fz_new_font_context(ctx);
+ fz_new_id_context(ctx);
+ }
+ fz_catch(ctx)
+ {
+ fprintf(stderr, "cannot create context (phase 2)\n");
+ fz_free_context(ctx);
+ return NULL;
+ }
+ return ctx;
+}
+
+fz_context *
+fz_clone_context(fz_context *ctx)
+{
+ /* We cannot safely clone the context without having locking/
+ * unlocking functions. */
+ if (ctx == NULL || ctx->locks == &fz_locks_default)
+ return NULL;
+ return fz_clone_context_internal(ctx);
+}
+
+fz_context *
+fz_clone_context_internal(fz_context *ctx)
+{
+ fz_context *new_ctx;
+
+ if (ctx == NULL || ctx->alloc == NULL)
+ return NULL;
+
+ new_ctx = new_context_phase1(ctx->alloc, ctx->locks);
+ if (!new_ctx)
+ return NULL;
+
+ /* Inherit AA defaults from old context. */
+ fz_copy_aa_context(new_ctx, ctx);
+
+ /* Keep thread lock checking happy by copying pointers first and locking under new context */
+ new_ctx->store = ctx->store;
+ new_ctx->store = fz_keep_store_context(new_ctx);
+ new_ctx->glyph_cache = ctx->glyph_cache;
+ new_ctx->glyph_cache = fz_keep_glyph_cache(new_ctx);
+ new_ctx->colorspace = ctx->colorspace;
+ new_ctx->colorspace = fz_keep_colorspace_context(new_ctx);
+ new_ctx->font = ctx->font;
+ new_ctx->font = fz_keep_font_context(new_ctx);
+ new_ctx->id = ctx->id;
+ new_ctx->id = fz_keep_id_context(new_ctx);
+
+ return new_ctx;
+}
+
+int
+fz_gen_id(fz_context *ctx)
+{
+ int id;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ /* We'll never wrap around in normal use, but if we *do*, then avoid
+ * 0. */
+ do
+ {
+ id = ++ctx->id->id;
+ }
+ while (id == 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return id;
+}
diff --git a/source/fitz/crypt-aes.c b/source/fitz/crypt-aes.c
new file mode 100644
index 00000000..6ce14903
--- /dev/null
+++ b/source/fitz/crypt-aes.c
@@ -0,0 +1,569 @@
+/*
+ * FIPS-197 compliant AES implementation
+ *
+ * Copyright (C) 2006-2007 Christophe Devine
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code _must_ retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form may or may not reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of XySSL nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * The AES block cipher was designed by Vincent Rijmen and Joan Daemen.
+ *
+ * http://csrc.nist.gov/encryption/aes/rijndael/Rijndael.pdf
+ * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
+ */
+
+#include "mupdf/fitz.h"
+
+#define aes_context fz_aes
+
+/* AES block cipher implementation from XYSSL */
+
+/*
+ * 32-bit integer manipulation macros (little endian)
+ */
+#ifndef GET_ULONG_LE
+#define GET_ULONG_LE(n,b,i) \
+{ \
+ (n) = ( (unsigned long) (b)[(i)] ) \
+ | ( (unsigned long) (b)[(i) + 1] << 8 ) \
+ | ( (unsigned long) (b)[(i) + 2] << 16 ) \
+ | ( (unsigned long) (b)[(i) + 3] << 24 ); \
+}
+#endif
+
+#ifndef PUT_ULONG_LE
+#define PUT_ULONG_LE(n,b,i) \
+{ \
+ (b)[(i) ] = (unsigned char) ( (n) ); \
+ (b)[(i) + 1] = (unsigned char) ( (n) >> 8 ); \
+ (b)[(i) + 2] = (unsigned char) ( (n) >> 16 ); \
+ (b)[(i) + 3] = (unsigned char) ( (n) >> 24 ); \
+}
+#endif
+
+/*
+ * Forward S-box & tables
+ */
+static unsigned char FSb[256];
+static unsigned long FT0[256];
+static unsigned long FT1[256];
+static unsigned long FT2[256];
+static unsigned long FT3[256];
+
+/*
+ * Reverse S-box & tables
+ */
+static unsigned char RSb[256];
+static unsigned long RT0[256];
+static unsigned long RT1[256];
+static unsigned long RT2[256];
+static unsigned long RT3[256];
+
+/*
+ * Round constants
+ */
+static unsigned long RCON[10];
+
+/*
+ * Tables generation code
+ */
+#define ROTL8(x) ( ( x << 8 ) & 0xFFFFFFFF ) | ( x >> 24 )
+#define XTIME(x) ( ( x << 1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) )
+#define MUL(x,y) ( ( x && y ) ? pow[(log[x]+log[y]) % 255] : 0 )
+
+static int aes_init_done = 0;
+
+static void aes_gen_tables( void )
+{
+ int i, x, y, z;
+ int pow[256];
+ int log[256];
+
+ /*
+ * compute pow and log tables over GF(2^8)
+ */
+ for( i = 0, x = 1; i < 256; i++ )
+ {
+ pow[i] = x;
+ log[x] = i;
+ x = ( x ^ XTIME( x ) ) & 0xFF;
+ }
+
+ /*
+ * calculate the round constants
+ */
+ for( i = 0, x = 1; i < 10; i++ )
+ {
+ RCON[i] = (unsigned long) x;
+ x = XTIME( x ) & 0xFF;
+ }
+
+ /*
+ * generate the forward and reverse S-boxes
+ */
+ FSb[0x00] = 0x63;
+ RSb[0x63] = 0x00;
+
+ for( i = 1; i < 256; i++ )
+ {
+ x = pow[255 - log[i]];
+
+ y = x; y = ( (y << 1) | (y >> 7) ) & 0xFF;
+ x ^= y; y = ( (y << 1) | (y >> 7) ) & 0xFF;
+ x ^= y; y = ( (y << 1) | (y >> 7) ) & 0xFF;
+ x ^= y; y = ( (y << 1) | (y >> 7) ) & 0xFF;
+ x ^= y ^ 0x63;
+
+ FSb[i] = (unsigned char) x;
+ RSb[x] = (unsigned char) i;
+ }
+
+ /*
+ * generate the forward and reverse tables
+ */
+ for( i = 0; i < 256; i++ )
+ {
+ x = FSb[i];
+ y = XTIME( x ) & 0xFF;
+ z = ( y ^ x ) & 0xFF;
+
+ FT0[i] = ( (unsigned long) y ) ^
+ ( (unsigned long) x << 8 ) ^
+ ( (unsigned long) x << 16 ) ^
+ ( (unsigned long) z << 24 );
+
+ FT1[i] = ROTL8( FT0[i] );
+ FT2[i] = ROTL8( FT1[i] );
+ FT3[i] = ROTL8( FT2[i] );
+
+ x = RSb[i];
+
+ RT0[i] = ( (unsigned long) MUL( 0x0E, x ) ) ^
+ ( (unsigned long) MUL( 0x09, x ) << 8 ) ^
+ ( (unsigned long) MUL( 0x0D, x ) << 16 ) ^
+ ( (unsigned long) MUL( 0x0B, x ) << 24 );
+
+ RT1[i] = ROTL8( RT0[i] );
+ RT2[i] = ROTL8( RT1[i] );
+ RT3[i] = ROTL8( RT2[i] );
+ }
+}
+
+/*
+ * AES key schedule (encryption)
+ */
+int aes_setkey_enc( aes_context *ctx, const unsigned char *key, int keysize )
+{
+ int i;
+ unsigned long *RK;
+
+#if !defined(XYSSL_AES_ROM_TABLES)
+ if( aes_init_done == 0 )
+ {
+ aes_gen_tables();
+ aes_init_done = 1;
+ }
+#endif
+
+ switch( keysize )
+ {
+ case 128: ctx->nr = 10; break;
+ case 192: ctx->nr = 12; break;
+ case 256: ctx->nr = 14; break;
+ default : return 1;
+ }
+
+#if defined(PADLOCK_ALIGN16)
+ ctx->rk = RK = PADLOCK_ALIGN16( ctx->buf );
+#else
+ ctx->rk = RK = ctx->buf;
+#endif
+
+ for( i = 0; i < (keysize >> 5); i++ )
+ {
+ GET_ULONG_LE( RK[i], key, i << 2 );
+ }
+
+ switch( ctx->nr )
+ {
+ case 10:
+
+ for( i = 0; i < 10; i++, RK += 4 )
+ {
+ RK[4] = RK[0] ^ RCON[i] ^
+ ( FSb[ ( RK[3] >> 8 ) & 0xFF ] ) ^
+ ( FSb[ ( RK[3] >> 16 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( RK[3] >> 24 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( RK[3] ) & 0xFF ] << 24 );
+
+ RK[5] = RK[1] ^ RK[4];
+ RK[6] = RK[2] ^ RK[5];
+ RK[7] = RK[3] ^ RK[6];
+ }
+ break;
+
+ case 12:
+
+ for( i = 0; i < 8; i++, RK += 6 )
+ {
+ RK[6] = RK[0] ^ RCON[i] ^
+ ( FSb[ ( RK[5] >> 8 ) & 0xFF ] ) ^
+ ( FSb[ ( RK[5] >> 16 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( RK[5] >> 24 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( RK[5] ) & 0xFF ] << 24 );
+
+ RK[7] = RK[1] ^ RK[6];
+ RK[8] = RK[2] ^ RK[7];
+ RK[9] = RK[3] ^ RK[8];
+ RK[10] = RK[4] ^ RK[9];
+ RK[11] = RK[5] ^ RK[10];
+ }
+ break;
+
+ case 14:
+
+ for( i = 0; i < 7; i++, RK += 8 )
+ {
+ RK[8] = RK[0] ^ RCON[i] ^
+ ( FSb[ ( RK[7] >> 8 ) & 0xFF ] ) ^
+ ( FSb[ ( RK[7] >> 16 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( RK[7] >> 24 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( RK[7] ) & 0xFF ] << 24 );
+
+ RK[9] = RK[1] ^ RK[8];
+ RK[10] = RK[2] ^ RK[9];
+ RK[11] = RK[3] ^ RK[10];
+
+ RK[12] = RK[4] ^
+ ( FSb[ ( RK[11] ) & 0xFF ] ) ^
+ ( FSb[ ( RK[11] >> 8 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( RK[11] >> 16 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( RK[11] >> 24 ) & 0xFF ] << 24 );
+
+ RK[13] = RK[5] ^ RK[12];
+ RK[14] = RK[6] ^ RK[13];
+ RK[15] = RK[7] ^ RK[14];
+ }
+ break;
+
+ default:
+
+ break;
+ }
+ return 0;
+}
+
+/*
+ * AES key schedule (decryption)
+ */
+int aes_setkey_dec(aes_context *ctx, const unsigned char *key, int keysize)
+{
+ int i, j;
+ aes_context cty;
+ unsigned long *RK;
+ unsigned long *SK;
+
+ switch( keysize )
+ {
+ case 128: ctx->nr = 10; break;
+ case 192: ctx->nr = 12; break;
+ case 256: ctx->nr = 14; break;
+ default: return 1;
+ }
+
+#if defined(PADLOCK_ALIGN16)
+ ctx->rk = RK = PADLOCK_ALIGN16( ctx->buf );
+#else
+ ctx->rk = RK = ctx->buf;
+#endif
+
+ i = aes_setkey_enc( &cty, key, keysize );
+ if (i)
+ return i;
+ SK = cty.rk + cty.nr * 4;
+
+ *RK++ = *SK++;
+ *RK++ = *SK++;
+ *RK++ = *SK++;
+ *RK++ = *SK++;
+
+ for( i = ctx->nr - 1, SK -= 8; i > 0; i--, SK -= 8 )
+ {
+ for( j = 0; j < 4; j++, SK++ )
+ {
+ *RK++ = RT0[ FSb[ ( *SK ) & 0xFF ] ] ^
+ RT1[ FSb[ ( *SK >> 8 ) & 0xFF ] ] ^
+ RT2[ FSb[ ( *SK >> 16 ) & 0xFF ] ] ^
+ RT3[ FSb[ ( *SK >> 24 ) & 0xFF ] ];
+ }
+ }
+
+ *RK++ = *SK++;
+ *RK++ = *SK++;
+ *RK++ = *SK++;
+ *RK++ = *SK++;
+
+ memset( &cty, 0, sizeof( aes_context ) );
+ return 0;
+}
+
+#define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \
+{ \
+ X0 = *RK++ ^ FT0[ ( Y0 ) & 0xFF ] ^ \
+ FT1[ ( Y1 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y2 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y3 >> 24 ) & 0xFF ]; \
+ \
+ X1 = *RK++ ^ FT0[ ( Y1 ) & 0xFF ] ^ \
+ FT1[ ( Y2 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y3 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y0 >> 24 ) & 0xFF ]; \
+ \
+ X2 = *RK++ ^ FT0[ ( Y2 ) & 0xFF ] ^ \
+ FT1[ ( Y3 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y0 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y1 >> 24 ) & 0xFF ]; \
+ \
+ X3 = *RK++ ^ FT0[ ( Y3 ) & 0xFF ] ^ \
+ FT1[ ( Y0 >> 8 ) & 0xFF ] ^ \
+ FT2[ ( Y1 >> 16 ) & 0xFF ] ^ \
+ FT3[ ( Y2 >> 24 ) & 0xFF ]; \
+}
+
+#define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \
+{ \
+ X0 = *RK++ ^ RT0[ ( Y0 ) & 0xFF ] ^ \
+ RT1[ ( Y3 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y2 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y1 >> 24 ) & 0xFF ]; \
+ \
+ X1 = *RK++ ^ RT0[ ( Y1 ) & 0xFF ] ^ \
+ RT1[ ( Y0 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y3 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y2 >> 24 ) & 0xFF ]; \
+ \
+ X2 = *RK++ ^ RT0[ ( Y2 ) & 0xFF ] ^ \
+ RT1[ ( Y1 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y0 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y3 >> 24 ) & 0xFF ]; \
+ \
+ X3 = *RK++ ^ RT0[ ( Y3 ) & 0xFF ] ^ \
+ RT1[ ( Y2 >> 8 ) & 0xFF ] ^ \
+ RT2[ ( Y1 >> 16 ) & 0xFF ] ^ \
+ RT3[ ( Y0 >> 24 ) & 0xFF ]; \
+}
+
+/*
+ * AES-ECB block encryption/decryption
+ */
+void aes_crypt_ecb( aes_context *ctx,
+ int mode,
+ const unsigned char input[16],
+ unsigned char output[16] )
+{
+ int i;
+ unsigned long *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3;
+
+#if defined(XYSSL_PADLOCK_C) && defined(XYSSL_HAVE_X86)
+ if( padlock_supports( PADLOCK_ACE ) )
+ {
+ if( padlock_xcryptecb( ctx, mode, input, output ) == 0 )
+ return;
+ }
+#endif
+
+ RK = ctx->rk;
+
+ GET_ULONG_LE( X0, input, 0 ); X0 ^= *RK++;
+ GET_ULONG_LE( X1, input, 4 ); X1 ^= *RK++;
+ GET_ULONG_LE( X2, input, 8 ); X2 ^= *RK++;
+ GET_ULONG_LE( X3, input, 12 ); X3 ^= *RK++;
+
+ if( mode == AES_DECRYPT )
+ {
+ for( i = (ctx->nr >> 1) - 1; i > 0; i-- )
+ {
+ AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+ AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );
+ }
+
+ AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+
+ X0 = *RK++ ^ ( RSb[ ( Y0 ) & 0xFF ] ) ^
+ ( RSb[ ( Y3 >> 8 ) & 0xFF ] << 8 ) ^
+ ( RSb[ ( Y2 >> 16 ) & 0xFF ] << 16 ) ^
+ ( RSb[ ( Y1 >> 24 ) & 0xFF ] << 24 );
+
+ X1 = *RK++ ^ ( RSb[ ( Y1 ) & 0xFF ] ) ^
+ ( RSb[ ( Y0 >>8 ) & 0xFF ] << 8 ) ^
+ ( RSb[ ( Y3 >> 16 ) & 0xFF ] << 16 ) ^
+ ( RSb[ ( Y2 >> 24 ) & 0xFF ] << 24 );
+
+ X2 = *RK++ ^ ( RSb[ ( Y2 ) & 0xFF ] ) ^
+ ( RSb[ ( Y1 >> 8 ) & 0xFF ] << 8 ) ^
+ ( RSb[ ( Y0 >> 16 ) & 0xFF ] << 16 ) ^
+ ( RSb[ ( Y3 >> 24 ) & 0xFF ] << 24 );
+
+ X3 = *RK++ ^ ( RSb[ ( Y3 ) & 0xFF ] ) ^
+ ( RSb[ ( Y2 >> 8 ) & 0xFF ] << 8 ) ^
+ ( RSb[ ( Y1 >> 16 ) & 0xFF ] << 16 ) ^
+ ( RSb[ ( Y0 >> 24 ) & 0xFF ] << 24 );
+ }
+ else /* AES_ENCRYPT */
+ {
+ for( i = (ctx->nr >> 1) - 1; i > 0; i-- )
+ {
+ AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+ AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 );
+ }
+
+ AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 );
+
+ X0 = *RK++ ^ ( FSb[ ( Y0 ) & 0xFF ] ) ^
+ ( FSb[ ( Y1 >> 8 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( Y2 >> 16 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( Y3 >> 24 ) & 0xFF ] << 24 );
+
+ X1 = *RK++ ^ ( FSb[ ( Y1 ) & 0xFF ] ) ^
+ ( FSb[ ( Y2 >> 8 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( Y3 >> 16 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( Y0 >> 24 ) & 0xFF ] << 24 );
+
+ X2 = *RK++ ^ ( FSb[ ( Y2 ) & 0xFF ] ) ^
+ ( FSb[ ( Y3 >> 8 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( Y0 >> 16 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( Y1 >> 24 ) & 0xFF ] << 24 );
+
+ X3 = *RK++ ^ ( FSb[ ( Y3 ) & 0xFF ] ) ^
+ ( FSb[ ( Y0 >> 8 ) & 0xFF ] << 8 ) ^
+ ( FSb[ ( Y1 >> 16 ) & 0xFF ] << 16 ) ^
+ ( FSb[ ( Y2 >> 24 ) & 0xFF ] << 24 );
+ }
+
+ PUT_ULONG_LE( X0, output, 0 );
+ PUT_ULONG_LE( X1, output, 4 );
+ PUT_ULONG_LE( X2, output, 8 );
+ PUT_ULONG_LE( X3, output, 12 );
+}
+
+/*
+ * AES-CBC buffer encryption/decryption
+ */
+void aes_crypt_cbc( aes_context *ctx,
+ int mode,
+ int length,
+ unsigned char iv[16],
+ const unsigned char *input,
+ unsigned char *output )
+{
+ int i;
+ unsigned char temp[16];
+
+#if defined(XYSSL_PADLOCK_C) && defined(XYSSL_HAVE_X86)
+ if( padlock_supports( PADLOCK_ACE ) )
+ {
+ if( padlock_xcryptcbc( ctx, mode, length, iv, input, output ) == 0 )
+ return;
+ }
+#endif
+
+ if( mode == AES_DECRYPT )
+ {
+ while( length > 0 )
+ {
+ memcpy( temp, input, 16 );
+ aes_crypt_ecb( ctx, mode, input, output );
+
+ for( i = 0; i < 16; i++ )
+ output[i] = (unsigned char)( output[i] ^ iv[i] );
+
+ memcpy( iv, temp, 16 );
+
+ input += 16;
+ output += 16;
+ length -= 16;
+ }
+ }
+ else
+ {
+ while( length > 0 )
+ {
+ for( i = 0; i < 16; i++ )
+ output[i] = (unsigned char)( input[i] ^ iv[i] );
+
+ aes_crypt_ecb( ctx, mode, output, output );
+ memcpy( iv, output, 16 );
+
+ input += 16;
+ output += 16;
+ length -= 16;
+ }
+ }
+}
+
+/*
+ * AES-CFB buffer encryption/decryption
+ */
+void aes_crypt_cfb( aes_context *ctx,
+ int mode,
+ int length,
+ int *iv_off,
+ unsigned char iv[16],
+ const unsigned char *input,
+ unsigned char *output )
+{
+ int c, n = *iv_off;
+
+ if( mode == AES_DECRYPT )
+ {
+ while( length-- )
+ {
+ if( n == 0 )
+ aes_crypt_ecb( ctx, AES_ENCRYPT, iv, iv );
+
+ c = *input++;
+ *output++ = (unsigned char)( c ^ iv[n] );
+ iv[n] = (unsigned char) c;
+
+ n = (n + 1) & 0x0F;
+ }
+ }
+ else
+ {
+ while( length-- )
+ {
+ if( n == 0 )
+ aes_crypt_ecb( ctx, AES_ENCRYPT, iv, iv );
+
+ iv[n] = *output++ = (unsigned char)( iv[n] ^ *input++ );
+
+ n = (n + 1) & 0x0F;
+ }
+ }
+
+ *iv_off = n;
+}
diff --git a/source/fitz/crypt-arc4.c b/source/fitz/crypt-arc4.c
new file mode 100644
index 00000000..9c54fbae
--- /dev/null
+++ b/source/fitz/crypt-arc4.c
@@ -0,0 +1,98 @@
+/* This code illustrates a sample implementation
+ * of the Arcfour algorithm
+ * Copyright (c) April 29, 1997 Kalle Kaukonen.
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that this copyright
+ * notice and disclaimer are retained.
+ *
+ * THIS SOFTWARE IS PROVIDED BY KALLE KAUKONEN AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KALLE
+ * KAUKONEN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mupdf/fitz.h"
+
+void
+fz_arc4_init(fz_arc4 *arc4, const unsigned char *key, unsigned keylen)
+{
+ unsigned int t, u;
+ unsigned int keyindex;
+ unsigned int stateindex;
+ unsigned char *state;
+ unsigned int counter;
+
+ state = arc4->state;
+
+ arc4->x = 0;
+ arc4->y = 0;
+
+ for (counter = 0; counter < 256; counter++)
+ {
+ state[counter] = counter;
+ }
+
+ keyindex = 0;
+ stateindex = 0;
+
+ for (counter = 0; counter < 256; counter++)
+ {
+ t = state[counter];
+ stateindex = (stateindex + key[keyindex] + t) & 0xff;
+ u = state[stateindex];
+
+ state[stateindex] = t;
+ state[counter] = u;
+
+ if (++keyindex >= keylen)
+ {
+ keyindex = 0;
+ }
+ }
+}
+
+static unsigned char
+fz_arc4_next(fz_arc4 *arc4)
+{
+ unsigned int x;
+ unsigned int y;
+ unsigned int sx, sy;
+ unsigned char *state;
+
+ state = arc4->state;
+
+ x = (arc4->x + 1) & 0xff;
+ sx = state[x];
+ y = (sx + arc4->y) & 0xff;
+ sy = state[y];
+
+ arc4->x = x;
+ arc4->y = y;
+
+ state[y] = sx;
+ state[x] = sy;
+
+ return state[(sx + sy) & 0xff];
+}
+
+void
+fz_arc4_encrypt(fz_arc4 *arc4, unsigned char *dest, const unsigned char *src, unsigned len)
+{
+ unsigned int i;
+ for (i = 0; i < len; i++)
+ {
+ unsigned char x;
+ x = fz_arc4_next(arc4);
+ dest[i] = src[i] ^ x;
+ }
+}
diff --git a/source/fitz/crypt-md5.c b/source/fitz/crypt-md5.c
new file mode 100644
index 00000000..7490c0bc
--- /dev/null
+++ b/source/fitz/crypt-md5.c
@@ -0,0 +1,272 @@
+/*
+MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+
+Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991.
+All rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+*/
+
+#include "mupdf/fitz.h"
+
+/* Constants for MD5Transform routine */
+enum
+{
+ S11 = 7, S12 = 12, S13 = 17, S14 = 22,
+ S21 = 5, S22 = 9, S23 = 14, S24 = 20,
+ S31 = 4, S32 = 11, S33 = 16, S34 = 23,
+ S41 = 6, S42 = 10, S43 = 15, S44 = 21
+};
+
+static void encode(unsigned char *, const unsigned int *, const unsigned);
+static void decode(unsigned int *, const unsigned char *, const unsigned);
+static void transform(unsigned int state[4], const unsigned char block[64]);
+
+static unsigned char padding[64] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE rotates x left n bits */
+#define ROTATE(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ * Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (unsigned int)(ac); \
+ (a) = ROTATE ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (unsigned int)(ac); \
+ (a) = ROTATE ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (unsigned int)(ac); \
+ (a) = ROTATE ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (unsigned int)(ac); \
+ (a) = ROTATE ((a), (s)); \
+ (a) += (b); \
+ }
+
+static void encode(unsigned char *output, const unsigned int *input, const unsigned len)
+{
+ unsigned i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+static void decode(unsigned int *output, const unsigned char *input, const unsigned len)
+{
+ unsigned i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ {
+ output[i] = ((unsigned int)input[j]) |
+ (((unsigned int)input[j+1]) << 8) |
+ (((unsigned int)input[j+2]) << 16) |
+ (((unsigned int)input[j+3]) << 24);
+ }
+}
+
+static void transform(unsigned int state[4], const unsigned char block[64])
+{
+ unsigned int a = state[0];
+ unsigned int b = state[1];
+ unsigned int c = state[2];
+ unsigned int d = state[3];
+ unsigned int x[16];
+
+ decode(x, block, 64);
+
+ /* Round 1 */
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x02441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x04881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information */
+ memset(x, 0, sizeof (x));
+}
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context. */
+void fz_md5_init(fz_md5 *context)
+{
+ context->count[0] = context->count[1] = 0;
+
+ /* Load magic initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest operation,
+ * processing another message block, and updating the context.
+ */
+void fz_md5_update(fz_md5 *context, const unsigned char *input, unsigned inlen)
+{
+ unsigned i, index, partlen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ context->count[0] += (unsigned int) inlen << 3;
+ if (context->count[0] < (unsigned int) inlen << 3)
+ context->count[1] ++;
+ context->count[1] += (unsigned int) inlen >> 29;
+
+ partlen = 64 - index;
+
+ /* Transform as many times as possible. */
+ if (inlen >= partlen)
+ {
+ memcpy(context->buffer + index, input, partlen);
+ transform(context->state, context->buffer);
+
+ for (i = partlen; i + 63 < inlen; i += 64)
+ transform(context->state, input + i);
+
+ index = 0;
+ }
+ else
+ {
+ i = 0;
+ }
+
+ /* Buffer remaining input */
+ memcpy(context->buffer + index, input + i, inlen - i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ * the message digest and zeroizing the context.
+ */
+void fz_md5_final(fz_md5 *context, unsigned char digest[16])
+{
+ unsigned char bits[8];
+ unsigned index, padlen;
+
+ /* Save number of bits */
+ encode(bits, context->count, 8);
+
+ /* Pad out to 56 mod 64 */
+ index = (unsigned)((context->count[0] >> 3) & 0x3f);
+ padlen = index < 56 ? 56 - index : 120 - index;
+ fz_md5_update(context, padding, padlen);
+
+ /* Append length (before padding) */
+ fz_md5_update(context, bits, 8);
+
+ /* Store state in digest */
+ encode(digest, context->state, 16);
+
+ /* Zeroize sensitive information */
+ memset(context, 0, sizeof(fz_md5));
+}
diff --git a/source/fitz/crypt-sha2.c b/source/fitz/crypt-sha2.c
new file mode 100644
index 00000000..ffedfc95
--- /dev/null
+++ b/source/fitz/crypt-sha2.c
@@ -0,0 +1,393 @@
+/*
+This code is based on the code found from 7-Zip, which has a modified
+version of the SHA-256 found from Crypto++ <http://www.cryptopp.com/>.
+The code was modified a little to fit into liblzma and fitz.
+
+This file has been put into the public domain.
+You can do whatever you want with this file.
+
+SHA-384 and SHA-512 were also taken from Crypto++ and adapted for fitz.
+*/
+
+#include "mupdf/fitz.h"
+
+static inline int isbigendian(void)
+{
+ static const int one = 1;
+ return *(char*)&one == 0;
+}
+
+static inline unsigned int bswap32(unsigned int num)
+{
+ if (!isbigendian())
+ {
+ return ( (((num) << 24))
+ | (((num) << 8) & 0x00FF0000)
+ | (((num) >> 8) & 0x0000FF00)
+ | (((num) >> 24)) );
+ }
+ return num;
+}
+
+static inline uint64_t bswap64(uint64_t num)
+{
+ if (!isbigendian())
+ {
+ return ( (((num) << 56))
+ | (((num) << 40) & 0x00FF000000000000ULL)
+ | (((num) << 24) & 0x0000FF0000000000ULL)
+ | (((num) << 8) & 0x000000FF00000000ULL)
+ | (((num) >> 8) & 0x00000000FF000000ULL)
+ | (((num) >> 24) & 0x0000000000FF0000ULL)
+ | (((num) >> 40) & 0x000000000000FF00ULL)
+ | (((num) >> 56)) );
+ }
+ return num;
+}
+
+/* At least on x86, GCC is able to optimize this to a rotate instruction. */
+#define rotr(num, amount) ((num) >> (amount) | (num) << (8 * sizeof(num) - (amount)))
+
+#define blk0(i) (W[i] = data[i])
+#define blk2(i) (W[i & 15] += s1(W[(i - 2) & 15]) + W[(i - 7) & 15] \
+ + s0(W[(i - 15) & 15]))
+
+#define Ch(x, y, z) (z ^ (x & (y ^ z)))
+#define Maj(x, y, z) ((x & y) | (z & (x | y)))
+
+#define a(i) T[(0 - i) & 7]
+#define b(i) T[(1 - i) & 7]
+#define c(i) T[(2 - i) & 7]
+#define d(i) T[(3 - i) & 7]
+#define e(i) T[(4 - i) & 7]
+#define f(i) T[(5 - i) & 7]
+#define g(i) T[(6 - i) & 7]
+#define h(i) T[(7 - i) & 7]
+
+#define R(i) \
+ h(i) += S1(e(i)) + Ch(e(i), f(i), g(i)) + K[i + j] \
+ + (j ? blk2(i) : blk0(i)); \
+ d(i) += h(i); \
+ h(i) += S0(a(i)) + Maj(a(i), b(i), c(i))
+
+/* For SHA256 */
+
+#define S0(x) (rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22))
+#define S1(x) (rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25))
+#define s0(x) (rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3))
+#define s1(x) (rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10))
+
+static const unsigned int SHA256_K[64] = {
+ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
+ 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
+ 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
+ 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
+ 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
+ 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
+ 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
+ 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
+ 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
+ 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
+ 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
+ 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
+ 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
+ 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
+ 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
+ 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
+};
+
+static void
+transform256(unsigned int state[8], const unsigned int data_xe[16])
+{
+ const unsigned int *K = SHA256_K;
+ unsigned int data[16];
+ unsigned int W[16];
+ unsigned int T[8];
+ unsigned int j;
+
+ /* ensure big-endian integers */
+ for (j = 0; j < 16; j++)
+ data[j] = bswap32(data_xe[j]);
+
+ /* Copy state[] to working vars. */
+ memcpy(T, state, sizeof(T));
+
+ /* 64 operations, partially loop unrolled */
+ for (j = 0; j < 64; j += 16) {
+ R( 0); R( 1); R( 2); R( 3);
+ R( 4); R( 5); R( 6); R( 7);
+ R( 8); R( 9); R(10); R(11);
+ R(12); R(13); R(14); R(15);
+ }
+
+ /* Add the working vars back into state[]. */
+ state[0] += a(0);
+ state[1] += b(0);
+ state[2] += c(0);
+ state[3] += d(0);
+ state[4] += e(0);
+ state[5] += f(0);
+ state[6] += g(0);
+ state[7] += h(0);
+}
+
+#undef S0
+#undef S1
+#undef s0
+#undef s1
+
+void fz_sha256_init(fz_sha256 *context)
+{
+ context->count[0] = context->count[1] = 0;
+
+ context->state[0] = 0x6A09E667;
+ context->state[1] = 0xBB67AE85;
+ context->state[2] = 0x3C6EF372;
+ context->state[3] = 0xA54FF53A;
+ context->state[4] = 0x510E527F;
+ context->state[5] = 0x9B05688C;
+ context->state[6] = 0x1F83D9AB;
+ context->state[7] = 0x5BE0CD19;
+}
+
+void fz_sha256_update(fz_sha256 *context, const unsigned char *input, unsigned int inlen)
+{
+ /* Copy the input data into a properly aligned temporary buffer.
+ * This way we can be called with arbitrarily sized buffers
+ * (no need to be multiple of 64 bytes), and the code works also
+ * on architectures that don't allow unaligned memory access. */
+ while (inlen > 0)
+ {
+ const unsigned int copy_start = context->count[0] & 0x3F;
+ unsigned int copy_size = 64 - copy_start;
+ if (copy_size > inlen)
+ copy_size = inlen;
+
+ memcpy(context->buffer.u8 + copy_start, input, copy_size);
+
+ input += copy_size;
+ inlen -= copy_size;
+ context->count[0] += copy_size;
+ /* carry overflow from low to high */
+ if (context->count[0] < copy_size)
+ context->count[1]++;
+
+ if ((context->count[0] & 0x3F) == 0)
+ transform256(context->state, context->buffer.u32);
+ }
+}
+
+void fz_sha256_final(fz_sha256 *context, unsigned char digest[32])
+{
+ /* Add padding as described in RFC 3174 (it describes SHA-1 but
+ * the same padding style is used for SHA-256 too). */
+ unsigned int j = context->count[0] & 0x3F;
+ context->buffer.u8[j++] = 0x80;
+
+ while (j != 56)
+ {
+ if (j == 64)
+ {
+ transform256(context->state, context->buffer.u32);
+ j = 0;
+ }
+ context->buffer.u8[j++] = 0x00;
+ }
+
+ /* Convert the message size from bytes to bits. */
+ context->count[1] = (context->count[1] << 3) + (context->count[0] >> 29);
+ context->count[0] = context->count[0] << 3;
+
+ context->buffer.u32[14] = bswap32(context->count[1]);
+ context->buffer.u32[15] = bswap32(context->count[0]);
+ transform256(context->state, context->buffer.u32);
+
+ for (j = 0; j < 8; j++)
+ ((unsigned int *)digest)[j] = bswap32(context->state[j]);
+ memset(context, 0, sizeof(fz_sha256));
+}
+
+/* For SHA512 */
+
+#define S0(x) (rotr(x, 28) ^ rotr(x, 34) ^ rotr(x, 39))
+#define S1(x) (rotr(x, 14) ^ rotr(x, 18) ^ rotr(x, 41))
+#define s0(x) (rotr(x, 1) ^ rotr(x, 8) ^ (x >> 7))
+#define s1(x) (rotr(x, 19) ^ rotr(x, 61) ^ (x >> 6))
+
+static const uint64_t SHA512_K[80] = {
+ 0x428A2F98D728AE22ULL, 0x7137449123EF65CDULL,
+ 0xB5C0FBCFEC4D3B2FULL, 0xE9B5DBA58189DBBCULL,
+ 0x3956C25BF348B538ULL, 0x59F111F1B605D019ULL,
+ 0x923F82A4AF194F9BULL, 0xAB1C5ED5DA6D8118ULL,
+ 0xD807AA98A3030242ULL, 0x12835B0145706FBEULL,
+ 0x243185BE4EE4B28CULL, 0x550C7DC3D5FFB4E2ULL,
+ 0x72BE5D74F27B896FULL, 0x80DEB1FE3B1696B1ULL,
+ 0x9BDC06A725C71235ULL, 0xC19BF174CF692694ULL,
+ 0xE49B69C19EF14AD2ULL, 0xEFBE4786384F25E3ULL,
+ 0x0FC19DC68B8CD5B5ULL, 0x240CA1CC77AC9C65ULL,
+ 0x2DE92C6F592B0275ULL, 0x4A7484AA6EA6E483ULL,
+ 0x5CB0A9DCBD41FBD4ULL, 0x76F988DA831153B5ULL,
+ 0x983E5152EE66DFABULL, 0xA831C66D2DB43210ULL,
+ 0xB00327C898FB213FULL, 0xBF597FC7BEEF0EE4ULL,
+ 0xC6E00BF33DA88FC2ULL, 0xD5A79147930AA725ULL,
+ 0x06CA6351E003826FULL, 0x142929670A0E6E70ULL,
+ 0x27B70A8546D22FFCULL, 0x2E1B21385C26C926ULL,
+ 0x4D2C6DFC5AC42AEDULL, 0x53380D139D95B3DFULL,
+ 0x650A73548BAF63DEULL, 0x766A0ABB3C77B2A8ULL,
+ 0x81C2C92E47EDAEE6ULL, 0x92722C851482353BULL,
+ 0xA2BFE8A14CF10364ULL, 0xA81A664BBC423001ULL,
+ 0xC24B8B70D0F89791ULL, 0xC76C51A30654BE30ULL,
+ 0xD192E819D6EF5218ULL, 0xD69906245565A910ULL,
+ 0xF40E35855771202AULL, 0x106AA07032BBD1B8ULL,
+ 0x19A4C116B8D2D0C8ULL, 0x1E376C085141AB53ULL,
+ 0x2748774CDF8EEB99ULL, 0x34B0BCB5E19B48A8ULL,
+ 0x391C0CB3C5C95A63ULL, 0x4ED8AA4AE3418ACBULL,
+ 0x5B9CCA4F7763E373ULL, 0x682E6FF3D6B2B8A3ULL,
+ 0x748F82EE5DEFB2FCULL, 0x78A5636F43172F60ULL,
+ 0x84C87814A1F0AB72ULL, 0x8CC702081A6439ECULL,
+ 0x90BEFFFA23631E28ULL, 0xA4506CEBDE82BDE9ULL,
+ 0xBEF9A3F7B2C67915ULL, 0xC67178F2E372532BULL,
+ 0xCA273ECEEA26619CULL, 0xD186B8C721C0C207ULL,
+ 0xEADA7DD6CDE0EB1EULL, 0xF57D4F7FEE6ED178ULL,
+ 0x06F067AA72176FBAULL, 0x0A637DC5A2C898A6ULL,
+ 0x113F9804BEF90DAEULL, 0x1B710B35131C471BULL,
+ 0x28DB77F523047D84ULL, 0x32CAAB7B40C72493ULL,
+ 0x3C9EBE0A15C9BEBCULL, 0x431D67C49C100D4CULL,
+ 0x4CC5D4BECB3E42B6ULL, 0x597F299CFC657E2AULL,
+ 0x5FCB6FAB3AD6FAECULL, 0x6C44198C4A475817ULL,
+};
+
+static void
+transform512(uint64_t state[8], const uint64_t data_xe[16])
+{
+ const uint64_t *K = SHA512_K;
+ uint64_t data[16];
+ uint64_t W[16];
+ uint64_t T[8];
+ unsigned int j;
+
+ /* ensure big-endian integers */
+ for (j = 0; j < 16; j++)
+ data[j] = bswap64(data_xe[j]);
+
+ /* Copy state[] to working vars. */
+ memcpy(T, state, sizeof(T));
+
+ /* 80 operations, partially loop unrolled */
+ for (j = 0; j < 80; j+= 16) {
+ R( 0); R( 1); R( 2); R( 3);
+ R( 4); R( 5); R( 6); R( 7);
+ R( 8); R( 9); R(10); R(11);
+ R(12); R(13); R(14); R(15);
+ }
+
+ /* Add the working vars back into state[]. */
+ state[0] += a(0);
+ state[1] += b(0);
+ state[2] += c(0);
+ state[3] += d(0);
+ state[4] += e(0);
+ state[5] += f(0);
+ state[6] += g(0);
+ state[7] += h(0);
+}
+
+#undef S0
+#undef S1
+#undef s0
+#undef s1
+
+void fz_sha512_init(fz_sha512 *context)
+{
+ context->count[0] = context->count[1] = 0;
+
+ context->state[0] = 0x6A09E667F3BCC908ull;
+ context->state[1] = 0xBB67AE8584CAA73Bull;
+ context->state[2] = 0x3C6EF372FE94F82Bull;
+ context->state[3] = 0xA54FF53A5F1D36F1ull;
+ context->state[4] = 0x510E527FADE682D1ull;
+ context->state[5] = 0x9B05688C2B3E6C1Full;
+ context->state[6] = 0x1F83D9ABFB41BD6Bull;
+ context->state[7] = 0x5BE0CD19137E2179ull;
+}
+
+void fz_sha512_update(fz_sha512 *context, const unsigned char *input, unsigned int inlen)
+{
+ /* Copy the input data into a properly aligned temporary buffer.
+ * This way we can be called with arbitrarily sized buffers
+ * (no need to be multiple of 128 bytes), and the code works also
+ * on architectures that don't allow unaligned memory access. */
+ while (inlen > 0)
+ {
+ const unsigned int copy_start = context->count[0] & 0x7F;
+ unsigned int copy_size = 128 - copy_start;
+ if (copy_size > inlen)
+ copy_size = inlen;
+
+ memcpy(context->buffer.u8 + copy_start, input, copy_size);
+
+ input += copy_size;
+ inlen -= copy_size;
+ context->count[0] += copy_size;
+ /* carry overflow from low to high */
+ if (context->count[0] < copy_size)
+ context->count[1]++;
+
+ if ((context->count[0] & 0x7F) == 0)
+ transform512(context->state, context->buffer.u64);
+ }
+}
+
+void fz_sha512_final(fz_sha512 *context, unsigned char digest[64])
+{
+ /* Add padding as described in RFC 3174 (it describes SHA-1 but
+ * the same padding style is used for SHA-512 too). */
+ unsigned int j = context->count[0] & 0x7F;
+ context->buffer.u8[j++] = 0x80;
+
+ while (j != 112)
+ {
+ if (j == 128)
+ {
+ transform512(context->state, context->buffer.u64);
+ j = 0;
+ }
+ context->buffer.u8[j++] = 0x00;
+ }
+
+ /* Convert the message size from bytes to bits. */
+ context->count[1] = (context->count[1] << 3) + (context->count[0] >> 29);
+ context->count[0] = context->count[0] << 3;
+
+ context->buffer.u64[14] = bswap64(context->count[1]);
+ context->buffer.u64[15] = bswap64(context->count[0]);
+ transform512(context->state, context->buffer.u64);
+
+ for (j = 0; j < 8; j++)
+ ((uint64_t *)digest)[j] = bswap64(context->state[j]);
+ memset(context, 0, sizeof(fz_sha512));
+}
+
+void fz_sha384_init(fz_sha384 *context)
+{
+ context->count[0] = context->count[1] = 0;
+
+ context->state[0] = 0xCBBB9D5DC1059ED8ull;
+ context->state[1] = 0x629A292A367CD507ull;
+ context->state[2] = 0x9159015A3070DD17ull;
+ context->state[3] = 0x152FECD8F70E5939ull;
+ context->state[4] = 0x67332667FFC00B31ull;
+ context->state[5] = 0x8EB44A8768581511ull;
+ context->state[6] = 0xDB0C2E0D64F98FA7ull;
+ context->state[7] = 0x47B5481DBEFA4FA4ull;
+}
+
+void fz_sha384_update(fz_sha384 *context, const unsigned char *input, unsigned int inlen)
+{
+ fz_sha512_update(context, input, inlen);
+}
+
+void fz_sha384_final(fz_sha384 *context, unsigned char digest[64])
+{
+ fz_sha512_final(context, digest);
+}
diff --git a/source/fitz/device.c b/source/fitz/device.c
new file mode 100644
index 00000000..175b00db
--- /dev/null
+++ b/source/fitz/device.c
@@ -0,0 +1,388 @@
+#include "mupdf/fitz.h"
+
+fz_device *
+fz_new_device(fz_context *ctx, void *user)
+{
+ fz_device *dev = fz_malloc_struct(ctx, fz_device);
+ dev->hints = 0;
+ dev->flags = 0;
+ dev->user = user;
+ dev->ctx = ctx;
+ dev->error_depth = 0;
+ return dev;
+}
+
+void
+fz_free_device(fz_device *dev)
+{
+ if (dev == NULL)
+ return;
+ if (dev->free_user)
+ dev->free_user(dev);
+ fz_free(dev->ctx, dev);
+}
+
+void
+fz_enable_device_hints(fz_device *dev, int hints)
+{
+ dev->hints |= hints;
+}
+
+void
+fz_disable_device_hints(fz_device *dev, int hints)
+{
+ dev->hints &= ~hints;
+}
+
+void
+fz_begin_page(fz_device *dev, const fz_rect *rect, const fz_matrix *ctm)
+{
+ if (dev->begin_page)
+ dev->begin_page(dev, rect, ctm);
+}
+
+void
+fz_end_page(fz_device *dev)
+{
+ if (dev->end_page)
+ dev->end_page(dev);
+}
+
+void
+fz_fill_path(fz_device *dev, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->fill_path)
+ dev->fill_path(dev, path, even_odd, ctm, colorspace, color, alpha);
+}
+
+void
+fz_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->stroke_path)
+ dev->stroke_path(dev, path, stroke, ctm, colorspace, color, alpha);
+}
+
+void
+fz_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->clip_path)
+ dev->clip_path(dev, path, rect, even_odd, ctm);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->clip_stroke_path)
+ dev->clip_stroke_path(dev, path, rect, stroke, ctm);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->fill_text)
+ dev->fill_text(dev, text, ctm, colorspace, color, alpha);
+}
+
+void
+fz_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->stroke_text)
+ dev->stroke_text(dev, text, stroke, ctm, colorspace, color, alpha);
+}
+
+void
+fz_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ if (accumulate == 0 || accumulate == 1)
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->clip_text)
+ dev->clip_text(dev, text, ctm, accumulate);
+ }
+ fz_catch(ctx)
+ {
+ if (accumulate == 2)
+ fz_rethrow(ctx);
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->clip_stroke_text)
+ dev->clip_stroke_text(dev, text, stroke, ctm);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->ignore_text)
+ dev->ignore_text(dev, text, ctm);
+}
+
+void
+fz_pop_clip(fz_device *dev)
+{
+ if (dev->error_depth)
+ {
+ dev->error_depth--;
+ if (dev->error_depth == 0)
+ fz_throw(dev->ctx, FZ_ERROR_GENERIC, "%s", dev->errmess);
+ return;
+ }
+ if (dev->pop_clip)
+ dev->pop_clip(dev);
+}
+
+void
+fz_fill_shade(fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->fill_shade)
+ dev->fill_shade(dev, shade, ctm, alpha);
+}
+
+void
+fz_fill_image(fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->fill_image)
+ dev->fill_image(dev, image, ctm, alpha);
+}
+
+void
+fz_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ if (dev->error_depth)
+ return;
+ if (dev->fill_image_mask)
+ dev->fill_image_mask(dev, image, ctm, colorspace, color, alpha);
+}
+
+void
+fz_clip_image_mask(fz_device *dev, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->clip_image_mask)
+ dev->clip_image_mask(dev, image, rect, ctm);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_begin_mask(fz_device *dev, const fz_rect *area, int luminosity, fz_colorspace *colorspace, float *bc)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->begin_mask)
+ dev->begin_mask(dev, area, luminosity, colorspace, bc);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_end_mask(fz_device *dev)
+{
+ if (dev->error_depth)
+ {
+ /* Converts from mask to clip, so no change in stack depth */
+ return;
+ }
+ if (dev->end_mask)
+ dev->end_mask(dev);
+}
+
+void
+fz_begin_group(fz_device *dev, const fz_rect *area, int isolated, int knockout, int blendmode, float alpha)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ if (dev->begin_group)
+ dev->begin_group(dev, area, isolated, knockout, blendmode, alpha);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+}
+
+void
+fz_end_group(fz_device *dev)
+{
+ if (dev->error_depth)
+ {
+ dev->error_depth--;
+ if (dev->error_depth == 0)
+ fz_throw(dev->ctx, FZ_ERROR_GENERIC, "%s", dev->errmess);
+ return;
+ }
+ if (dev->end_group)
+ dev->end_group(dev);
+}
+
+void
+fz_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm)
+{
+ (void)fz_begin_tile_id(dev, area, view, xstep, ystep, ctm, 0);
+}
+
+int
+fz_begin_tile_id(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ fz_context *ctx = dev->ctx;
+ int ret = 0;
+
+ if (dev->error_depth)
+ {
+ dev->error_depth++;
+ return 0;
+ }
+
+ if (xstep < 0)
+ xstep = -xstep;
+ if (ystep < 0)
+ ystep = -ystep;
+
+ fz_try(ctx)
+ {
+ if (dev->begin_tile)
+ ret = dev->begin_tile(dev, area, view, xstep, ystep, ctm, id);
+ }
+ fz_catch(ctx)
+ {
+ dev->error_depth = 1;
+ strcpy(dev->errmess, fz_caught_message(ctx));
+ /* Error swallowed */
+ }
+ return ret;
+}
+
+void
+fz_end_tile(fz_device *dev)
+{
+ if (dev->error_depth)
+ {
+ dev->error_depth--;
+ if (dev->error_depth == 0)
+ fz_throw(dev->ctx, FZ_ERROR_GENERIC, "%s", dev->errmess);
+ return;
+ }
+ if (dev->end_tile)
+ dev->end_tile(dev);
+}
diff --git a/source/fitz/document.c b/source/fitz/document.c
new file mode 100644
index 00000000..8adbf816
--- /dev/null
+++ b/source/fitz/document.c
@@ -0,0 +1,274 @@
+#include "mupdf/fitz.h"
+
+/* Yuck! Promiscuous we are. */
+extern struct pdf_document *pdf_open_document(fz_context *ctx, const char *filename);
+extern struct xps_document *xps_open_document(fz_context *ctx, const char *filename);
+extern struct cbz_document *cbz_open_document(fz_context *ctx, const char *filename);
+extern struct image_document *image_open_document(fz_context *ctx, const char *filename);
+
+extern struct pdf_document *pdf_open_document_with_stream(fz_context *ctx, fz_stream *file);
+extern struct xps_document *xps_open_document_with_stream(fz_context *ctx, fz_stream *file);
+extern struct cbz_document *cbz_open_document_with_stream(fz_context *ctx, fz_stream *file);
+extern struct image_document *image_open_document_with_stream(fz_context *ctx, fz_stream *file);
+
+extern int pdf_js_supported(void);
+
+static inline int fz_tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+static inline int fz_strcasecmp(const char *a, const char *b)
+{
+ while (fz_tolower(*a) == fz_tolower(*b))
+ {
+ if (*a++ == 0)
+ return 0;
+ b++;
+ }
+ return fz_tolower(*a) - fz_tolower(*b);
+}
+
+fz_document *
+fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream)
+{
+ char *ext = strrchr(magic, '.');
+
+ if (ext)
+ {
+ if (!fz_strcasecmp(ext, ".xps") || !fz_strcasecmp(ext, ".rels") || !fz_strcasecmp(ext, ".oxps"))
+ return (fz_document*) xps_open_document_with_stream(ctx, stream);
+ if (!fz_strcasecmp(ext, ".cbz") || !fz_strcasecmp(ext, ".zip"))
+ return (fz_document*) cbz_open_document_with_stream(ctx, stream);
+ if (!fz_strcasecmp(ext, ".pdf"))
+ return (fz_document*) pdf_open_document_with_stream(ctx, stream);
+ if (!fz_strcasecmp(ext, ".png") || !fz_strcasecmp(ext, ".jpg") ||
+ !fz_strcasecmp(ext, ".jpeg") || !fz_strcasecmp(ext, ".jfif") ||
+ !fz_strcasecmp(ext, ".jfif-tbnl") || !fz_strcasecmp(ext, ".jpe") ||
+ !fz_strcasecmp(ext, ".tif") || !fz_strcasecmp(ext, ".tiff"))
+ return (fz_document*) image_open_document_with_stream(ctx, stream);
+ }
+
+ if (!strcmp(magic, "cbz") || !strcmp(magic, "application/x-cbz"))
+ return (fz_document*) cbz_open_document_with_stream(ctx, stream);
+ if (!strcmp(magic, "xps") || !strcmp(magic, "oxps") || !strcmp(magic, "application/vnd.ms-xpsdocument"))
+ return (fz_document*) xps_open_document_with_stream(ctx, stream);
+ if (!strcmp(magic, "pdf") || !strcmp(magic, "application/pdf"))
+ return (fz_document*) pdf_open_document_with_stream(ctx, stream);
+ if (!strcmp(magic, "png") || !strcmp(magic, "image/png") ||
+ !strcmp(magic, "jpg") || !strcmp(magic, "image/jpeg") ||
+ !strcmp(magic, "jpeg") || !strcmp(magic, "image/pjpeg") ||
+ !strcmp(magic, "jpe") || !strcmp(magic, "jfif") ||
+ !strcmp(magic, "tif") || !strcmp(magic, "image/tiff") ||
+ !strcmp(magic, "tiff") || !strcmp(magic, "image/x-tiff"))
+ return (fz_document*) image_open_document_with_stream(ctx, stream);
+
+ /* last guess: pdf */
+ return (fz_document*) pdf_open_document_with_stream(ctx, stream);
+}
+
+fz_document *
+fz_open_document(fz_context *ctx, const char *filename)
+{
+ char *ext = strrchr(filename, '.');
+
+ if (ext)
+ {
+ if (!fz_strcasecmp(ext, ".xps") || !fz_strcasecmp(ext, ".rels") || !fz_strcasecmp(ext, ".oxps"))
+ return (fz_document*) xps_open_document(ctx, filename);
+ if (!fz_strcasecmp(ext, ".cbz") || !fz_strcasecmp(ext, ".zip"))
+ return (fz_document*) cbz_open_document(ctx, filename);
+ if (!fz_strcasecmp(ext, ".pdf"))
+ return (fz_document*) pdf_open_document(ctx, filename);
+ if (!fz_strcasecmp(ext, ".png") || !fz_strcasecmp(ext, ".jpg") ||
+ !fz_strcasecmp(ext, ".jpeg") || !fz_strcasecmp(ext, ".jpe") ||
+ !fz_strcasecmp(ext, ".jfif") || !fz_strcasecmp(ext, ".jfif-tbnl") ||
+ !fz_strcasecmp(ext, ".tif") || !fz_strcasecmp(ext, ".tiff"))
+ return (fz_document*) image_open_document(ctx, filename);
+ }
+
+ /* last guess: pdf */
+ return (fz_document*) pdf_open_document(ctx, filename);
+}
+
+void
+fz_close_document(fz_document *doc)
+{
+ if (doc && doc->close)
+ doc->close(doc);
+}
+
+int
+fz_needs_password(fz_document *doc)
+{
+ if (doc && doc->needs_password)
+ return doc->needs_password(doc);
+ return 0;
+}
+
+int
+fz_authenticate_password(fz_document *doc, char *password)
+{
+ if (doc && doc->authenticate_password)
+ return doc->authenticate_password(doc, password);
+ return 1;
+}
+
+fz_outline *
+fz_load_outline(fz_document *doc)
+{
+ if (doc && doc->load_outline)
+ return doc->load_outline(doc);
+ return NULL;
+}
+
+int
+fz_count_pages(fz_document *doc)
+{
+ if (doc && doc->count_pages)
+ return doc->count_pages(doc);
+ return 0;
+}
+
+fz_page *
+fz_load_page(fz_document *doc, int number)
+{
+ if (doc && doc->load_page)
+ return doc->load_page(doc, number);
+ return NULL;
+}
+
+fz_link *
+fz_load_links(fz_document *doc, fz_page *page)
+{
+ if (doc && doc->load_links && page)
+ return doc->load_links(doc, page);
+ return NULL;
+}
+
+fz_rect *
+fz_bound_page(fz_document *doc, fz_page *page, fz_rect *r)
+{
+ if (doc && doc->bound_page && page && r)
+ return doc->bound_page(doc, page, r);
+ if (r)
+ *r = fz_empty_rect;
+ return r;
+}
+
+fz_annot *
+fz_first_annot(fz_document *doc, fz_page *page)
+{
+ if (doc && doc->first_annot && page)
+ return doc->first_annot(doc, page);
+ return NULL;
+}
+
+fz_annot *
+fz_next_annot(fz_document *doc, fz_annot *annot)
+{
+ if (doc && doc->next_annot && annot)
+ return doc->next_annot(doc, annot);
+ return NULL;
+}
+
+fz_rect *
+fz_bound_annot(fz_document *doc, fz_annot *annot, fz_rect *rect)
+{
+ if (doc && doc->bound_annot && annot && rect)
+ return doc->bound_annot(doc, annot, rect);
+ if (rect)
+ *rect = fz_empty_rect;
+ return rect;
+}
+
+void
+fz_run_page_contents(fz_document *doc, fz_page *page, fz_device *dev, const fz_matrix *transform, fz_cookie *cookie)
+{
+ if (doc && doc->run_page_contents && page)
+ doc->run_page_contents(doc, page, dev, transform, cookie);
+}
+
+void
+fz_run_annot(fz_document *doc, fz_page *page, fz_annot *annot, fz_device *dev, const fz_matrix *transform, fz_cookie *cookie)
+{
+ if (doc && doc->run_annot && page && annot)
+ doc->run_annot(doc, page, annot, dev, transform, cookie);
+}
+
+void
+fz_run_page(fz_document *doc, fz_page *page, fz_device *dev, const fz_matrix *transform, fz_cookie *cookie)
+{
+ fz_annot *annot;
+ fz_rect mediabox;
+
+ fz_bound_page(doc, page, &mediabox);
+ fz_begin_page(dev, &mediabox, transform);
+
+ fz_run_page_contents(doc, page, dev, transform, cookie);
+
+ if (cookie && cookie->progress_max != -1)
+ {
+ int count = 1;
+ for (annot = fz_first_annot(doc, page); annot; annot = fz_next_annot(doc, annot))
+ count++;
+ cookie->progress_max += count;
+ }
+
+ for (annot = fz_first_annot(doc, page); annot; annot = fz_next_annot(doc, annot))
+ {
+ /* Check the cookie for aborting */
+ if (cookie)
+ {
+ if (cookie->abort)
+ break;
+ cookie->progress++;
+ }
+
+ fz_run_annot(doc, page, annot, dev, transform, cookie);
+ }
+
+ fz_end_page(dev);
+}
+
+void
+fz_free_page(fz_document *doc, fz_page *page)
+{
+ if (doc && doc->free_page && page)
+ doc->free_page(doc, page);
+}
+
+int
+fz_meta(fz_document *doc, int key, void *ptr, int size)
+{
+ if (doc && doc->meta)
+ return doc->meta(doc, key, ptr, size);
+ return FZ_META_UNKNOWN_KEY;
+}
+
+fz_transition *
+fz_page_presentation(fz_document *doc, fz_page *page, float *duration)
+{
+ float dummy;
+ if (duration)
+ *duration = 0;
+ else
+ duration = &dummy;
+ if (doc && doc->page_presentation && page)
+ return doc->page_presentation(doc, page, duration);
+ return NULL;
+}
+
+int fz_javascript_supported(void)
+{
+ return pdf_js_supported();
+}
+
+void
+fz_write_document(fz_document *doc, char *filename, fz_write_options *opts)
+{
+ if (doc && doc->write)
+ doc->write(doc, filename, opts);
+}
diff --git a/source/fitz/draw-affine.c b/source/fitz/draw-affine.c
new file mode 100644
index 00000000..a8ebfa02
--- /dev/null
+++ b/source/fitz/draw-affine.c
@@ -0,0 +1,755 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+typedef unsigned char byte;
+
+static inline float roundup(float x)
+{
+ return (x < 0) ? floorf(x) : ceilf(x);
+}
+
+static inline int lerp(int a, int b, int t)
+{
+ return a + (((b - a) * t) >> 16);
+}
+
+static inline int bilerp(int a, int b, int c, int d, int u, int v)
+{
+ return lerp(lerp(a, b, u), lerp(c, d, u), v);
+}
+
+static inline byte *sample_nearest(byte *s, int w, int h, int n, int u, int v)
+{
+ if (u < 0) u = 0;
+ if (v < 0) v = 0;
+ if (u >= w) u = w - 1;
+ if (v >= h) v = h - 1;
+ return s + (v * w + u) * n;
+}
+
+/* Blend premultiplied source image in constant alpha over destination */
+
+static inline void
+fz_paint_affine_alpha_N_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *hp)
+{
+ int k;
+ int n1 = n-1;
+
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ int uf = u & 0xffff;
+ int vf = v & 0xffff;
+ byte *a = sample_nearest(sp, sw, sh, n, ui, vi);
+ byte *b = sample_nearest(sp, sw, sh, n, ui+1, vi);
+ byte *c = sample_nearest(sp, sw, sh, n, ui, vi+1);
+ byte *d = sample_nearest(sp, sw, sh, n, ui+1, vi+1);
+ int xa = bilerp(a[n1], b[n1], c[n1], d[n1], uf, vf);
+ int t;
+ xa = fz_mul255(xa, alpha);
+ t = 255 - xa;
+ for (k = 0; k < n1; k++)
+ {
+ int x = bilerp(a[k], b[k], c[k], d[k], uf, vf);
+ dp[k] = fz_mul255(x, alpha) + fz_mul255(dp[k], t);
+ }
+ dp[n1] = xa + fz_mul255(dp[n1], t);
+ if (hp)
+ hp[0] = xa + fz_mul255(hp[0], t);
+ }
+ dp += n;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+/* Special case code for gray -> rgb */
+static inline void
+fz_paint_affine_alpha_g2rgb_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int alpha, byte *hp)
+{
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ int uf = u & 0xffff;
+ int vf = v & 0xffff;
+ byte *a = sample_nearest(sp, sw, sh, 2, ui, vi);
+ byte *b = sample_nearest(sp, sw, sh, 2, ui+1, vi);
+ byte *c = sample_nearest(sp, sw, sh, 2, ui, vi+1);
+ byte *d = sample_nearest(sp, sw, sh, 2, ui+1, vi+1);
+ int y = bilerp(a[1], b[1], c[1], d[1], uf, vf);
+ int x = bilerp(a[0], b[0], c[0], d[0], uf, vf);
+ int t;
+ x = fz_mul255(x, alpha);
+ y = fz_mul255(y, alpha);
+ t = 255 - y;
+ dp[0] = x + fz_mul255(dp[0], t);
+ dp[1] = x + fz_mul255(dp[1], t);
+ dp[2] = x + fz_mul255(dp[2], t);
+ dp[3] = y + fz_mul255(dp[3], t);
+ if (hp)
+ hp[0] = y + fz_mul255(hp[0], t);
+ }
+ dp += 4;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static inline void
+fz_paint_affine_alpha_N_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *hp)
+{
+ int k;
+ int n1 = n-1;
+
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ byte *sample = sp + ((vi * sw + ui) * n);
+ int a = fz_mul255(sample[n-1], alpha);
+ int t = 255 - a;
+ for (k = 0; k < n1; k++)
+ dp[k] = fz_mul255(sample[k], alpha) + fz_mul255(dp[k], t);
+ dp[n1] = a + fz_mul255(dp[n1], t);
+ if (hp)
+ hp[0] = a + fz_mul255(hp[0], t);
+ }
+ dp += n;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static inline void
+fz_paint_affine_alpha_g2rgb_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int alpha, byte *hp)
+{
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ byte *sample = sp + ((vi * sw + ui) * 2);
+ int x = fz_mul255(sample[0], alpha);
+ int a = fz_mul255(sample[1], alpha);
+ int t = 255 - a;
+ dp[0] = x + fz_mul255(dp[0], t);
+ dp[1] = x + fz_mul255(dp[1], t);
+ dp[2] = x + fz_mul255(dp[2], t);
+ dp[3] = a + fz_mul255(dp[3], t);
+ if (hp)
+ hp[0] = a + fz_mul255(hp[0], t);
+ }
+ dp += 4;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+/* Blend premultiplied source image over destination */
+
+static inline void
+fz_paint_affine_N_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, byte *hp)
+{
+ int k;
+ int n1 = n-1;
+
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ int uf = u & 0xffff;
+ int vf = v & 0xffff;
+ byte *a = sample_nearest(sp, sw, sh, n, ui, vi);
+ byte *b = sample_nearest(sp, sw, sh, n, ui+1, vi);
+ byte *c = sample_nearest(sp, sw, sh, n, ui, vi+1);
+ byte *d = sample_nearest(sp, sw, sh, n, ui+1, vi+1);
+ int y = bilerp(a[n1], b[n1], c[n1], d[n1], uf, vf);
+ int t = 255 - y;
+ for (k = 0; k < n1; k++)
+ {
+ int x = bilerp(a[k], b[k], c[k], d[k], uf, vf);
+ dp[k] = x + fz_mul255(dp[k], t);
+ }
+ dp[n1] = y + fz_mul255(dp[n1], t);
+ if (hp)
+ hp[0] = y + fz_mul255(hp[0], t);
+ }
+ dp += n;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static inline void
+fz_paint_affine_solid_g2rgb_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, byte *hp)
+{
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ int uf = u & 0xffff;
+ int vf = v & 0xffff;
+ byte *a = sample_nearest(sp, sw, sh, 2, ui, vi);
+ byte *b = sample_nearest(sp, sw, sh, 2, ui+1, vi);
+ byte *c = sample_nearest(sp, sw, sh, 2, ui, vi+1);
+ byte *d = sample_nearest(sp, sw, sh, 2, ui+1, vi+1);
+ int y = bilerp(a[1], b[1], c[1], d[1], uf, vf);
+ int t = 255 - y;
+ int x = bilerp(a[0], b[0], c[0], d[0], uf, vf);
+ dp[0] = x + fz_mul255(dp[0], t);
+ dp[1] = x + fz_mul255(dp[1], t);
+ dp[2] = x + fz_mul255(dp[2], t);
+ dp[3] = y + fz_mul255(dp[3], t);
+ if (hp)
+ hp[0] = y + fz_mul255(hp[0], t);
+ }
+ dp += 4;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static inline void
+fz_paint_affine_N_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, byte *hp)
+{
+ int k;
+ int n1 = n-1;
+
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ byte *sample = sp + ((vi * sw + ui) * n);
+ int a = sample[n1];
+ int t = 255 - a;
+ for (k = 0; k < n1; k++)
+ dp[k] = sample[k] + fz_mul255(dp[k], t);
+ dp[n1] = a + fz_mul255(dp[n1], t);
+ if (hp)
+ hp[0] = a + fz_mul255(hp[0], t);
+ }
+ dp += n;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static inline void
+fz_paint_affine_solid_g2rgb_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, byte *hp)
+{
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ byte *sample = sp + ((vi * sw + ui) * 2);
+ int x = sample[0];
+ int a = sample[1];
+ int t = 255 - a;
+ dp[0] = x + fz_mul255(dp[0], t);
+ dp[1] = x + fz_mul255(dp[1], t);
+ dp[2] = x + fz_mul255(dp[2], t);
+ dp[3] = a + fz_mul255(dp[3], t);
+ if (hp)
+ hp[0] = a + fz_mul255(hp[0], t);
+ }
+ dp += 4;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+/* Blend non-premultiplied color in source image mask over destination */
+
+static inline void
+fz_paint_affine_color_N_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, byte *color, byte *hp)
+{
+ int n1 = n - 1;
+ int sa = color[n1];
+ int k;
+
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ int uf = u & 0xffff;
+ int vf = v & 0xffff;
+ byte *a = sample_nearest(sp, sw, sh, 1, ui, vi);
+ byte *b = sample_nearest(sp, sw, sh, 1, ui+1, vi);
+ byte *c = sample_nearest(sp, sw, sh, 1, ui, vi+1);
+ byte *d = sample_nearest(sp, sw, sh, 1, ui+1, vi+1);
+ int ma = bilerp(a[0], b[0], c[0], d[0], uf, vf);
+ int masa = FZ_COMBINE(FZ_EXPAND(ma), sa);
+ for (k = 0; k < n1; k++)
+ dp[k] = FZ_BLEND(color[k], dp[k], masa);
+ dp[n1] = FZ_BLEND(255, dp[n1], masa);
+ if (hp)
+ hp[0] = FZ_BLEND(255, hp[0], masa);
+ }
+ dp += n;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static inline void
+fz_paint_affine_color_N_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, byte *color, byte *hp)
+{
+ int n1 = n-1;
+ int sa = color[n1];
+ int k;
+
+ while (w--)
+ {
+ int ui = u >> 16;
+ int vi = v >> 16;
+ if (ui >= 0 && ui < sw && vi >= 0 && vi < sh)
+ {
+ int ma = sp[vi * sw + ui];
+ int masa = FZ_COMBINE(FZ_EXPAND(ma), sa);
+ for (k = 0; k < n1; k++)
+ dp[k] = FZ_BLEND(color[k], dp[k], masa);
+ dp[n1] = FZ_BLEND(255, dp[n1], masa);
+ if (hp)
+ hp[0] = FZ_BLEND(255, hp[0], masa);
+ }
+ dp += n;
+ if (hp)
+ hp++;
+ u += fa;
+ v += fb;
+ }
+}
+
+static void
+fz_paint_affine_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *color/*unused*/, byte *hp)
+{
+ if (alpha == 255)
+ {
+ switch (n)
+ {
+ case 1: fz_paint_affine_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 1, hp); break;
+ case 2: fz_paint_affine_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 2, hp); break;
+ case 4: fz_paint_affine_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 4, hp); break;
+ default: fz_paint_affine_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, n, hp); break;
+ }
+ }
+ else if (alpha > 0)
+ {
+ switch (n)
+ {
+ case 1: fz_paint_affine_alpha_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 1, alpha, hp); break;
+ case 2: fz_paint_affine_alpha_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 2, alpha, hp); break;
+ case 4: fz_paint_affine_alpha_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 4, alpha, hp); break;
+ default: fz_paint_affine_alpha_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, n, alpha, hp); break;
+ }
+ }
+}
+
+static void
+fz_paint_affine_g2rgb_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *color/*unused*/, byte *hp)
+{
+ if (alpha == 255)
+ {
+ fz_paint_affine_solid_g2rgb_lerp(dp, sp, sw, sh, u, v, fa, fb, w, hp);
+ }
+ else if (alpha > 0)
+ {
+ fz_paint_affine_alpha_g2rgb_lerp(dp, sp, sw, sh, u, v, fa, fb, w, alpha, hp);
+ }
+}
+
+static void
+fz_paint_affine_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *color/*unused */, byte *hp)
+{
+ if (alpha == 255)
+ {
+ switch (n)
+ {
+ case 1: fz_paint_affine_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 1, hp); break;
+ case 2: fz_paint_affine_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 2, hp); break;
+ case 4: fz_paint_affine_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 4, hp); break;
+ default: fz_paint_affine_N_near(dp, sp, sw, sh, u, v, fa, fb, w, n, hp); break;
+ }
+ }
+ else if (alpha > 0)
+ {
+ switch (n)
+ {
+ case 1: fz_paint_affine_alpha_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 1, alpha, hp); break;
+ case 2: fz_paint_affine_alpha_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 2, alpha, hp); break;
+ case 4: fz_paint_affine_alpha_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 4, alpha, hp); break;
+ default: fz_paint_affine_alpha_N_near(dp, sp, sw, sh, u, v, fa, fb, w, n, alpha, hp); break;
+ }
+ }
+}
+
+static void
+fz_paint_affine_g2rgb_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *color/*unused*/, byte *hp)
+{
+ if (alpha == 255)
+ {
+ fz_paint_affine_solid_g2rgb_near(dp, sp, sw, sh, u, v, fa, fb, w, hp);
+ }
+ else if (alpha > 0)
+ {
+ fz_paint_affine_alpha_g2rgb_near(dp, sp, sw, sh, u, v, fa, fb, w, alpha, hp);
+ }
+}
+
+static void
+fz_paint_affine_color_lerp(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha/*unused*/, byte *color, byte *hp)
+{
+ switch (n)
+ {
+ case 2: fz_paint_affine_color_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 2, color, hp); break;
+ case 4: fz_paint_affine_color_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, 4, color, hp); break;
+ default: fz_paint_affine_color_N_lerp(dp, sp, sw, sh, u, v, fa, fb, w, n, color, hp); break;
+ }
+}
+
+static void
+fz_paint_affine_color_near(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha/*unused*/, byte *color, byte *hp)
+{
+ switch (n)
+ {
+ case 2: fz_paint_affine_color_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 2, color, hp); break;
+ case 4: fz_paint_affine_color_N_near(dp, sp, sw, sh, u, v, fa, fb, w, 4, color, hp); break;
+ default: fz_paint_affine_color_N_near(dp, sp, sw, sh, u, v, fa, fb, w, n, color, hp); break;
+ }
+}
+
+/* RJW: The following code was originally written to be sensitive to
+ * FLT_EPSILON. Given the way the 'minimum representable difference'
+ * between 2 floats changes size as we scale, we now pick a larger
+ * value to ensure idempotency even with rounding problems. The
+ * value we pick is still far smaller than would ever show up with
+ * antialiasing.
+ */
+#define MY_EPSILON 0.001
+
+void
+fz_gridfit_matrix(fz_matrix *m)
+{
+ if (fabsf(m->b) < FLT_EPSILON && fabsf(m->c) < FLT_EPSILON)
+ {
+ if (m->a > 0)
+ {
+ float f;
+ /* Adjust left hand side onto pixel boundary */
+ f = (float)(int)(m->e);
+ if (f - m->e > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves left */
+ m->a += m->e - f; /* width gets wider as f <= m.e */
+ m->e = f;
+ /* Adjust right hand side onto pixel boundary */
+ f = (float)(int)(m->a);
+ if (m->a - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves right */
+ m->a = f;
+ }
+ else if (m->a < 0)
+ {
+ float f;
+ /* Adjust right hand side onto pixel boundary */
+ f = (float)(int)(m->e);
+ if (m->e - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves right */
+ m->a += m->e - f; /* width gets wider (more -ve) */
+ m->e = f;
+ /* Adjust left hand side onto pixel boundary */
+ f = (float)(int)(m->a);
+ if (f - m->a > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves left */
+ m->a = f;
+ }
+ if (m->d > 0)
+ {
+ float f;
+ /* Adjust top onto pixel boundary */
+ f = (float)(int)(m->f);
+ if (f - m->f > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves upwards */
+ m->d += m->f - f; /* width gets wider as f <= m.f */
+ m->f = f;
+ /* Adjust bottom onto pixel boundary */
+ f = (float)(int)(m->d);
+ if (m->d - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves down */
+ m->d = f;
+ }
+ else if (m->d < 0)
+ {
+ float f;
+ /* Adjust bottom onto pixel boundary */
+ f = (float)(int)(m->f);
+ if (m->f - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves down */
+ m->d += m->f - f; /* width gets wider (more -ve) */
+ m->f = f;
+ /* Adjust top onto pixel boundary */
+ f = (float)(int)(m->d);
+ if (f - m->d > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves up */
+ m->d = f;
+ }
+ }
+ else if (fabsf(m->a) < FLT_EPSILON && fabsf(m->d) < FLT_EPSILON)
+ {
+ if (m->b > 0)
+ {
+ float f;
+ /* Adjust left hand side onto pixel boundary */
+ f = (float)(int)(m->f);
+ if (f - m->f > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves left */
+ m->b += m->f - f; /* width gets wider as f <= m.f */
+ m->f = f;
+ /* Adjust right hand side onto pixel boundary */
+ f = (float)(int)(m->b);
+ if (m->b - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves right */
+ m->b = f;
+ }
+ else if (m->b < 0)
+ {
+ float f;
+ /* Adjust right hand side onto pixel boundary */
+ f = (float)(int)(m->f);
+ if (m->f - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves right */
+ m->b += m->f - f; /* width gets wider (more -ve) */
+ m->f = f;
+ /* Adjust left hand side onto pixel boundary */
+ f = (float)(int)(m->b);
+ if (f - m->b > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves left */
+ m->b = f;
+ }
+ if (m->c > 0)
+ {
+ float f;
+ /* Adjust top onto pixel boundary */
+ f = (float)(int)(m->e);
+ if (f - m->e > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves upwards */
+ m->c += m->e - f; /* width gets wider as f <= m.e */
+ m->e = f;
+ /* Adjust bottom onto pixel boundary */
+ f = (float)(int)(m->c);
+ if (m->c - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves down */
+ m->c = f;
+ }
+ else if (m->c < 0)
+ {
+ float f;
+ /* Adjust bottom onto pixel boundary */
+ f = (float)(int)(m->e);
+ if (m->e - f > MY_EPSILON)
+ f += 1.0; /* Ensure it moves down */
+ m->c += m->e - f; /* width gets wider (more -ve) */
+ m->e = f;
+ /* Adjust top onto pixel boundary */
+ f = (float)(int)(m->c);
+ if (f - m->c > MY_EPSILON)
+ f -= 1.0; /* Ensure it moves up */
+ m->c = f;
+ }
+ }
+}
+
+/* Draw an image with an affine transform on destination */
+
+static void
+fz_paint_image_imp(fz_pixmap *dst, const fz_irect *scissor, fz_pixmap *shape, fz_pixmap *img, const fz_matrix *ctm, byte *color, int alpha)
+{
+ byte *dp, *sp, *hp;
+ int u, v, fa, fb, fc, fd;
+ int x, y, w, h;
+ int sw, sh, n, hw;
+ fz_irect bbox;
+ int dolerp;
+ void (*paintfn)(byte *dp, byte *sp, int sw, int sh, int u, int v, int fa, int fb, int w, int n, int alpha, byte *color, byte *hp);
+ fz_matrix local_ctm = *ctm;
+ fz_rect rect;
+ int is_rectilinear;
+
+ /* grid fit the image */
+ fz_gridfit_matrix(&local_ctm);
+
+ /* turn on interpolation for upscaled and non-rectilinear transforms */
+ dolerp = 0;
+ is_rectilinear = fz_is_rectilinear(&local_ctm);
+ if (!is_rectilinear)
+ dolerp = 1;
+ if (sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b) > img->w)
+ dolerp = 1;
+ if (sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d) > img->h)
+ dolerp = 1;
+
+ /* except when we shouldn't, at large magnifications */
+ if (!img->interpolate)
+ {
+ if (sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b) > img->w * 2)
+ dolerp = 0;
+ if (sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d) > img->h * 2)
+ dolerp = 0;
+ }
+
+ rect = fz_unit_rect;
+ fz_irect_from_rect(&bbox, fz_transform_rect(&rect, &local_ctm));
+ fz_intersect_irect(&bbox, scissor);
+
+ x = bbox.x0;
+ if (shape && shape->x > x)
+ x = shape->x;
+ y = bbox.y0;
+ if (shape && shape->y > y)
+ y = shape->y;
+ w = bbox.x1;
+ if (shape && shape->x + shape->w < w)
+ w = shape->x + shape->w;
+ w -= x;
+ h = bbox.y1;
+ if (shape && shape->y + shape->h < h)
+ h = shape->y + shape->h;
+ h -= y;
+ if (w < 0 || h < 0)
+ return;
+
+ /* map from screen space (x,y) to image space (u,v) */
+ fz_pre_scale(&local_ctm, 1.0f / img->w, 1.0f / img->h);
+ fz_invert_matrix(&local_ctm, &local_ctm);
+
+ fa = (int)(local_ctm.a *= 65536.0f);
+ fb = (int)(local_ctm.b *= 65536.0f);
+ fc = (int)(local_ctm.c *= 65536.0f);
+ fd = (int)(local_ctm.d *= 65536.0f);
+ local_ctm.e *= 65536.0f;
+ local_ctm.f *= 65536.0f;
+
+ /* Calculate initial texture positions. Do a half step to start. */
+ /* Bug 693021: Keep calculation in float for as long as possible to
+ * avoid overflow. */
+ u = (int)((local_ctm.a * x) + (local_ctm.c * y) + local_ctm.e + ((local_ctm.a + local_ctm.c) * .5f));
+ v = (int)((local_ctm.b * x) + (local_ctm.d * y) + local_ctm.f + ((local_ctm.b + local_ctm.d) * .5f));
+
+ /* RJW: The following is voodoo. No idea why it works, but it gives
+ * the best match between scaled/unscaled/interpolated/non-interpolated
+ * that we have found. */
+ if (dolerp) {
+ u -= 32768;
+ v -= 32768;
+ if (is_rectilinear)
+ {
+ if (u < 0)
+ u = 0;
+ if (v < 0)
+ v = 0;
+ }
+ }
+
+ dp = dst->samples + (unsigned int)(((y - dst->y) * dst->w + (x - dst->x)) * dst->n);
+ n = dst->n;
+ sp = img->samples;
+ sw = img->w;
+ sh = img->h;
+ if (shape)
+ {
+ hw = shape->w;
+ hp = shape->samples + (unsigned int)(((y - shape->y) * hw) + x - shape->x);
+ }
+ else
+ {
+ hw = 0;
+ hp = NULL;
+ }
+
+ /* TODO: if (fb == 0 && fa == 1) call fz_paint_span */
+
+ if (dst->n == 4 && img->n == 2)
+ {
+ assert(!color);
+ if (dolerp)
+ paintfn = fz_paint_affine_g2rgb_lerp;
+ else
+ paintfn = fz_paint_affine_g2rgb_near;
+ }
+ else
+ {
+ if (dolerp)
+ {
+ if (color)
+ paintfn = fz_paint_affine_color_lerp;
+ else
+ paintfn = fz_paint_affine_lerp;
+ }
+ else
+ {
+ if (color)
+ paintfn = fz_paint_affine_color_near;
+ else
+ paintfn = fz_paint_affine_near;
+ }
+ }
+
+ while (h--)
+ {
+ paintfn(dp, sp, sw, sh, u, v, fa, fb, w, n, alpha, color, hp);
+ dp += dst->w * n;
+ hp += hw;
+ u += fc;
+ v += fd;
+ }
+}
+
+void
+fz_paint_image_with_color(fz_pixmap *dst, const fz_irect *scissor, fz_pixmap *shape, fz_pixmap *img, const fz_matrix *ctm, byte *color)
+{
+ assert(img->n == 1);
+ fz_paint_image_imp(dst, scissor, shape, img, ctm, color, 255);
+}
+
+void
+fz_paint_image(fz_pixmap *dst, const fz_irect *scissor, fz_pixmap *shape, fz_pixmap *img, const fz_matrix *ctm, int alpha)
+{
+ assert(dst->n == img->n || (dst->n == 4 && img->n == 2));
+ fz_paint_image_imp(dst, scissor, shape, img, ctm, NULL, alpha);
+}
diff --git a/source/fitz/draw-blend.c b/source/fitz/draw-blend.c
new file mode 100644
index 00000000..62666fc5
--- /dev/null
+++ b/source/fitz/draw-blend.c
@@ -0,0 +1,636 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+/* PDF 1.4 blend modes. These are slow. */
+
+typedef unsigned char byte;
+
+static const char *fz_blendmode_names[] =
+{
+ "Normal",
+ "Multiply",
+ "Screen",
+ "Overlay",
+ "Darken",
+ "Lighten",
+ "ColorDodge",
+ "ColorBurn",
+ "HardLight",
+ "SoftLight",
+ "Difference",
+ "Exclusion",
+ "Hue",
+ "Saturation",
+ "Color",
+ "Luminosity",
+};
+
+int fz_lookup_blendmode(char *name)
+{
+ int i;
+ for (i = 0; i < nelem(fz_blendmode_names); i++)
+ if (!strcmp(name, fz_blendmode_names[i]))
+ return i;
+ return FZ_BLEND_NORMAL;
+}
+
+char *fz_blendmode_name(int blendmode)
+{
+ if (blendmode >= 0 && blendmode < nelem(fz_blendmode_names))
+ return (char*)fz_blendmode_names[blendmode];
+ return "Normal";
+}
+
+/* Separable blend modes */
+
+static inline int fz_screen_byte(int b, int s)
+{
+ return b + s - fz_mul255(b, s);
+}
+
+static inline int fz_hard_light_byte(int b, int s)
+{
+ int s2 = s << 1;
+ if (s <= 127)
+ return fz_mul255(b, s2);
+ else
+ return fz_screen_byte(b, s2 - 255);
+}
+
+static inline int fz_overlay_byte(int b, int s)
+{
+ return fz_hard_light_byte(s, b); /* note swapped order */
+}
+
+static inline int fz_darken_byte(int b, int s)
+{
+ return fz_mini(b, s);
+}
+
+static inline int fz_lighten_byte(int b, int s)
+{
+ return fz_maxi(b, s);
+}
+
+static inline int fz_color_dodge_byte(int b, int s)
+{
+ s = 255 - s;
+ if (b == 0)
+ return 0;
+ else if (b >= s)
+ return 255;
+ else
+ return (0x1fe * b + s) / (s << 1);
+}
+
+static inline int fz_color_burn_byte(int b, int s)
+{
+ b = 255 - b;
+ if (b == 0)
+ return 255;
+ else if (b >= s)
+ return 0;
+ else
+ return 0xff - (0x1fe * b + s) / (s << 1);
+}
+
+static inline int fz_soft_light_byte(int b, int s)
+{
+ /* review this */
+ if (s < 128) {
+ return b - fz_mul255(fz_mul255((255 - (s<<1)), b), 255 - b);
+ }
+ else {
+ int dbd;
+ if (b < 64)
+ dbd = fz_mul255(fz_mul255((b << 4) - 12, b) + 4, b);
+ else
+ dbd = (int)sqrtf(255.0f * b);
+ return b + fz_mul255(((s<<1) - 255), (dbd - b));
+ }
+}
+
+static inline int fz_difference_byte(int b, int s)
+{
+ return fz_absi(b - s);
+}
+
+static inline int fz_exclusion_byte(int b, int s)
+{
+ return b + s - (fz_mul255(b, s)<<1);
+}
+
+/* Non-separable blend modes */
+
+static void
+fz_luminosity_rgb(unsigned char *rd, unsigned char *gd, unsigned char *bd, int rb, int gb, int bb, int rs, int gs, int bs)
+{
+ int delta, scale;
+ int r, g, b, y;
+
+ /* 0.3, 0.59, 0.11 in fixed point */
+ delta = ((rs - rb) * 77 + (gs - gb) * 151 + (bs - bb) * 28 + 0x80) >> 8;
+ r = rb + delta;
+ g = gb + delta;
+ b = bb + delta;
+
+ if ((r | g | b) & 0x100)
+ {
+ y = (rs * 77 + gs * 151 + bs * 28 + 0x80) >> 8;
+ if (delta > 0)
+ {
+ int max;
+ max = fz_maxi(r, fz_maxi(g, b));
+ scale = (max == y ? 0 : ((255 - y) << 16) / (max - y));
+ }
+ else
+ {
+ int min;
+ min = fz_mini(r, fz_mini(g, b));
+ scale = (y == min ? 0 : (y << 16) / (y - min));
+ }
+ r = y + (((r - y) * scale + 0x8000) >> 16);
+ g = y + (((g - y) * scale + 0x8000) >> 16);
+ b = y + (((b - y) * scale + 0x8000) >> 16);
+ }
+
+ *rd = fz_clampi(r, 0, 255);
+ *gd = fz_clampi(g, 0, 255);
+ *bd = fz_clampi(b, 0, 255);
+}
+
+static void
+fz_saturation_rgb(unsigned char *rd, unsigned char *gd, unsigned char *bd, int rb, int gb, int bb, int rs, int gs, int bs)
+{
+ int minb, maxb;
+ int mins, maxs;
+ int y;
+ int scale;
+ int r, g, b;
+
+ minb = fz_mini(rb, fz_mini(gb, bb));
+ maxb = fz_maxi(rb, fz_maxi(gb, bb));
+ if (minb == maxb)
+ {
+ /* backdrop has zero saturation, avoid divide by 0 */
+ gb = fz_clampi(gb, 0, 255);
+ *rd = gb;
+ *gd = gb;
+ *bd = gb;
+ return;
+ }
+
+ mins = fz_mini(rs, fz_mini(gs, bs));
+ maxs = fz_maxi(rs, fz_maxi(gs, bs));
+
+ scale = ((maxs - mins) << 16) / (maxb - minb);
+ y = (rb * 77 + gb * 151 + bb * 28 + 0x80) >> 8;
+ r = y + ((((rb - y) * scale) + 0x8000) >> 16);
+ g = y + ((((gb - y) * scale) + 0x8000) >> 16);
+ b = y + ((((bb - y) * scale) + 0x8000) >> 16);
+
+ if ((r | g | b) & 0x100)
+ {
+ int scalemin, scalemax;
+ int min, max;
+
+ min = fz_mini(r, fz_mini(g, b));
+ max = fz_maxi(r, fz_maxi(g, b));
+
+ if (min < 0)
+ scalemin = (y << 16) / (y - min);
+ else
+ scalemin = 0x10000;
+
+ if (max > 255)
+ scalemax = ((255 - y) << 16) / (max - y);
+ else
+ scalemax = 0x10000;
+
+ scale = fz_mini(scalemin, scalemax);
+ r = y + (((r - y) * scale + 0x8000) >> 16);
+ g = y + (((g - y) * scale + 0x8000) >> 16);
+ b = y + (((b - y) * scale + 0x8000) >> 16);
+ }
+
+ *rd = fz_clampi(r, 0, 255);
+ *gd = fz_clampi(g, 0, 255);
+ *bd = fz_clampi(b, 0, 255);
+}
+
+static void
+fz_color_rgb(unsigned char *rr, unsigned char *rg, unsigned char *rb, int br, int bg, int bb, int sr, int sg, int sb)
+{
+ fz_luminosity_rgb(rr, rg, rb, sr, sg, sb, br, bg, bb);
+}
+
+static void
+fz_hue_rgb(unsigned char *rr, unsigned char *rg, unsigned char *rb, int br, int bg, int bb, int sr, int sg, int sb)
+{
+ unsigned char tr, tg, tb;
+ fz_luminosity_rgb(&tr, &tg, &tb, sr, sg, sb, br, bg, bb);
+ fz_saturation_rgb(rr, rg, rb, tr, tg, tb, br, bg, bb);
+}
+
+void
+fz_blend_pixel(unsigned char dp[3], unsigned char bp[3], unsigned char sp[3], int blendmode)
+{
+ int k;
+ /* non-separable blend modes */
+ switch (blendmode)
+ {
+ case FZ_BLEND_HUE: fz_hue_rgb(&dp[0], &dp[1], &dp[2], bp[0], bp[1], bp[2], sp[0], sp[1], sp[2]); return;
+ case FZ_BLEND_SATURATION: fz_saturation_rgb(&dp[0], &dp[1], &dp[2], bp[0], bp[1], bp[2], sp[0], sp[1], sp[2]); return;
+ case FZ_BLEND_COLOR: fz_color_rgb(&dp[0], &dp[1], &dp[2], bp[0], bp[1], bp[2], sp[0], sp[1], sp[2]); return;
+ case FZ_BLEND_LUMINOSITY: fz_luminosity_rgb(&dp[0], &dp[1], &dp[2], bp[0], bp[1], bp[2], sp[0], sp[1], sp[2]); return;
+ }
+ /* separable blend modes */
+ for (k = 0; k < 3; k++)
+ {
+ switch (blendmode)
+ {
+ default:
+ case FZ_BLEND_NORMAL: dp[k] = sp[k]; break;
+ case FZ_BLEND_MULTIPLY: dp[k] = fz_mul255(bp[k], sp[k]); break;
+ case FZ_BLEND_SCREEN: dp[k] = fz_screen_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_OVERLAY: dp[k] = fz_overlay_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_DARKEN: dp[k] = fz_darken_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_LIGHTEN: dp[k] = fz_lighten_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_COLOR_DODGE: dp[k] = fz_color_dodge_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_COLOR_BURN: dp[k] = fz_color_burn_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_HARD_LIGHT: dp[k] = fz_hard_light_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_SOFT_LIGHT: dp[k] = fz_soft_light_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_DIFFERENCE: dp[k] = fz_difference_byte(bp[k], sp[k]); break;
+ case FZ_BLEND_EXCLUSION: dp[k] = fz_exclusion_byte(bp[k], sp[k]); break;
+ }
+ }
+}
+
+/* Blending loops */
+
+void
+fz_blend_separable(byte * restrict bp, byte * restrict sp, int n, int w, int blendmode)
+{
+ int k;
+ int n1 = n - 1;
+ while (w--)
+ {
+ int sa = sp[n1];
+ int ba = bp[n1];
+ int saba = fz_mul255(sa, ba);
+
+ /* ugh, division to get non-premul components */
+ int invsa = sa ? 255 * 256 / sa : 0;
+ int invba = ba ? 255 * 256 / ba : 0;
+
+ for (k = 0; k < n1; k++)
+ {
+ int sc = (sp[k] * invsa) >> 8;
+ int bc = (bp[k] * invba) >> 8;
+ int rc;
+
+ switch (blendmode)
+ {
+ default:
+ case FZ_BLEND_NORMAL: rc = sc; break;
+ case FZ_BLEND_MULTIPLY: rc = fz_mul255(bc, sc); break;
+ case FZ_BLEND_SCREEN: rc = fz_screen_byte(bc, sc); break;
+ case FZ_BLEND_OVERLAY: rc = fz_overlay_byte(bc, sc); break;
+ case FZ_BLEND_DARKEN: rc = fz_darken_byte(bc, sc); break;
+ case FZ_BLEND_LIGHTEN: rc = fz_lighten_byte(bc, sc); break;
+ case FZ_BLEND_COLOR_DODGE: rc = fz_color_dodge_byte(bc, sc); break;
+ case FZ_BLEND_COLOR_BURN: rc = fz_color_burn_byte(bc, sc); break;
+ case FZ_BLEND_HARD_LIGHT: rc = fz_hard_light_byte(bc, sc); break;
+ case FZ_BLEND_SOFT_LIGHT: rc = fz_soft_light_byte(bc, sc); break;
+ case FZ_BLEND_DIFFERENCE: rc = fz_difference_byte(bc, sc); break;
+ case FZ_BLEND_EXCLUSION: rc = fz_exclusion_byte(bc, sc); break;
+ }
+
+ bp[k] = fz_mul255(255 - sa, bp[k]) + fz_mul255(255 - ba, sp[k]) + fz_mul255(saba, rc);
+ }
+
+ bp[k] = ba + sa - saba;
+
+ sp += n;
+ bp += n;
+ }
+}
+
+void
+fz_blend_nonseparable(byte * restrict bp, byte * restrict sp, int w, int blendmode)
+{
+ while (w--)
+ {
+ unsigned char rr, rg, rb;
+
+ int sa = sp[3];
+ int ba = bp[3];
+ int saba = fz_mul255(sa, ba);
+
+ /* ugh, division to get non-premul components */
+ int invsa = sa ? 255 * 256 / sa : 0;
+ int invba = ba ? 255 * 256 / ba : 0;
+
+ int sr = (sp[0] * invsa) >> 8;
+ int sg = (sp[1] * invsa) >> 8;
+ int sb = (sp[2] * invsa) >> 8;
+
+ int br = (bp[0] * invba) >> 8;
+ int bg = (bp[1] * invba) >> 8;
+ int bb = (bp[2] * invba) >> 8;
+
+ switch (blendmode)
+ {
+ default:
+ case FZ_BLEND_HUE:
+ fz_hue_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ case FZ_BLEND_SATURATION:
+ fz_saturation_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ case FZ_BLEND_COLOR:
+ fz_color_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ case FZ_BLEND_LUMINOSITY:
+ fz_luminosity_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ }
+
+ bp[0] = fz_mul255(255 - sa, bp[0]) + fz_mul255(255 - ba, sp[0]) + fz_mul255(saba, rr);
+ bp[1] = fz_mul255(255 - sa, bp[1]) + fz_mul255(255 - ba, sp[1]) + fz_mul255(saba, rg);
+ bp[2] = fz_mul255(255 - sa, bp[2]) + fz_mul255(255 - ba, sp[2]) + fz_mul255(saba, rb);
+ bp[3] = ba + sa - saba;
+
+ sp += 4;
+ bp += 4;
+ }
+}
+
+static void
+fz_blend_separable_nonisolated(byte * restrict bp, byte * restrict sp, int n, int w, int blendmode, byte * restrict hp, int alpha)
+{
+ int k;
+ int n1 = n - 1;
+
+ if (alpha == 255 && blendmode == 0)
+ {
+ /* In this case, the uncompositing and the recompositing
+ * cancel one another out, and it's just a simple copy. */
+ /* FIXME: Maybe we can avoid using the shape plane entirely
+ * and just copy? */
+ while (w--)
+ {
+ int ha = fz_mul255(*hp++, alpha); /* ha = shape_alpha */
+ /* If ha == 0 then leave everything unchanged */
+ if (ha != 0)
+ {
+ for (k = 0; k < n; k++)
+ {
+ bp[k] = sp[k];
+ }
+ }
+
+ sp += n;
+ bp += n;
+ }
+ return;
+ }
+ while (w--)
+ {
+ int ha = *hp++;
+ int haa = fz_mul255(ha, alpha); /* ha = shape_alpha */
+ /* If haa == 0 then leave everything unchanged */
+ while (haa != 0) /* Use while, so we can break out */
+ {
+ int sa, ba, bahaa, ra, invsa, invba, invha, invra;
+ sa = sp[n1];
+ if (sa == 0)
+ break; /* No change! */
+ invsa = sa ? 255 * 256 / sa : 0;
+ ba = bp[n1];
+ if (ba == 0)
+ {
+ /* Just copy pixels (allowing for change in
+ * premultiplied alphas) */
+ for (k = 0; k < n1; k++)
+ {
+ bp[k] = fz_mul255((sp[k] * invsa) >> 8, haa);
+ }
+ bp[n1] = haa;
+ break;
+ }
+ bahaa = fz_mul255(ba, haa);
+
+ /* ugh, division to get non-premul components */
+ invba = ba ? 255 * 256 / ba : 0;
+
+ /* Calculate result_alpha - a combination of the
+ * background alpha, and 'shape' */
+ ra = bp[n1] = ba - bahaa + haa;
+ if (ra == 0)
+ break;
+ /* Because we are a non-isolated group, we need to
+ * 'uncomposite' before we blend (recomposite).
+ * We assume that normal blending has been done inside
+ * the group, so: rc = (1-ha).bc + ha.sc
+ * A bit of rearrangement, and that gives us that:
+ * sc = (rc - bc)/ha + bc
+ * Now, the result of the blend (rc) was stored in src, so
+ * we actually want to calculate:
+ * sc = (sc-bc)/ha + bc
+ */
+ invha = ha ? 255 * 256 / ha : 0;
+ invra = ra ? 255 * 256 / ra : 0;
+
+ /* sa = the final alpha to blend with - this
+ * is calculated from the shape + alpha,
+ * divided by ra. */
+ sa = (haa*invra + 128)>>8;
+ if (sa < 0) sa = 0;
+ if (sa > 255) sa = 255;
+
+ for (k = 0; k < n1; k++)
+ {
+ /* Read pixels (and convert to non-premultiplied form) */
+ int sc = (sp[k] * invsa + 128) >> 8;
+ int bc = (bp[k] * invba + 128) >> 8;
+ int rc;
+
+ /* Uncomposite (see above) */
+ sc = (((sc-bc) * invha + 128)>>8) + bc;
+ if (sc < 0) sc = 0;
+ if (sc > 255) sc = 255;
+
+ switch (blendmode)
+ {
+ default:
+ case FZ_BLEND_NORMAL: rc = sc; break;
+ case FZ_BLEND_MULTIPLY: rc = fz_mul255(bc, sc); break;
+ case FZ_BLEND_SCREEN: rc = fz_screen_byte(bc, sc); break;
+ case FZ_BLEND_OVERLAY: rc = fz_overlay_byte(bc, sc); break;
+ case FZ_BLEND_DARKEN: rc = fz_darken_byte(bc, sc); break;
+ case FZ_BLEND_LIGHTEN: rc = fz_lighten_byte(bc, sc); break;
+ case FZ_BLEND_COLOR_DODGE: rc = fz_color_dodge_byte(bc, sc); break;
+ case FZ_BLEND_COLOR_BURN: rc = fz_color_burn_byte(bc, sc); break;
+ case FZ_BLEND_HARD_LIGHT: rc = fz_hard_light_byte(bc, sc); break;
+ case FZ_BLEND_SOFT_LIGHT: rc = fz_soft_light_byte(bc, sc); break;
+ case FZ_BLEND_DIFFERENCE: rc = fz_difference_byte(bc, sc); break;
+ case FZ_BLEND_EXCLUSION: rc = fz_exclusion_byte(bc, sc); break;
+ }
+ /* Composition formula, as given in pdf_reference17.pdf:
+ * rc = ( 1 - (ha/ra)) * bc + (ha/ra) * ((1-ba)*sc + ba * rc)
+ */
+ rc = bc + fz_mul255(sa, fz_mul255(255 - ba, sc) + fz_mul255(ba, rc) - bc);
+ if (rc < 0) rc = 0;
+ if (rc > 255) rc = 255;
+ bp[k] = fz_mul255(rc, ra);
+ }
+ break;
+ }
+
+ sp += n;
+ bp += n;
+ }
+}
+
+static void
+fz_blend_nonseparable_nonisolated(byte * restrict bp, byte * restrict sp, int w, int blendmode, byte * restrict hp, int alpha)
+{
+ while (w--)
+ {
+ int ha = *hp++;
+ int haa = fz_mul255(ha, alpha);
+ if (haa != 0)
+ {
+ int sa = sp[3];
+ int ba = bp[3];
+ int baha = fz_mul255(ba, haa);
+
+ /* Calculate result_alpha */
+ int ra = bp[3] = ba - baha + haa;
+ if (ra != 0)
+ {
+ /* Because we are a non-isolated group, we
+ * need to 'uncomposite' before we blend
+ * (recomposite). We assume that normal
+ * blending has been done inside the group,
+ * so: ra.rc = (1-ha).bc + ha.sc
+ * A bit of rearrangement, and that gives us
+ * that: sc = (ra.rc - bc)/ha + bc
+ * Now, the result of the blend was stored in
+ * src, so: */
+ int invha = ha ? 255 * 256 / ha : 0;
+
+ unsigned char rr, rg, rb;
+
+ /* ugh, division to get non-premul components */
+ int invsa = sa ? 255 * 256 / sa : 0;
+ int invba = ba ? 255 * 256 / ba : 0;
+
+ int sr = (sp[0] * invsa) >> 8;
+ int sg = (sp[1] * invsa) >> 8;
+ int sb = (sp[2] * invsa) >> 8;
+
+ int br = (bp[0] * invba) >> 8;
+ int bg = (bp[1] * invba) >> 8;
+ int bb = (bp[2] * invba) >> 8;
+
+ /* Uncomposite */
+ sr = (((sr-br)*invha)>>8) + br;
+ sg = (((sg-bg)*invha)>>8) + bg;
+ sb = (((sb-bb)*invha)>>8) + bb;
+
+ switch (blendmode)
+ {
+ default:
+ case FZ_BLEND_HUE:
+ fz_hue_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ case FZ_BLEND_SATURATION:
+ fz_saturation_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ case FZ_BLEND_COLOR:
+ fz_color_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ case FZ_BLEND_LUMINOSITY:
+ fz_luminosity_rgb(&rr, &rg, &rb, br, bg, bb, sr, sg, sb);
+ break;
+ }
+
+ rr = fz_mul255(255 - haa, bp[0]) + fz_mul255(fz_mul255(255 - ba, sr), haa) + fz_mul255(baha, rr);
+ rg = fz_mul255(255 - haa, bp[1]) + fz_mul255(fz_mul255(255 - ba, sg), haa) + fz_mul255(baha, rg);
+ rb = fz_mul255(255 - haa, bp[2]) + fz_mul255(fz_mul255(255 - ba, sb), haa) + fz_mul255(baha, rb);
+ bp[0] = fz_mul255(ra, rr);
+ bp[1] = fz_mul255(ra, rg);
+ bp[2] = fz_mul255(ra, rb);
+ }
+ }
+
+ sp += 4;
+ bp += 4;
+ }
+}
+
+void
+fz_blend_pixmap(fz_pixmap *dst, fz_pixmap *src, int alpha, int blendmode, int isolated, fz_pixmap *shape)
+{
+ unsigned char *sp, *dp;
+ fz_irect bbox;
+ fz_irect bbox2;
+ int x, y, w, h, n;
+
+ /* TODO: fix this hack! */
+ if (isolated && alpha < 255)
+ {
+ sp = src->samples;
+ n = src->w * src->h * src->n;
+ while (n--)
+ {
+ *sp = fz_mul255(*sp, alpha);
+ sp++;
+ }
+ }
+
+ fz_pixmap_bbox_no_ctx(dst, &bbox);
+ fz_pixmap_bbox_no_ctx(src, &bbox2);
+ fz_intersect_irect(&bbox, &bbox2);
+
+ x = bbox.x0;
+ y = bbox.y0;
+ w = bbox.x1 - bbox.x0;
+ h = bbox.y1 - bbox.y0;
+
+ n = src->n;
+ sp = src->samples + (unsigned int)(((y - src->y) * src->w + (x - src->x)) * n);
+ dp = dst->samples + (unsigned int)(((y - dst->y) * dst->w + (x - dst->x)) * n);
+
+ assert(src->n == dst->n);
+
+ if (!isolated)
+ {
+ unsigned char *hp = shape->samples + (unsigned int)((y - shape->y) * shape->w + (x - shape->x));
+
+ while (h--)
+ {
+ if (n == 4 && blendmode >= FZ_BLEND_HUE)
+ fz_blend_nonseparable_nonisolated(dp, sp, w, blendmode, hp, alpha);
+ else
+ fz_blend_separable_nonisolated(dp, sp, n, w, blendmode, hp, alpha);
+ sp += src->w * n;
+ dp += dst->w * n;
+ hp += shape->w;
+ }
+ }
+ else
+ {
+ while (h--)
+ {
+ if (n == 4 && blendmode >= FZ_BLEND_HUE)
+ fz_blend_nonseparable(dp, sp, w, blendmode);
+ else
+ fz_blend_separable(dp, sp, n, w, blendmode);
+ sp += src->w * n;
+ dp += dst->w * n;
+ }
+ }
+}
diff --git a/source/fitz/draw-device.c b/source/fitz/draw-device.c
new file mode 100644
index 00000000..e993daf7
--- /dev/null
+++ b/source/fitz/draw-device.c
@@ -0,0 +1,2126 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+#define QUANT(x,a) (((int)((x) * (a))) / (a))
+#define HSUBPIX 5.0
+#define VSUBPIX 5.0
+
+#define STACK_SIZE 96
+
+/* Enable the following to attempt to support knockout and/or isolated
+ * blending groups. */
+#define ATTEMPT_KNOCKOUT_AND_ISOLATED
+
+/* Enable the following to help debug group blending. */
+#undef DUMP_GROUP_BLENDS
+
+typedef struct fz_draw_device_s fz_draw_device;
+
+enum {
+ FZ_DRAWDEV_FLAGS_TYPE3 = 1,
+};
+
+typedef struct fz_draw_state_s fz_draw_state;
+
+struct fz_draw_state_s {
+ fz_irect scissor;
+ fz_pixmap *dest;
+ fz_pixmap *mask;
+ fz_pixmap *shape;
+ int blendmode;
+ int luminosity;
+ int id;
+ float alpha;
+ fz_matrix ctm;
+ float xstep, ystep;
+ fz_irect area;
+};
+
+struct fz_draw_device_s
+{
+ fz_gel *gel;
+ fz_context *ctx;
+ int flags;
+ int top;
+ fz_scale_cache *cache_x;
+ fz_scale_cache *cache_y;
+ fz_draw_state *stack;
+ int stack_max;
+ fz_draw_state init_stack[STACK_SIZE];
+};
+
+#ifdef DUMP_GROUP_BLENDS
+static int group_dump_count = 0;
+
+static void fz_dump_blend(fz_context *ctx, fz_pixmap *pix, const char *s)
+{
+ char name[80];
+
+ if (!pix)
+ return;
+
+ sprintf(name, "dump%02d.png", group_dump_count);
+ if (s)
+ printf("%s%02d", s, group_dump_count);
+ group_dump_count++;
+
+ fz_write_png(ctx, pix, name, (pix->n > 1));
+}
+
+static void dump_spaces(int x, const char *s)
+{
+ int i;
+ for (i = 0; i < x; i++)
+ printf(" ");
+ printf("%s", s);
+}
+
+#endif
+
+static void fz_grow_stack(fz_draw_device *dev)
+{
+ int max = dev->stack_max * 2;
+ fz_draw_state *stack;
+
+ if (dev->stack == &dev->init_stack[0])
+ {
+ stack = fz_malloc(dev->ctx, sizeof(*stack) * max);
+ memcpy(stack, dev->stack, sizeof(*stack) * dev->stack_max);
+ }
+ else
+ {
+ stack = fz_resize_array(dev->ctx, dev->stack, max, sizeof(*stack));
+ }
+ dev->stack = stack;
+ dev->stack_max = max;
+}
+
+/* 'Push' the stack. Returns a pointer to the current state, with state[1]
+ * already having been initialised to contain the same thing. Simply
+ * change any contents of state[1] that you want to and continue. */
+static fz_draw_state *
+push_stack(fz_draw_device *dev)
+{
+ fz_draw_state *state;
+
+ if (dev->top == dev->stack_max-1)
+ fz_grow_stack(dev);
+ state = &dev->stack[dev->top];
+ dev->top++;
+ memcpy(&state[1], state, sizeof(*state));
+ return state;
+}
+
+static void emergency_pop_stack(fz_draw_device *dev, fz_draw_state *state)
+{
+ fz_context *ctx = dev->ctx;
+
+ if (state[1].mask != state[0].mask)
+ fz_drop_pixmap(ctx, state[1].mask);
+ if (state[1].dest != state[0].dest)
+ fz_drop_pixmap(ctx, state[1].dest);
+ if (state[1].shape != state[0].shape)
+ fz_drop_pixmap(ctx, state[1].shape);
+ dev->top--;
+ fz_rethrow(ctx);
+}
+
+static fz_draw_state *
+fz_knockout_begin(fz_draw_device *dev)
+{
+ fz_context *ctx = dev->ctx;
+ fz_irect bbox;
+ fz_pixmap *dest, *shape;
+ fz_draw_state *state = &dev->stack[dev->top];
+ int isolated = state->blendmode & FZ_BLEND_ISOLATED;
+
+ if ((state->blendmode & FZ_BLEND_KNOCKOUT) == 0)
+ return state;
+
+ state = push_stack(dev);
+
+ fz_pixmap_bbox(dev->ctx, state->dest, &bbox);
+ fz_intersect_irect(&bbox, &state->scissor);
+ dest = fz_new_pixmap_with_bbox(dev->ctx, state->dest->colorspace, &bbox);
+
+ if (isolated)
+ {
+ fz_clear_pixmap(ctx, dest);
+ }
+ else
+ {
+ /* Find the last but one destination to copy */
+ int i = dev->top-1; /* i = the one on entry (i.e. the last one) */
+ fz_pixmap *prev = state->dest;
+ while (i > 0)
+ {
+ prev = dev->stack[--i].dest;
+ if (prev != state->dest)
+ break;
+ }
+ if (prev)
+ fz_copy_pixmap_rect(ctx, dest, prev, &bbox);
+ else
+ fz_clear_pixmap(ctx, dest);
+ }
+
+ if (state->blendmode == 0 && isolated)
+ {
+ /* We can render direct to any existing shape plane. If there
+ * isn't one, we don't need to make one. */
+ shape = state->shape;
+ }
+ else
+ {
+ shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Knockout begin\n");
+#endif
+ state[1].scissor = bbox;
+ state[1].dest = dest;
+ state[1].shape = shape;
+ state[1].blendmode &= ~FZ_BLEND_MODEMASK;
+
+ return &state[1];
+}
+
+static void fz_knockout_end(fz_draw_device *dev)
+{
+ fz_draw_state *state;
+ int blendmode;
+ int isolated;
+ fz_context *ctx = dev->ctx;
+
+ if (dev->top == 0)
+ {
+ fz_warn(ctx, "unexpected knockout end");
+ return;
+ }
+ state = &dev->stack[--dev->top];
+ if ((state[0].blendmode & FZ_BLEND_KNOCKOUT) == 0)
+ return;
+
+ blendmode = state->blendmode & FZ_BLEND_MODEMASK;
+ isolated = state->blendmode & FZ_BLEND_ISOLATED;
+
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top, "");
+ fz_dump_blend(dev->ctx, state[1].dest, "Knockout end: blending ");
+ if (state[1].shape)
+ fz_dump_blend(dev->ctx, state[1].shape, "/");
+ fz_dump_blend(dev->ctx, state[0].dest, " onto ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ if (blendmode != 0)
+ printf(" (blend %d)", blendmode);
+ if (isolated != 0)
+ printf(" (isolated)");
+ printf(" (knockout)");
+#endif
+ if ((blendmode == 0) && (state[0].shape == state[1].shape))
+ fz_paint_pixmap(state[0].dest, state[1].dest, 255);
+ else
+ fz_blend_pixmap(state[0].dest, state[1].dest, 255, blendmode, isolated, state[1].shape);
+
+ fz_drop_pixmap(dev->ctx, state[1].dest);
+ if (state[0].shape != state[1].shape)
+ {
+ if (state[0].shape)
+ fz_paint_pixmap(state[0].shape, state[1].shape, 255);
+ fz_drop_pixmap(dev->ctx, state[1].shape);
+ }
+#ifdef DUMP_GROUP_BLENDS
+ fz_dump_blend(dev->ctx, state[0].dest, " to get ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ printf("\n");
+#endif
+}
+
+static void
+fz_draw_fill_path(fz_device *devp, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_irect bbox;
+ int i;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+
+ if (model == NULL)
+ model = fz_device_gray(dev->ctx);
+
+ fz_reset_gel(dev->gel, &state->scissor);
+ fz_flatten_fill_path(dev->gel, path, ctm, flatness);
+ fz_sort_gel(dev->gel);
+
+ fz_intersect_irect(fz_bound_gel(dev->gel, &bbox), &state->scissor);
+
+ if (fz_is_empty_irect(&bbox))
+ return;
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ fz_convert_color(dev->ctx, model, colorfv, colorspace, color);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ fz_scan_convert(dev->gel, even_odd, &bbox, state->dest, colorbv);
+ if (state->shape)
+ {
+ fz_reset_gel(dev->gel, &state->scissor);
+ fz_flatten_fill_path(dev->gel, path, ctm, flatness);
+ fz_sort_gel(dev->gel);
+
+ colorbv[0] = alpha * 255;
+ fz_scan_convert(dev->gel, even_odd, &bbox, state->shape, colorbv);
+ }
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+static void
+fz_draw_stroke_path(fz_device *devp, fz_path *path, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ float linewidth = stroke->linewidth;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_irect bbox;
+ int i;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+
+ if (model == NULL)
+ model = fz_device_gray(dev->ctx);
+
+ if (linewidth * expansion < 0.1f)
+ linewidth = 1 / expansion;
+
+ fz_reset_gel(dev->gel, &state->scissor);
+ if (stroke->dash_len > 0)
+ fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ else
+ fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ fz_sort_gel(dev->gel);
+
+ fz_intersect_irect(fz_bound_gel(dev->gel, &bbox), &state->scissor);
+
+ if (fz_is_empty_irect(&bbox))
+ return;
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ fz_convert_color(dev->ctx, model, colorfv, colorspace, color);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ fz_scan_convert(dev->gel, 0, &bbox, state->dest, colorbv);
+ if (state->shape)
+ {
+ fz_reset_gel(dev->gel, &state->scissor);
+ if (stroke->dash_len > 0)
+ fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ else
+ fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ fz_sort_gel(dev->gel);
+
+ colorbv[0] = 255;
+ fz_scan_convert(dev->gel, 0, &bbox, state->shape, colorbv);
+ }
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+static void
+fz_draw_clip_path(fz_device *devp, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ fz_draw_device *dev = devp->user;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ fz_irect bbox;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model;
+ fz_context *ctx = dev->ctx;
+
+ fz_reset_gel(dev->gel, &state->scissor);
+ fz_flatten_fill_path(dev->gel, path, ctm, flatness);
+ fz_sort_gel(dev->gel);
+
+ state = push_stack(dev);
+ model = state->dest->colorspace;
+
+ fz_intersect_irect(fz_bound_gel(dev->gel, &bbox), &state->scissor);
+ if (rect)
+ {
+ fz_irect bbox2;
+ fz_intersect_irect(&bbox, fz_irect_from_rect(&bbox2, rect));
+ }
+
+ if (fz_is_empty_irect(&bbox) || fz_is_rect_gel(dev->gel))
+ {
+ state[1].scissor = bbox;
+ state[1].mask = NULL;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (rectangular) begin\n");
+#endif
+ return;
+ }
+
+ fz_try(ctx)
+ {
+ state[1].mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].mask);
+ state[1].dest = fz_new_pixmap_with_bbox(dev->ctx, model, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].dest);
+ if (state[1].shape)
+ {
+ state[1].shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].shape);
+ }
+
+ fz_scan_convert(dev->gel, even_odd, &bbox, state[1].mask, NULL);
+
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].scissor = bbox;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (non-rectangular) begin\n");
+#endif
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+}
+
+static void
+fz_draw_clip_stroke_path(fz_device *devp, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_draw_device *dev = devp->user;
+ float expansion = fz_matrix_expansion(ctm);
+ float flatness = 0.3f / expansion;
+ float linewidth = stroke->linewidth;
+ fz_irect bbox;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model;
+ fz_context *ctx = dev->ctx;
+
+ if (linewidth * expansion < 0.1f)
+ linewidth = 1 / expansion;
+
+ fz_reset_gel(dev->gel, &state->scissor);
+ if (stroke->dash_len > 0)
+ fz_flatten_dash_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ else
+ fz_flatten_stroke_path(dev->gel, path, stroke, ctm, flatness, linewidth);
+ fz_sort_gel(dev->gel);
+
+ state = push_stack(dev);
+ model = state->dest->colorspace;
+
+ fz_intersect_irect(fz_bound_gel(dev->gel, &bbox), &state->scissor);
+ if (rect)
+ {
+ fz_irect bbox2;
+ fz_intersect_irect(&bbox, fz_irect_from_rect(&bbox2, rect));
+ }
+
+ fz_try(ctx)
+ {
+ state[1].mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].mask);
+ state[1].dest = fz_new_pixmap_with_bbox(dev->ctx, model, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].dest);
+ if (state->shape)
+ {
+ state[1].shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].shape);
+ }
+
+ if (!fz_is_empty_irect(&bbox))
+ fz_scan_convert(dev->gel, 0, &bbox, state[1].mask, NULL);
+
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].scissor = bbox;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (stroke) begin\n");
+#endif
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+}
+
+static void
+draw_glyph(unsigned char *colorbv, fz_pixmap *dst, fz_pixmap *msk,
+ int xorig, int yorig, const fz_irect *scissor)
+{
+ unsigned char *dp, *mp;
+ fz_irect bbox;
+ int x, y, w, h;
+
+ fz_pixmap_bbox_no_ctx(msk, &bbox);
+ fz_translate_irect(&bbox, xorig, yorig);
+ fz_intersect_irect(&bbox, scissor); /* scissor < dst */
+ x = bbox.x0;
+ y = bbox.y0;
+ w = bbox.x1 - bbox.x0;
+ h = bbox.y1 - bbox.y0;
+
+ mp = msk->samples + (unsigned int)((y - msk->y - yorig) * msk->w + (x - msk->x - xorig));
+ dp = dst->samples + (unsigned int)(((y - dst->y) * dst->w + (x - dst->x)) * dst->n);
+
+ assert(msk->n == 1);
+
+ while (h--)
+ {
+ if (dst->colorspace)
+ fz_paint_span_with_color(dp, mp, dst->n, w, colorbv);
+ else
+ fz_paint_span(dp, mp, 1, w, 255);
+ dp += dst->w * dst->n;
+ mp += msk->w;
+ }
+}
+
+static void
+fz_draw_fill_text(fz_device *devp, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ unsigned char shapebv;
+ float colorfv[FZ_MAX_COLORS];
+ fz_matrix tm, trm, trunc_trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+ fz_irect scissor;
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ fz_convert_color(dev->ctx, model, colorfv, colorspace, color);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+ shapebv = 255;
+
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ fz_concat(&trm, &tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+
+ trunc_trm = trm;
+ trunc_trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trunc_trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ scissor.x0 = state->scissor.x0 - x;
+ scissor.y0 = state->scissor.y0 - y;
+ scissor.x1 = state->scissor.x1 - x;
+ scissor.y1 = state->scissor.y1 - y;
+
+ glyph = fz_render_glyph(dev->ctx, text->font, gid, &trunc_trm, model, scissor);
+ if (glyph)
+ {
+ if (glyph->n == 1)
+ {
+ draw_glyph(colorbv, state->dest, glyph, x, y, &state->scissor);
+ if (state->shape)
+ draw_glyph(&shapebv, state->shape, glyph, x, y, &state->scissor);
+ }
+ else
+ {
+ fz_matrix mat = {glyph->w, 0.0, 0.0, glyph->h, x + glyph->x, y + glyph->y};
+ fz_paint_image(state->dest, &state->scissor, state->shape, glyph, &mat, alpha * 255);
+ }
+ fz_drop_pixmap(dev->ctx, glyph);
+ }
+ else
+ {
+ fz_path *path = fz_outline_glyph(dev->ctx, text->font, gid, &trm);
+ if (path)
+ {
+ fz_draw_fill_path(devp, path, 0, &fz_identity, colorspace, color, alpha);
+ fz_free_path(dev->ctx, path);
+ }
+ else
+ {
+ fz_warn(dev->ctx, "cannot render glyph");
+ }
+ }
+ }
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+static void
+fz_draw_stroke_text(fz_device *devp, fz_text *text, fz_stroke_state *stroke,
+ const fz_matrix *ctm, fz_colorspace *colorspace,
+ float *color, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_matrix tm, trm, trunc_trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+ fz_irect scissor;
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ fz_convert_color(dev->ctx, model, colorfv, colorspace, color);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ fz_concat(&trm, &tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+
+ trunc_trm = trm;
+ trunc_trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trunc_trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ scissor.x0 = state->scissor.x0 - x;
+ scissor.y0 = state->scissor.y0 - y;
+ scissor.x1 = state->scissor.x1 - x;
+ scissor.y1 = state->scissor.y1 - y;
+
+ glyph = fz_render_stroked_glyph(dev->ctx, text->font, gid, &trunc_trm, ctm, stroke, scissor);
+ if (glyph)
+ {
+ draw_glyph(colorbv, state->dest, glyph, x, y, &state->scissor);
+ if (state->shape)
+ draw_glyph(colorbv, state->shape, glyph, x, y, &state->scissor);
+ fz_drop_pixmap(dev->ctx, glyph);
+ }
+ else
+ {
+ fz_path *path = fz_outline_glyph(dev->ctx, text->font, gid, &trm);
+ if (path)
+ {
+ fz_draw_stroke_path(devp, path, stroke, &fz_identity, colorspace, color, alpha);
+ fz_free_path(dev->ctx, path);
+ }
+ else
+ {
+ fz_warn(dev->ctx, "cannot render glyph");
+ }
+ }
+ }
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+static void
+fz_draw_clip_text(fz_device *devp, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ fz_draw_device *dev = devp->user;
+ fz_context *ctx = dev->ctx;
+ fz_irect bbox;
+ fz_pixmap *mask, *dest, *shape;
+ fz_matrix tm, trm, trunc_trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+ fz_draw_state *state;
+ fz_colorspace *model;
+
+ /* If accumulate == 0 then this text object is guaranteed complete */
+ /* If accumulate == 1 then this text object is the first (or only) in a sequence */
+ /* If accumulate == 2 then this text object is a continuation */
+
+ state = push_stack(dev);
+ model = state->dest->colorspace;
+
+ if (accumulate == 0)
+ {
+ /* make the mask the exact size needed */
+ fz_rect rect;
+
+ fz_irect_from_rect(&bbox, fz_bound_text(dev->ctx, text, NULL, ctm, &rect));
+ fz_intersect_irect(&bbox, &state->scissor);
+ }
+ else
+ {
+ /* be conservative about the size of the mask needed */
+ bbox = state->scissor;
+ }
+
+ fz_try(ctx)
+ {
+ if (accumulate == 0 || accumulate == 1)
+ {
+ mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, mask);
+ dest = fz_new_pixmap_with_bbox(dev->ctx, model, &bbox);
+ fz_clear_pixmap(dev->ctx, dest);
+ if (state->shape)
+ {
+ shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+ else
+ shape = NULL;
+
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].scissor = bbox;
+ state[1].dest = dest;
+ state[1].mask = mask;
+ state[1].shape = shape;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (text) begin\n");
+#endif
+ }
+ else
+ {
+ mask = state->mask;
+ dev->top--;
+ }
+
+ if (!fz_is_empty_irect(&bbox) && mask)
+ {
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ fz_irect scissor;
+
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ fz_concat(&trm, &tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+
+ trunc_trm = trm;
+ trunc_trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trunc_trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+ scissor.x0 = bbox.x0 - x;
+ scissor.y0 = bbox.y0 - y;
+ scissor.x1 = bbox.x1 - x;
+ scissor.y1 = bbox.y1 - y;
+
+ glyph = fz_render_glyph(dev->ctx, text->font, gid, &trunc_trm, model, scissor);
+ if (glyph)
+ {
+ draw_glyph(NULL, mask, glyph, x, y, &bbox);
+ if (state[1].shape)
+ draw_glyph(NULL, state[1].shape, glyph, x, y, &bbox);
+ fz_drop_pixmap(dev->ctx, glyph);
+ }
+ else
+ {
+ fz_path *path = fz_outline_glyph(dev->ctx, text->font, gid, &trm);
+ if (path)
+ {
+ fz_pixmap *old_dest;
+ float white = 1;
+
+ state = &dev->stack[dev->top];
+ old_dest = state[0].dest;
+ state[0].dest = state[0].mask;
+ state[0].mask = NULL;
+ fz_try(ctx)
+ {
+ fz_draw_fill_path(devp, path, 0, &fz_identity, fz_device_gray(ctx), &white, 1);
+ }
+ fz_always(ctx)
+ {
+ state[0].mask = state[0].dest;
+ state[0].dest = old_dest;
+ fz_free_path(dev->ctx, path);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ else
+ {
+ fz_warn(dev->ctx, "cannot render glyph for clipping");
+ }
+ }
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ if (accumulate == 0 || accumulate == 1)
+ emergency_pop_stack(dev, state);
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+fz_draw_clip_stroke_text(fz_device *devp, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_draw_device *dev = devp->user;
+ fz_context *ctx = dev->ctx;
+ fz_irect bbox;
+ fz_pixmap *mask, *dest, *shape;
+ fz_matrix tm, trm, trunc_trm;
+ fz_pixmap *glyph;
+ int i, x, y, gid;
+ fz_draw_state *state = push_stack(dev);
+ fz_colorspace *model = state->dest->colorspace;
+ fz_rect rect;
+
+ /* make the mask the exact size needed */
+ fz_irect_from_rect(&bbox, fz_bound_text(dev->ctx, text, stroke, ctm, &rect));
+ fz_intersect_irect(&bbox, &state->scissor);
+
+ fz_try(ctx)
+ {
+ state[1].mask = mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, mask);
+ state[1].dest = dest = fz_new_pixmap_with_bbox(dev->ctx, model, &bbox);
+ fz_clear_pixmap(dev->ctx, dest);
+ if (state->shape)
+ {
+ state[1].shape = shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+ else
+ shape = state->shape;
+
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].scissor = bbox;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (stroke text) begin\n");
+#endif
+
+ if (!fz_is_empty_irect(&bbox))
+ {
+ tm = text->trm;
+
+ for (i = 0; i < text->len; i++)
+ {
+ fz_irect scissor;
+ gid = text->items[i].gid;
+ if (gid < 0)
+ continue;
+
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ fz_concat(&trm, &tm, ctm);
+ x = floorf(trm.e);
+ y = floorf(trm.f);
+
+ trunc_trm = trm;
+ trunc_trm.e = QUANT(trm.e - floorf(trm.e), HSUBPIX);
+ trunc_trm.f = QUANT(trm.f - floorf(trm.f), VSUBPIX);
+
+ scissor.x0 = bbox.x0 - x;
+ scissor.y0 = bbox.y0 - y;
+ scissor.x1 = bbox.x1 - x;
+ scissor.y1 = bbox.y1 - y;
+
+ glyph = fz_render_stroked_glyph(dev->ctx, text->font, gid, &trunc_trm, ctm, stroke, scissor);
+ if (glyph)
+ {
+ draw_glyph(NULL, mask, glyph, x, y, &bbox);
+ if (shape)
+ draw_glyph(NULL, shape, glyph, x, y, &bbox);
+ fz_drop_pixmap(dev->ctx, glyph);
+ }
+ else
+ {
+ fz_path *path = fz_outline_glyph(dev->ctx, text->font, gid, &trm);
+ if (path)
+ {
+ fz_pixmap *old_dest;
+ float white = 1;
+
+ state = &dev->stack[dev->top];
+ old_dest = state[0].dest;
+ state[0].dest = state[0].mask;
+ state[0].mask = NULL;
+ fz_try(ctx)
+ {
+ fz_draw_stroke_path(devp, path, stroke, &fz_identity, fz_device_gray(ctx), &white, 1);
+ }
+ fz_always(ctx)
+ {
+ state[0].mask = state[0].dest;
+ state[0].dest = old_dest;
+ fz_free_path(dev->ctx, path);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ else
+ {
+ fz_warn(dev->ctx, "cannot render glyph for stroked clipping");
+ }
+ }
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+}
+
+static void
+fz_draw_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+}
+
+static void
+fz_draw_fill_shade(fz_device *devp, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ fz_rect bounds;
+ fz_irect bbox, scissor;
+ fz_pixmap *dest, *shape;
+ float colorfv[FZ_MAX_COLORS];
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+
+ fz_bound_shade(dev->ctx, shade, ctm, &bounds);
+ scissor = state->scissor;
+ fz_intersect_irect(fz_irect_from_rect(&bbox, &bounds), &scissor);
+
+ if (fz_is_empty_irect(&bbox))
+ return;
+
+ if (!model)
+ {
+ fz_warn(dev->ctx, "cannot render shading directly to an alpha mask");
+ return;
+ }
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ dest = state->dest;
+ shape = state->shape;
+
+ if (alpha < 1)
+ {
+ dest = fz_new_pixmap_with_bbox(dev->ctx, state->dest->colorspace, &bbox);
+ fz_clear_pixmap(dev->ctx, dest);
+ if (shape)
+ {
+ shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+ }
+
+ if (shade->use_background)
+ {
+ unsigned char *s;
+ int x, y, n, i;
+ fz_convert_color(dev->ctx, model, colorfv, shade->colorspace, shade->background);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = 255;
+
+ n = dest->n;
+ for (y = scissor.y0; y < scissor.y1; y++)
+ {
+ s = dest->samples + (unsigned int)(((scissor.x0 - dest->x) + (y - dest->y) * dest->w) * dest->n);
+ for (x = scissor.x0; x < scissor.x1; x++)
+ {
+ for (i = 0; i < n; i++)
+ *s++ = colorbv[i];
+ }
+ }
+ if (shape)
+ {
+ for (y = scissor.y0; y < scissor.y1; y++)
+ {
+ s = shape->samples + (unsigned int)((scissor.x0 - shape->x) + (y - shape->y) * shape->w);
+ for (x = scissor.x0; x < scissor.x1; x++)
+ {
+ *s++ = 255;
+ }
+ }
+ }
+ }
+
+ fz_paint_shade(dev->ctx, shade, ctm, dest, &bbox);
+ if (shape)
+ fz_clear_pixmap_rect_with_value(dev->ctx, shape, 255, &bbox);
+
+ if (alpha < 1)
+ {
+ fz_paint_pixmap(state->dest, dest, alpha * 255);
+ fz_drop_pixmap(dev->ctx, dest);
+ if (shape)
+ {
+ fz_paint_pixmap(state->shape, shape, alpha * 255);
+ fz_drop_pixmap(dev->ctx, shape);
+ }
+ }
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+static fz_pixmap *
+fz_transform_pixmap(fz_draw_device *dev, fz_pixmap *image, fz_matrix *ctm, int x, int y, int dx, int dy, int gridfit, const fz_irect *clip)
+{
+ fz_pixmap *scaled;
+ fz_context *ctx = dev->ctx;
+
+ if (ctm->a != 0 && ctm->b == 0 && ctm->c == 0 && ctm->d != 0)
+ {
+ /* Unrotated or X-flip or Y-flip or XY-flip */
+ fz_matrix m = *ctm;
+ if (gridfit)
+ fz_gridfit_matrix(&m);
+ scaled = fz_scale_pixmap_cached(ctx, image, m.e, m.f, m.a, m.d, clip, dev->cache_x, dev->cache_y);
+ if (!scaled)
+ return NULL;
+ ctm->a = scaled->w;
+ ctm->d = scaled->h;
+ ctm->e = scaled->x;
+ ctm->f = scaled->y;
+ return scaled;
+ }
+
+ if (ctm->a == 0 && ctm->b != 0 && ctm->c != 0 && ctm->d == 0)
+ {
+ /* Other orthogonal flip/rotation cases */
+ fz_matrix m = *ctm;
+ fz_irect rclip;
+ if (gridfit)
+ fz_gridfit_matrix(&m);
+ if (clip)
+ {
+ rclip.x0 = clip->y0;
+ rclip.y0 = clip->x0;
+ rclip.x1 = clip->y1;
+ rclip.y1 = clip->x1;
+ }
+ scaled = fz_scale_pixmap_cached(ctx, image, m.f, m.e, m.b, m.c, (clip ? &rclip : NULL), dev->cache_x, dev->cache_y);
+ if (!scaled)
+ return NULL;
+ ctm->b = scaled->w;
+ ctm->c = scaled->h;
+ ctm->f = scaled->x;
+ ctm->e = scaled->y;
+ return scaled;
+ }
+
+ /* Downscale, non rectilinear case */
+ if (dx > 0 && dy > 0)
+ {
+ scaled = fz_scale_pixmap_cached(ctx, image, 0, 0, (float)dx, (float)dy, NULL, dev->cache_x, dev->cache_y);
+ return scaled;
+ }
+
+ return NULL;
+}
+
+static void
+fz_draw_fill_image(fz_device *devp, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ fz_pixmap *converted = NULL;
+ fz_pixmap *scaled = NULL;
+ fz_pixmap *pixmap;
+ fz_pixmap *orig_pixmap;
+ int after;
+ int dx, dy;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+ fz_irect clip;
+ fz_matrix local_ctm = *ctm;
+
+ fz_intersect_irect(fz_pixmap_bbox(ctx, state->dest, &clip), &state->scissor);
+
+ fz_var(scaled);
+
+ if (!model)
+ {
+ fz_warn(dev->ctx, "cannot render image directly to an alpha mask");
+ return;
+ }
+
+ if (image->w == 0 || image->h == 0)
+ return;
+
+ dx = sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b);
+ dy = sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d);
+
+ pixmap = fz_image_to_pixmap(ctx, image, dx, dy);
+ orig_pixmap = pixmap;
+
+ /* convert images with more components (cmyk->rgb) before scaling */
+ /* convert images with fewer components (gray->rgb after scaling */
+ /* convert images with expensive colorspace transforms after scaling */
+
+ fz_try(ctx)
+ {
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ after = 0;
+ if (pixmap->colorspace == fz_device_gray(ctx))
+ after = 1;
+
+ if (pixmap->colorspace != model && !after)
+ {
+ fz_irect bbox;
+ fz_pixmap_bbox(ctx, pixmap, &bbox);
+ converted = fz_new_pixmap_with_bbox(ctx, model, &bbox);
+ fz_convert_pixmap(ctx, converted, pixmap);
+ pixmap = converted;
+ }
+
+ if (dx < pixmap->w && dy < pixmap->h)
+ {
+ int gridfit = alpha == 1.0f && !(dev->flags & FZ_DRAWDEV_FLAGS_TYPE3);
+ scaled = fz_transform_pixmap(dev, pixmap, &local_ctm, state->dest->x, state->dest->y, dx, dy, gridfit, &clip);
+ if (!scaled)
+ {
+ if (dx < 1)
+ dx = 1;
+ if (dy < 1)
+ dy = 1;
+ scaled = fz_scale_pixmap_cached(ctx, pixmap, pixmap->x, pixmap->y, dx, dy, NULL, dev->cache_x, dev->cache_y);
+ }
+ if (scaled)
+ pixmap = scaled;
+ }
+
+ if (pixmap->colorspace != model)
+ {
+ if ((pixmap->colorspace == fz_device_gray(ctx) && model == fz_device_rgb(ctx)) ||
+ (pixmap->colorspace == fz_device_gray(ctx) && model == fz_device_bgr(ctx)))
+ {
+ /* We have special case rendering code for gray -> rgb/bgr */
+ }
+ else
+ {
+ fz_irect bbox;
+ fz_pixmap_bbox(ctx, pixmap, &bbox);
+ converted = fz_new_pixmap_with_bbox(ctx, model, &bbox);
+ fz_convert_pixmap(ctx, converted, pixmap);
+ pixmap = converted;
+ }
+ }
+
+ fz_paint_image(state->dest, &state->scissor, state->shape, pixmap, &local_ctm, alpha * 255);
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_pixmap(ctx, scaled);
+ fz_drop_pixmap(ctx, converted);
+ fz_drop_pixmap(ctx, orig_pixmap);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+fz_draw_fill_image_mask(fz_device *devp, fz_image *image, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ unsigned char colorbv[FZ_MAX_COLORS + 1];
+ float colorfv[FZ_MAX_COLORS];
+ fz_pixmap *scaled = NULL;
+ fz_pixmap *pixmap;
+ fz_pixmap *orig_pixmap;
+ int dx, dy;
+ int i;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+ fz_irect clip;
+ fz_matrix local_ctm = *ctm;
+
+ fz_pixmap_bbox(ctx, state->dest, &clip);
+ fz_intersect_irect(&clip, &state->scissor);
+
+ if (image->w == 0 || image->h == 0)
+ return;
+
+ dx = sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b);
+ dy = sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d);
+ pixmap = fz_image_to_pixmap(ctx, image, dx, dy);
+ orig_pixmap = pixmap;
+
+ fz_try(ctx)
+ {
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ state = fz_knockout_begin(dev);
+
+ if (dx < pixmap->w && dy < pixmap->h)
+ {
+ int gridfit = alpha == 1.0f && !(dev->flags & FZ_DRAWDEV_FLAGS_TYPE3);
+ scaled = fz_transform_pixmap(dev, pixmap, &local_ctm, state->dest->x, state->dest->y, dx, dy, gridfit, &clip);
+ if (!scaled)
+ {
+ if (dx < 1)
+ dx = 1;
+ if (dy < 1)
+ dy = 1;
+ scaled = fz_scale_pixmap_cached(dev->ctx, pixmap, pixmap->x, pixmap->y, dx, dy, NULL, dev->cache_x, dev->cache_y);
+ }
+ if (scaled)
+ pixmap = scaled;
+ }
+
+ fz_convert_color(dev->ctx, model, colorfv, colorspace, color);
+ for (i = 0; i < model->n; i++)
+ colorbv[i] = colorfv[i] * 255;
+ colorbv[i] = alpha * 255;
+
+ fz_paint_image_with_color(state->dest, &state->scissor, state->shape, pixmap, &local_ctm, colorbv);
+
+ if (scaled)
+ fz_drop_pixmap(dev->ctx, scaled);
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_pixmap(dev->ctx, orig_pixmap);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+fz_draw_clip_image_mask(fz_device *devp, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ fz_draw_device *dev = devp->user;
+ fz_context *ctx = dev->ctx;
+ fz_irect bbox;
+ fz_pixmap *mask = NULL;
+ fz_pixmap *dest = NULL;
+ fz_pixmap *shape = NULL;
+ fz_pixmap *scaled = NULL;
+ fz_pixmap *pixmap = NULL;
+ fz_pixmap *orig_pixmap = NULL;
+ int dx, dy;
+ fz_draw_state *state = push_stack(dev);
+ fz_colorspace *model = state->dest->colorspace;
+ fz_irect clip;
+ fz_matrix local_ctm = *ctm;
+ fz_rect urect;
+
+ fz_pixmap_bbox(ctx, state->dest, &clip);
+ fz_intersect_irect(&clip, &state->scissor);
+
+ fz_var(mask);
+ fz_var(dest);
+ fz_var(shape);
+ fz_var(pixmap);
+ fz_var(orig_pixmap);
+
+ if (image->w == 0 || image->h == 0)
+ {
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (image mask) (empty) begin\n");
+#endif
+ state[1].scissor = fz_empty_irect;
+ state[1].mask = NULL;
+ return;
+ }
+
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Clip (image mask) begin\n");
+#endif
+
+ urect = fz_unit_rect;
+ fz_irect_from_rect(&bbox, fz_transform_rect(&urect, &local_ctm));
+ fz_intersect_irect(&bbox, &state->scissor);
+ if (rect)
+ {
+ fz_irect bbox2;
+ fz_intersect_irect(&bbox, fz_irect_from_rect(&bbox2, rect));
+ }
+
+ dx = sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b);
+ dy = sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d);
+
+ fz_try(ctx)
+ {
+ pixmap = fz_image_to_pixmap(ctx, image, dx, dy);
+ orig_pixmap = pixmap;
+
+ state[1].mask = mask = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, mask);
+
+ state[1].dest = dest = fz_new_pixmap_with_bbox(dev->ctx, model, &bbox);
+ fz_clear_pixmap(dev->ctx, dest);
+ if (state->shape)
+ {
+ state[1].shape = shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].scissor = bbox;
+
+ if (dx < pixmap->w && dy < pixmap->h)
+ {
+ int gridfit = !(dev->flags & FZ_DRAWDEV_FLAGS_TYPE3);
+ scaled = fz_transform_pixmap(dev, pixmap, &local_ctm, state->dest->x, state->dest->y, dx, dy, gridfit, &clip);
+ if (!scaled)
+ {
+ if (dx < 1)
+ dx = 1;
+ if (dy < 1)
+ dy = 1;
+ scaled = fz_scale_pixmap_cached(dev->ctx, pixmap, pixmap->x, pixmap->y, dx, dy, NULL, dev->cache_x, dev->cache_y);
+ }
+ if (scaled)
+ pixmap = scaled;
+ }
+ fz_paint_image(mask, &bbox, state->shape, pixmap, &local_ctm, 255);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_pixmap(ctx, scaled);
+ fz_drop_pixmap(ctx, orig_pixmap);
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+}
+
+static void
+fz_draw_pop_clip(fz_device *devp)
+{
+ fz_draw_device *dev = devp->user;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state;
+
+ if (dev->top == 0)
+ {
+ fz_warn(ctx, "Unexpected pop clip");
+ return;
+ }
+ state = &dev->stack[--dev->top];
+
+ /* We can get here with state[1].mask == NULL if the clipping actually
+ * resolved to a rectangle earlier.
+ */
+ if (state[1].mask)
+ {
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top, "");
+ fz_dump_blend(dev->ctx, state[1].dest, "Clipping ");
+ if (state[1].shape)
+ fz_dump_blend(dev->ctx, state[1].shape, "/");
+ fz_dump_blend(dev->ctx, state[0].dest, " onto ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ fz_dump_blend(dev->ctx, state[1].mask, " with ");
+#endif
+ fz_paint_pixmap_with_mask(state[0].dest, state[1].dest, state[1].mask);
+ if (state[0].shape != state[1].shape)
+ {
+ fz_paint_pixmap_with_mask(state[0].shape, state[1].shape, state[1].mask);
+ fz_drop_pixmap(dev->ctx, state[1].shape);
+ }
+ fz_drop_pixmap(dev->ctx, state[1].mask);
+ fz_drop_pixmap(dev->ctx, state[1].dest);
+#ifdef DUMP_GROUP_BLENDS
+ fz_dump_blend(dev->ctx, state[0].dest, " to get ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ printf("\n");
+#endif
+ }
+ else
+ {
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top, "Clip end\n");
+#endif
+ }
+}
+
+static void
+fz_draw_begin_mask(fz_device *devp, const fz_rect *rect, int luminosity, fz_colorspace *colorspace, float *colorfv)
+{
+ fz_draw_device *dev = devp->user;
+ fz_pixmap *dest;
+ fz_irect bbox;
+ fz_draw_state *state = push_stack(dev);
+ fz_pixmap *shape = state->shape;
+ fz_context *ctx = dev->ctx;
+
+ fz_intersect_irect(fz_irect_from_rect(&bbox, rect), &state->scissor);
+
+ fz_try(ctx)
+ {
+ state[1].dest = dest = fz_new_pixmap_with_bbox(dev->ctx, fz_device_gray(ctx), &bbox);
+ if (state->shape)
+ {
+ /* FIXME: If we ever want to support AIS true, then
+ * we probably want to create a shape pixmap here,
+ * using: shape = fz_new_pixmap_with_bbox(NULL, bbox);
+ * then, in the end_mask code, we create the mask
+ * from this rather than dest.
+ */
+ state[1].shape = shape = NULL;
+ }
+
+ if (luminosity)
+ {
+ float bc;
+ if (!colorspace)
+ colorspace = fz_device_gray(ctx);
+ fz_convert_color(dev->ctx, fz_device_gray(ctx), &bc, colorspace, colorfv);
+ fz_clear_pixmap_with_value(dev->ctx, dest, bc * 255);
+ if (shape)
+ fz_clear_pixmap_with_value(dev->ctx, shape, 255);
+ }
+ else
+ {
+ fz_clear_pixmap(dev->ctx, dest);
+ if (shape)
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Mask begin\n");
+#endif
+ state[1].scissor = bbox;
+ state[1].luminosity = luminosity;
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+}
+
+static void
+fz_draw_end_mask(fz_device *devp)
+{
+ fz_draw_device *dev = devp->user;
+ fz_pixmap *temp, *dest;
+ fz_irect bbox;
+ int luminosity;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state;
+
+ if (dev->top == 0)
+ {
+ fz_warn(ctx, "Unexpected draw_end_mask");
+ return;
+ }
+ state = &dev->stack[dev->top-1];
+ /* pop soft mask buffer */
+ luminosity = state[1].luminosity;
+
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Mask -> Clip\n");
+#endif
+ /* convert to alpha mask */
+ temp = fz_alpha_from_gray(dev->ctx, state[1].dest, luminosity);
+ if (state[1].dest != state[0].dest)
+ fz_drop_pixmap(dev->ctx, state[1].dest);
+ state[1].dest = NULL;
+ if (state[1].shape != state[0].shape)
+ fz_drop_pixmap(dev->ctx, state[1].shape);
+ state[1].shape = NULL;
+ if (state[1].mask != state[0].mask)
+ fz_drop_pixmap(dev->ctx, state[1].mask);
+ state[1].mask = NULL;
+
+ /* create new dest scratch buffer */
+ fz_pixmap_bbox(ctx, temp, &bbox);
+ dest = fz_new_pixmap_with_bbox(dev->ctx, state->dest->colorspace, &bbox);
+ fz_clear_pixmap(dev->ctx, dest);
+
+ /* push soft mask as clip mask */
+ state[1].mask = temp;
+ state[1].dest = dest;
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ /* If we have a shape, then it'll need to be masked with the
+ * clip mask when we pop. So create a new shape now. */
+ if (state[0].shape)
+ {
+ state[1].shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, state[1].shape);
+ }
+ state[1].scissor = bbox;
+}
+
+static void
+fz_draw_begin_group(fz_device *devp, const fz_rect *rect, int isolated, int knockout, int blendmode, float alpha)
+{
+ fz_draw_device *dev = devp->user;
+ fz_irect bbox;
+ fz_pixmap *dest, *shape;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_begin(dev);
+
+ state = push_stack(dev);
+ fz_intersect_irect(fz_irect_from_rect(&bbox, rect), &state->scissor);
+
+ fz_try(ctx)
+ {
+ state[1].dest = dest = fz_new_pixmap_with_bbox(ctx, model, &bbox);
+
+#ifndef ATTEMPT_KNOCKOUT_AND_ISOLATED
+ knockout = 0;
+ isolated = 1;
+#endif
+
+ if (isolated)
+ {
+ fz_clear_pixmap(dev->ctx, dest);
+ }
+ else
+ {
+ fz_copy_pixmap_rect(dev->ctx, dest, state[0].dest, &bbox);
+ }
+
+ if (blendmode == 0 && alpha == 1.0 && isolated)
+ {
+ /* We can render direct to any existing shape plane.
+ * If there isn't one, we don't need to make one. */
+ state[1].shape =shape = state[0].shape;
+ }
+ else
+ {
+ state[1].shape = shape = fz_new_pixmap_with_bbox(ctx, NULL, &bbox);
+ fz_clear_pixmap(dev->ctx, shape);
+ }
+
+ state[1].alpha = alpha;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Group begin\n");
+#endif
+
+ state[1].scissor = bbox;
+ state[1].blendmode = blendmode | (isolated ? FZ_BLEND_ISOLATED : 0) | (knockout ? FZ_BLEND_KNOCKOUT : 0);
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+}
+
+static void
+fz_draw_end_group(fz_device *devp)
+{
+ fz_draw_device *dev = devp->user;
+ int blendmode;
+ int isolated;
+ float alpha;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state;
+
+ if (dev->top == 0)
+ {
+ fz_warn(ctx, "Unexpected end_group");
+ return;
+ }
+
+ state = &dev->stack[--dev->top];
+ alpha = state[1].alpha;
+ blendmode = state[1].blendmode & FZ_BLEND_MODEMASK;
+ isolated = state[1].blendmode & FZ_BLEND_ISOLATED;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top, "");
+ fz_dump_blend(dev->ctx, state[1].dest, "Group end: blending ");
+ if (state[1].shape)
+ fz_dump_blend(dev->ctx, state[1].shape, "/");
+ fz_dump_blend(dev->ctx, state[0].dest, " onto ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ if (alpha != 1.0f)
+ printf(" (alpha %g)", alpha);
+ if (blendmode != 0)
+ printf(" (blend %d)", blendmode);
+ if (isolated != 0)
+ printf(" (isolated)");
+ if (state[1].blendmode & FZ_BLEND_KNOCKOUT)
+ printf(" (knockout)");
+#endif
+ if ((blendmode == 0) && (state[0].shape == state[1].shape))
+ fz_paint_pixmap(state[0].dest, state[1].dest, alpha * 255);
+ else
+ fz_blend_pixmap(state[0].dest, state[1].dest, alpha * 255, blendmode, isolated, state[1].shape);
+
+ fz_drop_pixmap(dev->ctx, state[1].dest);
+ if (state[0].shape != state[1].shape)
+ {
+ if (state[0].shape)
+ fz_paint_pixmap(state[0].shape, state[1].shape, alpha * 255);
+ fz_drop_pixmap(dev->ctx, state[1].shape);
+ }
+#ifdef DUMP_GROUP_BLENDS
+ fz_dump_blend(dev->ctx, state[0].dest, " to get ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ printf("\n");
+#endif
+
+ if (state[0].blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+typedef struct
+{
+ int refs;
+ float ctm[4];
+ int id;
+} tile_key;
+
+typedef struct
+{
+ fz_storable storable;
+ fz_pixmap *dest;
+ fz_pixmap *shape;
+} tile_record;
+
+static int
+fz_make_hash_tile_key(fz_store_hash *hash, void *key_)
+{
+ tile_key *key = (tile_key *)key_;
+
+ hash->u.im.id = key->id;
+ hash->u.im.m[0] = key->ctm[0];
+ hash->u.im.m[1] = key->ctm[1];
+ hash->u.im.m[2] = key->ctm[2];
+ hash->u.im.m[3] = key->ctm[3];
+ return 1;
+}
+
+static void *
+fz_keep_tile_key(fz_context *ctx, void *key_)
+{
+ tile_key *key = (tile_key *)key_;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ key->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ return (void *)key;
+}
+
+static void
+fz_drop_tile_key(fz_context *ctx, void *key_)
+{
+ tile_key *key = (tile_key *)key_;
+ int drop;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = --key->refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop == 0)
+ {
+ fz_free(ctx, key);
+ }
+}
+
+static int
+fz_cmp_tile_key(void *k0_, void *k1_)
+{
+ tile_key *k0 = (tile_key *)k0_;
+ tile_key *k1 = (tile_key *)k1_;
+
+ return k0->id == k1->id && k0->ctm[0] == k1->ctm[0] && k0->ctm[1] == k1->ctm[1] && k0->ctm[2] == k1->ctm[2] && k0->ctm[3] == k1->ctm[3];
+}
+
+#ifndef NDEBUG
+static void
+fz_debug_tile(FILE *out, void *key_)
+{
+ tile_key *key = (tile_key *)key_;
+
+ fprintf(out, "(tile id=%x, ctm=%g %g %g %g) ", key->id, key->ctm[0], key->ctm[1], key->ctm[2], key->ctm[3]);
+}
+#endif
+
+static fz_store_type fz_tile_store_type =
+{
+ fz_make_hash_tile_key,
+ fz_keep_tile_key,
+ fz_drop_tile_key,
+ fz_cmp_tile_key,
+#ifndef NDEBUG
+ fz_debug_tile
+#endif
+};
+
+static void
+fz_free_tile_record_imp(fz_context *ctx, fz_storable *storable)
+{
+ tile_record *tr = (tile_record *)(void *)storable;
+
+ if (tr == NULL)
+ return;
+ fz_drop_pixmap(ctx, tr->dest);
+ fz_drop_pixmap(ctx, tr->shape);
+ fz_free(ctx, tr);
+}
+
+static void
+fz_drop_tile_record(fz_context *ctx, tile_record *tile)
+{
+ fz_drop_storable(ctx, &tile->storable);
+}
+
+static tile_record *
+fz_new_tile_record(fz_context *ctx, fz_pixmap *dest, fz_pixmap *shape)
+{
+ tile_record *tile = fz_malloc_struct(ctx, tile_record);
+ FZ_INIT_STORABLE(tile, 1, fz_free_tile_record_imp);
+ tile->dest = fz_keep_pixmap(ctx, dest);
+ tile->shape = fz_keep_pixmap(ctx, shape);
+ return tile;
+}
+
+unsigned int
+fz_tile_size(fz_context *ctx, tile_record *tile)
+{
+ if (!tile)
+ return 0;
+ return sizeof(*tile) + fz_pixmap_size(ctx, tile->dest) + fz_pixmap_size(ctx, tile->shape);
+}
+
+static int
+fz_draw_begin_tile(fz_device *devp, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ fz_draw_device *dev = devp->user;
+ fz_pixmap *dest = NULL;
+ fz_pixmap *shape;
+ fz_irect bbox;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state = &dev->stack[dev->top];
+ fz_colorspace *model = state->dest->colorspace;
+ fz_rect local_view = *view;
+
+ /* area, view, xstep, ystep are in pattern space */
+ /* ctm maps from pattern space to device space */
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_begin(dev);
+
+ state = push_stack(dev);
+ fz_irect_from_rect(&bbox, fz_transform_rect(&local_view, ctm));
+ /* We should never have a bbox that entirely covers our destination.
+ * If we do, then the check for only 1 tile being visible above has
+ * failed. Actually, this *can* fail due to the round_rect, at extreme
+ * resolutions, so disable this assert.
+ * assert(bbox.x0 > state->dest->x || bbox.x1 < state->dest->x + state->dest->w ||
+ * bbox.y0 > state->dest->y || bbox.y1 < state->dest->y + state->dest->h);
+ */
+
+ /* Check to see if we have one cached */
+ if (id)
+ {
+ tile_key tk;
+ tile_record *tile;
+ tk.ctm[0] = ctm->a;
+ tk.ctm[1] = ctm->b;
+ tk.ctm[2] = ctm->c;
+ tk.ctm[3] = ctm->d;
+ tk.id = id;
+
+ tile = fz_find_item(ctx, fz_free_tile_record_imp, &tk, &fz_tile_store_type);
+ if (tile)
+ {
+ state[1].dest = fz_keep_pixmap(ctx, tile->dest);
+ state[1].shape = fz_keep_pixmap(ctx, tile->shape);
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].xstep = xstep;
+ state[1].ystep = ystep;
+ state[1].id = id;
+ fz_irect_from_rect(&state[1].area, area);
+ state[1].ctm = *ctm;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Tile begin (cached)\n");
+#endif
+
+ state[1].scissor = bbox;
+ fz_drop_tile_record(ctx, tile);
+ return 1;
+ }
+ }
+
+ fz_try(ctx)
+ {
+ state[1].dest = dest = fz_new_pixmap_with_bbox(dev->ctx, model, &bbox);
+ fz_clear_pixmap(ctx, dest);
+ shape = state[0].shape;
+ if (shape)
+ {
+ state[1].shape = shape = fz_new_pixmap_with_bbox(dev->ctx, NULL, &bbox);
+ fz_clear_pixmap(ctx, shape);
+ }
+ state[1].blendmode |= FZ_BLEND_ISOLATED;
+ state[1].xstep = xstep;
+ state[1].ystep = ystep;
+ state[1].id = id;
+ fz_irect_from_rect(&state[1].area, area);
+ state[1].ctm = *ctm;
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top-1, "Tile begin\n");
+#endif
+
+ state[1].scissor = bbox;
+ }
+ fz_catch(ctx)
+ {
+ emergency_pop_stack(dev, state);
+ }
+
+ return 0;
+}
+
+static void
+fz_draw_end_tile(fz_device *devp)
+{
+ fz_draw_device *dev = devp->user;
+ float xstep, ystep;
+ fz_matrix ttm, ctm, shapectm;
+ fz_irect area, scissor;
+ fz_rect scissor_tmp;
+ int x0, y0, x1, y1, x, y;
+ fz_context *ctx = dev->ctx;
+ fz_draw_state *state;
+ tile_record *tile;
+ tile_key *key;
+
+ if (dev->top == 0)
+ {
+ fz_warn(ctx, "Unexpected end_tile");
+ return;
+ }
+
+ state = &dev->stack[--dev->top];
+ xstep = state[1].xstep;
+ ystep = state[1].ystep;
+ area = state[1].area;
+ ctm = state[1].ctm;
+
+ /* Fudge the scissor bbox a little to allow for inaccuracies in the
+ * matrix inversion. */
+ fz_rect_from_irect(&scissor_tmp, &state[0].scissor);
+ fz_transform_rect(fz_expand_rect(&scissor_tmp, 1), fz_invert_matrix(&ttm, &ctm));
+ fz_intersect_irect(&area, fz_irect_from_rect(&scissor, &scissor_tmp));
+
+ /* FIXME: area is a bbox, so FP not appropriate here */
+ /* In PDF files xstep/ystep can be smaller than view (the area of a
+ * single tile) (see fts_15_1506.pdf for an example). This means that
+ * we have to bias the left hand/bottom edge calculations by the
+ * difference between the step and the width/height of the tile. */
+ /* state[0].scissor = view, transformed by ctm */
+ x0 = floorf((area.x0 + xstep - state[0].scissor.x1 + state[0].scissor.x0) / xstep);
+ y0 = floorf((area.y0 + ystep - state[0].scissor.y1 + state[0].scissor.y0) / ystep);
+ x1 = ceilf(area.x1 / xstep);
+ y1 = ceilf(area.y1 / ystep);
+
+ ctm.e = state[1].dest->x;
+ ctm.f = state[1].dest->y;
+ if (state[1].shape)
+ {
+ shapectm = ctm;
+ shapectm.e = state[1].shape->x;
+ shapectm.f = state[1].shape->y;
+ }
+
+#ifdef DUMP_GROUP_BLENDS
+ dump_spaces(dev->top, "");
+ fz_dump_blend(dev->ctx, state[1].dest, "Tiling ");
+ if (state[1].shape)
+ fz_dump_blend(dev->ctx, state[1].shape, "/");
+ fz_dump_blend(dev->ctx, state[0].dest, " onto ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+#endif
+
+ for (y = y0; y < y1; y++)
+ {
+ for (x = x0; x < x1; x++)
+ {
+ ttm = ctm;
+ fz_pre_translate(&ttm, x * xstep, y * ystep);
+ state[1].dest->x = ttm.e;
+ state[1].dest->y = ttm.f;
+ if (state[1].dest->x > 0 && state[1].dest->x + state[1].dest->w < 0)
+ continue;
+ if (state[1].dest->y > 0 && state[1].dest->y + state[1].dest->h < 0)
+ continue;
+ fz_paint_pixmap_with_bbox(state[0].dest, state[1].dest, 255, state[0].scissor);
+ if (state[1].shape)
+ {
+ ttm = shapectm;
+ fz_pre_translate(&ttm, x * xstep, y * ystep);
+ state[1].shape->x = ttm.e;
+ state[1].shape->y = ttm.f;
+ fz_paint_pixmap_with_bbox(state[0].shape, state[1].shape, 255, state[0].scissor);
+ }
+ }
+ }
+
+ state[1].dest->x = ctm.e;
+ state[1].dest->y = ctm.f;
+ if (state[1].shape)
+ {
+ state[1].shape->x = shapectm.e;
+ state[1].shape->y = shapectm.f;
+ }
+
+ /* Now we try to cache the tiles. Any failure here will just result
+ * in us not caching. */
+ tile = NULL;
+ key = NULL;
+ fz_var(tile);
+ fz_var(key);
+ fz_try(ctx)
+ {
+ tile_record *existing_tile;
+
+ tile = fz_new_tile_record(ctx, state[1].dest, state[1].shape);
+
+ key = fz_malloc_struct(ctx, tile_key);
+ key->refs = 1;
+ key->id = state[1].id;
+ key->ctm[0] = ctm.a;
+ key->ctm[1] = ctm.b;
+ key->ctm[2] = ctm.c;
+ key->ctm[3] = ctm.d;
+ existing_tile = fz_store_item(ctx, key, tile, fz_tile_size(ctx, tile), &fz_tile_store_type);
+ if (existing_tile)
+ {
+ /* We already have a tile. This will either have been
+ * produced by a racing thread, or there is already
+ * an entry for this one in the store. */
+ fz_drop_tile_record(ctx, tile);
+ tile = existing_tile;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_drop_tile_key(ctx, key);
+ fz_drop_tile_record(ctx, tile);
+ }
+ fz_catch(ctx)
+ {
+ /* Do nothing */
+ }
+
+ fz_drop_pixmap(dev->ctx, state[1].dest);
+ fz_drop_pixmap(dev->ctx, state[1].shape);
+#ifdef DUMP_GROUP_BLENDS
+ fz_dump_blend(dev->ctx, state[0].dest, " to get ");
+ if (state[0].shape)
+ fz_dump_blend(dev->ctx, state[0].shape, "/");
+ printf("\n");
+#endif
+
+ if (state->blendmode & FZ_BLEND_KNOCKOUT)
+ fz_knockout_end(dev);
+}
+
+static void
+fz_draw_free_user(fz_device *devp)
+{
+ fz_draw_device *dev = devp->user;
+ fz_context *ctx = dev->ctx;
+ /* pop and free the stacks */
+ if (dev->top > 0)
+ fz_warn(ctx, "items left on stack in draw device: %d", dev->top+1);
+
+ while(dev->top-- > 0)
+ {
+ fz_draw_state *state = &dev->stack[dev->top];
+ if (state[1].mask != state[0].mask)
+ fz_drop_pixmap(ctx, state[1].mask);
+ if (state[1].dest != state[0].dest)
+ fz_drop_pixmap(ctx, state[1].dest);
+ if (state[1].shape != state[0].shape)
+ fz_drop_pixmap(ctx, state[1].shape);
+ }
+ /* We never free the dest/mask/shape at level 0, as:
+ * 1) dest is passed in and ownership remains with the caller.
+ * 2) shape and mask are NULL at level 0.
+ */
+ if (dev->stack != &dev->init_stack[0])
+ fz_free(ctx, dev->stack);
+ fz_free_scale_cache(ctx, dev->cache_x);
+ fz_free_scale_cache(ctx, dev->cache_y);
+ fz_free_gel(dev->gel);
+ fz_free(ctx, dev);
+}
+
+fz_device *
+fz_new_draw_device(fz_context *ctx, fz_pixmap *dest)
+{
+ fz_device *dev = NULL;
+ fz_draw_device *ddev = fz_malloc_struct(ctx, fz_draw_device);
+
+ fz_var(dev);
+ fz_try(ctx)
+ {
+ ddev->gel = fz_new_gel(ctx);
+ ddev->flags = 0;
+ ddev->ctx = ctx;
+ ddev->top = 0;
+ ddev->cache_x = fz_new_scale_cache(ctx);
+ ddev->cache_y = fz_new_scale_cache(ctx);
+ ddev->stack = &ddev->init_stack[0];
+ ddev->stack_max = STACK_SIZE;
+ ddev->stack[0].dest = dest;
+ ddev->stack[0].shape = NULL;
+ ddev->stack[0].mask = NULL;
+ ddev->stack[0].blendmode = 0;
+ ddev->stack[0].scissor.x0 = dest->x;
+ ddev->stack[0].scissor.y0 = dest->y;
+ ddev->stack[0].scissor.x1 = dest->x + dest->w;
+ ddev->stack[0].scissor.y1 = dest->y + dest->h;
+
+ dev = fz_new_device(ctx, ddev);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_scale_cache(ctx, ddev->cache_x);
+ fz_free_scale_cache(ctx, ddev->cache_y);
+ fz_free_gel(ddev->gel);
+ fz_free(ctx, ddev);
+ fz_rethrow(ctx);
+ }
+ dev->free_user = fz_draw_free_user;
+
+ dev->fill_path = fz_draw_fill_path;
+ dev->stroke_path = fz_draw_stroke_path;
+ dev->clip_path = fz_draw_clip_path;
+ dev->clip_stroke_path = fz_draw_clip_stroke_path;
+
+ dev->fill_text = fz_draw_fill_text;
+ dev->stroke_text = fz_draw_stroke_text;
+ dev->clip_text = fz_draw_clip_text;
+ dev->clip_stroke_text = fz_draw_clip_stroke_text;
+ dev->ignore_text = fz_draw_ignore_text;
+
+ dev->fill_image_mask = fz_draw_fill_image_mask;
+ dev->clip_image_mask = fz_draw_clip_image_mask;
+ dev->fill_image = fz_draw_fill_image;
+ dev->fill_shade = fz_draw_fill_shade;
+
+ dev->pop_clip = fz_draw_pop_clip;
+
+ dev->begin_mask = fz_draw_begin_mask;
+ dev->end_mask = fz_draw_end_mask;
+ dev->begin_group = fz_draw_begin_group;
+ dev->end_group = fz_draw_end_group;
+
+ dev->begin_tile = fz_draw_begin_tile;
+ dev->end_tile = fz_draw_end_tile;
+
+ return dev;
+}
+
+fz_device *
+fz_new_draw_device_with_bbox(fz_context *ctx, fz_pixmap *dest, const fz_irect *clip)
+{
+ fz_device *dev = fz_new_draw_device(ctx, dest);
+ fz_draw_device *ddev = dev->user;
+
+ if (clip->x0 > ddev->stack[0].scissor.x0)
+ ddev->stack[0].scissor.x0 = clip->x0;
+ if (clip->x1 < ddev->stack[0].scissor.x1)
+ ddev->stack[0].scissor.x1 = clip->x1;
+ if (clip->y0 > ddev->stack[0].scissor.y0)
+ ddev->stack[0].scissor.y0 = clip->y0;
+ if (clip->y1 < ddev->stack[0].scissor.y1)
+ ddev->stack[0].scissor.y1 = clip->y1;
+ return dev;
+}
+
+fz_device *
+fz_new_draw_device_type3(fz_context *ctx, fz_pixmap *dest)
+{
+ fz_device *dev = fz_new_draw_device(ctx, dest);
+ fz_draw_device *ddev = dev->user;
+ ddev->flags |= FZ_DRAWDEV_FLAGS_TYPE3;
+ return dev;
+}
+
+fz_irect *
+fz_bound_path_accurate(fz_context *ctx, fz_irect *bbox, const fz_irect *scissor, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth)
+{
+ fz_gel *gel = fz_new_gel(ctx);
+
+ fz_reset_gel(gel, scissor);
+ if (stroke)
+ {
+ if (stroke->dash_len > 0)
+ fz_flatten_dash_path(gel, path, stroke, ctm, flatness, linewidth);
+ else
+ fz_flatten_stroke_path(gel, path, stroke, ctm, flatness, linewidth);
+ }
+ else
+ fz_flatten_fill_path(gel, path, ctm, flatness);
+ fz_bound_gel(gel, bbox);
+ fz_free_gel(gel);
+
+ return bbox;
+}
diff --git a/source/fitz/draw-edge.c b/source/fitz/draw-edge.c
new file mode 100644
index 00000000..5f2f45d3
--- /dev/null
+++ b/source/fitz/draw-edge.c
@@ -0,0 +1,972 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+#define BBOX_MIN -(1<<20)
+#define BBOX_MAX (1<<20)
+
+/* divide and floor towards -inf */
+static inline int fz_idiv(int a, int b)
+{
+ return a < 0 ? (a - b + 1) / b : a / b;
+}
+
+/* If AA_BITS is defined, then we assume constant N bits of antialiasing. We
+ * will attempt to provide at least that number of bits of accuracy in the
+ * antialiasing (to a maximum of 8). If it is defined to be 0 then no
+ * antialiasing is done. If it is undefined to we will leave the antialiasing
+ * accuracy as a run time choice.
+ */
+struct fz_aa_context_s
+{
+ int hscale;
+ int vscale;
+ int scale;
+ int bits;
+};
+
+void fz_new_aa_context(fz_context *ctx)
+{
+#ifndef AA_BITS
+ ctx->aa = fz_malloc_struct(ctx, fz_aa_context);
+ ctx->aa->hscale = 17;
+ ctx->aa->vscale = 15;
+ ctx->aa->scale = 256;
+ ctx->aa->bits = 8;
+
+#define fz_aa_hscale ((ctxaa)->hscale)
+#define fz_aa_vscale ((ctxaa)->vscale)
+#define fz_aa_scale ((ctxaa)->scale)
+#define fz_aa_bits ((ctxaa)->bits)
+#define AA_SCALE(x) ((x * fz_aa_scale) >> 8)
+
+#endif
+}
+
+void fz_copy_aa_context(fz_context *dst, fz_context *src)
+{
+ if (dst && dst->aa && src && src->aa)
+ memcpy(dst->aa, src->aa, sizeof(*src->aa));
+}
+
+void fz_free_aa_context(fz_context *ctx)
+{
+#ifndef AA_BITS
+ fz_free(ctx, ctx->aa);
+ ctx->aa = NULL;
+#endif
+}
+
+#ifdef AA_BITS
+
+#if AA_BITS > 6
+#define AA_SCALE(x) (x)
+#define fz_aa_hscale 17
+#define fz_aa_vscale 15
+#define fz_aa_bits 8
+
+#elif AA_BITS > 4
+#define AA_SCALE(x) ((x * 255) >> 6)
+#define fz_aa_hscale 8
+#define fz_aa_vscale 8
+#define fz_aa_bits 6
+
+#elif AA_BITS > 2
+#define AA_SCALE(x) (x * 17)
+#define fz_aa_hscale 5
+#define fz_aa_vscale 3
+#define fz_aa_bits 4
+
+#elif AA_BITS > 0
+#define AA_SCALE(x) ((x * 255) >> 2)
+#define fz_aa_hscale 2
+#define fz_aa_vscale 2
+#define fz_aa_bits 2
+
+#else
+#define AA_SCALE(x) (x * 255)
+#define fz_aa_hscale 1
+#define fz_aa_vscale 1
+#define fz_aa_bits 0
+
+#endif
+#endif
+
+int
+fz_aa_level(fz_context *ctx)
+{
+ fz_aa_context *ctxaa = ctx->aa;
+ return fz_aa_bits;
+}
+
+void
+fz_set_aa_level(fz_context *ctx, int level)
+{
+ fz_aa_context *ctxaa = ctx->aa;
+#ifdef AA_BITS
+ fz_warn(ctx, "anti-aliasing was compiled with a fixed precision of %d bits", fz_aa_bits);
+#else
+ if (level > 6)
+ {
+ fz_aa_hscale = 17;
+ fz_aa_vscale = 15;
+ fz_aa_bits = 8;
+ }
+ else if (level > 4)
+ {
+ fz_aa_hscale = 8;
+ fz_aa_vscale = 8;
+ fz_aa_bits = 6;
+ }
+ else if (level > 2)
+ {
+ fz_aa_hscale = 5;
+ fz_aa_vscale = 3;
+ fz_aa_bits = 4;
+ }
+ else if (level > 0)
+ {
+ fz_aa_hscale = 2;
+ fz_aa_vscale = 2;
+ fz_aa_bits = 2;
+ }
+ else
+ {
+ fz_aa_hscale = 1;
+ fz_aa_vscale = 1;
+ fz_aa_bits = 0;
+ }
+ fz_aa_scale = 0xFF00 / (fz_aa_hscale * fz_aa_vscale);
+#endif
+}
+
+/*
+ * Global Edge List -- list of straight path segments for scan conversion
+ *
+ * Stepping along the edges is with Bresenham's line algorithm.
+ *
+ * See Mike Abrash -- Graphics Programming Black Book (notably chapter 40)
+ */
+
+typedef struct fz_edge_s fz_edge;
+
+struct fz_edge_s
+{
+ int x, e, h, y;
+ int adj_up, adj_down;
+ int xmove;
+ int xdir, ydir; /* -1 or +1 */
+};
+
+struct fz_gel_s
+{
+ fz_rect clip;
+ fz_rect bbox;
+ int cap, len;
+ fz_edge *edges;
+ int acap, alen;
+ fz_edge **active;
+ fz_context *ctx;
+};
+
+fz_gel *
+fz_new_gel(fz_context *ctx)
+{
+ fz_gel *gel;
+
+ gel = fz_malloc_struct(ctx, fz_gel);
+ fz_try(ctx)
+ {
+ gel->edges = NULL;
+ gel->ctx = ctx;
+ gel->cap = 512;
+ gel->len = 0;
+ gel->edges = fz_malloc_array(ctx, gel->cap, sizeof(fz_edge));
+
+ gel->clip.x0 = gel->clip.y0 = BBOX_MAX;
+ gel->clip.x1 = gel->clip.y1 = BBOX_MIN;
+
+ gel->bbox.x0 = gel->bbox.y0 = BBOX_MAX;
+ gel->bbox.x1 = gel->bbox.y1 = BBOX_MIN;
+
+ gel->acap = 64;
+ gel->alen = 0;
+ gel->active = fz_malloc_array(ctx, gel->acap, sizeof(fz_edge*));
+ }
+ fz_catch(ctx)
+ {
+ if (gel)
+ fz_free(ctx, gel->edges);
+ fz_free(ctx, gel);
+ fz_rethrow(ctx);
+ }
+
+ return gel;
+}
+
+void
+fz_reset_gel(fz_gel *gel, const fz_irect *clip)
+{
+ fz_aa_context *ctxaa = gel->ctx->aa;
+
+ if (fz_is_infinite_irect(clip))
+ {
+ gel->clip.x0 = gel->clip.y0 = BBOX_MAX;
+ gel->clip.x1 = gel->clip.y1 = BBOX_MIN;
+ }
+ else {
+ gel->clip.x0 = clip->x0 * fz_aa_hscale;
+ gel->clip.x1 = clip->x1 * fz_aa_hscale;
+ gel->clip.y0 = clip->y0 * fz_aa_vscale;
+ gel->clip.y1 = clip->y1 * fz_aa_vscale;
+ }
+
+ gel->bbox.x0 = gel->bbox.y0 = BBOX_MAX;
+ gel->bbox.x1 = gel->bbox.y1 = BBOX_MIN;
+
+ gel->len = 0;
+}
+
+void
+fz_free_gel(fz_gel *gel)
+{
+ if (gel == NULL)
+ return;
+ fz_free(gel->ctx, gel->active);
+ fz_free(gel->ctx, gel->edges);
+ fz_free(gel->ctx, gel);
+}
+
+fz_irect *
+fz_bound_gel(const fz_gel *gel, fz_irect *bbox)
+{
+ fz_aa_context *ctxaa = gel->ctx->aa;
+ if (gel->len == 0)
+ {
+ *bbox = fz_empty_irect;
+ }
+ else
+ {
+ bbox->x0 = fz_idiv(gel->bbox.x0, fz_aa_hscale);
+ bbox->y0 = fz_idiv(gel->bbox.y0, fz_aa_vscale);
+ bbox->x1 = fz_idiv(gel->bbox.x1, fz_aa_hscale) + 1;
+ bbox->y1 = fz_idiv(gel->bbox.y1, fz_aa_vscale) + 1;
+ }
+ return bbox;
+}
+
+enum { INSIDE, OUTSIDE, LEAVE, ENTER };
+
+#define clip_lerp_y(v,m,x0,y0,x1,y1,t) clip_lerp_x(v,m,y0,x0,y1,x1,t)
+
+static int
+clip_lerp_x(int val, int m, int x0, int y0, int x1, int y1, int *out)
+{
+ int v0out = m ? x0 > val : x0 < val;
+ int v1out = m ? x1 > val : x1 < val;
+
+ if (v0out + v1out == 0)
+ return INSIDE;
+
+ if (v0out + v1out == 2)
+ return OUTSIDE;
+
+ if (v1out)
+ {
+ *out = y0 + (int)(((float)(y1 - y0)) * (val - x0) / (x1 - x0));
+ return LEAVE;
+ }
+
+ else
+ {
+ *out = y1 + (int)(((float)(y0 - y1)) * (val - x1) / (x0 - x1));
+ return ENTER;
+ }
+}
+
+static void
+fz_insert_gel_raw(fz_gel *gel, int x0, int y0, int x1, int y1)
+{
+ fz_edge *edge;
+ int dx, dy;
+ int winding;
+ int width;
+ int tmp;
+
+ if (y0 == y1)
+ return;
+
+ if (y0 > y1) {
+ winding = -1;
+ tmp = x0; x0 = x1; x1 = tmp;
+ tmp = y0; y0 = y1; y1 = tmp;
+ }
+ else
+ winding = 1;
+
+ if (x0 < gel->bbox.x0) gel->bbox.x0 = x0;
+ if (x0 > gel->bbox.x1) gel->bbox.x1 = x0;
+ if (x1 < gel->bbox.x0) gel->bbox.x0 = x1;
+ if (x1 > gel->bbox.x1) gel->bbox.x1 = x1;
+
+ if (y0 < gel->bbox.y0) gel->bbox.y0 = y0;
+ if (y1 > gel->bbox.y1) gel->bbox.y1 = y1;
+
+ if (gel->len + 1 == gel->cap) {
+ int new_cap = gel->cap + 512;
+ gel->edges = fz_resize_array(gel->ctx, gel->edges, new_cap, sizeof(fz_edge));
+ gel->cap = new_cap;
+ }
+
+ edge = &gel->edges[gel->len++];
+
+ dy = y1 - y0;
+ dx = x1 - x0;
+ width = fz_absi(dx);
+
+ edge->xdir = dx > 0 ? 1 : -1;
+ edge->ydir = winding;
+ edge->x = x0;
+ edge->y = y0;
+ edge->h = dy;
+ edge->adj_down = dy;
+
+ /* initial error term going l->r and r->l */
+ if (dx >= 0)
+ edge->e = 0;
+ else
+ edge->e = -dy + 1;
+
+ /* y-major edge */
+ if (dy >= width) {
+ edge->xmove = 0;
+ edge->adj_up = width;
+ }
+
+ /* x-major edge */
+ else {
+ edge->xmove = (width / dy) * edge->xdir;
+ edge->adj_up = width % dy;
+ }
+}
+
+void
+fz_insert_gel(fz_gel *gel, float fx0, float fy0, float fx1, float fy1)
+{
+ int x0, y0, x1, y1;
+ int d, v;
+ fz_aa_context *ctxaa = gel->ctx->aa;
+
+ fx0 = floorf(fx0 * fz_aa_hscale);
+ fx1 = floorf(fx1 * fz_aa_hscale);
+ fy0 = floorf(fy0 * fz_aa_vscale);
+ fy1 = floorf(fy1 * fz_aa_vscale);
+
+ /* Call fz_clamp so that clamping is done in the float domain, THEN
+ * cast down to an int. Calling fz_clampi causes problems due to the
+ * implicit cast down from float to int of the first argument
+ * over/underflowing and flipping sign at extreme values. */
+ x0 = (int)fz_clamp(fx0, BBOX_MIN * fz_aa_hscale, BBOX_MAX * fz_aa_hscale);
+ y0 = (int)fz_clamp(fy0, BBOX_MIN * fz_aa_vscale, BBOX_MAX * fz_aa_vscale);
+ x1 = (int)fz_clamp(fx1, BBOX_MIN * fz_aa_hscale, BBOX_MAX * fz_aa_hscale);
+ y1 = (int)fz_clamp(fy1, BBOX_MIN * fz_aa_vscale, BBOX_MAX * fz_aa_vscale);
+
+ d = clip_lerp_y(gel->clip.y0, 0, x0, y0, x1, y1, &v);
+ if (d == OUTSIDE) return;
+ if (d == LEAVE) { y1 = gel->clip.y0; x1 = v; }
+ if (d == ENTER) { y0 = gel->clip.y0; x0 = v; }
+
+ d = clip_lerp_y(gel->clip.y1, 1, x0, y0, x1, y1, &v);
+ if (d == OUTSIDE) return;
+ if (d == LEAVE) { y1 = gel->clip.y1; x1 = v; }
+ if (d == ENTER) { y0 = gel->clip.y1; x0 = v; }
+
+ d = clip_lerp_x(gel->clip.x0, 0, x0, y0, x1, y1, &v);
+ if (d == OUTSIDE) {
+ x0 = x1 = gel->clip.x0;
+ }
+ if (d == LEAVE) {
+ fz_insert_gel_raw(gel, gel->clip.x0, v, gel->clip.x0, y1);
+ x1 = gel->clip.x0;
+ y1 = v;
+ }
+ if (d == ENTER) {
+ fz_insert_gel_raw(gel, gel->clip.x0, y0, gel->clip.x0, v);
+ x0 = gel->clip.x0;
+ y0 = v;
+ }
+
+ d = clip_lerp_x(gel->clip.x1, 1, x0, y0, x1, y1, &v);
+ if (d == OUTSIDE) {
+ x0 = x1 = gel->clip.x1;
+ }
+ if (d == LEAVE) {
+ fz_insert_gel_raw(gel, gel->clip.x1, v, gel->clip.x1, y1);
+ x1 = gel->clip.x1;
+ y1 = v;
+ }
+ if (d == ENTER) {
+ fz_insert_gel_raw(gel, gel->clip.x1, y0, gel->clip.x1, v);
+ x0 = gel->clip.x1;
+ y0 = v;
+ }
+
+ fz_insert_gel_raw(gel, x0, y0, x1, y1);
+}
+
+void
+fz_sort_gel(fz_gel *gel)
+{
+ fz_edge *a = gel->edges;
+ int n = gel->len;
+
+ int h, i, k;
+ fz_edge t;
+
+ h = 1;
+ if (n < 14) {
+ h = 1;
+ }
+ else {
+ while (h < n)
+ h = 3 * h + 1;
+ h /= 3;
+ h /= 3;
+ }
+
+ while (h > 0)
+ {
+ for (i = 0; i < n; i++) {
+ t = a[i];
+ k = i - h;
+ /* TODO: sort on y major, x minor */
+ while (k >= 0 && a[k].y > t.y) {
+ a[k + h] = a[k];
+ k -= h;
+ }
+ a[k + h] = t;
+ }
+
+ h /= 3;
+ }
+}
+
+int
+fz_is_rect_gel(fz_gel *gel)
+{
+ /* a rectangular path is converted into two vertical edges of identical height */
+ if (gel->len == 2)
+ {
+ fz_edge *a = gel->edges + 0;
+ fz_edge *b = gel->edges + 1;
+ return a->y == b->y && a->h == b->h &&
+ a->xmove == 0 && a->adj_up == 0 &&
+ b->xmove == 0 && b->adj_up == 0;
+ }
+ return 0;
+}
+
+/*
+ * Active Edge List -- keep track of active edges while sweeping
+ */
+
+static void
+sort_active(fz_edge **a, int n)
+{
+ int h, i, k;
+ fz_edge *t;
+
+ h = 1;
+ if (n < 14) {
+ h = 1;
+ }
+ else {
+ while (h < n)
+ h = 3 * h + 1;
+ h /= 3;
+ h /= 3;
+ }
+
+ while (h > 0)
+ {
+ for (i = 0; i < n; i++) {
+ t = a[i];
+ k = i - h;
+ while (k >= 0 && a[k]->x > t->x) {
+ a[k + h] = a[k];
+ k -= h;
+ }
+ a[k + h] = t;
+ }
+
+ h /= 3;
+ }
+}
+
+static int
+insert_active(fz_gel *gel, int y, int *e_)
+{
+ int h_min = INT_MAX;
+ int e = *e_;
+
+ /* insert edges that start here */
+ if (e < gel->len && gel->edges[e].y == y)
+ {
+ do {
+ if (gel->alen + 1 == gel->acap) {
+ int newcap = gel->acap + 64;
+ fz_edge **newactive = fz_resize_array(gel->ctx, gel->active, newcap, sizeof(fz_edge*));
+ gel->active = newactive;
+ gel->acap = newcap;
+ }
+ gel->active[gel->alen++] = &gel->edges[e++];
+ } while (e < gel->len && gel->edges[e].y == y);
+ *e_ = e;
+ }
+
+ if (e < gel->len)
+ h_min = gel->edges[e].y - y;
+
+ for (e=0; e < gel->alen; e++)
+ {
+ if (gel->active[e]->xmove != 0 || gel->active[e]->adj_up != 0)
+ {
+ h_min = 1;
+ break;
+ }
+ if (gel->active[e]->h < h_min)
+ {
+ h_min = gel->active[e]->h;
+ if (h_min == 1)
+ break;
+ }
+ }
+
+ /* shell-sort the edges by increasing x */
+ sort_active(gel->active, gel->alen);
+
+ return h_min;
+}
+
+static void
+advance_active(fz_gel *gel, int inc)
+{
+ fz_edge *edge;
+ int i = 0;
+
+ while (i < gel->alen)
+ {
+ edge = gel->active[i];
+
+ edge->h -= inc;
+
+ /* terminator! */
+ if (edge->h == 0) {
+ gel->active[i] = gel->active[--gel->alen];
+ }
+
+ else {
+ edge->x += edge->xmove;
+ edge->e += edge->adj_up;
+ if (edge->e > 0) {
+ edge->x += edge->xdir;
+ edge->e -= edge->adj_down;
+ }
+ i ++;
+ }
+ }
+}
+
+/*
+ * Anti-aliased scan conversion.
+ */
+
+static inline void add_span_aa(fz_aa_context *ctxaa, int *list, int x0, int x1, int xofs, int h)
+{
+ int x0pix, x0sub;
+ int x1pix, x1sub;
+
+ if (x0 == x1)
+ return;
+
+ /* x between 0 and width of bbox */
+ x0 -= xofs;
+ x1 -= xofs;
+
+ /* The cast to unsigned below helps the compiler produce faster
+ * code on ARMs as the multiply by reciprocal trick it uses does not
+ * need to correct for signedness. */
+ x0pix = ((unsigned int)x0) / fz_aa_hscale;
+ x0sub = ((unsigned int)x0) % fz_aa_hscale;
+ x1pix = ((unsigned int)x1) / fz_aa_hscale;
+ x1sub = ((unsigned int)x1) % fz_aa_hscale;
+
+ if (x0pix == x1pix)
+ {
+ list[x0pix] += h*(x1sub - x0sub);
+ list[x0pix+1] += h*(x0sub - x1sub);
+ }
+
+ else
+ {
+ list[x0pix] += h*(fz_aa_hscale - x0sub);
+ list[x0pix+1] += h*x0sub;
+ list[x1pix] += h*(x1sub - fz_aa_hscale);
+ list[x1pix+1] += h*-x1sub;
+ }
+}
+
+static inline void non_zero_winding_aa(fz_gel *gel, int *list, int xofs, int h)
+{
+ int winding = 0;
+ int x = 0;
+ int i;
+ fz_aa_context *ctxaa = gel->ctx->aa;
+
+ for (i = 0; i < gel->alen; i++)
+ {
+ if (!winding && (winding + gel->active[i]->ydir))
+ x = gel->active[i]->x;
+ if (winding && !(winding + gel->active[i]->ydir))
+ add_span_aa(ctxaa, list, x, gel->active[i]->x, xofs, h);
+ winding += gel->active[i]->ydir;
+ }
+}
+
+static inline void even_odd_aa(fz_gel *gel, int *list, int xofs, int h)
+{
+ int even = 0;
+ int x = 0;
+ int i;
+ fz_aa_context *ctxaa = gel->ctx->aa;
+
+ for (i = 0; i < gel->alen; i++)
+ {
+ if (!even)
+ x = gel->active[i]->x;
+ else
+ add_span_aa(ctxaa, list, x, gel->active[i]->x, xofs, h);
+ even = !even;
+ }
+}
+
+static inline void undelta_aa(fz_aa_context *ctxaa, unsigned char * restrict out, int * restrict in, int n)
+{
+ int d = 0;
+ while (n--)
+ {
+ d += *in++;
+ *out++ = AA_SCALE(d);
+ }
+}
+
+static inline void blit_aa(fz_pixmap *dst, int x, int y,
+ unsigned char *mp, int w, unsigned char *color)
+{
+ unsigned char *dp;
+ dp = dst->samples + (unsigned int)(( (y - dst->y) * dst->w + (x - dst->x) ) * dst->n);
+ if (color)
+ fz_paint_span_with_color(dp, mp, dst->n, w, color);
+ else
+ fz_paint_span(dp, mp, 1, w, 255);
+}
+
+static void
+fz_scan_convert_aa(fz_gel *gel, int eofill, const fz_irect *clip,
+ fz_pixmap *dst, unsigned char *color)
+{
+ unsigned char *alphas;
+ int *deltas;
+ int y, e;
+ int yd, yc;
+ fz_context *ctx = gel->ctx;
+ fz_aa_context *ctxaa = ctx->aa;
+ int height, h0, rh;
+
+ int xmin = fz_idiv(gel->bbox.x0, fz_aa_hscale);
+ int xmax = fz_idiv(gel->bbox.x1, fz_aa_hscale) + 1;
+
+ int xofs = xmin * fz_aa_hscale;
+
+ int skipx = clip->x0 - xmin;
+ int clipn = clip->x1 - clip->x0;
+
+ if (gel->len == 0)
+ return;
+
+ assert(clip->x0 >= xmin);
+ assert(clip->x1 <= xmax);
+
+ alphas = fz_malloc_no_throw(ctx, xmax - xmin + 1);
+ deltas = fz_malloc_no_throw(ctx, (xmax - xmin + 1) * sizeof(int));
+ if (alphas == NULL || deltas == NULL)
+ {
+ fz_free(ctx, alphas);
+ fz_free(ctx, deltas);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "scan conversion failed (malloc failure)");
+ }
+ memset(deltas, 0, (xmax - xmin + 1) * sizeof(int));
+ gel->alen = 0;
+
+ /* The theory here is that we have a list of the edges (gel) of length
+ * gel->len. We have an initially empty list of 'active' edges (of
+ * length gel->alen). As we increase y, we move any edge that is
+ * active at this point into the active list. We know that any edge
+ * before index 'e' is either active, or has been retired.
+ * Once the length of the active list is 0, and e has reached gel->len
+ * we know we are finished.
+ *
+ * As we move through the list, we group fz_aa_vscale 'sub scanlines'
+ * into single scanlines, and we blit them.
+ */
+
+ e = 0;
+ y = gel->edges[0].y;
+ yd = fz_idiv(y, fz_aa_vscale);
+
+ /* Quickly skip to the start of the clip region */
+ while (yd < clip->y0 && (gel->alen > 0 || e < gel->len))
+ {
+ /* rh = remaining height = number of subscanlines left to be
+ * inserted into the current scanline, which will be plotted
+ * at yd. */
+ rh = (yd+1)*fz_aa_vscale - y;
+
+ /* height = The number of subscanlines with identical edge
+ * positions (i.e. 1 if we have any non vertical edges). */
+ height = insert_active(gel, y, &e);
+ h0 = height;
+ if (h0 >= rh)
+ {
+ /* We have enough subscanlines to skip to the next
+ * scanline. */
+ h0 -= rh;
+ yd++;
+ }
+ /* Skip any whole scanlines we can */
+ while (yd < clip->y0 && h0 >= fz_aa_vscale)
+ {
+ h0 -= fz_aa_vscale;
+ yd++;
+ }
+ /* If we haven't hit the start of the clip region, then we
+ * have less than a scanline left. */
+ if (yd < clip->y0)
+ {
+ h0 = 0;
+ }
+ height -= h0;
+ advance_active(gel, height);
+
+ y += height;
+ }
+
+ /* Now do the active lines */
+ while (gel->alen > 0 || e < gel->len)
+ {
+ yc = fz_idiv(y, fz_aa_vscale); /* yc = current scanline */
+ /* rh = remaining height = number of subscanlines left to be
+ * inserted into the current scanline, which will be plotted
+ * at yd. */
+ rh = (yc+1)*fz_aa_vscale - y;
+ if (yc != yd)
+ {
+ undelta_aa(ctxaa, alphas, deltas, skipx + clipn);
+ blit_aa(dst, xmin + skipx, yd, alphas + skipx, clipn, color);
+ memset(deltas, 0, (skipx + clipn) * sizeof(int));
+ }
+ yd = yc;
+ if (yd >= clip->y1)
+ break;
+
+ /* height = The number of subscanlines with identical edge
+ * positions (i.e. 1 if we have any non vertical edges). */
+ height = insert_active(gel, y, &e);
+ h0 = height;
+ if (h0 > rh)
+ {
+ if (rh < fz_aa_vscale)
+ {
+ /* We have to finish a scanline off, and we
+ * have more sub scanlines than will fit into
+ * it. */
+ if (eofill)
+ even_odd_aa(gel, deltas, xofs, rh);
+ else
+ non_zero_winding_aa(gel, deltas, xofs, rh);
+ undelta_aa(ctxaa, alphas, deltas, skipx + clipn);
+ blit_aa(dst, xmin + skipx, yd, alphas + skipx, clipn, color);
+ memset(deltas, 0, (skipx + clipn) * sizeof(int));
+ yd++;
+ if (yd >= clip->y1)
+ break;
+ h0 -= rh;
+ }
+ if (h0 > fz_aa_vscale)
+ {
+ /* Calculate the deltas for any completely full
+ * scanlines. */
+ h0 -= fz_aa_vscale;
+ if (eofill)
+ even_odd_aa(gel, deltas, xofs, fz_aa_vscale);
+ else
+ non_zero_winding_aa(gel, deltas, xofs, fz_aa_vscale);
+ undelta_aa(ctxaa, alphas, deltas, skipx + clipn);
+ do
+ {
+ /* Do any successive whole scanlines - no need
+ * to recalculate deltas here. */
+ blit_aa(dst, xmin + skipx, yd, alphas + skipx, clipn, color);
+ yd++;
+ if (yd >= clip->y1)
+ goto clip_ended;
+ h0 -= fz_aa_vscale;
+ }
+ while (h0 > 0);
+ /* If we have exactly one full scanline left
+ * to go, then the deltas/alphas are set up
+ * already. */
+ if (h0 == 0)
+ goto advance;
+ memset(deltas, 0, (skipx + clipn) * sizeof(int));
+ h0 += fz_aa_vscale;
+ }
+ }
+ if (eofill)
+ even_odd_aa(gel, deltas, xofs, h0);
+ else
+ non_zero_winding_aa(gel, deltas, xofs, h0);
+advance:
+ advance_active(gel, height);
+
+ y += height;
+ }
+
+ if (yd < clip->y1)
+ {
+ undelta_aa(ctxaa, alphas, deltas, skipx + clipn);
+ blit_aa(dst, xmin + skipx, yd, alphas + skipx, clipn, color);
+ }
+clip_ended:
+ fz_free(ctx, deltas);
+ fz_free(ctx, alphas);
+}
+
+/*
+ * Sharp (not anti-aliased) scan conversion
+ */
+
+static inline void blit_sharp(int x0, int x1, int y,
+ const fz_irect *clip, fz_pixmap *dst, unsigned char *color)
+{
+ unsigned char *dp;
+ x0 = fz_clampi(x0, dst->x, dst->x + dst->w);
+ x1 = fz_clampi(x1, dst->x, dst->x + dst->w);
+ if (x0 < x1)
+ {
+ dp = dst->samples + (unsigned int)(( (y - dst->y) * dst->w + (x0 - dst->x) ) * dst->n);
+ if (color)
+ fz_paint_solid_color(dp, dst->n, x1 - x0, color);
+ else
+ fz_paint_solid_alpha(dp, x1 - x0, 255);
+ }
+}
+
+static inline void non_zero_winding_sharp(fz_gel *gel, int y,
+ const fz_irect *clip, fz_pixmap *dst, unsigned char *color)
+{
+ int winding = 0;
+ int x = 0;
+ int i;
+ for (i = 0; i < gel->alen; i++)
+ {
+ if (!winding && (winding + gel->active[i]->ydir))
+ x = gel->active[i]->x;
+ if (winding && !(winding + gel->active[i]->ydir))
+ blit_sharp(x, gel->active[i]->x, y, clip, dst, color);
+ winding += gel->active[i]->ydir;
+ }
+}
+
+static inline void even_odd_sharp(fz_gel *gel, int y,
+ const fz_irect *clip, fz_pixmap *dst, unsigned char *color)
+{
+ int even = 0;
+ int x = 0;
+ int i;
+ for (i = 0; i < gel->alen; i++)
+ {
+ if (!even)
+ x = gel->active[i]->x;
+ else
+ blit_sharp(x, gel->active[i]->x, y, clip, dst, color);
+ even = !even;
+ }
+}
+
+static void
+fz_scan_convert_sharp(fz_gel *gel, int eofill, const fz_irect *clip,
+ fz_pixmap *dst, unsigned char *color)
+{
+ int e = 0;
+ int y = gel->edges[0].y;
+ int height;
+
+ gel->alen = 0;
+
+ /* Skip any lines before the clip region */
+ if (y < clip->y0)
+ {
+ while (gel->alen > 0 || e < gel->len)
+ {
+ height = insert_active(gel, y, &e);
+ y += height;
+ if (y >= clip->y0)
+ {
+ height -= y - clip->y0;
+ y = clip->y0;
+ break;
+ }
+ }
+ }
+
+ /* Now process as lines within the clip region */
+ while (gel->alen > 0 || e < gel->len)
+ {
+ height = insert_active(gel, y, &e);
+
+ if (gel->alen == 0)
+ y += height;
+ else
+ {
+ int h;
+ if (height >= clip->y1 - y)
+ height = clip->y1 - y;
+
+ h = height;
+ while (h--)
+ {
+ if (eofill)
+ even_odd_sharp(gel, y, clip, dst, color);
+ else
+ non_zero_winding_sharp(gel, y, clip, dst, color);
+ y++;
+ }
+ }
+ if (y >= clip->y1)
+ break;
+
+ advance_active(gel, height);
+ }
+}
+
+void
+fz_scan_convert(fz_gel *gel, int eofill, const fz_irect *clip,
+ fz_pixmap *dst, unsigned char *color)
+{
+ fz_aa_context *ctxaa = gel->ctx->aa;
+
+ if (fz_aa_bits > 0)
+ fz_scan_convert_aa(gel, eofill, clip, dst, color);
+ else
+ fz_scan_convert_sharp(gel, eofill, clip, dst, color);
+}
diff --git a/source/fitz/draw-glyph.c b/source/fitz/draw-glyph.c
new file mode 100644
index 00000000..b0a52949
--- /dev/null
+++ b/source/fitz/draw-glyph.c
@@ -0,0 +1,241 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+#define MAX_GLYPH_SIZE 256
+#define MAX_CACHE_SIZE (1024*1024)
+
+typedef struct fz_glyph_key_s fz_glyph_key;
+
+struct fz_glyph_cache_s
+{
+ int refs;
+ fz_hash_table *hash;
+ int total;
+};
+
+struct fz_glyph_key_s
+{
+ fz_font *font;
+ int a, b;
+ int c, d;
+ unsigned short gid;
+ unsigned char e, f;
+ int aa;
+};
+
+void
+fz_new_glyph_cache_context(fz_context *ctx)
+{
+ fz_glyph_cache *cache;
+
+ cache = fz_malloc_struct(ctx, fz_glyph_cache);
+ fz_try(ctx)
+ {
+ cache->hash = fz_new_hash_table(ctx, 509, sizeof(fz_glyph_key), FZ_LOCK_GLYPHCACHE);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, cache);
+ fz_rethrow(ctx);
+ }
+ cache->total = 0;
+ cache->refs = 1;
+
+ ctx->glyph_cache = cache;
+}
+
+/* The glyph cache lock is always held when this function is called. */
+static void
+fz_evict_glyph_cache(fz_context *ctx)
+{
+ fz_glyph_cache *cache = ctx->glyph_cache;
+ fz_glyph_key *key;
+ fz_pixmap *pixmap;
+ int i;
+
+ for (i = 0; i < fz_hash_len(ctx, cache->hash); i++)
+ {
+ key = fz_hash_get_key(ctx, cache->hash, i);
+ if (key->font)
+ fz_drop_font(ctx, key->font);
+ pixmap = fz_hash_get_val(ctx, cache->hash, i);
+ if (pixmap)
+ fz_drop_pixmap(ctx, pixmap);
+ }
+
+ cache->total = 0;
+
+ fz_empty_hash(ctx, cache->hash);
+}
+
+void
+fz_drop_glyph_cache_context(fz_context *ctx)
+{
+ if (!ctx->glyph_cache)
+ return;
+
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ ctx->glyph_cache->refs--;
+ if (ctx->glyph_cache->refs == 0)
+ {
+ fz_evict_glyph_cache(ctx);
+ fz_free_hash(ctx, ctx->glyph_cache->hash);
+ fz_free(ctx, ctx->glyph_cache);
+ ctx->glyph_cache = NULL;
+ }
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+}
+
+fz_glyph_cache *
+fz_keep_glyph_cache(fz_context *ctx)
+{
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ ctx->glyph_cache->refs++;
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ return ctx->glyph_cache;
+}
+
+fz_pixmap *
+fz_render_stroked_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, const fz_matrix *ctm, fz_stroke_state *stroke, fz_irect scissor)
+{
+ if (font->ft_face)
+ {
+ if (stroke->dash_len > 0)
+ return NULL;
+ return fz_render_ft_stroked_glyph(ctx, font, gid, trm, ctm, stroke);
+ }
+ return fz_render_glyph(ctx, font, gid, trm, NULL, scissor);
+}
+
+/*
+ Render a glyph and return a bitmap.
+ If the glyph is too large to fit the cache we have two choices:
+ 1) Return NULL so the caller can draw the glyph using an outline.
+ Only supported for freetype fonts.
+ 2) Render a clipped glyph by using the scissor rectangle.
+ Only supported for type 3 fonts.
+ This must not be inserted into the cache.
+ */
+fz_pixmap *
+fz_render_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *ctm, fz_colorspace *model, fz_irect scissor)
+{
+ fz_glyph_cache *cache;
+ fz_glyph_key key;
+ fz_pixmap *val;
+ float size = fz_matrix_expansion(ctm);
+ int do_cache, locked, caching;
+ fz_matrix local_ctm = *ctm;
+
+ fz_var(locked);
+ fz_var(caching);
+
+ if (size <= MAX_GLYPH_SIZE)
+ {
+ scissor = fz_infinite_irect;
+ do_cache = 1;
+ }
+ else
+ {
+ if (font->ft_face)
+ return NULL;
+ do_cache = 0;
+ }
+
+ cache = ctx->glyph_cache;
+
+ memset(&key, 0, sizeof key);
+ key.font = font;
+ key.gid = gid;
+ key.a = local_ctm.a * 65536;
+ key.b = local_ctm.b * 65536;
+ key.c = local_ctm.c * 65536;
+ key.d = local_ctm.d * 65536;
+ key.e = (local_ctm.e - floorf(local_ctm.e)) * 256;
+ key.f = (local_ctm.f - floorf(local_ctm.f)) * 256;
+ key.aa = fz_aa_level(ctx);
+
+ local_ctm.e = floorf(local_ctm.e) + key.e / 256.0f;
+ local_ctm.f = floorf(local_ctm.f) + key.f / 256.0f;
+
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ val = fz_hash_find(ctx, cache->hash, &key);
+ if (val)
+ {
+ fz_keep_pixmap(ctx, val);
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ return val;
+ }
+
+ locked = 1;
+ caching = 0;
+
+ fz_try(ctx)
+ {
+ if (font->ft_face)
+ {
+ val = fz_render_ft_glyph(ctx, font, gid, &local_ctm, key.aa);
+ }
+ else if (font->t3procs)
+ {
+ /* We drop the glyphcache here, and execute the t3
+ * glyph code. The danger here is that some other
+ * thread will come along, and want the same glyph
+ * too. If it does, we may both end up rendering
+ * pixmaps. We cope with this later on, by ensuring
+ * that only one gets inserted into the cache. If
+ * we insert ours to find one already there, we
+ * abandon ours, and use the one there already.
+ */
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ locked = 0;
+ val = fz_render_t3_glyph(ctx, font, gid, &local_ctm, model, scissor);
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ locked = 1;
+ }
+ else
+ {
+ fz_warn(ctx, "assert: uninitialized font structure");
+ val = NULL;
+ }
+ if (val && do_cache)
+ {
+ if (val->w < MAX_GLYPH_SIZE && val->h < MAX_GLYPH_SIZE)
+ {
+ fz_pixmap *pix;
+
+ /* If we throw an exception whilst caching,
+ * just ignore the exception and carry on. */
+ caching = 1;
+ if (cache->total + val->w * val->h > MAX_CACHE_SIZE)
+ fz_evict_glyph_cache(ctx);
+
+ pix = fz_hash_insert(ctx, cache->hash, &key, val);
+ if (pix)
+ {
+ fz_drop_pixmap(ctx, val);
+ val = pix;
+ }
+ else
+ {
+ fz_keep_font(ctx, key.font);
+ cache->total += val->w * val->h;
+ }
+ val = fz_keep_pixmap(ctx, val);
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ if (locked)
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ }
+ fz_catch(ctx)
+ {
+ if (caching)
+ fz_warn(ctx, "cannot encache glyph; continuing");
+ else
+ fz_rethrow(ctx);
+ }
+
+ return val;
+}
diff --git a/source/fitz/draw-imp.h b/source/fitz/draw-imp.h
new file mode 100644
index 00000000..ca3f2c81
--- /dev/null
+++ b/source/fitz/draw-imp.h
@@ -0,0 +1,46 @@
+#ifndef MUPDF_DRAW_IMP_H
+#define MUPDF_DRAW_IMP_H
+
+/*
+ * Scan converter
+ */
+
+typedef struct fz_gel_s fz_gel;
+
+fz_gel *fz_new_gel(fz_context *ctx);
+void fz_insert_gel(fz_gel *gel, float x0, float y0, float x1, float y1);
+void fz_reset_gel(fz_gel *gel, const fz_irect *clip);
+void fz_sort_gel(fz_gel *gel);
+fz_irect *fz_bound_gel(const fz_gel *gel, fz_irect *bbox);
+void fz_free_gel(fz_gel *gel);
+int fz_is_rect_gel(fz_gel *gel);
+
+void fz_scan_convert(fz_gel *gel, int eofill, const fz_irect *clip, fz_pixmap *pix, unsigned char *colorbv);
+
+void fz_flatten_fill_path(fz_gel *gel, fz_path *path, const fz_matrix *ctm, float flatness);
+void fz_flatten_stroke_path(fz_gel *gel, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth);
+void fz_flatten_dash_path(fz_gel *gel, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth);
+
+fz_irect *fz_bound_path_accurate(fz_context *ctx, fz_irect *bbox, const fz_irect *scissor, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth);
+
+/*
+ * Plotting functions.
+ */
+
+void fz_paint_solid_alpha(unsigned char * restrict dp, int w, int alpha);
+void fz_paint_solid_color(unsigned char * restrict dp, int n, int w, unsigned char *color);
+
+void fz_paint_span(unsigned char * restrict dp, unsigned char * restrict sp, int n, int w, int alpha);
+void fz_paint_span_with_color(unsigned char * restrict dp, unsigned char * restrict mp, int n, int w, unsigned char *color);
+
+void fz_paint_image(fz_pixmap *dst, const fz_irect *scissor, fz_pixmap *shape, fz_pixmap *img, const fz_matrix *ctm, int alpha);
+void fz_paint_image_with_color(fz_pixmap *dst, const fz_irect *scissor, fz_pixmap *shape, fz_pixmap *img, const fz_matrix *ctm, unsigned char *colorbv);
+
+void fz_paint_pixmap(fz_pixmap *dst, fz_pixmap *src, int alpha);
+void fz_paint_pixmap_with_mask(fz_pixmap *dst, fz_pixmap *src, fz_pixmap *msk);
+void fz_paint_pixmap_with_bbox(fz_pixmap *dst, fz_pixmap *src, int alpha, fz_irect bbox);
+
+void fz_blend_pixmap(fz_pixmap *dst, fz_pixmap *src, int alpha, int blendmode, int isolated, fz_pixmap *shape);
+void fz_blend_pixel(unsigned char dp[3], unsigned char bp[3], unsigned char sp[3], int blendmode);
+
+#endif
diff --git a/source/fitz/draw-mesh.c b/source/fitz/draw-mesh.c
new file mode 100644
index 00000000..c0ad45d2
--- /dev/null
+++ b/source/fitz/draw-mesh.c
@@ -0,0 +1,273 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+enum { MAXN = 2 + FZ_MAX_COLORS };
+
+static void paint_scan(fz_pixmap *restrict pix, int y, int fx0, int fx1, int cx0, int cx1, const int *restrict v0, const int *restrict v1, int n)
+{
+ unsigned char *p;
+ int c[MAXN], dc[MAXN];
+ int k, w;
+ float div, mul;
+ int x0, x1;
+
+ /* Ensure that fx0 is left edge, and fx1 is right */
+ if (fx0 > fx1)
+ {
+ const int *v;
+ int t = fx0; fx0 = fx1; fx1 = t;
+ v = v0; v0 = v1; v1 = v;
+ }
+ else if (fx0 == fx1)
+ return;
+
+ /* Clip fx0, fx1 to range */
+ if (fx0 >= cx1)
+ return;
+ if (fx1 <= cx0)
+ return;
+ x0 = (fx0 > cx0 ? fx0 : cx0);
+ x1 = (fx1 < cx1 ? fx1 : cx1);
+
+ w = x1 - x0;
+ if (w == 0)
+ return;
+
+ div = 1.0f / (fx1 - fx0);
+ mul = (x0 - fx0);
+ for (k = 0; k < n; k++)
+ {
+ dc[k] = (v1[k] - v0[k]) * div;
+ c[k] = v0[k] + dc[k] * mul;
+ }
+
+ p = pix->samples + ((x0 - pix->x) + (y - pix->y) * pix->w) * pix->n;
+ while (w--)
+ {
+ for (k = 0; k < n; k++)
+ {
+ *p++ = c[k]>>16;
+ c[k] += dc[k];
+ }
+ *p++ = 255;
+ }
+}
+
+typedef struct edge_data_s edge_data;
+
+struct edge_data_s
+{
+ float x;
+ float dx;
+ int v[2*MAXN];
+};
+
+static inline void prepare_edge(const float *restrict vtop, const float *restrict vbot, edge_data *restrict edge, float y, int n)
+{
+ float r = 1.0f / (vbot[1] - vtop[1]);
+ float t = (y - vtop[1]) * r;
+ float diff = vbot[0] - vtop[0];
+ int i;
+
+ edge->x = vtop[0] + diff * t;
+ edge->dx = diff * r;
+
+ for (i = 0; i < n; i++)
+ {
+ diff = vbot[i+2] - vtop[i+2];
+ edge->v[i] = (int)(65536.0f * (vtop[i+2] + diff * t));
+ edge->v[i+MAXN] = (int)(65536.0f * diff * r);
+ }
+}
+
+static inline void step_edge(edge_data *edge, int n)
+{
+ int i;
+
+ edge->x += edge->dx;
+
+ for (i = 0; i < n; i++)
+ {
+ edge->v[i] += edge->v[i + MAXN];
+ }
+}
+
+static void
+fz_paint_triangle(fz_pixmap *pix, float v[3][MAXN], int n, const fz_irect *bbox)
+{
+ edge_data e0, e1;
+ int top, mid, bot;
+ float y, y1;
+ int minx, maxx;
+
+ top = bot = 0;
+ if (v[1][1] < v[0][1]) top = 1; else bot = 1;
+ if (v[2][1] < v[top][1]) top = 2;
+ else if (v[2][1] > v[bot][1]) bot = 2;
+ if (v[top][1] == v[bot][1]) return;
+
+ /* Test if the triangle is completely outside the scissor rect */
+ if (v[bot][1] < bbox->y0) return;
+ if (v[top][1] > bbox->y1) return;
+
+ /* Magic! Ensure that mid/top/bot are all different */
+ mid = 3^top^bot;
+
+ assert(top != bot && top != mid && mid != bot);
+
+ minx = fz_maxi(bbox->x0, pix->x);
+ maxx = fz_mini(bbox->x1, pix->x + pix->w);
+
+ y = ceilf(fz_max(bbox->y0, v[top][1]));
+ y1 = ceilf(fz_min(bbox->y1, v[mid][1]));
+
+ n -= 2;
+ prepare_edge(v[top], v[bot], &e0, y, n);
+ if (y < y1)
+ {
+ prepare_edge(v[top], v[mid], &e1, y, n);
+
+ do
+ {
+ paint_scan(pix, y, (int)e0.x, (int)e1.x, minx, maxx, &e0.v[0], &e1.v[0], n);
+ step_edge(&e0, n);
+ step_edge(&e1, n);
+ y ++;
+ }
+ while (y < y1);
+ }
+
+ y1 = ceilf(fz_min(bbox->y1, v[bot][1]));
+ if (y < y1)
+ {
+ prepare_edge(v[mid], v[bot], &e1, y, n);
+
+ do
+ {
+ paint_scan(pix, y, (int)e0.x, (int)e1.x, minx, maxx, &e0.v[0], &e1.v[0], n);
+ y ++;
+ if (y >= y1)
+ break;
+ step_edge(&e0, n);
+ step_edge(&e1, n);
+ }
+ while (1);
+ }
+}
+
+struct paint_tri_data
+{
+ fz_context *ctx;
+ fz_shade *shade;
+ fz_pixmap *dest;
+ const fz_irect *bbox;
+};
+
+static void
+do_paint_tri(void *arg, fz_vertex *av, fz_vertex *bv, fz_vertex *cv)
+{
+ struct paint_tri_data *ptd = (struct paint_tri_data *)arg;
+ int i, k;
+ fz_vertex *vertices[3];
+ fz_vertex *v;
+ float *ltri;
+ fz_context *ctx;
+ fz_shade *shade;
+ fz_pixmap *dest;
+ float local[3][MAXN];
+
+ vertices[0] = av;
+ vertices[1] = bv;
+ vertices[2] = cv;
+
+ dest = ptd->dest;
+ ctx = ptd->ctx;
+ shade = ptd->shade;
+ for (k = 0; k < 3; k++)
+ {
+ v = vertices[k];
+ ltri = &local[k][0];
+ ltri[0] = v->p.x;
+ ltri[1] = v->p.y;
+ if (shade->use_function)
+ ltri[2] = v->c[0] * 255;
+ else
+ {
+ fz_convert_color(ctx, dest->colorspace, &ltri[2], shade->colorspace, v->c);
+ for (i = 0; i < dest->colorspace->n; i++)
+ ltri[i + 2] *= 255;
+ }
+ }
+ fz_paint_triangle(dest, local, 2 + dest->colorspace->n, ptd->bbox);
+}
+
+void
+fz_paint_shade(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_pixmap *dest, const fz_irect *bbox)
+{
+ unsigned char clut[256][FZ_MAX_COLORS];
+ fz_pixmap *temp = NULL;
+ fz_pixmap *conv = NULL;
+ float color[FZ_MAX_COLORS];
+ struct paint_tri_data ptd;
+ int i, k;
+ fz_matrix local_ctm;
+
+ fz_var(temp);
+ fz_var(conv);
+
+ fz_try(ctx)
+ {
+ fz_concat(&local_ctm, &shade->matrix, ctm);
+
+ if (shade->use_function)
+ {
+ fz_color_converter cc;
+ fz_lookup_color_converter(&cc, ctx, dest->colorspace, shade->colorspace);
+ for (i = 0; i < 256; i++)
+ {
+ cc.convert(&cc, color, shade->function[i]);
+ for (k = 0; k < dest->colorspace->n; k++)
+ clut[i][k] = color[k] * 255;
+ clut[i][k] = shade->function[i][shade->colorspace->n] * 255;
+ }
+ conv = fz_new_pixmap_with_bbox(ctx, dest->colorspace, bbox);
+ temp = fz_new_pixmap_with_bbox(ctx, fz_device_gray(ctx), bbox);
+ fz_clear_pixmap(ctx, temp);
+ }
+ else
+ {
+ temp = dest;
+ }
+
+ ptd.ctx = ctx;
+ ptd.dest = temp;
+ ptd.shade = shade;
+ ptd.bbox = bbox;
+
+ fz_process_mesh(ctx, shade, &local_ctm, &do_paint_tri, &ptd);
+
+ if (shade->use_function)
+ {
+ unsigned char *s = temp->samples;
+ unsigned char *d = conv->samples;
+ int len = temp->w * temp->h;
+ while (len--)
+ {
+ int v = *s++;
+ int a = fz_mul255(*s++, clut[v][conv->n - 1]);
+ for (k = 0; k < conv->n - 1; k++)
+ *d++ = fz_mul255(clut[v][k], a);
+ *d++ = a;
+ }
+ fz_paint_pixmap(dest, conv, 255);
+ fz_drop_pixmap(ctx, conv);
+ fz_drop_pixmap(ctx, temp);
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_pixmap(ctx, conv);
+ fz_drop_pixmap(ctx, temp);
+ fz_rethrow(ctx);
+ }
+}
diff --git a/source/fitz/draw-paint.c b/source/fitz/draw-paint.c
new file mode 100644
index 00000000..e7cfbdb4
--- /dev/null
+++ b/source/fitz/draw-paint.c
@@ -0,0 +1,479 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+/*
+
+The functions in this file implement various flavours of Porter-Duff blending.
+
+We take the following as definitions:
+
+ Cx = Color (from plane x)
+ ax = Alpha (from plane x)
+ cx = Cx.ax = Premultiplied color (from plane x)
+
+The general PorterDuff blending equation is:
+
+ Blend Z = X op Y cz = Fx.cx + Fy. cy where Fx and Fy depend on op
+
+The two operations we use in this file are: '(X in Y) over Z' and
+'S over Z'. The definitions of the 'over' and 'in' operations are as
+follows:
+
+ For S over Z, Fs = 1, Fz = 1-as
+ For X in Y, Fx = ay, Fy = 0
+
+We have 2 choices; we can either work with premultiplied data, or non
+premultiplied data. Our
+
+First the premultiplied case:
+
+ Let S = (X in Y)
+ Let R = (X in Y) over Z = S over Z
+
+ cs = cx.Fx + cy.Fy (where Fx = ay, Fy = 0)
+ = cx.ay
+ as = ax.Fx + ay.Fy
+ = ax.ay
+
+ cr = cs.Fs + cz.Fz (where Fs = 1, Fz = 1-as)
+ = cs + cz.(1-as)
+ = cx.ay + cz.(1-ax.ay)
+ ar = as.Fs + az.Fz
+ = as + az.(1-as)
+ = ax.ay + az.(1-ax.ay)
+
+This has various nice properties, like not needing any divisions, and
+being symmetric in color and alpha, so this is what we use. Because we
+went through the pain of deriving the non premultiplied forms, we list
+them here too, though they are not used.
+
+Non Pre-multiplied case:
+
+ Cs.as = Fx.Cx.ax + Fy.Cy.ay (where Fx = ay, Fy = 0)
+ = Cx.ay.ax
+ Cs = (Cx.ay.ax)/(ay.ax)
+ = Cx
+ Cr.ar = Fs.Cs.as + Fz.Cz.az (where Fs = 1, Fz = 1-as)
+ = Cs.as + (1-as).Cz.az
+ = Cx.ax.ay + Cz.az.(1-ax.ay)
+ Cr = (Cx.ax.ay + Cz.az.(1-ax.ay))/(ax.ay + az.(1-ax-ay))
+
+Much more complex, it seems. However, if we could restrict ourselves to
+the case where we were always plotting onto an opaque background (i.e.
+az = 1), then:
+
+ Cr = Cx.(ax.ay) + Cz.(1-ax.ay)
+ = (Cx-Cz)*(1-ax.ay) + Cz (a single MLA operation)
+ ar = 1
+
+Sadly, this is not true in the general case, so we abandon this effort
+and stick to using the premultiplied form.
+
+*/
+
+typedef unsigned char byte;
+
+/* These are used by the non-aa scan converter */
+
+void
+fz_paint_solid_alpha(byte * restrict dp, int w, int alpha)
+{
+ int t = FZ_EXPAND(255 - alpha);
+ while (w--)
+ {
+ *dp = alpha + FZ_COMBINE(*dp, t);
+ dp ++;
+ }
+}
+
+void
+fz_paint_solid_color(byte * restrict dp, int n, int w, byte *color)
+{
+ int n1 = n - 1;
+ int sa = FZ_EXPAND(color[n1]);
+ int k;
+ while (w--)
+ {
+ int ma = FZ_COMBINE(FZ_EXPAND(255), sa);
+ for (k = 0; k < n1; k++)
+ dp[k] = FZ_BLEND(color[k], dp[k], ma);
+ dp[k] = FZ_BLEND(255, dp[k], ma);
+ dp += n;
+ }
+}
+
+/* Blend a non-premultiplied color in mask over destination */
+
+static inline void
+fz_paint_span_with_color_2(byte * restrict dp, byte * restrict mp, int w, byte *color)
+{
+ int sa = FZ_EXPAND(color[1]);
+ int g = color[0];
+ while (w--)
+ {
+ int ma = *mp++;
+ ma = FZ_COMBINE(FZ_EXPAND(ma), sa);
+ dp[0] = FZ_BLEND(g, dp[0], ma);
+ dp[1] = FZ_BLEND(255, dp[1], ma);
+ dp += 2;
+ }
+}
+
+static inline void
+fz_paint_span_with_color_4(byte * restrict dp, byte * restrict mp, int w, byte *color)
+{
+ int sa = FZ_EXPAND(color[3]);
+ int r = color[0];
+ int g = color[1];
+ int b = color[2];
+ while (w--)
+ {
+ int ma = *mp++;
+ ma = FZ_COMBINE(FZ_EXPAND(ma), sa);
+ dp[0] = FZ_BLEND(r, dp[0], ma);
+ dp[1] = FZ_BLEND(g, dp[1], ma);
+ dp[2] = FZ_BLEND(b, dp[2], ma);
+ dp[3] = FZ_BLEND(255, dp[3], ma);
+ dp += 4;
+ }
+}
+
+static inline void
+fz_paint_span_with_color_N(byte * restrict dp, byte * restrict mp, int n, int w, byte *color)
+{
+ int n1 = n - 1;
+ int sa = FZ_EXPAND(color[n1]);
+ int k;
+ while (w--)
+ {
+ int ma = *mp++;
+ ma = FZ_COMBINE(FZ_EXPAND(ma), sa);
+ for (k = 0; k < n1; k++)
+ dp[k] = FZ_BLEND(color[k], dp[k], ma);
+ dp[k] = FZ_BLEND(255, dp[k], ma);
+ dp += n;
+ }
+}
+
+void
+fz_paint_span_with_color(byte * restrict dp, byte * restrict mp, int n, int w, byte *color)
+{
+ switch (n)
+ {
+ case 2: fz_paint_span_with_color_2(dp, mp, w, color); break;
+ case 4: fz_paint_span_with_color_4(dp, mp, w, color); break;
+ default: fz_paint_span_with_color_N(dp, mp, n, w, color); break;
+ }
+}
+
+/* Blend source in mask over destination */
+
+static inline void
+fz_paint_span_with_mask_2(byte * restrict dp, byte * restrict sp, byte * restrict mp, int w)
+{
+ while (w--)
+ {
+ int masa;
+ int ma = *mp++;
+ ma = FZ_EXPAND(ma);
+ masa = FZ_COMBINE(sp[1], ma);
+ masa = 255 - masa;
+ masa = FZ_EXPAND(masa);
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ }
+}
+
+static inline void
+fz_paint_span_with_mask_4(byte * restrict dp, byte * restrict sp, byte * restrict mp, int w)
+{
+ while (w--)
+ {
+ int masa;
+ int ma = *mp++;
+ ma = FZ_EXPAND(ma);
+ masa = FZ_COMBINE(sp[3], ma);
+ masa = 255 - masa;
+ masa = FZ_EXPAND(masa);
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ }
+}
+
+static inline void
+fz_paint_span_with_mask_N(byte * restrict dp, byte * restrict sp, byte * restrict mp, int n, int w)
+{
+ while (w--)
+ {
+ int k = n;
+ int masa;
+ int ma = *mp++;
+ ma = FZ_EXPAND(ma);
+ masa = FZ_COMBINE(sp[n-1], ma);
+ masa = 255-masa;
+ masa = FZ_EXPAND(masa);
+ while (k--)
+ {
+ *dp = FZ_COMBINE2(*sp, ma, *dp, masa);
+ sp++; dp++;
+ }
+ }
+}
+
+static void
+fz_paint_span_with_mask(byte * restrict dp, byte * restrict sp, byte * restrict mp, int n, int w)
+{
+ switch (n)
+ {
+ case 2: fz_paint_span_with_mask_2(dp, sp, mp, w); break;
+ case 4: fz_paint_span_with_mask_4(dp, sp, mp, w); break;
+ default: fz_paint_span_with_mask_N(dp, sp, mp, n, w); break;
+ }
+}
+
+/* Blend source in constant alpha over destination */
+
+static inline void
+fz_paint_span_2_with_alpha(byte * restrict dp, byte * restrict sp, int w, int alpha)
+{
+ alpha = FZ_EXPAND(alpha);
+ while (w--)
+ {
+ int masa = FZ_COMBINE(sp[1], alpha);
+ *dp = FZ_BLEND(*sp, *dp, masa);
+ dp++; sp++;
+ *dp = FZ_BLEND(*sp, *dp, masa);
+ dp++; sp++;
+ }
+}
+
+static inline void
+fz_paint_span_4_with_alpha(byte * restrict dp, byte * restrict sp, int w, int alpha)
+{
+ alpha = FZ_EXPAND(alpha);
+ while (w--)
+ {
+ int masa = FZ_COMBINE(sp[3], alpha);
+ *dp = FZ_BLEND(*sp, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_BLEND(*sp, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_BLEND(*sp, *dp, masa);
+ sp++; dp++;
+ *dp = FZ_BLEND(*sp, *dp, masa);
+ sp++; dp++;
+ }
+}
+
+static inline void
+fz_paint_span_N_with_alpha(byte * restrict dp, byte * restrict sp, int n, int w, int alpha)
+{
+ alpha = FZ_EXPAND(alpha);
+ while (w--)
+ {
+ int masa = FZ_COMBINE(sp[n-1], alpha);
+ int k = n;
+ while (k--)
+ {
+ *dp = FZ_BLEND(*sp++, *dp, masa);
+ dp++;
+ }
+ }
+}
+
+/* Blend source over destination */
+
+static inline void
+fz_paint_span_1(byte * restrict dp, byte * restrict sp, int w)
+{
+ while (w--)
+ {
+ int t = FZ_EXPAND(255 - sp[0]);
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp ++;
+ }
+}
+
+static inline void
+fz_paint_span_2(byte * restrict dp, byte * restrict sp, int w)
+{
+ while (w--)
+ {
+ int t = FZ_EXPAND(255 - sp[1]);
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ }
+}
+
+static inline void
+fz_paint_span_4(byte * restrict dp, byte * restrict sp, int w)
+{
+ while (w--)
+ {
+ int t = FZ_EXPAND(255 - sp[3]);
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ }
+}
+
+static inline void
+fz_paint_span_N(byte * restrict dp, byte * restrict sp, int n, int w)
+{
+ while (w--)
+ {
+ int k = n;
+ int t = FZ_EXPAND(255 - sp[n-1]);
+ while (k--)
+ {
+ *dp = *sp++ + FZ_COMBINE(*dp, t);
+ dp++;
+ }
+ }
+}
+
+void
+fz_paint_span(byte * restrict dp, byte * restrict sp, int n, int w, int alpha)
+{
+ if (alpha == 255)
+ {
+ switch (n)
+ {
+ case 1: fz_paint_span_1(dp, sp, w); break;
+ case 2: fz_paint_span_2(dp, sp, w); break;
+ case 4: fz_paint_span_4(dp, sp, w); break;
+ default: fz_paint_span_N(dp, sp, n, w); break;
+ }
+ }
+ else if (alpha > 0)
+ {
+ switch (n)
+ {
+ case 2: fz_paint_span_2_with_alpha(dp, sp, w, alpha); break;
+ case 4: fz_paint_span_4_with_alpha(dp, sp, w, alpha); break;
+ default: fz_paint_span_N_with_alpha(dp, sp, n, w, alpha); break;
+ }
+ }
+}
+
+/*
+ * Pixmap blending functions
+ */
+
+void
+fz_paint_pixmap_with_bbox(fz_pixmap *dst, fz_pixmap *src, int alpha, fz_irect bbox)
+{
+ unsigned char *sp, *dp;
+ int x, y, w, h, n;
+ fz_irect bbox2;
+
+ assert(dst->n == src->n);
+
+ fz_pixmap_bbox_no_ctx(dst, &bbox2);
+ fz_intersect_irect(&bbox, &bbox2);
+ fz_pixmap_bbox_no_ctx(src, &bbox2);
+ fz_intersect_irect(&bbox, &bbox2);
+
+ x = bbox.x0;
+ y = bbox.y0;
+ w = bbox.x1 - bbox.x0;
+ h = bbox.y1 - bbox.y0;
+ if ((w | h) == 0)
+ return;
+
+ n = src->n;
+ sp = src->samples + (unsigned int)(((y - src->y) * src->w + (x - src->x)) * src->n);
+ dp = dst->samples + (unsigned int)(((y - dst->y) * dst->w + (x - dst->x)) * dst->n);
+
+ while (h--)
+ {
+ fz_paint_span(dp, sp, n, w, alpha);
+ sp += src->w * n;
+ dp += dst->w * n;
+ }
+}
+
+void
+fz_paint_pixmap(fz_pixmap *dst, fz_pixmap *src, int alpha)
+{
+ unsigned char *sp, *dp;
+ fz_irect bbox;
+ fz_irect bbox2;
+ int x, y, w, h, n;
+
+ assert(dst->n == src->n);
+
+ fz_pixmap_bbox_no_ctx(dst, &bbox);
+ fz_pixmap_bbox_no_ctx(src, &bbox2);
+ fz_intersect_irect(&bbox, &bbox2);
+
+ x = bbox.x0;
+ y = bbox.y0;
+ w = bbox.x1 - bbox.x0;
+ h = bbox.y1 - bbox.y0;
+ if ((w | h) == 0)
+ return;
+
+ n = src->n;
+ sp = src->samples + (unsigned int)(((y - src->y) * src->w + (x - src->x)) * src->n);
+ dp = dst->samples + (unsigned int)(((y - dst->y) * dst->w + (x - dst->x)) * dst->n);
+
+ while (h--)
+ {
+ fz_paint_span(dp, sp, n, w, alpha);
+ sp += src->w * n;
+ dp += dst->w * n;
+ }
+}
+
+void
+fz_paint_pixmap_with_mask(fz_pixmap *dst, fz_pixmap *src, fz_pixmap *msk)
+{
+ unsigned char *sp, *dp, *mp;
+ fz_irect bbox, bbox2;
+ int x, y, w, h, n;
+
+ assert(dst->n == src->n);
+ assert(msk->n == 1);
+
+ fz_pixmap_bbox_no_ctx(dst, &bbox);
+ fz_pixmap_bbox_no_ctx(src, &bbox2);
+ fz_intersect_irect(&bbox, &bbox2);
+ fz_pixmap_bbox_no_ctx(msk, &bbox2);
+ fz_intersect_irect(&bbox, &bbox2);
+
+ x = bbox.x0;
+ y = bbox.y0;
+ w = bbox.x1 - bbox.x0;
+ h = bbox.y1 - bbox.y0;
+ if ((w | h) == 0)
+ return;
+
+ n = src->n;
+ sp = src->samples + (unsigned int)(((y - src->y) * src->w + (x - src->x)) * src->n);
+ mp = msk->samples + (unsigned int)(((y - msk->y) * msk->w + (x - msk->x)) * msk->n);
+ dp = dst->samples + (unsigned int)(((y - dst->y) * dst->w + (x - dst->x)) * dst->n);
+
+ while (h--)
+ {
+ fz_paint_span_with_mask(dp, sp, mp, n, w);
+ sp += src->w * n;
+ dp += dst->w * n;
+ mp += msk->w;
+ }
+}
diff --git a/source/fitz/draw-path.c b/source/fitz/draw-path.c
new file mode 100644
index 00000000..31b038b1
--- /dev/null
+++ b/source/fitz/draw-path.c
@@ -0,0 +1,831 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+#define MAX_DEPTH 8
+
+static void
+line(fz_gel *gel, const fz_matrix *ctm, float x0, float y0, float x1, float y1)
+{
+ float tx0 = ctm->a * x0 + ctm->c * y0 + ctm->e;
+ float ty0 = ctm->b * x0 + ctm->d * y0 + ctm->f;
+ float tx1 = ctm->a * x1 + ctm->c * y1 + ctm->e;
+ float ty1 = ctm->b * x1 + ctm->d * y1 + ctm->f;
+ fz_insert_gel(gel, tx0, ty0, tx1, ty1);
+}
+
+static void
+bezier(fz_gel *gel, const fz_matrix *ctm, float flatness,
+ float xa, float ya,
+ float xb, float yb,
+ float xc, float yc,
+ float xd, float yd, int depth)
+{
+ float dmax;
+ float xab, yab;
+ float xbc, ybc;
+ float xcd, ycd;
+ float xabc, yabc;
+ float xbcd, ybcd;
+ float xabcd, yabcd;
+
+ /* termination check */
+ dmax = fz_abs(xa - xb);
+ dmax = fz_max(dmax, fz_abs(ya - yb));
+ dmax = fz_max(dmax, fz_abs(xd - xc));
+ dmax = fz_max(dmax, fz_abs(yd - yc));
+ if (dmax < flatness || depth >= MAX_DEPTH)
+ {
+ line(gel, ctm, xa, ya, xd, yd);
+ return;
+ }
+
+ xab = xa + xb;
+ yab = ya + yb;
+ xbc = xb + xc;
+ ybc = yb + yc;
+ xcd = xc + xd;
+ ycd = yc + yd;
+
+ xabc = xab + xbc;
+ yabc = yab + ybc;
+ xbcd = xbc + xcd;
+ ybcd = ybc + ycd;
+
+ xabcd = xabc + xbcd;
+ yabcd = yabc + ybcd;
+
+ xab *= 0.5f; yab *= 0.5f;
+ xbc *= 0.5f; ybc *= 0.5f;
+ xcd *= 0.5f; ycd *= 0.5f;
+
+ xabc *= 0.25f; yabc *= 0.25f;
+ xbcd *= 0.25f; ybcd *= 0.25f;
+
+ xabcd *= 0.125f; yabcd *= 0.125f;
+
+ bezier(gel, ctm, flatness, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1);
+ bezier(gel, ctm, flatness, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1);
+}
+
+void
+fz_flatten_fill_path(fz_gel *gel, fz_path *path, const fz_matrix *ctm, float flatness)
+{
+ float x1, y1, x2, y2, x3, y3;
+ float cx = 0;
+ float cy = 0;
+ float bx = 0;
+ float by = 0;
+ int i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ /* implicit closepath before moveto */
+ if (cx != bx || cy != by)
+ line(gel, ctm, cx, cy, bx, by);
+ x1 = path->items[i++].v;
+ y1 = path->items[i++].v;
+ cx = bx = x1;
+ cy = by = y1;
+ break;
+
+ case FZ_LINETO:
+ x1 = path->items[i++].v;
+ y1 = path->items[i++].v;
+ line(gel, ctm, cx, cy, x1, y1);
+ cx = x1;
+ cy = y1;
+ break;
+
+ case FZ_CURVETO:
+ x1 = path->items[i++].v;
+ y1 = path->items[i++].v;
+ x2 = path->items[i++].v;
+ y2 = path->items[i++].v;
+ x3 = path->items[i++].v;
+ y3 = path->items[i++].v;
+ bezier(gel, ctm, flatness, cx, cy, x1, y1, x2, y2, x3, y3, 0);
+ cx = x3;
+ cy = y3;
+ break;
+
+ case FZ_CLOSE_PATH:
+ line(gel, ctm, cx, cy, bx, by);
+ cx = bx;
+ cy = by;
+ break;
+ }
+ }
+
+ if (cx != bx || cy != by)
+ line(gel, ctm, cx, cy, bx, by);
+}
+
+struct sctx
+{
+ fz_gel *gel;
+ const fz_matrix *ctm;
+ float flatness;
+
+ int linejoin;
+ float linewidth;
+ float miterlimit;
+ fz_point beg[2];
+ fz_point seg[2];
+ int sn, bn;
+ int dot;
+ int from_bezier;
+
+ const float *dash_list;
+ float dash_phase;
+ int dash_len;
+ int toggle, cap;
+ int offset;
+ float phase;
+ fz_point cur;
+};
+
+static void
+fz_add_line(struct sctx *s, float x0, float y0, float x1, float y1)
+{
+ float tx0 = s->ctm->a * x0 + s->ctm->c * y0 + s->ctm->e;
+ float ty0 = s->ctm->b * x0 + s->ctm->d * y0 + s->ctm->f;
+ float tx1 = s->ctm->a * x1 + s->ctm->c * y1 + s->ctm->e;
+ float ty1 = s->ctm->b * x1 + s->ctm->d * y1 + s->ctm->f;
+ fz_insert_gel(s->gel, tx0, ty0, tx1, ty1);
+}
+
+static void
+fz_add_arc(struct sctx *s,
+ float xc, float yc,
+ float x0, float y0,
+ float x1, float y1)
+{
+ float th0, th1, r;
+ float theta;
+ float ox, oy, nx, ny;
+ int n, i;
+
+ r = fabsf(s->linewidth);
+ theta = 2 * (float)M_SQRT2 * sqrtf(s->flatness / r);
+ th0 = atan2f(y0, x0);
+ th1 = atan2f(y1, x1);
+
+ if (r > 0)
+ {
+ if (th0 < th1)
+ th0 += (float)M_PI * 2;
+ n = ceilf((th0 - th1) / theta);
+ }
+ else
+ {
+ if (th1 < th0)
+ th1 += (float)M_PI * 2;
+ n = ceilf((th1 - th0) / theta);
+ }
+
+ ox = x0;
+ oy = y0;
+ for (i = 1; i < n; i++)
+ {
+ theta = th0 + (th1 - th0) * i / n;
+ nx = cosf(theta) * r;
+ ny = sinf(theta) * r;
+ fz_add_line(s, xc + ox, yc + oy, xc + nx, yc + ny);
+ ox = nx;
+ oy = ny;
+ }
+
+ fz_add_line(s, xc + ox, yc + oy, xc + x1, yc + y1);
+}
+
+static void
+fz_add_line_stroke(struct sctx *s, fz_point a, fz_point b)
+{
+ float dx = b.x - a.x;
+ float dy = b.y - a.y;
+ float scale = s->linewidth / sqrtf(dx * dx + dy * dy);
+ float dlx = dy * scale;
+ float dly = -dx * scale;
+ fz_add_line(s, a.x - dlx, a.y - dly, b.x - dlx, b.y - dly);
+ fz_add_line(s, b.x + dlx, b.y + dly, a.x + dlx, a.y + dly);
+}
+
+static void
+fz_add_line_join(struct sctx *s, fz_point a, fz_point b, fz_point c, int join_under)
+{
+ float miterlimit = s->miterlimit;
+ float linewidth = s->linewidth;
+ fz_linejoin linejoin = s->linejoin;
+ float dx0, dy0;
+ float dx1, dy1;
+ float dlx0, dly0;
+ float dlx1, dly1;
+ float dmx, dmy;
+ float dmr2;
+ float scale;
+ float cross;
+ float len0, len1;
+
+ dx0 = b.x - a.x;
+ dy0 = b.y - a.y;
+
+ dx1 = c.x - b.x;
+ dy1 = c.y - b.y;
+
+ cross = dx1 * dy0 - dx0 * dy1;
+ /* Ensure that cross >= 0 */
+ if (cross < 0)
+ {
+ float tmp;
+ tmp = dx1; dx1 = -dx0; dx0 = -tmp;
+ tmp = dy1; dy1 = -dy0; dy0 = -tmp;
+ cross = -cross;
+ }
+
+ len0 = dx0 * dx0 + dy0 * dy0;
+ if (len0 < FLT_EPSILON)
+ linejoin = FZ_LINEJOIN_BEVEL;
+ len1 = dx1 * dx1 + dy1 * dy1;
+ if (len1 < FLT_EPSILON)
+ linejoin = FZ_LINEJOIN_BEVEL;
+
+ scale = linewidth / sqrtf(len0);
+ dlx0 = dy0 * scale;
+ dly0 = -dx0 * scale;
+
+ scale = linewidth / sqrtf(len1);
+ dlx1 = dy1 * scale;
+ dly1 = -dx1 * scale;
+
+ dmx = (dlx0 + dlx1) * 0.5f;
+ dmy = (dly0 + dly1) * 0.5f;
+ dmr2 = dmx * dmx + dmy * dmy;
+
+ if (cross * cross < FLT_EPSILON && dx0 * dx1 + dy0 * dy1 >= 0)
+ linejoin = FZ_LINEJOIN_BEVEL;
+
+ if (join_under)
+ {
+ fz_add_line(s, b.x + dlx1, b.y + dly1, b.x + dlx0, b.y + dly0);
+ }
+ else
+ {
+ fz_add_line(s, b.x + dlx1, b.y + dly1, b.x, b.y);
+ fz_add_line(s, b.x, b.y, b.x + dlx0, b.y + dly0);
+ }
+
+ /* XPS miter joins are clipped at miterlength, rather than simply
+ * being converted to bevelled joins. */
+ if (linejoin == FZ_LINEJOIN_MITER_XPS)
+ {
+ if (cross == 0)
+ linejoin = FZ_LINEJOIN_BEVEL;
+ else if (dmr2 * miterlimit * miterlimit >= linewidth * linewidth)
+ linejoin = FZ_LINEJOIN_MITER;
+ else
+ {
+ float k, t0x, t0y, t1x, t1y;
+ scale = linewidth * linewidth / dmr2;
+ dmx *= scale;
+ dmy *= scale;
+ k = (scale - linewidth * miterlimit / sqrtf(dmr2)) / (scale - 1);
+ t0x = b.x - dmx + k * (dmx - dlx0);
+ t0y = b.y - dmy + k * (dmy - dly0);
+ t1x = b.x - dmx + k * (dmx - dlx1);
+ t1y = b.y - dmy + k * (dmy - dly1);
+
+ fz_add_line(s, b.x - dlx0, b.y - dly0, t0x, t0y);
+ fz_add_line(s, t0x, t0y, t1x, t1y);
+ fz_add_line(s, t1x, t1y, b.x - dlx1, b.y - dly1);
+ }
+ }
+ else if (linejoin == FZ_LINEJOIN_MITER)
+ if (dmr2 * miterlimit * miterlimit < linewidth * linewidth)
+ linejoin = FZ_LINEJOIN_BEVEL;
+
+ if (linejoin == FZ_LINEJOIN_MITER)
+ {
+ scale = linewidth * linewidth / dmr2;
+ dmx *= scale;
+ dmy *= scale;
+
+ fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dmx, b.y - dmy);
+ fz_add_line(s, b.x - dmx, b.y - dmy, b.x - dlx1, b.y - dly1);
+ }
+
+ if (linejoin == FZ_LINEJOIN_BEVEL)
+ {
+ fz_add_line(s, b.x - dlx0, b.y - dly0, b.x - dlx1, b.y - dly1);
+ }
+
+ if (linejoin == FZ_LINEJOIN_ROUND)
+ {
+ fz_add_arc(s, b.x, b.y, -dlx0, -dly0, -dlx1, -dly1);
+ }
+}
+
+static void
+fz_add_line_cap(struct sctx *s, fz_point a, fz_point b, fz_linecap linecap)
+{
+ float flatness = s->flatness;
+ float linewidth = s->linewidth;
+
+ float dx = b.x - a.x;
+ float dy = b.y - a.y;
+
+ float scale = linewidth / sqrtf(dx * dx + dy * dy);
+ float dlx = dy * scale;
+ float dly = -dx * scale;
+
+ if (linecap == FZ_LINECAP_BUTT)
+ fz_add_line(s, b.x - dlx, b.y - dly, b.x + dlx, b.y + dly);
+
+ if (linecap == FZ_LINECAP_ROUND)
+ {
+ int i;
+ int n = ceilf((float)M_PI / (2.0f * (float)M_SQRT2 * sqrtf(flatness / linewidth)));
+ float ox = b.x - dlx;
+ float oy = b.y - dly;
+ for (i = 1; i < n; i++)
+ {
+ float theta = (float)M_PI * i / n;
+ float cth = cosf(theta);
+ float sth = sinf(theta);
+ float nx = b.x - dlx * cth - dly * sth;
+ float ny = b.y - dly * cth + dlx * sth;
+ fz_add_line(s, ox, oy, nx, ny);
+ ox = nx;
+ oy = ny;
+ }
+ fz_add_line(s, ox, oy, b.x + dlx, b.y + dly);
+ }
+
+ if (linecap == FZ_LINECAP_SQUARE)
+ {
+ fz_add_line(s, b.x - dlx, b.y - dly,
+ b.x - dlx - dly, b.y - dly + dlx);
+ fz_add_line(s, b.x - dlx - dly, b.y - dly + dlx,
+ b.x + dlx - dly, b.y + dly + dlx);
+ fz_add_line(s, b.x + dlx - dly, b.y + dly + dlx,
+ b.x + dlx, b.y + dly);
+ }
+
+ if (linecap == FZ_LINECAP_TRIANGLE)
+ {
+ float mx = -dly;
+ float my = dlx;
+ fz_add_line(s, b.x - dlx, b.y - dly, b.x + mx, b.y + my);
+ fz_add_line(s, b.x + mx, b.y + my, b.x + dlx, b.y + dly);
+ }
+}
+
+static void
+fz_add_line_dot(struct sctx *s, fz_point a)
+{
+ float flatness = s->flatness;
+ float linewidth = s->linewidth;
+ int n = ceilf((float)M_PI / ((float)M_SQRT2 * sqrtf(flatness / linewidth)));
+ float ox = a.x - linewidth;
+ float oy = a.y;
+ int i;
+
+ for (i = 1; i < n; i++)
+ {
+ float theta = (float)M_PI * 2 * i / n;
+ float cth = cosf(theta);
+ float sth = sinf(theta);
+ float nx = a.x - cth * linewidth;
+ float ny = a.y + sth * linewidth;
+ fz_add_line(s, ox, oy, nx, ny);
+ ox = nx;
+ oy = ny;
+ }
+
+ fz_add_line(s, ox, oy, a.x - linewidth, a.y);
+}
+
+static void
+fz_stroke_flush(struct sctx *s, fz_linecap start_cap, fz_linecap end_cap)
+{
+ if (s->sn == 2)
+ {
+ fz_add_line_cap(s, s->beg[1], s->beg[0], start_cap);
+ fz_add_line_cap(s, s->seg[0], s->seg[1], end_cap);
+ }
+ else if (s->dot)
+ {
+ fz_add_line_dot(s, s->beg[0]);
+ }
+}
+
+static void
+fz_stroke_moveto(struct sctx *s, fz_point cur)
+{
+ s->seg[0] = cur;
+ s->beg[0] = cur;
+ s->sn = 1;
+ s->bn = 1;
+ s->dot = 0;
+ s->from_bezier = 0;
+}
+
+static void
+fz_stroke_lineto(struct sctx *s, fz_point cur, int from_bezier)
+{
+ float dx = cur.x - s->seg[s->sn-1].x;
+ float dy = cur.y - s->seg[s->sn-1].y;
+
+ if (dx * dx + dy * dy < FLT_EPSILON)
+ {
+ if (s->cap == FZ_LINECAP_ROUND || s->dash_list)
+ s->dot = 1;
+ return;
+ }
+
+ fz_add_line_stroke(s, s->seg[s->sn-1], cur);
+
+ if (s->sn == 2)
+ {
+ fz_add_line_join(s, s->seg[0], s->seg[1], cur, s->from_bezier & from_bezier);
+ s->seg[0] = s->seg[1];
+ s->seg[1] = cur;
+ }
+ s->from_bezier = from_bezier;
+
+ if (s->sn == 1)
+ s->seg[s->sn++] = cur;
+ if (s->bn == 1)
+ s->beg[s->bn++] = cur;
+}
+
+static void
+fz_stroke_closepath(struct sctx *s)
+{
+ if (s->sn == 2)
+ {
+ fz_stroke_lineto(s, s->beg[0], 0);
+ if (s->seg[1].x == s->beg[0].x && s->seg[1].y == s->beg[0].y)
+ fz_add_line_join(s, s->seg[0], s->beg[0], s->beg[1], 0);
+ else
+ fz_add_line_join(s, s->seg[1], s->beg[0], s->beg[1], 0);
+ }
+ else if (s->dot)
+ {
+ fz_add_line_dot(s, s->beg[0]);
+ }
+
+ s->seg[0] = s->beg[0];
+ s->bn = 1;
+ s->sn = 1;
+ s->dot = 0;
+ s->from_bezier = 0;
+}
+
+static void
+fz_stroke_bezier(struct sctx *s,
+ float xa, float ya,
+ float xb, float yb,
+ float xc, float yc,
+ float xd, float yd, int depth)
+{
+ float dmax;
+ float xab, yab;
+ float xbc, ybc;
+ float xcd, ycd;
+ float xabc, yabc;
+ float xbcd, ybcd;
+ float xabcd, yabcd;
+
+ /* termination check */
+ dmax = fz_abs(xa - xb);
+ dmax = fz_max(dmax, fz_abs(ya - yb));
+ dmax = fz_max(dmax, fz_abs(xd - xc));
+ dmax = fz_max(dmax, fz_abs(yd - yc));
+ if (dmax < s->flatness || depth >= MAX_DEPTH)
+ {
+ fz_point p;
+ p.x = xd;
+ p.y = yd;
+ fz_stroke_lineto(s, p, 1);
+ return;
+ }
+
+ xab = xa + xb;
+ yab = ya + yb;
+ xbc = xb + xc;
+ ybc = yb + yc;
+ xcd = xc + xd;
+ ycd = yc + yd;
+
+ xabc = xab + xbc;
+ yabc = yab + ybc;
+ xbcd = xbc + xcd;
+ ybcd = ybc + ycd;
+
+ xabcd = xabc + xbcd;
+ yabcd = yabc + ybcd;
+
+ xab *= 0.5f; yab *= 0.5f;
+ xbc *= 0.5f; ybc *= 0.5f;
+ xcd *= 0.5f; ycd *= 0.5f;
+
+ xabc *= 0.25f; yabc *= 0.25f;
+ xbcd *= 0.25f; ybcd *= 0.25f;
+
+ xabcd *= 0.125f; yabcd *= 0.125f;
+
+ fz_stroke_bezier(s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1);
+ fz_stroke_bezier(s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1);
+}
+
+void
+fz_flatten_stroke_path(fz_gel *gel, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth)
+{
+ struct sctx s;
+ fz_point p0, p1, p2, p3;
+ int i;
+
+ s.gel = gel;
+ s.ctm = ctm;
+ s.flatness = flatness;
+
+ s.linejoin = stroke->linejoin;
+ s.linewidth = linewidth * 0.5f; /* hairlines use a different value from the path value */
+ s.miterlimit = stroke->miterlimit;
+ s.sn = 0;
+ s.bn = 0;
+ s.dot = 0;
+
+ s.dash_list = NULL;
+ s.dash_phase = 0;
+ s.dash_len = 0;
+ s.toggle = 0;
+ s.offset = 0;
+ s.phase = 0;
+
+ s.cap = stroke->start_cap;
+
+ i = 0;
+
+ if (path->len > 0 && path->items[0].k != FZ_MOVETO)
+ return;
+
+ p0.x = p0.y = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ p1.x = path->items[i++].v;
+ p1.y = path->items[i++].v;
+ fz_stroke_flush(&s, stroke->start_cap, stroke->end_cap);
+ fz_stroke_moveto(&s, p1);
+ p0 = p1;
+ break;
+
+ case FZ_LINETO:
+ p1.x = path->items[i++].v;
+ p1.y = path->items[i++].v;
+ fz_stroke_lineto(&s, p1, 0);
+ p0 = p1;
+ break;
+
+ case FZ_CURVETO:
+ p1.x = path->items[i++].v;
+ p1.y = path->items[i++].v;
+ p2.x = path->items[i++].v;
+ p2.y = path->items[i++].v;
+ p3.x = path->items[i++].v;
+ p3.y = path->items[i++].v;
+ fz_stroke_bezier(&s, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, 0);
+ p0 = p3;
+ break;
+
+ case FZ_CLOSE_PATH:
+ fz_stroke_closepath(&s);
+ break;
+ }
+ }
+
+ fz_stroke_flush(&s, stroke->start_cap, stroke->end_cap);
+}
+
+static void
+fz_dash_moveto(struct sctx *s, fz_point a, fz_linecap start_cap, fz_linecap end_cap)
+{
+ s->toggle = 1;
+ s->offset = 0;
+ s->phase = s->dash_phase;
+
+ while (s->phase >= s->dash_list[s->offset])
+ {
+ s->toggle = !s->toggle;
+ s->phase -= s->dash_list[s->offset];
+ s->offset ++;
+ if (s->offset == s->dash_len)
+ s->offset = 0;
+ }
+
+ s->cur = a;
+
+ if (s->toggle)
+ {
+ fz_stroke_flush(s, s->cap, end_cap);
+ s->cap = start_cap;
+ fz_stroke_moveto(s, a);
+ }
+}
+
+static void
+fz_dash_lineto(struct sctx *s, fz_point b, int dash_cap, int from_bezier)
+{
+ float dx, dy;
+ float total, used, ratio;
+ fz_point a;
+ fz_point m;
+
+ a = s->cur;
+ dx = b.x - a.x;
+ dy = b.y - a.y;
+ total = sqrtf(dx * dx + dy * dy);
+ used = 0;
+
+ while (total - used > s->dash_list[s->offset] - s->phase)
+ {
+ used += s->dash_list[s->offset] - s->phase;
+ ratio = used / total;
+ m.x = a.x + ratio * dx;
+ m.y = a.y + ratio * dy;
+
+ if (s->toggle)
+ {
+ fz_stroke_lineto(s, m, from_bezier);
+ }
+ else
+ {
+ fz_stroke_flush(s, s->cap, dash_cap);
+ s->cap = dash_cap;
+ fz_stroke_moveto(s, m);
+ }
+
+ s->toggle = !s->toggle;
+ s->phase = 0;
+ s->offset ++;
+ if (s->offset == s->dash_len)
+ s->offset = 0;
+ }
+
+ s->phase += total - used;
+
+ s->cur = b;
+
+ if (s->toggle)
+ {
+ fz_stroke_lineto(s, b, from_bezier);
+ }
+}
+
+static void
+fz_dash_bezier(struct sctx *s,
+ float xa, float ya,
+ float xb, float yb,
+ float xc, float yc,
+ float xd, float yd, int depth,
+ int dash_cap)
+{
+ float dmax;
+ float xab, yab;
+ float xbc, ybc;
+ float xcd, ycd;
+ float xabc, yabc;
+ float xbcd, ybcd;
+ float xabcd, yabcd;
+
+ /* termination check */
+ dmax = fz_abs(xa - xb);
+ dmax = fz_max(dmax, fz_abs(ya - yb));
+ dmax = fz_max(dmax, fz_abs(xd - xc));
+ dmax = fz_max(dmax, fz_abs(yd - yc));
+ if (dmax < s->flatness || depth >= MAX_DEPTH)
+ {
+ fz_point p;
+ p.x = xd;
+ p.y = yd;
+ fz_dash_lineto(s, p, dash_cap, 1);
+ return;
+ }
+
+ xab = xa + xb;
+ yab = ya + yb;
+ xbc = xb + xc;
+ ybc = yb + yc;
+ xcd = xc + xd;
+ ycd = yc + yd;
+
+ xabc = xab + xbc;
+ yabc = yab + ybc;
+ xbcd = xbc + xcd;
+ ybcd = ybc + ycd;
+
+ xabcd = xabc + xbcd;
+ yabcd = yabc + ybcd;
+
+ xab *= 0.5f; yab *= 0.5f;
+ xbc *= 0.5f; ybc *= 0.5f;
+ xcd *= 0.5f; ycd *= 0.5f;
+
+ xabc *= 0.25f; yabc *= 0.25f;
+ xbcd *= 0.25f; ybcd *= 0.25f;
+
+ xabcd *= 0.125f; yabcd *= 0.125f;
+
+ fz_dash_bezier(s, xa, ya, xab, yab, xabc, yabc, xabcd, yabcd, depth + 1, dash_cap);
+ fz_dash_bezier(s, xabcd, yabcd, xbcd, ybcd, xcd, ycd, xd, yd, depth + 1, dash_cap);
+}
+
+void
+fz_flatten_dash_path(fz_gel *gel, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, float flatness, float linewidth)
+{
+ struct sctx s;
+ fz_point p0, p1, p2, p3, beg;
+ float phase_len, max_expand;
+ int i;
+
+ s.gel = gel;
+ s.ctm = ctm;
+ s.flatness = flatness;
+
+ s.linejoin = stroke->linejoin;
+ s.linewidth = linewidth * 0.5f;
+ s.miterlimit = stroke->miterlimit;
+ s.sn = 0;
+ s.bn = 0;
+ s.dot = 0;
+
+ s.dash_list = stroke->dash_list;
+ s.dash_phase = stroke->dash_phase;
+ s.dash_len = stroke->dash_len;
+ s.toggle = 0;
+ s.offset = 0;
+ s.phase = 0;
+
+ s.cap = stroke->start_cap;
+
+ if (path->len > 0 && path->items[0].k != FZ_MOVETO)
+ return;
+
+ phase_len = 0;
+ for (i = 0; i < stroke->dash_len; i++)
+ phase_len += stroke->dash_list[i];
+ max_expand = fz_matrix_max_expansion(ctm);
+ if (phase_len < 0.01f || phase_len * max_expand < 0.5f)
+ {
+ fz_flatten_stroke_path(gel, path, stroke, ctm, flatness, linewidth);
+ return;
+ }
+
+ p0.x = p0.y = 0;
+ i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ p1.x = path->items[i++].v;
+ p1.y = path->items[i++].v;
+ fz_dash_moveto(&s, p1, stroke->start_cap, stroke->end_cap);
+ beg = p0 = p1;
+ break;
+
+ case FZ_LINETO:
+ p1.x = path->items[i++].v;
+ p1.y = path->items[i++].v;
+ fz_dash_lineto(&s, p1, stroke->dash_cap, 0);
+ p0 = p1;
+ break;
+
+ case FZ_CURVETO:
+ p1.x = path->items[i++].v;
+ p1.y = path->items[i++].v;
+ p2.x = path->items[i++].v;
+ p2.y = path->items[i++].v;
+ p3.x = path->items[i++].v;
+ p3.y = path->items[i++].v;
+ fz_dash_bezier(&s, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, 0, stroke->dash_cap);
+ p0 = p3;
+ break;
+
+ case FZ_CLOSE_PATH:
+ fz_dash_lineto(&s, beg, stroke->dash_cap, 0);
+ p0 = p1 = beg;
+ break;
+ }
+ }
+
+ fz_stroke_flush(&s, s.cap, stroke->end_cap);
+}
diff --git a/source/fitz/draw-scale-simple.c b/source/fitz/draw-scale-simple.c
new file mode 100644
index 00000000..08dedf0b
--- /dev/null
+++ b/source/fitz/draw-scale-simple.c
@@ -0,0 +1,1509 @@
+/*
+This code does smooth scaling of a pixmap.
+
+This function returns a new pixmap representing the area starting at (0,0)
+given by taking the source pixmap src, scaling it to width w, and height h,
+and then positioning it at (frac(x),frac(y)).
+
+This is a cut-down version of draw_scale.c that only copes with filters
+that return values strictly in the 0..1 range, and uses bytes for
+intermediate results rather than ints.
+*/
+
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+/* Do we special case handling of single pixel high/wide images? The
+ * 'purest' handling is given by not special casing them, but certain
+ * files that use such images 'stack' them to give full images. Not
+ * special casing them results in then being fainter and giving noticeable
+ * rounding errors.
+ */
+#define SINGLE_PIXEL_SPECIALS
+
+#ifdef DEBUG_SCALING
+#ifdef WIN32
+#include <windows.h>
+static void debug_print(const char *fmt, ...)
+{
+ va_list args;
+ char text[256];
+ va_start(args, fmt);
+ vsprintf(text, fmt, args);
+ va_end(args);
+ OutputDebugStringA(text);
+ printf(text);
+}
+#else
+static void debug_print(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+}
+#endif
+#endif
+#ifdef DEBUG_SCALING
+#define DBUG(A) debug_print A
+#else
+#define DBUG(A) do {} while(0==1)
+#endif
+
+/*
+Consider a row of source samples, src, of width src_w, positioned at x,
+scaled to width dst_w.
+
+src[i] is centred at: x + (i + 0.5)*dst_w/src_w
+
+Therefore the distance between the centre of the jth output pixel and
+the centre of the ith source sample is:
+
+dist[j,i] = j + 0.5 - (x + (i + 0.5)*dst_w/src_w)
+
+When scaling up, therefore:
+
+dst[j] = SUM(filter(dist[j,i]) * src[i])
+ (for all ints i)
+
+This can be simplified by noticing that filters are only non zero within
+a given filter width (henceforth called W). So:
+
+dst[j] = SUM(filter(dist[j,i]) * src[i])
+ (for ints i, s.t. (j*src_w/dst_w)-W < i < (j*src_w/dst_w)+W)
+
+When scaling down, each filtered source sample is stretched to be wider
+to avoid aliasing issues. This effectively reduces the distance between
+centres.
+
+dst[j] = SUM(filter(dist[j,i] * F) * F * src[i])
+ (where F = dst_w/src_w)
+ (for ints i, s.t. (j-W)/F < i < (j+W)/F)
+
+*/
+
+typedef struct fz_scale_filter_s fz_scale_filter;
+
+struct fz_scale_filter_s
+{
+ int width;
+ float (*fn)(fz_scale_filter *, float);
+};
+
+/* Image scale filters */
+
+static float
+triangle(fz_scale_filter *filter, float f)
+{
+ if (f >= 1)
+ return 0;
+ return 1-f;
+}
+
+static float
+box(fz_scale_filter *filter, float f)
+{
+ if (f >= 0.5f)
+ return 0;
+ return 1;
+}
+
+static float
+simple(fz_scale_filter *filter, float x)
+{
+ if (x >= 1)
+ return 0;
+ return 1 + (2*x - 3)*x*x;
+}
+
+fz_scale_filter fz_scale_filter_box = { 1, box };
+fz_scale_filter fz_scale_filter_triangle = { 1, triangle };
+fz_scale_filter fz_scale_filter_simple = { 1, simple };
+
+/*
+We build ourselves a set of tables to contain the precalculated weights
+for a given set of scale settings.
+
+The first dst_w entries in index are the index into index of the
+sets of weight for each destination pixel.
+
+Each of the sets of weights is a set of values consisting of:
+ the minimum source pixel index used for this destination pixel
+ the number of weights used for this destination pixel
+ the weights themselves
+
+So to calculate dst[i] we do the following:
+
+ weights = &index[index[i]];
+ min = *weights++;
+ len = *weights++;
+ dst[i] = 0;
+ while (--len > 0)
+ dst[i] += src[min++] * *weights++
+
+in addition, we guarantee that at the end of this process weights will now
+point to the weights value for dst pixel i+1.
+
+In the simplest version of this algorithm, we would scale the whole image
+horizontally first into a temporary buffer, then scale that temporary
+buffer again vertically to give us our result. Using such a simple
+algorithm would mean that could use the same style of weights for both
+horizontal and vertical scaling.
+
+Unfortunately, this would also require a large temporary buffer,
+particularly in the case where we are scaling up.
+
+We therefore modify the algorithm as follows; we scale scanlines from the
+source image horizontally into a temporary buffer, until we have all the
+contributors for a given output scanline. We then produce that output
+scanline from the temporary buffer. In this way we restrict the height
+of the temporary buffer to a small fraction of the final size.
+
+Unfortunately, this means that the pseudo code for recombining a
+scanline of fully scaled pixels is as follows:
+
+ weights = &index[index[y]];
+ min = *weights++;
+ len = *weights++;
+ for (x=0 to dst_w)
+ min2 = min
+ len2 = len
+ weights2 = weights
+ dst[x] = 0;
+ while (--len2 > 0)
+ dst[x] += temp[x][(min2++) % tmp_buf_height] * *weights2++
+
+i.e. it requires a % operation for every source pixel - this is typically
+expensive.
+
+To avoid this, we alter the order in which vertical weights are stored,
+so that they are ordered in the same order as the temporary buffer lines
+would appear. This simplifies the algorithm to:
+
+ weights = &index[index[y]];
+ min = *weights++;
+ len = *weights++;
+ for (x=0 to dst_w)
+ min2 = 0
+ len2 = len
+ weights2 = weights
+ dst[x] = 0;
+ while (--len2 > 0)
+ dst[x] += temp[i][min2++] * *weights2++
+
+This means that len may be larger than it needs to be (due to the
+possible inclusion of a zero weight row or two), but in practise this
+is only an increase of 1 or 2 at worst.
+
+We implement this by generating the weights as normal (but ensuring we
+leave enough space) and then reordering afterwards.
+
+*/
+
+typedef struct fz_weights_s fz_weights;
+
+/* This structure is accessed from ARM code - bear this in mind before
+ * altering it! */
+struct fz_weights_s
+{
+ int flip; /* true if outputting reversed */
+ int count; /* number of output pixels we have records for in this table */
+ int max_len; /* Maximum number of weights for any one output pixel */
+ int n; /* number of components (src->n) */
+ int new_line; /* True if no weights for the current output pixel */
+ int patch_l; /* How many output pixels we skip over */
+ int index[1];
+};
+
+struct fz_scale_cache_s
+{
+ int src_w;
+ float x;
+ float dst_w;
+ fz_scale_filter *filter;
+ int vertical;
+ int dst_w_int;
+ int patch_l;
+ int patch_r;
+ int n;
+ int flip;
+ fz_weights *weights;
+};
+
+static fz_weights *
+new_weights(fz_context *ctx, fz_scale_filter *filter, int src_w, float dst_w, int patch_w, int n, int flip, int patch_l)
+{
+ int max_len;
+ fz_weights *weights;
+
+ if (src_w > dst_w)
+ {
+ /* Scaling down, so there will be a maximum of
+ * 2*filterwidth*src_w/dst_w src pixels
+ * contributing to each dst pixel. */
+ max_len = (int)ceilf((2 * filter->width * src_w)/dst_w);
+ if (max_len > src_w)
+ max_len = src_w;
+ }
+ else
+ {
+ /* Scaling up, so there will be a maximum of
+ * 2*filterwidth src pixels contributing to each dst pixel.
+ */
+ max_len = 2 * filter->width;
+ }
+ /* We need the size of the struct,
+ * plus patch_w*sizeof(int) for the index
+ * plus (2+max_len)*sizeof(int) for the weights
+ * plus room for an extra set of weights for reordering.
+ */
+ weights = fz_malloc(ctx, sizeof(*weights)+(max_len+3)*(patch_w+1)*sizeof(int));
+ if (!weights)
+ return NULL;
+ weights->count = -1;
+ weights->max_len = max_len;
+ weights->index[0] = patch_w;
+ weights->n = n;
+ weights->patch_l = patch_l;
+ weights->flip = flip;
+ return weights;
+}
+
+/* j is destination pixel in the patch_l..patch_l+patch_w range */
+static void
+init_weights(fz_weights *weights, int j)
+{
+ int index;
+
+ j -= weights->patch_l;
+ assert(weights->count == j-1);
+ weights->count++;
+ weights->new_line = 1;
+ if (j == 0)
+ index = weights->index[0];
+ else
+ {
+ index = weights->index[j-1];
+ index += 2 + weights->index[index+1];
+ }
+ weights->index[j] = index; /* row pointer */
+ weights->index[index] = 0; /* min */
+ weights->index[index+1] = 0; /* len */
+}
+
+static void
+add_weight(fz_weights *weights, int j, int i, fz_scale_filter *filter,
+ float x, float F, float G, int src_w, float dst_w)
+{
+ float dist = j - x + 0.5f - ((i + 0.5f)*dst_w/src_w);
+ float f;
+ int min, len, index, weight;
+
+ dist *= G;
+ if (dist < 0)
+ dist = -dist;
+ f = filter->fn(filter, dist)*F;
+ weight = (int)(256*f+0.5f);
+
+ /* Ensure i is in range */
+ if (i < 0 || i >= src_w)
+ return;
+ if (weight == 0)
+ {
+ /* We add a fudge factor here to allow for extreme downscales
+ * where all the weights round to 0. Ensure that at least one
+ * (arbitrarily the first one) is non zero. */
+ if (weights->new_line && f > 0)
+ weight = 1;
+ else
+ return;
+ }
+
+ DBUG(("add_weight[%d][%d] = %d(%g) dist=%g\n",j,i,weight,f,dist));
+
+ /* Move j from patch_l...patch_l+patch_w range to 0..patch_w range */
+ j -= weights->patch_l;
+ if (weights->new_line)
+ {
+ /* New line */
+ weights->new_line = 0;
+ index = weights->index[j]; /* row pointer */
+ weights->index[index] = i; /* min */
+ weights->index[index+1] = 0; /* len */
+ }
+ index = weights->index[j];
+ min = weights->index[index++];
+ len = weights->index[index++];
+ while (i < min)
+ {
+ /* This only happens in rare cases, but we need to insert
+ * one earlier. In exceedingly rare cases we may need to
+ * insert more than one earlier. */
+ int k;
+
+ for (k = len; k > 0; k--)
+ {
+ weights->index[index+k] = weights->index[index+k-1];
+ }
+ weights->index[index] = 0;
+ min--;
+ len++;
+ weights->index[index-2] = min;
+ weights->index[index-1] = len;
+ }
+ if (i-min >= len)
+ {
+ /* The usual case */
+ while (i-min >= ++len)
+ {
+ weights->index[index+len-1] = 0;
+ }
+ assert(len-1 == i-min);
+ weights->index[index+i-min] = weight;
+ weights->index[index-1] = len;
+ assert(len <= weights->max_len);
+ }
+ else
+ {
+ /* Infrequent case */
+ weights->index[index+i-min] += weight;
+ }
+}
+
+static void
+reorder_weights(fz_weights *weights, int j, int src_w)
+{
+ int idx = weights->index[j - weights->patch_l];
+ int min = weights->index[idx++];
+ int len = weights->index[idx++];
+ int max = weights->max_len;
+ int tmp = idx+max;
+ int i, off;
+
+ /* Copy into the temporary area */
+ memcpy(&weights->index[tmp], &weights->index[idx], sizeof(int)*len);
+
+ /* Pad out if required */
+ assert(len <= max);
+ assert(min+len <= src_w);
+ off = 0;
+ if (len < max)
+ {
+ memset(&weights->index[tmp+len], 0, sizeof(int)*(max-len));
+ len = max;
+ if (min + len > src_w)
+ {
+ off = min + len - src_w;
+ min = src_w - len;
+ weights->index[idx-2] = min;
+ }
+ weights->index[idx-1] = len;
+ }
+
+ /* Copy back into the proper places */
+ for (i = 0; i < len; i++)
+ {
+ weights->index[idx+((min+i+off) % max)] = weights->index[tmp+i];
+ }
+}
+
+/* Due to rounding and edge effects, the sums for the weights sometimes don't
+ * add up to 256. This causes visible rendering effects. Therefore, we take
+ * pains to ensure that they 1) never exceed 256, and 2) add up to exactly
+ * 256 for all pixels that are completely covered. See bug #691629. */
+static void
+check_weights(fz_weights *weights, int j, int w, float x, float wf)
+{
+ int idx, len;
+ int sum = 0;
+ int max = -256;
+ int maxidx = 0;
+ int i;
+
+ idx = weights->index[j - weights->patch_l];
+ idx++; /* min */
+ len = weights->index[idx++];
+
+ for(i=0; i < len; i++)
+ {
+ int v = weights->index[idx++];
+ sum += v;
+ if (v > max)
+ {
+ max = v;
+ maxidx = idx;
+ }
+ }
+ /* If we aren't the first or last pixel, OR if the sum is too big
+ * then adjust it. */
+ if (((j != 0) && (j != w-1)) || (sum > 256))
+ weights->index[maxidx-1] += 256-sum;
+ /* Otherwise, if we are the first pixel, and it's fully covered, then
+ * adjust it. */
+ else if ((j == 0) && (x < 0.0001F) && (sum != 256))
+ weights->index[maxidx-1] += 256-sum;
+ /* Finally, if we are the last pixel, and it's fully covered, then
+ * adjust it. */
+ else if ((j == w-1) && ((float)w-wf < 0.0001F) && (sum != 256))
+ weights->index[maxidx-1] += 256-sum;
+ DBUG(("total weight %d = %d\n", j, sum));
+}
+
+static fz_weights *
+make_weights(fz_context *ctx, int src_w, float x, float dst_w, fz_scale_filter *filter, int vertical, int dst_w_int, int patch_l, int patch_r, int n, int flip, fz_scale_cache *cache)
+{
+ fz_weights *weights;
+ float F, G;
+ float window;
+ int j;
+
+ if (cache)
+ {
+ if (cache->src_w == src_w && cache->x == x && cache->dst_w == dst_w &&
+ cache->filter == filter && cache->vertical == vertical &&
+ cache->dst_w_int == dst_w_int &&
+ cache->patch_l == patch_l && cache->patch_r == patch_r &&
+ cache->n == n && cache->flip == flip)
+ {
+ return cache->weights;
+ }
+ cache->src_w = src_w;
+ cache->x = x;
+ cache->dst_w = dst_w;
+ cache->filter = filter;
+ cache->vertical = vertical;
+ cache->dst_w_int = dst_w_int;
+ cache->patch_l = patch_l;
+ cache->patch_r = patch_r;
+ cache->n = n;
+ cache->flip = flip;
+ fz_free(ctx, cache->weights);
+ cache->weights = NULL;
+ }
+
+ if (dst_w < src_w)
+ {
+ /* Scaling down */
+ F = dst_w / src_w;
+ G = 1;
+ }
+ else
+ {
+ /* Scaling up */
+ F = 1;
+ G = src_w / dst_w;
+ }
+ window = filter->width / F;
+ DBUG(("make_weights src_w=%d x=%g dst_w=%g patch_l=%d patch_r=%d F=%g window=%g\n", src_w, x, dst_w, patch_l, patch_r, F, window));
+ weights = new_weights(ctx, filter, src_w, dst_w, patch_r-patch_l, n, flip, patch_l);
+ if (!weights)
+ return NULL;
+ for (j = patch_l; j < patch_r; j++)
+ {
+ /* find the position of the centre of dst[j] in src space */
+ float centre = (j - x + 0.5f)*src_w/dst_w - 0.5f;
+ int l, r;
+ l = ceilf(centre - window);
+ r = floorf(centre + window);
+ DBUG(("%d: centre=%g l=%d r=%d\n", j, centre, l, r));
+ init_weights(weights, j);
+ for (; l <= r; l++)
+ {
+ add_weight(weights, j, l, filter, x, F, G, src_w, dst_w);
+ }
+ check_weights(weights, j, dst_w_int, x, dst_w);
+ if (vertical)
+ {
+ reorder_weights(weights, j, src_w);
+ }
+ }
+ weights->count++; /* weights->count = dst_w_int now */
+ if (cache)
+ {
+ cache->weights = weights;
+ }
+ return weights;
+}
+
+static void
+scale_row_to_temp(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i, j, n;
+ unsigned char *min;
+ int tmp[FZ_MAX_COLORS];
+ int *t = tmp;
+
+ n = weights->n;
+ for (j = 0; j < n; j++)
+ tmp[j] = 128;
+ if (weights->flip)
+ {
+ dst += (weights->count-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ min = &src[n * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ for (j = n; j > 0; j--)
+ *t++ += *min++ * *contrib;
+ t -= n;
+ contrib++;
+ }
+ for (j = n; j > 0; j--)
+ {
+ *dst++ = (unsigned char)(*t>>8);
+ *t++ = 128;
+ }
+ t -= n;
+ dst -= n*2;
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ min = &src[n * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ for (j = n; j > 0; j--)
+ *t++ += *min++ * *contrib;
+ t -= n;
+ contrib++;
+ }
+ for (j = n; j > 0; j--)
+ {
+ *dst++ = (unsigned char)(*t>>8);
+ *t++ = 128;
+ }
+ t -= n;
+ }
+ }
+}
+
+#ifdef ARCH_ARM
+
+static void
+scale_row_to_temp1(unsigned char *dst, unsigned char *src, fz_weights *weights)
+__attribute__((naked));
+
+static void
+scale_row_to_temp2(unsigned char *dst, unsigned char *src, fz_weights *weights)
+__attribute__((naked));
+
+static void
+scale_row_to_temp4(unsigned char *dst, unsigned char *src, fz_weights *weights)
+__attribute__((naked));
+
+static void
+scale_row_from_temp(unsigned char *dst, unsigned char *src, fz_weights *weights, int width, int row)
+__attribute__((naked));
+
+static void
+scale_row_to_temp1(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r7,r9,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = weights \n"
+ "ldr r12,[r2],#4 @ r12= flip \n"
+ "ldr r3, [r2],#20 @ r3 = count r2 = &index\n"
+ "ldr r4, [r2] @ r4 = index[0] \n"
+ "cmp r12,#0 @ if (flip) \n"
+ "beq 5f @ { \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "add r0, r0, r3 @ dst += count \n"
+ "1: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #128 @ r5 = a = 128 \n"
+ "add r4, r1, r4 @ r4 = min = &src[r4] \n"
+ "subs r9, r9, #1 @ len-- \n"
+ "blt 3f @ while (len >= 0) \n"
+ "2: @ { \n"
+ "ldrgt r6, [r2], #4 @ r6 = *contrib++ \n"
+ "ldrgtb r7, [r4], #1 @ r7 = *min++ \n"
+ "ldr r12,[r2], #4 @ r12 = *contrib++ \n"
+ "ldrb r14,[r4], #1 @ r14 = *min++ \n"
+ "mlagt r5, r6, r7, r5 @ g += r6 * r7 \n"
+ "subs r9, r9, #2 @ r9 = len -= 2 \n"
+ "mla r5, r12,r14,r5 @ g += r14 * r12 \n"
+ "bge 2b @ } \n"
+ "3: \n"
+ "mov r5, r5, lsr #8 @ g >>= 8 \n"
+ "strb r5,[r0, #-1]! @ *--dst=a \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r7,r9,PC} @ pop, return to thumb \n"
+ "5:"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "6:"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #128 @ r5 = a = 128 \n"
+ "add r4, r1, r4 @ r4 = min = &src[r4] \n"
+ "subs r9, r9, #1 @ len-- \n"
+ "blt 9f @ while (len > 0) \n"
+ "7: @ { \n"
+ "ldrgt r6, [r2], #4 @ r6 = *contrib++ \n"
+ "ldrgtb r7, [r4], #1 @ r7 = *min++ \n"
+ "ldr r12,[r2], #4 @ r12 = *contrib++ \n"
+ "ldrb r14,[r4], #1 @ r14 = *min++ \n"
+ "mlagt r5, r6,r7,r5 @ a += r6 * r7 \n"
+ "subs r9, r9, #2 @ r9 = len -= 2 \n"
+ "mla r5, r12,r14,r5 @ a += r14 * r12 \n"
+ "bge 7b @ } \n"
+ "9: \n"
+ "mov r5, r5, LSR #8 @ a >>= 8 \n"
+ "strb r5, [r0], #1 @ *dst++=a \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 6b @ \n"
+ "ldmfd r13!,{r4-r7,r9,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+static void
+scale_row_to_temp2(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r6,r9-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = weights \n"
+ "ldr r12,[r2],#4 @ r12= flip \n"
+ "ldr r3, [r2],#20 @ r3 = count r2 = &index\n"
+ "ldr r4, [r2] @ r4 = index[0] \n"
+ "cmp r12,#0 @ if (flip) \n"
+ "beq 4f @ { \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "add r0, r0, r3, LSL #1 @ dst += 2*count \n"
+ "1: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #128 @ r5 = g = 128 \n"
+ "mov r6, #128 @ r6 = a = 128 \n"
+ "add r4, r1, r4, LSL #1 @ r4 = min = &src[2*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 3f @ { \n"
+ "2: \n"
+ "ldr r14,[r2], #4 @ r14 = *contrib++ \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "ldrb r12,[r4], #1 @ r12 = *min++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "mla r5, r14,r11,r5 @ g += r11 * r14 \n"
+ "mla r6, r14,r12,r6 @ a += r12 * r14 \n"
+ "bgt 2b @ } \n"
+ "3: \n"
+ "mov r5, r5, lsr #8 @ g >>= 8 \n"
+ "mov r6, r6, lsr #8 @ a >>= 8 \n"
+ "strb r5, [r0, #-2]! @ *--dst=a \n"
+ "strb r6, [r0, #1] @ *--dst=g \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r6,r9-r11,PC} @ pop, return to thumb \n"
+ "4:"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "5:"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #128 @ r5 = g = 128 \n"
+ "mov r6, #128 @ r6 = a = 128 \n"
+ "add r4, r1, r4, LSL #1 @ r4 = min = &src[2*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 7f @ { \n"
+ "6: \n"
+ "ldr r14,[r2], #4 @ r10 = *contrib++ \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "ldrb r12,[r4], #1 @ r12 = *min++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "mla r5, r14,r11,r5 @ g += r11 * r14 \n"
+ "mla r6, r14,r12,r6 @ a += r12 * r14 \n"
+ "bgt 6b @ } \n"
+ "7: \n"
+ "mov r5, r5, lsr #8 @ g >>= 8 \n"
+ "mov r6, r6, lsr #8 @ a >>= 8 \n"
+ "strb r5, [r0], #1 @ *dst++=g \n"
+ "strb r6, [r0], #1 @ *dst++=a \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 5b @ \n"
+ "ldmfd r13!,{r4-r6,r9-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+static void
+scale_row_to_temp4(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = weights \n"
+ "ldr r12,[r2],#4 @ r12= flip \n"
+ "ldr r3, [r2],#20 @ r3 = count r2 = &index\n"
+ "ldr r4, [r2] @ r4 = index[0] \n"
+ "ldr r5,=0x00800080 @ r5 = rounding \n"
+ "ldr r6,=0x00FF00FF @ r7 = 0x00FF00FF \n"
+ "cmp r12,#0 @ if (flip) \n"
+ "beq 4f @ { \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "add r0, r0, r3, LSL #2 @ dst += 4*count \n"
+ "1: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r7, r5 @ r7 = b = rounding \n"
+ "mov r8, r5 @ r8 = a = rounding \n"
+ "add r4, r1, r4, LSL #2 @ r4 = min = &src[4*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 3f @ { \n"
+ "2: \n"
+ "ldr r11,[r4], #4 @ r11 = *min++ \n"
+ "ldr r10,[r2], #4 @ r10 = *contrib++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "and r12,r6, r11 @ r12 = __22__00 \n"
+ "and r11,r6, r11,LSR #8 @ r11 = __33__11 \n"
+ "mla r7, r10,r12,r7 @ b += r14 * r10 \n"
+ "mla r8, r10,r11,r8 @ a += r11 * r10 \n"
+ "bgt 2b @ } \n"
+ "3: \n"
+ "and r7, r6, r7, lsr #8 @ r7 = __22__00 \n"
+ "bic r8, r8, r6 @ r8 = 33__11__ \n"
+ "orr r7, r7, r8 @ r7 = 33221100 \n"
+ "str r7, [r0, #-4]! @ *--dst=r \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ "4: \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "5: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r7, r5 @ r7 = b = rounding \n"
+ "mov r8, r5 @ r8 = a = rounding \n"
+ "add r4, r1, r4, LSL #2 @ r4 = min = &src[4*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 7f @ { \n"
+ "6: \n"
+ "ldr r11,[r4], #4 @ r11 = *min++ \n"
+ "ldr r10,[r2], #4 @ r10 = *contrib++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "and r12,r6, r11 @ r12 = __22__00 \n"
+ "and r11,r6, r11,LSR #8 @ r11 = __33__11 \n"
+ "mla r7, r10,r12,r7 @ b += r14 * r10 \n"
+ "mla r8, r10,r11,r8 @ a += r11 * r10 \n"
+ "bgt 6b @ } \n"
+ "7: \n"
+ "and r7, r6, r7, lsr #8 @ r7 = __22__00 \n"
+ "bic r8, r8, r6 @ r8 = 33__11__ \n"
+ "orr r7, r7, r8 @ r7 = 33221100 \n"
+ "str r7, [r0], #4 @ *dst++=r \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 5b @ \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+static void
+scale_row_from_temp(unsigned char *dst, unsigned char *src, fz_weights *weights, int width, int row)
+{
+ asm volatile(
+ ENTER_ARM
+ "ldr r12,[r13] @ r12= row \n"
+ "add r2, r2, #24 @ r2 = weights->index \n"
+ "stmfd r13!,{r4-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = &weights->index[0] \n"
+ "@ r3 = width \n"
+ "@ r12= row \n"
+ "ldr r4, [r2, r12, LSL #2] @ r4 = index[row] \n"
+ "add r2, r2, #4 @ r2 = &index[1] \n"
+ "subs r6, r3, #4 @ r6 = x = width-4 \n"
+ "ldr r14,[r2, r4, LSL #2]! @ r2 = contrib = index[index[row]+1]\n"
+ " @ r14= len = *contrib \n"
+ "blt 4f @ while (x >= 0) { \n"
+#ifndef ARCH_ARM_CAN_LOAD_UNALIGNED
+ "tst r3, #3 @ if ((r3 & 3) \n"
+ "tsteq r1, #3 @ || (r1 & 3)) \n"
+ "bne 4f @ can't do fast code \n"
+#endif
+ "ldr r9, =0x00FF00FF @ r9 = 0x00FF00FF \n"
+ "1: \n"
+ "ldr r7, =0x00800080 @ r5 = val0 = round \n"
+ "stmfd r13!,{r1,r2,r7} @ stash r1,r2,r5 \n"
+ " @ r1 = min = src \n"
+ " @ r2 = contrib2-4 \n"
+ "movs r8, r14 @ r8 = len2 = len \n"
+ "mov r5, r7 @ r7 = val1 = round \n"
+ "ble 3f @ while (len2-- > 0) { \n"
+ "2: \n"
+ "ldr r12,[r1], r3 @ r12 = *min r5 = min += width\n"
+ "ldr r10,[r2, #4]! @ r10 = *contrib2++ \n"
+ "subs r8, r8, #1 @ len2-- \n"
+ "and r11,r9, r12 @ r11= __22__00 \n"
+ "and r12,r9, r12,LSR #8 @ r12= __33__11 \n"
+ "mla r5, r10,r11,r5 @ r5 = val0 += r11 * r10\n"
+ "mla r7, r10,r12,r7 @ r7 = val1 += r12 * r10\n"
+ "bgt 2b @ } \n"
+ "and r5, r9, r5, LSR #8 @ r5 = __22__00 \n"
+ "and r7, r7, r9, LSL #8 @ r7 = 33__11__ \n"
+ "orr r5, r5, r7 @ r5 = 33221100 \n"
+ "3: \n"
+ "ldmfd r13!,{r1,r2,r7} @ restore r1,r2,r7 \n"
+ "subs r6, r6, #4 @ x-- \n"
+ "add r1, r1, #4 @ src++ \n"
+ "str r5, [r0], #4 @ *dst++ = val \n"
+ "bge 1b @ \n"
+ "4: @ } (Less than 4 to go) \n"
+ "adds r6, r6, #4 @ r6 = x += 4 \n"
+ "beq 8f @ if (x == 0) done \n"
+ "5: \n"
+ "mov r5, r1 @ r5 = min = src \n"
+ "mov r7, #128 @ r7 = val = 128 \n"
+ "movs r8, r14 @ r8 = len2 = len \n"
+ "add r9, r2, #4 @ r9 = contrib2 \n"
+ "ble 7f @ while (len2-- > 0) { \n"
+ "6: \n"
+ "ldr r10,[r9], #4 @ r10 = *contrib2++ \n"
+ "ldrb r12,[r5], r3 @ r12 = *min r5 = min += width\n"
+ "subs r8, r8, #1 @ len2-- \n"
+ "@ stall r12 \n"
+ "mla r7, r10,r12,r7 @ val += r12 * r10 \n"
+ "bgt 6b @ } \n"
+ "7: \n"
+ "mov r7, r7, asr #8 @ r7 = val >>= 8 \n"
+ "subs r6, r6, #1 @ x-- \n"
+ "add r1, r1, #1 @ src++ \n"
+ "strb r7, [r0], #1 @ *dst++ = val \n"
+ "bgt 5b @ \n"
+ "8: \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ ".ltorg \n"
+ ENTER_THUMB
+ );
+}
+#else
+
+static void
+scale_row_to_temp1(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i;
+ unsigned char *min;
+
+ assert(weights->n == 1);
+ if (weights->flip)
+ {
+ dst += weights->count;
+ for (i=weights->count; i > 0; i--)
+ {
+ int val = 128;
+ min = &src[*contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ val += *min++ * *contrib++;
+ }
+ *--dst = (unsigned char)(val>>8);
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ int val = 128;
+ min = &src[*contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ val += *min++ * *contrib++;
+ }
+ *dst++ = (unsigned char)(val>>8);
+ }
+ }
+}
+
+static void
+scale_row_to_temp2(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i;
+ unsigned char *min;
+
+ assert(weights->n == 2);
+ if (weights->flip)
+ {
+ dst += 2*weights->count;
+ for (i=weights->count; i > 0; i--)
+ {
+ int c1 = 128;
+ int c2 = 128;
+ min = &src[2 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ c1 += *min++ * *contrib;
+ c2 += *min++ * *contrib++;
+ }
+ *--dst = (unsigned char)(c2>>8);
+ *--dst = (unsigned char)(c1>>8);
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ int c1 = 128;
+ int c2 = 128;
+ min = &src[2 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ c1 += *min++ * *contrib;
+ c2 += *min++ * *contrib++;
+ }
+ *dst++ = (unsigned char)(c1>>8);
+ *dst++ = (unsigned char)(c2>>8);
+ }
+ }
+}
+
+static void
+scale_row_to_temp4(unsigned char *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i;
+ unsigned char *min;
+
+ assert(weights->n == 4);
+ if (weights->flip)
+ {
+ dst += 4*weights->count;
+ for (i=weights->count; i > 0; i--)
+ {
+ int r = 128;
+ int g = 128;
+ int b = 128;
+ int a = 128;
+ min = &src[4 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ r += *min++ * *contrib;
+ g += *min++ * *contrib;
+ b += *min++ * *contrib;
+ a += *min++ * *contrib++;
+ }
+ *--dst = (unsigned char)(a>>8);
+ *--dst = (unsigned char)(b>>8);
+ *--dst = (unsigned char)(g>>8);
+ *--dst = (unsigned char)(r>>8);
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ int r = 128;
+ int g = 128;
+ int b = 128;
+ int a = 128;
+ min = &src[4 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ r += *min++ * *contrib;
+ g += *min++ * *contrib;
+ b += *min++ * *contrib;
+ a += *min++ * *contrib++;
+ }
+ *dst++ = (unsigned char)(r>>8);
+ *dst++ = (unsigned char)(g>>8);
+ *dst++ = (unsigned char)(b>>8);
+ *dst++ = (unsigned char)(a>>8);
+ }
+ }
+}
+
+static void
+scale_row_from_temp(unsigned char *dst, unsigned char *src, fz_weights *weights, int width, int row)
+{
+ int *contrib = &weights->index[weights->index[row]];
+ int len, x;
+
+ contrib++; /* Skip min */
+ len = *contrib++;
+ for (x=width; x > 0; x--)
+ {
+ unsigned char *min = src;
+ int val = 128;
+ int len2 = len;
+ int *contrib2 = contrib;
+
+ while (len2-- > 0)
+ {
+ val += *min * *contrib2++;
+ min += width;
+ }
+ *dst++ = (unsigned char)(val>>8);
+ src++;
+ }
+}
+#endif
+
+#ifdef SINGLE_PIXEL_SPECIALS
+static void
+duplicate_single_pixel(unsigned char *dst, unsigned char *src, int n, int w, int h)
+{
+ int i;
+
+ for (i = n; i > 0; i--)
+ *dst++ = *src++;
+ for (i = (w*h-1)*n; i > 0; i--)
+ {
+ *dst = dst[-n];
+ dst++;
+ }
+}
+
+static void
+scale_single_row(unsigned char *dst, unsigned char *src, fz_weights *weights, int src_w, int h)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int min, len, i, j, n;
+ int tmp[FZ_MAX_COLORS];
+
+ n = weights->n;
+ /* Scale a single row */
+ for (j = 0; j < n; j++)
+ tmp[j] = 128;
+ if (weights->flip)
+ {
+ dst += (weights->count-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ min = *contrib++;
+ len = *contrib++;
+ min *= n;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[min++] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ *dst++ = (unsigned char)(tmp[j]>>8);
+ tmp[j] = 128;
+ }
+ dst -= 2*n;
+ }
+ dst += n * (weights->count+1);
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ min = *contrib++;
+ len = *contrib++;
+ min *= n;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[min++] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ *dst++ = (unsigned char)(tmp[j]>>8);
+ tmp[j] = 128;
+ }
+ }
+ }
+ /* And then duplicate it h times */
+ n *= weights->count;
+ while (--h > 0)
+ {
+ memcpy(dst, dst-n, n);
+ dst += n;
+ }
+}
+
+static void
+scale_single_col(unsigned char *dst, unsigned char *src, fz_weights *weights, int src_w, int n, int w, int flip_y)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int min, len, i, j;
+ int tmp[FZ_MAX_COLORS];
+
+ for (j = 0; j < n; j++)
+ tmp[j] = 128;
+ if (flip_y)
+ {
+ src_w = (src_w-1)*n;
+ w = (w-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ /* Scale the next pixel in the column */
+ min = *contrib++;
+ len = *contrib++;
+ min = src_w-min*n;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[src_w-min+j] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ *dst++ = (unsigned char)(tmp[j]>>8);
+ tmp[j] = 128;
+ }
+ /* And then duplicate it across the row */
+ for (j = w; j > 0; j--)
+ {
+ *dst = dst[-n];
+ dst++;
+ }
+ }
+ }
+ else
+ {
+ w = (w-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ /* Scale the next pixel in the column */
+ min = *contrib++;
+ len = *contrib++;
+ min *= n;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[min++] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ *dst++ = (unsigned char)(tmp[j]>>8);
+ tmp[j] = 128;
+ }
+ /* And then duplicate it across the row */
+ for (j = w; j > 0; j--)
+ {
+ *dst = dst[-n];
+ dst++;
+ }
+ }
+ }
+}
+#endif /* SINGLE_PIXEL_SPECIALS */
+
+fz_pixmap *
+fz_scale_pixmap(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, fz_irect *clip)
+{
+ return fz_scale_pixmap_cached(ctx, src, x, y, w, h, clip, NULL, NULL);
+}
+
+fz_pixmap *
+fz_scale_pixmap_cached(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, const fz_irect *clip, fz_scale_cache *cache_x, fz_scale_cache *cache_y)
+{
+ fz_scale_filter *filter = &fz_scale_filter_simple;
+ fz_weights *contrib_rows = NULL;
+ fz_weights *contrib_cols = NULL;
+ fz_pixmap *output = NULL;
+ unsigned char *temp = NULL;
+ int max_row, temp_span, temp_rows, row;
+ int dst_w_int, dst_h_int, dst_x_int, dst_y_int;
+ int flip_x, flip_y;
+ fz_rect patch;
+
+ fz_var(contrib_cols);
+ fz_var(contrib_rows);
+
+ DBUG(("Scale: (%d,%d) to (%g,%g) at (%g,%g)\n",src->w,src->h,w,h,x,y));
+
+ /* Avoid extreme scales where overflows become problematic. */
+ if (w > (1<<24) || h > (1<<24) || w < -(1<<24) || h < -(1<<24))
+ return NULL;
+
+ /* Clamp small ranges of w and h */
+ if (w <= -1)
+ {
+ }
+ else if (w < 0)
+ {
+ w = -1;
+ }
+ else if (w < 1)
+ {
+ w = 1;
+ }
+ if (h <= -1)
+ {
+ }
+ else if (h < 0)
+ {
+ h = -1;
+ }
+ else if (h < 1)
+ {
+ h = 1;
+ }
+
+ /* Find the destination bbox, width/height, and sub pixel offset,
+ * allowing for whether we're flipping or not. */
+ /* The (x,y) position given describes where the top left corner
+ * of the source image should be mapped to (i.e. where (0,0) in image
+ * space ends up). Also there are differences in the way we scale
+ * horizontally and vertically. When scaling rows horizontally, we
+ * always read forwards through the source, and store either forwards
+ * or in reverse as required. When scaling vertically, we always store
+ * out forwards, but may feed source rows in in a different order.
+ *
+ * Consider the image rectangle 'r' to which the image is mapped,
+ * and the (possibly) larger rectangle 'R', given by expanding 'r' to
+ * complete pixels.
+ *
+ * x can either be r.xmin-R.xmin or R.xmax-r.xmax depending on whether
+ * the image is x flipped or not. Whatever happens 0 <= x < 1.
+ * y is always R.ymax - r.ymax.
+ */
+ /* dst_x_int is calculated to be the left of the scaled image, and
+ * x (the sub pixel offset) is the distance in from either the left
+ * or right pixel expanded edge. */
+ flip_x = (w < 0);
+ if (flip_x)
+ {
+ float tmp;
+ w = -w;
+ dst_x_int = floorf(x-w);
+ tmp = ceilf(x);
+ dst_w_int = (int)tmp;
+ x = tmp - x;
+ dst_w_int -= dst_x_int;
+ }
+ else
+ {
+ dst_x_int = floorf(x);
+ x -= (float)dst_x_int;
+ dst_w_int = (int)ceilf(x + w);
+ }
+ /* dst_y_int is calculated to be the top of the scaled image, and
+ * y (the sub pixel offset) is the distance in from either the top
+ * or bottom pixel expanded edge.
+ */
+ flip_y = (h < 0);
+ if (flip_y)
+ {
+ float tmp;
+ h = -h;
+ dst_y_int = floorf(y-h);
+ tmp = ceilf(y);
+ dst_h_int = (int)tmp;
+ y = tmp - y;
+ dst_h_int -= dst_y_int;
+ }
+ else
+ {
+ dst_y_int = floorf(y);
+ y -= (float)dst_y_int;
+ dst_h_int = (int)ceilf(y + h);
+ }
+
+ DBUG(("Result image: (%d,%d) at (%d,%d) (subpix=%g,%g)\n", dst_w_int, dst_h_int, dst_x_int, dst_y_int, x, y));
+
+ /* Step 0: Calculate the patch */
+ patch.x0 = 0;
+ patch.y0 = 0;
+ patch.x1 = dst_w_int;
+ patch.y1 = dst_h_int;
+ if (clip)
+ {
+ if (flip_x)
+ {
+ if (dst_x_int + dst_w_int > clip->x1)
+ patch.x0 = dst_x_int + dst_w_int - clip->x1;
+ if (clip->x0 > dst_x_int)
+ {
+ patch.x1 = dst_w_int - (clip->x0 - dst_x_int);
+ dst_x_int = clip->x0;
+ }
+ }
+ else
+ {
+ if (dst_x_int + dst_w_int > clip->x1)
+ patch.x1 = clip->x1 - dst_x_int;
+ if (clip->x0 > dst_x_int)
+ {
+ patch.x0 = clip->x0 - dst_x_int;
+ dst_x_int += patch.x0;
+ }
+ }
+
+ if (flip_y)
+ {
+ if (dst_y_int + dst_h_int > clip->y1)
+ patch.y1 = clip->y1 - dst_y_int;
+ if (clip->y0 > dst_y_int)
+ {
+ patch.y0 = clip->y0 - dst_y_int;
+ dst_y_int = clip->y0;
+ }
+ }
+ else
+ {
+ if (dst_y_int + dst_h_int > clip->y1)
+ patch.y1 = clip->y1 - dst_y_int;
+ if (clip->y0 > dst_y_int)
+ {
+ patch.y0 = clip->y0 - dst_y_int;
+ dst_y_int += patch.y0;
+ }
+ }
+ }
+ if (patch.x0 >= patch.x1 || patch.y0 >= patch.y1)
+ return NULL;
+
+ fz_try(ctx)
+ {
+ /* Step 1: Calculate the weights for columns and rows */
+#ifdef SINGLE_PIXEL_SPECIALS
+ if (src->w == 1)
+ contrib_cols = NULL;
+ else
+#endif /* SINGLE_PIXEL_SPECIALS */
+ contrib_cols = make_weights(ctx, src->w, x, w, filter, 0, dst_w_int, patch.x0, patch.x1, src->n, flip_x, cache_x);
+#ifdef SINGLE_PIXEL_SPECIALS
+ if (src->h == 1)
+ contrib_rows = NULL;
+ else
+#endif /* SINGLE_PIXEL_SPECIALS */
+ contrib_rows = make_weights(ctx, src->h, y, h, filter, 1, dst_h_int, patch.y0, patch.y1, src->n, flip_y, cache_y);
+
+ output = fz_new_pixmap(ctx, src->colorspace, patch.x1 - patch.x0, patch.y1 - patch.y0);
+ }
+ fz_catch(ctx)
+ {
+ if (!cache_x)
+ fz_free(ctx, contrib_cols);
+ if (!cache_y)
+ fz_free(ctx, contrib_rows);
+ fz_rethrow(ctx);
+ }
+ output->x = dst_x_int;
+ output->y = dst_y_int;
+
+ /* Step 2: Apply the weights */
+#ifdef SINGLE_PIXEL_SPECIALS
+ if (!contrib_rows)
+ {
+ /* Only 1 source pixel high. */
+ if (!contrib_cols)
+ {
+ /* Only 1 pixel in the entire image! */
+ duplicate_single_pixel(output->samples, src->samples, src->n, patch.x1-patch.x0, patch.y1-patch.y0);
+ }
+ else
+ {
+ /* Scale the row once, then copy it. */
+ scale_single_row(output->samples, src->samples, contrib_cols, src->w, patch.y1-patch.y0);
+ }
+ }
+ else if (!contrib_cols)
+ {
+ /* Only 1 source pixel wide. Scale the col and duplicate. */
+ scale_single_col(output->samples, src->samples, contrib_rows, src->h, src->n, patch.x1-patch.x0, flip_y);
+ }
+ else
+#endif /* SINGLE_PIXEL_SPECIALS */
+ {
+ void (*row_scale)(unsigned char *dst, unsigned char *src, fz_weights *weights);
+
+ temp_span = contrib_cols->count * src->n;
+ temp_rows = contrib_rows->max_len;
+ if (temp_span <= 0 || temp_rows > INT_MAX / temp_span)
+ goto cleanup;
+ fz_try(ctx)
+ {
+ temp = fz_calloc(ctx, temp_span*temp_rows, sizeof(unsigned char));
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_pixmap(ctx, output);
+ if (!cache_x)
+ fz_free(ctx, contrib_cols);
+ if (!cache_y)
+ fz_free(ctx, contrib_rows);
+ fz_rethrow(ctx);
+ }
+ switch (src->n)
+ {
+ default:
+ row_scale = scale_row_to_temp;
+ break;
+ case 1: /* Image mask case */
+ row_scale = scale_row_to_temp1;
+ break;
+ case 2: /* Greyscale with alpha case */
+ row_scale = scale_row_to_temp2;
+ break;
+ case 4: /* RGBA */
+ row_scale = scale_row_to_temp4;
+ break;
+ }
+ max_row = contrib_rows->index[contrib_rows->index[0]];
+ for (row = 0; row < contrib_rows->count; row++)
+ {
+ /*
+ Which source rows do we need to have scaled into the
+ temporary buffer in order to be able to do the final
+ scale?
+ */
+ int row_index = contrib_rows->index[row];
+ int row_min = contrib_rows->index[row_index++];
+ int row_len = contrib_rows->index[row_index++];
+ while (max_row < row_min+row_len)
+ {
+ /* Scale another row */
+ assert(max_row < src->h);
+ DBUG(("scaling row %d to temp\n", max_row));
+ (*row_scale)(&temp[temp_span*(max_row % temp_rows)], &src->samples[(flip_y ? (src->h-1-max_row): max_row)*src->w*src->n], contrib_cols);
+ max_row++;
+ }
+
+ DBUG(("scaling row %d from temp\n", row));
+ scale_row_from_temp(&output->samples[row*output->w*output->n], temp, contrib_rows, temp_span, row);
+ }
+ fz_free(ctx, temp);
+ }
+
+cleanup:
+ if (!cache_y)
+ fz_free(ctx, contrib_rows);
+ if (!cache_x)
+ fz_free(ctx, contrib_cols);
+ return output;
+}
+
+void
+fz_free_scale_cache(fz_context *ctx, fz_scale_cache *sc)
+{
+ if (!sc)
+ return;
+ fz_free(ctx, sc->weights);
+ fz_free(ctx, sc);
+}
+
+fz_scale_cache *
+fz_new_scale_cache(fz_context *ctx)
+{
+ return fz_malloc_struct(ctx, fz_scale_cache);
+}
diff --git a/source/fitz/draw-scale.c b/source/fitz/draw-scale.c
new file mode 100644
index 00000000..553d7830
--- /dev/null
+++ b/source/fitz/draw-scale.c
@@ -0,0 +1,1541 @@
+/*
+This code does smooth scaling of a pixmap.
+
+This function returns a new pixmap representing the area starting at (0,0)
+given by taking the source pixmap src, scaling it to width w, and height h,
+and then positioning it at (frac(x),frac(y)).
+*/
+
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+/* Do we special case handling of single pixel high/wide images? The
+ * 'purest' handling is given by not special casing them, but certain
+ * files that use such images 'stack' them to give full images. Not
+ * special casing them results in then being fainter and giving noticeable
+ * rounding errors.
+ */
+#define SINGLE_PIXEL_SPECIALS
+
+/* If we're compiling as thumb code, then we need to tell the compiler
+ * to enter and exit ARM mode around our assembly sections. If we move
+ * the ARM functions to a separate file and arrange for it to be compiled
+ * without thumb mode, we can save some time on entry.
+ */
+#ifdef ARCH_ARM
+#ifdef ARCH_THUMB
+#define ENTER_ARM ".balign 4\nmov r12,pc\nbx r12\n0:.arm\n"
+#define ENTER_THUMB "9:.thumb\n"
+#else
+#define ENTER_ARM
+#define ENTER_THUMB
+#endif
+#endif
+
+#ifdef DEBUG_SCALING
+#ifdef WIN32
+#include <windows.h>
+static void debug_print(const char *fmt, ...)
+{
+ va_list args;
+ char text[256];
+ va_start(args, fmt);
+ vsprintf(text, fmt, args);
+ va_end(args);
+ OutputDebugStringA(text);
+ printf(text);
+}
+#else
+static void debug_print(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+}
+#endif
+#endif
+#ifdef DEBUG_SCALING
+#define DBUG(A) debug_print A
+#else
+#define DBUG(A) do {} while(0==1)
+#endif
+
+/*
+Consider a row of source samples, src, of width src_w, positioned at x,
+scaled to width dst_w.
+
+src[i] is centred at: x + (i + 0.5)*dst_w/src_w
+
+Therefore the distance between the centre of the jth output pixel and
+the centre of the ith source sample is:
+
+dist[j,i] = j + 0.5 - (x + (i + 0.5)*dst_w/src_w)
+
+When scaling up, therefore:
+
+dst[j] = SUM(filter(dist[j,i]) * src[i])
+ (for all ints i)
+
+This can be simplified by noticing that filters are only non zero within
+a given filter width (henceforth called W). So:
+
+dst[j] = SUM(filter(dist[j,i]) * src[i])
+ (for ints i, s.t. (j*src_w/dst_w)-W < i < (j*src_w/dst_w)+W)
+
+When scaling down, each filtered source sample is stretched to be wider
+to avoid aliasing issues. This effectively reduces the distance between
+centres.
+
+dst[j] = SUM(filter(dist[j,i] * F) * F * src[i])
+ (where F = dst_w/src_w)
+ (for ints i, s.t. (j-W)/F < i < (j+W)/F)
+
+*/
+
+typedef struct fz_scale_filter_s fz_scale_filter;
+
+struct fz_scale_filter_s
+{
+ int width;
+ float (*fn)(fz_scale_filter *, float);
+};
+
+/* Image scale filters */
+
+static float
+triangle(fz_scale_filter *filter, float f)
+{
+ if (f >= 1)
+ return 0;
+ return 1-f;
+}
+
+static float
+box(fz_scale_filter *filter, float f)
+{
+ if (f >= 0.5f)
+ return 0;
+ return 1;
+}
+
+static float
+simple(fz_scale_filter *filter, float x)
+{
+ if (x >= 1)
+ return 0;
+ return 1 + (2*x - 3)*x*x;
+}
+
+static float
+lanczos2(fz_scale_filter *filter, float x)
+{
+ if (x >= 2)
+ return 0;
+ return sinf(M_PI*x) * sinf(M_PI*x/2) / (M_PI*x) / (M_PI*x/2);
+}
+
+static float
+lanczos3(fz_scale_filter *filter, float f)
+{
+ if (f >= 3)
+ return 0;
+ return sinf(M_PI*f) * sinf(M_PI*f/3) / (M_PI*f) / (M_PI*f/3);
+}
+
+/*
+The Mitchell family of filters is defined:
+
+ f(x) = 1 { (12-9B-6C)x^3 + (-18+12B+6C)x^2 + (6-2B) for x < 1
+ - {
+ 6 { (-B-6C)x^3+(6B+30C)x^2+(-12B-48C)x+(8B+24C) for 1<=x<=2
+
+The 'best' ones lie along the line B+2C = 1.
+The literature suggests that B=1/3, C=1/3 is best.
+
+ f(x) = 1 { (12-3-2)x^3 - (-18+4+2)x^2 + (16/3) for x < 1
+ - {
+ 6 { (-7/3)x^3 + 12x^2 - 20x + (32/3) for 1<=x<=2
+
+ f(x) = 1 { 21x^3 - 36x^2 + 16 for x < 1
+ - {
+ 18{ -7x^3 + 36x^2 - 60x + 32 for 1<=x<=2
+*/
+
+static float
+mitchell(fz_scale_filter *filter, float x)
+{
+ if (x >= 2)
+ return 0;
+ if (x >= 1)
+ return (32 + x*(-60 + x*(36 - 7*x)))/18;
+ return (16 + x*x*(-36 + 21*x))/18;
+}
+
+fz_scale_filter fz_scale_filter_box = { 1, box };
+fz_scale_filter fz_scale_filter_triangle = { 1, triangle };
+fz_scale_filter fz_scale_filter_simple = { 1, simple };
+fz_scale_filter fz_scale_filter_lanczos2 = { 2, lanczos2 };
+fz_scale_filter fz_scale_filter_lanczos3 = { 3, lanczos3 };
+fz_scale_filter fz_scale_filter_mitchell = { 2, mitchell };
+
+/*
+We build ourselves a set of tables to contain the precalculated weights
+for a given set of scale settings.
+
+The first dst_w entries in index are the index into index of the
+sets of weight for each destination pixel.
+
+Each of the sets of weights is a set of values consisting of:
+ the minimum source pixel index used for this destination pixel
+ the number of weights used for this destination pixel
+ the weights themselves
+
+So to calculate dst[i] we do the following:
+
+ weights = &index[index[i]];
+ min = *weights++;
+ len = *weights++;
+ dst[i] = 0;
+ while (--len > 0)
+ dst[i] += src[min++] * *weights++
+
+in addition, we guarantee that at the end of this process weights will now
+point to the weights value for dst pixel i+1.
+
+In the simplest version of this algorithm, we would scale the whole image
+horizontally first into a temporary buffer, then scale that temporary
+buffer again vertically to give us our result. Using such a simple
+algorithm would mean that could use the same style of weights for both
+horizontal and vertical scaling.
+
+Unfortunately, this would also require a large temporary buffer,
+particularly in the case where we are scaling up.
+
+We therefore modify the algorithm as follows; we scale scanlines from the
+source image horizontally into a temporary buffer, until we have all the
+contributors for a given output scanline. We then produce that output
+scanline from the temporary buffer. In this way we restrict the height
+of the temporary buffer to a small fraction of the final size.
+
+Unfortunately, this means that the pseudo code for recombining a
+scanline of fully scaled pixels is as follows:
+
+ weights = &index[index[y]];
+ min = *weights++;
+ len = *weights++;
+ for (x=0 to dst_w)
+ min2 = min
+ len2 = len
+ weights2 = weights
+ dst[x] = 0;
+ while (--len2 > 0)
+ dst[x] += temp[x][(min2++) % tmp_buf_height] * *weights2++
+
+i.e. it requires a % operation for every source pixel - this is typically
+expensive.
+
+To avoid this, we alter the order in which vertical weights are stored,
+so that they are ordered in the same order as the temporary buffer lines
+would appear. This simplifies the algorithm to:
+
+ weights = &index[index[y]];
+ min = *weights++;
+ len = *weights++;
+ for (x=0 to dst_w)
+ min2 = 0
+ len2 = len
+ weights2 = weights
+ dst[x] = 0;
+ while (--len2 > 0)
+ dst[x] += temp[i][min2++] * *weights2++
+
+This means that len may be larger than it needs to be (due to the
+possible inclusion of a zero weight row or two), but in practise this
+is only an increase of 1 or 2 at worst.
+
+We implement this by generating the weights as normal (but ensuring we
+leave enough space) and then reordering afterwards.
+
+*/
+
+typedef struct fz_weights_s fz_weights;
+
+/* This structure is accessed from ARM code - bear this in mind before
+ * altering it! */
+struct fz_weights_s
+{
+ int flip; /* true if outputting reversed */
+ int count; /* number of output pixels we have records for in this table */
+ int max_len; /* Maximum number of weights for any one output pixel */
+ int n; /* number of components (src->n) */
+ int new_line; /* True if no weights for the current output pixel */
+ int patch_l; /* How many output pixels we skip over */
+ int index[1];
+};
+
+struct fz_scale_cache_s
+{
+ int src_w;
+ float x;
+ float dst_w;
+ fz_scale_filter *filter;
+ int vertical;
+ int dst_w_int;
+ int patch_l;
+ int patch_r;
+ int n;
+ int flip;
+ fz_weights *weights;
+};
+
+static fz_weights *
+new_weights(fz_context *ctx, fz_scale_filter *filter, int src_w, float dst_w, int patch_w, int n, int flip, int patch_l)
+{
+ int max_len;
+ fz_weights *weights;
+
+ if (src_w > dst_w)
+ {
+ /* Scaling down, so there will be a maximum of
+ * 2*filterwidth*src_w/dst_w src pixels
+ * contributing to each dst pixel. */
+ max_len = (int)ceilf((2 * filter->width * src_w)/dst_w);
+ if (max_len > src_w)
+ max_len = src_w;
+ }
+ else
+ {
+ /* Scaling up, so there will be a maximum of
+ * 2*filterwidth src pixels contributing to each dst pixel.
+ */
+ max_len = 2 * filter->width;
+ }
+ /* We need the size of the struct,
+ * plus patch_w*sizeof(int) for the index
+ * plus (2+max_len)*sizeof(int) for the weights
+ * plus room for an extra set of weights for reordering.
+ */
+ weights = fz_malloc(ctx, sizeof(*weights)+(max_len+3)*(patch_w+1)*sizeof(int));
+ if (!weights)
+ return NULL;
+ weights->count = -1;
+ weights->max_len = max_len;
+ weights->index[0] = patch_w;
+ weights->n = n;
+ weights->patch_l = patch_l;
+ weights->flip = flip;
+ return weights;
+}
+
+/* j is destination pixel in the patch_l..patch_l+patch_w range */
+static void
+init_weights(fz_weights *weights, int j)
+{
+ int index;
+
+ j -= weights->patch_l;
+ assert(weights->count == j-1);
+ weights->count++;
+ weights->new_line = 1;
+ if (j == 0)
+ index = weights->index[0];
+ else
+ {
+ index = weights->index[j-1];
+ index += 2 + weights->index[index+1];
+ }
+ weights->index[j] = index; /* row pointer */
+ weights->index[index] = 0; /* min */
+ weights->index[index+1] = 0; /* len */
+}
+
+static void
+add_weight(fz_weights *weights, int j, int i, fz_scale_filter *filter,
+ float x, float F, float G, int src_w, float dst_w)
+{
+ float dist = j - x + 0.5f - ((i + 0.5f)*dst_w/src_w);
+ float f;
+ int min, len, index, weight;
+
+ dist *= G;
+ if (dist < 0)
+ dist = -dist;
+ f = filter->fn(filter, dist)*F;
+ weight = (int)(256*f+0.5f);
+
+ /* Ensure i is in range */
+ if (i < 0 || i >= src_w)
+ return;
+ if (weight == 0)
+ {
+ /* We add a fudge factor here to allow for extreme downscales
+ * where all the weights round to 0. Ensure that at least one
+ * (arbitrarily the first one) is non zero. */
+ if (weights->new_line && f > 0)
+ weight = 1;
+ else
+ return;
+ }
+
+ DBUG(("add_weight[%d][%d] = %d(%g) dist=%g\n",j,i,weight,f,dist));
+
+ /* Move j from patch_l...patch_l+patch_w range to 0..patch_w range */
+ j -= weights->patch_l;
+ if (weights->new_line)
+ {
+ /* New line */
+ weights->new_line = 0;
+ index = weights->index[j]; /* row pointer */
+ weights->index[index] = i; /* min */
+ weights->index[index+1] = 0; /* len */
+ }
+ index = weights->index[j];
+ min = weights->index[index++];
+ len = weights->index[index++];
+ while (i < min)
+ {
+ /* This only happens in rare cases, but we need to insert
+ * one earlier. In exceedingly rare cases we may need to
+ * insert more than one earlier. */
+ int k;
+
+ for (k = len; k > 0; k--)
+ {
+ weights->index[index+k] = weights->index[index+k-1];
+ }
+ weights->index[index] = 0;
+ min--;
+ len++;
+ weights->index[index-2] = min;
+ weights->index[index-1] = len;
+ }
+ if (i-min >= len)
+ {
+ /* The usual case */
+ while (i-min >= ++len)
+ {
+ weights->index[index+len-1] = 0;
+ }
+ assert(len-1 == i-min);
+ weights->index[index+i-min] = weight;
+ weights->index[index-1] = len;
+ assert(len <= weights->max_len);
+ }
+ else
+ {
+ /* Infrequent case */
+ weights->index[index+i-min] += weight;
+ }
+}
+
+static void
+reorder_weights(fz_weights *weights, int j, int src_w)
+{
+ int idx = weights->index[j - weights->patch_l];
+ int min = weights->index[idx++];
+ int len = weights->index[idx++];
+ int max = weights->max_len;
+ int tmp = idx+max;
+ int i, off;
+
+ /* Copy into the temporary area */
+ memcpy(&weights->index[tmp], &weights->index[idx], sizeof(int)*len);
+
+ /* Pad out if required */
+ assert(len <= max);
+ assert(min+len <= src_w);
+ off = 0;
+ if (len < max)
+ {
+ memset(&weights->index[tmp+len], 0, sizeof(int)*(max-len));
+ len = max;
+ if (min + len > src_w)
+ {
+ off = min + len - src_w;
+ min = src_w - len;
+ weights->index[idx-2] = min;
+ }
+ weights->index[idx-1] = len;
+ }
+
+ /* Copy back into the proper places */
+ for (i = 0; i < len; i++)
+ {
+ weights->index[idx+((min+i+off) % max)] = weights->index[tmp+i];
+ }
+}
+
+/* Due to rounding and edge effects, the sums for the weights sometimes don't
+ * add up to 256. This causes visible rendering effects. Therefore, we take
+ * pains to ensure that they 1) never exceed 256, and 2) add up to exactly
+ * 256 for all pixels that are completely covered. See bug #691629. */
+static void
+check_weights(fz_weights *weights, int j, int w, float x, float wf)
+{
+ int idx, len;
+ int sum = 0;
+ int max = -256;
+ int maxidx = 0;
+ int i;
+
+ idx = weights->index[j - weights->patch_l];
+ idx++; /* min */
+ len = weights->index[idx++];
+
+ for(i=0; i < len; i++)
+ {
+ int v = weights->index[idx++];
+ sum += v;
+ if (v > max)
+ {
+ max = v;
+ maxidx = idx;
+ }
+ }
+ /* If we aren't the first or last pixel, OR if the sum is too big
+ * then adjust it. */
+ if (((j != 0) && (j != w-1)) || (sum > 256))
+ weights->index[maxidx-1] += 256-sum;
+ /* Otherwise, if we are the first pixel, and it's fully covered, then
+ * adjust it. */
+ else if ((j == 0) && (x < 0.0001F) && (sum != 256))
+ weights->index[maxidx-1] += 256-sum;
+ /* Finally, if we are the last pixel, and it's fully covered, then
+ * adjust it. */
+ else if ((j == w-1) && ((float)w-wf < 0.0001F) && (sum != 256))
+ weights->index[maxidx-1] += 256-sum;
+ DBUG(("total weight %d = %d\n", j, sum));
+}
+
+static fz_weights *
+make_weights(fz_context *ctx, int src_w, float x, float dst_w, fz_scale_filter *filter, int vertical, int dst_w_int, int patch_l, int patch_r, int n, int flip, fz_scale_cache *cache)
+{
+ fz_weights *weights;
+ float F, G;
+ float window;
+ int j;
+
+ if (cache)
+ {
+ if (cache->src_w == src_w && cache->x == x && cache->dst_w == dst_w &&
+ cache->filter == filter && cache->vertical == vertical &&
+ cache->dst_w_int == dst_w_int &&
+ cache->patch_l == patch_l && cache->patch_r == patch_r &&
+ cache->n == n && cache->flip == flip)
+ {
+ return cache->weights;
+ }
+ cache->src_w = src_w;
+ cache->x = x;
+ cache->dst_w = dst_w;
+ cache->filter = filter;
+ cache->vertical = vertical;
+ cache->dst_w_int = dst_w_int;
+ cache->patch_l = patch_l;
+ cache->patch_r = patch_r;
+ cache->n = n;
+ cache->flip = flip;
+ fz_free(ctx, cache->weights);
+ cache->weights = NULL;
+ }
+
+ if (dst_w < src_w)
+ {
+ /* Scaling down */
+ F = dst_w / src_w;
+ G = 1;
+ }
+ else
+ {
+ /* Scaling up */
+ F = 1;
+ G = src_w / dst_w;
+ }
+ window = filter->width / F;
+ DBUG(("make_weights src_w=%d x=%g dst_w=%g patch_l=%d patch_r=%d F=%g window=%g\n", src_w, x, dst_w, patch_l, patch_r, F, window));
+ weights = new_weights(ctx, filter, src_w, dst_w, patch_r-patch_l, n, flip, patch_l);
+ if (!weights)
+ return NULL;
+ for (j = patch_l; j < patch_r; j++)
+ {
+ /* find the position of the centre of dst[j] in src space */
+ float centre = (j - x + 0.5f)*src_w/dst_w - 0.5f;
+ int l, r;
+ l = ceilf(centre - window);
+ r = floorf(centre + window);
+ DBUG(("%d: centre=%g l=%d r=%d\n", j, centre, l, r));
+ init_weights(weights, j);
+ for (; l <= r; l++)
+ {
+ add_weight(weights, j, l, filter, x, F, G, src_w, dst_w);
+ }
+ check_weights(weights, j, dst_w_int, x, dst_w);
+ if (vertical)
+ {
+ reorder_weights(weights, j, src_w);
+ }
+ }
+ weights->count++; /* weights->count = dst_w_int now */
+ if (cache)
+ {
+ cache->weights = weights;
+ }
+ return weights;
+}
+
+static void
+scale_row_to_temp(int *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i, j, n;
+ unsigned char *min;
+
+ n = weights->n;
+ if (weights->flip)
+ {
+ dst += (weights->count-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ min = &src[n * *contrib++];
+ len = *contrib++;
+ for (j = 0; j < n; j++)
+ dst[j] = 0;
+ while (len-- > 0)
+ {
+ for (j = n; j > 0; j--)
+ *dst++ += *min++ * *contrib;
+ dst -= n;
+ contrib++;
+ }
+ dst -= n;
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ min = &src[n * *contrib++];
+ len = *contrib++;
+ for (j = 0; j < n; j++)
+ dst[j] = 0;
+ while (len-- > 0)
+ {
+ for (j = n; j > 0; j--)
+ *dst++ += *min++ * *contrib;
+ dst -= n;
+ contrib++;
+ }
+ dst += n;
+ }
+ }
+}
+
+#ifdef ARCH_ARM
+
+static void
+scale_row_to_temp1(int *dst, unsigned char *src, fz_weights *weights)
+__attribute__((naked));
+
+static void
+scale_row_to_temp2(int *dst, unsigned char *src, fz_weights *weights)
+__attribute__((naked));
+
+static void
+scale_row_to_temp4(int *dst, unsigned char *src, fz_weights *weights)
+__attribute__((naked));
+
+static void
+scale_row_from_temp(unsigned char *dst, int *src, fz_weights *weights, int width, int row)
+__attribute__((naked));
+
+static void
+scale_row_to_temp1(int *dst, unsigned char *src, fz_weights *weights)
+{
+ /* possible optimisation in here; unroll inner loops to avoid stall. */
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r5,r9,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = weights \n"
+ "ldr r12,[r2],#4 @ r12= flip \n"
+ "ldr r3, [r2],#20 @ r3 = count r2 = &index\n"
+ "ldr r4, [r2] @ r4 = index[0] \n"
+ "cmp r12,#0 @ if (flip) \n"
+ "beq 4f @ { \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "add r0, r0, r3, LSL #2 @ dst += count \n"
+ "1: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #0 @ r5 = a = 0 \n"
+ "add r4, r1, r4 @ r4 = min = &src[r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 3f @ { \n"
+ "2: \n"
+ "ldr r12,[r2], #4 @ r12 = *contrib++ \n"
+ "ldrb r14,[r4], #1 @ r14 = *min++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "@stall on r14 \n"
+ "mla r5, r12,r14,r5 @ g += r14 * r12 \n"
+ "bgt 2b @ } \n"
+ "3: \n"
+ "str r5,[r0, #-4]! @ *--dst=a \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r5,r9,PC} @ pop, return to thumb \n"
+ "4:"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "5:"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #0 @ r5 = a = 0 \n"
+ "add r4, r1, r4 @ r4 = min = &src[r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 7f @ { \n"
+ "6: \n"
+ "ldr r12,[r2], #4 @ r12 = *contrib++ \n"
+ "ldrb r14,[r4], #1 @ r14 = *min++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "@stall on r14 \n"
+ "mla r5, r12,r14,r5 @ a += r14 * r12 \n"
+ "bgt 6b @ } \n"
+ "7: \n"
+ "str r5, [r0], #4 @ *dst++=a \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 5b @ \n"
+ "ldmfd r13!,{r4-r5,r9,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+static void
+scale_row_to_temp2(int *dst, unsigned char *src, fz_weights *weights)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r6,r9-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = weights \n"
+ "ldr r12,[r2],#4 @ r12= flip \n"
+ "ldr r3, [r2],#20 @ r3 = count r2 = &index\n"
+ "ldr r4, [r2] @ r4 = index[0] \n"
+ "cmp r12,#0 @ if (flip) \n"
+ "beq 4f @ { \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "add r0, r0, r3, LSL #3 @ dst += 2*count \n"
+ "1: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #0 @ r5 = g = 0 \n"
+ "mov r6, #0 @ r6 = a = 0 \n"
+ "add r4, r1, r4, LSL #1 @ r4 = min = &src[2*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 3f @ { \n"
+ "2: \n"
+ "ldr r14,[r2], #4 @ r14 = *contrib++ \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "ldrb r12,[r4], #1 @ r12 = *min++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "mla r5, r14,r11,r5 @ g += r11 * r14 \n"
+ "mla r6, r14,r12,r6 @ a += r12 * r14 \n"
+ "bgt 2b @ } \n"
+ "3: \n"
+ "stmdb r0!,{r5,r6} @ *--dst=a;*--dst=g; \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r6,r9-r11,PC} @ pop, return to thumb \n"
+ "4:"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "5:"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #0 @ r5 = g = 0 \n"
+ "mov r6, #0 @ r6 = a = 0 \n"
+ "add r4, r1, r4, LSL #1 @ r4 = min = &src[2*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 7f @ { \n"
+ "6: \n"
+ "ldr r14,[r2], #4 @ r10 = *contrib++ \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "ldrb r12,[r4], #1 @ r12 = *min++ \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "mla r5, r14,r11,r5 @ g += r11 * r14 \n"
+ "mla r6, r14,r12,r6 @ a += r12 * r14 \n"
+ "bgt 6b @ } \n"
+ "7: \n"
+ "stmia r0!,{r5,r6} @ *dst++=r;*dst++=g; \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 5b @ \n"
+ "ldmfd r13!,{r4-r6,r9-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+static void
+scale_row_to_temp4(int *dst, unsigned char *src, fz_weights *weights)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r4-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = weights \n"
+ "ldr r12,[r2],#4 @ r12= flip \n"
+ "ldr r3, [r2],#20 @ r3 = count r2 = &index\n"
+ "ldr r4, [r2] @ r4 = index[0] \n"
+ "cmp r12,#0 @ if (flip) \n"
+ "beq 4f @ { \n"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "add r0, r0, r3, LSL #4 @ dst += 4*count \n"
+ "1: \n"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #0 @ r5 = r = 0 \n"
+ "mov r6, #0 @ r6 = g = 0 \n"
+ "mov r7, #0 @ r7 = b = 0 \n"
+ "mov r8, #0 @ r8 = a = 0 \n"
+ "add r4, r1, r4, LSL #2 @ r4 = min = &src[4*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 3f @ { \n"
+ "2: \n"
+ "ldr r10,[r2], #4 @ r10 = *contrib++ \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "ldrb r12,[r4], #1 @ r12 = *min++ \n"
+ "ldrb r14,[r4], #1 @ r14 = *min++ \n"
+ "mla r5, r10,r11,r5 @ r += r11 * r10 \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "mla r6, r10,r12,r6 @ g += r12 * r10 \n"
+ "mla r7, r10,r14,r7 @ b += r14 * r10 \n"
+ "mla r8, r10,r11,r8 @ a += r11 * r10 \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "bgt 2b @ } \n"
+ "3: \n"
+ "stmdb r0!,{r5,r6,r7,r8} @ *--dst=a;*--dst=b; \n"
+ " @ *--dst=g;*--dst=r; \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ "4:"
+ "add r2, r2, r4, LSL #2 @ r2 = &index[index[0]] \n"
+ "5:"
+ "ldr r4, [r2], #4 @ r4 = *contrib++ \n"
+ "ldr r9, [r2], #4 @ r9 = len = *contrib++ \n"
+ "mov r5, #0 @ r5 = r = 0 \n"
+ "mov r6, #0 @ r6 = g = 0 \n"
+ "mov r7, #0 @ r7 = b = 0 \n"
+ "mov r8, #0 @ r8 = a = 0 \n"
+ "add r4, r1, r4, LSL #2 @ r4 = min = &src[4*r4] \n"
+ "cmp r9, #0 @ while (len-- > 0) \n"
+ "beq 7f @ { \n"
+ "6: \n"
+ "ldr r10,[r2], #4 @ r10 = *contrib++ \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "ldrb r12,[r4], #1 @ r12 = *min++ \n"
+ "ldrb r14,[r4], #1 @ r14 = *min++ \n"
+ "mla r5, r10,r11,r5 @ r += r11 * r10 \n"
+ "ldrb r11,[r4], #1 @ r11 = *min++ \n"
+ "mla r6, r10,r12,r6 @ g += r12 * r10 \n"
+ "mla r7, r10,r14,r7 @ b += r14 * r10 \n"
+ "mla r8, r10,r11,r8 @ a += r11 * r10 \n"
+ "subs r9, r9, #1 @ r9 = len-- \n"
+ "bgt 6b @ } \n"
+ "7: \n"
+ "stmia r0!,{r5,r6,r7,r8} @ *dst++=r;*dst++=g; \n"
+ " @ *dst++=b;*dst++=a; \n"
+ "subs r3, r3, #1 @ i-- \n"
+ "bgt 5b @ \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+static void
+scale_row_from_temp(unsigned char *dst, int *src, fz_weights *weights, int width, int row)
+{
+ asm volatile(
+ ENTER_ARM
+ "ldr r12,[r13] @ r12= row \n"
+ "add r2, r2, #24 @ r2 = weights->index \n"
+ "stmfd r13!,{r4-r11,r14} \n"
+ "@ r0 = dst \n"
+ "@ r1 = src \n"
+ "@ r2 = &weights->index[0] \n"
+ "@ r3 = width \n"
+ "@ r12= row \n"
+ "ldr r4, [r2, r12, LSL #2] @ r4 = index[row] \n"
+ "add r2, r2, #4 @ r2 = &index[1] \n"
+ "mov r6, r3 @ r6 = x = width \n"
+ "ldr r14,[r2, r4, LSL #2]! @ r2 = contrib = index[index[row]+1]\n"
+ " @ r14= len = *contrib \n"
+ "1: \n"
+ "mov r5, r1 @ r5 = min = src \n"
+ "mov r7, #1<<15 @ r7 = val = 1<<15 \n"
+ "movs r8, r14 @ r8 = len2 = len \n"
+ "add r9, r2, #4 @ r9 = contrib2 \n"
+ "ble 3f @ while (len2-- > 0) { \n"
+ "2: \n"
+ "ldr r10,[r9], #4 @ r10 = *contrib2++ \n"
+ "ldr r12,[r5], r3, LSL #2 @ r12 = *min r5 = min += width\n"
+ "subs r8, r8, #1 @ len2-- \n"
+ "@ stall r12 \n"
+ "mla r7, r10,r12,r7 @ val += r12 * r10 \n"
+ "bgt 2b @ } \n"
+ "3: \n"
+ "movs r7, r7, asr #16 @ r7 = val >>= 16 \n"
+ "movlt r7, #0 @ if (r7 < 0) r7 = 0 \n"
+ "cmp r7, #255 @ if (r7 > 255) \n"
+ "add r1, r1, #4 @ src++ \n"
+ "movgt r7, #255 @ r7 = 255 \n"
+ "subs r6, r6, #1 @ x-- \n"
+ "strb r7, [r0], #1 @ *dst++ = val \n"
+ "bgt 1b @ \n"
+ "ldmfd r13!,{r4-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+#else
+
+static void
+scale_row_to_temp1(int *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i;
+ unsigned char *min;
+
+ assert(weights->n == 1);
+ if (weights->flip)
+ {
+ dst += weights->count;
+ for (i=weights->count; i > 0; i--)
+ {
+ int val = 0;
+ min = &src[*contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ val += *min++ * *contrib++;
+ }
+ *--dst = val;
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ int val = 0;
+ min = &src[*contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ val += *min++ * *contrib++;
+ }
+ *dst++ = val;
+ }
+ }
+}
+
+static void
+scale_row_to_temp2(int *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i;
+ unsigned char *min;
+
+ assert(weights->n == 2);
+ if (weights->flip)
+ {
+ dst += 2*weights->count;
+ for (i=weights->count; i > 0; i--)
+ {
+ int c1 = 0;
+ int c2 = 0;
+ min = &src[2 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ c1 += *min++ * *contrib;
+ c2 += *min++ * *contrib++;
+ }
+ *--dst = c2;
+ *--dst = c1;
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ int c1 = 0;
+ int c2 = 0;
+ min = &src[2 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ c1 += *min++ * *contrib;
+ c2 += *min++ * *contrib++;
+ }
+ *dst++ = c1;
+ *dst++ = c2;
+ }
+ }
+}
+
+static void
+scale_row_to_temp4(int *dst, unsigned char *src, fz_weights *weights)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int len, i;
+ unsigned char *min;
+
+ assert(weights->n == 4);
+ if (weights->flip)
+ {
+ dst += 4*weights->count;
+ for (i=weights->count; i > 0; i--)
+ {
+ int r = 0;
+ int g = 0;
+ int b = 0;
+ int a = 0;
+ min = &src[4 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ r += *min++ * *contrib;
+ g += *min++ * *contrib;
+ b += *min++ * *contrib;
+ a += *min++ * *contrib++;
+ }
+ *--dst = a;
+ *--dst = b;
+ *--dst = g;
+ *--dst = r;
+ }
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ int r = 0;
+ int g = 0;
+ int b = 0;
+ int a = 0;
+ min = &src[4 * *contrib++];
+ len = *contrib++;
+ while (len-- > 0)
+ {
+ r += *min++ * *contrib;
+ g += *min++ * *contrib;
+ b += *min++ * *contrib;
+ a += *min++ * *contrib++;
+ }
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+ *dst++ = a;
+ }
+ }
+}
+
+static void
+scale_row_from_temp(unsigned char *dst, int *src, fz_weights *weights, int width, int row)
+{
+ int *contrib = &weights->index[weights->index[row]];
+ int len, x;
+
+ contrib++; /* Skip min */
+ len = *contrib++;
+ for (x=width; x > 0; x--)
+ {
+ int *min = src;
+ int val = 0;
+ int len2 = len;
+ int *contrib2 = contrib;
+
+ while (len2-- > 0)
+ {
+ val += *min * *contrib2++;
+ min += width;
+ }
+ val = (val+(1<<15))>>16;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ *dst++ = val;
+ src++;
+ }
+}
+#endif
+
+#ifdef SINGLE_PIXEL_SPECIALS
+static void
+duplicate_single_pixel(unsigned char *dst, unsigned char *src, int n, int w, int h)
+{
+ int i;
+
+ for (i = n; i > 0; i--)
+ *dst++ = *src++;
+ for (i = (w*h-1)*n; i > 0; i--)
+ {
+ *dst = dst[-n];
+ dst++;
+ }
+}
+
+static void
+scale_single_row(unsigned char *dst, unsigned char *src, fz_weights *weights, int src_w, int h)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int min, len, i, j, val, n;
+ int tmp[FZ_MAX_COLORS];
+
+ n = weights->n;
+ /* Scale a single row */
+ if (weights->flip)
+ {
+ dst += (weights->count-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ min = *contrib++;
+ len = *contrib++;
+ min *= n;
+ for (j = 0; j < n; j++)
+ tmp[j] = 0;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[min++] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ val = (tmp[j]+(1<<7))>>8;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ *dst++ = val;
+ }
+ dst -= 2*n;
+ }
+ dst += n * (weights->count+1);
+ }
+ else
+ {
+ for (i=weights->count; i > 0; i--)
+ {
+ min = *contrib++;
+ len = *contrib++;
+ min *= n;
+ for (j = 0; j < n; j++)
+ tmp[j] = 0;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[min++] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ val = (tmp[j]+(1<<7))>>8;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ *dst++ = val;
+ }
+ }
+ }
+ /* And then duplicate it h times */
+ n *= weights->count;
+ while (--h > 0)
+ {
+ memcpy(dst, dst-n, n);
+ dst += n;
+ }
+}
+
+static void
+scale_single_col(unsigned char *dst, unsigned char *src, fz_weights *weights, int src_w, int n, int w, int flip_y)
+{
+ int *contrib = &weights->index[weights->index[0]];
+ int min, len, i, j, val;
+ int tmp[FZ_MAX_COLORS];
+
+ if (flip_y)
+ {
+ src_w = (src_w-1)*n;
+ w = (w-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ /* Scale the next pixel in the column */
+ min = *contrib++;
+ len = *contrib++;
+ min = src_w-min*n;
+ for (j = 0; j < n; j++)
+ tmp[j] = 0;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[src_w-min+j] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ val = (tmp[j]+(1<<7))>>8;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ *dst++ = val;
+ }
+ /* And then duplicate it across the row */
+ for (j = w; j > 0; j--)
+ {
+ *dst = dst[-n];
+ dst++;
+ }
+ }
+ }
+ else
+ {
+ w = (w-1)*n;
+ for (i=weights->count; i > 0; i--)
+ {
+ /* Scale the next pixel in the column */
+ min = *contrib++;
+ len = *contrib++;
+ min *= n;
+ for (j = 0; j < n; j++)
+ tmp[j] = 0;
+ while (len-- > 0)
+ {
+ for (j = 0; j < n; j++)
+ tmp[j] += src[min++] * *contrib;
+ contrib++;
+ }
+ for (j = 0; j < n; j++)
+ {
+ val = (tmp[j]+(1<<7))>>8;
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ *dst++ = val;
+ }
+ /* And then duplicate it across the row */
+ for (j = w; j > 0; j--)
+ {
+ *dst = dst[-n];
+ dst++;
+ }
+ }
+ }
+}
+#endif /* SINGLE_PIXEL_SPECIALS */
+
+fz_pixmap *
+fz_scale_pixmap(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, fz_irect *clip)
+{
+ return fz_scale_pixmap_cached(ctx, src, x, y, w, h, clip, NULL, NULL);
+}
+
+fz_pixmap *
+fz_scale_pixmap_cached(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, const fz_irect *clip, fz_scale_cache *cache_x, fz_scale_cache *cache_y)
+{
+ fz_scale_filter *filter = &fz_scale_filter_simple;
+ fz_weights *contrib_rows = NULL;
+ fz_weights *contrib_cols = NULL;
+ fz_pixmap *output = NULL;
+ int *temp = NULL;
+ int max_row, temp_span, temp_rows, row;
+ int dst_w_int, dst_h_int, dst_x_int, dst_y_int;
+ int flip_x, flip_y;
+ fz_rect patch;
+
+ fz_var(contrib_cols);
+ fz_var(contrib_rows);
+
+ DBUG(("Scale: (%d,%d) to (%g,%g) at (%g,%g)\n",src->w,src->h,w,h,x,y));
+
+ /* Avoid extreme scales where overflows become problematic. */
+ if (w > (1<<24) || h > (1<<24) || w < -(1<<24) || h < -(1<<24))
+ return NULL;
+
+ /* Clamp small ranges of w and h */
+ if (w <= -1)
+ {
+ }
+ else if (w < 0)
+ {
+ w = -1;
+ }
+ else if (w < 1)
+ {
+ w = 1;
+ }
+ if (h <= -1)
+ {
+ }
+ else if (h < 0)
+ {
+ h = -1;
+ }
+ else if (h < 1)
+ {
+ h = 1;
+ }
+
+ /* Find the destination bbox, width/height, and sub pixel offset,
+ * allowing for whether we're flipping or not. */
+ /* The (x,y) position given describes where the top left corner
+ * of the source image should be mapped to (i.e. where (0,0) in image
+ * space ends up). Also there are differences in the way we scale
+ * horizontally and vertically. When scaling rows horizontally, we
+ * always read forwards through the source, and store either forwards
+ * or in reverse as required. When scaling vertically, we always store
+ * out forwards, but may feed source rows in in a different order.
+ *
+ * Consider the image rectangle 'r' to which the image is mapped,
+ * and the (possibly) larger rectangle 'R', given by expanding 'r' to
+ * complete pixels.
+ *
+ * x can either be r.xmin-R.xmin or R.xmax-r.xmax depending on whether
+ * the image is x flipped or not. Whatever happens 0 <= x < 1.
+ * y is always R.ymax - r.ymax.
+ */
+ /* dst_x_int is calculated to be the left of the scaled image, and
+ * x (the sub pixel offset) is the distance in from either the left
+ * or right pixel expanded edge. */
+ flip_x = (w < 0);
+ if (flip_x)
+ {
+ float tmp;
+ w = -w;
+ dst_x_int = floorf(x-w);
+ tmp = ceilf(x);
+ dst_w_int = (int)tmp;
+ x = tmp - x;
+ dst_w_int -= dst_x_int;
+ }
+ else
+ {
+ dst_x_int = floorf(x);
+ x -= (float)dst_x_int;
+ dst_w_int = (int)ceilf(x + w);
+ }
+ /* dst_y_int is calculated to be the top of the scaled image, and
+ * y (the sub pixel offset) is the distance in from either the top
+ * or bottom pixel expanded edge.
+ */
+ flip_y = (h < 0);
+ if (flip_y)
+ {
+ float tmp;
+ h = -h;
+ dst_y_int = floorf(y-h);
+ tmp = ceilf(y);
+ dst_h_int = (int)tmp;
+ y = tmp - y;
+ dst_h_int -= dst_y_int;
+ }
+ else
+ {
+ dst_y_int = floorf(y);
+ y -= (float)dst_y_int;
+ dst_h_int = (int)ceilf(y + h);
+ }
+
+ DBUG(("Result image: (%d,%d) at (%d,%d) (subpix=%g,%g)\n", dst_w_int, dst_h_int, dst_x_int, dst_y_int, x, y));
+
+ /* Step 0: Calculate the patch */
+ patch.x0 = 0;
+ patch.y0 = 0;
+ patch.x1 = dst_w_int;
+ patch.y1 = dst_h_int;
+ if (clip)
+ {
+ if (flip_x)
+ {
+ if (dst_x_int + dst_w_int > clip->x1)
+ patch.x0 = dst_x_int + dst_w_int - clip->x1;
+ if (clip->x0 > dst_x_int)
+ {
+ patch.x1 = dst_w_int - (clip->x0 - dst_x_int);
+ dst_x_int = clip->x0;
+ }
+ }
+ else
+ {
+ if (dst_x_int + dst_w_int > clip->x1)
+ patch.x1 = clip->x1 - dst_x_int;
+ if (clip->x0 > dst_x_int)
+ {
+ patch.x0 = clip->x0 - dst_x_int;
+ dst_x_int += patch.x0;
+ }
+ }
+
+ if (flip_y)
+ {
+ if (dst_y_int + dst_h_int > clip->y1)
+ patch.y1 = clip->y1 - dst_y_int;
+ if (clip->y0 > dst_y_int)
+ {
+ patch.y0 = clip->y0 - dst_y_int;
+ dst_y_int = clip->y0;
+ }
+ }
+ else
+ {
+ if (dst_y_int + dst_h_int > clip->y1)
+ patch.y1 = clip->y1 - dst_y_int;
+ if (clip->y0 > dst_y_int)
+ {
+ patch.y0 = clip->y0 - dst_y_int;
+ dst_y_int += patch.y0;
+ }
+ }
+ }
+ if (patch.x0 >= patch.x1 || patch.y0 >= patch.y1)
+ return NULL;
+
+ fz_try(ctx)
+ {
+ /* Step 1: Calculate the weights for columns and rows */
+#ifdef SINGLE_PIXEL_SPECIALS
+ if (src->w == 1)
+ contrib_cols = NULL;
+ else
+#endif /* SINGLE_PIXEL_SPECIALS */
+ contrib_cols = make_weights(ctx, src->w, x, w, filter, 0, dst_w_int, patch.x0, patch.x1, src->n, flip_x, cache_x);
+#ifdef SINGLE_PIXEL_SPECIALS
+ if (src->h == 1)
+ contrib_rows = NULL;
+ else
+#endif /* SINGLE_PIXEL_SPECIALS */
+ contrib_rows = make_weights(ctx, src->h, y, h, filter, 1, dst_h_int, patch.y0, patch.y1, src->n, flip_y, cache_y);
+
+ output = fz_new_pixmap(ctx, src->colorspace, patch.x1 - patch.x0, patch.y1 - patch.y0);
+ }
+ fz_catch(ctx)
+ {
+ if (!cache_x)
+ fz_free(ctx, contrib_cols);
+ if (!cache_y)
+ fz_free(ctx, contrib_rows);
+ fz_rethrow(ctx);
+ }
+ output->x = dst_x_int;
+ output->y = dst_y_int;
+
+ /* Step 2: Apply the weights */
+#ifdef SINGLE_PIXEL_SPECIALS
+ if (!contrib_rows)
+ {
+ /* Only 1 source pixel high. */
+ if (!contrib_cols)
+ {
+ /* Only 1 pixel in the entire image! */
+ duplicate_single_pixel(output->samples, src->samples, src->n, patch.x1-patch.x0, patch.y1-patch.y0);
+ }
+ else
+ {
+ /* Scale the row once, then copy it. */
+ scale_single_row(output->samples, src->samples, contrib_cols, src->w, patch.y1-patch.y0);
+ }
+ }
+ else if (!contrib_cols)
+ {
+ /* Only 1 source pixel wide. Scale the col and duplicate. */
+ scale_single_col(output->samples, src->samples, contrib_rows, src->h, src->n, patch.x1-patch.x0, flip_y);
+ }
+ else
+#endif /* SINGLE_PIXEL_SPECIALS */
+ {
+ void (*row_scale)(int *dst, unsigned char *src, fz_weights *weights);
+
+ temp_span = contrib_cols->count * src->n;
+ temp_rows = contrib_rows->max_len;
+ if (temp_span <= 0 || temp_rows > INT_MAX / temp_span)
+ goto cleanup;
+ fz_try(ctx)
+ {
+ temp = fz_calloc(ctx, temp_span*temp_rows, sizeof(int));
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_pixmap(ctx, output);
+ if (!cache_x)
+ fz_free(ctx, contrib_cols);
+ if (!cache_y)
+ fz_free(ctx, contrib_rows);
+ fz_rethrow(ctx);
+ }
+ switch (src->n)
+ {
+ default:
+ row_scale = scale_row_to_temp;
+ break;
+ case 1: /* Image mask case */
+ row_scale = scale_row_to_temp1;
+ break;
+ case 2: /* Greyscale with alpha case */
+ row_scale = scale_row_to_temp2;
+ break;
+ case 4: /* RGBA */
+ row_scale = scale_row_to_temp4;
+ break;
+ }
+ max_row = contrib_rows->index[contrib_rows->index[0]];
+ for (row = 0; row < contrib_rows->count; row++)
+ {
+ /*
+ Which source rows do we need to have scaled into the
+ temporary buffer in order to be able to do the final
+ scale?
+ */
+ int row_index = contrib_rows->index[row];
+ int row_min = contrib_rows->index[row_index++];
+ int row_len = contrib_rows->index[row_index++];
+ while (max_row < row_min+row_len)
+ {
+ /* Scale another row */
+ assert(max_row < src->h);
+ DBUG(("scaling row %d to temp\n", max_row));
+ (*row_scale)(&temp[temp_span*(max_row % temp_rows)], &src->samples[(flip_y ? (src->h-1-max_row): max_row)*src->w*src->n], contrib_cols);
+ max_row++;
+ }
+
+ DBUG(("scaling row %d from temp\n", row));
+ scale_row_from_temp(&output->samples[row*output->w*output->n], temp, contrib_rows, temp_span, row);
+ }
+ fz_free(ctx, temp);
+ }
+
+cleanup:
+ if (!cache_y)
+ fz_free(ctx, contrib_rows);
+ if (!cache_x)
+ fz_free(ctx, contrib_cols);
+ return output;
+}
+
+void
+fz_free_scale_cache(fz_context *ctx, fz_scale_cache *sc)
+{
+ if (!sc)
+ return;
+ fz_free(ctx, sc->weights);
+ fz_free(ctx, sc);
+}
+
+fz_scale_cache *
+fz_new_scale_cache(fz_context *ctx)
+{
+ return fz_malloc_struct(ctx, fz_scale_cache);
+}
diff --git a/source/fitz/draw-unpack.c b/source/fitz/draw-unpack.c
new file mode 100644
index 00000000..d862ea2a
--- /dev/null
+++ b/source/fitz/draw-unpack.c
@@ -0,0 +1,242 @@
+#include "mupdf/fitz.h"
+#include "draw-imp.h"
+
+/* Unpack image samples and optionally pad pixels with opaque alpha */
+
+#define get1(buf,x) ((buf[x >> 3] >> ( 7 - (x & 7) ) ) & 1 )
+#define get2(buf,x) ((buf[x >> 2] >> ( ( 3 - (x & 3) ) << 1 ) ) & 3 )
+#define get4(buf,x) ((buf[x >> 1] >> ( ( 1 - (x & 1) ) << 2 ) ) & 15 )
+#define get8(buf,x) (buf[x])
+#define get16(buf,x) (buf[x << 1])
+
+static unsigned char get1_tab_1[256][8];
+static unsigned char get1_tab_1p[256][16];
+static unsigned char get1_tab_255[256][8];
+static unsigned char get1_tab_255p[256][16];
+
+static void
+init_get1_tables(void)
+{
+ static int once = 0;
+ unsigned char bits[1];
+ int i, k, x;
+
+ /* TODO: mutex lock here */
+
+ if (once)
+ return;
+
+ for (i = 0; i < 256; i++)
+ {
+ bits[0] = i;
+ for (k = 0; k < 8; k++)
+ {
+ x = get1(bits, k);
+
+ get1_tab_1[i][k] = x;
+ get1_tab_1p[i][k * 2] = x;
+ get1_tab_1p[i][k * 2 + 1] = 255;
+
+ get1_tab_255[i][k] = x * 255;
+ get1_tab_255p[i][k * 2] = x * 255;
+ get1_tab_255p[i][k * 2 + 1] = 255;
+ }
+ }
+
+ once = 1;
+}
+
+void
+fz_unpack_tile(fz_pixmap *dst, unsigned char * restrict src, int n, int depth, int stride, int scale)
+{
+ int pad, x, y, k;
+ int w = dst->w;
+
+ pad = 0;
+ if (dst->n > n)
+ pad = 255;
+
+ if (depth == 1)
+ init_get1_tables();
+
+ if (scale == 0)
+ {
+ switch (depth)
+ {
+ case 1: scale = 255; break;
+ case 2: scale = 85; break;
+ case 4: scale = 17; break;
+ }
+ }
+
+ for (y = 0; y < dst->h; y++)
+ {
+ unsigned char *sp = src + (unsigned int)(y * stride);
+ unsigned char *dp = dst->samples + (unsigned int)(y * dst->w * dst->n);
+
+ /* Specialized loops */
+
+ if (n == 1 && depth == 1 && scale == 1 && !pad)
+ {
+ int w3 = w >> 3;
+ for (x = 0; x < w3; x++)
+ {
+ memcpy(dp, get1_tab_1[*sp++], 8);
+ dp += 8;
+ }
+ x = x << 3;
+ if (x < w)
+ memcpy(dp, get1_tab_1[*sp], w - x);
+ }
+
+ else if (n == 1 && depth == 1 && scale == 255 && !pad)
+ {
+ int w3 = w >> 3;
+ for (x = 0; x < w3; x++)
+ {
+ memcpy(dp, get1_tab_255[*sp++], 8);
+ dp += 8;
+ }
+ x = x << 3;
+ if (x < w)
+ memcpy(dp, get1_tab_255[*sp], w - x);
+ }
+
+ else if (n == 1 && depth == 1 && scale == 1 && pad)
+ {
+ int w3 = w >> 3;
+ for (x = 0; x < w3; x++)
+ {
+ memcpy(dp, get1_tab_1p[*sp++], 16);
+ dp += 16;
+ }
+ x = x << 3;
+ if (x < w)
+ memcpy(dp, get1_tab_1p[*sp], (w - x) << 1);
+ }
+
+ else if (n == 1 && depth == 1 && scale == 255 && pad)
+ {
+ int w3 = w >> 3;
+ for (x = 0; x < w3; x++)
+ {
+ memcpy(dp, get1_tab_255p[*sp++], 16);
+ dp += 16;
+ }
+ x = x << 3;
+ if (x < w)
+ memcpy(dp, get1_tab_255p[*sp], (w - x) << 1);
+ }
+
+ else if (depth == 8 && !pad)
+ {
+ int len = w * n;
+ while (len--)
+ *dp++ = *sp++;
+ }
+
+ else if (depth == 8 && pad)
+ {
+ for (x = 0; x < w; x++)
+ {
+ for (k = 0; k < n; k++)
+ *dp++ = *sp++;
+ *dp++ = 255;
+ }
+ }
+
+ else
+ {
+ int b = 0;
+ for (x = 0; x < w; x++)
+ {
+ for (k = 0; k < n; k++)
+ {
+ switch (depth)
+ {
+ case 1: *dp++ = get1(sp, b) * scale; break;
+ case 2: *dp++ = get2(sp, b) * scale; break;
+ case 4: *dp++ = get4(sp, b) * scale; break;
+ case 8: *dp++ = get8(sp, b); break;
+ case 16: *dp++ = get16(sp, b); break;
+ }
+ b++;
+ }
+ if (pad)
+ *dp++ = 255;
+ }
+ }
+ }
+}
+
+/* Apply decode array */
+
+void
+fz_decode_indexed_tile(fz_pixmap *pix, float *decode, int maxval)
+{
+ int add[FZ_MAX_COLORS];
+ int mul[FZ_MAX_COLORS];
+ unsigned char *p = pix->samples;
+ int len = pix->w * pix->h;
+ int n = pix->n - 1;
+ int needed;
+ int k;
+
+ needed = 0;
+ for (k = 0; k < n; k++)
+ {
+ int min = decode[k * 2] * 256;
+ int max = decode[k * 2 + 1] * 256;
+ add[k] = min;
+ mul[k] = (max - min) / maxval;
+ needed |= min != 0 || max != maxval * 256;
+ }
+
+ if (!needed)
+ return;
+
+ while (len--)
+ {
+ for (k = 0; k < n; k++)
+ {
+ int value = (add[k] + (((p[k] << 8) * mul[k]) >> 8)) >> 8;
+ p[k] = fz_clampi(value, 0, 255);
+ }
+ p += n + 1;
+ }
+}
+
+void
+fz_decode_tile(fz_pixmap *pix, float *decode)
+{
+ int add[FZ_MAX_COLORS];
+ int mul[FZ_MAX_COLORS];
+ unsigned char *p = pix->samples;
+ int len = pix->w * pix->h;
+ int n = fz_maxi(1, pix->n - 1);
+ int needed;
+ int k;
+
+ needed = 0;
+ for (k = 0; k < n; k++)
+ {
+ int min = decode[k * 2] * 255;
+ int max = decode[k * 2 + 1] * 255;
+ add[k] = min;
+ mul[k] = max - min;
+ needed |= min != 0 || max != 255;
+ }
+
+ if (!needed)
+ return;
+
+ while (len--)
+ {
+ for (k = 0; k < n; k++)
+ {
+ int value = add[k] + fz_mul255(p[k], mul[k]);
+ p[k] = fz_clampi(value, 0, 255);
+ }
+ p += pix->n;
+ }
+}
diff --git a/source/fitz/error.c b/source/fitz/error.c
new file mode 100644
index 00000000..50b3c5aa
--- /dev/null
+++ b/source/fitz/error.c
@@ -0,0 +1,155 @@
+#include "mupdf/fitz.h"
+
+/* Warning context */
+
+void fz_var_imp(void *var)
+{
+ UNUSED(var); /* Do nothing */
+}
+
+void fz_flush_warnings(fz_context *ctx)
+{
+ if (ctx->warn->count > 1)
+ {
+ fprintf(stderr, "warning: ... repeated %d times ...\n", ctx->warn->count);
+ LOGE("warning: ... repeated %d times ...\n", ctx->warn->count);
+ }
+ ctx->warn->message[0] = 0;
+ ctx->warn->count = 0;
+}
+
+void fz_warn(fz_context *ctx, const char *fmt, ...)
+{
+ va_list ap;
+ char buf[sizeof ctx->warn->message];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof buf, fmt, ap);
+ va_end(ap);
+
+ if (!strcmp(buf, ctx->warn->message))
+ {
+ ctx->warn->count++;
+ }
+ else
+ {
+ fz_flush_warnings(ctx);
+ fprintf(stderr, "warning: %s\n", buf);
+ LOGE("warning: %s\n", buf);
+ fz_strlcpy(ctx->warn->message, buf, sizeof ctx->warn->message);
+ ctx->warn->count = 1;
+ }
+}
+
+/* Error context */
+
+/* When we first setjmp, code is set to 0. Whenever we throw, we add 2 to
+ * this code. Whenever we enter the always block, we add 1.
+ *
+ * fz_push_try sets code to 0.
+ * If (fz_throw called within fz_try)
+ * fz_throw makes code = 2.
+ * If (no always block present)
+ * enter catch region with code = 2. OK.
+ * else
+ * fz_always entered as code < 3; Makes code = 3;
+ * if (fz_throw called within fz_always)
+ * fz_throw makes code = 5
+ * fz_always is not reentered.
+ * catch region entered with code = 5. OK.
+ * else
+ * catch region entered with code = 3. OK
+ * else
+ * if (no always block present)
+ * catch region not entered as code = 0. OK.
+ * else
+ * fz_always entered as code < 3. makes code = 1
+ * if (fz_throw called within fz_always)
+ * fz_throw makes code = 3;
+ * fz_always NOT entered as code >= 3
+ * catch region entered with code = 3. OK.
+ * else
+ * catch region entered with code = 1.
+ */
+
+static void throw(fz_error_context *ex) FZ_NORETURN;
+
+static void throw(fz_error_context *ex)
+{
+ if (ex->top >= 0) {
+ fz_longjmp(ex->stack[ex->top].buffer, ex->stack[ex->top].code + 2);
+ } else {
+ fprintf(stderr, "uncaught exception: %s\n", ex->message);
+ LOGE("uncaught exception: %s\n", ex->message);
+ exit(EXIT_FAILURE);
+ }
+}
+
+int fz_push_try(fz_error_context *ex)
+{
+ assert(ex);
+ ex->top++;
+ /* Normal case, get out of here quick */
+ if (ex->top < nelem(ex->stack)-1)
+ return 1; /* We exit here, and the setjmp sets the code to 0 */
+ /* We reserve the top slot on the exception stack purely to cope with
+ * the case when we overflow. If we DO hit this, then we 'throw'
+ * immediately - returning 0 stops the setjmp happening and takes us
+ * direct to the always/catch clauses. */
+ assert(ex->top == nelem(ex->stack)-1);
+ strcpy(ex->message, "exception stack overflow!");
+ ex->stack[ex->top].code = 2;
+ fprintf(stderr, "error: %s\n", ex->message);
+ LOGE("error: %s\n", ex->message);
+ return 0;
+}
+
+int fz_caught(fz_context *ctx)
+{
+ assert(ctx && ctx->error && ctx->error->errcode >= FZ_ERROR_NONE);
+ return ctx->error->errcode;
+}
+
+const char *fz_caught_message(fz_context *ctx)
+{
+ assert(ctx && ctx->error && ctx->error->errcode >= FZ_ERROR_NONE);
+ return ctx->error->message;
+}
+
+void fz_throw(fz_context *ctx, int code, const char *fmt, ...)
+{
+ va_list args;
+ ctx->error->errcode = code;
+ va_start(args, fmt);
+ vsnprintf(ctx->error->message, sizeof ctx->error->message, fmt, args);
+ va_end(args);
+
+ fz_flush_warnings(ctx);
+ fprintf(stderr, "error: %s\n", ctx->error->message);
+ LOGE("error: %s\n", ctx->error->message);
+
+ throw(ctx->error);
+}
+
+void fz_rethrow(fz_context *ctx)
+{
+ assert(ctx && ctx->error && ctx->error->errcode >= FZ_ERROR_NONE);
+ throw(ctx->error);
+}
+
+void fz_rethrow_message(fz_context *ctx, const char *fmt, ...)
+{
+ va_list args;
+
+ assert(ctx && ctx->error && ctx->error->errcode >= FZ_ERROR_NONE);
+
+ va_start(args, fmt);
+ vsnprintf(ctx->error->message, sizeof ctx->error->message, fmt, args);
+ va_end(args);
+
+ fz_flush_warnings(ctx);
+ fprintf(stderr, "error: %s\n", ctx->error->message);
+ LOGE("error: %s\n", ctx->error->message);
+
+ throw(ctx->error);
+}
diff --git a/source/fitz/filter-basic.c b/source/fitz/filter-basic.c
new file mode 100644
index 00000000..3968d193
--- /dev/null
+++ b/source/fitz/filter-basic.c
@@ -0,0 +1,662 @@
+#include "mupdf/fitz.h"
+
+/* Pretend we have a filter that just copies data forever */
+
+fz_stream *
+fz_open_copy(fz_stream *chain)
+{
+ return fz_keep_stream(chain);
+}
+
+/* Null filter copies a specified amount of data */
+
+struct null_filter
+{
+ fz_stream *chain;
+ int remain;
+ int pos;
+};
+
+static int
+read_null(fz_stream *stm, unsigned char *buf, int len)
+{
+ struct null_filter *state = stm->state;
+ int amount = fz_mini(len, state->remain);
+ int n;
+
+ fz_seek(state->chain, state->pos, 0);
+ n = fz_read(state->chain, buf, amount);
+ state->remain -= n;
+ state->pos += n;
+ return n;
+}
+
+static void
+close_null(fz_context *ctx, void *state_)
+{
+ struct null_filter *state = (struct null_filter *)state_;
+ fz_stream *chain = state->chain;
+ fz_free(ctx, state);
+ fz_close(chain);
+}
+
+fz_stream *
+fz_open_null(fz_stream *chain, int len, int offset)
+{
+ struct null_filter *state;
+ fz_context *ctx = chain->ctx;
+
+ if (len < 0)
+ len = 0;
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, struct null_filter);
+ state->chain = chain;
+ state->remain = len;
+ state->pos = offset;
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_null, close_null);
+}
+
+/* Concat filter concatenates several streams into one */
+
+struct concat_filter
+{
+ int max;
+ int count;
+ int current;
+ int pad; /* 1 if we should add whitespace padding between streams */
+ int ws; /* 1 if we should send a whitespace padding byte next */
+ fz_stream *chain[1];
+};
+
+static int
+read_concat(fz_stream *stm, unsigned char *buf, int len)
+{
+ struct concat_filter *state = (struct concat_filter *)stm->state;
+ int n;
+ int read = 0;
+
+ if (len <= 0)
+ return 0;
+
+ while (state->current != state->count && len > 0)
+ {
+ /* If we need to send a whitespace char, do that */
+ if (state->ws)
+ {
+ *buf++ = 32;
+ read++;
+ len--;
+ state->ws = 0;
+ continue;
+ }
+ /* Otherwise, read as much data as will fit in the buffer */
+ n = fz_read(state->chain[state->current], buf, len);
+ read += n;
+ buf += n;
+ len -= n;
+ /* If we didn't read any, then we must have hit the end of
+ * our buffer space. Move to the next stream, and remember to
+ * pad. */
+ if (n == 0)
+ {
+ fz_close(state->chain[state->current]);
+ state->current++;
+ state->ws = state->pad;
+ }
+ }
+
+ return read;
+}
+
+static void
+close_concat(fz_context *ctx, void *state_)
+{
+ struct concat_filter *state = (struct concat_filter *)state_;
+ int i;
+
+ for (i = state->current; i < state->count; i++)
+ {
+ fz_close(state->chain[i]);
+ }
+ fz_free(ctx, state);
+}
+
+fz_stream *
+fz_open_concat(fz_context *ctx, int len, int pad)
+{
+ struct concat_filter *state;
+
+ state = fz_calloc(ctx, 1, sizeof(struct concat_filter) + (len-1)*sizeof(fz_stream *));
+ state->max = len;
+ state->count = 0;
+ state->current = 0;
+ state->pad = pad;
+ state->ws = 0; /* We never send padding byte at the start */
+
+ return fz_new_stream(ctx, state, read_concat, close_concat);
+}
+
+void
+fz_concat_push(fz_stream *concat, fz_stream *chain)
+{
+ struct concat_filter *state = (struct concat_filter *)concat->state;
+
+ if (state->count == state->max)
+ fz_throw(concat->ctx, FZ_ERROR_GENERIC, "Concat filter size exceeded");
+
+ state->chain[state->count++] = chain;
+}
+
+/* ASCII Hex Decode */
+
+typedef struct fz_ahxd_s fz_ahxd;
+
+struct fz_ahxd_s
+{
+ fz_stream *chain;
+ int eod;
+};
+
+static inline int iswhite(int a)
+{
+ switch (a) {
+ case '\n': case '\r': case '\t': case ' ':
+ case '\0': case '\f': case '\b': case 0177:
+ return 1;
+ }
+ return 0;
+}
+
+static inline int ishex(int a)
+{
+ return (a >= 'A' && a <= 'F') ||
+ (a >= 'a' && a <= 'f') ||
+ (a >= '0' && a <= '9');
+}
+
+static inline int unhex(int a)
+{
+ if (a >= 'A' && a <= 'F') return a - 'A' + 0xA;
+ if (a >= 'a' && a <= 'f') return a - 'a' + 0xA;
+ if (a >= '0' && a <= '9') return a - '0';
+ return 0;
+}
+
+static int
+read_ahxd(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_ahxd *state = stm->state;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+ int a, b, c, odd;
+
+ odd = 0;
+
+ while (p < ep)
+ {
+ if (state->eod)
+ return p - buf;
+
+ c = fz_read_byte(state->chain);
+ if (c < 0)
+ return p - buf;
+
+ if (ishex(c))
+ {
+ if (!odd)
+ {
+ a = unhex(c);
+ odd = 1;
+ }
+ else
+ {
+ b = unhex(c);
+ *p++ = (a << 4) | b;
+ odd = 0;
+ }
+ }
+ else if (c == '>')
+ {
+ if (odd)
+ *p++ = (a << 4);
+ state->eod = 1;
+ }
+ else if (!iswhite(c))
+ {
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "bad data in ahxd: '%c'", c);
+ }
+ }
+
+ return p - buf;
+}
+
+static void
+close_ahxd(fz_context *ctx, void *state_)
+{
+ fz_ahxd *state = (fz_ahxd *)state_;
+ fz_stream *chain = state->chain;
+ fz_free(ctx, state);
+ fz_close(chain);
+}
+
+fz_stream *
+fz_open_ahxd(fz_stream *chain)
+{
+ fz_ahxd *state;
+ fz_context *ctx = chain->ctx;
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_ahxd);
+ state->chain = chain;
+ state->eod = 0;
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_ahxd, close_ahxd);
+}
+
+/* ASCII 85 Decode */
+
+typedef struct fz_a85d_s fz_a85d;
+
+struct fz_a85d_s
+{
+ fz_stream *chain;
+ unsigned char bp[4];
+ unsigned char *rp, *wp;
+ int eod;
+};
+
+static int
+read_a85d(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_a85d *state = stm->state;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+ int count = 0;
+ int word = 0;
+ int c;
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+
+ while (p < ep)
+ {
+ if (state->eod)
+ return p - buf;
+
+ c = fz_read_byte(state->chain);
+ if (c < 0)
+ return p - buf;
+
+ if (c >= '!' && c <= 'u')
+ {
+ if (count == 4)
+ {
+ word = word * 85 + (c - '!');
+
+ state->bp[0] = (word >> 24) & 0xff;
+ state->bp[1] = (word >> 16) & 0xff;
+ state->bp[2] = (word >> 8) & 0xff;
+ state->bp[3] = (word) & 0xff;
+ state->rp = state->bp;
+ state->wp = state->bp + 4;
+
+ word = 0;
+ count = 0;
+ }
+ else
+ {
+ word = word * 85 + (c - '!');
+ count ++;
+ }
+ }
+
+ else if (c == 'z' && count == 0)
+ {
+ state->bp[0] = 0;
+ state->bp[1] = 0;
+ state->bp[2] = 0;
+ state->bp[3] = 0;
+ state->rp = state->bp;
+ state->wp = state->bp + 4;
+ }
+
+ else if (c == '~')
+ {
+ c = fz_read_byte(state->chain);
+ if (c != '>')
+ fz_warn(stm->ctx, "bad eod marker in a85d");
+
+ switch (count) {
+ case 0:
+ break;
+ case 1:
+ /* Specifically illegal in the spec, but adobe
+ * and gs both cope. See normal_87.pdf for a
+ * case where this matters. */
+ fz_warn(stm->ctx, "partial final byte in a85d");
+ break;
+ case 2:
+ word = word * (85 * 85 * 85) + 0xffffff;
+ state->bp[0] = word >> 24;
+ state->rp = state->bp;
+ state->wp = state->bp + 1;
+ break;
+ case 3:
+ word = word * (85 * 85) + 0xffff;
+ state->bp[0] = word >> 24;
+ state->bp[1] = word >> 16;
+ state->rp = state->bp;
+ state->wp = state->bp + 2;
+ break;
+ case 4:
+ word = word * 85 + 0xff;
+ state->bp[0] = word >> 24;
+ state->bp[1] = word >> 16;
+ state->bp[2] = word >> 8;
+ state->rp = state->bp;
+ state->wp = state->bp + 3;
+ break;
+ }
+ state->eod = 1;
+ }
+
+ else if (!iswhite(c))
+ {
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "bad data in a85d: '%c'", c);
+ }
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+ }
+
+ return p - buf;
+}
+
+static void
+close_a85d(fz_context *ctx, void *state_)
+{
+ fz_a85d *state = (fz_a85d *)state_;
+ fz_stream *chain = state->chain;
+
+ fz_free(ctx, state);
+ fz_close(chain);
+}
+
+fz_stream *
+fz_open_a85d(fz_stream *chain)
+{
+ fz_a85d *state;
+ fz_context *ctx = chain->ctx;
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_a85d);
+ state->chain = chain;
+ state->rp = state->bp;
+ state->wp = state->bp;
+ state->eod = 0;
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_a85d, close_a85d);
+}
+
+/* Run Length Decode */
+
+typedef struct fz_rld_s fz_rld;
+
+struct fz_rld_s
+{
+ fz_stream *chain;
+ int run, n, c;
+};
+
+static int
+read_rld(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_rld *state = stm->state;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+
+ while (p < ep)
+ {
+ if (state->run == 128)
+ return p - buf;
+
+ if (state->n == 0)
+ {
+ state->run = fz_read_byte(state->chain);
+ if (state->run < 0)
+ state->run = 128;
+ if (state->run < 128)
+ state->n = state->run + 1;
+ if (state->run > 128)
+ {
+ state->n = 257 - state->run;
+ state->c = fz_read_byte(state->chain);
+ if (state->c < 0)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "premature end of data in run length decode");
+ }
+ }
+
+ if (state->run < 128)
+ {
+ while (p < ep && state->n)
+ {
+ int c = fz_read_byte(state->chain);
+ if (c < 0)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "premature end of data in run length decode");
+ *p++ = c;
+ state->n--;
+ }
+ }
+
+ if (state->run > 128)
+ {
+ while (p < ep && state->n)
+ {
+ *p++ = state->c;
+ state->n--;
+ }
+ }
+ }
+
+ return p - buf;
+}
+
+static void
+close_rld(fz_context *ctx, void *state_)
+{
+ fz_rld *state = (fz_rld *)state_;
+ fz_stream *chain = state->chain;
+
+ fz_free(ctx, state);
+ fz_close(chain);
+}
+
+fz_stream *
+fz_open_rld(fz_stream *chain)
+{
+ fz_rld *state;
+ fz_context *ctx = chain->ctx;
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_rld);
+ state->chain = chain;
+ state->run = 0;
+ state->n = 0;
+ state->c = 0;
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_rld, close_rld);
+}
+
+/* RC4 Filter */
+
+typedef struct fz_arc4c_s fz_arc4c;
+
+struct fz_arc4c_s
+{
+ fz_stream *chain;
+ fz_arc4 arc4;
+};
+
+static int
+read_arc4(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_arc4c *state = stm->state;
+ int n = fz_read(state->chain, buf, len);
+ fz_arc4_encrypt(&state->arc4, buf, buf, n);
+ return n;
+}
+
+static void
+close_arc4(fz_context *ctx, void *state_)
+{
+ fz_arc4c *state = (fz_arc4c *)state_;
+ fz_stream *chain = state->chain;
+
+ fz_free(ctx, state);
+ fz_close(chain);
+}
+
+fz_stream *
+fz_open_arc4(fz_stream *chain, unsigned char *key, unsigned keylen)
+{
+ fz_arc4c *state;
+ fz_context *ctx = chain->ctx;
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_arc4c);
+ state->chain = chain;
+ fz_arc4_init(&state->arc4, key, keylen);
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_arc4, close_arc4);
+}
+
+/* AES Filter */
+
+typedef struct fz_aesd_s fz_aesd;
+
+struct fz_aesd_s
+{
+ fz_stream *chain;
+ fz_aes aes;
+ unsigned char iv[16];
+ int ivcount;
+ unsigned char bp[16];
+ unsigned char *rp, *wp;
+};
+
+static int
+read_aesd(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_aesd *state = stm->state;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+
+ while (state->ivcount < 16)
+ {
+ int c = fz_read_byte(state->chain);
+ if (c < 0)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "premature end in aes filter");
+ state->iv[state->ivcount++] = c;
+ }
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+
+ while (p < ep)
+ {
+ int n = fz_read(state->chain, state->bp, 16);
+ if (n == 0)
+ return p - buf;
+ else if (n < 16)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "partial block in aes filter");
+
+ aes_crypt_cbc(&state->aes, AES_DECRYPT, 16, state->iv, state->bp, state->bp);
+ state->rp = state->bp;
+ state->wp = state->bp + 16;
+
+ /* strip padding at end of file */
+ if (fz_is_eof(state->chain))
+ {
+ int pad = state->bp[15];
+ if (pad < 1 || pad > 16)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "aes padding out of range: %d", pad);
+ state->wp -= pad;
+ }
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+ }
+
+ return p - buf;
+}
+
+static void
+close_aesd(fz_context *ctx, void *state_)
+{
+ fz_aesd *state = (fz_aesd *)state_;
+ fz_stream *chain = state->chain;
+
+ fz_free(ctx, state);
+ fz_close(chain);
+}
+
+fz_stream *
+fz_open_aesd(fz_stream *chain, unsigned char *key, unsigned keylen)
+{
+ fz_aesd *state;
+ fz_context *ctx = chain->ctx;
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_aesd);
+ state->chain = chain;
+ if (aes_setkey_dec(&state->aes, key, keylen * 8))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", keylen * 8);
+ state->ivcount = 0;
+ state->rp = state->bp;
+ state->wp = state->bp;
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_aesd, close_aesd);
+}
diff --git a/source/fitz/filter-dct.c b/source/fitz/filter-dct.c
new file mode 100644
index 00000000..1a55e584
--- /dev/null
+++ b/source/fitz/filter-dct.c
@@ -0,0 +1,256 @@
+#include "mupdf/fitz.h"
+
+#include <jpeglib.h>
+#include <setjmp.h>
+
+typedef struct fz_dctd_s fz_dctd;
+
+struct fz_dctd_s
+{
+ fz_stream *chain;
+ fz_context *ctx;
+ int color_transform;
+ int init;
+ int stride;
+ int l2factor;
+ unsigned char *scanline;
+ unsigned char *rp, *wp;
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_source_mgr srcmgr;
+ struct jpeg_error_mgr errmgr;
+ jmp_buf jb;
+ char msg[JMSG_LENGTH_MAX];
+};
+
+static void error_exit(j_common_ptr cinfo)
+{
+ fz_dctd *state = cinfo->client_data;
+ cinfo->err->format_message(cinfo, state->msg);
+ longjmp(state->jb, 1);
+}
+
+static void init_source(j_decompress_ptr cinfo)
+{
+ /* nothing to do */
+}
+
+static void term_source(j_decompress_ptr cinfo)
+{
+ /* nothing to do */
+}
+
+static boolean fill_input_buffer(j_decompress_ptr cinfo)
+{
+ struct jpeg_source_mgr *src = cinfo->src;
+ fz_dctd *state = cinfo->client_data;
+ fz_stream *chain = state->chain;
+ fz_context *ctx = chain->ctx;
+
+ chain->rp = chain->wp;
+ fz_try(ctx)
+ {
+ fz_fill_buffer(chain);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TRYLATER */
+ return 0;
+ }
+ src->next_input_byte = chain->rp;
+ src->bytes_in_buffer = chain->wp - chain->rp;
+
+ if (src->bytes_in_buffer == 0)
+ {
+ static unsigned char eoi[2] = { 0xFF, JPEG_EOI };
+ fz_warn(state->ctx, "premature end of file in jpeg");
+ src->next_input_byte = eoi;
+ src->bytes_in_buffer = 2;
+ }
+
+ return 1;
+}
+
+static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+ struct jpeg_source_mgr *src = cinfo->src;
+ if (num_bytes > 0)
+ {
+ while ((size_t)num_bytes > src->bytes_in_buffer)
+ {
+ num_bytes -= src->bytes_in_buffer;
+ (void) src->fill_input_buffer(cinfo);
+ }
+ src->next_input_byte += num_bytes;
+ src->bytes_in_buffer -= num_bytes;
+ }
+}
+
+static int
+read_dctd(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_dctd *state = stm->state;
+ j_decompress_ptr cinfo = &state->cinfo;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+
+ if (setjmp(state->jb))
+ {
+ if (cinfo->src)
+ state->chain->rp = state->chain->wp - cinfo->src->bytes_in_buffer;
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "jpeg error: %s", state->msg);
+ }
+
+ if (!state->init)
+ {
+ int c;
+ cinfo->client_data = state;
+ cinfo->err = &state->errmgr;
+ jpeg_std_error(cinfo->err);
+ cinfo->err->error_exit = error_exit;
+ jpeg_create_decompress(cinfo);
+ state->init = 1;
+
+ /* Skip over any stray returns at the start of the stream */
+ while ((c = fz_peek_byte(state->chain)) == '\n' || c == '\r')
+ (void)fz_read_byte(state->chain);
+
+ cinfo->src = &state->srcmgr;
+ cinfo->src->init_source = init_source;
+ cinfo->src->fill_input_buffer = fill_input_buffer;
+ cinfo->src->skip_input_data = skip_input_data;
+ cinfo->src->resync_to_restart = jpeg_resync_to_restart;
+ cinfo->src->term_source = term_source;
+ cinfo->src->next_input_byte = state->chain->rp;
+ cinfo->src->bytes_in_buffer = state->chain->wp - state->chain->rp;
+
+ jpeg_read_header(cinfo, 1);
+
+ /* speed up jpeg decoding a bit */
+ cinfo->dct_method = JDCT_FASTEST;
+ cinfo->do_fancy_upsampling = FALSE;
+
+ /* default value if ColorTransform is not set */
+ if (state->color_transform == -1)
+ {
+ if (state->cinfo.num_components == 3)
+ state->color_transform = 1;
+ else
+ state->color_transform = 0;
+ }
+
+ if (cinfo->saw_Adobe_marker)
+ state->color_transform = cinfo->Adobe_transform;
+
+ /* Guess the input colorspace, and set output colorspace accordingly */
+ switch (cinfo->num_components)
+ {
+ case 3:
+ if (state->color_transform)
+ cinfo->jpeg_color_space = JCS_YCbCr;
+ else
+ cinfo->jpeg_color_space = JCS_RGB;
+ break;
+ case 4:
+ if (state->color_transform)
+ cinfo->jpeg_color_space = JCS_YCCK;
+ else
+ cinfo->jpeg_color_space = JCS_CMYK;
+ break;
+ }
+
+ cinfo->scale_num = 8/(1<<state->l2factor);
+ cinfo->scale_denom = 8;
+
+ jpeg_start_decompress(cinfo);
+
+ state->stride = cinfo->output_width * cinfo->output_components;
+ state->scanline = fz_malloc(state->ctx, state->stride);
+ state->rp = state->scanline;
+ state->wp = state->scanline;
+ }
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+
+ while (p < ep)
+ {
+ if (cinfo->output_scanline == cinfo->output_height)
+ break;
+
+ if (p + state->stride <= ep)
+ {
+ jpeg_read_scanlines(cinfo, &p, 1);
+ p += state->stride;
+ }
+ else
+ {
+ jpeg_read_scanlines(cinfo, &state->scanline, 1);
+ state->rp = state->scanline;
+ state->wp = state->scanline + state->stride;
+ }
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+ }
+
+ return p - buf;
+}
+
+static void
+close_dctd(fz_context *ctx, void *state_)
+{
+ fz_dctd *state = (fz_dctd *)state_;
+
+ if (setjmp(state->jb))
+ {
+ fz_warn(ctx, "jpeg error: %s", state->msg);
+ goto skip;
+ }
+
+ if (state->init)
+ jpeg_finish_decompress(&state->cinfo);
+
+skip:
+ if (state->cinfo.src)
+ state->chain->rp = state->chain->wp - state->cinfo.src->bytes_in_buffer;
+ if (state->init)
+ jpeg_destroy_decompress(&state->cinfo);
+
+ fz_free(ctx, state->scanline);
+ fz_close(state->chain);
+ fz_free(ctx, state);
+}
+
+/* Default: color_transform = -1 (unset) */
+fz_stream *
+fz_open_dctd(fz_stream *chain, int color_transform)
+{
+ return fz_open_resized_dctd(chain, color_transform, 0);
+}
+
+fz_stream *
+fz_open_resized_dctd(fz_stream *chain, int color_transform, int l2factor)
+{
+ fz_context *ctx = chain->ctx;
+ fz_dctd *state = NULL;
+
+ fz_var(state);
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(chain->ctx, fz_dctd);
+ state->ctx = ctx;
+ state->chain = chain;
+ state->color_transform = color_transform;
+ state->init = 0;
+ state->l2factor = l2factor;
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, state);
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_dctd, close_dctd);
+}
diff --git a/source/fitz/filter-fax.c b/source/fitz/filter-fax.c
new file mode 100644
index 00000000..8ac98f42
--- /dev/null
+++ b/source/fitz/filter-fax.c
@@ -0,0 +1,776 @@
+#include "mupdf/fitz.h"
+
+/* Fax G3/G4 decoder */
+
+/* TODO: uncompressed */
+
+/*
+<raph> the first 2^(initialbits) entries map bit patterns to decodes
+<raph> let's say initial_bits is 8 for the sake of example
+<raph> and that the code is 1001
+<raph> that means that entries 0x90 .. 0x9f have the entry { val, 4 }
+<raph> because those are all the bytes that start with the code
+<raph> and the 4 is the length of the code
+... if (n_bits > initial_bits) ...
+<raph> anyway, in that case, it basically points to a mini table
+<raph> the n_bits is the maximum length of all codes beginning with that byte
+<raph> so 2^(n_bits - initial_bits) is the size of the mini-table
+<raph> peter came up with this, and it makes sense
+*/
+
+typedef struct cfd_node_s cfd_node;
+
+struct cfd_node_s
+{
+ short val;
+ short nbits;
+};
+
+enum
+{
+ cfd_white_initial_bits = 8,
+ cfd_black_initial_bits = 7,
+ cfd_2d_initial_bits = 7,
+ cfd_uncompressed_initial_bits = 6 /* must be 6 */
+};
+
+/* non-run codes in tables */
+enum
+{
+ ERROR = -1,
+ ZEROS = -2, /* EOL follows, possibly with more padding first */
+ UNCOMPRESSED = -3
+};
+
+/* semantic codes for cf_2d_decode */
+enum
+{
+ P = -4,
+ H = -5,
+ VR3 = 0,
+ VR2 = 1,
+ VR1 = 2,
+ V0 = 3,
+ VL1 = 4,
+ VL2 = 5,
+ VL3 = 6
+};
+
+/* White decoding table. */
+static const cfd_node cf_white_decode[] = {
+ {256,12},{272,12},{29,8},{30,8},{45,8},{46,8},{22,7},{22,7},
+ {23,7},{23,7},{47,8},{48,8},{13,6},{13,6},{13,6},{13,6},{20,7},
+ {20,7},{33,8},{34,8},{35,8},{36,8},{37,8},{38,8},{19,7},{19,7},
+ {31,8},{32,8},{1,6},{1,6},{1,6},{1,6},{12,6},{12,6},{12,6},{12,6},
+ {53,8},{54,8},{26,7},{26,7},{39,8},{40,8},{41,8},{42,8},{43,8},
+ {44,8},{21,7},{21,7},{28,7},{28,7},{61,8},{62,8},{63,8},{0,8},
+ {320,8},{384,8},{10,5},{10,5},{10,5},{10,5},{10,5},{10,5},{10,5},
+ {10,5},{11,5},{11,5},{11,5},{11,5},{11,5},{11,5},{11,5},{11,5},
+ {27,7},{27,7},{59,8},{60,8},{288,9},{290,9},{18,7},{18,7},{24,7},
+ {24,7},{49,8},{50,8},{51,8},{52,8},{25,7},{25,7},{55,8},{56,8},
+ {57,8},{58,8},{192,6},{192,6},{192,6},{192,6},{1664,6},{1664,6},
+ {1664,6},{1664,6},{448,8},{512,8},{292,9},{640,8},{576,8},{294,9},
+ {296,9},{298,9},{300,9},{302,9},{256,7},{256,7},{2,4},{2,4},{2,4},
+ {2,4},{2,4},{2,4},{2,4},{2,4},{2,4},{2,4},{2,4},{2,4},{2,4},{2,4},
+ {2,4},{2,4},{3,4},{3,4},{3,4},{3,4},{3,4},{3,4},{3,4},{3,4},{3,4},
+ {3,4},{3,4},{3,4},{3,4},{3,4},{3,4},{3,4},{128,5},{128,5},{128,5},
+ {128,5},{128,5},{128,5},{128,5},{128,5},{8,5},{8,5},{8,5},{8,5},
+ {8,5},{8,5},{8,5},{8,5},{9,5},{9,5},{9,5},{9,5},{9,5},{9,5},{9,5},
+ {9,5},{16,6},{16,6},{16,6},{16,6},{17,6},{17,6},{17,6},{17,6},
+ {4,4},{4,4},{4,4},{4,4},{4,4},{4,4},{4,4},{4,4},{4,4},{4,4},{4,4},
+ {4,4},{4,4},{4,4},{4,4},{4,4},{5,4},{5,4},{5,4},{5,4},{5,4},{5,4},
+ {5,4},{5,4},{5,4},{5,4},{5,4},{5,4},{5,4},{5,4},{5,4},{5,4},
+ {14,6},{14,6},{14,6},{14,6},{15,6},{15,6},{15,6},{15,6},{64,5},
+ {64,5},{64,5},{64,5},{64,5},{64,5},{64,5},{64,5},{6,4},{6,4},
+ {6,4},{6,4},{6,4},{6,4},{6,4},{6,4},{6,4},{6,4},{6,4},{6,4},{6,4},
+ {6,4},{6,4},{6,4},{7,4},{7,4},{7,4},{7,4},{7,4},{7,4},{7,4},{7,4},
+ {7,4},{7,4},{7,4},{7,4},{7,4},{7,4},{7,4},{7,4},{-2,3},{-2,3},
+ {-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},
+ {-1,0},{-1,0},{-1,0},{-1,0},{-3,4},{1792,3},{1792,3},{1984,4},
+ {2048,4},{2112,4},{2176,4},{2240,4},{2304,4},{1856,3},{1856,3},
+ {1920,3},{1920,3},{2368,4},{2432,4},{2496,4},{2560,4},{1472,1},
+ {1536,1},{1600,1},{1728,1},{704,1},{768,1},{832,1},{896,1},
+ {960,1},{1024,1},{1088,1},{1152,1},{1216,1},{1280,1},{1344,1},
+ {1408,1}
+};
+
+/* Black decoding table. */
+static const cfd_node cf_black_decode[] = {
+ {128,12},{160,13},{224,12},{256,12},{10,7},{11,7},{288,12},{12,7},
+ {9,6},{9,6},{8,6},{8,6},{7,5},{7,5},{7,5},{7,5},{6,4},{6,4},{6,4},
+ {6,4},{6,4},{6,4},{6,4},{6,4},{5,4},{5,4},{5,4},{5,4},{5,4},{5,4},
+ {5,4},{5,4},{1,3},{1,3},{1,3},{1,3},{1,3},{1,3},{1,3},{1,3},{1,3},
+ {1,3},{1,3},{1,3},{1,3},{1,3},{1,3},{1,3},{4,3},{4,3},{4,3},{4,3},
+ {4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},
+ {4,3},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},
+ {3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},
+ {3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},{3,2},
+ {2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},
+ {2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},
+ {2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},{2,2},
+ {-2,4},{-2,4},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},
+ {-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-3,5},{1792,4},
+ {1792,4},{1984,5},{2048,5},{2112,5},{2176,5},{2240,5},{2304,5},
+ {1856,4},{1856,4},{1920,4},{1920,4},{2368,5},{2432,5},{2496,5},
+ {2560,5},{18,3},{18,3},{18,3},{18,3},{18,3},{18,3},{18,3},{18,3},
+ {52,5},{52,5},{640,6},{704,6},{768,6},{832,6},{55,5},{55,5},
+ {56,5},{56,5},{1280,6},{1344,6},{1408,6},{1472,6},{59,5},{59,5},
+ {60,5},{60,5},{1536,6},{1600,6},{24,4},{24,4},{24,4},{24,4},
+ {25,4},{25,4},{25,4},{25,4},{1664,6},{1728,6},{320,5},{320,5},
+ {384,5},{384,5},{448,5},{448,5},{512,6},{576,6},{53,5},{53,5},
+ {54,5},{54,5},{896,6},{960,6},{1024,6},{1088,6},{1152,6},{1216,6},
+ {64,3},{64,3},{64,3},{64,3},{64,3},{64,3},{64,3},{64,3},{13,1},
+ {13,1},{13,1},{13,1},{13,1},{13,1},{13,1},{13,1},{13,1},{13,1},
+ {13,1},{13,1},{13,1},{13,1},{13,1},{13,1},{23,4},{23,4},{50,5},
+ {51,5},{44,5},{45,5},{46,5},{47,5},{57,5},{58,5},{61,5},{256,5},
+ {16,3},{16,3},{16,3},{16,3},{17,3},{17,3},{17,3},{17,3},{48,5},
+ {49,5},{62,5},{63,5},{30,5},{31,5},{32,5},{33,5},{40,5},{41,5},
+ {22,4},{22,4},{14,1},{14,1},{14,1},{14,1},{14,1},{14,1},{14,1},
+ {14,1},{14,1},{14,1},{14,1},{14,1},{14,1},{14,1},{14,1},{14,1},
+ {15,2},{15,2},{15,2},{15,2},{15,2},{15,2},{15,2},{15,2},{128,5},
+ {192,5},{26,5},{27,5},{28,5},{29,5},{19,4},{19,4},{20,4},{20,4},
+ {34,5},{35,5},{36,5},{37,5},{38,5},{39,5},{21,4},{21,4},{42,5},
+ {43,5},{0,3},{0,3},{0,3},{0,3}
+};
+
+/* 2-D decoding table. */
+static const cfd_node cf_2d_decode[] = {
+ {128,11},{144,10},{6,7},{0,7},{5,6},{5,6},{1,6},{1,6},{-4,4},
+ {-4,4},{-4,4},{-4,4},{-4,4},{-4,4},{-4,4},{-4,4},{-5,3},{-5,3},
+ {-5,3},{-5,3},{-5,3},{-5,3},{-5,3},{-5,3},{-5,3},{-5,3},{-5,3},
+ {-5,3},{-5,3},{-5,3},{-5,3},{-5,3},{4,3},{4,3},{4,3},{4,3},{4,3},
+ {4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},{4,3},
+ {2,3},{2,3},{2,3},{2,3},{2,3},{2,3},{2,3},{2,3},{2,3},{2,3},{2,3},
+ {2,3},{2,3},{2,3},{2,3},{2,3},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},
+ {3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},
+ {3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},
+ {3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},
+ {3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},
+ {3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},{3,1},
+ {3,1},{3,1},{3,1},{-2,4},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},
+ {-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},
+ {-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-1,0},{-3,3}
+};
+
+/* Uncompressed decoding table. */
+static const cfd_node cf_uncompressed_decode[] = {
+ {64,12},{5,6},{4,5},{4,5},{3,4},{3,4},{3,4},{3,4},{2,3},{2,3},
+ {2,3},{2,3},{2,3},{2,3},{2,3},{2,3},{1,2},{1,2},{1,2},{1,2},{1,2},
+ {1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},
+ {0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},
+ {0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},
+ {0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},{0,1},
+ {-1,0},{-1,0},{8,6},{9,6},{6,5},{6,5},{7,5},{7,5},{4,4},{4,4},
+ {4,4},{4,4},{5,4},{5,4},{5,4},{5,4},{2,3},{2,3},{2,3},{2,3},{2,3},
+ {2,3},{2,3},{2,3},{3,3},{3,3},{3,3},{3,3},{3,3},{3,3},{3,3},{3,3},
+ {0,2},{0,2},{0,2},{0,2},{0,2},{0,2},{0,2},{0,2},{0,2},{0,2},{0,2},
+ {0,2},{0,2},{0,2},{0,2},{0,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},
+ {1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2},{1,2}
+};
+
+/* bit magic */
+
+static inline int getbit(const unsigned char *buf, int x)
+{
+ return ( buf[x >> 3] >> ( 7 - (x & 7) ) ) & 1;
+}
+
+static const unsigned char mask[8] = {
+ 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0
+};
+
+static const unsigned char clz[256] = {
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static inline int
+find_changing(const unsigned char *line, int x, int w)
+{
+ int a, b, m, W;
+
+ if (!line)
+ return w;
+
+ /* We assume w > 0, -1 <= x < w */
+ if (x < 0)
+ {
+ x = 0;
+ m = 0xFF;
+ }
+ else
+ {
+ /* Mask out the bits we've already used (including the one
+ * we started from) */
+ m = mask[x & 7];
+ }
+ /* We have 'w' pixels (bits) in line. The last pixel that can be
+ * safely accessed is the (w-1)th bit of line.
+ * By taking W = w>>3, we know that the first W bytes of line are
+ * full, with w&7 stray bits following. */
+ W = w>>3;
+ x >>= 3;
+ a = line[x]; /* Safe as x < w => x <= w-1 => x>>3 <= (w-1)>>3 */
+ b = a ^ (a>>1);
+ b &= m;
+ if (x >= W)
+ {
+ /* Within the last byte already */
+ x = (x<<3) + clz[b];
+ if (x > w)
+ x = w;
+ return x;
+ }
+ while (b == 0)
+ {
+ if (++x >= W)
+ goto nearend;
+ b = a & 1;
+ a = line[x];
+ b = (b<<7) ^ a ^ (a>>1);
+ }
+ return (x<<3) + clz[b];
+nearend:
+ /* We have less than a byte to go. If no stray bits, exit now. */
+ if ((x<<3) == w)
+ return w;
+ b = a&1;
+ a = line[x];
+ b = (b<<7) ^ a ^ (a>>1);
+ x = (x<<3) + clz[b];
+ if (x > w)
+ x = w;
+ return x;
+}
+
+static inline int
+find_changing_color(const unsigned char *line, int x, int w, int color)
+{
+ if (!line || x >= w)
+ return w;
+
+ x = find_changing(line, (x > 0 || !color) ? x : -1, w);
+
+ if (x < w && getbit(line, x) != color)
+ x = find_changing(line, x, w);
+
+ return x;
+}
+
+static const unsigned char lm[8] = {
+ 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01
+};
+
+static const unsigned char rm[8] = {
+ 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE
+};
+
+static inline void setbits(unsigned char *line, int x0, int x1)
+{
+ int a0, a1, b0, b1, a;
+
+ if (x1 <= x0)
+ return;
+
+ a0 = x0 >> 3;
+ a1 = x1 >> 3;
+
+ b0 = x0 & 7;
+ b1 = x1 & 7;
+
+ if (a0 == a1)
+ {
+ if (b1)
+ line[a0] |= lm[b0] & rm[b1];
+ }
+ else
+ {
+ line[a0] |= lm[b0];
+ for (a = a0 + 1; a < a1; a++)
+ line[a] = 0xFF;
+ if (b1)
+ line[a1] |= rm[b1];
+ }
+}
+
+typedef struct fz_faxd_s fz_faxd;
+
+enum
+{
+ STATE_NORMAL, /* neutral state, waiting for any code */
+ STATE_MAKEUP, /* got a 1d makeup code, waiting for terminating code */
+ STATE_EOL, /* at eol, needs output buffer space */
+ STATE_H1, STATE_H2, /* in H part 1 and 2 (both makeup and terminating codes) */
+ STATE_DONE /* all done */
+};
+
+struct fz_faxd_s
+{
+ fz_context *ctx;
+ fz_stream *chain;
+
+ int k;
+ int end_of_line;
+ int encoded_byte_align;
+ int columns;
+ int rows;
+ int end_of_block;
+ int black_is_1;
+
+ int stride;
+ int ridx;
+
+ int bidx;
+ unsigned int word;
+
+ int stage;
+
+ int a, c, dim, eolc;
+ unsigned char *ref;
+ unsigned char *dst;
+ unsigned char *rp, *wp;
+};
+
+static inline void eat_bits(fz_faxd *fax, int nbits)
+{
+ fax->word <<= nbits;
+ fax->bidx += nbits;
+}
+
+static int
+fill_bits(fz_faxd *fax)
+{
+ while (fax->bidx >= 8)
+ {
+ int c = fz_read_byte(fax->chain);
+ if (c == EOF)
+ return EOF;
+ fax->bidx -= 8;
+ fax->word |= c << fax->bidx;
+ }
+ return 0;
+}
+
+static int
+get_code(fz_faxd *fax, const cfd_node *table, int initialbits)
+{
+ unsigned int word = fax->word;
+ int tidx = word >> (32 - initialbits);
+ int val = table[tidx].val;
+ int nbits = table[tidx].nbits;
+
+ if (nbits > initialbits)
+ {
+ int mask = (1 << (32 - initialbits)) - 1;
+ tidx = val + ((word & mask) >> (32 - nbits));
+ val = table[tidx].val;
+ nbits = initialbits + table[tidx].nbits;
+ }
+
+ eat_bits(fax, nbits);
+
+ return val;
+}
+
+/* decode one 1d code */
+static void
+dec1d(fz_context *ctx, fz_faxd *fax)
+{
+ int code;
+
+ if (fax->a == -1)
+ fax->a = 0;
+
+ if (fax->c)
+ code = get_code(fax, cf_black_decode, cfd_black_initial_bits);
+ else
+ code = get_code(fax, cf_white_decode, cfd_white_initial_bits);
+
+ if (code == UNCOMPRESSED)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "uncompressed data in faxd");
+
+ if (code < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "negative code in 1d faxd");
+
+ if (fax->a + code > fax->columns)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "overflow in 1d faxd");
+
+ if (fax->c)
+ setbits(fax->dst, fax->a, fax->a + code);
+
+ fax->a += code;
+
+ if (code < 64)
+ {
+ fax->c = !fax->c;
+ fax->stage = STATE_NORMAL;
+ }
+ else
+ fax->stage = STATE_MAKEUP;
+}
+
+/* decode one 2d code */
+static void
+dec2d(fz_context *ctx, fz_faxd *fax)
+{
+ int code, b1, b2;
+
+ if (fax->stage == STATE_H1 || fax->stage == STATE_H2)
+ {
+ if (fax->a == -1)
+ fax->a = 0;
+
+ if (fax->c)
+ code = get_code(fax, cf_black_decode, cfd_black_initial_bits);
+ else
+ code = get_code(fax, cf_white_decode, cfd_white_initial_bits);
+
+ if (code == UNCOMPRESSED)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "uncompressed data in faxd");
+
+ if (code < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "negative code in 2d faxd");
+
+ if (fax->a + code > fax->columns)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "overflow in 2d faxd");
+
+ if (fax->c)
+ setbits(fax->dst, fax->a, fax->a + code);
+
+ fax->a += code;
+
+ if (code < 64)
+ {
+ fax->c = !fax->c;
+ if (fax->stage == STATE_H1)
+ fax->stage = STATE_H2;
+ else if (fax->stage == STATE_H2)
+ fax->stage = STATE_NORMAL;
+ }
+
+ return;
+ }
+
+ code = get_code(fax, cf_2d_decode, cfd_2d_initial_bits);
+
+ switch (code)
+ {
+ case H:
+ fax->stage = STATE_H1;
+ break;
+
+ case P:
+ b1 = find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 >= fax->columns)
+ b2 = fax->columns;
+ else
+ b2 = find_changing(fax->ref, b1, fax->columns);
+ if (fax->c) setbits(fax->dst, fax->a, b2);
+ fax->a = b2;
+ break;
+
+ case V0:
+ b1 = find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case VR1:
+ b1 = 1 + find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 >= fax->columns) b1 = fax->columns;
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case VR2:
+ b1 = 2 + find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 >= fax->columns) b1 = fax->columns;
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case VR3:
+ b1 = 3 + find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 >= fax->columns) b1 = fax->columns;
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case VL1:
+ b1 = -1 + find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 < 0) b1 = 0;
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case VL2:
+ b1 = -2 + find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 < 0) b1 = 0;
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case VL3:
+ b1 = -3 + find_changing_color(fax->ref, fax->a, fax->columns, !fax->c);
+ if (b1 < 0) b1 = 0;
+ if (fax->c) setbits(fax->dst, fax->a, b1);
+ fax->a = b1;
+ fax->c = !fax->c;
+ break;
+
+ case UNCOMPRESSED:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "uncompressed data in faxd");
+
+ case ERROR:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid code in 2d faxd");
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid code in 2d faxd (%d)", code);
+ }
+}
+
+static int
+read_faxd(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_faxd *fax = stm->state;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+ unsigned char *tmp;
+
+ if (fax->stage == STATE_DONE)
+ return 0;
+
+ if (fax->stage == STATE_EOL)
+ goto eol;
+
+loop:
+
+ if (fill_bits(fax))
+ {
+ if (fax->bidx > 31)
+ {
+ if (fax->a > 0)
+ goto eol;
+ goto rtc;
+ }
+ }
+
+ if ((fax->word >> (32 - 12)) == 0)
+ {
+ eat_bits(fax, 1);
+ goto loop;
+ }
+
+ if ((fax->word >> (32 - 12)) == 1)
+ {
+ eat_bits(fax, 12);
+ fax->eolc ++;
+
+ if (fax->k > 0)
+ {
+ if (fax->a == -1)
+ fax->a = 0;
+ if ((fax->word >> (32 - 1)) == 1)
+ fax->dim = 1;
+ else
+ fax->dim = 2;
+ eat_bits(fax, 1);
+ }
+ }
+ else if (fax->k > 0 && fax->a == -1)
+ {
+ fax->a = 0;
+ if ((fax->word >> (32 - 1)) == 1)
+ fax->dim = 1;
+ else
+ fax->dim = 2;
+ eat_bits(fax, 1);
+ }
+ else if (fax->dim == 1)
+ {
+ fax->eolc = 0;
+ dec1d(stm->ctx, fax);
+ }
+ else if (fax->dim == 2)
+ {
+ fax->eolc = 0;
+ dec2d(stm->ctx, fax);
+ }
+
+ /* no eol check after makeup codes nor in the middle of an H code */
+ if (fax->stage == STATE_MAKEUP || fax->stage == STATE_H1 || fax->stage == STATE_H2)
+ goto loop;
+
+ /* check for eol conditions */
+ if (fax->eolc || fax->a >= fax->columns)
+ {
+ if (fax->a > 0)
+ goto eol;
+ if (fax->eolc == (fax->k < 0 ? 2 : 6))
+ goto rtc;
+ }
+
+ goto loop;
+
+eol:
+ fax->stage = STATE_EOL;
+
+ if (fax->black_is_1)
+ {
+ while (fax->rp < fax->wp && p < ep)
+ *p++ = *fax->rp++;
+ }
+ else
+ {
+ while (fax->rp < fax->wp && p < ep)
+ *p++ = *fax->rp++ ^ 0xff;
+ }
+
+ if (fax->rp < fax->wp)
+ return p - buf;
+
+ tmp = fax->ref;
+ fax->ref = fax->dst;
+ fax->dst = tmp;
+ memset(fax->dst, 0, fax->stride);
+
+ fax->rp = fax->dst;
+ fax->wp = fax->dst + fax->stride;
+
+ fax->stage = STATE_NORMAL;
+ fax->c = 0;
+ fax->a = -1;
+ fax->ridx ++;
+
+ if (!fax->end_of_block && fax->rows)
+ {
+ if (fax->ridx >= fax->rows)
+ goto rtc;
+ }
+
+ /* we have not read dim from eol, make a guess */
+ if (fax->k > 0 && !fax->eolc && fax->a == -1)
+ {
+ if (fax->ridx % fax->k == 0)
+ fax->dim = 1;
+ else
+ fax->dim = 2;
+ }
+
+ /* if end_of_line & encoded_byte_align, EOLs are *not* optional */
+ if (fax->encoded_byte_align)
+ {
+ if (fax->end_of_line)
+ eat_bits(fax, (12 - fax->bidx) & 7);
+ else
+ eat_bits(fax, (8 - fax->bidx) & 7);
+ }
+
+ /* no more space in output, don't decode the next row yet */
+ if (p == buf + len)
+ return p - buf;
+
+ goto loop;
+
+rtc:
+ fax->stage = STATE_DONE;
+ return p - buf;
+}
+
+static void
+close_faxd(fz_context *ctx, void *state_)
+{
+ fz_faxd *fax = (fz_faxd *)state_;
+ int i;
+
+ /* if we read any extra bytes, try to put them back */
+ i = (32 - fax->bidx) / 8;
+ while (i--)
+ fz_unread_byte(fax->chain);
+
+ fz_close(fax->chain);
+ fz_free(ctx, fax->ref);
+ fz_free(ctx, fax->dst);
+ fz_free(ctx, fax);
+}
+
+/* Default: columns = 1728, end_of_block = 1, the rest = 0 */
+fz_stream *
+fz_open_faxd(fz_stream *chain,
+ int k, int end_of_line, int encoded_byte_align,
+ int columns, int rows, int end_of_block, int black_is_1)
+{
+ fz_context *ctx = chain->ctx;
+ fz_faxd *fax = NULL;
+
+ fz_var(fax);
+
+ fz_try(ctx)
+ {
+ fax = fz_malloc_struct(ctx, fz_faxd);
+ fax->chain = chain;
+
+ fax->ref = NULL;
+ fax->dst = NULL;
+
+ fax->k = k;
+ fax->end_of_line = end_of_line;
+ fax->encoded_byte_align = encoded_byte_align;
+ fax->columns = columns;
+ fax->rows = rows;
+ fax->end_of_block = end_of_block;
+ fax->black_is_1 = black_is_1;
+
+ fax->stride = ((fax->columns - 1) >> 3) + 1;
+ fax->ridx = 0;
+ fax->bidx = 32;
+ fax->word = 0;
+
+ fax->stage = STATE_NORMAL;
+ fax->a = -1;
+ fax->c = 0;
+ fax->dim = fax->k < 0 ? 2 : 1;
+ fax->eolc = 0;
+
+ fax->ref = fz_malloc(ctx, fax->stride);
+ fax->dst = fz_malloc(ctx, fax->stride);
+ fax->rp = fax->dst;
+ fax->wp = fax->dst + fax->stride;
+
+ memset(fax->ref, 0, fax->stride);
+ memset(fax->dst, 0, fax->stride);
+ }
+ fz_catch(ctx)
+ {
+ if (fax)
+ {
+ fz_free(ctx, fax->dst);
+ fz_free(ctx, fax->ref);
+ }
+ fz_free(ctx, fax);
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, fax, read_faxd, close_faxd);
+}
diff --git a/source/fitz/filter-flate.c b/source/fitz/filter-flate.c
new file mode 100644
index 00000000..73451d59
--- /dev/null
+++ b/source/fitz/filter-flate.c
@@ -0,0 +1,117 @@
+#include "mupdf/fitz.h"
+
+#include <zlib.h>
+
+typedef struct fz_flate_s fz_flate;
+
+struct fz_flate_s
+{
+ fz_stream *chain;
+ z_stream z;
+};
+
+static void *zalloc(void *opaque, unsigned int items, unsigned int size)
+{
+ return fz_malloc_array_no_throw(opaque, items, size);
+}
+
+static void zfree(void *opaque, void *ptr)
+{
+ fz_free(opaque, ptr);
+}
+
+static int
+read_flated(fz_stream *stm, unsigned char *outbuf, int outlen)
+{
+ fz_flate *state = stm->state;
+ fz_stream *chain = state->chain;
+ z_streamp zp = &state->z;
+ int code;
+
+ zp->next_out = outbuf;
+ zp->avail_out = outlen;
+
+ while (zp->avail_out > 0)
+ {
+ if (chain->rp == chain->wp)
+ fz_fill_buffer(chain);
+
+ zp->next_in = chain->rp;
+ zp->avail_in = chain->wp - chain->rp;
+
+ code = inflate(zp, Z_SYNC_FLUSH);
+
+ chain->rp = chain->wp - zp->avail_in;
+
+ if (code == Z_STREAM_END)
+ {
+ return outlen - zp->avail_out;
+ }
+ else if (code == Z_BUF_ERROR)
+ {
+ fz_warn(stm->ctx, "premature end of data in flate filter");
+ return outlen - zp->avail_out;
+ }
+ else if (code == Z_DATA_ERROR && zp->avail_in == 0)
+ {
+ fz_warn(stm->ctx, "ignoring zlib error: %s", zp->msg);
+ return outlen - zp->avail_out;
+ }
+ else if (code != Z_OK)
+ {
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "zlib error: %s", zp->msg);
+ }
+ }
+
+ return outlen - zp->avail_out;
+}
+
+static void
+close_flated(fz_context *ctx, void *state_)
+{
+ fz_flate *state = (fz_flate *)state_;
+ int code;
+
+ code = inflateEnd(&state->z);
+ if (code != Z_OK)
+ fz_warn(ctx, "zlib error: inflateEnd: %s", state->z.msg);
+
+ fz_close(state->chain);
+ fz_free(ctx, state);
+}
+
+fz_stream *
+fz_open_flated(fz_stream *chain)
+{
+ fz_flate *state = NULL;
+ int code = Z_OK;
+ fz_context *ctx = chain->ctx;
+
+ fz_var(code);
+ fz_var(state);
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_flate);
+ state->chain = chain;
+
+ state->z.zalloc = zalloc;
+ state->z.zfree = zfree;
+ state->z.opaque = ctx;
+ state->z.next_in = NULL;
+ state->z.avail_in = 0;
+
+ code = inflateInit(&state->z);
+ if (code != Z_OK)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib error: inflateInit: %s", state->z.msg);
+ }
+ fz_catch(ctx)
+ {
+ if (state && code == Z_OK)
+ inflateEnd(&state->z);
+ fz_free(ctx, state);
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+ return fz_new_stream(ctx, state, read_flated, close_flated);
+}
diff --git a/source/fitz/filter-jbig2.c b/source/fitz/filter-jbig2.c
new file mode 100644
index 00000000..12eb8c3b
--- /dev/null
+++ b/source/fitz/filter-jbig2.c
@@ -0,0 +1,108 @@
+#include "mupdf/fitz.h"
+
+#include <jbig2.h>
+
+typedef struct fz_jbig2d_s fz_jbig2d;
+
+struct fz_jbig2d_s
+{
+ fz_stream *chain;
+ Jbig2Ctx *ctx;
+ Jbig2GlobalCtx *gctx;
+ Jbig2Image *page;
+ int idx;
+};
+
+static void
+close_jbig2d(fz_context *ctx, void *state_)
+{
+ fz_jbig2d *state = (fz_jbig2d *)state_;
+ if (state->page)
+ jbig2_release_page(state->ctx, state->page);
+ if (state->gctx)
+ jbig2_global_ctx_free(state->gctx);
+ jbig2_ctx_free(state->ctx);
+ fz_close(state->chain);
+ fz_free(ctx, state);
+}
+
+static int
+read_jbig2d(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_jbig2d *state = stm->state;
+ unsigned char tmp[4096];
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+ unsigned char *s;
+ int x, w, n;
+
+ if (!state->page)
+ {
+ while (1)
+ {
+ n = fz_read(state->chain, tmp, sizeof tmp);
+ if (n == 0)
+ break;
+ jbig2_data_in(state->ctx, tmp, n);
+ }
+
+ jbig2_complete_page(state->ctx);
+
+ state->page = jbig2_page_out(state->ctx);
+ if (!state->page)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "jbig2_page_out failed");
+ }
+
+ s = state->page->data;
+ w = state->page->height * state->page->stride;
+ x = state->idx;
+ while (p < ep && x < w)
+ *p++ = s[x++] ^ 0xff;
+ state->idx = x;
+
+ return p - buf;
+}
+
+fz_stream *
+fz_open_jbig2d(fz_stream *chain, fz_buffer *globals)
+{
+ fz_jbig2d *state = NULL;
+ fz_context *ctx = chain->ctx;
+
+ fz_var(state);
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(chain->ctx, fz_jbig2d);
+ state->ctx = NULL;
+ state->gctx = NULL;
+ state->chain = chain;
+ state->ctx = jbig2_ctx_new(NULL, JBIG2_OPTIONS_EMBEDDED, NULL, NULL, NULL);
+ state->page = NULL;
+ state->idx = 0;
+
+ if (globals)
+ {
+ jbig2_data_in(state->ctx, globals->data, globals->len);
+ state->gctx = jbig2_make_global_ctx(state->ctx);
+ state->ctx = jbig2_ctx_new(NULL, JBIG2_OPTIONS_EMBEDDED, state->gctx, NULL, NULL);
+ }
+ }
+ fz_catch(ctx)
+ {
+ if (state)
+ {
+ if (state->gctx)
+ jbig2_global_ctx_free(state->gctx);
+ if (state->ctx)
+ jbig2_ctx_free(state->ctx);
+ }
+ fz_drop_buffer(ctx, globals);
+ fz_free(ctx, state);
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+ fz_drop_buffer(ctx, globals);
+
+ return fz_new_stream(ctx, state, read_jbig2d, close_jbig2d);
+}
diff --git a/source/fitz/filter-lzw.c b/source/fitz/filter-lzw.c
new file mode 100644
index 00000000..73909ca1
--- /dev/null
+++ b/source/fitz/filter-lzw.c
@@ -0,0 +1,224 @@
+#include "mupdf/fitz.h"
+
+/* TODO: error checking */
+
+enum
+{
+ MIN_BITS = 9,
+ MAX_BITS = 12,
+ NUM_CODES = (1 << MAX_BITS),
+ LZW_CLEAR = 256,
+ LZW_EOD = 257,
+ LZW_FIRST = 258,
+ MAX_LENGTH = 4097
+};
+
+typedef struct lzw_code_s lzw_code;
+
+struct lzw_code_s
+{
+ int prev; /* prev code (in string) */
+ unsigned short length; /* string len, including this token */
+ unsigned char value; /* data value */
+ unsigned char first_char; /* first token of string */
+};
+
+typedef struct fz_lzwd_s fz_lzwd;
+
+struct fz_lzwd_s
+{
+ fz_stream *chain;
+ int eod;
+
+ int early_change;
+
+ int code_bits; /* num bits/code */
+ int code; /* current code */
+ int old_code; /* previously recognized code */
+ int next_code; /* next free entry */
+
+ lzw_code table[NUM_CODES];
+
+ unsigned char bp[MAX_LENGTH];
+ unsigned char *rp, *wp;
+};
+
+static int
+read_lzwd(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_lzwd *lzw = stm->state;
+ lzw_code *table = lzw->table;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+ unsigned char *s;
+ int codelen;
+
+ int code_bits = lzw->code_bits;
+ int code = lzw->code;
+ int old_code = lzw->old_code;
+ int next_code = lzw->next_code;
+
+ while (lzw->rp < lzw->wp && p < ep)
+ *p++ = *lzw->rp++;
+
+ while (p < ep)
+ {
+ if (lzw->eod)
+ return 0;
+
+ code = fz_read_bits(lzw->chain, code_bits);
+
+ if (fz_is_eof_bits(lzw->chain))
+ {
+ lzw->eod = 1;
+ break;
+ }
+
+ if (code == LZW_EOD)
+ {
+ lzw->eod = 1;
+ break;
+ }
+
+ if (next_code >= NUM_CODES && code != LZW_CLEAR)
+ {
+ fz_warn(stm->ctx, "missing clear code in lzw decode");
+ code = LZW_CLEAR;
+ }
+
+ if (code == LZW_CLEAR)
+ {
+ code_bits = MIN_BITS;
+ next_code = LZW_FIRST;
+ old_code = -1;
+ continue;
+ }
+
+ /* if stream starts without a clear code, old_code is undefined... */
+ if (old_code == -1)
+ {
+ old_code = code;
+ }
+ else if (code > next_code || next_code >= NUM_CODES)
+ {
+ fz_warn(stm->ctx, "out of range code encountered in lzw decode");
+ }
+ else
+ {
+ /* add new entry to the code table */
+ table[next_code].prev = old_code;
+ table[next_code].first_char = table[old_code].first_char;
+ table[next_code].length = table[old_code].length + 1;
+ if (code < next_code)
+ table[next_code].value = table[code].first_char;
+ else if (code == next_code)
+ table[next_code].value = table[next_code].first_char;
+ else
+ fz_warn(stm->ctx, "out of range code encountered in lzw decode");
+
+ next_code ++;
+
+ if (next_code > (1 << code_bits) - lzw->early_change - 1)
+ {
+ code_bits ++;
+ if (code_bits > MAX_BITS)
+ code_bits = MAX_BITS;
+ }
+
+ old_code = code;
+ }
+
+ /* code maps to a string, copy to output (in reverse...) */
+ if (code > 255)
+ {
+ codelen = table[code].length;
+ lzw->rp = lzw->bp;
+ lzw->wp = lzw->bp + codelen;
+
+ assert(codelen < MAX_LENGTH);
+
+ s = lzw->wp;
+ do {
+ *(--s) = table[code].value;
+ code = table[code].prev;
+ } while (code >= 0 && s > lzw->bp);
+ }
+
+ /* ... or just a single character */
+ else
+ {
+ lzw->bp[0] = code;
+ lzw->rp = lzw->bp;
+ lzw->wp = lzw->bp + 1;
+ }
+
+ /* copy to output */
+ while (lzw->rp < lzw->wp && p < ep)
+ *p++ = *lzw->rp++;
+ }
+
+ lzw->code_bits = code_bits;
+ lzw->code = code;
+ lzw->old_code = old_code;
+ lzw->next_code = next_code;
+
+ return p - buf;
+}
+
+static void
+close_lzwd(fz_context *ctx, void *state_)
+{
+ fz_lzwd *lzw = (fz_lzwd *)state_;
+ fz_close(lzw->chain);
+ fz_free(ctx, lzw);
+}
+
+/* Default: early_change = 1 */
+fz_stream *
+fz_open_lzwd(fz_stream *chain, int early_change)
+{
+ fz_context *ctx = chain->ctx;
+ fz_lzwd *lzw = NULL;
+ int i;
+
+ fz_var(lzw);
+
+ fz_try(ctx)
+ {
+ lzw = fz_malloc_struct(ctx, fz_lzwd);
+ lzw->chain = chain;
+ lzw->eod = 0;
+ lzw->early_change = early_change;
+
+ for (i = 0; i < 256; i++)
+ {
+ lzw->table[i].value = i;
+ lzw->table[i].first_char = i;
+ lzw->table[i].length = 1;
+ lzw->table[i].prev = -1;
+ }
+
+ for (i = 256; i < NUM_CODES; i++)
+ {
+ lzw->table[i].value = 0;
+ lzw->table[i].first_char = 0;
+ lzw->table[i].length = 0;
+ lzw->table[i].prev = -1;
+ }
+
+ lzw->code_bits = MIN_BITS;
+ lzw->code = -1;
+ lzw->next_code = LZW_FIRST;
+ lzw->old_code = -1;
+ lzw->rp = lzw->bp;
+ lzw->wp = lzw->bp;
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, lzw);
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, lzw, read_lzwd, close_lzwd);
+}
diff --git a/source/fitz/filter-predict.c b/source/fitz/filter-predict.c
new file mode 100644
index 00000000..c8de290a
--- /dev/null
+++ b/source/fitz/filter-predict.c
@@ -0,0 +1,256 @@
+#include "mupdf/fitz.h"
+
+/* TODO: check if this works with 16bpp images */
+
+enum { MAXC = 32 };
+
+typedef struct fz_predict_s fz_predict;
+
+struct fz_predict_s
+{
+ fz_stream *chain;
+
+ int predictor;
+ int columns;
+ int colors;
+ int bpc;
+
+ int stride;
+ int bpp;
+ unsigned char *in;
+ unsigned char *out;
+ unsigned char *ref;
+ unsigned char *rp, *wp;
+};
+
+static inline int getcomponent(unsigned char *line, int x, int bpc)
+{
+ switch (bpc)
+ {
+ case 1: return (line[x >> 3] >> ( 7 - (x & 7) ) ) & 1;
+ case 2: return (line[x >> 2] >> ( ( 3 - (x & 3) ) << 1 ) ) & 3;
+ case 4: return (line[x >> 1] >> ( ( 1 - (x & 1) ) << 2 ) ) & 15;
+ case 8: return line[x];
+ case 16: return (line[x<<1]<<8)+line[(x<<1)+1];
+ }
+ return 0;
+}
+
+static inline void putcomponent(unsigned char *buf, int x, int bpc, int value)
+{
+ switch (bpc)
+ {
+ case 1: buf[x >> 3] |= value << (7 - (x & 7)); break;
+ case 2: buf[x >> 2] |= value << ((3 - (x & 3)) << 1); break;
+ case 4: buf[x >> 1] |= value << ((1 - (x & 1)) << 2); break;
+ case 8: buf[x] = value; break;
+ case 16: buf[x<<1] = value>>8; buf[(x<<1)+1] = value; break;
+ }
+}
+
+static inline int paeth(int a, int b, int c)
+{
+ /* The definitions of ac and bc are correct, not a typo. */
+ int ac = b - c, bc = a - c, abcc = ac + bc;
+ int pa = fz_absi(ac);
+ int pb = fz_absi(bc);
+ int pc = fz_absi(abcc);
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
+}
+
+static void
+fz_predict_tiff(fz_predict *state, unsigned char *out, unsigned char *in, int len)
+{
+ int left[MAXC];
+ int i, k;
+ const int mask = (1 << state->bpc)-1;
+
+ for (k = 0; k < state->colors; k++)
+ left[k] = 0;
+ memset(out, 0, state->stride);
+
+ for (i = 0; i < state->columns; i++)
+ {
+ for (k = 0; k < state->colors; k++)
+ {
+ int a = getcomponent(in, i * state->colors + k, state->bpc);
+ int b = a + left[k];
+ int c = b & mask;
+ putcomponent(out, i * state->colors + k, state->bpc, c);
+ left[k] = c;
+ }
+ }
+}
+
+static void
+fz_predict_png(fz_predict *state, unsigned char *out, unsigned char *in, int len, int predictor)
+{
+ int bpp = state->bpp;
+ int i;
+ unsigned char *ref = state->ref;
+
+ switch (predictor)
+ {
+ case 0:
+ memcpy(out, in, len);
+ break;
+ case 1:
+ for (i = bpp; i > 0; i--)
+ {
+ *out++ = *in++;
+ }
+ for (i = len - bpp; i > 0; i--)
+ {
+ *out = *in++ + out[-bpp];
+ out++;
+ }
+ break;
+ case 2:
+ for (i = bpp; i > 0; i--)
+ {
+ *out++ = *in++ + *ref++;
+ }
+ for (i = len - bpp; i > 0; i--)
+ {
+ *out++ = *in++ + *ref++;
+ }
+ break;
+ case 3:
+ for (i = bpp; i > 0; i--)
+ {
+ *out++ = *in++ + (*ref++) / 2;
+ }
+ for (i = len - bpp; i > 0; i--)
+ {
+ *out = *in++ + (out[-bpp] + *ref++) / 2;
+ out++;
+ }
+ break;
+ case 4:
+ for (i = bpp; i > 0; i--)
+ {
+ *out++ = *in++ + paeth(0, *ref++, 0);
+ }
+ for (i = len - bpp; i > 0; i --)
+ {
+ *out = *in++ + paeth(out[-bpp], *ref, ref[-bpp]);
+ ref++;
+ out++;
+ }
+ break;
+ }
+}
+
+static int
+read_predict(fz_stream *stm, unsigned char *buf, int len)
+{
+ fz_predict *state = stm->state;
+ unsigned char *p = buf;
+ unsigned char *ep = buf + len;
+ int ispng = state->predictor >= 10;
+ int n;
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+
+ while (p < ep)
+ {
+ n = fz_read(state->chain, state->in, state->stride + ispng);
+ if (n == 0)
+ return p - buf;
+
+ if (state->predictor == 1)
+ memcpy(state->out, state->in, n);
+ else if (state->predictor == 2)
+ fz_predict_tiff(state, state->out, state->in, n);
+ else
+ {
+ fz_predict_png(state, state->out, state->in + 1, n - 1, state->in[0]);
+ memcpy(state->ref, state->out, state->stride);
+ }
+
+ state->rp = state->out;
+ state->wp = state->out + n - ispng;
+
+ while (state->rp < state->wp && p < ep)
+ *p++ = *state->rp++;
+ }
+
+ return p - buf;
+}
+
+static void
+close_predict(fz_context *ctx, void *state_)
+{
+ fz_predict *state = (fz_predict *)state_;
+ fz_close(state->chain);
+ fz_free(ctx, state->in);
+ fz_free(ctx, state->out);
+ fz_free(ctx, state->ref);
+ fz_free(ctx, state);
+}
+
+/* Default values: predictor = 1, columns = 1, colors = 1, bpc = 8 */
+fz_stream *
+fz_open_predict(fz_stream *chain, int predictor, int columns, int colors, int bpc)
+{
+ fz_context *ctx = chain->ctx;
+ fz_predict *state = NULL;
+
+ fz_var(state);
+
+ if (predictor < 1)
+ predictor = 1;
+ if (columns < 1)
+ columns = 1;
+ if (colors < 1)
+ colors = 1;
+ if (bpc < 1)
+ bpc = 8;
+
+ fz_try(ctx)
+ {
+ state = fz_malloc_struct(ctx, fz_predict);
+ state->in = NULL;
+ state->out = NULL;
+ state->chain = chain;
+
+ state->predictor = predictor;
+ state->columns = columns;
+ state->colors = colors;
+ state->bpc = bpc;
+
+ if (state->predictor != 1 && state->predictor != 2 &&
+ state->predictor != 10 && state->predictor != 11 &&
+ state->predictor != 12 && state->predictor != 13 &&
+ state->predictor != 14 && state->predictor != 15)
+ {
+ fz_warn(ctx, "invalid predictor: %d", state->predictor);
+ state->predictor = 1;
+ }
+
+ state->stride = (state->bpc * state->colors * state->columns + 7) / 8;
+ state->bpp = (state->bpc * state->colors + 7) / 8;
+
+ state->in = fz_malloc(ctx, state->stride + 1);
+ state->out = fz_malloc(ctx, state->stride);
+ state->ref = fz_malloc(ctx, state->stride);
+ state->rp = state->out;
+ state->wp = state->out;
+
+ memset(state->ref, 0, state->stride);
+ }
+ fz_catch(ctx)
+ {
+ if (state)
+ {
+ fz_free(ctx, state->in);
+ fz_free(ctx, state->out);
+ }
+ fz_free(ctx, state);
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_stream(ctx, state, read_predict, close_predict);
+}
diff --git a/source/fitz/font.c b/source/fitz/font.c
new file mode 100644
index 00000000..e8613f84
--- /dev/null
+++ b/source/fitz/font.c
@@ -0,0 +1,1094 @@
+#include "mupdf/fitz.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_STROKER_H
+
+#define MAX_BBOX_TABLE_SIZE 4096
+
+/* 20 degrees */
+#define SHEAR 0.36397f
+
+static void fz_drop_freetype(fz_context *ctx);
+
+static fz_font *
+fz_new_font(fz_context *ctx, char *name, int use_glyph_bbox, int glyph_count)
+{
+ fz_font *font;
+ int i;
+
+ font = fz_malloc_struct(ctx, fz_font);
+ font->refs = 1;
+
+ if (name)
+ fz_strlcpy(font->name, name, sizeof font->name);
+ else
+ fz_strlcpy(font->name, "(null)", sizeof font->name);
+
+ font->ft_face = NULL;
+ font->ft_substitute = 0;
+ font->ft_bold = 0;
+ font->ft_italic = 0;
+ font->ft_hint = 0;
+
+ font->ft_file = NULL;
+ font->ft_data = NULL;
+ font->ft_size = 0;
+
+ font->t3matrix = fz_identity;
+ font->t3resources = NULL;
+ font->t3procs = NULL;
+ font->t3lists = NULL;
+ font->t3widths = NULL;
+ font->t3flags = NULL;
+ font->t3doc = NULL;
+ font->t3run = NULL;
+
+ font->bbox.x0 = 0;
+ font->bbox.y0 = 0;
+ font->bbox.x1 = 1;
+ font->bbox.y1 = 1;
+
+ font->use_glyph_bbox = use_glyph_bbox;
+ if (use_glyph_bbox && glyph_count <= MAX_BBOX_TABLE_SIZE)
+ {
+ font->bbox_count = glyph_count;
+ font->bbox_table = fz_malloc_array(ctx, glyph_count, sizeof(fz_rect));
+ for (i = 0; i < glyph_count; i++)
+ font->bbox_table[i] = fz_infinite_rect;
+ }
+ else
+ {
+ if (use_glyph_bbox)
+ fz_warn(ctx, "not building glyph bbox table for font '%s' with %d glyphs", font->name, glyph_count);
+ font->bbox_count = 0;
+ font->bbox_table = NULL;
+ }
+
+ font->width_count = 0;
+ font->width_table = NULL;
+
+ return font;
+}
+
+fz_font *
+fz_keep_font(fz_context *ctx, fz_font *font)
+{
+ if (!font)
+ return NULL;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ font->refs ++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return font;
+}
+
+void
+fz_drop_font(fz_context *ctx, fz_font *font)
+{
+ int fterr;
+ int i, drop;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = (font && --font->refs == 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (!drop)
+ return;
+
+ if (font->t3procs)
+ {
+ if (font->t3resources)
+ font->t3freeres(font->t3doc, font->t3resources);
+ for (i = 0; i < 256; i++)
+ {
+ if (font->t3procs[i])
+ fz_drop_buffer(ctx, font->t3procs[i]);
+ if (font->t3lists[i])
+ fz_drop_display_list(ctx, font->t3lists[i]);
+ }
+ fz_free(ctx, font->t3procs);
+ fz_free(ctx, font->t3lists);
+ fz_free(ctx, font->t3widths);
+ fz_free(ctx, font->t3flags);
+ }
+
+ if (font->ft_face)
+ {
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ fterr = FT_Done_Face((FT_Face)font->ft_face);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ if (fterr)
+ fz_warn(ctx, "freetype finalizing face: %s", ft_error_string(fterr));
+ fz_drop_freetype(ctx);
+ }
+
+ fz_free(ctx, font->ft_file);
+ fz_free(ctx, font->ft_data);
+ fz_free(ctx, font->bbox_table);
+ fz_free(ctx, font->width_table);
+ fz_free(ctx, font);
+}
+
+void
+fz_set_font_bbox(fz_context *ctx, fz_font *font, float xmin, float ymin, float xmax, float ymax)
+{
+ if (xmin >= xmax || ymin >= ymax)
+ {
+ /* Invalid bbox supplied. It would be prohibitively slow to
+ * measure the true one, so make one up. */
+ font->bbox.x0 = -1;
+ font->bbox.y0 = -1;
+ font->bbox.x1 = 2;
+ font->bbox.y1 = 2;
+ }
+ else
+ {
+ font->bbox.x0 = xmin;
+ font->bbox.y0 = ymin;
+ font->bbox.x1 = xmax;
+ font->bbox.y1 = ymax;
+ }
+}
+
+/*
+ * Freetype hooks
+ */
+
+struct fz_font_context_s {
+ int ctx_refs;
+ FT_Library ftlib;
+ int ftlib_refs;
+};
+
+#undef __FTERRORS_H__
+#define FT_ERRORDEF(e, v, s) { (e), (s) },
+#define FT_ERROR_START_LIST
+#define FT_ERROR_END_LIST { 0, NULL }
+
+struct ft_error
+{
+ int err;
+ char *str;
+};
+
+void fz_new_font_context(fz_context *ctx)
+{
+ ctx->font = fz_malloc_struct(ctx, fz_font_context);
+ ctx->font->ctx_refs = 1;
+ ctx->font->ftlib = NULL;
+ ctx->font->ftlib_refs = 0;
+}
+
+fz_font_context *
+fz_keep_font_context(fz_context *ctx)
+{
+ if (!ctx || !ctx->font)
+ return NULL;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ ctx->font->ctx_refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return ctx->font;
+}
+
+void fz_drop_font_context(fz_context *ctx)
+{
+ int drop;
+ if (!ctx || !ctx->font)
+ return;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = --ctx->font->ctx_refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop == 0)
+ fz_free(ctx, ctx->font);
+}
+
+static const struct ft_error ft_errors[] =
+{
+#include FT_ERRORS_H
+};
+
+char *ft_error_string(int err)
+{
+ const struct ft_error *e;
+
+ for (e = ft_errors; e->str; e++)
+ if (e->err == err)
+ return e->str;
+
+ return "Unknown error";
+}
+
+static void
+fz_keep_freetype(fz_context *ctx)
+{
+ int fterr;
+ int maj, min, pat;
+ fz_font_context *fct = ctx->font;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ if (fct->ftlib)
+ {
+ fct->ftlib_refs++;
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return;
+ }
+
+ fterr = FT_Init_FreeType(&fct->ftlib);
+ if (fterr)
+ {
+ char *mess = ft_error_string(fterr);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot init freetype: %s", mess);
+ }
+
+ FT_Library_Version(fct->ftlib, &maj, &min, &pat);
+ if (maj == 2 && min == 1 && pat < 7)
+ {
+ fterr = FT_Done_FreeType(fct->ftlib);
+ if (fterr)
+ fz_warn(ctx, "freetype finalizing: %s", ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "freetype version too old: %d.%d.%d", maj, min, pat);
+ }
+
+ fct->ftlib_refs++;
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+}
+
+static void
+fz_drop_freetype(fz_context *ctx)
+{
+ int fterr;
+ fz_font_context *fct = ctx->font;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ if (--fct->ftlib_refs == 0)
+ {
+ fterr = FT_Done_FreeType(fct->ftlib);
+ if (fterr)
+ fz_warn(ctx, "freetype finalizing: %s", ft_error_string(fterr));
+ fct->ftlib = NULL;
+ }
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+}
+
+fz_font *
+fz_new_font_from_file(fz_context *ctx, char *name, char *path, int index, int use_glyph_bbox)
+{
+ FT_Face face;
+ fz_font *font;
+ int fterr;
+
+ fz_keep_freetype(ctx);
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ fterr = FT_New_Face(ctx->font->ftlib, path, index, &face);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ if (fterr)
+ {
+ fz_drop_freetype(ctx);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "freetype: cannot load font: %s", ft_error_string(fterr));
+ }
+
+ if (!name)
+ name = face->family_name;
+
+ font = fz_new_font(ctx, name, use_glyph_bbox, face->num_glyphs);
+ font->ft_face = face;
+ fz_set_font_bbox(ctx, font,
+ (float) face->bbox.xMin / face->units_per_EM,
+ (float) face->bbox.yMin / face->units_per_EM,
+ (float) face->bbox.xMax / face->units_per_EM,
+ (float) face->bbox.yMax / face->units_per_EM);
+
+ return font;
+}
+
+fz_font *
+fz_new_font_from_memory(fz_context *ctx, char *name, unsigned char *data, int len, int index, int use_glyph_bbox)
+{
+ FT_Face face;
+ fz_font *font;
+ int fterr;
+
+ fz_keep_freetype(ctx);
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ fterr = FT_New_Memory_Face(ctx->font->ftlib, data, len, index, &face);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ if (fterr)
+ {
+ fz_drop_freetype(ctx);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "freetype: cannot load font: %s", ft_error_string(fterr));
+ }
+
+ if (!name)
+ name = face->family_name;
+
+ font = fz_new_font(ctx, name, use_glyph_bbox, face->num_glyphs);
+ font->ft_face = face;
+ fz_set_font_bbox(ctx, font,
+ (float) face->bbox.xMin / face->units_per_EM,
+ (float) face->bbox.yMin / face->units_per_EM,
+ (float) face->bbox.xMax / face->units_per_EM,
+ (float) face->bbox.yMax / face->units_per_EM);
+
+ return font;
+}
+
+static fz_matrix *
+fz_adjust_ft_glyph_width(fz_context *ctx, fz_font *font, int gid, fz_matrix *trm)
+{
+ /* Fudge the font matrix to stretch the glyph if we've substituted the font. */
+ if (font->ft_substitute && font->width_table && gid < font->width_count)
+ {
+ FT_Error fterr;
+ int subw;
+ int realw;
+ float scale;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ /* TODO: use FT_Get_Advance */
+ fterr = FT_Set_Char_Size(font->ft_face, 1000, 1000, 72, 72);
+ if (fterr)
+ fz_warn(ctx, "freetype setting character size: %s", ft_error_string(fterr));
+
+ fterr = FT_Load_Glyph(font->ft_face, gid,
+ FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
+ if (fterr)
+ fz_warn(ctx, "freetype failed to load glyph: %s", ft_error_string(fterr));
+
+ realw = ((FT_Face)font->ft_face)->glyph->metrics.horiAdvance;
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ subw = font->width_table[gid];
+ if (realw)
+ scale = (float) subw / realw;
+ else
+ scale = 1;
+
+ fz_pre_scale(trm, scale, 1);
+ }
+
+ return trm;
+}
+
+static fz_pixmap *
+fz_copy_ft_bitmap(fz_context *ctx, int left, int top, FT_Bitmap *bitmap)
+{
+ fz_pixmap *pixmap;
+ int y;
+
+ pixmap = fz_new_pixmap(ctx, NULL, bitmap->width, bitmap->rows);
+ pixmap->x = left;
+ pixmap->y = top - bitmap->rows;
+
+ if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO)
+ {
+ for (y = 0; y < pixmap->h; y++)
+ {
+ unsigned char *out = pixmap->samples + (unsigned int)(y * pixmap->w);
+ unsigned char *in = bitmap->buffer + (unsigned int)((pixmap->h - y - 1) * bitmap->pitch);
+ unsigned char bit = 0x80;
+ int w = pixmap->w;
+ while (w--)
+ {
+ *out++ = (*in & bit) ? 255 : 0;
+ bit >>= 1;
+ if (bit == 0)
+ {
+ bit = 0x80;
+ in++;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (y = 0; y < pixmap->h; y++)
+ {
+ memcpy(pixmap->samples + (unsigned int)(y * pixmap->w),
+ bitmap->buffer + (unsigned int)((pixmap->h - y - 1) * bitmap->pitch),
+ pixmap->w);
+ }
+ }
+
+ return pixmap;
+}
+
+/* The glyph cache lock is always taken when this is called. */
+fz_pixmap *
+fz_render_ft_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, int aa)
+{
+ FT_Face face = font->ft_face;
+ FT_Matrix m;
+ FT_Vector v;
+ FT_Error fterr;
+ fz_pixmap *result;
+ fz_matrix local_trm = *trm;
+
+ float strength = fz_matrix_expansion(trm) * 0.02f;
+
+ fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm);
+
+ if (font->ft_italic)
+ fz_pre_shear(&local_trm, SHEAR, 0);
+
+ /*
+ Freetype mutilates complex glyphs if they are loaded
+ with FT_Set_Char_Size 1.0. it rounds the coordinates
+ before applying transformation. to get more precision in
+ freetype, we shift part of the scale in the matrix
+ into FT_Set_Char_Size instead
+ */
+
+ m.xx = local_trm.a * 64; /* should be 65536 */
+ m.yx = local_trm.b * 64;
+ m.xy = local_trm.c * 64;
+ m.yy = local_trm.d * 64;
+ v.x = local_trm.e * 64;
+ v.y = local_trm.f * 64;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
+ if (fterr)
+ fz_warn(ctx, "freetype setting character size: %s", ft_error_string(fterr));
+ FT_Set_Transform(face, &m, &v);
+
+ if (aa == 0)
+ {
+ /* enable grid fitting for non-antialiased rendering */
+ float scale = fz_matrix_expansion(&local_trm);
+ m.xx = local_trm.a * 65536 / scale;
+ m.xy = local_trm.b * 65536 / scale;
+ m.yx = local_trm.c * 65536 / scale;
+ m.yy = local_trm.d * 65536 / scale;
+ v.x = 0;
+ v.y = 0;
+
+ fterr = FT_Set_Char_Size(face, 64 * scale, 64 * scale, 72, 72);
+ if (fterr)
+ fz_warn(ctx, "freetype setting character size: %s", ft_error_string(fterr));
+ FT_Set_Transform(face, &m, &v);
+ fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_MONO);
+ if (fterr) {
+ fz_warn(ctx, "freetype load hinted glyph (gid %d): %s", gid, ft_error_string(fterr));
+ goto retry_unhinted;
+ }
+ }
+ else if (font->ft_hint)
+ {
+ /*
+ Enable hinting, but keep the huge char size so that
+ it is hinted for a character. This will in effect nullify
+ the effect of grid fitting. This form of hinting should
+ only be used for DynaLab and similar tricky TrueType fonts,
+ so that we get the correct outline shape.
+ */
+ fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP);
+ if (fterr) {
+ fz_warn(ctx, "freetype load hinted glyph (gid %d): %s", gid, ft_error_string(fterr));
+ goto retry_unhinted;
+ }
+ }
+ else
+ {
+retry_unhinted:
+ fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
+ if (fterr)
+ {
+ fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+ }
+
+ if (font->ft_bold)
+ {
+ FT_Outline_Embolden(&face->glyph->outline, strength * 64);
+ FT_Outline_Translate(&face->glyph->outline, -strength * 32, -strength * 32);
+ }
+
+ fterr = FT_Render_Glyph(face->glyph, fz_aa_level(ctx) > 0 ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
+ if (fterr)
+ {
+ fz_warn(ctx, "freetype render glyph (gid %d): %s", gid, ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ fz_try(ctx)
+ {
+ result = fz_copy_ft_bitmap(ctx, face->glyph->bitmap_left, face->glyph->bitmap_top, &face->glyph->bitmap);
+ }
+ fz_always(ctx)
+ {
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return result;
+}
+
+fz_pixmap *
+fz_render_ft_stroked_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, const fz_matrix *ctm, fz_stroke_state *state)
+{
+ FT_Face face = font->ft_face;
+ float expansion = fz_matrix_expansion(ctm);
+ int linewidth = state->linewidth * expansion * 64 / 2;
+ FT_Matrix m;
+ FT_Vector v;
+ FT_Error fterr;
+ FT_Stroker stroker;
+ FT_Glyph glyph;
+ FT_BitmapGlyph bitmap;
+ fz_pixmap *pixmap;
+ FT_Stroker_LineJoin line_join;
+ fz_matrix local_trm = *trm;
+
+ fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm);
+
+ if (font->ft_italic)
+ fz_pre_shear(&local_trm, SHEAR, 0);
+
+ m.xx = local_trm.a * 64; /* should be 65536 */
+ m.yx = local_trm.b * 64;
+ m.xy = local_trm.c * 64;
+ m.yy = local_trm.d * 64;
+ v.x = local_trm.e * 64;
+ v.y = local_trm.f * 64;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
+ if (fterr)
+ {
+ fz_warn(ctx, "FT_Set_Char_Size: %s", ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ FT_Set_Transform(face, &m, &v);
+
+ fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
+ if (fterr)
+ {
+ fz_warn(ctx, "FT_Load_Glyph(gid %d): %s", gid, ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ fterr = FT_Stroker_New(ctx->font->ftlib, &stroker);
+ if (fterr)
+ {
+ fz_warn(ctx, "FT_Stroker_New: %s", ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+#if FREETYPE_MAJOR * 10000 + FREETYPE_MINOR * 100 + FREETYPE_PATCH > 20405
+ /* New freetype */
+ line_join =
+ state->linejoin == FZ_LINEJOIN_MITER ? FT_STROKER_LINEJOIN_MITER_FIXED :
+ state->linejoin == FZ_LINEJOIN_ROUND ? FT_STROKER_LINEJOIN_ROUND :
+ state->linejoin == FZ_LINEJOIN_BEVEL ? FT_STROKER_LINEJOIN_BEVEL :
+ FT_STROKER_LINEJOIN_MITER_VARIABLE;
+#else
+ /* Old freetype */
+ line_join =
+ state->linejoin == FZ_LINEJOIN_MITER ? FT_STROKER_LINEJOIN_MITER :
+ state->linejoin == FZ_LINEJOIN_ROUND ? FT_STROKER_LINEJOIN_ROUND :
+ state->linejoin == FZ_LINEJOIN_BEVEL ? FT_STROKER_LINEJOIN_BEVEL :
+ FT_STROKER_LINEJOIN_MITER;
+#endif
+ FT_Stroker_Set(stroker, linewidth, (FT_Stroker_LineCap)state->start_cap, line_join, state->miterlimit * 65536);
+
+ fterr = FT_Get_Glyph(face->glyph, &glyph);
+ if (fterr)
+ {
+ fz_warn(ctx, "FT_Get_Glyph: %s", ft_error_string(fterr));
+ FT_Stroker_Done(stroker);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ fterr = FT_Glyph_Stroke(&glyph, stroker, 1);
+ if (fterr)
+ {
+ fz_warn(ctx, "FT_Glyph_Stroke: %s", ft_error_string(fterr));
+ FT_Done_Glyph(glyph);
+ FT_Stroker_Done(stroker);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ FT_Stroker_Done(stroker);
+
+ fterr = FT_Glyph_To_Bitmap(&glyph, fz_aa_level(ctx) > 0 ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
+ if (fterr)
+ {
+ fz_warn(ctx, "FT_Glyph_To_Bitmap: %s", ft_error_string(fterr));
+ FT_Done_Glyph(glyph);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ bitmap = (FT_BitmapGlyph)glyph;
+ fz_try(ctx)
+ {
+ pixmap = fz_copy_ft_bitmap(ctx, bitmap->left, bitmap->top, &bitmap->bitmap);
+ }
+ fz_always(ctx)
+ {
+ FT_Done_Glyph(glyph);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return pixmap;
+}
+
+static fz_rect *
+fz_bound_ft_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_rect *bounds)
+{
+ FT_Face face = font->ft_face;
+ FT_Error fterr;
+ FT_BBox cbox;
+ FT_Matrix m;
+ FT_Vector v;
+
+ // TODO: refactor loading into fz_load_ft_glyph
+ // TODO: cache results
+
+ float strength = fz_matrix_expansion(trm) * 0.02f;
+ fz_matrix local_trm = *trm;
+
+ fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm);
+
+ if (font->ft_italic)
+ fz_pre_shear(&local_trm, SHEAR, 0);
+
+ m.xx = local_trm.a * 64; /* should be 65536 */
+ m.yx = local_trm.b * 64;
+ m.xy = local_trm.c * 64;
+ m.yy = local_trm.d * 64;
+ v.x = local_trm.e * 64;
+ v.y = local_trm.f * 64;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
+ if (fterr)
+ fz_warn(ctx, "freetype setting character size: %s", ft_error_string(fterr));
+ FT_Set_Transform(face, &m, &v);
+
+ fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
+ if (fterr)
+ {
+ fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ bounds->x0 = bounds->x1 = local_trm.e;
+ bounds->y0 = bounds->y1 = local_trm.f;
+ return bounds;
+ }
+
+ if (font->ft_bold)
+ {
+ FT_Outline_Embolden(&face->glyph->outline, strength * 64);
+ FT_Outline_Translate(&face->glyph->outline, -strength * 32, -strength * 32);
+ }
+
+ FT_Outline_Get_CBox(&face->glyph->outline, &cbox);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ bounds->x0 = cbox.xMin / 64.0f;
+ bounds->y0 = cbox.yMin / 64.0f;
+ bounds->x1 = cbox.xMax / 64.0f;
+ bounds->y1 = cbox.yMax / 64.0f;
+
+ if (fz_is_empty_rect(bounds))
+ {
+ bounds->x0 = bounds->x1 = local_trm.e;
+ bounds->y0 = bounds->y1 = local_trm.f;
+ }
+
+ return bounds;
+}
+
+/* Turn FT_Outline into a fz_path */
+
+struct closure {
+ fz_context *ctx;
+ fz_path *path;
+ float x, y;
+};
+
+static int move_to(const FT_Vector *p, void *cc)
+{
+ fz_context *ctx = ((struct closure *)cc)->ctx;
+ fz_path *path = ((struct closure *)cc)->path;
+ float tx = ((struct closure *)cc)->x;
+ float ty = ((struct closure *)cc)->y;
+ fz_moveto(ctx, path, tx + p->x / 64.0f, ty + p->y / 64.0f);
+ return 0;
+}
+
+static int line_to(const FT_Vector *p, void *cc)
+{
+ fz_context *ctx = ((struct closure *)cc)->ctx;
+ fz_path *path = ((struct closure *)cc)->path;
+ float tx = ((struct closure *)cc)->x;
+ float ty = ((struct closure *)cc)->y;
+ fz_lineto(ctx, path, tx + p->x / 64.0f, ty + p->y / 64.0f);
+ return 0;
+}
+
+static int conic_to(const FT_Vector *c, const FT_Vector *p, void *cc)
+{
+ fz_context *ctx = ((struct closure *)cc)->ctx;
+ fz_path *path = ((struct closure *)cc)->path;
+ float tx = ((struct closure *)cc)->x;
+ float ty = ((struct closure *)cc)->y;
+ fz_point s, c1, c2;
+ float cx = tx + c->x / 64.0f, cy = ty + c->y / 64.0f;
+ float px = tx + p->x / 64.0f, py = ty + p->y / 64.0f;
+ s = fz_currentpoint(ctx, path);
+ c1.x = (s.x + cx * 2) / 3;
+ c1.y = (s.y + cy * 2) / 3;
+ c2.x = (px + cx * 2) / 3;
+ c2.y = (py + cy * 2) / 3;
+ fz_curveto(ctx, path, c1.x, c1.y, c2.x, c2.y, px, py);
+ return 0;
+}
+
+static int cubic_to(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, void *cc)
+{
+ fz_context *ctx = ((struct closure *)cc)->ctx;
+ fz_path *path = ((struct closure *)cc)->path;
+ float tx = ((struct closure *)cc)->x;
+ float ty = ((struct closure *)cc)->y;
+ fz_curveto(ctx, path,
+ tx + c1->x/64.0f, ty + c1->y/64.0f,
+ tx + c2->x/64.0f, ty + c2->y/64.0f,
+ tx + p->x/64.0f, ty + p->y/64.0f);
+ return 0;
+}
+
+static const FT_Outline_Funcs outline_funcs = {
+ move_to, line_to, conic_to, cubic_to, 0, 0
+};
+
+fz_path *
+fz_outline_ft_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm)
+{
+ struct closure cc;
+ FT_Face face = font->ft_face;
+ FT_Matrix m;
+ FT_Vector v;
+ int fterr;
+ fz_matrix local_trm = *trm;
+
+ float strength = fz_matrix_expansion(trm) * 0.02f;
+
+ fz_adjust_ft_glyph_width(ctx, font, gid, &local_trm);
+
+ if (font->ft_italic)
+ fz_pre_shear(&local_trm, SHEAR, 0);
+
+ m.xx = local_trm.a * 64; /* should be 65536 */
+ m.yx = local_trm.b * 64;
+ m.xy = local_trm.c * 64;
+ m.yy = local_trm.d * 64;
+ v.x = 0;
+ v.y = 0;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+
+ fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */
+ if (fterr)
+ fz_warn(ctx, "freetype setting character size: %s", ft_error_string(fterr));
+ FT_Set_Transform(face, &m, &v);
+
+ fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
+ if (fterr)
+ {
+ fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr));
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ return NULL;
+ }
+
+ if (font->ft_bold)
+ {
+ FT_Outline_Embolden(&face->glyph->outline, strength * 64);
+ FT_Outline_Translate(&face->glyph->outline, -strength * 32, -strength * 32);
+ }
+
+ fz_try(ctx)
+ {
+ cc.ctx = ctx;
+ cc.path = fz_new_path(ctx);
+ cc.x = local_trm.e;
+ cc.y = local_trm.f;
+ fz_moveto(ctx, cc.path, cc.x, cc.y);
+ FT_Outline_Decompose(&face->glyph->outline, &outline_funcs, &cc);
+ fz_closepath(ctx, cc.path);
+ }
+ fz_always(ctx)
+ {
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "freetype cannot decompose outline");
+ fz_free(ctx, cc.path);
+ return NULL;
+ }
+
+ return cc.path;
+}
+
+/*
+ * Type 3 fonts...
+ */
+
+fz_font *
+fz_new_type3_font(fz_context *ctx, char *name, const fz_matrix *matrix)
+{
+ fz_font *font;
+ int i;
+
+ font = fz_new_font(ctx, name, 1, 256);
+ font->t3procs = fz_malloc_array(ctx, 256, sizeof(fz_buffer*));
+ font->t3lists = fz_malloc_array(ctx, 256, sizeof(fz_display_list*));
+ font->t3widths = fz_malloc_array(ctx, 256, sizeof(float));
+ font->t3flags = fz_malloc_array(ctx, 256, sizeof(char));
+
+ font->t3matrix = *matrix;
+ for (i = 0; i < 256; i++)
+ {
+ font->t3procs[i] = NULL;
+ font->t3lists[i] = NULL;
+ font->t3widths[i] = 0;
+ font->t3flags[i] = 0;
+ }
+
+ return font;
+}
+
+void
+fz_prepare_t3_glyph(fz_context *ctx, fz_font *font, int gid, int nested_depth)
+{
+ fz_buffer *contents;
+ fz_device *dev;
+
+ contents = font->t3procs[gid];
+ if (!contents)
+ return;
+
+ /* We've not already loaded this one! */
+ assert(font->t3lists[gid] == NULL);
+
+ font->t3lists[gid] = fz_new_display_list(ctx);
+
+ dev = fz_new_list_device(ctx, font->t3lists[gid]);
+ dev->flags = FZ_DEVFLAG_FILLCOLOR_UNDEFINED |
+ FZ_DEVFLAG_STROKECOLOR_UNDEFINED |
+ FZ_DEVFLAG_STARTCAP_UNDEFINED |
+ FZ_DEVFLAG_DASHCAP_UNDEFINED |
+ FZ_DEVFLAG_ENDCAP_UNDEFINED |
+ FZ_DEVFLAG_LINEJOIN_UNDEFINED |
+ FZ_DEVFLAG_MITERLIMIT_UNDEFINED |
+ FZ_DEVFLAG_LINEWIDTH_UNDEFINED;
+ font->t3run(font->t3doc, font->t3resources, contents, dev, &fz_identity, NULL, 0);
+ font->t3flags[gid] = dev->flags;
+ fz_free_device(dev);
+}
+
+static fz_rect *
+fz_bound_t3_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_rect *bounds)
+{
+ fz_display_list *list;
+ fz_matrix ctm;
+ fz_device *dev;
+
+ list = font->t3lists[gid];
+ if (!list)
+ {
+ *bounds = fz_empty_rect;
+ return fz_transform_rect(bounds, trm);
+ }
+
+ fz_concat(&ctm, &font->t3matrix, trm);
+ dev = fz_new_bbox_device(ctx, bounds);
+ fz_try(ctx)
+ {
+ fz_run_display_list(list, dev, &ctm, &fz_infinite_rect, NULL);
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return bounds;
+}
+
+fz_pixmap *
+fz_render_t3_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_colorspace *model, fz_irect scissor)
+{
+ fz_display_list *list;
+ fz_matrix ctm;
+ fz_rect bounds;
+ fz_irect bbox;
+ fz_device *dev;
+ fz_pixmap *glyph;
+ fz_pixmap *result;
+
+ if (gid < 0 || gid > 255)
+ return NULL;
+
+ list = font->t3lists[gid];
+ if (!list)
+ return NULL;
+
+ if (font->t3flags[gid] & FZ_DEVFLAG_MASK)
+ {
+ if (font->t3flags[gid] & FZ_DEVFLAG_COLOR)
+ fz_warn(ctx, "type3 glyph claims to be both masked and colored");
+ model = NULL;
+ }
+ else if (font->t3flags[gid] & FZ_DEVFLAG_COLOR)
+ {
+ if (!model)
+ fz_warn(ctx, "colored type3 glyph wanted in masked context");
+ }
+ else
+ {
+ fz_warn(ctx, "type3 glyph doesn't specify masked or colored");
+ model = NULL; /* Treat as masked */
+ }
+
+ fz_expand_rect(fz_bound_glyph(ctx, font, gid, trm, &bounds), 1);
+ fz_irect_from_rect(&bbox, &bounds);
+ fz_intersect_irect(&bbox, &scissor);
+
+ glyph = fz_new_pixmap_with_bbox(ctx, model ? model : fz_device_gray(ctx), &bbox);
+ fz_clear_pixmap(ctx, glyph);
+
+ fz_concat(&ctm, &font->t3matrix, trm);
+ dev = fz_new_draw_device_type3(ctx, glyph);
+ fz_run_display_list(list, dev, &ctm, &fz_infinite_rect, NULL);
+ fz_free_device(dev);
+
+ if (!model)
+ {
+ result = fz_alpha_from_gray(ctx, glyph, 0);
+ fz_drop_pixmap(ctx, glyph);
+ }
+ else
+ result = glyph;
+
+ return result;
+}
+
+void
+fz_render_t3_glyph_direct(fz_context *ctx, fz_device *dev, fz_font *font, int gid, const fz_matrix *trm, void *gstate, int nested_depth)
+{
+ fz_matrix ctm;
+ void *contents;
+
+ if (gid < 0 || gid > 255)
+ return;
+
+ contents = font->t3procs[gid];
+ if (!contents)
+ return;
+
+ if (font->t3flags[gid] & FZ_DEVFLAG_MASK)
+ {
+ if (font->t3flags[gid] & FZ_DEVFLAG_COLOR)
+ fz_warn(ctx, "type3 glyph claims to be both masked and colored");
+ }
+ else if (font->t3flags[gid] & FZ_DEVFLAG_COLOR)
+ {
+ }
+ else
+ {
+ fz_warn(ctx, "type3 glyph doesn't specify masked or colored");
+ }
+
+ fz_concat(&ctm, &font->t3matrix, trm);
+ font->t3run(font->t3doc, font->t3resources, contents, dev, &ctm, gstate, nested_depth);
+}
+
+#ifndef NDEBUG
+void
+fz_print_font(fz_context *ctx, FILE *out, fz_font *font)
+{
+ fprintf(out, "font '%s' {\n", font->name);
+
+ if (font->ft_face)
+ {
+ fprintf(out, "\tfreetype face %p\n", font->ft_face);
+ if (font->ft_substitute)
+ fprintf(out, "\tsubstitute font\n");
+ }
+
+ if (font->t3procs)
+ {
+ fprintf(out, "\ttype3 matrix [%g %g %g %g]\n",
+ font->t3matrix.a, font->t3matrix.b,
+ font->t3matrix.c, font->t3matrix.d);
+
+ fprintf(out, "\ttype3 bbox [%g %g %g %g]\n",
+ font->bbox.x0, font->bbox.y0,
+ font->bbox.x1, font->bbox.y1);
+ }
+
+ fprintf(out, "}\n");
+}
+#endif
+
+fz_rect *
+fz_bound_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *trm, fz_rect *rect)
+{
+ if (font->bbox_table && gid < font->bbox_count)
+ {
+ if (fz_is_infinite_rect(&font->bbox_table[gid]))
+ {
+ if (font->ft_face)
+ fz_bound_ft_glyph(ctx, font, gid, &fz_identity, &font->bbox_table[gid]);
+ else if (font->t3lists)
+ fz_bound_t3_glyph(ctx, font, gid, &fz_identity, &font->bbox_table[gid]);
+ else
+ font->bbox_table[gid] = fz_empty_rect;
+ }
+ *rect = font->bbox_table[gid];
+ }
+ else
+ {
+ /* fall back to font bbox */
+ *rect = font->bbox;
+ }
+
+ return fz_transform_rect(rect, trm);
+}
+
+fz_path *
+fz_outline_glyph(fz_context *ctx, fz_font *font, int gid, const fz_matrix *ctm)
+{
+ if (!font->ft_face)
+ return NULL;
+ return fz_outline_ft_glyph(ctx, font, gid, ctm);
+}
+
+int fz_glyph_cacheable(fz_context *ctx, fz_font *font, int gid)
+{
+ if (!font->t3procs || !font->t3flags || gid < 0 || gid >= font->bbox_count)
+ return 1;
+ return (font->t3flags[gid] & FZ_DEVFLAG_UNCACHEABLE) == 0;
+}
diff --git a/source/fitz/function.c b/source/fitz/function.c
new file mode 100644
index 00000000..b5ba4815
--- /dev/null
+++ b/source/fitz/function.c
@@ -0,0 +1,48 @@
+#include "mupdf/fitz.h"
+
+void
+fz_eval_function(fz_context *ctx, fz_function *func, float *in_, int inlen, float *out_, int outlen)
+{
+ float fakein[FZ_FN_MAXM];
+ float fakeout[FZ_FN_MAXN];
+ float *in = in_;
+ float *out = out_;
+
+ if (inlen < func->m)
+ {
+ in = fakein;
+ memset(in, 0, sizeof(float) * func->m);
+ memcpy(in, in_, sizeof(float) * inlen);
+ }
+
+ if (outlen < func->n)
+ {
+ out = fakeout;
+ memset(out, 0, sizeof(float) * func->n);
+ }
+ else
+ memset(out, 0, sizeof(float) * outlen);
+
+ func->evaluate(ctx, func, in, out);
+
+ if (outlen < func->n)
+ memcpy(out_, out, sizeof(float) * outlen);
+}
+
+fz_function *
+fz_keep_function(fz_context *ctx, fz_function *func)
+{
+ return (fz_function *)fz_keep_storable(ctx, &func->storable);
+}
+
+void
+fz_drop_function(fz_context *ctx, fz_function *func)
+{
+ fz_drop_storable(ctx, &func->storable);
+}
+
+unsigned int
+fz_function_size(fz_function *func)
+{
+ return (func ? func->size : 0);
+}
diff --git a/source/fitz/geometry.c b/source/fitz/geometry.c
new file mode 100644
index 00000000..81450246
--- /dev/null
+++ b/source/fitz/geometry.c
@@ -0,0 +1,483 @@
+#include "mupdf/fitz.h"
+
+#define MAX4(a,b,c,d) fz_max(fz_max(a,b), fz_max(c,d))
+#define MIN4(a,b,c,d) fz_min(fz_min(a,b), fz_min(c,d))
+
+/* A useful macro to add with overflow detection and clamping.
+
+ We want to do "b = a + x", but to allow for overflow. Consider the
+ top bits, and the cases in which overflow occurs:
+
+ overflow a x b ~a^x a^b (~a^x)&(a^b)
+ no 0 0 0 1 0 0
+ yes 0 0 1 1 1 1
+ no 0 1 0 0 0 0
+ no 0 1 1 0 1 0
+ no 1 0 0 0 1 0
+ no 1 0 1 0 0 0
+ yes 1 1 0 1 1 1
+ no 1 1 1 1 0 0
+*/
+#define ADD_WITH_SAT(b,a,x) \
+ ((b) = (a) + (x), (b) = (((~(a)^(x))&((a)^(b))) < 0 ? ((x) < 0 ? INT_MIN : INT_MAX) : (b)))
+
+/* Matrices, points and affine transformations */
+
+const fz_matrix fz_identity = { 1, 0, 0, 1, 0, 0 };
+
+fz_matrix *
+fz_concat(fz_matrix *dst, const fz_matrix *one, const fz_matrix *two)
+{
+ fz_matrix dst2;
+ dst2.a = one->a * two->a + one->b * two->c;
+ dst2.b = one->a * two->b + one->b * two->d;
+ dst2.c = one->c * two->a + one->d * two->c;
+ dst2.d = one->c * two->b + one->d * two->d;
+ dst2.e = one->e * two->a + one->f * two->c + two->e;
+ dst2.f = one->e * two->b + one->f * two->d + two->f;
+ *dst = dst2;
+ return dst;
+}
+
+fz_matrix *
+fz_scale(fz_matrix *m, float sx, float sy)
+{
+ m->a = sx; m->b = 0;
+ m->c = 0; m->d = sy;
+ m->e = 0; m->f = 0;
+ return m;
+}
+
+fz_matrix *
+fz_pre_scale(fz_matrix *mat, float sx, float sy)
+{
+ mat->a *= sx;
+ mat->b *= sx;
+ mat->c *= sy;
+ mat->d *= sy;
+ return mat;
+}
+
+fz_matrix *
+fz_shear(fz_matrix *mat, float h, float v)
+{
+ mat->a = 1; mat->b = v;
+ mat->c = h; mat->d = 1;
+ mat->e = 0; mat->f = 0;
+ return mat;
+}
+
+fz_matrix *
+fz_pre_shear(fz_matrix *mat, float h, float v)
+{
+ float a = mat->a;
+ float b = mat->b;
+ mat->a += v * mat->c;
+ mat->b += v * mat->d;
+ mat->c += h * a;
+ mat->d += h * b;
+ return mat;
+}
+
+fz_matrix *
+fz_rotate(fz_matrix *m, float theta)
+{
+ float s;
+ float c;
+
+ while (theta < 0)
+ theta += 360;
+ while (theta >= 360)
+ theta -= 360;
+
+ if (fabsf(0 - theta) < FLT_EPSILON)
+ {
+ s = 0;
+ c = 1;
+ }
+ else if (fabsf(90.0f - theta) < FLT_EPSILON)
+ {
+ s = 1;
+ c = 0;
+ }
+ else if (fabsf(180.0f - theta) < FLT_EPSILON)
+ {
+ s = 0;
+ c = -1;
+ }
+ else if (fabsf(270.0f - theta) < FLT_EPSILON)
+ {
+ s = -1;
+ c = 0;
+ }
+ else
+ {
+ s = sinf(theta * (float)M_PI / 180);
+ c = cosf(theta * (float)M_PI / 180);
+ }
+
+ m->a = c; m->b = s;
+ m->c = -s; m->d = c;
+ m->e = 0; m->f = 0;
+ return m;
+}
+
+fz_matrix *
+fz_pre_rotate(fz_matrix *m, float theta)
+{
+ while (theta < 0)
+ theta += 360;
+ while (theta >= 360)
+ theta -= 360;
+
+ if (fabsf(0 - theta) < FLT_EPSILON)
+ {
+ /* Nothing to do */
+ }
+ else if (fabsf(90.0f - theta) < FLT_EPSILON)
+ {
+ float a = m->a;
+ float b = m->b;
+ m->a = m->c;
+ m->b = m->d;
+ m->c = -a;
+ m->d = -b;
+ }
+ else if (fabsf(180.0f - theta) < FLT_EPSILON)
+ {
+ m->a = -m->a;
+ m->b = -m->b;
+ m->c = -m->c;
+ m->d = -m->d;
+ }
+ else if (fabsf(270.0f - theta) < FLT_EPSILON)
+ {
+ float a = m->a;
+ float b = m->b;
+ m->a = -m->c;
+ m->b = -m->d;
+ m->c = a;
+ m->d = b;
+ }
+ else
+ {
+ float s = sinf(theta * (float)M_PI / 180);
+ float c = cosf(theta * (float)M_PI / 180);
+ float a = m->a;
+ float b = m->b;
+ m->a = c * a + s * m->c;
+ m->b = c * b + s * m->d;
+ m->c =-s * a + c * m->c;
+ m->d =-s * b + c * m->d;
+ }
+
+ return m;
+}
+
+fz_matrix *
+fz_translate(fz_matrix *m, float tx, float ty)
+{
+ m->a = 1; m->b = 0;
+ m->c = 0; m->d = 1;
+ m->e = tx; m->f = ty;
+ return m;
+}
+
+fz_matrix *
+fz_pre_translate(fz_matrix *mat, float tx, float ty)
+{
+ mat->e += tx * mat->a + ty * mat->c;
+ mat->f += tx * mat->b + ty * mat->d;
+ return mat;
+}
+
+fz_matrix *
+fz_invert_matrix(fz_matrix *dst, const fz_matrix *src)
+{
+ /* Be careful to cope with dst == src */
+ float a = src->a;
+ float det = a * src->d - src->b * src->c;
+ if (det < -FLT_EPSILON || det > FLT_EPSILON)
+ {
+ float rdet = 1 / det;
+ dst->a = src->d * rdet;
+ dst->b = -src->b * rdet;
+ dst->c = -src->c * rdet;
+ dst->d = a * rdet;
+ a = -src->e * dst->a - src->f * dst->c;
+ dst->f = -src->e * dst->b - src->f * dst->d;
+ dst->e = a;
+ }
+ else
+ *dst = *src;
+ return dst;
+}
+
+int
+fz_is_rectilinear(const fz_matrix *m)
+{
+ return (fabsf(m->b) < FLT_EPSILON && fabsf(m->c) < FLT_EPSILON) ||
+ (fabsf(m->a) < FLT_EPSILON && fabsf(m->d) < FLT_EPSILON);
+}
+
+float
+fz_matrix_expansion(const fz_matrix *m)
+{
+ return sqrtf(fabsf(m->a * m->d - m->b * m->c));
+}
+
+float
+fz_matrix_max_expansion(const fz_matrix *m)
+{
+ float max = fabsf(m->a);
+ float x = fabsf(m->b);
+ if (max < x)
+ max = x;
+ x = fabsf(m->c);
+ if (max < x)
+ max = x;
+ x = fabsf(m->d);
+ if (max < x)
+ max = x;
+ return max;
+}
+
+fz_point *
+fz_transform_point(fz_point *restrict p, const fz_matrix *restrict m)
+{
+ float x = p->x;
+ p->x = x * m->a + p->y * m->c + m->e;
+ p->y = x * m->b + p->y * m->d + m->f;
+ return p;
+}
+
+fz_point *
+fz_transform_vector(fz_point *restrict p, const fz_matrix *restrict m)
+{
+ float x = p->x;
+ p->x = x * m->a + p->y * m->c;
+ p->y = x * m->b + p->y * m->d;
+ return p;
+}
+
+void
+fz_normalize_vector(fz_point *p)
+{
+ float len = p->x * p->x + p->y * p->y;
+ if (len != 0)
+ {
+ len = sqrtf(len);
+ p->x /= len;
+ p->y /= len;
+ }
+}
+
+/* Rectangles and bounding boxes */
+
+/* biggest and smallest integers that a float can represent perfectly (i.e. 24 bits) */
+#define MAX_SAFE_INT 16777216
+#define MIN_SAFE_INT -16777216
+
+const fz_rect fz_infinite_rect = { 1, 1, -1, -1 };
+const fz_rect fz_empty_rect = { 0, 0, 0, 0 };
+const fz_rect fz_unit_rect = { 0, 0, 1, 1 };
+
+const fz_irect fz_infinite_irect = { 1, 1, -1, -1 };
+const fz_irect fz_empty_irect = { 0, 0, 0, 0 };
+const fz_irect fz_unit_bbox = { 0, 0, 1, 1 };
+
+fz_irect *
+fz_irect_from_rect(fz_irect *restrict b, const fz_rect *restrict r)
+{
+ b->x0 = fz_clamp(floorf(r->x0), MIN_SAFE_INT, MAX_SAFE_INT);
+ b->y0 = fz_clamp(floorf(r->y0), MIN_SAFE_INT, MAX_SAFE_INT);
+ b->x1 = fz_clamp(ceilf(r->x1), MIN_SAFE_INT, MAX_SAFE_INT);
+ b->y1 = fz_clamp(ceilf(r->y1), MIN_SAFE_INT, MAX_SAFE_INT);
+ return b;
+}
+
+fz_rect *
+fz_rect_from_irect(fz_rect *restrict r, const fz_irect *restrict a)
+{
+ r->x0 = a->x0;
+ r->y0 = a->y0;
+ r->x1 = a->x1;
+ r->y1 = a->y1;
+ return r;
+}
+
+fz_irect *
+fz_round_rect(fz_irect * restrict b, const fz_rect *restrict r)
+{
+ int i;
+
+ i = floorf(r->x0 + 0.001);
+ b->x0 = fz_clamp(i, MIN_SAFE_INT, MAX_SAFE_INT);
+ i = floorf(r->y0 + 0.001);
+ b->y0 = fz_clamp(i, MIN_SAFE_INT, MAX_SAFE_INT);
+ i = ceilf(r->x1 - 0.001);
+ b->x1 = fz_clamp(i, MIN_SAFE_INT, MAX_SAFE_INT);
+ i = ceilf(r->y1 - 0.001);
+ b->y1 = fz_clamp(i, MIN_SAFE_INT, MAX_SAFE_INT);
+
+ return b;
+}
+
+fz_rect *
+fz_intersect_rect(fz_rect *restrict a, const fz_rect *restrict b)
+{
+ /* Check for empty box before infinite box */
+ if (fz_is_empty_rect(a)) return a;
+ if (fz_is_empty_rect(b)) {
+ *a = fz_empty_rect;
+ return a;
+ }
+ if (fz_is_infinite_rect(b)) return a;
+ if (fz_is_infinite_rect(a)) {
+ *a = *b;
+ return a;
+ }
+ if (a->x0 < b->x0)
+ a->x0 = b->x0;
+ if (a->y0 < b->y0)
+ a->y0 = b->y0;
+ if (a->x1 > b->x1)
+ a->x1 = b->x1;
+ if (a->y1 > b->y1)
+ a->y1 = b->y1;
+ if (a->x1 < a->x0 || a->y1 < a->y0)
+ *a = fz_empty_rect;
+ return a;
+}
+
+fz_irect *
+fz_intersect_irect(fz_irect *restrict a, const fz_irect *restrict b)
+{
+ /* Check for empty box before infinite box */
+ if (fz_is_empty_irect(a)) return a;
+ if (fz_is_empty_irect(b))
+ {
+ *a = fz_empty_irect;
+ return a;
+ }
+ if (fz_is_infinite_irect(b)) return a;
+ if (fz_is_infinite_irect(a))
+ {
+ *a = *b;
+ return a;
+ }
+ if (a->x0 < b->x0)
+ a->x0 = b->x0;
+ if (a->y0 < b->y0)
+ a->y0 = b->y0;
+ if (a->x1 > b->x1)
+ a->x1 = b->x1;
+ if (a->y1 > b->y1)
+ a->y1 = b->y1;
+ if (a->x1 < a->x0 || a->y1 < a->y0)
+ *a = fz_empty_irect;
+ return a;
+}
+
+fz_rect *
+fz_union_rect(fz_rect *restrict a, const fz_rect *restrict b)
+{
+ /* Check for empty box before infinite box */
+ if (fz_is_empty_rect(b)) return a;
+ if (fz_is_empty_rect(a)) {
+ *a = *b;
+ return a;
+ }
+ if (fz_is_infinite_rect(a)) return a;
+ if (fz_is_infinite_rect(b)) {
+ *a = *b;
+ return a;
+ }
+ if (a->x0 > b->x0)
+ a->x0 = b->x0;
+ if (a->y0 > b->y0)
+ a->y0 = b->y0;
+ if (a->x1 < b->x1)
+ a->x1 = b->x1;
+ if (a->y1 < b->y1)
+ a->y1 = b->y1;
+ return a;
+}
+
+fz_irect *
+fz_translate_irect(fz_irect *a, int xoff, int yoff)
+{
+ int t;
+
+ if (fz_is_empty_irect(a)) return a;
+ if (fz_is_infinite_irect(a)) return a;
+ a->x0 = ADD_WITH_SAT(t, a->x0, xoff);
+ a->y0 = ADD_WITH_SAT(t, a->y0, yoff);
+ a->x1 = ADD_WITH_SAT(t, a->x1, xoff);
+ a->y1 = ADD_WITH_SAT(t, a->y1, yoff);
+ return a;
+}
+
+fz_rect *
+fz_transform_rect(fz_rect *restrict r, const fz_matrix *restrict m)
+{
+ fz_point s, t, u, v;
+
+ if (fz_is_infinite_rect(r))
+ return r;
+
+ if (fabsf(m->b) < FLT_EPSILON && fabsf(m->c) < FLT_EPSILON)
+ {
+ if (m->a < 0)
+ {
+ float f = r->x0;
+ r->x0 = r->x1;
+ r->x1 = f;
+ }
+ if (m->d < 0)
+ {
+ float f = r->y0;
+ r->y0 = r->y1;
+ r->y1 = f;
+ }
+ fz_transform_point(fz_rect_min(r), m);
+ fz_transform_point(fz_rect_max(r), m);
+ return r;
+ }
+
+ s.x = r->x0; s.y = r->y0;
+ t.x = r->x0; t.y = r->y1;
+ u.x = r->x1; u.y = r->y1;
+ v.x = r->x1; v.y = r->y0;
+ fz_transform_point(&s, m);
+ fz_transform_point(&t, m);
+ fz_transform_point(&u, m);
+ fz_transform_point(&v, m);
+ r->x0 = MIN4(s.x, t.x, u.x, v.x);
+ r->y0 = MIN4(s.y, t.y, u.y, v.y);
+ r->x1 = MAX4(s.x, t.x, u.x, v.x);
+ r->y1 = MAX4(s.y, t.y, u.y, v.y);
+ return r;
+}
+
+fz_rect *
+fz_expand_rect(fz_rect *a, float expand)
+{
+ if (fz_is_empty_rect(a)) return a;
+ if (fz_is_infinite_rect(a)) return a;
+ a->x0 -= expand;
+ a->y0 -= expand;
+ a->x1 += expand;
+ a->y1 += expand;
+ return a;
+}
+
+fz_rect *fz_include_point_in_rect(fz_rect *r, const fz_point *p)
+{
+ if (p->x < r->x0) r->x0 = p->x;
+ if (p->x > r->x1) r->x1 = p->x;
+ if (p->y < r->y0) r->y0 = p->y;
+ if (p->y > r->y1) r->y1 = p->y;
+
+ return r;
+}
diff --git a/source/fitz/getopt.c b/source/fitz/getopt.c
new file mode 100644
index 00000000..2a6e5ac4
--- /dev/null
+++ b/source/fitz/getopt.c
@@ -0,0 +1,66 @@
+/*
+ * This is a version of the public domain getopt implementation by
+ * Henry Spencer originally posted to net.sources.
+ *
+ * This file is in the public domain.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#define getopt fz_getopt
+#define optarg fz_optarg
+#define optind fz_optind
+
+char *optarg; /* Global argument pointer. */
+int optind = 0; /* Global argv index. */
+
+static char *scan = NULL; /* Private scan pointer. */
+
+int
+getopt(int argc, char *argv[], char *optstring)
+{
+ char c;
+ char *place;
+
+ optarg = NULL;
+
+ if (!scan || *scan == '\0') {
+ if (optind == 0)
+ optind++;
+
+ if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
+ return EOF;
+ if (argv[optind][1] == '-' && argv[optind][2] == '\0') {
+ optind++;
+ return EOF;
+ }
+
+ scan = argv[optind]+1;
+ optind++;
+ }
+
+ c = *scan++;
+ place = strchr(optstring, c);
+
+ if (!place || c == ':') {
+ fprintf(stderr, "%s: unknown option -%c\n", argv[0], c);
+ return '?';
+ }
+
+ place++;
+ if (*place == ':') {
+ if (*scan != '\0') {
+ optarg = scan;
+ scan = NULL;
+ } else if( optind < argc ) {
+ optarg = argv[optind];
+ optind++;
+ } else {
+ fprintf(stderr, "%s: option requires argument -%c\n", argv[0], c);
+ return ':';
+ }
+ }
+
+ return c;
+}
diff --git a/source/fitz/halftone.c b/source/fitz/halftone.c
new file mode 100644
index 00000000..15aebead
--- /dev/null
+++ b/source/fitz/halftone.c
@@ -0,0 +1,202 @@
+#include "mupdf/fitz.h"
+
+fz_halftone *
+fz_new_halftone(fz_context *ctx, int comps)
+{
+ fz_halftone *ht;
+ int i;
+
+ ht = fz_malloc(ctx, sizeof(fz_halftone) + (comps-1)*sizeof(fz_pixmap *));
+ ht->refs = 1;
+ ht->n = comps;
+ for (i = 0; i < comps; i++)
+ ht->comp[i] = NULL;
+
+ return ht;
+}
+
+fz_halftone *
+fz_keep_halftone(fz_context *ctx, fz_halftone *ht)
+{
+ if (ht)
+ ht->refs++;
+ return ht;
+}
+
+void
+fz_drop_halftone(fz_context *ctx, fz_halftone *ht)
+{
+ int i;
+
+ if (!ht || --ht->refs != 0)
+ return;
+ for (i = 0; i < ht->n; i++)
+ fz_drop_pixmap(ctx, ht->comp[i]);
+ fz_free(ctx, ht);
+}
+
+/* Default mono halftone, lifted from Ghostscript. */
+/* The 0x00 entry has been changed to 0x01 to avoid problems with white
+ * pixels appearing in the output; as we use < 0 should not appear in the
+ * array. I think that gs scales this slighly and hence never actually uses
+ * the raw values here. */
+static unsigned char mono_ht[] =
+{
+ 0x0E, 0x8E, 0x2E, 0xAE, 0x06, 0x86, 0x26, 0xA6, 0x0C, 0x8C, 0x2C, 0xAC, 0x04, 0x84, 0x24, 0xA4,
+ 0xCE, 0x4E, 0xEE, 0x6E, 0xC6, 0x46, 0xE6, 0x66, 0xCC, 0x4C, 0xEC, 0x6C, 0xC4, 0x44, 0xE4, 0x64,
+ 0x3E, 0xBE, 0x1E, 0x9E, 0x36, 0xB6, 0x16, 0x96, 0x3C, 0xBC, 0x1C, 0x9C, 0x34, 0xB4, 0x14, 0x94,
+ 0xFE, 0x7E, 0xDE, 0x5E, 0xF6, 0x76, 0xD6, 0x56, 0xFC, 0x7C, 0xDC, 0x5C, 0xF4, 0x74, 0xD4, 0x54,
+ 0x01, 0x81, 0x21, 0xA1, 0x09, 0x89, 0x29, 0xA9, 0x03, 0x83, 0x23, 0xA3, 0x0B, 0x8B, 0x2B, 0xAB,
+ 0xC1, 0x41, 0xE1, 0x61, 0xC9, 0x49, 0xE9, 0x69, 0xC3, 0x43, 0xE3, 0x63, 0xCB, 0x4B, 0xEB, 0x6B,
+ 0x31, 0xB1, 0x11, 0x91, 0x39, 0xB9, 0x19, 0x99, 0x33, 0xB3, 0x13, 0x93, 0x3B, 0xBB, 0x1B, 0x9B,
+ 0xF1, 0x71, 0xD1, 0x51, 0xF9, 0x79, 0xD9, 0x59, 0xF3, 0x73, 0xD3, 0x53, 0xFB, 0x7B, 0xDB, 0x5B,
+ 0x0D, 0x8D, 0x2D, 0xAD, 0x05, 0x85, 0x25, 0xA5, 0x0F, 0x8F, 0x2F, 0xAF, 0x07, 0x87, 0x27, 0xA7,
+ 0xCD, 0x4D, 0xED, 0x6D, 0xC5, 0x45, 0xE5, 0x65, 0xCF, 0x4F, 0xEF, 0x6F, 0xC7, 0x47, 0xE7, 0x67,
+ 0x3D, 0xBD, 0x1D, 0x9D, 0x35, 0xB5, 0x15, 0x95, 0x3F, 0xBF, 0x1F, 0x9F, 0x37, 0xB7, 0x17, 0x97,
+ 0xFD, 0x7D, 0xDD, 0x5D, 0xF5, 0x75, 0xD5, 0x55, 0xFF, 0x7F, 0xDF, 0x5F, 0xF7, 0x77, 0xD7, 0x57,
+ 0x02, 0x82, 0x22, 0xA2, 0x0A, 0x8A, 0x2A, 0xAA, 0x01 /*0x00*/, 0x80, 0x20, 0xA0, 0x08, 0x88, 0x28, 0xA8,
+ 0xC2, 0x42, 0xE2, 0x62, 0xCA, 0x4A, 0xEA, 0x6A, 0xC0, 0x40, 0xE0, 0x60, 0xC8, 0x48, 0xE8, 0x68,
+ 0x32, 0xB2, 0x12, 0x92, 0x3A, 0xBA, 0x1A, 0x9A, 0x30, 0xB0, 0x10, 0x90, 0x38, 0xB8, 0x18, 0x98,
+ 0xF2, 0x72, 0xD2, 0x52, 0xFA, 0x7A, 0xDA, 0x5A, 0xF0, 0x70, 0xD0, 0x50, 0xF8, 0x78, 0xD8, 0x58
+};
+
+fz_halftone *fz_default_halftone(fz_context *ctx, int num_comps)
+{
+ fz_halftone *ht = fz_new_halftone(ctx, num_comps);
+ assert(num_comps == 1); /* Only support 1 component for now */
+ ht->comp[0] = fz_new_pixmap_with_data(ctx, NULL, 16, 16, mono_ht);
+ return ht;
+}
+
+/* Finally, code to actually perform halftoning. */
+static void make_ht_line(unsigned char *buf, fz_halftone *ht, int x, int y, int w)
+{
+ /* FIXME: There is a potential optimisation here; in the case where
+ * the LCM of the halftone tile widths is smaller than w, we could
+ * form just one 'LCM' run, then copy it repeatedly.
+ */
+ int k, n;
+ n = ht->n;
+ for (k = 0; k < n; k++)
+ {
+ fz_pixmap *tile = ht->comp[k];
+ unsigned char *b = buf++;
+ unsigned char *t;
+ unsigned char *tbase;
+ int px = x + tile->x;
+ int py = y + tile->y;
+ int tw = tile->w;
+ int th = tile->h;
+ int w2 = w;
+ int len;
+ px = px % tw;
+ if (px < 0)
+ px += tw;
+ py = py % th;
+ if (py < 0)
+ py += th;
+
+ assert(tile->n == 1);
+
+ /* Left hand section; from x to tile width */
+ tbase = tile->samples + (unsigned int)(py * tw);
+ t = tbase + px;
+ len = tw - px;
+ if (len > w2)
+ len = w2;
+ w2 -= len;
+ while (len--)
+ {
+ *b = *t++;
+ b += n;
+ }
+
+ /* Centre section - complete copies */
+ w2 -= tw;
+ while (w2 >= 0)
+ {
+ len = tw;
+ t = tbase;
+ while (len--)
+ {
+ *b = *t++;
+ b += n;
+ }
+ w2 -= tw;
+ }
+ w2 += tw;
+
+ /* Right hand section - stragglers */
+ t = tbase;
+ while (w2--)
+ {
+ *b = *t++;
+ b += n;
+ }
+ }
+}
+
+/* Inner mono thresholding code */
+static void do_threshold_1(unsigned char *ht_line, unsigned char *pixmap, unsigned char *out, int w)
+{
+ int bit = 0x80;
+ int h = 0;
+
+ do
+ {
+ if (*pixmap < *ht_line++)
+ h |= bit;
+ pixmap += 2; /* Skip the alpha */
+ bit >>= 1;
+ if (bit == 0)
+ {
+ *out++ = h;
+ h = 0;
+ bit = 0x80;
+ }
+
+ }
+ while (--w);
+ if (bit != 0x80)
+ *out++ = h;
+}
+
+fz_bitmap *fz_halftone_pixmap(fz_context *ctx, fz_pixmap *pix, fz_halftone *ht)
+{
+ fz_bitmap *out;
+ unsigned char *ht_line, *o, *p;
+ int w, h, x, y, n, pstride, ostride;
+ fz_halftone *ht_orig = ht;
+
+ if (!pix)
+ return NULL;
+
+ assert(pix->n == 2); /* Mono + Alpha */
+
+ n = pix->n-1; /* Remove alpha */
+ if (ht == NULL)
+ {
+ ht = fz_default_halftone(ctx, n);
+ }
+ ht_line = fz_malloc(ctx, pix->w * n);
+ out = fz_new_bitmap(ctx, pix->w, pix->h, n, pix->xres, pix->yres);
+ o = out->samples;
+ p = pix->samples;
+
+ h = pix->h;
+ x = pix->x;
+ y = pix->y;
+ w = pix->w;
+ ostride = out->stride;
+ pstride = pix->w * pix->n;
+ while (h--)
+ {
+ make_ht_line(ht_line, ht, x, y++, w);
+ do_threshold_1(ht_line, p, o, w);
+ o += ostride;
+ p += pstride;
+ }
+ if (!ht_orig)
+ fz_drop_halftone(ctx, ht);
+ return out;
+}
diff --git a/source/fitz/hash.c b/source/fitz/hash.c
new file mode 100644
index 00000000..624cc305
--- /dev/null
+++ b/source/fitz/hash.c
@@ -0,0 +1,357 @@
+#include "mupdf/fitz.h"
+
+/*
+Simple hashtable with open addressing linear probe.
+Unlike text book examples, removing entries works
+correctly in this implementation, so it wont start
+exhibiting bad behaviour if entries are inserted
+and removed frequently.
+*/
+
+enum { MAX_KEY_LEN = 48 };
+typedef struct fz_hash_entry_s fz_hash_entry;
+
+struct fz_hash_entry_s
+{
+ unsigned char key[MAX_KEY_LEN];
+ void *val;
+};
+
+struct fz_hash_table_s
+{
+ int keylen;
+ int size;
+ int load;
+ int lock; /* -1 or the lock used to protect this hash table */
+ fz_hash_entry *ents;
+};
+
+static unsigned hash(unsigned char *s, int len)
+{
+ unsigned val = 0;
+ int i;
+ for (i = 0; i < len; i++)
+ {
+ val += s[i];
+ val += (val << 10);
+ val ^= (val >> 6);
+ }
+ val += (val << 3);
+ val ^= (val >> 11);
+ val += (val << 15);
+ return val;
+}
+
+fz_hash_table *
+fz_new_hash_table(fz_context *ctx, int initialsize, int keylen, int lock)
+{
+ fz_hash_table *table;
+
+ assert(keylen <= MAX_KEY_LEN);
+
+ table = fz_malloc_struct(ctx, fz_hash_table);
+ table->keylen = keylen;
+ table->size = initialsize;
+ table->load = 0;
+ table->lock = lock;
+ fz_try(ctx)
+ {
+ table->ents = fz_malloc_array(ctx, table->size, sizeof(fz_hash_entry));
+ memset(table->ents, 0, sizeof(fz_hash_entry) * table->size);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, table);
+ fz_rethrow(ctx);
+ }
+
+ return table;
+}
+
+void
+fz_empty_hash(fz_context *ctx, fz_hash_table *table)
+{
+ table->load = 0;
+ memset(table->ents, 0, sizeof(fz_hash_entry) * table->size);
+}
+
+int
+fz_hash_len(fz_context *ctx, fz_hash_table *table)
+{
+ return table->size;
+}
+
+void *
+fz_hash_get_key(fz_context *ctx, fz_hash_table *table, int idx)
+{
+ return table->ents[idx].key;
+}
+
+void *
+fz_hash_get_val(fz_context *ctx, fz_hash_table *table, int idx)
+{
+ return table->ents[idx].val;
+}
+
+void
+fz_free_hash(fz_context *ctx, fz_hash_table *table)
+{
+ fz_free(ctx, table->ents);
+ fz_free(ctx, table);
+}
+
+static void *
+do_hash_insert(fz_context *ctx, fz_hash_table *table, void *key, void *val, unsigned *pos_ptr)
+{
+ fz_hash_entry *ents;
+ unsigned size;
+ unsigned pos;
+
+ ents = table->ents;
+ size = table->size;
+ pos = hash(key, table->keylen) % size;
+
+ if (table->lock >= 0)
+ fz_assert_lock_held(ctx, table->lock);
+
+ while (1)
+ {
+ if (!ents[pos].val)
+ {
+ memcpy(ents[pos].key, key, table->keylen);
+ ents[pos].val = val;
+ table->load ++;
+ if (pos_ptr)
+ *pos_ptr = pos;
+ return NULL;
+ }
+
+ if (memcmp(key, ents[pos].key, table->keylen) == 0)
+ {
+ /* This is legal, but should happen rarely in the non
+ * pos_ptr case. */
+ if (pos_ptr)
+ *pos_ptr = pos;
+ else
+ fz_warn(ctx, "assert: overwrite hash slot");
+ return ents[pos].val;
+ }
+
+ pos = (pos + 1) % size;
+ }
+}
+
+/* Entered with the lock taken, held throughout and at exit, UNLESS the lock
+ * is the alloc lock in which case it may be momentarily dropped. */
+static void
+fz_resize_hash(fz_context *ctx, fz_hash_table *table, int newsize)
+{
+ fz_hash_entry *oldents = table->ents;
+ fz_hash_entry *newents;
+ int oldsize = table->size;
+ int oldload = table->load;
+ int i;
+
+ if (newsize < oldload * 8 / 10)
+ {
+ fz_warn(ctx, "assert: resize hash too small");
+ return;
+ }
+
+ if (table->lock == FZ_LOCK_ALLOC)
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ newents = fz_malloc_array_no_throw(ctx, newsize, sizeof(fz_hash_entry));
+ if (table->lock == FZ_LOCK_ALLOC)
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (table->lock >= 0)
+ {
+ if (table->size >= newsize)
+ {
+ /* Someone else fixed it before we could lock! */
+ if (table->lock == FZ_LOCK_ALLOC)
+ fz_unlock(ctx, table->lock);
+ fz_free(ctx, newents);
+ if (table->lock == FZ_LOCK_ALLOC)
+ fz_lock(ctx, table->lock);
+ return;
+ }
+ }
+ if (newents == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "hash table resize failed; out of memory (%d entries)", newsize);
+ table->ents = newents;
+ memset(table->ents, 0, sizeof(fz_hash_entry) * newsize);
+ table->size = newsize;
+ table->load = 0;
+
+ for (i = 0; i < oldsize; i++)
+ {
+ if (oldents[i].val)
+ {
+ do_hash_insert(ctx, table, oldents[i].key, oldents[i].val, NULL);
+ }
+ }
+
+ if (table->lock == FZ_LOCK_ALLOC)
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ fz_free(ctx, oldents);
+ if (table->lock == FZ_LOCK_ALLOC)
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+}
+
+void *
+fz_hash_find(fz_context *ctx, fz_hash_table *table, void *key)
+{
+ fz_hash_entry *ents = table->ents;
+ unsigned size = table->size;
+ unsigned pos = hash(key, table->keylen) % size;
+
+ if (table->lock >= 0)
+ fz_assert_lock_held(ctx, table->lock);
+
+ while (1)
+ {
+ if (!ents[pos].val)
+ return NULL;
+
+ if (memcmp(key, ents[pos].key, table->keylen) == 0)
+ return ents[pos].val;
+
+ pos = (pos + 1) % size;
+ }
+}
+
+void *
+fz_hash_insert(fz_context *ctx, fz_hash_table *table, void *key, void *val)
+{
+ if (table->load > table->size * 8 / 10)
+ {
+ fz_resize_hash(ctx, table, table->size * 2);
+ }
+
+ return do_hash_insert(ctx, table, key, val, NULL);
+}
+
+void *
+fz_hash_insert_with_pos(fz_context *ctx, fz_hash_table *table, void *key, void *val, unsigned *pos)
+{
+ if (table->load > table->size * 8 / 10)
+ {
+ fz_resize_hash(ctx, table, table->size * 2);
+ }
+
+ return do_hash_insert(ctx, table, key, val, pos);
+}
+
+static void
+do_removal(fz_context *ctx, fz_hash_table *table, void *key, unsigned hole)
+{
+ fz_hash_entry *ents = table->ents;
+ unsigned size = table->size;
+ unsigned look, code;
+
+ if (table->lock >= 0)
+ fz_assert_lock_held(ctx, table->lock);
+
+ ents[hole].val = NULL;
+
+ look = hole + 1;
+ if (look == size)
+ look = 0;
+
+ while (ents[look].val)
+ {
+ code = hash(ents[look].key, table->keylen) % size;
+ if ((code <= hole && hole < look) ||
+ (look < code && code <= hole) ||
+ (hole < look && look < code))
+ {
+ ents[hole] = ents[look];
+ ents[look].val = NULL;
+ hole = look;
+ }
+
+ look++;
+ if (look == size)
+ look = 0;
+ }
+
+ table->load --;
+}
+
+void
+fz_hash_remove(fz_context *ctx, fz_hash_table *table, void *key)
+{
+ fz_hash_entry *ents = table->ents;
+ unsigned size = table->size;
+ unsigned pos = hash(key, table->keylen) % size;
+
+ if (table->lock >= 0)
+ fz_assert_lock_held(ctx, table->lock);
+
+ while (1)
+ {
+ if (!ents[pos].val)
+ {
+ fz_warn(ctx, "assert: remove non-existent hash entry");
+ return;
+ }
+
+ if (memcmp(key, ents[pos].key, table->keylen) == 0)
+ {
+ do_removal(ctx, table, key, pos);
+ return;
+ }
+
+ pos++;
+ if (pos == size)
+ pos = 0;
+ }
+}
+
+void
+fz_hash_remove_fast(fz_context *ctx, fz_hash_table *table, void *key, unsigned pos)
+{
+ fz_hash_entry *ents = table->ents;
+
+ if (ents[pos].val == NULL || memcmp(key, ents[pos].key, table->keylen) != 0)
+ {
+ /* The value isn't there, or the key didn't match! The table
+ * must have been rebuilt (or the contents moved) in the
+ * meantime. Do the removal the slow way. */
+ fz_hash_remove(ctx, table, key);
+ }
+ else
+ do_removal(ctx, table, key, pos);
+}
+
+#ifndef NDEBUG
+void
+fz_print_hash(fz_context *ctx, FILE *out, fz_hash_table *table)
+{
+ fz_print_hash_details(ctx, out, table, NULL);
+}
+
+void
+fz_print_hash_details(fz_context *ctx, FILE *out, fz_hash_table *table, void (*details)(FILE *,void*))
+{
+ int i, k;
+
+ fprintf(out, "cache load %d / %d\n", table->load, table->size);
+
+ for (i = 0; i < table->size; i++)
+ {
+ if (!table->ents[i].val)
+ fprintf(out, "table % 4d: empty\n", i);
+ else
+ {
+ fprintf(out, "table % 4d: key=", i);
+ for (k = 0; k < MAX_KEY_LEN; k++)
+ fprintf(out, "%02x", ((char*)table->ents[i].key)[k]);
+ if (details)
+ details(out, table->ents[i].val);
+ else
+ fprintf(out, " val=$%p\n", table->ents[i].val);
+ }
+ }
+}
+#endif
diff --git a/source/fitz/image.c b/source/fitz/image.c
new file mode 100644
index 00000000..73b4bc28
--- /dev/null
+++ b/source/fitz/image.c
@@ -0,0 +1,493 @@
+#include "mupdf/fitz.h"
+
+fz_pixmap *
+fz_image_to_pixmap(fz_context *ctx, fz_image *image, int w, int h)
+{
+ if (image == NULL)
+ return NULL;
+ return image->get_pixmap(ctx, image, w, h);
+}
+
+fz_image *
+fz_keep_image(fz_context *ctx, fz_image *image)
+{
+ return (fz_image *)fz_keep_storable(ctx, &image->storable);
+}
+
+void
+fz_drop_image(fz_context *ctx, fz_image *image)
+{
+ fz_drop_storable(ctx, &image->storable);
+}
+
+typedef struct fz_image_key_s fz_image_key;
+
+struct fz_image_key_s {
+ int refs;
+ fz_image *image;
+ int l2factor;
+};
+
+static int
+fz_make_hash_image_key(fz_store_hash *hash, void *key_)
+{
+ fz_image_key *key = (fz_image_key *)key_;
+
+ hash->u.pi.ptr = key->image;
+ hash->u.pi.i = key->l2factor;
+ return 1;
+}
+
+static void *
+fz_keep_image_key(fz_context *ctx, void *key_)
+{
+ fz_image_key *key = (fz_image_key *)key_;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ key->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ return (void *)key;
+}
+
+static void
+fz_drop_image_key(fz_context *ctx, void *key_)
+{
+ fz_image_key *key = (fz_image_key *)key_;
+ int drop;
+
+ if (key == NULL)
+ return;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = --key->refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop == 0)
+ {
+ fz_drop_image(ctx, key->image);
+ fz_free(ctx, key);
+ }
+}
+
+static int
+fz_cmp_image_key(void *k0_, void *k1_)
+{
+ fz_image_key *k0 = (fz_image_key *)k0_;
+ fz_image_key *k1 = (fz_image_key *)k1_;
+
+ return k0->image == k1->image && k0->l2factor == k1->l2factor;
+}
+
+#ifndef NDEBUG
+static void
+fz_debug_image(FILE *out, void *key_)
+{
+ fz_image_key *key = (fz_image_key *)key_;
+
+ fprintf(out, "(image %d x %d sf=%d) ", key->image->w, key->image->h, key->l2factor);
+}
+#endif
+
+static fz_store_type fz_image_store_type =
+{
+ fz_make_hash_image_key,
+ fz_keep_image_key,
+ fz_drop_image_key,
+ fz_cmp_image_key,
+#ifndef NDEBUG
+ fz_debug_image
+#endif
+};
+
+static void
+fz_mask_color_key(fz_pixmap *pix, int n, int *colorkey)
+{
+ unsigned char *p = pix->samples;
+ int len = pix->w * pix->h;
+ int k, t;
+ while (len--)
+ {
+ t = 1;
+ for (k = 0; k < n; k++)
+ if (p[k] < colorkey[k * 2] || p[k] > colorkey[k * 2 + 1])
+ t = 0;
+ if (t)
+ for (k = 0; k < pix->n; k++)
+ p[k] = 0;
+ p += pix->n;
+ }
+}
+
+fz_pixmap *
+fz_decomp_image_from_stream(fz_context *ctx, fz_stream *stm, fz_image *image, int in_line, int indexed, int l2factor, int native_l2factor)
+{
+ fz_pixmap *tile = NULL;
+ int stride, len, i;
+ unsigned char *samples = NULL;
+ int f = 1<<native_l2factor;
+ int w = (image->w + f-1) >> native_l2factor;
+ int h = (image->h + f-1) >> native_l2factor;
+
+ fz_var(tile);
+ fz_var(samples);
+
+ fz_try(ctx)
+ {
+ tile = fz_new_pixmap(ctx, image->colorspace, w, h);
+ tile->interpolate = image->interpolate;
+
+ stride = (w * image->n * image->bpc + 7) / 8;
+
+ samples = fz_malloc_array(ctx, h, stride);
+
+ len = fz_read(stm, samples, h * stride);
+ if (len < 0)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot read image data");
+ }
+
+ /* Make sure we read the EOF marker (for inline images only) */
+ if (in_line)
+ {
+ unsigned char tbuf[512];
+ fz_try(ctx)
+ {
+ int tlen = fz_read(stm, tbuf, sizeof tbuf);
+ if (tlen > 0)
+ fz_warn(ctx, "ignoring garbage at end of image");
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater? */
+ fz_warn(ctx, "ignoring error at end of image");
+ }
+ }
+
+ /* Pad truncated images */
+ if (len < stride * h)
+ {
+ fz_warn(ctx, "padding truncated image");
+ memset(samples + len, 0, stride * h - len);
+ }
+
+ /* Invert 1-bit image masks */
+ if (image->imagemask)
+ {
+ /* 0=opaque and 1=transparent so we need to invert */
+ unsigned char *p = samples;
+ len = h * stride;
+ for (i = 0; i < len; i++)
+ p[i] = ~p[i];
+ }
+
+ fz_unpack_tile(tile, samples, image->n, image->bpc, stride, indexed);
+
+ fz_free(ctx, samples);
+ samples = NULL;
+
+ if (image->usecolorkey)
+ fz_mask_color_key(tile, image->n, image->colorkey);
+
+ if (indexed)
+ {
+ fz_pixmap *conv;
+ fz_decode_indexed_tile(tile, image->decode, (1 << image->bpc) - 1);
+ conv = fz_expand_indexed_pixmap(ctx, tile);
+ fz_drop_pixmap(ctx, tile);
+ tile = conv;
+ }
+ else
+ {
+ fz_decode_tile(tile, image->decode);
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stm);
+ }
+ fz_catch(ctx)
+ {
+ if (tile)
+ fz_drop_pixmap(ctx, tile);
+ fz_free(ctx, samples);
+
+ fz_rethrow(ctx);
+ }
+
+ /* Now apply any extra subsampling required */
+ if (l2factor - native_l2factor > 0)
+ {
+ if (l2factor - native_l2factor > 8)
+ l2factor = native_l2factor + 8;
+ fz_subsample_pixmap(ctx, tile, l2factor - native_l2factor);
+ }
+
+ return tile;
+}
+
+void
+fz_free_image(fz_context *ctx, fz_storable *image_)
+{
+ fz_image *image = (fz_image *)image_;
+
+ if (image == NULL)
+ return;
+ fz_drop_pixmap(ctx, image->tile);
+ fz_free_compressed_buffer(ctx, image->buffer);
+ fz_drop_colorspace(ctx, image->colorspace);
+ fz_drop_image(ctx, image->mask);
+ fz_free(ctx, image);
+}
+
+fz_pixmap *
+fz_image_get_pixmap(fz_context *ctx, fz_image *image, int w, int h)
+{
+ fz_pixmap *tile;
+ fz_stream *stm;
+ int l2factor;
+ fz_image_key key;
+ int native_l2factor;
+ int indexed;
+ fz_image_key *keyp;
+
+ /* Check for 'simple' images which are just pixmaps */
+ if (image->buffer == NULL)
+ {
+ tile = image->tile;
+ if (!tile)
+ return NULL;
+ return fz_keep_pixmap(ctx, tile); /* That's all we can give you! */
+ }
+
+ /* Ensure our expectations for tile size are reasonable */
+ if (w > image->w)
+ w = image->w;
+ if (h > image->h)
+ h = image->h;
+
+ /* What is our ideal factor? */
+ if (w == 0 || h == 0)
+ l2factor = 0;
+ else
+ for (l2factor=0; image->w>>(l2factor+1) >= w && image->h>>(l2factor+1) >= h && l2factor < 8; l2factor++);
+
+ /* Can we find any suitable tiles in the cache? */
+ key.refs = 1;
+ key.image = image;
+ key.l2factor = l2factor;
+ do
+ {
+ tile = fz_find_item(ctx, fz_free_pixmap_imp, &key, &fz_image_store_type);
+ if (tile)
+ return tile;
+ key.l2factor--;
+ }
+ while (key.l2factor >= 0);
+
+ /* We need to make a new one. */
+ /* First check for ones that we can't decode using streams */
+ switch (image->buffer->params.type)
+ {
+ case FZ_IMAGE_PNG:
+ tile = fz_load_png(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
+ break;
+ case FZ_IMAGE_TIFF:
+ tile = fz_load_tiff(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
+ break;
+ default:
+ native_l2factor = l2factor;
+ stm = fz_open_image_decomp_stream(ctx, image->buffer, &native_l2factor);
+
+ indexed = fz_colorspace_is_indexed(image->colorspace);
+ tile = fz_decomp_image_from_stream(ctx, stm, image, 0, indexed, l2factor, native_l2factor);
+ break;
+ }
+
+ /* Now we try to cache the pixmap. Any failure here will just result
+ * in us not caching. */
+ fz_var(keyp);
+ fz_try(ctx)
+ {
+ fz_pixmap *existing_tile;
+
+ keyp = fz_malloc_struct(ctx, fz_image_key);
+ keyp->refs = 1;
+ keyp->image = fz_keep_image(ctx, image);
+ keyp->l2factor = l2factor;
+ existing_tile = fz_store_item(ctx, keyp, tile, fz_pixmap_size(ctx, tile), &fz_image_store_type);
+ if (existing_tile)
+ {
+ /* We already have a tile. This must have been produced by a
+ * racing thread. We'll throw away ours and use that one. */
+ fz_drop_pixmap(ctx, tile);
+ tile = existing_tile;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_drop_image_key(ctx, keyp);
+ }
+ fz_catch(ctx)
+ {
+ /* Do nothing */
+ }
+
+ return tile;
+}
+
+fz_image *
+fz_new_image_from_pixmap(fz_context *ctx, fz_pixmap *pixmap, fz_image *mask)
+{
+ fz_image *image;
+
+ assert(mask == NULL || mask->mask == NULL);
+
+ fz_try(ctx)
+ {
+ image = fz_malloc_struct(ctx, fz_image);
+ FZ_INIT_STORABLE(image, 1, fz_free_image);
+ image->w = pixmap->w;
+ image->h = pixmap->h;
+ image->n = pixmap->n;
+ image->colorspace = pixmap->colorspace;
+ image->bpc = 8;
+ image->buffer = NULL;
+ image->get_pixmap = fz_image_get_pixmap;
+ image->xres = pixmap->xres;
+ image->yres = pixmap->yres;
+ image->tile = pixmap;
+ image->mask = mask;
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_image(ctx, mask);
+ fz_rethrow(ctx);
+ }
+ return image;
+}
+
+fz_image *
+fz_new_image(fz_context *ctx, int w, int h, int bpc, fz_colorspace *colorspace,
+ int xres, int yres, int interpolate, int imagemask, float *decode,
+ int *colorkey, fz_compressed_buffer *buffer, fz_image *mask)
+{
+ fz_image *image;
+
+ assert(mask == NULL || mask->mask == NULL);
+
+ fz_try(ctx)
+ {
+ image = fz_malloc_struct(ctx, fz_image);
+ FZ_INIT_STORABLE(image, 1, fz_free_image);
+ image->get_pixmap = fz_image_get_pixmap;
+ image->w = w;
+ image->h = h;
+ image->xres = xres;
+ image->yres = yres;
+ image->bpc = bpc;
+ image->n = (colorspace ? colorspace->n : 1);
+ image->colorspace = colorspace;
+ image->interpolate = interpolate;
+ image->imagemask = imagemask;
+ image->usecolorkey = (colorkey != NULL);
+ if (colorkey)
+ memcpy(image->colorkey, colorkey, sizeof(int)*image->n*2);
+ if (decode)
+ memcpy(image->decode, decode, sizeof(float)*image->n*2);
+ else
+ {
+ float maxval = fz_colorspace_is_indexed(colorspace) ? (1 << bpc) - 1 : 1;
+ int i;
+ for (i = 0; i < image->n; i++)
+ {
+ image->decode[2*i] = 0;
+ image->decode[2*i+1] = maxval;
+ }
+ }
+ image->mask = mask;
+ image->buffer = buffer;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_compressed_buffer(ctx, buffer);
+ fz_rethrow(ctx);
+ }
+
+ return image;
+}
+
+fz_image *
+fz_new_image_from_data(fz_context *ctx, unsigned char *data, int len)
+{
+ fz_buffer *buffer = NULL;
+ fz_image *image;
+
+ fz_var(buffer);
+ fz_var(data);
+
+ fz_try(ctx)
+ {
+ buffer = fz_new_buffer_from_data(ctx, data, len);
+ data = NULL;
+ image = fz_new_image_from_buffer(ctx, buffer);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, buffer);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, data);
+ fz_rethrow(ctx);
+ }
+
+ return image;
+}
+
+fz_image *
+fz_new_image_from_buffer(fz_context *ctx, fz_buffer *buffer)
+{
+ fz_compressed_buffer *bc = NULL;
+ int w, h, xres, yres;
+ fz_colorspace *cspace;
+ int len = buffer->len;
+ unsigned char *buf = buffer->data;
+
+ fz_var(bc);
+
+ fz_try(ctx)
+ {
+ if (len < 8)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown image file format");
+
+ bc = fz_malloc_struct(ctx, fz_compressed_buffer);
+ bc->buffer = fz_keep_buffer(ctx, buffer);
+
+ if (buf[0] == 0xff && buf[1] == 0xd8)
+ {
+ bc->params.type = FZ_IMAGE_JPEG;
+ bc->params.u.jpeg.color_transform = -1;
+ fz_load_jpeg_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
+ }
+ else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
+ {
+ bc->params.type = FZ_IMAGE_PNG;
+ fz_load_png_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
+ }
+ else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "JPEG-XR codec is not available");
+ else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0)
+ {
+ bc->params.type = FZ_IMAGE_TIFF;
+ fz_load_tiff_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown image file format");
+ }
+ fz_catch(ctx)
+ {
+ fz_free_compressed_buffer(ctx, bc);
+ fz_rethrow(ctx);
+ }
+
+ return fz_new_image(ctx, w, h, 8, cspace, xres, yres, 0, 0, NULL, NULL, bc, NULL);
+}
diff --git a/source/fitz/link.c b/source/fitz/link.c
new file mode 100644
index 00000000..30dca222
--- /dev/null
+++ b/source/fitz/link.c
@@ -0,0 +1,65 @@
+#include "mupdf/fitz.h"
+
+void
+fz_free_link_dest(fz_context *ctx, fz_link_dest *dest)
+{
+ switch (dest->kind)
+ {
+ case FZ_LINK_NONE:
+ case FZ_LINK_GOTO:
+ break;
+ case FZ_LINK_URI:
+ fz_free(ctx, dest->ld.uri.uri);
+ break;
+ case FZ_LINK_LAUNCH:
+ fz_free(ctx, dest->ld.launch.file_spec);
+ break;
+ case FZ_LINK_NAMED:
+ fz_free(ctx, dest->ld.named.named);
+ break;
+ case FZ_LINK_GOTOR:
+ fz_free(ctx, dest->ld.gotor.file_spec);
+ break;
+ }
+}
+
+fz_link *
+fz_new_link(fz_context *ctx, const fz_rect *bbox, fz_link_dest dest)
+{
+ fz_link *link;
+
+ fz_try(ctx)
+ {
+ link = fz_malloc_struct(ctx, fz_link);
+ link->refs = 1;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_link_dest(ctx, &dest);
+ fz_rethrow(ctx);
+ }
+ link->dest = dest;
+ link->rect = *bbox;
+ link->next = NULL;
+ return link;
+}
+
+fz_link *
+fz_keep_link(fz_context *ctx, fz_link *link)
+{
+ if (link)
+ link->refs++;
+ return link;
+}
+
+void
+fz_drop_link(fz_context *ctx, fz_link *link)
+{
+ while (link && --link->refs == 0)
+ {
+ fz_link *next = link->next;
+ fz_free_link_dest(ctx, &link->dest);
+ fz_free(ctx, link);
+ link = next;
+ }
+}
diff --git a/source/fitz/list-device.c b/source/fitz/list-device.c
new file mode 100644
index 00000000..6ffe165f
--- /dev/null
+++ b/source/fitz/list-device.c
@@ -0,0 +1,851 @@
+#include "mupdf/fitz.h"
+
+typedef struct fz_display_node_s fz_display_node;
+
+#define STACK_SIZE 96
+
+typedef enum fz_display_command_e
+{
+ FZ_CMD_BEGIN_PAGE,
+ FZ_CMD_END_PAGE,
+ FZ_CMD_FILL_PATH,
+ FZ_CMD_STROKE_PATH,
+ FZ_CMD_CLIP_PATH,
+ FZ_CMD_CLIP_STROKE_PATH,
+ FZ_CMD_FILL_TEXT,
+ FZ_CMD_STROKE_TEXT,
+ FZ_CMD_CLIP_TEXT,
+ FZ_CMD_CLIP_STROKE_TEXT,
+ FZ_CMD_IGNORE_TEXT,
+ FZ_CMD_FILL_SHADE,
+ FZ_CMD_FILL_IMAGE,
+ FZ_CMD_FILL_IMAGE_MASK,
+ FZ_CMD_CLIP_IMAGE_MASK,
+ FZ_CMD_POP_CLIP,
+ FZ_CMD_BEGIN_MASK,
+ FZ_CMD_END_MASK,
+ FZ_CMD_BEGIN_GROUP,
+ FZ_CMD_END_GROUP,
+ FZ_CMD_BEGIN_TILE,
+ FZ_CMD_END_TILE
+} fz_display_command;
+
+struct fz_display_node_s
+{
+ fz_display_command cmd;
+ fz_display_node *next;
+ fz_rect rect;
+ union {
+ fz_path *path;
+ fz_text *text;
+ fz_shade *shade;
+ fz_image *image;
+ int blendmode;
+ } item;
+ fz_stroke_state *stroke;
+ int flag; /* even_odd, accumulate, isolated/knockout... */
+ fz_matrix ctm;
+ fz_colorspace *colorspace;
+ float alpha;
+ float color[FZ_MAX_COLORS];
+};
+
+struct fz_display_list_s
+{
+ fz_storable storable;
+ fz_display_node *first;
+ fz_display_node *last;
+ int len;
+
+ int top;
+ struct {
+ fz_rect *update;
+ fz_rect rect;
+ } stack[STACK_SIZE];
+ int tiled;
+};
+
+enum { ISOLATED = 1, KNOCKOUT = 2 };
+
+static fz_display_node *
+fz_new_display_node(fz_context *ctx, fz_display_command cmd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_display_node *node;
+ int i;
+
+ node = fz_malloc_struct(ctx, fz_display_node);
+ node->cmd = cmd;
+ node->next = NULL;
+ node->rect = fz_empty_rect;
+ node->item.path = NULL;
+ node->stroke = NULL;
+ node->flag = (cmd == FZ_CMD_BEGIN_TILE ? fz_gen_id(ctx) : 0);
+ node->ctm = *ctm;
+ if (colorspace)
+ {
+ node->colorspace = fz_keep_colorspace(ctx, colorspace);
+ if (color)
+ {
+ for (i = 0; i < node->colorspace->n; i++)
+ node->color[i] = color[i];
+ }
+ }
+ else
+ {
+ node->colorspace = NULL;
+ }
+ node->alpha = alpha;
+
+ return node;
+}
+
+static void
+fz_append_display_node(fz_display_list *list, fz_display_node *node)
+{
+ switch (node->cmd)
+ {
+ case FZ_CMD_CLIP_PATH:
+ case FZ_CMD_CLIP_STROKE_PATH:
+ case FZ_CMD_CLIP_IMAGE_MASK:
+ if (list->top < STACK_SIZE)
+ {
+ list->stack[list->top].update = &node->rect;
+ list->stack[list->top].rect = fz_empty_rect;
+ }
+ list->top++;
+ break;
+ case FZ_CMD_END_MASK:
+ case FZ_CMD_CLIP_TEXT:
+ case FZ_CMD_CLIP_STROKE_TEXT:
+ if (list->top < STACK_SIZE)
+ {
+ list->stack[list->top].update = NULL;
+ list->stack[list->top].rect = fz_empty_rect;
+ }
+ list->top++;
+ break;
+ case FZ_CMD_BEGIN_TILE:
+ list->tiled++;
+ if (list->top > 0 && list->top <= STACK_SIZE)
+ {
+ list->stack[list->top-1].rect = fz_infinite_rect;
+ }
+ break;
+ case FZ_CMD_END_TILE:
+ list->tiled--;
+ break;
+ case FZ_CMD_END_GROUP:
+ break;
+ case FZ_CMD_POP_CLIP:
+ if (list->top > STACK_SIZE)
+ {
+ list->top--;
+ node->rect = fz_infinite_rect;
+ }
+ else if (list->top > 0)
+ {
+ fz_rect *update;
+ list->top--;
+ update = list->stack[list->top].update;
+ if (list->tiled == 0)
+ {
+ if (update)
+ {
+ fz_intersect_rect(update, &list->stack[list->top].rect);
+ node->rect = *update;
+ }
+ else
+ node->rect = list->stack[list->top].rect;
+ }
+ else
+ node->rect = fz_infinite_rect;
+ }
+ /* fallthrough */
+ default:
+ if (list->top > 0 && list->tiled == 0 && list->top <= STACK_SIZE)
+ fz_union_rect(&list->stack[list->top-1].rect, &node->rect);
+ break;
+ }
+ if (!list->first)
+ {
+ list->first = node;
+ list->last = node;
+ }
+ else
+ {
+ list->last->next = node;
+ list->last = node;
+ }
+ list->len++;
+}
+
+static void
+fz_free_display_node(fz_context *ctx, fz_display_node *node)
+{
+ switch (node->cmd)
+ {
+ case FZ_CMD_FILL_PATH:
+ case FZ_CMD_STROKE_PATH:
+ case FZ_CMD_CLIP_PATH:
+ case FZ_CMD_CLIP_STROKE_PATH:
+ fz_free_path(ctx, node->item.path);
+ break;
+ case FZ_CMD_FILL_TEXT:
+ case FZ_CMD_STROKE_TEXT:
+ case FZ_CMD_CLIP_TEXT:
+ case FZ_CMD_CLIP_STROKE_TEXT:
+ case FZ_CMD_IGNORE_TEXT:
+ fz_free_text(ctx, node->item.text);
+ break;
+ case FZ_CMD_FILL_SHADE:
+ fz_drop_shade(ctx, node->item.shade);
+ break;
+ case FZ_CMD_FILL_IMAGE:
+ case FZ_CMD_FILL_IMAGE_MASK:
+ case FZ_CMD_CLIP_IMAGE_MASK:
+ fz_drop_image(ctx, node->item.image);
+ break;
+ case FZ_CMD_POP_CLIP:
+ case FZ_CMD_BEGIN_MASK:
+ case FZ_CMD_END_MASK:
+ case FZ_CMD_BEGIN_GROUP:
+ case FZ_CMD_END_GROUP:
+ case FZ_CMD_BEGIN_TILE:
+ case FZ_CMD_END_TILE:
+ case FZ_CMD_BEGIN_PAGE:
+ case FZ_CMD_END_PAGE:
+ break;
+ }
+ if (node->stroke)
+ fz_drop_stroke_state(ctx, node->stroke);
+ if (node->colorspace)
+ fz_drop_colorspace(ctx, node->colorspace);
+ fz_free(ctx, node);
+}
+
+static void
+fz_list_begin_page(fz_device *dev, const fz_rect *mediabox, const fz_matrix *ctm)
+{
+ fz_context *ctx = dev->ctx;
+ fz_display_node *node = fz_new_display_node(ctx, FZ_CMD_BEGIN_PAGE, ctm, NULL, NULL, 0);
+ node->rect = *mediabox;
+ fz_transform_rect(&node->rect, ctm);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_end_page(fz_device *dev)
+{
+ fz_context *ctx = dev->ctx;
+ fz_display_node *node = fz_new_display_node(ctx, FZ_CMD_END_PAGE, &fz_identity, NULL, NULL, 0);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_fill_path(fz_device *dev, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_FILL_PATH, ctm, colorspace, color, alpha);
+ fz_try(ctx)
+ {
+ fz_bound_path(dev->ctx, path, NULL, ctm, &node->rect);
+ node->item.path = fz_clone_path(dev->ctx, path);
+ node->flag = even_odd;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke,
+ const fz_matrix *ctm, fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_STROKE_PATH, ctm, colorspace, color, alpha);
+ fz_try(ctx)
+ {
+ fz_bound_path(dev->ctx, path, stroke, ctm, &node->rect);
+ node->item.path = fz_clone_path(dev->ctx, path);
+ node->stroke = fz_keep_stroke_state(dev->ctx, stroke);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_CLIP_PATH, ctm, NULL, NULL, 0);
+ fz_try(ctx)
+ {
+ fz_bound_path(dev->ctx, path, NULL, ctm, &node->rect);
+ if (rect)
+ fz_intersect_rect(&node->rect, rect);
+ node->item.path = fz_clone_path(dev->ctx, path);
+ node->flag = even_odd;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_CLIP_STROKE_PATH, ctm, NULL, NULL, 0);
+ fz_try(ctx)
+ {
+ fz_bound_path(dev->ctx, path, stroke, ctm, &node->rect);
+ if (rect)
+ fz_intersect_rect(&node->rect, rect);
+ node->item.path = fz_clone_path(dev->ctx, path);
+ node->stroke = fz_keep_stroke_state(dev->ctx, stroke);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_FILL_TEXT, ctm, colorspace, color, alpha);
+ fz_try(ctx)
+ {
+ fz_bound_text(dev->ctx, text, NULL, ctm, &node->rect);
+ node->item.text = fz_clone_text(dev->ctx, text);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_STROKE_TEXT, ctm, colorspace, color, alpha);
+ node->item.text = NULL;
+ fz_try(ctx)
+ {
+ fz_bound_text(dev->ctx, text, stroke, ctm, &node->rect);
+ node->item.text = fz_clone_text(dev->ctx, text);
+ node->stroke = fz_keep_stroke_state(dev->ctx, stroke);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_CLIP_TEXT, ctm, NULL, NULL, 0);
+ fz_try(ctx)
+ {
+ fz_bound_text(dev->ctx, text, NULL, ctm, &node->rect);
+ node->item.text = fz_clone_text(dev->ctx, text);
+ node->flag = accumulate;
+ /* when accumulating, be conservative about culling */
+ if (accumulate)
+ node->rect = fz_infinite_rect;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_CLIP_STROKE_TEXT, ctm, NULL, NULL, 0);
+ fz_try(ctx)
+ {
+ fz_bound_text(dev->ctx, text, stroke, ctm, &node->rect);
+ node->item.text = fz_clone_text(dev->ctx, text);
+ node->stroke = fz_keep_stroke_state(dev->ctx, stroke);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_IGNORE_TEXT, ctm, NULL, NULL, 0);
+ fz_try(ctx)
+ {
+ fz_bound_text(dev->ctx, text, NULL, ctm, &node->rect);
+ node->item.text = fz_clone_text(dev->ctx, text);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_display_node(ctx, node);
+ fz_rethrow(ctx);
+ }
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_pop_clip(fz_device *dev)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_POP_CLIP, &fz_identity, NULL, NULL, 0);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_fill_shade(fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+ fz_display_node *node;
+ fz_context *ctx = dev->ctx;
+ node = fz_new_display_node(ctx, FZ_CMD_FILL_SHADE, ctm, NULL, NULL, alpha);
+ fz_bound_shade(ctx, shade, ctm, &node->rect);
+ node->item.shade = fz_keep_shade(ctx, shade);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_fill_image(fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_FILL_IMAGE, ctm, NULL, NULL, alpha);
+ node->rect = fz_unit_rect;
+ fz_transform_rect(&node->rect, ctm);
+ node->item.image = fz_keep_image(dev->ctx, image);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_FILL_IMAGE_MASK, ctm, colorspace, color, alpha);
+ node->rect = fz_unit_rect;
+ fz_transform_rect(&node->rect, ctm);
+ node->item.image = fz_keep_image(dev->ctx, image);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_clip_image_mask(fz_device *dev, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_CLIP_IMAGE_MASK, ctm, NULL, NULL, 0);
+ node->rect = fz_unit_rect;
+ fz_transform_rect(&node->rect, ctm);
+ if (rect)
+ fz_intersect_rect(&node->rect, rect);
+ node->item.image = fz_keep_image(dev->ctx, image);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_begin_mask(fz_device *dev, const fz_rect *rect, int luminosity, fz_colorspace *colorspace, float *color)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_BEGIN_MASK, &fz_identity, colorspace, color, 0);
+ node->rect = *rect;
+ node->flag = luminosity;
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_end_mask(fz_device *dev)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_END_MASK, &fz_identity, NULL, NULL, 0);
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_begin_group(fz_device *dev, const fz_rect *rect, int isolated, int knockout, int blendmode, float alpha)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_BEGIN_GROUP, &fz_identity, NULL, NULL, alpha);
+ node->rect = *rect;
+ node->item.blendmode = blendmode;
+ node->flag |= isolated ? ISOLATED : 0;
+ node->flag |= knockout ? KNOCKOUT : 0;
+ fz_append_display_node(dev->user, node);
+}
+
+static void
+fz_list_end_group(fz_device *dev)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_END_GROUP, &fz_identity, NULL, NULL, 0);
+ fz_append_display_node(dev->user, node);
+}
+
+static int
+fz_list_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ /* We ignore id here, as we will pass on our own id */
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_BEGIN_TILE, ctm, NULL, NULL, 0);
+ node->rect = *area;
+ node->color[0] = xstep;
+ node->color[1] = ystep;
+ node->color[2] = view->x0;
+ node->color[3] = view->y0;
+ node->color[4] = view->x1;
+ node->color[5] = view->y1;
+ fz_append_display_node(dev->user, node);
+ return 0;
+}
+
+static void
+fz_list_end_tile(fz_device *dev)
+{
+ fz_display_node *node;
+ node = fz_new_display_node(dev->ctx, FZ_CMD_END_TILE, &fz_identity, NULL, NULL, 0);
+ fz_append_display_node(dev->user, node);
+}
+
+fz_device *
+fz_new_list_device(fz_context *ctx, fz_display_list *list)
+{
+ fz_device *dev = fz_new_device(ctx, list);
+
+ dev->begin_page = fz_list_begin_page;
+ dev->end_page = fz_list_end_page;
+
+ dev->fill_path = fz_list_fill_path;
+ dev->stroke_path = fz_list_stroke_path;
+ dev->clip_path = fz_list_clip_path;
+ dev->clip_stroke_path = fz_list_clip_stroke_path;
+
+ dev->fill_text = fz_list_fill_text;
+ dev->stroke_text = fz_list_stroke_text;
+ dev->clip_text = fz_list_clip_text;
+ dev->clip_stroke_text = fz_list_clip_stroke_text;
+ dev->ignore_text = fz_list_ignore_text;
+
+ dev->fill_shade = fz_list_fill_shade;
+ dev->fill_image = fz_list_fill_image;
+ dev->fill_image_mask = fz_list_fill_image_mask;
+ dev->clip_image_mask = fz_list_clip_image_mask;
+
+ dev->pop_clip = fz_list_pop_clip;
+
+ dev->begin_mask = fz_list_begin_mask;
+ dev->end_mask = fz_list_end_mask;
+ dev->begin_group = fz_list_begin_group;
+ dev->end_group = fz_list_end_group;
+
+ dev->begin_tile = fz_list_begin_tile;
+ dev->end_tile = fz_list_end_tile;
+
+ return dev;
+}
+
+static void
+fz_free_display_list(fz_context *ctx, fz_storable *list_)
+{
+ fz_display_list *list = (fz_display_list *)list_;
+ fz_display_node *node;
+
+ if (list == NULL)
+ return;
+ node = list->first;
+ while (node)
+ {
+ fz_display_node *next = node->next;
+ fz_free_display_node(ctx, node);
+ node = next;
+ }
+ fz_free(ctx, list);
+}
+
+fz_display_list *
+fz_new_display_list(fz_context *ctx)
+{
+ fz_display_list *list = fz_malloc_struct(ctx, fz_display_list);
+ FZ_INIT_STORABLE(list, 1, fz_free_display_list);
+ list->first = NULL;
+ list->last = NULL;
+ list->len = 0;
+ list->top = 0;
+ list->tiled = 0;
+ return list;
+}
+
+fz_display_list *
+fz_keep_display_list(fz_context *ctx, fz_display_list *list)
+{
+ return (fz_display_list *)fz_keep_storable(ctx, &list->storable);
+}
+
+void
+fz_drop_display_list(fz_context *ctx, fz_display_list *list)
+{
+ fz_drop_storable(ctx, &list->storable);
+}
+
+static fz_display_node *
+skip_to_end_tile(fz_display_node *node, int *progress)
+{
+ fz_display_node *next;
+ int depth = 1;
+
+ /* Skip through until we find the matching end_tile. Note that
+ * (somewhat nastily) we return the PREVIOUS node to this to help
+ * the calling routine. */
+ do
+ {
+ next = node->next;
+ if (next == NULL)
+ break;
+ if (next->cmd == FZ_CMD_BEGIN_TILE)
+ depth++;
+ else if (next->cmd == FZ_CMD_END_TILE)
+ {
+ depth--;
+ if (depth == 0)
+ return node;
+ }
+ (*progress)++;
+ node = next;
+ }
+ while (1);
+
+ return NULL;
+}
+
+void
+fz_run_display_list(fz_display_list *list, fz_device *dev, const fz_matrix *top_ctm, const fz_rect *scissor, fz_cookie *cookie)
+{
+ fz_display_node *node;
+ fz_matrix ctm;
+ int clipped = 0;
+ int tiled = 0;
+ int progress = 0;
+ fz_context *ctx = dev->ctx;
+
+ if (!scissor)
+ scissor = &fz_infinite_rect;
+
+ if (cookie)
+ {
+ cookie->progress_max = list->len;
+ cookie->progress = 0;
+ }
+
+ for (node = list->first; node; node = node->next)
+ {
+ int empty;
+
+ fz_rect node_rect = node->rect;
+ fz_transform_rect(&node_rect, top_ctm);
+
+ /* Check the cookie for aborting */
+ if (cookie)
+ {
+ if (cookie->abort)
+ break;
+ cookie->progress = progress++;
+ }
+
+ /* cull objects to draw using a quick visibility test */
+
+ if (tiled ||
+ node->cmd == FZ_CMD_BEGIN_TILE || node->cmd == FZ_CMD_END_TILE ||
+ node->cmd == FZ_CMD_BEGIN_PAGE || node->cmd == FZ_CMD_END_PAGE)
+ {
+ empty = 0;
+ }
+ else
+ {
+ fz_rect rect = node_rect;
+ fz_intersect_rect(&rect, scissor);
+ empty = fz_is_empty_rect(&rect);
+ }
+
+ if (clipped || empty)
+ {
+ switch (node->cmd)
+ {
+ case FZ_CMD_CLIP_PATH:
+ case FZ_CMD_CLIP_STROKE_PATH:
+ case FZ_CMD_CLIP_STROKE_TEXT:
+ case FZ_CMD_CLIP_IMAGE_MASK:
+ case FZ_CMD_BEGIN_MASK:
+ case FZ_CMD_BEGIN_GROUP:
+ clipped++;
+ continue;
+ case FZ_CMD_CLIP_TEXT:
+ /* Accumulated text has no extra pops */
+ if (node->flag != 2)
+ clipped++;
+ continue;
+ case FZ_CMD_POP_CLIP:
+ case FZ_CMD_END_GROUP:
+ if (!clipped)
+ goto visible;
+ clipped--;
+ continue;
+ case FZ_CMD_END_MASK:
+ if (!clipped)
+ goto visible;
+ continue;
+ default:
+ continue;
+ }
+ }
+
+visible:
+ fz_concat(&ctm, &node->ctm, top_ctm);
+
+
+ fz_try(ctx)
+ {
+ switch (node->cmd)
+ {
+ case FZ_CMD_BEGIN_PAGE:
+ fz_begin_page(dev, &node_rect, &ctm);
+ break;
+ case FZ_CMD_END_PAGE:
+ fz_end_page(dev);
+ break;
+ case FZ_CMD_FILL_PATH:
+ fz_fill_path(dev, node->item.path, node->flag, &ctm,
+ node->colorspace, node->color, node->alpha);
+ break;
+ case FZ_CMD_STROKE_PATH:
+ fz_stroke_path(dev, node->item.path, node->stroke, &ctm,
+ node->colorspace, node->color, node->alpha);
+ break;
+ case FZ_CMD_CLIP_PATH:
+ fz_clip_path(dev, node->item.path, &node_rect, node->flag, &ctm);
+ break;
+ case FZ_CMD_CLIP_STROKE_PATH:
+ fz_clip_stroke_path(dev, node->item.path, &node_rect, node->stroke, &ctm);
+ break;
+ case FZ_CMD_FILL_TEXT:
+ fz_fill_text(dev, node->item.text, &ctm,
+ node->colorspace, node->color, node->alpha);
+ break;
+ case FZ_CMD_STROKE_TEXT:
+ fz_stroke_text(dev, node->item.text, node->stroke, &ctm,
+ node->colorspace, node->color, node->alpha);
+ break;
+ case FZ_CMD_CLIP_TEXT:
+ fz_clip_text(dev, node->item.text, &ctm, node->flag);
+ break;
+ case FZ_CMD_CLIP_STROKE_TEXT:
+ fz_clip_stroke_text(dev, node->item.text, node->stroke, &ctm);
+ break;
+ case FZ_CMD_IGNORE_TEXT:
+ fz_ignore_text(dev, node->item.text, &ctm);
+ break;
+ case FZ_CMD_FILL_SHADE:
+ if ((dev->hints & FZ_IGNORE_SHADE) == 0)
+ fz_fill_shade(dev, node->item.shade, &ctm, node->alpha);
+ break;
+ case FZ_CMD_FILL_IMAGE:
+ if ((dev->hints & FZ_IGNORE_IMAGE) == 0)
+ fz_fill_image(dev, node->item.image, &ctm, node->alpha);
+ break;
+ case FZ_CMD_FILL_IMAGE_MASK:
+ if ((dev->hints & FZ_IGNORE_IMAGE) == 0)
+ fz_fill_image_mask(dev, node->item.image, &ctm,
+ node->colorspace, node->color, node->alpha);
+ break;
+ case FZ_CMD_CLIP_IMAGE_MASK:
+ if ((dev->hints & FZ_IGNORE_IMAGE) == 0)
+ fz_clip_image_mask(dev, node->item.image, &node_rect, &ctm);
+ break;
+ case FZ_CMD_POP_CLIP:
+ fz_pop_clip(dev);
+ break;
+ case FZ_CMD_BEGIN_MASK:
+ fz_begin_mask(dev, &node_rect, node->flag, node->colorspace, node->color);
+ break;
+ case FZ_CMD_END_MASK:
+ fz_end_mask(dev);
+ break;
+ case FZ_CMD_BEGIN_GROUP:
+ fz_begin_group(dev, &node_rect,
+ (node->flag & ISOLATED) != 0, (node->flag & KNOCKOUT) != 0,
+ node->item.blendmode, node->alpha);
+ break;
+ case FZ_CMD_END_GROUP:
+ fz_end_group(dev);
+ break;
+ case FZ_CMD_BEGIN_TILE:
+ {
+ int cached;
+ fz_rect tile_rect;
+ tiled++;
+ tile_rect.x0 = node->color[2];
+ tile_rect.y0 = node->color[3];
+ tile_rect.x1 = node->color[4];
+ tile_rect.y1 = node->color[5];
+ cached = fz_begin_tile_id(dev, &node->rect, &tile_rect, node->color[0], node->color[1], &ctm, node->flag);
+ if (cached)
+ node = skip_to_end_tile(node, &progress);
+ break;
+ }
+ case FZ_CMD_END_TILE:
+ tiled--;
+ fz_end_tile(dev);
+ break;
+ }
+ }
+ fz_catch(ctx)
+ {
+ /* Swallow the error */
+ if (cookie)
+ cookie->errors++;
+ fz_warn(ctx, "Ignoring error during interpretation");
+ }
+ }
+}
diff --git a/source/fitz/load-jpeg.c b/source/fitz/load-jpeg.c
new file mode 100644
index 00000000..b968c0cf
--- /dev/null
+++ b/source/fitz/load-jpeg.c
@@ -0,0 +1,111 @@
+#include "mupdf/fitz.h"
+
+#include <jpeglib.h>
+
+static void error_exit(j_common_ptr cinfo)
+{
+ char msg[JMSG_LENGTH_MAX];
+ fz_context *ctx = (fz_context *)cinfo->client_data;
+
+ cinfo->err->format_message(cinfo, msg);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "jpeg error: %s", msg);
+}
+
+static void init_source(j_decompress_ptr cinfo)
+{
+ /* nothing to do */
+}
+
+static void term_source(j_decompress_ptr cinfo)
+{
+ /* nothing to do */
+}
+
+static boolean fill_input_buffer(j_decompress_ptr cinfo)
+{
+ static unsigned char eoi[2] = { 0xFF, JPEG_EOI };
+ struct jpeg_source_mgr *src = cinfo->src;
+ src->next_input_byte = eoi;
+ src->bytes_in_buffer = 2;
+ return 1;
+}
+
+static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+ struct jpeg_source_mgr *src = cinfo->src;
+ if (num_bytes > 0)
+ {
+ size_t skip = (size_t)num_bytes; /* size_t may be 64bit */
+ if (skip > src->bytes_in_buffer)
+ skip = (size_t)src->bytes_in_buffer;
+ src->next_input_byte += skip;
+ src->bytes_in_buffer -= skip;
+ }
+}
+
+void
+fz_load_jpeg_info(fz_context *ctx, unsigned char *rbuf, int rlen, int *xp, int *yp, int *xresp, int *yresp, fz_colorspace **cspacep)
+{
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr err;
+ struct jpeg_source_mgr src;
+
+ fz_try(ctx)
+ {
+ cinfo.client_data = ctx;
+ cinfo.err = jpeg_std_error(&err);
+ err.error_exit = error_exit;
+
+ jpeg_create_decompress(&cinfo);
+
+ cinfo.src = &src;
+ src.init_source = init_source;
+ src.fill_input_buffer = fill_input_buffer;
+ src.skip_input_data = skip_input_data;
+ src.resync_to_restart = jpeg_resync_to_restart;
+ src.term_source = term_source;
+ src.next_input_byte = rbuf;
+ src.bytes_in_buffer = rlen;
+
+ jpeg_read_header(&cinfo, 1);
+
+ if (cinfo.num_components == 1)
+ *cspacep = fz_device_gray(ctx);
+ else if (cinfo.num_components == 3)
+ *cspacep = fz_device_rgb(ctx);
+ else if (cinfo.num_components == 4)
+ *cspacep = fz_device_cmyk(ctx);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "bad number of components in jpeg: %d", cinfo.num_components);
+
+ *xp = cinfo.image_width;
+ *yp = cinfo.image_height;
+
+ if (cinfo.density_unit == 1)
+ {
+ *xresp = cinfo.X_density;
+ *yresp = cinfo.Y_density;
+ }
+ else if (cinfo.density_unit == 2)
+ {
+ *xresp = cinfo.X_density * 254 / 100;
+ *yresp = cinfo.Y_density * 254 / 100;
+ }
+ else
+ {
+ *xresp = 0;
+ *yresp = 0;
+ }
+
+ if (*xresp <= 0) *xresp = 72;
+ if (*yresp <= 0) *yresp = 72;
+ }
+ fz_always(ctx)
+ {
+ jpeg_destroy_decompress(&cinfo);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
diff --git a/source/fitz/load-jpx.c b/source/fitz/load-jpx.c
new file mode 100644
index 00000000..cd41277d
--- /dev/null
+++ b/source/fitz/load-jpx.c
@@ -0,0 +1,253 @@
+#include "mupdf/fitz.h"
+
+/* Without the definition of OPJ_STATIC, compilation fails on windows
+ * due to the use of __stdcall. We believe it is required on some
+ * linux toolchains too. */
+#define OPJ_STATIC
+#ifndef _MSC_VER
+#define OPJ_HAVE_STDINT_H
+#endif
+
+#include <openjpeg.h>
+
+static void fz_opj_error_callback(const char *msg, void *client_data)
+{
+ fz_context *ctx = (fz_context *)client_data;
+ fz_warn(ctx, "openjpeg error: %s", msg);
+}
+
+static void fz_opj_warning_callback(const char *msg, void *client_data)
+{
+ fz_context *ctx = (fz_context *)client_data;
+ fz_warn(ctx, "openjpeg warning: %s", msg);
+}
+
+static void fz_opj_info_callback(const char *msg, void *client_data)
+{
+ /* fz_warn("openjpeg info: %s", msg); */
+}
+
+typedef struct stream_block_s
+{
+ unsigned char *data;
+ int size;
+ int pos;
+} stream_block;
+
+OPJ_SIZE_T stream_read(void * p_buffer, OPJ_SIZE_T p_nb_bytes, void * p_user_data)
+{
+ stream_block *sb = (stream_block *)p_user_data;
+ int len;
+
+ len = sb->size - sb->pos;
+ if (len < 0)
+ len = 0;
+ if (len == 0)
+ return (OPJ_SIZE_T)-1; /* End of file! */
+ if ((OPJ_SIZE_T)len > p_nb_bytes)
+ len = p_nb_bytes;
+ memcpy(p_buffer, sb->data + sb->pos, len);
+ sb->pos += len;
+ return len;
+}
+
+OPJ_OFF_T stream_skip(OPJ_OFF_T skip, void * p_user_data)
+{
+ stream_block *sb = (stream_block *)p_user_data;
+
+ if (skip > sb->size - sb->pos)
+ skip = sb->size - sb->pos;
+ sb->pos += skip;
+ return sb->pos;
+}
+
+OPJ_BOOL stream_seek(OPJ_OFF_T seek_pos, void * p_user_data)
+{
+ stream_block *sb = (stream_block *)p_user_data;
+
+ if (seek_pos > sb->size)
+ return OPJ_FALSE;
+ sb->pos = seek_pos;
+ return OPJ_TRUE;
+}
+
+fz_pixmap *
+fz_load_jpx(fz_context *ctx, unsigned char *data, int size, fz_colorspace *defcs, int indexed)
+{
+ fz_pixmap *img;
+ fz_colorspace *origcs;
+ opj_dparameters_t params;
+ opj_codec_t *codec;
+ opj_image_t *jpx;
+ opj_stream_t *stream;
+ fz_colorspace *colorspace;
+ unsigned char *p;
+ OPJ_CODEC_FORMAT format;
+ int a, n, w, h, depth, sgnd;
+ int x, y, k, v;
+ stream_block sb;
+
+ if (size < 2)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "not enough data to determine image format");
+
+ /* Check for SOC marker -- if found we have a bare J2K stream */
+ if (data[0] == 0xFF && data[1] == 0x4F)
+ format = OPJ_CODEC_J2K;
+ else
+ format = OPJ_CODEC_JP2;
+
+ opj_set_default_decoder_parameters(&params);
+ if (indexed)
+ params.flags |= OPJ_DPARAMETERS_IGNORE_PCLR_CMAP_CDEF_FLAG;
+
+ codec = opj_create_decompress(format);
+ opj_set_info_handler(codec, fz_opj_info_callback, ctx);
+ opj_set_warning_handler(codec, fz_opj_warning_callback, ctx);
+ opj_set_error_handler(codec, fz_opj_error_callback, ctx);
+ if (!opj_setup_decoder(codec, &params))
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "j2k decode failed");
+ }
+
+ stream = opj_stream_default_create(OPJ_TRUE);
+ sb.data = data;
+ sb.pos = 0;
+ sb.size = size;
+
+ opj_stream_set_read_function(stream, stream_read);
+ opj_stream_set_skip_function(stream, stream_skip);
+ opj_stream_set_seek_function(stream, stream_seek);
+ opj_stream_set_user_data(stream, &sb);
+ /* Set the length to avoid an assert */
+ opj_stream_set_user_data_length(stream, size);
+
+ if (!opj_read_header(stream, codec, &jpx))
+ {
+ opj_stream_destroy(stream);
+ opj_destroy_codec(codec);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to read JPX header");
+ }
+
+ if (!opj_decode(codec, stream, jpx))
+ {
+ opj_stream_destroy(stream);
+ opj_destroy_codec(codec);
+ opj_image_destroy(jpx);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to decode JPX image");
+ }
+
+ opj_stream_destroy(stream);
+ opj_destroy_codec(codec);
+
+ /* jpx should never be NULL here, but check anyway */
+ if (!jpx)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "opj_decode failed");
+
+ for (k = 1; k < (int)jpx->numcomps; k++)
+ {
+ if (jpx->comps[k].w != jpx->comps[0].w)
+ {
+ opj_image_destroy(jpx);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image components have different width");
+ }
+ if (jpx->comps[k].h != jpx->comps[0].h)
+ {
+ opj_image_destroy(jpx);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image components have different height");
+ }
+ if (jpx->comps[k].prec != jpx->comps[0].prec)
+ {
+ opj_image_destroy(jpx);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image components have different precision");
+ }
+ }
+
+ n = jpx->numcomps;
+ w = jpx->comps[0].w;
+ h = jpx->comps[0].h;
+ depth = jpx->comps[0].prec;
+ sgnd = jpx->comps[0].sgnd;
+
+ if (jpx->color_space == OPJ_CLRSPC_SRGB && n == 4) { n = 3; a = 1; }
+ else if (jpx->color_space == OPJ_CLRSPC_SYCC && n == 4) { n = 3; a = 1; }
+ else if (n == 2) { n = 1; a = 1; }
+ else if (n > 4) { n = 4; a = 1; }
+ else { a = 0; }
+
+ origcs = defcs;
+ if (defcs)
+ {
+ if (defcs->n == n)
+ {
+ colorspace = defcs;
+ }
+ else
+ {
+ fz_warn(ctx, "jpx file and dict colorspaces do not match");
+ defcs = NULL;
+ }
+ }
+
+ if (!defcs)
+ {
+ switch (n)
+ {
+ case 1: colorspace = fz_device_gray(ctx); break;
+ case 3: colorspace = fz_device_rgb(ctx); break;
+ case 4: colorspace = fz_device_cmyk(ctx); break;
+ }
+ }
+
+ fz_try(ctx)
+ {
+ img = fz_new_pixmap(ctx, colorspace, w, h);
+ }
+ fz_catch(ctx)
+ {
+ opj_image_destroy(jpx);
+ fz_rethrow_message(ctx, "out of memory loading jpx");
+ }
+
+ p = img->samples;
+ for (y = 0; y < h; y++)
+ {
+ for (x = 0; x < w; x++)
+ {
+ for (k = 0; k < n + a; k++)
+ {
+ v = jpx->comps[k].data[y * w + x];
+ if (sgnd)
+ v = v + (1 << (depth - 1));
+ if (depth > 8)
+ v = v >> (depth - 8);
+ *p++ = v;
+ }
+ if (!a)
+ *p++ = 255;
+ }
+ }
+
+ opj_image_destroy(jpx);
+
+ if (a)
+ {
+ if (n == 4)
+ {
+ fz_pixmap *tmp = fz_new_pixmap(ctx, fz_device_rgb(ctx), w, h);
+ fz_convert_pixmap(ctx, tmp, img);
+ fz_drop_pixmap(ctx, img);
+ img = tmp;
+ }
+ fz_premultiply_pixmap(ctx, img);
+ }
+
+ if (origcs != defcs)
+ {
+ fz_pixmap *tmp = fz_new_pixmap(ctx, origcs, w, h);
+ fz_convert_pixmap(ctx, tmp, img);
+ fz_drop_pixmap(ctx, img);
+ img = tmp;
+ }
+
+ return img;
+}
diff --git a/source/fitz/load-png.c b/source/fitz/load-png.c
new file mode 100644
index 00000000..ad22e128
--- /dev/null
+++ b/source/fitz/load-png.c
@@ -0,0 +1,599 @@
+#include "mupdf/fitz.h"
+
+#include <zlib.h>
+
+struct info
+{
+ fz_context *ctx;
+ unsigned int width, height, depth, n;
+ int interlace, indexed;
+ unsigned int size;
+ unsigned char *samples;
+ unsigned char palette[256*4];
+ int transparency;
+ int trns[3];
+ int xres, yres;
+};
+
+static inline unsigned int getuint(unsigned char *p)
+{
+ return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+}
+
+static inline int getcomp(unsigned char *line, int x, int bpc)
+{
+ switch (bpc)
+ {
+ case 1: return (line[x >> 3] >> ( 7 - (x & 7) ) ) & 1;
+ case 2: return (line[x >> 2] >> ( ( 3 - (x & 3) ) << 1 ) ) & 3;
+ case 4: return (line[x >> 1] >> ( ( 1 - (x & 1) ) << 2 ) ) & 15;
+ case 8: return line[x];
+ case 16: return line[x << 1] << 8 | line[(x << 1) + 1];
+ }
+ return 0;
+}
+
+static inline void putcomp(unsigned char *line, int x, int bpc, int value)
+{
+ int maxval = (1 << bpc) - 1;
+
+ switch (bpc)
+ {
+ case 1: line[x >> 3] &= ~(maxval << (7 - (x & 7))); break;
+ case 2: line[x >> 2] &= ~(maxval << ((3 - (x & 3)) << 1)); break;
+ case 4: line[x >> 1] &= ~(maxval << ((1 - (x & 1)) << 2)); break;
+ }
+
+ switch (bpc)
+ {
+ case 1: line[x >> 3] |= value << (7 - (x & 7)); break;
+ case 2: line[x >> 2] |= value << ((3 - (x & 3)) << 1); break;
+ case 4: line[x >> 1] |= value << ((1 - (x & 1)) << 2); break;
+ case 8: line[x] = value; break;
+ case 16: line[x << 1] = value >> 8; line[(x << 1) + 1] = value & 0xFF; break;
+ }
+}
+
+static const unsigned char png_signature[8] =
+{
+ 137, 80, 78, 71, 13, 10, 26, 10
+};
+
+static void *zalloc(void *opaque, unsigned int items, unsigned int size)
+{
+ return fz_malloc_array(opaque, items, size);
+}
+
+static void zfree(void *opaque, void *address)
+{
+ fz_free(opaque, address);
+}
+
+static inline int paeth(int a, int b, int c)
+{
+ /* The definitions of ac and bc are correct, not a typo. */
+ int ac = b - c, bc = a - c, abcc = ac + bc;
+ int pa = (ac < 0 ? -ac : ac);
+ int pb = (bc < 0 ? -bc : bc);
+ int pc = (abcc < 0 ? -abcc : abcc);
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
+}
+
+static void
+png_predict(unsigned char *samples, unsigned int width, unsigned int height, unsigned int n, unsigned int depth)
+{
+ unsigned int stride = (width * n * depth + 7) / 8;
+ unsigned int bpp = (n * depth + 7) / 8;
+ unsigned int i, row;
+
+ for (row = 0; row < height; row ++)
+ {
+ unsigned char *src = samples + (unsigned int)((stride + 1) * row);
+ unsigned char *dst = samples + (unsigned int)(stride * row);
+
+ unsigned char *a = dst;
+ unsigned char *b = dst - stride;
+ unsigned char *c = dst - stride;
+
+ switch (*src++)
+ {
+ default:
+ case 0: /* None */
+ for (i = 0; i < stride; i++)
+ *dst++ = *src++;
+ break;
+
+ case 1: /* Sub */
+ for (i = 0; i < bpp; i++)
+ *dst++ = *src++;
+ for (i = bpp; i < stride; i++)
+ *dst++ = *src++ + *a++;
+ break;
+
+ case 2: /* Up */
+ if (row == 0)
+ for (i = 0; i < stride; i++)
+ *dst++ = *src++;
+ else
+ for (i = 0; i < stride; i++)
+ *dst++ = *src++ + *b++;
+ break;
+
+ case 3: /* Average */
+ if (row == 0)
+ {
+ for (i = 0; i < bpp; i++)
+ *dst++ = *src++;
+ for (i = bpp; i < stride; i++)
+ *dst++ = *src++ + (*a++ >> 1);
+ }
+ else
+ {
+ for (i = 0; i < bpp; i++)
+ *dst++ = *src++ + (*b++ >> 1);
+ for (i = bpp; i < stride; i++)
+ *dst++ = *src++ + ((*b++ + *a++) >> 1);
+ }
+ break;
+
+ case 4: /* Paeth */
+ if (row == 0)
+ {
+ for (i = 0; i < bpp; i++)
+ *dst++ = *src++ + paeth(0, 0, 0);
+ for (i = bpp; i < stride; i++)
+ *dst++ = *src++ + paeth(*a++, 0, 0);
+ }
+ else
+ {
+ for (i = 0; i < bpp; i++)
+ *dst++ = *src++ + paeth(0, *b++, 0);
+ for (i = bpp; i < stride; i++)
+ *dst++ = *src++ + paeth(*a++, *b++, *c++);
+ }
+ break;
+ }
+ }
+}
+
+static const unsigned int adam7_ix[7] = { 0, 4, 0, 2, 0, 1, 0 };
+static const unsigned int adam7_dx[7] = { 8, 8, 4, 4, 2, 2, 1 };
+static const unsigned int adam7_iy[7] = { 0, 0, 4, 0, 2, 0, 1 };
+static const unsigned int adam7_dy[7] = { 8, 8, 8, 4, 4, 2, 2 };
+
+static void
+png_deinterlace_passes(struct info *info, unsigned int *w, unsigned int *h, unsigned int *ofs)
+{
+ int p, bpp = info->depth * info->n;
+ ofs[0] = 0;
+ for (p = 0; p < 7; p++)
+ {
+ w[p] = (info->width + adam7_dx[p] - adam7_ix[p] - 1) / adam7_dx[p];
+ h[p] = (info->height + adam7_dy[p] - adam7_iy[p] - 1) / adam7_dy[p];
+ if (w[p] == 0) h[p] = 0;
+ if (h[p] == 0) w[p] = 0;
+ if (w[p] && h[p])
+ ofs[p + 1] = ofs[p] + h[p] * (1 + (w[p] * bpp + 7) / 8);
+ else
+ ofs[p + 1] = ofs[p];
+ }
+}
+
+static void
+png_deinterlace(struct info *info, unsigned int *passw, unsigned int *passh, unsigned int *passofs)
+{
+ unsigned int n = info->n;
+ unsigned int depth = info->depth;
+ unsigned int stride = (info->width * n * depth + 7) / 8;
+ unsigned char *output;
+ unsigned int p, x, y, k;
+
+ output = fz_malloc_array(info->ctx, info->height, stride);
+
+ for (p = 0; p < 7; p++)
+ {
+ unsigned char *sp = info->samples + (passofs[p]);
+ unsigned int w = passw[p];
+ unsigned int h = passh[p];
+
+ png_predict(sp, w, h, n, depth);
+ for (y = 0; y < h; y++)
+ {
+ for (x = 0; x < w; x++)
+ {
+ int outx = x * adam7_dx[p] + adam7_ix[p];
+ int outy = y * adam7_dy[p] + adam7_iy[p];
+ unsigned char *dp = output + outy * stride;
+ for (k = 0; k < n; k++)
+ {
+ int v = getcomp(sp, x * n + k, depth);
+ putcomp(dp, outx * n + k, depth, v);
+ }
+ }
+ sp += (w * depth * n + 7) / 8;
+ }
+ }
+
+ fz_free(info->ctx, info->samples);
+ info->samples = output;
+}
+
+static void
+png_read_ihdr(struct info *info, unsigned char *p, unsigned int size)
+{
+ int color, compression, filter;
+
+ if (size != 13)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "IHDR chunk is the wrong size");
+
+ info->width = getuint(p + 0);
+ info->height = getuint(p + 4);
+ info->depth = p[8];
+
+ color = p[9];
+ compression = p[10];
+ filter = p[11];
+ info->interlace = p[12];
+
+ if (info->width <= 0)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "image width must be > 0");
+ if (info->height <= 0)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "image height must be > 0");
+
+ if (info->depth != 1 && info->depth != 2 && info->depth != 4 &&
+ info->depth != 8 && info->depth != 16)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "image bit depth must be one of 1, 2, 4, 8, 16");
+ if (color == 2 && info->depth < 8)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "illegal bit depth for truecolor");
+ if (color == 3 && info->depth > 8)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "illegal bit depth for indexed");
+ if (color == 4 && info->depth < 8)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "illegal bit depth for grayscale with alpha");
+ if (color == 6 && info->depth < 8)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "illegal bit depth for truecolor with alpha");
+
+ info->indexed = 0;
+ if (color == 0) /* gray */
+ info->n = 1;
+ else if (color == 2) /* rgb */
+ info->n = 3;
+ else if (color == 4) /* gray alpha */
+ info->n = 2;
+ else if (color == 6) /* rgb alpha */
+ info->n = 4;
+ else if (color == 3) /* indexed */
+ {
+ info->indexed = 1;
+ info->n = 1;
+ }
+ else
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "unknown color type");
+
+ if (compression != 0)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "unknown compression method");
+ if (filter != 0)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "unknown filter method");
+ if (info->interlace != 0 && info->interlace != 1)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "interlace method not supported");
+}
+
+static void
+png_read_plte(struct info *info, unsigned char *p, unsigned int size)
+{
+ int n = size / 3;
+ int i;
+
+ if (n > 256)
+ {
+ fz_warn(info->ctx, "too many samples in palette");
+ n = 256;
+ }
+
+ for (i = 0; i < n; i++)
+ {
+ info->palette[i * 4] = p[i * 3];
+ info->palette[i * 4 + 1] = p[i * 3 + 1];
+ info->palette[i * 4 + 2] = p[i * 3 + 2];
+ }
+
+ /* Fill in any missing palette entries */
+ for (; i < 256; i++)
+ {
+ info->palette[i * 4] = 0;
+ info->palette[i * 4 + 1] = 0;
+ info->palette[i * 4 + 2] = 0;
+ }
+}
+
+static void
+png_read_trns(struct info *info, unsigned char *p, unsigned int size)
+{
+ unsigned int i;
+
+ info->transparency = 1;
+
+ if (info->indexed)
+ {
+ if (size > 256)
+ {
+ fz_warn(info->ctx, "too many samples in transparency table");
+ size = 256;
+ }
+ for (i = 0; i < size; i++)
+ info->palette[i * 4 + 3] = p[i];
+ /* Fill in any missing entries */
+ for (; i < 256; i++)
+ info->palette[i * 4 + 3] = 255;
+ }
+ else
+ {
+ if (size != info->n * 2)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "tRNS chunk is the wrong size");
+ for (i = 0; i < info->n; i++)
+ info->trns[i] = (p[i * 2] << 8 | p[i * 2 + 1]) & ((1 << info->depth) - 1);
+ }
+}
+
+static void
+png_read_idat(struct info *info, unsigned char *p, unsigned int size, z_stream *stm)
+{
+ int code;
+
+ stm->next_in = p;
+ stm->avail_in = size;
+
+ code = inflate(stm, Z_SYNC_FLUSH);
+ if (code != Z_OK && code != Z_STREAM_END)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "zlib error: %s", stm->msg);
+ if (stm->avail_in != 0)
+ {
+ if (stm->avail_out == 0)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "ran out of output before input");
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "inflate did not consume buffer (%d remaining)", stm->avail_in);
+ }
+}
+
+static void
+png_read_phys(struct info *info, unsigned char *p, unsigned int size)
+{
+ if (size != 9)
+ fz_throw(info->ctx, FZ_ERROR_GENERIC, "pHYs chunk is the wrong size");
+ if (p[8] == 1)
+ {
+ info->xres = getuint(p) * 254 / 10000;
+ info->yres = getuint(p + 4) * 254 / 10000;
+ }
+}
+
+static void
+png_read_image(fz_context *ctx, struct info *info, unsigned char *p, unsigned int total)
+{
+ unsigned int passw[7], passh[7], passofs[8];
+ unsigned int code, size;
+ z_stream stm;
+
+ memset(info, 0, sizeof (struct info));
+ info->ctx = ctx;
+ memset(info->palette, 255, sizeof(info->palette));
+ info->xres = 96;
+ info->yres = 96;
+
+ /* Read signature */
+
+ if (total < 8 + 12 || memcmp(p, png_signature, 8))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "not a png image (wrong signature)");
+
+ p += 8;
+ total -= 8;
+
+ /* Read IHDR chunk (must come first) */
+
+ size = getuint(p);
+ if (total < 12 || size > total - 12)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "premature end of data in png image");
+
+ if (!memcmp(p + 4, "IHDR", 4))
+ png_read_ihdr(info, p + 8, size);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "png file must start with IHDR chunk");
+
+ p += size + 12;
+ total -= size + 12;
+
+ /* Prepare output buffer */
+
+ if (!info->interlace)
+ {
+ info->size = info->height * (1 + (info->width * info->n * info->depth + 7) / 8);
+ }
+ else
+ {
+ png_deinterlace_passes(info, passw, passh, passofs);
+ info->size = passofs[7];
+ }
+
+ info->samples = fz_malloc(ctx, info->size);
+
+ stm.zalloc = zalloc;
+ stm.zfree = zfree;
+ stm.opaque = ctx;
+
+ stm.next_out = info->samples;
+ stm.avail_out = info->size;
+
+ code = inflateInit(&stm);
+ if (code != Z_OK)
+ {
+ fz_free(ctx, info->samples);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib error: %s", stm.msg);
+ }
+
+ fz_try(ctx)
+ {
+ /* Read remaining chunks until IEND */
+ while (total > 8)
+ {
+ size = getuint(p);
+
+ if (total < 12 || size > total - 12)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "premature end of data in png image");
+
+ if (!memcmp(p + 4, "PLTE", 4))
+ png_read_plte(info, p + 8, size);
+ if (!memcmp(p + 4, "tRNS", 4))
+ png_read_trns(info, p + 8, size);
+ if (!memcmp(p + 4, "pHYs", 4))
+ png_read_phys(info, p + 8, size);
+ if (!memcmp(p + 4, "IDAT", 4))
+ png_read_idat(info, p + 8, size, &stm);
+ if (!memcmp(p + 4, "IEND", 4))
+ break;
+
+ p += size + 12;
+ total -= size + 12;
+ }
+ }
+ fz_catch(ctx)
+ {
+ inflateEnd(&stm);
+ fz_free(ctx, info->samples);
+ fz_rethrow(ctx);
+ }
+
+ code = inflateEnd(&stm);
+ if (code != Z_OK)
+ {
+ fz_free(ctx, info->samples);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib error: %s", stm.msg);
+ }
+
+ /* Apply prediction filter and deinterlacing */
+ fz_try(ctx)
+ {
+ if (!info->interlace)
+ png_predict(info->samples, info->width, info->height, info->n, info->depth);
+ else
+ png_deinterlace(info, passw, passh, passofs);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, info->samples);
+ fz_rethrow(ctx);
+ }
+}
+
+static fz_pixmap *
+png_expand_palette(fz_context *ctx, struct info *info, fz_pixmap *src)
+{
+ fz_pixmap *dst = fz_new_pixmap(ctx, fz_device_rgb(ctx), src->w, src->h);
+ unsigned char *sp = src->samples;
+ unsigned char *dp = dst->samples;
+ unsigned int x, y;
+
+ dst->xres = src->xres;
+ dst->yres = src->yres;
+
+ for (y = info->height; y > 0; y--)
+ {
+ for (x = info->width; x > 0; x--)
+ {
+ int v = *sp << 2;
+ *dp++ = info->palette[v];
+ *dp++ = info->palette[v + 1];
+ *dp++ = info->palette[v + 2];
+ *dp++ = info->palette[v + 3];
+ sp += 2;
+ }
+ }
+
+ fz_drop_pixmap(info->ctx, src);
+ return dst;
+}
+
+static void
+png_mask_transparency(struct info *info, fz_pixmap *dst)
+{
+ unsigned int stride = (info->width * info->n * info->depth + 7) / 8;
+ unsigned int depth = info->depth;
+ unsigned int n = info->n;
+ unsigned int x, y, k, t;
+
+ for (y = 0; y < info->height; y++)
+ {
+ unsigned char *sp = info->samples + (unsigned int)(y * stride);
+ unsigned char *dp = dst->samples + (unsigned int)(y * dst->w * dst->n);
+ for (x = 0; x < info->width; x++)
+ {
+ t = 1;
+ for (k = 0; k < n; k++)
+ if (getcomp(sp, x * n + k, depth) != info->trns[k])
+ t = 0;
+ if (t)
+ dp[x * dst->n + dst->n - 1] = 0;
+ }
+ }
+}
+
+fz_pixmap *
+fz_load_png(fz_context *ctx, unsigned char *p, int total)
+{
+ fz_pixmap *image;
+ fz_colorspace *colorspace;
+ struct info png;
+ int stride;
+
+ png_read_image(ctx, &png, p, total);
+
+ if (png.n == 3 || png.n == 4)
+ colorspace = fz_device_rgb(ctx);
+ else
+ colorspace = fz_device_gray(ctx);
+
+ stride = (png.width * png.n * png.depth + 7) / 8;
+
+ fz_try(ctx)
+ {
+ image = fz_new_pixmap(ctx, colorspace, png.width, png.height);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(png.ctx, png.samples);
+ fz_rethrow_message(ctx, "out of memory loading png");
+ }
+
+ image->xres = png.xres;
+ image->yres = png.yres;
+
+ fz_unpack_tile(image, png.samples, png.n, png.depth, stride, png.indexed);
+
+ if (png.indexed)
+ image = png_expand_palette(ctx, &png, image);
+ else if (png.transparency)
+ png_mask_transparency(&png, image);
+
+ if (png.transparency || png.n == 2 || png.n == 4)
+ fz_premultiply_pixmap(png.ctx, image);
+
+ fz_free(png.ctx, png.samples);
+
+ return image;
+}
+
+void
+fz_load_png_info(fz_context *ctx, unsigned char *p, int total, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
+{
+ struct info png;
+
+ png_read_image(ctx, &png, p, total);
+
+ if (png.n == 3 || png.n == 4)
+ *cspacep = fz_device_rgb(ctx);
+ else
+ *cspacep = fz_device_gray(ctx);
+
+ *wp = png.width;
+ *hp = png.height;
+ *xresp = png.xres;
+ *yresp = png.xres;
+ fz_free(png.ctx, png.samples);
+}
diff --git a/source/fitz/load-tiff.c b/source/fitz/load-tiff.c
new file mode 100644
index 00000000..5806bc00
--- /dev/null
+++ b/source/fitz/load-tiff.c
@@ -0,0 +1,867 @@
+#include "mupdf/fitz.h"
+
+/*
+ * TIFF image loader. Should be enough to support TIFF files in XPS.
+ * Baseline TIFF 6.0 plus CMYK, LZW, Flate and JPEG support.
+ * Limited bit depths (1,2,4,8).
+ * Limited planar configurations (1=chunky).
+ * No tiles (easy fix if necessary).
+ * TODO: RGBPal images
+ */
+
+struct tiff
+{
+ fz_context *ctx;
+
+ /* "file" */
+ unsigned char *bp, *rp, *ep;
+
+ /* byte order */
+ unsigned order;
+
+ /* where we can find the strips of image data */
+ unsigned rowsperstrip;
+ unsigned *stripoffsets;
+ unsigned *stripbytecounts;
+
+ /* colormap */
+ unsigned *colormap;
+
+ unsigned stripoffsetslen;
+ unsigned stripbytecountslen;
+ unsigned colormaplen;
+
+ /* assorted tags */
+ unsigned subfiletype;
+ unsigned photometric;
+ unsigned compression;
+ unsigned imagewidth;
+ unsigned imagelength;
+ unsigned samplesperpixel;
+ unsigned bitspersample;
+ unsigned planar;
+ unsigned extrasamples;
+ unsigned xresolution;
+ unsigned yresolution;
+ unsigned resolutionunit;
+ unsigned fillorder;
+ unsigned g3opts;
+ unsigned g4opts;
+ unsigned predictor;
+
+ unsigned ycbcrsubsamp[2];
+
+ unsigned char *jpegtables; /* point into "file" buffer */
+ unsigned jpegtableslen;
+
+ unsigned char *profile;
+ int profilesize;
+
+ /* decoded data */
+ fz_colorspace *colorspace;
+ unsigned char *samples;
+ int stride;
+};
+
+enum
+{
+ TII = 0x4949, /* 'II' */
+ TMM = 0x4d4d, /* 'MM' */
+ TBYTE = 1,
+ TASCII = 2,
+ TSHORT = 3,
+ TLONG = 4,
+ TRATIONAL = 5
+};
+
+#define NewSubfileType 254
+#define ImageWidth 256
+#define ImageLength 257
+#define BitsPerSample 258
+#define Compression 259
+#define PhotometricInterpretation 262
+#define FillOrder 266
+#define StripOffsets 273
+#define SamplesPerPixel 277
+#define RowsPerStrip 278
+#define StripByteCounts 279
+#define XResolution 282
+#define YResolution 283
+#define PlanarConfiguration 284
+#define T4Options 292
+#define T6Options 293
+#define ResolutionUnit 296
+#define Predictor 317
+#define ColorMap 320
+#define TileWidth 322
+#define TileLength 323
+#define TileOffsets 324
+#define TileByteCounts 325
+#define ExtraSamples 338
+#define JPEGTables 347
+#define YCbCrSubSampling 520
+#define ICCProfile 34675
+
+static const unsigned char bitrev[256] =
+{
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+};
+
+static void
+fz_decode_tiff_uncompressed(struct tiff *tiff, fz_stream *stm, unsigned char *wp, int wlen)
+{
+ fz_read(stm, wp, wlen);
+ fz_close(stm);
+}
+
+static void
+fz_decode_tiff_packbits(struct tiff *tiff, fz_stream *chain, unsigned char *wp, int wlen)
+{
+ fz_stream *stm = fz_open_rld(chain);
+ fz_read(stm, wp, wlen);
+ fz_close(stm);
+}
+
+static void
+fz_decode_tiff_lzw(struct tiff *tiff, fz_stream *chain, unsigned char *wp, int wlen)
+{
+ fz_stream *stm = fz_open_lzwd(chain, 1);
+ fz_read(stm, wp, wlen);
+ fz_close(stm);
+}
+
+static void
+fz_decode_tiff_flate(struct tiff *tiff, fz_stream *chain, unsigned char *wp, int wlen)
+{
+ fz_stream *stm = fz_open_flated(chain);
+ fz_read(stm, wp, wlen);
+ fz_close(stm);
+}
+
+static void
+fz_decode_tiff_fax(struct tiff *tiff, int comp, fz_stream *chain, unsigned char *wp, int wlen)
+{
+ fz_stream *stm;
+ int black_is_1 = tiff->photometric == 0;
+ int k = comp == 4 ? -1 : 0;
+ int encoded_byte_align = comp == 2;
+ stm = fz_open_faxd(chain,
+ k, 0, encoded_byte_align,
+ tiff->imagewidth, tiff->imagelength, 0, black_is_1);
+ fz_read(stm, wp, wlen);
+ fz_close(stm);
+}
+
+static void
+fz_decode_tiff_jpeg(struct tiff *tiff, fz_stream *chain, unsigned char *wp, int wlen)
+{
+ fz_stream *stm = fz_open_dctd(chain, -1);
+ fz_read(stm, wp, wlen);
+ fz_close(stm);
+}
+
+static inline int getcomp(unsigned char *line, int x, int bpc)
+{
+ switch (bpc)
+ {
+ case 1: return (line[x >> 3] >> ( 7 - (x & 7) ) ) & 1;
+ case 2: return (line[x >> 2] >> ( ( 3 - (x & 3) ) << 1 ) ) & 3;
+ case 4: return (line[x >> 1] >> ( ( 1 - (x & 1) ) << 2 ) ) & 15;
+ case 8: return line[x];
+ case 16: return line[x << 1] << 8 | line[(x << 1) + 1];
+ }
+ return 0;
+}
+
+static inline void putcomp(unsigned char *line, int x, int bpc, int value)
+{
+ int maxval = (1 << bpc) - 1;
+
+ switch (bpc)
+ {
+ case 1: line[x >> 3] &= ~(maxval << (7 - (x & 7))); break;
+ case 2: line[x >> 2] &= ~(maxval << ((3 - (x & 3)) << 1)); break;
+ case 4: line[x >> 1] &= ~(maxval << ((1 - (x & 1)) << 2)); break;
+ }
+
+ switch (bpc)
+ {
+ case 1: line[x >> 3] |= value << (7 - (x & 7)); break;
+ case 2: line[x >> 2] |= value << ((3 - (x & 3)) << 1); break;
+ case 4: line[x >> 1] |= value << ((1 - (x & 1)) << 2); break;
+ case 8: line[x] = value; break;
+ case 16: line[x << 1] = value >> 8; line[(x << 1) + 1] = value & 0xFF; break;
+ }
+}
+
+static void
+fz_unpredict_tiff(unsigned char *line, int width, int comps, int bits)
+{
+ unsigned char left[32];
+ int i, k, v;
+
+ for (k = 0; k < comps; k++)
+ left[k] = 0;
+
+ for (i = 0; i < width; i++)
+ {
+ for (k = 0; k < comps; k++)
+ {
+ v = getcomp(line, i * comps + k, bits);
+ v = v + left[k];
+ v = v % (1 << bits);
+ putcomp(line, i * comps + k, bits, v);
+ left[k] = v;
+ }
+ }
+}
+
+static void
+fz_invert_tiff(unsigned char *line, int width, int comps, int bits, int alpha)
+{
+ int i, k, v;
+ int m = (1 << bits) - 1;
+
+ for (i = 0; i < width; i++)
+ {
+ for (k = 0; k < comps; k++)
+ {
+ v = getcomp(line, i * comps + k, bits);
+ if (!alpha || k < comps - 1)
+ v = m - v;
+ putcomp(line, i * comps + k, bits, v);
+ }
+ }
+}
+
+static void
+fz_expand_tiff_colormap(struct tiff *tiff)
+{
+ int maxval = 1 << tiff->bitspersample;
+ unsigned char *samples;
+ unsigned char *src, *dst;
+ unsigned int x, y;
+ unsigned int stride;
+
+ /* colormap has first all red, then all green, then all blue values */
+ /* colormap values are 0..65535, bits is 4 or 8 */
+ /* image can be with or without extrasamples: comps is 1 or 2 */
+
+ if (tiff->samplesperpixel != 1 && tiff->samplesperpixel != 2)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "invalid number of samples for RGBPal");
+
+ if (tiff->bitspersample != 4 && tiff->bitspersample != 8)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "invalid number of bits for RGBPal");
+
+ if (tiff->colormaplen < (unsigned)maxval * 3)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "insufficient colormap data");
+
+ stride = tiff->imagewidth * (tiff->samplesperpixel + 2);
+
+ samples = fz_malloc(tiff->ctx, stride * tiff->imagelength);
+
+ for (y = 0; y < tiff->imagelength; y++)
+ {
+ src = tiff->samples + (unsigned int)(tiff->stride * y);
+ dst = samples + (unsigned int)(stride * y);
+
+ for (x = 0; x < tiff->imagewidth; x++)
+ {
+ if (tiff->extrasamples)
+ {
+ int c = getcomp(src, x * 2, tiff->bitspersample);
+ int a = getcomp(src, x * 2 + 1, tiff->bitspersample);
+ *dst++ = tiff->colormap[c + 0] >> 8;
+ *dst++ = tiff->colormap[c + maxval] >> 8;
+ *dst++ = tiff->colormap[c + maxval * 2] >> 8;
+ *dst++ = a << (8 - tiff->bitspersample);
+ }
+ else
+ {
+ int c = getcomp(src, x, tiff->bitspersample);
+ *dst++ = tiff->colormap[c + 0] >> 8;
+ *dst++ = tiff->colormap[c + maxval] >> 8;
+ *dst++ = tiff->colormap[c + maxval * 2] >> 8;
+ }
+ }
+ }
+
+ tiff->samplesperpixel += 2;
+ tiff->bitspersample = 8;
+ tiff->stride = stride;
+ fz_free(tiff->ctx, tiff->samples);
+ tiff->samples = samples;
+}
+
+static void
+fz_decode_tiff_strips(struct tiff *tiff)
+{
+ fz_stream *stm;
+
+ /* switch on compression to create a filter */
+ /* feed each strip to the filter */
+ /* read out the data and pack the samples into a pixmap */
+
+ /* type 32773 / packbits -- nothing special (same row-padding as PDF) */
+ /* type 2 / ccitt rle -- no EOL, no RTC, rows are byte-aligned */
+ /* type 3 and 4 / g3 and g4 -- each strip starts new section */
+ /* type 5 / lzw -- each strip is handled separately */
+
+ unsigned char *wp;
+ unsigned row;
+ unsigned strip;
+ unsigned i;
+
+ if (!tiff->rowsperstrip || !tiff->stripoffsets || !tiff->stripbytecounts)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "no image data in tiff; maybe it is tiled");
+
+ if (tiff->stripoffsetslen < (tiff->imagelength - 1) / tiff->rowsperstrip + 1 ||
+ tiff->stripbytecountslen < (tiff->imagelength - 1) / tiff->rowsperstrip + 1)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "insufficient strip offset data");
+
+ if (tiff->planar != 1)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "image data is not in chunky format");
+
+ tiff->stride = (tiff->imagewidth * tiff->samplesperpixel * tiff->bitspersample + 7) / 8;
+
+ switch (tiff->photometric)
+ {
+ case 0: /* WhiteIsZero -- inverted */
+ tiff->colorspace = fz_device_gray(tiff->ctx);
+ break;
+ case 1: /* BlackIsZero */
+ tiff->colorspace = fz_device_gray(tiff->ctx);
+ break;
+ case 2: /* RGB */
+ tiff->colorspace = fz_device_rgb(tiff->ctx);
+ break;
+ case 3: /* RGBPal */
+ tiff->colorspace = fz_device_rgb(tiff->ctx);
+ break;
+ case 5: /* CMYK */
+ tiff->colorspace = fz_device_cmyk(tiff->ctx);
+ break;
+ case 6: /* YCbCr */
+ /* it's probably a jpeg ... we let jpeg convert to rgb */
+ tiff->colorspace = fz_device_rgb(tiff->ctx);
+ break;
+ default:
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "unknown photometric: %d", tiff->photometric);
+ }
+
+ switch (tiff->resolutionunit)
+ {
+ case 2:
+ /* no unit conversion needed */
+ break;
+ case 3:
+ tiff->xresolution = tiff->xresolution * 254 / 100;
+ tiff->yresolution = tiff->yresolution * 254 / 100;
+ break;
+ default:
+ tiff->xresolution = 96;
+ tiff->yresolution = 96;
+ break;
+ }
+
+ /* Note xres and yres could be 0 even if unit was set. If so default to 96dpi. */
+ if (tiff->xresolution == 0 || tiff->yresolution == 0)
+ {
+ tiff->xresolution = 96;
+ tiff->yresolution = 96;
+ }
+
+ tiff->samples = fz_malloc_array(tiff->ctx, tiff->imagelength, tiff->stride);
+ memset(tiff->samples, 0x55, tiff->imagelength * tiff->stride);
+ wp = tiff->samples;
+
+ strip = 0;
+ for (row = 0; row < tiff->imagelength; row += tiff->rowsperstrip)
+ {
+ unsigned offset = tiff->stripoffsets[strip];
+ unsigned rlen = tiff->stripbytecounts[strip];
+ unsigned wlen = tiff->stride * tiff->rowsperstrip;
+ unsigned char *rp = tiff->bp + offset;
+
+ if (wp + wlen > tiff->samples + (unsigned int)(tiff->stride * tiff->imagelength))
+ wlen = tiff->samples + (unsigned int)(tiff->stride * tiff->imagelength) - wp;
+
+ if (rp + rlen > tiff->ep)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "strip extends beyond the end of the file");
+
+ /* the bits are in un-natural order */
+ if (tiff->fillorder == 2)
+ for (i = 0; i < rlen; i++)
+ rp[i] = bitrev[rp[i]];
+
+ /* the strip decoders will close this */
+ stm = fz_open_memory(tiff->ctx, rp, rlen);
+
+ switch (tiff->compression)
+ {
+ case 1:
+ fz_decode_tiff_uncompressed(tiff, stm, wp, wlen);
+ break;
+ case 2:
+ fz_decode_tiff_fax(tiff, 2, stm, wp, wlen);
+ break;
+ case 3:
+ fz_decode_tiff_fax(tiff, 3, stm, wp, wlen);
+ break;
+ case 4:
+ fz_decode_tiff_fax(tiff, 4, stm, wp, wlen);
+ break;
+ case 5:
+ fz_decode_tiff_lzw(tiff, stm, wp, wlen);
+ break;
+ case 6:
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "deprecated JPEG in TIFF compression not supported");
+ break;
+ case 7:
+ fz_decode_tiff_jpeg(tiff, stm, wp, wlen);
+ break;
+ case 8:
+ fz_decode_tiff_flate(tiff, stm, wp, wlen);
+ break;
+ case 32773:
+ fz_decode_tiff_packbits(tiff, stm, wp, wlen);
+ break;
+ default:
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "unknown TIFF compression: %d", tiff->compression);
+ }
+
+ /* scramble the bits back into original order */
+ if (tiff->fillorder == 2)
+ for (i = 0; i < rlen; i++)
+ rp[i] = bitrev[rp[i]];
+
+ wp += tiff->stride * tiff->rowsperstrip;
+ strip ++;
+ }
+
+ /* Predictor (only for LZW and Flate) */
+ if ((tiff->compression == 5 || tiff->compression == 8) && tiff->predictor == 2)
+ {
+ unsigned char *p = tiff->samples;
+ for (i = 0; i < tiff->imagelength; i++)
+ {
+ fz_unpredict_tiff(p, tiff->imagewidth, tiff->samplesperpixel, tiff->bitspersample);
+ p += tiff->stride;
+ }
+ }
+
+ /* RGBPal */
+ if (tiff->photometric == 3 && tiff->colormap)
+ fz_expand_tiff_colormap(tiff);
+
+ /* WhiteIsZero .. invert */
+ if (tiff->photometric == 0)
+ {
+ unsigned char *p = tiff->samples;
+ for (i = 0; i < tiff->imagelength; i++)
+ {
+ fz_invert_tiff(p, tiff->imagewidth, tiff->samplesperpixel, tiff->bitspersample, tiff->extrasamples);
+ p += tiff->stride;
+ }
+ }
+
+ /* Premultiplied transparency */
+ if (tiff->extrasamples == 1)
+ {
+ /* In GhostXPS we undo the premultiplication here; muxps holds
+ * all our images premultiplied by default, so nothing to do.
+ */
+ }
+
+ /* Non-premultiplied transparency */
+ if (tiff->extrasamples == 2)
+ {
+ /* Premultiplied files are corrected for elsewhere */
+ }
+}
+
+static inline int readbyte(struct tiff *tiff)
+{
+ if (tiff->rp < tiff->ep)
+ return *tiff->rp++;
+ return EOF;
+}
+
+static inline unsigned readshort(struct tiff *tiff)
+{
+ unsigned a = readbyte(tiff);
+ unsigned b = readbyte(tiff);
+ if (tiff->order == TII)
+ return (b << 8) | a;
+ return (a << 8) | b;
+}
+
+static inline unsigned readlong(struct tiff *tiff)
+{
+ unsigned a = readbyte(tiff);
+ unsigned b = readbyte(tiff);
+ unsigned c = readbyte(tiff);
+ unsigned d = readbyte(tiff);
+ if (tiff->order == TII)
+ return (d << 24) | (c << 16) | (b << 8) | a;
+ return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+static void
+fz_read_tiff_bytes(unsigned char *p, struct tiff *tiff, unsigned ofs, unsigned n)
+{
+ tiff->rp = tiff->bp + ofs;
+ if (tiff->rp > tiff->ep)
+ tiff->rp = tiff->bp;
+
+ while (n--)
+ *p++ = readbyte(tiff);
+}
+
+static void
+fz_read_tiff_tag_value(unsigned *p, struct tiff *tiff, unsigned type, unsigned ofs, unsigned n)
+{
+ tiff->rp = tiff->bp + ofs;
+ if (tiff->rp > tiff->ep)
+ tiff->rp = tiff->bp;
+
+ while (n--)
+ {
+ switch (type)
+ {
+ case TRATIONAL:
+ *p = readlong(tiff);
+ *p = *p / readlong(tiff);
+ p ++;
+ break;
+ case TBYTE: *p++ = readbyte(tiff); break;
+ case TSHORT: *p++ = readshort(tiff); break;
+ case TLONG: *p++ = readlong(tiff); break;
+ default: *p++ = 0; break;
+ }
+ }
+}
+
+static void
+fz_read_tiff_tag(struct tiff *tiff, unsigned offset)
+{
+ unsigned tag;
+ unsigned type;
+ unsigned count;
+ unsigned value;
+
+ tiff->rp = tiff->bp + offset;
+
+ tag = readshort(tiff);
+ type = readshort(tiff);
+ count = readlong(tiff);
+
+ if ((type == TBYTE && count <= 4) ||
+ (type == TSHORT && count <= 2) ||
+ (type == TLONG && count <= 1))
+ value = tiff->rp - tiff->bp;
+ else
+ value = readlong(tiff);
+
+ switch (tag)
+ {
+ case NewSubfileType:
+ fz_read_tiff_tag_value(&tiff->subfiletype, tiff, type, value, 1);
+ break;
+ case ImageWidth:
+ fz_read_tiff_tag_value(&tiff->imagewidth, tiff, type, value, 1);
+ break;
+ case ImageLength:
+ fz_read_tiff_tag_value(&tiff->imagelength, tiff, type, value, 1);
+ break;
+ case BitsPerSample:
+ fz_read_tiff_tag_value(&tiff->bitspersample, tiff, type, value, 1);
+ break;
+ case Compression:
+ fz_read_tiff_tag_value(&tiff->compression, tiff, type, value, 1);
+ break;
+ case PhotometricInterpretation:
+ fz_read_tiff_tag_value(&tiff->photometric, tiff, type, value, 1);
+ break;
+ case FillOrder:
+ fz_read_tiff_tag_value(&tiff->fillorder, tiff, type, value, 1);
+ break;
+ case SamplesPerPixel:
+ fz_read_tiff_tag_value(&tiff->samplesperpixel, tiff, type, value, 1);
+ break;
+ case RowsPerStrip:
+ fz_read_tiff_tag_value(&tiff->rowsperstrip, tiff, type, value, 1);
+ break;
+ case XResolution:
+ fz_read_tiff_tag_value(&tiff->xresolution, tiff, type, value, 1);
+ break;
+ case YResolution:
+ fz_read_tiff_tag_value(&tiff->yresolution, tiff, type, value, 1);
+ break;
+ case PlanarConfiguration:
+ fz_read_tiff_tag_value(&tiff->planar, tiff, type, value, 1);
+ break;
+ case T4Options:
+ fz_read_tiff_tag_value(&tiff->g3opts, tiff, type, value, 1);
+ break;
+ case T6Options:
+ fz_read_tiff_tag_value(&tiff->g4opts, tiff, type, value, 1);
+ break;
+ case Predictor:
+ fz_read_tiff_tag_value(&tiff->predictor, tiff, type, value, 1);
+ break;
+ case ResolutionUnit:
+ fz_read_tiff_tag_value(&tiff->resolutionunit, tiff, type, value, 1);
+ break;
+ case YCbCrSubSampling:
+ fz_read_tiff_tag_value(tiff->ycbcrsubsamp, tiff, type, value, 2);
+ break;
+ case ExtraSamples:
+ fz_read_tiff_tag_value(&tiff->extrasamples, tiff, type, value, 1);
+ break;
+
+ case ICCProfile:
+ tiff->profile = fz_malloc(tiff->ctx, count);
+ /* ICC profile data type is set to UNDEFINED.
+ * TBYTE reading not correct in fz_read_tiff_tag_value */
+ fz_read_tiff_bytes(tiff->profile, tiff, value, count);
+ tiff->profilesize = count;
+ break;
+
+ case JPEGTables:
+ fz_warn(tiff->ctx, "jpeg tables in tiff not implemented");
+ tiff->jpegtables = tiff->bp + value;
+ tiff->jpegtableslen = count;
+ break;
+
+ case StripOffsets:
+ tiff->stripoffsets = fz_malloc_array(tiff->ctx, count, sizeof(unsigned));
+ fz_read_tiff_tag_value(tiff->stripoffsets, tiff, type, value, count);
+ tiff->stripoffsetslen = count;
+ break;
+
+ case StripByteCounts:
+ tiff->stripbytecounts = fz_malloc_array(tiff->ctx, count, sizeof(unsigned));
+ fz_read_tiff_tag_value(tiff->stripbytecounts, tiff, type, value, count);
+ tiff->stripbytecountslen = count;
+ break;
+
+ case ColorMap:
+ tiff->colormap = fz_malloc_array(tiff->ctx, count, sizeof(unsigned));
+ fz_read_tiff_tag_value(tiff->colormap, tiff, type, value, count);
+ tiff->colormaplen = count;
+ break;
+
+ case TileWidth:
+ case TileLength:
+ case TileOffsets:
+ case TileByteCounts:
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "tiled tiffs not supported");
+
+ default:
+ /* printf("unknown tag: %d t=%d n=%d\n", tag, type, count); */
+ break;
+ }
+}
+
+static void
+fz_swap_tiff_byte_order(unsigned char *buf, int n)
+{
+ int i, t;
+ for (i = 0; i < n; i++)
+ {
+ t = buf[i * 2 + 0];
+ buf[i * 2 + 0] = buf[i * 2 + 1];
+ buf[i * 2 + 1] = t;
+ }
+}
+
+static void
+fz_decode_tiff_header(fz_context *ctx, struct tiff *tiff, unsigned char *buf, int len)
+{
+ unsigned version;
+ unsigned offset;
+ unsigned count;
+ unsigned i;
+
+ memset(tiff, 0, sizeof(struct tiff));
+ tiff->ctx = ctx;
+ tiff->bp = buf;
+ tiff->rp = buf;
+ tiff->ep = buf + len;
+
+ /* tag defaults, where applicable */
+ tiff->bitspersample = 1;
+ tiff->compression = 1;
+ tiff->samplesperpixel = 1;
+ tiff->resolutionunit = 2;
+ tiff->rowsperstrip = 0xFFFFFFFF;
+ tiff->fillorder = 1;
+ tiff->planar = 1;
+ tiff->subfiletype = 0;
+ tiff->predictor = 1;
+ tiff->ycbcrsubsamp[0] = 2;
+ tiff->ycbcrsubsamp[1] = 2;
+
+ /*
+ * Read IFH
+ */
+
+ /* get byte order marker */
+ tiff->order = TII;
+ tiff->order = readshort(tiff);
+ if (tiff->order != TII && tiff->order != TMM)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "not a TIFF file, wrong magic marker");
+
+ /* check version */
+ version = readshort(tiff);
+ if (version != 42)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "not a TIFF file, wrong version marker");
+
+ /* get offset of IFD */
+ offset = readlong(tiff);
+
+ /*
+ * Read IFD
+ */
+
+ tiff->rp = tiff->bp + offset;
+
+ if (tiff->rp < tiff->bp || tiff->rp > tiff->ep)
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "invalid IFD offset %u", offset);
+
+ count = readshort(tiff);
+
+ if (count * 12 > (unsigned)(tiff->ep - tiff->rp))
+ fz_throw(tiff->ctx, FZ_ERROR_GENERIC, "overlarge IFD entry count %u", count);
+
+ offset += 2;
+ for (i = 0; i < count; i++)
+ {
+ fz_read_tiff_tag(tiff, offset);
+ offset += 12;
+ }
+}
+
+fz_pixmap *
+fz_load_tiff(fz_context *ctx, unsigned char *buf, int len)
+{
+ fz_pixmap *image;
+ struct tiff tiff;
+
+ fz_try(ctx)
+ {
+ fz_decode_tiff_header(ctx, &tiff, buf, len);
+
+ /* Decode the image strips */
+
+ if (tiff.rowsperstrip > tiff.imagelength)
+ tiff.rowsperstrip = tiff.imagelength;
+
+ fz_decode_tiff_strips(&tiff);
+
+ /* Byte swap 16-bit images to big endian if necessary */
+ if (tiff.bitspersample == 16)
+ if (tiff.order == TII)
+ fz_swap_tiff_byte_order(tiff.samples, tiff.imagewidth * tiff.imagelength * tiff.samplesperpixel);
+
+ /* Expand into fz_pixmap struct */
+ image = fz_new_pixmap(tiff.ctx, tiff.colorspace, tiff.imagewidth, tiff.imagelength);
+ image->xres = tiff.xresolution;
+ image->yres = tiff.yresolution;
+
+ fz_unpack_tile(image, tiff.samples, tiff.samplesperpixel, tiff.bitspersample, tiff.stride, 0);
+
+ /* We should only do this on non-pre-multiplied images, but files in the wild are bad */
+ if (tiff.extrasamples /* == 2 */)
+ {
+ /* CMYK is a subtractive colorspace, we want additive for premul alpha */
+ if (image->n == 5)
+ {
+ fz_pixmap *rgb = fz_new_pixmap(tiff.ctx, fz_device_rgb(ctx), image->w, image->h);
+ fz_convert_pixmap(tiff.ctx, rgb, image);
+ rgb->xres = image->xres;
+ rgb->yres = image->yres;
+ fz_drop_pixmap(ctx, image);
+ image = rgb;
+ }
+ fz_premultiply_pixmap(ctx, image);
+ }
+ }
+ fz_always(ctx)
+ {
+ /* Clean up scratch memory */
+ if (tiff.colormap) fz_free(ctx, tiff.colormap);
+ if (tiff.stripoffsets) fz_free(ctx, tiff.stripoffsets);
+ if (tiff.stripbytecounts) fz_free(ctx, tiff.stripbytecounts);
+ if (tiff.samples) fz_free(ctx, tiff.samples);
+ if (tiff.profile) fz_free(ctx, tiff.profile);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "out of memory loading tiff");
+ }
+
+ return image;
+}
+
+void
+fz_load_tiff_info(fz_context *ctx, unsigned char *buf, int len, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
+{
+ struct tiff tiff;
+
+ fz_try(ctx)
+ {
+ fz_decode_tiff_header(ctx, &tiff, buf, len);
+
+ *wp = tiff.imagewidth;
+ *hp = tiff.imagelength;
+ *xresp = tiff.xresolution;
+ *yresp = tiff.yresolution;
+ *cspacep = tiff.colorspace;
+ }
+ fz_always(ctx)
+ {
+ /* Clean up scratch memory */
+ if (tiff.colormap) fz_free(ctx, tiff.colormap);
+ if (tiff.stripoffsets) fz_free(ctx, tiff.stripoffsets);
+ if (tiff.stripbytecounts) fz_free(ctx, tiff.stripbytecounts);
+ if (tiff.samples) fz_free(ctx, tiff.samples);
+ if (tiff.profile) fz_free(ctx, tiff.profile);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "out of memory loading tiff");
+ }
+}
diff --git a/source/fitz/memento.c b/source/fitz/memento.c
new file mode 100644
index 00000000..6a159f10
--- /dev/null
+++ b/source/fitz/memento.c
@@ -0,0 +1,1535 @@
+/* Copyright (C) 2001-2013 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* Inspired by Fortify by Simon P Bullen. */
+
+/* Set the following if you're only looking for leaks, not memory overwrites
+ * to speed the operation */
+/* #define MEMENTO_LEAKONLY */
+
+/* Don't keep blocks around if they'd mean losing more than a quarter of
+ * the freelist. */
+#define MEMENTO_FREELIST_MAX_SINGLE_BLOCK (MEMENTO_FREELIST_MAX/4)
+
+#define COMPILING_MEMENTO_C
+
+/* We have some GS specific tweaks; more for the GS build environment than
+ * anything else. */
+#undef MEMENTO_GS_HACKS
+
+#ifdef MEMENTO_GS_HACKS
+/* For GS we include malloc_.h. Anyone else would just include memento.h */
+#include "malloc_.h"
+#ifdef __MACH__
+#include <string.h>
+#else
+#ifndef memset
+void *memset(void *,int,size_t);
+#endif
+#endif
+int atexit(void (*)(void));
+#else
+#include "mupdf/memento.h"
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
+#ifdef MEMENTO_ANDROID
+#include <android/log.h>
+
+static int
+android_fprintf(FILE *file, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ __android_log_vprint(ANDROID_LOG_ERROR,"memento", fmt, args);
+ va_end(args);
+}
+
+#define fprintf android_fprintf
+#define MEMENTO_STACKTRACE_METHOD 0
+#endif
+
+#ifndef MEMENTO_STACKTRACE_METHOD
+#ifdef __GNUC__
+#define MEMENTO_STACKTRACE_METHOD 1
+#endif
+#endif
+
+#if defined(__linux__)
+#define MEMENTO_HAS_FORK
+#elif defined(__APPLE__) && defined(__MACH__)
+#define MEMENTO_HAS_FORK
+#endif
+
+/* Define the underlying allocators, just in case */
+void *MEMENTO_UNDERLYING_MALLOC(size_t);
+void MEMENTO_UNDERLYING_FREE(void *);
+void *MEMENTO_UNDERLYING_REALLOC(void *,size_t);
+void *MEMENTO_UNDERLYING_CALLOC(size_t,size_t);
+
+/* And some other standard functions we use. We don't include the header
+ * files, just in case they pull in unexpected others. */
+int atoi(const char *);
+char *getenv(const char *);
+
+/* How far to search for pointers in each block when calculating nestings */
+/* mupdf needs at least 34000ish (sizeof(fz_shade))/ */
+#define MEMENTO_PTRSEARCH 65536
+
+#ifndef MEMENTO_MAXPATTERN
+#define MEMENTO_MAXPATTERN 0
+#endif
+
+#ifdef MEMENTO
+
+#ifdef MEMENTO_GS_HACKS
+#include "valgrind.h"
+#else
+#ifdef HAVE_VALGRIND
+#include "valgrind/memcheck.h"
+#else
+#define VALGRIND_MAKE_MEM_NOACCESS(p,s) do { } while (0==1)
+#define VALGRIND_MAKE_MEM_UNDEFINED(p,s) do { } while (0==1)
+#define VALGRIND_MAKE_MEM_DEFINED(p,s) do { } while (0==1)
+#endif
+#endif
+
+enum {
+ Memento_PreSize = 16,
+ Memento_PostSize = 16
+};
+
+enum {
+ Memento_Flag_OldBlock = 1,
+ Memento_Flag_HasParent = 2,
+ Memento_Flag_BreakOnFree = 4,
+ Memento_Flag_BreakOnRealloc = 8
+};
+
+/* When we list leaked blocks at the end of execution, we search for pointers
+ * between blocks in order to be able to give a nice nested view.
+ * Unfortunately, if you have are running your own allocator (such as
+ * ghostscripts chunk allocator) you can often find that the header of the
+ * block always contains pointers to next or previous blocks. This tends to
+ * mean the nesting displayed is "uninteresting" at best :)
+ *
+ * As a hack to get around this, we have a define MEMENTO_SKIP_SEARCH that
+ * indicates how many bytes to skip over at the start of the chunk.
+ * This may cause us to miss true nestings, but such is life...
+ */
+#ifndef MEMENTO_SEARCH_SKIP
+#ifdef MEMENTO_GS_HACKS
+#define MEMENTO_SEARCH_SKIP (2*sizeof(void *))
+#else
+#define MEMENTO_SEARCH_SKIP 0
+#endif
+#endif
+
+typedef struct Memento_BlkHeader Memento_BlkHeader;
+
+struct Memento_BlkHeader
+{
+ size_t rawsize;
+ int sequence;
+ int lastCheckedOK;
+ int flags;
+ Memento_BlkHeader *next;
+ Memento_BlkHeader *parent; /* Only used while printing out nested list */
+
+ const char *label;
+
+ /* Entries for nesting display calculations */
+ Memento_BlkHeader *child;
+ Memento_BlkHeader *sibling;
+
+ char preblk[Memento_PreSize];
+};
+
+/* In future this could (should) be a smarter data structure, like, say,
+ * splay trees. For now, we use a list.
+ */
+typedef struct Memento_Blocks
+{
+ Memento_BlkHeader *head;
+ Memento_BlkHeader **tail;
+} Memento_Blocks;
+
+/* And our global structure */
+static struct {
+ int inited;
+ Memento_Blocks used;
+ Memento_Blocks free;
+ size_t freeListSize;
+ int sequence;
+ int paranoia;
+ int paranoidAt;
+ int countdown;
+ int lastChecked;
+ int breakAt;
+ int failAt;
+ int failing;
+ int nextFailAt;
+ int squeezeAt;
+ int squeezing;
+ int segv;
+ int pattern;
+ int nextPattern;
+ int patternBit;
+ size_t maxMemory;
+ size_t alloc;
+ size_t peakAlloc;
+ size_t totalAlloc;
+ size_t numMallocs;
+ size_t numFrees;
+ size_t numReallocs;
+} globals;
+
+#define MEMENTO_EXTRASIZE (sizeof(Memento_BlkHeader) + Memento_PostSize)
+
+/* Round up size S to the next multiple of N (where N is a power of 2) */
+#define MEMENTO_ROUNDUP(S,N) ((S + N-1)&~(N-1))
+
+#define MEMBLK_SIZE(s) MEMENTO_ROUNDUP(s + MEMENTO_EXTRASIZE, MEMENTO_MAXALIGN)
+
+#define MEMBLK_FROMBLK(B) (&((Memento_BlkHeader*)(void *)(B))[-1])
+#define MEMBLK_TOBLK(B) ((void*)(&((Memento_BlkHeader*)(void*)(B))[1]))
+#define MEMBLK_POSTPTR(B) \
+ (&((char *)(void *)(B))[(B)->rawsize + sizeof(Memento_BlkHeader)])
+
+void Memento_breakpoint(void)
+{
+ /* A handy externally visible function for breakpointing */
+#if 0 /* Enable this to force automatic breakpointing */
+#ifdef DEBUG
+#ifdef _MSC_VER
+ __asm int 3;
+#endif
+#endif
+#endif
+}
+
+static void Memento_addBlockHead(Memento_Blocks *blks,
+ Memento_BlkHeader *b,
+ int type)
+{
+ if (blks->tail == &blks->head) {
+ /* Adding into an empty list, means the tail changes too */
+ blks->tail = &b->next;
+ }
+ b->next = blks->head;
+ blks->head = b;
+#ifndef MEMENTO_LEAKONLY
+ memset(b->preblk, MEMENTO_PREFILL, Memento_PreSize);
+ memset(MEMBLK_POSTPTR(b), MEMENTO_POSTFILL, Memento_PostSize);
+#endif
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(b), Memento_PostSize);
+ if (type == 0) { /* malloc */
+ VALGRIND_MAKE_MEM_UNDEFINED(MEMBLK_TOBLK(b), b->rawsize);
+ } else if (type == 1) { /* free */
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_TOBLK(b), b->rawsize);
+ }
+ VALGRIND_MAKE_MEM_NOACCESS(b, sizeof(Memento_BlkHeader));
+}
+
+static void Memento_addBlockTail(Memento_Blocks *blks,
+ Memento_BlkHeader *b,
+ int type)
+{
+ VALGRIND_MAKE_MEM_DEFINED(blks->tail, sizeof(Memento_BlkHeader *));
+ *blks->tail = b;
+ blks->tail = &b->next;
+ b->next = NULL;
+ VALGRIND_MAKE_MEM_NOACCESS(blks->tail, sizeof(Memento_BlkHeader *));
+#ifndef MEMENTO_LEAKONLY
+ memset(b->preblk, MEMENTO_PREFILL, Memento_PreSize);
+ memset(MEMBLK_POSTPTR(b), MEMENTO_POSTFILL, Memento_PostSize);
+#endif
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(b), Memento_PostSize);
+ if (type == 0) { /* malloc */
+ VALGRIND_MAKE_MEM_UNDEFINED(MEMBLK_TOBLK(b), b->rawsize);
+ } else if (type == 1) { /* free */
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_TOBLK(b), b->rawsize);
+ }
+ VALGRIND_MAKE_MEM_NOACCESS(b, sizeof(Memento_BlkHeader));
+}
+
+typedef struct BlkCheckData {
+ int found;
+ int preCorrupt;
+ int postCorrupt;
+ int freeCorrupt;
+ int index;
+} BlkCheckData;
+
+static int Memento_Internal_checkAllocedBlock(Memento_BlkHeader *b, void *arg)
+{
+#ifndef MEMENTO_LEAKONLY
+ int i;
+ char *p;
+ int corrupt = 0;
+ BlkCheckData *data = (BlkCheckData *)arg;
+
+ p = b->preblk;
+ i = Memento_PreSize;
+ do {
+ corrupt |= (*p++ ^ (char)MEMENTO_PREFILL);
+ } while (--i);
+ if (corrupt) {
+ data->preCorrupt = 1;
+ }
+ p = MEMBLK_POSTPTR(b);
+ i = Memento_PreSize;
+ do {
+ corrupt |= (*p++ ^ (char)MEMENTO_POSTFILL);
+ } while (--i);
+ if (corrupt) {
+ data->postCorrupt = 1;
+ }
+ if ((data->freeCorrupt | data->preCorrupt | data->postCorrupt) == 0) {
+ b->lastCheckedOK = globals.sequence;
+ }
+ data->found |= 1;
+#endif
+ return 0;
+}
+
+static int Memento_Internal_checkFreedBlock(Memento_BlkHeader *b, void *arg)
+{
+#ifndef MEMENTO_LEAKONLY
+ int i;
+ char *p;
+ BlkCheckData *data = (BlkCheckData *)arg;
+
+ p = MEMBLK_TOBLK(b);
+ i = b->rawsize;
+ /* Attempt to speed this up by checking an (aligned) int at a time */
+ do {
+ if (((size_t)p) & 1) {
+ if (*p++ != (char)MEMENTO_FREEFILL)
+ break;
+ i--;
+ if (i == 0)
+ break;
+ }
+ if ((i >= 2) && (((size_t)p) & 2)) {
+ if (*(short *)p != (short)(MEMENTO_FREEFILL | (MEMENTO_FREEFILL<<8)))
+ goto mismatch;
+ p += 2;
+ i -= 2;
+ if (i == 0)
+ break;
+ }
+ i -= 4;
+ while (i >= 0) {
+ if (*(int *)p != (MEMENTO_FREEFILL |
+ (MEMENTO_FREEFILL<<8) |
+ (MEMENTO_FREEFILL<<16) |
+ (MEMENTO_FREEFILL<<24)))
+ goto mismatch;
+ p += 4;
+ i -= 4;
+ }
+ i += 4;
+ if ((i >= 2) && (((size_t)p) & 2)) {
+ if (*(short *)p != (short)(MEMENTO_FREEFILL | (MEMENTO_FREEFILL<<8)))
+ goto mismatch;
+ p += 2;
+ i -= 2;
+ }
+mismatch:
+ while (i) {
+ if (*p++ != (char)MEMENTO_FREEFILL)
+ break;
+ i--;
+ }
+ } while (0);
+ if (i) {
+ data->freeCorrupt = 1;
+ data->index = b->rawsize-i;
+ }
+ return Memento_Internal_checkAllocedBlock(b, arg);
+#else
+ return 0;
+#endif
+}
+
+static void Memento_removeBlock(Memento_Blocks *blks,
+ Memento_BlkHeader *b)
+{
+ Memento_BlkHeader *head = blks->head;
+ Memento_BlkHeader *prev = NULL;
+ while ((head) && (head != b)) {
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head));
+ prev = head;
+ head = head->next;
+ VALGRIND_MAKE_MEM_NOACCESS(prev, sizeof(*prev));
+ }
+ if (head == NULL) {
+ /* FAIL! Will have been reported to user earlier, so just exit. */
+ return;
+ }
+ VALGRIND_MAKE_MEM_DEFINED(blks->tail, sizeof(*blks->tail));
+ if (*blks->tail == head) {
+ /* Removing the tail of the list */
+ if (prev == NULL) {
+ /* Which is also the head */
+ blks->tail = &blks->head;
+ } else {
+ /* Which isn't the head */
+ blks->tail = &prev->next;
+ }
+ }
+ if (prev == NULL) {
+ /* Removing from the head of the list */
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head));
+ blks->head = head->next;
+ VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(*head));
+ } else {
+ /* Removing from not-the-head */
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head));
+ VALGRIND_MAKE_MEM_DEFINED(prev, sizeof(*prev));
+ prev->next = head->next;
+ VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(*head));
+ VALGRIND_MAKE_MEM_NOACCESS(prev, sizeof(*prev));
+ }
+}
+
+static int Memento_Internal_makeSpace(size_t space)
+{
+ /* If too big, it can never go on the freelist */
+ if (space > MEMENTO_FREELIST_MAX_SINGLE_BLOCK)
+ return 0;
+ /* Pretend we added it on. */
+ globals.freeListSize += space;
+ /* Ditch blocks until it fits within our limit */
+ while (globals.freeListSize > MEMENTO_FREELIST_MAX) {
+ Memento_BlkHeader *head = globals.free.head;
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(*head));
+ globals.free.head = head->next;
+ globals.freeListSize -= MEMBLK_SIZE(head->rawsize);
+ MEMENTO_UNDERLYING_FREE(head);
+ }
+ /* Make sure we haven't just completely emptied the free list */
+ /* (This should never happen, but belt and braces... */
+ if (globals.free.head == NULL)
+ globals.free.tail = &globals.free.head;
+ return 1;
+}
+
+static int Memento_appBlocks(Memento_Blocks *blks,
+ int (*app)(Memento_BlkHeader *,
+ void *),
+ void *arg)
+{
+ Memento_BlkHeader *head = blks->head;
+ Memento_BlkHeader *next;
+ int result;
+ while (head) {
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(Memento_BlkHeader));
+ VALGRIND_MAKE_MEM_DEFINED(MEMBLK_TOBLK(head),
+ head->rawsize + Memento_PostSize);
+ result = app(head, arg);
+ next = head->next;
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(head), Memento_PostSize);
+ VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(Memento_BlkHeader));
+ if (result)
+ return result;
+ head = next;
+ }
+ return 0;
+}
+
+static int Memento_appBlock(Memento_Blocks *blks,
+ int (*app)(Memento_BlkHeader *,
+ void *),
+ void *arg,
+ Memento_BlkHeader *b)
+{
+ Memento_BlkHeader *head = blks->head;
+ Memento_BlkHeader *next;
+ int result;
+ while (head && head != b) {
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(Memento_BlkHeader));
+ next = head->next;
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(head), Memento_PostSize);
+ head = next;
+ }
+ if (head == b) {
+ VALGRIND_MAKE_MEM_DEFINED(head, sizeof(Memento_BlkHeader));
+ VALGRIND_MAKE_MEM_DEFINED(MEMBLK_TOBLK(head),
+ head->rawsize + Memento_PostSize);
+ result = app(head, arg);
+ VALGRIND_MAKE_MEM_NOACCESS(MEMBLK_POSTPTR(head), Memento_PostSize);
+ VALGRIND_MAKE_MEM_NOACCESS(head, sizeof(Memento_BlkHeader));
+ return result;
+ }
+ return 0;
+}
+
+static void showBlock(Memento_BlkHeader *b, int space)
+{
+ fprintf(stderr, "0x%p:(size=%d,num=%d)",
+ MEMBLK_TOBLK(b), (int)b->rawsize, b->sequence);
+ if (b->label)
+ fprintf(stderr, "%c(%s)", space, b->label);
+}
+
+static void blockDisplay(Memento_BlkHeader *b, int n)
+{
+ n++;
+ while (n > 40)
+ {
+ fprintf(stderr, "*");
+ n -= 40;
+ }
+ while(n > 0)
+ {
+ int i = n;
+ if (i > 32)
+ i = 32;
+ n -= i;
+ fprintf(stderr, "%s", &" "[32-i]);
+ }
+ showBlock(b, '\t');
+ fprintf(stderr, "\n");
+}
+
+static int Memento_listBlock(Memento_BlkHeader *b,
+ void *arg)
+{
+ int *counts = (int *)arg;
+ blockDisplay(b, 0);
+ counts[0]++;
+ counts[1]+= b->rawsize;
+ return 0;
+}
+
+static void doNestedDisplay(Memento_BlkHeader *b,
+ int depth)
+{
+ /* Try and avoid recursion if we can help it */
+ do {
+ blockDisplay(b, depth);
+ if (b->sibling) {
+ if (b->child)
+ doNestedDisplay(b->child, depth+1);
+ b = b->sibling;
+ } else {
+ b = b->child;
+ depth++;
+ }
+ } while (b);
+}
+
+static int ptrcmp(const void *a_, const void *b_)
+{
+ const char **a = (const char **)a_;
+ const char **b = (const char **)b_;
+ return (int)(*a-*b);
+}
+
+static
+int Memento_listBlocksNested(void)
+{
+ int count, size, i;
+ Memento_BlkHeader *b;
+ void **blocks, *minptr, *maxptr;
+ long mask;
+
+ /* Count the blocks */
+ count = 0;
+ size = 0;
+ for (b = globals.used.head; b; b = b->next) {
+ size += b->rawsize;
+ count++;
+ }
+
+ /* Make our block list */
+ blocks = MEMENTO_UNDERLYING_MALLOC(sizeof(void *) * count);
+ if (blocks == NULL)
+ return 1;
+
+ /* Populate our block list */
+ b = globals.used.head;
+ minptr = maxptr = MEMBLK_TOBLK(b);
+ mask = (long)minptr;
+ for (i = 0; b; b = b->next, i++) {
+ void *p = MEMBLK_TOBLK(b);
+ mask &= (long)p;
+ if (p < minptr)
+ minptr = p;
+ if (p > maxptr)
+ maxptr = p;
+ blocks[i] = p;
+ b->flags &= ~Memento_Flag_HasParent;
+ b->child = NULL;
+ b->sibling = NULL;
+ b->parent = NULL;
+ }
+ qsort(blocks, count, sizeof(void *), ptrcmp);
+
+ /* Now, calculate tree */
+ for (b = globals.used.head; b; b = b->next) {
+ char *p = MEMBLK_TOBLK(b);
+ int end = (b->rawsize < MEMENTO_PTRSEARCH ? b->rawsize : MEMENTO_PTRSEARCH);
+ for (i = MEMENTO_SEARCH_SKIP; i < end; i += sizeof(void *)) {
+ void *q = *(void **)(&p[i]);
+ void **r;
+
+ /* Do trivial checks on pointer */
+ if ((mask & (int)q) != mask || q < minptr || q > maxptr)
+ continue;
+
+ /* Search for pointer */
+ r = bsearch(&q, blocks, count, sizeof(void *), ptrcmp);
+ if (r) {
+ /* Found child */
+ Memento_BlkHeader *child = MEMBLK_FROMBLK(*r);
+ Memento_BlkHeader *parent;
+
+ /* We're assuming tree structure, not graph - ignore second
+ * and subsequent pointers. */
+ if (child->parent != NULL)
+ continue;
+ if (child->flags & Memento_Flag_HasParent)
+ continue;
+
+ /* We're also assuming acyclicness here. If this is one of
+ * our parents, ignore it. */
+ parent = b->parent;
+ while (parent != NULL && parent != child)
+ parent = parent->parent;
+ if (parent == child)
+ continue;
+
+ child->sibling = b->child;
+ b->child = child;
+ child->parent = b;
+ child->flags |= Memento_Flag_HasParent;
+ }
+ }
+ }
+
+ /* Now display with nesting */
+ for (b = globals.used.head; b; b = b->next) {
+ if ((b->flags & Memento_Flag_HasParent) == 0)
+ doNestedDisplay(b, 0);
+ }
+ fprintf(stderr, " Total number of blocks = %d\n", count);
+ fprintf(stderr, " Total size of blocks = %d\n", size);
+
+ MEMENTO_UNDERLYING_FREE(blocks);
+ return 0;
+}
+
+void Memento_listBlocks(void)
+{
+ fprintf(stderr, "Allocated blocks:\n");
+ if (Memento_listBlocksNested())
+ {
+ int counts[2];
+ counts[0] = 0;
+ counts[1] = 0;
+ Memento_appBlocks(&globals.used, Memento_listBlock, &counts[0]);
+ fprintf(stderr, " Total number of blocks = %d\n", counts[0]);
+ fprintf(stderr, " Total size of blocks = %d\n", counts[1]);
+ }
+}
+
+static int Memento_listNewBlock(Memento_BlkHeader *b,
+ void *arg)
+{
+ if (b->flags & Memento_Flag_OldBlock)
+ return 0;
+ b->flags |= Memento_Flag_OldBlock;
+ return Memento_listBlock(b, arg);
+}
+
+void Memento_listNewBlocks(void) {
+ int counts[2];
+ counts[0] = 0;
+ counts[1] = 0;
+ fprintf(stderr, "Blocks allocated and still extant since last list:\n");
+ Memento_appBlocks(&globals.used, Memento_listNewBlock, &counts[0]);
+ fprintf(stderr, " Total number of blocks = %d\n", counts[0]);
+ fprintf(stderr, " Total size of blocks = %d\n", counts[1]);
+}
+
+static void Memento_endStats(void)
+{
+ fprintf(stderr, "Total memory malloced = %u bytes\n", (unsigned int)globals.totalAlloc);
+ fprintf(stderr, "Peak memory malloced = %u bytes\n", (unsigned int)globals.peakAlloc);
+ fprintf(stderr, "%u mallocs, %u frees, %u reallocs\n", (unsigned int)globals.numMallocs,
+ (unsigned int)globals.numFrees, (unsigned int)globals.numReallocs);
+ fprintf(stderr, "Average allocation size %u bytes\n", (unsigned int)
+ (globals.numMallocs != 0 ? globals.totalAlloc/globals.numMallocs: 0));
+}
+
+void Memento_stats(void)
+{
+ fprintf(stderr, "Current memory malloced = %u bytes\n", (unsigned int)globals.alloc);
+ Memento_endStats();
+}
+
+static void Memento_fin(void)
+{
+ Memento_checkAllMemory();
+ Memento_endStats();
+ if (globals.used.head != NULL) {
+ Memento_listBlocks();
+ Memento_breakpoint();
+ }
+ if (globals.segv) {
+ fprintf(stderr, "Memory dumped on SEGV while squeezing @ %d\n", globals.failAt);
+ } else if (globals.squeezing) {
+ if (globals.pattern == 0)
+ fprintf(stderr, "Memory squeezing @ %d complete\n", globals.squeezeAt);
+ else
+ fprintf(stderr, "Memory squeezing @ %d (%d) complete\n", globals.squeezeAt, globals.pattern);
+ }
+ if (globals.failing)
+ {
+ fprintf(stderr, "MEMENTO_FAILAT=%d\n", globals.failAt);
+ fprintf(stderr, "MEMENTO_PATTERN=%d\n", globals.pattern);
+ }
+ if (globals.nextFailAt != 0)
+ {
+ fprintf(stderr, "MEMENTO_NEXTFAILAT=%d\n", globals.nextFailAt);
+ fprintf(stderr, "MEMENTO_NEXTPATTERN=%d\n", globals.nextPattern);
+ }
+}
+
+static void Memento_inited(void)
+{
+ /* A good place for a breakpoint */
+}
+
+static void Memento_init(void)
+{
+ char *env;
+ memset(&globals, 0, sizeof(globals));
+ globals.inited = 1;
+ globals.used.head = NULL;
+ globals.used.tail = &globals.used.head;
+ globals.free.head = NULL;
+ globals.free.tail = &globals.free.head;
+ globals.sequence = 0;
+ globals.countdown = 1024;
+
+ env = getenv("MEMENTO_FAILAT");
+ globals.failAt = (env ? atoi(env) : 0);
+
+ env = getenv("MEMENTO_PARANOIA");
+ globals.paranoia = (env ? atoi(env) : 0);
+ if (globals.paranoia == 0)
+ globals.paranoia = 1024;
+
+ env = getenv("MEMENTO_PARANOIDAT");
+ globals.paranoidAt = (env ? atoi(env) : 0);
+
+ env = getenv("MEMENTO_SQUEEZEAT");
+ globals.squeezeAt = (env ? atoi(env) : 0);
+
+ env = getenv("MEMENTO_PATTERN");
+ globals.pattern = (env ? atoi(env) : 0);
+
+ env = getenv("MEMENTO_MAXMEMORY");
+ globals.maxMemory = (env ? atoi(env) : 0);
+
+ atexit(Memento_fin);
+
+ Memento_inited();
+}
+
+#ifdef MEMENTO_HAS_FORK
+#include <unistd.h>
+#include <sys/wait.h>
+#ifdef MEMENTO_STACKTRACE_METHOD
+#if MEMENTO_STACKTRACE_METHOD == 1
+#include <signal.h>
+#endif
+#endif
+
+/* FIXME: Find some portable way of getting this */
+/* MacOSX has 10240, Ubuntu seems to have 256 */
+#define OPEN_MAX 10240
+
+/* stashed_map[j] = i means that filedescriptor i-1 was duplicated to j */
+int stashed_map[OPEN_MAX];
+
+#ifdef MEMENTO_STACKTRACE_METHOD
+#if MEMENTO_STACKTRACE_METHOD == 1
+extern size_t backtrace(void **, int);
+extern void backtrace_symbols_fd(void **, size_t, int);
+#endif
+#endif
+
+static void Memento_signal(void)
+{
+ fprintf(stderr, "SEGV after Memory squeezing @ %d\n", globals.squeezeAt);
+
+#ifdef MEMENTO_STACKTRACE_METHOD
+#if MEMENTO_STACKTRACE_METHOD == 1
+ {
+ void *array[100];
+ size_t size;
+
+ size = backtrace(array, 100);
+ fprintf(stderr, "------------------------------------------------------------------------\n");
+ fprintf(stderr, "Backtrace:\n");
+ backtrace_symbols_fd(array, size, 2);
+ fprintf(stderr, "------------------------------------------------------------------------\n");
+ }
+#endif
+#endif
+
+ exit(1);
+}
+
+static int squeeze(void)
+{
+ pid_t pid;
+ int i, status;
+
+ if (globals.patternBit < 0)
+ return 1;
+ if (globals.squeezing && globals.patternBit >= MEMENTO_MAXPATTERN)
+ return 1;
+
+ if (globals.patternBit == 0)
+ globals.squeezeAt = globals.sequence;
+
+ if (!globals.squeezing) {
+ fprintf(stderr, "Memory squeezing @ %d\n", globals.squeezeAt);
+ } else
+ fprintf(stderr, "Memory squeezing @ %d (%x,%x)\n", globals.squeezeAt, globals.pattern, globals.patternBit);
+
+ /* When we fork below, the child is going to snaffle all our file pointers
+ * and potentially corrupt them. Let's make copies of all of them before
+ * we fork, so we can restore them when we restart. */
+ for (i = 0; i < OPEN_MAX; i++) {
+ if (stashed_map[i] == 0) {
+ int j = dup(i);
+ stashed_map[j] = i+1;
+ }
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ /* Child */
+ signal(SIGSEGV, Memento_signal);
+ /* In the child, we always fail the next allocation. */
+ if (globals.patternBit == 0) {
+ globals.patternBit = 1;
+ } else
+ globals.patternBit <<= 1;
+ globals.squeezing = 1;
+ return 1;
+ }
+
+ /* In the parent if we hit another allocation, pass it (and record the
+ * fact we passed it in the pattern. */
+ globals.pattern |= globals.patternBit;
+ globals.patternBit <<= 1;
+
+ /* Wait for pid to finish */
+ waitpid(pid, &status, 0);
+
+ if (status != 0) {
+ fprintf(stderr, "Child status=%d\n", status);
+ }
+
+ /* Put the files back */
+ for (i = 0; i < OPEN_MAX; i++) {
+ if (stashed_map[i] != 0) {
+ dup2(i, stashed_map[i]-1);
+ close(i);
+ stashed_map[i] = 0;
+ }
+ }
+
+ return 0;
+}
+#else
+#include <signal.h>
+
+static void Memento_signal(void)
+{
+ globals.segv = 1;
+ /* If we just return from this function the SEGV will be unhandled, and
+ * we'll launch into whatever JIT debugging system the OS provides. At
+ * least fprintf(stderr, something useful first. If MEMENTO_NOJIT is set, then
+ * just exit to avoid the JIT (and get the usual atexit handling). */
+ if (getenv("MEMENTO_NOJIT"))
+ exit(1);
+ else
+ Memento_fin();
+}
+
+int squeeze(void)
+{
+ fprintf(stderr, "Memento memory squeezing disabled as no fork!\n");
+ return 0;
+}
+#endif
+
+static void Memento_startFailing(void)
+{
+ if (!globals.failing) {
+ fprintf(stderr, "Starting to fail...\n");
+ fflush(stderr);
+ globals.failing = 1;
+ globals.failAt = globals.sequence;
+ globals.nextFailAt = globals.sequence+1;
+ globals.pattern = 0;
+ globals.patternBit = 0;
+ signal(SIGSEGV, Memento_signal);
+ signal(SIGABRT, Memento_signal);
+ Memento_breakpoint();
+ }
+}
+
+static void Memento_event(void)
+{
+ globals.sequence++;
+ if ((globals.sequence >= globals.paranoidAt) && (globals.paranoidAt != 0)) {
+ globals.paranoia = 1;
+ globals.countdown = 1;
+ }
+ if (--globals.countdown == 0) {
+ Memento_checkAllMemory();
+ globals.countdown = globals.paranoia;
+ }
+
+ if (globals.sequence == globals.breakAt) {
+ fprintf(stderr, "Breaking at event %d\n", globals.breakAt);
+ Memento_breakpoint();
+ }
+}
+
+int Memento_breakAt(int event)
+{
+ globals.breakAt = event;
+ return event;
+}
+
+void *Memento_label(void *ptr, const char *label)
+{
+ Memento_BlkHeader *block;
+
+ if (ptr == NULL)
+ return NULL;
+ block = MEMBLK_FROMBLK(ptr);
+ block->label = label;
+ return ptr;
+}
+
+int Memento_failThisEvent(void)
+{
+ int failThisOne;
+
+ if (!globals.inited)
+ Memento_init();
+
+ Memento_event();
+
+ if ((globals.sequence >= globals.failAt) && (globals.failAt != 0))
+ Memento_startFailing();
+ if ((globals.sequence >= globals.squeezeAt) && (globals.squeezeAt != 0)) {
+ return squeeze();
+ }
+
+ if (!globals.failing)
+ return 0;
+ failThisOne = ((globals.patternBit & globals.pattern) == 0);
+ /* If we are failing, and we've reached the end of the pattern and we've
+ * still got bits available in the pattern word, and we haven't already
+ * set a nextPattern, then extend the pattern. */
+ if (globals.failing &&
+ ((~(globals.patternBit-1) & globals.pattern) == 0) &&
+ (globals.patternBit != 0) &&
+ globals.nextPattern == 0)
+ {
+ /* We'll fail this one, and set the 'next' one to pass it. */
+ globals.nextFailAt = globals.failAt;
+ globals.nextPattern = globals.pattern | globals.patternBit;
+ }
+ globals.patternBit = (globals.patternBit ? globals.patternBit << 1 : 1);
+
+ return failThisOne;
+}
+
+void *Memento_malloc(size_t s)
+{
+ Memento_BlkHeader *memblk;
+ size_t smem = MEMBLK_SIZE(s);
+
+ if (Memento_failThisEvent())
+ return NULL;
+
+ if (s == 0)
+ return NULL;
+
+ globals.numMallocs++;
+
+ if (globals.maxMemory != 0 && globals.alloc + s > globals.maxMemory)
+ return NULL;
+
+ memblk = MEMENTO_UNDERLYING_MALLOC(smem);
+ if (memblk == NULL)
+ return NULL;
+
+ globals.alloc += s;
+ globals.totalAlloc += s;
+ if (globals.peakAlloc < globals.alloc)
+ globals.peakAlloc = globals.alloc;
+#ifndef MEMENTO_LEAKONLY
+ memset(MEMBLK_TOBLK(memblk), MEMENTO_ALLOCFILL, s);
+#endif
+ memblk->rawsize = s;
+ memblk->sequence = globals.sequence;
+ memblk->lastCheckedOK = memblk->sequence;
+ memblk->flags = 0;
+ memblk->label = 0;
+ memblk->child = NULL;
+ memblk->sibling = NULL;
+ Memento_addBlockHead(&globals.used, memblk, 0);
+ return MEMBLK_TOBLK(memblk);
+}
+
+void *Memento_calloc(size_t n, size_t s)
+{
+ void *block = Memento_malloc(n*s);
+
+ if (block)
+ memset(block, 0, n*s);
+ return block;
+}
+
+static int checkBlock(Memento_BlkHeader *memblk, const char *action)
+{
+#ifndef MEMENTO_LEAKONLY
+ BlkCheckData data;
+
+ memset(&data, 0, sizeof(data));
+ Memento_appBlock(&globals.used, Memento_Internal_checkAllocedBlock,
+ &data, memblk);
+ if (!data.found) {
+ /* Failure! */
+ fprintf(stderr, "Attempt to %s block ", action);
+ showBlock(memblk, 32);
+ Memento_breakpoint();
+ return 1;
+ } else if (data.preCorrupt || data.postCorrupt) {
+ fprintf(stderr, "Block ");
+ showBlock(memblk, ' ');
+ fprintf(stderr, " found to be corrupted on %s!\n", action);
+ if (data.preCorrupt) {
+ fprintf(stderr, "Preguard corrupted\n");
+ }
+ if (data.postCorrupt) {
+ fprintf(stderr, "Postguard corrupted\n");
+ }
+ fprintf(stderr, "Block last checked OK at allocation %d. Now %d.\n",
+ memblk->lastCheckedOK, globals.sequence);
+ Memento_breakpoint();
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+void Memento_free(void *blk)
+{
+ Memento_BlkHeader *memblk;
+
+ if (!globals.inited)
+ Memento_init();
+
+ Memento_event();
+
+ if (blk == NULL)
+ return;
+
+ memblk = MEMBLK_FROMBLK(blk);
+ VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk));
+ if (checkBlock(memblk, "free"))
+ return;
+
+ if (memblk->flags & Memento_Flag_BreakOnFree)
+ Memento_breakpoint();
+
+ VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk));
+ globals.alloc -= memblk->rawsize;
+ globals.numFrees++;
+
+ Memento_removeBlock(&globals.used, memblk);
+
+ VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk));
+ if (Memento_Internal_makeSpace(MEMBLK_SIZE(memblk->rawsize))) {
+ VALGRIND_MAKE_MEM_DEFINED(memblk, sizeof(*memblk));
+ VALGRIND_MAKE_MEM_DEFINED(MEMBLK_TOBLK(memblk),
+ memblk->rawsize + Memento_PostSize);
+#ifndef MEMENTO_LEAKONLY
+ memset(MEMBLK_TOBLK(memblk), MEMENTO_FREEFILL, memblk->rawsize);
+#endif
+ Memento_addBlockTail(&globals.free, memblk, 1);
+ } else {
+ MEMENTO_UNDERLYING_FREE(memblk);
+ }
+}
+
+void *Memento_realloc(void *blk, size_t newsize)
+{
+ Memento_BlkHeader *memblk, *newmemblk;
+ size_t newsizemem;
+ int flags;
+
+ if (blk == NULL)
+ return Memento_malloc(newsize);
+ if (newsize == 0) {
+ Memento_free(blk);
+ return NULL;
+ }
+
+ if (Memento_failThisEvent())
+ return NULL;
+
+ memblk = MEMBLK_FROMBLK(blk);
+ if (checkBlock(memblk, "realloc"))
+ return NULL;
+
+ if (memblk->flags & Memento_Flag_BreakOnRealloc)
+ Memento_breakpoint();
+
+ if (globals.maxMemory != 0 && globals.alloc - memblk->rawsize + newsize > globals.maxMemory)
+ return NULL;
+
+ newsizemem = MEMBLK_SIZE(newsize);
+ Memento_removeBlock(&globals.used, memblk);
+ flags = memblk->flags;
+ newmemblk = MEMENTO_UNDERLYING_REALLOC(memblk, newsizemem);
+ if (newmemblk == NULL)
+ {
+ Memento_addBlockHead(&globals.used, memblk, 2);
+ return NULL;
+ }
+ globals.numReallocs++;
+ globals.totalAlloc += newsize;
+ globals.alloc -= newmemblk->rawsize;
+ globals.alloc += newsize;
+ if (globals.peakAlloc < globals.alloc)
+ globals.peakAlloc = globals.alloc;
+ newmemblk->flags = flags;
+ if (newmemblk->rawsize < newsize) {
+ char *newbytes = ((char *)MEMBLK_TOBLK(newmemblk))+newmemblk->rawsize;
+#ifndef MEMENTO_LEAKONLY
+ memset(newbytes, MEMENTO_ALLOCFILL, newsize - newmemblk->rawsize);
+#endif
+ VALGRIND_MAKE_MEM_UNDEFINED(newbytes, newsize - newmemblk->rawsize);
+ }
+ newmemblk->rawsize = newsize;
+#ifndef MEMENTO_LEAKONLY
+ memset(newmemblk->preblk, MEMENTO_PREFILL, Memento_PreSize);
+ memset(MEMBLK_POSTPTR(newmemblk), MEMENTO_POSTFILL, Memento_PostSize);
+#endif
+ Memento_addBlockHead(&globals.used, newmemblk, 2);
+ return MEMBLK_TOBLK(newmemblk);
+}
+
+int Memento_checkBlock(void *blk)
+{
+ Memento_BlkHeader *memblk;
+
+ if (blk == NULL)
+ return 0;
+ memblk = MEMBLK_FROMBLK(blk);
+ return checkBlock(memblk, "check");
+}
+
+static int Memento_Internal_checkAllAlloced(Memento_BlkHeader *memblk, void *arg)
+{
+ BlkCheckData *data = (BlkCheckData *)arg;
+
+ Memento_Internal_checkAllocedBlock(memblk, data);
+ if (data->preCorrupt || data->postCorrupt) {
+ if ((data->found & 2) == 0) {
+ fprintf(stderr, "Allocated blocks:\n");
+ data->found |= 2;
+ }
+ fprintf(stderr, " Block ");
+ showBlock(memblk, ' ');
+ if (data->preCorrupt) {
+ fprintf(stderr, " Preguard ");
+ }
+ if (data->postCorrupt) {
+ fprintf(stderr, "%s Postguard ",
+ (data->preCorrupt ? "&" : ""));
+ }
+ fprintf(stderr, "corrupted.\n "
+ "Block last checked OK at allocation %d. Now %d.\n",
+ memblk->lastCheckedOK, globals.sequence);
+ data->preCorrupt = 0;
+ data->postCorrupt = 0;
+ data->freeCorrupt = 0;
+ }
+ else
+ memblk->lastCheckedOK = globals.sequence;
+ return 0;
+}
+
+static int Memento_Internal_checkAllFreed(Memento_BlkHeader *memblk, void *arg)
+{
+ BlkCheckData *data = (BlkCheckData *)arg;
+
+ Memento_Internal_checkFreedBlock(memblk, data);
+ if (data->preCorrupt || data->postCorrupt || data->freeCorrupt) {
+ if ((data->found & 4) == 0) {
+ fprintf(stderr, "Freed blocks:\n");
+ data->found |= 4;
+ }
+ fprintf(stderr, " ");
+ showBlock(memblk, ' ');
+ if (data->freeCorrupt) {
+ fprintf(stderr, " index %d (address 0x%p) onwards", data->index,
+ &((char *)MEMBLK_TOBLK(memblk))[data->index]);
+ if (data->preCorrupt) {
+ fprintf(stderr, "+ preguard");
+ }
+ if (data->postCorrupt) {
+ fprintf(stderr, "+ postguard");
+ }
+ } else {
+ if (data->preCorrupt) {
+ fprintf(stderr, " preguard");
+ }
+ if (data->postCorrupt) {
+ fprintf(stderr, "%s Postguard",
+ (data->preCorrupt ? "+" : ""));
+ }
+ }
+ fprintf(stderr, " corrupted.\n"
+ " Block last checked OK at allocation %d. Now %d.\n",
+ memblk->lastCheckedOK, globals.sequence);
+ data->preCorrupt = 0;
+ data->postCorrupt = 0;
+ data->freeCorrupt = 0;
+ }
+ else
+ memblk->lastCheckedOK = globals.sequence;
+ return 0;
+}
+
+int Memento_checkAllMemory(void)
+{
+#ifndef MEMENTO_LEAKONLY
+ BlkCheckData data;
+
+ memset(&data, 0, sizeof(data));
+ Memento_appBlocks(&globals.used, Memento_Internal_checkAllAlloced, &data);
+ Memento_appBlocks(&globals.free, Memento_Internal_checkAllFreed, &data);
+ if (data.found & 6) {
+ Memento_breakpoint();
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+int Memento_setParanoia(int i)
+{
+ globals.paranoia = i;
+ globals.countdown = globals.paranoia;
+ return i;
+}
+
+int Memento_paranoidAt(int i)
+{
+ globals.paranoidAt = i;
+ return i;
+}
+
+int Memento_getBlockNum(void *b)
+{
+ Memento_BlkHeader *memblk;
+ if (b == NULL)
+ return 0;
+ memblk = MEMBLK_FROMBLK(b);
+ return (memblk->sequence);
+}
+
+int Memento_check(void)
+{
+ int result;
+
+ fprintf(stderr, "Checking memory\n");
+ result = Memento_checkAllMemory();
+ fprintf(stderr, "Memory checked!\n");
+ return result;
+}
+
+typedef struct findBlkData {
+ void *addr;
+ Memento_BlkHeader *blk;
+ int flags;
+} findBlkData;
+
+static int Memento_containsAddr(Memento_BlkHeader *b,
+ void *arg)
+{
+ findBlkData *data = (findBlkData *)arg;
+ char *blkend = &((char *)MEMBLK_TOBLK(b))[b->rawsize];
+ if ((MEMBLK_TOBLK(b) <= data->addr) &&
+ ((void *)blkend > data->addr)) {
+ data->blk = b;
+ data->flags = 1;
+ return 1;
+ }
+ if (((void *)b <= data->addr) &&
+ (MEMBLK_TOBLK(b) > data->addr)) {
+ data->blk = b;
+ data->flags = 2;
+ return 1;
+ }
+ if (((void *)blkend <= data->addr) &&
+ ((void *)(blkend + Memento_PostSize) > data->addr)) {
+ data->blk = b;
+ data->flags = 3;
+ return 1;
+ }
+ return 0;
+}
+
+int Memento_find(void *a)
+{
+ findBlkData data;
+
+ data.addr = a;
+ data.blk = NULL;
+ data.flags = 0;
+ Memento_appBlocks(&globals.used, Memento_containsAddr, &data);
+ if (data.blk != NULL) {
+ fprintf(stderr, "Address 0x%p is in %sallocated block ",
+ data.addr,
+ (data.flags == 1 ? "" : (data.flags == 2 ?
+ "preguard of " : "postguard of ")));
+ showBlock(data.blk, ' ');
+ fprintf(stderr, "\n");
+ return data.blk->sequence;
+ }
+ data.blk = NULL;
+ data.flags = 0;
+ Memento_appBlocks(&globals.free, Memento_containsAddr, &data);
+ if (data.blk != NULL) {
+ fprintf(stderr, "Address 0x%p is in %sfreed block ",
+ data.addr,
+ (data.flags == 1 ? "" : (data.flags == 2 ?
+ "preguard of " : "postguard of ")));
+ showBlock(data.blk, ' ');
+ fprintf(stderr, "\n");
+ return data.blk->sequence;
+ }
+ return 0;
+}
+
+void Memento_breakOnFree(void *a)
+{
+ findBlkData data;
+
+ data.addr = a;
+ data.blk = NULL;
+ data.flags = 0;
+ Memento_appBlocks(&globals.used, Memento_containsAddr, &data);
+ if (data.blk != NULL) {
+ fprintf(stderr, "Will stop when address 0x%p (in %sallocated block ",
+ data.addr,
+ (data.flags == 1 ? "" : (data.flags == 2 ?
+ "preguard of " : "postguard of ")));
+ showBlock(data.blk, ' ');
+ fprintf(stderr, ") is freed\n");
+ data.blk->flags |= Memento_Flag_BreakOnFree;
+ return;
+ }
+ data.blk = NULL;
+ data.flags = 0;
+ Memento_appBlocks(&globals.free, Memento_containsAddr, &data);
+ if (data.blk != NULL) {
+ fprintf(stderr, "Can't stop on free; address 0x%p is in %sfreed block ",
+ data.addr,
+ (data.flags == 1 ? "" : (data.flags == 2 ?
+ "preguard of " : "postguard of ")));
+ showBlock(data.blk, ' ');
+ fprintf(stderr, "\n");
+ return;
+ }
+ fprintf(stderr, "Can't stop on free; address 0x%p is not in a known block.\n", a);
+}
+
+void Memento_breakOnRealloc(void *a)
+{
+ findBlkData data;
+
+ data.addr = a;
+ data.blk = NULL;
+ data.flags = 0;
+ Memento_appBlocks(&globals.used, Memento_containsAddr, &data);
+ if (data.blk != NULL) {
+ fprintf(stderr, "Will stop when address 0x%p (in %sallocated block ",
+ data.addr,
+ (data.flags == 1 ? "" : (data.flags == 2 ?
+ "preguard of " : "postguard of ")));
+ showBlock(data.blk, ' ');
+ fprintf(stderr, ") is freed (or realloced)\n");
+ data.blk->flags |= Memento_Flag_BreakOnFree | Memento_Flag_BreakOnRealloc;
+ return;
+ }
+ data.blk = NULL;
+ data.flags = 0;
+ Memento_appBlocks(&globals.free, Memento_containsAddr, &data);
+ if (data.blk != NULL) {
+ fprintf(stderr, "Can't stop on free/realloc; address 0x%p is in %sfreed block ",
+ data.addr,
+ (data.flags == 1 ? "" : (data.flags == 2 ?
+ "preguard of " : "postguard of ")));
+ showBlock(data.blk, ' ');
+ fprintf(stderr, "\n");
+ return;
+ }
+ fprintf(stderr, "Can't stop on free/realloc; address 0x%p is not in a known block.\n", a);
+}
+
+int Memento_failAt(int i)
+{
+ globals.failAt = i;
+ if ((globals.sequence > globals.failAt) &&
+ (globals.failing != 0))
+ Memento_startFailing();
+ return i;
+}
+
+size_t Memento_setMax(size_t max)
+{
+ globals.maxMemory = max;
+ return max;
+}
+
+#else
+
+/* Just in case anyone has left some debugging code in... */
+void (Memento_breakpoint)(void)
+{
+}
+
+int (Memento_checkBlock)(void *b)
+{
+ return 0;
+}
+
+int (Memento_checkAllMemory)(void)
+{
+ return 0;
+}
+
+int (Memento_check)(void)
+{
+ return 0;
+}
+
+int (Memento_setParanoia)(int i)
+{
+ return 0;
+}
+
+int (Memento_paranoidAt)(int i)
+{
+ return 0;
+}
+
+int (Memento_breakAt)(int i)
+{
+ return 0;
+}
+
+int (Memento_getBlockNum)(void *i)
+{
+ return 0;
+}
+
+int (Memento_find)(void *a)
+{
+ return 0;
+}
+
+int (Memento_failAt)(int i)
+{
+ return 0;
+}
+
+void (Memento_breakOnFree)(void *a)
+{
+}
+
+void (Memento_breakOnRealloc)(void *a)
+{
+}
+
+#undef Memento_malloc
+#undef Memento_free
+#undef Memento_realloc
+#undef Memento_calloc
+
+void *Memento_malloc(size_t size)
+{
+ return MEMENTO_UNDERLYING_MALLOC(size);
+}
+
+void Memento_free(void *b)
+{
+ MEMENTO_UNDERLYING_FREE(b);
+}
+
+void *Memento_realloc(void *b, size_t s)
+{
+ return MEMENTO_UNDERLYING_REALLOC(b, s);
+}
+
+void *Memento_calloc(size_t n, size_t s)
+{
+ return MEMENTO_UNDERLYING_CALLOC(n, s);
+}
+
+void (Memento_listBlocks)(void)
+{
+}
+
+void (Memento_listNewBlocks)(void)
+{
+}
+
+size_t (Memento_setMax)(size_t max)
+{
+ return 0;
+}
+
+void (Memento_stats)(void)
+{
+}
+
+void *(Memento_label)(void *ptr, const char *label)
+{
+ return ptr;
+}
+
+#endif
diff --git a/source/fitz/memory.c b/source/fitz/memory.c
new file mode 100644
index 00000000..f9e7b4f6
--- /dev/null
+++ b/source/fitz/memory.c
@@ -0,0 +1,402 @@
+#include "mupdf/fitz.h"
+
+/* Enable FITZ_DEBUG_LOCKING_TIMES below if you want to check the times
+ * for which locks are held too. */
+#ifdef FITZ_DEBUG_LOCKING
+#undef FITZ_DEBUG_LOCKING_TIMES
+#endif
+
+static void *
+do_scavenging_malloc(fz_context *ctx, unsigned int size)
+{
+ void *p;
+ int phase = 0;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ do {
+ p = ctx->alloc->malloc(ctx->alloc->user, size);
+ if (p != NULL)
+ {
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return p;
+ }
+ } while (fz_store_scavenge(ctx, size, &phase));
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ return NULL;
+}
+
+static void *
+do_scavenging_realloc(fz_context *ctx, void *p, unsigned int size)
+{
+ void *q;
+ int phase = 0;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ do {
+ q = ctx->alloc->realloc(ctx->alloc->user, p, size);
+ if (q != NULL)
+ {
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return q;
+ }
+ } while (fz_store_scavenge(ctx, size, &phase));
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ return NULL;
+}
+
+void *
+fz_malloc(fz_context *ctx, unsigned int size)
+{
+ void *p;
+
+ if (size == 0)
+ return NULL;
+
+ p = do_scavenging_malloc(ctx, size);
+ if (!p)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "malloc of %d bytes failed", size);
+ return p;
+}
+
+void *
+fz_malloc_no_throw(fz_context *ctx, unsigned int size)
+{
+ return do_scavenging_malloc(ctx, size);
+}
+
+void *
+fz_malloc_array(fz_context *ctx, unsigned int count, unsigned int size)
+{
+ void *p;
+
+ if (count == 0 || size == 0)
+ return 0;
+
+ if (count > UINT_MAX / size)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "malloc of array (%d x %d bytes) failed (integer overflow)", count, size);
+
+ p = do_scavenging_malloc(ctx, count * size);
+ if (!p)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "malloc of array (%d x %d bytes) failed", count, size);
+ return p;
+}
+
+void *
+fz_malloc_array_no_throw(fz_context *ctx, unsigned int count, unsigned int size)
+{
+ if (count == 0 || size == 0)
+ return 0;
+
+ if (count > UINT_MAX / size)
+ {
+ fprintf(stderr, "error: malloc of array (%d x %d bytes) failed (integer overflow)", count, size);
+ return NULL;
+ }
+
+ return do_scavenging_malloc(ctx, count * size);
+}
+
+void *
+fz_calloc(fz_context *ctx, unsigned int count, unsigned int size)
+{
+ void *p;
+
+ if (count == 0 || size == 0)
+ return 0;
+
+ if (count > UINT_MAX / size)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "calloc (%d x %d bytes) failed (integer overflow)", count, size);
+ }
+
+ p = do_scavenging_malloc(ctx, count * size);
+ if (!p)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "calloc (%d x %d bytes) failed", count, size);
+ }
+ memset(p, 0, count*size);
+ return p;
+}
+
+void *
+fz_calloc_no_throw(fz_context *ctx, unsigned int count, unsigned int size)
+{
+ void *p;
+
+ if (count == 0 || size == 0)
+ return 0;
+
+ if (count > UINT_MAX / size)
+ {
+ fprintf(stderr, "error: calloc (%d x %d bytes) failed (integer overflow)\n", count, size);
+ return NULL;
+ }
+
+ p = do_scavenging_malloc(ctx, count * size);
+ if (p)
+ {
+ memset(p, 0, count*size);
+ }
+ return p;
+}
+
+void *
+fz_resize_array(fz_context *ctx, void *p, unsigned int count, unsigned int size)
+{
+ void *np;
+
+ if (count == 0 || size == 0)
+ {
+ fz_free(ctx, p);
+ return 0;
+ }
+
+ if (count > UINT_MAX / size)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "resize array (%d x %d bytes) failed (integer overflow)", count, size);
+
+ np = do_scavenging_realloc(ctx, p, count * size);
+ if (!np)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "resize array (%d x %d bytes) failed", count, size);
+ return np;
+}
+
+void *
+fz_resize_array_no_throw(fz_context *ctx, void *p, unsigned int count, unsigned int size)
+{
+ if (count == 0 || size == 0)
+ {
+ fz_free(ctx, p);
+ return 0;
+ }
+
+ if (count > UINT_MAX / size)
+ {
+ fprintf(stderr, "error: resize array (%d x %d bytes) failed (integer overflow)\n", count, size);
+ return NULL;
+ }
+
+ return do_scavenging_realloc(ctx, p, count * size);
+}
+
+void
+fz_free(fz_context *ctx, void *p)
+{
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ ctx->alloc->free(ctx->alloc->user, p);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+}
+
+char *
+fz_strdup(fz_context *ctx, const char *s)
+{
+ int len = strlen(s) + 1;
+ char *ns = fz_malloc(ctx, len);
+ memcpy(ns, s, len);
+ return ns;
+}
+
+char *
+fz_strdup_no_throw(fz_context *ctx, const char *s)
+{
+ int len = strlen(s) + 1;
+ char *ns = fz_malloc_no_throw(ctx, len);
+ if (ns)
+ memcpy(ns, s, len);
+ return ns;
+}
+
+static void *
+fz_malloc_default(void *opaque, unsigned int size)
+{
+ return malloc(size);
+}
+
+static void *
+fz_realloc_default(void *opaque, void *old, unsigned int size)
+{
+ return realloc(old, size);
+}
+
+static void
+fz_free_default(void *opaque, void *ptr)
+{
+ free(ptr);
+}
+
+fz_alloc_context fz_alloc_default =
+{
+ NULL,
+ fz_malloc_default,
+ fz_realloc_default,
+ fz_free_default
+};
+
+static void
+fz_lock_default(void *user, int lock)
+{
+}
+
+static void
+fz_unlock_default(void *user, int lock)
+{
+}
+
+fz_locks_context fz_locks_default =
+{
+ NULL,
+ fz_lock_default,
+ fz_unlock_default
+};
+
+#ifdef FITZ_DEBUG_LOCKING
+
+enum
+{
+ FZ_LOCK_DEBUG_CONTEXT_MAX = 100
+};
+
+fz_context *fz_lock_debug_contexts[FZ_LOCK_DEBUG_CONTEXT_MAX];
+int fz_locks_debug[FZ_LOCK_DEBUG_CONTEXT_MAX][FZ_LOCK_MAX];
+#ifdef FITZ_DEBUG_LOCKING_TIMES
+int fz_debug_locking_inited = 0;
+int fz_lock_program_start;
+int fz_lock_time[FZ_LOCK_DEBUG_CONTEXT_MAX][FZ_LOCK_MAX] = { { 0 } };
+int fz_lock_taken[FZ_LOCK_DEBUG_CONTEXT_MAX][FZ_LOCK_MAX] = { { 0 } };
+
+/* We implement our own millisecond clock, as clock() cannot be trusted
+ * when threads are involved. */
+static int ms_clock(void)
+{
+#ifdef _WIN32
+ return (int)GetTickCount();
+#else
+ struct timeval tp;
+ gettimeofday(&tp, NULL);
+ return (tp.tv_sec*1000) + (tp.tv_usec/1000);
+#endif
+}
+
+static void dump_lock_times(void)
+{
+ int i, j;
+ int prog_time = ms_clock() - fz_lock_program_start;
+
+ for (j = 0; j < FZ_LOCK_MAX; j++)
+ {
+ int total = 0;
+ for (i = 0; i < FZ_LOCK_DEBUG_CONTEXT_MAX; i++)
+ {
+ total += fz_lock_time[i][j];
+ }
+ printf("Lock %d held for %g seconds (%g%%)\n", j, ((double)total)/1000, 100.0*total/prog_time);
+ }
+ printf("Total program time %g seconds\n", ((double)prog_time)/1000);
+}
+
+#endif
+
+static int find_context(fz_context *ctx)
+{
+ int i;
+
+ for (i = 0; i < FZ_LOCK_DEBUG_CONTEXT_MAX; i++)
+ {
+ if (fz_lock_debug_contexts[i] == ctx)
+ return i;
+ if (fz_lock_debug_contexts[i] == NULL)
+ {
+ int gottit = 0;
+ /* We've not locked on this context before, so use
+ * this one for this new context. We might have other
+ * threads trying here too though so, so claim it
+ * atomically. No one has locked on this context
+ * before, so we are safe to take the ALLOC lock. */
+ ctx->locks->lock(ctx->locks->user, FZ_LOCK_ALLOC);
+ /* If it's still free, then claim it as ours,
+ * otherwise we'll keep hunting. */
+ if (fz_lock_debug_contexts[i] == NULL)
+ {
+ gottit = 1;
+ fz_lock_debug_contexts[i] = ctx;
+#ifdef FITZ_DEBUG_LOCKING_TIMES
+ if (fz_debug_locking_inited == 0)
+ {
+ fz_debug_locking_inited = 1;
+ fz_lock_program_start = ms_clock();
+ atexit(dump_lock_times);
+ }
+#endif
+ }
+ ctx->locks->unlock(ctx->locks->user, FZ_LOCK_ALLOC);
+ if (gottit)
+ return i;
+ }
+ }
+ return -1;
+}
+
+void
+fz_assert_lock_held(fz_context *ctx, int lock)
+{
+ int idx = find_context(ctx);
+ if (idx < 0)
+ return;
+
+ if (fz_locks_debug[idx][lock] == 0)
+ fprintf(stderr, "Lock %d not held when expected\n", lock);
+}
+
+void
+fz_assert_lock_not_held(fz_context *ctx, int lock)
+{
+ int idx = find_context(ctx);
+ if (idx < 0)
+ return;
+
+ if (fz_locks_debug[idx][lock] != 0)
+ fprintf(stderr, "Lock %d held when not expected\n", lock);
+}
+
+void fz_lock_debug_lock(fz_context *ctx, int lock)
+{
+ int i;
+ int idx = find_context(ctx);
+ if (idx < 0)
+ return;
+
+ if (fz_locks_debug[idx][lock] != 0)
+ {
+ fprintf(stderr, "Attempt to take lock %d when held already!\n", lock);
+ }
+ for (i = lock-1; i >= 0; i--)
+ {
+ if (fz_locks_debug[idx][i] != 0)
+ {
+ fprintf(stderr, "Lock ordering violation: Attempt to take lock %d when %d held already!\n", lock, i);
+ }
+ }
+ fz_locks_debug[idx][lock] = 1;
+#ifdef FITZ_DEBUG_LOCKING_TIMES
+ fz_lock_taken[idx][lock] = clock();
+#endif
+}
+
+void fz_lock_debug_unlock(fz_context *ctx, int lock)
+{
+ int idx = find_context(ctx);
+ if (idx < 0)
+ return;
+
+ if (fz_locks_debug[idx][lock] == 0)
+ {
+ fprintf(stderr, "Attempt to release lock %d when not held!\n", lock);
+ }
+ fz_locks_debug[idx][lock] = 0;
+#ifdef FITZ_DEBUG_LOCKING_TIMES
+ fz_lock_time[idx][lock] += clock() - fz_lock_taken[idx][lock];
+#endif
+}
+
+#endif
diff --git a/source/fitz/outline.c b/source/fitz/outline.c
new file mode 100644
index 00000000..e26fd378
--- /dev/null
+++ b/source/fitz/outline.c
@@ -0,0 +1,62 @@
+#include "mupdf/fitz.h"
+
+void
+fz_free_outline(fz_context *ctx, fz_outline *outline)
+{
+ while (outline)
+ {
+ fz_outline *next = outline->next;
+ fz_free_outline(ctx, outline->down);
+ fz_free(ctx, outline->title);
+ fz_free_link_dest(ctx, &outline->dest);
+ fz_free(ctx, outline);
+ outline = next;
+ }
+}
+
+static void
+do_debug_outline_xml(fz_output *out, fz_outline *outline, int level)
+{
+ while (outline)
+ {
+ fz_printf(out, "<outline title=\"%s\" page=\"%d\"", outline->title, outline->dest.kind == FZ_LINK_GOTO ? outline->dest.ld.gotor.page + 1 : 0);
+ if (outline->down)
+ {
+ fz_printf(out, ">\n");
+ do_debug_outline_xml(out, outline->down, level + 1);
+ fz_printf(out, "</outline>\n");
+ }
+ else
+ {
+ fz_printf(out, " />\n");
+ }
+ outline = outline->next;
+ }
+}
+
+void
+fz_print_outline_xml(fz_context *ctx, fz_output *out, fz_outline *outline)
+{
+ do_debug_outline_xml(out, outline, 0);
+}
+
+static void
+do_debug_outline(fz_output *out, fz_outline *outline, int level)
+{
+ int i;
+ while (outline)
+ {
+ for (i = 0; i < level; i++)
+ fz_printf(out, "\t");
+ fz_printf(out, "%s\t%d\n", outline->title, outline->dest.kind == FZ_LINK_GOTO ? outline->dest.ld.gotor.page + 1 : 0);
+ if (outline->down)
+ do_debug_outline(out, outline->down, level + 1);
+ outline = outline->next;
+ }
+}
+
+void
+fz_print_outline(fz_context *ctx, fz_output *out, fz_outline *outline)
+{
+ do_debug_outline(out, outline, 0);
+}
diff --git a/source/fitz/output-pcl.c b/source/fitz/output-pcl.c
new file mode 100644
index 00000000..eae5dad2
--- /dev/null
+++ b/source/fitz/output-pcl.c
@@ -0,0 +1,856 @@
+#include "mupdf/fitz.h"
+
+/* Lifted from ghostscript gdevjlm.h */
+/*
+ * The notion that there is such a thing as a "PCL printer" is a fiction: no
+ * two "PCL" printers, even at the same PCL level, have identical command
+ * sets. (The H-P documentation isn't fully accurate either; for example,
+ * it doesn't reveal that the DeskJet printers implement anything beyond PCL
+ * 3.)
+ *
+ * This file contains feature definitions for a generic monochrome PCL
+ * driver (gdevdljm.c), and the specific feature values for all such
+ * printers that Ghostscript currently supports.
+ */
+
+/* Printer spacing capabilities. Include at most one of these. */
+#define PCL_NO_SPACING 0 /* no vertical spacing capability, must be 0 */
+#define PCL3_SPACING 1 /* <ESC>*p+<n>Y (PCL 3) */
+#define PCL4_SPACING 2 /* <ESC>*b<n>Y (PCL 4) */
+#define PCL5_SPACING 4 /* <ESC>*b<n>Y and clear seed row (PCL 5) */
+/* The following is only used internally. */
+#define PCL_ANY_SPACING \
+ (PCL3_SPACING | PCL4_SPACING | PCL5_SPACING)
+
+/* Individual printer properties. Any subset of these may be included. */
+#define PCL_MODE_2_COMPRESSION 8 /* compression mode 2 supported */
+ /* (PCL 4) */
+#define PCL_MODE_3_COMPRESSION 16 /* compression modes 2 & 3 supported */
+ /* (PCL 5) */
+#define PCL_END_GRAPHICS_DOES_RESET 32 /* <esc>*rB resets all parameters */
+#define PCL_HAS_DUPLEX 64 /* <esc>&l<duplex>S supported */
+#define PCL_CAN_SET_PAPER_SIZE 128 /* <esc>&l<sizecode>A supported */
+#define PCL_CAN_PRINT_COPIES 256 /* <esc>&l<copies>X supported */
+#define HACK__IS_A_LJET4PJL 512
+#define HACK__IS_A_OCE9050 1024
+
+/* Shorthands for the most common spacing/compression combinations. */
+#define PCL_MODE0 PCL3_SPACING
+#define PCL_MODE0NS PCL_NO_SPACING
+#define PCL_MODE2 (PCL4_SPACING | PCL_MODE_2_COMPRESSION)
+#define PCL_MODE2P (PCL_NO_SPACING | PCL_MODE_2_COMPRESSION)
+#define PCL_MODE3 (PCL5_SPACING | PCL_MODE_3_COMPRESSION)
+#define PCL_MODE3NS (PCL_NO_SPACING | PCL_MODE_3_COMPRESSION)
+
+#define MIN_SKIP_LINES 7
+static const char *const from2to3 = "\033*b3M";
+static const char *const from3to2 = "\033*b2M";
+static const int penalty_from2to3 = 5; /* strlen(from2to3); */
+static const int penalty_from3to2 = 5; /* strlen(from3to2); */
+
+/* H-P DeskJet */
+static const fz_pcl_options fz_pcl_options_ljet4 =
+{
+ (PCL_MODE2 | PCL_END_GRAPHICS_DOES_RESET | PCL_CAN_SET_PAPER_SIZE),
+ "\033&k1W\033*b2M",
+ "\033&k1W\033*b2M"
+};
+
+/* H-P DeskJet 500 */
+static const fz_pcl_options fz_pcl_options_dj500 =
+{
+ (PCL_MODE3 | PCL_END_GRAPHICS_DOES_RESET | PCL_CAN_SET_PAPER_SIZE),
+ "\033&k1W",
+ "\033&k1W"
+};
+
+/* Kyocera FS-600 */
+static const fz_pcl_options fz_pcl_options_fs600 =
+{
+ (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES),
+ "\033*r0F\033&u%dD",
+ "\033*r0F\033&u%dD"
+};
+
+/* H-P original LaserJet */
+/* H-P LaserJet Plus */
+static const fz_pcl_options fz_pcl_options_lj =
+{
+ (PCL_MODE0),
+ "\033*b0M",
+ "\033*b0M"
+};
+
+/* H-P LaserJet IIp, IId */
+static const fz_pcl_options fz_pcl_options_lj2 =
+{
+ (PCL_MODE2P | PCL_CAN_SET_PAPER_SIZE),
+ "\033*r0F\033*b2M",
+ "\033*r0F\033*b2M"
+};
+
+/* H-P LaserJet III* */
+static const fz_pcl_options fz_pcl_options_lj3 =
+{
+ (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES),
+ "\033&l-180u36Z\033*r0F",
+ "\033&l-180u36Z\033*r0F"
+};
+
+/* H-P LaserJet IIId */
+static const fz_pcl_options fz_pcl_options_lj3d =
+{
+ (PCL_MODE3 | PCL_HAS_DUPLEX | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES),
+ "\033&l-180u36Z\033*r0F",
+ "\033&l180u36Z\033*r0F"
+};
+
+/* H-P LaserJet 4 */
+static const fz_pcl_options fz_pcl_options_lj4 =
+{
+ (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES),
+ "\033&l-180u36Z\033*r0F\033&u%dD",
+ "\033&l-180u36Z\033*r0F\033&u%dD"
+};
+
+/* H-P LaserJet 4 PL */
+static const fz_pcl_options fz_pcl_options_lj4pl =
+{
+ (PCL_MODE3 | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES | HACK__IS_A_LJET4PJL),
+ "\033&l-180u36Z\033*r0F\033&u%dD",
+ "\033&l-180u36Z\033*r0F\033&u%dD"
+};
+
+/* H-P LaserJet 4d */
+static const fz_pcl_options fz_pcl_options_lj4d =
+{
+ (PCL_MODE3 | PCL_HAS_DUPLEX | PCL_CAN_SET_PAPER_SIZE | PCL_CAN_PRINT_COPIES),
+ "\033&l-180u36Z\033*r0F\033&u%dD",
+ "\033&l180u36Z\033*r0F\033&u%dD"
+};
+
+/* H-P 2563B line printer */
+static const fz_pcl_options fz_pcl_options_lp2563b =
+{
+ (PCL_MODE0NS | PCL_CAN_SET_PAPER_SIZE),
+ "\033*b0M",
+ "\033*b0M"
+};
+
+/* OCE 9050 line printer */
+static const fz_pcl_options fz_pcl_options_oce9050 =
+{
+ (PCL_MODE3NS | PCL_CAN_SET_PAPER_SIZE | HACK__IS_A_OCE9050),
+ "\033*b0M",
+ "\033*b0M"
+};
+
+static void copy_opts(fz_pcl_options *dst, const fz_pcl_options *src)
+{
+ if (dst)
+ *dst = *src;
+}
+
+void fz_pcl_preset(fz_context *ctx, fz_pcl_options *opts, const char *preset)
+{
+ if (preset == NULL || *preset == 0 || !strcmp(preset, "ljet4"))
+ copy_opts(opts, &fz_pcl_options_ljet4);
+ else if (!strcmp(preset, "dj500"))
+ copy_opts(opts, &fz_pcl_options_dj500);
+ else if (!strcmp(preset, "fs600"))
+ copy_opts(opts, &fz_pcl_options_fs600);
+ else if (!strcmp(preset, "lj"))
+ copy_opts(opts, &fz_pcl_options_lj);
+ else if (!strcmp(preset, "lj2"))
+ copy_opts(opts, &fz_pcl_options_lj2);
+ else if (!strcmp(preset, "lj3"))
+ copy_opts(opts, &fz_pcl_options_lj3);
+ else if (!strcmp(preset, "lj3d"))
+ copy_opts(opts, &fz_pcl_options_lj3d);
+ else if (!strcmp(preset, "lj4"))
+ copy_opts(opts, &fz_pcl_options_lj4);
+ else if (!strcmp(preset, "lj4pl"))
+ copy_opts(opts, &fz_pcl_options_lj4pl);
+ else if (!strcmp(preset, "lj4d"))
+ copy_opts(opts, &fz_pcl_options_lj4d);
+ else if (!strcmp(preset, "lp2563b"))
+ copy_opts(opts, &fz_pcl_options_lp2563b);
+ else if (!strcmp(preset, "oce9050"))
+ copy_opts(opts, &fz_pcl_options_oce9050);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Unknown preset '%s'", preset);
+}
+
+void fz_pcl_option(fz_context *ctx, fz_pcl_options *opts, const char *option, int val)
+{
+ if (opts == NULL)
+ return;
+
+ if (!strcmp(option, "spacing"))
+ {
+ switch (val)
+ {
+ case 0:
+ opts->features &= ~PCL_ANY_SPACING;
+ break;
+ case 1:
+ opts->features = (opts->features & ~PCL_ANY_SPACING) | PCL3_SPACING;
+ break;
+ case 2:
+ opts->features = (opts->features & ~PCL_ANY_SPACING) | PCL4_SPACING;
+ break;
+ case 3:
+ opts->features = (opts->features & ~PCL_ANY_SPACING) | PCL5_SPACING;
+ break;
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Unsupported PCL spacing %d (0-3 only)", val);
+ }
+ }
+ else if (!strcmp(option, "mode2"))
+ {
+ if (val == 0)
+ opts->features &= ~PCL_MODE_2_COMPRESSION;
+ else if (val == 1)
+ opts->features |= PCL_MODE_2_COMPRESSION;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for mode2 value");
+ }
+ else if (!strcmp(option, "mode3"))
+ {
+ if (val == 0)
+ opts->features &= ~PCL_MODE_3_COMPRESSION;
+ else if (val == 1)
+ opts->features |= PCL_MODE_3_COMPRESSION;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for mode3 value");
+ }
+ else if (!strcmp(option, "eog_reset"))
+ {
+ if (val == 0)
+ opts->features &= ~PCL_END_GRAPHICS_DOES_RESET;
+ else if (val == 1)
+ opts->features |= PCL_END_GRAPHICS_DOES_RESET;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for eog_reset value");
+ }
+ else if (!strcmp(option, "has_duplex"))
+ {
+ if (val == 0)
+ opts->features &= ~PCL_HAS_DUPLEX;
+ else if (val == 1)
+ opts->features |= PCL_HAS_DUPLEX;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for has_duplex value");
+ }
+ else if (!strcmp(option, "has_papersize"))
+ {
+ if (val == 0)
+ opts->features &= ~PCL_CAN_SET_PAPER_SIZE;
+ else if (val == 1)
+ opts->features |= PCL_CAN_SET_PAPER_SIZE;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for has_papersize value");
+ }
+ else if (!strcmp(option, "has_copies"))
+ {
+ if (val == 0)
+ opts->features &= ~PCL_CAN_PRINT_COPIES;
+ else if (val == 1)
+ opts->features |= PCL_CAN_PRINT_COPIES;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for has_papersize value");
+ }
+ else if (!strcmp(option, "is_ljet4pjl"))
+ {
+ if (val == 0)
+ opts->features &= ~HACK__IS_A_LJET4PJL;
+ else if (val == 1)
+ opts->features |= HACK__IS_A_LJET4PJL;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for is_ljet4pjl value");
+ }
+ else if (!strcmp(option, "is_oce9050"))
+ {
+ if (val == 0)
+ opts->features &= ~HACK__IS_A_OCE9050;
+ else if (val == 1)
+ opts->features |= HACK__IS_A_OCE9050;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Expected 0 or 1 for is_oce9050 value");
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Unknown pcl option '%s'", option);
+}
+
+static void
+make_init(fz_pcl_options *pcl, char *buf, unsigned long len, const char *str, int res)
+{
+ int paper_source = -1;
+
+ snprintf(buf, len, str, res);
+
+ if (pcl->manual_feed_set && pcl->manual_feed)
+ paper_source = 2;
+ else if (pcl->media_position_set && pcl->media_position >= 0)
+ paper_source = pcl->media_position;
+ if (paper_source >= 0)
+ {
+ char buf2[40];
+ snprintf(buf2, sizeof(buf2), "\033&l%dH", paper_source);
+ strncat(buf, buf2, len);
+ }
+}
+
+static void
+pcl_header(fz_output *out, fz_pcl_options *pcl, int num_copies, int xres)
+{
+ char odd_page_init[80];
+ char even_page_init[80];
+
+ make_init(pcl, odd_page_init, sizeof(odd_page_init), pcl->odd_page_init, xres);
+ make_init(pcl, even_page_init, sizeof(even_page_init), pcl->even_page_init, xres);
+
+ if (pcl->page_count == 0)
+ {
+ if (pcl->features & HACK__IS_A_LJET4PJL)
+ fz_puts(out, "\033%-12345X@PJL\r\n@PJL ENTER LANGUAGE = PCL\r\n");
+ fz_puts(out, "\033E"); /* reset printer */
+ /* If the printer supports it, set the paper size */
+ /* based on the actual requested size. */
+ if (pcl->features & PCL_CAN_SET_PAPER_SIZE)
+ fz_printf(out, "\033&l%dA", pcl->paper_size);
+ /* If printer can duplex, set duplex mode appropriately. */
+ if (pcl->features & PCL_HAS_DUPLEX)
+ {
+ if (pcl->duplex_set)
+ {
+ if (pcl->duplex)
+ {
+ if (!pcl->tumble)
+ fz_puts(out, "\033&l1S");
+ else
+ fz_puts(out, "\033&l2S");
+ }
+ else
+ fz_puts(out, "\033&l0S");
+ }
+ else
+ {
+ /* default to duplex for this printer */
+ fz_puts(out, "\033&l1S");
+ }
+ }
+ }
+
+ /* Put out per-page initialization. */
+ /* in duplex mode the sheet is already in process, so there are some
+ * commands which must not be sent to the printer for the 2nd page,
+ * as this commands will cause the printer to eject the sheet with
+ * only the 1st page printed. This commands are:
+ * \033&l%dA (setting paper size)
+ * \033&l%dH (setting paper tray)
+ * in simplex mode we set this parameters for each page,
+ * in duplex mode we set this parameters for each odd page
+ */
+
+ if ((pcl->features & PCL_HAS_DUPLEX) && pcl->duplex_set && pcl->duplex)
+ {
+ /* We are printing duplex, so change margins as needed */
+ if (((pcl->page_count/num_copies)%2) == 0)
+ {
+ if (pcl->page_count != 0 && (pcl->features & PCL_CAN_SET_PAPER_SIZE))
+ {
+ fz_printf(out, "\033&l%dA", pcl->paper_size);
+ }
+ fz_puts(out, "\033&l0o0l0E");
+ fz_puts(out, pcl->odd_page_init);
+ }
+ else
+ fz_puts(out, pcl->even_page_init);
+ }
+ else
+ {
+ if (pcl->features & PCL_CAN_SET_PAPER_SIZE)
+ {
+ fz_printf(out, "\033&l%dA", pcl->paper_size);
+ }
+ fz_puts(out, "\033&l0o0l0E");
+ fz_puts(out, pcl->odd_page_init);
+ }
+
+ fz_printf(out, "\033&l%dX", num_copies); /* # of copies */
+
+ /* End raster graphics, position cursor at top. */
+ fz_puts(out, "\033*rB\033*p0x0Y");
+
+ /* The DeskJet and DeskJet Plus reset everything upon */
+ /* receiving \033*rB, so we must reinitialize graphics mode. */
+ if (pcl->features & PCL_END_GRAPHICS_DOES_RESET)
+ {
+ fz_puts(out, pcl->odd_page_init); /* Assume this does the right thing */
+ fz_printf(out, "\033&l%dX", num_copies); /* # of copies */
+ }
+
+ /* Set resolution. */
+ fz_printf(out, "\033*t%dR", xres);
+ pcl->page_count++;
+}
+
+void
+fz_output_pcl(fz_output *out, const fz_pixmap *pixmap, fz_pcl_options *pcl)
+{
+ //unsigned char *sp;
+ //int y, x, sn, dn, ss;
+ fz_context *ctx;
+
+ if (!out || !pixmap)
+ return;
+
+ ctx = out->ctx;
+
+ if (pixmap->n != 1 && pixmap->n != 2 && pixmap->n != 4)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "pixmap must be grayscale or rgb to write as pcl");
+
+ pcl_header(out, pcl, 1, pixmap->xres);
+
+#if 0
+ sn = pixmap->n;
+ dn = pixmap->n;
+ if (dn == 2 || dn == 4)
+ dn--;
+
+ /* Now output the actual bitmap, using a packbits like compression */
+ sp = pixmap->samples;
+ ss = pixmap->w * sn;
+ y = 0;
+ while (y < pixmap->h)
+ {
+ int yrep;
+
+ assert(sp == pixmap->samples + y * ss);
+
+ /* Count the number of times this line is repeated */
+ for (yrep = 1; yrep < 256 && y+yrep < pixmap->h; yrep++)
+ {
+ if (memcmp(sp, sp + yrep * ss, ss) != 0)
+ break;
+ }
+ fz_write_byte(out, yrep-1);
+
+ /* Encode the line */
+ x = 0;
+ while (x < pixmap->w)
+ {
+ int d;
+
+ assert(sp == pixmap->samples + y * ss + x * sn);
+
+ /* How far do we have to look to find a repeated value? */
+ for (d = 1; d < 128 && x+d < pixmap->w; d++)
+ {
+ if (memcmp(sp + (d-1)*sn, sp + d*sn, sn) == 0)
+ break;
+ }
+ if (d == 1)
+ {
+ int xrep;
+
+ /* We immediately have a repeat (or we've hit
+ * the end of the line). Count the number of
+ * times this value is repeated. */
+ for (xrep = 1; xrep < 128 && x+xrep < pixmap->w; xrep++)
+ {
+ if (memcmp(sp, sp + xrep*sn, sn) != 0)
+ break;
+ }
+ fz_write_byte(out, xrep-1);
+ fz_write(out, sp, dn);
+ sp += sn*xrep;
+ x += xrep;
+ }
+ else
+ {
+ fz_write_byte(out, 257-d);
+ x += d;
+ while (d > 0)
+ {
+ fz_write(out, sp, dn);
+ sp += sn;
+ d--;
+ }
+ }
+ }
+
+ /* Move to the next line */
+ sp += ss*(yrep-1);
+ y += yrep;
+ }
+#endif
+}
+
+/*
+ * Mode 2 Row compression routine for the HP DeskJet & LaserJet IIp.
+ * Compresses data from row up to end_row, storing the result
+ * starting at compressed. Returns the number of bytes stored.
+ * Runs of K<=127 literal bytes are encoded as K-1 followed by
+ * the bytes; runs of 2<=K<=127 identical bytes are encoded as
+ * 257-K followed by the byte.
+ * In the worst case, the result is N+(N/127)+1 bytes long,
+ * where N is the original byte count (end_row - row).
+ */
+int
+mode2compress(unsigned char *out, unsigned char *in, int in_len)
+{
+ int x;
+ int out_len = 0;
+ int run;
+
+ for (x = 0; x < in_len; x += run)
+ {
+ /* How far do we have to look to find a value that isn't repeated? */
+ for (run = 1; run < 127 && x+run < in_len; run++)
+ if (in[0] != in[run])
+ break;
+ if (run > 1)
+ {
+ /* We have a run of matching bytes */
+ out[out_len++] = 257-run;
+ out[out_len++] = in[0];
+ }
+ else
+ {
+ int i;
+
+ /* How many literals do we need to copy? */
+ for (run = 1; run < 127 && x+run < in_len; run++)
+ if (in[run] == in[run+1])
+ break;
+ out[out_len++] = run-1;
+ for (i = 0; i < run; i++)
+ out[out_len++] = in[i];
+ }
+ in += run;
+ }
+ return out_len;
+}
+
+/*
+ * Mode 3 compression routine for the HP LaserJet III family.
+ * Compresses bytecount bytes starting at current, storing the result
+ * in compressed, comparing against and updating previous.
+ * Returns the number of bytes stored. In the worst case,
+ * the number of bytes is bytecount+(bytecount/8)+1.
+ */
+int
+mode3compress(unsigned char *out, const unsigned char *in, unsigned char *prev, int in_len)
+{
+ unsigned char *compressed = out;
+ const unsigned char *cur = in;
+ const unsigned char *end = in + in_len;
+
+ while (cur < end) { /* Detect a maximum run of unchanged bytes. */
+ const unsigned char *run = cur;
+ const unsigned char *diff;
+ const unsigned char *stop;
+ int offset, cbyte;
+
+ while (cur < end && *cur == *prev) {
+ cur++, prev++;
+ }
+ if (cur == end)
+ break; /* rest of row is unchanged */
+ /* Detect a run of up to 8 changed bytes. */
+ /* We know that *cur != *prev. */
+ diff = cur;
+ stop = (end - cur > 8 ? cur + 8 : end);
+ do
+ {
+ *prev++ = *cur++;
+ }
+ while (cur < stop && *cur != *prev);
+ /* Now [run..diff) are unchanged, and */
+ /* [diff..cur) are changed. */
+ /* Generate the command byte(s). */
+ offset = diff - run;
+ cbyte = (cur - diff - 1) << 5;
+ if (offset < 31)
+ *out++ = cbyte + offset;
+ else {
+ *out++ = cbyte + 31;
+ offset -= 31;
+ while (offset >= 255)
+ *out++ = 255, offset -= 255;
+ *out++ = offset;
+ }
+ /* Copy the changed data. */
+ while (diff < cur)
+ *out++ = *diff++;
+ }
+ return out - compressed;
+}
+
+void wind(void)
+{}
+
+void
+fz_output_pcl_bitmap(fz_output *out, const fz_bitmap *bitmap, fz_pcl_options *pcl)
+{
+ unsigned char *data, *out_data;
+ int y, ss, rmask, line_size;
+ fz_context *ctx;
+ int num_blank_lines;
+ int compression = -1;
+ unsigned char *prev_row = NULL;
+ unsigned char *out_row_mode_2 = NULL;
+ unsigned char *out_row_mode_3 = NULL;
+ int out_count;
+ int max_mode_2_size;
+ int max_mode_3_size;
+
+ if (!out || !bitmap)
+ return;
+
+ ctx = out->ctx;
+
+ if (pcl->features & HACK__IS_A_OCE9050)
+ {
+ /* Enter HPGL/2 mode, begin plot, Initialise (start plot), Enter PCL mode */
+ fz_puts(out, "\033%1BBPIN;\033%1A");
+ }
+
+ pcl_header(out, pcl, 1, bitmap->xres);
+
+ fz_var(prev_row);
+ fz_var(out_row_mode_2);
+ fz_var(out_row_mode_3);
+
+ fz_try(ctx)
+ {
+ num_blank_lines = 0;
+ rmask = ~0 << (-bitmap->w & 7);
+ line_size = (bitmap->w + 7)/8;
+ max_mode_2_size = line_size + (line_size/127) + 1;
+ max_mode_3_size = line_size + (line_size/8) + 1;
+ prev_row = fz_calloc(ctx, line_size, sizeof(unsigned char));
+ out_row_mode_2 = fz_calloc(ctx, max_mode_2_size, sizeof(unsigned char));
+ out_row_mode_3 = fz_calloc(ctx, max_mode_3_size, sizeof(unsigned char));
+
+ /* Transfer raster graphics. */
+ data = bitmap->samples;
+ ss = bitmap->stride;
+ for (y = 0; y < bitmap->h; y++, data += ss)
+ {
+ unsigned char *end_data = data + line_size;
+
+ if ((end_data[-1] & rmask) == 0)
+ {
+ end_data--;
+ while (end_data > data && end_data[-1] == 0)
+ end_data--;
+ }
+ if (end_data == data)
+ {
+ /* Blank line */
+ num_blank_lines++;
+ continue;
+ }
+ wind();
+
+ /* We've reached a non-blank line. */
+ /* Put out a spacing command if necessary. */
+ if (num_blank_lines == y) {
+ /* We're at the top of a page. */
+ if (pcl->features & PCL_ANY_SPACING)
+ {
+ if (num_blank_lines > 0)
+ fz_printf(out, "\033*p+%dY", num_blank_lines * bitmap->yres);
+ /* Start raster graphics. */
+ fz_puts(out, "\033*r1A");
+ }
+ else if (pcl->features & PCL_MODE_3_COMPRESSION)
+ {
+ /* Start raster graphics. */
+ fz_puts(out, "\033*r1A");
+ for (; num_blank_lines; num_blank_lines--)
+ fz_puts(out, "\033*b0W");
+ }
+ else
+ {
+ /* Start raster graphics. */
+ fz_puts(out, "\033*r1A");
+ for (; num_blank_lines; num_blank_lines--)
+ fz_puts(out, "\033*bW");
+ }
+ }
+
+ /* Skip blank lines if any */
+ else if (num_blank_lines != 0)
+ {
+ /* Moving down from current position causes head
+ * motion on the DeskJet, so if the number of lines
+ * is small, we're better off printing blanks.
+ *
+ * For Canon LBP4i and some others, <ESC>*b<n>Y
+ * doesn't properly clear the seed row if we are in
+ * compression mode 3.
+ */
+ if ((num_blank_lines < MIN_SKIP_LINES && compression != 3) ||
+ !(pcl->features & PCL_ANY_SPACING))
+ {
+ int mode_3ns = ((pcl->features & PCL_MODE_3_COMPRESSION) && !(pcl->features & PCL_ANY_SPACING));
+ if (mode_3ns && compression != 2)
+ {
+ /* Switch to mode 2 */
+ fz_puts(out, from3to2);
+ compression = 2;
+ }
+ if (pcl->features & PCL_MODE_3_COMPRESSION)
+ {
+ /* Must clear the seed row. */
+ fz_puts(out, "\033*b1Y");
+ num_blank_lines--;
+ }
+ if (mode_3ns)
+ {
+ for (; num_blank_lines; num_blank_lines--)
+ fz_puts(out, "\033*b0W");
+ }
+ else
+ {
+ for (; num_blank_lines; num_blank_lines--)
+ fz_puts(out, "\033*bW");
+ }
+ }
+ else if (pcl->features & PCL3_SPACING)
+ fz_printf(out, "\033*p+%dY", num_blank_lines * bitmap->yres);
+ else
+ fz_printf(out, "\033*b%dY", num_blank_lines);
+ /* Clear the seed row (only matters for mode 3 compression). */
+ memset(prev_row, 0, line_size);
+ }
+ num_blank_lines = 0;
+
+ /* Choose the best compression mode for this particular line. */
+ if (pcl->features & PCL_MODE_3_COMPRESSION)
+ {
+ /* Compression modes 2 and 3 are both available. Try
+ * both and see which produces the least output data.
+ */
+ int count3 = mode3compress(out_row_mode_3, data, prev_row, line_size);
+ int count2 = mode2compress(out_row_mode_2, data, line_size);
+ int penalty3 = (compression == 3 ? 0 : penalty_from2to3);
+ int penalty2 = (compression == 2 ? 0 : penalty_from3to2);
+
+ if (count3 + penalty3 < count2 + penalty2)
+ {
+ if (compression != 3)
+ fz_puts(out, from2to3);
+ compression = 3;
+ out_data = (unsigned char *)out_row_mode_3;
+ out_count = count3;
+ }
+ else
+ {
+ if (compression != 2)
+ fz_puts(out, from3to2);
+ compression = 2;
+ out_data = (unsigned char *)out_row_mode_2;
+ out_count = count2;
+ }
+ }
+ else if (pcl->features & PCL_MODE_2_COMPRESSION)
+ {
+ out_data = out_row_mode_2;
+ out_count = mode2compress(out_row_mode_2, data, line_size);
+ }
+ else
+ {
+ out_data = data;
+ out_count = line_size;
+ }
+
+ /* Transfer the data */
+ fz_printf(out, "\033*b%dW", out_count);
+ fz_write(out, out_data, out_count);
+ }
+
+ /* end raster graphics and eject page */
+ fz_puts(out, "\033*rB\f");
+
+ if (pcl->features & HACK__IS_A_OCE9050)
+ {
+ /* Pen up, pen select, advance full page, reset */
+ fz_puts(out, "\033%1BPUSP0PG;\033E");
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, prev_row);
+ fz_free(ctx, out_row_mode_2);
+ fz_free(ctx, out_row_mode_3);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+fz_write_pcl(fz_context *ctx, fz_pixmap *pixmap, char *filename, int append, fz_pcl_options *pcl)
+{
+ FILE *fp;
+ fz_output *out = NULL;
+
+ fp = fopen(filename, append ? "ab" : "wb");
+ if (!fp)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+ }
+
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ out = fz_new_output_with_file(ctx, fp);
+ fz_output_pcl(out, pixmap, pcl);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fclose(fp);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+fz_write_pcl_bitmap(fz_context *ctx, fz_bitmap *bitmap, char *filename, int append, fz_pcl_options *pcl)
+{
+ FILE *fp;
+ fz_output *out = NULL;
+
+ fp = fopen(filename, append ? "ab" : "wb");
+ if (!fp)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+ }
+
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ out = fz_new_output_with_file(ctx, fp);
+ fz_output_pcl_bitmap(out, bitmap, pcl);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fclose(fp);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
diff --git a/source/fitz/output-pwg.c b/source/fitz/output-pwg.c
new file mode 100644
index 00000000..fe1fcd2e
--- /dev/null
+++ b/source/fitz/output-pwg.c
@@ -0,0 +1,318 @@
+#include "mupdf/fitz.h"
+
+void
+fz_output_pwg_file_header(fz_output *out)
+{
+ static const unsigned char pwgsig[4] = { 'R', 'a', 'S', '2' };
+
+ /* Sync word */
+ fz_write(out, pwgsig, 4);
+}
+
+static void
+output_header(fz_output *out, const fz_pwg_options *pwg, int xres, int yres, int w, int h, int bpp)
+{
+ static const char zero[64] = { 0 };
+ int i;
+
+ /* Page Header: */
+ fz_write(out, pwg ? pwg->media_class : zero, 64);
+ fz_write(out, pwg ? pwg->media_color : zero, 64);
+ fz_write(out, pwg ? pwg->media_type : zero, 64);
+ fz_write(out, pwg ? pwg->output_type : zero, 64);
+ fz_write_int32be(out, pwg ? pwg->advance_distance : 0);
+ fz_write_int32be(out, pwg ? pwg->advance_media : 0);
+ fz_write_int32be(out, pwg ? pwg->collate : 0);
+ fz_write_int32be(out, pwg ? pwg->cut_media : 0);
+ fz_write_int32be(out, pwg ? pwg->duplex : 0);
+ fz_write_int32be(out, xres);
+ fz_write_int32be(out, yres);
+ /* CUPS format says that 284->300 are supposed to be the bbox of the
+ * page in points. PWG says 'Reserved'. */
+ for (i=284; i < 300; i += 4)
+ fz_write(out, zero, 4);
+ fz_write_int32be(out, pwg ? pwg->insert_sheet : 0);
+ fz_write_int32be(out, pwg ? pwg->jog : 0);
+ fz_write_int32be(out, pwg ? pwg->leading_edge : 0);
+ /* CUPS format says that 312->320 are supposed to be the margins of
+ * the lower left hand edge of page in points. PWG says 'Reserved'. */
+ for (i=312; i < 320; i += 4)
+ fz_write(out, zero, 4);
+ fz_write_int32be(out, pwg ? pwg->manual_feed : 0);
+ fz_write_int32be(out, pwg ? pwg->media_position : 0);
+ fz_write_int32be(out, pwg ? pwg->media_weight : 0);
+ fz_write_int32be(out, pwg ? pwg->mirror_print : 0);
+ fz_write_int32be(out, pwg ? pwg->negative_print : 0);
+ fz_write_int32be(out, pwg ? pwg->num_copies : 0);
+ fz_write_int32be(out, pwg ? pwg->orientation : 0);
+ fz_write_int32be(out, pwg ? pwg->output_face_up : 0);
+ fz_write_int32be(out, w * 72/ xres); /* Page size in points */
+ fz_write_int32be(out, h * 72/ yres);
+ fz_write_int32be(out, pwg ? pwg->separations : 0);
+ fz_write_int32be(out, pwg ? pwg->tray_switch : 0);
+ fz_write_int32be(out, pwg ? pwg->tumble : 0);
+ fz_write_int32be(out, w); /* Page image in pixels */
+ fz_write_int32be(out, h);
+ fz_write_int32be(out, pwg ? pwg->media_type_num : 0);
+ fz_write_int32be(out, bpp < 8 ? 1 : 8); /* Bits per color */
+ fz_write_int32be(out, bpp); /* Bits per pixel */
+ fz_write_int32be(out, (w * bpp + 7)/8); /* Bytes per line */
+ fz_write_int32be(out, 0); /* Chunky pixels */
+ fz_write_int32be(out, bpp == 1 ? 3 /* Black */ : (bpp == 8 ? 18 /* Sgray */ : 19 /* Srgb */)); /* Colorspace */
+ fz_write_int32be(out, pwg ? pwg->compression : 0);
+ fz_write_int32be(out, pwg ? pwg->row_count : 0);
+ fz_write_int32be(out, pwg ? pwg->row_feed : 0);
+ fz_write_int32be(out, pwg ? pwg->row_step : 0);
+ fz_write_int32be(out, bpp <= 8 ? 1 : 3); /* Num Colors */
+ for (i=424; i < 452; i += 4)
+ fz_write(out, zero, 4);
+ fz_write_int32be(out, 1); /* TotalPageCount */
+ fz_write_int32be(out, 1); /* CrossFeedTransform */
+ fz_write_int32be(out, 1); /* FeedTransform */
+ fz_write_int32be(out, 0); /* ImageBoxLeft */
+ fz_write_int32be(out, 0); /* ImageBoxTop */
+ fz_write_int32be(out, w); /* ImageBoxRight */
+ fz_write_int32be(out, h); /* ImageBoxBottom */
+ for (i=480; i < 1668; i += 4)
+ fz_write(out, zero, 4);
+ fz_write(out, pwg ? pwg->rendering_intent : zero, 64);
+ fz_write(out, pwg ? pwg->page_size_name : zero, 64);
+}
+
+void
+fz_output_pwg_page(fz_output *out, const fz_pixmap *pixmap, const fz_pwg_options *pwg)
+{
+ unsigned char *sp;
+ int y, x, sn, dn, ss;
+ fz_context *ctx;
+
+ if (!out || !pixmap)
+ return;
+
+ ctx = out->ctx;
+
+ if (pixmap->n != 1 && pixmap->n != 2 && pixmap->n != 4)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "pixmap must be grayscale or rgb to write as pwg");
+
+ sn = pixmap->n;
+ dn = pixmap->n;
+ if (dn == 2 || dn == 4)
+ dn--;
+
+ output_header(out, pwg, pixmap->xres, pixmap->yres, pixmap->w, pixmap->h, dn*8);
+
+ /* Now output the actual bitmap, using a packbits like compression */
+ sp = pixmap->samples;
+ ss = pixmap->w * sn;
+ y = 0;
+ while (y < pixmap->h)
+ {
+ int yrep;
+
+ assert(sp == pixmap->samples + y * ss);
+
+ /* Count the number of times this line is repeated */
+ for (yrep = 1; yrep < 256 && y+yrep < pixmap->h; yrep++)
+ {
+ if (memcmp(sp, sp + yrep * ss, ss) != 0)
+ break;
+ }
+ fz_write_byte(out, yrep-1);
+
+ /* Encode the line */
+ x = 0;
+ while (x < pixmap->w)
+ {
+ int d;
+
+ assert(sp == pixmap->samples + y * ss + x * sn);
+
+ /* How far do we have to look to find a repeated value? */
+ for (d = 1; d < 128 && x+d < pixmap->w; d++)
+ {
+ if (memcmp(sp + (d-1)*sn, sp + d*sn, sn) == 0)
+ break;
+ }
+ if (d == 1)
+ {
+ int xrep;
+
+ /* We immediately have a repeat (or we've hit
+ * the end of the line). Count the number of
+ * times this value is repeated. */
+ for (xrep = 1; xrep < 128 && x+xrep < pixmap->w; xrep++)
+ {
+ if (memcmp(sp, sp + xrep*sn, sn) != 0)
+ break;
+ }
+ fz_write_byte(out, xrep-1);
+ fz_write(out, sp, dn);
+ sp += sn*xrep;
+ x += xrep;
+ }
+ else
+ {
+ fz_write_byte(out, 257-d);
+ x += d;
+ while (d > 0)
+ {
+ fz_write(out, sp, dn);
+ sp += sn;
+ d--;
+ }
+ }
+ }
+
+ /* Move to the next line */
+ sp += ss*(yrep-1);
+ y += yrep;
+ }
+}
+
+void
+fz_output_pwg_bitmap_page(fz_output *out, const fz_bitmap *bitmap, const fz_pwg_options *pwg)
+{
+ unsigned char *sp;
+ int y, x, ss;
+ int byte_width;
+
+ if (!out || !bitmap)
+ return;
+
+ output_header(out, pwg, bitmap->xres, bitmap->yres, bitmap->w, bitmap->h, 1);
+
+ /* Now output the actual bitmap, using a packbits like compression */
+ sp = bitmap->samples;
+ ss = bitmap->stride;
+ byte_width = (bitmap->w+7)/8;
+ y = 0;
+ while (y < bitmap->h)
+ {
+ int yrep;
+
+ assert(sp == bitmap->samples + y * ss);
+
+ /* Count the number of times this line is repeated */
+ for (yrep = 1; yrep < 256 && y+yrep < bitmap->h; yrep++)
+ {
+ if (memcmp(sp, sp + yrep * ss, byte_width) != 0)
+ break;
+ }
+ fz_write_byte(out, yrep-1);
+
+ /* Encode the line */
+ x = 0;
+ while (x < byte_width)
+ {
+ int d;
+
+ assert(sp == bitmap->samples + y * ss + x);
+
+ /* How far do we have to look to find a repeated value? */
+ for (d = 1; d < 128 && x+d < byte_width; d++)
+ {
+ if (sp[d-1] == sp[d])
+ break;
+ }
+ if (d == 1)
+ {
+ int xrep;
+
+ /* We immediately have a repeat (or we've hit
+ * the end of the line). Count the number of
+ * times this value is repeated. */
+ for (xrep = 1; xrep < 128 && x+xrep < byte_width; xrep++)
+ {
+ if (sp[0] != sp[xrep])
+ break;
+ }
+ fz_write_byte(out, xrep-1);
+ fz_write(out, sp, 1);
+ sp += xrep;
+ x += xrep;
+ }
+ else
+ {
+ fz_write_byte(out, 257-d);
+ fz_write(out, sp, d);
+ sp += d;
+ x += d;
+ }
+ }
+
+ /* Move to the next line */
+ sp += ss*yrep - byte_width;
+ y += yrep;
+ }
+}
+
+void
+fz_output_pwg(fz_output *out, const fz_pixmap *pixmap, const fz_pwg_options *pwg)
+{
+ fz_output_pwg_file_header(out);
+ fz_output_pwg_page(out, pixmap, pwg);
+}
+
+void
+fz_write_pwg(fz_context *ctx, fz_pixmap *pixmap, char *filename, int append, const fz_pwg_options *pwg)
+{
+ FILE *fp;
+ fz_output *out = NULL;
+
+ fp = fopen(filename, append ? "ab" : "wb");
+ if (!fp)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+ }
+
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ out = fz_new_output_with_file(ctx, fp);
+ if (!append)
+ fz_output_pwg_file_header(out);
+ fz_output_pwg_page(out, pixmap, pwg);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fclose(fp);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+fz_write_pwg_bitmap(fz_context *ctx, fz_bitmap *bitmap, char *filename, int append, const fz_pwg_options *pwg)
+{
+ FILE *fp;
+ fz_output *out = NULL;
+
+ fp = fopen(filename, append ? "ab" : "wb");
+ if (!fp)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+ }
+
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ out = fz_new_output_with_file(ctx, fp);
+ if (!append)
+ fz_output_pwg_file_header(out);
+ fz_output_pwg_bitmap_page(out, bitmap, pwg);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fclose(fp);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
diff --git a/source/fitz/output.c b/source/fitz/output.c
new file mode 100644
index 00000000..f98b945e
--- /dev/null
+++ b/source/fitz/output.c
@@ -0,0 +1,100 @@
+#include "mupdf/fitz.h"
+
+static int
+file_printf(fz_output *out, const char *fmt, va_list ap)
+{
+ FILE *file = (FILE *)out->opaque;
+
+ return vfprintf(file, fmt, ap);
+}
+
+static int
+file_write(fz_output *out, const void *buffer, int count)
+{
+ FILE *file = (FILE *)out->opaque;
+
+ return fwrite(buffer, 1, count, file);
+}
+
+fz_output *
+fz_new_output_with_file(fz_context *ctx, FILE *file)
+{
+ fz_output *out = fz_malloc_struct(ctx, fz_output);
+ out->ctx = ctx;
+ out->opaque = file;
+ out->printf = file_printf;
+ out->write = file_write;
+ out->close = NULL;
+ return out;
+}
+
+void
+fz_close_output(fz_output *out)
+{
+ if (!out)
+ return;
+ if (out->close)
+ out->close(out);
+ fz_free(out->ctx, out);
+}
+
+int
+fz_printf(fz_output *out, const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ if (!out)
+ return 0;
+
+ va_start(ap, fmt);
+ ret = out->printf(out, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+int
+fz_write(fz_output *out, const void *data, int len)
+{
+ if (!out)
+ return 0;
+ return out->write(out, data, len);
+}
+
+int
+fz_puts(fz_output *out, const char *str)
+{
+ if (!out)
+ return 0;
+ return out->write(out, str, strlen(str));
+}
+
+static int
+buffer_printf(fz_output *out, const char *fmt, va_list list)
+{
+ fz_buffer *buffer = (fz_buffer *)out->opaque;
+
+ return fz_buffer_vprintf(out->ctx, buffer, fmt, list);
+}
+
+static int
+buffer_write(fz_output *out, const void *data, int len)
+{
+ fz_buffer *buffer = (fz_buffer *)out->opaque;
+
+ fz_write_buffer(out->ctx, buffer, (unsigned char *)data, len);
+ return len;
+}
+
+fz_output *
+fz_new_output_with_buffer(fz_context *ctx, fz_buffer *buf)
+{
+ fz_output *out = fz_malloc_struct(ctx, fz_output);
+ out->ctx = ctx;
+ out->opaque = buf;
+ out->printf = buffer_printf;
+ out->write = buffer_write;
+ out->close = NULL;
+ return out;
+}
diff --git a/source/fitz/path.c b/source/fitz/path.c
new file mode 100644
index 00000000..cbdfc7bc
--- /dev/null
+++ b/source/fitz/path.c
@@ -0,0 +1,507 @@
+#include <assert.h>
+#include "mupdf/fitz.h"
+
+fz_path *
+fz_new_path(fz_context *ctx)
+{
+ fz_path *path;
+
+ path = fz_malloc_struct(ctx, fz_path);
+ path->len = 0;
+ path->cap = 0;
+ path->items = NULL;
+ path->last = -1;
+
+ return path;
+}
+
+fz_path *
+fz_clone_path(fz_context *ctx, fz_path *old)
+{
+ fz_path *path;
+
+ assert(old);
+ path = fz_malloc_struct(ctx, fz_path);
+ fz_try(ctx)
+ {
+ path->len = old->len;
+ path->cap = old->len;
+ path->items = fz_malloc_array(ctx, path->cap, sizeof(fz_path_item));
+ memcpy(path->items, old->items, sizeof(fz_path_item) * path->len);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, path);
+ fz_rethrow(ctx);
+ }
+
+ return path;
+}
+
+void
+fz_free_path(fz_context *ctx, fz_path *path)
+{
+ if (path == NULL)
+ return;
+ fz_free(ctx, path->items);
+ fz_free(ctx, path);
+}
+
+static void
+grow_path(fz_context *ctx, fz_path *path, int n)
+{
+ int newcap = path->cap;
+ if (path->len + n <= path->cap)
+ {
+ path->last = path->len;
+ return;
+ }
+ while (path->len + n > newcap)
+ newcap = newcap + 36;
+ path->items = fz_resize_array(ctx, path->items, newcap, sizeof(fz_path_item));
+ path->cap = newcap;
+ path->last = path->len;
+}
+
+fz_point
+fz_currentpoint(fz_context *ctx, fz_path *path)
+{
+ fz_point c, m;
+ int i;
+
+ c.x = c.y = m.x = m.y = 0;
+ i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ m.x = c.x = path->items[i++].v;
+ m.y = c.y = path->items[i++].v;
+ break;
+ case FZ_LINETO:
+ c.x = path->items[i++].v;
+ c.y = path->items[i++].v;
+ break;
+ case FZ_CURVETO:
+ i += 4;
+ c.x = path->items[i++].v;
+ c.y = path->items[i++].v;
+ break;
+ case FZ_CLOSE_PATH:
+ c = m;
+ }
+ }
+
+ return c;
+}
+
+void
+fz_moveto(fz_context *ctx, fz_path *path, float x, float y)
+{
+ if (path->last >= 0 && path->items[path->last].k == FZ_MOVETO)
+ {
+ /* No point in having MOVETO then MOVETO */
+ path->len = path->last;
+ }
+ grow_path(ctx, path, 3);
+ path->items[path->len++].k = FZ_MOVETO;
+ path->items[path->len++].v = x;
+ path->items[path->len++].v = y;
+}
+
+void
+fz_lineto(fz_context *ctx, fz_path *path, float x, float y)
+{
+ float x0, y0;
+
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "lineto with no current point");
+ return;
+ }
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ {
+ x0 = path->items[path->last-2].v;
+ y0 = path->items[path->last-1].v;
+ }
+ else
+ {
+ x0 = path->items[path->len-2].v;
+ y0 = path->items[path->len-1].v;
+ }
+ /* Anything other than MoveTo followed by LineTo the same place is a nop */
+ if (path->items[path->last].k != FZ_MOVETO && x0 == x && y0 == y)
+ return;
+ grow_path(ctx, path, 3);
+ path->items[path->len++].k = FZ_LINETO;
+ path->items[path->len++].v = x;
+ path->items[path->len++].v = y;
+}
+
+void
+fz_curveto(fz_context *ctx, fz_path *path,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3)
+{
+ float x0, y0;
+
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "curveto with no current point");
+ return;
+ }
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ {
+ x0 = path->items[path->last-2].v;
+ y0 = path->items[path->last-1].v;
+ }
+ else
+ {
+ x0 = path->items[path->len-2].v;
+ y0 = path->items[path->len-1].v;
+ }
+
+ /* Check for degenerate cases: */
+ if (x0 == x1 && y0 == y1)
+ {
+ if (x2 == x3 && y2 == y3)
+ {
+ /* If (x1,y1)==(x2,y2) and prev wasn't a moveto, then skip */
+ if (x1 == x2 && y1 == y2 && path->items[path->last].k != FZ_MOVETO)
+ return;
+ /* Otherwise a line will suffice */
+ fz_lineto(ctx, path, x3, y3);
+ return;
+ }
+ if (x1 == x2 && y1 == y2)
+ {
+ /* A line will suffice */
+ fz_lineto(ctx, path, x3, y3);
+ return;
+ }
+ }
+ else if (x1 == x2 && y1 == y2 && x2 == x3 && y2 == y3)
+ {
+ /* A line will suffice */
+ fz_lineto(ctx, path, x3, y3);
+ return;
+ }
+
+ grow_path(ctx, path, 7);
+ path->items[path->len++].k = FZ_CURVETO;
+ path->items[path->len++].v = x1;
+ path->items[path->len++].v = y1;
+ path->items[path->len++].v = x2;
+ path->items[path->len++].v = y2;
+ path->items[path->len++].v = x3;
+ path->items[path->len++].v = y3;
+}
+
+void
+fz_curvetov(fz_context *ctx, fz_path *path, float x2, float y2, float x3, float y3)
+{
+ float x1, y1;
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "curvetov with no current point");
+ return;
+ }
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ {
+ x1 = path->items[path->last-2].v;
+ y1 = path->items[path->last-1].v;
+ }
+ else
+ {
+ x1 = path->items[path->len-2].v;
+ y1 = path->items[path->len-1].v;
+ }
+ fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3);
+}
+
+void
+fz_curvetoy(fz_context *ctx, fz_path *path, float x1, float y1, float x3, float y3)
+{
+ fz_curveto(ctx, path, x1, y1, x3, y3, x3, y3);
+}
+
+void
+fz_closepath(fz_context *ctx, fz_path *path)
+{
+ if (path->last < 0)
+ {
+ fz_warn(ctx, "closepath with no current point");
+ return;
+ }
+ /* CLOSE following a CLOSE is a NOP */
+ if (path->items[path->last].k == FZ_CLOSE_PATH)
+ return;
+ grow_path(ctx, path, 1);
+ path->items[path->len++].k = FZ_CLOSE_PATH;
+}
+
+static inline fz_rect *bound_expand(fz_rect *r, const fz_point *p)
+{
+ if (p->x < r->x0) r->x0 = p->x;
+ if (p->y < r->y0) r->y0 = p->y;
+ if (p->x > r->x1) r->x1 = p->x;
+ if (p->y > r->y1) r->y1 = p->y;
+ return r;
+}
+
+fz_rect *
+fz_bound_path(fz_context *ctx, fz_path *path, const fz_stroke_state *stroke, const fz_matrix *ctm, fz_rect *r)
+{
+ fz_point p;
+ int i = 0;
+
+ /* If the path is empty, return the empty rectangle here - don't wait
+ * for it to be expanded in the stroked case below. */
+ if (path->len == 0)
+ {
+ *r = fz_empty_rect;
+ return r;
+ }
+ /* A path must start with a moveto - and if that's all there is
+ * then the path is empty. */
+ if (path->len == 3)
+ {
+ *r = fz_empty_rect;
+ return r;
+ }
+
+ p.x = path->items[1].v;
+ p.y = path->items[2].v;
+ fz_transform_point(&p, ctm);
+ r->x0 = r->x1 = p.x;
+ r->y0 = r->y1 = p.y;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_CURVETO:
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ break;
+ case FZ_MOVETO:
+ if (i + 2 == path->len)
+ {
+ /* Trailing Moveto - cannot affect bbox */
+ i += 2;
+ break;
+ }
+ /* fallthrough */
+ case FZ_LINETO:
+ p.x = path->items[i++].v;
+ p.y = path->items[i++].v;
+ bound_expand(r, fz_transform_point(&p, ctm));
+ break;
+ case FZ_CLOSE_PATH:
+ break;
+ }
+ }
+
+ if (stroke)
+ {
+ fz_adjust_rect_for_stroke(r, stroke, ctm);
+ }
+
+ return r;
+}
+
+fz_rect *
+fz_adjust_rect_for_stroke(fz_rect *r, const fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ float expand;
+
+ if (!stroke)
+ return r;
+
+ expand = stroke->linewidth;
+ if (expand == 0)
+ expand = 1.0f;
+ expand *= fz_matrix_max_expansion(ctm);
+ if ((stroke->linejoin == FZ_LINEJOIN_MITER || stroke->linejoin == FZ_LINEJOIN_MITER_XPS) && stroke->miterlimit > 1)
+ expand *= stroke->miterlimit;
+
+ r->x0 -= expand;
+ r->y0 -= expand;
+ r->x1 += expand;
+ r->y1 += expand;
+ return r;
+}
+
+void
+fz_transform_path(fz_context *ctx, fz_path *path, const fz_matrix *ctm)
+{
+ int k, i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_CURVETO:
+ for (k = 0; k < 3; k++)
+ {
+ fz_transform_point((fz_point *)(void *)&path->items[i].v, ctm);
+ i += 2;
+ }
+ break;
+ case FZ_MOVETO:
+ case FZ_LINETO:
+ fz_transform_point((fz_point *)(void *)&path->items[i].v, ctm);
+ i += 2;
+ break;
+ case FZ_CLOSE_PATH:
+ break;
+ }
+ }
+}
+
+#ifndef NDEBUG
+void
+fz_print_path(fz_context *ctx, FILE *out, fz_path *path, int indent)
+{
+ float x, y;
+ int i = 0;
+ int n;
+ while (i < path->len)
+ {
+ for (n = 0; n < indent; n++)
+ fputc(' ', out);
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g m\n", x, y);
+ break;
+ case FZ_LINETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g l\n", x, y);
+ break;
+ case FZ_CURVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fprintf(out, "%g %g c\n", x, y);
+ break;
+ case FZ_CLOSE_PATH:
+ fprintf(out, "h\n");
+ break;
+ }
+ }
+}
+#endif
+
+fz_stroke_state *
+fz_keep_stroke_state(fz_context *ctx, fz_stroke_state *stroke)
+{
+ if (!stroke)
+ return NULL;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (stroke->refs > 0)
+ stroke->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return stroke;
+}
+
+void
+fz_drop_stroke_state(fz_context *ctx, fz_stroke_state *stroke)
+{
+ int drop;
+
+ if (!stroke)
+ return;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = (stroke->refs > 0 ? --stroke->refs == 0 : 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop)
+ fz_free(ctx, stroke);
+}
+
+fz_stroke_state *
+fz_new_stroke_state_with_len(fz_context *ctx, int len)
+{
+ fz_stroke_state *state;
+
+ len -= nelem(state->dash_list);
+ if (len < 0)
+ len = 0;
+
+ state = Memento_label(fz_malloc(ctx, sizeof(*state) + sizeof(state->dash_list[0]) * len), "fz_stroke_state");
+ state->refs = 1;
+ state->start_cap = FZ_LINECAP_BUTT;
+ state->dash_cap = FZ_LINECAP_BUTT;
+ state->end_cap = FZ_LINECAP_BUTT;
+ state->linejoin = FZ_LINEJOIN_MITER;
+ state->linewidth = 1;
+ state->miterlimit = 10;
+ state->dash_phase = 0;
+ state->dash_len = 0;
+ memset(state->dash_list, 0, sizeof(state->dash_list[0]) * (len + nelem(state->dash_list)));
+
+ return state;
+}
+
+fz_stroke_state *
+fz_new_stroke_state(fz_context *ctx)
+{
+ return fz_new_stroke_state_with_len(ctx, 0);
+}
+
+fz_stroke_state *
+fz_unshare_stroke_state_with_len(fz_context *ctx, fz_stroke_state *shared, int len)
+{
+ int single, unsize, shsize, shlen, drop;
+ fz_stroke_state *unshared;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ single = (shared->refs == 1);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ shlen = shared->dash_len - nelem(shared->dash_list);
+ if (shlen < 0)
+ shlen = 0;
+ shsize = sizeof(*shared) + sizeof(shared->dash_list[0]) * shlen;
+ len -= nelem(shared->dash_list);
+ if (len < 0)
+ len = 0;
+ if (single && shlen >= len)
+ return shared;
+ unsize = sizeof(*unshared) + sizeof(unshared->dash_list[0]) * len;
+ unshared = Memento_label(fz_malloc(ctx, unsize), "fz_stroke_state");
+ memcpy(unshared, shared, (shsize > unsize ? unsize : shsize));
+ unshared->refs = 1;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ drop = (shared->refs > 0 ? --shared->refs == 0 : 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop)
+ fz_free(ctx, shared);
+ return unshared;
+}
+
+fz_stroke_state *
+fz_unshare_stroke_state(fz_context *ctx, fz_stroke_state *shared)
+{
+ return fz_unshare_stroke_state_with_len(ctx, shared, shared->dash_len);
+}
diff --git a/source/fitz/pixmap.c b/source/fitz/pixmap.c
new file mode 100644
index 00000000..7391c17e
--- /dev/null
+++ b/source/fitz/pixmap.c
@@ -0,0 +1,1062 @@
+#include "mupdf/fitz.h"
+
+fz_pixmap *
+fz_keep_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ return (fz_pixmap *)fz_keep_storable(ctx, &pix->storable);
+}
+
+void
+fz_drop_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ fz_drop_storable(ctx, &pix->storable);
+}
+
+void
+fz_free_pixmap_imp(fz_context *ctx, fz_storable *pix_)
+{
+ fz_pixmap *pix = (fz_pixmap *)pix_;
+
+ if (pix->colorspace)
+ fz_drop_colorspace(ctx, pix->colorspace);
+ if (pix->free_samples)
+ fz_free(ctx, pix->samples);
+ fz_free(ctx, pix);
+}
+
+fz_pixmap *
+fz_new_pixmap_with_data(fz_context *ctx, fz_colorspace *colorspace, int w, int h, unsigned char *samples)
+{
+ fz_pixmap *pix;
+
+ if (w < 0 || h < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Illegal dimensions for pixmap %d %d", w, h);
+
+ pix = fz_malloc_struct(ctx, fz_pixmap);
+ FZ_INIT_STORABLE(pix, 1, fz_free_pixmap_imp);
+ pix->x = 0;
+ pix->y = 0;
+ pix->w = w;
+ pix->h = h;
+ pix->interpolate = 1;
+ pix->xres = 96;
+ pix->yres = 96;
+ pix->colorspace = NULL;
+ pix->n = 1;
+
+ if (colorspace)
+ {
+ pix->colorspace = fz_keep_colorspace(ctx, colorspace);
+ pix->n = 1 + colorspace->n;
+ }
+
+ pix->samples = samples;
+ if (samples)
+ {
+ pix->free_samples = 0;
+ }
+ else
+ {
+ fz_try(ctx)
+ {
+ if (pix->w + pix->n - 1 > INT_MAX / pix->n)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "overly wide image");
+ pix->samples = fz_malloc_array(ctx, pix->h, pix->w * pix->n);
+ }
+ fz_catch(ctx)
+ {
+ if (colorspace)
+ fz_drop_colorspace(ctx, colorspace);
+ fz_free(ctx, pix);
+ fz_rethrow(ctx);
+ }
+ pix->free_samples = 1;
+ }
+
+ return pix;
+}
+
+fz_pixmap *
+fz_new_pixmap(fz_context *ctx, fz_colorspace *colorspace, int w, int h)
+{
+ return fz_new_pixmap_with_data(ctx, colorspace, w, h, NULL);
+}
+
+fz_pixmap *
+fz_new_pixmap_with_bbox(fz_context *ctx, fz_colorspace *colorspace, const fz_irect *r)
+{
+ fz_pixmap *pixmap;
+ pixmap = fz_new_pixmap(ctx, colorspace, r->x1 - r->x0, r->y1 - r->y0);
+ pixmap->x = r->x0;
+ pixmap->y = r->y0;
+ return pixmap;
+}
+
+fz_pixmap *
+fz_new_pixmap_with_bbox_and_data(fz_context *ctx, fz_colorspace *colorspace, const fz_irect *r, unsigned char *samples)
+{
+ fz_pixmap *pixmap = fz_new_pixmap_with_data(ctx, colorspace, r->x1 - r->x0, r->y1 - r->y0, samples);
+ pixmap->x = r->x0;
+ pixmap->y = r->y0;
+ return pixmap;
+}
+
+fz_irect *
+fz_pixmap_bbox(fz_context *ctx, fz_pixmap *pix, fz_irect *bbox)
+{
+ bbox->x0 = pix->x;
+ bbox->y0 = pix->y;
+ bbox->x1 = pix->x + pix->w;
+ bbox->y1 = pix->y + pix->h;
+ return bbox;
+}
+
+fz_irect *
+fz_pixmap_bbox_no_ctx(fz_pixmap *pix, fz_irect *bbox)
+{
+ bbox->x0 = pix->x;
+ bbox->y0 = pix->y;
+ bbox->x1 = pix->x + pix->w;
+ bbox->y1 = pix->y + pix->h;
+ return bbox;
+}
+
+int
+fz_pixmap_width(fz_context *ctx, fz_pixmap *pix)
+{
+ return pix->w;
+}
+
+int
+fz_pixmap_height(fz_context *ctx, fz_pixmap *pix)
+{
+ return pix->h;
+}
+
+void
+fz_clear_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ memset(pix->samples, 0, (unsigned int)(pix->w * pix->h * pix->n));
+}
+
+void
+fz_clear_pixmap_with_value(fz_context *ctx, fz_pixmap *pix, int value)
+{
+ if (value == 255)
+ {
+ memset(pix->samples, 255, (unsigned int)(pix->w * pix->h * pix->n));
+ }
+ else
+ {
+ int k, x, y;
+ unsigned char *s = pix->samples;
+ for (y = 0; y < pix->h; y++)
+ {
+ for (x = 0; x < pix->w; x++)
+ {
+ for (k = 0; k < pix->n - 1; k++)
+ *s++ = value;
+ *s++ = 255;
+ }
+ }
+ }
+}
+
+void
+fz_copy_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_pixmap *src, const fz_irect *b)
+{
+ const unsigned char *srcp;
+ unsigned char *destp;
+ int x, y, w, destspan, srcspan;
+ fz_irect local_b, bb;
+
+ local_b = *b;
+ fz_intersect_irect(&local_b, fz_pixmap_bbox(ctx, dest, &bb));
+ fz_intersect_irect(&local_b, fz_pixmap_bbox(ctx, src, &bb));
+ w = local_b.x1 - local_b.x0;
+ y = local_b.y1 - local_b.y0;
+ if (w <= 0 || y <= 0)
+ return;
+
+ srcspan = src->w * src->n;
+ srcp = src->samples + (unsigned int)(srcspan * (local_b.y0 - src->y) + src->n * (local_b.x0 - src->x));
+ destspan = dest->w * dest->n;
+ destp = dest->samples + (unsigned int)(destspan * (local_b.y0 - dest->y) + dest->n * (local_b.x0 - dest->x));
+
+ if (src->n == dest->n)
+ {
+ w *= src->n;
+ do
+ {
+ memcpy(destp, srcp, w);
+ srcp += srcspan;
+ destp += destspan;
+ }
+ while (--y);
+ }
+ else if (src->n == 2 && dest->n == 4)
+ {
+ /* Copy, and convert from grey+alpha to rgb+alpha */
+ srcspan -= w*2;
+ destspan -= w*4;
+ do
+ {
+ for (x = w; x > 0; x--)
+ {
+ unsigned char v = *srcp++;
+ unsigned char a = *srcp++;
+ *destp++ = v;
+ *destp++ = v;
+ *destp++ = v;
+ *destp++ = a;
+ }
+ srcp += srcspan;
+ destp += destspan;
+ }
+ while (--y);
+ }
+ else if (src->n == 4 && dest->n == 2)
+ {
+ /* Copy, and convert from rgb+alpha to grey+alpha */
+ srcspan -= w*4;
+ destspan -= w*2;
+ do
+ {
+ for (x = w; x > 0; x--)
+ {
+ int v;
+ v = *srcp++;
+ v += *srcp++;
+ v += *srcp++;
+ *destp++ = (unsigned char)((v+1)/3);
+ *destp++ = *srcp++;
+ }
+ srcp += srcspan;
+ destp += destspan;
+ }
+ while (--y);
+ }
+ else
+ {
+ /* FIXME: Crap conversion */
+ int z;
+ int sn = src->n-1;
+ int dn = dest->n-1;
+
+ srcspan -= w*src->n;
+ destspan -= w*dest->n;
+ do
+ {
+ for (x = w; x > 0; x--)
+ {
+ int v = 0;
+ for (z = sn; z > 0; z--)
+ v += *srcp++;
+ v = (v * dn + (sn>>1)) / sn;
+ for (z = dn; z > 0; z--)
+ *destp++ = (unsigned char)v;
+ *destp++ = *srcp++;
+ }
+ srcp += srcspan;
+ destp += destspan;
+ }
+ while (--y);
+ }
+}
+
+void
+fz_clear_pixmap_rect_with_value(fz_context *ctx, fz_pixmap *dest, int value, const fz_irect *b)
+{
+ unsigned char *destp;
+ int x, y, w, k, destspan;
+ fz_irect bb;
+ fz_irect local_b = *b;
+
+ fz_intersect_irect(&local_b, fz_pixmap_bbox(ctx, dest, &bb));
+ w = local_b.x1 - local_b.x0;
+ y = local_b.y1 - local_b.y0;
+ if (w <= 0 || y <= 0)
+ return;
+
+ destspan = dest->w * dest->n;
+ destp = dest->samples + (unsigned int)(destspan * (local_b.y0 - dest->y) + dest->n * (local_b.x0 - dest->x));
+ if (value == 255)
+ do
+ {
+ memset(destp, 255, (unsigned int)(w * dest->n));
+ destp += destspan;
+ }
+ while (--y);
+ else
+ do
+ {
+ unsigned char *s = destp;
+ for (x = 0; x < w; x++)
+ {
+ for (k = 0; k < dest->n - 1; k++)
+ *s++ = value;
+ *s++ = 255;
+ }
+ destp += destspan;
+ }
+ while (--y);
+}
+
+void
+fz_premultiply_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ unsigned char *s = pix->samples;
+ unsigned char a;
+ int k, x, y;
+
+ for (y = 0; y < pix->h; y++)
+ {
+ for (x = 0; x < pix->w; x++)
+ {
+ a = s[pix->n - 1];
+ for (k = 0; k < pix->n - 1; k++)
+ s[k] = fz_mul255(s[k], a);
+ s += pix->n;
+ }
+ }
+}
+
+void
+fz_unmultiply_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ unsigned char *s = pix->samples;
+ int a, inva;
+ int k, x, y;
+
+ for (y = 0; y < pix->h; y++)
+ {
+ for (x = 0; x < pix->w; x++)
+ {
+ a = s[pix->n - 1];
+ inva = a ? 255 * 256 / a : 0;
+ for (k = 0; k < pix->n - 1; k++)
+ s[k] = (s[k] * inva) >> 8;
+ s += pix->n;
+ }
+ }
+}
+
+fz_pixmap *
+fz_alpha_from_gray(fz_context *ctx, fz_pixmap *gray, int luminosity)
+{
+ fz_pixmap *alpha;
+ unsigned char *sp, *dp;
+ int len;
+ fz_irect bbox;
+
+ assert(gray->n == 2);
+
+ alpha = fz_new_pixmap_with_bbox(ctx, NULL, fz_pixmap_bbox(ctx, gray, &bbox));
+ dp = alpha->samples;
+ sp = gray->samples;
+ if (!luminosity)
+ sp ++;
+
+ len = gray->w * gray->h;
+ while (len--)
+ {
+ *dp++ = sp[0];
+ sp += 2;
+ }
+
+ return alpha;
+}
+
+void
+fz_invert_pixmap(fz_context *ctx, fz_pixmap *pix)
+{
+ unsigned char *s = pix->samples;
+ int k, x, y;
+
+ for (y = 0; y < pix->h; y++)
+ {
+ for (x = 0; x < pix->w; x++)
+ {
+ for (k = 0; k < pix->n - 1; k++)
+ s[k] = 255 - s[k];
+ s += pix->n;
+ }
+ }
+}
+
+void fz_invert_pixmap_rect(fz_pixmap *image, const fz_irect *rect)
+{
+ unsigned char *p;
+ int x, y, n;
+
+ int x0 = fz_clampi(rect->x0 - image->x, 0, image->w - 1);
+ int x1 = fz_clampi(rect->x1 - image->x, 0, image->w - 1);
+ int y0 = fz_clampi(rect->y0 - image->y, 0, image->h - 1);
+ int y1 = fz_clampi(rect->y1 - image->y, 0, image->h - 1);
+
+ for (y = y0; y < y1; y++)
+ {
+ p = image->samples + (unsigned int)((y * image->w + x0) * image->n);
+ for (x = x0; x < x1; x++)
+ {
+ for (n = image->n; n > 1; n--, p++)
+ *p = 255 - *p;
+ p++;
+ }
+ }
+}
+
+void
+fz_gamma_pixmap(fz_context *ctx, fz_pixmap *pix, float gamma)
+{
+ unsigned char gamma_map[256];
+ unsigned char *s = pix->samples;
+ int k, x, y;
+
+ for (k = 0; k < 256; k++)
+ gamma_map[k] = pow(k / 255.0f, gamma) * 255;
+
+ for (y = 0; y < pix->h; y++)
+ {
+ for (x = 0; x < pix->w; x++)
+ {
+ for (k = 0; k < pix->n - 1; k++)
+ s[k] = gamma_map[s[k]];
+ s += pix->n;
+ }
+ }
+}
+
+/*
+ * Write pixmap to PNM file (without alpha channel)
+ */
+
+void
+fz_write_pnm(fz_context *ctx, fz_pixmap *pixmap, char *filename)
+{
+ FILE *fp;
+ unsigned char *p;
+ int len;
+
+ if (pixmap->n != 1 && pixmap->n != 2 && pixmap->n != 4)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "pixmap must be grayscale or rgb to write as pnm");
+
+ fp = fopen(filename, "wb");
+ if (!fp)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ if (pixmap->n == 1 || pixmap->n == 2)
+ fprintf(fp, "P5\n");
+ if (pixmap->n == 4)
+ fprintf(fp, "P6\n");
+ fprintf(fp, "%d %d\n", pixmap->w, pixmap->h);
+ fprintf(fp, "255\n");
+
+ len = pixmap->w * pixmap->h;
+ p = pixmap->samples;
+
+ switch (pixmap->n)
+ {
+ case 1:
+ fwrite(p, 1, len, fp);
+ break;
+ case 2:
+ while (len--)
+ {
+ putc(p[0], fp);
+ p += 2;
+ }
+ break;
+ case 4:
+ while (len--)
+ {
+ putc(p[0], fp);
+ putc(p[1], fp);
+ putc(p[2], fp);
+ p += 4;
+ }
+ }
+
+ fclose(fp);
+}
+
+/*
+ * Write pixmap to PAM file (with or without alpha channel)
+ */
+
+void
+fz_write_pam(fz_context *ctx, fz_pixmap *pixmap, char *filename, int savealpha)
+{
+ unsigned char *sp;
+ int y, w, k;
+ FILE *fp;
+
+ int sn = pixmap->n;
+ int dn = pixmap->n;
+ if (!savealpha && dn > 1)
+ dn--;
+
+ fp = fopen(filename, "wb");
+ if (!fp)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ fprintf(fp, "P7\n");
+ fprintf(fp, "WIDTH %d\n", pixmap->w);
+ fprintf(fp, "HEIGHT %d\n", pixmap->h);
+ fprintf(fp, "DEPTH %d\n", dn);
+ fprintf(fp, "MAXVAL 255\n");
+ if (pixmap->colorspace)
+ fprintf(fp, "# COLORSPACE %s\n", pixmap->colorspace->name);
+ switch (dn)
+ {
+ case 1: fprintf(fp, "TUPLTYPE GRAYSCALE\n"); break;
+ case 2: if (sn == 2) fprintf(fp, "TUPLTYPE GRAYSCALE_ALPHA\n"); break;
+ case 3: if (sn == 4) fprintf(fp, "TUPLTYPE RGB\n"); break;
+ case 4: if (sn == 4) fprintf(fp, "TUPLTYPE RGB_ALPHA\n"); break;
+ }
+ fprintf(fp, "ENDHDR\n");
+
+ sp = pixmap->samples;
+ for (y = 0; y < pixmap->h; y++)
+ {
+ w = pixmap->w;
+ while (w--)
+ {
+ for (k = 0; k < dn; k++)
+ putc(sp[k], fp);
+ sp += sn;
+ }
+ }
+
+ fclose(fp);
+}
+
+/*
+ * Write pixmap to PNG file (with or without alpha channel)
+ */
+
+#include <zlib.h>
+
+static inline void big32(unsigned char *buf, unsigned int v)
+{
+ buf[0] = (v >> 24) & 0xff;
+ buf[1] = (v >> 16) & 0xff;
+ buf[2] = (v >> 8) & 0xff;
+ buf[3] = (v) & 0xff;
+}
+
+static void putchunk(char *tag, unsigned char *data, int size, fz_output *out)
+{
+ unsigned int sum;
+ fz_write_int32be(out, size);
+ fz_write(out, tag, 4);
+ fz_write(out, data, size);
+ sum = crc32(0, NULL, 0);
+ sum = crc32(sum, (unsigned char*)tag, 4);
+ sum = crc32(sum, data, size);
+ fz_write_int32be(out, sum);
+}
+
+void
+fz_write_png(fz_context *ctx, fz_pixmap *pixmap, char *filename, int savealpha)
+{
+ FILE *fp = fopen(filename, "wb");
+ fz_output *out = NULL;
+
+ if (!fp)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+ }
+
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ out = fz_new_output_with_file(ctx, fp);
+ fz_output_png(out, pixmap, savealpha);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fclose(fp);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+fz_output_png(fz_output *out, const fz_pixmap *pixmap, int savealpha)
+{
+ static const unsigned char pngsig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
+ unsigned char head[13];
+ unsigned char *udata = NULL;
+ unsigned char *cdata = NULL;
+ unsigned char *sp, *dp;
+ uLong usize, csize;
+ int y, x, k, sn, dn;
+ int color;
+ int err;
+ fz_context *ctx;
+
+ if (!out || !pixmap)
+ return;
+
+ ctx = out->ctx;
+
+ fz_var(udata);
+ fz_var(cdata);
+
+ if (pixmap->n != 1 && pixmap->n != 2 && pixmap->n != 4)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "pixmap must be grayscale or rgb to write as png");
+
+ sn = pixmap->n;
+ dn = pixmap->n;
+ if (!savealpha && dn > 1)
+ dn--;
+
+ switch (dn)
+ {
+ default:
+ case 1: color = 0; break;
+ case 2: color = 4; break;
+ case 3: color = 2; break;
+ case 4: color = 6; break;
+ }
+
+ usize = (pixmap->w * dn + 1) * pixmap->h;
+ csize = compressBound(usize);
+ fz_try(ctx)
+ {
+ udata = fz_malloc(ctx, usize);
+ cdata = fz_malloc(ctx, csize);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, udata);
+ fz_rethrow(ctx);
+ }
+
+ sp = pixmap->samples;
+ dp = udata;
+ for (y = 0; y < pixmap->h; y++)
+ {
+ *dp++ = 1; /* sub prediction filter */
+ for (x = 0; x < pixmap->w; x++)
+ {
+ for (k = 0; k < dn; k++)
+ {
+ if (x == 0)
+ dp[k] = sp[k];
+ else
+ dp[k] = sp[k] - sp[k-sn];
+ }
+ sp += sn;
+ dp += dn;
+ }
+ }
+
+ err = compress(cdata, &csize, udata, usize);
+ if (err != Z_OK)
+ {
+ fz_free(ctx, udata);
+ fz_free(ctx, cdata);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot compress image data");
+ }
+
+ big32(head+0, pixmap->w);
+ big32(head+4, pixmap->h);
+ head[8] = 8; /* depth */
+ head[9] = color;
+ head[10] = 0; /* compression */
+ head[11] = 0; /* filter */
+ head[12] = 0; /* interlace */
+
+ fz_write(out, pngsig, 8);
+ putchunk("IHDR", head, 13, out);
+ putchunk("IDAT", cdata, csize, out);
+ putchunk("IEND", head, 0, out);
+
+ fz_free(ctx, udata);
+ fz_free(ctx, cdata);
+}
+
+fz_buffer *
+fz_image_as_png(fz_context *ctx, fz_image *image, int w, int h)
+{
+ fz_pixmap *pix = fz_image_get_pixmap(ctx, image, image->w, image->h);
+ fz_buffer *buf = NULL;
+ fz_output *out;
+
+ fz_var(buf);
+ fz_var(out);
+
+ fz_try(ctx)
+ {
+ if (pix->colorspace != fz_device_gray(ctx) || pix->colorspace != fz_device_rgb(ctx))
+ {
+ fz_pixmap *pix2 = fz_new_pixmap(ctx, fz_device_rgb(ctx), pix->w, pix->h);
+ fz_convert_pixmap(ctx, pix2, pix);
+ fz_drop_pixmap(ctx, pix);
+ pix = pix2;
+ }
+ buf = fz_new_buffer(ctx, 1024);
+ out = fz_new_output_with_buffer(ctx, buf);
+ fz_output_png(out, pix, 0);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fz_drop_pixmap(ctx, pix);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_buffer(ctx, buf);
+ fz_rethrow(ctx);
+ }
+ return buf;
+}
+
+unsigned int
+fz_pixmap_size(fz_context *ctx, fz_pixmap * pix)
+{
+ if (pix == NULL)
+ return 0;
+ return sizeof(*pix) + pix->n * pix->w * pix->h;
+}
+
+#ifdef ARCH_ARM
+static void
+fz_subsample_pixmap_ARM(unsigned char *ptr, int w, int h, int f, int factor,
+ int n, int fwd, int back, int back2, int fwd2,
+ int divX, int back4, int fwd4, int fwd3,
+ int divY, int back5, int divXY)
+__attribute__((naked));
+
+static void
+fz_subsample_pixmap_ARM(unsigned char *ptr, int w, int h, int f, int factor,
+ int n, int fwd, int back, int back2, int fwd2,
+ int divX, int back4, int fwd4, int fwd3,
+ int divY, int back5, int divXY)
+{
+ asm volatile(
+ ENTER_ARM
+ "stmfd r13!,{r1,r4-r11,r14} \n"
+ "@STACK:r1,<9>,factor,n,fwd,back,back2,fwd2,divX,back4,fwd4,fwd3,divY,back5,divXY\n"
+ "@ r0 = src = ptr \n"
+ "@ r1 = w \n"
+ "@ r2 = h \n"
+ "@ r3 = f \n"
+ "mov r9, r0 @ r9 = dst = ptr \n"
+ "ldr r6, [r13,#4*12] @ r6 = fwd \n"
+ "ldr r7, [r13,#4*13] @ r7 = back \n"
+ "subs r2, r2, r3 @ r2 = h -= f \n"
+ "blt 11f @ Skip if less than a full row \n"
+ "1: @ for (y = h; y > 0; y--) { \n"
+ "ldr r1, [r13] @ r1 = w \n"
+ "subs r1, r1, r3 @ r1 = w -= f \n"
+ "blt 6f @ Skip if less than a full col \n"
+ "ldr r4, [r13,#4*10] @ r4 = factor \n"
+ "ldr r8, [r13,#4*14] @ r8 = back2 \n"
+ "ldr r12,[r13,#4*15] @ r12= fwd2 \n"
+ "2: @ for (x = w; x > 0; x--) { \n"
+ "ldr r5, [r13,#4*11] @ for (nn = n; nn > 0; n--) { \n"
+ "3: @ \n"
+ "mov r14,#0 @ r14= v = 0 \n"
+ "sub r5, r5, r3, LSL #8 @ for (xx = f; xx > 0; x--) { \n"
+ "4: @ \n"
+ "add r5, r5, r3, LSL #16 @ for (yy = f; yy > 0; y--) { \n"
+ "5: @ \n"
+ "ldrb r11,[r0], r6 @ r11= *src src += fwd \n"
+ "subs r5, r5, #1<<16 @ xx-- \n"
+ "add r14,r14,r11 @ v += r11 \n"
+ "bgt 5b @ } \n"
+ "sub r0, r0, r7 @ src -= back \n"
+ "adds r5, r5, #1<<8 @ yy-- \n"
+ "blt 4b @ } \n"
+ "mov r14,r14,LSR r4 @ r14 = v >>= factor \n"
+ "strb r14,[r9], #1 @ *d++ = r14 \n"
+ "sub r0, r0, r8 @ s -= back2 \n"
+ "subs r5, r5, #1 @ n-- \n"
+ "bgt 3b @ } \n"
+ "add r0, r0, r12 @ s += fwd2 \n"
+ "subs r1, r1, r3 @ x -= f \n"
+ "bge 2b @ } \n"
+ "6: @ Less than a full column left \n"
+ "adds r1, r1, r3 @ x += f \n"
+ "beq 11f @ if (x == 0) next row \n"
+ "@ r0 = src \n"
+ "@ r1 = x \n"
+ "@ r2 = y \n"
+ "@ r3 = f \n"
+ "@ r4 = factor \n"
+ "@ r6 = fwd \n"
+ "@ r7 = back \n"
+ "@STACK:r1,<9>,factor,n,fwd,back,back2,fwd2,divX,back4,fwd4,fwd3,divY,back5,divXY\n"
+ "ldr r5, [r13,#4*11] @ for (nn = n; nn > 0; n--) { \n"
+ "ldr r4, [r13,#4*16] @ r4 = divX \n"
+ "ldr r8, [r13,#4*17] @ r8 = back4 \n"
+ "ldr r12,[r13,#4*18] @ r12= fwd4 \n"
+ "8: @ \n"
+ "mov r14,#0 @ r14= v = 0 \n"
+ "sub r5, r5, r1, LSL #8 @ for (xx = x; xx > 0; x--) { \n"
+ "9: @ \n"
+ "add r5, r5, r3, LSL #16 @ for (yy = f; yy > 0; y--) { \n"
+ "10: @ \n"
+ "ldrb r11,[r0], r6 @ r11= *src src += fwd \n"
+ "subs r5, r5, #1<<16 @ xx-- \n"
+ "add r14,r14,r11 @ v += r11 \n"
+ "bgt 10b @ } \n"
+ "sub r0, r0, r7 @ src -= back \n"
+ "adds r5, r5, #1<<8 @ yy-- \n"
+ "blt 9b @ } \n"
+ "mul r14,r4, r14 @ r14= v *= divX \n"
+ "mov r14,r14,LSR #16 @ r14= v >>= 16 \n"
+ "strb r14,[r9], #1 @ *d++ = r14 \n"
+ "sub r0, r0, r8 @ s -= back4 \n"
+ "subs r5, r5, #1 @ n-- \n"
+ "bgt 8b @ } \n"
+ "add r0, r0, r12 @ s += fwd4 \n"
+ "11: @ \n"
+ "ldr r14,[r13,#4*19] @ r14 = fwd3 \n"
+ "subs r2, r2, r3 @ h -= f \n"
+ "add r0, r0, r14 @ s += fwd3 \n"
+ "bge 1b @ } \n"
+ "adds r2, r2, r3 @ h += f \n"
+ "beq 21f @ if no stray row, end \n"
+ "@ So doing one last (partial) row \n"
+ "@STACK:r1,<9>,factor,n,fwd,back,back2,fwd2,divX,back4,fwd4,fwd3,divY,back5,divXY\n"
+ "@ r0 = src = ptr \n"
+ "@ r1 = w \n"
+ "@ r2 = h \n"
+ "@ r3 = f \n"
+ "@ r4 = factor \n"
+ "@ r5 = n \n"
+ "@ r6 = fwd \n"
+ "12: @ for (y = h; y > 0; y--) { \n"
+ "ldr r1, [r13] @ r1 = w \n"
+ "ldr r7, [r13,#4*21] @ r7 = back5 \n"
+ "ldr r8, [r13,#4*14] @ r8 = back2 \n"
+ "subs r1, r1, r3 @ r1 = w -= f \n"
+ "blt 17f @ Skip if less than a full col \n"
+ "ldr r4, [r13,#4*20] @ r4 = divY \n"
+ "ldr r12,[r13,#4*15] @ r12= fwd2 \n"
+ "13: @ for (x = w; x > 0; x--) { \n"
+ "ldr r5, [r13,#4*11] @ for (nn = n; nn > 0; n--) { \n"
+ "14: @ \n"
+ "mov r14,#0 @ r14= v = 0 \n"
+ "sub r5, r5, r3, LSL #8 @ for (xx = f; xx > 0; x--) { \n"
+ "15: @ \n"
+ "add r5, r5, r2, LSL #16 @ for (yy = y; yy > 0; y--) { \n"
+ "16: @ \n"
+ "ldrb r11,[r0], r6 @ r11= *src src += fwd \n"
+ "subs r5, r5, #1<<16 @ xx-- \n"
+ "add r14,r14,r11 @ v += r11 \n"
+ "bgt 16b @ } \n"
+ "sub r0, r0, r7 @ src -= back5 \n"
+ "adds r5, r5, #1<<8 @ yy-- \n"
+ "blt 15b @ } \n"
+ "mul r14,r4, r14 @ r14 = x *= divY \n"
+ "mov r14,r14,LSR #16 @ r14 = v >>= 16 \n"
+ "strb r14,[r9], #1 @ *d++ = r14 \n"
+ "sub r0, r0, r8 @ s -= back2 \n"
+ "subs r5, r5, #1 @ n-- \n"
+ "bgt 14b @ } \n"
+ "add r0, r0, r12 @ s += fwd2 \n"
+ "subs r1, r1, r3 @ x -= f \n"
+ "bge 13b @ } \n"
+ "17: @ Less than a full column left \n"
+ "adds r1, r1, r3 @ x += f \n"
+ "beq 21f @ if (x == 0) end \n"
+ "@ r0 = src \n"
+ "@ r1 = x \n"
+ "@ r2 = y \n"
+ "@ r3 = f \n"
+ "@ r4 = factor \n"
+ "@ r6 = fwd \n"
+ "@ r7 = back5 \n"
+ "@ r8 = back2 \n"
+ "@STACK:r1,<9>,factor,n,fwd,back,back2,fwd2,divX,back4,fwd4,fwd3,divY,back5,divXY\n"
+ "ldr r4, [r13,#4*22] @ r4 = divXY \n"
+ "ldr r5, [r13,#4*11] @ for (nn = n; nn > 0; n--) { \n"
+ "18: @ \n"
+ "mov r14,#0 @ r14= v = 0 \n"
+ "sub r5, r5, r1, LSL #8 @ for (xx = x; xx > 0; x--) { \n"
+ "19: @ \n"
+ "add r5, r5, r2, LSL #16 @ for (yy = y; yy > 0; y--) { \n"
+ "20: @ \n"
+ "ldrb r11,[r0],r6 @ r11= *src src += fwd \n"
+ "subs r5, r5, #1<<16 @ xx-- \n"
+ "add r14,r14,r11 @ v += r11 \n"
+ "bgt 20b @ } \n"
+ "sub r0, r0, r7 @ src -= back5 \n"
+ "adds r5, r5, #1<<8 @ yy-- \n"
+ "blt 19b @ } \n"
+ "mul r14,r4, r14 @ r14= v *= divX \n"
+ "mov r14,r14,LSR #16 @ r14= v >>= 16 \n"
+ "strb r14,[r9], #1 @ *d++ = r14 \n"
+ "sub r0, r0, r8 @ s -= back2 \n"
+ "subs r5, r5, #1 @ n-- \n"
+ "bgt 18b @ } \n"
+ "21: @ \n"
+ "ldmfd r13!,{r1,r4-r11,PC} @ pop, return to thumb \n"
+ ENTER_THUMB
+ );
+}
+
+#endif
+
+void
+fz_subsample_pixmap(fz_context *ctx, fz_pixmap *tile, int factor)
+{
+ int dst_w, dst_h, w, h, fwd, fwd2, fwd3, back, back2, x, y, n, xx, yy, nn, f;
+ unsigned char *s, *d;
+
+ if (!tile)
+ return;
+ s = d = tile->samples;
+ f = 1<<factor;
+ w = tile->w;
+ h = tile->h;
+ n = tile->n;
+ dst_w = (w + f-1)>>factor;
+ dst_h = (h + f-1)>>factor;
+ fwd = w*n;
+ back = f*fwd-n;
+ back2 = f*n-1;
+ fwd2 = (f-1)*n;
+ fwd3 = (f-1)*fwd;
+ factor *= 2;
+#ifdef ARCH_ARM
+ {
+ int strayX = w%f;
+ int divX = (strayX ? 65536/(strayX*f) : 0);
+ int fwd4 = (strayX-1) * n;
+ int back4 = strayX*n-1;
+ int strayY = h%f;
+ int divY = (strayY ? 65536/(strayY*f) : 0);
+ int back5 = fwd * strayY - n;
+ int divXY = (strayY*strayX ? 65536/(strayX*strayY) : 0);
+ fz_subsample_pixmap_ARM(s, w, h, f, factor, n, fwd, back,
+ back2, fwd2, divX, back4, fwd4, fwd3,
+ divY, back5, divXY);
+ }
+#else
+ for (y = h - f; y >= 0; y -= f)
+ {
+ for (x = w - f; x >= 0; x -= f)
+ {
+ for (nn = n; nn > 0; nn--)
+ {
+ int v = 0;
+ for (xx = f; xx > 0; xx--)
+ {
+ for (yy = f; yy > 0; yy--)
+ {
+ v += *s;
+ s += fwd;
+ }
+ s -= back;
+ }
+ *d++ = v >> factor;
+ s -= back2;
+ }
+ s += fwd2;
+ }
+ /* Do any strays */
+ x += f;
+ if (x > 0)
+ {
+ int div = x * f;
+ int fwd4 = (x-1) * n;
+ int back4 = x*n-1;
+ for (nn = n; nn > 0; nn--)
+ {
+ int v = 0;
+ for (xx = x; xx > 0; xx--)
+ {
+ for (yy = f; yy > 0; yy--)
+ {
+ v += *s;
+ s += fwd;
+ }
+ s -= back;
+ }
+ *d++ = v / div;
+ s -= back4;
+ }
+ s += fwd4;
+ }
+ s += fwd3;
+ }
+ /* Do any stray line */
+ y += f;
+ if (y > 0)
+ {
+ int div = y * f;
+ int back5 = fwd * y - n;
+ for (x = w - f; x >= 0; x -= f)
+ {
+ for (nn = n; nn > 0; nn--)
+ {
+ int v = 0;
+ for (xx = f; xx > 0; xx--)
+ {
+ for (yy = y; yy > 0; yy--)
+ {
+ v += *s;
+ s += fwd;
+ }
+ s -= back5;
+ }
+ *d++ = v / div;
+ s -= back2;
+ }
+ s += fwd2;
+ }
+ /* Do any stray at the end of the stray line */
+ x += f;
+ if (x > 0)
+ {
+ div = x * y;
+ for (nn = n; nn > 0; nn--)
+ {
+ int v = 0;
+ for (xx = x; xx > 0; xx--)
+ {
+ for (yy = y; yy > 0; yy--)
+ {
+ v += *s;
+ s += fwd;
+ }
+ s -= back5;
+ }
+ *d++ = v / div;
+ s -= back2;
+ }
+ }
+ }
+#endif
+ tile->w = dst_w;
+ tile->h = dst_h;
+ tile->samples = fz_resize_array(ctx, tile->samples, dst_w * n, dst_h);
+}
+
+void
+fz_pixmap_set_resolution(fz_pixmap *pix, int res)
+{
+ pix->xres = res;
+ pix->yres = res;
+}
+
+void
+fz_md5_pixmap(fz_pixmap *pix, unsigned char digest[16])
+{
+ fz_md5 md5;
+
+ fz_md5_init(&md5);
+ if (pix)
+ fz_md5_update(&md5, pix->samples, pix->w * pix->h * pix->n);
+ fz_md5_final(&md5, digest);
+}
diff --git a/source/fitz/shade.c b/source/fitz/shade.c
new file mode 100644
index 00000000..b13dc215
--- /dev/null
+++ b/source/fitz/shade.c
@@ -0,0 +1,1096 @@
+#include "mupdf/fitz.h"
+
+#define SWAP(a,b) {fz_vertex *t = (a); (a) = (b); (b) = t;}
+
+static void
+paint_tri(fz_mesh_processor *painter, fz_vertex *v0, fz_vertex *v1, fz_vertex *v2)
+{
+ painter->process(painter->process_arg, v0, v1, v2);
+}
+
+static void
+paint_quad(fz_mesh_processor *painter, fz_vertex *v0, fz_vertex *v1, fz_vertex *v2, fz_vertex *v3)
+{
+ /* For a quad with corners (in clockwise or anticlockwise order) are
+ * v0, v1, v2, v3. We can choose to split in in various different ways.
+ * Arbitrarily we can pick v0, v1, v3 for the first triangle. We then
+ * have to choose between v1, v2, v3 or v3, v2, v1 (or their equivalent
+ * rotations) for the second triangle.
+ *
+ * v1, v2, v3 has the property that both triangles share the same
+ * winding (useful if we were ever doing simple back face culling).
+ *
+ * v3, v2, v1 has the property that all the 'shared' edges (both
+ * within this quad, and with adjacent quads) are walked in the same
+ * direction every time. This can be useful in that depending on the
+ * implementation/rounding etc walking from A -> B can hit different
+ * pixels than walking from B->A.
+ *
+ * In the event neither of these things matter at the moment, as all
+ * the process functions where it matters order the edges from top to
+ * bottom before walking them.
+ */
+ painter->process(painter->process_arg, v0, v1, v3);
+ painter->process(painter->process_arg, v3, v2, v1);
+}
+
+static void
+fz_process_mesh_type1(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ float *p = shade->u.f.fn_vals;
+ int xdivs = shade->u.f.xdivs;
+ int ydivs = shade->u.f.ydivs;
+ float x0 = shade->u.f.domain[0][0];
+ float y0 = shade->u.f.domain[0][1];
+ float x1 = shade->u.f.domain[1][0];
+ float y1 = shade->u.f.domain[1][1];
+ int xx, yy;
+ float y, yn, x;
+ fz_vertex vs[2][2];
+ fz_vertex *v = vs[0];
+ fz_vertex *vn = vs[1];
+ int n = shade->colorspace->n;
+ fz_matrix local_ctm;
+
+ fz_concat(&local_ctm, &shade->u.f.matrix, ctm);
+
+ y = y0;
+ for (yy = 0; yy < ydivs; yy++)
+ {
+ yn = y0 + (y1 - y0) * (yy + 1) / ydivs;
+
+ x = x0;
+ v[0].p.x = x; v[0].p.y = y;
+ fz_transform_point(&v[0].p, &local_ctm);
+ memcpy(v[0].c, p, n*sizeof(float));
+ p += n;
+ v[1].p.x = x; v[1].p.y = yn;
+ fz_transform_point(&v[1].p, &local_ctm);
+ memcpy(v[1].c, p + xdivs*n, n*sizeof(float));
+ for (xx = 0; xx < xdivs; xx++)
+ {
+ x = x0 + (x1 - x0) * (xx + 1) / xdivs;
+
+ vn[0].p.x = x; vn[0].p.y = y;
+ fz_transform_point(&vn[0].p, &local_ctm);
+ memcpy(vn[0].c, p, n*sizeof(float));
+ p += n;
+ vn[1].p.x = x; vn[1].p.y = yn;
+ fz_transform_point(&vn[1].p, &local_ctm);
+ memcpy(vn[1].c, p + xdivs*n, n*sizeof(float));
+
+ paint_quad(painter, &v[0], &vn[0], &vn[1], &v[1]);
+ SWAP(v,vn);
+ }
+ y = yn;
+ }
+}
+
+/* FIXME: Nasty */
+#define HUGENUM 32000 /* how far to extend linear/radial shadings */
+
+static fz_point
+fz_point_on_circle(fz_point p, float r, float theta)
+{
+ p.x = p.x + cosf(theta) * r;
+ p.y = p.y + sinf(theta) * r;
+
+ return p;
+}
+
+static void
+fz_process_mesh_type2(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ fz_point p0, p1, dir;
+ fz_vertex v0, v1, v2, v3;
+ fz_vertex e0, e1;
+ float theta;
+
+ p0.x = shade->u.l_or_r.coords[0][0];
+ p0.y = shade->u.l_or_r.coords[0][1];
+ p1.x = shade->u.l_or_r.coords[1][0];
+ p1.y = shade->u.l_or_r.coords[1][1];
+ dir.x = p0.y - p1.y;
+ dir.y = p1.x - p0.x;
+ fz_transform_point(&p0, ctm);
+ fz_transform_point(&p1, ctm);
+ fz_transform_vector(&dir, ctm);
+ theta = atan2f(dir.y, dir.x);
+
+ v0.p = fz_point_on_circle(p0, HUGENUM, theta);
+ v1.p = fz_point_on_circle(p1, HUGENUM, theta);
+ v2.p = fz_point_on_circle(p0, -HUGENUM, theta);
+ v3.p = fz_point_on_circle(p1, -HUGENUM, theta);
+
+ v0.c[0] = 0;
+ v1.c[0] = 1;
+ v2.c[0] = 0;
+ v3.c[0] = 1;
+
+ paint_quad(painter, &v0, &v2, &v3, &v1);
+
+ if (shade->u.l_or_r.extend[0])
+ {
+ e0.p.x = v0.p.x - (p1.x - p0.x) * HUGENUM;
+ e0.p.y = v0.p.y - (p1.y - p0.y) * HUGENUM;
+
+ e1.p.x = v2.p.x - (p1.x - p0.x) * HUGENUM;
+ e1.p.y = v2.p.y - (p1.y - p0.y) * HUGENUM;
+
+ e0.c[0] = 0;
+ e1.c[0] = 0;
+ v0.c[0] = 0;
+ v2.c[0] = 0;
+
+ paint_quad(painter, &e0, &v0, &v2, &e1);
+ }
+
+ if (shade->u.l_or_r.extend[1])
+ {
+ e0.p.x = v1.p.x + (p1.x - p0.x) * HUGENUM;
+ e0.p.y = v1.p.y + (p1.y - p0.y) * HUGENUM;
+
+ e1.p.x = v3.p.x + (p1.x - p0.x) * HUGENUM;
+ e1.p.y = v3.p.y + (p1.y - p0.y) * HUGENUM;
+
+ e0.c[0] = 1;
+ e1.c[0] = 1;
+ v1.c[0] = 1;
+ v3.c[0] = 1;
+
+ paint_quad(painter, &e0, &v1, &v3, &e1);
+ }
+}
+
+/* FIXME: Nasty */
+#define RADSEGS 32 /* how many segments to generate for radial meshes */
+
+static void
+fz_paint_annulus(const fz_matrix *ctm,
+ fz_point p0, float r0, float c0,
+ fz_point p1, float r1, float c1,
+ fz_mesh_processor *painter)
+{
+ fz_vertex t0, t1, t2, t3, b0, b1, b2, b3;
+ float theta, step;
+ int i;
+
+ theta = atan2f(p1.y - p0.y, p1.x - p0.x);
+ step = (float)M_PI * 2 / RADSEGS;
+
+ for (i = 0; i < RADSEGS / 2; i++)
+ {
+ t0.p = fz_point_on_circle(p0, r0, theta + i * step);
+ t1.p = fz_point_on_circle(p0, r0, theta + i * step + step);
+ t2.p = fz_point_on_circle(p1, r1, theta + i * step);
+ t3.p = fz_point_on_circle(p1, r1, theta + i * step + step);
+ b0.p = fz_point_on_circle(p0, r0, theta - i * step);
+ b1.p = fz_point_on_circle(p0, r0, theta - i * step - step);
+ b2.p = fz_point_on_circle(p1, r1, theta - i * step);
+ b3.p = fz_point_on_circle(p1, r1, theta - i * step - step);
+
+ fz_transform_point(&t0.p, ctm);
+ fz_transform_point(&t1.p, ctm);
+ fz_transform_point(&t2.p, ctm);
+ fz_transform_point(&t3.p, ctm);
+ fz_transform_point(&b0.p, ctm);
+ fz_transform_point(&b1.p, ctm);
+ fz_transform_point(&b2.p, ctm);
+ fz_transform_point(&b3.p, ctm);
+
+ t0.c[0] = c0;
+ t1.c[0] = c0;
+ t2.c[0] = c1;
+ t3.c[0] = c1;
+ b0.c[0] = c0;
+ b1.c[0] = c0;
+ b2.c[0] = c1;
+ b3.c[0] = c1;
+
+ paint_quad(painter, &t0, &t2, &t3, &t1);
+ paint_quad(painter, &b0, &b2, &b3, &b1);
+ }
+}
+
+static void
+fz_process_mesh_type3(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ fz_point p0, p1;
+ float r0, r1;
+ fz_point e;
+ float er, rs;
+
+ p0.x = shade->u.l_or_r.coords[0][0];
+ p0.y = shade->u.l_or_r.coords[0][1];
+ r0 = shade->u.l_or_r.coords[0][2];
+
+ p1.x = shade->u.l_or_r.coords[1][0];
+ p1.y = shade->u.l_or_r.coords[1][1];
+ r1 = shade->u.l_or_r.coords[1][2];
+
+ if (shade->u.l_or_r.extend[0])
+ {
+ if (r0 < r1)
+ rs = r0 / (r0 - r1);
+ else
+ rs = -HUGENUM;
+
+ e.x = p0.x + (p1.x - p0.x) * rs;
+ e.y = p0.y + (p1.y - p0.y) * rs;
+ er = r0 + (r1 - r0) * rs;
+
+ fz_paint_annulus(ctm, e, er, 0, p0, r0, 0, painter);
+ }
+
+ fz_paint_annulus(ctm, p0, r0, 0, p1, r1, 1, painter);
+
+ if (shade->u.l_or_r.extend[1])
+ {
+ if (r0 > r1)
+ rs = r1 / (r1 - r0);
+ else
+ rs = -HUGENUM;
+
+ e.x = p1.x + (p0.x - p1.x) * rs;
+ e.y = p1.y + (p0.y - p1.y) * rs;
+ er = r1 + (r0 - r1) * rs;
+
+ fz_paint_annulus(ctm, p1, r1, 1, e, er, 1, painter);
+ }
+}
+
+static inline float read_sample(fz_stream *stream, int bits, float min, float max)
+{
+ /* we use pow(2,x) because (1<<x) would overflow the math on 32-bit samples */
+ float bitscale = 1 / (powf(2, bits) - 1);
+ return min + fz_read_bits(stream, bits) * (max - min) * bitscale;
+}
+
+static void
+fz_process_mesh_type4(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer);
+ fz_vertex v[4];
+ fz_vertex *va = &v[0];
+ fz_vertex *vb = &v[1];
+ fz_vertex *vc = &v[2];
+ fz_vertex *vd = &v[3];
+ int flag, i, ncomp = painter->ncomp;
+ int bpflag = shade->u.m.bpflag;
+ int bpcoord = shade->u.m.bpcoord;
+ int bpcomp = shade->u.m.bpcomp;
+ float x0 = shade->u.m.x0;
+ float x1 = shade->u.m.x1;
+ float y0 = shade->u.m.y0;
+ float y1 = shade->u.m.y1;
+ float *c0 = shade->u.m.c0;
+ float *c1 = shade->u.m.c1;
+
+ fz_try(ctx)
+ {
+ while (!fz_is_eof_bits(stream))
+ {
+ flag = fz_read_bits(stream, bpflag);
+ vd->p.x = read_sample(stream, bpcoord, x0, x1);
+ vd->p.y = read_sample(stream, bpcoord, y0, y1);
+ fz_transform_point(&vd->p, ctm);
+ for (i = 0; i < ncomp; i++)
+ vd->c[i] = read_sample(stream, bpcomp, c0[i], c1[i]);
+
+ switch (flag)
+ {
+ case 0: /* start new triangle */
+ SWAP(va, vd);
+
+ fz_read_bits(stream, bpflag);
+ vb->p.x = read_sample(stream, bpcoord, x0, x1);
+ vb->p.y = read_sample(stream, bpcoord, y0, y1);
+ fz_transform_point(&vb->p, ctm);
+ for (i = 0; i < ncomp; i++)
+ vb->c[i] = read_sample(stream, bpcomp, c0[i], c1[i]);
+
+ fz_read_bits(stream, bpflag);
+ vc->p.x = read_sample(stream, bpcoord, x0, x1);
+ vc->p.y = read_sample(stream, bpcoord, y0, y1);
+ fz_transform_point(&vc->p, ctm);
+ for (i = 0; i < ncomp; i++)
+ vc->c[i] = read_sample(stream, bpcomp, c0[i], c1[i]);
+
+ paint_tri(painter, va, vb, vc);
+ break;
+
+ case 1: /* Vb, Vc, Vd */
+ SWAP(va, vb);
+ SWAP(vb, vc);
+ SWAP(vc, vd);
+ paint_tri(painter, va, vb, vc);
+ break;
+
+ case 2: /* Va, Vc, Vd */
+ SWAP(vb, vc);
+ SWAP(vc, vd);
+ paint_tri(painter, va, vb, vc);
+ break;
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stream);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+fz_process_mesh_type5(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer);
+ fz_vertex *buf = NULL;
+ fz_vertex *ref = NULL;
+ int first;
+ int ncomp = painter->ncomp;
+ int i, k;
+ int vprow = shade->u.m.vprow;
+ int bpcoord = shade->u.m.bpcoord;
+ int bpcomp = shade->u.m.bpcomp;
+ float x0 = shade->u.m.x0;
+ float x1 = shade->u.m.x1;
+ float y0 = shade->u.m.y0;
+ float y1 = shade->u.m.y1;
+ float *c0 = shade->u.m.c0;
+ float *c1 = shade->u.m.c1;
+
+ fz_var(buf);
+ fz_var(ref);
+
+ fz_try(ctx)
+ {
+ ref = fz_malloc_array(ctx, vprow, sizeof(fz_vertex));
+ buf = fz_malloc_array(ctx, vprow, sizeof(fz_vertex));
+ first = 1;
+
+ while (!fz_is_eof_bits(stream))
+ {
+ for (i = 0; i < vprow; i++)
+ {
+ buf[i].p.x = read_sample(stream, bpcoord, x0, x1);
+ buf[i].p.y = read_sample(stream, bpcoord, y0, y1);
+ fz_transform_point(&buf[i].p, ctm);
+ for (k = 0; k < ncomp; k++)
+ buf[i].c[k] = read_sample(stream, bpcomp, c0[k], c1[k]);
+ }
+
+ if (!first)
+ for (i = 0; i < vprow - 1; i++)
+ paint_quad(painter, &ref[i], &ref[i+1], &buf[i+1], &buf[i]);
+
+ SWAP(ref,buf);
+ first = 0;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, ref);
+ fz_free(ctx, buf);
+ fz_close(stream);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+/* Subdivide and tessellate tensor-patches */
+
+typedef struct tensor_patch_s tensor_patch;
+
+struct tensor_patch_s
+{
+ fz_point pole[4][4];
+ float color[4][FZ_MAX_COLORS];
+};
+
+static void
+triangulate_patch(fz_mesh_processor *painter, tensor_patch p)
+{
+ fz_vertex v0, v1, v2, v3;
+ int col_len = painter->ncomp * sizeof(v0.c[0]);
+
+ v0.p = p.pole[0][0];
+ memcpy(v0.c, p.color[0], col_len);
+
+ v1.p = p.pole[0][3];
+ memcpy(v1.c, p.color[1], col_len);
+
+ v2.p = p.pole[3][3];
+ memcpy(v2.c, p.color[2], col_len);
+
+ v3.p = p.pole[3][0];
+ memcpy(v3.c, p.color[3], col_len);
+
+ paint_quad(painter, &v0, &v1, &v2, &v3);
+}
+
+static inline void midcolor(float *c, float *c1, float *c2, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ c[i] = (c1[i] + c2[i]) * 0.5f;
+}
+
+static void
+split_curve(fz_point *pole, fz_point *q0, fz_point *q1, int polestep)
+{
+ /*
+ split bezier curve given by control points pole[0]..pole[3]
+ using de casteljau algo at midpoint and build two new
+ bezier curves q0[0]..q0[3] and q1[0]..q1[3]. all indices
+ should be multiplies by polestep == 1 for vertical bezier
+ curves in patch and == 4 for horizontal bezier curves due
+ to C's multi-dimensional matrix memory layout.
+ */
+
+ float x12 = (pole[1 * polestep].x + pole[2 * polestep].x) * 0.5f;
+ float y12 = (pole[1 * polestep].y + pole[2 * polestep].y) * 0.5f;
+
+ q0[1 * polestep].x = (pole[0 * polestep].x + pole[1 * polestep].x) * 0.5f;
+ q0[1 * polestep].y = (pole[0 * polestep].y + pole[1 * polestep].y) * 0.5f;
+ q1[2 * polestep].x = (pole[2 * polestep].x + pole[3 * polestep].x) * 0.5f;
+ q1[2 * polestep].y = (pole[2 * polestep].y + pole[3 * polestep].y) * 0.5f;
+
+ q0[2 * polestep].x = (q0[1 * polestep].x + x12) * 0.5f;
+ q0[2 * polestep].y = (q0[1 * polestep].y + y12) * 0.5f;
+ q1[1 * polestep].x = (x12 + q1[2 * polestep].x) * 0.5f;
+ q1[1 * polestep].y = (y12 + q1[2 * polestep].y) * 0.5f;
+
+ q0[3 * polestep].x = (q0[2 * polestep].x + q1[1 * polestep].x) * 0.5f;
+ q0[3 * polestep].y = (q0[2 * polestep].y + q1[1 * polestep].y) * 0.5f;
+ q1[0 * polestep].x = (q0[2 * polestep].x + q1[1 * polestep].x) * 0.5f;
+ q1[0 * polestep].y = (q0[2 * polestep].y + q1[1 * polestep].y) * 0.5f;
+
+ q0[0 * polestep].x = pole[0 * polestep].x;
+ q0[0 * polestep].y = pole[0 * polestep].y;
+ q1[3 * polestep].x = pole[3 * polestep].x;
+ q1[3 * polestep].y = pole[3 * polestep].y;
+}
+
+static void
+split_stripe(tensor_patch *p, tensor_patch *s0, tensor_patch *s1, int n)
+{
+ /*
+ split all horizontal bezier curves in patch,
+ creating two new patches with half the width.
+ */
+ split_curve(&p->pole[0][0], &s0->pole[0][0], &s1->pole[0][0], 4);
+ split_curve(&p->pole[0][1], &s0->pole[0][1], &s1->pole[0][1], 4);
+ split_curve(&p->pole[0][2], &s0->pole[0][2], &s1->pole[0][2], 4);
+ split_curve(&p->pole[0][3], &s0->pole[0][3], &s1->pole[0][3], 4);
+
+ /* interpolate the colors for the two new patches. */
+ memcpy(s0->color[0], p->color[0], n * sizeof(s0->color[0][0]));
+ memcpy(s0->color[1], p->color[1], n * sizeof(s0->color[1][0]));
+ midcolor(s0->color[2], p->color[1], p->color[2], n);
+ midcolor(s0->color[3], p->color[0], p->color[3], n);
+
+ memcpy(s1->color[0], s0->color[3], n * sizeof(s1->color[0][0]));
+ memcpy(s1->color[1], s0->color[2], n * sizeof(s1->color[1][0]));
+ memcpy(s1->color[2], p->color[2], n * sizeof(s1->color[2][0]));
+ memcpy(s1->color[3], p->color[3], n * sizeof(s1->color[3][0]));
+}
+
+static void
+draw_stripe(fz_mesh_processor *painter, tensor_patch *p, int depth)
+{
+ tensor_patch s0, s1;
+
+ /* split patch into two half-height patches */
+ split_stripe(p, &s0, &s1, painter->ncomp);
+
+ depth--;
+ if (depth == 0)
+ {
+ /* if no more subdividing, draw two new patches... */
+ triangulate_patch(painter, s1);
+ triangulate_patch(painter, s0);
+ }
+ else
+ {
+ /* ...otherwise, continue subdividing. */
+ draw_stripe(painter, &s1, depth);
+ draw_stripe(painter, &s0, depth);
+ }
+}
+
+static void
+split_patch(tensor_patch *p, tensor_patch *s0, tensor_patch *s1, int n)
+{
+ /*
+ split all vertical bezier curves in patch,
+ creating two new patches with half the height.
+ */
+ split_curve(p->pole[0], s0->pole[0], s1->pole[0], 1);
+ split_curve(p->pole[1], s0->pole[1], s1->pole[1], 1);
+ split_curve(p->pole[2], s0->pole[2], s1->pole[2], 1);
+ split_curve(p->pole[3], s0->pole[3], s1->pole[3], 1);
+
+ /* interpolate the colors for the two new patches. */
+ memcpy(s0->color[0], p->color[0], n * sizeof(s0->color[0][0]));
+ midcolor(s0->color[1], p->color[0], p->color[1], n);
+ midcolor(s0->color[2], p->color[2], p->color[3], n);
+ memcpy(s0->color[3], p->color[3], n * sizeof(s0->color[3][0]));
+
+ memcpy(s1->color[0], s0->color[1], n * sizeof(s1->color[0][0]));
+ memcpy(s1->color[1], p->color[1], n * sizeof(s1->color[1][0]));
+ memcpy(s1->color[2], p->color[2], n * sizeof(s1->color[2][0]));
+ memcpy(s1->color[3], s0->color[2], n * sizeof(s1->color[3][0]));
+}
+
+static void
+draw_patch(fz_mesh_processor *painter, tensor_patch *p, int depth, int origdepth)
+{
+ tensor_patch s0, s1;
+
+ /* split patch into two half-width patches */
+ split_patch(p, &s0, &s1, painter->ncomp);
+
+ depth--;
+ if (depth == 0)
+ {
+ /* if no more subdividing, draw two new patches... */
+ draw_stripe(painter, &s0, origdepth);
+ draw_stripe(painter, &s1, origdepth);
+ }
+ else
+ {
+ /* ...otherwise, continue subdividing. */
+ draw_patch(painter, &s0, depth, origdepth);
+ draw_patch(painter, &s1, depth, origdepth);
+ }
+}
+
+static fz_point
+compute_tensor_interior(
+ fz_point a, fz_point b, fz_point c, fz_point d,
+ fz_point e, fz_point f, fz_point g, fz_point h)
+{
+ fz_point pt;
+
+ /* see equations at page 330 in pdf 1.7 */
+
+ pt.x = -4 * a.x;
+ pt.x += 6 * (b.x + c.x);
+ pt.x += -2 * (d.x + e.x);
+ pt.x += 3 * (f.x + g.x);
+ pt.x += -1 * h.x;
+ pt.x /= 9;
+
+ pt.y = -4 * a.y;
+ pt.y += 6 * (b.y + c.y);
+ pt.y += -2 * (d.y + e.y);
+ pt.y += 3 * (f.y + g.y);
+ pt.y += -1 * h.y;
+ pt.y /= 9;
+
+ return pt;
+}
+
+static void
+make_tensor_patch(tensor_patch *p, int type, fz_point *pt)
+{
+ if (type == 6)
+ {
+ /* see control point stream order at page 325 in pdf 1.7 */
+
+ p->pole[0][0] = pt[0];
+ p->pole[0][1] = pt[1];
+ p->pole[0][2] = pt[2];
+ p->pole[0][3] = pt[3];
+ p->pole[1][3] = pt[4];
+ p->pole[2][3] = pt[5];
+ p->pole[3][3] = pt[6];
+ p->pole[3][2] = pt[7];
+ p->pole[3][1] = pt[8];
+ p->pole[3][0] = pt[9];
+ p->pole[2][0] = pt[10];
+ p->pole[1][0] = pt[11];
+
+ /* see equations at page 330 in pdf 1.7 */
+
+ p->pole[1][1] = compute_tensor_interior(
+ p->pole[0][0], p->pole[0][1], p->pole[1][0], p->pole[0][3],
+ p->pole[3][0], p->pole[3][1], p->pole[1][3], p->pole[3][3]);
+
+ p->pole[1][2] = compute_tensor_interior(
+ p->pole[0][3], p->pole[0][2], p->pole[1][3], p->pole[0][0],
+ p->pole[3][3], p->pole[3][2], p->pole[1][0], p->pole[3][0]);
+
+ p->pole[2][1] = compute_tensor_interior(
+ p->pole[3][0], p->pole[3][1], p->pole[2][0], p->pole[3][3],
+ p->pole[0][0], p->pole[0][1], p->pole[2][3], p->pole[0][3]);
+
+ p->pole[2][2] = compute_tensor_interior(
+ p->pole[3][3], p->pole[3][2], p->pole[2][3], p->pole[3][0],
+ p->pole[0][3], p->pole[0][2], p->pole[2][0], p->pole[0][0]);
+ }
+ else if (type == 7)
+ {
+ /* see control point stream order at page 330 in pdf 1.7 */
+
+ p->pole[0][0] = pt[0];
+ p->pole[0][1] = pt[1];
+ p->pole[0][2] = pt[2];
+ p->pole[0][3] = pt[3];
+ p->pole[1][3] = pt[4];
+ p->pole[2][3] = pt[5];
+ p->pole[3][3] = pt[6];
+ p->pole[3][2] = pt[7];
+ p->pole[3][1] = pt[8];
+ p->pole[3][0] = pt[9];
+ p->pole[2][0] = pt[10];
+ p->pole[1][0] = pt[11];
+ p->pole[1][1] = pt[12];
+ p->pole[1][2] = pt[13];
+ p->pole[2][2] = pt[14];
+ p->pole[2][1] = pt[15];
+ }
+}
+
+/* FIXME: Nasty */
+#define SUBDIV 3 /* how many levels to subdivide patches */
+
+static void
+fz_process_mesh_type6(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer);
+ float color_storage[2][4][FZ_MAX_COLORS];
+ fz_point point_storage[2][12];
+ int store = 0;
+ int ncomp = painter->ncomp;
+ int i, k;
+ int bpflag = shade->u.m.bpflag;
+ int bpcoord = shade->u.m.bpcoord;
+ int bpcomp = shade->u.m.bpcomp;
+ float x0 = shade->u.m.x0;
+ float x1 = shade->u.m.x1;
+ float y0 = shade->u.m.y0;
+ float y1 = shade->u.m.y1;
+ float *c0 = shade->u.m.c0;
+ float *c1 = shade->u.m.c1;
+
+ fz_try(ctx)
+ {
+ float (*prevc)[FZ_MAX_COLORS] = NULL;
+ fz_point *prevp = NULL;
+ while (!fz_is_eof_bits(stream))
+ {
+ float (*c)[FZ_MAX_COLORS] = color_storage[store];
+ fz_point *v = point_storage[store];
+ int startcolor;
+ int startpt;
+ int flag;
+ tensor_patch patch;
+
+ flag = fz_read_bits(stream, bpflag);
+
+ if (flag == 0)
+ {
+ startpt = 0;
+ startcolor = 0;
+ }
+ else
+ {
+ startpt = 4;
+ startcolor = 2;
+ }
+
+ for (i = startpt; i < 12; i++)
+ {
+ v[i].x = read_sample(stream, bpcoord, x0, x1);
+ v[i].y = read_sample(stream, bpcoord, y0, y1);
+ fz_transform_point(&v[i], ctm);
+ }
+
+ for (i = startcolor; i < 4; i++)
+ {
+ for (k = 0; k < ncomp; k++)
+ c[i][k] = read_sample(stream, bpcomp, c0[k], c1[k]);
+ }
+
+ if (flag == 0)
+ {
+ }
+ else if (flag == 1 && prevc)
+ {
+ v[0] = prevp[3];
+ v[1] = prevp[4];
+ v[2] = prevp[5];
+ v[3] = prevp[6];
+ memcpy(c[0], prevc[1], ncomp * sizeof(float));
+ memcpy(c[1], prevc[2], ncomp * sizeof(float));
+ }
+ else if (flag == 2 && prevc)
+ {
+ v[0] = prevp[6];
+ v[1] = prevp[7];
+ v[2] = prevp[8];
+ v[3] = prevp[9];
+ memcpy(c[0], prevc[2], ncomp * sizeof(float));
+ memcpy(c[1], prevc[3], ncomp * sizeof(float));
+ }
+ else if (flag == 3 && prevc)
+ {
+ v[0] = prevp[ 9];
+ v[1] = prevp[10];
+ v[2] = prevp[11];
+ v[3] = prevp[ 0];
+ memcpy(c[0], prevc[3], ncomp * sizeof(float));
+ memcpy(c[1], prevc[0], ncomp * sizeof(float));
+ }
+ else
+ continue;
+
+ make_tensor_patch(&patch, 6, v);
+
+ for (i = 0; i < 4; i++)
+ memcpy(patch.color[i], c[i], ncomp * sizeof(float));
+
+ draw_patch(painter, &patch, SUBDIV, SUBDIV);
+
+ prevp = v;
+ prevc = c;
+ store ^= 1;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stream);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+fz_process_mesh_type7(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_mesh_processor *painter)
+{
+ fz_stream *stream = fz_open_compressed_buffer(ctx, shade->buffer);
+ int bpflag = shade->u.m.bpflag;
+ int bpcoord = shade->u.m.bpcoord;
+ int bpcomp = shade->u.m.bpcomp;
+ float x0 = shade->u.m.x0;
+ float x1 = shade->u.m.x1;
+ float y0 = shade->u.m.y0;
+ float y1 = shade->u.m.y1;
+ float *c0 = shade->u.m.c0;
+ float *c1 = shade->u.m.c1;
+ float color_storage[2][4][FZ_MAX_COLORS];
+ fz_point point_storage[2][16];
+ int store = 0;
+ int ncomp = painter->ncomp;
+ int i, k;
+ float (*prevc)[FZ_MAX_COLORS] = NULL;
+ fz_point (*prevp) = NULL;
+
+ fz_try(ctx)
+ {
+ while (!fz_is_eof_bits(stream))
+ {
+ float (*c)[FZ_MAX_COLORS] = color_storage[store];
+ fz_point *v = point_storage[store];
+ int startcolor;
+ int startpt;
+ int flag;
+ tensor_patch patch;
+
+ flag = fz_read_bits(stream, bpflag);
+
+ if (flag == 0)
+ {
+ startpt = 0;
+ startcolor = 0;
+ }
+ else
+ {
+ startpt = 4;
+ startcolor = 2;
+ }
+
+ for (i = startpt; i < 16; i++)
+ {
+ v[i].x = read_sample(stream, bpcoord, x0, x1);
+ v[i].y = read_sample(stream, bpcoord, y0, y1);
+ fz_transform_point(&v[i], ctm);
+ }
+
+ for (i = startcolor; i < 4; i++)
+ {
+ for (k = 0; k < ncomp; k++)
+ c[i][k] = read_sample(stream, bpcomp, c0[k], c1[k]);
+ }
+
+ if (flag == 0)
+ {
+ }
+ else if (flag == 1 && prevc)
+ {
+ v[0] = prevp[3];
+ v[1] = prevp[4];
+ v[2] = prevp[5];
+ v[3] = prevp[6];
+ memcpy(c[0], prevc[1], ncomp * sizeof(float));
+ memcpy(c[1], prevc[2], ncomp * sizeof(float));
+ }
+ else if (flag == 2 && prevc)
+ {
+ v[0] = prevp[6];
+ v[1] = prevp[7];
+ v[2] = prevp[8];
+ v[3] = prevp[9];
+ memcpy(c[0], prevc[2], ncomp * sizeof(float));
+ memcpy(c[1], prevc[3], ncomp * sizeof(float));
+ }
+ else if (flag == 3 && prevc)
+ {
+ v[0] = prevp[ 9];
+ v[1] = prevp[10];
+ v[2] = prevp[11];
+ v[3] = prevp[ 0];
+ memcpy(c[0], prevc[3], ncomp * sizeof(float));
+ memcpy(c[1], prevc[0], ncomp * sizeof(float));
+ }
+ else
+ continue; /* We have no patch! */
+
+ make_tensor_patch(&patch, 7, v);
+
+ for (i = 0; i < 4; i++)
+ memcpy(patch.color[i], c[i], ncomp * sizeof(float));
+
+ draw_patch(painter, &patch, SUBDIV, SUBDIV);
+
+ prevp = v;
+ prevc = c;
+ store ^= 1;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stream);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+fz_process_mesh(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm,
+ fz_mesh_process_fn *process, void *process_arg)
+{
+ fz_mesh_processor painter;
+
+ painter.ctx = ctx;
+ painter.shade = shade;
+ painter.process = process;
+ painter.process_arg = process_arg;
+ painter.ncomp = (shade->use_function > 0 ? 1 : shade->colorspace->n);
+
+ if (shade->type == FZ_FUNCTION_BASED)
+ fz_process_mesh_type1(ctx, shade, ctm, &painter);
+ else if (shade->type == FZ_LINEAR)
+ fz_process_mesh_type2(ctx, shade, ctm, &painter);
+ else if (shade->type == FZ_RADIAL)
+ fz_process_mesh_type3(ctx, shade, ctm, &painter);
+ else if (shade->type == FZ_MESH_TYPE4)
+ fz_process_mesh_type4(ctx, shade, ctm, &painter);
+ else if (shade->type == FZ_MESH_TYPE5)
+ fz_process_mesh_type5(ctx, shade, ctm, &painter);
+ else if (shade->type == FZ_MESH_TYPE6)
+ fz_process_mesh_type6(ctx, shade, ctm, &painter);
+ else if (shade->type == FZ_MESH_TYPE7)
+ fz_process_mesh_type7(ctx, shade, ctm, &painter);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Unexpected mesh type %d\n", shade->type);
+}
+
+static fz_rect *
+fz_bound_mesh_type1(fz_context *ctx, fz_shade *shade, fz_rect *bbox)
+{
+ bbox->x0 = shade->u.f.domain[0][0];
+ bbox->y0 = shade->u.f.domain[0][1];
+ bbox->x1 = shade->u.f.domain[1][0];
+ bbox->y1 = shade->u.f.domain[1][1];
+ return fz_transform_rect(bbox, &shade->u.f.matrix);
+}
+
+static fz_rect *
+fz_bound_mesh_type2(fz_context *ctx, fz_shade *shade, fz_rect *bbox)
+{
+ /* FIXME: If axis aligned and not extended, the bbox may only be
+ * infinite in one direction */
+ *bbox = fz_infinite_rect;
+ return bbox;
+}
+
+static fz_rect *
+fz_bound_mesh_type3(fz_context *ctx, fz_shade *shade, fz_rect *bbox)
+{
+ fz_point p0, p1;
+ float r0, r1;
+
+ r0 = shade->u.l_or_r.coords[0][2];
+ r1 = shade->u.l_or_r.coords[1][2];
+
+ if (shade->u.l_or_r.extend[0])
+ {
+ if (r0 >= r1)
+ {
+ *bbox = fz_infinite_rect;
+ return bbox;
+ }
+ }
+
+ if (shade->u.l_or_r.extend[1])
+ {
+ if (r0 <= r1)
+ {
+ *bbox = fz_infinite_rect;
+ return bbox;
+ }
+ }
+
+ p0.x = shade->u.l_or_r.coords[0][0];
+ p0.y = shade->u.l_or_r.coords[0][1];
+ p1.x = shade->u.l_or_r.coords[1][0];
+ p1.y = shade->u.l_or_r.coords[1][1];
+
+ bbox->x0 = p0.x - r0; bbox->y0 = p0.y - r0;
+ bbox->x1 = p0.x + r0; bbox->y1 = p0.x + r0;
+ if (bbox->x0 > p1.x - r1)
+ bbox->x0 = p1.x - r1;
+ if (bbox->x1 < p1.x + r1)
+ bbox->x1 = p1.x + r1;
+ if (bbox->y0 > p1.y - r1)
+ bbox->y0 = p1.y - r1;
+ if (bbox->y1 < p1.y + r1)
+ bbox->y1 = p1.y + r1;
+ return bbox;
+}
+
+static fz_rect *
+fz_bound_mesh_type4567(fz_context *ctx, fz_shade *shade, fz_rect *bbox)
+{
+ bbox->x0 = shade->u.m.x0;
+ bbox->y0 = shade->u.m.y0;
+ bbox->x1 = shade->u.m.x1;
+ bbox->y1 = shade->u.m.y1;
+ return bbox;
+}
+
+static fz_rect *
+fz_bound_mesh(fz_context *ctx, fz_shade *shade, fz_rect *bbox)
+{
+ if (shade->type == FZ_FUNCTION_BASED)
+ fz_bound_mesh_type1(ctx, shade, bbox);
+ else if (shade->type == FZ_LINEAR)
+ fz_bound_mesh_type2(ctx, shade, bbox);
+ else if (shade->type == FZ_RADIAL)
+ fz_bound_mesh_type3(ctx, shade, bbox);
+ else if (shade->type == FZ_MESH_TYPE4 ||
+ shade->type == FZ_MESH_TYPE5 ||
+ shade->type == FZ_MESH_TYPE6 ||
+ shade->type == FZ_MESH_TYPE7)
+ fz_bound_mesh_type4567(ctx, shade, bbox);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Unexpected mesh type %d\n", shade->type);
+
+ return bbox;
+}
+
+fz_shade *
+fz_keep_shade(fz_context *ctx, fz_shade *shade)
+{
+ return (fz_shade *)fz_keep_storable(ctx, &shade->storable);
+}
+
+void
+fz_free_shade_imp(fz_context *ctx, fz_storable *shade_)
+{
+ fz_shade *shade = (fz_shade *)shade_;
+
+ if (shade->colorspace)
+ fz_drop_colorspace(ctx, shade->colorspace);
+ if (shade->type == FZ_FUNCTION_BASED)
+ fz_free(ctx, shade->u.f.fn_vals);
+ fz_free_compressed_buffer(ctx, shade->buffer);
+ fz_free(ctx, shade);
+}
+
+void
+fz_drop_shade(fz_context *ctx, fz_shade *shade)
+{
+ fz_drop_storable(ctx, &shade->storable);
+}
+
+fz_rect *
+fz_bound_shade(fz_context *ctx, fz_shade *shade, const fz_matrix *ctm, fz_rect *s)
+{
+ fz_matrix local_ctm;
+ fz_rect rect;
+
+ fz_concat(&local_ctm, &shade->matrix, ctm);
+ *s = shade->bbox;
+ if (shade->type != FZ_LINEAR && shade->type != FZ_RADIAL)
+ {
+ fz_bound_mesh(ctx, shade, &rect);
+ fz_intersect_rect(s, &rect);
+ }
+ return fz_transform_rect(s, &local_ctm);
+}
+
+#ifndef NDEBUG
+void
+fz_print_shade(fz_context *ctx, FILE *out, fz_shade *shade)
+{
+ int i;
+
+ fprintf(out, "shading {\n");
+
+ switch (shade->type)
+ {
+ case FZ_FUNCTION_BASED: fprintf(out, "\ttype function_based\n"); break;
+ case FZ_LINEAR: fprintf(out, "\ttype linear\n"); break;
+ case FZ_RADIAL: fprintf(out, "\ttype radial\n"); break;
+ default: /* MESH */ fprintf(out, "\ttype mesh\n"); break;
+ }
+
+ fprintf(out, "\tbbox [%g %g %g %g]\n",
+ shade->bbox.x0, shade->bbox.y0,
+ shade->bbox.x1, shade->bbox.y1);
+
+ fprintf(out, "\tcolorspace %s\n", shade->colorspace->name);
+
+ fprintf(out, "\tmatrix [%g %g %g %g %g %g]\n",
+ shade->matrix.a, shade->matrix.b, shade->matrix.c,
+ shade->matrix.d, shade->matrix.e, shade->matrix.f);
+
+ if (shade->use_background)
+ {
+ fprintf(out, "\tbackground [");
+ for (i = 0; i < shade->colorspace->n; i++)
+ fprintf(out, "%s%g", i == 0 ? "" : " ", shade->background[i]);
+ fprintf(out, "]\n");
+ }
+
+ if (shade->use_function)
+ {
+ fprintf(out, "\tfunction\n");
+ }
+
+ fprintf(out, "}\n");
+}
+#endif
diff --git a/source/fitz/stext-device.c b/source/fitz/stext-device.c
new file mode 100644
index 00000000..89cf8566
--- /dev/null
+++ b/source/fitz/stext-device.c
@@ -0,0 +1,1027 @@
+#include "mupdf/fitz.h"
+#include "ucdn.h"
+
+/* Extract text into an unsorted span soup. */
+
+#define LINE_DIST 0.9f
+#define SPACE_DIST 0.2f
+#define SPACE_MAX_DIST 0.8f
+#define PARAGRAPH_DIST 0.5f
+
+#undef DEBUG_SPANS
+#undef DEBUG_INTERNALS
+#undef DEBUG_LINE_HEIGHTS
+#undef DEBUG_MASKS
+#undef DEBUG_ALIGN
+#undef DEBUG_INDENTS
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_ADVANCES_H
+
+typedef struct fz_text_device_s fz_text_device;
+
+typedef struct span_soup_s span_soup;
+
+struct fz_text_device_s
+{
+ fz_text_sheet *sheet;
+ fz_text_page *page;
+ span_soup *spans;
+ fz_text_span *cur_span;
+ int lastchar;
+};
+
+static fz_rect *
+add_point_to_rect(fz_rect *a, const fz_point *p)
+{
+ if (p->x < a->x0)
+ a->x0 = p->x;
+ if (p->x > a->x1)
+ a->x1 = p->x;
+ if (p->y < a->y0)
+ a->y0 = p->y;
+ if (p->y > a->y1)
+ a->y1 = p->y;
+ return a;
+}
+
+fz_rect *
+fz_text_char_bbox(fz_rect *bbox, fz_text_span *span, int i)
+{
+ fz_point a, d;
+ const fz_point *max;
+ fz_text_char *ch;
+
+ if (!span || i >= span->len)
+ {
+ *bbox = fz_empty_rect;
+ }
+ ch = &span->text[i];
+ if (i == span->len-1)
+ max = &span->max;
+ else
+ max = &span->text[i+1].p;
+ a.x = 0;
+ a.y = span->ascender_max;
+ fz_transform_vector(&a, &span->transform);
+ d.x = 0;
+ d.y = span->descender_min;
+ fz_transform_vector(&d, &span->transform);
+ bbox->x0 = bbox->x1 = ch->p.x + a.x;
+ bbox->y0 = bbox->y1 = ch->p.y + a.y;
+ a.x += max->x;
+ a.y += max->y;
+ add_point_to_rect(bbox, &a);
+ a.x = ch->p.x + d.x;
+ a.y = ch->p.y + d.y;
+ add_point_to_rect(bbox, &a);
+ a.x = max->x + d.x;
+ a.y = max->y + d.y;
+ add_point_to_rect(bbox, &a);
+ return bbox;
+}
+
+static void
+add_bbox_to_span(fz_text_span *span)
+{
+ fz_point a, d;
+ fz_rect *bbox = &span->bbox;
+
+ if (!span)
+ return;
+ a.x = 0;
+ a.y = span->ascender_max;
+ fz_transform_vector(&a, &span->transform);
+ d.x = 0;
+ d.y = span->descender_min;
+ fz_transform_vector(&d, &span->transform);
+ bbox->x0 = bbox->x1 = span->min.x + a.x;
+ bbox->y0 = bbox->y1 = span->min.y + a.y;
+ a.x += span->max.x;
+ a.y += span->max.y;
+ add_point_to_rect(bbox, &a);
+ a.x = span->min.x + d.x;
+ a.y = span->min.y + d.y;
+ add_point_to_rect(bbox, &a);
+ a.x = span->max.x + d.x;
+ a.y = span->max.y + d.y;
+ add_point_to_rect(bbox, &a);
+}
+
+struct span_soup_s
+{
+ fz_context *ctx;
+ int len, cap;
+ fz_text_span **spans;
+};
+
+static span_soup *
+new_span_soup(fz_context *ctx)
+{
+ span_soup *soup = fz_malloc_struct(ctx, span_soup);
+ soup->ctx = ctx;
+ soup->len = 0;
+ soup->cap = 0;
+ soup->spans = NULL;
+ return soup;
+}
+
+static void
+free_span_soup(span_soup *soup)
+{
+ int i;
+
+ if (soup == NULL)
+ return;
+ for (i = 0; i < soup->len; i++)
+ {
+ fz_free(soup->ctx, soup->spans[i]);
+ }
+ fz_free(soup->ctx, soup->spans);
+ fz_free(soup->ctx, soup);
+}
+
+static void
+add_span_to_soup(span_soup *soup, fz_text_span *span)
+{
+ if (span == NULL)
+ return;
+ if (soup->len == soup->cap)
+ {
+ int newcap = (soup->cap ? soup->cap * 2 : 16);
+ soup->spans = fz_resize_array(soup->ctx, soup->spans, newcap, sizeof(*soup->spans));
+ soup->cap = newcap;
+ }
+ add_bbox_to_span(span);
+ soup->spans[soup->len++] = span;
+}
+
+static fz_text_line *
+push_span(fz_context *ctx, fz_text_device *tdev, fz_text_span *span, int new_line, float distance)
+{
+ fz_text_line *line;
+ fz_text_block *block;
+ fz_text_page *page = tdev->page;
+ int prev_not_text = 0;
+
+ if (page->len == 0 || page->blocks[page->len-1].type != FZ_PAGE_BLOCK_TEXT)
+ prev_not_text = 1;
+
+ if (new_line || prev_not_text)
+ {
+ float size = fz_matrix_expansion(&span->transform);
+ /* So, a new line. Part of the same block or not? */
+ if (distance == 0 || distance > size * 1.5 || distance < -size * PARAGRAPH_DIST || page->len == 0 || prev_not_text)
+ {
+ /* New block */
+ if (page->len == page->cap)
+ {
+ int newcap = (page->cap ? page->cap*2 : 4);
+ page->blocks = fz_resize_array(ctx, page->blocks, newcap, sizeof(*page->blocks));
+ page->cap = newcap;
+ }
+ block = fz_malloc_struct(ctx, fz_text_block);
+ page->blocks[page->len].type = FZ_PAGE_BLOCK_TEXT;
+ page->blocks[page->len].u.text = block;
+ block->cap = 0;
+ block->len = 0;
+ block->lines = 0;
+ block->bbox = fz_empty_rect;
+ page->len++;
+ distance = 0;
+ }
+
+ /* New line */
+ block = page->blocks[page->len-1].u.text;
+ if (block->len == block->cap)
+ {
+ int newcap = (block->cap ? block->cap*2 : 4);
+ block->lines = fz_resize_array(ctx, block->lines, newcap, sizeof(*block->lines));
+ block->cap = newcap;
+ }
+ block->lines[block->len].first_span = NULL;
+ block->lines[block->len].last_span = NULL;
+ block->lines[block->len].distance = distance;
+ block->lines[block->len].bbox = fz_empty_rect;
+ block->len++;
+ }
+
+ /* Find last line and append to it */
+ block = page->blocks[page->len-1].u.text;
+ line = &block->lines[block->len-1];
+
+ fz_union_rect(&block->lines[block->len-1].bbox, &span->bbox);
+ fz_union_rect(&block->bbox, &span->bbox);
+ span->base_offset = (new_line ? 0 : distance);
+
+ if (!line->first_span)
+ {
+ line->first_span = line->last_span = span;
+ span->next = NULL;
+ }
+ else
+ {
+ line->last_span->next = span;
+ line->last_span = span;
+ }
+
+ return line;
+}
+
+#if defined(DEBUG_SPANS) || defined(DEBUG_ALIGN) || defined(DEBUG_INDENTS)
+static void
+dump_span(fz_text_span *s)
+{
+ int i;
+ for (i=0; i < s->len; i++)
+ {
+ printf("%c", s->text[i].c);
+ }
+}
+#endif
+
+#ifdef DEBUG_ALIGN
+static void
+dump_line(fz_text_line *line)
+{
+ int i;
+ for (i=0; i < line->len; i++)
+ {
+ fz_text_span *s = line->spans[i];
+ if (s->spacing > 1)
+ printf(" ");
+ dump_span(s);
+ }
+ printf("\n");
+}
+#endif
+
+static void
+strain_soup(fz_context *ctx, fz_text_device *tdev)
+{
+ span_soup *soup = tdev->spans;
+ fz_text_line *last_line = NULL;
+ fz_text_span *last_span = NULL;
+ int span_num;
+
+ /* Really dumb implementation to match what we had before */
+ for (span_num=0; span_num < soup->len; span_num++)
+ {
+ fz_text_span *span = soup->spans[span_num];
+ int new_line = 1;
+ float distance = 0;
+ float spacing = 0;
+ soup->spans[span_num] = NULL;
+ if (last_span)
+ {
+ /* If we have a last_span, we must have a last_line */
+ /* Do span and last_line share the same baseline? */
+ fz_point p, q, perp_r;
+ float dot;
+ float size = fz_matrix_expansion(&span->transform);
+
+#ifdef DEBUG_SPANS
+ {
+ printf("Comparing: \"");
+ dump_span(last_span);
+ printf("\" and \"");
+ dump_span(span);
+ printf("\"\n");
+ }
+#endif
+
+ p.x = last_line->first_span->max.x - last_line->first_span->min.x;
+ p.y = last_line->first_span->max.y - last_line->first_span->min.y;
+ fz_normalize_vector(&p);
+ q.x = span->max.x - span->min.x;
+ q.y = span->max.y - span->min.y;
+ fz_normalize_vector(&q);
+#ifdef DEBUG_SPANS
+ printf("last_span=%g %g -> %g %g = %g %g\n", last_span->min.x, last_span->min.y, last_span->max.x, last_span->max.y, p.x, p.y);
+ printf("span =%g %g -> %g %g = %g %g\n", span->min.x, span->min.y, span->max.x, span->max.y, q.x, q.y);
+#endif
+ perp_r.y = last_line->first_span->min.x - span->min.x;
+ perp_r.x = -(last_line->first_span->min.y - span->min.y);
+ /* Check if p and q are parallel. If so, then this
+ * line is parallel with the last one. */
+ dot = p.x * q.x + p.y * q.y;
+ if (fabsf(dot) > 0.9995)
+ {
+ /* If we take the dot product of normalised(p) and
+ * perp(r), we get the perpendicular distance from
+ * one line to the next (assuming they are parallel). */
+ distance = p.x * perp_r.x + p.y * perp_r.y;
+ /* We allow 'small' distances of baseline changes
+ * to cope with super/subscript. FIXME: We should
+ * gather subscript/superscript information here. */
+ new_line = (fabsf(distance) > size * LINE_DIST);
+ }
+ else
+ {
+ new_line = 1;
+ distance = 0;
+ }
+ if (!new_line)
+ {
+ fz_point delta;
+
+ delta.x = span->min.x - last_span->max.x;
+ delta.y = span->min.y - last_span->max.y;
+
+ spacing = (p.x * delta.x + p.y * delta.y);
+ spacing = fabsf(spacing);
+ /* Only allow changes in baseline (subscript/superscript etc)
+ * when the spacing is small. */
+ if (spacing * fabsf(distance) > size * LINE_DIST && fabsf(distance) > size * 0.1f)
+ {
+ new_line = 1;
+ distance = 0;
+ spacing = 0;
+ }
+ else
+ {
+ spacing /= size * SPACE_DIST;
+ /* Apply the same logic here as when we're adding chars to build spans. */
+ if (spacing >= 1 && spacing < (SPACE_MAX_DIST/SPACE_DIST))
+ spacing = 1;
+ }
+ }
+#ifdef DEBUG_SPANS
+ printf("dot=%g new_line=%d distance=%g size=%g spacing=%g\n", dot, new_line, distance, size, spacing);
+#endif
+ }
+ span->spacing = spacing;
+ last_line = push_span(ctx, tdev, span, new_line, distance);
+ last_span = span;
+ }
+}
+
+fz_text_sheet *
+fz_new_text_sheet(fz_context *ctx)
+{
+ fz_text_sheet *sheet = fz_malloc(ctx, sizeof *sheet);
+ sheet->maxid = 0;
+ sheet->style = NULL;
+ return sheet;
+}
+
+void
+fz_free_text_sheet(fz_context *ctx, fz_text_sheet *sheet)
+{
+ fz_text_style *style;
+
+ if (sheet == NULL)
+ return;
+
+ style = sheet->style;
+ while (style)
+ {
+ fz_text_style *next = style->next;
+ fz_drop_font(ctx, style->font);
+ fz_free(ctx, style);
+ style = next;
+ }
+ fz_free(ctx, sheet);
+}
+
+static fz_text_style *
+fz_lookup_text_style_imp(fz_context *ctx, fz_text_sheet *sheet,
+ float size, fz_font *font, int wmode, int script)
+{
+ fz_text_style *style;
+
+ for (style = sheet->style; style; style = style->next)
+ {
+ if (style->font == font &&
+ style->size == size &&
+ style->wmode == wmode &&
+ style->script == script) /* FIXME: others */
+ {
+ return style;
+ }
+ }
+
+ /* Better make a new one and add it to our list */
+ style = fz_malloc(ctx, sizeof *style);
+ style->id = sheet->maxid++;
+ style->font = fz_keep_font(ctx, font);
+ style->size = size;
+ style->wmode = wmode;
+ style->script = script;
+ style->next = sheet->style;
+ sheet->style = style;
+ return style;
+}
+
+static fz_text_style *
+fz_lookup_text_style(fz_context *ctx, fz_text_sheet *sheet, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha, fz_stroke_state *stroke)
+{
+ float size = 1.0f;
+ fz_font *font = text ? text->font : NULL;
+ int wmode = text ? text->wmode : 0;
+ if (ctm && text)
+ {
+ fz_matrix tm = text->trm;
+ fz_matrix trm;
+ tm.e = 0;
+ tm.f = 0;
+ fz_concat(&trm, &tm, ctm);
+ size = fz_matrix_expansion(&trm);
+ }
+ return fz_lookup_text_style_imp(ctx, sheet, size, font, wmode, 0);
+}
+
+fz_text_page *
+fz_new_text_page(fz_context *ctx)
+{
+ fz_text_page *page = fz_malloc(ctx, sizeof(*page));
+ page->mediabox = fz_empty_rect;
+ page->len = 0;
+ page->cap = 0;
+ page->blocks = NULL;
+ page->next = NULL;
+ return page;
+}
+
+static void
+fz_free_text_line_contents(fz_context *ctx, fz_text_line *line)
+{
+ fz_text_span *span, *next;
+ for (span = line->first_span; span; span=next)
+ {
+ next = span->next;
+ fz_free(ctx, span->text);
+ fz_free(ctx, span);
+ }
+}
+
+static void
+fz_free_text_block(fz_context *ctx, fz_text_block *block)
+{
+ fz_text_line *line;
+ if (block == NULL)
+ return;
+ for (line = block->lines; line < block->lines + block->len; line++)
+ fz_free_text_line_contents(ctx, line);
+ fz_free(ctx, block->lines);
+ fz_free(ctx, block);
+}
+
+static void
+fz_free_image_block(fz_context *ctx, fz_image_block *block)
+{
+ if (block == NULL)
+ return;
+ fz_drop_image(ctx, block->image);
+ fz_drop_colorspace(ctx, block->cspace);
+ fz_free(ctx, block);
+}
+
+void
+fz_free_text_page(fz_context *ctx, fz_text_page *page)
+{
+ fz_page_block *block;
+ if (page == NULL)
+ return;
+ for (block = page->blocks; block < page->blocks + page->len; block++)
+ {
+ switch (block->type)
+ {
+ case FZ_PAGE_BLOCK_TEXT:
+ fz_free_text_block(ctx, block->u.text);
+ break;
+ case FZ_PAGE_BLOCK_IMAGE:
+ fz_free_image_block(ctx, block->u.image);
+ break;
+ }
+ }
+ fz_free(ctx, page->blocks);
+ fz_free(ctx, page);
+}
+
+static fz_text_span *
+fz_new_text_span(fz_context *ctx, const fz_point *p, int wmode, const fz_matrix *trm)
+{
+ fz_text_span *span = fz_malloc_struct(ctx, fz_text_span);
+ span->ascender_max = 0;
+ span->descender_min = 0;
+ span->cap = 0;
+ span->len = 0;
+ span->min = *p;
+ span->max = *p;
+ span->wmode = wmode;
+ span->transform.a = trm->a;
+ span->transform.b = trm->b;
+ span->transform.c = trm->c;
+ span->transform.d = trm->d;
+ span->transform.e = 0;
+ span->transform.f = 0;
+ span->text = NULL;
+ span->next = NULL;
+ return span;
+}
+
+static void
+add_char_to_span(fz_context *ctx, fz_text_span *span, int c, fz_point *p, fz_point *max, fz_text_style *style)
+{
+ if (span->len == span->cap)
+ {
+ int newcap = (span->cap ? span->cap * 2 : 16);
+ span->text = fz_resize_array(ctx, span->text, newcap, sizeof(fz_text_char));
+ span->cap = newcap;
+ span->bbox = fz_empty_rect;
+ }
+ span->max = *max;
+ if (style->ascender > span->ascender_max)
+ span->ascender_max = style->ascender;
+ if (style->descender < span->descender_min)
+ span->descender_min = style->descender;
+ span->text[span->len].c = c;
+ span->text[span->len].p = *p;
+ span->text[span->len].style = style;
+ span->len++;
+}
+
+static void
+fz_add_text_char_imp(fz_context *ctx, fz_text_device *dev, fz_text_style *style, int c, fz_matrix *trm, float adv, int wmode)
+{
+ int can_append = 1;
+ int add_space = 0;
+ fz_point dir, ndir, p, q;
+ float size;
+ fz_point delta;
+ float spacing = 0;
+ float base_offset = 0;
+
+ if (wmode == 0)
+ {
+ dir.x = 1;
+ dir.y = 0;
+ }
+ else
+ {
+ dir.x = 0;
+ dir.y = 1;
+ }
+ fz_transform_vector(&dir, trm);
+ ndir = dir;
+ fz_normalize_vector(&ndir);
+ /* dir = direction vector for motion. ndir = normalised(dir) */
+
+ size = fz_matrix_expansion(trm);
+
+ if (dev->cur_span == NULL ||
+ trm->a != dev->cur_span->transform.a || trm->b != dev->cur_span->transform.b ||
+ trm->c != dev->cur_span->transform.c || trm->d != dev->cur_span->transform.d)
+ {
+ /* If the matrix has changed (or if we don't have a span at
+ * all), then we can't append. */
+#ifdef DEBUG_SPANS
+ printf("Transform changed\n");
+#endif
+ can_append = 0;
+ }
+ else
+ {
+ /* Calculate how far we've moved since the end of the current
+ * span. */
+ delta.x = trm->e - dev->cur_span->max.x;
+ delta.y = trm->f - dev->cur_span->max.y;
+
+ /* The transform has not changed, so we know we're in the same
+ * direction. Calculate 2 distances; how far off the previous
+ * baseline we are, together with how far along the baseline
+ * we are from the expected position. */
+ spacing = ndir.x * delta.x + ndir.y * delta.y;
+ base_offset = -ndir.y * delta.x + ndir.x * delta.y;
+
+ spacing /= size * SPACE_DIST;
+ spacing = fabsf(spacing);
+ if (fabsf(base_offset) < size * 0.1)
+ {
+ /* Only a small amount off the baseline - we'll take this */
+ if (spacing < 1.0)
+ {
+ /* Motion is in line, and small. */
+ }
+ else if (spacing >= 1 && spacing < (SPACE_MAX_DIST/SPACE_DIST))
+ {
+ /* Motion is in line, but large enough
+ * to warrant us adding a space */
+ if (dev->lastchar != ' ' && wmode == 0)
+ add_space = 1;
+ }
+ else
+ {
+ /* Motion is in line, but too large - split to a new span */
+ can_append = 0;
+ }
+ }
+ else
+ {
+ can_append = 0;
+ spacing = 0;
+ }
+ }
+
+#ifdef DEBUG_SPANS
+ printf("%c%c append=%d space=%d size=%g spacing=%g base_offset=%g\n", dev->lastchar, c, can_append, add_space, size, spacing, base_offset);
+#endif
+
+ p.x = trm->e;
+ p.y = trm->f;
+ if (can_append == 0)
+ {
+ /* Start a new span */
+ add_span_to_soup(dev->spans, dev->cur_span);
+ dev->cur_span = NULL;
+ dev->cur_span = fz_new_text_span(ctx, &p, wmode, trm);
+ dev->cur_span->spacing = 0;
+ }
+ if (add_space)
+ {
+ q.x = - 0.2f;
+ q.y = 0;
+ fz_transform_point(&q, trm);
+ add_char_to_span(ctx, dev->cur_span, ' ', &p, &q, style);
+ }
+ /* Advance the matrix */
+ q.x = trm->e += adv * dir.x;
+ q.y = trm->f += adv * dir.y;
+ add_char_to_span(ctx, dev->cur_span, c, &p, &q, style);
+}
+
+static void
+fz_add_text_char(fz_context *ctx, fz_text_device *dev, fz_text_style *style, int c, fz_matrix *trm, float adv, int wmode)
+{
+ switch (c)
+ {
+ case -1: /* ignore when one unicode character maps to multiple glyphs */
+ break;
+ case 0xFB00: /* ff */
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/2, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/2, wmode);
+ break;
+ case 0xFB01: /* fi */
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/2, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'i', trm, adv/2, wmode);
+ break;
+ case 0xFB02: /* fl */
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/2, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'l', trm, adv/2, wmode);
+ break;
+ case 0xFB03: /* ffi */
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/3, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/3, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'i', trm, adv/3, wmode);
+ break;
+ case 0xFB04: /* ffl */
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/3, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'f', trm, adv/3, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 'l', trm, adv/3, wmode);
+ break;
+ case 0xFB05: /* long st */
+ case 0xFB06: /* st */
+ fz_add_text_char_imp(ctx, dev, style, 's', trm, adv/2, wmode);
+ fz_add_text_char_imp(ctx, dev, style, 't', trm, adv/2, wmode);
+ break;
+ default:
+ fz_add_text_char_imp(ctx, dev, style, c, trm, adv, wmode);
+ break;
+ }
+}
+
+static void
+fz_text_extract(fz_context *ctx, fz_text_device *dev, fz_text *text, const fz_matrix *ctm, fz_text_style *style)
+{
+ fz_font *font = text->font;
+ FT_Face face = font->ft_face;
+ fz_matrix tm = text->trm;
+ fz_matrix trm;
+ float adv;
+ float ascender = 1;
+ float descender = 0;
+ int multi;
+ int i, j, err;
+
+ if (text->len == 0)
+ return;
+
+ if (font->ft_face)
+ {
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72);
+ if (err)
+ fz_warn(ctx, "freetype set character size: %s", ft_error_string(err));
+ ascender = (float)face->ascender / face->units_per_EM;
+ descender = (float)face->descender / face->units_per_EM;
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ }
+ else if (font->t3procs && !fz_is_empty_rect(&font->bbox))
+ {
+ ascender = font->bbox.y1;
+ descender = font->bbox.y0;
+ }
+ style->ascender = ascender;
+ style->descender = descender;
+
+ tm.e = 0;
+ tm.f = 0;
+ fz_concat(&trm, &tm, ctm);
+
+ for (i = 0; i < text->len; i++)
+ {
+ /* Calculate new pen location and delta */
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ fz_concat(&trm, &tm, ctm);
+
+ /* Calculate bounding box and new pen position based on font metrics */
+ if (font->ft_face)
+ {
+ FT_Fixed ftadv = 0;
+ int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM;
+
+ /* TODO: freetype returns broken vertical metrics */
+ /* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72);
+ if (err)
+ fz_warn(ctx, "freetype set character size: %s", ft_error_string(err));
+ FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv);
+ adv = ftadv / 65536.0f;
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ }
+ else
+ {
+ adv = font->t3widths[text->items[i].gid];
+ }
+
+ /* Check for one glyph to many char mapping */
+ for (j = i + 1; j < text->len; j++)
+ if (text->items[j].gid >= 0)
+ break;
+ multi = j - i;
+
+ if (multi == 1)
+ {
+ fz_add_text_char(ctx, dev, style, text->items[i].ucs, &trm, adv, text->wmode);
+ }
+ else
+ {
+ for (j = 0; j < multi; j++)
+ {
+ fz_add_text_char(ctx, dev, style, text->items[i + j].ucs, &trm, adv/multi, text->wmode);
+ }
+ i += j - 1;
+ }
+
+ dev->lastchar = text->items[i].ucs;
+ }
+}
+
+static void
+fz_text_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_text_device *tdev = dev->user;
+ fz_text_style *style;
+ style = fz_lookup_text_style(dev->ctx, tdev->sheet, text, ctm, colorspace, color, alpha, NULL);
+ fz_text_extract(dev->ctx, tdev, text, ctm, style);
+}
+
+static void
+fz_text_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_text_device *tdev = dev->user;
+ fz_text_style *style;
+ style = fz_lookup_text_style(dev->ctx, tdev->sheet, text, ctm, colorspace, color, alpha, stroke);
+ fz_text_extract(dev->ctx, tdev, text, ctm, style);
+}
+
+static void
+fz_text_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ fz_text_device *tdev = dev->user;
+ fz_text_style *style;
+ style = fz_lookup_text_style(dev->ctx, tdev->sheet, text, ctm, NULL, NULL, 0, NULL);
+ fz_text_extract(dev->ctx, tdev, text, ctm, style);
+}
+
+static void
+fz_text_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ fz_text_device *tdev = dev->user;
+ fz_text_style *style;
+ style = fz_lookup_text_style(dev->ctx, tdev->sheet, text, ctm, NULL, NULL, 0, stroke);
+ fz_text_extract(dev->ctx, tdev, text, ctm, style);
+}
+
+static void
+fz_text_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+ fz_text_device *tdev = dev->user;
+ fz_text_style *style;
+ style = fz_lookup_text_style(dev->ctx, tdev->sheet, text, ctm, NULL, NULL, 0, NULL);
+ fz_text_extract(dev->ctx, tdev, text, ctm, style);
+}
+
+static void
+fz_text_fill_image_mask(fz_device *dev, fz_image *img, const fz_matrix *ctm,
+ fz_colorspace *cspace, float *color, float alpha)
+{
+ fz_text_device *tdev = dev->user;
+ fz_text_page *page = tdev->page;
+ fz_image_block *block;
+ fz_context *ctx = dev->ctx;
+
+ /* If the alpha is less than 50% then it's probably a watermark or
+ * effect or something. Skip it */
+ if (alpha < 0.5)
+ return;
+
+ /* New block */
+ if (page->len == page->cap)
+ {
+ int newcap = (page->cap ? page->cap*2 : 4);
+ page->blocks = fz_resize_array(ctx, page->blocks, newcap, sizeof(*page->blocks));
+ page->cap = newcap;
+ }
+ block = fz_malloc_struct(ctx, fz_image_block);
+ page->blocks[page->len].type = FZ_PAGE_BLOCK_IMAGE;
+ page->blocks[page->len].u.image = block;
+ block->image = fz_keep_image(ctx, img);
+ block->cspace = fz_keep_colorspace(ctx, cspace);
+ if (cspace)
+ memcpy(block->colors, color, sizeof(block->colors[0])*cspace->n);
+ page->len++;
+}
+
+static void
+fz_text_fill_image(fz_device *dev, fz_image *img, const fz_matrix *ctm, float alpha)
+{
+ fz_text_fill_image_mask(dev, img, ctm, NULL, NULL, alpha);
+}
+
+static int
+fz_bidi_direction(int bidiclass, int curdir)
+{
+ switch (bidiclass)
+ {
+ /* strong */
+ case UCDN_BIDI_CLASS_L: return 1;
+ case UCDN_BIDI_CLASS_R: return -1;
+ case UCDN_BIDI_CLASS_AL: return -1;
+
+ /* weak */
+ case UCDN_BIDI_CLASS_EN:
+ case UCDN_BIDI_CLASS_ES:
+ case UCDN_BIDI_CLASS_ET:
+ case UCDN_BIDI_CLASS_AN:
+ case UCDN_BIDI_CLASS_CS:
+ case UCDN_BIDI_CLASS_NSM:
+ case UCDN_BIDI_CLASS_BN:
+ return curdir;
+
+ /* neutral */
+ case UCDN_BIDI_CLASS_B:
+ case UCDN_BIDI_CLASS_S:
+ case UCDN_BIDI_CLASS_WS:
+ case UCDN_BIDI_CLASS_ON:
+ return curdir;
+
+ /* embedding, override, pop ... we don't support them */
+ default:
+ return 0;
+ }
+}
+
+static void
+fz_bidi_reorder_run(fz_text_span *span, int a, int b, int dir)
+{
+ if (a < b && dir == -1)
+ {
+ fz_text_char c;
+ int m = a + (b - a) / 2;
+ while (a < m)
+ {
+ b--;
+ c = span->text[a];
+ span->text[a] = span->text[b];
+ span->text[b] = c;
+ a++;
+ }
+ }
+}
+
+static void
+fz_bidi_reorder_span(fz_text_span *span)
+{
+ int a, b, dir, curdir;
+
+ a = 0;
+ curdir = 1;
+ for (b = 0; b < span->len; b++)
+ {
+ dir = fz_bidi_direction(ucdn_get_bidi_class(span->text[b].c), curdir);
+ if (dir != curdir)
+ {
+ fz_bidi_reorder_run(span, a, b, curdir);
+ curdir = dir;
+ a = b;
+ }
+ }
+ fz_bidi_reorder_run(span, a, b, curdir);
+}
+
+static void
+fz_bidi_reorder_text_page(fz_context *ctx, fz_text_page *page)
+{
+ fz_page_block *pageblock;
+ fz_text_block *block;
+ fz_text_line *line;
+ fz_text_span *span;
+
+ for (pageblock = page->blocks; pageblock < page->blocks + page->len; pageblock++)
+ if (pageblock->type == FZ_PAGE_BLOCK_TEXT)
+ for (block = pageblock->u.text, line = block->lines; line < block->lines + block->len; line++)
+ for (span = line->first_span; span; span = span->next)
+ fz_bidi_reorder_span(span);
+}
+
+static void
+fz_text_begin_page(fz_device *dev, const fz_rect *mediabox, const fz_matrix *ctm)
+{
+ fz_context *ctx = dev->ctx;
+ fz_text_device *tdev = dev->user;
+
+ if (tdev->page->len)
+ {
+ tdev->page->next = fz_new_text_page(ctx);
+ tdev->page = tdev->page->next;
+ }
+
+ tdev->page->mediabox = *mediabox;
+ fz_transform_rect(&tdev->page->mediabox, ctm);
+
+ tdev->spans = new_span_soup(ctx);
+}
+
+static void
+fz_text_end_page(fz_device *dev)
+{
+ fz_context *ctx = dev->ctx;
+ fz_text_device *tdev = dev->user;
+
+ add_span_to_soup(tdev->spans, tdev->cur_span);
+ tdev->cur_span = NULL;
+
+ strain_soup(ctx, tdev);
+ free_span_soup(tdev->spans);
+ tdev->spans = NULL;
+
+ /* TODO: smart sorting of blocks in reading order */
+ /* TODO: unicode NFC normalization */
+
+ fz_bidi_reorder_text_page(ctx, tdev->page);
+}
+
+static void
+fz_text_free_user(fz_device *dev)
+{
+ fz_text_device *tdev = dev->user;
+ free_span_soup(tdev->spans);
+ fz_free(dev->ctx, tdev);
+}
+
+fz_device *
+fz_new_text_device(fz_context *ctx, fz_text_sheet *sheet, fz_text_page *page)
+{
+ fz_device *dev;
+
+ fz_text_device *tdev = fz_malloc_struct(ctx, fz_text_device);
+ tdev->sheet = sheet;
+ tdev->page = page;
+ tdev->spans = NULL;
+ tdev->cur_span = NULL;
+ tdev->lastchar = ' ';
+
+ dev = fz_new_device(ctx, tdev);
+ dev->hints = FZ_IGNORE_IMAGE | FZ_IGNORE_SHADE;
+ dev->begin_page = fz_text_begin_page;
+ dev->end_page = fz_text_end_page;
+ dev->free_user = fz_text_free_user;
+ dev->fill_text = fz_text_fill_text;
+ dev->stroke_text = fz_text_stroke_text;
+ dev->clip_text = fz_text_clip_text;
+ dev->clip_stroke_text = fz_text_clip_stroke_text;
+ dev->ignore_text = fz_text_ignore_text;
+ dev->fill_image = fz_text_fill_image;
+ dev->fill_image_mask = fz_text_fill_image_mask;
+
+ return dev;
+}
diff --git a/source/fitz/stext-output.c b/source/fitz/stext-output.c
new file mode 100644
index 00000000..d3241131
--- /dev/null
+++ b/source/fitz/stext-output.c
@@ -0,0 +1,400 @@
+#include "mupdf/fitz.h"
+
+#define SUBSCRIPT_OFFSET 0.2f
+#define SUPERSCRIPT_OFFSET -0.2f
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+/* XML, HTML and plain-text output */
+
+static int font_is_bold(fz_font *font)
+{
+ FT_Face face = font->ft_face;
+ if (face && (face->style_flags & FT_STYLE_FLAG_BOLD))
+ return 1;
+ if (strstr(font->name, "Bold"))
+ return 1;
+ return 0;
+}
+
+static int font_is_italic(fz_font *font)
+{
+ FT_Face face = font->ft_face;
+ if (face && (face->style_flags & FT_STYLE_FLAG_ITALIC))
+ return 1;
+ if (strstr(font->name, "Italic") || strstr(font->name, "Oblique"))
+ return 1;
+ return 0;
+}
+
+static void
+fz_print_style_begin(fz_output *out, fz_text_style *style)
+{
+ int script = style->script;
+ fz_printf(out, "<span class=\"s%d\">", style->id);
+ while (script-- > 0)
+ fz_printf(out, "<sup>");
+ while (++script < 0)
+ fz_printf(out, "<sub>");
+}
+
+static void
+fz_print_style_end(fz_output *out, fz_text_style *style)
+{
+ int script = style->script;
+ while (script-- > 0)
+ fz_printf(out, "</sup>");
+ while (++script < 0)
+ fz_printf(out, "</sub>");
+ fz_printf(out, "</span>");
+}
+
+static void
+fz_print_style(fz_output *out, fz_text_style *style)
+{
+ char *s = strchr(style->font->name, '+');
+ s = s ? s + 1 : style->font->name;
+ fz_printf(out, "span.s%d{font-family:\"%s\";font-size:%gpt;",
+ style->id, s, style->size);
+ if (font_is_italic(style->font))
+ fz_printf(out, "font-style:italic;");
+ if (font_is_bold(style->font))
+ fz_printf(out, "font-weight:bold;");
+ fz_printf(out, "}\n");
+}
+
+void
+fz_print_text_sheet(fz_context *ctx, fz_output *out, fz_text_sheet *sheet)
+{
+ fz_text_style *style;
+ for (style = sheet->style; style; style = style->next)
+ fz_print_style(out, style);
+}
+
+static void
+send_data_base64(fz_output *out, fz_buffer *buffer)
+{
+ int i, len;
+ static const char set[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ len = buffer->len/3;
+ for (i = 0; i < len; i++)
+ {
+ int c = buffer->data[3*i];
+ int d = buffer->data[3*i+1];
+ int e = buffer->data[3*i+2];
+ if ((i & 15) == 0)
+ fz_printf(out, "\n");
+ fz_printf(out, "%c%c%c%c", set[c>>2], set[((c&3)<<4)|(d>>4)], set[((d&15)<<2)|(e>>6)], set[e & 63]);
+ }
+ i *= 3;
+ switch (buffer->len-i)
+ {
+ case 2:
+ {
+ int c = buffer->data[i];
+ int d = buffer->data[i+1];
+ fz_printf(out, "%c%c%c=", set[c>>2], set[((c&3)<<4)|(d>>4)], set[((d&15)<<2)]);
+ break;
+ }
+ case 1:
+ {
+ int c = buffer->data[i];
+ fz_printf(out, "%c%c==", set[c>>2], set[(c&3)<<4]);
+ break;
+ }
+ default:
+ case 0:
+ break;
+ }
+}
+
+void
+fz_print_text_page_html(fz_context *ctx, fz_output *out, fz_text_page *page)
+{
+ int block_n, line_n, ch_n;
+ fz_text_style *style = NULL;
+ fz_text_line *line;
+ fz_text_span *span;
+ void *last_region = NULL;
+
+ fz_printf(out, "<div class=\"page\">\n");
+
+ for (block_n = 0; block_n < page->len; block_n++)
+ {
+ switch (page->blocks[block_n].type)
+ {
+ case FZ_PAGE_BLOCK_TEXT:
+ {
+ fz_text_block * block = page->blocks[block_n].u.text;
+ fz_printf(out, "<div class=\"block\"><p>\n");
+ for (line_n = 0; line_n < block->len; line_n++)
+ {
+ int lastcol=-1;
+ line = &block->lines[line_n];
+ style = NULL;
+
+ if (line->region != last_region)
+ {
+ if (last_region)
+ fz_printf(out, "</div>");
+ fz_printf(out, "<div class=\"metaline\">");
+ last_region = line->region;
+ }
+ fz_printf(out, "<div class=\"line\"");
+#ifdef DEBUG_INTERNALS
+ if (line->region)
+ fz_printf(out, " region=\"%x\"", line->region);
+#endif
+ fz_printf(out, ">");
+ for (span = line->first_span; span; span = span->next)
+ {
+ float size = fz_matrix_expansion(&span->transform);
+ float base_offset = span->base_offset / size;
+
+ if (lastcol != span->column)
+ {
+ if (lastcol >= 0)
+ {
+ fz_printf(out, "</div>");
+ }
+ /* If we skipped any columns then output some spacer spans */
+ while (lastcol < span->column-1)
+ {
+ fz_printf(out, "<div class=\"cell\"></div>");
+ lastcol++;
+ }
+ lastcol++;
+ /* Now output the span to contain this entire column */
+ fz_printf(out, "<div class=\"cell\" style=\"");
+ {
+ fz_text_span *sn;
+ for (sn = span->next; sn; sn = sn->next)
+ {
+ if (sn->column != lastcol)
+ break;
+ }
+ fz_printf(out, "width:%g%%;align:%s", span->column_width, (span->align == 0 ? "left" : (span->align == 1 ? "center" : "right")));
+ }
+ if (span->indent > 1)
+ fz_printf(out, ";padding-left:1em;text-indent:-1em");
+ if (span->indent < -1)
+ fz_printf(out, ";text-indent:1em");
+ fz_printf(out, "\">");
+ }
+#ifdef DEBUG_INTERNALS
+ fz_printf(out, "<span class=\"internal_span\"");
+ if (span->column)
+ fz_printf(out, " col=\"%x\"", span->column);
+ fz_printf(out, ">");
+#endif
+ if (span->spacing >= 1)
+ fz_printf(out, " ");
+ if (base_offset > SUBSCRIPT_OFFSET)
+ fz_printf(out, "<sub>");
+ else if (base_offset < SUPERSCRIPT_OFFSET)
+ fz_printf(out, "<sup>");
+ for (ch_n = 0; ch_n < span->len; ch_n++)
+ {
+ fz_text_char *ch = &span->text[ch_n];
+ if (style != ch->style)
+ {
+ if (style)
+ fz_print_style_end(out, style);
+ fz_print_style_begin(out, ch->style);
+ style = ch->style;
+ }
+
+ if (ch->c == '<')
+ fz_printf(out, "&lt;");
+ else if (ch->c == '>')
+ fz_printf(out, "&gt;");
+ else if (ch->c == '&')
+ fz_printf(out, "&amp;");
+ else if (ch->c >= 32 && ch->c <= 127)
+ fz_printf(out, "%c", ch->c);
+ else
+ fz_printf(out, "&#x%x;", ch->c);
+ }
+ if (style)
+ {
+ fz_print_style_end(out, style);
+ style = NULL;
+ }
+ if (base_offset > SUBSCRIPT_OFFSET)
+ fz_printf(out, "</sub>");
+ else if (base_offset < SUPERSCRIPT_OFFSET)
+ fz_printf(out, "</sup>");
+#ifdef DEBUG_INTERNALS
+ fz_printf(out, "</span>");
+#endif
+ }
+ /* Close our floating span */
+ fz_printf(out, "</div>");
+ /* Close the line */
+ fz_printf(out, "</div>");
+ fz_printf(out, "\n");
+ }
+ /* Close the metaline */
+ fz_printf(out, "</div>");
+ last_region = NULL;
+ fz_printf(out, "</p></div>\n");
+ break;
+ }
+ case FZ_PAGE_BLOCK_IMAGE:
+ {
+ fz_image_block *image = page->blocks[block_n].u.image;
+ fz_printf(out, "<img width=%d height=%d src=\"data:", image->image->w, image->image->h);
+ switch (image->image->buffer == NULL ? FZ_IMAGE_JPX : image->image->buffer->params.type)
+ {
+ case FZ_IMAGE_JPEG:
+ fz_printf(out, "image/jpeg;base64,");
+ send_data_base64(out, image->image->buffer->buffer);
+ break;
+ case FZ_IMAGE_PNG:
+ fz_printf(out, "image/png;base64,");
+ send_data_base64(out, image->image->buffer->buffer);
+ break;
+ default:
+ {
+ fz_buffer *buf = fz_image_as_png(ctx, image->image, image->image->w, image->image->h);
+ fz_printf(out, "image/png;base64,");
+ send_data_base64(out, buf);
+ fz_drop_buffer(ctx, buf);
+ break;
+ }
+ }
+ fz_printf(out, "\">\n");
+ break;
+ }
+ }
+ }
+
+ fz_printf(out, "</div>\n");
+}
+
+void
+fz_print_text_page_xml(fz_context *ctx, fz_output *out, fz_text_page *page)
+{
+ int block_n;
+
+ fz_printf(out, "<page width=\"%g\" height=\"%g\">\n",
+ page->mediabox.x1 - page->mediabox.x0,
+ page->mediabox.y1 - page->mediabox.y0);
+
+ for (block_n = 0; block_n < page->len; block_n++)
+ {
+ switch (page->blocks[block_n].type)
+ {
+ case FZ_PAGE_BLOCK_TEXT:
+ {
+ fz_text_block *block = page->blocks[block_n].u.text;
+ fz_text_line *line;
+ char *s;
+
+ fz_printf(out, "<block bbox=\"%g %g %g %g\">\n",
+ block->bbox.x0, block->bbox.y0, block->bbox.x1, block->bbox.y1);
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ fz_text_span *span;
+ fz_printf(out, "<line bbox=\"%g %g %g %g\">\n",
+ line->bbox.x0, line->bbox.y0, line->bbox.x1, line->bbox.y1);
+ for (span = line->first_span; span; span = span->next)
+ {
+ fz_text_style *style = NULL;
+ int char_num;
+ for (char_num = 0; char_num < span->len; char_num++)
+ {
+ fz_text_char *ch = &span->text[char_num];
+ if (ch->style != style)
+ {
+ if (style)
+ {
+ fz_printf(out, "</span>\n");
+ }
+ style = ch->style;
+ s = strchr(style->font->name, '+');
+ s = s ? s + 1 : style->font->name;
+ fz_printf(out, "<span bbox=\"%g %g %g %g\" font=\"%s\" size=\"%g\">\n",
+ span->bbox.x0, span->bbox.y0, span->bbox.x1, span->bbox.y1,
+ s, style->size);
+ }
+ {
+ fz_rect rect;
+ fz_text_char_bbox(&rect, span, char_num);
+ fz_printf(out, "<char bbox=\"%g %g %g %g\" x=\"%g\" y=\"%g\" c=\"",
+ rect.x0, rect.y0, rect.x1, rect.y1, ch->p.x, ch->p.y);
+ }
+ switch (ch->c)
+ {
+ case '<': fz_printf(out, "&lt;"); break;
+ case '>': fz_printf(out, "&gt;"); break;
+ case '&': fz_printf(out, "&amp;"); break;
+ case '"': fz_printf(out, "&quot;"); break;
+ case '\'': fz_printf(out, "&apos;"); break;
+ default:
+ if (ch->c >= 32 && ch->c <= 127)
+ fz_printf(out, "%c", ch->c);
+ else
+ fz_printf(out, "&#x%x;", ch->c);
+ break;
+ }
+ fz_printf(out, "\"/>\n");
+ }
+ if (style)
+ fz_printf(out, "</span>\n");
+ }
+ fz_printf(out, "</line>\n");
+ }
+ fz_printf(out, "</block>\n");
+ break;
+ }
+ case FZ_PAGE_BLOCK_IMAGE:
+ {
+ break;
+ }
+ }
+ }
+ fz_printf(out, "</page>\n");
+}
+
+void
+fz_print_text_page(fz_context *ctx, fz_output *out, fz_text_page *page)
+{
+ int block_n;
+
+ for (block_n = 0; block_n < page->len; block_n++)
+ {
+ switch (page->blocks[block_n].type)
+ {
+ case FZ_PAGE_BLOCK_TEXT:
+ {
+ fz_text_block *block = page->blocks[block_n].u.text;
+ fz_text_line *line;
+ fz_text_char *ch;
+ char utf[10];
+ int i, n;
+
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ fz_text_span *span;
+ for (span = line->first_span; span; span = span->next)
+ {
+ for (ch = span->text; ch < span->text + span->len; ch++)
+ {
+ n = fz_runetochar(utf, ch->c);
+ for (i = 0; i < n; i++)
+ fz_printf(out, "%c", utf[i]);
+ }
+ }
+ fz_printf(out, "\n");
+ }
+ fz_printf(out, "\n");
+ break;
+ }
+ case FZ_PAGE_BLOCK_IMAGE:
+ break;
+ }
+ }
+}
diff --git a/source/fitz/stext-paragraph.c b/source/fitz/stext-paragraph.c
new file mode 100644
index 00000000..51062938
--- /dev/null
+++ b/source/fitz/stext-paragraph.c
@@ -0,0 +1,1500 @@
+#include "mupdf/fitz.h"
+
+/* Assemble span soup into blocks and lines. */
+
+#define MY_EPSILON 0.001f
+
+#undef DEBUG_LINE_HEIGHTS
+#undef DEBUG_MASKS
+#undef DEBUG_ALIGN
+#undef DEBUG_INDENTS
+
+#undef SPOT_LINE_NUMBERS
+
+typedef struct line_height_s
+{
+ float height;
+ int count;
+ fz_text_style *style;
+} line_height;
+
+typedef struct line_heights_s
+{
+ fz_context *ctx;
+ int cap;
+ int len;
+ line_height *lh;
+} line_heights;
+
+static line_heights *
+new_line_heights(fz_context *ctx)
+{
+ line_heights *lh = fz_malloc_struct(ctx, line_heights);
+ lh->ctx = ctx;
+ return lh;
+}
+
+static void
+free_line_heights(line_heights *lh)
+{
+ if (!lh)
+ return;
+ fz_free(lh->ctx, lh->lh);
+ fz_free(lh->ctx, lh);
+}
+
+static void
+insert_line_height(line_heights *lh, fz_text_style *style, float height)
+{
+ int i;
+
+#ifdef DEBUG_LINE_HEIGHTS
+ printf("style=%x height=%g\n", style, height);
+#endif
+
+ /* If we have one already, add it in */
+ for (i=0; i < lh->len; i++)
+ {
+ /* Match if we are within 5% */
+ if (lh->lh[i].style == style && lh->lh[i].height * 0.95 <= height && lh->lh[i].height * 1.05 >= height)
+ {
+ /* Ensure that the average height is correct */
+ lh->lh[i].height = (lh->lh[i].height * lh->lh[i].count + height) / (lh->lh[i].count+1);
+ lh->lh[i].count++;
+ return;
+ }
+ }
+
+ /* Otherwise extend (if required) and add it */
+ if (lh->cap == lh->len)
+ {
+ int newcap = (lh->cap ? lh->cap * 2 : 4);
+ lh->lh = fz_resize_array(lh->ctx, lh->lh, newcap, sizeof(line_height));
+ lh->cap = newcap;
+ }
+
+ lh->lh[lh->len].count = 1;
+ lh->lh[lh->len].height = height;
+ lh->lh[lh->len].style = style;
+ lh->len++;
+}
+
+static void
+cull_line_heights(line_heights *lh)
+{
+ int i, j, k;
+
+#ifdef DEBUG_LINE_HEIGHTS
+ printf("Before culling:\n");
+ for (i = 0; i < lh->len; i++)
+ {
+ fz_text_style *style = lh->lh[i].style;
+ printf("style=%x height=%g count=%d\n", style, lh->lh[i].height, lh->lh[i].count);
+ }
+#endif
+ for (i = 0; i < lh->len; i++)
+ {
+ fz_text_style *style = lh->lh[i].style;
+ int count = lh->lh[i].count;
+ int max = i;
+
+ /* Find the max for this style */
+ for (j = i+1; j < lh->len; j++)
+ {
+ if (lh->lh[j].style == style && lh->lh[j].count > count)
+ {
+ max = j;
+ count = lh->lh[j].count;
+ }
+ }
+
+ /* Destroy all the ones other than the max */
+ if (max != i)
+ {
+ lh->lh[i].count = count;
+ lh->lh[i].height = lh->lh[max].height;
+ lh->lh[max].count = 0;
+ }
+ j = i+1;
+ for (k = j; k < lh->len; k++)
+ {
+ if (lh->lh[k].style != style)
+ lh->lh[j++] = lh->lh[k];
+ }
+ lh->len = j;
+ }
+#ifdef DEBUG_LINE_HEIGHTS
+ printf("After culling:\n");
+ for (i = 0; i < lh->len; i++)
+ {
+ fz_text_style *style = lh->lh[i].style;
+ printf("style=%x height=%g count=%d\n", style, lh->lh[i].height, lh->lh[i].count);
+ }
+#endif
+}
+
+static float
+line_height_for_style(line_heights *lh, fz_text_style *style)
+{
+ int i;
+
+ for (i=0; i < lh->len; i++)
+ {
+ if (lh->lh[i].style == style)
+ return lh->lh[i].height;
+ }
+ return 0.0; /* Never reached */
+}
+
+static void
+split_block(fz_context *ctx, fz_text_page *page, int block_num, int linenum)
+{
+ int split_len;
+ fz_text_block *block, *block2;
+
+ if (page->len == page->cap)
+ {
+ int new_cap = fz_maxi(16, page->cap * 2);
+ page->blocks = fz_resize_array(ctx, page->blocks, new_cap, sizeof(*page->blocks));
+ page->cap = new_cap;
+ }
+
+ memmove(page->blocks+block_num+1, page->blocks+block_num, (page->len - block_num)*sizeof(*page->blocks));
+ page->len++;
+
+ block2 = fz_malloc_struct(ctx, fz_text_block);
+ block = page->blocks[block_num].u.text;
+
+ page->blocks[block_num+1].type = FZ_PAGE_BLOCK_TEXT;
+ page->blocks[block_num+1].u.text = block2;
+ split_len = block->len - linenum;
+ block2->bbox = block->bbox; /* FIXME! */
+ block2->cap = 0;
+ block2->len = 0;
+ block2->lines = NULL;
+ block2->lines = fz_malloc_array(ctx, split_len, sizeof(fz_text_line));
+ block2->cap = block2->len;
+ block2->len = split_len;
+ block->len = linenum;
+ memcpy(block2->lines, block->lines + linenum, split_len * sizeof(fz_text_line));
+ block2->lines[0].distance = 0;
+}
+
+static inline int
+is_unicode_wspace(int c)
+{
+ return (c == 9 || /* TAB */
+ c == 0x0a || /* HT */
+ c == 0x0b || /* LF */
+ c == 0x0c || /* VT */
+ c == 0x0d || /* FF */
+ c == 0x20 || /* CR */
+ c == 0x85 || /* NEL */
+ c == 0xA0 || /* No break space */
+ c == 0x1680 || /* Ogham space mark */
+ c == 0x180E || /* Mongolian Vowel Separator */
+ c == 0x2000 || /* En quad */
+ c == 0x2001 || /* Em quad */
+ c == 0x2002 || /* En space */
+ c == 0x2003 || /* Em space */
+ c == 0x2004 || /* Three-per-Em space */
+ c == 0x2005 || /* Four-per-Em space */
+ c == 0x2006 || /* Five-per-Em space */
+ c == 0x2007 || /* Figure space */
+ c == 0x2008 || /* Punctuation space */
+ c == 0x2009 || /* Thin space */
+ c == 0x200A || /* Hair space */
+ c == 0x2028 || /* Line separator */
+ c == 0x2029 || /* Paragraph separator */
+ c == 0x202F || /* Narrow no-break space */
+ c == 0x205F || /* Medium mathematical space */
+ c == 0x3000); /* Ideographic space */
+}
+
+static inline int
+is_unicode_bullet(int c)
+{
+ /* The last 2 aren't strictly bullets, but will do for our usage here */
+ return (c == 0x2022 || /* Bullet */
+ c == 0x2023 || /* Triangular bullet */
+ c == 0x25e6 || /* White bullet */
+ c == 0x2043 || /* Hyphen bullet */
+ c == 0x2219 || /* Bullet operator */
+ c == 149 || /* Ascii bullet */
+ c == '*');
+}
+
+static inline int
+is_number(int c)
+{
+ return ((c >= '0' && c <= '9') ||
+ (c == '.'));
+}
+
+static inline int
+is_latin_char(int c)
+{
+ return ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z'));
+}
+
+static inline int
+is_roman(int c)
+{
+ return (c == 'i' || c == 'I' ||
+ c == 'v' || c == 'V' ||
+ c == 'x' || c == 'X' ||
+ c == 'l' || c == 'L' ||
+ c == 'c' || c == 'C' ||
+ c == 'm' || c == 'M');
+}
+
+static int
+is_list_entry(fz_text_line *line, fz_text_span *span, int *char_num_ptr)
+{
+ int char_num;
+ fz_text_char *chr;
+
+ /* First, skip over any whitespace */
+ for (char_num = 0; char_num < span->len; char_num++)
+ {
+ chr = &span->text[char_num];
+ if (!is_unicode_wspace(chr->c))
+ break;
+ }
+ *char_num_ptr = char_num;
+
+ if (span != line->first_span || char_num >= span->len)
+ return 0;
+
+ /* Now we check for various special cases, which we consider to mean
+ * that this is probably a list entry and therefore should always count
+ * as a separate paragraph (and hence not be entered in the line height
+ * table). */
+ chr = &span->text[char_num];
+
+ /* Is the first char on the line, a bullet point? */
+ if (is_unicode_bullet(chr->c))
+ return 1;
+
+#ifdef SPOT_LINE_NUMBERS
+ /* Is the entire first span a number? Or does it start with a number
+ * followed by ) or : ? Allow to involve single latin chars too. */
+ if (is_number(chr->c) || is_latin_char(chr->c))
+ {
+ int cn = char_num;
+ int met_char = is_latin_char(chr->c);
+ for (cn = char_num+1; cn < span->len; cn++)
+ {
+ fz_text_char *chr2 = &span->text[cn];
+
+ if (is_latin_char(chr2->c) && !met_char)
+ {
+ met_char = 1;
+ continue;
+ }
+ met_char = 0;
+ if (!is_number(chr2->c) && !is_unicode_wspace(chr2->c))
+ break;
+ else if (chr2->c == ')' || chr2->c == ':')
+ {
+ cn = span->len;
+ break;
+ }
+ }
+ if (cn == span->len)
+ return 1;
+ }
+
+ /* Is the entire first span a roman numeral? Or does it start with
+ * a roman numeral followed by ) or : ? */
+ if (is_roman(chr->c))
+ {
+ int cn = char_num;
+ for (cn = char_num+1; cn < span->len; cn++)
+ {
+ fz_text_char *chr2 = &span->text[cn];
+
+ if (!is_roman(chr2->c) && !is_unicode_wspace(chr2->c))
+ break;
+ else if (chr2->c == ')' || chr2->c == ':')
+ {
+ cn = span->len;
+ break;
+ }
+ }
+ if (cn == span->len)
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+typedef struct region_masks_s region_masks;
+
+typedef struct region_mask_s region_mask;
+
+typedef struct region_s region;
+
+struct region_s
+{
+ float start;
+ float stop;
+ float ave_start;
+ float ave_stop;
+ int align;
+ float colw;
+};
+
+struct region_mask_s
+{
+ fz_context *ctx;
+ int freq;
+ fz_point blv;
+ int cap;
+ int len;
+ float size;
+ region *mask;
+};
+
+struct region_masks_s
+{
+ fz_context *ctx;
+ int cap;
+ int len;
+ region_mask **mask;
+};
+
+static region_masks *
+new_region_masks(fz_context *ctx)
+{
+ region_masks *rms = fz_malloc_struct(ctx, region_masks);
+ rms->ctx = ctx;
+ rms->cap = 0;
+ rms->len = 0;
+ rms->mask = NULL;
+ return rms;
+}
+
+static void
+free_region_mask(region_mask *rm)
+{
+ if (!rm)
+ return;
+ fz_free(rm->ctx, rm->mask);
+ fz_free(rm->ctx, rm);
+}
+
+static void
+free_region_masks(region_masks *rms)
+{
+ int i;
+
+ if (!rms)
+ return;
+ for (i=0; i < rms->len; i++)
+ {
+ free_region_mask(rms->mask[i]);
+ }
+ fz_free(rms->ctx, rms->mask);
+ fz_free(rms->ctx, rms);
+}
+
+static int region_masks_mergeable(const region_mask *rm1, const region_mask *rm2, float *score)
+{
+ int i1, i2;
+ int count = 0;
+
+ *score = 0;
+ if (fabsf(rm1->blv.x-rm2->blv.x) >= MY_EPSILON || fabsf(rm1->blv.y-rm2->blv.y) >= MY_EPSILON)
+ return 0;
+
+ for (i1 = 0, i2 = 0; i1 < rm1->len && i2 < rm2->len; )
+ {
+ if (rm1->mask[i1].stop < rm2->mask[i2].start)
+ {
+ /* rm1's region is entirely before rm2's */
+ *score += rm1->mask[i1].stop - rm1->mask[i1].start;
+ i1++;
+ }
+ else if (rm1->mask[i1].start > rm2->mask[i2].stop)
+ {
+ /* rm2's region is entirely before rm1's */
+ *score += rm2->mask[i2].stop - rm2->mask[i2].start;
+ i2++;
+ }
+ else
+ {
+ float lscore, rscore;
+ if (rm1->mask[i1].start < rm2->mask[i2].start)
+ {
+ if (i2 > 0 && rm2->mask[i2-1].stop >= rm1->mask[i1].start)
+ return 0; /* Not compatible */
+ lscore = rm2->mask[i2].start - rm1->mask[i1].start;
+ }
+ else
+ {
+ if (i1 > 0 && rm1->mask[i1-1].stop >= rm2->mask[i2].start)
+ return 0; /* Not compatible */
+ lscore = rm1->mask[i1].start - rm2->mask[i2].start;
+ }
+ if (rm1->mask[i1].stop > rm2->mask[i2].stop)
+ {
+ if (i2+1 < rm2->len && rm2->mask[i2+1].start <= rm1->mask[i1].stop)
+ return 0; /* Not compatible */
+ rscore = rm1->mask[i1].stop - rm2->mask[i2].stop;
+ }
+ else
+ {
+ if (i1+1 < rm1->len && rm1->mask[i1+1].start <= rm2->mask[i2].stop)
+ return 0; /* Not compatible */
+ rscore = rm2->mask[i2].stop - rm1->mask[i1].stop;
+ }
+ /* In order to allow a region to merge, either the
+ * left, the right, or the centre must agree */
+ if (lscore < 1)
+ {
+ if (rscore < 1)
+ {
+ rscore = 0;
+ }
+ lscore = 0;
+ }
+ else if (rscore < 1)
+ {
+ rscore = 0;
+ }
+ else
+ {
+ /* Neither Left or right agree. Does the centre? */
+ float ave1 = rm1->mask[i1].start + rm1->mask[i1].stop;
+ float ave2 = rm2->mask[i2].start + rm2->mask[i2].stop;
+ if (fabsf(ave1-ave2) > 1)
+ {
+ /* Nothing agrees, so don't merge */
+ return 0;
+ }
+ lscore = 0;
+ rscore = 0;
+ }
+ *score += lscore + rscore;
+ /* These two regions could be merged */
+ i1++;
+ i2++;
+ }
+ count++;
+ }
+ count += rm1->len-i1 + rm2->len-i2;
+ return count;
+}
+
+static int region_mask_matches(const region_mask *rm1, const region_mask *rm2, float *score)
+{
+ int i1, i2;
+ int close = 1;
+
+ *score = 0;
+ if (fabsf(rm1->blv.x-rm2->blv.x) >= MY_EPSILON || fabsf(rm1->blv.y-rm2->blv.y) >= MY_EPSILON)
+ return 0;
+
+ for (i1 = 0, i2 = 0; i1 < rm1->len && i2 < rm2->len; )
+ {
+ if (rm1->mask[i1].stop < rm2->mask[i2].start)
+ {
+ /* rm1's region is entirely before rm2's */
+ *score += rm1->mask[i1].stop - rm1->mask[i1].start;
+ i1++;
+ }
+ else if (rm1->mask[i1].start > rm2->mask[i2].stop)
+ {
+ /* Not compatible */
+ return 0;
+ }
+ else
+ {
+ float lscore, rscore;
+ if (rm1->mask[i1].start > rm2->mask[i2].start)
+ {
+ /* Not compatible */
+ return 0;
+ }
+ if (rm1->mask[i1].stop < rm2->mask[i2].stop)
+ {
+ /* Not compatible */
+ return 0;
+ }
+ lscore = rm2->mask[i2].start - rm1->mask[i1].start;
+ rscore = rm1->mask[i1].stop - rm2->mask[i2].stop;
+ if (lscore < 1)
+ {
+ if (rscore < 1)
+ close++;
+ close++;
+ }
+ else if (rscore < 1)
+ close++;
+ else if (fabsf(lscore - rscore) < 1)
+ {
+ lscore = fabsf(lscore-rscore);
+ rscore = 0;
+ close++;
+ }
+ *score += lscore + rscore;
+ i1++;
+ i2++;
+ }
+ }
+ if (i1 < rm1->len)
+ {
+ /* Still more to go in rm1 */
+ if (rm1->mask[i1].start < rm2->mask[rm2->len-1].stop)
+ return 0;
+ }
+ else if (i2 < rm2->len)
+ {
+ /* Still more to go in rm2 */
+ if (rm2->mask[i2].start < rm1->mask[rm1->len-1].stop)
+ return 0;
+ }
+
+ return close;
+}
+
+static void region_mask_merge(region_mask *rm1, const region_mask *rm2, int newlen)
+{
+ int o, i1, i2;
+
+ /* First, ensure that rm1 is long enough */
+ if (rm1->cap < newlen)
+ {
+ int newcap = rm1->cap ? rm1->cap : 2;
+ do
+ {
+ newcap *= 2;
+ }
+ while (newcap < newlen);
+ rm1->mask = fz_resize_array(rm1->ctx, rm1->mask, newcap, sizeof(*rm1->mask));
+ rm1->cap = newcap;
+ }
+
+ /* Now run backwards along rm1, filling it out with the merged regions */
+ for (o = newlen-1, i1 = rm1->len-1, i2 = rm2->len-1; o >= 0; o--)
+ {
+ /* So we read from i1 and i2 and store in o */
+ if (i1 < 0)
+ {
+ /* Just copy i2 */
+ rm1->mask[o] = rm2->mask[i2];
+ i2--;
+ }
+ else if (i2 < 0)
+ {
+ /* Just copy i1 */
+ rm1->mask[o] = rm1->mask[i1];
+ i1--;
+ }
+ else if (rm1->mask[i1].stop < rm2->mask[i2].start)
+ {
+ /* rm1's region is entirely before rm2's - copy rm2's */
+ rm1->mask[o] = rm2->mask[i2];
+ i2--;
+ }
+ else if (rm2->mask[i2].stop < rm1->mask[i1].start)
+ {
+ /* rm2's region is entirely before rm1's - copy rm1's */
+ rm1->mask[o] = rm1->mask[i1];
+ i1--;
+ }
+ else
+ {
+ /* We must be merging */
+ rm1->mask[o].ave_start = (rm1->mask[i1].start * rm1->freq + rm2->mask[i2].start * rm2->freq)/(rm1->freq + rm2->freq);
+ rm1->mask[o].ave_stop = (rm1->mask[i1].stop * rm1->freq + rm2->mask[i2].stop * rm2->freq)/(rm1->freq + rm2->freq);
+ rm1->mask[o].start = fz_min(rm1->mask[i1].start, rm2->mask[i2].start);
+ rm1->mask[o].stop = fz_max(rm1->mask[i1].stop, rm2->mask[i2].stop);
+ i1--;
+ i2--;
+ }
+ }
+ rm1->freq += rm2->freq;
+ rm1->len = newlen;
+}
+
+static region_mask *region_masks_match(const region_masks *rms, const region_mask *rm, fz_text_line *line, region_mask *prev_match)
+{
+ int i;
+ float best_score = 9999999;
+ float score;
+ int best = -1;
+ int best_count = 0;
+
+ /* If the 'previous match' matches, use it regardless. */
+ if (prev_match && region_mask_matches(prev_match, rm, &score))
+ {
+ return prev_match;
+ }
+
+ /* Run through and find the 'most compatible' region mask. We are
+ * guaranteed that there will always be at least one compatible one!
+ */
+ for (i=0; i < rms->len; i++)
+ {
+ int count = region_mask_matches(rms->mask[i], rm, &score);
+ if (count > best_count || (count == best_count && (score < best_score || best == -1)))
+ {
+ best = i;
+ best_score = score;
+ best_count = count;
+ }
+ }
+ assert(best >= 0 && best < rms->len);
+
+ /* So we have the matching mask. */
+ return rms->mask[best];
+}
+
+#ifdef DEBUG_MASKS
+static void
+dump_region_mask(const region_mask *rm)
+{
+ int j;
+ for (j = 0; j < rm->len; j++)
+ {
+ printf("%g->%g ", rm->mask[j].start, rm->mask[j].stop);
+ }
+ printf("* %d\n", rm->freq);
+}
+
+static void
+dump_region_masks(const region_masks *rms)
+{
+ int i;
+
+ for (i = 0; i < rms->len; i++)
+ {
+ region_mask *rm = rms->mask[i];
+ dump_region_mask(rm);
+ }
+}
+#endif
+
+static void region_masks_add(region_masks *rms, region_mask *rm)
+{
+ /* Add rm to rms */
+ if (rms->len == rms->cap)
+ {
+ int newcap = (rms->cap ? rms->cap * 2 : 4);
+ rms->mask = fz_resize_array(rms->ctx, rms->mask, newcap, sizeof(*rms->mask));
+ rms->cap = newcap;
+ }
+ rms->mask[rms->len] = rm;
+ rms->len++;
+}
+
+static void region_masks_sort(region_masks *rms)
+{
+ int i, j;
+
+ /* First calculate sizes */
+ for (i=0; i < rms->len; i++)
+ {
+ region_mask *rm = rms->mask[i];
+ float size = 0;
+ for (j=0; j < rm->len; j++)
+ {
+ size += rm->mask[j].stop - rm->mask[j].start;
+ }
+ rm->size = size;
+ }
+
+ /* Now, sort on size */
+ /* FIXME: bubble sort - use heapsort for efficiency */
+ for (i=0; i < rms->len-1; i++)
+ {
+ for (j=i+1; j < rms->len; j++)
+ {
+ if (rms->mask[i]->size < rms->mask[j]->size)
+ {
+ region_mask *tmp = rms->mask[i];
+ rms->mask[i] = rms->mask[j];
+ rms->mask[j] = tmp;
+ }
+ }
+ }
+}
+
+static void region_masks_merge(region_masks *rms, region_mask *rm)
+{
+ int i;
+ float best_score = 9999999;
+ float score;
+ int best = -1;
+ int best_count = 0;
+
+#ifdef DEBUG_MASKS
+ printf("\nAdding:\n");
+ dump_region_mask(rm);
+ printf("To:\n");
+ dump_region_masks(rms);
+#endif
+ for (i=0; i < rms->len; i++)
+ {
+ int count = region_masks_mergeable(rms->mask[i], rm, &score);
+ if (count && (score < best_score || best == -1))
+ {
+ best = i;
+ best_count = count;
+ best_score = score;
+ }
+ }
+ if (best != -1)
+ {
+ region_mask_merge(rms->mask[best], rm, best_count);
+#ifdef DEBUG_MASKS
+ printf("Merges to give:\n");
+ dump_region_masks(rms);
+#endif
+ free_region_mask(rm);
+ return;
+ }
+ region_masks_add(rms, rm);
+#ifdef DEBUG_MASKS
+ printf("Adding new one to give:\n");
+ dump_region_masks(rms);
+#endif
+}
+
+static region_mask *
+new_region_mask(fz_context *ctx, const fz_point *blv)
+{
+ region_mask *rm = fz_malloc_struct(ctx, region_mask);
+ rm->ctx = ctx;
+ rm->freq = 1;
+ rm->blv = *blv;
+ rm->cap = 0;
+ rm->len = 0;
+ rm->mask = NULL;
+ return rm;
+}
+
+static void
+region_mask_project(const region_mask *rm, const fz_point *min, const fz_point *max, float *start, float *end)
+{
+ /* We project min and max down onto the blv */
+ float s = min->x * rm->blv.x + min->y * rm->blv.y;
+ float e = max->x * rm->blv.x + max->y * rm->blv.y;
+ if (s > e)
+ {
+ *start = e;
+ *end = s;
+ }
+ else
+ {
+ *start = s;
+ *end = e;
+ }
+}
+
+static void
+region_mask_add(region_mask *rm, const fz_point *min, const fz_point *max)
+{
+ float start, end;
+ int i, j;
+
+ region_mask_project(rm, min, max, &start, &end);
+
+ /* Now add start/end into our region list. Typically we will be adding
+ * to the end of the region list, so search from there backwards. */
+ for (i = rm->len; i > 0;)
+ {
+ if (start > rm->mask[i-1].stop)
+ break;
+ i--;
+ }
+ /* So we know that our interval can only affect list items >= i.
+ * We know that start is after our previous end. */
+ if (i == rm->len || end < rm->mask[i].start)
+ {
+ /* Insert new one. No overlap. No merging */
+ if (rm->len == rm->cap)
+ {
+ int newcap = (rm->cap ? rm->cap * 2 : 4);
+ rm->mask = fz_resize_array(rm->ctx, rm->mask, newcap, sizeof(*rm->mask));
+ rm->cap = newcap;
+ }
+ if (rm->len > i)
+ memmove(&rm->mask[i+1], &rm->mask[i], (rm->len - i) * sizeof(*rm->mask));
+ rm->mask[i].ave_start = start;
+ rm->mask[i].ave_stop = end;
+ rm->mask[i].start = start;
+ rm->mask[i].stop = end;
+ rm->len++;
+ }
+ else
+ {
+ /* Extend current one down. */
+ rm->mask[i].ave_start = start;
+ rm->mask[i].start = start;
+ if (rm->mask[i].stop < end)
+ {
+ rm->mask[i].stop = end;
+ rm->mask[i].ave_stop = end;
+ /* Our region may now extend upwards too far */
+ i++;
+ j = i;
+ while (j < rm->len && rm->mask[j].start <= end)
+ {
+ rm->mask[i-1].stop = end = rm->mask[j].stop;
+ j++;
+ }
+ if (i != j)
+ {
+ /* Move everything from j down to i */
+ while (j < rm->len)
+ {
+ rm->mask[i++] = rm->mask[j++];
+ }
+ }
+ rm->len -= j-i;
+ }
+ }
+}
+
+static int
+region_mask_column(region_mask *rm, const fz_point *min, const fz_point *max, int *align, float *colw, float *left_)
+{
+ float start, end, left, right;
+ int i;
+
+ region_mask_project(rm, min, max, &start, &end);
+
+ for (i = 0; i < rm->len; i++)
+ {
+ /* The use of MY_EPSILON here is because we might be matching
+ * start/end values calculated with slightly different blv's */
+ if (rm->mask[i].start - MY_EPSILON <= start && rm->mask[i].stop + MY_EPSILON >= end)
+ break;
+ }
+ if (i >= rm->len)
+ {
+ *align = 0;
+ *colw = 0;
+ return 0;
+ }
+ left = start - rm->mask[i].start;
+ right = rm->mask[i].stop - end;
+ if (left < 1 && right < 1)
+ *align = rm->mask[i].align;
+ else if (left*2 <= right)
+ *align = 0; /* Left */
+ else if (right * 2 < left)
+ *align = 2; /* Right */
+ else
+ *align = 1;
+ *left_ = left;
+ *colw = rm->mask[i].colw;
+ return i;
+}
+
+static void
+region_mask_alignment(region_mask *rm)
+{
+ int i;
+ float width = 0;
+
+ for (i = 0; i < rm->len; i++)
+ {
+ width += rm->mask[i].stop - rm->mask[i].start;
+ }
+ for (i = 0; i < rm->len; i++)
+ {
+ region *r = &rm->mask[i];
+ float left = r->ave_start - r->start;
+ float right = r->stop - r->ave_stop;
+ if (left*2 <= right)
+ r->align = 0; /* Left */
+ else if (right * 2 < left)
+ r->align = 2; /* Right */
+ else
+ r->align = 1;
+ r->colw = 100 * (rm->mask[i].stop - rm->mask[i].start) / width;
+ }
+}
+
+static void
+region_masks_alignment(region_masks *rms)
+{
+ int i;
+
+ for (i = 0; i < rms->len; i++)
+ {
+ region_mask_alignment(rms->mask[i]);
+ }
+}
+
+static int
+is_unicode_hyphen(int c)
+{
+ /* We omit 0x2011 (Non breaking hyphen) and 0x2043 (Hyphen Bullet)
+ * from this list. */
+ return (c == '-' ||
+ c == 0x2010 || /* Hyphen */
+ c == 0x002d || /* Hyphen-Minus */
+ c == 0x00ad || /* Soft hyphen */
+ c == 0x058a || /* Armenian Hyphen */
+ c == 0x1400 || /* Canadian Syllabive Hyphen */
+ c == 0x1806); /* Mongolian Todo soft hyphen */
+}
+
+static int
+is_unicode_hyphenatable(int c)
+{
+ /* This is a pretty ad-hoc collection. It may need tuning. */
+ return ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 0x00c0 && c <= 0x00d6) ||
+ (c >= 0x00d8 && c <= 0x00f6) ||
+ (c >= 0x00f8 && c <= 0x02af) ||
+ (c >= 0x1d00 && c <= 0x1dbf) ||
+ (c >= 0x1e00 && c <= 0x1eff) ||
+ (c >= 0x2c60 && c <= 0x2c7f) ||
+ (c >= 0xa722 && c <= 0xa78e) ||
+ (c >= 0xa790 && c <= 0xa793) ||
+ (c >= 0xa7a8 && c <= 0xa7af) ||
+ (c >= 0xfb00 && c <= 0xfb07) ||
+ (c >= 0xff21 && c <= 0xff3a) ||
+ (c >= 0xff41 && c <= 0xff5a));
+}
+
+static void
+dehyphenate(fz_text_span *s1, fz_text_span *s2)
+{
+ int i;
+
+ for (i = s1->len-1; i > 0; i--)
+ if (!is_unicode_wspace(s1->text[i].c))
+ break;
+ /* Can't leave an empty span. */
+ if (i == 0)
+ return;
+
+ if (!is_unicode_hyphen(s1->text[i].c))
+ return;
+ if (!is_unicode_hyphenatable(s1->text[i-1].c))
+ return;
+ if (!is_unicode_hyphenatable(s2->text[0].c))
+ return;
+ s1->len = i;
+ s2->spacing = 0;
+}
+
+void
+fz_analyze_text(fz_context *ctx, fz_text_sheet *sheet, fz_text_page *page)
+{
+ fz_text_line *line;
+ fz_text_span *span;
+ line_heights *lh;
+ region_masks *rms;
+ int block_num;
+
+ /* Simple paragraph analysis; look for the most common 'inter line'
+ * spacing. This will be assumed to be our line spacing. Anything
+ * more than 25% wider than this will be assumed to be a paragraph
+ * space. */
+
+ /* Step 1: Gather the line height information */
+ lh = new_line_heights(ctx);
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ fz_text_block *block;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ /* For every style in the line, add lineheight to the
+ * record for that style. FIXME: This is a nasty n^2
+ * algorithm at the moment. */
+ fz_text_style *style = NULL;
+
+ if (line->distance == 0)
+ continue;
+
+ for (span = line->first_span; span; span = span->next)
+ {
+ int char_num;
+
+ if (is_list_entry(line, span, &char_num))
+ goto list_entry;
+
+ for (; char_num < span->len; char_num++)
+ {
+ fz_text_char *chr = &span->text[char_num];
+
+ /* Ignore any whitespace chars */
+ if (is_unicode_wspace(chr->c))
+ continue;
+
+ if (chr->style != style)
+ {
+ /* Have we had this style before? */
+ int match = 0;
+ fz_text_span *span2;
+ for (span2 = line->first_span; span2; span2 = span2->next)
+ {
+ int char_num2;
+ for (char_num2 = 0; char_num2 < span2->len; char_num2++)
+ {
+ fz_text_char *chr2 = &span2->text[char_num2];
+ if (chr2->style == chr->style)
+ {
+ match = 1;
+ break;
+ }
+ }
+ }
+ if (char_num > 0 && match == 0)
+ {
+ fz_text_span *span2 = span;
+ int char_num2;
+ for (char_num2 = 0; char_num2 < char_num; char_num2++)
+ {
+ fz_text_char *chr2 = &span2->text[char_num2];
+ if (chr2->style == chr->style)
+ {
+ match = 1;
+ break;
+ }
+ }
+ }
+ if (match == 0)
+ insert_line_height(lh, chr->style, line->distance);
+ style = chr->style;
+ }
+ }
+list_entry:
+ {}
+ }
+ }
+ }
+
+ /* Step 2: Find the most popular line height for each style */
+ cull_line_heights(lh);
+
+ /* Step 3: Run through the blocks, breaking each block into two if
+ * the line height isn't right. */
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ int line_num;
+ fz_text_block *block;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+
+ for (line_num = 0; line_num < block->len; line_num++)
+ {
+ /* For every style in the line, check to see if lineheight
+ * is correct for that style. FIXME: We check each style
+ * more than once, currently. */
+ int ok = 0; /* -1 = early exit, split now. 0 = split. 1 = don't split. */
+ fz_text_style *style = NULL;
+ line = &block->lines[line_num];
+
+ if (line->distance == 0)
+ continue;
+
+#ifdef DEBUG_LINE_HEIGHTS
+ printf("line height=%g nspans=%d\n", line->distance, line->len);
+#endif
+ for (span = line->first_span; span; span = span->next)
+ {
+ int char_num;
+
+ if (is_list_entry(line, span, &char_num))
+ goto force_paragraph;
+
+ /* Now we do the rest of the line */
+ for (; char_num < span->len; char_num++)
+ {
+ fz_text_char *chr = &span->text[char_num];
+
+ /* Ignore any whitespace chars */
+ if (is_unicode_wspace(chr->c))
+ continue;
+
+ if (chr->style != style)
+ {
+ float proper_step = line_height_for_style(lh, chr->style);
+ if (proper_step * 0.95 <= line->distance && line->distance <= proper_step * 1.05)
+ {
+ ok = 1;
+ break;
+ }
+ style = chr->style;
+ }
+ }
+ if (ok)
+ break;
+ }
+ if (!ok)
+ {
+force_paragraph:
+ split_block(ctx, page, block_num, line_num);
+ break;
+ }
+ }
+ }
+ free_line_heights(lh);
+
+ /* Simple line region analysis:
+ * For each line:
+ * form a list of 'start/stop' points (henceforth a 'region mask')
+ * find the normalised baseline vector for the line.
+ * Store the region mask and baseline vector.
+ * Collate lines that have compatible region masks and identical
+ * baseline vectors.
+ * If the collated masks are column-like, then split into columns.
+ * Otherwise split into tables.
+ */
+ rms = new_region_masks(ctx);
+
+ /* Step 1: Form the region masks and store them into a list with the
+ * normalised baseline vectors. */
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ fz_text_block *block;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ fz_point blv;
+ region_mask *rm;
+
+#ifdef DEBUG_MASKS
+ printf("Line: ");
+ dump_line(line);
+#endif
+ blv = line->first_span->max;
+ blv.x -= line->first_span->min.x;
+ blv.y -= line->first_span->min.y;
+ fz_normalize_vector(&blv);
+
+ rm = new_region_mask(ctx, &blv);
+ for (span = line->first_span; span; span = span->next)
+ {
+ fz_point *region_min = &span->min;
+ fz_point *region_max = &span->max;
+
+ /* Treat adjacent spans as one big region */
+ while (span->next && span->next->spacing < 1.5)
+ {
+ span = span->next;
+ region_max = &span->max;
+ }
+
+ region_mask_add(rm, region_min, region_max);
+ }
+#ifdef DEBUG_MASKS
+ dump_region_mask(rm);
+#endif
+ region_masks_add(rms, rm);
+ }
+ }
+
+ /* Step 2: Sort the region_masks by size of masked region */
+ region_masks_sort(rms);
+
+#ifdef DEBUG_MASKS
+ printf("Sorted list of regions:\n");
+ dump_region_masks(rms);
+#endif
+ /* Step 3: Merge the region masks where possible (large ones first) */
+ {
+ int i;
+ region_masks *rms2;
+ rms2 = new_region_masks(ctx);
+ for (i=0; i < rms->len; i++)
+ {
+ region_mask *rm = rms->mask[i];
+ rms->mask[i] = NULL;
+ region_masks_merge(rms2, rm);
+ }
+ free_region_masks(rms);
+ rms = rms2;
+ }
+
+#ifdef DEBUG_MASKS
+ printf("Merged list of regions:\n");
+ dump_region_masks(rms);
+#endif
+
+ /* Step 4: Figure out alignment */
+ region_masks_alignment(rms);
+
+ /* Step 5: At this point, we should probably look at the region masks
+ * to try to guess which ones represent columns on the page. With our
+ * current code, we could only get blocks of lines that span 2 or more
+ * columns if the PDF producer wrote text out horizontally across 2
+ * or more columns, and we've never seen that (yet!). So we skip this
+ * step for now. */
+
+ /* Step 6: Run through the lines again, deciding which ones fit into
+ * which region mask. */
+ {
+ region_mask *prev_match = NULL;
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ fz_text_block *block;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ fz_point blv;
+ region_mask *rm;
+ region_mask *match;
+
+ blv = line->first_span->max;
+ blv.x -= line->first_span->min.x;
+ blv.y -= line->first_span->min.y;
+ fz_normalize_vector(&blv);
+
+#ifdef DEBUG_MASKS
+ dump_line(line);
+#endif
+ rm = new_region_mask(ctx, &blv);
+ for (span = line->first_span; span; span = span->next)
+ {
+ fz_point *region_min = &span->min;
+ fz_point *region_max = &span->max;
+
+ /* Treat adjacent spans as one big region */
+ while (span->next && span->next->spacing < 1.5)
+ {
+ span = span->next;
+ region_max = &span->max;
+ }
+
+ region_mask_add(rm, region_min, region_max);
+ }
+#ifdef DEBUG_MASKS
+ printf("Mask: ");
+ dump_region_mask(rm);
+#endif
+ match = region_masks_match(rms, rm, line, prev_match);
+ prev_match = match;
+#ifdef DEBUG_MASKS
+ printf("Matches: ");
+ dump_region_mask(match);
+#endif
+ free_region_mask(rm);
+ span = line->first_span;
+ while (span)
+ {
+ fz_point *region_min = &span->min;
+ fz_point *region_max = &span->max;
+ fz_text_span *sn;
+ int col, align;
+ float colw, left;
+
+ /* Treat adjacent spans as one big region */
+#ifdef DEBUG_ALIGN
+ dump_span(span);
+#endif
+ for (sn = span->next; sn && sn->spacing < 1.5; sn = sn->next)
+ {
+ region_max = &sn->max;
+#ifdef DEBUG_ALIGN
+ dump_span(sn);
+#endif
+ }
+ col = region_mask_column(match, region_min, region_max, &align, &colw, &left);
+#ifdef DEBUG_ALIGN
+ printf(" = col%d colw=%g align=%d\n", col, colw, align);
+#endif
+ do
+ {
+ span->column = col;
+ span->align = align;
+ span->indent = left;
+ span->column_width = colw;
+ span = span->next;
+ }
+ while (span != sn);
+
+ if (span)
+ span = span->next;
+ }
+ line->region = match;
+ }
+ }
+ free_region_masks(rms);
+ }
+
+ /* Step 7: Collate lines within a block that share the same region
+ * mask. */
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ int line_num;
+ int prev_line_num;
+
+ fz_text_block *block;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+
+ /* First merge lines. This may leave empty lines behind. */
+ for (prev_line_num = 0, line_num = 1; line_num < block->len; line_num++)
+ {
+ fz_text_line *prev_line;
+ line = &block->lines[line_num];
+ if (!line->first_span)
+ continue;
+ prev_line = &block->lines[prev_line_num];
+ if (prev_line->region == line->region)
+ {
+ /* We only merge lines if the second line
+ * only uses 1 of the columns. */
+ int col = line->first_span->column;
+ /* Copy the left value for the first span
+ * in the first column in this line forward
+ * for all the rest of the spans in the same
+ * column. */
+ float indent = line->first_span->indent;
+ for (span = line->first_span->next; span; span = span->next)
+ {
+ if (col != span->column)
+ break;
+ span->indent = indent;
+ }
+ if (span)
+ {
+ prev_line_num = line_num;
+ continue;
+ }
+
+ /* Merge line into prev_line */
+ {
+ fz_text_span **prev_line_span = &prev_line->first_span;
+ int try_dehyphen = -1;
+ fz_text_span *prev_span = NULL;
+ span = line->first_span;
+ while (span)
+ {
+ /* Skip forwards through the original
+ * line, until we find a place where
+ * span should go. */
+ if ((*prev_line_span)->column <= span->column)
+ {
+ /* The current span we are considering
+ * in prev_line is earlier than span.
+ * Just skip forwards in prev_line. */
+ prev_span = (*prev_line_span);
+ prev_line_span = &prev_span->next;
+ try_dehyphen = span->column;
+ }
+ else
+ {
+ /* We want to copy span into prev_line. */
+ fz_text_span *next = (*prev_line_span)->next;
+
+ if (prev_line_span == &prev_line->first_span)
+ prev_line->first_span = span;
+ if (next == NULL)
+ prev_line->last_span = span;
+ if (try_dehyphen == span->column)
+ dehyphenate(prev_span, span);
+ try_dehyphen = -1;
+ prev_span = *prev_line_span = span;
+ span = span->next;
+ (*prev_line_span)->next = next;
+ prev_line_span = &span->next;
+ }
+ }
+ while (span || *prev_line_span);
+ line->first_span = NULL;
+ line->last_span = NULL;
+ }
+ }
+ else
+ prev_line_num = line_num;
+ }
+
+ /* Now get rid of the empty lines */
+ for (prev_line_num = 0, line_num = 0; line_num < block->len; line_num++)
+ {
+ line = &block->lines[line_num];
+ if (line->first_span)
+ block->lines[prev_line_num++] = *line;
+ }
+ block->len = prev_line_num;
+
+ /* Now try to spot indents */
+ for (line_num = 0; line_num < block->len; line_num++)
+ {
+ fz_text_span *span_num, *sn;
+ int col, count;
+ line = &block->lines[line_num];
+
+ /* Run through the spans... */
+ span_num = line->first_span;
+ {
+ float indent = 0;
+ /* For each set of spans that share the same
+ * column... */
+ col = span_num->column;
+#ifdef DEBUG_INDENTS
+ printf("Indent %g: ", span_num->indent);
+ dump_span(span_num);
+ printf("\n");
+#endif
+
+ /* find the average indent of all but the first.. */
+ for (sn = span_num->next, count = 0; sn && sn->column == col; sn = sn->next, count++)
+ {
+#ifdef DEBUG_INDENTS
+ printf("Indent %g: ", sn->indent);
+ dump_span(sn);
+ printf("\n");
+#endif
+ indent += sn->indent;
+ sn->indent = 0;
+ }
+ if (sn != span_num->next)
+ indent /= count;
+
+ /* And compare this indent with the first one... */
+#ifdef DEBUG_INDENTS
+ printf("Average indent %g ", indent);
+#endif
+ indent -= span_num->indent;
+#ifdef DEBUG_INDENTS
+ printf("delta %g ", indent);
+#endif
+ if (fabsf(indent) < 1)
+ {
+ /* No indent worth speaking of */
+ indent = 0;
+ }
+#ifdef DEBUG_INDENTS
+ printf("recorded %g\n", indent);
+#endif
+ span_num->indent = indent;
+ span_num = sn;
+ }
+ for (; span_num; span_num = span_num->next)
+ {
+ span_num->indent = 0;
+ }
+ }
+ }
+}
diff --git a/source/fitz/stext-search.c b/source/fitz/stext-search.c
new file mode 100644
index 00000000..f1f0d203
--- /dev/null
+++ b/source/fitz/stext-search.c
@@ -0,0 +1,279 @@
+#include "mupdf/fitz.h"
+
+static inline int fz_tolower(int c)
+{
+ /* TODO: proper unicode case folding */
+ if (c >= 'A' && c <= 'Z')
+ return c - 'A' + 'a';
+ return c;
+}
+
+static inline int iswhite(int c)
+{
+ return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+}
+
+fz_char_and_box *fz_text_char_at(fz_char_and_box *cab, fz_text_page *page, int idx)
+{
+ int block_num;
+ int ofs = 0;
+
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ fz_text_block *block;
+ fz_text_line *line;
+ fz_text_span *span;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ for (span = line->first_span; span; span = span->next)
+ {
+ if (idx < ofs + span->len)
+ {
+ cab->c = span->text[idx - ofs].c;
+ fz_text_char_bbox(&cab->bbox, span, idx - ofs);
+ return cab;
+ }
+ ofs += span->len;
+ }
+ /* pseudo-newline */
+ if (idx == ofs)
+ {
+ cab->bbox = fz_empty_rect;
+ cab->c = ' ';
+ return cab;
+ }
+ ofs++;
+ }
+ }
+ cab->bbox = fz_empty_rect;
+ cab->c = 0;
+ return cab;
+}
+
+static int charat(fz_text_page *page, int idx)
+{
+ fz_char_and_box cab;
+ return fz_text_char_at(&cab, page, idx)->c;
+}
+
+static fz_rect *bboxat(fz_text_page *page, int idx, fz_rect *bbox)
+{
+ fz_char_and_box cab;
+ /* FIXME: Nasty extra copy */
+ *bbox = fz_text_char_at(&cab, page, idx)->bbox;
+ return bbox;
+}
+
+static int textlen(fz_text_page *page)
+{
+ int len = 0;
+ int block_num;
+
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ fz_text_block *block;
+ fz_text_line *line;
+ fz_text_span *span;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ for (span = line->first_span; span; span = span->next)
+ {
+ len += span->len;
+ }
+ len++; /* pseudo-newline */
+ }
+ }
+ return len;
+}
+
+static int match(fz_text_page *page, const char *s, int n)
+{
+ int orig = n;
+ int c;
+ while (*s)
+ {
+ s += fz_chartorune(&c, (char *)s);
+ if (iswhite(c) && iswhite(charat(page, n)))
+ {
+ const char *s_next;
+
+ /* Skip over whitespace in the document */
+ do
+ n++;
+ while (iswhite(charat(page, n)));
+
+ /* Skip over multiple whitespace in the search string */
+ while (s_next = s + fz_chartorune(&c, (char *)s), iswhite(c))
+ s = s_next;
+ }
+ else
+ {
+ if (fz_tolower(c) != fz_tolower(charat(page, n)))
+ return 0;
+ n++;
+ }
+ }
+ return n - orig;
+}
+
+int
+fz_search_text_page(fz_context *ctx, fz_text_page *text, const char *needle, fz_rect *hit_bbox, int hit_max)
+{
+ int pos, len, i, n, hit_count;
+
+ if (strlen(needle) == 0)
+ return 0;
+
+ hit_count = 0;
+ len = textlen(text);
+ for (pos = 0; pos < len; pos++)
+ {
+ n = match(text, needle, pos);
+ if (n)
+ {
+ fz_rect linebox = fz_empty_rect;
+ for (i = 0; i < n; i++)
+ {
+ fz_rect charbox;
+ bboxat(text, pos + i, &charbox);
+ if (!fz_is_empty_rect(&charbox))
+ {
+ if (charbox.y0 != linebox.y0 || fz_abs(charbox.x0 - linebox.x1) > 5)
+ {
+ if (!fz_is_empty_rect(&linebox) && hit_count < hit_max)
+ hit_bbox[hit_count++] = linebox;
+ linebox = charbox;
+ }
+ else
+ {
+ fz_union_rect(&linebox, &charbox);
+ }
+ }
+ }
+ if (!fz_is_empty_rect(&linebox) && hit_count < hit_max)
+ hit_bbox[hit_count++] = linebox;
+ }
+ }
+
+ return hit_count;
+}
+
+int
+fz_highlight_selection(fz_context *ctx, fz_text_page *page, fz_rect rect, fz_rect *hit_bbox, int hit_max)
+{
+ fz_rect linebox, charbox;
+ fz_text_block *block;
+ fz_text_line *line;
+ fz_text_span *span;
+ int i, block_num, hit_count;
+
+ float x0 = rect.x0;
+ float x1 = rect.x1;
+ float y0 = rect.y0;
+ float y1 = rect.y1;
+
+ hit_count = 0;
+
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ linebox = fz_empty_rect;
+ for (span = line->first_span; span; span = span->next)
+ {
+ for (i = 0; i < span->len; i++)
+ {
+ fz_text_char_bbox(&charbox, span, i);
+ if (charbox.x1 >= x0 && charbox.x0 <= x1 && charbox.y1 >= y0 && charbox.y0 <= y1)
+ {
+ if (charbox.y0 != linebox.y0 || fz_abs(charbox.x0 - linebox.x1) > 5)
+ {
+ if (!fz_is_empty_rect(&linebox) && hit_count < hit_max)
+ hit_bbox[hit_count++] = linebox;
+ linebox = charbox;
+ }
+ else
+ {
+ fz_union_rect(&linebox, &charbox);
+ }
+ }
+ }
+ }
+ if (!fz_is_empty_rect(&linebox) && hit_count < hit_max)
+ hit_bbox[hit_count++] = linebox;
+ }
+ }
+
+ return hit_count;
+}
+
+char *
+fz_copy_selection(fz_context *ctx, fz_text_page *page, fz_rect rect)
+{
+ fz_buffer *buffer;
+ fz_rect hitbox;
+ int c, i, block_num, seen = 0;
+ char *s;
+
+ float x0 = rect.x0;
+ float x1 = rect.x1;
+ float y0 = rect.y0;
+ float y1 = rect.y1;
+
+ buffer = fz_new_buffer(ctx, 1024);
+
+ for (block_num = 0; block_num < page->len; block_num++)
+ {
+ fz_text_block *block;
+ fz_text_line *line;
+ fz_text_span *span;
+
+ if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
+ continue;
+ block = page->blocks[block_num].u.text;
+ for (line = block->lines; line < block->lines + block->len; line++)
+ {
+ for (span = line->first_span; span; span = span->next)
+ {
+ if (seen)
+ {
+ fz_write_buffer_byte(ctx, buffer, '\n');
+ }
+
+ seen = 0;
+
+ for (i = 0; i < span->len; i++)
+ {
+ fz_text_char_bbox(&hitbox, span, i);
+ c = span->text[i].c;
+ if (c < 32)
+ c = '?';
+ if (hitbox.x1 >= x0 && hitbox.x0 <= x1 && hitbox.y1 >= y0 && hitbox.y0 <= y1)
+ {
+ fz_write_buffer_rune(ctx, buffer, c);
+ seen = 1;
+ }
+ }
+
+ seen = (seen && span == line->last_span);
+ }
+ }
+ }
+
+ fz_write_buffer_byte(ctx, buffer, 0);
+
+ s = (char*)buffer->data;
+ fz_free(ctx, buffer);
+ return s;
+}
diff --git a/source/fitz/store.c b/source/fitz/store.c
new file mode 100644
index 00000000..609f10dc
--- /dev/null
+++ b/source/fitz/store.c
@@ -0,0 +1,638 @@
+#include "mupdf/fitz.h"
+
+typedef struct fz_item_s fz_item;
+
+struct fz_item_s
+{
+ void *key;
+ fz_storable *val;
+ unsigned int size;
+ fz_item *next;
+ fz_item *prev;
+ fz_store *store;
+ fz_store_type *type;
+};
+
+struct fz_store_s
+{
+ int refs;
+
+ /* Every item in the store is kept in a doubly linked list, ordered
+ * by usage (so LRU entries are at the end). */
+ fz_item *head;
+ fz_item *tail;
+
+ /* We have a hash table that allows to quickly find a subset of the
+ * entries (those whose keys are indirect objects). */
+ fz_hash_table *hash;
+
+ /* We keep track of the size of the store, and keep it below max. */
+ unsigned int max;
+ unsigned int size;
+};
+
+void
+fz_new_store_context(fz_context *ctx, unsigned int max)
+{
+ fz_store *store;
+ store = fz_malloc_struct(ctx, fz_store);
+ fz_try(ctx)
+ {
+ store->hash = fz_new_hash_table(ctx, 4096, sizeof(fz_store_hash), FZ_LOCK_ALLOC);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, store);
+ fz_rethrow(ctx);
+ }
+ store->refs = 1;
+ store->head = NULL;
+ store->tail = NULL;
+ store->size = 0;
+ store->max = max;
+ ctx->store = store;
+}
+
+void *
+fz_keep_storable(fz_context *ctx, fz_storable *s)
+{
+ if (s == NULL)
+ return NULL;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (s->refs > 0)
+ s->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return s;
+}
+
+void
+fz_drop_storable(fz_context *ctx, fz_storable *s)
+{
+ int do_free = 0;
+
+ if (s == NULL)
+ return;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (s->refs < 0)
+ {
+ /* It's a static object. Dropping does nothing. */
+ }
+ else if (--s->refs == 0)
+ {
+ /* If we are dropping the last reference to an object, then
+ * it cannot possibly be in the store (as the store always
+ * keeps a ref to everything in it, and doesn't drop via
+ * this method. So we can simply drop the storable object
+ * itself without any operations on the fz_store. */
+ do_free = 1;
+ }
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (do_free)
+ s->free(ctx, s);
+}
+
+static void
+evict(fz_context *ctx, fz_item *item)
+{
+ fz_store *store = ctx->store;
+ int drop;
+
+ store->size -= item->size;
+ /* Unlink from the linked list */
+ if (item->next)
+ item->next->prev = item->prev;
+ else
+ store->tail = item->prev;
+ if (item->prev)
+ item->prev->next = item->next;
+ else
+ store->head = item->next;
+ /* Drop a reference to the value (freeing if required) */
+ drop = (item->val->refs > 0 && --item->val->refs == 0);
+ /* Remove from the hash table */
+ if (item->type->make_hash_key)
+ {
+ fz_store_hash hash = { NULL };
+ hash.free = item->val->free;
+ if (item->type->make_hash_key(&hash, item->key))
+ fz_hash_remove(ctx, store->hash, &hash);
+ }
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop)
+ item->val->free(ctx, item->val);
+ /* Always drops the key and free the item */
+ item->type->drop_key(ctx, item->key);
+ fz_free(ctx, item);
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+}
+
+static int
+ensure_space(fz_context *ctx, unsigned int tofree)
+{
+ fz_item *item, *prev;
+ unsigned int count;
+ fz_store *store = ctx->store;
+
+ fz_assert_lock_held(ctx, FZ_LOCK_ALLOC);
+
+ /* First check that we *can* free tofree; if not, we'd rather not
+ * cache this. */
+ count = 0;
+ for (item = store->tail; item; item = item->prev)
+ {
+ if (item->val->refs == 1)
+ {
+ count += item->size;
+ if (count >= tofree)
+ break;
+ }
+ }
+
+ /* If we ran out of items to search, then we can never free enough */
+ if (item == NULL)
+ {
+ return 0;
+ }
+
+ /* Actually free the items */
+ count = 0;
+ for (item = store->tail; item; item = prev)
+ {
+ prev = item->prev;
+ if (item->val->refs == 1)
+ {
+ /* Free this item. Evict has to drop the lock to
+ * manage that, which could cause prev to be removed
+ * in the meantime. To avoid that we bump its reference
+ * count here. This may cause another simultaneous
+ * evict process to fail to make enough space as prev is
+ * pinned - but that will only happen if we're near to
+ * the limit anyway, and it will only cause something to
+ * not be cached. */
+ count += item->size;
+ if (prev)
+ prev->val->refs++;
+ evict(ctx, item); /* Drops then retakes lock */
+ /* So the store has 1 reference to prev, as do we, so
+ * no other evict process can have thrown prev away in
+ * the meantime. So we are safe to just decrement its
+ * reference count here. */
+ if (prev)
+ --prev->val->refs;
+
+ if (count >= tofree)
+ return count;
+ }
+ }
+
+ return count;
+}
+
+static void
+touch(fz_store *store, fz_item *item)
+{
+ if (item->next != item)
+ {
+ /* Already in the list - unlink it */
+ if (item->next)
+ item->next->prev = item->prev;
+ else
+ store->tail = item->prev;
+ if (item->prev)
+ item->prev->next = item->next;
+ else
+ store->head = item->next;
+ }
+ /* Now relink it at the start of the LRU chain */
+ item->next = store->head;
+ if (item->next)
+ item->next->prev = item;
+ else
+ store->tail = item;
+ store->head = item;
+ item->prev = NULL;
+}
+
+void *
+fz_store_item(fz_context *ctx, void *key, void *val_, unsigned int itemsize, fz_store_type *type)
+{
+ fz_item *item = NULL;
+ unsigned int size;
+ fz_storable *val = (fz_storable *)val_;
+ fz_store *store = ctx->store;
+ fz_store_hash hash = { NULL };
+ int use_hash = 0;
+ unsigned pos;
+
+ if (!store)
+ return NULL;
+
+ fz_var(item);
+
+ if (store->max != FZ_STORE_UNLIMITED && store->max < itemsize)
+ {
+ /* Our item would take up more room than we can ever
+ * possibly have in the store. Just give up now. */
+ return NULL;
+ }
+
+ /* If we fail for any reason, we swallow the exception and continue.
+ * All that the above program will see is that we failed to store
+ * the item. */
+ fz_try(ctx)
+ {
+ item = fz_malloc_struct(ctx, fz_item);
+ }
+ fz_catch(ctx)
+ {
+ return NULL;
+ }
+
+ if (type->make_hash_key)
+ {
+ hash.free = val->free;
+ use_hash = type->make_hash_key(&hash, key);
+ }
+
+ type->keep_key(ctx, key);
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+
+ /* Fill out the item. To start with, we always set item->next == item
+ * and item->prev == item. This is so that we can spot items that have
+ * been put into the hash table without having made it into the linked
+ * list yet. */
+ item->key = key;
+ item->val = val;
+ item->size = itemsize;
+ item->next = item;
+ item->prev = item;
+ item->type = type;
+
+ /* If we can index it fast, put it into the hash table. This serves
+ * to check whether we have one there already. */
+ if (use_hash)
+ {
+ fz_item *existing;
+
+ fz_try(ctx)
+ {
+ /* May drop and retake the lock */
+ existing = fz_hash_insert_with_pos(ctx, store->hash, &hash, item, &pos);
+ }
+ fz_catch(ctx)
+ {
+ /* Any error here means that item never made it into the
+ * hash - so no one else can have a reference. */
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ fz_free(ctx, item);
+ type->drop_key(ctx, key);
+ return NULL;
+ }
+ if (existing)
+ {
+ /* There was one there already! Take a new reference
+ * to the existing one, and drop our current one. */
+ touch(store, existing);
+ if (existing->val->refs > 0)
+ existing->val->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ fz_free(ctx, item);
+ type->drop_key(ctx, key);
+ return existing->val;
+ }
+ }
+ /* Now bump the ref */
+ if (val->refs > 0)
+ val->refs++;
+ /* If we haven't got an infinite store, check for space within it */
+ if (store->max != FZ_STORE_UNLIMITED)
+ {
+ size = store->size + itemsize;
+ while (size > store->max)
+ {
+ /* ensure_space may drop, then retake the lock */
+ int saved = ensure_space(ctx, size - store->max);
+ if (saved == 0)
+ {
+ /* Failed to free any space. */
+ /* If we are using the hash table, then we've already
+ * inserted item - remove it. */
+ if (use_hash)
+ {
+ /* If someone else has already picked up a reference
+ * to item, then we cannot remove it. Leave it in the
+ * store, and we'll live with being over budget. We
+ * know this is the case, if it's in the linked list. */
+ if (item->next != item)
+ break;
+ fz_hash_remove_fast(ctx, store->hash, &hash, pos);
+ }
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ fz_free(ctx, item);
+ type->drop_key(ctx, key);
+ if (val->refs > 0)
+ val->refs--;
+ return NULL;
+ }
+ size -= saved;
+ }
+ }
+ store->size += itemsize;
+
+ /* Regardless of whether it's indexed, it goes into the linked list */
+ touch(store, item);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ return NULL;
+}
+
+void *
+fz_find_item(fz_context *ctx, fz_store_free_fn *free, void *key, fz_store_type *type)
+{
+ fz_item *item;
+ fz_store *store = ctx->store;
+ fz_store_hash hash = { NULL };
+ int use_hash = 0;
+
+ if (!store)
+ return NULL;
+
+ if (!key)
+ return NULL;
+
+ if (type->make_hash_key)
+ {
+ hash.free = free;
+ use_hash = type->make_hash_key(&hash, key);
+ }
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (use_hash)
+ {
+ /* We can find objects keyed on indirected objects quickly */
+ item = fz_hash_find(ctx, store->hash, &hash);
+ }
+ else
+ {
+ /* Others we have to hunt for slowly */
+ for (item = store->head; item; item = item->next)
+ {
+ if (item->val->free == free && !type->cmp_key(item->key, key))
+ break;
+ }
+ }
+ if (item)
+ {
+ /* LRU the block. This also serves to ensure that any item
+ * picked up from the hash before it has made it into the
+ * linked list does not get whipped out again due to the
+ * store being full. */
+ touch(store, item);
+ /* And bump the refcount before returning */
+ if (item->val->refs > 0)
+ item->val->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return (void *)item->val;
+ }
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+
+ return NULL;
+}
+
+void
+fz_remove_item(fz_context *ctx, fz_store_free_fn *free, void *key, fz_store_type *type)
+{
+ fz_item *item;
+ fz_store *store = ctx->store;
+ int drop;
+ fz_store_hash hash = { NULL };
+ int use_hash = 0;
+
+ if (type->make_hash_key)
+ {
+ hash.free = free;
+ use_hash = type->make_hash_key(&hash, key);
+ }
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (use_hash)
+ {
+ /* We can find objects keyed on indirect objects quickly */
+ item = fz_hash_find(ctx, store->hash, &hash);
+ if (item)
+ fz_hash_remove(ctx, store->hash, &hash);
+ }
+ else
+ {
+ /* Others we have to hunt for slowly */
+ for (item = store->head; item; item = item->next)
+ if (item->val->free == free && !type->cmp_key(item->key, key))
+ break;
+ }
+ if (item)
+ {
+ /* Momentarily things can be in the hash table without being
+ * in the list. Don't attempt to unlink these. We indicate
+ * such items by setting item->next == item. */
+ if (item->next != item)
+ {
+ if (item->next)
+ item->next->prev = item->prev;
+ else
+ store->tail = item->prev;
+ if (item->prev)
+ item->prev->next = item->next;
+ else
+ store->head = item->next;
+ }
+ drop = (item->val->refs > 0 && --item->val->refs == 0);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (drop)
+ item->val->free(ctx, item->val);
+ type->drop_key(ctx, item->key);
+ fz_free(ctx, item);
+ }
+ else
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+}
+
+void
+fz_empty_store(fz_context *ctx)
+{
+ fz_store *store = ctx->store;
+
+ if (store == NULL)
+ return;
+
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ /* Run through all the items in the store */
+ while (store->head)
+ {
+ evict(ctx, store->head); /* Drops then retakes lock */
+ }
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+}
+
+fz_store *
+fz_keep_store_context(fz_context *ctx)
+{
+ if (ctx == NULL || ctx->store == NULL)
+ return NULL;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ ctx->store->refs++;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ return ctx->store;
+}
+
+void
+fz_drop_store_context(fz_context *ctx)
+{
+ int refs;
+ if (ctx == NULL || ctx->store == NULL)
+ return;
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ refs = --ctx->store->refs;
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ if (refs != 0)
+ return;
+
+ fz_empty_store(ctx);
+ fz_free_hash(ctx, ctx->store->hash);
+ fz_free(ctx, ctx->store);
+ ctx->store = NULL;
+}
+
+#ifndef NDEBUG
+static void
+print_item(FILE *out, void *item_)
+{
+ fz_item *item = (fz_item *)item_;
+ fprintf(out, " val=%p item=%p\n", item->val, item);
+ fflush(out);
+}
+
+void
+fz_print_store_locked(fz_context *ctx, FILE *out)
+{
+ fz_item *item, *next;
+ fz_store *store = ctx->store;
+
+ fprintf(out, "-- resource store contents --\n");
+ fflush(out);
+
+ for (item = store->head; item; item = next)
+ {
+ next = item->next;
+ if (next)
+ next->val->refs++;
+ fprintf(out, "store[*][refs=%d][size=%d] ", item->val->refs, item->size);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+ item->type->debug(out, item->key);
+ fprintf(out, " = %p\n", item->val);
+ fflush(out);
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ if (next)
+ next->val->refs--;
+ }
+ fprintf(out, "-- resource store hash contents --\n");
+ fz_print_hash_details(ctx, out, store->hash, print_item);
+ fprintf(out, "-- end --\n");
+ fflush(out);
+}
+
+void
+fz_print_store(fz_context *ctx, FILE *out)
+{
+ fz_lock(ctx, FZ_LOCK_ALLOC);
+ fz_print_store_locked(ctx, out);
+ fz_unlock(ctx, FZ_LOCK_ALLOC);
+}
+#endif
+
+/* This is now an n^2 algorithm - not ideal, but it'll only be bad if we are
+ * actually managing to scavenge lots of blocks back. */
+static int
+scavenge(fz_context *ctx, unsigned int tofree)
+{
+ fz_store *store = ctx->store;
+ unsigned int count = 0;
+ fz_item *item, *prev;
+
+ /* Free the items */
+ for (item = store->tail; item; item = prev)
+ {
+ prev = item->prev;
+ if (item->val->refs == 1)
+ {
+ /* Free this item */
+ count += item->size;
+ evict(ctx, item); /* Drops then retakes lock */
+
+ if (count >= tofree)
+ break;
+
+ /* Have to restart search again, as prev may no longer
+ * be valid due to release of lock in evict. */
+ prev = store->tail;
+ }
+ }
+ /* Success is managing to evict any blocks */
+ return count != 0;
+}
+
+int fz_store_scavenge(fz_context *ctx, unsigned int size, int *phase)
+{
+ fz_store *store;
+ unsigned int max;
+
+ if (ctx == NULL)
+ return 0;
+ store = ctx->store;
+ if (store == NULL)
+ return 0;
+
+#ifdef DEBUG_SCAVENGING
+ printf("Scavenging: store=%d size=%d phase=%d\n", store->size, size, *phase);
+ fz_print_store_locked(ctx, stderr);
+ Memento_stats();
+#endif
+ do
+ {
+ unsigned int tofree;
+
+ /* Calculate 'max' as the maximum size of the store for this phase */
+ if (*phase >= 16)
+ max = 0;
+ else if (store->max != FZ_STORE_UNLIMITED)
+ max = store->max / 16 * (16 - *phase);
+ else
+ max = store->size / (16 - *phase) * (15 - *phase);
+ (*phase)++;
+
+ /* Slightly baroque calculations to avoid overflow */
+ if (size > UINT_MAX - store->size)
+ tofree = UINT_MAX - max;
+ else if (size + store->size > max)
+ continue;
+ else
+ tofree = size + store->size - max;
+
+ if (scavenge(ctx, tofree))
+ {
+#ifdef DEBUG_SCAVENGING
+ printf("scavenged: store=%d\n", store->size);
+ fz_print_store(ctx, stderr);
+ Memento_stats();
+#endif
+ return 1;
+ }
+ }
+ while (max > 0);
+
+#ifdef DEBUG_SCAVENGING
+ printf("scavenging failed\n");
+ fz_print_store(ctx, stderr);
+ Memento_listBlocks();
+#endif
+ return 0;
+}
diff --git a/source/fitz/stream-open.c b/source/fitz/stream-open.c
new file mode 100644
index 00000000..9e4dc8d4
--- /dev/null
+++ b/source/fitz/stream-open.c
@@ -0,0 +1,210 @@
+#include "mupdf/fitz.h"
+
+fz_stream *
+fz_new_stream(fz_context *ctx, void *state,
+ int(*read)(fz_stream *stm, unsigned char *buf, int len),
+ void(*close)(fz_context *ctx, void *state))
+{
+ fz_stream *stm;
+
+ fz_try(ctx)
+ {
+ stm = fz_malloc_struct(ctx, fz_stream);
+ }
+ fz_catch(ctx)
+ {
+ close(ctx, state);
+ fz_rethrow(ctx);
+ }
+
+ stm->refs = 1;
+ stm->error = 0;
+ stm->eof = 0;
+ stm->pos = 0;
+
+ stm->bits = 0;
+ stm->avail = 0;
+
+ stm->bp = stm->buf;
+ stm->rp = stm->bp;
+ stm->wp = stm->bp;
+ stm->ep = stm->buf + sizeof stm->buf;
+
+ stm->state = state;
+ stm->read = read;
+ stm->close = close;
+ stm->seek = NULL;
+ stm->ctx = ctx;
+
+ return stm;
+}
+
+fz_stream *
+fz_keep_stream(fz_stream *stm)
+{
+ if (stm)
+ stm->refs ++;
+ return stm;
+}
+
+void
+fz_close(fz_stream *stm)
+{
+ if (!stm)
+ return;
+ stm->refs --;
+ if (stm->refs == 0)
+ {
+ if (stm->close)
+ stm->close(stm->ctx, stm->state);
+ fz_free(stm->ctx, stm);
+ }
+}
+
+/* File stream */
+
+static int read_file(fz_stream *stm, unsigned char *buf, int len)
+{
+ int n = read(*(int*)stm->state, buf, len);
+ if (n < 0)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "read error: %s", strerror(errno));
+ return n;
+}
+
+static void seek_file(fz_stream *stm, int offset, int whence)
+{
+ int n = lseek(*(int*)stm->state, offset, whence);
+ if (n < 0)
+ fz_throw(stm->ctx, FZ_ERROR_GENERIC, "cannot lseek: %s", strerror(errno));
+ stm->pos = n;
+ stm->rp = stm->bp;
+ stm->wp = stm->bp;
+}
+
+static void close_file(fz_context *ctx, void *state)
+{
+ int n = close(*(int*)state);
+ if (n < 0)
+ fz_warn(ctx, "close error: %s", strerror(errno));
+ fz_free(ctx, state);
+}
+
+fz_stream *
+fz_open_fd(fz_context *ctx, int fd)
+{
+ fz_stream *stm;
+ int *state;
+
+ state = fz_malloc_struct(ctx, int);
+ *state = fd;
+
+ fz_try(ctx)
+ {
+ stm = fz_new_stream(ctx, state, read_file, close_file);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, state);
+ fz_rethrow(ctx);
+ }
+ stm->seek = seek_file;
+
+ return stm;
+}
+
+fz_stream *
+fz_open_file(fz_context *ctx, const char *name)
+{
+#ifdef _WIN32
+ char *s = (char*)name;
+ wchar_t *wname, *d;
+ int c, fd;
+ d = wname = fz_malloc(ctx, (strlen(name)+1) * sizeof(wchar_t));
+ while (*s) {
+ s += fz_chartorune(&c, s);
+ *d++ = c;
+ }
+ *d = 0;
+ fd = _wopen(wname, O_BINARY | O_RDONLY, 0);
+ fz_free(ctx, wname);
+#else
+ int fd = open(name, O_BINARY | O_RDONLY, 0);
+#endif
+ if (fd == -1)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open %s", name);
+ return fz_open_fd(ctx, fd);
+}
+
+#ifdef _WIN32
+fz_stream *
+fz_open_file_w(fz_context *ctx, const wchar_t *name)
+{
+ int fd = _wopen(name, O_BINARY | O_RDONLY, 0);
+ if (fd == -1)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file %ls", name);
+ return fz_open_fd(ctx, fd);
+}
+#endif
+
+/* Memory stream */
+
+static int read_buffer(fz_stream *stm, unsigned char *buf, int len)
+{
+ return 0;
+}
+
+static void seek_buffer(fz_stream *stm, int offset, int whence)
+{
+ if (whence == 0)
+ stm->rp = stm->bp + offset;
+ if (whence == 1)
+ stm->rp += offset;
+ if (whence == 2)
+ stm->rp = stm->ep - offset;
+ stm->rp = fz_clampp(stm->rp, stm->bp, stm->ep);
+ stm->wp = stm->ep;
+}
+
+static void close_buffer(fz_context *ctx, void *state_)
+{
+ fz_buffer *state = (fz_buffer *)state_;
+ if (state)
+ fz_drop_buffer(ctx, state);
+}
+
+fz_stream *
+fz_open_buffer(fz_context *ctx, fz_buffer *buf)
+{
+ fz_stream *stm;
+
+ fz_keep_buffer(ctx, buf);
+ stm = fz_new_stream(ctx, buf, read_buffer, close_buffer);
+ stm->seek = seek_buffer;
+
+ stm->bp = buf->data;
+ stm->rp = buf->data;
+ stm->wp = buf->data + buf->len;
+ stm->ep = buf->data + buf->len;
+
+ stm->pos = buf->len;
+
+ return stm;
+}
+
+fz_stream *
+fz_open_memory(fz_context *ctx, unsigned char *data, int len)
+{
+ fz_stream *stm;
+
+ stm = fz_new_stream(ctx, NULL, read_buffer, close_buffer);
+ stm->seek = seek_buffer;
+
+ stm->bp = data;
+ stm->rp = data;
+ stm->wp = data + len;
+ stm->ep = data + len;
+
+ stm->pos = len;
+
+ return stm;
+}
diff --git a/source/fitz/stream-read.c b/source/fitz/stream-read.c
new file mode 100644
index 00000000..ee3d1cad
--- /dev/null
+++ b/source/fitz/stream-read.c
@@ -0,0 +1,219 @@
+#include "mupdf/fitz.h"
+
+#define MIN_BOMB (100 << 20)
+
+int
+fz_read(fz_stream *stm, unsigned char *buf, int len)
+{
+ int count, n;
+
+ count = fz_mini(len, stm->wp - stm->rp);
+ if (count)
+ {
+ memcpy(buf, stm->rp, count);
+ stm->rp += count;
+ }
+
+ if (count == len || stm->error || stm->eof)
+ return count;
+
+ assert(stm->rp == stm->wp);
+
+ if (len - count < stm->ep - stm->bp)
+ {
+ n = stm->read(stm, stm->bp, stm->ep - stm->bp);
+ if (n == 0)
+ {
+ stm->eof = 1;
+ }
+ else if (n > 0)
+ {
+ stm->rp = stm->bp;
+ stm->wp = stm->bp + n;
+ stm->pos += n;
+ }
+
+ n = fz_mini(len - count, stm->wp - stm->rp);
+ if (n)
+ {
+ memcpy(buf + count, stm->rp, n);
+ stm->rp += n;
+ count += n;
+ }
+ }
+ else
+ {
+ n = stm->read(stm, buf + count, len - count);
+ if (n == 0)
+ {
+ stm->eof = 1;
+ }
+ else if (n > 0)
+ {
+ stm->pos += n;
+ count += n;
+ }
+ }
+
+ return count;
+}
+
+void
+fz_fill_buffer(fz_stream *stm)
+{
+ int n;
+
+ assert(stm->rp == stm->wp);
+
+ if (stm->error || stm->eof)
+ return;
+
+ fz_try(stm->ctx)
+ {
+ n = stm->read(stm, stm->bp, stm->ep - stm->bp);
+ if (n == 0)
+ {
+ stm->eof = 1;
+ }
+ else if (n > 0)
+ {
+ stm->rp = stm->bp;
+ stm->wp = stm->bp + n;
+ stm->pos += n;
+ }
+ }
+ fz_catch(stm->ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(stm->ctx, "read error; treating as end of file");
+ stm->error = 1;
+ }
+}
+
+fz_buffer *
+fz_read_all(fz_stream *stm, int initial)
+{
+ return fz_read_best(stm, initial, NULL);
+}
+
+fz_buffer *
+fz_read_best(fz_stream *stm, int initial, int *truncated)
+{
+ fz_buffer *buf = NULL;
+ int n;
+ fz_context *ctx = stm->ctx;
+
+ fz_var(buf);
+
+ if (truncated)
+ *truncated = 0;
+
+ fz_try(ctx)
+ {
+ if (initial < 1024)
+ initial = 1024;
+
+ buf = fz_new_buffer(ctx, initial+1);
+
+ while (1)
+ {
+ if (buf->len == buf->cap)
+ fz_grow_buffer(ctx, buf);
+
+ if (buf->len >= MIN_BOMB && buf->len / 200 > initial)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "compression bomb detected");
+ }
+
+ n = fz_read(stm, buf->data + buf->len, buf->cap - buf->len);
+ if (n == 0)
+ break;
+
+ buf->len += n;
+ }
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ if (truncated)
+ {
+ *truncated = 1;
+ }
+ else
+ {
+ fz_drop_buffer(ctx, buf);
+ fz_rethrow(ctx);
+ }
+ }
+ fz_trim_buffer(ctx, buf);
+
+ return buf;
+}
+
+void
+fz_read_line(fz_stream *stm, char *mem, int n)
+{
+ char *s = mem;
+ int c = EOF;
+ while (n > 1)
+ {
+ c = fz_read_byte(stm);
+ if (c == EOF)
+ break;
+ if (c == '\r') {
+ c = fz_peek_byte(stm);
+ if (c == '\n')
+ fz_read_byte(stm);
+ break;
+ }
+ if (c == '\n')
+ break;
+ *s++ = c;
+ n--;
+ }
+ if (n)
+ *s = '\0';
+}
+
+int
+fz_tell(fz_stream *stm)
+{
+ return stm->pos - (stm->wp - stm->rp);
+}
+
+void
+fz_seek(fz_stream *stm, int offset, int whence)
+{
+ if (stm->seek)
+ {
+ if (whence == 1)
+ {
+ offset = fz_tell(stm) + offset;
+ whence = 0;
+ }
+ if (whence == 0)
+ {
+ int dist = stm->pos - offset;
+ if (dist >= 0 && dist <= stm->wp - stm->bp)
+ {
+ stm->rp = stm->wp - dist;
+ stm->eof = 0;
+ return;
+ }
+ }
+ stm->seek(stm, offset, whence);
+ stm->eof = 0;
+ }
+ else if (whence != 2)
+ {
+ if (whence == 0)
+ offset -= fz_tell(stm);
+ if (offset < 0)
+ fz_warn(stm->ctx, "cannot seek backwards");
+ /* dog slow, but rare enough */
+ while (offset-- > 0)
+ fz_read_byte(stm);
+ }
+ else
+ fz_warn(stm->ctx, "cannot seek");
+}
diff --git a/source/fitz/string.c b/source/fitz/string.c
new file mode 100644
index 00000000..b29cdbdc
--- /dev/null
+++ b/source/fitz/string.c
@@ -0,0 +1,264 @@
+#include "mupdf/fitz.h"
+
+char *
+fz_strsep(char **stringp, const char *delim)
+{
+ char *ret = *stringp;
+ if (!ret) return NULL;
+ if ((*stringp = strpbrk(*stringp, delim)))
+ *((*stringp)++) = '\0';
+ return ret;
+}
+
+int
+fz_strlcpy(char *dst, const char *src, int siz)
+{
+ register char *d = dst;
+ register const char *s = src;
+ register int n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+int
+fz_strlcat(char *dst, const char *src, int siz)
+{
+ register char *d = dst;
+ register const char *s = src;
+ register int n = siz;
+ int dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (*d != '\0' && n-- != 0)
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return dlen + strlen(s);
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return dlen + (s - src); /* count does not include NUL */
+}
+
+enum
+{
+ UTFmax = 4, /* maximum bytes per rune */
+ Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */
+ Runeself = 0x80, /* rune and UTF sequences are the same (<) */
+ Runeerror = 0xFFFD, /* decoding error in UTF */
+ Runemax = 0x10FFFF, /* maximum rune value */
+};
+
+enum
+{
+ Bit1 = 7,
+ Bitx = 6,
+ Bit2 = 5,
+ Bit3 = 4,
+ Bit4 = 3,
+ Bit5 = 2,
+
+ T1 = ((1<<(Bit1+1))-1) ^ 0xFF, /* 0000 0000 */
+ Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */
+ T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */
+ T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */
+ T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */
+ T5 = ((1<<(Bit5+1))-1) ^ 0xFF, /* 1111 1000 */
+
+ Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0111 1111 */
+ Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0111 1111 1111 */
+ Rune3 = (1<<(Bit3+2*Bitx))-1, /* 1111 1111 1111 1111 */
+ Rune4 = (1<<(Bit4+3*Bitx))-1, /* 0001 1111 1111 1111 1111 1111 */
+
+ Maskx = (1<<Bitx)-1, /* 0011 1111 */
+ Testx = Maskx ^ 0xFF, /* 1100 0000 */
+
+ Bad = Runeerror,
+};
+
+int
+fz_chartorune(int *rune, const char *str)
+{
+ int c, c1, c2, c3;
+ long l;
+
+ /*
+ * one character sequence
+ * 00000-0007F => T1
+ */
+ c = *(const unsigned char*)str;
+ if(c < Tx) {
+ *rune = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ c1 = *(const unsigned char*)(str+1) ^ Tx;
+ if(c1 & Testx)
+ goto bad;
+ if(c < T3) {
+ if(c < T2)
+ goto bad;
+ l = ((c << Bitx) | c1) & Rune2;
+ if(l <= Rune1)
+ goto bad;
+ *rune = l;
+ return 2;
+ }
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ c2 = *(const unsigned char*)(str+2) ^ Tx;
+ if(c2 & Testx)
+ goto bad;
+ if(c < T4) {
+ l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
+ if(l <= Rune2)
+ goto bad;
+ *rune = l;
+ return 3;
+ }
+
+ /*
+ * four character sequence (21-bit value)
+ * 10000-1FFFFF => T4 Tx Tx Tx
+ */
+ c3 = *(const unsigned char*)(str+3) ^ Tx;
+ if (c3 & Testx)
+ goto bad;
+ if (c < T5) {
+ l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4;
+ if (l <= Rune3)
+ goto bad;
+ *rune = l;
+ return 4;
+ }
+ /*
+ * Support for 5-byte or longer UTF-8 would go here, but
+ * since we don't have that, we'll just fall through to bad.
+ */
+
+ /*
+ * bad decoding
+ */
+bad:
+ *rune = Bad;
+ return 1;
+}
+
+int
+fz_runetochar(char *str, int rune)
+{
+ /* Runes are signed, so convert to unsigned for range check. */
+ unsigned long c = (unsigned long)rune;
+
+ /*
+ * one character sequence
+ * 00000-0007F => 00-7F
+ */
+ if(c <= Rune1) {
+ str[0] = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ if(c <= Rune2) {
+ str[0] = T2 | (c >> 1*Bitx);
+ str[1] = Tx | (c & Maskx);
+ return 2;
+ }
+
+ /*
+ * If the Rune is out of range, convert it to the error rune.
+ * Do this test here because the error rune encodes to three bytes.
+ * Doing it earlier would duplicate work, since an out of range
+ * Rune wouldn't have fit in one or two bytes.
+ */
+ if (c > Runemax)
+ c = Runeerror;
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ if (c <= Rune3) {
+ str[0] = T3 | (c >> 2*Bitx);
+ str[1] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[2] = Tx | (c & Maskx);
+ return 3;
+ }
+
+ /*
+ * four character sequence (21-bit value)
+ * 10000-1FFFFF => T4 Tx Tx Tx
+ */
+ str[0] = T4 | (c >> 3*Bitx);
+ str[1] = Tx | ((c >> 2*Bitx) & Maskx);
+ str[2] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[3] = Tx | (c & Maskx);
+ return 4;
+}
+
+int
+fz_runelen(int c)
+{
+ char str[10];
+ return fz_runetochar(str, c);
+}
+
+float fz_atof(const char *s)
+{
+ double d;
+
+ /* The errno voodoo here checks for us reading numbers that are too
+ * big to fit into a double. The checks for FLT_MAX ensure that we
+ * don't read a number that's OK as a double and then become invalid
+ * as we convert to a float. */
+ errno = 0;
+ d = strtod(s, NULL);
+ if (errno == ERANGE || isnan(d)) {
+ /* Return 1.0, as it's a small known value that won't cause a divide by 0. */
+ return 1.0;
+ }
+ d = fz_clampd(d, -FLT_MAX, FLT_MAX);
+ return (float)d;
+}
+
+int fz_atoi(const char *s)
+{
+ if (s == NULL)
+ return 0;
+ return atoi(s);
+}
diff --git a/source/fitz/svg-device.c b/source/fitz/svg-device.c
new file mode 100644
index 00000000..4dd82120
--- /dev/null
+++ b/source/fitz/svg-device.c
@@ -0,0 +1,619 @@
+#include "mupdf/fitz.h"
+
+typedef struct svg_device_s svg_device;
+
+typedef struct tile_s tile;
+
+struct tile_s
+{
+ int pattern;
+ fz_matrix ctm;
+ fz_rect view;
+ fz_rect area;
+ fz_point step;
+};
+
+struct svg_device_s
+{
+ fz_context *ctx;
+ fz_output *out;
+
+ int id;
+
+ int num_tiles;
+ int max_tiles;
+ tile *tiles;
+};
+
+/* Helper functions */
+
+static void
+svg_dev_path(svg_device *sdev, fz_path *path)
+{
+ fz_output *out = sdev->out;
+ float x, y;
+ int i = 0;
+ fz_printf(out, " d=\"");
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_printf(out, "M %g %g ", x, y);
+ break;
+ case FZ_LINETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_printf(out, "L %g %g ", x, y);
+ break;
+ case FZ_CURVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_printf(out, "C %g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_printf(out, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_printf(out, "%g %g ", x, y);
+ break;
+ case FZ_CLOSE_PATH:
+ fz_printf(out, "Z ");
+ break;
+ }
+ }
+ fz_printf(out, "\"");
+}
+
+static void
+svg_dev_ctm(svg_device *sdev, const fz_matrix *ctm)
+{
+ fz_output *out = sdev->out;
+
+ if (ctm->a != 1.0 || ctm->b != 0 || ctm->c != 0 || ctm->d != 1.0 || ctm->e != 0 || ctm->f != 0)
+ {
+ fz_printf(out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
+ ctm->a, ctm->b, ctm->c, ctm->d, ctm->e, ctm->f);
+ }
+}
+
+static void
+svg_dev_stroke_state(svg_device *sdev, fz_stroke_state *stroke_state)
+{
+ fz_output *out = sdev->out;
+
+ fz_printf(out, " stroke-width=\"%g\"", stroke_state->linewidth);
+ fz_printf(out, " stroke-linecap=\"%s\"",
+ (stroke_state->start_cap == FZ_LINECAP_SQUARE ? "square" :
+ (stroke_state->start_cap == FZ_LINECAP_ROUND ? "round" : "butt")));
+ if (stroke_state->dash_len != 0)
+ {
+ int i;
+ fz_printf(out, " stroke-dasharray=");
+ for (i = 0; i < stroke_state->dash_len; i++)
+ fz_printf(out, "%c%g", (i == 0 ? '\"' : ','), stroke_state->dash_list[i]);
+ fz_printf(out, "\"");
+ if (stroke_state->dash_phase != 0)
+ fz_printf(out, " stroke-dashoffset=\"%g\"", stroke_state->dash_phase);
+ }
+ if (stroke_state->linejoin == FZ_LINEJOIN_MITER || stroke_state->linejoin == FZ_LINEJOIN_MITER_XPS)
+ fz_printf(out, " stroke-miterlimit=\"%g\"", stroke_state->miterlimit);
+ fz_printf(out, " stroke-linejoin=\"%s\"",
+ (stroke_state->linejoin == FZ_LINEJOIN_BEVEL ? "bevel" :
+ (stroke_state->linejoin == FZ_LINEJOIN_ROUND ? "round" : "miter")));
+}
+
+static void
+svg_dev_fill_color(svg_device *sdev, fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_context *ctx = sdev->ctx;
+ fz_output *out = sdev->out;
+ float rgb[FZ_MAX_COLORS];
+
+ if (colorspace != fz_device_rgb(ctx))
+ {
+ /* If it's not rgb, make it rgb */
+ colorspace->to_rgb(ctx, colorspace, color, rgb);
+ color = rgb;
+ }
+
+ if (color[0] == 0 && color[1] == 0 && color[2] == 0)
+ {
+ /* don't send a fill, as it will be assumed to be black */
+ }
+ else
+ fz_printf(out, " fill=\"rgb(%d,%d,%d)\"", (int)(255*color[0] + 0.5), (int)(255*color[1] + 0.5), (int)(255*color[2]+0.5));
+ if (alpha != 1)
+ fz_printf(out, " fill-opacity=\"%g\"", alpha);
+}
+
+static void
+svg_dev_stroke_color(svg_device *sdev, fz_colorspace *colorspace, float *color, float alpha)
+{
+ fz_context *ctx = sdev->ctx;
+ fz_output *out = sdev->out;
+ float rgb[FZ_MAX_COLORS];
+
+ if (colorspace != fz_device_rgb(ctx))
+ {
+ /* If it's not rgb, make it rgb */
+ colorspace->to_rgb(ctx, colorspace, color, rgb);
+ color = rgb;
+ }
+
+ fz_printf(out, " fill=\"none\" stroke=\"rgb(%d,%d,%d)\"", (int)(255*color[0] + 0.5), (int)(255*color[1] + 0.5), (int)(255*color[2]+0.5));
+ if (alpha != 1)
+ fz_printf(out, " stroke-opacity=\"%g\"", alpha);
+}
+
+static void
+svg_dev_text(svg_device *sdev, const fz_matrix *ctm, fz_text *text)
+{
+ fz_output *out = sdev->out;
+ int i;
+ fz_matrix inverse;
+ fz_matrix local_trm;
+ float size;
+
+ /* Rely on the fact that trm.{e,f} == 0 */
+ size = fz_matrix_expansion(&text->trm);
+ local_trm.a = text->trm.a / size;
+ local_trm.b = text->trm.b / size;
+ local_trm.c = -text->trm.c / size;
+ local_trm.d = -text->trm.d / size;
+ local_trm.e = 0;
+ local_trm.f = 0;
+ fz_invert_matrix(&inverse, &local_trm);
+ fz_concat(&local_trm, &local_trm, ctm);
+
+ fz_printf(out, " transform=\"matrix(%g,%g,%g,%g,%g,%g)\"",
+ local_trm.a, local_trm.b, local_trm.c, local_trm.d, local_trm.e, local_trm.f);
+ fz_printf(out, " font-size=\"%g\"", size);
+ fz_printf(out, " font-family=\"%s\"", text->font->name);
+
+ fz_printf(out, " x=");
+ for (i=0; i < text->len; i++)
+ {
+ fz_text_item *it = &text->items[i];
+ fz_point p;
+ p.x = it->x;
+ p.y = it->y;
+ fz_transform_point(&p, &inverse);
+ fz_printf(out, "%c%g", i == 0 ? '\"' : ' ', p.x);
+ }
+ fz_printf(out, "\" y=");
+ for (i=0; i < text->len; i++)
+ {
+ fz_text_item *it = &text->items[i];
+ fz_point p;
+ p.x = it->x;
+ p.y = it->y;
+ fz_transform_point(&p, &inverse);
+ fz_printf(out, "%c%g", i == 0 ? '\"' : ' ', p.y);
+ }
+ fz_printf(out, "\">\n");
+ for (i=0; i < text->len; i++)
+ {
+ fz_text_item *it = &text->items[i];
+ int c = it->ucs;
+ if (c >= 32 && c <= 127 && c != '<' && c != '&')
+ fz_printf(out, "%c", c);
+ else
+ fz_printf(out, "&#x%04x;", c);
+ }
+ fz_printf(out, "\n</text>\n");
+}
+
+/* Entry points */
+
+static void
+svg_dev_fill_path(fz_device *dev, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+
+ fz_printf(out, "<path");
+ svg_dev_ctm(sdev, ctm);
+ svg_dev_path(sdev, path);
+ svg_dev_fill_color(sdev, colorspace, color, alpha);
+ if (even_odd)
+ fz_printf(out, " fill-rule=\"evenodd\" ");
+ fz_printf(out, "/>\n");
+}
+
+static void
+svg_dev_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+
+ fz_printf(out, "<path");
+ svg_dev_ctm(sdev, ctm);
+ svg_dev_stroke_state(sdev, stroke);
+ svg_dev_stroke_color(sdev, colorspace, color, alpha);
+ svg_dev_path(sdev, path);
+ fz_printf(out, "/>\n");
+}
+
+static void
+svg_dev_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+ int num = sdev->id++;
+
+ fz_printf(out, "<clipPath id=\"cp%d\">\n", num);
+ fz_printf(out, "<path");
+ svg_dev_ctm(sdev, ctm);
+ svg_dev_path(sdev, path);
+ if (even_odd)
+ fz_printf(out, " fill-rule=\"evenodd\" ");
+ fz_printf(out, "/>\n</clipPath>\n<g clip-path=\"url(#cp%d)\">\n", num);
+}
+
+static void
+svg_dev_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+ fz_context *ctx = dev->ctx;
+ fz_rect bounds;
+ int num = sdev->id++;
+ float white[3] = { 255, 255, 255 };
+
+ fz_bound_path(ctx, path, stroke, ctm, &bounds);
+
+ fz_printf(out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\" >\n",
+ num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
+ fz_printf(out, "<path");
+ svg_dev_ctm(sdev, ctm);
+ svg_dev_stroke_state(sdev, stroke);
+ svg_dev_stroke_color(sdev, fz_device_rgb(ctx), white, 1);
+ svg_dev_path(sdev, path);
+ fz_printf(out, "/>\n</mask>\n<g mask=\"url(#ma%d)\">\n", num);
+}
+
+static void
+svg_dev_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+
+ fz_printf(out, "<text");
+ svg_dev_fill_color(sdev, colorspace, color, alpha);
+ svg_dev_text(sdev, ctm, text);
+}
+
+static void
+svg_dev_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+
+ fz_printf(out, "<text");
+ svg_dev_ctm(sdev, ctm);
+ svg_dev_stroke_state(sdev, stroke);
+ svg_dev_stroke_color(sdev, colorspace, color, alpha);
+ svg_dev_text(sdev, ctm, text);
+}
+
+static void
+svg_dev_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+ fz_context *ctx = dev->ctx;
+ fz_rect bounds;
+ int num = sdev->id++;
+ float white[3] = { 255, 255, 255 };
+
+ fz_bound_text(ctx, text, NULL, ctm, &bounds);
+
+ fz_printf(out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\" >\n",
+ num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
+ fz_printf(out, "<text");
+ svg_dev_fill_color(sdev, fz_device_rgb(ctx), white, 1.0f);
+ svg_dev_text(sdev, ctm, text);
+ fz_printf(out, "</mask>\n<g mask=\"url(#ma%d)\">\n", num);
+}
+
+static void
+svg_dev_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+ fz_context *ctx = dev->ctx;
+ fz_rect bounds;
+ int num = sdev->id++;
+ float white[3] = { 255, 255, 255 };
+
+ fz_bound_text(ctx, text, NULL, ctm, &bounds);
+
+ fz_printf(out, "<mask id=\"ma%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" maskUnits=\"userSpaceOnUse\" maskContentUnits=\"userSpaceOnUse\" >\n",
+ num, bounds.x0, bounds.y0, bounds.x1 - bounds.x0, bounds.y1 - bounds.y0);
+ fz_printf(out, "<text");
+ svg_dev_stroke_state(sdev, stroke);
+ svg_dev_stroke_color(sdev, fz_device_rgb(ctx), white, 1.0f);
+ svg_dev_text(sdev, ctm, text);
+ fz_printf(out, "</mask>\n<g mask=\"url(#ma%d)\">\n", num);
+}
+
+static void
+svg_dev_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+}
+
+static void
+send_data_base64(fz_output *out, fz_buffer *buffer)
+{
+ int i, len;
+ static const char set[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ len = buffer->len/3;
+ for (i = 0; i < len; i++)
+ {
+ int c = buffer->data[3*i];
+ int d = buffer->data[3*i+1];
+ int e = buffer->data[3*i+2];
+ if ((i & 15) == 0)
+ fz_printf(out, "\n");
+ fz_printf(out, "%c%c%c%c", set[c>>2], set[((c&3)<<4)|(d>>4)], set[((d&15)<<2)|(e>>6)], set[e & 63]);
+ }
+ i *= 3;
+ switch (buffer->len-i)
+ {
+ case 2:
+ {
+ int c = buffer->data[i];
+ int d = buffer->data[i+1];
+ fz_printf(out, "%c%c%c=", set[c>>2], set[((c&3)<<4)|(d>>4)], set[((d&15)<<2)]);
+ break;
+ }
+ case 1:
+ {
+ int c = buffer->data[i];
+ fz_printf(out, "%c%c==", set[c>>2], set[(c&3)<<4]);
+ break;
+ }
+ default:
+ case 0:
+ break;
+ }
+}
+
+static void
+svg_dev_fill_image(fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ svg_device *sdev = (svg_device *)dev->user;
+ fz_context *ctx = dev->ctx;
+ fz_output *out = sdev->out;
+ fz_matrix local_ctm = *ctm;
+ fz_matrix scale = { 1.0f/image->w, 0, 0, 1.0f/image->h, 0, 0};
+
+ fz_concat(&local_ctm, &scale, ctm);
+ fz_printf(out, "<image");
+ svg_dev_ctm(sdev, &local_ctm);
+ fz_printf(out, "width=\"%dpx\" height=\"%dpx\" xlink:href=\"data:", image->w, image->h);
+ switch (image->buffer == NULL ? FZ_IMAGE_JPX : image->buffer->params.type)
+ {
+ case FZ_IMAGE_JPEG:
+ fz_printf(out, "image/jpeg;base64,");
+ send_data_base64(out, image->buffer->buffer);
+ break;
+ case FZ_IMAGE_PNG:
+ fz_printf(out, "image/png;base64,");
+ send_data_base64(out, image->buffer->buffer);
+ break;
+ default:
+ {
+ fz_buffer *buf = fz_image_as_png(ctx, image, image->w, image->h);
+ fz_printf(out, "image/png;base64,");
+ send_data_base64(out, buf);
+ fz_drop_buffer(ctx, buf);
+ break;
+ }
+ }
+ fz_printf(out, "\"/>\n");
+}
+
+static void
+svg_dev_fill_shade(fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+}
+
+static void
+svg_dev_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
+fz_colorspace *colorspace, float *color, float alpha)
+{
+}
+
+static void
+svg_dev_clip_image_mask(fz_device *dev, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ svg_device *sdev = dev->user;
+ fz_output *out = sdev->out;
+
+ fz_printf(out, "<g>\n");
+}
+
+static void
+svg_dev_pop_clip(fz_device *dev)
+{
+ svg_device *sdev = (svg_device *)dev->user;
+ fz_output *out = sdev->out;
+
+ /* FIXME */
+ fz_printf(out, "</g>\n");
+}
+
+static void
+svg_dev_begin_mask(fz_device *dev, const fz_rect *bbox, int luminosity, fz_colorspace *colorspace, float *color)
+{
+}
+
+static void
+svg_dev_end_mask(fz_device *dev)
+{
+
+}
+
+static void
+svg_dev_begin_group(fz_device *dev, const fz_rect *bbox, int isolated, int knockout, int blendmode, float alpha)
+{
+
+}
+
+static void
+svg_dev_end_group(fz_device *dev)
+{
+
+}
+
+static int
+svg_dev_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ svg_device *sdev = (svg_device *)dev->user;
+ fz_output *out = sdev->out;
+ fz_context *ctx = dev->ctx;
+ fz_matrix inverse;
+ int num;
+ tile *t;
+
+ if (sdev->num_tiles == sdev->max_tiles)
+ {
+ int n = (sdev->num_tiles == 0 ? 4 : sdev->num_tiles * 2);
+
+ sdev->tiles = fz_resize_array(ctx, sdev->tiles, n, sizeof(tile));
+ sdev->max_tiles = n;
+ }
+ num = sdev->num_tiles++;
+ t = &sdev->tiles[num];
+ t->area = *area;
+ t->view = *view;
+ t->ctm = *ctm;
+ t->pattern = sdev->id++;
+ t->step.x = xstep;
+ t->step.y = ystep;
+
+ /* view = area of our reference tile in pattern space.
+ * area = area to tile into in pattern space.
+ * xstep/ystep = pattern repeat step in pattern space.
+ * All of these need to be transformed by ctm to get to device space.
+ * SVG only allows us to specify pattern tiles as axis aligned
+ * rectangles, so we send these through as is, and ensure that the
+ * correct matrix is used on the fill.
+ */
+
+ /* In svg, the reference tile is taken from (x,y) to (x+width,y+height)
+ * and is repeated at (x+n*width,y+m*height) for all integer n and m.
+ * This means that width and height correspond to xstep and ystep. */
+ fz_printf(out, "<pattern id=\"pa%d\" patternUnits=\"userSpaceOnUse\" patternContentUnits=\"userSpaceOnUse\"",
+ t->pattern);
+ fz_printf(out, " x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\">\n",
+ view->x0, view->y0, xstep, ystep);
+ /* All the pattern contents will have their own ctm applied. Let's
+ * undo the current one to allow for this */
+ fz_invert_matrix(&inverse, ctm);
+ fz_printf(out, "<g");
+ svg_dev_ctm(sdev, &inverse);
+ fz_printf(out, ">\n");
+
+ return 0;
+}
+
+static void
+svg_dev_end_tile(fz_device *dev)
+{
+ svg_device *sdev = (svg_device *)dev->user;
+ fz_output *out = sdev->out;
+ int num;
+ tile *t;
+
+ if (sdev->num_tiles == 0)
+ return;
+ num = --sdev->num_tiles;
+ t = &sdev->tiles[num];
+
+ fz_printf(out, "</g>\n</pattern>\n");
+ fz_printf(out, "<rect");
+ svg_dev_ctm(sdev, &t->ctm);
+ fz_printf(out, " fill=\"url(#pa%d)\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
+ t->pattern, t->area.x0, t->area.y0, t->area.x1 - t->area.x0, t->area.y1 - t->area.y0);
+}
+
+static void
+svg_dev_free_user(fz_device *dev)
+{
+ svg_device *sdev = dev->user;
+ fz_context *ctx = sdev->ctx;
+ fz_output *out = sdev->out;
+
+ fz_free(ctx, sdev->tiles);
+
+ fz_printf(out, "</svg>\n");
+
+ fz_free(ctx, sdev);
+}
+
+fz_device *fz_new_svg_device(fz_context *ctx, fz_output *out, float page_width, float page_height)
+{
+ svg_device *sdev = fz_malloc_struct(ctx, svg_device);
+ fz_device *dev;
+
+ fz_try(ctx)
+ {
+ sdev->ctx = ctx;
+ sdev->out = out;
+ sdev->id = 0;
+
+ dev = fz_new_device(ctx, sdev);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, sdev);
+ fz_rethrow(ctx);
+ }
+
+ dev->free_user = svg_dev_free_user;
+
+ dev->fill_path = svg_dev_fill_path;
+ dev->stroke_path = svg_dev_stroke_path;
+ dev->clip_path = svg_dev_clip_path;
+ dev->clip_stroke_path = svg_dev_clip_stroke_path;
+
+ dev->fill_text = svg_dev_fill_text;
+ dev->stroke_text = svg_dev_stroke_text;
+ dev->clip_text = svg_dev_clip_text;
+ dev->clip_stroke_text = svg_dev_clip_stroke_text;
+ dev->ignore_text = svg_dev_ignore_text;
+
+ dev->fill_shade = svg_dev_fill_shade;
+ dev->fill_image = svg_dev_fill_image;
+ dev->fill_image_mask = svg_dev_fill_image_mask;
+ dev->clip_image_mask = svg_dev_clip_image_mask;
+
+ dev->pop_clip = svg_dev_pop_clip;
+
+ dev->begin_mask = svg_dev_begin_mask;
+ dev->end_mask = svg_dev_end_mask;
+ dev->begin_group = svg_dev_begin_group;
+ dev->end_group = svg_dev_end_group;
+
+ dev->begin_tile = svg_dev_begin_tile;
+ dev->end_tile = svg_dev_end_tile;
+
+ fz_printf(out, "<?xml version=\"1.0\" standalone=\"no\"?>\n");
+ fz_printf(out, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+ fz_printf(out, "<svg xmlns=\"http://www.w3.org/2000/svg\" "
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" "
+ "width=\"%gcm\" height=\"%gcm\" viewBox=\"0 0 %g %g\">\n",
+ page_width*2.54/72, page_height*2.54/72, page_width, page_height);
+
+ return dev;
+}
diff --git a/source/fitz/text.c b/source/fitz/text.c
new file mode 100644
index 00000000..81e2e1d0
--- /dev/null
+++ b/source/fitz/text.c
@@ -0,0 +1,154 @@
+#include "mupdf/fitz.h"
+
+fz_text *
+fz_new_text(fz_context *ctx, fz_font *font, const fz_matrix *trm, int wmode)
+{
+ fz_text *text;
+
+ text = fz_malloc_struct(ctx, fz_text);
+ text->font = fz_keep_font(ctx, font);
+ text->trm = *trm;
+ text->wmode = wmode;
+ text->len = 0;
+ text->cap = 0;
+ text->items = NULL;
+
+ return text;
+}
+
+void
+fz_free_text(fz_context *ctx, fz_text *text)
+{
+ if (text != NULL)
+ {
+ fz_drop_font(ctx, text->font);
+ fz_free(ctx, text->items);
+ }
+ fz_free(ctx, text);
+}
+
+fz_text *
+fz_clone_text(fz_context *ctx, fz_text *old)
+{
+ fz_text *text;
+
+ text = fz_malloc_struct(ctx, fz_text);
+ text->len = old->len;
+ fz_try(ctx)
+ {
+ text->items = fz_malloc_array(ctx, text->len, sizeof(fz_text_item));
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, text);
+ fz_rethrow(ctx);
+ }
+ memcpy(text->items, old->items, text->len * sizeof(fz_text_item));
+ text->font = fz_keep_font(ctx, old->font);
+ text->trm = old->trm;
+ text->wmode = old->wmode;
+ text->cap = text->len;
+
+ return text;
+}
+
+fz_rect *
+fz_bound_text(fz_context *ctx, fz_text *text, const fz_stroke_state *stroke, const fz_matrix *ctm, fz_rect *bbox)
+{
+ fz_matrix tm, trm;
+ fz_rect gbox;
+ int i;
+
+ if (text->len == 0)
+ {
+ *bbox = fz_empty_rect;
+ return bbox;
+ }
+
+ // TODO: stroke state
+
+ tm = text->trm;
+
+ tm.e = text->items[0].x;
+ tm.f = text->items[0].y;
+ fz_concat(&trm, &tm, ctm);
+ fz_bound_glyph(ctx, text->font, text->items[0].gid, &trm, bbox);
+
+ for (i = 1; i < text->len; i++)
+ {
+ if (text->items[i].gid >= 0)
+ {
+ tm.e = text->items[i].x;
+ tm.f = text->items[i].y;
+ fz_concat(&trm, &tm, ctm);
+ fz_bound_glyph(ctx, text->font, text->items[i].gid, &trm, &gbox);
+
+ bbox->x0 = fz_min(bbox->x0, gbox.x0);
+ bbox->y0 = fz_min(bbox->y0, gbox.y0);
+ bbox->x1 = fz_max(bbox->x1, gbox.x1);
+ bbox->y1 = fz_max(bbox->y1, gbox.y1);
+ }
+ }
+
+ if (stroke)
+ fz_adjust_rect_for_stroke(bbox, stroke, ctm);
+
+ /* Compensate for the glyph cache limited positioning precision */
+ bbox->x0 -= 1;
+ bbox->y0 -= 1;
+ bbox->x1 += 1;
+ bbox->y1 += 1;
+
+ return bbox;
+}
+
+static void
+fz_grow_text(fz_context *ctx, fz_text *text, int n)
+{
+ int new_cap = text->cap;
+ if (text->len + n < new_cap)
+ return;
+ while (text->len + n > new_cap)
+ new_cap = new_cap + 36;
+ text->items = fz_resize_array(ctx, text->items, new_cap, sizeof(fz_text_item));
+ text->cap = new_cap;
+}
+
+void
+fz_add_text(fz_context *ctx, fz_text *text, int gid, int ucs, float x, float y)
+{
+ fz_grow_text(ctx, text, 1);
+ text->items[text->len].ucs = ucs;
+ text->items[text->len].gid = gid;
+ text->items[text->len].x = x;
+ text->items[text->len].y = y;
+ text->len++;
+}
+
+static int
+isxmlmeta(int c)
+{
+ return c < 32 || c >= 128 || c == '&' || c == '<' || c == '>' || c == '\'' || c == '"';
+}
+
+static void
+do_print_text(FILE *out, fz_text *text, int indent)
+{
+ int i, n;
+ for (i = 0; i < text->len; i++)
+ {
+ for (n = 0; n < indent; n++)
+ fputc(' ', out);
+ if (!isxmlmeta(text->items[i].ucs))
+ fprintf(out, "<g ucs=\"%c\" gid=\"%d\" x=\"%g\" y=\"%g\" />\n",
+ text->items[i].ucs, text->items[i].gid, text->items[i].x, text->items[i].y);
+ else
+ fprintf(out, "<g ucs=\"U+%04X\" gid=\"%d\" x=\"%g\" y=\"%g\" />\n",
+ text->items[i].ucs, text->items[i].gid, text->items[i].x, text->items[i].y);
+ }
+}
+
+void fz_print_text(fz_context *ctx, FILE *out, fz_text *text)
+{
+ do_print_text(out, text, 0);
+}
diff --git a/source/fitz/time.c b/source/fitz/time.c
new file mode 100644
index 00000000..0e3d21b5
--- /dev/null
+++ b/source/fitz/time.c
@@ -0,0 +1,144 @@
+#ifdef _MSC_VER
+
+#include "mupdf/fitz.h"
+
+#include <time.h>
+#include <windows.h>
+
+#ifndef _WINRT
+
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64
+
+struct timeval;
+struct timezone;
+
+int gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+ FILETIME ft;
+ unsigned __int64 tmpres = 0;
+
+ if (tv)
+ {
+ GetSystemTimeAsFileTime(&ft);
+
+ tmpres |= ft.dwHighDateTime;
+ tmpres <<= 32;
+ tmpres |= ft.dwLowDateTime;
+
+ tmpres /= 10; /*convert into microseconds*/
+ /*converting file time to unix epoch*/
+ tmpres -= DELTA_EPOCH_IN_MICROSECS;
+ tv->tv_sec = (long)(tmpres / 1000000UL);
+ tv->tv_usec = (long)(tmpres % 1000000UL);
+ }
+
+ return 0;
+}
+
+#endif /* !_WINRT */
+
+char *
+fz_utf8_from_wchar(const wchar_t *s)
+{
+ const wchar_t *src = s;
+ char *d;
+ char *dst;
+ int len = 1;
+
+ while (*src)
+ {
+ len += fz_runelen(*src++);
+ }
+
+ d = malloc(len);
+ if (d != NULL)
+ {
+ dst = d;
+ src = s;
+ while (*src)
+ {
+ dst += fz_runetochar(dst, *src++);
+ }
+ *dst = 0;
+ }
+ return d;
+}
+
+wchar_t *
+fz_wchar_from_utf8(const char *s)
+{
+ wchar_t *d, *r;
+ int c;
+ r = d = malloc((strlen(s) + 1) * sizeof(wchar_t));
+ if (!r)
+ return NULL;
+ while (*s) {
+ s += fz_chartorune(&c, s);
+ *d++ = c;
+ }
+ *d = 0;
+ return r;
+}
+
+FILE *
+fz_fopen_utf8(const char *name, const char *mode)
+{
+ wchar_t *wname, *wmode;
+ FILE *file;
+
+ wname = fz_wchar_from_utf8(name);
+ if (wname == NULL)
+ {
+ return NULL;
+ }
+
+ wmode = fz_wchar_from_utf8(mode);
+ if (wmode == NULL)
+ {
+ free(wname);
+ return NULL;
+ }
+
+ file = _wfopen(wname, wmode);
+
+ free(wname);
+ free(wmode);
+ return file;
+}
+
+char **
+fz_argv_from_wargv(int argc, wchar_t **wargv)
+{
+ char **argv;
+ int i;
+
+ argv = calloc(argc, sizeof(char *));
+ if (argv == NULL)
+ {
+ fprintf(stderr, "Out of memory while processing command line args!\n");
+ exit(1);
+ }
+
+ for (i = 0; i < argc; i++)
+ {
+ argv[i] = fz_utf8_from_wchar(wargv[i]);
+ if (argv[i] == NULL)
+ {
+ fprintf(stderr, "Out of memory while processing command line args!\n");
+ exit(1);
+ }
+ }
+
+ return argv;
+}
+
+void
+fz_free_argv(int argc, char **argv)
+{
+ int i;
+ for (i = 0; i < argc; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+#endif /* _MSC_VER */
diff --git a/source/fitz/trace-device.c b/source/fitz/trace-device.c
new file mode 100644
index 00000000..b3cade91
--- /dev/null
+++ b/source/fitz/trace-device.c
@@ -0,0 +1,339 @@
+#include "mupdf/fitz.h"
+
+static void
+fz_trace_matrix(const fz_matrix *ctm)
+{
+ printf(" matrix=\"%g %g %g %g %g %g\"",
+ ctm->a, ctm->b, ctm->c, ctm->d, ctm->e, ctm->f);
+}
+
+static void
+fz_trace_trm(const fz_matrix *trm)
+{
+ printf(" trm=\"%g %g %g %g\"",
+ trm->a, trm->b, trm->c, trm->d);
+}
+
+static void
+fz_trace_color(fz_colorspace *colorspace, float *color, float alpha)
+{
+ int i;
+ printf(" colorspace=\"%s\" color=\"", colorspace->name);
+ for (i = 0; i < colorspace->n; i++)
+ printf("%s%g", i == 0 ? "" : " ", color[i]);
+ printf("\"");
+ if (alpha < 1)
+ printf(" alpha=\"%g\"", alpha);
+}
+
+static void
+fz_trace_path(fz_path *path, int indent)
+{
+ float x, y;
+ int i = 0;
+ int n;
+ while (i < path->len)
+ {
+ for (n = 0; n < indent; n++)
+ putchar(' ');
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ printf("<moveto x=\"%g\" y=\"%g\"/>\n", x, y);
+ break;
+ case FZ_LINETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ printf("<lineto x=\"%g\" y=\"%g\"/>\n", x, y);
+ break;
+ case FZ_CURVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ printf("<curveto x1=\"%g\" y1=\"%g\"", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ printf(" x2=\"%g\" y2=\"%g\"", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ printf(" x3=\"%g\" y3=\"%g\"/>\n", x, y);
+ break;
+ case FZ_CLOSE_PATH:
+ printf("<closepath/>\n");
+ break;
+ }
+ }
+}
+
+static void
+fz_trace_begin_page(fz_device *dev, const fz_rect *rect, const fz_matrix *ctm)
+{
+ printf("<page mediabox=\"%g %g %g %g\"", rect->x0, rect->y0, rect->x1, rect->y1);
+ fz_trace_matrix(ctm);
+ printf(">\n");
+}
+
+static void
+fz_trace_end_page(fz_device *dev)
+{
+ printf("</page>\n");
+}
+
+static void
+fz_trace_fill_path(fz_device *dev, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ printf("<fill_path");
+ if (even_odd)
+ printf(" winding=\"eofill\"");
+ else
+ printf(" winding=\"nonzero\"");
+ fz_trace_color(colorspace, color, alpha);
+ fz_trace_matrix(ctm);
+ printf(">\n");
+ fz_trace_path(path, 0);
+ printf("</fill_path>\n");
+}
+
+static void
+fz_trace_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ int i;
+
+ printf("<stroke_path");
+ printf(" linewidth=\"%g\"", stroke->linewidth);
+ printf(" miterlimit=\"%g\"", stroke->miterlimit);
+ printf(" linecap=\"%d,%d,%d\"", stroke->start_cap, stroke->dash_cap, stroke->end_cap);
+ printf(" linejoin=\"%d\"", stroke->linejoin);
+
+ if (stroke->dash_len)
+ {
+ printf(" dash_phase=\"%g\" dash=\"", stroke->dash_phase);
+ for (i = 0; i < stroke->dash_len; i++)
+ printf("%s%g", i > 0 ? " " : "", stroke->dash_list[i]);
+ printf("\"");
+ }
+
+ fz_trace_color(colorspace, color, alpha);
+ fz_trace_matrix(ctm);
+ printf(">\n");
+
+ fz_trace_path(path, 0);
+
+ printf("</stroke_path>\n");
+}
+
+static void
+fz_trace_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ printf("<clip_path");
+ if (even_odd)
+ printf(" winding=\"eofill\"");
+ else
+ printf(" winding=\"nonzero\"");
+ fz_trace_matrix(ctm);
+ if (rect)
+ printf(" contentbbox=\"%g %g %g %g\">\n", rect->x0, rect->y0, rect->x1, rect->y1);
+ else
+ printf(">\n");
+ fz_trace_path(path, 0);
+ printf("</clip_path>\n");
+}
+
+static void
+fz_trace_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ printf("<clip_stroke_path");
+ fz_trace_matrix(ctm);
+ printf(">\n");
+ fz_trace_path(path, 0);
+ printf("</clip_stroke_path>\n");
+}
+
+static void
+fz_trace_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ printf("<fill_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+ fz_trace_color(colorspace, color, alpha);
+ fz_trace_matrix(ctm);
+ fz_trace_trm(&text->trm);
+ printf(">\n");
+ fz_print_text(dev->ctx, stdout, text);
+ printf("</fill_text>\n");
+}
+
+static void
+fz_trace_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ printf("<stroke_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+ fz_trace_color(colorspace, color, alpha);
+ fz_trace_matrix(ctm);
+ fz_trace_trm(&text->trm);
+ printf(">\n");
+ fz_print_text(dev->ctx, stdout, text);
+ printf("</stroke_text>\n");
+}
+
+static void
+fz_trace_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ printf("<clip_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+ printf(" accumulate=\"%d\"", accumulate);
+ fz_trace_matrix(ctm);
+ fz_trace_trm(&text->trm);
+ printf(">\n");
+ fz_print_text(dev->ctx, stdout, text);
+ printf("</clip_text>\n");
+}
+
+static void
+fz_trace_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ printf("<clip_stroke_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+ fz_trace_matrix(ctm);
+ fz_trace_trm(&text->trm);
+ printf(">\n");
+ fz_print_text(dev->ctx, stdout, text);
+ printf("</clip_stroke_text>\n");
+}
+
+static void
+fz_trace_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+ printf("<ignore_text font=\"%s\" wmode=\"%d\"", text->font->name, text->wmode);
+ fz_trace_matrix(ctm);
+ fz_trace_trm(&text->trm);
+ printf(">\n");
+ fz_print_text(dev->ctx, stdout, text);
+ printf("</ignore_text>\n");
+}
+
+static void
+fz_trace_fill_image(fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ printf("<fill_image alpha=\"%g\"", alpha);
+ fz_trace_matrix(ctm);
+ printf(" width=\"%d\" height=\"%d\"", image->w, image->h);
+ printf("/>\n");
+}
+
+static void
+fz_trace_fill_shade(fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+ printf("<fill_shade alpha=\"%g\"", alpha);
+ fz_trace_matrix(ctm);
+ printf("/>\n");
+}
+
+static void
+fz_trace_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
+fz_colorspace *colorspace, float *color, float alpha)
+{
+ printf("<fill_image_mask");
+ fz_trace_matrix(ctm);
+ fz_trace_color(colorspace, color, alpha);
+ printf(" width=\"%d\" height=\"%d\"", image->w, image->h);
+ printf("/>\n");
+}
+
+static void
+fz_trace_clip_image_mask(fz_device *dev, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ printf("<clip_image_mask");
+ fz_trace_matrix(ctm);
+ printf(" width=\"%d\" height=\"%d\"", image->w, image->h);
+ printf("/>\n");
+}
+
+static void
+fz_trace_pop_clip(fz_device *dev)
+{
+ printf("<pop_clip/>\n");
+}
+
+static void
+fz_trace_begin_mask(fz_device *dev, const fz_rect *bbox, int luminosity, fz_colorspace *colorspace, float *color)
+{
+ printf("<mask bbox=\"%g %g %g %g\" s=\"%s\"",
+ bbox->x0, bbox->y0, bbox->x1, bbox->y1,
+ luminosity ? "luminosity" : "alpha");
+ printf(">\n");
+}
+
+static void
+fz_trace_end_mask(fz_device *dev)
+{
+ printf("</mask>\n");
+}
+
+static void
+fz_trace_begin_group(fz_device *dev, const fz_rect *bbox, int isolated, int knockout, int blendmode, float alpha)
+{
+ printf("<group bbox=\"%g %g %g %g\" isolated=\"%d\" knockout=\"%d\" blendmode=\"%s\" alpha=\"%g\">\n",
+ bbox->x0, bbox->y0, bbox->x1, bbox->y1,
+ isolated, knockout, fz_blendmode_name(blendmode), alpha);
+}
+
+static void
+fz_trace_end_group(fz_device *dev)
+{
+ printf("</group>\n");
+}
+
+static int
+fz_trace_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ printf("<tile");
+ printf(" area=\"%g %g %g %g\"", area->x0, area->y0, area->x1, area->y1);
+ printf(" view=\"%g %g %g %g\"", view->x0, view->y0, view->x1, view->y1);
+ printf(" xstep=\"%g\" ystep=\"%g\"", xstep, ystep);
+ fz_trace_matrix(ctm);
+ printf(">\n");
+ return 0;
+}
+
+static void
+fz_trace_end_tile(fz_device *dev)
+{
+ printf("</tile>\n");
+}
+
+fz_device *fz_new_trace_device(fz_context *ctx)
+{
+ fz_device *dev = fz_new_device(ctx, NULL);
+
+ dev->begin_page = fz_trace_begin_page;
+ dev->end_page = fz_trace_end_page;
+
+ dev->fill_path = fz_trace_fill_path;
+ dev->stroke_path = fz_trace_stroke_path;
+ dev->clip_path = fz_trace_clip_path;
+ dev->clip_stroke_path = fz_trace_clip_stroke_path;
+
+ dev->fill_text = fz_trace_fill_text;
+ dev->stroke_text = fz_trace_stroke_text;
+ dev->clip_text = fz_trace_clip_text;
+ dev->clip_stroke_text = fz_trace_clip_stroke_text;
+ dev->ignore_text = fz_trace_ignore_text;
+
+ dev->fill_shade = fz_trace_fill_shade;
+ dev->fill_image = fz_trace_fill_image;
+ dev->fill_image_mask = fz_trace_fill_image_mask;
+ dev->clip_image_mask = fz_trace_clip_image_mask;
+
+ dev->pop_clip = fz_trace_pop_clip;
+
+ dev->begin_mask = fz_trace_begin_mask;
+ dev->end_mask = fz_trace_end_mask;
+ dev->begin_group = fz_trace_begin_group;
+ dev->end_group = fz_trace_end_group;
+
+ dev->begin_tile = fz_trace_begin_tile;
+ dev->end_tile = fz_trace_end_tile;
+
+ return dev;
+}
diff --git a/source/fitz/transition.c b/source/fitz/transition.c
new file mode 100644
index 00000000..92582253
--- /dev/null
+++ b/source/fitz/transition.c
@@ -0,0 +1,165 @@
+#include "mupdf/fitz.h"
+
+static int
+fade(fz_pixmap *tpix, fz_pixmap *opix, fz_pixmap *npix, int time)
+{
+ unsigned char *t, *o, *n;
+ int size;
+
+ if (!tpix || !opix || !npix || tpix->w != opix->w || opix->w != npix->w || tpix->h != opix->h || opix->h != npix->h || tpix->n != opix->n || opix->n != npix->n)
+ return 0;
+ size = tpix->w * tpix->h * tpix->n;
+ t = tpix->samples;
+ o = opix->samples;
+ n = npix->samples;
+ while (size-- > 0)
+ {
+ int op = *o++;
+ int np = *n++;
+ *t++ = ((op<<8) + ((np-op) * time) + 0x80)>>8;
+ }
+ return 1;
+}
+
+static int
+blind_horiz(fz_pixmap *tpix, fz_pixmap *opix, fz_pixmap *npix, int time)
+{
+ unsigned char *t, *o, *n;
+ int blind_height, span, position, y;
+
+ if (!tpix || !opix || !npix || tpix->w != opix->w || opix->w != npix->w || tpix->h != opix->h || opix->h != npix->h || tpix->n != opix->n || opix->n != npix->n)
+ return 0;
+ span = tpix->w * tpix->n;
+ blind_height = (tpix->h+7) / 8;
+ position = blind_height * time / 256;
+ t = tpix->samples;
+ o = opix->samples;
+ n = npix->samples;
+ for (y = 0; y < tpix->h; y++)
+ {
+ memcpy(t, ((y % blind_height) <= position ? n : o), span);
+ t += span;
+ o += span;
+ n += span;
+ }
+ return 1;
+}
+
+static int
+blind_vertical(fz_pixmap *tpix, fz_pixmap *opix, fz_pixmap *npix, int time)
+{
+ unsigned char *t, *o, *n;
+ int blind_width, span, position, y;
+
+ if (!tpix || !opix || !npix || tpix->w != opix->w || opix->w != npix->w || tpix->h != opix->h || opix->h != npix->h || tpix->n != opix->n || opix->n != npix->n)
+ return 0;
+ span = tpix->w * tpix->n;
+ blind_width = (tpix->w+7) / 8;
+ position = blind_width * time / 256;
+ blind_width *= tpix->n;
+ position *= tpix->n;
+ t = tpix->samples;
+ o = opix->samples;
+ n = npix->samples;
+ for (y = 0; y < tpix->h; y++)
+ {
+ int w, x;
+ x = 0;
+ while ((w = span - x) > 0)
+ {
+ int p;
+ if (w > blind_width)
+ w = blind_width;
+ p = position;
+ if (p > w)
+ p = w;
+ memcpy(t, n, p);
+ memcpy(t+position, o+position, w - p);
+ x += blind_width;
+ t += w;
+ o += w;
+ n += w;
+ }
+ }
+ return 1;
+}
+
+static int
+wipe_tb(fz_pixmap *tpix, fz_pixmap *opix, fz_pixmap *npix, int time)
+{
+ unsigned char *t, *o, *n;
+ int span, position, y;
+
+ if (!tpix || !opix || !npix || tpix->w != opix->w || opix->w != npix->w || tpix->h != opix->h || opix->h != npix->h || tpix->n != opix->n || opix->n != npix->n)
+ return 0;
+ span = tpix->w * tpix->n;
+ position = tpix->h * time / 256;
+ t = tpix->samples;
+ o = opix->samples;
+ n = npix->samples;
+ for (y = 0; y < position; y++)
+ {
+ memcpy(t, n, span);
+ t += span;
+ o += span;
+ n += span;
+ }
+ for (; y < tpix->h; y++)
+ {
+ memcpy(t, o, span);
+ t += span;
+ o += span;
+ n += span;
+ }
+ return 1;
+}
+
+static int
+wipe_lr(fz_pixmap *tpix, fz_pixmap *opix, fz_pixmap *npix, int time)
+{
+ unsigned char *t, *o, *n;
+ int span, position, y;
+
+ if (!tpix || !opix || !npix || tpix->w != opix->w || opix->w != npix->w || tpix->h != opix->h || opix->h != npix->h || tpix->n != opix->n || opix->n != npix->n)
+ return 0;
+ span = tpix->w * tpix->n;
+ position = tpix->w * time / 256;
+ position *= tpix->n;
+ t = tpix->samples;
+ o = opix->samples + position;
+ n = npix->samples;
+ for (y = 0; y < tpix->h; y++)
+ {
+ memcpy(t, n, position);
+ memcpy(t+position, o, span-position);
+ t += span;
+ o += span;
+ n += span;
+ }
+ return 1;
+}
+
+int fz_generate_transition(fz_pixmap *tpix, fz_pixmap *opix, fz_pixmap *npix, int time, fz_transition *trans)
+{
+ switch (trans->type)
+ {
+ default:
+ case FZ_TRANSITION_FADE:
+ return fade(tpix, opix, npix, time);
+ case FZ_TRANSITION_BLINDS:
+ if (trans->vertical)
+ return blind_vertical(tpix, opix, npix, time);
+ else
+ return blind_horiz(tpix, opix, npix, time);
+ case FZ_TRANSITION_WIPE:
+ switch (((trans->direction + 45 + 360) % 360) / 90)
+ {
+ default:
+ case 0: return wipe_lr(tpix, opix, npix, time);
+ case 1: return wipe_tb(tpix, npix, opix, 256-time);
+ case 2: return wipe_lr(tpix, npix, opix, 256-time);
+ case 3: return wipe_tb(tpix, opix, npix, time);
+ }
+ }
+ return 0;
+}
diff --git a/source/fitz/ucdn.c b/source/fitz/ucdn.c
new file mode 100644
index 00000000..ce2cb7c1
--- /dev/null
+++ b/source/fitz/ucdn.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2012 Grigori Goronzy <greg@kinoho.net>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "ucdn.h"
+
+typedef struct {
+ unsigned char category;
+ unsigned char combining;
+ unsigned char bidi_class;
+ unsigned char mirrored;
+ unsigned char east_asian_width;
+ unsigned char normalization_check;
+ unsigned char script;
+} UCDRecord;
+
+typedef struct {
+ unsigned short from, to;
+} MirrorPair;
+
+typedef struct {
+ int start;
+ short count, index;
+} Reindex;
+
+#include "unicodedata_db.h"
+
+/* constants required for Hangul (de)composition */
+#define SBASE 0xAC00
+#define LBASE 0x1100
+#define VBASE 0x1161
+#define TBASE 0x11A7
+#define SCOUNT 11172
+#define LCOUNT 19
+#define VCOUNT 21
+#define TCOUNT 28
+#define NCOUNT (VCOUNT * TCOUNT)
+
+static const UCDRecord *get_ucd_record(unsigned int code)
+{
+ int index, offset;
+
+ if (code >= 0x110000)
+ index = 0;
+ else {
+ index = index0[code >> (SHIFT1+SHIFT2)] << SHIFT1;
+ offset = (code >> SHIFT2) & ((1<<SHIFT1) - 1);
+ index = index1[index + offset] << SHIFT2;
+ offset = code & ((1<<SHIFT2) - 1);
+ index = index2[index + offset];
+ }
+
+ return &ucd_records[index];
+}
+
+static const unsigned short *get_decomp_record(unsigned int code)
+{
+ int index, offset;
+
+ if (code >= 0x110000)
+ index = 0;
+ else {
+ index = decomp_index0[code >> (DECOMP_SHIFT1+DECOMP_SHIFT2)]
+ << DECOMP_SHIFT1;
+ offset = (code >> DECOMP_SHIFT2) & ((1<<DECOMP_SHIFT1) - 1);
+ index = decomp_index1[index + offset] << DECOMP_SHIFT2;
+ offset = code & ((1<<DECOMP_SHIFT2) - 1);
+ index = decomp_index2[index + offset];
+ }
+
+ return &decomp_data[index];
+}
+
+static int get_comp_index(unsigned int code, const Reindex *idx)
+{
+ int i;
+
+ for (i = 0; idx[i].start; i++) {
+ const Reindex *cur = &idx[i];
+ if (code < cur->start)
+ return -1;
+ if (code <= cur->start + cur->count) {
+ return cur->index + (code - cur->start);
+ }
+ }
+
+ return -1;
+}
+
+static int compare_mp(const void *a, const void *b)
+{
+ MirrorPair *mpa = (MirrorPair *)a;
+ MirrorPair *mpb = (MirrorPair *)b;
+ return mpa->from - mpb->from;
+}
+
+static int hangul_pair_decompose(unsigned int code, unsigned int *a, unsigned int *b)
+{
+ int si = code - SBASE;
+
+ if (si < 0 || si >= SCOUNT)
+ return 0;
+
+ if (si % TCOUNT) {
+ /* LV,T */
+ *a = SBASE + (si / TCOUNT) * TCOUNT;
+ *b = TBASE + (si % TCOUNT);
+ return 3;
+ } else {
+ /* L,V */
+ *a = LBASE + (si / NCOUNT);
+ *b = VBASE + (si % NCOUNT) / TCOUNT;
+ return 2;
+ }
+}
+
+static int hangul_pair_compose(unsigned int *code, unsigned int a, unsigned int b)
+{
+ if (b < VBASE || b >= (TBASE + TCOUNT))
+ return 0;
+
+ if ((a < LBASE || a >= (LBASE + LCOUNT))
+ && (a < SBASE || a >= (SBASE + SCOUNT)))
+ return 0;
+
+ if (a >= SBASE) {
+ /* LV,T */
+ *code = a + (b - TBASE);
+ return 3;
+ } else {
+ /* L,V */
+ int li = a - LBASE;
+ int vi = b - VBASE;
+ *code = SBASE + li * NCOUNT + vi * TCOUNT;
+ return 2;
+ }
+}
+
+static unsigned int decode_utf16(const unsigned short **code_ptr)
+{
+ const unsigned short *code = *code_ptr;
+
+ if ((code[0] & 0xd800) != 0xd800) {
+ *code_ptr += 1;
+ return (unsigned int)code[0];
+ } else {
+ *code_ptr += 2;
+ return 0x10000 + ((unsigned int)code[1] - 0xdc00) +
+ (((unsigned int)code[0] - 0xd800) << 10);
+ }
+}
+
+const char *ucdn_get_unicode_version(void)
+{
+ return UNIDATA_VERSION;
+}
+
+int ucdn_get_combining_class(unsigned int code)
+{
+ return get_ucd_record(code)->combining;
+}
+
+int ucdn_get_east_asian_width(unsigned int code)
+{
+ return get_ucd_record(code)->east_asian_width;
+}
+
+int ucdn_get_general_category(unsigned int code)
+{
+ return get_ucd_record(code)->category;
+}
+
+int ucdn_get_bidi_class(unsigned int code)
+{
+ return get_ucd_record(code)->bidi_class;
+}
+
+int ucdn_get_mirrored(unsigned int code)
+{
+ return get_ucd_record(code)->mirrored;
+}
+
+int ucdn_get_script(unsigned int code)
+{
+ return get_ucd_record(code)->script;
+}
+
+unsigned int ucdn_mirror(unsigned int code)
+{
+ MirrorPair mp = {0};
+ MirrorPair *res;
+
+ if (get_ucd_record(code)->mirrored == 0)
+ return code;
+
+ mp.from = code;
+ res = bsearch(&mp, mirror_pairs, BIDI_MIRROR_LEN, sizeof(MirrorPair),
+ compare_mp);
+
+ if (res == NULL)
+ return code;
+ else
+ return res->to;
+}
+
+int ucdn_decompose(unsigned int code, unsigned int *a, unsigned int *b)
+{
+ const unsigned short *rec;
+ int len;
+
+ if (hangul_pair_decompose(code, a, b))
+ return 1;
+
+ rec = get_decomp_record(code);
+ len = rec[0] >> 8;
+
+ if ((rec[0] & 0xff) != 0 || len == 0)
+ return 0;
+
+ rec++;
+ *a = decode_utf16(&rec);
+ if (len > 1)
+ *b = decode_utf16(&rec);
+ else
+ *b = 0;
+
+ return 1;
+}
+
+int ucdn_compose(unsigned int *code, unsigned int a, unsigned int b)
+{
+ int l, r, index, indexi, offset;
+
+ if (hangul_pair_compose(code, a, b))
+ return 1;
+
+ l = get_comp_index(a, nfc_first);
+ r = get_comp_index(b, nfc_last);
+
+ if (l < 0 || r < 0)
+ return 0;
+
+ indexi = l * TOTAL_LAST + r;
+ index = comp_index0[indexi >> (COMP_SHIFT1+COMP_SHIFT2)] << COMP_SHIFT1;
+ offset = (indexi >> COMP_SHIFT2) & ((1<<COMP_SHIFT1) - 1);
+ index = comp_index1[index + offset] << COMP_SHIFT2;
+ offset = indexi & ((1<<COMP_SHIFT2) - 1);
+ *code = comp_data[index + offset];
+
+ return *code != 0;
+}
+
+int ucdn_compat_decompose(unsigned int code, unsigned int *decomposed)
+{
+ int i, len;
+ const unsigned short *rec = get_decomp_record(code);
+ len = rec[0] >> 8;
+
+ if (len == 0)
+ return 0;
+
+ rec++;
+ for (i = 0; i < len; i++)
+ decomposed[i] = decode_utf16(&rec);
+
+ return len;
+}
diff --git a/source/fitz/ucdn.h b/source/fitz/ucdn.h
new file mode 100644
index 00000000..63fe7516
--- /dev/null
+++ b/source/fitz/ucdn.h
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2012 Grigori Goronzy <greg@kinoho.net>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef UCDN_H
+#define UCDN_H
+
+#define UCDN_EAST_ASIAN_F 0
+#define UCDN_EAST_ASIAN_H 1
+#define UCDN_EAST_ASIAN_W 2
+#define UCDN_EAST_ASIAN_NA 3
+#define UCDN_EAST_ASIAN_A 4
+#define UCDN_EAST_ASIAN_N 5
+
+#define UCDN_SCRIPT_COMMON 0
+#define UCDN_SCRIPT_LATIN 1
+#define UCDN_SCRIPT_GREEK 2
+#define UCDN_SCRIPT_CYRILLIC 3
+#define UCDN_SCRIPT_ARMENIAN 4
+#define UCDN_SCRIPT_HEBREW 5
+#define UCDN_SCRIPT_ARABIC 6
+#define UCDN_SCRIPT_SYRIAC 7
+#define UCDN_SCRIPT_THAANA 8
+#define UCDN_SCRIPT_DEVANAGARI 9
+#define UCDN_SCRIPT_BENGALI 10
+#define UCDN_SCRIPT_GURMUKHI 11
+#define UCDN_SCRIPT_GUJARATI 12
+#define UCDN_SCRIPT_ORIYA 13
+#define UCDN_SCRIPT_TAMIL 14
+#define UCDN_SCRIPT_TELUGU 15
+#define UCDN_SCRIPT_KANNADA 16
+#define UCDN_SCRIPT_MALAYALAM 17
+#define UCDN_SCRIPT_SINHALA 18
+#define UCDN_SCRIPT_THAI 19
+#define UCDN_SCRIPT_LAO 20
+#define UCDN_SCRIPT_TIBETAN 21
+#define UCDN_SCRIPT_MYANMAR 22
+#define UCDN_SCRIPT_GEORGIAN 23
+#define UCDN_SCRIPT_HANGUL 24
+#define UCDN_SCRIPT_ETHIOPIC 25
+#define UCDN_SCRIPT_CHEROKEE 26
+#define UCDN_SCRIPT_CANADIAN_ABORIGINAL 27
+#define UCDN_SCRIPT_OGHAM 28
+#define UCDN_SCRIPT_RUNIC 29
+#define UCDN_SCRIPT_KHMER 30
+#define UCDN_SCRIPT_MONGOLIAN 31
+#define UCDN_SCRIPT_HIRAGANA 32
+#define UCDN_SCRIPT_KATAKANA 33
+#define UCDN_SCRIPT_BOPOMOFO 34
+#define UCDN_SCRIPT_HAN 35
+#define UCDN_SCRIPT_YI 36
+#define UCDN_SCRIPT_OLD_ITALIC 37
+#define UCDN_SCRIPT_GOTHIC 38
+#define UCDN_SCRIPT_DESERET 39
+#define UCDN_SCRIPT_INHERITED 40
+#define UCDN_SCRIPT_TAGALOG 41
+#define UCDN_SCRIPT_HANUNOO 42
+#define UCDN_SCRIPT_BUHID 43
+#define UCDN_SCRIPT_TAGBANWA 44
+#define UCDN_SCRIPT_LIMBU 45
+#define UCDN_SCRIPT_TAI_LE 46
+#define UCDN_SCRIPT_LINEAR_B 47
+#define UCDN_SCRIPT_UGARITIC 48
+#define UCDN_SCRIPT_SHAVIAN 49
+#define UCDN_SCRIPT_OSMANYA 50
+#define UCDN_SCRIPT_CYPRIOT 51
+#define UCDN_SCRIPT_BRAILLE 52
+#define UCDN_SCRIPT_BUGINESE 53
+#define UCDN_SCRIPT_COPTIC 54
+#define UCDN_SCRIPT_NEW_TAI_LUE 55
+#define UCDN_SCRIPT_GLAGOLITIC 56
+#define UCDN_SCRIPT_TIFINAGH 57
+#define UCDN_SCRIPT_SYLOTI_NAGRI 58
+#define UCDN_SCRIPT_OLD_PERSIAN 59
+#define UCDN_SCRIPT_KHAROSHTHI 60
+#define UCDN_SCRIPT_BALINESE 61
+#define UCDN_SCRIPT_CUNEIFORM 62
+#define UCDN_SCRIPT_PHOENICIAN 63
+#define UCDN_SCRIPT_PHAGS_PA 64
+#define UCDN_SCRIPT_NKO 65
+#define UCDN_SCRIPT_SUNDANESE 66
+#define UCDN_SCRIPT_LEPCHA 67
+#define UCDN_SCRIPT_OL_CHIKI 68
+#define UCDN_SCRIPT_VAI 69
+#define UCDN_SCRIPT_SAURASHTRA 70
+#define UCDN_SCRIPT_KAYAH_LI 71
+#define UCDN_SCRIPT_REJANG 72
+#define UCDN_SCRIPT_LYCIAN 73
+#define UCDN_SCRIPT_CARIAN 74
+#define UCDN_SCRIPT_LYDIAN 75
+#define UCDN_SCRIPT_CHAM 76
+#define UCDN_SCRIPT_TAI_THAM 77
+#define UCDN_SCRIPT_TAI_VIET 78
+#define UCDN_SCRIPT_AVESTAN 79
+#define UCDN_SCRIPT_EGYPTIAN_HIEROGLYPHS 80
+#define UCDN_SCRIPT_SAMARITAN 81
+#define UCDN_SCRIPT_LISU 82
+#define UCDN_SCRIPT_BAMUM 83
+#define UCDN_SCRIPT_JAVANESE 84
+#define UCDN_SCRIPT_MEETEI_MAYEK 85
+#define UCDN_SCRIPT_IMPERIAL_ARAMAIC 86
+#define UCDN_SCRIPT_OLD_SOUTH_ARABIAN 87
+#define UCDN_SCRIPT_INSCRIPTIONAL_PARTHIAN 88
+#define UCDN_SCRIPT_INSCRIPTIONAL_PAHLAVI 89
+#define UCDN_SCRIPT_OLD_TURKIC 90
+#define UCDN_SCRIPT_KAITHI 91
+#define UCDN_SCRIPT_BATAK 92
+#define UCDN_SCRIPT_BRAHMI 93
+#define UCDN_SCRIPT_MANDAIC 94
+#define UCDN_SCRIPT_CHAKMA 95
+#define UCDN_SCRIPT_MEROITIC_CURSIVE 96
+#define UCDN_SCRIPT_MEROITIC_HIEROGLYPHS 97
+#define UCDN_SCRIPT_MIAO 98
+#define UCDN_SCRIPT_SHARADA 99
+#define UCDN_SCRIPT_SORA_SOMPENG 100
+#define UCDN_SCRIPT_TAKRI 101
+#define UCDN_SCRIPT_UNKNOWN 102
+
+#define UCDN_GENERAL_CATEGORY_CC 0
+#define UCDN_GENERAL_CATEGORY_CF 1
+#define UCDN_GENERAL_CATEGORY_CN 2
+#define UCDN_GENERAL_CATEGORY_CO 3
+#define UCDN_GENERAL_CATEGORY_CS 4
+#define UCDN_GENERAL_CATEGORY_LL 5
+#define UCDN_GENERAL_CATEGORY_LM 6
+#define UCDN_GENERAL_CATEGORY_LO 7
+#define UCDN_GENERAL_CATEGORY_LT 8
+#define UCDN_GENERAL_CATEGORY_LU 9
+#define UCDN_GENERAL_CATEGORY_MC 10
+#define UCDN_GENERAL_CATEGORY_ME 11
+#define UCDN_GENERAL_CATEGORY_MN 12
+#define UCDN_GENERAL_CATEGORY_ND 13
+#define UCDN_GENERAL_CATEGORY_NL 14
+#define UCDN_GENERAL_CATEGORY_NO 15
+#define UCDN_GENERAL_CATEGORY_PC 16
+#define UCDN_GENERAL_CATEGORY_PD 17
+#define UCDN_GENERAL_CATEGORY_PE 18
+#define UCDN_GENERAL_CATEGORY_PF 19
+#define UCDN_GENERAL_CATEGORY_PI 20
+#define UCDN_GENERAL_CATEGORY_PO 21
+#define UCDN_GENERAL_CATEGORY_PS 22
+#define UCDN_GENERAL_CATEGORY_SC 23
+#define UCDN_GENERAL_CATEGORY_SK 24
+#define UCDN_GENERAL_CATEGORY_SM 25
+#define UCDN_GENERAL_CATEGORY_SO 26
+#define UCDN_GENERAL_CATEGORY_ZL 27
+#define UCDN_GENERAL_CATEGORY_ZP 28
+#define UCDN_GENERAL_CATEGORY_ZS 29
+
+#define UCDN_BIDI_CLASS_L 0
+#define UCDN_BIDI_CLASS_LRE 1
+#define UCDN_BIDI_CLASS_LRO 2
+#define UCDN_BIDI_CLASS_R 3
+#define UCDN_BIDI_CLASS_AL 4
+#define UCDN_BIDI_CLASS_RLE 5
+#define UCDN_BIDI_CLASS_RLO 6
+#define UCDN_BIDI_CLASS_PDF 7
+#define UCDN_BIDI_CLASS_EN 8
+#define UCDN_BIDI_CLASS_ES 9
+#define UCDN_BIDI_CLASS_ET 10
+#define UCDN_BIDI_CLASS_AN 11
+#define UCDN_BIDI_CLASS_CS 12
+#define UCDN_BIDI_CLASS_NSM 13
+#define UCDN_BIDI_CLASS_BN 14
+#define UCDN_BIDI_CLASS_B 15
+#define UCDN_BIDI_CLASS_S 16
+#define UCDN_BIDI_CLASS_WS 17
+#define UCDN_BIDI_CLASS_ON 18
+
+/**
+ * Return version of the Unicode database.
+ *
+ * @return Unicode database version
+ */
+const char *ucdn_get_unicode_version(void);
+
+/**
+ * Get combining class of a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @return combining class value, as defined in UAX#44
+ */
+int ucdn_get_combining_class(unsigned int code);
+
+/**
+ * Get east-asian width of a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @return value according to UCDN_EAST_ASIAN_* and as defined in UAX#11.
+ */
+int ucdn_get_east_asian_width(unsigned int code);
+
+/**
+ * Get general category of a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @return value according to UCDN_GENERAL_CATEGORY_* and as defined in
+ * UAX#44.
+ */
+int ucdn_get_general_category(unsigned int code);
+
+/**
+ * Get bidirectional class of a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @return value according to UCDN_BIDI_CLASS_* and as defined in UAX#44.
+ */
+int ucdn_get_bidi_class(unsigned int code);
+
+/**
+ * Get script of a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @return value according to UCDN_SCRIPT_* and as defined in UAX#24.
+ */
+int ucdn_get_script(unsigned int code);
+
+/**
+ * Check if codepoint can be mirrored.
+ *
+ * @param code Unicode codepoint
+ * @return 1 if mirrored character exists, otherwise 0
+ */
+int ucdn_get_mirrored(unsigned int code);
+
+/**
+ * Mirror a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @return mirrored codepoint or the original codepoint if no
+ * mirrored character exists
+ */
+unsigned int ucdn_mirror(unsigned int code);
+
+/**
+ * Pairwise canonical decomposition of a codepoint. This includes
+ * Hangul Jamo decomposition (see chapter 3.12 of the Unicode core
+ * specification).
+ *
+ * Hangul is decomposed into L and V jamos for LV forms, and an
+ * LV precomposed syllable and a T jamo for LVT forms.
+ *
+ * @param code Unicode codepoint
+ * @param a filled with first codepoint of decomposition
+ * @param b filled with second codepoint of decomposition, or 0
+ * @return success
+ */
+int ucdn_decompose(unsigned int code, unsigned int *a, unsigned int *b);
+
+/**
+ * Compatibility decomposition of a codepoint.
+ *
+ * @param code Unicode codepoint
+ * @param decomposed filled with decomposition, must be able to hold 18
+ * characters
+ * @return length of decomposition or 0 in case none exists
+ */
+int ucdn_compat_decompose(unsigned int code, unsigned int *decomposed);
+
+/**
+ * Pairwise canonical composition of two codepoints. This includes
+ * Hangul Jamo composition (see chapter 3.12 of the Unicode core
+ * specification).
+ *
+ * Hangul composition expects either L and V jamos, or an LV
+ * precomposed syllable and a T jamo. This is exactly the inverse
+ * of pairwise Hangul decomposition.
+ *
+ * @param code filled with composition
+ * @param a first codepoint
+ * @param b second codepoint
+ * @return success
+ */
+int ucdn_compose(unsigned int *code, unsigned int a, unsigned int b);
+
+#endif
diff --git a/source/fitz/unicodedata_db.h b/source/fitz/unicodedata_db.h
new file mode 100644
index 00000000..a0a4347a
--- /dev/null
+++ b/source/fitz/unicodedata_db.h
@@ -0,0 +1,4753 @@
+/* this file was generated by makeunicodedata.py 3.2 */
+
+#define UNIDATA_VERSION "6.2.0"
+/* a list of unique database records */
+static const UCDRecord ucd_records[] = {
+ {2, 0, 18, 0, 5, 0, 102},
+ {0, 0, 14, 0, 5, 0, 0},
+ {0, 0, 16, 0, 5, 0, 0},
+ {0, 0, 15, 0, 5, 0, 0},
+ {0, 0, 17, 0, 5, 0, 0},
+ {29, 0, 17, 0, 3, 0, 0},
+ {21, 0, 18, 0, 3, 0, 0},
+ {21, 0, 10, 0, 3, 0, 0},
+ {23, 0, 10, 0, 3, 0, 0},
+ {22, 0, 18, 1, 3, 0, 0},
+ {18, 0, 18, 1, 3, 0, 0},
+ {25, 0, 9, 0, 3, 0, 0},
+ {21, 0, 12, 0, 3, 0, 0},
+ {17, 0, 9, 0, 3, 0, 0},
+ {13, 0, 8, 0, 3, 0, 0},
+ {25, 0, 18, 1, 3, 0, 0},
+ {25, 0, 18, 0, 3, 0, 0},
+ {9, 0, 0, 0, 3, 0, 1},
+ {24, 0, 18, 0, 3, 0, 0},
+ {16, 0, 18, 0, 3, 0, 0},
+ {5, 0, 0, 0, 3, 0, 1},
+ {29, 0, 12, 0, 5, 0, 0},
+ {21, 0, 18, 0, 4, 0, 0},
+ {23, 0, 10, 0, 4, 0, 0},
+ {26, 0, 18, 0, 3, 0, 0},
+ {24, 0, 18, 0, 4, 0, 0},
+ {26, 0, 18, 0, 5, 0, 0},
+ {7, 0, 0, 0, 4, 0, 1},
+ {20, 0, 18, 1, 5, 0, 0},
+ {1, 0, 14, 0, 4, 0, 0},
+ {26, 0, 18, 0, 4, 0, 0},
+ {26, 0, 10, 0, 4, 0, 0},
+ {25, 0, 10, 0, 4, 0, 0},
+ {15, 0, 8, 0, 4, 0, 0},
+ {5, 0, 0, 0, 5, 0, 0},
+ {19, 0, 18, 1, 5, 0, 0},
+ {15, 0, 18, 0, 4, 0, 0},
+ {9, 0, 0, 0, 5, 0, 1},
+ {9, 0, 0, 0, 4, 0, 1},
+ {25, 0, 18, 0, 4, 0, 0},
+ {5, 0, 0, 0, 4, 0, 1},
+ {5, 0, 0, 0, 5, 0, 1},
+ {7, 0, 0, 0, 5, 0, 1},
+ {8, 0, 0, 0, 5, 0, 1},
+ {6, 0, 0, 0, 5, 0, 1},
+ {6, 0, 18, 0, 5, 0, 0},
+ {6, 0, 0, 0, 5, 0, 0},
+ {24, 0, 18, 0, 5, 0, 0},
+ {6, 0, 18, 0, 4, 0, 0},
+ {6, 0, 0, 0, 4, 0, 0},
+ {24, 0, 18, 0, 5, 0, 34},
+ {12, 230, 13, 0, 4, 0, 40},
+ {12, 232, 13, 0, 4, 0, 40},
+ {12, 220, 13, 0, 4, 0, 40},
+ {12, 216, 13, 0, 4, 0, 40},
+ {12, 202, 13, 0, 4, 0, 40},
+ {12, 1, 13, 0, 4, 0, 40},
+ {12, 240, 13, 0, 4, 0, 40},
+ {12, 0, 13, 0, 4, 0, 40},
+ {12, 233, 13, 0, 4, 0, 40},
+ {12, 234, 13, 0, 4, 0, 40},
+ {9, 0, 0, 0, 5, 0, 2},
+ {5, 0, 0, 0, 5, 0, 2},
+ {24, 0, 18, 0, 5, 0, 2},
+ {2, 0, 18, 0, 5, 0, 102},
+ {6, 0, 0, 0, 5, 0, 2},
+ {21, 0, 18, 0, 5, 0, 0},
+ {9, 0, 0, 0, 4, 0, 2},
+ {5, 0, 0, 0, 4, 0, 2},
+ {9, 0, 0, 0, 5, 0, 54},
+ {5, 0, 0, 0, 5, 0, 54},
+ {25, 0, 18, 0, 5, 0, 2},
+ {9, 0, 0, 0, 5, 0, 3},
+ {9, 0, 0, 0, 4, 0, 3},
+ {5, 0, 0, 0, 4, 0, 3},
+ {5, 0, 0, 0, 5, 0, 3},
+ {26, 0, 0, 0, 5, 0, 3},
+ {12, 230, 13, 0, 5, 0, 3},
+ {12, 230, 13, 0, 5, 0, 40},
+ {11, 0, 13, 0, 5, 0, 3},
+ {9, 0, 0, 0, 5, 0, 4},
+ {6, 0, 0, 0, 5, 0, 4},
+ {21, 0, 0, 0, 5, 0, 4},
+ {5, 0, 0, 0, 5, 0, 4},
+ {21, 0, 0, 0, 5, 0, 0},
+ {17, 0, 18, 0, 5, 0, 4},
+ {23, 0, 10, 0, 5, 0, 4},
+ {12, 220, 13, 0, 5, 0, 5},
+ {12, 230, 13, 0, 5, 0, 5},
+ {12, 222, 13, 0, 5, 0, 5},
+ {12, 228, 13, 0, 5, 0, 5},
+ {12, 10, 13, 0, 5, 0, 5},
+ {12, 11, 13, 0, 5, 0, 5},
+ {12, 12, 13, 0, 5, 0, 5},
+ {12, 13, 13, 0, 5, 0, 5},
+ {12, 14, 13, 0, 5, 0, 5},
+ {12, 15, 13, 0, 5, 0, 5},
+ {12, 16, 13, 0, 5, 0, 5},
+ {12, 17, 13, 0, 5, 0, 5},
+ {12, 18, 13, 0, 5, 0, 5},
+ {12, 19, 13, 0, 5, 0, 5},
+ {12, 20, 13, 0, 5, 0, 5},
+ {12, 21, 13, 0, 5, 0, 5},
+ {12, 22, 13, 0, 5, 0, 5},
+ {17, 0, 3, 0, 5, 0, 5},
+ {12, 23, 13, 0, 5, 0, 5},
+ {21, 0, 3, 0, 5, 0, 5},
+ {12, 24, 13, 0, 5, 0, 5},
+ {12, 25, 13, 0, 5, 0, 5},
+ {7, 0, 3, 0, 5, 0, 5},
+ {1, 0, 11, 0, 5, 0, 6},
+ {25, 0, 18, 0, 5, 0, 6},
+ {25, 0, 4, 0, 5, 0, 6},
+ {21, 0, 10, 0, 5, 0, 6},
+ {23, 0, 4, 0, 5, 0, 6},
+ {21, 0, 12, 0, 5, 0, 0},
+ {21, 0, 4, 0, 5, 0, 6},
+ {26, 0, 18, 0, 5, 0, 6},
+ {12, 230, 13, 0, 5, 0, 6},
+ {12, 30, 13, 0, 5, 0, 6},
+ {12, 31, 13, 0, 5, 0, 6},
+ {12, 32, 13, 0, 5, 0, 6},
+ {21, 0, 4, 0, 5, 0, 0},
+ {7, 0, 4, 0, 5, 0, 6},
+ {6, 0, 4, 0, 5, 0, 0},
+ {12, 27, 13, 0, 5, 0, 40},
+ {12, 28, 13, 0, 5, 0, 40},
+ {12, 29, 13, 0, 5, 0, 40},
+ {12, 30, 13, 0, 5, 0, 40},
+ {12, 31, 13, 0, 5, 0, 40},
+ {12, 32, 13, 0, 5, 0, 40},
+ {12, 33, 13, 0, 5, 0, 40},
+ {12, 34, 13, 0, 5, 0, 40},
+ {12, 220, 13, 0, 5, 0, 40},
+ {12, 220, 13, 0, 5, 0, 6},
+ {13, 0, 11, 0, 5, 0, 0},
+ {21, 0, 11, 0, 5, 0, 6},
+ {12, 35, 13, 0, 5, 0, 40},
+ {1, 0, 11, 0, 5, 0, 0},
+ {6, 0, 4, 0, 5, 0, 6},
+ {13, 0, 8, 0, 5, 0, 6},
+ {26, 0, 4, 0, 5, 0, 6},
+ {21, 0, 4, 0, 5, 0, 7},
+ {1, 0, 4, 0, 5, 0, 7},
+ {7, 0, 4, 0, 5, 0, 7},
+ {12, 36, 13, 0, 5, 0, 7},
+ {12, 230, 13, 0, 5, 0, 7},
+ {12, 220, 13, 0, 5, 0, 7},
+ {7, 0, 4, 0, 5, 0, 8},
+ {12, 0, 13, 0, 5, 0, 8},
+ {13, 0, 3, 0, 5, 0, 65},
+ {7, 0, 3, 0, 5, 0, 65},
+ {12, 230, 13, 0, 5, 0, 65},
+ {12, 220, 13, 0, 5, 0, 65},
+ {6, 0, 3, 0, 5, 0, 65},
+ {26, 0, 18, 0, 5, 0, 65},
+ {21, 0, 18, 0, 5, 0, 65},
+ {7, 0, 3, 0, 5, 0, 81},
+ {12, 230, 13, 0, 5, 0, 81},
+ {6, 0, 3, 0, 5, 0, 81},
+ {21, 0, 3, 0, 5, 0, 81},
+ {7, 0, 3, 0, 5, 0, 94},
+ {12, 220, 13, 0, 5, 0, 94},
+ {21, 0, 3, 0, 5, 0, 94},
+ {12, 27, 13, 0, 5, 0, 6},
+ {12, 28, 13, 0, 5, 0, 6},
+ {12, 29, 13, 0, 5, 0, 6},
+ {12, 0, 13, 0, 5, 0, 9},
+ {10, 0, 0, 0, 5, 0, 9},
+ {7, 0, 0, 0, 5, 0, 9},
+ {12, 7, 13, 0, 5, 0, 9},
+ {12, 9, 13, 0, 5, 0, 9},
+ {12, 230, 13, 0, 5, 0, 9},
+ {13, 0, 0, 0, 5, 0, 9},
+ {21, 0, 0, 0, 5, 0, 9},
+ {6, 0, 0, 0, 5, 0, 9},
+ {12, 0, 13, 0, 5, 0, 10},
+ {10, 0, 0, 0, 5, 0, 10},
+ {7, 0, 0, 0, 5, 0, 10},
+ {12, 7, 13, 0, 5, 0, 10},
+ {12, 9, 13, 0, 5, 0, 10},
+ {13, 0, 0, 0, 5, 0, 10},
+ {23, 0, 10, 0, 5, 0, 10},
+ {15, 0, 0, 0, 5, 0, 10},
+ {26, 0, 0, 0, 5, 0, 10},
+ {12, 0, 13, 0, 5, 0, 11},
+ {10, 0, 0, 0, 5, 0, 11},
+ {7, 0, 0, 0, 5, 0, 11},
+ {12, 7, 13, 0, 5, 0, 11},
+ {12, 9, 13, 0, 5, 0, 11},
+ {13, 0, 0, 0, 5, 0, 11},
+ {12, 0, 13, 0, 5, 0, 12},
+ {10, 0, 0, 0, 5, 0, 12},
+ {7, 0, 0, 0, 5, 0, 12},
+ {12, 7, 13, 0, 5, 0, 12},
+ {12, 9, 13, 0, 5, 0, 12},
+ {13, 0, 0, 0, 5, 0, 12},
+ {21, 0, 0, 0, 5, 0, 12},
+ {23, 0, 10, 0, 5, 0, 12},
+ {12, 0, 13, 0, 5, 0, 13},
+ {10, 0, 0, 0, 5, 0, 13},
+ {7, 0, 0, 0, 5, 0, 13},
+ {12, 7, 13, 0, 5, 0, 13},
+ {12, 9, 13, 0, 5, 0, 13},
+ {13, 0, 0, 0, 5, 0, 13},
+ {26, 0, 0, 0, 5, 0, 13},
+ {15, 0, 0, 0, 5, 0, 13},
+ {12, 0, 13, 0, 5, 0, 14},
+ {7, 0, 0, 0, 5, 0, 14},
+ {10, 0, 0, 0, 5, 0, 14},
+ {12, 9, 13, 0, 5, 0, 14},
+ {13, 0, 0, 0, 5, 0, 14},
+ {15, 0, 0, 0, 5, 0, 14},
+ {26, 0, 18, 0, 5, 0, 14},
+ {23, 0, 10, 0, 5, 0, 14},
+ {10, 0, 0, 0, 5, 0, 15},
+ {7, 0, 0, 0, 5, 0, 15},
+ {12, 0, 13, 0, 5, 0, 15},
+ {12, 9, 13, 0, 5, 0, 15},
+ {12, 84, 13, 0, 5, 0, 15},
+ {12, 91, 13, 0, 5, 0, 15},
+ {13, 0, 0, 0, 5, 0, 15},
+ {15, 0, 18, 0, 5, 0, 15},
+ {26, 0, 0, 0, 5, 0, 15},
+ {10, 0, 0, 0, 5, 0, 16},
+ {7, 0, 0, 0, 5, 0, 16},
+ {12, 7, 13, 0, 5, 0, 16},
+ {12, 0, 0, 0, 5, 0, 16},
+ {12, 0, 13, 0, 5, 0, 16},
+ {12, 9, 13, 0, 5, 0, 16},
+ {13, 0, 0, 0, 5, 0, 16},
+ {10, 0, 0, 0, 5, 0, 17},
+ {7, 0, 0, 0, 5, 0, 17},
+ {12, 0, 13, 0, 5, 0, 17},
+ {12, 9, 13, 0, 5, 0, 17},
+ {13, 0, 0, 0, 5, 0, 17},
+ {15, 0, 0, 0, 5, 0, 17},
+ {26, 0, 0, 0, 5, 0, 17},
+ {10, 0, 0, 0, 5, 0, 18},
+ {7, 0, 0, 0, 5, 0, 18},
+ {12, 9, 13, 0, 5, 0, 18},
+ {12, 0, 13, 0, 5, 0, 18},
+ {21, 0, 0, 0, 5, 0, 18},
+ {7, 0, 0, 0, 5, 0, 19},
+ {12, 0, 13, 0, 5, 0, 19},
+ {12, 103, 13, 0, 5, 0, 19},
+ {12, 9, 13, 0, 5, 0, 19},
+ {23, 0, 10, 0, 5, 0, 0},
+ {6, 0, 0, 0, 5, 0, 19},
+ {12, 107, 13, 0, 5, 0, 19},
+ {21, 0, 0, 0, 5, 0, 19},
+ {13, 0, 0, 0, 5, 0, 19},
+ {7, 0, 0, 0, 5, 0, 20},
+ {12, 0, 13, 0, 5, 0, 20},
+ {12, 118, 13, 0, 5, 0, 20},
+ {6, 0, 0, 0, 5, 0, 20},
+ {12, 122, 13, 0, 5, 0, 20},
+ {13, 0, 0, 0, 5, 0, 20},
+ {7, 0, 0, 0, 5, 0, 21},
+ {26, 0, 0, 0, 5, 0, 21},
+ {21, 0, 0, 0, 5, 0, 21},
+ {12, 220, 13, 0, 5, 0, 21},
+ {13, 0, 0, 0, 5, 0, 21},
+ {15, 0, 0, 0, 5, 0, 21},
+ {12, 216, 13, 0, 5, 0, 21},
+ {22, 0, 18, 1, 5, 0, 21},
+ {18, 0, 18, 1, 5, 0, 21},
+ {10, 0, 0, 0, 5, 0, 21},
+ {12, 129, 13, 0, 5, 0, 21},
+ {12, 130, 13, 0, 5, 0, 21},
+ {12, 0, 13, 0, 5, 0, 21},
+ {12, 132, 13, 0, 5, 0, 21},
+ {12, 230, 13, 0, 5, 0, 21},
+ {12, 9, 13, 0, 5, 0, 21},
+ {26, 0, 0, 0, 5, 0, 0},
+ {7, 0, 0, 0, 5, 0, 22},
+ {10, 0, 0, 0, 5, 0, 22},
+ {12, 0, 13, 0, 5, 0, 22},
+ {12, 7, 13, 0, 5, 0, 22},
+ {12, 9, 13, 0, 5, 0, 22},
+ {13, 0, 0, 0, 5, 0, 22},
+ {21, 0, 0, 0, 5, 0, 22},
+ {12, 220, 13, 0, 5, 0, 22},
+ {26, 0, 0, 0, 5, 0, 22},
+ {9, 0, 0, 0, 5, 0, 23},
+ {7, 0, 0, 0, 5, 0, 23},
+ {6, 0, 0, 0, 5, 0, 23},
+ {7, 0, 0, 0, 2, 0, 24},
+ {7, 0, 0, 0, 5, 0, 24},
+ {7, 0, 0, 0, 5, 0, 25},
+ {12, 230, 13, 0, 5, 0, 25},
+ {21, 0, 0, 0, 5, 0, 25},
+ {15, 0, 0, 0, 5, 0, 25},
+ {26, 0, 18, 0, 5, 0, 25},
+ {7, 0, 0, 0, 5, 0, 26},
+ {17, 0, 18, 0, 5, 0, 27},
+ {7, 0, 0, 0, 5, 0, 27},
+ {21, 0, 0, 0, 5, 0, 27},
+ {29, 0, 17, 0, 5, 0, 28},
+ {7, 0, 0, 0, 5, 0, 28},
+ {22, 0, 18, 1, 5, 0, 28},
+ {18, 0, 18, 1, 5, 0, 28},
+ {7, 0, 0, 0, 5, 0, 29},
+ {14, 0, 0, 0, 5, 0, 29},
+ {7, 0, 0, 0, 5, 0, 41},
+ {12, 0, 13, 0, 5, 0, 41},
+ {12, 9, 13, 0, 5, 0, 41},
+ {7, 0, 0, 0, 5, 0, 42},
+ {12, 0, 13, 0, 5, 0, 42},
+ {12, 9, 13, 0, 5, 0, 42},
+ {7, 0, 0, 0, 5, 0, 43},
+ {12, 0, 13, 0, 5, 0, 43},
+ {7, 0, 0, 0, 5, 0, 44},
+ {12, 0, 13, 0, 5, 0, 44},
+ {7, 0, 0, 0, 5, 0, 30},
+ {12, 0, 13, 0, 5, 0, 30},
+ {10, 0, 0, 0, 5, 0, 30},
+ {12, 9, 13, 0, 5, 0, 30},
+ {21, 0, 0, 0, 5, 0, 30},
+ {6, 0, 0, 0, 5, 0, 30},
+ {23, 0, 10, 0, 5, 0, 30},
+ {12, 230, 13, 0, 5, 0, 30},
+ {13, 0, 0, 0, 5, 0, 30},
+ {15, 0, 18, 0, 5, 0, 30},
+ {21, 0, 18, 0, 5, 0, 31},
+ {17, 0, 18, 0, 5, 0, 31},
+ {12, 0, 13, 0, 5, 0, 31},
+ {29, 0, 17, 0, 5, 0, 31},
+ {13, 0, 0, 0, 5, 0, 31},
+ {7, 0, 0, 0, 5, 0, 31},
+ {6, 0, 0, 0, 5, 0, 31},
+ {12, 228, 13, 0, 5, 0, 31},
+ {7, 0, 0, 0, 5, 0, 45},
+ {12, 0, 13, 0, 5, 0, 45},
+ {10, 0, 0, 0, 5, 0, 45},
+ {12, 222, 13, 0, 5, 0, 45},
+ {12, 230, 13, 0, 5, 0, 45},
+ {12, 220, 13, 0, 5, 0, 45},
+ {26, 0, 18, 0, 5, 0, 45},
+ {21, 0, 18, 0, 5, 0, 45},
+ {13, 0, 0, 0, 5, 0, 45},
+ {7, 0, 0, 0, 5, 0, 46},
+ {7, 0, 0, 0, 5, 0, 55},
+ {10, 0, 0, 0, 5, 0, 55},
+ {13, 0, 0, 0, 5, 0, 55},
+ {15, 0, 0, 0, 5, 0, 55},
+ {26, 0, 18, 0, 5, 0, 55},
+ {26, 0, 18, 0, 5, 0, 30},
+ {7, 0, 0, 0, 5, 0, 53},
+ {12, 230, 13, 0, 5, 0, 53},
+ {12, 220, 13, 0, 5, 0, 53},
+ {10, 0, 0, 0, 5, 0, 53},
+ {21, 0, 0, 0, 5, 0, 53},
+ {7, 0, 0, 0, 5, 0, 77},
+ {10, 0, 0, 0, 5, 0, 77},
+ {12, 0, 13, 0, 5, 0, 77},
+ {12, 9, 13, 0, 5, 0, 77},
+ {12, 230, 13, 0, 5, 0, 77},
+ {12, 220, 13, 0, 5, 0, 77},
+ {13, 0, 0, 0, 5, 0, 77},
+ {21, 0, 0, 0, 5, 0, 77},
+ {6, 0, 0, 0, 5, 0, 77},
+ {12, 0, 13, 0, 5, 0, 61},
+ {10, 0, 0, 0, 5, 0, 61},
+ {7, 0, 0, 0, 5, 0, 61},
+ {12, 7, 13, 0, 5, 0, 61},
+ {10, 9, 0, 0, 5, 0, 61},
+ {13, 0, 0, 0, 5, 0, 61},
+ {21, 0, 0, 0, 5, 0, 61},
+ {26, 0, 0, 0, 5, 0, 61},
+ {12, 230, 13, 0, 5, 0, 61},
+ {12, 220, 13, 0, 5, 0, 61},
+ {12, 0, 13, 0, 5, 0, 66},
+ {10, 0, 0, 0, 5, 0, 66},
+ {7, 0, 0, 0, 5, 0, 66},
+ {10, 9, 0, 0, 5, 0, 66},
+ {12, 9, 13, 0, 5, 0, 66},
+ {13, 0, 0, 0, 5, 0, 66},
+ {7, 0, 0, 0, 5, 0, 92},
+ {12, 7, 13, 0, 5, 0, 92},
+ {10, 0, 0, 0, 5, 0, 92},
+ {12, 0, 13, 0, 5, 0, 92},
+ {10, 9, 0, 0, 5, 0, 92},
+ {21, 0, 0, 0, 5, 0, 92},
+ {7, 0, 0, 0, 5, 0, 67},
+ {10, 0, 0, 0, 5, 0, 67},
+ {12, 0, 13, 0, 5, 0, 67},
+ {12, 7, 13, 0, 5, 0, 67},
+ {21, 0, 0, 0, 5, 0, 67},
+ {13, 0, 0, 0, 5, 0, 67},
+ {13, 0, 0, 0, 5, 0, 68},
+ {7, 0, 0, 0, 5, 0, 68},
+ {6, 0, 0, 0, 5, 0, 68},
+ {21, 0, 0, 0, 5, 0, 68},
+ {21, 0, 0, 0, 5, 0, 66},
+ {12, 1, 13, 0, 5, 0, 40},
+ {10, 0, 0, 0, 5, 0, 0},
+ {7, 0, 0, 0, 5, 0, 0},
+ {6, 0, 0, 0, 5, 0, 3},
+ {12, 234, 13, 0, 5, 0, 40},
+ {12, 214, 13, 0, 5, 0, 40},
+ {12, 202, 13, 0, 5, 0, 40},
+ {12, 233, 13, 0, 5, 0, 40},
+ {8, 0, 0, 0, 5, 0, 2},
+ {29, 0, 17, 0, 5, 0, 0},
+ {1, 0, 14, 0, 5, 0, 0},
+ {1, 0, 14, 0, 5, 0, 40},
+ {1, 0, 0, 0, 5, 0, 0},
+ {1, 0, 3, 0, 5, 0, 0},
+ {17, 0, 18, 0, 4, 0, 0},
+ {17, 0, 18, 0, 5, 0, 0},
+ {20, 0, 18, 0, 4, 0, 0},
+ {19, 0, 18, 0, 4, 0, 0},
+ {22, 0, 18, 0, 5, 0, 0},
+ {20, 0, 18, 0, 5, 0, 0},
+ {27, 0, 17, 0, 5, 0, 0},
+ {28, 0, 15, 0, 5, 0, 0},
+ {1, 0, 1, 0, 5, 0, 0},
+ {1, 0, 5, 0, 5, 0, 0},
+ {1, 0, 7, 0, 5, 0, 0},
+ {1, 0, 2, 0, 5, 0, 0},
+ {1, 0, 6, 0, 5, 0, 0},
+ {21, 0, 10, 0, 4, 0, 0},
+ {21, 0, 10, 0, 5, 0, 0},
+ {16, 0, 18, 0, 5, 0, 0},
+ {25, 0, 12, 0, 5, 0, 0},
+ {22, 0, 18, 1, 5, 0, 0},
+ {18, 0, 18, 1, 5, 0, 0},
+ {25, 0, 18, 0, 5, 0, 0},
+ {15, 0, 8, 0, 5, 0, 0},
+ {25, 0, 9, 0, 5, 0, 0},
+ {6, 0, 0, 0, 4, 0, 1},
+ {23, 0, 10, 0, 1, 0, 0},
+ {11, 0, 13, 0, 5, 0, 40},
+ {9, 0, 0, 0, 5, 0, 0},
+ {5, 0, 0, 0, 4, 0, 0},
+ {26, 0, 10, 0, 5, 0, 0},
+ {25, 0, 18, 1, 5, 0, 0},
+ {15, 0, 18, 0, 5, 0, 0},
+ {14, 0, 0, 0, 4, 0, 1},
+ {14, 0, 0, 0, 5, 0, 1},
+ {25, 0, 18, 1, 4, 0, 0},
+ {25, 0, 10, 0, 5, 0, 0},
+ {22, 0, 18, 1, 2, 0, 0},
+ {18, 0, 18, 1, 2, 0, 0},
+ {26, 0, 0, 0, 4, 0, 0},
+ {26, 0, 0, 0, 5, 0, 52},
+ {9, 0, 0, 0, 5, 0, 56},
+ {5, 0, 0, 0, 5, 0, 56},
+ {26, 0, 18, 0, 5, 0, 54},
+ {12, 230, 13, 0, 5, 0, 54},
+ {21, 0, 18, 0, 5, 0, 54},
+ {15, 0, 18, 0, 5, 0, 54},
+ {5, 0, 0, 0, 5, 0, 23},
+ {7, 0, 0, 0, 5, 0, 57},
+ {6, 0, 0, 0, 5, 0, 57},
+ {21, 0, 0, 0, 5, 0, 57},
+ {12, 9, 13, 0, 5, 0, 57},
+ {26, 0, 18, 0, 2, 0, 35},
+ {26, 0, 18, 0, 2, 0, 0},
+ {29, 0, 17, 0, 0, 0, 0},
+ {21, 0, 18, 0, 2, 0, 0},
+ {6, 0, 0, 0, 2, 0, 35},
+ {7, 0, 0, 0, 2, 0, 0},
+ {14, 0, 0, 0, 2, 0, 35},
+ {17, 0, 18, 0, 2, 0, 0},
+ {22, 0, 18, 0, 2, 0, 0},
+ {18, 0, 18, 0, 2, 0, 0},
+ {12, 218, 13, 0, 2, 0, 40},
+ {12, 228, 13, 0, 2, 0, 40},
+ {12, 232, 13, 0, 2, 0, 40},
+ {12, 222, 13, 0, 2, 0, 40},
+ {10, 224, 0, 0, 2, 0, 24},
+ {6, 0, 0, 0, 2, 0, 0},
+ {7, 0, 0, 0, 2, 0, 32},
+ {12, 8, 13, 0, 2, 0, 40},
+ {24, 0, 18, 0, 2, 0, 0},
+ {6, 0, 0, 0, 2, 0, 32},
+ {7, 0, 0, 0, 2, 0, 33},
+ {6, 0, 0, 0, 2, 0, 33},
+ {7, 0, 0, 0, 2, 0, 34},
+ {26, 0, 0, 0, 2, 0, 0},
+ {15, 0, 0, 0, 2, 0, 0},
+ {26, 0, 0, 0, 2, 0, 24},
+ {26, 0, 18, 0, 2, 0, 24},
+ {15, 0, 0, 0, 4, 0, 0},
+ {15, 0, 18, 0, 2, 0, 0},
+ {26, 0, 0, 0, 2, 0, 33},
+ {7, 0, 0, 0, 2, 0, 35},
+ {2, 0, 18, 0, 2, 0, 35},
+ {2, 0, 18, 0, 2, 0, 102},
+ {7, 0, 0, 0, 2, 0, 36},
+ {6, 0, 0, 0, 2, 0, 36},
+ {26, 0, 18, 0, 2, 0, 36},
+ {7, 0, 0, 0, 5, 0, 82},
+ {6, 0, 0, 0, 5, 0, 82},
+ {21, 0, 0, 0, 5, 0, 82},
+ {7, 0, 0, 0, 5, 0, 69},
+ {6, 0, 0, 0, 5, 0, 69},
+ {21, 0, 18, 0, 5, 0, 69},
+ {13, 0, 0, 0, 5, 0, 69},
+ {7, 0, 0, 0, 5, 0, 3},
+ {21, 0, 18, 0, 5, 0, 3},
+ {6, 0, 18, 0, 5, 0, 3},
+ {7, 0, 0, 0, 5, 0, 83},
+ {14, 0, 0, 0, 5, 0, 83},
+ {12, 230, 13, 0, 5, 0, 83},
+ {21, 0, 0, 0, 5, 0, 83},
+ {24, 0, 0, 0, 5, 0, 0},
+ {7, 0, 0, 0, 5, 0, 58},
+ {12, 0, 13, 0, 5, 0, 58},
+ {12, 9, 13, 0, 5, 0, 58},
+ {10, 0, 0, 0, 5, 0, 58},
+ {26, 0, 18, 0, 5, 0, 58},
+ {15, 0, 0, 0, 5, 0, 0},
+ {7, 0, 0, 0, 5, 0, 64},
+ {21, 0, 18, 0, 5, 0, 64},
+ {10, 0, 0, 0, 5, 0, 70},
+ {7, 0, 0, 0, 5, 0, 70},
+ {12, 9, 13, 0, 5, 0, 70},
+ {21, 0, 0, 0, 5, 0, 70},
+ {13, 0, 0, 0, 5, 0, 70},
+ {13, 0, 0, 0, 5, 0, 71},
+ {7, 0, 0, 0, 5, 0, 71},
+ {12, 0, 13, 0, 5, 0, 71},
+ {12, 220, 13, 0, 5, 0, 71},
+ {21, 0, 0, 0, 5, 0, 71},
+ {7, 0, 0, 0, 5, 0, 72},
+ {12, 0, 13, 0, 5, 0, 72},
+ {10, 0, 0, 0, 5, 0, 72},
+ {10, 9, 0, 0, 5, 0, 72},
+ {21, 0, 0, 0, 5, 0, 72},
+ {12, 0, 13, 0, 5, 0, 84},
+ {10, 0, 0, 0, 5, 0, 84},
+ {7, 0, 0, 0, 5, 0, 84},
+ {12, 7, 13, 0, 5, 0, 84},
+ {10, 9, 0, 0, 5, 0, 84},
+ {21, 0, 0, 0, 5, 0, 84},
+ {6, 0, 0, 0, 5, 0, 84},
+ {13, 0, 0, 0, 5, 0, 84},
+ {7, 0, 0, 0, 5, 0, 76},
+ {12, 0, 13, 0, 5, 0, 76},
+ {10, 0, 0, 0, 5, 0, 76},
+ {13, 0, 0, 0, 5, 0, 76},
+ {21, 0, 0, 0, 5, 0, 76},
+ {6, 0, 0, 0, 5, 0, 22},
+ {7, 0, 0, 0, 5, 0, 78},
+ {12, 230, 13, 0, 5, 0, 78},
+ {12, 220, 13, 0, 5, 0, 78},
+ {6, 0, 0, 0, 5, 0, 78},
+ {21, 0, 0, 0, 5, 0, 78},
+ {7, 0, 0, 0, 5, 0, 85},
+ {10, 0, 0, 0, 5, 0, 85},
+ {12, 0, 13, 0, 5, 0, 85},
+ {21, 0, 0, 0, 5, 0, 85},
+ {6, 0, 0, 0, 5, 0, 85},
+ {12, 9, 13, 0, 5, 0, 85},
+ {13, 0, 0, 0, 5, 0, 85},
+ {2, 0, 18, 0, 2, 0, 24},
+ {4, 0, 0, 0, 5, 0, 102},
+ {3, 0, 0, 0, 4, 0, 102},
+ {2, 0, 18, 0, 4, 0, 102},
+ {12, 26, 13, 0, 5, 0, 5},
+ {25, 0, 9, 0, 5, 0, 5},
+ {24, 0, 4, 0, 5, 0, 6},
+ {18, 0, 18, 0, 5, 0, 0},
+ {16, 0, 18, 0, 2, 0, 0},
+ {21, 0, 12, 0, 2, 0, 0},
+ {21, 0, 10, 0, 2, 0, 0},
+ {25, 0, 9, 0, 2, 0, 0},
+ {17, 0, 9, 0, 2, 0, 0},
+ {25, 0, 18, 1, 2, 0, 0},
+ {25, 0, 18, 0, 2, 0, 0},
+ {23, 0, 10, 0, 2, 0, 0},
+ {21, 0, 18, 0, 0, 0, 0},
+ {21, 0, 10, 0, 0, 0, 0},
+ {23, 0, 10, 0, 0, 0, 0},
+ {22, 0, 18, 1, 0, 0, 0},
+ {18, 0, 18, 1, 0, 0, 0},
+ {25, 0, 9, 0, 0, 0, 0},
+ {21, 0, 12, 0, 0, 0, 0},
+ {17, 0, 9, 0, 0, 0, 0},
+ {13, 0, 8, 0, 0, 0, 0},
+ {25, 0, 18, 1, 0, 0, 0},
+ {25, 0, 18, 0, 0, 0, 0},
+ {9, 0, 0, 0, 0, 0, 1},
+ {24, 0, 18, 0, 0, 0, 0},
+ {16, 0, 18, 0, 0, 0, 0},
+ {5, 0, 0, 0, 0, 0, 1},
+ {21, 0, 18, 0, 1, 0, 0},
+ {22, 0, 18, 1, 1, 0, 0},
+ {18, 0, 18, 1, 1, 0, 0},
+ {7, 0, 0, 0, 1, 0, 33},
+ {6, 0, 0, 0, 1, 0, 0},
+ {7, 0, 0, 0, 1, 0, 24},
+ {26, 0, 18, 0, 0, 0, 0},
+ {26, 0, 18, 0, 1, 0, 0},
+ {25, 0, 18, 0, 1, 0, 0},
+ {1, 0, 18, 0, 5, 0, 0},
+ {7, 0, 0, 0, 5, 0, 47},
+ {14, 0, 18, 0, 5, 0, 2},
+ {15, 0, 18, 0, 5, 0, 2},
+ {26, 0, 18, 0, 5, 0, 2},
+ {7, 0, 0, 0, 5, 0, 73},
+ {7, 0, 0, 0, 5, 0, 74},
+ {7, 0, 0, 0, 5, 0, 37},
+ {15, 0, 0, 0, 5, 0, 37},
+ {7, 0, 0, 0, 5, 0, 38},
+ {14, 0, 0, 0, 5, 0, 38},
+ {7, 0, 0, 0, 5, 0, 48},
+ {21, 0, 0, 0, 5, 0, 48},
+ {7, 0, 0, 0, 5, 0, 59},
+ {21, 0, 0, 0, 5, 0, 59},
+ {14, 0, 0, 0, 5, 0, 59},
+ {9, 0, 0, 0, 5, 0, 39},
+ {5, 0, 0, 0, 5, 0, 39},
+ {7, 0, 0, 0, 5, 0, 49},
+ {7, 0, 0, 0, 5, 0, 50},
+ {13, 0, 0, 0, 5, 0, 50},
+ {7, 0, 3, 0, 5, 0, 51},
+ {7, 0, 3, 0, 5, 0, 86},
+ {21, 0, 3, 0, 5, 0, 86},
+ {15, 0, 3, 0, 5, 0, 86},
+ {7, 0, 3, 0, 5, 0, 63},
+ {15, 0, 3, 0, 5, 0, 63},
+ {21, 0, 18, 0, 5, 0, 63},
+ {7, 0, 3, 0, 5, 0, 75},
+ {21, 0, 3, 0, 5, 0, 75},
+ {7, 0, 3, 0, 5, 0, 97},
+ {7, 0, 3, 0, 5, 0, 96},
+ {7, 0, 3, 0, 5, 0, 60},
+ {12, 0, 13, 0, 5, 0, 60},
+ {12, 220, 13, 0, 5, 0, 60},
+ {12, 230, 13, 0, 5, 0, 60},
+ {12, 1, 13, 0, 5, 0, 60},
+ {12, 9, 13, 0, 5, 0, 60},
+ {15, 0, 3, 0, 5, 0, 60},
+ {21, 0, 3, 0, 5, 0, 60},
+ {7, 0, 3, 0, 5, 0, 87},
+ {15, 0, 3, 0, 5, 0, 87},
+ {21, 0, 3, 0, 5, 0, 87},
+ {7, 0, 3, 0, 5, 0, 79},
+ {21, 0, 18, 0, 5, 0, 79},
+ {7, 0, 3, 0, 5, 0, 88},
+ {15, 0, 3, 0, 5, 0, 88},
+ {7, 0, 3, 0, 5, 0, 89},
+ {15, 0, 3, 0, 5, 0, 89},
+ {7, 0, 3, 0, 5, 0, 90},
+ {15, 0, 11, 0, 5, 0, 6},
+ {10, 0, 0, 0, 5, 0, 93},
+ {12, 0, 13, 0, 5, 0, 93},
+ {7, 0, 0, 0, 5, 0, 93},
+ {12, 9, 13, 0, 5, 0, 93},
+ {21, 0, 0, 0, 5, 0, 93},
+ {15, 0, 18, 0, 5, 0, 93},
+ {13, 0, 0, 0, 5, 0, 93},
+ {12, 0, 13, 0, 5, 0, 91},
+ {10, 0, 0, 0, 5, 0, 91},
+ {7, 0, 0, 0, 5, 0, 91},
+ {12, 9, 13, 0, 5, 0, 91},
+ {12, 7, 13, 0, 5, 0, 91},
+ {21, 0, 0, 0, 5, 0, 91},
+ {1, 0, 0, 0, 5, 0, 91},
+ {7, 0, 0, 0, 5, 0, 100},
+ {13, 0, 0, 0, 5, 0, 100},
+ {12, 230, 13, 0, 5, 0, 95},
+ {7, 0, 0, 0, 5, 0, 95},
+ {12, 0, 13, 0, 5, 0, 95},
+ {10, 0, 0, 0, 5, 0, 95},
+ {12, 9, 13, 0, 5, 0, 95},
+ {13, 0, 0, 0, 5, 0, 95},
+ {21, 0, 0, 0, 5, 0, 95},
+ {12, 0, 13, 0, 5, 0, 99},
+ {10, 0, 0, 0, 5, 0, 99},
+ {7, 0, 0, 0, 5, 0, 99},
+ {10, 9, 0, 0, 5, 0, 99},
+ {21, 0, 0, 0, 5, 0, 99},
+ {13, 0, 0, 0, 5, 0, 99},
+ {7, 0, 0, 0, 5, 0, 101},
+ {12, 0, 13, 0, 5, 0, 101},
+ {10, 0, 0, 0, 5, 0, 101},
+ {10, 9, 0, 0, 5, 0, 101},
+ {12, 7, 13, 0, 5, 0, 101},
+ {13, 0, 0, 0, 5, 0, 101},
+ {7, 0, 0, 0, 5, 0, 62},
+ {14, 0, 0, 0, 5, 0, 62},
+ {21, 0, 0, 0, 5, 0, 62},
+ {7, 0, 0, 0, 5, 0, 80},
+ {7, 0, 0, 0, 5, 0, 98},
+ {10, 0, 0, 0, 5, 0, 98},
+ {12, 0, 13, 0, 5, 0, 98},
+ {6, 0, 0, 0, 5, 0, 98},
+ {10, 216, 0, 0, 5, 0, 0},
+ {10, 226, 0, 0, 5, 0, 0},
+ {12, 230, 13, 0, 5, 0, 2},
+ {25, 0, 0, 0, 5, 0, 0},
+ {13, 0, 8, 0, 5, 0, 0},
+ {26, 0, 0, 0, 2, 0, 32},
+};
+
+#define BIDI_MIRROR_LEN 364
+static const MirrorPair mirror_pairs[] = {
+ {40, 41},
+ {41, 40},
+ {60, 62},
+ {62, 60},
+ {91, 93},
+ {93, 91},
+ {123, 125},
+ {125, 123},
+ {171, 187},
+ {187, 171},
+ {3898, 3899},
+ {3899, 3898},
+ {3900, 3901},
+ {3901, 3900},
+ {5787, 5788},
+ {5788, 5787},
+ {8249, 8250},
+ {8250, 8249},
+ {8261, 8262},
+ {8262, 8261},
+ {8317, 8318},
+ {8318, 8317},
+ {8333, 8334},
+ {8334, 8333},
+ {8712, 8715},
+ {8713, 8716},
+ {8714, 8717},
+ {8715, 8712},
+ {8716, 8713},
+ {8717, 8714},
+ {8725, 10741},
+ {8764, 8765},
+ {8765, 8764},
+ {8771, 8909},
+ {8786, 8787},
+ {8787, 8786},
+ {8788, 8789},
+ {8789, 8788},
+ {8804, 8805},
+ {8805, 8804},
+ {8806, 8807},
+ {8807, 8806},
+ {8808, 8809},
+ {8809, 8808},
+ {8810, 8811},
+ {8811, 8810},
+ {8814, 8815},
+ {8815, 8814},
+ {8816, 8817},
+ {8817, 8816},
+ {8818, 8819},
+ {8819, 8818},
+ {8820, 8821},
+ {8821, 8820},
+ {8822, 8823},
+ {8823, 8822},
+ {8824, 8825},
+ {8825, 8824},
+ {8826, 8827},
+ {8827, 8826},
+ {8828, 8829},
+ {8829, 8828},
+ {8830, 8831},
+ {8831, 8830},
+ {8832, 8833},
+ {8833, 8832},
+ {8834, 8835},
+ {8835, 8834},
+ {8836, 8837},
+ {8837, 8836},
+ {8838, 8839},
+ {8839, 8838},
+ {8840, 8841},
+ {8841, 8840},
+ {8842, 8843},
+ {8843, 8842},
+ {8847, 8848},
+ {8848, 8847},
+ {8849, 8850},
+ {8850, 8849},
+ {8856, 10680},
+ {8866, 8867},
+ {8867, 8866},
+ {8870, 10974},
+ {8872, 10980},
+ {8873, 10979},
+ {8875, 10981},
+ {8880, 8881},
+ {8881, 8880},
+ {8882, 8883},
+ {8883, 8882},
+ {8884, 8885},
+ {8885, 8884},
+ {8886, 8887},
+ {8887, 8886},
+ {8905, 8906},
+ {8906, 8905},
+ {8907, 8908},
+ {8908, 8907},
+ {8909, 8771},
+ {8912, 8913},
+ {8913, 8912},
+ {8918, 8919},
+ {8919, 8918},
+ {8920, 8921},
+ {8921, 8920},
+ {8922, 8923},
+ {8923, 8922},
+ {8924, 8925},
+ {8925, 8924},
+ {8926, 8927},
+ {8927, 8926},
+ {8928, 8929},
+ {8929, 8928},
+ {8930, 8931},
+ {8931, 8930},
+ {8932, 8933},
+ {8933, 8932},
+ {8934, 8935},
+ {8935, 8934},
+ {8936, 8937},
+ {8937, 8936},
+ {8938, 8939},
+ {8939, 8938},
+ {8940, 8941},
+ {8941, 8940},
+ {8944, 8945},
+ {8945, 8944},
+ {8946, 8954},
+ {8947, 8955},
+ {8948, 8956},
+ {8950, 8957},
+ {8951, 8958},
+ {8954, 8946},
+ {8955, 8947},
+ {8956, 8948},
+ {8957, 8950},
+ {8958, 8951},
+ {8968, 8969},
+ {8969, 8968},
+ {8970, 8971},
+ {8971, 8970},
+ {9001, 9002},
+ {9002, 9001},
+ {10088, 10089},
+ {10089, 10088},
+ {10090, 10091},
+ {10091, 10090},
+ {10092, 10093},
+ {10093, 10092},
+ {10094, 10095},
+ {10095, 10094},
+ {10096, 10097},
+ {10097, 10096},
+ {10098, 10099},
+ {10099, 10098},
+ {10100, 10101},
+ {10101, 10100},
+ {10179, 10180},
+ {10180, 10179},
+ {10181, 10182},
+ {10182, 10181},
+ {10184, 10185},
+ {10185, 10184},
+ {10187, 10189},
+ {10189, 10187},
+ {10197, 10198},
+ {10198, 10197},
+ {10205, 10206},
+ {10206, 10205},
+ {10210, 10211},
+ {10211, 10210},
+ {10212, 10213},
+ {10213, 10212},
+ {10214, 10215},
+ {10215, 10214},
+ {10216, 10217},
+ {10217, 10216},
+ {10218, 10219},
+ {10219, 10218},
+ {10220, 10221},
+ {10221, 10220},
+ {10222, 10223},
+ {10223, 10222},
+ {10627, 10628},
+ {10628, 10627},
+ {10629, 10630},
+ {10630, 10629},
+ {10631, 10632},
+ {10632, 10631},
+ {10633, 10634},
+ {10634, 10633},
+ {10635, 10636},
+ {10636, 10635},
+ {10637, 10640},
+ {10638, 10639},
+ {10639, 10638},
+ {10640, 10637},
+ {10641, 10642},
+ {10642, 10641},
+ {10643, 10644},
+ {10644, 10643},
+ {10645, 10646},
+ {10646, 10645},
+ {10647, 10648},
+ {10648, 10647},
+ {10680, 8856},
+ {10688, 10689},
+ {10689, 10688},
+ {10692, 10693},
+ {10693, 10692},
+ {10703, 10704},
+ {10704, 10703},
+ {10705, 10706},
+ {10706, 10705},
+ {10708, 10709},
+ {10709, 10708},
+ {10712, 10713},
+ {10713, 10712},
+ {10714, 10715},
+ {10715, 10714},
+ {10741, 8725},
+ {10744, 10745},
+ {10745, 10744},
+ {10748, 10749},
+ {10749, 10748},
+ {10795, 10796},
+ {10796, 10795},
+ {10797, 10798},
+ {10798, 10797},
+ {10804, 10805},
+ {10805, 10804},
+ {10812, 10813},
+ {10813, 10812},
+ {10852, 10853},
+ {10853, 10852},
+ {10873, 10874},
+ {10874, 10873},
+ {10877, 10878},
+ {10878, 10877},
+ {10879, 10880},
+ {10880, 10879},
+ {10881, 10882},
+ {10882, 10881},
+ {10883, 10884},
+ {10884, 10883},
+ {10891, 10892},
+ {10892, 10891},
+ {10897, 10898},
+ {10898, 10897},
+ {10899, 10900},
+ {10900, 10899},
+ {10901, 10902},
+ {10902, 10901},
+ {10903, 10904},
+ {10904, 10903},
+ {10905, 10906},
+ {10906, 10905},
+ {10907, 10908},
+ {10908, 10907},
+ {10913, 10914},
+ {10914, 10913},
+ {10918, 10919},
+ {10919, 10918},
+ {10920, 10921},
+ {10921, 10920},
+ {10922, 10923},
+ {10923, 10922},
+ {10924, 10925},
+ {10925, 10924},
+ {10927, 10928},
+ {10928, 10927},
+ {10931, 10932},
+ {10932, 10931},
+ {10939, 10940},
+ {10940, 10939},
+ {10941, 10942},
+ {10942, 10941},
+ {10943, 10944},
+ {10944, 10943},
+ {10945, 10946},
+ {10946, 10945},
+ {10947, 10948},
+ {10948, 10947},
+ {10949, 10950},
+ {10950, 10949},
+ {10957, 10958},
+ {10958, 10957},
+ {10959, 10960},
+ {10960, 10959},
+ {10961, 10962},
+ {10962, 10961},
+ {10963, 10964},
+ {10964, 10963},
+ {10965, 10966},
+ {10966, 10965},
+ {10974, 8870},
+ {10979, 8873},
+ {10980, 8872},
+ {10981, 8875},
+ {10988, 10989},
+ {10989, 10988},
+ {10999, 11000},
+ {11000, 10999},
+ {11001, 11002},
+ {11002, 11001},
+ {11778, 11779},
+ {11779, 11778},
+ {11780, 11781},
+ {11781, 11780},
+ {11785, 11786},
+ {11786, 11785},
+ {11788, 11789},
+ {11789, 11788},
+ {11804, 11805},
+ {11805, 11804},
+ {11808, 11809},
+ {11809, 11808},
+ {11810, 11811},
+ {11811, 11810},
+ {11812, 11813},
+ {11813, 11812},
+ {11814, 11815},
+ {11815, 11814},
+ {11816, 11817},
+ {11817, 11816},
+ {12296, 12297},
+ {12297, 12296},
+ {12298, 12299},
+ {12299, 12298},
+ {12300, 12301},
+ {12301, 12300},
+ {12302, 12303},
+ {12303, 12302},
+ {12304, 12305},
+ {12305, 12304},
+ {12308, 12309},
+ {12309, 12308},
+ {12310, 12311},
+ {12311, 12310},
+ {12312, 12313},
+ {12313, 12312},
+ {12314, 12315},
+ {12315, 12314},
+ {65113, 65114},
+ {65114, 65113},
+ {65115, 65116},
+ {65116, 65115},
+ {65117, 65118},
+ {65118, 65117},
+ {65124, 65125},
+ {65125, 65124},
+ {65288, 65289},
+ {65289, 65288},
+ {65308, 65310},
+ {65310, 65308},
+ {65339, 65341},
+ {65341, 65339},
+ {65371, 65373},
+ {65373, 65371},
+ {65375, 65376},
+ {65376, 65375},
+ {65378, 65379},
+ {65379, 65378},
+};
+
+/* Reindexing of NFC first characters. */
+#define TOTAL_FIRST 372
+#define TOTAL_LAST 56
+static const Reindex nfc_first[] = {
+ { 60, 2, 0},
+ { 65, 15, 3},
+ { 82, 8, 19},
+ { 97, 15, 28},
+ { 114, 8, 44},
+ { 168, 0, 53},
+ { 194, 0, 54},
+ { 196, 3, 55},
+ { 202, 0, 59},
+ { 207, 0, 60},
+ { 212, 2, 61},
+ { 216, 0, 64},
+ { 220, 0, 65},
+ { 226, 0, 66},
+ { 228, 3, 67},
+ { 234, 0, 71},
+ { 239, 0, 72},
+ { 244, 2, 73},
+ { 248, 0, 76},
+ { 252, 0, 77},
+ { 258, 1, 78},
+ { 274, 1, 80},
+ { 332, 1, 82},
+ { 346, 1, 84},
+ { 352, 1, 86},
+ { 360, 3, 88},
+ { 383, 0, 92},
+ { 416, 1, 93},
+ { 431, 1, 95},
+ { 439, 0, 97},
+ { 490, 1, 98},
+ { 550, 3, 100},
+ { 558, 1, 104},
+ { 658, 0, 106},
+ { 913, 0, 107},
+ { 917, 0, 108},
+ { 919, 0, 109},
+ { 921, 0, 110},
+ { 927, 0, 111},
+ { 929, 0, 112},
+ { 933, 0, 113},
+ { 937, 0, 114},
+ { 940, 0, 115},
+ { 942, 0, 116},
+ { 945, 0, 117},
+ { 949, 0, 118},
+ { 951, 0, 119},
+ { 953, 0, 120},
+ { 959, 0, 121},
+ { 961, 0, 122},
+ { 965, 0, 123},
+ { 969, 2, 124},
+ { 974, 0, 127},
+ { 978, 0, 128},
+ { 1030, 0, 129},
+ { 1040, 0, 130},
+ { 1043, 0, 131},
+ { 1045, 3, 132},
+ { 1050, 0, 136},
+ { 1054, 0, 137},
+ { 1059, 0, 138},
+ { 1063, 0, 139},
+ { 1067, 0, 140},
+ { 1069, 0, 141},
+ { 1072, 0, 142},
+ { 1075, 0, 143},
+ { 1077, 3, 144},
+ { 1082, 0, 148},
+ { 1086, 0, 149},
+ { 1091, 0, 150},
+ { 1095, 0, 151},
+ { 1099, 0, 152},
+ { 1101, 0, 153},
+ { 1110, 0, 154},
+ { 1140, 1, 155},
+ { 1240, 1, 157},
+ { 1256, 1, 159},
+ { 1575, 0, 161},
+ { 1608, 0, 162},
+ { 1610, 0, 163},
+ { 1729, 0, 164},
+ { 1746, 0, 165},
+ { 1749, 0, 166},
+ { 2344, 0, 167},
+ { 2352, 0, 168},
+ { 2355, 0, 169},
+ { 2503, 0, 170},
+ { 2887, 0, 171},
+ { 2962, 0, 172},
+ { 3014, 1, 173},
+ { 3142, 0, 175},
+ { 3263, 0, 176},
+ { 3270, 0, 177},
+ { 3274, 0, 178},
+ { 3398, 1, 179},
+ { 3545, 0, 181},
+ { 3548, 0, 182},
+ { 4133, 0, 183},
+ { 6917, 0, 184},
+ { 6919, 0, 185},
+ { 6921, 0, 186},
+ { 6923, 0, 187},
+ { 6925, 0, 188},
+ { 6929, 0, 189},
+ { 6970, 0, 190},
+ { 6972, 0, 191},
+ { 6974, 1, 192},
+ { 6978, 0, 194},
+ { 7734, 1, 195},
+ { 7770, 1, 197},
+ { 7778, 1, 199},
+ { 7840, 1, 201},
+ { 7864, 1, 203},
+ { 7884, 1, 205},
+ { 7936, 17, 207},
+ { 7960, 1, 225},
+ { 7968, 17, 227},
+ { 7992, 1, 245},
+ { 8000, 1, 247},
+ { 8008, 1, 249},
+ { 8016, 1, 251},
+ { 8025, 0, 253},
+ { 8032, 16, 254},
+ { 8052, 0, 271},
+ { 8060, 0, 272},
+ { 8118, 0, 273},
+ { 8127, 0, 274},
+ { 8134, 0, 275},
+ { 8182, 0, 276},
+ { 8190, 0, 277},
+ { 8592, 0, 278},
+ { 8594, 0, 279},
+ { 8596, 0, 280},
+ { 8656, 0, 281},
+ { 8658, 0, 282},
+ { 8660, 0, 283},
+ { 8707, 0, 284},
+ { 8712, 0, 285},
+ { 8715, 0, 286},
+ { 8739, 0, 287},
+ { 8741, 0, 288},
+ { 8764, 0, 289},
+ { 8771, 0, 290},
+ { 8773, 0, 291},
+ { 8776, 0, 292},
+ { 8781, 0, 293},
+ { 8801, 0, 294},
+ { 8804, 1, 295},
+ { 8818, 1, 297},
+ { 8822, 1, 299},
+ { 8826, 3, 301},
+ { 8834, 1, 305},
+ { 8838, 1, 307},
+ { 8849, 1, 309},
+ { 8866, 0, 311},
+ { 8872, 1, 312},
+ { 8875, 0, 314},
+ { 8882, 3, 315},
+ { 12358, 0, 319},
+ { 12363, 0, 320},
+ { 12365, 0, 321},
+ { 12367, 0, 322},
+ { 12369, 0, 323},
+ { 12371, 0, 324},
+ { 12373, 0, 325},
+ { 12375, 0, 326},
+ { 12377, 0, 327},
+ { 12379, 0, 328},
+ { 12381, 0, 329},
+ { 12383, 0, 330},
+ { 12385, 0, 331},
+ { 12388, 0, 332},
+ { 12390, 0, 333},
+ { 12392, 0, 334},
+ { 12399, 0, 335},
+ { 12402, 0, 336},
+ { 12405, 0, 337},
+ { 12408, 0, 338},
+ { 12411, 0, 339},
+ { 12445, 0, 340},
+ { 12454, 0, 341},
+ { 12459, 0, 342},
+ { 12461, 0, 343},
+ { 12463, 0, 344},
+ { 12465, 0, 345},
+ { 12467, 0, 346},
+ { 12469, 0, 347},
+ { 12471, 0, 348},
+ { 12473, 0, 349},
+ { 12475, 0, 350},
+ { 12477, 0, 351},
+ { 12479, 0, 352},
+ { 12481, 0, 353},
+ { 12484, 0, 354},
+ { 12486, 0, 355},
+ { 12488, 0, 356},
+ { 12495, 0, 357},
+ { 12498, 0, 358},
+ { 12501, 0, 359},
+ { 12504, 0, 360},
+ { 12507, 0, 361},
+ { 12527, 3, 362},
+ { 12541, 0, 366},
+ { 69785, 0, 367},
+ { 69787, 0, 368},
+ { 69797, 0, 369},
+ { 69937, 1, 370},
+ {0,0,0}
+};
+
+static const Reindex nfc_last[] = {
+ { 768, 4, 0},
+ { 774, 6, 5},
+ { 783, 0, 12},
+ { 785, 0, 13},
+ { 787, 1, 14},
+ { 795, 0, 16},
+ { 803, 5, 17},
+ { 813, 1, 23},
+ { 816, 1, 25},
+ { 824, 0, 27},
+ { 834, 0, 28},
+ { 837, 0, 29},
+ { 1619, 2, 30},
+ { 2364, 0, 33},
+ { 2494, 0, 34},
+ { 2519, 0, 35},
+ { 2878, 0, 36},
+ { 2902, 1, 37},
+ { 3006, 0, 39},
+ { 3031, 0, 40},
+ { 3158, 0, 41},
+ { 3266, 0, 42},
+ { 3285, 1, 43},
+ { 3390, 0, 45},
+ { 3415, 0, 46},
+ { 3530, 0, 47},
+ { 3535, 0, 48},
+ { 3551, 0, 49},
+ { 4142, 0, 50},
+ { 6965, 0, 51},
+ { 12441, 1, 52},
+ { 69818, 0, 54},
+ { 69927, 0, 55},
+ {0,0,0}
+};
+
+#define UCDN_EAST_ASIAN_F 0
+#define UCDN_EAST_ASIAN_H 1
+#define UCDN_EAST_ASIAN_W 2
+#define UCDN_EAST_ASIAN_NA 3
+#define UCDN_EAST_ASIAN_A 4
+#define UCDN_EAST_ASIAN_N 5
+
+#define UCDN_SCRIPT_COMMON 0
+#define UCDN_SCRIPT_LATIN 1
+#define UCDN_SCRIPT_GREEK 2
+#define UCDN_SCRIPT_CYRILLIC 3
+#define UCDN_SCRIPT_ARMENIAN 4
+#define UCDN_SCRIPT_HEBREW 5
+#define UCDN_SCRIPT_ARABIC 6
+#define UCDN_SCRIPT_SYRIAC 7
+#define UCDN_SCRIPT_THAANA 8
+#define UCDN_SCRIPT_DEVANAGARI 9
+#define UCDN_SCRIPT_BENGALI 10
+#define UCDN_SCRIPT_GURMUKHI 11
+#define UCDN_SCRIPT_GUJARATI 12
+#define UCDN_SCRIPT_ORIYA 13
+#define UCDN_SCRIPT_TAMIL 14
+#define UCDN_SCRIPT_TELUGU 15
+#define UCDN_SCRIPT_KANNADA 16
+#define UCDN_SCRIPT_MALAYALAM 17
+#define UCDN_SCRIPT_SINHALA 18
+#define UCDN_SCRIPT_THAI 19
+#define UCDN_SCRIPT_LAO 20
+#define UCDN_SCRIPT_TIBETAN 21
+#define UCDN_SCRIPT_MYANMAR 22
+#define UCDN_SCRIPT_GEORGIAN 23
+#define UCDN_SCRIPT_HANGUL 24
+#define UCDN_SCRIPT_ETHIOPIC 25
+#define UCDN_SCRIPT_CHEROKEE 26
+#define UCDN_SCRIPT_CANADIAN_ABORIGINAL 27
+#define UCDN_SCRIPT_OGHAM 28
+#define UCDN_SCRIPT_RUNIC 29
+#define UCDN_SCRIPT_KHMER 30
+#define UCDN_SCRIPT_MONGOLIAN 31
+#define UCDN_SCRIPT_HIRAGANA 32
+#define UCDN_SCRIPT_KATAKANA 33
+#define UCDN_SCRIPT_BOPOMOFO 34
+#define UCDN_SCRIPT_HAN 35
+#define UCDN_SCRIPT_YI 36
+#define UCDN_SCRIPT_OLD_ITALIC 37
+#define UCDN_SCRIPT_GOTHIC 38
+#define UCDN_SCRIPT_DESERET 39
+#define UCDN_SCRIPT_INHERITED 40
+#define UCDN_SCRIPT_TAGALOG 41
+#define UCDN_SCRIPT_HANUNOO 42
+#define UCDN_SCRIPT_BUHID 43
+#define UCDN_SCRIPT_TAGBANWA 44
+#define UCDN_SCRIPT_LIMBU 45
+#define UCDN_SCRIPT_TAI_LE 46
+#define UCDN_SCRIPT_LINEAR_B 47
+#define UCDN_SCRIPT_UGARITIC 48
+#define UCDN_SCRIPT_SHAVIAN 49
+#define UCDN_SCRIPT_OSMANYA 50
+#define UCDN_SCRIPT_CYPRIOT 51
+#define UCDN_SCRIPT_BRAILLE 52
+#define UCDN_SCRIPT_BUGINESE 53
+#define UCDN_SCRIPT_COPTIC 54
+#define UCDN_SCRIPT_NEW_TAI_LUE 55
+#define UCDN_SCRIPT_GLAGOLITIC 56
+#define UCDN_SCRIPT_TIFINAGH 57
+#define UCDN_SCRIPT_SYLOTI_NAGRI 58
+#define UCDN_SCRIPT_OLD_PERSIAN 59
+#define UCDN_SCRIPT_KHAROSHTHI 60
+#define UCDN_SCRIPT_BALINESE 61
+#define UCDN_SCRIPT_CUNEIFORM 62
+#define UCDN_SCRIPT_PHOENICIAN 63
+#define UCDN_SCRIPT_PHAGS_PA 64
+#define UCDN_SCRIPT_NKO 65
+#define UCDN_SCRIPT_SUNDANESE 66
+#define UCDN_SCRIPT_LEPCHA 67
+#define UCDN_SCRIPT_OL_CHIKI 68
+#define UCDN_SCRIPT_VAI 69
+#define UCDN_SCRIPT_SAURASHTRA 70
+#define UCDN_SCRIPT_KAYAH_LI 71
+#define UCDN_SCRIPT_REJANG 72
+#define UCDN_SCRIPT_LYCIAN 73
+#define UCDN_SCRIPT_CARIAN 74
+#define UCDN_SCRIPT_LYDIAN 75
+#define UCDN_SCRIPT_CHAM 76
+#define UCDN_SCRIPT_TAI_THAM 77
+#define UCDN_SCRIPT_TAI_VIET 78
+#define UCDN_SCRIPT_AVESTAN 79
+#define UCDN_SCRIPT_EGYPTIAN_HIEROGLYPHS 80
+#define UCDN_SCRIPT_SAMARITAN 81
+#define UCDN_SCRIPT_LISU 82
+#define UCDN_SCRIPT_BAMUM 83
+#define UCDN_SCRIPT_JAVANESE 84
+#define UCDN_SCRIPT_MEETEI_MAYEK 85
+#define UCDN_SCRIPT_IMPERIAL_ARAMAIC 86
+#define UCDN_SCRIPT_OLD_SOUTH_ARABIAN 87
+#define UCDN_SCRIPT_INSCRIPTIONAL_PARTHIAN 88
+#define UCDN_SCRIPT_INSCRIPTIONAL_PAHLAVI 89
+#define UCDN_SCRIPT_OLD_TURKIC 90
+#define UCDN_SCRIPT_KAITHI 91
+#define UCDN_SCRIPT_BATAK 92
+#define UCDN_SCRIPT_BRAHMI 93
+#define UCDN_SCRIPT_MANDAIC 94
+#define UCDN_SCRIPT_CHAKMA 95
+#define UCDN_SCRIPT_MEROITIC_CURSIVE 96
+#define UCDN_SCRIPT_MEROITIC_HIEROGLYPHS 97
+#define UCDN_SCRIPT_MIAO 98
+#define UCDN_SCRIPT_SHARADA 99
+#define UCDN_SCRIPT_SORA_SOMPENG 100
+#define UCDN_SCRIPT_TAKRI 101
+#define UCDN_SCRIPT_UNKNOWN 102
+
+#define UCDN_GENERAL_CATEGORY_CC 0
+#define UCDN_GENERAL_CATEGORY_CF 1
+#define UCDN_GENERAL_CATEGORY_CN 2
+#define UCDN_GENERAL_CATEGORY_CO 3
+#define UCDN_GENERAL_CATEGORY_CS 4
+#define UCDN_GENERAL_CATEGORY_LL 5
+#define UCDN_GENERAL_CATEGORY_LM 6
+#define UCDN_GENERAL_CATEGORY_LO 7
+#define UCDN_GENERAL_CATEGORY_LT 8
+#define UCDN_GENERAL_CATEGORY_LU 9
+#define UCDN_GENERAL_CATEGORY_MC 10
+#define UCDN_GENERAL_CATEGORY_ME 11
+#define UCDN_GENERAL_CATEGORY_MN 12
+#define UCDN_GENERAL_CATEGORY_ND 13
+#define UCDN_GENERAL_CATEGORY_NL 14
+#define UCDN_GENERAL_CATEGORY_NO 15
+#define UCDN_GENERAL_CATEGORY_PC 16
+#define UCDN_GENERAL_CATEGORY_PD 17
+#define UCDN_GENERAL_CATEGORY_PE 18
+#define UCDN_GENERAL_CATEGORY_PF 19
+#define UCDN_GENERAL_CATEGORY_PI 20
+#define UCDN_GENERAL_CATEGORY_PO 21
+#define UCDN_GENERAL_CATEGORY_PS 22
+#define UCDN_GENERAL_CATEGORY_SC 23
+#define UCDN_GENERAL_CATEGORY_SK 24
+#define UCDN_GENERAL_CATEGORY_SM 25
+#define UCDN_GENERAL_CATEGORY_SO 26
+#define UCDN_GENERAL_CATEGORY_ZL 27
+#define UCDN_GENERAL_CATEGORY_ZP 28
+#define UCDN_GENERAL_CATEGORY_ZS 29
+
+#define UCDN_BIDI_CLASS_L 0
+#define UCDN_BIDI_CLASS_LRE 1
+#define UCDN_BIDI_CLASS_LRO 2
+#define UCDN_BIDI_CLASS_R 3
+#define UCDN_BIDI_CLASS_AL 4
+#define UCDN_BIDI_CLASS_RLE 5
+#define UCDN_BIDI_CLASS_RLO 6
+#define UCDN_BIDI_CLASS_PDF 7
+#define UCDN_BIDI_CLASS_EN 8
+#define UCDN_BIDI_CLASS_ES 9
+#define UCDN_BIDI_CLASS_ET 10
+#define UCDN_BIDI_CLASS_AN 11
+#define UCDN_BIDI_CLASS_CS 12
+#define UCDN_BIDI_CLASS_NSM 13
+#define UCDN_BIDI_CLASS_BN 14
+#define UCDN_BIDI_CLASS_B 15
+#define UCDN_BIDI_CLASS_S 16
+#define UCDN_BIDI_CLASS_WS 17
+#define UCDN_BIDI_CLASS_ON 18
+
+/* index tables for the database records */
+#define SHIFT1 5
+#define SHIFT2 3
+static const unsigned char index0[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 54, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 55, 56, 57, 57, 57, 58,
+ 59, 60, 61, 62, 63, 64, 65, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 68, 69, 70, 70,
+ 71, 69, 70, 70, 72, 73, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 76, 77, 78, 79, 80, 81,
+ 82, 83, 84, 85, 86, 87, 70, 70, 70, 88, 89, 90, 91, 92, 70, 93, 70, 94,
+ 95, 70, 70, 70, 70, 96, 70, 70, 70, 70, 70, 70, 70, 70, 70, 97, 97, 97,
+ 98, 99, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 100, 100, 100, 100,
+ 101, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 102, 102,
+ 103, 70, 70, 70, 70, 104, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 105, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 106, 107, 108, 109, 110,
+ 111, 112, 113, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 114, 70, 115, 116, 117, 118, 119, 120,
+ 121, 122, 70, 70, 70, 70, 70, 70, 70, 70, 52, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 123, 52, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 124, 125, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 76, 76, 127, 126, 126, 126, 126, 128, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 128, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 129, 130, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 73, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 131, 73, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 131,
+};
+
+static const unsigned short index1[] = {
+ 0, 1, 0, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 11, 12, 13, 0, 0, 0, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 29, 31, 32,
+ 33, 34, 35, 27, 30, 29, 27, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
+ 47, 48, 27, 27, 49, 27, 27, 27, 27, 27, 27, 27, 50, 51, 52, 27, 53, 54,
+ 53, 54, 54, 54, 54, 54, 55, 54, 54, 54, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 64, 65, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 65, 77, 78,
+ 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
+ 97, 97, 97, 97, 98, 98, 98, 98, 99, 100, 101, 101, 101, 101, 102, 103,
+ 101, 101, 101, 101, 101, 101, 104, 105, 101, 101, 101, 101, 101, 101,
+ 101, 101, 101, 101, 101, 106, 107, 108, 108, 108, 109, 110, 111, 112,
+ 112, 112, 112, 113, 114, 115, 116, 117, 118, 119, 120, 106, 121, 121,
+ 121, 122, 123, 106, 124, 125, 126, 127, 128, 128, 128, 128, 129, 130,
+ 131, 132, 133, 134, 135, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 145, 145,
+ 146, 147, 148, 149, 128, 128, 128, 128, 128, 128, 150, 150, 150, 150,
+ 151, 152, 153, 106, 154, 155, 156, 156, 156, 157, 158, 159, 160, 160,
+ 161, 162, 163, 164, 165, 166, 167, 167, 167, 168, 106, 106, 106, 106,
+ 106, 106, 106, 106, 169, 170, 106, 106, 106, 106, 106, 106, 171, 172,
+ 173, 174, 175, 176, 176, 176, 176, 176, 176, 177, 178, 179, 180, 176,
+ 181, 182, 183, 184, 185, 186, 187, 188, 188, 189, 190, 191, 192, 193,
+ 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 203, 204, 205, 206,
+ 207, 208, 209, 210, 211, 212, 213, 106, 214, 215, 216, 217, 217, 218,
+ 219, 220, 221, 222, 223, 106, 224, 225, 226, 106, 227, 228, 229, 230,
+ 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 106, 241, 242,
+ 243, 244, 245, 242, 246, 247, 248, 249, 250, 106, 251, 252, 253, 254,
+ 255, 256, 257, 258, 258, 257, 259, 260, 261, 262, 263, 264, 265, 266,
+ 106, 267, 268, 269, 270, 271, 271, 270, 272, 273, 274, 275, 276, 277,
+ 278, 279, 280, 106, 281, 282, 283, 284, 284, 284, 284, 285, 286, 287,
+ 288, 106, 289, 290, 291, 292, 293, 294, 295, 296, 294, 294, 297, 298,
+ 295, 299, 300, 301, 106, 106, 302, 106, 303, 304, 304, 304, 304, 304,
+ 305, 306, 307, 308, 309, 310, 106, 106, 106, 106, 311, 312, 313, 314,
+ 315, 316, 317, 318, 319, 320, 321, 322, 106, 106, 106, 106, 323, 324,
+ 325, 326, 327, 328, 329, 330, 331, 332, 331, 331, 331, 333, 334, 335,
+ 336, 337, 338, 339, 338, 338, 338, 340, 341, 342, 343, 344, 106, 106,
+ 106, 106, 345, 345, 345, 345, 345, 346, 347, 348, 349, 350, 351, 352,
+ 353, 354, 355, 345, 356, 357, 349, 358, 359, 359, 359, 359, 360, 361,
+ 362, 362, 362, 362, 362, 363, 364, 364, 364, 364, 364, 364, 364, 364,
+ 364, 364, 364, 364, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365,
+ 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 366, 366, 366, 366,
+ 366, 366, 366, 366, 366, 367, 368, 367, 366, 366, 366, 366, 366, 367,
+ 366, 366, 366, 366, 367, 368, 367, 366, 368, 366, 366, 366, 366, 366,
+ 366, 366, 367, 366, 366, 366, 366, 366, 366, 366, 366, 369, 370, 371,
+ 372, 373, 366, 366, 374, 375, 376, 376, 376, 376, 376, 376, 376, 376,
+ 376, 376, 377, 106, 378, 379, 379, 379, 379, 379, 379, 379, 379, 379,
+ 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379,
+ 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379,
+ 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379,
+ 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379,
+ 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 380, 379, 379,
+ 381, 382, 382, 383, 384, 384, 384, 384, 384, 384, 384, 384, 384, 385,
+ 386, 106, 387, 388, 389, 106, 390, 390, 391, 106, 392, 392, 393, 106,
+ 394, 395, 396, 106, 397, 397, 397, 397, 397, 397, 398, 399, 400, 401,
+ 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 412, 412, 412,
+ 413, 412, 412, 412, 412, 412, 412, 106, 412, 412, 412, 412, 412, 414,
+ 379, 379, 379, 379, 379, 379, 379, 379, 415, 106, 416, 416, 416, 417,
+ 418, 419, 420, 421, 422, 423, 424, 424, 424, 425, 426, 106, 427, 427,
+ 427, 427, 427, 428, 429, 429, 430, 431, 432, 433, 434, 434, 434, 434,
+ 435, 435, 436, 437, 438, 438, 438, 438, 438, 438, 439, 440, 441, 442,
+ 443, 444, 445, 446, 445, 446, 447, 448, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 449, 450, 450, 450, 450, 450, 451, 452, 453, 454,
+ 455, 456, 457, 458, 459, 460, 461, 462, 462, 462, 463, 464, 465, 466,
+ 467, 467, 467, 467, 468, 469, 470, 471, 472, 472, 472, 472, 473, 474,
+ 475, 476, 477, 478, 479, 480, 481, 481, 481, 482, 106, 106, 106, 106,
+ 106, 106, 106, 106, 483, 106, 484, 485, 486, 487, 488, 106, 54, 54, 54,
+ 54, 489, 490, 56, 56, 56, 56, 56, 491, 492, 493, 54, 494, 54, 54, 54,
+ 495, 56, 56, 56, 496, 497, 498, 499, 500, 501, 106, 106, 502, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 503, 504, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 505, 506, 507, 508, 505, 506,
+ 505, 506, 507, 508, 505, 509, 505, 506, 505, 507, 505, 510, 505, 510,
+ 505, 510, 511, 512, 513, 514, 515, 516, 505, 517, 518, 519, 520, 521,
+ 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535,
+ 536, 537, 56, 538, 539, 540, 539, 541, 106, 106, 542, 543, 544, 545, 546,
+ 106, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559,
+ 560, 559, 561, 562, 563, 564, 565, 566, 567, 568, 569, 568, 570, 571,
+ 568, 572, 568, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583,
+ 584, 585, 586, 587, 588, 583, 583, 589, 590, 591, 592, 593, 583, 583,
+ 594, 574, 595, 596, 583, 583, 597, 583, 583, 568, 598, 599, 568, 600,
+ 601, 602, 603, 603, 603, 603, 603, 603, 603, 603, 604, 568, 568, 605,
+ 606, 574, 574, 607, 568, 568, 568, 568, 573, 608, 568, 609, 106, 568,
+ 568, 568, 568, 610, 106, 106, 106, 568, 611, 106, 106, 612, 612, 612,
+ 612, 612, 613, 613, 614, 615, 615, 615, 615, 615, 615, 615, 615, 615,
+ 616, 612, 612, 617, 617, 617, 617, 617, 617, 617, 617, 617, 618, 617,
+ 617, 617, 617, 618, 568, 617, 617, 619, 568, 620, 569, 621, 622, 623,
+ 624, 569, 568, 619, 572, 568, 574, 625, 626, 622, 627, 568, 568, 568,
+ 568, 628, 568, 568, 568, 629, 630, 568, 568, 568, 568, 568, 631, 568,
+ 632, 568, 631, 633, 634, 617, 617, 635, 617, 617, 617, 636, 568, 568,
+ 568, 568, 568, 568, 637, 568, 568, 572, 568, 568, 638, 639, 612, 640,
+ 640, 641, 568, 568, 568, 568, 568, 642, 643, 644, 645, 646, 647, 574,
+ 574, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648,
+ 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648,
+ 648, 648, 648, 648, 648, 574, 574, 574, 574, 574, 574, 574, 574, 574,
+ 574, 574, 574, 574, 574, 574, 574, 649, 650, 650, 651, 583, 583, 574,
+ 652, 597, 653, 654, 655, 656, 657, 658, 659, 574, 660, 583, 661, 662,
+ 663, 664, 645, 574, 574, 586, 652, 664, 665, 666, 667, 583, 583, 583,
+ 583, 668, 669, 583, 583, 583, 583, 670, 671, 672, 645, 673, 674, 568,
+ 568, 568, 568, 568, 568, 574, 574, 675, 676, 677, 678, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 679, 679, 679, 679, 679, 680, 681, 681, 681, 681, 681,
+ 682, 683, 684, 685, 686, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
+ 687, 688, 689, 690, 691, 691, 691, 691, 692, 693, 694, 694, 694, 694,
+ 694, 694, 694, 695, 696, 697, 366, 366, 368, 106, 368, 368, 368, 368,
+ 368, 368, 368, 368, 698, 698, 698, 698, 699, 700, 701, 702, 703, 704,
+ 529, 705, 106, 106, 106, 106, 106, 106, 106, 106, 706, 706, 706, 707,
+ 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 708, 106, 706, 706,
+ 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706,
+ 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 709, 106, 106, 106,
+ 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 721, 721,
+ 721, 721, 721, 721, 721, 721, 722, 723, 724, 725, 725, 725, 725, 725,
+ 725, 725, 725, 725, 725, 726, 727, 728, 728, 728, 728, 729, 730, 364,
+ 364, 364, 364, 364, 364, 364, 364, 364, 364, 731, 732, 733, 728, 728,
+ 728, 734, 710, 710, 710, 710, 711, 106, 725, 725, 735, 735, 735, 736,
+ 737, 738, 733, 733, 733, 739, 740, 741, 735, 735, 735, 742, 737, 738,
+ 733, 733, 733, 733, 743, 741, 733, 744, 745, 745, 745, 745, 745, 746,
+ 745, 745, 745, 745, 745, 745, 745, 745, 745, 745, 745, 733, 733, 733,
+ 747, 748, 733, 733, 733, 733, 733, 733, 733, 733, 733, 733, 733, 749,
+ 733, 733, 733, 747, 750, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 752, 753, 568, 568, 568, 568, 568, 568,
+ 568, 568, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 754,
+ 753, 753, 753, 753, 753, 753, 755, 755, 756, 755, 755, 755, 755, 755,
+ 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+ 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+ 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+ 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+ 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+ 755, 755, 755, 757, 758, 758, 758, 758, 758, 758, 759, 106, 760, 760,
+ 760, 760, 760, 761, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762,
+ 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762,
+ 762, 762, 762, 762, 762, 762, 762, 762, 762, 763, 762, 762, 764, 765,
+ 106, 106, 101, 101, 101, 101, 101, 766, 767, 768, 101, 101, 101, 769,
+ 770, 770, 770, 770, 770, 770, 770, 770, 771, 772, 773, 106, 64, 64, 774,
+ 775, 776, 27, 777, 27, 27, 27, 27, 27, 27, 27, 778, 779, 27, 780, 781,
+ 106, 27, 782, 106, 106, 106, 106, 106, 106, 106, 106, 106, 783, 784, 785,
+ 786, 786, 787, 788, 789, 790, 791, 791, 791, 791, 791, 791, 792, 106,
+ 793, 794, 794, 794, 794, 794, 795, 796, 797, 798, 799, 800, 801, 801,
+ 802, 803, 804, 805, 806, 806, 807, 808, 809, 809, 810, 811, 812, 813,
+ 364, 364, 364, 814, 815, 816, 816, 816, 816, 816, 817, 818, 819, 820,
+ 821, 822, 106, 106, 106, 106, 823, 823, 823, 823, 823, 824, 825, 106,
+ 826, 827, 828, 829, 345, 345, 830, 831, 832, 832, 832, 832, 832, 832,
+ 833, 834, 835, 106, 106, 836, 837, 838, 839, 106, 840, 840, 840, 106,
+ 368, 368, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 837, 837, 837, 837, 841, 842, 843, 844,
+ 845, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846,
+ 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846,
+ 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846,
+ 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846,
+ 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846,
+ 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846,
+ 847, 106, 365, 365, 848, 849, 365, 365, 365, 365, 365, 850, 851, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 852, 851, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 852, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 852,
+ 853, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 855, 856, 856,
+ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ 856, 857, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
+ 856, 858, 753, 753, 753, 753, 859, 106, 860, 861, 121, 862, 863, 864,
+ 865, 121, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 866, 867, 868, 106, 869, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 870, 106, 106, 128, 128, 128, 128,
+ 128, 128, 128, 128, 871, 128, 128, 128, 128, 128, 128, 106, 106, 106,
+ 106, 106, 128, 872, 873, 873, 874, 875, 501, 106, 876, 877, 878, 879,
+ 880, 881, 882, 883, 884, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+ 128, 128, 128, 128, 128, 128, 128, 885, 886, 887, 888, 889, 890, 891,
+ 891, 892, 893, 894, 894, 895, 896, 897, 898, 897, 897, 897, 897, 899,
+ 900, 900, 900, 901, 902, 902, 902, 903, 904, 905, 106, 906, 907, 908,
+ 907, 907, 909, 907, 907, 910, 907, 911, 907, 911, 106, 106, 106, 106,
+ 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907,
+ 907, 912, 913, 914, 914, 914, 914, 914, 915, 603, 916, 916, 916, 916,
+ 916, 916, 917, 918, 919, 920, 568, 609, 106, 106, 106, 106, 106, 106,
+ 603, 603, 603, 603, 603, 921, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 922, 922, 922, 923, 924, 924,
+ 924, 924, 924, 924, 925, 106, 106, 106, 106, 106, 926, 926, 926, 927,
+ 928, 106, 929, 929, 930, 931, 106, 106, 106, 106, 106, 106, 932, 932,
+ 932, 933, 934, 934, 934, 934, 935, 934, 936, 106, 106, 106, 106, 106,
+ 937, 937, 937, 937, 937, 938, 938, 938, 938, 938, 939, 939, 939, 939,
+ 939, 939, 940, 940, 940, 941, 942, 943, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 944, 945, 946, 946, 946, 946, 947, 948, 949, 949,
+ 950, 951, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 952, 952, 953, 954, 955, 955,
+ 955, 956, 106, 106, 106, 106, 106, 106, 106, 106, 957, 957, 957, 957,
+ 958, 958, 958, 959, 106, 106, 106, 106, 106, 106, 106, 106, 960, 961,
+ 962, 963, 964, 964, 965, 966, 967, 106, 968, 969, 970, 970, 970, 971,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 972, 972, 972, 972, 972, 972, 973, 974, 975, 975, 976, 977,
+ 978, 978, 979, 980, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 981, 981, 981, 981, 981, 981, 981, 981,
+ 981, 982, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 983, 983, 983, 984, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 985, 986, 986, 986, 986, 986, 986, 987, 988, 989, 990, 991, 992, 993,
+ 106, 106, 994, 995, 995, 995, 995, 995, 996, 997, 998, 106, 999, 999,
+ 999, 1000, 1001, 1002, 1003, 1004, 1004, 1004, 1005, 1006, 1007, 1008,
+ 1009, 106, 106, 106, 106, 106, 106, 106, 1010, 1011, 1011, 1011, 1011,
+ 1011, 1012, 1013, 1014, 1015, 1016, 1017, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 1018, 1018, 1018, 1018, 1018, 1019, 1020, 106, 1021, 1022, 106, 106, 106,
+ 106, 106, 106, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1024, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025,
+ 1025, 1025, 1025, 1025, 1026, 106, 1027, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 1028, 1028, 1028,
+ 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028,
+ 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028,
+ 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1029, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 770, 770, 770,
+ 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770,
+ 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770,
+ 770, 770, 770, 770, 770, 770, 770, 770, 1030, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031,
+ 1032, 106, 1033, 1034, 1034, 1034, 1034, 1035, 106, 1036, 1037, 1038,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 1039, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603,
+ 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603,
+ 603, 603, 603, 603, 1040, 106, 603, 603, 603, 603, 1041, 1042, 603, 603,
+ 603, 603, 603, 603, 1043, 1044, 1045, 1046, 1047, 1048, 603, 603, 603,
+ 1049, 603, 603, 603, 603, 603, 1040, 106, 106, 106, 106, 919, 919, 919,
+ 919, 919, 919, 919, 919, 1050, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 610, 106, 914,
+ 914, 1051, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 1052, 1052, 1052, 1053, 1054, 1054, 1055, 1052,
+ 1052, 1056, 1057, 1054, 1054, 1052, 1052, 1052, 1053, 1054, 1054, 1058,
+ 1059, 1060, 1056, 1061, 1062, 1054, 1052, 1052, 1052, 1053, 1054, 1054,
+ 1063, 1064, 1065, 1066, 1054, 1054, 1054, 1067, 1068, 1069, 1070, 1054,
+ 1054, 1055, 1052, 1052, 1056, 1054, 1054, 1054, 1052, 1052, 1052, 1053,
+ 1054, 1054, 1055, 1052, 1052, 1056, 1054, 1054, 1054, 1052, 1052, 1052,
+ 1053, 1054, 1054, 1055, 1052, 1052, 1056, 1054, 1054, 1054, 1052, 1052,
+ 1052, 1053, 1054, 1054, 1071, 1052, 1052, 1052, 1072, 1054, 1054, 1073,
+ 1074, 1052, 1052, 1075, 1054, 1054, 1076, 1055, 1052, 1052, 1077, 1054,
+ 1054, 1078, 1079, 1052, 1052, 1080, 1054, 1054, 1054, 1081, 1052, 1052,
+ 1052, 1072, 1054, 1054, 1073, 1082, 1083, 1083, 1083, 1083, 1083, 1083,
+ 1084, 128, 128, 128, 1085, 1086, 1087, 1088, 1089, 1090, 1085, 1091,
+ 1085, 1087, 1087, 1092, 128, 1093, 128, 1094, 1095, 1093, 128, 1094, 106,
+ 106, 106, 106, 106, 106, 1096, 106, 568, 568, 568, 568, 568, 609, 568,
+ 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 609, 106, 568,
+ 610, 636, 610, 636, 568, 636, 568, 106, 106, 106, 106, 613, 1097, 615,
+ 615, 615, 1098, 615, 615, 615, 615, 615, 615, 615, 1099, 615, 615, 615,
+ 615, 615, 1100, 106, 106, 106, 106, 106, 106, 106, 106, 1101, 603, 603,
+ 603, 1102, 106, 733, 733, 733, 733, 733, 1103, 733, 1104, 1105, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 568, 568, 568, 568, 1106, 106, 1107, 568, 568,
+ 568, 568, 568, 568, 568, 568, 1108, 568, 568, 609, 106, 568, 568, 568,
+ 568, 1109, 611, 106, 106, 568, 568, 1106, 106, 568, 568, 568, 568, 568,
+ 568, 568, 610, 1110, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568,
+ 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 1111, 568,
+ 568, 568, 568, 568, 568, 568, 1112, 609, 106, 568, 568, 568, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 106, 106, 1113, 568, 568, 568, 568, 568, 568, 568, 568, 1114, 568, 106,
+ 106, 106, 106, 106, 106, 568, 568, 568, 568, 568, 568, 568, 568, 1112,
+ 106, 106, 106, 106, 106, 106, 106, 568, 568, 568, 568, 568, 568, 568,
+ 568, 568, 568, 568, 568, 568, 568, 609, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 1115, 753, 753, 753, 753,
+ 753, 751, 751, 751, 751, 751, 751, 754, 753, 750, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751,
+ 751, 751, 751, 751, 751, 751, 751, 751, 752, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 856,
+ 856, 856, 857, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753,
+ 753, 753, 753, 753, 753, 753, 1116, 1117, 106, 106, 106, 1118, 1118,
+ 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 106, 106,
+ 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+ 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873,
+ 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873,
+ 873, 873, 106, 106, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854,
+ 854, 854, 854, 854, 854, 854, 854, 1119,
+};
+
+static const unsigned short index2[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 4, 3, 1, 1, 1, 1, 1, 1, 3, 3, 3, 2,
+ 5, 6, 6, 7, 8, 7, 6, 6, 9, 10, 6, 11, 12, 13, 12, 12, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 12, 6, 15, 16, 15, 6, 6, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 9, 6, 10, 18, 19, 18, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 9, 16,
+ 10, 16, 1, 1, 1, 1, 1, 1, 3, 1, 1, 21, 22, 8, 8, 23, 8, 24, 22, 25, 26,
+ 27, 28, 16, 29, 30, 18, 31, 32, 33, 33, 25, 34, 22, 22, 25, 33, 27, 35,
+ 36, 36, 36, 22, 37, 37, 37, 37, 37, 37, 38, 37, 37, 37, 37, 37, 37, 37,
+ 37, 37, 38, 37, 37, 37, 37, 37, 37, 39, 38, 37, 37, 37, 37, 37, 38, 40,
+ 40, 40, 41, 41, 41, 41, 40, 41, 40, 40, 40, 41, 40, 40, 41, 41, 40, 41,
+ 40, 40, 41, 41, 41, 39, 40, 40, 40, 41, 40, 41, 40, 41, 37, 40, 37, 41,
+ 37, 41, 37, 41, 37, 41, 37, 41, 37, 41, 37, 41, 37, 40, 37, 40, 37, 41,
+ 37, 41, 37, 41, 37, 40, 37, 41, 37, 41, 37, 41, 37, 41, 37, 41, 38, 40,
+ 37, 40, 38, 40, 37, 41, 37, 41, 40, 37, 41, 37, 41, 37, 41, 38, 40, 38,
+ 40, 37, 40, 37, 41, 37, 40, 40, 38, 40, 37, 40, 37, 41, 37, 41, 38, 40,
+ 37, 41, 37, 41, 37, 37, 41, 37, 41, 37, 41, 41, 41, 37, 37, 41, 37, 41,
+ 37, 37, 41, 37, 37, 37, 41, 41, 37, 37, 37, 37, 41, 37, 37, 41, 37, 37,
+ 37, 41, 41, 41, 37, 37, 41, 37, 37, 41, 37, 41, 37, 41, 37, 37, 41, 37,
+ 41, 41, 37, 41, 37, 37, 41, 37, 37, 37, 41, 37, 41, 37, 37, 41, 41, 42,
+ 37, 41, 41, 41, 42, 42, 42, 42, 37, 43, 41, 37, 43, 41, 37, 43, 41, 37,
+ 40, 37, 40, 37, 40, 37, 40, 37, 40, 37, 40, 37, 40, 37, 40, 41, 37, 41,
+ 41, 37, 43, 41, 37, 41, 37, 37, 37, 41, 37, 41, 41, 41, 41, 41, 41, 41,
+ 37, 37, 41, 37, 37, 41, 41, 37, 41, 37, 37, 37, 37, 41, 41, 40, 41, 41,
+ 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 41,
+ 41, 41, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 45, 46, 46, 46, 46, 46,
+ 46, 46, 47, 47, 25, 47, 45, 48, 45, 48, 48, 48, 45, 48, 45, 45, 49, 46,
+ 47, 47, 47, 47, 47, 47, 25, 25, 25, 25, 47, 25, 47, 25, 44, 44, 44, 44,
+ 44, 47, 47, 47, 47, 47, 50, 50, 45, 47, 46, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 53, 53,
+ 53, 53, 52, 54, 53, 53, 53, 53, 53, 55, 55, 53, 53, 53, 53, 55, 55, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 56, 56, 56, 56, 56, 53, 53, 53,
+ 53, 51, 51, 51, 51, 51, 51, 51, 51, 57, 51, 53, 53, 53, 51, 51, 51, 53,
+ 53, 58, 51, 51, 51, 53, 53, 53, 53, 51, 52, 53, 53, 51, 59, 60, 60, 59,
+ 60, 60, 59, 51, 51, 51, 51, 51, 61, 62, 61, 62, 45, 63, 61, 62, 64, 64,
+ 65, 62, 62, 62, 66, 64, 64, 64, 64, 64, 63, 47, 61, 66, 61, 61, 61, 64,
+ 61, 64, 61, 61, 62, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 64, 67, 67, 67, 67, 67, 67, 67, 61, 61, 62, 62, 62, 62,
+ 62, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,
+ 62, 68, 68, 68, 68, 68, 68, 68, 62, 62, 62, 62, 62, 61, 62, 62, 61, 61,
+ 61, 62, 62, 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, 69, 70, 69, 70,
+ 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 62, 62, 62, 62, 61, 62, 71, 61,
+ 62, 61, 61, 62, 62, 61, 61, 61, 72, 73, 72, 72, 72, 72, 72, 72, 72, 72,
+ 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74,
+ 74, 74, 74, 74, 75, 74, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75,
+ 75, 75, 72, 75, 72, 75, 72, 75, 72, 75, 72, 75, 76, 77, 77, 78, 78, 77,
+ 79, 79, 72, 75, 72, 75, 72, 75, 72, 72, 75, 72, 75, 72, 75, 72, 75, 72,
+ 75, 72, 75, 72, 75, 75, 64, 64, 64, 64, 64, 64, 64, 64, 64, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 64, 64, 81, 82, 82, 82, 82, 82, 82, 64, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 64, 84, 85, 64, 64, 64, 64, 86, 64, 87,
+ 88, 88, 88, 88, 87, 88, 88, 88, 89, 87, 88, 88, 88, 88, 88, 88, 87, 87,
+ 87, 87, 87, 87, 88, 88, 87, 88, 88, 89, 90, 88, 91, 92, 93, 94, 95, 96,
+ 97, 98, 99, 100, 100, 101, 102, 103, 104, 105, 106, 107, 108, 106, 88,
+ 87, 106, 99, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 64,
+ 64, 64, 64, 64, 109, 109, 109, 106, 106, 64, 64, 64, 110, 110, 110, 110,
+ 110, 64, 111, 111, 112, 113, 113, 114, 115, 116, 117, 117, 118, 118, 118,
+ 118, 118, 118, 118, 118, 119, 120, 121, 122, 64, 64, 116, 122, 123, 123,
+ 123, 123, 123, 123, 123, 123, 124, 123, 123, 123, 123, 123, 123, 123,
+ 123, 123, 123, 125, 126, 127, 128, 129, 130, 131, 132, 78, 78, 133, 134,
+ 118, 118, 118, 118, 118, 134, 118, 118, 134, 135, 135, 135, 135, 135,
+ 135, 135, 135, 135, 135, 113, 136, 136, 116, 123, 123, 137, 123, 123,
+ 123, 123, 123, 123, 123, 123, 123, 123, 123, 116, 123, 118, 118, 118,
+ 118, 118, 118, 118, 138, 117, 118, 118, 118, 118, 134, 118, 139, 139,
+ 118, 118, 117, 134, 118, 118, 134, 123, 123, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 123, 123, 123, 141, 141, 123, 142, 142, 142,
+ 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 64, 143, 144, 145,
+ 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144,
+ 146, 147, 146, 146, 147, 146, 146, 147, 147, 147, 146, 147, 147, 146,
+ 147, 146, 146, 146, 147, 146, 147, 146, 147, 146, 147, 146, 146, 64, 64,
+ 144, 144, 144, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148,
+ 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149,
+ 148, 64, 64, 64, 64, 64, 64, 150, 150, 150, 150, 150, 150, 150, 150, 150,
+ 150, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151,
+ 151, 151, 151, 151, 152, 152, 152, 152, 152, 152, 152, 153, 152, 154,
+ 154, 155, 156, 156, 156, 154, 64, 64, 64, 64, 64, 157, 157, 157, 157,
+ 157, 157, 157, 157, 157, 157, 157, 157, 157, 157, 158, 158, 158, 158,
+ 159, 158, 158, 158, 158, 158, 158, 158, 158, 158, 159, 158, 158, 158,
+ 159, 158, 158, 158, 158, 158, 64, 64, 160, 160, 160, 160, 160, 160, 160,
+ 160, 160, 160, 160, 160, 160, 160, 160, 64, 161, 161, 161, 161, 161, 161,
+ 161, 161, 161, 162, 162, 162, 64, 64, 163, 64, 123, 64, 123, 123, 123,
+ 123, 123, 123, 123, 123, 123, 123, 123, 64, 64, 64, 64, 64, 64, 64, 118,
+ 118, 134, 118, 118, 134, 118, 118, 118, 134, 134, 134, 164, 165, 166,
+ 118, 118, 118, 134, 118, 118, 134, 134, 118, 118, 118, 118, 64, 167, 167,
+ 167, 168, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169,
+ 169, 169, 167, 168, 170, 169, 168, 168, 168, 167, 167, 167, 167, 167,
+ 167, 167, 167, 168, 168, 168, 168, 171, 168, 168, 169, 78, 133, 172, 172,
+ 167, 167, 167, 169, 169, 167, 167, 84, 84, 173, 173, 173, 173, 173, 173,
+ 173, 173, 173, 173, 174, 175, 169, 169, 169, 169, 169, 169, 64, 169, 169,
+ 169, 169, 169, 169, 169, 64, 176, 177, 177, 64, 178, 178, 178, 178, 178,
+ 178, 178, 178, 64, 64, 178, 178, 64, 64, 178, 178, 178, 178, 178, 178,
+ 178, 178, 178, 178, 178, 178, 178, 178, 64, 178, 178, 178, 178, 178, 178,
+ 178, 64, 178, 64, 64, 64, 178, 178, 178, 178, 64, 64, 179, 178, 177, 177,
+ 177, 176, 176, 176, 176, 64, 64, 177, 177, 64, 64, 177, 177, 180, 178,
+ 64, 64, 64, 64, 64, 64, 64, 64, 177, 64, 64, 64, 64, 178, 178, 64, 178,
+ 178, 178, 176, 176, 64, 64, 181, 181, 181, 181, 181, 181, 181, 181, 181,
+ 181, 178, 178, 182, 182, 183, 183, 183, 183, 183, 183, 184, 182, 64, 64,
+ 64, 64, 64, 185, 185, 186, 64, 187, 187, 187, 187, 187, 187, 64, 64, 64,
+ 64, 187, 187, 64, 64, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
+ 187, 187, 187, 187, 64, 187, 187, 187, 187, 187, 187, 187, 64, 187, 187,
+ 64, 187, 187, 64, 187, 187, 64, 64, 188, 64, 186, 186, 186, 185, 185, 64,
+ 64, 64, 64, 185, 185, 64, 64, 185, 185, 189, 64, 64, 64, 185, 64, 64, 64,
+ 64, 64, 64, 64, 187, 187, 187, 187, 64, 187, 64, 64, 64, 64, 64, 64, 64,
+ 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 185, 185, 187, 187,
+ 187, 185, 64, 64, 64, 191, 191, 192, 64, 193, 193, 193, 193, 193, 193,
+ 193, 193, 193, 64, 193, 193, 193, 64, 193, 193, 193, 193, 193, 193, 193,
+ 193, 193, 193, 193, 193, 193, 193, 64, 193, 193, 193, 193, 193, 193, 193,
+ 64, 193, 193, 64, 193, 193, 193, 193, 193, 64, 64, 194, 193, 192, 192,
+ 192, 191, 191, 191, 191, 191, 64, 191, 191, 192, 64, 192, 192, 195, 64,
+ 64, 193, 64, 64, 64, 64, 64, 64, 64, 193, 193, 191, 191, 64, 64, 196,
+ 196, 196, 196, 196, 196, 196, 196, 196, 196, 197, 198, 64, 64, 64, 64,
+ 64, 64, 64, 199, 200, 200, 64, 201, 201, 201, 201, 201, 201, 201, 201,
+ 64, 64, 201, 201, 64, 64, 201, 201, 201, 201, 201, 201, 201, 201, 201,
+ 201, 201, 201, 201, 201, 64, 201, 201, 201, 201, 201, 201, 201, 64, 201,
+ 201, 64, 201, 201, 201, 201, 201, 64, 64, 202, 201, 200, 199, 200, 199,
+ 199, 199, 199, 64, 64, 200, 200, 64, 64, 200, 200, 203, 64, 64, 64, 64,
+ 64, 64, 64, 64, 199, 200, 64, 64, 64, 64, 201, 201, 64, 201, 201, 201,
+ 199, 199, 64, 64, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205,
+ 201, 206, 206, 206, 206, 206, 206, 64, 64, 207, 208, 64, 208, 208, 208,
+ 208, 208, 208, 64, 64, 64, 208, 208, 208, 64, 208, 208, 208, 208, 64, 64,
+ 64, 208, 208, 64, 208, 64, 208, 208, 64, 64, 64, 208, 208, 64, 64, 64,
+ 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 64, 64, 64, 64, 209,
+ 209, 207, 209, 209, 64, 64, 64, 209, 209, 209, 64, 209, 209, 209, 210,
+ 64, 64, 208, 64, 64, 64, 64, 64, 64, 209, 64, 64, 64, 64, 64, 64, 211,
+ 211, 211, 211, 211, 211, 211, 211, 211, 211, 212, 212, 212, 213, 213,
+ 213, 213, 213, 213, 214, 213, 64, 64, 64, 64, 64, 64, 215, 215, 215, 64,
+ 216, 216, 216, 216, 216, 216, 216, 216, 64, 216, 216, 216, 64, 216, 216,
+ 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216,
+ 216, 216, 64, 216, 216, 216, 216, 216, 64, 64, 64, 216, 217, 217, 217,
+ 215, 215, 215, 215, 64, 217, 217, 217, 64, 217, 217, 217, 218, 64, 64,
+ 64, 64, 64, 64, 64, 219, 220, 64, 216, 216, 64, 64, 64, 64, 64, 64, 216,
+ 216, 217, 217, 64, 64, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
+ 222, 222, 222, 222, 222, 222, 222, 223, 64, 64, 224, 224, 64, 225, 225,
+ 225, 225, 225, 225, 225, 225, 64, 225, 225, 225, 64, 225, 225, 225, 225,
+ 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 64,
+ 225, 225, 225, 225, 225, 64, 64, 226, 225, 224, 227, 224, 224, 224, 224,
+ 224, 64, 227, 224, 224, 64, 224, 224, 228, 229, 64, 64, 64, 64, 64, 64,
+ 64, 224, 224, 64, 64, 64, 64, 64, 64, 64, 225, 64, 225, 225, 228, 228,
+ 64, 64, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 64, 225, 225,
+ 64, 64, 64, 64, 64, 64, 64, 231, 231, 64, 232, 232, 232, 232, 232, 232,
+ 232, 232, 64, 232, 232, 232, 64, 232, 232, 232, 232, 232, 232, 232, 232,
+ 232, 232, 232, 232, 232, 232, 232, 232, 232, 64, 64, 232, 231, 231, 231,
+ 233, 233, 233, 233, 64, 231, 231, 231, 64, 231, 231, 231, 234, 232, 64,
+ 64, 64, 64, 64, 64, 64, 64, 231, 232, 232, 233, 233, 64, 64, 235, 235,
+ 235, 235, 235, 235, 235, 235, 235, 235, 236, 236, 236, 236, 236, 236, 64,
+ 64, 64, 237, 232, 232, 232, 232, 232, 232, 64, 64, 238, 238, 64, 239,
+ 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239,
+ 239, 239, 239, 64, 64, 64, 239, 239, 239, 239, 239, 239, 239, 239, 64,
+ 239, 239, 239, 239, 239, 239, 239, 239, 239, 64, 239, 64, 64, 64, 64,
+ 240, 64, 64, 64, 64, 238, 238, 238, 241, 241, 241, 64, 241, 64, 238, 238,
+ 238, 238, 238, 238, 238, 238, 64, 64, 238, 238, 242, 64, 64, 64, 64, 243,
+ 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243,
+ 243, 244, 243, 243, 244, 244, 244, 244, 245, 245, 246, 64, 64, 64, 64,
+ 247, 243, 243, 243, 243, 243, 243, 248, 244, 249, 249, 249, 249, 244,
+ 244, 244, 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 250,
+ 250, 64, 64, 64, 64, 64, 252, 252, 64, 252, 64, 64, 252, 252, 64, 252,
+ 64, 64, 252, 64, 64, 64, 64, 64, 64, 252, 252, 252, 252, 64, 252, 252,
+ 252, 252, 252, 252, 252, 64, 252, 252, 252, 64, 252, 64, 252, 64, 64,
+ 252, 252, 64, 252, 252, 252, 252, 253, 252, 252, 253, 253, 253, 253, 254,
+ 254, 64, 253, 253, 252, 64, 64, 252, 252, 252, 252, 252, 64, 255, 64,
+ 256, 256, 256, 256, 253, 253, 64, 64, 257, 257, 257, 257, 257, 257, 257,
+ 257, 257, 257, 64, 64, 252, 252, 252, 252, 258, 259, 259, 259, 260, 260,
+ 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 259,
+ 260, 259, 259, 259, 261, 261, 259, 259, 259, 259, 259, 259, 262, 262,
+ 262, 262, 262, 262, 262, 262, 262, 262, 263, 263, 263, 263, 263, 263,
+ 263, 263, 263, 263, 259, 261, 259, 261, 259, 264, 265, 266, 265, 266,
+ 267, 267, 258, 258, 258, 258, 258, 258, 258, 258, 64, 258, 258, 258, 258,
+ 258, 258, 258, 258, 258, 258, 258, 258, 64, 64, 64, 64, 268, 269, 270,
+ 271, 270, 270, 270, 270, 270, 269, 269, 269, 269, 270, 267, 269, 270,
+ 272, 272, 273, 260, 272, 272, 258, 258, 258, 258, 258, 270, 270, 270,
+ 270, 270, 270, 270, 270, 270, 270, 270, 64, 270, 270, 270, 270, 270, 270,
+ 270, 270, 270, 270, 270, 270, 64, 259, 259, 259, 259, 259, 259, 259, 259,
+ 261, 259, 259, 259, 259, 259, 259, 64, 259, 259, 260, 260, 260, 260, 260,
+ 274, 274, 274, 274, 260, 260, 64, 64, 64, 64, 64, 275, 275, 275, 275,
+ 275, 275, 275, 275, 275, 275, 275, 276, 276, 277, 277, 277, 277, 276,
+ 277, 277, 277, 277, 277, 278, 276, 279, 279, 276, 276, 277, 277, 275,
+ 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281,
+ 281, 281, 275, 275, 275, 275, 275, 275, 276, 276, 277, 277, 275, 275,
+ 275, 275, 277, 277, 277, 275, 276, 276, 276, 275, 275, 276, 276, 276,
+ 276, 276, 276, 276, 275, 275, 275, 277, 277, 277, 277, 275, 275, 275,
+ 275, 275, 277, 276, 276, 277, 277, 276, 276, 276, 276, 276, 276, 282,
+ 275, 276, 280, 280, 276, 276, 276, 277, 283, 283, 284, 284, 284, 284,
+ 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 64, 284, 64, 64, 64,
+ 64, 64, 284, 64, 64, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285,
+ 285, 84, 286, 285, 285, 285, 287, 287, 287, 287, 287, 287, 287, 287, 288,
+ 288, 288, 288, 288, 288, 288, 288, 289, 289, 289, 289, 289, 289, 289,
+ 289, 289, 64, 289, 289, 289, 289, 64, 64, 289, 289, 289, 289, 289, 289,
+ 289, 64, 289, 289, 289, 64, 64, 290, 290, 290, 291, 291, 291, 291, 291,
+ 291, 291, 291, 291, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292,
+ 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 64, 64, 64, 293, 293,
+ 293, 293, 293, 293, 293, 293, 293, 293, 64, 64, 64, 64, 64, 64, 294, 294,
+ 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 64, 64, 64, 295,
+ 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296,
+ 296, 296, 296, 296, 296, 296, 297, 297, 296, 298, 299, 299, 299, 299,
+ 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299,
+ 300, 301, 64, 64, 64, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302,
+ 302, 84, 84, 84, 303, 303, 303, 64, 64, 64, 64, 64, 64, 64, 304, 304,
+ 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 64, 304, 304, 304,
+ 304, 305, 305, 306, 64, 64, 64, 307, 307, 307, 307, 307, 307, 307, 307,
+ 307, 307, 308, 308, 309, 84, 84, 64, 310, 310, 310, 310, 310, 310, 310,
+ 310, 310, 310, 311, 311, 64, 64, 64, 64, 312, 312, 312, 312, 312, 312,
+ 312, 312, 312, 312, 312, 312, 312, 64, 312, 312, 312, 64, 313, 313, 64,
+ 64, 64, 64, 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, 314,
+ 315, 315, 316, 315, 315, 315, 315, 315, 315, 315, 316, 316, 316, 316,
+ 316, 316, 316, 316, 315, 316, 316, 315, 315, 315, 315, 315, 315, 315,
+ 315, 315, 317, 315, 318, 318, 318, 319, 318, 318, 318, 320, 314, 321, 64,
+ 64, 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, 64, 64, 64, 64, 64,
+ 64, 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, 64, 64, 64, 64, 64,
+ 64, 324, 324, 66, 66, 324, 66, 325, 324, 324, 324, 324, 326, 326, 326,
+ 327, 64, 328, 328, 328, 328, 328, 328, 328, 328, 328, 328, 64, 64, 64,
+ 64, 64, 64, 329, 329, 329, 329, 329, 329, 329, 329, 329, 329, 329, 330,
+ 329, 329, 329, 329, 329, 331, 329, 64, 64, 64, 64, 64, 296, 296, 296,
+ 296, 296, 296, 64, 64, 332, 332, 332, 332, 332, 332, 332, 332, 332, 332,
+ 332, 332, 332, 64, 64, 64, 333, 333, 333, 334, 334, 334, 334, 333, 333,
+ 334, 334, 334, 64, 64, 64, 64, 334, 334, 333, 334, 334, 334, 334, 334,
+ 334, 335, 336, 337, 64, 64, 64, 64, 338, 64, 64, 64, 339, 339, 340, 340,
+ 340, 340, 340, 340, 340, 340, 340, 340, 341, 341, 341, 341, 341, 341,
+ 341, 341, 341, 341, 341, 341, 341, 341, 64, 64, 341, 341, 341, 341, 341,
+ 64, 64, 64, 342, 342, 342, 342, 342, 342, 342, 342, 342, 342, 342, 342,
+ 64, 64, 64, 64, 343, 343, 343, 343, 343, 343, 343, 343, 343, 342, 342,
+ 342, 342, 342, 342, 342, 343, 343, 64, 64, 64, 64, 64, 64, 344, 344, 344,
+ 344, 344, 344, 344, 344, 344, 344, 345, 64, 64, 64, 346, 346, 347, 347,
+ 347, 347, 347, 347, 347, 347, 348, 348, 348, 348, 348, 348, 348, 348,
+ 348, 348, 348, 348, 348, 348, 348, 349, 350, 351, 351, 351, 64, 64, 352,
+ 352, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353,
+ 354, 355, 354, 355, 355, 355, 355, 355, 355, 355, 64, 356, 354, 355, 354,
+ 354, 355, 355, 355, 355, 355, 355, 355, 355, 354, 354, 354, 354, 354,
+ 354, 355, 355, 357, 357, 357, 357, 357, 357, 357, 357, 64, 64, 358, 359,
+ 359, 359, 359, 359, 359, 359, 359, 359, 359, 64, 64, 64, 64, 64, 64, 360,
+ 360, 360, 360, 360, 360, 360, 361, 360, 360, 360, 360, 360, 360, 64, 64,
+ 362, 362, 362, 362, 363, 364, 364, 364, 364, 364, 364, 364, 364, 364,
+ 364, 364, 364, 364, 364, 364, 365, 363, 362, 362, 362, 362, 362, 363,
+ 362, 363, 363, 363, 363, 363, 362, 363, 366, 364, 364, 364, 364, 364,
+ 364, 364, 64, 64, 64, 64, 367, 367, 367, 367, 367, 367, 367, 367, 367,
+ 367, 368, 368, 368, 368, 368, 368, 368, 369, 369, 369, 369, 369, 369,
+ 369, 369, 369, 369, 370, 371, 370, 370, 370, 370, 370, 370, 370, 369,
+ 369, 369, 369, 369, 369, 369, 369, 369, 64, 64, 64, 372, 372, 373, 374,
+ 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 373,
+ 372, 372, 372, 372, 373, 373, 372, 372, 375, 376, 373, 373, 374, 374,
+ 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 374, 374, 374, 374,
+ 374, 374, 378, 378, 378, 378, 378, 378, 378, 378, 378, 378, 378, 378,
+ 378, 378, 379, 380, 381, 381, 380, 380, 380, 381, 380, 381, 381, 381,
+ 382, 382, 64, 64, 64, 64, 64, 64, 64, 64, 383, 383, 383, 383, 384, 384,
+ 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 385, 385, 385, 385,
+ 385, 385, 385, 385, 386, 386, 386, 386, 386, 386, 386, 386, 385, 385,
+ 386, 387, 64, 64, 64, 388, 388, 388, 388, 388, 389, 389, 389, 389, 389,
+ 389, 389, 389, 389, 389, 64, 64, 64, 384, 384, 384, 390, 390, 390, 390,
+ 390, 390, 390, 390, 390, 390, 391, 391, 391, 391, 391, 391, 391, 391,
+ 391, 391, 391, 391, 391, 391, 392, 392, 392, 392, 392, 392, 393, 393,
+ 394, 394, 394, 394, 394, 394, 394, 394, 78, 78, 78, 84, 395, 133, 133,
+ 133, 133, 133, 78, 78, 133, 133, 133, 133, 78, 396, 395, 395, 395, 395,
+ 395, 395, 395, 397, 397, 397, 397, 133, 397, 397, 397, 397, 396, 396, 78,
+ 397, 397, 64, 41, 41, 41, 41, 41, 41, 62, 62, 62, 62, 62, 75, 44, 44, 44,
+ 44, 44, 44, 44, 44, 44, 65, 65, 65, 65, 65, 44, 44, 44, 44, 65, 65, 65,
+ 65, 65, 41, 41, 41, 41, 41, 398, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 65, 78, 78, 133, 78, 78,
+ 78, 78, 78, 78, 78, 133, 78, 78, 399, 400, 133, 401, 78, 78, 78, 78, 78,
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 64,
+ 64, 64, 64, 64, 402, 133, 78, 133, 37, 41, 37, 41, 37, 41, 41, 41, 41,
+ 41, 41, 41, 41, 41, 37, 41, 62, 62, 62, 62, 62, 62, 62, 62, 61, 61, 61,
+ 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 64, 64, 61, 61, 61, 61, 61,
+ 61, 64, 64, 64, 61, 64, 61, 64, 61, 64, 61, 403, 403, 403, 403, 403, 403,
+ 403, 403, 62, 62, 62, 62, 62, 64, 62, 62, 61, 61, 61, 61, 403, 63, 62,
+ 63, 63, 63, 62, 62, 62, 64, 62, 62, 61, 61, 61, 61, 403, 63, 63, 63, 62,
+ 62, 62, 62, 64, 64, 62, 62, 61, 61, 61, 61, 64, 63, 63, 63, 61, 61, 61,
+ 61, 61, 63, 63, 63, 64, 64, 62, 62, 62, 64, 62, 62, 61, 61, 61, 61, 403,
+ 63, 63, 64, 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, 405,
+ 406, 406, 407, 408, 409, 410, 410, 409, 409, 409, 22, 66, 411, 412, 413,
+ 414, 411, 412, 413, 414, 22, 22, 22, 66, 22, 22, 22, 22, 415, 416, 417,
+ 418, 419, 420, 421, 21, 422, 423, 422, 422, 423, 22, 66, 66, 66, 28, 35,
+ 22, 66, 66, 22, 424, 424, 66, 66, 66, 425, 426, 427, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 428, 66, 424, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 404, 405, 405, 405, 405, 405, 64, 64, 64, 64, 64, 405, 405, 405, 405,
+ 405, 405, 429, 44, 64, 64, 33, 429, 429, 429, 429, 429, 430, 430, 428,
+ 426, 427, 431, 429, 33, 33, 33, 33, 429, 429, 429, 429, 429, 430, 430,
+ 428, 426, 427, 64, 44, 44, 44, 44, 44, 64, 64, 64, 247, 247, 247, 247,
+ 247, 247, 247, 247, 247, 432, 247, 247, 23, 247, 247, 247, 247, 247, 247,
+ 64, 64, 64, 64, 64, 78, 78, 395, 395, 78, 78, 78, 78, 395, 395, 395, 78,
+ 78, 433, 433, 433, 433, 78, 433, 433, 433, 395, 395, 78, 133, 78, 395,
+ 395, 133, 133, 133, 133, 78, 64, 64, 64, 64, 64, 64, 64, 26, 26, 434, 30,
+ 26, 30, 26, 434, 26, 30, 34, 434, 434, 434, 34, 34, 434, 434, 434, 435,
+ 26, 434, 30, 26, 428, 434, 434, 434, 434, 434, 26, 26, 26, 30, 30, 26,
+ 434, 26, 67, 26, 434, 26, 37, 38, 434, 434, 436, 34, 434, 434, 37, 434,
+ 34, 397, 397, 397, 397, 34, 26, 26, 34, 34, 434, 434, 437, 428, 428, 428,
+ 428, 434, 34, 34, 34, 34, 26, 428, 26, 26, 41, 274, 438, 438, 438, 36,
+ 36, 438, 438, 438, 438, 438, 438, 36, 36, 36, 36, 438, 439, 439, 439,
+ 439, 439, 439, 439, 439, 439, 439, 439, 439, 440, 440, 440, 440, 439,
+ 439, 440, 440, 440, 440, 440, 440, 440, 440, 440, 37, 41, 440, 440, 440,
+ 440, 36, 64, 64, 64, 64, 64, 64, 39, 39, 39, 39, 39, 30, 30, 30, 30, 30,
+ 428, 428, 26, 26, 26, 26, 428, 26, 26, 428, 26, 26, 428, 26, 26, 26, 26,
+ 26, 26, 26, 428, 26, 26, 26, 26, 26, 26, 26, 26, 26, 30, 30, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 428, 428, 26, 26, 39, 26, 39, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 30, 26, 26, 26, 26, 428, 428, 428, 428,
+ 428, 428, 428, 428, 428, 428, 428, 428, 39, 437, 441, 441, 437, 428, 428,
+ 39, 441, 437, 437, 441, 437, 437, 428, 39, 428, 441, 430, 442, 428, 441,
+ 437, 428, 428, 428, 441, 437, 437, 441, 39, 441, 441, 437, 437, 39, 437,
+ 39, 437, 39, 39, 39, 39, 441, 441, 437, 441, 437, 437, 437, 437, 437, 39,
+ 39, 39, 39, 428, 437, 428, 437, 441, 441, 437, 437, 437, 437, 437, 437,
+ 437, 437, 437, 437, 441, 437, 437, 437, 441, 428, 428, 428, 428, 428,
+ 441, 437, 437, 437, 428, 428, 428, 428, 428, 428, 428, 428, 428, 437,
+ 441, 39, 437, 428, 441, 441, 441, 441, 437, 437, 441, 441, 428, 428, 441,
+ 441, 437, 437, 441, 441, 437, 437, 441, 441, 437, 437, 437, 437, 437,
+ 428, 428, 437, 437, 437, 437, 428, 428, 39, 428, 428, 437, 39, 428, 428,
+ 428, 428, 428, 428, 428, 428, 437, 437, 428, 39, 437, 437, 437, 428, 428,
+ 428, 428, 428, 437, 441, 428, 437, 437, 437, 437, 437, 428, 428, 437,
+ 437, 428, 428, 428, 428, 437, 437, 437, 437, 437, 437, 437, 437, 428,
+ 428, 437, 437, 437, 437, 26, 26, 26, 26, 26, 26, 30, 26, 26, 26, 26, 26,
+ 437, 437, 26, 26, 26, 26, 26, 26, 26, 443, 444, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274,
+ 274, 274, 274, 26, 428, 26, 26, 26, 26, 26, 26, 26, 26, 274, 26, 26, 26,
+ 26, 26, 428, 428, 428, 428, 428, 428, 428, 428, 428, 26, 26, 26, 26, 428,
+ 428, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 64, 64, 64, 64, 26, 26, 26,
+ 26, 26, 26, 26, 64, 26, 26, 26, 64, 64, 64, 64, 64, 36, 36, 36, 36, 36,
+ 36, 36, 36, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 445, 445,
+ 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, 438, 36, 36,
+ 36, 36, 36, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 26, 26, 26,
+ 26, 26, 26, 30, 30, 30, 30, 26, 26, 30, 30, 26, 30, 30, 30, 30, 30, 26,
+ 26, 30, 30, 26, 26, 30, 39, 26, 26, 26, 26, 30, 30, 26, 26, 30, 39, 26,
+ 26, 26, 26, 30, 30, 30, 26, 26, 30, 26, 26, 30, 30, 26, 26, 26, 26, 26,
+ 30, 30, 26, 26, 30, 26, 26, 26, 26, 30, 30, 26, 26, 26, 26, 30, 26, 30,
+ 26, 30, 26, 30, 26, 26, 26, 26, 26, 30, 30, 26, 30, 30, 30, 26, 30, 30,
+ 30, 30, 26, 30, 30, 26, 39, 26, 26, 26, 26, 26, 26, 30, 30, 26, 26, 26,
+ 26, 274, 26, 26, 26, 26, 26, 26, 26, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 26, 30, 30, 30, 26, 30, 26, 26, 26, 26, 64, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 30, 26, 26, 426, 427, 426, 427, 426, 427, 426,
+ 427, 426, 427, 426, 427, 426, 427, 36, 36, 438, 438, 438, 438, 438, 438,
+ 438, 438, 438, 438, 438, 438, 26, 26, 26, 26, 437, 428, 428, 437, 437,
+ 426, 427, 428, 437, 437, 428, 437, 437, 437, 428, 428, 428, 428, 428,
+ 437, 437, 437, 437, 428, 428, 428, 428, 428, 437, 437, 437, 428, 428,
+ 428, 437, 437, 437, 437, 9, 10, 9, 10, 9, 10, 9, 10, 426, 427, 446, 446,
+ 446, 446, 446, 446, 446, 446, 428, 428, 428, 426, 427, 9, 10, 426, 427,
+ 426, 427, 426, 427, 426, 427, 426, 427, 428, 428, 437, 437, 437, 437,
+ 437, 437, 428, 428, 428, 428, 428, 428, 428, 428, 437, 428, 428, 428,
+ 428, 437, 437, 437, 437, 437, 428, 437, 437, 428, 428, 426, 427, 426,
+ 427, 437, 428, 428, 428, 428, 437, 428, 437, 437, 437, 428, 428, 437,
+ 437, 428, 428, 428, 428, 428, 428, 428, 428, 428, 428, 437, 437, 437,
+ 437, 437, 437, 428, 428, 426, 427, 428, 428, 428, 428, 437, 437, 437,
+ 437, 437, 437, 437, 437, 437, 437, 437, 428, 437, 437, 437, 437, 428,
+ 428, 437, 428, 437, 428, 428, 437, 428, 437, 437, 437, 437, 428, 428,
+ 428, 428, 428, 437, 437, 428, 428, 428, 428, 437, 437, 437, 437, 428,
+ 437, 437, 428, 428, 437, 437, 428, 428, 428, 428, 437, 437, 437, 437,
+ 437, 437, 437, 437, 437, 437, 437, 428, 428, 437, 437, 437, 437, 437,
+ 437, 437, 437, 428, 437, 437, 437, 437, 437, 437, 437, 437, 428, 428,
+ 428, 428, 428, 437, 428, 437, 428, 428, 428, 437, 437, 437, 437, 437,
+ 428, 428, 428, 428, 437, 428, 428, 428, 437, 437, 437, 437, 437, 428,
+ 437, 428, 428, 428, 428, 428, 428, 428, 26, 26, 428, 428, 428, 428, 428,
+ 428, 64, 64, 64, 26, 26, 26, 26, 26, 30, 30, 30, 30, 30, 64, 64, 64, 64,
+ 64, 64, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447,
+ 447, 447, 64, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448,
+ 448, 448, 448, 64, 37, 41, 37, 37, 37, 41, 41, 37, 41, 37, 41, 37, 41,
+ 37, 37, 37, 37, 41, 37, 41, 41, 37, 41, 41, 41, 41, 41, 41, 44, 44, 37,
+ 37, 69, 70, 69, 70, 70, 449, 449, 449, 449, 449, 449, 69, 70, 69, 70,
+ 450, 450, 450, 69, 70, 64, 64, 64, 64, 64, 451, 451, 451, 451, 452, 451,
+ 451, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453,
+ 453, 64, 453, 64, 64, 64, 64, 64, 453, 64, 64, 454, 454, 454, 454, 454,
+ 454, 454, 454, 64, 64, 64, 64, 64, 64, 64, 455, 456, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 457, 77, 77, 77, 77, 77, 77, 77, 77,
+ 66, 66, 28, 35, 28, 35, 66, 66, 66, 28, 35, 66, 28, 35, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 410, 66, 66, 410, 66, 28, 35, 66, 66, 28, 35, 426,
+ 427, 426, 427, 426, 427, 426, 427, 66, 66, 66, 66, 66, 45, 66, 66, 410,
+ 410, 64, 64, 64, 64, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458,
+ 64, 458, 458, 458, 458, 458, 458, 458, 458, 458, 64, 64, 64, 64, 458,
+ 458, 458, 458, 458, 458, 64, 64, 459, 459, 459, 459, 459, 459, 459, 459,
+ 459, 459, 459, 459, 64, 64, 64, 64, 460, 461, 461, 461, 459, 462, 463,
+ 464, 443, 444, 443, 444, 443, 444, 443, 444, 443, 444, 459, 459, 443,
+ 444, 443, 444, 443, 444, 443, 444, 465, 466, 467, 467, 459, 464, 464,
+ 464, 464, 464, 464, 464, 464, 464, 468, 469, 470, 471, 472, 472, 465,
+ 473, 473, 473, 473, 473, 459, 459, 464, 464, 464, 462, 463, 461, 459, 26,
+ 64, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474,
+ 474, 474, 474, 474, 474, 474, 474, 474, 64, 64, 475, 475, 476, 476, 477,
+ 477, 474, 465, 478, 478, 478, 478, 478, 478, 478, 478, 478, 478, 478,
+ 478, 478, 478, 478, 478, 478, 478, 461, 473, 479, 479, 478, 64, 64, 64,
+ 64, 64, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480,
+ 480, 480, 480, 480, 64, 64, 64, 287, 287, 287, 287, 287, 287, 287, 287,
+ 287, 287, 287, 287, 287, 287, 64, 481, 481, 482, 482, 482, 482, 481, 481,
+ 481, 481, 481, 481, 481, 481, 481, 481, 480, 480, 480, 64, 64, 64, 64,
+ 64, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 484,
+ 484, 64, 482, 482, 482, 482, 482, 482, 482, 482, 482, 482, 481, 481, 481,
+ 481, 481, 481, 485, 485, 485, 485, 485, 485, 485, 485, 459, 486, 486,
+ 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 483,
+ 483, 483, 483, 484, 484, 484, 481, 481, 486, 486, 486, 486, 486, 486,
+ 486, 481, 481, 481, 481, 459, 459, 459, 459, 487, 487, 487, 487, 487,
+ 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 64, 481, 481, 481, 481,
+ 481, 481, 481, 459, 459, 459, 459, 481, 481, 481, 481, 481, 481, 481,
+ 481, 481, 481, 481, 459, 459, 488, 489, 489, 489, 489, 489, 489, 489,
+ 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 488,
+ 490, 490, 490, 490, 490, 490, 490, 490, 490, 490, 489, 489, 489, 489,
+ 488, 490, 490, 490, 491, 491, 491, 491, 491, 491, 491, 491, 491, 491,
+ 491, 491, 491, 492, 491, 491, 491, 491, 491, 491, 491, 64, 64, 64, 493,
+ 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 64,
+ 494, 494, 494, 494, 494, 494, 494, 494, 495, 495, 495, 495, 495, 495,
+ 496, 496, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497,
+ 498, 499, 499, 499, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500,
+ 497, 497, 64, 64, 64, 64, 72, 75, 72, 75, 72, 75, 501, 77, 79, 79, 79,
+ 502, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 502, 503, 64, 64, 64, 64,
+ 64, 64, 64, 77, 504, 504, 504, 504, 504, 504, 504, 504, 504, 504, 504,
+ 504, 504, 504, 505, 505, 505, 505, 505, 505, 505, 505, 505, 505, 506,
+ 506, 507, 507, 507, 507, 507, 507, 47, 47, 47, 47, 47, 47, 47, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 47, 47, 37, 41, 37, 41, 37, 41, 41, 41, 37,
+ 41, 37, 41, 37, 41, 44, 41, 41, 41, 41, 41, 41, 41, 41, 37, 41, 37, 41,
+ 37, 37, 41, 45, 508, 508, 37, 41, 37, 41, 64, 37, 41, 37, 41, 64, 64, 64,
+ 64, 37, 41, 37, 64, 64, 64, 64, 64, 44, 44, 41, 42, 42, 42, 42, 42, 509,
+ 509, 510, 509, 509, 509, 511, 509, 509, 509, 509, 510, 509, 509, 509,
+ 509, 509, 509, 509, 509, 509, 509, 509, 509, 509, 509, 509, 512, 512,
+ 510, 510, 512, 513, 513, 513, 513, 64, 64, 64, 64, 514, 514, 514, 514,
+ 514, 514, 274, 274, 247, 436, 64, 64, 64, 64, 64, 64, 515, 515, 515, 515,
+ 515, 515, 515, 515, 515, 515, 515, 515, 516, 516, 516, 516, 517, 517,
+ 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518,
+ 518, 518, 518, 518, 517, 517, 517, 517, 517, 517, 517, 517, 517, 517,
+ 517, 517, 517, 517, 517, 517, 519, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 520, 520, 521, 521, 521, 521, 521, 521, 521, 521, 521, 521, 64, 64, 64,
+ 64, 64, 64, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 169, 169,
+ 169, 169, 169, 169, 174, 174, 174, 169, 64, 64, 64, 64, 522, 522, 522,
+ 522, 522, 522, 522, 522, 522, 522, 523, 523, 523, 523, 523, 523, 523,
+ 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 524,
+ 524, 524, 524, 524, 525, 525, 525, 526, 526, 527, 527, 527, 527, 527,
+ 527, 527, 527, 527, 527, 527, 527, 527, 527, 527, 528, 528, 528, 528,
+ 528, 528, 528, 528, 528, 528, 528, 529, 530, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 531, 287, 287, 287, 287, 287, 64, 64, 64, 532, 532, 532,
+ 533, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534,
+ 534, 534, 535, 533, 533, 532, 532, 532, 532, 533, 533, 532, 533, 533,
+ 533, 536, 537, 537, 537, 537, 537, 537, 537, 537, 537, 537, 537, 537,
+ 537, 64, 538, 539, 539, 539, 539, 539, 539, 539, 539, 539, 539, 64, 64,
+ 64, 64, 537, 537, 540, 540, 540, 540, 540, 540, 540, 540, 540, 541, 541,
+ 541, 541, 541, 541, 542, 542, 541, 541, 542, 542, 541, 541, 64, 540, 540,
+ 540, 541, 540, 540, 540, 540, 540, 540, 540, 540, 541, 542, 64, 64, 543,
+ 543, 543, 543, 543, 543, 543, 543, 543, 543, 64, 64, 544, 544, 544, 544,
+ 545, 275, 275, 275, 275, 275, 275, 283, 283, 283, 275, 276, 64, 64, 64,
+ 64, 546, 546, 546, 546, 546, 546, 546, 546, 547, 546, 547, 547, 548, 546,
+ 546, 547, 547, 546, 546, 546, 546, 546, 547, 547, 546, 547, 546, 64, 64,
+ 64, 64, 64, 64, 64, 64, 546, 546, 549, 550, 550, 551, 551, 551, 551, 551,
+ 551, 551, 551, 551, 551, 551, 552, 553, 553, 552, 552, 554, 554, 551,
+ 555, 555, 552, 556, 64, 64, 289, 289, 289, 289, 289, 289, 64, 551, 551,
+ 551, 552, 552, 553, 552, 552, 553, 552, 552, 554, 552, 556, 64, 64, 557,
+ 557, 557, 557, 557, 557, 557, 557, 557, 557, 64, 64, 64, 64, 64, 64, 287,
+ 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558,
+ 558, 558, 558, 558, 287, 64, 64, 64, 64, 288, 288, 288, 288, 288, 288,
+ 288, 64, 64, 64, 64, 288, 288, 288, 288, 288, 288, 288, 288, 288, 64, 64,
+ 64, 64, 559, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 559,
+ 560, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561,
+ 561, 561, 561, 561, 561, 561, 561, 561, 561, 560, 488, 488, 488, 488,
+ 488, 488, 488, 488, 488, 488, 488, 488, 488, 488, 490, 490, 488, 488,
+ 490, 490, 490, 490, 490, 490, 41, 41, 41, 41, 41, 41, 41, 64, 64, 64, 64,
+ 83, 83, 83, 83, 83, 64, 64, 64, 64, 64, 109, 562, 109, 109, 563, 109,
+ 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 64, 109, 109,
+ 109, 109, 109, 64, 109, 64, 109, 109, 64, 109, 109, 64, 109, 109, 123,
+ 123, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564,
+ 564, 564, 564, 64, 64, 64, 64, 64, 64, 64, 64, 64, 123, 123, 123, 123,
+ 123, 123, 123, 123, 123, 123, 123, 413, 565, 64, 64, 123, 123, 123, 123,
+ 123, 123, 123, 123, 123, 123, 114, 26, 64, 64, 58, 58, 58, 58, 58, 58,
+ 58, 58, 461, 461, 461, 461, 461, 461, 461, 466, 467, 461, 64, 64, 64, 64,
+ 64, 64, 461, 465, 465, 566, 566, 466, 467, 466, 467, 466, 467, 466, 467,
+ 466, 467, 466, 467, 466, 467, 466, 467, 461, 461, 466, 467, 461, 461,
+ 461, 461, 566, 566, 566, 567, 461, 567, 64, 461, 567, 461, 461, 465, 443,
+ 444, 443, 444, 443, 444, 568, 461, 461, 569, 570, 571, 571, 572, 64, 461,
+ 573, 568, 461, 64, 64, 64, 64, 123, 123, 123, 123, 123, 64, 123, 123,
+ 123, 123, 123, 123, 123, 64, 64, 405, 64, 574, 574, 575, 576, 575, 574,
+ 574, 577, 578, 574, 579, 580, 581, 580, 580, 582, 582, 582, 582, 582,
+ 582, 582, 582, 582, 582, 580, 574, 583, 584, 583, 574, 574, 585, 585,
+ 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585,
+ 585, 585, 577, 574, 578, 586, 587, 586, 588, 588, 588, 588, 588, 588,
+ 588, 588, 588, 588, 588, 588, 588, 588, 588, 588, 588, 588, 577, 584,
+ 578, 584, 577, 578, 589, 590, 591, 589, 589, 592, 592, 592, 592, 592,
+ 592, 592, 592, 592, 592, 593, 592, 592, 592, 592, 592, 592, 592, 592,
+ 592, 592, 592, 592, 592, 593, 593, 594, 594, 594, 594, 594, 594, 594,
+ 594, 594, 594, 594, 594, 594, 594, 594, 64, 64, 64, 594, 594, 594, 594,
+ 594, 594, 64, 64, 594, 594, 594, 64, 64, 64, 576, 576, 584, 586, 595,
+ 576, 576, 64, 596, 597, 597, 597, 597, 596, 596, 64, 64, 598, 598, 598,
+ 26, 30, 64, 64, 599, 599, 599, 599, 599, 599, 599, 599, 599, 599, 599,
+ 599, 64, 599, 599, 599, 599, 599, 599, 599, 599, 599, 599, 64, 599, 599,
+ 599, 64, 599, 599, 64, 599, 599, 599, 599, 599, 599, 599, 64, 64, 599,
+ 599, 599, 64, 64, 64, 64, 64, 84, 66, 84, 64, 64, 64, 64, 514, 514, 514,
+ 514, 514, 514, 514, 514, 514, 514, 514, 514, 514, 64, 64, 64, 274, 600,
+ 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 601, 601,
+ 601, 601, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602,
+ 602, 602, 602, 602, 602, 601, 64, 64, 64, 64, 64, 274, 274, 274, 274,
+ 274, 133, 64, 64, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603,
+ 603, 603, 64, 64, 64, 604, 604, 604, 604, 604, 604, 604, 604, 604, 64,
+ 64, 64, 64, 64, 64, 64, 605, 605, 605, 605, 605, 605, 605, 605, 605, 605,
+ 605, 605, 605, 605, 605, 64, 606, 606, 606, 606, 64, 64, 64, 64, 607,
+ 607, 607, 607, 607, 607, 607, 607, 607, 608, 607, 607, 607, 607, 607,
+ 607, 607, 607, 608, 64, 64, 64, 64, 64, 609, 609, 609, 609, 609, 609,
+ 609, 609, 609, 609, 609, 609, 609, 609, 64, 610, 611, 611, 611, 611, 611,
+ 611, 611, 611, 611, 611, 611, 611, 64, 64, 64, 64, 612, 613, 613, 613,
+ 613, 613, 64, 64, 614, 614, 614, 614, 614, 614, 614, 614, 615, 615, 615,
+ 615, 615, 615, 615, 615, 616, 616, 616, 616, 616, 616, 616, 616, 617,
+ 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 64, 64,
+ 618, 618, 618, 618, 618, 618, 618, 618, 618, 618, 64, 64, 64, 64, 64, 64,
+ 619, 619, 619, 619, 619, 619, 64, 64, 619, 64, 619, 619, 619, 619, 619,
+ 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619,
+ 619, 64, 619, 619, 64, 64, 64, 619, 64, 64, 619, 620, 620, 620, 620, 620,
+ 620, 620, 620, 620, 620, 620, 620, 620, 620, 64, 621, 622, 622, 622, 622,
+ 622, 622, 622, 622, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623,
+ 623, 623, 623, 623, 624, 624, 624, 624, 624, 624, 64, 64, 64, 625, 626,
+ 626, 626, 626, 626, 626, 626, 626, 626, 626, 64, 64, 64, 64, 64, 627,
+ 628, 628, 628, 628, 628, 628, 628, 628, 629, 629, 629, 629, 629, 629,
+ 629, 629, 64, 64, 64, 64, 64, 64, 629, 629, 630, 631, 631, 631, 64, 631,
+ 631, 64, 64, 64, 64, 64, 631, 632, 631, 633, 630, 630, 630, 630, 64, 630,
+ 630, 630, 64, 630, 630, 630, 630, 630, 630, 630, 630, 630, 630, 630, 630,
+ 630, 630, 630, 630, 630, 630, 630, 64, 64, 64, 64, 633, 634, 632, 64, 64,
+ 64, 64, 635, 636, 636, 636, 636, 636, 636, 636, 636, 637, 637, 637, 637,
+ 637, 637, 637, 637, 637, 64, 64, 64, 64, 64, 64, 64, 638, 638, 638, 638,
+ 638, 638, 638, 638, 638, 638, 638, 638, 638, 639, 639, 640, 641, 641,
+ 641, 641, 641, 641, 641, 641, 641, 641, 641, 641, 641, 641, 64, 64, 64,
+ 642, 642, 642, 642, 642, 642, 642, 643, 643, 643, 643, 643, 643, 643,
+ 643, 643, 643, 643, 643, 643, 643, 64, 64, 644, 644, 644, 644, 644, 644,
+ 644, 644, 645, 645, 645, 645, 645, 645, 645, 645, 645, 645, 645, 64, 64,
+ 64, 64, 64, 646, 646, 646, 646, 646, 646, 646, 646, 647, 647, 647, 647,
+ 647, 647, 647, 647, 647, 64, 64, 64, 64, 64, 64, 64, 648, 648, 648, 648,
+ 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 64, 649, 650, 649,
+ 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 650,
+ 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 652,
+ 653, 653, 653, 653, 653, 653, 653, 64, 64, 64, 64, 654, 654, 654, 654,
+ 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654,
+ 654, 654, 655, 655, 655, 655, 655, 655, 655, 655, 655, 655, 656, 656,
+ 657, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658,
+ 657, 657, 657, 656, 656, 656, 656, 657, 657, 659, 660, 661, 661, 662,
+ 661, 661, 661, 661, 64, 64, 64, 64, 64, 64, 663, 663, 663, 663, 663, 663,
+ 663, 663, 663, 64, 64, 64, 64, 64, 64, 64, 664, 664, 664, 664, 664, 664,
+ 664, 664, 664, 664, 64, 64, 64, 64, 64, 64, 665, 665, 665, 666, 666, 666,
+ 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666,
+ 666, 666, 666, 667, 667, 667, 667, 667, 668, 667, 667, 667, 667, 667,
+ 667, 669, 669, 64, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 671,
+ 671, 671, 671, 64, 64, 64, 64, 672, 672, 673, 674, 674, 674, 674, 674,
+ 674, 674, 674, 674, 674, 674, 674, 674, 674, 674, 674, 673, 673, 673,
+ 672, 672, 672, 672, 672, 672, 672, 672, 672, 673, 675, 674, 674, 674,
+ 674, 676, 676, 676, 676, 64, 64, 64, 64, 64, 64, 64, 677, 677, 677, 677,
+ 677, 677, 677, 677, 677, 677, 64, 64, 64, 64, 64, 64, 678, 678, 678, 678,
+ 678, 678, 678, 678, 678, 678, 678, 679, 680, 679, 680, 680, 679, 679,
+ 679, 679, 679, 679, 681, 682, 683, 683, 683, 683, 683, 683, 683, 683,
+ 683, 683, 64, 64, 64, 64, 64, 64, 684, 684, 684, 684, 684, 684, 684, 684,
+ 684, 684, 684, 684, 684, 684, 684, 64, 685, 685, 685, 685, 685, 685, 685,
+ 685, 685, 685, 685, 64, 64, 64, 64, 64, 686, 686, 686, 686, 64, 64, 64,
+ 64, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687,
+ 687, 64, 504, 64, 64, 64, 64, 64, 64, 64, 688, 688, 688, 688, 688, 688,
+ 688, 688, 688, 688, 688, 688, 688, 64, 64, 64, 688, 689, 689, 689, 689,
+ 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689,
+ 689, 689, 689, 689, 64, 64, 64, 64, 64, 64, 64, 64, 690, 690, 690, 690,
+ 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 478,
+ 474, 64, 64, 64, 64, 64, 64, 274, 274, 274, 274, 274, 274, 64, 64, 274,
+ 274, 274, 274, 274, 274, 274, 64, 64, 274, 274, 274, 274, 274, 274, 274,
+ 274, 274, 274, 274, 274, 692, 692, 395, 395, 395, 274, 274, 274, 693,
+ 692, 692, 692, 692, 692, 405, 405, 405, 405, 405, 405, 405, 405, 133,
+ 133, 133, 133, 133, 133, 133, 133, 274, 274, 78, 78, 78, 78, 78, 133,
+ 133, 274, 274, 274, 274, 274, 274, 78, 78, 78, 78, 274, 274, 602, 602,
+ 694, 694, 694, 602, 64, 64, 514, 514, 64, 64, 64, 64, 64, 64, 434, 434,
+ 434, 434, 434, 434, 434, 434, 434, 434, 34, 34, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 434, 434, 434, 434, 434, 434,
+ 434, 434, 434, 434, 34, 34, 34, 34, 34, 34, 34, 64, 34, 34, 34, 34, 34,
+ 34, 434, 64, 434, 434, 64, 64, 434, 64, 64, 434, 434, 64, 64, 434, 434,
+ 434, 434, 64, 434, 434, 34, 34, 64, 34, 64, 34, 34, 34, 34, 34, 34, 34,
+ 64, 34, 34, 34, 34, 34, 34, 34, 434, 434, 64, 434, 434, 434, 434, 64, 64,
+ 434, 434, 434, 434, 434, 434, 434, 434, 64, 434, 434, 434, 434, 434, 434,
+ 434, 64, 34, 34, 434, 434, 64, 434, 434, 434, 434, 64, 434, 434, 434,
+ 434, 434, 64, 434, 64, 64, 64, 434, 434, 434, 434, 434, 434, 434, 64, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 64, 64, 434, 695, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 437, 34, 34, 34, 34, 34, 34, 434, 434, 434, 434,
+ 434, 434, 434, 434, 434, 695, 34, 34, 34, 34, 34, 34, 34, 34, 34, 437,
+ 34, 34, 434, 434, 434, 434, 434, 695, 34, 34, 34, 34, 34, 34, 34, 34, 34,
+ 437, 34, 34, 34, 34, 34, 34, 434, 434, 434, 434, 434, 434, 434, 434, 434,
+ 695, 34, 437, 34, 34, 34, 34, 34, 34, 34, 34, 434, 34, 64, 64, 696, 696,
+ 696, 696, 696, 696, 696, 696, 696, 696, 123, 123, 123, 123, 64, 123, 123,
+ 123, 64, 123, 123, 64, 123, 64, 64, 123, 64, 123, 123, 123, 123, 123,
+ 123, 123, 123, 123, 123, 64, 123, 123, 123, 123, 64, 123, 64, 123, 64,
+ 64, 64, 64, 64, 64, 123, 64, 64, 64, 64, 123, 64, 123, 64, 123, 64, 123,
+ 123, 123, 64, 123, 64, 123, 64, 123, 64, 123, 64, 123, 123, 123, 123, 64,
+ 123, 64, 123, 123, 64, 123, 123, 123, 123, 123, 123, 123, 123, 123, 64,
+ 64, 64, 64, 64, 123, 123, 123, 64, 123, 123, 123, 111, 111, 64, 64, 64,
+ 64, 64, 64, 33, 33, 33, 64, 64, 64, 64, 64, 445, 445, 445, 445, 445, 445,
+ 274, 64, 445, 445, 26, 26, 64, 64, 64, 64, 445, 445, 445, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 274, 274, 697, 481, 481, 64, 64, 64, 64, 64,
+ 481, 481, 481, 64, 64, 64, 64, 64, 481, 64, 64, 64, 64, 64, 64, 64, 481,
+ 481, 64, 64, 64, 64, 64, 64, 26, 64, 64, 64, 64, 64, 64, 64, 26, 26, 26,
+ 26, 26, 26, 64, 26, 26, 26, 26, 26, 26, 64, 64, 64, 26, 26, 26, 26, 26,
+ 64, 26, 26, 26, 64, 26, 26, 26, 26, 26, 26, 64, 26, 26, 26, 26, 64, 64,
+ 64, 26, 26, 26, 26, 26, 26, 64, 64, 64, 64, 64, 26, 26, 26, 26, 26, 26,
+ 64, 64, 64, 64, 26, 26, 26, 489, 489, 489, 489, 489, 489, 488, 490, 490,
+ 490, 490, 490, 490, 490, 64, 64, 64, 405, 64, 64, 64, 64, 64, 64, 405,
+ 405, 405, 405, 405, 405, 405, 405, 561, 561, 561, 561, 561, 560, 64, 64,
+};
+
+/* decomposition data */
+static const unsigned short decomp_data[] = {
+ 0, 257, 32, 514, 32, 776, 259, 97, 514, 32, 772, 259, 50, 259, 51, 514,
+ 32, 769, 258, 956, 514, 32, 807, 259, 49, 259, 111, 772, 49, 8260, 52,
+ 772, 49, 8260, 50, 772, 51, 8260, 52, 512, 65, 768, 512, 65, 769, 512,
+ 65, 770, 512, 65, 771, 512, 65, 776, 512, 65, 778, 512, 67, 807, 512, 69,
+ 768, 512, 69, 769, 512, 69, 770, 512, 69, 776, 512, 73, 768, 512, 73,
+ 769, 512, 73, 770, 512, 73, 776, 512, 78, 771, 512, 79, 768, 512, 79,
+ 769, 512, 79, 770, 512, 79, 771, 512, 79, 776, 512, 85, 768, 512, 85,
+ 769, 512, 85, 770, 512, 85, 776, 512, 89, 769, 512, 97, 768, 512, 97,
+ 769, 512, 97, 770, 512, 97, 771, 512, 97, 776, 512, 97, 778, 512, 99,
+ 807, 512, 101, 768, 512, 101, 769, 512, 101, 770, 512, 101, 776, 512,
+ 105, 768, 512, 105, 769, 512, 105, 770, 512, 105, 776, 512, 110, 771,
+ 512, 111, 768, 512, 111, 769, 512, 111, 770, 512, 111, 771, 512, 111,
+ 776, 512, 117, 768, 512, 117, 769, 512, 117, 770, 512, 117, 776, 512,
+ 121, 769, 512, 121, 776, 512, 65, 772, 512, 97, 772, 512, 65, 774, 512,
+ 97, 774, 512, 65, 808, 512, 97, 808, 512, 67, 769, 512, 99, 769, 512, 67,
+ 770, 512, 99, 770, 512, 67, 775, 512, 99, 775, 512, 67, 780, 512, 99,
+ 780, 512, 68, 780, 512, 100, 780, 512, 69, 772, 512, 101, 772, 512, 69,
+ 774, 512, 101, 774, 512, 69, 775, 512, 101, 775, 512, 69, 808, 512, 101,
+ 808, 512, 69, 780, 512, 101, 780, 512, 71, 770, 512, 103, 770, 512, 71,
+ 774, 512, 103, 774, 512, 71, 775, 512, 103, 775, 512, 71, 807, 512, 103,
+ 807, 512, 72, 770, 512, 104, 770, 512, 73, 771, 512, 105, 771, 512, 73,
+ 772, 512, 105, 772, 512, 73, 774, 512, 105, 774, 512, 73, 808, 512, 105,
+ 808, 512, 73, 775, 514, 73, 74, 514, 105, 106, 512, 74, 770, 512, 106,
+ 770, 512, 75, 807, 512, 107, 807, 512, 76, 769, 512, 108, 769, 512, 76,
+ 807, 512, 108, 807, 512, 76, 780, 512, 108, 780, 514, 76, 183, 514, 108,
+ 183, 512, 78, 769, 512, 110, 769, 512, 78, 807, 512, 110, 807, 512, 78,
+ 780, 512, 110, 780, 514, 700, 110, 512, 79, 772, 512, 111, 772, 512, 79,
+ 774, 512, 111, 774, 512, 79, 779, 512, 111, 779, 512, 82, 769, 512, 114,
+ 769, 512, 82, 807, 512, 114, 807, 512, 82, 780, 512, 114, 780, 512, 83,
+ 769, 512, 115, 769, 512, 83, 770, 512, 115, 770, 512, 83, 807, 512, 115,
+ 807, 512, 83, 780, 512, 115, 780, 512, 84, 807, 512, 116, 807, 512, 84,
+ 780, 512, 116, 780, 512, 85, 771, 512, 117, 771, 512, 85, 772, 512, 117,
+ 772, 512, 85, 774, 512, 117, 774, 512, 85, 778, 512, 117, 778, 512, 85,
+ 779, 512, 117, 779, 512, 85, 808, 512, 117, 808, 512, 87, 770, 512, 119,
+ 770, 512, 89, 770, 512, 121, 770, 512, 89, 776, 512, 90, 769, 512, 122,
+ 769, 512, 90, 775, 512, 122, 775, 512, 90, 780, 512, 122, 780, 258, 115,
+ 512, 79, 795, 512, 111, 795, 512, 85, 795, 512, 117, 795, 514, 68, 381,
+ 514, 68, 382, 514, 100, 382, 514, 76, 74, 514, 76, 106, 514, 108, 106,
+ 514, 78, 74, 514, 78, 106, 514, 110, 106, 512, 65, 780, 512, 97, 780,
+ 512, 73, 780, 512, 105, 780, 512, 79, 780, 512, 111, 780, 512, 85, 780,
+ 512, 117, 780, 512, 220, 772, 512, 252, 772, 512, 220, 769, 512, 252,
+ 769, 512, 220, 780, 512, 252, 780, 512, 220, 768, 512, 252, 768, 512,
+ 196, 772, 512, 228, 772, 512, 550, 772, 512, 551, 772, 512, 198, 772,
+ 512, 230, 772, 512, 71, 780, 512, 103, 780, 512, 75, 780, 512, 107, 780,
+ 512, 79, 808, 512, 111, 808, 512, 490, 772, 512, 491, 772, 512, 439, 780,
+ 512, 658, 780, 512, 106, 780, 514, 68, 90, 514, 68, 122, 514, 100, 122,
+ 512, 71, 769, 512, 103, 769, 512, 78, 768, 512, 110, 768, 512, 197, 769,
+ 512, 229, 769, 512, 198, 769, 512, 230, 769, 512, 216, 769, 512, 248,
+ 769, 512, 65, 783, 512, 97, 783, 512, 65, 785, 512, 97, 785, 512, 69,
+ 783, 512, 101, 783, 512, 69, 785, 512, 101, 785, 512, 73, 783, 512, 105,
+ 783, 512, 73, 785, 512, 105, 785, 512, 79, 783, 512, 111, 783, 512, 79,
+ 785, 512, 111, 785, 512, 82, 783, 512, 114, 783, 512, 82, 785, 512, 114,
+ 785, 512, 85, 783, 512, 117, 783, 512, 85, 785, 512, 117, 785, 512, 83,
+ 806, 512, 115, 806, 512, 84, 806, 512, 116, 806, 512, 72, 780, 512, 104,
+ 780, 512, 65, 775, 512, 97, 775, 512, 69, 807, 512, 101, 807, 512, 214,
+ 772, 512, 246, 772, 512, 213, 772, 512, 245, 772, 512, 79, 775, 512, 111,
+ 775, 512, 558, 772, 512, 559, 772, 512, 89, 772, 512, 121, 772, 259, 104,
+ 259, 614, 259, 106, 259, 114, 259, 633, 259, 635, 259, 641, 259, 119,
+ 259, 121, 514, 32, 774, 514, 32, 775, 514, 32, 778, 514, 32, 808, 514,
+ 32, 771, 514, 32, 779, 259, 611, 259, 108, 259, 115, 259, 120, 259, 661,
+ 256, 768, 256, 769, 256, 787, 512, 776, 769, 256, 697, 514, 32, 837, 256,
+ 59, 514, 32, 769, 512, 168, 769, 512, 913, 769, 256, 183, 512, 917, 769,
+ 512, 919, 769, 512, 921, 769, 512, 927, 769, 512, 933, 769, 512, 937,
+ 769, 512, 970, 769, 512, 921, 776, 512, 933, 776, 512, 945, 769, 512,
+ 949, 769, 512, 951, 769, 512, 953, 769, 512, 971, 769, 512, 953, 776,
+ 512, 965, 776, 512, 959, 769, 512, 965, 769, 512, 969, 769, 258, 946,
+ 258, 952, 258, 933, 512, 978, 769, 512, 978, 776, 258, 966, 258, 960,
+ 258, 954, 258, 961, 258, 962, 258, 920, 258, 949, 258, 931, 512, 1045,
+ 768, 512, 1045, 776, 512, 1043, 769, 512, 1030, 776, 512, 1050, 769, 512,
+ 1048, 768, 512, 1059, 774, 512, 1048, 774, 512, 1080, 774, 512, 1077,
+ 768, 512, 1077, 776, 512, 1075, 769, 512, 1110, 776, 512, 1082, 769, 512,
+ 1080, 768, 512, 1091, 774, 512, 1140, 783, 512, 1141, 783, 512, 1046,
+ 774, 512, 1078, 774, 512, 1040, 774, 512, 1072, 774, 512, 1040, 776, 512,
+ 1072, 776, 512, 1045, 774, 512, 1077, 774, 512, 1240, 776, 512, 1241,
+ 776, 512, 1046, 776, 512, 1078, 776, 512, 1047, 776, 512, 1079, 776, 512,
+ 1048, 772, 512, 1080, 772, 512, 1048, 776, 512, 1080, 776, 512, 1054,
+ 776, 512, 1086, 776, 512, 1256, 776, 512, 1257, 776, 512, 1069, 776, 512,
+ 1101, 776, 512, 1059, 772, 512, 1091, 772, 512, 1059, 776, 512, 1091,
+ 776, 512, 1059, 779, 512, 1091, 779, 512, 1063, 776, 512, 1095, 776, 512,
+ 1067, 776, 512, 1099, 776, 514, 1381, 1410, 512, 1575, 1619, 512, 1575,
+ 1620, 512, 1608, 1620, 512, 1575, 1621, 512, 1610, 1620, 514, 1575, 1652,
+ 514, 1608, 1652, 514, 1735, 1652, 514, 1610, 1652, 512, 1749, 1620, 512,
+ 1729, 1620, 512, 1746, 1620, 512, 2344, 2364, 512, 2352, 2364, 512, 2355,
+ 2364, 512, 2325, 2364, 512, 2326, 2364, 512, 2327, 2364, 512, 2332, 2364,
+ 512, 2337, 2364, 512, 2338, 2364, 512, 2347, 2364, 512, 2351, 2364, 512,
+ 2503, 2494, 512, 2503, 2519, 512, 2465, 2492, 512, 2466, 2492, 512, 2479,
+ 2492, 512, 2610, 2620, 512, 2616, 2620, 512, 2582, 2620, 512, 2583, 2620,
+ 512, 2588, 2620, 512, 2603, 2620, 512, 2887, 2902, 512, 2887, 2878, 512,
+ 2887, 2903, 512, 2849, 2876, 512, 2850, 2876, 512, 2962, 3031, 512, 3014,
+ 3006, 512, 3015, 3006, 512, 3014, 3031, 512, 3142, 3158, 512, 3263, 3285,
+ 512, 3270, 3285, 512, 3270, 3286, 512, 3270, 3266, 512, 3274, 3285, 512,
+ 3398, 3390, 512, 3399, 3390, 512, 3398, 3415, 512, 3545, 3530, 512, 3545,
+ 3535, 512, 3548, 3530, 512, 3545, 3551, 514, 3661, 3634, 514, 3789, 3762,
+ 514, 3755, 3737, 514, 3755, 3745, 257, 3851, 512, 3906, 4023, 512, 3916,
+ 4023, 512, 3921, 4023, 512, 3926, 4023, 512, 3931, 4023, 512, 3904, 4021,
+ 512, 3953, 3954, 512, 3953, 3956, 512, 4018, 3968, 514, 4018, 3969, 512,
+ 4019, 3968, 514, 4019, 3969, 512, 3953, 3968, 512, 3986, 4023, 512, 3996,
+ 4023, 512, 4001, 4023, 512, 4006, 4023, 512, 4011, 4023, 512, 3984, 4021,
+ 512, 4133, 4142, 259, 4316, 512, 6917, 6965, 512, 6919, 6965, 512, 6921,
+ 6965, 512, 6923, 6965, 512, 6925, 6965, 512, 6929, 6965, 512, 6970, 6965,
+ 512, 6972, 6965, 512, 6974, 6965, 512, 6975, 6965, 512, 6978, 6965, 259,
+ 65, 259, 198, 259, 66, 259, 68, 259, 69, 259, 398, 259, 71, 259, 72, 259,
+ 73, 259, 74, 259, 75, 259, 76, 259, 77, 259, 78, 259, 79, 259, 546, 259,
+ 80, 259, 82, 259, 84, 259, 85, 259, 87, 259, 97, 259, 592, 259, 593, 259,
+ 7426, 259, 98, 259, 100, 259, 101, 259, 601, 259, 603, 259, 604, 259,
+ 103, 259, 107, 259, 109, 259, 331, 259, 111, 259, 596, 259, 7446, 259,
+ 7447, 259, 112, 259, 116, 259, 117, 259, 7453, 259, 623, 259, 118, 259,
+ 7461, 259, 946, 259, 947, 259, 948, 259, 966, 259, 967, 261, 105, 261,
+ 114, 261, 117, 261, 118, 261, 946, 261, 947, 261, 961, 261, 966, 261,
+ 967, 259, 1085, 259, 594, 259, 99, 259, 597, 259, 240, 259, 604, 259,
+ 102, 259, 607, 259, 609, 259, 613, 259, 616, 259, 617, 259, 618, 259,
+ 7547, 259, 669, 259, 621, 259, 7557, 259, 671, 259, 625, 259, 624, 259,
+ 626, 259, 627, 259, 628, 259, 629, 259, 632, 259, 642, 259, 643, 259,
+ 427, 259, 649, 259, 650, 259, 7452, 259, 651, 259, 652, 259, 122, 259,
+ 656, 259, 657, 259, 658, 259, 952, 512, 65, 805, 512, 97, 805, 512, 66,
+ 775, 512, 98, 775, 512, 66, 803, 512, 98, 803, 512, 66, 817, 512, 98,
+ 817, 512, 199, 769, 512, 231, 769, 512, 68, 775, 512, 100, 775, 512, 68,
+ 803, 512, 100, 803, 512, 68, 817, 512, 100, 817, 512, 68, 807, 512, 100,
+ 807, 512, 68, 813, 512, 100, 813, 512, 274, 768, 512, 275, 768, 512, 274,
+ 769, 512, 275, 769, 512, 69, 813, 512, 101, 813, 512, 69, 816, 512, 101,
+ 816, 512, 552, 774, 512, 553, 774, 512, 70, 775, 512, 102, 775, 512, 71,
+ 772, 512, 103, 772, 512, 72, 775, 512, 104, 775, 512, 72, 803, 512, 104,
+ 803, 512, 72, 776, 512, 104, 776, 512, 72, 807, 512, 104, 807, 512, 72,
+ 814, 512, 104, 814, 512, 73, 816, 512, 105, 816, 512, 207, 769, 512, 239,
+ 769, 512, 75, 769, 512, 107, 769, 512, 75, 803, 512, 107, 803, 512, 75,
+ 817, 512, 107, 817, 512, 76, 803, 512, 108, 803, 512, 7734, 772, 512,
+ 7735, 772, 512, 76, 817, 512, 108, 817, 512, 76, 813, 512, 108, 813, 512,
+ 77, 769, 512, 109, 769, 512, 77, 775, 512, 109, 775, 512, 77, 803, 512,
+ 109, 803, 512, 78, 775, 512, 110, 775, 512, 78, 803, 512, 110, 803, 512,
+ 78, 817, 512, 110, 817, 512, 78, 813, 512, 110, 813, 512, 213, 769, 512,
+ 245, 769, 512, 213, 776, 512, 245, 776, 512, 332, 768, 512, 333, 768,
+ 512, 332, 769, 512, 333, 769, 512, 80, 769, 512, 112, 769, 512, 80, 775,
+ 512, 112, 775, 512, 82, 775, 512, 114, 775, 512, 82, 803, 512, 114, 803,
+ 512, 7770, 772, 512, 7771, 772, 512, 82, 817, 512, 114, 817, 512, 83,
+ 775, 512, 115, 775, 512, 83, 803, 512, 115, 803, 512, 346, 775, 512, 347,
+ 775, 512, 352, 775, 512, 353, 775, 512, 7778, 775, 512, 7779, 775, 512,
+ 84, 775, 512, 116, 775, 512, 84, 803, 512, 116, 803, 512, 84, 817, 512,
+ 116, 817, 512, 84, 813, 512, 116, 813, 512, 85, 804, 512, 117, 804, 512,
+ 85, 816, 512, 117, 816, 512, 85, 813, 512, 117, 813, 512, 360, 769, 512,
+ 361, 769, 512, 362, 776, 512, 363, 776, 512, 86, 771, 512, 118, 771, 512,
+ 86, 803, 512, 118, 803, 512, 87, 768, 512, 119, 768, 512, 87, 769, 512,
+ 119, 769, 512, 87, 776, 512, 119, 776, 512, 87, 775, 512, 119, 775, 512,
+ 87, 803, 512, 119, 803, 512, 88, 775, 512, 120, 775, 512, 88, 776, 512,
+ 120, 776, 512, 89, 775, 512, 121, 775, 512, 90, 770, 512, 122, 770, 512,
+ 90, 803, 512, 122, 803, 512, 90, 817, 512, 122, 817, 512, 104, 817, 512,
+ 116, 776, 512, 119, 778, 512, 121, 778, 514, 97, 702, 512, 383, 775, 512,
+ 65, 803, 512, 97, 803, 512, 65, 777, 512, 97, 777, 512, 194, 769, 512,
+ 226, 769, 512, 194, 768, 512, 226, 768, 512, 194, 777, 512, 226, 777,
+ 512, 194, 771, 512, 226, 771, 512, 7840, 770, 512, 7841, 770, 512, 258,
+ 769, 512, 259, 769, 512, 258, 768, 512, 259, 768, 512, 258, 777, 512,
+ 259, 777, 512, 258, 771, 512, 259, 771, 512, 7840, 774, 512, 7841, 774,
+ 512, 69, 803, 512, 101, 803, 512, 69, 777, 512, 101, 777, 512, 69, 771,
+ 512, 101, 771, 512, 202, 769, 512, 234, 769, 512, 202, 768, 512, 234,
+ 768, 512, 202, 777, 512, 234, 777, 512, 202, 771, 512, 234, 771, 512,
+ 7864, 770, 512, 7865, 770, 512, 73, 777, 512, 105, 777, 512, 73, 803,
+ 512, 105, 803, 512, 79, 803, 512, 111, 803, 512, 79, 777, 512, 111, 777,
+ 512, 212, 769, 512, 244, 769, 512, 212, 768, 512, 244, 768, 512, 212,
+ 777, 512, 244, 777, 512, 212, 771, 512, 244, 771, 512, 7884, 770, 512,
+ 7885, 770, 512, 416, 769, 512, 417, 769, 512, 416, 768, 512, 417, 768,
+ 512, 416, 777, 512, 417, 777, 512, 416, 771, 512, 417, 771, 512, 416,
+ 803, 512, 417, 803, 512, 85, 803, 512, 117, 803, 512, 85, 777, 512, 117,
+ 777, 512, 431, 769, 512, 432, 769, 512, 431, 768, 512, 432, 768, 512,
+ 431, 777, 512, 432, 777, 512, 431, 771, 512, 432, 771, 512, 431, 803,
+ 512, 432, 803, 512, 89, 768, 512, 121, 768, 512, 89, 803, 512, 121, 803,
+ 512, 89, 777, 512, 121, 777, 512, 89, 771, 512, 121, 771, 512, 945, 787,
+ 512, 945, 788, 512, 7936, 768, 512, 7937, 768, 512, 7936, 769, 512, 7937,
+ 769, 512, 7936, 834, 512, 7937, 834, 512, 913, 787, 512, 913, 788, 512,
+ 7944, 768, 512, 7945, 768, 512, 7944, 769, 512, 7945, 769, 512, 7944,
+ 834, 512, 7945, 834, 512, 949, 787, 512, 949, 788, 512, 7952, 768, 512,
+ 7953, 768, 512, 7952, 769, 512, 7953, 769, 512, 917, 787, 512, 917, 788,
+ 512, 7960, 768, 512, 7961, 768, 512, 7960, 769, 512, 7961, 769, 512, 951,
+ 787, 512, 951, 788, 512, 7968, 768, 512, 7969, 768, 512, 7968, 769, 512,
+ 7969, 769, 512, 7968, 834, 512, 7969, 834, 512, 919, 787, 512, 919, 788,
+ 512, 7976, 768, 512, 7977, 768, 512, 7976, 769, 512, 7977, 769, 512,
+ 7976, 834, 512, 7977, 834, 512, 953, 787, 512, 953, 788, 512, 7984, 768,
+ 512, 7985, 768, 512, 7984, 769, 512, 7985, 769, 512, 7984, 834, 512,
+ 7985, 834, 512, 921, 787, 512, 921, 788, 512, 7992, 768, 512, 7993, 768,
+ 512, 7992, 769, 512, 7993, 769, 512, 7992, 834, 512, 7993, 834, 512, 959,
+ 787, 512, 959, 788, 512, 8000, 768, 512, 8001, 768, 512, 8000, 769, 512,
+ 8001, 769, 512, 927, 787, 512, 927, 788, 512, 8008, 768, 512, 8009, 768,
+ 512, 8008, 769, 512, 8009, 769, 512, 965, 787, 512, 965, 788, 512, 8016,
+ 768, 512, 8017, 768, 512, 8016, 769, 512, 8017, 769, 512, 8016, 834, 512,
+ 8017, 834, 512, 933, 788, 512, 8025, 768, 512, 8025, 769, 512, 8025, 834,
+ 512, 969, 787, 512, 969, 788, 512, 8032, 768, 512, 8033, 768, 512, 8032,
+ 769, 512, 8033, 769, 512, 8032, 834, 512, 8033, 834, 512, 937, 787, 512,
+ 937, 788, 512, 8040, 768, 512, 8041, 768, 512, 8040, 769, 512, 8041, 769,
+ 512, 8040, 834, 512, 8041, 834, 512, 945, 768, 256, 940, 512, 949, 768,
+ 256, 941, 512, 951, 768, 256, 942, 512, 953, 768, 256, 943, 512, 959,
+ 768, 256, 972, 512, 965, 768, 256, 973, 512, 969, 768, 256, 974, 512,
+ 7936, 837, 512, 7937, 837, 512, 7938, 837, 512, 7939, 837, 512, 7940,
+ 837, 512, 7941, 837, 512, 7942, 837, 512, 7943, 837, 512, 7944, 837, 512,
+ 7945, 837, 512, 7946, 837, 512, 7947, 837, 512, 7948, 837, 512, 7949,
+ 837, 512, 7950, 837, 512, 7951, 837, 512, 7968, 837, 512, 7969, 837, 512,
+ 7970, 837, 512, 7971, 837, 512, 7972, 837, 512, 7973, 837, 512, 7974,
+ 837, 512, 7975, 837, 512, 7976, 837, 512, 7977, 837, 512, 7978, 837, 512,
+ 7979, 837, 512, 7980, 837, 512, 7981, 837, 512, 7982, 837, 512, 7983,
+ 837, 512, 8032, 837, 512, 8033, 837, 512, 8034, 837, 512, 8035, 837, 512,
+ 8036, 837, 512, 8037, 837, 512, 8038, 837, 512, 8039, 837, 512, 8040,
+ 837, 512, 8041, 837, 512, 8042, 837, 512, 8043, 837, 512, 8044, 837, 512,
+ 8045, 837, 512, 8046, 837, 512, 8047, 837, 512, 945, 774, 512, 945, 772,
+ 512, 8048, 837, 512, 945, 837, 512, 940, 837, 512, 945, 834, 512, 8118,
+ 837, 512, 913, 774, 512, 913, 772, 512, 913, 768, 256, 902, 512, 913,
+ 837, 514, 32, 787, 256, 953, 514, 32, 787, 514, 32, 834, 512, 168, 834,
+ 512, 8052, 837, 512, 951, 837, 512, 942, 837, 512, 951, 834, 512, 8134,
+ 837, 512, 917, 768, 256, 904, 512, 919, 768, 256, 905, 512, 919, 837,
+ 512, 8127, 768, 512, 8127, 769, 512, 8127, 834, 512, 953, 774, 512, 953,
+ 772, 512, 970, 768, 256, 912, 512, 953, 834, 512, 970, 834, 512, 921,
+ 774, 512, 921, 772, 512, 921, 768, 256, 906, 512, 8190, 768, 512, 8190,
+ 769, 512, 8190, 834, 512, 965, 774, 512, 965, 772, 512, 971, 768, 256,
+ 944, 512, 961, 787, 512, 961, 788, 512, 965, 834, 512, 971, 834, 512,
+ 933, 774, 512, 933, 772, 512, 933, 768, 256, 910, 512, 929, 788, 512,
+ 168, 768, 256, 901, 256, 96, 512, 8060, 837, 512, 969, 837, 512, 974,
+ 837, 512, 969, 834, 512, 8182, 837, 512, 927, 768, 256, 908, 512, 937,
+ 768, 256, 911, 512, 937, 837, 256, 180, 514, 32, 788, 256, 8194, 256,
+ 8195, 258, 32, 258, 32, 258, 32, 258, 32, 258, 32, 257, 32, 258, 32, 258,
+ 32, 258, 32, 257, 8208, 514, 32, 819, 258, 46, 514, 46, 46, 770, 46, 46,
+ 46, 257, 32, 514, 8242, 8242, 770, 8242, 8242, 8242, 514, 8245, 8245,
+ 770, 8245, 8245, 8245, 514, 33, 33, 514, 32, 773, 514, 63, 63, 514, 63,
+ 33, 514, 33, 63, 1026, 8242, 8242, 8242, 8242, 258, 32, 259, 48, 259,
+ 105, 259, 52, 259, 53, 259, 54, 259, 55, 259, 56, 259, 57, 259, 43, 259,
+ 8722, 259, 61, 259, 40, 259, 41, 259, 110, 261, 48, 261, 49, 261, 50,
+ 261, 51, 261, 52, 261, 53, 261, 54, 261, 55, 261, 56, 261, 57, 261, 43,
+ 261, 8722, 261, 61, 261, 40, 261, 41, 261, 97, 261, 101, 261, 111, 261,
+ 120, 261, 601, 261, 104, 261, 107, 261, 108, 261, 109, 261, 110, 261,
+ 112, 261, 115, 261, 116, 514, 82, 115, 770, 97, 47, 99, 770, 97, 47, 115,
+ 262, 67, 514, 176, 67, 770, 99, 47, 111, 770, 99, 47, 117, 258, 400, 514,
+ 176, 70, 262, 103, 262, 72, 262, 72, 262, 72, 262, 104, 262, 295, 262,
+ 73, 262, 73, 262, 76, 262, 108, 262, 78, 514, 78, 111, 262, 80, 262, 81,
+ 262, 82, 262, 82, 262, 82, 515, 83, 77, 770, 84, 69, 76, 515, 84, 77,
+ 262, 90, 256, 937, 262, 90, 256, 75, 256, 197, 262, 66, 262, 67, 262,
+ 101, 262, 69, 262, 70, 262, 77, 262, 111, 258, 1488, 258, 1489, 258,
+ 1490, 258, 1491, 262, 105, 770, 70, 65, 88, 262, 960, 262, 947, 262, 915,
+ 262, 928, 262, 8721, 262, 68, 262, 100, 262, 101, 262, 105, 262, 106,
+ 772, 49, 8260, 55, 772, 49, 8260, 57, 1028, 49, 8260, 49, 48, 772, 49,
+ 8260, 51, 772, 50, 8260, 51, 772, 49, 8260, 53, 772, 50, 8260, 53, 772,
+ 51, 8260, 53, 772, 52, 8260, 53, 772, 49, 8260, 54, 772, 53, 8260, 54,
+ 772, 49, 8260, 56, 772, 51, 8260, 56, 772, 53, 8260, 56, 772, 55, 8260,
+ 56, 516, 49, 8260, 258, 73, 514, 73, 73, 770, 73, 73, 73, 514, 73, 86,
+ 258, 86, 514, 86, 73, 770, 86, 73, 73, 1026, 86, 73, 73, 73, 514, 73, 88,
+ 258, 88, 514, 88, 73, 770, 88, 73, 73, 258, 76, 258, 67, 258, 68, 258,
+ 77, 258, 105, 514, 105, 105, 770, 105, 105, 105, 514, 105, 118, 258, 118,
+ 514, 118, 105, 770, 118, 105, 105, 1026, 118, 105, 105, 105, 514, 105,
+ 120, 258, 120, 514, 120, 105, 770, 120, 105, 105, 258, 108, 258, 99, 258,
+ 100, 258, 109, 772, 48, 8260, 51, 512, 8592, 824, 512, 8594, 824, 512,
+ 8596, 824, 512, 8656, 824, 512, 8660, 824, 512, 8658, 824, 512, 8707,
+ 824, 512, 8712, 824, 512, 8715, 824, 512, 8739, 824, 512, 8741, 824, 514,
+ 8747, 8747, 770, 8747, 8747, 8747, 514, 8750, 8750, 770, 8750, 8750,
+ 8750, 512, 8764, 824, 512, 8771, 824, 512, 8773, 824, 512, 8776, 824,
+ 512, 61, 824, 512, 8801, 824, 512, 8781, 824, 512, 60, 824, 512, 62, 824,
+ 512, 8804, 824, 512, 8805, 824, 512, 8818, 824, 512, 8819, 824, 512,
+ 8822, 824, 512, 8823, 824, 512, 8826, 824, 512, 8827, 824, 512, 8834,
+ 824, 512, 8835, 824, 512, 8838, 824, 512, 8839, 824, 512, 8866, 824, 512,
+ 8872, 824, 512, 8873, 824, 512, 8875, 824, 512, 8828, 824, 512, 8829,
+ 824, 512, 8849, 824, 512, 8850, 824, 512, 8882, 824, 512, 8883, 824, 512,
+ 8884, 824, 512, 8885, 824, 256, 12296, 256, 12297, 263, 49, 263, 50, 263,
+ 51, 263, 52, 263, 53, 263, 54, 263, 55, 263, 56, 263, 57, 519, 49, 48,
+ 519, 49, 49, 519, 49, 50, 519, 49, 51, 519, 49, 52, 519, 49, 53, 519, 49,
+ 54, 519, 49, 55, 519, 49, 56, 519, 49, 57, 519, 50, 48, 770, 40, 49, 41,
+ 770, 40, 50, 41, 770, 40, 51, 41, 770, 40, 52, 41, 770, 40, 53, 41, 770,
+ 40, 54, 41, 770, 40, 55, 41, 770, 40, 56, 41, 770, 40, 57, 41, 1026, 40,
+ 49, 48, 41, 1026, 40, 49, 49, 41, 1026, 40, 49, 50, 41, 1026, 40, 49, 51,
+ 41, 1026, 40, 49, 52, 41, 1026, 40, 49, 53, 41, 1026, 40, 49, 54, 41,
+ 1026, 40, 49, 55, 41, 1026, 40, 49, 56, 41, 1026, 40, 49, 57, 41, 1026,
+ 40, 50, 48, 41, 514, 49, 46, 514, 50, 46, 514, 51, 46, 514, 52, 46, 514,
+ 53, 46, 514, 54, 46, 514, 55, 46, 514, 56, 46, 514, 57, 46, 770, 49, 48,
+ 46, 770, 49, 49, 46, 770, 49, 50, 46, 770, 49, 51, 46, 770, 49, 52, 46,
+ 770, 49, 53, 46, 770, 49, 54, 46, 770, 49, 55, 46, 770, 49, 56, 46, 770,
+ 49, 57, 46, 770, 50, 48, 46, 770, 40, 97, 41, 770, 40, 98, 41, 770, 40,
+ 99, 41, 770, 40, 100, 41, 770, 40, 101, 41, 770, 40, 102, 41, 770, 40,
+ 103, 41, 770, 40, 104, 41, 770, 40, 105, 41, 770, 40, 106, 41, 770, 40,
+ 107, 41, 770, 40, 108, 41, 770, 40, 109, 41, 770, 40, 110, 41, 770, 40,
+ 111, 41, 770, 40, 112, 41, 770, 40, 113, 41, 770, 40, 114, 41, 770, 40,
+ 115, 41, 770, 40, 116, 41, 770, 40, 117, 41, 770, 40, 118, 41, 770, 40,
+ 119, 41, 770, 40, 120, 41, 770, 40, 121, 41, 770, 40, 122, 41, 263, 65,
+ 263, 66, 263, 67, 263, 68, 263, 69, 263, 70, 263, 71, 263, 72, 263, 73,
+ 263, 74, 263, 75, 263, 76, 263, 77, 263, 78, 263, 79, 263, 80, 263, 81,
+ 263, 82, 263, 83, 263, 84, 263, 85, 263, 86, 263, 87, 263, 88, 263, 89,
+ 263, 90, 263, 97, 263, 98, 263, 99, 263, 100, 263, 101, 263, 102, 263,
+ 103, 263, 104, 263, 105, 263, 106, 263, 107, 263, 108, 263, 109, 263,
+ 110, 263, 111, 263, 112, 263, 113, 263, 114, 263, 115, 263, 116, 263,
+ 117, 263, 118, 263, 119, 263, 120, 263, 121, 263, 122, 263, 48, 1026,
+ 8747, 8747, 8747, 8747, 770, 58, 58, 61, 514, 61, 61, 770, 61, 61, 61,
+ 512, 10973, 824, 261, 106, 259, 86, 259, 11617, 258, 27597, 258, 40863,
+ 258, 19968, 258, 20008, 258, 20022, 258, 20031, 258, 20057, 258, 20101,
+ 258, 20108, 258, 20128, 258, 20154, 258, 20799, 258, 20837, 258, 20843,
+ 258, 20866, 258, 20886, 258, 20907, 258, 20960, 258, 20981, 258, 20992,
+ 258, 21147, 258, 21241, 258, 21269, 258, 21274, 258, 21304, 258, 21313,
+ 258, 21340, 258, 21353, 258, 21378, 258, 21430, 258, 21448, 258, 21475,
+ 258, 22231, 258, 22303, 258, 22763, 258, 22786, 258, 22794, 258, 22805,
+ 258, 22823, 258, 22899, 258, 23376, 258, 23424, 258, 23544, 258, 23567,
+ 258, 23586, 258, 23608, 258, 23662, 258, 23665, 258, 24027, 258, 24037,
+ 258, 24049, 258, 24062, 258, 24178, 258, 24186, 258, 24191, 258, 24308,
+ 258, 24318, 258, 24331, 258, 24339, 258, 24400, 258, 24417, 258, 24435,
+ 258, 24515, 258, 25096, 258, 25142, 258, 25163, 258, 25903, 258, 25908,
+ 258, 25991, 258, 26007, 258, 26020, 258, 26041, 258, 26080, 258, 26085,
+ 258, 26352, 258, 26376, 258, 26408, 258, 27424, 258, 27490, 258, 27513,
+ 258, 27571, 258, 27595, 258, 27604, 258, 27611, 258, 27663, 258, 27668,
+ 258, 27700, 258, 28779, 258, 29226, 258, 29238, 258, 29243, 258, 29247,
+ 258, 29255, 258, 29273, 258, 29275, 258, 29356, 258, 29572, 258, 29577,
+ 258, 29916, 258, 29926, 258, 29976, 258, 29983, 258, 29992, 258, 30000,
+ 258, 30091, 258, 30098, 258, 30326, 258, 30333, 258, 30382, 258, 30399,
+ 258, 30446, 258, 30683, 258, 30690, 258, 30707, 258, 31034, 258, 31160,
+ 258, 31166, 258, 31348, 258, 31435, 258, 31481, 258, 31859, 258, 31992,
+ 258, 32566, 258, 32593, 258, 32650, 258, 32701, 258, 32769, 258, 32780,
+ 258, 32786, 258, 32819, 258, 32895, 258, 32905, 258, 33251, 258, 33258,
+ 258, 33267, 258, 33276, 258, 33292, 258, 33307, 258, 33311, 258, 33390,
+ 258, 33394, 258, 33400, 258, 34381, 258, 34411, 258, 34880, 258, 34892,
+ 258, 34915, 258, 35198, 258, 35211, 258, 35282, 258, 35328, 258, 35895,
+ 258, 35910, 258, 35925, 258, 35960, 258, 35997, 258, 36196, 258, 36208,
+ 258, 36275, 258, 36523, 258, 36554, 258, 36763, 258, 36784, 258, 36789,
+ 258, 37009, 258, 37193, 258, 37318, 258, 37324, 258, 37329, 258, 38263,
+ 258, 38272, 258, 38428, 258, 38582, 258, 38585, 258, 38632, 258, 38737,
+ 258, 38750, 258, 38754, 258, 38761, 258, 38859, 258, 38893, 258, 38899,
+ 258, 38913, 258, 39080, 258, 39131, 258, 39135, 258, 39318, 258, 39321,
+ 258, 39340, 258, 39592, 258, 39640, 258, 39647, 258, 39717, 258, 39727,
+ 258, 39730, 258, 39740, 258, 39770, 258, 40165, 258, 40565, 258, 40575,
+ 258, 40613, 258, 40635, 258, 40643, 258, 40653, 258, 40657, 258, 40697,
+ 258, 40701, 258, 40718, 258, 40723, 258, 40736, 258, 40763, 258, 40778,
+ 258, 40786, 258, 40845, 258, 40860, 258, 40864, 264, 32, 258, 12306, 258,
+ 21313, 258, 21316, 258, 21317, 512, 12363, 12441, 512, 12365, 12441, 512,
+ 12367, 12441, 512, 12369, 12441, 512, 12371, 12441, 512, 12373, 12441,
+ 512, 12375, 12441, 512, 12377, 12441, 512, 12379, 12441, 512, 12381,
+ 12441, 512, 12383, 12441, 512, 12385, 12441, 512, 12388, 12441, 512,
+ 12390, 12441, 512, 12392, 12441, 512, 12399, 12441, 512, 12399, 12442,
+ 512, 12402, 12441, 512, 12402, 12442, 512, 12405, 12441, 512, 12405,
+ 12442, 512, 12408, 12441, 512, 12408, 12442, 512, 12411, 12441, 512,
+ 12411, 12442, 512, 12358, 12441, 514, 32, 12441, 514, 32, 12442, 512,
+ 12445, 12441, 521, 12424, 12426, 512, 12459, 12441, 512, 12461, 12441,
+ 512, 12463, 12441, 512, 12465, 12441, 512, 12467, 12441, 512, 12469,
+ 12441, 512, 12471, 12441, 512, 12473, 12441, 512, 12475, 12441, 512,
+ 12477, 12441, 512, 12479, 12441, 512, 12481, 12441, 512, 12484, 12441,
+ 512, 12486, 12441, 512, 12488, 12441, 512, 12495, 12441, 512, 12495,
+ 12442, 512, 12498, 12441, 512, 12498, 12442, 512, 12501, 12441, 512,
+ 12501, 12442, 512, 12504, 12441, 512, 12504, 12442, 512, 12507, 12441,
+ 512, 12507, 12442, 512, 12454, 12441, 512, 12527, 12441, 512, 12528,
+ 12441, 512, 12529, 12441, 512, 12530, 12441, 512, 12541, 12441, 521,
+ 12467, 12488, 258, 4352, 258, 4353, 258, 4522, 258, 4354, 258, 4524, 258,
+ 4525, 258, 4355, 258, 4356, 258, 4357, 258, 4528, 258, 4529, 258, 4530,
+ 258, 4531, 258, 4532, 258, 4533, 258, 4378, 258, 4358, 258, 4359, 258,
+ 4360, 258, 4385, 258, 4361, 258, 4362, 258, 4363, 258, 4364, 258, 4365,
+ 258, 4366, 258, 4367, 258, 4368, 258, 4369, 258, 4370, 258, 4449, 258,
+ 4450, 258, 4451, 258, 4452, 258, 4453, 258, 4454, 258, 4455, 258, 4456,
+ 258, 4457, 258, 4458, 258, 4459, 258, 4460, 258, 4461, 258, 4462, 258,
+ 4463, 258, 4464, 258, 4465, 258, 4466, 258, 4467, 258, 4468, 258, 4469,
+ 258, 4448, 258, 4372, 258, 4373, 258, 4551, 258, 4552, 258, 4556, 258,
+ 4558, 258, 4563, 258, 4567, 258, 4569, 258, 4380, 258, 4573, 258, 4575,
+ 258, 4381, 258, 4382, 258, 4384, 258, 4386, 258, 4387, 258, 4391, 258,
+ 4393, 258, 4395, 258, 4396, 258, 4397, 258, 4398, 258, 4399, 258, 4402,
+ 258, 4406, 258, 4416, 258, 4423, 258, 4428, 258, 4593, 258, 4594, 258,
+ 4439, 258, 4440, 258, 4441, 258, 4484, 258, 4485, 258, 4488, 258, 4497,
+ 258, 4498, 258, 4500, 258, 4510, 258, 4513, 259, 19968, 259, 20108, 259,
+ 19977, 259, 22235, 259, 19978, 259, 20013, 259, 19979, 259, 30002, 259,
+ 20057, 259, 19993, 259, 19969, 259, 22825, 259, 22320, 259, 20154, 770,
+ 40, 4352, 41, 770, 40, 4354, 41, 770, 40, 4355, 41, 770, 40, 4357, 41,
+ 770, 40, 4358, 41, 770, 40, 4359, 41, 770, 40, 4361, 41, 770, 40, 4363,
+ 41, 770, 40, 4364, 41, 770, 40, 4366, 41, 770, 40, 4367, 41, 770, 40,
+ 4368, 41, 770, 40, 4369, 41, 770, 40, 4370, 41, 1026, 40, 4352, 4449, 41,
+ 1026, 40, 4354, 4449, 41, 1026, 40, 4355, 4449, 41, 1026, 40, 4357, 4449,
+ 41, 1026, 40, 4358, 4449, 41, 1026, 40, 4359, 4449, 41, 1026, 40, 4361,
+ 4449, 41, 1026, 40, 4363, 4449, 41, 1026, 40, 4364, 4449, 41, 1026, 40,
+ 4366, 4449, 41, 1026, 40, 4367, 4449, 41, 1026, 40, 4368, 4449, 41, 1026,
+ 40, 4369, 4449, 41, 1026, 40, 4370, 4449, 41, 1026, 40, 4364, 4462, 41,
+ 1794, 40, 4363, 4457, 4364, 4453, 4523, 41, 1538, 40, 4363, 4457, 4370,
+ 4462, 41, 770, 40, 19968, 41, 770, 40, 20108, 41, 770, 40, 19977, 41,
+ 770, 40, 22235, 41, 770, 40, 20116, 41, 770, 40, 20845, 41, 770, 40,
+ 19971, 41, 770, 40, 20843, 41, 770, 40, 20061, 41, 770, 40, 21313, 41,
+ 770, 40, 26376, 41, 770, 40, 28779, 41, 770, 40, 27700, 41, 770, 40,
+ 26408, 41, 770, 40, 37329, 41, 770, 40, 22303, 41, 770, 40, 26085, 41,
+ 770, 40, 26666, 41, 770, 40, 26377, 41, 770, 40, 31038, 41, 770, 40,
+ 21517, 41, 770, 40, 29305, 41, 770, 40, 36001, 41, 770, 40, 31069, 41,
+ 770, 40, 21172, 41, 770, 40, 20195, 41, 770, 40, 21628, 41, 770, 40,
+ 23398, 41, 770, 40, 30435, 41, 770, 40, 20225, 41, 770, 40, 36039, 41,
+ 770, 40, 21332, 41, 770, 40, 31085, 41, 770, 40, 20241, 41, 770, 40,
+ 33258, 41, 770, 40, 33267, 41, 263, 21839, 263, 24188, 263, 25991, 263,
+ 31631, 778, 80, 84, 69, 519, 50, 49, 519, 50, 50, 519, 50, 51, 519, 50,
+ 52, 519, 50, 53, 519, 50, 54, 519, 50, 55, 519, 50, 56, 519, 50, 57, 519,
+ 51, 48, 519, 51, 49, 519, 51, 50, 519, 51, 51, 519, 51, 52, 519, 51, 53,
+ 263, 4352, 263, 4354, 263, 4355, 263, 4357, 263, 4358, 263, 4359, 263,
+ 4361, 263, 4363, 263, 4364, 263, 4366, 263, 4367, 263, 4368, 263, 4369,
+ 263, 4370, 519, 4352, 4449, 519, 4354, 4449, 519, 4355, 4449, 519, 4357,
+ 4449, 519, 4358, 4449, 519, 4359, 4449, 519, 4361, 4449, 519, 4363, 4449,
+ 519, 4364, 4449, 519, 4366, 4449, 519, 4367, 4449, 519, 4368, 4449, 519,
+ 4369, 4449, 519, 4370, 4449, 1287, 4366, 4449, 4535, 4352, 4457, 1031,
+ 4364, 4462, 4363, 4468, 519, 4363, 4462, 263, 19968, 263, 20108, 263,
+ 19977, 263, 22235, 263, 20116, 263, 20845, 263, 19971, 263, 20843, 263,
+ 20061, 263, 21313, 263, 26376, 263, 28779, 263, 27700, 263, 26408, 263,
+ 37329, 263, 22303, 263, 26085, 263, 26666, 263, 26377, 263, 31038, 263,
+ 21517, 263, 29305, 263, 36001, 263, 31069, 263, 21172, 263, 31192, 263,
+ 30007, 263, 22899, 263, 36969, 263, 20778, 263, 21360, 263, 27880, 263,
+ 38917, 263, 20241, 263, 20889, 263, 27491, 263, 19978, 263, 20013, 263,
+ 19979, 263, 24038, 263, 21491, 263, 21307, 263, 23447, 263, 23398, 263,
+ 30435, 263, 20225, 263, 36039, 263, 21332, 263, 22812, 519, 51, 54, 519,
+ 51, 55, 519, 51, 56, 519, 51, 57, 519, 52, 48, 519, 52, 49, 519, 52, 50,
+ 519, 52, 51, 519, 52, 52, 519, 52, 53, 519, 52, 54, 519, 52, 55, 519, 52,
+ 56, 519, 52, 57, 519, 53, 48, 514, 49, 26376, 514, 50, 26376, 514, 51,
+ 26376, 514, 52, 26376, 514, 53, 26376, 514, 54, 26376, 514, 55, 26376,
+ 514, 56, 26376, 514, 57, 26376, 770, 49, 48, 26376, 770, 49, 49, 26376,
+ 770, 49, 50, 26376, 522, 72, 103, 778, 101, 114, 103, 522, 101, 86, 778,
+ 76, 84, 68, 263, 12450, 263, 12452, 263, 12454, 263, 12456, 263, 12458,
+ 263, 12459, 263, 12461, 263, 12463, 263, 12465, 263, 12467, 263, 12469,
+ 263, 12471, 263, 12473, 263, 12475, 263, 12477, 263, 12479, 263, 12481,
+ 263, 12484, 263, 12486, 263, 12488, 263, 12490, 263, 12491, 263, 12492,
+ 263, 12493, 263, 12494, 263, 12495, 263, 12498, 263, 12501, 263, 12504,
+ 263, 12507, 263, 12510, 263, 12511, 263, 12512, 263, 12513, 263, 12514,
+ 263, 12516, 263, 12518, 263, 12520, 263, 12521, 263, 12522, 263, 12523,
+ 263, 12524, 263, 12525, 263, 12527, 263, 12528, 263, 12529, 263, 12530,
+ 1034, 12450, 12497, 12540, 12488, 1034, 12450, 12523, 12501, 12449, 1034,
+ 12450, 12531, 12506, 12450, 778, 12450, 12540, 12523, 1034, 12452, 12491,
+ 12531, 12464, 778, 12452, 12531, 12481, 778, 12454, 12457, 12531, 1290,
+ 12456, 12473, 12463, 12540, 12489, 1034, 12456, 12540, 12459, 12540, 778,
+ 12458, 12531, 12473, 778, 12458, 12540, 12512, 778, 12459, 12452, 12522,
+ 1034, 12459, 12521, 12483, 12488, 1034, 12459, 12525, 12522, 12540, 778,
+ 12460, 12525, 12531, 778, 12460, 12531, 12510, 522, 12462, 12460, 778,
+ 12462, 12491, 12540, 1034, 12461, 12517, 12522, 12540, 1034, 12462,
+ 12523, 12480, 12540, 522, 12461, 12525, 1290, 12461, 12525, 12464, 12521,
+ 12512, 1546, 12461, 12525, 12513, 12540, 12488, 12523, 1290, 12461,
+ 12525, 12527, 12483, 12488, 778, 12464, 12521, 12512, 1290, 12464, 12521,
+ 12512, 12488, 12531, 1290, 12463, 12523, 12476, 12452, 12525, 1034,
+ 12463, 12525, 12540, 12493, 778, 12465, 12540, 12473, 778, 12467, 12523,
+ 12490, 778, 12467, 12540, 12509, 1034, 12469, 12452, 12463, 12523, 1290,
+ 12469, 12531, 12481, 12540, 12512, 1034, 12471, 12522, 12531, 12464, 778,
+ 12475, 12531, 12481, 778, 12475, 12531, 12488, 778, 12480, 12540, 12473,
+ 522, 12487, 12471, 522, 12489, 12523, 522, 12488, 12531, 522, 12490,
+ 12494, 778, 12494, 12483, 12488, 778, 12495, 12452, 12484, 1290, 12497,
+ 12540, 12475, 12531, 12488, 778, 12497, 12540, 12484, 1034, 12496, 12540,
+ 12524, 12523, 1290, 12500, 12450, 12473, 12488, 12523, 778, 12500, 12463,
+ 12523, 522, 12500, 12467, 522, 12499, 12523, 1290, 12501, 12449, 12521,
+ 12483, 12489, 1034, 12501, 12451, 12540, 12488, 1290, 12502, 12483,
+ 12471, 12455, 12523, 778, 12501, 12521, 12531, 1290, 12504, 12463, 12479,
+ 12540, 12523, 522, 12506, 12477, 778, 12506, 12491, 12498, 778, 12504,
+ 12523, 12484, 778, 12506, 12531, 12473, 778, 12506, 12540, 12472, 778,
+ 12505, 12540, 12479, 1034, 12509, 12452, 12531, 12488, 778, 12508, 12523,
+ 12488, 522, 12507, 12531, 778, 12509, 12531, 12489, 778, 12507, 12540,
+ 12523, 778, 12507, 12540, 12531, 1034, 12510, 12452, 12463, 12525, 778,
+ 12510, 12452, 12523, 778, 12510, 12483, 12495, 778, 12510, 12523, 12463,
+ 1290, 12510, 12531, 12471, 12519, 12531, 1034, 12511, 12463, 12525,
+ 12531, 522, 12511, 12522, 1290, 12511, 12522, 12496, 12540, 12523, 522,
+ 12513, 12460, 1034, 12513, 12460, 12488, 12531, 1034, 12513, 12540,
+ 12488, 12523, 778, 12516, 12540, 12489, 778, 12516, 12540, 12523, 778,
+ 12518, 12450, 12531, 1034, 12522, 12483, 12488, 12523, 522, 12522, 12521,
+ 778, 12523, 12500, 12540, 1034, 12523, 12540, 12502, 12523, 522, 12524,
+ 12512, 1290, 12524, 12531, 12488, 12466, 12531, 778, 12527, 12483, 12488,
+ 514, 48, 28857, 514, 49, 28857, 514, 50, 28857, 514, 51, 28857, 514, 52,
+ 28857, 514, 53, 28857, 514, 54, 28857, 514, 55, 28857, 514, 56, 28857,
+ 514, 57, 28857, 770, 49, 48, 28857, 770, 49, 49, 28857, 770, 49, 50,
+ 28857, 770, 49, 51, 28857, 770, 49, 52, 28857, 770, 49, 53, 28857, 770,
+ 49, 54, 28857, 770, 49, 55, 28857, 770, 49, 56, 28857, 770, 49, 57,
+ 28857, 770, 50, 48, 28857, 770, 50, 49, 28857, 770, 50, 50, 28857, 770,
+ 50, 51, 28857, 770, 50, 52, 28857, 778, 104, 80, 97, 522, 100, 97, 522,
+ 65, 85, 778, 98, 97, 114, 522, 111, 86, 522, 112, 99, 522, 100, 109, 778,
+ 100, 109, 178, 778, 100, 109, 179, 522, 73, 85, 522, 24179, 25104, 522,
+ 26157, 21644, 522, 22823, 27491, 522, 26126, 27835, 1034, 26666, 24335,
+ 20250, 31038, 522, 112, 65, 522, 110, 65, 522, 956, 65, 522, 109, 65,
+ 522, 107, 65, 522, 75, 66, 522, 77, 66, 522, 71, 66, 778, 99, 97, 108,
+ 1034, 107, 99, 97, 108, 522, 112, 70, 522, 110, 70, 522, 956, 70, 522,
+ 956, 103, 522, 109, 103, 522, 107, 103, 522, 72, 122, 778, 107, 72, 122,
+ 778, 77, 72, 122, 778, 71, 72, 122, 778, 84, 72, 122, 522, 956, 8467,
+ 522, 109, 8467, 522, 100, 8467, 522, 107, 8467, 522, 102, 109, 522, 110,
+ 109, 522, 956, 109, 522, 109, 109, 522, 99, 109, 522, 107, 109, 778, 109,
+ 109, 178, 778, 99, 109, 178, 522, 109, 178, 778, 107, 109, 178, 778, 109,
+ 109, 179, 778, 99, 109, 179, 522, 109, 179, 778, 107, 109, 179, 778, 109,
+ 8725, 115, 1034, 109, 8725, 115, 178, 522, 80, 97, 778, 107, 80, 97, 778,
+ 77, 80, 97, 778, 71, 80, 97, 778, 114, 97, 100, 1290, 114, 97, 100, 8725,
+ 115, 1546, 114, 97, 100, 8725, 115, 178, 522, 112, 115, 522, 110, 115,
+ 522, 956, 115, 522, 109, 115, 522, 112, 86, 522, 110, 86, 522, 956, 86,
+ 522, 109, 86, 522, 107, 86, 522, 77, 86, 522, 112, 87, 522, 110, 87, 522,
+ 956, 87, 522, 109, 87, 522, 107, 87, 522, 77, 87, 522, 107, 937, 522, 77,
+ 937, 1034, 97, 46, 109, 46, 522, 66, 113, 522, 99, 99, 522, 99, 100,
+ 1034, 67, 8725, 107, 103, 778, 67, 111, 46, 522, 100, 66, 522, 71, 121,
+ 522, 104, 97, 522, 72, 80, 522, 105, 110, 522, 75, 75, 522, 75, 77, 522,
+ 107, 116, 522, 108, 109, 522, 108, 110, 778, 108, 111, 103, 522, 108,
+ 120, 522, 109, 98, 778, 109, 105, 108, 778, 109, 111, 108, 522, 80, 72,
+ 1034, 112, 46, 109, 46, 778, 80, 80, 77, 522, 80, 82, 522, 115, 114, 522,
+ 83, 118, 522, 87, 98, 778, 86, 8725, 109, 778, 65, 8725, 109, 514, 49,
+ 26085, 514, 50, 26085, 514, 51, 26085, 514, 52, 26085, 514, 53, 26085,
+ 514, 54, 26085, 514, 55, 26085, 514, 56, 26085, 514, 57, 26085, 770, 49,
+ 48, 26085, 770, 49, 49, 26085, 770, 49, 50, 26085, 770, 49, 51, 26085,
+ 770, 49, 52, 26085, 770, 49, 53, 26085, 770, 49, 54, 26085, 770, 49, 55,
+ 26085, 770, 49, 56, 26085, 770, 49, 57, 26085, 770, 50, 48, 26085, 770,
+ 50, 49, 26085, 770, 50, 50, 26085, 770, 50, 51, 26085, 770, 50, 52,
+ 26085, 770, 50, 53, 26085, 770, 50, 54, 26085, 770, 50, 55, 26085, 770,
+ 50, 56, 26085, 770, 50, 57, 26085, 770, 51, 48, 26085, 770, 51, 49,
+ 26085, 778, 103, 97, 108, 259, 42863, 259, 294, 259, 339, 256, 35912,
+ 256, 26356, 256, 36554, 256, 36040, 256, 28369, 256, 20018, 256, 21477,
+ 256, 40860, 256, 40860, 256, 22865, 256, 37329, 256, 21895, 256, 22856,
+ 256, 25078, 256, 30313, 256, 32645, 256, 34367, 256, 34746, 256, 35064,
+ 256, 37007, 256, 27138, 256, 27931, 256, 28889, 256, 29662, 256, 33853,
+ 256, 37226, 256, 39409, 256, 20098, 256, 21365, 256, 27396, 256, 29211,
+ 256, 34349, 256, 40478, 256, 23888, 256, 28651, 256, 34253, 256, 35172,
+ 256, 25289, 256, 33240, 256, 34847, 256, 24266, 256, 26391, 256, 28010,
+ 256, 29436, 256, 37070, 256, 20358, 256, 20919, 256, 21214, 256, 25796,
+ 256, 27347, 256, 29200, 256, 30439, 256, 32769, 256, 34310, 256, 34396,
+ 256, 36335, 256, 38706, 256, 39791, 256, 40442, 256, 30860, 256, 31103,
+ 256, 32160, 256, 33737, 256, 37636, 256, 40575, 256, 35542, 256, 22751,
+ 256, 24324, 256, 31840, 256, 32894, 256, 29282, 256, 30922, 256, 36034,
+ 256, 38647, 256, 22744, 256, 23650, 256, 27155, 256, 28122, 256, 28431,
+ 256, 32047, 256, 32311, 256, 38475, 256, 21202, 256, 32907, 256, 20956,
+ 256, 20940, 256, 31260, 256, 32190, 256, 33777, 256, 38517, 256, 35712,
+ 256, 25295, 256, 27138, 256, 35582, 256, 20025, 256, 23527, 256, 24594,
+ 256, 29575, 256, 30064, 256, 21271, 256, 30971, 256, 20415, 256, 24489,
+ 256, 19981, 256, 27852, 256, 25976, 256, 32034, 256, 21443, 256, 22622,
+ 256, 30465, 256, 33865, 256, 35498, 256, 27578, 256, 36784, 256, 27784,
+ 256, 25342, 256, 33509, 256, 25504, 256, 30053, 256, 20142, 256, 20841,
+ 256, 20937, 256, 26753, 256, 31975, 256, 33391, 256, 35538, 256, 37327,
+ 256, 21237, 256, 21570, 256, 22899, 256, 24300, 256, 26053, 256, 28670,
+ 256, 31018, 256, 38317, 256, 39530, 256, 40599, 256, 40654, 256, 21147,
+ 256, 26310, 256, 27511, 256, 36706, 256, 24180, 256, 24976, 256, 25088,
+ 256, 25754, 256, 28451, 256, 29001, 256, 29833, 256, 31178, 256, 32244,
+ 256, 32879, 256, 36646, 256, 34030, 256, 36899, 256, 37706, 256, 21015,
+ 256, 21155, 256, 21693, 256, 28872, 256, 35010, 256, 35498, 256, 24265,
+ 256, 24565, 256, 25467, 256, 27566, 256, 31806, 256, 29557, 256, 20196,
+ 256, 22265, 256, 23527, 256, 23994, 256, 24604, 256, 29618, 256, 29801,
+ 256, 32666, 256, 32838, 256, 37428, 256, 38646, 256, 38728, 256, 38936,
+ 256, 20363, 256, 31150, 256, 37300, 256, 38584, 256, 24801, 256, 20102,
+ 256, 20698, 256, 23534, 256, 23615, 256, 26009, 256, 27138, 256, 29134,
+ 256, 30274, 256, 34044, 256, 36988, 256, 40845, 256, 26248, 256, 38446,
+ 256, 21129, 256, 26491, 256, 26611, 256, 27969, 256, 28316, 256, 29705,
+ 256, 30041, 256, 30827, 256, 32016, 256, 39006, 256, 20845, 256, 25134,
+ 256, 38520, 256, 20523, 256, 23833, 256, 28138, 256, 36650, 256, 24459,
+ 256, 24900, 256, 26647, 256, 29575, 256, 38534, 256, 21033, 256, 21519,
+ 256, 23653, 256, 26131, 256, 26446, 256, 26792, 256, 27877, 256, 29702,
+ 256, 30178, 256, 32633, 256, 35023, 256, 35041, 256, 37324, 256, 38626,
+ 256, 21311, 256, 28346, 256, 21533, 256, 29136, 256, 29848, 256, 34298,
+ 256, 38563, 256, 40023, 256, 40607, 256, 26519, 256, 28107, 256, 33256,
+ 256, 31435, 256, 31520, 256, 31890, 256, 29376, 256, 28825, 256, 35672,
+ 256, 20160, 256, 33590, 256, 21050, 256, 20999, 256, 24230, 256, 25299,
+ 256, 31958, 256, 23429, 256, 27934, 256, 26292, 256, 36667, 256, 34892,
+ 256, 38477, 256, 35211, 256, 24275, 256, 20800, 256, 21952, 256, 22618,
+ 256, 26228, 256, 20958, 256, 29482, 256, 30410, 256, 31036, 256, 31070,
+ 256, 31077, 256, 31119, 256, 38742, 256, 31934, 256, 32701, 256, 34322,
+ 256, 35576, 256, 36920, 256, 37117, 256, 39151, 256, 39164, 256, 39208,
+ 256, 40372, 256, 37086, 256, 38583, 256, 20398, 256, 20711, 256, 20813,
+ 256, 21193, 256, 21220, 256, 21329, 256, 21917, 256, 22022, 256, 22120,
+ 256, 22592, 256, 22696, 256, 23652, 256, 23662, 256, 24724, 256, 24936,
+ 256, 24974, 256, 25074, 256, 25935, 256, 26082, 256, 26257, 256, 26757,
+ 256, 28023, 256, 28186, 256, 28450, 256, 29038, 256, 29227, 256, 29730,
+ 256, 30865, 256, 31038, 256, 31049, 256, 31048, 256, 31056, 256, 31062,
+ 256, 31069, 256, 31117, 256, 31118, 256, 31296, 256, 31361, 256, 31680,
+ 256, 32244, 256, 32265, 256, 32321, 256, 32626, 256, 32773, 256, 33261,
+ 256, 33401, 256, 33401, 256, 33879, 256, 35088, 256, 35222, 256, 35585,
+ 256, 35641, 256, 36051, 256, 36104, 256, 36790, 256, 36920, 256, 38627,
+ 256, 38911, 256, 38971, 256, 24693, 256, 55376, 57070, 256, 33304, 256,
+ 20006, 256, 20917, 256, 20840, 256, 20352, 256, 20805, 256, 20864, 256,
+ 21191, 256, 21242, 256, 21917, 256, 21845, 256, 21913, 256, 21986, 256,
+ 22618, 256, 22707, 256, 22852, 256, 22868, 256, 23138, 256, 23336, 256,
+ 24274, 256, 24281, 256, 24425, 256, 24493, 256, 24792, 256, 24910, 256,
+ 24840, 256, 24974, 256, 24928, 256, 25074, 256, 25140, 256, 25540, 256,
+ 25628, 256, 25682, 256, 25942, 256, 26228, 256, 26391, 256, 26395, 256,
+ 26454, 256, 27513, 256, 27578, 256, 27969, 256, 28379, 256, 28363, 256,
+ 28450, 256, 28702, 256, 29038, 256, 30631, 256, 29237, 256, 29359, 256,
+ 29482, 256, 29809, 256, 29958, 256, 30011, 256, 30237, 256, 30239, 256,
+ 30410, 256, 30427, 256, 30452, 256, 30538, 256, 30528, 256, 30924, 256,
+ 31409, 256, 31680, 256, 31867, 256, 32091, 256, 32244, 256, 32574, 256,
+ 32773, 256, 33618, 256, 33775, 256, 34681, 256, 35137, 256, 35206, 256,
+ 35222, 256, 35519, 256, 35576, 256, 35531, 256, 35585, 256, 35582, 256,
+ 35565, 256, 35641, 256, 35722, 256, 36104, 256, 36664, 256, 36978, 256,
+ 37273, 256, 37494, 256, 38524, 256, 38627, 256, 38742, 256, 38875, 256,
+ 38911, 256, 38923, 256, 38971, 256, 39698, 256, 40860, 256, 55370, 56394,
+ 256, 55370, 56388, 256, 55372, 57301, 256, 15261, 256, 16408, 256, 16441,
+ 256, 55380, 56905, 256, 55383, 56528, 256, 55391, 57043, 256, 40771, 256,
+ 40846, 514, 102, 102, 514, 102, 105, 514, 102, 108, 770, 102, 102, 105,
+ 770, 102, 102, 108, 514, 383, 116, 514, 115, 116, 514, 1396, 1398, 514,
+ 1396, 1381, 514, 1396, 1387, 514, 1406, 1398, 514, 1396, 1389, 512, 1497,
+ 1460, 512, 1522, 1463, 262, 1506, 262, 1488, 262, 1491, 262, 1492, 262,
+ 1499, 262, 1500, 262, 1501, 262, 1512, 262, 1514, 262, 43, 512, 1513,
+ 1473, 512, 1513, 1474, 512, 64329, 1473, 512, 64329, 1474, 512, 1488,
+ 1463, 512, 1488, 1464, 512, 1488, 1468, 512, 1489, 1468, 512, 1490, 1468,
+ 512, 1491, 1468, 512, 1492, 1468, 512, 1493, 1468, 512, 1494, 1468, 512,
+ 1496, 1468, 512, 1497, 1468, 512, 1498, 1468, 512, 1499, 1468, 512, 1500,
+ 1468, 512, 1502, 1468, 512, 1504, 1468, 512, 1505, 1468, 512, 1507, 1468,
+ 512, 1508, 1468, 512, 1510, 1468, 512, 1511, 1468, 512, 1512, 1468, 512,
+ 1513, 1468, 512, 1514, 1468, 512, 1493, 1465, 512, 1489, 1471, 512, 1499,
+ 1471, 512, 1508, 1471, 514, 1488, 1500, 267, 1649, 268, 1649, 267, 1659,
+ 268, 1659, 269, 1659, 270, 1659, 267, 1662, 268, 1662, 269, 1662, 270,
+ 1662, 267, 1664, 268, 1664, 269, 1664, 270, 1664, 267, 1658, 268, 1658,
+ 269, 1658, 270, 1658, 267, 1663, 268, 1663, 269, 1663, 270, 1663, 267,
+ 1657, 268, 1657, 269, 1657, 270, 1657, 267, 1700, 268, 1700, 269, 1700,
+ 270, 1700, 267, 1702, 268, 1702, 269, 1702, 270, 1702, 267, 1668, 268,
+ 1668, 269, 1668, 270, 1668, 267, 1667, 268, 1667, 269, 1667, 270, 1667,
+ 267, 1670, 268, 1670, 269, 1670, 270, 1670, 267, 1671, 268, 1671, 269,
+ 1671, 270, 1671, 267, 1677, 268, 1677, 267, 1676, 268, 1676, 267, 1678,
+ 268, 1678, 267, 1672, 268, 1672, 267, 1688, 268, 1688, 267, 1681, 268,
+ 1681, 267, 1705, 268, 1705, 269, 1705, 270, 1705, 267, 1711, 268, 1711,
+ 269, 1711, 270, 1711, 267, 1715, 268, 1715, 269, 1715, 270, 1715, 267,
+ 1713, 268, 1713, 269, 1713, 270, 1713, 267, 1722, 268, 1722, 267, 1723,
+ 268, 1723, 269, 1723, 270, 1723, 267, 1728, 268, 1728, 267, 1729, 268,
+ 1729, 269, 1729, 270, 1729, 267, 1726, 268, 1726, 269, 1726, 270, 1726,
+ 267, 1746, 268, 1746, 267, 1747, 268, 1747, 267, 1709, 268, 1709, 269,
+ 1709, 270, 1709, 267, 1735, 268, 1735, 267, 1734, 268, 1734, 267, 1736,
+ 268, 1736, 267, 1655, 267, 1739, 268, 1739, 267, 1733, 268, 1733, 267,
+ 1737, 268, 1737, 267, 1744, 268, 1744, 269, 1744, 270, 1744, 269, 1609,
+ 270, 1609, 523, 1574, 1575, 524, 1574, 1575, 523, 1574, 1749, 524, 1574,
+ 1749, 523, 1574, 1608, 524, 1574, 1608, 523, 1574, 1735, 524, 1574, 1735,
+ 523, 1574, 1734, 524, 1574, 1734, 523, 1574, 1736, 524, 1574, 1736, 523,
+ 1574, 1744, 524, 1574, 1744, 525, 1574, 1744, 523, 1574, 1609, 524, 1574,
+ 1609, 525, 1574, 1609, 267, 1740, 268, 1740, 269, 1740, 270, 1740, 523,
+ 1574, 1580, 523, 1574, 1581, 523, 1574, 1605, 523, 1574, 1609, 523, 1574,
+ 1610, 523, 1576, 1580, 523, 1576, 1581, 523, 1576, 1582, 523, 1576, 1605,
+ 523, 1576, 1609, 523, 1576, 1610, 523, 1578, 1580, 523, 1578, 1581, 523,
+ 1578, 1582, 523, 1578, 1605, 523, 1578, 1609, 523, 1578, 1610, 523, 1579,
+ 1580, 523, 1579, 1605, 523, 1579, 1609, 523, 1579, 1610, 523, 1580, 1581,
+ 523, 1580, 1605, 523, 1581, 1580, 523, 1581, 1605, 523, 1582, 1580, 523,
+ 1582, 1581, 523, 1582, 1605, 523, 1587, 1580, 523, 1587, 1581, 523, 1587,
+ 1582, 523, 1587, 1605, 523, 1589, 1581, 523, 1589, 1605, 523, 1590, 1580,
+ 523, 1590, 1581, 523, 1590, 1582, 523, 1590, 1605, 523, 1591, 1581, 523,
+ 1591, 1605, 523, 1592, 1605, 523, 1593, 1580, 523, 1593, 1605, 523, 1594,
+ 1580, 523, 1594, 1605, 523, 1601, 1580, 523, 1601, 1581, 523, 1601, 1582,
+ 523, 1601, 1605, 523, 1601, 1609, 523, 1601, 1610, 523, 1602, 1581, 523,
+ 1602, 1605, 523, 1602, 1609, 523, 1602, 1610, 523, 1603, 1575, 523, 1603,
+ 1580, 523, 1603, 1581, 523, 1603, 1582, 523, 1603, 1604, 523, 1603, 1605,
+ 523, 1603, 1609, 523, 1603, 1610, 523, 1604, 1580, 523, 1604, 1581, 523,
+ 1604, 1582, 523, 1604, 1605, 523, 1604, 1609, 523, 1604, 1610, 523, 1605,
+ 1580, 523, 1605, 1581, 523, 1605, 1582, 523, 1605, 1605, 523, 1605, 1609,
+ 523, 1605, 1610, 523, 1606, 1580, 523, 1606, 1581, 523, 1606, 1582, 523,
+ 1606, 1605, 523, 1606, 1609, 523, 1606, 1610, 523, 1607, 1580, 523, 1607,
+ 1605, 523, 1607, 1609, 523, 1607, 1610, 523, 1610, 1580, 523, 1610, 1581,
+ 523, 1610, 1582, 523, 1610, 1605, 523, 1610, 1609, 523, 1610, 1610, 523,
+ 1584, 1648, 523, 1585, 1648, 523, 1609, 1648, 779, 32, 1612, 1617, 779,
+ 32, 1613, 1617, 779, 32, 1614, 1617, 779, 32, 1615, 1617, 779, 32, 1616,
+ 1617, 779, 32, 1617, 1648, 524, 1574, 1585, 524, 1574, 1586, 524, 1574,
+ 1605, 524, 1574, 1606, 524, 1574, 1609, 524, 1574, 1610, 524, 1576, 1585,
+ 524, 1576, 1586, 524, 1576, 1605, 524, 1576, 1606, 524, 1576, 1609, 524,
+ 1576, 1610, 524, 1578, 1585, 524, 1578, 1586, 524, 1578, 1605, 524, 1578,
+ 1606, 524, 1578, 1609, 524, 1578, 1610, 524, 1579, 1585, 524, 1579, 1586,
+ 524, 1579, 1605, 524, 1579, 1606, 524, 1579, 1609, 524, 1579, 1610, 524,
+ 1601, 1609, 524, 1601, 1610, 524, 1602, 1609, 524, 1602, 1610, 524, 1603,
+ 1575, 524, 1603, 1604, 524, 1603, 1605, 524, 1603, 1609, 524, 1603, 1610,
+ 524, 1604, 1605, 524, 1604, 1609, 524, 1604, 1610, 524, 1605, 1575, 524,
+ 1605, 1605, 524, 1606, 1585, 524, 1606, 1586, 524, 1606, 1605, 524, 1606,
+ 1606, 524, 1606, 1609, 524, 1606, 1610, 524, 1609, 1648, 524, 1610, 1585,
+ 524, 1610, 1586, 524, 1610, 1605, 524, 1610, 1606, 524, 1610, 1609, 524,
+ 1610, 1610, 525, 1574, 1580, 525, 1574, 1581, 525, 1574, 1582, 525, 1574,
+ 1605, 525, 1574, 1607, 525, 1576, 1580, 525, 1576, 1581, 525, 1576, 1582,
+ 525, 1576, 1605, 525, 1576, 1607, 525, 1578, 1580, 525, 1578, 1581, 525,
+ 1578, 1582, 525, 1578, 1605, 525, 1578, 1607, 525, 1579, 1605, 525, 1580,
+ 1581, 525, 1580, 1605, 525, 1581, 1580, 525, 1581, 1605, 525, 1582, 1580,
+ 525, 1582, 1605, 525, 1587, 1580, 525, 1587, 1581, 525, 1587, 1582, 525,
+ 1587, 1605, 525, 1589, 1581, 525, 1589, 1582, 525, 1589, 1605, 525, 1590,
+ 1580, 525, 1590, 1581, 525, 1590, 1582, 525, 1590, 1605, 525, 1591, 1581,
+ 525, 1592, 1605, 525, 1593, 1580, 525, 1593, 1605, 525, 1594, 1580, 525,
+ 1594, 1605, 525, 1601, 1580, 525, 1601, 1581, 525, 1601, 1582, 525, 1601,
+ 1605, 525, 1602, 1581, 525, 1602, 1605, 525, 1603, 1580, 525, 1603, 1581,
+ 525, 1603, 1582, 525, 1603, 1604, 525, 1603, 1605, 525, 1604, 1580, 525,
+ 1604, 1581, 525, 1604, 1582, 525, 1604, 1605, 525, 1604, 1607, 525, 1605,
+ 1580, 525, 1605, 1581, 525, 1605, 1582, 525, 1605, 1605, 525, 1606, 1580,
+ 525, 1606, 1581, 525, 1606, 1582, 525, 1606, 1605, 525, 1606, 1607, 525,
+ 1607, 1580, 525, 1607, 1605, 525, 1607, 1648, 525, 1610, 1580, 525, 1610,
+ 1581, 525, 1610, 1582, 525, 1610, 1605, 525, 1610, 1607, 526, 1574, 1605,
+ 526, 1574, 1607, 526, 1576, 1605, 526, 1576, 1607, 526, 1578, 1605, 526,
+ 1578, 1607, 526, 1579, 1605, 526, 1579, 1607, 526, 1587, 1605, 526, 1587,
+ 1607, 526, 1588, 1605, 526, 1588, 1607, 526, 1603, 1604, 526, 1603, 1605,
+ 526, 1604, 1605, 526, 1606, 1605, 526, 1606, 1607, 526, 1610, 1605, 526,
+ 1610, 1607, 782, 1600, 1614, 1617, 782, 1600, 1615, 1617, 782, 1600,
+ 1616, 1617, 523, 1591, 1609, 523, 1591, 1610, 523, 1593, 1609, 523, 1593,
+ 1610, 523, 1594, 1609, 523, 1594, 1610, 523, 1587, 1609, 523, 1587, 1610,
+ 523, 1588, 1609, 523, 1588, 1610, 523, 1581, 1609, 523, 1581, 1610, 523,
+ 1580, 1609, 523, 1580, 1610, 523, 1582, 1609, 523, 1582, 1610, 523, 1589,
+ 1609, 523, 1589, 1610, 523, 1590, 1609, 523, 1590, 1610, 523, 1588, 1580,
+ 523, 1588, 1581, 523, 1588, 1582, 523, 1588, 1605, 523, 1588, 1585, 523,
+ 1587, 1585, 523, 1589, 1585, 523, 1590, 1585, 524, 1591, 1609, 524, 1591,
+ 1610, 524, 1593, 1609, 524, 1593, 1610, 524, 1594, 1609, 524, 1594, 1610,
+ 524, 1587, 1609, 524, 1587, 1610, 524, 1588, 1609, 524, 1588, 1610, 524,
+ 1581, 1609, 524, 1581, 1610, 524, 1580, 1609, 524, 1580, 1610, 524, 1582,
+ 1609, 524, 1582, 1610, 524, 1589, 1609, 524, 1589, 1610, 524, 1590, 1609,
+ 524, 1590, 1610, 524, 1588, 1580, 524, 1588, 1581, 524, 1588, 1582, 524,
+ 1588, 1605, 524, 1588, 1585, 524, 1587, 1585, 524, 1589, 1585, 524, 1590,
+ 1585, 525, 1588, 1580, 525, 1588, 1581, 525, 1588, 1582, 525, 1588, 1605,
+ 525, 1587, 1607, 525, 1588, 1607, 525, 1591, 1605, 526, 1587, 1580, 526,
+ 1587, 1581, 526, 1587, 1582, 526, 1588, 1580, 526, 1588, 1581, 526, 1588,
+ 1582, 526, 1591, 1605, 526, 1592, 1605, 524, 1575, 1611, 523, 1575, 1611,
+ 781, 1578, 1580, 1605, 780, 1578, 1581, 1580, 781, 1578, 1581, 1580, 781,
+ 1578, 1581, 1605, 781, 1578, 1582, 1605, 781, 1578, 1605, 1580, 781,
+ 1578, 1605, 1581, 781, 1578, 1605, 1582, 780, 1580, 1605, 1581, 781,
+ 1580, 1605, 1581, 780, 1581, 1605, 1610, 780, 1581, 1605, 1609, 781,
+ 1587, 1581, 1580, 781, 1587, 1580, 1581, 780, 1587, 1580, 1609, 780,
+ 1587, 1605, 1581, 781, 1587, 1605, 1581, 781, 1587, 1605, 1580, 780,
+ 1587, 1605, 1605, 781, 1587, 1605, 1605, 780, 1589, 1581, 1581, 781,
+ 1589, 1581, 1581, 780, 1589, 1605, 1605, 780, 1588, 1581, 1605, 781,
+ 1588, 1581, 1605, 780, 1588, 1580, 1610, 780, 1588, 1605, 1582, 781,
+ 1588, 1605, 1582, 780, 1588, 1605, 1605, 781, 1588, 1605, 1605, 780,
+ 1590, 1581, 1609, 780, 1590, 1582, 1605, 781, 1590, 1582, 1605, 780,
+ 1591, 1605, 1581, 781, 1591, 1605, 1581, 781, 1591, 1605, 1605, 780,
+ 1591, 1605, 1610, 780, 1593, 1580, 1605, 780, 1593, 1605, 1605, 781,
+ 1593, 1605, 1605, 780, 1593, 1605, 1609, 780, 1594, 1605, 1605, 780,
+ 1594, 1605, 1610, 780, 1594, 1605, 1609, 780, 1601, 1582, 1605, 781,
+ 1601, 1582, 1605, 780, 1602, 1605, 1581, 780, 1602, 1605, 1605, 780,
+ 1604, 1581, 1605, 780, 1604, 1581, 1610, 780, 1604, 1581, 1609, 781,
+ 1604, 1580, 1580, 780, 1604, 1580, 1580, 780, 1604, 1582, 1605, 781,
+ 1604, 1582, 1605, 780, 1604, 1605, 1581, 781, 1604, 1605, 1581, 781,
+ 1605, 1581, 1580, 781, 1605, 1581, 1605, 780, 1605, 1581, 1610, 781,
+ 1605, 1580, 1581, 781, 1605, 1580, 1605, 781, 1605, 1582, 1580, 781,
+ 1605, 1582, 1605, 781, 1605, 1580, 1582, 781, 1607, 1605, 1580, 781,
+ 1607, 1605, 1605, 781, 1606, 1581, 1605, 780, 1606, 1581, 1609, 780,
+ 1606, 1580, 1605, 781, 1606, 1580, 1605, 780, 1606, 1580, 1609, 780,
+ 1606, 1605, 1610, 780, 1606, 1605, 1609, 780, 1610, 1605, 1605, 781,
+ 1610, 1605, 1605, 780, 1576, 1582, 1610, 780, 1578, 1580, 1610, 780,
+ 1578, 1580, 1609, 780, 1578, 1582, 1610, 780, 1578, 1582, 1609, 780,
+ 1578, 1605, 1610, 780, 1578, 1605, 1609, 780, 1580, 1605, 1610, 780,
+ 1580, 1581, 1609, 780, 1580, 1605, 1609, 780, 1587, 1582, 1609, 780,
+ 1589, 1581, 1610, 780, 1588, 1581, 1610, 780, 1590, 1581, 1610, 780,
+ 1604, 1580, 1610, 780, 1604, 1605, 1610, 780, 1610, 1581, 1610, 780,
+ 1610, 1580, 1610, 780, 1610, 1605, 1610, 780, 1605, 1605, 1610, 780,
+ 1602, 1605, 1610, 780, 1606, 1581, 1610, 781, 1602, 1605, 1581, 781,
+ 1604, 1581, 1605, 780, 1593, 1605, 1610, 780, 1603, 1605, 1610, 781,
+ 1606, 1580, 1581, 780, 1605, 1582, 1610, 781, 1604, 1580, 1605, 780,
+ 1603, 1605, 1605, 780, 1604, 1580, 1605, 780, 1606, 1580, 1581, 780,
+ 1580, 1581, 1610, 780, 1581, 1580, 1610, 780, 1605, 1580, 1610, 780,
+ 1601, 1605, 1610, 780, 1576, 1581, 1610, 781, 1603, 1605, 1605, 781,
+ 1593, 1580, 1605, 781, 1589, 1605, 1605, 780, 1587, 1582, 1610, 780,
+ 1606, 1580, 1610, 779, 1589, 1604, 1746, 779, 1602, 1604, 1746, 1035,
+ 1575, 1604, 1604, 1607, 1035, 1575, 1603, 1576, 1585, 1035, 1605, 1581,
+ 1605, 1583, 1035, 1589, 1604, 1593, 1605, 1035, 1585, 1587, 1608, 1604,
+ 1035, 1593, 1604, 1610, 1607, 1035, 1608, 1587, 1604, 1605, 779, 1589,
+ 1604, 1609, 4619, 1589, 1604, 1609, 32, 1575, 1604, 1604, 1607, 32, 1593,
+ 1604, 1610, 1607, 32, 1608, 1587, 1604, 1605, 2059, 1580, 1604, 32, 1580,
+ 1604, 1575, 1604, 1607, 1035, 1585, 1740, 1575, 1604, 265, 44, 265,
+ 12289, 265, 12290, 265, 58, 265, 59, 265, 33, 265, 63, 265, 12310, 265,
+ 12311, 265, 8230, 265, 8229, 265, 8212, 265, 8211, 265, 95, 265, 95, 265,
+ 40, 265, 41, 265, 123, 265, 125, 265, 12308, 265, 12309, 265, 12304, 265,
+ 12305, 265, 12298, 265, 12299, 265, 12296, 265, 12297, 265, 12300, 265,
+ 12301, 265, 12302, 265, 12303, 265, 91, 265, 93, 258, 8254, 258, 8254,
+ 258, 8254, 258, 8254, 258, 95, 258, 95, 258, 95, 271, 44, 271, 12289,
+ 271, 46, 271, 59, 271, 58, 271, 63, 271, 33, 271, 8212, 271, 40, 271, 41,
+ 271, 123, 271, 125, 271, 12308, 271, 12309, 271, 35, 271, 38, 271, 42,
+ 271, 43, 271, 45, 271, 60, 271, 62, 271, 61, 271, 92, 271, 36, 271, 37,
+ 271, 64, 523, 32, 1611, 526, 1600, 1611, 523, 32, 1612, 523, 32, 1613,
+ 523, 32, 1614, 526, 1600, 1614, 523, 32, 1615, 526, 1600, 1615, 523, 32,
+ 1616, 526, 1600, 1616, 523, 32, 1617, 526, 1600, 1617, 523, 32, 1618,
+ 526, 1600, 1618, 267, 1569, 267, 1570, 268, 1570, 267, 1571, 268, 1571,
+ 267, 1572, 268, 1572, 267, 1573, 268, 1573, 267, 1574, 268, 1574, 269,
+ 1574, 270, 1574, 267, 1575, 268, 1575, 267, 1576, 268, 1576, 269, 1576,
+ 270, 1576, 267, 1577, 268, 1577, 267, 1578, 268, 1578, 269, 1578, 270,
+ 1578, 267, 1579, 268, 1579, 269, 1579, 270, 1579, 267, 1580, 268, 1580,
+ 269, 1580, 270, 1580, 267, 1581, 268, 1581, 269, 1581, 270, 1581, 267,
+ 1582, 268, 1582, 269, 1582, 270, 1582, 267, 1583, 268, 1583, 267, 1584,
+ 268, 1584, 267, 1585, 268, 1585, 267, 1586, 268, 1586, 267, 1587, 268,
+ 1587, 269, 1587, 270, 1587, 267, 1588, 268, 1588, 269, 1588, 270, 1588,
+ 267, 1589, 268, 1589, 269, 1589, 270, 1589, 267, 1590, 268, 1590, 269,
+ 1590, 270, 1590, 267, 1591, 268, 1591, 269, 1591, 270, 1591, 267, 1592,
+ 268, 1592, 269, 1592, 270, 1592, 267, 1593, 268, 1593, 269, 1593, 270,
+ 1593, 267, 1594, 268, 1594, 269, 1594, 270, 1594, 267, 1601, 268, 1601,
+ 269, 1601, 270, 1601, 267, 1602, 268, 1602, 269, 1602, 270, 1602, 267,
+ 1603, 268, 1603, 269, 1603, 270, 1603, 267, 1604, 268, 1604, 269, 1604,
+ 270, 1604, 267, 1605, 268, 1605, 269, 1605, 270, 1605, 267, 1606, 268,
+ 1606, 269, 1606, 270, 1606, 267, 1607, 268, 1607, 269, 1607, 270, 1607,
+ 267, 1608, 268, 1608, 267, 1609, 268, 1609, 267, 1610, 268, 1610, 269,
+ 1610, 270, 1610, 523, 1604, 1570, 524, 1604, 1570, 523, 1604, 1571, 524,
+ 1604, 1571, 523, 1604, 1573, 524, 1604, 1573, 523, 1604, 1575, 524, 1604,
+ 1575, 264, 33, 264, 34, 264, 35, 264, 36, 264, 37, 264, 38, 264, 39, 264,
+ 40, 264, 41, 264, 42, 264, 43, 264, 44, 264, 45, 264, 46, 264, 47, 264,
+ 48, 264, 49, 264, 50, 264, 51, 264, 52, 264, 53, 264, 54, 264, 55, 264,
+ 56, 264, 57, 264, 58, 264, 59, 264, 60, 264, 61, 264, 62, 264, 63, 264,
+ 64, 264, 65, 264, 66, 264, 67, 264, 68, 264, 69, 264, 70, 264, 71, 264,
+ 72, 264, 73, 264, 74, 264, 75, 264, 76, 264, 77, 264, 78, 264, 79, 264,
+ 80, 264, 81, 264, 82, 264, 83, 264, 84, 264, 85, 264, 86, 264, 87, 264,
+ 88, 264, 89, 264, 90, 264, 91, 264, 92, 264, 93, 264, 94, 264, 95, 264,
+ 96, 264, 97, 264, 98, 264, 99, 264, 100, 264, 101, 264, 102, 264, 103,
+ 264, 104, 264, 105, 264, 106, 264, 107, 264, 108, 264, 109, 264, 110,
+ 264, 111, 264, 112, 264, 113, 264, 114, 264, 115, 264, 116, 264, 117,
+ 264, 118, 264, 119, 264, 120, 264, 121, 264, 122, 264, 123, 264, 124,
+ 264, 125, 264, 126, 264, 10629, 264, 10630, 272, 12290, 272, 12300, 272,
+ 12301, 272, 12289, 272, 12539, 272, 12530, 272, 12449, 272, 12451, 272,
+ 12453, 272, 12455, 272, 12457, 272, 12515, 272, 12517, 272, 12519, 272,
+ 12483, 272, 12540, 272, 12450, 272, 12452, 272, 12454, 272, 12456, 272,
+ 12458, 272, 12459, 272, 12461, 272, 12463, 272, 12465, 272, 12467, 272,
+ 12469, 272, 12471, 272, 12473, 272, 12475, 272, 12477, 272, 12479, 272,
+ 12481, 272, 12484, 272, 12486, 272, 12488, 272, 12490, 272, 12491, 272,
+ 12492, 272, 12493, 272, 12494, 272, 12495, 272, 12498, 272, 12501, 272,
+ 12504, 272, 12507, 272, 12510, 272, 12511, 272, 12512, 272, 12513, 272,
+ 12514, 272, 12516, 272, 12518, 272, 12520, 272, 12521, 272, 12522, 272,
+ 12523, 272, 12524, 272, 12525, 272, 12527, 272, 12531, 272, 12441, 272,
+ 12442, 272, 12644, 272, 12593, 272, 12594, 272, 12595, 272, 12596, 272,
+ 12597, 272, 12598, 272, 12599, 272, 12600, 272, 12601, 272, 12602, 272,
+ 12603, 272, 12604, 272, 12605, 272, 12606, 272, 12607, 272, 12608, 272,
+ 12609, 272, 12610, 272, 12611, 272, 12612, 272, 12613, 272, 12614, 272,
+ 12615, 272, 12616, 272, 12617, 272, 12618, 272, 12619, 272, 12620, 272,
+ 12621, 272, 12622, 272, 12623, 272, 12624, 272, 12625, 272, 12626, 272,
+ 12627, 272, 12628, 272, 12629, 272, 12630, 272, 12631, 272, 12632, 272,
+ 12633, 272, 12634, 272, 12635, 272, 12636, 272, 12637, 272, 12638, 272,
+ 12639, 272, 12640, 272, 12641, 272, 12642, 272, 12643, 264, 162, 264,
+ 163, 264, 172, 264, 175, 264, 166, 264, 165, 264, 8361, 272, 9474, 272,
+ 8592, 272, 8593, 272, 8594, 272, 8595, 272, 9632, 272, 9675, 512, 55300,
+ 56473, 55300, 56506, 512, 55300, 56475, 55300, 56506, 512, 55300, 56485,
+ 55300, 56506, 512, 55300, 56625, 55300, 56615, 512, 55300, 56626, 55300,
+ 56615, 512, 55348, 56663, 55348, 56677, 512, 55348, 56664, 55348, 56677,
+ 512, 55348, 56671, 55348, 56686, 512, 55348, 56671, 55348, 56687, 512,
+ 55348, 56671, 55348, 56688, 512, 55348, 56671, 55348, 56689, 512, 55348,
+ 56671, 55348, 56690, 512, 55348, 56761, 55348, 56677, 512, 55348, 56762,
+ 55348, 56677, 512, 55348, 56763, 55348, 56686, 512, 55348, 56764, 55348,
+ 56686, 512, 55348, 56763, 55348, 56687, 512, 55348, 56764, 55348, 56687,
+ 262, 65, 262, 66, 262, 67, 262, 68, 262, 69, 262, 70, 262, 71, 262, 72,
+ 262, 73, 262, 74, 262, 75, 262, 76, 262, 77, 262, 78, 262, 79, 262, 80,
+ 262, 81, 262, 82, 262, 83, 262, 84, 262, 85, 262, 86, 262, 87, 262, 88,
+ 262, 89, 262, 90, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101, 262,
+ 102, 262, 103, 262, 104, 262, 105, 262, 106, 262, 107, 262, 108, 262,
+ 109, 262, 110, 262, 111, 262, 112, 262, 113, 262, 114, 262, 115, 262,
+ 116, 262, 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65,
+ 262, 66, 262, 67, 262, 68, 262, 69, 262, 70, 262, 71, 262, 72, 262, 73,
+ 262, 74, 262, 75, 262, 76, 262, 77, 262, 78, 262, 79, 262, 80, 262, 81,
+ 262, 82, 262, 83, 262, 84, 262, 85, 262, 86, 262, 87, 262, 88, 262, 89,
+ 262, 90, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101, 262, 102, 262,
+ 103, 262, 105, 262, 106, 262, 107, 262, 108, 262, 109, 262, 110, 262,
+ 111, 262, 112, 262, 113, 262, 114, 262, 115, 262, 116, 262, 117, 262,
+ 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65, 262, 66, 262, 67,
+ 262, 68, 262, 69, 262, 70, 262, 71, 262, 72, 262, 73, 262, 74, 262, 75,
+ 262, 76, 262, 77, 262, 78, 262, 79, 262, 80, 262, 81, 262, 82, 262, 83,
+ 262, 84, 262, 85, 262, 86, 262, 87, 262, 88, 262, 89, 262, 90, 262, 97,
+ 262, 98, 262, 99, 262, 100, 262, 101, 262, 102, 262, 103, 262, 104, 262,
+ 105, 262, 106, 262, 107, 262, 108, 262, 109, 262, 110, 262, 111, 262,
+ 112, 262, 113, 262, 114, 262, 115, 262, 116, 262, 117, 262, 118, 262,
+ 119, 262, 120, 262, 121, 262, 122, 262, 65, 262, 67, 262, 68, 262, 71,
+ 262, 74, 262, 75, 262, 78, 262, 79, 262, 80, 262, 81, 262, 83, 262, 84,
+ 262, 85, 262, 86, 262, 87, 262, 88, 262, 89, 262, 90, 262, 97, 262, 98,
+ 262, 99, 262, 100, 262, 102, 262, 104, 262, 105, 262, 106, 262, 107, 262,
+ 108, 262, 109, 262, 110, 262, 112, 262, 113, 262, 114, 262, 115, 262,
+ 116, 262, 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65,
+ 262, 66, 262, 67, 262, 68, 262, 69, 262, 70, 262, 71, 262, 72, 262, 73,
+ 262, 74, 262, 75, 262, 76, 262, 77, 262, 78, 262, 79, 262, 80, 262, 81,
+ 262, 82, 262, 83, 262, 84, 262, 85, 262, 86, 262, 87, 262, 88, 262, 89,
+ 262, 90, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101, 262, 102, 262,
+ 103, 262, 104, 262, 105, 262, 106, 262, 107, 262, 108, 262, 109, 262,
+ 110, 262, 111, 262, 112, 262, 113, 262, 114, 262, 115, 262, 116, 262,
+ 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65, 262, 66,
+ 262, 68, 262, 69, 262, 70, 262, 71, 262, 74, 262, 75, 262, 76, 262, 77,
+ 262, 78, 262, 79, 262, 80, 262, 81, 262, 83, 262, 84, 262, 85, 262, 86,
+ 262, 87, 262, 88, 262, 89, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101,
+ 262, 102, 262, 103, 262, 104, 262, 105, 262, 106, 262, 107, 262, 108,
+ 262, 109, 262, 110, 262, 111, 262, 112, 262, 113, 262, 114, 262, 115,
+ 262, 116, 262, 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122,
+ 262, 65, 262, 66, 262, 68, 262, 69, 262, 70, 262, 71, 262, 73, 262, 74,
+ 262, 75, 262, 76, 262, 77, 262, 79, 262, 83, 262, 84, 262, 85, 262, 86,
+ 262, 87, 262, 88, 262, 89, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101,
+ 262, 102, 262, 103, 262, 104, 262, 105, 262, 106, 262, 107, 262, 108,
+ 262, 109, 262, 110, 262, 111, 262, 112, 262, 113, 262, 114, 262, 115,
+ 262, 116, 262, 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122,
+ 262, 65, 262, 66, 262, 67, 262, 68, 262, 69, 262, 70, 262, 71, 262, 72,
+ 262, 73, 262, 74, 262, 75, 262, 76, 262, 77, 262, 78, 262, 79, 262, 80,
+ 262, 81, 262, 82, 262, 83, 262, 84, 262, 85, 262, 86, 262, 87, 262, 88,
+ 262, 89, 262, 90, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101, 262,
+ 102, 262, 103, 262, 104, 262, 105, 262, 106, 262, 107, 262, 108, 262,
+ 109, 262, 110, 262, 111, 262, 112, 262, 113, 262, 114, 262, 115, 262,
+ 116, 262, 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65,
+ 262, 66, 262, 67, 262, 68, 262, 69, 262, 70, 262, 71, 262, 72, 262, 73,
+ 262, 74, 262, 75, 262, 76, 262, 77, 262, 78, 262, 79, 262, 80, 262, 81,
+ 262, 82, 262, 83, 262, 84, 262, 85, 262, 86, 262, 87, 262, 88, 262, 89,
+ 262, 90, 262, 97, 262, 98, 262, 99, 262, 100, 262, 101, 262, 102, 262,
+ 103, 262, 104, 262, 105, 262, 106, 262, 107, 262, 108, 262, 109, 262,
+ 110, 262, 111, 262, 112, 262, 113, 262, 114, 262, 115, 262, 116, 262,
+ 117, 262, 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65, 262, 66,
+ 262, 67, 262, 68, 262, 69, 262, 70, 262, 71, 262, 72, 262, 73, 262, 74,
+ 262, 75, 262, 76, 262, 77, 262, 78, 262, 79, 262, 80, 262, 81, 262, 82,
+ 262, 83, 262, 84, 262, 85, 262, 86, 262, 87, 262, 88, 262, 89, 262, 90,
+ 262, 97, 262, 98, 262, 99, 262, 100, 262, 101, 262, 102, 262, 103, 262,
+ 104, 262, 105, 262, 106, 262, 107, 262, 108, 262, 109, 262, 110, 262,
+ 111, 262, 112, 262, 113, 262, 114, 262, 115, 262, 116, 262, 117, 262,
+ 118, 262, 119, 262, 120, 262, 121, 262, 122, 262, 65, 262, 66, 262, 67,
+ 262, 68, 262, 69, 262, 70, 262, 71, 262, 72, 262, 73, 262, 74, 262, 75,
+ 262, 76, 262, 77, 262, 78, 262, 79, 262, 80, 262, 81, 262, 82, 262, 83,
+ 262, 84, 262, 85, 262, 86, 262, 87, 262, 88, 262, 89, 262, 90, 262, 97,
+ 262, 98, 262, 99, 262, 100, 262, 101, 262, 102, 262, 103, 262, 104, 262,
+ 105, 262, 106, 262, 107, 262, 108, 262, 109, 262, 110, 262, 111, 262,
+ 112, 262, 113, 262, 114, 262, 115, 262, 116, 262, 117, 262, 118, 262,
+ 119, 262, 120, 262, 121, 262, 122, 262, 65, 262, 66, 262, 67, 262, 68,
+ 262, 69, 262, 70, 262, 71, 262, 72, 262, 73, 262, 74, 262, 75, 262, 76,
+ 262, 77, 262, 78, 262, 79, 262, 80, 262, 81, 262, 82, 262, 83, 262, 84,
+ 262, 85, 262, 86, 262, 87, 262, 88, 262, 89, 262, 90, 262, 97, 262, 98,
+ 262, 99, 262, 100, 262, 101, 262, 102, 262, 103, 262, 104, 262, 105, 262,
+ 106, 262, 107, 262, 108, 262, 109, 262, 110, 262, 111, 262, 112, 262,
+ 113, 262, 114, 262, 115, 262, 116, 262, 117, 262, 118, 262, 119, 262,
+ 120, 262, 121, 262, 122, 262, 65, 262, 66, 262, 67, 262, 68, 262, 69,
+ 262, 70, 262, 71, 262, 72, 262, 73, 262, 74, 262, 75, 262, 76, 262, 77,
+ 262, 78, 262, 79, 262, 80, 262, 81, 262, 82, 262, 83, 262, 84, 262, 85,
+ 262, 86, 262, 87, 262, 88, 262, 89, 262, 90, 262, 97, 262, 98, 262, 99,
+ 262, 100, 262, 101, 262, 102, 262, 103, 262, 104, 262, 105, 262, 106,
+ 262, 107, 262, 108, 262, 109, 262, 110, 262, 111, 262, 112, 262, 113,
+ 262, 114, 262, 115, 262, 116, 262, 117, 262, 118, 262, 119, 262, 120,
+ 262, 121, 262, 122, 262, 305, 262, 567, 262, 913, 262, 914, 262, 915,
+ 262, 916, 262, 917, 262, 918, 262, 919, 262, 920, 262, 921, 262, 922,
+ 262, 923, 262, 924, 262, 925, 262, 926, 262, 927, 262, 928, 262, 929,
+ 262, 1012, 262, 931, 262, 932, 262, 933, 262, 934, 262, 935, 262, 936,
+ 262, 937, 262, 8711, 262, 945, 262, 946, 262, 947, 262, 948, 262, 949,
+ 262, 950, 262, 951, 262, 952, 262, 953, 262, 954, 262, 955, 262, 956,
+ 262, 957, 262, 958, 262, 959, 262, 960, 262, 961, 262, 962, 262, 963,
+ 262, 964, 262, 965, 262, 966, 262, 967, 262, 968, 262, 969, 262, 8706,
+ 262, 1013, 262, 977, 262, 1008, 262, 981, 262, 1009, 262, 982, 262, 913,
+ 262, 914, 262, 915, 262, 916, 262, 917, 262, 918, 262, 919, 262, 920,
+ 262, 921, 262, 922, 262, 923, 262, 924, 262, 925, 262, 926, 262, 927,
+ 262, 928, 262, 929, 262, 1012, 262, 931, 262, 932, 262, 933, 262, 934,
+ 262, 935, 262, 936, 262, 937, 262, 8711, 262, 945, 262, 946, 262, 947,
+ 262, 948, 262, 949, 262, 950, 262, 951, 262, 952, 262, 953, 262, 954,
+ 262, 955, 262, 956, 262, 957, 262, 958, 262, 959, 262, 960, 262, 961,
+ 262, 962, 262, 963, 262, 964, 262, 965, 262, 966, 262, 967, 262, 968,
+ 262, 969, 262, 8706, 262, 1013, 262, 977, 262, 1008, 262, 981, 262, 1009,
+ 262, 982, 262, 913, 262, 914, 262, 915, 262, 916, 262, 917, 262, 918,
+ 262, 919, 262, 920, 262, 921, 262, 922, 262, 923, 262, 924, 262, 925,
+ 262, 926, 262, 927, 262, 928, 262, 929, 262, 1012, 262, 931, 262, 932,
+ 262, 933, 262, 934, 262, 935, 262, 936, 262, 937, 262, 8711, 262, 945,
+ 262, 946, 262, 947, 262, 948, 262, 949, 262, 950, 262, 951, 262, 952,
+ 262, 953, 262, 954, 262, 955, 262, 956, 262, 957, 262, 958, 262, 959,
+ 262, 960, 262, 961, 262, 962, 262, 963, 262, 964, 262, 965, 262, 966,
+ 262, 967, 262, 968, 262, 969, 262, 8706, 262, 1013, 262, 977, 262, 1008,
+ 262, 981, 262, 1009, 262, 982, 262, 913, 262, 914, 262, 915, 262, 916,
+ 262, 917, 262, 918, 262, 919, 262, 920, 262, 921, 262, 922, 262, 923,
+ 262, 924, 262, 925, 262, 926, 262, 927, 262, 928, 262, 929, 262, 1012,
+ 262, 931, 262, 932, 262, 933, 262, 934, 262, 935, 262, 936, 262, 937,
+ 262, 8711, 262, 945, 262, 946, 262, 947, 262, 948, 262, 949, 262, 950,
+ 262, 951, 262, 952, 262, 953, 262, 954, 262, 955, 262, 956, 262, 957,
+ 262, 958, 262, 959, 262, 960, 262, 961, 262, 962, 262, 963, 262, 964,
+ 262, 965, 262, 966, 262, 967, 262, 968, 262, 969, 262, 8706, 262, 1013,
+ 262, 977, 262, 1008, 262, 981, 262, 1009, 262, 982, 262, 913, 262, 914,
+ 262, 915, 262, 916, 262, 917, 262, 918, 262, 919, 262, 920, 262, 921,
+ 262, 922, 262, 923, 262, 924, 262, 925, 262, 926, 262, 927, 262, 928,
+ 262, 929, 262, 1012, 262, 931, 262, 932, 262, 933, 262, 934, 262, 935,
+ 262, 936, 262, 937, 262, 8711, 262, 945, 262, 946, 262, 947, 262, 948,
+ 262, 949, 262, 950, 262, 951, 262, 952, 262, 953, 262, 954, 262, 955,
+ 262, 956, 262, 957, 262, 958, 262, 959, 262, 960, 262, 961, 262, 962,
+ 262, 963, 262, 964, 262, 965, 262, 966, 262, 967, 262, 968, 262, 969,
+ 262, 8706, 262, 1013, 262, 977, 262, 1008, 262, 981, 262, 1009, 262, 982,
+ 262, 988, 262, 989, 262, 48, 262, 49, 262, 50, 262, 51, 262, 52, 262, 53,
+ 262, 54, 262, 55, 262, 56, 262, 57, 262, 48, 262, 49, 262, 50, 262, 51,
+ 262, 52, 262, 53, 262, 54, 262, 55, 262, 56, 262, 57, 262, 48, 262, 49,
+ 262, 50, 262, 51, 262, 52, 262, 53, 262, 54, 262, 55, 262, 56, 262, 57,
+ 262, 48, 262, 49, 262, 50, 262, 51, 262, 52, 262, 53, 262, 54, 262, 55,
+ 262, 56, 262, 57, 262, 48, 262, 49, 262, 50, 262, 51, 262, 52, 262, 53,
+ 262, 54, 262, 55, 262, 56, 262, 57, 262, 1575, 262, 1576, 262, 1580, 262,
+ 1583, 262, 1608, 262, 1586, 262, 1581, 262, 1591, 262, 1610, 262, 1603,
+ 262, 1604, 262, 1605, 262, 1606, 262, 1587, 262, 1593, 262, 1601, 262,
+ 1589, 262, 1602, 262, 1585, 262, 1588, 262, 1578, 262, 1579, 262, 1582,
+ 262, 1584, 262, 1590, 262, 1592, 262, 1594, 262, 1646, 262, 1722, 262,
+ 1697, 262, 1647, 262, 1576, 262, 1580, 262, 1607, 262, 1581, 262, 1610,
+ 262, 1603, 262, 1604, 262, 1605, 262, 1606, 262, 1587, 262, 1593, 262,
+ 1601, 262, 1589, 262, 1602, 262, 1588, 262, 1578, 262, 1579, 262, 1582,
+ 262, 1590, 262, 1594, 262, 1580, 262, 1581, 262, 1610, 262, 1604, 262,
+ 1606, 262, 1587, 262, 1593, 262, 1589, 262, 1602, 262, 1588, 262, 1582,
+ 262, 1590, 262, 1594, 262, 1722, 262, 1647, 262, 1576, 262, 1580, 262,
+ 1607, 262, 1581, 262, 1591, 262, 1610, 262, 1603, 262, 1605, 262, 1606,
+ 262, 1587, 262, 1593, 262, 1601, 262, 1589, 262, 1602, 262, 1588, 262,
+ 1578, 262, 1579, 262, 1582, 262, 1590, 262, 1592, 262, 1594, 262, 1646,
+ 262, 1697, 262, 1575, 262, 1576, 262, 1580, 262, 1583, 262, 1607, 262,
+ 1608, 262, 1586, 262, 1581, 262, 1591, 262, 1610, 262, 1604, 262, 1605,
+ 262, 1606, 262, 1587, 262, 1593, 262, 1601, 262, 1589, 262, 1602, 262,
+ 1585, 262, 1588, 262, 1578, 262, 1579, 262, 1582, 262, 1584, 262, 1590,
+ 262, 1592, 262, 1594, 262, 1576, 262, 1580, 262, 1583, 262, 1608, 262,
+ 1586, 262, 1581, 262, 1591, 262, 1610, 262, 1604, 262, 1605, 262, 1606,
+ 262, 1587, 262, 1593, 262, 1601, 262, 1589, 262, 1602, 262, 1585, 262,
+ 1588, 262, 1578, 262, 1579, 262, 1582, 262, 1584, 262, 1590, 262, 1592,
+ 262, 1594, 514, 48, 46, 514, 48, 44, 514, 49, 44, 514, 50, 44, 514, 51,
+ 44, 514, 52, 44, 514, 53, 44, 514, 54, 44, 514, 55, 44, 514, 56, 44, 514,
+ 57, 44, 770, 40, 65, 41, 770, 40, 66, 41, 770, 40, 67, 41, 770, 40, 68,
+ 41, 770, 40, 69, 41, 770, 40, 70, 41, 770, 40, 71, 41, 770, 40, 72, 41,
+ 770, 40, 73, 41, 770, 40, 74, 41, 770, 40, 75, 41, 770, 40, 76, 41, 770,
+ 40, 77, 41, 770, 40, 78, 41, 770, 40, 79, 41, 770, 40, 80, 41, 770, 40,
+ 81, 41, 770, 40, 82, 41, 770, 40, 83, 41, 770, 40, 84, 41, 770, 40, 85,
+ 41, 770, 40, 86, 41, 770, 40, 87, 41, 770, 40, 88, 41, 770, 40, 89, 41,
+ 770, 40, 90, 41, 770, 12308, 83, 12309, 263, 67, 263, 82, 519, 67, 68,
+ 519, 87, 90, 266, 65, 266, 66, 266, 67, 266, 68, 266, 69, 266, 70, 266,
+ 71, 266, 72, 266, 73, 266, 74, 266, 75, 266, 76, 266, 77, 266, 78, 266,
+ 79, 266, 80, 266, 81, 266, 82, 266, 83, 266, 84, 266, 85, 266, 86, 266,
+ 87, 266, 88, 266, 89, 266, 90, 522, 72, 86, 522, 77, 86, 522, 83, 68,
+ 522, 83, 83, 778, 80, 80, 86, 522, 87, 67, 515, 77, 67, 515, 77, 68, 522,
+ 68, 74, 522, 12411, 12363, 522, 12467, 12467, 266, 12469, 266, 25163,
+ 266, 23383, 266, 21452, 266, 12487, 266, 20108, 266, 22810, 266, 35299,
+ 266, 22825, 266, 20132, 266, 26144, 266, 28961, 266, 26009, 266, 21069,
+ 266, 24460, 266, 20877, 266, 26032, 266, 21021, 266, 32066, 266, 29983,
+ 266, 36009, 266, 22768, 266, 21561, 266, 28436, 266, 25237, 266, 25429,
+ 266, 19968, 266, 19977, 266, 36938, 266, 24038, 266, 20013, 266, 21491,
+ 266, 25351, 266, 36208, 266, 25171, 266, 31105, 266, 31354, 266, 21512,
+ 266, 28288, 266, 26377, 266, 26376, 266, 30003, 266, 21106, 266, 21942,
+ 770, 12308, 26412, 12309, 770, 12308, 19977, 12309, 770, 12308, 20108,
+ 12309, 770, 12308, 23433, 12309, 770, 12308, 28857, 12309, 770, 12308,
+ 25171, 12309, 770, 12308, 30423, 12309, 770, 12308, 21213, 12309, 770,
+ 12308, 25943, 12309, 263, 24471, 263, 21487, 256, 20029, 256, 20024, 256,
+ 20033, 256, 55360, 56610, 256, 20320, 256, 20398, 256, 20411, 256, 20482,
+ 256, 20602, 256, 20633, 256, 20711, 256, 20687, 256, 13470, 256, 55361,
+ 56890, 256, 20813, 256, 20820, 256, 20836, 256, 20855, 256, 55361, 56604,
+ 256, 13497, 256, 20839, 256, 20877, 256, 55361, 56651, 256, 20887, 256,
+ 20900, 256, 20172, 256, 20908, 256, 20917, 256, 55396, 56799, 256, 20981,
+ 256, 20995, 256, 13535, 256, 21051, 256, 21062, 256, 21106, 256, 21111,
+ 256, 13589, 256, 21191, 256, 21193, 256, 21220, 256, 21242, 256, 21253,
+ 256, 21254, 256, 21271, 256, 21321, 256, 21329, 256, 21338, 256, 21363,
+ 256, 21373, 256, 21375, 256, 21375, 256, 21375, 256, 55362, 56876, 256,
+ 28784, 256, 21450, 256, 21471, 256, 55362, 57187, 256, 21483, 256, 21489,
+ 256, 21510, 256, 21662, 256, 21560, 256, 21576, 256, 21608, 256, 21666,
+ 256, 21750, 256, 21776, 256, 21843, 256, 21859, 256, 21892, 256, 21892,
+ 256, 21913, 256, 21931, 256, 21939, 256, 21954, 256, 22294, 256, 22022,
+ 256, 22295, 256, 22097, 256, 22132, 256, 20999, 256, 22766, 256, 22478,
+ 256, 22516, 256, 22541, 256, 22411, 256, 22578, 256, 22577, 256, 22700,
+ 256, 55365, 56548, 256, 22770, 256, 22775, 256, 22790, 256, 22810, 256,
+ 22818, 256, 22882, 256, 55365, 57000, 256, 55365, 57066, 256, 23020, 256,
+ 23067, 256, 23079, 256, 23000, 256, 23142, 256, 14062, 256, 14076, 256,
+ 23304, 256, 23358, 256, 23358, 256, 55366, 56776, 256, 23491, 256, 23512,
+ 256, 23527, 256, 23539, 256, 55366, 57112, 256, 23551, 256, 23558, 256,
+ 24403, 256, 23586, 256, 14209, 256, 23648, 256, 23662, 256, 23744, 256,
+ 23693, 256, 55367, 56804, 256, 23875, 256, 55367, 56806, 256, 23918, 256,
+ 23915, 256, 23932, 256, 24033, 256, 24034, 256, 14383, 256, 24061, 256,
+ 24104, 256, 24125, 256, 24169, 256, 14434, 256, 55368, 56707, 256, 14460,
+ 256, 24240, 256, 24243, 256, 24246, 256, 24266, 256, 55400, 57234, 256,
+ 24318, 256, 55368, 57137, 256, 55368, 57137, 256, 33281, 256, 24354, 256,
+ 24354, 256, 14535, 256, 55372, 57016, 256, 55384, 56794, 256, 24418, 256,
+ 24427, 256, 14563, 256, 24474, 256, 24525, 256, 24535, 256, 24569, 256,
+ 24705, 256, 14650, 256, 14620, 256, 24724, 256, 55369, 57044, 256, 24775,
+ 256, 24904, 256, 24908, 256, 24910, 256, 24908, 256, 24954, 256, 24974,
+ 256, 25010, 256, 24996, 256, 25007, 256, 25054, 256, 25074, 256, 25078,
+ 256, 25104, 256, 25115, 256, 25181, 256, 25265, 256, 25300, 256, 25424,
+ 256, 55370, 57100, 256, 25405, 256, 25340, 256, 25448, 256, 25475, 256,
+ 25572, 256, 55370, 57329, 256, 25634, 256, 25541, 256, 25513, 256, 14894,
+ 256, 25705, 256, 25726, 256, 25757, 256, 25719, 256, 14956, 256, 25935,
+ 256, 25964, 256, 55372, 56330, 256, 26083, 256, 26360, 256, 26185, 256,
+ 15129, 256, 26257, 256, 15112, 256, 15076, 256, 20882, 256, 20885, 256,
+ 26368, 256, 26268, 256, 32941, 256, 17369, 256, 26391, 256, 26395, 256,
+ 26401, 256, 26462, 256, 26451, 256, 55372, 57283, 256, 15177, 256, 26618,
+ 256, 26501, 256, 26706, 256, 26757, 256, 55373, 56429, 256, 26766, 256,
+ 26655, 256, 26900, 256, 15261, 256, 26946, 256, 27043, 256, 27114, 256,
+ 27304, 256, 55373, 56995, 256, 27355, 256, 15384, 256, 27425, 256, 55374,
+ 56487, 256, 27476, 256, 15438, 256, 27506, 256, 27551, 256, 27578, 256,
+ 27579, 256, 55374, 56973, 256, 55367, 56587, 256, 55374, 57082, 256,
+ 27726, 256, 55375, 56508, 256, 27839, 256, 27853, 256, 27751, 256, 27926,
+ 256, 27966, 256, 28023, 256, 27969, 256, 28009, 256, 28024, 256, 28037,
+ 256, 55375, 56606, 256, 27956, 256, 28207, 256, 28270, 256, 15667, 256,
+ 28363, 256, 28359, 256, 55375, 57041, 256, 28153, 256, 28526, 256, 55375,
+ 57182, 256, 55375, 57230, 256, 28614, 256, 28729, 256, 28702, 256, 28699,
+ 256, 15766, 256, 28746, 256, 28797, 256, 28791, 256, 28845, 256, 55361,
+ 56613, 256, 28997, 256, 55376, 56931, 256, 29084, 256, 55376, 57259, 256,
+ 29224, 256, 29237, 256, 29264, 256, 55377, 56840, 256, 29312, 256, 29333,
+ 256, 55377, 57141, 256, 55378, 56340, 256, 29562, 256, 29579, 256, 16044,
+ 256, 29605, 256, 16056, 256, 16056, 256, 29767, 256, 29788, 256, 29809,
+ 256, 29829, 256, 29898, 256, 16155, 256, 29988, 256, 55379, 56374, 256,
+ 30014, 256, 55379, 56466, 256, 30064, 256, 55368, 56735, 256, 30224, 256,
+ 55379, 57249, 256, 55379, 57272, 256, 55380, 56388, 256, 16380, 256,
+ 16392, 256, 30452, 256, 55380, 56563, 256, 55380, 56562, 256, 55380,
+ 56601, 256, 55380, 56627, 256, 30494, 256, 30495, 256, 30495, 256, 30538,
+ 256, 16441, 256, 30603, 256, 16454, 256, 16534, 256, 55381, 56349, 256,
+ 30798, 256, 30860, 256, 30924, 256, 16611, 256, 55381, 56870, 256, 31062,
+ 256, 55381, 56986, 256, 55381, 57029, 256, 31119, 256, 31211, 256, 16687,
+ 256, 31296, 256, 31306, 256, 31311, 256, 55382, 56700, 256, 55382, 56999,
+ 256, 55382, 56999, 256, 31470, 256, 16898, 256, 55382, 57259, 256, 31686,
+ 256, 31689, 256, 16935, 256, 55383, 56448, 256, 31954, 256, 17056, 256,
+ 31976, 256, 31971, 256, 32000, 256, 55383, 57222, 256, 32099, 256, 17153,
+ 256, 32199, 256, 32258, 256, 32325, 256, 17204, 256, 55384, 56872, 256,
+ 55384, 56903, 256, 17241, 256, 55384, 57049, 256, 32634, 256, 55384,
+ 57150, 256, 32661, 256, 32762, 256, 32773, 256, 55385, 56538, 256, 55385,
+ 56611, 256, 32864, 256, 55385, 56744, 256, 32880, 256, 55372, 57183, 256,
+ 17365, 256, 32946, 256, 33027, 256, 17419, 256, 33086, 256, 23221, 256,
+ 55385, 57255, 256, 55385, 57269, 256, 55372, 57235, 256, 55372, 57244,
+ 256, 33281, 256, 33284, 256, 36766, 256, 17515, 256, 33425, 256, 33419,
+ 256, 33437, 256, 21171, 256, 33457, 256, 33459, 256, 33469, 256, 33510,
+ 256, 55386, 57148, 256, 33509, 256, 33565, 256, 33635, 256, 33709, 256,
+ 33571, 256, 33725, 256, 33767, 256, 33879, 256, 33619, 256, 33738, 256,
+ 33740, 256, 33756, 256, 55387, 56374, 256, 55387, 56683, 256, 55387,
+ 56533, 256, 17707, 256, 34033, 256, 34035, 256, 34070, 256, 55388, 57290,
+ 256, 34148, 256, 55387, 57132, 256, 17757, 256, 17761, 256, 55387, 57265,
+ 256, 55388, 56530, 256, 17771, 256, 34384, 256, 34396, 256, 34407, 256,
+ 34409, 256, 34473, 256, 34440, 256, 34574, 256, 34530, 256, 34681, 256,
+ 34600, 256, 34667, 256, 34694, 256, 17879, 256, 34785, 256, 34817, 256,
+ 17913, 256, 34912, 256, 34915, 256, 55389, 56935, 256, 35031, 256, 35038,
+ 256, 17973, 256, 35066, 256, 13499, 256, 55390, 56494, 256, 55390, 56678,
+ 256, 18110, 256, 18119, 256, 35488, 256, 35565, 256, 35722, 256, 35925,
+ 256, 55391, 56488, 256, 36011, 256, 36033, 256, 36123, 256, 36215, 256,
+ 55391, 57135, 256, 55362, 56324, 256, 36299, 256, 36284, 256, 36336, 256,
+ 55362, 56542, 256, 36564, 256, 36664, 256, 55393, 56786, 256, 55393,
+ 56813, 256, 37012, 256, 37105, 256, 37137, 256, 55393, 57134, 256, 37147,
+ 256, 37432, 256, 37591, 256, 37592, 256, 37500, 256, 37881, 256, 37909,
+ 256, 55394, 57338, 256, 38283, 256, 18837, 256, 38327, 256, 55395, 56695,
+ 256, 18918, 256, 38595, 256, 23986, 256, 38691, 256, 55396, 56645, 256,
+ 55396, 56858, 256, 19054, 256, 19062, 256, 38880, 256, 55397, 56330, 256,
+ 19122, 256, 55397, 56470, 256, 38923, 256, 38923, 256, 38953, 256, 55397,
+ 56758, 256, 39138, 256, 19251, 256, 39209, 256, 39335, 256, 39362, 256,
+ 39422, 256, 19406, 256, 55398, 57136, 256, 39698, 256, 40000, 256, 40189,
+ 256, 19662, 256, 19693, 256, 40295, 256, 55400, 56526, 256, 19704, 256,
+ 55400, 56581, 256, 55400, 56846, 256, 55400, 56977, 256, 40635, 256,
+ 19798, 256, 40697, 256, 40702, 256, 40709, 256, 40719, 256, 40726, 256,
+ 40763, 256, 55401, 56832,
+};
+
+/* index tables for the decomposition data */
+#define DECOMP_SHIFT1 6
+#define DECOMP_SHIFT2 4
+static const unsigned char decomp_index0[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 13, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 14, 15, 5, 5, 5, 5, 16, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 17, 18,
+ 5, 5, 5, 5, 5, 19, 20, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 21, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+};
+
+static const unsigned short decomp_index1[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 0, 0, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 0, 0, 0, 0, 0, 0, 0,
+ 25, 0, 26, 27, 0, 0, 0, 0, 0, 28, 0, 0, 29, 30, 31, 32, 33, 34, 35, 0,
+ 36, 37, 38, 0, 39, 0, 40, 0, 41, 0, 0, 0, 0, 42, 43, 44, 45, 0, 0, 0, 0,
+ 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 48, 0, 0, 0,
+ 0, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 53, 0, 0, 0, 0,
+ 0, 0, 54, 55, 0, 0, 0, 0, 0, 56, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 58, 59, 0, 0, 0, 60, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
+ 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0,
+ 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 67, 0, 68, 0, 0, 69, 0, 0, 0, 70,
+ 71, 72, 73, 74, 75, 76, 77, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 81, 0,
+ 82, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 84, 85, 86, 87, 88, 89, 0, 90, 91, 92, 0, 0, 0, 0,
+ 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
+ 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122,
+ 123, 124, 125, 126, 127, 128, 129, 130, 0, 131, 132, 133, 134, 0, 0, 0,
+ 0, 0, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 0, 146, 0,
+ 0, 0, 147, 0, 148, 149, 150, 0, 151, 152, 153, 0, 154, 0, 0, 0, 155, 0,
+ 0, 0, 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157,
+ 158, 159, 160, 161, 162, 163, 164, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 0,
+ 0, 0, 0, 0, 0, 167, 0, 0, 0, 0, 0, 168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 171, 0, 0, 0, 0, 0, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181,
+ 182, 183, 184, 185, 186, 0, 0, 187, 0, 0, 188, 189, 190, 191, 192, 0,
+ 193, 194, 195, 196, 197, 0, 198, 0, 0, 0, 199, 200, 201, 202, 203, 204,
+ 205, 0, 0, 0, 0, 0, 0, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215,
+ 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
+ 230, 231, 232, 233, 234, 235, 236, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 0, 0, 0,
+ 0, 0, 0, 0, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240,
+ 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268,
+ 269, 0, 0, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 0,
+ 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295,
+ 296, 297, 298, 299, 300, 301, 302, 303, 304, 0, 305, 306, 307, 308, 309,
+ 310, 311, 312, 0, 0, 313, 0, 314, 0, 315, 316, 317, 318, 319, 320, 321,
+ 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335,
+ 336, 337, 338, 339, 340, 341, 342, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 343,
+ 344, 0, 0, 0, 0, 0, 0, 0, 345, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 346, 347, 0, 0, 0, 0, 348, 349, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363,
+ 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
+ 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391,
+ 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405,
+ 406, 407, 408, 409, 410, 411, 412, 413, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 414, 415,
+ 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 426, 427, 428, 429, 430, 0, 431, 0, 0, 432, 0, 0, 0, 0, 0, 0,
+ 433, 434, 435, 436, 437, 438, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 439, 440, 441, 442, 443, 444, 445,
+ 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459,
+ 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,
+};
+
+static const unsigned short decomp_index2[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 3, 0, 6, 0, 0, 0, 0, 8, 0, 0, 11, 13, 15, 18, 0, 0, 20, 23, 25, 0, 27,
+ 31, 35, 0, 39, 42, 45, 48, 51, 54, 0, 57, 60, 63, 66, 69, 72, 75, 78, 81,
+ 0, 84, 87, 90, 93, 96, 99, 0, 0, 102, 105, 108, 111, 114, 0, 0, 117, 120,
+ 123, 126, 129, 132, 0, 135, 138, 141, 144, 147, 150, 153, 156, 159, 0,
+ 162, 165, 168, 171, 174, 177, 0, 0, 180, 183, 186, 189, 192, 0, 195, 198,
+ 201, 204, 207, 210, 213, 216, 219, 222, 225, 228, 231, 234, 237, 240,
+ 243, 0, 0, 246, 249, 252, 255, 258, 261, 264, 267, 270, 273, 276, 279,
+ 282, 285, 288, 291, 294, 297, 300, 303, 0, 0, 306, 309, 312, 315, 318,
+ 321, 324, 327, 330, 0, 333, 336, 339, 342, 345, 348, 0, 351, 354, 357,
+ 360, 363, 366, 369, 372, 0, 0, 375, 378, 381, 384, 387, 390, 393, 0, 0,
+ 396, 399, 402, 405, 408, 411, 0, 0, 414, 417, 420, 423, 426, 429, 432,
+ 435, 438, 441, 444, 447, 450, 453, 456, 459, 462, 465, 0, 0, 468, 471,
+ 474, 477, 480, 483, 486, 489, 492, 495, 498, 501, 504, 507, 510, 513,
+ 516, 519, 522, 525, 528, 531, 534, 537, 539, 542, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 545, 548, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 551, 554, 557, 560, 563, 566, 569, 572, 575, 578, 581, 584, 587,
+ 590, 593, 596, 599, 602, 605, 608, 611, 614, 617, 620, 623, 0, 626, 629,
+ 632, 635, 638, 641, 0, 0, 644, 647, 650, 653, 656, 659, 662, 665, 668,
+ 671, 674, 677, 680, 683, 686, 689, 0, 0, 692, 695, 698, 701, 704, 707,
+ 710, 713, 716, 719, 722, 725, 728, 731, 734, 737, 740, 743, 746, 749,
+ 752, 755, 758, 761, 764, 767, 770, 773, 776, 779, 782, 785, 788, 791,
+ 794, 797, 0, 0, 800, 803, 0, 0, 0, 0, 0, 0, 806, 809, 812, 815, 818, 821,
+ 824, 827, 830, 833, 836, 839, 842, 845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 848, 850, 852, 854, 856, 858, 860, 862, 864, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 866, 869, 872, 875, 878, 881, 0, 0, 884, 886, 888,
+ 890, 892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 894, 896, 0, 898, 900, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 903, 0, 0, 0, 0, 0, 905, 0, 0, 0,
+ 908, 0, 0, 0, 0, 0, 910, 913, 916, 919, 921, 924, 927, 0, 930, 0, 933,
+ 936, 939, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 942, 945, 948, 951, 954, 957, 960, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 963, 966, 969, 972, 975,
+ 0, 978, 980, 982, 984, 987, 990, 992, 0, 0, 0, 0, 0, 0, 0, 0, 0, 994,
+ 996, 998, 0, 1000, 1002, 0, 0, 0, 1004, 0, 0, 0, 0, 0, 0, 1006, 1009, 0,
+ 1012, 0, 0, 0, 1015, 0, 0, 0, 0, 1018, 1021, 1024, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1030, 0, 0,
+ 0, 0, 0, 0, 1033, 1036, 0, 1039, 0, 0, 0, 1042, 0, 0, 0, 0, 1045, 1048,
+ 1051, 0, 0, 0, 0, 0, 0, 0, 1054, 1057, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1060,
+ 1063, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1066, 1069, 1072, 1075, 0,
+ 0, 1078, 1081, 0, 0, 1084, 1087, 1090, 1093, 1096, 1099, 0, 0, 1102,
+ 1105, 1108, 1111, 1114, 1117, 0, 0, 1120, 1123, 1126, 1129, 1132, 1135,
+ 1138, 1141, 1144, 1147, 1150, 1153, 0, 0, 1156, 1159, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1162, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1165, 1168,
+ 1171, 1174, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1180, 1183,
+ 1186, 1189, 0, 0, 0, 0, 0, 0, 0, 1192, 0, 1195, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1198, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1201, 0, 0, 0, 0, 0, 0, 0, 1204, 0, 0, 1207, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1210, 1213, 1216,
+ 1219, 1222, 1225, 1228, 1231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1234,
+ 1237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1240, 1243, 0, 1246,
+ 0, 0, 0, 1249, 0, 0, 1252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1255, 1258, 1261, 0, 0, 1264, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1267,
+ 0, 0, 1270, 1273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1276,
+ 1279, 0, 0, 0, 0, 0, 0, 1282, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1285, 1288, 1291, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1294, 0, 0, 0, 0, 0, 0, 0, 1297, 0, 0, 0, 0, 0, 0, 1300, 1303, 0, 1306,
+ 1309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1312, 1315, 1318, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1321, 0, 1324, 1327, 1330, 0, 0, 0, 0,
+ 1333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1336, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1339, 1342, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1345, 0, 0, 0, 0, 0, 0, 1347, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1350, 0, 0, 0, 0, 1353, 0, 0, 0, 0, 1356, 0, 0,
+ 0, 0, 1359, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1362, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1365, 0, 1368, 1371, 1374, 1377, 1380, 0, 0, 0, 0, 0, 0, 0,
+ 1383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1386, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1389, 0, 0, 0, 0, 1392, 0, 0, 0, 0, 1395, 0, 0, 0, 0,
+ 1398, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1401, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1404, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1407, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1409, 0, 1412, 0, 1415, 0,
+ 1418, 0, 1421, 0, 0, 0, 1424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1427, 0, 1430, 0, 0, 1433, 1436, 0, 1439,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1442, 1444, 1446, 0, 1448, 1450, 1452, 1454, 1456, 1458, 1460, 1462,
+ 1464, 1466, 1468, 0, 1470, 1472, 1474, 1476, 1478, 1480, 1482, 1484,
+ 1486, 1488, 1490, 1492, 1494, 1496, 1498, 1500, 1502, 1504, 0, 1506,
+ 1508, 1510, 1512, 1514, 1516, 1518, 1520, 1522, 1524, 1526, 1528, 1530,
+ 1532, 1534, 1536, 1538, 1540, 1542, 1544, 1546, 1548, 1550, 1552, 1554,
+ 1556, 1558, 1560, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1562, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1564, 1566, 1568, 1570,
+ 1572, 1574, 1576, 1578, 1580, 1582, 1584, 1586, 1588, 1590, 1592, 1594,
+ 1596, 1598, 1600, 1602, 1604, 1606, 1608, 1610, 1612, 1614, 1616, 1618,
+ 1620, 1622, 1624, 1626, 1628, 1630, 1632, 1634, 1636, 1638, 1641, 1644,
+ 1647, 1650, 1653, 1656, 1659, 1662, 1665, 1668, 1671, 1674, 1677, 1680,
+ 1683, 1686, 1689, 1692, 1695, 1698, 1701, 1704, 1707, 1710, 1713, 1716,
+ 1719, 1722, 1725, 1728, 1731, 1734, 1737, 1740, 1743, 1746, 1749, 1752,
+ 1755, 1758, 1761, 1764, 1767, 1770, 1773, 1776, 1779, 1782, 1785, 1788,
+ 1791, 1794, 1797, 1800, 1803, 1806, 1809, 1812, 1815, 1818, 1821, 1824,
+ 1827, 1830, 1833, 1836, 1839, 1842, 1845, 1848, 1851, 1854, 1857, 1860,
+ 1863, 1866, 1869, 1872, 1875, 1878, 1881, 1884, 1887, 1890, 1893, 1896,
+ 1899, 1902, 1905, 1908, 1911, 1914, 1917, 1920, 1923, 1926, 1929, 1932,
+ 1935, 1938, 1941, 1944, 1947, 1950, 1953, 1956, 1959, 1962, 1965, 1968,
+ 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004,
+ 2007, 2010, 2013, 2016, 2019, 2022, 2025, 2028, 2031, 2034, 2037, 2040,
+ 2043, 2046, 2049, 2052, 2055, 2058, 2061, 2064, 2067, 2070, 2073, 2076,
+ 2079, 2082, 2085, 2088, 2091, 2094, 2097, 2100, 2103, 0, 0, 0, 0, 2106,
+ 2109, 2112, 2115, 2118, 2121, 2124, 2127, 2130, 2133, 2136, 2139, 2142,
+ 2145, 2148, 2151, 2154, 2157, 2160, 2163, 2166, 2169, 2172, 2175, 2178,
+ 2181, 2184, 2187, 2190, 2193, 2196, 2199, 2202, 2205, 2208, 2211, 2214,
+ 2217, 2220, 2223, 2226, 2229, 2232, 2235, 2238, 2241, 2244, 2247, 2250,
+ 2253, 2256, 2259, 2262, 2265, 2268, 2271, 2274, 2277, 2280, 2283, 2286,
+ 2289, 2292, 2295, 2298, 2301, 2304, 2307, 2310, 2313, 2316, 2319, 2322,
+ 2325, 2328, 2331, 2334, 2337, 2340, 2343, 2346, 2349, 2352, 2355, 2358,
+ 2361, 2364, 2367, 2370, 2373, 0, 0, 0, 0, 0, 0, 2376, 2379, 2382, 2385,
+ 2388, 2391, 2394, 2397, 2400, 2403, 2406, 2409, 2412, 2415, 2418, 2421,
+ 2424, 2427, 2430, 2433, 2436, 2439, 0, 0, 2442, 2445, 2448, 2451, 2454,
+ 2457, 0, 0, 2460, 2463, 2466, 2469, 2472, 2475, 2478, 2481, 2484, 2487,
+ 2490, 2493, 2496, 2499, 2502, 2505, 2508, 2511, 2514, 2517, 2520, 2523,
+ 2526, 2529, 2532, 2535, 2538, 2541, 2544, 2547, 2550, 2553, 2556, 2559,
+ 2562, 2565, 2568, 2571, 0, 0, 2574, 2577, 2580, 2583, 2586, 2589, 0, 0,
+ 2592, 2595, 2598, 2601, 2604, 2607, 2610, 2613, 0, 2616, 0, 2619, 0,
+ 2622, 0, 2625, 2628, 2631, 2634, 2637, 2640, 2643, 2646, 2649, 2652,
+ 2655, 2658, 2661, 2664, 2667, 2670, 2673, 2676, 2679, 2681, 2684, 2686,
+ 2689, 2691, 2694, 2696, 2699, 2701, 2704, 2706, 2709, 0, 0, 2711, 2714,
+ 2717, 2720, 2723, 2726, 2729, 2732, 2735, 2738, 2741, 2744, 2747, 2750,
+ 2753, 2756, 2759, 2762, 2765, 2768, 2771, 2774, 2777, 2780, 2783, 2786,
+ 2789, 2792, 2795, 2798, 2801, 2804, 2807, 2810, 2813, 2816, 2819, 2822,
+ 2825, 2828, 2831, 2834, 2837, 2840, 2843, 2846, 2849, 2852, 2855, 2858,
+ 2861, 2864, 2867, 0, 2870, 2873, 2876, 2879, 2882, 2885, 2887, 2890,
+ 2893, 2895, 2898, 2901, 2904, 2907, 2910, 0, 2913, 2916, 2919, 2922,
+ 2924, 2927, 2929, 2932, 2935, 2938, 2941, 2944, 2947, 2950, 0, 0, 2952,
+ 2955, 2958, 2961, 2964, 2967, 0, 2969, 2972, 2975, 2978, 2981, 2984,
+ 2987, 2989, 2992, 2995, 2998, 3001, 3004, 3007, 3010, 3012, 3015, 3018,
+ 3020, 0, 0, 3022, 3025, 3028, 0, 3031, 3034, 3037, 3040, 3042, 3045,
+ 3047, 3050, 3052, 0, 3055, 3057, 3059, 3061, 3063, 3065, 3067, 3069,
+ 3071, 3073, 3075, 0, 0, 0, 0, 0, 0, 3077, 0, 0, 0, 0, 0, 3079, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3082, 3084, 3087, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3091, 0, 0, 0, 3093, 3096, 0, 3100, 3103, 0, 0, 0, 0, 3107, 0, 3110, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3113, 3116, 3119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3122, 0, 0, 0, 0, 0, 0, 0, 3127, 3129, 3131, 0, 0, 3133, 3135,
+ 3137, 3139, 3141, 3143, 3145, 3147, 3149, 3151, 3153, 3155, 3157, 3159,
+ 3161, 3163, 3165, 3167, 3169, 3171, 3173, 3175, 3177, 3179, 3181, 3183,
+ 3185, 0, 3187, 3189, 3191, 3193, 3195, 3197, 3199, 3201, 3203, 3205,
+ 3207, 3209, 3211, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3213, 0, 0, 0, 0, 0,
+ 0, 0, 3216, 3220, 3224, 3226, 0, 3229, 3233, 3237, 0, 3239, 3242, 3244,
+ 3246, 3248, 3250, 3252, 3254, 3256, 3258, 3260, 0, 3262, 3264, 0, 0,
+ 3267, 3269, 3271, 3273, 3275, 0, 0, 3277, 3280, 3284, 0, 3287, 0, 3289,
+ 0, 3291, 0, 3293, 3295, 3297, 3299, 0, 3301, 3303, 3305, 0, 3307, 3309,
+ 3311, 3313, 3315, 3317, 3319, 0, 3321, 3325, 3327, 3329, 3331, 3333, 0,
+ 0, 0, 0, 3335, 3337, 3339, 3341, 3343, 0, 0, 0, 0, 0, 0, 3345, 3349,
+ 3353, 3358, 3362, 3366, 3370, 3374, 3378, 3382, 3386, 3390, 3394, 3398,
+ 3402, 3406, 3409, 3411, 3414, 3418, 3421, 3423, 3426, 3430, 3435, 3438,
+ 3440, 3443, 3447, 3449, 3451, 3453, 3455, 3457, 3460, 3464, 3467, 3469,
+ 3472, 3476, 3481, 3484, 3486, 3489, 3493, 3495, 3497, 3499, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 3501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3505, 3508, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3511,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3514, 3517, 3520, 0, 0, 0, 0,
+ 3523, 0, 0, 0, 0, 3526, 0, 0, 3529, 0, 0, 0, 0, 0, 0, 0, 3532, 0, 3535,
+ 0, 0, 0, 0, 0, 3538, 3541, 0, 3545, 3548, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3552, 0, 0, 3555, 0, 0, 3558, 0, 3561, 0, 0, 0, 0, 0,
+ 0, 3564, 0, 3567, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3570, 3573, 3576, 3579,
+ 3582, 0, 0, 3585, 3588, 0, 0, 3591, 3594, 0, 0, 0, 0, 0, 0, 3597, 3600,
+ 0, 0, 3603, 3606, 0, 0, 3609, 3612, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3615, 3618, 3621, 3624, 3627, 3630, 3633, 3636, 0, 0,
+ 0, 0, 0, 0, 3639, 3642, 3645, 3648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3651, 3653, 0, 0, 0, 0, 0, 3655, 3657, 3659, 3661, 3663, 3665, 3667,
+ 3669, 3671, 3673, 3676, 3679, 3682, 3685, 3688, 3691, 3694, 3697, 3700,
+ 3703, 3706, 3710, 3714, 3718, 3722, 3726, 3730, 3734, 3738, 3742, 3747,
+ 3752, 3757, 3762, 3767, 3772, 3777, 3782, 3787, 3792, 3797, 3800, 3803,
+ 3806, 3809, 3812, 3815, 3818, 3821, 3824, 3828, 3832, 3836, 3840, 3844,
+ 3848, 3852, 3856, 3860, 3864, 3868, 3872, 3876, 3880, 3884, 3888, 3892,
+ 3896, 3900, 3904, 3908, 3912, 3916, 3920, 3924, 3928, 3932, 3936, 3940,
+ 3944, 3948, 3952, 3956, 3960, 3964, 3968, 3972, 3974, 3976, 3978, 3980,
+ 3982, 3984, 3986, 3988, 3990, 3992, 3994, 3996, 3998, 4000, 4002, 4004,
+ 4006, 4008, 4010, 4012, 4014, 4016, 4018, 4020, 4022, 4024, 4026, 4028,
+ 4030, 4032, 4034, 4036, 4038, 4040, 4042, 4044, 4046, 4048, 4050, 4052,
+ 4054, 4056, 4058, 4060, 4062, 4064, 4066, 4068, 4070, 4072, 4074, 4076,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4078, 0, 0, 0, 0, 0,
+ 0, 0, 4083, 4087, 4090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 4094, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4097,
+ 4099, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4101, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4103, 0, 0, 0, 4105, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 4107, 4109, 4111, 4113, 4115, 4117, 4119, 4121,
+ 4123, 4125, 4127, 4129, 4131, 4133, 4135, 4137, 4139, 4141, 4143, 4145,
+ 4147, 4149, 4151, 4153, 4155, 4157, 4159, 4161, 4163, 4165, 4167, 4169,
+ 4171, 4173, 4175, 4177, 4179, 4181, 4183, 4185, 4187, 4189, 4191, 4193,
+ 4195, 4197, 4199, 4201, 4203, 4205, 4207, 4209, 4211, 4213, 4215, 4217,
+ 4219, 4221, 4223, 4225, 4227, 4229, 4231, 4233, 4235, 4237, 4239, 4241,
+ 4243, 4245, 4247, 4249, 4251, 4253, 4255, 4257, 4259, 4261, 4263, 4265,
+ 4267, 4269, 4271, 4273, 4275, 4277, 4279, 4281, 4283, 4285, 4287, 4289,
+ 4291, 4293, 4295, 4297, 4299, 4301, 4303, 4305, 4307, 4309, 4311, 4313,
+ 4315, 4317, 4319, 4321, 4323, 4325, 4327, 4329, 4331, 4333, 4335, 4337,
+ 4339, 4341, 4343, 4345, 4347, 4349, 4351, 4353, 4355, 4357, 4359, 4361,
+ 4363, 4365, 4367, 4369, 4371, 4373, 4375, 4377, 4379, 4381, 4383, 4385,
+ 4387, 4389, 4391, 4393, 4395, 4397, 4399, 4401, 4403, 4405, 4407, 4409,
+ 4411, 4413, 4415, 4417, 4419, 4421, 4423, 4425, 4427, 4429, 4431, 4433,
+ 4435, 4437, 4439, 4441, 4443, 4445, 4447, 4449, 4451, 4453, 4455, 4457,
+ 4459, 4461, 4463, 4465, 4467, 4469, 4471, 4473, 4475, 4477, 4479, 4481,
+ 4483, 4485, 4487, 4489, 4491, 4493, 4495, 4497, 4499, 4501, 4503, 4505,
+ 4507, 4509, 4511, 4513, 4515, 4517, 4519, 4521, 4523, 4525, 4527, 4529,
+ 4531, 4533, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4535, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4537, 0, 4539, 4541, 4543, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4545, 0, 4548, 0, 4551, 0,
+ 4554, 0, 4557, 0, 4560, 0, 4563, 0, 4566, 0, 4569, 0, 4572, 0, 4575, 0,
+ 4578, 0, 0, 4581, 0, 4584, 0, 4587, 0, 0, 0, 0, 0, 0, 4590, 4593, 0,
+ 4596, 4599, 0, 4602, 4605, 0, 4608, 4611, 0, 4614, 4617, 0, 0, 0, 0, 0,
+ 0, 4620, 0, 0, 0, 0, 0, 0, 4623, 4626, 0, 4629, 4632, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 4635, 0, 4638, 0, 4641, 0, 4644, 0, 4647, 0, 4650, 0,
+ 4653, 0, 4656, 0, 4659, 0, 4662, 0, 4665, 0, 4668, 0, 0, 4671, 0, 4674,
+ 0, 4677, 0, 0, 0, 0, 0, 0, 4680, 4683, 0, 4686, 4689, 0, 4692, 4695, 0,
+ 4698, 4701, 0, 4704, 4707, 0, 0, 0, 0, 0, 0, 4710, 0, 0, 4713, 4716,
+ 4719, 4722, 0, 0, 0, 4725, 4728, 0, 4731, 4733, 4735, 4737, 4739, 4741,
+ 4743, 4745, 4747, 4749, 4751, 4753, 4755, 4757, 4759, 4761, 4763, 4765,
+ 4767, 4769, 4771, 4773, 4775, 4777, 4779, 4781, 4783, 4785, 4787, 4789,
+ 4791, 4793, 4795, 4797, 4799, 4801, 4803, 4805, 4807, 4809, 4811, 4813,
+ 4815, 4817, 4819, 4821, 4823, 4825, 4827, 4829, 4831, 4833, 4835, 4837,
+ 4839, 4841, 4843, 4845, 4847, 4849, 4851, 4853, 4855, 4857, 4859, 4861,
+ 4863, 4865, 4867, 4869, 4871, 4873, 4875, 4877, 4879, 4881, 4883, 4885,
+ 4887, 4889, 4891, 4893, 4895, 4897, 4899, 4901, 4903, 4905, 4907, 4909,
+ 4911, 4913, 4915, 4917, 0, 0, 0, 4919, 4921, 4923, 4925, 4927, 4929,
+ 4931, 4933, 4935, 4937, 4939, 4941, 4943, 4945, 4947, 4951, 4955, 4959,
+ 4963, 4967, 4971, 4975, 4979, 4983, 4987, 4991, 4995, 4999, 5003, 5008,
+ 5013, 5018, 5023, 5028, 5033, 5038, 5043, 5048, 5053, 5058, 5063, 5068,
+ 5073, 5078, 5086, 0, 5093, 5097, 5101, 5105, 5109, 5113, 5117, 5121,
+ 5125, 5129, 5133, 5137, 5141, 5145, 5149, 5153, 5157, 5161, 5165, 5169,
+ 5173, 5177, 5181, 5185, 5189, 5193, 5197, 5201, 5205, 5209, 5213, 5217,
+ 5221, 5225, 5229, 5233, 5237, 5239, 5241, 5243, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5245, 5249, 5252, 5255, 5258, 5261, 5264, 5267, 5270, 5273, 5276, 5279,
+ 5282, 5285, 5288, 5291, 5294, 5296, 5298, 5300, 5302, 5304, 5306, 5308,
+ 5310, 5312, 5314, 5316, 5318, 5320, 5322, 5325, 5328, 5331, 5334, 5337,
+ 5340, 5343, 5346, 5349, 5352, 5355, 5358, 5361, 5364, 5370, 5375, 0,
+ 5378, 5380, 5382, 5384, 5386, 5388, 5390, 5392, 5394, 5396, 5398, 5400,
+ 5402, 5404, 5406, 5408, 5410, 5412, 5414, 5416, 5418, 5420, 5422, 5424,
+ 5426, 5428, 5430, 5432, 5434, 5436, 5438, 5440, 5442, 5444, 5446, 5448,
+ 5450, 5452, 5454, 5456, 5458, 5460, 5462, 5464, 5466, 5468, 5470, 5472,
+ 5474, 5476, 5479, 5482, 5485, 5488, 5491, 5494, 5497, 5500, 5503, 5506,
+ 5509, 5512, 5515, 5518, 5521, 5524, 5527, 5530, 5533, 5536, 5539, 5542,
+ 5545, 5548, 5552, 5556, 5560, 5563, 5567, 5570, 5574, 5576, 5578, 5580,
+ 5582, 5584, 5586, 5588, 5590, 5592, 5594, 5596, 5598, 5600, 5602, 5604,
+ 5606, 5608, 5610, 5612, 5614, 5616, 5618, 5620, 5622, 5624, 5626, 5628,
+ 5630, 5632, 5634, 5636, 5638, 5640, 5642, 5644, 5646, 5648, 5650, 5652,
+ 5654, 5656, 5658, 5660, 5662, 5664, 5666, 0, 5668, 5673, 5678, 5683,
+ 5687, 5692, 5696, 5700, 5706, 5711, 5715, 5719, 5723, 5728, 5733, 5737,
+ 5741, 5744, 5748, 5753, 5758, 5761, 5767, 5774, 5780, 5784, 5790, 5796,
+ 5801, 5805, 5809, 5813, 5818, 5824, 5829, 5833, 5837, 5841, 5844, 5847,
+ 5850, 5853, 5857, 5861, 5867, 5871, 5876, 5882, 5886, 5889, 5892, 5898,
+ 5903, 5909, 5913, 5919, 5922, 5926, 5930, 5934, 5938, 5942, 5947, 5951,
+ 5954, 5958, 5962, 5966, 5971, 5975, 5979, 5983, 5989, 5994, 5997, 6003,
+ 6006, 6011, 6016, 6020, 6024, 6028, 6033, 6036, 6040, 6045, 6048, 6054,
+ 6058, 6061, 6064, 6067, 6070, 6073, 6076, 6079, 6082, 6085, 6088, 6092,
+ 6096, 6100, 6104, 6108, 6112, 6116, 6120, 6124, 6128, 6132, 6136, 6140,
+ 6144, 6148, 6152, 6155, 6158, 6162, 6165, 6168, 6171, 6175, 6179, 6182,
+ 6185, 6188, 6191, 6194, 6199, 6202, 6205, 6208, 6211, 6214, 6217, 6220,
+ 6223, 6227, 6232, 6235, 6238, 6241, 6244, 6247, 6250, 6253, 6257, 6261,
+ 6265, 6269, 6272, 6275, 6278, 6281, 6284, 6287, 6290, 6293, 6296, 6299,
+ 6303, 6307, 6310, 6314, 6318, 6322, 6325, 6329, 6333, 6338, 6341, 6345,
+ 6349, 6353, 6357, 6363, 6370, 6373, 6376, 6379, 6382, 6385, 6388, 6391,
+ 6394, 6397, 6400, 6403, 6406, 6409, 6412, 6415, 6418, 6421, 6424, 6429,
+ 6432, 6435, 6438, 6443, 6447, 6450, 6453, 6456, 6459, 6462, 6465, 6468,
+ 6471, 6474, 6477, 6481, 6484, 6487, 6491, 6495, 6498, 6503, 6507, 6510,
+ 6513, 6516, 6519, 6523, 6527, 6530, 6533, 6536, 6539, 6542, 6545, 6548,
+ 6551, 6554, 6558, 6562, 6566, 6570, 6574, 6578, 6582, 6586, 6590, 6594,
+ 6598, 6602, 6606, 6610, 6614, 6618, 6622, 6626, 6630, 6634, 6638, 6642,
+ 6646, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 6648, 6650, 0, 0, 0, 0, 0, 0, 6652, 6654, 6656, 6658, 6660, 6662,
+ 6664, 6666, 6668, 6670, 6672, 6674, 6676, 6678, 6680, 6682, 6684, 6686,
+ 6688, 6690, 6692, 6694, 6696, 6698, 6700, 6702, 6704, 6706, 6708, 6710,
+ 6712, 6714, 6716, 6718, 6720, 6722, 6724, 6726, 6728, 6730, 6732, 6734,
+ 6736, 6738, 6740, 6742, 6744, 6746, 6748, 6750, 6752, 6754, 6756, 6758,
+ 6760, 6762, 6764, 6766, 6768, 6770, 6772, 6774, 6776, 6778, 6780, 6782,
+ 6784, 6786, 6788, 6790, 6792, 6794, 6796, 6798, 6800, 6802, 6804, 6806,
+ 6808, 6810, 6812, 6814, 6816, 6818, 6820, 6822, 6824, 6826, 6828, 6830,
+ 6832, 6834, 6836, 6838, 6840, 6842, 6844, 6846, 6848, 6850, 6852, 6854,
+ 6856, 6858, 6860, 6862, 6864, 6866, 6868, 6870, 6872, 6874, 6876, 6878,
+ 6880, 6882, 6884, 6886, 6888, 6890, 6892, 6894, 6896, 6898, 6900, 6902,
+ 6904, 6906, 6908, 6910, 6912, 6914, 6916, 6918, 6920, 6922, 6924, 6926,
+ 6928, 6930, 6932, 6934, 6936, 6938, 6940, 6942, 6944, 6946, 6948, 6950,
+ 6952, 6954, 6956, 6958, 6960, 6962, 6964, 6966, 6968, 6970, 6972, 6974,
+ 6976, 6978, 6980, 6982, 6984, 6986, 6988, 6990, 6992, 6994, 6996, 6998,
+ 7000, 7002, 7004, 7006, 7008, 7010, 7012, 7014, 7016, 7018, 7020, 7022,
+ 7024, 7026, 7028, 7030, 7032, 7034, 7036, 7038, 7040, 7042, 7044, 7046,
+ 7048, 7050, 7052, 7054, 7056, 7058, 7060, 7062, 7064, 7066, 7068, 7070,
+ 7072, 7074, 7076, 7078, 7080, 7082, 7084, 7086, 7088, 7090, 7092, 7094,
+ 7096, 7098, 7100, 7102, 7104, 7106, 7108, 7110, 7112, 7114, 7116, 7118,
+ 7120, 7122, 7124, 7126, 7128, 7130, 7132, 7134, 7136, 7138, 7140, 7142,
+ 7144, 7146, 7148, 7150, 7152, 7154, 7156, 7158, 7160, 7162, 7164, 7166,
+ 7168, 7170, 7172, 7174, 7176, 7178, 7180, 7182, 7184, 7186, 7188, 7190,
+ 0, 0, 7192, 0, 7194, 0, 0, 7196, 7198, 7200, 7202, 7204, 7206, 7208,
+ 7210, 7212, 7214, 0, 7216, 0, 7218, 0, 0, 7220, 7222, 0, 0, 0, 7224,
+ 7226, 7228, 7230, 7232, 7234, 7236, 7238, 7240, 7242, 7244, 7246, 7248,
+ 7250, 7252, 7254, 7256, 7258, 7260, 7262, 7264, 7266, 7268, 7270, 7272,
+ 7274, 7276, 7278, 7280, 7282, 7284, 7286, 7288, 7290, 7292, 7294, 7296,
+ 7298, 7300, 7302, 7304, 7306, 7308, 7310, 7312, 7314, 7316, 7318, 7320,
+ 7322, 7324, 7326, 7328, 7330, 7332, 7334, 7336, 7338, 7340, 7342, 7344,
+ 7346, 7348, 7350, 7352, 7354, 7356, 7359, 0, 0, 7361, 7363, 7365, 7367,
+ 7369, 7371, 7373, 7375, 7377, 7379, 7381, 7383, 7385, 7387, 7389, 7391,
+ 7393, 7395, 7397, 7399, 7401, 7403, 7405, 7407, 7409, 7411, 7413, 7415,
+ 7417, 7419, 7421, 7423, 7425, 7427, 7429, 7431, 7433, 7435, 7437, 7439,
+ 7441, 7443, 7445, 7447, 7449, 7451, 7453, 7455, 7457, 7459, 7461, 7463,
+ 7465, 7467, 7469, 7471, 7473, 7475, 7477, 7479, 7481, 7483, 7485, 7487,
+ 7489, 7491, 7493, 7495, 7497, 7499, 7501, 7503, 7505, 7507, 7509, 7511,
+ 7513, 7515, 7517, 7519, 7521, 7523, 7525, 7527, 7529, 7531, 7533, 7535,
+ 7537, 7539, 7541, 7543, 7545, 7547, 7549, 7551, 7554, 7557, 7560, 7562,
+ 7564, 7566, 7569, 7572, 7575, 7577, 0, 0, 0, 0, 0, 0, 7579, 7582, 7585,
+ 7588, 7592, 7596, 7599, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7602, 7605,
+ 7608, 7611, 7614, 0, 0, 0, 0, 0, 7617, 0, 7620, 7623, 7625, 7627, 7629,
+ 7631, 7633, 7635, 7637, 7639, 7641, 7643, 7646, 7649, 7652, 7655, 7658,
+ 7661, 7664, 7667, 7670, 7673, 7676, 7679, 0, 7682, 7685, 7688, 7691,
+ 7694, 0, 7697, 0, 7700, 7703, 0, 7706, 7709, 0, 7712, 7715, 7718, 7721,
+ 7724, 7727, 7730, 7733, 7736, 7739, 7742, 7744, 7746, 7748, 7750, 7752,
+ 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7776,
+ 7778, 7780, 7782, 7784, 7786, 7788, 7790, 7792, 7794, 7796, 7798, 7800,
+ 7802, 7804, 7806, 7808, 7810, 7812, 7814, 7816, 7818, 7820, 7822, 7824,
+ 7826, 7828, 7830, 7832, 7834, 7836, 7838, 7840, 7842, 7844, 7846, 7848,
+ 7850, 7852, 7854, 7856, 7858, 7860, 7862, 7864, 7866, 7868, 7870, 7872,
+ 7874, 7876, 7878, 7880, 7882, 7884, 7886, 7888, 7890, 7892, 7894, 7896,
+ 7898, 7900, 7902, 7904, 7906, 7908, 7910, 7912, 7914, 7916, 7918, 7920,
+ 7922, 7924, 7926, 7928, 7930, 7932, 7934, 7936, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 7938, 7940, 7942, 7944, 7946, 7948, 7950,
+ 7952, 7954, 7956, 7958, 7960, 7962, 7964, 7966, 7968, 7970, 7972, 7974,
+ 7976, 7978, 7980, 7982, 7984, 7987, 7990, 7993, 7996, 7999, 8002, 8005,
+ 8008, 8011, 8014, 8017, 8020, 8023, 8026, 8029, 8032, 8035, 8038, 8040,
+ 8042, 8044, 8046, 8049, 8052, 8055, 8058, 8061, 8064, 8067, 8070, 8073,
+ 8076, 8079, 8082, 8085, 8088, 8091, 8094, 8097, 8100, 8103, 8106, 8109,
+ 8112, 8115, 8118, 8121, 8124, 8127, 8130, 8133, 8136, 8139, 8142, 8145,
+ 8148, 8151, 8154, 8157, 8160, 8163, 8166, 8169, 8172, 8175, 8178, 8181,
+ 8184, 8187, 8190, 8193, 8196, 8199, 8202, 8205, 8208, 8211, 8214, 8217,
+ 8220, 8223, 8226, 8229, 8232, 8235, 8238, 8241, 8244, 8247, 8250, 8253,
+ 8256, 8259, 8262, 8265, 8268, 8271, 8274, 8277, 8280, 8283, 8286, 8289,
+ 8292, 8295, 8298, 8301, 8304, 8307, 8310, 8313, 8316, 8319, 8322, 8325,
+ 8328, 8332, 8336, 8340, 8344, 8348, 8352, 8355, 8358, 8361, 8364, 8367,
+ 8370, 8373, 8376, 8379, 8382, 8385, 8388, 8391, 8394, 8397, 8400, 8403,
+ 8406, 8409, 8412, 8415, 8418, 8421, 8424, 8427, 8430, 8433, 8436, 8439,
+ 8442, 8445, 8448, 8451, 8454, 8457, 8460, 8463, 8466, 8469, 8472, 8475,
+ 8478, 8481, 8484, 8487, 8490, 8493, 8496, 8499, 8502, 8505, 8508, 8511,
+ 8514, 8517, 8520, 8523, 8526, 8529, 8532, 8535, 8538, 8541, 8544, 8547,
+ 8550, 8553, 8556, 8559, 8562, 8565, 8568, 8571, 8574, 8577, 8580, 8583,
+ 8586, 8589, 8592, 8595, 8598, 8601, 8604, 8607, 8610, 8613, 8616, 8619,
+ 8622, 8625, 8628, 8631, 8634, 8637, 8640, 8643, 8646, 8649, 8652, 8655,
+ 8658, 8661, 8664, 8667, 8670, 8673, 8676, 8679, 8682, 8685, 8688, 8691,
+ 8694, 8697, 8700, 8703, 8706, 8709, 8712, 8715, 8718, 8721, 8724, 8727,
+ 8730, 8733, 8736, 8739, 8742, 8745, 8748, 8751, 8754, 8757, 8760, 8763,
+ 8766, 8769, 8772, 8775, 8778, 8782, 8786, 8790, 8793, 8796, 8799, 8802,
+ 8805, 8808, 8811, 8814, 8817, 8820, 8823, 8826, 8829, 8832, 8835, 8838,
+ 8841, 8844, 8847, 8850, 8853, 8856, 8859, 8862, 8865, 8868, 8871, 8874,
+ 8877, 8880, 8883, 8886, 8889, 8892, 8895, 8898, 8901, 8904, 8907, 8910,
+ 8913, 8916, 8919, 8922, 8925, 8928, 8931, 8934, 8937, 8940, 8943, 8946,
+ 8949, 8952, 8955, 8958, 8961, 8964, 8967, 8970, 8973, 8976, 8979, 8982,
+ 8985, 8988, 8991, 8994, 8997, 9000, 9003, 9006, 0, 0, 9009, 9013, 9017,
+ 9021, 9025, 9029, 9033, 9037, 9041, 9045, 9049, 9053, 9057, 9061, 9065,
+ 9069, 9073, 9077, 9081, 9085, 9089, 9093, 9097, 9101, 9105, 9109, 9113,
+ 9117, 9121, 9125, 9129, 9133, 9137, 9141, 9145, 9149, 9153, 9157, 9161,
+ 9165, 9169, 9173, 9177, 9181, 9185, 9189, 9193, 9197, 9201, 9205, 9209,
+ 9213, 9217, 9221, 9225, 9229, 9233, 9237, 9241, 9245, 9249, 9253, 9257,
+ 9261, 0, 0, 9265, 9269, 9273, 9277, 9281, 9285, 9289, 9293, 9297, 9301,
+ 9305, 9309, 9313, 9317, 9321, 9325, 9329, 9333, 9337, 9341, 9345, 9349,
+ 9353, 9357, 9361, 9365, 9369, 9373, 9377, 9381, 9385, 9389, 9393, 9397,
+ 9401, 9405, 9409, 9413, 9417, 9421, 9425, 9429, 9433, 9437, 9441, 9445,
+ 9449, 9453, 9457, 9461, 9465, 9469, 9473, 9477, 0, 0, 0, 0, 0, 0, 0, 0,
+ 9481, 9485, 9489, 9494, 9499, 9504, 9509, 9514, 9519, 9524, 9528, 9547,
+ 9556, 0, 0, 0, 9561, 9563, 9565, 9567, 9569, 9571, 9573, 9575, 9577,
+ 9579, 0, 0, 0, 0, 0, 0, 9581, 9583, 9585, 9587, 9589, 9591, 9593, 9595,
+ 9597, 9599, 9601, 9603, 9605, 9607, 9609, 9611, 9613, 9615, 9617, 9619,
+ 9621, 0, 0, 9623, 9625, 9627, 9629, 9631, 9633, 9635, 9637, 9639, 9641,
+ 9643, 9645, 0, 9647, 9649, 9651, 9653, 9655, 9657, 9659, 9661, 9663,
+ 9665, 9667, 9669, 9671, 9673, 9675, 9677, 9679, 9681, 9683, 0, 9685,
+ 9687, 9689, 9691, 0, 0, 0, 0, 9693, 9696, 9699, 0, 9702, 0, 9705, 9708,
+ 9711, 9714, 9717, 9720, 9723, 9726, 9729, 9732, 9735, 9737, 9739, 9741,
+ 9743, 9745, 9747, 9749, 9751, 9753, 9755, 9757, 9759, 9761, 9763, 9765,
+ 9767, 9769, 9771, 9773, 9775, 9777, 9779, 9781, 9783, 9785, 9787, 9789,
+ 9791, 9793, 9795, 9797, 9799, 9801, 9803, 9805, 9807, 9809, 9811, 9813,
+ 9815, 9817, 9819, 9821, 9823, 9825, 9827, 9829, 9831, 9833, 9835, 9837,
+ 9839, 9841, 9843, 9845, 9847, 9849, 9851, 9853, 9855, 9857, 9859, 9861,
+ 9863, 9865, 9867, 9869, 9871, 9873, 9875, 9877, 9879, 9881, 9883, 9885,
+ 9887, 9889, 9891, 9893, 9895, 9897, 9899, 9901, 9903, 9905, 9907, 9909,
+ 9911, 9913, 9915, 9917, 9919, 9921, 9923, 9925, 9927, 9929, 9931, 9933,
+ 9935, 9937, 9939, 9941, 9943, 9945, 9947, 9949, 9951, 9953, 9955, 9957,
+ 9959, 9961, 9963, 9965, 9967, 9969, 9972, 9975, 9978, 9981, 9984, 9987,
+ 9990, 0, 0, 0, 0, 9993, 9995, 9997, 9999, 10001, 10003, 10005, 10007,
+ 10009, 10011, 10013, 10015, 10017, 10019, 10021, 10023, 10025, 10027,
+ 10029, 10031, 10033, 10035, 10037, 10039, 10041, 10043, 10045, 10047,
+ 10049, 10051, 10053, 10055, 10057, 10059, 10061, 10063, 10065, 10067,
+ 10069, 10071, 10073, 10075, 10077, 10079, 10081, 10083, 10085, 10087,
+ 10089, 10091, 10093, 10095, 10097, 10099, 10101, 10103, 10105, 10107,
+ 10109, 10111, 10113, 10115, 10117, 10119, 10121, 10123, 10125, 10127,
+ 10129, 10131, 10133, 10135, 10137, 10139, 10141, 10143, 10145, 10147,
+ 10149, 10151, 10153, 10155, 10157, 10159, 10161, 10163, 10165, 10167,
+ 10169, 10171, 10173, 10175, 10177, 10179, 10181, 10183, 10185, 10187,
+ 10189, 10191, 10193, 10195, 10197, 10199, 10201, 10203, 10205, 10207,
+ 10209, 10211, 10213, 10215, 10217, 10219, 10221, 10223, 10225, 10227,
+ 10229, 10231, 10233, 10235, 10237, 10239, 10241, 10243, 10245, 10247,
+ 10249, 10251, 10253, 10255, 10257, 10259, 10261, 10263, 10265, 10267,
+ 10269, 10271, 10273, 10275, 10277, 10279, 10281, 10283, 10285, 10287,
+ 10289, 10291, 10293, 10295, 10297, 10299, 10301, 10303, 10305, 10307,
+ 10309, 10311, 10313, 10315, 10317, 10319, 10321, 10323, 10325, 10327,
+ 10329, 10331, 10333, 10335, 10337, 10339, 10341, 10343, 10345, 10347,
+ 10349, 10351, 10353, 10355, 10357, 10359, 10361, 10363, 10365, 10367,
+ 10369, 10371, 0, 0, 0, 10373, 10375, 10377, 10379, 10381, 10383, 0, 0,
+ 10385, 10387, 10389, 10391, 10393, 10395, 0, 0, 10397, 10399, 10401,
+ 10403, 10405, 10407, 0, 0, 10409, 10411, 10413, 0, 0, 0, 10415, 10417,
+ 10419, 10421, 10423, 10425, 10427, 0, 10429, 10431, 10433, 10435, 10437,
+ 10439, 10441, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10443, 0, 10448, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10453, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 10458, 10463, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 10468, 10473, 10478, 10483, 10488, 10493, 10498, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10503, 10508, 10513, 10518,
+ 10523, 10528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10533, 10535,
+ 10537, 10539, 10541, 10543, 10545, 10547, 10549, 10551, 10553, 10555,
+ 10557, 10559, 10561, 10563, 10565, 10567, 10569, 10571, 10573, 10575,
+ 10577, 10579, 10581, 10583, 10585, 10587, 10589, 10591, 10593, 10595,
+ 10597, 10599, 10601, 10603, 10605, 10607, 10609, 10611, 10613, 10615,
+ 10617, 10619, 10621, 10623, 10625, 10627, 10629, 10631, 10633, 10635,
+ 10637, 10639, 10641, 10643, 10645, 10647, 10649, 10651, 10653, 10655,
+ 10657, 10659, 10661, 10663, 10665, 10667, 10669, 10671, 10673, 10675,
+ 10677, 10679, 10681, 10683, 10685, 10687, 10689, 10691, 10693, 10695,
+ 10697, 10699, 10701, 0, 10703, 10705, 10707, 10709, 10711, 10713, 10715,
+ 10717, 10719, 10721, 10723, 10725, 10727, 10729, 10731, 10733, 10735,
+ 10737, 10739, 10741, 10743, 10745, 10747, 10749, 10751, 10753, 10755,
+ 10757, 10759, 10761, 10763, 10765, 10767, 10769, 10771, 10773, 10775,
+ 10777, 10779, 10781, 10783, 10785, 10787, 10789, 10791, 10793, 10795,
+ 10797, 10799, 10801, 10803, 10805, 10807, 10809, 10811, 10813, 10815,
+ 10817, 10819, 10821, 10823, 10825, 10827, 10829, 10831, 10833, 10835,
+ 10837, 10839, 10841, 10843, 0, 10845, 10847, 0, 0, 10849, 0, 0, 10851,
+ 10853, 0, 0, 10855, 10857, 10859, 10861, 0, 10863, 10865, 10867, 10869,
+ 10871, 10873, 10875, 10877, 10879, 10881, 10883, 10885, 0, 10887, 0,
+ 10889, 10891, 10893, 10895, 10897, 10899, 10901, 0, 10903, 10905, 10907,
+ 10909, 10911, 10913, 10915, 10917, 10919, 10921, 10923, 10925, 10927,
+ 10929, 10931, 10933, 10935, 10937, 10939, 10941, 10943, 10945, 10947,
+ 10949, 10951, 10953, 10955, 10957, 10959, 10961, 10963, 10965, 10967,
+ 10969, 10971, 10973, 10975, 10977, 10979, 10981, 10983, 10985, 10987,
+ 10989, 10991, 10993, 10995, 10997, 10999, 11001, 11003, 11005, 11007,
+ 11009, 11011, 11013, 11015, 11017, 11019, 11021, 11023, 11025, 11027,
+ 11029, 11031, 0, 11033, 11035, 11037, 11039, 0, 0, 11041, 11043, 11045,
+ 11047, 11049, 11051, 11053, 11055, 0, 11057, 11059, 11061, 11063, 11065,
+ 11067, 11069, 0, 11071, 11073, 11075, 11077, 11079, 11081, 11083, 11085,
+ 11087, 11089, 11091, 11093, 11095, 11097, 11099, 11101, 11103, 11105,
+ 11107, 11109, 11111, 11113, 11115, 11117, 11119, 11121, 11123, 11125, 0,
+ 11127, 11129, 11131, 11133, 0, 11135, 11137, 11139, 11141, 11143, 0,
+ 11145, 0, 0, 0, 11147, 11149, 11151, 11153, 11155, 11157, 11159, 0,
+ 11161, 11163, 11165, 11167, 11169, 11171, 11173, 11175, 11177, 11179,
+ 11181, 11183, 11185, 11187, 11189, 11191, 11193, 11195, 11197, 11199,
+ 11201, 11203, 11205, 11207, 11209, 11211, 11213, 11215, 11217, 11219,
+ 11221, 11223, 11225, 11227, 11229, 11231, 11233, 11235, 11237, 11239,
+ 11241, 11243, 11245, 11247, 11249, 11251, 11253, 11255, 11257, 11259,
+ 11261, 11263, 11265, 11267, 11269, 11271, 11273, 11275, 11277, 11279,
+ 11281, 11283, 11285, 11287, 11289, 11291, 11293, 11295, 11297, 11299,
+ 11301, 11303, 11305, 11307, 11309, 11311, 11313, 11315, 11317, 11319,
+ 11321, 11323, 11325, 11327, 11329, 11331, 11333, 11335, 11337, 11339,
+ 11341, 11343, 11345, 11347, 11349, 11351, 11353, 11355, 11357, 11359,
+ 11361, 11363, 11365, 11367, 11369, 11371, 11373, 11375, 11377, 11379,
+ 11381, 11383, 11385, 11387, 11389, 11391, 11393, 11395, 11397, 11399,
+ 11401, 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419,
+ 11421, 11423, 11425, 11427, 11429, 11431, 11433, 11435, 11437, 11439,
+ 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, 11457, 11459,
+ 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479,
+ 11481, 11483, 11485, 11487, 11489, 11491, 11493, 11495, 11497, 11499,
+ 11501, 11503, 11505, 11507, 11509, 11511, 11513, 11515, 11517, 11519,
+ 11521, 11523, 11525, 11527, 11529, 11531, 11533, 11535, 11537, 11539,
+ 11541, 11543, 11545, 11547, 11549, 11551, 11553, 11555, 11557, 11559,
+ 11561, 11563, 11565, 11567, 11569, 11571, 11573, 11575, 11577, 11579,
+ 11581, 11583, 11585, 11587, 11589, 11591, 11593, 11595, 11597, 11599,
+ 11601, 11603, 11605, 11607, 11609, 11611, 11613, 11615, 11617, 11619,
+ 11621, 11623, 11625, 11627, 11629, 11631, 11633, 11635, 11637, 11639,
+ 11641, 11643, 11645, 11647, 11649, 11651, 11653, 11655, 11657, 11659,
+ 11661, 11663, 11665, 11667, 11669, 11671, 11673, 11675, 11677, 11679,
+ 11681, 11683, 11685, 11687, 11689, 11691, 11693, 11695, 11697, 11699,
+ 11701, 11703, 11705, 11707, 11709, 11711, 11713, 11715, 11717, 11719,
+ 11721, 11723, 11725, 11727, 11729, 11731, 11733, 11735, 11737, 11739,
+ 11741, 11743, 11745, 11747, 11749, 11751, 11753, 11755, 11757, 11759,
+ 11761, 11763, 11765, 11767, 11769, 11771, 11773, 11775, 11777, 11779,
+ 11781, 11783, 11785, 11787, 11789, 11791, 11793, 11795, 11797, 11799,
+ 11801, 11803, 11805, 11807, 11809, 11811, 11813, 11815, 11817, 11819,
+ 11821, 11823, 11825, 11827, 11829, 11831, 11833, 11835, 11837, 11839, 0,
+ 0, 11841, 11843, 11845, 11847, 11849, 11851, 11853, 11855, 11857, 11859,
+ 11861, 11863, 11865, 11867, 11869, 11871, 11873, 11875, 11877, 11879,
+ 11881, 11883, 11885, 11887, 11889, 11891, 11893, 11895, 11897, 11899,
+ 11901, 11903, 11905, 11907, 11909, 11911, 11913, 11915, 11917, 11919,
+ 11921, 11923, 11925, 11927, 11929, 11931, 11933, 11935, 11937, 11939,
+ 11941, 11943, 11945, 11947, 11949, 11951, 11953, 11955, 11957, 11959,
+ 11961, 11963, 11965, 11967, 11969, 11971, 11973, 11975, 11977, 11979,
+ 11981, 11983, 11985, 11987, 11989, 11991, 11993, 11995, 11997, 11999,
+ 12001, 12003, 12005, 12007, 12009, 12011, 12013, 12015, 12017, 12019,
+ 12021, 12023, 12025, 12027, 12029, 12031, 12033, 12035, 12037, 12039,
+ 12041, 12043, 12045, 12047, 12049, 12051, 12053, 12055, 12057, 12059,
+ 12061, 12063, 12065, 12067, 12069, 12071, 12073, 12075, 12077, 12079,
+ 12081, 12083, 12085, 12087, 12089, 12091, 12093, 12095, 12097, 12099,
+ 12101, 12103, 12105, 12107, 12109, 12111, 12113, 12115, 12117, 12119,
+ 12121, 12123, 12125, 12127, 12129, 12131, 12133, 12135, 12137, 12139,
+ 12141, 12143, 12145, 12147, 12149, 12151, 12153, 12155, 12157, 12159,
+ 12161, 12163, 12165, 12167, 12169, 12171, 12173, 12175, 12177, 12179,
+ 12181, 12183, 12185, 12187, 12189, 12191, 12193, 12195, 12197, 12199,
+ 12201, 12203, 12205, 12207, 12209, 12211, 12213, 12215, 12217, 12219,
+ 12221, 12223, 12225, 12227, 12229, 12231, 12233, 12235, 12237, 12239,
+ 12241, 12243, 12245, 12247, 12249, 12251, 12253, 12255, 12257, 12259,
+ 12261, 12263, 12265, 12267, 12269, 12271, 12273, 12275, 12277, 12279,
+ 12281, 12283, 12285, 12287, 12289, 12291, 12293, 12295, 12297, 12299,
+ 12301, 12303, 12305, 12307, 12309, 12311, 12313, 12315, 12317, 12319,
+ 12321, 12323, 12325, 12327, 12329, 12331, 12333, 12335, 12337, 12339,
+ 12341, 12343, 12345, 12347, 12349, 12351, 12353, 12355, 12357, 12359,
+ 12361, 12363, 12365, 12367, 12369, 12371, 12373, 12375, 12377, 12379,
+ 12381, 12383, 12385, 12387, 12389, 12391, 12393, 12395, 12397, 12399,
+ 12401, 12403, 12405, 12407, 12409, 12411, 12413, 12415, 12417, 12419,
+ 12421, 12423, 0, 0, 12425, 12427, 12429, 12431, 12433, 12435, 12437,
+ 12439, 12441, 12443, 12445, 12447, 12449, 12451, 12453, 12455, 12457,
+ 12459, 12461, 12463, 12465, 12467, 12469, 12471, 12473, 12475, 12477,
+ 12479, 12481, 12483, 12485, 12487, 12489, 12491, 12493, 12495, 12497,
+ 12499, 12501, 12503, 12505, 12507, 12509, 12511, 12513, 12515, 12517,
+ 12519, 12521, 12523, 12525, 12527, 12529, 12531, 0, 12533, 12535, 12537,
+ 12539, 12541, 12543, 12545, 12547, 12549, 12551, 12553, 12555, 12557,
+ 12559, 12561, 12563, 12565, 12567, 12569, 12571, 12573, 12575, 12577,
+ 12579, 12581, 12583, 12585, 0, 12587, 12589, 0, 12591, 0, 0, 12593, 0,
+ 12595, 12597, 12599, 12601, 12603, 12605, 12607, 12609, 12611, 12613, 0,
+ 12615, 12617, 12619, 12621, 0, 12623, 0, 12625, 0, 0, 0, 0, 0, 0, 12627,
+ 0, 0, 0, 0, 12629, 0, 12631, 0, 12633, 0, 12635, 12637, 12639, 0, 12641,
+ 12643, 0, 12645, 0, 0, 12647, 0, 12649, 0, 12651, 0, 12653, 0, 12655, 0,
+ 12657, 12659, 0, 12661, 0, 0, 12663, 12665, 12667, 12669, 0, 12671,
+ 12673, 12675, 12677, 12679, 12681, 12683, 0, 12685, 12687, 12689, 12691,
+ 0, 12693, 12695, 12697, 12699, 0, 12701, 0, 12703, 12705, 12707, 12709,
+ 12711, 12713, 12715, 12717, 12719, 12721, 0, 12723, 12725, 12727, 12729,
+ 12731, 12733, 12735, 12737, 12739, 12741, 12743, 12745, 12747, 12749,
+ 12751, 12753, 12755, 0, 0, 0, 0, 0, 12757, 12759, 12761, 0, 12763, 12765,
+ 12767, 12769, 12771, 0, 12773, 12775, 12777, 12779, 12781, 12783, 12785,
+ 12787, 12789, 12791, 12793, 12795, 12797, 12799, 12801, 12803, 12805, 0,
+ 0, 0, 0, 12807, 12810, 12813, 12816, 12819, 12822, 12825, 12828, 12831,
+ 12834, 12837, 0, 0, 0, 0, 0, 12840, 12844, 12848, 12852, 12856, 12860,
+ 12864, 12868, 12872, 12876, 12880, 12884, 12888, 12892, 12896, 12900,
+ 12904, 12908, 12912, 12916, 12920, 12924, 12928, 12932, 12936, 12940,
+ 12944, 12948, 12950, 12952, 12955, 0, 12958, 12960, 12962, 12964, 12966,
+ 12968, 12970, 12972, 12974, 12976, 12978, 12980, 12982, 12984, 12986,
+ 12988, 12990, 12992, 12994, 12996, 12998, 13000, 13002, 13004, 13006,
+ 13008, 13010, 13013, 13016, 13019, 13022, 13026, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 13029, 13032, 0, 0, 0, 0, 13035, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 13038, 13041, 13044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 13046, 13048, 13050, 13052, 13054, 13056, 13058, 13060, 13062, 13064,
+ 13066, 13068, 13070, 13072, 13074, 13076, 13078, 13080, 13082, 13084,
+ 13086, 13088, 13090, 13092, 13094, 13096, 13098, 13100, 13102, 13104,
+ 13106, 13108, 13110, 13112, 13114, 13116, 13118, 13120, 13122, 13124,
+ 13126, 13128, 13130, 0, 0, 0, 0, 0, 13132, 13136, 13140, 13144, 13148,
+ 13152, 13156, 13160, 13164, 0, 0, 0, 0, 0, 0, 0, 13168, 13170, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13172, 13174, 13176, 13178, 13181,
+ 13183, 13185, 13187, 13189, 13191, 13193, 13195, 13197, 13199, 13202,
+ 13204, 13206, 13208, 13210, 13213, 13215, 13217, 13219, 13222, 13224,
+ 13226, 13228, 13230, 13232, 13235, 13237, 13239, 13241, 13243, 13245,
+ 13247, 13249, 13251, 13253, 13255, 13257, 13259, 13261, 13263, 13265,
+ 13267, 13269, 13271, 13273, 13275, 13277, 13279, 13281, 13284, 13286,
+ 13288, 13290, 13293, 13295, 13297, 13299, 13301, 13303, 13305, 13307,
+ 13309, 13311, 13313, 13315, 13317, 13319, 13321, 13323, 13325, 13327,
+ 13329, 13331, 13333, 13335, 13337, 13339, 13341, 13343, 13345, 13347,
+ 13349, 13351, 13353, 13355, 13357, 13360, 13362, 13364, 13366, 13368,
+ 13370, 13372, 13375, 13378, 13380, 13382, 13384, 13386, 13388, 13390,
+ 13392, 13394, 13396, 13398, 13401, 13403, 13405, 13407, 13409, 13412,
+ 13414, 13416, 13418, 13420, 13422, 13424, 13426, 13428, 13430, 13433,
+ 13435, 13438, 13440, 13442, 13444, 13446, 13448, 13450, 13452, 13454,
+ 13456, 13458, 13460, 13463, 13465, 13467, 13469, 13471, 13473, 13476,
+ 13478, 13481, 13484, 13486, 13488, 13490, 13492, 13495, 13498, 13500,
+ 13502, 13504, 13506, 13508, 13510, 13512, 13514, 13516, 13518, 13520,
+ 13523, 13525, 13527, 13529, 13531, 13533, 13535, 13537, 13539, 13541,
+ 13543, 13545, 13547, 13549, 13551, 13553, 13555, 13557, 13559, 13561,
+ 13564, 13566, 13568, 13570, 13572, 13574, 13577, 13579, 13581, 13583,
+ 13585, 13587, 13589, 13591, 13593, 13595, 13597, 13599, 13602, 13604,
+ 13606, 13608, 13610, 13612, 13614, 13616, 13618, 13620, 13622, 13624,
+ 13626, 13628, 13630, 13632, 13634, 13636, 13638, 13641, 13643, 13645,
+ 13647, 13649, 13651, 13654, 13656, 13658, 13660, 13662, 13664, 13666,
+ 13668, 13670, 13673, 13675, 13677, 13679, 13682, 13684, 13686, 13688,
+ 13690, 13692, 13694, 13697, 13700, 13703, 13705, 13708, 13710, 13712,
+ 13714, 13716, 13718, 13720, 13722, 13724, 13726, 13728, 13731, 13733,
+ 13735, 13737, 13739, 13741, 13743, 13746, 13748, 13750, 13753, 13756,
+ 13758, 13760, 13762, 13764, 13766, 13768, 13770, 13772, 13774, 13777,
+ 13779, 13782, 13784, 13787, 13789, 13791, 13793, 13796, 13798, 13800,
+ 13803, 13806, 13808, 13810, 13812, 13814, 13816, 13818, 13820, 13822,
+ 13824, 13826, 13828, 13830, 13832, 13835, 13837, 13840, 13842, 13845,
+ 13847, 13850, 13853, 13856, 13858, 13860, 13862, 13865, 13868, 13871,
+ 13874, 13876, 13878, 13880, 13882, 13884, 13886, 13888, 13890, 13893,
+ 13895, 13897, 13899, 13901, 13904, 13906, 13909, 13912, 13914, 13916,
+ 13918, 13920, 13922, 13924, 13927, 13930, 13933, 13935, 13937, 13940,
+ 13942, 13944, 13946, 13949, 13951, 13953, 13955, 13957, 13959, 13962,
+ 13964, 13966, 13968, 13970, 13972, 13974, 13977, 13980, 13982, 13985,
+ 13987, 13990, 13992, 13994, 13996, 13999, 14002, 14004, 14007, 14009,
+ 14012, 14014, 14016, 14018, 14020, 14022, 14024, 14027, 14030, 14033,
+ 14036, 14038, 14040, 14042, 14044, 14046, 14048, 14050, 14052, 14054,
+ 14056, 14058, 14060, 14063, 14065, 14067, 14069, 14071, 14073, 14075,
+ 14077, 14079, 14081, 14083, 14085, 14087, 14090, 14093, 14096, 14098,
+ 14100, 14102, 14104, 14107, 14109, 14112, 14114, 14116, 14119, 14122,
+ 14124, 14126, 14128, 14130, 14132, 14134, 14136, 14138, 14140, 14142,
+ 14144, 14146, 14148, 14150, 14152, 14154, 14156, 14158, 14160, 14163,
+ 14165, 14167, 14169, 14171, 14173, 14176, 14179, 14181, 14183, 14185,
+ 14187, 14189, 14191, 14194, 14196, 14198, 14200, 14202, 14205, 14208,
+ 14210, 14212, 14214, 14217, 14219, 14221, 14224, 14227, 14229, 14231,
+ 14233, 14236, 14238, 14240, 14242, 14244, 14246, 14248, 14250, 14253,
+ 14255, 14257, 14259, 14262, 14264, 14266, 14268, 14270, 14273, 14276,
+ 14278, 14280, 14282, 14285, 14287, 14290, 14292, 14294, 14296, 14299,
+ 14301, 14303, 14305, 14307, 14309, 14311, 14313, 14316, 14318, 14320,
+ 14322, 14324, 14326, 14328, 14331, 14333, 14336, 14339, 14342, 14344,
+ 14346, 14348, 14350, 14352, 14354, 14356, 14358, 0, 0,
+};
+
+/* NFC pairs */
+#define COMP_SHIFT1 2
+#define COMP_SHIFT2 1
+static const unsigned short comp_index0[] = {
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4, 5, 6,
+ 0, 0, 0, 0, 7, 0, 8, 9, 0, 0, 0, 10, 11, 12, 0, 0, 0, 0, 13, 14, 15, 16,
+ 0, 0, 0, 17, 18, 19, 20, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 22, 23, 24, 0, 0,
+ 0, 0, 25, 26, 27, 28, 0, 0, 0, 29, 30, 31, 32, 0, 0, 0, 33, 0, 0, 0, 0,
+ 0, 0, 34, 35, 36, 37, 0, 0, 0, 38, 39, 40, 41, 0, 0, 0, 42, 0, 43, 0, 0,
+ 0, 0, 44, 45, 46, 47, 0, 0, 0, 48, 49, 50, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0,
+ 0, 52, 53, 54, 55, 0, 0, 0, 56, 57, 58, 0, 0, 0, 0, 59, 60, 61, 62, 0, 0,
+ 0, 63, 64, 65, 66, 0, 0, 0, 67, 0, 68, 0, 0, 0, 0, 69, 0, 70, 0, 0, 0, 0,
+ 71, 0, 0, 0, 0, 0, 0, 72, 73, 74, 0, 0, 0, 0, 75, 76, 77, 78, 0, 0, 0,
+ 79, 80, 81, 0, 0, 0, 0, 82, 0, 83, 84, 0, 0, 0, 85, 86, 87, 0, 0, 0, 0,
+ 88, 89, 90, 91, 0, 0, 0, 92, 93, 94, 95, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0,
+ 97, 98, 99, 0, 0, 0, 0, 100, 101, 102, 103, 0, 0, 0, 104, 105, 106, 107,
+ 0, 0, 0, 108, 109, 0, 0, 0, 0, 0, 110, 111, 112, 113, 0, 0, 0, 114, 115,
+ 116, 117, 0, 0, 0, 118, 0, 119, 0, 0, 0, 0, 120, 121, 122, 123, 0, 0, 0,
+ 124, 125, 126, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 128, 129, 130, 131, 0,
+ 0, 0, 132, 133, 134, 0, 0, 0, 0, 135, 136, 137, 138, 0, 0, 0, 139, 140,
+ 141, 142, 0, 0, 0, 143, 0, 144, 0, 0, 0, 0, 145, 146, 147, 0, 0, 0, 0,
+ 148, 0, 0, 0, 0, 0, 0, 149, 150, 151, 0, 0, 0, 0, 152, 153, 154, 155, 0,
+ 0, 0, 156, 0, 0, 157, 0, 0, 0, 158, 159, 0, 0, 0, 0, 0, 160, 0, 0, 0, 0,
+ 0, 0, 161, 0, 0, 0, 0, 0, 0, 162, 0, 0, 0, 0, 0, 0, 163, 0, 0, 0, 0, 0,
+ 0, 164, 165, 0, 0, 0, 0, 0, 166, 0, 0, 0, 0, 0, 0, 167, 168, 0, 0, 0, 0,
+ 0, 169, 0, 0, 0, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0,
+ 172, 173, 0, 0, 0, 0, 0, 174, 175, 0, 0, 0, 0, 0, 176, 0, 0, 0, 0, 0, 0,
+ 177, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 0, 0, 0, 179, 0, 0, 0, 0, 0, 0, 180,
+ 181, 0, 0, 0, 0, 0, 182, 0, 0, 0, 0, 0, 0, 183, 184, 0, 0, 0, 0, 0, 185,
+ 0, 0, 0, 0, 0, 0, 186, 0, 0, 0, 0, 0, 0, 187, 0, 0, 0, 0, 0, 0, 188, 189,
+ 0, 0, 0, 0, 0, 190, 191, 0, 0, 0, 0, 0, 192, 193, 0, 0, 0, 0, 0, 194, 0,
+ 0, 0, 0, 0, 0, 195, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 197, 0, 0,
+ 0, 0, 0, 0, 198, 0, 0, 0, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0,
+ 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 0,
+ 0, 0, 204, 0, 0, 0, 0, 0, 0, 205, 0, 0, 0, 0, 0, 0, 206, 0, 0, 0, 0, 0,
+ 0, 207, 208, 209, 0, 0, 0, 0, 210, 211, 212, 0, 0, 0, 0, 213, 214, 215,
+ 0, 0, 0, 0, 216, 217, 218, 0, 0, 0, 0, 0, 219, 0, 0, 0, 0, 0, 220, 0, 0,
+ 0, 0, 0, 0, 221, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0,
+ 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 225, 0, 0, 0, 0, 0, 0, 226, 0, 0, 0, 0,
+ 0, 0, 227, 0, 0, 0, 0, 0, 0, 0, 228, 0, 0, 0, 0, 0, 229, 230, 0, 231, 0,
+ 0, 0, 232, 233, 0, 0, 0, 0, 0, 234, 235, 0, 236, 0, 0, 0, 237, 238, 0, 0,
+ 0, 0, 0, 239, 240, 0, 0, 0, 0, 0, 0, 241, 0, 0, 0, 0, 0, 242, 243, 0, 0,
+ 0, 0, 0, 244, 245, 0, 246, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 248,
+ 0, 0, 0, 249, 250, 0, 251, 0, 0, 0, 252, 253, 0, 0, 0, 0, 0, 254, 255, 0,
+ 256, 0, 0, 0, 257, 258, 0, 259, 0, 0, 0, 260, 261, 0, 0, 0, 0, 0, 0, 262,
+ 0, 0, 0, 0, 0, 263, 264, 0, 265, 0, 0, 0, 266, 267, 0, 268, 0, 0, 0, 269,
+ 0, 0, 270, 0, 0, 0, 271, 0, 0, 272, 0, 0, 0, 0, 0, 0, 273, 0, 0, 0, 274,
+ 0, 0, 0, 0, 0, 0, 275, 0, 0, 0, 0, 0, 0, 276, 0, 0, 0, 0, 0, 0, 277, 0,
+ 0, 0, 0, 0, 0, 278, 0, 0, 0, 0, 0, 0, 279, 0, 0, 0, 0, 0, 0, 280, 0, 0,
+ 0, 0, 0, 0, 281, 0, 0, 0, 0, 0, 0, 282, 0, 0, 0, 0, 0, 0, 283, 0, 0, 0,
+ 0, 0, 0, 284, 285, 0, 0, 0, 0, 0, 286, 0, 0, 0, 0, 0, 0, 287, 0, 0, 0, 0,
+ 0, 0, 288, 0, 0, 0, 0, 0, 0, 289, 0, 0, 0, 0, 0, 0, 290, 0, 0, 0, 0, 0,
+ 0, 291, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 0, 0, 293, 0, 0, 0, 0, 0, 0,
+ 294, 0, 0, 0, 0, 0, 0, 295, 0, 0, 0, 0, 0, 0, 296, 0, 0, 0, 0, 0, 0, 297,
+ 298, 0, 0, 0, 0, 0, 299, 0, 0, 0, 0, 0, 0, 300, 0, 0, 0, 0, 0, 0, 301, 0,
+ 0, 0, 0, 0, 0, 302, 0, 0, 0, 0, 0, 0, 0, 303, 0, 0, 0, 0, 0, 0, 304, 0,
+ 0, 0, 0, 0, 305, 0, 0, 0, 0, 0, 0, 306, 0, 0, 0, 0, 0, 0, 307, 0, 0, 0,
+ 0, 0, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 310, 0, 0, 0, 0, 0, 311, 0,
+ 0, 0, 0, 0, 0, 312, 0, 0, 0, 0, 0, 0, 313, 0, 0, 0, 0, 0, 0, 314, 0, 0,
+ 0, 0, 0, 0, 315, 0, 0, 0, 0, 0, 0, 0, 316, 0, 0, 0, 0, 0, 0, 317, 0, 0,
+ 0, 0, 0, 0, 318, 0, 0, 0, 0, 0, 0, 319, 0, 0, 0, 0, 0, 0, 320, 0, 0, 0,
+ 0, 0, 0, 0, 321, 0, 0, 0, 0, 0, 322, 323, 0, 0, 0, 0, 0, 324, 0, 0, 0, 0,
+ 0, 0, 0, 325, 0, 0, 0, 0, 0, 0, 326, 0, 0, 0, 0, 0, 0, 327, 0, 0, 0, 0,
+ 0, 0, 328, 0, 0, 0, 0, 0, 0, 329, 0, 0, 0, 0, 0, 0, 330, 0, 0, 0, 0, 0,
+ 0, 331, 332, 0, 0, 0, 0, 0, 333, 0, 0, 0, 0, 0, 0, 0, 334, 0, 0, 0, 0, 0,
+ 0, 335, 0, 0, 0, 0, 0, 0, 336, 0, 0, 0, 0, 0, 0, 337, 0, 0, 0, 0, 0, 0,
+ 338, 0, 0, 0, 0, 0, 0, 339, 0, 0, 0, 0, 0, 0, 340, 0, 0, 0, 0, 0, 0, 341,
+ 0, 0, 0, 0, 0, 0, 342, 0, 0, 0, 0, 0, 0, 343, 0, 0, 0, 0, 0, 0, 344, 0,
+ 0, 0, 0, 0, 0, 345, 346, 0, 0, 0, 0, 0, 0, 347, 0, 0, 0, 0, 0, 0, 348, 0,
+ 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 350, 0, 0, 0, 0, 0, 0, 351, 0, 0,
+ 0, 0, 0, 0, 352, 0, 0, 0, 0, 0, 0, 353, 0, 0, 0, 0, 0, 0, 354, 0, 0, 0,
+ 0, 0, 0, 355, 0, 0, 0, 0, 0, 0, 356, 0, 0, 0, 0, 0, 0, 357, 0, 0, 0, 0,
+ 0, 0, 358, 0, 0, 359, 0, 0, 0, 360, 0, 0, 361, 0, 0, 0, 0, 0, 0, 362, 0,
+ 0, 0, 0, 0, 0, 363, 0, 0, 0, 0, 0, 0, 364, 0, 0, 0, 0, 0, 0, 365, 0, 0,
+ 0, 0, 0, 0, 366, 0, 0, 0, 0, 0, 0, 367, 0, 0, 0, 368, 0, 0, 369, 0, 0, 0,
+ 370, 0, 0, 371, 0, 0, 0, 0, 0, 0, 372, 0, 0, 0, 0, 0, 0, 373, 0, 0, 0, 0,
+ 0, 0, 374, 0, 0, 0, 0, 0, 0, 375, 0, 0, 0, 0, 0, 0, 376, 0, 0, 0, 0, 0,
+ 0, 377, 0, 0, 0, 378, 0, 0, 0, 0, 0, 0, 379, 0, 0, 0, 0, 0, 0, 380, 0, 0,
+ 0, 0, 0, 0, 381, 0, 0, 0, 0, 0, 0, 382, 0, 0, 383, 0, 0, 0, 384, 0, 0,
+ 385, 0, 0, 0, 0, 0, 0, 386, 0, 0, 0, 0, 0, 0, 387, 0, 0, 0, 0, 0, 0, 388,
+ 0, 0, 0, 0, 0, 0, 389, 0, 0, 0, 0, 0, 0, 390, 0, 0, 0, 0, 0, 0, 391, 0,
+ 0, 0, 392, 0, 0, 393, 0, 0, 0, 394, 0, 0, 395, 0, 0, 0, 0, 0, 0, 396, 0,
+ 0, 0, 0, 0, 0, 397, 0, 0, 0, 0, 0, 0, 398, 0, 0, 0, 0, 0, 0, 399, 0, 0,
+ 0, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 401, 0, 0, 0, 402, 0, 0, 403, 0, 0, 0,
+ 404, 0, 0, 405, 0, 0, 0, 406, 0, 0, 407, 0, 0, 0, 408, 0, 0, 409, 0, 0,
+ 0, 410, 0, 0, 0, 0, 0, 0, 411, 0, 0, 0, 0, 0, 0, 412, 0, 0, 0, 0, 0, 0,
+ 413, 0, 0, 0, 0, 0, 0, 414, 0, 0, 415, 0, 0, 0, 416, 0, 0, 417, 0, 0, 0,
+ 418, 0, 0, 419, 0, 0, 0, 420, 0, 0, 421, 0, 0, 0, 422, 0, 0, 423, 0, 0,
+ 0, 0, 0, 0, 424, 0, 0, 0, 0, 0, 0, 425, 0, 0, 0, 0, 0, 0, 426, 0, 0, 0,
+ 0, 0, 0, 427, 0, 0, 0, 0, 0, 0, 428, 0, 0, 0, 0, 0, 0, 429, 0, 0, 0, 430,
+ 0, 0, 431, 0, 0, 0, 432, 0, 0, 433, 0, 0, 0, 0, 0, 0, 434, 0, 0, 0, 0, 0,
+ 0, 435, 0, 0, 0, 0, 0, 0, 436, 0, 0, 0, 0, 0, 0, 437, 0, 0, 0, 0, 0, 0,
+ 438, 0, 0, 0, 0, 0, 0, 439, 0, 0, 0, 0, 0, 0, 440, 0, 0, 0, 0, 0, 0, 441,
+ 0, 0, 0, 0, 0, 0, 442, 0, 0, 0, 0, 0, 0, 443, 0, 0, 0, 444, 0, 0, 445, 0,
+ 0, 0, 0, 0, 0, 446, 0, 0, 0, 0, 0, 0, 447, 0, 0, 0, 448, 0, 0, 449, 0, 0,
+ 0, 0, 0, 0, 450, 0, 0, 0, 0, 0, 0, 451, 0, 0, 0, 0, 0, 0, 452, 0, 0, 0,
+ 0, 0, 0, 453, 0, 0, 0, 0, 0, 0, 454, 0, 0, 0, 0, 0, 0, 455, 0, 0, 0, 0,
+ 0, 0, 456, 0, 0, 0, 0, 0, 0, 457, 0, 0, 0, 0, 0, 0, 458, 0, 0, 0, 0, 0,
+ 0, 459, 0, 0, 0, 0, 0, 0, 460, 0, 0, 0, 0, 0, 0, 461, 0, 0, 0, 0, 0, 0,
+ 462, 0, 0, 0, 0, 0, 0, 463, 0, 0, 0, 0, 0, 0, 464, 0, 0, 0, 0, 0, 0, 465,
+ 0, 0, 0, 0, 0, 0, 466, 0, 0, 0, 0, 0, 0, 467, 0, 0, 0, 0, 0, 0, 468, 0,
+ 0, 0, 0, 0, 0, 469, 0, 0, 0, 0, 0, 0, 470, 0, 0, 0, 0, 0, 0, 471, 0, 0,
+ 0, 0, 0, 0, 472, 0, 0, 0, 0, 0, 0, 473, 0, 0, 0, 0, 0, 0, 474, 0, 0, 0,
+ 0, 0, 0, 475, 0, 0, 0, 0, 0, 0, 476, 0, 0, 0, 0, 0, 0, 477, 0, 0, 0, 0,
+ 0, 0, 478, 0, 0, 0, 0, 0, 0, 479, 0, 0, 0, 0, 0, 0, 480, 0, 0, 0, 0, 0,
+ 0, 481, 0, 0, 0, 0, 0, 0, 482, 0, 0, 0, 0, 0, 0, 483, 0, 0, 0, 0, 0, 0,
+ 484, 0, 0, 0, 0, 0, 0, 485, 0, 0, 0, 0, 0, 0, 486, 0, 0, 0, 0, 0, 0, 487,
+ 0, 0, 0, 0, 0, 0, 488, 0, 0, 0, 0, 0, 0, 489, 0, 0, 0, 0, 0, 0, 490, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 491, 0, 0, 0, 0, 0, 0, 492, 0, 0, 0, 0, 0, 0,
+ 493, 0, 0, 0, 0, 0, 0, 494, 0, 0, 0, 0, 0, 0, 495, 0, 0, 0, 0, 0, 0, 496,
+ 0, 0, 0, 0, 0, 0, 497, 0, 0, 0, 0, 0, 0, 498, 0, 0, 0, 0, 0, 0, 499, 0,
+ 0, 0, 0, 0, 0, 500, 0, 0, 0, 0, 0, 0, 501, 0, 0, 0, 0, 0, 0, 502, 0, 0,
+ 0, 0, 0, 0, 503, 0, 0, 0, 0, 0, 0, 504, 0, 0, 0, 0, 0, 0, 505, 0, 0, 0,
+ 0, 0, 0, 506, 0, 0, 0, 0, 0, 0, 507, 0, 0, 0, 0, 0, 0, 508, 0, 0, 0, 0,
+ 0, 0, 509, 0, 0, 0, 0, 0, 0, 510, 0, 0, 0, 0, 0, 0, 511, 0, 0, 0, 0, 0,
+ 0, 512, 0, 0, 0, 0, 0, 0, 513, 0, 0, 0, 0, 0, 0, 514, 0, 0, 0, 0, 0, 0,
+ 515, 0, 0, 0, 0, 0, 0, 516, 0, 0, 0, 0, 0, 0, 517, 0, 0, 0, 0, 0, 0, 518,
+ 0, 0, 0, 0, 0, 0, 519, 0, 0, 0, 0, 0, 0, 520, 0, 0, 0, 0, 0, 0, 521, 0,
+ 0, 0, 0, 0, 0, 522, 0, 0, 0, 0, 0, 0, 523, 0, 0, 0, 0, 0, 0, 524, 0, 0,
+ 0, 0, 0, 0, 525, 0, 0, 0, 0, 0, 0, 526, 0, 0, 0, 0, 0, 0, 527, 0, 0, 0,
+ 0, 0, 0, 528, 0, 0, 0, 0, 0, 0, 529, 0, 0, 0, 0, 0, 0, 530, 0, 0, 0, 0,
+ 0, 0, 531, 0, 0, 0, 0, 0, 0, 532, 0, 0, 0, 0, 0, 0, 533, 0, 0, 0, 0, 0,
+ 0, 534, 0, 0, 0, 0, 0, 0, 535, 0, 0, 0, 0, 0, 0, 536, 0, 0, 0, 0, 0, 0,
+ 537, 0, 0, 0, 0, 0, 0, 538, 0, 0, 0, 0, 0, 0, 539, 0, 0, 0, 0, 0, 0, 540,
+ 0, 0, 0, 0, 0, 0, 541, 0, 0, 0, 0, 0, 0, 542, 0, 0, 0, 0, 0, 0, 543,
+};
+
+static const unsigned short comp_index1[] = {
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 4, 5, 6, 7, 8, 9, 10, 0,
+ 11, 12, 0, 13, 0, 0, 0, 14, 15, 0, 0, 0, 0, 16, 0, 0, 17, 18, 0, 19, 0,
+ 20, 0, 0, 0, 0, 21, 0, 0, 0, 0, 22, 0, 23, 0, 0, 24, 0, 25, 26, 0, 27, 0,
+ 0, 28, 29, 30, 31, 32, 33, 34, 0, 35, 0, 36, 37, 38, 0, 0, 0, 0, 0, 0,
+ 39, 40, 41, 42, 43, 0, 44, 0, 0, 0, 0, 45, 0, 0, 46, 0, 47, 0, 48, 0, 0,
+ 49, 0, 50, 0, 51, 0, 0, 0, 52, 53, 54, 55, 56, 57, 58, 0, 59, 0, 0, 60,
+ 61, 0, 0, 0, 0, 62, 0, 0, 63, 0, 0, 0, 0, 64, 0, 0, 65, 0, 66, 0, 0, 67,
+ 0, 0, 68, 0, 0, 0, 0, 69, 0, 0, 70, 0, 71, 72, 0, 73, 0, 0, 74, 0, 0, 75,
+ 76, 0, 0, 0, 77, 78, 0, 79, 0, 80, 0, 0, 81, 0, 82, 83, 0, 84, 0, 0, 85,
+ 86, 87, 88, 89, 90, 91, 0, 92, 0, 0, 93, 94, 0, 0, 95, 96, 0, 0, 97, 0,
+ 98, 99, 0, 100, 0, 101, 0, 0, 102, 0, 0, 103, 104, 0, 105, 0, 106, 0, 0,
+ 107, 0, 108, 0, 0, 0, 0, 109, 0, 110, 0, 0, 111, 0, 112, 113, 0, 114, 0,
+ 0, 115, 116, 117, 118, 119, 120, 121, 0, 122, 123, 0, 124, 125, 0, 0, 0,
+ 0, 126, 0, 0, 127, 0, 0, 0, 128, 129, 0, 130, 131, 0, 0, 0, 0, 0, 0, 132,
+ 133, 134, 135, 136, 137, 0, 0, 0, 138, 0, 0, 0, 139, 140, 0, 141, 0, 142,
+ 0, 0, 143, 0, 0, 0, 0, 144, 0, 0, 145, 146, 147, 148, 149, 150, 151, 0,
+ 152, 153, 0, 154, 0, 0, 0, 155, 156, 0, 0, 0, 0, 157, 0, 0, 158, 159, 0,
+ 160, 0, 161, 0, 0, 0, 0, 162, 0, 0, 0, 0, 163, 0, 164, 0, 0, 165, 0, 166,
+ 167, 0, 168, 0, 0, 169, 170, 171, 172, 173, 174, 175, 0, 176, 0, 177,
+ 178, 179, 0, 0, 0, 0, 0, 0, 180, 181, 182, 183, 184, 0, 185, 0, 0, 0, 0,
+ 186, 0, 0, 187, 0, 188, 0, 189, 0, 0, 190, 0, 191, 0, 192, 193, 0, 0,
+ 194, 195, 196, 197, 198, 199, 200, 0, 201, 0, 0, 202, 203, 0, 0, 0, 0,
+ 204, 0, 0, 0, 205, 0, 0, 206, 0, 0, 0, 0, 207, 0, 0, 208, 0, 209, 0, 0,
+ 210, 0, 0, 211, 0, 0, 0, 0, 212, 0, 0, 213, 0, 214, 215, 0, 216, 0, 0,
+ 217, 0, 0, 218, 219, 0, 0, 0, 220, 221, 0, 222, 0, 223, 0, 0, 224, 0,
+ 225, 226, 0, 227, 0, 0, 228, 229, 230, 231, 232, 233, 234, 0, 235, 0, 0,
+ 236, 237, 0, 0, 238, 239, 0, 0, 240, 0, 241, 242, 0, 243, 0, 244, 0, 0,
+ 245, 0, 0, 246, 247, 0, 248, 0, 249, 0, 0, 250, 0, 251, 0, 0, 0, 0, 252,
+ 0, 253, 0, 0, 254, 0, 255, 256, 0, 257, 0, 0, 258, 259, 260, 261, 262,
+ 263, 264, 0, 265, 266, 0, 267, 268, 0, 0, 0, 0, 269, 0, 0, 270, 0, 0, 0,
+ 271, 272, 0, 273, 274, 0, 0, 0, 275, 0, 0, 0, 0, 0, 0, 276, 277, 278,
+ 279, 280, 281, 0, 0, 0, 282, 0, 0, 0, 283, 284, 0, 285, 0, 286, 0, 0,
+ 287, 0, 0, 0, 0, 288, 0, 0, 289, 0, 0, 0, 0, 0, 290, 0, 291, 292, 0, 0,
+ 293, 0, 0, 0, 0, 0, 294, 0, 295, 0, 0, 0, 296, 0, 297, 0, 298, 0, 0, 0,
+ 299, 300, 0, 0, 301, 0, 0, 0, 302, 0, 0, 0, 303, 304, 0, 0, 305, 0, 0, 0,
+ 306, 0, 307, 308, 0, 0, 309, 0, 310, 0, 0, 0, 311, 0, 312, 0, 0, 313, 0,
+ 0, 314, 315, 0, 0, 316, 0, 0, 0, 0, 0, 317, 0, 318, 0, 0, 0, 319, 0, 320,
+ 0, 321, 0, 0, 0, 322, 323, 0, 0, 324, 0, 0, 0, 325, 0, 0, 0, 326, 327, 0,
+ 0, 328, 0, 0, 0, 329, 0, 330, 331, 0, 0, 332, 0, 333, 0, 0, 0, 334, 0,
+ 335, 0, 0, 336, 0, 0, 337, 338, 0, 0, 339, 0, 0, 0, 340, 341, 0, 0, 342,
+ 0, 0, 0, 343, 0, 0, 0, 344, 0, 0, 0, 345, 0, 0, 0, 346, 0, 0, 0, 0, 0, 0,
+ 347, 0, 0, 0, 348, 0, 0, 0, 349, 0, 0, 0, 350, 351, 0, 0, 0, 352, 0, 0,
+ 0, 0, 0, 0, 353, 0, 0, 0, 354, 0, 0, 0, 355, 356, 357, 0, 0, 358, 0, 0,
+ 0, 359, 0, 0, 0, 360, 361, 0, 0, 362, 0, 0, 0, 363, 0, 0, 0, 364, 365, 0,
+ 0, 366, 0, 0, 0, 367, 0, 0, 0, 368, 369, 0, 0, 370, 0, 0, 0, 371, 0, 0,
+ 0, 0, 372, 0, 0, 0, 0, 373, 0, 0, 0, 374, 0, 0, 0, 375, 0, 0, 0, 376, 0,
+ 0, 0, 377, 0, 0, 0, 378, 0, 0, 0, 379, 0, 0, 0, 380, 0, 0, 381, 0, 0,
+ 382, 0, 383, 0, 0, 0, 0, 384, 0, 0, 385, 0, 386, 0, 0, 0, 0, 0, 0, 387,
+ 388, 0, 0, 0, 0, 0, 0, 389, 0, 0, 390, 0, 391, 0, 392, 393, 0, 0, 0, 394,
+ 395, 0, 0, 0, 0, 0, 0, 396, 0, 0, 0, 397, 398, 0, 399, 400, 0, 0, 0, 401,
+ 402, 0, 0, 0, 0, 0, 0, 403, 0, 0, 404, 0, 0, 0, 405, 0, 0, 0, 406, 0,
+ 407, 0, 408, 0, 0, 0, 0, 409, 0, 0, 410, 0, 411, 0, 0, 0, 0, 0, 0, 412,
+ 413, 0, 0, 0, 0, 0, 0, 414, 0, 0, 415, 0, 416, 0, 417, 418, 0, 0, 0, 419,
+ 0, 0, 420, 0, 421, 0, 0, 0, 0, 0, 0, 422, 0, 0, 0, 423, 424, 0, 425, 426,
+ 0, 0, 0, 427, 0, 0, 428, 0, 429, 0, 0, 0, 0, 0, 0, 430, 0, 0, 431, 0,
+ 432, 0, 0, 0, 0, 0, 433, 0, 434, 0, 0, 0, 0, 0, 435, 0, 0, 0, 436, 0,
+ 437, 0, 0, 438, 0, 0, 0, 439, 0, 0, 440, 441, 442, 0, 0, 0, 443, 0, 444,
+ 445, 0, 0, 446, 447, 0, 0, 0, 448, 449, 0, 450, 451, 452, 0, 0, 0, 0, 0,
+ 0, 453, 0, 0, 454, 455, 0, 456, 0, 0, 0, 0, 0, 457, 0, 0, 0, 458, 0, 0,
+ 0, 459, 0, 0, 460, 461, 462, 0, 0, 0, 463, 0, 464, 465, 0, 0, 466, 467,
+ 0, 0, 0, 468, 469, 0, 470, 471, 472, 0, 0, 0, 0, 0, 0, 473, 0, 0, 474,
+ 475, 0, 476, 0, 0, 0, 0, 0, 477, 0, 0, 0, 478, 0, 0, 0, 479, 0, 0, 0,
+ 480, 0, 0, 481, 0, 0, 0, 482, 0, 0, 0, 0, 483, 0, 0, 0, 484, 0, 0, 0,
+ 485, 0, 0, 0, 486, 0, 0, 0, 487, 488, 0, 0, 0, 0, 0, 0, 489, 0, 0, 0,
+ 490, 0, 0, 0, 491, 0, 0, 0, 492, 0, 0, 0, 493, 494, 0, 0, 0, 495, 0, 0,
+ 0, 496, 0, 0, 0, 0, 497, 0, 0, 0, 0, 498, 499, 500, 0, 0, 0, 0, 0, 0,
+ 501, 502, 0, 0, 0, 0, 0, 0, 503, 504, 0, 0, 0, 0, 505, 0, 0, 0, 506, 507,
+ 0, 0, 508, 0, 0, 0, 0, 509, 510, 0, 0, 511, 0, 0, 0, 0, 512, 513, 0, 0,
+ 0, 0, 0, 0, 514, 0, 515, 0, 0, 0, 516, 0, 0, 0, 517, 0, 0, 0, 518, 0, 0,
+ 0, 519, 0, 0, 0, 520, 0, 0, 0, 521, 0, 0, 0, 522, 0, 0, 0, 523, 0, 0, 0,
+ 524, 0, 0, 0, 525, 0, 0, 0, 526, 0, 0, 0, 0, 527, 0, 0, 0, 528, 0, 0, 0,
+ 529, 0, 0, 0, 530, 0, 0, 0, 0, 531, 0, 0, 0, 532, 0, 533, 534, 0, 0, 535,
+ 536, 0, 0, 537, 0, 0, 0, 538, 0, 0, 0, 539, 0, 0, 0, 540, 0, 0, 541, 0,
+ 0, 0, 0, 0, 542, 0, 543, 0, 0, 0, 0, 0, 544, 0, 0, 0, 545, 0, 0, 0, 546,
+ 0, 0, 0, 547, 0, 0, 0, 548, 0, 0, 0, 549, 0, 0, 0, 550, 0, 551, 0, 0, 0,
+ 0, 0, 552, 0, 553, 0, 0, 0, 0, 0, 554, 0, 0, 0, 555, 0, 0, 0, 556, 0, 0,
+ 0, 557, 0, 0, 0, 558, 0, 0, 0, 559, 0, 0, 0, 560, 0, 561, 0, 0, 0, 562,
+ 0, 0, 0, 563, 0, 0, 0, 564, 0, 0, 0, 565, 0, 0, 0, 0, 0, 566, 0, 567, 0,
+ 0, 0, 0, 0, 568, 0, 0, 0, 569, 0, 0, 0, 570, 0, 0, 0, 571, 0, 0, 0, 572,
+ 0, 0, 0, 573, 0, 0, 0, 574, 0, 575, 0, 0, 0, 0, 0, 576, 0, 577, 0, 0, 0,
+ 0, 0, 578, 0, 0, 0, 579, 0, 0, 0, 580, 0, 0, 0, 581, 0, 0, 0, 582, 0, 0,
+ 0, 583, 0, 0, 0, 584, 0, 585, 0, 0, 0, 0, 0, 586, 0, 587, 0, 0, 0, 0, 0,
+ 588, 0, 589, 0, 0, 0, 0, 0, 590, 0, 591, 0, 0, 0, 0, 0, 592, 0, 593, 0,
+ 0, 0, 594, 0, 0, 0, 595, 0, 0, 0, 596, 0, 0, 0, 597, 0, 0, 0, 0, 0, 598,
+ 0, 599, 0, 0, 0, 0, 0, 600, 0, 601, 0, 0, 0, 0, 0, 602, 0, 603, 0, 0, 0,
+ 0, 0, 604, 0, 605, 0, 0, 0, 0, 0, 606, 0, 0, 0, 607, 0, 0, 0, 608, 0, 0,
+ 0, 609, 0, 0, 0, 610, 0, 0, 0, 611, 0, 0, 0, 612, 0, 613, 0, 0, 0, 0, 0,
+ 614, 0, 615, 0, 0, 0, 0, 0, 616, 0, 0, 0, 617, 0, 0, 0, 618, 0, 0, 0,
+ 619, 0, 0, 0, 620, 0, 0, 0, 621, 0, 0, 0, 622, 0, 0, 0, 623, 0, 0, 0,
+ 624, 0, 0, 0, 625, 0, 0, 0, 626, 0, 627, 0, 0, 0, 0, 0, 628, 0, 0, 0,
+ 629, 0, 0, 0, 630, 0, 631, 0, 0, 0, 0, 0, 632, 0, 0, 633, 0, 0, 0, 634,
+ 0, 0, 0, 635, 0, 0, 0, 636, 0, 0, 0, 637, 0, 0, 0, 638, 0, 0, 0, 639, 0,
+ 0, 0, 640, 0, 0, 0, 641, 0, 0, 0, 642, 0, 0, 0, 643, 0, 0, 0, 644, 0, 0,
+ 0, 645, 0, 0, 0, 646, 0, 0, 0, 647, 0, 0, 0, 648, 0, 0, 0, 649, 0, 0, 0,
+ 650, 0, 0, 0, 651, 0, 0, 0, 652, 0, 0, 0, 653, 0, 0, 0, 654, 0, 0, 0,
+ 655, 0, 0, 0, 656, 0, 0, 0, 657, 0, 0, 0, 658, 0, 0, 0, 659, 0, 0, 0,
+ 660, 0, 0, 0, 661, 0, 0, 0, 662, 0, 0, 0, 663, 0, 0, 0, 664, 0, 0, 0,
+ 665, 0, 0, 0, 666, 0, 0, 0, 667, 0, 0, 0, 668, 0, 0, 0, 669, 0, 0, 0,
+ 670, 0, 0, 0, 671, 0, 0, 0, 672, 0, 0, 0, 673, 0, 0, 0, 0, 674, 0, 0, 0,
+ 675, 0, 0, 0, 676, 0, 0, 0, 677, 0, 0, 0, 678, 0, 0, 0, 679, 0, 0, 0,
+ 680, 0, 0, 0, 681, 0, 0, 0, 682, 0, 0, 0, 683, 0, 0, 0, 684, 0, 0, 0,
+ 685, 0, 0, 0, 686, 0, 0, 0, 687, 0, 0, 0, 688, 0, 0, 0, 689, 0, 0, 0,
+ 690, 0, 0, 0, 691, 0, 0, 0, 692, 0, 0, 0, 693, 0, 0, 0, 694, 0, 0, 0,
+ 695, 0, 0, 0, 696, 0, 0, 0, 697, 0, 0, 0, 698, 0, 0, 0, 699, 0, 0, 0,
+ 700, 0, 0, 0, 701, 0, 0, 0, 702, 0, 0, 0, 703, 0, 0, 0, 704, 0, 0, 0,
+ 705, 0, 0, 0, 706, 0, 0, 0, 707, 0, 0, 0, 708, 0, 0, 0, 709, 0, 0, 0,
+ 710, 0, 0, 0, 711, 0, 0, 0, 712, 0, 0, 0, 713, 0, 0, 0, 714, 0, 0, 0,
+ 715, 0, 0, 0, 716, 0, 0, 0, 717, 0, 0, 0, 718, 0, 0, 0, 719, 0, 0, 0,
+ 720, 0, 0, 0, 721, 0, 0, 0, 0, 722, 0, 0, 0, 723, 0, 0, 0, 724, 0, 0, 0,
+ 725, 0, 0, 0, 726,
+};
+
+static const unsigned int comp_data[] = {
+ 0, 0, 0, 8814, 0, 8800, 0, 8815, 192, 193, 194, 195, 256, 258, 550, 196,
+ 7842, 197, 0, 461, 512, 514, 0, 7840, 0, 7680, 260, 0, 7682, 0, 0, 7684,
+ 7686, 0, 0, 262, 264, 0, 266, 0, 0, 268, 0, 199, 7690, 0, 0, 270, 0,
+ 7692, 0, 7696, 0, 7698, 7694, 0, 200, 201, 202, 7868, 274, 276, 278, 203,
+ 7866, 0, 0, 282, 516, 518, 0, 7864, 0, 552, 280, 7704, 0, 7706, 7710, 0,
+ 0, 500, 284, 0, 7712, 286, 288, 0, 0, 486, 0, 290, 292, 0, 7714, 7718, 0,
+ 542, 0, 7716, 0, 7720, 7722, 0, 204, 205, 206, 296, 298, 300, 304, 207,
+ 7880, 0, 0, 463, 520, 522, 0, 7882, 302, 0, 0, 7724, 308, 0, 0, 7728, 0,
+ 488, 0, 7730, 0, 310, 7732, 0, 0, 313, 0, 317, 0, 7734, 0, 315, 0, 7740,
+ 7738, 0, 0, 7742, 7744, 0, 0, 7746, 504, 323, 0, 209, 7748, 0, 0, 327, 0,
+ 7750, 0, 325, 0, 7754, 7752, 0, 210, 211, 212, 213, 332, 334, 558, 214,
+ 7886, 0, 336, 465, 524, 526, 416, 7884, 490, 0, 0, 7764, 7766, 0, 0, 340,
+ 7768, 0, 0, 344, 528, 530, 0, 7770, 0, 342, 7774, 0, 0, 346, 348, 0,
+ 7776, 0, 0, 352, 0, 7778, 536, 350, 7786, 0, 0, 356, 0, 7788, 538, 354,
+ 0, 7792, 7790, 0, 217, 218, 219, 360, 362, 364, 0, 220, 7910, 366, 368,
+ 467, 532, 534, 431, 7908, 7794, 0, 370, 7798, 0, 7796, 0, 7804, 0, 7806,
+ 7808, 7810, 372, 0, 7814, 7812, 0, 7816, 7818, 7820, 7922, 221, 374,
+ 7928, 562, 0, 7822, 376, 7926, 0, 0, 7924, 0, 377, 7824, 0, 379, 0, 0,
+ 381, 0, 7826, 7828, 0, 224, 225, 226, 227, 257, 259, 551, 228, 7843, 229,
+ 0, 462, 513, 515, 0, 7841, 0, 7681, 261, 0, 7683, 0, 0, 7685, 7687, 0, 0,
+ 263, 265, 0, 267, 0, 0, 269, 0, 231, 7691, 0, 0, 271, 0, 7693, 0, 7697,
+ 0, 7699, 7695, 0, 232, 233, 234, 7869, 275, 277, 279, 235, 7867, 0, 0,
+ 283, 517, 519, 0, 7865, 0, 553, 281, 7705, 0, 7707, 7711, 0, 0, 501, 285,
+ 0, 7713, 287, 289, 0, 0, 487, 0, 291, 293, 0, 7715, 7719, 0, 543, 0,
+ 7717, 0, 7721, 7723, 0, 7830, 0, 236, 237, 238, 297, 299, 301, 0, 239,
+ 7881, 0, 0, 464, 521, 523, 0, 7883, 303, 0, 0, 7725, 309, 0, 0, 496, 0,
+ 7729, 0, 489, 0, 7731, 0, 311, 7733, 0, 0, 314, 0, 318, 0, 7735, 0, 316,
+ 0, 7741, 7739, 0, 0, 7743, 7745, 0, 0, 7747, 505, 324, 0, 241, 7749, 0,
+ 0, 328, 0, 7751, 0, 326, 0, 7755, 7753, 0, 242, 243, 244, 245, 333, 335,
+ 559, 246, 7887, 0, 337, 466, 525, 527, 417, 7885, 491, 0, 0, 7765, 7767,
+ 0, 0, 341, 7769, 0, 0, 345, 529, 531, 0, 7771, 0, 343, 7775, 0, 0, 347,
+ 349, 0, 7777, 0, 0, 353, 0, 7779, 537, 351, 7787, 7831, 0, 357, 0, 7789,
+ 539, 355, 0, 7793, 7791, 0, 249, 250, 251, 361, 363, 365, 0, 252, 7911,
+ 367, 369, 468, 533, 535, 432, 7909, 7795, 0, 371, 7799, 0, 7797, 0, 7805,
+ 0, 7807, 7809, 7811, 373, 0, 7815, 7813, 0, 7832, 0, 7817, 7819, 7821,
+ 7923, 253, 375, 7929, 563, 0, 7823, 255, 7927, 7833, 0, 7925, 0, 378,
+ 7825, 0, 380, 0, 0, 382, 0, 7827, 7829, 0, 8173, 901, 8129, 0, 7846,
+ 7844, 0, 7850, 7848, 0, 478, 0, 0, 506, 0, 508, 482, 0, 0, 7688, 7872,
+ 7870, 0, 7876, 7874, 0, 0, 7726, 7890, 7888, 0, 7894, 7892, 0, 0, 7756,
+ 556, 0, 0, 7758, 554, 0, 0, 510, 475, 471, 469, 0, 0, 473, 7847, 7845, 0,
+ 7851, 7849, 0, 479, 0, 0, 507, 0, 509, 483, 0, 0, 7689, 7873, 7871, 0,
+ 7877, 7875, 0, 0, 7727, 7891, 7889, 0, 7895, 7893, 0, 0, 7757, 557, 0, 0,
+ 7759, 555, 0, 0, 511, 476, 472, 470, 0, 0, 474, 7856, 7854, 0, 7860,
+ 7858, 0, 7857, 7855, 0, 7861, 7859, 0, 7700, 7702, 7701, 7703, 7760,
+ 7762, 7761, 7763, 7780, 0, 7781, 0, 7782, 0, 7783, 0, 0, 7800, 0, 7801,
+ 0, 7802, 0, 7803, 7835, 0, 7900, 7898, 0, 7904, 7902, 0, 0, 7906, 7901,
+ 7899, 0, 7905, 7903, 0, 0, 7907, 7914, 7912, 0, 7918, 7916, 0, 0, 7920,
+ 7915, 7913, 0, 7919, 7917, 0, 0, 7921, 0, 494, 492, 0, 493, 0, 480, 0,
+ 481, 0, 0, 7708, 0, 7709, 560, 0, 561, 0, 0, 495, 8122, 902, 8121, 8120,
+ 7944, 7945, 0, 8124, 8136, 904, 7960, 7961, 8138, 905, 7976, 7977, 0,
+ 8140, 8154, 906, 8153, 8152, 0, 938, 7992, 7993, 8184, 908, 8008, 8009,
+ 0, 8172, 8170, 910, 8169, 8168, 0, 939, 0, 8025, 8186, 911, 8040, 8041,
+ 0, 8188, 0, 8116, 0, 8132, 8048, 940, 8113, 8112, 7936, 7937, 8118, 8115,
+ 8050, 941, 7952, 7953, 8052, 942, 7968, 7969, 8134, 8131, 8054, 943,
+ 8145, 8144, 0, 970, 7984, 7985, 8150, 0, 8056, 972, 8000, 8001, 8164,
+ 8165, 8058, 973, 8161, 8160, 0, 971, 8016, 8017, 8166, 0, 8060, 974,
+ 8032, 8033, 8182, 8179, 8146, 912, 8151, 0, 8162, 944, 8167, 0, 0, 8180,
+ 0, 979, 0, 980, 0, 1031, 0, 1232, 0, 1234, 0, 1027, 1024, 0, 0, 1238, 0,
+ 1025, 0, 1217, 0, 1244, 0, 1246, 1037, 0, 1250, 1049, 0, 1252, 0, 1036,
+ 0, 1254, 1262, 1038, 0, 1264, 1266, 0, 0, 1268, 0, 1272, 0, 1260, 0,
+ 1233, 0, 1235, 0, 1107, 1104, 0, 0, 1239, 0, 1105, 0, 1218, 0, 1245, 0,
+ 1247, 1117, 0, 1251, 1081, 0, 1253, 0, 1116, 0, 1255, 1263, 1118, 0,
+ 1265, 1267, 0, 0, 1269, 0, 1273, 0, 1261, 0, 1111, 1142, 0, 1143, 0, 0,
+ 1242, 0, 1243, 0, 1258, 0, 1259, 1570, 1571, 1573, 0, 0, 1572, 0, 1574,
+ 0, 1730, 0, 1747, 0, 1728, 0, 2345, 0, 2353, 0, 2356, 2507, 2508, 2891,
+ 2888, 2892, 0, 2964, 0, 0, 3018, 3020, 0, 0, 3019, 0, 3144, 0, 3264,
+ 3274, 3271, 3272, 0, 0, 3275, 0, 3402, 3404, 0, 0, 3403, 0, 3546, 3548,
+ 3550, 0, 3549, 4134, 0, 0, 6918, 0, 6920, 0, 6922, 0, 6924, 0, 6926, 0,
+ 6930, 0, 6971, 0, 6973, 0, 6976, 0, 6977, 0, 6979, 7736, 0, 7737, 0,
+ 7772, 0, 7773, 0, 7784, 0, 7785, 0, 7852, 0, 0, 7862, 7853, 0, 0, 7863,
+ 7878, 0, 7879, 0, 7896, 0, 7897, 0, 7938, 7940, 7942, 8064, 7939, 7941,
+ 7943, 8065, 0, 8066, 0, 8067, 0, 8068, 0, 8069, 0, 8070, 0, 8071, 7946,
+ 7948, 7950, 8072, 7947, 7949, 7951, 8073, 0, 8074, 0, 8075, 0, 8076, 0,
+ 8077, 0, 8078, 0, 8079, 7954, 7956, 7955, 7957, 7962, 7964, 7963, 7965,
+ 7970, 7972, 7974, 8080, 7971, 7973, 7975, 8081, 0, 8082, 0, 8083, 0,
+ 8084, 0, 8085, 0, 8086, 0, 8087, 7978, 7980, 7982, 8088, 7979, 7981,
+ 7983, 8089, 0, 8090, 0, 8091, 0, 8092, 0, 8093, 0, 8094, 0, 8095, 7986,
+ 7988, 7990, 0, 7987, 7989, 7991, 0, 7994, 7996, 7998, 0, 7995, 7997,
+ 7999, 0, 8002, 8004, 8003, 8005, 8010, 8012, 8011, 8013, 8018, 8020,
+ 8022, 0, 8019, 8021, 8023, 0, 8027, 8029, 8031, 0, 8034, 8036, 8038,
+ 8096, 8035, 8037, 8039, 8097, 0, 8098, 0, 8099, 0, 8100, 0, 8101, 0,
+ 8102, 0, 8103, 8042, 8044, 8046, 8104, 8043, 8045, 8047, 8105, 0, 8106,
+ 0, 8107, 0, 8108, 0, 8109, 0, 8110, 0, 8111, 0, 8114, 0, 8130, 0, 8178,
+ 0, 8119, 8141, 8142, 8143, 0, 0, 8135, 0, 8183, 8157, 8158, 8159, 0, 0,
+ 8602, 0, 8603, 0, 8622, 0, 8653, 0, 8655, 0, 8654, 0, 8708, 0, 8713, 0,
+ 8716, 0, 8740, 0, 8742, 0, 8769, 0, 8772, 0, 8775, 0, 8777, 0, 8813, 0,
+ 8802, 0, 8816, 0, 8817, 0, 8820, 0, 8821, 0, 8824, 0, 8825, 0, 8832, 0,
+ 8833, 0, 8928, 0, 8929, 0, 8836, 0, 8837, 0, 8840, 0, 8841, 0, 8930, 0,
+ 8931, 0, 8876, 0, 8877, 0, 8878, 0, 8879, 0, 8938, 0, 8939, 0, 8940, 0,
+ 8941, 12436, 0, 12364, 0, 12366, 0, 12368, 0, 12370, 0, 12372, 0, 12374,
+ 0, 12376, 0, 12378, 0, 12380, 0, 12382, 0, 12384, 0, 12386, 0, 12389, 0,
+ 12391, 0, 12393, 0, 12400, 12401, 12403, 12404, 12406, 12407, 12409,
+ 12410, 12412, 12413, 12446, 0, 12532, 0, 12460, 0, 12462, 0, 12464, 0,
+ 12466, 0, 12468, 0, 12470, 0, 12472, 0, 12474, 0, 12476, 0, 12478, 0,
+ 12480, 0, 12482, 0, 12485, 0, 12487, 0, 12489, 0, 12496, 12497, 12499,
+ 12500, 12502, 12503, 12505, 12506, 12508, 12509, 12535, 0, 12536, 0,
+ 12537, 0, 12538, 0, 12542, 0, 69786, 0, 69788, 0, 69803, 0, 0, 69934, 0,
+ 69935,
+};
diff --git a/source/fitz/xml.c b/source/fitz/xml.c
new file mode 100644
index 00000000..8c97562c
--- /dev/null
+++ b/source/fitz/xml.c
@@ -0,0 +1,460 @@
+#include "mupdf/fitz.h"
+
+struct parser
+{
+ fz_xml *head;
+ fz_context *ctx;
+};
+
+struct attribute
+{
+ char name[40];
+ char *value;
+ struct attribute *next;
+};
+
+struct fz_xml_s
+{
+ char name[40];
+ char *text;
+ struct attribute *atts;
+ fz_xml *up, *down, *next;
+};
+
+static inline void indent(int n)
+{
+ while (n--) putchar(' ');
+}
+
+void fz_debug_xml(fz_xml *item, int level)
+{
+ while (item) {
+ if (item->text) {
+ printf("%s\n", item->text);
+ } else {
+ struct attribute *att;
+ indent(level);
+ printf("<%s", item->name);
+ for (att = item->atts; att; att = att->next)
+ printf(" %s=\"%s\"", att->name, att->value);
+ if (item->down) {
+ printf(">\n");
+ fz_debug_xml(item->down, level + 1);
+ indent(level);
+ printf("</%s>\n", item->name);
+ }
+ else {
+ printf("/>\n");
+ }
+ }
+ item = item->next;
+ }
+}
+
+fz_xml *fz_xml_next(fz_xml *item)
+{
+ return item->next;
+}
+
+fz_xml *fz_xml_down(fz_xml *item)
+{
+ return item->down;
+}
+
+char *fz_xml_text(fz_xml *item)
+{
+ return item->text;
+}
+
+char *fz_xml_tag(fz_xml *item)
+{
+ return item->name;
+}
+
+char *fz_xml_att(fz_xml *item, const char *name)
+{
+ struct attribute *att;
+ for (att = item->atts; att; att = att->next)
+ if (!strcmp(att->name, name))
+ return att->value;
+ return NULL;
+}
+
+static void xml_free_attribute(fz_context *ctx, struct attribute *att)
+{
+ while (att) {
+ struct attribute *next = att->next;
+ if (att->value)
+ fz_free(ctx, att->value);
+ fz_free(ctx, att);
+ att = next;
+ }
+}
+
+void fz_free_xml(fz_context *ctx, fz_xml *item)
+{
+ while (item)
+ {
+ fz_xml *next = item->next;
+ if (item->text)
+ fz_free(ctx, item->text);
+ if (item->atts)
+ xml_free_attribute(ctx, item->atts);
+ if (item->down)
+ fz_free_xml(ctx, item->down);
+ fz_free(ctx, item);
+ item = next;
+ }
+}
+
+void fz_detach_xml(fz_xml *node)
+{
+ if (node->up)
+ node->up->down = NULL;
+}
+
+static int xml_parse_entity(int *c, char *a)
+{
+ char *b;
+ if (a[1] == '#') {
+ if (a[2] == 'x')
+ *c = strtol(a + 3, &b, 16);
+ else
+ *c = strtol(a + 2, &b, 10);
+ if (*b == ';')
+ return b - a + 1;
+ }
+ else if (a[1] == 'l' && a[2] == 't' && a[3] == ';') {
+ *c = '<';
+ return 4;
+ }
+ else if (a[1] == 'g' && a[2] == 't' && a[3] == ';') {
+ *c = '>';
+ return 4;
+ }
+ else if (a[1] == 'a' && a[2] == 'm' && a[3] == 'p' && a[4] == ';') {
+ *c = '&';
+ return 5;
+ }
+ else if (a[1] == 'a' && a[2] == 'p' && a[3] == 'o' && a[4] == 's' && a[5] == ';') {
+ *c = '\'';
+ return 6;
+ }
+ else if (a[1] == 'q' && a[2] == 'u' && a[3] == 'o' && a[4] == 't' && a[5] == ';') {
+ *c = '"';
+ return 6;
+ }
+ *c = *a++;
+ return 1;
+}
+
+static inline int isname(int c)
+{
+ return c == '.' || c == '-' || c == '_' || c == ':' ||
+ (c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z');
+}
+
+static inline int iswhite(int c)
+{
+ return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+}
+
+static void xml_emit_open_tag(struct parser *parser, char *a, char *b)
+{
+ fz_xml *head, *tail;
+
+ head = fz_malloc_struct(parser->ctx, fz_xml);
+ if (b - a > sizeof(head->name) - 1)
+ b = a + sizeof(head->name) - 1;
+ memcpy(head->name, a, b - a);
+ head->name[b - a] = 0;
+
+ head->atts = NULL;
+ head->text = NULL;
+ head->up = parser->head;
+ head->down = NULL;
+ head->next = NULL;
+
+ if (!parser->head->down) {
+ parser->head->down = head;
+ }
+ else {
+ tail = parser->head->down;
+ while (tail->next)
+ tail = tail->next;
+ tail->next = head;
+ }
+
+ parser->head = head;
+}
+
+static void xml_emit_att_name(struct parser *parser, char *a, char *b)
+{
+ fz_xml *head = parser->head;
+ struct attribute *att;
+
+ att = fz_malloc_struct(parser->ctx, struct attribute);
+ if (b - a > sizeof(att->name) - 1)
+ b = a + sizeof(att->name) - 1;
+ memcpy(att->name, a, b - a);
+ att->name[b - a] = 0;
+ att->value = NULL;
+ att->next = head->atts;
+ head->atts = att;
+}
+
+static void xml_emit_att_value(struct parser *parser, char *a, char *b)
+{
+ fz_xml *head = parser->head;
+ struct attribute *att = head->atts;
+ char *s;
+ int c;
+
+ /* entities are all longer than UTFmax so runetochar is safe */
+ s = att->value = fz_malloc(parser->ctx, b - a + 1);
+ while (a < b) {
+ if (*a == '&') {
+ a += xml_parse_entity(&c, a);
+ s += fz_runetochar(s, c);
+ }
+ else {
+ *s++ = *a++;
+ }
+ }
+ *s = 0;
+}
+
+static void xml_emit_close_tag(struct parser *parser)
+{
+ if (parser->head->up)
+ parser->head = parser->head->up;
+}
+
+static void xml_emit_text(struct parser *parser, char *a, char *b)
+{
+ static char *empty = "";
+ fz_xml *head;
+ char *s;
+ int c;
+
+ /* Skip all-whitespace text nodes */
+ for (s = a; s < b; s++)
+ if (!iswhite(*s))
+ break;
+ if (s == b)
+ return;
+
+ xml_emit_open_tag(parser, empty, empty);
+ head = parser->head;
+
+ /* entities are all longer than UTFmax so runetochar is safe */
+ s = head->text = fz_malloc(parser->ctx, b - a + 1);
+ while (a < b) {
+ if (*a == '&') {
+ a += xml_parse_entity(&c, a);
+ s += fz_runetochar(s, c);
+ }
+ else {
+ *s++ = *a++;
+ }
+ }
+ *s = 0;
+
+ xml_emit_close_tag(parser);
+}
+
+static char *xml_parse_document_imp(struct parser *x, char *p)
+{
+ char *mark;
+ int quote;
+
+parse_text:
+ mark = p;
+ while (*p && *p != '<') ++p;
+ xml_emit_text(x, mark, p);
+ if (*p == '<') { ++p; goto parse_element; }
+ return NULL;
+
+parse_element:
+ if (*p == '/') { ++p; goto parse_closing_element; }
+ if (*p == '!') { ++p; goto parse_comment; }
+ if (*p == '?') { ++p; goto parse_processing_instruction; }
+ while (iswhite(*p)) ++p;
+ if (isname(*p))
+ goto parse_element_name;
+ return "syntax error in element";
+
+parse_comment:
+ if (*p == '[') goto parse_cdata;
+ if (*p++ != '-') return "syntax error in comment (<! not followed by --)";
+ if (*p++ != '-') return "syntax error in comment (<!- not followed by -)";
+ mark = p;
+ while (*p) {
+ if (p[0] == '-' && p[1] == '-' && p[2] == '>') {
+ p += 3;
+ goto parse_text;
+ }
+ ++p;
+ }
+ return "end of data in comment";
+
+parse_cdata:
+ if (p[1] != 'C' || p[2] != 'D' || p[3] != 'A' || p[4] != 'T' || p[5] != 'A' || p[6] != '[')
+ return "syntax error in CDATA section";
+ p += 7;
+ mark = p;
+ while (*p) {
+ if (p[0] == ']' && p[1] == ']' && p[2] == '>') {
+ p += 3;
+ goto parse_text;
+ }
+ ++p;
+ }
+ return "end of data in CDATA section";
+
+parse_processing_instruction:
+ while (*p) {
+ if (p[0] == '?' && p[1] == '>') {
+ p += 2;
+ goto parse_text;
+ }
+ ++p;
+ }
+ return "end of data in processing instruction";
+
+parse_closing_element:
+ while (iswhite(*p)) ++p;
+ mark = p;
+ while (isname(*p)) ++p;
+ while (iswhite(*p)) ++p;
+ if (*p != '>')
+ return "syntax error in closing element";
+ xml_emit_close_tag(x);
+ ++p;
+ goto parse_text;
+
+parse_element_name:
+ mark = p;
+ while (isname(*p)) ++p;
+ xml_emit_open_tag(x, mark, p);
+ if (*p == '>') { ++p; goto parse_text; }
+ if (p[0] == '/' && p[1] == '>') {
+ xml_emit_close_tag(x);
+ p += 2;
+ goto parse_text;
+ }
+ if (iswhite(*p))
+ goto parse_attributes;
+ return "syntax error after element name";
+
+parse_attributes:
+ while (iswhite(*p)) ++p;
+ if (isname(*p))
+ goto parse_attribute_name;
+ if (*p == '>') { ++p; goto parse_text; }
+ if (p[0] == '/' && p[1] == '>') {
+ xml_emit_close_tag(x);
+ p += 2;
+ goto parse_text;
+ }
+ return "syntax error in attributes";
+
+parse_attribute_name:
+ mark = p;
+ while (isname(*p)) ++p;
+ xml_emit_att_name(x, mark, p);
+ while (iswhite(*p)) ++p;
+ if (*p == '=') { ++p; goto parse_attribute_value; }
+ return "syntax error after attribute name";
+
+parse_attribute_value:
+ while (iswhite(*p)) ++p;
+ quote = *p++;
+ if (quote != '"' && quote != '\'')
+ return "missing quote character";
+ mark = p;
+ while (*p && *p != quote) ++p;
+ if (*p == quote) {
+ xml_emit_att_value(x, mark, p++);
+ goto parse_attributes;
+ }
+ return "end of data in attribute value";
+}
+
+static char *convert_to_utf8(fz_context *doc, unsigned char *s, int n, int *dofree)
+{
+ unsigned char *e = s + n;
+ char *dst, *d;
+ int c;
+
+ if (s[0] == 0xFE && s[1] == 0xFF) {
+ s += 2;
+ dst = d = fz_malloc(doc, n * 2);
+ while (s + 1 < e) {
+ c = s[0] << 8 | s[1];
+ d += fz_runetochar(d, c);
+ s += 2;
+ }
+ *d = 0;
+ *dofree = 1;
+ return dst;
+ }
+
+ if (s[0] == 0xFF && s[1] == 0xFE) {
+ s += 2;
+ dst = d = fz_malloc(doc, n * 2);
+ while (s + 1 < e) {
+ c = s[0] | s[1] << 8;
+ d += fz_runetochar(d, c);
+ s += 2;
+ }
+ *d = 0;
+ *dofree = 1;
+ return dst;
+ }
+
+ *dofree = 0;
+
+ if (s[0] == 0xEF && s[1] == 0xBB && s[2] == 0xBF)
+ return (char*)s+3;
+
+ return (char*)s;
+}
+
+fz_xml *
+fz_parse_xml(fz_context *ctx, unsigned char *s, int n)
+{
+ struct parser parser;
+ fz_xml root;
+ char *p, *error;
+ int dofree;
+
+ /* s is already null-terminated (see xps_new_part) */
+
+ memset(&root, 0, sizeof(root));
+ parser.head = &root;
+ parser.ctx = ctx;
+
+ p = convert_to_utf8(ctx, s, n, &dofree);
+
+ fz_try(ctx)
+ {
+ error = xml_parse_document_imp(&parser, p);
+ if (error)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "%s", error);
+ }
+ fz_always(ctx)
+ {
+ if (dofree)
+ fz_free(ctx, p);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_xml(ctx, root.down);
+ fz_rethrow(ctx);
+ }
+
+ return root.down;
+}
diff --git a/source/img/muimage.c b/source/img/muimage.c
new file mode 100644
index 00000000..ed3280b2
--- /dev/null
+++ b/source/img/muimage.c
@@ -0,0 +1,148 @@
+#include "mupdf/image.h"
+
+#include <ctype.h> /* for tolower */
+
+#define DPI 72.0f
+
+static void image_init_document(image_document *doc);
+
+struct image_document_s
+{
+ fz_document super;
+
+ fz_context *ctx;
+ fz_stream *file;
+ fz_image *image;
+};
+
+image_document *
+image_open_document_with_stream(fz_context *ctx, fz_stream *file)
+{
+ image_document *doc;
+ fz_buffer *buffer = NULL;
+
+ doc = fz_malloc_struct(ctx, image_document);
+ image_init_document(doc);
+ doc->ctx = ctx;
+ doc->file = fz_keep_stream(file);
+
+ fz_var(buffer);
+
+ fz_try(ctx)
+ {
+ buffer = fz_read_all(doc->file, 1024);
+ doc->image = fz_new_image_from_buffer(ctx, buffer);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, buffer);
+ }
+ fz_catch(ctx)
+ {
+ image_close_document(doc);
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+image_document *
+image_open_document(fz_context *ctx, const char *filename)
+{
+ fz_stream *file;
+ image_document *doc;
+
+ file = fz_open_file(ctx, filename);
+ if (!file)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ fz_try(ctx)
+ {
+ doc = image_open_document_with_stream(ctx, file);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+void
+image_close_document(image_document *doc)
+{
+ fz_context *ctx = doc->ctx;
+ fz_drop_image(ctx, doc->image);
+ fz_close(doc->file);
+ fz_free(ctx, doc);
+}
+
+int
+image_count_pages(image_document *doc)
+{
+ return 1;
+}
+
+image_page *
+image_load_page(image_document *doc, int number)
+{
+ if (number != 0)
+ return NULL;
+
+ return (image_page *)doc->image;
+}
+
+void
+image_free_page(image_document *doc, image_page *page)
+{
+}
+
+fz_rect *
+image_bound_page(image_document *doc, image_page *page, fz_rect *bbox)
+{
+ fz_image *image = (fz_image *)page;
+ bbox->x0 = bbox->y0 = 0;
+ bbox->x1 = image->w * DPI / image->xres;
+ bbox->y1 = image->h * DPI / image->yres;
+ return bbox;
+}
+
+void
+image_run_page(image_document *doc, image_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ fz_matrix local_ctm = *ctm;
+ fz_image *image = (fz_image *)page;
+ float w = image->w * DPI / image->xres;
+ float h = image->h * DPI / image->yres;
+ fz_pre_scale(&local_ctm, w, h);
+ fz_fill_image(dev, image, &local_ctm, 1);
+}
+
+static int
+image_meta(image_document *doc, int key, void *ptr, int size)
+{
+ switch(key)
+ {
+ case FZ_META_FORMAT_INFO:
+ sprintf((char *)ptr, "IMAGE");
+ return FZ_META_OK;
+ default:
+ return FZ_META_UNKNOWN_KEY;
+ }
+}
+
+static void
+image_init_document(image_document *doc)
+{
+ doc->super.close = (void*)image_close_document;
+ doc->super.count_pages = (void*)image_count_pages;
+ doc->super.load_page = (void*)image_load_page;
+ doc->super.bound_page = (void*)image_bound_page;
+ doc->super.run_page_contents = (void*)image_run_page;
+ doc->super.free_page = (void*)image_free_page;
+ doc->super.meta = (void*)image_meta;
+}
diff --git a/source/pdf/js/pdf-js-none.c b/source/pdf/js/pdf-js-none.c
new file mode 100644
index 00000000..00cc5b54
--- /dev/null
+++ b/source/pdf/js/pdf-js-none.c
@@ -0,0 +1,36 @@
+#include "mupdf/pdf.h"
+
+pdf_js *pdf_new_js(pdf_document *doc)
+{
+ return NULL;
+}
+
+void pdf_js_load_document_level(pdf_js *js)
+{
+}
+
+void pdf_drop_js(pdf_js *js)
+{
+}
+
+void pdf_js_setup_event(pdf_js *js, pdf_js_event *e)
+{
+}
+
+pdf_js_event *pdf_js_get_event(pdf_js *js)
+{
+ return NULL;
+}
+
+void pdf_js_execute(pdf_js *js, char *code)
+{
+}
+
+void pdf_js_execute_count(pdf_js *js, char *code, int count)
+{
+}
+
+int pdf_js_supported(void)
+{
+ return 0;
+}
diff --git a/source/pdf/js/pdf-js.c b/source/pdf/js/pdf-js.c
new file mode 100644
index 00000000..4a2313f1
--- /dev/null
+++ b/source/pdf/js/pdf-js.c
@@ -0,0 +1,919 @@
+#include "mupdf/pdf.h"
+
+struct pdf_js_s
+{
+ pdf_document *doc;
+ pdf_obj *form;
+ pdf_js_event event;
+ pdf_jsimp *imp;
+ pdf_jsimp_type *doctype;
+ pdf_jsimp_type *eventtype;
+ pdf_jsimp_type *fieldtype;
+ pdf_jsimp_type *apptype;
+};
+
+static pdf_jsimp_obj *app_alert(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_jsimp_obj *cMsg_obj = NULL;
+ pdf_jsimp_obj *nIcon_obj = NULL;
+ pdf_jsimp_obj *nType_obj = NULL;
+ pdf_jsimp_obj *cTitle_obj = NULL;
+ pdf_jsimp_obj *nButton_obj = NULL;
+ pdf_alert_event event;
+ int arg_is_obj = 0;
+
+ if (argc < 1 || argc > 6)
+ return NULL;
+
+ event.message = "";
+ event.icon_type = PDF_ALERT_ICON_ERROR;
+ event.button_group_type = PDF_ALERT_BUTTON_GROUP_OK;
+ event.title = "MuPDF";
+ event.check_box_message = NULL;
+ event.button_pressed = 0;
+
+ fz_var(cMsg_obj);
+ fz_var(nIcon_obj);
+ fz_var(nType_obj);
+ fz_var(cTitle_obj);
+ fz_try(ctx)
+ {
+ arg_is_obj = (argc == 1 && pdf_jsimp_to_type(js->imp, args[0]) != JS_TYPE_STRING);
+ if (arg_is_obj)
+ {
+ cMsg_obj = pdf_jsimp_property(js->imp, args[0], "cMsg");
+ nIcon_obj = pdf_jsimp_property(js->imp, args[0], "nIcon");
+ nType_obj = pdf_jsimp_property(js->imp, args[0], "nType");
+ cTitle_obj = pdf_jsimp_property(js->imp, args[0], "cTitle");
+ }
+ else
+ {
+ switch (argc)
+ {
+ case 6:
+ case 5:
+ case 4:
+ cTitle_obj = args[3];
+ case 3:
+ nType_obj = args[2];
+ case 2:
+ nIcon_obj = args[1];
+ case 1:
+ cMsg_obj = args[0];
+ }
+ }
+
+ if (cMsg_obj)
+ event.message = pdf_jsimp_to_string(js->imp, cMsg_obj);
+
+ if (nIcon_obj)
+ event.icon_type = (int)pdf_jsimp_to_number(js->imp, nIcon_obj);
+
+ if (nType_obj)
+ event.button_group_type = (int)pdf_jsimp_to_number(js->imp, nType_obj);
+
+ if (cTitle_obj)
+ event.title = pdf_jsimp_to_string(js->imp, cTitle_obj);
+
+ pdf_event_issue_alert(js->doc, &event);
+ nButton_obj = pdf_jsimp_from_number(js->imp, (double)event.button_pressed);
+ }
+ fz_always(ctx)
+ {
+ if (arg_is_obj)
+ {
+ pdf_jsimp_drop_obj(js->imp, cMsg_obj);
+ pdf_jsimp_drop_obj(js->imp, nIcon_obj);
+ pdf_jsimp_drop_obj(js->imp, nType_obj);
+ pdf_jsimp_drop_obj(js->imp, cTitle_obj);
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return nButton_obj;
+}
+
+static pdf_jsimp_obj *app_execDialog(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ pdf_event_issue_exec_dialog(js->doc);
+
+ return NULL;
+}
+
+static pdf_jsimp_obj *app_execMenuItem(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ if (argc == 1)
+ pdf_event_issue_exec_menu_item(js->doc, pdf_jsimp_to_string(js->imp, args[0]));
+
+ return NULL;
+}
+
+static pdf_jsimp_obj *app_launchURL(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ char *cUrl;
+ int bNewFrame = 0;
+
+ switch (argc)
+ {
+ default:
+ return NULL;
+ case 2:
+ bNewFrame = (int)pdf_jsimp_to_number(js->imp, args[1]);
+ case 1:
+ cUrl = pdf_jsimp_to_string(js->imp, args[0]);
+ }
+
+ pdf_event_issue_launch_url(js->doc, cUrl, bNewFrame);
+
+ return NULL;
+}
+
+static pdf_obj *load_color(fz_context *ctx, pdf_jsimp *imp, pdf_jsimp_obj *val)
+{
+ pdf_obj *col = NULL;
+ pdf_obj *comp = NULL;
+ pdf_jsimp_obj *jscomp = NULL;
+ int i;
+ int n;
+
+ n = pdf_jsimp_array_len(imp, val);
+
+ /* The only legitimate color expressed as an array of length 1
+ * is [T], meaning transparent. Return a NULL object to represent
+ * transparent */
+ if (n <= 1)
+ return NULL;
+
+ col = pdf_new_array(ctx, n-1);
+
+ fz_var(comp);
+ fz_var(jscomp);
+ fz_try(ctx)
+ {
+ for (i = 0; i < n-1; i++)
+ {
+ jscomp = pdf_jsimp_array_item(imp, val, i+1);
+ comp = pdf_new_real(ctx, pdf_jsimp_to_number(imp, jscomp));
+ pdf_array_push(col, comp);
+ pdf_jsimp_drop_obj(imp, jscomp);
+ jscomp = NULL;
+ pdf_drop_obj(comp);
+ comp = NULL;
+ }
+ }
+ fz_catch(ctx)
+ {
+ pdf_jsimp_drop_obj(imp, jscomp);
+ pdf_drop_obj(comp);
+ pdf_drop_obj(col);
+ fz_rethrow(ctx);
+ }
+
+ return col;
+}
+
+static pdf_jsimp_obj *field_buttonSetCaption(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ pdf_obj *field = (pdf_obj *)obj;
+ char *name;
+
+ if (argc != 1)
+ return NULL;
+
+ name = pdf_jsimp_to_string(js->imp, args[0]);
+ pdf_field_set_button_caption(js->doc, field, name);
+
+ return NULL;
+}
+
+static pdf_jsimp_obj *field_getName(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_obj *field = (pdf_obj *)obj;
+ char *name;
+ pdf_jsimp_obj *oname = NULL;
+
+ if (field == NULL)
+ return NULL;
+
+ name = pdf_field_name(js->doc, field);
+ fz_try(ctx)
+ {
+ oname = pdf_jsimp_from_string(js->imp, name);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, name);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return oname;
+}
+
+static void field_setName(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_warn(js->doc->ctx, "Unexpected call to field_setName");
+}
+
+static pdf_jsimp_obj *field_getDisplay(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ pdf_obj *field = (pdf_obj *)obj;
+
+ return field ? pdf_jsimp_from_number(js->imp, (double)pdf_field_display(js->doc, field)) : NULL;
+}
+
+static void field_setDisplay(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_obj *field = (pdf_obj *)obj;
+
+ if (field)
+ pdf_field_set_display(js->doc, field, (int)pdf_jsimp_to_number(js->imp, val));
+}
+
+static pdf_jsimp_obj *field_getFillColor(void *jsctx, void *obj)
+{
+ return NULL;
+}
+
+static void field_setFillColor(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_obj *field = (pdf_obj *)obj;
+ pdf_obj *col;
+
+ if (!field)
+ return;
+
+ col = load_color(js->doc->ctx, js->imp, val);
+ fz_try(ctx)
+ {
+ pdf_field_set_fill_color(js->doc, field, col);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(col);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static pdf_jsimp_obj *field_getTextColor(void *jsctx, void *obj)
+{
+ return NULL;
+}
+
+static void field_setTextColor(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_obj *field = (pdf_obj *)obj;
+ pdf_obj *col;
+
+ if (!field)
+ return;
+
+ col = load_color(js->doc->ctx, js->imp, val);
+ fz_try(ctx)
+ {
+ pdf_field_set_text_color(js->doc, field, col);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(col);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static pdf_jsimp_obj *field_getBorderStyle(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ pdf_obj *field = (pdf_obj *)obj;
+
+ return field ? pdf_jsimp_from_string(js->imp, pdf_field_border_style(js->doc, field)) : NULL;
+}
+
+static void field_setBorderStyle(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ pdf_obj *field = (pdf_obj *)obj;
+
+ if (field)
+ pdf_field_set_border_style(js->doc, field, pdf_jsimp_to_string(js->imp, val));
+}
+
+static pdf_jsimp_obj *field_getValue(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ pdf_obj *field = (pdf_obj *)obj;
+ char *fval;
+
+ if (!field)
+ return NULL;
+
+ fval = pdf_field_value(js->doc, field);
+ return pdf_jsimp_from_string(js->imp, fval?fval:"");
+}
+
+static void field_setValue(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ pdf_obj *field = (pdf_obj *)obj;
+
+ if (field)
+ (void)pdf_field_set_value(js->doc, field, pdf_jsimp_to_string(js->imp, val));
+}
+
+static pdf_jsimp_obj *event_getTarget(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ return pdf_jsimp_new_obj(js->imp, js->fieldtype, js->event.target);
+}
+
+static void event_setTarget(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_warn(js->doc->ctx, "Unexpected call to event_setTarget");
+}
+
+static pdf_jsimp_obj *event_getValue(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ char *v = js->event.value;
+
+ return pdf_jsimp_from_string(js->imp, v?v:"");
+}
+
+static void event_setValue(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ fz_free(ctx, js->event.value);
+ js->event.value = NULL;
+ js->event.value = fz_strdup(ctx, pdf_jsimp_to_string(js->imp, val));
+}
+
+static pdf_jsimp_obj *event_getWillCommit(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ return pdf_jsimp_from_number(js->imp, 1.0);
+}
+
+static void event_setWillCommit(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_warn(js->doc->ctx, "Unexpected call to event_setWillCommit");
+}
+
+static pdf_jsimp_obj *event_getRC(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ return pdf_jsimp_from_number(js->imp, (double)js->event.rc);
+}
+
+static void event_setRC(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ js->event.rc = (int)pdf_jsimp_to_number(js->imp, val);
+}
+
+static pdf_jsimp_obj *doc_getEvent(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ return pdf_jsimp_new_obj(js->imp, js->eventtype, &js->event);
+}
+
+static void doc_setEvent(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_warn(js->doc->ctx, "Unexpected call to doc_setEvent");
+}
+
+static pdf_jsimp_obj *doc_getApp(void *jsctx, void *obj)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ return pdf_jsimp_new_obj(js->imp, js->apptype, NULL);
+}
+
+static void doc_setApp(void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_warn(js->doc->ctx, "Unexpected call to doc_setApp");
+}
+
+static char *utf8_to_pdf(fz_context *ctx, char *utf8)
+{
+ char *pdf = fz_malloc(ctx, strlen(utf8)+1);
+ int i = 0;
+ unsigned char c;
+
+ while ((c = *utf8) != 0)
+ {
+ if ((c & 0x80) == 0 && pdf_doc_encoding[c] == c)
+ {
+ pdf[i++] = c;
+ utf8++ ;
+ }
+ else
+ {
+ int rune;
+ int j;
+
+ utf8 += fz_chartorune(&rune, utf8);
+
+ for (j = 0; j < sizeof(pdf_doc_encoding) && pdf_doc_encoding[j] != rune; j++)
+ ;
+
+ if (j < sizeof(pdf_doc_encoding))
+ pdf[i++] = j;
+ }
+ }
+
+ pdf[i] = 0;
+
+ return pdf;
+}
+
+static pdf_jsimp_obj *doc_getField(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_obj *dict = NULL;
+ char *utf8;
+ char *name = NULL;
+
+ if (argc != 1)
+ return NULL;
+
+ fz_var(dict);
+ fz_var(name);
+ fz_try(ctx)
+ {
+ utf8 = pdf_jsimp_to_string(js->imp, args[0]);
+
+ if (utf8)
+ {
+ name = utf8_to_pdf(ctx, utf8);
+ dict = pdf_lookup_field(js->form, name);
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, name);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(ctx, "doc_getField failed: %s", fz_caught_message(ctx));
+ dict = NULL;
+ }
+
+ return dict ? pdf_jsimp_new_obj(js->imp, js->fieldtype, dict) : NULL;
+}
+
+static void reset_field(pdf_js *js, pdf_jsimp_obj *item)
+{
+ fz_context *ctx = js->doc->ctx;
+ char *name = NULL;
+ char *utf8 = pdf_jsimp_to_string(js->imp, item);
+
+ if (utf8)
+ {
+ pdf_obj *field;
+
+ fz_var(name);
+ fz_try(ctx)
+ {
+ name = utf8_to_pdf(ctx, utf8);
+ field = pdf_lookup_field(js->form, name);
+ if (field)
+ pdf_field_reset(js->doc, field);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, name);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+}
+
+static pdf_jsimp_obj *doc_resetForm(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_jsimp_obj *arr = NULL;
+ pdf_jsimp_obj *elem = NULL;
+
+ switch (argc)
+ {
+ case 0:
+ break;
+ case 1:
+ switch (pdf_jsimp_to_type(js->imp, args[0]))
+ {
+ case JS_TYPE_NULL:
+ break;
+ case JS_TYPE_ARRAY:
+ arr = args[0];
+ break;
+ case JS_TYPE_STRING:
+ elem = args[0];
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ default:
+ return NULL;
+ }
+
+ fz_try(ctx)
+ {
+ if(arr)
+ {
+ /* An array of fields has been passed in. Call
+ * pdf_reset_field on each */
+ int i, n = pdf_jsimp_array_len(js->imp, arr);
+
+ for (i = 0; i < n; i++)
+ {
+ pdf_jsimp_obj *item = pdf_jsimp_array_item(js->imp, arr, i);
+
+ if (item)
+ reset_field(js, item);
+
+ }
+ }
+ else if (elem)
+ {
+ reset_field(js, elem);
+ }
+ else
+ {
+ /* No argument or null passed in means reset all. */
+ int i, n = pdf_array_len(js->form);
+
+ for (i = 0; i < n; i++)
+ pdf_field_reset(js->doc, pdf_array_get(js->form, i));
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "doc_resetForm failed: %s", fz_caught_message(ctx));
+ }
+
+ return NULL;
+}
+
+static pdf_jsimp_obj *doc_print(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+
+ pdf_event_issue_print(js->doc);
+
+ return NULL;
+}
+
+static pdf_jsimp_obj *doc_mailDoc(void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ pdf_js *js = (pdf_js *)jsctx;
+ fz_context *ctx = js->doc->ctx;
+ pdf_jsimp_obj *bUI_obj = NULL;
+ pdf_jsimp_obj *cTo_obj = NULL;
+ pdf_jsimp_obj *cCc_obj = NULL;
+ pdf_jsimp_obj *cBcc_obj = NULL;
+ pdf_jsimp_obj *cSubject_obj = NULL;
+ pdf_jsimp_obj *cMessage_obj = NULL;
+ pdf_mail_doc_event event;
+ int arg_is_obj = 0;
+
+ if (argc < 1 || argc > 6)
+ return NULL;
+
+ event.ask_user = 1;
+ event.to = "";
+ event.cc = "";
+ event.bcc = "";
+ event.subject = "";
+ event.message = "";
+
+ fz_var(bUI_obj);
+ fz_var(cTo_obj);
+ fz_var(cCc_obj);
+ fz_var(cBcc_obj);
+ fz_var(cSubject_obj);
+ fz_var(cMessage_obj);
+ fz_try(ctx)
+ {
+ arg_is_obj = (argc == 1 && pdf_jsimp_to_type(js->imp, args[0]) != JS_TYPE_BOOLEAN);
+ if (arg_is_obj)
+ {
+ bUI_obj = pdf_jsimp_property(js->imp, args[0], "bUI");
+ cTo_obj = pdf_jsimp_property(js->imp, args[0], "cTo");
+ cCc_obj = pdf_jsimp_property(js->imp, args[0], "cCc");
+ cBcc_obj = pdf_jsimp_property(js->imp, args[0], "cBcc");
+ cSubject_obj = pdf_jsimp_property(js->imp, args[0], "cSubject");
+ cMessage_obj = pdf_jsimp_property(js->imp, args[0], "cMessage");
+ }
+ else
+ {
+ switch (argc)
+ {
+ case 6:
+ cMessage_obj = args[5];
+ case 5:
+ cSubject_obj = args[4];
+ case 4:
+ cBcc_obj = args[3];
+ case 3:
+ cCc_obj = args[2];
+ case 2:
+ cTo_obj = args[1];
+ case 1:
+ bUI_obj = args[0];
+ }
+ }
+
+ if (bUI_obj)
+ event.ask_user = (int)pdf_jsimp_to_number(js->imp, bUI_obj);
+
+ if (cTo_obj)
+ event.to = pdf_jsimp_to_string(js->imp, cTo_obj);
+
+ if (cCc_obj)
+ event.cc = pdf_jsimp_to_string(js->imp, cCc_obj);
+
+ if (cBcc_obj)
+ event.bcc = pdf_jsimp_to_string(js->imp, cBcc_obj);
+
+ if (cSubject_obj)
+ event.subject = pdf_jsimp_to_string(js->imp, cSubject_obj);
+
+ if (cMessage_obj)
+ event.message = pdf_jsimp_to_string(js->imp, cMessage_obj);
+
+ pdf_event_issue_mail_doc(js->doc, &event);
+ }
+ fz_always(ctx)
+ {
+ if (arg_is_obj)
+ {
+ pdf_jsimp_drop_obj(js->imp, bUI_obj);
+ pdf_jsimp_drop_obj(js->imp, cTo_obj);
+ pdf_jsimp_drop_obj(js->imp, cCc_obj);
+ pdf_jsimp_drop_obj(js->imp, cBcc_obj);
+ pdf_jsimp_drop_obj(js->imp, cSubject_obj);
+ pdf_jsimp_drop_obj(js->imp, cMessage_obj);
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return NULL;
+}
+
+static void declare_dom(pdf_js *js)
+{
+ pdf_jsimp *imp = js->imp;
+
+ /* Create the document type */
+ js->doctype = pdf_jsimp_new_type(imp, NULL);
+ pdf_jsimp_addmethod(imp, js->doctype, "getField", doc_getField);
+ pdf_jsimp_addmethod(imp, js->doctype, "resetForm", doc_resetForm);
+ pdf_jsimp_addmethod(imp, js->doctype, "print", doc_print);
+ pdf_jsimp_addmethod(imp, js->doctype, "mailDoc", doc_mailDoc);
+ pdf_jsimp_addproperty(imp, js->doctype, "event", doc_getEvent, doc_setEvent);
+ pdf_jsimp_addproperty(imp, js->doctype, "app", doc_getApp, doc_setApp);
+
+ /* Create the event type */
+ js->eventtype = pdf_jsimp_new_type(imp, NULL);
+ pdf_jsimp_addproperty(imp, js->eventtype, "target", event_getTarget, event_setTarget);
+ pdf_jsimp_addproperty(imp, js->eventtype, "value", event_getValue, event_setValue);
+ pdf_jsimp_addproperty(imp, js->eventtype, "willCommit", event_getWillCommit, event_setWillCommit);
+ pdf_jsimp_addproperty(imp, js->eventtype, "rc", event_getRC, event_setRC);
+
+ /* Create the field type */
+ js->fieldtype = pdf_jsimp_new_type(imp, NULL);
+ pdf_jsimp_addproperty(imp, js->fieldtype, "value", field_getValue, field_setValue);
+ pdf_jsimp_addproperty(imp, js->fieldtype, "borderStyle", field_getBorderStyle, field_setBorderStyle);
+ pdf_jsimp_addproperty(imp, js->fieldtype, "textColor", field_getTextColor, field_setTextColor);
+ pdf_jsimp_addproperty(imp, js->fieldtype, "fillColor", field_getFillColor, field_setFillColor);
+ pdf_jsimp_addproperty(imp, js->fieldtype, "display", field_getDisplay, field_setDisplay);
+ pdf_jsimp_addproperty(imp, js->fieldtype, "name", field_getName, field_setName);
+ pdf_jsimp_addmethod(imp, js->fieldtype, "buttonSetCaption", field_buttonSetCaption);
+
+ /* Create the app type */
+ js->apptype = pdf_jsimp_new_type(imp, NULL);
+ pdf_jsimp_addmethod(imp, js->apptype, "alert", app_alert);
+ pdf_jsimp_addmethod(imp, js->apptype, "execDialog", app_execDialog);
+ pdf_jsimp_addmethod(imp, js->apptype, "execMenuItem", app_execMenuItem);
+ pdf_jsimp_addmethod(imp, js->apptype, "launchURL", app_launchURL);
+
+ /* Create the document object and tell the engine to use */
+ pdf_jsimp_set_global_type(js->imp, js->doctype);
+}
+
+static void preload_helpers(pdf_js *js)
+{
+ /* When testing on the cluster, redefine the Date object
+ * to use a fixed date */
+#ifdef CLUSTER
+ pdf_jsimp_execute(js->imp,
+"var MuPDFOldDate = Date\n"
+"Date = function() { return new MuPDFOldDate(1979,5,15); }\n"
+ );
+#endif
+
+ pdf_jsimp_execute(js->imp,
+#include "gen_js_util.h"
+ );
+}
+
+pdf_js *pdf_new_js(pdf_document *doc)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_js *js = NULL;
+
+ fz_var(js);
+ fz_try(ctx)
+ {
+ pdf_obj *root, *acroform;
+
+ js = fz_malloc_struct(ctx, pdf_js);
+ js->doc = doc;
+
+ /* Find the form array */
+ root = pdf_dict_gets(pdf_trailer(doc), "Root");
+ acroform = pdf_dict_gets(root, "AcroForm");
+ js->form = pdf_dict_gets(acroform, "Fields");
+
+ /* Initialise the javascript engine, passing the main context
+ * for use in memory allocation and exception handling. Also
+ * pass our js context, for it to pass back to us. */
+ js->imp = pdf_new_jsimp(ctx, js);
+ declare_dom(js);
+
+ preload_helpers(js);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_js(js);
+ js = NULL;
+ }
+
+ return js;
+}
+
+void pdf_js_load_document_level(pdf_js *js)
+{
+ pdf_document *doc = js->doc;
+ fz_context *ctx = doc->ctx;
+ pdf_obj *javascript = NULL;
+ char *codebuf = NULL;
+
+ fz_var(javascript);
+ fz_var(codebuf);
+ fz_try(ctx)
+ {
+ int len, i;
+
+ javascript = pdf_load_name_tree(doc, "JavaScript");
+ len = pdf_dict_len(javascript);
+
+ for (i = 0; i < len; i++)
+ {
+ pdf_obj *fragment = pdf_dict_get_val(javascript, i);
+ pdf_obj *code = pdf_dict_gets(fragment, "JS");
+
+ fz_var(codebuf);
+ fz_try(ctx)
+ {
+ codebuf = pdf_to_utf8(doc, code);
+ pdf_jsimp_execute(js->imp, codebuf);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, codebuf);
+ codebuf = NULL;
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(ctx, "Warning: %s", fz_caught_message(ctx));
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(javascript);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void pdf_drop_js(pdf_js *js)
+{
+ if (js)
+ {
+ fz_context *ctx = js->doc->ctx;
+ fz_free(ctx, js->event.value);
+ pdf_jsimp_drop_type(js->imp, js->apptype);
+ pdf_jsimp_drop_type(js->imp, js->fieldtype);
+ pdf_jsimp_drop_type(js->imp, js->doctype);
+ pdf_drop_jsimp(js->imp);
+ fz_free(ctx, js);
+ }
+}
+
+void pdf_js_setup_event(pdf_js *js, pdf_js_event *e)
+{
+ if (js)
+ {
+ fz_context *ctx = js->doc->ctx;
+ char *ev = e->value ? e->value : "";
+ char *v = fz_strdup(ctx, ev);
+
+ fz_free(ctx, js->event.value);
+ js->event.value = v;
+
+ js->event.target = e->target;
+ js->event.rc = 1;
+ }
+}
+
+pdf_js_event *pdf_js_get_event(pdf_js *js)
+{
+ return js ? &js->event : NULL;
+}
+
+void pdf_js_execute(pdf_js *js, char *code)
+{
+ if (js)
+ {
+ fz_context *ctx = js->doc->ctx;
+ fz_try(ctx)
+ {
+ pdf_jsimp_execute(js->imp, code);
+ }
+ fz_catch(ctx)
+ {
+ }
+ }
+}
+
+void pdf_js_execute_count(pdf_js *js, char *code, int count)
+{
+ if (js)
+ {
+ fz_context *ctx = js->doc->ctx;
+ fz_try(ctx)
+ {
+ pdf_jsimp_execute_count(js->imp, code, count);
+ }
+ fz_catch(ctx)
+ {
+ }
+ }
+}
+
+int pdf_js_supported(void)
+{
+ return 1;
+}
diff --git a/source/pdf/js/pdf-jsimp-cpp.c b/source/pdf/js/pdf-jsimp-cpp.c
new file mode 100644
index 00000000..7e8031a6
--- /dev/null
+++ b/source/pdf/js/pdf-jsimp-cpp.c
@@ -0,0 +1,225 @@
+/* This file contains wrapper functions for pdf_jsimp functions implemented
+ * in C++, from which calls to fz_throw aren't safe. The C++ versions
+ * return errors explicitly, and these wrappers then throw them. */
+
+#include "mupdf/pdf.h"
+#include "pdf-jsimp-cpp.h"
+
+pdf_jsimp *pdf_new_jsimp(fz_context *ctx, void *jsctx)
+{
+ pdf_jsimp *jsi = NULL;
+ const char *err = pdf_new_jsimp_cpp(ctx, jsctx, &jsi);
+ if (err != NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "%s", err);
+
+ return jsi;
+}
+
+void pdf_drop_jsimp(pdf_jsimp *imp)
+{
+ if (imp)
+ {
+ fz_context *ctx = pdf_jsimp_ctx_cpp(imp);
+ const char *err = pdf_drop_jsimp_cpp(imp);
+ if (err != NULL)
+ fz_warn(ctx, "%s", err);
+ }
+}
+
+pdf_jsimp_type *pdf_jsimp_new_type(pdf_jsimp *imp, pdf_jsimp_dtr *dtr)
+{
+ pdf_jsimp_type *type = NULL;
+ const char *err = pdf_jsimp_new_type_cpp(imp, dtr, &type);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return type;
+}
+
+void pdf_jsimp_drop_type(pdf_jsimp *imp, pdf_jsimp_type *type)
+{
+ const char *err = pdf_jsimp_drop_type_cpp(imp, type);
+ if (err != NULL)
+ fz_warn(pdf_jsimp_ctx_cpp(imp), "%s", err);
+}
+
+void pdf_jsimp_addmethod(pdf_jsimp *imp, pdf_jsimp_type *type, char *name, pdf_jsimp_method *meth)
+{
+ const char *err = pdf_jsimp_addmethod_cpp(imp, type, name, meth);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+}
+
+void pdf_jsimp_addproperty(pdf_jsimp *imp, pdf_jsimp_type *type, char *name, pdf_jsimp_getter *get, pdf_jsimp_setter *set)
+{
+ const char *err = pdf_jsimp_addproperty_cpp(imp, type, name, get, set);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+}
+
+void pdf_jsimp_set_global_type(pdf_jsimp *imp, pdf_jsimp_type *type)
+{
+ const char *err = pdf_jsimp_set_global_type_cpp(imp, type);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+}
+
+pdf_jsimp_obj *pdf_jsimp_new_obj(pdf_jsimp *imp, pdf_jsimp_type *type, void *natobj)
+{
+ pdf_jsimp_obj *obj = NULL;
+ const char *err = pdf_jsimp_new_obj_cpp(imp, type, natobj, &obj);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return obj;
+}
+
+void pdf_jsimp_drop_obj(pdf_jsimp *imp, pdf_jsimp_obj *obj)
+{
+ const char *err = pdf_jsimp_drop_obj_cpp(imp, obj);
+ if (err != NULL)
+ fz_warn(pdf_jsimp_ctx_cpp(imp), "%s", err);
+}
+
+int pdf_jsimp_to_type(pdf_jsimp *imp, pdf_jsimp_obj *obj)
+{
+ int type = 0;
+ const char *err = pdf_jsimp_to_type_cpp(imp, obj, &type);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return type;
+}
+
+pdf_jsimp_obj *pdf_jsimp_from_string(pdf_jsimp *imp, char *str)
+{
+ pdf_jsimp_obj *obj = NULL;
+ const char *err = pdf_jsimp_from_string_cpp(imp, str, &obj);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return obj;
+}
+
+char *pdf_jsimp_to_string(pdf_jsimp *imp, pdf_jsimp_obj *obj)
+{
+ char *str = NULL;
+ const char *err = pdf_jsimp_to_string_cpp(imp, obj, &str);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return str;
+}
+
+pdf_jsimp_obj *pdf_jsimp_from_number(pdf_jsimp *imp, double num)
+{
+ pdf_jsimp_obj *obj = NULL;
+ const char *err = pdf_jsimp_from_number_cpp(imp, num, &obj);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return obj;
+}
+
+double pdf_jsimp_to_number(pdf_jsimp *imp, pdf_jsimp_obj *obj)
+{
+ double num;
+ const char *err = pdf_jsimp_to_number_cpp(imp, obj, &num);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return num;
+}
+
+int pdf_jsimp_array_len(pdf_jsimp *imp, pdf_jsimp_obj *obj)
+{
+ int len = 0;
+ const char *err = pdf_jsimp_array_len_cpp(imp, obj, &len);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return len;
+}
+
+pdf_jsimp_obj *pdf_jsimp_array_item(pdf_jsimp *imp, pdf_jsimp_obj *obj, int i)
+{
+ pdf_jsimp_obj *item = NULL;
+ const char *err = pdf_jsimp_array_item_cpp(imp, obj, i, &item);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return item;
+}
+
+pdf_jsimp_obj *pdf_jsimp_property(pdf_jsimp *imp, pdf_jsimp_obj *obj, char *prop)
+{
+ pdf_jsimp_obj *pobj = NULL;
+ const char *err = pdf_jsimp_property_cpp(imp, obj, prop, &pobj);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+
+ return pobj;
+}
+
+void pdf_jsimp_execute(pdf_jsimp *imp, char *code)
+{
+ const char *err = pdf_jsimp_execute_cpp(imp, code);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+}
+
+void pdf_jsimp_execute_count(pdf_jsimp *imp, char *code, int count)
+{
+ const char *err = pdf_jsimp_execute_count_cpp(imp, code, count);
+ if (err != NULL)
+ fz_throw(pdf_jsimp_ctx_cpp(imp), FZ_ERROR_GENERIC, "%s", err);
+}
+pdf_jsimp_obj *pdf_jsimp_call_method(pdf_jsimp *imp, pdf_jsimp_method *meth, void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[])
+{
+ fz_context *ctx = pdf_jsimp_ctx_cpp(imp);
+ pdf_jsimp_obj *res;
+
+ fz_try(ctx)
+ {
+ res = meth(jsctx, obj, argc, args);
+ }
+ fz_catch(ctx)
+ {
+ res = NULL;
+ fz_warn(ctx, "%s", fz_caught_message(ctx));
+ }
+
+ return res;
+}
+
+pdf_jsimp_obj *pdf_jsimp_call_getter(pdf_jsimp *imp, pdf_jsimp_getter *get, void *jsctx, void *obj)
+{
+ fz_context *ctx = pdf_jsimp_ctx_cpp(imp);
+ pdf_jsimp_obj *res;
+
+ fz_try(ctx)
+ {
+ res = get(jsctx, obj);
+ }
+ fz_catch(ctx)
+ {
+ res = NULL;
+ fz_warn(ctx, "%s", fz_caught_message(ctx));
+ }
+
+ return res;
+}
+
+void pdf_jsimp_call_setter(pdf_jsimp *imp, pdf_jsimp_setter *set, void *jsctx, void *obj, pdf_jsimp_obj *val)
+{
+ fz_context *ctx = pdf_jsimp_ctx_cpp(imp);
+
+ fz_try(ctx)
+ {
+ set(jsctx, obj, val);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "%s", fz_caught_message(ctx));
+ }
+}
diff --git a/source/pdf/js/pdf-jsimp-cpp.h b/source/pdf/js/pdf-jsimp-cpp.h
new file mode 100644
index 00000000..83af1d23
--- /dev/null
+++ b/source/pdf/js/pdf-jsimp-cpp.h
@@ -0,0 +1,29 @@
+/* C++ version of the pdf_jsimp api. C++ cannot safely call fz_throw,
+ * so C++ implementations return explicit errors in char * form. */
+
+fz_context *pdf_jsimp_ctx_cpp(pdf_jsimp *imp);
+const char *pdf_new_jsimp_cpp(fz_context *ctx, void *jsctx, pdf_jsimp **imp);
+const char *pdf_drop_jsimp_cpp(pdf_jsimp *imp);
+const char *pdf_jsimp_new_type_cpp(pdf_jsimp *imp, pdf_jsimp_dtr *dtr, pdf_jsimp_type **type);
+const char *pdf_jsimp_drop_type_cpp(pdf_jsimp *imp, pdf_jsimp_type *type);
+const char *pdf_jsimp_addmethod_cpp(pdf_jsimp *imp, pdf_jsimp_type *type, char *name, pdf_jsimp_method *meth);
+const char *pdf_jsimp_addproperty_cpp(pdf_jsimp *imp, pdf_jsimp_type *type, char *name, pdf_jsimp_getter *get, pdf_jsimp_setter *set);
+const char *pdf_jsimp_set_global_type_cpp(pdf_jsimp *imp, pdf_jsimp_type *type);
+const char *pdf_jsimp_new_obj_cpp(pdf_jsimp *imp, pdf_jsimp_type *type, void *natobj, pdf_jsimp_obj **obj);
+const char *pdf_jsimp_drop_obj_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj);
+const char *pdf_jsimp_to_type_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, int *type);
+const char *pdf_jsimp_from_string_cpp(pdf_jsimp *imp, char *str, pdf_jsimp_obj **obj);
+const char *pdf_jsimp_to_string_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, char **str);
+const char *pdf_jsimp_from_number_cpp(pdf_jsimp *imp, double num, pdf_jsimp_obj **obj);
+const char *pdf_jsimp_to_number_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, double *num);
+const char *pdf_jsimp_array_len_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, int *len);
+const char *pdf_jsimp_array_item_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, int i, pdf_jsimp_obj **item);
+const char *pdf_jsimp_property_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, char *prop, pdf_jsimp_obj **pobj);
+const char *pdf_jsimp_execute_cpp(pdf_jsimp *imp, char *code);
+const char *pdf_jsimp_execute_count_cpp(pdf_jsimp *imp, char *code, int count);
+
+/* Also when calling back into mupdf, all exceptions must be caught. The functions bellow
+ * wrap these calls */
+pdf_jsimp_obj *pdf_jsimp_call_method(pdf_jsimp *imp, pdf_jsimp_method *meth, void *jsctx, void *obj, int argc, pdf_jsimp_obj *args[]);
+pdf_jsimp_obj *pdf_jsimp_call_getter(pdf_jsimp *imp, pdf_jsimp_getter *get, void *jsctx, void *obj);
+void pdf_jsimp_call_setter(pdf_jsimp *imp, pdf_jsimp_setter *set, void *jsctx, void *obj, pdf_jsimp_obj *val);
diff --git a/source/pdf/js/pdf-jsimp-v8.cpp b/source/pdf/js/pdf-jsimp-v8.cpp
new file mode 100644
index 00000000..f3894b6b
--- /dev/null
+++ b/source/pdf/js/pdf-jsimp-v8.cpp
@@ -0,0 +1,476 @@
+/*
+ * This is a dummy JavaScript engine. It cheats by recognising the specific
+ * strings in calc.pdf, and hence will work only for that file. It is for
+ * testing only.
+ */
+
+extern "C" {
+#include "mupdf/fitz.h"
+#include "mupdf/pdf.h"
+#include "pdf-jsimp-cpp.h"
+}
+
+#include <vector>
+#include <set>
+#include <v8.h>
+
+using namespace v8;
+using namespace std;
+
+struct PDFJSImp;
+
+/* Object we pass to FunctionTemplate::New, which v8 passes back to us in
+ * callMethod, allowing us to call our client's, passed-in method. */
+struct PDFJSImpMethod
+{
+ PDFJSImp *imp;
+ pdf_jsimp_method *meth;
+
+ PDFJSImpMethod(PDFJSImp *imp, pdf_jsimp_method *meth) : imp(imp), meth(meth) {}
+};
+
+/* Object we pass to ObjectTemplate::SetAccessor, which v8 passes back to us in
+ * setProp and getProp, allowing us to call our client's, passed-in set/get methods. */
+struct PDFJSImpProperty
+{
+ PDFJSImp *imp;
+ pdf_jsimp_getter *get;
+ pdf_jsimp_setter *set;
+
+ PDFJSImpProperty(PDFJSImp *imp, pdf_jsimp_getter *get, pdf_jsimp_setter *set) : imp(imp), get(get), set(set) {}
+};
+
+/* Internal representation of the pdf_jsimp_type object */
+struct PDFJSImpType
+{
+ PDFJSImp *imp;
+ Persistent<ObjectTemplate> templ;
+ pdf_jsimp_dtr *dtr;
+ vector<PDFJSImpMethod *> methods;
+ vector<PDFJSImpProperty *> properties;
+
+ PDFJSImpType(PDFJSImp *imp, pdf_jsimp_dtr *dtr): imp(imp), dtr(dtr)
+ {
+ HandleScope scope;
+ templ = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ templ->SetInternalFieldCount(1);
+ }
+
+ ~PDFJSImpType()
+ {
+ vector<PDFJSImpMethod *>::iterator mit;
+ for (mit = methods.begin(); mit < methods.end(); mit++)
+ delete *mit;
+
+ vector<PDFJSImpProperty *>::iterator pit;
+ for (pit = properties.begin(); pit < properties.end(); pit++)
+ delete *pit;
+
+ templ.Dispose();
+ }
+};
+
+/* Info via which we destroy the client side part of objects that
+ * v8 garbage collects */
+struct PDFJSImpGCObj
+{
+ Persistent<Object> pobj;
+ PDFJSImpType *type;
+
+ PDFJSImpGCObj(Handle<Object> obj, PDFJSImpType *type): type(type)
+ {
+ pobj = Persistent<Object>::New(obj);
+ }
+
+ ~PDFJSImpGCObj()
+ {
+ pobj.Dispose();
+ }
+};
+
+/* Internal representation of the pdf_jsimp object */
+struct PDFJSImp
+{
+ fz_context *ctx;
+ void *jsctx;
+ Persistent<Context> context;
+ vector<PDFJSImpType *> types;
+ set<PDFJSImpGCObj *> gclist;
+
+ PDFJSImp(fz_context *ctx, void *jsctx) : ctx(ctx), jsctx(jsctx)
+ {
+ HandleScope scope;
+ context = Persistent<Context>::New(Context::New());
+ }
+
+ ~PDFJSImp()
+ {
+ HandleScope scope;
+ /* Tell v8 our context will not be used again */
+ context.Dispose();
+
+ /* Unlink and destroy all the objects that v8 has yet to gc */
+ set<PDFJSImpGCObj *>::iterator oit;
+ for (oit = gclist.begin(); oit != gclist.end(); oit++)
+ {
+ (*oit)->pobj.ClearWeak(); /* So that gcCallback wont get called */
+ PDFJSImpType *vType = (*oit)->type;
+ Local<External> owrap = Local<External>::Cast((*oit)->pobj->GetInternalField(0));
+ vType->dtr(vType->imp->jsctx, owrap->Value());
+ delete *oit;
+ }
+
+ vector<PDFJSImpType *>::iterator it;
+ for (it = types.begin(); it < types.end(); it++)
+ delete *it;
+ }
+};
+
+/* Internal representation of the pdf_jsimp_obj object */
+class PDFJSImpObject
+{
+ Persistent<Value> pobj;
+ String::Utf8Value *utf8;
+
+public:
+ PDFJSImpObject(Handle<Value> obj): utf8(NULL)
+ {
+ pobj = Persistent<Value>::New(obj);
+ }
+
+ PDFJSImpObject(const char *str): utf8(NULL)
+ {
+ pobj = Persistent<Value>::New(String::New(str));
+ }
+
+ PDFJSImpObject(double num): utf8(NULL)
+ {
+ pobj = Persistent<Value>::New(Number::New(num));
+ }
+
+ ~PDFJSImpObject()
+ {
+ delete utf8;
+ pobj.Dispose();
+ }
+
+ int type()
+ {
+ if (pobj->IsNull())
+ return JS_TYPE_NULL;
+ else if (pobj->IsString() || pobj->IsStringObject())
+ return JS_TYPE_STRING;
+ else if (pobj->IsNumber() || pobj->IsNumberObject())
+ return JS_TYPE_NUMBER;
+ else if (pobj->IsArray())
+ return JS_TYPE_ARRAY;
+ else if (pobj->IsBoolean() || pobj->IsBooleanObject())
+ return JS_TYPE_BOOLEAN;
+ else
+ return JS_TYPE_UNKNOWN;
+ }
+
+ char *toString()
+ {
+ delete utf8;
+ utf8 = new String::Utf8Value(pobj);
+ return **utf8;
+ }
+
+ double toNumber()
+ {
+ return pobj->NumberValue();
+ }
+
+ Handle<Value> toValue()
+ {
+ return pobj;
+ }
+};
+
+extern "C" fz_context *pdf_jsimp_ctx_cpp(pdf_jsimp *imp)
+{
+ return reinterpret_cast<PDFJSImp *>(imp)->ctx;
+}
+
+extern "C" const char *pdf_new_jsimp_cpp(fz_context *ctx, void *jsctx, pdf_jsimp **imp)
+{
+ Locker lock;
+ *imp = reinterpret_cast<pdf_jsimp *>(new PDFJSImp(ctx, jsctx));
+
+ return NULL;
+}
+
+extern "C" const char *pdf_drop_jsimp_cpp(pdf_jsimp *imp)
+{
+ Locker lock;
+ delete reinterpret_cast<PDFJSImp *>(imp);
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_new_type_cpp(pdf_jsimp *imp, pdf_jsimp_dtr *dtr, pdf_jsimp_type **type)
+{
+ Locker lock;
+ PDFJSImp *vImp = reinterpret_cast<PDFJSImp *>(imp);
+ PDFJSImpType *vType = new PDFJSImpType(vImp, dtr);
+ vImp->types.push_back(vType);
+ *type = reinterpret_cast<pdf_jsimp_type *>(vType);
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_drop_type_cpp(pdf_jsimp *imp, pdf_jsimp_type *type)
+{
+ /* Types are recorded and destroyed as part of PDFJSImp */
+ return NULL;
+}
+
+static Handle<Value> callMethod(const Arguments &args)
+{
+ HandleScope scope;
+ Local<External> mwrap = Local<External>::Cast(args.Data());
+ PDFJSImpMethod *m = (PDFJSImpMethod *)mwrap->Value();
+
+ Local<Object> self = args.Holder();
+ Local<External> owrap;
+ void *nself = NULL;
+ if (self->InternalFieldCount() > 0)
+ {
+ owrap = Local<External>::Cast(self->GetInternalField(0));
+ nself = owrap->Value();
+ }
+
+ int c = args.Length();
+ PDFJSImpObject **native_args = new PDFJSImpObject*[c];
+ for (int i = 0; i < c; i++)
+ native_args[i] = new PDFJSImpObject(args[i]);
+
+ PDFJSImpObject *obj = reinterpret_cast<PDFJSImpObject *>(pdf_jsimp_call_method(reinterpret_cast<pdf_jsimp *>(m->imp), m->meth, m->imp->jsctx, nself, c, reinterpret_cast<pdf_jsimp_obj **>(native_args)));
+ Handle<Value> val;
+ if (obj)
+ val = obj->toValue();
+ delete obj;
+
+ for (int i = 0; i < c; i++)
+ delete native_args[i];
+
+ delete native_args;
+
+ return scope.Close(val);
+}
+
+extern "C" const char *pdf_jsimp_addmethod_cpp(pdf_jsimp *imp, pdf_jsimp_type *type, char *name, pdf_jsimp_method *meth)
+{
+ Locker lock;
+ PDFJSImpType *vType = reinterpret_cast<PDFJSImpType *>(type);
+ HandleScope scope;
+
+ PDFJSImpMethod *pmeth = new PDFJSImpMethod(vType->imp, meth);
+ vType->templ->Set(String::New(name), FunctionTemplate::New(callMethod, External::New(pmeth)));
+ vType->methods.push_back(pmeth);
+ return NULL;
+}
+
+static Handle<Value> getProp(Local<String> property, const AccessorInfo &info)
+{
+ HandleScope scope;
+ Local<External> pwrap = Local<External>::Cast(info.Data());
+ PDFJSImpProperty *p = reinterpret_cast<PDFJSImpProperty *>(pwrap->Value());
+
+ Local<Object> self = info.Holder();
+ Local<External> owrap;
+ void *nself = NULL;
+ if (self->InternalFieldCount() > 0)
+ {
+ Local<Value> val = self->GetInternalField(0);
+ if (val->IsExternal())
+ {
+ owrap = Local<External>::Cast(val);
+ nself = owrap->Value();
+ }
+ }
+
+ PDFJSImpObject *obj = reinterpret_cast<PDFJSImpObject *>(pdf_jsimp_call_getter(reinterpret_cast<pdf_jsimp *>(p->imp), p->get, p->imp->jsctx, nself));
+ Handle<Value> val;
+ if (obj)
+ val = obj->toValue();
+ delete obj;
+ return scope.Close(val);
+}
+
+static void setProp(Local<String> property, Local<Value> value, const AccessorInfo &info)
+{
+ HandleScope scope;
+ Local<External> wrap = Local<External>::Cast(info.Data());
+ PDFJSImpProperty *p = reinterpret_cast<PDFJSImpProperty *>(wrap->Value());
+
+ Local<Object> self = info.Holder();
+ Local<External> owrap;
+ void *nself = NULL;
+ if (self->InternalFieldCount() > 0)
+ {
+ owrap = Local<External>::Cast(self->GetInternalField(0));
+ nself = owrap->Value();
+ }
+
+ PDFJSImpObject *obj = new PDFJSImpObject(value);
+
+ pdf_jsimp_call_setter(reinterpret_cast<pdf_jsimp *>(p->imp), p->set, p->imp->jsctx, nself, reinterpret_cast<pdf_jsimp_obj *>(obj));
+ delete obj;
+}
+
+extern "C" const char *pdf_jsimp_addproperty_cpp(pdf_jsimp *imp, pdf_jsimp_type *type, char *name, pdf_jsimp_getter *get, pdf_jsimp_setter *set)
+{
+ Locker lock;
+ PDFJSImpType *vType = reinterpret_cast<PDFJSImpType *>(type);
+ HandleScope scope;
+
+ PDFJSImpProperty *prop = new PDFJSImpProperty(vType->imp, get, set);
+ vType->templ->SetAccessor(String::New(name), getProp, setProp, External::New(prop));
+ vType->properties.push_back(prop);
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_set_global_type_cpp(pdf_jsimp *imp, pdf_jsimp_type *type)
+{
+ Locker lock;
+ PDFJSImp *vImp = reinterpret_cast<PDFJSImp *>(imp);
+ PDFJSImpType *vType = reinterpret_cast<PDFJSImpType *>(type);
+ HandleScope scope;
+
+ vImp->context = Persistent<Context>::New(Context::New(NULL, vType->templ));
+ return NULL;
+}
+
+static void gcCallback(Persistent<Value> val, void *parm)
+{
+ PDFJSImpGCObj *gco = reinterpret_cast<PDFJSImpGCObj *>(parm);
+ PDFJSImpType *vType = gco->type;
+ HandleScope scope;
+ Persistent<Object> obj = Persistent<Object>::Cast(val);
+
+ Local<External> owrap = Local<External>::Cast(obj->GetInternalField(0));
+ vType->dtr(vType->imp->jsctx, owrap->Value());
+ vType->imp->gclist.erase(gco);
+ delete gco; /* Disposes of the persistent handle */
+}
+
+extern "C" const char *pdf_jsimp_new_obj_cpp(pdf_jsimp *imp, pdf_jsimp_type *type, void *natobj, pdf_jsimp_obj **robj)
+{
+ Locker lock;
+ PDFJSImpType *vType = reinterpret_cast<PDFJSImpType *>(type);
+ HandleScope scope;
+ Local<Object> obj = vType->templ->NewInstance();
+ obj->SetInternalField(0, External::New(natobj));
+
+ /* Arrange for destructor to be called on the client-side object
+ * when the v8 object is garbage collected */
+ if (vType->dtr)
+ {
+ /* Wrap obj in a PDFJSImpGCObj, which takes a persistent handle to
+ * obj, and stores its type with it. The persistent handle tells v8
+ * it cannot just destroy obj leaving the client-side object hanging */
+ PDFJSImpGCObj *gco = new PDFJSImpGCObj(obj, vType);
+ /* Keep the wrapped object in a list, so that we can take back control
+ * of destroying client-side objects when shutting down this context */
+ vType->imp->gclist.insert(gco);
+ /* Tell v8 that it can destroy the persistent handle to obj when it has
+ * no further need for it, but it must inform us via gcCallback */
+ gco->pobj.MakeWeak(gco, gcCallback);
+ }
+
+ *robj = reinterpret_cast<pdf_jsimp_obj *>(new PDFJSImpObject(obj));
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_drop_obj_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj)
+{
+ Locker lock;
+ delete reinterpret_cast<PDFJSImpObject *>(obj);
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_to_type_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, int *type)
+{
+ Locker lock;
+ *type = reinterpret_cast<PDFJSImpObject *>(obj)->type();
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_from_string_cpp(pdf_jsimp *imp, char *str, pdf_jsimp_obj **obj)
+{
+ Locker lock;
+ *obj = reinterpret_cast<pdf_jsimp_obj *>(new PDFJSImpObject(str));
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_to_string_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, char **str)
+{
+ Locker lock;
+ *str = reinterpret_cast<PDFJSImpObject *>(obj)->toString();
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_from_number_cpp(pdf_jsimp *imp, double num, pdf_jsimp_obj **obj)
+{
+ Locker lock;
+ *obj = reinterpret_cast<pdf_jsimp_obj *>(new PDFJSImpObject(num));
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_to_number_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, double *num)
+{
+ Locker lock;
+ *num = reinterpret_cast<PDFJSImpObject *>(obj)->toNumber();
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_array_len_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, int *len)
+{
+ Locker lock;
+ Local<Object> jsobj = reinterpret_cast<PDFJSImpObject *>(obj)->toValue()->ToObject();
+ Local<Array> arr = Local<Array>::Cast(jsobj);
+ *len = arr->Length();
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_array_item_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, int i, pdf_jsimp_obj **item)
+{
+ Locker lock;
+ Local<Object> jsobj = reinterpret_cast<PDFJSImpObject *>(obj)->toValue()->ToObject();
+ *item = reinterpret_cast<pdf_jsimp_obj *>(new PDFJSImpObject(jsobj->Get(Number::New(i))));
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_property_cpp(pdf_jsimp *imp, pdf_jsimp_obj *obj, char *prop, pdf_jsimp_obj **pobj)
+{
+ Locker lock;
+ Local<Object> jsobj = reinterpret_cast<PDFJSImpObject *>(obj)->toValue()->ToObject();
+ *pobj = reinterpret_cast<pdf_jsimp_obj *>(new PDFJSImpObject(jsobj->Get(String::New(prop))));
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_execute_cpp(pdf_jsimp *imp, char *code)
+{
+ Locker lock;
+ PDFJSImp *vImp = reinterpret_cast<PDFJSImp *>(imp);
+ HandleScope scope;
+ Context::Scope context_scope(vImp->context);
+ Handle<Script> script = Script::Compile(String::New(code));
+ if (script.IsEmpty())
+ return "compile failed in pdf_jsimp_execute";
+ script->Run();
+ return NULL;
+}
+
+extern "C" const char *pdf_jsimp_execute_count_cpp(pdf_jsimp *imp, char *code, int count)
+{
+ Locker lock;
+ PDFJSImp *vImp = reinterpret_cast<PDFJSImp *>(imp);
+ HandleScope scope;
+ Context::Scope context_scope(vImp->context);
+ Handle<Script> script = Script::Compile(String::New(code, count));
+ if (script.IsEmpty())
+ return "compile failed in pdf_jsimp_execute_count";
+ script->Run();
+ return NULL;
+}
diff --git a/source/pdf/js/pdf-util.js b/source/pdf/js/pdf-util.js
new file mode 100644
index 00000000..06f4874b
--- /dev/null
+++ b/source/pdf/js/pdf-util.js
@@ -0,0 +1,875 @@
+var MuPDF = new Array();
+
+MuPDF.monthName = ['January','February','March','April','May','June','July','August','September','October','November','December'];
+MuPDF.dayName = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
+
+MuPDF.shortMonthName = new Array();
+
+for (var i = 0; i < MuPDF.monthName.length; i++)
+ MuPDF.shortMonthName.push(MuPDF.monthName[i].substr(0,3));
+
+MuPDF.monthPattern = new RegExp();
+MuPDF.monthPattern.compile('('+MuPDF.shortMonthName.join('|')+')');
+
+MuPDF.padZeros = function(num, places)
+{
+ var s = num.toString();
+
+ if (s.length < places)
+ s = new Array(places-s.length+1).join('0') + s;
+
+ return s;
+}
+
+MuPDF.convertCase = function(str,cmd)
+{
+ switch (cmd)
+ {
+ case '>': return str.toUpperCase();
+ case '<': return str.toLowerCase();
+ default: return str;
+ }
+}
+
+/* display must be kept in sync with an enum in pdf_form.c */
+var display = new Array();
+display.visible = 0;
+display.hidden = 1;
+display.noPrint = 2;
+display.noView = 3;
+var border = new Array();
+border.s = "Solid";
+border.d = "Dashed";
+border.b = "Beveled";
+border.i = "Inset";
+border.u = "Underline";
+var color = new Array();
+color.transparent = [ "T" ];
+color.black = [ "G", 0];
+color.white = [ "G", 1];
+color.red = [ "RGB", 1,0,0 ];
+color.green = [ "RGB", 0,1,0 ];
+color.blue = [ "RGB", 0,0,1 ];
+color.cyan = [ "CMYK", 1,0,0,0 ];
+color.magenta = [ "CMYK", 0,1,0,0 ];
+color.yellow = [ "CMYK", 0,0,1,0 ];
+color.dkGray = [ "G", 0.25];
+color.gray = [ "G", 0.5];
+color.ltGray = [ "G", 0.75];
+
+var util = new Array();
+
+util.printd = function(fmt, d)
+{
+ var regexp = /(m+|d+|y+|H+|h+|M+|s+|t+|[^mdyHhMst]+)/g;
+ var res = '';
+
+ if (!d)
+ return null;
+
+ var tokens = fmt.match(regexp);
+ var length = tokens ? tokens.length : 0;
+
+ for (var i = 0; i < length; i++)
+ {
+ switch(tokens[i])
+ {
+ case 'mmmm': res += MuPDF.monthName[d.getMonth()]; break;
+ case 'mmm': res += MuPDF.monthName[d.getMonth()].substr(0,3); break;
+ case 'mm': res += MuPDF.padZeros(d.getMonth()+1, 2); break;
+ case 'm': res += d.getMonth()+1; break;
+ case 'dddd': res += MuPDF.dayName[d.getDay()]; break;
+ case 'ddd': res += MuPDF.dayName[d.getDay()].substr(0,3); break;
+ case 'dd': res += MuPDF.padZeros(d.getDate(), 2); break;
+ case 'd': res += d.getDate(); break;
+ case 'yyyy': res += d.getFullYear(); break;
+ case 'yy': res += d.getFullYear()%100; break;
+ case 'HH': res += MuPDF.padZeros(d.getHours(), 2); break;
+ case 'H': res += d.getHours(); break;
+ case 'hh': res += MuPDF.padZeros((d.getHours()+11)%12+1, 2); break;
+ case 'h': res += (d.getHours()+11)%12+1; break;
+ case 'MM': res += MuPDF.padZeros(d.getMinutes(), 2); break;
+ case 'M': res += d.getMinutes(); break;
+ case 'ss': res += MuPDF.padZeros(d.getSeconds(), 2); break;
+ case 's': res += d.getSeconds(); break;
+ case 'tt': res += d.getHours() < 12 ? 'am' : 'pm'; break;
+ case 't': res += d.getHours() < 12 ? 'a' : 'p'; break;
+ default: res += tokens[i];
+ }
+ }
+
+ return res;
+}
+
+util.printx = function(fmt, val)
+{
+ var cs = '=';
+ var res = '';
+ var i = 0;
+ var m;
+ var length = fmt ? fmt.length : 0;
+
+ while (i < length)
+ {
+ switch (fmt.charAt(i))
+ {
+ case '\\':
+ i++;
+ if (i >= length) return res;
+ res += fmt.charAt(i);
+ break;
+
+ case 'X':
+ m = val.match(/\w/);
+ if (!m) return res;
+ res += MuPDF.convertCase(m[0],cs);
+ val = val.replace(/^\W*\w/,'');
+ break;
+
+ case 'A':
+ m = val.match(/[A-z]/);
+ if (!m) return res;
+ res += MuPDF.convertCase(m[0],cs);
+ val = val.replace(/^[^A-z]*[A-z]/,'');
+ break;
+
+ case '9':
+ m = val.match(/\d/);
+ if (!m) return res;
+ res += m[0];
+ val = val.replace(/^\D*\d/,'');
+ break;
+
+ case '*':
+ res += val;
+ val = '';
+ break;
+
+ case '?':
+ if (!val) return res;
+ res += MuPDF.convertCase(val.charAt(0),cs);
+ val = val.substr(1);
+ break;
+
+ case '=':
+ case '>':
+ case '<':
+ cs = fmt.charAt(i);
+ break;
+
+ default:
+ res += MuPDF.convertCase(fmt.charAt(i),cs);
+ break;
+ }
+
+ i++;
+ }
+
+ return res;
+}
+
+util.printf = function()
+{
+ if (arguments.length < 1)
+ return "";
+
+ var res = "";
+ var arg_i = 1;
+ var regexp = /%[^dfsx]*[dfsx]|[^%]*/g;
+ var tokens = arguments[0].match(regexp);
+ var length = tokens ? tokens.length : 0;
+
+ for (var i = 0; i < length; i++)
+ {
+ var tok = tokens[i];
+ if (tok.match(/^%/))
+ {
+ if (arg_i < arguments.length)
+ {
+ var val = arguments[arg_i++];
+ var fval = '';
+ var neg = false;
+ var decsep_re = /^,[0123]/;
+ var flags_re = /^[+ 0#]+/;
+ var width_re = /^\d+/;
+ var prec_re = /^\.\d+/;
+ var conv_re = /^[dfsx]/;
+
+ tok = tok.replace(/^%/, "");
+ var decsep = tok.match(decsep_re);
+ if (decsep) decsep = decsep[0];
+ tok = tok.replace(decsep_re, "");
+ var flags = tok.match(flags_re);
+ if (flags) flags = flags[0];
+ tok = tok.replace(flags_re, "");
+ var width = tok.match(width_re);
+ if (width) width = width[0];
+ tok = tok.replace(width_re, "");
+ var prec = tok.match(prec_re);
+ if (prec) prec = prec[0];
+ tok = tok.replace(prec_re, "");
+ var conv = tok.match(conv_re);
+ if (conv) conv = conv[0];
+
+ prec = prec ? Number(prec.replace(/^\./, '')) : 0;
+ var poschar = (flags && flags.match(/[+ ]/)) ? flags.match(/[+ ]/)[0] : '';
+ var pad = (flags && flags.match(/0/)) ? '0' : ' ';
+
+ var point = '.';
+ var thou = '';
+
+ if (decsep)
+ {
+ switch(decsep)
+ {
+ case ',0': thou = ','; break;
+ case ',1': break;
+ case ',2': thou = '.'; point = ','; break;
+ case ',3': point = ','; break;
+ }
+ }
+
+ switch(conv)
+ {
+ case 'x':
+ val = Math.floor(val);
+ neg = (val < 0);
+ if (neg)
+ val = -val;
+
+ // Convert to hex
+ while (val)
+ {
+ fval = '0123456789ABCDEF'.charAt(val%16) + fval;
+ val = Math.floor(val/16);
+ }
+
+ if (neg)
+ fval = '-' + fval;
+ else
+ fval = poschar + fval;
+ break;
+
+ case 'd':
+ fval = String(Math.floor(val));
+ break;
+
+ case 's':
+ // Always pad strings with space
+ pad = ' ';
+ fval = String(val);
+ break;
+
+ case 'f':
+ fval = String(val);
+
+ if (prec)
+ {
+ var frac = fval.match(/\.\d+/);
+ if (frac)
+ {
+ frac = frac[0];
+ // Matched string includes the dot, so make it
+ // prec+1 in length
+ if (frac.length > prec+1)
+ frac = frac.substr(0, prec+1);
+ else if(frac.length < prec+1)
+ frac += new Array(prec+1-frac.length+1).join('0');
+
+ fval = fval.replace(/\.\d+/, frac);
+ }
+ }
+ break;
+ }
+
+ if (conv.match(/[fd]/))
+ {
+ if (fval >= 0)
+ fval = poschar + fval;
+
+ if (point != '.')
+ fval.replace(/\./, point);
+
+ if (thou)
+ {
+ var intpart = fval.match(/\d+/)[0];
+ intpart = new Array(2-(intpart.length+2)%3+1).join('0') + intpart;
+ intpart = intpart.match(/.../g).join(thou).replace(/^0*[,.]?/,'');
+ fval = fval.replace(/\d+/, intpart);
+ }
+ }
+
+ if (width && fval.length < width)
+ fval = new Array(width - fval.length + 1).join(pad) + fval;
+
+ res += fval;
+ }
+ }
+ else
+ {
+ res += tok;
+ }
+ }
+
+ return res;
+}
+
+function AFMergeChange(event)
+{
+ return event.value;
+}
+
+function AFMakeNumber(str)
+{
+ var nums = str.match(/\d+/g);
+
+ if (!nums)
+ return null;
+
+ var res = nums.join('.');
+
+ if (str.match(/^[^0-9]*\./))
+ res = '0.'+res;
+
+ return res * (str.match(/-/) ? -1.0 : 1.0);
+}
+
+function AFExtractTime(dt)
+{
+ var ampm = dt.match(/(am|pm)/);
+ dt = dt.replace(/(am|pm)/, '');
+ var t = dt.match(/\d{1,2}:\d{1,2}:\d{1,2}/);
+ dt = dt.replace(/\d{1,2}:\d{1,2}:\d{1,2}/, '');
+ if (!t)
+ {
+ t = dt.match(/\d{1,2}:\d{1,2}/);
+ dt = dt.replace(/\d{1,2}:\d{1,2}/, '');
+ }
+
+ return [dt, t?t[0]+(ampm?ampm[0]:''):''];
+}
+
+function AFParseDateOrder(fmt)
+{
+ var order = '';
+
+ // Ensure all present with those not added in default order
+ fmt += "mdy";
+
+ for (var i = 0; i < fmt.length; i++)
+ {
+ var c = fmt.charAt(i);
+
+ if ('ymd'.indexOf(c) != -1 && order.indexOf(c) == -1)
+ order += c;
+ }
+
+ return order;
+}
+
+function AFMatchMonth(d)
+{
+ var m = d.match(MuPDF.monthPattern);
+
+ return m ? MuPDF.shortMonthName.indexOf(m[0]) : null;
+}
+
+function AFParseTime(str, d)
+{
+ if (!str)
+ return d;
+
+ if (!d)
+ d = new Date();
+
+ var ampm = str.match(/(am|pm)/);
+ var nums = str.match(/\d+/g);
+ var hour, min, sec;
+
+ if (!nums)
+ return null;
+
+ sec = 0;
+
+ switch (nums.length)
+ {
+ case 3:
+ sec = nums[2];
+ case 2:
+ hour = nums[0];
+ min = nums[1];
+ break;
+
+ default:
+ return null;
+ }
+
+ if (ampm == 'am' && hour < 12)
+ hour = 12 + hour;
+
+ if (ampm == 'pm' && hour >= 12)
+ hour = 0 + hour - 12;
+
+ d.setHours(hour, min, sec);
+
+ if (d.getHours() != hour || d.getMinutes() != min || d.getSeconds() != sec)
+ return null;
+
+ return d;
+}
+
+function AFParseDateEx(d, fmt)
+{
+ var dt = AFExtractTime(d);
+ var nums = dt[0].match(/\d+/g);
+ var order = AFParseDateOrder(fmt);
+ var text_month = AFMatchMonth(dt[0]);
+ var dout = new Date();
+ var year = dout.getFullYear();
+ var month = dout.getMonth();
+ var date = dout.getDate();
+
+ dout.setHours(12,0,0);
+
+ if (!nums || nums.length < 1 || nums.length > 3)
+ return null;
+
+ if (nums.length < 3 && text_month)
+ {
+ // Use the text month rather than one of the numbers
+ month = text_month;
+ order = order.replace('m','');
+ }
+
+ order = order.substr(0, nums.length);
+
+ // If year and month specified but not date then use the 1st
+ if (order == "ym" || (order == "y" && text_month))
+ date = 1;
+
+ for (var i = 0; i < nums.length; i++)
+ {
+ switch (order.charAt(i))
+ {
+ case 'y': year = nums[i]; break;
+ case 'm': month = nums[i] - 1; break;
+ case 'd': date = nums[i]; break;
+ }
+ }
+
+ if (year < 100)
+ {
+ if (fmt.search("yyyy") != -1)
+ return null;
+
+ if (year >= 50)
+ year = 1900 + year;
+ else if (year >= 0)
+ year = 2000 + year;
+ }
+
+ dout.setFullYear(year, month, date);
+
+ if (dout.getFullYear() != year || dout.getMonth() != month || dout.getDate() != date)
+ return null;
+
+ return AFParseTime(dt[1], dout);
+}
+
+function AFDate_KeystrokeEx(fmt)
+{
+ if (event.willCommit && !AFParseDateEx(event.value, fmt))
+ {
+ app.alert("Invalid date/time. please ensure that the date/time exists. Field [ "+event.target.name+" ] should match format "+fmt);
+ event.rc = false;
+ }
+}
+
+function AFDate_Keystroke(index)
+{
+ var formats = ['m/d','m/d/yy','mm/dd/yy','mm/yy','d-mmm','d-mmm-yy','dd-mm-yy','yy-mm-dd',
+ 'mmm-yy','mmmm-yy','mmm d, yyyy','mmmm d, yyyy','m/d/yy h:MM tt','m/d/yy HH:MM'];
+ AFDate_KeystrokeEx(formats[index]);
+}
+
+function AFDate_FormatEx(fmt)
+{
+ var d = AFParseDateEx(event.value, fmt);
+
+ event.value = d ? util.printd(fmt, d) : "";
+}
+
+function AFDate_Format(index)
+{
+ var formats = ['m/d','m/d/yy','mm/dd/yy','mm/yy','d-mmm','d-mmm-yy','dd-mm-yy','yy-mm-dd',
+ 'mmm-yy','mmmm-yy','mmm d, yyyy','mmmm d, yyyy','m/d/yy h:MM tt','m/d/yy HH:MM'];
+ AFDate_FormatEx(formats[index]);
+}
+
+function AFTime_Keystroke(index)
+{
+ if (event.willCommit && !AFParseTime(event.value, null))
+ {
+ app.alert("The value entered does not match the format of the field [ "+event.target.name+" ]");
+ event.rc = false;
+ }
+}
+
+function AFTime_FormatEx(fmt)
+{
+ var d = AFParseTime(event.value, null);
+
+ event.value = d ? util.printd(fmt, d) : '';
+}
+
+function AFTime_Format(index)
+{
+ var formats = ['HH:MM','h:MM tt','HH:MM:ss','h:MM:ss tt'];
+
+ AFTime_FormatEx(formats[index]);
+}
+
+function AFSpecial_KeystrokeEx(fmt)
+{
+ var cs = '=';
+ var val = event.value;
+ var res = '';
+ var i = 0;
+ var m;
+ var length = fmt ? fmt.length : 0;
+
+ while (i < length)
+ {
+ switch (fmt.charAt(i))
+ {
+ case '\\':
+ i++;
+ if (i >= length)
+ break;
+ res += fmt.charAt(i);
+ if (val && val.charAt(0) == fmt.charAt(i))
+ val = val.substr(1);
+ break;
+
+ case 'X':
+ m = val.match(/^\w/);
+ if (!m)
+ {
+ event.rc = false;
+ break;
+ }
+ res += MuPDF.convertCase(m[0],cs);
+ val = val.substr(1);
+ break;
+
+ case 'A':
+ m = val.match(/^[A-z]/);
+ if (!m)
+ {
+ event.rc = false;
+ break;
+ }
+ res += MuPDF.convertCase(m[0],cs);
+ val = val.substr(1);
+ break;
+
+ case '9':
+ m = val.match(/^\d/);
+ if (!m)
+ {
+ event.rc = false;
+ break;
+ }
+ res += m[0];
+ val = val.substr(1);
+ break;
+
+ case '*':
+ res += val;
+ val = '';
+ break;
+
+ case '?':
+ if (!val)
+ {
+ event.rc = false;
+ break;
+ }
+ res += MuPDF.convertCase(val.charAt(0),cs);
+ val = val.substr(1);
+ break;
+
+ case '=':
+ case '>':
+ case '<':
+ cs = fmt.charAt(i);
+ break;
+
+ default:
+ res += fmt.charAt(i);
+ if (val && val.charAt(0) == fmt.charAt(i))
+ val = val.substr(1);
+ break;
+ }
+
+ i++;
+ }
+
+ if (event.rc)
+ event.value = res;
+ else if (event.willCommit)
+ app.alert("The value entered does not match the format of the field [ "+event.target.name+" ] should be "+fmt);
+}
+
+function AFSpecial_Keystroke(index)
+{
+ if (event.willCommit)
+ {
+ switch (index)
+ {
+ case 0:
+ if (!event.value.match(/^\d{5}$/))
+ event.rc = false;
+ break;
+ case 1:
+ if (!event.value.match(/^\d{5}[-. ]?\d{4}$/))
+ event.rc = false;
+ break;
+ case 2:
+ if (!event.value.match(/^((\(\d{3}\)|\d{3})[-. ]?)?\d{3}[-. ]?\d{4}$/))
+ event.rc = false;
+ break;
+ case 3:
+ if (!event.value.match(/^\d{3}[-. ]?\d{2}[-. ]?\d{4}$/))
+ event.rc = false;
+ break;
+ }
+
+ if (!event.rc)
+ app.alert("The value entered does not match the format of the field [ "+event.target.name+" ]");
+ }
+}
+
+function AFSpecial_Format(index)
+{
+ var res;
+
+ switch (index)
+ {
+ case 0:
+ res = util.printx('99999', event.value);
+ break;
+ case 1:
+ res = util.printx('99999-9999', event.value);
+ break;
+ case 2:
+ res = util.printx('9999999999', event.value);
+ res = util.printx(res.length >= 10 ? '(999) 999-9999' : '999-9999', event.value);
+ break;
+ case 3:
+ res = util.printx('999-99-9999', event.value);
+ break;
+ }
+
+ event.value = res ? res : '';
+}
+
+function AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend)
+{
+ if (sepStyle & 2)
+ {
+ if (!event.value.match(/^[+-]?\d*[,.]?\d*$/))
+ event.rc = false;
+ }
+ else
+ {
+ if (!event.value.match(/^[+-]?\d*\.?\d*$/))
+ event.rc = false;
+ }
+
+ if (event.willCommit)
+ {
+ if (!event.value.match(/\d/))
+ event.rc = false;
+
+ if (!event.rc)
+ app.alert("The value entered does not match the format of the field [ "+event.target.name+" ]");
+ }
+}
+
+function AFNumber_Format(nDec,sepStyle,negStyle,currStyle,strCurrency,bCurrencyPrepend)
+{
+ var val = event.value;
+ var fracpart;
+ var intpart;
+ var point = sepStyle&2 ? ',' : '.';
+ var separator = sepStyle&2 ? '.' : ',';
+
+ if (/^\D*\./.test(val))
+ val = '0'+val;
+
+ var groups = val.match(/\d+/g);
+
+ if (!groups)
+ return;
+
+ switch (groups.length)
+ {
+ case 0:
+ return;
+ case 1:
+ fracpart = '';
+ intpart = groups[0];
+ break;
+ default:
+ fracpart = groups.pop();
+ intpart = groups.join('');
+ break;
+ }
+
+ // Remove leading zeros
+ intpart = intpart.replace(/^0*/,'');
+ if (!intpart)
+ intpart = '0';
+
+ if ((sepStyle & 1) == 0)
+ {
+ // Add the thousands sepearators: pad to length multiple of 3 with zeros,
+ // split into 3s, join with separator, and remove the leading zeros
+ intpart = new Array(2-(intpart.length+2)%3+1).join('0') + intpart;
+ intpart = intpart.match(/.../g).join(separator).replace(/^0*/,'');
+ }
+
+ if (!intpart)
+ intpart = '0';
+
+ // Adjust fractional part to correct number of decimal places
+ fracpart += new Array(nDec+1).join('0');
+ fracpart = fracpart.substr(0,nDec);
+
+ if (fracpart)
+ intpart += point+fracpart;
+
+ if (bCurrencyPrepend)
+ intpart = strCurrency+intpart;
+ else
+ intpart += strCurrency;
+
+ if (/-/.test(val))
+ {
+ switch (negStyle)
+ {
+ case 0:
+ intpart = '-'+intpart;
+ break;
+ case 1:
+ break;
+ case 2:
+ case 3:
+ intpart = '('+intpart+')';
+ break;
+ }
+ }
+
+ if (negStyle&1)
+ event.target.textColor = /-/.test(val) ? color.red : color.black;
+
+ event.value = intpart;
+}
+
+function AFPercent_Keystroke(nDec, sepStyle)
+{
+ AFNumber_Keystroke(nDec, sepStyle, 0, 0, "", true);
+}
+
+function AFPercent_Format(nDec, sepStyle)
+{
+ var val = AFMakeNumber(event.value);
+
+ if (!val)
+ {
+ event.value = '';
+ return;
+ }
+
+ event.value = (val * 100) + '';
+
+ AFNumber_Format(nDec, sepStyle, 0, 0, "%", false);
+}
+
+function AFSimple_Calculate(op, list)
+{
+ var res;
+
+ switch (op)
+ {
+ case 'SUM':
+ res = 0;
+ break;
+ case 'PRD':
+ res = 1;
+ break;
+ case 'AVG':
+ res = 0;
+ break;
+ }
+
+ if (typeof list == 'string')
+ list = list.split(/ *, */);
+
+ for (var i = 0; i < list.length; i++)
+ {
+ var field = getField(list[i]);
+ var value = Number(field.value);
+
+ switch (op)
+ {
+ case 'SUM':
+ res += value;
+ break;
+ case 'PRD':
+ res *= value;
+ break;
+ case 'AVG':
+ res += value;
+ break;
+ case 'MIN':
+ if (i == 0 || value < res)
+ res = value;
+ break;
+ case 'MAX':
+ if (i == 0 || value > res)
+ res = value;
+ break;
+ }
+ }
+
+ if (op == 'AVG')
+ res /= list.length;
+
+ event.value = res;
+}
+
+function AFRange_Validate(lowerCheck, lowerLimit, upperCheck, upperLimit)
+{
+ if (upperCheck && event.value > upperLimit)
+ {
+ event.rc = false;
+ }
+
+ if (lowerCheck && event.value < lowerLimit)
+ {
+ event.rc = false;
+ }
+
+
+ if (!event.rc)
+ {
+ if (lowerCheck && upperCheck)
+ app.alert(util.printf("Invalid value: must be greater than or equal to %s and less than or equal to %s", lowerLimit, upperLimit));
+ else if (lowerCheck)
+ app.alert(util.printf("Invalid value: must be greater than or equal to %s", lowerLimit));
+ else
+ app.alert(util.printf("Invalid value: must be less than or equal to %s", upperLimit));
+ }
+}
diff --git a/source/pdf/pdf-annot.c b/source/pdf/pdf-annot.c
new file mode 100644
index 00000000..c50bbba2
--- /dev/null
+++ b/source/pdf/pdf-annot.c
@@ -0,0 +1,1200 @@
+#include "mupdf/pdf.h"
+
+#define SMALL_FLOAT (0.00001)
+
+static pdf_obj *
+resolve_dest_rec(pdf_document *xref, pdf_obj *dest, int depth)
+{
+ if (depth > 10) /* Arbitrary to avoid infinite recursion */
+ return NULL;
+
+ if (pdf_is_name(dest) || pdf_is_string(dest))
+ {
+ dest = pdf_lookup_dest(xref, dest);
+ return resolve_dest_rec(xref, dest, depth+1);
+ }
+
+ else if (pdf_is_array(dest))
+ {
+ return dest;
+ }
+
+ else if (pdf_is_dict(dest))
+ {
+ dest = pdf_dict_gets(dest, "D");
+ return resolve_dest_rec(xref, dest, depth+1);
+ }
+
+ else if (pdf_is_indirect(dest))
+ return dest;
+
+ return NULL;
+}
+
+static pdf_obj *
+resolve_dest(pdf_document *xref, pdf_obj *dest)
+{
+ return resolve_dest_rec(xref, dest, 0);
+}
+
+fz_link_dest
+pdf_parse_link_dest(pdf_document *xref, pdf_obj *dest)
+{
+ fz_link_dest ld;
+ pdf_obj *obj;
+
+ int l_from_2 = 0;
+ int b_from_3 = 0;
+ int r_from_4 = 0;
+ int t_from_5 = 0;
+ int t_from_3 = 0;
+ int t_from_2 = 0;
+ int z_from_4 = 0;
+
+ dest = resolve_dest(xref, dest);
+ if (dest == NULL || !pdf_is_array(dest))
+ {
+ ld.kind = FZ_LINK_NONE;
+ return ld;
+ }
+ obj = pdf_array_get(dest, 0);
+ if (pdf_is_int(obj))
+ ld.ld.gotor.page = pdf_to_int(obj);
+ else
+ ld.ld.gotor.page = pdf_lookup_page_number(xref, obj);
+
+ ld.kind = FZ_LINK_GOTO;
+ ld.ld.gotor.flags = 0;
+ ld.ld.gotor.lt.x = 0;
+ ld.ld.gotor.lt.y = 0;
+ ld.ld.gotor.rb.x = 0;
+ ld.ld.gotor.rb.y = 0;
+ ld.ld.gotor.file_spec = NULL;
+ ld.ld.gotor.new_window = 0;
+
+ obj = pdf_array_get(dest, 1);
+ if (!pdf_is_name(obj))
+ return ld;
+
+ if (!strcmp("XYZ", pdf_to_name(obj)))
+ {
+ l_from_2 = t_from_3 = z_from_4 = 1;
+ ld.ld.gotor.flags |= fz_link_flag_r_is_zoom;
+ }
+ else if ((!strcmp("Fit", pdf_to_name(obj))) || (!strcmp("FitB", pdf_to_name(obj))))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_fit_h;
+ ld.ld.gotor.flags |= fz_link_flag_fit_v;
+ }
+ else if ((!strcmp("FitH", pdf_to_name(obj))) || (!strcmp("FitBH", pdf_to_name(obj))))
+ {
+ t_from_2 = 1;
+ ld.ld.gotor.flags |= fz_link_flag_fit_h;
+ }
+ else if ((!strcmp("FitV", pdf_to_name(obj))) || (!strcmp("FitBV", pdf_to_name(obj))))
+ {
+ l_from_2 = 1;
+ ld.ld.gotor.flags |= fz_link_flag_fit_v;
+ }
+ else if (!strcmp("FitR", pdf_to_name(obj)))
+ {
+ l_from_2 = b_from_3 = r_from_4 = t_from_5 = 1;
+ ld.ld.gotor.flags |= fz_link_flag_fit_h;
+ ld.ld.gotor.flags |= fz_link_flag_fit_v;
+ }
+
+ if (l_from_2)
+ {
+ obj = pdf_array_get(dest, 2);
+ if (pdf_is_int(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_l_valid;
+ ld.ld.gotor.lt.x = pdf_to_int(obj);
+ }
+ else if (pdf_is_real(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_l_valid;
+ ld.ld.gotor.lt.x = pdf_to_real(obj);
+ }
+ }
+ if (b_from_3)
+ {
+ obj = pdf_array_get(dest, 3);
+ if (pdf_is_int(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_b_valid;
+ ld.ld.gotor.rb.y = pdf_to_int(obj);
+ }
+ else if (pdf_is_real(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_b_valid;
+ ld.ld.gotor.rb.y = pdf_to_real(obj);
+ }
+ }
+ if (r_from_4)
+ {
+ obj = pdf_array_get(dest, 4);
+ if (pdf_is_int(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_r_valid;
+ ld.ld.gotor.rb.x = pdf_to_int(obj);
+ }
+ else if (pdf_is_real(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_r_valid;
+ ld.ld.gotor.rb.x = pdf_to_real(obj);
+ }
+ }
+ if (t_from_5 || t_from_3 || t_from_2)
+ {
+ if (t_from_5)
+ obj = pdf_array_get(dest, 5);
+ else if (t_from_3)
+ obj = pdf_array_get(dest, 3);
+ else
+ obj = pdf_array_get(dest, 2);
+ if (pdf_is_int(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_t_valid;
+ ld.ld.gotor.lt.y = pdf_to_int(obj);
+ }
+ else if (pdf_is_real(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_t_valid;
+ ld.ld.gotor.lt.y = pdf_to_real(obj);
+ }
+ }
+ if (z_from_4)
+ {
+ obj = pdf_array_get(dest, 4);
+ if (pdf_is_int(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_r_valid;
+ ld.ld.gotor.rb.x = pdf_to_int(obj);
+ }
+ else if (pdf_is_real(obj))
+ {
+ ld.ld.gotor.flags |= fz_link_flag_r_valid;
+ ld.ld.gotor.rb.x = pdf_to_real(obj);
+ }
+ }
+
+ /* Duplicate the values out for the sake of stupid clients */
+ if ((ld.ld.gotor.flags & (fz_link_flag_l_valid | fz_link_flag_r_valid)) == fz_link_flag_l_valid)
+ ld.ld.gotor.rb.x = ld.ld.gotor.lt.x;
+ if ((ld.ld.gotor.flags & (fz_link_flag_l_valid | fz_link_flag_r_valid | fz_link_flag_r_is_zoom)) == fz_link_flag_r_valid)
+ ld.ld.gotor.lt.x = ld.ld.gotor.rb.x;
+ if ((ld.ld.gotor.flags & (fz_link_flag_t_valid | fz_link_flag_b_valid)) == fz_link_flag_t_valid)
+ ld.ld.gotor.rb.y = ld.ld.gotor.lt.y;
+ if ((ld.ld.gotor.flags & (fz_link_flag_t_valid | fz_link_flag_b_valid)) == fz_link_flag_b_valid)
+ ld.ld.gotor.lt.y = ld.ld.gotor.rb.y;
+
+ return ld;
+}
+
+static char *
+pdf_parse_file_spec(pdf_document *xref, pdf_obj *file_spec)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *filename;
+
+ if (pdf_is_string(file_spec))
+ return pdf_to_utf8(xref, file_spec);
+
+ if (pdf_is_dict(file_spec)) {
+ filename = pdf_dict_gets(file_spec, "UF");
+ if (!filename)
+ filename = pdf_dict_gets(file_spec, "F");
+ if (!filename)
+ filename = pdf_dict_gets(file_spec, "Unix");
+ if (!filename)
+ filename = pdf_dict_gets(file_spec, "Mac");
+ if (!filename)
+ filename = pdf_dict_gets(file_spec, "DOS");
+
+ return pdf_to_utf8(xref, filename);
+ }
+
+ fz_warn(ctx, "cannot parse file specification");
+ return NULL;
+}
+
+fz_link_dest
+pdf_parse_action(pdf_document *xref, pdf_obj *action)
+{
+ fz_link_dest ld;
+ pdf_obj *obj, *dest;
+ fz_context *ctx = xref->ctx;
+
+ UNUSED(ctx);
+
+ ld.kind = FZ_LINK_NONE;
+
+ if (!action)
+ return ld;
+
+ obj = pdf_dict_gets(action, "S");
+ if (!strcmp(pdf_to_name(obj), "GoTo"))
+ {
+ dest = pdf_dict_gets(action, "D");
+ ld = pdf_parse_link_dest(xref, dest);
+ }
+ else if (!strcmp(pdf_to_name(obj), "URI"))
+ {
+ ld.kind = FZ_LINK_URI;
+ ld.ld.uri.is_map = pdf_to_bool(pdf_dict_gets(action, "IsMap"));
+ ld.ld.uri.uri = pdf_to_utf8(xref, pdf_dict_gets(action, "URI"));
+ }
+ else if (!strcmp(pdf_to_name(obj), "Launch"))
+ {
+ ld.kind = FZ_LINK_LAUNCH;
+ dest = pdf_dict_gets(action, "F");
+ ld.ld.launch.file_spec = pdf_parse_file_spec(xref, dest);
+ ld.ld.launch.new_window = pdf_to_int(pdf_dict_gets(action, "NewWindow"));
+ }
+ else if (!strcmp(pdf_to_name(obj), "Named"))
+ {
+ ld.kind = FZ_LINK_NAMED;
+ ld.ld.named.named = pdf_to_utf8(xref, pdf_dict_gets(action, "N"));
+ }
+ else if (!strcmp(pdf_to_name(obj), "GoToR"))
+ {
+ dest = pdf_dict_gets(action, "D");
+ ld = pdf_parse_link_dest(xref, dest);
+ ld.kind = FZ_LINK_GOTOR;
+ dest = pdf_dict_gets(action, "F");
+ ld.ld.gotor.file_spec = pdf_parse_file_spec(xref, dest);
+ ld.ld.gotor.new_window = pdf_to_int(pdf_dict_gets(action, "NewWindow"));
+ }
+ return ld;
+}
+
+static fz_link *
+pdf_load_link(pdf_document *xref, pdf_obj *dict, const fz_matrix *page_ctm)
+{
+ pdf_obj *dest = NULL;
+ pdf_obj *action;
+ pdf_obj *obj;
+ fz_rect bbox;
+ fz_context *ctx = xref->ctx;
+ fz_link_dest ld;
+
+ obj = pdf_dict_gets(dict, "Rect");
+ if (obj)
+ pdf_to_rect(ctx, obj, &bbox);
+ else
+ bbox = fz_empty_rect;
+
+ fz_transform_rect(&bbox, page_ctm);
+
+ obj = pdf_dict_gets(dict, "Dest");
+ if (obj)
+ {
+ dest = resolve_dest(xref, obj);
+ ld = pdf_parse_link_dest(xref, dest);
+ }
+ else
+ {
+ action = pdf_dict_gets(dict, "A");
+ /* fall back to additional action button's down/up action */
+ if (!action)
+ action = pdf_dict_getsa(pdf_dict_gets(dict, "AA"), "U", "D");
+
+ ld = pdf_parse_action(xref, action);
+ }
+ if (ld.kind == FZ_LINK_NONE)
+ return NULL;
+ return fz_new_link(ctx, &bbox, ld);
+}
+
+fz_link *
+pdf_load_link_annots(pdf_document *xref, pdf_obj *annots, const fz_matrix *page_ctm)
+{
+ fz_link *link, *head, *tail;
+ pdf_obj *obj;
+ int i, n;
+
+ head = tail = NULL;
+ link = NULL;
+
+ n = pdf_array_len(annots);
+ for (i = 0; i < n; i++)
+ {
+ fz_try(xref->ctx)
+ {
+ obj = pdf_array_get(annots, i);
+ link = pdf_load_link(xref, obj, page_ctm);
+ }
+ fz_catch(xref->ctx)
+ {
+ /* FIXME: TryLater */
+ link = NULL;
+ }
+
+ if (link)
+ {
+ if (!head)
+ head = tail = link;
+ else
+ {
+ tail->next = link;
+ tail = link;
+ }
+ }
+ }
+
+ return head;
+}
+
+void
+pdf_free_annot(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_annot *next;
+
+ while (annot)
+ {
+ next = annot->next;
+ if (annot->ap)
+ pdf_drop_xobject(ctx, annot->ap);
+ pdf_drop_obj(annot->obj);
+ fz_free(ctx, annot);
+ annot = next;
+ }
+}
+
+static void
+pdf_transform_annot(pdf_annot *annot)
+{
+ fz_rect bbox = annot->ap->bbox;
+ fz_rect rect = annot->rect;
+ float w, h, x, y;
+
+ fz_transform_rect(&bbox, &annot->ap->matrix);
+ if (bbox.x1 == bbox.x0)
+ w = 0;
+ else
+ w = (rect.x1 - rect.x0) / (bbox.x1 - bbox.x0);
+ if (bbox.y1 == bbox.y0)
+ h = 0;
+ else
+ h = (rect.y1 - rect.y0) / (bbox.y1 - bbox.y0);
+ x = rect.x0 - bbox.x0;
+ y = rect.y0 - bbox.y0;
+
+ fz_pre_scale(fz_translate(&annot->matrix, x, y), w, h);
+}
+
+fz_annot_type pdf_annot_obj_type(pdf_obj *obj)
+{
+ char *subtype = pdf_to_name(pdf_dict_gets(obj, "Subtype"));
+ if (!strcmp(subtype, "Text"))
+ return FZ_ANNOT_TEXT;
+ else if (!strcmp(subtype, "Link"))
+ return FZ_ANNOT_LINK;
+ else if (!strcmp(subtype, "FreeText"))
+ return FZ_ANNOT_FREETEXT;
+ else if (!strcmp(subtype, "Line"))
+ return FZ_ANNOT_LINE;
+ else if (!strcmp(subtype, "Square"))
+ return FZ_ANNOT_SQUARE;
+ else if (!strcmp(subtype, "Circle"))
+ return FZ_ANNOT_CIRCLE;
+ else if (!strcmp(subtype, "Polygon"))
+ return FZ_ANNOT_POLYGON;
+ else if (!strcmp(subtype, "PolyLine"))
+ return FZ_ANNOT_POLYLINE;
+ else if (!strcmp(subtype, "Highlight"))
+ return FZ_ANNOT_HIGHLIGHT;
+ else if (!strcmp(subtype, "Underline"))
+ return FZ_ANNOT_UNDERLINE;
+ else if (!strcmp(subtype, "Squiggly"))
+ return FZ_ANNOT_SQUIGGLY;
+ else if (!strcmp(subtype, "StrikeOut"))
+ return FZ_ANNOT_STRIKEOUT;
+ else if (!strcmp(subtype, "Stamp"))
+ return FZ_ANNOT_STAMP;
+ else if (!strcmp(subtype, "Caret"))
+ return FZ_ANNOT_CARET;
+ else if (!strcmp(subtype, "Ink"))
+ return FZ_ANNOT_INK;
+ else if (!strcmp(subtype, "Popup"))
+ return FZ_ANNOT_POPUP;
+ else if (!strcmp(subtype, "FileAttachment"))
+ return FZ_ANNOT_FILEATTACHMENT;
+ else if (!strcmp(subtype, "Sound"))
+ return FZ_ANNOT_SOUND;
+ else if (!strcmp(subtype, "Movie"))
+ return FZ_ANNOT_MOVIE;
+ else if (!strcmp(subtype, "Widget"))
+ return FZ_ANNOT_WIDGET;
+ else if (!strcmp(subtype, "Screen"))
+ return FZ_ANNOT_SCREEN;
+ else if (!strcmp(subtype, "PrinterMark"))
+ return FZ_ANNOT_PRINTERMARK;
+ else if (!strcmp(subtype, "TrapNet"))
+ return FZ_ANNOT_TRAPNET;
+ else if (!strcmp(subtype, "Watermark"))
+ return FZ_ANNOT_WATERMARK;
+ else if (!strcmp(subtype, "3D"))
+ return FZ_ANNOT_3D;
+ else
+ return -1;
+}
+
+static const char *annot_type_str(fz_annot_type type)
+{
+ switch (type)
+ {
+ case FZ_ANNOT_TEXT: return "Text";
+ case FZ_ANNOT_LINK: return "Link";
+ case FZ_ANNOT_FREETEXT: return "FreeText";
+ case FZ_ANNOT_LINE: return "Line";
+ case FZ_ANNOT_SQUARE: return "Square";
+ case FZ_ANNOT_CIRCLE: return "Circle";
+ case FZ_ANNOT_POLYGON: return "Polygon";
+ case FZ_ANNOT_POLYLINE: return "PolyLine";
+ case FZ_ANNOT_HIGHLIGHT: return "Highlight";
+ case FZ_ANNOT_UNDERLINE: return "Underline";
+ case FZ_ANNOT_SQUIGGLY: return "Squiggly";
+ case FZ_ANNOT_STRIKEOUT: return "StrikeOut";
+ case FZ_ANNOT_STAMP: return "Stamp";
+ case FZ_ANNOT_CARET: return "Caret";
+ case FZ_ANNOT_INK: return "Ink";
+ case FZ_ANNOT_POPUP: return "Popup";
+ case FZ_ANNOT_FILEATTACHMENT: return "FileAttachment";
+ case FZ_ANNOT_SOUND: return "Sound";
+ case FZ_ANNOT_MOVIE: return "Movie";
+ case FZ_ANNOT_WIDGET: return "Widget";
+ case FZ_ANNOT_SCREEN: return "Screen";
+ case FZ_ANNOT_PRINTERMARK: return "PrinterMark";
+ case FZ_ANNOT_TRAPNET: return "TrapNet";
+ case FZ_ANNOT_WATERMARK: return "Watermark";
+ case FZ_ANNOT_3D: return "3D";
+ default: return "";
+ }
+}
+
+pdf_annot *
+pdf_load_annots(pdf_document *xref, pdf_obj *annots, pdf_page *page)
+{
+ pdf_annot *annot, *head, *tail;
+ pdf_obj *obj, *ap, *as, *n, *rect;
+ int i, len, is_dict;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(annot);
+
+ head = tail = NULL;
+
+ len = pdf_array_len(annots);
+ for (i = 0; i < len; i++)
+ {
+ fz_try(ctx)
+ {
+ obj = pdf_array_get(annots, i);
+
+ if (xref->update_appearance)
+ xref->update_appearance(xref, obj);
+
+ rect = pdf_dict_gets(obj, "Rect");
+ ap = pdf_dict_gets(obj, "AP");
+ as = pdf_dict_gets(obj, "AS");
+ is_dict = pdf_is_dict(ap);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ ap = NULL;
+ is_dict = 0;
+ }
+
+ if (!is_dict)
+ continue;
+
+ annot = NULL;
+ fz_try(ctx)
+ {
+ pdf_hotspot *hp = &xref->hotspot;
+
+ n = NULL;
+
+ if (hp->num == pdf_to_num(obj)
+ && hp->gen == pdf_to_gen(obj)
+ && (hp->state & HOTSPOT_POINTER_DOWN))
+ {
+ n = pdf_dict_gets(ap, "D"); /* down state */
+ }
+
+ if (n == NULL)
+ n = pdf_dict_gets(ap, "N"); /* normal state */
+
+ /* lookup current state in sub-dictionary */
+ if (!pdf_is_stream(xref, pdf_to_num(n), pdf_to_gen(n)))
+ n = pdf_dict_get(n, as);
+
+ annot = fz_malloc_struct(ctx, pdf_annot);
+ annot->page = page;
+ annot->obj = pdf_keep_obj(obj);
+ pdf_to_rect(ctx, rect, &annot->rect);
+ annot->pagerect = annot->rect;
+ fz_transform_rect(&annot->pagerect, &page->ctm);
+ annot->ap = NULL;
+ annot->annot_type = pdf_annot_obj_type(obj);
+ annot->widget_type = annot->annot_type == FZ_ANNOT_WIDGET ? pdf_field_type(xref, obj) : PDF_WIDGET_TYPE_NOT_WIDGET;
+
+ if (pdf_is_stream(xref, pdf_to_num(n), pdf_to_gen(n)))
+ {
+ annot->ap = pdf_load_xobject(xref, n);
+ pdf_transform_annot(annot);
+ annot->ap_iteration = annot->ap->iteration;
+ }
+
+ annot->next = NULL;
+
+ if (obj == xref->focus_obj)
+ xref->focus = annot;
+
+ if (!head)
+ head = tail = annot;
+ else
+ {
+ tail->next = annot;
+ tail = annot;
+ }
+ }
+ fz_catch(ctx)
+ {
+ pdf_free_annot(ctx, annot);
+ fz_warn(ctx, "ignoring broken annotation");
+ /* FIXME: TryLater */
+ }
+ }
+
+ return head;
+}
+
+void
+pdf_update_annot(pdf_document *xref, pdf_annot *annot)
+{
+ pdf_obj *obj, *ap, *as, *n;
+ fz_context *ctx = xref->ctx;
+
+ obj = annot->obj;
+
+ if (xref->update_appearance)
+ xref->update_appearance(xref, obj);
+
+ ap = pdf_dict_gets(obj, "AP");
+ as = pdf_dict_gets(obj, "AS");
+
+ if (pdf_is_dict(ap))
+ {
+ pdf_hotspot *hp = &xref->hotspot;
+
+ n = NULL;
+
+ if (hp->num == pdf_to_num(obj)
+ && hp->gen == pdf_to_gen(obj)
+ && (hp->state & HOTSPOT_POINTER_DOWN))
+ {
+ n = pdf_dict_gets(ap, "D"); /* down state */
+ }
+
+ if (n == NULL)
+ n = pdf_dict_gets(ap, "N"); /* normal state */
+
+ /* lookup current state in sub-dictionary */
+ if (!pdf_is_stream(xref, pdf_to_num(n), pdf_to_gen(n)))
+ n = pdf_dict_get(n, as);
+
+ pdf_drop_xobject(ctx, annot->ap);
+ annot->ap = NULL;
+
+ if (pdf_is_stream(xref, pdf_to_num(n), pdf_to_gen(n)))
+ {
+ fz_try(ctx)
+ {
+ annot->ap = pdf_load_xobject(xref, n);
+ pdf_transform_annot(annot);
+ annot->ap_iteration = annot->ap->iteration;
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "ignoring broken annotation");
+ /* FIXME: TryLater */
+ }
+ }
+ }
+}
+
+pdf_annot *
+pdf_first_annot(pdf_document *doc, pdf_page *page)
+{
+ return page ? page->annots : NULL;
+}
+
+pdf_annot *
+pdf_next_annot(pdf_document *doc, pdf_annot *annot)
+{
+ return annot ? annot->next : NULL;
+}
+
+fz_rect *
+pdf_bound_annot(pdf_document *doc, pdf_annot *annot, fz_rect *rect)
+{
+ if (rect == NULL)
+ return NULL;
+
+ if (annot)
+ *rect = annot->pagerect;
+ else
+ *rect = fz_empty_rect;
+ return rect;
+}
+
+fz_annot_type
+pdf_annot_type(pdf_annot *annot)
+{
+ return annot->annot_type;
+}
+
+pdf_annot *
+pdf_create_annot(pdf_document *doc, pdf_page *page, fz_annot_type type)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_annot *annot = NULL;
+ pdf_obj *annot_obj = pdf_new_dict(ctx, 0);
+ pdf_obj *ind_obj = NULL;
+
+ fz_var(annot);
+ fz_var(ind_obj);
+ fz_try(ctx)
+ {
+ int ind_obj_num;
+ fz_rect rect = {0.0, 0.0, 0.0, 0.0};
+ const char *type_str = annot_type_str(type);
+ pdf_obj *annot_arr = pdf_dict_gets(page->me, "Annots");
+ if (annot_arr == NULL)
+ {
+ annot_arr = pdf_new_array(ctx, 0);
+ pdf_dict_puts_drop(page->me, "Annots", annot_arr);
+ }
+
+ pdf_dict_puts_drop(annot_obj, "Type", pdf_new_name(ctx, "Annot"));
+
+ pdf_dict_puts_drop(annot_obj, "Subtype", pdf_new_name(ctx, type_str));
+ pdf_dict_puts_drop(annot_obj, "Rect", pdf_new_rect(ctx, &rect));
+
+ annot = fz_malloc_struct(ctx, pdf_annot);
+ annot->page = page;
+ annot->obj = pdf_keep_obj(annot_obj);
+ annot->rect = rect;
+ annot->pagerect = rect;
+ annot->ap = NULL;
+ annot->widget_type = PDF_WIDGET_TYPE_NOT_WIDGET;
+ annot->annot_type = type;
+
+ /*
+ Both annotation object and annotation structure are now created.
+ Insert the object in the hierarchy and the structure in the
+ page's array.
+ */
+ ind_obj_num = pdf_create_object(doc);
+ pdf_update_object(doc, ind_obj_num, annot_obj);
+ ind_obj = pdf_new_indirect(ctx, ind_obj_num, 0, doc);
+ pdf_array_push(annot_arr, ind_obj);
+
+ /*
+ Linking must be done after any call that might throw because
+ pdf_free_annot below actually frees a list
+ */
+ annot->next = page->annots;
+ page->annots = annot;
+
+ doc->dirty = 1;
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(annot_obj);
+ pdf_drop_obj(ind_obj);
+ }
+ fz_catch(ctx)
+ {
+ pdf_free_annot(ctx, annot);
+ fz_rethrow(ctx);
+ }
+
+ return annot;
+}
+
+void
+pdf_delete_annot(pdf_document *doc, pdf_page *page, pdf_annot *annot)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_annot **annotptr;
+ pdf_obj *old_annot_arr;
+ pdf_obj *annot_arr;
+
+ if (annot == NULL)
+ return;
+
+ /* Remove annot from page's list */
+ for (annotptr = &page->annots; *annotptr; annotptr = &(*annotptr)->next)
+ {
+ if (*annotptr == annot)
+ break;
+ }
+
+ /* Check the passed annotation was of this page */
+ if (*annotptr == NULL)
+ return;
+
+ *annotptr = annot->next;
+
+ /* Stick it in the deleted list */
+ annot->next = page->deleted_annots;
+ page->deleted_annots = annot;
+
+ pdf_drop_xobject(ctx, annot->ap);
+ annot->ap = NULL;
+
+ /* Recreate the "Annots" array with this annot removed */
+ old_annot_arr = pdf_dict_gets(page->me, "Annots");
+
+ if (old_annot_arr)
+ {
+ int i, n = pdf_array_len(old_annot_arr);
+ annot_arr = pdf_new_array(ctx, n?(n-1):0);
+
+ fz_try(ctx)
+ {
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *obj = pdf_array_get(old_annot_arr, i);
+
+ if (obj != annot->obj)
+ pdf_array_push(annot_arr, obj);
+ }
+
+ /*
+ Overwrite "Annots" in the page dictionary, which has the
+ side-effect of releasing the last reference to old_annot_arr
+ */
+ pdf_dict_puts(page->me, "Annots", annot_arr);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(annot_arr);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+
+ pdf_drop_obj(annot->obj);
+ annot->obj = NULL;
+ doc->dirty = 1;
+}
+
+static fz_colorspace *pdf_to_color(pdf_document *doc, pdf_obj *col, float color[4])
+{
+ fz_colorspace *cs;
+ int i, ncol = pdf_array_len(col);
+
+ switch (ncol)
+ {
+ case 1: cs = fz_device_gray(doc->ctx); break;
+ case 3: cs = fz_device_rgb(doc->ctx); break;
+ case 4: cs = fz_device_cmyk(doc->ctx); break;
+ default: return NULL;
+ }
+
+ for (i = 0; i < ncol; i++)
+ color[i] = pdf_to_real(pdf_array_get(col, i));
+
+ return cs;
+}
+
+static fz_point *
+quadpoints(pdf_document *doc, pdf_obj *annot, int *nout)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *quad = pdf_dict_gets(annot, "QuadPoints");
+ fz_point *qp = NULL;
+ int i, n;
+
+ if (!quad)
+ return NULL;
+
+ n = pdf_array_len(quad);
+
+ if (n%8 != 0)
+ return NULL;
+
+ fz_var(qp);
+ fz_try(ctx)
+ {
+ qp = fz_malloc_array(ctx, n/2, sizeof(fz_point));
+
+ for (i = 0; i < n; i += 2)
+ {
+ qp[i/2].x = pdf_to_real(pdf_array_get(quad, i));
+ qp[i/2].y = pdf_to_real(pdf_array_get(quad, i+1));
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, qp);
+ fz_rethrow(ctx);
+ }
+
+ *nout = n/2;
+
+ return qp;
+}
+
+void
+pdf_set_markup_annot_quadpoints(pdf_document *doc, pdf_annot *annot, fz_point *qp, int n)
+{
+ fz_context *ctx = doc->ctx;
+ fz_matrix ctm;
+ pdf_obj *arr = pdf_new_array(ctx, n*2);
+ int i;
+
+ fz_invert_matrix(&ctm, &annot->page->ctm);
+
+ pdf_dict_puts_drop(annot->obj, "QuadPoints", arr);
+
+ for (i = 0; i < n; i++)
+ {
+ fz_point pt = qp[i];
+ pdf_obj *r;
+
+ fz_transform_point(&pt, &ctm);
+ r = pdf_new_real(ctx, pt.x);
+ pdf_array_push_drop(arr, r);
+ r = pdf_new_real(ctx, pt.y);
+ pdf_array_push_drop(arr, r);
+ }
+}
+
+static void update_rect(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_to_rect(ctx, pdf_dict_gets(annot->obj, "Rect"), &annot->rect);
+ annot->pagerect = annot->rect;
+ fz_transform_rect(&annot->pagerect, &annot->page->ctm);
+}
+
+void
+pdf_set_ink_annot_list(pdf_document *doc, pdf_annot *annot, fz_point *pts, int *counts, int ncount, float color[3], float thickness)
+{
+ fz_context *ctx = doc->ctx;
+ fz_matrix ctm;
+ pdf_obj *list = pdf_new_array(ctx, ncount);
+ pdf_obj *bs, *col;
+ fz_rect rect;
+ int i, k = 0;
+
+ fz_invert_matrix(&ctm, &annot->page->ctm);
+
+ pdf_dict_puts_drop(annot->obj, "InkList", list);
+
+ for (i = 0; i < ncount; i++)
+ {
+ int j;
+ pdf_obj *arc = pdf_new_array(ctx, counts[i]);
+
+ pdf_array_push_drop(list, arc);
+
+ for (j = 0; j < counts[i]; j++)
+ {
+ fz_point pt = pts[k];
+
+ fz_transform_point(&pt, &ctm);
+
+ if (i == 0 && j == 0)
+ {
+ rect.x0 = rect.x1 = pt.x;
+ rect.y0 = rect.y1 = pt.y;
+ }
+ else
+ {
+ fz_include_point_in_rect(&rect, &pt);
+ }
+
+ pdf_array_push_drop(arc, pdf_new_real(ctx, pt.x));
+ pdf_array_push_drop(arc, pdf_new_real(ctx, pt.y));
+ k++;
+ }
+ }
+
+ fz_expand_rect(&rect, thickness);
+ pdf_dict_puts_drop(annot->obj, "Rect", pdf_new_rect(ctx, &rect));
+ update_rect(ctx, annot);
+
+ bs = pdf_new_dict(ctx, 1);
+ pdf_dict_puts_drop(annot->obj, "BS", bs);
+ pdf_dict_puts_drop(bs, "W", pdf_new_real(ctx, thickness));
+
+ col = pdf_new_array(ctx, 3);
+ pdf_dict_puts_drop(annot->obj, "C", col);
+ for (i = 0; i < 3; i++)
+ pdf_array_push_drop(col, pdf_new_real(ctx, color[i]));
+}
+
+void
+pdf_set_annot_obj_appearance(pdf_document *doc, pdf_obj *obj, const fz_matrix *page_ctm, fz_rect *rect, fz_display_list *disp_list)
+{
+ fz_context *ctx = doc->ctx;
+ fz_matrix ctm;
+ fz_matrix mat = fz_identity;
+ fz_device *dev = NULL;
+ pdf_xobject *xobj = NULL;
+
+ fz_invert_matrix(&ctm, page_ctm);
+
+ fz_var(dev);
+ fz_try(ctx)
+ {
+ pdf_obj *ap_obj;
+ fz_rect trect = *rect;
+
+ fz_transform_rect(&trect, &ctm);
+
+ pdf_dict_puts_drop(obj, "Rect", pdf_new_rect(ctx, &trect));
+
+ /* See if there is a current normal appearance */
+ ap_obj = pdf_dict_getp(obj, "AP/N");
+ if (!pdf_is_stream(doc, pdf_to_num(obj), pdf_to_gen(obj)))
+ ap_obj = NULL;
+
+ if (ap_obj == NULL)
+ {
+ ap_obj = pdf_new_xobject(doc, &trect, &mat);
+ pdf_dict_putp_drop(obj, "AP/N", ap_obj);
+ }
+ else
+ {
+ pdf_dict_puts_drop(ap_obj, "Rect", pdf_new_rect(ctx, &trect));
+ pdf_dict_puts_drop(ap_obj, "Matrix", pdf_new_matrix(ctx, &mat));
+ }
+
+ dev = pdf_new_pdf_device(doc, ap_obj, pdf_dict_gets(ap_obj, "Resources"), &mat);
+ fz_run_display_list(disp_list, dev, &ctm, &fz_infinite_rect, NULL);
+ fz_free_device(dev);
+
+ /* Mark the appearance as changed - required for partial update */
+ xobj = pdf_load_xobject(doc, ap_obj);
+ if (xobj)
+ {
+ xobj->iteration++;
+ pdf_drop_xobject(ctx, xobj);
+ }
+
+ doc->dirty = 1;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_device(dev);
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_set_annot_appearance(pdf_document *doc, pdf_annot *annot, fz_rect *rect, fz_display_list *disp_list)
+{
+ pdf_set_annot_obj_appearance(doc, annot->obj, &annot->page->ctm, rect, disp_list);
+ update_rect(doc->ctx, annot);
+}
+
+void
+pdf_set_markup_obj_appearance(pdf_document *doc, pdf_obj *annot, float color[3], float alpha, float line_thickness, float line_height)
+{
+ fz_context *ctx = doc->ctx;
+ fz_path *path = NULL;
+ fz_stroke_state *stroke = NULL;
+ fz_device *dev = NULL;
+ fz_display_list *strike_list = NULL;
+ int i, n;
+ fz_point *qp = quadpoints(doc, annot, &n);
+
+ if (!qp || n <= 0)
+ return;
+
+ fz_var(path);
+ fz_var(stroke);
+ fz_var(dev);
+ fz_var(strike_list);
+ fz_try(ctx)
+ {
+ fz_rect rect = fz_empty_rect;
+
+ rect.x0 = rect.x1 = qp[0].x;
+ rect.y0 = rect.y1 = qp[0].y;
+ for (i = 0; i < n; i++)
+ fz_include_point_in_rect(&rect, &qp[i]);
+
+ strike_list = fz_new_display_list(ctx);
+ dev = fz_new_list_device(ctx, strike_list);
+
+ for (i = 0; i < n; i += 4)
+ {
+ fz_point pt0 = qp[i];
+ fz_point pt1 = qp[i+1];
+ fz_point up;
+ float thickness;
+
+ up.x = qp[i+2].x - qp[i+1].x;
+ up.y = qp[i+2].y - qp[i+1].y;
+
+ pt0.x += line_height * up.x;
+ pt0.y += line_height * up.y;
+ pt1.x += line_height * up.x;
+ pt1.y += line_height * up.y;
+
+ thickness = sqrtf(up.x * up.x + up.y * up.y) * line_thickness;
+
+ if (!stroke || fz_abs(stroke->linewidth - thickness) < SMALL_FLOAT)
+ {
+ if (stroke)
+ {
+ // assert(path)
+ fz_stroke_path(dev, path, stroke, &fz_identity, fz_device_rgb(ctx), color, alpha);
+ fz_drop_stroke_state(ctx, stroke);
+ stroke = NULL;
+ fz_free_path(ctx, path);
+ path = NULL;
+ }
+
+ stroke = fz_new_stroke_state(ctx);
+ stroke->linewidth = thickness;
+ path = fz_new_path(ctx);
+ }
+
+ fz_moveto(ctx, path, pt0.x, pt0.y);
+ fz_lineto(ctx, path, pt1.x, pt1.y);
+ }
+
+ if (stroke)
+ {
+ fz_stroke_path(dev, path, stroke, &fz_identity, fz_device_rgb(ctx), color, alpha);
+ }
+
+ pdf_set_annot_obj_appearance(doc, annot, &fz_identity, &rect, strike_list);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, qp);
+ fz_free_device(dev);
+ fz_drop_stroke_state(ctx, stroke);
+ fz_free_path(ctx, path);
+ fz_drop_display_list(ctx, strike_list);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_set_ink_obj_appearance(pdf_document *doc, pdf_obj *annot)
+{
+ fz_context *ctx = doc->ctx;
+ fz_path *path = NULL;
+ fz_stroke_state *stroke = NULL;
+ fz_device *dev = NULL;
+ fz_display_list *strike_list = NULL;
+
+ fz_var(path);
+ fz_var(stroke);
+ fz_var(dev);
+ fz_var(strike_list);
+ fz_try(ctx)
+ {
+ fz_rect rect = fz_empty_rect;
+ fz_colorspace *cs;
+ float color[4];
+ float width;
+ pdf_obj *list;
+ int n, m, i, j;
+
+ cs = pdf_to_color(doc, pdf_dict_gets(annot, "C"), color);
+ if (!cs)
+ {
+ cs = fz_device_rgb(ctx);
+ color[0] = 1.0f;
+ color[1] = 0.0f;
+ color[2] = 0.0f;
+ }
+
+ width = pdf_to_real(pdf_dict_gets(pdf_dict_gets(annot, "BS"), "W"));
+ if (width == 0.0f)
+ width = 1.0f;
+
+ list = pdf_dict_gets(annot, "InkList");
+
+ n = pdf_array_len(list);
+
+ strike_list = fz_new_display_list(ctx);
+ dev = fz_new_list_device(ctx, strike_list);
+ path = fz_new_path(ctx);
+ stroke = fz_new_stroke_state(ctx);
+ stroke->linewidth = width;
+
+ for (i = 0; i < n; i ++)
+ {
+ fz_point pt_last;
+ pdf_obj *arc = pdf_array_get(list, i);
+ m = pdf_array_len(arc);
+
+ for (j = 0; j < m-1; j += 2)
+ {
+ fz_point pt;
+ pt.x = pdf_to_real(pdf_array_get(arc, j));
+ pt.y = pdf_to_real(pdf_array_get(arc, j+1));
+
+ if (i == 0 && j == 0)
+ {
+ rect.x0 = rect.x1 = pt.x;
+ rect.y0 = rect.y1 = pt.y;
+ }
+ else
+ {
+ fz_include_point_in_rect(&rect, &pt);
+ }
+
+ if (j == 0)
+ fz_moveto(ctx, path, pt.x, pt.y);
+ else
+ fz_curvetov(ctx, path, pt_last.x, pt_last.y, (pt.x + pt_last.x) / 2, (pt.y + pt_last.y) / 2);
+ pt_last = pt;
+ }
+ fz_lineto(ctx, path, pt_last.x, pt_last.y);
+ }
+
+ fz_stroke_path(dev, path, stroke, &fz_identity, cs, color, 1.0f);
+
+ fz_expand_rect(&rect, width);
+
+ pdf_set_annot_obj_appearance(doc, annot, &fz_identity, &rect, strike_list);
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ fz_drop_stroke_state(ctx, stroke);
+ fz_free_path(ctx, path);
+ fz_drop_display_list(ctx, strike_list);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_set_markup_appearance(pdf_document *doc, pdf_annot *annot, float color[3], float alpha, float line_thickness, float line_height)
+{
+ pdf_set_markup_obj_appearance(doc, annot->obj, color, alpha, line_thickness, line_height);
+ update_rect(doc->ctx, annot);
+}
diff --git a/source/pdf/pdf-cmap-load.c b/source/pdf/pdf-cmap-load.c
new file mode 100644
index 00000000..5fcae15d
--- /dev/null
+++ b/source/pdf/pdf-cmap-load.c
@@ -0,0 +1,141 @@
+#include "mupdf/pdf.h"
+
+unsigned int
+pdf_cmap_size(fz_context *ctx, pdf_cmap *cmap)
+{
+ if (cmap == NULL)
+ return 0;
+ if (cmap->storable.refs < 0)
+ return 0;
+
+ return cmap->rcap * sizeof(pdf_range) + cmap->tcap * sizeof(short) + pdf_cmap_size(ctx, cmap->usecmap);
+}
+
+/*
+ * Load CMap stream in PDF file
+ */
+pdf_cmap *
+pdf_load_embedded_cmap(pdf_document *xref, pdf_obj *stmobj)
+{
+ fz_stream *file = NULL;
+ pdf_cmap *cmap = NULL;
+ pdf_cmap *usecmap;
+ pdf_obj *wmode;
+ pdf_obj *obj = NULL;
+ fz_context *ctx = xref->ctx;
+ int phase = 0;
+
+ fz_var(phase);
+ fz_var(obj);
+ fz_var(file);
+ fz_var(cmap);
+
+ if (pdf_obj_marked(stmobj))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Recursion in embedded cmap");
+
+ if ((cmap = pdf_find_item(ctx, pdf_free_cmap_imp, stmobj)))
+ {
+ return cmap;
+ }
+
+ fz_try(ctx)
+ {
+ file = pdf_open_stream(xref, pdf_to_num(stmobj), pdf_to_gen(stmobj));
+ phase = 1;
+ cmap = pdf_load_cmap(ctx, file);
+ phase = 2;
+ fz_close(file);
+ file = NULL;
+
+ wmode = pdf_dict_gets(stmobj, "WMode");
+ if (pdf_is_int(wmode))
+ pdf_set_cmap_wmode(ctx, cmap, pdf_to_int(wmode));
+ obj = pdf_dict_gets(stmobj, "UseCMap");
+ if (pdf_is_name(obj))
+ {
+ usecmap = pdf_load_system_cmap(ctx, pdf_to_name(obj));
+ pdf_set_usecmap(ctx, cmap, usecmap);
+ pdf_drop_cmap(ctx, usecmap);
+ }
+ else if (pdf_is_indirect(obj))
+ {
+ phase = 3;
+ pdf_obj_mark(obj);
+ usecmap = pdf_load_embedded_cmap(xref, obj);
+ pdf_obj_unmark(obj);
+ phase = 4;
+ pdf_set_usecmap(ctx, cmap, usecmap);
+ pdf_drop_cmap(ctx, usecmap);
+ }
+
+ pdf_store_item(ctx, stmobj, cmap, pdf_cmap_size(ctx, cmap));
+ }
+ fz_catch(ctx)
+ {
+ if (file)
+ fz_close(file);
+ if (cmap)
+ pdf_drop_cmap(ctx, cmap);
+ if (phase < 1)
+ fz_rethrow_message(ctx, "cannot open cmap stream (%d %d R)", pdf_to_num(stmobj), pdf_to_gen(stmobj));
+ else if (phase < 2)
+ fz_rethrow_message(ctx, "cannot parse cmap stream (%d %d R)", pdf_to_num(stmobj), pdf_to_gen(stmobj));
+ else if (phase < 3)
+ fz_rethrow_message(ctx, "cannot load system usecmap '%s'", pdf_to_name(obj));
+ else
+ {
+ if (phase == 3)
+ pdf_obj_unmark(obj);
+ fz_rethrow_message(ctx, "cannot load embedded usecmap (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ }
+ }
+
+ return cmap;
+}
+
+/*
+ * Create an Identity-* CMap (for both 1 and 2-byte encodings)
+ */
+pdf_cmap *
+pdf_new_identity_cmap(fz_context *ctx, int wmode, int bytes)
+{
+ pdf_cmap *cmap = pdf_new_cmap(ctx);
+ fz_try(ctx)
+ {
+ sprintf(cmap->cmap_name, "Identity-%c", wmode ? 'V' : 'H');
+ pdf_add_codespace(ctx, cmap, 0x0000, 0xffff, bytes);
+ pdf_map_range_to_range(ctx, cmap, 0x0000, 0xffff, 0);
+ pdf_sort_cmap(ctx, cmap);
+ pdf_set_cmap_wmode(ctx, cmap, wmode);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_cmap(ctx, cmap);
+ fz_rethrow(ctx);
+ }
+ return cmap;
+}
+
+/*
+ * Load predefined CMap from system.
+ */
+pdf_cmap *
+pdf_load_system_cmap(fz_context *ctx, char *cmap_name)
+{
+ pdf_cmap *usecmap;
+ pdf_cmap *cmap;
+
+ cmap = pdf_load_builtin_cmap(ctx, cmap_name);
+ if (!cmap)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no builtin cmap file: %s", cmap_name);
+
+ if (cmap->usecmap_name[0] && !cmap->usecmap)
+ {
+ usecmap = pdf_load_builtin_cmap(ctx, cmap->usecmap_name);
+ if (!usecmap)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "nu builtin cmap file: %s", cmap->usecmap_name);
+ pdf_set_usecmap(ctx, cmap, usecmap);
+ }
+
+ return cmap;
+}
diff --git a/source/pdf/pdf-cmap-parse.c b/source/pdf/pdf-cmap-parse.c
new file mode 100644
index 00000000..361c512f
--- /dev/null
+++ b/source/pdf/pdf-cmap-parse.c
@@ -0,0 +1,344 @@
+#include "mupdf/pdf.h"
+
+/*
+ * CMap parser
+ */
+
+static int
+pdf_code_from_string(char *buf, int len)
+{
+ int a = 0;
+ while (len--)
+ a = (a << 8) | *(unsigned char *)buf++;
+ return a;
+}
+
+static void
+pdf_parse_cmap_name(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_NAME)
+ fz_strlcpy(cmap->cmap_name, buf->scratch, sizeof(cmap->cmap_name));
+ else
+ fz_warn(ctx, "expected name after CMapName in cmap");
+}
+
+static void
+pdf_parse_wmode(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_INT)
+ pdf_set_cmap_wmode(ctx, cmap, buf->i);
+ else
+ fz_warn(ctx, "expected integer after WMode in cmap");
+}
+
+static void
+pdf_parse_codespace_range(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ int lo, hi;
+
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "endcodespacerange"))
+ return;
+
+ else if (tok == PDF_TOK_STRING)
+ {
+ lo = pdf_code_from_string(buf->scratch, buf->len);
+ tok = pdf_lex(file, buf);
+ if (tok == PDF_TOK_STRING)
+ {
+ hi = pdf_code_from_string(buf->scratch, buf->len);
+ pdf_add_codespace(ctx, cmap, lo, hi, buf->len);
+ }
+ else break;
+ }
+
+ else break;
+ }
+
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or endcodespacerange");
+}
+
+static void
+pdf_parse_cid_range(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ int lo, hi, dst;
+
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "endcidrange"))
+ return;
+
+ else if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or endcidrange");
+
+ lo = pdf_code_from_string(buf->scratch, buf->len);
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string");
+
+ hi = pdf_code_from_string(buf->scratch, buf->len);
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected integer");
+
+ dst = buf->i;
+
+ pdf_map_range_to_range(ctx, cmap, lo, hi, dst);
+ }
+}
+
+static void
+pdf_parse_cid_char(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ int src, dst;
+
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "endcidchar"))
+ return;
+
+ else if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or endcidchar");
+
+ src = pdf_code_from_string(buf->scratch, buf->len);
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected integer");
+
+ dst = buf->i;
+
+ pdf_map_range_to_range(ctx, cmap, src, src, dst);
+ }
+}
+
+static void
+pdf_parse_bf_range_array(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf, int lo, int hi)
+{
+ pdf_token tok;
+ int dst[256];
+ int i;
+
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_CLOSE_ARRAY)
+ return;
+
+ /* Note: does not handle [ /Name /Name ... ] */
+ else if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or ]");
+
+ if (buf->len / 2)
+ {
+ int len = fz_mini(buf->len / 2, nelem(dst));
+ for (i = 0; i < len; i++)
+ dst[i] = pdf_code_from_string(&buf->scratch[i * 2], 2);
+
+ pdf_map_one_to_many(ctx, cmap, lo, dst, buf->len / 2);
+ }
+
+ lo ++;
+ }
+}
+
+static void
+pdf_parse_bf_range(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ int lo, hi, dst;
+
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "endbfrange"))
+ return;
+
+ else if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or endbfrange");
+
+ lo = pdf_code_from_string(buf->scratch, buf->len);
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string");
+
+ hi = pdf_code_from_string(buf->scratch, buf->len);
+ if (lo < 0 || lo > 65535 || hi < 0 || hi > 65535 || lo > hi)
+ {
+ fz_warn(ctx, "bf_range limits out of range in cmap %s", cmap->cmap_name);
+ return;
+ }
+
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_STRING)
+ {
+ if (buf->len == 2)
+ {
+ dst = pdf_code_from_string(buf->scratch, buf->len);
+ pdf_map_range_to_range(ctx, cmap, lo, hi, dst);
+ }
+ else
+ {
+ int dststr[256];
+ int i;
+
+ if (buf->len / 2)
+ {
+ int len = fz_mini(buf->len / 2, nelem(dststr));
+ for (i = 0; i < len; i++)
+ dststr[i] = pdf_code_from_string(&buf->scratch[i * 2], 2);
+
+ while (lo <= hi)
+ {
+ dststr[i-1] ++;
+ pdf_map_one_to_many(ctx, cmap, lo, dststr, i);
+ lo ++;
+ }
+ }
+ }
+ }
+
+ else if (tok == PDF_TOK_OPEN_ARRAY)
+ {
+ pdf_parse_bf_range_array(ctx, cmap, file, buf, lo, hi);
+ }
+
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or array or endbfrange");
+ }
+ }
+}
+
+static void
+pdf_parse_bf_char(fz_context *ctx, pdf_cmap *cmap, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ int dst[256];
+ int src;
+ int i;
+
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "endbfchar"))
+ return;
+
+ else if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string or endbfchar");
+
+ src = pdf_code_from_string(buf->scratch, buf->len);
+
+ tok = pdf_lex(file, buf);
+ /* Note: does not handle /dstName */
+ if (tok != PDF_TOK_STRING)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected string");
+
+ if (buf->len / 2)
+ {
+ int len = fz_mini(buf->len / 2, nelem(dst));
+ for (i = 0; i < len; i++)
+ dst[i] = pdf_code_from_string(&buf->scratch[i * 2], 2);
+ pdf_map_one_to_many(ctx, cmap, src, dst, i);
+ }
+ }
+}
+
+pdf_cmap *
+pdf_load_cmap(fz_context *ctx, fz_stream *file)
+{
+ pdf_cmap *cmap;
+ char key[64];
+ pdf_lexbuf buf;
+ pdf_token tok;
+
+ pdf_lexbuf_init(ctx, &buf, PDF_LEXBUF_SMALL);
+ cmap = pdf_new_cmap(ctx);
+
+ strcpy(key, ".notdef");
+
+ fz_try(ctx)
+ {
+ while (1)
+ {
+ tok = pdf_lex(file, &buf);
+
+ if (tok == PDF_TOK_EOF)
+ break;
+
+ else if (tok == PDF_TOK_NAME)
+ {
+ if (!strcmp(buf.scratch, "CMapName"))
+ pdf_parse_cmap_name(ctx, cmap, file, &buf);
+ else if (!strcmp(buf.scratch, "WMode"))
+ pdf_parse_wmode(ctx, cmap, file, &buf);
+ else
+ fz_strlcpy(key, buf.scratch, sizeof key);
+ }
+
+ else if (tok == PDF_TOK_KEYWORD)
+ {
+ if (!strcmp(buf.scratch, "endcmap"))
+ break;
+
+ else if (!strcmp(buf.scratch, "usecmap"))
+ fz_strlcpy(cmap->usecmap_name, key, sizeof(cmap->usecmap_name));
+
+ else if (!strcmp(buf.scratch, "begincodespacerange"))
+ pdf_parse_codespace_range(ctx, cmap, file, &buf);
+
+ else if (!strcmp(buf.scratch, "beginbfchar"))
+ pdf_parse_bf_char(ctx, cmap, file, &buf);
+
+ else if (!strcmp(buf.scratch, "begincidchar"))
+ pdf_parse_cid_char(ctx, cmap, file, &buf);
+
+ else if (!strcmp(buf.scratch, "beginbfrange"))
+ pdf_parse_bf_range(ctx, cmap, file, &buf);
+
+ else if (!strcmp(buf.scratch, "begincidrange"))
+ pdf_parse_cid_range(ctx, cmap, file, &buf);
+ }
+
+ /* ignore everything else */
+ }
+
+ pdf_sort_cmap(ctx, cmap);
+ }
+ fz_always(ctx)
+ {
+ pdf_lexbuf_fin(&buf);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_cmap(ctx, cmap);
+ fz_rethrow_message(ctx, "syntaxerror in cmap");
+ }
+
+ return cmap;
+}
diff --git a/source/pdf/pdf-cmap-table.c b/source/pdf/pdf-cmap-table.c
new file mode 100644
index 00000000..ef69c703
--- /dev/null
+++ b/source/pdf/pdf-cmap-table.c
@@ -0,0 +1,183 @@
+#include "mupdf/pdf.h"
+
+#ifndef NOCJK
+#include "gen_cmap_cns.h"
+#include "gen_cmap_gb.h"
+#include "gen_cmap_japan.h"
+#include "gen_cmap_korea.h"
+#endif
+
+static const struct { char *name; pdf_cmap *cmap; } cmap_table[] =
+{
+#ifndef NOCJK
+ {"78-EUC-H",&cmap_78_EUC_H},
+ {"78-EUC-V",&cmap_78_EUC_V},
+ {"78-H",&cmap_78_H},
+ {"78-RKSJ-H",&cmap_78_RKSJ_H},
+ {"78-RKSJ-V",&cmap_78_RKSJ_V},
+ {"78-V",&cmap_78_V},
+ {"78ms-RKSJ-H",&cmap_78ms_RKSJ_H},
+ {"78ms-RKSJ-V",&cmap_78ms_RKSJ_V},
+ {"83pv-RKSJ-H",&cmap_83pv_RKSJ_H},
+ {"90ms-RKSJ-H",&cmap_90ms_RKSJ_H},
+ {"90ms-RKSJ-V",&cmap_90ms_RKSJ_V},
+ {"90msp-RKSJ-H",&cmap_90msp_RKSJ_H},
+ {"90msp-RKSJ-V",&cmap_90msp_RKSJ_V},
+ {"90pv-RKSJ-H",&cmap_90pv_RKSJ_H},
+ {"90pv-RKSJ-V",&cmap_90pv_RKSJ_V},
+ {"Add-H",&cmap_Add_H},
+ {"Add-RKSJ-H",&cmap_Add_RKSJ_H},
+ {"Add-RKSJ-V",&cmap_Add_RKSJ_V},
+ {"Add-V",&cmap_Add_V},
+ {"Adobe-CNS1-0",&cmap_Adobe_CNS1_0},
+ {"Adobe-CNS1-1",&cmap_Adobe_CNS1_1},
+ {"Adobe-CNS1-2",&cmap_Adobe_CNS1_2},
+ {"Adobe-CNS1-3",&cmap_Adobe_CNS1_3},
+ {"Adobe-CNS1-4",&cmap_Adobe_CNS1_4},
+ {"Adobe-CNS1-5",&cmap_Adobe_CNS1_5},
+ {"Adobe-CNS1-6",&cmap_Adobe_CNS1_6},
+ {"Adobe-CNS1-UCS2",&cmap_Adobe_CNS1_UCS2},
+ {"Adobe-GB1-0",&cmap_Adobe_GB1_0},
+ {"Adobe-GB1-1",&cmap_Adobe_GB1_1},
+ {"Adobe-GB1-2",&cmap_Adobe_GB1_2},
+ {"Adobe-GB1-3",&cmap_Adobe_GB1_3},
+ {"Adobe-GB1-4",&cmap_Adobe_GB1_4},
+ {"Adobe-GB1-5",&cmap_Adobe_GB1_5},
+ {"Adobe-GB1-UCS2",&cmap_Adobe_GB1_UCS2},
+ {"Adobe-Japan1-0",&cmap_Adobe_Japan1_0},
+ {"Adobe-Japan1-1",&cmap_Adobe_Japan1_1},
+ {"Adobe-Japan1-2",&cmap_Adobe_Japan1_2},
+ {"Adobe-Japan1-3",&cmap_Adobe_Japan1_3},
+ {"Adobe-Japan1-4",&cmap_Adobe_Japan1_4},
+ {"Adobe-Japan1-5",&cmap_Adobe_Japan1_5},
+ {"Adobe-Japan1-6",&cmap_Adobe_Japan1_6},
+ {"Adobe-Japan1-UCS2",&cmap_Adobe_Japan1_UCS2},
+ {"Adobe-Japan2-0",&cmap_Adobe_Japan2_0},
+ {"Adobe-Korea1-0",&cmap_Adobe_Korea1_0},
+ {"Adobe-Korea1-1",&cmap_Adobe_Korea1_1},
+ {"Adobe-Korea1-2",&cmap_Adobe_Korea1_2},
+ {"Adobe-Korea1-UCS2",&cmap_Adobe_Korea1_UCS2},
+ {"B5-H",&cmap_B5_H},
+ {"B5-V",&cmap_B5_V},
+ {"B5pc-H",&cmap_B5pc_H},
+ {"B5pc-V",&cmap_B5pc_V},
+ {"CNS-EUC-H",&cmap_CNS_EUC_H},
+ {"CNS-EUC-V",&cmap_CNS_EUC_V},
+ {"CNS1-H",&cmap_CNS1_H},
+ {"CNS1-V",&cmap_CNS1_V},
+ {"CNS2-H",&cmap_CNS2_H},
+ {"CNS2-V",&cmap_CNS2_V},
+ {"ETHK-B5-H",&cmap_ETHK_B5_H},
+ {"ETHK-B5-V",&cmap_ETHK_B5_V},
+ {"ETen-B5-H",&cmap_ETen_B5_H},
+ {"ETen-B5-V",&cmap_ETen_B5_V},
+ {"ETenms-B5-H",&cmap_ETenms_B5_H},
+ {"ETenms-B5-V",&cmap_ETenms_B5_V},
+ {"EUC-H",&cmap_EUC_H},
+ {"EUC-V",&cmap_EUC_V},
+ {"Ext-H",&cmap_Ext_H},
+ {"Ext-RKSJ-H",&cmap_Ext_RKSJ_H},
+ {"Ext-RKSJ-V",&cmap_Ext_RKSJ_V},
+ {"Ext-V",&cmap_Ext_V},
+ {"GB-EUC-H",&cmap_GB_EUC_H},
+ {"GB-EUC-V",&cmap_GB_EUC_V},
+ {"GB-H",&cmap_GB_H},
+ {"GB-V",&cmap_GB_V},
+ {"GBK-EUC-H",&cmap_GBK_EUC_H},
+ {"GBK-EUC-V",&cmap_GBK_EUC_V},
+ {"GBK2K-H",&cmap_GBK2K_H},
+ {"GBK2K-V",&cmap_GBK2K_V},
+ {"GBKp-EUC-H",&cmap_GBKp_EUC_H},
+ {"GBKp-EUC-V",&cmap_GBKp_EUC_V},
+ {"GBT-EUC-H",&cmap_GBT_EUC_H},
+ {"GBT-EUC-V",&cmap_GBT_EUC_V},
+ {"GBT-H",&cmap_GBT_H},
+ {"GBT-V",&cmap_GBT_V},
+ {"GBTpc-EUC-H",&cmap_GBTpc_EUC_H},
+ {"GBTpc-EUC-V",&cmap_GBTpc_EUC_V},
+ {"GBpc-EUC-H",&cmap_GBpc_EUC_H},
+ {"GBpc-EUC-V",&cmap_GBpc_EUC_V},
+ {"H",&cmap_H},
+ {"HKdla-B5-H",&cmap_HKdla_B5_H},
+ {"HKdla-B5-V",&cmap_HKdla_B5_V},
+ {"HKdlb-B5-H",&cmap_HKdlb_B5_H},
+ {"HKdlb-B5-V",&cmap_HKdlb_B5_V},
+ {"HKgccs-B5-H",&cmap_HKgccs_B5_H},
+ {"HKgccs-B5-V",&cmap_HKgccs_B5_V},
+ {"HKm314-B5-H",&cmap_HKm314_B5_H},
+ {"HKm314-B5-V",&cmap_HKm314_B5_V},
+ {"HKm471-B5-H",&cmap_HKm471_B5_H},
+ {"HKm471-B5-V",&cmap_HKm471_B5_V},
+ {"HKscs-B5-H",&cmap_HKscs_B5_H},
+ {"HKscs-B5-V",&cmap_HKscs_B5_V},
+ {"Hankaku",&cmap_Hankaku},
+ {"Hiragana",&cmap_Hiragana},
+ {"Hojo-EUC-H",&cmap_Hojo_EUC_H},
+ {"Hojo-EUC-V",&cmap_Hojo_EUC_V},
+ {"Hojo-H",&cmap_Hojo_H},
+ {"Hojo-V",&cmap_Hojo_V},
+ {"KSC-EUC-H",&cmap_KSC_EUC_H},
+ {"KSC-EUC-V",&cmap_KSC_EUC_V},
+ {"KSC-H",&cmap_KSC_H},
+ {"KSC-Johab-H",&cmap_KSC_Johab_H},
+ {"KSC-Johab-V",&cmap_KSC_Johab_V},
+ {"KSC-V",&cmap_KSC_V},
+ {"KSCms-UHC-H",&cmap_KSCms_UHC_H},
+ {"KSCms-UHC-HW-H",&cmap_KSCms_UHC_HW_H},
+ {"KSCms-UHC-HW-V",&cmap_KSCms_UHC_HW_V},
+ {"KSCms-UHC-V",&cmap_KSCms_UHC_V},
+ {"KSCpc-EUC-H",&cmap_KSCpc_EUC_H},
+ {"KSCpc-EUC-V",&cmap_KSCpc_EUC_V},
+ {"Katakana",&cmap_Katakana},
+ {"NWP-H",&cmap_NWP_H},
+ {"NWP-V",&cmap_NWP_V},
+ {"RKSJ-H",&cmap_RKSJ_H},
+ {"RKSJ-V",&cmap_RKSJ_V},
+ {"Roman",&cmap_Roman},
+ {"UniCNS-UCS2-H",&cmap_UniCNS_UCS2_H},
+ {"UniCNS-UCS2-V",&cmap_UniCNS_UCS2_V},
+ {"UniCNS-UTF16-H",&cmap_UniCNS_UTF16_H},
+ {"UniCNS-UTF16-V",&cmap_UniCNS_UTF16_V},
+ {"UniGB-UCS2-H",&cmap_UniGB_UCS2_H},
+ {"UniGB-UCS2-V",&cmap_UniGB_UCS2_V},
+ {"UniGB-UTF16-H",&cmap_UniGB_UTF16_H},
+ {"UniGB-UTF16-V",&cmap_UniGB_UTF16_V},
+ {"UniHojo-UCS2-H",&cmap_UniHojo_UCS2_H},
+ {"UniHojo-UCS2-V",&cmap_UniHojo_UCS2_V},
+ {"UniHojo-UTF16-H",&cmap_UniHojo_UTF16_H},
+ {"UniHojo-UTF16-V",&cmap_UniHojo_UTF16_V},
+ {"UniJIS-UCS2-H",&cmap_UniJIS_UCS2_H},
+ {"UniJIS-UCS2-HW-H",&cmap_UniJIS_UCS2_HW_H},
+ {"UniJIS-UCS2-HW-V",&cmap_UniJIS_UCS2_HW_V},
+ {"UniJIS-UCS2-V",&cmap_UniJIS_UCS2_V},
+ {"UniJIS-UTF16-H",&cmap_UniJIS_UTF16_H},
+ {"UniJIS-UTF16-V",&cmap_UniJIS_UTF16_V},
+ {"UniJISPro-UCS2-HW-V",&cmap_UniJISPro_UCS2_HW_V},
+ {"UniJISPro-UCS2-V",&cmap_UniJISPro_UCS2_V},
+ {"UniKS-UCS2-H",&cmap_UniKS_UCS2_H},
+ {"UniKS-UCS2-V",&cmap_UniKS_UCS2_V},
+ {"UniKS-UTF16-H",&cmap_UniKS_UTF16_H},
+ {"UniKS-UTF16-V",&cmap_UniKS_UTF16_V},
+ {"V",&cmap_V},
+ {"WP-Symbol",&cmap_WP_Symbol},
+#endif
+};
+
+pdf_cmap *
+pdf_load_builtin_cmap(fz_context *ctx, char *cmap_name)
+{
+ int l = 0;
+ int r = nelem(cmap_table) - 1;
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = strcmp(cmap_name, cmap_table[m].name);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return cmap_table[m].cmap;
+ }
+ return NULL;
+}
diff --git a/source/pdf/pdf-cmap.c b/source/pdf/pdf-cmap.c
new file mode 100644
index 00000000..c006c6bb
--- /dev/null
+++ b/source/pdf/pdf-cmap.c
@@ -0,0 +1,518 @@
+/*
+ * The CMap data structure here is constructed on the fly by
+ * adding simple range-to-range mappings. Then the data structure
+ * is optimized to contain both range-to-range and range-to-table
+ * lookups.
+ *
+ * Any one-to-many mappings are inserted as one-to-table
+ * lookups in the beginning, and are not affected by the optimization
+ * stage.
+ *
+ * There is a special function to add a 256-length range-to-table mapping.
+ * The ranges do not have to be added in order.
+ *
+ * This code can be a lot simpler if we don't care about wasting memory,
+ * or can trust the parser to give us optimal mappings.
+ */
+
+#include "mupdf/pdf.h"
+
+/* Macros for accessing the combined extent_flags field */
+#define pdf_range_high(r) ((r)->low + ((r)->extent_flags >> 2))
+#define pdf_range_flags(r) ((r)->extent_flags & 3)
+#define pdf_range_set_high(r, h) \
+ ((r)->extent_flags = (((r)->extent_flags & 3) | ((h - (r)->low) << 2)))
+#define pdf_range_set_flags(r, f) \
+ ((r)->extent_flags = (((r)->extent_flags & ~3) | f))
+
+/*
+ * Allocate, destroy and simple parameters.
+ */
+
+void
+pdf_free_cmap_imp(fz_context *ctx, fz_storable *cmap_)
+{
+ pdf_cmap *cmap = (pdf_cmap *)cmap_;
+ if (cmap->usecmap)
+ pdf_drop_cmap(ctx, cmap->usecmap);
+ fz_free(ctx, cmap->ranges);
+ fz_free(ctx, cmap->table);
+ fz_free(ctx, cmap);
+}
+
+pdf_cmap *
+pdf_new_cmap(fz_context *ctx)
+{
+ pdf_cmap *cmap;
+
+ cmap = fz_malloc_struct(ctx, pdf_cmap);
+ FZ_INIT_STORABLE(cmap, 1, pdf_free_cmap_imp);
+
+ strcpy(cmap->cmap_name, "");
+ strcpy(cmap->usecmap_name, "");
+ cmap->usecmap = NULL;
+ cmap->wmode = 0;
+ cmap->codespace_len = 0;
+
+ cmap->rlen = 0;
+ cmap->rcap = 0;
+ cmap->ranges = NULL;
+
+ cmap->tlen = 0;
+ cmap->tcap = 0;
+ cmap->table = NULL;
+
+ return cmap;
+}
+
+/* Could be a macro for speed */
+pdf_cmap *
+pdf_keep_cmap(fz_context *ctx, pdf_cmap *cmap)
+{
+ return (pdf_cmap *)fz_keep_storable(ctx, &cmap->storable);
+}
+
+/* Could be a macro for speed */
+void
+pdf_drop_cmap(fz_context *ctx, pdf_cmap *cmap)
+{
+ fz_drop_storable(ctx, &cmap->storable);
+}
+
+void
+pdf_set_usecmap(fz_context *ctx, pdf_cmap *cmap, pdf_cmap *usecmap)
+{
+ int i;
+
+ if (cmap->usecmap)
+ pdf_drop_cmap(ctx, cmap->usecmap);
+ cmap->usecmap = pdf_keep_cmap(ctx, usecmap);
+
+ if (cmap->codespace_len == 0)
+ {
+ cmap->codespace_len = usecmap->codespace_len;
+ for (i = 0; i < usecmap->codespace_len; i++)
+ cmap->codespace[i] = usecmap->codespace[i];
+ }
+}
+
+int
+pdf_cmap_wmode(fz_context *ctx, pdf_cmap *cmap)
+{
+ return cmap->wmode;
+}
+
+void
+pdf_set_cmap_wmode(fz_context *ctx, pdf_cmap *cmap, int wmode)
+{
+ cmap->wmode = wmode;
+}
+
+#ifndef NDEBUG
+void
+pdf_print_cmap(fz_context *ctx, pdf_cmap *cmap)
+{
+ int i, k, n;
+
+ printf("cmap $%p /%s {\n", (void *) cmap, cmap->cmap_name);
+
+ if (cmap->usecmap_name[0])
+ printf("\tusecmap /%s\n", cmap->usecmap_name);
+ if (cmap->usecmap)
+ printf("\tusecmap $%p\n", (void *) cmap->usecmap);
+
+ printf("\twmode %d\n", cmap->wmode);
+
+ printf("\tcodespaces {\n");
+ for (i = 0; i < cmap->codespace_len; i++)
+ {
+ printf("\t\t<%x> <%x>\n", cmap->codespace[i].low, cmap->codespace[i].high);
+ }
+ printf("\t}\n");
+
+ printf("\tranges (%d,%d) {\n", cmap->rlen, cmap->tlen);
+ for (i = 0; i < cmap->rlen; i++)
+ {
+ pdf_range *r = &cmap->ranges[i];
+ printf("\t\t<%04x> <%04x> ", r->low, pdf_range_high(r));
+ if (pdf_range_flags(r) == PDF_CMAP_TABLE)
+ {
+ printf("[ ");
+ for (k = 0; k < pdf_range_high(r) - r->low + 1; k++)
+ printf("%d ", cmap->table[r->offset + k]);
+ printf("]\n");
+ }
+ else if (pdf_range_flags(r) == PDF_CMAP_MULTI)
+ {
+ printf("< ");
+ n = cmap->table[r->offset];
+ for (k = 0; k < n; k++)
+ printf("%04x ", cmap->table[r->offset + 1 + k]);
+ printf(">\n");
+ }
+ else
+ printf("%d\n", r->offset);
+ }
+ printf("\t}\n}\n");
+}
+#endif
+
+/*
+ * Add a codespacerange section.
+ * These ranges are used by pdf_decode_cmap to decode
+ * multi-byte encoded strings.
+ */
+void
+pdf_add_codespace(fz_context *ctx, pdf_cmap *cmap, int low, int high, int n)
+{
+ if (cmap->codespace_len + 1 == nelem(cmap->codespace))
+ {
+ fz_warn(ctx, "assert: too many code space ranges");
+ return;
+ }
+
+ cmap->codespace[cmap->codespace_len].n = n;
+ cmap->codespace[cmap->codespace_len].low = low;
+ cmap->codespace[cmap->codespace_len].high = high;
+ cmap->codespace_len ++;
+}
+
+/*
+ * Add an integer to the table.
+ */
+static void
+add_table(fz_context *ctx, pdf_cmap *cmap, int value)
+{
+ if (cmap->tlen >= USHRT_MAX + 1)
+ {
+ fz_warn(ctx, "cmap table is full; ignoring additional entries");
+ return;
+ }
+ if (cmap->tlen + 1 > cmap->tcap)
+ {
+ int new_cap = cmap->tcap > 1 ? (cmap->tcap * 3) / 2 : 256;
+ cmap->table = fz_resize_array(ctx, cmap->table, new_cap, sizeof(unsigned short));
+ cmap->tcap = new_cap;
+ }
+ cmap->table[cmap->tlen++] = value;
+}
+
+/*
+ * Add a range.
+ */
+static void
+add_range(fz_context *ctx, pdf_cmap *cmap, int low, int high, int flag, int offset)
+{
+ /* Sanity check ranges */
+ if (low < 0 || low > 65535 || high < 0 || high > 65535 || low > high)
+ {
+ fz_warn(ctx, "range limits out of range in cmap %s", cmap->cmap_name);
+ return;
+ }
+ /* If the range is too large to be represented, split it */
+ if (high - low > 0x3fff)
+ {
+ add_range(ctx, cmap, low, low+0x3fff, flag, offset);
+ add_range(ctx, cmap, low+0x3fff, high, flag, offset+0x3fff);
+ return;
+ }
+ if (cmap->rlen + 1 > cmap->rcap)
+ {
+ int new_cap = cmap->rcap > 1 ? (cmap->rcap * 3) / 2 : 256;
+ cmap->ranges = fz_resize_array(ctx, cmap->ranges, new_cap, sizeof(pdf_range));
+ cmap->rcap = new_cap;
+ }
+ cmap->ranges[cmap->rlen].low = low;
+ pdf_range_set_high(&cmap->ranges[cmap->rlen], high);
+ pdf_range_set_flags(&cmap->ranges[cmap->rlen], flag);
+ cmap->ranges[cmap->rlen].offset = offset;
+ cmap->rlen ++;
+}
+
+/*
+ * Add a range-to-table mapping.
+ */
+void
+pdf_map_range_to_table(fz_context *ctx, pdf_cmap *cmap, int low, int *table, int len)
+{
+ int i;
+ int high = low + len;
+ int offset = cmap->tlen;
+ if (cmap->tlen + len >= USHRT_MAX + 1)
+ fz_warn(ctx, "cannot map range to table; table is full");
+ else
+ {
+ for (i = 0; i < len; i++)
+ add_table(ctx, cmap, table[i]);
+ add_range(ctx, cmap, low, high, PDF_CMAP_TABLE, offset);
+ }
+}
+
+/*
+ * Add a range of contiguous one-to-one mappings (ie 1..5 maps to 21..25)
+ */
+void
+pdf_map_range_to_range(fz_context *ctx, pdf_cmap *cmap, int low, int high, int offset)
+{
+ add_range(ctx, cmap, low, high, high - low == 0 ? PDF_CMAP_SINGLE : PDF_CMAP_RANGE, offset);
+}
+
+/*
+ * Add a single one-to-many mapping.
+ */
+void
+pdf_map_one_to_many(fz_context *ctx, pdf_cmap *cmap, int low, int *values, int len)
+{
+ int offset, i;
+
+ if (len == 1)
+ {
+ add_range(ctx, cmap, low, low, PDF_CMAP_SINGLE, values[0]);
+ return;
+ }
+
+ if (len > 8)
+ {
+ fz_warn(ctx, "one to many mapping is too large (%d); truncating", len);
+ len = 8;
+ }
+
+ if (len == 2 &&
+ values[0] >= 0xD800 && values[0] <= 0xDBFF &&
+ values[1] >= 0xDC00 && values[1] <= 0xDFFF)
+ {
+ fz_warn(ctx, "ignoring surrogate pair mapping in cmap %s", cmap->cmap_name);
+ return;
+ }
+
+ if (cmap->tlen + len + 1 >= USHRT_MAX + 1)
+ fz_warn(ctx, "cannot map one to many; table is full");
+ else
+ {
+ offset = cmap->tlen;
+ add_table(ctx, cmap, len);
+ for (i = 0; i < len; i++)
+ add_table(ctx, cmap, values[i]);
+ add_range(ctx, cmap, low, low, PDF_CMAP_MULTI, offset);
+ }
+}
+
+/*
+ * Sort the input ranges.
+ * Merge contiguous input ranges to range-to-range if the output is contiguous.
+ * Merge contiguous input ranges to range-to-table if the output is random.
+ */
+
+static int cmprange(const void *va, const void *vb)
+{
+ return ((const pdf_range*)va)->low - ((const pdf_range*)vb)->low;
+}
+
+void
+pdf_sort_cmap(fz_context *ctx, pdf_cmap *cmap)
+{
+ pdf_range *a; /* last written range on output */
+ pdf_range *b; /* current range examined on input */
+
+ if (cmap->rlen == 0)
+ return;
+
+ qsort(cmap->ranges, cmap->rlen, sizeof(pdf_range), cmprange);
+
+ if (cmap->tlen >= USHRT_MAX + 1)
+ {
+ fz_warn(ctx, "cmap table is full; will not combine ranges");
+ return;
+ }
+
+ a = cmap->ranges;
+ b = cmap->ranges + 1;
+
+ while (b < cmap->ranges + cmap->rlen)
+ {
+ /* ignore one-to-many mappings */
+ if (pdf_range_flags(b) == PDF_CMAP_MULTI)
+ {
+ *(++a) = *b;
+ }
+
+ /* input contiguous */
+ else if (pdf_range_high(a) + 1 == b->low)
+ {
+ /* output contiguous */
+ if (pdf_range_high(a) - a->low + a->offset + 1 == b->offset)
+ {
+ /* SR -> R and SS -> R and RR -> R and RS -> R */
+ if ((pdf_range_flags(a) == PDF_CMAP_SINGLE || pdf_range_flags(a) == PDF_CMAP_RANGE) && (pdf_range_high(b) - a->low <= 0x3fff))
+ {
+ pdf_range_set_flags(a, PDF_CMAP_RANGE);
+ pdf_range_set_high(a, pdf_range_high(b));
+ }
+
+ /* LS -> L */
+ else if (pdf_range_flags(a) == PDF_CMAP_TABLE && pdf_range_flags(b) == PDF_CMAP_SINGLE && (pdf_range_high(b) - a->low <= 0x3fff))
+ {
+ pdf_range_set_high(a, pdf_range_high(b));
+ add_table(ctx, cmap, b->offset);
+ }
+
+ /* LR -> LR */
+ else if (pdf_range_flags(a) == PDF_CMAP_TABLE && pdf_range_flags(b) == PDF_CMAP_RANGE)
+ {
+ *(++a) = *b;
+ }
+
+ /* XX -> XX */
+ else
+ {
+ *(++a) = *b;
+ }
+ }
+
+ /* output separated */
+ else
+ {
+ /* SS -> L */
+ if (pdf_range_flags(a) == PDF_CMAP_SINGLE && pdf_range_flags(b) == PDF_CMAP_SINGLE)
+ {
+ pdf_range_set_flags(a, PDF_CMAP_TABLE);
+ pdf_range_set_high(a, pdf_range_high(b));
+ add_table(ctx, cmap, a->offset);
+ add_table(ctx, cmap, b->offset);
+ a->offset = cmap->tlen - 2;
+ }
+
+ /* LS -> L */
+ else if (pdf_range_flags(a) == PDF_CMAP_TABLE && pdf_range_flags(b) == PDF_CMAP_SINGLE && (pdf_range_high(b) - a->low <= 0x3fff))
+ {
+ pdf_range_set_high(a, pdf_range_high(b));
+ add_table(ctx, cmap, b->offset);
+ }
+
+ /* XX -> XX */
+ else
+ {
+ *(++a) = *b;
+ }
+ }
+ }
+
+ /* input separated: XX -> XX */
+ else
+ {
+ *(++a) = *b;
+ }
+
+ b ++;
+ }
+
+ cmap->rlen = a - cmap->ranges + 1;
+}
+
+/*
+ * Lookup the mapping of a codepoint.
+ */
+int
+pdf_lookup_cmap(pdf_cmap *cmap, int cpt)
+{
+ int l = 0;
+ int r = cmap->rlen - 1;
+ int m;
+
+ while (l <= r)
+ {
+ m = (l + r) >> 1;
+ if (cpt < cmap->ranges[m].low)
+ r = m - 1;
+ else if (cpt > pdf_range_high(&cmap->ranges[m]))
+ l = m + 1;
+ else
+ {
+ int i = cpt - cmap->ranges[m].low + cmap->ranges[m].offset;
+ if (pdf_range_flags(&cmap->ranges[m]) == PDF_CMAP_TABLE)
+ return cmap->table[i];
+ if (pdf_range_flags(&cmap->ranges[m]) == PDF_CMAP_MULTI)
+ return -1; /* should use lookup_cmap_full */
+ return i;
+ }
+ }
+
+ if (cmap->usecmap)
+ return pdf_lookup_cmap(cmap->usecmap, cpt);
+
+ return -1;
+}
+
+int
+pdf_lookup_cmap_full(pdf_cmap *cmap, int cpt, int *out)
+{
+ int i, k, n;
+ int l = 0;
+ int r = cmap->rlen - 1;
+ int m;
+
+ while (l <= r)
+ {
+ m = (l + r) >> 1;
+ if (cpt < cmap->ranges[m].low)
+ r = m - 1;
+ else if (cpt > pdf_range_high(&cmap->ranges[m]))
+ l = m + 1;
+ else
+ {
+ k = cpt - cmap->ranges[m].low + cmap->ranges[m].offset;
+ if (pdf_range_flags(&cmap->ranges[m]) == PDF_CMAP_TABLE)
+ {
+ out[0] = cmap->table[k];
+ return 1;
+ }
+ else if (pdf_range_flags(&cmap->ranges[m]) == PDF_CMAP_MULTI)
+ {
+ n = cmap->ranges[m].offset;
+ for (i = 0; i < cmap->table[n]; i++)
+ out[i] = cmap->table[n + i + 1];
+ return cmap->table[n];
+ }
+ else
+ {
+ out[0] = k;
+ return 1;
+ }
+ }
+ }
+
+ if (cmap->usecmap)
+ return pdf_lookup_cmap_full(cmap->usecmap, cpt, out);
+
+ return 0;
+}
+
+/*
+ * Use the codespace ranges to extract a codepoint from a
+ * multi-byte encoded string.
+ */
+int
+pdf_decode_cmap(pdf_cmap *cmap, unsigned char *buf, int *cpt)
+{
+ int k, n, c;
+
+ c = 0;
+ for (n = 0; n < 4; n++)
+ {
+ c = (c << 8) | buf[n];
+ for (k = 0; k < cmap->codespace_len; k++)
+ {
+ if (cmap->codespace[k].n == n + 1)
+ {
+ if (c >= cmap->codespace[k].low && c <= cmap->codespace[k].high)
+ {
+ *cpt = c;
+ return n + 1;
+ }
+ }
+ }
+ }
+
+ *cpt = 0;
+ return 1;
+}
diff --git a/source/pdf/pdf-colorspace.c b/source/pdf/pdf-colorspace.c
new file mode 100644
index 00000000..84b3e847
--- /dev/null
+++ b/source/pdf/pdf-colorspace.c
@@ -0,0 +1,338 @@
+#include "mupdf/pdf.h"
+
+/* ICCBased */
+
+static fz_colorspace *
+load_icc_based(pdf_document *xref, pdf_obj *dict)
+{
+ int n;
+
+ n = pdf_to_int(pdf_dict_gets(dict, "N"));
+
+ switch (n)
+ {
+ case 1: return fz_device_gray(xref->ctx);
+ case 3: return fz_device_rgb(xref->ctx);
+ case 4: return fz_device_cmyk(xref->ctx);
+ }
+
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "syntaxerror: ICCBased must have 1, 3 or 4 components");
+ return NULL; /* Stupid MSVC */
+}
+
+/* Lab */
+
+static inline float fung(float x)
+{
+ if (x >= 6.0f / 29.0f)
+ return x * x * x;
+ return (108.0f / 841.0f) * (x - (4.0f / 29.0f));
+}
+
+static void
+lab_to_rgb(fz_context *ctx, fz_colorspace *cs, float *lab, float *rgb)
+{
+ /* input is in range (0..100, -128..127, -128..127) not (0..1, 0..1, 0..1) */
+ float lstar, astar, bstar, l, m, n, x, y, z, r, g, b;
+ lstar = lab[0];
+ astar = lab[1];
+ bstar = lab[2];
+ m = (lstar + 16) / 116;
+ l = m + astar / 500;
+ n = m - bstar / 200;
+ x = fung(l);
+ y = fung(m);
+ z = fung(n);
+ r = (3.240449f * x + -1.537136f * y + -0.498531f * z) * 0.830026f;
+ g = (-0.969265f * x + 1.876011f * y + 0.041556f * z) * 1.05452f;
+ b = (0.055643f * x + -0.204026f * y + 1.057229f * z) * 1.1003f;
+ rgb[0] = sqrtf(fz_clamp(r, 0, 1));
+ rgb[1] = sqrtf(fz_clamp(g, 0, 1));
+ rgb[2] = sqrtf(fz_clamp(b, 0, 1));
+}
+
+static void
+rgb_to_lab(fz_context *ctx, fz_colorspace *cs, float *rgb, float *lab)
+{
+ fz_warn(ctx, "cannot convert into L*a*b colorspace");
+ lab[0] = rgb[0];
+ lab[1] = rgb[1];
+ lab[2] = rgb[2];
+}
+
+static fz_colorspace k_device_lab = { {-1, fz_free_colorspace_imp}, 0, "Lab", 3, lab_to_rgb, rgb_to_lab };
+static fz_colorspace *fz_device_lab = &k_device_lab;
+
+/* Separation and DeviceN */
+
+struct separation
+{
+ fz_colorspace *base;
+ fz_function *tint;
+};
+
+static void
+separation_to_rgb(fz_context *ctx, fz_colorspace *cs, float *color, float *rgb)
+{
+ struct separation *sep = cs->data;
+ float alt[FZ_MAX_COLORS];
+ fz_eval_function(ctx, sep->tint, color, cs->n, alt, sep->base->n);
+ sep->base->to_rgb(ctx, sep->base, alt, rgb);
+}
+
+static void
+free_separation(fz_context *ctx, fz_colorspace *cs)
+{
+ struct separation *sep = cs->data;
+ fz_drop_colorspace(ctx, sep->base);
+ fz_drop_function(ctx, sep->tint);
+ fz_free(ctx, sep);
+}
+
+static fz_colorspace *
+load_separation(pdf_document *xref, pdf_obj *array)
+{
+ fz_colorspace *cs;
+ struct separation *sep = NULL;
+ fz_context *ctx = xref->ctx;
+ pdf_obj *nameobj = pdf_array_get(array, 1);
+ pdf_obj *baseobj = pdf_array_get(array, 2);
+ pdf_obj *tintobj = pdf_array_get(array, 3);
+ fz_colorspace *base;
+ fz_function *tint = NULL;
+ int n;
+
+ fz_var(tint);
+ fz_var(sep);
+
+ if (pdf_is_array(nameobj))
+ n = pdf_array_len(nameobj);
+ else
+ n = 1;
+
+ if (n > FZ_MAX_COLORS)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "too many components in colorspace");
+
+ base = pdf_load_colorspace(xref, baseobj);
+
+ fz_try(ctx)
+ {
+ tint = pdf_load_function(xref, tintobj, n, base->n);
+ /* RJW: fz_drop_colorspace(ctx, base);
+ * "cannot load tint function (%d %d R)", pdf_to_num(tintobj), pdf_to_gen(tintobj) */
+
+ sep = fz_malloc_struct(ctx, struct separation);
+ sep->base = base;
+ sep->tint = tint;
+
+ cs = fz_new_colorspace(ctx, n == 1 ? "Separation" : "DeviceN", n);
+ cs->to_rgb = separation_to_rgb;
+ cs->free_data = free_separation;
+ cs->data = sep;
+ cs->size += sizeof(struct separation) + (base ? base->size : 0) + fz_function_size(tint);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_colorspace(ctx, base);
+ fz_drop_function(ctx, tint);
+ fz_free(ctx, sep);
+ fz_rethrow(ctx);
+ }
+
+ return cs;
+}
+
+static fz_colorspace *
+load_indexed(pdf_document *xref, pdf_obj *array)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *baseobj = pdf_array_get(array, 1);
+ pdf_obj *highobj = pdf_array_get(array, 2);
+ pdf_obj *lookupobj = pdf_array_get(array, 3);
+ fz_colorspace *base = NULL;
+ fz_colorspace *cs;
+ int i, n, high;
+ unsigned char *lookup = NULL;
+
+ fz_var(base);
+
+ fz_try(ctx)
+ {
+ base = pdf_load_colorspace(xref, baseobj);
+
+ high = pdf_to_int(highobj);
+ high = fz_clampi(high, 0, 255);
+ n = base->n * (high + 1);
+ lookup = fz_malloc_array(ctx, 1, n);
+
+ if (pdf_is_string(lookupobj) && pdf_to_str_len(lookupobj) == n)
+ {
+ unsigned char *buf = (unsigned char *) pdf_to_str_buf(lookupobj);
+ for (i = 0; i < n; i++)
+ lookup[i] = buf[i];
+ }
+ else if (pdf_is_indirect(lookupobj))
+ {
+ fz_stream *file = NULL;
+
+ fz_var(file);
+
+ fz_try(ctx)
+ {
+ file = pdf_open_stream(xref, pdf_to_num(lookupobj), pdf_to_gen(lookupobj));
+ i = fz_read(file, lookup, n);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot open colorspace lookup table (%d 0 R)", pdf_to_num(lookupobj));
+ }
+ }
+ else
+ {
+ fz_rethrow_message(ctx, "cannot parse colorspace lookup table");
+ }
+
+ cs = fz_new_indexed_colorspace(ctx, base, high, lookup);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_colorspace(ctx, base);
+ fz_free(ctx, lookup);
+ fz_rethrow(ctx);
+ }
+
+ return cs;
+}
+
+/* Parse and create colorspace from PDF object */
+
+static fz_colorspace *
+pdf_load_colorspace_imp(pdf_document *xref, pdf_obj *obj)
+{
+ fz_context *ctx = xref->ctx;
+
+ if (pdf_obj_marked(obj))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Recursion in colorspace definition");
+
+ if (pdf_is_name(obj))
+ {
+ const char *str = pdf_to_name(obj);
+ if (!strcmp(str, "Pattern"))
+ return fz_device_gray(ctx);
+ else if (!strcmp(str, "G"))
+ return fz_device_gray(ctx);
+ else if (!strcmp(str, "RGB"))
+ return fz_device_rgb(ctx);
+ else if (!strcmp(str, "CMYK"))
+ return fz_device_cmyk(ctx);
+ else if (!strcmp(str, "DeviceGray"))
+ return fz_device_gray(ctx);
+ else if (!strcmp(str, "DeviceRGB"))
+ return fz_device_rgb(ctx);
+ else if (!strcmp(str, "DeviceCMYK"))
+ return fz_device_cmyk(ctx);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown colorspace: %s", pdf_to_name(obj));
+ }
+
+ else if (pdf_is_array(obj))
+ {
+ pdf_obj *name = pdf_array_get(obj, 0);
+ const char *str = pdf_to_name(name);
+
+ if (pdf_is_name(name))
+ {
+ /* load base colorspace instead */
+ if (!strcmp(str, "G"))
+ return fz_device_gray(ctx);
+ else if (!strcmp(str, "RGB"))
+ return fz_device_rgb(ctx);
+ else if (!strcmp(str, "CMYK"))
+ return fz_device_cmyk(ctx);
+ else if (!strcmp(str, "DeviceGray"))
+ return fz_device_gray(ctx);
+ else if (!strcmp(str, "DeviceRGB"))
+ return fz_device_rgb(ctx);
+ else if (!strcmp(str, "DeviceCMYK"))
+ return fz_device_cmyk(ctx);
+ else if (!strcmp(str, "CalGray"))
+ return fz_device_gray(ctx);
+ else if (!strcmp(str, "CalRGB"))
+ return fz_device_rgb(ctx);
+ else if (!strcmp(str, "CalCMYK"))
+ return fz_device_cmyk(ctx);
+ else if (!strcmp(str, "Lab"))
+ return fz_device_lab;
+ else
+ {
+ fz_colorspace *cs;
+ fz_try(ctx)
+ {
+ pdf_obj_mark(obj);
+ if (!strcmp(str, "ICCBased"))
+ cs = load_icc_based(xref, pdf_array_get(obj, 1));
+
+ else if (!strcmp(str, "Indexed"))
+ cs = load_indexed(xref, obj);
+ else if (!strcmp(str, "I"))
+ cs = load_indexed(xref, obj);
+
+ else if (!strcmp(str, "Separation"))
+ cs = load_separation(xref, obj);
+
+ else if (!strcmp(str, "DeviceN"))
+ cs = load_separation(xref, obj);
+ else if (!strcmp(str, "Pattern"))
+ {
+ pdf_obj *pobj;
+
+ pobj = pdf_array_get(obj, 1);
+ if (!pobj)
+ {
+ cs = fz_device_gray(ctx);
+ break;
+ }
+
+ cs = pdf_load_colorspace(xref, pobj);
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: unknown colorspace %s", str);
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(obj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ return cs;
+ }
+ }
+ }
+
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "syntaxerror: could not parse color space (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ return NULL; /* Stupid MSVC */
+}
+
+fz_colorspace *
+pdf_load_colorspace(pdf_document *xref, pdf_obj *obj)
+{
+ fz_context *ctx = xref->ctx;
+ fz_colorspace *cs;
+
+ if ((cs = pdf_find_item(ctx, fz_free_colorspace_imp, obj)))
+ {
+ return cs;
+ }
+
+ cs = pdf_load_colorspace_imp(xref, obj);
+
+ pdf_store_item(ctx, obj, cs, cs->size);
+
+ return cs;
+}
diff --git a/source/pdf/pdf-crypt.c b/source/pdf/pdf-crypt.c
new file mode 100644
index 00000000..5128473c
--- /dev/null
+++ b/source/pdf/pdf-crypt.c
@@ -0,0 +1,1010 @@
+#include "mupdf/pdf.h"
+
+enum
+{
+ PDF_CRYPT_NONE,
+ PDF_CRYPT_RC4,
+ PDF_CRYPT_AESV2,
+ PDF_CRYPT_AESV3,
+ PDF_CRYPT_UNKNOWN,
+};
+
+typedef struct pdf_crypt_filter_s pdf_crypt_filter;
+
+struct pdf_crypt_filter_s
+{
+ int method;
+ int length;
+};
+
+struct pdf_crypt_s
+{
+ pdf_obj *id;
+
+ int v;
+ int length;
+ pdf_obj *cf;
+ pdf_crypt_filter stmf;
+ pdf_crypt_filter strf;
+
+ int r;
+ unsigned char o[48];
+ unsigned char u[48];
+ unsigned char oe[32];
+ unsigned char ue[32];
+ int p;
+ int encrypt_metadata;
+
+ unsigned char key[32]; /* decryption key generated from password */
+ fz_context *ctx;
+};
+
+static void pdf_parse_crypt_filter(fz_context *ctx, pdf_crypt_filter *cf, pdf_crypt *crypt, char *name);
+
+/*
+ * Create crypt object for decrypting strings and streams
+ * given the Encryption and ID objects.
+ */
+
+pdf_crypt *
+pdf_new_crypt(fz_context *ctx, pdf_obj *dict, pdf_obj *id)
+{
+ pdf_crypt *crypt;
+ pdf_obj *obj;
+
+ crypt = fz_malloc_struct(ctx, pdf_crypt);
+
+ /* Common to all security handlers (PDF 1.7 table 3.18) */
+
+ obj = pdf_dict_gets(dict, "Filter");
+ if (!pdf_is_name(obj))
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unspecified encryption handler");
+ }
+ if (strcmp(pdf_to_name(obj), "Standard") != 0)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown encryption handler: '%s'", pdf_to_name(obj));
+ }
+
+ crypt->v = 0;
+ obj = pdf_dict_gets(dict, "V");
+ if (pdf_is_int(obj))
+ crypt->v = pdf_to_int(obj);
+ if (crypt->v != 1 && crypt->v != 2 && crypt->v != 4 && crypt->v != 5)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown encryption version");
+ }
+
+ /* Standard security handler (PDF 1.7 table 3.19) */
+
+ obj = pdf_dict_gets(dict, "R");
+ if (pdf_is_int(obj))
+ crypt->r = pdf_to_int(obj);
+ else if (crypt->v <= 4)
+ {
+ fz_warn(ctx, "encryption dictionary missing revision value, guessing...");
+ if (crypt->v < 2)
+ crypt->r = 2;
+ else if (crypt->v == 2)
+ crypt->r = 3;
+ else if (crypt->v == 4)
+ crypt->r = 4;
+ }
+ else
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing version and revision value");
+ }
+
+ obj = pdf_dict_gets(dict, "O");
+ if (pdf_is_string(obj) && pdf_to_str_len(obj) == 32)
+ memcpy(crypt->o, pdf_to_str_buf(obj), 32);
+ /* /O and /U are supposed to be 48 bytes long for revision 5 and 6, they're often longer, though */
+ else if (crypt->r >= 5 && pdf_is_string(obj) && pdf_to_str_len(obj) >= 48)
+ memcpy(crypt->o, pdf_to_str_buf(obj), 48);
+ else
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing owner password");
+ }
+
+ obj = pdf_dict_gets(dict, "U");
+ if (pdf_is_string(obj) && pdf_to_str_len(obj) == 32)
+ memcpy(crypt->u, pdf_to_str_buf(obj), 32);
+ /* /O and /U are supposed to be 48 bytes long for revision 5 and 6, they're often longer, though */
+ else if (crypt->r >= 5 && pdf_is_string(obj) && pdf_to_str_len(obj) >= 48)
+ memcpy(crypt->u, pdf_to_str_buf(obj), 48);
+ else if (pdf_is_string(obj) && pdf_to_str_len(obj) < 32)
+ {
+ fz_warn(ctx, "encryption password key too short (%d)", pdf_to_str_len(obj));
+ memcpy(crypt->u, pdf_to_str_buf(obj), pdf_to_str_len(obj));
+ }
+ else
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing user password");
+ }
+
+ obj = pdf_dict_gets(dict, "P");
+ if (pdf_is_int(obj))
+ crypt->p = pdf_to_int(obj);
+ else
+ {
+ fz_warn(ctx, "encryption dictionary missing permissions");
+ crypt->p = 0xfffffffc;
+ }
+
+ if (crypt->r == 5 || crypt->r == 6)
+ {
+ obj = pdf_dict_gets(dict, "OE");
+ if (!pdf_is_string(obj) || pdf_to_str_len(obj) != 32)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing owner encryption key");
+ }
+ memcpy(crypt->oe, pdf_to_str_buf(obj), 32);
+
+ obj = pdf_dict_gets(dict, "UE");
+ if (!pdf_is_string(obj) || pdf_to_str_len(obj) != 32)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing user encryption key");
+ }
+ memcpy(crypt->ue, pdf_to_str_buf(obj), 32);
+ }
+
+ crypt->encrypt_metadata = 1;
+ obj = pdf_dict_gets(dict, "EncryptMetadata");
+ if (pdf_is_bool(obj))
+ crypt->encrypt_metadata = pdf_to_bool(obj);
+
+ /* Extract file identifier string */
+
+ if (pdf_is_array(id) && pdf_array_len(id) == 2)
+ {
+ obj = pdf_array_get(id, 0);
+ if (pdf_is_string(obj))
+ crypt->id = pdf_keep_obj(obj);
+ }
+ else
+ fz_warn(ctx, "missing file identifier, may not be able to do decryption");
+
+ /* Determine encryption key length */
+
+ crypt->length = 40;
+ if (crypt->v == 2 || crypt->v == 4)
+ {
+ obj = pdf_dict_gets(dict, "Length");
+ if (pdf_is_int(obj))
+ crypt->length = pdf_to_int(obj);
+
+ /* work-around for pdf generators that assume length is in bytes */
+ if (crypt->length < 40)
+ crypt->length = crypt->length * 8;
+
+ if (crypt->length % 8 != 0)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid encryption key length");
+ }
+ if (crypt->length < 0 || crypt->length > 256)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid encryption key length");
+ }
+ }
+
+ if (crypt->v == 5)
+ crypt->length = 256;
+
+ if (crypt->v == 1 || crypt->v == 2)
+ {
+ crypt->stmf.method = PDF_CRYPT_RC4;
+ crypt->stmf.length = crypt->length;
+
+ crypt->strf.method = PDF_CRYPT_RC4;
+ crypt->strf.length = crypt->length;
+ }
+
+ if (crypt->v == 4 || crypt->v == 5)
+ {
+ crypt->stmf.method = PDF_CRYPT_NONE;
+ crypt->stmf.length = crypt->length;
+
+ crypt->strf.method = PDF_CRYPT_NONE;
+ crypt->strf.length = crypt->length;
+
+ obj = pdf_dict_gets(dict, "CF");
+ if (pdf_is_dict(obj))
+ {
+ crypt->cf = pdf_keep_obj(obj);
+ }
+ else
+ {
+ crypt->cf = NULL;
+ }
+
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(dict, "StmF");
+ if (pdf_is_name(obj))
+ pdf_parse_crypt_filter(ctx, &crypt->stmf, crypt, pdf_to_name(obj));
+
+ obj = pdf_dict_gets(dict, "StrF");
+ if (pdf_is_name(obj))
+ pdf_parse_crypt_filter(ctx, &crypt->strf, crypt, pdf_to_name(obj));
+ }
+ fz_catch(ctx)
+ {
+ pdf_free_crypt(ctx, crypt);
+ fz_rethrow_message(ctx, "cannot parse string crypt filter (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ }
+
+ /* in crypt revision 4, the crypt filter determines the key length */
+ if (crypt->strf.method != PDF_CRYPT_NONE)
+ crypt->length = crypt->stmf.length;
+ }
+
+ return crypt;
+}
+
+void
+pdf_free_crypt(fz_context *ctx, pdf_crypt *crypt)
+{
+ pdf_drop_obj(crypt->id);
+ pdf_drop_obj(crypt->cf);
+ fz_free(ctx, crypt);
+}
+
+/*
+ * Parse a CF dictionary entry (PDF 1.7 table 3.22)
+ */
+
+static void
+pdf_parse_crypt_filter(fz_context *ctx, pdf_crypt_filter *cf, pdf_crypt *crypt, char *name)
+{
+ pdf_obj *obj;
+ pdf_obj *dict;
+ int is_identity = (strcmp(name, "Identity") == 0);
+ int is_stdcf = (!is_identity && (strcmp(name, "StdCF") == 0));
+
+ if (!is_identity && !is_stdcf)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Crypt Filter not Identity or StdCF (%d %d R)", pdf_to_num(crypt->cf), pdf_to_gen(crypt->cf));
+
+ cf->method = PDF_CRYPT_NONE;
+ cf->length = crypt->length;
+
+ if (!crypt->cf)
+ {
+ cf->method = (is_identity ? PDF_CRYPT_NONE : PDF_CRYPT_RC4);
+ return;
+ }
+
+ dict = pdf_dict_gets(crypt->cf, name);
+ if (!pdf_is_dict(dict))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot parse crypt filter (%d %d R)", pdf_to_num(crypt->cf), pdf_to_gen(crypt->cf));
+
+ obj = pdf_dict_gets(dict, "CFM");
+ if (pdf_is_name(obj))
+ {
+ if (!strcmp(pdf_to_name(obj), "None"))
+ cf->method = PDF_CRYPT_NONE;
+ else if (!strcmp(pdf_to_name(obj), "V2"))
+ cf->method = PDF_CRYPT_RC4;
+ else if (!strcmp(pdf_to_name(obj), "AESV2"))
+ cf->method = PDF_CRYPT_AESV2;
+ else if (!strcmp(pdf_to_name(obj), "AESV3"))
+ cf->method = PDF_CRYPT_AESV3;
+ else
+ fz_warn(ctx, "unknown encryption method: %s", pdf_to_name(obj));
+ }
+
+ obj = pdf_dict_gets(dict, "Length");
+ if (pdf_is_int(obj))
+ cf->length = pdf_to_int(obj);
+
+ /* the length for crypt filters is supposed to be in bytes not bits */
+ if (cf->length < 40)
+ cf->length = cf->length * 8;
+
+ if ((cf->length % 8) != 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key length: %d", cf->length);
+
+ if ((crypt->r == 1 || crypt->r == 2 || crypt->r == 4) &&
+ (cf->length < 0 || cf->length > 128))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key length: %d", cf->length);
+ if ((crypt->r == 5 || crypt->r == 6) && cf->length != 256)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key length: %d", cf->length);
+}
+
+/*
+ * Compute an encryption key (PDF 1.7 algorithm 3.2)
+ */
+
+static const unsigned char padding[32] =
+{
+ 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
+ 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
+ 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
+ 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
+};
+
+static void
+pdf_compute_encryption_key(pdf_crypt *crypt, unsigned char *password, int pwlen, unsigned char *key)
+{
+ unsigned char buf[32];
+ unsigned int p;
+ int i, n;
+ fz_md5 md5;
+
+ n = crypt->length / 8;
+
+ /* Step 1 - copy and pad password string */
+ if (pwlen > 32)
+ pwlen = 32;
+ memcpy(buf, password, pwlen);
+ memcpy(buf + pwlen, padding, 32 - pwlen);
+
+ /* Step 2 - init md5 and pass value of step 1 */
+ fz_md5_init(&md5);
+ fz_md5_update(&md5, buf, 32);
+
+ /* Step 3 - pass O value */
+ fz_md5_update(&md5, crypt->o, 32);
+
+ /* Step 4 - pass P value as unsigned int, low-order byte first */
+ p = (unsigned int) crypt->p;
+ buf[0] = (p) & 0xFF;
+ buf[1] = (p >> 8) & 0xFF;
+ buf[2] = (p >> 16) & 0xFF;
+ buf[3] = (p >> 24) & 0xFF;
+ fz_md5_update(&md5, buf, 4);
+
+ /* Step 5 - pass first element of ID array */
+ fz_md5_update(&md5, (unsigned char *)pdf_to_str_buf(crypt->id), pdf_to_str_len(crypt->id));
+
+ /* Step 6 (revision 4 or greater) - if metadata is not encrypted pass 0xFFFFFFFF */
+ if (crypt->r >= 4)
+ {
+ if (!crypt->encrypt_metadata)
+ {
+ buf[0] = 0xFF;
+ buf[1] = 0xFF;
+ buf[2] = 0xFF;
+ buf[3] = 0xFF;
+ fz_md5_update(&md5, buf, 4);
+ }
+ }
+
+ /* Step 7 - finish the hash */
+ fz_md5_final(&md5, buf);
+
+ /* Step 8 (revision 3 or greater) - do some voodoo 50 times */
+ if (crypt->r >= 3)
+ {
+ for (i = 0; i < 50; i++)
+ {
+ fz_md5_init(&md5);
+ fz_md5_update(&md5, buf, n);
+ fz_md5_final(&md5, buf);
+ }
+ }
+
+ /* Step 9 - the key is the first 'n' bytes of the result */
+ memcpy(key, buf, n);
+}
+
+/*
+ * Compute an encryption key (PDF 1.7 ExtensionLevel 3 algorithm 3.2a)
+ */
+
+static void
+pdf_compute_encryption_key_r5(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, int ownerkey, unsigned char *validationkey)
+{
+ unsigned char buffer[128 + 8 + 48];
+ fz_sha256 sha256;
+ fz_aes aes;
+
+ /* Step 2 - truncate UTF-8 password to 127 characters */
+
+ if (pwlen > 127)
+ pwlen = 127;
+
+ /* Step 3/4 - test password against owner/user key and compute encryption key */
+
+ memcpy(buffer, password, pwlen);
+ if (ownerkey)
+ {
+ memcpy(buffer + pwlen, crypt->o + 32, 8);
+ memcpy(buffer + pwlen + 8, crypt->u, 48);
+ }
+ else
+ memcpy(buffer + pwlen, crypt->u + 32, 8);
+
+ fz_sha256_init(&sha256);
+ fz_sha256_update(&sha256, buffer, pwlen + 8 + (ownerkey ? 48 : 0));
+ fz_sha256_final(&sha256, validationkey);
+
+ /* Step 3.5/4.5 - compute file encryption key from OE/UE */
+
+ memcpy(buffer + pwlen, crypt->u + 40, 8);
+
+ fz_sha256_init(&sha256);
+ fz_sha256_update(&sha256, buffer, pwlen + 8);
+ fz_sha256_final(&sha256, buffer);
+
+ /* clear password buffer and use it as iv */
+ memset(buffer + 32, 0, sizeof(buffer) - 32);
+ if (aes_setkey_dec(&aes, buffer, crypt->length))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", crypt->length);
+ aes_crypt_cbc(&aes, AES_DECRYPT, 32, buffer + 32, ownerkey ? crypt->oe : crypt->ue, crypt->key);
+}
+
+/*
+ * Compute an encryption key (PDF 1.7 ExtensionLevel 8 algorithm)
+ *
+ * Adobe has not yet released the details, so the algorithm reference is:
+ * http://esec-lab.sogeti.com/post/The-undocumented-password-validation-algorithm-of-Adobe-Reader-X
+ */
+
+static void
+pdf_compute_hardened_hash_r6(fz_context *ctx, unsigned char *password, int pwlen, unsigned char salt[16], unsigned char *ownerkey, unsigned char hash[32])
+{
+ unsigned char data[(128 + 64 + 48) * 64];
+ unsigned char block[64];
+ int block_size = 32;
+ int data_len = 0;
+ int i, j, sum;
+
+ fz_sha256 sha256;
+ fz_sha384 sha384;
+ fz_sha512 sha512;
+ fz_aes aes;
+
+ /* Step 1: calculate initial data block */
+ fz_sha256_init(&sha256);
+ fz_sha256_update(&sha256, password, pwlen);
+ fz_sha256_update(&sha256, salt, 8);
+ if (ownerkey)
+ fz_sha256_update(&sha256, ownerkey, 48);
+ fz_sha256_final(&sha256, block);
+
+ for (i = 0; i < 64 || i < data[data_len * 64 - 1] + 32; i++)
+ {
+ /* Step 2: repeat password and data block 64 times */
+ memcpy(data, password, pwlen);
+ memcpy(data + pwlen, block, block_size);
+ memcpy(data + pwlen + block_size, ownerkey, ownerkey ? 48 : 0);
+ data_len = pwlen + block_size + (ownerkey ? 48 : 0);
+ for (j = 1; j < 64; j++)
+ memcpy(data + j * data_len, data, data_len);
+
+ /* Step 3: encrypt data using data block as key and iv */
+ if (aes_setkey_enc(&aes, block, 128))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", 128);
+ aes_crypt_cbc(&aes, AES_ENCRYPT, data_len * 64, block + 16, data, data);
+
+ /* Step 4: determine SHA-2 hash size for this round */
+ for (j = 0, sum = 0; j < 16; j++)
+ sum += data[j];
+
+ /* Step 5: calculate data block for next round */
+ block_size = 32 + (sum % 3) * 16;
+ switch (block_size)
+ {
+ case 32:
+ fz_sha256_init(&sha256);
+ fz_sha256_update(&sha256, data, data_len * 64);
+ fz_sha256_final(&sha256, block);
+ break;
+ case 48:
+ fz_sha384_init(&sha384);
+ fz_sha384_update(&sha384, data, data_len * 64);
+ fz_sha384_final(&sha384, block);
+ break;
+ case 64:
+ fz_sha512_init(&sha512);
+ fz_sha512_update(&sha512, data, data_len * 64);
+ fz_sha512_final(&sha512, block);
+ break;
+ }
+ }
+
+ memset(data, 0, sizeof(data));
+ memcpy(hash, block, 32);
+}
+
+static void
+pdf_compute_encryption_key_r6(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, int ownerkey, unsigned char *validationkey)
+{
+ unsigned char hash[32];
+ unsigned char iv[16];
+ fz_aes aes;
+
+ if (pwlen > 127)
+ pwlen = 127;
+
+ pdf_compute_hardened_hash_r6(ctx, password, pwlen,
+ (ownerkey ? crypt->o : crypt->u) + 32,
+ ownerkey ? crypt->u : NULL, validationkey);
+ pdf_compute_hardened_hash_r6(ctx, password, pwlen,
+ crypt->u + 40, NULL, hash);
+
+ memset(iv, 0, sizeof(iv));
+ if (aes_setkey_dec(&aes, hash, 256))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=256)");
+ aes_crypt_cbc(&aes, AES_DECRYPT, 32, iv,
+ ownerkey ? crypt->oe : crypt->ue, crypt->key);
+}
+
+/*
+ * Computing the user password (PDF 1.7 algorithm 3.4 and 3.5)
+ * Also save the generated key for decrypting objects and streams in crypt->key.
+ */
+
+static void
+pdf_compute_user_password(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, unsigned char *output)
+{
+ if (crypt->r == 2)
+ {
+ fz_arc4 arc4;
+
+ pdf_compute_encryption_key(crypt, password, pwlen, crypt->key);
+ fz_arc4_init(&arc4, crypt->key, crypt->length / 8);
+ fz_arc4_encrypt(&arc4, output, padding, 32);
+ }
+
+ if (crypt->r == 3 || crypt->r == 4)
+ {
+ unsigned char xor[32];
+ unsigned char digest[16];
+ fz_md5 md5;
+ fz_arc4 arc4;
+ int i, x, n;
+
+ n = crypt->length / 8;
+
+ pdf_compute_encryption_key(crypt, password, pwlen, crypt->key);
+
+ fz_md5_init(&md5);
+ fz_md5_update(&md5, padding, 32);
+ fz_md5_update(&md5, (unsigned char*)pdf_to_str_buf(crypt->id), pdf_to_str_len(crypt->id));
+ fz_md5_final(&md5, digest);
+
+ fz_arc4_init(&arc4, crypt->key, n);
+ fz_arc4_encrypt(&arc4, output, digest, 16);
+
+ for (x = 1; x <= 19; x++)
+ {
+ for (i = 0; i < n; i++)
+ xor[i] = crypt->key[i] ^ x;
+ fz_arc4_init(&arc4, xor, n);
+ fz_arc4_encrypt(&arc4, output, output, 16);
+ }
+
+ memcpy(output + 16, padding, 16);
+ }
+
+ if (crypt->r == 5)
+ {
+ pdf_compute_encryption_key_r5(ctx, crypt, password, pwlen, 0, output);
+ }
+
+ if (crypt->r == 6)
+ {
+ pdf_compute_encryption_key_r6(ctx, crypt, password, pwlen, 0, output);
+ }
+}
+
+/*
+ * Authenticating the user password (PDF 1.7 algorithm 3.6
+ * and ExtensionLevel 3 algorithm 3.11)
+ * This also has the side effect of saving a key generated
+ * from the password for decrypting objects and streams.
+ */
+
+static int
+pdf_authenticate_user_password(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen)
+{
+ unsigned char output[32];
+ pdf_compute_user_password(ctx, crypt, password, pwlen, output);
+ if (crypt->r == 2 || crypt->r == 5 || crypt->r == 6)
+ return memcmp(output, crypt->u, 32) == 0;
+ if (crypt->r == 3 || crypt->r == 4)
+ return memcmp(output, crypt->u, 16) == 0;
+ return 0;
+}
+
+/*
+ * Authenticating the owner password (PDF 1.7 algorithm 3.7
+ * and ExtensionLevel 3 algorithm 3.12)
+ * Generates the user password from the owner password
+ * and calls pdf_authenticate_user_password.
+ */
+
+static int
+pdf_authenticate_owner_password(fz_context *ctx, pdf_crypt *crypt, unsigned char *ownerpass, int pwlen)
+{
+ unsigned char pwbuf[32];
+ unsigned char key[32];
+ unsigned char xor[32];
+ unsigned char userpass[32];
+ int i, n, x;
+ fz_md5 md5;
+ fz_arc4 arc4;
+
+ if (crypt->r == 5)
+ {
+ /* PDF 1.7 ExtensionLevel 3 algorithm 3.12 */
+ pdf_compute_encryption_key_r5(ctx, crypt, ownerpass, pwlen, 1, key);
+ return !memcmp(key, crypt->o, 32);
+ }
+ else if (crypt->r == 6)
+ {
+ /* PDF 1.7 ExtensionLevel 8 algorithm */
+ pdf_compute_encryption_key_r6(ctx, crypt, ownerpass, pwlen, 1, key);
+ return !memcmp(key, crypt->o, 32);
+ }
+
+ n = crypt->length / 8;
+
+ /* Step 1 -- steps 1 to 4 of PDF 1.7 algorithm 3.3 */
+
+ /* copy and pad password string */
+ if (pwlen > 32)
+ pwlen = 32;
+ memcpy(pwbuf, ownerpass, pwlen);
+ memcpy(pwbuf + pwlen, padding, 32 - pwlen);
+
+ /* take md5 hash of padded password */
+ fz_md5_init(&md5);
+ fz_md5_update(&md5, pwbuf, 32);
+ fz_md5_final(&md5, key);
+
+ /* do some voodoo 50 times (Revision 3 or greater) */
+ if (crypt->r >= 3)
+ {
+ for (i = 0; i < 50; i++)
+ {
+ fz_md5_init(&md5);
+ fz_md5_update(&md5, key, 16);
+ fz_md5_final(&md5, key);
+ }
+ }
+
+ /* Step 2 (Revision 2) */
+ if (crypt->r == 2)
+ {
+ fz_arc4_init(&arc4, key, n);
+ fz_arc4_encrypt(&arc4, userpass, crypt->o, 32);
+ }
+
+ /* Step 2 (Revision 3 or greater) */
+ if (crypt->r >= 3)
+ {
+ memcpy(userpass, crypt->o, 32);
+ for (x = 0; x < 20; x++)
+ {
+ for (i = 0; i < n; i++)
+ xor[i] = key[i] ^ (19 - x);
+ fz_arc4_init(&arc4, xor, n);
+ fz_arc4_encrypt(&arc4, userpass, userpass, 32);
+ }
+ }
+
+ return pdf_authenticate_user_password(ctx, crypt, userpass, 32);
+}
+
+static void pdf_docenc_from_utf8(char *password, const char *utf8, int n)
+{
+ int i = 0, k, c;
+ while (*utf8 && i + 1 < n)
+ {
+ utf8 += fz_chartorune(&c, utf8);
+ for (k = 0; k < 256; k++)
+ {
+ if (c == pdf_doc_encoding[k])
+ {
+ password[i++] = k;
+ break;
+ }
+ }
+ /* FIXME: drop characters that can't be encoded or return an error? */
+ }
+ password[i] = 0;
+}
+
+static void pdf_saslprep_from_utf8(char *password, const char *utf8, int n)
+{
+ /* TODO: stringprep with SALSprep profile */
+ fz_strlcpy(password, utf8, n);
+}
+
+int
+pdf_authenticate_password(pdf_document *xref, const char *pwd_utf8)
+{
+ char password[2048];
+
+ if (xref->crypt)
+ {
+ password[0] = 0;
+ if (pwd_utf8)
+ {
+ if (xref->crypt->r <= 4)
+ pdf_docenc_from_utf8(password, pwd_utf8, sizeof password);
+ else
+ pdf_saslprep_from_utf8(password, pwd_utf8, sizeof password);
+ }
+
+ if (pdf_authenticate_user_password(xref->ctx, xref->crypt, (unsigned char *)password, strlen(password)))
+ return 1;
+ if (pdf_authenticate_owner_password(xref->ctx, xref->crypt, (unsigned char *)password, strlen(password)))
+ return 1;
+ return 0;
+ }
+ return 1;
+}
+
+int
+pdf_needs_password(pdf_document *xref)
+{
+ if (!xref->crypt)
+ return 0;
+ if (pdf_authenticate_password(xref, ""))
+ return 0;
+ return 1;
+}
+
+int
+pdf_has_permission(pdf_document *xref, int p)
+{
+ if (!xref->crypt)
+ return 1;
+ return xref->crypt->p & p;
+}
+
+unsigned char *
+pdf_crypt_key(pdf_document *xref)
+{
+ if (xref->crypt)
+ return xref->crypt->key;
+ return NULL;
+}
+
+int
+pdf_crypt_version(pdf_document *xref)
+{
+ if (xref->crypt)
+ return xref->crypt->v;
+ return 0;
+}
+
+int pdf_crypt_revision(pdf_document *xref)
+{
+ if (xref->crypt)
+ return xref->crypt->r;
+ return 0;
+}
+
+char *
+pdf_crypt_method(pdf_document *xref)
+{
+ if (xref->crypt)
+ {
+ switch (xref->crypt->strf.method)
+ {
+ case PDF_CRYPT_NONE: return "None";
+ case PDF_CRYPT_RC4: return "RC4";
+ case PDF_CRYPT_AESV2: return "AES";
+ case PDF_CRYPT_AESV3: return "AES";
+ case PDF_CRYPT_UNKNOWN: return "Unknown";
+ }
+ }
+ return "None";
+}
+
+int
+pdf_crypt_length(pdf_document *xref)
+{
+ if (xref->crypt)
+ return xref->crypt->length;
+ return 0;
+}
+
+/*
+ * PDF 1.7 algorithm 3.1 and ExtensionLevel 3 algorithm 3.1a
+ *
+ * Using the global encryption key that was generated from the
+ * password, create a new key that is used to decrypt individual
+ * objects and streams. This key is based on the object and
+ * generation numbers.
+ */
+
+static int
+pdf_compute_object_key(pdf_crypt *crypt, pdf_crypt_filter *cf, int num, int gen, unsigned char *key, int max_len)
+{
+ fz_md5 md5;
+ unsigned char message[5];
+ int key_len = crypt->length / 8;
+
+ if (key_len > max_len)
+ key_len = max_len;
+
+ if (cf->method == PDF_CRYPT_AESV3)
+ {
+ memcpy(key, crypt->key, key_len);
+ return key_len;
+ }
+
+ fz_md5_init(&md5);
+ fz_md5_update(&md5, crypt->key, key_len);
+ message[0] = (num) & 0xFF;
+ message[1] = (num >> 8) & 0xFF;
+ message[2] = (num >> 16) & 0xFF;
+ message[3] = (gen) & 0xFF;
+ message[4] = (gen >> 8) & 0xFF;
+ fz_md5_update(&md5, message, 5);
+
+ if (cf->method == PDF_CRYPT_AESV2)
+ fz_md5_update(&md5, (unsigned char *)"sAlT", 4);
+
+ fz_md5_final(&md5, key);
+
+ if (key_len + 5 > 16)
+ return 16;
+ return key_len + 5;
+}
+
+/*
+ * PDF 1.7 algorithm 3.1 and ExtensionLevel 3 algorithm 3.1a
+ *
+ * Decrypt all strings in obj modifying the data in-place.
+ * Recurse through arrays and dictionaries, but do not follow
+ * indirect references.
+ */
+
+static void
+pdf_crypt_obj_imp(fz_context *ctx, pdf_crypt *crypt, pdf_obj *obj, unsigned char *key, int keylen)
+{
+ unsigned char *s;
+ int i, n;
+
+ if (pdf_is_indirect(obj))
+ return;
+
+ if (pdf_is_string(obj))
+ {
+ s = (unsigned char *)pdf_to_str_buf(obj);
+ n = pdf_to_str_len(obj);
+
+ if (crypt->strf.method == PDF_CRYPT_RC4)
+ {
+ fz_arc4 arc4;
+ fz_arc4_init(&arc4, key, keylen);
+ fz_arc4_encrypt(&arc4, s, s, n);
+ }
+
+ if (crypt->strf.method == PDF_CRYPT_AESV2 || crypt->strf.method == PDF_CRYPT_AESV3)
+ {
+ if (n == 0)
+ {
+ /* Empty strings are permissible */
+ }
+ else if (n & 15 || n < 32)
+ fz_warn(ctx, "invalid string length for aes encryption");
+ else
+ {
+ unsigned char iv[16];
+ fz_aes aes;
+ memcpy(iv, s, 16);
+ if (aes_setkey_dec(&aes, key, keylen * 8))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", keylen * 8);
+ aes_crypt_cbc(&aes, AES_DECRYPT, n - 16, iv, s + 16, s);
+ /* delete space used for iv and padding bytes at end */
+ if (s[n - 17] < 1 || s[n - 17] > 16)
+ fz_warn(ctx, "aes padding out of range");
+ else
+ pdf_set_str_len(obj, n - 16 - s[n - 17]);
+ }
+ }
+ }
+
+ else if (pdf_is_array(obj))
+ {
+ n = pdf_array_len(obj);
+ for (i = 0; i < n; i++)
+ {
+ pdf_crypt_obj_imp(ctx, crypt, pdf_array_get(obj, i), key, keylen);
+ }
+ }
+
+ else if (pdf_is_dict(obj))
+ {
+ n = pdf_dict_len(obj);
+ for (i = 0; i < n; i++)
+ {
+ pdf_crypt_obj_imp(ctx, crypt, pdf_dict_get_val(obj, i), key, keylen);
+ }
+ }
+}
+
+void
+pdf_crypt_obj(fz_context *ctx, pdf_crypt *crypt, pdf_obj *obj, int num, int gen)
+{
+ unsigned char key[32];
+ int len;
+
+ len = pdf_compute_object_key(crypt, &crypt->strf, num, gen, key, 32);
+
+ pdf_crypt_obj_imp(ctx, crypt, obj, key, len);
+}
+
+/*
+ * PDF 1.7 algorithm 3.1 and ExtensionLevel 3 algorithm 3.1a
+ *
+ * Create filter suitable for de/encrypting a stream.
+ */
+static fz_stream *
+pdf_open_crypt_imp(fz_stream *chain, pdf_crypt *crypt, pdf_crypt_filter *stmf, int num, int gen)
+{
+ unsigned char key[32];
+ int len;
+
+ crypt->ctx = chain->ctx;
+ len = pdf_compute_object_key(crypt, stmf, num, gen, key, 32);
+
+ if (stmf->method == PDF_CRYPT_RC4)
+ return fz_open_arc4(chain, key, len);
+
+ if (stmf->method == PDF_CRYPT_AESV2 || stmf->method == PDF_CRYPT_AESV3)
+ return fz_open_aesd(chain, key, len);
+
+ return fz_open_copy(chain);
+}
+
+fz_stream *
+pdf_open_crypt(fz_stream *chain, pdf_crypt *crypt, int num, int gen)
+{
+ return pdf_open_crypt_imp(chain, crypt, &crypt->stmf, num, gen);
+}
+
+fz_stream *
+pdf_open_crypt_with_filter(fz_stream *chain, pdf_crypt *crypt, char *name, int num, int gen)
+{
+ if (strcmp(name, "Identity"))
+ {
+ pdf_crypt_filter cf;
+ pdf_parse_crypt_filter(chain->ctx, &cf, crypt, name);
+ return pdf_open_crypt_imp(chain, crypt, &cf, num, gen);
+ }
+ return chain;
+}
+
+#ifndef NDEBUG
+void pdf_print_crypt(pdf_crypt *crypt)
+{
+ int i;
+
+ printf("crypt {\n");
+
+ printf("\tv=%d length=%d\n", crypt->v, crypt->length);
+ printf("\tstmf method=%d length=%d\n", crypt->stmf.method, crypt->stmf.length);
+ printf("\tstrf method=%d length=%d\n", crypt->strf.method, crypt->strf.length);
+ printf("\tr=%d\n", crypt->r);
+
+ printf("\to=<");
+ for (i = 0; i < 32; i++)
+ printf("%02X", crypt->o[i]);
+ printf(">\n");
+
+ printf("\tu=<");
+ for (i = 0; i < 32; i++)
+ printf("%02X", crypt->u[i]);
+ printf(">\n");
+
+ printf("}\n");
+}
+#endif
diff --git a/source/pdf/pdf-device.c b/source/pdf/pdf-device.c
new file mode 100644
index 00000000..602a778f
--- /dev/null
+++ b/source/pdf/pdf-device.c
@@ -0,0 +1,1263 @@
+#include "mupdf/pdf.h"
+
+typedef struct pdf_device_s pdf_device;
+
+typedef struct gstate_s gstate;
+
+struct gstate_s
+{
+ /* The first few entries aren't really graphics state things, but
+ * they are recorded here as they are fundamentally intertwined with
+ * the push/pulling of the gstates. */
+ fz_buffer *buf;
+ void (*on_pop)(pdf_device*,void *);
+ void *on_pop_arg;
+ /* The graphics state proper */
+ fz_colorspace *colorspace[2];
+ float color[2][4];
+ fz_matrix ctm;
+ fz_stroke_state *stroke_state;
+ float alpha[2];
+ int font;
+ float font_size;
+ float char_spacing;
+ float word_spacing;
+ float horizontal_scaling;
+ float leading;
+ int text_rendering_mode;
+ float rise;
+ int knockout;
+ fz_matrix tm;
+};
+
+typedef struct image_entry_s image_entry;
+
+struct image_entry_s
+{
+ char digest[16];
+ pdf_obj *ref;
+};
+
+typedef struct alpha_entry_s alpha_entry;
+
+struct alpha_entry_s
+{
+ float alpha;
+ int stroke;
+};
+
+typedef struct font_entry_s font_entry;
+
+struct font_entry_s
+{
+ fz_font *font;
+};
+
+typedef struct group_entry_s group_entry;
+
+struct group_entry_s
+{
+ int blendmode;
+ int alpha;
+ int isolated;
+ int knockout;
+ fz_colorspace *colorspace;
+ pdf_obj *ref;
+};
+
+struct pdf_device_s
+{
+ fz_context *ctx;
+ pdf_document *xref;
+ pdf_obj *contents;
+ pdf_obj *resources;
+
+ int in_text;
+
+ int num_forms;
+ int num_smasks;
+
+ int num_gstates;
+ int max_gstates;
+ gstate *gstates;
+
+ int num_imgs;
+ int max_imgs;
+ image_entry *images;
+
+ int num_alphas;
+ int max_alphas;
+ alpha_entry *alphas;
+
+ int num_fonts;
+ int max_fonts;
+ font_entry *fonts;
+
+ int num_groups;
+ int max_groups;
+ group_entry *groups;
+};
+
+#define CURRENT_GSTATE(pdev) (&(pdev)->gstates[(pdev)->num_gstates-1])
+
+/* Helper functions */
+
+static int
+send_image(pdf_device *pdev, fz_image *image, int mask, int smask)
+{
+ fz_context *ctx = pdev->ctx;
+ fz_pixmap *pixmap = NULL;
+ pdf_obj *imobj = NULL;
+ pdf_obj *imref = NULL;
+ fz_compressed_buffer *cbuffer = NULL;
+ fz_compression_params *cp = NULL;
+ fz_buffer *buffer = NULL;
+ int i, num;
+ fz_md5 state;
+ unsigned char digest[16];
+ int bpc = 8;
+ fz_colorspace *colorspace = image->colorspace;
+
+ fz_var(pixmap);
+ fz_var(buffer);
+ fz_var(imobj);
+ fz_var(imref);
+
+ fz_try(ctx)
+ {
+ if (cbuffer == NULL)
+ {
+ unsigned int size;
+ int n;
+ /* Currently, set to maintain resolution; should we consider
+ * subsampling here according to desired output res? */
+ pixmap = image->get_pixmap(ctx, image, image->w, image->h);
+ colorspace = pixmap->colorspace; /* May be different to image->colorspace! */
+ n = (pixmap->n == 1 ? 1 : pixmap->n-1);
+ size = image->w * image->h * n;
+ buffer = fz_new_buffer(ctx, size);
+ buffer->len = size;
+ if (pixmap->n == 1)
+ {
+ memcpy(buffer->data, pixmap->samples, size);
+ }
+ else
+ {
+ /* Need to remove the alpha plane */
+ unsigned char *d = buffer->data;
+ unsigned char *s = pixmap->samples;
+ int mod = n;
+ while (size--)
+ {
+ *d++ = *s++;
+ mod--;
+ if (mod == 0)
+ s++, mod = n;
+ }
+ }
+ }
+ else
+ {
+ buffer = fz_keep_buffer(ctx, cbuffer->buffer);
+ cp = &cbuffer->params;
+ }
+
+ fz_md5_init(&state);
+ fz_md5_update(&state, buffer->data, buffer->len);
+ fz_md5_final(&state, digest);
+ for(i=0; i < pdev->num_imgs; i++)
+ {
+ if (!memcmp(&digest, pdev->images[i].digest, sizeof(16)))
+ {
+ num = i;
+ break;
+ }
+ }
+
+ if (i < pdev->num_imgs)
+ break;
+
+ if (pdev->num_imgs == pdev->max_imgs)
+ {
+ int newmax = pdev->max_imgs * 2;
+ if (newmax == 0)
+ newmax = 4;
+ pdev->images = fz_resize_array(ctx, pdev->images, newmax, sizeof(*pdev->images));
+ pdev->max_imgs = newmax;
+ }
+ num = pdev->num_imgs++;
+ memcpy(pdev->images[num].digest,digest,16);
+ pdev->images[num].ref = NULL; /* Will be filled in later */
+
+ imobj = pdf_new_dict(ctx, 3);
+ pdf_dict_puts_drop(imobj, "Type", pdf_new_name(ctx, "XObject"));
+ pdf_dict_puts_drop(imobj, "Subtype", pdf_new_name(ctx, "Image"));
+ pdf_dict_puts_drop(imobj, "Width", pdf_new_int(ctx, image->w));
+ pdf_dict_puts_drop(imobj, "Height", pdf_new_int(ctx, image->h));
+ if (mask)
+ {}
+ else if (!colorspace || colorspace->n == 1)
+ pdf_dict_puts_drop(imobj, "ColorSpace", pdf_new_name(ctx, "DeviceGray"));
+ else if (colorspace->n == 3)
+ pdf_dict_puts_drop(imobj, "ColorSpace", pdf_new_name(ctx, "DeviceRGB"));
+ else if (colorspace->n == 4)
+ pdf_dict_puts_drop(imobj, "ColorSpace", pdf_new_name(ctx, "DeviceCMYK"));
+ switch (cp ? cp->type : FZ_IMAGE_UNKNOWN)
+ {
+ case FZ_IMAGE_UNKNOWN:
+ default:
+ break;
+ case FZ_IMAGE_JPEG:
+ if (cp->u.jpeg.color_transform != -1)
+ pdf_dict_puts_drop(imobj, "ColorTransform", pdf_new_int(ctx, cp->u.jpeg.color_transform));
+ pdf_dict_puts_drop(imobj, "Filter", pdf_new_name(ctx, "DCTDecode"));
+ break;
+ case FZ_IMAGE_JPX:
+ if (cp->u.jpx.smask_in_data)
+ pdf_dict_puts_drop(imobj, "SMaskInData", pdf_new_int(ctx, cp->u.jpx.smask_in_data));
+ pdf_dict_puts_drop(imobj, "Filter", pdf_new_name(ctx, "JPXDecode"));
+ break;
+ case FZ_IMAGE_FAX:
+ if (cp->u.fax.columns)
+ pdf_dict_puts(imobj, "Columns", pdf_new_int(ctx, cp->u.fax.columns));
+ if (cp->u.fax.rows)
+ pdf_dict_puts(imobj, "Rows", pdf_new_int(ctx, cp->u.fax.rows));
+ if (cp->u.fax.k)
+ pdf_dict_puts(imobj, "K", pdf_new_int(ctx, cp->u.fax.k));
+ if (cp->u.fax.end_of_line)
+ pdf_dict_puts(imobj, "EndOfLine", pdf_new_int(ctx, cp->u.fax.end_of_line));
+ if (cp->u.fax.encoded_byte_align)
+ pdf_dict_puts(imobj, "EncodedByteAlign", pdf_new_int(ctx, cp->u.fax.encoded_byte_align));
+ if (cp->u.fax.end_of_block)
+ pdf_dict_puts(imobj, "EndOfBlock", pdf_new_int(ctx, cp->u.fax.end_of_block));
+ if (cp->u.fax.black_is_1)
+ pdf_dict_puts(imobj, "BlackIs1", pdf_new_int(ctx, cp->u.fax.black_is_1));
+ if (cp->u.fax.damaged_rows_before_error)
+ pdf_dict_puts(imobj, "DamagedRowsBeforeError", pdf_new_int(ctx, cp->u.fax.damaged_rows_before_error));
+ pdf_dict_puts(imobj, "Filter", pdf_new_name(ctx, "CCITTFaxDecode"));
+ break;
+ case FZ_IMAGE_JBIG2:
+ /* FIXME - jbig2globals */
+ cp->type = FZ_IMAGE_UNKNOWN;
+ /* bpc = 1; */
+ break;
+ case FZ_IMAGE_FLATE:
+ if (cp->u.flate.columns)
+ pdf_dict_puts(imobj, "Columns", pdf_new_int(ctx, cp->u.flate.columns));
+ if (cp->u.flate.colors)
+ pdf_dict_puts(imobj, "Colors", pdf_new_int(ctx, cp->u.flate.colors));
+ if (cp->u.flate.predictor)
+ pdf_dict_puts(imobj, "Predictor", pdf_new_int(ctx, cp->u.flate.predictor));
+ if (cp->u.flate.bpc)
+ bpc = cp->u.flate.bpc;
+ pdf_dict_puts(imobj, "Filter", pdf_new_name(ctx, "FlateDecode"));
+ break;
+ case FZ_IMAGE_LZW:
+ if (cp->u.lzw.columns)
+ pdf_dict_puts(imobj, "Columns", pdf_new_int(ctx, cp->u.lzw.columns));
+ if (cp->u.lzw.colors)
+ pdf_dict_puts(imobj, "Colors", pdf_new_int(ctx, cp->u.lzw.colors));
+ if (cp->u.lzw.predictor)
+ pdf_dict_puts(imobj, "Predictor", pdf_new_int(ctx, cp->u.lzw.predictor));
+ if (cp->u.lzw.bpc)
+ bpc = cp->u.lzw.bpc;
+ if (cp->u.lzw.early_change)
+ pdf_dict_puts(imobj, "EarlyChange", pdf_new_int(ctx, cp->u.lzw.early_change));
+ pdf_dict_puts(imobj, "Filter", pdf_new_name(ctx, "LZWDecode"));
+ break;
+ case FZ_IMAGE_RLD:
+ pdf_dict_puts(imobj, "Filter", pdf_new_name(ctx, "RunLengthDecode"));
+ break;
+ }
+ if (mask)
+ {
+ pdf_dict_puts_drop(imobj, "ImageMask", pdf_new_bool(ctx, 1));
+ bpc = 1;
+ }
+ if (image->mask)
+ {
+ int smasknum = send_image(pdev, image->mask, 0, 1);
+ pdf_dict_puts(imobj, "SMask", pdev->images[smasknum].ref);
+ }
+ if (bpc)
+ pdf_dict_puts_drop(imobj, "BitsPerComponent", pdf_new_int(ctx, bpc));
+
+ imref = pdf_new_ref(pdev->xref, imobj);
+ pdf_update_stream(pdev->xref, pdf_to_num(imref), buffer);
+ pdf_dict_puts_drop(imobj, "Length", pdf_new_int(ctx, buffer->len));
+
+ {
+ char text[32];
+ snprintf(text, sizeof(text), "XObject/Img%d", num);
+ pdf_dict_putp(pdev->resources, text, imref);
+ }
+ pdev->images[num].ref = imref;
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, buffer);
+ pdf_drop_obj(imobj);
+ fz_drop_pixmap(ctx, pixmap);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(imref);
+ fz_rethrow(ctx);
+ }
+ return num;
+}
+
+static void
+pdf_dev_stroke_state(pdf_device *pdev, fz_stroke_state *stroke_state)
+{
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ if (stroke_state == gs->stroke_state)
+ return;
+ if (gs->stroke_state && !memcmp(stroke_state, gs->stroke_state, sizeof(*stroke_state)))
+ return;
+ if (!gs->stroke_state || gs->stroke_state->linewidth != stroke_state->linewidth)
+ {
+ fz_buffer_printf(ctx, gs->buf, "%f w\n", stroke_state->linewidth);
+ }
+ if (!gs->stroke_state || gs->stroke_state->start_cap != stroke_state->start_cap)
+ {
+ int cap = stroke_state->start_cap;
+ /* FIXME: Triangle caps aren't supported in pdf */
+ if (cap == FZ_LINECAP_TRIANGLE)
+ cap = FZ_LINECAP_BUTT;
+ fz_buffer_printf(ctx, gs->buf, "%d J\n", cap);
+ }
+ if (!gs->stroke_state || gs->stroke_state->linejoin != stroke_state->linejoin)
+ {
+ int join = stroke_state->linejoin;
+ if (join == FZ_LINEJOIN_MITER_XPS)
+ join = FZ_LINEJOIN_MITER;
+ fz_buffer_printf(ctx, gs->buf, "%d j\n", join);
+ }
+ if (!gs->stroke_state || gs->stroke_state->miterlimit != stroke_state->miterlimit)
+ {
+ fz_buffer_printf(ctx, gs->buf, "%f M\n", stroke_state->miterlimit);
+ }
+ if (gs->stroke_state == NULL && stroke_state->dash_len == 0)
+ {}
+ else if (!gs->stroke_state || gs->stroke_state->dash_phase != stroke_state->dash_phase || gs->stroke_state->dash_len != stroke_state->dash_len ||
+ memcmp(gs->stroke_state->dash_list, stroke_state->dash_list, sizeof(float)*stroke_state->dash_len))
+ {
+ int i;
+ if (stroke_state->dash_len == 0)
+ fz_buffer_printf(ctx, gs->buf, "[");
+ for (i = 0; i < stroke_state->dash_len; i++)
+ fz_buffer_printf(ctx, gs->buf, "%c%f", (i == 0 ? '[' : ' '), stroke_state->dash_list[i]);
+ fz_buffer_printf(ctx, gs->buf, "]%f d\n", stroke_state->dash_phase);
+
+ }
+ fz_drop_stroke_state(ctx, gs->stroke_state);
+ gs->stroke_state = fz_keep_stroke_state(ctx, stroke_state);
+}
+
+static void
+pdf_dev_path(pdf_device *pdev, fz_path *path)
+{
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ float x, y;
+ int i = 0;
+ while (i < path->len)
+ {
+ switch (path->items[i++].k)
+ {
+ case FZ_MOVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_buffer_printf(ctx, gs->buf, "%g %g m\n", x, y);
+ break;
+ case FZ_LINETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_buffer_printf(ctx, gs->buf, "%g %g l\n", x, y);
+ break;
+ case FZ_CURVETO:
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_buffer_printf(ctx, gs->buf, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_buffer_printf(ctx, gs->buf, "%g %g ", x, y);
+ x = path->items[i++].v;
+ y = path->items[i++].v;
+ fz_buffer_printf(ctx, gs->buf, "%g %g c\n", x, y);
+ break;
+ case FZ_CLOSE_PATH:
+ fz_buffer_printf(ctx, gs->buf, "h\n");
+ break;
+ }
+ }
+}
+
+static void
+pdf_dev_ctm(pdf_device *pdev, const fz_matrix *ctm)
+{
+ fz_matrix inverse;
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ if (memcmp(&gs->ctm, ctm, sizeof(*ctm)) == 0)
+ return;
+ fz_invert_matrix(&inverse, &gs->ctm);
+ fz_concat(&inverse, ctm, &inverse);
+ memcpy(&gs->ctm, ctm, sizeof(*ctm));
+ fz_buffer_printf(pdev->ctx, gs->buf, "%f %f %f %f %f %f cm\n", inverse.a, inverse.b, inverse.c, inverse.d, inverse.e, inverse.f);
+}
+
+static void
+pdf_dev_color(pdf_device *pdev, fz_colorspace *colorspace, float *color, int stroke)
+{
+ int diff = 0;
+ int i;
+ int cspace = 0;
+ fz_context *ctx = pdev->ctx;
+ float rgb[FZ_MAX_COLORS];
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ if (colorspace == fz_device_gray(ctx))
+ cspace = 1;
+ else if (colorspace == fz_device_rgb(ctx))
+ cspace = 3;
+ else if (colorspace == fz_device_cmyk(ctx))
+ cspace = 4;
+
+ if (cspace == 0)
+ {
+ /* If it's an unknown colorspace, fallback to rgb */
+ colorspace->to_rgb(ctx, colorspace, color, rgb);
+ color = rgb;
+ colorspace = fz_device_rgb(ctx);
+ }
+
+ if (gs->colorspace[stroke] != colorspace)
+ {
+ gs->colorspace[stroke] = colorspace;
+ diff = 1;
+ }
+
+ for (i=0; i < colorspace->n; i++)
+ if (gs->color[stroke][i] != color[i])
+ {
+ gs->color[stroke][i] = color[i];
+ diff = 1;
+ }
+
+ if (diff == 0)
+ return;
+
+ switch (cspace + stroke*8)
+ {
+ case 1:
+ fz_buffer_printf(ctx, gs->buf, "%f g\n", color[0]);
+ break;
+ case 3:
+ fz_buffer_printf(ctx, gs->buf, "%f %f %f rg\n", color[0], color[1], color[2]);
+ break;
+ case 4:
+ fz_buffer_printf(ctx, gs->buf, "%f %f %f %f k\n", color[0], color[1], color[2], color[3]);
+ break;
+ case 1+8:
+ fz_buffer_printf(ctx, gs->buf, "%f G\n", color[0]);
+ break;
+ case 3+8:
+ fz_buffer_printf(ctx, gs->buf, "%f %f %f RG\n", color[0], color[1], color[2]);
+ break;
+ case 4+8:
+ fz_buffer_printf(ctx, gs->buf, "%f %f %f %f K\n", color[0], color[1], color[2], color[3]);
+ break;
+ }
+}
+
+static void
+pdf_dev_alpha(pdf_device *pdev, float alpha, int stroke)
+{
+ int i;
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ /* If the alpha is unchanged, nothing to do */
+ if (gs->alpha[stroke] == alpha)
+ return;
+
+ /* Have we sent such an alpha before? */
+ for (i = 0; i < pdev->num_alphas; i++)
+ if (pdev->alphas[i].alpha == alpha && pdev->alphas[i].stroke == stroke)
+ break;
+
+ if (i == pdev->num_alphas)
+ {
+ pdf_obj *o;
+ pdf_obj *ref = NULL;
+
+ fz_var(ref);
+
+ /* No. Need to make a new one */
+ if (pdev->num_alphas == pdev->max_alphas)
+ {
+ int newmax = pdev->max_alphas * 2;
+ if (newmax == 0)
+ newmax = 4;
+ pdev->alphas = fz_resize_array(ctx, pdev->alphas, newmax, sizeof(*pdev->alphas));
+ pdev->max_alphas = newmax;
+ }
+ pdev->alphas[i].alpha = alpha;
+ pdev->alphas[i].stroke = stroke;
+
+ o = pdf_new_dict(ctx, 1);
+ fz_try(ctx)
+ {
+ char text[32];
+ pdf_dict_puts_drop(o, (stroke ? "CA" : "ca"), pdf_new_real(ctx, alpha));
+ ref = pdf_new_ref(pdev->xref, o);
+ snprintf(text, sizeof(text), "ExtGState/Alp%d", i);
+ pdf_dict_putp(pdev->resources, text, ref);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(o);
+ pdf_drop_obj(ref);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ pdev->num_alphas++;
+ }
+ fz_buffer_printf(ctx, gs->buf, "/Alp%d gs\n", i);
+}
+
+static void
+pdf_dev_font(pdf_device *pdev, fz_font *font, float size)
+{
+ int i;
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ /* If the font is unchanged, nothing to do */
+ if (gs->font >= 0 && pdev->fonts[gs->font].font == font)
+ return;
+
+ /* Have we sent such a font before? */
+ for (i = 0; i < pdev->num_fonts; i++)
+ if (pdev->fonts[i].font == font)
+ break;
+
+ if (i == pdev->num_fonts)
+ {
+ pdf_obj *o;
+ pdf_obj *ref = NULL;
+
+ fz_var(ref);
+
+ /* No. Need to make a new one */
+ if (pdev->num_fonts == pdev->max_fonts)
+ {
+ int newmax = pdev->max_fonts * 2;
+ if (newmax == 0)
+ newmax = 4;
+ pdev->fonts = fz_resize_array(ctx, pdev->fonts, newmax, sizeof(*pdev->fonts));
+ pdev->max_fonts = newmax;
+ }
+ pdev->fonts[i].font = fz_keep_font(ctx, font);
+
+ o = pdf_new_dict(ctx, 3);
+ fz_try(ctx)
+ {
+ /* BIG FIXME: Get someone who understands fonts to fill this bit in. */
+ char text[32];
+ pdf_dict_puts_drop(o, "Type", pdf_new_name(ctx, "Font"));
+ pdf_dict_puts_drop(o, "Subtype", pdf_new_name(ctx, "Type1"));
+ pdf_dict_puts_drop(o, "BaseFont", pdf_new_name(ctx, "Helvetica"));
+ ref = pdf_new_ref(pdev->xref, o);
+ snprintf(text, sizeof(text), "Font/F%d", i);
+ pdf_dict_putp(pdev->resources, text, ref);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(o);
+ pdf_drop_obj(ref);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ pdev->num_fonts++;
+ }
+ fz_buffer_printf(ctx, gs->buf, "/F%d %g Tf\n", i, size);
+}
+
+static void
+pdf_dev_tm(pdf_device *pdev, const fz_matrix *tm)
+{
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ if (memcmp(&gs->tm, tm, sizeof(*tm)) == 0)
+ return;
+ fz_buffer_printf(pdev->ctx, gs->buf, "%f %f %f %f %f %f Tm\n", tm->a, tm->b, tm->c, tm->d, tm->e, tm->f);
+ gs->tm = *tm;
+}
+
+static void
+pdf_dev_push_new_buf(pdf_device *pdev, fz_buffer *buf, void (*on_pop)(pdf_device*,void *), void *on_pop_arg)
+{
+ fz_context *ctx = pdev->ctx;
+
+ if (pdev->num_gstates == pdev->max_gstates)
+ {
+ int newmax = pdev->max_gstates*2;
+
+ pdev->gstates = fz_resize_array(ctx, pdev->gstates, newmax, sizeof(*pdev->gstates));
+ pdev->max_gstates = newmax;
+ }
+ memcpy(&pdev->gstates[pdev->num_gstates], &pdev->gstates[pdev->num_gstates-1], sizeof(*pdev->gstates));
+ fz_keep_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state);
+ if (buf)
+ pdev->gstates[pdev->num_gstates].buf = buf;
+ else
+ fz_keep_buffer(ctx, pdev->gstates[pdev->num_gstates].buf);
+ pdev->gstates[pdev->num_gstates].on_pop = on_pop;
+ pdev->gstates[pdev->num_gstates].on_pop_arg = on_pop_arg;
+ fz_buffer_printf(ctx, pdev->gstates[pdev->num_gstates].buf, "q\n");
+ pdev->num_gstates++;
+}
+
+static void
+pdf_dev_push(pdf_device *pdev)
+{
+ pdf_dev_push_new_buf(pdev, NULL, NULL, NULL);
+}
+
+static void *
+pdf_dev_pop(pdf_device *pdev)
+{
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ void *arg = gs->on_pop_arg;
+
+ fz_buffer_printf(pdev->ctx, gs->buf, "Q\n");
+ if (gs->on_pop)
+ gs->on_pop(pdev, arg);
+ pdev->num_gstates--;
+ fz_drop_stroke_state(ctx, pdev->gstates[pdev->num_gstates].stroke_state);
+ fz_drop_buffer(ctx, pdev->gstates[pdev->num_gstates].buf);
+ return arg;
+}
+
+static void
+pdf_dev_text(pdf_device *pdev, fz_text *text)
+{
+ int i;
+ fz_matrix trm;
+ fz_matrix inverse;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ fz_matrix trunc_trm;
+
+ /* BIG FIXME: Get someone who understands fonts to fill this bit in. */
+ trm = gs->tm;
+ trunc_trm.a = trm.a;
+ trunc_trm.b = trm.b;
+ trunc_trm.c = trm.c;
+ trunc_trm.d = trm.d;
+ trunc_trm.e = 0;
+ trunc_trm.f = 0;
+ fz_invert_matrix(&inverse, &trunc_trm);
+
+ for (i=0; i < text->len; i++)
+ {
+ fz_text_item *it = &text->items[i];
+ fz_point delta;
+ delta.x = it->x - trm.e;
+ delta.y = it->y - trm.f;
+ fz_transform_point(&delta, &inverse);
+ if (delta.x != 0 || delta.y != 0)
+ {
+ fz_buffer_printf(pdev->ctx, gs->buf, "%g %g Td ", delta.x, delta.y);
+ trm.e = it->x;
+ trm.f = it->y;
+ }
+ fz_buffer_printf(pdev->ctx, gs->buf, "<%02x> Tj\n", it->ucs);
+ /* FIXME: Advance the text position - doesn't matter at the
+ * moment as we absolutely position each glyph, but we should
+ * use more efficient text outputting where possible. */
+ }
+ gs->tm.e = trm.e;
+ gs->tm.f = trm.f;
+}
+
+static void
+pdf_dev_trm(pdf_device *pdev, int trm)
+{
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ if (gs->text_rendering_mode == trm)
+ return;
+ gs->text_rendering_mode = trm;
+ fz_buffer_printf(pdev->ctx, gs->buf, "%d Tr\n", trm);
+}
+
+static void
+pdf_dev_begin_text(pdf_device *pdev, const fz_matrix *tm, int trm)
+{
+ pdf_dev_trm(pdev, trm);
+ if (!pdev->in_text)
+ {
+ gstate *gs = CURRENT_GSTATE(pdev);
+ fz_buffer_printf(pdev->ctx, gs->buf, "BT\n");
+ gs->tm.a = 1;
+ gs->tm.b = 0;
+ gs->tm.c = 0;
+ gs->tm.d = 1;
+ gs->tm.e = 0;
+ gs->tm.f = 0;
+ pdev->in_text = 1;
+ }
+ pdf_dev_tm(pdev, tm);
+}
+
+static void
+pdf_dev_end_text(pdf_device *pdev)
+{
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ if (!pdev->in_text)
+ return;
+ pdev->in_text = 0;
+ fz_buffer_printf(pdev->ctx, gs->buf, "ET\n");
+}
+
+static int
+pdf_dev_new_form(pdf_obj **form_ref, pdf_device *pdev, const fz_rect *bbox, int isolated, int knockout, int blendmode, float alpha, fz_colorspace *colorspace)
+{
+ fz_context *ctx = pdev->ctx;
+ int num;
+ pdf_obj *group_ref;
+ pdf_obj *group;
+ pdf_obj *form;
+
+ *form_ref = NULL;
+
+ /* Find (or make) a new group with the required options. */
+ for(num = 0; num < pdev->num_groups; num++)
+ {
+ group_entry *g = &pdev->groups[num];
+ if (g->isolated == isolated && g->knockout == knockout && g->blendmode == blendmode && g->alpha == alpha && g->colorspace == colorspace)
+ {
+ group_ref = pdev->groups[num].ref;
+ break;
+ }
+ }
+
+ /* If we didn't find one, make one */
+ if (num == pdev->num_groups)
+ {
+ if (pdev->num_groups == pdev->max_groups)
+ {
+ int newmax = pdev->max_groups * 2;
+ if (newmax == 0)
+ newmax = 4;
+ pdev->groups = fz_resize_array(ctx, pdev->groups, newmax, sizeof(*pdev->groups));
+ pdev->max_groups = newmax;
+ }
+ pdev->num_groups++;
+ pdev->groups[num].isolated = isolated;
+ pdev->groups[num].knockout = knockout;
+ pdev->groups[num].blendmode = blendmode;
+ pdev->groups[num].alpha = alpha;
+ pdev->groups[num].colorspace = fz_keep_colorspace(ctx, colorspace);
+ pdev->groups[num].ref = NULL;
+ group = pdf_new_dict(ctx, 5);
+ fz_try(ctx)
+ {
+ pdf_dict_puts_drop(group, "Type", pdf_new_name(ctx, "Group"));
+ pdf_dict_puts_drop(group, "S", pdf_new_name(ctx, "Transparency"));
+ pdf_dict_puts_drop(group, "K", pdf_new_bool(ctx, knockout));
+ pdf_dict_puts_drop(group, "I", pdf_new_bool(ctx, isolated));
+ pdf_dict_puts_drop(group, "K", pdf_new_bool(ctx, knockout));
+ pdf_dict_puts_drop(group, "BM", pdf_new_name(ctx, fz_blendmode_name(blendmode)));
+ if (!colorspace)
+ {}
+ else if (colorspace->n == 1)
+ pdf_dict_puts_drop(group, "CS", pdf_new_name(ctx, "DeviceGray"));
+ else if (colorspace->n == 4)
+ pdf_dict_puts_drop(group, "CS", pdf_new_name(ctx, "DeviceCMYK"));
+ else
+ pdf_dict_puts_drop(group, "CS", pdf_new_name(ctx, "DeviceRGB"));
+ group_ref = pdev->groups[num].ref = pdf_new_ref(pdev->xref, group);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(group);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+
+ /* Make us a new Forms object that points to that group, and change
+ * to writing into the buffer for that Forms object. */
+ form = pdf_new_dict(ctx, 4);
+ fz_try(ctx)
+ {
+ pdf_dict_puts_drop(form, "Subtype", pdf_new_name(ctx, "Form"));
+ pdf_dict_puts(form, "Group", group_ref);
+ pdf_dict_puts_drop(form, "FormType", pdf_new_int(ctx, 1));
+ pdf_dict_puts_drop(form, "BBox", pdf_new_rect(ctx, bbox));
+ *form_ref = pdf_new_ref(pdev->xref, form);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(form);
+ fz_rethrow(ctx);
+ }
+
+ /* Insert the new form object into the resources */
+ {
+ char text[32];
+ num = pdev->num_forms++;
+ snprintf(text, sizeof(text), "XObject/Fm%d", num);
+ pdf_dict_putp(pdev->resources, text, *form_ref);
+ }
+
+ return num;
+}
+
+/* Entry points */
+
+static void
+pdf_dev_fill_path(fz_device *dev, fz_path *path, int even_odd, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ pdf_device *pdev = dev->user;
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ pdf_dev_end_text(pdev);
+ pdf_dev_alpha(pdev, alpha, 0);
+ pdf_dev_color(pdev, colorspace, color, 0);
+ pdf_dev_ctm(pdev, ctm);
+ pdf_dev_path(pdev, path);
+ fz_buffer_printf(dev->ctx, gs->buf, (even_odd ? "f*\n" : "f\n"));
+}
+
+static void
+pdf_dev_stroke_path(fz_device *dev, fz_path *path, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ pdf_device *pdev = dev->user;
+ gstate *gs = CURRENT_GSTATE(pdev);
+
+ pdf_dev_end_text(pdev);
+ pdf_dev_alpha(pdev, alpha, 1);
+ pdf_dev_color(pdev, colorspace, color, 1);
+ pdf_dev_ctm(pdev, ctm);
+ pdf_dev_stroke_state(pdev, stroke);
+ pdf_dev_path(pdev, path);
+ fz_buffer_printf(dev->ctx, gs->buf, "S\n");
+}
+
+static void
+pdf_dev_clip_path(fz_device *dev, fz_path *path, const fz_rect *rect, int even_odd, const fz_matrix *ctm)
+{
+ pdf_device *pdev = dev->user;
+ gstate *gs;
+
+ pdf_dev_end_text(pdev);
+ pdf_dev_push(pdev);
+ pdf_dev_ctm(pdev, ctm);
+ pdf_dev_path(pdev, path);
+ gs = CURRENT_GSTATE(pdev);
+ fz_buffer_printf(dev->ctx, gs->buf, (even_odd ? "W* n\n" : "W n\n"));
+}
+
+static void
+pdf_dev_clip_stroke_path(fz_device *dev, fz_path *path, const fz_rect *rect, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ pdf_device *pdev = dev->user;
+ gstate *gs;
+
+ pdf_dev_end_text(pdev);
+ pdf_dev_push(pdev);
+ /* FIXME: Need to push a group, select a pattern (or shading) here,
+ * stroke with the pattern/shading. Then move to defining that pattern
+ * with the next calls to the device interface until the next pop
+ * when we pop the group. */
+ pdf_dev_ctm(pdev, ctm);
+ pdf_dev_path(pdev, path);
+ gs = CURRENT_GSTATE(pdev);
+ fz_buffer_printf(dev->ctx, gs->buf, "W n\n");
+}
+
+static void
+pdf_dev_fill_text(fz_device *dev, fz_text *text, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ pdf_device *pdev = dev->user;
+
+ pdf_dev_begin_text(pdev, &text->trm, 0);
+ pdf_dev_font(pdev, text->font, 1);
+ pdf_dev_text(pdev, text);
+}
+
+static void
+pdf_dev_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm,
+ fz_colorspace *colorspace, float *color, float alpha)
+{
+ pdf_device *pdev = dev->user;
+
+ pdf_dev_begin_text(pdev, &text->trm, 1);
+ pdf_dev_font(pdev, text->font, 1);
+ pdf_dev_text(pdev, text);
+}
+
+static void
+pdf_dev_clip_text(fz_device *dev, fz_text *text, const fz_matrix *ctm, int accumulate)
+{
+ pdf_device *pdev = dev->user;
+
+ pdf_dev_begin_text(pdev, &text->trm, 0);
+ pdf_dev_font(pdev, text->font, 7);
+ pdf_dev_text(pdev, text);
+}
+
+static void
+pdf_dev_clip_stroke_text(fz_device *dev, fz_text *text, fz_stroke_state *stroke, const fz_matrix *ctm)
+{
+ pdf_device *pdev = dev->user;
+
+ pdf_dev_begin_text(pdev, &text->trm, 0);
+ pdf_dev_font(pdev, text->font, 5);
+ pdf_dev_text(pdev, text);
+}
+
+static void
+pdf_dev_ignore_text(fz_device *dev, fz_text *text, const fz_matrix *ctm)
+{
+ pdf_device *pdev = dev->user;
+
+ pdf_dev_begin_text(pdev, &text->trm, 0);
+ pdf_dev_font(pdev, text->font, 3);
+ pdf_dev_text(pdev, text);
+}
+
+static void
+pdf_dev_fill_image(fz_device *dev, fz_image *image, const fz_matrix *ctm, float alpha)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+ int num;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ fz_matrix local_ctm = *ctm;
+
+ pdf_dev_end_text(pdev);
+ num = send_image(pdev, image, 0, 0);
+ fz_buffer_printf(dev->ctx, gs->buf, "q\n");
+ pdf_dev_alpha(pdev, alpha, 0);
+ /* PDF images are upside down, so fiddle the ctm */
+ fz_pre_scale(&local_ctm, 1, -1);
+ fz_pre_translate(&local_ctm, 0, -1);
+ pdf_dev_ctm(pdev, &local_ctm);
+ fz_buffer_printf(dev->ctx, gs->buf, "/Img%d Do Q\n", num);
+}
+
+static void
+pdf_dev_fill_shade(fz_device *dev, fz_shade *shade, const fz_matrix *ctm, float alpha)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+
+ /* FIXME */
+ pdf_dev_end_text(pdev);
+}
+
+static void
+pdf_dev_fill_image_mask(fz_device *dev, fz_image *image, const fz_matrix *ctm,
+fz_colorspace *colorspace, float *color, float alpha)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ int num;
+ fz_matrix local_ctm = *ctm;
+
+ pdf_dev_end_text(pdev);
+ num = send_image(pdev, image, 1, 0);
+ fz_buffer_printf(dev->ctx, gs->buf, "q\n");
+ pdf_dev_alpha(pdev, alpha, 0);
+ pdf_dev_color(pdev, colorspace, color, 0);
+ /* PDF images are upside down, so fiddle the ctm */
+ fz_pre_scale(&local_ctm, 1, -1);
+ fz_pre_translate(&local_ctm, 0, -1);
+ pdf_dev_ctm(pdev, &local_ctm);
+ fz_buffer_printf(dev->ctx, gs->buf, "/Img%d Do Q\n", num);
+}
+
+static void
+pdf_dev_clip_image_mask(fz_device *dev, fz_image *image, const fz_rect *rect, const fz_matrix *ctm)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+
+ /* FIXME */
+ pdf_dev_end_text(pdev);
+ pdf_dev_push(pdev);
+}
+
+static void
+pdf_dev_pop_clip(fz_device *dev)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+
+ /* FIXME */
+ pdf_dev_end_text(pdev);
+ pdf_dev_pop(pdev);
+}
+
+static void
+pdf_dev_begin_mask(fz_device *dev, const fz_rect *bbox, int luminosity, fz_colorspace *colorspace, float *color)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+ fz_context *ctx = pdev->ctx;
+ gstate *gs;
+ pdf_obj *smask = NULL;
+ pdf_obj *egs = NULL;
+ pdf_obj *egs_ref;
+ pdf_obj *form_ref;
+ pdf_obj *color_obj = NULL;
+ int i;
+
+ fz_var(smask);
+ fz_var(egs);
+ fz_var(color_obj);
+
+ pdf_dev_end_text(pdev);
+
+ /* Make a new form to contain the contents of the softmask */
+ pdf_dev_new_form(&form_ref, pdev, bbox, 0, 0, 0, 1, colorspace);
+
+ fz_try(ctx)
+ {
+ smask = pdf_new_dict(ctx, 4);
+ pdf_dict_puts(smask, "Type", pdf_new_name(ctx, "Mask"));
+ pdf_dict_puts_drop(smask, "S", pdf_new_name(ctx, (luminosity ? "Luminosity" : "Alpha")));
+ pdf_dict_puts(smask, "G", form_ref);
+ color_obj = pdf_new_array(ctx, colorspace->n);
+ for (i = 0; i < colorspace->n; i++)
+ pdf_array_push(color_obj, pdf_new_real(ctx, color[i]));
+ pdf_dict_puts_drop(smask, "BC", color_obj);
+ color_obj = NULL;
+
+ egs = pdf_new_dict(ctx, 5);
+ pdf_dict_puts_drop(egs, "Type", pdf_new_name(ctx, "ExtGState"));
+ pdf_dict_puts_drop(egs, "SMask", pdf_new_ref(pdev->xref, smask));
+ egs_ref = pdf_new_ref(pdev->xref, egs);
+
+ {
+ char text[32];
+ snprintf(text, sizeof(text), "ExtGState/SM%d", pdev->num_smasks++);
+ pdf_dict_putp(pdev->resources, text, egs_ref);
+ pdf_drop_obj(egs_ref);
+ }
+ gs = CURRENT_GSTATE(pdev);
+ fz_buffer_printf(dev->ctx, gs->buf, "/SM%d gs\n", pdev->num_smasks-1);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(smask);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(form_ref);
+ pdf_drop_obj(color_obj);
+ fz_rethrow(ctx);
+ }
+
+ /* Now, everything we get until the end_mask needs to go into a
+ * new buffer, which will be the stream contents for the form. */
+ pdf_dev_push_new_buf(pdev, fz_new_buffer(ctx, 1024), NULL, form_ref);
+}
+
+static void
+pdf_dev_end_mask(fz_device *dev)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ fz_buffer *buf = fz_keep_buffer(ctx, gs->buf);
+ pdf_obj *form_ref = (pdf_obj *)gs->on_pop_arg;
+
+ /* Here we do part of the pop, but not all of it. */
+ pdf_dev_end_text(pdev);
+ fz_buffer_printf(pdev->ctx, buf, "Q\n");
+ pdf_dict_puts_drop(form_ref, "Length", pdf_new_int(ctx, buf->len));
+ pdf_update_stream(pdev->xref, pdf_to_num(form_ref), buf);
+ fz_drop_buffer(ctx, buf);
+ gs->buf = fz_keep_buffer(ctx, gs[-1].buf);
+ gs->on_pop_arg = NULL;
+ pdf_drop_obj(form_ref);
+ fz_buffer_printf(pdev->ctx, gs->buf, "q\n");
+}
+
+static void
+pdf_dev_begin_group(fz_device *dev, const fz_rect *bbox, int isolated, int knockout, int blendmode, float alpha)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+ fz_context *ctx = pdev->ctx;
+ int num;
+ pdf_obj *form_ref;
+ gstate *gs;
+
+ pdf_dev_end_text(pdev);
+
+ num = pdf_dev_new_form(&form_ref, pdev, bbox, isolated, knockout, blendmode, alpha, NULL);
+
+ /* Add the call to this group */
+ gs = CURRENT_GSTATE(pdev);
+ fz_buffer_printf(dev->ctx, gs->buf, "/Fm%d Do\n", num);
+
+ /* Now, everything we get until the end of group needs to go into a
+ * new buffer, which will be the stream contents for the form. */
+ pdf_dev_push_new_buf(pdev, fz_new_buffer(ctx, 1024), NULL, form_ref);
+}
+
+static void
+pdf_dev_end_group(fz_device *dev)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ fz_context *ctx = pdev->ctx;
+ fz_buffer *buf = fz_keep_buffer(ctx, gs->buf);
+ pdf_obj *form_ref;
+
+ pdf_dev_end_text(pdev);
+ form_ref = (pdf_obj *)pdf_dev_pop(pdev);
+ pdf_dict_puts_drop(form_ref, "Length", pdf_new_int(ctx, gs->buf->len));
+ pdf_update_stream(pdev->xref, pdf_to_num(form_ref), buf);
+ fz_drop_buffer(ctx, buf);
+ pdf_drop_obj(form_ref);
+}
+
+static int
+pdf_dev_begin_tile(fz_device *dev, const fz_rect *area, const fz_rect *view, float xstep, float ystep, const fz_matrix *ctm, int id)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+
+ /* FIXME */
+ pdf_dev_end_text(pdev);
+ return 0;
+}
+
+static void
+pdf_dev_end_tile(fz_device *dev)
+{
+ pdf_device *pdev = (pdf_device *)dev->user;
+
+ /* FIXME */
+ pdf_dev_end_text(pdev);
+}
+
+static void
+pdf_dev_free_user(fz_device *dev)
+{
+ pdf_device *pdev = dev->user;
+ fz_context *ctx = pdev->ctx;
+ gstate *gs = CURRENT_GSTATE(pdev);
+ int i;
+
+ pdf_dev_end_text(pdev);
+
+ pdf_dict_puts_drop(pdev->contents, "Length", pdf_new_int(ctx, gs->buf->len));
+
+ pdf_update_stream(pdev->xref, pdf_to_num(pdev->contents), gs->buf);
+
+ for (i = pdev->num_gstates-1; i >= 0; i--)
+ {
+ fz_drop_stroke_state(ctx, pdev->gstates[i].stroke_state);
+ }
+
+ for (i = pdev->num_fonts-1; i >= 0; i--)
+ {
+ fz_drop_font(ctx, pdev->fonts[i].font);
+ }
+
+ for (i = pdev->num_imgs-1; i >= 0; i--)
+ {
+ pdf_drop_obj(pdev->images[i].ref);
+ }
+
+ pdf_drop_obj(pdev->contents);
+ pdf_drop_obj(pdev->resources);
+
+ fz_free(ctx, pdev->images);
+ fz_free(ctx, pdev->alphas);
+ fz_free(ctx, pdev->gstates);
+ fz_free(ctx, pdev);
+}
+
+fz_device *pdf_new_pdf_device(pdf_document *doc, pdf_obj *contents, pdf_obj *resources, const fz_matrix *ctm)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_device *pdev = fz_malloc_struct(ctx, pdf_device);
+ fz_device *dev;
+
+ fz_try(ctx)
+ {
+ pdev->ctx = ctx;
+ pdev->xref = doc;
+ pdev->contents = pdf_keep_obj(contents);
+ pdev->resources = pdf_keep_obj(resources);
+ pdev->gstates = fz_malloc_struct(ctx, gstate);
+ pdev->gstates[0].buf = fz_new_buffer(ctx, 256);
+ pdev->gstates[0].ctm = *ctm;
+ pdev->gstates[0].colorspace[0] = fz_device_gray(ctx);
+ pdev->gstates[0].colorspace[1] = fz_device_gray(ctx);
+ pdev->gstates[0].color[0][0] = 1;
+ pdev->gstates[0].color[1][0] = 1;
+ pdev->gstates[0].alpha[0] = 1.0;
+ pdev->gstates[0].alpha[1] = 1.0;
+ pdev->gstates[0].font = -1;
+ pdev->gstates[0].horizontal_scaling = 100;
+ pdev->num_gstates = 1;
+ pdev->max_gstates = 1;
+
+ dev = fz_new_device(ctx, pdev);
+ }
+ fz_catch(ctx)
+ {
+ if (pdev->gstates)
+ fz_drop_buffer(ctx, pdev->gstates[0].buf);
+ fz_free(ctx, pdev);
+ fz_rethrow(ctx);
+ }
+
+ dev->free_user = pdf_dev_free_user;
+
+ dev->fill_path = pdf_dev_fill_path;
+ dev->stroke_path = pdf_dev_stroke_path;
+ dev->clip_path = pdf_dev_clip_path;
+ dev->clip_stroke_path = pdf_dev_clip_stroke_path;
+
+ dev->fill_text = pdf_dev_fill_text;
+ dev->stroke_text = pdf_dev_stroke_text;
+ dev->clip_text = pdf_dev_clip_text;
+ dev->clip_stroke_text = pdf_dev_clip_stroke_text;
+ dev->ignore_text = pdf_dev_ignore_text;
+
+ dev->fill_shade = pdf_dev_fill_shade;
+ dev->fill_image = pdf_dev_fill_image;
+ dev->fill_image_mask = pdf_dev_fill_image_mask;
+ dev->clip_image_mask = pdf_dev_clip_image_mask;
+
+ dev->pop_clip = pdf_dev_pop_clip;
+
+ dev->begin_mask = pdf_dev_begin_mask;
+ dev->end_mask = pdf_dev_end_mask;
+ dev->begin_group = pdf_dev_begin_group;
+ dev->end_group = pdf_dev_end_group;
+
+ dev->begin_tile = pdf_dev_begin_tile;
+ dev->end_tile = pdf_dev_end_tile;
+
+ return dev;
+}
diff --git a/source/pdf/pdf-encoding.c b/source/pdf/pdf-encoding.c
new file mode 100644
index 00000000..c634a9ee
--- /dev/null
+++ b/source/pdf/pdf-encoding.c
@@ -0,0 +1,82 @@
+#include "mupdf/pdf.h"
+
+#include "pdf-encodings.h"
+#include "pdf-glyphlist.h"
+
+void
+pdf_load_encoding(char **estrings, char *encoding)
+{
+ char **bstrings = NULL;
+ int i;
+
+ if (!strcmp(encoding, "StandardEncoding"))
+ bstrings = (char**) pdf_standard;
+ if (!strcmp(encoding, "MacRomanEncoding"))
+ bstrings = (char**) pdf_mac_roman;
+ if (!strcmp(encoding, "MacExpertEncoding"))
+ bstrings = (char**) pdf_mac_expert;
+ if (!strcmp(encoding, "WinAnsiEncoding"))
+ bstrings = (char**) pdf_win_ansi;
+
+ if (bstrings)
+ for (i = 0; i < 256; i++)
+ estrings[i] = bstrings[i];
+}
+
+int
+pdf_lookup_agl(char *name)
+{
+ char buf[64];
+ char *p;
+ int l = 0;
+ int r = nelem(agl_name_list) - 1;
+
+ fz_strlcpy(buf, name, sizeof buf);
+
+ /* kill anything after first period and underscore */
+ p = strchr(buf, '.');
+ if (p) p[0] = 0;
+ p = strchr(buf, '_');
+ if (p) p[0] = 0;
+
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = strcmp(buf, agl_name_list[m]);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return agl_code_list[m];
+ }
+
+ if (strstr(buf, "uni") == buf)
+ return strtol(buf + 3, NULL, 16);
+ else if (strstr(buf, "u") == buf)
+ return strtol(buf + 1, NULL, 16);
+ else if (strstr(buf, "a") == buf && strlen(buf) >= 3)
+ return strtol(buf + 1, NULL, 10);
+
+ return 0;
+}
+
+static const char *empty_dup_list[] = { 0 };
+
+const char **
+pdf_lookup_agl_duplicates(int ucs)
+{
+ int l = 0;
+ int r = nelem(agl_dup_offsets) / 2 - 1;
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ if (ucs < agl_dup_offsets[m << 1])
+ r = m - 1;
+ else if (ucs > agl_dup_offsets[m << 1])
+ l = m + 1;
+ else
+ return agl_dup_names + agl_dup_offsets[(m << 1) + 1];
+ }
+ return empty_dup_list;
+}
diff --git a/source/pdf/pdf-encodings.h b/source/pdf/pdf-encodings.h
new file mode 100644
index 00000000..025e9d03
--- /dev/null
+++ b/source/pdf/pdf-encodings.h
@@ -0,0 +1,215 @@
+#define _notdef NULL
+
+const unsigned short pdf_doc_encoding[256] =
+{
+ /* 0x0 to 0x17 except \t, \n and \r are really undefined */
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x02d8, 0x02c7, 0x02c6, 0x02d9, 0x02dd, 0x02db, 0x02da, 0x02dc,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000,
+ 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044,
+ 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018,
+ 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x0141, 0x0152, 0x0160,
+ 0x0178, 0x017d, 0x0131, 0x0142, 0x0153, 0x0161, 0x017e, 0x0000,
+ 0x20ac, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x0000, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+};
+
+const char * const pdf_standard[256] = { _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ "space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
+ "ampersand", "quoteright", "parenleft", "parenright", "asterisk",
+ "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
+ "three", "four", "five", "six", "seven", "eight", "nine", "colon",
+ "semicolon", "less", "equal", "greater", "question", "at", "A",
+ "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
+ "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+ "bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
+ "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
+ "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
+ "y", "z", "braceleft", "bar", "braceright", "asciitilde", _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, "exclamdown", "cent", "sterling",
+ "fraction", "yen", "florin", "section", "currency", "quotesingle",
+ "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright",
+ "fi", "fl", _notdef, "endash", "dagger", "daggerdbl", "periodcentered",
+ _notdef, "paragraph", "bullet", "quotesinglbase", "quotedblbase",
+ "quotedblright", "guillemotright", "ellipsis", "perthousand",
+ _notdef, "questiondown", _notdef, "grave", "acute", "circumflex",
+ "tilde", "macron", "breve", "dotaccent", "dieresis", _notdef,
+ "ring", "cedilla", _notdef, "hungarumlaut", "ogonek", "caron",
+ "emdash", _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, "AE",
+ _notdef, "ordfeminine", _notdef, _notdef, _notdef, _notdef,
+ "Lslash", "Oslash", "OE", "ordmasculine", _notdef, _notdef,
+ _notdef, _notdef, _notdef, "ae", _notdef, _notdef,
+ _notdef, "dotlessi", _notdef, _notdef, "lslash", "oslash",
+ "oe", "germandbls", _notdef, _notdef, _notdef, _notdef
+};
+
+const char * const pdf_mac_roman[256] = { _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ "space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
+ "ampersand", "quotesingle", "parenleft", "parenright", "asterisk",
+ "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
+ "three", "four", "five", "six", "seven", "eight", "nine", "colon",
+ "semicolon", "less", "equal", "greater", "question", "at", "A",
+ "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
+ "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+ "bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
+ "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
+ "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
+ "y", "z", "braceleft", "bar", "braceright", "asciitilde", _notdef,
+ "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis",
+ "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde",
+ "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis",
+ "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute",
+ "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave",
+ "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
+ "section", "bullet", "paragraph", "germandbls", "registered",
+ "copyright", "trademark", "acute", "dieresis", _notdef, "AE",
+ "Oslash", _notdef, "plusminus", _notdef, _notdef, "yen", "mu",
+ _notdef, _notdef, _notdef, _notdef, _notdef, "ordfeminine",
+ "ordmasculine", _notdef, "ae", "oslash", "questiondown", "exclamdown",
+ "logicalnot", _notdef, "florin", _notdef, _notdef, "guillemotleft",
+ "guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde",
+ "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright",
+ "quoteleft", "quoteright", "divide", _notdef, "ydieresis",
+ "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright",
+ "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase",
+ "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute",
+ "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave",
+ "Oacute", "Ocircumflex", _notdef, "Ograve", "Uacute", "Ucircumflex",
+ "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve",
+ "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron"
+};
+
+const char * const pdf_mac_expert[256] = { _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ "space", "exclamsmall", "Hungarumlautsmall", "centoldstyle",
+ "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
+ "parenleftsuperior", "parenrightsuperior", "twodotenleader",
+ "onedotenleader", "comma", "hyphen", "period", "fraction",
+ "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle",
+ "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle",
+ "eightoldstyle", "nineoldstyle", "colon", "semicolon", _notdef,
+ "threequartersemdash", _notdef, "questionsmall", _notdef,
+ _notdef, _notdef, _notdef, "Ethsmall", _notdef, _notdef,
+ "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths",
+ "fiveeighths", "seveneighths", "onethird", "twothirds", _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, "ff", "fi",
+ "fl", "ffi", "ffl", "parenleftinferior", _notdef, "parenrightinferior",
+ "Circumflexsmall", "hypheninferior", "Gravesmall", "Asmall", "Bsmall",
+ "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall",
+ "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
+ "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall",
+ "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah",
+ "Tildesmall", _notdef, _notdef, "asuperior", "centsuperior",
+ _notdef, _notdef, _notdef, _notdef, "Aacutesmall",
+ "Agravesmall", "Acircumflexsmall", "Adieresissmall", "Atildesmall",
+ "Aringsmall", "Ccedillasmall", "Eacutesmall", "Egravesmall",
+ "Ecircumflexsmall", "Edieresissmall", "Iacutesmall", "Igravesmall",
+ "Icircumflexsmall", "Idieresissmall", "Ntildesmall", "Oacutesmall",
+ "Ogravesmall", "Ocircumflexsmall", "Odieresissmall", "Otildesmall",
+ "Uacutesmall", "Ugravesmall", "Ucircumflexsmall", "Udieresissmall",
+ _notdef, "eightsuperior", "fourinferior", "threeinferior",
+ "sixinferior", "eightinferior", "seveninferior", "Scaronsmall",
+ _notdef, "centinferior", "twoinferior", _notdef, "Dieresissmall",
+ _notdef, "Caronsmall", "osuperior", "fiveinferior", _notdef,
+ "commainferior", "periodinferior", "Yacutesmall", _notdef,
+ "dollarinferior", _notdef, _notdef, "Thornsmall", _notdef,
+ "nineinferior", "zeroinferior", "Zcaronsmall", "AEsmall", "Oslashsmall",
+ "questiondownsmall", "oneinferior", "Lslashsmall", _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, "Cedillasmall",
+ _notdef, _notdef, _notdef, _notdef, _notdef, "OEsmall",
+ "figuredash", "hyphensuperior", _notdef, _notdef, _notdef,
+ _notdef, "exclamdownsmall", _notdef, "Ydieresissmall", _notdef,
+ "onesuperior", "twosuperior", "threesuperior", "foursuperior",
+ "fivesuperior", "sixsuperior", "sevensuperior", "ninesuperior",
+ "zerosuperior", _notdef, "esuperior", "rsuperior", "tsuperior",
+ _notdef, _notdef, "isuperior", "ssuperior", "dsuperior",
+ _notdef, _notdef, _notdef, _notdef, _notdef, "lsuperior",
+ "Ogoneksmall", "Brevesmall", "Macronsmall", "bsuperior", "nsuperior",
+ "msuperior", "commasuperior", "periodsuperior", "Dotaccentsmall",
+ "Ringsmall", _notdef, _notdef, _notdef, _notdef };
+
+const char * const pdf_win_ansi[256] = { _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, _notdef,
+ _notdef, _notdef, _notdef, _notdef, _notdef, "space",
+ "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand",
+ "quotesingle", "parenleft", "parenright", "asterisk", "plus",
+ "comma", "hyphen", "period", "slash", "zero", "one", "two", "three",
+ "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
+ "less", "equal", "greater", "question", "at", "A", "B", "C", "D",
+ "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
+ "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft",
+ "backslash", "bracketright", "asciicircum", "underscore", "grave",
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+ "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+ "braceleft", "bar", "braceright", "asciitilde", "bullet", "Euro",
+ "bullet", "quotesinglbase", "florin", "quotedblbase", "ellipsis",
+ "dagger", "daggerdbl", "circumflex", "perthousand", "Scaron",
+ "guilsinglleft", "OE", "bullet", "Zcaron", "bullet", "bullet",
+ "quoteleft", "quoteright", "quotedblleft", "quotedblright", "bullet",
+ "endash", "emdash", "tilde", "trademark", "scaron", "guilsinglright",
+ "oe", "bullet", "zcaron", "Ydieresis", "space", "exclamdown", "cent",
+ "sterling", "currency", "yen", "brokenbar", "section", "dieresis",
+ "copyright", "ordfeminine", "guillemotleft", "logicalnot", "hyphen",
+ "registered", "macron", "degree", "plusminus", "twosuperior",
+ "threesuperior", "acute", "mu", "paragraph", "periodcentered",
+ "cedilla", "onesuperior", "ordmasculine", "guillemotright",
+ "onequarter", "onehalf", "threequarters", "questiondown", "Agrave",
+ "Aacute", "Acircumflex", "Atilde", "Adieresis", "Aring", "AE",
+ "Ccedilla", "Egrave", "Eacute", "Ecircumflex", "Edieresis", "Igrave",
+ "Iacute", "Icircumflex", "Idieresis", "Eth", "Ntilde", "Ograve",
+ "Oacute", "Ocircumflex", "Otilde", "Odieresis", "multiply", "Oslash",
+ "Ugrave", "Uacute", "Ucircumflex", "Udieresis", "Yacute", "Thorn",
+ "germandbls", "agrave", "aacute", "acircumflex", "atilde", "adieresis",
+ "aring", "ae", "ccedilla", "egrave", "eacute", "ecircumflex",
+ "edieresis", "igrave", "iacute", "icircumflex", "idieresis", "eth",
+ "ntilde", "ograve", "oacute", "ocircumflex", "otilde", "odieresis",
+ "divide", "oslash", "ugrave", "uacute", "ucircumflex", "udieresis",
+ "yacute", "thorn", "ydieresis"
+};
diff --git a/source/pdf/pdf-event.c b/source/pdf/pdf-event.c
new file mode 100644
index 00000000..dc908985
--- /dev/null
+++ b/source/pdf/pdf-event.c
@@ -0,0 +1,144 @@
+#include "mupdf/fitz.h"
+#include "mupdf/pdf.h"
+
+typedef struct
+{
+ pdf_doc_event base;
+ pdf_alert_event alert;
+} pdf_alert_event_internal;
+
+pdf_alert_event *pdf_access_alert_event(pdf_doc_event *event)
+{
+ pdf_alert_event *alert = NULL;
+
+ if (event->type == PDF_DOCUMENT_EVENT_ALERT)
+ alert = &((pdf_alert_event_internal *)event)->alert;
+
+ return alert;
+}
+
+void pdf_event_issue_alert(pdf_document *doc, pdf_alert_event *alert)
+{
+ if (doc->event_cb)
+ {
+ pdf_alert_event_internal ievent;
+ ievent.base.type = PDF_DOCUMENT_EVENT_ALERT;
+ ievent.alert = *alert;
+
+ doc->event_cb((pdf_doc_event *)&ievent, doc->event_cb_data);
+
+ *alert = ievent.alert;
+ }
+}
+
+void pdf_event_issue_print(pdf_document *doc)
+{
+ pdf_doc_event e;
+
+ e.type = PDF_DOCUMENT_EVENT_PRINT;
+
+ if (doc->event_cb)
+ doc->event_cb(&e, doc->event_cb_data);
+}
+
+typedef struct
+{
+ pdf_doc_event base;
+ char *item;
+} pdf_exec_menu_item_event_internal;
+
+char *pdf_access_exec_menu_item_event(pdf_doc_event *event)
+{
+ char *item = NULL;
+
+ if (event->type == PDF_DOCUMENT_EVENT_EXEC_MENU_ITEM)
+ item = ((pdf_exec_menu_item_event_internal *)event)->item;
+
+ return item;
+}
+
+void pdf_event_issue_exec_menu_item(pdf_document *doc, char *item)
+{
+ if (doc->event_cb)
+ {
+ pdf_exec_menu_item_event_internal ievent;
+ ievent.base.type = PDF_DOCUMENT_EVENT_EXEC_MENU_ITEM;
+ ievent.item = item;
+
+ doc->event_cb((pdf_doc_event *)&ievent, doc->event_cb_data);
+ }
+}
+
+void pdf_event_issue_exec_dialog(pdf_document *doc)
+{
+ pdf_doc_event e;
+
+ e.type = PDF_DOCUMENT_EVENT_EXEC_DIALOG;
+
+ if (doc->event_cb)
+ doc->event_cb(&e, doc->event_cb_data);
+}
+
+typedef struct
+{
+ pdf_doc_event base;
+ pdf_launch_url_event launch_url;
+} pdf_launch_url_event_internal;
+
+pdf_launch_url_event *pdf_access_launch_url_event(pdf_doc_event *event)
+{
+ pdf_launch_url_event *launch_url = NULL;
+
+ if (event->type == PDF_DOCUMENT_EVENT_LAUNCH_URL)
+ launch_url = &((pdf_launch_url_event_internal *)event)->launch_url;
+
+ return launch_url;
+}
+
+void pdf_event_issue_launch_url(pdf_document *doc, char *url, int new_frame)
+{
+ if (doc->event_cb)
+ {
+ pdf_launch_url_event_internal e;
+
+ e.base.type = PDF_DOCUMENT_EVENT_LAUNCH_URL;
+ e.launch_url.url = url;
+ e.launch_url.new_frame = new_frame;
+ doc->event_cb((pdf_doc_event *)&e, doc->event_cb_data);
+ }
+}
+
+typedef struct
+{
+ pdf_doc_event base;
+ pdf_mail_doc_event mail_doc;
+} pdf_mail_doc_event_internal;
+
+pdf_mail_doc_event *pdf_access_mail_doc_event(pdf_doc_event *event)
+{
+ pdf_mail_doc_event *mail_doc = NULL;
+
+ if (event->type == PDF_DOCUMENT_EVENT_MAIL_DOC)
+ mail_doc = &((pdf_mail_doc_event_internal *)event)->mail_doc;
+
+ return mail_doc;
+}
+
+void pdf_event_issue_mail_doc(pdf_document *doc, pdf_mail_doc_event *event)
+{
+ if (doc->event_cb)
+ {
+ pdf_mail_doc_event_internal e;
+
+ e.base.type = PDF_DOCUMENT_EVENT_MAIL_DOC;
+ e.mail_doc = *event;
+
+ doc->event_cb((pdf_doc_event *)&e, doc->event_cb_data);
+ }
+}
+
+void pdf_set_doc_event_callback(pdf_document *doc, pdf_doc_event_cb *fn, void *data)
+{
+ doc->event_cb = fn;
+ doc->event_cb_data = data;
+}
diff --git a/source/pdf/pdf-field.c b/source/pdf/pdf-field.c
new file mode 100644
index 00000000..d8e1a240
--- /dev/null
+++ b/source/pdf/pdf-field.c
@@ -0,0 +1,56 @@
+#include "mupdf/pdf.h"
+
+pdf_obj *pdf_get_inheritable(pdf_document *doc, pdf_obj *obj, char *key)
+{
+ pdf_obj *fobj = NULL;
+
+ while (!fobj && obj)
+ {
+ fobj = pdf_dict_gets(obj, key);
+
+ if (!fobj)
+ obj = pdf_dict_gets(obj, "Parent");
+ }
+
+ return fobj ? fobj
+ : pdf_dict_gets(pdf_dict_gets(pdf_dict_gets(pdf_trailer(doc), "Root"), "AcroForm"), key);
+}
+
+int pdf_get_field_flags(pdf_document *doc, pdf_obj *obj)
+{
+ return pdf_to_int(pdf_get_inheritable(doc, obj, "Ff"));
+}
+
+static char *get_field_type_name(pdf_document *doc, pdf_obj *obj)
+{
+ return pdf_to_name(pdf_get_inheritable(doc, obj, "FT"));
+}
+
+int pdf_field_type(pdf_document *doc, pdf_obj *obj)
+{
+ char *type = get_field_type_name(doc, obj);
+ int flags = pdf_get_field_flags(doc, obj);
+
+ if (!strcmp(type, "Btn"))
+ {
+ if (flags & Ff_Pushbutton)
+ return PDF_WIDGET_TYPE_PUSHBUTTON;
+ else if (flags & Ff_Radio)
+ return PDF_WIDGET_TYPE_RADIOBUTTON;
+ else
+ return PDF_WIDGET_TYPE_CHECKBOX;
+ }
+ else if (!strcmp(type, "Tx"))
+ return PDF_WIDGET_TYPE_TEXT;
+ else if (!strcmp(type, "Ch"))
+ {
+ if (flags & Ff_Combo)
+ return PDF_WIDGET_TYPE_COMBOBOX;
+ else
+ return PDF_WIDGET_TYPE_LISTBOX;
+ }
+ else if (!strcmp(type, "Sig"))
+ return PDF_WIDGET_TYPE_SIGNATURE;
+ else
+ return PDF_WIDGET_TYPE_NOT_WIDGET;
+}
diff --git a/source/pdf/pdf-font.c b/source/pdf/pdf-font.c
new file mode 100644
index 00000000..1c2beb7b
--- /dev/null
+++ b/source/pdf/pdf-font.c
@@ -0,0 +1,1263 @@
+#include "mupdf/pdf.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_XFREE86_H
+
+static void pdf_load_font_descriptor(pdf_font_desc *fontdesc, pdf_document *xref, pdf_obj *dict, char *collection, char *basefont, int iscidfont);
+
+static char *base_font_names[][10] =
+{
+ { "Courier", "CourierNew", "CourierNewPSMT", NULL },
+ { "Courier-Bold", "CourierNew,Bold", "Courier,Bold",
+ "CourierNewPS-BoldMT", "CourierNew-Bold", NULL },
+ { "Courier-Oblique", "CourierNew,Italic", "Courier,Italic",
+ "CourierNewPS-ItalicMT", "CourierNew-Italic", NULL },
+ { "Courier-BoldOblique", "CourierNew,BoldItalic", "Courier,BoldItalic",
+ "CourierNewPS-BoldItalicMT", "CourierNew-BoldItalic", NULL },
+ { "Helvetica", "ArialMT", "Arial", NULL },
+ { "Helvetica-Bold", "Arial-BoldMT", "Arial,Bold", "Arial-Bold",
+ "Helvetica,Bold", NULL },
+ { "Helvetica-Oblique", "Arial-ItalicMT", "Arial,Italic", "Arial-Italic",
+ "Helvetica,Italic", "Helvetica-Italic", NULL },
+ { "Helvetica-BoldOblique", "Arial-BoldItalicMT",
+ "Arial,BoldItalic", "Arial-BoldItalic",
+ "Helvetica,BoldItalic", "Helvetica-BoldItalic", NULL },
+ { "Times-Roman", "TimesNewRomanPSMT", "TimesNewRoman",
+ "TimesNewRomanPS", NULL },
+ { "Times-Bold", "TimesNewRomanPS-BoldMT", "TimesNewRoman,Bold",
+ "TimesNewRomanPS-Bold", "TimesNewRoman-Bold", NULL },
+ { "Times-Italic", "TimesNewRomanPS-ItalicMT", "TimesNewRoman,Italic",
+ "TimesNewRomanPS-Italic", "TimesNewRoman-Italic", NULL },
+ { "Times-BoldItalic", "TimesNewRomanPS-BoldItalicMT",
+ "TimesNewRoman,BoldItalic", "TimesNewRomanPS-BoldItalic",
+ "TimesNewRoman-BoldItalic", NULL },
+ { "Symbol", "Symbol,Italic", "Symbol,Bold", "Symbol,BoldItalic",
+ "SymbolMT", "SymbolMT,Italic", "SymbolMT,Bold", "SymbolMT,BoldItalic", NULL },
+ { "ZapfDingbats", NULL }
+};
+
+static int is_dynalab(char *name)
+{
+ if (strstr(name, "HuaTian"))
+ return 1;
+ if (strstr(name, "MingLi"))
+ return 1;
+ if ((strstr(name, "DF") == name) || strstr(name, "+DF"))
+ return 1;
+ if ((strstr(name, "DLC") == name) || strstr(name, "+DLC"))
+ return 1;
+ return 0;
+}
+
+static int strcmp_ignore_space(char *a, char *b)
+{
+ while (1)
+ {
+ while (*a == ' ')
+ a++;
+ while (*b == ' ')
+ b++;
+ if (*a != *b)
+ return 1;
+ if (*a == 0)
+ return *a != *b;
+ if (*b == 0)
+ return *a != *b;
+ a++;
+ b++;
+ }
+}
+
+static char *clean_font_name(char *fontname)
+{
+ int i, k;
+ for (i = 0; i < nelem(base_font_names); i++)
+ for (k = 0; base_font_names[i][k]; k++)
+ if (!strcmp_ignore_space(base_font_names[i][k], fontname))
+ return base_font_names[i][0];
+ return fontname;
+}
+
+/*
+ * FreeType and Rendering glue
+ */
+
+enum { UNKNOWN, TYPE1, TRUETYPE };
+
+static int ft_kind(FT_Face face)
+{
+ const char *kind = FT_Get_X11_Font_Format(face);
+ if (!strcmp(kind, "TrueType"))
+ return TRUETYPE;
+ if (!strcmp(kind, "Type 1"))
+ return TYPE1;
+ if (!strcmp(kind, "CFF"))
+ return TYPE1;
+ if (!strcmp(kind, "CID Type 1"))
+ return TYPE1;
+ return UNKNOWN;
+}
+
+static int ft_is_bold(FT_Face face)
+{
+ return face->style_flags & FT_STYLE_FLAG_BOLD;
+}
+
+static int ft_is_italic(FT_Face face)
+{
+ return face->style_flags & FT_STYLE_FLAG_ITALIC;
+}
+
+static int ft_char_index(FT_Face face, int cid)
+{
+ int gid = FT_Get_Char_Index(face, cid);
+ if (gid == 0)
+ gid = FT_Get_Char_Index(face, 0xf000 + cid);
+
+ /* some chinese fonts only ship the similarly looking 0x2026 */
+ if (gid == 0 && cid == 0x22ef)
+ gid = FT_Get_Char_Index(face, 0x2026);
+
+ return gid;
+}
+
+static int ft_cid_to_gid(pdf_font_desc *fontdesc, int cid)
+{
+ if (fontdesc->to_ttf_cmap)
+ {
+ cid = pdf_lookup_cmap(fontdesc->to_ttf_cmap, cid);
+ return ft_char_index(fontdesc->font->ft_face, cid);
+ }
+
+ if (fontdesc->cid_to_gid && cid < fontdesc->cid_to_gid_len && cid >= 0)
+ return fontdesc->cid_to_gid[cid];
+
+ return cid;
+}
+
+int
+pdf_font_cid_to_gid(fz_context *ctx, pdf_font_desc *fontdesc, int cid)
+{
+ if (fontdesc->font->ft_face)
+ return ft_cid_to_gid(fontdesc, cid);
+ return cid;
+}
+
+static int ft_width(fz_context *ctx, pdf_font_desc *fontdesc, int cid)
+{
+ int gid = ft_cid_to_gid(fontdesc, cid);
+ int fterr;
+
+ fterr = FT_Load_Glyph(fontdesc->font->ft_face, gid,
+ FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
+ if (fterr)
+ {
+ fz_warn(ctx, "freetype load glyph (gid %d): %s", gid, ft_error_string(fterr));
+ return 0;
+ }
+ return ((FT_Face)fontdesc->font->ft_face)->glyph->advance.x;
+}
+
+static int lookup_mre_code(char *name)
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (pdf_mac_roman[i] && !strcmp(name, pdf_mac_roman[i]))
+ return i;
+ return -1;
+}
+
+/*
+ * Load font files.
+ */
+
+static void
+pdf_load_builtin_font(fz_context *ctx, pdf_font_desc *fontdesc, char *fontname)
+{
+ unsigned char *data;
+ unsigned int len;
+
+ fontname = clean_font_name(fontname);
+
+ data = pdf_lookup_builtin_font(fontname, &len);
+ if (!data)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find builtin font: '%s'", fontname);
+
+ fontdesc->font = fz_new_font_from_memory(ctx, fontname, data, len, 0, 1);
+
+ if (!strcmp(fontname, "Symbol") || !strcmp(fontname, "ZapfDingbats"))
+ fontdesc->flags |= PDF_FD_SYMBOLIC;
+}
+
+static void
+pdf_load_substitute_font(fz_context *ctx, pdf_font_desc *fontdesc, char *fontname, int mono, int serif, int bold, int italic)
+{
+ unsigned char *data;
+ unsigned int len;
+
+ data = pdf_lookup_substitute_font(mono, serif, bold, italic, &len);
+ if (!data)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find substitute font");
+
+ fontdesc->font = fz_new_font_from_memory(ctx, fontname, data, len, 0, 1);
+
+ fontdesc->font->ft_substitute = 1;
+ fontdesc->font->ft_bold = bold && !ft_is_bold(fontdesc->font->ft_face);
+ fontdesc->font->ft_italic = italic && !ft_is_italic(fontdesc->font->ft_face);
+}
+
+static void
+pdf_load_substitute_cjk_font(fz_context *ctx, pdf_font_desc *fontdesc, char *fontname, int ros, int serif)
+{
+ unsigned char *data;
+ unsigned int len;
+
+ data = pdf_lookup_substitute_cjk_font(ros, serif, &len);
+ if (!data)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find builtin CJK font");
+
+ /* a glyph bbox cache is too big for droid sans fallback (51k glyphs!) */
+ fontdesc->font = fz_new_font_from_memory(ctx, fontname, data, len, 0, 0);
+
+ fontdesc->font->ft_substitute = 1;
+}
+
+static void
+pdf_load_system_font(fz_context *ctx, pdf_font_desc *fontdesc, char *fontname, char *collection)
+{
+ int bold = 0;
+ int italic = 0;
+ int serif = 0;
+ int mono = 0;
+
+ if (strstr(fontname, "Bold"))
+ bold = 1;
+ if (strstr(fontname, "Italic"))
+ italic = 1;
+ if (strstr(fontname, "Oblique"))
+ italic = 1;
+
+ if (fontdesc->flags & PDF_FD_FIXED_PITCH)
+ mono = 1;
+ if (fontdesc->flags & PDF_FD_SERIF)
+ serif = 1;
+ if (fontdesc->flags & PDF_FD_ITALIC)
+ italic = 1;
+ if (fontdesc->flags & PDF_FD_FORCE_BOLD)
+ bold = 1;
+
+ if (collection)
+ {
+ if (!strcmp(collection, "Adobe-CNS1"))
+ pdf_load_substitute_cjk_font(ctx, fontdesc, fontname, PDF_ROS_CNS, serif);
+ else if (!strcmp(collection, "Adobe-GB1"))
+ pdf_load_substitute_cjk_font(ctx, fontdesc, fontname, PDF_ROS_GB, serif);
+ else if (!strcmp(collection, "Adobe-Japan1"))
+ pdf_load_substitute_cjk_font(ctx, fontdesc, fontname, PDF_ROS_JAPAN, serif);
+ else if (!strcmp(collection, "Adobe-Korea1"))
+ pdf_load_substitute_cjk_font(ctx, fontdesc, fontname, PDF_ROS_KOREA, serif);
+ else
+ {
+ if (strcmp(collection, "Adobe-Identity") != 0)
+ fz_warn(ctx, "unknown cid collection: %s", collection);
+ pdf_load_substitute_font(ctx, fontdesc, fontname, mono, serif, bold, italic);
+ }
+ }
+ else
+ {
+ pdf_load_substitute_font(ctx, fontdesc, fontname, mono, serif, bold, italic);
+ }
+}
+
+static void
+pdf_load_embedded_font(pdf_document *xref, pdf_font_desc *fontdesc, char *fontname, pdf_obj *stmref)
+{
+ fz_buffer *buf;
+ fz_context *ctx = xref->ctx;
+
+ fz_try(ctx)
+ {
+ buf = pdf_load_stream(xref, pdf_to_num(stmref), pdf_to_gen(stmref));
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load font stream (%d %d R)", pdf_to_num(stmref), pdf_to_gen(stmref));
+ }
+
+ fz_try(ctx)
+ {
+ fontdesc->font = fz_new_font_from_memory(ctx, fontname, buf->data, buf->len, 0, 1);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_buffer(ctx, buf);
+ fz_rethrow_message(ctx, "cannot load embedded font (%d %d R)", pdf_to_num(stmref), pdf_to_gen(stmref));
+ }
+ fontdesc->size += buf->len;
+
+ /* save the buffer so we can free it later */
+ fontdesc->font->ft_data = buf->data;
+ fontdesc->font->ft_size = buf->len;
+ fz_free(ctx, buf); /* only free the fz_buffer struct, not the contained data */
+
+ fontdesc->is_embedded = 1;
+}
+
+/*
+ * Create and destroy
+ */
+
+pdf_font_desc *
+pdf_keep_font(fz_context *ctx, pdf_font_desc *fontdesc)
+{
+ return (pdf_font_desc *)fz_keep_storable(ctx, &fontdesc->storable);
+}
+
+void
+pdf_drop_font(fz_context *ctx, pdf_font_desc *fontdesc)
+{
+ fz_drop_storable(ctx, &fontdesc->storable);
+}
+
+static void
+pdf_free_font_imp(fz_context *ctx, fz_storable *fontdesc_)
+{
+ pdf_font_desc *fontdesc = (pdf_font_desc *)fontdesc_;
+
+ if (fontdesc->font)
+ fz_drop_font(ctx, fontdesc->font);
+ if (fontdesc->encoding)
+ pdf_drop_cmap(ctx, fontdesc->encoding);
+ if (fontdesc->to_ttf_cmap)
+ pdf_drop_cmap(ctx, fontdesc->to_ttf_cmap);
+ if (fontdesc->to_unicode)
+ pdf_drop_cmap(ctx, fontdesc->to_unicode);
+ fz_free(ctx, fontdesc->cid_to_gid);
+ fz_free(ctx, fontdesc->cid_to_ucs);
+ fz_free(ctx, fontdesc->hmtx);
+ fz_free(ctx, fontdesc->vmtx);
+ fz_free(ctx, fontdesc);
+}
+
+pdf_font_desc *
+pdf_new_font_desc(fz_context *ctx)
+{
+ pdf_font_desc *fontdesc;
+
+ fontdesc = fz_malloc_struct(ctx, pdf_font_desc);
+ FZ_INIT_STORABLE(fontdesc, 1, pdf_free_font_imp);
+ fontdesc->size = sizeof(pdf_font_desc);
+
+ fontdesc->font = NULL;
+
+ fontdesc->flags = 0;
+ fontdesc->italic_angle = 0;
+ fontdesc->ascent = 0;
+ fontdesc->descent = 0;
+ fontdesc->cap_height = 0;
+ fontdesc->x_height = 0;
+ fontdesc->missing_width = 0;
+
+ fontdesc->encoding = NULL;
+ fontdesc->to_ttf_cmap = NULL;
+ fontdesc->cid_to_gid_len = 0;
+ fontdesc->cid_to_gid = NULL;
+
+ fontdesc->to_unicode = NULL;
+ fontdesc->cid_to_ucs_len = 0;
+ fontdesc->cid_to_ucs = NULL;
+
+ fontdesc->wmode = 0;
+
+ fontdesc->hmtx_cap = 0;
+ fontdesc->vmtx_cap = 0;
+ fontdesc->hmtx_len = 0;
+ fontdesc->vmtx_len = 0;
+ fontdesc->hmtx = NULL;
+ fontdesc->vmtx = NULL;
+
+ fontdesc->dhmtx.lo = 0x0000;
+ fontdesc->dhmtx.hi = 0xFFFF;
+ fontdesc->dhmtx.w = 1000;
+
+ fontdesc->dvmtx.lo = 0x0000;
+ fontdesc->dvmtx.hi = 0xFFFF;
+ fontdesc->dvmtx.x = 0;
+ fontdesc->dvmtx.y = 880;
+ fontdesc->dvmtx.w = -1000;
+
+ fontdesc->is_embedded = 0;
+
+ return fontdesc;
+}
+
+/*
+ * Simple fonts (Type1 and TrueType)
+ */
+
+static pdf_font_desc *
+pdf_load_simple_font(pdf_document *xref, pdf_obj *dict)
+{
+ pdf_obj *descriptor;
+ pdf_obj *encoding;
+ pdf_obj *widths;
+ unsigned short *etable = NULL;
+ pdf_font_desc *fontdesc = NULL;
+ char *subtype;
+ FT_Face face;
+ FT_CharMap cmap;
+ int symbolic;
+ int kind;
+
+ char *basefont;
+ char *estrings[256];
+ char ebuffer[256][32];
+ int i, k, n;
+ int fterr;
+ int has_lock = 0;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(fontdesc);
+ fz_var(etable);
+ fz_var(has_lock);
+
+ basefont = pdf_to_name(pdf_dict_gets(dict, "BaseFont"));
+
+ /* Load font file */
+ fz_try(ctx)
+ {
+ fontdesc = pdf_new_font_desc(ctx);
+
+ descriptor = pdf_dict_gets(dict, "FontDescriptor");
+ if (descriptor)
+ pdf_load_font_descriptor(fontdesc, xref, descriptor, NULL, basefont, 0);
+ else
+ pdf_load_builtin_font(ctx, fontdesc, basefont);
+
+ /* Some chinese documents mistakenly consider WinAnsiEncoding to be codepage 936 */
+ if (descriptor && pdf_is_string(pdf_dict_gets(descriptor, "FontName")) &&
+ !pdf_dict_gets(dict, "ToUnicode") &&
+ !strcmp(pdf_to_name(pdf_dict_gets(dict, "Encoding")), "WinAnsiEncoding") &&
+ pdf_to_int(pdf_dict_gets(descriptor, "Flags")) == 4)
+ {
+ char *cp936fonts[] = {
+ "\xCB\xCE\xCC\xE5", "SimSun,Regular",
+ "\xBA\xDA\xCC\xE5", "SimHei,Regular",
+ "\xBF\xAC\xCC\xE5_GB2312", "SimKai,Regular",
+ "\xB7\xC2\xCB\xCE_GB2312", "SimFang,Regular",
+ "\xC1\xA5\xCA\xE9", "SimLi,Regular",
+ NULL
+ };
+ for (i = 0; cp936fonts[i]; i += 2)
+ if (!strcmp(basefont, cp936fonts[i]))
+ break;
+ if (cp936fonts[i])
+ {
+ fz_warn(ctx, "workaround for S22PDF lying about chinese font encodings");
+ pdf_drop_font(ctx, fontdesc);
+ fontdesc = NULL;
+ fontdesc = pdf_new_font_desc(ctx);
+ pdf_load_font_descriptor(fontdesc, xref, descriptor, "Adobe-GB1", cp936fonts[i+1], 0);
+ fontdesc->encoding = pdf_load_system_cmap(ctx, "GBK-EUC-H");
+ fontdesc->to_unicode = pdf_load_system_cmap(ctx, "Adobe-GB1-UCS2");
+ fontdesc->to_ttf_cmap = pdf_load_system_cmap(ctx, "Adobe-GB1-UCS2");
+
+ face = fontdesc->font->ft_face;
+ kind = ft_kind(face);
+ goto skip_encoding;
+ }
+ }
+
+ face = fontdesc->font->ft_face;
+ kind = ft_kind(face);
+
+ /* Encoding */
+
+ symbolic = fontdesc->flags & 4;
+
+ if (face->num_charmaps > 0)
+ cmap = face->charmaps[0];
+ else
+ cmap = NULL;
+
+ for (i = 0; i < face->num_charmaps; i++)
+ {
+ FT_CharMap test = face->charmaps[i];
+
+ if (kind == TYPE1)
+ {
+ if (test->platform_id == 7)
+ cmap = test;
+ }
+
+ if (kind == TRUETYPE)
+ {
+ if (test->platform_id == 1 && test->encoding_id == 0)
+ cmap = test;
+ if (test->platform_id == 3 && test->encoding_id == 1)
+ cmap = test;
+ if (symbolic && test->platform_id == 3 && test->encoding_id == 0)
+ cmap = test;
+ }
+ }
+
+ if (cmap)
+ {
+ fterr = FT_Set_Charmap(face, cmap);
+ if (fterr)
+ fz_warn(ctx, "freetype could not set cmap: %s", ft_error_string(fterr));
+ }
+ else
+ fz_warn(ctx, "freetype could not find any cmaps");
+
+ etable = fz_malloc_array(ctx, 256, sizeof(unsigned short));
+ fontdesc->size += 256 * sizeof(unsigned short);
+ for (i = 0; i < 256; i++)
+ {
+ estrings[i] = NULL;
+ etable[i] = 0;
+ }
+
+ encoding = pdf_dict_gets(dict, "Encoding");
+ if (encoding)
+ {
+ if (pdf_is_name(encoding))
+ pdf_load_encoding(estrings, pdf_to_name(encoding));
+
+ if (pdf_is_dict(encoding))
+ {
+ pdf_obj *base, *diff, *item;
+
+ base = pdf_dict_gets(encoding, "BaseEncoding");
+ if (pdf_is_name(base))
+ pdf_load_encoding(estrings, pdf_to_name(base));
+ else if (!fontdesc->is_embedded && !symbolic)
+ pdf_load_encoding(estrings, "StandardEncoding");
+
+ diff = pdf_dict_gets(encoding, "Differences");
+ if (pdf_is_array(diff))
+ {
+ n = pdf_array_len(diff);
+ k = 0;
+ for (i = 0; i < n; i++)
+ {
+ item = pdf_array_get(diff, i);
+ if (pdf_is_int(item))
+ k = pdf_to_int(item);
+ if (pdf_is_name(item) && k >= 0 && k < nelem(estrings))
+ estrings[k++] = pdf_to_name(item);
+ }
+ }
+ }
+ }
+
+ /* start with the builtin encoding */
+ for (i = 0; i < 256; i++)
+ etable[i] = ft_char_index(face, i);
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ has_lock = 1;
+
+ /* built-in and substitute fonts may be a different type than what the document expects */
+ subtype = pdf_to_name(pdf_dict_gets(dict, "Subtype"));
+ if (!strcmp(subtype, "Type1"))
+ kind = TYPE1;
+ else if (!strcmp(subtype, "MMType1"))
+ kind = TYPE1;
+ else if (!strcmp(subtype, "TrueType"))
+ kind = TRUETYPE;
+ else if (!strcmp(subtype, "CIDFontType0"))
+ kind = TYPE1;
+ else if (!strcmp(subtype, "CIDFontType2"))
+ kind = TRUETYPE;
+
+ /* encode by glyph name where we can */
+ if (kind == TYPE1)
+ {
+ for (i = 0; i < 256; i++)
+ {
+ if (estrings[i])
+ {
+ etable[i] = FT_Get_Name_Index(face, estrings[i]);
+ if (etable[i] == 0)
+ {
+ int aglcode = pdf_lookup_agl(estrings[i]);
+ const char **dupnames = pdf_lookup_agl_duplicates(aglcode);
+ while (*dupnames)
+ {
+ etable[i] = FT_Get_Name_Index(face, (char*)*dupnames);
+ if (etable[i])
+ break;
+ dupnames++;
+ }
+ }
+ }
+ }
+ }
+
+ /* encode by glyph name where we can */
+ if (kind == TRUETYPE)
+ {
+ /* Unicode cmap */
+ if (!symbolic && face->charmap && face->charmap->platform_id == 3)
+ {
+ for (i = 0; i < 256; i++)
+ {
+ if (estrings[i])
+ {
+ int aglcode = pdf_lookup_agl(estrings[i]);
+ if (!aglcode)
+ etable[i] = FT_Get_Name_Index(face, estrings[i]);
+ else
+ etable[i] = ft_char_index(face, aglcode);
+ }
+ }
+ }
+
+ /* MacRoman cmap */
+ else if (!symbolic && face->charmap && face->charmap->platform_id == 1)
+ {
+ for (i = 0; i < 256; i++)
+ {
+ if (estrings[i])
+ {
+ k = lookup_mre_code(estrings[i]);
+ if (k <= 0)
+ etable[i] = FT_Get_Name_Index(face, estrings[i]);
+ else
+ etable[i] = ft_char_index(face, k);
+ }
+ }
+ }
+
+ /* Symbolic cmap */
+ else if (!face->charmap || face->charmap->encoding != FT_ENCODING_MS_SYMBOL)
+ {
+ for (i = 0; i < 256; i++)
+ {
+ if (estrings[i])
+ {
+ etable[i] = FT_Get_Name_Index(face, estrings[i]);
+ if (etable[i] == 0)
+ etable[i] = ft_char_index(face, i);
+ }
+ }
+ }
+ }
+
+ /* try to reverse the glyph names from the builtin encoding */
+ for (i = 0; i < 256; i++)
+ {
+ if (etable[i] && !estrings[i])
+ {
+ if (FT_HAS_GLYPH_NAMES(face))
+ {
+ fterr = FT_Get_Glyph_Name(face, etable[i], ebuffer[i], 32);
+ if (fterr)
+ fz_warn(ctx, "freetype get glyph name (gid %d): %s", etable[i], ft_error_string(fterr));
+ if (ebuffer[i][0])
+ estrings[i] = ebuffer[i];
+ }
+ else
+ {
+ estrings[i] = (char*) pdf_win_ansi[i]; /* discard const */
+ }
+ }
+ }
+
+ /* symbolic Type 1 fonts with an implicit encoding and non-standard glyph names */
+ if (kind == TYPE1 && symbolic)
+ {
+ for (i = 0; i < 256; i++)
+ if (etable[i] && estrings[i] && !pdf_lookup_agl(estrings[i]))
+ estrings[i] = (char*) pdf_standard[i];
+ }
+
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ has_lock = 0;
+
+ fontdesc->encoding = pdf_new_identity_cmap(ctx, 0, 1);
+ fontdesc->size += pdf_cmap_size(ctx, fontdesc->encoding);
+ fontdesc->cid_to_gid_len = 256;
+ fontdesc->cid_to_gid = etable;
+
+ fz_try(ctx)
+ {
+ pdf_load_to_unicode(xref, fontdesc, estrings, NULL, pdf_dict_gets(dict, "ToUnicode"));
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "cannot load ToUnicode CMap");
+ }
+
+ skip_encoding:
+
+ /* Widths */
+
+ pdf_set_default_hmtx(ctx, fontdesc, fontdesc->missing_width);
+
+ widths = pdf_dict_gets(dict, "Widths");
+ if (widths)
+ {
+ int first, last;
+
+ first = pdf_to_int(pdf_dict_gets(dict, "FirstChar"));
+ last = pdf_to_int(pdf_dict_gets(dict, "LastChar"));
+
+ if (first < 0 || last > 255 || first > last)
+ first = last = 0;
+
+ for (i = 0; i < last - first + 1; i++)
+ {
+ int wid = pdf_to_int(pdf_array_get(widths, i));
+ pdf_add_hmtx(ctx, fontdesc, i + first, i + first, wid);
+ }
+ }
+ else
+ {
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ has_lock = 1;
+ fterr = FT_Set_Char_Size(face, 1000, 1000, 72, 72);
+ if (fterr)
+ fz_warn(ctx, "freetype set character size: %s", ft_error_string(fterr));
+ for (i = 0; i < 256; i++)
+ {
+ pdf_add_hmtx(ctx, fontdesc, i, i, ft_width(ctx, fontdesc, i));
+ }
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ has_lock = 0;
+ }
+
+ pdf_end_hmtx(ctx, fontdesc);
+ }
+ fz_catch(ctx)
+ {
+ if (has_lock)
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+ if (fontdesc && etable != fontdesc->cid_to_gid)
+ fz_free(ctx, etable);
+ pdf_drop_font(ctx, fontdesc);
+ fz_rethrow_message(ctx, "cannot load simple font (%d %d R)", pdf_to_num(dict), pdf_to_gen(dict));
+ }
+ return fontdesc;
+}
+
+/*
+ * CID Fonts
+ */
+
+static pdf_font_desc *
+load_cid_font(pdf_document *xref, pdf_obj *dict, pdf_obj *encoding, pdf_obj *to_unicode)
+{
+ pdf_obj *widths;
+ pdf_obj *descriptor;
+ pdf_font_desc *fontdesc = NULL;
+ FT_Face face;
+ int kind;
+ char collection[256];
+ char *basefont;
+ int i, k, fterr;
+ pdf_obj *obj;
+ int dw;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(fontdesc);
+
+ fz_try(ctx)
+ {
+ /* Get font name and CID collection */
+
+ basefont = pdf_to_name(pdf_dict_gets(dict, "BaseFont"));
+
+ {
+ pdf_obj *cidinfo;
+ char tmpstr[64];
+ int tmplen;
+
+ cidinfo = pdf_dict_gets(dict, "CIDSystemInfo");
+ if (!cidinfo)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cid font is missing info");
+
+ obj = pdf_dict_gets(cidinfo, "Registry");
+ tmplen = fz_mini(sizeof tmpstr - 1, pdf_to_str_len(obj));
+ memcpy(tmpstr, pdf_to_str_buf(obj), tmplen);
+ tmpstr[tmplen] = '\0';
+ fz_strlcpy(collection, tmpstr, sizeof collection);
+
+ fz_strlcat(collection, "-", sizeof collection);
+
+ obj = pdf_dict_gets(cidinfo, "Ordering");
+ tmplen = fz_mini(sizeof tmpstr - 1, pdf_to_str_len(obj));
+ memcpy(tmpstr, pdf_to_str_buf(obj), tmplen);
+ tmpstr[tmplen] = '\0';
+ fz_strlcat(collection, tmpstr, sizeof collection);
+ }
+
+ /* Load font file */
+
+ fontdesc = pdf_new_font_desc(ctx);
+
+ descriptor = pdf_dict_gets(dict, "FontDescriptor");
+ if (!descriptor)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: missing font descriptor");
+ pdf_load_font_descriptor(fontdesc, xref, descriptor, collection, basefont, 1);
+
+ face = fontdesc->font->ft_face;
+ kind = ft_kind(face);
+
+ /* Encoding */
+
+ if (pdf_is_name(encoding))
+ {
+ if (!strcmp(pdf_to_name(encoding), "Identity-H"))
+ fontdesc->encoding = pdf_new_identity_cmap(ctx, 0, 2);
+ else if (!strcmp(pdf_to_name(encoding), "Identity-V"))
+ fontdesc->encoding = pdf_new_identity_cmap(ctx, 1, 2);
+ else
+ fontdesc->encoding = pdf_load_system_cmap(ctx, pdf_to_name(encoding));
+ }
+ else if (pdf_is_indirect(encoding))
+ {
+ fontdesc->encoding = pdf_load_embedded_cmap(xref, encoding);
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: font missing encoding");
+ }
+ fontdesc->size += pdf_cmap_size(ctx, fontdesc->encoding);
+
+ pdf_set_font_wmode(ctx, fontdesc, pdf_cmap_wmode(ctx, fontdesc->encoding));
+
+ if (kind == TRUETYPE)
+ {
+ pdf_obj *cidtogidmap;
+
+ cidtogidmap = pdf_dict_gets(dict, "CIDToGIDMap");
+ if (pdf_is_indirect(cidtogidmap))
+ {
+ fz_buffer *buf;
+
+ buf = pdf_load_stream(xref, pdf_to_num(cidtogidmap), pdf_to_gen(cidtogidmap));
+
+ fontdesc->cid_to_gid_len = (buf->len) / 2;
+ fontdesc->cid_to_gid = fz_malloc_array(ctx, fontdesc->cid_to_gid_len, sizeof(unsigned short));
+ fontdesc->size += fontdesc->cid_to_gid_len * sizeof(unsigned short);
+ for (i = 0; i < fontdesc->cid_to_gid_len; i++)
+ fontdesc->cid_to_gid[i] = (buf->data[i * 2] << 8) + buf->data[i * 2 + 1];
+
+ fz_drop_buffer(ctx, buf);
+ }
+
+ /* if truetype font is external, cidtogidmap should not be identity */
+ /* so we map from cid to unicode and then map that through the (3 1) */
+ /* unicode cmap to get a glyph id */
+ else if (fontdesc->font->ft_substitute)
+ {
+ fterr = FT_Select_Charmap(face, ft_encoding_unicode);
+ if (fterr)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "fonterror: no unicode cmap when emulating CID font: %s", ft_error_string(fterr));
+ }
+
+ if (!strcmp(collection, "Adobe-CNS1"))
+ fontdesc->to_ttf_cmap = pdf_load_system_cmap(ctx, "Adobe-CNS1-UCS2");
+ else if (!strcmp(collection, "Adobe-GB1"))
+ fontdesc->to_ttf_cmap = pdf_load_system_cmap(ctx, "Adobe-GB1-UCS2");
+ else if (!strcmp(collection, "Adobe-Japan1"))
+ fontdesc->to_ttf_cmap = pdf_load_system_cmap(ctx, "Adobe-Japan1-UCS2");
+ else if (!strcmp(collection, "Adobe-Japan2"))
+ fontdesc->to_ttf_cmap = pdf_load_system_cmap(ctx, "Adobe-Japan2-UCS2");
+ else if (!strcmp(collection, "Adobe-Korea1"))
+ fontdesc->to_ttf_cmap = pdf_load_system_cmap(ctx, "Adobe-Korea1-UCS2");
+ }
+ }
+
+ pdf_load_to_unicode(xref, fontdesc, NULL, collection, to_unicode);
+
+ /* If we have an identity encoding, we're supposed to use the glyph ids directly.
+ * If we only have a substitute font, that won't work.
+ * Make a last ditch attempt by using
+ * the ToUnicode table if it exists to map via the substitute font's cmap. */
+ if (strstr(fontdesc->encoding->cmap_name, "Identity-") && fontdesc->font->ft_substitute)
+ {
+ fz_warn(ctx, "non-embedded font using identity encoding: %s", basefont);
+ if (fontdesc->to_unicode && !fontdesc->to_ttf_cmap)
+ fontdesc->to_ttf_cmap = pdf_keep_cmap(ctx, fontdesc->to_unicode);
+ }
+
+ /* Horizontal */
+
+ dw = 1000;
+ obj = pdf_dict_gets(dict, "DW");
+ if (obj)
+ dw = pdf_to_int(obj);
+ pdf_set_default_hmtx(ctx, fontdesc, dw);
+
+ widths = pdf_dict_gets(dict, "W");
+ if (widths)
+ {
+ int c0, c1, w, n, m;
+
+ n = pdf_array_len(widths);
+ for (i = 0; i < n; )
+ {
+ c0 = pdf_to_int(pdf_array_get(widths, i));
+ obj = pdf_array_get(widths, i + 1);
+ if (pdf_is_array(obj))
+ {
+ m = pdf_array_len(obj);
+ for (k = 0; k < m; k++)
+ {
+ w = pdf_to_int(pdf_array_get(obj, k));
+ pdf_add_hmtx(ctx, fontdesc, c0 + k, c0 + k, w);
+ }
+ i += 2;
+ }
+ else
+ {
+ c1 = pdf_to_int(obj);
+ w = pdf_to_int(pdf_array_get(widths, i + 2));
+ pdf_add_hmtx(ctx, fontdesc, c0, c1, w);
+ i += 3;
+ }
+ }
+ }
+
+ pdf_end_hmtx(ctx, fontdesc);
+
+ /* Vertical */
+
+ if (pdf_cmap_wmode(ctx, fontdesc->encoding) == 1)
+ {
+ int dw2y = 880;
+ int dw2w = -1000;
+
+ obj = pdf_dict_gets(dict, "DW2");
+ if (obj)
+ {
+ dw2y = pdf_to_int(pdf_array_get(obj, 0));
+ dw2w = pdf_to_int(pdf_array_get(obj, 1));
+ }
+
+ pdf_set_default_vmtx(ctx, fontdesc, dw2y, dw2w);
+
+ widths = pdf_dict_gets(dict, "W2");
+ if (widths)
+ {
+ int c0, c1, w, x, y, n;
+
+ n = pdf_array_len(widths);
+ for (i = 0; i < n; )
+ {
+ c0 = pdf_to_int(pdf_array_get(widths, i));
+ obj = pdf_array_get(widths, i + 1);
+ if (pdf_is_array(obj))
+ {
+ int m = pdf_array_len(obj);
+ for (k = 0; k * 3 < m; k ++)
+ {
+ w = pdf_to_int(pdf_array_get(obj, k * 3 + 0));
+ x = pdf_to_int(pdf_array_get(obj, k * 3 + 1));
+ y = pdf_to_int(pdf_array_get(obj, k * 3 + 2));
+ pdf_add_vmtx(ctx, fontdesc, c0 + k, c0 + k, x, y, w);
+ }
+ i += 2;
+ }
+ else
+ {
+ c1 = pdf_to_int(obj);
+ w = pdf_to_int(pdf_array_get(widths, i + 2));
+ x = pdf_to_int(pdf_array_get(widths, i + 3));
+ y = pdf_to_int(pdf_array_get(widths, i + 4));
+ pdf_add_vmtx(ctx, fontdesc, c0, c1, x, y, w);
+ i += 5;
+ }
+ }
+ }
+
+ pdf_end_vmtx(ctx, fontdesc);
+ }
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_font(ctx, fontdesc);
+ fz_rethrow_message(ctx, "cannot load cid font (%d %d R)", pdf_to_num(dict), pdf_to_gen(dict));
+ }
+
+ return fontdesc;
+}
+
+static pdf_font_desc *
+pdf_load_type0_font(pdf_document *xref, pdf_obj *dict)
+{
+ pdf_obj *dfonts;
+ pdf_obj *dfont;
+ pdf_obj *subtype;
+ pdf_obj *encoding;
+ pdf_obj *to_unicode;
+
+ dfonts = pdf_dict_gets(dict, "DescendantFonts");
+ if (!dfonts)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cid font is missing descendant fonts");
+
+ dfont = pdf_array_get(dfonts, 0);
+
+ subtype = pdf_dict_gets(dfont, "Subtype");
+ encoding = pdf_dict_gets(dict, "Encoding");
+ to_unicode = pdf_dict_gets(dict, "ToUnicode");
+
+ if (pdf_is_name(subtype) && !strcmp(pdf_to_name(subtype), "CIDFontType0"))
+ return load_cid_font(xref, dfont, encoding, to_unicode);
+ else if (pdf_is_name(subtype) && !strcmp(pdf_to_name(subtype), "CIDFontType2"))
+ return load_cid_font(xref, dfont, encoding, to_unicode);
+ else
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "syntaxerror: unknown cid font type");
+
+ return NULL; /* Stupid MSVC */
+}
+
+/*
+ * FontDescriptor
+ */
+
+static void
+pdf_load_font_descriptor(pdf_font_desc *fontdesc, pdf_document *xref, pdf_obj *dict, char *collection, char *basefont, int iscidfont)
+{
+ pdf_obj *obj1, *obj2, *obj3, *obj;
+ char *fontname, *origname;
+ FT_Face face;
+ fz_context *ctx = xref->ctx;
+
+ /* Prefer BaseFont; don't bother with FontName */
+ origname = basefont;
+
+ /* Look through list of alternate names for built in fonts */
+ fontname = clean_font_name(origname);
+
+ fontdesc->flags = pdf_to_int(pdf_dict_gets(dict, "Flags"));
+ fontdesc->italic_angle = pdf_to_real(pdf_dict_gets(dict, "ItalicAngle"));
+ fontdesc->ascent = pdf_to_real(pdf_dict_gets(dict, "Ascent"));
+ fontdesc->descent = pdf_to_real(pdf_dict_gets(dict, "Descent"));
+ fontdesc->cap_height = pdf_to_real(pdf_dict_gets(dict, "CapHeight"));
+ fontdesc->x_height = pdf_to_real(pdf_dict_gets(dict, "XHeight"));
+ fontdesc->missing_width = pdf_to_real(pdf_dict_gets(dict, "MissingWidth"));
+
+ obj1 = pdf_dict_gets(dict, "FontFile");
+ obj2 = pdf_dict_gets(dict, "FontFile2");
+ obj3 = pdf_dict_gets(dict, "FontFile3");
+ obj = obj1 ? obj1 : obj2 ? obj2 : obj3;
+
+ if (pdf_is_indirect(obj))
+ {
+ fz_try(ctx)
+ {
+ pdf_load_embedded_font(xref, fontdesc, fontname, obj);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "ignored error when loading embedded font; attempting to load system font");
+ if (origname != fontname && !iscidfont)
+ pdf_load_builtin_font(ctx, fontdesc, fontname);
+ else
+ pdf_load_system_font(ctx, fontdesc, fontname, collection);
+ }
+ }
+ else
+ {
+ if (origname != fontname && !iscidfont)
+ pdf_load_builtin_font(ctx, fontdesc, fontname);
+ else
+ pdf_load_system_font(ctx, fontdesc, fontname, collection);
+ }
+
+ /* Check for DynaLab fonts that must use hinting */
+ face = fontdesc->font->ft_face;
+ if (ft_kind(face) == TRUETYPE)
+ {
+ if (FT_IS_TRICKY(face) || is_dynalab(fontdesc->font->name))
+ fontdesc->font->ft_hint = 1;
+ }
+}
+
+static void
+pdf_make_width_table(fz_context *ctx, pdf_font_desc *fontdesc)
+{
+ fz_font *font = fontdesc->font;
+ int i, k, n, cid, gid;
+
+ n = 0;
+ for (i = 0; i < fontdesc->hmtx_len; i++)
+ {
+ for (k = fontdesc->hmtx[i].lo; k <= fontdesc->hmtx[i].hi; k++)
+ {
+ cid = pdf_lookup_cmap(fontdesc->encoding, k);
+ gid = pdf_font_cid_to_gid(ctx, fontdesc, cid);
+ if (gid > n)
+ n = gid;
+ }
+ };
+
+ font->width_count = n + 1;
+ font->width_table = fz_malloc_array(ctx, font->width_count, sizeof(int));
+ memset(font->width_table, 0, font->width_count * sizeof(int));
+ fontdesc->size += font->width_count * sizeof(int);
+
+ for (i = 0; i < fontdesc->hmtx_len; i++)
+ {
+ for (k = fontdesc->hmtx[i].lo; k <= fontdesc->hmtx[i].hi; k++)
+ {
+ cid = pdf_lookup_cmap(fontdesc->encoding, k);
+ gid = pdf_font_cid_to_gid(ctx, fontdesc, cid);
+ if (gid >= 0 && gid < font->width_count)
+ font->width_table[gid] = fz_maxi(fontdesc->hmtx[i].w, font->width_table[gid]);
+ }
+ }
+}
+
+pdf_font_desc *
+pdf_load_font(pdf_document *xref, pdf_obj *rdb, pdf_obj *dict, int nested_depth)
+{
+ char *subtype;
+ pdf_obj *dfonts;
+ pdf_obj *charprocs;
+ fz_context *ctx = xref->ctx;
+ pdf_font_desc *fontdesc;
+ int type3 = 0;
+
+ if ((fontdesc = pdf_find_item(ctx, pdf_free_font_imp, dict)))
+ {
+ return fontdesc;
+ }
+
+ subtype = pdf_to_name(pdf_dict_gets(dict, "Subtype"));
+ dfonts = pdf_dict_gets(dict, "DescendantFonts");
+ charprocs = pdf_dict_gets(dict, "CharProcs");
+
+ if (subtype && !strcmp(subtype, "Type0"))
+ fontdesc = pdf_load_type0_font(xref, dict);
+ else if (subtype && !strcmp(subtype, "Type1"))
+ fontdesc = pdf_load_simple_font(xref, dict);
+ else if (subtype && !strcmp(subtype, "MMType1"))
+ fontdesc = pdf_load_simple_font(xref, dict);
+ else if (subtype && !strcmp(subtype, "TrueType"))
+ fontdesc = pdf_load_simple_font(xref, dict);
+ else if (subtype && !strcmp(subtype, "Type3"))
+ {
+ fontdesc = pdf_load_type3_font(xref, rdb, dict);
+ type3 = 1;
+ }
+ else if (charprocs)
+ {
+ fz_warn(ctx, "unknown font format, guessing type3.");
+ fontdesc = pdf_load_type3_font(xref, rdb, dict);
+ type3 = 1;
+ }
+ else if (dfonts)
+ {
+ fz_warn(ctx, "unknown font format, guessing type0.");
+ fontdesc = pdf_load_type0_font(xref, dict);
+ }
+ else
+ {
+ fz_warn(ctx, "unknown font format, guessing type1 or truetype.");
+ fontdesc = pdf_load_simple_font(xref, dict);
+ }
+
+ /* Save the widths to stretch non-CJK substitute fonts */
+ if (fontdesc->font->ft_substitute && !fontdesc->to_ttf_cmap)
+ pdf_make_width_table(ctx, fontdesc);
+
+ pdf_store_item(ctx, dict, fontdesc, fontdesc->size);
+
+ if (type3)
+ pdf_load_type3_glyphs(xref, fontdesc, nested_depth);
+
+ return fontdesc;
+}
+
+#ifndef NDEBUG
+void
+pdf_print_font(fz_context *ctx, pdf_font_desc *fontdesc)
+{
+ int i;
+
+ printf("fontdesc {\n");
+
+ if (fontdesc->font->ft_face)
+ printf("\tfreetype font\n");
+ if (fontdesc->font->t3procs)
+ printf("\ttype3 font\n");
+
+ printf("\twmode %d\n", fontdesc->wmode);
+ printf("\tDW %d\n", fontdesc->dhmtx.w);
+
+ printf("\tW {\n");
+ for (i = 0; i < fontdesc->hmtx_len; i++)
+ printf("\t\t<%04x> <%04x> %d\n",
+ fontdesc->hmtx[i].lo, fontdesc->hmtx[i].hi, fontdesc->hmtx[i].w);
+ printf("\t}\n");
+
+ if (fontdesc->wmode)
+ {
+ printf("\tDW2 [%d %d]\n", fontdesc->dvmtx.y, fontdesc->dvmtx.w);
+ printf("\tW2 {\n");
+ for (i = 0; i < fontdesc->vmtx_len; i++)
+ printf("\t\t<%04x> <%04x> %d %d %d\n", fontdesc->vmtx[i].lo, fontdesc->vmtx[i].hi,
+ fontdesc->vmtx[i].x, fontdesc->vmtx[i].y, fontdesc->vmtx[i].w);
+ printf("\t}\n");
+ }
+}
+#endif
+
+fz_rect *pdf_measure_text(fz_context *ctx, pdf_font_desc *fontdesc, unsigned char *buf, int len, fz_rect *acc)
+{
+ pdf_hmtx h;
+ int gid;
+ int i;
+ float x = 0.0;
+ fz_rect bbox;
+
+ *acc = fz_empty_rect;
+ for (i = 0; i < len; i++)
+ {
+ gid = pdf_font_cid_to_gid(ctx, fontdesc, buf[i]);
+ h = pdf_lookup_hmtx(ctx, fontdesc, buf[i]);
+ fz_bound_glyph(ctx, fontdesc->font, gid, &fz_identity, &bbox);
+ bbox.x0 += x;
+ bbox.x1 += x;
+ fz_union_rect(acc, &bbox);
+ x += h.w / 1000.0;
+ }
+
+ return acc;
+}
+
+float pdf_text_stride(fz_context *ctx, pdf_font_desc *fontdesc, float fontsize, unsigned char *buf, int len, float room, int *count)
+{
+ pdf_hmtx h;
+ int i = 0;
+ float x = 0.0;
+
+ while(i < len)
+ {
+ float span;
+
+ h = pdf_lookup_hmtx(ctx, fontdesc, buf[i]);
+
+ span = h.w * fontsize / 1000.0;
+
+ if (x + span > room)
+ break;
+
+ x += span;
+ i ++;
+ }
+
+ if (count)
+ *count = i;
+
+ return x;
+}
diff --git a/source/pdf/pdf-fontfile.c b/source/pdf/pdf-fontfile.c
new file mode 100644
index 00000000..c9990dad
--- /dev/null
+++ b/source/pdf/pdf-fontfile.c
@@ -0,0 +1,153 @@
+#include "mupdf/pdf.h"
+
+/*
+ Which fonts are embedded is based on a few preprocessor definitions.
+
+ The base 14 fonts are always embedded.
+ For font substitution we embed DroidSans which has good glyph coverage.
+ For CJK font substitution we embed DroidSansFallback.
+
+ Set NOCJK to skip all CJK support (this also omits embedding the CJK CMaps)
+ Set NOCJKFONT to skip the embedded CJK font.
+ Set NOCJKFULL to embed a smaller CJK font without CJK Extension A support.
+
+ Set NODROIDFONT to use the base 14 fonts as substitute fonts.
+*/
+
+#ifdef NOCJK
+#define NOCJKFONT
+#endif
+
+#include "gen_font_base14.h"
+
+#ifndef NODROIDFONT
+#include "gen_font_droid.h"
+#endif
+
+#ifndef NOCJKFONT
+#ifndef NOCJKFULL
+#include "gen_font_cjk_full.h"
+#else
+#include "gen_font_cjk.h"
+#endif
+#endif
+
+unsigned char *
+pdf_lookup_builtin_font(char *name, unsigned int *len)
+{
+ if (!strcmp("Courier", name)) {
+ *len = sizeof pdf_font_NimbusMonL_Regu;
+ return (unsigned char*) pdf_font_NimbusMonL_Regu;
+ }
+ if (!strcmp("Courier-Bold", name)) {
+ *len = sizeof pdf_font_NimbusMonL_Bold;
+ return (unsigned char*) pdf_font_NimbusMonL_Bold;
+ }
+ if (!strcmp("Courier-Oblique", name)) {
+ *len = sizeof pdf_font_NimbusMonL_ReguObli;
+ return (unsigned char*) pdf_font_NimbusMonL_ReguObli;
+ }
+ if (!strcmp("Courier-BoldOblique", name)) {
+ *len = sizeof pdf_font_NimbusMonL_BoldObli;
+ return (unsigned char*) pdf_font_NimbusMonL_BoldObli;
+ }
+ if (!strcmp("Helvetica", name)) {
+ *len = sizeof pdf_font_NimbusSanL_Regu;
+ return (unsigned char*) pdf_font_NimbusSanL_Regu;
+ }
+ if (!strcmp("Helvetica-Bold", name)) {
+ *len = sizeof pdf_font_NimbusSanL_Bold;
+ return (unsigned char*) pdf_font_NimbusSanL_Bold;
+ }
+ if (!strcmp("Helvetica-Oblique", name)) {
+ *len = sizeof pdf_font_NimbusSanL_ReguItal;
+ return (unsigned char*) pdf_font_NimbusSanL_ReguItal;
+ }
+ if (!strcmp("Helvetica-BoldOblique", name)) {
+ *len = sizeof pdf_font_NimbusSanL_BoldItal;
+ return (unsigned char*) pdf_font_NimbusSanL_BoldItal;
+ }
+ if (!strcmp("Times-Roman", name)) {
+ *len = sizeof pdf_font_NimbusRomNo9L_Regu;
+ return (unsigned char*) pdf_font_NimbusRomNo9L_Regu;
+ }
+ if (!strcmp("Times-Bold", name)) {
+ *len = sizeof pdf_font_NimbusRomNo9L_Medi;
+ return (unsigned char*) pdf_font_NimbusRomNo9L_Medi;
+ }
+ if (!strcmp("Times-Italic", name)) {
+ *len = sizeof pdf_font_NimbusRomNo9L_ReguItal;
+ return (unsigned char*) pdf_font_NimbusRomNo9L_ReguItal;
+ }
+ if (!strcmp("Times-BoldItalic", name)) {
+ *len = sizeof pdf_font_NimbusRomNo9L_MediItal;
+ return (unsigned char*) pdf_font_NimbusRomNo9L_MediItal;
+ }
+ if (!strcmp("Symbol", name)) {
+ *len = sizeof pdf_font_StandardSymL;
+ return (unsigned char*) pdf_font_StandardSymL;
+ }
+ if (!strcmp("ZapfDingbats", name)) {
+ *len = sizeof pdf_font_Dingbats;
+ return (unsigned char*) pdf_font_Dingbats;
+ }
+ *len = 0;
+ return NULL;
+}
+
+unsigned char *
+pdf_lookup_substitute_font(int mono, int serif, int bold, int italic, unsigned int *len)
+{
+#ifdef NODROIDFONT
+ if (mono) {
+ if (bold) {
+ if (italic) return pdf_lookup_builtin_font("Courier-BoldOblique", len);
+ else return pdf_lookup_builtin_font("Courier-Bold", len);
+ } else {
+ if (italic) return pdf_lookup_builtin_font("Courier-Oblique", len);
+ else return pdf_lookup_builtin_font("Courier", len);
+ }
+ } else if (serif) {
+ if (bold) {
+ if (italic) return pdf_lookup_builtin_font("Times-BoldItalic", len);
+ else return pdf_lookup_builtin_font("Times-Bold", len);
+ } else {
+ if (italic) return pdf_lookup_builtin_font("Times-Italic", len);
+ else return pdf_lookup_builtin_font("Times-Roman", len);
+ }
+ } else {
+ if (bold) {
+ if (italic) return pdf_lookup_builtin_font("Helvetica-BoldOblique", len);
+ else return pdf_lookup_builtin_font("Helvetica-Bold", len);
+ } else {
+ if (italic) return pdf_lookup_builtin_font("Helvetica-Oblique", len);
+ else return pdf_lookup_builtin_font("Helvetica", len);
+ }
+ }
+#else
+ if (mono) {
+ *len = sizeof pdf_font_DroidSansMono;
+ return (unsigned char*) pdf_font_DroidSansMono;
+ } else {
+ *len = sizeof pdf_font_DroidSans;
+ return (unsigned char*) pdf_font_DroidSans;
+ }
+#endif
+}
+
+unsigned char *
+pdf_lookup_substitute_cjk_font(int ros, int serif, unsigned int *len)
+{
+#ifndef NOCJKFONT
+#ifndef NOCJKFULL
+ *len = sizeof pdf_font_DroidSansFallbackFull;
+ return (unsigned char*) pdf_font_DroidSansFallbackFull;
+#else
+ *len = sizeof pdf_font_DroidSansFallback;
+ return (unsigned char*) pdf_font_DroidSansFallback;
+#endif
+#else
+ *len = 0;
+ return NULL;
+#endif
+}
diff --git a/source/pdf/pdf-form.c b/source/pdf/pdf-form.c
new file mode 100644
index 00000000..6e1d26ce
--- /dev/null
+++ b/source/pdf/pdf-form.c
@@ -0,0 +1,2876 @@
+#include "mupdf/pdf.h"
+
+#define MATRIX_COEFS (6)
+
+#define STRIKE_HEIGHT (0.375f)
+#define UNDERLINE_HEIGHT (0.075f)
+#define LINE_THICKNESS (0.07f)
+#define SMALL_FLOAT (0.00001)
+
+enum
+{
+ F_Invisible = 1 << (1-1),
+ F_Hidden = 1 << (2-1),
+ F_Print = 1 << (3-1),
+ F_NoZoom = 1 << (4-1),
+ F_NoRotate = 1 << (5-1),
+ F_NoView = 1 << (6-1),
+ F_ReadOnly = 1 << (7-1),
+ F_Locked = 1 << (8-1),
+ F_ToggleNoView = 1 << (9-1),
+ F_LockedContents = 1 << (10-1)
+};
+
+enum
+{
+ BS_Solid,
+ BS_Dashed,
+ BS_Beveled,
+ BS_Inset,
+ BS_Underline
+};
+
+/* Must be kept in sync with definitions in pdf_util.js */
+enum
+{
+ Display_Visible,
+ Display_Hidden,
+ Display_NoPrint,
+ Display_NoView
+};
+
+enum
+{
+ Q_Left = 0,
+ Q_Cent = 1,
+ Q_Right = 2
+};
+
+typedef struct da_info_s
+{
+ char *font_name;
+ int font_size;
+ float col[4];
+ int col_size;
+} da_info;
+
+typedef struct font_info_s
+{
+ da_info da_rec;
+ pdf_font_desc *font;
+} font_info;
+
+typedef struct text_widget_info_s
+{
+ pdf_obj *dr;
+ pdf_obj *col;
+ font_info font_rec;
+ int q;
+ int multiline;
+ int comb;
+ int max_len;
+} text_widget_info;
+
+static const char *fmt_re = "%f %f %f %f re\n";
+static const char *fmt_f = "f\n";
+static const char *fmt_s = "s\n";
+static const char *fmt_g = "%f g\n";
+static const char *fmt_m = "%f %f m\n";
+static const char *fmt_l = "%f %f l\n";
+static const char *fmt_w = "%f w\n";
+static const char *fmt_Tx_BMC = "/Tx BMC\n";
+static const char *fmt_q = "q\n";
+static const char *fmt_W = "W\n";
+static const char *fmt_n = "n\n";
+static const char *fmt_BT = "BT\n";
+static const char *fmt_Tm = "%1.2f %1.2f %1.2f %1.2f %1.2f %1.2f Tm\n";
+static const char *fmt_Td = "%f %f Td\n";
+static const char *fmt_Tj = " Tj\n";
+static const char *fmt_ET = "ET\n";
+static const char *fmt_Q = "Q\n";
+static const char *fmt_EMC = "EMC\n";
+
+static void account_for_rot(fz_rect *rect, fz_matrix *mat, int rot)
+{
+ float width = rect->x1;
+ float height = rect->y1;
+
+ switch (rot)
+ {
+ default:
+ *mat = fz_identity;
+ break;
+ case 90:
+ fz_pre_rotate(fz_translate(mat, width, 0), rot);
+ rect->x1 = height;
+ rect->y1 = width;
+ break;
+ case 180:
+ fz_pre_rotate(fz_translate(mat, width, height), rot);
+ break;
+ case 270:
+ fz_pre_rotate(fz_translate(mat, 0, height), rot);
+ rect->x1 = height;
+ rect->y1 = width;
+ break;
+ }
+}
+
+static char *get_string_or_stream(pdf_document *doc, pdf_obj *obj)
+{
+ fz_context *ctx = doc->ctx;
+ int len = 0;
+ char *buf = NULL;
+ fz_buffer *strmbuf = NULL;
+ char *text = NULL;
+
+ fz_var(strmbuf);
+ fz_var(text);
+ fz_try(ctx)
+ {
+ if (pdf_is_string(obj))
+ {
+ len = pdf_to_str_len(obj);
+ buf = pdf_to_str_buf(obj);
+ }
+ else if (pdf_is_stream(doc, pdf_to_num(obj), pdf_to_gen(obj)))
+ {
+ strmbuf = pdf_load_stream(doc, pdf_to_num(obj), pdf_to_gen(obj));
+ len = fz_buffer_storage(ctx, strmbuf, (unsigned char **)&buf);
+ }
+
+ if (buf)
+ {
+ text = fz_malloc(ctx, len+1);
+ memcpy(text, buf, len);
+ text[len] = 0;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, strmbuf);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, text);
+ fz_rethrow(ctx);
+ }
+
+ return text;
+}
+
+/* Find the point in a field hierarchy where all descendents
+ * share the same name */
+static pdf_obj *find_head_of_field_group(pdf_obj *obj)
+{
+ if (obj == NULL || pdf_dict_gets(obj, "T"))
+ return obj;
+ else
+ return find_head_of_field_group(pdf_dict_gets(obj, "Parent"));
+}
+
+static void pdf_field_mark_dirty(fz_context *ctx, pdf_obj *field)
+{
+ pdf_obj *kids = pdf_dict_gets(field, "Kids");
+ if (kids)
+ {
+ int i, n = pdf_array_len(kids);
+
+ for (i = 0; i < n; i++)
+ pdf_field_mark_dirty(ctx, pdf_array_get(kids, i));
+ }
+ else if (!pdf_dict_gets(field, "Dirty"))
+ {
+ pdf_obj *nullobj = pdf_new_null(ctx);
+ fz_try(ctx)
+ {
+ pdf_dict_puts(field, "Dirty", nullobj);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(nullobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+}
+
+static void copy_resources(pdf_obj *dst, pdf_obj *src)
+{
+ int i, len;
+
+ len = pdf_dict_len(src);
+ for (i = 0; i < len; i++)
+ {
+ pdf_obj *key = pdf_dict_get_key(src, i);
+
+ if (!pdf_dict_get(dst, key))
+ pdf_dict_put(dst, key, pdf_dict_get_val(src, i));
+ }
+}
+
+static void da_info_fin(fz_context *ctx, da_info *di)
+{
+ fz_free(ctx, di->font_name);
+ di->font_name = NULL;
+}
+
+static void da_check_stack(float *stack, int *top)
+{
+ if (*top == 32)
+ {
+ memmove(stack, stack + 1, 31 * sizeof(stack[0]));
+ *top = 31;
+ }
+}
+
+static void parse_da(fz_context *ctx, char *da, da_info *di)
+{
+ float stack[32];
+ int top = 0;
+ pdf_token tok;
+ char *name = NULL;
+ pdf_lexbuf lbuf;
+ fz_stream *str = fz_open_memory(ctx, (unsigned char *)da, strlen(da));
+
+ pdf_lexbuf_init(ctx, &lbuf, PDF_LEXBUF_SMALL);
+
+ fz_var(str);
+ fz_var(name);
+ fz_try(ctx)
+ {
+ for (tok = pdf_lex(str, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str, &lbuf))
+ {
+ switch (tok)
+ {
+ case PDF_TOK_NAME:
+ fz_free(ctx, name);
+ name = fz_strdup(ctx, lbuf.scratch);
+ break;
+
+ case PDF_TOK_INT:
+ da_check_stack(stack, &top);
+ stack[top] = lbuf.i;
+ top ++;
+ break;
+
+ case PDF_TOK_REAL:
+ da_check_stack(stack, &top);
+ stack[top] = lbuf.f;
+ top ++;
+ break;
+
+ case PDF_TOK_KEYWORD:
+ if (!strcmp(lbuf.scratch, "Tf"))
+ {
+ di->font_size = stack[0];
+ di->font_name = name;
+ name = NULL;
+ }
+ else if (!strcmp(lbuf.scratch, "rg"))
+ {
+ di->col[0] = stack[0];
+ di->col[1] = stack[1];
+ di->col[2] = stack[2];
+ di->col_size = 3;
+ }
+
+ fz_free(ctx, name);
+ name = NULL;
+ top = 0;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, name);
+ fz_close(str);
+ pdf_lexbuf_fin(&lbuf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void get_font_info(pdf_document *doc, pdf_obj *dr, char *da, font_info *font_rec)
+{
+ fz_context *ctx = doc->ctx;
+
+ parse_da(ctx, da, &font_rec->da_rec);
+ if (font_rec->da_rec.font_name == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "No font name in default appearance");
+ font_rec->font = pdf_load_font(doc, dr, pdf_dict_gets(pdf_dict_gets(dr, "Font"), font_rec->da_rec.font_name), 0);
+}
+
+static void font_info_fin(fz_context *ctx, font_info *font_rec)
+{
+ pdf_drop_font(ctx, font_rec->font);
+ font_rec->font = NULL;
+ da_info_fin(ctx, &font_rec->da_rec);
+}
+
+static void get_text_widget_info(pdf_document *doc, pdf_obj *widget, text_widget_info *info)
+{
+ char *da = pdf_to_str_buf(pdf_get_inheritable(doc, widget, "DA"));
+ int ff = pdf_get_field_flags(doc, widget);
+ pdf_obj *ml = pdf_get_inheritable(doc, widget, "MaxLen");
+
+ info->dr = pdf_get_inheritable(doc, widget, "DR");
+ info->col = pdf_dict_getp(widget, "MK/BG");
+ info->q = pdf_to_int(pdf_get_inheritable(doc, widget, "Q"));
+ info->multiline = (ff & Ff_Multiline) != 0;
+ info->comb = (ff & (Ff_Multiline|Ff_Password|Ff_FileSelect|Ff_Comb)) == Ff_Comb;
+
+ if (ml == NULL)
+ info->comb = 0;
+ else
+ info->max_len = pdf_to_int(ml);
+
+ get_font_info(doc, info->dr, da, &info->font_rec);
+}
+
+static void fzbuf_print_da(fz_context *ctx, fz_buffer *fzbuf, da_info *di)
+{
+ if (di->font_name != NULL && di->font_size != 0)
+ fz_buffer_printf(ctx, fzbuf, "/%s %d Tf", di->font_name, di->font_size);
+
+ switch (di->col_size)
+ {
+ case 1:
+ fz_buffer_printf(ctx, fzbuf, " %f g", di->col[0]);
+ break;
+
+ case 3:
+ fz_buffer_printf(ctx, fzbuf, " %f %f %f rg", di->col[0], di->col[1], di->col[2]);
+ break;
+
+ case 4:
+ fz_buffer_printf(ctx, fzbuf, " %f %f %f %f k", di->col[0], di->col[1], di->col[2], di->col[3]);
+ break;
+
+ default:
+ fz_buffer_printf(ctx, fzbuf, " 0 g");
+ break;
+ }
+}
+
+static fz_rect *measure_text(pdf_document *doc, font_info *font_rec, const fz_matrix *tm, char *text, fz_rect *bbox)
+{
+ pdf_measure_text(doc->ctx, font_rec->font, (unsigned char *)text, strlen(text), bbox);
+
+ bbox->x0 *= font_rec->da_rec.font_size * tm->a;
+ bbox->y0 *= font_rec->da_rec.font_size * tm->d;
+ bbox->x1 *= font_rec->da_rec.font_size * tm->a;
+ bbox->y1 *= font_rec->da_rec.font_size * tm->d;
+
+ return bbox;
+}
+
+static void fzbuf_print_color(fz_context *ctx, fz_buffer *fzbuf, pdf_obj *arr, int stroke, float adj)
+{
+ switch (pdf_array_len(arr))
+ {
+ case 1:
+ fz_buffer_printf(ctx, fzbuf, stroke?"%f G\n":"%f g\n",
+ pdf_to_real(pdf_array_get(arr, 0)) + adj);
+ break;
+ case 3:
+ fz_buffer_printf(ctx, fzbuf, stroke?"%f %f %f RG\n":"%f %f %f rg\n",
+ pdf_to_real(pdf_array_get(arr, 0)) + adj,
+ pdf_to_real(pdf_array_get(arr, 1)) + adj,
+ pdf_to_real(pdf_array_get(arr, 2)) + adj);
+ break;
+ case 4:
+ fz_buffer_printf(ctx, fzbuf, stroke?"%f %f %f %f K\n":"%f %f %f %f k\n",
+ pdf_to_real(pdf_array_get(arr, 0)),
+ pdf_to_real(pdf_array_get(arr, 1)),
+ pdf_to_real(pdf_array_get(arr, 2)),
+ pdf_to_real(pdf_array_get(arr, 3)));
+ break;
+ }
+}
+
+static void fzbuf_print_text(fz_context *ctx, fz_buffer *fzbuf, const fz_rect *clip, pdf_obj *col, font_info *font_rec, const fz_matrix *tm, char *text)
+{
+ fz_buffer_printf(ctx, fzbuf, fmt_q);
+ if (clip)
+ {
+ fz_buffer_printf(ctx, fzbuf, fmt_re, clip->x0, clip->y0, clip->x1 - clip->x0, clip->y1 - clip->y0);
+ fz_buffer_printf(ctx, fzbuf, fmt_W);
+ if (col)
+ {
+ fzbuf_print_color(ctx, fzbuf, col, 0, 0.0);
+ fz_buffer_printf(ctx, fzbuf, fmt_f);
+ }
+ else
+ {
+ fz_buffer_printf(ctx, fzbuf, fmt_n);
+ }
+ }
+
+ fz_buffer_printf(ctx, fzbuf, fmt_BT);
+
+ fzbuf_print_da(ctx, fzbuf, &font_rec->da_rec);
+
+ fz_buffer_printf(ctx, fzbuf, "\n");
+ if (tm)
+ fz_buffer_printf(ctx, fzbuf, fmt_Tm, tm->a, tm->b, tm->c, tm->d, tm->e, tm->f);
+
+ fz_buffer_cat_pdf_string(ctx, fzbuf, text);
+ fz_buffer_printf(ctx, fzbuf, fmt_Tj);
+ fz_buffer_printf(ctx, fzbuf, fmt_ET);
+ fz_buffer_printf(ctx, fzbuf, fmt_Q);
+}
+
+static fz_buffer *create_text_buffer(fz_context *ctx, const fz_rect *clip, text_widget_info *info, const fz_matrix *tm, char *text)
+{
+ fz_buffer *fzbuf = fz_new_buffer(ctx, 0);
+
+ fz_try(ctx)
+ {
+ fz_buffer_printf(ctx, fzbuf, fmt_Tx_BMC);
+ fzbuf_print_text(ctx, fzbuf, clip, info->col, &info->font_rec, tm, text);
+ fz_buffer_printf(ctx, fzbuf, fmt_EMC);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_buffer(ctx, fzbuf);
+ fz_rethrow(ctx);
+ }
+
+ return fzbuf;
+}
+
+static fz_buffer *create_aligned_text_buffer(pdf_document *doc, const fz_rect *clip, text_widget_info *info, const fz_matrix *tm, char *text)
+{
+ fz_context *ctx = doc->ctx;
+ fz_matrix atm = *tm;
+
+ if (info->q != Q_Left)
+ {
+ fz_rect rect;
+
+ measure_text(doc, &info->font_rec, tm, text, &rect);
+ atm.e -= info->q == Q_Right ? rect.x1 : (rect.x1 - rect.x0) / 2;
+ }
+
+ return create_text_buffer(ctx, clip, info, &atm, text);
+}
+
+static void measure_ascent_descent(pdf_document *doc, font_info *finf, char *text, float *ascent, float *descent)
+{
+ fz_context *ctx = doc->ctx;
+ char *testtext = NULL;
+ fz_rect bbox;
+ font_info tinf = *finf;
+
+ fz_var(testtext);
+ fz_try(ctx)
+ {
+ /* Heuristic: adding "My" to text will in most cases
+ * produce a measurement that will encompass all chars */
+ testtext = fz_malloc(ctx, strlen(text) + 3);
+ strcpy(testtext, "My");
+ strcat(testtext, text);
+ tinf.da_rec.font_size = 1;
+ measure_text(doc, &tinf, &fz_identity, testtext, &bbox);
+ *descent = -bbox.y0;
+ *ascent = bbox.y1;
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, testtext);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+typedef struct text_splitter_s
+{
+ font_info *info;
+ float width;
+ float height;
+ float scale;
+ float unscaled_width;
+ float fontsize;
+ char *text;
+ int done;
+ float x_orig;
+ float y_orig;
+ float x;
+ float x_end;
+ int text_start;
+ int text_end;
+ int max_lines;
+ int retry;
+} text_splitter;
+
+static void text_splitter_init(text_splitter *splitter, font_info *info, char *text, float width, float height, int variable)
+{
+ float fontsize = info->da_rec.font_size;
+
+ memset(splitter, 0, sizeof(*splitter));
+ splitter->info = info;
+ splitter->text = text;
+ splitter->width = width;
+ splitter->unscaled_width = width;
+ splitter->height = height;
+ splitter->fontsize = fontsize;
+ splitter->scale = 1.0;
+ /* RJW: The cast in the following line is important, as otherwise
+ * under MSVC in the variable = 0 case, splitter->max_lines becomes
+ * INT_MIN. */
+ splitter->max_lines = variable ? (int)(height/fontsize) : INT_MAX;
+}
+
+static void text_splitter_start_pass(text_splitter *splitter)
+{
+ splitter->text_end = 0;
+ splitter->x_orig = 0;
+ splitter->y_orig = 0;
+}
+
+static void text_splitter_start_line(text_splitter *splitter)
+{
+ splitter->x_end = 0;
+}
+
+static int text_splitter_layout(fz_context *ctx, text_splitter *splitter)
+{
+ char *text;
+ float room;
+ float stride;
+ int count;
+ int len;
+ float fontsize = splitter->info->da_rec.font_size;
+
+ splitter->x = splitter->x_end;
+ splitter->text_start = splitter->text_end;
+
+ text = splitter->text + splitter->text_start;
+ room = splitter->unscaled_width - splitter->x;
+
+ if (strchr("\r\n", text[0]))
+ {
+ /* Consume return chars and report end of line */
+ splitter->text_end += strspn(text, "\r\n");
+ splitter->text_start = splitter->text_end;
+ splitter->done = (splitter->text[splitter->text_end] == '\0');
+ return 0;
+ }
+ else if (text[0] == ' ')
+ {
+ /* Treat each space as a word */
+ len = 1;
+ }
+ else
+ {
+ len = 0;
+ while (text[len] != '\0' && !strchr(" \r\n", text[len]))
+ len ++;
+ }
+
+ stride = pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, len, room, &count);
+
+ /* If not a single char fits although the line is empty, then force one char */
+ if (count == 0 && splitter->x == 0.0)
+ stride = pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, 1, FLT_MAX, &count);
+
+ if (count < len && splitter->retry)
+ {
+ /* The word didn't fit and we are in retry mode. Work out the
+ * least additional scaling that may help */
+ float fitwidth; /* width if we force the word in */
+ float hstretchwidth; /* width if we just bump by 10% */
+ float vstretchwidth; /* width resulting from forcing in another line */
+ float bestwidth;
+
+ fitwidth = splitter->x +
+ pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, len, FLT_MAX, &count);
+ /* FIXME: temporary fiddle factor. Would be better to work in integers */
+ fitwidth *= 1.001f;
+
+ /* Stretching by 10% is worth trying only if processing the first word on the line */
+ hstretchwidth = splitter->x == 0.0
+ ? splitter->width * 1.1 / splitter->scale
+ : FLT_MAX;
+
+ vstretchwidth = splitter->width * (splitter->max_lines + 1) * splitter->fontsize
+ / splitter->height;
+
+ bestwidth = fz_min(fitwidth, fz_min(hstretchwidth, vstretchwidth));
+
+ if (bestwidth == vstretchwidth)
+ splitter->max_lines ++;
+
+ splitter->scale = splitter->width / bestwidth;
+ splitter->unscaled_width = bestwidth;
+
+ splitter->retry = 0;
+
+ /* Try again */
+ room = splitter->unscaled_width - splitter->x;
+ stride = pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, len, room, &count);
+ }
+
+ /* This is not the first word on the line. Best to give up on this line and push
+ * the word onto the next */
+ if (count < len && splitter->x > 0.0)
+ return 0;
+
+ splitter->text_end = splitter->text_start + count;
+ splitter->x_end = splitter->x + stride;
+ splitter->done = (splitter->text[splitter->text_end] == '\0');
+ return 1;
+}
+
+static void text_splitter_move(text_splitter *splitter, float newy, float *relx, float *rely)
+{
+ *relx = splitter->x - splitter->x_orig;
+ *rely = newy - splitter->y_orig;
+
+ splitter->x_orig = splitter->x;
+ splitter->y_orig = newy;
+}
+
+static void text_splitter_retry(text_splitter *splitter)
+{
+ if (splitter->retry)
+ {
+ /* Already tried expanding lines. Overflow must
+ * be caused by carriage control */
+ splitter->max_lines ++;
+ splitter->retry = 0;
+ splitter->unscaled_width = splitter->width * splitter->max_lines * splitter->fontsize
+ / splitter->height;
+ splitter->scale = splitter->width / splitter->unscaled_width;
+ }
+ else
+ {
+ splitter->retry = 1;
+ }
+}
+
+static void fzbuf_print_text_start(fz_context *ctx, fz_buffer *fzbuf, const fz_rect *clip, pdf_obj *col, font_info *font, const fz_matrix *tm)
+{
+ fz_buffer_printf(ctx, fzbuf, fmt_Tx_BMC);
+ fz_buffer_printf(ctx, fzbuf, fmt_q);
+
+ if (clip)
+ {
+ fz_buffer_printf(ctx, fzbuf, fmt_re, clip->x0, clip->y0, clip->x1 - clip->x0, clip->y1 - clip->y0);
+ fz_buffer_printf(ctx, fzbuf, fmt_W);
+ if (col)
+ {
+ fzbuf_print_color(ctx, fzbuf, col, 0, 0.0);
+ fz_buffer_printf(ctx, fzbuf, fmt_f);
+ }
+ else
+ {
+ fz_buffer_printf(ctx, fzbuf, fmt_n);
+ }
+ }
+
+ fz_buffer_printf(ctx, fzbuf, fmt_BT);
+
+ fzbuf_print_da(ctx, fzbuf, &font->da_rec);
+ fz_buffer_printf(ctx, fzbuf, "\n");
+
+ fz_buffer_printf(ctx, fzbuf, fmt_Tm, tm->a, tm->b, tm->c, tm->d, tm->e, tm->f);
+}
+
+static void fzbuf_print_text_end(fz_context *ctx, fz_buffer *fzbuf)
+{
+ fz_buffer_printf(ctx, fzbuf, fmt_ET);
+ fz_buffer_printf(ctx, fzbuf, fmt_Q);
+ fz_buffer_printf(ctx, fzbuf, fmt_EMC);
+}
+
+static void fzbuf_print_text_word(fz_context *ctx, fz_buffer *fzbuf, float x, float y, char *text, int count)
+{
+ int i;
+
+ fz_buffer_printf(ctx, fzbuf, fmt_Td, x, y);
+ fz_buffer_printf(ctx, fzbuf, "(");
+
+ for (i = 0; i < count; i++)
+ fz_buffer_printf(ctx, fzbuf, "%c", text[i]);
+
+ fz_buffer_printf(ctx, fzbuf, ") Tj\n");
+}
+
+static fz_buffer *create_text_appearance(pdf_document *doc, const fz_rect *bbox, const fz_matrix *oldtm, text_widget_info *info, char *text)
+{
+ fz_context *ctx = doc->ctx;
+ int fontsize;
+ int variable;
+ float height, width, full_width;
+ fz_buffer *fzbuf = NULL;
+ fz_buffer *fztmp = NULL;
+ fz_rect rect;
+ fz_rect tbox;
+ rect = *bbox;
+
+ if (rect.x1 - rect.x0 > 3.0 && rect.y1 - rect.y0 > 3.0)
+ {
+ rect.x0 += 1.0;
+ rect.x1 -= 1.0;
+ rect.y0 += 1.0;
+ rect.y1 -= 1.0;
+ }
+
+ height = rect.y1 - rect.y0;
+ width = rect.x1 - rect.x0;
+ full_width = bbox->x1 - bbox->x0;
+
+ fz_var(fzbuf);
+ fz_var(fztmp);
+ fz_try(ctx)
+ {
+ float ascent, descent;
+ fz_matrix tm;
+
+ variable = (info->font_rec.da_rec.font_size == 0);
+ fontsize = variable
+ ? (info->multiline ? 14.0 : floor(height))
+ : info->font_rec.da_rec.font_size;
+
+ info->font_rec.da_rec.font_size = fontsize;
+
+ measure_ascent_descent(doc, &info->font_rec, text, &ascent, &descent);
+
+ if (info->multiline)
+ {
+ text_splitter splitter;
+
+ text_splitter_init(&splitter, &info->font_rec, text, width, height, variable);
+
+ while (!splitter.done)
+ {
+ /* Try a layout pass */
+ int line = 0;
+
+ fz_drop_buffer(ctx, fztmp);
+ fztmp = NULL;
+ fztmp = fz_new_buffer(ctx, 0);
+
+ text_splitter_start_pass(&splitter);
+
+ /* Layout unscaled text to a scaled-up width, so that
+ * the scaled-down text will fit the unscaled width */
+
+ while (!splitter.done && line < splitter.max_lines)
+ {
+ /* Layout a line */
+ text_splitter_start_line(&splitter);
+
+ while (!splitter.done && text_splitter_layout(ctx, &splitter))
+ {
+ if (splitter.text[splitter.text_start] != ' ')
+ {
+ float x, y;
+ char *word = text+splitter.text_start;
+ int wordlen = splitter.text_end-splitter.text_start;
+
+ text_splitter_move(&splitter, -line*fontsize, &x, &y);
+ fzbuf_print_text_word(ctx, fztmp, x, y, word, wordlen);
+ }
+ }
+
+ line ++;
+ }
+
+ if (!splitter.done)
+ text_splitter_retry(&splitter);
+ }
+
+ fzbuf = fz_new_buffer(ctx, 0);
+
+ tm.a = splitter.scale;
+ tm.b = 0.0;
+ tm.c = 0.0;
+ tm.d = splitter.scale;
+ tm.e = rect.x0;
+ tm.f = rect.y1 - (1.0+ascent-descent)*fontsize*splitter.scale/2.0;
+
+ fzbuf_print_text_start(ctx, fzbuf, &rect, info->col, &info->font_rec, &tm);
+
+ fz_buffer_cat(ctx, fzbuf, fztmp);
+
+ fzbuf_print_text_end(ctx, fzbuf);
+ }
+ else if (info->comb)
+ {
+ int i, n = fz_mini((int)strlen(text), info->max_len);
+ float comb_width = full_width/info->max_len;
+ float char_width = pdf_text_stride(ctx, info->font_rec.font, fontsize, (unsigned char *)"M", 1, FLT_MAX, NULL);
+ float init_skip = (comb_width - char_width)/2.0;
+
+ fz_translate(&tm, rect.x0, rect.y1 - (height+(ascent-descent)*fontsize)/2.0);
+
+ fzbuf = fz_new_buffer(ctx, 0);
+
+ fzbuf_print_text_start(ctx, fzbuf, &rect, info->col, &info->font_rec, &tm);
+
+ for (i = 0; i < n; i++)
+ fzbuf_print_text_word(ctx, fzbuf, i == 0 ? init_skip : comb_width, 0.0, text+i, 1);
+
+ fzbuf_print_text_end(ctx, fzbuf);
+ }
+ else
+ {
+ if (oldtm)
+ {
+ tm = *oldtm;
+ }
+ else
+ {
+ fz_translate(&tm, rect.x0, rect.y1 - (height+(ascent-descent)*fontsize)/2.0);
+
+ switch (info->q)
+ {
+ case Q_Right: tm.e += width; break;
+ case Q_Cent: tm.e += width/2; break;
+ }
+ }
+
+ if (variable)
+ {
+ measure_text(doc, &info->font_rec, &tm, text, &tbox);
+
+ if (tbox.x1 - tbox.x0 > width)
+ {
+ /* Scale the text to fit but use the same offset
+ * to keep the baseline constant */
+ tm.a *= width / (tbox.x1 - tbox.x0);
+ tm.d *= width / (tbox.x1 - tbox.x0);
+ }
+ }
+
+ fzbuf = create_aligned_text_buffer(doc, &rect, info, &tm, text);
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, fztmp);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_buffer(ctx, fzbuf);
+ fz_rethrow(ctx);
+ }
+
+ return fzbuf;
+}
+
+static void update_marked_content(pdf_document *doc, pdf_xobject *form, fz_buffer *fzbuf)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_token tok;
+ pdf_lexbuf lbuf;
+ fz_stream *str_outer = NULL;
+ fz_stream *str_inner = NULL;
+ unsigned char *buf;
+ int len;
+ fz_buffer *newbuf = NULL;
+
+ pdf_lexbuf_init(ctx, &lbuf, PDF_LEXBUF_SMALL);
+
+ fz_var(str_outer);
+ fz_var(str_inner);
+ fz_var(newbuf);
+ fz_try(ctx)
+ {
+ int bmc_found;
+ int first = 1;
+
+ newbuf = fz_new_buffer(ctx, 0);
+ str_outer = pdf_open_stream(doc, pdf_to_num(form->contents), pdf_to_gen(form->contents));
+ len = fz_buffer_storage(ctx, fzbuf, &buf);
+ str_inner = fz_open_memory(ctx, buf, len);
+
+ /* Copy the existing appearance stream to newbuf while looking for BMC */
+ for (tok = pdf_lex(str_outer, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_outer, &lbuf))
+ {
+ if (first)
+ first = 0;
+ else
+ fz_buffer_printf(ctx, newbuf, " ");
+
+ pdf_print_token(ctx, newbuf, tok, &lbuf);
+ if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "BMC"))
+ break;
+ }
+
+ bmc_found = (tok != PDF_TOK_EOF);
+
+ if (bmc_found)
+ {
+ /* Drop Tx BMC from the replacement appearance stream */
+ (void)pdf_lex(str_inner, &lbuf);
+ (void)pdf_lex(str_inner, &lbuf);
+ }
+
+ /* Copy the replacement appearance stream to newbuf */
+ for (tok = pdf_lex(str_inner, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_inner, &lbuf))
+ {
+ fz_buffer_printf(ctx, newbuf, " ");
+ pdf_print_token(ctx, newbuf, tok, &lbuf);
+ }
+
+ if (bmc_found)
+ {
+ /* Drop the rest of the existing appearance stream until EMC found */
+ for (tok = pdf_lex(str_outer, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_outer, &lbuf))
+ {
+ if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "EMC"))
+ break;
+ }
+
+ /* Copy the rest of the existing appearance stream to newbuf */
+ for (tok = pdf_lex(str_outer, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_outer, &lbuf))
+ {
+ fz_buffer_printf(ctx, newbuf, " ");
+ pdf_print_token(ctx, newbuf, tok, &lbuf);
+ }
+ }
+
+ /* Use newbuf in place of the existing appearance stream */
+ pdf_update_xobject_contents(doc, form, newbuf);
+ }
+ fz_always(ctx)
+ {
+ fz_close(str_outer);
+ fz_close(str_inner);
+ fz_drop_buffer(ctx, newbuf);
+ pdf_lexbuf_fin(&lbuf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static int get_matrix(pdf_document *doc, pdf_xobject *form, int q, fz_matrix *mt)
+{
+ fz_context *ctx = doc->ctx;
+ int found = 0;
+ pdf_lexbuf lbuf;
+ fz_stream *str;
+
+ str = pdf_open_stream(doc, pdf_to_num(form->contents), pdf_to_gen(form->contents));
+
+ pdf_lexbuf_init(ctx, &lbuf, PDF_LEXBUF_SMALL);
+
+ fz_try(ctx)
+ {
+ int tok;
+ float coefs[MATRIX_COEFS];
+ int coef_i = 0;
+
+ /* Look for the text matrix Tm in the stream */
+ for (tok = pdf_lex(str, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str, &lbuf))
+ {
+ if (tok == PDF_TOK_INT || tok == PDF_TOK_REAL)
+ {
+ if (coef_i >= MATRIX_COEFS)
+ {
+ int i;
+ for (i = 0; i < MATRIX_COEFS-1; i++)
+ coefs[i] = coefs[i+1];
+
+ coef_i = MATRIX_COEFS-1;
+ }
+
+ coefs[coef_i++] = tok == PDF_TOK_INT ? lbuf.i : lbuf.f;
+ }
+ else
+ {
+ if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "Tm") && coef_i == MATRIX_COEFS)
+ {
+ found = 1;
+ mt->a = coefs[0];
+ mt->b = coefs[1];
+ mt->c = coefs[2];
+ mt->d = coefs[3];
+ mt->e = coefs[4];
+ mt->f = coefs[5];
+ }
+
+ coef_i = 0;
+ }
+ }
+
+ if (found)
+ {
+ fz_rect bbox;
+ pdf_to_rect(ctx, pdf_dict_gets(form->contents, "BBox"), &bbox);
+
+ switch (q)
+ {
+ case Q_Left:
+ mt->e = bbox.x0 + 1;
+ break;
+
+ case Q_Cent:
+ mt->e = (bbox.x1 - bbox.x0) / 2;
+ break;
+
+ case Q_Right:
+ mt->e = bbox.x1 - 1;
+ break;
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(str);
+ pdf_lexbuf_fin(&lbuf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ return found;
+}
+
+static void update_field_value(fz_context *ctx, pdf_obj *obj, char *text)
+{
+ pdf_obj *sobj = NULL;
+ pdf_obj *grp;
+
+ if (!text)
+ text = "";
+
+ /* All fields of the same name should be updated, so
+ * set the value at the head of the group */
+ grp = find_head_of_field_group(obj);
+ if (grp)
+ obj = grp;
+
+ fz_var(sobj);
+ fz_try(ctx)
+ {
+ sobj = pdf_new_string(ctx, text, strlen(text));
+ pdf_dict_puts(obj, "V", sobj);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(sobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ pdf_field_mark_dirty(ctx, obj);
+}
+
+static pdf_xobject *load_or_create_form(pdf_document *doc, pdf_obj *obj, fz_rect *rect)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *ap = NULL;
+ fz_matrix mat;
+ int rot;
+ pdf_obj *formobj = NULL;
+ pdf_xobject *form = NULL;
+ char *dn = "N";
+ fz_buffer *fzbuf = NULL;
+ int create_form = 0;
+
+ fz_var(formobj);
+ fz_var(form);
+ fz_var(fzbuf);
+ fz_try(ctx)
+ {
+ rot = pdf_to_int(pdf_dict_getp(obj, "MK/R"));
+ pdf_to_rect(ctx, pdf_dict_gets(obj, "Rect"), rect);
+ rect->x1 -= rect->x0;
+ rect->y1 -= rect->y0;
+ rect->x0 = rect->y0 = 0;
+ account_for_rot(rect, &mat, rot);
+
+ ap = pdf_dict_gets(obj, "AP");
+ if (ap == NULL)
+ {
+ ap = pdf_new_dict(ctx, 1);
+ pdf_dict_puts_drop(obj, "AP", ap);
+ }
+
+ formobj = pdf_dict_gets(ap, dn);
+ if (formobj == NULL)
+ {
+ formobj = pdf_new_xobject(doc, rect, &mat);
+ pdf_dict_puts_drop(ap, dn, formobj);
+ create_form = 1;
+ }
+
+ form = pdf_load_xobject(doc, formobj);
+ if (create_form)
+ {
+ fzbuf = fz_new_buffer(ctx, 1);
+ pdf_update_xobject_contents(doc, form, fzbuf);
+ }
+
+ copy_resources(form->resources, pdf_get_inheritable(doc, obj, "DR"));
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, fzbuf);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_xobject(ctx, form);
+ fz_rethrow(ctx);
+ }
+
+ return form;
+}
+
+static char *to_font_encoding(fz_context *ctx, pdf_font_desc *font, char *utf8)
+{
+ int i;
+ int needs_converting = 0;
+
+ /* Temporay partial solution. We are using a slow lookup in the conversion
+ * below, so we avoid performing the conversion unnecessarily. We check for
+ * top-bit-set chars, and convert only if they are present. We should also
+ * check that the font encoding is one that agrees with utf8 from 0 to 7f,
+ * but for now we get away without doing so. This is after all an improvement
+ * on just strdup */
+ for (i = 0; utf8[i] != '\0'; i++)
+ {
+ if (utf8[i] & 0x80)
+ needs_converting = 1;
+ }
+
+ /* Even if we need to convert, we cannot do so if the font has no cid_to_ucs mapping */
+ if (needs_converting && font->cid_to_ucs)
+ {
+ char *buf = fz_malloc(ctx, strlen(utf8) + 1);
+ char *bufp = buf;
+
+ fz_try(ctx)
+ {
+ while(*utf8)
+ {
+ if (*utf8 & 0x80)
+ {
+ int rune;
+
+ utf8 += fz_chartorune(&rune, utf8);
+
+ /* Slow search for the cid that maps to the unicode value held in 'rune" */
+ for (i = 0; i < font->cid_to_ucs_len && font->cid_to_ucs[i] != rune; i++)
+ ;
+
+ /* If found store the cid */
+ if (i < font->cid_to_ucs_len)
+ *bufp++ = i;
+ }
+ else
+ {
+ *bufp++ = *utf8++;
+ }
+ }
+
+ *bufp = '\0';
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, buf);
+ fz_rethrow(ctx);
+ }
+
+ return buf;
+ }
+ else
+ {
+ /* If either no conversion is needed or the font has no cid_to_ucs
+ * mapping then leave unconverted, although in the latter case the result
+ * is likely incorrect */
+ return fz_strdup(ctx, utf8);
+ }
+}
+
+static void update_text_appearance(pdf_document *doc, pdf_obj *obj, char *eventValue)
+{
+ fz_context *ctx = doc->ctx;
+ text_widget_info info;
+ pdf_xobject *form = NULL;
+ fz_buffer *fzbuf = NULL;
+ fz_matrix tm;
+ fz_rect rect;
+ int has_tm;
+ char *text = NULL;
+
+ memset(&info, 0, sizeof(info));
+
+ fz_var(info);
+ fz_var(form);
+ fz_var(fzbuf);
+ fz_var(text);
+ fz_try(ctx)
+ {
+ get_text_widget_info(doc, obj, &info);
+
+ if (eventValue)
+ text = to_font_encoding(ctx, info.font_rec.font, eventValue);
+ else
+ text = pdf_field_value(doc, obj);
+
+ form = load_or_create_form(doc, obj, &rect);
+
+ has_tm = get_matrix(doc, form, info.q, &tm);
+ fzbuf = create_text_appearance(doc, &form->bbox, has_tm ? &tm : NULL, &info,
+ text?text:"");
+ update_marked_content(doc, form, fzbuf);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, text);
+ pdf_drop_xobject(ctx, form);
+ fz_drop_buffer(ctx, fzbuf);
+ font_info_fin(ctx, &info.font_rec);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "update_text_appearance failed");
+ }
+}
+
+static void update_combobox_appearance(pdf_document *doc, pdf_obj *obj)
+{
+ fz_context *ctx = doc->ctx;
+ text_widget_info info;
+ pdf_xobject *form = NULL;
+ fz_buffer *fzbuf = NULL;
+ fz_matrix tm;
+ fz_rect rect;
+ int has_tm;
+ pdf_obj *val;
+ char *text;
+
+ memset(&info, 0, sizeof(info));
+
+ fz_var(info);
+ fz_var(form);
+ fz_var(fzbuf);
+ fz_try(ctx)
+ {
+ get_text_widget_info(doc, obj, &info);
+
+ val = pdf_get_inheritable(doc, obj, "V");
+
+ if (pdf_is_array(val))
+ val = pdf_array_get(val, 0);
+
+ text = pdf_to_str_buf(val);
+
+ if (!text)
+ text = "";
+
+ form = load_or_create_form(doc, obj, &rect);
+
+ has_tm = get_matrix(doc, form, info.q, &tm);
+ fzbuf = create_text_appearance(doc, &form->bbox, has_tm ? &tm : NULL, &info,
+ text?text:"");
+ update_marked_content(doc, form, fzbuf);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_xobject(ctx, form);
+ fz_drop_buffer(ctx, fzbuf);
+ font_info_fin(ctx, &info.font_rec);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "update_text_appearance failed");
+ }
+}
+
+static int get_border_style(pdf_obj *obj)
+{
+ char *sname = pdf_to_name(pdf_dict_getp(obj, "BS/S"));
+
+ if (!strcmp(sname, "D"))
+ return BS_Dashed;
+ else if (!strcmp(sname, "B"))
+ return BS_Beveled;
+ else if (!strcmp(sname, "I"))
+ return BS_Inset;
+ else if (!strcmp(sname, "U"))
+ return BS_Underline;
+ else
+ return BS_Solid;
+}
+
+static float get_border_width(pdf_obj *obj)
+{
+ float w = pdf_to_real(pdf_dict_getp(obj, "BS/W"));
+ return w == 0.0 ? 1.0 : w;
+}
+
+static void update_pushbutton_appearance(pdf_document *doc, pdf_obj *obj)
+{
+ fz_context *ctx = doc->ctx;
+ fz_rect rect;
+ pdf_xobject *form = NULL;
+ fz_buffer *fzbuf = NULL;
+ pdf_obj *tobj = NULL;
+ font_info font_rec;
+ int bstyle;
+ float bwidth;
+ float btotal;
+
+ memset(&font_rec, 0, sizeof(font_rec));
+
+ fz_var(font_rec);
+ fz_var(form);
+ fz_var(fzbuf);
+ fz_try(ctx)
+ {
+ form = load_or_create_form(doc, obj, &rect);
+ fzbuf = fz_new_buffer(ctx, 0);
+ tobj = pdf_dict_getp(obj, "MK/BG");
+ if (pdf_is_array(tobj))
+ {
+ fzbuf_print_color(ctx, fzbuf, tobj, 0, 0.0);
+ fz_buffer_printf(ctx, fzbuf, fmt_re,
+ rect.x0, rect.y0, rect.x1, rect.y1);
+ fz_buffer_printf(ctx, fzbuf, fmt_f);
+ }
+ bstyle = get_border_style(obj);
+ bwidth = get_border_width(obj);
+ btotal = bwidth;
+ if (bstyle == BS_Beveled || bstyle == BS_Inset)
+ {
+ btotal += bwidth;
+
+ if (bstyle == BS_Beveled)
+ fz_buffer_printf(ctx, fzbuf, fmt_g, 1.0);
+ else
+ fz_buffer_printf(ctx, fzbuf, fmt_g, 0.33);
+ fz_buffer_printf(ctx, fzbuf, fmt_m, bwidth, bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, bwidth, rect.y1 - bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - bwidth, rect.y1 - bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - 2 * bwidth, rect.y1 - 2 * bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, 2 * bwidth, rect.y1 - 2 * bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, 2 * bwidth, 2 * bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_f);
+ if (bstyle == BS_Beveled)
+ fzbuf_print_color(ctx, fzbuf, tobj, 0, -0.25);
+ else
+ fz_buffer_printf(ctx, fzbuf, fmt_g, 0.66);
+ fz_buffer_printf(ctx, fzbuf, fmt_m, rect.x1 - bwidth, rect.y1 - bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - bwidth, bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, bwidth, bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, 2 * bwidth, 2 * bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - 2 * bwidth, 2 * bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - 2 * bwidth, rect.y1 - 2 * bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_f);
+ }
+
+ tobj = pdf_dict_getp(obj, "MK/BC");
+ if (tobj)
+ {
+ fzbuf_print_color(ctx, fzbuf, tobj, 1, 0.0);
+ fz_buffer_printf(ctx, fzbuf, fmt_w, bwidth);
+ fz_buffer_printf(ctx, fzbuf, fmt_re,
+ bwidth/2, bwidth/2,
+ rect.x1 -bwidth/2, rect.y1 - bwidth/2);
+ fz_buffer_printf(ctx, fzbuf, fmt_s);
+ }
+
+ tobj = pdf_dict_getp(obj, "MK/CA");
+ if (tobj)
+ {
+ fz_rect clip = rect;
+ fz_rect bounds;
+ fz_matrix mat;
+ char *da = pdf_to_str_buf(pdf_get_inheritable(doc, obj, "DA"));
+ char *text = pdf_to_str_buf(tobj);
+
+ clip.x0 += btotal;
+ clip.y0 += btotal;
+ clip.x1 -= btotal;
+ clip.y1 -= btotal;
+
+ get_font_info(doc, form->resources, da, &font_rec);
+ measure_text(doc, &font_rec, &fz_identity, text, &bounds);
+ fz_translate(&mat, (rect.x1 - bounds.x1)/2, (rect.y1 - bounds.y1)/2);
+ fzbuf_print_text(ctx, fzbuf, &clip, NULL, &font_rec, &mat, text);
+ }
+
+ pdf_update_xobject_contents(doc, form, fzbuf);
+ }
+ fz_always(ctx)
+ {
+ font_info_fin(ctx, &font_rec);
+ fz_drop_buffer(ctx, fzbuf);
+ pdf_drop_xobject(ctx, form);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static pdf_obj *find_field(pdf_obj *dict, char *name, int len)
+{
+ pdf_obj *field;
+
+ int i, n = pdf_array_len(dict);
+
+ for (i = 0; i < n; i++)
+ {
+ char *part;
+
+ field = pdf_array_get(dict, i);
+ part = pdf_to_str_buf(pdf_dict_gets(field, "T"));
+ if (strlen(part) == len && !memcmp(part, name, len))
+ return field;
+ }
+
+ return NULL;
+}
+
+pdf_obj *pdf_lookup_field(pdf_obj *form, char *name)
+{
+ char *dot;
+ char *namep;
+ pdf_obj *dict = NULL;
+ int len;
+
+ /* Process the fully qualified field name which has
+ * the partial names delimited by '.'. Pretend there
+ * was a preceding '.' to simplify the loop */
+ dot = name - 1;
+
+ while (dot && form)
+ {
+ namep = dot + 1;
+ dot = strchr(namep, '.');
+ len = dot ? dot - namep : strlen(namep);
+ dict = find_field(form, namep, len);
+ if (dot)
+ form = pdf_dict_gets(dict, "Kids");
+ }
+
+ return dict;
+}
+
+static void reset_field(pdf_document *doc, pdf_obj *field)
+{
+ fz_context *ctx = doc->ctx;
+ /* Set V to DV whereever DV is present, and delete V where DV is not.
+ * FIXME: we assume for now that V has not been set unequal
+ * to DV higher in the hierarchy than "field".
+ *
+ * At the bottom of the hierarchy we may find widget annotations
+ * that aren't also fields, but DV and V will not be present in their
+ * dictionaries, and attempts to remove V will be harmless. */
+ pdf_obj *dv = pdf_dict_gets(field, "DV");
+ pdf_obj *kids = pdf_dict_gets(field, "Kids");
+
+ if (dv)
+ pdf_dict_puts(field, "V", dv);
+ else
+ pdf_dict_dels(field, "V");
+
+ if (kids == NULL)
+ {
+ /* The leaves of the tree are widget annotations
+ * In some cases we need to update the appearance state;
+ * in others we need to mark the field as dirty so that
+ * the appearance stream will be regenerated. */
+ switch (pdf_field_type(doc, field))
+ {
+ case PDF_WIDGET_TYPE_RADIOBUTTON:
+ case PDF_WIDGET_TYPE_CHECKBOX:
+ {
+ pdf_obj *leafv = pdf_get_inheritable(doc, field, "V");
+
+ if (leafv)
+ pdf_keep_obj(leafv);
+ else
+ leafv = pdf_new_name(ctx, "Off");
+
+ fz_try(ctx)
+ {
+ pdf_dict_puts(field, "AS", leafv);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(leafv);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ break;
+
+ case PDF_WIDGET_TYPE_PUSHBUTTON:
+ break;
+
+ default:
+ pdf_field_mark_dirty(ctx, field);
+ break;
+ }
+ }
+
+ doc->dirty = 1;
+}
+
+void pdf_field_reset(pdf_document *doc, pdf_obj *field)
+{
+ pdf_obj *kids = pdf_dict_gets(field, "Kids");
+
+ reset_field(doc, field);
+
+ if (kids)
+ {
+ int i, n = pdf_array_len(kids);
+
+ for (i = 0; i < n; i++)
+ pdf_field_reset(doc, pdf_array_get(kids, i));
+ }
+}
+
+static void add_field_hierarchy_to_array(pdf_obj *array, pdf_obj *field)
+{
+ pdf_obj *kids = pdf_dict_gets(field, "Kids");
+ pdf_obj *exclude = pdf_dict_gets(field, "Exclude");
+
+ if (exclude)
+ return;
+
+ pdf_array_push(array, field);
+
+ if (kids)
+ {
+ int i, n = pdf_array_len(kids);
+
+ for (i = 0; i < n; i++)
+ add_field_hierarchy_to_array(array, pdf_array_get(kids, i));
+ }
+}
+
+/*
+ When resetting or submitting a form, the fields to act upon are defined
+ by an array of either field references or field names, plus a flag determining
+ whether to act upon the fields in the array, or all fields other than those in
+ the array. specified_fields interprets this information and produces the array
+ of fields to be acted upon.
+*/
+static pdf_obj *specified_fields(pdf_document *doc, pdf_obj *fields, int exclude)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *form = pdf_dict_getp(pdf_trailer(doc), "Root/AcroForm/Fields");
+ int i, n;
+ pdf_obj *result = pdf_new_array(ctx, 0);
+ pdf_obj *nil = NULL;
+
+ fz_var(nil);
+ fz_try(ctx)
+ {
+ /* The 'fields' array not being present signals that all fields
+ * should be acted upon, so handle it using the exclude case - excluding none */
+ if (exclude || !fields)
+ {
+ /* mark the fields we don't want to act upon */
+ nil = pdf_new_null(ctx);
+
+ n = pdf_array_len(fields);
+
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *field = pdf_array_get(fields, i);
+
+ if (pdf_is_string(field))
+ field = pdf_lookup_field(form, pdf_to_str_buf(field));
+
+ if (field)
+ pdf_dict_puts(field, "Exclude", nil);
+ }
+
+ /* Act upon all unmarked fields */
+ n = pdf_array_len(form);
+
+ for (i = 0; i < n; i++)
+ add_field_hierarchy_to_array(result, pdf_array_get(form, i));
+
+ /* Unmark the marked fields */
+ n = pdf_array_len(fields);
+
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *field = pdf_array_get(fields, i);
+
+ if (pdf_is_string(field))
+ field = pdf_lookup_field(form, pdf_to_str_buf(field));
+
+ if (field)
+ pdf_dict_dels(field, "Exclude");
+ }
+ }
+ else
+ {
+ n = pdf_array_len(fields);
+
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *field = pdf_array_get(fields, i);
+
+ if (pdf_is_string(field))
+ field = pdf_lookup_field(form, pdf_to_str_buf(field));
+
+ if (field)
+ add_field_hierarchy_to_array(result, field);
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(nil);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(result);
+ fz_rethrow(ctx);
+ }
+
+ return result;
+}
+
+static void reset_form(pdf_document *doc, pdf_obj *fields, int exclude)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *sfields = specified_fields(doc, fields, exclude);
+
+ fz_try(ctx)
+ {
+ int i, n = pdf_array_len(sfields);
+
+ for (i = 0; i < n; i++)
+ reset_field(doc, pdf_array_get(sfields, i));
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(sfields);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void execute_action(pdf_document *doc, pdf_obj *obj, pdf_obj *a)
+{
+ fz_context *ctx = doc->ctx;
+ if (a)
+ {
+ char *type = pdf_to_name(pdf_dict_gets(a, "S"));
+
+ if (!strcmp(type, "JavaScript"))
+ {
+ pdf_obj *js = pdf_dict_gets(a, "JS");
+ if (js)
+ {
+ char *code = pdf_to_utf8(doc, js);
+ fz_try(ctx)
+ {
+ pdf_js_execute(doc->js, code);
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, code);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ }
+ else if (!strcmp(type, "ResetForm"))
+ {
+ reset_form(doc, pdf_dict_gets(a, "Fields"), pdf_to_int(pdf_dict_gets(a, "Flags")) & 1);
+ }
+ else if (!strcmp(type, "Named"))
+ {
+ char *name = pdf_to_name(pdf_dict_gets(a, "N"));
+
+ if (!strcmp(name, "Print"))
+ pdf_event_issue_print(doc);
+ }
+ }
+}
+
+static void update_text_markup_appearance(pdf_document *doc, pdf_obj *annot, fz_annot_type type)
+{
+ float color[3];
+ float alpha;
+ float line_height;
+ float line_thickness;
+
+ switch (type)
+ {
+ case FZ_ANNOT_HIGHLIGHT:
+ color[0] = 1.0;
+ color[1] = 1.0;
+ color[2] = 0.0;
+ alpha = 0.5;
+ line_thickness = 1.0;
+ line_height = 0.5;
+ break;
+ case FZ_ANNOT_UNDERLINE:
+ color[0] = 0.0;
+ color[1] = 0.0;
+ color[2] = 1.0;
+ alpha = 1.0;
+ line_thickness = LINE_THICKNESS;
+ line_height = UNDERLINE_HEIGHT;
+ break;
+ case FZ_ANNOT_STRIKEOUT:
+ color[0] = 1.0;
+ color[1] = 0.0;
+ color[2] = 0.0;
+ alpha = 1.0;
+ line_thickness = LINE_THICKNESS;
+ line_height = STRIKE_HEIGHT;
+ break;
+ default:
+ return;
+ }
+
+ pdf_set_markup_obj_appearance(doc, annot, color, alpha, line_thickness, line_height);
+}
+
+void pdf_update_appearance(pdf_document *doc, pdf_obj *obj)
+{
+ if (!pdf_dict_gets(obj, "AP") || pdf_dict_gets(obj, "Dirty"))
+ {
+ fz_annot_type type = pdf_annot_obj_type(obj);
+ switch (type)
+ {
+ case FZ_ANNOT_WIDGET:
+ switch (pdf_field_type(doc, obj))
+ {
+ case PDF_WIDGET_TYPE_TEXT:
+ {
+ pdf_obj *formatting = pdf_dict_getp(obj, "AA/F");
+ if (formatting && doc->js)
+ {
+ /* Apply formatting */
+ pdf_js_event e;
+
+ e.target = obj;
+ e.value = pdf_field_value(doc, obj);
+ pdf_js_setup_event(doc->js, &e);
+ execute_action(doc, obj, formatting);
+ /* Update appearance from JS event.value */
+ update_text_appearance(doc, obj, pdf_js_get_event(doc->js)->value);
+ }
+ else
+ {
+ /* Update appearance from field value */
+ update_text_appearance(doc, obj, NULL);
+ }
+ }
+ break;
+ case PDF_WIDGET_TYPE_PUSHBUTTON:
+ update_pushbutton_appearance(doc, obj);
+ break;
+ case PDF_WIDGET_TYPE_LISTBOX:
+ case PDF_WIDGET_TYPE_COMBOBOX:
+ /* Treating listbox and combobox identically for now,
+ * and the behaviour is most appropriate for a combobox */
+ update_combobox_appearance(doc, obj);
+ break;
+ }
+ break;
+ case FZ_ANNOT_STRIKEOUT:
+ case FZ_ANNOT_UNDERLINE:
+ case FZ_ANNOT_HIGHLIGHT:
+ update_text_markup_appearance(doc, obj, type);
+ break;
+ case FZ_ANNOT_INK:
+ pdf_set_ink_obj_appearance(doc, obj);
+ break;
+ default:
+ break;
+ }
+
+ pdf_dict_dels(obj, "Dirty");
+ }
+}
+
+static void execute_action_chain(pdf_document *doc, pdf_obj *obj)
+{
+ pdf_obj *a = pdf_dict_gets(obj, "A");
+ pdf_js_event e;
+
+ e.target = obj;
+ e.value = "";
+ pdf_js_setup_event(doc->js, &e);
+
+ while (a)
+ {
+ execute_action(doc, obj, a);
+ a = pdf_dict_gets(a, "Next");
+ }
+}
+
+static void execute_additional_action(pdf_document *doc, pdf_obj *obj, char *act)
+{
+ pdf_obj *a = pdf_dict_getp(obj, act);
+
+ if (a)
+ {
+ pdf_js_event e;
+
+ e.target = obj;
+ e.value = "";
+ pdf_js_setup_event(doc->js, &e);
+ execute_action(doc, obj, a);
+ }
+}
+
+static void check_off(fz_context *ctx, pdf_obj *obj)
+{
+ pdf_obj *off = NULL;
+
+ fz_var(off);
+ fz_try(ctx);
+ {
+ off = pdf_new_name(ctx, "Off");
+ pdf_dict_puts(obj, "AS", off);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(off);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void set_check(fz_context *ctx, pdf_obj *chk, char *name)
+{
+ pdf_obj *n = pdf_dict_getp(chk, "AP/N");
+ pdf_obj *val = NULL;
+
+ fz_var(val);
+ fz_try(ctx)
+ {
+ /* If name is a possible value of this check
+ * box then use it, otherwise use "Off" */
+ if (pdf_dict_gets(n, name))
+ val = pdf_new_name(ctx, name);
+ else
+ val = pdf_new_name(ctx, "Off");
+
+ pdf_dict_puts(chk, "AS", val);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+/* Set the values of all fields in a group defined by a node
+ * in the hierarchy */
+static void set_check_grp(fz_context *ctx, pdf_obj *grp, char *val)
+{
+ pdf_obj *kids = pdf_dict_gets(grp, "Kids");
+
+ if (kids == NULL)
+ {
+ set_check(ctx, grp, val);
+ }
+ else
+ {
+ int i, n = pdf_array_len(kids);
+
+ for (i = 0; i < n; i++)
+ set_check_grp(ctx, pdf_array_get(kids, i), val);
+ }
+}
+
+static void recalculate(pdf_document *doc)
+{
+ fz_context *ctx = doc->ctx;
+
+ if (doc->recalculating)
+ return;
+
+ doc->recalculating = 1;
+ fz_try(ctx)
+ {
+ pdf_obj *co = pdf_dict_getp(pdf_trailer(doc), "Root/AcroForm/CO");
+
+ if (co && doc->js)
+ {
+ int i, n = pdf_array_len(co);
+
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *field = pdf_array_get(co, i);
+ pdf_obj *calc = pdf_dict_getp(field, "AA/C");
+
+ if (calc)
+ {
+ pdf_js_event e;
+
+ e.target = field;
+ e.value = pdf_field_value(doc, field);
+ pdf_js_setup_event(doc->js, &e);
+ execute_action(doc, field, calc);
+ /* A calculate action, updates event.value. We need
+ * to place the value in the field */
+ update_field_value(doc->ctx, field, pdf_js_get_event(doc->js)->value);
+ }
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ doc->recalculating = 0;
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void toggle_check_box(pdf_document *doc, pdf_obj *obj)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *as = pdf_dict_gets(obj, "AS");
+ int ff = pdf_get_field_flags(doc, obj);
+ int radio = ((ff & (Ff_Pushbutton|Ff_Radio)) == Ff_Radio);
+ char *val = NULL;
+ pdf_obj *grp = radio ? pdf_dict_gets(obj, "Parent") : find_head_of_field_group(obj);
+
+ if (!grp)
+ grp = obj;
+
+ if (as && strcmp(pdf_to_name(as), "Off"))
+ {
+ /* "as" neither missing nor set to Off. Set it to Off, unless
+ * this is a non-toggle-off radio button. */
+ if ((ff & (Ff_Pushbutton|Ff_NoToggleToOff|Ff_Radio)) != (Ff_NoToggleToOff|Ff_Radio))
+ {
+ check_off(ctx, obj);
+ val = "Off";
+ }
+ }
+ else
+ {
+ pdf_obj *n, *key = NULL;
+ int len, i;
+
+ n = pdf_dict_getp(obj, "AP/N");
+
+ /* Look for a key that isn't "Off" */
+ len = pdf_dict_len(n);
+ for (i = 0; i < len; i++)
+ {
+ key = pdf_dict_get_key(n, i);
+ if (pdf_is_name(key) && strcmp(pdf_to_name(key), "Off"))
+ break;
+ }
+
+ /* If we found no alternative value to Off then we have no value to use */
+ if (!key)
+ return;
+
+ val = pdf_to_name(key);
+
+ if (radio)
+ {
+ /* For radio buttons, first turn off all buttons in the group and
+ * then set the one that was clicked */
+ pdf_obj *kids = pdf_dict_gets(grp, "Kids");
+
+ len = pdf_array_len(kids);
+ for (i = 0; i < len; i++)
+ check_off(ctx, pdf_array_get(kids, i));
+
+ pdf_dict_puts(obj, "AS", key);
+ }
+ else
+ {
+ /* For check boxes, we have located the node of the field hierarchy
+ * below which all fields share a name with the clicked one. Set
+ * all to the same value. This may cause the group to act like
+ * radio buttons, if each have distinct "On" values */
+ if (grp)
+ set_check_grp(doc->ctx, grp, val);
+ else
+ set_check(doc->ctx, obj, val);
+ }
+ }
+
+ if (val && grp)
+ {
+ pdf_obj *v = NULL;
+
+ fz_var(v);
+ fz_try(ctx)
+ {
+ v = pdf_new_string(ctx, val, strlen(val));
+ pdf_dict_puts(grp, "V", v);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(v);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ recalculate(doc);
+ }
+}
+
+int pdf_has_unsaved_changes(pdf_document *doc)
+{
+ return doc->dirty;
+}
+
+int pdf_pass_event(pdf_document *doc, pdf_page *page, pdf_ui_event *ui_event)
+{
+ pdf_annot *annot;
+ pdf_hotspot *hp = &doc->hotspot;
+ fz_point *pt = &(ui_event->event.pointer.pt);
+ int changed = 0;
+
+ for (annot = page->annots; annot; annot = annot->next)
+ {
+ if (pt->x >= annot->pagerect.x0 && pt->x <= annot->pagerect.x1)
+ if (pt->y >= annot->pagerect.y0 && pt->y <= annot->pagerect.y1)
+ break;
+ }
+
+ if (annot)
+ {
+ int f = pdf_to_int(pdf_dict_gets(annot->obj, "F"));
+
+ if (f & (F_Hidden|F_NoView))
+ annot = NULL;
+ }
+
+ switch (ui_event->etype)
+ {
+ case PDF_EVENT_TYPE_POINTER:
+ {
+ switch (ui_event->event.pointer.ptype)
+ {
+ case PDF_POINTER_DOWN:
+ if (doc->focus_obj)
+ {
+ /* Execute the blur action */
+ execute_additional_action(doc, doc->focus_obj, "AA/Bl");
+ doc->focus = NULL;
+ pdf_drop_obj(doc->focus_obj);
+ doc->focus_obj = NULL;
+ }
+
+ if (annot)
+ {
+ doc->focus = annot;
+ doc->focus_obj = pdf_keep_obj(annot->obj);
+
+ hp->num = pdf_to_num(annot->obj);
+ hp->gen = pdf_to_gen(annot->obj);
+ hp->state = HOTSPOT_POINTER_DOWN;
+ changed = 1;
+ /* Exectute the down and focus actions */
+ execute_additional_action(doc, annot->obj, "AA/Fo");
+ execute_additional_action(doc, annot->obj, "AA/D");
+ }
+ break;
+
+ case PDF_POINTER_UP:
+ if (hp->state != 0)
+ changed = 1;
+
+ hp->num = 0;
+ hp->gen = 0;
+ hp->state = 0;
+
+ if (annot)
+ {
+ switch (annot->widget_type)
+ {
+ case PDF_WIDGET_TYPE_RADIOBUTTON:
+ case PDF_WIDGET_TYPE_CHECKBOX:
+ /* FIXME: treating radio buttons like check boxes, for now */
+ toggle_check_box(doc, annot->obj);
+ changed = 1;
+ break;
+ }
+
+ /* Execute the up action */
+ execute_additional_action(doc, annot->obj, "AA/U");
+ /* Execute the main action chain */
+ execute_action_chain(doc, annot->obj);
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ return changed;
+}
+
+void pdf_update_page(pdf_document *doc, pdf_page *page)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_annot *annot;
+
+ /* Reset changed_annots to empty */
+ page->changed_annots = NULL;
+
+ /*
+ Free all annots in tmp_annots, since these were
+ referenced only from changed_annots.
+ */
+ if (page->tmp_annots)
+ {
+ pdf_free_annot(ctx, page->tmp_annots);
+ page->tmp_annots = NULL;
+ }
+
+ /* Add all changed annots to the list */
+ for (annot = page->annots; annot; annot = annot->next)
+ {
+ pdf_xobject *ap = pdf_keep_xobject(ctx, annot->ap);
+ int ap_iteration = annot->ap_iteration;
+
+ fz_try(ctx)
+ {
+ pdf_update_annot(doc, annot);
+
+ if ((ap != annot->ap || ap_iteration != annot->ap_iteration))
+ {
+ annot->next_changed = page->changed_annots;
+ page->changed_annots = annot;
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_xobject(ctx, ap);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+
+ /*
+ Add all deleted annots to the list, since these also
+ warrant a screen update
+ */
+ for (annot = page->deleted_annots; annot; annot = annot->next)
+ {
+ annot->next_changed = page->changed_annots;
+ page->changed_annots = annot;
+ }
+
+ /*
+ Move deleted_annots to tmp_annots to keep them separate
+ from any future deleted ones. They cannot yet be freed
+ since they are linked into changed_annots
+ */
+ page->tmp_annots = page->deleted_annots;
+ page->deleted_annots = NULL;
+}
+
+pdf_annot *pdf_poll_changed_annot(pdf_document *idoc, pdf_page *page)
+{
+ pdf_annot *annot = page->changed_annots;
+
+ if (annot)
+ page->changed_annots = annot->next_changed;
+
+ return annot;
+}
+
+pdf_widget *pdf_focused_widget(pdf_document *doc)
+{
+ return (pdf_widget *)doc->focus;
+}
+
+pdf_widget *pdf_first_widget(pdf_document *doc, pdf_page *page)
+{
+ pdf_annot *annot = page->annots;
+
+ while (annot && annot->widget_type == PDF_WIDGET_TYPE_NOT_WIDGET)
+ annot = annot->next;
+
+ return (pdf_widget *)annot;
+}
+
+pdf_widget *pdf_next_widget(pdf_widget *previous)
+{
+ pdf_annot *annot = (pdf_annot *)previous;
+
+ if (annot)
+ annot = annot->next;
+
+ while (annot && annot->widget_type == PDF_WIDGET_TYPE_NOT_WIDGET)
+ annot = annot->next;
+
+ return (pdf_widget *)annot;
+}
+
+int pdf_widget_get_type(pdf_widget *widget)
+{
+ pdf_annot *annot = (pdf_annot *)widget;
+ return annot->widget_type;
+}
+
+char *pdf_field_value(pdf_document *doc, pdf_obj *field)
+{
+ return get_string_or_stream(doc, pdf_get_inheritable(doc, field, "V"));
+}
+
+static int set_text_field_value(pdf_document *doc, pdf_obj *field, char *text)
+{
+ pdf_obj *v = pdf_dict_getp(field, "AA/V");
+
+ if (v && doc->js)
+ {
+ pdf_js_event e;
+
+ e.target = field;
+ e.value = text;
+ pdf_js_setup_event(doc->js, &e);
+ execute_action(doc, field, v);
+
+ if (!pdf_js_get_event(doc->js)->rc)
+ return 0;
+
+ text = pdf_js_get_event(doc->js)->value;
+ }
+
+ doc->dirty = 1;
+ update_field_value(doc->ctx, field, text);
+
+ return 1;
+}
+
+static void update_checkbox_selector(pdf_document *doc, pdf_obj *field, char *val)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *kids = pdf_dict_gets(field, "Kids");
+
+ if (kids)
+ {
+ int i, n = pdf_array_len(kids);
+
+ for (i = 0; i < n; i++)
+ update_checkbox_selector(doc, pdf_array_get(kids, i), val);
+ }
+ else
+ {
+ pdf_obj *n = pdf_dict_getp(field, "AP/N");
+ pdf_obj *oval = NULL;
+
+ fz_var(oval);
+ fz_try(ctx)
+ {
+ if (pdf_dict_gets(n, val))
+ oval = pdf_new_name(ctx, val);
+ else
+ oval = pdf_new_name(ctx, "Off");
+
+ pdf_dict_puts(field, "AS", oval);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(oval);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+}
+
+static int set_checkbox_value(pdf_document *doc, pdf_obj *field, char *val)
+{
+ update_checkbox_selector(doc, field, val);
+ update_field_value(doc->ctx, field, val);
+ return 1;
+}
+
+int pdf_field_set_value(pdf_document *doc, pdf_obj *field, char *text)
+{
+ int res = 0;
+
+ switch (pdf_field_type(doc, field))
+ {
+ case PDF_WIDGET_TYPE_TEXT:
+ res = set_text_field_value(doc, field, text);
+ break;
+
+ case PDF_WIDGET_TYPE_CHECKBOX:
+ case PDF_WIDGET_TYPE_RADIOBUTTON:
+ res = set_checkbox_value(doc, field, text);
+ break;
+
+ default:
+ /* text updater will do in most cases */
+ update_field_value(doc->ctx, field, text);
+ res = 1;
+ break;
+ }
+
+ recalculate(doc);
+
+ return res;
+}
+
+char *pdf_field_border_style(pdf_document *doc, pdf_obj *field)
+{
+ char *bs = pdf_to_name(pdf_dict_getp(field, "BS/S"));
+
+ switch (*bs)
+ {
+ case 'S': return "Solid";
+ case 'D': return "Dashed";
+ case 'B': return "Beveled";
+ case 'I': return "Inset";
+ case 'U': return "Underline";
+ }
+
+ return "Solid";
+}
+
+void pdf_field_set_border_style(pdf_document *doc, pdf_obj *field, char *text)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *val = NULL;
+
+ if (!strcmp(text, "Solid"))
+ val = pdf_new_name(ctx, "S");
+ else if (!strcmp(text, "Dashed"))
+ val = pdf_new_name(ctx, "D");
+ else if (!strcmp(text, "Beveled"))
+ val = pdf_new_name(ctx, "B");
+ else if (!strcmp(text, "Inset"))
+ val = pdf_new_name(ctx, "I");
+ else if (!strcmp(text, "Underline"))
+ val = pdf_new_name(ctx, "U");
+ else
+ return;
+
+ fz_try(ctx);
+ {
+ pdf_dict_putp(field, "BS/S", val);
+ pdf_field_mark_dirty(ctx, field);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void pdf_field_set_button_caption(pdf_document *doc, pdf_obj *field, char *text)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *val = pdf_new_string(ctx, text, strlen(text));
+
+ fz_try(ctx);
+ {
+ if (pdf_field_type(doc, field) == PDF_WIDGET_TYPE_PUSHBUTTON)
+ {
+ pdf_dict_putp(field, "MK/CA", val);
+ pdf_field_mark_dirty(ctx, field);
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+int pdf_field_display(pdf_document *doc, pdf_obj *field)
+{
+ pdf_obj *kids;
+ int f, res = Display_Visible;
+
+ /* Base response on first of children. Not ideal,
+ * but not clear how to handle children with
+ * differing values */
+ while ((kids = pdf_dict_gets(field, "Kids")) != NULL)
+ field = pdf_array_get(kids, 0);
+
+ f = pdf_to_int(pdf_dict_gets(field, "F"));
+
+ if (f & F_Hidden)
+ {
+ res = Display_Hidden;
+ }
+ else if (f & F_Print)
+ {
+ if (f & F_NoView)
+ res = Display_NoView;
+ }
+ else
+ {
+ if (f & F_NoView)
+ res = Display_Hidden;
+ else
+ res = Display_NoPrint;
+ }
+
+ return res;
+}
+
+/*
+ * get the field name in a char buffer that has spare room to
+ * add more characters at the end.
+ */
+static char *get_field_name(pdf_document *doc, pdf_obj *field, int spare)
+{
+ fz_context *ctx = doc->ctx;
+ char *res = NULL;
+ pdf_obj *parent = pdf_dict_gets(field, "Parent");
+ char *lname = pdf_to_str_buf(pdf_dict_gets(field, "T"));
+ int llen = strlen(lname);
+
+ /*
+ * If we found a name at this point in the field hierarchy
+ * then we'll need extra space for it and a dot
+ */
+ if (llen)
+ spare += llen+1;
+
+ if (parent)
+ {
+ res = get_field_name(doc, parent, spare);
+ }
+ else
+ {
+ res = fz_malloc(ctx, spare+1);
+ res[0] = 0;
+ }
+
+ if (llen)
+ {
+ if (res[0])
+ strcat(res, ".");
+
+ strcat(res, lname);
+ }
+
+ return res;
+}
+
+char *pdf_field_name(pdf_document *doc, pdf_obj *field)
+{
+ return get_field_name(doc, field, 0);
+}
+
+void pdf_field_set_display(pdf_document *doc, pdf_obj *field, int d)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_obj *kids = pdf_dict_gets(field, "Kids");
+
+ if (!kids)
+ {
+ int mask = (F_Hidden|F_Print|F_NoView);
+ int f = pdf_to_int(pdf_dict_gets(field, "F")) & ~mask;
+ pdf_obj *fo = NULL;
+
+ switch (d)
+ {
+ case Display_Visible:
+ f |= F_Print;
+ break;
+ case Display_Hidden:
+ f |= F_Hidden;
+ break;
+ case Display_NoView:
+ f |= (F_Print|F_NoView);
+ break;
+ case Display_NoPrint:
+ break;
+ }
+
+ fz_var(fo);
+ fz_try(ctx)
+ {
+ fo = pdf_new_int(ctx, f);
+ pdf_dict_puts(field, "F", fo);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(fo);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ else
+ {
+ int i, n = pdf_array_len(kids);
+
+ for (i = 0; i < n; i++)
+ pdf_field_set_display(doc, pdf_array_get(kids, i), d);
+ }
+}
+
+void pdf_field_set_fill_color(pdf_document *doc, pdf_obj *field, pdf_obj *col)
+{
+ /* col == NULL mean transparent, but we can simply pass it on as with
+ * non-NULL values because pdf_dict_putp interprets a NULL value as
+ * delete */
+ pdf_dict_putp(field, "MK/BG", col);
+ pdf_field_mark_dirty(doc->ctx, field);
+}
+
+void pdf_field_set_text_color(pdf_document *doc, pdf_obj *field, pdf_obj *col)
+{
+ fz_context *ctx = doc->ctx;
+ da_info di;
+ fz_buffer *fzbuf = NULL;
+ char *da = pdf_to_str_buf(pdf_get_inheritable(doc, field, "DA"));
+ unsigned char *buf;
+ int len;
+ pdf_obj *daobj = NULL;
+
+ memset(&di, 0, sizeof(di));
+
+ fz_var(fzbuf);
+ fz_var(di);
+ fz_var(daobj);
+ fz_try(ctx)
+ {
+ int i;
+
+ parse_da(ctx, da, &di);
+ di.col_size = pdf_array_len(col);
+
+ len = fz_mini(di.col_size, nelem(di.col));
+ for (i = 0; i < len; i++)
+ di.col[i] = pdf_to_real(pdf_array_get(col, i));
+
+ fzbuf = fz_new_buffer(ctx, 0);
+ fzbuf_print_da(ctx, fzbuf, &di);
+ len = fz_buffer_storage(ctx, fzbuf, &buf);
+ daobj = pdf_new_string(ctx, (char *)buf, len);
+ pdf_dict_puts(field, "DA", daobj);
+ pdf_field_mark_dirty(ctx, field);
+ }
+ fz_always(ctx)
+ {
+ da_info_fin(ctx, &di);
+ fz_drop_buffer(ctx, fzbuf);
+ pdf_drop_obj(daobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "%s", fz_caught_message(ctx));
+ }
+}
+
+fz_rect *pdf_bound_widget(pdf_widget *widget, fz_rect *rect)
+{
+ pdf_annot *annot = (pdf_annot *)widget;
+
+ if (rect == NULL)
+ return NULL;
+ *rect = annot->pagerect;
+
+ return rect;
+}
+
+char *pdf_text_widget_text(pdf_document *doc, pdf_widget *tw)
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+ fz_context *ctx = doc->ctx;
+ char *text = NULL;
+
+ fz_var(text);
+ fz_try(ctx)
+ {
+ text = pdf_field_value(doc, annot->obj);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "failed allocation in fz_text_widget_text");
+ }
+
+ return text;
+}
+
+int pdf_text_widget_max_len(pdf_document *doc, pdf_widget *tw)
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+
+ return pdf_to_int(pdf_get_inheritable(doc, annot->obj, "MaxLen"));
+}
+
+int pdf_text_widget_content_type(pdf_document *doc, pdf_widget *tw)
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+ fz_context *ctx = doc->ctx;
+ char *code = NULL;
+ int type = PDF_WIDGET_CONTENT_UNRESTRAINED;
+
+ fz_var(code);
+ fz_try(ctx)
+ {
+ code = get_string_or_stream(doc, pdf_dict_getp(annot->obj, "AA/F/JS"));
+ if (code)
+ {
+ if (strstr(code, "AFNumber_Format"))
+ type = PDF_WIDGET_CONTENT_NUMBER;
+ else if (strstr(code, "AFSpecial_Format"))
+ type = PDF_WIDGET_CONTENT_SPECIAL;
+ else if (strstr(code, "AFDate_FormatEx"))
+ type = PDF_WIDGET_CONTENT_DATE;
+ else if (strstr(code, "AFTime_FormatEx"))
+ type = PDF_WIDGET_CONTENT_TIME;
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, code);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "failure in fz_text_widget_content_type");
+ }
+
+ return type;
+}
+
+static int run_keystroke(pdf_document *doc, pdf_obj *field, char **text)
+{
+ pdf_obj *k = pdf_dict_getp(field, "AA/K");
+
+ if (k && doc->js)
+ {
+ pdf_js_event e;
+
+ e.target = field;
+ e.value = *text;
+ pdf_js_setup_event(doc->js, &e);
+ execute_action(doc, field, k);
+
+ if (!pdf_js_get_event(doc->js)->rc)
+ return 0;
+
+ *text = pdf_js_get_event(doc->js)->value;
+ }
+
+ return 1;
+}
+
+int pdf_text_widget_set_text(pdf_document *doc, pdf_widget *tw, char *text)
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+ fz_context *ctx = doc->ctx;
+ int accepted = 0;
+
+ fz_try(ctx)
+ {
+ accepted = run_keystroke(doc, annot->obj, &text);
+ if (accepted)
+ accepted = pdf_field_set_value(doc, annot->obj, text);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "fz_text_widget_set_text failed");
+ }
+
+ return accepted;
+}
+
+int pdf_choice_widget_options(pdf_document *doc, pdf_widget *tw, char *opts[])
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+ pdf_obj *optarr;
+ int i, n;
+
+ if (!annot)
+ return 0;
+
+ optarr = pdf_dict_gets(annot->obj, "Opt");
+ n = pdf_array_len(optarr);
+
+ if (opts)
+ {
+ for (i = 0; i < n; i++)
+ {
+ opts[i] = pdf_to_str_buf(pdf_array_get(optarr, i));
+ }
+ }
+
+ return n;
+}
+
+int pdf_choice_widget_is_multiselect(pdf_document *doc, pdf_widget *tw)
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+
+ if (!annot) return 0;
+
+ switch (pdf_field_type(doc, annot->obj))
+ {
+ case PDF_WIDGET_TYPE_LISTBOX:
+ case PDF_WIDGET_TYPE_COMBOBOX:
+ return (pdf_get_field_flags(doc, annot->obj) & Ff_MultiSelect) != 0;
+ default:
+ return 0;
+ }
+}
+
+int pdf_choice_widget_value(pdf_document *doc, pdf_widget *tw, char *opts[])
+{
+ pdf_annot *annot = (pdf_annot *)tw;
+ pdf_obj *optarr;
+ int i, n;
+
+ if (!annot)
+ return 0;
+
+ optarr = pdf_dict_gets(annot->obj, "V");
+
+ if (pdf_is_string(optarr))
+ {
+ if (opts)
+ opts[0] = pdf_to_str_buf(optarr);
+
+ return 1;
+ }
+ else
+ {
+ n = pdf_array_len(optarr);
+
+ if (opts)
+ {
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *elem = pdf_array_get(optarr, i);
+
+ if (pdf_is_array(elem))
+ elem = pdf_array_get(elem, 1);
+
+ opts[i] = pdf_to_str_buf(elem);
+ }
+ }
+
+ return n;
+ }
+}
+
+void pdf_choice_widget_set_value(pdf_document *doc, pdf_widget *tw, int n, char *opts[])
+{
+ fz_context *ctx = doc->ctx;
+ pdf_annot *annot = (pdf_annot *)tw;
+ pdf_obj *optarr = NULL, *opt = NULL;
+ int i;
+
+ if (!annot)
+ return;
+
+ fz_var(optarr);
+ fz_var(opt);
+ fz_try(ctx)
+ {
+ if (n != 1)
+ {
+ optarr = pdf_new_array(ctx, n);
+
+ for (i = 0; i < n; i++)
+ {
+ opt = pdf_new_string(ctx, opts[i], strlen(opts[i]));
+ pdf_array_push(optarr, opt);
+ pdf_drop_obj(opt);
+ opt = NULL;
+ }
+
+ pdf_dict_puts(annot->obj, "V", optarr);
+ pdf_drop_obj(optarr);
+ }
+ else
+ {
+ opt = pdf_new_string(ctx, opts[0], strlen(opts[0]));
+ pdf_dict_puts(annot->obj, "V", opt);
+ pdf_drop_obj(opt);
+ }
+
+ /* FIXME: when n > 1, we should be regenerating the indexes */
+ pdf_dict_dels(annot->obj, "I");
+
+ pdf_field_mark_dirty(ctx, annot->obj);
+ doc->dirty = 1;
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(optarr);
+ pdf_drop_obj(opt);
+ fz_rethrow(ctx);
+ }
+}
+
+int pdf_signature_widget_byte_range(pdf_document *doc, pdf_widget *widget, int (*byte_range)[2])
+{
+ pdf_annot *annot = (pdf_annot *)widget;
+ pdf_obj *br = pdf_dict_getp(annot->obj, "V/ByteRange");
+ int i, n = pdf_array_len(br)/2;
+
+ if (byte_range)
+ {
+ for (i = 0; i < n; i++)
+ {
+ byte_range[i][0] = pdf_to_int(pdf_array_get(br, 2*i));
+ byte_range[i][1] = pdf_to_int(pdf_array_get(br, 2*i+1));
+ }
+ }
+
+ return n;
+}
+
+int pdf_signature_widget_contents(pdf_document *doc, pdf_widget *widget, char **contents)
+{
+ pdf_annot *annot = (pdf_annot *)widget;
+ pdf_obj *c = pdf_dict_getp(annot->obj, "V/Contents");
+ if (contents)
+ *contents = pdf_to_str_buf(c);
+ return pdf_to_str_len(c);
+}
diff --git a/source/pdf/pdf-function.c b/source/pdf/pdf-function.c
new file mode 100644
index 00000000..4771fbfd
--- /dev/null
+++ b/source/pdf/pdf-function.c
@@ -0,0 +1,1718 @@
+#include "mupdf/pdf.h"
+
+typedef struct psobj_s psobj;
+
+enum
+{
+ SAMPLE = 0,
+ EXPONENTIAL = 2,
+ STITCHING = 3,
+ POSTSCRIPT = 4
+};
+
+typedef struct pdf_function_s pdf_function;
+
+struct pdf_function_s
+{
+ fz_function base;
+ int type; /* 0=sample 2=exponential 3=stitching 4=postscript */
+ float domain[FZ_FN_MAXM][2]; /* even index : min value, odd index : max value */
+ float range[FZ_FN_MAXN][2]; /* even index : min value, odd index : max value */
+ int has_range;
+
+ union
+ {
+ struct {
+ unsigned short bps;
+ int size[FZ_FN_MAXM];
+ float encode[FZ_FN_MAXM][2];
+ float decode[FZ_FN_MAXN][2];
+ float *samples;
+ } sa;
+
+ struct {
+ float n;
+ float c0[FZ_FN_MAXN];
+ float c1[FZ_FN_MAXN];
+ } e;
+
+ struct {
+ int k;
+ fz_function **funcs; /* k */
+ float *bounds; /* k - 1 */
+ float *encode; /* k * 2 */
+ } st;
+
+ struct {
+ psobj *code;
+ int cap;
+ } p;
+ } u;
+};
+
+#define RADIAN 57.2957795
+
+static inline float lerp(float x, float xmin, float xmax, float ymin, float ymax)
+{
+ if (xmin == xmax)
+ return ymin;
+ if (ymin == ymax)
+ return ymin;
+ return ymin + (x - xmin) * (ymax - ymin) / (xmax - xmin);
+}
+
+/*
+ * PostScript calculator
+ */
+
+enum { PS_BOOL, PS_INT, PS_REAL, PS_OPERATOR, PS_BLOCK };
+
+enum
+{
+ PS_OP_ABS, PS_OP_ADD, PS_OP_AND, PS_OP_ATAN, PS_OP_BITSHIFT,
+ PS_OP_CEILING, PS_OP_COPY, PS_OP_COS, PS_OP_CVI, PS_OP_CVR,
+ PS_OP_DIV, PS_OP_DUP, PS_OP_EQ, PS_OP_EXCH, PS_OP_EXP,
+ PS_OP_FALSE, PS_OP_FLOOR, PS_OP_GE, PS_OP_GT, PS_OP_IDIV, PS_OP_IF,
+ PS_OP_IFELSE, PS_OP_INDEX, PS_OP_LE, PS_OP_LN, PS_OP_LOG, PS_OP_LT,
+ PS_OP_MOD, PS_OP_MUL, PS_OP_NE, PS_OP_NEG, PS_OP_NOT, PS_OP_OR,
+ PS_OP_POP, PS_OP_RETURN, PS_OP_ROLL, PS_OP_ROUND, PS_OP_SIN,
+ PS_OP_SQRT, PS_OP_SUB, PS_OP_TRUE, PS_OP_TRUNCATE, PS_OP_XOR
+};
+
+static char *ps_op_names[] =
+{
+ "abs", "add", "and", "atan", "bitshift", "ceiling", "copy",
+ "cos", "cvi", "cvr", "div", "dup", "eq", "exch", "exp",
+ "false", "floor", "ge", "gt", "idiv", "if", "ifelse", "index", "le", "ln",
+ "log", "lt", "mod", "mul", "ne", "neg", "not", "or", "pop", "return",
+ "roll", "round", "sin", "sqrt", "sub", "true", "truncate", "xor"
+};
+
+struct psobj_s
+{
+ int type;
+ union
+ {
+ int b; /* boolean (stack only) */
+ int i; /* integer (stack and code) */
+ float f; /* real (stack and code) */
+ int op; /* operator (code only) */
+ int block; /* if/ifelse block pointer (code only) */
+ } u;
+};
+
+typedef struct ps_stack_s ps_stack;
+
+struct ps_stack_s
+{
+ psobj stack[100];
+ int sp;
+};
+
+#ifndef NDEBUG
+void
+pdf_debug_ps_stack(ps_stack *st)
+{
+ int i;
+
+ printf("stack: ");
+
+ for (i = 0; i < st->sp; i++)
+ {
+ switch (st->stack[i].type)
+ {
+ case PS_BOOL:
+ if (st->stack[i].u.b)
+ printf("true ");
+ else
+ printf("false ");
+ break;
+
+ case PS_INT:
+ printf("%d ", st->stack[i].u.i);
+ break;
+
+ case PS_REAL:
+ printf("%g ", st->stack[i].u.f);
+ break;
+ }
+ }
+ printf("\n");
+
+}
+#endif
+
+static void
+ps_init_stack(ps_stack *st)
+{
+ memset(st->stack, 0, sizeof(st->stack));
+ st->sp = 0;
+}
+
+static inline int ps_overflow(ps_stack *st, int n)
+{
+ return n < 0 || st->sp + n >= nelem(st->stack);
+}
+
+static inline int ps_underflow(ps_stack *st, int n)
+{
+ return n < 0 || st->sp - n < 0;
+}
+
+static inline int ps_is_type(ps_stack *st, int t)
+{
+ return !ps_underflow(st, 1) && st->stack[st->sp - 1].type == t;
+}
+
+static inline int ps_is_type2(ps_stack *st, int t)
+{
+ return !ps_underflow(st, 2) && st->stack[st->sp - 1].type == t && st->stack[st->sp - 2].type == t;
+}
+
+static void
+ps_push_bool(ps_stack *st, int b)
+{
+ if (!ps_overflow(st, 1))
+ {
+ st->stack[st->sp].type = PS_BOOL;
+ st->stack[st->sp].u.b = b;
+ st->sp++;
+ }
+}
+
+static void
+ps_push_int(ps_stack *st, int n)
+{
+ if (!ps_overflow(st, 1))
+ {
+ st->stack[st->sp].type = PS_INT;
+ st->stack[st->sp].u.i = n;
+ st->sp++;
+ }
+}
+
+static void
+ps_push_real(ps_stack *st, float n)
+{
+ if (!ps_overflow(st, 1))
+ {
+ st->stack[st->sp].type = PS_REAL;
+ if (isnan(n))
+ {
+ /* Push 1.0, as it's a small known value that won't
+ * cause a divide by 0. Same reason as in fz_atof. */
+ n = 1.0;
+ }
+ st->stack[st->sp].u.f = fz_clamp(n, -FLT_MAX, FLT_MAX);
+ st->sp++;
+ }
+}
+
+static int
+ps_pop_bool(ps_stack *st)
+{
+ if (!ps_underflow(st, 1))
+ {
+ if (ps_is_type(st, PS_BOOL))
+ return st->stack[--st->sp].u.b;
+ }
+ return 0;
+}
+
+static int
+ps_pop_int(ps_stack *st)
+{
+ if (!ps_underflow(st, 1))
+ {
+ if (ps_is_type(st, PS_INT))
+ return st->stack[--st->sp].u.i;
+ if (ps_is_type(st, PS_REAL))
+ return st->stack[--st->sp].u.f;
+ }
+ return 0;
+}
+
+static float
+ps_pop_real(ps_stack *st)
+{
+ if (!ps_underflow(st, 1))
+ {
+ if (ps_is_type(st, PS_INT))
+ return st->stack[--st->sp].u.i;
+ if (ps_is_type(st, PS_REAL))
+ return st->stack[--st->sp].u.f;
+ }
+ return 0;
+}
+
+static void
+ps_copy(ps_stack *st, int n)
+{
+ if (!ps_underflow(st, n) && !ps_overflow(st, n))
+ {
+ memcpy(st->stack + st->sp, st->stack + st->sp - n, n * sizeof(psobj));
+ st->sp += n;
+ }
+}
+
+static void
+ps_roll(ps_stack *st, int n, int j)
+{
+ psobj tmp;
+ int i;
+
+ if (ps_underflow(st, n) || j == 0 || n == 0)
+ return;
+
+ if (j >= 0)
+ {
+ j %= n;
+ }
+ else
+ {
+ j = -j % n;
+ if (j != 0)
+ j = n - j;
+ }
+
+ for (i = 0; i < j; i++)
+ {
+ tmp = st->stack[st->sp - 1];
+ memmove(st->stack + st->sp - n + 1, st->stack + st->sp - n, n * sizeof(psobj));
+ st->stack[st->sp - n] = tmp;
+ }
+}
+
+static void
+ps_index(ps_stack *st, int n)
+{
+ if (!ps_overflow(st, 1) && !ps_underflow(st, n))
+ {
+ st->stack[st->sp] = st->stack[st->sp - n - 1];
+ st->sp++;
+ }
+}
+
+static void
+ps_run(fz_context *ctx, psobj *code, ps_stack *st, int pc)
+{
+ int i1, i2;
+ float r1, r2;
+ int b1, b2;
+
+ while (1)
+ {
+ switch (code[pc].type)
+ {
+ case PS_INT:
+ ps_push_int(st, code[pc++].u.i);
+ break;
+
+ case PS_REAL:
+ ps_push_real(st, code[pc++].u.f);
+ break;
+
+ case PS_OPERATOR:
+ switch (code[pc++].u.op)
+ {
+ case PS_OP_ABS:
+ if (ps_is_type(st, PS_INT))
+ ps_push_int(st, abs(ps_pop_int(st)));
+ else
+ ps_push_real(st, fabsf(ps_pop_real(st)));
+ break;
+
+ case PS_OP_ADD:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_int(st, i1 + i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_real(st, r1 + r2);
+ }
+ break;
+
+ case PS_OP_AND:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_int(st, i1 & i2);
+ }
+ else {
+ b2 = ps_pop_bool(st);
+ b1 = ps_pop_bool(st);
+ ps_push_bool(st, b1 && b2);
+ }
+ break;
+
+ case PS_OP_ATAN:
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ r1 = atan2f(r1, r2) * RADIAN;
+ if (r1 < 0)
+ r1 += 360;
+ ps_push_real(st, r1);
+ break;
+
+ case PS_OP_BITSHIFT:
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ if (i2 > 0 && i2 < 8 * sizeof (i2))
+ ps_push_int(st, i1 << i2);
+ else if (i2 < 0 && i2 > -8 * (int)sizeof (i2))
+ ps_push_int(st, (int)((unsigned int)i1 >> -i2));
+ else
+ ps_push_int(st, i1);
+ break;
+
+ case PS_OP_CEILING:
+ r1 = ps_pop_real(st);
+ ps_push_real(st, ceilf(r1));
+ break;
+
+ case PS_OP_COPY:
+ ps_copy(st, ps_pop_int(st));
+ break;
+
+ case PS_OP_COS:
+ r1 = ps_pop_real(st);
+ ps_push_real(st, cosf(r1/RADIAN));
+ break;
+
+ case PS_OP_CVI:
+ ps_push_int(st, ps_pop_int(st));
+ break;
+
+ case PS_OP_CVR:
+ ps_push_real(st, ps_pop_real(st));
+ break;
+
+ case PS_OP_DIV:
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ if (fabsf(r2) >= FLT_EPSILON)
+ ps_push_real(st, r1 / r2);
+ else
+ ps_push_real(st, DIV_BY_ZERO(r1, r2, -FLT_MAX, FLT_MAX));
+ break;
+
+ case PS_OP_DUP:
+ ps_copy(st, 1);
+ break;
+
+ case PS_OP_EQ:
+ if (ps_is_type2(st, PS_BOOL)) {
+ b2 = ps_pop_bool(st);
+ b1 = ps_pop_bool(st);
+ ps_push_bool(st, b1 == b2);
+ }
+ else if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_bool(st, i1 == i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_bool(st, r1 == r2);
+ }
+ break;
+
+ case PS_OP_EXCH:
+ ps_roll(st, 2, 1);
+ break;
+
+ case PS_OP_EXP:
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_real(st, powf(r1, r2));
+ break;
+
+ case PS_OP_FALSE:
+ ps_push_bool(st, 0);
+ break;
+
+ case PS_OP_FLOOR:
+ r1 = ps_pop_real(st);
+ ps_push_real(st, floorf(r1));
+ break;
+
+ case PS_OP_GE:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_bool(st, i1 >= i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_bool(st, r1 >= r2);
+ }
+ break;
+
+ case PS_OP_GT:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_bool(st, i1 > i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_bool(st, r1 > r2);
+ }
+ break;
+
+ case PS_OP_IDIV:
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ if (i2 != 0)
+ ps_push_int(st, i1 / i2);
+ else
+ ps_push_int(st, DIV_BY_ZERO(i1, i2, INT_MIN, INT_MAX));
+ break;
+
+ case PS_OP_INDEX:
+ ps_index(st, ps_pop_int(st));
+ break;
+
+ case PS_OP_LE:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_bool(st, i1 <= i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_bool(st, r1 <= r2);
+ }
+ break;
+
+ case PS_OP_LN:
+ r1 = ps_pop_real(st);
+ /* Bug 692941 - logf as separate statement */
+ r2 = logf(r1);
+ ps_push_real(st, r2);
+ break;
+
+ case PS_OP_LOG:
+ r1 = ps_pop_real(st);
+ ps_push_real(st, log10f(r1));
+ break;
+
+ case PS_OP_LT:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_bool(st, i1 < i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_bool(st, r1 < r2);
+ }
+ break;
+
+ case PS_OP_MOD:
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ if (i2 != 0)
+ ps_push_int(st, i1 % i2);
+ else
+ ps_push_int(st, DIV_BY_ZERO(i1, i2, INT_MIN, INT_MAX));
+ break;
+
+ case PS_OP_MUL:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_int(st, i1 * i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_real(st, r1 * r2);
+ }
+ break;
+
+ case PS_OP_NE:
+ if (ps_is_type2(st, PS_BOOL)) {
+ b2 = ps_pop_bool(st);
+ b1 = ps_pop_bool(st);
+ ps_push_bool(st, b1 != b2);
+ }
+ else if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_bool(st, i1 != i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_bool(st, r1 != r2);
+ }
+ break;
+
+ case PS_OP_NEG:
+ if (ps_is_type(st, PS_INT))
+ ps_push_int(st, -ps_pop_int(st));
+ else
+ ps_push_real(st, -ps_pop_real(st));
+ break;
+
+ case PS_OP_NOT:
+ if (ps_is_type(st, PS_BOOL))
+ ps_push_bool(st, !ps_pop_bool(st));
+ else
+ ps_push_int(st, ~ps_pop_int(st));
+ break;
+
+ case PS_OP_OR:
+ if (ps_is_type2(st, PS_BOOL)) {
+ b2 = ps_pop_bool(st);
+ b1 = ps_pop_bool(st);
+ ps_push_bool(st, b1 || b2);
+ }
+ else {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_int(st, i1 | i2);
+ }
+ break;
+
+ case PS_OP_POP:
+ if (!ps_underflow(st, 1))
+ st->sp--;
+ break;
+
+ case PS_OP_ROLL:
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_roll(st, i1, i2);
+ break;
+
+ case PS_OP_ROUND:
+ if (!ps_is_type(st, PS_INT)) {
+ r1 = ps_pop_real(st);
+ ps_push_real(st, (r1 >= 0) ? floorf(r1 + 0.5f) : ceilf(r1 - 0.5f));
+ }
+ break;
+
+ case PS_OP_SIN:
+ r1 = ps_pop_real(st);
+ ps_push_real(st, sinf(r1/RADIAN));
+ break;
+
+ case PS_OP_SQRT:
+ r1 = ps_pop_real(st);
+ ps_push_real(st, sqrtf(r1));
+ break;
+
+ case PS_OP_SUB:
+ if (ps_is_type2(st, PS_INT)) {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_int(st, i1 - i2);
+ }
+ else {
+ r2 = ps_pop_real(st);
+ r1 = ps_pop_real(st);
+ ps_push_real(st, r1 - r2);
+ }
+ break;
+
+ case PS_OP_TRUE:
+ ps_push_bool(st, 1);
+ break;
+
+ case PS_OP_TRUNCATE:
+ if (!ps_is_type(st, PS_INT)) {
+ r1 = ps_pop_real(st);
+ ps_push_real(st, (r1 >= 0) ? floorf(r1) : ceilf(r1));
+ }
+ break;
+
+ case PS_OP_XOR:
+ if (ps_is_type2(st, PS_BOOL)) {
+ b2 = ps_pop_bool(st);
+ b1 = ps_pop_bool(st);
+ ps_push_bool(st, b1 ^ b2);
+ }
+ else {
+ i2 = ps_pop_int(st);
+ i1 = ps_pop_int(st);
+ ps_push_int(st, i1 ^ i2);
+ }
+ break;
+
+ case PS_OP_IF:
+ b1 = ps_pop_bool(st);
+ if (b1)
+ ps_run(ctx, code, st, code[pc + 1].u.block);
+ pc = code[pc + 2].u.block;
+ break;
+
+ case PS_OP_IFELSE:
+ b1 = ps_pop_bool(st);
+ if (b1)
+ ps_run(ctx, code, st, code[pc + 1].u.block);
+ else
+ ps_run(ctx, code, st, code[pc + 0].u.block);
+ pc = code[pc + 2].u.block;
+ break;
+
+ case PS_OP_RETURN:
+ return;
+
+ default:
+ fz_warn(ctx, "foreign operator in calculator function");
+ return;
+ }
+ break;
+
+ default:
+ fz_warn(ctx, "foreign object in calculator function");
+ return;
+ }
+ }
+}
+
+static void
+resize_code(fz_context *ctx, pdf_function *func, int newsize)
+{
+ if (newsize >= func->u.p.cap)
+ {
+ int new_cap = func->u.p.cap + 64;
+ func->u.p.code = fz_resize_array(ctx, func->u.p.code, new_cap, sizeof(psobj));
+ func->u.p.cap = new_cap;
+ }
+}
+
+static void
+parse_code(pdf_function *func, fz_stream *stream, int *codeptr, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ int opptr, elseptr, ifptr;
+ int a, b, mid, cmp;
+ fz_context *ctx = stream->ctx;
+
+ while (1)
+ {
+ tok = pdf_lex(stream, buf);
+
+ switch (tok)
+ {
+ case PDF_TOK_EOF:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "truncated calculator function");
+
+ case PDF_TOK_INT:
+ resize_code(ctx, func, *codeptr);
+ func->u.p.code[*codeptr].type = PS_INT;
+ func->u.p.code[*codeptr].u.i = buf->i;
+ ++*codeptr;
+ break;
+
+ case PDF_TOK_TRUE:
+ resize_code(ctx, func, *codeptr);
+ func->u.p.code[*codeptr].type = PS_BOOL;
+ func->u.p.code[*codeptr].u.b = 1;
+ ++*codeptr;
+ break;
+
+ case PDF_TOK_FALSE:
+ resize_code(ctx, func, *codeptr);
+ func->u.p.code[*codeptr].type = PS_BOOL;
+ func->u.p.code[*codeptr].u.b = 0;
+ ++*codeptr;
+ break;
+
+ case PDF_TOK_REAL:
+ resize_code(ctx, func, *codeptr);
+ func->u.p.code[*codeptr].type = PS_REAL;
+ func->u.p.code[*codeptr].u.f = buf->f;
+ ++*codeptr;
+ break;
+
+ case PDF_TOK_OPEN_BRACE:
+ opptr = *codeptr;
+ *codeptr += 4;
+
+ resize_code(ctx, func, *codeptr);
+
+ ifptr = *codeptr;
+ parse_code(func, stream, codeptr, buf);
+
+ tok = pdf_lex(stream, buf);
+
+ if (tok == PDF_TOK_OPEN_BRACE)
+ {
+ elseptr = *codeptr;
+ parse_code(func, stream, codeptr, buf);
+
+ tok = pdf_lex(stream, buf);
+ }
+ else
+ {
+ elseptr = -1;
+ }
+
+ if (tok != PDF_TOK_KEYWORD)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "missing keyword in 'if-else' context");
+
+ if (!strcmp(buf->scratch, "if"))
+ {
+ if (elseptr >= 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "too many branches for 'if'");
+ func->u.p.code[opptr].type = PS_OPERATOR;
+ func->u.p.code[opptr].u.op = PS_OP_IF;
+ func->u.p.code[opptr+2].type = PS_BLOCK;
+ func->u.p.code[opptr+2].u.block = ifptr;
+ func->u.p.code[opptr+3].type = PS_BLOCK;
+ func->u.p.code[opptr+3].u.block = *codeptr;
+ }
+ else if (!strcmp(buf->scratch, "ifelse"))
+ {
+ if (elseptr < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "not enough branches for 'ifelse'");
+ func->u.p.code[opptr].type = PS_OPERATOR;
+ func->u.p.code[opptr].u.op = PS_OP_IFELSE;
+ func->u.p.code[opptr+1].type = PS_BLOCK;
+ func->u.p.code[opptr+1].u.block = elseptr;
+ func->u.p.code[opptr+2].type = PS_BLOCK;
+ func->u.p.code[opptr+2].u.block = ifptr;
+ func->u.p.code[opptr+3].type = PS_BLOCK;
+ func->u.p.code[opptr+3].u.block = *codeptr;
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown keyword in 'if-else' context: '%s'", buf->scratch);
+ }
+ break;
+
+ case PDF_TOK_CLOSE_BRACE:
+ resize_code(ctx, func, *codeptr);
+ func->u.p.code[*codeptr].type = PS_OPERATOR;
+ func->u.p.code[*codeptr].u.op = PS_OP_RETURN;
+ ++*codeptr;
+ return;
+
+ case PDF_TOK_KEYWORD:
+ cmp = -1;
+ a = -1;
+ b = nelem(ps_op_names);
+ while (b - a > 1)
+ {
+ mid = (a + b) / 2;
+ cmp = strcmp(buf->scratch, ps_op_names[mid]);
+ if (cmp > 0)
+ a = mid;
+ else if (cmp < 0)
+ b = mid;
+ else
+ a = b = mid;
+ }
+ if (cmp != 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown operator: '%s'", buf->scratch);
+
+ resize_code(ctx, func, *codeptr);
+ func->u.p.code[*codeptr].type = PS_OPERATOR;
+ func->u.p.code[*codeptr].u.op = a;
+ ++*codeptr;
+ break;
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "calculator function syntax error");
+ }
+ }
+}
+
+static void
+load_postscript_func(pdf_function *func, pdf_document *xref, pdf_obj *dict, int num, int gen)
+{
+ fz_stream *stream = NULL;
+ int codeptr;
+ pdf_lexbuf buf;
+ pdf_token tok;
+ fz_context *ctx = xref->ctx;
+ int locked = 0;
+
+ pdf_lexbuf_init(ctx, &buf, PDF_LEXBUF_SMALL);
+
+ fz_var(stream);
+ fz_var(locked);
+
+ fz_try(ctx)
+ {
+ stream = pdf_open_stream(xref, num, gen);
+
+ tok = pdf_lex(stream, &buf);
+ if (tok != PDF_TOK_OPEN_BRACE)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "stream is not a calculator function");
+ }
+
+ func->u.p.code = NULL;
+ func->u.p.cap = 0;
+
+ codeptr = 0;
+ parse_code(func, stream, &codeptr, &buf);
+ }
+ fz_always(ctx)
+ {
+ fz_close(stream);
+ pdf_lexbuf_fin(&buf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot parse calculator function (%d %d R)", num, gen);
+ }
+
+ func->base.size += func->u.p.cap * sizeof(psobj);
+}
+
+static void
+eval_postscript_func(fz_context *ctx, pdf_function *func, float *in, float *out)
+{
+ ps_stack st;
+ float x;
+ int i;
+
+ ps_init_stack(&st);
+
+ for (i = 0; i < func->base.m; i++)
+ {
+ x = fz_clamp(in[i], func->domain[i][0], func->domain[i][1]);
+ ps_push_real(&st, x);
+ }
+
+ ps_run(ctx, func->u.p.code, &st, 0);
+
+ for (i = func->base.n - 1; i >= 0; i--)
+ {
+ x = ps_pop_real(&st);
+ out[i] = fz_clamp(x, func->range[i][0], func->range[i][1]);
+ }
+}
+
+/*
+ * Sample function
+ */
+
+#define MAX_SAMPLE_FUNCTION_SIZE (100 << 20)
+
+static void
+load_sample_func(pdf_function *func, pdf_document *xref, pdf_obj *dict, int num, int gen)
+{
+ fz_context *ctx = xref->ctx;
+ fz_stream *stream;
+ pdf_obj *obj;
+ int samplecount;
+ int bps;
+ int i;
+
+ func->u.sa.samples = NULL;
+
+ obj = pdf_dict_gets(dict, "Size");
+ if (pdf_array_len(obj) < func->base.m)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "too few sample function dimension sizes");
+ if (pdf_array_len(obj) > func->base.m)
+ fz_warn(ctx, "too many sample function dimension sizes");
+ for (i = 0; i < func->base.m; i++)
+ {
+ func->u.sa.size[i] = pdf_to_int(pdf_array_get(obj, i));
+ if (func->u.sa.size[i] <= 0)
+ {
+ fz_warn(ctx, "non-positive sample function dimension size");
+ func->u.sa.size[i] = 1;
+ }
+ }
+
+ obj = pdf_dict_gets(dict, "BitsPerSample");
+ func->u.sa.bps = bps = pdf_to_int(obj);
+
+ for (i = 0; i < func->base.m; i++)
+ {
+ func->u.sa.encode[i][0] = 0;
+ func->u.sa.encode[i][1] = func->u.sa.size[i] - 1;
+ }
+ obj = pdf_dict_gets(dict, "Encode");
+ if (pdf_is_array(obj))
+ {
+ int ranges = fz_mini(func->base.m, pdf_array_len(obj) / 2);
+ if (ranges != func->base.m)
+ fz_warn(ctx, "wrong number of sample function input mappings");
+
+ for (i = 0; i < ranges; i++)
+ {
+ func->u.sa.encode[i][0] = pdf_to_real(pdf_array_get(obj, i * 2 + 0));
+ func->u.sa.encode[i][1] = pdf_to_real(pdf_array_get(obj, i * 2 + 1));
+ }
+ }
+
+ for (i = 0; i < func->base.n; i++)
+ {
+ func->u.sa.decode[i][0] = func->range[i][0];
+ func->u.sa.decode[i][1] = func->range[i][1];
+ }
+
+ obj = pdf_dict_gets(dict, "Decode");
+ if (pdf_is_array(obj))
+ {
+ int ranges = fz_mini(func->base.n, pdf_array_len(obj) / 2);
+ if (ranges != func->base.n)
+ fz_warn(ctx, "wrong number of sample function output mappings");
+
+ for (i = 0; i < ranges; i++)
+ {
+ func->u.sa.decode[i][0] = pdf_to_real(pdf_array_get(obj, i * 2 + 0));
+ func->u.sa.decode[i][1] = pdf_to_real(pdf_array_get(obj, i * 2 + 1));
+ }
+ }
+
+ for (i = 0, samplecount = func->base.n; i < func->base.m; i++)
+ samplecount *= func->u.sa.size[i];
+
+ if (samplecount > MAX_SAMPLE_FUNCTION_SIZE)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "sample function too large");
+
+ func->u.sa.samples = fz_malloc_array(ctx, samplecount, sizeof(float));
+ func->base.size += samplecount * sizeof(float);
+
+ stream = pdf_open_stream(xref, num, gen);
+
+ /* read samples */
+ for (i = 0; i < samplecount; i++)
+ {
+ unsigned int x;
+ float s;
+
+ if (fz_is_eof_bits(stream))
+ {
+ fz_close(stream);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "truncated sample function stream");
+ }
+
+ switch (bps)
+ {
+ case 1: s = fz_read_bits(stream, 1); break;
+ case 2: s = fz_read_bits(stream, 2) / 3.0f; break;
+ case 4: s = fz_read_bits(stream, 4) / 15.0f; break;
+ case 8: s = fz_read_byte(stream) / 255.0f; break;
+ case 12: s = fz_read_bits(stream, 12) / 4095.0f; break;
+ case 16:
+ x = fz_read_byte(stream) << 8;
+ x |= fz_read_byte(stream);
+ s = x / 65535.0f;
+ break;
+ case 24:
+ x = fz_read_byte(stream) << 16;
+ x |= fz_read_byte(stream) << 8;
+ x |= fz_read_byte(stream);
+ s = x / 16777215.0f;
+ break;
+ case 32:
+ x = fz_read_byte(stream) << 24;
+ x |= fz_read_byte(stream) << 16;
+ x |= fz_read_byte(stream) << 8;
+ x |= fz_read_byte(stream);
+ s = x / 4294967295.0f;
+ break;
+ default:
+ fz_close(stream);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "sample stream bit depth %d unsupported", bps);
+ }
+
+ func->u.sa.samples[i] = s;
+ }
+
+ fz_close(stream);
+}
+
+static float
+interpolate_sample(pdf_function *func, int *scale, int *e0, int *e1, float *efrac, int dim, int idx)
+{
+ float a, b;
+ int idx0, idx1;
+
+ idx0 = e0[dim] * scale[dim] + idx;
+ idx1 = e1[dim] * scale[dim] + idx;
+
+ if (dim == 0)
+ {
+ a = func->u.sa.samples[idx0];
+ b = func->u.sa.samples[idx1];
+ }
+ else
+ {
+ a = interpolate_sample(func, scale, e0, e1, efrac, dim - 1, idx0);
+ b = interpolate_sample(func, scale, e0, e1, efrac, dim - 1, idx1);
+ }
+
+ return a + (b - a) * efrac[dim];
+}
+
+static void
+eval_sample_func(fz_context *ctx, pdf_function *func, float *in, float *out)
+{
+ int e0[FZ_FN_MAXM], e1[FZ_FN_MAXM], scale[FZ_FN_MAXM];
+ float efrac[FZ_FN_MAXM];
+ float x;
+ int i;
+
+ /* encode input coordinates */
+ for (i = 0; i < func->base.m; i++)
+ {
+ x = fz_clamp(in[i], func->domain[i][0], func->domain[i][1]);
+ x = lerp(x, func->domain[i][0], func->domain[i][1],
+ func->u.sa.encode[i][0], func->u.sa.encode[i][1]);
+ x = fz_clamp(x, 0, func->u.sa.size[i] - 1);
+ e0[i] = floorf(x);
+ e1[i] = ceilf(x);
+ efrac[i] = x - floorf(x);
+ }
+
+ scale[0] = func->base.n;
+ for (i = 1; i < func->base.m; i++)
+ scale[i] = scale[i - 1] * func->u.sa.size[i];
+
+ for (i = 0; i < func->base.n; i++)
+ {
+ if (func->base.m == 1)
+ {
+ float a = func->u.sa.samples[e0[0] * func->base.n + i];
+ float b = func->u.sa.samples[e1[0] * func->base.n + i];
+
+ float ab = a + (b - a) * efrac[0];
+
+ out[i] = lerp(ab, 0, 1, func->u.sa.decode[i][0], func->u.sa.decode[i][1]);
+ out[i] = fz_clamp(out[i], func->range[i][0], func->range[i][1]);
+ }
+
+ else if (func->base.m == 2)
+ {
+ int s0 = func->base.n;
+ int s1 = s0 * func->u.sa.size[0];
+
+ float a = func->u.sa.samples[e0[0] * s0 + e0[1] * s1 + i];
+ float b = func->u.sa.samples[e1[0] * s0 + e0[1] * s1 + i];
+ float c = func->u.sa.samples[e0[0] * s0 + e1[1] * s1 + i];
+ float d = func->u.sa.samples[e1[0] * s0 + e1[1] * s1 + i];
+
+ float ab = a + (b - a) * efrac[0];
+ float cd = c + (d - c) * efrac[0];
+ float abcd = ab + (cd - ab) * efrac[1];
+
+ out[i] = lerp(abcd, 0, 1, func->u.sa.decode[i][0], func->u.sa.decode[i][1]);
+ out[i] = fz_clamp(out[i], func->range[i][0], func->range[i][1]);
+ }
+
+ else
+ {
+ x = interpolate_sample(func, scale, e0, e1, efrac, func->base.m - 1, i);
+ out[i] = lerp(x, 0, 1, func->u.sa.decode[i][0], func->u.sa.decode[i][1]);
+ out[i] = fz_clamp(out[i], func->range[i][0], func->range[i][1]);
+ }
+ }
+}
+
+/*
+ * Exponential function
+ */
+
+static void
+load_exponential_func(fz_context *ctx, pdf_function *func, pdf_obj *dict)
+{
+ pdf_obj *obj;
+ int i;
+
+ if (func->base.m > 1)
+ fz_warn(ctx, "exponential functions have at most one input");
+ func->base.m = 1;
+
+ obj = pdf_dict_gets(dict, "N");
+ func->u.e.n = pdf_to_real(obj);
+
+ /* See exponential functions (PDF 1.7 section 3.9.2) */
+ if (func->u.e.n != (int) func->u.e.n)
+ {
+ /* If N is non-integer, input values may never be negative */
+ for (i = 0; i < func->base.m; i++)
+ if (func->domain[i][0] < 0 || func->domain[i][1] < 0)
+ fz_warn(ctx, "exponential function input domain includes illegal negative input values");
+ }
+ else if (func->u.e.n < 0)
+ {
+ /* if N is negative, input values may never be zero */
+ for (i = 0; i < func->base.m; i++)
+ if (func->domain[i][0] == 0 || func->domain[i][1] == 0 ||
+ (func->domain[i][0] < 0 && func->domain[i][1] > 0))
+ fz_warn(ctx, "exponential function input domain includes illegal input value zero");
+ }
+
+ for (i = 0; i < func->base.n; i++)
+ {
+ func->u.e.c0[i] = 0;
+ func->u.e.c1[i] = 1;
+ }
+
+ obj = pdf_dict_gets(dict, "C0");
+ if (pdf_is_array(obj))
+ {
+ int ranges = fz_mini(func->base.n, pdf_array_len(obj));
+ if (ranges != func->base.n)
+ fz_warn(ctx, "wrong number of C0 constants for exponential function");
+
+ for (i = 0; i < ranges; i++)
+ func->u.e.c0[i] = pdf_to_real(pdf_array_get(obj, i));
+ }
+
+ obj = pdf_dict_gets(dict, "C1");
+ if (pdf_is_array(obj))
+ {
+ int ranges = fz_mini(func->base.n, pdf_array_len(obj));
+ if (ranges != func->base.n)
+ fz_warn(ctx, "wrong number of C1 constants for exponential function");
+
+ for (i = 0; i < ranges; i++)
+ func->u.e.c1[i] = pdf_to_real(pdf_array_get(obj, i));
+ }
+}
+
+static void
+eval_exponential_func(fz_context *ctx, pdf_function *func, float in, float *out)
+{
+ float x = in;
+ float tmp;
+ int i;
+
+ x = fz_clamp(x, func->domain[0][0], func->domain[0][1]);
+
+ /* Default output is zero, which is suitable for violated constraints */
+ if ((func->u.e.n != (int)func->u.e.n && x < 0) || (func->u.e.n < 0 && x == 0))
+ return;
+
+ tmp = powf(x, func->u.e.n);
+ for (i = 0; i < func->base.n; i++)
+ {
+ out[i] = func->u.e.c0[i] + tmp * (func->u.e.c1[i] - func->u.e.c0[i]);
+ if (func->has_range)
+ out[i] = fz_clamp(out[i], func->range[i][0], func->range[i][1]);
+ }
+}
+
+/*
+ * Stitching function
+ */
+
+static void
+load_stitching_func(pdf_function *func, pdf_document *xref, pdf_obj *dict)
+{
+ fz_context *ctx = xref->ctx;
+ fz_function **funcs;
+ pdf_obj *obj;
+ pdf_obj *sub;
+ pdf_obj *num;
+ int k;
+ int i;
+
+ func->u.st.k = 0;
+
+ if (func->base.m > 1)
+ fz_warn(ctx, "stitching functions have at most one input");
+ func->base.m = 1;
+
+ obj = pdf_dict_gets(dict, "Functions");
+ if (!pdf_is_array(obj))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "stitching function has no input functions");
+
+ fz_try(ctx)
+ {
+ pdf_obj_mark(obj);
+ k = pdf_array_len(obj);
+
+ func->u.st.funcs = fz_malloc_array(ctx, k, sizeof(fz_function*));
+ func->u.st.bounds = fz_malloc_array(ctx, k - 1, sizeof(float));
+ func->u.st.encode = fz_malloc_array(ctx, k * 2, sizeof(float));
+ funcs = func->u.st.funcs;
+
+ for (i = 0; i < k; i++)
+ {
+ sub = pdf_array_get(obj, i);
+ funcs[i] = pdf_load_function(xref, sub, 1, func->base.n);
+
+ func->base.size += fz_function_size(funcs[i]);
+ func->u.st.k ++;
+
+ if (funcs[i]->m != func->base.m)
+ fz_warn(ctx, "wrong number of inputs for sub function %d", i);
+ if (funcs[i]->n != func->base.n)
+ fz_warn(ctx, "wrong number of outputs for sub function %d", i);
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(obj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ obj = pdf_dict_gets(dict, "Bounds");
+ if (!pdf_is_array(obj))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "stitching function has no bounds");
+ {
+ if (pdf_array_len(obj) < k - 1)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "too few subfunction boundaries");
+ if (pdf_array_len(obj) > k)
+ fz_warn(ctx, "too many subfunction boundaries");
+
+ for (i = 0; i < k - 1; i++)
+ {
+ num = pdf_array_get(obj, i);
+ func->u.st.bounds[i] = pdf_to_real(num);
+ if (i && func->u.st.bounds[i - 1] > func->u.st.bounds[i])
+ fz_throw(ctx, FZ_ERROR_GENERIC, "subfunction %d boundary out of range", i);
+ }
+
+ if (k > 1 && (func->domain[0][0] > func->u.st.bounds[0] ||
+ func->domain[0][1] < func->u.st.bounds[k - 2]))
+ fz_warn(ctx, "subfunction boundaries outside of input mapping");
+ }
+
+ for (i = 0; i < k; i++)
+ {
+ func->u.st.encode[i * 2 + 0] = 0;
+ func->u.st.encode[i * 2 + 1] = 0;
+ }
+
+ obj = pdf_dict_gets(dict, "Encode");
+ if (pdf_is_array(obj))
+ {
+ int ranges = fz_mini(k, pdf_array_len(obj) / 2);
+ if (ranges != k)
+ fz_warn(ctx, "wrong number of stitching function input mappings");
+
+ for (i = 0; i < ranges; i++)
+ {
+ func->u.st.encode[i * 2 + 0] = pdf_to_real(pdf_array_get(obj, i * 2 + 0));
+ func->u.st.encode[i * 2 + 1] = pdf_to_real(pdf_array_get(obj, i * 2 + 1));
+ }
+ }
+}
+
+static void
+eval_stitching_func(fz_context *ctx, pdf_function *func, float in, float *out)
+{
+ float low, high;
+ int k = func->u.st.k;
+ float *bounds = func->u.st.bounds;
+ int i;
+
+ in = fz_clamp(in, func->domain[0][0], func->domain[0][1]);
+
+ for (i = 0; i < k - 1; i++)
+ {
+ if (in < bounds[i])
+ break;
+ }
+
+ if (i == 0 && k == 1)
+ {
+ low = func->domain[0][0];
+ high = func->domain[0][1];
+ }
+ else if (i == 0)
+ {
+ low = func->domain[0][0];
+ high = bounds[0];
+ }
+ else if (i == k - 1)
+ {
+ low = bounds[k - 2];
+ high = func->domain[0][1];
+ }
+ else
+ {
+ low = bounds[i - 1];
+ high = bounds[i];
+ }
+
+ in = lerp(in, low, high, func->u.st.encode[i * 2 + 0], func->u.st.encode[i * 2 + 1]);
+
+ fz_eval_function(ctx, func->u.st.funcs[i], &in, 1, out, func->u.st.funcs[i]->n);
+}
+
+/*
+ * Common
+ */
+
+static void
+pdf_free_function_imp(fz_context *ctx, fz_storable *func_)
+{
+ pdf_function *func = (pdf_function *)func_;
+ int i;
+
+ switch (func->type)
+ {
+ case SAMPLE:
+ fz_free(ctx, func->u.sa.samples);
+ break;
+ case EXPONENTIAL:
+ break;
+ case STITCHING:
+ for (i = 0; i < func->u.st.k; i++)
+ fz_drop_function(ctx, func->u.st.funcs[i]);
+ fz_free(ctx, func->u.st.funcs);
+ fz_free(ctx, func->u.st.bounds);
+ fz_free(ctx, func->u.st.encode);
+ break;
+ case POSTSCRIPT:
+ fz_free(ctx, func->u.p.code);
+ break;
+ }
+ fz_free(ctx, func);
+}
+
+static void
+pdf_eval_function(fz_context *ctx, fz_function *func_, float *in, float *out)
+{
+ pdf_function *func = (pdf_function *)func_;
+
+ switch (func->type)
+ {
+ case SAMPLE: eval_sample_func(ctx, func, in, out); break;
+ case EXPONENTIAL: eval_exponential_func(ctx, func, *in, out); break;
+ case STITCHING: eval_stitching_func(ctx, func, *in, out); break;
+ case POSTSCRIPT: eval_postscript_func(ctx, func, in, out); break;
+ }
+}
+
+/*
+ * Debugging prints
+ */
+
+#ifndef NDEBUG
+static void
+pdf_debug_indent(char *prefix, int level, char *suffix)
+{
+ int i;
+
+ printf("%s", prefix);
+
+ for (i = 0; i < level; i++)
+ printf("\t");
+
+ printf("%s", suffix);
+}
+
+static void
+pdf_debug_ps_func_code(psobj *funccode, psobj *code, int level)
+{
+ int eof, wasop;
+
+ pdf_debug_indent("", level, "{");
+
+ /* Print empty blocks as { }, instead of separating braces on different lines. */
+ if (code->type == PS_OPERATOR && code->u.op == PS_OP_RETURN)
+ {
+ printf(" } ");
+ return;
+ }
+
+ pdf_debug_indent("\n", ++level, "");
+
+ eof = 0;
+ wasop = 0;
+ while (!eof)
+ {
+ switch (code->type)
+ {
+ case PS_INT:
+ if (wasop)
+ pdf_debug_indent("\n", level, "");
+
+ printf("%d ", code->u.i);
+ wasop = 0;
+ code++;
+ break;
+
+ case PS_REAL:
+ if (wasop)
+ pdf_debug_indent("\n", level, "");
+
+ printf("%g ", code->u.f);
+ wasop = 0;
+ code++;
+ break;
+
+ case PS_OPERATOR:
+ if (code->u.op == PS_OP_RETURN)
+ {
+ printf("\n");
+ eof = 1;
+ }
+ else if (code->u.op == PS_OP_IF)
+ {
+ printf("\n");
+ pdf_debug_ps_func_code(funccode, &funccode[(code + 2)->u.block], level);
+
+ printf("%s", ps_op_names[code->u.op]);
+ code = &funccode[(code + 3)->u.block];
+ if (code->type != PS_OPERATOR || code->u.op != PS_OP_RETURN)
+ pdf_debug_indent("\n", level, "");
+
+ wasop = 0;
+ }
+ else if (code->u.op == PS_OP_IFELSE)
+ {
+ printf("\n");
+ pdf_debug_ps_func_code(funccode, &funccode[(code + 2)->u.block], level);
+
+ printf("\n");
+ pdf_debug_ps_func_code(funccode, &funccode[(code + 1)->u.block], level);
+
+ printf("%s", ps_op_names[code->u.op]);
+ code = &funccode[(code + 3)->u.block];
+ if (code->type != PS_OPERATOR || code->u.op != PS_OP_RETURN)
+ pdf_debug_indent("\n", level, "");
+
+ wasop = 0;
+ }
+ else
+ {
+ printf("%s ", ps_op_names[code->u.op]);
+ code++;
+ wasop = 1;
+ }
+ break;
+ }
+ }
+
+ pdf_debug_indent("", --level, "} ");
+}
+
+static void
+pdf_debug_function_imp(fz_function *func_, int level)
+{
+ int i;
+ pdf_function *func = (pdf_function *)func_;
+
+ pdf_debug_indent("", level, "function {\n");
+
+ pdf_debug_indent("", ++level, "");
+ switch (func->type)
+ {
+ case SAMPLE:
+ printf("sampled");
+ break;
+ case EXPONENTIAL:
+ printf("exponential");
+ break;
+ case STITCHING:
+ printf("stitching");
+ break;
+ case POSTSCRIPT:
+ printf("postscript");
+ break;
+ }
+
+ pdf_debug_indent("\n", level, "");
+ printf("%d input -> %d output\n", func->base.m, func->base.n);
+
+ pdf_debug_indent("", level, "domain ");
+ for (i = 0; i < func->base.m; i++)
+ printf("%g %g ", func->domain[i][0], func->domain[i][1]);
+ printf("\n");
+
+ if (func->has_range)
+ {
+ pdf_debug_indent("", level, "range ");
+ for (i = 0; i < func->base.n; i++)
+ printf("%g %g ", func->range[i][0], func->range[i][1]);
+ printf("\n");
+ }
+
+ switch (func->type)
+ {
+ case SAMPLE:
+ pdf_debug_indent("", level, "");
+ printf("bps: %d\n", func->u.sa.bps);
+
+ pdf_debug_indent("", level, "");
+ printf("size: [ ");
+ for (i = 0; i < func->base.m; i++)
+ printf("%d ", func->u.sa.size[i]);
+ printf("]\n");
+
+ pdf_debug_indent("", level, "");
+ printf("encode: [ ");
+ for (i = 0; i < func->base.m; i++)
+ printf("%g %g ", func->u.sa.encode[i][0], func->u.sa.encode[i][1]);
+ printf("]\n");
+
+ pdf_debug_indent("", level, "");
+ printf("decode: [ ");
+ for (i = 0; i < func->base.m; i++)
+ printf("%g %g ", func->u.sa.decode[i][0], func->u.sa.decode[i][1]);
+ printf("]\n");
+ break;
+
+ case EXPONENTIAL:
+ pdf_debug_indent("", level, "");
+ printf("n: %g\n", func->u.e.n);
+
+ pdf_debug_indent("", level, "");
+ printf("c0: [ ");
+ for (i = 0; i < func->base.n; i++)
+ printf("%g ", func->u.e.c0[i]);
+ printf("]\n");
+
+ pdf_debug_indent("", level, "");
+ printf("c1: [ ");
+ for (i = 0; i < func->base.n; i++)
+ printf("%g ", func->u.e.c1[i]);
+ printf("]\n");
+ break;
+
+ case STITCHING:
+ pdf_debug_indent("", level, "");
+ printf("%d functions\n", func->u.st.k);
+
+ pdf_debug_indent("", level, "");
+ printf("bounds: [ ");
+ for (i = 0; i < func->u.st.k - 1; i++)
+ printf("%g ", func->u.st.bounds[i]);
+ printf("]\n");
+
+ pdf_debug_indent("", level, "");
+ printf("encode: [ ");
+ for (i = 0; i < func->u.st.k * 2; i++)
+ printf("%g ", func->u.st.encode[i]);
+ printf("]\n");
+
+ for (i = 0; i < func->u.st.k; i++)
+ pdf_debug_function_imp(func->u.st.funcs[i], level);
+ break;
+
+ case POSTSCRIPT:
+ pdf_debug_ps_func_code(func->u.p.code, func->u.p.code, level);
+ printf("\n");
+ break;
+ }
+
+ pdf_debug_indent("", --level, "}\n");
+}
+
+void
+pdf_debug_function(fz_function *func)
+{
+ pdf_debug_function_imp(func, 0);
+}
+#endif
+
+fz_function *
+pdf_load_function(pdf_document *xref, pdf_obj *dict, int in, int out)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_function *func;
+ pdf_obj *obj;
+ int i;
+
+ if (pdf_obj_marked(dict))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Recursion in function definition");
+
+ if ((func = pdf_find_item(ctx, pdf_free_function_imp, dict)))
+ {
+ return (fz_function *)func;
+ }
+
+ func = fz_malloc_struct(ctx, pdf_function);
+ FZ_INIT_STORABLE(&func->base, 1, pdf_free_function_imp);
+ func->base.size = sizeof(*func);
+ func->base.evaluate = pdf_eval_function;
+#ifndef NDEBUG
+ func->base.debug = pdf_debug_function;
+#endif
+
+ obj = pdf_dict_gets(dict, "FunctionType");
+ func->type = pdf_to_int(obj);
+
+ /* required for all */
+ obj = pdf_dict_gets(dict, "Domain");
+ func->base.m = fz_clampi(pdf_array_len(obj) / 2, 1, FZ_FN_MAXM);
+ for (i = 0; i < func->base.m; i++)
+ {
+ func->domain[i][0] = pdf_to_real(pdf_array_get(obj, i * 2 + 0));
+ func->domain[i][1] = pdf_to_real(pdf_array_get(obj, i * 2 + 1));
+ }
+
+ /* required for type0 and type4, optional otherwise */
+ obj = pdf_dict_gets(dict, "Range");
+ if (pdf_is_array(obj))
+ {
+ func->has_range = 1;
+ func->base.n = fz_clampi(pdf_array_len(obj) / 2, 1, FZ_FN_MAXN);
+ for (i = 0; i < func->base.n; i++)
+ {
+ func->range[i][0] = pdf_to_real(pdf_array_get(obj, i * 2 + 0));
+ func->range[i][1] = pdf_to_real(pdf_array_get(obj, i * 2 + 1));
+ }
+ }
+ else
+ {
+ func->has_range = 0;
+ func->base.n = out;
+ }
+
+ if (func->base.m != in)
+ fz_warn(ctx, "wrong number of function inputs");
+ if (func->base.n != out)
+ fz_warn(ctx, "wrong number of function outputs");
+
+ fz_try(ctx)
+ {
+ switch (func->type)
+ {
+ case SAMPLE:
+ load_sample_func(func, xref, dict, pdf_to_num(dict), pdf_to_gen(dict));
+ break;
+
+ case EXPONENTIAL:
+ load_exponential_func(ctx, func, dict);
+ break;
+
+ case STITCHING:
+ load_stitching_func(func, xref, dict);
+ break;
+
+ case POSTSCRIPT:
+ load_postscript_func(func, xref, dict, pdf_to_num(dict), pdf_to_gen(dict));
+ break;
+
+ default:
+ fz_free(ctx, func);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown function type (%d %d R)", pdf_to_num(dict), pdf_to_gen(dict));
+ }
+
+ pdf_store_item(ctx, dict, func, func->base.size);
+ }
+ fz_catch(ctx)
+ {
+ int type = func->type;
+ fz_drop_function(ctx, (fz_function *)func);
+ fz_rethrow_message(ctx, "cannot load %s function (%d %d R)",
+ type == SAMPLE ? "sampled" :
+ type == EXPONENTIAL ? "exponential" :
+ type == STITCHING ? "stitching" :
+ type == POSTSCRIPT ? "calculator" :
+ "unknown",
+ pdf_to_num(dict), pdf_to_gen(dict));
+ }
+
+ return (fz_function *)func;
+}
diff --git a/source/pdf/pdf-glyphlist.h b/source/pdf/pdf-glyphlist.h
new file mode 100644
index 00000000..f1416916
--- /dev/null
+++ b/source/pdf/pdf-glyphlist.h
@@ -0,0 +1,1461 @@
+/*
+# Name: Adobe Glyph List
+# Table version: 2.0
+# Date: September 20, 2002
+#
+# See http://partners.adobe.com/asn/developer/typeforum/unicodegn.html
+#
+# Format: Semicolon-delimited fields:
+# (1) glyph name
+# (2) Unicode scalar value
+#--end
+*/
+
+static const char *agl_name_list[] = {
+"A","AE","AEacute","AEmacron","AEsmall","Aacute","Aacutesmall","Abreve",
+"Abreveacute","Abrevecyrillic","Abrevedotbelow","Abrevegrave",
+"Abrevehookabove","Abrevetilde","Acaron","Acircle","Acircumflex",
+"Acircumflexacute","Acircumflexdotbelow","Acircumflexgrave",
+"Acircumflexhookabove","Acircumflexsmall","Acircumflextilde","Acute",
+"Acutesmall","Acyrillic","Adblgrave","Adieresis","Adieresiscyrillic",
+"Adieresismacron","Adieresissmall","Adotbelow","Adotmacron","Agrave",
+"Agravesmall","Ahookabove","Aiecyrillic","Ainvertedbreve","Alpha",
+"Alphatonos","Amacron","Amonospace","Aogonek","Aring","Aringacute",
+"Aringbelow","Aringsmall","Asmall","Atilde","Atildesmall","Aybarmenian","B",
+"Bcircle","Bdotaccent","Bdotbelow","Becyrillic","Benarmenian","Beta","Bhook",
+"Blinebelow","Bmonospace","Brevesmall","Bsmall","Btopbar","C","Caarmenian",
+"Cacute","Caron","Caronsmall","Ccaron","Ccedilla","Ccedillaacute",
+"Ccedillasmall","Ccircle","Ccircumflex","Cdot","Cdotaccent","Cedillasmall",
+"Chaarmenian","Cheabkhasiancyrillic","Checyrillic",
+"Chedescenderabkhasiancyrillic","Chedescendercyrillic","Chedieresiscyrillic",
+"Cheharmenian","Chekhakassiancyrillic","Cheverticalstrokecyrillic","Chi",
+"Chook","Circumflexsmall","Cmonospace","Coarmenian","Csmall","D","DZ",
+"DZcaron","Daarmenian","Dafrican","Dcaron","Dcedilla","Dcircle",
+"Dcircumflexbelow","Dcroat","Ddotaccent","Ddotbelow","Decyrillic","Deicoptic",
+"Delta","Deltagreek","Dhook","Dieresis","DieresisAcute","DieresisGrave",
+"Dieresissmall","Digammagreek","Djecyrillic","Dlinebelow","Dmonospace",
+"Dotaccentsmall","Dslash","Dsmall","Dtopbar","Dz","Dzcaron",
+"Dzeabkhasiancyrillic","Dzecyrillic","Dzhecyrillic","E","Eacute",
+"Eacutesmall","Ebreve","Ecaron","Ecedillabreve","Echarmenian","Ecircle",
+"Ecircumflex","Ecircumflexacute","Ecircumflexbelow","Ecircumflexdotbelow",
+"Ecircumflexgrave","Ecircumflexhookabove","Ecircumflexsmall",
+"Ecircumflextilde","Ecyrillic","Edblgrave","Edieresis","Edieresissmall",
+"Edot","Edotaccent","Edotbelow","Efcyrillic","Egrave","Egravesmall",
+"Eharmenian","Ehookabove","Eightroman","Einvertedbreve","Eiotifiedcyrillic",
+"Elcyrillic","Elevenroman","Emacron","Emacronacute","Emacrongrave",
+"Emcyrillic","Emonospace","Encyrillic","Endescendercyrillic","Eng",
+"Enghecyrillic","Enhookcyrillic","Eogonek","Eopen","Epsilon","Epsilontonos",
+"Ercyrillic","Ereversed","Ereversedcyrillic","Escyrillic",
+"Esdescendercyrillic","Esh","Esmall","Eta","Etarmenian","Etatonos","Eth",
+"Ethsmall","Etilde","Etildebelow","Euro","Ezh","Ezhcaron","Ezhreversed","F",
+"Fcircle","Fdotaccent","Feharmenian","Feicoptic","Fhook","Fitacyrillic",
+"Fiveroman","Fmonospace","Fourroman","Fsmall","G","GBsquare","Gacute","Gamma",
+"Gammaafrican","Gangiacoptic","Gbreve","Gcaron","Gcedilla","Gcircle",
+"Gcircumflex","Gcommaaccent","Gdot","Gdotaccent","Gecyrillic","Ghadarmenian",
+"Ghemiddlehookcyrillic","Ghestrokecyrillic","Gheupturncyrillic","Ghook",
+"Gimarmenian","Gjecyrillic","Gmacron","Gmonospace","Grave","Gravesmall",
+"Gsmall","Gsmallhook","Gstroke","H","H18533","H18543","H18551","H22073",
+"HPsquare","Haabkhasiancyrillic","Hadescendercyrillic","Hardsigncyrillic",
+"Hbar","Hbrevebelow","Hcedilla","Hcircle","Hcircumflex","Hdieresis",
+"Hdotaccent","Hdotbelow","Hmonospace","Hoarmenian","Horicoptic","Hsmall",
+"Hungarumlaut","Hungarumlautsmall","Hzsquare","I","IAcyrillic","IJ",
+"IUcyrillic","Iacute","Iacutesmall","Ibreve","Icaron","Icircle","Icircumflex",
+"Icircumflexsmall","Icyrillic","Idblgrave","Idieresis","Idieresisacute",
+"Idieresiscyrillic","Idieresissmall","Idot","Idotaccent","Idotbelow",
+"Iebrevecyrillic","Iecyrillic","Ifraktur","Igrave","Igravesmall","Ihookabove",
+"Iicyrillic","Iinvertedbreve","Iishortcyrillic","Imacron","Imacroncyrillic",
+"Imonospace","Iniarmenian","Iocyrillic","Iogonek","Iota","Iotaafrican",
+"Iotadieresis","Iotatonos","Ismall","Istroke","Itilde","Itildebelow",
+"Izhitsacyrillic","Izhitsadblgravecyrillic","J","Jaarmenian","Jcircle",
+"Jcircumflex","Jecyrillic","Jheharmenian","Jmonospace","Jsmall","K",
+"KBsquare","KKsquare","Kabashkircyrillic","Kacute","Kacyrillic",
+"Kadescendercyrillic","Kahookcyrillic","Kappa","Kastrokecyrillic",
+"Kaverticalstrokecyrillic","Kcaron","Kcedilla","Kcircle","Kcommaaccent",
+"Kdotbelow","Keharmenian","Kenarmenian","Khacyrillic","Kheicoptic","Khook",
+"Kjecyrillic","Klinebelow","Kmonospace","Koppacyrillic","Koppagreek",
+"Ksicyrillic","Ksmall","L","LJ","LL","Lacute","Lambda","Lcaron","Lcedilla",
+"Lcircle","Lcircumflexbelow","Lcommaaccent","Ldot","Ldotaccent","Ldotbelow",
+"Ldotbelowmacron","Liwnarmenian","Lj","Ljecyrillic","Llinebelow","Lmonospace",
+"Lslash","Lslashsmall","Lsmall","M","MBsquare","Macron","Macronsmall",
+"Macute","Mcircle","Mdotaccent","Mdotbelow","Menarmenian","Mmonospace",
+"Msmall","Mturned","Mu","N","NJ","Nacute","Ncaron","Ncedilla","Ncircle",
+"Ncircumflexbelow","Ncommaaccent","Ndotaccent","Ndotbelow","Nhookleft",
+"Nineroman","Nj","Njecyrillic","Nlinebelow","Nmonospace","Nowarmenian",
+"Nsmall","Ntilde","Ntildesmall","Nu","O","OE","OEsmall","Oacute",
+"Oacutesmall","Obarredcyrillic","Obarreddieresiscyrillic","Obreve","Ocaron",
+"Ocenteredtilde","Ocircle","Ocircumflex","Ocircumflexacute",
+"Ocircumflexdotbelow","Ocircumflexgrave","Ocircumflexhookabove",
+"Ocircumflexsmall","Ocircumflextilde","Ocyrillic","Odblacute","Odblgrave",
+"Odieresis","Odieresiscyrillic","Odieresissmall","Odotbelow","Ogoneksmall",
+"Ograve","Ogravesmall","Oharmenian","Ohm","Ohookabove","Ohorn","Ohornacute",
+"Ohorndotbelow","Ohorngrave","Ohornhookabove","Ohorntilde","Ohungarumlaut",
+"Oi","Oinvertedbreve","Omacron","Omacronacute","Omacrongrave","Omega",
+"Omegacyrillic","Omegagreek","Omegaroundcyrillic","Omegatitlocyrillic",
+"Omegatonos","Omicron","Omicrontonos","Omonospace","Oneroman","Oogonek",
+"Oogonekmacron","Oopen","Oslash","Oslashacute","Oslashsmall","Osmall",
+"Ostrokeacute","Otcyrillic","Otilde","Otildeacute","Otildedieresis",
+"Otildesmall","P","Pacute","Pcircle","Pdotaccent","Pecyrillic","Peharmenian",
+"Pemiddlehookcyrillic","Phi","Phook","Pi","Piwrarmenian","Pmonospace","Psi",
+"Psicyrillic","Psmall","Q","Qcircle","Qmonospace","Qsmall","R","Raarmenian",
+"Racute","Rcaron","Rcedilla","Rcircle","Rcommaaccent","Rdblgrave",
+"Rdotaccent","Rdotbelow","Rdotbelowmacron","Reharmenian","Rfraktur","Rho",
+"Ringsmall","Rinvertedbreve","Rlinebelow","Rmonospace","Rsmall",
+"Rsmallinverted","Rsmallinvertedsuperior","S","SF010000","SF020000",
+"SF030000","SF040000","SF050000","SF060000","SF070000","SF080000","SF090000",
+"SF100000","SF110000","SF190000","SF200000","SF210000","SF220000","SF230000",
+"SF240000","SF250000","SF260000","SF270000","SF280000","SF360000","SF370000",
+"SF380000","SF390000","SF400000","SF410000","SF420000","SF430000","SF440000",
+"SF450000","SF460000","SF470000","SF480000","SF490000","SF500000","SF510000",
+"SF520000","SF530000","SF540000","Sacute","Sacutedotaccent","Sampigreek",
+"Scaron","Scarondotaccent","Scaronsmall","Scedilla","Schwa","Schwacyrillic",
+"Schwadieresiscyrillic","Scircle","Scircumflex","Scommaaccent","Sdotaccent",
+"Sdotbelow","Sdotbelowdotaccent","Seharmenian","Sevenroman","Shaarmenian",
+"Shacyrillic","Shchacyrillic","Sheicoptic","Shhacyrillic","Shimacoptic",
+"Sigma","Sixroman","Smonospace","Softsigncyrillic","Ssmall","Stigmagreek","T",
+"Tau","Tbar","Tcaron","Tcedilla","Tcircle","Tcircumflexbelow","Tcommaaccent",
+"Tdotaccent","Tdotbelow","Tecyrillic","Tedescendercyrillic","Tenroman",
+"Tetsecyrillic","Theta","Thook","Thorn","Thornsmall","Threeroman",
+"Tildesmall","Tiwnarmenian","Tlinebelow","Tmonospace","Toarmenian","Tonefive",
+"Tonesix","Tonetwo","Tretroflexhook","Tsecyrillic","Tshecyrillic","Tsmall",
+"Twelveroman","Tworoman","U","Uacute","Uacutesmall","Ubreve","Ucaron",
+"Ucircle","Ucircumflex","Ucircumflexbelow","Ucircumflexsmall","Ucyrillic",
+"Udblacute","Udblgrave","Udieresis","Udieresisacute","Udieresisbelow",
+"Udieresiscaron","Udieresiscyrillic","Udieresisgrave","Udieresismacron",
+"Udieresissmall","Udotbelow","Ugrave","Ugravesmall","Uhookabove","Uhorn",
+"Uhornacute","Uhorndotbelow","Uhorngrave","Uhornhookabove","Uhorntilde",
+"Uhungarumlaut","Uhungarumlautcyrillic","Uinvertedbreve","Ukcyrillic",
+"Umacron","Umacroncyrillic","Umacrondieresis","Umonospace","Uogonek",
+"Upsilon","Upsilon1","Upsilonacutehooksymbolgreek","Upsilonafrican",
+"Upsilondieresis","Upsilondieresishooksymbolgreek","Upsilonhooksymbol",
+"Upsilontonos","Uring","Ushortcyrillic","Usmall","Ustraightcyrillic",
+"Ustraightstrokecyrillic","Utilde","Utildeacute","Utildebelow","V","Vcircle",
+"Vdotbelow","Vecyrillic","Vewarmenian","Vhook","Vmonospace","Voarmenian",
+"Vsmall","Vtilde","W","Wacute","Wcircle","Wcircumflex","Wdieresis",
+"Wdotaccent","Wdotbelow","Wgrave","Wmonospace","Wsmall","X","Xcircle",
+"Xdieresis","Xdotaccent","Xeharmenian","Xi","Xmonospace","Xsmall","Y",
+"Yacute","Yacutesmall","Yatcyrillic","Ycircle","Ycircumflex","Ydieresis",
+"Ydieresissmall","Ydotaccent","Ydotbelow","Yericyrillic",
+"Yerudieresiscyrillic","Ygrave","Yhook","Yhookabove","Yiarmenian",
+"Yicyrillic","Yiwnarmenian","Ymonospace","Ysmall","Ytilde","Yusbigcyrillic",
+"Yusbigiotifiedcyrillic","Yuslittlecyrillic","Yuslittleiotifiedcyrillic","Z",
+"Zaarmenian","Zacute","Zcaron","Zcaronsmall","Zcircle","Zcircumflex","Zdot",
+"Zdotaccent","Zdotbelow","Zecyrillic","Zedescendercyrillic",
+"Zedieresiscyrillic","Zeta","Zhearmenian","Zhebrevecyrillic","Zhecyrillic",
+"Zhedescendercyrillic","Zhedieresiscyrillic","Zlinebelow","Zmonospace",
+"Zsmall","Zstroke","a","aabengali","aacute","aadeva","aagujarati",
+"aagurmukhi","aamatragurmukhi","aarusquare","aavowelsignbengali",
+"aavowelsigndeva","aavowelsigngujarati","abbreviationmarkarmenian",
+"abbreviationsigndeva","abengali","abopomofo","abreve","abreveacute",
+"abrevecyrillic","abrevedotbelow","abrevegrave","abrevehookabove",
+"abrevetilde","acaron","acircle","acircumflex","acircumflexacute",
+"acircumflexdotbelow","acircumflexgrave","acircumflexhookabove",
+"acircumflextilde","acute","acutebelowcmb","acutecmb","acutecomb","acutedeva",
+"acutelowmod","acutetonecmb","acyrillic","adblgrave","addakgurmukhi","adeva",
+"adieresis","adieresiscyrillic","adieresismacron","adotbelow","adotmacron",
+"ae","aeacute","aekorean","aemacron","afii00208","afii08941","afii10017",
+"afii10018","afii10019","afii10020","afii10021","afii10022","afii10023",
+"afii10024","afii10025","afii10026","afii10027","afii10028","afii10029",
+"afii10030","afii10031","afii10032","afii10033","afii10034","afii10035",
+"afii10036","afii10037","afii10038","afii10039","afii10040","afii10041",
+"afii10042","afii10043","afii10044","afii10045","afii10046","afii10047",
+"afii10048","afii10049","afii10050","afii10051","afii10052","afii10053",
+"afii10054","afii10055","afii10056","afii10057","afii10058","afii10059",
+"afii10060","afii10061","afii10062","afii10063","afii10064","afii10065",
+"afii10066","afii10067","afii10068","afii10069","afii10070","afii10071",
+"afii10072","afii10073","afii10074","afii10075","afii10076","afii10077",
+"afii10078","afii10079","afii10080","afii10081","afii10082","afii10083",
+"afii10084","afii10085","afii10086","afii10087","afii10088","afii10089",
+"afii10090","afii10091","afii10092","afii10093","afii10094","afii10095",
+"afii10096","afii10097","afii10098","afii10099","afii10100","afii10101",
+"afii10102","afii10103","afii10104","afii10105","afii10106","afii10107",
+"afii10108","afii10109","afii10110","afii10145","afii10146","afii10147",
+"afii10148","afii10192","afii10193","afii10194","afii10195","afii10196",
+"afii10831","afii10832","afii10846","afii299","afii300","afii301","afii57381",
+"afii57388","afii57392","afii57393","afii57394","afii57395","afii57396",
+"afii57397","afii57398","afii57399","afii57400","afii57401","afii57403",
+"afii57407","afii57409","afii57410","afii57411","afii57412","afii57413",
+"afii57414","afii57415","afii57416","afii57417","afii57418","afii57419",
+"afii57420","afii57421","afii57422","afii57423","afii57424","afii57425",
+"afii57426","afii57427","afii57428","afii57429","afii57430","afii57431",
+"afii57432","afii57433","afii57434","afii57440","afii57441","afii57442",
+"afii57443","afii57444","afii57445","afii57446","afii57448","afii57449",
+"afii57450","afii57451","afii57452","afii57453","afii57454","afii57455",
+"afii57456","afii57457","afii57458","afii57470","afii57505","afii57506",
+"afii57507","afii57508","afii57509","afii57511","afii57512","afii57513",
+"afii57514","afii57519","afii57534","afii57636","afii57645","afii57658",
+"afii57664","afii57665","afii57666","afii57667","afii57668","afii57669",
+"afii57670","afii57671","afii57672","afii57673","afii57674","afii57675",
+"afii57676","afii57677","afii57678","afii57679","afii57680","afii57681",
+"afii57682","afii57683","afii57684","afii57685","afii57686","afii57687",
+"afii57688","afii57689","afii57690","afii57694","afii57695","afii57700",
+"afii57705","afii57716","afii57717","afii57718","afii57723","afii57793",
+"afii57794","afii57795","afii57796","afii57797","afii57798","afii57799",
+"afii57800","afii57801","afii57802","afii57803","afii57804","afii57806",
+"afii57807","afii57839","afii57841","afii57842","afii57929","afii61248",
+"afii61289","afii61352","afii61573","afii61574","afii61575","afii61664",
+"afii63167","afii64937","agrave","agujarati","agurmukhi","ahiragana",
+"ahookabove","aibengali","aibopomofo","aideva","aiecyrillic","aigujarati",
+"aigurmukhi","aimatragurmukhi","ainarabic","ainfinalarabic",
+"aininitialarabic","ainmedialarabic","ainvertedbreve","aivowelsignbengali",
+"aivowelsigndeva","aivowelsigngujarati","akatakana","akatakanahalfwidth",
+"akorean","alef","alefarabic","alefdageshhebrew","aleffinalarabic",
+"alefhamzaabovearabic","alefhamzaabovefinalarabic","alefhamzabelowarabic",
+"alefhamzabelowfinalarabic","alefhebrew","aleflamedhebrew",
+"alefmaddaabovearabic","alefmaddaabovefinalarabic","alefmaksuraarabic",
+"alefmaksurafinalarabic","alefmaksurainitialarabic","alefmaksuramedialarabic",
+"alefpatahhebrew","alefqamatshebrew","aleph","allequal","alpha","alphatonos",
+"amacron","amonospace","ampersand","ampersandmonospace","ampersandsmall",
+"amsquare","anbopomofo","angbopomofo","angkhankhuthai","angle",
+"anglebracketleft","anglebracketleftvertical","anglebracketright",
+"anglebracketrightvertical","angleleft","angleright","angstrom","anoteleia",
+"anudattadeva","anusvarabengali","anusvaradeva","anusvaragujarati","aogonek",
+"apaatosquare","aparen","apostrophearmenian","apostrophemod","apple",
+"approaches","approxequal","approxequalorimage","approximatelyequal",
+"araeaekorean","araeakorean","arc","arighthalfring","aring","aringacute",
+"aringbelow","arrowboth","arrowdashdown","arrowdashleft","arrowdashright",
+"arrowdashup","arrowdblboth","arrowdbldown","arrowdblleft","arrowdblright",
+"arrowdblup","arrowdown","arrowdownleft","arrowdownright","arrowdownwhite",
+"arrowheaddownmod","arrowheadleftmod","arrowheadrightmod","arrowheadupmod",
+"arrowhorizex","arrowleft","arrowleftdbl","arrowleftdblstroke",
+"arrowleftoverright","arrowleftwhite","arrowright","arrowrightdblstroke",
+"arrowrightheavy","arrowrightoverleft","arrowrightwhite","arrowtableft",
+"arrowtabright","arrowup","arrowupdn","arrowupdnbse","arrowupdownbase",
+"arrowupleft","arrowupleftofdown","arrowupright","arrowupwhite","arrowvertex",
+"asciicircum","asciicircummonospace","asciitilde","asciitildemonospace",
+"ascript","ascriptturned","asmallhiragana","asmallkatakana",
+"asmallkatakanahalfwidth","asterisk","asteriskaltonearabic","asteriskarabic",
+"asteriskmath","asteriskmonospace","asterisksmall","asterism","asuperior",
+"asymptoticallyequal","at","atilde","atmonospace","atsmall","aturned",
+"aubengali","aubopomofo","audeva","augujarati","augurmukhi",
+"aulengthmarkbengali","aumatragurmukhi","auvowelsignbengali",
+"auvowelsigndeva","auvowelsigngujarati","avagrahadeva","aybarmenian","ayin",
+"ayinaltonehebrew","ayinhebrew","b","babengali","backslash",
+"backslashmonospace","badeva","bagujarati","bagurmukhi","bahiragana",
+"bahtthai","bakatakana","bar","barmonospace","bbopomofo","bcircle",
+"bdotaccent","bdotbelow","beamedsixteenthnotes","because","becyrillic",
+"beharabic","behfinalarabic","behinitialarabic","behiragana",
+"behmedialarabic","behmeeminitialarabic","behmeemisolatedarabic",
+"behnoonfinalarabic","bekatakana","benarmenian","bet","beta",
+"betasymbolgreek","betdagesh","betdageshhebrew","bethebrew","betrafehebrew",
+"bhabengali","bhadeva","bhagujarati","bhagurmukhi","bhook","bihiragana",
+"bikatakana","bilabialclick","bindigurmukhi","birusquare","blackcircle",
+"blackdiamond","blackdownpointingtriangle","blackleftpointingpointer",
+"blackleftpointingtriangle","blacklenticularbracketleft",
+"blacklenticularbracketleftvertical","blacklenticularbracketright",
+"blacklenticularbracketrightvertical","blacklowerlefttriangle",
+"blacklowerrighttriangle","blackrectangle","blackrightpointingpointer",
+"blackrightpointingtriangle","blacksmallsquare","blacksmilingface",
+"blacksquare","blackstar","blackupperlefttriangle","blackupperrighttriangle",
+"blackuppointingsmalltriangle","blackuppointingtriangle","blank","blinebelow",
+"block","bmonospace","bobaimaithai","bohiragana","bokatakana","bparen",
+"bqsquare","braceex","braceleft","braceleftbt","braceleftmid",
+"braceleftmonospace","braceleftsmall","bracelefttp","braceleftvertical",
+"braceright","bracerightbt","bracerightmid","bracerightmonospace",
+"bracerightsmall","bracerighttp","bracerightvertical","bracketleft",
+"bracketleftbt","bracketleftex","bracketleftmonospace","bracketlefttp",
+"bracketright","bracketrightbt","bracketrightex","bracketrightmonospace",
+"bracketrighttp","breve","brevebelowcmb","brevecmb","breveinvertedbelowcmb",
+"breveinvertedcmb","breveinverteddoublecmb","bridgebelowcmb",
+"bridgeinvertedbelowcmb","brokenbar","bstroke","bsuperior","btopbar",
+"buhiragana","bukatakana","bullet","bulletinverse","bulletoperator",
+"bullseye","c","caarmenian","cabengali","cacute","cadeva","cagujarati",
+"cagurmukhi","calsquare","candrabindubengali","candrabinducmb",
+"candrabindudeva","candrabindugujarati","capslock","careof","caron",
+"caronbelowcmb","caroncmb","carriagereturn","cbopomofo","ccaron","ccedilla",
+"ccedillaacute","ccircle","ccircumflex","ccurl","cdot","cdotaccent",
+"cdsquare","cedilla","cedillacmb","cent","centigrade","centinferior",
+"centmonospace","centoldstyle","centsuperior","chaarmenian","chabengali",
+"chadeva","chagujarati","chagurmukhi","chbopomofo","cheabkhasiancyrillic",
+"checkmark","checyrillic","chedescenderabkhasiancyrillic",
+"chedescendercyrillic","chedieresiscyrillic","cheharmenian",
+"chekhakassiancyrillic","cheverticalstrokecyrillic","chi",
+"chieuchacirclekorean","chieuchaparenkorean","chieuchcirclekorean",
+"chieuchkorean","chieuchparenkorean","chochangthai","chochanthai",
+"chochingthai","chochoethai","chook","cieucacirclekorean","cieucaparenkorean",
+"cieuccirclekorean","cieuckorean","cieucparenkorean","cieucuparenkorean",
+"circle","circlemultiply","circleot","circleplus","circlepostalmark",
+"circlewithlefthalfblack","circlewithrighthalfblack","circumflex",
+"circumflexbelowcmb","circumflexcmb","clear","clickalveolar","clickdental",
+"clicklateral","clickretroflex","club","clubsuitblack","clubsuitwhite",
+"cmcubedsquare","cmonospace","cmsquaredsquare","coarmenian","colon",
+"colonmonetary","colonmonospace","colonsign","colonsmall",
+"colontriangularhalfmod","colontriangularmod","comma","commaabovecmb",
+"commaaboverightcmb","commaaccent","commaarabic","commaarmenian",
+"commainferior","commamonospace","commareversedabovecmb","commareversedmod",
+"commasmall","commasuperior","commaturnedabovecmb","commaturnedmod","compass",
+"congruent","contourintegral","control","controlACK","controlBEL","controlBS",
+"controlCAN","controlCR","controlDC1","controlDC2","controlDC3","controlDC4",
+"controlDEL","controlDLE","controlEM","controlENQ","controlEOT","controlESC",
+"controlETB","controlETX","controlFF","controlFS","controlGS","controlHT",
+"controlLF","controlNAK","controlRS","controlSI","controlSO","controlSOT",
+"controlSTX","controlSUB","controlSYN","controlUS","controlVT","copyright",
+"copyrightsans","copyrightserif","cornerbracketleft",
+"cornerbracketlefthalfwidth","cornerbracketleftvertical","cornerbracketright",
+"cornerbracketrighthalfwidth","cornerbracketrightvertical",
+"corporationsquare","cosquare","coverkgsquare","cparen","cruzeiro",
+"cstretched","curlyand","curlyor","currency","cyrBreve","cyrFlex","cyrbreve",
+"cyrflex","d","daarmenian","dabengali","dadarabic","dadeva","dadfinalarabic",
+"dadinitialarabic","dadmedialarabic","dagesh","dageshhebrew","dagger",
+"daggerdbl","dagujarati","dagurmukhi","dahiragana","dakatakana","dalarabic",
+"dalet","daletdagesh","daletdageshhebrew","dalethatafpatah",
+"dalethatafpatahhebrew","dalethatafsegol","dalethatafsegolhebrew",
+"dalethebrew","dalethiriq","dalethiriqhebrew","daletholam","daletholamhebrew",
+"daletpatah","daletpatahhebrew","daletqamats","daletqamatshebrew",
+"daletqubuts","daletqubutshebrew","daletsegol","daletsegolhebrew",
+"daletsheva","daletshevahebrew","dalettsere","dalettserehebrew",
+"dalfinalarabic","dammaarabic","dammalowarabic","dammatanaltonearabic",
+"dammatanarabic","danda","dargahebrew","dargalefthebrew",
+"dasiapneumatacyrilliccmb","dblGrave","dblanglebracketleft",
+"dblanglebracketleftvertical","dblanglebracketright",
+"dblanglebracketrightvertical","dblarchinvertedbelowcmb","dblarrowleft",
+"dblarrowright","dbldanda","dblgrave","dblgravecmb","dblintegral",
+"dbllowline","dbllowlinecmb","dbloverlinecmb","dblprimemod","dblverticalbar",
+"dblverticallineabovecmb","dbopomofo","dbsquare","dcaron","dcedilla",
+"dcircle","dcircumflexbelow","dcroat","ddabengali","ddadeva","ddagujarati",
+"ddagurmukhi","ddalarabic","ddalfinalarabic","dddhadeva","ddhabengali",
+"ddhadeva","ddhagujarati","ddhagurmukhi","ddotaccent","ddotbelow",
+"decimalseparatorarabic","decimalseparatorpersian","decyrillic","degree",
+"dehihebrew","dehiragana","deicoptic","dekatakana","deleteleft","deleteright",
+"delta","deltaturned","denominatorminusonenumeratorbengali","dezh",
+"dhabengali","dhadeva","dhagujarati","dhagurmukhi","dhook","dialytikatonos",
+"dialytikatonoscmb","diamond","diamondsuitwhite","dieresis","dieresisacute",
+"dieresisbelowcmb","dieresiscmb","dieresisgrave","dieresistonos","dihiragana",
+"dikatakana","dittomark","divide","divides","divisionslash","djecyrillic",
+"dkshade","dlinebelow","dlsquare","dmacron","dmonospace","dnblock",
+"dochadathai","dodekthai","dohiragana","dokatakana","dollar","dollarinferior",
+"dollarmonospace","dollaroldstyle","dollarsmall","dollarsuperior","dong",
+"dorusquare","dotaccent","dotaccentcmb","dotbelowcmb","dotbelowcomb",
+"dotkatakana","dotlessi","dotlessj","dotlessjstrokehook","dotmath",
+"dottedcircle","doubleyodpatah","doubleyodpatahhebrew","downtackbelowcmb",
+"downtackmod","dparen","dsuperior","dtail","dtopbar","duhiragana",
+"dukatakana","dz","dzaltone","dzcaron","dzcurl","dzeabkhasiancyrillic",
+"dzecyrillic","dzhecyrillic","e","eacute","earth","ebengali","ebopomofo",
+"ebreve","ecandradeva","ecandragujarati","ecandravowelsigndeva",
+"ecandravowelsigngujarati","ecaron","ecedillabreve","echarmenian",
+"echyiwnarmenian","ecircle","ecircumflex","ecircumflexacute",
+"ecircumflexbelow","ecircumflexdotbelow","ecircumflexgrave",
+"ecircumflexhookabove","ecircumflextilde","ecyrillic","edblgrave","edeva",
+"edieresis","edot","edotaccent","edotbelow","eegurmukhi","eematragurmukhi",
+"efcyrillic","egrave","egujarati","eharmenian","ehbopomofo","ehiragana",
+"ehookabove","eibopomofo","eight","eightarabic","eightbengali","eightcircle",
+"eightcircleinversesansserif","eightdeva","eighteencircle","eighteenparen",
+"eighteenperiod","eightgujarati","eightgurmukhi","eighthackarabic",
+"eighthangzhou","eighthnotebeamed","eightideographicparen","eightinferior",
+"eightmonospace","eightoldstyle","eightparen","eightperiod","eightpersian",
+"eightroman","eightsuperior","eightthai","einvertedbreve","eiotifiedcyrillic",
+"ekatakana","ekatakanahalfwidth","ekonkargurmukhi","ekorean","elcyrillic",
+"element","elevencircle","elevenparen","elevenperiod","elevenroman",
+"ellipsis","ellipsisvertical","emacron","emacronacute","emacrongrave",
+"emcyrillic","emdash","emdashvertical","emonospace","emphasismarkarmenian",
+"emptyset","enbopomofo","encyrillic","endash","endashvertical",
+"endescendercyrillic","eng","engbopomofo","enghecyrillic","enhookcyrillic",
+"enspace","eogonek","eokorean","eopen","eopenclosed","eopenreversed",
+"eopenreversedclosed","eopenreversedhook","eparen","epsilon","epsilontonos",
+"equal","equalmonospace","equalsmall","equalsuperior","equivalence",
+"erbopomofo","ercyrillic","ereversed","ereversedcyrillic","escyrillic",
+"esdescendercyrillic","esh","eshcurl","eshortdeva","eshortvowelsigndeva",
+"eshreversedloop","eshsquatreversed","esmallhiragana","esmallkatakana",
+"esmallkatakanahalfwidth","estimated","esuperior","eta","etarmenian",
+"etatonos","eth","etilde","etildebelow","etnahtafoukhhebrew",
+"etnahtafoukhlefthebrew","etnahtahebrew","etnahtalefthebrew","eturned",
+"eukorean","euro","evowelsignbengali","evowelsigndeva","evowelsigngujarati",
+"exclam","exclamarmenian","exclamdbl","exclamdown","exclamdownsmall",
+"exclammonospace","exclamsmall","existential","ezh","ezhcaron","ezhcurl",
+"ezhreversed","ezhtail","f","fadeva","fagurmukhi","fahrenheit","fathaarabic",
+"fathalowarabic","fathatanarabic","fbopomofo","fcircle","fdotaccent",
+"feharabic","feharmenian","fehfinalarabic","fehinitialarabic",
+"fehmedialarabic","feicoptic","female","ff","ffi","ffl","fi","fifteencircle",
+"fifteenparen","fifteenperiod","figuredash","filledbox","filledrect",
+"finalkaf","finalkafdagesh","finalkafdageshhebrew","finalkafhebrew",
+"finalkafqamats","finalkafqamatshebrew","finalkafsheva","finalkafshevahebrew",
+"finalmem","finalmemhebrew","finalnun","finalnunhebrew","finalpe",
+"finalpehebrew","finaltsadi","finaltsadihebrew","firsttonechinese","fisheye",
+"fitacyrillic","five","fivearabic","fivebengali","fivecircle",
+"fivecircleinversesansserif","fivedeva","fiveeighths","fivegujarati",
+"fivegurmukhi","fivehackarabic","fivehangzhou","fiveideographicparen",
+"fiveinferior","fivemonospace","fiveoldstyle","fiveparen","fiveperiod",
+"fivepersian","fiveroman","fivesuperior","fivethai","fl","florin",
+"fmonospace","fmsquare","fofanthai","fofathai","fongmanthai","forall","four",
+"fourarabic","fourbengali","fourcircle","fourcircleinversesansserif",
+"fourdeva","fourgujarati","fourgurmukhi","fourhackarabic","fourhangzhou",
+"fourideographicparen","fourinferior","fourmonospace","fournumeratorbengali",
+"fouroldstyle","fourparen","fourperiod","fourpersian","fourroman",
+"foursuperior","fourteencircle","fourteenparen","fourteenperiod","fourthai",
+"fourthtonechinese","fparen","fraction","franc","g","gabengali","gacute",
+"gadeva","gafarabic","gaffinalarabic","gafinitialarabic","gafmedialarabic",
+"gagujarati","gagurmukhi","gahiragana","gakatakana","gamma","gammalatinsmall",
+"gammasuperior","gangiacoptic","gbopomofo","gbreve","gcaron","gcedilla",
+"gcircle","gcircumflex","gcommaaccent","gdot","gdotaccent","gecyrillic",
+"gehiragana","gekatakana","geometricallyequal","gereshaccenthebrew",
+"gereshhebrew","gereshmuqdamhebrew","germandbls","gershayimaccenthebrew",
+"gershayimhebrew","getamark","ghabengali","ghadarmenian","ghadeva",
+"ghagujarati","ghagurmukhi","ghainarabic","ghainfinalarabic",
+"ghaininitialarabic","ghainmedialarabic","ghemiddlehookcyrillic",
+"ghestrokecyrillic","gheupturncyrillic","ghhadeva","ghhagurmukhi","ghook",
+"ghzsquare","gihiragana","gikatakana","gimarmenian","gimel","gimeldagesh",
+"gimeldageshhebrew","gimelhebrew","gjecyrillic","glottalinvertedstroke",
+"glottalstop","glottalstopinverted","glottalstopmod","glottalstopreversed",
+"glottalstopreversedmod","glottalstopreversedsuperior","glottalstopstroke",
+"glottalstopstrokereversed","gmacron","gmonospace","gohiragana","gokatakana",
+"gparen","gpasquare","gradient","grave","gravebelowcmb","gravecmb",
+"gravecomb","gravedeva","gravelowmod","gravemonospace","gravetonecmb",
+"greater","greaterequal","greaterequalorless","greatermonospace",
+"greaterorequivalent","greaterorless","greateroverequal","greatersmall",
+"gscript","gstroke","guhiragana","guillemotleft","guillemotright",
+"guilsinglleft","guilsinglright","gukatakana","guramusquare","gysquare","h",
+"haabkhasiancyrillic","haaltonearabic","habengali","hadescendercyrillic",
+"hadeva","hagujarati","hagurmukhi","haharabic","hahfinalarabic",
+"hahinitialarabic","hahiragana","hahmedialarabic","haitusquare","hakatakana",
+"hakatakanahalfwidth","halantgurmukhi","hamzaarabic","hamzadammaarabic",
+"hamzadammatanarabic","hamzafathaarabic","hamzafathatanarabic",
+"hamzalowarabic","hamzalowkasraarabic","hamzalowkasratanarabic",
+"hamzasukunarabic","hangulfiller","hardsigncyrillic","harpoonleftbarbup",
+"harpoonrightbarbup","hasquare","hatafpatah","hatafpatah16","hatafpatah23",
+"hatafpatah2f","hatafpatahhebrew","hatafpatahnarrowhebrew",
+"hatafpatahquarterhebrew","hatafpatahwidehebrew","hatafqamats",
+"hatafqamats1b","hatafqamats28","hatafqamats34","hatafqamatshebrew",
+"hatafqamatsnarrowhebrew","hatafqamatsquarterhebrew","hatafqamatswidehebrew",
+"hatafsegol","hatafsegol17","hatafsegol24","hatafsegol30","hatafsegolhebrew",
+"hatafsegolnarrowhebrew","hatafsegolquarterhebrew","hatafsegolwidehebrew",
+"hbar","hbopomofo","hbrevebelow","hcedilla","hcircle","hcircumflex",
+"hdieresis","hdotaccent","hdotbelow","he","heart","heartsuitblack",
+"heartsuitwhite","hedagesh","hedageshhebrew","hehaltonearabic","heharabic",
+"hehebrew","hehfinalaltonearabic","hehfinalalttwoarabic","hehfinalarabic",
+"hehhamzaabovefinalarabic","hehhamzaaboveisolatedarabic",
+"hehinitialaltonearabic","hehinitialarabic","hehiragana",
+"hehmedialaltonearabic","hehmedialarabic","heiseierasquare","hekatakana",
+"hekatakanahalfwidth","hekutaarusquare","henghook","herutusquare","het",
+"hethebrew","hhook","hhooksuperior","hieuhacirclekorean","hieuhaparenkorean",
+"hieuhcirclekorean","hieuhkorean","hieuhparenkorean","hihiragana",
+"hikatakana","hikatakanahalfwidth","hiriq","hiriq14","hiriq21","hiriq2d",
+"hiriqhebrew","hiriqnarrowhebrew","hiriqquarterhebrew","hiriqwidehebrew",
+"hlinebelow","hmonospace","hoarmenian","hohipthai","hohiragana","hokatakana",
+"hokatakanahalfwidth","holam","holam19","holam26","holam32","holamhebrew",
+"holamnarrowhebrew","holamquarterhebrew","holamwidehebrew","honokhukthai",
+"hookabovecomb","hookcmb","hookpalatalizedbelowcmb","hookretroflexbelowcmb",
+"hoonsquare","horicoptic","horizontalbar","horncmb","hotsprings","house",
+"hparen","hsuperior","hturned","huhiragana","huiitosquare","hukatakana",
+"hukatakanahalfwidth","hungarumlaut","hungarumlautcmb","hv","hyphen",
+"hypheninferior","hyphenmonospace","hyphensmall","hyphensuperior","hyphentwo",
+"i","iacute","iacyrillic","ibengali","ibopomofo","ibreve","icaron","icircle",
+"icircumflex","icyrillic","idblgrave","ideographearthcircle",
+"ideographfirecircle","ideographicallianceparen","ideographiccallparen",
+"ideographiccentrecircle","ideographicclose","ideographiccomma",
+"ideographiccommaleft","ideographiccongratulationparen",
+"ideographiccorrectcircle","ideographicearthparen",
+"ideographicenterpriseparen","ideographicexcellentcircle",
+"ideographicfestivalparen","ideographicfinancialcircle",
+"ideographicfinancialparen","ideographicfireparen","ideographichaveparen",
+"ideographichighcircle","ideographiciterationmark","ideographiclaborcircle",
+"ideographiclaborparen","ideographicleftcircle","ideographiclowcircle",
+"ideographicmedicinecircle","ideographicmetalparen","ideographicmoonparen",
+"ideographicnameparen","ideographicperiod","ideographicprintcircle",
+"ideographicreachparen","ideographicrepresentparen",
+"ideographicresourceparen","ideographicrightcircle","ideographicsecretcircle",
+"ideographicselfparen","ideographicsocietyparen","ideographicspace",
+"ideographicspecialparen","ideographicstockparen","ideographicstudyparen",
+"ideographicsunparen","ideographicsuperviseparen","ideographicwaterparen",
+"ideographicwoodparen","ideographiczero","ideographmetalcircle",
+"ideographmooncircle","ideographnamecircle","ideographsuncircle",
+"ideographwatercircle","ideographwoodcircle","ideva","idieresis",
+"idieresisacute","idieresiscyrillic","idotbelow","iebrevecyrillic",
+"iecyrillic","ieungacirclekorean","ieungaparenkorean","ieungcirclekorean",
+"ieungkorean","ieungparenkorean","igrave","igujarati","igurmukhi","ihiragana",
+"ihookabove","iibengali","iicyrillic","iideva","iigujarati","iigurmukhi",
+"iimatragurmukhi","iinvertedbreve","iishortcyrillic","iivowelsignbengali",
+"iivowelsigndeva","iivowelsigngujarati","ij","ikatakana","ikatakanahalfwidth",
+"ikorean","ilde","iluyhebrew","imacron","imacroncyrillic",
+"imageorapproximatelyequal","imatragurmukhi","imonospace","increment",
+"infinity","iniarmenian","integral","integralbottom","integralbt",
+"integralex","integraltop","integraltp","intersection","intisquare",
+"invbullet","invcircle","invsmileface","iocyrillic","iogonek","iota",
+"iotadieresis","iotadieresistonos","iotalatin","iotatonos","iparen",
+"irigurmukhi","ismallhiragana","ismallkatakana","ismallkatakanahalfwidth",
+"issharbengali","istroke","isuperior","iterationhiragana","iterationkatakana",
+"itilde","itildebelow","iubopomofo","iucyrillic","ivowelsignbengali",
+"ivowelsigndeva","ivowelsigngujarati","izhitsacyrillic",
+"izhitsadblgravecyrillic","j","jaarmenian","jabengali","jadeva","jagujarati",
+"jagurmukhi","jbopomofo","jcaron","jcircle","jcircumflex","jcrossedtail",
+"jdotlessstroke","jecyrillic","jeemarabic","jeemfinalarabic",
+"jeeminitialarabic","jeemmedialarabic","jeharabic","jehfinalarabic",
+"jhabengali","jhadeva","jhagujarati","jhagurmukhi","jheharmenian","jis",
+"jmonospace","jparen","jsuperior","k","kabashkircyrillic","kabengali",
+"kacute","kacyrillic","kadescendercyrillic","kadeva","kaf","kafarabic",
+"kafdagesh","kafdageshhebrew","kaffinalarabic","kafhebrew","kafinitialarabic",
+"kafmedialarabic","kafrafehebrew","kagujarati","kagurmukhi","kahiragana",
+"kahookcyrillic","kakatakana","kakatakanahalfwidth","kappa",
+"kappasymbolgreek","kapyeounmieumkorean","kapyeounphieuphkorean",
+"kapyeounpieupkorean","kapyeounssangpieupkorean","karoriisquare",
+"kashidaautoarabic","kashidaautonosidebearingarabic","kasmallkatakana",
+"kasquare","kasraarabic","kasratanarabic","kastrokecyrillic",
+"katahiraprolongmarkhalfwidth","kaverticalstrokecyrillic","kbopomofo",
+"kcalsquare","kcaron","kcedilla","kcircle","kcommaaccent","kdotbelow",
+"keharmenian","kehiragana","kekatakana","kekatakanahalfwidth","kenarmenian",
+"kesmallkatakana","kgreenlandic","khabengali","khacyrillic","khadeva",
+"khagujarati","khagurmukhi","khaharabic","khahfinalarabic",
+"khahinitialarabic","khahmedialarabic","kheicoptic","khhadeva","khhagurmukhi",
+"khieukhacirclekorean","khieukhaparenkorean","khieukhcirclekorean",
+"khieukhkorean","khieukhparenkorean","khokhaithai","khokhonthai",
+"khokhuatthai","khokhwaithai","khomutthai","khook","khorakhangthai",
+"khzsquare","kihiragana","kikatakana","kikatakanahalfwidth",
+"kiroguramusquare","kiromeetorusquare","kirosquare","kiyeokacirclekorean",
+"kiyeokaparenkorean","kiyeokcirclekorean","kiyeokkorean","kiyeokparenkorean",
+"kiyeoksioskorean","kjecyrillic","klinebelow","klsquare","kmcubedsquare",
+"kmonospace","kmsquaredsquare","kohiragana","kohmsquare","kokaithai",
+"kokatakana","kokatakanahalfwidth","kooposquare","koppacyrillic",
+"koreanstandardsymbol","koroniscmb","kparen","kpasquare","ksicyrillic",
+"ktsquare","kturned","kuhiragana","kukatakana","kukatakanahalfwidth",
+"kvsquare","kwsquare","l","labengali","lacute","ladeva","lagujarati",
+"lagurmukhi","lakkhangyaothai","lamaleffinalarabic",
+"lamalefhamzaabovefinalarabic","lamalefhamzaaboveisolatedarabic",
+"lamalefhamzabelowfinalarabic","lamalefhamzabelowisolatedarabic",
+"lamalefisolatedarabic","lamalefmaddaabovefinalarabic",
+"lamalefmaddaaboveisolatedarabic","lamarabic","lambda","lambdastroke","lamed",
+"lameddagesh","lameddageshhebrew","lamedhebrew","lamedholam",
+"lamedholamdagesh","lamedholamdageshhebrew","lamedholamhebrew",
+"lamfinalarabic","lamhahinitialarabic","laminitialarabic",
+"lamjeeminitialarabic","lamkhahinitialarabic","lamlamhehisolatedarabic",
+"lammedialarabic","lammeemhahinitialarabic","lammeeminitialarabic",
+"lammeemjeeminitialarabic","lammeemkhahinitialarabic","largecircle","lbar",
+"lbelt","lbopomofo","lcaron","lcedilla","lcircle","lcircumflexbelow",
+"lcommaaccent","ldot","ldotaccent","ldotbelow","ldotbelowmacron",
+"leftangleabovecmb","lefttackbelowcmb","less","lessequal",
+"lessequalorgreater","lessmonospace","lessorequivalent","lessorgreater",
+"lessoverequal","lesssmall","lezh","lfblock","lhookretroflex","lira",
+"liwnarmenian","lj","ljecyrillic","ll","lladeva","llagujarati","llinebelow",
+"llladeva","llvocalicbengali","llvocalicdeva","llvocalicvowelsignbengali",
+"llvocalicvowelsigndeva","lmiddletilde","lmonospace","lmsquare","lochulathai",
+"logicaland","logicalnot","logicalnotreversed","logicalor","lolingthai",
+"longs","lowlinecenterline","lowlinecmb","lowlinedashed","lozenge","lparen",
+"lslash","lsquare","lsuperior","ltshade","luthai","lvocalicbengali",
+"lvocalicdeva","lvocalicvowelsignbengali","lvocalicvowelsigndeva","lxsquare",
+"m","mabengali","macron","macronbelowcmb","macroncmb","macronlowmod",
+"macronmonospace","macute","madeva","magujarati","magurmukhi",
+"mahapakhhebrew","mahapakhlefthebrew","mahiragana","maichattawalowleftthai",
+"maichattawalowrightthai","maichattawathai","maichattawaupperleftthai",
+"maieklowleftthai","maieklowrightthai","maiekthai","maiekupperleftthai",
+"maihanakatleftthai","maihanakatthai","maitaikhuleftthai","maitaikhuthai",
+"maitholowleftthai","maitholowrightthai","maithothai","maithoupperleftthai",
+"maitrilowleftthai","maitrilowrightthai","maitrithai","maitriupperleftthai",
+"maiyamokthai","makatakana","makatakanahalfwidth","male","mansyonsquare",
+"maqafhebrew","mars","masoracirclehebrew","masquare","mbopomofo","mbsquare",
+"mcircle","mcubedsquare","mdotaccent","mdotbelow","meemarabic",
+"meemfinalarabic","meeminitialarabic","meemmedialarabic",
+"meemmeeminitialarabic","meemmeemisolatedarabic","meetorusquare","mehiragana",
+"meizierasquare","mekatakana","mekatakanahalfwidth","mem","memdagesh",
+"memdageshhebrew","memhebrew","menarmenian","merkhahebrew",
+"merkhakefulahebrew","merkhakefulalefthebrew","merkhalefthebrew","mhook",
+"mhzsquare","middledotkatakanahalfwidth","middot","mieumacirclekorean",
+"mieumaparenkorean","mieumcirclekorean","mieumkorean","mieumpansioskorean",
+"mieumparenkorean","mieumpieupkorean","mieumsioskorean","mihiragana",
+"mikatakana","mikatakanahalfwidth","minus","minusbelowcmb","minuscircle",
+"minusmod","minusplus","minute","miribaarusquare","mirisquare",
+"mlonglegturned","mlsquare","mmcubedsquare","mmonospace","mmsquaredsquare",
+"mohiragana","mohmsquare","mokatakana","mokatakanahalfwidth","molsquare",
+"momathai","moverssquare","moverssquaredsquare","mparen","mpasquare",
+"mssquare","msuperior","mturned","mu","mu1","muasquare","muchgreater",
+"muchless","mufsquare","mugreek","mugsquare","muhiragana","mukatakana",
+"mukatakanahalfwidth","mulsquare","multiply","mumsquare","munahhebrew",
+"munahlefthebrew","musicalnote","musicalnotedbl","musicflatsign",
+"musicsharpsign","mussquare","muvsquare","muwsquare","mvmegasquare",
+"mvsquare","mwmegasquare","mwsquare","n","nabengali","nabla","nacute",
+"nadeva","nagujarati","nagurmukhi","nahiragana","nakatakana",
+"nakatakanahalfwidth","napostrophe","nasquare","nbopomofo","nbspace","ncaron",
+"ncedilla","ncircle","ncircumflexbelow","ncommaaccent","ndotaccent",
+"ndotbelow","nehiragana","nekatakana","nekatakanahalfwidth","newsheqelsign",
+"nfsquare","ngabengali","ngadeva","ngagujarati","ngagurmukhi","ngonguthai",
+"nhiragana","nhookleft","nhookretroflex","nieunacirclekorean",
+"nieunaparenkorean","nieuncieuckorean","nieuncirclekorean","nieunhieuhkorean",
+"nieunkorean","nieunpansioskorean","nieunparenkorean","nieunsioskorean",
+"nieuntikeutkorean","nihiragana","nikatakana","nikatakanahalfwidth",
+"nikhahitleftthai","nikhahitthai","nine","ninearabic","ninebengali",
+"ninecircle","ninecircleinversesansserif","ninedeva","ninegujarati",
+"ninegurmukhi","ninehackarabic","ninehangzhou","nineideographicparen",
+"nineinferior","ninemonospace","nineoldstyle","nineparen","nineperiod",
+"ninepersian","nineroman","ninesuperior","nineteencircle","nineteenparen",
+"nineteenperiod","ninethai","nj","njecyrillic","nkatakana",
+"nkatakanahalfwidth","nlegrightlong","nlinebelow","nmonospace","nmsquare",
+"nnabengali","nnadeva","nnagujarati","nnagurmukhi","nnnadeva","nohiragana",
+"nokatakana","nokatakanahalfwidth","nonbreakingspace","nonenthai","nonuthai",
+"noonarabic","noonfinalarabic","noonghunnaarabic","noonghunnafinalarabic",
+"noonhehinitialarabic","nooninitialarabic","noonjeeminitialarabic",
+"noonjeemisolatedarabic","noonmedialarabic","noonmeeminitialarabic",
+"noonmeemisolatedarabic","noonnoonfinalarabic","notcontains","notelement",
+"notelementof","notequal","notgreater","notgreaternorequal",
+"notgreaternorless","notidentical","notless","notlessnorequal","notparallel",
+"notprecedes","notsubset","notsucceeds","notsuperset","nowarmenian","nparen",
+"nssquare","nsuperior","ntilde","nu","nuhiragana","nukatakana",
+"nukatakanahalfwidth","nuktabengali","nuktadeva","nuktagujarati",
+"nuktagurmukhi","numbersign","numbersignmonospace","numbersignsmall",
+"numeralsigngreek","numeralsignlowergreek","numero","nun","nundagesh",
+"nundageshhebrew","nunhebrew","nvsquare","nwsquare","nyabengali","nyadeva",
+"nyagujarati","nyagurmukhi","o","oacute","oangthai","obarred",
+"obarredcyrillic","obarreddieresiscyrillic","obengali","obopomofo","obreve",
+"ocandradeva","ocandragujarati","ocandravowelsigndeva",
+"ocandravowelsigngujarati","ocaron","ocircle","ocircumflex",
+"ocircumflexacute","ocircumflexdotbelow","ocircumflexgrave",
+"ocircumflexhookabove","ocircumflextilde","ocyrillic","odblacute","odblgrave",
+"odeva","odieresis","odieresiscyrillic","odotbelow","oe","oekorean","ogonek",
+"ogonekcmb","ograve","ogujarati","oharmenian","ohiragana","ohookabove",
+"ohorn","ohornacute","ohorndotbelow","ohorngrave","ohornhookabove",
+"ohorntilde","ohungarumlaut","oi","oinvertedbreve","okatakana",
+"okatakanahalfwidth","okorean","olehebrew","omacron","omacronacute",
+"omacrongrave","omdeva","omega","omega1","omegacyrillic","omegalatinclosed",
+"omegaroundcyrillic","omegatitlocyrillic","omegatonos","omgujarati","omicron",
+"omicrontonos","omonospace","one","onearabic","onebengali","onecircle",
+"onecircleinversesansserif","onedeva","onedotenleader","oneeighth",
+"onefitted","onegujarati","onegurmukhi","onehackarabic","onehalf",
+"onehangzhou","oneideographicparen","oneinferior","onemonospace",
+"onenumeratorbengali","oneoldstyle","oneparen","oneperiod","onepersian",
+"onequarter","oneroman","onesuperior","onethai","onethird","oogonek",
+"oogonekmacron","oogurmukhi","oomatragurmukhi","oopen","oparen","openbullet",
+"option","ordfeminine","ordmasculine","orthogonal","oshortdeva",
+"oshortvowelsigndeva","oslash","oslashacute","osmallhiragana",
+"osmallkatakana","osmallkatakanahalfwidth","ostrokeacute","osuperior",
+"otcyrillic","otilde","otildeacute","otildedieresis","oubopomofo","overline",
+"overlinecenterline","overlinecmb","overlinedashed","overlinedblwavy",
+"overlinewavy","overscore","ovowelsignbengali","ovowelsigndeva",
+"ovowelsigngujarati","p","paampssquare","paasentosquare","pabengali","pacute",
+"padeva","pagedown","pageup","pagujarati","pagurmukhi","pahiragana",
+"paiyannoithai","pakatakana","palatalizationcyrilliccmb","palochkacyrillic",
+"pansioskorean","paragraph","parallel","parenleft","parenleftaltonearabic",
+"parenleftbt","parenleftex","parenleftinferior","parenleftmonospace",
+"parenleftsmall","parenleftsuperior","parenlefttp","parenleftvertical",
+"parenright","parenrightaltonearabic","parenrightbt","parenrightex",
+"parenrightinferior","parenrightmonospace","parenrightsmall",
+"parenrightsuperior","parenrighttp","parenrightvertical","partialdiff",
+"paseqhebrew","pashtahebrew","pasquare","patah","patah11","patah1d","patah2a",
+"patahhebrew","patahnarrowhebrew","patahquarterhebrew","patahwidehebrew",
+"pazerhebrew","pbopomofo","pcircle","pdotaccent","pe","pecyrillic","pedagesh",
+"pedageshhebrew","peezisquare","pefinaldageshhebrew","peharabic",
+"peharmenian","pehebrew","pehfinalarabic","pehinitialarabic","pehiragana",
+"pehmedialarabic","pekatakana","pemiddlehookcyrillic","perafehebrew",
+"percent","percentarabic","percentmonospace","percentsmall","period",
+"periodarmenian","periodcentered","periodhalfwidth","periodinferior",
+"periodmonospace","periodsmall","periodsuperior","perispomenigreekcmb",
+"perpendicular","perthousand","peseta","pfsquare","phabengali","phadeva",
+"phagujarati","phagurmukhi","phi","phi1","phieuphacirclekorean",
+"phieuphaparenkorean","phieuphcirclekorean","phieuphkorean",
+"phieuphparenkorean","philatin","phinthuthai","phisymbolgreek","phook",
+"phophanthai","phophungthai","phosamphaothai","pi","pieupacirclekorean",
+"pieupaparenkorean","pieupcieuckorean","pieupcirclekorean",
+"pieupkiyeokkorean","pieupkorean","pieupparenkorean","pieupsioskiyeokkorean",
+"pieupsioskorean","pieupsiostikeutkorean","pieupthieuthkorean",
+"pieuptikeutkorean","pihiragana","pikatakana","pisymbolgreek","piwrarmenian",
+"plus","plusbelowcmb","pluscircle","plusminus","plusmod","plusmonospace",
+"plussmall","plussuperior","pmonospace","pmsquare","pohiragana",
+"pointingindexdownwhite","pointingindexleftwhite","pointingindexrightwhite",
+"pointingindexupwhite","pokatakana","poplathai","postalmark","postalmarkface",
+"pparen","precedes","prescription","primemod","primereversed","product",
+"projective","prolongedkana","propellor","propersubset","propersuperset",
+"proportion","proportional","psi","psicyrillic","psilipneumatacyrilliccmb",
+"pssquare","puhiragana","pukatakana","pvsquare","pwsquare","q","qadeva",
+"qadmahebrew","qafarabic","qaffinalarabic","qafinitialarabic",
+"qafmedialarabic","qamats","qamats10","qamats1a","qamats1c","qamats27",
+"qamats29","qamats33","qamatsde","qamatshebrew","qamatsnarrowhebrew",
+"qamatsqatanhebrew","qamatsqatannarrowhebrew","qamatsqatanquarterhebrew",
+"qamatsqatanwidehebrew","qamatsquarterhebrew","qamatswidehebrew",
+"qarneyparahebrew","qbopomofo","qcircle","qhook","qmonospace","qof",
+"qofdagesh","qofdageshhebrew","qofhatafpatah","qofhatafpatahhebrew",
+"qofhatafsegol","qofhatafsegolhebrew","qofhebrew","qofhiriq","qofhiriqhebrew",
+"qofholam","qofholamhebrew","qofpatah","qofpatahhebrew","qofqamats",
+"qofqamatshebrew","qofqubuts","qofqubutshebrew","qofsegol","qofsegolhebrew",
+"qofsheva","qofshevahebrew","qoftsere","qoftserehebrew","qparen",
+"quarternote","qubuts","qubuts18","qubuts25","qubuts31","qubutshebrew",
+"qubutsnarrowhebrew","qubutsquarterhebrew","qubutswidehebrew","question",
+"questionarabic","questionarmenian","questiondown","questiondownsmall",
+"questiongreek","questionmonospace","questionsmall","quotedbl","quotedblbase",
+"quotedblleft","quotedblmonospace","quotedblprime","quotedblprimereversed",
+"quotedblright","quoteleft","quoteleftreversed","quotereversed","quoteright",
+"quoterightn","quotesinglbase","quotesingle","quotesinglemonospace","r",
+"raarmenian","rabengali","racute","radeva","radical","radicalex",
+"radoverssquare","radoverssquaredsquare","radsquare","rafe","rafehebrew",
+"ragujarati","ragurmukhi","rahiragana","rakatakana","rakatakanahalfwidth",
+"ralowerdiagonalbengali","ramiddlediagonalbengali","ramshorn","ratio",
+"rbopomofo","rcaron","rcedilla","rcircle","rcommaaccent","rdblgrave",
+"rdotaccent","rdotbelow","rdotbelowmacron","referencemark","reflexsubset",
+"reflexsuperset","registered","registersans","registerserif","reharabic",
+"reharmenian","rehfinalarabic","rehiragana","rehyehaleflamarabic",
+"rekatakana","rekatakanahalfwidth","resh","reshdageshhebrew","reshhatafpatah",
+"reshhatafpatahhebrew","reshhatafsegol","reshhatafsegolhebrew","reshhebrew",
+"reshhiriq","reshhiriqhebrew","reshholam","reshholamhebrew","reshpatah",
+"reshpatahhebrew","reshqamats","reshqamatshebrew","reshqubuts",
+"reshqubutshebrew","reshsegol","reshsegolhebrew","reshsheva",
+"reshshevahebrew","reshtsere","reshtserehebrew","reversedtilde","reviahebrew",
+"reviamugrashhebrew","revlogicalnot","rfishhook","rfishhookreversed",
+"rhabengali","rhadeva","rho","rhook","rhookturned","rhookturnedsuperior",
+"rhosymbolgreek","rhotichookmod","rieulacirclekorean","rieulaparenkorean",
+"rieulcirclekorean","rieulhieuhkorean","rieulkiyeokkorean",
+"rieulkiyeoksioskorean","rieulkorean","rieulmieumkorean","rieulpansioskorean",
+"rieulparenkorean","rieulphieuphkorean","rieulpieupkorean",
+"rieulpieupsioskorean","rieulsioskorean","rieulthieuthkorean",
+"rieultikeutkorean","rieulyeorinhieuhkorean","rightangle","righttackbelowcmb",
+"righttriangle","rihiragana","rikatakana","rikatakanahalfwidth","ring",
+"ringbelowcmb","ringcmb","ringhalfleft","ringhalfleftarmenian",
+"ringhalfleftbelowcmb","ringhalfleftcentered","ringhalfright",
+"ringhalfrightbelowcmb","ringhalfrightcentered","rinvertedbreve",
+"rittorusquare","rlinebelow","rlongleg","rlonglegturned","rmonospace",
+"rohiragana","rokatakana","rokatakanahalfwidth","roruathai","rparen",
+"rrabengali","rradeva","rragurmukhi","rreharabic","rrehfinalarabic",
+"rrvocalicbengali","rrvocalicdeva","rrvocalicgujarati",
+"rrvocalicvowelsignbengali","rrvocalicvowelsigndeva",
+"rrvocalicvowelsigngujarati","rsuperior","rtblock","rturned",
+"rturnedsuperior","ruhiragana","rukatakana","rukatakanahalfwidth",
+"rupeemarkbengali","rupeesignbengali","rupiah","ruthai","rvocalicbengali",
+"rvocalicdeva","rvocalicgujarati","rvocalicvowelsignbengali",
+"rvocalicvowelsigndeva","rvocalicvowelsigngujarati","s","sabengali","sacute",
+"sacutedotaccent","sadarabic","sadeva","sadfinalarabic","sadinitialarabic",
+"sadmedialarabic","sagujarati","sagurmukhi","sahiragana","sakatakana",
+"sakatakanahalfwidth","sallallahoualayhewasallamarabic","samekh",
+"samekhdagesh","samekhdageshhebrew","samekhhebrew","saraaathai","saraaethai",
+"saraaimaimalaithai","saraaimaimuanthai","saraamthai","saraathai","saraethai",
+"saraiileftthai","saraiithai","saraileftthai","saraithai","saraothai",
+"saraueeleftthai","saraueethai","saraueleftthai","sarauethai","sarauthai",
+"sarauuthai","sbopomofo","scaron","scarondotaccent","scedilla","schwa",
+"schwacyrillic","schwadieresiscyrillic","schwahook","scircle","scircumflex",
+"scommaaccent","sdotaccent","sdotbelow","sdotbelowdotaccent",
+"seagullbelowcmb","second","secondtonechinese","section","seenarabic",
+"seenfinalarabic","seeninitialarabic","seenmedialarabic","segol","segol13",
+"segol1f","segol2c","segolhebrew","segolnarrowhebrew","segolquarterhebrew",
+"segoltahebrew","segolwidehebrew","seharmenian","sehiragana","sekatakana",
+"sekatakanahalfwidth","semicolon","semicolonarabic","semicolonmonospace",
+"semicolonsmall","semivoicedmarkkana","semivoicedmarkkanahalfwidth",
+"sentisquare","sentosquare","seven","sevenarabic","sevenbengali",
+"sevencircle","sevencircleinversesansserif","sevendeva","seveneighths",
+"sevengujarati","sevengurmukhi","sevenhackarabic","sevenhangzhou",
+"sevenideographicparen","seveninferior","sevenmonospace","sevenoldstyle",
+"sevenparen","sevenperiod","sevenpersian","sevenroman","sevensuperior",
+"seventeencircle","seventeenparen","seventeenperiod","seventhai","sfthyphen",
+"shaarmenian","shabengali","shacyrillic","shaddaarabic","shaddadammaarabic",
+"shaddadammatanarabic","shaddafathaarabic","shaddafathatanarabic",
+"shaddakasraarabic","shaddakasratanarabic","shade","shadedark","shadelight",
+"shademedium","shadeva","shagujarati","shagurmukhi","shalshelethebrew",
+"shbopomofo","shchacyrillic","sheenarabic","sheenfinalarabic",
+"sheeninitialarabic","sheenmedialarabic","sheicoptic","sheqel","sheqelhebrew",
+"sheva","sheva115","sheva15","sheva22","sheva2e","shevahebrew",
+"shevanarrowhebrew","shevaquarterhebrew","shevawidehebrew","shhacyrillic",
+"shimacoptic","shin","shindagesh","shindageshhebrew","shindageshshindot",
+"shindageshshindothebrew","shindageshsindot","shindageshsindothebrew",
+"shindothebrew","shinhebrew","shinshindot","shinshindothebrew","shinsindot",
+"shinsindothebrew","shook","sigma","sigma1","sigmafinal",
+"sigmalunatesymbolgreek","sihiragana","sikatakana","sikatakanahalfwidth",
+"siluqhebrew","siluqlefthebrew","similar","sindothebrew","siosacirclekorean",
+"siosaparenkorean","sioscieuckorean","sioscirclekorean","sioskiyeokkorean",
+"sioskorean","siosnieunkorean","siosparenkorean","siospieupkorean",
+"siostikeutkorean","six","sixarabic","sixbengali","sixcircle",
+"sixcircleinversesansserif","sixdeva","sixgujarati","sixgurmukhi",
+"sixhackarabic","sixhangzhou","sixideographicparen","sixinferior",
+"sixmonospace","sixoldstyle","sixparen","sixperiod","sixpersian","sixroman",
+"sixsuperior","sixteencircle","sixteencurrencydenominatorbengali",
+"sixteenparen","sixteenperiod","sixthai","slash","slashmonospace","slong",
+"slongdotaccent","smileface","smonospace","sofpasuqhebrew","softhyphen",
+"softsigncyrillic","sohiragana","sokatakana","sokatakanahalfwidth",
+"soliduslongoverlaycmb","solidusshortoverlaycmb","sorusithai","sosalathai",
+"sosothai","sosuathai","space","spacehackarabic","spade","spadesuitblack",
+"spadesuitwhite","sparen","squarebelowcmb","squarecc","squarecm",
+"squarediagonalcrosshatchfill","squarehorizontalfill","squarekg","squarekm",
+"squarekmcapital","squareln","squarelog","squaremg","squaremil","squaremm",
+"squaremsquared","squareorthogonalcrosshatchfill",
+"squareupperlefttolowerrightfill","squareupperrighttolowerleftfill",
+"squareverticalfill","squarewhitewithsmallblack","srsquare","ssabengali",
+"ssadeva","ssagujarati","ssangcieuckorean","ssanghieuhkorean",
+"ssangieungkorean","ssangkiyeokkorean","ssangnieunkorean","ssangpieupkorean",
+"ssangsioskorean","ssangtikeutkorean","ssuperior","sterling",
+"sterlingmonospace","strokelongoverlaycmb","strokeshortoverlaycmb","subset",
+"subsetnotequal","subsetorequal","succeeds","suchthat","suhiragana",
+"sukatakana","sukatakanahalfwidth","sukunarabic","summation","sun","superset",
+"supersetnotequal","supersetorequal","svsquare","syouwaerasquare","t",
+"tabengali","tackdown","tackleft","tadeva","tagujarati","tagurmukhi",
+"taharabic","tahfinalarabic","tahinitialarabic","tahiragana",
+"tahmedialarabic","taisyouerasquare","takatakana","takatakanahalfwidth",
+"tatweelarabic","tau","tav","tavdages","tavdagesh","tavdageshhebrew",
+"tavhebrew","tbar","tbopomofo","tcaron","tccurl","tcedilla","tcheharabic",
+"tchehfinalarabic","tchehinitialarabic","tchehmedialarabic",
+"tchehmeeminitialarabic","tcircle","tcircumflexbelow","tcommaaccent",
+"tdieresis","tdotaccent","tdotbelow","tecyrillic","tedescendercyrillic",
+"teharabic","tehfinalarabic","tehhahinitialarabic","tehhahisolatedarabic",
+"tehinitialarabic","tehiragana","tehjeeminitialarabic",
+"tehjeemisolatedarabic","tehmarbutaarabic","tehmarbutafinalarabic",
+"tehmedialarabic","tehmeeminitialarabic","tehmeemisolatedarabic",
+"tehnoonfinalarabic","tekatakana","tekatakanahalfwidth","telephone",
+"telephoneblack","telishagedolahebrew","telishaqetanahebrew","tencircle",
+"tenideographicparen","tenparen","tenperiod","tenroman","tesh","tet",
+"tetdagesh","tetdageshhebrew","tethebrew","tetsecyrillic","tevirhebrew",
+"tevirlefthebrew","thabengali","thadeva","thagujarati","thagurmukhi",
+"thalarabic","thalfinalarabic","thanthakhatlowleftthai",
+"thanthakhatlowrightthai","thanthakhatthai","thanthakhatupperleftthai",
+"theharabic","thehfinalarabic","thehinitialarabic","thehmedialarabic",
+"thereexists","therefore","theta","theta1","thetasymbolgreek",
+"thieuthacirclekorean","thieuthaparenkorean","thieuthcirclekorean",
+"thieuthkorean","thieuthparenkorean","thirteencircle","thirteenparen",
+"thirteenperiod","thonangmonthothai","thook","thophuthaothai","thorn",
+"thothahanthai","thothanthai","thothongthai","thothungthai",
+"thousandcyrillic","thousandsseparatorarabic","thousandsseparatorpersian",
+"three","threearabic","threebengali","threecircle",
+"threecircleinversesansserif","threedeva","threeeighths","threegujarati",
+"threegurmukhi","threehackarabic","threehangzhou","threeideographicparen",
+"threeinferior","threemonospace","threenumeratorbengali","threeoldstyle",
+"threeparen","threeperiod","threepersian","threequarters",
+"threequartersemdash","threeroman","threesuperior","threethai","thzsquare",
+"tihiragana","tikatakana","tikatakanahalfwidth","tikeutacirclekorean",
+"tikeutaparenkorean","tikeutcirclekorean","tikeutkorean","tikeutparenkorean",
+"tilde","tildebelowcmb","tildecmb","tildecomb","tildedoublecmb",
+"tildeoperator","tildeoverlaycmb","tildeverticalcmb","timescircle",
+"tipehahebrew","tipehalefthebrew","tippigurmukhi","titlocyrilliccmb",
+"tiwnarmenian","tlinebelow","tmonospace","toarmenian","tohiragana",
+"tokatakana","tokatakanahalfwidth","tonebarextrahighmod","tonebarextralowmod",
+"tonebarhighmod","tonebarlowmod","tonebarmidmod","tonefive","tonesix",
+"tonetwo","tonos","tonsquare","topatakthai","tortoiseshellbracketleft",
+"tortoiseshellbracketleftsmall","tortoiseshellbracketleftvertical",
+"tortoiseshellbracketright","tortoiseshellbracketrightsmall",
+"tortoiseshellbracketrightvertical","totaothai","tpalatalhook","tparen",
+"trademark","trademarksans","trademarkserif","tretroflexhook","triagdn",
+"triaglf","triagrt","triagup","ts","tsadi","tsadidagesh","tsadidageshhebrew",
+"tsadihebrew","tsecyrillic","tsere","tsere12","tsere1e","tsere2b",
+"tserehebrew","tserenarrowhebrew","tserequarterhebrew","tserewidehebrew",
+"tshecyrillic","tsuperior","ttabengali","ttadeva","ttagujarati","ttagurmukhi",
+"tteharabic","ttehfinalarabic","ttehinitialarabic","ttehmedialarabic",
+"tthabengali","tthadeva","tthagujarati","tthagurmukhi","tturned","tuhiragana",
+"tukatakana","tukatakanahalfwidth","tusmallhiragana","tusmallkatakana",
+"tusmallkatakanahalfwidth","twelvecircle","twelveparen","twelveperiod",
+"twelveroman","twentycircle","twentyhangzhou","twentyparen","twentyperiod",
+"two","twoarabic","twobengali","twocircle","twocircleinversesansserif",
+"twodeva","twodotenleader","twodotleader","twodotleadervertical",
+"twogujarati","twogurmukhi","twohackarabic","twohangzhou",
+"twoideographicparen","twoinferior","twomonospace","twonumeratorbengali",
+"twooldstyle","twoparen","twoperiod","twopersian","tworoman","twostroke",
+"twosuperior","twothai","twothirds","u","uacute","ubar","ubengali",
+"ubopomofo","ubreve","ucaron","ucircle","ucircumflex","ucircumflexbelow",
+"ucyrillic","udattadeva","udblacute","udblgrave","udeva","udieresis",
+"udieresisacute","udieresisbelow","udieresiscaron","udieresiscyrillic",
+"udieresisgrave","udieresismacron","udotbelow","ugrave","ugujarati",
+"ugurmukhi","uhiragana","uhookabove","uhorn","uhornacute","uhorndotbelow",
+"uhorngrave","uhornhookabove","uhorntilde","uhungarumlaut",
+"uhungarumlautcyrillic","uinvertedbreve","ukatakana","ukatakanahalfwidth",
+"ukcyrillic","ukorean","umacron","umacroncyrillic","umacrondieresis",
+"umatragurmukhi","umonospace","underscore","underscoredbl",
+"underscoremonospace","underscorevertical","underscorewavy","union",
+"universal","uogonek","uparen","upblock","upperdothebrew","upsilon",
+"upsilondieresis","upsilondieresistonos","upsilonlatin","upsilontonos",
+"uptackbelowcmb","uptackmod","uragurmukhi","uring","ushortcyrillic",
+"usmallhiragana","usmallkatakana","usmallkatakanahalfwidth",
+"ustraightcyrillic","ustraightstrokecyrillic","utilde","utildeacute",
+"utildebelow","uubengali","uudeva","uugujarati","uugurmukhi",
+"uumatragurmukhi","uuvowelsignbengali","uuvowelsigndeva",
+"uuvowelsigngujarati","uvowelsignbengali","uvowelsigndeva",
+"uvowelsigngujarati","v","vadeva","vagujarati","vagurmukhi","vakatakana",
+"vav","vavdagesh","vavdagesh65","vavdageshhebrew","vavhebrew","vavholam",
+"vavholamhebrew","vavvavhebrew","vavyodhebrew","vcircle","vdotbelow",
+"vecyrillic","veharabic","vehfinalarabic","vehinitialarabic",
+"vehmedialarabic","vekatakana","venus","verticalbar","verticallineabovecmb",
+"verticallinebelowcmb","verticallinelowmod","verticallinemod","vewarmenian",
+"vhook","vikatakana","viramabengali","viramadeva","viramagujarati",
+"visargabengali","visargadeva","visargagujarati","vmonospace","voarmenian",
+"voicediterationhiragana","voicediterationkatakana","voicedmarkkana",
+"voicedmarkkanahalfwidth","vokatakana","vparen","vtilde","vturned",
+"vuhiragana","vukatakana","w","wacute","waekorean","wahiragana","wakatakana",
+"wakatakanahalfwidth","wakorean","wasmallhiragana","wasmallkatakana",
+"wattosquare","wavedash","wavyunderscorevertical","wawarabic",
+"wawfinalarabic","wawhamzaabovearabic","wawhamzaabovefinalarabic","wbsquare",
+"wcircle","wcircumflex","wdieresis","wdotaccent","wdotbelow","wehiragana",
+"weierstrass","wekatakana","wekorean","weokorean","wgrave","whitebullet",
+"whitecircle","whitecircleinverse","whitecornerbracketleft",
+"whitecornerbracketleftvertical","whitecornerbracketright",
+"whitecornerbracketrightvertical","whitediamond",
+"whitediamondcontainingblacksmalldiamond","whitedownpointingsmalltriangle",
+"whitedownpointingtriangle","whiteleftpointingsmalltriangle",
+"whiteleftpointingtriangle","whitelenticularbracketleft",
+"whitelenticularbracketright","whiterightpointingsmalltriangle",
+"whiterightpointingtriangle","whitesmallsquare","whitesmilingface",
+"whitesquare","whitestar","whitetelephone","whitetortoiseshellbracketleft",
+"whitetortoiseshellbracketright","whiteuppointingsmalltriangle",
+"whiteuppointingtriangle","wihiragana","wikatakana","wikorean","wmonospace",
+"wohiragana","wokatakana","wokatakanahalfwidth","won","wonmonospace",
+"wowaenthai","wparen","wring","wsuperior","wturned","wynn","x","xabovecmb",
+"xbopomofo","xcircle","xdieresis","xdotaccent","xeharmenian","xi",
+"xmonospace","xparen","xsuperior","y","yaadosquare","yabengali","yacute",
+"yadeva","yaekorean","yagujarati","yagurmukhi","yahiragana","yakatakana",
+"yakatakanahalfwidth","yakorean","yamakkanthai","yasmallhiragana",
+"yasmallkatakana","yasmallkatakanahalfwidth","yatcyrillic","ycircle",
+"ycircumflex","ydieresis","ydotaccent","ydotbelow","yeharabic",
+"yehbarreearabic","yehbarreefinalarabic","yehfinalarabic",
+"yehhamzaabovearabic","yehhamzaabovefinalarabic","yehhamzaaboveinitialarabic",
+"yehhamzaabovemedialarabic","yehinitialarabic","yehmedialarabic",
+"yehmeeminitialarabic","yehmeemisolatedarabic","yehnoonfinalarabic",
+"yehthreedotsbelowarabic","yekorean","yen","yenmonospace","yeokorean",
+"yeorinhieuhkorean","yerahbenyomohebrew","yerahbenyomolefthebrew",
+"yericyrillic","yerudieresiscyrillic","yesieungkorean",
+"yesieungpansioskorean","yesieungsioskorean","yetivhebrew","ygrave","yhook",
+"yhookabove","yiarmenian","yicyrillic","yikorean","yinyang","yiwnarmenian",
+"ymonospace","yod","yoddagesh","yoddageshhebrew","yodhebrew","yodyodhebrew",
+"yodyodpatahhebrew","yohiragana","yoikorean","yokatakana",
+"yokatakanahalfwidth","yokorean","yosmallhiragana","yosmallkatakana",
+"yosmallkatakanahalfwidth","yotgreek","yoyaekorean","yoyakorean","yoyakthai",
+"yoyingthai","yparen","ypogegrammeni","ypogegrammenigreekcmb","yr","yring",
+"ysuperior","ytilde","yturned","yuhiragana","yuikorean","yukatakana",
+"yukatakanahalfwidth","yukorean","yusbigcyrillic","yusbigiotifiedcyrillic",
+"yuslittlecyrillic","yuslittleiotifiedcyrillic","yusmallhiragana",
+"yusmallkatakana","yusmallkatakanahalfwidth","yuyekorean","yuyeokorean",
+"yyabengali","yyadeva","z","zaarmenian","zacute","zadeva","zagurmukhi",
+"zaharabic","zahfinalarabic","zahinitialarabic","zahiragana",
+"zahmedialarabic","zainarabic","zainfinalarabic","zakatakana",
+"zaqefgadolhebrew","zaqefqatanhebrew","zarqahebrew","zayin","zayindagesh",
+"zayindageshhebrew","zayinhebrew","zbopomofo","zcaron","zcircle",
+"zcircumflex","zcurl","zdot","zdotaccent","zdotbelow","zecyrillic",
+"zedescendercyrillic","zedieresiscyrillic","zehiragana","zekatakana","zero",
+"zeroarabic","zerobengali","zerodeva","zerogujarati","zerogurmukhi",
+"zerohackarabic","zeroinferior","zeromonospace","zerooldstyle","zeropersian",
+"zerosuperior","zerothai","zerowidthjoiner","zerowidthnonjoiner",
+"zerowidthspace","zeta","zhbopomofo","zhearmenian","zhebrevecyrillic",
+"zhecyrillic","zhedescendercyrillic","zhedieresiscyrillic","zihiragana",
+"zikatakana","zinorhebrew","zlinebelow","zmonospace","zohiragana",
+"zokatakana","zparen","zretroflexhook","zstroke","zuhiragana","zukatakana",
+};
+
+static const unsigned short agl_code_list[] = {
+65,198,508,482,63462,193,63457,258,7854,1232,7862,7856,7858,7860,461,9398,194,
+7844,7852,7846,7848,63458,7850,63177,63412,1040,512,196,1234,478,63460,7840,
+480,192,63456,7842,1236,514,913,902,256,65313,260,197,506,7680,63461,63329,
+195,63459,1329,66,9399,7682,7684,1041,1330,914,385,7686,65314,63220,63330,386,
+67,1342,262,63178,63221,268,199,7688,63463,9400,264,266,266,63416,1353,1212,
+1063,1214,1206,1268,1347,1227,1208,935,391,63222,65315,1361,63331,68,497,452,
+1332,393,270,7696,9401,7698,272,7690,7692,1044,1006,8710,916,394,63179,63180,
+63181,63400,988,1026,7694,65316,63223,272,63332,395,498,453,1248,1029,1039,69,
+201,63465,276,282,7708,1333,9402,202,7870,7704,7878,7872,7874,63466,7876,1028,
+516,203,63467,278,278,7864,1060,200,63464,1335,7866,8551,518,1124,1051,8554,
+274,7702,7700,1052,65317,1053,1186,330,1188,1223,280,400,917,904,1056,398,
+1069,1057,1194,425,63333,919,1336,905,208,63472,7868,7706,8364,439,494,440,70,
+9403,7710,1366,996,401,1138,8548,65318,8547,63334,71,13191,500,915,404,1002,
+286,486,290,9404,284,290,288,288,1043,1346,1172,1170,1168,403,1331,1027,7712,
+65319,63182,63328,63335,667,484,72,9679,9642,9643,9633,13259,1192,1202,1066,
+294,7722,7720,9405,292,7718,7714,7716,65320,1344,1000,63336,63183,63224,13200,
+73,1071,306,1070,205,63469,300,463,9406,206,63470,1030,520,207,7726,1252,
+63471,304,304,7882,1238,1045,8465,204,63468,7880,1048,522,1049,298,1250,65321,
+1339,1025,302,921,406,938,906,63337,407,296,7724,1140,1142,74,1345,9407,308,
+1032,1355,65322,63338,75,13189,13261,1184,7728,1050,1178,1219,922,1182,1180,
+488,310,9408,310,7730,1364,1343,1061,998,408,1036,7732,65323,1152,990,1134,
+63339,76,455,63167,313,923,317,315,9409,7740,315,319,319,7734,7736,1340,456,
+1033,7738,65324,321,63225,63340,77,13190,63184,63407,7742,9410,7744,7746,1348,
+65325,63341,412,924,78,458,323,327,325,9411,7754,325,7748,7750,413,8552,459,
+1034,7752,65326,1350,63342,209,63473,925,79,338,63226,211,63475,1256,1258,334,
+465,415,9412,212,7888,7896,7890,7892,63476,7894,1054,336,524,214,1254,63478,
+7884,63227,210,63474,1365,8486,7886,416,7898,7906,7900,7902,7904,336,418,526,
+332,7762,7760,8486,1120,937,1146,1148,911,927,908,65327,8544,490,492,390,216,
+510,63480,63343,510,1150,213,7756,7758,63477,80,7764,9413,7766,1055,1354,1190,
+934,420,928,1363,65328,936,1136,63344,81,9414,65329,63345,82,1356,340,344,342,
+9415,342,528,7768,7770,7772,1360,8476,929,63228,530,7774,65330,63346,641,694,
+83,9484,9492,9488,9496,9532,9516,9524,9500,9508,9472,9474,9569,9570,9558,9557,
+9571,9553,9559,9565,9564,9563,9566,9567,9562,9556,9577,9574,9568,9552,9580,
+9575,9576,9572,9573,9561,9560,9554,9555,9579,9578,346,7780,992,352,7782,63229,
+350,399,1240,1242,9416,348,536,7776,7778,7784,1357,8550,1351,1064,1065,994,
+1210,1004,931,8549,65331,1068,63347,986,84,932,358,356,354,9417,7792,354,7786,
+7788,1058,1196,8553,1204,920,428,222,63486,8546,63230,1359,7790,65332,1337,
+444,388,423,430,1062,1035,63348,8555,8545,85,218,63482,364,467,9418,219,7798,
+63483,1059,368,532,220,471,7794,473,1264,475,469,63484,7908,217,63481,7910,
+431,7912,7920,7914,7916,7918,368,1266,534,1144,362,1262,7802,65333,370,933,
+978,979,433,939,980,978,910,366,1038,63349,1198,1200,360,7800,7796,86,9419,
+7806,1042,1358,434,65334,1352,63350,7804,87,7810,9420,372,7812,7814,7816,7808,
+65335,63351,88,9421,7820,7818,1341,926,65336,63352,89,221,63485,1122,9422,374,
+376,63487,7822,7924,1067,1272,7922,435,7926,1349,1031,1362,65337,63353,7928,
+1130,1132,1126,1128,90,1334,377,381,63231,9423,7824,379,379,7826,1047,1176,
+1246,918,1338,1217,1046,1174,1244,7828,65338,63354,437,97,2438,225,2310,2694,
+2566,2622,13059,2494,2366,2750,1375,2416,2437,12570,259,7855,1233,7863,7857,
+7859,7861,462,9424,226,7845,7853,7847,7849,7851,180,791,769,769,2388,719,833,
+1072,513,2673,2309,228,1235,479,7841,481,230,509,12624,483,8213,8356,1040,
+1041,1042,1043,1044,1045,1025,1046,1047,1048,1049,1050,1051,1052,1053,1054,
+1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,
+1070,1071,1168,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1038,
+63172,63173,1072,1073,1074,1075,1076,1077,1105,1078,1079,1080,1081,1082,1083,
+1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,
+1099,1100,1101,1102,1103,1169,1106,1107,1108,1109,1110,1111,1112,1113,1114,
+1115,1116,1118,1039,1122,1138,1140,63174,1119,1123,1139,1141,63175,63176,1241,
+8206,8207,8205,1642,1548,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,
+1563,1567,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,
+1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1600,1601,
+1602,1603,1604,1605,1606,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,
+1618,1607,1700,1662,1670,1688,1711,1657,1672,1681,1722,1746,1749,8362,1470,
+1475,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,
+1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,64298,64299,
+64331,64287,1520,1521,1522,64309,1460,1461,1462,1467,1464,1463,1456,1458,1457,
+1459,1474,1473,1465,1468,1469,1471,1472,700,8453,8467,8470,8236,8237,8238,
+8204,1645,701,224,2693,2565,12354,7843,2448,12574,2320,1237,2704,2576,2632,
+1593,65226,65227,65228,515,2504,2376,2760,12450,65393,12623,1488,1575,64304,
+65166,1571,65156,1573,65160,1488,64335,1570,65154,1609,65264,65267,65268,
+64302,64303,8501,8780,945,940,257,65345,38,65286,63270,13250,12578,12580,3674,
+8736,12296,65087,12297,65088,9001,9002,8491,903,2386,2434,2306,2690,261,13056,
+9372,1370,700,63743,8784,8776,8786,8773,12686,12685,8978,7834,229,507,7681,
+8596,8675,8672,8674,8673,8660,8659,8656,8658,8657,8595,8601,8600,8681,709,706,
+707,708,63719,8592,8656,8653,8646,8678,8594,8655,10142,8644,8680,8676,8677,
+8593,8597,8616,8616,8598,8645,8599,8679,63718,94,65342,126,65374,593,594,
+12353,12449,65383,42,1645,1645,8727,65290,65121,8258,63209,8771,64,227,65312,
+65131,592,2452,12576,2324,2708,2580,2519,2636,2508,2380,2764,2365,1377,1506,
+64288,1506,98,2476,92,65340,2348,2732,2604,12400,3647,12496,124,65372,12549,
+9425,7683,7685,9836,8757,1073,1576,65168,65169,12409,65170,64671,64520,64621,
+12505,1378,1489,946,976,64305,64305,1489,64332,2477,2349,2733,2605,595,12403,
+12499,664,2562,13105,9679,9670,9660,9668,9664,12304,65083,12305,65084,9699,
+9698,9644,9658,9654,9642,9787,9632,9733,9700,9701,9652,9650,9251,7687,9608,
+65346,3610,12412,12508,9373,13251,63732,123,63731,63730,65371,65115,63729,
+65079,125,63742,63741,65373,65116,63740,65080,91,63728,63727,65339,63726,93,
+63739,63738,65341,63737,728,814,774,815,785,865,810,826,166,384,63210,387,
+12406,12502,8226,9688,8729,9678,99,1390,2458,263,2330,2714,2586,13192,2433,
+784,2305,2689,8682,8453,711,812,780,8629,12568,269,231,7689,9426,265,597,267,
+267,13253,184,807,162,8451,63199,65504,63394,63200,1401,2459,2331,2715,2587,
+12564,1213,10003,1095,1215,1207,1269,1395,1228,1209,967,12919,12823,12905,
+12618,12809,3594,3592,3593,3596,392,12918,12822,12904,12616,12808,12828,9675,
+8855,8857,8853,12342,9680,9681,710,813,770,8999,450,448,449,451,9827,9827,
+9831,13220,65347,13216,1409,58,8353,65306,8353,65109,721,720,44,787,789,63171,
+1548,1373,63201,65292,788,701,65104,63202,786,699,9788,8773,8750,8963,6,7,8,
+24,13,17,18,19,20,127,16,25,5,4,27,23,3,12,28,29,9,10,21,30,15,14,2,1,26,22,
+31,11,169,63721,63193,12300,65378,65089,12301,65379,65090,13183,13255,13254,
+9374,8354,663,8911,8910,164,63185,63186,63188,63189,100,1380,2470,1590,2342,
+65214,65215,65216,1468,1468,8224,8225,2726,2598,12384,12480,1583,1491,64307,
+64307,1491,1491,1491,1491,1491,1491,1491,1491,1491,1491,1491,1491,1491,1491,
+1491,1491,1491,1491,1491,1491,1491,65194,1615,1615,1612,1612,2404,1447,1447,
+1157,63187,12298,65085,12299,65086,811,8660,8658,2405,63190,783,8748,8215,819,
+831,698,8214,782,12553,13256,271,7697,9427,7699,273,2465,2337,2721,2593,1672,
+64393,2396,2466,2338,2722,2594,7691,7693,1643,1643,1076,176,1453,12391,1007,
+12487,9003,8998,948,397,2552,676,2471,2343,2727,2599,599,901,836,9830,9826,
+168,63191,804,776,63192,901,12386,12482,12291,247,8739,8725,1106,9619,7695,
+13207,273,65348,9604,3598,3604,12393,12489,36,63203,65284,63268,65129,63204,
+8363,13094,729,775,803,803,12539,305,63166,644,8901,9676,64287,64287,798,725,
+9375,63211,598,396,12389,12485,499,675,454,677,1249,1109,1119,101,233,9793,
+2447,12572,277,2317,2701,2373,2757,283,7709,1381,1415,9428,234,7871,7705,7879,
+7873,7875,7877,1108,517,2319,235,279,279,7865,2575,2631,1092,232,2703,1383,
+12573,12360,7867,12575,56,1640,2542,9319,10129,2414,9329,9349,9369,2798,2670,
+1640,12328,9835,12839,8328,65304,63288,9339,9359,1784,8567,8312,3672,519,1125,
+12456,65396,2676,12628,1083,8712,9322,9342,9362,8570,8230,8942,275,7703,7701,
+1084,8212,65073,65349,1371,8709,12579,1085,8211,65074,1187,331,12581,1189,
+1224,8194,281,12627,603,666,604,606,605,9376,949,941,61,65309,65126,8316,8801,
+12582,1088,600,1101,1089,1195,643,646,2318,2374,426,645,12359,12455,65386,
+8494,63212,951,1384,942,240,7869,7707,1425,1425,1425,1425,477,12641,8364,2503,
+2375,2759,33,1372,8252,161,63393,65281,63265,8707,658,495,659,441,442,102,
+2398,2654,8457,1614,1614,1611,12552,9429,7711,1601,1414,65234,65235,65236,997,
+9792,64256,64259,64260,64257,9326,9346,9366,8210,9632,9644,1498,64314,64314,
+1498,1498,1498,1498,1498,1501,1501,1503,1503,1507,1507,1509,1509,713,9673,
+1139,53,1637,2539,9316,10126,2411,8541,2795,2667,1637,12325,12836,8325,65301,
+63285,9336,9356,1781,8564,8309,3669,64258,402,65350,13209,3615,3613,3663,8704,
+52,1636,2538,9315,10125,2410,2794,2666,1636,12324,12835,8324,65300,2551,63284,
+9335,9355,1780,8563,8308,9325,9345,9365,3668,715,9377,8260,8355,103,2455,501,
+2327,1711,64403,64404,64405,2711,2583,12364,12460,947,611,736,1003,12557,287,
+487,291,9430,285,291,289,289,1075,12370,12466,8785,1436,1523,1437,223,1438,
+1524,12307,2456,1394,2328,2712,2584,1594,65230,65231,65232,1173,1171,1169,
+2394,2650,608,13203,12366,12462,1379,1490,64306,64306,1490,1107,446,660,662,
+704,661,705,740,673,674,7713,65351,12372,12468,9378,13228,8711,96,790,768,768,
+2387,718,65344,832,62,8805,8923,65310,8819,8823,8807,65125,609,485,12368,171,
+187,8249,8250,12464,13080,13257,104,1193,1729,2489,1203,2361,2745,2617,1581,
+65186,65187,12399,65188,13098,12495,65418,2637,1569,1569,1569,1569,1569,1569,
+1569,1569,1569,12644,1098,8636,8640,13258,1458,1458,1458,1458,1458,1458,1458,
+1458,1459,1459,1459,1459,1459,1459,1459,1459,1457,1457,1457,1457,1457,1457,
+1457,1457,295,12559,7723,7721,9431,293,7719,7715,7717,1492,9829,9829,9825,
+64308,64308,1729,1607,1492,64423,65258,65258,64421,64420,64424,65259,12408,
+64425,65260,13179,12504,65421,13110,615,13113,1495,1495,614,689,12923,12827,
+12909,12622,12813,12402,12498,65419,1460,1460,1460,1460,1460,1460,1460,1460,
+7830,65352,1392,3627,12411,12507,65422,1465,1465,1465,1465,1465,1465,1465,
+1465,3630,777,777,801,802,13122,1001,8213,795,9832,8962,9379,688,613,12405,
+13107,12501,65420,733,779,405,45,63205,65293,65123,63206,8208,105,237,1103,
+2439,12583,301,464,9432,238,1110,521,12943,12939,12863,12858,12965,12294,
+12289,65380,12855,12963,12847,12861,12957,12864,12950,12854,12843,12850,12964,
+12293,12952,12856,12967,12966,12969,12846,12842,12852,12290,12958,12867,12857,
+12862,12968,12953,12866,12851,12288,12853,12849,12859,12848,12860,12844,12845,
+12295,12942,12938,12948,12944,12940,12941,2311,239,7727,1253,7883,1239,1077,
+12917,12821,12903,12615,12807,236,2695,2567,12356,7881,2440,1080,2312,2696,
+2568,2624,523,1081,2496,2368,2752,307,12452,65394,12643,732,1452,299,1251,
+8787,2623,65353,8710,8734,1387,8747,8993,8993,63733,8992,8992,8745,13061,9688,
+9689,9787,1105,303,953,970,912,617,943,9380,2674,12355,12451,65384,2554,616,
+63213,12445,12541,297,7725,12585,1102,2495,2367,2751,1141,1143,106,1393,2460,
+2332,2716,2588,12560,496,9433,309,669,607,1112,1580,65182,65183,65184,1688,
+64395,2461,2333,2717,2589,1403,12292,65354,9381,690,107,1185,2453,7729,1082,
+1179,2325,1499,1603,64315,64315,65242,1499,65243,65244,64333,2709,2581,12363,
+1220,12459,65398,954,1008,12657,12676,12664,12665,13069,1600,1600,12533,13188,
+1616,1613,1183,65392,1181,12558,13193,489,311,9434,311,7731,1412,12369,12465,
+65401,1391,12534,312,2454,1093,2326,2710,2582,1582,65190,65191,65192,999,2393,
+2649,12920,12824,12906,12619,12810,3586,3589,3587,3588,3675,409,3590,13201,
+12365,12461,65399,13077,13078,13076,12910,12814,12896,12593,12800,12595,1116,
+7733,13208,13222,65355,13218,12371,13248,3585,12467,65402,13086,1153,12927,
+835,9382,13226,1135,13263,670,12367,12463,65400,13240,13246,108,2482,314,2354,
+2738,2610,3653,65276,65272,65271,65274,65273,65275,65270,65269,1604,955,411,
+1500,64316,64316,1500,1500,1500,1500,1500,65246,64714,65247,64713,64715,65010,
+65248,64904,64716,65247,65247,9711,410,620,12556,318,316,9435,7741,316,320,
+320,7735,7737,794,792,60,8804,8922,65308,8818,8822,8806,65124,622,9612,621,
+8356,1388,457,1113,63168,2355,2739,7739,2356,2529,2401,2531,2403,619,65356,
+13264,3628,8743,172,8976,8744,3621,383,65102,818,65101,9674,9383,322,8467,
+63214,9617,3622,2444,2316,2530,2402,13267,109,2478,175,817,772,717,65507,7743,
+2350,2734,2606,1444,1444,12414,63637,63636,3659,63635,63628,63627,3656,63626,
+63620,3633,63625,3655,63631,63630,3657,63629,63634,63633,3658,63632,3654,
+12510,65423,9794,13127,1470,9794,1455,13187,12551,13268,9436,13221,7745,7747,
+1605,65250,65251,65252,64721,64584,13133,12417,13182,12513,65426,1502,64318,
+64318,1502,1396,1445,1446,1446,1445,625,13202,65381,183,12914,12818,12900,
+12609,12656,12804,12654,12655,12415,12511,65424,8722,800,8854,727,8723,8242,
+13130,13129,624,13206,13219,65357,13215,12418,13249,12514,65427,13270,3617,
+13223,13224,9384,13227,13235,63215,623,181,181,13186,8811,8810,13196,956,
+13197,12416,12512,65425,13205,215,13211,1443,1443,9834,9835,9837,9839,13234,
+13238,13244,13241,13239,13247,13245,110,2472,8711,324,2344,2728,2600,12394,
+12490,65413,329,13185,12555,160,328,326,9437,7755,326,7749,7751,12397,12493,
+65416,8362,13195,2457,2329,2713,2585,3591,12435,626,627,12911,12815,12597,
+12897,12598,12596,12648,12801,12647,12646,12395,12491,65414,63641,3661,57,
+1641,2543,9320,10130,2415,2799,2671,1641,12329,12840,8329,65305,63289,9340,
+9360,1785,8568,8313,9330,9350,9370,3673,460,1114,12531,65437,414,7753,65358,
+13210,2467,2339,2723,2595,2345,12398,12494,65417,160,3603,3609,1606,65254,
+1722,64415,65255,65255,64722,64587,65256,64725,64590,64653,8716,8713,8713,
+8800,8815,8817,8825,8802,8814,8816,8742,8832,8836,8833,8837,1398,9385,13233,
+8319,241,957,12396,12492,65415,2492,2364,2748,2620,35,65283,65119,884,885,
+8470,1504,64320,64320,1504,13237,13243,2462,2334,2718,2590,111,243,3629,629,
+1257,1259,2451,12571,335,2321,2705,2377,2761,466,9438,244,7889,7897,7891,7893,
+7895,1086,337,525,2323,246,1255,7885,339,12634,731,808,242,2707,1413,12362,
+7887,417,7899,7907,7901,7903,7905,337,419,527,12458,65397,12631,1451,333,7763,
+7761,2384,969,982,1121,631,1147,1149,974,2768,959,972,65359,49,1633,2535,9312,
+10122,2407,8228,8539,63196,2791,2663,1633,189,12321,12832,8321,65297,2548,
+63281,9332,9352,1777,188,8560,185,3665,8531,491,493,2579,2635,596,9386,9702,
+8997,170,186,8735,2322,2378,248,511,12361,12457,65387,511,63216,1151,245,7757,
+7759,12577,8254,65098,773,65097,65100,65099,175,2507,2379,2763,112,13184,
+13099,2474,7765,2346,8671,8670,2730,2602,12401,3631,12497,1156,1216,12671,182,
+8741,40,64830,63725,63724,8333,65288,65113,8317,63723,65077,41,64831,63736,
+63735,8334,65289,65114,8318,63734,65078,8706,1472,1433,13225,1463,1463,1463,
+1463,1463,1463,1463,1463,1441,12550,9439,7767,1508,1087,64324,64324,13115,
+64323,1662,1402,1508,64343,64344,12410,64345,12506,1191,64334,37,1642,65285,
+65130,46,1417,183,65377,63207,65294,65106,63208,834,8869,8240,8359,13194,2475,
+2347,2731,2603,966,981,12922,12826,12908,12621,12812,632,3642,981,421,3614,
+3612,3616,960,12915,12819,12662,12901,12658,12610,12805,12660,12612,12661,
+12663,12659,12404,12500,982,1411,43,799,8853,177,726,65291,65122,8314,65360,
+13272,12413,9759,9756,9758,9757,12509,3611,12306,12320,9387,8826,8478,697,
+8245,8719,8965,12540,8984,8834,8835,8759,8733,968,1137,1158,13232,12407,12503,
+13236,13242,113,2392,1448,1602,65238,65239,65240,1464,1464,1464,1464,1464,
+1464,1464,1464,1464,1464,1464,1464,1464,1464,1464,1464,1439,12561,9440,672,
+65361,1511,64327,64327,1511,1511,1511,1511,1511,1511,1511,1511,1511,1511,1511,
+1511,1511,1511,1511,1511,1511,1511,1511,1511,1511,9388,9833,1467,1467,1467,
+1467,1467,1467,1467,1467,63,1567,1374,191,63423,894,65311,63295,34,8222,8220,
+65282,12318,12317,8221,8216,8219,8219,8217,329,8218,39,65287,114,1404,2480,
+341,2352,8730,63717,13230,13231,13229,1471,1471,2736,2608,12425,12521,65431,
+2545,2544,612,8758,12566,345,343,9441,343,529,7769,7771,7773,8251,8838,8839,
+174,63720,63194,1585,1408,65198,12428,1585,12524,65434,1512,64328,1512,1512,
+1512,1512,1512,1512,1512,1512,1512,1512,1512,1512,1512,1512,1512,1512,1512,
+1512,1512,1512,1512,8765,1431,1431,8976,638,639,2525,2397,961,637,635,693,
+1009,734,12913,12817,12899,12608,12602,12649,12601,12603,12652,12803,12607,
+12604,12651,12605,12606,12650,12653,8735,793,8895,12426,12522,65432,730,805,
+778,703,1369,796,723,702,825,722,531,13137,7775,636,634,65362,12429,12525,
+65435,3619,9389,2524,2353,2652,1681,64397,2528,2400,2784,2500,2372,2756,63217,
+9616,633,692,12427,12523,65433,2546,2547,63197,3620,2443,2315,2699,2499,2371,
+2755,115,2488,347,7781,1589,2360,65210,65211,65212,2744,2616,12373,12469,
+65403,65018,1505,64321,64321,1505,3634,3649,3652,3651,3635,3632,3648,63622,
+3637,63621,3636,3650,63624,3639,63623,3638,3640,3641,12569,353,7783,351,601,
+1241,1243,602,9442,349,537,7777,7779,7785,828,8243,714,167,1587,65202,65203,
+65204,1462,1462,1462,1462,1462,1462,1462,1426,1462,1405,12379,12475,65406,59,
+1563,65307,65108,12444,65439,13090,13091,55,1639,2541,9318,10128,2413,8542,
+2797,2669,1639,12327,12838,8327,65303,63287,9338,9358,1783,8566,8311,9328,
+9348,9368,3671,173,1399,2486,1096,1617,64609,64606,64608,1617,64610,64607,
+9618,9619,9617,9618,2358,2742,2614,1427,12565,1097,1588,65206,65207,65208,995,
+8362,8362,1456,1456,1456,1456,1456,1456,1456,1456,1456,1211,1005,1513,64329,
+64329,64300,64300,64301,64301,1473,1513,64298,64298,64299,64299,642,963,962,
+962,1010,12375,12471,65404,1469,1469,8764,1474,12916,12820,12670,12902,12666,
+12613,12667,12806,12669,12668,54,1638,2540,9317,10127,2412,2796,2668,1638,
+12326,12837,8326,65302,63286,9337,9357,1782,8565,8310,9327,2553,9347,9367,
+3670,47,65295,383,7835,9786,65363,1475,173,1100,12381,12477,65407,824,823,
+3625,3624,3595,3626,32,32,9824,9824,9828,9390,827,13252,13213,9641,9636,13199,
+13214,13262,13265,13266,13198,13269,13212,13217,9638,9639,9640,9637,9635,
+13275,2487,2359,2743,12617,12677,12672,12594,12645,12611,12614,12600,63218,
+163,65505,822,821,8834,8842,8838,8827,8715,12377,12473,65405,1618,8721,9788,
+8835,8843,8839,13276,13180,116,2468,8868,8867,2340,2724,2596,1591,65218,65219,
+12383,65220,13181,12479,65408,1600,964,1514,64330,64330,64330,1514,359,12554,
+357,680,355,1670,64379,64380,64381,64380,9443,7793,355,7831,7787,7789,1090,
+1197,1578,65174,64674,64524,65175,12390,64673,64523,1577,65172,65176,64676,
+64526,64627,12486,65411,8481,9742,1440,1449,9321,12841,9341,9361,8569,679,
+1496,64312,64312,1496,1205,1435,1435,2469,2341,2725,2597,1584,65196,63640,
+63639,3660,63638,1579,65178,65179,65180,8707,8756,952,977,977,12921,12825,
+12907,12620,12811,9324,9344,9364,3601,429,3602,254,3607,3600,3608,3606,1154,
+1644,1644,51,1635,2537,9314,10124,2409,8540,2793,2665,1635,12323,12834,8323,
+65299,2550,63283,9334,9354,1779,190,63198,8562,179,3667,13204,12385,12481,
+65409,12912,12816,12898,12599,12802,732,816,771,771,864,8764,820,830,8855,
+1430,1430,2672,1155,1407,7791,65364,1385,12392,12488,65412,741,745,742,744,
+743,445,389,424,900,13095,3599,12308,65117,65081,12309,65118,65082,3605,427,
+9391,8482,63722,63195,648,9660,9668,9658,9650,678,1510,64326,64326,1510,1094,
+1461,1461,1461,1461,1461,1461,1461,1461,1115,63219,2463,2335,2719,2591,1657,
+64359,64360,64361,2464,2336,2720,2592,647,12388,12484,65410,12387,12483,65391,
+9323,9343,9363,8571,9331,21316,9351,9371,50,1634,2536,9313,10123,2408,8229,
+8229,65072,2792,2664,1634,12322,12833,8322,65298,2549,63282,9333,9353,1778,
+8561,443,178,3666,8532,117,250,649,2441,12584,365,468,9444,251,7799,1091,2385,
+369,533,2313,252,472,7795,474,1265,476,470,7909,249,2697,2569,12358,7911,432,
+7913,7921,7915,7917,7919,369,1267,535,12454,65395,1145,12636,363,1263,7803,
+2625,65365,95,8215,65343,65075,65103,8746,8704,371,9392,9600,1476,965,971,944,
+650,973,797,724,2675,367,1118,12357,12453,65385,1199,1201,361,7801,7797,2442,
+2314,2698,2570,2626,2498,2370,2754,2497,2369,2753,118,2357,2741,2613,12535,
+1493,64309,64309,64309,1493,64331,64331,1520,1521,9445,7807,1074,1700,64363,
+64364,64365,12537,9792,124,781,809,716,712,1406,651,12536,2509,2381,2765,2435,
+2307,2691,65366,1400,12446,12542,12443,65438,12538,9393,7805,652,12436,12532,
+119,7811,12633,12431,12527,65436,12632,12430,12526,13143,12316,65076,1608,
+65262,1572,65158,13277,9446,373,7813,7815,7817,12433,8472,12529,12638,12637,
+7809,9702,9675,9689,12302,65091,12303,65092,9671,9672,9663,9661,9667,9665,
+12310,12311,9657,9655,9643,9786,9633,9734,9743,12312,12313,9653,9651,12432,
+12528,12639,65367,12434,12530,65382,8361,65510,3623,9394,7832,695,653,447,120,
+829,12562,9447,7821,7819,1389,958,65368,9395,739,121,13134,2479,253,2351,
+12626,2735,2607,12420,12516,65428,12625,3662,12419,12515,65388,1123,9448,375,
+255,7823,7925,1610,1746,64431,65266,1574,65162,65163,65164,65267,65268,64733,
+64600,64660,1745,12630,165,65509,12629,12678,1450,1450,1099,1273,12673,12675,
+12674,1434,7923,436,7927,1397,1111,12642,9775,1410,65369,1497,64313,64313,
+1497,1522,64287,12424,12681,12520,65430,12635,12423,12519,65390,1011,12680,
+12679,3618,3597,9396,890,837,422,7833,696,7929,654,12422,12684,12518,65429,
+12640,1131,1133,1127,1129,12421,12517,65389,12683,12682,2527,2399,122,1382,
+378,2395,2651,1592,65222,65223,12374,65224,1586,65200,12470,1429,1428,1432,
+1494,64310,64310,1494,12567,382,9449,7825,657,380,380,7827,1079,1177,1247,
+12380,12476,48,1632,2534,2406,2790,2662,1632,8320,65296,63280,1776,8304,3664,
+65279,8204,8203,950,12563,1386,1218,1078,1175,1245,12376,12472,1454,7829,
+65370,12382,12478,9397,656,438,12378,12474,
+};
+
+static const unsigned short agl_dup_offsets[] = {
+32,0,124,3,160,6,173,9,175,12,181,15,183,18,266,21,267,24,272,27,273,30,
+278,33,279,36,288,39,289,42,290,45,291,48,304,51,310,54,311,57,315,60,316,63,
+319,66,320,69,325,72,326,75,329,78,336,81,337,84,342,87,343,90,354,93,355,96,
+368,99,369,102,379,105,380,108,383,111,510,114,511,117,700,120,701,123,
+732,126,768,129,769,132,771,135,777,138,803,141,901,144,962,147,977,150,
+978,153,981,156,982,159,1025,162,1026,165,1027,168,1028,171,1029,174,1030,177,
+1031,180,1032,183,1033,186,1034,189,1035,192,1036,195,1038,198,1039,201,
+1040,204,1041,207,1042,210,1043,213,1044,216,1045,219,1046,222,1047,225,
+1048,228,1049,231,1050,234,1051,237,1052,240,1053,243,1054,246,1055,249,
+1056,252,1057,255,1058,258,1059,261,1060,264,1061,267,1062,270,1063,273,
+1064,276,1065,279,1066,282,1067,285,1068,288,1069,291,1070,294,1071,297,
+1072,300,1073,303,1074,306,1075,309,1076,312,1077,315,1078,318,1079,321,
+1080,324,1081,327,1082,330,1083,333,1084,336,1085,339,1086,342,1087,345,
+1088,348,1089,351,1090,354,1091,357,1092,360,1093,363,1094,366,1095,369,
+1096,372,1097,375,1098,378,1099,381,1100,384,1101,387,1102,390,1103,393,
+1105,396,1106,399,1107,402,1108,405,1109,408,1110,411,1111,414,1112,417,
+1113,420,1114,423,1115,426,1116,429,1118,432,1119,435,1122,438,1123,441,
+1138,444,1139,447,1140,450,1141,453,1168,456,1169,459,1241,462,1425,465,
+1430,470,1431,473,1435,476,1443,479,1444,482,1445,485,1446,488,1447,491,
+1450,494,1456,497,1457,508,1458,518,1459,528,1460,538,1461,548,1462,558,
+1463,568,1464,578,1465,596,1467,606,1468,616,1469,620,1470,624,1471,627,
+1472,631,1473,634,1474,637,1475,640,1488,643,1489,647,1490,651,1491,655,
+1492,679,1493,683,1494,687,1495,691,1496,695,1497,699,1498,703,1499,711,
+1500,715,1501,723,1502,727,1503,731,1504,735,1505,739,1506,743,1507,747,
+1508,751,1509,755,1510,759,1511,763,1512,787,1513,811,1514,815,1520,819,
+1521,822,1522,825,1548,828,1563,831,1567,834,1569,837,1570,848,1571,851,
+1572,854,1573,857,1574,860,1575,863,1576,866,1577,869,1578,872,1579,875,
+1580,878,1581,881,1582,884,1583,887,1584,890,1585,893,1586,897,1587,900,
+1588,903,1589,906,1590,909,1591,912,1592,915,1593,918,1594,921,1600,924,
+1601,929,1602,932,1603,935,1604,938,1605,941,1606,944,1607,947,1608,950,
+1609,953,1610,956,1611,959,1612,962,1613,966,1614,969,1615,973,1616,977,
+1617,980,1618,984,1632,987,1633,991,1634,995,1635,999,1636,1003,1637,1007,
+1638,1011,1639,1015,1640,1019,1641,1023,1642,1027,1643,1030,1644,1033,
+1645,1036,1657,1040,1662,1043,1670,1046,1672,1049,1681,1052,1688,1055,
+1700,1058,1711,1061,1722,1064,1729,1067,1746,1070,8204,1073,8213,1076,
+8215,1079,8219,1082,8229,1085,8353,1088,8356,1091,8362,1094,8364,1099,
+8453,1102,8467,1105,8470,1108,8486,1111,8616,1114,8656,1117,8658,1120,
+8660,1123,8704,1126,8707,1129,8710,1132,8711,1135,8713,1138,8735,1141,
+8764,1144,8773,1147,8834,1150,8835,1153,8838,1156,8839,1159,8853,1162,
+8855,1165,8976,1168,8992,1171,8993,1174,9617,1177,9618,1180,9619,1183,
+9632,1186,9633,1189,9642,1192,9643,1195,9644,1198,9650,1201,9658,1204,
+9660,1207,9668,1210,9675,1213,9679,1216,9688,1219,9689,1222,9702,1225,
+9786,1228,9787,1231,9788,1234,9792,1237,9794,1240,9824,1243,9827,1246,
+9829,1249,9835,1252,64287,1255,64298,1260,64299,1264,64300,1268,64301,1271,
+64305,1274,64306,1277,64307,1280,64308,1283,64309,1286,64310,1291,64312,1294,
+64313,1297,64314,1300,64315,1303,64316,1306,64318,1309,64320,1312,64321,1315,
+64324,1318,64326,1321,64327,1324,64329,1327,64330,1330,64331,1334,64380,1338,
+65247,1341,65255,1345,65258,1348,65267,1351,65268,1354,
+};
+
+static const char *agl_dup_names[] = {
+"space","spacehackarabic",0,"bar","verticalbar",0,"nbspace",
+"nonbreakingspace",0,"sfthyphen","softhyphen",0,"macron","overscore",0,"mu",
+"mu1",0,"middot","periodcentered",0,"Cdot","Cdotaccent",0,"cdot","cdotaccent",
+0,"Dcroat","Dslash",0,"dcroat","dmacron",0,"Edot","Edotaccent",0,"edot",
+"edotaccent",0,"Gdot","Gdotaccent",0,"gdot","gdotaccent",0,"Gcedilla",
+"Gcommaaccent",0,"gcedilla","gcommaaccent",0,"Idot","Idotaccent",0,"Kcedilla",
+"Kcommaaccent",0,"kcedilla","kcommaaccent",0,"Lcedilla","Lcommaaccent",0,
+"lcedilla","lcommaaccent",0,"Ldot","Ldotaccent",0,"ldot","ldotaccent",0,
+"Ncedilla","Ncommaaccent",0,"ncedilla","ncommaaccent",0,"napostrophe",
+"quoterightn",0,"Odblacute","Ohungarumlaut",0,"odblacute","ohungarumlaut",0,
+"Rcedilla","Rcommaaccent",0,"rcedilla","rcommaaccent",0,"Tcedilla",
+"Tcommaaccent",0,"tcedilla","tcommaaccent",0,"Udblacute","Uhungarumlaut",0,
+"udblacute","uhungarumlaut",0,"Zdot","Zdotaccent",0,"zdot","zdotaccent",0,
+"longs","slong",0,"Oslashacute","Ostrokeacute",0,"oslashacute","ostrokeacute",
+0,"afii57929","apostrophemod",0,"afii64937","commareversedmod",0,"ilde",
+"tilde",0,"gravecmb","gravecomb",0,"acutecmb","acutecomb",0,"tildecmb",
+"tildecomb",0,"hookabovecomb","hookcmb",0,"dotbelowcmb","dotbelowcomb",0,
+"dialytikatonos","dieresistonos",0,"sigma1","sigmafinal",0,"theta1",
+"thetasymbolgreek",0,"Upsilon1","Upsilonhooksymbol",0,"phi1","phisymbolgreek",
+0,"omega1","pisymbolgreek",0,"Iocyrillic","afii10023",0,"Djecyrillic",
+"afii10051",0,"Gjecyrillic","afii10052",0,"Ecyrillic","afii10053",0,
+"Dzecyrillic","afii10054",0,"Icyrillic","afii10055",0,"Yicyrillic",
+"afii10056",0,"Jecyrillic","afii10057",0,"Ljecyrillic","afii10058",0,
+"Njecyrillic","afii10059",0,"Tshecyrillic","afii10060",0,"Kjecyrillic",
+"afii10061",0,"Ushortcyrillic","afii10062",0,"Dzhecyrillic","afii10145",0,
+"Acyrillic","afii10017",0,"Becyrillic","afii10018",0,"Vecyrillic","afii10019",
+0,"Gecyrillic","afii10020",0,"Decyrillic","afii10021",0,"Iecyrillic",
+"afii10022",0,"Zhecyrillic","afii10024",0,"Zecyrillic","afii10025",0,
+"Iicyrillic","afii10026",0,"Iishortcyrillic","afii10027",0,"Kacyrillic",
+"afii10028",0,"Elcyrillic","afii10029",0,"Emcyrillic","afii10030",0,
+"Encyrillic","afii10031",0,"Ocyrillic","afii10032",0,"Pecyrillic","afii10033",
+0,"Ercyrillic","afii10034",0,"Escyrillic","afii10035",0,"Tecyrillic",
+"afii10036",0,"Ucyrillic","afii10037",0,"Efcyrillic","afii10038",0,
+"Khacyrillic","afii10039",0,"Tsecyrillic","afii10040",0,"Checyrillic",
+"afii10041",0,"Shacyrillic","afii10042",0,"Shchacyrillic","afii10043",0,
+"Hardsigncyrillic","afii10044",0,"Yericyrillic","afii10045",0,
+"Softsigncyrillic","afii10046",0,"Ereversedcyrillic","afii10047",0,
+"IUcyrillic","afii10048",0,"IAcyrillic","afii10049",0,"acyrillic","afii10065",
+0,"afii10066","becyrillic",0,"afii10067","vecyrillic",0,"afii10068",
+"gecyrillic",0,"afii10069","decyrillic",0,"afii10070","iecyrillic",0,
+"afii10072","zhecyrillic",0,"afii10073","zecyrillic",0,"afii10074",
+"iicyrillic",0,"afii10075","iishortcyrillic",0,"afii10076","kacyrillic",0,
+"afii10077","elcyrillic",0,"afii10078","emcyrillic",0,"afii10079",
+"encyrillic",0,"afii10080","ocyrillic",0,"afii10081","pecyrillic",0,
+"afii10082","ercyrillic",0,"afii10083","escyrillic",0,"afii10084",
+"tecyrillic",0,"afii10085","ucyrillic",0,"afii10086","efcyrillic",0,
+"afii10087","khacyrillic",0,"afii10088","tsecyrillic",0,"afii10089",
+"checyrillic",0,"afii10090","shacyrillic",0,"afii10091","shchacyrillic",0,
+"afii10092","hardsigncyrillic",0,"afii10093","yericyrillic",0,"afii10094",
+"softsigncyrillic",0,"afii10095","ereversedcyrillic",0,"afii10096",
+"iucyrillic",0,"afii10097","iacyrillic",0,"afii10071","iocyrillic",0,
+"afii10099","djecyrillic",0,"afii10100","gjecyrillic",0,"afii10101",
+"ecyrillic",0,"afii10102","dzecyrillic",0,"afii10103","icyrillic",0,
+"afii10104","yicyrillic",0,"afii10105","jecyrillic",0,"afii10106",
+"ljecyrillic",0,"afii10107","njecyrillic",0,"afii10108","tshecyrillic",0,
+"afii10109","kjecyrillic",0,"afii10110","ushortcyrillic",0,"afii10193",
+"dzhecyrillic",0,"Yatcyrillic","afii10146",0,"afii10194","yatcyrillic",0,
+"Fitacyrillic","afii10147",0,"afii10195","fitacyrillic",0,"Izhitsacyrillic",
+"afii10148",0,"afii10196","izhitsacyrillic",0,"Gheupturncyrillic","afii10050",
+0,"afii10098","gheupturncyrillic",0,"afii10846","schwacyrillic",0,
+"etnahtafoukhhebrew","etnahtafoukhlefthebrew","etnahtahebrew",
+"etnahtalefthebrew",0,"tipehahebrew","tipehalefthebrew",0,"reviahebrew",
+"reviamugrashhebrew",0,"tevirhebrew","tevirlefthebrew",0,"munahhebrew",
+"munahlefthebrew",0,"mahapakhhebrew","mahapakhlefthebrew",0,"merkhahebrew",
+"merkhalefthebrew",0,"merkhakefulahebrew","merkhakefulalefthebrew",0,
+"dargahebrew","dargalefthebrew",0,"yerahbenyomohebrew",
+"yerahbenyomolefthebrew",0,"afii57799","sheva","sheva115","sheva15","sheva22",
+"sheva2e","shevahebrew","shevanarrowhebrew","shevaquarterhebrew",
+"shevawidehebrew",0,"afii57801","hatafsegol","hatafsegol17","hatafsegol24",
+"hatafsegol30","hatafsegolhebrew","hatafsegolnarrowhebrew",
+"hatafsegolquarterhebrew","hatafsegolwidehebrew",0,"afii57800","hatafpatah",
+"hatafpatah16","hatafpatah23","hatafpatah2f","hatafpatahhebrew",
+"hatafpatahnarrowhebrew","hatafpatahquarterhebrew","hatafpatahwidehebrew",0,
+"afii57802","hatafqamats","hatafqamats1b","hatafqamats28","hatafqamats34",
+"hatafqamatshebrew","hatafqamatsnarrowhebrew","hatafqamatsquarterhebrew",
+"hatafqamatswidehebrew",0,"afii57793","hiriq","hiriq14","hiriq21","hiriq2d",
+"hiriqhebrew","hiriqnarrowhebrew","hiriqquarterhebrew","hiriqwidehebrew",0,
+"afii57794","tsere","tsere12","tsere1e","tsere2b","tserehebrew",
+"tserenarrowhebrew","tserequarterhebrew","tserewidehebrew",0,"afii57795",
+"segol","segol13","segol1f","segol2c","segolhebrew","segolnarrowhebrew",
+"segolquarterhebrew","segolwidehebrew",0,"afii57798","patah","patah11",
+"patah1d","patah2a","patahhebrew","patahnarrowhebrew","patahquarterhebrew",
+"patahwidehebrew",0,"afii57797","qamats","qamats10","qamats1a","qamats1c",
+"qamats27","qamats29","qamats33","qamatsde","qamatshebrew",
+"qamatsnarrowhebrew","qamatsqatanhebrew","qamatsqatannarrowhebrew",
+"qamatsqatanquarterhebrew","qamatsqatanwidehebrew","qamatsquarterhebrew",
+"qamatswidehebrew",0,"afii57806","holam","holam19","holam26","holam32",
+"holamhebrew","holamnarrowhebrew","holamquarterhebrew","holamwidehebrew",0,
+"afii57796","qubuts","qubuts18","qubuts25","qubuts31","qubutshebrew",
+"qubutsnarrowhebrew","qubutsquarterhebrew","qubutswidehebrew",0,"afii57807",
+"dagesh","dageshhebrew",0,"afii57839","siluqhebrew","siluqlefthebrew",0,
+"afii57645","maqafhebrew",0,"afii57841","rafe","rafehebrew",0,"afii57842",
+"paseqhebrew",0,"afii57804","shindothebrew",0,"afii57803","sindothebrew",0,
+"afii57658","sofpasuqhebrew",0,"afii57664","alef","alefhebrew",0,"afii57665",
+"bet","bethebrew",0,"afii57666","gimel","gimelhebrew",0,"afii57667","dalet",
+"dalethatafpatah","dalethatafpatahhebrew","dalethatafsegol",
+"dalethatafsegolhebrew","dalethebrew","dalethiriq","dalethiriqhebrew",
+"daletholam","daletholamhebrew","daletpatah","daletpatahhebrew","daletqamats",
+"daletqamatshebrew","daletqubuts","daletqubutshebrew","daletsegol",
+"daletsegolhebrew","daletsheva","daletshevahebrew","dalettsere",
+"dalettserehebrew",0,"afii57668","he","hehebrew",0,"afii57669","vav",
+"vavhebrew",0,"afii57670","zayin","zayinhebrew",0,"afii57671","het",
+"hethebrew",0,"afii57672","tet","tethebrew",0,"afii57673","yod","yodhebrew",0,
+"afii57674","finalkaf","finalkafhebrew","finalkafqamats",
+"finalkafqamatshebrew","finalkafsheva","finalkafshevahebrew",0,"afii57675",
+"kaf","kafhebrew",0,"afii57676","lamed","lamedhebrew","lamedholam",
+"lamedholamdagesh","lamedholamdageshhebrew","lamedholamhebrew",0,"afii57677",
+"finalmem","finalmemhebrew",0,"afii57678","mem","memhebrew",0,"afii57679",
+"finalnun","finalnunhebrew",0,"afii57680","nun","nunhebrew",0,"afii57681",
+"samekh","samekhhebrew",0,"afii57682","ayin","ayinhebrew",0,"afii57683",
+"finalpe","finalpehebrew",0,"afii57684","pe","pehebrew",0,"afii57685",
+"finaltsadi","finaltsadihebrew",0,"afii57686","tsadi","tsadihebrew",0,
+"afii57687","qof","qofhatafpatah","qofhatafpatahhebrew","qofhatafsegol",
+"qofhatafsegolhebrew","qofhebrew","qofhiriq","qofhiriqhebrew","qofholam",
+"qofholamhebrew","qofpatah","qofpatahhebrew","qofqamats","qofqamatshebrew",
+"qofqubuts","qofqubutshebrew","qofsegol","qofsegolhebrew","qofsheva",
+"qofshevahebrew","qoftsere","qoftserehebrew",0,"afii57688","resh",
+"reshhatafpatah","reshhatafpatahhebrew","reshhatafsegol",
+"reshhatafsegolhebrew","reshhebrew","reshhiriq","reshhiriqhebrew","reshholam",
+"reshholamhebrew","reshpatah","reshpatahhebrew","reshqamats",
+"reshqamatshebrew","reshqubuts","reshqubutshebrew","reshsegol",
+"reshsegolhebrew","reshsheva","reshshevahebrew","reshtsere","reshtserehebrew",
+0,"afii57689","shin","shinhebrew",0,"afii57690","tav","tavhebrew",0,
+"afii57716","vavvavhebrew",0,"afii57717","vavyodhebrew",0,"afii57718",
+"yodyodhebrew",0,"afii57388","commaarabic",0,"afii57403","semicolonarabic",0,
+"afii57407","questionarabic",0,"afii57409","hamzaarabic","hamzadammaarabic",
+"hamzadammatanarabic","hamzafathaarabic","hamzafathatanarabic",
+"hamzalowarabic","hamzalowkasraarabic","hamzalowkasratanarabic",
+"hamzasukunarabic",0,"afii57410","alefmaddaabovearabic",0,"afii57411",
+"alefhamzaabovearabic",0,"afii57412","wawhamzaabovearabic",0,"afii57413",
+"alefhamzabelowarabic",0,"afii57414","yehhamzaabovearabic",0,"afii57415",
+"alefarabic",0,"afii57416","beharabic",0,"afii57417","tehmarbutaarabic",0,
+"afii57418","teharabic",0,"afii57419","theharabic",0,"afii57420","jeemarabic",
+0,"afii57421","haharabic",0,"afii57422","khaharabic",0,"afii57423",
+"dalarabic",0,"afii57424","thalarabic",0,"afii57425","reharabic",
+"rehyehaleflamarabic",0,"afii57426","zainarabic",0,"afii57427","seenarabic",0,
+"afii57428","sheenarabic",0,"afii57429","sadarabic",0,"afii57430","dadarabic",
+0,"afii57431","taharabic",0,"afii57432","zaharabic",0,"afii57433","ainarabic",
+0,"afii57434","ghainarabic",0,"afii57440","kashidaautoarabic",
+"kashidaautonosidebearingarabic","tatweelarabic",0,"afii57441","feharabic",0,
+"afii57442","qafarabic",0,"afii57443","kafarabic",0,"afii57444","lamarabic",0,
+"afii57445","meemarabic",0,"afii57446","noonarabic",0,"afii57470","heharabic",
+0,"afii57448","wawarabic",0,"afii57449","alefmaksuraarabic",0,"afii57450",
+"yeharabic",0,"afii57451","fathatanarabic",0,"afii57452",
+"dammatanaltonearabic","dammatanarabic",0,"afii57453","kasratanarabic",0,
+"afii57454","fathaarabic","fathalowarabic",0,"afii57455","dammaarabic",
+"dammalowarabic",0,"afii57456","kasraarabic",0,"afii57457","shaddaarabic",
+"shaddafathatanarabic",0,"afii57458","sukunarabic",0,"afii57392","zeroarabic",
+"zerohackarabic",0,"afii57393","onearabic","onehackarabic",0,"afii57394",
+"twoarabic","twohackarabic",0,"afii57395","threearabic","threehackarabic",0,
+"afii57396","fourarabic","fourhackarabic",0,"afii57397","fivearabic",
+"fivehackarabic",0,"afii57398","sixarabic","sixhackarabic",0,"afii57399",
+"sevenarabic","sevenhackarabic",0,"afii57400","eightarabic","eighthackarabic",
+0,"afii57401","ninearabic","ninehackarabic",0,"afii57381","percentarabic",0,
+"decimalseparatorarabic","decimalseparatorpersian",0,
+"thousandsseparatorarabic","thousandsseparatorpersian",0,"afii63167",
+"asteriskaltonearabic","asteriskarabic",0,"afii57511","tteharabic",0,
+"afii57506","peharabic",0,"afii57507","tcheharabic",0,"afii57512",
+"ddalarabic",0,"afii57513","rreharabic",0,"afii57508","jeharabic",0,
+"afii57505","veharabic",0,"afii57509","gafarabic",0,"afii57514",
+"noonghunnaarabic",0,"haaltonearabic","hehaltonearabic",0,"afii57519",
+"yehbarreearabic",0,"afii61664","zerowidthnonjoiner",0,"afii00208",
+"horizontalbar",0,"dbllowline","underscoredbl",0,"quoteleftreversed",
+"quotereversed",0,"twodotenleader","twodotleader",0,"colonmonetary",
+"colonsign",0,"afii08941","lira",0,"afii57636","newsheqelsign","sheqel",
+"sheqelhebrew",0,"Euro","euro",0,"afii61248","careof",0,"afii61289","lsquare",
+0,"afii61352","numero",0,"Ohm","Omega",0,"arrowupdnbse","arrowupdownbase",0,
+"arrowdblleft","arrowleftdbl",0,"arrowdblright","dblarrowright",0,
+"arrowdblboth","dblarrowleft",0,"forall","universal",0,"existential",
+"thereexists",0,"Delta","increment",0,"gradient","nabla",0,"notelement",
+"notelementof",0,"orthogonal","rightangle",0,"similar","tildeoperator",0,
+"approximatelyequal","congruent",0,"propersubset","subset",0,"propersuperset",
+"superset",0,"reflexsubset","subsetorequal",0,"reflexsuperset",
+"supersetorequal",0,"circleplus","pluscircle",0,"circlemultiply",
+"timescircle",0,"logicalnotreversed","revlogicalnot",0,"integraltop",
+"integraltp",0,"integralbottom","integralbt",0,"ltshade","shadelight",0,
+"shade","shademedium",0,"dkshade","shadedark",0,"blacksquare","filledbox",0,
+"H22073","whitesquare",0,"H18543","blacksmallsquare",0,"H18551",
+"whitesmallsquare",0,"blackrectangle","filledrect",0,
+"blackuppointingtriangle","triagup",0,"blackrightpointingpointer","triagrt",0,
+"blackdownpointingtriangle","triagdn",0,"blackleftpointingpointer","triaglf",
+0,"circle","whitecircle",0,"H18533","blackcircle",0,"bulletinverse",
+"invbullet",0,"invcircle","whitecircleinverse",0,"openbullet","whitebullet",0,
+"smileface","whitesmilingface",0,"blacksmilingface","invsmileface",0,
+"compass","sun",0,"female","venus",0,"male","mars",0,"spade","spadesuitblack",
+0,"club","clubsuitblack",0,"heart","heartsuitblack",0,"eighthnotebeamed",
+"musicalnotedbl",0,"afii57705","doubleyodpatah","doubleyodpatahhebrew",
+"yodyodpatahhebrew",0,"afii57694","shinshindot","shinshindothebrew",0,
+"afii57695","shinsindot","shinsindothebrew",0,"shindageshshindot",
+"shindageshshindothebrew",0,"shindageshsindot","shindageshsindothebrew",0,
+"betdagesh","betdageshhebrew",0,"gimeldagesh","gimeldageshhebrew",0,
+"daletdagesh","daletdageshhebrew",0,"hedagesh","hedageshhebrew",0,"afii57723",
+"vavdagesh","vavdagesh65","vavdageshhebrew",0,"zayindagesh",
+"zayindageshhebrew",0,"tetdagesh","tetdageshhebrew",0,"yoddagesh",
+"yoddageshhebrew",0,"finalkafdagesh","finalkafdageshhebrew",0,"kafdagesh",
+"kafdageshhebrew",0,"lameddagesh","lameddageshhebrew",0,"memdagesh",
+"memdageshhebrew",0,"nundagesh","nundageshhebrew",0,"samekhdagesh",
+"samekhdageshhebrew",0,"pedagesh","pedageshhebrew",0,"tsadidagesh",
+"tsadidageshhebrew",0,"qofdagesh","qofdageshhebrew",0,"shindagesh",
+"shindageshhebrew",0,"tavdages","tavdagesh","tavdageshhebrew",0,"afii57700",
+"vavholam","vavholamhebrew",0,"tchehinitialarabic","tchehmeeminitialarabic",0,
+"laminitialarabic","lammeemjeeminitialarabic","lammeemkhahinitialarabic",0,
+"noonhehinitialarabic","nooninitialarabic",0,"hehfinalalttwoarabic",
+"hehfinalarabic",0,"alefmaksurainitialarabic","yehinitialarabic",0,
+"alefmaksuramedialarabic","yehmedialarabic",0,
+};
diff --git a/source/pdf/pdf-image.c b/source/pdf/pdf-image.c
new file mode 100644
index 00000000..719841d5
--- /dev/null
+++ b/source/pdf/pdf-image.c
@@ -0,0 +1,285 @@
+#include "mupdf/pdf.h"
+
+static fz_image *pdf_load_jpx(pdf_document *xref, pdf_obj *dict, int forcemask);
+
+static fz_image *
+pdf_load_image_imp(pdf_document *xref, pdf_obj *rdb, pdf_obj *dict, fz_stream *cstm, int forcemask)
+{
+ fz_stream *stm = NULL;
+ fz_image *image = NULL;
+ pdf_obj *obj, *res;
+
+ int w, h, bpc, n;
+ int imagemask;
+ int interpolate;
+ int indexed;
+ fz_image *mask = NULL; /* explicit mask/soft mask image */
+ int usecolorkey = 0;
+ fz_colorspace *colorspace = NULL;
+ float decode[FZ_MAX_COLORS * 2];
+ int colorkey[FZ_MAX_COLORS * 2];
+
+ int i;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(stm);
+ fz_var(mask);
+ fz_var(image);
+
+ fz_try(ctx)
+ {
+ /* special case for JPEG2000 images */
+ if (pdf_is_jpx_image(ctx, dict))
+ {
+ image = pdf_load_jpx(xref, dict, forcemask);
+
+ if (forcemask)
+ {
+ fz_pixmap *mask_pixmap;
+ if (image->n != 2)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "soft mask must be grayscale");
+ mask_pixmap = fz_alpha_from_gray(ctx, image->tile, 1);
+ fz_drop_pixmap(ctx, image->tile);
+ image->tile = mask_pixmap;
+ }
+ break; /* Out of fz_try */
+ }
+
+ w = pdf_to_int(pdf_dict_getsa(dict, "Width", "W"));
+ h = pdf_to_int(pdf_dict_getsa(dict, "Height", "H"));
+ bpc = pdf_to_int(pdf_dict_getsa(dict, "BitsPerComponent", "BPC"));
+ if (bpc == 0)
+ bpc = 8;
+ imagemask = pdf_to_bool(pdf_dict_getsa(dict, "ImageMask", "IM"));
+ interpolate = pdf_to_bool(pdf_dict_getsa(dict, "Interpolate", "I"));
+
+ indexed = 0;
+ usecolorkey = 0;
+ mask = NULL;
+
+ if (imagemask)
+ bpc = 1;
+
+ if (w <= 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image width is zero (or less)");
+ if (h <= 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image height is zero (or less)");
+ if (bpc <= 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image depth is zero (or less)");
+ if (bpc > 16)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image depth is too large: %d", bpc);
+ if (w > (1 << 16))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image is too wide");
+ if (h > (1 << 16))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "image is too high");
+
+ obj = pdf_dict_getsa(dict, "ColorSpace", "CS");
+ if (obj && !imagemask && !forcemask)
+ {
+ /* colorspace resource lookup is only done for inline images */
+ if (pdf_is_name(obj))
+ {
+ res = pdf_dict_get(pdf_dict_gets(rdb, "ColorSpace"), obj);
+ if (res)
+ obj = res;
+ }
+
+ colorspace = pdf_load_colorspace(xref, obj);
+
+ if (!strcmp(colorspace->name, "Indexed"))
+ indexed = 1;
+
+ n = colorspace->n;
+ }
+ else
+ {
+ n = 1;
+ }
+
+ obj = pdf_dict_getsa(dict, "Decode", "D");
+ if (obj)
+ {
+ for (i = 0; i < n * 2; i++)
+ decode[i] = pdf_to_real(pdf_array_get(obj, i));
+ }
+ else
+ {
+ float maxval = indexed ? (1 << bpc) - 1 : 1;
+ for (i = 0; i < n * 2; i++)
+ decode[i] = i & 1 ? maxval : 0;
+ }
+
+ obj = pdf_dict_getsa(dict, "SMask", "Mask");
+ if (pdf_is_dict(obj))
+ {
+ /* Not allowed for inline images or soft masks */
+ if (cstm)
+ fz_warn(ctx, "Ignoring invalid inline image soft mask");
+ else if (forcemask)
+ fz_warn(ctx, "Ignoring recursive image soft mask");
+ else
+ mask = (fz_image *)pdf_load_image_imp(xref, rdb, obj, NULL, 1);
+ }
+ else if (pdf_is_array(obj))
+ {
+ usecolorkey = 1;
+ for (i = 0; i < n * 2; i++)
+ {
+ if (!pdf_is_int(pdf_array_get(obj, i)))
+ {
+ fz_warn(ctx, "invalid value in color key mask");
+ usecolorkey = 0;
+ }
+ colorkey[i] = pdf_to_int(pdf_array_get(obj, i));
+ }
+ }
+
+ /* Now, do we load a ref, or do we load the actual thing? */
+ if (!cstm)
+ {
+ /* Just load the compressed image data now and we can
+ * decode it on demand. */
+ int num = pdf_to_num(dict);
+ int gen = pdf_to_gen(dict);
+ fz_compressed_buffer *buffer = pdf_load_compressed_stream(xref, num, gen);
+ image = fz_new_image(ctx, w, h, bpc, colorspace, 96, 96, interpolate, imagemask, decode, usecolorkey ? colorkey : NULL, buffer, mask);
+ break; /* Out of fz_try */
+ }
+
+ /* We need to decompress the image now */
+ if (cstm)
+ {
+ int stride = (w * n * bpc + 7) / 8;
+ stm = pdf_open_inline_stream(xref, dict, stride * h, cstm, NULL);
+ }
+ else
+ {
+ stm = pdf_open_stream(xref, pdf_to_num(dict), pdf_to_gen(dict));
+ }
+
+ image = fz_new_image(ctx, w, h, bpc, colorspace, 96, 96, interpolate, imagemask, decode, usecolorkey ? colorkey : NULL, NULL, mask);
+ image->tile = fz_decomp_image_from_stream(ctx, stm, image, cstm != NULL, indexed, 0, 0);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_image(ctx, image);
+ fz_rethrow(ctx);
+ }
+ return image;
+}
+
+fz_image *
+pdf_load_inline_image(pdf_document *xref, pdf_obj *rdb, pdf_obj *dict, fz_stream *file)
+{
+ return (fz_image *)pdf_load_image_imp(xref, rdb, dict, file, 0);
+}
+
+int
+pdf_is_jpx_image(fz_context *ctx, pdf_obj *dict)
+{
+ pdf_obj *filter;
+ int i, n;
+
+ filter = pdf_dict_gets(dict, "Filter");
+ if (!strcmp(pdf_to_name(filter), "JPXDecode"))
+ return 1;
+ n = pdf_array_len(filter);
+ for (i = 0; i < n; i++)
+ if (!strcmp(pdf_to_name(pdf_array_get(filter, i)), "JPXDecode"))
+ return 1;
+ return 0;
+}
+
+static fz_image *
+pdf_load_jpx(pdf_document *xref, pdf_obj *dict, int forcemask)
+{
+ fz_buffer *buf = NULL;
+ fz_colorspace *colorspace = NULL;
+ fz_pixmap *img = NULL;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+ int indexed = 0;
+ fz_image *mask = NULL;
+
+ fz_var(img);
+ fz_var(buf);
+ fz_var(colorspace);
+ fz_var(mask);
+
+ buf = pdf_load_stream(xref, pdf_to_num(dict), pdf_to_gen(dict));
+
+ /* FIXME: We can't handle decode arrays for indexed images currently */
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(dict, "ColorSpace");
+ if (obj)
+ {
+ colorspace = pdf_load_colorspace(xref, obj);
+ indexed = !strcmp(colorspace->name, "Indexed");
+ }
+
+ img = fz_load_jpx(ctx, buf->data, buf->len, colorspace, indexed);
+
+ if (img && colorspace == NULL)
+ colorspace = fz_keep_colorspace(ctx, img->colorspace);
+
+ fz_drop_buffer(ctx, buf);
+ buf = NULL;
+
+ obj = pdf_dict_getsa(dict, "SMask", "Mask");
+ if (pdf_is_dict(obj))
+ {
+ if (forcemask)
+ fz_warn(ctx, "Ignoring recursive JPX soft mask");
+ else
+ mask = (fz_image *)pdf_load_image_imp(xref, NULL, obj, NULL, 1);
+ }
+
+ obj = pdf_dict_getsa(dict, "Decode", "D");
+ if (obj && !indexed)
+ {
+ float decode[FZ_MAX_COLORS * 2];
+ int i;
+
+ for (i = 0; i < img->n * 2; i++)
+ decode[i] = pdf_to_real(pdf_array_get(obj, i));
+
+ fz_decode_tile(img, decode);
+ }
+ }
+ fz_catch(ctx)
+ {
+ if (colorspace)
+ fz_drop_colorspace(ctx, colorspace);
+ fz_drop_buffer(ctx, buf);
+ fz_drop_pixmap(ctx, img);
+ fz_rethrow(ctx);
+ }
+ return fz_new_image_from_pixmap(ctx, img, mask);
+}
+
+static int
+fz_image_size(fz_context *ctx, fz_image *im)
+{
+ if (im == NULL)
+ return 0;
+ return sizeof(*im) + fz_pixmap_size(ctx, im->tile) + (im->buffer && im->buffer->buffer ? im->buffer->buffer->cap : 0);
+}
+
+fz_image *
+pdf_load_image(pdf_document *xref, pdf_obj *dict)
+{
+ fz_context *ctx = xref->ctx;
+ fz_image *image;
+
+ if ((image = pdf_find_item(ctx, fz_free_image, dict)))
+ {
+ return (fz_image *)image;
+ }
+
+ image = pdf_load_image_imp(xref, NULL, dict, NULL, 0);
+
+ pdf_store_item(ctx, dict, image, fz_image_size(ctx, image));
+
+ return (fz_image *)image;
+}
diff --git a/source/pdf/pdf-interpret.c b/source/pdf/pdf-interpret.c
new file mode 100644
index 00000000..43a6d466
--- /dev/null
+++ b/source/pdf/pdf-interpret.c
@@ -0,0 +1,3111 @@
+#include "mupdf/pdf.h"
+
+#define TILE
+
+typedef struct pdf_material_s pdf_material;
+typedef struct pdf_gstate_s pdf_gstate;
+typedef struct pdf_csi_s pdf_csi;
+
+enum
+{
+ PDF_FILL,
+ PDF_STROKE,
+};
+
+enum
+{
+ PDF_MAT_NONE,
+ PDF_MAT_COLOR,
+ PDF_MAT_PATTERN,
+ PDF_MAT_SHADE,
+};
+
+struct pdf_material_s
+{
+ int kind;
+ fz_colorspace *colorspace;
+ pdf_pattern *pattern;
+ fz_shade *shade;
+ int gstate_num;
+ float alpha;
+ float v[FZ_MAX_COLORS];
+};
+
+struct pdf_gstate_s
+{
+ fz_matrix ctm;
+ int clip_depth;
+
+ /* path stroking */
+ fz_stroke_state *stroke_state;
+
+ /* materials */
+ pdf_material stroke;
+ pdf_material fill;
+
+ /* text state */
+ float char_space;
+ float word_space;
+ float scale;
+ float leading;
+ pdf_font_desc *font;
+ float size;
+ int render;
+ float rise;
+
+ /* transparency */
+ int blendmode;
+ pdf_xobject *softmask;
+ fz_matrix softmask_ctm;
+ float softmask_bc[FZ_MAX_COLORS];
+ int luminosity;
+};
+
+struct pdf_csi_s
+{
+ fz_device *dev;
+ pdf_document *xref;
+
+ int nested_depth;
+
+ /* usage mode for optional content groups */
+ char *event; /* "View", "Print", "Export" */
+
+ /* interpreter stack */
+ pdf_obj *obj;
+ char name[256];
+ unsigned char string[256];
+ int string_len;
+ float stack[32];
+ int top;
+
+ int xbalance;
+ int in_text;
+ int in_hidden_ocg;
+
+ /* path object state */
+ fz_path *path;
+ int clip;
+ int clip_even_odd;
+
+ /* text object state */
+ fz_text *text;
+ fz_rect text_bbox;
+ fz_matrix tlm;
+ fz_matrix tm;
+ int text_mode;
+ int accumulate;
+
+ /* graphics state */
+ pdf_gstate *gstate;
+ int gcap;
+ int gtop;
+ int gbot;
+ int gparent;
+
+ /* cookie support */
+ fz_cookie *cookie;
+};
+
+static void pdf_run_contents_object(pdf_csi *csi, pdf_obj *rdb, pdf_obj *contents);
+static void pdf_run_xobject(pdf_csi *csi, pdf_obj *resources, pdf_xobject *xobj, const fz_matrix *transform);
+static void pdf_show_pattern(pdf_csi *csi, pdf_pattern *pat, pdf_gstate *pat_gstate, const fz_rect *area, int what);
+
+static int
+ocg_intents_include(pdf_ocg_descriptor *desc, char *name)
+{
+ int i, len;
+
+ if (strcmp(name, "All") == 0)
+ return 1;
+
+ /* In the absence of a specified intent, it's 'View' */
+ if (!desc->intent)
+ return (strcmp(name, "View") == 0);
+
+ if (pdf_is_name(desc->intent))
+ {
+ char *intent = pdf_to_name(desc->intent);
+ if (strcmp(intent, "All") == 0)
+ return 1;
+ return (strcmp(intent, name) == 0);
+ }
+ if (!pdf_is_array(desc->intent))
+ return 0;
+
+ len = pdf_array_len(desc->intent);
+ for (i=0; i < len; i++)
+ {
+ char *intent = pdf_to_name(pdf_array_get(desc->intent, i));
+ if (strcmp(intent, "All") == 0)
+ return 1;
+ if (strcmp(intent, name) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+pdf_is_hidden_ocg(pdf_obj *ocg, pdf_csi *csi, pdf_obj *rdb)
+{
+ char event_state[16];
+ pdf_obj *obj, *obj2;
+ char *type;
+ pdf_ocg_descriptor *desc = csi->xref->ocg;
+ fz_context *ctx = csi->dev->ctx;
+
+ /* Avoid infinite recursions */
+ if (pdf_obj_marked(ocg))
+ return 0;
+
+ /* If no ocg descriptor, everything is visible */
+ if (!desc)
+ return 0;
+
+ /* If we've been handed a name, look it up in the properties. */
+ if (pdf_is_name(ocg))
+ {
+ ocg = pdf_dict_gets(pdf_dict_gets(rdb, "Properties"), pdf_to_name(ocg));
+ }
+ /* If we haven't been given an ocg at all, then we're visible */
+ if (!ocg)
+ return 0;
+
+ fz_strlcpy(event_state, csi->event, sizeof event_state);
+ fz_strlcat(event_state, "State", sizeof event_state);
+
+ type = pdf_to_name(pdf_dict_gets(ocg, "Type"));
+
+ if (strcmp(type, "OCG") == 0)
+ {
+ /* An Optional Content Group */
+ int num = pdf_to_num(ocg);
+ int gen = pdf_to_gen(ocg);
+ int len = desc->len;
+ int i;
+
+ for (i = 0; i < len; i++)
+ {
+ if (desc->ocgs[i].num == num && desc->ocgs[i].gen == gen)
+ {
+ if (desc->ocgs[i].state == 0)
+ return 1; /* If off, hidden */
+ break;
+ }
+ }
+
+ /* Check Intents; if our intent is not part of the set given
+ * by the current config, we should ignore it. */
+ obj = pdf_dict_gets(ocg, "Intent");
+ if (pdf_is_name(obj))
+ {
+ /* If it doesn't match, it's hidden */
+ if (ocg_intents_include(desc, pdf_to_name(obj)) == 0)
+ return 1;
+ }
+ else if (pdf_is_array(obj))
+ {
+ int match = 0;
+ len = pdf_array_len(obj);
+ for (i=0; i<len; i++) {
+ match |= ocg_intents_include(desc, pdf_to_name(pdf_array_get(obj, i)));
+ if (match)
+ break;
+ }
+ /* If we don't match any, it's hidden */
+ if (match == 0)
+ return 1;
+ }
+ else
+ {
+ /* If it doesn't match, it's hidden */
+ if (ocg_intents_include(desc, "View") == 0)
+ return 1;
+ }
+
+ /* FIXME: Currently we do a very simple check whereby we look
+ * at the Usage object (an Optional Content Usage Dictionary)
+ * and check to see if the corresponding 'event' key is on
+ * or off.
+ *
+ * Really we should only look at Usage dictionaries that
+ * correspond to entries in the AS list in the OCG config.
+ * Given that we don't handle Zoom or User, or Language
+ * dicts, this is not really a problem. */
+ obj = pdf_dict_gets(ocg, "Usage");
+ if (!pdf_is_dict(obj))
+ return 0;
+ /* FIXME: Should look at Zoom (and return hidden if out of
+ * max/min range) */
+ /* FIXME: Could provide hooks to the caller to check if
+ * User is appropriate - if not return hidden. */
+ obj2 = pdf_dict_gets(obj, csi->event);
+ if (strcmp(pdf_to_name(pdf_dict_gets(obj2, event_state)), "OFF") == 0)
+ {
+ return 1;
+ }
+ return 0;
+ }
+ else if (strcmp(type, "OCMD") == 0)
+ {
+ /* An Optional Content Membership Dictionary */
+ char *name;
+ int combine, on;
+
+ obj = pdf_dict_gets(ocg, "VE");
+ if (pdf_is_array(obj)) {
+ /* FIXME: Calculate visibility from array */
+ return 0;
+ }
+ name = pdf_to_name(pdf_dict_gets(ocg, "P"));
+ /* Set combine; Bit 0 set => AND, Bit 1 set => true means
+ * Off, otherwise true means On */
+ if (strcmp(name, "AllOn") == 0)
+ {
+ combine = 1;
+ }
+ else if (strcmp(name, "AnyOff") == 0)
+ {
+ combine = 2;
+ }
+ else if (strcmp(name, "AllOff") == 0)
+ {
+ combine = 3;
+ }
+ else /* Assume it's the default (AnyOn) */
+ {
+ combine = 0;
+ }
+
+ if (pdf_obj_mark(ocg))
+ return 0; /* Should never happen */
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(ocg, "OCGs");
+ on = combine & 1;
+ if (pdf_is_array(obj)) {
+ int i, len;
+ len = pdf_array_len(obj);
+ for (i = 0; i < len; i++)
+ {
+ int hidden;
+ hidden = pdf_is_hidden_ocg(pdf_array_get(obj, i), csi, rdb);
+ if ((combine & 1) == 0)
+ hidden = !hidden;
+ if (combine & 2)
+ on &= hidden;
+ else
+ on |= hidden;
+ }
+ }
+ else
+ {
+ on = pdf_is_hidden_ocg(obj, csi, rdb);
+ if ((combine & 1) == 0)
+ on = !on;
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(ocg);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ return !on;
+ }
+ /* No idea what sort of object this is - be visible */
+ return 0;
+}
+
+/*
+ * Emit graphics calls to device.
+ */
+
+typedef struct softmask_save_s softmask_save;
+
+struct softmask_save_s
+{
+ pdf_xobject *softmask;
+ fz_matrix ctm;
+};
+
+static pdf_gstate *
+begin_softmask(pdf_csi * csi, softmask_save *save)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_xobject *softmask = gstate->softmask;
+ fz_rect mask_bbox;
+ fz_context *ctx;
+ fz_matrix save_tm, save_tlm, save_ctm;
+ int save_in_text;
+
+ save->softmask = softmask;
+ if (softmask == NULL)
+ return gstate;
+ save->ctm = gstate->softmask_ctm;
+ save_ctm = gstate->ctm;
+
+ mask_bbox = softmask->bbox;
+ ctx = csi->dev->ctx;
+ save_tm = csi->tm;
+ save_tlm = csi->tlm;
+ save_in_text = csi->in_text;
+
+ csi->in_text = 0;
+ if (gstate->luminosity)
+ mask_bbox = fz_infinite_rect;
+ else
+ {
+ fz_transform_rect(&mask_bbox, &softmask->matrix);
+ fz_transform_rect(&mask_bbox, &gstate->softmask_ctm);
+ }
+ gstate->softmask = NULL;
+ gstate->ctm = gstate->softmask_ctm;
+
+ fz_begin_mask(csi->dev, &mask_bbox, gstate->luminosity,
+ softmask->colorspace, gstate->softmask_bc);
+ fz_try(ctx)
+ {
+ pdf_run_xobject(csi, NULL, softmask, &fz_identity);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* FIXME: Ignore error - nasty, but if we throw from
+ * here the clip stack would be messed up. */
+ if (csi->cookie)
+ csi->cookie->errors++;
+ }
+
+ fz_end_mask(csi->dev);
+
+ csi->tm = save_tm;
+ csi->tlm = save_tlm;
+ csi->in_text = save_in_text;
+
+ gstate = csi->gstate + csi->gtop;
+ gstate->ctm = save_ctm;
+
+ return gstate;
+}
+
+static void
+end_softmask(pdf_csi *csi, softmask_save *save)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+
+ if (save->softmask == NULL)
+ return;
+
+ gstate->softmask = save->softmask;
+ gstate->softmask_ctm = save->ctm;
+ fz_pop_clip(csi->dev);
+}
+
+static void
+pdf_begin_group(pdf_csi *csi, const fz_rect *bbox, softmask_save *softmask)
+{
+ pdf_gstate *gstate = begin_softmask(csi, softmask);
+
+ if (gstate->blendmode)
+ fz_begin_group(csi->dev, bbox, 1, 0, gstate->blendmode, 1);
+}
+
+static void
+pdf_end_group(pdf_csi *csi, softmask_save *softmask)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+
+ if (gstate->blendmode)
+ fz_end_group(csi->dev);
+
+ end_softmask(csi, softmask);
+}
+
+static void
+pdf_show_shade(pdf_csi *csi, fz_shade *shd)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_rect bbox;
+ softmask_save softmask = { NULL };
+
+ if (csi->in_hidden_ocg > 0)
+ return;
+
+ fz_bound_shade(ctx, shd, &gstate->ctm, &bbox);
+
+ pdf_begin_group(csi, &bbox, &softmask);
+
+ /* FIXME: The gstate->ctm in the next line may be wrong; maybe
+ * it should be the parent gstates ctm? */
+ fz_fill_shade(csi->dev, shd, &gstate->ctm, gstate->fill.alpha);
+
+ pdf_end_group(csi, &softmask);
+}
+
+static void
+pdf_show_image(pdf_csi *csi, fz_image *image)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_matrix image_ctm;
+ fz_rect bbox;
+ softmask_save softmask = { NULL };
+
+ if (csi->in_hidden_ocg > 0)
+ return;
+
+ /* PDF has images bottom-up, so flip them right side up here */
+ image_ctm = gstate->ctm;
+ fz_pre_scale(fz_pre_translate(&image_ctm, 0, 1), 1, -1);
+
+ bbox = fz_unit_rect;
+ fz_transform_rect(&bbox, &image_ctm);
+
+ if (image->mask)
+ {
+ /* apply blend group even though we skip the soft mask */
+ if (gstate->blendmode)
+ fz_begin_group(csi->dev, &bbox, 0, 0, gstate->blendmode, 1);
+ fz_clip_image_mask(csi->dev, image->mask, &bbox, &image_ctm);
+ }
+ else
+ pdf_begin_group(csi, &bbox, &softmask);
+
+ if (!image->colorspace)
+ {
+
+ switch (gstate->fill.kind)
+ {
+ case PDF_MAT_NONE:
+ break;
+ case PDF_MAT_COLOR:
+ fz_fill_image_mask(csi->dev, image, &image_ctm,
+ gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha);
+ break;
+ case PDF_MAT_PATTERN:
+ if (gstate->fill.pattern)
+ {
+ fz_clip_image_mask(csi->dev, image, &bbox, &image_ctm);
+ pdf_show_pattern(csi, gstate->fill.pattern, &csi->gstate[gstate->fill.gstate_num], &bbox, PDF_FILL);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ case PDF_MAT_SHADE:
+ if (gstate->fill.shade)
+ {
+ fz_clip_image_mask(csi->dev, image, &bbox, &image_ctm);
+ fz_fill_shade(csi->dev, gstate->fill.shade, &csi->gstate[gstate->fill.gstate_num].ctm, gstate->fill.alpha);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ }
+ }
+ else
+ {
+ fz_fill_image(csi->dev, image, &image_ctm, gstate->fill.alpha);
+ }
+
+ if (image->mask)
+ {
+ fz_pop_clip(csi->dev);
+ if (gstate->blendmode)
+ fz_end_group(csi->dev);
+ }
+ else
+ pdf_end_group(csi, &softmask);
+}
+
+static void
+pdf_show_path(pdf_csi *csi, int doclose, int dofill, int dostroke, int even_odd)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_path *path;
+ fz_rect bbox;
+ softmask_save softmask = { NULL };
+
+ if (dostroke) {
+ if (csi->dev->flags & (FZ_DEVFLAG_STROKECOLOR_UNDEFINED | FZ_DEVFLAG_LINEJOIN_UNDEFINED | FZ_DEVFLAG_LINEWIDTH_UNDEFINED))
+ csi->dev->flags |= FZ_DEVFLAG_UNCACHEABLE;
+ else if (gstate->stroke_state->dash_len != 0 && csi->dev->flags & (FZ_DEVFLAG_STARTCAP_UNDEFINED | FZ_DEVFLAG_DASHCAP_UNDEFINED | FZ_DEVFLAG_ENDCAP_UNDEFINED))
+ csi->dev->flags |= FZ_DEVFLAG_UNCACHEABLE;
+ else if (gstate->stroke_state->linejoin == FZ_LINEJOIN_MITER && (csi->dev->flags & FZ_DEVFLAG_MITERLIMIT_UNDEFINED))
+ csi->dev->flags |= FZ_DEVFLAG_UNCACHEABLE;
+ }
+ if (dofill) {
+ if (csi->dev->flags & FZ_DEVFLAG_FILLCOLOR_UNDEFINED)
+ csi->dev->flags |= FZ_DEVFLAG_UNCACHEABLE;
+ }
+
+ path = csi->path;
+ csi->path = fz_new_path(ctx);
+
+ fz_try(ctx)
+ {
+ if (doclose)
+ fz_closepath(ctx, path);
+
+ fz_bound_path(ctx, path, (dostroke ? gstate->stroke_state : NULL), &gstate->ctm, &bbox);
+
+ if (csi->clip)
+ {
+ gstate->clip_depth++;
+ fz_clip_path(csi->dev, path, NULL, csi->clip_even_odd, &gstate->ctm);
+ csi->clip = 0;
+ }
+
+ if (csi->in_hidden_ocg > 0)
+ dostroke = dofill = 0;
+
+ if (dofill || dostroke)
+ pdf_begin_group(csi, &bbox, &softmask);
+
+ if (dofill)
+ {
+ switch (gstate->fill.kind)
+ {
+ case PDF_MAT_NONE:
+ break;
+ case PDF_MAT_COLOR:
+ fz_fill_path(csi->dev, path, even_odd, &gstate->ctm,
+ gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha);
+ break;
+ case PDF_MAT_PATTERN:
+ if (gstate->fill.pattern)
+ {
+ fz_clip_path(csi->dev, path, NULL, even_odd, &gstate->ctm);
+ pdf_show_pattern(csi, gstate->fill.pattern, &csi->gstate[gstate->fill.gstate_num], &bbox, PDF_FILL);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ case PDF_MAT_SHADE:
+ if (gstate->fill.shade)
+ {
+ fz_clip_path(csi->dev, path, NULL, even_odd, &gstate->ctm);
+ /* The cluster and page 2 of patterns.pdf shows that fz_fill_shade should NOT be called with gstate->ctm. */
+ fz_fill_shade(csi->dev, gstate->fill.shade, &csi->gstate[gstate->fill.gstate_num].ctm, gstate->fill.alpha);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ }
+ }
+
+ if (dostroke)
+ {
+ switch (gstate->stroke.kind)
+ {
+ case PDF_MAT_NONE:
+ break;
+ case PDF_MAT_COLOR:
+ fz_stroke_path(csi->dev, path, gstate->stroke_state, &gstate->ctm,
+ gstate->stroke.colorspace, gstate->stroke.v, gstate->stroke.alpha);
+ break;
+ case PDF_MAT_PATTERN:
+ if (gstate->stroke.pattern)
+ {
+ fz_clip_stroke_path(csi->dev, path, &bbox, gstate->stroke_state, &gstate->ctm);
+ pdf_show_pattern(csi, gstate->stroke.pattern, &csi->gstate[gstate->stroke.gstate_num], &bbox, PDF_STROKE);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ case PDF_MAT_SHADE:
+ if (gstate->stroke.shade)
+ {
+ fz_clip_stroke_path(csi->dev, path, &bbox, gstate->stroke_state, &gstate->ctm);
+ fz_fill_shade(csi->dev, gstate->stroke.shade, &csi->gstate[gstate->stroke.gstate_num].ctm, gstate->stroke.alpha);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ }
+ }
+
+ if (dofill || dostroke)
+ pdf_end_group(csi, &softmask);
+ }
+ fz_always(ctx)
+ {
+ fz_free_path(ctx, path);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+/*
+ * Assemble and emit text
+ */
+
+static void
+pdf_flush_text(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_text *text;
+ int dofill;
+ int dostroke;
+ int doclip;
+ int doinvisible;
+ fz_context *ctx = csi->dev->ctx;
+ softmask_save softmask = { NULL };
+
+ if (!csi->text)
+ return;
+ text = csi->text;
+ csi->text = NULL;
+
+ dofill = dostroke = doclip = doinvisible = 0;
+ switch (csi->text_mode)
+ {
+ case 0: dofill = 1; break;
+ case 1: dostroke = 1; break;
+ case 2: dofill = dostroke = 1; break;
+ case 3: doinvisible = 1; break;
+ case 4: dofill = doclip = 1; break;
+ case 5: dostroke = doclip = 1; break;
+ case 6: dofill = dostroke = doclip = 1; break;
+ case 7: doclip = 1; break;
+ }
+
+ if (csi->in_hidden_ocg > 0)
+ dostroke = dofill = 0;
+
+ fz_try(ctx)
+ {
+ fz_rect tb = csi->text_bbox;
+
+ fz_transform_rect(&tb, &gstate->ctm);
+
+ /* Don't bother sending a text group with nothing in it */
+ if (text->len == 0)
+ break;
+
+ pdf_begin_group(csi, &tb, &softmask);
+
+ if (doinvisible)
+ fz_ignore_text(csi->dev, text, &gstate->ctm);
+
+ if (dofill)
+ {
+ switch (gstate->fill.kind)
+ {
+ case PDF_MAT_NONE:
+ break;
+ case PDF_MAT_COLOR:
+ fz_fill_text(csi->dev, text, &gstate->ctm,
+ gstate->fill.colorspace, gstate->fill.v, gstate->fill.alpha);
+ break;
+ case PDF_MAT_PATTERN:
+ if (gstate->fill.pattern)
+ {
+ fz_clip_text(csi->dev, text, &gstate->ctm, 0);
+ pdf_show_pattern(csi, gstate->fill.pattern, &csi->gstate[gstate->fill.gstate_num], &tb, PDF_FILL);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ case PDF_MAT_SHADE:
+ if (gstate->fill.shade)
+ {
+ fz_clip_text(csi->dev, text, &gstate->ctm, 0);
+ /* Page 2 of patterns.pdf shows that fz_fill_shade should NOT be called with gstate->ctm */
+ fz_fill_shade(csi->dev, gstate->fill.shade, &csi->gstate[gstate->fill.gstate_num].ctm, gstate->fill.alpha);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ }
+ }
+
+ if (dostroke)
+ {
+ switch (gstate->stroke.kind)
+ {
+ case PDF_MAT_NONE:
+ break;
+ case PDF_MAT_COLOR:
+ fz_stroke_text(csi->dev, text, gstate->stroke_state, &gstate->ctm,
+ gstate->stroke.colorspace, gstate->stroke.v, gstate->stroke.alpha);
+ break;
+ case PDF_MAT_PATTERN:
+ if (gstate->stroke.pattern)
+ {
+ fz_clip_stroke_text(csi->dev, text, gstate->stroke_state, &gstate->ctm);
+ pdf_show_pattern(csi, gstate->stroke.pattern, &csi->gstate[gstate->stroke.gstate_num], &tb, PDF_STROKE);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ case PDF_MAT_SHADE:
+ if (gstate->stroke.shade)
+ {
+ fz_clip_stroke_text(csi->dev, text, gstate->stroke_state, &gstate->ctm);
+ fz_fill_shade(csi->dev, gstate->stroke.shade, &csi->gstate[gstate->stroke.gstate_num].ctm, gstate->stroke.alpha);
+ fz_pop_clip(csi->dev);
+ }
+ break;
+ }
+ }
+
+ if (doclip)
+ {
+ if (csi->accumulate < 2)
+ gstate->clip_depth++;
+ fz_clip_text(csi->dev, text, &gstate->ctm, csi->accumulate);
+ csi->accumulate = 2;
+ }
+
+ pdf_end_group(csi, &softmask);
+ }
+ fz_always(ctx)
+ {
+ fz_free_text(ctx, text);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+pdf_show_char(pdf_csi *csi, int cid)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_font_desc *fontdesc = gstate->font;
+ fz_matrix tsm, trm;
+ float w0, w1, tx, ty;
+ pdf_hmtx h;
+ pdf_vmtx v;
+ int gid;
+ int ucsbuf[8];
+ int ucslen;
+ int i;
+ fz_rect bbox;
+ int render_direct;
+
+ tsm.a = gstate->size * gstate->scale;
+ tsm.b = 0;
+ tsm.c = 0;
+ tsm.d = gstate->size;
+ tsm.e = 0;
+ tsm.f = gstate->rise;
+
+ ucslen = 0;
+ if (fontdesc->to_unicode)
+ ucslen = pdf_lookup_cmap_full(fontdesc->to_unicode, cid, ucsbuf);
+ if (ucslen == 0 && cid < fontdesc->cid_to_ucs_len)
+ {
+ ucsbuf[0] = fontdesc->cid_to_ucs[cid];
+ ucslen = 1;
+ }
+ if (ucslen == 0 || (ucslen == 1 && ucsbuf[0] == 0))
+ {
+ ucsbuf[0] = '?';
+ ucslen = 1;
+ }
+
+ gid = pdf_font_cid_to_gid(ctx, fontdesc, cid);
+
+ if (fontdesc->wmode == 1)
+ {
+ v = pdf_lookup_vmtx(ctx, fontdesc, cid);
+ tsm.e -= v.x * fabsf(gstate->size) * 0.001f;
+ tsm.f -= v.y * gstate->size * 0.001f;
+ }
+
+ fz_concat(&trm, &tsm, &csi->tm);
+
+ fz_bound_glyph(ctx, fontdesc->font, gid, &trm, &bbox);
+ /* Compensate for the glyph cache limited positioning precision */
+ bbox.x0 -= 1;
+ bbox.y0 -= 1;
+ bbox.x1 += 1;
+ bbox.y1 += 1;
+
+ /* If we are a type3 font within a type 3 font, or are otherwise
+ * uncachable, then render direct. */
+ render_direct = (!fontdesc->font->ft_face && csi->nested_depth > 0) || !fz_glyph_cacheable(ctx, fontdesc->font, gid);
+
+ /* flush buffered text if face or matrix or rendermode has changed */
+ if (!csi->text ||
+ fontdesc->font != csi->text->font ||
+ fontdesc->wmode != csi->text->wmode ||
+ fabsf(trm.a - csi->text->trm.a) > FLT_EPSILON ||
+ fabsf(trm.b - csi->text->trm.b) > FLT_EPSILON ||
+ fabsf(trm.c - csi->text->trm.c) > FLT_EPSILON ||
+ fabsf(trm.d - csi->text->trm.d) > FLT_EPSILON ||
+ gstate->render != csi->text_mode ||
+ render_direct)
+ {
+ pdf_flush_text(csi);
+
+ csi->text = fz_new_text(ctx, fontdesc->font, &trm, fontdesc->wmode);
+ csi->text->trm.e = 0;
+ csi->text->trm.f = 0;
+ csi->text_mode = gstate->render;
+ csi->text_bbox = fz_empty_rect;
+ }
+
+ if (render_direct)
+ {
+ /* Render the glyph stream direct here (only happens for
+ * type3 glyphs that seem to inherit current graphics
+ * attributes, or type 3 glyphs within type3 glyphs). */
+ fz_matrix composed;
+ fz_concat(&composed, &trm, &gstate->ctm);
+ fz_render_t3_glyph_direct(ctx, csi->dev, fontdesc->font, gid, &composed, gstate, csi->nested_depth);
+ }
+ else
+ {
+ fz_union_rect(&csi->text_bbox, &bbox);
+
+ /* add glyph to textobject */
+ fz_add_text(ctx, csi->text, gid, ucsbuf[0], trm.e, trm.f);
+
+ /* add filler glyphs for one-to-many unicode mapping */
+ for (i = 1; i < ucslen; i++)
+ fz_add_text(ctx, csi->text, -1, ucsbuf[i], trm.e, trm.f);
+ }
+
+ if (fontdesc->wmode == 0)
+ {
+ h = pdf_lookup_hmtx(ctx, fontdesc, cid);
+ w0 = h.w * 0.001f;
+ tx = (w0 * gstate->size + gstate->char_space) * gstate->scale;
+ fz_pre_translate(&csi->tm, tx, 0);
+ }
+
+ if (fontdesc->wmode == 1)
+ {
+ w1 = v.w * 0.001f;
+ ty = w1 * gstate->size + gstate->char_space;
+ fz_pre_translate(&csi->tm, 0, ty);
+ }
+}
+
+static void
+pdf_show_space(pdf_csi *csi, float tadj)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_font_desc *fontdesc = gstate->font;
+
+ if (!fontdesc)
+ {
+ fz_warn(ctx, "cannot draw text since font and size not set");
+ return;
+ }
+
+ if (fontdesc->wmode == 0)
+ fz_pre_translate(&csi->tm, tadj * gstate->scale, 0);
+ else
+ fz_pre_translate(&csi->tm, 0, tadj);
+}
+
+static void
+pdf_show_string(pdf_csi *csi, unsigned char *buf, int len)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_font_desc *fontdesc = gstate->font;
+ unsigned char *end = buf + len;
+ int cpt, cid;
+
+ if (!fontdesc)
+ {
+ fz_warn(ctx, "cannot draw text since font and size not set");
+ return;
+ }
+
+ while (buf < end)
+ {
+ int w = pdf_decode_cmap(fontdesc->encoding, buf, &cpt);
+ buf += w;
+
+ cid = pdf_lookup_cmap(fontdesc->encoding, cpt);
+ if (cid >= 0)
+ pdf_show_char(csi, cid);
+ else
+ fz_warn(ctx, "cannot encode character with code point %#x", cpt);
+ if (cpt == 32 && w == 1)
+ pdf_show_space(csi, gstate->word_space);
+ }
+}
+
+static void
+pdf_show_text(pdf_csi *csi, pdf_obj *text)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ int i;
+
+ if (pdf_is_array(text))
+ {
+ int n = pdf_array_len(text);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *item = pdf_array_get(text, i);
+ if (pdf_is_string(item))
+ pdf_show_string(csi, (unsigned char *)pdf_to_str_buf(item), pdf_to_str_len(item));
+ else
+ pdf_show_space(csi, - pdf_to_real(item) * gstate->size * 0.001f);
+ }
+ }
+ else if (pdf_is_string(text))
+ {
+ pdf_show_string(csi, (unsigned char *)pdf_to_str_buf(text), pdf_to_str_len(text));
+ }
+}
+
+/*
+ * Interpreter and graphics state stack.
+ */
+
+static void
+pdf_init_gstate(fz_context *ctx, pdf_gstate *gs, const fz_matrix *ctm)
+{
+ gs->ctm = *ctm;
+ gs->clip_depth = 0;
+
+ gs->stroke_state = fz_new_stroke_state(ctx);
+
+ gs->stroke.kind = PDF_MAT_COLOR;
+ gs->stroke.colorspace = fz_device_gray(ctx); /* No fz_keep_colorspace as static */
+ gs->stroke.v[0] = 0;
+ gs->stroke.pattern = NULL;
+ gs->stroke.shade = NULL;
+ gs->stroke.alpha = 1;
+ gs->stroke.gstate_num = -1;
+
+ gs->fill.kind = PDF_MAT_COLOR;
+ gs->fill.colorspace = fz_device_gray(ctx); /* No fz_keep_colorspace as static */
+ gs->fill.v[0] = 0;
+ gs->fill.pattern = NULL;
+ gs->fill.shade = NULL;
+ gs->fill.alpha = 1;
+ gs->fill.gstate_num = -1;
+
+ gs->char_space = 0;
+ gs->word_space = 0;
+ gs->scale = 1;
+ gs->leading = 0;
+ gs->font = NULL;
+ gs->size = -1;
+ gs->render = 0;
+ gs->rise = 0;
+
+ gs->blendmode = 0;
+ gs->softmask = NULL;
+ gs->softmask_ctm = fz_identity;
+ gs->luminosity = 0;
+}
+
+static pdf_material *
+pdf_keep_material(fz_context *ctx, pdf_material *mat)
+{
+ if (mat->colorspace)
+ fz_keep_colorspace(ctx, mat->colorspace);
+ if (mat->pattern)
+ pdf_keep_pattern(ctx, mat->pattern);
+ if (mat->shade)
+ fz_keep_shade(ctx, mat->shade);
+ return mat;
+}
+
+static pdf_material *
+pdf_drop_material(fz_context *ctx, pdf_material *mat)
+{
+ if (mat->colorspace)
+ fz_drop_colorspace(ctx, mat->colorspace);
+ if (mat->pattern)
+ pdf_drop_pattern(ctx, mat->pattern);
+ if (mat->shade)
+ fz_drop_shade(ctx, mat->shade);
+ return mat;
+}
+
+static void
+pdf_keep_gstate(fz_context *ctx, pdf_gstate *gs)
+{
+ pdf_keep_material(ctx, &gs->stroke);
+ pdf_keep_material(ctx, &gs->fill);
+ if (gs->font)
+ pdf_keep_font(ctx, gs->font);
+ if (gs->softmask)
+ pdf_keep_xobject(ctx, gs->softmask);
+ fz_keep_stroke_state(ctx, gs->stroke_state);
+}
+
+static void
+pdf_drop_gstate(fz_context *ctx, pdf_gstate *gs)
+{
+ pdf_drop_material(ctx, &gs->stroke);
+ pdf_drop_material(ctx, &gs->fill);
+ if (gs->font)
+ pdf_drop_font(ctx, gs->font);
+ if (gs->softmask)
+ pdf_drop_xobject(ctx, gs->softmask);
+ fz_drop_stroke_state(ctx, gs->stroke_state);
+}
+
+static void
+pdf_copy_gstate(fz_context *ctx, pdf_gstate *gs, pdf_gstate *old)
+{
+ pdf_drop_gstate(ctx, gs);
+ *gs = *old;
+ pdf_keep_gstate(ctx, gs);
+}
+
+static void
+pdf_copy_pattern_gstate(fz_context *ctx, pdf_gstate *gs, const pdf_gstate *old)
+{
+ gs->ctm = old->ctm;
+ gs->font = old->font;
+ gs->softmask = old->softmask;
+
+ fz_drop_stroke_state(ctx, gs->stroke_state);
+ gs->stroke_state = fz_keep_stroke_state(ctx, old->stroke_state);
+
+ if (gs->font)
+ pdf_keep_font(ctx, gs->font);
+ if (gs->softmask)
+ pdf_keep_xobject(ctx, gs->softmask);
+}
+
+static pdf_csi *
+pdf_new_csi(pdf_document *xref, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie, pdf_gstate *gstate, int nested)
+{
+ pdf_csi *csi;
+ fz_context *ctx = dev->ctx;
+
+ csi = fz_malloc_struct(ctx, pdf_csi);
+ fz_try(ctx)
+ {
+ csi->xref = xref;
+ csi->dev = dev;
+ csi->event = event;
+
+ csi->top = 0;
+ csi->obj = NULL;
+ csi->name[0] = 0;
+ csi->string_len = 0;
+ memset(csi->stack, 0, sizeof csi->stack);
+
+ csi->xbalance = 0;
+ csi->in_text = 0;
+ csi->in_hidden_ocg = 0;
+
+ csi->path = fz_new_path(ctx);
+ csi->clip = 0;
+ csi->clip_even_odd = 0;
+
+ csi->text = NULL;
+ csi->tlm = fz_identity;
+ csi->tm = fz_identity;
+ csi->text_mode = 0;
+ csi->accumulate = 1;
+
+ csi->gcap = 64;
+ csi->gstate = fz_malloc_array(ctx, csi->gcap, sizeof(pdf_gstate));
+
+ csi->nested_depth = nested;
+ pdf_init_gstate(ctx, &csi->gstate[0], ctm);
+ if (gstate)
+ {
+ pdf_copy_gstate(ctx, &csi->gstate[0], gstate);
+ csi->gstate[0].ctm = *ctm;
+ }
+ csi->gtop = 0;
+ csi->gbot = 0;
+ csi->gparent = 0;
+
+ csi->cookie = cookie;
+ }
+ fz_catch(ctx)
+ {
+ fz_free_path(ctx, csi->path);
+ fz_free(ctx, csi);
+ fz_rethrow(ctx);
+ }
+
+ return csi;
+}
+
+static void
+pdf_clear_stack(pdf_csi *csi)
+{
+ int i;
+
+ pdf_drop_obj(csi->obj);
+ csi->obj = NULL;
+
+ csi->name[0] = 0;
+ csi->string_len = 0;
+ for (i = 0; i < csi->top; i++)
+ csi->stack[i] = 0;
+
+ csi->top = 0;
+}
+
+static void
+pdf_gsave(pdf_csi *csi)
+{
+ fz_context *ctx = csi->dev->ctx;
+
+ if (csi->gtop == csi->gcap-1)
+ {
+ csi->gstate = fz_resize_array(ctx, csi->gstate, csi->gcap*2, sizeof(pdf_gstate));
+ csi->gcap *= 2;
+ }
+
+ memcpy(&csi->gstate[csi->gtop + 1], &csi->gstate[csi->gtop], sizeof(pdf_gstate));
+
+ csi->gtop++;
+ pdf_keep_gstate(ctx, &csi->gstate[csi->gtop]);
+}
+
+static void
+pdf_grestore(pdf_csi *csi)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gs = csi->gstate + csi->gtop;
+ int clip_depth = gs->clip_depth;
+
+ if (csi->gtop <= csi->gbot)
+ {
+ fz_warn(ctx, "gstate underflow in content stream");
+ return;
+ }
+
+ pdf_drop_gstate(ctx, gs);
+ csi->gtop --;
+
+ gs = csi->gstate + csi->gtop;
+ while (clip_depth > gs->clip_depth)
+ {
+ fz_try(ctx)
+ {
+ fz_pop_clip(csi->dev);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* Silently swallow the problem */
+ }
+ clip_depth--;
+ }
+}
+
+static void
+pdf_free_csi(pdf_csi *csi)
+{
+ fz_context *ctx = csi->dev->ctx;
+
+ while (csi->gtop)
+ pdf_grestore(csi);
+
+ pdf_drop_material(ctx, &csi->gstate[0].fill);
+ pdf_drop_material(ctx, &csi->gstate[0].stroke);
+ if (csi->gstate[0].font)
+ pdf_drop_font(ctx, csi->gstate[0].font);
+ if (csi->gstate[0].softmask)
+ pdf_drop_xobject(ctx, csi->gstate[0].softmask);
+ fz_drop_stroke_state(ctx, csi->gstate[0].stroke_state);
+
+ while (csi->gstate[0].clip_depth--)
+ fz_pop_clip(csi->dev);
+
+ if (csi->path) fz_free_path(ctx, csi->path);
+ if (csi->text) fz_free_text(ctx, csi->text);
+
+ pdf_clear_stack(csi);
+
+ fz_free(ctx, csi->gstate);
+
+ fz_free(ctx, csi);
+}
+
+/*
+ * Material state
+ */
+
+static void
+pdf_set_colorspace(pdf_csi *csi, int what, fz_colorspace *colorspace)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gs = csi->gstate + csi->gtop;
+ pdf_material *mat;
+
+ pdf_flush_text(csi);
+
+ mat = what == PDF_FILL ? &gs->fill : &gs->stroke;
+
+ fz_drop_colorspace(ctx, mat->colorspace);
+
+ mat->kind = PDF_MAT_COLOR;
+ mat->colorspace = fz_keep_colorspace(ctx, colorspace);
+
+ mat->v[0] = 0;
+ mat->v[1] = 0;
+ mat->v[2] = 0;
+ mat->v[3] = 1;
+}
+
+static void
+pdf_set_color(pdf_csi *csi, int what, float *v)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gs = csi->gstate + csi->gtop;
+ pdf_material *mat;
+ int i;
+
+ pdf_flush_text(csi);
+
+ mat = what == PDF_FILL ? &gs->fill : &gs->stroke;
+
+ switch (mat->kind)
+ {
+ case PDF_MAT_PATTERN:
+ case PDF_MAT_COLOR:
+ if (!strcmp(mat->colorspace->name, "Lab"))
+ {
+ mat->v[0] = v[0] / 100;
+ mat->v[1] = (v[1] + 100) / 200;
+ mat->v[2] = (v[2] + 100) / 200;
+ }
+ for (i = 0; i < mat->colorspace->n; i++)
+ mat->v[i] = v[i];
+ break;
+ default:
+ fz_warn(ctx, "color incompatible with material");
+ }
+}
+
+static void
+pdf_set_shade(pdf_csi *csi, int what, fz_shade *shade)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gs = csi->gstate + csi->gtop;
+ pdf_material *mat;
+
+ pdf_flush_text(csi);
+
+ mat = what == PDF_FILL ? &gs->fill : &gs->stroke;
+
+ if (mat->shade)
+ fz_drop_shade(ctx, mat->shade);
+
+ mat->kind = PDF_MAT_SHADE;
+ mat->shade = fz_keep_shade(ctx, shade);
+}
+
+static void
+pdf_set_pattern(pdf_csi *csi, int what, pdf_pattern *pat, float *v)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gs = csi->gstate + csi->gtop;
+ pdf_material *mat;
+
+ pdf_flush_text(csi);
+
+ mat = what == PDF_FILL ? &gs->fill : &gs->stroke;
+
+ if (mat->pattern)
+ pdf_drop_pattern(ctx, mat->pattern);
+
+ mat->kind = PDF_MAT_PATTERN;
+ if (pat)
+ mat->pattern = pdf_keep_pattern(ctx, pat);
+ else
+ mat->pattern = NULL;
+ mat->gstate_num = csi->gparent;
+
+ if (v)
+ pdf_set_color(csi, what, v);
+}
+
+static void
+pdf_unset_pattern(pdf_csi *csi, int what)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gs = csi->gstate + csi->gtop;
+ pdf_material *mat;
+ mat = what == PDF_FILL ? &gs->fill : &gs->stroke;
+ if (mat->kind == PDF_MAT_PATTERN)
+ {
+ if (mat->pattern)
+ pdf_drop_pattern(ctx, mat->pattern);
+ mat->pattern = NULL;
+ mat->kind = PDF_MAT_COLOR;
+ }
+}
+
+/*
+ * Patterns, XObjects and ExtGState
+ */
+
+static void
+pdf_show_pattern(pdf_csi *csi, pdf_pattern *pat, pdf_gstate *pat_gstate, const fz_rect *area, int what)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate;
+ int gparent_save;
+ fz_matrix ptm, invptm, gparent_save_ctm;
+ int x0, y0, x1, y1;
+ float fx0, fy0, fx1, fy1;
+ int oldtop;
+ fz_rect local_area;
+
+ pdf_gsave(csi);
+ gstate = csi->gstate + csi->gtop;
+ /* Patterns are run with the gstate of the parent */
+ pdf_copy_pattern_gstate(ctx, gstate, pat_gstate);
+
+ if (pat->ismask)
+ {
+ pdf_unset_pattern(csi, PDF_FILL);
+ pdf_unset_pattern(csi, PDF_STROKE);
+ if (what == PDF_FILL)
+ {
+ pdf_drop_material(ctx, &gstate->stroke);
+ pdf_keep_material(ctx, &gstate->fill);
+ gstate->stroke = gstate->fill;
+ }
+ if (what == PDF_STROKE)
+ {
+ pdf_drop_material(ctx, &gstate->fill);
+ pdf_keep_material(ctx, &gstate->stroke);
+ gstate->fill = gstate->stroke;
+ }
+ }
+ else
+ {
+ // TODO: unset only the current fill/stroke or both?
+ pdf_unset_pattern(csi, what);
+ }
+
+ /* don't apply soft masks to objects in the pattern as well */
+ if (gstate->softmask)
+ {
+ pdf_drop_xobject(ctx, gstate->softmask);
+ gstate->softmask = NULL;
+ }
+
+ fz_concat(&ptm, &pat->matrix, &pat_gstate->ctm);
+ fz_invert_matrix(&invptm, &ptm);
+
+ /* The parent_ctm is amended with our pattern matrix */
+ gparent_save = csi->gparent;
+ csi->gparent = csi->gtop-1;
+ gparent_save_ctm = csi->gstate[csi->gparent].ctm;
+ csi->gstate[csi->gparent].ctm = ptm;
+
+ fz_try(ctx)
+ {
+ /* patterns are painted using the parent_ctm. area = bbox of
+ * shape to be filled in device space. Map it back to pattern
+ * space. */
+ local_area = *area;
+ fz_transform_rect(&local_area, &invptm);
+
+ fx0 = (local_area.x0 - pat->bbox.x0) / pat->xstep;
+ fy0 = (local_area.y0 - pat->bbox.y0) / pat->ystep;
+ fx1 = (local_area.x1 - pat->bbox.x0) / pat->xstep;
+ fy1 = (local_area.y1 - pat->bbox.y0) / pat->ystep;
+ if (fx0 > fx1)
+ {
+ float t = fx0; fx0 = fx1; fx1 = t;
+ }
+ if (fy0 > fy1)
+ {
+ float t = fy0; fy0 = fy1; fy1 = t;
+ }
+
+ oldtop = csi->gtop;
+
+#ifdef TILE
+ /* We have tried various formulations in the past, but this one is
+ * best we've found; only use it as a tile if a whole repeat is
+ * required in at least one direction. Note, that this allows for
+ * 'sections' of 4 tiles to be show, but all non-overlapping. */
+ if (fx1-fx0 > 1 || fy1-fy0 > 1)
+#else
+ if (0)
+#endif
+ {
+ fz_begin_tile(csi->dev, &local_area, &pat->bbox, pat->xstep, pat->ystep, &ptm);
+ gstate->ctm = ptm;
+ pdf_gsave(csi);
+ pdf_run_contents_object(csi, pat->resources, pat->contents);
+ pdf_grestore(csi);
+ while (oldtop < csi->gtop)
+ pdf_grestore(csi);
+ fz_end_tile(csi->dev);
+ }
+ else
+ {
+ int x, y;
+
+ /* When calculating the number of tiles required, we adjust by
+ * a small amount to allow for rounding errors. By choosing
+ * this amount to be smaller than 1/256, we guarantee we won't
+ * cause problems that will be visible even under our most
+ * extreme antialiasing. */
+ x0 = floorf(fx0 + 0.001);
+ y0 = floorf(fy0 + 0.001);
+ x1 = ceilf(fx1 - 0.001);
+ y1 = ceilf(fy1 - 0.001);
+
+ for (y = y0; y < y1; y++)
+ {
+ for (x = x0; x < x1; x++)
+ {
+ gstate->ctm = ptm;
+ fz_pre_translate(&gstate->ctm, x * pat->xstep, y * pat->ystep);
+ pdf_gsave(csi);
+ fz_try(ctx)
+ {
+ pdf_run_contents_object(csi, pat->resources, pat->contents);
+ }
+ fz_always(ctx)
+ {
+ pdf_grestore(csi);
+ while (oldtop < csi->gtop)
+ pdf_grestore(csi);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot render pattern tile");
+ }
+ }
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ csi->gstate[csi->gparent].ctm = gparent_save_ctm;
+ csi->gparent = gparent_save;
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ pdf_grestore(csi);
+}
+
+static void
+pdf_run_xobject(pdf_csi *csi, pdf_obj *resources, pdf_xobject *xobj, const fz_matrix *transform)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = NULL;
+ int oldtop = 0;
+ fz_matrix local_transform = *transform;
+ softmask_save softmask = { NULL };
+ int gparent_save;
+ fz_matrix gparent_save_ctm;
+
+ /* Avoid infinite recursion */
+ if (xobj == NULL || pdf_obj_mark(xobj->me))
+ return;
+
+ fz_var(gstate);
+ fz_var(oldtop);
+
+ gparent_save = csi->gparent;
+ csi->gparent = csi->gtop;
+
+ fz_try(ctx)
+ {
+ pdf_gsave(csi);
+
+ gstate = csi->gstate + csi->gtop;
+ oldtop = csi->gtop;
+
+ /* apply xobject's transform matrix */
+ fz_concat(&local_transform, &xobj->matrix, &local_transform);
+ fz_concat(&gstate->ctm, &local_transform, &gstate->ctm);
+
+ /* The gparent is updated with the modified ctm */
+ gparent_save_ctm = csi->gstate[csi->gparent].ctm;
+ csi->gstate[csi->gparent].ctm = gstate->ctm;
+
+ /* apply soft mask, create transparency group and reset state */
+ if (xobj->transparency)
+ {
+ fz_rect bbox = xobj->bbox;
+ fz_transform_rect(&bbox, &gstate->ctm);
+ gstate = begin_softmask(csi, &softmask);
+
+ fz_begin_group(csi->dev, &bbox,
+ xobj->isolated, xobj->knockout, gstate->blendmode, gstate->fill.alpha);
+
+ gstate->blendmode = 0;
+ gstate->stroke.alpha = 1;
+ gstate->fill.alpha = 1;
+ }
+
+ /* clip to the bounds */
+
+ fz_moveto(ctx, csi->path, xobj->bbox.x0, xobj->bbox.y0);
+ fz_lineto(ctx, csi->path, xobj->bbox.x1, xobj->bbox.y0);
+ fz_lineto(ctx, csi->path, xobj->bbox.x1, xobj->bbox.y1);
+ fz_lineto(ctx, csi->path, xobj->bbox.x0, xobj->bbox.y1);
+ fz_closepath(ctx, csi->path);
+ csi->clip = 1;
+ pdf_show_path(csi, 0, 0, 0, 0);
+
+ /* run contents */
+
+ if (xobj->resources)
+ resources = xobj->resources;
+
+ pdf_run_contents_object(csi, resources, xobj->contents);
+ }
+ fz_always(ctx)
+ {
+ csi->gstate[csi->gparent].ctm = gparent_save_ctm;
+ csi->gparent = gparent_save;
+
+ if (gstate)
+ {
+ while (oldtop < csi->gtop)
+ pdf_grestore(csi);
+
+ pdf_grestore(csi);
+ }
+
+ pdf_obj_unmark(xobj->me);
+
+ /* wrap up transparency stacks */
+ if (xobj->transparency)
+ {
+ fz_end_group(csi->dev);
+ end_softmask(csi, &softmask);
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+}
+
+static void
+pdf_run_extgstate(pdf_csi *csi, pdf_obj *rdb, pdf_obj *extgstate)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_colorspace *colorspace;
+ int i, k, n;
+
+ pdf_flush_text(csi);
+
+ n = pdf_dict_len(extgstate);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *key = pdf_dict_get_key(extgstate, i);
+ pdf_obj *val = pdf_dict_get_val(extgstate, i);
+ char *s = pdf_to_name(key);
+
+ if (!strcmp(s, "Font"))
+ {
+ if (pdf_is_array(val) && pdf_array_len(val) == 2)
+ {
+ pdf_obj *font = pdf_array_get(val, 0);
+
+ if (gstate->font)
+ {
+ pdf_drop_font(ctx, gstate->font);
+ gstate->font = NULL;
+ }
+
+ gstate->font = pdf_load_font(csi->xref, rdb, font, csi->nested_depth);
+ if (!gstate->font)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find font in store");
+ gstate->size = pdf_to_real(pdf_array_get(val, 1));
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "malformed /Font dictionary");
+ }
+
+ else if (!strcmp(s, "LC"))
+ {
+ csi->dev->flags &= ~(FZ_DEVFLAG_STARTCAP_UNDEFINED | FZ_DEVFLAG_DASHCAP_UNDEFINED | FZ_DEVFLAG_ENDCAP_UNDEFINED);
+ gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state);
+ gstate->stroke_state->start_cap = pdf_to_int(val);
+ gstate->stroke_state->dash_cap = pdf_to_int(val);
+ gstate->stroke_state->end_cap = pdf_to_int(val);
+ }
+ else if (!strcmp(s, "LW"))
+ {
+ csi->dev->flags &= ~FZ_DEVFLAG_LINEWIDTH_UNDEFINED;
+ gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state);
+ gstate->stroke_state->linewidth = pdf_to_real(val);
+ }
+ else if (!strcmp(s, "LJ"))
+ {
+ csi->dev->flags &= ~FZ_DEVFLAG_LINEJOIN_UNDEFINED;
+ gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state);
+ gstate->stroke_state->linejoin = pdf_to_int(val);
+ }
+ else if (!strcmp(s, "ML"))
+ {
+ csi->dev->flags &= ~FZ_DEVFLAG_MITERLIMIT_UNDEFINED;
+ gstate->stroke_state = fz_unshare_stroke_state(ctx, gstate->stroke_state);
+ gstate->stroke_state->miterlimit = pdf_to_real(val);
+ }
+
+ else if (!strcmp(s, "D"))
+ {
+ if (pdf_is_array(val) && pdf_array_len(val) == 2)
+ {
+ pdf_obj *dashes = pdf_array_get(val, 0);
+ int len = pdf_array_len(dashes);
+ gstate->stroke_state = fz_unshare_stroke_state_with_len(ctx, gstate->stroke_state, len);
+ gstate->stroke_state->dash_len = len;
+ for (k = 0; k < len; k++)
+ gstate->stroke_state->dash_list[k] = pdf_to_real(pdf_array_get(dashes, k));
+ gstate->stroke_state->dash_phase = pdf_to_real(pdf_array_get(val, 1));
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "malformed /D");
+ }
+
+ else if (!strcmp(s, "CA"))
+ gstate->stroke.alpha = fz_clamp(pdf_to_real(val), 0, 1);
+
+ else if (!strcmp(s, "ca"))
+ gstate->fill.alpha = fz_clamp(pdf_to_real(val), 0, 1);
+
+ else if (!strcmp(s, "BM"))
+ {
+ if (pdf_is_array(val))
+ val = pdf_array_get(val, 0);
+ gstate->blendmode = fz_lookup_blendmode(pdf_to_name(val));
+ }
+
+ else if (!strcmp(s, "SMask"))
+ {
+ if (pdf_is_dict(val))
+ {
+ pdf_xobject *xobj;
+ pdf_obj *group, *luminosity, *bc, *tr;
+
+ if (gstate->softmask)
+ {
+ pdf_drop_xobject(ctx, gstate->softmask);
+ gstate->softmask = NULL;
+ }
+
+ group = pdf_dict_gets(val, "G");
+ if (!group)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot load softmask xobject (%d %d R)", pdf_to_num(val), pdf_to_gen(val));
+ xobj = pdf_load_xobject(csi->xref, group);
+
+ colorspace = xobj->colorspace;
+ if (!colorspace)
+ colorspace = fz_device_gray(ctx);
+
+ /* The softmask_ctm no longer has the softmask matrix rolled into it, as this
+ * causes the softmask matrix to be applied twice. */
+ gstate->softmask_ctm = gstate->ctm;
+ gstate->softmask = xobj;
+ for (k = 0; k < colorspace->n; k++)
+ gstate->softmask_bc[k] = 0;
+
+ bc = pdf_dict_gets(val, "BC");
+ if (pdf_is_array(bc))
+ {
+ for (k = 0; k < colorspace->n; k++)
+ gstate->softmask_bc[k] = pdf_to_real(pdf_array_get(bc, k));
+ }
+
+ luminosity = pdf_dict_gets(val, "S");
+ if (pdf_is_name(luminosity) && !strcmp(pdf_to_name(luminosity), "Luminosity"))
+ gstate->luminosity = 1;
+ else
+ gstate->luminosity = 0;
+
+ tr = pdf_dict_gets(val, "TR");
+ if (tr && strcmp(pdf_to_name(tr), "Identity"))
+ fz_warn(ctx, "ignoring transfer function");
+ }
+ else if (pdf_is_name(val) && !strcmp(pdf_to_name(val), "None"))
+ {
+ if (gstate->softmask)
+ {
+ pdf_drop_xobject(ctx, gstate->softmask);
+ gstate->softmask = NULL;
+ }
+ }
+ }
+
+ else if (!strcmp(s, "TR2"))
+ {
+ if (strcmp(pdf_to_name(val), "Identity") && strcmp(pdf_to_name(val), "Default"))
+ fz_warn(ctx, "ignoring transfer function");
+ }
+
+ else if (!strcmp(s, "TR"))
+ {
+ /* TR is ignored in the presence of TR2 */
+ pdf_obj *tr2 = pdf_dict_gets(extgstate, "TR2");
+ if (tr2 && strcmp(pdf_to_name(val), "Identity"))
+ fz_warn(ctx, "ignoring transfer function");
+ }
+ }
+}
+
+/*
+ * Operators
+ */
+
+static void pdf_run_BDC(pdf_csi *csi, pdf_obj *rdb)
+{
+ pdf_obj *ocg;
+
+ /* If we are already in a hidden OCG, then we'll still be hidden -
+ * just increment the depth so we pop back to visibility when we've
+ * seen enough EDCs. */
+ if (csi->in_hidden_ocg > 0)
+ {
+ csi->in_hidden_ocg++;
+ return;
+ }
+
+ ocg = pdf_dict_gets(pdf_dict_gets(rdb, "Properties"), csi->name);
+ if (!ocg)
+ {
+ /* No Properties array, or name not found in the properties
+ * means visible. */
+ return;
+ }
+ if (strcmp(pdf_to_name(pdf_dict_gets(ocg, "Type")), "OCG") != 0)
+ {
+ /* Wrong type of property */
+ return;
+ }
+ if (pdf_is_hidden_ocg(ocg, csi, rdb))
+ csi->in_hidden_ocg++;
+}
+
+static void pdf_run_BI(pdf_csi *csi, pdf_obj *rdb, fz_stream *file)
+{
+ fz_context *ctx = csi->dev->ctx;
+ int ch;
+ fz_image *img;
+ pdf_obj *obj;
+
+ obj = pdf_parse_dict(csi->xref, file, &csi->xref->lexbuf.base);
+
+ /* read whitespace after ID keyword */
+ ch = fz_read_byte(file);
+ if (ch == '\r')
+ if (fz_peek_byte(file) == '\n')
+ fz_read_byte(file);
+
+ fz_try(ctx)
+ {
+ img = pdf_load_inline_image(csi->xref, rdb, obj, file);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(obj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ pdf_show_image(csi, img);
+
+ fz_drop_image(ctx, img);
+
+ /* find EI */
+ ch = fz_read_byte(file);
+ while (ch != 'E' && ch != EOF)
+ ch = fz_read_byte(file);
+ ch = fz_read_byte(file);
+ if (ch != 'I')
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error after inline image");
+}
+
+static void pdf_run_B(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 1, 1, 0);
+}
+
+static void pdf_run_BMC(pdf_csi *csi)
+{
+ /* If we are already in a hidden OCG, then we'll still be hidden -
+ * just increment the depth so we pop back to visibility when we've
+ * seen enough EDCs. */
+ if (csi->in_hidden_ocg > 0)
+ {
+ csi->in_hidden_ocg++;
+ }
+}
+
+static void pdf_run_BT(pdf_csi *csi)
+{
+ csi->in_text = 1;
+ csi->tm = fz_identity;
+ csi->tlm = fz_identity;
+}
+
+static void pdf_run_BX(pdf_csi *csi)
+{
+ csi->xbalance ++;
+}
+
+static void pdf_run_Bstar(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 1, 1, 1);
+}
+
+static void pdf_run_cs_imp(pdf_csi *csi, pdf_obj *rdb, int what)
+{
+ fz_context *ctx = csi->dev->ctx;
+ fz_colorspace *colorspace;
+ pdf_obj *obj, *dict;
+
+ if (!strcmp(csi->name, "Pattern"))
+ {
+ pdf_set_pattern(csi, what, NULL, NULL);
+ }
+ else
+ {
+ if (!strcmp(csi->name, "DeviceGray"))
+ colorspace = fz_device_gray(ctx); /* No fz_keep_colorspace as static */
+ else if (!strcmp(csi->name, "DeviceRGB"))
+ colorspace = fz_device_rgb(ctx); /* No fz_keep_colorspace as static */
+ else if (!strcmp(csi->name, "DeviceCMYK"))
+ colorspace = fz_device_cmyk(ctx); /* No fz_keep_colorspace as static */
+ else
+ {
+ dict = pdf_dict_gets(rdb, "ColorSpace");
+ if (!dict)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find ColorSpace dictionary");
+ obj = pdf_dict_gets(dict, csi->name);
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find colorspace resource '%s'", csi->name);
+ colorspace = pdf_load_colorspace(csi->xref, obj);
+ }
+
+ pdf_set_colorspace(csi, what, colorspace);
+
+ fz_drop_colorspace(ctx, colorspace);
+ }
+}
+
+static void pdf_run_CS(pdf_csi *csi, pdf_obj *rdb)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED;
+
+ pdf_run_cs_imp(csi, rdb, PDF_STROKE);
+}
+
+static void pdf_run_cs(pdf_csi *csi, pdf_obj *rdb)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED;
+
+ pdf_run_cs_imp(csi, rdb, PDF_FILL);
+}
+
+static void pdf_run_DP(pdf_csi *csi)
+{
+}
+
+static void pdf_run_Do(pdf_csi *csi, pdf_obj *rdb)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_obj *dict;
+ pdf_obj *obj;
+ pdf_obj *subtype;
+
+ dict = pdf_dict_gets(rdb, "XObject");
+ if (!dict)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find XObject dictionary when looking for: '%s'", csi->name);
+
+ obj = pdf_dict_gets(dict, csi->name);
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find xobject resource: '%s'", csi->name);
+
+ subtype = pdf_dict_gets(obj, "Subtype");
+ if (!pdf_is_name(subtype))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no XObject subtype specified");
+
+ if (pdf_is_hidden_ocg(pdf_dict_gets(obj, "OC"), csi, rdb))
+ return;
+
+ if (!strcmp(pdf_to_name(subtype), "Form") && pdf_dict_gets(obj, "Subtype2"))
+ subtype = pdf_dict_gets(obj, "Subtype2");
+
+ if (!strcmp(pdf_to_name(subtype), "Form"))
+ {
+ pdf_xobject *xobj;
+
+ xobj = pdf_load_xobject(csi->xref, obj);
+
+ /* Inherit parent resources, in case this one was empty XXX check where it's loaded */
+ if (!xobj->resources)
+ xobj->resources = pdf_keep_obj(rdb);
+
+ fz_try(ctx)
+ {
+ pdf_run_xobject(csi, xobj->resources, xobj, &fz_identity);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_xobject(ctx, xobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot draw xobject (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ }
+ }
+
+ else if (!strcmp(pdf_to_name(subtype), "Image"))
+ {
+ if ((csi->dev->hints & FZ_IGNORE_IMAGE) == 0)
+ {
+ fz_image *img = pdf_load_image(csi->xref, obj);
+
+ fz_try(ctx)
+ {
+ pdf_show_image(csi, img);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_image(ctx, img);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ }
+
+ else if (!strcmp(pdf_to_name(subtype), "PS"))
+ {
+ fz_warn(ctx, "ignoring XObject with subtype PS");
+ }
+
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown XObject subtype: '%s'", pdf_to_name(subtype));
+ }
+}
+
+static void pdf_run_EMC(pdf_csi *csi)
+{
+ if (csi->in_hidden_ocg > 0)
+ csi->in_hidden_ocg--;
+}
+
+static void pdf_run_ET(pdf_csi *csi)
+{
+ pdf_flush_text(csi);
+ csi->accumulate = 1;
+ csi->in_text = 0;
+}
+
+static void pdf_run_EX(pdf_csi *csi)
+{
+ csi->xbalance --;
+}
+
+static void pdf_run_F(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 1, 0, 0);
+}
+
+static void pdf_run_G(pdf_csi *csi)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED;
+ pdf_set_colorspace(csi, PDF_STROKE, fz_device_gray(csi->dev->ctx));
+ pdf_set_color(csi, PDF_STROKE, csi->stack);
+}
+
+static void pdf_run_J(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ csi->dev->flags &= ~(FZ_DEVFLAG_STARTCAP_UNDEFINED | FZ_DEVFLAG_DASHCAP_UNDEFINED | FZ_DEVFLAG_ENDCAP_UNDEFINED);
+ gstate->stroke_state = fz_unshare_stroke_state(csi->dev->ctx, gstate->stroke_state);
+ gstate->stroke_state->start_cap = csi->stack[0];
+ gstate->stroke_state->dash_cap = csi->stack[0];
+ gstate->stroke_state->end_cap = csi->stack[0];
+}
+
+static void pdf_run_K(pdf_csi *csi)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED;
+ pdf_set_colorspace(csi, PDF_STROKE, fz_device_cmyk(csi->dev->ctx));
+ pdf_set_color(csi, PDF_STROKE, csi->stack);
+}
+
+static void pdf_run_M(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ csi->dev->flags &= ~FZ_DEVFLAG_MITERLIMIT_UNDEFINED;
+ gstate->stroke_state = fz_unshare_stroke_state(csi->dev->ctx, gstate->stroke_state);
+ gstate->stroke_state->miterlimit = csi->stack[0];
+}
+
+static void pdf_run_MP(pdf_csi *csi)
+{
+}
+
+static void pdf_run_Q(pdf_csi *csi)
+{
+ pdf_grestore(csi);
+}
+
+static void pdf_run_RG(pdf_csi *csi)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED;
+ pdf_set_colorspace(csi, PDF_STROKE, fz_device_rgb(csi->dev->ctx));
+ pdf_set_color(csi, PDF_STROKE, csi->stack);
+}
+
+static void pdf_run_S(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 0, 1, 0);
+}
+
+static void pdf_run_SC_imp(pdf_csi *csi, pdf_obj *rdb, int what, pdf_material *mat)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_obj *patterntype;
+ pdf_obj *dict;
+ pdf_obj *obj;
+ int kind;
+
+ kind = mat->kind;
+ if (csi->name[0])
+ kind = PDF_MAT_PATTERN;
+
+ switch (kind)
+ {
+ case PDF_MAT_NONE:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot set color in mask objects");
+
+ case PDF_MAT_COLOR:
+ pdf_set_color(csi, what, csi->stack);
+ break;
+
+ case PDF_MAT_PATTERN:
+ dict = pdf_dict_gets(rdb, "Pattern");
+ if (!dict)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find Pattern dictionary");
+
+ obj = pdf_dict_gets(dict, csi->name);
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find pattern resource '%s'", csi->name);
+
+ patterntype = pdf_dict_gets(obj, "PatternType");
+
+ if (pdf_to_int(patterntype) == 1)
+ {
+ pdf_pattern *pat;
+ pat = pdf_load_pattern(csi->xref, obj);
+ pdf_set_pattern(csi, what, pat, csi->top > 0 ? csi->stack : NULL);
+ pdf_drop_pattern(ctx, pat);
+ }
+ else if (pdf_to_int(patterntype) == 2)
+ {
+ fz_shade *shd;
+ shd = pdf_load_shading(csi->xref, obj);
+ pdf_set_shade(csi, what, shd);
+ fz_drop_shade(ctx, shd);
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown pattern type: %d", pdf_to_int(patterntype));
+ }
+ break;
+
+ case PDF_MAT_SHADE:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot set color in shade objects");
+ }
+ mat->gstate_num = csi->gparent;
+}
+
+static void pdf_run_SC(pdf_csi *csi, pdf_obj *rdb)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ csi->dev->flags &= ~FZ_DEVFLAG_STROKECOLOR_UNDEFINED;
+ pdf_run_SC_imp(csi, rdb, PDF_STROKE, &gstate->stroke);
+}
+
+static void pdf_run_sc(pdf_csi *csi, pdf_obj *rdb)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ csi->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED;
+ pdf_run_SC_imp(csi, rdb, PDF_FILL, &gstate->fill);
+}
+
+static void pdf_run_Tc(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ gstate->char_space = csi->stack[0];
+}
+
+static void pdf_run_Tw(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ gstate->word_space = csi->stack[0];
+}
+
+static void pdf_run_Tz(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ float a = csi->stack[0] / 100;
+ pdf_flush_text(csi);
+ gstate->scale = a;
+}
+
+static void pdf_run_TL(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ gstate->leading = csi->stack[0];
+}
+
+static void pdf_run_Tf(pdf_csi *csi, pdf_obj *rdb)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_obj *dict;
+ pdf_obj *obj;
+
+ gstate->size = csi->stack[0];
+ if (gstate->font)
+ pdf_drop_font(ctx, gstate->font);
+ gstate->font = NULL;
+
+ dict = pdf_dict_gets(rdb, "Font");
+ if (!dict)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find Font dictionary");
+
+ obj = pdf_dict_gets(dict, csi->name);
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find font resource: '%s'", csi->name);
+
+ gstate->font = pdf_load_font(csi->xref, rdb, obj, csi->nested_depth);
+}
+
+static void pdf_run_Tr(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ gstate->render = csi->stack[0];
+}
+
+static void pdf_run_Ts(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ gstate->rise = csi->stack[0];
+}
+
+static void pdf_run_Td(pdf_csi *csi)
+{
+ fz_pre_translate(&csi->tlm, csi->stack[0], csi->stack[1]);
+ csi->tm = csi->tlm;
+}
+
+static void pdf_run_TD(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+
+ gstate->leading = -csi->stack[1];
+ fz_pre_translate(&csi->tlm, csi->stack[0], csi->stack[1]);
+ csi->tm = csi->tlm;
+}
+
+static void pdf_run_Tm(pdf_csi *csi)
+{
+ csi->tm.a = csi->stack[0];
+ csi->tm.b = csi->stack[1];
+ csi->tm.c = csi->stack[2];
+ csi->tm.d = csi->stack[3];
+ csi->tm.e = csi->stack[4];
+ csi->tm.f = csi->stack[5];
+ csi->tlm = csi->tm;
+}
+
+static void pdf_run_Tstar(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_pre_translate(&csi->tlm, 0, -gstate->leading);
+ csi->tm = csi->tlm;
+}
+
+static void pdf_run_Tj(pdf_csi *csi)
+{
+ if (csi->string_len)
+ pdf_show_string(csi, csi->string, csi->string_len);
+ else
+ pdf_show_text(csi, csi->obj);
+}
+
+static void pdf_run_TJ(pdf_csi *csi)
+{
+ if (csi->string_len)
+ pdf_show_string(csi, csi->string, csi->string_len);
+ else
+ pdf_show_text(csi, csi->obj);
+}
+
+static void pdf_run_W(pdf_csi *csi)
+{
+ csi->clip = 1;
+ csi->clip_even_odd = 0;
+}
+
+static void pdf_run_Wstar(pdf_csi *csi)
+{
+ csi->clip = 1;
+ csi->clip_even_odd = 1;
+}
+
+static void pdf_run_b(pdf_csi *csi)
+{
+ pdf_show_path(csi, 1, 1, 1, 0);
+}
+
+static void pdf_run_bstar(pdf_csi *csi)
+{
+ pdf_show_path(csi, 1, 1, 1, 1);
+}
+
+static void pdf_run_c(pdf_csi *csi)
+{
+ float a, b, c, d, e, f;
+ a = csi->stack[0];
+ b = csi->stack[1];
+ c = csi->stack[2];
+ d = csi->stack[3];
+ e = csi->stack[4];
+ f = csi->stack[5];
+ fz_curveto(csi->dev->ctx, csi->path, a, b, c, d, e, f);
+}
+
+static void pdf_run_cm(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ fz_matrix m;
+
+ m.a = csi->stack[0];
+ m.b = csi->stack[1];
+ m.c = csi->stack[2];
+ m.d = csi->stack[3];
+ m.e = csi->stack[4];
+ m.f = csi->stack[5];
+
+ fz_concat(&gstate->ctm, &m, &gstate->ctm);
+}
+
+static void pdf_run_d(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_obj *array;
+ int i;
+ int len;
+
+ array = csi->obj;
+ len = pdf_array_len(array);
+ gstate->stroke_state = fz_unshare_stroke_state_with_len(csi->dev->ctx, gstate->stroke_state, len);
+ gstate->stroke_state->dash_len = len;
+ for (i = 0; i < len; i++)
+ gstate->stroke_state->dash_list[i] = pdf_to_real(pdf_array_get(array, i));
+ gstate->stroke_state->dash_phase = csi->stack[0];
+}
+
+static void pdf_run_d0(pdf_csi *csi)
+{
+ if (csi->nested_depth > 1)
+ return;
+ csi->dev->flags |= FZ_DEVFLAG_COLOR;
+}
+
+static void pdf_run_d1(pdf_csi *csi)
+{
+ if (csi->nested_depth > 1)
+ return;
+ csi->dev->flags |= FZ_DEVFLAG_MASK;
+ csi->dev->flags &= ~(FZ_DEVFLAG_FILLCOLOR_UNDEFINED |
+ FZ_DEVFLAG_STROKECOLOR_UNDEFINED |
+ FZ_DEVFLAG_STARTCAP_UNDEFINED |
+ FZ_DEVFLAG_DASHCAP_UNDEFINED |
+ FZ_DEVFLAG_ENDCAP_UNDEFINED |
+ FZ_DEVFLAG_LINEJOIN_UNDEFINED |
+ FZ_DEVFLAG_MITERLIMIT_UNDEFINED |
+ FZ_DEVFLAG_LINEWIDTH_UNDEFINED);
+}
+
+static void pdf_run_f(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 1, 0, 0);
+}
+
+static void pdf_run_fstar(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 1, 0, 1);
+}
+
+static void pdf_run_g(pdf_csi *csi)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED;
+ pdf_set_colorspace(csi, PDF_FILL, fz_device_gray(csi->dev->ctx));
+ pdf_set_color(csi, PDF_FILL, csi->stack);
+}
+
+static void pdf_run_gs(pdf_csi *csi, pdf_obj *rdb)
+{
+ pdf_obj *dict;
+ pdf_obj *obj;
+ fz_context *ctx = csi->dev->ctx;
+
+ dict = pdf_dict_gets(rdb, "ExtGState");
+ if (!dict)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find ExtGState dictionary");
+
+ obj = pdf_dict_gets(dict, csi->name);
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find extgstate resource '%s'", csi->name);
+
+ pdf_run_extgstate(csi, rdb, obj);
+}
+
+static void pdf_run_h(pdf_csi *csi)
+{
+ fz_closepath(csi->dev->ctx, csi->path);
+}
+
+static void pdf_run_i(pdf_csi *csi)
+{
+}
+
+static void pdf_run_j(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ csi->dev->flags &= ~FZ_DEVFLAG_LINEJOIN_UNDEFINED;
+ gstate->stroke_state = fz_unshare_stroke_state(csi->dev->ctx, gstate->stroke_state);
+ gstate->stroke_state->linejoin = csi->stack[0];
+}
+
+static void pdf_run_k(pdf_csi *csi)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED;
+ pdf_set_colorspace(csi, PDF_FILL, fz_device_cmyk(csi->dev->ctx));
+ pdf_set_color(csi, PDF_FILL, csi->stack);
+}
+
+static void pdf_run_l(pdf_csi *csi)
+{
+ float a, b;
+ a = csi->stack[0];
+ b = csi->stack[1];
+ fz_lineto(csi->dev->ctx, csi->path, a, b);
+}
+
+static void pdf_run_m(pdf_csi *csi)
+{
+ float a, b;
+ a = csi->stack[0];
+ b = csi->stack[1];
+ fz_moveto(csi->dev->ctx, csi->path, a, b);
+}
+
+static void pdf_run_n(pdf_csi *csi)
+{
+ pdf_show_path(csi, 0, 0, 0, 0);
+}
+
+static void pdf_run_q(pdf_csi *csi)
+{
+ pdf_gsave(csi);
+}
+
+static void pdf_run_re(pdf_csi *csi)
+{
+ fz_context *ctx = csi->dev->ctx;
+ float x, y, w, h;
+
+ x = csi->stack[0];
+ y = csi->stack[1];
+ w = csi->stack[2];
+ h = csi->stack[3];
+
+ fz_moveto(ctx, csi->path, x, y);
+ fz_lineto(ctx, csi->path, x + w, y);
+ fz_lineto(ctx, csi->path, x + w, y + h);
+ fz_lineto(ctx, csi->path, x, y + h);
+ fz_closepath(ctx, csi->path);
+}
+
+static void pdf_run_rg(pdf_csi *csi)
+{
+ csi->dev->flags &= ~FZ_DEVFLAG_FILLCOLOR_UNDEFINED;
+ pdf_set_colorspace(csi, PDF_FILL, fz_device_rgb(csi->dev->ctx));
+ pdf_set_color(csi, PDF_FILL, csi->stack);
+}
+
+static void pdf_run_ri(pdf_csi *csi)
+{
+}
+
+static void pdf_run(pdf_csi *csi)
+{
+ pdf_show_path(csi, 1, 0, 1, 0);
+}
+
+static void pdf_run_sh(pdf_csi *csi, pdf_obj *rdb)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_obj *dict;
+ pdf_obj *obj;
+ fz_shade *shd;
+
+ dict = pdf_dict_gets(rdb, "Shading");
+ if (!dict)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find shading dictionary");
+
+ obj = pdf_dict_gets(dict, csi->name);
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find shading resource: '%s'", csi->name);
+
+ if ((csi->dev->hints & FZ_IGNORE_SHADE) == 0)
+ {
+ shd = pdf_load_shading(csi->xref, obj);
+
+ fz_try(ctx)
+ {
+ pdf_show_shade(csi, shd);
+ }
+ fz_always(ctx)
+ {
+ fz_drop_shade(ctx, shd);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+}
+
+static void pdf_run_v(pdf_csi *csi)
+{
+ float a, b, c, d;
+ a = csi->stack[0];
+ b = csi->stack[1];
+ c = csi->stack[2];
+ d = csi->stack[3];
+ fz_curvetov(csi->dev->ctx, csi->path, a, b, c, d);
+}
+
+static void pdf_run_w(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_flush_text(csi); /* linewidth affects stroked text rendering mode */
+ csi->dev->flags &= ~FZ_DEVFLAG_LINEWIDTH_UNDEFINED;
+ gstate->stroke_state = fz_unshare_stroke_state(csi->dev->ctx, gstate->stroke_state);
+ gstate->stroke_state->linewidth = csi->stack[0];
+}
+
+static void pdf_run_y(pdf_csi *csi)
+{
+ float a, b, c, d;
+ a = csi->stack[0];
+ b = csi->stack[1];
+ c = csi->stack[2];
+ d = csi->stack[3];
+ fz_curvetoy(csi->dev->ctx, csi->path, a, b, c, d);
+}
+
+static void pdf_run_squote(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+
+ fz_pre_translate(&csi->tlm, 0, -gstate->leading);
+ csi->tm = csi->tlm;
+
+ if (csi->string_len)
+ pdf_show_string(csi, csi->string, csi->string_len);
+ else
+ pdf_show_text(csi, csi->obj);
+}
+
+static void pdf_run_dquote(pdf_csi *csi)
+{
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+
+ gstate->word_space = csi->stack[0];
+ gstate->char_space = csi->stack[1];
+
+ fz_pre_translate(&csi->tlm, 0, -gstate->leading);
+ csi->tm = csi->tlm;
+
+ if (csi->string_len)
+ pdf_show_string(csi, csi->string, csi->string_len);
+ else
+ pdf_show_text(csi, csi->obj);
+}
+
+#define A(a) (a)
+#define B(a,b) (a | b << 8)
+#define C(a,b,c) (a | b << 8 | c << 16)
+
+static int
+pdf_run_keyword(pdf_csi *csi, pdf_obj *rdb, fz_stream *file, char *buf)
+{
+ fz_context *ctx = csi->dev->ctx;
+ int key;
+
+ key = buf[0];
+ if (buf[1])
+ {
+ key |= buf[1] << 8;
+ if (buf[2])
+ {
+ key |= buf[2] << 16;
+ if (buf[3])
+ key = 0;
+ }
+ }
+
+ switch (key)
+ {
+ case A('"'): pdf_run_dquote(csi); break;
+ case A('\''): pdf_run_squote(csi); break;
+ case A('B'): pdf_run_B(csi); break;
+ case B('B','*'): pdf_run_Bstar(csi); break;
+ case C('B','D','C'): pdf_run_BDC(csi, rdb); break;
+ case B('B','I'):
+ pdf_run_BI(csi, rdb, file);
+ break;
+ case C('B','M','C'): pdf_run_BMC(csi); break;
+ case B('B','T'): pdf_run_BT(csi); break;
+ case B('B','X'): pdf_run_BX(csi); break;
+ case B('C','S'): pdf_run_CS(csi, rdb); break;
+ case B('D','P'): pdf_run_DP(csi); break;
+ case B('D','o'):
+ fz_try(ctx)
+ {
+ pdf_run_Do(csi, rdb);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot draw xobject/image");
+ }
+ break;
+ case C('E','M','C'): pdf_run_EMC(csi); break;
+ case B('E','T'): pdf_run_ET(csi); break;
+ case B('E','X'): pdf_run_EX(csi); break;
+ case A('F'): pdf_run_F(csi); break;
+ case A('G'): pdf_run_G(csi); break;
+ case A('J'): pdf_run_J(csi); break;
+ case A('K'): pdf_run_K(csi); break;
+ case A('M'): pdf_run_M(csi); break;
+ case B('M','P'): pdf_run_MP(csi); break;
+ case A('Q'): pdf_run_Q(csi); break;
+ case B('R','G'): pdf_run_RG(csi); break;
+ case A('S'): pdf_run_S(csi); break;
+ case B('S','C'): pdf_run_SC(csi, rdb); break;
+ case C('S','C','N'): pdf_run_SC(csi, rdb); break;
+ case B('T','*'): pdf_run_Tstar(csi); break;
+ case B('T','D'): pdf_run_TD(csi); break;
+ case B('T','J'): pdf_run_TJ(csi); break;
+ case B('T','L'): pdf_run_TL(csi); break;
+ case B('T','c'): pdf_run_Tc(csi); break;
+ case B('T','d'): pdf_run_Td(csi); break;
+ case B('T','f'):
+ fz_try(ctx)
+ {
+ pdf_run_Tf(csi, rdb);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot set font");
+ }
+ break;
+ case B('T','j'): pdf_run_Tj(csi); break;
+ case B('T','m'): pdf_run_Tm(csi); break;
+ case B('T','r'): pdf_run_Tr(csi); break;
+ case B('T','s'): pdf_run_Ts(csi); break;
+ case B('T','w'): pdf_run_Tw(csi); break;
+ case B('T','z'): pdf_run_Tz(csi); break;
+ case A('W'): pdf_run_W(csi); break;
+ case B('W','*'): pdf_run_Wstar(csi); break;
+ case A('b'): pdf_run_b(csi); break;
+ case B('b','*'): pdf_run_bstar(csi); break;
+ case A('c'): pdf_run_c(csi); break;
+ case B('c','m'): pdf_run_cm(csi); break;
+ case B('c','s'): pdf_run_cs(csi, rdb); break;
+ case A('d'): pdf_run_d(csi); break;
+ case B('d','0'): pdf_run_d0(csi); break;
+ case B('d','1'): pdf_run_d1(csi); break;
+ case A('f'): pdf_run_f(csi); break;
+ case B('f','*'): pdf_run_fstar(csi); break;
+ case A('g'): pdf_run_g(csi); break;
+ case B('g','s'):
+ fz_try(ctx)
+ {
+ pdf_run_gs(csi, rdb);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot set graphics state");
+ }
+ break;
+ case A('h'): pdf_run_h(csi); break;
+ case A('i'): pdf_run_i(csi); break;
+ case A('j'): pdf_run_j(csi); break;
+ case A('k'): pdf_run_k(csi); break;
+ case A('l'): pdf_run_l(csi); break;
+ case A('m'): pdf_run_m(csi); break;
+ case A('n'): pdf_run_n(csi); break;
+ case A('q'): pdf_run_q(csi); break;
+ case B('r','e'): pdf_run_re(csi); break;
+ case B('r','g'): pdf_run_rg(csi); break;
+ case B('r','i'): pdf_run_ri(csi); break;
+ case A('s'): pdf_run(csi); break;
+ case B('s','c'): pdf_run_sc(csi, rdb); break;
+ case C('s','c','n'): pdf_run_sc(csi, rdb); break;
+ case B('s','h'):
+ fz_try(ctx)
+ {
+ pdf_run_sh(csi, rdb);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot draw shading");
+ }
+ break;
+ case A('v'): pdf_run_v(csi); break;
+ case A('w'): pdf_run_w(csi); break;
+ case A('y'): pdf_run_y(csi); break;
+ default:
+ if (!csi->xbalance)
+ {
+ fz_warn(ctx, "unknown keyword: '%s'", buf);
+ return 1;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void
+pdf_run_stream(pdf_csi *csi, pdf_obj *rdb, fz_stream *file, pdf_lexbuf *buf)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_token tok = PDF_TOK_ERROR;
+ int in_array;
+ int ignoring_errors = 0;
+
+ /* make sure we have a clean slate if we come here from flush_text */
+ pdf_clear_stack(csi);
+ in_array = 0;
+
+ fz_var(in_array);
+ fz_var(tok);
+
+ if (csi->cookie)
+ {
+ csi->cookie->progress_max = -1;
+ csi->cookie->progress = 0;
+ }
+
+ do
+ {
+ fz_try(ctx)
+ {
+ do
+ {
+ /* Check the cookie */
+ if (csi->cookie)
+ {
+ if (csi->cookie->abort)
+ {
+ tok = PDF_TOK_EOF;
+ break;
+ }
+ csi->cookie->progress++;
+ }
+
+ tok = pdf_lex(file, buf);
+
+ if (in_array)
+ {
+ if (tok == PDF_TOK_CLOSE_ARRAY)
+ {
+ in_array = 0;
+ }
+ else if (tok == PDF_TOK_REAL)
+ {
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_show_space(csi, -buf->f * gstate->size * 0.001f);
+ }
+ else if (tok == PDF_TOK_INT)
+ {
+ pdf_gstate *gstate = csi->gstate + csi->gtop;
+ pdf_show_space(csi, -buf->i * gstate->size * 0.001f);
+ }
+ else if (tok == PDF_TOK_STRING)
+ {
+ pdf_show_string(csi, (unsigned char *)buf->scratch, buf->len);
+ }
+ else if (tok == PDF_TOK_KEYWORD)
+ {
+ if (!strcmp(buf->scratch, "Tw") || !strcmp(buf->scratch, "Tc"))
+ fz_warn(ctx, "ignoring keyword '%s' inside array", buf->scratch);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in array");
+ }
+ else if (tok == PDF_TOK_EOF)
+ break;
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in array");
+ }
+
+ else switch (tok)
+ {
+ case PDF_TOK_ENDSTREAM:
+ case PDF_TOK_EOF:
+ tok = PDF_TOK_EOF;
+ break;
+
+ case PDF_TOK_OPEN_ARRAY:
+ if (!csi->in_text)
+ {
+ if (csi->obj)
+ {
+ pdf_drop_obj(csi->obj);
+ csi->obj = NULL;
+ }
+ csi->obj = pdf_parse_array(csi->xref, file, buf);
+ }
+ else
+ {
+ in_array = 1;
+ }
+ break;
+
+ case PDF_TOK_OPEN_DICT:
+ if (csi->obj)
+ {
+ pdf_drop_obj(csi->obj);
+ csi->obj = NULL;
+ }
+ csi->obj = pdf_parse_dict(csi->xref, file, buf);
+ break;
+
+ case PDF_TOK_NAME:
+ fz_strlcpy(csi->name, buf->scratch, sizeof(csi->name));
+ break;
+
+ case PDF_TOK_INT:
+ if (csi->top < nelem(csi->stack)) {
+ csi->stack[csi->top] = buf->i;
+ csi->top ++;
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "stack overflow");
+ break;
+
+ case PDF_TOK_REAL:
+ if (csi->top < nelem(csi->stack)) {
+ csi->stack[csi->top] = buf->f;
+ csi->top ++;
+ }
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "stack overflow");
+ break;
+
+ case PDF_TOK_STRING:
+ if (buf->len <= sizeof(csi->string))
+ {
+ memcpy(csi->string, buf->scratch, buf->len);
+ csi->string_len = buf->len;
+ }
+ else
+ {
+ if (csi->obj)
+ {
+ pdf_drop_obj(csi->obj);
+ csi->obj = NULL;
+ }
+ csi->obj = pdf_new_string(ctx, buf->scratch, buf->len);
+ }
+ break;
+
+ case PDF_TOK_KEYWORD:
+ if (pdf_run_keyword(csi, rdb, file, buf->scratch))
+ {
+ tok = PDF_TOK_EOF;
+ }
+ pdf_clear_stack(csi);
+ break;
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in content stream");
+ }
+ }
+ while (tok != PDF_TOK_EOF);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* Swallow the error */
+ if (csi->cookie)
+ csi->cookie->errors++;
+ if (!ignoring_errors)
+ {
+ fz_warn(ctx, "Ignoring errors during rendering");
+ ignoring_errors = 1;
+ }
+ /* If we do catch an error, then reset ourselves to a
+ * base lexing state */
+ in_array = 0;
+ }
+ }
+ while (tok != PDF_TOK_EOF);
+}
+
+/*
+ * Entry points
+ */
+
+static void
+pdf_run_contents_stream(pdf_csi *csi, pdf_obj *rdb, fz_stream *file)
+{
+ fz_context *ctx = csi->dev->ctx;
+ pdf_lexbuf *buf;
+ int save_in_text;
+ int save_gbot;
+
+ fz_var(buf);
+
+ if (file == NULL)
+ return;
+
+ buf = fz_malloc(ctx, sizeof(*buf)); /* we must be re-entrant for type3 fonts */
+ pdf_lexbuf_init(ctx, buf, PDF_LEXBUF_SMALL);
+ save_in_text = csi->in_text;
+ csi->in_text = 0;
+ save_gbot = csi->gbot;
+ csi->gbot = csi->gtop;
+ fz_try(ctx)
+ {
+ pdf_run_stream(csi, rdb, file, buf);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "Content stream parsing error - rendering truncated");
+ }
+ while (csi->gtop > csi->gbot)
+ pdf_grestore(csi);
+ csi->gbot = save_gbot;
+ csi->in_text = save_in_text;
+ pdf_lexbuf_fin(buf);
+ fz_free(ctx, buf);
+}
+
+static void
+pdf_run_contents_object(pdf_csi *csi, pdf_obj *rdb, pdf_obj *contents)
+{
+ fz_context *ctx = csi->dev->ctx;
+ fz_stream *file = NULL;
+
+ if (contents == NULL)
+ return;
+
+ file = pdf_open_contents_stream(csi->xref, contents);
+ fz_try(ctx)
+ {
+ pdf_run_contents_stream(csi, rdb, file);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+pdf_run_contents_buffer(pdf_csi *csi, pdf_obj *rdb, fz_buffer *contents)
+{
+ fz_context *ctx = csi->dev->ctx;
+ fz_stream *file = NULL;
+
+ if (contents == NULL)
+ return;
+
+ file = fz_open_buffer(ctx, contents);
+ fz_try(ctx)
+ {
+ pdf_run_contents_stream(csi, rdb, file);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void pdf_run_page_contents_with_usage(pdf_document *xref, pdf_page *page, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie)
+{
+ fz_context *ctx = dev->ctx;
+ pdf_csi *csi;
+ fz_matrix local_ctm;
+
+ fz_concat(&local_ctm, &page->ctm, ctm);
+
+ if (page->transparency)
+ {
+ fz_rect mediabox = page->mediabox;
+ fz_begin_group(dev, fz_transform_rect(&mediabox, &local_ctm), 1, 0, 0, 1);
+ }
+
+ csi = pdf_new_csi(xref, dev, &local_ctm, event, cookie, NULL, 0);
+ fz_try(ctx)
+ {
+ /* We need to save an extra level here to allow for level 0
+ * to be the 'parent' gstate level. */
+ pdf_gsave(csi);
+ pdf_run_contents_object(csi, page->resources, page->contents);
+ }
+ fz_always(ctx)
+ {
+ while (csi->gtop > 0)
+ pdf_grestore(csi);
+ pdf_free_csi(csi);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot parse page content stream");
+ }
+
+ if (page->transparency)
+ fz_end_group(dev);
+}
+
+void pdf_run_page_contents(pdf_document *xref, pdf_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ pdf_run_page_contents_with_usage(xref, page, dev, ctm, "View", cookie);
+}
+
+static void pdf_run_annot_with_usage(pdf_document *xref, pdf_page *page, pdf_annot *annot, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie)
+{
+ fz_context *ctx = dev->ctx;
+ pdf_csi *csi;
+ int flags;
+ fz_matrix local_ctm;
+
+ fz_concat(&local_ctm, &page->ctm, ctm);
+
+ flags = pdf_to_int(pdf_dict_gets(annot->obj, "F"));
+
+ /* TODO: NoZoom and NoRotate */
+ if (flags & (1 << 0)) /* Invisible */
+ return;
+ if (flags & (1 << 1)) /* Hidden */
+ return;
+ if (!strcmp(event, "Print") && !(flags & (1 << 2))) /* Print */
+ return;
+ if (!strcmp(event, "View") && (flags & (1 << 5))) /* NoView */
+ return;
+
+ csi = pdf_new_csi(xref, dev, &local_ctm, event, cookie, NULL, 0);
+ if (!pdf_is_hidden_ocg(pdf_dict_gets(annot->obj, "OC"), csi, page->resources))
+ {
+ fz_try(ctx)
+ {
+ /* We need to save an extra level here to allow for level 0
+ * to be the 'parent' gstate level. */
+ pdf_gsave(csi);
+ pdf_run_xobject(csi, page->resources, annot->ap, &annot->matrix);
+ }
+ fz_catch(ctx)
+ {
+ while (csi->gtop > 0)
+ pdf_grestore(csi);
+ pdf_free_csi(csi);
+ fz_rethrow_message(ctx, "cannot parse annotation appearance stream");
+ }
+ }
+ pdf_free_csi(csi);
+}
+
+void pdf_run_annot(pdf_document *xref, pdf_page *page, pdf_annot *annot, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ pdf_run_annot_with_usage(xref, page, annot, dev, ctm, "View", cookie);
+}
+
+static void pdf_run_page_annots_with_usage(pdf_document *xref, pdf_page *page, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie)
+{
+ pdf_annot *annot;
+
+ if (cookie && cookie->progress_max != -1)
+ {
+ int count = 1;
+ for (annot = page->annots; annot; annot = annot->next)
+ count++;
+ cookie->progress_max += count;
+ }
+
+ for (annot = page->annots; annot; annot = annot->next)
+ {
+ /* Check the cookie for aborting */
+ if (cookie)
+ {
+ if (cookie->abort)
+ break;
+ cookie->progress++;
+ }
+
+ pdf_run_annot_with_usage(xref, page, annot, dev, ctm, event, cookie);
+ }
+}
+
+void
+pdf_run_page_with_usage(pdf_document *xref, pdf_page *page, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie)
+{
+ pdf_run_page_contents_with_usage(xref, page, dev, ctm, event, cookie);
+ pdf_run_page_annots_with_usage(xref, page, dev, ctm, event, cookie);
+}
+
+void
+pdf_run_page(pdf_document *xref, pdf_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ pdf_run_page_with_usage(xref, page, dev, ctm, "View", cookie);
+}
+
+void
+pdf_run_glyph(pdf_document *xref, pdf_obj *resources, fz_buffer *contents, fz_device *dev, const fz_matrix *ctm, void *gstate, int nested_depth)
+{
+ pdf_csi *csi = pdf_new_csi(xref, dev, ctm, "View", NULL, gstate, nested_depth+1);
+ fz_context *ctx = xref->ctx;
+
+ fz_try(ctx)
+ {
+ if (nested_depth > 10)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Too many nestings of Type3 glyphs");
+ pdf_run_contents_buffer(csi, resources, contents);
+ }
+ fz_always(ctx)
+ {
+ pdf_free_csi(csi);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot parse glyph content stream");
+ }
+}
diff --git a/source/pdf/pdf-lex.c b/source/pdf/pdf-lex.c
new file mode 100644
index 00000000..a8bf9f4b
--- /dev/null
+++ b/source/pdf/pdf-lex.c
@@ -0,0 +1,553 @@
+#include "mupdf/pdf.h"
+
+#define IS_NUMBER \
+ '+':case'-':case'.':case'0':case'1':case'2':case'3':\
+ case'4':case'5':case'6':case'7':case'8':case'9'
+#define IS_WHITE \
+ '\000':case'\011':case'\012':case'\014':case'\015':case'\040'
+#define IS_HEX \
+ '0':case'1':case'2':case'3':case'4':case'5':case'6':\
+ case'7':case'8':case'9':case'A':case'B':case'C':\
+ case'D':case'E':case'F':case'a':case'b':case'c':\
+ case'd':case'e':case'f'
+#define IS_DELIM \
+ '(':case')':case'<':case'>':case'[':case']':case'{':\
+ case'}':case'/':case'%'
+
+#define RANGE_0_9 \
+ '0':case'1':case'2':case'3':case'4':case'5':\
+ case'6':case'7':case'8':case'9'
+#define RANGE_a_f \
+ 'a':case'b':case'c':case'd':case'e':case'f'
+#define RANGE_A_F \
+ 'A':case'B':case'C':case'D':case'E':case'F'
+
+static inline int iswhite(int ch)
+{
+ return
+ ch == '\000' ||
+ ch == '\011' ||
+ ch == '\012' ||
+ ch == '\014' ||
+ ch == '\015' ||
+ ch == '\040';
+}
+
+static inline int unhex(int ch)
+{
+ if (ch >= '0' && ch <= '9') return ch - '0';
+ if (ch >= 'A' && ch <= 'F') return ch - 'A' + 0xA;
+ if (ch >= 'a' && ch <= 'f') return ch - 'a' + 0xA;
+ return 0;
+}
+
+static void
+lex_white(fz_stream *f)
+{
+ int c;
+ do {
+ c = fz_read_byte(f);
+ } while ((c <= 32) && (iswhite(c)));
+ if (c != EOF)
+ fz_unread_byte(f);
+}
+
+static void
+lex_comment(fz_stream *f)
+{
+ int c;
+ do {
+ c = fz_read_byte(f);
+ } while ((c != '\012') && (c != '\015') && (c != EOF));
+}
+
+static int
+lex_number(fz_stream *f, pdf_lexbuf *buf, int c)
+{
+ int neg = 0;
+ int i = 0;
+ int n;
+ int d;
+ float v;
+
+ /* Initially we might have +, -, . or a digit */
+ switch (c)
+ {
+ case '.':
+ goto loop_after_dot;
+ case '-':
+ neg = 1;
+ break;
+ case '+':
+ break;
+ default: /* Must be a digit */
+ i = c - '0';
+ break;
+ }
+
+ while (1)
+ {
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case '.':
+ goto loop_after_dot;
+ case RANGE_0_9:
+ i = 10*i + c - '0';
+ /* FIXME: Need overflow check here; do we care? */
+ break;
+ default:
+ fz_unread_byte(f);
+ /* Fallthrough */
+ case EOF:
+ if (neg)
+ i = -i;
+ buf->i = i;
+ return PDF_TOK_INT;
+ }
+ }
+
+ /* In here, we've seen a dot, so can accept just digits */
+loop_after_dot:
+ n = 0;
+ d = 1;
+ while (1)
+ {
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case RANGE_0_9:
+ if (d >= INT_MAX/10)
+ goto underflow;
+ n = n*10 + (c - '0');
+ d *= 10;
+ break;
+ default:
+ fz_unread_byte(f);
+ /* Fallthrough */
+ case EOF:
+ v = (float)i + ((float)n / (float)d);
+ if (neg)
+ v = -v;
+ buf->f = v;
+ return PDF_TOK_REAL;
+ }
+ }
+
+underflow:
+ /* Ignore any digits after here, because they are too small */
+ while (1)
+ {
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case RANGE_0_9:
+ break;
+ default:
+ fz_unread_byte(f);
+ /* Fallthrough */
+ case EOF:
+ v = (float)i + ((float)n / (float)d);
+ if (neg)
+ v = -v;
+ buf->f = v;
+ return PDF_TOK_REAL;
+ }
+ }
+}
+
+static void
+lex_name(fz_stream *f, pdf_lexbuf *buf)
+{
+ char *s = buf->scratch;
+ int n = buf->size;
+
+ while (n > 1)
+ {
+ int c = fz_read_byte(f);
+ switch (c)
+ {
+ case IS_WHITE:
+ case IS_DELIM:
+ fz_unread_byte(f);
+ goto end;
+ case EOF:
+ goto end;
+ case '#':
+ {
+ int d;
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case RANGE_0_9:
+ d = (c - '0') << 4;
+ break;
+ case RANGE_a_f:
+ d = (c - 'a' + 10) << 4;
+ break;
+ case RANGE_A_F:
+ d = (c - 'A' + 10) << 4;
+ break;
+ default:
+ fz_unread_byte(f);
+ /* fallthrough */
+ case EOF:
+ goto end;
+ }
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case RANGE_0_9:
+ c -= '0';
+ break;
+ case RANGE_a_f:
+ c -= 'a' - 10;
+ break;
+ case RANGE_A_F:
+ c -= 'A' - 10;
+ break;
+ default:
+ fz_unread_byte(f);
+ /* fallthrough */
+ case EOF:
+ *s++ = d;
+ n--;
+ goto end;
+ }
+ *s++ = d + c;
+ n--;
+ break;
+ }
+ default:
+ *s++ = c;
+ n--;
+ break;
+ }
+ }
+end:
+ *s = '\0';
+ buf->len = s - buf->scratch;
+}
+
+static int
+lex_string(fz_stream *f, pdf_lexbuf *lb)
+{
+ char *s = lb->scratch;
+ char *e = s + lb->size;
+ int bal = 1;
+ int oct;
+ int c;
+
+ while (1)
+ {
+ if (s == e)
+ {
+ s += pdf_lexbuf_grow(lb);
+ e = lb->scratch + lb->size;
+ }
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case EOF:
+ goto end;
+ case '(':
+ bal++;
+ *s++ = c;
+ break;
+ case ')':
+ bal --;
+ if (bal == 0)
+ goto end;
+ *s++ = c;
+ break;
+ case '\\':
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case EOF:
+ goto end;
+ case 'n':
+ *s++ = '\n';
+ break;
+ case 'r':
+ *s++ = '\r';
+ break;
+ case 't':
+ *s++ = '\t';
+ break;
+ case 'b':
+ *s++ = '\b';
+ break;
+ case 'f':
+ *s++ = '\f';
+ break;
+ case '(':
+ *s++ = '(';
+ break;
+ case ')':
+ *s++ = ')';
+ break;
+ case '\\':
+ *s++ = '\\';
+ break;
+ case RANGE_0_9:
+ oct = c - '0';
+ c = fz_read_byte(f);
+ if (c >= '0' && c <= '9')
+ {
+ oct = oct * 8 + (c - '0');
+ c = fz_read_byte(f);
+ if (c >= '0' && c <= '9')
+ oct = oct * 8 + (c - '0');
+ else if (c != EOF)
+ fz_unread_byte(f);
+ }
+ else if (c != EOF)
+ fz_unread_byte(f);
+ *s++ = oct;
+ break;
+ case '\n':
+ break;
+ case '\r':
+ c = fz_read_byte(f);
+ if ((c != '\n') && (c != EOF))
+ fz_unread_byte(f);
+ break;
+ default:
+ *s++ = c;
+ }
+ break;
+ default:
+ *s++ = c;
+ break;
+ }
+ }
+end:
+ lb->len = s - lb->scratch;
+ return PDF_TOK_STRING;
+}
+
+static int
+lex_hex_string(fz_stream *f, pdf_lexbuf *lb)
+{
+ char *s = lb->scratch;
+ char *e = s + lb->size;
+ int a = 0, x = 0;
+ int c;
+
+ while (1)
+ {
+ if (s == e)
+ {
+ s += pdf_lexbuf_grow(lb);
+ e = lb->scratch + lb->size;
+ }
+ c = fz_read_byte(f);
+ switch (c)
+ {
+ case IS_WHITE:
+ break;
+ case IS_HEX:
+ if (x)
+ {
+ *s++ = a * 16 + unhex(c);
+ x = !x;
+ }
+ else
+ {
+ a = unhex(c);
+ x = !x;
+ }
+ break;
+ case '>':
+ case EOF:
+ goto end;
+ default:
+ fz_warn(f->ctx, "ignoring invalid character in hex string");
+ }
+ }
+end:
+ lb->len = s - lb->scratch;
+ return PDF_TOK_STRING;
+}
+
+static pdf_token
+pdf_token_from_keyword(char *key)
+{
+ switch (*key)
+ {
+ case 'R':
+ if (!strcmp(key, "R")) return PDF_TOK_R;
+ break;
+ case 't':
+ if (!strcmp(key, "true")) return PDF_TOK_TRUE;
+ if (!strcmp(key, "trailer")) return PDF_TOK_TRAILER;
+ break;
+ case 'f':
+ if (!strcmp(key, "false")) return PDF_TOK_FALSE;
+ break;
+ case 'n':
+ if (!strcmp(key, "null")) return PDF_TOK_NULL;
+ break;
+ case 'o':
+ if (!strcmp(key, "obj")) return PDF_TOK_OBJ;
+ break;
+ case 'e':
+ if (!strcmp(key, "endobj")) return PDF_TOK_ENDOBJ;
+ if (!strcmp(key, "endstream")) return PDF_TOK_ENDSTREAM;
+ break;
+ case 's':
+ if (!strcmp(key, "stream")) return PDF_TOK_STREAM;
+ if (!strcmp(key, "startxref")) return PDF_TOK_STARTXREF;
+ break;
+ case 'x':
+ if (!strcmp(key, "xref")) return PDF_TOK_XREF;
+ break;
+ default:
+ break;
+ }
+
+ return PDF_TOK_KEYWORD;
+}
+
+void pdf_lexbuf_init(fz_context *ctx, pdf_lexbuf *lb, int size)
+{
+ lb->size = lb->base_size = size;
+ lb->len = 0;
+ lb->ctx = ctx;
+ lb->scratch = &lb->buffer[0];
+}
+
+void pdf_lexbuf_fin(pdf_lexbuf *lb)
+{
+ if (lb && lb->size != lb->base_size)
+ fz_free(lb->ctx, lb->scratch);
+}
+
+ptrdiff_t pdf_lexbuf_grow(pdf_lexbuf *lb)
+{
+ char *old = lb->scratch;
+ int newsize = lb->size * 2;
+ if (lb->size == lb->base_size)
+ {
+ lb->scratch = fz_malloc(lb->ctx, newsize);
+ memcpy(lb->scratch, lb->buffer, lb->size);
+ }
+ else
+ {
+ lb->scratch = fz_resize_array(lb->ctx, lb->scratch, newsize, 1);
+ }
+ lb->size = newsize;
+ return lb->scratch - old;
+}
+
+pdf_token
+pdf_lex(fz_stream *f, pdf_lexbuf *buf)
+{
+ while (1)
+ {
+ int c = fz_read_byte(f);
+ switch (c)
+ {
+ case EOF:
+ return PDF_TOK_EOF;
+ case IS_WHITE:
+ lex_white(f);
+ break;
+ case '%':
+ lex_comment(f);
+ break;
+ case '/':
+ lex_name(f, buf);
+ return PDF_TOK_NAME;
+ case '(':
+ return lex_string(f, buf);
+ case ')':
+ fz_warn(f->ctx, "lexical error (unexpected ')')");
+ continue;
+ case '<':
+ c = fz_read_byte(f);
+ if (c == '<')
+ {
+ return PDF_TOK_OPEN_DICT;
+ }
+ else
+ {
+ fz_unread_byte(f);
+ return lex_hex_string(f, buf);
+ }
+ case '>':
+ c = fz_read_byte(f);
+ if (c == '>')
+ {
+ return PDF_TOK_CLOSE_DICT;
+ }
+ fz_warn(f->ctx, "lexical error (unexpected '>')");
+ fz_unread_byte(f);
+ continue;
+ case '[':
+ return PDF_TOK_OPEN_ARRAY;
+ case ']':
+ return PDF_TOK_CLOSE_ARRAY;
+ case '{':
+ return PDF_TOK_OPEN_BRACE;
+ case '}':
+ return PDF_TOK_CLOSE_BRACE;
+ case IS_NUMBER:
+ return lex_number(f, buf, c);
+ default: /* isregular: !isdelim && !iswhite && c != EOF */
+ fz_unread_byte(f);
+ lex_name(f, buf);
+ return pdf_token_from_keyword(buf->scratch);
+ }
+ }
+}
+
+void pdf_print_token(fz_context *ctx, fz_buffer *fzbuf, int tok, pdf_lexbuf *buf)
+{
+ switch (tok)
+ {
+ case PDF_TOK_NAME:
+ fz_buffer_printf(ctx, fzbuf, "/%s", buf->scratch);
+ break;
+ case PDF_TOK_STRING:
+ if (buf->len >= buf->size)
+ pdf_lexbuf_grow(buf);
+ buf->scratch[buf->len] = 0;
+ fz_buffer_cat_pdf_string(ctx, fzbuf, buf->scratch);
+ break;
+ case PDF_TOK_OPEN_DICT:
+ fz_buffer_printf(ctx, fzbuf, "<<");
+ break;
+ case PDF_TOK_CLOSE_DICT:
+ fz_buffer_printf(ctx, fzbuf, ">>");
+ break;
+ case PDF_TOK_OPEN_ARRAY:
+ fz_buffer_printf(ctx, fzbuf, "[");
+ break;
+ case PDF_TOK_CLOSE_ARRAY:
+ fz_buffer_printf(ctx, fzbuf, "]");
+ break;
+ case PDF_TOK_OPEN_BRACE:
+ fz_buffer_printf(ctx, fzbuf, "{");
+ break;
+ case PDF_TOK_CLOSE_BRACE:
+ fz_buffer_printf(ctx, fzbuf, "}");
+ break;
+ case PDF_TOK_INT:
+ fz_buffer_printf(ctx, fzbuf, "%d", buf->i);
+ break;
+ case PDF_TOK_REAL:
+ {
+ char sbuf[256];
+ sprintf(sbuf, "%g", buf->f);
+ if (strchr(sbuf, 'e')) /* bad news! */
+ sprintf(sbuf, fabsf(buf->f) > 1 ? "%1.1f" : "%1.8f", buf->f);
+ fz_buffer_printf(ctx, fzbuf, "%s", sbuf);
+ }
+ break;
+ default:
+ fz_buffer_printf(ctx, fzbuf, "%s", buf->scratch);
+ break;
+ }
+}
diff --git a/source/pdf/pdf-metrics.c b/source/pdf/pdf-metrics.c
new file mode 100644
index 00000000..8a4b7d11
--- /dev/null
+++ b/source/pdf/pdf-metrics.c
@@ -0,0 +1,141 @@
+#include "mupdf/pdf.h"
+
+void
+pdf_set_font_wmode(fz_context *ctx, pdf_font_desc *font, int wmode)
+{
+ font->wmode = wmode;
+}
+
+void
+pdf_set_default_hmtx(fz_context *ctx, pdf_font_desc *font, int w)
+{
+ font->dhmtx.w = w;
+}
+
+void
+pdf_set_default_vmtx(fz_context *ctx, pdf_font_desc *font, int y, int w)
+{
+ font->dvmtx.y = y;
+ font->dvmtx.w = w;
+}
+
+void
+pdf_add_hmtx(fz_context *ctx, pdf_font_desc *font, int lo, int hi, int w)
+{
+ if (font->hmtx_len + 1 >= font->hmtx_cap)
+ {
+ int new_cap = font->hmtx_cap + 16;
+ font->hmtx = fz_resize_array(ctx, font->hmtx, new_cap, sizeof(pdf_hmtx));
+ font->hmtx_cap = new_cap;
+ }
+
+ font->hmtx[font->hmtx_len].lo = lo;
+ font->hmtx[font->hmtx_len].hi = hi;
+ font->hmtx[font->hmtx_len].w = w;
+ font->hmtx_len++;
+}
+
+void
+pdf_add_vmtx(fz_context *ctx, pdf_font_desc *font, int lo, int hi, int x, int y, int w)
+{
+ if (font->vmtx_len + 1 >= font->vmtx_cap)
+ {
+ int new_cap = font->vmtx_cap + 16;
+ font->vmtx = fz_resize_array(ctx, font->vmtx, new_cap, sizeof(pdf_vmtx));
+ font->vmtx_cap = new_cap;
+ }
+
+ font->vmtx[font->vmtx_len].lo = lo;
+ font->vmtx[font->vmtx_len].hi = hi;
+ font->vmtx[font->vmtx_len].x = x;
+ font->vmtx[font->vmtx_len].y = y;
+ font->vmtx[font->vmtx_len].w = w;
+ font->vmtx_len++;
+}
+
+static int cmph(const void *a0, const void *b0)
+{
+ pdf_hmtx *a = (pdf_hmtx*)a0;
+ pdf_hmtx *b = (pdf_hmtx*)b0;
+ return a->lo - b->lo;
+}
+
+static int cmpv(const void *a0, const void *b0)
+{
+ pdf_vmtx *a = (pdf_vmtx*)a0;
+ pdf_vmtx *b = (pdf_vmtx*)b0;
+ return a->lo - b->lo;
+}
+
+void
+pdf_end_hmtx(fz_context *ctx, pdf_font_desc *font)
+{
+ if (!font->hmtx)
+ return;
+ qsort(font->hmtx, font->hmtx_len, sizeof(pdf_hmtx), cmph);
+ font->size += font->hmtx_cap * sizeof(pdf_hmtx);
+}
+
+void
+pdf_end_vmtx(fz_context *ctx, pdf_font_desc *font)
+{
+ if (!font->vmtx)
+ return;
+ qsort(font->vmtx, font->vmtx_len, sizeof(pdf_vmtx), cmpv);
+ font->size += font->vmtx_cap * sizeof(pdf_vmtx);
+}
+
+pdf_hmtx
+pdf_lookup_hmtx(fz_context *ctx, pdf_font_desc *font, int cid)
+{
+ int l = 0;
+ int r = font->hmtx_len - 1;
+ int m;
+
+ if (!font->hmtx)
+ goto notfound;
+
+ while (l <= r)
+ {
+ m = (l + r) >> 1;
+ if (cid < font->hmtx[m].lo)
+ r = m - 1;
+ else if (cid > font->hmtx[m].hi)
+ l = m + 1;
+ else
+ return font->hmtx[m];
+ }
+
+notfound:
+ return font->dhmtx;
+}
+
+pdf_vmtx
+pdf_lookup_vmtx(fz_context *ctx, pdf_font_desc *font, int cid)
+{
+ pdf_hmtx h;
+ pdf_vmtx v;
+ int l = 0;
+ int r = font->vmtx_len - 1;
+ int m;
+
+ if (!font->vmtx)
+ goto notfound;
+
+ while (l <= r)
+ {
+ m = (l + r) >> 1;
+ if (cid < font->vmtx[m].lo)
+ r = m - 1;
+ else if (cid > font->vmtx[m].hi)
+ l = m + 1;
+ else
+ return font->vmtx[m];
+ }
+
+notfound:
+ h = pdf_lookup_hmtx(ctx, font, cid);
+ v = font->dvmtx;
+ v.x = h.w / 2;
+ return v;
+}
diff --git a/source/pdf/pdf-nametree.c b/source/pdf/pdf-nametree.c
new file mode 100644
index 00000000..ea386dda
--- /dev/null
+++ b/source/pdf/pdf-nametree.c
@@ -0,0 +1,166 @@
+#include "mupdf/pdf.h"
+
+static pdf_obj *
+pdf_lookup_name_imp(fz_context *ctx, pdf_obj *node, pdf_obj *needle)
+{
+ pdf_obj *kids = pdf_dict_gets(node, "Kids");
+ pdf_obj *names = pdf_dict_gets(node, "Names");
+
+ if (pdf_is_array(kids))
+ {
+ int l = 0;
+ int r = pdf_array_len(kids) - 1;
+
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ pdf_obj *kid = pdf_array_get(kids, m);
+ pdf_obj *limits = pdf_dict_gets(kid, "Limits");
+ pdf_obj *first = pdf_array_get(limits, 0);
+ pdf_obj *last = pdf_array_get(limits, 1);
+
+ if (pdf_objcmp(needle, first) < 0)
+ r = m - 1;
+ else if (pdf_objcmp(needle, last) > 0)
+ l = m + 1;
+ else
+ {
+ pdf_obj *obj;
+
+ if (pdf_obj_mark(node))
+ break;
+ obj = pdf_lookup_name_imp(ctx, kid, needle);
+ pdf_obj_unmark(node);
+ return obj;
+ }
+ }
+ }
+
+ if (pdf_is_array(names))
+ {
+ int l = 0;
+ int r = (pdf_array_len(names) / 2) - 1;
+
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c;
+ pdf_obj *key = pdf_array_get(names, m * 2);
+ pdf_obj *val = pdf_array_get(names, m * 2 + 1);
+
+ c = pdf_objcmp(needle, key);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return val;
+ }
+
+ /* Spec says names should be sorted (hence the binary search,
+ * above), but Acrobat copes with non-sorted. Drop back to a
+ * simple search if the binary search fails. */
+ r = pdf_array_len(names)/2;
+ for (l = 0; l < r; l++)
+ if (!pdf_objcmp(needle, pdf_array_get(names, l * 2)))
+ return pdf_array_get(names, l * 2 + 1);
+ }
+
+ return NULL;
+}
+
+pdf_obj *
+pdf_lookup_name(pdf_document *xref, char *which, pdf_obj *needle)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_obj *root = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pdf_obj *names = pdf_dict_gets(root, "Names");
+ pdf_obj *tree = pdf_dict_gets(names, which);
+ return pdf_lookup_name_imp(ctx, tree, needle);
+}
+
+pdf_obj *
+pdf_lookup_dest(pdf_document *xref, pdf_obj *needle)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_obj *root = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pdf_obj *dests = pdf_dict_gets(root, "Dests");
+ pdf_obj *names = pdf_dict_gets(root, "Names");
+ pdf_obj *dest = NULL;
+
+ /* PDF 1.1 has destinations in a dictionary */
+ if (dests)
+ {
+ if (pdf_is_name(needle))
+ return pdf_dict_get(dests, needle);
+ else
+ return pdf_dict_gets(dests, pdf_to_str_buf(needle));
+ }
+
+ /* PDF 1.2 has destinations in a name tree */
+ if (names && !dest)
+ {
+ pdf_obj *tree = pdf_dict_gets(names, "Dests");
+ return pdf_lookup_name_imp(ctx, tree, needle);
+ }
+
+ return NULL;
+}
+
+static void
+pdf_load_name_tree_imp(pdf_obj *dict, pdf_document *xref, pdf_obj *node)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *kids = pdf_dict_gets(node, "Kids");
+ pdf_obj *names = pdf_dict_gets(node, "Names");
+ int i;
+
+ UNUSED(ctx);
+
+ if (kids && !pdf_obj_mark(node))
+ {
+ int len = pdf_array_len(kids);
+ for (i = 0; i < len; i++)
+ pdf_load_name_tree_imp(dict, xref, pdf_array_get(kids, i));
+ pdf_obj_unmark(node);
+ }
+
+ if (names)
+ {
+ int len = pdf_array_len(names);
+ for (i = 0; i + 1 < len; i += 2)
+ {
+ pdf_obj *key = pdf_array_get(names, i);
+ pdf_obj *val = pdf_array_get(names, i + 1);
+ if (pdf_is_string(key))
+ {
+ key = pdf_to_utf8_name(xref, key);
+ pdf_dict_put(dict, key, val);
+ pdf_drop_obj(key);
+ }
+ else if (pdf_is_name(key))
+ {
+ pdf_dict_put(dict, key, val);
+ }
+ }
+ }
+}
+
+pdf_obj *
+pdf_load_name_tree(pdf_document *xref, char *which)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_obj *root = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pdf_obj *names = pdf_dict_gets(root, "Names");
+ pdf_obj *tree = pdf_dict_gets(names, which);
+ if (pdf_is_dict(tree))
+ {
+ pdf_obj *dict = pdf_new_dict(ctx, 100);
+ pdf_load_name_tree_imp(dict, xref, tree);
+ return dict;
+ }
+ return NULL;
+}
diff --git a/source/pdf/pdf-object.c b/source/pdf/pdf-object.c
new file mode 100644
index 00000000..8e5cf419
--- /dev/null
+++ b/source/pdf/pdf-object.c
@@ -0,0 +1,1576 @@
+#include "mupdf/pdf.h"
+
+typedef enum pdf_objkind_e
+{
+ PDF_NULL = 0,
+ PDF_BOOL = 'b',
+ PDF_INT = 'i',
+ PDF_REAL = 'f',
+ PDF_STRING = 's',
+ PDF_NAME = 'n',
+ PDF_ARRAY = 'a',
+ PDF_DICT = 'd',
+ PDF_INDIRECT = 'r'
+} pdf_objkind;
+
+struct keyval
+{
+ pdf_obj *k;
+ pdf_obj *v;
+};
+
+struct pdf_obj_s
+{
+ int refs;
+ char kind;
+ char marked;
+ fz_context *ctx;
+ union
+ {
+ int b;
+ int i;
+ float f;
+ struct {
+ unsigned short len;
+ char buf[1];
+ } s;
+ char n[1];
+ struct {
+ int len;
+ int cap;
+ pdf_obj **items;
+ } a;
+ struct {
+ char sorted;
+ int len;
+ int cap;
+ struct keyval *items;
+ } d;
+ struct {
+ int num;
+ int gen;
+ pdf_document *xref;
+ } r;
+ } u;
+};
+
+pdf_obj *
+pdf_new_null(fz_context *ctx)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(null)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_NULL;
+ obj->marked = 0;
+ return obj;
+}
+
+pdf_obj *
+pdf_new_bool(fz_context *ctx, int b)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(bool)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_BOOL;
+ obj->marked = 0;
+ obj->u.b = b;
+ return obj;
+}
+
+pdf_obj *
+pdf_new_int(fz_context *ctx, int i)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(int)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_INT;
+ obj->marked = 0;
+ obj->u.i = i;
+ return obj;
+}
+
+pdf_obj *
+pdf_new_real(fz_context *ctx, float f)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(real)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_REAL;
+ obj->marked = 0;
+ obj->u.f = f;
+ return obj;
+}
+
+pdf_obj *
+pdf_new_string(fz_context *ctx, const char *str, int len)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, offsetof(pdf_obj, u.s.buf) + len + 1), "pdf_obj(string)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_STRING;
+ obj->marked = 0;
+ obj->u.s.len = len;
+ memcpy(obj->u.s.buf, str, len);
+ obj->u.s.buf[len] = '\0';
+ return obj;
+}
+
+pdf_obj *
+pdf_new_name(fz_context *ctx, const char *str)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, offsetof(pdf_obj, u.n) + strlen(str) + 1), "pdf_obj(name)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_NAME;
+ obj->marked = 0;
+ strcpy(obj->u.n, str);
+ return obj;
+}
+
+pdf_obj *
+pdf_new_indirect(fz_context *ctx, int num, int gen, void *xref)
+{
+ pdf_obj *obj;
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(indirect)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_INDIRECT;
+ obj->marked = 0;
+ obj->u.r.num = num;
+ obj->u.r.gen = gen;
+ obj->u.r.xref = xref;
+ return obj;
+}
+
+pdf_obj *
+pdf_keep_obj(pdf_obj *obj)
+{
+ if (obj)
+ obj->refs ++;
+ return obj;
+}
+
+int pdf_is_indirect(pdf_obj *obj)
+{
+ return obj ? obj->kind == PDF_INDIRECT : 0;
+}
+
+#define RESOLVE(obj) \
+ do { \
+ if (obj && obj->kind == PDF_INDIRECT) \
+ {\
+ obj = pdf_resolve_indirect(obj); \
+ } \
+ } while (0)
+
+int pdf_is_null(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_NULL : 0;
+}
+
+int pdf_is_bool(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_BOOL : 0;
+}
+
+int pdf_is_int(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_INT : 0;
+}
+
+int pdf_is_real(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_REAL : 0;
+}
+
+int pdf_is_string(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_STRING : 0;
+}
+
+int pdf_is_name(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_NAME : 0;
+}
+
+int pdf_is_array(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_ARRAY : 0;
+}
+
+int pdf_is_dict(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return obj ? obj->kind == PDF_DICT : 0;
+}
+
+int pdf_to_bool(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj)
+ return 0;
+ return obj->kind == PDF_BOOL ? obj->u.b : 0;
+}
+
+int pdf_to_int(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj)
+ return 0;
+ if (obj->kind == PDF_INT)
+ return obj->u.i;
+ if (obj->kind == PDF_REAL)
+ return (int)(obj->u.f + 0.5f); /* No roundf in MSVC */
+ return 0;
+}
+
+float pdf_to_real(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj)
+ return 0;
+ if (obj->kind == PDF_REAL)
+ return obj->u.f;
+ if (obj->kind == PDF_INT)
+ return obj->u.i;
+ return 0;
+}
+
+char *pdf_to_name(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_NAME)
+ return "";
+ return obj->u.n;
+}
+
+char *pdf_to_str_buf(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_STRING)
+ return "";
+ return obj->u.s.buf;
+}
+
+int pdf_to_str_len(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_STRING)
+ return 0;
+ return obj->u.s.len;
+}
+
+void pdf_set_int(pdf_obj *obj, int i)
+{
+ if (!obj || obj->kind != PDF_INT)
+ return;
+ obj->u.i = i;
+}
+
+/* for use by pdf_crypt_obj_imp to decrypt AES string in place */
+void pdf_set_str_len(pdf_obj *obj, int newlen)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_STRING)
+ return; /* This should never happen */
+ if (newlen > obj->u.s.len)
+ return; /* This should never happen */
+ obj->u.s.len = newlen;
+}
+
+pdf_obj *pdf_to_dict(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ return (obj && obj->kind == PDF_DICT ? obj : NULL);
+}
+
+int pdf_to_num(pdf_obj *obj)
+{
+ if (!obj || obj->kind != PDF_INDIRECT)
+ return 0;
+ return obj->u.r.num;
+}
+
+int pdf_to_gen(pdf_obj *obj)
+{
+ if (!obj || obj->kind != PDF_INDIRECT)
+ return 0;
+ return obj->u.r.gen;
+}
+
+pdf_document *pdf_get_indirect_document(pdf_obj *obj)
+{
+ if (!obj || obj->kind != PDF_INDIRECT)
+ return NULL;
+ return obj->u.r.xref;
+}
+
+int
+pdf_objcmp(pdf_obj *a, pdf_obj *b)
+{
+ int i;
+
+ if (a == b)
+ return 0;
+
+ if (!a || !b)
+ return 1;
+
+ if (a->kind != b->kind)
+ return 1;
+
+ switch (a->kind)
+ {
+ case PDF_NULL:
+ return 0;
+
+ case PDF_BOOL:
+ return a->u.b - b->u.b;
+
+ case PDF_INT:
+ return a->u.i - b->u.i;
+
+ case PDF_REAL:
+ if (a->u.f < b->u.f)
+ return -1;
+ if (a->u.f > b->u.f)
+ return 1;
+ return 0;
+
+ case PDF_STRING:
+ if (a->u.s.len < b->u.s.len)
+ {
+ if (memcmp(a->u.s.buf, b->u.s.buf, a->u.s.len) <= 0)
+ return -1;
+ return 1;
+ }
+ if (a->u.s.len > b->u.s.len)
+ {
+ if (memcmp(a->u.s.buf, b->u.s.buf, b->u.s.len) >= 0)
+ return 1;
+ return -1;
+ }
+ return memcmp(a->u.s.buf, b->u.s.buf, a->u.s.len);
+
+ case PDF_NAME:
+ return strcmp(a->u.n, b->u.n);
+
+ case PDF_INDIRECT:
+ if (a->u.r.num == b->u.r.num)
+ return a->u.r.gen - b->u.r.gen;
+ return a->u.r.num - b->u.r.num;
+
+ case PDF_ARRAY:
+ if (a->u.a.len != b->u.a.len)
+ return a->u.a.len - b->u.a.len;
+ for (i = 0; i < a->u.a.len; i++)
+ if (pdf_objcmp(a->u.a.items[i], b->u.a.items[i]))
+ return 1;
+ return 0;
+
+ case PDF_DICT:
+ if (a->u.d.len != b->u.d.len)
+ return a->u.d.len - b->u.d.len;
+ for (i = 0; i < a->u.d.len; i++)
+ {
+ if (pdf_objcmp(a->u.d.items[i].k, b->u.d.items[i].k))
+ return 1;
+ if (pdf_objcmp(a->u.d.items[i].v, b->u.d.items[i].v))
+ return 1;
+ }
+ return 0;
+
+ }
+ return 1;
+}
+
+static char *
+pdf_objkindstr(pdf_obj *obj)
+{
+ if (!obj)
+ return "<NULL>";
+ switch (obj->kind)
+ {
+ case PDF_NULL: return "null";
+ case PDF_BOOL: return "boolean";
+ case PDF_INT: return "integer";
+ case PDF_REAL: return "real";
+ case PDF_STRING: return "string";
+ case PDF_NAME: return "name";
+ case PDF_ARRAY: return "array";
+ case PDF_DICT: return "dictionary";
+ case PDF_INDIRECT: return "reference";
+ }
+ return "<unknown>";
+}
+
+pdf_obj *
+pdf_new_array(fz_context *ctx, int initialcap)
+{
+ pdf_obj *obj;
+ int i;
+
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(array)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_ARRAY;
+ obj->marked = 0;
+
+ obj->u.a.len = 0;
+ obj->u.a.cap = initialcap > 1 ? initialcap : 6;
+
+ fz_try(ctx)
+ {
+ obj->u.a.items = Memento_label(fz_malloc_array(ctx, obj->u.a.cap, sizeof(pdf_obj*)), "pdf_obj(array items)");
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, obj);
+ fz_rethrow(ctx);
+ }
+ for (i = 0; i < obj->u.a.cap; i++)
+ obj->u.a.items[i] = NULL;
+
+ return obj;
+}
+
+static void
+pdf_array_grow(pdf_obj *obj)
+{
+ int i;
+ int new_cap = (obj->u.a.cap * 3) / 2;
+
+ obj->u.a.items = fz_resize_array(obj->ctx, obj->u.a.items, new_cap, sizeof(pdf_obj*));
+ obj->u.a.cap = new_cap;
+
+ for (i = obj->u.a.len ; i < obj->u.a.cap; i++)
+ obj->u.a.items[i] = NULL;
+}
+
+pdf_obj *
+pdf_copy_array(fz_context *ctx, pdf_obj *obj)
+{
+ pdf_obj *arr;
+ int i;
+ int n;
+
+ RESOLVE(obj);
+ if (!obj)
+ return NULL; /* Can't warn :( */
+ if (obj->kind != PDF_ARRAY)
+ fz_warn(ctx, "assert: not an array (%s)", pdf_objkindstr(obj));
+
+ arr = pdf_new_array(ctx, pdf_array_len(obj));
+ n = pdf_array_len(obj);
+ for (i = 0; i < n; i++)
+ pdf_array_push(arr, pdf_array_get(obj, i));
+
+ return arr;
+}
+
+int
+pdf_array_len(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_ARRAY)
+ return 0;
+ return obj->u.a.len;
+}
+
+pdf_obj *
+pdf_array_get(pdf_obj *obj, int i)
+{
+ RESOLVE(obj);
+
+ if (!obj || obj->kind != PDF_ARRAY)
+ return NULL;
+
+ if (i < 0 || i >= obj->u.a.len)
+ return NULL;
+
+ return obj->u.a.items[i];
+}
+
+void
+pdf_array_put(pdf_obj *obj, int i, pdf_obj *item)
+{
+ RESOLVE(obj);
+
+ if (!obj)
+ return; /* Can't warn :( */
+ if (obj->kind != PDF_ARRAY)
+ fz_warn(obj->ctx, "assert: not an array (%s)", pdf_objkindstr(obj));
+ else if (i < 0)
+ fz_warn(obj->ctx, "assert: index %d < 0", i);
+ else if (i >= obj->u.a.len)
+ fz_warn(obj->ctx, "assert: index %d > length %d", i, obj->u.a.len);
+ else
+ {
+ pdf_drop_obj(obj->u.a.items[i]);
+ obj->u.a.items[i] = pdf_keep_obj(item);
+ }
+}
+
+void
+pdf_array_push(pdf_obj *obj, pdf_obj *item)
+{
+ RESOLVE(obj);
+
+ if (!obj)
+ return; /* Can't warn :( */
+ if (obj->kind != PDF_ARRAY)
+ fz_warn(obj->ctx, "assert: not an array (%s)", pdf_objkindstr(obj));
+ else
+ {
+ if (obj->u.a.len + 1 > obj->u.a.cap)
+ pdf_array_grow(obj);
+ obj->u.a.items[obj->u.a.len] = pdf_keep_obj(item);
+ obj->u.a.len++;
+ }
+}
+
+void
+pdf_array_push_drop(pdf_obj *obj, pdf_obj *item)
+{
+ fz_context *ctx = obj->ctx;
+
+ fz_try(ctx)
+ {
+ pdf_array_push(obj, item);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(item);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_array_insert(pdf_obj *obj, pdf_obj *item)
+{
+ RESOLVE(obj);
+
+ if (!obj)
+ return; /* Can't warn :( */
+ if (obj->kind != PDF_ARRAY)
+ fz_warn(obj->ctx, "assert: not an array (%s)", pdf_objkindstr(obj));
+ else
+ {
+ if (obj->u.a.len + 1 > obj->u.a.cap)
+ pdf_array_grow(obj);
+ memmove(obj->u.a.items + 1, obj->u.a.items, obj->u.a.len * sizeof(pdf_obj*));
+ obj->u.a.items[0] = pdf_keep_obj(item);
+ obj->u.a.len++;
+ }
+}
+
+int
+pdf_array_contains(pdf_obj *arr, pdf_obj *obj)
+{
+ int i, len;
+
+ len = pdf_array_len(arr);
+ for (i = 0; i < len; i++)
+ if (!pdf_objcmp(pdf_array_get(arr, i), obj))
+ return 1;
+
+ return 0;
+}
+
+pdf_obj *pdf_new_rect(fz_context *ctx, const fz_rect *rect)
+{
+ pdf_obj *arr = NULL;
+ pdf_obj *item = NULL;
+
+ fz_var(arr);
+ fz_var(item);
+ fz_try(ctx)
+ {
+ arr = pdf_new_array(ctx, 4);
+
+ item = pdf_new_real(ctx, rect->x0);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, rect->y0);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, rect->x1);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, rect->y1);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(item);
+ pdf_drop_obj(arr);
+ fz_rethrow(ctx);
+ }
+
+ return arr;
+}
+
+pdf_obj *pdf_new_matrix(fz_context *ctx, const fz_matrix *mtx)
+{
+ pdf_obj *arr = NULL;
+ pdf_obj *item = NULL;
+
+ fz_var(arr);
+ fz_var(item);
+ fz_try(ctx)
+ {
+ arr = pdf_new_array(ctx, 6);
+
+ item = pdf_new_real(ctx, mtx->a);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, mtx->b);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, mtx->c);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, mtx->d);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, mtx->e);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+
+ item = pdf_new_real(ctx, mtx->f);
+ pdf_array_push(arr, item);
+ pdf_drop_obj(item);
+ item = NULL;
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(item);
+ pdf_drop_obj(arr);
+ fz_rethrow(ctx);
+ }
+
+ return arr;
+}
+
+/* dicts may only have names as keys! */
+
+static int keyvalcmp(const void *ap, const void *bp)
+{
+ const struct keyval *a = ap;
+ const struct keyval *b = bp;
+ return strcmp(pdf_to_name(a->k), pdf_to_name(b->k));
+}
+
+pdf_obj *
+pdf_new_dict(fz_context *ctx, int initialcap)
+{
+ pdf_obj *obj;
+ int i;
+
+ obj = Memento_label(fz_malloc(ctx, sizeof(pdf_obj)), "pdf_obj(dict)");
+ obj->ctx = ctx;
+ obj->refs = 1;
+ obj->kind = PDF_DICT;
+ obj->marked = 0;
+
+ obj->u.d.sorted = 0;
+ obj->u.d.len = 0;
+ obj->u.d.cap = initialcap > 1 ? initialcap : 10;
+
+ fz_try(ctx)
+ {
+ obj->u.d.items = Memento_label(fz_malloc_array(ctx, obj->u.d.cap, sizeof(struct keyval)), "pdf_obj(dict items)");
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, obj);
+ fz_rethrow(ctx);
+ }
+ for (i = 0; i < obj->u.d.cap; i++)
+ {
+ obj->u.d.items[i].k = NULL;
+ obj->u.d.items[i].v = NULL;
+ }
+
+ return obj;
+}
+
+static void
+pdf_dict_grow(pdf_obj *obj)
+{
+ int i;
+ int new_cap = (obj->u.d.cap * 3) / 2;
+
+ obj->u.d.items = fz_resize_array(obj->ctx, obj->u.d.items, new_cap, sizeof(struct keyval));
+ obj->u.d.cap = new_cap;
+
+ for (i = obj->u.d.len; i < obj->u.d.cap; i++)
+ {
+ obj->u.d.items[i].k = NULL;
+ obj->u.d.items[i].v = NULL;
+ }
+}
+
+pdf_obj *
+pdf_copy_dict(fz_context *ctx, pdf_obj *obj)
+{
+ pdf_obj *dict;
+ int i, n;
+
+ RESOLVE(obj);
+ if (!obj)
+ return NULL; /* Can't warn :( */
+ if (obj->kind != PDF_DICT)
+ fz_warn(ctx, "assert: not a dict (%s)", pdf_objkindstr(obj));
+
+ n = pdf_dict_len(obj);
+ dict = pdf_new_dict(ctx, n);
+ for (i = 0; i < n; i++)
+ pdf_dict_put(dict, pdf_dict_get_key(obj, i), pdf_dict_get_val(obj, i));
+
+ return dict;
+}
+
+int
+pdf_dict_len(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_DICT)
+ return 0;
+ return obj->u.d.len;
+}
+
+pdf_obj *
+pdf_dict_get_key(pdf_obj *obj, int i)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_DICT)
+ return NULL;
+
+ if (i < 0 || i >= obj->u.d.len)
+ return NULL;
+
+ return obj->u.d.items[i].k;
+}
+
+pdf_obj *
+pdf_dict_get_val(pdf_obj *obj, int i)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_DICT)
+ return NULL;
+
+ if (i < 0 || i >= obj->u.d.len)
+ return NULL;
+
+ return obj->u.d.items[i].v;
+}
+
+static int
+pdf_dict_finds(pdf_obj *obj, const char *key, int *location)
+{
+ if (obj->u.d.sorted && obj->u.d.len > 0)
+ {
+ int l = 0;
+ int r = obj->u.d.len - 1;
+
+ if (strcmp(pdf_to_name(obj->u.d.items[r].k), key) < 0)
+ {
+ if (location)
+ *location = r + 1;
+ return -1;
+ }
+
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = -strcmp(pdf_to_name(obj->u.d.items[m].k), key);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return m;
+
+ if (location)
+ *location = l;
+ }
+ }
+
+ else
+ {
+ int i;
+ for (i = 0; i < obj->u.d.len; i++)
+ if (strcmp(pdf_to_name(obj->u.d.items[i].k), key) == 0)
+ return i;
+
+ if (location)
+ *location = obj->u.d.len;
+ }
+
+ return -1;
+}
+
+pdf_obj *
+pdf_dict_gets(pdf_obj *obj, const char *key)
+{
+ int i;
+
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_DICT)
+ return NULL;
+
+ i = pdf_dict_finds(obj, key, NULL);
+ if (i >= 0)
+ return obj->u.d.items[i].v;
+
+ return NULL;
+}
+
+pdf_obj *
+pdf_dict_getp(pdf_obj *obj, const char *keys)
+{
+ char buf[256];
+ char *k, *e;
+
+ if (strlen(keys)+1 > 256)
+ fz_throw(obj->ctx, FZ_ERROR_GENERIC, "buffer overflow in pdf_dict_getp");
+
+ strcpy(buf, keys);
+
+ e = buf;
+ while (*e && obj)
+ {
+ k = e;
+ while (*e != '/' && *e != '\0')
+ e++;
+
+ if (*e == '/')
+ {
+ *e = '\0';
+ e++;
+ }
+
+ obj = pdf_dict_gets(obj, k);
+ }
+
+ return obj;
+}
+
+pdf_obj *
+pdf_dict_get(pdf_obj *obj, pdf_obj *key)
+{
+ if (!key || key->kind != PDF_NAME)
+ return NULL;
+ return pdf_dict_gets(obj, pdf_to_name(key));
+}
+
+pdf_obj *
+pdf_dict_getsa(pdf_obj *obj, const char *key, const char *abbrev)
+{
+ pdf_obj *v;
+ v = pdf_dict_gets(obj, key);
+ if (v)
+ return v;
+ return pdf_dict_gets(obj, abbrev);
+}
+
+void
+pdf_dict_put(pdf_obj *obj, pdf_obj *key, pdf_obj *val)
+{
+ int location;
+ char *s;
+ int i;
+
+ RESOLVE(obj);
+ if (!obj)
+ return; /* Can't warn :( */
+ if (obj->kind != PDF_DICT)
+ {
+ fz_warn(obj->ctx, "assert: not a dict (%s)", pdf_objkindstr(obj));
+ return;
+ }
+
+ RESOLVE(key);
+ if (!key || key->kind != PDF_NAME)
+ {
+ fz_warn(obj->ctx, "assert: key is not a name (%s)", pdf_objkindstr(obj));
+ return;
+ }
+ else
+ s = pdf_to_name(key);
+
+ if (!val)
+ {
+ fz_warn(obj->ctx, "assert: val does not exist for key (%s)", s);
+ return;
+ }
+
+ if (obj->u.d.len > 100 && !obj->u.d.sorted)
+ pdf_sort_dict(obj);
+
+ i = pdf_dict_finds(obj, s, &location);
+ if (i >= 0 && i < obj->u.d.len)
+ {
+ if (obj->u.d.items[i].v != val)
+ {
+ pdf_drop_obj(obj->u.d.items[i].v);
+ obj->u.d.items[i].v = pdf_keep_obj(val);
+ }
+ }
+ else
+ {
+ if (obj->u.d.len + 1 > obj->u.d.cap)
+ pdf_dict_grow(obj);
+
+ i = location;
+ if (obj->u.d.sorted && obj->u.d.len > 0)
+ memmove(&obj->u.d.items[i + 1],
+ &obj->u.d.items[i],
+ (obj->u.d.len - i) * sizeof(struct keyval));
+
+ obj->u.d.items[i].k = pdf_keep_obj(key);
+ obj->u.d.items[i].v = pdf_keep_obj(val);
+ obj->u.d.len ++;
+ }
+}
+
+void
+pdf_dict_puts(pdf_obj *obj, const char *key, pdf_obj *val)
+{
+ fz_context *ctx = obj->ctx;
+ pdf_obj *keyobj = pdf_new_name(ctx, key);
+
+ fz_try(ctx)
+ {
+ pdf_dict_put(obj, keyobj, val);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(keyobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_dict_puts_drop(pdf_obj *obj, const char *key, pdf_obj *val)
+{
+ fz_context *ctx = obj->ctx;
+ pdf_obj *keyobj = NULL;
+
+ fz_var(keyobj);
+
+ fz_try(ctx)
+ {
+ keyobj = pdf_new_name(ctx, key);
+ pdf_dict_put(obj, keyobj, val);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(keyobj);
+ pdf_drop_obj(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_dict_putp(pdf_obj *obj, const char *keys, pdf_obj *val)
+{
+ fz_context *ctx = obj->ctx;
+ char buf[256];
+ char *k, *e;
+ pdf_obj *cobj = NULL;
+
+ if (strlen(keys)+1 > 256)
+ fz_throw(obj->ctx, FZ_ERROR_GENERIC, "buffer overflow in pdf_dict_getp");
+
+ strcpy(buf, keys);
+
+ e = buf;
+ while (*e)
+ {
+ k = e;
+ while (*e != '/' && *e != '\0')
+ e++;
+
+ if (*e == '/')
+ {
+ *e = '\0';
+ e++;
+ }
+
+ if (*e)
+ {
+ /* Not the last key in the key path. Create subdict if not already there. */
+ cobj = pdf_dict_gets(obj, k);
+ if (cobj == NULL)
+ {
+ cobj = pdf_new_dict(ctx, 1);
+ fz_try(ctx)
+ {
+ pdf_dict_puts(obj, k, cobj);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(cobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ /* Move to subdict */
+ obj = cobj;
+ }
+ else
+ {
+ /* Last key. Use it to store the value */
+ /* Use val = NULL to request delete */
+ if (val)
+ pdf_dict_puts(obj, k, val);
+ else
+ pdf_dict_dels(obj, k);
+ }
+ }
+}
+
+void
+pdf_dict_putp_drop(pdf_obj *obj, const char *keys, pdf_obj *val)
+{
+ fz_context *ctx = obj->ctx;
+
+ fz_try(ctx)
+ {
+ pdf_dict_putp(obj, keys, val);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_dict_dels(pdf_obj *obj, const char *key)
+{
+ RESOLVE(obj);
+
+ if (!obj)
+ return; /* Can't warn :( */
+ if (obj->kind != PDF_DICT)
+ fz_warn(obj->ctx, "assert: not a dict (%s)", pdf_objkindstr(obj));
+ else
+ {
+ int i = pdf_dict_finds(obj, key, NULL);
+ if (i >= 0)
+ {
+ pdf_drop_obj(obj->u.d.items[i].k);
+ pdf_drop_obj(obj->u.d.items[i].v);
+ obj->u.d.sorted = 0;
+ obj->u.d.items[i] = obj->u.d.items[obj->u.d.len-1];
+ obj->u.d.len --;
+ }
+ }
+}
+
+void
+pdf_dict_del(pdf_obj *obj, pdf_obj *key)
+{
+ RESOLVE(key);
+ if (!key || key->kind != PDF_NAME)
+ fz_warn(obj->ctx, "assert: key is not a name (%s)", pdf_objkindstr(obj));
+ else
+ pdf_dict_dels(obj, key->u.n);
+}
+
+void
+pdf_sort_dict(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj || obj->kind != PDF_DICT)
+ return;
+ if (!obj->u.d.sorted)
+ {
+ qsort(obj->u.d.items, obj->u.d.len, sizeof(struct keyval), keyvalcmp);
+ obj->u.d.sorted = 1;
+ }
+}
+
+int
+pdf_obj_marked(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj)
+ return 0;
+ return obj->marked;
+}
+
+int
+pdf_obj_mark(pdf_obj *obj)
+{
+ int marked;
+ RESOLVE(obj);
+ if (!obj)
+ return 0;
+ marked = obj->marked;
+ obj->marked = 1;
+ return marked;
+}
+
+void
+pdf_obj_unmark(pdf_obj *obj)
+{
+ RESOLVE(obj);
+ if (!obj)
+ return;
+ obj->marked = 0;
+}
+
+static void
+pdf_free_array(pdf_obj *obj)
+{
+ int i;
+
+ for (i = 0; i < obj->u.a.len; i++)
+ pdf_drop_obj(obj->u.a.items[i]);
+
+ fz_free(obj->ctx, obj->u.a.items);
+ fz_free(obj->ctx, obj);
+}
+
+static void
+pdf_free_dict(pdf_obj *obj)
+{
+ int i;
+
+ for (i = 0; i < obj->u.d.len; i++) {
+ pdf_drop_obj(obj->u.d.items[i].k);
+ pdf_drop_obj(obj->u.d.items[i].v);
+ }
+
+ fz_free(obj->ctx, obj->u.d.items);
+ fz_free(obj->ctx, obj);
+}
+
+void
+pdf_drop_obj(pdf_obj *obj)
+{
+ if (!obj)
+ return;
+ if (--obj->refs)
+ return;
+ if (obj->kind == PDF_ARRAY)
+ pdf_free_array(obj);
+ else if (obj->kind == PDF_DICT)
+ pdf_free_dict(obj);
+ else
+ fz_free(obj->ctx, obj);
+}
+
+pdf_obj *pdf_new_obj_from_str(fz_context *ctx, const char *src)
+{
+ pdf_obj *result;
+ pdf_lexbuf lexbuf;
+ fz_stream *stream = fz_open_memory(ctx, (unsigned char *)src, strlen(src));
+
+ pdf_lexbuf_init(ctx, &lexbuf, PDF_LEXBUF_SMALL);
+ fz_try(ctx)
+ {
+ result = pdf_parse_stm_obj(NULL, stream, &lexbuf);
+ }
+ fz_always(ctx)
+ {
+ pdf_lexbuf_fin(&lexbuf);
+ fz_close(stream);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ return NULL;
+ }
+
+ return result;
+}
+
+/* Pretty printing objects */
+
+struct fmt
+{
+ char *buf;
+ int cap;
+ int len;
+ int indent;
+ int tight;
+ int col;
+ int sep;
+ int last;
+};
+
+static void fmt_obj(struct fmt *fmt, pdf_obj *obj);
+
+static inline int iswhite(int ch)
+{
+ return
+ ch == '\000' ||
+ ch == '\011' ||
+ ch == '\012' ||
+ ch == '\014' ||
+ ch == '\015' ||
+ ch == '\040';
+}
+
+static inline int isdelim(int ch)
+{
+ return
+ ch == '(' || ch == ')' ||
+ ch == '<' || ch == '>' ||
+ ch == '[' || ch == ']' ||
+ ch == '{' || ch == '}' ||
+ ch == '/' ||
+ ch == '%';
+}
+
+static inline void fmt_putc(struct fmt *fmt, int c)
+{
+ if (fmt->sep && !isdelim(fmt->last) && !isdelim(c)) {
+ fmt->sep = 0;
+ fmt_putc(fmt, ' ');
+ }
+ fmt->sep = 0;
+
+ if (fmt->buf && fmt->len < fmt->cap)
+ fmt->buf[fmt->len] = c;
+
+ if (c == '\n')
+ fmt->col = 0;
+ else
+ fmt->col ++;
+
+ fmt->len ++;
+
+ fmt->last = c;
+}
+
+static inline void fmt_indent(struct fmt *fmt)
+{
+ int i = fmt->indent;
+ while (i--) {
+ fmt_putc(fmt, ' ');
+ fmt_putc(fmt, ' ');
+ }
+}
+
+static inline void fmt_puts(struct fmt *fmt, char *s)
+{
+ while (*s)
+ fmt_putc(fmt, *s++);
+}
+
+static inline void fmt_sep(struct fmt *fmt)
+{
+ fmt->sep = 1;
+}
+
+static void fmt_str(struct fmt *fmt, pdf_obj *obj)
+{
+ char *s = pdf_to_str_buf(obj);
+ int n = pdf_to_str_len(obj);
+ int i, c;
+
+ fmt_putc(fmt, '(');
+ for (i = 0; i < n; i++)
+ {
+ c = (unsigned char)s[i];
+ if (c == '\n')
+ fmt_puts(fmt, "\\n");
+ else if (c == '\r')
+ fmt_puts(fmt, "\\r");
+ else if (c == '\t')
+ fmt_puts(fmt, "\\t");
+ else if (c == '\b')
+ fmt_puts(fmt, "\\b");
+ else if (c == '\f')
+ fmt_puts(fmt, "\\f");
+ else if (c == '(')
+ fmt_puts(fmt, "\\(");
+ else if (c == ')')
+ fmt_puts(fmt, "\\)");
+ else if (c == '\\')
+ fmt_puts(fmt, "\\\\");
+ else if (c < 32 || c >= 127) {
+ char buf[16];
+ fmt_putc(fmt, '\\');
+ sprintf(buf, "%03o", c);
+ fmt_puts(fmt, buf);
+ }
+ else
+ fmt_putc(fmt, c);
+ }
+ fmt_putc(fmt, ')');
+}
+
+static void fmt_hex(struct fmt *fmt, pdf_obj *obj)
+{
+ char *s = pdf_to_str_buf(obj);
+ int n = pdf_to_str_len(obj);
+ int i, b, c;
+
+ fmt_putc(fmt, '<');
+ for (i = 0; i < n; i++) {
+ b = (unsigned char) s[i];
+ c = (b >> 4) & 0x0f;
+ fmt_putc(fmt, c < 0xA ? c + '0' : c + 'A' - 0xA);
+ c = (b) & 0x0f;
+ fmt_putc(fmt, c < 0xA ? c + '0' : c + 'A' - 0xA);
+ }
+ fmt_putc(fmt, '>');
+}
+
+static void fmt_name(struct fmt *fmt, pdf_obj *obj)
+{
+ unsigned char *s = (unsigned char *) pdf_to_name(obj);
+ int i, c;
+
+ fmt_putc(fmt, '/');
+
+ for (i = 0; s[i]; i++)
+ {
+ if (isdelim(s[i]) || iswhite(s[i]) ||
+ s[i] == '#' || s[i] < 32 || s[i] >= 127)
+ {
+ fmt_putc(fmt, '#');
+ c = (s[i] >> 4) & 0xf;
+ fmt_putc(fmt, c < 0xA ? c + '0' : c + 'A' - 0xA);
+ c = s[i] & 0xf;
+ fmt_putc(fmt, c < 0xA ? c + '0' : c + 'A' - 0xA);
+ }
+ else
+ {
+ fmt_putc(fmt, s[i]);
+ }
+ }
+}
+
+static void fmt_array(struct fmt *fmt, pdf_obj *obj)
+{
+ int i, n;
+
+ n = pdf_array_len(obj);
+ if (fmt->tight) {
+ fmt_putc(fmt, '[');
+ for (i = 0; i < n; i++) {
+ fmt_obj(fmt, pdf_array_get(obj, i));
+ fmt_sep(fmt);
+ }
+ fmt_putc(fmt, ']');
+ }
+ else {
+ fmt_puts(fmt, "[ ");
+ for (i = 0; i < n; i++) {
+ if (fmt->col > 60) {
+ fmt_putc(fmt, '\n');
+ fmt_indent(fmt);
+ }
+ fmt_obj(fmt, pdf_array_get(obj, i));
+ fmt_putc(fmt, ' ');
+ }
+ fmt_putc(fmt, ']');
+ fmt_sep(fmt);
+ }
+}
+
+static void fmt_dict(struct fmt *fmt, pdf_obj *obj)
+{
+ int i, n;
+ pdf_obj *key, *val;
+
+ n = pdf_dict_len(obj);
+ if (fmt->tight) {
+ fmt_puts(fmt, "<<");
+ for (i = 0; i < n; i++) {
+ fmt_obj(fmt, pdf_dict_get_key(obj, i));
+ fmt_sep(fmt);
+ fmt_obj(fmt, pdf_dict_get_val(obj, i));
+ fmt_sep(fmt);
+ }
+ fmt_puts(fmt, ">>");
+ }
+ else {
+ fmt_puts(fmt, "<<\n");
+ fmt->indent ++;
+ for (i = 0; i < n; i++) {
+ key = pdf_dict_get_key(obj, i);
+ val = pdf_dict_get_val(obj, i);
+ fmt_indent(fmt);
+ fmt_obj(fmt, key);
+ fmt_putc(fmt, ' ');
+ if (!pdf_is_indirect(val) && pdf_is_array(val))
+ fmt->indent ++;
+ fmt_obj(fmt, val);
+ fmt_putc(fmt, '\n');
+ if (!pdf_is_indirect(val) && pdf_is_array(val))
+ fmt->indent --;
+ }
+ fmt->indent --;
+ fmt_indent(fmt);
+ fmt_puts(fmt, ">>");
+ }
+}
+
+static void fmt_obj(struct fmt *fmt, pdf_obj *obj)
+{
+ char buf[256];
+
+ if (!obj)
+ fmt_puts(fmt, "<NULL>");
+ else if (pdf_is_indirect(obj))
+ {
+ sprintf(buf, "%d %d R", pdf_to_num(obj), pdf_to_gen(obj));
+ fmt_puts(fmt, buf);
+ }
+ else if (pdf_is_null(obj))
+ fmt_puts(fmt, "null");
+ else if (pdf_is_bool(obj))
+ fmt_puts(fmt, pdf_to_bool(obj) ? "true" : "false");
+ else if (pdf_is_int(obj))
+ {
+ sprintf(buf, "%d", pdf_to_int(obj));
+ fmt_puts(fmt, buf);
+ }
+ else if (pdf_is_real(obj))
+ {
+ sprintf(buf, "%1.9g", pdf_to_real(obj));
+ if (strchr(buf, 'e')) /* bad news! */
+ sprintf(buf, fabsf(pdf_to_real(obj)) > 1 ? "%1.1f" : "%1.8f", pdf_to_real(obj));
+ fmt_puts(fmt, buf);
+ }
+ else if (pdf_is_string(obj))
+ {
+ char *str = pdf_to_str_buf(obj);
+ int len = pdf_to_str_len(obj);
+ int added = 0;
+ int i, c;
+ for (i = 0; i < len; i++) {
+ c = (unsigned char)str[i];
+ if (strchr("()\\\n\r\t\b\f", c))
+ added ++;
+ else if (c < 32 || c >= 127)
+ added += 3;
+ }
+ if (added < len)
+ fmt_str(fmt, obj);
+ else
+ fmt_hex(fmt, obj);
+ }
+ else if (pdf_is_name(obj))
+ fmt_name(fmt, obj);
+ else if (pdf_is_array(obj))
+ fmt_array(fmt, obj);
+ else if (pdf_is_dict(obj))
+ fmt_dict(fmt, obj);
+ else
+ fmt_puts(fmt, "<unknown object>");
+}
+
+static int
+pdf_sprint_obj(char *s, int n, pdf_obj *obj, int tight)
+{
+ struct fmt fmt;
+
+ fmt.indent = 0;
+ fmt.col = 0;
+ fmt.sep = 0;
+ fmt.last = 0;
+
+ fmt.tight = tight;
+ fmt.buf = s;
+ fmt.cap = n;
+ fmt.len = 0;
+ fmt_obj(&fmt, obj);
+
+ if (fmt.buf && fmt.len < fmt.cap)
+ fmt.buf[fmt.len] = '\0';
+
+ return fmt.len;
+}
+
+int
+pdf_fprint_obj(FILE *fp, pdf_obj *obj, int tight)
+{
+ char buf[1024];
+ char *ptr;
+ int n;
+
+ n = pdf_sprint_obj(NULL, 0, obj, tight);
+ if ((n + 1) < sizeof buf)
+ {
+ pdf_sprint_obj(buf, sizeof buf, obj, tight);
+ fputs(buf, fp);
+ fputc('\n', fp);
+ }
+ else
+ {
+ ptr = fz_malloc(obj->ctx, n + 1);
+ pdf_sprint_obj(ptr, n + 1, obj, tight);
+ fputs(ptr, fp);
+ fputc('\n', fp);
+ fz_free(obj->ctx, ptr);
+ }
+ return n;
+}
+
+#ifndef NDEBUG
+void
+pdf_print_obj(pdf_obj *obj)
+{
+ pdf_fprint_obj(stdout, obj, 0);
+}
+
+void
+pdf_print_ref(pdf_obj *ref)
+{
+ pdf_print_obj(pdf_resolve_indirect(ref));
+}
+#endif
diff --git a/source/pdf/pdf-outline.c b/source/pdf/pdf-outline.c
new file mode 100644
index 00000000..584d60ea
--- /dev/null
+++ b/source/pdf/pdf-outline.c
@@ -0,0 +1,72 @@
+#include "mupdf/pdf.h"
+
+static fz_outline *
+pdf_load_outline_imp(pdf_document *xref, pdf_obj *dict)
+{
+ fz_context *ctx = xref->ctx;
+ fz_outline *node, **prev, *first;
+ pdf_obj *obj;
+ pdf_obj *odict = dict;
+
+ fz_var(dict);
+ fz_var(first);
+
+ fz_try(ctx)
+ {
+ first = NULL;
+ prev = &first;
+ while (dict && pdf_is_dict(dict))
+ {
+ if (pdf_obj_mark(dict))
+ break;
+ node = fz_malloc_struct(ctx, fz_outline);
+ node->title = NULL;
+ node->dest.kind = FZ_LINK_NONE;
+ node->down = NULL;
+ node->next = NULL;
+ *prev = node;
+ prev = &node->next;
+
+ obj = pdf_dict_gets(dict, "Title");
+ if (obj)
+ node->title = pdf_to_utf8(xref, obj);
+
+ if ((obj = pdf_dict_gets(dict, "Dest")))
+ node->dest = pdf_parse_link_dest(xref, obj);
+ else if ((obj = pdf_dict_gets(dict, "A")))
+ node->dest = pdf_parse_action(xref, obj);
+
+ obj = pdf_dict_gets(dict, "First");
+ if (obj)
+ node->down = pdf_load_outline_imp(xref, obj);
+
+ dict = pdf_dict_gets(dict, "Next");
+ }
+ }
+ fz_always(ctx)
+ {
+ for (dict = odict; dict && pdf_obj_marked(dict); dict = pdf_dict_gets(dict, "Next"))
+ pdf_obj_unmark(dict);
+ }
+ fz_catch(ctx)
+ {
+ fz_free_outline(ctx, first);
+ fz_rethrow(ctx);
+ }
+
+ return first;
+}
+
+fz_outline *
+pdf_load_outline(pdf_document *xref)
+{
+ pdf_obj *root, *obj, *first;
+
+ root = pdf_dict_gets(pdf_trailer(xref), "Root");
+ obj = pdf_dict_gets(root, "Outlines");
+ first = pdf_dict_gets(obj, "First");
+ if (first)
+ return pdf_load_outline_imp(xref, first);
+
+ return NULL;
+}
diff --git a/source/pdf/pdf-page.c b/source/pdf/pdf-page.c
new file mode 100644
index 00000000..8a12b67b
--- /dev/null
+++ b/source/pdf/pdf-page.c
@@ -0,0 +1,489 @@
+#include "mupdf/pdf.h"
+
+struct info
+{
+ pdf_obj *resources;
+ pdf_obj *mediabox;
+ pdf_obj *cropbox;
+ pdf_obj *rotate;
+};
+
+static void
+put_marker_bool(fz_context *ctx, pdf_obj *rdb, char *marker, int val)
+{
+ pdf_obj *tmp;
+
+ tmp = pdf_new_bool(ctx, val);
+ fz_try(ctx)
+ {
+ pdf_dict_puts(rdb, marker, tmp);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(tmp);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+typedef struct pdf_page_load_s pdf_page_load;
+
+struct pdf_page_load_s
+{
+ int max;
+ int pos;
+ pdf_obj *node;
+ pdf_obj *kids;
+ struct info info;
+};
+
+static void
+pdf_load_page_tree_node(pdf_document *xref, pdf_obj *node, struct info info)
+{
+ pdf_obj *dict, *kids, *count;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+ pdf_page_load *stack = NULL;
+ int stacklen = -1;
+ int stackmax = 0;
+
+ fz_try(ctx)
+ {
+ do
+ {
+ if (!node || pdf_obj_mark(node))
+ {
+ /* NULL node, or we've been here before.
+ * Nothing to do. */
+ }
+ else
+ {
+ kids = pdf_dict_gets(node, "Kids");
+ count = pdf_dict_gets(node, "Count");
+ if (pdf_is_array(kids) && pdf_is_int(count))
+ {
+ /* Push this onto the stack */
+ obj = pdf_dict_gets(node, "Resources");
+ if (obj)
+ info.resources = obj;
+ obj = pdf_dict_gets(node, "MediaBox");
+ if (obj)
+ info.mediabox = obj;
+ obj = pdf_dict_gets(node, "CropBox");
+ if (obj)
+ info.cropbox = obj;
+ obj = pdf_dict_gets(node, "Rotate");
+ if (obj)
+ info.rotate = obj;
+ stacklen++;
+ if (stacklen == stackmax)
+ {
+ stack = fz_resize_array(ctx, stack, stackmax ? stackmax*2 : 10, sizeof(*stack));
+ stackmax = stackmax ? stackmax*2 : 10;
+ }
+ stack[stacklen].kids = kids;
+ stack[stacklen].node = node;
+ stack[stacklen].pos = -1;
+ stack[stacklen].max = pdf_array_len(kids);
+ stack[stacklen].info = info;
+ }
+ else if ((dict = pdf_to_dict(node)) != NULL)
+ {
+ if (info.resources && !pdf_dict_gets(dict, "Resources"))
+ pdf_dict_puts(dict, "Resources", info.resources);
+ if (info.mediabox && !pdf_dict_gets(dict, "MediaBox"))
+ pdf_dict_puts(dict, "MediaBox", info.mediabox);
+ if (info.cropbox && !pdf_dict_gets(dict, "CropBox"))
+ pdf_dict_puts(dict, "CropBox", info.cropbox);
+ if (info.rotate && !pdf_dict_gets(dict, "Rotate"))
+ pdf_dict_puts(dict, "Rotate", info.rotate);
+
+ if (xref->page_len == xref->page_cap)
+ {
+ fz_warn(ctx, "found more pages than expected");
+ xref->page_refs = fz_resize_array(ctx, xref->page_refs, xref->page_cap+1, sizeof(pdf_obj*));
+ xref->page_objs = fz_resize_array(ctx, xref->page_objs, xref->page_cap+1, sizeof(pdf_obj*));
+ xref->page_cap ++;
+ }
+
+ xref->page_refs[xref->page_len] = pdf_keep_obj(node);
+ xref->page_objs[xref->page_len] = pdf_keep_obj(dict);
+ xref->page_len ++;
+ pdf_obj_unmark(node);
+ }
+ }
+ /* Get the next node */
+ if (stacklen < 0)
+ break;
+ while (++stack[stacklen].pos == stack[stacklen].max)
+ {
+ pdf_obj_unmark(stack[stacklen].node);
+ stacklen--;
+ if (stacklen < 0) /* No more to pop! */
+ break;
+ node = stack[stacklen].node;
+ info = stack[stacklen].info;
+ pdf_obj_unmark(node); /* Unmark it, cos we're about to mark it again */
+ }
+ if (stacklen >= 0)
+ node = pdf_array_get(stack[stacklen].kids, stack[stacklen].pos);
+ }
+ while (stacklen >= 0);
+ }
+ fz_always(ctx)
+ {
+ while (stacklen >= 0)
+ pdf_obj_unmark(stack[stacklen--].node);
+ fz_free(ctx, stack);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+pdf_load_page_tree(pdf_document *xref)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *catalog;
+ pdf_obj *pages;
+ pdf_obj *count;
+ struct info info;
+
+ if (xref->page_refs)
+ return;
+
+ catalog = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pages = pdf_dict_gets(catalog, "Pages");
+ count = pdf_dict_gets(pages, "Count");
+
+ if (!pdf_is_dict(pages))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "missing page tree");
+ if (!pdf_is_int(count) || pdf_to_int(count) < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "missing page count");
+
+ xref->page_cap = pdf_to_int(count);
+ xref->page_len = 0;
+ xref->page_refs = fz_malloc_array(ctx, xref->page_cap, sizeof(pdf_obj*));
+ xref->page_objs = fz_malloc_array(ctx, xref->page_cap, sizeof(pdf_obj*));
+
+ info.resources = NULL;
+ info.mediabox = NULL;
+ info.cropbox = NULL;
+ info.rotate = NULL;
+
+ pdf_load_page_tree_node(xref, pages, info);
+}
+
+int
+pdf_count_pages(pdf_document *xref)
+{
+ pdf_load_page_tree(xref);
+ return xref->page_len;
+}
+
+int
+pdf_lookup_page_number(pdf_document *xref, pdf_obj *page)
+{
+ int i, num = pdf_to_num(page);
+
+ pdf_load_page_tree(xref);
+ for (i = 0; i < xref->page_len; i++)
+ if (num == pdf_to_num(xref->page_refs[i]))
+ return i;
+ return -1;
+}
+
+/* We need to know whether to install a page-level transparency group */
+
+static int pdf_resources_use_blending(fz_context *ctx, pdf_obj *rdb);
+
+static int
+pdf_extgstate_uses_blending(fz_context *ctx, pdf_obj *dict)
+{
+ pdf_obj *obj = pdf_dict_gets(dict, "BM");
+ if (pdf_is_name(obj) && strcmp(pdf_to_name(obj), "Normal"))
+ return 1;
+ return 0;
+}
+
+static int
+pdf_pattern_uses_blending(fz_context *ctx, pdf_obj *dict)
+{
+ pdf_obj *obj;
+ obj = pdf_dict_gets(dict, "Resources");
+ if (pdf_resources_use_blending(ctx, obj))
+ return 1;
+ obj = pdf_dict_gets(dict, "ExtGState");
+ return pdf_extgstate_uses_blending(ctx, obj);
+}
+
+static int
+pdf_xobject_uses_blending(fz_context *ctx, pdf_obj *dict)
+{
+ pdf_obj *obj = pdf_dict_gets(dict, "Resources");
+ return pdf_resources_use_blending(ctx, obj);
+}
+
+static int
+pdf_resources_use_blending(fz_context *ctx, pdf_obj *rdb)
+{
+ pdf_obj *obj;
+ int i, n, useBM = 0;
+
+ if (!rdb)
+ return 0;
+
+ /* Have we been here before and stashed an answer? */
+ obj = pdf_dict_gets(rdb, ".useBM");
+ if (obj)
+ return pdf_to_bool(obj);
+
+ /* stop on cyclic resource dependencies */
+ if (pdf_obj_mark(rdb))
+ return 0;
+
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(rdb, "ExtGState");
+ n = pdf_dict_len(obj);
+ for (i = 0; i < n; i++)
+ if (pdf_extgstate_uses_blending(ctx, pdf_dict_get_val(obj, i)))
+ goto found;
+
+ obj = pdf_dict_gets(rdb, "Pattern");
+ n = pdf_dict_len(obj);
+ for (i = 0; i < n; i++)
+ if (pdf_pattern_uses_blending(ctx, pdf_dict_get_val(obj, i)))
+ goto found;
+
+ obj = pdf_dict_gets(rdb, "XObject");
+ n = pdf_dict_len(obj);
+ for (i = 0; i < n; i++)
+ if (pdf_xobject_uses_blending(ctx, pdf_dict_get_val(obj, i)))
+ goto found;
+ if (0)
+ {
+found:
+ useBM = 1;
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(rdb);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ put_marker_bool(ctx, rdb, ".useBM", useBM);
+ return useBM;
+}
+
+static void
+pdf_load_transition(pdf_document *xref, pdf_page *page, pdf_obj *transdict)
+{
+ char *name;
+ pdf_obj *obj;
+ int type;
+
+ obj = pdf_dict_gets(transdict, "D");
+ page->transition.duration = (obj ? pdf_to_real(obj) : 1);
+
+ page->transition.vertical = (pdf_to_name(pdf_dict_gets(transdict, "Dm"))[0] != 'H');
+ page->transition.outwards = (pdf_to_name(pdf_dict_gets(transdict, "M"))[0] != 'I');
+ /* FIXME: If 'Di' is None, it should be handled differently, but
+ * this only affects Fly, and we don't implement that currently. */
+ page->transition.direction = (pdf_to_int(pdf_dict_gets(transdict, "Di")));
+ /* FIXME: Read SS for Fly when we implement it */
+ /* FIXME: Read B for Fly when we implement it */
+
+ name = pdf_to_name(pdf_dict_gets(transdict, "S"));
+ if (!strcmp(name, "Split"))
+ type = FZ_TRANSITION_SPLIT;
+ else if (!strcmp(name, "Blinds"))
+ type = FZ_TRANSITION_BLINDS;
+ else if (!strcmp(name, "Box"))
+ type = FZ_TRANSITION_BOX;
+ else if (!strcmp(name, "Wipe"))
+ type = FZ_TRANSITION_WIPE;
+ else if (!strcmp(name, "Dissolve"))
+ type = FZ_TRANSITION_DISSOLVE;
+ else if (!strcmp(name, "Glitter"))
+ type = FZ_TRANSITION_GLITTER;
+ else if (!strcmp(name, "Fly"))
+ type = FZ_TRANSITION_FLY;
+ else if (!strcmp(name, "Push"))
+ type = FZ_TRANSITION_PUSH;
+ else if (!strcmp(name, "Cover"))
+ type = FZ_TRANSITION_COVER;
+ else if (!strcmp(name, "Uncover"))
+ type = FZ_TRANSITION_UNCOVER;
+ else if (!strcmp(name, "Fade"))
+ type = FZ_TRANSITION_FADE;
+ else
+ type = FZ_TRANSITION_NONE;
+ page->transition.type = type;
+}
+
+pdf_page *
+pdf_load_page(pdf_document *xref, int number)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_page *page;
+ pdf_annot *annot;
+ pdf_obj *pageobj, *pageref, *obj;
+ fz_rect mediabox, cropbox, realbox;
+ float userunit;
+ fz_matrix mat;
+
+ pdf_load_page_tree(xref);
+ if (number < 0 || number >= xref->page_len)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find page %d", number + 1);
+
+ pageobj = xref->page_objs[number];
+ pageref = xref->page_refs[number];
+
+ page = fz_malloc_struct(ctx, pdf_page);
+ page->resources = NULL;
+ page->contents = NULL;
+ page->transparency = 0;
+ page->links = NULL;
+ page->annots = NULL;
+ page->deleted_annots = NULL;
+ page->tmp_annots = NULL;
+ page->me = pdf_keep_obj(pageobj);
+
+ obj = pdf_dict_gets(pageobj, "UserUnit");
+ if (pdf_is_real(obj))
+ userunit = pdf_to_real(obj);
+ else
+ userunit = 1;
+
+ pdf_to_rect(ctx, pdf_dict_gets(pageobj, "MediaBox"), &mediabox);
+ if (fz_is_empty_rect(&mediabox))
+ {
+ fz_warn(ctx, "cannot find page size for page %d", number + 1);
+ mediabox.x0 = 0;
+ mediabox.y0 = 0;
+ mediabox.x1 = 612;
+ mediabox.y1 = 792;
+ }
+
+ pdf_to_rect(ctx, pdf_dict_gets(pageobj, "CropBox"), &cropbox);
+ if (!fz_is_empty_rect(&cropbox))
+ fz_intersect_rect(&mediabox, &cropbox);
+
+ page->mediabox.x0 = fz_min(mediabox.x0, mediabox.x1) * userunit;
+ page->mediabox.y0 = fz_min(mediabox.y0, mediabox.y1) * userunit;
+ page->mediabox.x1 = fz_max(mediabox.x0, mediabox.x1) * userunit;
+ page->mediabox.y1 = fz_max(mediabox.y0, mediabox.y1) * userunit;
+
+ if (page->mediabox.x1 - page->mediabox.x0 < 1 || page->mediabox.y1 - page->mediabox.y0 < 1)
+ {
+ fz_warn(ctx, "invalid page size in page %d", number + 1);
+ page->mediabox = fz_unit_rect;
+ }
+
+ page->rotate = pdf_to_int(pdf_dict_gets(pageobj, "Rotate"));
+ /* Snap page->rotate to 0, 90, 180 or 270 */
+ if (page->rotate < 0)
+ page->rotate = 360 - ((-page->rotate) % 360);
+ if (page->rotate >= 360)
+ page->rotate = page->rotate % 360;
+ page->rotate = 90*((page->rotate + 45)/90);
+ if (page->rotate > 360)
+ page->rotate = 0;
+
+ fz_pre_rotate(fz_scale(&page->ctm, 1, -1), -page->rotate);
+ realbox = page->mediabox;
+ fz_transform_rect(&realbox, &page->ctm);
+ fz_pre_scale(fz_translate(&mat, -realbox.x0, -realbox.y0), userunit, userunit);
+ fz_concat(&page->ctm, &page->ctm, &mat);
+
+ obj = pdf_dict_gets(pageobj, "Annots");
+ if (obj)
+ {
+ page->links = pdf_load_link_annots(xref, obj, &page->ctm);
+ page->annots = pdf_load_annots(xref, obj, page);
+ }
+
+ page->duration = pdf_to_real(pdf_dict_gets(pageobj, "Dur"));
+
+ obj = pdf_dict_gets(pageobj, "Trans");
+ page->transition_present = (obj != NULL);
+ if (obj)
+ {
+ pdf_load_transition(xref, page, obj);
+ }
+
+ page->resources = pdf_dict_gets(pageobj, "Resources");
+ if (page->resources)
+ pdf_keep_obj(page->resources);
+
+ obj = pdf_dict_gets(pageobj, "Contents");
+ fz_try(ctx)
+ {
+ page->contents = pdf_keep_obj(obj);
+
+ if (pdf_resources_use_blending(ctx, page->resources))
+ page->transparency = 1;
+
+ for (annot = page->annots; annot && !page->transparency; annot = annot->next)
+ if (annot->ap && pdf_resources_use_blending(ctx, annot->ap->resources))
+ page->transparency = 1;
+ }
+ fz_catch(ctx)
+ {
+ pdf_free_page(xref, page);
+ fz_rethrow_message(ctx, "cannot load page %d contents (%d 0 R)", number + 1, pdf_to_num(pageref));
+ }
+
+ return page;
+}
+
+fz_rect *
+pdf_bound_page(pdf_document *xref, pdf_page *page, fz_rect *bounds)
+{
+ fz_matrix mtx;
+ fz_rect mediabox = page->mediabox;
+ fz_transform_rect(&mediabox, fz_rotate(&mtx, page->rotate));
+ bounds->x0 = bounds->y0 = 0;
+ bounds->x1 = mediabox.x1 - mediabox.x0;
+ bounds->y1 = mediabox.y1 - mediabox.y0;
+ return bounds;
+}
+
+fz_link *
+pdf_load_links(pdf_document *xref, pdf_page *page)
+{
+ return fz_keep_link(xref->ctx, page->links);
+}
+
+void
+pdf_free_page(pdf_document *xref, pdf_page *page)
+{
+ if (page == NULL)
+ return;
+ pdf_drop_obj(page->resources);
+ pdf_drop_obj(page->contents);
+ if (page->links)
+ fz_drop_link(xref->ctx, page->links);
+ if (page->annots)
+ pdf_free_annot(xref->ctx, page->annots);
+ if (page->deleted_annots)
+ pdf_free_annot(xref->ctx, page->deleted_annots);
+ if (page->tmp_annots)
+ pdf_free_annot(xref->ctx, page->tmp_annots);
+ /* xref->focus, when not NULL, refers to one of
+ * the annotations and must be NULLed when the
+ * annotations are destroyed. xref->focus_obj
+ * keeps track of the actual annotation object. */
+ xref->focus = NULL;
+ pdf_drop_obj(page->me);
+ fz_free(xref->ctx, page);
+}
diff --git a/source/pdf/pdf-parse.c b/source/pdf/pdf-parse.c
new file mode 100644
index 00000000..18ab3113
--- /dev/null
+++ b/source/pdf/pdf-parse.c
@@ -0,0 +1,611 @@
+#include "mupdf/pdf.h"
+
+fz_rect *
+pdf_to_rect(fz_context *ctx, pdf_obj *array, fz_rect *r)
+{
+ float a = pdf_to_real(pdf_array_get(array, 0));
+ float b = pdf_to_real(pdf_array_get(array, 1));
+ float c = pdf_to_real(pdf_array_get(array, 2));
+ float d = pdf_to_real(pdf_array_get(array, 3));
+ r->x0 = fz_min(a, c);
+ r->y0 = fz_min(b, d);
+ r->x1 = fz_max(a, c);
+ r->y1 = fz_max(b, d);
+ return r;
+}
+
+fz_matrix *
+pdf_to_matrix(fz_context *ctx, pdf_obj *array, fz_matrix *m)
+{
+ m->a = pdf_to_real(pdf_array_get(array, 0));
+ m->b = pdf_to_real(pdf_array_get(array, 1));
+ m->c = pdf_to_real(pdf_array_get(array, 2));
+ m->d = pdf_to_real(pdf_array_get(array, 3));
+ m->e = pdf_to_real(pdf_array_get(array, 4));
+ m->f = pdf_to_real(pdf_array_get(array, 5));
+ return m;
+}
+
+/* Convert Unicode/PdfDocEncoding string into utf-8 */
+char *
+pdf_to_utf8(pdf_document *xref, pdf_obj *src)
+{
+ fz_context *ctx = xref->ctx;
+ fz_buffer *strmbuf = NULL;
+ unsigned char *srcptr;
+ char *dstptr, *dst;
+ int srclen;
+ int dstlen = 0;
+ int ucs;
+ int i;
+
+ fz_var(strmbuf);
+ fz_try(ctx)
+ {
+ if (pdf_is_string(src))
+ {
+ srcptr = (unsigned char *) pdf_to_str_buf(src);
+ srclen = pdf_to_str_len(src);
+ }
+ else if (pdf_is_stream(xref, pdf_to_num(src), pdf_to_gen(src)))
+ {
+ strmbuf = pdf_load_stream(xref, pdf_to_num(src), pdf_to_gen(src));
+ srclen = fz_buffer_storage(ctx, strmbuf, (unsigned char **)&srcptr);
+ }
+ else
+ {
+ srclen = 0;
+ }
+
+ if (srclen >= 2 && srcptr[0] == 254 && srcptr[1] == 255)
+ {
+ for (i = 2; i + 1 < srclen; i += 2)
+ {
+ ucs = srcptr[i] << 8 | srcptr[i+1];
+ dstlen += fz_runelen(ucs);
+ }
+
+ dstptr = dst = fz_malloc(ctx, dstlen + 1);
+
+ for (i = 2; i + 1 < srclen; i += 2)
+ {
+ ucs = srcptr[i] << 8 | srcptr[i+1];
+ dstptr += fz_runetochar(dstptr, ucs);
+ }
+ }
+ else if (srclen >= 2 && srcptr[0] == 255 && srcptr[1] == 254)
+ {
+ for (i = 2; i + 1 < srclen; i += 2)
+ {
+ ucs = srcptr[i] | srcptr[i+1] << 8;
+ dstlen += fz_runelen(ucs);
+ }
+
+ dstptr = dst = fz_malloc(ctx, dstlen + 1);
+
+ for (i = 2; i + 1 < srclen; i += 2)
+ {
+ ucs = srcptr[i] | srcptr[i+1] << 8;
+ dstptr += fz_runetochar(dstptr, ucs);
+ }
+ }
+ else
+ {
+ for (i = 0; i < srclen; i++)
+ dstlen += fz_runelen(pdf_doc_encoding[srcptr[i]]);
+
+ dstptr = dst = fz_malloc(ctx, dstlen + 1);
+
+ for (i = 0; i < srclen; i++)
+ {
+ ucs = pdf_doc_encoding[srcptr[i]];
+ dstptr += fz_runetochar(dstptr, ucs);
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, strmbuf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ *dstptr = '\0';
+ return dst;
+}
+
+/* Convert Unicode/PdfDocEncoding string into ucs-2 */
+unsigned short *
+pdf_to_ucs2(pdf_document *xref, pdf_obj *src)
+{
+ fz_context *ctx = xref->ctx;
+ unsigned char *srcptr = (unsigned char *) pdf_to_str_buf(src);
+ unsigned short *dstptr, *dst;
+ int srclen = pdf_to_str_len(src);
+ int i;
+
+ if (srclen >= 2 && srcptr[0] == 254 && srcptr[1] == 255)
+ {
+ dstptr = dst = fz_malloc_array(ctx, (srclen - 2) / 2 + 1, sizeof(short));
+ for (i = 2; i + 1 < srclen; i += 2)
+ *dstptr++ = srcptr[i] << 8 | srcptr[i+1];
+ }
+ else if (srclen >= 2 && srcptr[0] == 255 && srcptr[1] == 254)
+ {
+ dstptr = dst = fz_malloc_array(ctx, (srclen - 2) / 2 + 1, sizeof(short));
+ for (i = 2; i + 1 < srclen; i += 2)
+ *dstptr++ = srcptr[i] | srcptr[i+1] << 8;
+ }
+ else
+ {
+ dstptr = dst = fz_malloc_array(ctx, srclen + 1, sizeof(short));
+ for (i = 0; i < srclen; i++)
+ *dstptr++ = pdf_doc_encoding[srcptr[i]];
+ }
+
+ *dstptr = '\0';
+ return dst;
+}
+
+/* allow to convert to UCS-2 without the need for an fz_context */
+/* (buffer must be at least (fz_to_str_len(src) + 1) * 2 bytes in size) */
+void
+pdf_to_ucs2_buf(unsigned short *buffer, pdf_obj *src)
+{
+ unsigned char *srcptr = (unsigned char *) pdf_to_str_buf(src);
+ unsigned short *dstptr = buffer;
+ int srclen = pdf_to_str_len(src);
+ int i;
+
+ if (srclen >= 2 && srcptr[0] == 254 && srcptr[1] == 255)
+ {
+ for (i = 2; i + 1 < srclen; i += 2)
+ *dstptr++ = srcptr[i] << 8 | srcptr[i+1];
+ }
+ else if (srclen >= 2 && srcptr[0] == 255 && srcptr[1] == 254)
+ {
+ for (i = 2; i + 1 < srclen; i += 2)
+ *dstptr++ = srcptr[i] | srcptr[i+1] << 8;
+ }
+ else
+ {
+ for (i = 0; i < srclen; i++)
+ *dstptr++ = pdf_doc_encoding[srcptr[i]];
+ }
+
+ *dstptr = '\0';
+}
+
+/* Convert UCS-2 string into PdfDocEncoding for authentication */
+char *
+pdf_from_ucs2(pdf_document *xref, unsigned short *src)
+{
+ fz_context *ctx = xref->ctx;
+ int i, j, len;
+ char *docstr;
+
+ len = 0;
+ while (src[len])
+ len++;
+
+ docstr = fz_malloc(ctx, len + 1);
+
+ for (i = 0; i < len; i++)
+ {
+ /* shortcut: check if the character has the same code point in both encodings */
+ if (0 < src[i] && src[i] < 256 && pdf_doc_encoding[src[i]] == src[i]) {
+ docstr[i] = src[i];
+ continue;
+ }
+
+ /* search through pdf_docencoding for the character's code point */
+ for (j = 0; j < 256; j++)
+ if (pdf_doc_encoding[j] == src[i])
+ break;
+ docstr[i] = j;
+
+ /* fail, if a character can't be encoded */
+ if (!docstr[i])
+ {
+ fz_free(ctx, docstr);
+ return NULL;
+ }
+ }
+ docstr[len] = '\0';
+
+ return docstr;
+}
+
+pdf_obj *
+pdf_to_utf8_name(pdf_document *xref, pdf_obj *src)
+{
+ char *buf = pdf_to_utf8(xref, src);
+ pdf_obj *dst = pdf_new_name(xref->ctx, buf);
+ fz_free(xref->ctx, buf);
+ return dst;
+}
+
+pdf_obj *
+pdf_parse_array(pdf_document *xref, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_obj *ary = NULL;
+ pdf_obj *obj = NULL;
+ int a = 0, b = 0, n = 0;
+ pdf_token tok;
+ fz_context *ctx = file->ctx;
+ pdf_obj *op;
+
+ fz_var(obj);
+
+ ary = pdf_new_array(ctx, 4);
+
+ fz_try(ctx)
+ {
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+
+ if (tok != PDF_TOK_INT && tok != PDF_TOK_R)
+ {
+ if (n > 0)
+ {
+ obj = pdf_new_int(ctx, a);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ }
+ if (n > 1)
+ {
+ obj = pdf_new_int(ctx, b);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ }
+ n = 0;
+ }
+
+ if (tok == PDF_TOK_INT && n == 2)
+ {
+ obj = pdf_new_int(ctx, a);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ a = b;
+ n --;
+ }
+
+ switch (tok)
+ {
+ case PDF_TOK_CLOSE_ARRAY:
+ op = ary;
+ goto end;
+
+ case PDF_TOK_INT:
+ if (n == 0)
+ a = buf->i;
+ if (n == 1)
+ b = buf->i;
+ n ++;
+ break;
+
+ case PDF_TOK_R:
+ if (n != 2)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot parse indirect reference in array");
+ obj = pdf_new_indirect(ctx, a, b, xref);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ n = 0;
+ break;
+
+ case PDF_TOK_OPEN_ARRAY:
+ obj = pdf_parse_array(xref, file, buf);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+
+ case PDF_TOK_OPEN_DICT:
+ obj = pdf_parse_dict(xref, file, buf);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+
+ case PDF_TOK_NAME:
+ obj = pdf_new_name(ctx, buf->scratch);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+ case PDF_TOK_REAL:
+ obj = pdf_new_real(ctx, buf->f);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+ case PDF_TOK_STRING:
+ obj = pdf_new_string(ctx, buf->scratch, buf->len);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+ case PDF_TOK_TRUE:
+ obj = pdf_new_bool(ctx, 1);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+ case PDF_TOK_FALSE:
+ obj = pdf_new_bool(ctx, 0);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+ case PDF_TOK_NULL:
+ obj = pdf_new_null(ctx);
+ pdf_array_push(ary, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ break;
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot parse token in array");
+ }
+ }
+end:
+ {}
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(obj);
+ pdf_drop_obj(ary);
+ fz_rethrow_message(ctx, "cannot parse array");
+ }
+ return op;
+}
+
+pdf_obj *
+pdf_parse_dict(pdf_document *xref, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_obj *dict;
+ pdf_obj *key = NULL;
+ pdf_obj *val = NULL;
+ pdf_token tok;
+ int a, b;
+ fz_context *ctx = file->ctx;
+
+ dict = pdf_new_dict(ctx, 8);
+
+ fz_var(key);
+ fz_var(val);
+
+ fz_try(ctx)
+ {
+ while (1)
+ {
+ tok = pdf_lex(file, buf);
+ skip:
+ if (tok == PDF_TOK_CLOSE_DICT)
+ break;
+
+ /* for BI .. ID .. EI in content streams */
+ if (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "ID"))
+ break;
+
+ if (tok != PDF_TOK_NAME)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key in dict");
+
+ key = pdf_new_name(ctx, buf->scratch);
+
+ tok = pdf_lex(file, buf);
+
+ switch (tok)
+ {
+ case PDF_TOK_OPEN_ARRAY:
+ val = pdf_parse_array(xref, file, buf);
+ break;
+
+ case PDF_TOK_OPEN_DICT:
+ val = pdf_parse_dict(xref, file, buf);
+ break;
+
+ case PDF_TOK_NAME: val = pdf_new_name(ctx, buf->scratch); break;
+ case PDF_TOK_REAL: val = pdf_new_real(ctx, buf->f); break;
+ case PDF_TOK_STRING: val = pdf_new_string(ctx, buf->scratch, buf->len); break;
+ case PDF_TOK_TRUE: val = pdf_new_bool(ctx, 1); break;
+ case PDF_TOK_FALSE: val = pdf_new_bool(ctx, 0); break;
+ case PDF_TOK_NULL: val = pdf_new_null(ctx); break;
+
+ case PDF_TOK_INT:
+ /* 64-bit to allow for numbers > INT_MAX and overflow */
+ a = buf->i;
+ tok = pdf_lex(file, buf);
+ if (tok == PDF_TOK_CLOSE_DICT || tok == PDF_TOK_NAME ||
+ (tok == PDF_TOK_KEYWORD && !strcmp(buf->scratch, "ID")))
+ {
+ val = pdf_new_int(ctx, a);
+ pdf_dict_put(dict, key, val);
+ pdf_drop_obj(val);
+ val = NULL;
+ pdf_drop_obj(key);
+ key = NULL;
+ goto skip;
+ }
+ if (tok == PDF_TOK_INT)
+ {
+ b = buf->i;
+ tok = pdf_lex(file, buf);
+ if (tok == PDF_TOK_R)
+ {
+ val = pdf_new_indirect(ctx, a, b, xref);
+ break;
+ }
+ }
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid indirect reference in dict");
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown token in dict");
+ }
+
+ pdf_dict_put(dict, key, val);
+ pdf_drop_obj(val);
+ val = NULL;
+ pdf_drop_obj(key);
+ key = NULL;
+ }
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(dict);
+ pdf_drop_obj(key);
+ pdf_drop_obj(val);
+ fz_rethrow_message(ctx, "cannot parse dict");
+ }
+ return dict;
+}
+
+pdf_obj *
+pdf_parse_stm_obj(pdf_document *xref, fz_stream *file, pdf_lexbuf *buf)
+{
+ pdf_token tok;
+ fz_context *ctx = file->ctx;
+
+ tok = pdf_lex(file, buf);
+
+ switch (tok)
+ {
+ case PDF_TOK_OPEN_ARRAY:
+ return pdf_parse_array(xref, file, buf);
+ case PDF_TOK_OPEN_DICT:
+ return pdf_parse_dict(xref, file, buf);
+ case PDF_TOK_NAME: return pdf_new_name(ctx, buf->scratch); break;
+ case PDF_TOK_REAL: return pdf_new_real(ctx, buf->f); break;
+ case PDF_TOK_STRING: return pdf_new_string(ctx, buf->scratch, buf->len); break;
+ case PDF_TOK_TRUE: return pdf_new_bool(ctx, 1); break;
+ case PDF_TOK_FALSE: return pdf_new_bool(ctx, 0); break;
+ case PDF_TOK_NULL: return pdf_new_null(ctx); break;
+ case PDF_TOK_INT: return pdf_new_int(ctx, buf->i); break;
+ default: fz_throw(ctx, FZ_ERROR_GENERIC, "unknown token in object stream");
+ }
+ return NULL; /* Stupid MSVC */
+}
+
+pdf_obj *
+pdf_parse_ind_obj(pdf_document *xref,
+ fz_stream *file, pdf_lexbuf *buf,
+ int *onum, int *ogen, int *ostmofs)
+{
+ pdf_obj *obj = NULL;
+ int num = 0, gen = 0, stm_ofs;
+ pdf_token tok;
+ int a, b;
+ fz_context *ctx = file->ctx;
+
+ fz_var(obj);
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected object number");
+ num = buf->i;
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected generation number (%d ? obj)", num);
+ gen = buf->i;
+
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_OBJ)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected 'obj' keyword (%d %d ?)", num, gen);
+
+ tok = pdf_lex(file, buf);
+
+ switch (tok)
+ {
+ case PDF_TOK_OPEN_ARRAY:
+ obj = pdf_parse_array(xref, file, buf);
+ break;
+
+ case PDF_TOK_OPEN_DICT:
+ obj = pdf_parse_dict(xref, file, buf);
+ break;
+
+ case PDF_TOK_NAME: obj = pdf_new_name(ctx, buf->scratch); break;
+ case PDF_TOK_REAL: obj = pdf_new_real(ctx, buf->f); break;
+ case PDF_TOK_STRING: obj = pdf_new_string(ctx, buf->scratch, buf->len); break;
+ case PDF_TOK_TRUE: obj = pdf_new_bool(ctx, 1); break;
+ case PDF_TOK_FALSE: obj = pdf_new_bool(ctx, 0); break;
+ case PDF_TOK_NULL: obj = pdf_new_null(ctx); break;
+
+ case PDF_TOK_INT:
+ a = buf->i;
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_STREAM || tok == PDF_TOK_ENDOBJ)
+ {
+ obj = pdf_new_int(ctx, a);
+ goto skip;
+ }
+ if (tok == PDF_TOK_INT)
+ {
+ b = buf->i;
+ tok = pdf_lex(file, buf);
+ if (tok == PDF_TOK_R)
+ {
+ obj = pdf_new_indirect(ctx, a, b, xref);
+ break;
+ }
+ }
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected 'R' keyword (%d %d R)", num, gen);
+
+ case PDF_TOK_ENDOBJ:
+ obj = pdf_new_null(ctx);
+ goto skip;
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in object (%d %d R)", num, gen);
+ }
+
+ fz_try(ctx)
+ {
+ tok = pdf_lex(file, buf);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(obj);
+ fz_rethrow_message(ctx, "cannot parse indirect object (%d %d R)", num, gen);
+ }
+
+skip:
+ if (tok == PDF_TOK_STREAM)
+ {
+ int c = fz_read_byte(file);
+ while (c == ' ')
+ c = fz_read_byte(file);
+ if (c == '\r')
+ {
+ c = fz_peek_byte(file);
+ if (c != '\n')
+ fz_warn(ctx, "line feed missing after stream begin marker (%d %d R)", num, gen);
+ else
+ fz_read_byte(file);
+ }
+ stm_ofs = fz_tell(file);
+ }
+ else if (tok == PDF_TOK_ENDOBJ)
+ {
+ stm_ofs = 0;
+ }
+ else
+ {
+ fz_warn(ctx, "expected 'endobj' or 'stream' keyword (%d %d R)", num, gen);
+ stm_ofs = 0;
+ }
+
+ if (onum) *onum = num;
+ if (ogen) *ogen = gen;
+ if (ostmofs) *ostmofs = stm_ofs;
+ return obj;
+}
diff --git a/source/pdf/pdf-pattern.c b/source/pdf/pdf-pattern.c
new file mode 100644
index 00000000..622705b2
--- /dev/null
+++ b/source/pdf/pdf-pattern.c
@@ -0,0 +1,83 @@
+#include "mupdf/pdf.h"
+
+pdf_pattern *
+pdf_keep_pattern(fz_context *ctx, pdf_pattern *pat)
+{
+ return (pdf_pattern *)fz_keep_storable(ctx, &pat->storable);
+}
+
+void
+pdf_drop_pattern(fz_context *ctx, pdf_pattern *pat)
+{
+ fz_drop_storable(ctx, &pat->storable);
+}
+
+static void
+pdf_free_pattern_imp(fz_context *ctx, fz_storable *pat_)
+{
+ pdf_pattern *pat = (pdf_pattern *)pat_;
+
+ if (pat->resources)
+ pdf_drop_obj(pat->resources);
+ if (pat->contents)
+ pdf_drop_obj(pat->contents);
+ fz_free(ctx, pat);
+}
+
+static unsigned int
+pdf_pattern_size(pdf_pattern *pat)
+{
+ if (pat == NULL)
+ return 0;
+ return sizeof(*pat);
+}
+
+pdf_pattern *
+pdf_load_pattern(pdf_document *xref, pdf_obj *dict)
+{
+ pdf_pattern *pat;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+
+ if ((pat = pdf_find_item(ctx, pdf_free_pattern_imp, dict)))
+ {
+ return pat;
+ }
+
+ pat = fz_malloc_struct(ctx, pdf_pattern);
+ FZ_INIT_STORABLE(pat, 1, pdf_free_pattern_imp);
+ pat->resources = NULL;
+ pat->contents = NULL;
+
+ /* Store pattern now, to avoid possible recursion if objects refer back to this one */
+ pdf_store_item(ctx, dict, pat, pdf_pattern_size(pat));
+
+ pat->ismask = pdf_to_int(pdf_dict_gets(dict, "PaintType")) == 2;
+ pat->xstep = pdf_to_real(pdf_dict_gets(dict, "XStep"));
+ pat->ystep = pdf_to_real(pdf_dict_gets(dict, "YStep"));
+
+ obj = pdf_dict_gets(dict, "BBox");
+ pdf_to_rect(ctx, obj, &pat->bbox);
+
+ obj = pdf_dict_gets(dict, "Matrix");
+ if (obj)
+ pdf_to_matrix(ctx, obj, &pat->matrix);
+ else
+ pat->matrix = fz_identity;
+
+ pat->resources = pdf_dict_gets(dict, "Resources");
+ if (pat->resources)
+ pdf_keep_obj(pat->resources);
+
+ fz_try(ctx)
+ {
+ pat->contents = pdf_keep_obj(dict);
+ }
+ fz_catch(ctx)
+ {
+ pdf_remove_item(ctx, pdf_free_pattern_imp, dict);
+ pdf_drop_pattern(ctx, pat);
+ fz_rethrow_message(ctx, "cannot load pattern stream (%d %d R)", pdf_to_num(dict), pdf_to_gen(dict));
+ }
+ return pat;
+}
diff --git a/source/pdf/pdf-pkcs7.c b/source/pdf/pdf-pkcs7.c
new file mode 100644
index 00000000..1bc50b6e
--- /dev/null
+++ b/source/pdf/pdf-pkcs7.c
@@ -0,0 +1,400 @@
+#include "mupdf/pdf.h" // TODO: move this file to pdf module
+
+#ifdef HAVE_OPENSSL
+
+#include "openssl/err.h"
+#include "openssl/bio.h"
+#include "openssl/asn1.h"
+#include "openssl/x509.h"
+#include "openssl/err.h"
+#include "openssl/objects.h"
+#include "openssl/pem.h"
+#include "openssl/pkcs7.h"
+
+enum
+{
+ SEG_START = 0,
+ SEG_SIZE = 1
+};
+
+typedef struct bsegs_struct
+{
+ int (*seg)[2];
+ int nsegs;
+ int current_seg;
+ int seg_pos;
+} BIO_SEGS_CTX;
+
+static int bsegs_read(BIO *b, char *buf, int size)
+{
+ BIO_SEGS_CTX *ctx = (BIO_SEGS_CTX *)b->ptr;
+ int read = 0;
+
+ while (size > 0 && ctx->current_seg < ctx->nsegs)
+ {
+ int nb = ctx->seg[ctx->current_seg][SEG_SIZE] - ctx->seg_pos;
+
+ if (nb > size)
+ nb = size;
+
+ if (nb > 0)
+ {
+ if (ctx->seg_pos == 0)
+ (void)BIO_seek(b->next_bio, ctx->seg[ctx->current_seg][SEG_START]);
+
+ (void)BIO_read(b->next_bio, buf, nb);
+ ctx->seg_pos += nb;
+ read += nb;
+ buf += nb;
+ size -= nb;
+ }
+ else
+ {
+ ctx->current_seg++;
+
+ if (ctx->current_seg < ctx->nsegs)
+ ctx->seg_pos = 0;
+ }
+ }
+
+ return read;
+}
+
+static long bsegs_ctrl(BIO *b, int cmd, long arg1, void *arg2)
+{
+ return BIO_ctrl(b->next_bio, cmd, arg1, arg2);
+}
+
+static int bsegs_new(BIO *b)
+{
+ BIO_SEGS_CTX *ctx;
+
+ ctx = (BIO_SEGS_CTX *)malloc(sizeof(BIO_SEGS_CTX));
+ if (ctx == NULL)
+ return 0;
+
+ ctx->current_seg = 0;
+ ctx->seg_pos = 0;
+ ctx->seg = NULL;
+ ctx->nsegs = 0;
+
+ b->init = 1;
+ b->ptr = (char *)ctx;
+ b->flags = 0;
+ b->num = 0;
+
+ return 1;
+}
+
+static int bsegs_free(BIO *b)
+{
+ if (b == NULL)
+ return 0;
+
+ free(b->ptr);
+ b->ptr = NULL;
+ b->init = 0;
+ b->flags = 0;
+
+ return 1;
+}
+
+static long bsegs_callback_ctrl(BIO *b, int cmd, bio_info_cb *fp)
+{
+ return BIO_callback_ctrl(b->next_bio, cmd, fp);
+}
+
+static BIO_METHOD methods_bsegs =
+{
+ 0,"segment reader",
+ NULL,
+ bsegs_read,
+ NULL,
+ NULL,
+ bsegs_ctrl,
+ bsegs_new,
+ bsegs_free,
+ bsegs_callback_ctrl,
+};
+
+static BIO_METHOD *BIO_f_segments(void)
+{
+ return &methods_bsegs;
+}
+
+static void BIO_set_segments(BIO *b, int (*seg)[2], int nsegs)
+{
+ BIO_SEGS_CTX *ctx = (BIO_SEGS_CTX *)b->ptr;
+
+ ctx->seg = seg;
+ ctx->nsegs = nsegs;
+}
+
+typedef struct verify_context_s
+{
+ X509_STORE_CTX x509_ctx;
+ char certdesc[256];
+ int err;
+} verify_context;
+
+static int verify_callback(int ok, X509_STORE_CTX *ctx)
+{
+ verify_context *vctx;
+ X509 *err_cert;
+ int err, depth;
+
+ vctx = (verify_context *)ctx;
+
+ err_cert = X509_STORE_CTX_get_current_cert(ctx);
+ err = X509_STORE_CTX_get_error(ctx);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ X509_NAME_oneline(X509_get_subject_name(err_cert), vctx->certdesc, sizeof(vctx->certdesc));
+
+ if (!ok && depth >= 6)
+ {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ }
+
+ switch (ctx->error)
+ {
+ case X509_V_ERR_INVALID_PURPOSE:
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+ err = X509_V_OK;
+ X509_STORE_CTX_set_error(ctx, X509_V_OK);
+ ok = 1;
+ break;
+
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ /*
+ In this case, don't reset err to X509_V_OK, so that it can be reported,
+ although we do return 1, so that the digest will still be checked
+ */
+ ok = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ if (ok && vctx->err == X509_V_OK)
+ vctx->err = err;
+ return ok;
+}
+
+static int pk7_verify(X509_STORE *cert_store, PKCS7 *p7, BIO *detached, char *ebuf, int ebufsize)
+{
+ PKCS7_SIGNER_INFO *si;
+ verify_context vctx;
+ BIO *p7bio=NULL;
+ char readbuf[1024*4];
+ int res = 1;
+ int i;
+ STACK_OF(PKCS7_SIGNER_INFO) *sk;
+
+ vctx.err = X509_V_OK;
+ ebuf[0] = 0;
+
+ OpenSSL_add_all_algorithms();
+
+ EVP_add_digest(EVP_md5());
+ EVP_add_digest(EVP_sha1());
+
+ ERR_load_crypto_strings();
+
+ ERR_clear_error();
+
+ X509_VERIFY_PARAM_set_flags(cert_store->param, X509_V_FLAG_CB_ISSUER_CHECK);
+ X509_STORE_set_verify_cb_func(cert_store, verify_callback);
+
+ p7bio = PKCS7_dataInit(p7, detached);
+
+ /* We now have to 'read' from p7bio to calculate digests etc. */
+ while (BIO_read(p7bio, readbuf, sizeof(readbuf)) > 0)
+ ;
+
+ /* We can now verify signatures */
+ sk = PKCS7_get_signer_info(p7);
+ if (sk == NULL)
+ {
+ /* there are no signatures on this data */
+ res = 0;
+ strncpy(ebuf, "No signatures", sizeof(ebuf));
+ goto exit;
+ }
+
+ for (i=0; i<sk_PKCS7_SIGNER_INFO_num(sk); i++)
+ {
+ int rc;
+ si = sk_PKCS7_SIGNER_INFO_value(sk, i);
+ rc = PKCS7_dataVerify(cert_store, &vctx.x509_ctx, p7bio,p7, si);
+ if (rc <= 0 || vctx.err != X509_V_OK)
+ {
+ char tbuf[120];
+
+ if (rc <= 0)
+ {
+ strncpy(ebuf, ERR_error_string(ERR_get_error(), tbuf), ebufsize-1);
+ }
+ else
+ {
+ /* Error while checking the certificate chain */
+ snprintf(ebuf, ebufsize-1, "%s(%d): %s", X509_verify_cert_error_string(vctx.err), vctx.err, vctx.certdesc);
+ }
+
+ ebuf[ebufsize-1] = 0;
+
+ res = 0;
+ goto exit;
+ }
+ }
+
+exit:
+ X509_STORE_CTX_cleanup(&vctx.x509_ctx);
+ ERR_free_strings();
+
+ return res;
+}
+
+static unsigned char adobe_ca[] =
+{
+#include "gen_adobe_ca.h"
+};
+
+static int verify_sig(char *sig, int sig_len, char *file, int (*byte_range)[2], int byte_range_len, char *ebuf, int ebufsize)
+{
+ PKCS7 *pk7sig = NULL;
+ PKCS7 *pk7cert = NULL;
+ X509_STORE *st = NULL;
+ BIO *bsig = NULL;
+ BIO *bcert = NULL;
+ BIO *bdata = NULL;
+ BIO *bsegs = NULL;
+ STACK_OF(X509) *certs = NULL;
+ int t;
+ int res = 0;
+
+ bsig = BIO_new_mem_buf(sig, sig_len);
+ pk7sig = d2i_PKCS7_bio(bsig, NULL);
+ if (pk7sig == NULL)
+ goto exit;
+
+ bdata = BIO_new(BIO_s_file());
+ BIO_read_filename(bdata, file);
+
+ bsegs = BIO_new(BIO_f_segments());
+ if (bsegs == NULL)
+ goto exit;
+
+ bsegs->next_bio = bdata;
+ BIO_set_segments(bsegs, byte_range, byte_range_len);
+
+ /* Find the certificates in the pk7 file */
+ bcert = BIO_new_mem_buf(adobe_ca, sizeof(adobe_ca));
+ pk7cert = d2i_PKCS7_bio(bcert, NULL);
+ if (pk7cert == NULL)
+ goto exit;
+
+ t = OBJ_obj2nid(pk7cert->type);
+ switch (t)
+ {
+ case NID_pkcs7_signed:
+ certs = pk7cert->d.sign->cert;
+ break;
+
+ case NID_pkcs7_signedAndEnveloped:
+ certs = pk7cert->d.sign->cert;
+ break;
+
+ default:
+ break;
+ }
+
+ st = X509_STORE_new();
+ if (st == NULL)
+ goto exit;
+
+ /* Add the certificates to the store */
+ if (certs != NULL)
+ {
+ int i, n = sk_X509_num(certs);
+
+ for (i = 0; i < n; i++)
+ {
+ X509 *c = sk_X509_value(certs, i);
+ X509_STORE_add_cert(st, c);
+ }
+ }
+
+ res = pk7_verify(st, pk7sig, bsegs, ebuf, ebufsize);
+
+exit:
+ BIO_free(bsig);
+ BIO_free(bdata);
+ BIO_free(bsegs);
+ BIO_free(bcert);
+ PKCS7_free(pk7sig);
+ PKCS7_free(pk7cert);
+ X509_STORE_free(st);
+
+ return res;
+}
+
+int pdf_check_signature(fz_context *ctx, pdf_document *doc, pdf_widget *widget, char *file, char *ebuf, int ebufsize)
+{
+ int (*byte_range)[2] = NULL;
+ int byte_range_len;
+ char *contents = NULL;
+ int contents_len;
+ int res = 0;
+
+ fz_var(byte_range);
+ fz_var(res);
+ fz_try(ctx);
+ {
+ byte_range_len = pdf_signature_widget_byte_range(doc, widget, NULL);
+ if (byte_range_len)
+ {
+ byte_range = fz_calloc(ctx, byte_range_len, sizeof(*byte_range));
+ pdf_signature_widget_byte_range(doc, widget, byte_range);
+ }
+
+ contents_len = pdf_signature_widget_contents(doc, widget, &contents);
+ if (byte_range && contents)
+ {
+ res = verify_sig(contents, contents_len, file, byte_range, byte_range_len, ebuf, ebufsize);
+ }
+ else
+ {
+ res = 0;
+ strncpy(ebuf, "Not signed", ebufsize);
+ }
+
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, byte_range);
+ }
+ fz_catch(ctx)
+ {
+ res = 0;
+ strncpy(ebuf, fz_caught_message(ctx), ebufsize);
+ }
+
+ if (ebufsize > 0)
+ ebuf[ebufsize-1] = 0;
+
+ return res;
+}
+
+#else /* HAVE_OPENSSL */
+
+int pdf_check_signature(fz_context *ctx, pdf_document *doc, pdf_widget *widget, char *file, char *ebuf, int ebufsize)
+{
+ strncpy(ebuf, "This version of MuPDF was built without signature support", ebufsize);
+
+ return 0;
+}
+
+#endif /* HAVE_OPENSSL */
diff --git a/source/pdf/pdf-repair.c b/source/pdf/pdf-repair.c
new file mode 100644
index 00000000..421696a2
--- /dev/null
+++ b/source/pdf/pdf-repair.c
@@ -0,0 +1,587 @@
+#include "mupdf/pdf.h"
+
+/* Scan file for objects and reconstruct xref table */
+
+/* Define in PDF 1.7 to be 8388607, but mupdf is more lenient. */
+#define MAX_OBJECT_NUMBER (10 << 20)
+
+struct entry
+{
+ int num;
+ int gen;
+ int ofs;
+ int stm_ofs;
+ int stm_len;
+};
+
+static int
+pdf_repair_obj(fz_stream *file, pdf_lexbuf *buf, int *stmofsp, int *stmlenp, pdf_obj **encrypt, pdf_obj **id, int *tmpofs)
+{
+ pdf_token tok;
+ int stm_len;
+ int n;
+ fz_context *ctx = file->ctx;
+
+ *stmofsp = 0;
+ *stmlenp = -1;
+
+ stm_len = 0;
+
+ /* On entry to this function, we know that we've just seen
+ * '<int> <int> obj'. We expect the next thing we see to be a
+ * pdf object. Regardless of the type of thing we meet next
+ * we only need to fully parse it if it is a dictionary. */
+ tok = pdf_lex(file, buf);
+
+ if (tok == PDF_TOK_OPEN_DICT)
+ {
+ pdf_obj *dict, *obj;
+
+ /* Send NULL xref so we don't try to resolve references */
+ fz_try(ctx)
+ {
+ dict = pdf_parse_dict(NULL, file, buf);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* Don't let a broken object at EOF overwrite a good one */
+ if (file->eof)
+ fz_rethrow_message(ctx, "broken object at EOF ignored");
+ /* Silently swallow the error */
+ dict = pdf_new_dict(ctx, 2);
+ }
+
+ obj = pdf_dict_gets(dict, "Type");
+ if (pdf_is_name(obj) && !strcmp(pdf_to_name(obj), "XRef"))
+ {
+ obj = pdf_dict_gets(dict, "Encrypt");
+ if (obj)
+ {
+ pdf_drop_obj(*encrypt);
+ *encrypt = pdf_keep_obj(obj);
+ }
+
+ obj = pdf_dict_gets(dict, "ID");
+ if (obj)
+ {
+ pdf_drop_obj(*id);
+ *id = pdf_keep_obj(obj);
+ }
+ }
+
+ obj = pdf_dict_gets(dict, "Length");
+ if (!pdf_is_indirect(obj) && pdf_is_int(obj))
+ stm_len = pdf_to_int(obj);
+
+ pdf_drop_obj(dict);
+ }
+
+ while ( tok != PDF_TOK_STREAM &&
+ tok != PDF_TOK_ENDOBJ &&
+ tok != PDF_TOK_ERROR &&
+ tok != PDF_TOK_EOF &&
+ tok != PDF_TOK_INT )
+ {
+ *tmpofs = fz_tell(file);
+ if (*tmpofs < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot tell in file");
+ tok = pdf_lex(file, buf);
+ }
+
+ if (tok == PDF_TOK_STREAM)
+ {
+ int c = fz_read_byte(file);
+ if (c == '\r') {
+ c = fz_peek_byte(file);
+ if (c == '\n')
+ fz_read_byte(file);
+ }
+
+ *stmofsp = fz_tell(file);
+ if (*stmofsp < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot seek in file");
+
+ if (stm_len > 0)
+ {
+ fz_seek(file, *stmofsp + stm_len, 0);
+ fz_try(ctx)
+ {
+ tok = pdf_lex(file, buf);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "cannot find endstream token, falling back to scanning");
+ }
+ if (tok == PDF_TOK_ENDSTREAM)
+ goto atobjend;
+ fz_seek(file, *stmofsp, 0);
+ }
+
+ n = fz_read(file, (unsigned char *) buf->scratch, 9);
+ if (n < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot read from file");
+
+ while (memcmp(buf->scratch, "endstream", 9) != 0)
+ {
+ c = fz_read_byte(file);
+ if (c == EOF)
+ break;
+ memmove(&buf->scratch[0], &buf->scratch[1], 8);
+ buf->scratch[8] = c;
+ }
+
+ *stmlenp = fz_tell(file) - *stmofsp - 9;
+
+atobjend:
+ *tmpofs = fz_tell(file);
+ if (*tmpofs < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot tell in file");
+ tok = pdf_lex(file, buf);
+ if (tok != PDF_TOK_ENDOBJ)
+ fz_warn(ctx, "object missing 'endobj' token");
+ else
+ {
+ /* Read another token as we always return the next one */
+ *tmpofs = fz_tell(file);
+ if (*tmpofs < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot tell in file");
+ tok = pdf_lex(file, buf);
+ }
+ }
+ return tok;
+}
+
+static void
+pdf_repair_obj_stm(pdf_document *xref, int num, int gen)
+{
+ pdf_obj *obj;
+ fz_stream *stm = NULL;
+ pdf_token tok;
+ int i, n, count;
+ fz_context *ctx = xref->ctx;
+ pdf_lexbuf buf;
+
+ fz_var(stm);
+
+ pdf_lexbuf_init(ctx, &buf, PDF_LEXBUF_SMALL);
+
+ fz_try(ctx)
+ {
+ obj = pdf_load_object(xref, num, gen);
+
+ count = pdf_to_int(pdf_dict_gets(obj, "N"));
+
+ pdf_drop_obj(obj);
+
+ stm = pdf_open_stream(xref, num, gen);
+
+ for (i = 0; i < count; i++)
+ {
+ pdf_xref_entry *entry;
+
+ tok = pdf_lex(stm, &buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "corrupt object stream (%d %d R)", num, gen);
+
+ n = buf.i;
+ if (n < 0)
+ {
+ fz_warn(ctx, "ignoring object with invalid object number (%d %d R)", n, i);
+ continue;
+ }
+ else if (n > MAX_OBJECT_NUMBER)
+ {
+ fz_warn(ctx, "ignoring object with invalid object number (%d %d R)", n, i);
+ continue;
+ }
+
+ entry = pdf_get_populating_xref_entry(xref, n);
+ entry->ofs = num;
+ entry->gen = i;
+ entry->stm_ofs = 0;
+ pdf_drop_obj(entry->obj);
+ entry->obj = NULL;
+ entry->type = 'o';
+
+ tok = pdf_lex(stm, &buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "corrupt object stream (%d %d R)", num, gen);
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stm);
+ pdf_lexbuf_fin(&buf);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load object stream object (%d %d R)", num, gen);
+ }
+}
+
+/* Entered with file locked, remains locked throughout. */
+void
+pdf_repair_xref(pdf_document *xref, pdf_lexbuf *buf)
+{
+ pdf_obj *dict, *obj = NULL;
+ pdf_obj *length;
+
+ pdf_obj *encrypt = NULL;
+ pdf_obj *id = NULL;
+ pdf_obj *root = NULL;
+ pdf_obj *info = NULL;
+
+ struct entry *list = NULL;
+ int listlen;
+ int listcap;
+ int maxnum = 0;
+
+ int num = 0;
+ int gen = 0;
+ int tmpofs, numofs = 0, genofs = 0;
+ int stm_len, stm_ofs = 0;
+ pdf_token tok;
+ int next;
+ int i, n, c;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(encrypt);
+ fz_var(id);
+ fz_var(root);
+ fz_var(info);
+ fz_var(list);
+ fz_var(obj);
+
+ xref->dirty = 1;
+
+ fz_seek(xref->file, 0, 0);
+
+ fz_try(ctx)
+ {
+ pdf_xref_entry *entry;
+ listlen = 0;
+ listcap = 1024;
+ list = fz_malloc_array(ctx, listcap, sizeof(struct entry));
+
+ /* look for '%PDF' version marker within first kilobyte of file */
+ n = fz_read(xref->file, (unsigned char *)buf->scratch, fz_mini(buf->size, 1024));
+ if (n < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot read from file");
+
+ fz_seek(xref->file, 0, 0);
+ for (i = 0; i < n - 4; i++)
+ {
+ if (memcmp(&buf->scratch[i], "%PDF", 4) == 0)
+ {
+ fz_seek(xref->file, i + 8, 0); /* skip "%PDF-X.Y" */
+ break;
+ }
+ }
+
+ /* skip comment line after version marker since some generators
+ * forget to terminate the comment with a newline */
+ c = fz_read_byte(xref->file);
+ while (c >= 0 && (c == ' ' || c == '%'))
+ c = fz_read_byte(xref->file);
+ fz_unread_byte(xref->file);
+
+ while (1)
+ {
+ tmpofs = fz_tell(xref->file);
+ if (tmpofs < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot tell in file");
+
+ fz_try(ctx)
+ {
+ tok = pdf_lex(xref->file, buf);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "ignoring the rest of the file");
+ break;
+ }
+
+ /* If we have the next token already, then we'll jump
+ * back here, rather than going through the top of
+ * the loop. */
+ have_next_token:
+
+ if (tok == PDF_TOK_INT)
+ {
+ numofs = genofs;
+ num = gen;
+ genofs = tmpofs;
+ gen = buf->i;
+ }
+
+ else if (tok == PDF_TOK_OBJ)
+ {
+ fz_try(ctx)
+ {
+ tok = pdf_repair_obj(xref->file, buf, &stm_ofs, &stm_len, &encrypt, &id, &tmpofs);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* If we haven't seen a root yet, there is nothing
+ * we can do, but give up. Otherwise, we'll make
+ * do. */
+ if (!root)
+ fz_rethrow(ctx);
+ fz_warn(ctx, "cannot parse object (%d %d R) - ignoring rest of file", num, gen);
+ break;
+ }
+
+ if (num <= 0)
+ {
+ fz_warn(ctx, "ignoring object with invalid object number (%d %d R)", num, gen);
+ continue;
+ }
+ else if (num > MAX_OBJECT_NUMBER)
+ {
+ fz_warn(ctx, "ignoring object with invalid object number (%d %d R)", num, gen);
+ continue;
+ }
+
+ gen = fz_clampi(gen, 0, 65535);
+
+ if (listlen + 1 == listcap)
+ {
+ listcap = (listcap * 3) / 2;
+ list = fz_resize_array(ctx, list, listcap, sizeof(struct entry));
+ }
+
+ list[listlen].num = num;
+ list[listlen].gen = gen;
+ list[listlen].ofs = numofs;
+ list[listlen].stm_ofs = stm_ofs;
+ list[listlen].stm_len = stm_len;
+ listlen ++;
+
+ if (num > maxnum)
+ maxnum = num;
+
+ goto have_next_token;
+ }
+
+ /* trailer dictionary */
+ else if (tok == PDF_TOK_OPEN_DICT)
+ {
+ fz_try(ctx)
+ {
+ dict = pdf_parse_dict(xref, xref->file, buf);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* If we haven't seen a root yet, there is nothing
+ * we can do, but give up. Otherwise, we'll make
+ * do. */
+ if (!root)
+ fz_rethrow(ctx);
+ fz_warn(ctx, "cannot parse trailer dictionary - ignoring rest of file");
+ break;
+ }
+
+ obj = pdf_dict_gets(dict, "Encrypt");
+ if (obj)
+ {
+ pdf_drop_obj(encrypt);
+ encrypt = pdf_keep_obj(obj);
+ }
+
+ obj = pdf_dict_gets(dict, "ID");
+ if (obj)
+ {
+ pdf_drop_obj(id);
+ id = pdf_keep_obj(obj);
+ }
+
+ obj = pdf_dict_gets(dict, "Root");
+ if (obj)
+ {
+ pdf_drop_obj(root);
+ root = pdf_keep_obj(obj);
+ }
+
+ obj = pdf_dict_gets(dict, "Info");
+ if (obj)
+ {
+ pdf_drop_obj(info);
+ info = pdf_keep_obj(obj);
+ }
+
+ pdf_drop_obj(dict);
+ obj = NULL;
+ }
+
+ else if (tok == PDF_TOK_ERROR)
+ fz_read_byte(xref->file);
+
+ else if (tok == PDF_TOK_EOF)
+ break;
+ }
+
+ /* make xref reasonable */
+
+ /*
+ Dummy access to entry to assure sufficient space in the xref table
+ and avoid repeated reallocs in the loop
+ */
+ (void)pdf_get_populating_xref_entry(xref, maxnum);
+
+ for (i = 0; i < listlen; i++)
+ {
+ entry = pdf_get_populating_xref_entry(xref, list[i].num);
+ entry->type = 'n';
+ entry->ofs = list[i].ofs;
+ entry->gen = list[i].gen;
+
+ entry->stm_ofs = list[i].stm_ofs;
+
+ /* correct stream length for unencrypted documents */
+ if (!encrypt && list[i].stm_len >= 0)
+ {
+ dict = pdf_load_object(xref, list[i].num, list[i].gen);
+
+ length = pdf_new_int(ctx, list[i].stm_len);
+ pdf_dict_puts(dict, "Length", length);
+ pdf_drop_obj(length);
+
+ pdf_drop_obj(dict);
+ }
+ }
+
+ entry = pdf_get_populating_xref_entry(xref, 0);
+ entry->type = 'f';
+ entry->ofs = 0;
+ entry->gen = 65535;
+ entry->stm_ofs = 0;
+ entry->obj = NULL;
+
+ next = 0;
+ for (i = pdf_xref_len(xref) - 1; i >= 0; i--)
+ {
+ entry = pdf_get_populating_xref_entry(xref, i);
+ if (entry->type == 'f')
+ {
+ entry->ofs = next;
+ if (entry->gen < 65535)
+ entry->gen ++;
+ next = i;
+ }
+ }
+
+ /* create a repaired trailer, Root will be added later */
+
+ obj = pdf_new_dict(ctx, 5);
+ /* During repair there is only a single xref section */
+ pdf_set_populating_xref_trailer(xref, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ obj = pdf_new_int(ctx, maxnum + 1);
+ pdf_dict_puts(pdf_trailer(xref), "Size", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ if (root)
+ {
+ pdf_dict_puts(pdf_trailer(xref), "Root", root);
+ pdf_drop_obj(root);
+ root = NULL;
+ }
+ if (info)
+ {
+ pdf_dict_puts(pdf_trailer(xref), "Info", info);
+ pdf_drop_obj(info);
+ info = NULL;
+ }
+
+ if (encrypt)
+ {
+ if (pdf_is_indirect(encrypt))
+ {
+ /* create new reference with non-NULL xref pointer */
+ obj = pdf_new_indirect(ctx, pdf_to_num(encrypt), pdf_to_gen(encrypt), xref);
+ pdf_drop_obj(encrypt);
+ encrypt = obj;
+ obj = NULL;
+ }
+ pdf_dict_puts(pdf_trailer(xref), "Encrypt", encrypt);
+ pdf_drop_obj(encrypt);
+ encrypt = NULL;
+ }
+
+ if (id)
+ {
+ if (pdf_is_indirect(id))
+ {
+ /* create new reference with non-NULL xref pointer */
+ obj = pdf_new_indirect(ctx, pdf_to_num(id), pdf_to_gen(id), xref);
+ pdf_drop_obj(id);
+ id = obj;
+ obj = NULL;
+ }
+ pdf_dict_puts(pdf_trailer(xref), "ID", id);
+ pdf_drop_obj(id);
+ id = NULL;
+ }
+
+ fz_free(ctx, list);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(encrypt);
+ pdf_drop_obj(id);
+ pdf_drop_obj(root);
+ pdf_drop_obj(obj);
+ pdf_drop_obj(info);
+ fz_free(ctx, list);
+ fz_rethrow(ctx);
+ }
+}
+
+void
+pdf_repair_obj_stms(pdf_document *xref)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *dict;
+ int i;
+ int xref_len = pdf_xref_len(xref);
+
+ for (i = 0; i < xref_len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_populating_xref_entry(xref, i);
+
+ if (entry->stm_ofs)
+ {
+ dict = pdf_load_object(xref, i, 0);
+ fz_try(ctx)
+ {
+ if (!strcmp(pdf_to_name(pdf_dict_gets(dict, "Type")), "ObjStm"))
+ pdf_repair_obj_stm(xref, i, 0);
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(dict);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ }
+ }
+
+ /* Ensure that streamed objects reside inside a known non-streamed object */
+ for (i = 0; i < xref_len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_populating_xref_entry(xref, i);
+
+ if (entry->type == 'o' && pdf_get_populating_xref_entry(xref, entry->ofs)->type != 'n')
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "invalid reference to non-object-stream: %d (%d 0 R)", entry->ofs, i);
+ }
+}
diff --git a/source/pdf/pdf-shade.c b/source/pdf/pdf-shade.c
new file mode 100644
index 00000000..41ddcc1a
--- /dev/null
+++ b/source/pdf/pdf-shade.c
@@ -0,0 +1,498 @@
+#include "mupdf/pdf.h"
+
+/* FIXME: Remove this somehow */
+#define FUNSEGS 32 /* size of sampled mesh for function-based shadings */
+
+/* Sample various functions into lookup tables */
+
+static void
+pdf_sample_composite_shade_function(fz_context *ctx, fz_shade *shade, fz_function *func, float t0, float t1)
+{
+ int i;
+ float t;
+
+ for (i = 0; i < 256; i++)
+ {
+ t = t0 + (i / 255.0f) * (t1 - t0);
+ fz_eval_function(ctx, func, &t, 1, shade->function[i], shade->colorspace->n);
+ shade->function[i][shade->colorspace->n] = 1;
+ }
+}
+
+static void
+pdf_sample_component_shade_function(fz_context *ctx, fz_shade *shade, int funcs, fz_function **func, float t0, float t1)
+{
+ int i, k;
+ float t;
+
+ for (i = 0; i < 256; i++)
+ {
+ t = t0 + (i / 255.0f) * (t1 - t0);
+ for (k = 0; k < funcs; k++)
+ fz_eval_function(ctx, func[k], &t, 1, &shade->function[i][k], 1);
+ shade->function[i][k] = 1;
+ }
+}
+
+static void
+pdf_sample_shade_function(fz_context *ctx, fz_shade *shade, int funcs, fz_function **func, float t0, float t1)
+{
+ shade->use_function = 1;
+ if (funcs == 1)
+ pdf_sample_composite_shade_function(ctx, shade, func[0], t0, t1);
+ else
+ pdf_sample_component_shade_function(ctx, shade, funcs, func, t0, t1);
+}
+
+/* Type 1-3 -- Function-based, linear and radial shadings */
+
+static void
+pdf_load_function_based_shading(fz_shade *shade, pdf_document *xref, pdf_obj *dict, fz_function *func)
+{
+ pdf_obj *obj;
+ float x0, y0, x1, y1;
+ float fv[2];
+ fz_matrix matrix;
+ int xx, yy;
+ fz_context *ctx = xref->ctx;
+ float *p;
+
+ x0 = y0 = 0;
+ x1 = y1 = 1;
+ obj = pdf_dict_gets(dict, "Domain");
+ if (obj)
+ {
+ x0 = pdf_to_real(pdf_array_get(obj, 0));
+ x1 = pdf_to_real(pdf_array_get(obj, 1));
+ y0 = pdf_to_real(pdf_array_get(obj, 2));
+ y1 = pdf_to_real(pdf_array_get(obj, 3));
+ }
+
+ obj = pdf_dict_gets(dict, "Matrix");
+ if (obj)
+ pdf_to_matrix(ctx, obj, &matrix);
+ else
+ matrix = fz_identity;
+ shade->u.f.matrix = matrix;
+ shade->u.f.xdivs = FUNSEGS;
+ shade->u.f.ydivs = FUNSEGS;
+ shade->u.f.fn_vals = fz_malloc(ctx, (FUNSEGS+1)*(FUNSEGS+1)*shade->colorspace->n*sizeof(float));
+ shade->u.f.domain[0][0] = x0;
+ shade->u.f.domain[0][1] = y0;
+ shade->u.f.domain[1][0] = x1;
+ shade->u.f.domain[1][1] = y1;
+
+ p = shade->u.f.fn_vals;
+ for (yy = 0; yy <= FUNSEGS; yy++)
+ {
+ fv[1] = y0 + (y1 - y0) * yy / FUNSEGS;
+
+ for (xx = 0; xx <= FUNSEGS; xx++)
+ {
+ fv[0] = x0 + (x1 - x0) * xx / FUNSEGS;
+
+ fz_eval_function(ctx, func, fv, 2, p, shade->colorspace->n);
+ p += shade->colorspace->n;
+ }
+ }
+}
+
+static void
+pdf_load_linear_shading(fz_shade *shade, pdf_document *xref, pdf_obj *dict, int funcs, fz_function **func)
+{
+ pdf_obj *obj;
+ float d0, d1;
+ int e0, e1;
+ fz_context *ctx = xref->ctx;
+
+ obj = pdf_dict_gets(dict, "Coords");
+ shade->u.l_or_r.coords[0][0] = pdf_to_real(pdf_array_get(obj, 0));
+ shade->u.l_or_r.coords[0][1] = pdf_to_real(pdf_array_get(obj, 1));
+ shade->u.l_or_r.coords[1][0] = pdf_to_real(pdf_array_get(obj, 2));
+ shade->u.l_or_r.coords[1][1] = pdf_to_real(pdf_array_get(obj, 3));
+
+ d0 = 0;
+ d1 = 1;
+ obj = pdf_dict_gets(dict, "Domain");
+ if (obj)
+ {
+ d0 = pdf_to_real(pdf_array_get(obj, 0));
+ d1 = pdf_to_real(pdf_array_get(obj, 1));
+ }
+
+ e0 = e1 = 0;
+ obj = pdf_dict_gets(dict, "Extend");
+ if (obj)
+ {
+ e0 = pdf_to_bool(pdf_array_get(obj, 0));
+ e1 = pdf_to_bool(pdf_array_get(obj, 1));
+ }
+
+ pdf_sample_shade_function(ctx, shade, funcs, func, d0, d1);
+
+ shade->u.l_or_r.extend[0] = e0;
+ shade->u.l_or_r.extend[1] = e1;
+}
+
+static void
+pdf_load_radial_shading(fz_shade *shade, pdf_document *xref, pdf_obj *dict, int funcs, fz_function **func)
+{
+ pdf_obj *obj;
+ float d0, d1;
+ int e0, e1;
+ fz_context *ctx = xref->ctx;
+
+ obj = pdf_dict_gets(dict, "Coords");
+ shade->u.l_or_r.coords[0][0] = pdf_to_real(pdf_array_get(obj, 0));
+ shade->u.l_or_r.coords[0][1] = pdf_to_real(pdf_array_get(obj, 1));
+ shade->u.l_or_r.coords[0][2] = pdf_to_real(pdf_array_get(obj, 2));
+ shade->u.l_or_r.coords[1][0] = pdf_to_real(pdf_array_get(obj, 3));
+ shade->u.l_or_r.coords[1][1] = pdf_to_real(pdf_array_get(obj, 4));
+ shade->u.l_or_r.coords[1][2] = pdf_to_real(pdf_array_get(obj, 5));
+
+ d0 = 0;
+ d1 = 1;
+ obj = pdf_dict_gets(dict, "Domain");
+ if (obj)
+ {
+ d0 = pdf_to_real(pdf_array_get(obj, 0));
+ d1 = pdf_to_real(pdf_array_get(obj, 1));
+ }
+
+ e0 = e1 = 0;
+ obj = pdf_dict_gets(dict, "Extend");
+ if (obj)
+ {
+ e0 = pdf_to_bool(pdf_array_get(obj, 0));
+ e1 = pdf_to_bool(pdf_array_get(obj, 1));
+ }
+
+ pdf_sample_shade_function(ctx, shade, funcs, func, d0, d1);
+
+ shade->u.l_or_r.extend[0] = e0;
+ shade->u.l_or_r.extend[1] = e1;
+}
+
+/* Type 4-7 -- Triangle and patch mesh shadings */
+
+struct mesh_params
+{
+ int vprow;
+ int bpflag;
+ int bpcoord;
+ int bpcomp;
+ float x0, x1;
+ float y0, y1;
+ float c0[FZ_MAX_COLORS];
+ float c1[FZ_MAX_COLORS];
+};
+
+static void
+pdf_load_mesh_params(fz_shade *shade, pdf_document *xref, pdf_obj *dict)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *obj;
+ int i, n;
+
+ shade->u.m.x0 = shade->u.m.y0 = 0;
+ shade->u.m.x1 = shade->u.m.y1 = 1;
+ for (i = 0; i < FZ_MAX_COLORS; i++)
+ {
+ shade->u.m.c0[i] = 0;
+ shade->u.m.c1[i] = 1;
+ }
+
+ shade->u.m.vprow = pdf_to_int(pdf_dict_gets(dict, "VerticesPerRow"));
+ shade->u.m.bpflag = pdf_to_int(pdf_dict_gets(dict, "BitsPerFlag"));
+ shade->u.m.bpcoord = pdf_to_int(pdf_dict_gets(dict, "BitsPerCoordinate"));
+ shade->u.m.bpcomp = pdf_to_int(pdf_dict_gets(dict, "BitsPerComponent"));
+
+ obj = pdf_dict_gets(dict, "Decode");
+ if (pdf_array_len(obj) >= 6)
+ {
+ n = (pdf_array_len(obj) - 4) / 2;
+ shade->u.m.x0 = pdf_to_real(pdf_array_get(obj, 0));
+ shade->u.m.x1 = pdf_to_real(pdf_array_get(obj, 1));
+ shade->u.m.y0 = pdf_to_real(pdf_array_get(obj, 2));
+ shade->u.m.y1 = pdf_to_real(pdf_array_get(obj, 3));
+ for (i = 0; i < n; i++)
+ {
+ shade->u.m.c0[i] = pdf_to_real(pdf_array_get(obj, 4 + i * 2));
+ shade->u.m.c1[i] = pdf_to_real(pdf_array_get(obj, 5 + i * 2));
+ }
+ }
+
+ if (shade->u.m.vprow < 2 && shade->type == 5)
+ {
+ fz_warn(ctx, "Too few vertices per row (%d)", shade->u.m.vprow);
+ shade->u.m.vprow = 2;
+ }
+
+ if (shade->u.m.bpflag != 2 && shade->u.m.bpflag != 4 && shade->u.m.bpflag != 8 &&
+ shade->type != 5)
+ {
+ fz_warn(ctx, "Invalid number of bits per flag (%d)", shade->u.m.bpflag);
+ shade->u.m.bpflag = 8;
+ }
+
+ if (shade->u.m.bpcoord != 1 && shade->u.m.bpcoord != 2 && shade->u.m.bpcoord != 4 &&
+ shade->u.m.bpcoord != 8 && shade->u.m.bpcoord != 12 && shade->u.m.bpcoord != 16 &&
+ shade->u.m.bpcoord != 24 && shade->u.m.bpcoord != 32)
+ {
+ fz_warn(ctx, "Invalid number of bits per coordinate (%d)", shade->u.m.bpcoord);
+ shade->u.m.bpcoord = 8;
+ }
+
+ if (shade->u.m.bpcomp != 1 && shade->u.m.bpcomp != 2 && shade->u.m.bpcomp != 4 &&
+ shade->u.m.bpcomp != 8 && shade->u.m.bpcomp != 12 && shade->u.m.bpcomp != 16)
+ {
+ fz_warn(ctx, "Invalid number of bits per component (%d)", shade->u.m.bpcomp);
+ shade->u.m.bpcomp = 8;
+ }
+}
+
+static void
+pdf_load_type4_shade(fz_shade *shade, pdf_document *xref, pdf_obj *dict,
+ int funcs, fz_function **func)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_load_mesh_params(shade, xref, dict);
+
+ if (funcs > 0)
+ pdf_sample_shade_function(ctx, shade, funcs, func, shade->u.m.c0[0], shade->u.m.c1[0]);
+
+ shade->buffer = pdf_load_compressed_stream(xref, pdf_to_num(dict), pdf_to_gen(dict));
+}
+
+static void
+pdf_load_type5_shade(fz_shade *shade, pdf_document *xref, pdf_obj *dict,
+ int funcs, fz_function **func)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_load_mesh_params(shade, xref, dict);
+
+ if (funcs > 0)
+ pdf_sample_shade_function(ctx, shade, funcs, func, shade->u.m.c0[0], shade->u.m.c1[0]);
+
+ shade->buffer = pdf_load_compressed_stream(xref, pdf_to_num(dict), pdf_to_gen(dict));
+}
+
+/* Type 6 & 7 -- Patch mesh shadings */
+
+static void
+pdf_load_type6_shade(fz_shade *shade, pdf_document *xref, pdf_obj *dict,
+ int funcs, fz_function **func)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_load_mesh_params(shade, xref, dict);
+
+ if (funcs > 0)
+ pdf_sample_shade_function(ctx, shade, funcs, func, shade->u.m.c0[0], shade->u.m.c1[0]);
+
+ shade->buffer = pdf_load_compressed_stream(xref, pdf_to_num(dict), pdf_to_gen(dict));
+}
+
+static void
+pdf_load_type7_shade(fz_shade *shade, pdf_document *xref, pdf_obj *dict,
+ int funcs, fz_function **func)
+{
+ fz_context *ctx = xref->ctx;
+
+ pdf_load_mesh_params(shade, xref, dict);
+
+ if (funcs > 0)
+ pdf_sample_shade_function(ctx, shade, funcs, func, shade->u.m.c0[0], shade->u.m.c1[0]);
+
+ shade->buffer = pdf_load_compressed_stream(xref, pdf_to_num(dict), pdf_to_gen(dict));
+}
+
+/* Load all of the shading dictionary parameters, then switch on the shading type. */
+
+static fz_shade *
+pdf_load_shading_dict(pdf_document *xref, pdf_obj *dict, const fz_matrix *transform)
+{
+ fz_shade *shade = NULL;
+ fz_function *func[FZ_MAX_COLORS] = { NULL };
+ pdf_obj *obj;
+ int funcs = 0;
+ int type = 0;
+ int i, in, out;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(shade);
+ fz_var(func);
+ fz_var(funcs);
+ fz_var(type);
+
+ fz_try(ctx)
+ {
+ shade = fz_malloc_struct(ctx, fz_shade);
+ FZ_INIT_STORABLE(shade, 1, fz_free_shade_imp);
+ shade->type = FZ_MESH_TYPE4;
+ shade->use_background = 0;
+ shade->use_function = 0;
+ shade->matrix = *transform;
+ shade->bbox = fz_infinite_rect;
+
+ shade->colorspace = NULL;
+
+ funcs = 0;
+
+ obj = pdf_dict_gets(dict, "ShadingType");
+ type = pdf_to_int(obj);
+
+ obj = pdf_dict_gets(dict, "ColorSpace");
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "shading colorspace is missing");
+ shade->colorspace = pdf_load_colorspace(xref, obj);
+
+ obj = pdf_dict_gets(dict, "Background");
+ if (obj)
+ {
+ shade->use_background = 1;
+ for (i = 0; i < shade->colorspace->n; i++)
+ shade->background[i] = pdf_to_real(pdf_array_get(obj, i));
+ }
+
+ obj = pdf_dict_gets(dict, "BBox");
+ if (pdf_is_array(obj))
+ pdf_to_rect(ctx, obj, &shade->bbox);
+
+ obj = pdf_dict_gets(dict, "Function");
+ if (pdf_is_dict(obj))
+ {
+ funcs = 1;
+
+ if (type == 1)
+ in = 2;
+ else
+ in = 1;
+ out = shade->colorspace->n;
+
+ func[0] = pdf_load_function(xref, obj, in, out);
+ if (!func[0])
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot load shading function (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ }
+ else if (pdf_is_array(obj))
+ {
+ funcs = pdf_array_len(obj);
+ if (funcs != 1 && funcs != shade->colorspace->n)
+ {
+ funcs = 0;
+ fz_throw(ctx, FZ_ERROR_GENERIC, "incorrect number of shading functions");
+ }
+ if (funcs > FZ_MAX_COLORS)
+ {
+ funcs = 0;
+ fz_throw(ctx, FZ_ERROR_GENERIC, "too many shading functions");
+ }
+
+ if (type == 1)
+ in = 2;
+ else
+ in = 1;
+ out = 1;
+
+ for (i = 0; i < funcs; i++)
+ {
+ func[i] = pdf_load_function(xref, pdf_array_get(obj, i), in, out);
+ if (!func[i])
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot load shading function (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ }
+ }
+ else if (type < 4)
+ {
+ /* Functions are compulsory for types 1,2,3 */
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot load shading function (%d %d R)", pdf_to_num(obj), pdf_to_gen(obj));
+ }
+
+ shade->type = type;
+ switch (type)
+ {
+ case 1: pdf_load_function_based_shading(shade, xref, dict, func[0]); break;
+ case 2: pdf_load_linear_shading(shade, xref, dict, funcs, func); break;
+ case 3: pdf_load_radial_shading(shade, xref, dict, funcs, func); break;
+ case 4: pdf_load_type4_shade(shade, xref, dict, funcs, func); break;
+ case 5: pdf_load_type5_shade(shade, xref, dict, funcs, func); break;
+ case 6: pdf_load_type6_shade(shade, xref, dict, funcs, func); break;
+ case 7: pdf_load_type7_shade(shade, xref, dict, funcs, func); break;
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown shading type: %d", type);
+ }
+ }
+ fz_always(ctx)
+ {
+ for (i = 0; i < funcs; i++)
+ if (func[i])
+ fz_drop_function(ctx, func[i]);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_shade(ctx, shade);
+
+ fz_rethrow_message(ctx, "cannot load shading type %d (%d %d R)", type, pdf_to_num(dict), pdf_to_gen(dict));
+ }
+ return shade;
+}
+
+static unsigned int
+fz_shade_size(fz_shade *s)
+{
+ if (s == NULL)
+ return 0;
+ if (s->type == FZ_FUNCTION_BASED)
+ return sizeof(*s) + sizeof(float) * s->u.f.xdivs * s->u.f.ydivs * s->colorspace->n;
+ return sizeof(*s) + fz_compressed_buffer_size(s->buffer);
+}
+
+fz_shade *
+pdf_load_shading(pdf_document *xref, pdf_obj *dict)
+{
+ fz_matrix mat;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+ fz_shade *shade;
+
+ if ((shade = pdf_find_item(ctx, fz_free_shade_imp, dict)))
+ {
+ return shade;
+ }
+
+ /* Type 2 pattern dictionary */
+ if (pdf_dict_gets(dict, "PatternType"))
+ {
+ obj = pdf_dict_gets(dict, "Matrix");
+ if (obj)
+ pdf_to_matrix(ctx, obj, &mat);
+ else
+ mat = fz_identity;
+
+ obj = pdf_dict_gets(dict, "ExtGState");
+ if (obj)
+ {
+ if (pdf_dict_gets(obj, "CA") || pdf_dict_gets(obj, "ca"))
+ {
+ fz_warn(ctx, "shading with alpha not supported");
+ }
+ }
+
+ obj = pdf_dict_gets(dict, "Shading");
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: missing shading dictionary");
+
+ shade = pdf_load_shading_dict(xref, obj, &mat);
+ }
+
+ /* Naked shading dictionary */
+ else
+ {
+ shade = pdf_load_shading_dict(xref, dict, &fz_identity);
+ }
+
+ pdf_store_item(ctx, dict, shade, fz_shade_size(shade));
+
+ return shade;
+}
diff --git a/source/pdf/pdf-store.c b/source/pdf/pdf-store.c
new file mode 100644
index 00000000..d3873e76
--- /dev/null
+++ b/source/pdf/pdf-store.c
@@ -0,0 +1,76 @@
+#include "mupdf/pdf.h"
+
+static int
+pdf_make_hash_key(fz_store_hash *hash, void *key_)
+{
+ pdf_obj *key = (pdf_obj *)key_;
+
+ if (!pdf_is_indirect(key))
+ return 0;
+ hash->u.i.i0 = pdf_to_num(key);
+ hash->u.i.i1 = pdf_to_gen(key);
+ return 1;
+}
+
+static void *
+pdf_keep_key(fz_context *ctx, void *key)
+{
+ return (void *)pdf_keep_obj((pdf_obj *)key);
+}
+
+static void
+pdf_drop_key(fz_context *ctx, void *key)
+{
+ pdf_drop_obj((pdf_obj *)key);
+}
+
+static int
+pdf_cmp_key(void *k0, void *k1)
+{
+ return pdf_objcmp((pdf_obj *)k0, (pdf_obj *)k1);
+}
+
+#ifndef NDEBUG
+static void
+pdf_debug_key(FILE *out, void *key_)
+{
+ pdf_obj *key = (pdf_obj *)key_;
+
+ if (pdf_is_indirect(key))
+ {
+ fprintf(out, "(%d %d R) ", pdf_to_num(key), pdf_to_gen(key));
+ } else
+ pdf_fprint_obj(out, key, 0);
+}
+#endif
+
+static fz_store_type pdf_obj_store_type =
+{
+ pdf_make_hash_key,
+ pdf_keep_key,
+ pdf_drop_key,
+ pdf_cmp_key,
+#ifndef NDEBUG
+ pdf_debug_key
+#endif
+};
+
+void
+pdf_store_item(fz_context *ctx, pdf_obj *key, void *val, unsigned int itemsize)
+{
+ void *existing;
+ existing = fz_store_item(ctx, key, val, itemsize, &pdf_obj_store_type);
+ assert(existing == NULL);
+}
+
+void *
+pdf_find_item(fz_context *ctx, fz_store_free_fn *free, pdf_obj *key)
+{
+ return fz_find_item(ctx, free, key, &pdf_obj_store_type);
+}
+
+void
+pdf_remove_item(fz_context *ctx, fz_store_free_fn *free, pdf_obj *key)
+{
+ fz_remove_item(ctx, free, key, &pdf_obj_store_type);
+}
diff --git a/source/pdf/pdf-stream.c b/source/pdf/pdf-stream.c
new file mode 100644
index 00000000..7e74b666
--- /dev/null
+++ b/source/pdf/pdf-stream.c
@@ -0,0 +1,564 @@
+#include "mupdf/pdf.h"
+
+/*
+ * Check if an object is a stream or not.
+ */
+int
+pdf_is_stream(pdf_document *xref, int num, int gen)
+{
+ pdf_xref_entry *entry;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ return 0;
+
+ pdf_cache_object(xref, num, gen);
+
+ entry = pdf_get_xref_entry(xref, num);
+ return entry->stm_ofs != 0 || entry->stm_buf;
+}
+
+/*
+ * Scan stream dictionary for an explicit /Crypt filter
+ */
+static int
+pdf_stream_has_crypt(fz_context *ctx, pdf_obj *stm)
+{
+ pdf_obj *filters;
+ pdf_obj *obj;
+ int i;
+
+ filters = pdf_dict_getsa(stm, "Filter", "F");
+ if (filters)
+ {
+ if (!strcmp(pdf_to_name(filters), "Crypt"))
+ return 1;
+ if (pdf_is_array(filters))
+ {
+ int n = pdf_array_len(filters);
+ for (i = 0; i < n; i++)
+ {
+ obj = pdf_array_get(filters, i);
+ if (!strcmp(pdf_to_name(obj), "Crypt"))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * Create a filter given a name and param dictionary.
+ */
+static fz_stream *
+build_filter(fz_stream *chain, pdf_document * xref, pdf_obj * f, pdf_obj * p, int num, int gen, fz_compression_params *params)
+{
+ fz_context *ctx = chain->ctx;
+ char *s = pdf_to_name(f);
+
+ int predictor = pdf_to_int(pdf_dict_gets(p, "Predictor"));
+ pdf_obj *columns_obj = pdf_dict_gets(p, "Columns");
+ int columns = pdf_to_int(columns_obj);
+ int colors = pdf_to_int(pdf_dict_gets(p, "Colors"));
+ int bpc = pdf_to_int(pdf_dict_gets(p, "BitsPerComponent"));
+
+ if (!strcmp(s, "ASCIIHexDecode") || !strcmp(s, "AHx"))
+ return fz_open_ahxd(chain);
+
+ else if (!strcmp(s, "ASCII85Decode") || !strcmp(s, "A85"))
+ return fz_open_a85d(chain);
+
+ else if (!strcmp(s, "CCITTFaxDecode") || !strcmp(s, "CCF"))
+ {
+ pdf_obj *k = pdf_dict_gets(p, "K");
+ pdf_obj *eol = pdf_dict_gets(p, "EndOfLine");
+ pdf_obj *eba = pdf_dict_gets(p, "EncodedByteAlign");
+ pdf_obj *rows = pdf_dict_gets(p, "Rows");
+ pdf_obj *eob = pdf_dict_gets(p, "EndOfBlock");
+ pdf_obj *bi1 = pdf_dict_gets(p, "BlackIs1");
+ if (params)
+ {
+ /* We will shortstop here */
+ params->type = FZ_IMAGE_FAX;
+ params->u.fax.k = (k ? pdf_to_int(k) : 0);
+ params->u.fax.end_of_line = (eol ? pdf_to_bool(eol) : 0);
+ params->u.fax.encoded_byte_align = (eba ? pdf_to_bool(eba) : 0);
+ params->u.fax.columns = (columns_obj ? columns : 1728);
+ params->u.fax.rows = (rows ? pdf_to_int(rows) : 0);
+ params->u.fax.end_of_block = (eob ? pdf_to_bool(eob) : 1);
+ params->u.fax.black_is_1 = (bi1 ? pdf_to_bool(bi1) : 0);
+ return chain;
+ }
+ return fz_open_faxd(chain,
+ k ? pdf_to_int(k) : 0,
+ eol ? pdf_to_bool(eol) : 0,
+ eba ? pdf_to_bool(eba) : 0,
+ columns_obj ? columns : 1728,
+ rows ? pdf_to_int(rows) : 0,
+ eob ? pdf_to_bool(eob) : 1,
+ bi1 ? pdf_to_bool(bi1) : 0);
+ }
+
+ else if (!strcmp(s, "DCTDecode") || !strcmp(s, "DCT"))
+ {
+ pdf_obj *ct = pdf_dict_gets(p, "ColorTransform");
+ if (params)
+ {
+ /* We will shortstop here */
+ params->type = FZ_IMAGE_JPEG;
+ params->u.jpeg.color_transform = (ct ? pdf_to_int(ct) : -1);
+ return chain;
+ }
+ return fz_open_dctd(chain, ct ? pdf_to_int(ct) : -1);
+ }
+
+ else if (!strcmp(s, "RunLengthDecode") || !strcmp(s, "RL"))
+ {
+ if (params)
+ {
+ /* We will shortstop here */
+ params->type = FZ_IMAGE_RLD;
+ return chain;
+ }
+ return fz_open_rld(chain);
+ }
+ else if (!strcmp(s, "FlateDecode") || !strcmp(s, "Fl"))
+ {
+ if (params)
+ {
+ /* We will shortstop here */
+ params->type = FZ_IMAGE_FLATE;
+ params->u.flate.predictor = predictor;
+ params->u.flate.columns = columns;
+ params->u.flate.colors = colors;
+ params->u.flate.bpc = bpc;
+ return chain;
+ }
+ chain = fz_open_flated(chain);
+ if (predictor > 1)
+ chain = fz_open_predict(chain, predictor, columns, colors, bpc);
+ return chain;
+ }
+
+ else if (!strcmp(s, "LZWDecode") || !strcmp(s, "LZW"))
+ {
+ pdf_obj *ec = pdf_dict_gets(p, "EarlyChange");
+ if (params)
+ {
+ /* We will shortstop here */
+ params->type = FZ_IMAGE_LZW;
+ params->u.lzw.predictor = predictor;
+ params->u.lzw.columns = columns;
+ params->u.lzw.colors = colors;
+ params->u.lzw.bpc = bpc;
+ params->u.lzw.early_change = (ec ? pdf_to_int(ec) : 1);
+ return chain;
+ }
+ chain = fz_open_lzwd(chain, ec ? pdf_to_int(ec) : 1);
+ if (predictor > 1)
+ chain = fz_open_predict(chain, predictor, columns, colors, bpc);
+ return chain;
+ }
+
+ else if (!strcmp(s, "JBIG2Decode"))
+ {
+ fz_buffer *globals = NULL;
+ pdf_obj *obj = pdf_dict_gets(p, "JBIG2Globals");
+ if (obj)
+ globals = pdf_load_stream(xref, pdf_to_num(obj), pdf_to_gen(obj));
+ /* fz_open_jbig2d takes possession of globals */
+ return fz_open_jbig2d(chain, globals);
+ }
+
+ else if (!strcmp(s, "JPXDecode"))
+ return chain; /* JPX decoding is special cased in the image loading code */
+
+ else if (!strcmp(s, "Crypt"))
+ {
+ pdf_obj *name;
+
+ if (!xref->crypt)
+ {
+ fz_warn(ctx, "crypt filter in unencrypted document");
+ return chain;
+ }
+
+ name = pdf_dict_gets(p, "Name");
+ if (pdf_is_name(name))
+ return pdf_open_crypt_with_filter(chain, xref->crypt, pdf_to_name(name), num, gen);
+
+ return chain;
+ }
+
+ fz_warn(ctx, "unknown filter name (%s)", s);
+ return chain;
+}
+
+/*
+ * Build a chain of filters given filter names and param dicts.
+ * If head is given, start filter chain with it.
+ * Assume ownership of head.
+ */
+static fz_stream *
+build_filter_chain(fz_stream *chain, pdf_document *xref, pdf_obj *fs, pdf_obj *ps, int num, int gen, fz_compression_params *params)
+{
+ pdf_obj *f;
+ pdf_obj *p;
+ int i, n;
+
+ n = pdf_array_len(fs);
+ for (i = 0; i < n; i++)
+ {
+ f = pdf_array_get(fs, i);
+ p = pdf_array_get(ps, i);
+ chain = build_filter(chain, xref, f, p, num, gen, (i == n-1 ? params : NULL));
+ }
+
+ return chain;
+}
+
+/*
+ * Build a filter for reading raw stream data.
+ * This is a null filter to constrain reading to the stream length (and to
+ * allow for other people accessing the file), followed by a decryption
+ * filter.
+ *
+ * orig_num and orig_gen are used purely to seed the encryption.
+ */
+static fz_stream *
+pdf_open_raw_filter(fz_stream *chain, pdf_document *xref, pdf_obj *stmobj, int num, int orig_num, int orig_gen, int offset)
+{
+ fz_context *ctx = chain->ctx;
+ int hascrypt;
+ int len;
+
+ if (num > 0 && num < pdf_xref_len(xref))
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, num);
+ if (entry->stm_buf)
+ return fz_open_buffer(ctx, entry->stm_buf);
+ }
+
+ /* don't close chain when we close this filter */
+ fz_keep_stream(chain);
+
+ len = pdf_to_int(pdf_dict_gets(stmobj, "Length"));
+ chain = fz_open_null(chain, len, offset);
+
+ fz_try(ctx)
+ {
+ hascrypt = pdf_stream_has_crypt(ctx, stmobj);
+ if (xref->crypt && !hascrypt)
+ chain = pdf_open_crypt(chain, xref->crypt, orig_num, orig_gen);
+ }
+ fz_catch(ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(ctx);
+ }
+
+ return chain;
+}
+
+/*
+ * Construct a filter to decode a stream, constraining
+ * to stream length and decrypting.
+ */
+static fz_stream *
+pdf_open_filter(fz_stream *chain, pdf_document *xref, pdf_obj *stmobj, int num, int gen, int offset, fz_compression_params *imparams)
+{
+ pdf_obj *filters;
+ pdf_obj *params;
+
+ filters = pdf_dict_getsa(stmobj, "Filter", "F");
+ params = pdf_dict_getsa(stmobj, "DecodeParms", "DP");
+
+ chain = pdf_open_raw_filter(chain, xref, stmobj, num, num, gen, offset);
+
+ fz_try(xref->ctx)
+ {
+ if (pdf_is_name(filters))
+ chain = build_filter(chain, xref, filters, params, num, gen, imparams);
+ else if (pdf_array_len(filters) > 0)
+ chain = build_filter_chain(chain, xref, filters, params, num, gen, imparams);
+ }
+ fz_catch(xref->ctx)
+ {
+ fz_close(chain);
+ fz_rethrow(xref->ctx);
+ }
+
+ return chain;
+}
+
+/*
+ * Construct a filter to decode a stream, without
+ * constraining to stream length, and without decryption.
+ */
+fz_stream *
+pdf_open_inline_stream(pdf_document *xref, pdf_obj *stmobj, int length, fz_stream *chain, fz_compression_params *imparams)
+{
+ pdf_obj *filters;
+ pdf_obj *params;
+
+ filters = pdf_dict_getsa(stmobj, "Filter", "F");
+ params = pdf_dict_getsa(stmobj, "DecodeParms", "DP");
+
+ /* don't close chain when we close this filter */
+ fz_keep_stream(chain);
+
+ if (pdf_is_name(filters))
+ return build_filter(chain, xref, filters, params, 0, 0, imparams);
+ if (pdf_array_len(filters) > 0)
+ return build_filter_chain(chain, xref, filters, params, 0, 0, imparams);
+
+ return fz_open_null(chain, length, fz_tell(chain));
+}
+
+/*
+ * Open a stream for reading the raw (compressed but decrypted) data.
+ */
+fz_stream *
+pdf_open_raw_stream(pdf_document *xref, int num, int gen)
+{
+ return pdf_open_raw_renumbered_stream(xref, num, gen, num, gen);
+}
+
+fz_stream *
+pdf_open_raw_renumbered_stream(pdf_document *xref, int num, int gen, int orig_num, int orig_gen)
+{
+ pdf_xref_entry *x;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "object id out of range (%d %d R)", num, gen);
+
+ x = pdf_get_xref_entry(xref, num);
+
+ pdf_cache_object(xref, num, gen);
+
+ if (x->stm_ofs == 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "object is not a stream");
+
+ return pdf_open_raw_filter(xref->file, xref, x->obj, num, orig_num, orig_gen, x->stm_ofs);
+}
+
+static fz_stream *
+pdf_open_image_stream(pdf_document *xref, int num, int gen, int orig_num, int orig_gen, fz_compression_params *params)
+{
+ pdf_xref_entry *x;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "object id out of range (%d %d R)", num, gen);
+
+ x = pdf_get_xref_entry(xref, num);
+
+ pdf_cache_object(xref, num, gen);
+
+ if (x->stm_ofs == 0 && x->stm_buf == NULL)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "object is not a stream");
+
+ return pdf_open_filter(xref->file, xref, x->obj, orig_num, orig_gen, x->stm_ofs, params);
+}
+
+/*
+ * Open a stream for reading uncompressed data.
+ * Put the opened file in xref->stream.
+ * Using xref->file while a stream is open is a Bad idea.
+ */
+fz_stream *
+pdf_open_stream(pdf_document *xref, int num, int gen)
+{
+ return pdf_open_image_stream(xref, num, gen, num, gen, NULL);
+}
+
+fz_stream *
+pdf_open_stream_with_offset(pdf_document *xref, int num, int gen, pdf_obj *dict, int stm_ofs)
+{
+ if (stm_ofs == 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "object is not a stream");
+
+ return pdf_open_filter(xref->file, xref, dict, num, gen, stm_ofs, NULL);
+}
+
+/*
+ * Load raw (compressed but decrypted) contents of a stream into buf.
+ */
+fz_buffer *
+pdf_load_raw_stream(pdf_document *xref, int num, int gen)
+{
+ return pdf_load_raw_renumbered_stream(xref, num, gen, num, gen);
+}
+
+fz_buffer *
+pdf_load_raw_renumbered_stream(pdf_document *xref, int num, int gen, int orig_num, int orig_gen)
+{
+ fz_stream *stm;
+ pdf_obj *dict;
+ int len;
+ fz_buffer *buf;
+
+ if (num > 0 && num < pdf_xref_len(xref))
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, num);
+ if (entry->stm_buf)
+ return fz_keep_buffer(xref->ctx, entry->stm_buf);
+ }
+
+ dict = pdf_load_object(xref, num, gen);
+
+ len = pdf_to_int(pdf_dict_gets(dict, "Length"));
+
+ pdf_drop_obj(dict);
+
+ stm = pdf_open_raw_renumbered_stream(xref, num, gen, orig_num, orig_gen);
+
+ buf = fz_read_all(stm, len);
+
+ fz_close(stm);
+ return buf;
+}
+
+static int
+pdf_guess_filter_length(int len, char *filter)
+{
+ if (!strcmp(filter, "ASCIIHexDecode"))
+ return len / 2;
+ if (!strcmp(filter, "ASCII85Decode"))
+ return len * 4 / 5;
+ if (!strcmp(filter, "FlateDecode"))
+ return len * 3;
+ if (!strcmp(filter, "RunLengthDecode"))
+ return len * 3;
+ if (!strcmp(filter, "LZWDecode"))
+ return len * 2;
+ return len;
+}
+
+static fz_buffer *
+pdf_load_image_stream(pdf_document *xref, int num, int gen, int orig_num, int orig_gen, fz_compression_params *params, int *truncated)
+{
+ fz_context *ctx = xref->ctx;
+ fz_stream *stm = NULL;
+ pdf_obj *dict, *obj;
+ int i, len, n;
+ fz_buffer *buf;
+
+ fz_var(buf);
+
+ if (num > 0 && num < pdf_xref_len(xref))
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, num);
+ if (entry->stm_buf)
+ return fz_keep_buffer(xref->ctx, entry->stm_buf);
+ }
+
+ dict = pdf_load_object(xref, num, gen);
+
+ len = pdf_to_int(pdf_dict_gets(dict, "Length"));
+ obj = pdf_dict_gets(dict, "Filter");
+ len = pdf_guess_filter_length(len, pdf_to_name(obj));
+ n = pdf_array_len(obj);
+ for (i = 0; i < n; i++)
+ len = pdf_guess_filter_length(len, pdf_to_name(pdf_array_get(obj, i)));
+
+ pdf_drop_obj(dict);
+
+ stm = pdf_open_image_stream(xref, num, gen, orig_num, orig_gen, params);
+
+ fz_try(ctx)
+ {
+ if (truncated)
+ buf = fz_read_best(stm, len, truncated);
+ else
+ buf = fz_read_all(stm, len);
+ }
+ fz_always(ctx)
+ {
+ fz_close(stm);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot read raw stream (%d %d R)", num, gen);
+ }
+
+ return buf;
+}
+
+/*
+ * Load uncompressed contents of a stream into buf.
+ */
+fz_buffer *
+pdf_load_stream(pdf_document *xref, int num, int gen)
+{
+ return pdf_load_image_stream(xref, num, gen, num, gen, NULL, NULL);
+}
+
+fz_buffer *
+pdf_load_renumbered_stream(pdf_document *xref, int num, int gen, int orig_num, int orig_gen, int *truncated)
+{
+ return pdf_load_image_stream(xref, num, gen, orig_num, orig_gen, NULL, truncated);
+}
+
+fz_compressed_buffer *
+pdf_load_compressed_stream(pdf_document *xref, int num, int gen)
+{
+ fz_context *ctx = xref->ctx;
+ fz_compressed_buffer *bc = fz_malloc_struct(ctx, fz_compressed_buffer);
+
+ fz_try(ctx)
+ {
+ bc->buffer = pdf_load_image_stream(xref, num, gen, num, gen, &bc->params, NULL);
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, bc);
+ fz_rethrow(ctx);
+ }
+ return bc;
+}
+
+static fz_stream *
+pdf_open_object_array(pdf_document *xref, pdf_obj *list)
+{
+ int i, n;
+ fz_context *ctx = xref->ctx;
+ fz_stream *stm;
+
+ n = pdf_array_len(list);
+ stm = fz_open_concat(ctx, n, 1);
+
+ fz_var(i); /* Workaround Mac compiler bug */
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *obj = pdf_array_get(list, i);
+ fz_try(ctx)
+ {
+ fz_concat_push(stm, pdf_open_stream(xref, pdf_to_num(obj), pdf_to_gen(obj)));
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "cannot load content stream part %d/%d", i + 1, n);
+ continue;
+ }
+ }
+
+ return stm;
+}
+
+fz_stream *
+pdf_open_contents_stream(pdf_document *xref, pdf_obj *obj)
+{
+ fz_context *ctx = xref->ctx;
+ int num, gen;
+
+ if (pdf_is_array(obj))
+ return pdf_open_object_array(xref, obj);
+
+ num = pdf_to_num(obj);
+ gen = pdf_to_gen(obj);
+ if (pdf_is_stream(xref, num, gen))
+ return pdf_open_image_stream(xref, num, gen, num, gen, NULL);
+
+ fz_warn(ctx, "pdf object stream missing (%d %d R)", num, gen);
+ return NULL;
+}
diff --git a/source/pdf/pdf-type3.c b/source/pdf/pdf-type3.c
new file mode 100644
index 00000000..d85d9f11
--- /dev/null
+++ b/source/pdf/pdf-type3.c
@@ -0,0 +1,190 @@
+#include "mupdf/pdf.h"
+
+static void
+pdf_run_glyph_func(void *doc, void *rdb, fz_buffer *contents, fz_device *dev, const fz_matrix *ctm, void *gstate, int nested_depth)
+{
+ pdf_run_glyph(doc, (pdf_obj *)rdb, contents, dev, ctm, gstate, nested_depth);
+}
+
+static void
+pdf_t3_free_resources(void *doc, void *rdb_)
+{
+ pdf_obj *rdb = (pdf_obj *)rdb_;
+ pdf_drop_obj(rdb);
+}
+
+pdf_font_desc *
+pdf_load_type3_font(pdf_document *xref, pdf_obj *rdb, pdf_obj *dict)
+{
+ char buf[256];
+ char *estrings[256];
+ pdf_font_desc *fontdesc = NULL;
+ pdf_obj *encoding;
+ pdf_obj *widths;
+ pdf_obj *charprocs;
+ pdf_obj *obj;
+ int first, last;
+ int i, k, n;
+ fz_rect bbox;
+ fz_matrix matrix;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(fontdesc);
+
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(dict, "Name");
+ if (pdf_is_name(obj))
+ fz_strlcpy(buf, pdf_to_name(obj), sizeof buf);
+ else
+ sprintf(buf, "Unnamed-T3");
+
+ fontdesc = pdf_new_font_desc(ctx);
+
+ obj = pdf_dict_gets(dict, "FontMatrix");
+ pdf_to_matrix(ctx, obj, &matrix);
+
+ obj = pdf_dict_gets(dict, "FontBBox");
+ fz_transform_rect(pdf_to_rect(ctx, obj, &bbox), &matrix);
+
+ fontdesc->font = fz_new_type3_font(ctx, buf, &matrix);
+ fontdesc->size += sizeof(fz_font) + 256 * (sizeof(fz_buffer*) + sizeof(float));
+
+ fz_set_font_bbox(ctx, fontdesc->font, bbox.x0, bbox.y0, bbox.x1, bbox.y1);
+
+ /* Encoding */
+
+ for (i = 0; i < 256; i++)
+ estrings[i] = NULL;
+
+ encoding = pdf_dict_gets(dict, "Encoding");
+ if (!encoding)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: Type3 font missing Encoding");
+ }
+
+ if (pdf_is_name(encoding))
+ pdf_load_encoding(estrings, pdf_to_name(encoding));
+
+ if (pdf_is_dict(encoding))
+ {
+ pdf_obj *base, *diff, *item;
+
+ base = pdf_dict_gets(encoding, "BaseEncoding");
+ if (pdf_is_name(base))
+ pdf_load_encoding(estrings, pdf_to_name(base));
+
+ diff = pdf_dict_gets(encoding, "Differences");
+ if (pdf_is_array(diff))
+ {
+ n = pdf_array_len(diff);
+ k = 0;
+ for (i = 0; i < n; i++)
+ {
+ item = pdf_array_get(diff, i);
+ if (pdf_is_int(item))
+ k = pdf_to_int(item);
+ if (pdf_is_name(item) && k >= 0 && k < nelem(estrings))
+ estrings[k++] = pdf_to_name(item);
+ }
+ }
+ }
+
+ fontdesc->encoding = pdf_new_identity_cmap(ctx, 0, 1);
+ fontdesc->size += pdf_cmap_size(ctx, fontdesc->encoding);
+
+ pdf_load_to_unicode(xref, fontdesc, estrings, NULL, pdf_dict_gets(dict, "ToUnicode"));
+
+ /* Widths */
+
+ pdf_set_default_hmtx(ctx, fontdesc, 0);
+
+ first = pdf_to_int(pdf_dict_gets(dict, "FirstChar"));
+ last = pdf_to_int(pdf_dict_gets(dict, "LastChar"));
+
+ if (first < 0 || last > 255 || first > last)
+ first = last = 0;
+
+ widths = pdf_dict_gets(dict, "Widths");
+ if (!widths)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: Type3 font missing Widths");
+ }
+
+ for (i = first; i <= last; i++)
+ {
+ float w = pdf_to_real(pdf_array_get(widths, i - first));
+ w = fontdesc->font->t3matrix.a * w * 1000;
+ fontdesc->font->t3widths[i] = w * 0.001f;
+ pdf_add_hmtx(ctx, fontdesc, i, i, w);
+ }
+
+ pdf_end_hmtx(ctx, fontdesc);
+
+ /* Resources -- inherit page resources if the font doesn't have its own */
+
+ fontdesc->font->t3freeres = pdf_t3_free_resources;
+ fontdesc->font->t3resources = pdf_dict_gets(dict, "Resources");
+ if (!fontdesc->font->t3resources)
+ fontdesc->font->t3resources = rdb;
+ if (fontdesc->font->t3resources)
+ pdf_keep_obj(fontdesc->font->t3resources);
+ if (!fontdesc->font->t3resources)
+ fz_warn(ctx, "no resource dictionary for type 3 font!");
+
+ fontdesc->font->t3doc = xref;
+ fontdesc->font->t3run = pdf_run_glyph_func;
+
+ /* CharProcs */
+
+ charprocs = pdf_dict_gets(dict, "CharProcs");
+ if (!charprocs)
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntaxerror: Type3 font missing CharProcs");
+ }
+
+ for (i = 0; i < 256; i++)
+ {
+ if (estrings[i])
+ {
+ obj = pdf_dict_gets(charprocs, estrings[i]);
+ if (pdf_is_stream(xref, pdf_to_num(obj), pdf_to_gen(obj)))
+ {
+ fontdesc->font->t3procs[i] = pdf_load_stream(xref, pdf_to_num(obj), pdf_to_gen(obj));
+ fontdesc->size += fontdesc->font->t3procs[i]->cap;
+ fontdesc->size += 0; // TODO: display list size calculation
+ }
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ if (fontdesc)
+ pdf_drop_font(ctx, fontdesc);
+ fz_rethrow_message(ctx, "cannot load type3 font (%d %d R)", pdf_to_num(dict), pdf_to_gen(dict));
+ }
+ return fontdesc;
+}
+
+void pdf_load_type3_glyphs(pdf_document *xref, pdf_font_desc *fontdesc, int nested_depth)
+{
+ int i;
+ fz_context *ctx = xref->ctx;
+
+ fz_try(ctx)
+ {
+ for (i = 0; i < 256; i++)
+ {
+ if (fontdesc->font->t3procs[i])
+ {
+ fz_prepare_t3_glyph(ctx, fontdesc->font, i, nested_depth);
+ fontdesc->size += 0; // TODO: display list size calculation
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ fz_warn(ctx, "Type3 glyph load failed: %s", fz_caught_message(ctx));
+ }
+}
diff --git a/source/pdf/pdf-unicode.c b/source/pdf/pdf-unicode.c
new file mode 100644
index 00000000..694cbac6
--- /dev/null
+++ b/source/pdf/pdf-unicode.c
@@ -0,0 +1,77 @@
+#include "mupdf/pdf.h"
+
+/* Load or synthesize ToUnicode map for fonts */
+
+void
+pdf_load_to_unicode(pdf_document *xref, pdf_font_desc *font,
+ char **strings, char *collection, pdf_obj *cmapstm)
+{
+ pdf_cmap *cmap;
+ int cid;
+ int ucsbuf[8];
+ int ucslen;
+ int i;
+ fz_context *ctx = xref->ctx;
+
+ if (pdf_is_stream(xref, pdf_to_num(cmapstm), pdf_to_gen(cmapstm)))
+ {
+ cmap = pdf_load_embedded_cmap(xref, cmapstm);
+
+ font->to_unicode = pdf_new_cmap(ctx);
+
+ for (i = 0; i < (strings ? 256 : 65536); i++)
+ {
+ cid = pdf_lookup_cmap(font->encoding, i);
+ if (cid >= 0)
+ {
+ ucslen = pdf_lookup_cmap_full(cmap, i, ucsbuf);
+ if (ucslen == 1)
+ pdf_map_range_to_range(ctx, font->to_unicode, cid, cid, ucsbuf[0]);
+ if (ucslen > 1)
+ pdf_map_one_to_many(ctx, font->to_unicode, cid, ucsbuf, ucslen);
+ }
+ }
+
+ pdf_sort_cmap(ctx, font->to_unicode);
+
+ pdf_drop_cmap(ctx, cmap);
+ font->size += pdf_cmap_size(ctx, font->to_unicode);
+ }
+
+ else if (collection)
+ {
+ if (!strcmp(collection, "Adobe-CNS1"))
+ font->to_unicode = pdf_load_system_cmap(ctx, "Adobe-CNS1-UCS2");
+ else if (!strcmp(collection, "Adobe-GB1"))
+ font->to_unicode = pdf_load_system_cmap(ctx, "Adobe-GB1-UCS2");
+ else if (!strcmp(collection, "Adobe-Japan1"))
+ font->to_unicode = pdf_load_system_cmap(ctx, "Adobe-Japan1-UCS2");
+ else if (!strcmp(collection, "Adobe-Korea1"))
+ font->to_unicode = pdf_load_system_cmap(ctx, "Adobe-Korea1-UCS2");
+
+ return;
+ }
+
+ if (strings)
+ {
+ /* TODO one-to-many mappings */
+
+ font->cid_to_ucs_len = 256;
+ font->cid_to_ucs = fz_malloc_array(ctx, 256, sizeof(unsigned short));
+ font->size += 256 * sizeof(unsigned short);
+
+ for (i = 0; i < 256; i++)
+ {
+ if (strings[i])
+ font->cid_to_ucs[i] = pdf_lookup_agl(strings[i]);
+ else
+ font->cid_to_ucs[i] = '?';
+ }
+ }
+
+ if (!font->to_unicode && !font->cid_to_ucs)
+ {
+ /* TODO: synthesize a ToUnicode if it's a freetype font with
+ * cmap and/or post tables or if it has glyph names. */
+ }
+}
diff --git a/source/pdf/pdf-write.c b/source/pdf/pdf-write.c
new file mode 100644
index 00000000..a423edef
--- /dev/null
+++ b/source/pdf/pdf-write.c
@@ -0,0 +1,2363 @@
+#include "mupdf/pdf.h"
+
+/* #define DEBUG_LINEARIZATION */
+/* #define DEBUG_HEAP_SORT */
+/* #define DEBUG_WRITING */
+
+typedef struct pdf_write_options_s pdf_write_options;
+
+/*
+ As part of linearization, we need to keep a list of what objects are used
+ by what page. We do this by recording the objects used in a given page
+ in a page_objects structure. We have a list of these structures (one per
+ page) in the page_objects_list structure.
+
+ The page_objects structure maintains a heap in the object array, so
+ insertion takes log n time, and we can heapsort and dedupe at the end for
+ a total worse case n log n time.
+
+ The magic heap invariant is that:
+ entry[n] >= entry[(n+1)*2-1] & entry[n] >= entry[(n+1)*2]
+ or equivalently:
+ entry[(n-1)>>1] >= entry[n]
+
+ For a discussion of the heap data structure (and heapsort) see Kingston,
+ "Algorithms and Data Structures".
+*/
+
+typedef struct {
+ int num_shared;
+ int page_object_number;
+ int num_objects;
+ int min_ofs;
+ int max_ofs;
+ /* Extensible list of objects used on this page */
+ int cap;
+ int len;
+ int object[1];
+} page_objects;
+
+typedef struct {
+ int cap;
+ int len;
+ page_objects *page[1];
+} page_objects_list;
+
+struct pdf_write_options_s
+{
+ FILE *out;
+ int do_ascii;
+ int do_expand;
+ int do_garbage;
+ int do_linear;
+ int *use_list;
+ int *ofs_list;
+ int *gen_list;
+ int *renumber_map;
+ int continue_on_error;
+ int *errors;
+ /* The following extras are required for linearization */
+ int *rev_renumber_map;
+ int *rev_gen_list;
+ int start;
+ int first_xref_offset;
+ int main_xref_offset;
+ int first_xref_entry_offset;
+ int file_len;
+ int hints_shared_offset;
+ int hintstream_len;
+ pdf_obj *linear_l;
+ pdf_obj *linear_h0;
+ pdf_obj *linear_h1;
+ pdf_obj *linear_o;
+ pdf_obj *linear_e;
+ pdf_obj *linear_n;
+ pdf_obj *linear_t;
+ pdf_obj *hints_s;
+ pdf_obj *hints_length;
+ int page_count;
+ page_objects_list *page_object_lists;
+};
+
+/*
+ * Constants for use with use_list.
+ *
+ * If use_list[num] = 0, then object num is unused.
+ * If use_list[num] & PARAMS, then object num is the linearisation params obj.
+ * If use_list[num] & CATALOGUE, then object num is used by the catalogue.
+ * If use_list[num] & PAGE1, then object num is used by page 1.
+ * If use_list[num] & SHARED, then object num is shared between pages.
+ * If use_list[num] & PAGE_OBJECT then this must be the first object in a page.
+ * If use_list[num] & OTHER_OBJECTS then this must should appear in section 9.
+ * Otherwise object num is used by page (use_list[num]>>USE_PAGE_SHIFT).
+ */
+enum
+{
+ USE_CATALOGUE = 2,
+ USE_PAGE1 = 4,
+ USE_SHARED = 8,
+ USE_PARAMS = 16,
+ USE_HINTS = 32,
+ USE_PAGE_OBJECT = 64,
+ USE_OTHER_OBJECTS = 128,
+ USE_PAGE_MASK = ~255,
+ USE_PAGE_SHIFT = 8
+};
+
+/*
+ * page_objects and page_object_list handling functions
+ */
+static page_objects_list *
+page_objects_list_create(fz_context *ctx)
+{
+ page_objects_list *pol = fz_calloc(ctx, 1, sizeof(*pol));
+
+ pol->cap = 1;
+ pol->len = 0;
+ return pol;
+}
+
+static void
+page_objects_list_destroy(fz_context *ctx, page_objects_list *pol)
+{
+ int i;
+
+ if (!pol)
+ return;
+ for (i = 0; i < pol->len; i++)
+ {
+ fz_free(ctx, pol->page[i]);
+ }
+ fz_free(ctx, pol);
+}
+
+static void
+page_objects_list_ensure(fz_context *ctx, page_objects_list **pol, int newcap)
+{
+ int oldcap = (*pol)->cap;
+ if (newcap <= oldcap)
+ return;
+ *pol = fz_resize_array(ctx, *pol, 1, sizeof(page_objects_list) + (newcap-1)*sizeof(page_objects *));
+ memset(&(*pol)->page[oldcap], 0, (newcap-oldcap)*sizeof(page_objects *));
+ (*pol)->cap = newcap;
+}
+
+static page_objects *
+page_objects_create(fz_context *ctx)
+{
+ int initial_cap = 8;
+ page_objects *po = fz_calloc(ctx, 1, sizeof(*po) + (initial_cap-1) * sizeof(int));
+
+ po->cap = initial_cap;
+ po->len = 0;
+ return po;
+
+}
+
+static void
+page_objects_insert(fz_context *ctx, page_objects **ppo, int i)
+{
+ page_objects *po;
+
+ /* Make a page_objects if we don't have one */
+ if (*ppo == NULL)
+ *ppo = page_objects_create(ctx);
+
+ po = *ppo;
+ /* page_objects insertion: extend the page_objects by 1, and put us on the end */
+ if (po->len == po->cap)
+ {
+ po = fz_resize_array(ctx, po, 1, sizeof(page_objects) + (po->cap*2 - 1)*sizeof(int));
+ po->cap *= 2;
+ *ppo = po;
+ }
+ po->object[po->len++] = i;
+}
+
+static void
+page_objects_list_insert(fz_context *ctx, pdf_write_options *opts, int page, int object)
+{
+ page_objects_list_ensure(ctx, &opts->page_object_lists, page+1);
+ if (opts->page_object_lists->len < page+1)
+ opts->page_object_lists->len = page+1;
+ page_objects_insert(ctx, &opts->page_object_lists->page[page], object);
+}
+
+static void
+page_objects_list_set_page_object(fz_context *ctx, pdf_write_options *opts, int page, int object)
+{
+ page_objects_list_ensure(ctx, &opts->page_object_lists, page+1);
+ opts->page_object_lists->page[page]->page_object_number = object;
+}
+
+static void
+page_objects_sort(fz_context *ctx, page_objects *po)
+{
+ int i, j;
+ int n = po->len;
+
+ /* Step 1: Make a heap */
+ /* Invariant: Valid heap in [0..i), unsorted elements in [i..n) */
+ for (i = 1; i < n; i++)
+ {
+ /* Now bubble backwards to maintain heap invariant */
+ j = i;
+ while (j != 0)
+ {
+ int tmp;
+ int k = (j-1)>>1;
+ if (po->object[k] >= po->object[j])
+ break;
+ tmp = po->object[k];
+ po->object[k] = po->object[j];
+ po->object[j] = tmp;
+ j = k;
+ }
+ }
+
+ /* Step 2: Heap sort */
+ /* Invariant: valid heap in [0..i), sorted list in [i..n) */
+ /* Initially: i = n */
+ for (i = n-1; i > 0; i--)
+ {
+ /* Swap the maximum (0th) element from the page_objects into its place
+ * in the sorted list (position i). */
+ int tmp = po->object[0];
+ po->object[0] = po->object[i];
+ po->object[i] = tmp;
+ /* Now, the page_objects is invalid because the 0th element is out
+ * of place. Bubble it until the page_objects is valid. */
+ j = 0;
+ while (1)
+ {
+ /* Children are k and k+1 */
+ int k = (j+1)*2-1;
+ /* If both children out of the page_objects, we're done */
+ if (k > i-1)
+ break;
+ /* If both are in the page_objects, pick the larger one */
+ if (k < i-1 && po->object[k] < po->object[k+1])
+ k++;
+ /* If j is bigger than k (i.e. both of it's children),
+ * we're done */
+ if (po->object[j] > po->object[k])
+ break;
+ tmp = po->object[k];
+ po->object[k] = po->object[j];
+ po->object[j] = tmp;
+ j = k;
+ }
+ }
+}
+
+static int
+order_ge(int ui, int uj)
+{
+ /*
+ For linearization, we need to order the sections as follows:
+
+ Remaining pages (Part 7)
+ Shared objects (Part 8)
+ Objects not associated with any page (Part 9)
+ Any "other" objects
+ (Header)(Part 1)
+ (Linearization params) (Part 2)
+ (1st page Xref/Trailer) (Part 3)
+ Catalogue (and other document level objects) (Part 4)
+ First page (Part 6)
+ (Primary Hint stream) (*) (Part 5)
+ Any free objects
+
+ Note, this is NOT the same order they appear in
+ the final file!
+
+ (*) The PDF reference gives us the option of putting the hint stream
+ after the first page, and we take it, for simplicity.
+ */
+
+ /* If the 2 objects are in the same section, then page object comes first. */
+ if (((ui ^ uj) & ~USE_PAGE_OBJECT) == 0)
+ return ((ui & USE_PAGE_OBJECT) == 0);
+ /* Put unused objects last */
+ else if (ui == 0)
+ return 1;
+ else if (uj == 0)
+ return 0;
+ /* Put the hint stream before that... */
+ else if (ui & USE_HINTS)
+ return 1;
+ else if (uj & USE_HINTS)
+ return 0;
+ /* Put page 1 before that... */
+ else if (ui & USE_PAGE1)
+ return 1;
+ else if (uj & USE_PAGE1)
+ return 0;
+ /* Put the catalogue before that... */
+ else if (ui & USE_CATALOGUE)
+ return 1;
+ else if (uj & USE_CATALOGUE)
+ return 0;
+ /* Put the linearization params before that... */
+ else if (ui & USE_PARAMS)
+ return 1;
+ else if (uj & USE_PARAMS)
+ return 0;
+ /* Put other objects before that */
+ else if (ui & USE_OTHER_OBJECTS)
+ return 1;
+ else if (uj & USE_OTHER_OBJECTS)
+ return 0;
+ /* Put objects not associated with any page (anything
+ * not touched by the catalogue) before that... */
+ else if (ui == 0)
+ return 1;
+ else if (uj == 0)
+ return 0;
+ /* Put shared objects before that... */
+ else if (ui & USE_SHARED)
+ return 1;
+ else if (uj & USE_SHARED)
+ return 0;
+ /* And otherwise, order by the page number on which
+ * they are used. */
+ return (ui>>USE_PAGE_SHIFT) >= (uj>>USE_PAGE_SHIFT);
+}
+
+static void
+heap_sort(int *list, int n, const int *val, int (*ge)(int, int))
+{
+ int i, j;
+
+#ifdef DEBUG_HEAP_SORT
+ fprintf(stderr, "Initially:\n");
+ for (i=0; i < n; i++)
+ {
+ fprintf(stderr, "%d: %d %x\n", i, list[i], val[list[i]]);
+ }
+#endif
+ /* Step 1: Make a heap */
+ /* Invariant: Valid heap in [0..i), unsorted elements in [i..n) */
+ for (i = 1; i < n; i++)
+ {
+ /* Now bubble backwards to maintain heap invariant */
+ j = i;
+ while (j != 0)
+ {
+ int tmp;
+ int k = (j-1)>>1;
+ if (ge(val[list[k]], val[list[j]]))
+ break;
+ tmp = list[k];
+ list[k] = list[j];
+ list[j] = tmp;
+ j = k;
+ }
+ }
+#ifdef DEBUG_HEAP_SORT
+ fprintf(stderr, "Valid heap:\n");
+ for (i=0; i < n; i++)
+ {
+ int k;
+ fprintf(stderr, "%d: %d %x ", i, list[i], val[list[i]]);
+ k = (i+1)*2-1;
+ if (k < n)
+ {
+ if (ge(val[list[i]], val[list[k]]))
+ fprintf(stderr, "OK ");
+ else
+ fprintf(stderr, "BAD ");
+ }
+ if (k+1 < n)
+ {
+ if (ge(val[list[i]], val[list[k+1]]))
+ fprintf(stderr, "OK\n");
+ else
+ fprintf(stderr, "BAD\n");
+ }
+ else
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ /* Step 2: Heap sort */
+ /* Invariant: valid heap in [0..i), sorted list in [i..n) */
+ /* Initially: i = n */
+ for (i = n-1; i > 0; i--)
+ {
+ /* Swap the maximum (0th) element from the page_objects into its place
+ * in the sorted list (position i). */
+ int tmp = list[0];
+ list[0] = list[i];
+ list[i] = tmp;
+ /* Now, the page_objects is invalid because the 0th element is out
+ * of place. Bubble it until the page_objects is valid. */
+ j = 0;
+ while (1)
+ {
+ /* Children are k and k+1 */
+ int k = (j+1)*2-1;
+ /* If both children out of the page_objects, we're done */
+ if (k > i-1)
+ break;
+ /* If both are in the page_objects, pick the larger one */
+ if (k < i-1 && ge(val[list[k+1]], val[list[k]]))
+ k++;
+ /* If j is bigger than k (i.e. both of it's children),
+ * we're done */
+ if (ge(val[list[j]], val[list[k]]))
+ break;
+ tmp = list[k];
+ list[k] = list[j];
+ list[j] = tmp;
+ j = k;
+ }
+ }
+#ifdef DEBUG_HEAP_SORT
+ fprintf(stderr, "Sorted:\n");
+ for (i=0; i < n; i++)
+ {
+ fprintf(stderr, "%d: %d %x ", i, list[i], val[list[i]]);
+ if (i+1 < n)
+ {
+ if (ge(val[list[i+1]], val[list[i]]))
+ fprintf(stderr, "OK");
+ else
+ fprintf(stderr, "BAD");
+ }
+ fprintf(stderr, "\n");
+ }
+#endif
+}
+
+static void
+page_objects_dedupe(fz_context *ctx, page_objects *po)
+{
+ int i, j;
+ int n = po->len-1;
+
+ for (i = 0; i < n; i++)
+ {
+ if (po->object[i] == po->object[i+1])
+ break;
+ }
+ j = i; /* j points to the last valid one */
+ i++; /* i points to the first one we haven't looked at */
+ for (; i < n; i++)
+ {
+ if (po->object[j] != po->object[i])
+ po->object[++j] = po->object[i];
+ }
+ po->len = j+1;
+}
+
+static void
+page_objects_list_sort_and_dedupe(fz_context *ctx, page_objects_list *pol)
+{
+ int i;
+ int n = pol->len;
+
+ for (i = 0; i < n; i++)
+ {
+ page_objects_sort(ctx, pol->page[i]);
+ page_objects_dedupe(ctx, pol->page[i]);
+ }
+}
+
+#ifdef DEBUG_LINEARIZATION
+static void
+page_objects_dump(pdf_write_options *opts)
+{
+ page_objects_list *pol = opts->page_object_lists;
+ int i, j;
+
+ for (i = 0; i < pol->len; i++)
+ {
+ page_objects *p = pol->page[i];
+ fprintf(stderr, "Page %d\n", i+1);
+ for (j = 0; j < p->len; j++)
+ {
+ int o = p->object[j];
+ fprintf(stderr, "\tObject %d: use=%x\n", o, opts->use_list[o]);
+ }
+ fprintf(stderr, "Byte range=%d->%d\n", p->min_ofs, p->max_ofs);
+ fprintf(stderr, "Number of objects=%d, Number of shared objects=%d\n", p->num_objects, p->num_shared);
+ fprintf(stderr, "Page object number=%d\n", p->page_object_number);
+ }
+}
+
+static void
+objects_dump(pdf_document *xref, pdf_write_options *opts)
+{
+ int i;
+
+ for (i=0; i < pdf_xref_len(xref); i++)
+ {
+ fprintf(stderr, "Object %d use=%x offset=%d\n", i, opts->use_list[i], opts->ofs_list[i]);
+ }
+}
+#endif
+
+/*
+ * Garbage collect objects not reachable from the trailer.
+ */
+
+static pdf_obj *sweepref(pdf_document *xref, pdf_write_options *opts, pdf_obj *obj)
+{
+ int num = pdf_to_num(obj);
+ int gen = pdf_to_gen(obj);
+ fz_context *ctx = xref->ctx;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ return NULL;
+ if (opts->use_list[num])
+ return NULL;
+
+ opts->use_list[num] = 1;
+
+ /* Bake in /Length in stream objects */
+ fz_try(ctx)
+ {
+ if (pdf_is_stream(xref, num, gen))
+ {
+ pdf_obj *len = pdf_dict_gets(obj, "Length");
+ if (pdf_is_indirect(len))
+ {
+ opts->use_list[pdf_to_num(len)] = 0;
+ len = pdf_resolve_indirect(len);
+ pdf_dict_puts(obj, "Length", len);
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater */
+ /* Leave broken */
+ }
+
+ return pdf_resolve_indirect(obj);
+}
+
+static void sweepobj(pdf_document *xref, pdf_write_options *opts, pdf_obj *obj)
+{
+ int i;
+
+ if (pdf_is_indirect(obj))
+ obj = sweepref(xref, opts, obj);
+
+ if (pdf_is_dict(obj))
+ {
+ int n = pdf_dict_len(obj);
+ for (i = 0; i < n; i++)
+ sweepobj(xref, opts, pdf_dict_get_val(obj, i));
+ }
+
+ else if (pdf_is_array(obj))
+ {
+ int n = pdf_array_len(obj);
+ for (i = 0; i < n; i++)
+ sweepobj(xref, opts, pdf_array_get(obj, i));
+ }
+}
+
+/*
+ * Scan for and remove duplicate objects (slow)
+ */
+
+static void removeduplicateobjs(pdf_document *xref, pdf_write_options *opts)
+{
+ int num, other;
+ fz_context *ctx = xref->ctx;
+ int xref_len = pdf_xref_len(xref);
+
+ for (num = 1; num < xref_len; num++)
+ {
+ /* Only compare an object to objects preceding it */
+ for (other = 1; other < num; other++)
+ {
+ pdf_obj *a, *b;
+ int differ, newnum, streama, streamb;
+
+ if (num == other || !opts->use_list[num] || !opts->use_list[other])
+ continue;
+
+ /*
+ * Comparing stream objects data contents would take too long.
+ *
+ * pdf_is_stream calls pdf_cache_object and ensures
+ * that the xref table has the objects loaded.
+ */
+ fz_try(ctx)
+ {
+ streama = pdf_is_stream(xref, num, 0);
+ streamb = pdf_is_stream(xref, other, 0);
+ differ = streama || streamb;
+ if (streama && streamb && opts->do_garbage >= 4)
+ differ = 0;
+ }
+ fz_catch(ctx)
+ {
+ /* Assume different */
+ differ = 1;
+ }
+ if (differ)
+ continue;
+
+ a = pdf_get_xref_entry(xref, num)->obj;
+ b = pdf_get_xref_entry(xref, other)->obj;
+
+ a = pdf_resolve_indirect(a);
+ b = pdf_resolve_indirect(b);
+
+ if (pdf_objcmp(a, b))
+ continue;
+
+ if (streama && streamb)
+ {
+ /* Check to see if streams match too. */
+ fz_buffer *sa = NULL;
+ fz_buffer *sb = NULL;
+
+ fz_var(sa);
+ fz_var(sb);
+
+ differ = 1;
+ fz_try(ctx)
+ {
+ unsigned char *dataa, *datab;
+ int lena, lenb;
+ sa = pdf_load_raw_renumbered_stream(xref, num, 0, num, 0);
+ sb = pdf_load_raw_renumbered_stream(xref, other, 0, other, 0);
+ lena = fz_buffer_storage(ctx, sa, &dataa);
+ lenb = fz_buffer_storage(ctx, sb, &datab);
+ if (lena == lenb && memcmp(dataa, datab, lena) == 0)
+ differ = 0;
+ }
+ fz_always(ctx)
+ {
+ fz_drop_buffer(ctx, sa);
+ fz_drop_buffer(ctx, sb);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ if (differ)
+ continue;
+ }
+
+ /* Keep the lowest numbered object */
+ newnum = fz_mini(num, other);
+ opts->renumber_map[num] = newnum;
+ opts->renumber_map[other] = newnum;
+ opts->rev_renumber_map[newnum] = num; /* Either will do */
+ opts->use_list[fz_maxi(num, other)] = 0;
+
+ /* One duplicate was found, do not look for another */
+ break;
+ }
+ }
+}
+
+/*
+ * Renumber objects sequentially so the xref is more compact
+ *
+ * This code assumes that any opts->renumber_map[n] <= n for all n.
+ */
+
+static void compactxref(pdf_document *xref, pdf_write_options *opts)
+{
+ int num, newnum;
+ int xref_len = pdf_xref_len(xref);
+
+ /*
+ * Update renumber_map in-place, clustering all used
+ * objects together at low object ids. Objects that
+ * already should be renumbered will have their new
+ * object ids be updated to reflect the compaction.
+ */
+
+ newnum = 1;
+ for (num = 1; num < xref_len; num++)
+ {
+ /* If it's not used, map it to zero */
+ if (!opts->use_list[opts->renumber_map[num]])
+ {
+ opts->renumber_map[num] = 0;
+ }
+ /* If it's not moved, compact it. */
+ else if (opts->renumber_map[num] == num)
+ {
+ opts->rev_renumber_map[newnum] = opts->rev_renumber_map[num];
+ opts->rev_gen_list[newnum] = opts->rev_gen_list[num];
+ opts->renumber_map[num] = newnum++;
+ }
+ /* Otherwise it's used, and moved. We know that it must have
+ * moved down, so the place it's moved to will be in the right
+ * place already. */
+ else
+ {
+ opts->renumber_map[num] = opts->renumber_map[opts->renumber_map[num]];
+ }
+ }
+}
+
+/*
+ * Update indirect objects according to renumbering established when
+ * removing duplicate objects and compacting the xref.
+ */
+
+static void renumberobj(pdf_document *xref, pdf_write_options *opts, pdf_obj *obj)
+{
+ int i;
+ fz_context *ctx = xref->ctx;
+
+ if (pdf_is_dict(obj))
+ {
+ int n = pdf_dict_len(obj);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *key = pdf_dict_get_key(obj, i);
+ pdf_obj *val = pdf_dict_get_val(obj, i);
+ if (pdf_is_indirect(val))
+ {
+ val = pdf_new_indirect(ctx, opts->renumber_map[pdf_to_num(val)], 0, xref);
+ pdf_dict_put(obj, key, val);
+ pdf_drop_obj(val);
+ }
+ else
+ {
+ renumberobj(xref, opts, val);
+ }
+ }
+ }
+
+ else if (pdf_is_array(obj))
+ {
+ int n = pdf_array_len(obj);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *val = pdf_array_get(obj, i);
+ if (pdf_is_indirect(val))
+ {
+ val = pdf_new_indirect(ctx, opts->renumber_map[pdf_to_num(val)], 0, xref);
+ pdf_array_put(obj, i, val);
+ pdf_drop_obj(val);
+ }
+ else
+ {
+ renumberobj(xref, opts, val);
+ }
+ }
+ }
+}
+
+static void renumberobjs(pdf_document *xref, pdf_write_options *opts)
+{
+ pdf_xref_entry *newxref = NULL;
+ int newlen;
+ int num;
+ fz_context *ctx = xref->ctx;
+ int *new_use_list;
+ int xref_len = pdf_xref_len(xref);
+
+ new_use_list = fz_calloc(ctx, pdf_xref_len(xref)+3, sizeof(int));
+
+ fz_var(newxref);
+ fz_try(ctx)
+ {
+ /* Apply renumber map to indirect references in all objects in xref */
+ renumberobj(xref, opts, pdf_trailer(xref));
+ for (num = 0; num < xref_len; num++)
+ {
+ pdf_obj *obj = pdf_get_xref_entry(xref, num)->obj;
+
+ if (pdf_is_indirect(obj))
+ {
+ obj = pdf_new_indirect(ctx, opts->renumber_map[pdf_to_num(obj)], 0, xref);
+ pdf_update_object(xref, num, obj);
+ pdf_drop_obj(obj);
+ }
+ else
+ {
+ renumberobj(xref, opts, obj);
+ }
+ }
+
+ /* Create new table for the reordered, compacted xref */
+ newxref = fz_malloc_array(ctx, xref_len + 3, sizeof(pdf_xref_entry));
+ newxref[0] = *pdf_get_xref_entry(xref, 0);
+
+ /* Move used objects into the new compacted xref */
+ newlen = 0;
+ for (num = 1; num < xref_len; num++)
+ {
+ if (opts->use_list[num])
+ {
+ if (newlen < opts->renumber_map[num])
+ newlen = opts->renumber_map[num];
+ newxref[opts->renumber_map[num]] = *pdf_get_xref_entry(xref, num);
+ new_use_list[opts->renumber_map[num]] = opts->use_list[num];
+ }
+ else
+ {
+ pdf_drop_obj(pdf_get_xref_entry(xref, num)->obj);
+ }
+ }
+
+ pdf_replace_xref(xref, newxref, newlen + 1);
+ newxref = NULL;
+ }
+ fz_catch(ctx)
+ {
+ fz_free(ctx, newxref);
+ fz_free(ctx, new_use_list);
+ fz_rethrow(ctx);
+ }
+ fz_free(ctx, opts->use_list);
+ opts->use_list = new_use_list;
+
+ for (num = 1; num < xref_len; num++)
+ {
+ opts->renumber_map[num] = num;
+ }
+}
+
+static void page_objects_list_renumber(pdf_write_options *opts)
+{
+ int i, j;
+
+ for (i = 0; i < opts->page_object_lists->len; i++)
+ {
+ page_objects *po = opts->page_object_lists->page[i];
+ for (j = 0; j < po->len; j++)
+ {
+ po->object[j] = opts->renumber_map[po->object[j]];
+ }
+ po->page_object_number = opts->renumber_map[po->page_object_number];
+ }
+}
+
+static void
+mark_all(pdf_document *xref, pdf_write_options *opts, pdf_obj *val, int flag, int page)
+{
+ fz_context *ctx = xref->ctx;
+
+ if (pdf_obj_mark(val))
+ return;
+
+ fz_try(ctx)
+ {
+ if (pdf_is_indirect(val))
+ {
+ int num = pdf_to_num(val);
+ if (opts->use_list[num] & USE_PAGE_MASK)
+ /* Already used */
+ opts->use_list[num] |= USE_SHARED;
+ else
+ opts->use_list[num] |= flag;
+ if (page >= 0)
+ page_objects_list_insert(ctx, opts, page, num);
+ }
+
+ if (pdf_is_dict(val))
+ {
+ int i, n = pdf_dict_len(val);
+
+ for (i = 0; i < n; i++)
+ {
+ mark_all(xref, opts, pdf_dict_get_val(val, i), flag, page);
+ }
+ }
+ else if (pdf_is_array(val))
+ {
+ int i, n = pdf_array_len(val);
+
+ for (i = 0; i < n; i++)
+ {
+ mark_all(xref, opts, pdf_array_get(val, i), flag, page);
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static int
+mark_pages(pdf_document *xref, pdf_write_options *opts, pdf_obj *val, int pagenum)
+{
+ fz_context *ctx = xref->ctx;
+
+ if (pdf_obj_mark(val))
+ return pagenum;
+
+ fz_try(ctx)
+ {
+ if (pdf_is_dict(val))
+ {
+ if (!strcmp("Page", pdf_to_name(pdf_dict_gets(val, "Type"))))
+ {
+ int num = pdf_to_num(val);
+ pdf_obj_unmark(val);
+ mark_all(xref, opts, val, pagenum == 0 ? USE_PAGE1 : (pagenum<<USE_PAGE_SHIFT), pagenum);
+ page_objects_list_set_page_object(ctx, opts, pagenum, num);
+ pagenum++;
+ opts->use_list[num] |= USE_PAGE_OBJECT;
+ }
+ else
+ {
+ int i, n = pdf_dict_len(val);
+
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *key = pdf_dict_get_key(val, i);
+ pdf_obj *obj = pdf_dict_get_val(val, i);
+
+ if (!strcmp("Kids", pdf_to_name(key)))
+ pagenum = mark_pages(xref, opts, obj, pagenum);
+ else
+ mark_all(xref, opts, obj, USE_CATALOGUE, -1);
+ }
+
+ if (pdf_is_indirect(val))
+ {
+ int num = pdf_to_num(val);
+ opts->use_list[num] |= USE_CATALOGUE;
+ }
+ }
+ }
+ else if (pdf_is_array(val))
+ {
+ int i, n = pdf_array_len(val);
+
+ for (i = 0; i < n; i++)
+ {
+ pagenum = mark_pages(xref, opts, pdf_array_get(val, i), pagenum);
+ }
+ if (pdf_is_indirect(val))
+ {
+ int num = pdf_to_num(val);
+ opts->use_list[num] |= USE_CATALOGUE;
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(val);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+ return pagenum;
+}
+
+static void
+mark_root(pdf_document *xref, pdf_write_options *opts, pdf_obj *dict)
+{
+ fz_context *ctx = xref->ctx;
+ int i, n = pdf_dict_len(dict);
+
+ if (pdf_obj_mark(dict))
+ return;
+
+ fz_try(ctx)
+ {
+ if (pdf_is_indirect(dict))
+ {
+ int num = pdf_to_num(dict);
+ opts->use_list[num] |= USE_CATALOGUE;
+ }
+
+ for (i = 0; i < n; i++)
+ {
+ char *key = pdf_to_name(pdf_dict_get_key(dict, i));
+ pdf_obj *val = pdf_dict_get_val(dict, i);
+
+ if (!strcmp("Pages", key))
+ opts->page_count = mark_pages(xref, opts, val, 0);
+ else if (!strcmp("Names", key))
+ mark_all(xref, opts, val, USE_OTHER_OBJECTS, -1);
+ else if (!strcmp("Dests", key))
+ mark_all(xref, opts, val, USE_OTHER_OBJECTS, -1);
+ else if (!strcmp("Outlines", key))
+ {
+ int section;
+ /* Look at PageMode to decide whether to
+ * USE_OTHER_OBJECTS or USE_PAGE1 here. */
+ if (strcmp(pdf_to_name(pdf_dict_gets(dict, "PageMode")), "UseOutlines") == 0)
+ section = USE_PAGE1;
+ else
+ section = USE_OTHER_OBJECTS;
+ mark_all(xref, opts, val, section, -1);
+ }
+ else
+ mark_all(xref, opts, val, USE_CATALOGUE, -1);
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(dict);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+mark_trailer(pdf_document *xref, pdf_write_options *opts, pdf_obj *dict)
+{
+ fz_context *ctx = xref->ctx;
+ int i, n = pdf_dict_len(dict);
+
+ if (pdf_obj_mark(dict))
+ return;
+
+ fz_try(ctx)
+ {
+ for (i = 0; i < n; i++)
+ {
+ char *key = pdf_to_name(pdf_dict_get_key(dict, i));
+ pdf_obj *val = pdf_dict_get_val(dict, i);
+
+ if (!strcmp("Root", key))
+ mark_root(xref, opts, val);
+ else
+ mark_all(xref, opts, val, USE_CATALOGUE, -1);
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_obj_unmark(dict);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+add_linearization_objs(pdf_document *xref, pdf_write_options *opts)
+{
+ pdf_obj *params_obj = NULL;
+ pdf_obj *params_ref = NULL;
+ pdf_obj *hint_obj = NULL;
+ pdf_obj *hint_ref = NULL;
+ pdf_obj *o = NULL;
+ int params_num, hint_num;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(params_obj);
+ fz_var(params_ref);
+ fz_var(hint_obj);
+ fz_var(hint_ref);
+ fz_var(o);
+
+ fz_try(ctx)
+ {
+ /* Linearization params */
+ params_obj = pdf_new_dict(ctx, 10);
+ params_ref = pdf_new_ref(xref, params_obj);
+ params_num = pdf_to_num(params_ref);
+
+ opts->use_list[params_num] = USE_PARAMS;
+ opts->renumber_map[params_num] = params_num;
+ opts->rev_renumber_map[params_num] = params_num;
+ opts->gen_list[params_num] = 0;
+ opts->rev_gen_list[params_num] = 0;
+ pdf_dict_puts_drop(params_obj, "Linearized", pdf_new_real(ctx, 1.0));
+ opts->linear_l = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(params_obj, "L", opts->linear_l);
+ opts->linear_h0 = pdf_new_int(ctx, INT_MIN);
+ o = pdf_new_array(ctx, 2);
+ pdf_array_push(o, opts->linear_h0);
+ opts->linear_h1 = pdf_new_int(ctx, INT_MIN);
+ pdf_array_push(o, opts->linear_h1);
+ pdf_dict_puts_drop(params_obj, "H", o);
+ o = NULL;
+ opts->linear_o = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(params_obj, "O", opts->linear_o);
+ opts->linear_e = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(params_obj, "E", opts->linear_e);
+ opts->linear_n = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(params_obj, "N", opts->linear_n);
+ opts->linear_t = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(params_obj, "T", opts->linear_t);
+
+ /* Primary hint stream */
+ hint_obj = pdf_new_dict(ctx, 10);
+ hint_ref = pdf_new_ref(xref, hint_obj);
+ hint_num = pdf_to_num(hint_ref);
+
+ opts->use_list[hint_num] = USE_HINTS;
+ opts->renumber_map[hint_num] = hint_num;
+ opts->rev_renumber_map[hint_num] = hint_num;
+ opts->gen_list[hint_num] = 0;
+ opts->rev_gen_list[hint_num] = 0;
+ pdf_dict_puts_drop(hint_obj, "P", pdf_new_int(ctx, 0));
+ opts->hints_s = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(hint_obj, "S", opts->hints_s);
+ /* FIXME: Do we have thumbnails? Do a T entry */
+ /* FIXME: Do we have outlines? Do an O entry */
+ /* FIXME: Do we have article threads? Do an A entry */
+ /* FIXME: Do we have named destinations? Do a E entry */
+ /* FIXME: Do we have interactive forms? Do a V entry */
+ /* FIXME: Do we have document information? Do an I entry */
+ /* FIXME: Do we have logical structure heirarchy? Do a C entry */
+ /* FIXME: Do L, Page Label hint table */
+ pdf_dict_puts_drop(hint_obj, "Filter", pdf_new_name(ctx, "FlateDecode"));
+ opts->hints_length = pdf_new_int(ctx, INT_MIN);
+ pdf_dict_puts(hint_obj, "Length", opts->hints_length);
+ pdf_get_xref_entry(xref, hint_num)->stm_ofs = -1;
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(params_obj);
+ pdf_drop_obj(params_ref);
+ pdf_drop_obj(hint_ref);
+ pdf_drop_obj(hint_obj);
+ pdf_drop_obj(o);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static void
+lpr_inherit_res_contents(fz_context *ctx, pdf_obj *res, pdf_obj *dict, char *text)
+{
+ pdf_obj *o, *r;
+ int i, n;
+
+ /* If the parent node doesn't have an entry of this type, give up. */
+ o = pdf_dict_gets(dict, text);
+ if (!o)
+ return;
+
+ /* If the resources dict we are building doesn't have an entry of this
+ * type yet, then just copy it (ensuring it's not a reference) */
+ r = pdf_dict_gets(res, text);
+ if (r == NULL)
+ {
+ o = pdf_resolve_indirect(o);
+ if (pdf_is_dict(o))
+ o = pdf_copy_dict(ctx, o);
+ else if (pdf_is_array(o))
+ o = pdf_copy_array(ctx, o);
+ else
+ o = NULL;
+ if (o)
+ pdf_dict_puts(res, text, o);
+ return;
+ }
+
+ /* Otherwise we need to merge o into r */
+ if (pdf_is_dict(o))
+ {
+ n = pdf_dict_len(o);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *key = pdf_dict_get_key(o, i);
+ pdf_obj *val = pdf_dict_get_val(o, i);
+
+ if (pdf_dict_gets(res, pdf_to_name(key)))
+ continue;
+ pdf_dict_puts(res, pdf_to_name(key), val);
+ }
+ }
+}
+
+static void
+lpr_inherit_res(fz_context *ctx, pdf_obj *node, int depth, pdf_obj *dict)
+{
+ while (1)
+ {
+ pdf_obj *o;
+
+ node = pdf_dict_gets(node, "Parent");
+ depth--;
+ if (!node || depth < 0)
+ break;
+
+ o = pdf_dict_gets(node, "Resources");
+ if (o)
+ {
+ lpr_inherit_res_contents(ctx, dict, o, "ExtGState");
+ lpr_inherit_res_contents(ctx, dict, o, "ColorSpace");
+ lpr_inherit_res_contents(ctx, dict, o, "Pattern");
+ lpr_inherit_res_contents(ctx, dict, o, "Shading");
+ lpr_inherit_res_contents(ctx, dict, o, "XObject");
+ lpr_inherit_res_contents(ctx, dict, o, "Font");
+ lpr_inherit_res_contents(ctx, dict, o, "ProcSet");
+ lpr_inherit_res_contents(ctx, dict, o, "Properties");
+ }
+ }
+}
+
+static pdf_obj *
+lpr_inherit(fz_context *ctx, pdf_obj *node, char *text, int depth)
+{
+ do
+ {
+ pdf_obj *o = pdf_dict_gets(node, text);
+
+ if (o)
+ return pdf_resolve_indirect(o);
+ node = pdf_dict_gets(node, "Parent");
+ depth--;
+ }
+ while (depth >= 0 && node);
+
+ return NULL;
+}
+
+static int
+lpr(fz_context *ctx, pdf_obj *node, int depth, int page)
+{
+ pdf_obj *kids;
+ pdf_obj *o = NULL;
+ int i, n;
+
+ if (pdf_obj_mark(node))
+ return page;
+
+ fz_var(o);
+
+ fz_try(ctx)
+ {
+ if (!strcmp("Page", pdf_to_name(pdf_dict_gets(node, "Type"))))
+ {
+ pdf_obj *r; /* r is deliberately not cleaned up */
+
+ /* Copy resources down to the child */
+ o = pdf_keep_obj(pdf_dict_gets(node, "Resources"));
+ if (!o)
+ {
+ o = pdf_keep_obj(pdf_new_dict(ctx, 2));
+ pdf_dict_puts(node, "Resources", o);
+ }
+ lpr_inherit_res(ctx, node, depth, o);
+ r = lpr_inherit(ctx, node, "MediaBox", depth);
+ if (r)
+ pdf_dict_puts(node, "MediaBox", r);
+ r = lpr_inherit(ctx, node, "CropBox", depth);
+ if (r)
+ pdf_dict_puts(node, "CropBox", r);
+ r = lpr_inherit(ctx, node, "BleedBox", depth);
+ if (r)
+ pdf_dict_puts(node, "BleedBox", r);
+ r = lpr_inherit(ctx, node, "TrimBox", depth);
+ if (r)
+ pdf_dict_puts(node, "TrimBox", r);
+ r = lpr_inherit(ctx, node, "ArtBox", depth);
+ if (r)
+ pdf_dict_puts(node, "ArtBox", r);
+ r = lpr_inherit(ctx, node, "Rotate", depth);
+ if (r)
+ pdf_dict_puts(node, "Rotate", r);
+ page++;
+ }
+ else
+ {
+ kids = pdf_dict_gets(node, "Kids");
+ n = pdf_array_len(kids);
+ for(i = 0; i < n; i++)
+ {
+ page = lpr(ctx, pdf_array_get(kids, i), depth+1, page);
+ }
+ pdf_dict_dels(node, "Resources");
+ pdf_dict_dels(node, "MediaBox");
+ pdf_dict_dels(node, "CropBox");
+ pdf_dict_dels(node, "BleedBox");
+ pdf_dict_dels(node, "TrimBox");
+ pdf_dict_dels(node, "ArtBox");
+ pdf_dict_dels(node, "Rotate");
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(o);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ pdf_obj_unmark(node);
+
+ return page;
+}
+
+void
+pdf_localise_page_resources(pdf_document *xref)
+{
+ fz_context *ctx = xref->ctx;
+
+ if (xref->resources_localised)
+ return;
+
+ lpr(ctx, pdf_dict_getp(pdf_trailer(xref), "Root/Pages"), 0, 0);
+
+ xref->resources_localised = 1;
+}
+
+static void
+linearize(pdf_document *xref, pdf_write_options *opts)
+{
+ int i;
+ int n = pdf_xref_len(xref) + 2;
+ int *reorder;
+ int *rev_renumber_map;
+ int *rev_gen_list;
+ fz_context *ctx = xref->ctx;
+
+ opts->page_object_lists = page_objects_list_create(ctx);
+
+ /* Ensure that every page has local references of its resources */
+ /* FIXME: We could 'thin' the resources according to what is actually
+ * required for each page, but this would require us to run the page
+ * content streams. */
+ pdf_localise_page_resources(xref);
+
+ /* Walk the objects for each page, marking which ones are used, where */
+ memset(opts->use_list, 0, n * sizeof(int));
+ mark_trailer(xref, opts, pdf_trailer(xref));
+
+ /* Add new objects required for linearization */
+ add_linearization_objs(xref, opts);
+
+#ifdef DEBUG_WRITING
+ fprintf(stderr, "Usage calculated:\n");
+ for (i=0; i < pdf_xref_len(xref); i++)
+ {
+ fprintf(stderr, "%d: use=%d\n", i, opts->use_list[i]);
+ }
+#endif
+
+ /* Allocate/init the structures used for renumbering the objects */
+ reorder = fz_calloc(ctx, n, sizeof(int));
+ rev_renumber_map = fz_calloc(ctx, n, sizeof(int));
+ rev_gen_list = fz_calloc(ctx, n, sizeof(int));
+ for (i = 0; i < n; i++)
+ {
+ reorder[i] = i;
+ }
+
+ /* Heap sort the reordering */
+ heap_sort(reorder+1, n-1, opts->use_list, &order_ge);
+
+#ifdef DEBUG_WRITING
+ fprintf(stderr, "Reordered:\n");
+ for (i=1; i < pdf_xref_len(xref); i++)
+ {
+ fprintf(stderr, "%d: use=%d\n", i, opts->use_list[reorder[i]]);
+ }
+#endif
+
+ /* Find the split point */
+ for (i = 1; (opts->use_list[reorder[i]] & USE_PARAMS) == 0; i++);
+ opts->start = i;
+
+ /* Roll the reordering into the renumber_map */
+ for (i = 0; i < n; i++)
+ {
+ opts->renumber_map[reorder[i]] = i;
+ rev_renumber_map[i] = opts->rev_renumber_map[reorder[i]];
+ rev_gen_list[i] = opts->rev_gen_list[reorder[i]];
+ }
+ fz_free(ctx, opts->rev_renumber_map);
+ fz_free(ctx, opts->rev_gen_list);
+ opts->rev_renumber_map = rev_renumber_map;
+ opts->rev_gen_list = rev_gen_list;
+ fz_free(ctx, reorder);
+
+ /* Apply the renumber_map */
+ page_objects_list_renumber(opts);
+ renumberobjs(xref, opts);
+
+ page_objects_list_sort_and_dedupe(ctx, opts->page_object_lists);
+}
+
+static void
+update_linearization_params(pdf_document *xref, pdf_write_options *opts)
+{
+ int offset;
+ pdf_set_int(opts->linear_l, opts->file_len);
+ /* Primary hint stream offset (of object, not stream!) */
+ pdf_set_int(opts->linear_h0, opts->ofs_list[pdf_xref_len(xref)-1]);
+ /* Primary hint stream length (of object, not stream!) */
+ offset = (opts->start == 1 ? opts->main_xref_offset : opts->ofs_list[1] + opts->hintstream_len);
+ pdf_set_int(opts->linear_h1, offset - opts->ofs_list[pdf_xref_len(xref)-1]);
+ /* Object number of first pages page object (the first object of page 0) */
+ pdf_set_int(opts->linear_o, opts->page_object_lists->page[0]->object[0]);
+ /* Offset of end of first page (first page is followed by primary
+ * hint stream (object n-1) then remaining pages (object 1...). The
+ * primary hint stream counts as part of the first pages data, I think.
+ */
+ offset = (opts->start == 1 ? opts->main_xref_offset : opts->ofs_list[1] + opts->hintstream_len);
+ pdf_set_int(opts->linear_e, offset);
+ /* Number of pages in document */
+ pdf_set_int(opts->linear_n, opts->page_count);
+ /* Offset of first entry in main xref table */
+ pdf_set_int(opts->linear_t, opts->first_xref_entry_offset + opts->hintstream_len);
+ /* Offset of shared objects hint table in the primary hint stream */
+ pdf_set_int(opts->hints_s, opts->hints_shared_offset);
+ /* Primary hint stream length */
+ pdf_set_int(opts->hints_length, opts->hintstream_len);
+}
+
+/*
+ * Make sure we have loaded objects from object streams.
+ */
+
+static void preloadobjstms(pdf_document *xref)
+{
+ pdf_obj *obj;
+ int num;
+ int xref_len = pdf_xref_len(xref);
+
+ for (num = 0; num < xref_len; num++)
+ {
+ if (pdf_get_xref_entry(xref, num)->type == 'o')
+ {
+ obj = pdf_load_object(xref, num, 0);
+ pdf_drop_obj(obj);
+ }
+ }
+}
+
+/*
+ * Save streams and objects to the output
+ */
+
+static inline int isbinary(int c)
+{
+ if (c == '\n' || c == '\r' || c == '\t')
+ return 0;
+ return c < 32 || c > 127;
+}
+
+static int isbinarystream(fz_buffer *buf)
+{
+ int i;
+ for (i = 0; i < buf->len; i++)
+ if (isbinary(buf->data[i]))
+ return 1;
+ return 0;
+}
+
+static fz_buffer *hexbuf(fz_context *ctx, unsigned char *p, int n)
+{
+ static const char hex[16] = "0123456789abcdef";
+ fz_buffer *buf;
+ int x = 0;
+
+ buf = fz_new_buffer(ctx, n * 2 + (n / 32) + 2);
+
+ while (n--)
+ {
+ buf->data[buf->len++] = hex[*p >> 4];
+ buf->data[buf->len++] = hex[*p & 15];
+ if (++x == 32)
+ {
+ buf->data[buf->len++] = '\n';
+ x = 0;
+ }
+ p++;
+ }
+
+ buf->data[buf->len++] = '>';
+ buf->data[buf->len++] = '\n';
+
+ return buf;
+}
+
+static void addhexfilter(pdf_document *xref, pdf_obj *dict)
+{
+ pdf_obj *f, *dp, *newf, *newdp;
+ pdf_obj *ahx, *nullobj;
+ fz_context *ctx = xref->ctx;
+
+ ahx = pdf_new_name(ctx, "ASCIIHexDecode");
+ nullobj = pdf_new_null(ctx);
+ newf = newdp = NULL;
+
+ f = pdf_dict_gets(dict, "Filter");
+ dp = pdf_dict_gets(dict, "DecodeParms");
+
+ if (pdf_is_name(f))
+ {
+ newf = pdf_new_array(ctx, 2);
+ pdf_array_push(newf, ahx);
+ pdf_array_push(newf, f);
+ f = newf;
+ if (pdf_is_dict(dp))
+ {
+ newdp = pdf_new_array(ctx, 2);
+ pdf_array_push(newdp, nullobj);
+ pdf_array_push(newdp, dp);
+ dp = newdp;
+ }
+ }
+ else if (pdf_is_array(f))
+ {
+ pdf_array_insert(f, ahx);
+ if (pdf_is_array(dp))
+ pdf_array_insert(dp, nullobj);
+ }
+ else
+ f = ahx;
+
+ pdf_dict_puts(dict, "Filter", f);
+ if (dp)
+ pdf_dict_puts(dict, "DecodeParms", dp);
+
+ pdf_drop_obj(ahx);
+ pdf_drop_obj(nullobj);
+ pdf_drop_obj(newf);
+ pdf_drop_obj(newdp);
+}
+
+static void copystream(pdf_document *xref, pdf_write_options *opts, pdf_obj *obj_orig, int num, int gen)
+{
+ fz_buffer *buf, *tmp;
+ pdf_obj *newlen;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+ int orig_num = opts->rev_renumber_map[num];
+ int orig_gen = opts->rev_gen_list[num];
+
+ buf = pdf_load_raw_renumbered_stream(xref, num, gen, orig_num, orig_gen);
+
+ obj = pdf_copy_dict(ctx, obj_orig);
+ if (opts->do_ascii && isbinarystream(buf))
+ {
+ tmp = hexbuf(ctx, buf->data, buf->len);
+ fz_drop_buffer(ctx, buf);
+ buf = tmp;
+
+ addhexfilter(xref, obj);
+
+ newlen = pdf_new_int(ctx, buf->len);
+ pdf_dict_puts(obj, "Length", newlen);
+ pdf_drop_obj(newlen);
+ }
+
+ fprintf(opts->out, "%d %d obj\n", num, gen);
+ pdf_fprint_obj(opts->out, obj, opts->do_expand == 0);
+ fprintf(opts->out, "stream\n");
+ fwrite(buf->data, 1, buf->len, opts->out);
+ fprintf(opts->out, "endstream\nendobj\n\n");
+
+ fz_drop_buffer(ctx, buf);
+ pdf_drop_obj(obj);
+}
+
+static void expandstream(pdf_document *xref, pdf_write_options *opts, pdf_obj *obj_orig, int num, int gen)
+{
+ fz_buffer *buf, *tmp;
+ pdf_obj *newlen;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+ int orig_num = opts->rev_renumber_map[num];
+ int orig_gen = opts->rev_gen_list[num];
+ int truncated = 0;
+
+ buf = pdf_load_renumbered_stream(xref, num, gen, orig_num, orig_gen, (opts->continue_on_error ? &truncated : NULL));
+ if (truncated && opts->errors)
+ (*opts->errors)++;
+
+ obj = pdf_copy_dict(ctx, obj_orig);
+ pdf_dict_dels(obj, "Filter");
+ pdf_dict_dels(obj, "DecodeParms");
+
+ if (opts->do_ascii && isbinarystream(buf))
+ {
+ tmp = hexbuf(ctx, buf->data, buf->len);
+ fz_drop_buffer(ctx, buf);
+ buf = tmp;
+
+ addhexfilter(xref, obj);
+ }
+
+ newlen = pdf_new_int(ctx, buf->len);
+ pdf_dict_puts(obj, "Length", newlen);
+ pdf_drop_obj(newlen);
+
+ fprintf(opts->out, "%d %d obj\n", num, gen);
+ pdf_fprint_obj(opts->out, obj, opts->do_expand == 0);
+ fprintf(opts->out, "stream\n");
+ fwrite(buf->data, 1, buf->len, opts->out);
+ fprintf(opts->out, "endstream\nendobj\n\n");
+
+ fz_drop_buffer(ctx, buf);
+ pdf_drop_obj(obj);
+}
+
+static int is_image_filter(char *s)
+{
+ if (!strcmp(s, "CCITTFaxDecode") || !strcmp(s, "CCF") ||
+ !strcmp(s, "DCTDecode") || !strcmp(s, "DCT") ||
+ !strcmp(s, "RunLengthDecode") || !strcmp(s, "RL") ||
+ !strcmp(s, "JBIG2Decode") ||
+ !strcmp(s, "JPXDecode"))
+ return 1;
+ return 0;
+}
+
+static int filter_implies_image(pdf_document *xref, pdf_obj *o)
+{
+ if (!o)
+ return 0;
+ if (pdf_is_name(o))
+ return is_image_filter(pdf_to_name(o));
+ if (pdf_is_array(o))
+ {
+ int i, len;
+ len = pdf_array_len(o);
+ for (i = 0; i < len; i++)
+ if (is_image_filter(pdf_to_name(pdf_array_get(o, i))))
+ return 1;
+ }
+ return 0;
+}
+
+static void writeobject(pdf_document *xref, pdf_write_options *opts, int num, int gen)
+{
+ pdf_xref_entry *entry;
+ pdf_obj *obj;
+ pdf_obj *type;
+ fz_context *ctx = xref->ctx;
+
+ fz_try(ctx)
+ {
+ obj = pdf_load_object(xref, num, gen);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ if (opts->continue_on_error)
+ {
+ fprintf(opts->out, "%d %d obj\nnull\nendobj\n", num, gen);
+ if (opts->errors)
+ (*opts->errors)++;
+ fz_warn(ctx, "%s", fz_caught_message(ctx));
+ return;
+ }
+ else
+ fz_rethrow(ctx);
+ }
+
+ /* skip ObjStm and XRef objects */
+ if (pdf_is_dict(obj))
+ {
+ type = pdf_dict_gets(obj, "Type");
+ if (pdf_is_name(type) && !strcmp(pdf_to_name(type), "ObjStm"))
+ {
+ opts->use_list[num] = 0;
+ pdf_drop_obj(obj);
+ return;
+ }
+ if (pdf_is_name(type) && !strcmp(pdf_to_name(type), "XRef"))
+ {
+ opts->use_list[num] = 0;
+ pdf_drop_obj(obj);
+ return;
+ }
+ }
+
+ entry = pdf_get_xref_entry(xref, num);
+ if (!pdf_is_stream(xref, num, gen))
+ {
+ fprintf(opts->out, "%d %d obj\n", num, gen);
+ pdf_fprint_obj(opts->out, obj, opts->do_expand == 0);
+ fprintf(opts->out, "endobj\n\n");
+ }
+ else if (entry->stm_ofs < 0 && entry->stm_buf == NULL)
+ {
+ fprintf(opts->out, "%d %d obj\n", num, gen);
+ pdf_fprint_obj(opts->out, obj, opts->do_expand == 0);
+ fprintf(opts->out, "stream\nendstream\nendobj\n\n");
+ }
+ else
+ {
+ int dontexpand = 0;
+ if (opts->do_expand != 0 && opts->do_expand != fz_expand_all)
+ {
+ pdf_obj *o;
+
+ if ((o = pdf_dict_gets(obj, "Type"), !strcmp(pdf_to_name(o), "XObject")) &&
+ (o = pdf_dict_gets(obj, "Subtype"), !strcmp(pdf_to_name(o), "Image")))
+ dontexpand = !(opts->do_expand & fz_expand_images);
+ if (o = pdf_dict_gets(obj, "Type"), !strcmp(pdf_to_name(o), "Font"))
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if (o = pdf_dict_gets(obj, "Type"), !strcmp(pdf_to_name(o), "FontDescriptor"))
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if ((o = pdf_dict_gets(obj, "Length1")) != NULL)
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if ((o = pdf_dict_gets(obj, "Length2")) != NULL)
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if ((o = pdf_dict_gets(obj, "Length3")) != NULL)
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if (o = pdf_dict_gets(obj, "Subtype"), !strcmp(pdf_to_name(o), "Type1C"))
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if (o = pdf_dict_gets(obj, "Subtype"), !strcmp(pdf_to_name(o), "CIDFontType0C"))
+ dontexpand = !(opts->do_expand & fz_expand_fonts);
+ if (o = pdf_dict_gets(obj, "Filter"), filter_implies_image(xref, o))
+ dontexpand = !(opts->do_expand & fz_expand_images);
+ if (pdf_dict_gets(obj, "Width") != NULL && pdf_dict_gets(obj, "Height") != NULL)
+ dontexpand = !(opts->do_expand & fz_expand_images);
+ }
+ fz_try(ctx)
+ {
+ if (opts->do_expand && !dontexpand && !pdf_is_jpx_image(ctx, obj))
+ expandstream(xref, opts, obj, num, gen);
+ else
+ copystream(xref, opts, obj, num, gen);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ if (opts->continue_on_error)
+ {
+ fprintf(opts->out, "%d %d obj\nnull\nendobj\n", num, gen);
+ if (opts->errors)
+ (*opts->errors)++;
+ fz_warn(ctx, "%s", fz_caught_message(ctx));
+ }
+ else
+ {
+ pdf_drop_obj(obj);
+ fz_rethrow(ctx);
+ }
+ }
+ }
+
+ pdf_drop_obj(obj);
+}
+
+static void writexref(pdf_document *xref, pdf_write_options *opts, int from, int to, int first, int main_xref_offset, int startxref)
+{
+ pdf_obj *trailer = NULL;
+ pdf_obj *obj;
+ pdf_obj *nobj = NULL;
+ int num;
+ fz_context *ctx = xref->ctx;
+
+ fprintf(opts->out, "xref\n%d %d\n", from, to - from);
+ opts->first_xref_entry_offset = ftell(opts->out);
+ for (num = from; num < to; num++)
+ {
+ if (opts->use_list[num])
+ fprintf(opts->out, "%010d %05d n \n", opts->ofs_list[num], opts->gen_list[num]);
+ else
+ fprintf(opts->out, "%010d %05d f \n", opts->ofs_list[num], opts->gen_list[num]);
+ }
+ fprintf(opts->out, "\n");
+
+ fz_var(trailer);
+ fz_var(nobj);
+
+ fz_try(ctx)
+ {
+ trailer = pdf_new_dict(ctx, 5);
+
+ nobj = pdf_new_int(ctx, to);
+ pdf_dict_puts(trailer, "Size", nobj);
+ pdf_drop_obj(nobj);
+ nobj = NULL;
+
+ if (first)
+ {
+ obj = pdf_dict_gets(pdf_trailer(xref), "Info");
+ if (obj)
+ pdf_dict_puts(trailer, "Info", obj);
+
+ obj = pdf_dict_gets(pdf_trailer(xref), "Root");
+ if (obj)
+ pdf_dict_puts(trailer, "Root", obj);
+
+ obj = pdf_dict_gets(pdf_trailer(xref), "ID");
+ if (obj)
+ pdf_dict_puts(trailer, "ID", obj);
+ }
+ if (main_xref_offset != 0)
+ {
+ nobj = pdf_new_int(ctx, main_xref_offset);
+ pdf_dict_puts(trailer, "Prev", nobj);
+ pdf_drop_obj(nobj);
+ nobj = NULL;
+ }
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(nobj);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+
+ fprintf(opts->out, "trailer\n");
+ pdf_fprint_obj(opts->out, trailer, opts->do_expand == 0);
+ fprintf(opts->out, "\n");
+
+ pdf_drop_obj(trailer);
+
+ fprintf(opts->out, "startxref\n%d\n%%%%EOF\n", startxref);
+}
+
+static void
+padto(FILE *file, int target)
+{
+ int pos = ftell(file);
+
+ assert(pos <= target);
+ while (pos < target)
+ {
+ fputc('\n', file);
+ pos++;
+ }
+}
+
+static void
+dowriteobject(pdf_document *xref, pdf_write_options *opts, int num, int pass)
+{
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, num);
+ if (entry->type == 'f')
+ opts->gen_list[num] = entry->gen;
+ if (entry->type == 'n')
+ opts->gen_list[num] = entry->gen;
+ if (entry->type == 'o')
+ opts->gen_list[num] = 0;
+
+ /* If we are renumbering, then make sure all generation numbers are
+ * zero (except object 0 which must be free, and have a gen number of
+ * 65535). Changing the generation numbers (and indeed object numbers)
+ * will break encryption - so only do this if we are renumbering
+ * anyway. */
+ if (opts->do_garbage >= 2)
+ opts->gen_list[num] = (num == 0 ? 65535 : 0);
+
+ if (opts->do_garbage && !opts->use_list[num])
+ return;
+
+ if (entry->type == 'n' || entry->type == 'o')
+ {
+ if (pass > 0)
+ padto(opts->out, opts->ofs_list[num]);
+ opts->ofs_list[num] = ftell(opts->out);
+ writeobject(xref, opts, num, opts->gen_list[num]);
+ }
+ else
+ opts->use_list[num] = 0;
+}
+
+static void
+writeobjects(pdf_document *xref, pdf_write_options *opts, int pass)
+{
+ int num;
+ int xref_len = pdf_xref_len(xref);
+
+ fprintf(opts->out, "%%PDF-%d.%d\n", xref->version / 10, xref->version % 10);
+ fprintf(opts->out, "%%\316\274\341\277\246\n\n");
+
+ dowriteobject(xref, opts, opts->start, pass);
+
+ if (opts->do_linear)
+ {
+ /* Write first xref */
+ if (pass == 0)
+ opts->first_xref_offset = ftell(opts->out);
+ else
+ padto(opts->out, opts->first_xref_offset);
+ writexref(xref, opts, opts->start, pdf_xref_len(xref), 1, opts->main_xref_offset, 0);
+ }
+
+ for (num = opts->start+1; num < xref_len; num++)
+ dowriteobject(xref, opts, num, pass);
+ if (opts->do_linear && pass == 1)
+ {
+ int offset = (opts->start == 1 ? opts->main_xref_offset : opts->ofs_list[1] + opts->hintstream_len);
+ padto(opts->out, offset);
+ }
+ for (num = 1; num < opts->start; num++)
+ {
+ if (pass == 1)
+ opts->ofs_list[num] += opts->hintstream_len;
+ dowriteobject(xref, opts, num, pass);
+ }
+}
+
+static int
+my_log2(int x)
+{
+ int i = 0;
+
+ if (x <= 0)
+ return 0;
+
+ while ((1<<i) <= x && (1<<i) > 0)
+ i++;
+
+ if ((1<<i) <= 0)
+ return 0;
+
+ return i;
+}
+
+static void
+make_page_offset_hints(pdf_document *xref, pdf_write_options *opts, fz_buffer *buf)
+{
+ fz_context *ctx = xref->ctx;
+ int i, j;
+ int min_objs_per_page, max_objs_per_page;
+ int min_page_length, max_page_length;
+ int objs_per_page_bits;
+ int min_shared_object, max_shared_object;
+ int max_shared_object_refs;
+ int min_shared_length, max_shared_length;
+ page_objects **pop = &opts->page_object_lists->page[0];
+ int page_len_bits, shared_object_bits, shared_object_id_bits;
+ int shared_length_bits;
+ int xref_len = pdf_xref_len(xref);
+
+ min_shared_object = pdf_xref_len(xref);
+ max_shared_object = 1;
+ min_shared_length = opts->file_len;
+ max_shared_length = 0;
+ for (i=1; i < xref_len; i++)
+ {
+ int min, max, page;
+
+ min = opts->ofs_list[i];
+ if (i == opts->start-1 || (opts->start == 1 && i == xref_len-1))
+ max = opts->main_xref_offset;
+ else if (i == xref_len-1)
+ max = opts->ofs_list[1];
+ else
+ max = opts->ofs_list[i+1];
+
+ assert(max > min);
+
+ if (opts->use_list[i] & USE_SHARED)
+ {
+ page = -1;
+ if (i < min_shared_object)
+ min_shared_object = i;
+ if (i > max_shared_object)
+ max_shared_object = i;
+ if (min_shared_length > max - min)
+ min_shared_length = max - min;
+ if (max_shared_length < max - min)
+ max_shared_length = max - min;
+ }
+ else if (opts->use_list[i] & (USE_CATALOGUE | USE_HINTS | USE_PARAMS))
+ page = -1;
+ else if (opts->use_list[i] & USE_PAGE1)
+ {
+ page = 0;
+ if (min_shared_length > max - min)
+ min_shared_length = max - min;
+ if (max_shared_length < max - min)
+ max_shared_length = max - min;
+ }
+ else if (opts->use_list[i] == 0)
+ page = -1;
+ else
+ page = opts->use_list[i]>>USE_PAGE_SHIFT;
+
+ if (page >= 0)
+ {
+ pop[page]->num_objects++;
+ if (pop[page]->min_ofs > min)
+ pop[page]->min_ofs = min;
+ if (pop[page]->max_ofs < max)
+ pop[page]->max_ofs = max;
+ }
+ }
+
+ min_objs_per_page = max_objs_per_page = pop[0]->num_objects;
+ min_page_length = max_page_length = pop[0]->max_ofs - pop[0]->min_ofs;
+ for (i=1; i < opts->page_count; i++)
+ {
+ int tmp;
+ if (min_objs_per_page > pop[i]->num_objects)
+ min_objs_per_page = pop[i]->num_objects;
+ if (max_objs_per_page < pop[i]->num_objects)
+ max_objs_per_page = pop[i]->num_objects;
+ tmp = pop[i]->max_ofs - pop[i]->min_ofs;
+ if (tmp < min_page_length)
+ min_page_length = tmp;
+ if (tmp > max_page_length)
+ max_page_length = tmp;
+ }
+
+ for (i=0; i < opts->page_count; i++)
+ {
+ int count = 0;
+ page_objects *po = opts->page_object_lists->page[i];
+ for (j = 0; j < po->len; j++)
+ {
+ if (i == 0 && opts->use_list[po->object[j]] & USE_PAGE1)
+ count++;
+ else if (i != 0 && opts->use_list[po->object[j]] & USE_SHARED)
+ count++;
+ }
+ po->num_shared = count;
+ if (i == 0 || count > max_shared_object_refs)
+ max_shared_object_refs = count;
+ }
+ if (min_shared_object > max_shared_object)
+ min_shared_object = max_shared_object = 0;
+
+ /* Table F.3 - Header */
+ /* Header Item 1: Least number of objects in a page */
+ fz_write_buffer_bits(ctx, buf, min_objs_per_page, 32);
+ /* Header Item 2: Location of first pages page object */
+ fz_write_buffer_bits(ctx, buf, opts->ofs_list[pop[0]->page_object_number], 32);
+ /* Header Item 3: Number of bits required to represent the difference
+ * between the greatest and least number of objects in a page. */
+ objs_per_page_bits = my_log2(max_objs_per_page - min_objs_per_page);
+ fz_write_buffer_bits(ctx, buf, objs_per_page_bits, 16);
+ /* Header Item 4: Least length of a page. */
+ fz_write_buffer_bits(ctx, buf, min_page_length, 32);
+ /* Header Item 5: Number of bits needed to represent the difference
+ * between the greatest and least length of a page. */
+ page_len_bits = my_log2(max_page_length - min_page_length);
+ fz_write_buffer_bits(ctx, buf, page_len_bits, 16);
+ /* Header Item 6: Least offset to start of content stream (Acrobat
+ * sets this to always be 0) */
+ fz_write_buffer_bits(ctx, buf, 0, 32);
+ /* Header Item 7: Number of bits needed to represent the difference
+ * between the greatest and least offset to content stream (Acrobat
+ * sets this to always be 0) */
+ fz_write_buffer_bits(ctx, buf, 0, 16);
+ /* Header Item 8: Least content stream length. (Acrobat
+ * sets this to always be 0) */
+ fz_write_buffer_bits(ctx, buf, 0, 32);
+ /* Header Item 9: Number of bits needed to represent the difference
+ * between the greatest and least content stream length (Acrobat
+ * sets this to always be the same as item 5) */
+ fz_write_buffer_bits(ctx, buf, page_len_bits, 16);
+ /* Header Item 10: Number of bits needed to represent the greatest
+ * number of shared object references. */
+ shared_object_bits = my_log2(max_shared_object_refs);
+ fz_write_buffer_bits(ctx, buf, shared_object_bits, 16);
+ /* Header Item 11: Number of bits needed to represent the greatest
+ * shared object identifier. */
+ shared_object_id_bits = my_log2(max_shared_object - min_shared_object + pop[0]->num_shared);
+ fz_write_buffer_bits(ctx, buf, shared_object_id_bits, 16);
+ /* Header Item 12: Number of bits needed to represent the numerator
+ * of the fractions. We always send 0. */
+ fz_write_buffer_bits(ctx, buf, 0, 16);
+ /* Header Item 13: Number of bits needed to represent the denominator
+ * of the fractions. We always send 0. */
+ fz_write_buffer_bits(ctx, buf, 0, 16);
+
+ /* Table F.4 - Page offset hint table (per page) */
+ /* Item 1: A number that, when added to the least number of objects
+ * on a page, gives the number of objects in the page. */
+ for (i = 0; i < opts->page_count; i++)
+ {
+ fz_write_buffer_bits(ctx, buf, pop[i]->num_objects - min_objs_per_page, objs_per_page_bits);
+ }
+ fz_write_buffer_pad(ctx, buf);
+ /* Item 2: A number that, when added to the least page length, gives
+ * the length of the page in bytes. */
+ for (i = 0; i < opts->page_count; i++)
+ {
+ fz_write_buffer_bits(ctx, buf, pop[i]->max_ofs - pop[i]->min_ofs - min_page_length, page_len_bits);
+ }
+ fz_write_buffer_pad(ctx, buf);
+ /* Item 3: The number of shared objects referenced from the page. */
+ for (i = 0; i < opts->page_count; i++)
+ {
+ fz_write_buffer_bits(ctx, buf, pop[i]->num_shared, shared_object_bits);
+ }
+ fz_write_buffer_pad(ctx, buf);
+ /* Item 4: Shared object id for each shared object ref in every page.
+ * Spec says "not for page 1", but acrobat does send page 1's - all
+ * as zeros. */
+ for (i = 0; i < opts->page_count; i++)
+ {
+ for (j = 0; j < pop[i]->len; j++)
+ {
+ int o = pop[i]->object[j];
+ if (i == 0 && opts->use_list[o] & USE_PAGE1)
+ fz_write_buffer_bits(ctx, buf, 0 /* o - pop[0]->page_object_number */, shared_object_id_bits);
+ if (i != 0 && opts->use_list[o] & USE_SHARED)
+ fz_write_buffer_bits(ctx, buf, o - min_shared_object + pop[0]->num_shared, shared_object_id_bits);
+ }
+ }
+ fz_write_buffer_pad(ctx, buf);
+ /* Item 5: Numerator of fractional position for each shared object reference. */
+ /* We always send 0 in 0 bits */
+ /* Item 6: A number that, when added to the least offset to the start
+ * of the content stream (F.3 Item 6), gives the offset in bytes of
+ * start of the pages content stream object relative to the beginning
+ * of the page. Always 0 in 0 bits. */
+ /* Item 7: A number that, when added to the least content stream length
+ * (F.3 Item 8), gives the length of the pages content stream object.
+ * Always == Item 2 as least content stream length = least page stream
+ * length.
+ */
+ for (i = 0; i < opts->page_count; i++)
+ {
+ fz_write_buffer_bits(ctx, buf, pop[i]->max_ofs - pop[i]->min_ofs - min_page_length, page_len_bits);
+ }
+
+ /* Pad, and then do shared object hint table */
+ fz_write_buffer_pad(ctx, buf);
+ opts->hints_shared_offset = buf->len;
+
+ /* Table F.5: */
+ /* Header Item 1: Object number of the first object in the shared
+ * objects section. */
+ fz_write_buffer_bits(ctx, buf, min_shared_object, 32);
+ /* Header Item 2: Location of first object in the shared objects
+ * section. */
+ fz_write_buffer_bits(ctx, buf, opts->ofs_list[min_shared_object], 32);
+ /* Header Item 3: The number of shared object entries for the first
+ * page. */
+ fz_write_buffer_bits(ctx, buf, pop[0]->num_shared, 32);
+ /* Header Item 4: The number of shared object entries for the shared
+ * objects section + first page. */
+ fz_write_buffer_bits(ctx, buf, max_shared_object - min_shared_object + pop[0]->num_shared, 32);
+ /* Header Item 5: The number of bits needed to represent the greatest
+ * number of objects in a shared object group (Always 0). */
+ fz_write_buffer_bits(ctx, buf, 0, 16);
+ /* Header Item 6: The least length of a shared object group in bytes. */
+ fz_write_buffer_bits(ctx, buf, min_shared_length, 32);
+ /* Header Item 7: The number of bits required to represent the
+ * difference between the greatest and least length of a shared object
+ * group. */
+ shared_length_bits = my_log2(max_shared_length - min_shared_length);
+ fz_write_buffer_bits(ctx, buf, shared_length_bits, 16);
+
+ /* Table F.6 */
+ /* Item 1: Shared object group length (page 1 objects) */
+ for (j = 0; j < pop[0]->len; j++)
+ {
+ int o = pop[0]->object[j];
+ int min, max;
+ min = opts->ofs_list[o];
+ if (o == opts->start-1)
+ max = opts->main_xref_offset;
+ else if (o < xref_len-1)
+ max = opts->ofs_list[o+1];
+ else
+ max = opts->ofs_list[1];
+ if (opts->use_list[o] & USE_PAGE1)
+ fz_write_buffer_bits(ctx, buf, max - min - min_shared_length, shared_length_bits);
+ }
+ /* Item 1: Shared object group length (shared objects) */
+ for (i = min_shared_object; i <= max_shared_object; i++)
+ {
+ int min, max;
+ min = opts->ofs_list[i];
+ if (i == opts->start-1)
+ max = opts->main_xref_offset;
+ else if (i < xref_len-1)
+ max = opts->ofs_list[i+1];
+ else
+ max = opts->ofs_list[1];
+ fz_write_buffer_bits(ctx, buf, max - min - min_shared_length, shared_length_bits);
+ }
+ fz_write_buffer_pad(ctx, buf);
+
+ /* Item 2: MD5 presence flags */
+ for (i = max_shared_object - min_shared_object + pop[0]->num_shared; i > 0; i--)
+ {
+ fz_write_buffer_bits(ctx, buf, 0, 1);
+ }
+ fz_write_buffer_pad(ctx, buf);
+ /* Item 3: MD5 sums (not present) */
+ fz_write_buffer_pad(ctx, buf);
+ /* Item 4: Number of objects in the group (not present) */
+}
+
+static void
+make_hint_stream(pdf_document *xref, pdf_write_options *opts)
+{
+ fz_context *ctx = xref->ctx;
+ fz_buffer *buf = fz_new_buffer(ctx, 100);
+
+ fz_try(ctx)
+ {
+ make_page_offset_hints(xref, opts, buf);
+ pdf_update_stream(xref, pdf_xref_len(xref)-1, buf);
+ opts->hintstream_len = buf->len;
+ fz_drop_buffer(ctx, buf);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_buffer(ctx, buf);
+ fz_rethrow(ctx);
+ }
+}
+
+#ifdef DEBUG_WRITING
+static void dump_object_details(pdf_document *xref, pdf_write_options *opts)
+{
+ int i;
+
+ for (i = 0; i < pdf_xref_len(xref); i++)
+ {
+ fprintf(stderr, "%d@%d: use=%d\n", i, opts->ofs_list[i], opts->use_list[i]);
+ }
+}
+#endif
+
+void pdf_write_document(pdf_document *xref, char *filename, fz_write_options *fz_opts)
+{
+ int lastfree;
+ int num;
+ pdf_write_options opts = { 0 };
+ fz_context *ctx;
+ int xref_len = pdf_xref_len(xref);
+
+ if (!xref)
+ return;
+
+ ctx = xref->ctx;
+
+ opts.out = fopen(filename, "wb");
+ if (!opts.out)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open output file '%s'", filename);
+
+ fz_try(ctx)
+ {
+ opts.do_expand = fz_opts ? fz_opts->do_expand : 0;
+ opts.do_garbage = fz_opts ? fz_opts->do_garbage : 0;
+ opts.do_ascii = fz_opts ? fz_opts->do_ascii: 0;
+ opts.do_linear = fz_opts ? fz_opts->do_linear: 0;
+ opts.start = 0;
+ opts.main_xref_offset = INT_MIN;
+ /* We deliberately make these arrays long enough to cope with
+ * 1 to n access rather than 0..n-1, and add space for 2 new
+ * extra entries that may be required for linearization. */
+ opts.use_list = fz_malloc_array(ctx, pdf_xref_len(xref) + 3, sizeof(int));
+ opts.ofs_list = fz_malloc_array(ctx, pdf_xref_len(xref) + 3, sizeof(int));
+ opts.gen_list = fz_calloc(ctx, pdf_xref_len(xref) + 3, sizeof(int));
+ opts.renumber_map = fz_malloc_array(ctx, pdf_xref_len(xref) + 3, sizeof(int));
+ opts.rev_renumber_map = fz_malloc_array(ctx, pdf_xref_len(xref) + 3, sizeof(int));
+ opts.rev_gen_list = fz_malloc_array(ctx, pdf_xref_len(xref) + 3, sizeof(int));
+ opts.continue_on_error = fz_opts->continue_on_error;
+ opts.errors = fz_opts->errors;
+
+ for (num = 0; num < xref_len; num++)
+ {
+ opts.use_list[num] = 0;
+ opts.ofs_list[num] = 0;
+ opts.renumber_map[num] = num;
+ opts.rev_renumber_map[num] = num;
+ opts.rev_gen_list[num] = pdf_get_xref_entry(xref, num)->gen;
+ }
+
+ /* Make sure any objects hidden in compressed streams have been loaded */
+ preloadobjstms(xref);
+
+ /* Sweep & mark objects from the trailer */
+ if (opts.do_garbage >= 1)
+ sweepobj(xref, &opts, pdf_trailer(xref));
+ else
+ for (num = 0; num < xref_len; num++)
+ opts.use_list[num] = 1;
+
+ /* Coalesce and renumber duplicate objects */
+ if (opts.do_garbage >= 3)
+ removeduplicateobjs(xref, &opts);
+
+ /* Compact xref by renumbering and removing unused objects */
+ if (opts.do_garbage >= 2 || opts.do_linear)
+ compactxref(xref, &opts);
+
+ /* Make renumbering affect all indirect references and update xref */
+ if (opts.do_garbage >= 2 || opts.do_linear)
+ renumberobjs(xref, &opts);
+
+ if (opts.do_linear)
+ {
+ linearize(xref, &opts);
+ }
+
+ writeobjects(xref, &opts, 0);
+
+#ifdef DEBUG_WRITING
+ dump_object_details(xref, &opts);
+#endif
+
+ /* Construct linked list of free object slots */
+ lastfree = 0;
+ for (num = 0; num < xref_len; num++)
+ {
+ if (!opts.use_list[num])
+ {
+ opts.gen_list[num]++;
+ opts.ofs_list[lastfree] = num;
+ lastfree = num;
+ }
+ }
+
+ if (opts.do_linear)
+ {
+ opts.main_xref_offset = ftell(opts.out);
+ writexref(xref, &opts, 0, opts.start, 0, 0, opts.first_xref_offset);
+ opts.file_len = ftell(opts.out);
+
+ make_hint_stream(xref, &opts);
+ opts.file_len += opts.hintstream_len;
+ opts.main_xref_offset += opts.hintstream_len;
+ update_linearization_params(xref, &opts);
+ fseek(opts.out, 0, 0);
+ writeobjects(xref, &opts, 1);
+
+ padto(opts.out, opts.main_xref_offset);
+ writexref(xref, &opts, 0, opts.start, 0, 0, opts.first_xref_offset);
+ }
+ else
+ {
+ opts.first_xref_offset = ftell(opts.out);
+ writexref(xref, &opts, 0, xref_len, 1, 0, opts.first_xref_offset);
+ }
+
+ xref->dirty = 0;
+ }
+ fz_always(ctx)
+ {
+#ifdef DEBUG_LINEARIZATION
+ page_objects_dump(&opts);
+ objects_dump(xref, &opts);
+#endif
+ fz_free(ctx, opts.use_list);
+ fz_free(ctx, opts.ofs_list);
+ fz_free(ctx, opts.gen_list);
+ fz_free(ctx, opts.renumber_map);
+ fz_free(ctx, opts.rev_renumber_map);
+ fz_free(ctx, opts.rev_gen_list);
+ pdf_drop_obj(opts.linear_l);
+ pdf_drop_obj(opts.linear_h0);
+ pdf_drop_obj(opts.linear_h1);
+ pdf_drop_obj(opts.linear_o);
+ pdf_drop_obj(opts.linear_e);
+ pdf_drop_obj(opts.linear_n);
+ pdf_drop_obj(opts.linear_t);
+ pdf_drop_obj(opts.hints_s);
+ pdf_drop_obj(opts.hints_length);
+ page_objects_list_destroy(ctx, opts.page_object_lists);
+ fclose(opts.out);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
diff --git a/source/pdf/pdf-xobject.c b/source/pdf/pdf-xobject.c
new file mode 100644
index 00000000..61fc876a
--- /dev/null
+++ b/source/pdf/pdf-xobject.c
@@ -0,0 +1,232 @@
+#include "mupdf/pdf.h"
+
+pdf_xobject *
+pdf_keep_xobject(fz_context *ctx, pdf_xobject *xobj)
+{
+ return (pdf_xobject *)fz_keep_storable(ctx, &xobj->storable);
+}
+
+void
+pdf_drop_xobject(fz_context *ctx, pdf_xobject *xobj)
+{
+ fz_drop_storable(ctx, &xobj->storable);
+}
+
+static void
+pdf_free_xobject_imp(fz_context *ctx, fz_storable *xobj_)
+{
+ pdf_xobject *xobj = (pdf_xobject *)xobj_;
+
+ if (xobj->colorspace)
+ fz_drop_colorspace(ctx, xobj->colorspace);
+ pdf_drop_obj(xobj->resources);
+ pdf_drop_obj(xobj->contents);
+ pdf_drop_obj(xobj->me);
+ fz_free(ctx, xobj);
+}
+
+static unsigned int
+pdf_xobject_size(pdf_xobject *xobj)
+{
+ if (xobj == NULL)
+ return 0;
+ return sizeof(*xobj) + (xobj->colorspace ? xobj->colorspace->size : 0);
+}
+
+pdf_xobject *
+pdf_load_xobject(pdf_document *xref, pdf_obj *dict)
+{
+ pdf_xobject *form;
+ pdf_obj *obj;
+ fz_context *ctx = xref->ctx;
+
+ if ((form = pdf_find_item(ctx, pdf_free_xobject_imp, dict)))
+ {
+ return form;
+ }
+
+ form = fz_malloc_struct(ctx, pdf_xobject);
+ FZ_INIT_STORABLE(form, 1, pdf_free_xobject_imp);
+ form->resources = NULL;
+ form->contents = NULL;
+ form->colorspace = NULL;
+ form->me = NULL;
+ form->iteration = 0;
+
+ /* Store item immediately, to avoid possible recursion if objects refer back to this one */
+ pdf_store_item(ctx, dict, form, pdf_xobject_size(form));
+
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(dict, "BBox");
+ pdf_to_rect(ctx, obj, &form->bbox);
+
+ obj = pdf_dict_gets(dict, "Matrix");
+ if (obj)
+ pdf_to_matrix(ctx, obj, &form->matrix);
+ else
+ form->matrix = fz_identity;
+
+ form->isolated = 0;
+ form->knockout = 0;
+ form->transparency = 0;
+
+ obj = pdf_dict_gets(dict, "Group");
+ if (obj)
+ {
+ pdf_obj *attrs = obj;
+
+ form->isolated = pdf_to_bool(pdf_dict_gets(attrs, "I"));
+ form->knockout = pdf_to_bool(pdf_dict_gets(attrs, "K"));
+
+ obj = pdf_dict_gets(attrs, "S");
+ if (pdf_is_name(obj) && !strcmp(pdf_to_name(obj), "Transparency"))
+ form->transparency = 1;
+
+ obj = pdf_dict_gets(attrs, "CS");
+ if (obj)
+ {
+ form->colorspace = pdf_load_colorspace(xref, obj);
+ if (!form->colorspace)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot load xobject colorspace");
+ }
+ }
+
+ form->resources = pdf_dict_gets(dict, "Resources");
+ if (form->resources)
+ pdf_keep_obj(form->resources);
+
+ form->contents = pdf_keep_obj(dict);
+ }
+ fz_catch(ctx)
+ {
+ pdf_remove_item(ctx, pdf_free_xobject_imp, dict);
+ pdf_drop_xobject(ctx, form);
+ fz_rethrow_message(ctx, "cannot load xobject content stream (%d %d R)", pdf_to_num(dict), pdf_to_gen(dict));
+ }
+ form->me = pdf_keep_obj(dict);
+
+ return form;
+}
+
+pdf_obj *
+pdf_new_xobject(pdf_document *xref, const fz_rect *bbox, const fz_matrix *mat)
+{
+ int idict_num;
+ pdf_obj *idict = NULL;
+ pdf_obj *dict = NULL;
+ pdf_xobject *form = NULL;
+ pdf_obj *obj = NULL;
+ pdf_obj *res = NULL;
+ pdf_obj *procset = NULL;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(idict);
+ fz_var(dict);
+ fz_var(form);
+ fz_var(obj);
+ fz_var(res);
+ fz_var(procset);
+ fz_try(ctx)
+ {
+ dict = pdf_new_dict(ctx, 0);
+
+ obj = pdf_new_rect(ctx, bbox);
+ pdf_dict_puts(dict, "BBox", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ obj = pdf_new_int(ctx, 1);
+ pdf_dict_puts(dict, "FormType", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ obj = pdf_new_int(ctx, 0);
+ pdf_dict_puts(dict, "Length", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ obj = pdf_new_matrix(ctx, mat);
+ pdf_dict_puts(dict, "Matrix", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ res = pdf_new_dict(ctx, 0);
+ procset = pdf_new_array(ctx, 2);
+ obj = pdf_new_name(ctx, "PDF");
+ pdf_array_push(procset, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ obj = pdf_new_name(ctx, "Text");
+ pdf_array_push(procset, obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+ pdf_dict_puts(res, "ProcSet", procset);
+ pdf_drop_obj(procset);
+ procset = NULL;
+ pdf_dict_puts(dict, "Resources", res);
+
+ obj = pdf_new_name(ctx, "Form");
+ pdf_dict_puts(dict, "Subtype", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ obj = pdf_new_name(ctx, "XObject");
+ pdf_dict_puts(dict, "Type", obj);
+ pdf_drop_obj(obj);
+ obj = NULL;
+
+ form = fz_malloc_struct(ctx, pdf_xobject);
+ FZ_INIT_STORABLE(form, 1, pdf_free_xobject_imp);
+ form->resources = NULL;
+ form->contents = NULL;
+ form->colorspace = NULL;
+ form->me = NULL;
+ form->iteration = 0;
+
+ form->bbox = *bbox;
+
+ form->matrix = *mat;
+
+ form->isolated = 0;
+ form->knockout = 0;
+ form->transparency = 0;
+
+ form->resources = res;
+ res = NULL;
+
+ idict_num = pdf_create_object(xref);
+ pdf_update_object(xref, idict_num, dict);
+ idict = pdf_new_indirect(ctx, idict_num, 0, xref);
+ pdf_drop_obj(dict);
+ dict = NULL;
+
+ pdf_store_item(ctx, idict, form, pdf_xobject_size(form));
+
+ form->contents = pdf_keep_obj(idict);
+ form->me = pdf_keep_obj(idict);
+
+ pdf_drop_xobject(ctx, form);
+ form = NULL;
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(procset);
+ pdf_drop_obj(res);
+ pdf_drop_obj(obj);
+ pdf_drop_obj(dict);
+ pdf_drop_obj(idict);
+ pdf_drop_xobject(ctx, form);
+ fz_rethrow_message(ctx, "failed to create xobject)");
+ }
+
+ return idict;
+}
+
+void pdf_update_xobject_contents(pdf_document *xref, pdf_xobject *form, fz_buffer *buffer)
+{
+ pdf_dict_dels(form->contents, "Filter");
+ pdf_dict_puts_drop(form->contents, "Length", pdf_new_int(xref->ctx, buffer->len));
+ pdf_update_stream(xref, pdf_to_num(form->contents), buffer);
+ form->iteration ++;
+}
diff --git a/source/pdf/pdf-xref-aux.c b/source/pdf/pdf-xref-aux.c
new file mode 100644
index 00000000..48634374
--- /dev/null
+++ b/source/pdf/pdf-xref-aux.c
@@ -0,0 +1,39 @@
+#include "mupdf/pdf.h"
+
+/*
+ These functions have been split out of pdf_xref.c to allow tools
+ to be linked without pulling in the interpreter. The interpreter
+ references the built-in font and cmap resources which are quite
+ big. Not linking those into the tools saves roughly 6MB in the
+ resulting executables.
+*/
+
+static void pdf_run_page_contents_shim(fz_document *doc, fz_page *page, fz_device *dev, const fz_matrix *transform, fz_cookie *cookie)
+{
+ pdf_run_page_contents((pdf_document*)doc, (pdf_page*)page, dev, transform, cookie);
+}
+
+static void pdf_run_annot_shim(fz_document *doc, fz_page *page, fz_annot *annot, fz_device *dev, const fz_matrix *transform, fz_cookie *cookie)
+{
+ pdf_run_annot((pdf_document*)doc, (pdf_page*)page, (pdf_annot *)annot, dev, transform, cookie);
+}
+
+pdf_document *
+pdf_open_document_with_stream(fz_context *ctx, fz_stream *file)
+{
+ pdf_document *doc = pdf_open_document_no_run_with_stream(ctx, file);
+ doc->super.run_page_contents = pdf_run_page_contents_shim;
+ doc->super.run_annot = pdf_run_annot_shim;
+ doc->update_appearance = pdf_update_appearance;
+ return doc;
+}
+
+pdf_document *
+pdf_open_document(fz_context *ctx, const char *filename)
+{
+ pdf_document *doc = pdf_open_document_no_run(ctx, filename);
+ doc->super.run_page_contents = pdf_run_page_contents_shim;
+ doc->super.run_annot = pdf_run_annot_shim;
+ doc->update_appearance = pdf_update_appearance;
+ return doc;
+}
diff --git a/source/pdf/pdf-xref.c b/source/pdf/pdf-xref.c
new file mode 100644
index 00000000..9224d515
--- /dev/null
+++ b/source/pdf/pdf-xref.c
@@ -0,0 +1,1552 @@
+#include "mupdf/pdf.h"
+
+static inline int iswhite(int ch)
+{
+ return
+ ch == '\000' || ch == '\011' || ch == '\012' ||
+ ch == '\014' || ch == '\015' || ch == '\040';
+}
+
+/*
+ * xref tables
+ */
+
+static void pdf_free_xref_sections(pdf_document *doc)
+{
+ fz_context *ctx = doc->ctx;
+ int x, e;
+
+ for (x = 0; x < doc->num_xref_sections; x++)
+ {
+ pdf_xref *xref = &doc->xref_sections[x];
+
+ for (e = 0; e < xref->len; e++)
+ {
+ pdf_xref_entry *entry = &xref->table[e];
+
+ if (entry->obj)
+ {
+ pdf_drop_obj(entry->obj);
+ fz_drop_buffer(ctx, entry->stm_buf);
+ }
+ }
+
+ fz_free(ctx, xref->table);
+ pdf_drop_obj(xref->trailer);
+ }
+
+ fz_free(ctx, doc->xref_sections);
+ doc->xref_sections = NULL;
+ doc->num_xref_sections = 0;
+}
+
+static void pdf_resize_xref(fz_context *ctx, pdf_xref *xref, int newlen)
+{
+ int i;
+
+ xref->table = fz_resize_array(ctx, xref->table, newlen, sizeof(pdf_xref_entry));
+ for (i = xref->len; i < newlen; i++)
+ {
+ xref->table[i].type = 0;
+ xref->table[i].ofs = 0;
+ xref->table[i].gen = 0;
+ xref->table[i].stm_ofs = 0;
+ xref->table[i].stm_buf = NULL;
+ xref->table[i].obj = NULL;
+ }
+ xref->len = newlen;
+}
+
+static void pdf_populate_next_xref_level(pdf_document *doc)
+{
+ pdf_xref *xref;
+ doc->xref_sections = fz_resize_array(doc->ctx, doc->xref_sections, doc->num_xref_sections + 1, sizeof(pdf_xref));
+ doc->num_xref_sections++;
+
+ xref = &doc->xref_sections[doc->num_xref_sections - 1];
+ xref->len = 0;
+ xref->table = NULL;
+ xref->trailer = NULL;
+}
+
+pdf_obj *pdf_trailer(pdf_document *doc)
+{
+ /* Return the document's final trailer */
+ pdf_xref *xref = &doc->xref_sections[0];
+
+ return xref->trailer;
+}
+
+void pdf_set_populating_xref_trailer(pdf_document *doc, pdf_obj *trailer)
+{
+ /* Update the trailer of the xref section being populated */
+ pdf_xref *xref = &doc->xref_sections[doc->num_xref_sections - 1];
+ pdf_drop_obj(xref->trailer);
+ xref->trailer = pdf_keep_obj(trailer);
+}
+
+int pdf_xref_len(pdf_document *doc)
+{
+ /* Return the length of the document's final xref section */
+ pdf_xref *xref = &doc->xref_sections[0];
+
+ return xref->len;
+}
+
+/* Used while reading the individual xref sections from a file */
+pdf_xref_entry *pdf_get_populating_xref_entry(pdf_document *doc, int i)
+{
+ /* Return an entry within the xref currently being populated */
+ pdf_xref *xref;
+
+ if (doc->num_xref_sections == 0)
+ {
+ doc->xref_sections = fz_calloc(doc->ctx, 1, sizeof(pdf_xref));
+ doc->num_xref_sections = 1;
+ }
+
+ xref = &doc->xref_sections[doc->num_xref_sections - 1];
+
+ if (i >= xref->len)
+ pdf_resize_xref(doc->ctx, xref, i+1);
+
+ return &xref->table[i];
+}
+
+/* Used after loading a document to access entries */
+pdf_xref_entry *pdf_get_xref_entry(pdf_document *doc, int i)
+{
+ int j;
+
+ /* Find the first xref section where the entry is defined. */
+ for (j = 0; j < doc->num_xref_sections; j++)
+ {
+ pdf_xref *xref = &doc->xref_sections[j];
+
+ if (i >= 0 && i < xref->len)
+ {
+ pdf_xref_entry *entry = &xref->table[i];
+
+ if (entry->type)
+ return entry;
+ }
+ }
+
+ /*
+ Didn't find the entry in any section. Return the entry from the final
+ section.
+ */
+ return &doc->xref_sections[0].table[i];
+}
+
+/* Used when altering a document */
+static pdf_xref_entry *pdf_get_new_xref_entry(pdf_document *doc, int i)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_xref *xref;
+
+ /* Make a new final xref section if we haven't already */
+ if (!doc->xref_altered)
+ {
+ doc->xref_sections = fz_resize_array(ctx, doc->xref_sections, doc->num_xref_sections + 1, sizeof(pdf_xref));
+ memmove(&doc->xref_sections[1], &doc->xref_sections[0], doc->num_xref_sections * sizeof(pdf_xref));
+ doc->num_xref_sections++;
+ xref = &doc->xref_sections[0];
+ xref->len = 0;
+ xref->table = NULL;
+ xref->trailer = pdf_keep_obj(doc->xref_sections[1].trailer);
+ doc->xref_altered = 1;
+ }
+
+ xref = &doc->xref_sections[0];
+ if (i >= xref->len)
+ pdf_resize_xref(ctx, xref, i + 1);
+
+ return &xref->table[i];
+}
+
+void pdf_replace_xref(pdf_document *doc, pdf_xref_entry *entries, int n)
+{
+ fz_context *ctx = doc->ctx;
+ pdf_xref *xref;
+ pdf_obj *trailer = pdf_keep_obj(pdf_trailer(doc));
+
+ /* The new table completely replaces the previous separate sections */
+ pdf_free_xref_sections(doc);
+
+ fz_var(trailer);
+ fz_try(ctx)
+ {
+ xref = fz_calloc(ctx, 1, sizeof(pdf_xref));
+ xref->table = entries;
+ xref->len = n;
+ xref->trailer = trailer;
+ trailer = NULL;
+
+ doc->xref_sections = xref;
+ doc->num_xref_sections = 1;
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(trailer);
+ fz_rethrow(ctx);
+ }
+}
+
+/*
+ * magic version tag and startxref
+ */
+
+static void
+pdf_load_version(pdf_document *xref)
+{
+ char buf[20];
+
+ fz_seek(xref->file, 0, SEEK_SET);
+ fz_read_line(xref->file, buf, sizeof buf);
+ if (memcmp(buf, "%PDF-", 5) != 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot recognize version marker");
+
+ xref->version = atoi(buf + 5) * 10 + atoi(buf + 7);
+}
+
+static void
+pdf_read_start_xref(pdf_document *xref)
+{
+ unsigned char buf[1024];
+ int t, n;
+ int i;
+
+ fz_seek(xref->file, 0, SEEK_END);
+
+ xref->file_size = fz_tell(xref->file);
+
+ t = fz_maxi(0, xref->file_size - (int)sizeof buf);
+ fz_seek(xref->file, t, SEEK_SET);
+
+ n = fz_read(xref->file, buf, sizeof buf);
+ if (n < 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot read from file");
+
+ for (i = n - 9; i >= 0; i--)
+ {
+ if (memcmp(buf + i, "startxref", 9) == 0)
+ {
+ i += 9;
+ while (iswhite(buf[i]) && i < n)
+ i ++;
+ xref->startxref = atoi((char*)(buf + i));
+ if (xref->startxref != 0)
+ return;
+ break;
+ }
+ }
+
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot find startxref");
+}
+
+/*
+ * trailer dictionary
+ */
+
+static int
+pdf_xref_size_from_old_trailer(pdf_document *xref, pdf_lexbuf *buf)
+{
+ int len;
+ char *s;
+ int t;
+ pdf_token tok;
+ int c;
+ int size;
+ int ofs;
+
+ /* Record the current file read offset so that we can reinstate it */
+ ofs = fz_tell(xref->file);
+
+ fz_read_line(xref->file, buf->scratch, buf->size);
+ if (strncmp(buf->scratch, "xref", 4) != 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot find xref marker");
+
+ while (1)
+ {
+ c = fz_peek_byte(xref->file);
+ if (!(c >= '0' && c <= '9'))
+ break;
+
+ fz_read_line(xref->file, buf->scratch, buf->size);
+ s = buf->scratch;
+ fz_strsep(&s, " "); /* ignore ofs */
+ if (!s)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "invalid range marker in xref");
+ len = fz_atoi(fz_strsep(&s, " "));
+
+ /* broken pdfs where the section is not on a separate line */
+ if (s && *s != '\0')
+ fz_seek(xref->file, -(2 + (int)strlen(s)), SEEK_CUR);
+
+ t = fz_tell(xref->file);
+ if (t < 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot tell in file");
+
+ fz_seek(xref->file, t + 20 * len, SEEK_SET);
+ }
+
+ fz_try(xref->ctx)
+ {
+ pdf_obj *trailer;
+ tok = pdf_lex(xref->file, buf);
+ if (tok != PDF_TOK_TRAILER)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "expected trailer marker");
+
+ tok = pdf_lex(xref->file, buf);
+ if (tok != PDF_TOK_OPEN_DICT)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "expected trailer dictionary");
+
+ trailer = pdf_parse_dict(xref, xref->file, buf);
+
+ size = pdf_to_int(pdf_dict_gets(trailer, "Size"));
+ if (!size)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "trailer missing Size entry");
+
+ pdf_drop_obj(trailer);
+ }
+ fz_catch(xref->ctx)
+ {
+ fz_rethrow_message(xref->ctx, "cannot parse trailer");
+ }
+
+ fz_seek(xref->file, ofs, SEEK_SET);
+
+ return size;
+}
+
+pdf_obj *
+pdf_new_ref(pdf_document *xref, pdf_obj *obj)
+{
+ int num = pdf_create_object(xref);
+ pdf_update_object(xref, num, obj);
+ return pdf_new_indirect(xref->ctx, num, 0, xref);
+}
+
+static pdf_obj *
+pdf_read_old_xref(pdf_document *xref, pdf_lexbuf *buf)
+{
+ int ofs, len;
+ char *s;
+ int n;
+ pdf_token tok;
+ int i;
+ int c;
+ pdf_obj *trailer;
+ int xref_len = pdf_xref_size_from_old_trailer(xref, buf);
+
+ /* Access last entry to ensure xref size up front and avoid reallocs */
+ (void)pdf_get_populating_xref_entry(xref, xref_len - 1);
+
+ fz_read_line(xref->file, buf->scratch, buf->size);
+ if (strncmp(buf->scratch, "xref", 4) != 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot find xref marker");
+
+ while (1)
+ {
+ c = fz_peek_byte(xref->file);
+ if (!(c >= '0' && c <= '9'))
+ break;
+
+ fz_read_line(xref->file, buf->scratch, buf->size);
+ s = buf->scratch;
+ ofs = fz_atoi(fz_strsep(&s, " "));
+ len = fz_atoi(fz_strsep(&s, " "));
+
+ /* broken pdfs where the section is not on a separate line */
+ if (s && *s != '\0')
+ {
+ fz_warn(xref->ctx, "broken xref section. proceeding anyway.");
+ fz_seek(xref->file, -(2 + (int)strlen(s)), SEEK_CUR);
+ }
+
+ if (ofs < 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "out of range object num in xref: %d", ofs);
+
+ /* broken pdfs where size in trailer undershoots entries in xref sections */
+ if (ofs + len > xref_len)
+ {
+ fz_warn(xref->ctx, "broken xref section, proceeding anyway.");
+ /* Access last entry to ensure size */
+ (void)pdf_get_populating_xref_entry(xref, ofs + len - 1);
+ }
+
+ for (i = ofs; i < ofs + len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_populating_xref_entry(xref, i);
+ n = fz_read(xref->file, (unsigned char *) buf->scratch, 20);
+ if (n < 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "cannot read xref table");
+ if (!entry->type)
+ {
+ s = buf->scratch;
+
+ /* broken pdfs where line start with white space */
+ while (*s != '\0' && iswhite(*s))
+ s++;
+
+ entry->ofs = atoi(s);
+ entry->gen = atoi(s + 11);
+ entry->type = s[17];
+ if (s[17] != 'f' && s[17] != 'n' && s[17] != 'o')
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "unexpected xref type: %#x (%d %d R)", s[17], i, entry->gen);
+ }
+ }
+ }
+
+ fz_try(xref->ctx)
+ {
+ tok = pdf_lex(xref->file, buf);
+ if (tok != PDF_TOK_TRAILER)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "expected trailer marker");
+
+ tok = pdf_lex(xref->file, buf);
+ if (tok != PDF_TOK_OPEN_DICT)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "expected trailer dictionary");
+
+ trailer = pdf_parse_dict(xref, xref->file, buf);
+ }
+ fz_catch(xref->ctx)
+ {
+ fz_rethrow_message(xref->ctx, "cannot parse trailer");
+ }
+ return trailer;
+}
+
+static void
+pdf_read_new_xref_section(pdf_document *xref, fz_stream *stm, int i0, int i1, int w0, int w1, int w2)
+{
+ int i, n;
+
+ if (i0 < 0 || i1 < 0)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "negative xref stream entry index");
+ if (i0 + i1 > pdf_xref_len(xref))
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "xref stream has too many entries");
+
+ for (i = i0; i < i0 + i1; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_populating_xref_entry(xref, i);
+ int a = 0;
+ int b = 0;
+ int c = 0;
+
+ if (fz_is_eof(stm))
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "truncated xref stream");
+
+ for (n = 0; n < w0; n++)
+ a = (a << 8) + fz_read_byte(stm);
+ for (n = 0; n < w1; n++)
+ b = (b << 8) + fz_read_byte(stm);
+ for (n = 0; n < w2; n++)
+ c = (c << 8) + fz_read_byte(stm);
+
+ if (!entry->type)
+ {
+ int t = w0 ? a : 1;
+ entry->type = t == 0 ? 'f' : t == 1 ? 'n' : t == 2 ? 'o' : 0;
+ entry->ofs = w1 ? b : 0;
+ entry->gen = w2 ? c : 0;
+ }
+ }
+}
+
+/* Entered with file locked, remains locked throughout. */
+static pdf_obj *
+pdf_read_new_xref(pdf_document *xref, pdf_lexbuf *buf)
+{
+ fz_stream *stm = NULL;
+ pdf_obj *trailer = NULL;
+ pdf_obj *index = NULL;
+ pdf_obj *obj = NULL;
+ int num, gen, stm_ofs;
+ int size, w0, w1, w2;
+ int t;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(trailer);
+ fz_var(stm);
+
+ fz_try(ctx)
+ {
+ pdf_xref_entry *entry;
+ int ofs = fz_tell(xref->file);
+ trailer = pdf_parse_ind_obj(xref, xref->file, buf, &num, &gen, &stm_ofs);
+ entry = pdf_get_populating_xref_entry(xref, num);
+ entry->ofs = ofs;
+ entry->gen = gen;
+ entry->stm_ofs = stm_ofs;
+ pdf_drop_obj(entry->obj);
+ entry->obj = pdf_keep_obj(trailer);
+ entry->type = 'n';
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot parse compressed xref stream object");
+ }
+
+ fz_try(ctx)
+ {
+ obj = pdf_dict_gets(trailer, "Size");
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "xref stream missing Size entry (%d %d R)", num, gen);
+
+ size = pdf_to_int(obj);
+ /* Access xref entry to assure table size */
+ (void)pdf_get_populating_xref_entry(xref, size-1);
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "object id (%d %d R) out of range (0..%d)", num, gen, pdf_xref_len(xref) - 1);
+
+ obj = pdf_dict_gets(trailer, "W");
+ if (!obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "xref stream missing W entry (%d %d R)", num, gen);
+ w0 = pdf_to_int(pdf_array_get(obj, 0));
+ w1 = pdf_to_int(pdf_array_get(obj, 1));
+ w2 = pdf_to_int(pdf_array_get(obj, 2));
+
+ if (w0 < 0)
+ fz_warn(ctx, "xref stream objects have corrupt type");
+ if (w1 < 0)
+ fz_warn(ctx, "xref stream objects have corrupt offset");
+ if (w2 < 0)
+ fz_warn(ctx, "xref stream objects have corrupt generation");
+
+ w0 = w0 < 0 ? 0 : w0;
+ w1 = w1 < 0 ? 0 : w1;
+ w2 = w2 < 0 ? 0 : w2;
+
+ index = pdf_dict_gets(trailer, "Index");
+
+ stm = pdf_open_stream_with_offset(xref, num, gen, trailer, stm_ofs);
+
+ if (!index)
+ {
+ pdf_read_new_xref_section(xref, stm, 0, size, w0, w1, w2);
+ }
+ else
+ {
+ int n = pdf_array_len(index);
+ for (t = 0; t < n; t += 2)
+ {
+ int i0 = pdf_to_int(pdf_array_get(index, t + 0));
+ int i1 = pdf_to_int(pdf_array_get(index, t + 1));
+ pdf_read_new_xref_section(xref, stm, i0, i1, w0, w1, w2);
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stm);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(trailer);
+ fz_rethrow(ctx);
+ }
+
+ return trailer;
+}
+
+/* File is locked on entry, and exit (but may be dropped in the middle) */
+static pdf_obj *
+pdf_read_xref(pdf_document *xref, int ofs, pdf_lexbuf *buf)
+{
+ int c;
+ fz_context *ctx = xref->ctx;
+ pdf_obj *trailer;
+
+ fz_seek(xref->file, ofs, SEEK_SET);
+
+ while (iswhite(fz_peek_byte(xref->file)))
+ fz_read_byte(xref->file);
+
+ fz_try(ctx)
+ {
+ c = fz_peek_byte(xref->file);
+ if (c == 'x')
+ trailer = pdf_read_old_xref(xref, buf);
+ else if (c >= '0' && c <= '9')
+ trailer = pdf_read_new_xref(xref, buf);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot recognize xref format");
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot read xref (ofs=%d)", ofs);
+ }
+ return trailer;
+}
+
+typedef struct ofs_list_s ofs_list;
+
+struct ofs_list_s
+{
+ int max;
+ int len;
+ int *list;
+};
+
+static int
+read_xref_section(pdf_document *xref, int ofs, pdf_lexbuf *buf, ofs_list *offsets)
+{
+ pdf_obj *trailer = NULL;
+ fz_context *ctx = xref->ctx;
+ int xrefstmofs = 0;
+ int prevofs = 0;
+
+ fz_var(trailer);
+
+ fz_try(ctx)
+ {
+ int i;
+ /* Avoid potential infinite recursion */
+ for (i = 0; i < offsets->len; i ++)
+ {
+ if (offsets->list[i] == ofs)
+ break;
+ }
+ if (i < offsets->len)
+ {
+ fz_warn(ctx, "ignoring xref recursion with offset %d", ofs);
+ return 0;
+ }
+ if (offsets->len == offsets->max)
+ {
+ offsets->list = fz_resize_array(ctx, offsets->list, offsets->max*2, sizeof(int));
+ offsets->max *= 2;
+ }
+ offsets->list[offsets->len++] = ofs;
+
+ trailer = pdf_read_xref(xref, ofs, buf);
+
+ pdf_set_populating_xref_trailer(xref, trailer);
+
+ /* FIXME: do we overwrite free entries properly? */
+ xrefstmofs = pdf_to_int(pdf_dict_gets(trailer, "XRefStm"));
+ if (xrefstmofs)
+ {
+ if (xrefstmofs < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "negative xref stream offset");
+
+ /*
+ Read the XRefStm stream, but throw away the resulting trailer. We do not
+ follow any Prev tag therein, as specified on Page 108 of the PDF reference
+ 1.7
+ */
+ pdf_drop_obj(pdf_read_xref(xref, xrefstmofs, buf));
+ }
+
+ prevofs = pdf_to_int(pdf_dict_gets(trailer, "Prev"));
+ if (prevofs < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "negative xref stream offset for previous xref stream");
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(trailer);
+ trailer = NULL;
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot read xref at offset %d", ofs);
+ }
+
+ return prevofs;
+}
+
+static void
+pdf_read_xref_sections(pdf_document *xref, int ofs, pdf_lexbuf *buf)
+{
+ fz_context *ctx = xref->ctx;
+ ofs_list list;
+
+ list.len = 0;
+ list.max = 10;
+ list.list = fz_malloc_array(ctx, 10, sizeof(int));
+ fz_try(ctx)
+ {
+ while(ofs)
+ {
+ pdf_populate_next_xref_level(xref);
+ ofs = read_xref_section(xref, ofs, buf, &list);
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free(ctx, list.list);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+/*
+ * load xref tables from pdf
+ *
+ * File locked on entry, throughout and on exit.
+ */
+
+static void
+pdf_load_xref(pdf_document *xref, pdf_lexbuf *buf)
+{
+ int i;
+ int xref_len;
+ fz_context *ctx = xref->ctx;
+
+ pdf_load_version(xref);
+
+ pdf_read_start_xref(xref);
+
+ pdf_read_xref_sections(xref, xref->startxref, buf);
+
+ /* broken pdfs where first object is not free */
+ if (pdf_get_xref_entry(xref, 0)->type != 'f')
+ fz_throw(ctx, FZ_ERROR_GENERIC, "first object in xref is not free");
+
+ /* broken pdfs where object offsets are out of range */
+ xref_len = pdf_xref_len(xref);
+ for (i = 0; i < xref_len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, i);
+ if (entry->type == 'n')
+ {
+ /* Special case code: "0000000000 * n" means free,
+ * according to some producers (inc Quartz) */
+ if (entry->ofs == 0)
+ entry->type = 'f';
+ else if (entry->ofs <= 0 || entry->ofs >= xref->file_size)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "object offset out of range: %d (%d 0 R)", entry->ofs, i);
+ }
+ if (entry->type == 'o')
+ if (entry->ofs <= 0 || entry->ofs >= xref_len || pdf_get_xref_entry(xref, entry->ofs)->type != 'n')
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid reference to an objstm that does not exist: %d (%d 0 R)", entry->ofs, i);
+ }
+}
+
+void
+pdf_ocg_set_config(pdf_document *xref, int config)
+{
+ int i, j, len, len2;
+ pdf_ocg_descriptor *desc = xref->ocg;
+ pdf_obj *obj, *cobj;
+ char *name;
+
+ obj = pdf_dict_gets(pdf_dict_gets(pdf_trailer(xref), "Root"), "OCProperties");
+ if (!obj)
+ {
+ if (config == 0)
+ return;
+ else
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "Unknown OCG config (None known!)");
+ }
+ if (config == 0)
+ {
+ cobj = pdf_dict_gets(obj, "D");
+ if (!cobj)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "No default OCG config");
+ }
+ else
+ {
+ cobj = pdf_array_get(pdf_dict_gets(obj, "Configs"), config);
+ if (!cobj)
+ fz_throw(xref->ctx, FZ_ERROR_GENERIC, "Illegal OCG config");
+ }
+
+ pdf_drop_obj(desc->intent);
+ desc->intent = pdf_dict_gets(cobj, "Intent");
+ if (desc->intent)
+ pdf_keep_obj(desc->intent);
+
+ len = desc->len;
+ name = pdf_to_name(pdf_dict_gets(cobj, "BaseState"));
+ if (strcmp(name, "Unchanged") == 0)
+ {
+ /* Do nothing */
+ }
+ else if (strcmp(name, "OFF") == 0)
+ {
+ for (i = 0; i < len; i++)
+ {
+ desc->ocgs[i].state = 0;
+ }
+ }
+ else /* Default to ON */
+ {
+ for (i = 0; i < len; i++)
+ {
+ desc->ocgs[i].state = 1;
+ }
+ }
+
+ obj = pdf_dict_gets(cobj, "ON");
+ len2 = pdf_array_len(obj);
+ for (i = 0; i < len2; i++)
+ {
+ pdf_obj *o = pdf_array_get(obj, i);
+ int n = pdf_to_num(o);
+ int g = pdf_to_gen(o);
+ for (j=0; j < len; j++)
+ {
+ if (desc->ocgs[j].num == n && desc->ocgs[j].gen == g)
+ {
+ desc->ocgs[j].state = 1;
+ break;
+ }
+ }
+ }
+
+ obj = pdf_dict_gets(cobj, "OFF");
+ len2 = pdf_array_len(obj);
+ for (i = 0; i < len2; i++)
+ {
+ pdf_obj *o = pdf_array_get(obj, i);
+ int n = pdf_to_num(o);
+ int g = pdf_to_gen(o);
+ for (j=0; j < len; j++)
+ {
+ if (desc->ocgs[j].num == n && desc->ocgs[j].gen == g)
+ {
+ desc->ocgs[j].state = 0;
+ break;
+ }
+ }
+ }
+
+ /* FIXME: Should make 'num configs' available in the descriptor. */
+ /* FIXME: Should copy out 'Intent' here into the descriptor, and remove
+ * csi->intent in favour of that. */
+ /* FIXME: Should copy 'AS' into the descriptor, and visibility
+ * decisions should respect it. */
+ /* FIXME: Make 'Order' available via the descriptor (when we have an
+ * app that needs it) */
+ /* FIXME: Make 'ListMode' available via the descriptor (when we have
+ * an app that needs it) */
+ /* FIXME: Make 'RBGroups' available via the descriptor (when we have
+ * an app that needs it) */
+ /* FIXME: Make 'Locked' available via the descriptor (when we have
+ * an app that needs it) */
+}
+
+static void
+pdf_read_ocg(pdf_document *xref)
+{
+ pdf_obj *obj, *ocg;
+ int len, i;
+ pdf_ocg_descriptor *desc;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(desc);
+
+ obj = pdf_dict_gets(pdf_dict_gets(pdf_trailer(xref), "Root"), "OCProperties");
+ if (!obj)
+ return;
+ ocg = pdf_dict_gets(obj, "OCGs");
+ if (!ocg || !pdf_is_array(ocg))
+ /* Not ever supposed to happen, but live with it. */
+ return;
+ len = pdf_array_len(ocg);
+ fz_try(ctx)
+ {
+ desc = fz_calloc(ctx, 1, sizeof(*desc));
+ desc->len = len;
+ desc->ocgs = fz_calloc(ctx, len, sizeof(*desc->ocgs));
+ desc->intent = NULL;
+ for (i=0; i < len; i++)
+ {
+ pdf_obj *o = pdf_array_get(ocg, i);
+ desc->ocgs[i].num = pdf_to_num(o);
+ desc->ocgs[i].gen = pdf_to_gen(o);
+ desc->ocgs[i].state = 0;
+ }
+ xref->ocg = desc;
+ }
+ fz_catch(ctx)
+ {
+ if (desc)
+ fz_free(ctx, desc->ocgs);
+ fz_free(ctx, desc);
+ fz_rethrow(ctx);
+ }
+
+ pdf_ocg_set_config(xref, 0);
+}
+
+static void
+pdf_free_ocg(fz_context *ctx, pdf_ocg_descriptor *desc)
+{
+ if (!desc)
+ return;
+
+ pdf_drop_obj(desc->intent);
+ fz_free(ctx, desc->ocgs);
+ fz_free(ctx, desc);
+}
+
+/*
+ * Initialize and load xref tables.
+ * If password is not null, try to decrypt.
+ */
+
+static void
+pdf_init_document(pdf_document *xref)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_obj *encrypt, *id;
+ pdf_obj *dict = NULL;
+ pdf_obj *obj;
+ pdf_obj *nobj = NULL;
+ int i, repaired = 0;
+
+ fz_var(dict);
+ fz_var(nobj);
+
+ fz_try(ctx)
+ {
+ pdf_load_xref(xref, &xref->lexbuf.base);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ pdf_free_xref_sections(xref);
+ fz_warn(xref->ctx, "trying to repair broken xref");
+ repaired = 1;
+ }
+
+ fz_try(ctx)
+ {
+ int hasroot, hasinfo;
+
+ if (repaired)
+ pdf_repair_xref(xref, &xref->lexbuf.base);
+
+ encrypt = pdf_dict_gets(pdf_trailer(xref), "Encrypt");
+ id = pdf_dict_gets(pdf_trailer(xref), "ID");
+ if (pdf_is_dict(encrypt))
+ xref->crypt = pdf_new_crypt(ctx, encrypt, id);
+
+ /* Allow lazy clients to read encrypted files with a blank password */
+ pdf_authenticate_password(xref, "");
+
+ if (repaired)
+ {
+ int xref_len = pdf_xref_len(xref);
+ pdf_repair_obj_stms(xref);
+
+ hasroot = (pdf_dict_gets(pdf_trailer(xref), "Root") != NULL);
+ hasinfo = (pdf_dict_gets(pdf_trailer(xref), "Info") != NULL);
+
+ for (i = 1; i < xref_len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, i);
+ if (entry->type == 0 || entry->type == 'f')
+ continue;
+
+ fz_try(ctx)
+ {
+ dict = pdf_load_object(xref, i, 0);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(ctx, "ignoring broken object (%d 0 R)", i);
+ continue;
+ }
+
+ if (!hasroot)
+ {
+ obj = pdf_dict_gets(dict, "Type");
+ if (pdf_is_name(obj) && !strcmp(pdf_to_name(obj), "Catalog"))
+ {
+ nobj = pdf_new_indirect(ctx, i, 0, xref);
+ pdf_dict_puts(pdf_trailer(xref), "Root", nobj);
+ pdf_drop_obj(nobj);
+ nobj = NULL;
+ }
+ }
+
+ if (!hasinfo)
+ {
+ if (pdf_dict_gets(dict, "Creator") || pdf_dict_gets(dict, "Producer"))
+ {
+ nobj = pdf_new_indirect(ctx, i, 0, xref);
+ pdf_dict_puts(pdf_trailer(xref), "Info", nobj);
+ pdf_drop_obj(nobj);
+ nobj = NULL;
+ }
+ }
+
+ pdf_drop_obj(dict);
+ dict = NULL;
+ }
+ }
+ xref->js = pdf_new_js(xref);
+ pdf_js_load_document_level(xref->js);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(dict);
+ pdf_drop_obj(nobj);
+ pdf_close_document(xref);
+ fz_rethrow_message(ctx, "cannot open document");
+ }
+
+ fz_try(ctx)
+ {
+ pdf_read_ocg(xref);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(ctx, "Ignoring Broken Optional Content");
+ }
+}
+
+void
+pdf_close_document(pdf_document *xref)
+{
+ int i;
+ fz_context *ctx;
+
+ if (!xref)
+ return;
+ ctx = xref->ctx;
+
+ pdf_drop_js(xref->js);
+
+ pdf_free_xref_sections(xref);
+
+ if (xref->page_objs)
+ {
+ for (i = 0; i < xref->page_len; i++)
+ pdf_drop_obj(xref->page_objs[i]);
+ fz_free(ctx, xref->page_objs);
+ }
+
+ if (xref->page_refs)
+ {
+ for (i = 0; i < xref->page_len; i++)
+ pdf_drop_obj(xref->page_refs[i]);
+ fz_free(ctx, xref->page_refs);
+ }
+
+ if (xref->focus_obj)
+ pdf_drop_obj(xref->focus_obj);
+ if (xref->file)
+ fz_close(xref->file);
+ if (xref->crypt)
+ pdf_free_crypt(ctx, xref->crypt);
+
+ pdf_free_ocg(ctx, xref->ocg);
+
+ fz_empty_store(ctx);
+
+ pdf_lexbuf_fin(&xref->lexbuf.base);
+
+ fz_free(ctx, xref);
+}
+
+void
+pdf_print_xref(pdf_document *xref)
+{
+ int i;
+ int xref_len = pdf_xref_len(xref);
+ printf("xref\n0 %d\n", pdf_xref_len(xref));
+ for (i = 0; i < xref_len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(xref, i);
+ printf("%05d: %010d %05d %c (stm_ofs=%d; stm_buf=%p)\n", i,
+ entry->ofs,
+ entry->gen,
+ entry->type ? entry->type : '-',
+ entry->stm_ofs,
+ entry->stm_buf);
+ }
+}
+
+/*
+ * compressed object streams
+ */
+
+static void
+pdf_load_obj_stm(pdf_document *xref, int num, int gen, pdf_lexbuf *buf)
+{
+ fz_stream *stm = NULL;
+ pdf_obj *objstm = NULL;
+ int *numbuf = NULL;
+ int *ofsbuf = NULL;
+
+ pdf_obj *obj;
+ int first;
+ int count;
+ int i;
+ pdf_token tok;
+ fz_context *ctx = xref->ctx;
+
+ fz_var(numbuf);
+ fz_var(ofsbuf);
+ fz_var(objstm);
+ fz_var(stm);
+
+ fz_try(ctx)
+ {
+ objstm = pdf_load_object(xref, num, gen);
+
+ count = pdf_to_int(pdf_dict_gets(objstm, "N"));
+ first = pdf_to_int(pdf_dict_gets(objstm, "First"));
+
+ if (count < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "negative number of objects in object stream");
+ if (first < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "first object in object stream resides outside stream");
+
+ numbuf = fz_calloc(ctx, count, sizeof(int));
+ ofsbuf = fz_calloc(ctx, count, sizeof(int));
+
+ stm = pdf_open_stream(xref, num, gen);
+ for (i = 0; i < count; i++)
+ {
+ tok = pdf_lex(stm, buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "corrupt object stream (%d %d R)", num, gen);
+ numbuf[i] = buf->i;
+
+ tok = pdf_lex(stm, buf);
+ if (tok != PDF_TOK_INT)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "corrupt object stream (%d %d R)", num, gen);
+ ofsbuf[i] = buf->i;
+ }
+
+ fz_seek(stm, first, SEEK_SET);
+
+ for (i = 0; i < count; i++)
+ {
+ int xref_len = pdf_xref_len(xref);
+ pdf_xref_entry *entry;
+ fz_seek(stm, first + ofsbuf[i], SEEK_SET);
+
+ obj = pdf_parse_stm_obj(xref, stm, buf);
+
+ if (numbuf[i] < 1 || numbuf[i] >= xref_len)
+ {
+ pdf_drop_obj(obj);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "object id (%d 0 R) out of range (0..%d)", numbuf[i], xref_len - 1);
+ }
+
+ entry = pdf_get_xref_entry(xref, numbuf[i]);
+
+ if (entry->type == 'o' && entry->ofs == num)
+ {
+ /* If we already have an entry for this object,
+ * we'd like to drop it and use the new one -
+ * but this means that anyone currently holding
+ * a pointer to the old one will be left with a
+ * stale pointer. Instead, we drop the new one
+ * and trust that the old one is correct. */
+ if (entry->obj) {
+ if (pdf_objcmp(entry->obj, obj))
+ fz_warn(ctx, "Encountered new definition for object %d - keeping the original one", numbuf[i]);
+ pdf_drop_obj(obj);
+ } else
+ entry->obj = obj;
+ }
+ else
+ {
+ pdf_drop_obj(obj);
+ }
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_close(stm);
+ fz_free(xref->ctx, ofsbuf);
+ fz_free(xref->ctx, numbuf);
+ pdf_drop_obj(objstm);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot open object stream (%d %d R)", num, gen);
+ }
+}
+
+/*
+ * object loading
+ */
+
+void
+pdf_cache_object(pdf_document *xref, int num, int gen)
+{
+ pdf_xref_entry *x;
+ int rnum, rgen;
+ fz_context *ctx = xref->ctx;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "object out of range (%d %d R); xref size %d", num, gen, pdf_xref_len(xref));
+
+ x = pdf_get_xref_entry(xref, num);
+
+ if (x->obj)
+ return;
+
+ if (x->type == 'f')
+ {
+ x->obj = pdf_new_null(ctx);
+ return;
+ }
+ else if (x->type == 'n')
+ {
+ fz_seek(xref->file, x->ofs, SEEK_SET);
+
+ fz_try(ctx)
+ {
+ x->obj = pdf_parse_ind_obj(xref, xref->file, &xref->lexbuf.base,
+ &rnum, &rgen, &x->stm_ofs);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot parse object (%d %d R)", num, gen);
+ }
+
+ if (rnum != num)
+ {
+ pdf_drop_obj(x->obj);
+ x->obj = NULL;
+ fz_rethrow_message(ctx, "found object (%d %d R) instead of (%d %d R)", rnum, rgen, num, gen);
+ }
+
+ if (xref->crypt)
+ pdf_crypt_obj(ctx, xref->crypt, x->obj, num, gen);
+ }
+ else if (x->type == 'o')
+ {
+ if (!x->obj)
+ {
+ fz_try(ctx)
+ {
+ pdf_load_obj_stm(xref, x->ofs, 0, &xref->lexbuf.base);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load object stream containing object (%d %d R)", num, gen);
+ }
+ if (!x->obj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "object (%d %d R) was not found in its object stream", num, gen);
+ }
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find object in xref (%d %d R)", num, gen);
+ }
+}
+
+pdf_obj *
+pdf_load_object(pdf_document *xref, int num, int gen)
+{
+ fz_context *ctx = xref->ctx;
+ pdf_xref_entry *entry;
+
+ fz_try(ctx)
+ {
+ pdf_cache_object(xref, num, gen);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load object (%d %d R) into cache", num, gen);
+ }
+
+ entry = pdf_get_xref_entry(xref, num);
+
+ assert(entry->obj);
+
+ return pdf_keep_obj(entry->obj);
+}
+
+pdf_obj *
+pdf_resolve_indirect(pdf_obj *ref)
+{
+ int sanity = 10;
+ int num;
+ int gen;
+ fz_context *ctx = NULL; /* Avoid warning for stupid compilers */
+ pdf_document *xref;
+ pdf_xref_entry *entry;
+
+ while (pdf_is_indirect(ref))
+ {
+ if (--sanity == 0)
+ {
+ fz_warn(ctx, "Too many indirections (possible indirection cycle involving %d %d R)", num, gen);
+ return NULL;
+ }
+ xref = pdf_get_indirect_document(ref);
+ if (!xref)
+ return NULL;
+ ctx = xref->ctx;
+ num = pdf_to_num(ref);
+ gen = pdf_to_gen(ref);
+ fz_try(ctx)
+ {
+ pdf_cache_object(xref, num, gen);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(ctx, "cannot load object (%d %d R) into cache", num, gen);
+ return NULL;
+ }
+ entry = pdf_get_xref_entry(xref, num);
+ if (!entry->obj)
+ return NULL;
+ ref = entry->obj;
+ }
+
+ return ref;
+}
+
+int
+pdf_count_objects(pdf_document *doc)
+{
+ return pdf_xref_len(doc);
+}
+
+int
+pdf_create_object(pdf_document *xref)
+{
+ /* TODO: reuse free object slots by properly linking free object chains in the ofs field */
+ pdf_xref_entry *entry;
+ int num = pdf_xref_len(xref);
+ entry = pdf_get_new_xref_entry(xref, num);
+ entry->type = 'f';
+ entry->ofs = -1;
+ entry->gen = 0;
+ entry->stm_ofs = 0;
+ entry->stm_buf = NULL;
+ entry->obj = NULL;
+ return num;
+}
+
+void
+pdf_delete_object(pdf_document *xref, int num)
+{
+ pdf_xref_entry *x;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ {
+ fz_warn(xref->ctx, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(xref));
+ return;
+ }
+
+ x = pdf_get_new_xref_entry(xref, num);
+
+ fz_drop_buffer(xref->ctx, x->stm_buf);
+ pdf_drop_obj(x->obj);
+
+ x->type = 'f';
+ x->ofs = 0;
+ x->gen = 0;
+ x->stm_ofs = 0;
+ x->stm_buf = NULL;
+ x->obj = NULL;
+}
+
+void
+pdf_update_object(pdf_document *xref, int num, pdf_obj *newobj)
+{
+ pdf_xref_entry *x;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ {
+ fz_warn(xref->ctx, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(xref));
+ return;
+ }
+
+ x = pdf_get_new_xref_entry(xref, num);
+
+ pdf_drop_obj(x->obj);
+
+ x->type = 'n';
+ x->ofs = 0;
+ x->obj = pdf_keep_obj(newobj);
+}
+
+void
+pdf_update_stream(pdf_document *xref, int num, fz_buffer *newbuf)
+{
+ pdf_xref_entry *x;
+
+ if (num < 0 || num >= pdf_xref_len(xref))
+ {
+ fz_warn(xref->ctx, "object out of range (%d 0 R); xref size %d", num, pdf_xref_len(xref));
+ return;
+ }
+
+ x = pdf_get_xref_entry(xref, num);
+
+ fz_drop_buffer(xref->ctx, x->stm_buf);
+ x->stm_buf = fz_keep_buffer(xref->ctx, newbuf);
+}
+
+int
+pdf_meta(pdf_document *doc, int key, void *ptr, int size)
+{
+ switch (key)
+ {
+ /*
+ ptr: Pointer to block (uninitialised on entry)
+ size: Size of block (at least 64 bytes)
+ Returns: Document format as a brief text string.
+ */
+ case FZ_META_FORMAT_INFO:
+ sprintf((char *)ptr, "PDF %d.%d", doc->version/10, doc->version % 10);
+ return FZ_META_OK;
+ case FZ_META_CRYPT_INFO:
+ if (doc->crypt)
+ sprintf((char *)ptr, "Standard V%d R%d %d-bit %s",
+ pdf_crypt_version(doc),
+ pdf_crypt_revision(doc),
+ pdf_crypt_length(doc),
+ pdf_crypt_method(doc));
+ else
+ sprintf((char *)ptr, "None");
+ return FZ_META_OK;
+ case FZ_META_HAS_PERMISSION:
+ {
+ int i;
+ switch (size)
+ {
+ case FZ_PERMISSION_PRINT:
+ i = PDF_PERM_PRINT;
+ break;
+ case FZ_PERMISSION_CHANGE:
+ i = PDF_PERM_CHANGE;
+ break;
+ case FZ_PERMISSION_COPY:
+ i = PDF_PERM_COPY;
+ break;
+ case FZ_PERMISSION_NOTES:
+ i = PDF_PERM_NOTES;
+ break;
+ default:
+ return 0;
+ }
+ return pdf_has_permission(doc, i);
+ }
+ case FZ_META_INFO:
+ {
+ pdf_obj *info = pdf_dict_gets(pdf_trailer(doc), "Info");
+ if (!info)
+ {
+ if (ptr)
+ *(char *)ptr = 0;
+ return 0;
+ }
+ info = pdf_dict_gets(info, *(char **)ptr);
+ if (!info)
+ {
+ if (ptr)
+ *(char *)ptr = 0;
+ return 0;
+ }
+ if (info && ptr && size)
+ {
+ char *utf8 = pdf_to_utf8(doc, info);
+ fz_strlcpy(ptr, utf8, size);
+ fz_free(doc->ctx, utf8);
+ }
+ return 1;
+ }
+ default:
+ return FZ_META_UNKNOWN_KEY;
+ }
+}
+
+fz_transition *
+pdf_page_presentation(pdf_document *doc, pdf_page *page, float *duration)
+{
+ *duration = page->duration;
+ if (!page->transition_present)
+ return NULL;
+ return &page->transition;
+}
+
+/*
+ Initializers for the fz_document interface.
+
+ The functions are split across two files to allow calls to a
+ version of the constructor that does not link in the interpreter.
+ The interpreter references the built-in font and cmap resources
+ which are quite big. Not linking those into the mubusy binary
+ saves roughly 6MB of space.
+*/
+
+static pdf_document *
+pdf_new_document(fz_context *ctx, fz_stream *file)
+{
+ pdf_document *doc = fz_malloc_struct(ctx, pdf_document);
+
+ doc->super.close = (void*)pdf_close_document;
+ doc->super.needs_password = (void*)pdf_needs_password;
+ doc->super.authenticate_password = (void*)pdf_authenticate_password;
+ doc->super.load_outline = (void*)pdf_load_outline;
+ doc->super.count_pages = (void*)pdf_count_pages;
+ doc->super.load_page = (void*)pdf_load_page;
+ doc->super.load_links = (void*)pdf_load_links;
+ doc->super.bound_page = (void*)pdf_bound_page;
+ doc->super.first_annot = (void*)pdf_first_annot;
+ doc->super.next_annot = (void*)pdf_next_annot;
+ doc->super.bound_annot = (void*)pdf_bound_annot;
+ doc->super.run_page_contents = NULL; /* see pdf_xref_aux.c */
+ doc->super.run_annot = NULL; /* see pdf_xref_aux.c */
+ doc->super.free_page = (void*)pdf_free_page;
+ doc->super.meta = (void*)pdf_meta;
+ doc->super.page_presentation = (void*)pdf_page_presentation;
+ doc->super.write = (void*)pdf_write_document;
+
+ pdf_lexbuf_init(ctx, &doc->lexbuf.base, PDF_LEXBUF_LARGE);
+ doc->file = fz_keep_stream(file);
+ doc->ctx = ctx;
+
+ return doc;
+}
+
+pdf_document *
+pdf_open_document_no_run_with_stream(fz_context *ctx, fz_stream *file)
+{
+ pdf_document *doc = pdf_new_document(ctx, file);
+ pdf_init_document(doc);
+ return doc;
+}
+
+pdf_document *
+pdf_open_document_no_run(fz_context *ctx, const char *filename)
+{
+ fz_stream *file = NULL;
+ pdf_document *doc;
+
+ fz_var(file);
+
+ fz_try(ctx)
+ {
+ file = fz_open_file(ctx, filename);
+ doc = pdf_new_document(ctx, file);
+ pdf_init_document(doc);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load document '%s'", filename);
+ }
+ return doc;
+}
+
+pdf_document *pdf_specifics(fz_document *doc)
+{
+ return (pdf_document *)(doc->close == (void *)pdf_close_document ? doc : NULL);
+}
diff --git a/source/tools/mudraw.c b/source/tools/mudraw.c
new file mode 100644
index 00000000..57bd4be2
--- /dev/null
+++ b/source/tools/mudraw.c
@@ -0,0 +1,1090 @@
+/*
+ * mudraw -- command line tool for drawing pdf/xps/cbz documents
+ */
+
+#include "mupdf/fitz.h"
+#include "mupdf/pdf.h" /* for mujstest */
+
+#ifdef _MSC_VER
+#include <winsock2.h>
+#define main main_utf8
+#else
+#include <sys/time.h>
+#endif
+
+enum { TEXT_PLAIN = 1, TEXT_HTML = 2, TEXT_XML = 3 };
+
+enum { OUT_PNG, OUT_PPM, OUT_PNM, OUT_PAM, OUT_PGM, OUT_PBM, OUT_SVG, OUT_PWG, OUT_PCL };
+
+enum { CS_INVALID, CS_UNSET, CS_MONO, CS_GRAY, CS_GRAYALPHA, CS_RGB, CS_RGBA };
+
+typedef struct
+{
+ char *suffix;
+ int format;
+} suffix_t;
+
+static const suffix_t suffix_table[] =
+{
+ { ".png", OUT_PNG },
+ { ".pgm", OUT_PGM },
+ { ".ppm", OUT_PPM },
+ { ".pnm", OUT_PNM },
+ { ".pam", OUT_PAM },
+ { ".pbm", OUT_PBM },
+ { ".svg", OUT_SVG },
+ { ".pwg", OUT_PWG },
+ { ".pcl", OUT_PCL }
+};
+
+typedef struct
+{
+ char *name;
+ int colorspace;
+} cs_name_t;
+
+static const cs_name_t cs_name_table[] =
+{
+ { "m", CS_MONO },
+ { "mono", CS_MONO },
+ { "g", CS_GRAY },
+ { "gray", CS_GRAY },
+ { "grey", CS_GRAY },
+ { "ga", CS_GRAYALPHA },
+ { "grayalpha", CS_GRAYALPHA },
+ { "greyalpha", CS_GRAYALPHA },
+ { "rgb", CS_RGB },
+ { "rgba", CS_RGBA },
+ { "rgbalpha", CS_RGBA }
+};
+
+typedef struct
+{
+ int format;
+ int default_cs;
+ int permitted_cs[6];
+} format_cs_table_t;
+
+static const format_cs_table_t format_cs_table[] =
+{
+ { OUT_PNG, CS_RGB, { CS_GRAY, CS_GRAYALPHA, CS_RGB, CS_RGBA } },
+ { OUT_PPM, CS_RGB, { CS_GRAY, CS_RGB } },
+ { OUT_PNM, CS_GRAY, { CS_GRAY, CS_RGB } },
+ { OUT_PAM, CS_RGBA, { CS_RGBA } },
+ { OUT_PGM, CS_GRAY, { CS_GRAY, CS_RGB } },
+ { OUT_PBM, CS_MONO, { CS_MONO } },
+ { OUT_SVG, CS_RGB, { CS_RGB } },
+ { OUT_PWG, CS_RGB, { CS_MONO, CS_GRAY, CS_RGB } },
+ { OUT_PCL, CS_MONO, { CS_MONO } }
+};
+
+/*
+ A useful bit of bash script to call this to generate mjs files:
+ for f in tests_private/pdf/forms/v1.3/ *.pdf ; do g=${f%.*} ; echo $g ; ../mupdf.git/win32/debug/mudraw.exe -j $g.mjs $g.pdf ; done
+
+ Remove the space from "/ *.pdf" before running - can't leave that
+ in here, as it causes a warning about a possibly malformed comment.
+*/
+
+static char lorem[] =
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum "
+"vehicula augue id est lobortis mollis. Aenean vestibulum metus sed est "
+"gravida non tempus lacus aliquet. Nulla vehicula lobortis tincidunt. "
+"Donec malesuada nisl et lacus condimentum nec tincidunt urna gravida. "
+"Sed dapibus magna eu velit ultrices non rhoncus risus lacinia. Fusce "
+"vitae nulla volutpat elit dictum ornare at eu libero. Maecenas felis "
+"enim, tempor a tincidunt id, commodo consequat lectus.\n"
+"Morbi tincidunt adipiscing lacus eu dignissim. Pellentesque augue elit, "
+"ultrices vitae fermentum et, faucibus et purus. Nam ante libero, lacinia "
+"id tincidunt at, ultricies a lorem. Donec non neque at purus condimentum "
+"eleifend quis sit amet libero. Sed semper, mi ut tempus tincidunt, lacus "
+"eros pellentesque lacus, id vehicula est diam eu quam. Integer tristique "
+"fringilla rhoncus. Phasellus convallis, justo ut mollis viverra, dui odio "
+"euismod ante, nec fringilla nisl mi ac diam.\n"
+"Maecenas mi urna, ornare commodo feugiat id, cursus in massa. Vivamus "
+"augue augue, aliquam at varius eu, venenatis fermentum felis. Sed varius "
+"turpis a felis ultrices quis aliquet nunc tincidunt. Suspendisse posuere "
+"commodo nunc non viverra. Praesent condimentum varius quam, vel "
+"consectetur odio volutpat in. Sed malesuada augue ut lectus commodo porta. "
+"Vivamus eget mauris sit amet diam ultrices sollicitudin. Cras pharetra leo "
+"non elit lacinia vulputate.\n"
+"Donec ac enim justo, ornare scelerisque diam. Ut vel ante at lorem "
+"placerat bibendum ultricies mattis metus. Phasellus in imperdiet odio. "
+"Proin semper lacinia libero, sed rutrum eros blandit non. Duis tincidunt "
+"ligula est, non pellentesque mauris. Aliquam in erat scelerisque lacus "
+"dictum suscipit eget semper magna. Nullam luctus imperdiet risus a "
+"semper.\n"
+"Curabitur sit amet tempor sapien. Quisque et tortor in lacus dictum "
+"pulvinar. Nunc at nisl ut velit vehicula hendrerit. Mauris elementum "
+"sollicitudin leo ac ullamcorper. Proin vel leo nec justo tempus aliquet "
+"nec ut mi. Pellentesque vel nisl id dui hendrerit fermentum nec quis "
+"tortor. Proin eu sem luctus est consequat euismod. Vestibulum ante ipsum "
+"primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce "
+"consectetur ultricies nisl ornare dictum. Cras sagittis consectetur lorem "
+"sed posuere. Mauris accumsan laoreet arcu, id molestie lorem faucibus eu. "
+"Vivamus commodo, neque nec imperdiet pretium, lorem metus viverra turpis, "
+"malesuada vulputate justo eros sit amet neque. Nunc quis justo elit, non "
+"rutrum mauris. Maecenas blandit condimentum nibh, nec vulputate orci "
+"pulvinar at. Proin sed arcu vel odio tempus lobortis sed posuere ipsum. Ut "
+"feugiat pellentesque tortor nec ornare.\n";
+
+static char *output = NULL;
+static float resolution = 72;
+static int res_specified = 0;
+static float rotation = 0;
+
+static int showxml = 0;
+static int showtext = 0;
+static int showtime = 0;
+static int showmd5 = 0;
+static int showoutline = 0;
+static int uselist = 1;
+static int alphabits = 8;
+static float gamma_value = 1;
+static int invert = 0;
+static int width = 0;
+static int height = 0;
+static int fit = 0;
+static int errored = 0;
+static int ignore_errors = 0;
+static int output_format;
+static int append = 0;
+static int out_cs = CS_UNSET;
+
+static fz_text_sheet *sheet = NULL;
+static fz_colorspace *colorspace;
+static char *filename;
+static int files = 0;
+fz_output *out = NULL;
+
+static char *mujstest_filename = NULL;
+static FILE *mujstest_file = NULL;
+static int mujstest_count = 0;
+
+static struct {
+ int count, total;
+ int min, max;
+ int minpage, maxpage;
+ char *minfilename;
+ char *maxfilename;
+} timing;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "usage: mudraw [options] input [pages]\n"
+ "\t-o -\toutput filename (%%d for page number)\n"
+ "\t\tsupported formats: pgm, ppm, pam, png, pbm\n"
+ "\t-p -\tpassword\n"
+ "\t-r -\tresolution in dpi (default: 72)\n"
+ "\t-w -\twidth (in pixels) (maximum width if -r is specified)\n"
+ "\t-h -\theight (in pixels) (maximum height if -r is specified)\n"
+ "\t-f -\tfit width and/or height exactly (ignore aspect)\n"
+ "\t-c -\tcolorspace {mono,gray,grayalpha,rgb,rgba}\n"
+ "\t-b -\tnumber of bits of antialiasing (0 to 8)\n"
+ "\t-g\trender in grayscale\n"
+ "\t-m\tshow timing information\n"
+ "\t-t\tshow text (-tt for xml, -ttt for more verbose xml)\n"
+ "\t-x\tshow display list\n"
+ "\t-d\tdisable use of display list\n"
+ "\t-5\tshow md5 checksums\n"
+ "\t-R -\trotate clockwise by given number of degrees\n"
+ "\t-G -\tgamma correct output\n"
+ "\t-I\tinvert output\n"
+ "\t-l\tprint outline\n"
+ "\t-j -\tOutput mujstest file\n"
+ "\t-i\tignore errors and continue with the next file\n"
+ "\tpages\tcomma separated list of ranges\n");
+ exit(1);
+}
+
+static int gettime(void)
+{
+ static struct timeval first;
+ static int once = 1;
+ struct timeval now;
+ if (once)
+ {
+ gettimeofday(&first, NULL);
+ once = 0;
+ }
+ gettimeofday(&now, NULL);
+ return (now.tv_sec - first.tv_sec) * 1000 + (now.tv_usec - first.tv_usec) / 1000;
+}
+
+static int isrange(char *s)
+{
+ while (*s)
+ {
+ if ((*s < '0' || *s > '9') && *s != '-' && *s != ',')
+ return 0;
+ s++;
+ }
+ return 1;
+}
+
+static void escape_string(FILE *out, int len, const char *string)
+{
+ while (len-- && *string)
+ {
+ char c = *string++;
+ switch (c)
+ {
+ case '\n':
+ fputc('\\', out);
+ fputc('n', out);
+ break;
+ case '\r':
+ fputc('\\', out);
+ fputc('r', out);
+ break;
+ case '\t':
+ fputc('\\', out);
+ fputc('t', out);
+ break;
+ default:
+ fputc(c, out);
+ }
+ }
+}
+
+static void drawpage(fz_context *ctx, fz_document *doc, int pagenum)
+{
+ fz_page *page;
+ fz_display_list *list = NULL;
+ fz_device *dev = NULL;
+ int start;
+ fz_cookie cookie = { 0 };
+ int needshot = 0;
+
+ fz_var(list);
+ fz_var(dev);
+
+ if (showtime)
+ {
+ start = gettime();
+ }
+
+ fz_try(ctx)
+ {
+ page = fz_load_page(doc, pagenum - 1);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load page %d in file '%s'", pagenum, filename);
+ }
+
+ if (mujstest_file)
+ {
+ pdf_document *inter = pdf_specifics(doc);
+ pdf_widget *widget = NULL;
+
+ if (inter)
+ widget = pdf_first_widget(inter, (pdf_page *)page);
+
+ if (widget)
+ {
+ fprintf(mujstest_file, "GOTO %d\n", pagenum);
+ needshot = 1;
+ }
+ for (;widget; widget = pdf_next_widget(widget))
+ {
+ fz_rect rect;
+ int w, h, len;
+ int type = pdf_widget_get_type(widget);
+
+ pdf_bound_widget(widget, &rect);
+ w = (rect.x1 - rect.x0);
+ h = (rect.y1 - rect.y0);
+ ++mujstest_count;
+ switch (type)
+ {
+ default:
+ fprintf(mujstest_file, "%% UNKNOWN %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ break;
+ case PDF_WIDGET_TYPE_PUSHBUTTON:
+ fprintf(mujstest_file, "%% PUSHBUTTON %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ break;
+ case PDF_WIDGET_TYPE_CHECKBOX:
+ fprintf(mujstest_file, "%% CHECKBOX %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ break;
+ case PDF_WIDGET_TYPE_RADIOBUTTON:
+ fprintf(mujstest_file, "%% RADIOBUTTON %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ break;
+ case PDF_WIDGET_TYPE_TEXT:
+ {
+ int maxlen = pdf_text_widget_max_len(inter, widget);
+ int texttype = pdf_text_widget_content_type(inter, widget);
+
+ /* If height is low, assume a single row, and base
+ * the width off that. */
+ if (h < 10)
+ {
+ w = (w+h-1) / (h ? h : 1);
+ h = 1;
+ }
+ /* Otherwise, if width is low, work off height */
+ else if (w < 10)
+ {
+ h = (w+h-1) / (w ? w : 1);
+ w = 1;
+ }
+ else
+ {
+ w = (w+9)/10;
+ h = (h+9)/10;
+ }
+ len = w*h;
+ if (len < 2)
+ len = 2;
+ if (len > maxlen)
+ len = maxlen;
+ fprintf(mujstest_file, "%% TEXT %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ switch (texttype)
+ {
+ default:
+ case PDF_WIDGET_CONTENT_UNRESTRAINED:
+ fprintf(mujstest_file, "TEXT %d ", mujstest_count);
+ escape_string(mujstest_file, len-3, lorem);
+ fprintf(mujstest_file, "\n");
+ break;
+ case PDF_WIDGET_CONTENT_NUMBER:
+ fprintf(mujstest_file, "TEXT %d\n", mujstest_count);
+ break;
+ case PDF_WIDGET_CONTENT_SPECIAL:
+#ifdef __MINGW32__
+ fprintf(mujstest_file, "TEXT %I64d\n", 46702919800LL + mujstest_count);
+#else
+ fprintf(mujstest_file, "TEXT %lld\n", 46702919800LL + mujstest_count);
+#endif
+ break;
+ case PDF_WIDGET_CONTENT_DATE:
+ fprintf(mujstest_file, "TEXT Jun %d 1979\n", 1 + ((13 + mujstest_count) % 30));
+ break;
+ case PDF_WIDGET_CONTENT_TIME:
+ ++mujstest_count;
+ fprintf(mujstest_file, "TEXT %02d:%02d\n", ((mujstest_count/60) % 24), mujstest_count % 60);
+ break;
+ }
+ break;
+ }
+ case PDF_WIDGET_TYPE_LISTBOX:
+ fprintf(mujstest_file, "%% LISTBOX %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ break;
+ case PDF_WIDGET_TYPE_COMBOBOX:
+ fprintf(mujstest_file, "%% COMBOBOX %0.2f %0.2f %0.2f %0.2f\n", rect.x0, rect.y0, rect.x1, rect.y1);
+ break;
+ }
+ fprintf(mujstest_file, "CLICK %0.2f %0.2f\n", (rect.x0+rect.x1)/2, (rect.y0+rect.y1)/2);
+ }
+ }
+
+ if (uselist)
+ {
+ fz_try(ctx)
+ {
+ list = fz_new_display_list(ctx);
+ dev = fz_new_list_device(ctx, list);
+ fz_run_page(doc, page, dev, &fz_identity, &cookie);
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ dev = NULL;
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_display_list(ctx, list);
+ fz_free_page(doc, page);
+ fz_rethrow_message(ctx, "cannot draw page %d in file '%s'", pagenum, filename);
+ }
+ }
+
+ if (showxml)
+ {
+ fz_try(ctx)
+ {
+ dev = fz_new_trace_device(ctx);
+ if (list)
+ fz_run_display_list(list, dev, &fz_identity, &fz_infinite_rect, &cookie);
+ else
+ fz_run_page(doc, page, dev, &fz_identity, &cookie);
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ dev = NULL;
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_display_list(ctx, list);
+ fz_free_page(doc, page);
+ fz_rethrow(ctx);
+ }
+ }
+
+ if (showtext)
+ {
+ fz_text_page *text = NULL;
+
+ fz_var(text);
+
+ fz_try(ctx)
+ {
+ text = fz_new_text_page(ctx);
+ dev = fz_new_text_device(ctx, sheet, text);
+ if (showtext == TEXT_HTML)
+ fz_disable_device_hints(dev, FZ_IGNORE_IMAGE);
+ if (list)
+ fz_run_display_list(list, dev, &fz_identity, &fz_infinite_rect, &cookie);
+ else
+ fz_run_page(doc, page, dev, &fz_identity, &cookie);
+ fz_free_device(dev);
+ dev = NULL;
+ if (showtext == TEXT_XML)
+ {
+ fz_print_text_page_xml(ctx, out, text);
+ }
+ else if (showtext == TEXT_HTML)
+ {
+ fz_analyze_text(ctx, sheet, text);
+ fz_print_text_page_html(ctx, out, text);
+ }
+ else if (showtext == TEXT_PLAIN)
+ {
+ fz_print_text_page(ctx, out, text);
+ fz_printf(out, "\f\n");
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ dev = NULL;
+ fz_free_text_page(ctx, text);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_display_list(ctx, list);
+ fz_free_page(doc, page);
+ fz_rethrow(ctx);
+ }
+ }
+
+ if (showmd5 || showtime)
+ printf("page %s %d", filename, pagenum);
+
+ if (output && output_format == OUT_SVG)
+ {
+ float zoom;
+ fz_matrix ctm;
+ fz_rect bounds, tbounds;
+ char buf[512];
+ FILE *file;
+ fz_output *out;
+
+ sprintf(buf, output, pagenum);
+ file = fopen(buf, "wb");
+ if (file == NULL)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", buf, strerror(errno));
+ out = fz_new_output_with_file(ctx, file);
+
+ fz_bound_page(doc, page, &bounds);
+ zoom = resolution / 72;
+ fz_pre_rotate(fz_scale(&ctm, zoom, zoom), rotation);
+ tbounds = bounds;
+ fz_transform_rect(&tbounds, &ctm);
+
+ fz_try(ctx)
+ {
+ dev = fz_new_svg_device(ctx, out, tbounds.x1-tbounds.x0, tbounds.y1-tbounds.y0);
+ if (list)
+ fz_run_display_list(list, dev, &ctm, &tbounds, &cookie);
+ else
+ fz_run_page(doc, page, dev, &ctm, &cookie);
+ fz_free_device(dev);
+ dev = NULL;
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ dev = NULL;
+ fz_close_output(out);
+ fclose(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_display_list(ctx, list);
+ fz_free_page(doc, page);
+ fz_rethrow(ctx);
+ }
+ }
+
+ if ((output && output_format != OUT_SVG)|| showmd5 || showtime)
+ {
+ float zoom;
+ fz_matrix ctm;
+ fz_rect bounds, tbounds;
+ fz_irect ibounds;
+ fz_pixmap *pix = NULL;
+ int w, h;
+
+ fz_var(pix);
+
+ fz_bound_page(doc, page, &bounds);
+ zoom = resolution / 72;
+ fz_pre_scale(fz_rotate(&ctm, rotation), zoom, zoom);
+ tbounds = bounds;
+ fz_round_rect(&ibounds, fz_transform_rect(&tbounds, &ctm));
+
+ /* Make local copies of our width/height */
+ w = width;
+ h = height;
+
+ /* If a resolution is specified, check to see whether w/h are
+ * exceeded; if not, unset them. */
+ if (res_specified)
+ {
+ int t;
+ t = ibounds.x1 - ibounds.x0;
+ if (w && t <= w)
+ w = 0;
+ t = ibounds.y1 - ibounds.y0;
+ if (h && t <= h)
+ h = 0;
+ }
+
+ /* Now w or h will be 0 unless they need to be enforced. */
+ if (w || h)
+ {
+ float scalex = w / (tbounds.x1 - tbounds.x0);
+ float scaley = h / (tbounds.y1 - tbounds.y0);
+ fz_matrix scale_mat;
+
+ if (fit)
+ {
+ if (w == 0)
+ scalex = 1.0f;
+ if (h == 0)
+ scaley = 1.0f;
+ }
+ else
+ {
+ if (w == 0)
+ scalex = scaley;
+ if (h == 0)
+ scaley = scalex;
+ }
+ if (!fit)
+ {
+ if (scalex > scaley)
+ scalex = scaley;
+ else
+ scaley = scalex;
+ }
+ fz_scale(&scale_mat, scalex, scaley);
+ fz_concat(&ctm, &ctm, &scale_mat);
+ tbounds = bounds;
+ fz_transform_rect(&tbounds, &ctm);
+ }
+ fz_round_rect(&ibounds, &tbounds);
+ fz_rect_from_irect(&tbounds, &ibounds);
+
+ /* TODO: banded rendering and multi-page ppm */
+
+ fz_try(ctx)
+ {
+ int savealpha = (out_cs == CS_RGBA || out_cs == CS_GRAYALPHA);
+
+ pix = fz_new_pixmap_with_bbox(ctx, colorspace, &ibounds);
+ fz_pixmap_set_resolution(pix, resolution);
+
+ if (savealpha)
+ fz_clear_pixmap(ctx, pix);
+ else
+ fz_clear_pixmap_with_value(ctx, pix, 255);
+
+ dev = fz_new_draw_device(ctx, pix);
+ if (list)
+ fz_run_display_list(list, dev, &ctm, &tbounds, &cookie);
+ else
+ fz_run_page(doc, page, dev, &ctm, &cookie);
+ fz_free_device(dev);
+ dev = NULL;
+
+ if (invert)
+ fz_invert_pixmap(ctx, pix);
+ if (gamma_value != 1)
+ fz_gamma_pixmap(ctx, pix, gamma_value);
+
+ if (savealpha)
+ fz_unmultiply_pixmap(ctx, pix);
+
+ if (output)
+ {
+ char buf[512];
+ sprintf(buf, output, pagenum);
+ if (output_format == OUT_PGM || output_format == OUT_PPM || output_format == OUT_PNM)
+ fz_write_pnm(ctx, pix, buf);
+ else if (output_format == OUT_PAM)
+ fz_write_pam(ctx, pix, buf, savealpha);
+ else if (output_format == OUT_PNG)
+ fz_write_png(ctx, pix, buf, savealpha);
+ else if (output_format == OUT_PWG)
+ {
+ if (strstr(output, "%d") != NULL)
+ append = 0;
+ if (out_cs == CS_MONO)
+ {
+ fz_bitmap *bit = fz_halftone_pixmap(ctx, pix, NULL);
+ fz_write_pwg_bitmap(ctx, bit, buf, append, NULL);
+ fz_drop_bitmap(ctx, bit);
+ }
+ else
+ fz_write_pwg(ctx, pix, buf, append, NULL);
+ append = 1;
+ }
+ else if (output_format == OUT_PCL)
+ {
+ fz_pcl_options options;
+
+ fz_pcl_preset(ctx, &options, "ljet4");
+
+ if (strstr(output, "%d") != NULL)
+ append = 0;
+ if (out_cs == CS_MONO)
+ {
+ fz_bitmap *bit = fz_halftone_pixmap(ctx, pix, NULL);
+ fz_write_pcl_bitmap(ctx, bit, buf, append, &options);
+ fz_drop_bitmap(ctx, bit);
+ }
+ else
+ fz_write_pcl(ctx, pix, buf, append, &options);
+ append = 1;
+ }
+ else if (output_format == OUT_PBM) {
+ fz_bitmap *bit = fz_halftone_pixmap(ctx, pix, NULL);
+ fz_write_pbm(ctx, bit, buf);
+ fz_drop_bitmap(ctx, bit);
+ }
+ }
+
+ if (showmd5)
+ {
+ unsigned char digest[16];
+ int i;
+
+ fz_md5_pixmap(pix, digest);
+ printf(" ");
+ for (i = 0; i < 16; i++)
+ printf("%02x", digest[i]);
+ }
+ }
+ fz_always(ctx)
+ {
+ fz_free_device(dev);
+ dev = NULL;
+ fz_drop_pixmap(ctx, pix);
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_display_list(ctx, list);
+ fz_free_page(doc, page);
+ fz_rethrow(ctx);
+ }
+ }
+
+ if (list)
+ fz_drop_display_list(ctx, list);
+
+ fz_free_page(doc, page);
+
+ if (showtime)
+ {
+ int end = gettime();
+ int diff = end - start;
+
+ if (diff < timing.min)
+ {
+ timing.min = diff;
+ timing.minpage = pagenum;
+ timing.minfilename = filename;
+ }
+ if (diff > timing.max)
+ {
+ timing.max = diff;
+ timing.maxpage = pagenum;
+ timing.maxfilename = filename;
+ }
+ timing.total += diff;
+ timing.count ++;
+
+ printf(" %dms", diff);
+ }
+
+ if (showmd5 || showtime)
+ printf("\n");
+
+ fz_flush_warnings(ctx);
+
+ if (mujstest_file && needshot)
+ {
+ fprintf(mujstest_file, "SCREENSHOT\n");
+ }
+
+ if (cookie.errors)
+ errored = 1;
+}
+
+static void drawrange(fz_context *ctx, fz_document *doc, char *range)
+{
+ int page, spage, epage, pagecount;
+ char *spec, *dash;
+
+ pagecount = fz_count_pages(doc);
+ spec = fz_strsep(&range, ",");
+ while (spec)
+ {
+ dash = strchr(spec, '-');
+
+ if (dash == spec)
+ spage = epage = pagecount;
+ else
+ spage = epage = atoi(spec);
+
+ if (dash)
+ {
+ if (strlen(dash) > 1)
+ epage = atoi(dash + 1);
+ else
+ epage = pagecount;
+ }
+
+ spage = fz_clampi(spage, 1, pagecount);
+ epage = fz_clampi(epage, 1, pagecount);
+
+ if (spage < epage)
+ for (page = spage; page <= epage; page++)
+ drawpage(ctx, doc, page);
+ else
+ for (page = spage; page >= epage; page--)
+ drawpage(ctx, doc, page);
+
+ spec = fz_strsep(&range, ",");
+ }
+}
+
+static void drawoutline(fz_context *ctx, fz_document *doc)
+{
+ fz_outline *outline = fz_load_outline(doc);
+ fz_output *out = NULL;
+
+ fz_var(out);
+ fz_try(ctx)
+ {
+ out = fz_new_output_with_file(ctx, stdout);
+ if (showoutline > 1)
+ fz_print_outline_xml(ctx, out, outline);
+ else
+ fz_print_outline(ctx, out, outline);
+ }
+ fz_always(ctx)
+ {
+ fz_close_output(out);
+ fz_free_outline(ctx, outline);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+static int
+parse_colorspace(const char *name)
+{
+ int i;
+
+ for (i = 0; i < nelem(cs_name_table); i++)
+ {
+ if (!strcmp(name, cs_name_table[i].name))
+ return cs_name_table[i].colorspace;
+ }
+ fprintf(stderr, "Unknown colorspace \"%s\"\n", name);
+ exit(1);
+ return -1;
+}
+
+int main(int argc, char **argv)
+{
+ char *password = "";
+ fz_document *doc = NULL;
+ int c;
+ fz_context *ctx;
+
+ fz_var(doc);
+
+ while ((c = fz_getopt(argc, argv, "lo:p:r:R:b:c:dgmtx5G:Iw:h:fij:")) != -1)
+ {
+ switch (c)
+ {
+ case 'o': output = fz_optarg; break;
+ case 'p': password = fz_optarg; break;
+ case 'r': resolution = atof(fz_optarg); res_specified = 1; break;
+ case 'R': rotation = atof(fz_optarg); break;
+ case 'b': alphabits = atoi(fz_optarg); break;
+ case 'l': showoutline++; break;
+ case 'm': showtime++; break;
+ case 't': showtext++; break;
+ case 'x': showxml++; break;
+ case '5': showmd5++; break;
+ case 'g': out_cs = CS_GRAY; break;
+ case 'd': uselist = 0; break;
+ case 'c': out_cs = parse_colorspace(fz_optarg); break;
+ case 'G': gamma_value = atof(fz_optarg); break;
+ case 'w': width = atof(fz_optarg); break;
+ case 'h': height = atof(fz_optarg); break;
+ case 'f': fit = 1; break;
+ case 'I': invert++; break;
+ case 'j': mujstest_filename = fz_optarg; break;
+ case 'i': ignore_errors = 1; break;
+ default: usage(); break;
+ }
+ }
+
+ if (fz_optind == argc)
+ usage();
+
+ if (!showtext && !showxml && !showtime && !showmd5 && !showoutline && !output && !mujstest_filename)
+ {
+ printf("nothing to do\n");
+ exit(0);
+ }
+
+ if (mujstest_filename)
+ {
+ if (strcmp(mujstest_filename, "-") == 0)
+ mujstest_file = stdout;
+ else
+ mujstest_file = fopen(mujstest_filename, "wb");
+ }
+
+ ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);
+ if (!ctx)
+ {
+ fprintf(stderr, "cannot initialise context\n");
+ exit(1);
+ }
+
+ fz_set_aa_level(ctx, alphabits);
+
+ /* Determine output type */
+ output_format = OUT_PNG;
+ if (output)
+ {
+ char *suffix = output;
+ int i;
+
+ for (i = 0; i < nelem(suffix_table); i++)
+ {
+ char *s = strstr(suffix, suffix_table[i].suffix);
+
+ if (s != NULL)
+ {
+ suffix = s+1;
+ output_format = suffix_table[i].format;
+ i = 0;
+ }
+ }
+ }
+
+ {
+ int i, j;
+
+ for (i = 0; i < nelem(format_cs_table); i++)
+ {
+ if (format_cs_table[i].format == output_format)
+ {
+ if (out_cs == CS_UNSET)
+ out_cs = format_cs_table[i].default_cs;
+ for (j = 0; j < nelem(format_cs_table[i].permitted_cs); j++)
+ {
+ if (format_cs_table[i].permitted_cs[j] == out_cs)
+ break;
+ }
+ if (j == nelem(format_cs_table[i].permitted_cs))
+ {
+ fprintf(stderr, "Unsupported colorspace for this format\n");
+ exit(1);
+ }
+ }
+ }
+ }
+
+ switch (out_cs)
+ {
+ case CS_MONO:
+ case CS_GRAY:
+ case CS_GRAYALPHA:
+ colorspace = fz_device_gray(ctx);
+ break;
+ case CS_RGB:
+ case CS_RGBA:
+ colorspace = fz_device_rgb(ctx);
+ break;
+ default:
+ fprintf(stderr, "Unknown colorspace!\n");
+ exit(1);
+ break;
+ }
+
+ timing.count = 0;
+ timing.total = 0;
+ timing.min = 1 << 30;
+ timing.max = 0;
+ timing.minpage = 0;
+ timing.maxpage = 0;
+ timing.minfilename = "";
+ timing.maxfilename = "";
+
+ if (showxml || showtext)
+ out = fz_new_output_with_file(ctx, stdout);
+
+ if (showxml || showtext == TEXT_XML)
+ fz_printf(out, "<?xml version=\"1.0\"?>\n");
+
+ if (showtext)
+ sheet = fz_new_text_sheet(ctx);
+
+ if (showtext == TEXT_HTML)
+ {
+ fz_printf(out, "<style>\n");
+ fz_printf(out, "body{background-color:gray;margin:12pt;}\n");
+ fz_printf(out, "div.page{background-color:white;margin:6pt;padding:6pt;}\n");
+ fz_printf(out, "div.block{border:1px solid gray;margin:6pt;padding:6pt;}\n");
+ fz_printf(out, "div.metaline{display:table;width:100%%}\n");
+ fz_printf(out, "div.line{display:table-row;padding:6pt}\n");
+ fz_printf(out, "div.cell{display:table-cell;padding-left:6pt;padding-right:6pt}\n");
+ fz_printf(out, "p{margin:0pt;padding:0pt;}\n");
+ fz_printf(out, "</style>\n");
+ fz_printf(out, "<body>\n");
+ }
+
+ fz_try(ctx)
+ {
+ while (fz_optind < argc)
+ {
+ fz_try(ctx)
+ {
+ filename = argv[fz_optind++];
+ files++;
+
+ fz_try(ctx)
+ {
+ doc = fz_open_document(ctx, filename);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot open document: %s", filename);
+ }
+
+ if (fz_needs_password(doc))
+ {
+ if (!fz_authenticate_password(doc, password))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", filename);
+ if (mujstest_file)
+ fprintf(mujstest_file, "PASSWORD %s\n", password);
+ }
+
+ if (mujstest_file)
+ {
+ fprintf(mujstest_file, "OPEN %s\n", filename);
+ }
+
+ if (showxml || showtext == TEXT_XML)
+ fz_printf(out, "<document name=\"%s\">\n", filename);
+
+ if (showoutline)
+ drawoutline(ctx, doc);
+
+ if (showtext || showxml || showtime || showmd5 || output || mujstest_file)
+ {
+ if (fz_optind == argc || !isrange(argv[fz_optind]))
+ drawrange(ctx, doc, "1-");
+ if (fz_optind < argc && isrange(argv[fz_optind]))
+ drawrange(ctx, doc, argv[fz_optind++]);
+ }
+
+ if (showxml || showtext == TEXT_XML)
+ fz_printf(out, "</document>\n");
+
+ fz_close_document(doc);
+ doc = NULL;
+ }
+ fz_catch(ctx)
+ {
+ if (!ignore_errors)
+ fz_rethrow(ctx);
+
+ fz_close_document(doc);
+ doc = NULL;
+ fz_warn(ctx, "ignoring error in '%s'", filename);
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_close_document(doc);
+ fprintf(stderr, "error: cannot draw '%s'\n", filename);
+ errored = 1;
+ }
+
+ if (showtext == TEXT_HTML)
+ {
+ fz_printf(out, "</body>\n");
+ fz_printf(out, "<style>\n");
+ fz_print_text_sheet(ctx, out, sheet);
+ fz_printf(out, "</style>\n");
+ }
+
+ if (showtext)
+ fz_free_text_sheet(ctx, sheet);
+
+ if (showxml || showtext)
+ {
+ fz_close_output(out);
+ out = NULL;
+ }
+
+ if (showtime && timing.count > 0)
+ {
+ if (files == 1)
+ {
+ printf("total %dms / %d pages for an average of %dms\n",
+ timing.total, timing.count, timing.total / timing.count);
+ printf("fastest page %d: %dms\n", timing.minpage, timing.min);
+ printf("slowest page %d: %dms\n", timing.maxpage, timing.max);
+ }
+ else
+ {
+ printf("total %dms / %d pages for an average of %dms in %d files\n",
+ timing.total, timing.count, timing.total / timing.count, files);
+ printf("fastest page %d: %dms (%s)\n", timing.minpage, timing.min, timing.minfilename);
+ printf("slowest page %d: %dms (%s)\n", timing.maxpage, timing.max, timing.maxfilename);
+ }
+ }
+
+ if (mujstest_file && mujstest_file != stdout)
+ fclose(mujstest_file);
+
+ fz_free_context(ctx);
+ return (errored != 0);
+}
+
+#ifdef _MSC_VER
+int wmain(int argc, wchar_t *wargv[])
+{
+ char **argv = fz_argv_from_wargv(argc, wargv);
+ int ret = main(argc, argv);
+ fz_free_argv(argc, argv);
+ return ret;
+}
+#endif
diff --git a/source/tools/mutool.c b/source/tools/mutool.c
new file mode 100644
index 00000000..62355f32
--- /dev/null
+++ b/source/tools/mutool.c
@@ -0,0 +1,93 @@
+/*
+ * mutool -- swiss army knife of pdf manipulation tools
+ */
+
+#include "mupdf/fitz.h"
+
+#ifdef _MSC_VER
+#define main main_utf8
+#endif
+
+int pdfclean_main(int argc, char *argv[]);
+int pdfextract_main(int argc, char *argv[]);
+int pdfinfo_main(int argc, char *argv[]);
+int pdfposter_main(int argc, char *argv[]);
+int pdfshow_main(int argc, char *argv[]);
+
+static struct {
+ int (*func)(int argc, char *argv[]);
+ char *name;
+ char *desc;
+} tools[] = {
+ { pdfclean_main, "clean", "rewrite pdf file" },
+ { pdfextract_main, "extract", "extract font and image resources" },
+ { pdfinfo_main, "info", "show information about pdf resources" },
+ { pdfposter_main, "poster", "split large page into many tiles" },
+ { pdfshow_main, "show", "show internal pdf objects" },
+};
+
+static int
+namematch(const char *end, const char *start, const char *match)
+{
+ int len = strlen(match);
+ return ((end-len >= start) && (strncmp(end-len, match, len) == 0));
+}
+
+int main(int argc, char **argv)
+{
+ char *start, *end;
+ char buf[32];
+ int i;
+
+ if (argc == 0)
+ {
+ fprintf(stderr, "No command name found!\n");
+ return 1;
+ }
+
+ /* Check argv[0] */
+
+ if (argc > 0)
+ {
+ end = start = argv[0];
+ while (*end)
+ end++;
+ if ((end-4 >= start) && (end[-4] == '.') && (end[-3] == 'e') && (end[-2] == 'x') && (end[-1] == 'e'))
+ end = end-4;
+ for (i = 0; i < nelem(tools); i++)
+ {
+ strcpy(buf, "mupdf");
+ strcat(buf, tools[i].name);
+ if (namematch(end, start, buf) || namematch(end, start, buf+2))
+ return tools[i].func(argc, argv);
+ }
+ }
+
+ /* Check argv[1] */
+
+ if (argc > 1)
+ {
+ for (i = 0; i < nelem(tools); i++)
+ if (!strcmp(tools[i].name, argv[1]))
+ return tools[i].func(argc - 1, argv + 1);
+ }
+
+ /* Print usage */
+
+ fprintf(stderr, "usage: mutool <command> [options]\n");
+
+ for (i = 0; i < nelem(tools); i++)
+ fprintf(stderr, "\t%s\t-- %s\n", tools[i].name, tools[i].desc);
+
+ return 1;
+}
+
+#ifdef _MSC_VER
+int wmain(int argc, wchar_t *wargv[])
+{
+ char **argv = fz_argv_from_wargv(argc, wargv);
+ int ret = main(argc, argv);
+ fz_free_argv(argc, argv);
+ return ret;
+}
+#endif
diff --git a/source/tools/pdfclean.c b/source/tools/pdfclean.c
new file mode 100644
index 00000000..c437b819
--- /dev/null
+++ b/source/tools/pdfclean.c
@@ -0,0 +1,237 @@
+/*
+ * PDF cleaning tool: general purpose pdf syntax washer.
+ *
+ * Rewrite PDF with pretty printed objects.
+ * Garbage collect unreachable objects.
+ * Inflate compressed streams.
+ * Create subset documents.
+ *
+ * TODO: linearize document for fast web view
+ */
+
+#include "mupdf/pdf.h"
+
+static pdf_document *xref = NULL;
+static fz_context *ctx = NULL;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "usage: mutool clean [options] input.pdf [output.pdf] [pages]\n"
+ "\t-p -\tpassword\n"
+ "\t-g\tgarbage collect unused objects\n"
+ "\t-gg\tin addition to -g compact xref table\n"
+ "\t-ggg\tin addition to -gg merge duplicate objects\n"
+ "\t-d\tdecompress all streams\n"
+ "\t-l\tlinearize PDF\n"
+ "\t-i\ttoggle decompression of image streams\n"
+ "\t-f\ttoggle decompression of font streams\n"
+ "\t-a\tascii hex encode binary streams\n"
+ "\tpages\tcomma separated list of ranges\n");
+ exit(1);
+}
+
+/*
+ * Recreate page tree to only retain specified pages.
+ */
+
+static void retainpages(int argc, char **argv)
+{
+ pdf_obj *oldroot, *root, *pages, *kids, *countobj, *parent, *olddests;
+
+ /* Keep only pages/type and (reduced) dest entries to avoid
+ * references to unretained pages */
+ oldroot = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pages = pdf_dict_gets(oldroot, "Pages");
+ olddests = pdf_load_name_tree(xref, "Dests");
+
+ root = pdf_new_dict(ctx, 2);
+ pdf_dict_puts(root, "Type", pdf_dict_gets(oldroot, "Type"));
+ pdf_dict_puts(root, "Pages", pdf_dict_gets(oldroot, "Pages"));
+
+ pdf_update_object(xref, pdf_to_num(oldroot), root);
+
+ pdf_drop_obj(root);
+
+ /* Create a new kids array with only the pages we want to keep */
+ parent = pdf_new_indirect(ctx, pdf_to_num(pages), pdf_to_gen(pages), xref);
+ kids = pdf_new_array(ctx, 1);
+
+ /* Retain pages specified */
+ while (argc - fz_optind)
+ {
+ int page, spage, epage, pagecount;
+ char *spec, *dash;
+ char *pagelist = argv[fz_optind];
+
+ pagecount = pdf_count_pages(xref);
+ spec = fz_strsep(&pagelist, ",");
+ while (spec)
+ {
+ dash = strchr(spec, '-');
+
+ if (dash == spec)
+ spage = epage = pagecount;
+ else
+ spage = epage = atoi(spec);
+
+ if (dash)
+ {
+ if (strlen(dash) > 1)
+ epage = atoi(dash + 1);
+ else
+ epage = pagecount;
+ }
+
+ if (spage > epage)
+ page = spage, spage = epage, epage = page;
+
+ spage = fz_clampi(spage, 1, pagecount);
+ epage = fz_clampi(epage, 1, pagecount);
+
+ for (page = spage; page <= epage; page++)
+ {
+ pdf_obj *pageobj = xref->page_objs[page-1];
+ pdf_obj *pageref = xref->page_refs[page-1];
+
+ pdf_dict_puts(pageobj, "Parent", parent);
+
+ /* Store page object in new kids array */
+ pdf_array_push(kids, pageref);
+ }
+
+ spec = fz_strsep(&pagelist, ",");
+ }
+
+ fz_optind++;
+ }
+
+ pdf_drop_obj(parent);
+
+ /* Update page count and kids array */
+ countobj = pdf_new_int(ctx, pdf_array_len(kids));
+ pdf_dict_puts(pages, "Count", countobj);
+ pdf_drop_obj(countobj);
+ pdf_dict_puts(pages, "Kids", kids);
+ pdf_drop_obj(kids);
+
+ /* Also preserve the (partial) Dests name tree */
+ if (olddests)
+ {
+ int i;
+ pdf_obj *names = pdf_new_dict(ctx, 1);
+ pdf_obj *dests = pdf_new_dict(ctx, 1);
+ pdf_obj *names_list = pdf_new_array(ctx, 32);
+ int len = pdf_dict_len(olddests);
+
+ for (i = 0; i < len; i++)
+ {
+ pdf_obj *key = pdf_dict_get_key(olddests, i);
+ pdf_obj *val = pdf_dict_get_val(olddests, i);
+ pdf_obj *key_str = pdf_new_string(ctx, pdf_to_name(key), strlen(pdf_to_name(key)));
+ pdf_obj *dest = pdf_dict_gets(val, "D");
+
+ dest = pdf_array_get(dest ? dest : val, 0);
+ if (pdf_array_contains(pdf_dict_gets(pages, "Kids"), dest))
+ {
+ pdf_array_push(names_list, key_str);
+ pdf_array_push(names_list, val);
+ }
+ pdf_drop_obj(key_str);
+ }
+
+ root = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pdf_dict_puts(dests, "Names", names_list);
+ pdf_dict_puts(names, "Dests", dests);
+ pdf_dict_puts(root, "Names", names);
+
+ pdf_drop_obj(names);
+ pdf_drop_obj(dests);
+ pdf_drop_obj(names_list);
+ pdf_drop_obj(olddests);
+ }
+}
+
+int pdfclean_main(int argc, char **argv)
+{
+ char *infile;
+ char *outfile = "out.pdf";
+ char *password = "";
+ int c;
+ int subset;
+ fz_write_options opts;
+ int write_failed = 0;
+ int errors = 0;
+
+ opts.do_garbage = 0;
+ opts.do_expand = 0;
+ opts.do_ascii = 0;
+ opts.do_linear = 0;
+ opts.continue_on_error = 1;
+ opts.errors = &errors;
+
+ while ((c = fz_getopt(argc, argv, "adfgilp:")) != -1)
+ {
+ switch (c)
+ {
+ case 'p': password = fz_optarg; break;
+ case 'g': opts.do_garbage ++; break;
+ case 'd': opts.do_expand ^= fz_expand_all; break;
+ case 'f': opts.do_expand ^= fz_expand_fonts; break;
+ case 'i': opts.do_expand ^= fz_expand_images; break;
+ case 'l': opts.do_linear ++; break;
+ case 'a': opts.do_ascii ++; break;
+ default: usage(); break;
+ }
+ }
+
+ if (argc - fz_optind < 1)
+ usage();
+
+ infile = argv[fz_optind++];
+
+ if (argc - fz_optind > 0 &&
+ (strstr(argv[fz_optind], ".pdf") || strstr(argv[fz_optind], ".PDF")))
+ {
+ outfile = argv[fz_optind++];
+ }
+
+ subset = 0;
+ if (argc - fz_optind > 0)
+ subset = 1;
+
+ ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
+ if (!ctx)
+ {
+ fprintf(stderr, "cannot initialise context\n");
+ exit(1);
+ }
+
+ fz_try(ctx)
+ {
+ xref = pdf_open_document_no_run(ctx, infile);
+ if (pdf_needs_password(xref))
+ if (!pdf_authenticate_password(xref, password))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", infile);
+
+ /* Only retain the specified subset of the pages */
+ if (subset)
+ retainpages(argc, argv);
+
+ pdf_write_document(xref, outfile, &opts);
+ }
+ fz_always(ctx)
+ {
+ pdf_close_document(xref);
+ }
+ fz_catch(ctx)
+ {
+ write_failed = 1;
+ }
+
+ fz_free_context(ctx);
+
+ if (errors)
+ write_failed = 1;
+ return write_failed ? 1 : 0;
+}
diff --git a/source/tools/pdfextract.c b/source/tools/pdfextract.c
new file mode 100644
index 00000000..6e8e4aec
--- /dev/null
+++ b/source/tools/pdfextract.c
@@ -0,0 +1,231 @@
+/*
+ * pdfextract -- the ultimate way to extract images and fonts from pdfs
+ */
+
+#include "mupdf/pdf.h"
+
+static pdf_document *doc = NULL;
+static fz_context *ctx = NULL;
+static int dorgb = 0;
+
+static void usage(void)
+{
+ fprintf(stderr, "usage: mutool extract [options] file.pdf [object numbers]\n");
+ fprintf(stderr, "\t-p\tpassword\n");
+ fprintf(stderr, "\t-r\tconvert images to rgb\n");
+ exit(1);
+}
+
+static int isimage(pdf_obj *obj)
+{
+ pdf_obj *type = pdf_dict_gets(obj, "Subtype");
+ return pdf_is_name(type) && !strcmp(pdf_to_name(type), "Image");
+}
+
+static int isfontdesc(pdf_obj *obj)
+{
+ pdf_obj *type = pdf_dict_gets(obj, "Type");
+ return pdf_is_name(type) && !strcmp(pdf_to_name(type), "FontDescriptor");
+}
+
+static void writepixmap(fz_context *ctx, fz_pixmap *pix, char *file, int rgb)
+{
+ char name[1024];
+ fz_pixmap *converted = NULL;
+
+ if (!pix)
+ return;
+
+ if (rgb && pix->colorspace && pix->colorspace != fz_device_rgb(ctx))
+ {
+ fz_irect bbox;
+ converted = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_pixmap_bbox(ctx, pix, &bbox));
+ fz_convert_pixmap(ctx, converted, pix);
+ pix = converted;
+ }
+
+ if (pix->n <= 4)
+ {
+ sprintf(name, "%s.png", file);
+ printf("extracting image %s\n", name);
+ fz_write_png(ctx, pix, name, 0);
+ }
+ else
+ {
+ sprintf(name, "%s.pam", file);
+ printf("extracting image %s\n", name);
+ fz_write_pam(ctx, pix, name, 0);
+ }
+
+ fz_drop_pixmap(ctx, converted);
+}
+
+static void saveimage(int num)
+{
+ fz_image *image;
+ fz_pixmap *pix;
+ pdf_obj *ref;
+ char name[32];
+
+ ref = pdf_new_indirect(ctx, num, 0, doc);
+
+ /* TODO: detect DCTD and save as jpeg */
+
+ image = pdf_load_image(doc, ref);
+ pix = fz_image_to_pixmap(ctx, image, 0, 0);
+ fz_drop_image(ctx, image);
+
+ sprintf(name, "img-%04d", num);
+ writepixmap(ctx, pix, name, dorgb);
+
+ fz_drop_pixmap(ctx, pix);
+ pdf_drop_obj(ref);
+}
+
+static void savefont(pdf_obj *dict, int num)
+{
+ char name[1024];
+ char *subtype;
+ fz_buffer *buf;
+ pdf_obj *stream = NULL;
+ pdf_obj *obj;
+ char *ext = "";
+ FILE *f;
+ char *fontname = "font";
+ int n, len;
+ unsigned char *data;
+
+ obj = pdf_dict_gets(dict, "FontName");
+ if (obj)
+ fontname = pdf_to_name(obj);
+
+ obj = pdf_dict_gets(dict, "FontFile");
+ if (obj)
+ {
+ stream = obj;
+ ext = "pfa";
+ }
+
+ obj = pdf_dict_gets(dict, "FontFile2");
+ if (obj)
+ {
+ stream = obj;
+ ext = "ttf";
+ }
+
+ obj = pdf_dict_gets(dict, "FontFile3");
+ if (obj)
+ {
+ stream = obj;
+
+ obj = pdf_dict_gets(obj, "Subtype");
+ if (obj && !pdf_is_name(obj))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid font descriptor subtype");
+
+ subtype = pdf_to_name(obj);
+ if (!strcmp(subtype, "Type1C"))
+ ext = "cff";
+ else if (!strcmp(subtype, "CIDFontType0C"))
+ ext = "cid";
+ else if (!strcmp(subtype, "OpenType"))
+ ext = "otf";
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Unhandled font type '%s'", subtype);
+ }
+
+ if (!stream)
+ {
+ fz_warn(ctx, "Unhandled font type");
+ return;
+ }
+
+ buf = pdf_load_stream(doc, pdf_to_num(stream), pdf_to_gen(stream));
+
+ sprintf(name, "%s-%04d.%s", fontname, num, ext);
+ printf("extracting font %s\n", name);
+
+ f = fopen(name, "wb");
+ if (!f)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Error creating font file");
+
+ len = fz_buffer_storage(ctx, buf, &data);
+ n = fwrite(data, 1, len, f);
+ if (n < len)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Error writing font file");
+
+ if (fclose(f) < 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "Error closing font file");
+
+ fz_drop_buffer(ctx, buf);
+}
+
+static void showobject(int num)
+{
+ pdf_obj *obj;
+
+ if (!doc)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no file specified");
+
+ obj = pdf_load_object(doc, num, 0);
+
+ if (isimage(obj))
+ saveimage(num);
+ else if (isfontdesc(obj))
+ savefont(obj, num);
+
+ pdf_drop_obj(obj);
+}
+
+int pdfextract_main(int argc, char **argv)
+{
+ char *infile;
+ char *password = "";
+ int c, o;
+
+ while ((c = fz_getopt(argc, argv, "p:r")) != -1)
+ {
+ switch (c)
+ {
+ case 'p': password = fz_optarg; break;
+ case 'r': dorgb++; break;
+ default: usage(); break;
+ }
+ }
+
+ if (fz_optind == argc)
+ usage();
+
+ infile = argv[fz_optind++];
+
+ ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
+ if (!ctx)
+ {
+ fprintf(stderr, "cannot initialise context\n");
+ exit(1);
+ }
+
+ doc = pdf_open_document_no_run(ctx, infile);
+ if (pdf_needs_password(doc))
+ if (!pdf_authenticate_password(doc, password))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", infile);
+
+ if (fz_optind == argc)
+ {
+ int len = pdf_count_objects(doc);
+ for (o = 0; o < len; o++)
+ showobject(o);
+ }
+ else
+ {
+ while (fz_optind < argc)
+ {
+ showobject(atoi(argv[fz_optind]));
+ fz_optind++;
+ }
+ }
+
+ pdf_close_document(doc);
+ fz_flush_warnings(ctx);
+ fz_free_context(ctx);
+ return 0;
+}
diff --git a/source/tools/pdfinfo.c b/source/tools/pdfinfo.c
new file mode 100644
index 00000000..79743892
--- /dev/null
+++ b/source/tools/pdfinfo.c
@@ -0,0 +1,1032 @@
+/*
+ * Information tool.
+ * Print information about the input pdf.
+ */
+
+#include "mupdf/pdf.h"
+
+pdf_document *xref;
+fz_context *ctx;
+int pagecount;
+
+void closexref(void);
+
+void openxref(char *filename, char *password, int dieonbadpass, int loadpages);
+
+enum
+{
+ DIMENSIONS = 0x01,
+ FONTS = 0x02,
+ IMAGES = 0x04,
+ SHADINGS = 0x08,
+ PATTERNS = 0x10,
+ XOBJS = 0x20,
+ ALL = DIMENSIONS | FONTS | IMAGES | SHADINGS | PATTERNS | XOBJS
+};
+
+struct info
+{
+ int page;
+ pdf_obj *pageref;
+ pdf_obj *pageobj;
+ union {
+ struct {
+ pdf_obj *obj;
+ } info;
+ struct {
+ pdf_obj *obj;
+ } crypt;
+ struct {
+ pdf_obj *obj;
+ fz_rect *bbox;
+ } dim;
+ struct {
+ pdf_obj *obj;
+ pdf_obj *subtype;
+ pdf_obj *name;
+ } font;
+ struct {
+ pdf_obj *obj;
+ pdf_obj *width;
+ pdf_obj *height;
+ pdf_obj *bpc;
+ pdf_obj *filter;
+ pdf_obj *cs;
+ pdf_obj *altcs;
+ } image;
+ struct {
+ pdf_obj *obj;
+ pdf_obj *type;
+ } shading;
+ struct {
+ pdf_obj *obj;
+ pdf_obj *type;
+ pdf_obj *paint;
+ pdf_obj *tiling;
+ pdf_obj *shading;
+ } pattern;
+ struct {
+ pdf_obj *obj;
+ pdf_obj *groupsubtype;
+ pdf_obj *reference;
+ } form;
+ } u;
+};
+
+static struct info *dim = NULL;
+static int dims = 0;
+static struct info *font = NULL;
+static int fonts = 0;
+static struct info *image = NULL;
+static int images = 0;
+static struct info *shading = NULL;
+static int shadings = 0;
+static struct info *pattern = NULL;
+static int patterns = 0;
+static struct info *form = NULL;
+static int forms = 0;
+static struct info *psobj = NULL;
+static int psobjs = 0;
+
+void closexref(void)
+{
+ int i;
+ if (xref)
+ {
+ pdf_close_document(xref);
+ xref = NULL;
+ }
+
+ if (dim)
+ {
+ for (i = 0; i < dims; i++)
+ fz_free(ctx, dim[i].u.dim.bbox);
+ fz_free(ctx, dim);
+ dim = NULL;
+ dims = 0;
+ }
+
+ if (font)
+ {
+ fz_free(ctx, font);
+ font = NULL;
+ fonts = 0;
+ }
+
+ if (image)
+ {
+ fz_free(ctx, image);
+ image = NULL;
+ images = 0;
+ }
+
+ if (shading)
+ {
+ fz_free(ctx, shading);
+ shading = NULL;
+ shadings = 0;
+ }
+
+ if (pattern)
+ {
+ fz_free(ctx, pattern);
+ pattern = NULL;
+ patterns = 0;
+ }
+
+ if (form)
+ {
+ fz_free(ctx, form);
+ form = NULL;
+ forms = 0;
+ }
+
+ if (psobj)
+ {
+ fz_free(ctx, psobj);
+ psobj = NULL;
+ psobjs = 0;
+ }
+}
+
+static void
+infousage(void)
+{
+ fprintf(stderr,
+ "usage: mutool info [options] [file.pdf ... ]\n"
+ "\t-d -\tpassword for decryption\n"
+ "\t-f\tlist fonts\n"
+ "\t-i\tlist images\n"
+ "\t-m\tlist dimensions\n"
+ "\t-p\tlist patterns\n"
+ "\t-s\tlist shadings\n"
+ "\t-x\tlist form and postscript xobjects\n");
+ exit(1);
+}
+
+static void
+showglobalinfo(void)
+{
+ pdf_obj *obj;
+
+ printf("\nPDF-%d.%d\n", xref->version / 10, xref->version % 10);
+
+ obj = pdf_dict_gets(pdf_trailer(xref), "Info");
+ if (obj)
+ {
+ printf("Info object (%d %d R):\n", pdf_to_num(obj), pdf_to_gen(obj));
+ pdf_fprint_obj(stdout, pdf_resolve_indirect(obj), 0);
+ }
+
+ obj = pdf_dict_gets(pdf_trailer(xref), "Encrypt");
+ if (obj)
+ {
+ printf("\nEncryption object (%d %d R):\n", pdf_to_num(obj), pdf_to_gen(obj));
+ pdf_fprint_obj(stdout, pdf_resolve_indirect(obj), 0);
+ }
+
+ printf("\nPages: %d\n\n", pagecount);
+}
+
+static void
+gatherdimensions(int page, pdf_obj *pageref, pdf_obj *pageobj)
+{
+ fz_rect bbox;
+ pdf_obj *obj;
+ int j;
+
+ obj = pdf_dict_gets(pageobj, "MediaBox");
+ if (!pdf_is_array(obj))
+ return;
+
+ pdf_to_rect(ctx, obj, &bbox);
+
+ obj = pdf_dict_gets(pageobj, "UserUnit");
+ if (pdf_is_real(obj))
+ {
+ float unit = pdf_to_real(obj);
+ bbox.x0 *= unit;
+ bbox.y0 *= unit;
+ bbox.x1 *= unit;
+ bbox.y1 *= unit;
+ }
+
+ for (j = 0; j < dims; j++)
+ if (!memcmp(dim[j].u.dim.bbox, &bbox, sizeof (fz_rect)))
+ break;
+
+ if (j < dims)
+ return;
+
+ dim = fz_resize_array(ctx, dim, dims+1, sizeof(struct info));
+ dims++;
+
+ dim[dims - 1].page = page;
+ dim[dims - 1].pageref = pageref;
+ dim[dims - 1].pageobj = pageobj;
+ dim[dims - 1].u.dim.bbox = fz_malloc(ctx, sizeof(fz_rect));
+ memcpy(dim[dims - 1].u.dim.bbox, &bbox, sizeof (fz_rect));
+
+ return;
+}
+
+static void
+gatherfonts(int page, pdf_obj *pageref, pdf_obj *pageobj, pdf_obj *dict)
+{
+ int i, n;
+
+ n = pdf_dict_len(dict);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *fontdict = NULL;
+ pdf_obj *subtype = NULL;
+ pdf_obj *basefont = NULL;
+ pdf_obj *name = NULL;
+ int k;
+
+ fontdict = pdf_dict_get_val(dict, i);
+ if (!pdf_is_dict(fontdict))
+ {
+ fz_warn(ctx, "not a font dict (%d %d R)", pdf_to_num(fontdict), pdf_to_gen(fontdict));
+ continue;
+ }
+
+ subtype = pdf_dict_gets(fontdict, "Subtype");
+ basefont = pdf_dict_gets(fontdict, "BaseFont");
+ if (!basefont || pdf_is_null(basefont))
+ name = pdf_dict_gets(fontdict, "Name");
+
+ for (k = 0; k < fonts; k++)
+ if (!pdf_objcmp(font[k].u.font.obj, fontdict))
+ break;
+
+ if (k < fonts)
+ continue;
+
+ font = fz_resize_array(ctx, font, fonts+1, sizeof(struct info));
+ fonts++;
+
+ font[fonts - 1].page = page;
+ font[fonts - 1].pageref = pageref;
+ font[fonts - 1].pageobj = pageobj;
+ font[fonts - 1].u.font.obj = fontdict;
+ font[fonts - 1].u.font.subtype = subtype;
+ font[fonts - 1].u.font.name = basefont ? basefont : name;
+ }
+}
+
+static void
+gatherimages(int page, pdf_obj *pageref, pdf_obj *pageobj, pdf_obj *dict)
+{
+ int i, n;
+
+ n = pdf_dict_len(dict);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *imagedict;
+ pdf_obj *type;
+ pdf_obj *width;
+ pdf_obj *height;
+ pdf_obj *bpc = NULL;
+ pdf_obj *filter = NULL;
+ pdf_obj *cs = NULL;
+ pdf_obj *altcs;
+ int k;
+
+ imagedict = pdf_dict_get_val(dict, i);
+ if (!pdf_is_dict(imagedict))
+ {
+ fz_warn(ctx, "not an image dict (%d %d R)", pdf_to_num(imagedict), pdf_to_gen(imagedict));
+ continue;
+ }
+
+ type = pdf_dict_gets(imagedict, "Subtype");
+ if (strcmp(pdf_to_name(type), "Image"))
+ continue;
+
+ filter = pdf_dict_gets(imagedict, "Filter");
+
+ altcs = NULL;
+ cs = pdf_dict_gets(imagedict, "ColorSpace");
+ if (pdf_is_array(cs))
+ {
+ pdf_obj *cses = cs;
+
+ cs = pdf_array_get(cses, 0);
+ if (pdf_is_name(cs) && (!strcmp(pdf_to_name(cs), "DeviceN") || !strcmp(pdf_to_name(cs), "Separation")))
+ {
+ altcs = pdf_array_get(cses, 2);
+ if (pdf_is_array(altcs))
+ altcs = pdf_array_get(altcs, 0);
+ }
+ }
+
+ width = pdf_dict_gets(imagedict, "Width");
+ height = pdf_dict_gets(imagedict, "Height");
+ bpc = pdf_dict_gets(imagedict, "BitsPerComponent");
+
+ for (k = 0; k < images; k++)
+ if (!pdf_objcmp(image[k].u.image.obj, imagedict))
+ break;
+
+ if (k < images)
+ continue;
+
+ image = fz_resize_array(ctx, image, images+1, sizeof(struct info));
+ images++;
+
+ image[images - 1].page = page;
+ image[images - 1].pageref = pageref;
+ image[images - 1].pageobj = pageobj;
+ image[images - 1].u.image.obj = imagedict;
+ image[images - 1].u.image.width = width;
+ image[images - 1].u.image.height = height;
+ image[images - 1].u.image.bpc = bpc;
+ image[images - 1].u.image.filter = filter;
+ image[images - 1].u.image.cs = cs;
+ image[images - 1].u.image.altcs = altcs;
+ }
+}
+
+static void
+gatherforms(int page, pdf_obj *pageref, pdf_obj *pageobj, pdf_obj *dict)
+{
+ int i, n;
+
+ n = pdf_dict_len(dict);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *xobjdict;
+ pdf_obj *type;
+ pdf_obj *subtype;
+ pdf_obj *group;
+ pdf_obj *groupsubtype;
+ pdf_obj *reference;
+ int k;
+
+ xobjdict = pdf_dict_get_val(dict, i);
+ if (!pdf_is_dict(xobjdict))
+ {
+ fz_warn(ctx, "not a xobject dict (%d %d R)", pdf_to_num(xobjdict), pdf_to_gen(xobjdict));
+ continue;
+ }
+
+ type = pdf_dict_gets(xobjdict, "Subtype");
+ if (strcmp(pdf_to_name(type), "Form"))
+ continue;
+
+ subtype = pdf_dict_gets(xobjdict, "Subtype2");
+ if (!strcmp(pdf_to_name(subtype), "PS"))
+ continue;
+
+ group = pdf_dict_gets(xobjdict, "Group");
+ groupsubtype = pdf_dict_gets(group, "S");
+ reference = pdf_dict_gets(xobjdict, "Ref");
+
+ for (k = 0; k < forms; k++)
+ if (!pdf_objcmp(form[k].u.form.obj, xobjdict))
+ break;
+
+ if (k < forms)
+ continue;
+
+ form = fz_resize_array(ctx, form, forms+1, sizeof(struct info));
+ forms++;
+
+ form[forms - 1].page = page;
+ form[forms - 1].pageref = pageref;
+ form[forms - 1].pageobj = pageobj;
+ form[forms - 1].u.form.obj = xobjdict;
+ form[forms - 1].u.form.groupsubtype = groupsubtype;
+ form[forms - 1].u.form.reference = reference;
+ }
+}
+
+static void
+gatherpsobjs(int page, pdf_obj *pageref, pdf_obj *pageobj, pdf_obj *dict)
+{
+ int i, n;
+
+ n = pdf_dict_len(dict);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *xobjdict;
+ pdf_obj *type;
+ pdf_obj *subtype;
+ int k;
+
+ xobjdict = pdf_dict_get_val(dict, i);
+ if (!pdf_is_dict(xobjdict))
+ {
+ fz_warn(ctx, "not a xobject dict (%d %d R)", pdf_to_num(xobjdict), pdf_to_gen(xobjdict));
+ continue;
+ }
+
+ type = pdf_dict_gets(xobjdict, "Subtype");
+ subtype = pdf_dict_gets(xobjdict, "Subtype2");
+ if (strcmp(pdf_to_name(type), "PS") &&
+ (strcmp(pdf_to_name(type), "Form") || strcmp(pdf_to_name(subtype), "PS")))
+ continue;
+
+ for (k = 0; k < psobjs; k++)
+ if (!pdf_objcmp(psobj[k].u.form.obj, xobjdict))
+ break;
+
+ if (k < psobjs)
+ continue;
+
+ psobj = fz_resize_array(ctx, psobj, psobjs+1, sizeof(struct info));
+ psobjs++;
+
+ psobj[psobjs - 1].page = page;
+ psobj[psobjs - 1].pageref = pageref;
+ psobj[psobjs - 1].pageobj = pageobj;
+ psobj[psobjs - 1].u.form.obj = xobjdict;
+ }
+}
+
+static void
+gathershadings(int page, pdf_obj *pageref, pdf_obj *pageobj, pdf_obj *dict)
+{
+ int i, n;
+
+ n = pdf_dict_len(dict);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *shade;
+ pdf_obj *type;
+ int k;
+
+ shade = pdf_dict_get_val(dict, i);
+ if (!pdf_is_dict(shade))
+ {
+ fz_warn(ctx, "not a shading dict (%d %d R)", pdf_to_num(shade), pdf_to_gen(shade));
+ continue;
+ }
+
+ type = pdf_dict_gets(shade, "ShadingType");
+ if (!pdf_is_int(type) || pdf_to_int(type) < 1 || pdf_to_int(type) > 7)
+ {
+ fz_warn(ctx, "not a shading type (%d %d R)", pdf_to_num(shade), pdf_to_gen(shade));
+ type = NULL;
+ }
+
+ for (k = 0; k < shadings; k++)
+ if (!pdf_objcmp(shading[k].u.shading.obj, shade))
+ break;
+
+ if (k < shadings)
+ continue;
+
+ shading = fz_resize_array(ctx, shading, shadings+1, sizeof(struct info));
+ shadings++;
+
+ shading[shadings - 1].page = page;
+ shading[shadings - 1].pageref = pageref;
+ shading[shadings - 1].pageobj = pageobj;
+ shading[shadings - 1].u.shading.obj = shade;
+ shading[shadings - 1].u.shading.type = type;
+ }
+}
+
+static void
+gatherpatterns(int page, pdf_obj *pageref, pdf_obj *pageobj, pdf_obj *dict)
+{
+ int i, n;
+
+ n = pdf_dict_len(dict);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *patterndict;
+ pdf_obj *type;
+ pdf_obj *paint = NULL;
+ pdf_obj *tiling = NULL;
+ pdf_obj *shading = NULL;
+ int k;
+
+ patterndict = pdf_dict_get_val(dict, i);
+ if (!pdf_is_dict(patterndict))
+ {
+ fz_warn(ctx, "not a pattern dict (%d %d R)", pdf_to_num(patterndict), pdf_to_gen(patterndict));
+ continue;
+ }
+
+ type = pdf_dict_gets(patterndict, "PatternType");
+ if (!pdf_is_int(type) || pdf_to_int(type) < 1 || pdf_to_int(type) > 2)
+ {
+ fz_warn(ctx, "not a pattern type (%d %d R)", pdf_to_num(patterndict), pdf_to_gen(patterndict));
+ type = NULL;
+ }
+
+ if (pdf_to_int(type) == 1)
+ {
+ paint = pdf_dict_gets(patterndict, "PaintType");
+ if (!pdf_is_int(paint) || pdf_to_int(paint) < 1 || pdf_to_int(paint) > 2)
+ {
+ fz_warn(ctx, "not a pattern paint type (%d %d R)", pdf_to_num(patterndict), pdf_to_gen(patterndict));
+ paint = NULL;
+ }
+
+ tiling = pdf_dict_gets(patterndict, "TilingType");
+ if (!pdf_is_int(tiling) || pdf_to_int(tiling) < 1 || pdf_to_int(tiling) > 3)
+ {
+ fz_warn(ctx, "not a pattern tiling type (%d %d R)", pdf_to_num(patterndict), pdf_to_gen(patterndict));
+ tiling = NULL;
+ }
+ }
+ else
+ {
+ shading = pdf_dict_gets(patterndict, "Shading");
+ }
+
+ for (k = 0; k < patterns; k++)
+ if (!pdf_objcmp(pattern[k].u.pattern.obj, patterndict))
+ break;
+
+ if (k < patterns)
+ continue;
+
+ pattern = fz_resize_array(ctx, pattern, patterns+1, sizeof(struct info));
+ patterns++;
+
+ pattern[patterns - 1].page = page;
+ pattern[patterns - 1].pageref = pageref;
+ pattern[patterns - 1].pageobj = pageobj;
+ pattern[patterns - 1].u.pattern.obj = patterndict;
+ pattern[patterns - 1].u.pattern.type = type;
+ pattern[patterns - 1].u.pattern.paint = paint;
+ pattern[patterns - 1].u.pattern.tiling = tiling;
+ pattern[patterns - 1].u.pattern.shading = shading;
+ }
+}
+
+static void
+gatherresourceinfo(int page, pdf_obj *rsrc, int show)
+{
+ pdf_obj *pageobj;
+ pdf_obj *pageref;
+ pdf_obj *font;
+ pdf_obj *xobj;
+ pdf_obj *shade;
+ pdf_obj *pattern;
+ pdf_obj *subrsrc;
+ int i;
+
+ pageobj = xref->page_objs[page-1];
+ pageref = xref->page_refs[page-1];
+
+ if (!pageobj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot retrieve info from page %d", page);
+
+ font = pdf_dict_gets(rsrc, "Font");
+ if (show & FONTS && font)
+ {
+ int n;
+
+ gatherfonts(page, pageref, pageobj, font);
+ n = pdf_dict_len(font);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *obj = pdf_dict_get_val(font, i);
+
+ subrsrc = pdf_dict_gets(obj, "Resources");
+ if (subrsrc && pdf_objcmp(rsrc, subrsrc))
+ gatherresourceinfo(page, subrsrc, show);
+ }
+ }
+
+ xobj = pdf_dict_gets(rsrc, "XObject");
+ if (show & XOBJS && xobj)
+ {
+ int n;
+
+ gatherimages(page, pageref, pageobj, xobj);
+ gatherforms(page, pageref, pageobj, xobj);
+ gatherpsobjs(page, pageref, pageobj, xobj);
+ n = pdf_dict_len(xobj);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *obj = pdf_dict_get_val(xobj, i);
+ subrsrc = pdf_dict_gets(obj, "Resources");
+ if (subrsrc && pdf_objcmp(rsrc, subrsrc))
+ gatherresourceinfo(page, subrsrc, show);
+ }
+ }
+
+ shade = pdf_dict_gets(rsrc, "Shading");
+ if (show & SHADINGS && shade)
+ gathershadings(page, pageref, pageobj, shade);
+
+ pattern = pdf_dict_gets(rsrc, "Pattern");
+ if (show & PATTERNS && pattern)
+ {
+ int n;
+ gatherpatterns(page, pageref, pageobj, pattern);
+ n = pdf_dict_len(pattern);
+ for (i = 0; i < n; i++)
+ {
+ pdf_obj *obj = pdf_dict_get_val(pattern, i);
+ subrsrc = pdf_dict_gets(obj, "Resources");
+ if (subrsrc && pdf_objcmp(rsrc, subrsrc))
+ gatherresourceinfo(page, subrsrc, show);
+ }
+ }
+}
+
+static void
+gatherpageinfo(int page, int show)
+{
+ pdf_obj *pageobj;
+ pdf_obj *pageref;
+ pdf_obj *rsrc;
+
+ pageobj = xref->page_objs[page-1];
+ pageref = xref->page_refs[page-1];
+
+ if (!pageobj)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot retrieve info from page %d", page);
+
+ gatherdimensions(page, pageref, pageobj);
+
+ rsrc = pdf_dict_gets(pageobj, "Resources");
+ gatherresourceinfo(page, rsrc, show);
+}
+
+static void
+printinfo(char *filename, int show, int page)
+{
+ int i;
+ int j;
+
+#define PAGE_FMT "\t% 5d (% 7d %1d R): "
+
+ if (show & DIMENSIONS && dims > 0)
+ {
+ printf("Mediaboxes (%d):\n", dims);
+ for (i = 0; i < dims; i++)
+ {
+ printf(PAGE_FMT "[ %g %g %g %g ]\n",
+ dim[i].page,
+ pdf_to_num(dim[i].pageref), pdf_to_gen(dim[i].pageref),
+ dim[i].u.dim.bbox->x0,
+ dim[i].u.dim.bbox->y0,
+ dim[i].u.dim.bbox->x1,
+ dim[i].u.dim.bbox->y1);
+ }
+ printf("\n");
+ }
+
+ if (show & FONTS && fonts > 0)
+ {
+ printf("Fonts (%d):\n", fonts);
+ for (i = 0; i < fonts; i++)
+ {
+ printf(PAGE_FMT "%s '%s' (%d %d R)\n",
+ font[i].page,
+ pdf_to_num(font[i].pageref), pdf_to_gen(font[i].pageref),
+ pdf_to_name(font[i].u.font.subtype),
+ pdf_to_name(font[i].u.font.name),
+ pdf_to_num(font[i].u.font.obj), pdf_to_gen(font[i].u.font.obj));
+ }
+ printf("\n");
+ }
+
+ if (show & IMAGES && images > 0)
+ {
+ printf("Images (%d):\n", images);
+ for (i = 0; i < images; i++)
+ {
+ char *cs = NULL;
+ char *altcs = NULL;
+
+ printf(PAGE_FMT "[ ",
+ image[i].page,
+ pdf_to_num(image[i].pageref), pdf_to_gen(image[i].pageref));
+
+ if (pdf_is_array(image[i].u.image.filter))
+ {
+ int n = pdf_array_len(image[i].u.image.filter);
+ for (j = 0; j < n; j++)
+ {
+ pdf_obj *obj = pdf_array_get(image[i].u.image.filter, j);
+ char *filter = fz_strdup(ctx, pdf_to_name(obj));
+
+ if (strstr(filter, "Decode"))
+ *(strstr(filter, "Decode")) = '\0';
+
+ printf("%s%s",
+ filter,
+ j == pdf_array_len(image[i].u.image.filter) - 1 ? "" : " ");
+ fz_free(ctx, filter);
+ }
+ }
+ else if (image[i].u.image.filter)
+ {
+ pdf_obj *obj = image[i].u.image.filter;
+ char *filter = fz_strdup(ctx, pdf_to_name(obj));
+
+ if (strstr(filter, "Decode"))
+ *(strstr(filter, "Decode")) = '\0';
+
+ printf("%s", filter);
+ fz_free(ctx, filter);
+ }
+ else
+ printf("Raw");
+
+ if (image[i].u.image.cs)
+ {
+ cs = fz_strdup(ctx, pdf_to_name(image[i].u.image.cs));
+
+ if (!strncmp(cs, "Device", 6))
+ {
+ int len = strlen(cs + 6);
+ memmove(cs + 3, cs + 6, len + 1);
+ cs[3 + len + 1] = '\0';
+ }
+ if (strstr(cs, "ICC"))
+ fz_strlcpy(cs, "ICC", 4);
+ if (strstr(cs, "Indexed"))
+ fz_strlcpy(cs, "Idx", 4);
+ if (strstr(cs, "Pattern"))
+ fz_strlcpy(cs, "Pat", 4);
+ if (strstr(cs, "Separation"))
+ fz_strlcpy(cs, "Sep", 4);
+ }
+ if (image[i].u.image.altcs)
+ {
+ altcs = fz_strdup(ctx, pdf_to_name(image[i].u.image.altcs));
+
+ if (!strncmp(altcs, "Device", 6))
+ {
+ int len = strlen(altcs + 6);
+ memmove(altcs + 3, altcs + 6, len + 1);
+ altcs[3 + len + 1] = '\0';
+ }
+ if (strstr(altcs, "ICC"))
+ fz_strlcpy(altcs, "ICC", 4);
+ if (strstr(altcs, "Indexed"))
+ fz_strlcpy(altcs, "Idx", 4);
+ if (strstr(altcs, "Pattern"))
+ fz_strlcpy(altcs, "Pat", 4);
+ if (strstr(altcs, "Separation"))
+ fz_strlcpy(altcs, "Sep", 4);
+ }
+
+ printf(" ] %dx%d %dbpc %s%s%s (%d %d R)\n",
+ pdf_to_int(image[i].u.image.width),
+ pdf_to_int(image[i].u.image.height),
+ image[i].u.image.bpc ? pdf_to_int(image[i].u.image.bpc) : 1,
+ image[i].u.image.cs ? cs : "ImageMask",
+ image[i].u.image.altcs ? " " : "",
+ image[i].u.image.altcs ? altcs : "",
+ pdf_to_num(image[i].u.image.obj), pdf_to_gen(image[i].u.image.obj));
+
+ fz_free(ctx, cs);
+ fz_free(ctx, altcs);
+ }
+ printf("\n");
+ }
+
+ if (show & SHADINGS && shadings > 0)
+ {
+ printf("Shading patterns (%d):\n", shadings);
+ for (i = 0; i < shadings; i++)
+ {
+ char *shadingtype[] =
+ {
+ "",
+ "Function",
+ "Axial",
+ "Radial",
+ "Triangle mesh",
+ "Lattice",
+ "Coons patch",
+ "Tensor patch",
+ };
+
+ printf(PAGE_FMT "%s (%d %d R)\n",
+ shading[i].page,
+ pdf_to_num(shading[i].pageref), pdf_to_gen(shading[i].pageref),
+ shadingtype[pdf_to_int(shading[i].u.shading.type)],
+ pdf_to_num(shading[i].u.shading.obj), pdf_to_gen(shading[i].u.shading.obj));
+ }
+ printf("\n");
+ }
+
+ if (show & PATTERNS && patterns > 0)
+ {
+ printf("Patterns (%d):\n", patterns);
+ for (i = 0; i < patterns; i++)
+ {
+ if (pdf_to_int(pattern[i].u.pattern.type) == 1)
+ {
+ char *painttype[] =
+ {
+ "",
+ "Colored",
+ "Uncolored",
+ };
+ char *tilingtype[] =
+ {
+ "",
+ "Constant",
+ "No distortion",
+ "Constant/fast tiling",
+ };
+
+ printf(PAGE_FMT "Tiling %s %s (%d %d R)\n",
+ pattern[i].page,
+ pdf_to_num(pattern[i].pageref), pdf_to_gen(pattern[i].pageref),
+ painttype[pdf_to_int(pattern[i].u.pattern.paint)],
+ tilingtype[pdf_to_int(pattern[i].u.pattern.tiling)],
+ pdf_to_num(pattern[i].u.pattern.obj), pdf_to_gen(pattern[i].u.pattern.obj));
+ }
+ else
+ {
+ printf(PAGE_FMT "Shading %d %d R (%d %d R)\n",
+ pattern[i].page,
+ pdf_to_num(pattern[i].pageref), pdf_to_gen(pattern[i].pageref),
+ pdf_to_num(pattern[i].u.pattern.shading), pdf_to_gen(pattern[i].u.pattern.shading),
+ pdf_to_num(pattern[i].u.pattern.obj), pdf_to_gen(pattern[i].u.pattern.obj));
+ }
+ }
+ printf("\n");
+ }
+
+ if (show & XOBJS && forms > 0)
+ {
+ printf("Form xobjects (%d):\n", forms);
+ for (i = 0; i < forms; i++)
+ {
+ printf(PAGE_FMT "Form%s%s%s%s (%d %d R)\n",
+ form[i].page,
+ pdf_to_num(form[i].pageref), pdf_to_gen(form[i].pageref),
+ form[i].u.form.groupsubtype ? " " : "",
+ form[i].u.form.groupsubtype ? pdf_to_name(form[i].u.form.groupsubtype) : "",
+ form[i].u.form.groupsubtype ? " Group" : "",
+ form[i].u.form.reference ? " Reference" : "",
+ pdf_to_num(form[i].u.form.obj), pdf_to_gen(form[i].u.form.obj));
+ }
+ printf("\n");
+ }
+
+ if (show & XOBJS && psobjs > 0)
+ {
+ printf("Postscript xobjects (%d):\n", psobjs);
+ for (i = 0; i < psobjs; i++)
+ {
+ printf(PAGE_FMT "(%d %d R)\n",
+ psobj[i].page,
+ pdf_to_num(psobj[i].pageref), pdf_to_gen(psobj[i].pageref),
+ pdf_to_num(psobj[i].u.form.obj), pdf_to_gen(psobj[i].u.form.obj));
+ }
+ printf("\n");
+ }
+}
+
+static void
+showinfo(char *filename, int show, char *pagelist)
+{
+ int page, spage, epage;
+ char *spec, *dash;
+ int allpages;
+ int pagecount;
+
+ if (!xref)
+ infousage();
+
+ allpages = !strcmp(pagelist, "1-");
+
+ pagecount = pdf_count_pages(xref);
+ spec = fz_strsep(&pagelist, ",");
+ while (spec && pagecount)
+ {
+ dash = strchr(spec, '-');
+
+ if (dash == spec)
+ spage = epage = pagecount;
+ else
+ spage = epage = atoi(spec);
+
+ if (dash)
+ {
+ if (strlen(dash) > 1)
+ epage = atoi(dash + 1);
+ else
+ epage = pagecount;
+ }
+
+ if (spage > epage)
+ page = spage, spage = epage, epage = page;
+
+ spage = fz_clampi(spage, 1, pagecount);
+ epage = fz_clampi(epage, 1, pagecount);
+
+ if (allpages)
+ printf("Retrieving info from pages %d-%d...\n", spage, epage);
+ for (page = spage; page <= epage; page++)
+ {
+ gatherpageinfo(page, show);
+ if (!allpages)
+ {
+ printf("Page %d:\n", page);
+ printinfo(filename, show, page);
+ printf("\n");
+ }
+ }
+
+ spec = fz_strsep(&pagelist, ",");
+ }
+
+ if (allpages)
+ printinfo(filename, show, -1);
+}
+
+static int arg_is_page_range(const char *arg)
+{
+ int c;
+
+ while ((c = *arg++) != 0)
+ {
+ if ((c < '0' || c > '9') && (c != '-') && (c != ','))
+ return 0;
+ }
+ return 1;
+}
+
+int pdfinfo_main(int argc, char **argv)
+{
+ enum { NO_FILE_OPENED, NO_INFO_GATHERED, INFO_SHOWN } state;
+ char *filename = "";
+ char *password = "";
+ int show = ALL;
+ int c;
+
+ while ((c = fz_getopt(argc, argv, "mfispxd:")) != -1)
+ {
+ switch (c)
+ {
+ case 'm': if (show == ALL) show = DIMENSIONS; else show |= DIMENSIONS; break;
+ case 'f': if (show == ALL) show = FONTS; else show |= FONTS; break;
+ case 'i': if (show == ALL) show = IMAGES; else show |= IMAGES; break;
+ case 's': if (show == ALL) show = SHADINGS; else show |= SHADINGS; break;
+ case 'p': if (show == ALL) show = PATTERNS; else show |= PATTERNS; break;
+ case 'x': if (show == ALL) show = XOBJS; else show |= XOBJS; break;
+ case 'd': password = fz_optarg; break;
+ default:
+ infousage();
+ break;
+ }
+ }
+
+ if (fz_optind == argc)
+ infousage();
+
+ ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
+ if (!ctx)
+ {
+ fprintf(stderr, "cannot initialise context\n");
+ exit(1);
+ }
+
+ state = NO_FILE_OPENED;
+ while (fz_optind < argc)
+ {
+ if (state == NO_FILE_OPENED || !arg_is_page_range(argv[fz_optind]))
+ {
+ if (state == NO_INFO_GATHERED)
+ {
+ showinfo(filename, show, "1-");
+ closexref();
+ }
+
+ closexref();
+
+ filename = argv[fz_optind];
+ printf("%s:\n", filename);
+ xref = pdf_open_document_no_run(ctx, filename);
+ if (pdf_needs_password(xref))
+ if (!pdf_authenticate_password(xref, password))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", filename);
+ pagecount = pdf_count_pages(xref);
+
+ showglobalinfo();
+ state = NO_INFO_GATHERED;
+ }
+ else
+ {
+ showinfo(filename, show, argv[fz_optind]);
+ state = INFO_SHOWN;
+ }
+
+ fz_optind++;
+ }
+
+ if (state == NO_INFO_GATHERED)
+ showinfo(filename, show, "1-");
+
+ closexref();
+ fz_free_context(ctx);
+ return 0;
+}
diff --git a/source/tools/pdfposter.c b/source/tools/pdfposter.c
new file mode 100644
index 00000000..ed3a4bda
--- /dev/null
+++ b/source/tools/pdfposter.c
@@ -0,0 +1,183 @@
+/*
+ * PDF cleaning tool: general purpose pdf syntax washer.
+ *
+ * Rewrite PDF with pretty printed objects.
+ * Garbage collect unreachable objects.
+ * Inflate compressed streams.
+ * Create subset documents.
+ *
+ * TODO: linearize document for fast web view
+ */
+
+#include "mupdf/pdf.h"
+
+static int x_factor = 0;
+static int y_factor = 0;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "usage: mutool poster [options] input.pdf [output.pdf]\n"
+ "\t-p -\tpassword\n"
+ "\t-x\tx decimation factor\n"
+ "\t-y\ty decimation factor\n");
+ exit(1);
+}
+
+/*
+ * Recreate page tree to only retain specified pages.
+ */
+
+static void decimatepages(pdf_document *xref)
+{
+ pdf_obj *oldroot, *root, *pages, *kids, *parent;
+ fz_context *ctx = xref->ctx;
+ int num_pages = pdf_count_pages(xref);
+ int page, kidcount;
+
+ /* Keep only pages/type and (reduced) dest entries to avoid
+ * references to unretained pages */
+ oldroot = pdf_dict_gets(pdf_trailer(xref), "Root");
+ pages = pdf_dict_gets(oldroot, "Pages");
+
+ root = pdf_new_dict(ctx, 2);
+ pdf_dict_puts(root, "Type", pdf_dict_gets(oldroot, "Type"));
+ pdf_dict_puts(root, "Pages", pdf_dict_gets(oldroot, "Pages"));
+
+ pdf_update_object(xref, pdf_to_num(oldroot), root);
+
+ pdf_drop_obj(root);
+
+ /* Create a new kids array with only the pages we want to keep */
+ parent = pdf_new_indirect(ctx, pdf_to_num(pages), pdf_to_gen(pages), xref);
+ kids = pdf_new_array(ctx, 1);
+
+ kidcount = 0;
+ for (page=0; page < num_pages; page++)
+ {
+ pdf_page *page_details = pdf_load_page(xref, page);
+ int xf = x_factor, yf = y_factor;
+ int x, y;
+ float w = page_details->mediabox.x1 - page_details->mediabox.x0;
+ float h = page_details->mediabox.y1 - page_details->mediabox.y0;
+
+ if (xf == 0 && yf == 0)
+ {
+ /* Nothing specified, so split along the long edge */
+ if (w > h)
+ xf = 2, yf = 1;
+ else
+ xf = 1, yf = 2;
+ }
+ else if (xf == 0)
+ xf = 1;
+ else if (yf == 0)
+ yf = 1;
+
+ for (y = yf-1; y >= 0; y--)
+ {
+ for (x = 0; x < xf; x++)
+ {
+ pdf_obj *newpageobj, *newpageref, *newmediabox;
+ fz_rect mb;
+ int num;
+
+ newpageobj = pdf_copy_dict(ctx, xref->page_objs[page]);
+ num = pdf_create_object(xref);
+ pdf_update_object(xref, num, newpageobj);
+ newpageref = pdf_new_indirect(ctx, num, 0, xref);
+
+ newmediabox = pdf_new_array(ctx, 4);
+
+ mb.x0 = page_details->mediabox.x0 + (w/xf)*x;
+ if (x == xf-1)
+ mb.x1 = page_details->mediabox.x1;
+ else
+ mb.x1 = page_details->mediabox.x0 + (w/xf)*(x+1);
+ mb.y0 = page_details->mediabox.y0 + (h/yf)*y;
+ if (y == yf-1)
+ mb.y1 = page_details->mediabox.y1;
+ else
+ mb.y1 = page_details->mediabox.y0 + (h/yf)*(y+1);
+
+ pdf_array_push(newmediabox, pdf_new_real(ctx, mb.x0));
+ pdf_array_push(newmediabox, pdf_new_real(ctx, mb.y0));
+ pdf_array_push(newmediabox, pdf_new_real(ctx, mb.x1));
+ pdf_array_push(newmediabox, pdf_new_real(ctx, mb.y1));
+
+ pdf_dict_puts(newpageobj, "Parent", parent);
+ pdf_dict_puts(newpageobj, "MediaBox", newmediabox);
+
+ /* Store page object in new kids array */
+ pdf_array_push(kids, newpageref);
+
+ kidcount++;
+ }
+ }
+ }
+
+ pdf_drop_obj(parent);
+
+ /* Update page count and kids array */
+ pdf_dict_puts(pages, "Count", pdf_new_int(ctx, kidcount));
+ pdf_dict_puts(pages, "Kids", kids);
+ pdf_drop_obj(kids);
+}
+
+int pdfposter_main(int argc, char **argv)
+{
+ char *infile;
+ char *outfile = "out.pdf";
+ char *password = "";
+ int c;
+ fz_write_options opts;
+ pdf_document *xref;
+ fz_context *ctx;
+
+ opts.do_garbage = 0;
+ opts.do_expand = 0;
+ opts.do_ascii = 0;
+
+ while ((c = fz_getopt(argc, argv, "x:y:")) != -1)
+ {
+ switch (c)
+ {
+ case 'p': password = fz_optarg; break;
+ case 'x': x_factor = atoi(fz_optarg); break;
+ case 'y': y_factor = atoi(fz_optarg); break;
+ default: usage(); break;
+ }
+ }
+
+ if (argc - fz_optind < 1)
+ usage();
+
+ infile = argv[fz_optind++];
+
+ if (argc - fz_optind > 0 &&
+ (strstr(argv[fz_optind], ".pdf") || strstr(argv[fz_optind], ".PDF")))
+ {
+ outfile = argv[fz_optind++];
+ }
+
+ ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
+ if (!ctx)
+ {
+ fprintf(stderr, "cannot initialise context\n");
+ exit(1);
+ }
+
+ xref = pdf_open_document_no_run(ctx, infile);
+ if (pdf_needs_password(xref))
+ if (!pdf_authenticate_password(xref, password))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", infile);
+
+ /* Only retain the specified subset of the pages */
+ decimatepages(xref);
+
+ pdf_write_document(xref, outfile, &opts);
+
+ pdf_close_document(xref);
+ fz_free_context(ctx);
+ return 0;
+}
diff --git a/source/tools/pdfshow.c b/source/tools/pdfshow.c
new file mode 100644
index 00000000..78e3fd08
--- /dev/null
+++ b/source/tools/pdfshow.c
@@ -0,0 +1,251 @@
+/*
+ * pdfshow -- the ultimate pdf debugging tool
+ */
+
+#include "mupdf/pdf.h"
+
+static pdf_document *doc = NULL;
+static fz_context *ctx = NULL;
+static int showbinary = 0;
+static int showdecode = 1;
+static int showcolumn;
+
+static void usage(void)
+{
+ fprintf(stderr, "usage: mutool show [options] file.pdf [grepable] [xref] [trailer] [pagetree] [object numbers]\n");
+ fprintf(stderr, "\t-b\tprint streams as binary data\n");
+ fprintf(stderr, "\t-e\tprint encoded streams (don't decode)\n");
+ fprintf(stderr, "\t-p\tpassword\n");
+ exit(1);
+}
+
+static void showtrailer(void)
+{
+ if (!doc)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no file specified");
+ printf("trailer\n");
+ pdf_fprint_obj(stdout, pdf_trailer(doc), 0);
+ printf("\n");
+}
+
+static void showencrypt(void)
+{
+ pdf_obj *encrypt;
+
+ if (!doc)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no file specified");
+ encrypt = pdf_dict_gets(pdf_trailer(doc), "Encrypt");
+ if (!encrypt)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "document not encrypted");
+ printf("encryption dictionary\n");
+ pdf_fprint_obj(stdout, pdf_resolve_indirect(encrypt), 0);
+ printf("\n");
+}
+
+static void showxref(void)
+{
+ if (!doc)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no file specified");
+ pdf_print_xref(doc);
+ printf("\n");
+}
+
+static void showpagetree(void)
+{
+ pdf_obj *ref;
+ int count;
+ int i;
+
+ if (!doc)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no file specified");
+
+ count = pdf_count_pages(doc);
+ for (i = 0; i < count; i++)
+ {
+ ref = doc->page_refs[i];
+ printf("page %d = %d %d R\n", i + 1, pdf_to_num(ref), pdf_to_gen(ref));
+ }
+ printf("\n");
+}
+
+static void showsafe(unsigned char *buf, int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ if (buf[i] == '\r' || buf[i] == '\n') {
+ putchar('\n');
+ showcolumn = 0;
+ }
+ else if (buf[i] < 32 || buf[i] > 126) {
+ putchar('.');
+ showcolumn ++;
+ }
+ else {
+ putchar(buf[i]);
+ showcolumn ++;
+ }
+ if (showcolumn == 79) {
+ putchar('\n');
+ showcolumn = 0;
+ }
+ }
+}
+
+static void showstream(int num, int gen)
+{
+ fz_stream *stm;
+ unsigned char buf[2048];
+ int n;
+
+ showcolumn = 0;
+
+ if (showdecode)
+ stm = pdf_open_stream(doc, num, gen);
+ else
+ stm = pdf_open_raw_stream(doc, num, gen);
+
+ while (1)
+ {
+ n = fz_read(stm, buf, sizeof buf);
+ if (n == 0)
+ break;
+ if (showbinary)
+ fwrite(buf, 1, n, stdout);
+ else
+ showsafe(buf, n);
+ }
+
+ fz_close(stm);
+}
+
+static void showobject(int num, int gen)
+{
+ pdf_obj *obj;
+
+ if (!doc)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no file specified");
+
+ obj = pdf_load_object(doc, num, gen);
+
+ if (pdf_is_stream(doc, num, gen))
+ {
+ if (showbinary)
+ {
+ showstream(num, gen);
+ }
+ else
+ {
+ printf("%d %d obj\n", num, gen);
+ pdf_fprint_obj(stdout, obj, 0);
+ printf("stream\n");
+ showstream(num, gen);
+ printf("endstream\n");
+ printf("endobj\n\n");
+ }
+ }
+ else
+ {
+ printf("%d %d obj\n", num, gen);
+ pdf_fprint_obj(stdout, obj, 0);
+ printf("endobj\n\n");
+ }
+
+ pdf_drop_obj(obj);
+}
+
+static void showgrep(char *filename)
+{
+ pdf_obj *obj;
+ int i, len;
+
+ len = pdf_count_objects(doc);
+ for (i = 0; i < len; i++)
+ {
+ pdf_xref_entry *entry = pdf_get_xref_entry(doc, i);
+ if (entry->type == 'n' || entry->type == 'o')
+ {
+ fz_try(ctx)
+ {
+ obj = pdf_load_object(doc, i, 0);
+ }
+ fz_catch(ctx)
+ {
+ fz_warn(ctx, "skipping object (%d 0 R)", i);
+ continue;
+ }
+
+ pdf_sort_dict(obj);
+
+ printf("%s:%d: ", filename, i);
+ pdf_fprint_obj(stdout, obj, 1);
+
+ pdf_drop_obj(obj);
+ }
+ }
+
+ printf("%s:trailer: ", filename);
+ pdf_fprint_obj(stdout, pdf_trailer(doc), 1);
+}
+
+int pdfshow_main(int argc, char **argv)
+{
+ char *password = NULL; /* don't throw errors if encrypted */
+ char *filename;
+ int c;
+
+ while ((c = fz_getopt(argc, argv, "p:be")) != -1)
+ {
+ switch (c)
+ {
+ case 'p': password = fz_optarg; break;
+ case 'b': showbinary = 1; break;
+ case 'e': showdecode = 0; break;
+ default: usage(); break;
+ }
+ }
+
+ if (fz_optind == argc)
+ usage();
+
+ filename = argv[fz_optind++];
+
+ ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
+ if (!ctx)
+ {
+ fprintf(stderr, "cannot initialise context\n");
+ exit(1);
+ }
+
+ fz_var(doc);
+ fz_try(ctx)
+ {
+ doc = pdf_open_document_no_run(ctx, filename);
+ if (pdf_needs_password(doc))
+ if (!pdf_authenticate_password(doc, password))
+ fz_warn(ctx, "cannot authenticate password: %s", filename);
+
+ if (fz_optind == argc)
+ showtrailer();
+
+ while (fz_optind < argc)
+ {
+ switch (argv[fz_optind][0])
+ {
+ case 't': showtrailer(); break;
+ case 'e': showencrypt(); break;
+ case 'x': showxref(); break;
+ case 'p': showpagetree(); break;
+ case 'g': showgrep(filename); break;
+ default: showobject(atoi(argv[fz_optind]), 0); break;
+ }
+ fz_optind++;
+ }
+ }
+ fz_catch(ctx)
+ {
+ }
+
+ pdf_close_document(doc);
+ fz_free_context(ctx);
+ return 0;
+}
diff --git a/source/xps/xps-common.c b/source/xps/xps-common.c
new file mode 100644
index 00000000..ec16d879
--- /dev/null
+++ b/source/xps/xps-common.c
@@ -0,0 +1,311 @@
+#include "mupdf/xps.h"
+
+static inline int unhex(int a)
+{
+ if (a >= 'A' && a <= 'F') return a - 'A' + 0xA;
+ if (a >= 'a' && a <= 'f') return a - 'a' + 0xA;
+ if (a >= '0' && a <= '9') return a - '0';
+ return 0;
+}
+
+fz_xml *
+xps_lookup_alternate_content(fz_xml *node)
+{
+ for (node = fz_xml_down(node); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "mc:Choice") && fz_xml_att(node, "Requires"))
+ {
+ char list[64];
+ char *next = list, *item;
+ fz_strlcpy(list, fz_xml_att(node, "Requires"), sizeof(list));
+ while ((item = fz_strsep(&next, " \t\r\n")) && (!*item || !strcmp(item, "xps")));
+ if (!item)
+ return fz_xml_down(node);
+ }
+ else if (!strcmp(fz_xml_tag(node), "mc:Fallback"))
+ return fz_xml_down(node);
+ }
+ return NULL;
+}
+
+void
+xps_parse_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict, fz_xml *node)
+{
+ if (doc->cookie && doc->cookie->abort)
+ return;
+ /* SolidColorBrushes are handled in a special case and will never show up here */
+ if (!strcmp(fz_xml_tag(node), "ImageBrush"))
+ xps_parse_image_brush(doc, ctm, area, base_uri, dict, node);
+ else if (!strcmp(fz_xml_tag(node), "VisualBrush"))
+ xps_parse_visual_brush(doc, ctm, area, base_uri, dict, node);
+ else if (!strcmp(fz_xml_tag(node), "LinearGradientBrush"))
+ xps_parse_linear_gradient_brush(doc, ctm, area, base_uri, dict, node);
+ else if (!strcmp(fz_xml_tag(node), "RadialGradientBrush"))
+ xps_parse_radial_gradient_brush(doc, ctm, area, base_uri, dict, node);
+ else
+ fz_warn(doc->ctx, "unknown brush tag: %s", fz_xml_tag(node));
+}
+
+void
+xps_parse_element(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict, fz_xml *node)
+{
+ if (doc->cookie && doc->cookie->abort)
+ return;
+ if (!strcmp(fz_xml_tag(node), "Path"))
+ xps_parse_path(doc, ctm, base_uri, dict, node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs"))
+ xps_parse_glyphs(doc, ctm, base_uri, dict, node);
+ if (!strcmp(fz_xml_tag(node), "Canvas"))
+ xps_parse_canvas(doc, ctm, area, base_uri, dict, node);
+ if (!strcmp(fz_xml_tag(node), "mc:AlternateContent"))
+ {
+ node = xps_lookup_alternate_content(node);
+ if (node)
+ xps_parse_element(doc, ctm, area, base_uri, dict, node);
+ }
+ /* skip unknown tags (like Foo.Resources and similar) */
+}
+
+void
+xps_begin_opacity(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict,
+ char *opacity_att, fz_xml *opacity_mask_tag)
+{
+ float opacity;
+
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+
+ opacity = 1;
+ if (opacity_att)
+ opacity = fz_atof(opacity_att);
+
+ if (opacity_mask_tag && !strcmp(fz_xml_tag(opacity_mask_tag), "SolidColorBrush"))
+ {
+ char *scb_opacity_att = fz_xml_att(opacity_mask_tag, "Opacity");
+ char *scb_color_att = fz_xml_att(opacity_mask_tag, "Color");
+ if (scb_opacity_att)
+ opacity = opacity * fz_atof(scb_opacity_att);
+ if (scb_color_att)
+ {
+ fz_colorspace *colorspace;
+ float samples[32];
+ xps_parse_color(doc, base_uri, scb_color_att, &colorspace, samples);
+ opacity = opacity * samples[0];
+ }
+ opacity_mask_tag = NULL;
+ }
+
+ if (doc->opacity_top + 1 < nelem(doc->opacity))
+ {
+ doc->opacity[doc->opacity_top + 1] = doc->opacity[doc->opacity_top] * opacity;
+ doc->opacity_top++;
+ }
+
+ if (opacity_mask_tag)
+ {
+ fz_begin_mask(doc->dev, area, 0, NULL, NULL);
+ xps_parse_brush(doc, ctm, area, base_uri, dict, opacity_mask_tag);
+ fz_end_mask(doc->dev);
+ }
+}
+
+void
+xps_end_opacity(xps_document *doc, char *base_uri, xps_resource *dict,
+ char *opacity_att, fz_xml *opacity_mask_tag)
+{
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+
+ if (doc->opacity_top > 0)
+ doc->opacity_top--;
+
+ if (opacity_mask_tag)
+ {
+ if (strcmp(fz_xml_tag(opacity_mask_tag), "SolidColorBrush"))
+ fz_pop_clip(doc->dev);
+ }
+}
+
+void
+xps_parse_render_transform(xps_document *doc, char *transform, fz_matrix *matrix)
+{
+ float args[6];
+ char *s = transform;
+ int i;
+
+ args[0] = 1; args[1] = 0;
+ args[2] = 0; args[3] = 1;
+ args[4] = 0; args[5] = 0;
+
+ for (i = 0; i < 6 && *s; i++)
+ {
+ args[i] = fz_atof(s);
+ while (*s && *s != ',')
+ s++;
+ if (*s == ',')
+ s++;
+ }
+
+ matrix->a = args[0]; matrix->b = args[1];
+ matrix->c = args[2]; matrix->d = args[3];
+ matrix->e = args[4]; matrix->f = args[5];
+}
+
+void
+xps_parse_matrix_transform(xps_document *doc, fz_xml *root, fz_matrix *matrix)
+{
+ char *transform;
+
+ *matrix = fz_identity;
+
+ if (!strcmp(fz_xml_tag(root), "MatrixTransform"))
+ {
+ transform = fz_xml_att(root, "Matrix");
+ if (transform)
+ xps_parse_render_transform(doc, transform, matrix);
+ }
+}
+
+void
+xps_parse_rectangle(xps_document *doc, char *text, fz_rect *rect)
+{
+ float args[4];
+ char *s = text;
+ int i;
+
+ args[0] = 0; args[1] = 0;
+ args[2] = 1; args[3] = 1;
+
+ for (i = 0; i < 4 && *s; i++)
+ {
+ args[i] = fz_atof(s);
+ while (*s && *s != ',')
+ s++;
+ if (*s == ',')
+ s++;
+ }
+
+ rect->x0 = args[0];
+ rect->y0 = args[1];
+ rect->x1 = args[0] + args[2];
+ rect->y1 = args[1] + args[3];
+}
+
+static int count_commas(char *s)
+{
+ int n = 0;
+ while (*s)
+ {
+ if (*s == ',')
+ n ++;
+ s ++;
+ }
+ return n;
+}
+
+void
+xps_parse_color(xps_document *doc, char *base_uri, char *string,
+ fz_colorspace **csp, float *samples)
+{
+ char *p;
+ int i, n;
+ char buf[1024];
+ char *profile;
+
+ *csp = fz_device_rgb(doc->ctx);
+
+ samples[0] = 1;
+ samples[1] = 0;
+ samples[2] = 0;
+ samples[3] = 0;
+
+ if (string[0] == '#')
+ {
+ if (strlen(string) == 9)
+ {
+ samples[0] = unhex(string[1]) * 16 + unhex(string[2]);
+ samples[1] = unhex(string[3]) * 16 + unhex(string[4]);
+ samples[2] = unhex(string[5]) * 16 + unhex(string[6]);
+ samples[3] = unhex(string[7]) * 16 + unhex(string[8]);
+ }
+ else
+ {
+ samples[0] = 255;
+ samples[1] = unhex(string[1]) * 16 + unhex(string[2]);
+ samples[2] = unhex(string[3]) * 16 + unhex(string[4]);
+ samples[3] = unhex(string[5]) * 16 + unhex(string[6]);
+ }
+
+ samples[0] /= 255;
+ samples[1] /= 255;
+ samples[2] /= 255;
+ samples[3] /= 255;
+ }
+
+ else if (string[0] == 's' && string[1] == 'c' && string[2] == '#')
+ {
+ if (count_commas(string) == 2)
+ sscanf(string, "sc#%g,%g,%g", samples + 1, samples + 2, samples + 3);
+ if (count_commas(string) == 3)
+ sscanf(string, "sc#%g,%g,%g,%g", samples, samples + 1, samples + 2, samples + 3);
+ }
+
+ else if (strstr(string, "ContextColor ") == string)
+ {
+ /* Crack the string for profile name and sample values */
+ fz_strlcpy(buf, string, sizeof buf);
+
+ profile = strchr(buf, ' ');
+ if (!profile)
+ {
+ fz_warn(doc->ctx, "cannot find icc profile uri in '%s'", string);
+ return;
+ }
+
+ *profile++ = 0;
+ p = strchr(profile, ' ');
+ if (!p)
+ {
+ fz_warn(doc->ctx, "cannot find component values in '%s'", profile);
+ return;
+ }
+
+ *p++ = 0;
+ n = count_commas(p) + 1;
+ i = 0;
+ while (i < n)
+ {
+ samples[i++] = fz_atof(p);
+ p = strchr(p, ',');
+ if (!p)
+ break;
+ p ++;
+ if (*p == ' ')
+ p ++;
+ }
+ while (i < n)
+ {
+ samples[i++] = 0;
+ }
+
+ /* TODO: load ICC profile */
+ switch (n)
+ {
+ case 2: *csp = fz_device_gray(doc->ctx); break;
+ case 4: *csp = fz_device_rgb(doc->ctx); break;
+ case 5: *csp = fz_device_cmyk(doc->ctx); break;
+ default: *csp = fz_device_gray(doc->ctx); break;
+ }
+ }
+}
+
+void
+xps_set_color(xps_document *doc, fz_colorspace *colorspace, float *samples)
+{
+ int i;
+ doc->colorspace = colorspace;
+ for (i = 0; i < colorspace->n; i++)
+ doc->color[i] = samples[i + 1];
+ doc->alpha = samples[0] * doc->opacity[doc->opacity_top];
+}
diff --git a/source/xps/xps-doc.c b/source/xps/xps-doc.c
new file mode 100644
index 00000000..9953cb37
--- /dev/null
+++ b/source/xps/xps-doc.c
@@ -0,0 +1,534 @@
+#include "mupdf/xps.h"
+
+#define REL_START_PART \
+ "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
+#define REL_DOC_STRUCTURE \
+ "http://schemas.microsoft.com/xps/2005/06/documentstructure"
+#define REL_REQUIRED_RESOURCE \
+ "http://schemas.microsoft.com/xps/2005/06/required-resource"
+#define REL_REQUIRED_RESOURCE_RECURSIVE \
+ "http://schemas.microsoft.com/xps/2005/06/required-resource#recursive"
+
+#define REL_START_PART_OXPS \
+ "http://schemas.openxps.org/oxps/v1.0/fixedrepresentation"
+#define REL_DOC_STRUCTURE_OXPS \
+ "http://schemas.openxps.org/oxps/v1.0/documentstructure"
+
+static void
+xps_rels_for_part(char *buf, char *name, int buflen)
+{
+ char *p, *basename;
+ p = strrchr(name, '/');
+ basename = p ? p + 1 : name;
+ fz_strlcpy(buf, name, buflen);
+ p = strrchr(buf, '/');
+ if (p) *p = 0;
+ fz_strlcat(buf, "/_rels/", buflen);
+ fz_strlcat(buf, basename, buflen);
+ fz_strlcat(buf, ".rels", buflen);
+}
+
+/*
+ * The FixedDocumentSequence and FixedDocument parts determine
+ * which parts correspond to actual pages, and the page order.
+ */
+
+void
+xps_print_page_list(xps_document *doc)
+{
+ xps_fixdoc *fixdoc = doc->first_fixdoc;
+ xps_page *page = doc->first_page;
+
+ if (doc->start_part)
+ printf("start part %s\n", doc->start_part);
+
+ while (fixdoc)
+ {
+ printf("fixdoc %s\n", fixdoc->name);
+ fixdoc = fixdoc->next;
+ }
+
+ while (page)
+ {
+ printf("page[%d] %s w=%d h=%d\n", page->number, page->name, page->width, page->height);
+ page = page->next;
+ }
+}
+
+static void
+xps_add_fixed_document(xps_document *doc, char *name)
+{
+ xps_fixdoc *fixdoc;
+
+ /* Check for duplicates first */
+ for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ if (!strcmp(fixdoc->name, name))
+ return;
+
+ fixdoc = fz_malloc_struct(doc->ctx, xps_fixdoc);
+ fixdoc->name = fz_strdup(doc->ctx, name);
+ fixdoc->outline = NULL;
+ fixdoc->next = NULL;
+
+ if (!doc->first_fixdoc)
+ {
+ doc->first_fixdoc = fixdoc;
+ doc->last_fixdoc = fixdoc;
+ }
+ else
+ {
+ doc->last_fixdoc->next = fixdoc;
+ doc->last_fixdoc = fixdoc;
+ }
+}
+
+void
+xps_add_link(xps_document *doc, const fz_rect *area, char *base_uri, char *target_uri)
+{
+ int len;
+ char *buffer = NULL;
+ char *uri;
+ xps_target *target;
+ fz_link_dest dest;
+ fz_link *link;
+ fz_context *ctx = doc->ctx;
+
+ fz_var(buffer);
+
+ if (doc->current_page == NULL || doc->current_page->links_resolved)
+ return;
+
+ fz_try(ctx)
+ {
+ len = 2 + (base_uri ? strlen(base_uri) : 0) +
+ (target_uri ? strlen(target_uri) : 0);
+ buffer = fz_malloc(doc->ctx, len);
+ xps_resolve_url(buffer, base_uri, target_uri, len);
+ if (xps_url_is_remote(buffer))
+ {
+ dest.kind = FZ_LINK_URI;
+ dest.ld.uri.is_map = 0;
+ dest.ld.uri.uri = buffer;
+ buffer = NULL;
+ }
+ else
+ {
+ uri = buffer;
+
+ /* FIXME: This won't work for remote docs */
+ /* Skip until we find the fragment marker */
+ while (*uri && *uri != '#')
+ uri++;
+ if (*uri == '#')
+ uri++;
+
+ for (target = doc->target; target; target = target->next)
+ if (!strcmp(target->name, uri))
+ break;
+
+ if (target == NULL)
+ break;
+
+ dest.kind = FZ_LINK_GOTO;
+ dest.ld.gotor.flags = 0;
+ dest.ld.gotor.lt.x = 0;
+ dest.ld.gotor.lt.y = 0;
+ dest.ld.gotor.rb.x = 0;
+ dest.ld.gotor.rb.y = 0;
+ dest.ld.gotor.page = target->page;
+ dest.ld.gotor.file_spec = NULL;
+ dest.ld.gotor.new_window = 0;
+ }
+
+ link = fz_new_link(doc->ctx, area, dest);
+ link->next = doc->current_page->links;
+ doc->current_page->links = link;
+ }
+ fz_always(ctx)
+ {
+ fz_free(doc->ctx, buffer);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+fz_link *
+xps_load_links(xps_document *doc, xps_page *page)
+{
+ if (!page->links_resolved)
+ fz_warn(doc->ctx, "xps_load_links before page has been executed!");
+ return fz_keep_link(doc->ctx, page->links);
+}
+
+static void
+xps_add_fixed_page(xps_document *doc, char *name, int width, int height)
+{
+ xps_page *page;
+
+ /* Check for duplicates first */
+ for (page = doc->first_page; page; page = page->next)
+ if (!strcmp(page->name, name))
+ return;
+
+ page = fz_malloc_struct(doc->ctx, xps_page);
+ page->name = fz_strdup(doc->ctx, name);
+ page->number = doc->page_count++;
+ page->width = width;
+ page->height = height;
+ page->links = NULL;
+ page->links_resolved = 0;
+ page->root = NULL;
+ page->next = NULL;
+
+ if (!doc->first_page)
+ {
+ doc->first_page = page;
+ doc->last_page = page;
+ }
+ else
+ {
+ doc->last_page->next = page;
+ doc->last_page = page;
+ }
+}
+
+static void
+xps_add_link_target(xps_document *doc, char *name)
+{
+ xps_page *page = doc->last_page;
+ xps_target *target = fz_malloc_struct(doc->ctx, xps_target);
+ target->name = fz_strdup(doc->ctx, name);
+ target->page = page->number;
+ target->next = doc->target;
+ doc->target = target;
+}
+
+int
+xps_lookup_link_target(xps_document *doc, char *target_uri)
+{
+ xps_target *target;
+ char *needle = strrchr(target_uri, '#');
+ needle = needle ? needle + 1 : target_uri;
+ for (target = doc->target; target; target = target->next)
+ if (!strcmp(target->name, needle))
+ return target->page;
+ return 0;
+}
+
+static void
+xps_free_link_targets(xps_document *doc)
+{
+ xps_target *target = doc->target, *next;
+ while (target)
+ {
+ next = target->next;
+ fz_free(doc->ctx, target->name);
+ fz_free(doc->ctx, target);
+ target = next;
+ }
+}
+
+static void
+xps_free_fixed_pages(xps_document *doc)
+{
+ xps_page *page = doc->first_page;
+ while (page)
+ {
+ xps_page *next = page->next;
+ xps_free_page(doc, page);
+ fz_drop_link(doc->ctx, page->links);
+ fz_free(doc->ctx, page->name);
+ fz_free(doc->ctx, page);
+ page = next;
+ }
+ doc->first_page = NULL;
+ doc->last_page = NULL;
+}
+
+static void
+xps_free_fixed_documents(xps_document *doc)
+{
+ xps_fixdoc *fixdoc = doc->first_fixdoc;
+ while (fixdoc)
+ {
+ xps_fixdoc *next = fixdoc->next;
+ fz_free(doc->ctx, fixdoc->name);
+ fz_free(doc->ctx, fixdoc->outline);
+ fz_free(doc->ctx, fixdoc);
+ fixdoc = next;
+ }
+ doc->first_fixdoc = NULL;
+ doc->last_fixdoc = NULL;
+}
+
+void
+xps_free_page_list(xps_document *doc)
+{
+ xps_free_fixed_documents(doc);
+ xps_free_fixed_pages(doc);
+ xps_free_link_targets(doc);
+}
+
+/*
+ * Parse the fixed document sequence structure and _rels/.rels to find the start part.
+ */
+
+static void
+xps_parse_metadata_imp(xps_document *doc, fz_xml *item, xps_fixdoc *fixdoc)
+{
+ while (item)
+ {
+ if (!strcmp(fz_xml_tag(item), "Relationship"))
+ {
+ char *target = fz_xml_att(item, "Target");
+ char *type = fz_xml_att(item, "Type");
+ if (target && type)
+ {
+ char tgtbuf[1024];
+ xps_resolve_url(tgtbuf, doc->base_uri, target, sizeof tgtbuf);
+ if (!strcmp(type, REL_START_PART) || !strcmp(type, REL_START_PART_OXPS))
+ doc->start_part = fz_strdup(doc->ctx, tgtbuf);
+ if ((!strcmp(type, REL_DOC_STRUCTURE) || !strcmp(type, REL_DOC_STRUCTURE_OXPS)) && fixdoc)
+ fixdoc->outline = fz_strdup(doc->ctx, tgtbuf);
+ if (!fz_xml_att(item, "Id"))
+ fz_warn(doc->ctx, "missing relationship id for %s", target);
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(item), "DocumentReference"))
+ {
+ char *source = fz_xml_att(item, "Source");
+ if (source)
+ {
+ char srcbuf[1024];
+ xps_resolve_url(srcbuf, doc->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_document(doc, srcbuf);
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(item), "PageContent"))
+ {
+ char *source = fz_xml_att(item, "Source");
+ char *width_att = fz_xml_att(item, "Width");
+ char *height_att = fz_xml_att(item, "Height");
+ int width = width_att ? atoi(width_att) : 0;
+ int height = height_att ? atoi(height_att) : 0;
+ if (source)
+ {
+ char srcbuf[1024];
+ xps_resolve_url(srcbuf, doc->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_page(doc, srcbuf, width, height);
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(item), "LinkTarget"))
+ {
+ char *name = fz_xml_att(item, "Name");
+ if (name)
+ xps_add_link_target(doc, name);
+ }
+
+ xps_parse_metadata_imp(doc, fz_xml_down(item), fixdoc);
+
+ item = fz_xml_next(item);
+ }
+}
+
+static void
+xps_parse_metadata(xps_document *doc, xps_part *part, xps_fixdoc *fixdoc)
+{
+ fz_xml *root;
+ char buf[1024];
+ char *s;
+
+ /* Save directory name part */
+ fz_strlcpy(buf, part->name, sizeof buf);
+ s = strrchr(buf, '/');
+ if (s)
+ s[0] = 0;
+
+ /* _rels parts are voodoo: their URI references are from
+ * the part they are associated with, not the actual _rels
+ * part being parsed.
+ */
+ s = strstr(buf, "/_rels");
+ if (s)
+ *s = 0;
+
+ doc->base_uri = buf;
+ doc->part_uri = part->name;
+
+ root = fz_parse_xml(doc->ctx, part->data, part->size);
+ xps_parse_metadata_imp(doc, root, fixdoc);
+ fz_free_xml(doc->ctx, root);
+
+ doc->base_uri = NULL;
+ doc->part_uri = NULL;
+}
+
+static void
+xps_read_and_process_metadata_part(xps_document *doc, char *name, xps_fixdoc *fixdoc)
+{
+ fz_context *ctx = doc->ctx;
+ xps_part *part;
+
+ if (!xps_has_part(doc, name))
+ return;
+
+ part = xps_read_part(doc, name);
+ fz_try(ctx)
+ {
+ xps_parse_metadata(doc, part, fixdoc);
+ }
+ fz_always(ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+xps_read_page_list(xps_document *doc)
+{
+ xps_fixdoc *fixdoc;
+
+ xps_read_and_process_metadata_part(doc, "/_rels/.rels", NULL);
+
+ if (!doc->start_part)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find fixed document sequence start part");
+
+ xps_read_and_process_metadata_part(doc, doc->start_part, NULL);
+
+ for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ {
+ char relbuf[1024];
+ fz_try(doc->ctx)
+ {
+ xps_rels_for_part(relbuf, fixdoc->name, sizeof relbuf);
+ xps_read_and_process_metadata_part(doc, relbuf, fixdoc);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot process FixedDocument rels part");
+ }
+ xps_read_and_process_metadata_part(doc, fixdoc->name, fixdoc);
+ }
+}
+
+int
+xps_count_pages(xps_document *doc)
+{
+ return doc->page_count;
+}
+
+static void
+xps_load_fixed_page(xps_document *doc, xps_page *page)
+{
+ xps_part *part;
+ fz_xml *root;
+ char *width_att;
+ char *height_att;
+ fz_context *ctx = doc->ctx;
+
+ part = xps_read_part(doc, page->name);
+ fz_try(ctx)
+ {
+ root = fz_parse_xml(doc->ctx, part->data, part->size);
+ }
+ fz_always(ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ root = NULL;
+ }
+ if (!root)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing root element");
+
+ if (!strcmp(fz_xml_tag(root), "mc:AlternateContent"))
+ {
+ fz_xml *node = xps_lookup_alternate_content(root);
+ if (!node)
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing alternate root element");
+ }
+ fz_detach_xml(node);
+ fz_free_xml(doc->ctx, root);
+ root = node;
+ }
+
+ if (strcmp(fz_xml_tag(root), "FixedPage"))
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "expected FixedPage element");
+ }
+
+ width_att = fz_xml_att(root, "Width");
+ if (!width_att)
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Width");
+ }
+
+ height_att = fz_xml_att(root, "Height");
+ if (!height_att)
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Height");
+ }
+
+ page->width = atoi(width_att);
+ page->height = atoi(height_att);
+ page->root = root;
+}
+
+xps_page *
+xps_load_page(xps_document *doc, int number)
+{
+ xps_page *page;
+ int n = 0;
+
+ for (page = doc->first_page; page; page = page->next)
+ {
+ if (n == number)
+ {
+ doc->current_page = page;
+ if (!page->root)
+ xps_load_fixed_page(doc, page);
+ return page;
+ }
+ n ++;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find page %d", number + 1);
+ return NULL;
+}
+
+fz_rect *
+xps_bound_page(xps_document *doc, xps_page *page, fz_rect *bounds)
+{
+ bounds->x0 = bounds->y0 = 0;
+ bounds->x1 = page->width * 72.0f / 96.0f;
+ bounds->y1 = page->height * 72.0f / 96.0f;
+ return bounds;
+}
+
+void
+xps_free_page(xps_document *doc, xps_page *page)
+{
+ if (page == NULL)
+ return;
+ /* only free the XML contents */
+ if (page->root)
+ fz_free_xml(doc->ctx, page->root);
+ page->root = NULL;
+}
diff --git a/source/xps/xps-glyphs.c b/source/xps/xps-glyphs.c
new file mode 100644
index 00000000..72f37ca1
--- /dev/null
+++ b/source/xps/xps-glyphs.c
@@ -0,0 +1,623 @@
+#include "mupdf/xps.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_ADVANCES_H
+
+static inline int ishex(int a)
+{
+ return (a >= 'A' && a <= 'F') ||
+ (a >= 'a' && a <= 'f') ||
+ (a >= '0' && a <= '9');
+}
+
+static inline int unhex(int a)
+{
+ if (a >= 'A' && a <= 'F') return a - 'A' + 0xA;
+ if (a >= 'a' && a <= 'f') return a - 'a' + 0xA;
+ if (a >= '0' && a <= '9') return a - '0';
+ return 0;
+}
+
+int
+xps_count_font_encodings(fz_font *font)
+{
+ FT_Face face = font->ft_face;
+ return face->num_charmaps;
+}
+
+void
+xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid)
+{
+ FT_Face face = font->ft_face;
+ *pid = face->charmaps[idx]->platform_id;
+ *eid = face->charmaps[idx]->encoding_id;
+}
+
+void
+xps_select_font_encoding(fz_font *font, int idx)
+{
+ FT_Face face = font->ft_face;
+ FT_Set_Charmap(face, face->charmaps[idx]);
+}
+
+int
+xps_encode_font_char(fz_font *font, int code)
+{
+ FT_Face face = font->ft_face;
+ int gid = FT_Get_Char_Index(face, code);
+ if (gid == 0 && face->charmap && face->charmap->platform_id == 3 && face->charmap->encoding_id == 0)
+ gid = FT_Get_Char_Index(face, 0xF000 | code);
+ return gid;
+}
+
+void
+xps_measure_font_glyph(xps_document *doc, fz_font *font, int gid, xps_glyph_metrics *mtx)
+{
+ int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM;
+ FT_Face face = font->ft_face;
+ FT_Fixed hadv, vadv;
+ fz_context *ctx = doc->ctx;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ FT_Set_Char_Size(face, 64, 64, 72, 72);
+ FT_Get_Advance(face, gid, mask, &hadv);
+ FT_Get_Advance(face, gid, mask | FT_LOAD_VERTICAL_LAYOUT, &vadv);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+
+ mtx->hadv = hadv / 65536.0f;
+ mtx->vadv = vadv / 65536.0f;
+ mtx->vorg = face->ascender / (float) face->units_per_EM;
+}
+
+static fz_font *
+xps_lookup_font(xps_document *doc, char *name)
+{
+ xps_font_cache *cache;
+ for (cache = doc->font_table; cache; cache = cache->next)
+ if (!xps_strcasecmp(cache->name, name))
+ return fz_keep_font(doc->ctx, cache->font);
+ return NULL;
+}
+
+static void
+xps_insert_font(xps_document *doc, char *name, fz_font *font)
+{
+ xps_font_cache *cache = fz_malloc_struct(doc->ctx, xps_font_cache);
+ cache->name = fz_strdup(doc->ctx, name);
+ cache->font = fz_keep_font(doc->ctx, font);
+ cache->next = doc->font_table;
+ doc->font_table = cache;
+}
+
+/*
+ * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the
+ * data with the GUID in the fontname.
+ */
+static void
+xps_deobfuscate_font_resource(xps_document *doc, xps_part *part)
+{
+ unsigned char buf[33];
+ unsigned char key[16];
+ char *p;
+ int i;
+
+ p = strrchr(part->name, '/');
+ if (!p)
+ p = part->name;
+
+ for (i = 0; i < 32 && *p; p++)
+ {
+ if (ishex(*p))
+ buf[i++] = *p;
+ }
+ buf[i] = 0;
+
+ if (i != 32)
+ {
+ fz_warn(doc->ctx, "cannot extract GUID from obfuscated font part name");
+ return;
+ }
+
+ for (i = 0; i < 16; i++)
+ key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]);
+
+ for (i = 0; i < 16; i++)
+ {
+ part->data[i] ^= key[15-i];
+ part->data[i+16] ^= key[15-i];
+ }
+}
+
+static void
+xps_select_best_font_encoding(xps_document *doc, fz_font *font)
+{
+ static struct { int pid, eid; } xps_cmap_list[] =
+ {
+ { 3, 10 }, /* Unicode with surrogates */
+ { 3, 1 }, /* Unicode without surrogates */
+ { 3, 5 }, /* Wansung */
+ { 3, 4 }, /* Big5 */
+ { 3, 3 }, /* Prc */
+ { 3, 2 }, /* ShiftJis */
+ { 3, 0 }, /* Symbol */
+ { 1, 0 },
+ { -1, -1 },
+ };
+
+ int i, k, n, pid, eid;
+
+ n = xps_count_font_encodings(font);
+ for (k = 0; xps_cmap_list[k].pid != -1; k++)
+ {
+ for (i = 0; i < n; i++)
+ {
+ xps_identify_font_encoding(font, i, &pid, &eid);
+ if (pid == xps_cmap_list[k].pid && eid == xps_cmap_list[k].eid)
+ {
+ xps_select_font_encoding(font, i);
+ return;
+ }
+ }
+ }
+
+ fz_warn(doc->ctx, "cannot find a suitable cmap");
+}
+
+/*
+ * Parse and draw an XPS <Glyphs> element.
+ *
+ * Indices syntax:
+
+ GlyphIndices = GlyphMapping ( ";" GlyphMapping )
+ GlyphMapping = ( [ClusterMapping] GlyphIndex ) [GlyphMetrics]
+ ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")"
+ ClusterCodeUnitCount = * DIGIT
+ ClusterGlyphCount = * DIGIT
+ GlyphIndex = * DIGIT
+ GlyphMetrics = "," AdvanceWidth ["," uOffset ["," vOffset]]
+ AdvanceWidth = ["+"] RealNum
+ uOffset = ["+" | "-"] RealNum
+ vOffset = ["+" | "-"] RealNum
+ RealNum = ((DIGIT ["." DIGIT]) | ("." DIGIT)) [Exponent]
+ Exponent = ( ("E"|"e") ("+"|"-") DIGIT )
+
+ */
+
+static char *
+xps_parse_digits(char *s, int *digit)
+{
+ *digit = 0;
+ while (*s >= '0' && *s <= '9')
+ {
+ *digit = *digit * 10 + (*s - '0');
+ s ++;
+ }
+ return s;
+}
+
+static inline int is_real_num_char(int c)
+{
+ return (c >= '0' && c <= '9') ||
+ c == 'e' || c == 'E' || c == '+' || c == '-' || c == '.';
+}
+
+static char *
+xps_parse_real_num(char *s, float *number)
+{
+ char buf[64];
+ char *p = buf;
+ while (is_real_num_char(*s))
+ *p++ = *s++;
+ *p = 0;
+ if (buf[0])
+ *number = fz_atof(buf);
+ return s;
+}
+
+static char *
+xps_parse_cluster_mapping(char *s, int *code_count, int *glyph_count)
+{
+ if (*s == '(')
+ s = xps_parse_digits(s + 1, code_count);
+ if (*s == ':')
+ s = xps_parse_digits(s + 1, glyph_count);
+ if (*s == ')')
+ s ++;
+ return s;
+}
+
+static char *
+xps_parse_glyph_index(char *s, int *glyph_index)
+{
+ if (*s >= '0' && *s <= '9')
+ s = xps_parse_digits(s, glyph_index);
+ return s;
+}
+
+static char *
+xps_parse_glyph_metrics(char *s, float *advance, float *uofs, float *vofs)
+{
+ if (*s == ',')
+ s = xps_parse_real_num(s + 1, advance);
+ if (*s == ',')
+ s = xps_parse_real_num(s + 1, uofs);
+ if (*s == ',')
+ s = xps_parse_real_num(s + 1, vofs);
+ return s;
+}
+
+/*
+ * Parse unicode and indices strings and encode glyphs.
+ * Calculate metrics for positioning.
+ */
+static fz_text *
+xps_parse_glyphs_imp(xps_document *doc, const fz_matrix *ctm,
+ fz_font *font, float size, float originx, float originy,
+ int is_sideways, int bidi_level,
+ char *indices, char *unicode)
+{
+ xps_glyph_metrics mtx;
+ fz_text *text;
+ fz_matrix tm;
+ float e, f;
+ float x = originx;
+ float y = originy;
+ char *us = unicode;
+ char *is = indices;
+ int un = 0;
+
+ if (!unicode && !indices)
+ fz_warn(doc->ctx, "glyphs element with neither characters nor indices");
+
+ if (us)
+ {
+ if (us[0] == '{' && us[1] == '}')
+ us = us + 2;
+ un = strlen(us);
+ }
+
+ if (is_sideways)
+ {
+ fz_pre_scale(fz_rotate(&tm, 90), -size, size);
+ }
+ else
+ fz_scale(&tm, size, -size);
+
+ text = fz_new_text(doc->ctx, font, &tm, is_sideways);
+
+ while ((us && un > 0) || (is && *is))
+ {
+ int char_code = '?';
+ int code_count = 1;
+ int glyph_count = 1;
+
+ if (is && *is)
+ {
+ is = xps_parse_cluster_mapping(is, &code_count, &glyph_count);
+ }
+
+ if (code_count < 1)
+ code_count = 1;
+ if (glyph_count < 1)
+ glyph_count = 1;
+
+ /* TODO: add code chars with cluster mappings for text extraction */
+
+ while (code_count--)
+ {
+ if (us && un > 0)
+ {
+ int t = fz_chartorune(&char_code, us);
+ us += t; un -= t;
+ }
+ }
+
+ while (glyph_count--)
+ {
+ int glyph_index = -1;
+ float u_offset = 0;
+ float v_offset = 0;
+ float advance;
+
+ if (is && *is)
+ is = xps_parse_glyph_index(is, &glyph_index);
+
+ if (glyph_index == -1)
+ glyph_index = xps_encode_font_char(font, char_code);
+
+ xps_measure_font_glyph(doc, font, glyph_index, &mtx);
+ if (is_sideways)
+ advance = mtx.vadv * 100;
+ else if (bidi_level & 1)
+ advance = -mtx.hadv * 100;
+ else
+ advance = mtx.hadv * 100;
+
+ if (font->ft_bold)
+ advance *= 1.02f;
+
+ if (is && *is)
+ {
+ is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset);
+ if (*is == ';')
+ is ++;
+ }
+
+ if (bidi_level & 1)
+ u_offset = -mtx.hadv * 100 - u_offset;
+
+ u_offset = u_offset * 0.01f * size;
+ v_offset = v_offset * 0.01f * size;
+
+ if (is_sideways)
+ {
+ e = x + u_offset + (mtx.vorg * size);
+ f = y - v_offset + (mtx.hadv * 0.5f * size);
+ }
+ else
+ {
+ e = x + u_offset;
+ f = y - v_offset;
+ }
+
+ fz_add_text(doc->ctx, text, glyph_index, char_code, e, f);
+
+ x += advance * 0.01f * size;
+ }
+ }
+
+ return text;
+}
+
+void
+xps_parse_glyphs(xps_document *doc, const fz_matrix *ctm,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ fz_xml *node;
+
+ char *fill_uri;
+ char *opacity_mask_uri;
+
+ char *bidi_level_att;
+ char *fill_att;
+ char *font_size_att;
+ char *font_uri_att;
+ char *origin_x_att;
+ char *origin_y_att;
+ char *is_sideways_att;
+ char *indices_att;
+ char *unicode_att;
+ char *style_att;
+ char *transform_att;
+ char *clip_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+ char *navigate_uri_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *clip_tag = NULL;
+ fz_xml *fill_tag = NULL;
+ fz_xml *opacity_mask_tag = NULL;
+
+ char *fill_opacity_att = NULL;
+
+ xps_part *part;
+ fz_font *font;
+
+ char partname[1024];
+ char fakename[1024];
+ char *subfont;
+
+ float font_size = 10;
+ int subfontid = 0;
+ int is_sideways = 0;
+ int bidi_level = 0;
+
+ fz_text *text;
+ fz_rect area;
+
+ fz_matrix local_ctm = *ctm;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ bidi_level_att = fz_xml_att(root, "BidiLevel");
+ fill_att = fz_xml_att(root, "Fill");
+ font_size_att = fz_xml_att(root, "FontRenderingEmSize");
+ font_uri_att = fz_xml_att(root, "FontUri");
+ origin_x_att = fz_xml_att(root, "OriginX");
+ origin_y_att = fz_xml_att(root, "OriginY");
+ is_sideways_att = fz_xml_att(root, "IsSideways");
+ indices_att = fz_xml_att(root, "Indices");
+ unicode_att = fz_xml_att(root, "UnicodeString");
+ style_att = fz_xml_att(root, "StyleSimulations");
+ transform_att = fz_xml_att(root, "RenderTransform");
+ clip_att = fz_xml_att(root, "Clip");
+ opacity_att = fz_xml_att(root, "Opacity");
+ opacity_mask_att = fz_xml_att(root, "OpacityMask");
+ navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "Glyphs.RenderTransform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs.OpacityMask"))
+ opacity_mask_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs.Clip"))
+ clip_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs.Fill"))
+ fill_tag = fz_xml_down(node);
+ }
+
+ fill_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ /*
+ * Check that we have all the necessary information.
+ */
+
+ if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att) {
+ fz_warn(doc->ctx, "missing attributes in glyphs element");
+ return;
+ }
+
+ if (!indices_att && !unicode_att)
+ return; /* nothing to draw */
+
+ if (is_sideways_att)
+ is_sideways = !strcmp(is_sideways_att, "true");
+
+ if (bidi_level_att)
+ bidi_level = atoi(bidi_level_att);
+
+ /*
+ * Find and load the font resource
+ */
+
+ xps_resolve_url(partname, base_uri, font_uri_att, sizeof partname);
+ subfont = strrchr(partname, '#');
+ if (subfont)
+ {
+ subfontid = atoi(subfont + 1);
+ *subfont = 0;
+ }
+
+ /* Make a new part name for font with style simulation applied */
+ fz_strlcpy(fakename, partname, sizeof fakename);
+ if (style_att)
+ {
+ if (!strcmp(style_att, "BoldSimulation"))
+ fz_strlcat(fakename, "#Bold", sizeof fakename);
+ else if (!strcmp(style_att, "ItalicSimulation"))
+ fz_strlcat(fakename, "#Italic", sizeof fakename);
+ else if (!strcmp(style_att, "BoldItalicSimulation"))
+ fz_strlcat(fakename, "#BoldItalic", sizeof fakename);
+ }
+
+ font = xps_lookup_font(doc, fakename);
+ if (!font)
+ {
+ fz_try(doc->ctx)
+ {
+ part = xps_read_part(doc, partname);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot find font resource part '%s'", partname);
+ return;
+ }
+
+ /* deobfuscate if necessary */
+ if (strstr(part->name, ".odttf"))
+ xps_deobfuscate_font_resource(doc, part);
+ if (strstr(part->name, ".ODTTF"))
+ xps_deobfuscate_font_resource(doc, part);
+
+ fz_try(doc->ctx)
+ {
+ font = fz_new_font_from_memory(doc->ctx, NULL, part->data, part->size, subfontid, 1);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot load font resource '%s'", partname);
+ xps_free_part(doc, part);
+ return;
+ }
+
+ if (style_att)
+ {
+ font->ft_bold = !!strstr(style_att, "Bold");
+ font->ft_italic = !!strstr(style_att, "Italic");
+ }
+
+ xps_select_best_font_encoding(doc, font);
+
+ xps_insert_font(doc, fakename, font);
+
+ /* NOTE: we keep part->data in the font */
+ font->ft_data = part->data;
+ font->ft_size = part->size;
+ fz_free(doc->ctx, part->name);
+ fz_free(doc->ctx, part);
+ }
+
+ /*
+ * Set up graphics state.
+ */
+
+ if (transform_att || transform_tag)
+ {
+ fz_matrix transform;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&local_ctm, &transform, &local_ctm);
+ }
+
+ if (clip_att || clip_tag)
+ xps_clip(doc, &local_ctm, dict, clip_att, clip_tag);
+
+ font_size = fz_atof(font_size_att);
+
+ text = xps_parse_glyphs_imp(doc, &local_ctm, font, font_size,
+ fz_atof(origin_x_att), fz_atof(origin_y_att),
+ is_sideways, bidi_level, indices_att, unicode_att);
+
+ fz_bound_text(doc->ctx, text, NULL, &local_ctm, &area);
+
+ if (navigate_uri_att)
+ xps_add_link(doc, &area, base_uri, navigate_uri_att);
+
+ xps_begin_opacity(doc, &local_ctm, &area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ /* If it's a solid color brush fill/stroke do a simple fill */
+
+ if (fill_tag && !strcmp(fz_xml_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
+ fill_att = fz_xml_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (fill_att)
+ {
+ float samples[32];
+ fz_colorspace *colorspace;
+
+ xps_parse_color(doc, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] *= fz_atof(fill_opacity_att);
+ xps_set_color(doc, colorspace, samples);
+
+ fz_fill_text(doc->dev, text, &local_ctm,
+ doc->colorspace, doc->color, doc->alpha);
+ }
+
+ /* If it's a complex brush, use the charpath as a clip mask */
+
+ if (fill_tag)
+ {
+ fz_clip_text(doc->dev, text, &local_ctm, 0);
+ xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag);
+ fz_pop_clip(doc->dev);
+ }
+
+ xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ fz_free_text(doc->ctx, text);
+
+ if (clip_att || clip_tag)
+ fz_pop_clip(doc->dev);
+
+ fz_drop_font(doc->ctx, font);
+}
diff --git a/source/xps/xps-gradient.c b/source/xps/xps-gradient.c
new file mode 100644
index 00000000..88ed470b
--- /dev/null
+++ b/source/xps/xps-gradient.c
@@ -0,0 +1,525 @@
+#include "mupdf/xps.h"
+
+#define MAX_STOPS 256
+
+enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT };
+
+/*
+ * Parse a list of GradientStop elements.
+ * Fill the offset and color arrays, and
+ * return the number of stops parsed.
+ */
+
+struct stop
+{
+ float offset;
+ float r, g, b, a;
+};
+
+static int cmp_stop(const void *a, const void *b)
+{
+ const struct stop *astop = a;
+ const struct stop *bstop = b;
+ float diff = astop->offset - bstop->offset;
+ if (diff < 0)
+ return -1;
+ if (diff > 0)
+ return 1;
+ return 0;
+}
+
+static inline float lerp(float a, float b, float x)
+{
+ return a + (b - a) * x;
+}
+
+static int
+xps_parse_gradient_stops(xps_document *doc, char *base_uri, fz_xml *node,
+ struct stop *stops, int maxcount)
+{
+ fz_colorspace *colorspace;
+ float sample[8];
+ float rgb[3];
+ int before, after;
+ int count;
+ int i;
+
+ /* We may have to insert 2 extra stops when postprocessing */
+ maxcount -= 2;
+
+ count = 0;
+ while (node && count < maxcount)
+ {
+ if (!strcmp(fz_xml_tag(node), "GradientStop"))
+ {
+ char *offset = fz_xml_att(node, "Offset");
+ char *color = fz_xml_att(node, "Color");
+ if (offset && color)
+ {
+ stops[count].offset = fz_atof(offset);
+
+ xps_parse_color(doc, base_uri, color, &colorspace, sample);
+
+ fz_convert_color(doc->ctx, fz_device_rgb(doc->ctx), rgb, colorspace, sample + 1);
+
+ stops[count].r = rgb[0];
+ stops[count].g = rgb[1];
+ stops[count].b = rgb[2];
+ stops[count].a = sample[0];
+
+ count ++;
+ }
+ }
+ node = fz_xml_next(node);
+ }
+
+ if (count == 0)
+ {
+ fz_warn(doc->ctx, "gradient brush has no gradient stops");
+ stops[0].offset = 0;
+ stops[0].r = 0;
+ stops[0].g = 0;
+ stops[0].b = 0;
+ stops[0].a = 1;
+ stops[1].offset = 1;
+ stops[1].r = 1;
+ stops[1].g = 1;
+ stops[1].b = 1;
+ stops[1].a = 1;
+ return 2;
+ }
+
+ if (count == maxcount)
+ fz_warn(doc->ctx, "gradient brush exceeded maximum number of gradient stops");
+
+ /* Postprocess to make sure the range of offsets is 0.0 to 1.0 */
+
+ qsort(stops, count, sizeof(struct stop), cmp_stop);
+
+ before = -1;
+ after = -1;
+
+ for (i = 0; i < count; i++)
+ {
+ if (stops[i].offset < 0)
+ before = i;
+ if (stops[i].offset > 1)
+ {
+ after = i;
+ break;
+ }
+ }
+
+ /* Remove all stops < 0 except the largest one */
+ if (before > 0)
+ {
+ memmove(stops, stops + before, (count - before) * sizeof(struct stop));
+ count -= before;
+ }
+
+ /* Remove all stops > 1 except the smallest one */
+ if (after >= 0)
+ count = after + 1;
+
+ /* Expand single stop to 0 .. 1 */
+ if (count == 1)
+ {
+ stops[1] = stops[0];
+ stops[0].offset = 0;
+ stops[1].offset = 1;
+ return 2;
+ }
+
+ /* First stop < 0 -- interpolate value to 0 */
+ if (stops[0].offset < 0)
+ {
+ float d = -stops[0].offset / (stops[1].offset - stops[0].offset);
+ stops[0].offset = 0;
+ stops[0].r = lerp(stops[0].r, stops[1].r, d);
+ stops[0].g = lerp(stops[0].g, stops[1].g, d);
+ stops[0].b = lerp(stops[0].b, stops[1].b, d);
+ stops[0].a = lerp(stops[0].a, stops[1].a, d);
+ }
+
+ /* Last stop > 1 -- interpolate value to 1 */
+ if (stops[count-1].offset > 1)
+ {
+ float d = (1 - stops[count-2].offset) / (stops[count-1].offset - stops[count-2].offset);
+ stops[count-1].offset = 1;
+ stops[count-1].r = lerp(stops[count-2].r, stops[count-1].r, d);
+ stops[count-1].g = lerp(stops[count-2].g, stops[count-1].g, d);
+ stops[count-1].b = lerp(stops[count-2].b, stops[count-1].b, d);
+ stops[count-1].a = lerp(stops[count-2].a, stops[count-1].a, d);
+ }
+
+ /* First stop > 0 -- insert a duplicate at 0 */
+ if (stops[0].offset > 0)
+ {
+ memmove(stops + 1, stops, count * sizeof(struct stop));
+ stops[0] = stops[1];
+ stops[0].offset = 0;
+ count++;
+ }
+
+ /* Last stop < 1 -- insert a duplicate at 1 */
+ if (stops[count-1].offset < 1)
+ {
+ stops[count] = stops[count-1];
+ stops[count].offset = 1;
+ count++;
+ }
+
+ return count;
+}
+
+static void
+xps_sample_gradient_stops(fz_shade *shade, struct stop *stops, int count)
+{
+ float offset, d;
+ int i, k;
+
+ k = 0;
+ for (i = 0; i < 256; i++)
+ {
+ offset = i / 255.0f;
+ while (k + 1 < count && offset > stops[k+1].offset)
+ k++;
+
+ d = (offset - stops[k].offset) / (stops[k+1].offset - stops[k].offset);
+
+ shade->function[i][0] = lerp(stops[k].r, stops[k+1].r, d);
+ shade->function[i][1] = lerp(stops[k].g, stops[k+1].g, d);
+ shade->function[i][2] = lerp(stops[k].b, stops[k+1].b, d);
+ shade->function[i][3] = lerp(stops[k].a, stops[k+1].a, d);
+ }
+}
+
+/*
+ * Radial gradients map more or less to Radial shadings.
+ * The inner circle is always a point.
+ * The outer circle is actually an ellipse,
+ * mess with the transform to squash the circle into the right aspect.
+ */
+
+static void
+xps_draw_one_radial_gradient(xps_document *doc, const fz_matrix *ctm,
+ struct stop *stops, int count,
+ int extend,
+ float x0, float y0, float r0,
+ float x1, float y1, float r1)
+{
+ fz_shade *shade;
+
+ /* TODO: this (and the stuff in pdf_shade) should move to res_shade.c */
+ shade = fz_malloc_struct(doc->ctx, fz_shade);
+ FZ_INIT_STORABLE(shade, 1, fz_free_shade_imp);
+ shade->colorspace = fz_device_rgb(doc->ctx);
+ shade->bbox = fz_infinite_rect;
+ shade->matrix = fz_identity;
+ shade->use_background = 0;
+ shade->use_function = 1;
+ shade->type = FZ_RADIAL;
+ shade->u.l_or_r.extend[0] = extend;
+ shade->u.l_or_r.extend[1] = extend;
+
+ xps_sample_gradient_stops(shade, stops, count);
+
+ shade->u.l_or_r.coords[0][0] = x0;
+ shade->u.l_or_r.coords[0][1] = y0;
+ shade->u.l_or_r.coords[0][2] = r0;
+ shade->u.l_or_r.coords[1][0] = x1;
+ shade->u.l_or_r.coords[1][1] = y1;
+ shade->u.l_or_r.coords[1][2] = r1;
+
+ fz_fill_shade(doc->dev, shade, ctm, 1);
+
+ fz_drop_shade(doc->ctx, shade);
+}
+
+/*
+ * Linear gradients.
+ */
+
+static void
+xps_draw_one_linear_gradient(xps_document *doc, const fz_matrix *ctm,
+ struct stop *stops, int count,
+ int extend,
+ float x0, float y0, float x1, float y1)
+{
+ fz_shade *shade;
+
+ /* TODO: this (and the stuff in pdf_shade) should move to res_shade.c */
+ shade = fz_malloc_struct(doc->ctx, fz_shade);
+ FZ_INIT_STORABLE(shade, 1, fz_free_shade_imp);
+ shade->colorspace = fz_device_rgb(doc->ctx);
+ shade->bbox = fz_infinite_rect;
+ shade->matrix = fz_identity;
+ shade->use_background = 0;
+ shade->use_function = 1;
+ shade->type = FZ_LINEAR;
+ shade->u.l_or_r.extend[0] = extend;
+ shade->u.l_or_r.extend[1] = extend;
+
+ xps_sample_gradient_stops(shade, stops, count);
+
+ shade->u.l_or_r.coords[0][0] = x0;
+ shade->u.l_or_r.coords[0][1] = y0;
+ shade->u.l_or_r.coords[0][2] = 0;
+ shade->u.l_or_r.coords[1][0] = x1;
+ shade->u.l_or_r.coords[1][1] = y1;
+ shade->u.l_or_r.coords[1][2] = 0;
+
+ fz_fill_shade(doc->dev, shade, ctm, doc->opacity[doc->opacity_top]);
+
+ fz_drop_shade(doc->ctx, shade);
+}
+
+/*
+ * We need to loop and create many shading objects to account
+ * for the Repeat and Reflect SpreadMethods.
+ * I'm not smart enough to calculate this analytically
+ * so we iterate and check each object until we
+ * reach a reasonable limit for infinite cases.
+ */
+
+static inline float point_inside_circle(float px, float py, float x, float y, float r)
+{
+ float dx = px - x;
+ float dy = py - y;
+ return dx * dx + dy * dy <= r * r;
+}
+
+static void
+xps_draw_radial_gradient(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ struct stop *stops, int count,
+ fz_xml *root, int spread)
+{
+ float x0, y0, r0;
+ float x1, y1, r1;
+ float xrad = 1;
+ float yrad = 1;
+ float invscale;
+ int i, ma = 1;
+ fz_matrix local_ctm = *ctm;
+ fz_matrix inv;
+ fz_rect local_area = *area;
+
+ char *center_att = fz_xml_att(root, "Center");
+ char *origin_att = fz_xml_att(root, "GradientOrigin");
+ char *radius_x_att = fz_xml_att(root, "RadiusX");
+ char *radius_y_att = fz_xml_att(root, "RadiusY");
+
+ x0 = y0 = 0.0;
+ x1 = y1 = 1.0;
+ xrad = 1.0;
+ yrad = 1.0;
+
+ if (origin_att)
+ xps_parse_point(origin_att, &x0, &y0);
+ if (center_att)
+ xps_parse_point(center_att, &x1, &y1);
+ if (radius_x_att)
+ xrad = fz_atof(radius_x_att);
+ if (radius_y_att)
+ yrad = fz_atof(radius_y_att);
+
+ xrad = fz_max(0.01f, xrad);
+ yrad = fz_max(0.01f, yrad);
+
+ /* scale the ctm to make ellipses */
+ if (fz_abs(xrad) > FLT_EPSILON)
+ {
+ fz_pre_scale(&local_ctm, 1, yrad/xrad);
+ }
+
+ if (yrad != 0.0)
+ {
+ invscale = xrad / yrad;
+ y0 = y0 * invscale;
+ y1 = y1 * invscale;
+ }
+
+ r0 = 0;
+ r1 = xrad;
+
+ fz_transform_rect(&local_area, fz_invert_matrix(&inv, &local_ctm));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x0 - x0, local_area.y0 - y0) / xrad));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x1 - x0, local_area.y0 - y0) / xrad));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x0 - x0, local_area.y1 - y0) / xrad));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x1 - x0, local_area.y1 - y0) / xrad));
+
+ if (spread == SPREAD_REPEAT)
+ {
+ for (i = ma - 1; i >= 0; i--)
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
+ }
+ else if (spread == SPREAD_REFLECT)
+ {
+ if ((ma % 2) != 0)
+ ma++;
+ for (i = ma - 2; i >= 0; i -= 2)
+ {
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 0, x0, y0, r0 + (i + 2) * xrad, x1, y1, r1 + i * xrad);
+ }
+ }
+ else
+ {
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 1, x0, y0, r0, x1, y1, r1);
+ }
+}
+
+/*
+ * Calculate how many iterations are needed to cover
+ * the bounding box.
+ */
+
+static void
+xps_draw_linear_gradient(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ struct stop *stops, int count,
+ fz_xml *root, int spread)
+{
+ float x0, y0, x1, y1;
+ int i, mi, ma;
+ float dx, dy, x, y, k;
+ fz_point p1, p2;
+ fz_matrix inv;
+ fz_rect local_area = *area;
+
+ char *start_point_att = fz_xml_att(root, "StartPoint");
+ char *end_point_att = fz_xml_att(root, "EndPoint");
+
+ x0 = y0 = 0;
+ x1 = y1 = 1;
+
+ if (start_point_att)
+ xps_parse_point(start_point_att, &x0, &y0);
+ if (end_point_att)
+ xps_parse_point(end_point_att, &x1, &y1);
+
+ p1.x = x0; p1.y = y0; p2.x = x1; p2.y = y1;
+ fz_transform_rect(&local_area, fz_invert_matrix(&inv, ctm));
+ x = p2.x - p1.x; y = p2.y - p1.y;
+ k = ((local_area.x0 - p1.x) * x + (local_area.y0 - p1.y) * y) / (x * x + y * y);
+ mi = floorf(k); ma = ceilf(k);
+ k = ((local_area.x1 - p1.x) * x + (local_area.y0 - p1.y) * y) / (x * x + y * y);
+ mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
+ k = ((local_area.x0 - p1.x) * x + (local_area.y1 - p1.y) * y) / (x * x + y * y);
+ mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
+ k = ((local_area.x1 - p1.x) * x + (local_area.y1 - p1.y) * y) / (x * x + y * y);
+ mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
+ dx = x1 - x0; dy = y1 - y0;
+
+ if (spread == SPREAD_REPEAT)
+ {
+ for (i = mi; i < ma; i++)
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
+ }
+ else if (spread == SPREAD_REFLECT)
+ {
+ if ((mi % 2) != 0)
+ mi--;
+ for (i = mi; i < ma; i += 2)
+ {
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 0, x0 + (i + 2) * dx, y0 + (i + 2) * dy, x1 + i * dx, y1 + i * dy);
+ }
+ }
+ else
+ {
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 1, x0, y0, x1, y1);
+ }
+}
+
+/*
+ * Parse XML tag and attributes for a gradient brush, create color/opacity
+ * function objects and call gradient drawing primitives.
+ */
+
+static void
+xps_parse_gradient_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root,
+ void (*draw)(xps_document *, const fz_matrix*, const fz_rect *, struct stop *, int, fz_xml *, int))
+{
+ fz_xml *node;
+
+ char *opacity_att;
+ char *spread_att;
+ char *transform_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *stop_tag = NULL;
+
+ struct stop stop_list[MAX_STOPS];
+ int stop_count;
+ fz_matrix transform;
+ int spread_method;
+
+ opacity_att = fz_xml_att(root, "Opacity");
+ spread_att = fz_xml_att(root, "SpreadMethod");
+ transform_att = fz_xml_att(root, "Transform");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "LinearGradientBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "RadialGradientBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "LinearGradientBrush.GradientStops"))
+ stop_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "RadialGradientBrush.GradientStops"))
+ stop_tag = fz_xml_down(node);
+ }
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+
+ spread_method = SPREAD_PAD;
+ if (spread_att)
+ {
+ if (!strcmp(spread_att, "Pad"))
+ spread_method = SPREAD_PAD;
+ if (!strcmp(spread_att, "Reflect"))
+ spread_method = SPREAD_REFLECT;
+ if (!strcmp(spread_att, "Repeat"))
+ spread_method = SPREAD_REPEAT;
+ }
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&transform, &transform, ctm);
+
+ if (!stop_tag) {
+ fz_warn(doc->ctx, "missing gradient stops tag");
+ return;
+ }
+
+ stop_count = xps_parse_gradient_stops(doc, base_uri, stop_tag, stop_list, MAX_STOPS);
+ if (stop_count == 0)
+ {
+ fz_warn(doc->ctx, "no gradient stops found");
+ return;
+ }
+
+ xps_begin_opacity(doc, &transform, area, base_uri, dict, opacity_att, NULL);
+
+ draw(doc, &transform, area, stop_list, stop_count, root, spread_method);
+
+ xps_end_opacity(doc, base_uri, dict, opacity_att, NULL);
+}
+
+void
+xps_parse_linear_gradient_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_parse_gradient_brush(doc, ctm, area, base_uri, dict, root, xps_draw_linear_gradient);
+}
+
+void
+xps_parse_radial_gradient_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_parse_gradient_brush(doc, ctm, area, base_uri, dict, root, xps_draw_radial_gradient);
+}
diff --git a/source/xps/xps-image.c b/source/xps/xps-image.c
new file mode 100644
index 00000000..eb0d876f
--- /dev/null
+++ b/source/xps/xps-image.c
@@ -0,0 +1,128 @@
+#include "mupdf/xps.h"
+
+static fz_image *
+xps_load_image(fz_context *ctx, xps_part *part)
+{
+ /* Ownership of data always passes in here */
+ unsigned char *data = part->data;
+ part->data = NULL;
+ return fz_new_image_from_data(ctx, data, part->size);
+}
+
+/* FIXME: area unused! */
+static void
+xps_paint_image_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict,
+ fz_xml *root, void *vimage)
+{
+ fz_image *image = vimage;
+ float xs, ys;
+ fz_matrix local_ctm = *ctm;
+
+ if (image->xres == 0 || image->yres == 0)
+ return;
+ xs = image->w * 96 / image->xres;
+ ys = image->h * 96 / image->yres;
+ fz_pre_scale(&local_ctm, xs, ys);
+ fz_fill_image(doc->dev, image, &local_ctm, doc->opacity[doc->opacity_top]);
+}
+
+static void
+xps_find_image_brush_source_part(xps_document *doc, char *base_uri, fz_xml *root, xps_part **image_part, xps_part **profile_part)
+{
+ char *image_source_att;
+ char buf[1024];
+ char partname[1024];
+ char *image_name;
+ char *profile_name;
+ char *p;
+
+ image_source_att = fz_xml_att(root, "ImageSource");
+ if (!image_source_att)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find image source attribute");
+
+ /* "{ColorConvertedBitmap /Resources/Image.tiff /Resources/Profile.icc}" */
+ if (strstr(image_source_att, "{ColorConvertedBitmap") == image_source_att)
+ {
+ image_name = NULL;
+ profile_name = NULL;
+
+ fz_strlcpy(buf, image_source_att, sizeof buf);
+ p = strchr(buf, ' ');
+ if (p)
+ {
+ image_name = p + 1;
+ p = strchr(p + 1, ' ');
+ if (p)
+ {
+ *p = 0;
+ profile_name = p + 1;
+ p = strchr(p + 1, '}');
+ if (p)
+ *p = 0;
+ }
+ }
+ }
+ else
+ {
+ image_name = image_source_att;
+ profile_name = NULL;
+ }
+
+ if (!image_name)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find image source");
+
+ if (image_part)
+ {
+ xps_resolve_url(partname, base_uri, image_name, sizeof partname);
+ *image_part = xps_read_part(doc, partname);
+ }
+
+ if (profile_part)
+ {
+ if (profile_name)
+ {
+ xps_resolve_url(partname, base_uri, profile_name, sizeof partname);
+ *profile_part = xps_read_part(doc, partname);
+ }
+ else
+ *profile_part = NULL;
+ }
+}
+
+void
+xps_parse_image_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_part *part;
+ fz_image *image;
+
+ fz_try(doc->ctx)
+ {
+ xps_find_image_brush_source_part(doc, base_uri, root, &part, NULL);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot find image source");
+ return;
+ }
+
+ fz_try(doc->ctx)
+ {
+ image = xps_load_image(doc->ctx, part);
+ }
+ fz_always(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot decode image resource");
+ return;
+ }
+
+ xps_parse_tiling_brush(doc, ctm, area, base_uri, dict, root, xps_paint_image_brush, image);
+
+ fz_drop_image(doc->ctx, image);
+}
diff --git a/source/xps/xps-outline.c b/source/xps/xps-outline.c
new file mode 100644
index 00000000..b87460a4
--- /dev/null
+++ b/source/xps/xps-outline.c
@@ -0,0 +1,152 @@
+#include "mupdf/xps.h"
+
+/*
+ * Parse the document structure / outline parts referenced from fixdoc relationships.
+ */
+
+static fz_outline *
+xps_lookup_last_outline_at_level(fz_outline *node, int level, int target_level)
+{
+ while (node->next)
+ node = node->next;
+ if (level == target_level || !node->down)
+ return node;
+ return xps_lookup_last_outline_at_level(node->down, level + 1, target_level);
+}
+
+static fz_outline *
+xps_parse_document_outline(xps_document *doc, fz_xml *root)
+{
+ fz_xml *node;
+ fz_outline *head = NULL, *entry, *tail;
+ int last_level = 1, this_level;
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "OutlineEntry"))
+ {
+ char *level = fz_xml_att(node, "OutlineLevel");
+ char *target = fz_xml_att(node, "OutlineTarget");
+ char *description = fz_xml_att(node, "Description");
+ if (!target || !description)
+ continue;
+
+ entry = fz_malloc_struct(doc->ctx, fz_outline);
+ entry->title = fz_strdup(doc->ctx, description);
+ entry->dest.kind = FZ_LINK_GOTO;
+ entry->dest.ld.gotor.flags = 0;
+ entry->dest.ld.gotor.page = xps_lookup_link_target(doc, target);
+ entry->down = NULL;
+ entry->next = NULL;
+
+ this_level = level ? atoi(level) : 1;
+
+ if (!head)
+ {
+ head = entry;
+ }
+ else
+ {
+ tail = xps_lookup_last_outline_at_level(head, 1, this_level);
+ if (this_level > last_level)
+ tail->down = entry;
+ else
+ tail->next = entry;
+ }
+
+ last_level = this_level;
+ }
+ }
+ return head;
+}
+
+static fz_outline *
+xps_parse_document_structure(xps_document *doc, fz_xml *root)
+{
+ fz_xml *node;
+ if (!strcmp(fz_xml_tag(root), "DocumentStructure"))
+ {
+ node = fz_xml_down(root);
+ if (node && !strcmp(fz_xml_tag(node), "DocumentStructure.Outline"))
+ {
+ node = fz_xml_down(node);
+ if (node && !strcmp(fz_xml_tag(node), "DocumentOutline"))
+ return xps_parse_document_outline(doc, node);
+ }
+ }
+ return NULL;
+}
+
+static fz_outline *
+xps_load_document_structure(xps_document *doc, xps_fixdoc *fixdoc)
+{
+ xps_part *part;
+ fz_xml *root;
+ fz_outline *outline;
+
+ part = xps_read_part(doc, fixdoc->outline);
+ fz_try(doc->ctx)
+ {
+ root = fz_parse_xml(doc->ctx, part->data, part->size);
+ }
+ fz_always(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(doc->ctx)
+ {
+ fz_rethrow(doc->ctx);
+ }
+ if (!root)
+ return NULL;
+
+ fz_try(doc->ctx)
+ {
+ outline = xps_parse_document_structure(doc, root);
+ }
+ fz_always(doc->ctx)
+ {
+ fz_free_xml(doc->ctx, root);
+ }
+ fz_catch(doc->ctx)
+ {
+ fz_rethrow(doc->ctx);
+ }
+
+ return outline;
+}
+
+fz_outline *
+xps_load_outline(xps_document *doc)
+{
+ xps_fixdoc *fixdoc;
+ fz_outline *head = NULL, *tail, *outline;
+
+ for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ {
+ if (fixdoc->outline)
+ {
+ fz_try(doc->ctx)
+ {
+ outline = xps_load_document_structure(doc, fixdoc);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ outline = NULL;
+ }
+ if (!outline)
+ continue;
+
+ if (!head)
+ head = outline;
+ else
+ {
+ while (tail->next)
+ tail = tail->next;
+ tail->next = outline;
+ }
+ tail = outline;
+ }
+ }
+ return head;
+}
diff --git a/source/xps/xps-path.c b/source/xps/xps-path.c
new file mode 100644
index 00000000..371f52ab
--- /dev/null
+++ b/source/xps/xps-path.c
@@ -0,0 +1,1053 @@
+#include "mupdf/xps.h"
+
+static char *
+xps_parse_float_array(char *s, int num, float *x)
+{
+ int k = 0;
+
+ if (s == NULL || *s == 0)
+ return NULL;
+
+ while (*s)
+ {
+ while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
+ s++;
+ x[k] = (float)strtod(s, &s);
+ while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
+ s++;
+ if (*s == ',')
+ s++;
+ if (++k == num)
+ break;
+ }
+ return s;
+}
+
+char *
+xps_parse_point(char *s_in, float *x, float *y)
+{
+ char *s_out = s_in;
+ float xy[2];
+
+ s_out = xps_parse_float_array(s_out, 2, &xy[0]);
+ *x = xy[0];
+ *y = xy[1];
+ return s_out;
+}
+
+/* Draw an arc segment transformed by the matrix, we approximate with straight
+ * line segments. We cannot use the fz_arc function because they only draw
+ * circular arcs, we need to transform the line to make them elliptical but
+ * without transforming the line width.
+ *
+ * We are guaranteed that on entry the point is at the point that would be
+ * calculated by th0, and on exit, a point is generated for us at th0.
+ */
+static void
+xps_draw_arc_segment(fz_context *doc, fz_path *path, const fz_matrix *mtx, float th0, float th1, int iscw)
+{
+ float t, d;
+ fz_point p;
+
+ while (th1 < th0)
+ th1 += (float)M_PI * 2;
+
+ d = (float)M_PI / 180; /* 1-degree precision */
+
+ if (iscw)
+ {
+ for (t = th0 + d; t < th1 - d/2; t += d)
+ {
+ p.x = cosf(t);
+ p.y = sinf(t);
+ fz_transform_point(&p, mtx);
+ fz_lineto(doc, path, p.x, p.y);
+ }
+ }
+ else
+ {
+ th0 += (float)M_PI * 2;
+ for (t = th0 - d; t > th1 + d/2; t -= d)
+ {
+ p.x = cosf(t);
+ p.y = sinf(t);
+ fz_transform_point(&p, mtx);
+ fz_lineto(doc, path, p.x, p.y);
+ }
+ }
+}
+
+/* Given two vectors find the angle between them. */
+static float
+angle_between(const fz_point u, const fz_point v)
+{
+ float det = u.x * v.y - u.y * v.x;
+ float sign = (det < 0 ? -1 : 1);
+ float magu = u.x * u.x + u.y * u.y;
+ float magv = v.x * v.x + v.y * v.y;
+ float udotv = u.x * v.x + u.y * v.y;
+ float t = udotv / (magu * magv);
+ /* guard against rounding errors when near |1| (where acos will return NaN) */
+ if (t < -1) t = -1;
+ if (t > 1) t = 1;
+ return sign * acosf(t);
+}
+
+/*
+ Some explaination of the parameters here is warranted. See:
+
+ http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+
+ Add an arc segment to path, that describes a section of an elliptical
+ arc from the current point of path to (point_x,point_y), such that:
+
+ The arc segment is taken from an elliptical arc of semi major radius
+ size_x, semi minor radius size_y, where the semi major axis of the
+ ellipse is rotated by rotation_angle.
+
+ If is_large_arc, then the arc segment is selected to be > 180 degrees.
+
+ If is_clockwise, then the arc sweeps clockwise.
+*/
+static void
+xps_draw_arc(fz_context *doc, fz_path *path,
+ float size_x, float size_y, float rotation_angle,
+ int is_large_arc, int is_clockwise,
+ float point_x, float point_y)
+{
+ fz_matrix rotmat, revmat;
+ fz_matrix mtx;
+ fz_point pt;
+ float rx, ry;
+ float x1, y1, x2, y2;
+ float x1t, y1t;
+ float cxt, cyt, cx, cy;
+ float t1, t2, t3;
+ float sign;
+ float th1, dth;
+
+ pt = fz_currentpoint(doc, path);
+ x1 = pt.x;
+ y1 = pt.y;
+ x2 = point_x;
+ y2 = point_y;
+ rx = size_x;
+ ry = size_y;
+
+ if (is_clockwise != is_large_arc)
+ sign = 1;
+ else
+ sign = -1;
+
+ fz_rotate(&rotmat, rotation_angle);
+ fz_rotate(&revmat, -rotation_angle);
+
+ /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
+ /* Conversion from endpoint to center parameterization */
+
+ /* F.6.6.1 -- ensure radii are positive and non-zero */
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+ if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
+ {
+ fz_lineto(doc, path, x2, y2);
+ return;
+ }
+
+ /* F.6.5.1 */
+ pt.x = (x1 - x2) / 2;
+ pt.y = (y1 - y2) / 2;
+ fz_transform_vector(&pt, &revmat);
+ x1t = pt.x;
+ y1t = pt.y;
+
+ /* F.6.6.2 -- ensure radii are large enough */
+ t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
+ if (t1 > 1)
+ {
+ rx = rx * sqrtf(t1);
+ ry = ry * sqrtf(t1);
+ }
+
+ /* F.6.5.2 */
+ t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
+ t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
+ t3 = t1 / t2;
+ /* guard against rounding errors; sqrt of negative numbers is bad for your health */
+ if (t3 < 0) t3 = 0;
+ t3 = sqrtf(t3);
+
+ cxt = sign * t3 * (rx * y1t) / ry;
+ cyt = sign * t3 * -(ry * x1t) / rx;
+
+ /* F.6.5.3 */
+ pt.x = cxt;
+ pt.y = cyt;
+ fz_transform_vector(&pt, &rotmat);
+ cx = pt.x + (x1 + x2) / 2;
+ cy = pt.y + (y1 + y2) / 2;
+
+ /* F.6.5.4 */
+ {
+ fz_point coord1, coord2, coord3, coord4;
+ coord1.x = 1;
+ coord1.y = 0;
+ coord2.x = (x1t - cxt) / rx;
+ coord2.y = (y1t - cyt) / ry;
+ coord3.x = (x1t - cxt) / rx;
+ coord3.y = (y1t - cyt) / ry;
+ coord4.x = (-x1t - cxt) / rx;
+ coord4.y = (-y1t - cyt) / ry;
+ th1 = angle_between(coord1, coord2);
+ dth = angle_between(coord3, coord4);
+ if (dth < 0 && !is_clockwise)
+ dth += (((float)M_PI / 180) * 360);
+ if (dth > 0 && is_clockwise)
+ dth -= (((float)M_PI / 180) * 360);
+ }
+
+ fz_pre_scale(fz_pre_rotate(fz_translate(&mtx, cx, cy), rotation_angle), rx, ry);
+ xps_draw_arc_segment(doc, path, &mtx, th1, th1 + dth, is_clockwise);
+
+ fz_lineto(doc, path, point_x, point_y);
+}
+
+/*
+ * Parse an abbreviated geometry string, and call
+ * ghostscript moveto/lineto/curveto functions to
+ * build up a path.
+ */
+
+static fz_path *
+xps_parse_abbreviated_geometry(xps_document *doc, char *geom, int *fill_rule)
+{
+ fz_path *path;
+ char **args;
+ char **pargs;
+ char *s = geom;
+ fz_point pt;
+ int i, n;
+ int cmd, old;
+ float x1, y1, x2, y2, x3, y3;
+ float smooth_x, smooth_y; /* saved cubic bezier control point for smooth curves */
+ int reset_smooth;
+
+ path = fz_new_path(doc->ctx);
+
+ args = fz_malloc_array(doc->ctx, strlen(geom) + 1, sizeof(char*));
+ pargs = args;
+
+ while (*s)
+ {
+ if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))
+ {
+ *pargs++ = s++;
+ }
+ else if ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
+ {
+ *pargs++ = s;
+ while ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
+ s ++;
+ }
+ else
+ {
+ s++;
+ }
+ }
+
+ *pargs = s;
+
+ n = pargs - args;
+ i = 0;
+
+ old = 0;
+
+ reset_smooth = 1;
+ smooth_x = 0;
+ smooth_y = 0;
+
+ while (i < n)
+ {
+ cmd = args[i][0];
+ if (cmd == '+' || cmd == '.' || cmd == '-' || (cmd >= '0' && cmd <= '9'))
+ cmd = old; /* it's a number, repeat old command */
+ else
+ i ++;
+
+ if (reset_smooth)
+ {
+ smooth_x = 0;
+ smooth_y = 0;
+ }
+
+ reset_smooth = 1;
+
+ switch (cmd)
+ {
+ case 'F':
+ if (i >= n) break;
+ *fill_rule = atoi(args[i]);
+ i ++;
+ break;
+
+ case 'M':
+ if (i + 1 >= n) break;
+ fz_moveto(doc->ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
+ i += 2;
+ break;
+ case 'm':
+ if (i + 1 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_moveto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'L':
+ if (i + 1 >= n) break;
+ fz_lineto(doc->ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
+ i += 2;
+ break;
+ case 'l':
+ if (i + 1 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'H':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, fz_atof(args[i]), pt.y);
+ i += 1;
+ break;
+ case 'h':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y);
+ i += 1;
+ break;
+
+ case 'V':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x, fz_atof(args[i]));
+ i += 1;
+ break;
+ case 'v':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x, pt.y + fz_atof(args[i]));
+ i += 1;
+ break;
+
+ case 'C':
+ if (i + 5 >= n) break;
+ x1 = fz_atof(args[i+0]);
+ y1 = fz_atof(args[i+1]);
+ x2 = fz_atof(args[i+2]);
+ y2 = fz_atof(args[i+3]);
+ x3 = fz_atof(args[i+4]);
+ y3 = fz_atof(args[i+5]);
+ fz_curveto(doc->ctx, path, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'c':
+ if (i + 5 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]) + pt.x;
+ y1 = fz_atof(args[i+1]) + pt.y;
+ x2 = fz_atof(args[i+2]) + pt.x;
+ y2 = fz_atof(args[i+3]) + pt.y;
+ x3 = fz_atof(args[i+4]) + pt.x;
+ y3 = fz_atof(args[i+5]) + pt.y;
+ fz_curveto(doc->ctx, path, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'S':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]);
+ y1 = fz_atof(args[i+1]);
+ x2 = fz_atof(args[i+2]);
+ y2 = fz_atof(args[i+3]);
+ fz_curveto(doc->ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
+ i += 4;
+ reset_smooth = 0;
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ break;
+
+ case 's':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]) + pt.x;
+ y1 = fz_atof(args[i+1]) + pt.y;
+ x2 = fz_atof(args[i+2]) + pt.x;
+ y2 = fz_atof(args[i+3]) + pt.y;
+ fz_curveto(doc->ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
+ i += 4;
+ reset_smooth = 0;
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ break;
+
+ case 'Q':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]);
+ y1 = fz_atof(args[i+1]);
+ x2 = fz_atof(args[i+2]);
+ y2 = fz_atof(args[i+3]);
+ fz_curveto(doc->ctx, path,
+ (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ i += 4;
+ break;
+ case 'q':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]) + pt.x;
+ y1 = fz_atof(args[i+1]) + pt.y;
+ x2 = fz_atof(args[i+2]) + pt.x;
+ y2 = fz_atof(args[i+3]) + pt.y;
+ fz_curveto(doc->ctx, path,
+ (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ i += 4;
+ break;
+
+ case 'A':
+ if (i + 6 >= n) break;
+ xps_draw_arc(doc->ctx, path,
+ fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ fz_atof(args[i+5]), fz_atof(args[i+6]));
+ i += 7;
+ break;
+ case 'a':
+ if (i + 6 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ xps_draw_arc(doc->ctx, path,
+ fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ fz_atof(args[i+5]) + pt.x, fz_atof(args[i+6]) + pt.y);
+ i += 7;
+ break;
+
+ case 'Z':
+ case 'z':
+ fz_closepath(doc->ctx, path);
+ break;
+
+ default:
+ /* eek */
+ fz_warn(doc->ctx, "ignoring invalid command '%c'", cmd);
+ /* Skip any trailing numbers to avoid an infinite loop */
+ while (i < n && (args[i][0] == '+' || args[i][0] == '.' || args[i][0] == '-' || (args[i][0] >= '0' && args[i][0] <= '9')))
+ i ++;
+ break;
+ }
+
+ old = cmd;
+ }
+
+ fz_free(doc->ctx, args);
+ return path;
+}
+
+static void
+xps_parse_arc_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ /* ArcSegment pretty much follows the SVG algorithm for converting an
+ * arc in endpoint representation to an arc in centerpoint
+ * representation. Once in centerpoint it can be given to the
+ * graphics library in the form of a postscript arc. */
+
+ float rotation_angle;
+ int is_large_arc, is_clockwise;
+ float point_x, point_y;
+ float size_x, size_y;
+ int is_stroked;
+
+ char *point_att = fz_xml_att(root, "Point");
+ char *size_att = fz_xml_att(root, "Size");
+ char *rotation_angle_att = fz_xml_att(root, "RotationAngle");
+ char *is_large_arc_att = fz_xml_att(root, "IsLargeArc");
+ char *sweep_direction_att = fz_xml_att(root, "SweepDirection");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+
+ if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
+ {
+ fz_warn(doc, "ArcSegment element is missing attributes");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ point_x = point_y = 0;
+ size_x = size_y = 0;
+
+ xps_parse_point(point_att, &point_x, &point_y);
+ xps_parse_point(size_att, &size_x, &size_y);
+ rotation_angle = fz_atof(rotation_angle_att);
+ is_large_arc = !strcmp(is_large_arc_att, "true");
+ is_clockwise = !strcmp(sweep_direction_att, "Clockwise");
+
+ if (stroking && !is_stroked)
+ {
+ fz_moveto(doc, path, point_x, point_y);
+ return;
+ }
+
+ xps_draw_arc(doc, path, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y);
+}
+
+static void
+xps_parse_poly_quadratic_bezier_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = fz_xml_att(root, "Points");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+ float x[2], y[2];
+ int is_stroked;
+ fz_point pt;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ fz_warn(doc, "PolyQuadraticBezierSegment element has no points");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ s = points_att;
+ n = 0;
+ while (*s != 0)
+ {
+ while (*s == ' ') s++;
+ s = xps_parse_point(s, &x[n], &y[n]);
+ n ++;
+ if (n == 2)
+ {
+ if (stroking && !is_stroked)
+ {
+ fz_moveto(doc, path, x[1], y[1]);
+ }
+ else
+ {
+ pt = fz_currentpoint(doc, path);
+ fz_curveto(doc, path,
+ (pt.x + 2 * x[0]) / 3, (pt.y + 2 * y[0]) / 3,
+ (x[1] + 2 * x[0]) / 3, (y[1] + 2 * y[0]) / 3,
+ x[1], y[1]);
+ }
+ n = 0;
+ }
+ }
+}
+
+static void
+xps_parse_poly_bezier_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = fz_xml_att(root, "Points");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+ float x[3], y[3];
+ int is_stroked;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ fz_warn(doc, "PolyBezierSegment element has no points");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ s = points_att;
+ n = 0;
+ while (*s != 0)
+ {
+ while (*s == ' ') s++;
+ s = xps_parse_point(s, &x[n], &y[n]);
+ n ++;
+ if (n == 3)
+ {
+ if (stroking && !is_stroked)
+ fz_moveto(doc, path, x[2], y[2]);
+ else
+ fz_curveto(doc, path, x[0], y[0], x[1], y[1], x[2], y[2]);
+ n = 0;
+ }
+ }
+}
+
+static void
+xps_parse_poly_line_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = fz_xml_att(root, "Points");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+ int is_stroked;
+ float x, y;
+ char *s;
+
+ if (!points_att)
+ {
+ fz_warn(doc, "PolyLineSegment element has no points");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ s = points_att;
+ while (*s != 0)
+ {
+ while (*s == ' ') s++;
+ s = xps_parse_point(s, &x, &y);
+ if (stroking && !is_stroked)
+ fz_moveto(doc, path, x, y);
+ else
+ fz_lineto(doc, path, x, y);
+ }
+}
+
+static void
+xps_parse_path_figure(fz_context *doc, fz_path *path, fz_xml *root, int stroking)
+{
+ fz_xml *node;
+
+ char *is_closed_att;
+ char *start_point_att;
+ char *is_filled_att;
+
+ int is_closed = 0;
+ int is_filled = 1;
+ float start_x = 0;
+ float start_y = 0;
+
+ int skipped_stroke = 0;
+
+ is_closed_att = fz_xml_att(root, "IsClosed");
+ start_point_att = fz_xml_att(root, "StartPoint");
+ is_filled_att = fz_xml_att(root, "IsFilled");
+
+ if (is_closed_att)
+ is_closed = !strcmp(is_closed_att, "true");
+ if (is_filled_att)
+ is_filled = !strcmp(is_filled_att, "true");
+ if (start_point_att)
+ xps_parse_point(start_point_att, &start_x, &start_y);
+
+ if (!stroking && !is_filled) /* not filled, when filling */
+ return;
+
+ fz_moveto(doc, path, start_x, start_y);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "ArcSegment"))
+ xps_parse_arc_segment(doc, path, node, stroking, &skipped_stroke);
+ if (!strcmp(fz_xml_tag(node), "PolyBezierSegment"))
+ xps_parse_poly_bezier_segment(doc, path, node, stroking, &skipped_stroke);
+ if (!strcmp(fz_xml_tag(node), "PolyLineSegment"))
+ xps_parse_poly_line_segment(doc, path, node, stroking, &skipped_stroke);
+ if (!strcmp(fz_xml_tag(node), "PolyQuadraticBezierSegment"))
+ xps_parse_poly_quadratic_bezier_segment(doc, path, node, stroking, &skipped_stroke);
+ }
+
+ if (is_closed)
+ {
+ if (stroking && skipped_stroke)
+ fz_lineto(doc, path, start_x, start_y); /* we've skipped using fz_moveto... */
+ else
+ fz_closepath(doc, path); /* no skipped segments, safe to closepath properly */
+ }
+}
+
+fz_path *
+xps_parse_path_geometry(xps_document *doc, xps_resource *dict, fz_xml *root, int stroking, int *fill_rule)
+{
+ fz_xml *node;
+
+ char *figures_att;
+ char *fill_rule_att;
+ char *transform_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *figures_tag = NULL; /* only used by resource */
+
+ fz_matrix transform;
+ fz_path *path;
+
+ figures_att = fz_xml_att(root, "Figures");
+ fill_rule_att = fz_xml_att(root, "FillRule");
+ transform_att = fz_xml_att(root, "Transform");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "PathGeometry.Transform"))
+ transform_tag = fz_xml_down(node);
+ }
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &figures_att, &figures_tag, NULL);
+
+ if (fill_rule_att)
+ {
+ if (!strcmp(fill_rule_att, "NonZero"))
+ *fill_rule = 1;
+ if (!strcmp(fill_rule_att, "EvenOdd"))
+ *fill_rule = 0;
+ }
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+
+ if (figures_att)
+ path = xps_parse_abbreviated_geometry(doc, figures_att, fill_rule);
+ else
+ path = fz_new_path(doc->ctx);
+
+ if (figures_tag)
+ xps_parse_path_figure(doc->ctx, path, figures_tag, stroking);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "PathFigure"))
+ xps_parse_path_figure(doc->ctx, path, node, stroking);
+ }
+
+ if (transform_att || transform_tag)
+ fz_transform_path(doc->ctx, path, &transform);
+
+ return path;
+}
+
+static int
+xps_parse_line_cap(char *attr)
+{
+ if (attr)
+ {
+ if (!strcmp(attr, "Flat")) return 0;
+ if (!strcmp(attr, "Round")) return 1;
+ if (!strcmp(attr, "Square")) return 2;
+ if (!strcmp(attr, "Triangle")) return 3;
+ }
+ return 0;
+}
+
+void
+xps_clip(xps_document *doc, const fz_matrix *ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag)
+{
+ fz_path *path;
+ int fill_rule = 0;
+
+ if (clip_att)
+ path = xps_parse_abbreviated_geometry(doc, clip_att, &fill_rule);
+ else if (clip_tag)
+ path = xps_parse_path_geometry(doc, dict, clip_tag, 0, &fill_rule);
+ else
+ path = fz_new_path(doc->ctx);
+ fz_clip_path(doc->dev, path, NULL, fill_rule == 0, ctm);
+ fz_free_path(doc->ctx, path);
+}
+
+/*
+ * Parse an XPS <Path> element, and call relevant ghostscript
+ * functions for drawing and/or clipping the child elements.
+ */
+
+void
+xps_parse_path(xps_document *doc, const fz_matrix *ctm, char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ fz_xml *node;
+
+ char *fill_uri;
+ char *stroke_uri;
+ char *opacity_mask_uri;
+
+ char *transform_att;
+ char *clip_att;
+ char *data_att;
+ char *fill_att;
+ char *stroke_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *clip_tag = NULL;
+ fz_xml *data_tag = NULL;
+ fz_xml *fill_tag = NULL;
+ fz_xml *stroke_tag = NULL;
+ fz_xml *opacity_mask_tag = NULL;
+
+ char *fill_opacity_att = NULL;
+ char *stroke_opacity_att = NULL;
+
+ char *stroke_dash_array_att;
+ char *stroke_dash_cap_att;
+ char *stroke_dash_offset_att;
+ char *stroke_end_line_cap_att;
+ char *stroke_start_line_cap_att;
+ char *stroke_line_join_att;
+ char *stroke_miter_limit_att;
+ char *stroke_thickness_att;
+ char *navigate_uri_att;
+
+ fz_stroke_state *stroke = NULL;
+ fz_matrix transform;
+ float samples[32];
+ fz_colorspace *colorspace;
+ fz_path *path = NULL;
+ fz_path *stroke_path = NULL;
+ fz_rect area;
+ int fill_rule;
+ int dash_len = 0;
+ fz_matrix local_ctm;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ transform_att = fz_xml_att(root, "RenderTransform");
+ clip_att = fz_xml_att(root, "Clip");
+ data_att = fz_xml_att(root, "Data");
+ fill_att = fz_xml_att(root, "Fill");
+ stroke_att = fz_xml_att(root, "Stroke");
+ opacity_att = fz_xml_att(root, "Opacity");
+ opacity_mask_att = fz_xml_att(root, "OpacityMask");
+
+ stroke_dash_array_att = fz_xml_att(root, "StrokeDashArray");
+ stroke_dash_cap_att = fz_xml_att(root, "StrokeDashCap");
+ stroke_dash_offset_att = fz_xml_att(root, "StrokeDashOffset");
+ stroke_end_line_cap_att = fz_xml_att(root, "StrokeEndLineCap");
+ stroke_start_line_cap_att = fz_xml_att(root, "StrokeStartLineCap");
+ stroke_line_join_att = fz_xml_att(root, "StrokeLineJoin");
+ stroke_miter_limit_att = fz_xml_att(root, "StrokeMiterLimit");
+ stroke_thickness_att = fz_xml_att(root, "StrokeThickness");
+ navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "Path.RenderTransform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.OpacityMask"))
+ opacity_mask_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Clip"))
+ clip_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Fill"))
+ fill_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Stroke"))
+ stroke_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Data"))
+ data_tag = fz_xml_down(node);
+ }
+
+ fill_uri = base_uri;
+ stroke_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(doc, dict, &data_att, &data_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
+ xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ /*
+ * Act on the information we have gathered:
+ */
+
+ if (!data_att && !data_tag)
+ return;
+
+ if (fill_tag && !strcmp(fz_xml_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
+ fill_att = fz_xml_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (stroke_tag && !strcmp(fz_xml_tag(stroke_tag), "SolidColorBrush"))
+ {
+ stroke_opacity_att = fz_xml_att(stroke_tag, "Opacity");
+ stroke_att = fz_xml_att(stroke_tag, "Color");
+ stroke_tag = NULL;
+ }
+
+ if (stroke_att || stroke_tag)
+ {
+ if (stroke_dash_array_att)
+ {
+ char *s = stroke_dash_array_att;
+
+ while (*s)
+ {
+ while (*s == ' ')
+ s++;
+ if (*s) /* needed in case of a space before the last quote */
+ dash_len++;
+
+ while (*s && *s != ' ')
+ s++;
+ }
+ }
+ stroke = fz_new_stroke_state_with_len(doc->ctx, dash_len);
+ stroke->start_cap = xps_parse_line_cap(stroke_start_line_cap_att);
+ stroke->dash_cap = xps_parse_line_cap(stroke_dash_cap_att);
+ stroke->end_cap = xps_parse_line_cap(stroke_end_line_cap_att);
+
+ stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
+ if (stroke_line_join_att)
+ {
+ if (!strcmp(stroke_line_join_att, "Miter")) stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
+ if (!strcmp(stroke_line_join_att, "Round")) stroke->linejoin = FZ_LINEJOIN_ROUND;
+ if (!strcmp(stroke_line_join_att, "Bevel")) stroke->linejoin = FZ_LINEJOIN_BEVEL;
+ }
+
+ stroke->miterlimit = 10;
+ if (stroke_miter_limit_att)
+ stroke->miterlimit = fz_atof(stroke_miter_limit_att);
+
+ stroke->linewidth = 1;
+ if (stroke_thickness_att)
+ stroke->linewidth = fz_atof(stroke_thickness_att);
+
+ stroke->dash_phase = 0;
+ stroke->dash_len = 0;
+ if (stroke_dash_array_att)
+ {
+ char *s = stroke_dash_array_att;
+
+ if (stroke_dash_offset_att)
+ stroke->dash_phase = fz_atof(stroke_dash_offset_att) * stroke->linewidth;
+
+ while (*s)
+ {
+ while (*s == ' ')
+ s++;
+ if (*s) /* needed in case of a space before the last quote */
+ stroke->dash_list[stroke->dash_len++] = fz_atof(s) * stroke->linewidth;
+ while (*s && *s != ' ')
+ s++;
+ }
+ stroke->dash_len = dash_len;
+ }
+ }
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&local_ctm, &transform, ctm);
+
+ if (clip_att || clip_tag)
+ xps_clip(doc, &local_ctm, dict, clip_att, clip_tag);
+
+ fill_rule = 0;
+ if (data_att)
+ path = xps_parse_abbreviated_geometry(doc, data_att, &fill_rule);
+ else if (data_tag)
+ {
+ path = xps_parse_path_geometry(doc, dict, data_tag, 0, &fill_rule);
+ if (stroke_att || stroke_tag)
+ stroke_path = xps_parse_path_geometry(doc, dict, data_tag, 1, &fill_rule);
+ }
+ if (!stroke_path)
+ stroke_path = path;
+
+ if (stroke_att || stroke_tag)
+ {
+ fz_bound_path(doc->ctx, stroke_path, stroke, &local_ctm, &area);
+ if (stroke_path != path && (fill_att || fill_tag)) {
+ fz_rect bounds;
+ fz_bound_path(doc->ctx, path, NULL, &local_ctm, &bounds);
+ fz_union_rect(&area, &bounds);
+ }
+ }
+ else
+ fz_bound_path(doc->ctx, path, NULL, &local_ctm, &area);
+
+ if (navigate_uri_att)
+ xps_add_link(doc, &area, base_uri, navigate_uri_att);
+
+ xps_begin_opacity(doc, &local_ctm, &area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (fill_att)
+ {
+ xps_parse_color(doc, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] *= fz_atof(fill_opacity_att);
+ xps_set_color(doc, colorspace, samples);
+
+ fz_fill_path(doc->dev, path, fill_rule == 0, &local_ctm,
+ doc->colorspace, doc->color, doc->alpha);
+ }
+
+ if (fill_tag)
+ {
+ fz_clip_path(doc->dev, path, NULL, fill_rule == 0, &local_ctm);
+ xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag);
+ fz_pop_clip(doc->dev);
+ }
+
+ if (stroke_att)
+ {
+ xps_parse_color(doc, base_uri, stroke_att, &colorspace, samples);
+ if (stroke_opacity_att)
+ samples[0] *= fz_atof(stroke_opacity_att);
+ xps_set_color(doc, colorspace, samples);
+
+ fz_stroke_path(doc->dev, stroke_path, stroke, &local_ctm,
+ doc->colorspace, doc->color, doc->alpha);
+ }
+
+ if (stroke_tag)
+ {
+ fz_clip_stroke_path(doc->dev, stroke_path, NULL, stroke, &local_ctm);
+ xps_parse_brush(doc, &local_ctm, &area, stroke_uri, dict, stroke_tag);
+ fz_pop_clip(doc->dev);
+ }
+
+ xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (stroke_path != path)
+ fz_free_path(doc->ctx, stroke_path);
+ fz_free_path(doc->ctx, path);
+ path = NULL;
+ fz_drop_stroke_state(doc->ctx, stroke);
+
+ if (clip_att || clip_tag)
+ fz_pop_clip(doc->dev);
+}
diff --git a/source/xps/xps-resource.c b/source/xps/xps-resource.c
new file mode 100644
index 00000000..5c927e1d
--- /dev/null
+++ b/source/xps/xps-resource.c
@@ -0,0 +1,172 @@
+#include "mupdf/xps.h"
+
+static fz_xml *
+xps_lookup_resource(xps_document *doc, xps_resource *dict, char *name, char **urip)
+{
+ xps_resource *head, *node;
+ for (head = dict; head; head = head->parent)
+ {
+ for (node = head; node; node = node->next)
+ {
+ if (!strcmp(node->name, name))
+ {
+ if (urip && head->base_uri)
+ *urip = head->base_uri;
+ return node->data;
+ }
+ }
+ }
+ return NULL;
+}
+
+static fz_xml *
+xps_parse_resource_reference(xps_document *doc, xps_resource *dict, char *att, char **urip)
+{
+ char name[1024];
+ char *s;
+
+ if (strstr(att, "{StaticResource ") != att)
+ return NULL;
+
+ fz_strlcpy(name, att + 16, sizeof name);
+ s = strrchr(name, '}');
+ if (s)
+ *s = 0;
+
+ return xps_lookup_resource(doc, dict, name, urip);
+}
+
+void
+xps_resolve_resource_reference(xps_document *doc, xps_resource *dict,
+ char **attp, fz_xml **tagp, char **urip)
+{
+ if (*attp)
+ {
+ fz_xml *rsrc = xps_parse_resource_reference(doc, dict, *attp, urip);
+ if (rsrc)
+ {
+ *attp = NULL;
+ *tagp = rsrc;
+ }
+ }
+}
+
+static xps_resource *
+xps_parse_remote_resource_dictionary(xps_document *doc, char *base_uri, char *source_att)
+{
+ char part_name[1024];
+ char part_uri[1024];
+ xps_resource *dict;
+ xps_part *part;
+ fz_xml *xml;
+ char *s;
+ fz_context *ctx = doc->ctx;
+
+ /* External resource dictionaries MUST NOT reference other resource dictionaries */
+ xps_resolve_url(part_name, base_uri, source_att, sizeof part_name);
+ part = xps_read_part(doc, part_name);
+ fz_try(ctx)
+ {
+ xml = fz_parse_xml(doc->ctx, part->data, part->size);
+ }
+ fz_always(ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ xml = NULL;
+ }
+
+ if (!xml)
+ return NULL;
+
+ if (strcmp(fz_xml_tag(xml), "ResourceDictionary"))
+ {
+ fz_free_xml(doc->ctx, xml);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "expected ResourceDictionary element");
+ }
+
+ fz_strlcpy(part_uri, part_name, sizeof part_uri);
+ s = strrchr(part_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ dict = xps_parse_resource_dictionary(doc, part_uri, xml);
+ if (dict)
+ dict->base_xml = xml; /* pass on ownership */
+
+ return dict;
+}
+
+xps_resource *
+xps_parse_resource_dictionary(xps_document *doc, char *base_uri, fz_xml *root)
+{
+ xps_resource *head;
+ xps_resource *entry;
+ fz_xml *node;
+ char *source;
+ char *key;
+
+ source = fz_xml_att(root, "Source");
+ if (source)
+ return xps_parse_remote_resource_dictionary(doc, base_uri, source);
+
+ head = NULL;
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ key = fz_xml_att(node, "x:Key");
+ if (key)
+ {
+ entry = fz_malloc_struct(doc->ctx, xps_resource);
+ entry->name = key;
+ entry->base_uri = NULL;
+ entry->base_xml = NULL;
+ entry->data = node;
+ entry->next = head;
+ entry->parent = NULL;
+ head = entry;
+ }
+ }
+
+ if (head)
+ head->base_uri = fz_strdup(doc->ctx, base_uri);
+
+ return head;
+}
+
+void
+xps_free_resource_dictionary(xps_document *doc, xps_resource *dict)
+{
+ xps_resource *next;
+ while (dict)
+ {
+ next = dict->next;
+ if (dict->base_xml)
+ fz_free_xml(doc->ctx, dict->base_xml);
+ if (dict->base_uri)
+ fz_free(doc->ctx, dict->base_uri);
+ fz_free(doc->ctx, dict);
+ dict = next;
+ }
+}
+
+void
+xps_print_resource_dictionary(xps_resource *dict)
+{
+ while (dict)
+ {
+ if (dict->base_uri)
+ printf("URI = '%s'\n", dict->base_uri);
+ printf("KEY = '%s' VAL = %p\n", dict->name, dict->data);
+ if (dict->parent)
+ {
+ printf("PARENT = {\n");
+ xps_print_resource_dictionary(dict->parent);
+ printf("}\n");
+ }
+ dict = dict->next;
+ }
+}
diff --git a/source/xps/xps-tile.c b/source/xps/xps-tile.c
new file mode 100644
index 00000000..cbf3943d
--- /dev/null
+++ b/source/xps/xps-tile.c
@@ -0,0 +1,390 @@
+#include "mupdf/xps.h"
+
+#define TILE
+
+/*
+ * Parse a tiling brush (visual and image brushes at this time) common
+ * properties. Use the callback to draw the individual tiles.
+ */
+
+enum { TILE_NONE, TILE_TILE, TILE_FLIP_X, TILE_FLIP_Y, TILE_FLIP_X_Y };
+
+struct closure
+{
+ char *base_uri;
+ xps_resource *dict;
+ fz_xml *root;
+ void *user;
+ void (*func)(xps_document*, const fz_matrix *, const fz_rect *, char*, xps_resource*, fz_xml*, void*);
+};
+
+static void
+xps_paint_tiling_brush_clipped(xps_document *doc, const fz_matrix *ctm, const fz_rect *viewbox, struct closure *c)
+{
+ fz_path *path = fz_new_path(doc->ctx);
+ fz_moveto(doc->ctx, path, viewbox->x0, viewbox->y0);
+ fz_lineto(doc->ctx, path, viewbox->x0, viewbox->y1);
+ fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y1);
+ fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y0);
+ fz_closepath(doc->ctx, path);
+ fz_clip_path(doc->dev, path, NULL, 0, ctm);
+ fz_free_path(doc->ctx, path);
+ c->func(doc, ctm, viewbox, c->base_uri, c->dict, c->root, c->user);
+ fz_pop_clip(doc->dev);
+}
+
+static void
+xps_paint_tiling_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *viewbox, int tile_mode, struct closure *c)
+{
+ fz_matrix ttm;
+
+ xps_paint_tiling_brush_clipped(doc, ctm, viewbox, c);
+
+ if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = *ctm;
+ fz_pre_scale(fz_pre_translate(&ttm, viewbox->x1 * 2, 0), -1, 1);
+ xps_paint_tiling_brush_clipped(doc, &ttm, viewbox, c);
+ }
+
+ if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = *ctm;
+ fz_pre_scale(fz_pre_translate(&ttm, 0, viewbox->y1 * 2), 1, -1);
+ xps_paint_tiling_brush_clipped(doc, &ttm, viewbox, c);
+ }
+
+ if (tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = *ctm;
+ fz_pre_scale(fz_pre_translate(&ttm, viewbox->x1 * 2, viewbox->y1 * 2), -1, -1);
+ xps_paint_tiling_brush_clipped(doc, &ttm, viewbox, c);
+ }
+}
+
+void
+xps_parse_tiling_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root,
+ void (*func)(xps_document*, const fz_matrix*, const fz_rect*, char*, xps_resource*, fz_xml*, void*), void *user)
+{
+ fz_xml *node;
+ struct closure c;
+
+ char *opacity_att;
+ char *transform_att;
+ char *viewbox_att;
+ char *viewport_att;
+ char *tile_mode_att;
+
+ fz_xml *transform_tag = NULL;
+
+ fz_matrix transform;
+ fz_rect viewbox;
+ fz_rect viewport;
+ float xstep, ystep;
+ float xscale, yscale;
+ int tile_mode;
+
+ opacity_att = fz_xml_att(root, "Opacity");
+ transform_att = fz_xml_att(root, "Transform");
+ viewbox_att = fz_xml_att(root, "Viewbox");
+ viewport_att = fz_xml_att(root, "Viewport");
+ tile_mode_att = fz_xml_att(root, "TileMode");
+
+ c.base_uri = base_uri;
+ c.dict = dict;
+ c.root = root;
+ c.user = user;
+ c.func = func;
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "ImageBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "VisualBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ }
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&transform, &transform, ctm);
+
+ viewbox = fz_unit_rect;
+ if (viewbox_att)
+ xps_parse_rectangle(doc, viewbox_att, &viewbox);
+
+ viewport = fz_unit_rect;
+ if (viewport_att)
+ xps_parse_rectangle(doc, viewport_att, &viewport);
+
+ if (fabsf(viewport.x1 - viewport.x0) < 0.01f || fabsf(viewport.y1 - viewport.y0) < 0.01f)
+ fz_warn(doc->ctx, "not drawing tile for viewport size %.4f x %.4f", viewport.x1 - viewport.x0, viewport.y1 - viewport.y0);
+ else if (fabsf(viewbox.x1 - viewbox.x0) < 0.01f || fabsf(viewbox.y1 - viewbox.y0) < 0.01f)
+ fz_warn(doc->ctx, "not drawing tile for viewbox size %.4f x %.4f", viewbox.x1 - viewbox.x0, viewbox.y1 - viewbox.y0);
+
+ /* some sanity checks on the viewport/viewbox size */
+ if (fabsf(viewport.x1 - viewport.x0) < 0.01f) return;
+ if (fabsf(viewport.y1 - viewport.y0) < 0.01f) return;
+ if (fabsf(viewbox.x1 - viewbox.x0) < 0.01f) return;
+ if (fabsf(viewbox.y1 - viewbox.y0) < 0.01f) return;
+
+ xstep = viewbox.x1 - viewbox.x0;
+ ystep = viewbox.y1 - viewbox.y0;
+
+ xscale = (viewport.x1 - viewport.x0) / xstep;
+ yscale = (viewport.y1 - viewport.y0) / ystep;
+
+ tile_mode = TILE_NONE;
+ if (tile_mode_att)
+ {
+ if (!strcmp(tile_mode_att, "None"))
+ tile_mode = TILE_NONE;
+ if (!strcmp(tile_mode_att, "Tile"))
+ tile_mode = TILE_TILE;
+ if (!strcmp(tile_mode_att, "FlipX"))
+ tile_mode = TILE_FLIP_X;
+ if (!strcmp(tile_mode_att, "FlipY"))
+ tile_mode = TILE_FLIP_Y;
+ if (!strcmp(tile_mode_att, "FlipXY"))
+ tile_mode = TILE_FLIP_X_Y;
+ }
+
+ if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
+ xstep *= 2;
+ if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
+ ystep *= 2;
+
+ xps_begin_opacity(doc, &transform, area, base_uri, dict, opacity_att, NULL);
+
+ fz_pre_translate(&transform, viewport.x0, viewport.y0);
+ fz_pre_scale(&transform, xscale, yscale);
+ fz_pre_translate(&transform, -viewbox.x0, -viewbox.y0);
+
+ if (tile_mode != TILE_NONE)
+ {
+ int x0, y0, x1, y1;
+ fz_matrix invctm;
+ fz_rect local_area = *area;
+ area = fz_transform_rect(&local_area, fz_invert_matrix(&invctm, &transform));
+ x0 = floorf(local_area.x0 / xstep);
+ y0 = floorf(local_area.y0 / ystep);
+ x1 = ceilf(local_area.x1 / xstep);
+ y1 = ceilf(local_area.y1 / ystep);
+
+#ifdef TILE
+ if ((x1 - x0) * (y1 - y0) > 1)
+#else
+ if (0)
+#endif
+ {
+ fz_rect bigview = viewbox;
+ bigview.x1 = bigview.x0 + xstep;
+ bigview.y1 = bigview.y0 + ystep;
+ fz_begin_tile(doc->dev, &local_area, &bigview, xstep, ystep, &transform);
+ xps_paint_tiling_brush(doc, &transform, &viewbox, tile_mode, &c);
+ fz_end_tile(doc->dev);
+ }
+ else
+ {
+ int x, y;
+ for (y = y0; y < y1; y++)
+ {
+ for (x = x0; x < x1; x++)
+ {
+ fz_matrix ttm = transform;
+ fz_pre_translate(&ttm, xstep * x, ystep * y);
+ xps_paint_tiling_brush(doc, &ttm, &viewbox, tile_mode, &c);
+ }
+ }
+ }
+ }
+ else
+ {
+ xps_paint_tiling_brush(doc, &transform, &viewbox, tile_mode, &c);
+ }
+
+ xps_end_opacity(doc, base_uri, dict, opacity_att, NULL);
+}
+
+static void
+xps_paint_visual_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root, void *visual_tag)
+{
+ xps_parse_element(doc, ctm, area, base_uri, dict, (fz_xml *)visual_tag);
+}
+
+void
+xps_parse_visual_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ fz_xml *node;
+
+ char *visual_uri;
+ char *visual_att;
+ fz_xml *visual_tag = NULL;
+
+ visual_att = fz_xml_att(root, "Visual");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "VisualBrush.Visual"))
+ visual_tag = fz_xml_down(node);
+ }
+
+ visual_uri = base_uri;
+ xps_resolve_resource_reference(doc, dict, &visual_att, &visual_tag, &visual_uri);
+
+ if (visual_tag)
+ {
+ xps_parse_tiling_brush(doc, ctm, area,
+ visual_uri, dict, root, xps_paint_visual_brush, visual_tag);
+ }
+}
+
+void
+xps_parse_canvas(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_resource *new_dict = NULL;
+ fz_xml *node;
+ char *opacity_mask_uri;
+
+ char *transform_att;
+ char *clip_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+ char *navigate_uri_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *clip_tag = NULL;
+ fz_xml *opacity_mask_tag = NULL;
+
+ fz_matrix transform;
+
+ transform_att = fz_xml_att(root, "RenderTransform");
+ clip_att = fz_xml_att(root, "Clip");
+ opacity_att = fz_xml_att(root, "Opacity");
+ opacity_mask_att = fz_xml_att(root, "OpacityMask");
+ navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "Canvas.Resources") && fz_xml_down(node))
+ {
+ if (new_dict)
+ {
+ fz_warn(doc->ctx, "ignoring follow-up resource dictionaries");
+ }
+ else
+ {
+ new_dict = xps_parse_resource_dictionary(doc, base_uri, fz_xml_down(node));
+ if (new_dict)
+ {
+ new_dict->parent = dict;
+ dict = new_dict;
+ }
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(node), "Canvas.RenderTransform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Canvas.Clip"))
+ clip_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Canvas.OpacityMask"))
+ opacity_mask_tag = fz_xml_down(node);
+ }
+
+ opacity_mask_uri = base_uri;
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&transform, &transform, ctm);
+
+ if (navigate_uri_att)
+ xps_add_link(doc, area, base_uri, navigate_uri_att);
+
+ if (clip_att || clip_tag)
+ xps_clip(doc, &transform, dict, clip_att, clip_tag);
+
+ xps_begin_opacity(doc, &transform, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ xps_parse_element(doc, &transform, area, base_uri, dict, node);
+ }
+
+ xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (clip_att || clip_tag)
+ fz_pop_clip(doc->dev);
+
+ if (new_dict)
+ xps_free_resource_dictionary(doc, new_dict);
+}
+
+void
+xps_parse_fixed_page(xps_document *doc, const fz_matrix *ctm, xps_page *page)
+{
+ fz_xml *node;
+ xps_resource *dict;
+ char base_uri[1024];
+ fz_rect area;
+ char *s;
+ fz_matrix scm;
+
+ fz_strlcpy(base_uri, page->name, sizeof base_uri);
+ s = strrchr(base_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ dict = NULL;
+
+ doc->opacity_top = 0;
+ doc->opacity[0] = 1;
+
+ if (!page->root)
+ return;
+
+ area = fz_unit_rect;
+ fz_transform_rect(&area, fz_scale(&scm, page->width, page->height));
+
+ for (node = fz_xml_down(page->root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "FixedPage.Resources") && fz_xml_down(node))
+ {
+ if (dict)
+ fz_warn(doc->ctx, "ignoring follow-up resource dictionaries");
+ else
+ dict = xps_parse_resource_dictionary(doc, base_uri, fz_xml_down(node));
+ }
+ xps_parse_element(doc, ctm, &area, base_uri, dict, node);
+ }
+
+ if (dict)
+ xps_free_resource_dictionary(doc, dict);
+}
+
+void
+xps_run_page(xps_document *doc, xps_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ fz_matrix page_ctm = *ctm;
+
+ fz_pre_scale(&page_ctm, 72.0f / 96.0f, 72.0f / 96.0f);
+
+ doc->cookie = cookie;
+ doc->dev = dev;
+ xps_parse_fixed_page(doc, &page_ctm, page);
+ doc->cookie = NULL;
+ doc->dev = NULL;
+ page->links_resolved = 1;
+}
diff --git a/source/xps/xps-util.c b/source/xps/xps-util.c
new file mode 100644
index 00000000..a74dc30f
--- /dev/null
+++ b/source/xps/xps-util.c
@@ -0,0 +1,165 @@
+#include "mupdf/xps.h"
+
+static inline int xps_tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+int
+xps_strcasecmp(char *a, char *b)
+{
+ while (xps_tolower(*a) == xps_tolower(*b))
+ {
+ if (*a++ == 0)
+ return 0;
+ b++;
+ }
+ return xps_tolower(*a) - xps_tolower(*b);
+}
+
+/* A URL is defined as consisting of a:
+ * SCHEME (e.g. http:)
+ * AUTHORITY (username, password, hostname, port, eg //test:passwd@mupdf.com:999)
+ * PATH (e.g. /download)
+ * QUERY (e.g. ?view=page)
+ * FRAGMENT (e.g. #fred) (not strictly part of the URL)
+ */
+static char *
+skip_scheme(char *path)
+{
+ char *p = path;
+
+ /* Skip over: alpha *(alpha | digit | "+" | "-" | ".") looking for : */
+ if (*p >= 'a' && *p <= 'z')
+ {}
+ else if (*p >= 'A' && *p <= 'Z')
+ {}
+ else
+ return path;
+
+ while (*++p)
+ {
+ if (*p >= 'a' && *p <= 'z')
+ {}
+ else if (*p >= 'A' && *p <= 'Z')
+ {}
+ else if (*p >= '0' && *p <= '9')
+ {}
+ else if (*p == '+')
+ {}
+ else if (*p == '-')
+ {}
+ else if (*p == '.')
+ {}
+ else if (*p == ':')
+ return p+1;
+ else
+ break;
+ }
+ return path;
+}
+
+static char *
+skip_authority(char *path)
+{
+ char *p = path;
+
+ /* Authority section must start with '//' */
+ if (p[0] != '/' || p[1] != '/')
+ return path;
+ p += 2;
+
+ /* Authority is terminated by end of URL, '/' or '?' */
+ while (*p && *p != '/' && *p != '?')
+ p++;
+
+ return p;
+}
+
+#define SEP(x) ((x)=='/' || (x) == 0)
+
+static char *
+xps_clean_path(char *name)
+{
+ char *p, *q, *dotdot, *start;
+ int rooted;
+
+ start = skip_scheme(name);
+ start = skip_authority(start);
+ rooted = start[0] == '/';
+
+ /*
+ * invariants:
+ * p points at beginning of path element we're considering.
+ * q points just past the last path element we wrote (no slash).
+ * dotdot points just past the point where .. cannot backtrack
+ * any further (no slash).
+ */
+ p = q = dotdot = start + rooted;
+ while (*p)
+ {
+ if(p[0] == '/') /* null element */
+ p++;
+ else if (p[0] == '.' && SEP(p[1]))
+ p += 1; /* don't count the separator in case it is nul */
+ else if (p[0] == '.' && p[1] == '.' && SEP(p[2]))
+ {
+ p += 2;
+ if (q > dotdot) /* can backtrack */
+ {
+ while(--q > dotdot && *q != '/')
+ ;
+ }
+ else if (!rooted) /* /.. is / but ./../ is .. */
+ {
+ if (q != start)
+ *q++ = '/';
+ *q++ = '.';
+ *q++ = '.';
+ dotdot = q;
+ }
+ }
+ else /* real path element */
+ {
+ if (q != start+rooted)
+ *q++ = '/';
+ while ((*q = *p) != '/' && *q != 0)
+ p++, q++;
+ }
+ }
+
+ if (q == start) /* empty string is really "." */
+ *q++ = '.';
+ *q = '\0';
+
+ return name;
+}
+
+void
+xps_resolve_url(char *output, char *base_uri, char *path, int output_size)
+{
+ char *p = skip_authority(skip_scheme(path));
+
+ if (p != path || path[0] == '/')
+ {
+ fz_strlcpy(output, path, output_size);
+ }
+ else
+ {
+ int len = fz_strlcpy(output, base_uri, output_size);
+ if (len == 0 || output[len-1] != '/')
+ fz_strlcat(output, "/", output_size);
+ fz_strlcat(output, path, output_size);
+ }
+ xps_clean_path(output);
+}
+
+int
+xps_url_is_remote(char *path)
+{
+ char *p = skip_authority(skip_scheme(path));
+
+ return p != path;
+}
diff --git a/source/xps/xps-zip.c b/source/xps/xps-zip.c
new file mode 100644
index 00000000..70b643af
--- /dev/null
+++ b/source/xps/xps-zip.c
@@ -0,0 +1,697 @@
+#include "mupdf/xps.h"
+
+#include <zlib.h>
+
+#define ZIP_LOCAL_FILE_SIG 0x04034b50
+#define ZIP_DATA_DESC_SIG 0x08074b50
+#define ZIP_CENTRAL_DIRECTORY_SIG 0x02014b50
+#define ZIP_END_OF_CENTRAL_DIRECTORY_SIG 0x06054b50
+
+#define ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG 0x07064b50
+#define ZIP64_END_OF_CENTRAL_DIRECTORY_SIG 0x06064b50
+#define ZIP64_EXTRA_FIELD_SIG 0x0001
+
+static void xps_init_document(xps_document *doc);
+
+xps_part *
+xps_new_part(xps_document *doc, char *name, int size)
+{
+ xps_part *part;
+
+ part = fz_malloc(doc->ctx, sizeof(xps_part));
+ part->name = fz_strdup(doc->ctx, name);
+ part->size = size;
+ part->data = fz_malloc(doc->ctx, size + 1);
+ part->data[size] = 0; /* null-terminate for xml parser */
+
+ return part;
+}
+
+void
+xps_free_part(xps_document *doc, xps_part *part)
+{
+ fz_free(doc->ctx, part->name);
+ fz_free(doc->ctx, part->data);
+ fz_free(doc->ctx, part);
+}
+
+static inline int getshort(fz_stream *file)
+{
+ int a = fz_read_byte(file);
+ int b = fz_read_byte(file);
+ return a | b << 8;
+}
+
+static inline int getlong(fz_stream *file)
+{
+ int a = fz_read_byte(file);
+ int b = fz_read_byte(file);
+ int c = fz_read_byte(file);
+ int d = fz_read_byte(file);
+ return a | b << 8 | c << 16 | d << 24;
+}
+
+static inline int getlong64(fz_stream *file)
+{
+ int a = getlong(file);
+ int b = getlong(file);
+ return b != 0 ? -1 : a;
+}
+
+static void *
+xps_zip_alloc_items(xps_document *doc, int items, int size)
+{
+ return fz_malloc_array(doc->ctx, items, size);
+}
+
+static void
+xps_zip_free(xps_document *doc, void *ptr)
+{
+ fz_free(doc->ctx, ptr);
+}
+
+static int
+xps_compare_entries(const void *a0, const void *b0)
+{
+ xps_entry *a = (xps_entry*) a0;
+ xps_entry *b = (xps_entry*) b0;
+ return xps_strcasecmp(a->name, b->name);
+}
+
+static xps_entry *
+xps_lookup_zip_entry(xps_document *doc, char *name)
+{
+ int l = 0;
+ int r = doc->zip_count - 1;
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = xps_strcasecmp(name, doc->zip_table[m].name);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return &doc->zip_table[m];
+ }
+ return NULL;
+}
+
+static void
+xps_read_zip_entry(xps_document *doc, xps_entry *ent, unsigned char *outbuf)
+{
+ z_stream stream;
+ unsigned char *inbuf;
+ int sig;
+ int method;
+ int namelength, extralength;
+ int code;
+ fz_context *ctx = doc->ctx;
+
+ fz_seek(doc->file, ent->offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP_LOCAL_FILE_SIG)
+ {
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip local file signature (0x%x)", sig);
+ }
+
+ (void) getshort(doc->file); /* version */
+ (void) getshort(doc->file); /* general */
+ method = getshort(doc->file);
+ (void) getshort(doc->file); /* file time */
+ (void) getshort(doc->file); /* file date */
+ (void) getlong(doc->file); /* crc-32 */
+ (void) getlong(doc->file); /* csize */
+ (void) getlong(doc->file); /* usize */
+ namelength = getshort(doc->file);
+ extralength = getshort(doc->file);
+
+ fz_seek(doc->file, namelength + extralength, 1);
+
+ if (method == 0)
+ {
+ fz_read(doc->file, outbuf, ent->usize);
+ }
+ else if (method == 8)
+ {
+ inbuf = fz_malloc(ctx, ent->csize);
+
+ fz_read(doc->file, inbuf, ent->csize);
+
+ memset(&stream, 0, sizeof(z_stream));
+ stream.zalloc = (alloc_func) xps_zip_alloc_items;
+ stream.zfree = (free_func) xps_zip_free;
+ stream.opaque = doc;
+ stream.next_in = inbuf;
+ stream.avail_in = ent->csize;
+ stream.next_out = outbuf;
+ stream.avail_out = ent->usize;
+
+ code = inflateInit2(&stream, -15);
+ if (code != Z_OK)
+ {
+ fz_free(ctx, inbuf);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflateInit2 error: %s", stream.msg);
+ }
+ code = inflate(&stream, Z_FINISH);
+ if (code != Z_STREAM_END)
+ {
+ inflateEnd(&stream);
+ fz_free(ctx, inbuf);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflate error: %s", stream.msg);
+ }
+ code = inflateEnd(&stream);
+ if (code != Z_OK)
+ {
+ fz_free(ctx, inbuf);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflateEnd error: %s", stream.msg);
+ }
+
+ fz_free(ctx, inbuf);
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown compression method (%d)", method);
+ }
+}
+
+/*
+ * Read the central directory in a zip file.
+ */
+
+static void
+xps_read_zip_dir(xps_document *doc, int start_offset)
+{
+ int sig;
+ int offset, count;
+ int namesize, metasize, commentsize;
+ int i;
+
+ fz_seek(doc->file, start_offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP_END_OF_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip end of central directory signature (0x%x)", sig);
+
+ (void) getshort(doc->file); /* this disk */
+ (void) getshort(doc->file); /* start disk */
+ (void) getshort(doc->file); /* entries in this disk */
+ count = getshort(doc->file); /* entries in central directory disk */
+ (void) getlong(doc->file); /* size of central directory */
+ offset = getlong(doc->file); /* offset to central directory */
+
+ /* ZIP64 */
+ if (count == 0xFFFF)
+ {
+ fz_seek(doc->file, start_offset - 20, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip64 end of central directory locator signature (0x%x)", sig);
+
+ (void) getlong(doc->file); /* start disk */
+ offset = getlong64(doc->file); /* offset to end of central directory record */
+ if (offset < 0)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "zip64 files larger than 2 GB aren't supported");
+
+ fz_seek(doc->file, offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP64_END_OF_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip64 end of central directory signature (0x%x)", sig);
+
+ (void) getlong64(doc->file); /* size of record */
+ (void) getshort(doc->file); /* version made by */
+ (void) getshort(doc->file); /* version to extract */
+ (void) getlong(doc->file); /* disk number */
+ (void) getlong(doc->file); /* disk number start */
+ count = getlong64(doc->file); /* entries in central directory disk */
+ (void) getlong64(doc->file); /* entries in central directory */
+ (void) getlong64(doc->file); /* size of central directory */
+ offset = getlong64(doc->file); /* offset to central directory */
+
+ if (count < 0 || offset < 0)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "zip64 files larger than 2 GB aren't supported");
+ }
+
+ doc->zip_table = fz_malloc_array(doc->ctx, count, sizeof(xps_entry));
+ memset(doc->zip_table, 0, count * sizeof(xps_entry));
+ doc->zip_count = count;
+
+ fz_seek(doc->file, offset, 0);
+
+ for (i = 0; i < count; i++)
+ {
+ sig = getlong(doc->file);
+ if (sig != ZIP_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip central directory signature (0x%x)", sig);
+
+ (void) getshort(doc->file); /* version made by */
+ (void) getshort(doc->file); /* version to extract */
+ (void) getshort(doc->file); /* general */
+ (void) getshort(doc->file); /* method */
+ (void) getshort(doc->file); /* last mod file time */
+ (void) getshort(doc->file); /* last mod file date */
+ (void) getlong(doc->file); /* crc-32 */
+ doc->zip_table[i].csize = getlong(doc->file);
+ doc->zip_table[i].usize = getlong(doc->file);
+ namesize = getshort(doc->file);
+ metasize = getshort(doc->file);
+ commentsize = getshort(doc->file);
+ (void) getshort(doc->file); /* disk number start */
+ (void) getshort(doc->file); /* int file atts */
+ (void) getlong(doc->file); /* ext file atts */
+ doc->zip_table[i].offset = getlong(doc->file);
+
+ doc->zip_table[i].name = fz_malloc(doc->ctx, namesize + 1);
+ fz_read(doc->file, (unsigned char*)doc->zip_table[i].name, namesize);
+ doc->zip_table[i].name[namesize] = 0;
+
+ while (metasize > 0)
+ {
+ int type = getshort(doc->file);
+ int size = getshort(doc->file);
+ if (type == ZIP64_EXTRA_FIELD_SIG)
+ {
+ doc->zip_table[i].usize = getlong64(doc->file);
+ doc->zip_table[i].csize = getlong64(doc->file);
+ doc->zip_table[i].offset = getlong64(doc->file);
+ fz_seek(doc->file, -24, 1);
+ }
+ fz_seek(doc->file, size, 1);
+ metasize -= 4 + size;
+ }
+ if (doc->zip_table[i].usize < 0 || doc->zip_table[i].csize < 0 || doc->zip_table[i].offset < 0)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "zip64 files larger than 2 GB aren't supported");
+
+ fz_seek(doc->file, commentsize, 1);
+ }
+
+ qsort(doc->zip_table, count, sizeof(xps_entry), xps_compare_entries);
+}
+
+static void
+xps_find_and_read_zip_dir(xps_document *doc)
+{
+ unsigned char buf[512];
+ int file_size, back, maxback;
+ int i, n;
+ fz_context *ctx = doc->ctx;
+
+ fz_seek(doc->file, 0, SEEK_END);
+ file_size = fz_tell(doc->file);
+
+ maxback = fz_mini(file_size, 0xFFFF + sizeof buf);
+ back = fz_mini(maxback, sizeof buf);
+
+ while (back < maxback)
+ {
+ fz_seek(doc->file, file_size - back, 0);
+ n = fz_read(doc->file, buf, sizeof buf);
+ for (i = n - 4; i > 0; i--)
+ {
+ if (!memcmp(buf + i, "PK\5\6", 4))
+ {
+ xps_read_zip_dir(doc, file_size - back + i);
+ return;
+ }
+ }
+
+ back += sizeof buf - 4;
+ }
+
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find end of central directory");
+}
+
+/*
+ * Read and interleave split parts from a ZIP file.
+ */
+static xps_part *
+xps_read_zip_part(xps_document *doc, char *partname)
+{
+ char buf[2048];
+ xps_entry *ent;
+ xps_part *part;
+ int count, size, offset, i;
+ char *name;
+ int seen_last = 0;
+
+ name = partname;
+ if (name[0] == '/')
+ name ++;
+
+ /* All in one piece */
+ ent = xps_lookup_zip_entry(doc, name);
+ if (ent)
+ {
+ part = xps_new_part(doc, partname, ent->usize);
+ fz_try(doc->ctx)
+ {
+ xps_read_zip_entry(doc, ent, part->data);
+ }
+ fz_catch(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ fz_rethrow(doc->ctx);
+ }
+ return part;
+ }
+
+ /* Count the number of pieces and their total size */
+ count = 0;
+ size = 0;
+ while (!seen_last)
+ {
+ sprintf(buf, "%s/[%d].piece", name, count);
+ ent = xps_lookup_zip_entry(doc, buf);
+ if (!ent)
+ {
+ sprintf(buf, "%s/[%d].last.piece", name, count);
+ ent = xps_lookup_zip_entry(doc, buf);
+ seen_last = (ent != NULL);
+ }
+ if (!ent)
+ break;
+ count ++;
+ size += ent->usize;
+ }
+ if (!seen_last)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find all pieces for part '%s'", partname);
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(doc, partname, size);
+ offset = 0;
+ for (i = 0; i < count; i++)
+ {
+ if (i < count - 1)
+ sprintf(buf, "%s/[%d].piece", name, i);
+ else
+ sprintf(buf, "%s/[%d].last.piece", name, i);
+ ent = xps_lookup_zip_entry(doc, buf);
+ fz_try(doc->ctx)
+ {
+ xps_read_zip_entry(doc, ent, part->data + offset);
+ }
+ fz_catch(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ fz_rethrow(doc->ctx);
+ }
+ offset += ent->usize;
+ }
+ return part;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find part '%s'", partname);
+ return NULL;
+}
+
+static int
+xps_has_zip_part(xps_document *doc, char *name)
+{
+ char buf[2048];
+ if (name[0] == '/')
+ name++;
+ if (xps_lookup_zip_entry(doc, name))
+ return 1;
+ sprintf(buf, "%s/[0].piece", name);
+ if (xps_lookup_zip_entry(doc, buf))
+ return 1;
+ sprintf(buf, "%s/[0].last.piece", name);
+ if (xps_lookup_zip_entry(doc, buf))
+ return 1;
+ return 0;
+}
+
+/*
+ * Read and interleave split parts from files in the directory.
+ */
+static xps_part *
+xps_read_dir_part(xps_document *doc, char *name)
+{
+ char buf[2048];
+ xps_part *part;
+ FILE *file;
+ int count, size, offset, i, n;
+ int seen_last = 0;
+
+ fz_strlcpy(buf, doc->directory, sizeof buf);
+ fz_strlcat(buf, name, sizeof buf);
+
+ /* All in one piece */
+ file = fopen(buf, "rb");
+ if (file)
+ {
+ fseek(file, 0, SEEK_END);
+ size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+ part = xps_new_part(doc, name, size);
+ fread(part->data, 1, size, file);
+ fclose(file);
+ return part;
+ }
+
+ /* Count the number of pieces and their total size */
+ count = 0;
+ size = 0;
+ while (!seen_last)
+ {
+ sprintf(buf, "%s%s/[%d].piece", doc->directory, name, count);
+ file = fopen(buf, "rb");
+ if (!file)
+ {
+ sprintf(buf, "%s%s/[%d].last.piece", doc->directory, name, count);
+ file = fopen(buf, "rb");
+ seen_last = (file != NULL);
+ }
+ if (!file)
+ break;
+ count ++;
+ fseek(file, 0, SEEK_END);
+ size += ftell(file);
+ fclose(file);
+ }
+ if (!seen_last)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find all pieces for part '%s'", name);
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(doc, name, size);
+ offset = 0;
+ for (i = 0; i < count; i++)
+ {
+ if (i < count - 1)
+ sprintf(buf, "%s%s/[%d].piece", doc->directory, name, i);
+ else
+ sprintf(buf, "%s%s/[%d].last.piece", doc->directory, name, i);
+ file = fopen(buf, "rb");
+ if (!file)
+ {
+ xps_free_part(doc, part);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot open file '%s'", buf);
+ }
+ n = fread(part->data + offset, 1, size - offset, file);
+ offset += n;
+ fclose(file);
+ }
+ return part;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find part '%s'", name);
+ return NULL;
+}
+
+static int
+file_exists(xps_document *doc, char *name)
+{
+ char buf[2048];
+ FILE *file;
+ fz_strlcpy(buf, doc->directory, sizeof buf);
+ fz_strlcat(buf, name, sizeof buf);
+ file = fopen(buf, "rb");
+ if (file)
+ {
+ fclose(file);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+xps_has_dir_part(xps_document *doc, char *name)
+{
+ char buf[2048];
+ if (file_exists(doc, name))
+ return 1;
+ sprintf(buf, "%s/[0].piece", name);
+ if (file_exists(doc, buf))
+ return 1;
+ sprintf(buf, "%s/[0].last.piece", name);
+ if (file_exists(doc, buf))
+ return 1;
+ return 0;
+}
+
+xps_part *
+xps_read_part(xps_document *doc, char *partname)
+{
+ if (doc->directory)
+ return xps_read_dir_part(doc, partname);
+ return xps_read_zip_part(doc, partname);
+}
+
+int
+xps_has_part(xps_document *doc, char *partname)
+{
+ if (doc->directory)
+ return xps_has_dir_part(doc, partname);
+ return xps_has_zip_part(doc, partname);
+}
+
+static xps_document *
+xps_open_document_with_directory(fz_context *ctx, const char *directory)
+{
+ xps_document *doc;
+
+ doc = fz_malloc_struct(ctx, xps_document);
+ xps_init_document(doc);
+ doc->ctx = ctx;
+ doc->directory = fz_strdup(ctx, directory);
+
+ fz_try(ctx)
+ {
+ xps_read_page_list(doc);
+ }
+ fz_catch(ctx)
+ {
+ xps_close_document(doc);
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+xps_document *
+xps_open_document_with_stream(fz_context *ctx, fz_stream *file)
+{
+ xps_document *doc;
+
+ doc = fz_malloc_struct(ctx, xps_document);
+ xps_init_document(doc);
+ doc->ctx = ctx;
+ doc->file = fz_keep_stream(file);
+
+ fz_try(ctx)
+ {
+ xps_find_and_read_zip_dir(doc);
+ xps_read_page_list(doc);
+ }
+ fz_catch(ctx)
+ {
+ xps_close_document(doc);
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+xps_document *
+xps_open_document(fz_context *ctx, const char *filename)
+{
+ char buf[2048];
+ fz_stream *file;
+ char *p;
+ xps_document *doc;
+
+ if (strstr(filename, "/_rels/.rels") || strstr(filename, "\\_rels\\.rels"))
+ {
+ fz_strlcpy(buf, filename, sizeof buf);
+ p = strstr(buf, "/_rels/.rels");
+ if (!p)
+ p = strstr(buf, "\\_rels\\.rels");
+ *p = 0;
+ return xps_open_document_with_directory(ctx, buf);
+ }
+
+ file = fz_open_file(ctx, filename);
+ if (!file)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ fz_try(ctx)
+ {
+ doc = xps_open_document_with_stream(ctx, file);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load document '%s'", filename);
+ }
+ return doc;
+}
+
+void
+xps_close_document(xps_document *doc)
+{
+ xps_font_cache *font, *next;
+ int i;
+
+ if (!doc)
+ return;
+
+ if (doc->file)
+ fz_close(doc->file);
+
+ for (i = 0; i < doc->zip_count; i++)
+ fz_free(doc->ctx, doc->zip_table[i].name);
+ fz_free(doc->ctx, doc->zip_table);
+
+ font = doc->font_table;
+ while (font)
+ {
+ next = font->next;
+ fz_drop_font(doc->ctx, font->font);
+ fz_free(doc->ctx, font->name);
+ fz_free(doc->ctx, font);
+ font = next;
+ }
+
+ xps_free_page_list(doc);
+
+ fz_free(doc->ctx, doc->start_part);
+ fz_free(doc->ctx, doc->directory);
+ fz_free(doc->ctx, doc);
+}
+
+static int
+xps_meta(xps_document *doc, int key, void *ptr, int size)
+{
+ switch (key)
+ {
+ case FZ_META_FORMAT_INFO:
+ sprintf((char *)ptr, "XPS");
+ return FZ_META_OK;
+ default:
+ return FZ_META_UNKNOWN_KEY;
+ }
+}
+
+static void
+xps_init_document(xps_document *doc)
+{
+ doc->super.close = (void*)xps_close_document;
+ doc->super.load_outline = (void*)xps_load_outline;
+ doc->super.count_pages = (void*)xps_count_pages;
+ doc->super.load_page = (void*)xps_load_page;
+ doc->super.load_links = (void*)xps_load_links;
+ doc->super.bound_page = (void*)xps_bound_page;
+ doc->super.run_page_contents = (void*)xps_run_page;
+ doc->super.free_page = (void*)xps_free_page;
+ doc->super.meta = (void*)xps_meta;
+}