summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2011-04-03 19:59:11 +0200
committerTor Andersson <tor.andersson@artifex.com>2011-04-03 19:59:11 +0200
commit5ea6c5701e7ec3f738a8adb6d20790edcda781ba (patch)
tree38352e2cff9f1b9ae7f48d20480b5a8088624de9
parenta5bfe0eef4361ec3ca8190b2533eca041eb7ef61 (diff)
parentbc5be11061e7687a80bcda6edc9bf3c136529111 (diff)
downloadmupdf-5ea6c5701e7ec3f738a8adb6d20790edcda781ba.tar.xz
Merge branch 'xps'
-rw-r--r--Makefile39
-rw-r--r--Makerules2
-rw-r--r--apps/pdfapp.c179
-rw-r--r--apps/pdfapp.h8
-rw-r--r--apps/win_main.c15
-rw-r--r--apps/x11_main.c1
-rw-r--r--apps/xpsdraw.c367
-rw-r--r--draw/imageunpack.c10
-rw-r--r--draw/meshdraw.c3
-rw-r--r--fitz/fitz.h12
-rw-r--r--fitz/obj_simple.c32
-rw-r--r--fitz/res_font.c28
-rw-r--r--fitz/res_path.c36
-rw-r--r--fitz/res_pixmap.c38
-rw-r--r--mupdf/mupdf.h2
-rw-r--r--mupdf/pdf_page.c6
-rw-r--r--mupdf/pdf_shade.c2
-rw-r--r--mupdf/pdf_xref.c28
-rw-r--r--win32/libmupdf.vcproj64
-rw-r--r--win32/mupdf.vcproj4
-rw-r--r--xps/muxps.h247
-rw-r--r--xps/xps_common.c282
-rw-r--r--xps/xps_doc.c339
-rw-r--r--xps/xps_glyphs.c559
-rw-r--r--xps/xps_gradient.c458
-rw-r--r--xps/xps_hash.c190
-rw-r--r--xps/xps_image.c128
-rw-r--r--xps/xps_jpeg.c131
-rw-r--r--xps/xps_path.c990
-rw-r--r--xps/xps_png.c574
-rw-r--r--xps/xps_resource.c187
-rw-r--r--xps/xps_tiff.c853
-rw-r--r--xps/xps_tile.c347
-rw-r--r--xps/xps_util.c94
-rw-r--r--xps/xps_xml.c387
-rw-r--r--xps/xps_zip.c472
36 files changed, 7007 insertions, 107 deletions
diff --git a/Makefile b/Makefile
index 9479ffad..da793520 100644
--- a/Makefile
+++ b/Makefile
@@ -170,6 +170,26 @@ MUPDF_SRC := \
MUPDF_OBJ := $(MUPDF_SRC:mupdf/%.c=$(OBJDIR)/%.o)
$(MUPDF_OBJ): $(MUPDF_HDR)
+MUXPS_HDR := $(FITZ_HDR) xps/muxps.h
+MUXPS_SRC := \
+ xps/xps_common.c \
+ xps/xps_doc.c \
+ xps/xps_glyphs.c \
+ xps/xps_gradient.c \
+ xps/xps_hash.c \
+ xps/xps_image.c \
+ xps/xps_jpeg.c \
+ xps/xps_path.c \
+ xps/xps_png.c \
+ xps/xps_resource.c \
+ xps/xps_tiff.c \
+ xps/xps_tile.c \
+ xps/xps_util.c \
+ xps/xps_xml.c \
+ xps/xps_zip.c
+MUXPS_OBJ := $(MUXPS_SRC:xps/%.c=$(OBJDIR)/%.o)
+$(MUXPS_OBJ): $(MUXPS_HDR)
+
$(OBJDIR)/%.o: fitz/%.c
$(CC_CMD)
$(OBJDIR)/%.o: draw/%.c
@@ -178,6 +198,8 @@ $(OBJDIR)/%.o: draw/%.s
$(CC_CMD)
$(OBJDIR)/%.o: mupdf/%.c
$(CC_CMD)
+$(OBJDIR)/%.o: xps/%.c
+ $(CC_CMD)
$(OBJDIR)/%.o: $(GENDIR)/%.c
$(CC_CMD)
@@ -298,11 +320,15 @@ MUPDF_LIB = $(OBJDIR)/libmupdf.a
$(MUPDF_LIB): $(FITZ_OBJ) $(DRAW_OBJ) $(MUPDF_OBJ) $(CMAP_OBJ) $(FONT_OBJ)
$(AR_CMD)
+MUXPS_LIB = $(OBJDIR)/libmuxps.a
+$(MUXPS_LIB): $(FITZ_OBJ) $(DRAW_OBJ) $(MUXPS_OBJ)
+ $(AR_CMD)
+
#
# Applications
#
-APPS = $(PDFSHOW_EXE) $(PDFCLEAN_EXE) $(PDFDRAW_EXE) $(PDFEXTRACT_EXE) $(PDFINFO_EXE) $(PDFVIEW_EXE)
+APPS = $(PDFSHOW_EXE) $(PDFCLEAN_EXE) $(PDFDRAW_EXE) $(PDFEXTRACT_EXE) $(PDFINFO_EXE) $(PDFVIEW_EXE) $(XPSDRAW_EXE)
APPS_MAN = \
apps/man/mupdf.1 \
@@ -348,6 +374,13 @@ $(PDFINFO_OBJ): $(MUPDF_HDR)
$(PDFINFO_EXE): $(PDFINFO_OBJ) $(MUPDF_LIB) $(THIRD_LIBS)
$(LD_CMD)
+XPSDRAW_SRC=apps/xpsdraw.c
+XPSDRAW_OBJ=$(XPSDRAW_SRC:apps/%.c=$(OBJDIR)/%.o)
+XPSDRAW_EXE=$(OBJDIR)/xpsdraw
+$(XPSDRAW_OBJ): $(MUXPS_HDR)
+$(XPSDRAW_EXE): $(XPSDRAW_OBJ) $(MUXPS_LIB) $(THIRD_LIBS)
+ $(LD_CMD)
+
PDFAPP_HDR = apps/pdfapp.h
X11VIEW_SRC=apps/x11_main.c apps/x11_image.c apps/pdfapp.c
@@ -355,7 +388,7 @@ X11VIEW_OBJ=$(X11VIEW_SRC:apps/%.c=$(OBJDIR)/%.o)
X11VIEW_EXE=$(OBJDIR)/mupdf
$(X11VIEW_OBJ): $(MUPDF_HDR) $(PDFAPP_HDR)
-$(X11VIEW_EXE): $(X11VIEW_OBJ) $(MUPDF_LIB) $(THIRD_LIBS)
+$(X11VIEW_EXE): $(X11VIEW_OBJ) $(MUPDF_LIB) $(MUXPS_LIB) $(THIRD_LIBS)
$(LD_CMD) $(X11LIBS)
WINVIEW_SRC=apps/win_main.c apps/pdfapp.c
@@ -367,7 +400,7 @@ $(OBJDIR)/%.o: apps/%.rc
$(WINDRES) -i $< -o $@ --include-dir=apps
$(WINVIEW_OBJ): $(MUPDF_HDR) $(PDFAPP_HDR)
-$(WINVIEW_EXE): $(WINVIEW_OBJ) $(MUPDF_LIB) $(THIRD_LIBS)
+$(WINVIEW_EXE): $(WINVIEW_OBJ) $(MUPDF_LIB) $(MUXPS_LIB) $(THIRD_LIBS)
$(LD_CMD) $(W32LIBS)
#
diff --git a/Makerules b/Makerules
index 34bf6ea6..e3e5a987 100644
--- a/Makerules
+++ b/Makerules
@@ -7,7 +7,7 @@ CC ?=
CFLAGS ?=
LDFLAGS ?=
-CFLAGS += -Ifitz -Imupdf -Wall
+CFLAGS += -Ifitz -Imupdf -Ixps -Wall
ifeq "$(build)" "debug"
CFLAGS += -pipe -g
diff --git a/apps/pdfapp.c b/apps/pdfapp.c
index 2fca8d0c..0faff3b3 100644
--- a/apps/pdfapp.c
+++ b/apps/pdfapp.c
@@ -1,5 +1,6 @@
-#include <fitz.h>
-#include <mupdf.h>
+#include "fitz.h"
+#include "mupdf.h"
+#include "muxps.h"
#include "pdfapp.h"
#include <ctype.h> /* for tolower() */
@@ -95,15 +96,13 @@ void pdfapp_invert(pdfapp_t *app, fz_bbox rect)
}
}
-void pdfapp_open(pdfapp_t *app, char *filename, int fd)
+static void pdfapp_open_pdf(pdfapp_t *app, char *filename, int fd)
{
fz_error error;
+ fz_stream *file;
+ char *password = "";
fz_obj *obj;
fz_obj *info;
- char *password = "";
- fz_stream *file;
-
- app->cache = fz_newglyphcache();
/*
* Open PDF and load xref table
@@ -161,6 +160,32 @@ void pdfapp_open(pdfapp_t *app, char *filename, int fd)
pdfapp_error(app, fz_rethrow(error, "cannot load page tree"));
app->pagecount = pdf_getpagecount(app->xref);
+}
+
+static void pdfapp_open_xps(pdfapp_t *app, char *filename, int fd)
+{
+ fz_error error;
+
+ close(fd); // TODO: fix this for windows
+
+ app->xps = xps_new_context();
+ error = xps_open_file(app->xps, filename);
+ if (error)
+ pdfapp_error(app, fz_rethrow(error, "cannot open document: '%s'", filename));
+
+ app->doctitle = filename;
+
+ app->pagecount = xps_count_pages(app->xps);
+}
+
+void pdfapp_open(pdfapp_t *app, char *filename, int fd)
+{
+ if (strstr(filename, ".xps") || strstr(filename, ".XPS"))
+ pdfapp_open_xps(app, filename, fd);
+ else
+ pdfapp_open_pdf(app, filename, fd);
+
+ app->cache = fz_newglyphcache();
app->shrinkwrap = 1;
if (app->pageno < 1)
@@ -184,10 +209,6 @@ void pdfapp_close(pdfapp_t *app)
fz_freeglyphcache(app->cache);
app->cache = nil;
- if (app->page)
- pdf_freepage(app->page);
- app->page = nil;
-
if (app->image)
fz_droppixmap(app->image);
app->image = nil;
@@ -206,6 +227,12 @@ void pdfapp_close(pdfapp_t *app)
app->xref = nil;
}
+ if (app->xps)
+ {
+ xps_free_context(app->xps);
+ app->xps = NULL;
+ }
+
fz_flushwarnings();
}
@@ -213,9 +240,12 @@ static fz_matrix pdfapp_viewctm(pdfapp_t *app)
{
fz_matrix ctm;
ctm = fz_identity;
- ctm = fz_concat(ctm, fz_translate(0, -app->page->mediabox.y1));
- ctm = fz_concat(ctm, fz_scale(app->resolution/72.0f, -app->resolution/72.0f));
- ctm = fz_concat(ctm, fz_rotate(app->rotate + app->page->rotate));
+ ctm = fz_concat(ctm, fz_translate(0, -app->page_bbox.y1));
+ if (app->xref)
+ ctm = fz_concat(ctm, fz_scale(app->resolution/72.0f, -app->resolution/72.0f));
+ else
+ ctm = fz_concat(ctm, fz_scale(app->resolution/96.0f, app->resolution/96.0f));
+ ctm = fz_concat(ctm, fz_rotate(app->rotate + app->page_rotate));
return ctm;
}
@@ -243,51 +273,100 @@ static void pdfapp_panview(pdfapp_t *app, int newx, int newy)
app->pany = newy;
}
+static void pdfapp_loadpage_pdf(pdfapp_t *app)
+{
+ pdf_page *page;
+ fz_error error;
+ fz_device *mdev;
+ fz_obj *obj;
+
+ obj = pdf_getpageobject(app->xref, app->pageno);
+ error = pdf_loadpage(&page, app->xref, obj);
+ if (error)
+ pdfapp_error(app, error);
+
+ app->page_bbox = page->mediabox;
+ app->page_rotate = page->rotate;
+ app->page_links = page->links;
+ page->links = NULL;
+
+ /* Create display list */
+ app->page_list = fz_newdisplaylist();
+ mdev = fz_newlistdevice(app->page_list);
+ error = pdf_runpage(app->xref, page, mdev, fz_identity);
+ if (error)
+ {
+ error = fz_rethrow(error, "cannot draw page %d in '%s'", app->pageno, app->doctitle);
+ pdfapp_error(app, error);
+ }
+ fz_freedevice(mdev);
+
+ pdf_freepage(page);
+
+ pdf_agestore(app->xref->store, 3);
+}
+
+static void pdfapp_loadpage_xps(pdfapp_t *app)
+{
+ xps_page *page;
+ fz_device *mdev;
+
+ page = xps_load_page(app->xps, app->pageno - 1);
+ if (!page)
+ pdfapp_error(app, fz_rethrow(-1, "cannot load page %d in file '%s'", app->pageno, app->doctitle));
+
+ app->page_bbox.x0 = 0;
+ app->page_bbox.y0 = 0;
+ app->page_bbox.x1 = page->width;
+ app->page_bbox.y1 = page->height;
+ app->page_rotate = 0;
+ app->page_links = NULL;
+
+ /* Create display list */
+ app->page_list = fz_newdisplaylist();
+ mdev = fz_newlistdevice(app->page_list);
+ app->xps->dev = mdev;
+ xps_parse_fixed_page(app->xps, fz_identity, page);
+ app->xps->dev = nil;
+ fz_freedevice(mdev);
+
+ xps_free_page(app->xps, page);
+}
+
static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint)
{
char buf[256];
- fz_error error;
- fz_device *idev, *tdev, *mdev;
+ fz_device *idev;
+ fz_device *tdev;
fz_colorspace *colorspace;
fz_matrix ctm;
fz_bbox bbox;
- fz_obj *obj;
wincursor(app, WAIT);
if (loadpage)
{
- if (app->page)
- pdf_freepage(app->page);
- app->page = nil;
-
- obj = pdf_getpageobject(app->xref, app->pageno);
- error = pdf_loadpage(&app->page, app->xref, obj);
- if (error)
- pdfapp_error(app, error);
-
- /* Create display list */
- app->page->list = fz_newdisplaylist();
- mdev = fz_newlistdevice(app->page->list);
- error = pdf_runpage(app->xref, app->page, mdev, fz_identity);
- if (error)
- {
- error = fz_rethrow(error, "cannot draw page %d in '%s'", app->pageno, app->doctitle);
- pdfapp_error(app, error);
- }
- fz_freedevice(mdev);
+ if (app->page_list)
+ fz_freedisplaylist(app->page_list);
+ if (app->page_text)
+ fz_freetextspan(app->page_text);
+ if (app->page_links)
+ pdf_freelink(app->page_links);
+
+ if (app->xref)
+ pdfapp_loadpage_pdf(app);
+ if (app->xps)
+ pdfapp_loadpage_xps(app);
/* Zero search hit position */
app->hit = -1;
app->hitlen = 0;
/* Extract text */
- app->page->text = fz_newtextspan();
- tdev = fz_newtextdevice(app->page->text);
- fz_executedisplaylist(app->page->list, tdev, fz_identity);
+ app->page_text = fz_newtextspan();
+ tdev = fz_newtextdevice(app->page_text);
+ fz_executedisplaylist(app->page_list, tdev, fz_identity);
fz_freedevice(tdev);
-
- pdf_agestore(app->xref->store, 3);
}
if (drawpage)
@@ -297,7 +376,7 @@ static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repai
wintitle(app, buf);
ctm = pdfapp_viewctm(app);
- bbox = fz_roundrect(fz_transformrect(ctm, app->page->mediabox));
+ bbox = fz_roundrect(fz_transformrect(ctm, app->page_bbox));
/* Draw */
if (app->image)
@@ -313,7 +392,7 @@ static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repai
app->image = fz_newpixmapwithrect(colorspace, bbox);
fz_clearpixmapwithcolor(app->image, 255);
idev = fz_newdrawdevice(app->cache, app->image);
- fz_executedisplaylist(app->page->list, idev, ctm);
+ fz_executedisplaylist(app->page_list, idev, ctm);
fz_freedevice(idev);
}
@@ -402,7 +481,7 @@ void pdfapp_inverthit(pdfapp_t *app)
for (i = app->hit; i < app->hit + app->hitlen; i++)
{
- bbox = bboxcharat(app->page->text, i);
+ bbox = bboxcharat(app->page_text, i);
if (fz_isemptyrect(bbox))
{
if (!fz_isemptyrect(hitbox))
@@ -492,7 +571,7 @@ static void pdfapp_searchforward(pdfapp_t *app)
do
{
- len = textlen(app->page->text);
+ len = textlen(app->page_text);
if (app->hit >= 0)
test = app->hit + strlen(app->search);
@@ -501,7 +580,7 @@ static void pdfapp_searchforward(pdfapp_t *app)
while (test < len)
{
- matchlen = match(app->search, app->page->text, test);
+ matchlen = match(app->search, app->page_text, test);
if (matchlen)
{
app->hit = test;
@@ -540,7 +619,7 @@ static void pdfapp_searchbackward(pdfapp_t *app)
do
{
- len = textlen(app->page->text);
+ len = textlen(app->page_text);
if (app->hit >= 0)
test = app->hit - 1;
@@ -549,7 +628,7 @@ static void pdfapp_searchbackward(pdfapp_t *app)
while (test >= 0)
{
- matchlen = match(app->search, app->page->text, test);
+ matchlen = match(app->search, app->page_text, test);
if (matchlen)
{
app->hit = test;
@@ -881,7 +960,7 @@ void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int sta
p = fz_transformpoint(ctm, p);
- for (link = app->page->links; link; link = link->next)
+ for (link = app->page_links; link; link = link->next)
{
if (p.x >= link->rect.x0 && p.x <= link->rect.x1)
if (p.y >= link->rect.y0 && p.y <= link->rect.y1)
@@ -1005,7 +1084,7 @@ void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen)
ctm = pdfapp_viewctm(app);
p = 0;
- for (span = app->page->text; span; span = span->next)
+ for (span = app->page_text; span; span = span->next)
{
seen = 0;
diff --git a/apps/pdfapp.h b/apps/pdfapp.h
index 3771485e..dda637db 100644
--- a/apps/pdfapp.h
+++ b/apps/pdfapp.h
@@ -31,6 +31,8 @@ struct pdfapp_s
char *doctitle;
pdf_xref *xref;
pdf_outline *outline;
+ xps_context *xps;
+
int pagecount;
fz_glyphcache *cache;
@@ -42,7 +44,11 @@ struct pdfapp_s
/* current page params */
int pageno;
- pdf_page *page;
+ fz_rect page_bbox;
+ float page_rotate;
+ fz_displaylist *page_list;
+ fz_textspan *page_text;
+ pdf_link *page_links;
/* snapback history */
int hist[256];
diff --git a/apps/win_main.c b/apps/win_main.c
index e217dfbb..ae62b62b 100644
--- a/apps/win_main.c
+++ b/apps/win_main.c
@@ -1,5 +1,6 @@
-#include <fitz.h>
-#include <mupdf.h>
+#include "fitz.h"
+#include "mupdf.h"
+#include "muxps.h"
#include "pdfapp.h"
#ifndef UNICODE
@@ -90,7 +91,7 @@ int winfilename(wchar_t *buf, int len)
ofn.nMaxFile = len;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = L"MuPDF: Open PDF file";
- ofn.lpstrFilter = L"PDF Files (*.pdf)\0*.pdf\0All Files\0*\0\0";
+ ofn.lpstrFilter = L"Documents (*.pdf;*.xps)\0*.xps;*.pdf\0PDF Files (*.pdf)\0*.pdf\0XPS Files (*.xps)\0*.xps\0All Files\0*\0\0";
ofn.Flags = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
return GetOpenFileNameW(&ofn);
}
@@ -157,6 +158,14 @@ dloginfoproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
SetDlgItemTextW(hwnd, 0x10, wbuf);
+ if (!xref)
+ {
+ SetDlgItemTextA(hwnd, 0x11, "XPS");
+ SetDlgItemTextA(hwnd, 0x12, "None");
+ SetDlgItemTextA(hwnd, 0x13, "n/a");
+ return TRUE;
+ }
+
sprintf(buf, "PDF %d.%d", xref->version / 10, xref->version % 10);
SetDlgItemTextA(hwnd, 0x11, buf);
diff --git a/apps/x11_main.c b/apps/x11_main.c
index 73c8c568..1e08368e 100644
--- a/apps/x11_main.c
+++ b/apps/x11_main.c
@@ -1,5 +1,6 @@
#include "fitz.h"
#include "mupdf.h"
+#include "muxps.h"
#include "pdfapp.h"
#include <X11/Xlib.h>
diff --git a/apps/xpsdraw.c b/apps/xpsdraw.c
new file mode 100644
index 00000000..a9050a1c
--- /dev/null
+++ b/apps/xpsdraw.c
@@ -0,0 +1,367 @@
+#include "fitz.h"
+#include "muxps.h"
+
+#ifdef _MSC_VER
+#include <winsock2.h>
+#else
+#include <sys/time.h>
+#endif
+
+char *output = NULL;
+float resolution = 72;
+
+int showxml = 0;
+int showtext = 0;
+int showtime = 0;
+int showmd5 = 0;
+int savealpha = 0;
+int uselist = 1;
+
+fz_colorspace *colorspace;
+fz_glyphcache *glyphcache;
+char *filename;
+
+struct {
+ int count, total;
+ int min, max;
+ int minpage, maxpage;
+} timing;
+
+static void die(fz_error error)
+{
+ fz_catch(error, "aborting");
+ exit(1);
+}
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "usage: xpsdraw [options] input.xps [pages]\n"
+ "\t-o -\toutput filename (%%d for page number)\n"
+ "\t\tsupported formats: pgm, ppm, pam, png\n"
+ "\t-r -\tresolution in dpi (default: 72)\n"
+ "\t-a\tsave alpha channel (only pam and png)\n"
+ "\t-g\trender in grayscale\n"
+ "\t-m\tshow timing information\n"
+ "\t-t\tshow text (-tt for xml)\n"
+ "\t-x\tshow display list\n"
+ "\t-d\tdisable use of display list\n"
+ "\t-5\tshow md5 checksums\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
+xps_run_page(xps_context *ctx, xps_page *page, fz_device *dev, fz_matrix ctm)
+{
+ ctx->dev = dev;
+ xps_parse_fixed_page(ctx, ctm, page);
+ ctx->dev = nil;
+}
+
+static void drawpage(xps_context *ctx, int pagenum)
+{
+ xps_page *page;
+ fz_displaylist *list;
+ fz_device *dev;
+ int start;
+
+ if (showtime)
+ {
+ start = gettime();
+ }
+
+ page = xps_load_page(ctx, pagenum - 1);
+ if (!page)
+ die(fz_throw("cannot load page %d in file '%s'", pagenum, filename));
+
+ list = nil;
+
+ if (uselist)
+ {
+ list = fz_newdisplaylist();
+ dev = fz_newlistdevice(list);
+ xps_run_page(ctx, page, dev, fz_identity);
+ fz_freedevice(dev);
+ }
+
+ if (showxml)
+ {
+ dev = fz_newtracedevice();
+ printf("<page number=\"%d\">\n", pagenum);
+ if (list)
+ fz_executedisplaylist(list, dev, fz_identity);
+ else
+ xps_run_page(ctx, page, dev, fz_identity);
+ printf("</page>\n");
+ fz_freedevice(dev);
+ }
+
+ if (showtext)
+ {
+ fz_textspan *text = fz_newtextspan();
+ dev = fz_newtextdevice(text);
+ if (list)
+ fz_executedisplaylist(list, dev, fz_identity);
+ else
+ xps_run_page(ctx, page, dev, fz_identity);
+ fz_freedevice(dev);
+ printf("[Page %d]\n", pagenum);
+ if (showtext > 1)
+ fz_debugtextspanxml(text);
+ else
+ fz_debugtextspan(text);
+ printf("\n");
+ fz_freetextspan(text);
+ }
+
+ if (showmd5 || showtime)
+ printf("page %s %d", filename, pagenum);
+
+ if (output || showmd5 || showtime)
+ {
+ float zoom;
+ fz_matrix ctm;
+ fz_rect rect;
+ fz_bbox bbox;
+ fz_pixmap *pix;
+
+ rect.x0 = rect.y0 = 0;
+ rect.x1 = page->width;
+ rect.y1 = page->height;
+
+ zoom = resolution / 96;
+ ctm = fz_translate(0, -page->height);
+ ctm = fz_concat(ctm, fz_scale(zoom, zoom));
+ bbox = fz_roundrect(fz_transformrect(ctm, rect));
+
+ /* TODO: banded rendering and multi-page ppm */
+
+ pix = fz_newpixmapwithrect(colorspace, bbox);
+
+ if (savealpha)
+ fz_clearpixmap(pix);
+ else
+ fz_clearpixmapwithcolor(pix, 255);
+
+ dev = fz_newdrawdevice(glyphcache, pix);
+ if (list)
+ fz_executedisplaylist(list, dev, ctm);
+ else
+ xps_run_page(ctx, page, dev, ctm);
+ fz_freedevice(dev);
+
+ if (output)
+ {
+ char buf[512];
+ sprintf(buf, output, pagenum);
+ if (strstr(output, ".pgm") || strstr(output, ".ppm") || strstr(output, ".pnm"))
+ fz_writepnm(pix, buf);
+ else if (strstr(output, ".pam"))
+ fz_writepam(pix, buf, savealpha);
+ else if (strstr(output, ".png"))
+ fz_writepng(pix, buf, savealpha);
+ }
+
+ if (showmd5)
+ {
+ fz_md5 md5;
+ unsigned char digest[16];
+ int i;
+
+ fz_md5init(&md5);
+ fz_md5update(&md5, pix->samples, pix->w * pix->h * pix->n);
+ fz_md5final(&md5, digest);
+
+ printf(" ");
+ for (i = 0; i < 16; i++)
+ printf("%02x", digest[i]);
+ }
+
+ fz_droppixmap(pix);
+ }
+
+ if (list)
+ fz_freedisplaylist(list);
+
+ if (showtime)
+ {
+ int end = gettime();
+ int diff = end - start;
+
+ if (diff < timing.min)
+ {
+ timing.min = diff;
+ timing.minpage = pagenum;
+ }
+ if (diff > timing.max)
+ {
+ timing.max = diff;
+ timing.maxpage = pagenum;
+ }
+ timing.total += diff;
+ timing.count ++;
+
+ printf(" %dms", diff);
+ }
+
+ if (showmd5 || showtime)
+ printf("\n");
+}
+
+
+static void drawrange(xps_context *ctx, char *range)
+{
+ int page, spage, epage;
+ char *spec, *dash;
+
+ spec = fz_strsep(&range, ",");
+ while (spec)
+ {
+ dash = strchr(spec, '-');
+
+ if (dash == spec)
+ spage = epage = xps_count_pages(ctx);
+ else
+ spage = epage = atoi(spec);
+
+ if (dash)
+ {
+ if (strlen(dash) > 1)
+ epage = atoi(dash + 1);
+ else
+ epage = xps_count_pages(ctx);
+ }
+
+ spage = CLAMP(spage, 1, xps_count_pages(ctx));
+ epage = CLAMP(epage, 1, xps_count_pages(ctx));
+
+ if (spage < epage)
+ for (page = spage; page <= epage; page++)
+ drawpage(ctx, page);
+ else
+ for (page = spage; page >= epage; page--)
+ drawpage(ctx, page);
+
+ spec = fz_strsep(&range, ",");
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int grayscale = 0;
+ int accelerate = 1;
+ xps_context *ctx;
+ int code;
+ int c;
+
+ while ((c = fz_getopt(argc, argv, "o:p:r:Aadgmtx5")) != -1)
+ {
+ switch (c)
+ {
+ case 'o': output = fz_optarg; break;
+ case 'r': resolution = atof(fz_optarg); break;
+ case 'A': accelerate = 0; break;
+ case 'a': savealpha = 1; break;
+ case 'm': showtime++; break;
+ case 't': showtext++; break;
+ case 'x': showxml++; break;
+ case '5': showmd5++; break;
+ case 'g': grayscale++; break;
+ case 'd': uselist = 0; break;
+ default: usage(); break;
+ }
+ }
+
+ if (fz_optind == argc)
+ usage();
+
+ if (!showtext && !showxml && !showtime && !showmd5 && !output)
+ {
+ printf("nothing to do\n");
+ exit(0);
+ }
+
+ if (accelerate)
+ fz_accelerate();
+
+ glyphcache = fz_newglyphcache();
+
+ colorspace = fz_devicergb;
+ if (grayscale)
+ colorspace = fz_devicegray;
+ if (output && strstr(output, ".pgm"))
+ colorspace = fz_devicegray;
+ if (output && strstr(output, ".ppm"))
+ colorspace = fz_devicergb;
+
+ timing.count = 0;
+ timing.total = 0;
+ timing.min = 1 << 30;
+ timing.max = 0;
+ timing.minpage = 0;
+ timing.maxpage = 0;
+
+ if (showxml)
+ printf("<?xml version=\"1.0\"?>\n");
+
+ while (fz_optind < argc)
+ {
+ filename = argv[fz_optind++];
+
+ ctx = xps_new_context();
+ code = xps_open_file(ctx, filename);
+ if (code)
+ die(fz_rethrow(code, "cannot open document: %s", filename));
+
+ if (showxml)
+ printf("<document name=\"%s\">\n", filename);
+
+ if (fz_optind == argc || !isrange(argv[fz_optind]))
+ drawrange(ctx, "1-");
+ if (fz_optind < argc && isrange(argv[fz_optind]))
+ drawrange(ctx, argv[fz_optind++]);
+
+ if (showxml)
+ printf("</document>\n");
+
+ xps_free_context(ctx);
+ }
+
+ if (showtime)
+ {
+ 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);
+ }
+
+ fz_freeglyphcache(glyphcache);
+
+ return 0;
+}
diff --git a/draw/imageunpack.c b/draw/imageunpack.c
index bbc22c70..2c7cb452 100644
--- a/draw/imageunpack.c
+++ b/draw/imageunpack.c
@@ -58,6 +58,16 @@ fz_unpacktile(fz_pixmap *dst, unsigned char * restrict src, int n, int depth, in
if (depth == 1)
initget1tables();
+ 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 + y * stride;
diff --git a/draw/meshdraw.c b/draw/meshdraw.c
index f40af8c3..79437505 100644
--- a/draw/meshdraw.c
+++ b/draw/meshdraw.c
@@ -540,6 +540,7 @@ fz_paintshade(fz_shade *shade, fz_matrix ctm, fz_pixmap *dest, fz_bbox bbox)
fz_convertcolor(shade->colorspace, shade->function[i], dest->colorspace, color);
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_newpixmapwithrect(dest->colorspace, bbox);
temp = fz_newpixmapwithrect(fz_devicegray, bbox);
@@ -565,7 +566,7 @@ fz_paintshade(fz_shade *shade, fz_matrix ctm, fz_pixmap *dest, fz_bbox bbox)
while (len--)
{
int v = *s++;
- int a = *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;
diff --git a/fitz/fitz.h b/fitz/fitz.h
index 6de8d9b7..d79e280e 100644
--- a/fitz/fitz.h
+++ b/fitz/fitz.h
@@ -406,13 +406,15 @@ struct fz_obj_s
} u;
};
+extern fz_obj* (*fz_resolveindirect)(fz_obj*);
+
fz_obj *fz_newnull(void);
fz_obj *fz_newbool(int b);
fz_obj *fz_newint(int i);
fz_obj *fz_newreal(float f);
fz_obj *fz_newname(char *str);
fz_obj *fz_newstring(char *str, int len);
-fz_obj *fz_newindirect(int num, int gen, struct pdf_xref_s *xref);
+fz_obj *fz_newindirect(int num, int gen, void *xref);
fz_obj *fz_newarray(int initialcap);
fz_obj *fz_newdict(int initialcap);
@@ -435,8 +437,6 @@ int fz_isindirect(fz_obj *obj);
int fz_objcmp(fz_obj *a, fz_obj *b);
-fz_obj *fz_resolveindirect(fz_obj *obj);
-
/* silent failure, no error reporting */
int fz_tobool(fz_obj *obj);
int fz_toint(fz_obj *obj);
@@ -673,6 +673,7 @@ struct fz_pixmap_s
int x, y, w, h, n;
fz_pixmap *mask; /* explicit soft/image mask */
int interpolate;
+ int xres, yres;
fz_colorspace *colorspace;
unsigned char *samples;
int freesamples;
@@ -685,6 +686,7 @@ fz_pixmap *fz_keeppixmap(fz_pixmap *pix);
void fz_droppixmap(fz_pixmap *pix);
void fz_clearpixmap(fz_pixmap *pix);
void fz_clearpixmapwithcolor(fz_pixmap *pix, int value);
+void fz_premultiplypixmap(fz_pixmap *pix);
fz_pixmap *fz_alphafromgray(fz_pixmap *gray, int luminosity);
fz_bbox fz_boundpixmap(fz_pixmap *pix);
@@ -829,6 +831,8 @@ void fz_curvetoy(fz_path*, float, float, float, float);
void fz_closepath(fz_path*);
void fz_freepath(fz_path *path);
+void fz_transformpath(fz_path *path, fz_matrix transform);
+
fz_path *fz_clonepath(fz_path *old);
fz_rect fz_boundpath(fz_path *path, fz_strokestate *stroke, fz_matrix ctm);
@@ -897,7 +901,7 @@ struct fz_shade_s
float background[FZ_MAXCOLORS];
int usefunction;
- float function[256][FZ_MAXCOLORS];
+ float function[256][FZ_MAXCOLORS + 1];
int type; /* linear, radial, mesh */
int extend[2];
diff --git a/fitz/obj_simple.c b/fitz/obj_simple.c
index f279bd55..b629974e 100644
--- a/fitz/obj_simple.c
+++ b/fitz/obj_simple.c
@@ -1,9 +1,15 @@
#include "fitz.h"
-#include "mupdf.h" /* for pdf_loadobject */
extern void fz_freearray(fz_obj *array);
extern void fz_freedict(fz_obj *dict);
+static fz_obj *fz_resolve_indirect_null(fz_obj *ref)
+{
+ return ref;
+}
+
+fz_obj* (*fz_resolveindirect)(fz_obj*) = fz_resolve_indirect_null;
+
fz_obj *
fz_newnull(void)
{
@@ -66,7 +72,7 @@ fz_newname(char *str)
}
fz_obj *
-fz_newindirect(int num, int gen, pdf_xref *xref)
+fz_newindirect(int num, int gen, void *xref)
{
fz_obj *o = fz_malloc(sizeof(fz_obj));
o->refs = 1;
@@ -219,28 +225,6 @@ int fz_togen(fz_obj *obj)
return 0;
}
-fz_obj *fz_resolveindirect(fz_obj *ref)
-{
- if (fz_isindirect(ref))
- {
- pdf_xref *xref = ref->u.r.xref;
- int num = fz_tonum(ref);
- int gen = fz_togen(ref);
- if (xref)
- {
- fz_error error = pdf_cacheobject(xref, num, gen);
- if (error)
- {
- fz_catch(error, "cannot load object (%d %d R) into cache", num, gen);
- return ref;
- }
- if (xref->table[num].obj)
- return xref->table[num].obj;
- }
- }
- return ref;
-}
-
int
fz_objcmp(fz_obj *a, fz_obj *b)
{
diff --git a/fitz/res_font.c b/fitz/res_font.c
index 1df8b2e6..44046451 100644
--- a/fitz/res_font.c
+++ b/fitz/res_font.c
@@ -176,6 +176,7 @@ fz_finalizefreetype(void)
fz_error
fz_newfontfromfile(fz_font **fontp, char *path, int index)
{
+ FT_Face face;
fz_error error;
fz_font *font;
int fterr;
@@ -184,14 +185,16 @@ fz_newfontfromfile(fz_font **fontp, char *path, int index)
if (error)
return fz_rethrow(error, "cannot init freetype library");
- font = fz_newfont();
-
- fterr = FT_New_Face(fz_ftlib, path, index, (FT_Face*)&font->ftface);
+ fterr = FT_New_Face(fz_ftlib, path, index, &face);
if (fterr)
- {
- fz_free(font);
return fz_throw("freetype: cannot load font: %s", ft_errorstring(fterr));
- }
+
+ font = fz_newfont();
+ font->ftface = face;
+ font->bbox.x0 = face->bbox.xMin * 1000 / face->units_per_EM;
+ font->bbox.y0 = face->bbox.yMin * 1000 / face->units_per_EM;
+ font->bbox.x1 = face->bbox.xMax * 1000 / face->units_per_EM;
+ font->bbox.y1 = face->bbox.yMax * 1000 / face->units_per_EM;
*fontp = font;
return fz_okay;
@@ -200,6 +203,7 @@ fz_newfontfromfile(fz_font **fontp, char *path, int index)
fz_error
fz_newfontfrombuffer(fz_font **fontp, unsigned char *data, int len, int index)
{
+ FT_Face face;
fz_error error;
fz_font *font;
int fterr;
@@ -210,12 +214,16 @@ fz_newfontfrombuffer(fz_font **fontp, unsigned char *data, int len, int index)
font = fz_newfont();
- fterr = FT_New_Memory_Face(fz_ftlib, data, len, index, (FT_Face*)&font->ftface);
+ fterr = FT_New_Memory_Face(fz_ftlib, data, len, index, &face);
if (fterr)
- {
- fz_free(font);
return fz_throw("freetype: cannot load font: %s", ft_errorstring(fterr));
- }
+
+ font = fz_newfont();
+ font->ftface = face;
+ font->bbox.x0 = face->bbox.xMin * 1000 / face->units_per_EM;
+ font->bbox.y0 = face->bbox.yMin * 1000 / face->units_per_EM;
+ font->bbox.x1 = face->bbox.xMax * 1000 / face->units_per_EM;
+ font->bbox.y1 = face->bbox.yMax * 1000 / face->units_per_EM;
*fontp = font;
return fz_okay;
diff --git a/fitz/res_path.c b/fitz/res_path.c
index ebfb3cdc..b4ac415a 100644
--- a/fitz/res_path.c
+++ b/fitz/res_path.c
@@ -171,6 +171,42 @@ fz_boundpath(fz_path *path, fz_strokestate *stroke, fz_matrix ctm)
}
void
+fz_transformpath(fz_path *path, fz_matrix ctm)
+{
+ fz_point p;
+ int k, i = 0;
+
+ while (i < path->len)
+ {
+ switch (path->els[i++].k)
+ {
+ case FZ_CURVETO:
+ for (k = 0; k < 3; k++)
+ {
+ p.x = path->els[i].v;
+ p.y = path->els[i+1].v;
+ p = fz_transformpoint(ctm, p);
+ path->els[i].v = p.x;
+ path->els[i+1].v = p.y;
+ i += 2;
+ }
+ break;
+ case FZ_MOVETO:
+ case FZ_LINETO:
+ p.x = path->els[i].v;
+ p.y = path->els[i+1].v;
+ p = fz_transformpoint(ctm, p);
+ path->els[i].v = p.x;
+ path->els[i+1].v = p.y;
+ i += 2;
+ break;
+ case FZ_CLOSEPATH:
+ break;
+ }
+ }
+}
+
+void
fz_debugpath(fz_path *path, int indent)
{
float x, y;
diff --git a/fitz/res_pixmap.c b/fitz/res_pixmap.c
index e70d084d..9f336ab1 100644
--- a/fitz/res_pixmap.c
+++ b/fitz/res_pixmap.c
@@ -13,6 +13,8 @@ fz_newpixmapwithdata(fz_colorspace *colorspace, int x, int y, int w, int h, unsi
pix->h = h;
pix->mask = nil;
pix->interpolate = 1;
+ pix->xres = 96;
+ pix->yres = 96;
pix->colorspace = nil;
pix->n = 1;
@@ -97,6 +99,42 @@ fz_clearpixmapwithcolor(fz_pixmap *pix, int value)
}
}
+void
+fz_premultiplypixmap(fz_pixmap *pix)
+{
+ unsigned char *s = pix->samples;
+ unsigned char a;
+ int k, x, y;
+
+ /* special case for CMYK (subtractive colors) */
+ if (pix->n == 5)
+ {
+ 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] = 255 - fz_mul255(255 - s[k], a);
+ s += pix->n;
+ }
+ }
+ }
+ else
+ {
+ 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;
+ }
+ }
+ }
+}
+
fz_bbox
fz_boundpixmap(fz_pixmap *pix)
{
diff --git a/mupdf/mupdf.h b/mupdf/mupdf.h
index cbfeb651..fe4cfee3 100644
--- a/mupdf/mupdf.h
+++ b/mupdf/mupdf.h
@@ -515,8 +515,6 @@ struct pdf_page_s
int transparency;
fz_obj *resources;
fz_buffer *contents;
- fz_displaylist *list;
- fz_textspan *text;
pdf_link *links;
pdf_annot *annots;
};
diff --git a/mupdf/pdf_page.c b/mupdf/pdf_page.c
index e09cafb0..869f7a5c 100644
--- a/mupdf/pdf_page.c
+++ b/mupdf/pdf_page.c
@@ -168,8 +168,6 @@ pdf_loadpage(pdf_page **pagep, pdf_xref *xref, fz_obj *dict)
page->resources = nil;
page->contents = nil;
page->transparency = 0;
- page->list = nil;
- page->text = nil;
page->links = nil;
page->annots = nil;
@@ -240,10 +238,6 @@ pdf_freepage(pdf_page *page)
fz_dropobj(page->resources);
if (page->contents)
fz_dropbuffer(page->contents);
- if (page->list)
- fz_freedisplaylist(page->list);
- if (page->text)
- fz_freetextspan(page->text);
if (page->links)
pdf_freelink(page->links);
if (page->annots)
diff --git a/mupdf/pdf_shade.c b/mupdf/pdf_shade.c
index cf430583..1b301724 100644
--- a/mupdf/pdf_shade.c
+++ b/mupdf/pdf_shade.c
@@ -332,6 +332,7 @@ pdf_samplecompositeshadefunction(fz_shade *shade, pdf_function *func, float t0,
{
t = t0 + (i / 255.0f) * (t1 - t0);
pdf_evalfunction(func, &t, 1, shade->function[i], shade->colorspace->n);
+ shade->function[i][shade->colorspace->n] = 1;
}
}
@@ -346,6 +347,7 @@ pdf_samplecomponentshadefunction(fz_shade *shade, int funcs, pdf_function **func
t = t0 + (i / 255.0f) * (t1 - t0);
for (k = 0; k < funcs; k++)
pdf_evalfunction(func[k], &t, 1, &shade->function[i][k], 1);
+ shade->function[i][k] = 1;
}
}
diff --git a/mupdf/pdf_xref.c b/mupdf/pdf_xref.c
index 7174cfc5..4ec23a30 100644
--- a/mupdf/pdf_xref.c
+++ b/mupdf/pdf_xref.c
@@ -1,6 +1,8 @@
#include "fitz.h"
#include "mupdf.h"
+static fz_obj *pdf_resolveindirect(fz_obj *ref);
+
static inline int iswhite(int ch)
{
return
@@ -535,6 +537,9 @@ pdf_openxrefwithstream(pdf_xref **xrefp, fz_stream *file, char *password)
fz_obj *dict, *obj;
int i, repaired = 0;
+ /* install pdf specific callback */
+ fz_resolveindirect = pdf_resolveindirect;
+
xref = fz_malloc(sizeof(pdf_xref));
memset(xref, 0, sizeof(pdf_xref));
@@ -893,6 +898,29 @@ pdf_loadobject(fz_obj **objp, pdf_xref *xref, int num, int gen)
return fz_okay;
}
+static fz_obj *
+pdf_resolveindirect(fz_obj *ref)
+{
+ if (fz_isindirect(ref))
+ {
+ pdf_xref *xref = ref->u.r.xref;
+ int num = fz_tonum(ref);
+ int gen = fz_togen(ref);
+ if (xref)
+ {
+ fz_error error = pdf_cacheobject(xref, num, gen);
+ if (error)
+ {
+ fz_catch(error, "cannot load object (%d %d R) into cache", num, gen);
+ return ref;
+ }
+ if (xref->table[num].obj)
+ return xref->table[num].obj;
+ }
+ }
+ return ref;
+}
+
/* Replace numbered object -- for use by pdfclean and similar tools */
void
pdf_updateobject(pdf_xref *xref, int num, int gen, fz_obj *newobj)
diff --git a/win32/libmupdf.vcproj b/win32/libmupdf.vcproj
index 8ff67a52..7fd5634f 100644
--- a/win32/libmupdf.vcproj
+++ b/win32/libmupdf.vcproj
@@ -520,6 +520,70 @@
>
</File>
</Filter>
+ <Filter
+ Name="xps"
+ >
+ <File
+ RelativePath="..\xps\xps_common.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_doc.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_glyphs.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_gradient.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_hash.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_image.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_jpeg.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_path.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_png.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_resource.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_tiff.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_tile.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_util.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_xml.c"
+ >
+ </File>
+ <File
+ RelativePath="..\xps\xps_zip.c"
+ >
+ </File>
+ </Filter>
</Files>
<Globals>
</Globals>
diff --git a/win32/mupdf.vcproj b/win32/mupdf.vcproj
index 684093e9..509ef4df 100644
--- a/win32/mupdf.vcproj
+++ b/win32/mupdf.vcproj
@@ -40,7 +40,7 @@
<Tool
Name="VCCLCompilerTool"
Optimization="0"
- AdditionalIncludeDirectories="..\fitz;..\mupdf"
+ AdditionalIncludeDirectories="..\fitz;..\mupdf;..\xps"
PreprocessorDefinitions="FT2_BUILD_LIBRARY;OPJ_STATIC"
MinimalRebuild="true"
BasicRuntimeChecks="3"
@@ -111,7 +111,7 @@
Name="VCCLCompilerTool"
Optimization="2"
EnableIntrinsicFunctions="true"
- AdditionalIncludeDirectories="..\fitz;..\mupdf"
+ AdditionalIncludeDirectories="..\fitz;..\mupdf;..\xps"
RuntimeLibrary="0"
EnableFunctionLevelLinking="true"
WarningLevel="3"
diff --git a/xps/muxps.h b/xps/muxps.h
new file mode 100644
index 00000000..8a29d674
--- /dev/null
+++ b/xps/muxps.h
@@ -0,0 +1,247 @@
+#ifndef _MUXPS_H_
+#define _MUXPS_H_
+
+#ifndef _FITZ_H_
+#error "fitz.h must be included before muxps.h"
+#endif
+
+typedef unsigned char byte;
+
+/*
+ * XPS and ZIP constants.
+ */
+
+typedef struct xps_context_s xps_context;
+
+#define REL_START_PART \
+ "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
+#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 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
+
+/*
+ * Memory, and string functions.
+ */
+
+int xps_strcasecmp(char *a, char *b);
+void xps_absolute_path(char *output, char *base_uri, char *path, int output_size);
+
+/*
+ * Generic hashtable.
+ */
+
+typedef struct xps_hash_table_s xps_hash_table;
+
+xps_hash_table *xps_hash_new(void);
+void *xps_hash_lookup(xps_hash_table *table, char *key);
+int xps_hash_insert(xps_hash_table *table, char *key, void *value);
+void xps_hash_free(xps_hash_table *table,
+ void (*free_key)(void *),
+ void (*free_value)(void *));
+void xps_hash_debug(xps_hash_table *table);
+
+/*
+ * XML document model
+ */
+
+typedef struct element xml_element;
+
+xml_element *xml_parse_document(byte *buf, int len);
+xml_element *xml_next(xml_element *item);
+xml_element *xml_down(xml_element *item);
+char *xml_tag(xml_element *item);
+char *xml_att(xml_element *item, const char *att);
+void xml_free_element(xml_element *item);
+void xml_print_element(xml_element *item, int level);
+
+/*
+ * Container parts.
+ */
+
+typedef struct xps_part_s xps_part;
+
+struct xps_part_s
+{
+ char *name;
+ int size;
+ int cap;
+ byte *data;
+};
+
+xps_part *xps_new_part(xps_context *ctx, char *name, int size);
+xps_part *xps_read_part(xps_context *ctx, char *partname);
+void xps_free_part(xps_context *ctx, xps_part *part);
+
+/*
+ * Document structure.
+ */
+
+typedef struct xps_document_s xps_document;
+typedef struct xps_page_s xps_page;
+
+struct xps_document_s
+{
+ char *name;
+ xps_document *next;
+};
+
+struct xps_page_s
+{
+ char *name;
+ int width;
+ int height;
+ xml_element *root;
+ xps_page *next;
+};
+
+int xps_read_page_list(xps_context *ctx);
+void xps_debug_page_list(xps_context *ctx);
+void xps_free_page_list(xps_context *ctx);
+
+int xps_count_pages(xps_context *ctx);
+xps_page *xps_load_page(xps_context *ctx, int number);
+void xps_free_page(xps_context *ctx, xps_page *page);
+
+/*
+ * Images.
+ */
+
+int xps_decode_jpeg(fz_pixmap **imagep, byte *rbuf, int rlen);
+int xps_decode_png(fz_pixmap **imagep, byte *rbuf, int rlen);
+int xps_decode_tiff(fz_pixmap **imagep, byte *rbuf, int rlen);
+
+/*
+ * Fonts.
+ */
+
+typedef struct xps_glyph_metrics_s xps_glyph_metrics;
+
+struct xps_glyph_metrics_s
+{
+ float hadv, vadv, vorg;
+};
+
+int xps_count_font_encodings(fz_font *font);
+void xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid);
+void xps_select_font_encoding(fz_font *font, int idx);
+int xps_encode_font_char(fz_font *font, int key);
+
+void xps_measure_font_glyph(xps_context *ctx, fz_font *font, int gid, xps_glyph_metrics *mtx);
+
+void xps_debug_path(xps_context *ctx);
+
+/*
+ * Colorspaces and colors.
+ */
+
+void xps_parse_color(xps_context *ctx, char *base_uri, char *hexstring, fz_colorspace **csp, float *samples);
+void xps_set_color(xps_context *ctx, fz_colorspace *colorspace, float *samples);
+
+/*
+ * Resource dictionaries.
+ */
+
+typedef struct xps_resource_s xps_resource;
+
+struct xps_resource_s
+{
+ char *name;
+ char *base_uri; /* only used in the head nodes */
+ xml_element *base_xml; /* only used in the head nodes, to free the xml document */
+ xml_element *data;
+ xps_resource *next;
+ xps_resource *parent; /* up to the previous dict in the stack */
+};
+
+int xps_parse_resource_dictionary(xps_context *ctx, xps_resource **dictp, char *base_uri, xml_element *root);
+void xps_free_resource_dictionary(xps_context *ctx, xps_resource *dict);
+void xps_resolve_resource_reference(xps_context *ctx, xps_resource *dict, char **attp, xml_element **tagp, char **urip);
+
+void xps_debug_resource_dictionary(xps_resource *dict);
+
+/*
+ * Fixed page/graphics parsing.
+ */
+
+void xps_parse_fixed_page(xps_context *ctx, fz_matrix ctm, xps_page *page);
+void xps_parse_canvas(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_path(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_glyphs(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_solid_color_brush(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_image_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_visual_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_linear_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_radial_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+
+void xps_parse_tiling_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *root, void(*func)(xps_context*, fz_matrix, fz_rect, char*, xps_resource*, xml_element*, void*), void *user);
+
+void xps_parse_matrix_transform(xps_context *ctx, xml_element *root, fz_matrix *matrix);
+void xps_parse_render_transform(xps_context *ctx, char *text, fz_matrix *matrix);
+void xps_parse_rectangle(xps_context *ctx, char *text, fz_rect *rect);
+
+void xps_begin_opacity(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, char *opacity_att, xml_element *opacity_mask_tag);
+void xps_end_opacity(xps_context *ctx, char *base_uri, xps_resource *dict, char *opacity_att, xml_element *opacity_mask_tag);
+
+void xps_parse_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+void xps_parse_element(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node);
+
+void xps_clip(xps_context *ctx, fz_matrix ctm, xps_resource *dict, char *clip_att, xml_element *clip_tag);
+
+/*
+ * The interpreter context.
+ */
+
+typedef struct xps_entry_s xps_entry;
+
+struct xps_entry_s
+{
+ char *name;
+ int offset;
+ int csize;
+ int usize;
+};
+
+struct xps_context_s
+{
+ char *directory;
+ FILE *file;
+ int zip_count;
+ xps_entry *zip_table;
+
+ char *start_part; /* fixed document sequence */
+ xps_document *first_fixdoc; /* first fixed document */
+ xps_document *last_fixdoc; /* last fixed document */
+ xps_page *first_page; /* first page of document */
+ xps_page *last_page; /* last page of document */
+
+ char *base_uri; /* base uri for parsing XML and resolving relative paths */
+ char *part_uri; /* part uri for parsing metadata relations */
+
+ /* We cache font and colorspace resources */
+ xps_hash_table *font_table;
+ xps_hash_table *colorspace_table;
+
+ /* Opacity attribute stack */
+ float opacity[64];
+ int opacity_top;
+
+ /* Current color */
+ fz_colorspace *colorspace;
+ float color[8];
+ float alpha;
+
+ /* Current device */
+ fz_device *dev;
+};
+
+xps_context *xps_new_context(void);
+int xps_open_file(xps_context *ctx, char *filename);
+int xps_free_context(xps_context *ctx);
+
+#endif
diff --git a/xps/xps_common.c b/xps/xps_common.c
new file mode 100644
index 00000000..65f06e60
--- /dev/null
+++ b/xps/xps_common.c
@@ -0,0 +1,282 @@
+#include "fitz.h"
+#include "muxps.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;
+}
+
+void
+xps_parse_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node)
+{
+ /* SolidColorBrushes are handled in a special case and will never show up here */
+ if (!strcmp(xml_tag(node), "ImageBrush"))
+ xps_parse_image_brush(ctx, ctm, area, base_uri, dict, node);
+ else if (!strcmp(xml_tag(node), "VisualBrush"))
+ xps_parse_visual_brush(ctx, ctm, area, base_uri, dict, node);
+ else if (!strcmp(xml_tag(node), "LinearGradientBrush"))
+ xps_parse_linear_gradient_brush(ctx, ctm, area, base_uri, dict, node);
+ else if (!strcmp(xml_tag(node), "RadialGradientBrush"))
+ xps_parse_radial_gradient_brush(ctx, ctm, area, base_uri, dict, node);
+ else
+ fz_warn("unknown brush tag: %s", xml_tag(node));
+}
+
+void
+xps_parse_element(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node)
+{
+ if (!strcmp(xml_tag(node), "Path"))
+ xps_parse_path(ctx, ctm, base_uri, dict, node);
+ if (!strcmp(xml_tag(node), "Glyphs"))
+ xps_parse_glyphs(ctx, ctm, base_uri, dict, node);
+ if (!strcmp(xml_tag(node), "Canvas"))
+ xps_parse_canvas(ctx, ctm, area, base_uri, dict, node);
+ /* skip unknown tags (like Foo.Resources and similar) */
+}
+
+void
+xps_begin_opacity(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict,
+ char *opacity_att, xml_element *opacity_mask_tag)
+{
+ float opacity;
+
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+
+ opacity = 1.0;
+ if (opacity_att)
+ opacity = atof(opacity_att);
+
+ if (opacity_mask_tag && !strcmp(xml_tag(opacity_mask_tag), "SolidColorBrush"))
+ {
+ char *scb_opacity_att = xml_att(opacity_mask_tag, "Opacity");
+ char *scb_color_att = xml_att(opacity_mask_tag, "Color");
+ if (scb_opacity_att)
+ opacity = opacity * atof(scb_opacity_att);
+ if (scb_color_att)
+ {
+ fz_colorspace *colorspace;
+ float samples[32];
+ xps_parse_color(ctx, base_uri, scb_color_att, &colorspace, samples);
+ opacity = opacity * samples[0];
+ }
+ opacity_mask_tag = NULL;
+ }
+
+ if (ctx->opacity_top + 1 < nelem(ctx->opacity))
+ {
+ ctx->opacity[ctx->opacity_top + 1] = ctx->opacity[ctx->opacity_top] * opacity;
+ ctx->opacity_top++;
+ }
+
+ if (opacity_mask_tag)
+ {
+ ctx->dev->beginmask(ctx->dev->user, area, 0, NULL, NULL);
+ xps_parse_brush(ctx, ctm, area, base_uri, dict, opacity_mask_tag);
+ ctx->dev->endmask(ctx->dev->user);
+ }
+}
+
+void
+xps_end_opacity(xps_context *ctx, char *base_uri, xps_resource *dict,
+ char *opacity_att, xml_element *opacity_mask_tag)
+{
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+
+ if (ctx->opacity_top > 0)
+ ctx->opacity_top--;
+
+ if (opacity_mask_tag)
+ {
+ if (strcmp(xml_tag(opacity_mask_tag), "SolidColorBrush"))
+ ctx->dev->popclip(ctx->dev->user);
+ }
+}
+
+void
+xps_parse_render_transform(xps_context *ctx, char *transform, fz_matrix *matrix)
+{
+ float args[6];
+ char *s = transform;
+ int i;
+
+ args[0] = 1.0; args[1] = 0.0;
+ args[2] = 0.0; args[3] = 1.0;
+ args[4] = 0.0; args[5] = 0.0;
+
+ for (i = 0; i < 6 && *s; i++)
+ {
+ args[i] = 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_context *ctx, xml_element *root, fz_matrix *matrix)
+{
+ char *transform;
+
+ *matrix = fz_identity;
+
+ if (!strcmp(xml_tag(root), "MatrixTransform"))
+ {
+ transform = xml_att(root, "Matrix");
+ if (transform)
+ xps_parse_render_transform(ctx, transform, matrix);
+ }
+}
+
+void
+xps_parse_rectangle(xps_context *ctx, char *text, fz_rect *rect)
+{
+ float args[4];
+ char *s = text;
+ int i;
+
+ args[0] = 0.0; args[1] = 0.0;
+ args[2] = 1.0; args[3] = 1.0;
+
+ for (i = 0; i < 4 && *s; i++)
+ {
+ args[i] = 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_context *ctx, char *base_uri, char *string,
+ fz_colorspace **csp, float *samples)
+{
+ char *p;
+ int i, n;
+ char buf[1024];
+ char *profile;
+
+ *csp = fz_devicergb;
+
+ samples[0] = 1.0;
+ samples[1] = 0.0;
+ samples[2] = 0.0;
+ samples[3] = 0.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.0;
+ 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.0;
+ samples[1] /= 255.0;
+ samples[2] /= 255.0;
+ samples[3] /= 255.0;
+ }
+
+ 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 */
+ strcpy(buf, string);
+
+ profile = strchr(buf, ' ');
+ if (!profile)
+ {
+ fz_warn("cannot find icc profile uri in '%s'", string);
+ return;
+ }
+
+ *profile++ = 0;
+ p = strchr(profile, ' ');
+ if (!p)
+ {
+ fz_warn("cannot find component values in '%s'", profile);
+ return;
+ }
+
+ *p++ = 0;
+ n = count_commas(p) + 1;
+ i = 0;
+ while (i < n)
+ {
+ samples[i++] = atof(p);
+ p = strchr(p, ',');
+ if (!p)
+ break;
+ p ++;
+ if (*p == ' ')
+ p ++;
+ }
+ while (i < n)
+ {
+ samples[i++] = 0.0;
+ }
+
+ /* TODO: load ICC profile */
+ switch (n)
+ {
+ case 2: *csp = fz_devicegray; break;
+ case 4: *csp = fz_devicergb; break;
+ case 5: *csp = fz_devicecmyk; break;
+ default: *csp = fz_devicegray; break;
+ }
+ }
+}
+
+void
+xps_set_color(xps_context *ctx, fz_colorspace *colorspace, float *samples)
+{
+ int i;
+ ctx->colorspace = colorspace;
+ for (i = 0; i < colorspace->n; i++)
+ ctx->color[i] = samples[i + 1];
+ ctx->alpha = samples[0] * ctx->opacity[ctx->opacity_top];
+}
diff --git a/xps/xps_doc.c b/xps/xps_doc.c
new file mode 100644
index 00000000..c9a4eec5
--- /dev/null
+++ b/xps/xps_doc.c
@@ -0,0 +1,339 @@
+#include "fitz.h"
+#include "muxps.h"
+
+/*
+ * The FixedDocumentSequence and FixedDocument parts determine
+ * which parts correspond to actual pages, and the page order.
+ */
+
+void
+xps_debug_page_list(xps_context *ctx)
+{
+ xps_document *fixdoc = ctx->first_fixdoc;
+ xps_page *page = ctx->first_page;
+
+ if (ctx->start_part)
+ printf("start part %s\n", ctx->start_part);
+
+ while (fixdoc)
+ {
+ printf("fixdoc %s\n", fixdoc->name);
+ fixdoc = fixdoc->next;
+ }
+
+ while (page)
+ {
+ printf("page %s w=%d h=%d\n", page->name, page->width, page->height);
+ page = page->next;
+ }
+}
+
+static void
+xps_add_fixed_document(xps_context *ctx, char *name)
+{
+ xps_document *fixdoc;
+
+ /* Check for duplicates first */
+ for (fixdoc = ctx->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ if (!strcmp(fixdoc->name, name))
+ return;
+
+ fixdoc = fz_malloc(sizeof(xps_document));
+ fixdoc->name = fz_strdup(name);
+ fixdoc->next = NULL;
+
+ if (!ctx->first_fixdoc)
+ {
+ ctx->first_fixdoc = fixdoc;
+ ctx->last_fixdoc = fixdoc;
+ }
+ else
+ {
+ ctx->last_fixdoc->next = fixdoc;
+ ctx->last_fixdoc = fixdoc;
+ }
+}
+
+static void
+xps_add_fixed_page(xps_context *ctx, char *name, int width, int height)
+{
+ xps_page *page;
+
+ /* Check for duplicates first */
+ for (page = ctx->first_page; page; page = page->next)
+ if (!strcmp(page->name, name))
+ return;
+
+ page = fz_malloc(sizeof(xps_page));
+ page->name = fz_strdup(name);
+ page->width = width;
+ page->height = height;
+ page->root = NULL;
+ page->next = NULL;
+
+ if (!ctx->first_page)
+ {
+ ctx->first_page = page;
+ ctx->last_page = page;
+ }
+ else
+ {
+ ctx->last_page->next = page;
+ ctx->last_page = page;
+ }
+}
+
+static void
+xps_free_fixed_pages(xps_context *ctx)
+{
+ xps_page *node = ctx->first_page;
+ while (node)
+ {
+ xps_page *next = node->next;
+ fz_free(node->name);
+ fz_free(node);
+ node = next;
+ }
+ ctx->first_page = NULL;
+ ctx->last_page = NULL;
+}
+
+static void
+xps_free_fixed_documents(xps_context *ctx)
+{
+ xps_document *node = ctx->first_fixdoc;
+ while (node)
+ {
+ xps_document *next = node->next;
+ fz_free(node->name);
+ fz_free(node);
+ node = next;
+ }
+ ctx->first_fixdoc = NULL;
+ ctx->last_fixdoc = NULL;
+}
+
+void
+xps_free_page_list(xps_context *ctx)
+{
+ xps_free_fixed_documents(ctx);
+ xps_free_fixed_pages(ctx);
+}
+
+/*
+ * Parse the fixed document sequence structure and _rels/.rels to find the start part.
+ */
+
+static void
+xps_parse_metadata_imp(xps_context *ctx, xml_element *item)
+{
+ while (item)
+ {
+ xps_parse_metadata_imp(ctx, xml_down(item));
+
+ if (!strcmp(xml_tag(item), "Relationship"))
+ {
+ char *target = xml_att(item, "Target");
+ char *type = xml_att(item, "Type");
+ if (target && type)
+ {
+ char tgtbuf[1024];
+ xps_absolute_path(tgtbuf, ctx->base_uri, target, sizeof tgtbuf);
+ if (!strcmp(type, REL_START_PART))
+ ctx->start_part = fz_strdup(tgtbuf);
+ }
+ }
+
+ if (!strcmp(xml_tag(item), "DocumentReference"))
+ {
+ char *source = xml_att(item, "Source");
+ if (source)
+ {
+ char srcbuf[1024];
+ xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_document(ctx, srcbuf);
+ }
+ }
+
+ if (!strcmp(xml_tag(item), "PageContent"))
+ {
+ char *source = xml_att(item, "Source");
+ char *width_att = xml_att(item, "Width");
+ char *height_att = 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_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_page(ctx, srcbuf, width, height);
+ }
+ }
+
+ item = xml_next(item);
+ }
+}
+
+static int
+xps_parse_metadata(xps_context *ctx, xps_part *part)
+{
+ xml_element *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;
+
+ ctx->base_uri = buf;
+ ctx->part_uri = part->name;
+
+ root = xml_parse_document(part->data, part->size);
+ if (!root)
+ return fz_rethrow(-1, "cannot parse metadata part '%s'", part->name);
+
+ xps_parse_metadata_imp(ctx, root);
+
+ xml_free_element(root);
+
+ ctx->base_uri = NULL;
+ ctx->part_uri = NULL;
+
+ return fz_okay;
+}
+
+static int
+xps_read_and_process_metadata_part(xps_context *ctx, char *name)
+{
+ xps_part *part;
+ int code;
+
+ part = xps_read_part(ctx, name);
+ if (!part)
+ return fz_rethrow(-1, "cannot read zip part '%s'", name);
+
+ code = xps_parse_metadata(ctx, part);
+ if (code)
+ return fz_rethrow(code, "cannot process metadata part '%s'", name);
+
+ xps_free_part(ctx, part);
+
+ return fz_okay;
+}
+
+int
+xps_read_page_list(xps_context *ctx)
+{
+ xps_document *doc;
+ int code;
+
+ code = xps_read_and_process_metadata_part(ctx, "/_rels/.rels");
+ if (code)
+ return fz_rethrow(code, "cannot process root relationship part");
+
+ if (!ctx->start_part)
+ return fz_throw("cannot find fixed document sequence start part");
+
+ code = xps_read_and_process_metadata_part(ctx, ctx->start_part);
+ if (code)
+ return fz_rethrow(code, "cannot process FixedDocumentSequence part");
+
+ for (doc = ctx->first_fixdoc; doc; doc = doc->next)
+ {
+ code = xps_read_and_process_metadata_part(ctx, doc->name);
+ if (code)
+ return fz_rethrow(code, "cannot process FixedDocument part");
+ }
+
+ return fz_okay;
+}
+
+int
+xps_count_pages(xps_context *ctx)
+{
+ xps_page *page;
+ int n = 0;
+ for (page = ctx->first_page; page; page = page->next)
+ n ++;
+ return n;
+}
+
+static int
+xps_load_fixed_page(xps_context *ctx, xps_page *page)
+{
+ xps_part *part;
+ xml_element *root;
+ char *width_att;
+ char *height_att;
+
+ part = xps_read_part(ctx, page->name);
+ if (!part)
+ return fz_rethrow(-1, "cannot read zip part '%s'", page->name);
+
+ root = xml_parse_document(part->data, part->size);
+ if (!root)
+ return fz_rethrow(-1, "cannot parse xml part '%s'", page->name);
+
+ xps_free_part(ctx, part);
+
+ if (strcmp(xml_tag(root), "FixedPage"))
+ return fz_throw("expected FixedPage element (found %s)", xml_tag(root));
+
+ width_att = xml_att(root, "Width");
+ if (!width_att)
+ return fz_throw("FixedPage missing required attribute: Width");
+
+ height_att = xml_att(root, "Height");
+ if (!height_att)
+ return fz_throw("FixedPage missing required attribute: Height");
+
+ page->width = atoi(width_att);
+ page->height = atoi(height_att);
+ page->root = root;
+
+ return 0;
+}
+
+xps_page *
+xps_load_page(xps_context *ctx, int number)
+{
+ xps_page *page;
+ int code;
+ int n = 0;
+
+ for (page = ctx->first_page; page; page = page->next)
+ {
+ if (n == number)
+ {
+ if (!page->root)
+ {
+ code = xps_load_fixed_page(ctx, page);
+ if (code) {
+ fz_rethrow(code, "cannot load page %d", number + 1);
+ return NULL;
+ }
+ }
+ return page;
+ }
+ n ++;
+ }
+ return nil;
+}
+
+void
+xps_free_page(xps_context *ctx, xps_page *page)
+{
+ if (page->root)
+ xml_free_element(page->root);
+ page->root = NULL;
+}
diff --git a/xps/xps_glyphs.c b/xps/xps_glyphs.c
new file mode 100644
index 00000000..9d0d9fca
--- /dev/null
+++ b/xps/xps_glyphs.c
@@ -0,0 +1,559 @@
+#include "fitz.h"
+#include "muxps.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->ftface;
+ return face->num_charmaps;
+}
+
+void
+xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid)
+{
+ FT_Face face = font->ftface;
+ *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->ftface;
+ FT_Set_Charmap(face, face->charmaps[idx]);
+}
+
+int
+xps_encode_font_char(fz_font *font, int code)
+{
+ FT_Face face = font->ftface;
+ int gid = FT_Get_Char_Index(face, code);
+ if (gid == 0 && 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_context *ctx, 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->ftface;
+ FT_Fixed hadv, vadv;
+
+ 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);
+
+ mtx->hadv = hadv / 65536.0f;
+ mtx->vadv = vadv / 65536.0f;
+ mtx->vorg = face->ascender / (float) face->units_per_EM;
+}
+
+/*
+ * 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_context *ctx, xps_part *part)
+{
+ byte buf[33];
+ byte 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("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(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 */
+ // { 0, * }, -- Unicode (deprecated)
+ { 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("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 = 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_context *ctx, 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("glyphs element with neither characters nor indices");
+
+ if (us)
+ {
+ if (us[0] == '{' && us[1] == '}')
+ us = us + 2;
+ un = strlen(us);
+ }
+
+ if (is_sideways)
+ tm = fz_concat(fz_scale(-size, size), fz_rotate(90));
+ else
+ tm = fz_scale(size, -size);
+
+ text = fz_newtext(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 proper text extraction */
+
+ while (code_count--)
+ {
+ if (us && un > 0)
+ {
+ int t = chartorune(&char_code, us);
+ us += t; un -= t;
+ }
+ }
+
+ while (glyph_count--)
+ {
+ int glyph_index = -1;
+ float u_offset = 0.0;
+ float v_offset = 0.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(ctx, font, glyph_index, &mtx);
+ if (is_sideways)
+ advance = mtx.vadv * 100.0;
+ else if (bidi_level & 1)
+ advance = -mtx.hadv * 100.0;
+ else
+ advance = mtx.hadv * 100.0;
+
+ 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.01 * size;
+ v_offset = v_offset * 0.01 * size;
+
+ if (is_sideways)
+ {
+ e = x + u_offset + (mtx.vorg * size);
+ f = y - v_offset + (mtx.hadv * 0.5 * size);
+ }
+ else
+ {
+ e = x + u_offset;
+ f = y - v_offset;
+ }
+
+ fz_addtext(text, glyph_index, char_code, e, f);
+
+ x += advance * 0.01 * size;
+ }
+ }
+
+ return text;
+}
+
+void
+xps_parse_glyphs(xps_context *ctx, fz_matrix ctm,
+ char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xml_element *node;
+ int code;
+
+ char *fill_uri;
+ char *opacity_mask_uri;
+
+ char *bidi_level_att;
+ char *caret_stops_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;
+
+ xml_element *transform_tag = NULL;
+ xml_element *clip_tag = NULL;
+ xml_element *fill_tag = NULL;
+ xml_element *opacity_mask_tag = NULL;
+
+ char *fill_opacity_att = NULL;
+
+ xps_part *part;
+ fz_font *font;
+
+ char partname[1024];
+ char *subfont;
+
+ float font_size = 10.0;
+ int subfontid = 0;
+ int is_sideways = 0;
+ int bidi_level = 0;
+
+ fz_text *text;
+ fz_rect area;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ bidi_level_att = xml_att(root, "BidiLevel");
+ caret_stops_att = xml_att(root, "CaretStops");
+ fill_att = xml_att(root, "Fill");
+ font_size_att = xml_att(root, "FontRenderingEmSize");
+ font_uri_att = xml_att(root, "FontUri");
+ origin_x_att = xml_att(root, "OriginX");
+ origin_y_att = xml_att(root, "OriginY");
+ is_sideways_att = xml_att(root, "IsSideways");
+ indices_att = xml_att(root, "Indices");
+ unicode_att = xml_att(root, "UnicodeString");
+ style_att = xml_att(root, "StyleSimulations");
+ transform_att = xml_att(root, "RenderTransform");
+ clip_att = xml_att(root, "Clip");
+ opacity_att = xml_att(root, "Opacity");
+ opacity_mask_att = xml_att(root, "OpacityMask");
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "Glyphs.RenderTransform"))
+ transform_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Glyphs.OpacityMask"))
+ opacity_mask_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Glyphs.Clip"))
+ clip_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Glyphs.Fill"))
+ fill_tag = xml_down(node);
+ }
+
+ fill_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(ctx, 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("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_absolute_path(partname, base_uri, font_uri_att, sizeof partname);
+ subfont = strrchr(partname, '#');
+ if (subfont)
+ {
+ subfontid = atoi(subfont + 1);
+ *subfont = 0;
+ }
+
+ font = xps_hash_lookup(ctx->font_table, partname);
+ if (!font)
+ {
+ part = xps_read_part(ctx, partname);
+ if (!part) {
+ fz_warn("cannot find font resource part '%s'", partname);
+ return;
+ }
+
+ /* deobfuscate if necessary */
+ if (strstr(part->name, ".odttf"))
+ xps_deobfuscate_font_resource(ctx, part);
+ if (strstr(part->name, ".ODTTF"))
+ xps_deobfuscate_font_resource(ctx, part);
+
+ code = fz_newfontfrombuffer(&font, part->data, part->size, subfontid);
+ if (code) {
+ fz_catch(code, "cannot load font resource '%s'", partname);
+ xps_free_part(ctx, part);
+ return;
+ }
+
+ xps_select_best_font_encoding(font);
+
+ xps_hash_insert(ctx->font_table, part->name, font);
+
+ /* NOTE: we kept part->name in the hashtable and part->data in the font */
+ fz_free(part);
+ }
+
+ /*
+ * Set up graphics state.
+ */
+
+ if (transform_att || transform_tag)
+ {
+ fz_matrix transform;
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ ctm = fz_concat(transform, ctm);
+ }
+
+ if (clip_att || clip_tag)
+ xps_clip(ctx, ctm, dict, clip_att, clip_tag);
+
+ font_size = atof(font_size_att);
+
+ text = xps_parse_glyphs_imp(ctx, ctm, font, font_size,
+ atof(origin_x_att), atof(origin_y_att),
+ is_sideways, bidi_level, indices_att, unicode_att);
+
+ area = fz_boundtext(text, ctm);
+
+ xps_begin_opacity(ctx, 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(xml_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = xml_att(fill_tag, "Opacity");
+ fill_att = xml_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (fill_att)
+ {
+ float samples[32];
+ fz_colorspace *colorspace;
+
+ xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] = atof(fill_opacity_att);
+ xps_set_color(ctx, colorspace, samples);
+
+ ctx->dev->filltext(ctx->dev->user, text, ctm,
+ ctx->colorspace, ctx->color, ctx->alpha);
+ }
+
+ /*
+ * If it's a visual brush or image, use the charpath as a clip mask to paint brush
+ */
+
+ if (fill_tag)
+ {
+ ctx->dev->cliptext(ctx->dev->user, text, ctm, 0);
+ xps_parse_brush(ctx, ctm, area, fill_uri, dict, fill_tag);
+ ctx->dev->popclip(ctx->dev->user);
+ }
+
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ fz_freetext(text);
+
+ if (clip_att || clip_tag)
+ ctx->dev->popclip(ctx->dev->user);
+}
diff --git a/xps/xps_gradient.c b/xps/xps_gradient.c
new file mode 100644
index 00000000..b23098bd
--- /dev/null
+++ b/xps/xps_gradient.c
@@ -0,0 +1,458 @@
+#include "fitz.h"
+#include "muxps.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_context *ctx, char *base_uri, xml_element *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(xml_tag(node), "GradientStop"))
+ {
+ char *offset = xml_att(node, "Offset");
+ char *color = xml_att(node, "Color");
+ if (offset && color)
+ {
+ stops[count].offset = atof(offset);
+
+ xps_parse_color(ctx, base_uri, color, &colorspace, sample);
+
+ fz_convertcolor(colorspace, sample + 1, fz_devicergb, rgb);
+
+ stops[count].r = rgb[0];
+ stops[count].g = rgb[1];
+ stops[count].b = rgb[2];
+ stops[count].a = sample[0];
+
+ count ++;
+ }
+ }
+ node = xml_next(node);
+ }
+
+ if (count == 0)
+ {
+ fz_warn("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("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[0].r = lerp(stops[count-2].r, stops[count-1].r, d);
+ stops[0].g = lerp(stops[count-2].g, stops[count-1].g, d);
+ stops[0].b = lerp(stops[count-2].b, stops[count-1].b, d);
+ stops[0].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_context *ctx, 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(sizeof(fz_shade));
+ shade->refs = 1;
+ shade->colorspace = fz_devicergb;
+ shade->bbox = fz_infiniterect;
+ shade->matrix = fz_identity;
+ shade->usebackground = 0;
+ shade->usefunction = 1;
+ shade->type = FZ_RADIAL;
+ shade->extend[0] = extend;
+ shade->extend[1] = extend;
+
+ xps_sample_gradient_stops(shade, stops, count);
+
+ shade->meshlen = 6;
+ shade->meshcap = 6;
+ shade->mesh = fz_calloc(shade->meshcap, sizeof(float));
+ shade->mesh[0] = x0;
+ shade->mesh[1] = y0;
+ shade->mesh[2] = r0;
+ shade->mesh[3] = x1;
+ shade->mesh[4] = y1;
+ shade->mesh[5] = r1;
+
+ ctx->dev->fillshade(ctx->dev->user, shade, ctm, 1);
+
+ fz_dropshade(shade);
+}
+
+/*
+ * Linear gradients map to Axial shadings.
+ */
+
+static void
+xps_draw_one_linear_gradient(xps_context *ctx, 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(sizeof(fz_shade));
+ shade->refs = 1;
+ shade->colorspace = fz_devicergb;
+ shade->bbox = fz_infiniterect;
+ shade->matrix = fz_identity;
+ shade->usebackground = 0;
+ shade->usefunction = 1;
+ shade->type = FZ_LINEAR;
+ shade->extend[0] = extend;
+ shade->extend[1] = extend;
+
+ xps_sample_gradient_stops(shade, stops, count);
+
+ shade->meshlen = 6;
+ shade->meshcap = 6;
+ shade->mesh = fz_calloc(shade->meshcap, sizeof(float));
+ shade->mesh[0] = x0;
+ shade->mesh[1] = y0;
+ shade->mesh[2] = 0;
+ shade->mesh[3] = x1;
+ shade->mesh[4] = y1;
+ shade->mesh[5] = 0;
+
+ ctx->dev->fillshade(ctx->dev->user, shade, ctm, 1);
+
+ fz_dropshade(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_context *ctx, fz_matrix ctm,
+ struct stop *stops, int count,
+ xml_element *root, int spread)
+{
+ float x0, y0, r0;
+ float x1, y1, r1;
+ float xrad = 1;
+ float yrad = 1;
+ float invscale;
+
+ char *center_att = xml_att(root, "Center");
+ char *origin_att = xml_att(root, "GradientOrigin");
+ char *radius_x_att = xml_att(root, "RadiusX");
+ char *radius_y_att = xml_att(root, "RadiusY");
+
+ if (origin_att)
+ sscanf(origin_att, "%g,%g", &x0, &y0);
+ if (center_att)
+ sscanf(center_att, "%g,%g", &x1, &y1);
+ if (radius_x_att)
+ xrad = atof(radius_x_att);
+ if (radius_y_att)
+ yrad = atof(radius_y_att);
+
+ /* scale the ctm to make ellipses */
+ ctm = fz_concat(fz_scale(1.0, yrad / xrad), ctm);
+
+ invscale = xrad / yrad;
+ y0 = y0 * invscale;
+ y1 = y1 * invscale;
+
+ r0 = 0.0;
+ r1 = xrad;
+
+ xps_draw_one_radial_gradient(ctx, 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_context *ctx, fz_matrix ctm,
+ struct stop *stops, int count,
+ xml_element *root, int spread)
+{
+ float x0, y0, x1, y1;
+
+ char *start_point_att = xml_att(root, "StartPoint");
+ char *end_point_att = xml_att(root, "EndPoint");
+
+ x0 = y0 = 0;
+ x1 = y1 = 1;
+
+ if (start_point_att)
+ sscanf(start_point_att, "%g,%g", &x0, &y0);
+ if (end_point_att)
+ sscanf(end_point_att, "%g,%g", &x1, &y1);
+
+ xps_draw_one_linear_gradient(ctx, 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_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root,
+ void (*draw)(xps_context *, fz_matrix, struct stop *, int, xml_element *, int))
+{
+ xml_element *node;
+
+ char *opacity_att;
+ char *interpolation_att;
+ char *spread_att;
+ char *mapping_att;
+ char *transform_att;
+
+ xml_element *transform_tag = NULL;
+ xml_element *stop_tag = NULL;
+
+ struct stop stop_list[MAX_STOPS];
+ int stop_count;
+ fz_matrix transform;
+ int spread_method;
+
+ opacity_att = xml_att(root, "Opacity");
+ interpolation_att = xml_att(root, "ColorInterpolationMode");
+ spread_att = xml_att(root, "SpreadMethod");
+ mapping_att = xml_att(root, "MappingMode");
+ transform_att = xml_att(root, "Transform");
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "LinearGradientBrush.Transform"))
+ transform_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "RadialGradientBrush.Transform"))
+ transform_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "LinearGradientBrush.GradientStops"))
+ stop_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "RadialGradientBrush.GradientStops"))
+ stop_tag = xml_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, 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(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ ctm = fz_concat(transform, ctm);
+
+ if (!stop_tag) {
+ fz_warn("missing gradient stops tag");
+ return;
+ }
+
+ stop_count = xps_parse_gradient_stops(ctx, base_uri, stop_tag, stop_list, MAX_STOPS);
+ if (stop_count == 0)
+ {
+ fz_warn("no gradient stops found");
+ return;
+ }
+
+ xps_begin_opacity(ctx, ctm, area, base_uri, dict, opacity_att, NULL);
+
+ draw(ctx, ctm, stop_list, stop_count, root, spread_method);
+
+ xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
+}
+
+void
+xps_parse_linear_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xps_parse_gradient_brush(ctx, ctm, area, base_uri, dict, root, xps_draw_linear_gradient);
+}
+
+void
+xps_parse_radial_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xps_parse_gradient_brush(ctx, ctm, area, base_uri, dict, root, xps_draw_radial_gradient);
+}
diff --git a/xps/xps_hash.c b/xps/xps_hash.c
new file mode 100644
index 00000000..f2a7d48a
--- /dev/null
+++ b/xps/xps_hash.c
@@ -0,0 +1,190 @@
+/* Linear probe hash table.
+ *
+ * Simple hashtable with open adressing linear probe.
+ * Does not manage memory of key/value pointers.
+ * Does not support deleting entries.
+ */
+
+#include "fitz.h"
+#include "muxps.h"
+
+static const unsigned primes[] =
+{
+ 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
+ 131071, 262139, 524287, 1048573, 2097143, 4194301, 8388593, 0
+};
+
+typedef struct xps_hash_entry_s xps_hash_entry;
+
+struct xps_hash_entry_s
+{
+ char *key;
+ void *value;
+};
+
+struct xps_hash_table_s
+{
+ void *ctx;
+ unsigned int size;
+ unsigned int load;
+ xps_hash_entry *entries;
+};
+
+static inline int
+xps_tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+static unsigned int
+xps_hash(char *s)
+{
+ unsigned int h = 0;
+ while (*s)
+ h = xps_tolower(*s++) + (h << 6) + (h << 16) - h;
+ return h;
+}
+
+xps_hash_table *
+xps_hash_new(void)
+{
+ xps_hash_table *table;
+
+ table = fz_malloc(sizeof(xps_hash_table));
+ table->size = primes[0];
+ table->load = 0;
+
+ table->entries = fz_calloc(table->size, sizeof(xps_hash_entry));
+ memset(table->entries, 0, table->size * sizeof(xps_hash_entry));
+
+ return table;
+}
+
+static int
+xps_hash_double(xps_hash_table *table)
+{
+ xps_hash_entry *old_entries;
+ xps_hash_entry *new_entries;
+ unsigned int old_size = table->size;
+ unsigned int new_size = table->size * 2;
+ int i;
+
+ for (i = 0; primes[i] != 0; i++)
+ {
+ if (primes[i] > old_size)
+ {
+ new_size = primes[i];
+ break;
+ }
+ }
+
+ old_entries = table->entries;
+ new_entries = fz_calloc(new_size, sizeof(xps_hash_entry));
+
+ table->size = new_size;
+ table->entries = new_entries;
+ table->load = 0;
+
+ memset(table->entries, 0, table->size * sizeof(xps_hash_entry));
+
+ for (i = 0; i < old_size; i++)
+ if (old_entries[i].value)
+ xps_hash_insert(table, old_entries[i].key, old_entries[i].value);
+
+ fz_free(old_entries);
+
+ return 0;
+}
+
+void
+xps_hash_free(xps_hash_table *table,
+ void (*free_key)(void *),
+ void (*free_value)(void *))
+{
+ int i;
+
+ for (i = 0; i < table->size; i++)
+ {
+ if (table->entries[i].key && free_key)
+ free_key(table->entries[i].key);
+ if (table->entries[i].value && free_value)
+ free_value(table->entries[i].value);
+ }
+
+ fz_free(table->entries);
+ fz_free(table);
+}
+
+void *
+xps_hash_lookup(xps_hash_table *table, char *key)
+{
+ xps_hash_entry *entries = table->entries;
+ unsigned int size = table->size;
+ unsigned int pos = xps_hash(key) % size;
+
+ while (1)
+ {
+ if (!entries[pos].value)
+ return NULL;
+
+ if (xps_strcasecmp(key, entries[pos].key) == 0)
+ return entries[pos].value;
+
+ pos = (pos + 1) % size;
+ }
+}
+
+int
+xps_hash_insert(xps_hash_table *table, char *key, void *value)
+{
+ xps_hash_entry *entries;
+ unsigned int size, pos;
+
+ /* Grow the table at 80% load */
+ if (table->load > table->size * 8 / 10)
+ {
+ if (xps_hash_double(table) < 0)
+ return fz_rethrow(-1, "cannot grow hash table");
+ }
+
+ entries = table->entries;
+ size = table->size;
+ pos = xps_hash(key) % size;
+
+ while (1)
+ {
+ if (!entries[pos].value)
+ {
+ entries[pos].key = key;
+ entries[pos].value = value;
+ table->load ++;
+ return 0;
+ }
+
+ if (xps_strcasecmp(key, entries[pos].key) == 0)
+ {
+ return 0;
+ }
+
+ pos = (pos + 1) % size;
+ }
+}
+
+void
+xps_hash_debug(xps_hash_table *table)
+{
+ int i;
+
+ printf("hash table load %d / %d\n", table->load, table->size);
+
+ for (i = 0; i < table->size; i++)
+ {
+ if (!table->entries[i].value)
+ printf("table % 4d: empty\n", i);
+ else
+ printf("table % 4d: key=%s value=%p\n", i,
+ table->entries[i].key, table->entries[i].value);
+ }
+}
diff --git a/xps/xps_image.c b/xps/xps_image.c
new file mode 100644
index 00000000..f4b75a1b
--- /dev/null
+++ b/xps/xps_image.c
@@ -0,0 +1,128 @@
+#include "fitz.h"
+#include "muxps.h"
+
+static int
+xps_decode_image(fz_pixmap **imagep, byte *buf, int len)
+{
+ int error;
+
+ if (len < 8)
+ return fz_throw("unknown image file format");
+
+ if (buf[0] == 0xff && buf[1] == 0xd8)
+ {
+ error = xps_decode_jpeg(imagep, buf, len);
+ if (error)
+ return fz_rethrow(error, "cannot decode jpeg image");
+ }
+ else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
+ {
+ error = xps_decode_png(imagep, buf, len);
+ if (error)
+ return fz_rethrow(error, "cannot decode png image");
+ }
+ else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
+ {
+ return fz_throw("JPEG-XR codec is not available");
+ }
+ else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0)
+ {
+ error = xps_decode_tiff(imagep, buf, len);
+ if (error)
+ return fz_rethrow(error, "cannot decode TIFF image");
+ }
+ else
+ return fz_throw("unknown image file format");
+
+ return fz_okay;
+}
+
+static void
+xps_paint_image_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict,
+ xml_element *root, void *vimage)
+{
+ fz_pixmap *pixmap = vimage;
+ float xs = pixmap->w * 96.0 / pixmap->xres;
+ float ys = pixmap->h * 96.0 / pixmap->yres;
+ fz_matrix im = fz_scale(xs, -ys);
+ im.f = ys;
+ ctm = fz_concat(im, ctm);
+ ctx->dev->fillimage(ctx->dev->user, pixmap, ctm, ctx->opacity[ctx->opacity_top]);
+}
+
+static xps_part *
+xps_find_image_brush_source_part(xps_context *ctx, char *base_uri, xml_element *root)
+{
+ char *image_source_att;
+ char buf[1024];
+ char partname[1024];
+ char *image_name;
+ char *profile_name;
+ char *p;
+
+ image_source_att = xml_att(root, "ImageSource");
+ if (!image_source_att)
+ return NULL;
+
+ /* "{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)
+ return NULL;
+
+ xps_absolute_path(partname, base_uri, image_name, sizeof partname);
+
+ return xps_read_part(ctx, partname);
+}
+
+void
+xps_parse_image_brush(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xps_part *part;
+ fz_pixmap *image;
+ int code;
+
+ part = xps_find_image_brush_source_part(ctx, base_uri, root);
+ if (!part) {
+ fz_warn("cannot find image source");
+ return;
+ }
+
+ code = xps_decode_image(&image, part->data, part->size);
+ if (code < 0) {
+ xps_free_part(ctx, part);
+ fz_catch(-1, "cannot decode image resource");
+ return;
+ }
+
+ xps_parse_tiling_brush(ctx, ctm, area, base_uri, dict, root, xps_paint_image_brush, image);
+
+ fz_droppixmap(image);
+ xps_free_part(ctx, part);
+}
diff --git a/xps/xps_jpeg.c b/xps/xps_jpeg.c
new file mode 100644
index 00000000..a8ea40f5
--- /dev/null
+++ b/xps/xps_jpeg.c
@@ -0,0 +1,131 @@
+#include "fitz.h"
+#include "muxps.h"
+
+#include <jpeglib.h>
+#include <setjmp.h>
+
+struct jpeg_error_mgr_jmp
+{
+ struct jpeg_error_mgr super;
+ jmp_buf env;
+ char msg[JMSG_LENGTH_MAX];
+};
+
+static void error_exit(j_common_ptr cinfo)
+{
+ struct jpeg_error_mgr_jmp *err = (struct jpeg_error_mgr_jmp *)cinfo->err;
+ cinfo->err->format_message(cinfo, err->msg);
+ longjmp(err->env, 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)
+{
+ 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)
+ {
+ src->next_input_byte += num_bytes;
+ src->bytes_in_buffer -= num_bytes;
+ }
+}
+
+int
+xps_decode_jpeg(fz_pixmap **imagep, byte *rbuf, int rlen)
+{
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr_jmp err;
+ struct jpeg_source_mgr src;
+ unsigned char *row[1], *sp, *dp;
+ fz_colorspace *colorspace;
+ int x, k;
+
+ fz_pixmap *image = NULL;
+
+ if (setjmp(err.env))
+ {
+ if (image)
+ fz_droppixmap(image);
+ return fz_throw("jpeg error: %s", err.msg);
+ }
+
+ cinfo.err = jpeg_std_error(&err.super);
+ err.super.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);
+
+ jpeg_start_decompress(&cinfo);
+
+ if (cinfo.output_components == 1)
+ colorspace = fz_devicegray;
+ else if (cinfo.output_components == 3)
+ colorspace = fz_devicergb;
+ else if (cinfo.output_components == 4)
+ colorspace = fz_devicecmyk;
+ else
+ return fz_throw("bad number of components in jpeg: %d", cinfo.output_components);
+
+ image = fz_newpixmap(colorspace, 0, 0, cinfo.output_width, cinfo.output_height);
+
+ if (cinfo.density_unit == 1)
+ {
+ image->xres = cinfo.X_density;
+ image->yres = cinfo.Y_density;
+ }
+ else if (cinfo.density_unit == 2)
+ {
+ image->xres = cinfo.X_density * 2.54;
+ image->yres = cinfo.Y_density * 2.54;
+ }
+
+ fz_clearpixmap(image);
+
+ row[0] = fz_malloc(cinfo.output_components * cinfo.output_width);
+ dp = image->samples;
+ while (cinfo.output_scanline < cinfo.output_height)
+ {
+ jpeg_read_scanlines(&cinfo, row, 1);
+ sp = row[0];
+ for (x = 0; x < cinfo.output_width; x++)
+ {
+ for (k = 0; k < cinfo.output_components; k++)
+ *dp++ = *sp++;
+ *dp++ = 255;
+ }
+ }
+ fz_free(row[0]);
+
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ *imagep = image;
+ return fz_okay;
+}
diff --git a/xps/xps_path.c b/xps/xps_path.c
new file mode 100644
index 00000000..28b34894
--- /dev/null
+++ b/xps/xps_path.c
@@ -0,0 +1,990 @@
+#include "fitz.h"
+#include "muxps.h"
+
+static fz_point
+fz_currentpoint(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->els[i++].k)
+ {
+ case FZ_MOVETO:
+ m.x = c.x = path->els[i++].v;
+ m.y = c.y = path->els[i++].v;
+ break;
+ case FZ_LINETO:
+ c.x = path->els[i++].v;
+ c.y = path->els[i++].v;
+ break;
+ case FZ_CURVETO:
+ i += 4;
+ c.x = path->els[i++].v;
+ c.y = path->els[i++].v;
+ break;
+ case FZ_CLOSEPATH:
+ c = m;
+ }
+ }
+
+ return c;
+}
+
+/* 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.
+ */
+static inline void
+xps_draw_arc_segment(fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
+{
+ float t, d;
+ fz_point p;
+
+ while (th1 < th0)
+ th1 += M_PI * 2.0;
+
+ d = 1 * (M_PI / 180.0); /* 1-degree precision */
+
+ if (iscw)
+ {
+ p.x = cos(th0);
+ p.y = sin(th0);
+ p = fz_transformpoint(mtx, p);
+ fz_lineto(path, p.x, p.y);
+ for (t = th0; t < th1; t += d)
+ {
+ p.x = cos(t);
+ p.y = sin(t);
+ p = fz_transformpoint(mtx, p);
+ fz_lineto(path, p.x, p.y);
+ }
+ p.x = cos(th1);
+ p.y = sin(th1);
+ p = fz_transformpoint(mtx, p);
+ fz_lineto(path, p.x, p.y);
+ }
+ else
+ {
+ th0 += M_PI * 2;
+ p.x = cos(th0);
+ p.y = sin(th0);
+ p = fz_transformpoint(mtx, p);
+ fz_lineto(path, p.x, p.y);
+ for (t = th0; t > th1; t -= d)
+ {
+ p.x = cos(t);
+ p.y = sin(t);
+ p = fz_transformpoint(mtx, p);
+ fz_lineto(path, p.x, p.y);
+ }
+ p.x = cos(th1);
+ p.y = sin(th1);
+ p = fz_transformpoint(mtx, p);
+ fz_lineto(path, p.x, p.y);
+ }
+}
+
+/* Given two vectors find the angle between them. */
+static inline double
+angle_between(const fz_point u, const fz_point v)
+{
+ double det = u.x * v.y - u.y * v.x;
+ double sign = (det < 0 ? -1.0 : 1.0);
+ double magu = u.x * u.x + u.y * u.y;
+ double magv = v.x * v.x + v.y * v.y;
+ double udotv = u.x * v.x + u.y * v.y;
+ double t = udotv / (magu * magv);
+ /* guard against rounding errors when near |1| (where acos will return NaN) */
+ if (t < -1.0) t = -1.0;
+ if (t > 1.0) t = 1.0;
+ return sign * acos(t);
+}
+
+static void
+xps_draw_arc(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;
+ double rx, ry;
+ double x1, y1, x2, y2;
+ double x1t, y1t;
+ double cxt, cyt, cx, cy;
+ double t1, t2, t3;
+ double sign;
+ double th1, dth;
+
+ pt = fz_currentpoint(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;
+
+ rotmat = fz_rotate(rotation_angle);
+ revmat = fz_rotate(-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.001 || ry < 0.001)
+ {
+ fz_lineto(path, x2, y2);
+ return;
+ }
+
+ /* F.6.5.1 */
+ pt.x = (x1 - x2) / 2;
+ pt.y = (y1 - y2) / 2;
+ pt = fz_transformvector(revmat, pt);
+ 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.0)
+ {
+ 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.0) t3 = 0.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;
+ pt = fz_transformvector(rotmat, pt);
+ 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 += ((M_PI / 180.0) * 360);
+ if (dth > 0 && is_clockwise)
+ dth -= ((M_PI / 180.0) * 360);
+ }
+
+ mtx = fz_identity;
+ mtx = fz_concat(fz_translate(cx, cy), mtx);
+ mtx = fz_concat(fz_rotate(rotation_angle), mtx);
+ mtx = fz_concat(fz_scale(rx, ry), mtx);
+ xps_draw_arc_segment(path, mtx, th1, th1 + dth, is_clockwise);
+
+ fz_lineto(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_context *ctx, 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_newpath();
+
+ args = fz_calloc(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[0] = s;
+ pargs[1] = 0;
+
+ n = pargs - args;
+ i = 0;
+
+ old = 0;
+
+ reset_smooth = 1;
+ smooth_x = 0.0;
+ smooth_y = 0.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.0;
+ smooth_y = 0.0;
+ }
+
+ reset_smooth = 1;
+
+ switch (cmd)
+ {
+ case 'F':
+ *fill_rule = atoi(args[i]);
+ i ++;
+ break;
+
+ case 'M':
+ fz_moveto(path, atof(args[i]), atof(args[i+1]));
+ i += 2;
+ break;
+ case 'm':
+ pt = fz_currentpoint(path);
+ fz_moveto(path, pt.x + atof(args[i]), pt.y + atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'L':
+ fz_lineto(path, atof(args[i]), atof(args[i+1]));
+ i += 2;
+ break;
+ case 'l':
+ pt = fz_currentpoint(path);
+ fz_lineto(path, pt.x + atof(args[i]), pt.y + atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'H':
+ pt = fz_currentpoint(path);
+ fz_lineto(path, atof(args[i]), pt.y);
+ i += 1;
+ break;
+ case 'h':
+ pt = fz_currentpoint(path);
+ fz_lineto(path, pt.x + atof(args[i]), pt.y);
+ i += 1;
+ break;
+
+ case 'V':
+ pt = fz_currentpoint(path);
+ fz_lineto(path, pt.x, atof(args[i]));
+ i += 1;
+ break;
+ case 'v':
+ pt = fz_currentpoint(path);
+ fz_lineto(path, pt.x, pt.y + atof(args[i]));
+ i += 1;
+ break;
+
+ case 'C':
+ x1 = atof(args[i+0]);
+ y1 = atof(args[i+1]);
+ x2 = atof(args[i+2]);
+ y2 = atof(args[i+3]);
+ x3 = atof(args[i+4]);
+ y3 = atof(args[i+5]);
+ fz_curveto(path, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'c':
+ pt = fz_currentpoint(path);
+ x1 = atof(args[i+0]) + pt.x;
+ y1 = atof(args[i+1]) + pt.y;
+ x2 = atof(args[i+2]) + pt.x;
+ y2 = atof(args[i+3]) + pt.y;
+ x3 = atof(args[i+4]) + pt.x;
+ y3 = atof(args[i+5]) + pt.y;
+ fz_curveto(path, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'S':
+ pt = fz_currentpoint(path);
+ x1 = atof(args[i+0]);
+ y1 = atof(args[i+1]);
+ x2 = atof(args[i+2]);
+ y2 = atof(args[i+3]);
+ fz_curveto(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':
+ pt = fz_currentpoint(path);
+ x1 = atof(args[i+0]) + pt.x;
+ y1 = atof(args[i+1]) + pt.y;
+ x2 = atof(args[i+2]) + pt.x;
+ y2 = atof(args[i+3]) + pt.y;
+ fz_curveto(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':
+ pt = fz_currentpoint(path);
+ x1 = atof(args[i+0]);
+ y1 = atof(args[i+1]);
+ x2 = atof(args[i+2]);
+ y2 = atof(args[i+3]);
+ fz_curveto(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':
+ pt = fz_currentpoint(path);
+ x1 = atof(args[i+0]) + pt.x;
+ y1 = atof(args[i+1]) + pt.y;
+ x2 = atof(args[i+2]) + pt.x;
+ y2 = atof(args[i+3]) + pt.y;
+ fz_curveto(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':
+ xps_draw_arc(path,
+ atof(args[i+0]), atof(args[i+1]), atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ atof(args[i+5]), atof(args[i+6]));
+ i += 7;
+ break;
+ case 'a':
+ pt = fz_currentpoint(path);
+ xps_draw_arc(path,
+ atof(args[i+0]), atof(args[i+1]), atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ atof(args[i+5]) + pt.x, atof(args[i+6]) + pt.y);
+ i += 7;
+ break;
+
+ case 'Z':
+ case 'z':
+ fz_closepath(path);
+ break;
+
+ default:
+ /* eek */
+ break;
+ }
+
+ old = cmd;
+ }
+
+ fz_free(args);
+ return path;
+}
+
+static void
+xps_parse_arc_segment(fz_path *path, xml_element *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 = xml_att(root, "Point");
+ char *size_att = xml_att(root, "Size");
+ char *rotation_angle_att = xml_att(root, "RotationAngle");
+ char *is_large_arc_att = xml_att(root, "IsLargeArc");
+ char *sweep_direction_att = xml_att(root, "SweepDirection");
+ char *is_stroked_att = xml_att(root, "IsStroked");
+
+ if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
+ {
+ fz_warn("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;
+
+ sscanf(point_att, "%g,%g", &point_x, &point_y);
+ sscanf(size_att, "%g,%g", &size_x, &size_y);
+ rotation_angle = 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(path, point_x, point_y);
+ return;
+ }
+
+ xps_draw_arc(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_path *path, xml_element *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = xml_att(root, "Points");
+ char *is_stroked_att = xml_att(root, "IsStroked");
+ float x[2], y[2];
+ int is_stroked;
+ fz_point pt;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ fz_warn("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++;
+ sscanf(s, "%g,%g", &x[n], &y[n]);
+ while (*s != ' ' && *s != 0) s++;
+ n ++;
+ if (n == 2)
+ {
+ if (stroking && !is_stroked)
+ {
+ fz_moveto(path, x[1], y[1]);
+ }
+ else
+ {
+ pt = fz_currentpoint(path);
+ fz_curveto(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_path *path, xml_element *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = xml_att(root, "Points");
+ char *is_stroked_att = xml_att(root, "IsStroked");
+ float x[3], y[3];
+ int is_stroked;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ fz_warn("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++;
+ sscanf(s, "%g,%g", &x[n], &y[n]);
+ while (*s != ' ' && *s != 0) s++;
+ n ++;
+ if (n == 3)
+ {
+ if (stroking && !is_stroked)
+ fz_moveto(path, x[2], y[2]);
+ else
+ fz_curveto(path, x[0], y[0], x[1], y[1], x[2], y[2]);
+ n = 0;
+ }
+ }
+}
+
+static void
+xps_parse_poly_line_segment(fz_path *path, xml_element *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = xml_att(root, "Points");
+ char *is_stroked_att = xml_att(root, "IsStroked");
+ int is_stroked;
+ float x, y;
+ char *s;
+
+ if (!points_att)
+ {
+ fz_warn("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++;
+ sscanf(s, "%g,%g", &x, &y);
+ if (stroking && !is_stroked)
+ fz_moveto(path, x, y);
+ else
+ fz_lineto(path, x, y);
+ while (*s != ' ' && *s != 0) s++;
+ }
+}
+
+static void
+xps_parse_path_figure(fz_path *path, xml_element *root, int stroking)
+{
+ xml_element *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.0;
+ float start_y = 0.0;
+
+ int skipped_stroke = 0;
+
+ is_closed_att = xml_att(root, "IsClosed");
+ start_point_att = xml_att(root, "StartPoint");
+ is_filled_att = 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)
+ sscanf(start_point_att, "%g,%g", &start_x, &start_y);
+
+ if (!stroking && !is_filled) /* not filled, when filling */
+ return;
+
+ fz_moveto(path, start_x, start_y);
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "ArcSegment"))
+ xps_parse_arc_segment(path, node, stroking, &skipped_stroke);
+ if (!strcmp(xml_tag(node), "PolyBezierSegment"))
+ xps_parse_poly_bezier_segment(path, node, stroking, &skipped_stroke);
+ if (!strcmp(xml_tag(node), "PolyLineSegment"))
+ xps_parse_poly_line_segment(path, node, stroking, &skipped_stroke);
+ if (!strcmp(xml_tag(node), "PolyQuadraticBezierSegment"))
+ xps_parse_poly_quadratic_bezier_segment(path, node, stroking, &skipped_stroke);
+ }
+
+ if (is_closed)
+ {
+ if (stroking && skipped_stroke)
+ fz_lineto(path, start_x, start_y); /* we've skipped using fz_moveto... */
+ else
+ fz_closepath(path); /* no skipped segments, safe to closepath properly */
+ }
+}
+
+fz_path *
+xps_parse_path_geometry(xps_context *ctx, xps_resource *dict, xml_element *root, int stroking, int *fill_rule)
+{
+ xml_element *node;
+
+ char *figures_att;
+ char *fill_rule_att;
+ char *transform_att;
+
+ xml_element *transform_tag = NULL;
+ xml_element *figures_tag = NULL; /* only used by resource */
+
+ fz_matrix transform;
+ fz_path *path;
+
+ figures_att = xml_att(root, "Figures");
+ fill_rule_att = xml_att(root, "FillRule");
+ transform_att = xml_att(root, "Transform");
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "PathGeometry.Transform"))
+ transform_tag = xml_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, 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(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+
+ if (figures_att)
+ path = xps_parse_abbreviated_geometry(ctx, figures_att, fill_rule);
+ else
+ path = fz_newpath();
+
+ if (figures_tag)
+ xps_parse_path_figure(path, figures_tag, stroking);
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "PathFigure"))
+ xps_parse_path_figure(path, node, stroking);
+ }
+
+ if (transform_att || transform_tag)
+ fz_transformpath(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; /* FIXME add triangle caps */
+ }
+ return 0;
+}
+
+void
+xps_clip(xps_context *ctx, fz_matrix ctm, xps_resource *dict, char *clip_att, xml_element *clip_tag)
+{
+ fz_path *path;
+ int fill_rule = 0;
+
+ if (clip_att)
+ path = xps_parse_abbreviated_geometry(ctx, clip_att, &fill_rule);
+ else if (clip_tag)
+ path = xps_parse_path_geometry(ctx, dict, clip_tag, 0, &fill_rule);
+ else
+ path = fz_newpath();
+ ctx->dev->clippath(ctx->dev->user, path, fill_rule == 0, ctm);
+ fz_freepath(path);
+}
+
+/*
+ * Parse an XPS <Path> element, and call relevant ghostscript
+ * functions for drawing and/or clipping the child elements.
+ */
+
+void
+xps_parse_path(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xml_element *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;
+
+ xml_element *transform_tag = NULL;
+ xml_element *clip_tag = NULL;
+ xml_element *data_tag = NULL;
+ xml_element *fill_tag = NULL;
+ xml_element *stroke_tag = NULL;
+ xml_element *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;
+
+ fz_strokestate stroke;
+ fz_matrix transform;
+ float samples[32];
+ fz_colorspace *colorspace;
+ fz_path *path;
+ fz_rect area;
+ int fill_rule;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ transform_att = xml_att(root, "RenderTransform");
+ clip_att = xml_att(root, "Clip");
+ data_att = xml_att(root, "Data");
+ fill_att = xml_att(root, "Fill");
+ stroke_att = xml_att(root, "Stroke");
+ opacity_att = xml_att(root, "Opacity");
+ opacity_mask_att = xml_att(root, "OpacityMask");
+
+ stroke_dash_array_att = xml_att(root, "StrokeDashArray");
+ stroke_dash_cap_att = xml_att(root, "StrokeDashCap");
+ stroke_dash_offset_att = xml_att(root, "StrokeDashOffset");
+ stroke_end_line_cap_att = xml_att(root, "StrokeEndLineCap");
+ stroke_start_line_cap_att = xml_att(root, "StrokeStartLineCap");
+ stroke_line_join_att = xml_att(root, "StrokeLineJoin");
+ stroke_miter_limit_att = xml_att(root, "StrokeMiterLimit");
+ stroke_thickness_att = xml_att(root, "StrokeThickness");
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "Path.RenderTransform"))
+ transform_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Path.OpacityMask"))
+ opacity_mask_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Path.Clip"))
+ clip_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Path.Fill"))
+ fill_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Path.Stroke"))
+ stroke_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Path.Data"))
+ data_tag = xml_down(node);
+ }
+
+ fill_uri = base_uri;
+ stroke_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(ctx, dict, &data_att, &data_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(ctx, dict, &stroke_att, &stroke_tag, &stroke_uri);
+ xps_resolve_resource_reference(ctx, 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(xml_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = xml_att(fill_tag, "Opacity");
+ fill_att = xml_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (stroke_tag && !strcmp(xml_tag(stroke_tag), "SolidColorBrush"))
+ {
+ stroke_opacity_att = xml_att(stroke_tag, "Opacity");
+ stroke_att = xml_att(stroke_tag, "Color");
+ stroke_tag = NULL;
+ }
+
+ stroke.linecap = xps_parse_line_cap(stroke_start_line_cap_att);
+// fz_setlineendcap(ctx->pgs, xps_parse_line_cap(stroke_end_line_cap_att));
+// fz_setlinedashcap(ctx->pgs, xps_parse_line_cap(stroke_dash_cap_att));
+
+ stroke.linejoin = 0;
+ if (stroke_line_join_att)
+ {
+ if (!strcmp(stroke_line_join_att, "Miter")) stroke.linejoin = 0;
+ if (!strcmp(stroke_line_join_att, "Round")) stroke.linejoin = 1;
+ if (!strcmp(stroke_line_join_att, "Bevel")) stroke.linejoin = 2;
+ }
+
+ stroke.miterlimit = 10.0;
+ if (stroke_miter_limit_att)
+ stroke.miterlimit = atof(stroke_miter_limit_att);
+
+ stroke.linewidth = 1.0;
+ if (stroke_thickness_att)
+ stroke.linewidth = atof(stroke_thickness_att);
+
+ stroke.dashphase = 0;
+ stroke.dashlen = 0;
+ if (stroke_dash_array_att)
+ {
+ char *s = stroke_dash_array_att;
+
+ if (stroke_dash_offset_att)
+ stroke.dashphase = atof(stroke_dash_offset_att) * stroke.linewidth;
+
+ while (*s && stroke.dashlen < nelem(stroke.dashlist))
+ {
+ while (*s == ' ')
+ s++;
+ stroke.dashlist[stroke.dashlen++] = atof(s) * stroke.linewidth;
+ while (*s && *s != ' ')
+ s++;
+ }
+ }
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ ctm = fz_concat(transform, ctm);
+
+ if (clip_att || clip_tag)
+ xps_clip(ctx, ctm, dict, clip_att, clip_tag);
+
+ fill_rule = 0;
+ if (data_att)
+ path = xps_parse_abbreviated_geometry(ctx, data_att, &fill_rule);
+ else if (data_tag)
+ path = xps_parse_path_geometry(ctx, dict, data_tag, 0, &fill_rule);
+
+ if (stroke_att || stroke_tag)
+ area = fz_boundpath(path, &stroke, ctm);
+ else
+ area = fz_boundpath(path, NULL, ctm);
+
+ xps_begin_opacity(ctx, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (fill_att)
+ {
+ xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] = atof(fill_opacity_att);
+ xps_set_color(ctx, colorspace, samples);
+
+ ctx->dev->fillpath(ctx->dev->user, path, fill_rule == 0, ctm,
+ ctx->colorspace, ctx->color, ctx->alpha);
+ }
+
+ if (fill_tag)
+ {
+ area = fz_boundpath(path, NULL, ctm);
+
+ ctx->dev->clippath(ctx->dev->user, path, fill_rule == 0, ctm);
+ xps_parse_brush(ctx, ctm, area, fill_uri, dict, fill_tag);
+ ctx->dev->popclip(ctx->dev->user);
+ }
+
+ if (stroke_att)
+ {
+ xps_parse_color(ctx, base_uri, stroke_att, &colorspace, samples);
+ if (stroke_opacity_att)
+ samples[0] = atof(stroke_opacity_att);
+ xps_set_color(ctx, colorspace, samples);
+
+ ctx->dev->strokepath(ctx->dev->user, path, &stroke, ctm,
+ ctx->colorspace, ctx->color, ctx->alpha);
+ }
+
+ if (stroke_tag)
+ {
+ ctx->dev->clipstrokepath(ctx->dev->user, path, &stroke, ctm);
+ xps_parse_brush(ctx, ctm, area, stroke_uri, dict, stroke_tag);
+ ctx->dev->popclip(ctx->dev->user);
+ }
+
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ fz_freepath(path);
+ path = NULL;
+
+ if (clip_att || clip_tag)
+ ctx->dev->popclip(ctx->dev->user);
+}
diff --git a/xps/xps_png.c b/xps/xps_png.c
new file mode 100644
index 00000000..829bde90
--- /dev/null
+++ b/xps/xps_png.c
@@ -0,0 +1,574 @@
+#include "fitz.h"
+#include "muxps.h"
+
+#include <zlib.h>
+
+struct info
+{
+ int width, height, depth, n;
+ int interlace, indexed;
+ int size;
+ unsigned char *samples;
+ unsigned char palette[256*4];
+ int transparency;
+ int trns[3];
+ int xres, yres;
+};
+
+static inline int getint(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_calloc(items, size);
+}
+
+static void zfree(void *opaque, void *address)
+{
+ fz_free(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, int width, int height, int n, int depth)
+{
+ int stride = (width * n * depth + 7) / 8;
+ int bpp = (n * depth + 7) / 8;
+ int i, row;
+
+ for (row = 0; row < height; row ++)
+ {
+ unsigned char *src = samples + (stride + 1) * row;
+ unsigned char *dst = samples + 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 int adam7_ix[7] = { 0, 4, 0, 2, 0, 1, 0 };
+static const int adam7_dx[7] = { 8, 8, 4, 4, 2, 2, 1 };
+static const int adam7_iy[7] = { 0, 0, 4, 0, 2, 0, 1 };
+static const int adam7_dy[7] = { 8, 8, 8, 4, 4, 2, 2 };
+
+static void
+png_deinterlace_passes(struct info *info, int *w, int *h, 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, int *passw, int *passh, int *passofs)
+{
+ int n = info->n;
+ int depth = info->depth;
+ int stride = (info->width * n * depth + 7) / 8;
+ unsigned char *output;
+ int p, x, y, k;
+
+ output = fz_calloc(info->height, stride);
+
+ for (p = 0; p < 7; p++)
+ {
+ unsigned char *sp = info->samples + passofs[p];
+ int w = passw[p];
+ 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->samples);
+ info->samples = output;
+}
+
+static int
+png_read_ihdr(struct info *info, unsigned char *p, int size)
+{
+ int color, compression, filter;
+
+ if (size != 13)
+ return fz_throw("IHDR chunk is the wrong size");
+
+ info->width = getint(p + 0);
+ info->height = getint(p + 4);
+ info->depth = p[8];
+
+ color = p[9];
+ compression = p[10];
+ filter = p[11];
+ info->interlace = p[12];
+
+ if (info->width <= 0)
+ return fz_throw("image width must be > 0");
+ if (info->height <= 0)
+ return fz_throw("image height must be > 0");
+
+ if (info->depth != 1 && info->depth != 2 && info->depth != 4 &&
+ info->depth != 8 && info->depth != 16)
+ return fz_throw("image bit depth must be one of 1, 2, 4, 8, 16");
+ if (color == 2 && info->depth < 8)
+ return fz_throw("illegal bit depth for truecolor");
+ if (color == 3 && info->depth > 8)
+ return fz_throw("illegal bit depth for indexed");
+ if (color == 4 && info->depth < 8)
+ return fz_throw("illegal bit depth for grayscale with alpha");
+ if (color == 6 && info->depth < 8)
+ return fz_throw("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
+ return fz_throw("unknown color type");
+
+ if (compression != 0)
+ return fz_throw("unknown compression method");
+ if (filter != 0)
+ return fz_throw("unknown filter method");
+ if (info->interlace != 0 && info->interlace != 1)
+ return fz_throw("interlace method not supported");
+
+ return fz_okay;
+}
+
+static int
+png_read_plte(struct info *info, unsigned char *p, int size)
+{
+ int n = size / 3;
+ int i;
+
+ if (n > 256 || n > (1 << info->depth))
+ return fz_throw("too many samples in palette");
+
+ 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];
+ }
+
+ return fz_okay;
+}
+
+static int
+png_read_trns(struct info *info, unsigned char *p, int size)
+{
+ int i;
+
+ info->transparency = 1;
+
+ if (info->indexed)
+ {
+ if (size > 256 || size > (1 << info->depth))
+ return fz_throw("too many samples in transparency table");
+ for (i = 0; i < size; i++)
+ info->palette[i * 4 + 3] = p[i];
+ }
+ else
+ {
+ if (size != info->n * 2)
+ return fz_throw("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);
+ }
+
+ return fz_okay;
+}
+
+static int
+png_read_idat(struct info *info, unsigned char *p, 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)
+ return fz_throw("zlib error: %s", stm->msg);
+ if (stm->avail_in != 0)
+ {
+ if (stm->avail_out == 0)
+ return fz_throw("ran out of output before input");
+ return fz_throw("inflate did not consume buffer (%d remaining)", stm->avail_in);
+ }
+
+ return fz_okay;
+}
+
+static int
+png_read_phys(struct info *info, unsigned char *p, int size)
+{
+ if (size != 9)
+ return fz_throw("pHYs chunk is the wrong size");
+ if (p[8] == 1)
+ {
+ info->xres = getint(p) * 254 / 10000;
+ info->yres = getint(p + 4) * 254 / 10000;
+ }
+ return fz_okay;
+}
+
+static int
+png_read_image(struct info *info, unsigned char *p, int total)
+{
+ int passw[7], passh[7], passofs[8];
+ int code, size;
+ z_stream stm;
+
+ memset(info, 0, sizeof (struct info));
+ memset(info->palette, 255, sizeof(info->palette));
+ info->xres = 96;
+ info->yres = 96;
+
+ /* Read signature */
+
+ if (total < 8 + 12 || memcmp(p, png_signature, 8))
+ return fz_throw("not a png image (wrong signature)");
+
+ p += 8;
+ total -= 8;
+
+ /* Read IHDR chunk (must come first) */
+
+ size = getint(p);
+
+ if (size + 12 > total)
+ return fz_throw("premature end of data in png image");
+
+ if (!memcmp(p + 4, "IHDR", 4))
+ {
+ code = png_read_ihdr(info, p + 8, size);
+ if (code)
+ return fz_rethrow(code, "cannot read png header");
+ }
+ else
+ return fz_throw("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(info->size);
+
+ stm.zalloc = zalloc;
+ stm.zfree = zfree;
+ stm.opaque = NULL;
+
+ stm.next_out = info->samples;
+ stm.avail_out = info->size;
+
+ code = inflateInit(&stm);
+ if (code != Z_OK)
+ return fz_throw("zlib error: %s", stm.msg);
+
+ /* Read remaining chunks until IEND */
+
+ while (total > 8)
+ {
+ size = getint(p);
+
+ if (size + 12 > total)
+ return fz_throw("premature end of data in png image");
+
+ if (!memcmp(p + 4, "PLTE", 4))
+ {
+ code = png_read_plte(info, p + 8, size);
+ if (code)
+ return fz_rethrow(code, "cannot read png palette");
+ }
+
+ if (!memcmp(p + 4, "tRNS", 4))
+ {
+ code = png_read_trns(info, p + 8, size);
+ if (code)
+ return fz_rethrow(code, "cannot read png transparency");
+ }
+
+ if (!memcmp(p + 4, "pHYs", 4))
+ {
+ code = png_read_phys(info, p + 8, size);
+ if (code)
+ return fz_rethrow(code, "cannot read png resolution");
+ }
+
+ if (!memcmp(p + 4, "IDAT", 4))
+ {
+ code = png_read_idat(info, p + 8, size, &stm);
+ if (code)
+ return fz_rethrow(code, "cannot read png image data");
+ }
+
+ if (!memcmp(p + 4, "IEND", 4))
+ break;
+
+ p += size + 12;
+ total -= size + 12;
+ }
+
+ code = inflateEnd(&stm);
+ if (code != Z_OK)
+ return fz_throw("zlib error: %s", stm.msg);
+
+ /* Apply prediction filter and deinterlacing */
+
+ if (!info->interlace)
+ png_predict(info->samples, info->width, info->height, info->n, info->depth);
+ else
+ png_deinterlace(info, passw, passh, passofs);
+
+ return fz_okay;
+}
+
+static fz_pixmap *
+png_expand_palette(struct info *info, fz_pixmap *src)
+{
+ fz_pixmap *dst = fz_newpixmap(fz_devicergb, 0, 0, src->w, src->h);
+ unsigned char *sp = src->samples;
+ unsigned char *dp = dst->samples;
+ int x, y;
+
+ dst->xres = src->xres;
+ dst->yres = src->yres;
+
+ for (y = 0; y < info->height; y++)
+ {
+ for (x = 0; x < info->width; 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_droppixmap(src);
+ return dst;
+}
+
+static void
+png_mask_transparency(struct info *info, fz_pixmap *dst)
+{
+ int stride = (info->width * info->n * info->depth + 7) / 8;
+ int depth = info->depth;
+ int n = info->n;
+ int x, y, k, t;
+
+ for (y = 0; y < info->height; y++)
+ {
+ unsigned char *sp = info->samples + y * stride;
+ unsigned char *dp = dst->samples + 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;
+ }
+ }
+}
+
+int
+xps_decode_png(fz_pixmap **imagep, byte *p, int total)
+{
+ fz_pixmap *image;
+ fz_colorspace *colorspace;
+ struct info png;
+ int code;
+ int stride;
+
+ code = png_read_image(&png, p, total);
+ if (code)
+ return fz_rethrow(code, "cannot read png image");
+
+ if (png.n == 3 || png.n == 4)
+ colorspace = fz_devicergb;
+ else
+ colorspace = fz_devicegray;
+
+ stride = (png.width * png.n * png.depth + 7) / 8;
+
+ image = fz_newpixmap(colorspace, 0, 0, png.width, png.height);
+ image->xres = png.xres;
+ image->yres = png.yres;
+
+ fz_unpacktile(image, png.samples, png.n, png.depth, stride, png.indexed);
+
+ if (png.indexed)
+ image = png_expand_palette(&png, image);
+ else if (png.transparency)
+ png_mask_transparency(&png, image);
+
+ if (png.transparency || png.n == 2 || png.n == 4)
+ fz_premultiplypixmap(image);
+
+ fz_free(png.samples);
+
+ *imagep = image;
+ return fz_okay;
+}
diff --git a/xps/xps_resource.c b/xps/xps_resource.c
new file mode 100644
index 00000000..7cbfe91f
--- /dev/null
+++ b/xps/xps_resource.c
@@ -0,0 +1,187 @@
+#include "fitz.h"
+#include "muxps.h"
+
+static xml_element *
+xps_find_resource(xps_context *ctx, 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 xml_element *
+xps_parse_resource_reference(xps_context *ctx, 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_find_resource(ctx, dict, name, urip);
+}
+
+void
+xps_resolve_resource_reference(xps_context *ctx, xps_resource *dict,
+ char **attp, xml_element **tagp, char **urip)
+{
+ if (*attp)
+ {
+ xml_element *rsrc = xps_parse_resource_reference(ctx, dict, *attp, urip);
+ if (rsrc)
+ {
+ *attp = NULL;
+ *tagp = rsrc;
+ }
+ }
+}
+
+static int
+xps_parse_remote_resource_dictionary(xps_context *ctx, xps_resource **dictp, char *base_uri, char *source_att)
+{
+ char part_name[1024];
+ char part_uri[1024];
+ xps_resource *dict;
+ xps_part *part;
+ xml_element *xml;
+ char *s;
+ int code;
+
+ /* External resource dictionaries MUST NOT reference other resource dictionaries */
+ xps_absolute_path(part_name, base_uri, source_att, sizeof part_name);
+ part = xps_read_part(ctx, part_name);
+ if (!part)
+ {
+ return fz_throw("cannot find remote resource part '%s'", part_name);
+ }
+
+ xml = xml_parse_document(part->data, part->size);
+ if (!xml)
+ {
+ xps_free_part(ctx, part);
+ return fz_rethrow(-1, "cannot parse xml");
+ }
+
+ if (strcmp(xml_tag(xml), "ResourceDictionary"))
+ {
+ xml_free_element(xml);
+ xps_free_part(ctx, part);
+ return fz_throw("expected ResourceDictionary element (found %s)", xml_tag(xml));
+ }
+
+ fz_strlcpy(part_uri, part_name, sizeof part_uri);
+ s = strrchr(part_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ code = xps_parse_resource_dictionary(ctx, &dict, part_uri, xml);
+ if (code)
+ {
+ xml_free_element(xml);
+ xps_free_part(ctx, part);
+ return fz_rethrow(code, "cannot parse remote resource dictionary: %s", part_uri);
+ }
+
+ dict->base_xml = xml; /* pass on ownership */
+
+ xps_free_part(ctx, part);
+
+ *dictp = dict;
+ return fz_okay;
+}
+
+int
+xps_parse_resource_dictionary(xps_context *ctx, xps_resource **dictp, char *base_uri, xml_element *root)
+{
+ xps_resource *head;
+ xps_resource *entry;
+ xml_element *node;
+ char *source;
+ char *key;
+ int code;
+
+ source = xml_att(root, "Source");
+ if (source)
+ {
+ code = xps_parse_remote_resource_dictionary(ctx, dictp, base_uri, source);
+ if (code)
+ return fz_rethrow(code, "cannot parse remote resource dictionary");
+ return fz_okay;
+ }
+
+ head = NULL;
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ key = xml_att(node, "x:Key");
+ if (key)
+ {
+ entry = fz_malloc(sizeof(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(base_uri);
+ }
+
+ *dictp = head;
+ return fz_okay;
+}
+
+void
+xps_free_resource_dictionary(xps_context *ctx, xps_resource *dict)
+{
+ xps_resource *next;
+ while (dict)
+ {
+ next = dict->next;
+ if (dict->base_xml)
+ xml_free_element(dict->base_xml);
+ if (dict->base_uri)
+ fz_free(dict->base_uri);
+ fz_free(dict);
+ dict = next;
+ }
+}
+
+void
+xps_debug_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_debug_resource_dictionary(dict->parent);
+ printf("}\n");
+ }
+ dict = dict->next;
+ }
+}
diff --git a/xps/xps_tiff.c b/xps/xps_tiff.c
new file mode 100644
index 00000000..d70d5f87
--- /dev/null
+++ b/xps/xps_tiff.c
@@ -0,0 +1,853 @@
+#include "fitz.h"
+#include "muxps.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
+{
+ /* "file" */
+ byte *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;
+
+ /* 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];
+
+ byte *jpegtables; /* point into "file" buffer */
+ unsigned jpegtableslen;
+
+ byte *profile;
+ int profilesize;
+
+ /* decoded data */
+ fz_colorspace *colorspace;
+ byte *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 byte 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 int
+xps_decode_tiff_uncompressed(struct tiff *tiff, fz_stream *stm, byte *wp, int wlen)
+{
+ int n = fz_read(stm, wp, wlen);
+ fz_close(stm);
+ if (n < 0)
+ return fz_rethrow(n, "cannot read uncompressed strip");
+ return fz_okay;
+}
+
+static int
+xps_decode_tiff_packbits(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen)
+{
+ fz_stream *stm = fz_openrld(chain);
+ int n = fz_read(stm, wp, wlen);
+ fz_close(stm);
+ if (n < 0)
+ return fz_rethrow(n, "cannot read packbits strip");
+ return fz_okay;
+}
+
+static int
+xps_decode_tiff_lzw(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen)
+{
+ fz_stream *stm = fz_openlzwd(chain, NULL);
+ int n = fz_read(stm, wp, wlen);
+ fz_close(stm);
+ if (n < 0)
+ return fz_rethrow(n, "cannot read lzw strip");
+ return fz_okay;
+}
+static int
+xps_decode_tiff_flate(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen)
+{
+ fz_stream *stm = fz_openflated(chain);
+ int n = fz_read(stm, wp, wlen);
+ fz_close(stm);
+ if (n < 0)
+ return fz_rethrow(n, "cannot read flate strip");
+ return fz_okay;
+}
+
+static int
+xps_decode_tiff_fax(struct tiff *tiff, int comp, fz_stream *chain, byte *wp, int wlen)
+{
+ fz_stream *stm;
+ fz_obj *params;
+ fz_obj *columns, *rows, *blackis1, *k, *encodedbytealign;
+ int n;
+
+ columns = fz_newint(tiff->imagewidth);
+ rows = fz_newint(tiff->imagelength);
+ blackis1 = fz_newbool(tiff->photometric == 0);
+ k = fz_newint(comp == 4 ? -1 : 0);
+ encodedbytealign = fz_newbool(comp == 2);
+
+ params = fz_newdict(5);
+ fz_dictputs(params, "Columns", columns);
+ fz_dictputs(params, "Rows", rows);
+ fz_dictputs(params, "BlackIs1", blackis1);
+ fz_dictputs(params, "K", k);
+ fz_dictputs(params, "EncodedByteAlign", encodedbytealign);
+
+ fz_dropobj(columns);
+ fz_dropobj(rows);
+ fz_dropobj(blackis1);
+ fz_dropobj(k);
+ fz_dropobj(encodedbytealign);
+
+ stm = fz_openfaxd(chain, params);
+ n = fz_read(stm, wp, wlen);
+ fz_close(stm);
+ fz_dropobj(params);
+
+ if (n < 0)
+ return fz_rethrow(n, "cannot read fax strip");
+ return fz_okay;
+}
+
+static int
+xps_decode_tiff_jpeg(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen)
+{
+ fz_stream *stm = fz_opendctd(chain, NULL);
+ int n = fz_read(stm, wp, wlen);
+ fz_close(stm);
+ if (n < 0)
+ return fz_rethrow(n, "cannot read jpeg strip");
+ return fz_okay;
+}
+
+static inline int
+getcomp(byte *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(byte *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
+xps_unpredict_tiff(byte *line, int width, int comps, int bits)
+{
+ byte 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
+xps_invert_tiff(byte *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 int
+xps_expand_tiff_colormap(struct tiff *tiff)
+{
+ int maxval = 1 << tiff->bitspersample;
+ byte *samples;
+ byte *src, *dst;
+ int stride;
+ int x, y;
+
+ /* 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)
+ return fz_throw("invalid number of samples for RGBPal");
+
+ if (tiff->bitspersample != 4 && tiff->bitspersample != 8)
+ return fz_throw("invalid number of bits for RGBPal");
+
+ stride = tiff->imagewidth * (tiff->samplesperpixel + 2);
+
+ samples = fz_malloc(stride * tiff->imagelength);
+
+ for (y = 0; y < tiff->imagelength; y++)
+ {
+ src = tiff->samples + (tiff->stride * y);
+ dst = samples + (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;
+ tiff->samples = samples;
+ return fz_okay;
+}
+
+static int
+xps_decode_tiff_strips(struct tiff *tiff)
+{
+ fz_buffer buf;
+ fz_stream *stm;
+ int error;
+
+ /* switch on compression to create a filter */
+ /* feed each strip to the filter */
+ /* read out the data and pack the samples into an xps_image */
+
+ /* 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 */
+
+ byte *wp;
+ unsigned row;
+ unsigned strip;
+ unsigned i;
+
+ if (!tiff->rowsperstrip || !tiff->stripoffsets || !tiff->rowsperstrip)
+ return fz_throw("no image data in tiff; maybe it is tiled");
+
+ if (tiff->planar != 1)
+ return fz_throw("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_devicegray;
+ break;
+ case 1: /* BlackIsZero */
+ tiff->colorspace = fz_devicegray;
+ break;
+ case 2: /* RGB */
+ tiff->colorspace = fz_devicergb;
+ break;
+ case 3: /* RGBPal */
+ tiff->colorspace = fz_devicergb;
+ break;
+ case 5: /* CMYK */
+ tiff->colorspace = fz_devicecmyk;
+ break;
+ case 6: /* YCbCr */
+ /* it's probably a jpeg ... we let jpeg convert to rgb */
+ tiff->colorspace = fz_devicergb;
+ break;
+ default:
+ return fz_throw("unknown photometric: %d", tiff->photometric);
+ }
+
+ switch (tiff->resolutionunit)
+ {
+ case 2:
+ /* no unit conversion needed */
+ break;
+ case 3:
+ tiff->xresolution *= 2.54;
+ tiff->yresolution *= 2.54;
+ 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_calloc(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;
+ byte *rp = tiff->bp + offset;
+
+ if (wp + wlen > tiff->samples + tiff->stride * tiff->imagelength)
+ wlen = tiff->samples + tiff->stride * tiff->imagelength - wp;
+
+ if (rp + rlen > tiff->ep)
+ return fz_throw("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]];
+
+ /* create a fz_buffer on the stack */
+ buf.refs = 2;
+ buf.data = rp;
+ buf.len = rlen;
+ buf.cap = rlen;
+
+ /* the strip decoders will close this */
+ stm = fz_openbuffer(&buf);
+
+ switch (tiff->compression)
+ {
+ case 1:
+ error = xps_decode_tiff_uncompressed(tiff, stm, wp, wlen);
+ break;
+ case 2:
+ error = xps_decode_tiff_fax(tiff, 2, stm, wp, wlen);
+ break;
+ case 3:
+ error = xps_decode_tiff_fax(tiff, 3, stm, wp, wlen);
+ break;
+ case 4:
+ error = xps_decode_tiff_fax(tiff, 4, stm, wp, wlen);
+ break;
+ case 5:
+ error = xps_decode_tiff_lzw(tiff, stm, wp, wlen);
+ break;
+ case 6:
+ error = fz_throw("deprecated JPEG in TIFF compression not supported");
+ break;
+ case 7:
+ error = xps_decode_tiff_jpeg(tiff, stm, wp, wlen);
+ break;
+ case 8:
+ error = xps_decode_tiff_flate(tiff, stm, wp, wlen);
+ break;
+ case 32773:
+ error = xps_decode_tiff_packbits(tiff, stm, wp, wlen);
+ break;
+ default:
+ error = fz_throw("unknown TIFF compression: %d", tiff->compression);
+ }
+
+ if (error)
+ return fz_rethrow(error, "cannot decode strip %d", row / tiff->rowsperstrip);
+
+ /* 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)
+ {
+ byte *p = tiff->samples;
+ for (i = 0; i < tiff->imagelength; i++)
+ {
+ xps_unpredict_tiff(p, tiff->imagewidth, tiff->samplesperpixel, tiff->bitspersample);
+ p += tiff->stride;
+ }
+ }
+
+ /* RGBPal */
+ if (tiff->photometric == 3 && tiff->colormap)
+ {
+ error = xps_expand_tiff_colormap(tiff);
+ if (error)
+ return fz_rethrow(error, "cannot expand colormap");
+ }
+
+ /* WhiteIsZero .. invert */
+ if (tiff->photometric == 0)
+ {
+ byte *p = tiff->samples;
+ for (i = 0; i < tiff->imagelength; i++)
+ {
+ xps_invert_tiff(p, tiff->imagewidth, tiff->samplesperpixel, tiff->bitspersample, tiff->extrasamples);
+ p += tiff->stride;
+ }
+ }
+
+ return fz_okay;
+}
+
+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
+xps_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
+xps_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 int
+xps_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:
+ xps_read_tiff_tag_value(&tiff->subfiletype, tiff, type, value, 1);
+ break;
+ case ImageWidth:
+ xps_read_tiff_tag_value(&tiff->imagewidth, tiff, type, value, 1);
+ break;
+ case ImageLength:
+ xps_read_tiff_tag_value(&tiff->imagelength, tiff, type, value, 1);
+ break;
+ case BitsPerSample:
+ xps_read_tiff_tag_value(&tiff->bitspersample, tiff, type, value, 1);
+ break;
+ case Compression:
+ xps_read_tiff_tag_value(&tiff->compression, tiff, type, value, 1);
+ break;
+ case PhotometricInterpretation:
+ xps_read_tiff_tag_value(&tiff->photometric, tiff, type, value, 1);
+ break;
+ case FillOrder:
+ xps_read_tiff_tag_value(&tiff->fillorder, tiff, type, value, 1);
+ break;
+ case SamplesPerPixel:
+ xps_read_tiff_tag_value(&tiff->samplesperpixel, tiff, type, value, 1);
+ break;
+ case RowsPerStrip:
+ xps_read_tiff_tag_value(&tiff->rowsperstrip, tiff, type, value, 1);
+ break;
+ case XResolution:
+ xps_read_tiff_tag_value(&tiff->xresolution, tiff, type, value, 1);
+ break;
+ case YResolution:
+ xps_read_tiff_tag_value(&tiff->yresolution, tiff, type, value, 1);
+ break;
+ case PlanarConfiguration:
+ xps_read_tiff_tag_value(&tiff->planar, tiff, type, value, 1);
+ break;
+ case T4Options:
+ xps_read_tiff_tag_value(&tiff->g3opts, tiff, type, value, 1);
+ break;
+ case T6Options:
+ xps_read_tiff_tag_value(&tiff->g4opts, tiff, type, value, 1);
+ break;
+ case Predictor:
+ xps_read_tiff_tag_value(&tiff->predictor, tiff, type, value, 1);
+ break;
+ case ResolutionUnit:
+ xps_read_tiff_tag_value(&tiff->resolutionunit, tiff, type, value, 1);
+ break;
+ case YCbCrSubSampling:
+ xps_read_tiff_tag_value(tiff->ycbcrsubsamp, tiff, type, value, 2);
+ break;
+ case ExtraSamples:
+ xps_read_tiff_tag_value(&tiff->extrasamples, tiff, type, value, 1);
+ break;
+
+ case ICCProfile:
+ tiff->profile = fz_malloc(count);
+ /* ICC profile data type is set to UNDEFINED.
+ * TBYTE reading not correct in xps_read_tiff_tag_value */
+ xps_read_tiff_bytes(tiff->profile, tiff, value, count);
+ tiff->profilesize = count;
+ break;
+
+ case JPEGTables:
+ fz_warn("jpeg tables in tiff not implemented");
+ tiff->jpegtables = tiff->bp + value;
+ tiff->jpegtableslen = count;
+ break;
+
+ case StripOffsets:
+ tiff->stripoffsets = fz_calloc(count, sizeof(unsigned));
+ xps_read_tiff_tag_value(tiff->stripoffsets, tiff, type, value, count);
+ break;
+
+ case StripByteCounts:
+ tiff->stripbytecounts = fz_calloc(count, sizeof(unsigned));
+ xps_read_tiff_tag_value(tiff->stripbytecounts, tiff, type, value, count);
+ break;
+
+ case ColorMap:
+ tiff->colormap = fz_calloc(count, sizeof(unsigned));
+ xps_read_tiff_tag_value(tiff->colormap, tiff, type, value, count);
+ break;
+
+ case TileWidth:
+ case TileLength:
+ case TileOffsets:
+ case TileByteCounts:
+ return fz_throw("tiled tiffs not supported");
+
+ default:
+ /* printf("unknown tag: %d t=%d n=%d\n", tag, type, count); */
+ break;
+ }
+
+ return fz_okay;
+}
+
+static void
+xps_swap_byte_order(byte *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 int
+xps_decode_tiff_header(struct tiff *tiff, byte *buf, int len)
+{
+ unsigned version;
+ unsigned offset;
+ unsigned count;
+ unsigned i;
+ int error;
+
+ memset(tiff, 0, sizeof(struct tiff));
+
+ 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)
+ return fz_throw("not a TIFF file, wrong magic marker");
+
+ /* check version */
+ version = readshort(tiff);
+ if (version != 42)
+ return fz_throw("not a TIFF file, wrong version marker");
+
+ /* get offset of IFD */
+ offset = readlong(tiff);
+
+ /*
+ * Read IFD
+ */
+
+ tiff->rp = tiff->bp + offset;
+
+ count = readshort(tiff);
+
+ offset += 2;
+ for (i = 0; i < count; i++)
+ {
+ error = xps_read_tiff_tag(tiff, offset);
+ if (error)
+ return fz_rethrow(error, "cannot read TIFF header tag");
+ offset += 12;
+ }
+
+ return fz_okay;
+}
+
+int
+xps_decode_tiff(fz_pixmap **imagep, byte *buf, int len)
+{
+ int error;
+ fz_pixmap *image;
+ struct tiff tiff;
+
+ error = xps_decode_tiff_header(&tiff, buf, len);
+ if (error)
+ return fz_rethrow(error, "cannot decode tiff header");
+
+ /* Decode the image strips */
+
+ if (tiff.rowsperstrip > tiff.imagelength)
+ tiff.rowsperstrip = tiff.imagelength;
+
+ error = xps_decode_tiff_strips(&tiff);
+ if (error)
+ return fz_rethrow(error, "cannot decode image data");
+
+ /* Byte swap 16-bit images to big endian if necessary */
+ if (tiff.bitspersample == 16)
+ {
+ if (tiff.order == TII)
+ xps_swap_byte_order(tiff.samples, tiff.imagewidth * tiff.imagelength * tiff.samplesperpixel);
+ }
+
+ /* Expand into fz_pixmap struct */
+
+ image = fz_newpixmap(tiff.colorspace, 0, 0, tiff.imagewidth, tiff.imagelength);
+ image->xres = tiff.xresolution;
+ image->yres = tiff.yresolution;
+
+ fz_unpacktile(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 */)
+ fz_premultiplypixmap(image);
+
+ /* Clean up scratch memory */
+
+ if (tiff.colormap) fz_free(tiff.colormap);
+ if (tiff.stripoffsets) fz_free(tiff.stripoffsets);
+ if (tiff.stripbytecounts) fz_free(tiff.stripbytecounts);
+ if (tiff.samples) fz_free(tiff.samples);
+
+ *imagep = image;
+ return fz_okay;
+}
diff --git a/xps/xps_tile.c b/xps/xps_tile.c
new file mode 100644
index 00000000..59dd181b
--- /dev/null
+++ b/xps/xps_tile.c
@@ -0,0 +1,347 @@
+#include "fitz.h"
+#include "muxps.h"
+
+/*
+ * 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;
+ xml_element *root;
+ void *user;
+ void (*func)(xps_context*, fz_matrix, fz_rect, char*, xps_resource*, xml_element*, void*);
+};
+
+static void
+xps_paint_tiling_brush_clipped(xps_context *ctx, fz_matrix ctm, fz_rect viewbox, struct closure *c)
+{
+ fz_path *path = fz_newpath();
+ fz_moveto(path, viewbox.x0, viewbox.y0);
+ fz_lineto(path, viewbox.x0, viewbox.y1);
+ fz_lineto(path, viewbox.x1, viewbox.y1);
+ fz_lineto(path, viewbox.x1, viewbox.y0);
+ fz_closepath(path);
+
+ ctx->dev->clippath(ctx->dev->user, path, 0, ctm);
+
+ c->func(ctx, ctm, viewbox, c->base_uri, c->dict, c->root, c->user);
+
+ ctx->dev->popclip(ctx->dev->user);
+}
+
+static void
+xps_paint_tiling_brush(xps_context *ctx, fz_matrix ctm, fz_rect viewbox, int tile_mode, struct closure *c)
+{
+ fz_matrix ttm;
+
+ xps_paint_tiling_brush_clipped(ctx, ctm, viewbox, c);
+
+ if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = fz_concat(fz_translate(viewbox.x1 * 2, 0), ctm);
+ ttm = fz_concat(fz_scale(-1, 1), ttm);
+ xps_paint_tiling_brush_clipped(ctx, ttm, viewbox, c);
+ }
+
+ if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = fz_concat(fz_translate(0, viewbox.y1 * 2), ctm);
+ ttm = fz_concat(fz_scale(1, -1), ttm);
+ xps_paint_tiling_brush_clipped(ctx, ttm, viewbox, c);
+ }
+
+ if (tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = fz_concat(fz_translate(viewbox.x1 * 2, viewbox.y1 * 2), ctm);
+ ttm = fz_concat(fz_scale(-1, -1), ttm);
+ xps_paint_tiling_brush_clipped(ctx, ttm, viewbox, c);
+ }
+}
+
+void
+xps_parse_tiling_brush(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root,
+ void (*func)(xps_context*, fz_matrix, fz_rect, char*, xps_resource*, xml_element*, void*), void *user)
+{
+ xml_element *node;
+ struct closure c;
+
+ char *opacity_att;
+ char *transform_att;
+ char *viewbox_att;
+ char *viewport_att;
+ char *tile_mode_att;
+ char *viewbox_units_att;
+ char *viewport_units_att;
+
+ xml_element *transform_tag = NULL;
+
+ fz_matrix transform;
+ fz_rect viewbox;
+ fz_rect viewport;
+ float xstep, ystep;
+ float xscale, yscale;
+ int tile_mode;
+
+ opacity_att = xml_att(root, "Opacity");
+ transform_att = xml_att(root, "Transform");
+ viewbox_att = xml_att(root, "Viewbox");
+ viewport_att = xml_att(root, "Viewport");
+ tile_mode_att = xml_att(root, "TileMode");
+ viewbox_units_att = xml_att(root, "ViewboxUnits");
+ viewport_units_att = xml_att(root, "ViewportUnits");
+
+ c.base_uri = base_uri;
+ c.dict = dict;
+ c.root = root;
+ c.user = user;
+ c.func = func;
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "ImageBrush.Transform"))
+ transform_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "VisualBrush.Transform"))
+ transform_tag = xml_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ ctm = fz_concat(transform, ctm);
+
+ viewbox = fz_unitrect;
+ if (viewbox_att)
+ xps_parse_rectangle(ctx, viewbox_att, &viewbox);
+
+ viewport = fz_unitrect;
+ if (viewport_att)
+ xps_parse_rectangle(ctx, viewport_att, &viewport);
+
+ /* some sanity checks on the viewport/viewbox size */
+ if (fabs(viewport.x1 - viewport.x0) < 0.01) return;
+ if (fabs(viewport.y1 - viewport.y0) < 0.01) return;
+ if (fabs(viewbox.x1 - viewbox.x0) < 0.01) return;
+ if (fabs(viewbox.y1 - viewbox.y0) < 0.01) 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(ctx, ctm, area, base_uri, dict, opacity_att, NULL);
+
+ ctm = fz_concat(fz_translate(viewport.x0, viewport.y0), ctm);
+ ctm = fz_concat(fz_scale(xscale, yscale), ctm);
+ ctm = fz_concat(fz_translate(-viewbox.x0, -viewbox.y0), ctm);
+
+ if (tile_mode != TILE_NONE)
+ {
+ fz_matrix invctm = fz_invertmatrix(ctm);
+ fz_rect bbox = fz_transformrect(invctm, area);
+ int x0 = floorf(bbox.x0 / xstep);
+ int y0 = floorf(bbox.y0 / ystep);
+ int x1 = ceilf(bbox.x1 / xstep);
+ int y1 = ceilf(bbox.y1 / ystep);
+ int x, y;
+
+ for (y = y0; y < y1; y++)
+ {
+ for (x = x0; x < x1; x++)
+ {
+ fz_matrix ttm = fz_concat(fz_translate(xstep * x, ystep * y), ctm);
+ xps_paint_tiling_brush(ctx, ttm, viewbox, tile_mode, &c);
+ }
+ }
+ }
+ else
+ {
+ xps_paint_tiling_brush(ctx, ctm, viewbox, tile_mode, &c);
+ }
+
+ xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
+}
+
+static void
+xps_paint_visual_brush(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root, void *visual_tag)
+{
+ xps_parse_element(ctx, ctm, area, base_uri, dict, (xml_element *)visual_tag);
+}
+
+void
+xps_parse_visual_brush(xps_context *ctx, fz_matrix ctm, fz_rect area,
+ char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xml_element *node;
+
+ char *visual_uri;
+ char *visual_att;
+ xml_element *visual_tag = NULL;
+
+ visual_att = xml_att(root, "Visual");
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "VisualBrush.Visual"))
+ visual_tag = xml_down(node);
+ }
+
+ visual_uri = base_uri;
+ xps_resolve_resource_reference(ctx, dict, &visual_att, &visual_tag, &visual_uri);
+
+ if (visual_tag)
+ {
+ xps_parse_tiling_brush(ctx, ctm, area,
+ visual_uri, dict, root, xps_paint_visual_brush, visual_tag);
+ }
+}
+
+void
+xps_parse_canvas(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *root)
+{
+ xps_resource *new_dict = NULL;
+ xml_element *node;
+ char *opacity_mask_uri;
+ int code;
+
+ char *transform_att;
+ char *clip_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+
+ xml_element *transform_tag = NULL;
+ xml_element *clip_tag = NULL;
+ xml_element *opacity_mask_tag = NULL;
+
+ fz_matrix transform;
+
+ transform_att = xml_att(root, "RenderTransform");
+ clip_att = xml_att(root, "Clip");
+ opacity_att = xml_att(root, "Opacity");
+ opacity_mask_att = xml_att(root, "OpacityMask");
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "Canvas.Resources") && xml_down(node))
+ {
+ code = xps_parse_resource_dictionary(ctx, &new_dict, base_uri, xml_down(node));
+ if (code)
+ fz_catch(code, "cannot load Canvas.Resources");
+ else
+ {
+ new_dict->parent = dict;
+ dict = new_dict;
+ }
+ }
+
+ if (!strcmp(xml_tag(node), "Canvas.RenderTransform"))
+ transform_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Canvas.Clip"))
+ clip_tag = xml_down(node);
+ if (!strcmp(xml_tag(node), "Canvas.OpacityMask"))
+ opacity_mask_tag = xml_down(node);
+ }
+
+ opacity_mask_uri = base_uri;
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ ctm = fz_concat(transform, ctm);
+
+ if (clip_att || clip_tag)
+ xps_clip(ctx, ctm, dict, clip_att, clip_tag);
+
+ xps_begin_opacity(ctx, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ for (node = xml_down(root); node; node = xml_next(node))
+ {
+ xps_parse_element(ctx, ctm, area, base_uri, dict, node);
+ }
+
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (clip_att || clip_tag)
+ ctx->dev->popclip(ctx->dev->user);
+
+ if (new_dict)
+ xps_free_resource_dictionary(ctx, new_dict);
+}
+
+void
+xps_parse_fixed_page(xps_context *ctx, fz_matrix ctm, xps_page *page)
+{
+ xml_element *node;
+ xps_resource *dict;
+ char base_uri[1024];
+ fz_rect area;
+ char *s;
+ int code;
+
+ fz_strlcpy(base_uri, page->name, sizeof base_uri);
+ s = strrchr(base_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ dict = NULL;
+
+ ctx->opacity_top = 0;
+ ctx->opacity[0] = 1;
+
+ if (!page->root)
+ return;
+
+ area = fz_transformrect(fz_scale(page->width, page->height), fz_unitrect);
+
+ for (node = xml_down(page->root); node; node = xml_next(node))
+ {
+ if (!strcmp(xml_tag(node), "FixedPage.Resources") && xml_down(node))
+ {
+ code = xps_parse_resource_dictionary(ctx, &dict, base_uri, xml_down(node));
+ if (code)
+ fz_catch(code, "cannot load FixedPage.Resources");
+ }
+ xps_parse_element(ctx, ctm, area, base_uri, dict, node);
+ }
+
+ if (dict)
+ {
+ xps_free_resource_dictionary(ctx, dict);
+ }
+}
diff --git a/xps/xps_util.c b/xps/xps_util.c
new file mode 100644
index 00000000..76175d68
--- /dev/null
+++ b/xps/xps_util.c
@@ -0,0 +1,94 @@
+#include "fitz.h"
+#include "muxps.h"
+
+static inline int tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+int
+xps_strcasecmp(char *a, char *b)
+{
+ while (tolower(*a) == tolower(*b))
+ {
+ if (*a++ == 0)
+ return 0;
+ b++;
+ }
+ return tolower(*a) - tolower(*b);
+}
+
+#define SEP(x) ((x)=='/' || (x) == 0)
+
+static char *
+xps_clean_path(char *name)
+{
+ char *p, *q, *dotdot;
+ int rooted;
+
+ rooted = name[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 = name + 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 != name)
+ *q++ = '/';
+ *q++ = '.';
+ *q++ = '.';
+ dotdot = q;
+ }
+ }
+ else /* real path element */
+ {
+ if (q != name+rooted)
+ *q++ = '/';
+ while ((*q = *p) != '/' && *q != 0)
+ p++, q++;
+ }
+ }
+
+ if (q == name) /* empty string is really "." */
+ *q++ = '.';
+ *q = '\0';
+
+ return name;
+}
+
+void
+xps_absolute_path(char *output, char *base_uri, char *path, int output_size)
+{
+ if (path[0] == '/')
+ {
+ fz_strlcpy(output, path, output_size);
+ }
+ else
+ {
+ fz_strlcpy(output, base_uri, output_size);
+ fz_strlcat(output, "/", output_size);
+ fz_strlcat(output, path, output_size);
+ }
+ xps_clean_path(output);
+}
diff --git a/xps/xps_xml.c b/xps/xps_xml.c
new file mode 100644
index 00000000..8448ecef
--- /dev/null
+++ b/xps/xps_xml.c
@@ -0,0 +1,387 @@
+#include "fitz.h"
+#include "muxps.h"
+
+struct attribute
+{
+ char name[40];
+ char *value;
+ struct attribute *next;
+};
+
+struct element
+{
+ char name[40];
+ struct attribute *atts;
+ struct element *up, *down, *next;
+};
+
+struct parser
+{
+ struct element *head;
+};
+
+static inline void indent(int n)
+{
+ while (n--) putchar(' ');
+}
+
+void xml_print_element(struct element *item, int level)
+{
+ while (item) {
+ 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");
+ xml_print_element(item->down, level + 1);
+ indent(level);
+ printf("</%s>\n", item->name);
+ }
+ else {
+ printf("/>\n");
+ }
+ item = item->next;
+ }
+}
+
+struct element *xml_next(struct element *item)
+{
+ return item->next;
+}
+
+struct element *xml_down(struct element *item)
+{
+ return item->down;
+}
+
+char *xml_tag(struct element *item)
+{
+ return item->name;
+}
+
+char *xml_att(struct element *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(struct attribute *att)
+{
+ while (att) {
+ struct attribute *next = att->next;
+ if (att->value)
+ fz_free(att->value);
+ fz_free(att);
+ att = next;
+ }
+}
+
+void xml_free_element(struct element *item)
+{
+ while (item) {
+ struct element *next = item->next;
+ if (item->atts)
+ xml_free_attribute(item->atts);
+ if (item->down)
+ xml_free_element(item->down);
+ fz_free(item);
+ item = next;
+ }
+}
+
+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 void xml_emit_open_tag(struct parser *parser, char *a, char *b)
+{
+ struct element *head, *tail;
+
+ head = fz_malloc(sizeof(struct element));
+ if (b - a > sizeof(head->name))
+ b = a + sizeof(head->name);
+ memcpy(head->name, a, b - a);
+ head->name[b - a] = 0;
+
+ head->atts = 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)
+{
+ struct element *head = parser->head;
+ struct attribute *att;
+
+ att = fz_malloc(sizeof(struct attribute));
+ if (b - a > sizeof(att->name))
+ b = a + sizeof(att->name);
+ 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)
+{
+ struct element *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(b - a + 1);
+ while (a < b) {
+ if (*a == '&') {
+ a += xml_parse_entity(&c, a);
+ s += 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 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 char *xml_parse_document_imp(struct parser *x, char *p)
+{
+ char *mark;
+ int quote;
+
+parse_text:
+ mark = p;
+ while (*p && *p != '<') ++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(unsigned char *s, int n)
+{
+ unsigned char *e = s + n;
+ char *dst, *d;
+ int c;
+
+ if (s[0] == 0xFE && s[1] == 0xFF) {
+ dst = d = fz_malloc(n * 2);
+ while (s + 1 < e) {
+ c = s[0] << 8 | s[1];
+ d += runetochar(d, &c);
+ s += 2;
+ }
+ *d = 0;
+ return dst;
+ }
+
+ if (s[0] == 0xFF && s[1] == 0xFE) {
+ dst = d = fz_malloc(n * 2);
+ while (s + 1 < e) {
+ c = s[0] | s[1] << 8;
+ d += runetochar(d, &c);
+ s += 2;
+ }
+ *d = 0;
+ return dst;
+ }
+
+ return (char*)s;
+}
+
+struct element *
+xml_parse_document(unsigned char *s, int n)
+{
+ struct parser parser;
+ struct element root;
+ char *p, *error;
+
+ /* s is already null-terminated (see xps_new_part) */
+
+ memset(&root, 0, sizeof(root));
+ parser.head = &root;
+
+ p = convert_to_utf8(s, n);
+
+ error = xml_parse_document_imp(&parser, p);
+ if (error) {
+ fz_throw(error);
+ return NULL;
+ }
+
+ if (p != (char*)s)
+ fz_free(p);
+
+ return root.down;
+}
diff --git a/xps/xps_zip.c b/xps/xps_zip.c
new file mode 100644
index 00000000..d353ec11
--- /dev/null
+++ b/xps/xps_zip.c
@@ -0,0 +1,472 @@
+#include "fitz.h"
+#include "muxps.h"
+
+#include <zlib.h>
+
+xps_part *
+xps_new_part(xps_context *ctx, char *name, int size)
+{
+ xps_part *part;
+
+ part = fz_malloc(sizeof(xps_part));
+ part->name = fz_strdup(name);
+ part->size = size;
+ part->data = fz_malloc(size + 1);
+ part->data[size] = 0; /* null-terminate for xml parser */
+
+ return part;
+}
+
+void
+xps_free_part(xps_context *ctx, xps_part *part)
+{
+ fz_free(part->name);
+ fz_free(part->data);
+ fz_free(part);
+}
+
+static inline int getshort(FILE *file)
+{
+ int a = getc(file);
+ int b = getc(file);
+ return a | (b << 8);
+}
+
+static inline int getlong(FILE *file)
+{
+ int a = getc(file);
+ int b = getc(file);
+ int c = getc(file);
+ int d = getc(file);
+ return a | (b << 8) | (c << 16) | (d << 24);
+}
+
+static void *
+xps_zip_alloc_items(xps_context *ctx, int items, int size)
+{
+ return fz_calloc(items, size);
+}
+
+static void
+xps_zip_free(xps_context *ctx, void *ptr)
+{
+ fz_free(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_find_zip_entry(xps_context *ctx, char *name)
+{
+ int l = 0;
+ int r = ctx->zip_count - 1;
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = xps_strcasecmp(name, ctx->zip_table[m].name);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return &ctx->zip_table[m];
+ }
+ return NULL;
+}
+
+static int
+xps_read_zip_entry(xps_context *ctx, xps_entry *ent, unsigned char *outbuf)
+{
+ z_stream stream;
+ unsigned char *inbuf;
+ int sig;
+ int version, general, method;
+ int namelength, extralength;
+ int code;
+
+ fseek(ctx->file, ent->offset, 0);
+
+ sig = getlong(ctx->file);
+ if (sig != ZIP_LOCAL_FILE_SIG)
+ return fz_throw("wrong zip local file signature (0x%x)", sig);
+
+ version = getshort(ctx->file);
+ general = getshort(ctx->file);
+ method = getshort(ctx->file);
+ (void) getshort(ctx->file); /* file time */
+ (void) getshort(ctx->file); /* file date */
+ (void) getlong(ctx->file); /* crc-32 */
+ (void) getlong(ctx->file); /* csize */
+ (void) getlong(ctx->file); /* usize */
+ namelength = getshort(ctx->file);
+ extralength = getshort(ctx->file);
+
+ fseek(ctx->file, namelength + extralength, 1);
+
+ if (method == 0)
+ {
+ fread(outbuf, 1, ent->usize, ctx->file);
+ }
+ else if (method == 8)
+ {
+ inbuf = fz_malloc(ent->csize);
+
+ fread(inbuf, 1, ent->csize, ctx->file);
+
+ memset(&stream, 0, sizeof(z_stream));
+ stream.zalloc = (alloc_func) xps_zip_alloc_items;
+ stream.zfree = (free_func) xps_zip_free;
+ stream.opaque = ctx;
+ 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)
+ return fz_throw("zlib inflateInit2 error: %s", stream.msg);
+ code = inflate(&stream, Z_FINISH);
+ if (code != Z_STREAM_END)
+ {
+ inflateEnd(&stream);
+ return fz_throw("zlib inflate error: %s", stream.msg);
+ }
+ code = inflateEnd(&stream);
+ if (code != Z_OK)
+ return fz_throw("zlib inflateEnd error: %s", stream.msg);
+
+ fz_free(inbuf);
+ }
+ else
+ {
+ return fz_throw("unknown compression method (%d)", method);
+ }
+
+ return fz_okay;
+}
+
+/*
+ * Read the central directory in a zip file.
+ */
+
+static int
+xps_read_zip_dir(xps_context *ctx, int start_offset)
+{
+ int sig;
+ int offset, count;
+ int namesize, metasize, commentsize;
+ int i;
+
+ fseek(ctx->file, start_offset, 0);
+
+ sig = getlong(ctx->file);
+ if (sig != ZIP_END_OF_CENTRAL_DIRECTORY_SIG)
+ return fz_throw("wrong zip end of central directory signature (0x%x)", sig);
+
+ (void) getshort(ctx->file); /* this disk */
+ (void) getshort(ctx->file); /* start disk */
+ (void) getshort(ctx->file); /* entries in this disk */
+ count = getshort(ctx->file); /* entries in central directory disk */
+ (void) getlong(ctx->file); /* size of central directory */
+ offset = getlong(ctx->file); /* offset to central directory */
+
+ ctx->zip_count = count;
+ ctx->zip_table = fz_calloc(count, sizeof(xps_entry));
+ memset(ctx->zip_table, 0, sizeof(xps_entry) * count);
+
+ fseek(ctx->file, offset, 0);
+
+ for (i = 0; i < count; i++)
+ {
+ sig = getlong(ctx->file);
+ if (sig != ZIP_CENTRAL_DIRECTORY_SIG)
+ return fz_throw("wrong zip central directory signature (0x%x)", sig);
+
+ (void) getshort(ctx->file); /* version made by */
+ (void) getshort(ctx->file); /* version to extract */
+ (void) getshort(ctx->file); /* general */
+ (void) getshort(ctx->file); /* method */
+ (void) getshort(ctx->file); /* last mod file time */
+ (void) getshort(ctx->file); /* last mod file date */
+ (void) getlong(ctx->file); /* crc-32 */
+ ctx->zip_table[i].csize = getlong(ctx->file);
+ ctx->zip_table[i].usize = getlong(ctx->file);
+ namesize = getshort(ctx->file);
+ metasize = getshort(ctx->file);
+ commentsize = getshort(ctx->file);
+ (void) getshort(ctx->file); /* disk number start */
+ (void) getshort(ctx->file); /* int file atts */
+ (void) getlong(ctx->file); /* ext file atts */
+ ctx->zip_table[i].offset = getlong(ctx->file);
+
+ ctx->zip_table[i].name = fz_malloc(namesize + 1);
+ fread(ctx->zip_table[i].name, 1, namesize, ctx->file);
+ ctx->zip_table[i].name[namesize] = 0;
+
+ fseek(ctx->file, metasize, 1);
+ fseek(ctx->file, commentsize, 1);
+ }
+
+ qsort(ctx->zip_table, count, sizeof(xps_entry), xps_compare_entries);
+
+ return fz_okay;
+}
+
+static int
+xps_find_and_read_zip_dir(xps_context *ctx)
+{
+ int filesize, back, maxback;
+ int i, n;
+ char buf[512];
+
+ fseek(ctx->file, 0, SEEK_END);
+ filesize = ftell(ctx->file);
+
+ maxback = MIN(filesize, 0xFFFF + sizeof buf);
+ back = MIN(maxback, sizeof buf);
+
+ while (back < maxback)
+ {
+ fseek(ctx->file, filesize - back, 0);
+
+ n = fread(buf, 1, sizeof buf, ctx->file);
+ if (n < 0)
+ return fz_throw("cannot read end of central directory");
+
+ for (i = n - 4; i > 0; i--)
+ if (!memcmp(buf + i, "PK\5\6", 4))
+ return xps_read_zip_dir(ctx, filesize - back + i);
+
+ back += sizeof buf - 4;
+ }
+
+ return fz_throw("cannot find end of central directory");
+}
+
+/*
+ * Read and interleave split parts from a ZIP file.
+ */
+static xps_part *
+xps_read_zip_part(xps_context *ctx, char *partname)
+{
+ char buf[2048];
+ xps_entry *ent;
+ xps_part *part;
+ int count, size, offset, i;
+ char *name;
+
+ name = partname;
+ if (name[0] == '/')
+ name ++;
+
+ /* All in one piece */
+ ent = xps_find_zip_entry(ctx, name);
+ if (ent)
+ {
+ part = xps_new_part(ctx, partname, ent->usize);
+ xps_read_zip_entry(ctx, ent, part->data);
+ return part;
+ }
+
+ /* Count the number of pieces and their total size */
+ count = 0;
+ size = 0;
+ while (1)
+ {
+ sprintf(buf, "%s/[%d].piece", name, count);
+ ent = xps_find_zip_entry(ctx, buf);
+ if (!ent)
+ {
+ sprintf(buf, "%s/[%d].last.piece", name, count);
+ ent = xps_find_zip_entry(ctx, buf);
+ }
+ if (!ent)
+ break;
+ count ++;
+ size += ent->usize;
+ }
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(ctx, 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_find_zip_entry(ctx, buf);
+ xps_read_zip_entry(ctx, ent, part->data + offset);
+ offset += ent->usize;
+ }
+ return part;
+ }
+
+ return NULL;
+}
+
+/*
+ * Read and interleave split parts from files in the directory.
+ */
+static xps_part *
+xps_read_dir_part(xps_context *ctx, char *name)
+{
+ char buf[2048];
+ xps_part *part;
+ FILE *file;
+ int count, size, offset, i, n;
+
+ fz_strlcpy(buf, ctx->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(ctx, 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 (1)
+ {
+ sprintf(buf, "%s%s/[%d].piece", ctx->directory, name, count);
+ file = fopen(buf, "rb");
+ if (!file)
+ {
+ sprintf(buf, "%s%s/[%d].last.piece", ctx->directory, name, count);
+ file = fopen(buf, "rb");
+ }
+ if (!file)
+ break;
+ count ++;
+ fseek(file, 0, SEEK_END);
+ size += ftell(file);
+ fclose(file);
+ }
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(ctx, name, size);
+ offset = 0;
+ for (i = 0; i < count; i++)
+ {
+ if (i < count - 1)
+ sprintf(buf, "%s%s/[%d].piece", ctx->directory, name, i);
+ else
+ sprintf(buf, "%s%s/[%d].last.piece", ctx->directory, name, i);
+ file = fopen(buf, "rb");
+ n = fread(part->data + offset, 1, size - offset, file);
+ offset += n;
+ fclose(file);
+ }
+ return part;
+ }
+
+ return NULL;
+}
+
+xps_part *
+xps_read_part(xps_context *ctx, char *partname)
+{
+ if (ctx->directory)
+ return xps_read_dir_part(ctx, partname);
+ return xps_read_zip_part(ctx, partname);
+}
+
+int
+xps_open_file(xps_context *ctx, char *filename)
+{
+ char buf[2048];
+ int code;
+ char *p;
+
+ ctx->file = fopen(filename, "rb");
+ if (!ctx->file)
+ return fz_throw("cannot open file: '%s'", filename);
+
+ 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;
+ ctx->directory = fz_strdup(buf);
+ }
+ else
+ {
+ code = xps_find_and_read_zip_dir(ctx);
+ if (code < 0)
+ return fz_rethrow(code, "cannot read zip central directory");
+ }
+
+ code = xps_read_page_list(ctx);
+ if (code)
+ return fz_rethrow(code, "cannot read page list");
+
+ return fz_okay;
+}
+
+xps_context *
+xps_new_context(void)
+{
+ xps_context *ctx;
+
+ ctx = fz_malloc(sizeof(xps_context));
+
+ memset(ctx, 0, sizeof(xps_context));
+
+ ctx->font_table = xps_hash_new();
+ ctx->colorspace_table = xps_hash_new();
+
+ ctx->start_part = NULL;
+
+ return ctx;
+}
+
+static void xps_free_key_func(void *ptr)
+{
+ fz_free(ptr);
+}
+
+static void xps_free_font_func(void *ptr)
+{
+ fz_dropfont(ptr);
+}
+
+int
+xps_free_context(xps_context *ctx)
+{
+ int i;
+
+ if (ctx->file)
+ fclose(ctx->file);
+
+ for (i = 0; i < ctx->zip_count; i++)
+ fz_free(ctx->zip_table[i].name);
+ fz_free(ctx->zip_table);
+
+ xps_hash_free(ctx->font_table, xps_free_key_func, xps_free_font_func);
+ xps_hash_free(ctx->colorspace_table, xps_free_key_func, NULL);
+
+ xps_free_page_list(ctx);
+
+ return 0;
+}