[gs-commits] mupdf 1.16.1.93 Chapter based API for faster EPUB loading

[email protected] (Robin Watts) Wed, 16 Oct 2019 12:56:29 +0000 (UTC)
Newsgroups gmane.comp.printing.ghostscript.cvs
Message-ID <[email protected]>
commit 80606caf66c9bd1306ae264f972c5c859e154f17
Author: Tor Andersson <[email protected]>
Date:   Tue Sep 10 12:04:01 2019 +0200

    Chapter based API for faster EPUB loading.
    
      * Navigate using locations.
      * Show current location in outline in single-chapter documents.
      * Add chapter based page API to JNI bindings; reimplement old
        countPages and loadPage on top of new API.
      * Add accelerator API and hook up stubs for epub.
      * Fill in stubs to read/write accelerator data for epub.
      * Remove dirty flag from epub_chapter.
      * fz_html now knows the size it was last laid out to, and so we
        can check it there to avoid unnecessary relayouts.
      * Make fz_html storable.
      * All epub chapters to be dropped between uses.
      * Make accelerator depend on user css and document css flag.
      * Change fz_bookmark functions to use fz_location.

diff --git a/include/mupdf/fitz/document.h b/include/mupdf/fitz/document.h
index 379b5d6..bd7fcfd 100644
--- a/include/mupdf/fitz/document.h
+++ b/include/mupdf/fitz/document.h
@@ -15,6 +15,18 @@ typedef struct fz_document_handler_s fz_document_handler;
 typedef struct fz_page_s fz_page;
 typedef intptr_t fz_bookmark;
 
+typedef struct fz_location_s
+{
+	int chapter;
+	int page;
+} fz_location;
+
+static inline fz_location fz_make_location(int chapter, int page)
+{
+	fz_location loc = { chapter, page };
+	return loc;
+}
+
 enum
 {
 	/* 6in at 4:3 */
@@ -115,20 +127,27 @@ typedef void (fz_document_layout_fn)(fz_context *ctx, fz_document *doc, float w,
 	resolve an internal link to a page number. See fz_resolve_link
 	for more information.
 */
-typedef int (fz_document_resolve_link_fn)(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp);
+typedef fz_location (fz_document_resolve_link_fn)(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp);
+
+/*
+	Type for a function to be called to
+	count the number of chapters in a document. See fz_count_chapters for
+	more information.
+*/
+typedef int (fz_document_count_chapters_fn)(fz_context *ctx, fz_document *doc);
 
 /*
 	Type for a function to be called to
 	count the number of pages in a document. See fz_count_pages for
 	more information.
 */
-typedef int (fz_document_count_pages_fn)(fz_context *ctx, fz_document *doc);
+typedef int (fz_document_count_pages_fn)(fz_context *ctx, fz_document *doc, int chapter);
 
 /*
 	Type for a function to load a given
 	page from a document. See fz_load_page for more information.
 */
-typedef fz_page *(fz_document_load_page_fn)(fz_context *ctx, fz_document *doc, int number);
+typedef fz_page *(fz_document_load_page_fn)(fz_context *ctx, fz_document *doc, int chapter, int page);
 
 /*
 	Type for a function to query
@@ -143,16 +162,21 @@ typedef int (fz_document_lookup_metadata_fn)(fz_context *ctx, fz_document *doc,
 typedef fz_colorspace* (fz_document_output_intent_fn)(fz_context *ctx, fz_document *doc);
 
 /*
+	Write document accelerator data
+*/
+typedef void (fz_document_output_accelerator_fn)(fz_context *ctx, fz_document *doc, fz_output *out);
+
+/*
 	Type for a function to make
 	a bookmark. See fz_make_bookmark for more information.
 */
-typedef fz_bookmark (fz_document_make_bookmark_fn)(fz_context *ctx, fz_document *doc, int page);
+typedef fz_bookmark (fz_document_make_bookmark_fn)(fz_context *ctx, fz_document *doc, fz_location loc);
 
 /*
 	Type for a function to lookup
 	a bookmark. See fz_lookup_bookmark for more information.
 */
-typedef int (fz_document_lookup_bookmark_fn)(fz_context *ctx, fz_document *doc, fz_bookmark mark);
+typedef fz_location (fz_document_lookup_bookmark_fn)(fz_context *ctx, fz_document *doc, fz_bookmark mark);
 
 /*
 	Type for a function to release all the
@@ -223,7 +247,8 @@ typedef int (fz_page_uses_overprint_fn)(fz_context *ctx, fz_page *page);
 struct fz_page_s
 {
 	int refs;
-	int number; /* page number */
+	int chapter; /* chapter number */
+	int number; /* page number in chapter */
 	int incomplete; /* incomplete from progressive loading; don't cache! */
 	fz_page_drop_page_fn *drop_page;
 	fz_page_bound_page_fn *bound_page;
@@ -257,10 +282,12 @@ struct fz_document_s
 	fz_document_make_bookmark_fn *make_bookmark;
 	fz_document_lookup_bookmark_fn *lookup_bookmark;
 	fz_document_resolve_link_fn *resolve_link;
+	fz_document_count_chapters_fn *count_chapters;
 	fz_document_count_pages_fn *count_pages;
 	fz_document_load_page_fn *load_page;
 	fz_document_lookup_metadata_fn *lookup_metadata;
 	fz_document_output_intent_fn *get_output_intent;
+	fz_document_output_accelerator_fn *output_accelerator;
 	int did_layout;
 	int is_reflowable;
 	fz_page *open; /* linked list of currently open pages */
@@ -288,6 +315,32 @@ typedef fz_document *(fz_document_open_fn)(fz_context *ctx, const char *filename
 typedef fz_document *(fz_document_open_with_stream_fn)(fz_context *ctx, fz_stream *stream);
 
 /*
+	Function type to open a document from a
+	file, with accelerator data.
+
+	filename: file to open
+
+	accel: accelerator file
+
+	Pointer to opened document. Throws exception in case of error.
+*/
+typedef fz_document *(fz_document_open_accel_fn)(fz_context *ctx, const char *filename, const char *accel);
+
+/*
+	Function type to open a document from a file,
+	with accelerator data.
+
+	stream: fz_stream to read document data from. Must be
+	seekable for formats that require it.
+
+	accel: fz_stream to read accelerator data from. Must be
+	seekable for formats that require it.
+
+	Pointer to opened document. Throws exception in case of error.
+*/
+typedef fz_document *(fz_document_open_accel_with_stream_fn)(fz_context *ctx, fz_stream *stream, fz_stream *accel);
+
+/*
 	Recognize a document type from
 	a magic string.
 
@@ -307,6 +360,8 @@ struct fz_document_handler_s
 	fz_document_open_with_stream_fn *open_with_stream;
 	const char **extensions;
 	const char **mimetypes;
+	fz_document_open_accel_fn *open_accel;
+	fz_document_open_accel_with_stream_fn *open_accel_with_stream;
 };
 
 void fz_register_document_handler(fz_context *ctx, const fz_document_handler *handler);
@@ -317,8 +372,18 @@ const fz_document_handler *fz_recognize_document(fz_context *ctx, const char *ma
 
 fz_document *fz_open_document(fz_context *ctx, const char *filename);
 
+fz_document *fz_open_accelerated_document(fz_context *ctx, const char *filename, const char *accel);
+
 fz_document *fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream);
 
+fz_document *fz_open_accelerated_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream, fz_stream *accel);
+
+int fz_document_supports_accelerator(fz_context *ctx, fz_document *doc);
+
+void fz_save_accelerator(fz_context *ctx, fz_document *doc, const char *accel);
+
+void fz_output_accelerator(fz_context *ctx, fz_document *doc, fz_output *accel);
+
 void *fz_new_document_of_size(fz_context *ctx, int size);
 #define fz_new_derived_document(C,M) ((M*)Memento_label(fz_new_document_of_size(C, sizeof(M)), #M))
 
@@ -335,16 +400,26 @@ int fz_is_document_reflowable(fz_context *ctx, fz_document *doc);
 
 void fz_layout_document(fz_context *ctx, fz_document *doc, float w, float h, float em);
 
-fz_bookmark fz_make_bookmark(fz_context *ctx, fz_document *doc, int page);
+fz_bookmark fz_make_bookmark(fz_context *ctx, fz_document *doc, fz_location loc);
 
-int fz_lookup_bookmark(fz_context *ctx, fz_document *doc, fz_bookmark mark);
+fz_location fz_lookup_bookmark(fz_context *ctx, fz_document *doc, fz_bookmark mark);
 
 int fz_count_pages(fz_context *ctx, fz_document *doc);
 
-int fz_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp);
+fz_location fz_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp);
+fz_location fz_last_page(fz_context *ctx, fz_document *doc);
+fz_location fz_next_page(fz_context *ctx, fz_document *doc, fz_location loc);
+fz_location fz_previous_page(fz_context *ctx, fz_document *doc, fz_location loc);
+fz_location fz_clamp_location(fz_context *ctx, fz_document *doc, fz_location loc);
+fz_location fz_location_from_page_number(fz_context *ctx, fz_document *doc, int number);
+int fz_page_number_from_location(fz_context *ctx, fz_document *doc, fz_location loc);
 
 fz_page *fz_load_page(fz_context *ctx, fz_document *doc, int number);
 
+int fz_count_chapters(fz_context *ctx, fz_document *doc);
+int fz_count_chapter_pages(fz_context *ctx, fz_document *doc, int chapter);
+fz_page *fz_load_chapter_page(fz_context *ctx, fz_document *doc, int chapter, int page);
+
 fz_link *fz_load_links(fz_context *ctx, fz_page *page);
 
 fz_page *fz_new_page_of_size(fz_context *ctx, int size);
diff --git a/include/mupdf/fitz/util.h b/include/mupdf/fitz/util.h
index a787ee1..2b5fb0f 100644
--- a/include/mupdf/fitz/util.h
+++ b/include/mupdf/fitz/util.h
@@ -34,6 +34,7 @@ fz_buffer *fz_new_buffer_from_display_list(fz_context *ctx, fz_display_list *lis
 
 int fz_search_page(fz_context *ctx, fz_page *page, const char *needle, fz_quad *hit_bbox, int hit_max);
 int fz_search_page_number(fz_context *ctx, fz_document *doc, int number, const char *needle, fz_quad *hit_bbox, int hit_max);
+int fz_search_chapter_page_number(fz_context *ctx, fz_document *doc, int chapter, int page, const char *needle, fz_quad *hit_bbox, int hit_max);
 int fz_search_display_list(fz_context *ctx, fz_display_list *list, const char *needle, fz_quad *hit_bbox, int hit_max);
 
 fz_display_list *fz_new_display_list_from_svg(fz_context *ctx, fz_buffer *buf, const char *base_uri, fz_archive *zip, float *w, float *h);
diff --git a/include/mupdf/pdf/annot.h b/include/mupdf/pdf/annot.h
index f532d18..afaf618 100644
--- a/include/mupdf/pdf/annot.h
+++ b/include/mupdf/pdf/annot.h
@@ -115,6 +115,7 @@ pdf_obj *pdf_load_name_tree(fz_context *ctx, pdf_document *doc, pdf_obj *which);
 pdf_obj *pdf_lookup_number(fz_context *ctx, pdf_obj *root, int needle);
 
 int pdf_resolve_link(fz_context *ctx, pdf_document *doc, const char *uri, float *xp, float *yp);
+fz_location pdf_resolve_link_imp(fz_context *ctx, fz_document *doc_, const char *uri, float *xp, float *yp);
 fz_link *pdf_load_link_annots(fz_context *ctx, pdf_document *, pdf_obj *annots, int pagenum, fz_matrix page_ctm);
 
 fz_matrix pdf_annot_transform(fz_context *ctx, pdf_annot *annot);
diff --git a/include/mupdf/pdf/page.h b/include/mupdf/pdf/page.h
index e42986c..4926a65 100644
--- a/include/mupdf/pdf/page.h
+++ b/include/mupdf/pdf/page.h
@@ -5,6 +5,7 @@
 
 int pdf_lookup_page_number(fz_context *ctx, pdf_document *doc, pdf_obj *pageobj);
 int pdf_count_pages(fz_context *ctx, pdf_document *doc);
+int pdf_count_pages_imp(fz_context *ctx, fz_document *doc, int chapter);
 pdf_obj *pdf_lookup_page_obj(fz_context *ctx, pdf_document *doc, int needle);
 void pdf_load_page_tree(fz_context *ctx, pdf_document *doc);
 void pdf_drop_page_tree(fz_context *ctx, pdf_document *doc);
@@ -14,6 +15,7 @@ int pdf_lookup_anchor(fz_context *ctx, pdf_document *doc, const char *name, floa
 void pdf_flatten_inheritable_page_items(fz_context *ctx, pdf_obj *page);
 
 pdf_page *pdf_load_page(fz_context *ctx, pdf_document *doc, int number);
+fz_page *pdf_load_page_imp(fz_context *ctx, fz_document *doc, int chapter, int number);
 
 void pdf_page_obj_transform(fz_context *ctx, pdf_obj *pageobj, fz_rect *page_mediabox, fz_matrix *page_ctm);
 void pdf_page_transform(fz_context *ctx, pdf_page *page, fz_rect *mediabox, fz_matrix *ctm);
diff --git a/platform/gl/gl-main.c b/platform/gl/gl-main.c
index ffc5dd3..30a1c28 100644
--- a/platform/gl/gl-main.c
+++ b/platform/gl/gl-main.c
@@ -165,7 +165,7 @@ static int oldinvert = 0, currentinvert = 0;
 static int oldicc = 1, currenticc = 1;
 static int oldaa = 8, currentaa = 8;
 static int oldseparations = 0, currentseparations = 0;
-static int oldpage = 0, currentpage = 0;
+static fz_location oldpage = {0,0}, currentpage = {0,0};
 static float oldzoom = DEFRES, currentzoom = DEFRES;
 static float oldrotate = 0, currentrotate = 0;
 
@@ -181,7 +181,7 @@ static const char *tooltip = NULL;
 
 struct mark
 {
-	int page;
+	fz_location loc;
 	fz_point scroll;
 };
 
@@ -245,6 +245,37 @@ static void read_history_file_as_json(js_State *J)
 	fz_drop_buffer(ctx, buf);
 }
 
+static fz_location try_location(js_State *J)
+{
+	fz_location loc;
+	if (js_isnumber(J, -1))
+		loc = fz_make_location(0, js_tryinteger(J, -1, 1) - 1);
+	else
+	{
+		js_getindex(J, -1, 0);
+		loc.chapter = js_tryinteger(J, -1, 1) - 1;
+		js_pop(J, 1);
+		js_getindex(J, -1, 1);
+		loc.page = js_tryinteger(J, -1, 1) - 1;
+		js_pop(J, 1);
+	}
+	return loc;
+}
+
+static void push_location(js_State *J, fz_location loc)
+{
+	if (loc.chapter == 0)
+		js_pushnumber(J, loc.page+1);
+	else
+	{
+		js_newarray(J);
+		js_pushnumber(J, loc.chapter+1);
+		js_setindex(J, -2, 0);
+		js_pushnumber(J, loc.page+1);
+		js_setindex(J, -2, 1);
+	}
+}
+
 static void load_history(void)
 {
 	js_State *J;
@@ -262,7 +293,7 @@ static void load_history(void)
 	{
 		if (js_hasproperty(J, -1, "current"))
 		{
-			currentpage = js_tryinteger(J, -1, 1) - 1;
+			currentpage = try_location(J);
 			js_pop(J, 1);
 		}
 
@@ -274,7 +305,7 @@ static void load_history(void)
 				for (i = 0; i < history_count; ++i)
 				{
 					js_getindex(J, -1, i);
-					history[i].page = js_tryinteger(J, -1, 1) - 1;
+					history[i].loc = try_location(J);
 					js_pop(J, 1);
 				}
 			}
@@ -289,7 +320,7 @@ static void load_history(void)
 				for (i = 0; i < future_count; ++i)
 				{
 					js_getindex(J, -1, i);
-					future[i].page = js_tryinteger(J, -1, 1) - 1;
+					future[i].loc = try_location(J);
 					js_pop(J, 1);
 				}
 			}
@@ -304,7 +335,7 @@ static void load_history(void)
 				for (i = 0; i < n; ++i)
 				{
 					js_getindex(J, -1, i);
-					marks[i].page = js_tryinteger(J, -1, 1) - 1;
+					marks[i].loc = try_location(J);
 					js_pop(J, 1);
 				}
 			}
@@ -337,13 +368,13 @@ static void save_history(void)
 
 	js_newobject(J);
 	{
-		js_pushnumber(J, currentpage+1);
+		push_location(J, currentpage);
 		js_setproperty(J, -2, "current");
 
 		js_newarray(J);
 		for (i = 0; i < history_count; ++i)
 		{
-			js_pushnumber(J, history[i].page+1);
+			push_location(J, history[i].loc);
 			js_setindex(J, -2, i);
 		}
 		js_setproperty(J, -2, "history");
@@ -351,7 +382,7 @@ static void save_history(void)
 		js_newarray(J);
 		for (i = 0; i < future_count; ++i)
 		{
-			js_pushnumber(J, future[i].page+1);
+			push_location(J, future[i].loc);
 			js_setindex(J, -2, i);
 		}
 		js_setproperty(J, -2, "future");
@@ -359,7 +390,7 @@ static void save_history(void)
 		js_newarray(J);
 		for (i = 0; i < (int)nelem(marks); ++i)
 		{
-			js_pushnumber(J, marks[i].page+1);
+			push_location(J, marks[i].loc);
 			js_setindex(J, -2, i);
 		}
 		js_setproperty(J, -2, "marks");
@@ -395,8 +426,8 @@ static int search_active = 0;
 static struct input search_input = { { 0 }, 0 };
 static char *search_needle = 0;
 static int search_dir = 1;
-static int search_page = -1;
-static int search_hit_page = -1;
+static fz_location search_page = {-1, -1};
+static fz_location search_hit_page = {-1, -1};
 static int search_hit_count = 0;
 static fz_quad search_hit_quads[5000];
 
@@ -520,6 +551,8 @@ void update_title(void)
 	char *extra = "";
 	size_t n;
 
+	int nc = fz_count_chapters(ctx, doc);
+
 	title = strrchr(filename, '/');
 	if (!title)
 		title = strrchr(filename, '\\');
@@ -533,9 +566,24 @@ void update_title(void)
 
 	n = strlen(title);
 	if (n > 50)
-		sprintf(buf, "...%s%s - %d / %d", title + n - 50, extra, currentpage + 1, fz_count_pages(ctx, doc));
+	{
+		if (nc == 1)
+			sprintf(buf, "...%s%s - %d/%d", title + n - 50, extra, currentpage.page + 1, fz_count_pages(ctx, doc));
+		else
+			sprintf(buf, "...%s%s - %d/%d - %d/%d", title + n - 50, extra,
+				currentpage.chapter + 1, nc,
+				currentpage.page + 1, fz_count_chapter_pages(ctx, doc, currentpage.chapter));
+	}
 	else
-		sprintf(buf, "%s%s - %d / %d", title, extra, currentpage + 1, fz_count_pages(ctx, doc));
+	{
+		if (nc == 1)
+			sprintf(buf, "%s%s - %d/%d", title, extra, currentpage.page + 1, fz_count_pages(ctx, doc));
+		else
+
+			sprintf(buf, "%s%s - %d/%d - %d/%d", title, extra,
+				currentpage.chapter + 1, nc,
+				currentpage.page + 1, fz_count_chapter_pages(ctx, doc, currentpage.chapter));
+	}
 	glutSetWindowTitle(buf);
 	glutSetIconTitle(buf);
 }
@@ -564,7 +612,7 @@ void load_page(void)
 	fz_drop_page(ctx, fzpage);
 	fzpage = NULL;
 
-	fzpage = fz_load_page(ctx, doc, currentpage);
+	fzpage = fz_load_chapter_page(ctx, doc, currentpage.chapter, currentpage.page);
 	if (pdf)
 		page = (pdf_page*)fzpage;
 
@@ -625,7 +673,8 @@ void render_page(void)
 
 void render_page_if_changed(void)
 {
-	if (oldpage != currentpage ||
+	if (oldpage.chapter != currentpage.chapter ||
+		oldpage.page != currentpage.page ||
 		oldzoom != currentzoom ||
 		oldrotate != currentrotate ||
 		oldinvert != currentinvert ||
@@ -649,22 +698,38 @@ void render_page_if_changed(void)
 static struct mark save_mark()
 {
 	struct mark mark;
-	mark.page = currentpage;
+	mark.loc = currentpage;
 	mark.scroll = fz_transform_point_xy(scroll_x, scroll_y, view_page_inv_ctm);
 	return mark;
 }
 
 static void restore_mark(struct mark mark)
 {
-	currentpage = mark.page;
+	currentpage = mark.loc;
 	mark.scroll = fz_transform_point(mark.scroll, draw_page_ctm);
 	scroll_x = mark.scroll.x;
 	scroll_y = mark.scroll.y;
 }
 
+static int eqloc(fz_location a, fz_location b)
+{
+	return a.chapter == b.chapter && a.page == b.page;
+}
+
+static int is_first_page(fz_location loc)
+{
+	return (loc.chapter == 0 && loc.page == 0);
+}
+
+static int is_last_page(fz_location loc)
+{
+	fz_location last = fz_last_page(ctx, doc);
+	return (loc.chapter == last.chapter && loc.page == last.page);
+}
+
 static void push_history(void)
 {
-	if (history_count > 0 && history[history_count-1].page == currentpage)
+	if (history_count > 0 && eqloc(history[history_count-1].loc, currentpage))
 		return;
 	if (history_count + 1 >= (int)nelem(history))
 	{
@@ -695,22 +760,41 @@ static void clear_future(void)
 	future_count = 0;
 }
 
+static void jump_to_location(fz_location loc)
+{
+	clear_future();
+	push_history();
+	currentpage = fz_clamp_location(ctx, doc, loc);
+	push_history();
+}
+
+static void jump_to_location_xy(fz_location loc, float x, float y)
+{
+	fz_point p = fz_transform_point_xy(x, y, draw_page_ctm);
+	clear_future();
+	push_history();
+	currentpage = fz_clamp_location(ctx, doc, loc);
+	scroll_x = p.x;
+	scroll_y = p.y;
+	push_history();
+}
+
 static void jump_to_page(int newpage)
 {
-	newpage = fz_clampi(newpage, 0, fz_count_pages(ctx, doc) - 1);
 	clear_future();
 	push_history();
-	currentpage = newpage;
+	currentpage = fz_location_from_page_number(ctx, doc, newpage);
+	currentpage = fz_clamp_location(ctx, doc, currentpage);
 	push_history();
 }
 
 static void jump_to_page_xy(int newpage, float x, float y)
 {
 	fz_point p = fz_transform_point_xy(x, y, draw_page_ctm);
-	newpage = fz_clampi(newpage, 0, fz_count_pages(ctx, doc) - 1);
 	clear_future();
 	push_history();
-	currentpage = newpage;
+	currentpage = fz_location_from_page_number(ctx, doc, newpage);
+	currentpage = fz_clamp_location(ctx, doc, currentpage);
 	scroll_x = p.x;
 	scroll_y = p.y;
 	push_history();
@@ -718,17 +802,17 @@ static void jump_to_page_xy(int newpage, float x, float y)
 
 static void pop_history(void)
 {
-	int here = currentpage;
+	fz_location here = currentpage;
 	push_future();
-	while (history_count > 0 && currentpage == here)
+	while (history_count > 0 && eqloc(currentpage, here))
 		restore_mark(history[--history_count]);
 }
 
 static void pop_future(void)
 {
-	int here = currentpage;
+	fz_location here = currentpage;
 	push_history();
-	while (future_count > 0 && currentpage == here)
+	while (future_count > 0 && eqloc(currentpage, here))
 		restore_mark(future[--future_count]);
 	push_history();
 }
@@ -758,16 +842,15 @@ static int count_outline(fz_outline *node, int end)
 	while (node)
 	{
 		p = node->page;
-		if (p >= 0)
-		{
-			count += 1;
-			n = end;
-			if (node->next && node->next->page >= 0)
-				n = node->next->page;
-			is_selected = (currentpage == p || (currentpage > p && currentpage < n));
-			if (node->down && (node->is_open || is_selected))
-				count += count_outline(node->down, end);
-		}
+		count += 1;
+		n = end;
+		if (node->next && node->next->page >= 0)
+			n = node->next->page;
+		is_selected = 0;
+		if (fz_count_chapters(ctx, doc) == 1)
+			is_selected = (p>=0) && (currentpage.page == p || (currentpage.page > p && currentpage.page < n));
+		if (node->down && (node->is_open || is_selected))
+			count += count_outline(node->down, end);
 		node = node->next;
 	}
 	return count;
@@ -775,25 +858,34 @@ static int count_outline(fz_outline *node, int end)
 
 static void do_outline_imp(struct list *list, int end, fz_outline *node, int depth)
 {
-	int selected, was_open, n;
+	int is_selected, was_open, n;
 
 	while (node)
 	{
 		int p = node->page;
-		if (p >= 0)
+		n = end;
+		if (node->next && node->next->page >= 0)
+			n = node->next->page;
+
+		was_open = node->is_open;
+		is_selected = 0;
+		if (fz_count_chapters(ctx, doc) == 1)
+			is_selected = (p>=0) && (currentpage.page == p || (currentpage.page > p && currentpage.page < n));
+		if (ui_tree_item(list, node, node->title, is_selected, depth, !!node->down, &node->is_open))
 		{
-			n = end;
-			if (node->next && node->next->page >= 0)
-				n = node->next->page;
-
-			was_open = node->is_open;
-			selected = (currentpage == p || (currentpage > p && currentpage < n));
-			if (ui_tree_item(list, node, node->title, selected, depth, !!node->down, &node->is_open))
+			if (p < 0)
+			{
+				currentpage = fz_resolve_link(ctx, doc, node->uri, &node->x, &node->y);
+				jump_to_location_xy(currentpage, node->x, node->y);
+			}
+			else
+			{
 				jump_to_page_xy(p, node->x, node->y);
-
-			if (node->down && (was_open || selected))
-				do_outline_imp(list, n, node->down, depth + 1);
+			}
 		}
+
+		if (node->down && (was_open || is_selected))
+			do_outline_imp(list, n, node->down, depth + 1);
 		node = node->next;
 	}
 }
@@ -802,8 +894,8 @@ static void do_outline(fz_outline *node)
 {
 	static struct list list;
 	ui_layout(L, BOTH, NW, 0, 0);
-	ui_tree_begin(&list, count_outline(node, fz_count_pages(ctx, doc)), outline_w, 0, 1);
-	do_outline_imp(&list, fz_count_pages(ctx, doc), node, 0);
+	ui_tree_begin(&list, count_outline(node, 65535), outline_w, 0, 1);
+	do_outline_imp(&list, 65535, node, 0);
 	ui_tree_end(&list);
 	ui_splitter(&outline_w, 150, 500, R);
 }
@@ -850,11 +942,8 @@ static void do_links(fz_link *link)
 					open_browser(link->uri);
 				else
 				{
-					int p = fz_resolve_link(ctx, doc, link->uri, &link_x, &link_y);
-					if (p >= 0)
-						jump_to_page_xy(p, link_x, link_y);
-					else
-						fz_warn(ctx, "cannot find link destination '%s'", link->uri);
+					fz_location loc = fz_resolve_link(ctx, doc, link->uri, &link_x, &link_y);
+					jump_to_location_xy(loc, link_x, link_y);
 				}
 			}
 		}
@@ -1057,7 +1146,7 @@ static void load_document(void)
 	}
 	anchor = NULL;
 
-	currentpage = fz_clampi(currentpage, 0, fz_count_pages(ctx, doc) - 1);
+	currentpage = fz_clamp_location(ctx, doc, currentpage);
 }
 
 void reload(void)
@@ -1128,11 +1217,12 @@ static void smart_move_backward(void)
 	{
 		if (scroll_x <= slop_x)
 		{
-			if (currentpage - 1 >= 0)
+			fz_location prev = fz_previous_page(ctx, doc, currentpage);
+			if (!eqloc(currentpage, prev))
 			{
-				scroll_x = page_tex.w;
-				scroll_y = page_tex.h;
-				currentpage -= 1;
+				scroll_x = (page_tex.w <= canvas_w) ? 0 : page_tex.w - canvas_w;
+				scroll_y = (page_tex.h <= canvas_h) ? 0 : page_tex.h - canvas_h;
+				currentpage = prev;
 			}
 		}
 		else
@@ -1155,11 +1245,12 @@ static void smart_move_forward(void)
 	{
 		if (scroll_x + canvas_w >= page_tex.w - slop_x)
 		{
-			if (currentpage + 1 < fz_count_pages(ctx, doc))
+			fz_location next = fz_next_page(ctx, doc, currentpage);
+			if (!eqloc(currentpage, next))
 			{
 				scroll_x = 0;
 				scroll_y = 0;
-				currentpage += 1;
+				currentpage = next;
 			}
 		}
 		else
@@ -1177,7 +1268,8 @@ static void smart_move_forward(void)
 static void clear_search(void)
 {
 	showsearch = 0;
-	search_hit_page = -1;
+	search_page = currentpage;
+	search_hit_page = fz_make_location(-1, -1);
 	search_hit_count = 0;
 }
 
@@ -1228,10 +1320,19 @@ static void do_app(void)
 
 		case 'b': number = fz_maxi(number, 1); while (number--) smart_move_backward(); break;
 		case ' ': number = fz_maxi(number, 1); while (number--) smart_move_forward(); break;
-		case ',': case KEY_PAGE_UP: currentpage -= fz_maxi(number, 1); break;
-		case '.': case KEY_PAGE_DOWN: currentpage += fz_maxi(number, 1); break;
 		case 'g': jump_to_page(number - 1); break;
-		case 'G': jump_to_page(fz_count_pages(ctx, doc) - 1); break;
+		case 'G': jump_to_location(fz_last_page(ctx, doc)); break;
+
+		case ',': case KEY_PAGE_UP:
+			number = fz_maxi(number, 1);
+			while (number--)
+				currentpage = fz_previous_page(ctx, doc, currentpage);
+			break;
+		case '.': case KEY_PAGE_DOWN:
+			number = fz_maxi(number, 1);
+			while (number--)
+				currentpage = fz_next_page(ctx, doc, currentpage);
+			break;
 
 		case 'A':
 			if (number == 0)
@@ -1256,7 +1357,7 @@ static void do_app(void)
 			{
 				struct mark mark = marks[number];
 				restore_mark(mark);
-				jump_to_page(mark.page);
+				jump_to_location(mark.loc);
 			}
 			break;
 		case 'T':
@@ -1285,29 +1386,33 @@ static void do_app(void)
 			break;
 		case 'N':
 			search_dir = -1;
-			if (search_hit_page == currentpage)
-				search_page = currentpage + search_dir;
+			search_active = !!search_needle;
+			if (eqloc(search_hit_page, currentpage))
+			{
+				search_page = fz_previous_page(ctx, doc, currentpage);
+				if (is_first_page(search_page))
+					search_active = 0;
+			}
 			else
-				search_page = currentpage;
-			if (search_page >= 0 && search_page < fz_count_pages(ctx, doc))
 			{
-				search_hit_page = -1;
-				if (search_needle)
-					search_active = 1;
+				search_page = currentpage;
 			}
+			search_hit_page = fz_make_location(-1, -1);
 			break;
 		case 'n':
 			search_dir = 1;
-			if (search_hit_page == currentpage)
-				search_page = currentpage + search_dir;
+			search_active = !!search_needle;
+			if (eqloc(search_hit_page, currentpage))
+			{
+				search_page = fz_next_page(ctx, doc, currentpage);
+				if (is_last_page(search_page))
+					search_active = 0;
+			}
 			else
-				search_page = currentpage;
-			if (search_page >= 0 && search_page < fz_count_pages(ctx, doc))
 			{
-				search_hit_page = -1;
-				if (search_needle)
-					search_active = 1;
+				search_page = currentpage;
 			}
+			search_hit_page = fz_make_location(-1, -1);
 			break;
 		}
 
@@ -1316,12 +1421,12 @@ static void do_app(void)
 		else
 			number = 0;
 
-		currentpage = fz_clampi(currentpage, 0, fz_count_pages(ctx, doc) - 1);
+		currentpage = fz_clamp_location(ctx, doc, currentpage);
 		while (currentrotate < 0) currentrotate += 360;
 		while (currentrotate >= 360) currentrotate -= 360;
 
-		if (search_hit_page != currentpage)
-			search_hit_page = -1; /* clear highlights when navigating */
+		if (!eqloc(search_hit_page, currentpage))
+			search_hit_page = fz_make_location(-1, -1); /* clear highlights when navigating */
 
 		ui.key = 0; /* we ate the key event, so zap it */
 	}
@@ -1363,7 +1468,7 @@ static void do_info(void)
 			fz_strlcat(buf, "none", sizeof buf);
 		ui_label("Permissions: %s", buf);
 	}
-	ui_label("Page: %d / %d", currentpage + 1, fz_count_pages(ctx, doc));
+	ui_label("Page: %d / %d", fz_page_number_from_location(ctx, doc, currentpage)+1, fz_count_pages(ctx, doc));
 	{
 		int w = (int)(page_bounds.x1 - page_bounds.x0 + 0.5f);
 		int h = (int)(page_bounds.y1 - page_bounds.y0 + 0.5f);
@@ -1473,7 +1578,7 @@ static void do_canvas(void)
 		ui_layout(T, X, NW, 0, 0);
 		ui_panel_begin(0, ui.gridsize+8, 4, 4, 1);
 		ui_layout(L, NONE, W, 2, 0);
-		ui_label("Searching page %d of %d.", search_page + 1, fz_count_pages(ctx, doc));
+		ui_label("Searching chapter %d page %d...", search_page.chapter, search_page.page);
 		ui_panel_end();
 	}
 	else
@@ -1486,7 +1591,7 @@ static void do_canvas(void)
 		do_links(links);
 		do_page_selection();
 
-		if (search_hit_page == currentpage && search_hit_count > 0)
+		if (eqloc(search_hit_page, currentpage) && search_hit_count > 0)
 			do_search_hits();
 	}
 
@@ -1500,7 +1605,7 @@ static void do_canvas(void)
 		if (ui_input(&search_input, 0, 1) == UI_INPUT_ACCEPT)
 		{
 			showsearch = 0;
-			search_page = -1;
+			search_page = fz_make_location(-1, -1);
 			if (search_needle)
 			{
 				fz_free(ctx, search_needle);
@@ -1544,24 +1649,33 @@ void do_main(void)
 		ui.key = ui.mod = ui.plain = 0;
 		ui.down = ui.middle = ui.right = 0;
 
-		while (glutGet(GLUT_ELAPSED_TIME) < start_time + 200)
+		while (search_active && glutGet(GLUT_ELAPSED_TIME) < start_time + 200)
 		{
-			search_hit_count = fz_search_page_number(ctx, doc, search_page, search_needle,
-					search_hit_quads, nelem(search_hit_quads));
+			search_hit_count = fz_search_chapter_page_number(ctx, doc,
+				search_page.chapter, search_page.page,
+				search_needle,
+				search_hit_quads, nelem(search_hit_quads));
 			if (search_hit_count)
 			{
 				search_active = 0;
 				search_hit_page = search_page;
-				jump_to_page(search_hit_page);
-				break;
+				jump_to_location(search_hit_page);
 			}
 			else
 			{
-				search_page += search_dir;
-				if (search_page < 0 || search_page == fz_count_pages(ctx, doc))
+				if (search_dir > 0)
 				{
-					search_active = 0;
-					break;
+					if (is_last_page(search_page))
+						search_active = 0;
+					else
+						search_page = fz_next_page(ctx, doc, search_page);
+				}
+				else
+				{
+					if (is_first_page(search_page))
+						search_active = 0;
+					else
+						search_page = fz_previous_page(ctx, doc, search_page);
 				}
 			}
 		}
@@ -1576,7 +1690,7 @@ void do_main(void)
 	if (showoutline)
 		do_outline(outline);
 
-	if (oldpage != currentpage || oldseparations != currentseparations || oldicc != currenticc)
+	if (!eqloc(oldpage, currentpage) || oldseparations != currentseparations || oldicc != currenticc)
 	{
 		load_page();
 		update_title();
diff --git a/platform/java/example/Viewer.java b/platform/java/example/Viewer.java
index a39ffd6..8f0ec2e 100644
--- a/platform/java/example/Viewer.java
+++ b/platform/java/example/Viewer.java
@@ -421,12 +421,12 @@ public class Viewer extends Frame implements WindowListener, ActionListener, Ite
 		}
 
 		if (layoutEm != oldLayoutEm) {
-			long mark = doc.makeBookmark(pageNumber);
+			long mark = doc.makeBookmark(doc.locationFromPageNumber(pageNumber));
 			doc.layout(layoutWidth, layoutHeight, layoutEm);
 			updateOutline();
 			pageCount = doc.countPages();
 			pageLabel.setText("/ " + pageCount);
-			pageNumber = doc.findBookmark(mark);
+			pageNumber = doc.pageNumberFromLocation(doc.findBookmark(mark));
 		}
 
 		if (zoomLevel != oldZoomLevel || pageNumber != oldPageNumber || layoutEm != oldLayoutEm || searchHits != oldSearchHits)
@@ -444,9 +444,10 @@ public class Viewer extends Frame implements WindowListener, ActionListener, Ite
 		if (source == outlineList) {
 			int i = outlineList.getSelectedIndex();
 			Outline node = flatOutline.elementAt(i);
-			if (node.page >= 0) {
-				if (node.page != pageNumber) {
-					pageNumber = node.page;
+			int linkPage = doc.pageNumberFromLocation(doc.resolveLink(node));
+			if (linkPage >= 0) {
+				if (linkPage != pageNumber) {
+					pageNumber = linkPage;
 					updatePageCanvas();
 				}
 			}
diff --git a/platform/java/mupdf_native.c b/platform/java/mupdf_native.c
index 2f199ee..9eaaab4 100644
--- a/platform/java/mupdf_native.c
+++ b/platform/java/mupdf_native.c
@@ -69,6 +69,7 @@ static jclass cls_Image;
 static jclass cls_IndexOutOfBoundsException;
 static jclass cls_IntegerArray;
 static jclass cls_Link;
+static jclass cls_Location;
 static jclass cls_Matrix;
 static jclass cls_NativeDevice;
 static jclass cls_NullPointerException;
@@ -114,9 +115,6 @@ static jfieldID fid_DocumentWriter_pointer;
 static jfieldID fid_Document_pointer;
 static jfieldID fid_Font_pointer;
 static jfieldID fid_Image_pointer;
-static jfieldID fid_Link_bounds;
-static jfieldID fid_Link_page;
-static jfieldID fid_Link_uri;
 static jfieldID fid_Matrix_a;
 static jfieldID fid_Matrix_b;
 static jfieldID fid_Matrix_c;
@@ -195,6 +193,7 @@ static jmethodID mid_Document_init;
 static jmethodID mid_Font_init;
 static jmethodID mid_Image_init;
 static jmethodID mid_Link_init;
+static jmethodID mid_Location_init;
 static jmethodID mid_Matrix_init;
 static jmethodID mid_Object_toString;
 static jmethodID mid_Outline_init;
@@ -579,10 +578,10 @@ static int find_fids(JNIEnv *env)
 	mid_Image_init = get_method(&err, env, "<init>", "(J)V");
 
 	cls_Link = get_class(&err, env, PKG"Link");
-	fid_Link_bounds = get_field(&err, env, "bounds", "L"PKG"Rect;");
-	fid_Link_page = get_field(&err, env, "page", "I");
-	fid_Link_uri = get_field(&err, env, "uri", "Ljava/lang/String;");
-	mid_Link_init = get_method(&err, env, "<init>", "(L"PKG"Rect;ILjava/lang/String;)V");
+	mid_Link_init = get_method(&err, env, "<init>", "(L"PKG"Rect;Ljava/lang/String;)V");
+
+	cls_Location = get_class(&err, env, PKG"Location");
+	mid_Location_init = get_method(&err, env, "<init>", "(IIFF)V");
 
 	cls_Matrix = get_class(&err, env, PKG"Matrix");
 	fid_Matrix_a = get_field(&err, env, "a", "F");
@@ -598,7 +597,7 @@ static int find_fids(JNIEnv *env)
 	fid_NativeDevice_nativeInfo = get_field(&err, env, "nativeInfo", "J");
 
 	cls_Outline = get_class(&err, env, PKG"Outline");
-	mid_Outline_init = get_method(&err, env, "<init>", "(Ljava/lang/String;ILjava/lang/String;FF[L"PKG"Outline;)V");
+	mid_Outline_init = get_method(&err, env, "<init>", "(Ljava/lang/String;Ljava/lang/String;[L"PKG"Outline;)V");
 
 	cls_Page = get_class(&err, env, PKG"Page");
 	fid_Page_pointer = get_field(&err, env, "pointer", "J");
@@ -806,6 +805,7 @@ static void lose_fids(JNIEnv *env)
 	(*env)->DeleteGlobalRef(env, cls_IntegerArray);
 	(*env)->DeleteGlobalRef(env, cls_IOException);
 	(*env)->DeleteGlobalRef(env, cls_Link);
+	(*env)->DeleteGlobalRef(env, cls_Location);
 	(*env)->DeleteGlobalRef(env, cls_Matrix);
 	(*env)->DeleteGlobalRef(env, cls_NativeDevice);
 	(*env)->DeleteGlobalRef(env, cls_NullPointerException);
@@ -1472,11 +1472,8 @@ static inline jobject to_Outline_safe(fz_context *ctx, JNIEnv *env, fz_document
 	while (outline)
 	{
 		jstring jtitle = NULL;
-		jint jpage = -1;
 		jstring juri = NULL;
 		jobject jdown = NULL;
-		float x = 0;
-		float y = 0;
 
 		if (outline->title)
 		{
@@ -1486,13 +1483,8 @@ static inline jobject to_Outline_safe(fz_context *ctx, JNIEnv *env, fz_document
 
 		if (outline->uri)
 		{
-			if (fz_is_external_link(ctx, outline->uri))
-			{
-				juri = (*env)->NewStringUTF(env, outline->uri);
-				if (!juri) return NULL;
-			}
-			else
-				jpage = fz_resolve_link(ctx, doc, outline->uri, &x, &y);
+			juri = (*env)->NewStringUTF(env, outline->uri);
+			if (!juri) return NULL;
 		}
 
 		if (outline->down)
@@ -1501,7 +1493,7 @@ static inline jobject to_Outline_safe(fz_context *ctx, JNIEnv *env, fz_document
 			if (!jdown) return NULL;
 		}
 
-		joutline = (*env)->NewObject(env, cls_Outline, mid_Outline_init, jtitle, jpage, juri, x, y, jdown);
+		joutline = (*env)->NewObject(env, cls_Outline, mid_Outline_init, jtitle, juri, jdown);
 		if (!joutline) return NULL;
 
 		if (jdown)
@@ -5252,7 +5244,27 @@ FUN(Document_authenticatePassword)(JNIEnv *env, jobject self, jstring jpassword)
 }
 
 JNIEXPORT jint JNICALL
-FUN(Document_countPages)(JNIEnv *env, jobject self)
+FUN(Document_countChapters)(JNIEnv *env, jobject self)
+{
+	fz_context *ctx = get_context(env);
+	fz_document *doc = from_Document(env, self);
+	int count = 0;
+
+	if (!ctx || !doc) return 0;
+
+	fz_try(ctx)
+		count = fz_count_chapters(ctx, doc);
+	fz_catch(ctx)
+	{
+		jni_rethrow(env, ctx);
+		return 0;
+	}
+
+	return count;
+}
+
+JNIEXPORT jint JNICALL
+FUN(Document_countPages)(JNIEnv *env, jobject self, jint chapter)
 {
 	fz_context *ctx = get_context(env);
 	fz_document *doc = from_Document(env, self);
@@ -5261,7 +5273,7 @@ FUN(Document_countPages)(JNIEnv *env, jobject self)
 	if (!ctx || !doc) return 0;
 
 	fz_try(ctx)
-		count = fz_count_pages(ctx, doc);
+		count = fz_count_chapter_pages(ctx, doc, chapter);
 	fz_catch(ctx)
 	{
 		jni_rethrow(env, ctx);
@@ -5306,7 +5318,7 @@ FUN(Document_layout)(JNIEnv *env, jobject self, jfloat w, jfloat h, jfloat em)
 }
 
 JNIEXPORT jobject JNICALL
-FUN(Document_loadPage)(JNIEnv *env, jobject self, jint number)
+FUN(Document_loadPage)(JNIEnv *env, jobject self, jint chapter, jint number)
 {
 	fz_context *ctx = get_context(env);
 	fz_document *doc = from_Document(env, self);
@@ -5315,7 +5327,7 @@ FUN(Document_loadPage)(JNIEnv *env, jobject self, jint number)
 	if (!ctx || !doc) return NULL;
 
 	fz_try(ctx)
-		page = fz_load_page(ctx, doc, number);
+		page = fz_load_chapter_page(ctx, doc, chapter, number);
 	fz_catch(ctx)
 	{
 		jni_rethrow(env, ctx);
@@ -5401,14 +5413,14 @@ FUN(Document_loadOutline)(JNIEnv *env, jobject self)
 }
 
 JNIEXPORT jlong JNICALL
-FUN(Document_makeBookmark)(JNIEnv *env, jobject self, jint page)
+FUN(Document_makeBookmark)(JNIEnv *env, jobject self, jint chapter, jint page)
 {
 	fz_context *ctx = get_context(env);
 	fz_document *doc = from_Document(env, self);
 	fz_bookmark mark = 0;
 
 	fz_try(ctx)
-		mark = fz_make_bookmark(ctx, doc, page);
+		mark = fz_make_bookmark(ctx, doc, fz_make_location(chapter, page));
 	fz_catch(ctx)
 	{
 		jni_rethrow(env, ctx);
@@ -5418,22 +5430,52 @@ FUN(Document_makeBookmark)(JNIEnv *env, jobject self, jint page)
 	return mark;
 }
 
-JNIEXPORT jint JNICALL
+JNIEXPORT jobject JNICALL
 FUN(Document_findBookmark)(JNIEnv *env, jobject self, jlong mark)
 {
 	fz_context *ctx = get_context(env);
 	fz_document *doc = from_Document(env, self);
-	int page = -1;
+	fz_location loc = { -1, -1 };
 
 	fz_try(ctx)
-		page = fz_lookup_bookmark(ctx, doc, mark);
+		loc = fz_lookup_bookmark(ctx, doc, mark);
 	fz_catch(ctx)
 	{
 		jni_rethrow(env, ctx);
-		return -1;
+		return NULL;
 	}
 
-	return page;
+	return (*env)->NewObject(env, cls_Location, mid_Location_init, loc.chapter, loc.page, 0, 0);
+}
+
+JNIEXPORT jobject JNICALL
+FUN(Document_resolveLink)(JNIEnv *env, jobject self, jstring juri)
+{
+	fz_context *ctx = get_context(env);
+	fz_document *doc = from_Document(env, self);
+	fz_location loc = { -1, -1 };
+	float x = 0, y = 0;
+	const char *uri = "";
+
+	if (juri)
+	{
+		uri = (*env)->GetStringUTFChars(env, juri, NULL);
+		if (!uri)
+			return NULL;
+	}
+
+	fz_try(ctx)
+		loc = fz_resolve_link(ctx, doc, uri, &x, &y);
+	fz_always(ctx)
+		if (juri)
+			(*env)->ReleaseStringUTFChars(env, juri, uri);
+	fz_catch(ctx)
+	{
+		jni_rethrow(env, ctx);
+		return NULL;
+	}
+
+	return (*env)->NewObject(env, cls_Location, mid_Location_init, loc.chapter, loc.page, x, y);
 }
 
 /* Page interface */
@@ -5758,7 +5800,6 @@ FUN(Page_getLinks)(JNIEnv *env, jobject self)
 		jobject jbounds = NULL;
 		jobject jlink = NULL;
 		jobject juri = NULL;
-		int page = 0;
 
 		jbounds = to_Rect_safe(ctx, env, link->rect);
 		if (!jbounds) return NULL;
@@ -5768,10 +5809,8 @@ FUN(Page_getLinks)(JNIEnv *env, jobject self)
 			juri = (*env)->NewStringUTF(env, link->uri);
 			if (!juri) return NULL;
 		}
-		else
-			page = fz_resolve_link(ctx, link->doc, link->uri, NULL, NULL);
 
-		jlink = (*env)->NewObject(env, cls_Link, mid_Link_init, jbounds, page, juri);
+		jlink = (*env)->NewObject(env, cls_Link, mid_Link_init, jbounds, juri);
 		(*env)->DeleteLocalRef(env, jbounds);
 		if (!jlink) return NULL;
 		if (juri)
diff --git a/platform/java/mupdf_native.h b/platform/java/mupdf_native.h
index 0ccba08..4b72e9d 100644
--- a/platform/java/mupdf_native.h
+++ b/platform/java/mupdf_native.h
@@ -484,19 +484,35 @@ JNIEXPORT jboolean JNICALL Java_com_artifex_mupdf_fitz_Document_authenticatePass
 
 /*
  * Class:     com_artifex_mupdf_fitz_Document
- * Method:    countPages
+ * Method:    countChapters
  * Signature: ()I
  */
-JNIEXPORT jint JNICALL Java_com_artifex_mupdf_fitz_Document_countPages
+JNIEXPORT jint JNICALL Java_com_artifex_mupdf_fitz_Document_countChapters
   (JNIEnv *, jobject);
 
 /*
  * Class:     com_artifex_mupdf_fitz_Document
+ * Method:    countPages
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_com_artifex_mupdf_fitz_Document_countPages
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     com_artifex_mupdf_fitz_Document
  * Method:    loadPage
- * Signature: (I)Lcom/artifex/mupdf/fitz/Page;
+ * Signature: (II)Lcom/artifex/mupdf/fitz/Page;
  */
 JNIEXPORT jobject JNICALL Java_com_artifex_mupdf_fitz_Document_loadPage
-  (JNIEnv *, jobject, jint);
+  (JNIEnv *, jobject, jint, jint);
+
+/*
+ * Class:     com_artifex_mupdf_fitz_Document
+ * Method:    resolveLink
+ * Signature: (Ljava/lang/String;)Lcom/artifex/mupdf/fitz/Location;
+ */
+JNIEXPORT jobject JNICALL Java_com_artifex_mupdf_fitz_Document_resolveLink
+  (JNIEnv *, jobject, jstring);
 
 /*
  * Class:     com_artifex_mupdf_fitz_Document
@@ -532,19 +548,19 @@ JNIEXPORT void JNICALL Java_com_artifex_mupdf_fitz_Document_layout
 
 /*
  * Class:     com_artifex_mupdf_fitz_Document
- * Method:    makeBookmark
- * Signature: (I)J
+ * Method:    findBookmark
+ * Signature: (J)Lcom/artifex/mupdf/fitz/Location;
  */
-JNIEXPORT jlong JNICALL Java_com_artifex_mupdf_fitz_Document_makeBookmark
-  (JNIEnv *, jobject, jint);
+JNIEXPORT jobject JNICALL Java_com_artifex_mupdf_fitz_Document_findBookmark
+  (JNIEnv *, jobject, jlong);
 
 /*
  * Class:     com_artifex_mupdf_fitz_Document
- * Method:    findBookmark
- * Signature: (J)I
+ * Method:    makeBookmark
+ * Signature: (II)J
  */
-JNIEXPORT jint JNICALL Java_com_artifex_mupdf_fitz_Document_findBookmark
-  (JNIEnv *, jobject, jlong);
+JNIEXPORT jlong JNICALL Java_com_artifex_mupdf_fitz_Document_makeBookmark
+  (JNIEnv *, jobject, jint, jint);
 
 /*
  * Class:     com_artifex_mupdf_fitz_Document
@@ -2663,46 +2679,6 @@ JNIEXPORT jboolean JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_isEditing
 JNIEXPORT jboolean JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_setChoiceValue
   (JNIEnv *, jobject, jstring);
 
-/*
- * Class:     com_artifex_mupdf_fitz_PDFWidget
- * Method:    isSigned
- * Signature: ()Z
- */
-JNIEXPORT jboolean JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_isSigned
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_artifex_mupdf_fitz_PDFWidget
- * Method:    clearSignature
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_clearSignature
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_artifex_mupdf_fitz_PDFWidget
- * Method:    signWithPFX
- * Signature: ([B[B)V
- */
-JNIEXPORT void JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_signWithPFX
-  (JNIEnv *, jobject, jbyteArray, jbyteArray);
-
-/*
- * Class:     com_artifex_mupdf_fitz_PDFWidget
- * Method:    checkSignatureDigest
- * Signature: ()Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_checkSignatureDigest
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_artifex_mupdf_fitz_PDFWidget
- * Method:    checkSignatureCertificate
- * Signature: ()Ljava/lang/String;
- */
-JNIEXPORT jstring JNICALL Java_com_artifex_mupdf_fitz_PDFWidget_checkSignatureCertificate
-  (JNIEnv *, jobject);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/platform/java/src/com/artifex/mupdf/fitz/Document.java b/platform/java/src/com/artifex/mupdf/fitz/Document.java
index f957ab6..43517f1 100644
--- a/platform/java/src/com/artifex/mupdf/fitz/Document.java
+++ b/platform/java/src/com/artifex/mupdf/fitz/Document.java
@@ -45,15 +45,122 @@ public class Document
 	public native boolean needsPassword();
 	public native boolean authenticatePassword(String password);
 
-	public native int countPages();
-	public native Page loadPage(int number);
+
+	public native int countChapters();
+	public native int countPages(int chapter);
+	public native Page loadPage(int chapter, int number);
+
+	public int countPages() {
+		int np = 0;
+		int nc = countChapters();
+		for (int i = 0; i < nc; ++i)
+			np += countPages(i);
+		return np;
+	}
+
+	public Page loadPage(Location loc) {
+		return loadPage(loc.chapter, loc.page);
+	}
+
+	public Page loadPage(int number) {
+		int start = 0;
+		int nc = countChapters();
+		for (int i = 0; i < nc; ++i) {
+			int np = countPages(i);
+			if (number < start + np)
+				return loadPage(i, number - start);
+			start += np;
+		}
+		throw new IllegalArgumentException("page number out of range");
+	}
+
+	public Location lastPage() {
+		int nc = countChapters();
+		int np = countPages(nc-1);
+		return new Location(nc-1, np-1);
+	}
+
+	public Location nextPage(Location loc) {
+		int np = countPages(loc.chapter);
+		if (loc.page + 1 == np) {
+			int nc = countChapters();
+			if (loc.chapter + 1 < nc)
+				return new Location(loc.chapter + 1, 0);
+		} else {
+			return new Location(loc.chapter, loc.page + 1);
+		}
+		return loc;
+	}
+
+	public Location previousPage(Location loc) {
+		if (loc.page == 0) {
+			if (loc.chapter > 0) {
+				int np = countPages(loc.chapter - 1);
+				return new Location(loc.chapter - 1, np - 1);
+			}
+		} else {
+			return new Location(loc.chapter, loc.page - 1);
+		}
+		return loc;
+	}
+
+	public Location clampLocation(Location input) {
+		int c = input.chapter;
+		int p = input.page;
+		int nc = countChapters();
+		if (c < 0) c = 0;
+		if (c >= nc) c = nc - 1;
+		int np = countPages(c);
+		if (p < 0) p = 0;
+		if (p >= np) p = np - 1;
+		if (input.chapter == c && input.page == p)
+			return input;
+		return new Location(c, p);
+	}
+
+	public Location locationFromPageNumber(int number) {
+		int i, start = 0, np = 0, nc = countChapters();
+		if (number < 0)
+			number = 0;
+		for (i = 0; i < nc; ++i)
+		{
+			np = countPages(i);
+			if (number < start + np)
+				return new Location(i, number - start);
+			start += np;
+		}
+		return new Location(start, np - 1);
+	}
+
+	public int pageNumberFromLocation(Location loc) {
+		int nc = countChapters();
+		int start = 0;
+		for (int i = 0; i < nc; ++i) {
+			if (i == loc.chapter)
+				return start + loc.page;
+			start += countPages(i);
+		}
+		return -1;
+	}
+
+	public native Location resolveLink(String uri);
+	public Location resolveLink(Outline link) {
+		return resolveLink(link.uri);
+	}
+	public Location resolveLink(Link link) {
+		return resolveLink(link.uri);
+	}
+
 	public native Outline[] loadOutline();
 	public native String getMetaData(String key);
 	public native boolean isReflowable();
 	public native void layout(float width, float height, float em);
 
-	public native long makeBookmark(int page);
-	public native int findBookmark(long mark);
+	public native Location findBookmark(long mark);
+	public native long makeBookmark(int chapter, int page);
+	public long makeBookmark(Location loc) {
+		return makeBookmark(loc.chapter, loc.page);
+	}
 
 	public native boolean isUnencryptedPDF();
 
diff --git a/platform/java/src/com/artifex/mupdf/fitz/Link.java b/platform/java/src/com/artifex/mupdf/fitz/Link.java
index 1f7eddb..5e30598 100644
--- a/platform/java/src/com/artifex/mupdf/fitz/Link.java
+++ b/platform/java/src/com/artifex/mupdf/fitz/Link.java
@@ -3,16 +3,14 @@ package com.artifex.mupdf.fitz;
 public class Link
 {
 	public Rect bounds;
-	public int page;
 	public String uri;
 
-	public Link(Rect bounds, int page, String uri) {
+	public Link(Rect bounds, String uri) {
 		this.bounds = bounds;
-		this.page = page;
 		this.uri = uri;
 	}
 
 	public String toString() {
-		return "Link(b="+bounds+",page="+page+",uri="+uri+")";
+		return "Link(bounds="+bounds+",uri="+uri+")";
 	}
 }
diff --git a/platform/java/src/com/artifex/mupdf/fitz/Location.java b/platform/java/src/com/artifex/mupdf/fitz/Location.java
new file mode 100644
index 0000000..0476111
--- /dev/null
+++ b/platform/java/src/com/artifex/mupdf/fitz/Location.java
@@ -0,0 +1,19 @@
+package com.artifex.mupdf.fitz;
+
+public final class Location
+{
+	public final int chapter;
+	public final int page;
+	public final float x, y;
+	public Location(int chapter, int page) {
+		this.chapter = chapter;
+		this.page = page;
+		this.x = this.y = 0;
+	}
+	public Location(int chapter, int page, float x, float y) {
+		this.chapter = chapter;
+		this.page = page;
+		this.x = x;
+		this.y = y;
+	}
+}
diff --git a/platform/java/src/com/artifex/mupdf/fitz/Outline.java b/platform/java/src/com/artifex/mupdf/fitz/Outline.java
index f230118..13e2a52 100644
--- a/platform/java/src/com/artifex/mupdf/fitz/Outline.java
+++ b/platform/java/src/com/artifex/mupdf/fitz/Outline.java
@@ -4,26 +4,18 @@ public class Outline
 {
 	public String title;
 	public String uri;
-	public int page;
 	public Outline[] down;
-	public float x;
-	public float y;
 
-	public Outline(String title, int page, String uri, float x, float y, Outline[] down) {
+	public Outline(String title, String uri, Outline[] down) {
 		this.title = title;
-		this.page = page;
 		this.uri = uri;
 		this.down = down;
-		this.x = x;
-		this.y = y;
 	}
 
 	public String toString()
 	{
 		StringBuffer s = new StringBuffer();
 
-		s.append(page);
-		s.append(": ");
 		s.append(title);
 		s.append(' ');
 		s.append(uri);
diff --git a/platform/x11/pdfapp.c b/platform/x11/pdfapp.c
index ef7d174..b3d6ef4 100644
--- a/platform/x11/pdfapp.c
+++ b/platform/x11/pdfapp.c
@@ -1230,22 +1230,22 @@ void pdfapp_onkey(pdfapp_t *app, int c, int modifiers)
 	case '<':
 		if (app->layout_em > 6)
 		{
-			fz_bookmark mark = fz_make_bookmark(app->ctx, app->doc, app->pageno);
+			fz_bookmark mark = fz_make_bookmark(app->ctx, app->doc, fz_location_from_page_number(app->ctx, app->doc, app->pageno));
 			app->layout_em -= 1;
 			fz_layout_document(app->ctx, app->doc, app->layout_w, app->layout_h, app->layout_em);
 			app->pagecount = fz_count_pages(app->ctx, app->doc);
-			app->pageno = fz_lookup_bookmark(app->ctx, app->doc, mark);
+			app->pageno = fz_page_number_from_location(app->ctx, app->doc, fz_lookup_bookmark(app->ctx, app->doc, mark));
 			pdfapp_showpage(app, 1, 1, 1, 0, 0);
 		}
 		break;
 	case '>':
 		if (app->layout_em < 36)
 		{
-			fz_bookmark mark = fz_make_bookmark(app->ctx, app->doc, app->pageno);
+			fz_bookmark mark = fz_make_bookmark(app->ctx, app->doc, fz_location_from_page_number(app->ctx, app->doc, app->pageno));
 			app->layout_em += 1;
 			fz_layout_document(app->ctx, app->doc, app->layout_w, app->layout_h, app->layout_em);
 			app->pagecount = fz_count_pages(app->ctx, app->doc);
-			app->pageno = fz_lookup_bookmark(app->ctx, app->doc, mark);
+			app->pageno = fz_page_number_from_location(app->ctx, app->doc, fz_lookup_bookmark(app->ctx, app->doc, mark));
 			pdfapp_showpage(app, 1, 1, 1, 0, 0);
 		}
 		break;
@@ -1714,7 +1714,10 @@ void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int sta
 			if (fz_is_external_link(ctx, link->uri))
 				pdfapp_gotouri(app, link->uri);
 			else
-				pdfapp_gotopage(app, fz_resolve_link(ctx, app->doc, link->uri, NULL, NULL) + 1);
+			{
+				fz_location loc = fz_resolve_link(ctx, app->doc, link->uri, NULL, NULL);
+				pdfapp_gotopage(app, fz_page_number_from_location(ctx, app->doc, loc)+1);
+			}
 			return;
 		}
 	}
diff --git a/source/cbz/mucbz.c b/source/cbz/mucbz.c
index 7040f82..89c687f 100644
--- a/source/cbz/mucbz.c
+++ b/source/cbz/mucbz.c
@@ -130,7 +130,7 @@ cbz_drop_document(fz_context *ctx, fz_document *doc_)
 }
 
 static int
-cbz_count_pages(fz_context *ctx, fz_document *doc_)
+cbz_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
 {
 	cbz_document *doc = (cbz_document*)doc_;
 	return doc->page_count;
@@ -175,7 +175,7 @@ cbz_drop_page(fz_context *ctx, fz_page *page_)
 }
 
 static fz_page *
-cbz_load_page(fz_context *ctx, fz_document *doc_, int number)
+cbz_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
 {
 	cbz_document *doc = (cbz_document*)doc_;
 	cbz_page *page = NULL;
@@ -271,5 +271,7 @@ fz_document_handler cbz_document_handler =
 	NULL,
 	cbz_open_document_with_stream,
 	cbz_extensions,
-	cbz_mimetypes
+	cbz_mimetypes,
+	NULL,
+	NULL
 };
diff --git a/source/cbz/muimg.c b/source/cbz/muimg.c
index baaef53..600365f 100644
--- a/source/cbz/muimg.c
+++ b/source/cbz/muimg.c
@@ -30,7 +30,7 @@ img_drop_document(fz_context *ctx, fz_document *doc_)
 }
 
 static int
-img_count_pages(fz_context *ctx, fz_document *doc_)
+img_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
 {
 	img_document *doc = (img_document*)doc_;
 	return doc->page_count;
@@ -74,7 +74,7 @@ img_drop_page(fz_context *ctx, fz_page *page_)
 }
 
 static fz_page *
-img_load_page(fz_context *ctx, fz_document *doc_, int number)
+img_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
 {
 	img_document *doc = (img_document*)doc_;
 	fz_pixmap *pixmap = NULL;
@@ -247,5 +247,7 @@ fz_document_handler img_document_handler =
 	NULL,
 	img_open_document_with_stream,
 	img_extensions,
-	img_mimetypes
+	img_mimetypes,
+	NULL,
+	NULL
 };
diff --git a/source/fitz/document.c b/source/fitz/document.c
index a4c5f34..c627749 100644
--- a/source/fitz/document.c
+++ b/source/fitz/document.c
@@ -150,7 +150,7 @@ extern fz_document_handler pdf_document_handler;
 	magic: a string used to detect document type; either a file name or mime-type.
 */
 fz_document *
-fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream)
+fz_open_accelerated_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream, fz_stream *accel)
 {
 	const fz_document_handler *handler;
 
@@ -164,13 +164,36 @@ fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stre
 #else
 		fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find document handler for file type: %s", magic);
 #endif
-
+	if (handler->open_accel_with_stream)
+		if (accel || handler->open_with_stream == NULL)
+			return handler->open_accel_with_stream(ctx, stream, accel);
+	if (accel)
+	{
+		/* We've had an accelerator passed to a format that doesn't
+		 * handle it. This should never happen, as how did the
+		 * accelerator get created? */
+		fz_drop_stream(ctx, accel);
+	}
 	return handler->open_with_stream(ctx, stream);
 }
 
 /*
 	Open a PDF, XPS or CBZ document.
 
+	Open a document using the specified stream object rather than
+	opening a file on disk.
+
+	magic: a string used to detect document type; either a file name or mime-type.
+*/
+fz_document *
+fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream)
+{
+	return fz_open_accelerated_document_with_stream(ctx, magic, stream, NULL);
+}
+
+/*
+	Open a PDF, XPS or CBZ document.
+
 	Open a document file and read its basic structure so pages and
 	objects can be located. MuPDF will try to repair broken
 	documents (without actually changing the file contents).
@@ -181,12 +204,15 @@ fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stre
 	filename: a path to a file as it would be given to open(2).
 */
 fz_document *
-fz_open_document(fz_context *ctx, const char *filename)
+fz_open_accelerated_document(fz_context *ctx, const char *filename, const char *accel)
 {
 	const fz_document_handler *handler;
 	fz_stream *file;
+	fz_stream *afile = NULL;
 	fz_document *doc = NULL;
 
+	fz_var(afile);
+
 	if (filename == NULL)
 		fz_throw(ctx, FZ_ERROR_GENERIC, "no document to open");
 
@@ -198,21 +224,91 @@ fz_open_document(fz_context *ctx, const char *filename)
 		fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find document handler for file: %s", filename);
 #endif
 
-	if (handler->open)
+	if (accel) {
+		if (handler->open_accel)
+			return handler->open_accel(ctx, filename, accel);
+		if (handler->open_accel_with_stream == NULL)
+		{
+			/* We're not going to be able to use the accelerator - this
+			 * should never happen, as how can one have been created? */
+			accel = NULL;
+		}
+	}
+	if (!accel && handler->open)
 		return handler->open(ctx, filename);
 
 	file = fz_open_file(ctx, filename);
 
 	fz_try(ctx)
-		doc = handler->open_with_stream(ctx, file);
+	{
+		if (accel || handler->open_with_stream == NULL)
+		{
+			if (accel)
+				afile = fz_open_file(ctx, accel);
+			doc = handler->open_accel_with_stream(ctx, file, afile);
+		}
+		else
+			doc = handler->open_with_stream(ctx, file);
+	}
 	fz_always(ctx)
+	{
+		fz_drop_stream(ctx, afile);
 		fz_drop_stream(ctx, file);
+	}
 	fz_catch(ctx)
 		fz_rethrow(ctx);
 
 	return doc;
 }
 
+/*
+	Open a PDF, XPS or CBZ document.
+
+	Open a document file and read its basic structure so pages and
+	objects can be located. MuPDF will try to repair broken
+	documents (without actually changing the file contents).
+
+	The returned fz_document is used when calling most other
+	document related functions.
+
+	filename: a path to a file as it would be given to open(2).
+*/
+fz_document *
+fz_open_document(fz_context *ctx, const char *filename)
+{
+	return fz_open_accelerated_document(ctx, filename, NULL);
+}
+
+void fz_save_accelerator(fz_context *ctx, fz_document *doc, const char *accel)
+{
+	if (doc == NULL)
+		return;
+	if (doc->output_accelerator == NULL)
+		return;
+
+	fz_output_accelerator(ctx, doc, fz_new_output_with_path(ctx, accel, 0));
+}
+
+void fz_output_accelerator(fz_context *ctx, fz_document *doc, fz_output *accel)
+{
+	if (doc == NULL || accel == NULL)
+		return;
+	if (doc->output_accelerator == NULL)
+	{
+		fz_drop_output(ctx, accel);
+		fz_throw(ctx, FZ_ERROR_GENERIC, "Document does not support writing an accelerator");
+	}
+
+	doc->output_accelerator(ctx, doc, accel);
+}
+
+int fz_document_supports_accelerator(fz_context *ctx, fz_document *doc)
+{
+	if (doc == NULL)
+		return 0;
+	return (doc->output_accelerator) != NULL;
+}
+
 void *
 fz_new_document_of_size(fz_context *ctx, int size)
 {
@@ -264,21 +360,21 @@ fz_is_document_reflowable(fz_context *ctx, fz_document *doc)
 	same location after the document has been laid out with different
 	parameters.
 */
-fz_bookmark fz_make_bookmark(fz_context *ctx, fz_document *doc, int page)
+fz_bookmark fz_make_bookmark(fz_context *ctx, fz_document *doc, fz_location loc)
 {
 	if (doc && doc->make_bookmark)
-		return doc->make_bookmark(ctx, doc, page);
-	return (fz_bookmark)page;
+		return doc->make_bookmark(ctx, doc, loc);
+	return (loc.chapter<<16) + loc.page;
 }
 
 /*
 	Find a bookmark and return its page number.
 */
-int fz_lookup_bookmark(fz_context *ctx, fz_document *doc, fz_bookmark mark)
+fz_location fz_lookup_bookmark(fz_context *ctx, fz_document *doc, fz_bookmark mark)
 {
 	if (doc && doc->lookup_bookmark)
 		return doc->lookup_bookmark(ctx, doc, mark);
-	return (int)mark;
+	return fz_make_location((mark>>16) & 0xffff, mark & 0xffff);
 }
 
 /*
@@ -348,9 +444,9 @@ fz_load_outline(fz_context *ctx, fz_document *doc)
 
 	xp, yp: Pointer to store coordinate of destination on the page.
 
-	Returns -1 if the URI cannot be resolved.
+	Returns (-1,-1) if the URI cannot be resolved.
 */
-int
+fz_location
 fz_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp)
 {
 	fz_ensure_layout(ctx, doc);
@@ -358,7 +454,7 @@ fz_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, f
 	if (yp) *yp = 0;
 	if (doc && doc->resolve_link)
 		return doc->resolve_link(ctx, doc, uri, xp, yp);
-	return -1;
+	return fz_make_location(-1, -1);
 }
 
 /*
@@ -378,19 +474,145 @@ fz_layout_document(fz_context *ctx, fz_document *doc, float w, float h, float em
 }
 
 /*
-	Return the number of pages in document
+	Return the number of chapters in the document.
+	At least 1.
+*/
+int
+fz_count_chapters(fz_context *ctx, fz_document *doc)
+{
+	fz_ensure_layout(ctx, doc);
+	if (doc && doc->count_chapters)
+		return doc->count_chapters(ctx, doc);
+	return 1;
+}
 
-	May return 0 for documents with no pages.
+/*
+	Return the number of pages in a chapter.
+	May return 0.
 */
 int
-fz_count_pages(fz_context *ctx, fz_document *doc)
+fz_count_chapter_pages(fz_context *ctx, fz_document *doc, int chapter)
 {
 	fz_ensure_layout(ctx, doc);
 	if (doc && doc->count_pages)
-		return doc->count_pages(ctx, doc);
+		return doc->count_pages(ctx, doc, chapter);
 	return 0;
 }
 
+
+/*
+	Return the number of pages in document
+
+	May return 0 for documents with no pages.
+*/
+int
+fz_count_pages(fz_context *ctx, fz_document *doc)
+{
+	int i, c, n = 0;
+	c = fz_count_chapters(ctx, doc);
+	for (i = 0; i < c; ++i)
+		n += fz_count_chapter_pages(ctx, doc, i);
+	return n;
+}
+
+fz_page *
+fz_load_page(fz_context *ctx, fz_document *doc, int number)
+{
+	int i, n = fz_count_chapters(ctx, doc);
+	int start = 0;
+	for (i = 0; i < n; ++i)
+	{
+		int m = fz_count_chapter_pages(ctx, doc, i);
+		if (number < start + m)
+			return fz_load_chapter_page(ctx, doc, i, number - start);
+		start += m;
+	}
+	fz_throw(ctx, FZ_ERROR_GENERIC, "Page not found: %d", number+1);
+}
+
+fz_location fz_last_page(fz_context *ctx, fz_document *doc)
+{
+	int nc = fz_count_chapters(ctx, doc);
+	int np = fz_count_chapter_pages(ctx, doc, nc-1);
+	return fz_make_location(nc-1, np-1);
+}
+
+fz_location fz_next_page(fz_context *ctx, fz_document *doc, fz_location loc)
+{
+	int nc = fz_count_chapters(ctx, doc);
+	int np = fz_count_chapter_pages(ctx, doc, loc.chapter);
+	if (loc.page + 1 == np)
+	{
+		if (loc.chapter + 1 < nc)
+		{
+			return fz_make_location(loc.chapter + 1, 0);
+		}
+	}
+	else
+	{
+		return fz_make_location(loc.chapter, loc.page + 1);
+	}
+	return loc;
+}
+
+fz_location fz_previous_page(fz_context *ctx, fz_document *doc, fz_location loc)
+{
+	if (loc.page == 0)
+	{
+		if (loc.chapter > 0)
+		{
+			int np = fz_count_chapter_pages(ctx, doc, loc.chapter - 1);
+			return fz_make_location(loc.chapter - 1, np - 1);
+		}
+	}
+	else
+	{
+		return fz_make_location(loc.chapter, loc.page - 1);
+	}
+	return loc;
+}
+
+fz_location fz_clamp_location(fz_context *ctx, fz_document *doc, fz_location loc)
+{
+	int nc = fz_count_chapters(ctx, doc);
+	int np;
+	if (loc.chapter < 0) loc.chapter = 0;
+	if (loc.chapter >= nc) loc.chapter = nc - 1;
+	np = fz_count_chapter_pages(ctx, doc, loc.chapter);
+	if (loc.page < 0) loc.page = 0;
+	if (loc.page >= np) loc.page = np - 1;
+	return loc;
+}
+
+fz_location fz_location_from_page_number(fz_context *ctx, fz_document *doc, int number)
+{
+	int i, m = 0, n = fz_count_chapters(ctx, doc);
+	int start = 0;
+	if (number < 0)
+		number = 0;
+	for (i = 0; i < n; ++i)
+	{
+		m = fz_count_chapter_pages(ctx, doc, i);
+		if (number < start + m)
+			return fz_make_location(i, number - start);
+		start += m;
+	}
+	return fz_make_location(start, m-1);
+}
+
+int fz_page_number_from_location(fz_context *ctx, fz_document *doc, fz_location loc)
+{
+	int i, n, start = 0;
+	n = fz_count_chapters(ctx, doc);
+	for (i = 0; i < n; ++i)
+	{
+		if (i == loc.chapter)
+			return start + loc.page;
+		start += fz_count_chapter_pages(ctx, doc, i);
+	}
+	return -1;
+}
+
 /*
 	Retrieve document meta data strings.
 
@@ -447,10 +669,11 @@ fz_document_output_intent(fz_context *ctx, fz_document *doc)
 	page using fz_bound_page, or to render the page using
 	fz_run_page_*. Free the page by calling fz_drop_page.
 
-	number: page number, 0 is the first page of the document.
+	chapter: chapter number, 0 is the first chapter of the document.
+	number: page number, 0 is the first page of the chapter.
 */
 fz_page *
-fz_load_page(fz_context *ctx, fz_document *doc, int number)
+fz_load_chapter_page(fz_context *ctx, fz_document *doc, int chapter, int number)
 {
 	fz_page *page;
 
@@ -458,12 +681,13 @@ fz_load_page(fz_context *ctx, fz_document *doc, int number)
 
 	if (doc)
 		for (page = doc->open; page; page = page->next)
-			if (page->number == number)
+			if (page->chapter == chapter && page->number == number)
 				return fz_keep_page(ctx, page);
 
 	if (doc && doc->load_page)
 	{
-		page = doc->load_page(ctx, doc, number);
+		page = doc->load_page(ctx, doc, chapter, number);
+		page->chapter = chapter;
 		page->number = number;
 
 		/* Insert new page at the head of the list of open pages. */
diff --git a/source/fitz/util.c b/source/fitz/util.c
index e20da80..fb8ca5b 100644
--- a/source/fitz/util.c
+++ b/source/fitz/util.c
@@ -369,6 +369,22 @@ fz_search_page_number(fz_context *ctx, fz_document *doc, int number, const char
 	return count;
 }
 
+int
+fz_search_chapter_page_number(fz_context *ctx, fz_document *doc, int chapter, int number, const char *needle, fz_quad *hit_bbox, int hit_max)
+{
+	fz_page *page;
+	int count = 0;
+
+	page = fz_load_chapter_page(ctx, doc, chapter, number);
+	fz_try(ctx)
+		count = fz_search_page(ctx, page, needle, hit_bbox, hit_max);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
 /*
 	Convert structured text into plain text.
 */
diff --git a/source/fitz/xml.c b/source/fitz/xml.c
index 9279267..d1a5ec8 100644
--- a/source/fitz/xml.c
+++ b/source/fitz/xml.c
@@ -472,7 +472,6 @@ static void xml_emit_close_tag(fz_context *ctx, struct parser *parser)
 
 static void xml_emit_text(fz_context *ctx, struct parser *parser, char *a, char *b)
 {
-	static char *empty = "";
 	fz_xml *head;
 	char *s;
 	int c;
@@ -512,7 +511,6 @@ static void xml_emit_text(fz_context *ctx, struct parser *parser, char *a, char
 
 static void xml_emit_cdata(fz_context *ctx, struct parser *parser, char *a, char *b)
 {
-	static char *empty = "";
 	fz_xml *head;
 	char *s;
 
diff --git a/source/html/epub-doc.c b/source/html/epub-doc.c
index 5c283df..2135aca 100644
--- a/source/html/epub-doc.c
+++ b/source/html/epub-doc.c
@@ -3,12 +3,16 @@
 
 #include <string.h>
 #include <math.h>
+#include <assert.h>
+
+#include <zlib.h> /* for crc32 */
 
 enum { T, R, B, L };
 
 typedef struct epub_document_s epub_document;
 typedef struct epub_chapter_s epub_chapter;
 typedef struct epub_page_s epub_page;
+typedef struct epub_accelerator_s epub_accelerator;
 
 struct epub_document_s
 {
@@ -19,13 +23,26 @@ struct epub_document_s
 	epub_chapter *spine;
 	fz_outline *outline;
 	char *dc_title, *dc_creator;
+	float layout_w, layout_h, layout_em;
+	epub_accelerator *accel;
+	uint32_t css_sum;
+
+	/* A common pattern of use is for us to open a document,
+	 * load a page, draw it, drop it, load the next page,
+	 * draw it, drop it etc. This means that the HTML for
+	 * a chapter might get thrown away between the drop and
+	 * the the next load (if the chapter is large, and the
+	 * store size is low). Accordingly, we store a handle
+	 * to the most recently used html block here, thus
+	 * ensuring that the stored copy won't be evicted. */
+	fz_html *most_recent_html;
 };
 
 struct epub_chapter_s
 {
+	epub_document *doc;
 	char *path;
-	int start;
-	fz_html *html;
+	int number;
 	epub_chapter *next;
 };
 
@@ -33,140 +50,392 @@ struct epub_page_s
 {
 	fz_page super;
 	epub_document *doc;
+	epub_chapter *ch;
 	int number;
+	fz_html *html;
+};
+
+struct epub_accelerator_s
+{
+	int max_chapters;
+	int num_chapters;
+	float layout_w;
+	float layout_h;
+	float layout_em;
+	uint32_t css_sum;
+	int use_doc_css;
+	int *pages_in_chapter;
 };
 
-static int count_chapter_pages(epub_chapter *ch)
+static uint32_t
+user_css_sum(fz_context *ctx)
 {
-	if (ch->html->root->b > 0)
-		return ceilf(ch->html->root->b / ch->html->page_h);
+	uint32_t sum = 0;
+	const char *css = fz_user_css(ctx);
+	sum = crc32(0, NULL, 0);
+	if (css)
+		sum = crc32(sum, (Byte*)css, strlen(css));
+	return sum;
+}
+
+static fz_html *epub_get_laid_out_html(fz_context *ctx, epub_document *doc, epub_chapter *ch);
+
+static int count_laid_out_pages(fz_html *html)
+{
+	if (html->root->b > 0)
+		return ceilf(html->root->b / html->page_h);
 	return 1;
 }
 
-static int
+static void
+invalidate_accelerator(fz_context *ctx, epub_accelerator *acc)
+{
+	int i;
+
+	for (i = 0; i < acc->max_chapters; i++)
+		acc->pages_in_chapter[i] = -1;
+}
+
+static int count_chapter_pages(fz_context *ctx, epub_document *doc, epub_chapter *ch)
+{
+	epub_accelerator *acc = doc->accel;
+	int use_doc_css = fz_use_document_css(ctx);
+
+	if (use_doc_css != acc->use_doc_css || doc->css_sum != acc->css_sum)
+	{
+		acc->use_doc_css = use_doc_css;
+		acc->css_sum = doc->css_sum;
+		invalidate_accelerator(ctx, acc);
+	}
+
+	if (ch->number < acc->num_chapters && acc->pages_in_chapter[ch->number] != -1)
+		return acc->pages_in_chapter[ch->number];
+
+	fz_drop_html(ctx, epub_get_laid_out_html(ctx, doc, ch));
+	return acc->pages_in_chapter[ch->number];
+}
+
+static fz_location
 epub_resolve_link(fz_context *ctx, fz_document *doc_, const char *dest, float *xp, float *yp)
 {
 	epub_document *doc = (epub_document*)doc_;
 	epub_chapter *ch;
+	int i;
 
 	const char *s = strchr(dest, '#');
 	size_t n = s ? (size_t)(s - dest) : strlen(dest);
 	if (s && s[1] == 0)
 		s = NULL;
 
-	for (ch = doc->spine; ch; ch = ch->next)
+	for (i = 0, ch = doc->spine; ch; ++i, ch = ch->next)
 	{
 		if (!strncmp(ch->path, dest, n) && ch->path[n] == 0)
 		{
 			if (s)
 			{
+				float y;
+				fz_html *html = epub_get_laid_out_html(ctx, doc, ch);
+				int ph = html->page_h;
+
 				/* Search for a matching fragment */
-				float y = fz_find_html_target(ctx, ch->html, s+1);
+				y = fz_find_html_target(ctx, html, s+1);
+				fz_drop_html(ctx, html);
 				if (y >= 0)
 				{
-					int page = y / ch->html->page_h;
-					if (yp) *yp = y - page * ch->html->page_h;
-					return ch->start + page;
+					int page = y / ph;
+					if (yp) *yp = y - page * ph;
+					return fz_make_location(i, page);
 				}
-				return -1;
+				return fz_make_location(-1, -1);
 			}
-			return ch->start;
+			return fz_make_location(i, 0);
 		}
 	}
 
-	return -1;
+	return fz_make_location(-1, -1);
 }
 
 static void
-epub_update_outline(fz_context *ctx, fz_document *doc, fz_outline *node)
+epub_layout(fz_context *ctx, fz_document *doc_, float w, float h, float em)
 {
-	while (node)
-	{
-		node->page = epub_resolve_link(ctx, doc, node->uri, &node->x, &node->y);
-		epub_update_outline(ctx, doc, node->down);
-		node = node->next;
-	}
+	epub_document *doc = (epub_document*)doc_;
+	uint32_t css_sum = user_css_sum(ctx);
+	int use_doc_css = fz_use_document_css(ctx);
+
+	if (doc->layout_w == w && doc->layout_h == h && doc->layout_em == em && doc->css_sum == css_sum)
+		return;
+	doc->layout_w = w;
+	doc->layout_h = h;
+	doc->layout_em = em;
+
+	if (doc->accel == NULL)
+		return;
+
+	/* When we load the saved accelerator, doc->accel
+	 * can be populated with different values than doc.
+	 * This is really useful as doc starts out with the
+	 * values being 0. If we've got the right values
+	 * already, then don't bin the data! */
+	if (doc->accel->layout_w == w &&
+		doc->accel->layout_h == h &&
+		doc->accel->layout_em == em &&
+		doc->accel->use_doc_css == use_doc_css &&
+		doc->accel->css_sum == css_sum)
+		return;
+
+	doc->accel->layout_w = w;
+	doc->accel->layout_h = h;
+	doc->accel->layout_em = em;
+	doc->accel->use_doc_css = use_doc_css;
+	doc->accel->css_sum = css_sum;
+	invalidate_accelerator(ctx, doc->accel);
 }
 
-static void
-epub_layout(fz_context *ctx, fz_document *doc_, float w, float h, float em)
+static int
+epub_count_chapters(fz_context *ctx, fz_document *doc_)
 {
 	epub_document *doc = (epub_document*)doc_;
 	epub_chapter *ch;
 	int count = 0;
-
 	for (ch = doc->spine; ch; ch = ch->next)
-	{
-		ch->start = count;
-		fz_layout_html(ctx, ch->html, w, h, em);
-		count += count_chapter_pages(ch);
-	}
-
-	epub_update_outline(ctx, doc_, doc->outline);
+		++count;
+	return count;
 }
 
 static int
-epub_count_pages(fz_context *ctx, fz_document *doc_)
+epub_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
 {
 	epub_document *doc = (epub_document*)doc_;
 	epub_chapter *ch;
-	int count = 0;
-	for (ch = doc->spine; ch; ch = ch->next)
-		count += count_chapter_pages(ch);
-	return count;
+	int i;
+	for (i = 0, ch = doc->spine; ch; ++i, ch = ch->next)
+	{
+		if (i == chapter)
+		{
+			return count_chapter_pages(ctx, doc, ch);
+		}
+	}
+	return 0;
+}
+
+#define MAGIC_ACCELERATOR 0xacce1e7a
+#define MAGIC_ACCEL_EPUB  0x62755065
+#define ACCEL_VERSION     0x00010001
+
+static void epub_load_accelerator(fz_context *ctx, epub_document *doc, fz_stream *accel)
+{
+	int v;
+	float w, h, em;
+	int num_chapters;
+	epub_accelerator *acc = NULL;
+	uint32_t css_sum;
+	int use_doc_css;
+	int make_new = (accel == NULL);
+
+	fz_var(acc);
+
+	if (accel)
+	{
+		/* Try to read the accelerator data. If we fail silently give up. */
+		fz_try(ctx)
+		{
+			v = fz_read_int32_le(ctx, accel);
+			if (v != (int32_t)MAGIC_ACCELERATOR)
+			{
+				make_new = 1;
+				break;
+			}
+
+			v = fz_read_int32_le(ctx, accel);
+			if (v != MAGIC_ACCEL_EPUB)
+			{
+				make_new = 1;
+				break;
+			}
+
+			v = fz_read_int32_le(ctx, accel);
+			if (v != ACCEL_VERSION)
+			{
+				make_new = 1;
+				break;
+			}
+
+			w = fz_read_float_le(ctx, accel);
+			h = fz_read_float_le(ctx, accel);
+			em = fz_read_float_le(ctx, accel);
+			css_sum = fz_read_uint32_le(ctx, accel);
+			use_doc_css = fz_read_int32_le(ctx, accel);
+
+			num_chapters = fz_read_int32_le(ctx, accel);
+			if (num_chapters <= 0)
+			{
+				make_new = 1;
+				break;
+			}
+
+			acc = fz_malloc_struct(ctx, epub_accelerator);
+			acc->pages_in_chapter = fz_malloc_array(ctx, num_chapters, int);
+			acc->max_chapters = acc->num_chapters = num_chapters;
+			acc->layout_w = w;
+			acc->layout_h = h;
+			acc->layout_em = em;
+			acc->css_sum = css_sum;
+			acc->use_doc_css = use_doc_css;
+
+			for (v = 0; v < num_chapters; v++)
+				acc->pages_in_chapter[v] = fz_read_int32_le(ctx, accel);
+		}
+		fz_catch(ctx)
+		{
+			if (acc)
+				fz_free(ctx, acc->pages_in_chapter);
+			fz_free(ctx, acc);
+			/* Swallow the error and run unaccelerated */
+			make_new = 1;
+		}
+	}
+
+	/* If we aren't given an accelerator to load (or the one we're given
+	 * is bad) create a blank stub and we can fill it out as we go. */
+	if (make_new)
+	{
+		acc = fz_malloc_struct(ctx, epub_accelerator);
+		acc->css_sum = doc->css_sum;
+		acc->use_doc_css = fz_use_document_css(ctx);
+	}
+
+	doc->accel = acc;
+}
+
+static void
+accelerate_chapter(fz_context *ctx, epub_document *doc, epub_chapter *ch, fz_html *html)
+{
+	epub_accelerator *acc = doc->accel;
+	int p = count_laid_out_pages(html);
+
+	if (ch->number < acc->num_chapters)
+	{
+		assert(acc->pages_in_chapter[ch->number] == p || acc->pages_in_chapter[ch->number] == -1);
+		acc->pages_in_chapter[ch->number] = p;
+		return;
+	}
+
+	if (ch->number >= acc->max_chapters)
+	{
+		int n  = acc->max_chapters * 2;
+		int i;
+		if (n == 0)
+			n = 4;
+		while (n < ch->number)
+			n *= 2;
+
+		acc->pages_in_chapter = fz_realloc_array(ctx, acc->pages_in_chapter, n, int);
+		for (i = acc->max_chapters; i < n; i++)
+			acc->pages_in_chapter[i] = -1;
+		acc->max_chapters = n;
+	}
+	acc->pages_in_chapter[ch->number] = p;
+	if (acc->num_chapters < ch->number+1)
+		acc->num_chapters = ch->number+1;
 }
 
 static void
 epub_drop_page(fz_context *ctx, fz_page *page_)
 {
+	epub_page *page = (epub_page *)page_;
+	fz_drop_document(ctx, &page->doc->super);
+	fz_drop_html(ctx, page->html);
 }
 
-static fz_rect
-epub_bound_page(fz_context *ctx, fz_page *page_)
+static epub_chapter *
+epub_load_chapter(fz_context *ctx, epub_document *doc, const char *path, int i)
 {
-	epub_page *page = (epub_page*)page_;
-	epub_document *doc = page->doc;
 	epub_chapter *ch;
-	int n = page->number;
-	int count = 0;
-	fz_rect bbox;
 
-	for (ch = doc->spine; ch; ch = ch->next)
+	ch = fz_malloc_struct(ctx, epub_chapter);
+	fz_try(ctx)
 	{
-		int cn = count_chapter_pages(ch);
-		if (n < count + cn)
-		{
-			bbox.x0 = 0;
-			bbox.y0 = 0;
-			bbox.x1 = ch->html->page_w + ch->html->page_margin[L] + ch->html->page_margin[R];
-			bbox.y1 = ch->html->page_h + ch->html->page_margin[T] + ch->html->page_margin[B];
-			return bbox;
-		}
-		count += cn;
+		ch->path = Memento_label(fz_strdup(ctx, path), "chapter_path");
+		ch->number = i;
+	}
+	fz_catch(ctx)
+	{
+		fz_free(ctx, ch);
+		fz_rethrow(ctx);
+	}
+
+	return ch;
+}
+
+static fz_html *
+epub_parse_chapter(fz_context *ctx, epub_document *doc, epub_chapter *ch)
+{
+	fz_archive *zip = doc->zip;
+	fz_buffer *buf;
+	char base_uri[2048];
+	fz_html *html;
+
+	/* Look for one we made earlier */
+	html = fz_find_html(ctx, doc, ch->number);
+	if (html)
+		return html;
+
+	fz_dirname(base_uri, ch->path, sizeof base_uri);
+
+	buf = fz_read_archive_entry(ctx, zip, ch->path);
+	fz_try(ctx)
+		html = fz_parse_html(ctx, doc->set, zip, base_uri, buf, fz_user_css(ctx));
+	fz_always(ctx)
+		fz_drop_buffer(ctx, buf);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+
+	return fz_store_html(ctx, html, doc, ch->number);
+}
+
+static fz_html *
+epub_get_laid_out_html(fz_context *ctx, epub_document *doc, epub_chapter *ch)
+{
+	fz_html *html = epub_parse_chapter(ctx, doc, ch);
+	fz_try(ctx)
+	{
+		fz_layout_html(ctx, html, doc->layout_w, doc->layout_h, doc->layout_em);
+		accelerate_chapter(ctx, doc, ch, html);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_html(ctx, html);
+		fz_rethrow(ctx);
 	}
 
-	return fz_unit_rect;
+	fz_drop_html(ctx, doc->most_recent_html);
+	doc->most_recent_html = fz_keep_html(ctx, html);
+
+	return html;
+}
+
+static fz_rect
+epub_bound_page(fz_context *ctx, fz_page *page_)
+{
+	epub_page *page = (epub_page*)page_;
+	epub_chapter *ch = page->ch;
+	fz_rect bbox;
+	fz_html *html = epub_get_laid_out_html(ctx, page->doc, ch);
+
+	bbox.x0 = 0;
+	bbox.y0 = 0;
+	bbox.x1 = html->page_w + html->page_margin[L] + html->page_margin[R];
+	bbox.y1 = html->page_h + html->page_margin[T] + html->page_margin[B];
+	fz_drop_html(ctx, html);
+	return bbox;
 }
 
 static void
 epub_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
 {
 	epub_page *page = (epub_page*)page_;
-	epub_document *doc = page->doc;
-	epub_chapter *ch;
-	int n = page->number;
-	int count = 0;
 
-	for (ch = doc->spine; ch; ch = ch->next)
-	{
-		int cn = count_chapter_pages(ch);
-		if (n < count + cn)
-		{
-			fz_draw_html(ctx, dev, ctm, ch->html, n-count);
-			break;
-		}
-		count += cn;
-	}
+	fz_draw_html(ctx, dev, ctm, page->html, page->number);
 }
 
 static fz_link *
@@ -174,66 +443,83 @@ epub_load_links(fz_context *ctx, fz_page *page_)
 {
 	epub_page *page = (epub_page*)page_;
 	epub_document *doc = page->doc;
-	epub_chapter *ch;
-	int n = page->number;
-	int count = 0;
+	epub_chapter *ch = page->ch;
 
-	for (ch = doc->spine; ch; ch = ch->next)
-	{
-		int cn = count_chapter_pages(ch);
-		if (n < count + cn)
-			return fz_load_html_links(ctx, ch->html, n - count, ch->path, doc);
-		count += cn;
-	}
-
-	return NULL;
+	return fz_load_html_links(ctx, page->html, page->number, ch->path, doc);
 }
 
 static fz_bookmark
-epub_make_bookmark(fz_context *ctx, fz_document *doc_, int n)
+epub_make_bookmark(fz_context *ctx, fz_document *doc_, fz_location loc)
 {
 	epub_document *doc = (epub_document*)doc_;
 	epub_chapter *ch;
-	int count = 0;
+	int i;
 
-	for (ch = doc->spine; ch; ch = ch->next)
+	for (i = 0, ch = doc->spine; ch; ++i, ch = ch->next)
 	{
-		int cn = count_chapter_pages(ch);
-		if (n < count + cn)
-			return fz_make_html_bookmark(ctx, ch->html, n - count);
-		count += cn;
+		if (i == loc.chapter)
+		{
+			fz_html *html = epub_get_laid_out_html(ctx, doc, ch);
+			fz_bookmark mark = fz_make_html_bookmark(ctx, html, loc.page);
+			fz_drop_html(ctx, html);
+			return mark;
+		}
 	}
 
 	return 0;
 }
 
-static int
+static fz_location
 epub_lookup_bookmark(fz_context *ctx, fz_document *doc_, fz_bookmark mark)
 {
 	epub_document *doc = (epub_document*)doc_;
 	epub_chapter *ch;
+	int i;
 
-	for (ch = doc->spine; ch; ch = ch->next)
+	for (i = 0, ch = doc->spine; ch; ++i, ch = ch->next)
 	{
-		int p = fz_lookup_html_bookmark(ctx, ch->html, mark);
+		fz_html *html = epub_get_laid_out_html(ctx, doc, ch);
+		int p = fz_lookup_html_bookmark(ctx, html, mark);
+		fz_drop_html(ctx, html);
 		if (p != -1)
-			return ch->start + p;
+			return fz_make_location(i, p);
 	}
-	return -1;
+	return fz_make_location(-1, -1);
 }
 
 static fz_page *
-epub_load_page(fz_context *ctx, fz_document *doc_, int number)
+epub_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
 {
 	epub_document *doc = (epub_document*)doc_;
-	epub_page *page = fz_new_derived_page(ctx, epub_page);
-	page->super.bound_page = epub_bound_page;
-	page->super.run_page_contents = epub_run_page;
-	page->super.load_links = epub_load_links;
-	page->super.drop_page = epub_drop_page;
-	page->doc = doc;
-	page->number = number;
-	return (fz_page*)page;
+	epub_chapter *ch;
+	int i;
+	for (i = 0, ch = doc->spine; ch; ++i, ch = ch->next)
+	{
+		if (i == chapter)
+		{
+			epub_page *page = fz_new_derived_page(ctx, epub_page);
+			page->super.bound_page = epub_bound_page;
+			page->super.run_page_contents = epub_run_page;
+			page->super.load_links = epub_load_links;
+			page->super.drop_page = epub_drop_page;
+			page->doc = (epub_document *)fz_keep_document(ctx, doc_);
+			page->ch = ch;
+			page->number = number;
+			page->html = epub_get_laid_out_html(ctx, doc, ch);
+			return (fz_page*)page;
+		}
+	}
+	return NULL;
+}
+
+static void
+epub_drop_accelerator(fz_context *ctx, epub_accelerator *acc)
+{
+	if (acc == NULL)
+		return;
+
+	fz_free(ctx, acc->pages_in_chapter);
+	fz_free(ctx, acc);
 }
 
 static void
@@ -245,16 +531,18 @@ epub_drop_document(fz_context *ctx, fz_document *doc_)
 	while (ch)
 	{
 		next = ch->next;
-		fz_drop_html(ctx, ch->html);
 		fz_free(ctx, ch->path);
 		fz_free(ctx, ch);
 		ch = next;
 	}
+	epub_drop_accelerator(ctx, doc->accel);
 	fz_drop_archive(ctx, doc->zip);
 	fz_drop_html_font_set(ctx, doc->set);
 	fz_drop_outline(ctx, doc->outline);
 	fz_free(ctx, doc->dc_title);
 	fz_free(ctx, doc->dc_creator);
+	fz_drop_html(ctx, doc->most_recent_html);
+	fz_purge_stored_html(ctx, doc);
 }
 
 static const char *
@@ -289,42 +577,6 @@ path_from_idref(char *path, fz_xml *manifest, const char *base_uri, const char *
 	return fz_cleanname(fz_urldecode(path));
 }
 
-static epub_chapter *
-epub_parse_chapter(fz_context *ctx, epub_document *doc, const char *path)
-{
-	fz_archive *zip = doc->zip;
-	fz_buffer *buf = NULL;
-	epub_chapter *ch;
-	char base_uri[2048];
-
-	fz_dirname(base_uri, path, sizeof base_uri);
-
-	ch = fz_malloc_struct(ctx, epub_chapter);
-	ch->path = NULL;
-	ch->html = NULL;
-	ch->next = NULL;
-
-	fz_var(buf);
-
-	fz_try(ctx)
-	{
-		buf = fz_read_archive_entry(ctx, zip, path);
-		ch->path = Memento_label(fz_strdup(ctx, path), "chapter_path");
-		ch->html = fz_parse_html(ctx, doc->set, zip, base_uri, buf, fz_user_css(ctx));
-	}
-	fz_always(ctx)
-		fz_drop_buffer(ctx, buf);
-	fz_catch(ctx)
-	{
-		fz_drop_html(ctx, ch->html);
-		fz_free(ctx, ch->path);
-		fz_free(ctx, ch);
-		fz_rethrow(ctx);
-	}
-
-	return ch;
-}
-
 static fz_outline *
 epub_parse_ncx_imp(fz_context *ctx, epub_document *doc, fz_xml *node, char *base_uri)
 {
@@ -419,6 +671,7 @@ epub_parse_header(fz_context *ctx, epub_document *doc)
 	const char *version;
 	char ncx[2048], s[2048];
 	epub_chapter **tailp;
+	int i;
 
 	if (fz_has_archive_entry(ctx, zip, "META-INF/rights.xml"))
 		fz_throw(ctx, FZ_ERROR_GENERIC, "EPUB is locked by DRM");
@@ -477,14 +730,16 @@ epub_parse_header(fz_context *ctx, epub_document *doc)
 		doc->spine = NULL;
 		tailp = &doc->spine;
 		itemref = fz_xml_find_down(spine, "itemref");
+		i = 0;
 		while (itemref)
 		{
 			if (path_from_idref(s, manifest, base_uri, fz_xml_att(itemref, "idref"), sizeof s))
 			{
 				fz_try(ctx)
 				{
-					*tailp = epub_parse_chapter(ctx, doc, s);
+					*tailp = epub_load_chapter(ctx, doc, s, i);
 					tailp = &(*tailp)->next;
+					i++;
 				}
 				fz_catch(ctx)
 				{
@@ -525,8 +780,39 @@ epub_lookup_metadata(fz_context *ctx, fz_document *doc_, const char *key, char *
 	return -1;
 }
 
+static void
+epub_output_accelerator(fz_context *ctx, fz_document *doc_, fz_output *out)
+{
+	epub_document *doc = (epub_document*)doc_;
+	int i;
+
+	fz_try(ctx)
+	{
+		if (doc->accel == NULL)
+			fz_throw(ctx, FZ_ERROR_GENERIC, "No accelerator data to write");
+
+		fz_write_int32_le(ctx, out, MAGIC_ACCELERATOR);
+		fz_write_int32_le(ctx, out, MAGIC_ACCEL_EPUB);
+		fz_write_int32_le(ctx, out, ACCEL_VERSION);
+		fz_write_float_le(ctx, out, doc->accel->layout_w);
+		fz_write_float_le(ctx, out, doc->accel->layout_h);
+		fz_write_float_le(ctx, out, doc->accel->layout_em);
+		fz_write_uint32_le(ctx, out, doc->accel->css_sum);
+		fz_write_int32_le(ctx, out, doc->accel->use_doc_css);
+		fz_write_int32_le(ctx, out, doc->accel->num_chapters);
+		for (i = 0; i < doc->accel->num_chapters; i++)
+			fz_write_int32_le(ctx, out, doc->accel->pages_in_chapter[i]);
+
+		fz_close_output(ctx, out);
+	}
+	fz_always(ctx)
+		fz_drop_output(ctx, out);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
 static fz_document *
-epub_init(fz_context *ctx, fz_archive *zip)
+epub_init(fz_context *ctx, fz_archive *zip, fz_stream *accel)
 {
 	epub_document *doc;
 
@@ -540,15 +826,22 @@ epub_init(fz_context *ctx, fz_archive *zip)
 	doc->super.resolve_link = epub_resolve_link;
 	doc->super.make_bookmark = epub_make_bookmark;
 	doc->super.lookup_bookmark = epub_lookup_bookmark;
+	doc->super.count_chapters = epub_count_chapters;
 	doc->super.count_pages = epub_count_pages;
 	doc->super.load_page = epub_load_page;
 	doc->super.lookup_metadata = epub_lookup_metadata;
+	doc->super.output_accelerator = epub_output_accelerator;
 	doc->super.is_reflowable = 1;
 
+	doc->css_sum = user_css_sum(ctx);
+
 	fz_try(ctx)
 	{
+		epub_load_accelerator(ctx, doc, accel);
 		epub_parse_header(ctx, doc);
 	}
+	fz_always(ctx)
+		fz_drop_stream(ctx, accel);
 	fz_catch(ctx)
 	{
 		fz_drop_document(ctx, &doc->super);
@@ -559,14 +852,18 @@ epub_init(fz_context *ctx, fz_archive *zip)
 }
 
 static fz_document *
-epub_open_document_with_stream(fz_context *ctx, fz_stream *file)
+epub_open_document_with_stream(fz_context *ctx, fz_stream *file, fz_stream *accel)
 {
-	return epub_init(ctx, fz_open_zip_archive_with_stream(ctx, file));
+	return epub_init(ctx, fz_open_zip_archive_with_stream(ctx, file), accel);
 }
 
 static fz_document *
-epub_open_document(fz_context *ctx, const char *filename)
+epub_open_document(fz_context *ctx, const char *filename, const char *accel)
 {
+	fz_stream *afile = NULL;
+
+	if (accel)
+		afile = fz_open_file(ctx, accel);
 	if (strstr(filename, "META-INF/container.xml") || strstr(filename, "META-INF\\container.xml"))
 	{
 		char dirname[2048], *p;
@@ -575,10 +872,10 @@ epub_open_document(fz_context *ctx, const char *filename)
 		*p = 0;
 		if (!dirname[0])
 			fz_strlcpy(dirname, ".", sizeof dirname);
-		return epub_init(ctx, fz_open_directory(ctx, dirname));
+		return epub_init(ctx, fz_open_directory(ctx, dirname), afile);
 	}
 
-	return epub_init(ctx, fz_open_zip_archive(ctx, filename));
+	return epub_init(ctx, fz_open_zip_archive(ctx, filename), afile);
 }
 
 static int
@@ -604,8 +901,10 @@ static const char *epub_mimetypes[] =
 fz_document_handler epub_document_handler =
 {
 	epub_recognize,
-	epub_open_document,
-	epub_open_document_with_stream,
+	NULL,
+	NULL,
 	epub_extensions,
-	epub_mimetypes
+	epub_mimetypes,
+	epub_open_document,
+	epub_open_document_with_stream
 };
diff --git a/source/html/html-doc.c b/source/html/html-doc.c
index 94b2975..1a49cf8 100644
--- a/source/html/html-doc.c
+++ b/source/html/html-doc.c
@@ -35,7 +35,7 @@ htdoc_drop_document(fz_context *ctx, fz_document *doc_)
 	fz_drop_outline(ctx, doc->outline);
 }
 
-static int
+static fz_location
 htdoc_resolve_link(fz_context *ctx, fz_document *doc_, const char *dest, float *xp, float *yp)
 {
 	html_document *doc = (html_document*)doc_;
@@ -47,15 +47,15 @@ htdoc_resolve_link(fz_context *ctx, fz_document *doc_, const char *dest, float *
 		{
 			int page = y / doc->html->page_h;
 			if (yp) *yp = y - page * doc->html->page_h;
-			return page;
+			return fz_make_location(0, page);
 		}
 	}
 
-	return -1;
+	return fz_make_location(-1, -1);
 }
 
 static int
-htdoc_count_pages(fz_context *ctx, fz_document *doc_)
+htdoc_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
 {
 	html_document *doc = (html_document*)doc_;
 	if (doc->html->root->b > 0)
@@ -68,7 +68,7 @@ htdoc_update_outline(fz_context *ctx, fz_document *doc, fz_outline *node)
 {
 	while (node)
 	{
-		node->page = htdoc_resolve_link(ctx, doc, node->uri, &node->x, &node->y);
+		node->page = htdoc_resolve_link(ctx, doc, node->uri, &node->x, &node->y).page;
 		htdoc_update_outline(ctx, doc, node->down);
 		node = node->next;
 	}
@@ -119,21 +119,21 @@ htdoc_load_links(fz_context *ctx, fz_page *page_)
 }
 
 static fz_bookmark
-htdoc_make_bookmark(fz_context *ctx, fz_document *doc_, int page)
+htdoc_make_bookmark(fz_context *ctx, fz_document *doc_, fz_location loc)
 {
 	html_document *doc = (html_document*)doc_;
-	return fz_make_html_bookmark(ctx, doc->html, page);
+	return fz_make_html_bookmark(ctx, doc->html, loc.page);
 }
 
-static int
+static fz_location
 htdoc_lookup_bookmark(fz_context *ctx, fz_document *doc_, fz_bookmark mark)
 {
 	html_document *doc = (html_document*)doc_;
-	return fz_lookup_html_bookmark(ctx, doc->html, mark);
+	return fz_make_location(0, fz_lookup_html_bookmark(ctx, doc->html, mark));
 }
 
 static fz_page *
-htdoc_load_page(fz_context *ctx, fz_document *doc_, int number)
+htdoc_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
 {
 	html_document *doc = (html_document*)doc_;
 	html_page *page = fz_new_derived_page(ctx, html_page);
diff --git a/source/html/html-imp.h b/source/html/html-imp.h
index 64e1164..2b5a80d 100644
--- a/source/html/html-imp.h
+++ b/source/html/html-imp.h
@@ -205,8 +205,10 @@ enum
 
 struct fz_html_s
 {
+	fz_storable storable;
 	fz_pool *pool; /* pool allocator for this html tree */
 	float page_w, page_h;
+	float layout_w, layout_h, layout_em;
 	float page_margin[4];
 	fz_html_box *root;
 	char *title;
@@ -281,6 +283,7 @@ struct fz_html_flow_s
 	} content;
 };
 
+
 fz_css *fz_new_css(fz_context *ctx);
 void fz_parse_css(fz_context *ctx, fz_css *css, const char *source, const char *file);
 fz_css_property *fz_parse_css_properties(fz_context *ctx, fz_pool *pool, const char *source);
@@ -313,9 +316,14 @@ fz_outline *fz_load_html_outline(fz_context *ctx, fz_html *node);
 
 float fz_find_html_target(fz_context *ctx, fz_html *html, const char *id);
 fz_link *fz_load_html_links(fz_context *ctx, fz_html *html, int page, const char *base_uri, void *doc);
+fz_html *fz_keep_html(fz_context *ctx, fz_html *html);
 void fz_drop_html(fz_context *ctx, fz_html *html);
 fz_bookmark fz_make_html_bookmark(fz_context *ctx, fz_html *html, int page);
 int fz_lookup_html_bookmark(fz_context *ctx, fz_html *html, fz_bookmark mark);
 void fz_debug_html(fz_context *ctx, fz_html_box *box);
 
+fz_html *fz_store_html(fz_context *ctx, fz_html *html, void *doc, int chapter);
+fz_html *fz_find_html(fz_context *ctx, void *doc, int chapter);
+void fz_purge_stored_html(fz_context *ctx, void *doc);
+
 #endif
diff --git a/source/html/html-layout.c b/source/html/html-layout.c
index 17da431..9dc55c9 100644
--- a/source/html/html-layout.c
+++ b/source/html/html-layout.c
@@ -809,6 +809,11 @@ fz_layout_html(fz_context *ctx, fz_html *html, float w, float h, float em)
 	fz_var(hb_buf);
 	fz_var(unlocked);
 
+	/* If we're already laid out to the specifications we need,
+	 * nothing to do. */
+	if (html->layout_w == w && html->layout_h == h && html->layout_em == em)
+		return;
+
 	html->page_margin[T] = fz_from_css_number(html->root->style->margin[T], em, em, 0);
 	html->page_margin[B] = fz_from_css_number(html->root->style->margin[B], em, em, 0);
 	html->page_margin[L] = fz_from_css_number(html->root->style->margin[L], em, em, 0);
@@ -864,6 +869,12 @@ fz_layout_html(fz_context *ctx, fz_html *html, float w, float h, float em)
 	if (h == 0)
 		html->page_h = box->b;
 
+	/* Remember how we're laid out so we can avoid needless
+	 * relayouts in future. */
+	html->layout_w = w;
+	html->layout_h = h;
+	html->layout_em = em;
+
 #ifndef NDEBUG
 	if (fz_atoi(getenv("FZ_DEBUG_HTML")))
 		fz_debug_html(ctx, html->root);
diff --git a/source/html/html-parse.c b/source/html/html-parse.c
index f7af9fd..92b8db2 100644
--- a/source/html/html-parse.c
+++ b/source/html/html-parse.c
@@ -1,4 +1,4 @@
-#include "mupdf/fitz.h"
+#include "../fitz/fitz-imp.h"
 #include "mupdf/ucdn.h"
 #include "html-imp.h"
 
@@ -499,13 +499,23 @@ static void fz_drop_html_box(fz_context *ctx, fz_html_box *box)
 	}
 }
 
+static void fz_drop_html_imp(fz_context *ctx, fz_storable *stor)
+{
+	fz_html *html = (fz_html *)stor;
+	fz_drop_html_box(ctx, html->root);
+	fz_drop_pool(ctx, html->pool);
+}
+
 void fz_drop_html(fz_context *ctx, fz_html *html)
 {
-	if (html)
-	{
-		fz_drop_html_box(ctx, html->root);
-		fz_drop_pool(ctx, html->pool);
-	}
+	fz_defer_reap_start(ctx);
+	fz_drop_storable(ctx, &html->storable);
+	fz_defer_reap_end(ctx);
+}
+
+fz_html *fz_keep_html(fz_context *ctx, fz_html *html)
+{
+	return fz_keep_storable(ctx, &html->storable);
 }
 
 static fz_html_box *new_box(fz_context *ctx, fz_pool *pool, fz_bidi_direction markup_dir)
@@ -1332,8 +1342,12 @@ fz_parse_html(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const cha
 
 		g.pool = fz_new_pool(ctx);
 		html = fz_pool_alloc(ctx, g.pool, sizeof *html);
+		FZ_INIT_STORABLE(html, 1, fz_drop_html_imp);
 		html->pool = g.pool;
 		html->root = new_box(ctx, g.pool, DEFAULT_DIR);
+		html->layout_w = 0;
+		html->layout_h = 0;
+		html->layout_em = 0;
 		fz_default_css_style(ctx, &style);
 
 		match.up = NULL;
@@ -1505,3 +1519,121 @@ fz_debug_html(fz_context *ctx, fz_html_box *box)
 {
 	fz_debug_html_box(ctx, box, 0);
 }
+
+static size_t
+fz_html_size(fz_context *ctx, fz_html *html)
+{
+	return html ? fz_pool_size(ctx, html->pool) : 0;
+}
+
+/* Magic to make html storable. */
+typedef struct {
+	int refs;
+	void *doc;
+	int chapter_num;
+} fz_html_key;
+
+static int
+fz_make_hash_html_key(fz_context *ctx, fz_store_hash *hash, void *key_)
+{
+	fz_html_key *key = (fz_html_key *)key_;
+	hash->u.pi.ptr = key->doc;
+	hash->u.pi.i = key->chapter_num;
+	return 1;
+}
+
+static void *
+fz_keep_html_key(fz_context *ctx, void *key_)
+{
+	fz_html_key *key = (fz_html_key *)key_;
+	return fz_keep_imp(ctx, key, &key->refs);
+}
+
+static void
+fz_drop_html_key(fz_context *ctx, void *key_)
+{
+	fz_html_key *key = (fz_html_key *)key_;
+	if (fz_drop_imp(ctx, key, &key->refs))
+	{
+		fz_free(ctx, key);
+	}
+}
+
+static int
+fz_cmp_html_key(fz_context *ctx, void *k0_, void *k1_)
+{
+	fz_html_key *k0 = (fz_html_key *)k0_;
+	fz_html_key *k1 = (fz_html_key *)k1_;
+	return k0->doc == k1->doc && k0->chapter_num == k1->chapter_num;
+}
+
+static void
+fz_format_html_key(fz_context *ctx, char *s, int n, void *key_)
+{
+	fz_html_key *key = (fz_html_key *)key_;
+	fz_snprintf(s, n, "(html doc=%p, ch=%d)", key->doc, key->chapter_num);
+}
+
+static const fz_store_type fz_html_store_type =
+{
+	fz_make_hash_html_key,
+	fz_keep_html_key,
+	fz_drop_html_key,
+	fz_cmp_html_key,
+	fz_format_html_key,
+	NULL
+};
+
+fz_html *fz_store_html(fz_context *ctx, fz_html *html, void *doc, int chapter)
+{
+	fz_html_key *key = NULL;
+	fz_html *other_html;
+
+	/* Stick the parsed html in the store */
+	fz_var(key);
+
+	fz_try(ctx)
+	{
+		key = fz_malloc_struct(ctx, fz_html_key);
+		key->refs = 1;
+		key->doc = doc;
+		key->chapter_num = chapter;
+		other_html = fz_store_item(ctx, key, html, fz_html_size(ctx, html), &fz_html_store_type);
+		if (other_html)
+		{
+			fz_drop_html(ctx, html);
+			html = other_html;
+		}
+	}
+	fz_always(ctx)
+		fz_drop_html_key(ctx, key);
+	fz_catch(ctx)
+	{
+		/* Do nothing */
+	}
+
+	return html;
+}
+
+fz_html *fz_find_html(fz_context *ctx, void *doc, int chapter)
+{
+	fz_html_key key;
+
+	key.refs = 1;
+	key.doc = doc;
+	key.chapter_num = chapter;
+	return fz_find_item(ctx, &fz_drop_html_imp, &key, &fz_html_store_type);
+}
+
+static int
+html_filter_store(fz_context *ctx, void *doc, void *key_)
+{
+	fz_html_key *key = (fz_html_key *)key_;
+
+	return (doc == key->doc);
+}
+
+void fz_purge_stored_html(fz_context *ctx, void *doc)
+{
+	fz_filter_store(ctx, html_filter_store, doc, &fz_html_store_type);
+}
diff --git a/source/pdf/pdf-link.c b/source/pdf/pdf-link.c
index 347649a..3669759 100644
--- a/source/pdf/pdf-link.c
+++ b/source/pdf/pdf-link.c
@@ -348,3 +348,10 @@ pdf_resolve_link(fz_context *ctx, pdf_document *doc, const char *uri, float *xp,
 	fz_warn(ctx, "unknown link uri '%s'", uri);
 	return -1;
 }
+
+fz_location
+pdf_resolve_link_imp(fz_context *ctx, fz_document *doc_, const char *uri, float *xp, float *yp)
+{
+	pdf_document *doc = (pdf_document*)doc_;
+	return fz_make_location(0, pdf_resolve_link(ctx, doc, uri, xp, yp));
+}
diff --git a/source/pdf/pdf-page.c b/source/pdf/pdf-page.c
index f8ec5d0..ec39f50 100644
--- a/source/pdf/pdf-page.c
+++ b/source/pdf/pdf-page.c
@@ -15,6 +15,11 @@ pdf_count_pages(fz_context *ctx, pdf_document *doc)
 	return pdf_to_int(ctx, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/Pages/Count"));
 }
 
+int pdf_count_pages_imp(fz_context *ctx, fz_document *doc, int chapter)
+{
+	return pdf_count_pages(ctx, (pdf_document*)doc);
+}
+
 static int
 pdf_load_page_tree_imp(fz_context *ctx, pdf_document *doc, pdf_obj *node, int idx)
 {
@@ -1163,6 +1168,11 @@ pdf_load_page(fz_context *ctx, pdf_document *doc, int number)
 	return page;
 }
 
+fz_page *pdf_load_page_imp(fz_context *ctx, fz_document *doc, int chapter, int number)
+{
+	return (fz_page*)pdf_load_page(ctx, (pdf_document*)doc, number);
+}
+
 /*
 	Delete a page from the page tree of
 	a document. This does not remove the page contents
diff --git a/source/pdf/pdf-xref.c b/source/pdf/pdf-xref.c
index 3ce3baa..bcb31b2 100644
--- a/source/pdf/pdf-xref.c
+++ b/source/pdf/pdf-xref.c
@@ -2321,9 +2321,9 @@ pdf_new_document(fz_context *ctx, fz_stream *file)
 	doc->super.authenticate_password = (fz_document_authenticate_password_fn*)pdf_authenticate_password;
 	doc->super.has_permission = (fz_document_has_permission_fn*)pdf_has_permission;
 	doc->super.load_outline = (fz_document_load_outline_fn*)pdf_load_outline;
-	doc->super.resolve_link = (fz_document_resolve_link_fn*)pdf_resolve_link;
-	doc->super.count_pages = (fz_document_count_pages_fn*)pdf_count_pages;
-	doc->super.load_page = (fz_document_load_page_fn*)pdf_load_page;
+	doc->super.resolve_link = pdf_resolve_link_imp;
+	doc->super.count_pages = pdf_count_pages_imp;
+	doc->super.load_page = pdf_load_page_imp;
 	doc->super.lookup_metadata = (fz_document_lookup_metadata_fn*)pdf_lookup_metadata;
 
 	pdf_lexbuf_init(ctx, &doc->lexbuf.base, PDF_LEXBUF_LARGE);
@@ -2738,7 +2738,7 @@ pdf_obj *pdf_progressive_advance(fz_context *ctx, pdf_document *doc, int pagenum
 */
 pdf_document *pdf_document_from_fz_document(fz_context *ctx, fz_document *ptr)
 {
-	return (pdf_document *)((ptr && ptr->count_pages == (fz_document_count_pages_fn*)pdf_count_pages) ? ptr : NULL);
+	return (pdf_document *)((ptr && ptr->count_pages == pdf_count_pages_imp) ? ptr : NULL);
 }
 
 pdf_page *pdf_page_from_fz_page(fz_context *ctx, fz_page *ptr)
@@ -2877,7 +2877,9 @@ fz_document_handler pdf_document_handler =
 	(fz_document_open_fn*)pdf_open_document,
 	(fz_document_open_with_stream_fn*)pdf_open_document_with_stream,
 	pdf_extensions,
-	pdf_mimetypes
+	pdf_mimetypes,
+	NULL,
+	NULL
 };
 
 void pdf_mark_xref(fz_context *ctx, pdf_document *doc)
diff --git a/source/svg/svg-doc.c b/source/svg/svg-doc.c
index f434b2a..1d3162d 100644
--- a/source/svg/svg-doc.c
+++ b/source/svg/svg-doc.c
@@ -18,7 +18,7 @@ svg_drop_document(fz_context *ctx, fz_document *doc_)
 }
 
 static int
-svg_count_pages(fz_context *ctx, fz_document *doc_)
+svg_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
 {
 	return 1;
 }
@@ -49,7 +49,7 @@ svg_drop_page(fz_context *ctx, fz_page *page_)
 }
 
 static fz_page *
-svg_load_page(fz_context *ctx, fz_document *doc_, int number)
+svg_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
 {
 	svg_document *doc = (svg_document*)doc_;
 	svg_page *page;
@@ -262,5 +262,7 @@ fz_document_handler svg_document_handler =
 	NULL,
 	svg_open_document_with_stream,
 	svg_extensions,
-	svg_mimetypes
+	svg_mimetypes,
+	NULL,
+	NULL
 };
diff --git a/source/xps/xps-doc.c b/source/xps/xps-doc.c
index a7969c8..b71182c 100644
--- a/source/xps/xps-doc.c
+++ b/source/xps/xps-doc.c
@@ -133,7 +133,7 @@ xps_add_link_target(fz_context *ctx, xps_document *doc, char *name)
 	doc->target = target;
 }
 
-int
+fz_location
 xps_lookup_link_target(fz_context *ctx, fz_document *doc_, const char *target_uri, float *xp, float *yp)
 {
 	xps_document *doc = (xps_document*)doc_;
@@ -142,8 +142,8 @@ xps_lookup_link_target(fz_context *ctx, fz_document *doc_, const char *target_ur
 	needle = needle ? needle + 1 : target_uri;
 	for (target = doc->target; target; target = target->next)
 		if (!strcmp(target->name, needle))
-			return target->page;
-	return 0;
+			return fz_make_location(0, target->page);
+	return fz_make_location(-1, -1);
 }
 
 static void
@@ -358,7 +358,7 @@ xps_read_page_list(fz_context *ctx, xps_document *doc)
 }
 
 int
-xps_count_pages(fz_context *ctx, fz_document *doc_)
+xps_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
 {
 	xps_document *doc = (xps_document*)doc_;
 	return doc->page_count;
@@ -436,7 +436,7 @@ xps_drop_page_imp(fz_context *ctx, fz_page *page_)
 }
 
 fz_page *
-xps_load_page(fz_context *ctx, fz_document *doc_, int number)
+xps_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
 {
 	xps_document *doc = (xps_document*)doc_;
 	xps_page *page = NULL;
@@ -505,5 +505,7 @@ fz_document_handler xps_document_handler =
 	xps_open_document,
 	xps_open_document_with_stream,
 	xps_extensions,
-	xps_mimetypes
+	xps_mimetypes,
+	NULL,
+	NULL
 };
diff --git a/source/xps/xps-imp.h b/source/xps/xps-imp.h
index 905e68b..e7eb78d 100644
--- a/source/xps/xps-imp.h
+++ b/source/xps/xps-imp.h
@@ -6,12 +6,12 @@ typedef struct xps_page_s xps_page;
 
 fz_document *xps_open_document(fz_context *ctx, const char *filename);
 fz_document *xps_open_document_with_stream(fz_context *ctx, fz_stream *file);
-int xps_count_pages(fz_context *ctx, fz_document *doc);
-fz_page *xps_load_page(fz_context *ctx, fz_document *doc, int number);
+int xps_count_pages(fz_context *ctx, fz_document *doc, int chapter);
+fz_page *xps_load_page(fz_context *ctx, fz_document *doc, int chapter, int number);
 fz_outline *xps_load_outline(fz_context *ctx, fz_document *doc);
 void xps_run_page(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix ctm, fz_cookie *cookie);
 fz_link *xps_load_links(fz_context *ctx, fz_page *page);
-int xps_lookup_link_target(fz_context *ctx, fz_document *doc, const char *target_uri, float *xp, float *yp);
+fz_location xps_lookup_link_target(fz_context *ctx, fz_document *doc, const char *target_uri, float *xp, float *yp);
 
 int xps_strcasecmp(char *a, char *b);
 void xps_resolve_url(fz_context *ctx, xps_document *doc, char *output, char *base_uri, char *path, int output_size);
diff --git a/source/xps/xps-outline.c b/source/xps/xps-outline.c
index dfa5143..dd44275 100644
--- a/source/xps/xps-outline.c
+++ b/source/xps/xps-outline.c
@@ -37,7 +37,7 @@ xps_parse_document_outline(fz_context *ctx, xps_document *doc, fz_xml *root)
 			entry = fz_new_outline(ctx);
 			entry->title = Memento_label(fz_strdup(ctx, description), "outline_title");
 			entry->uri = Memento_label(fz_strdup(ctx, target), "outline_uri");
-			entry->page = xps_lookup_link_target(ctx, (fz_document*)doc, target, NULL, NULL);
+			entry->page = xps_lookup_link_target(ctx, (fz_document*)doc, target, NULL, NULL).page;
 			entry->down = NULL;
 			entry->next = NULL;
 

http://git.ghostscript.com/?p=mupdf.git;a=commit;h=80606caf66c9bd1306ae264f972c5c859e154f17

--
MuPDF library
Artifex Software, Inc.