[cairo-commit] 6 commits - src/cairo-array.c src/cairo-array-private.h src/cairo-pdf-interchange.c src/cairo-pdf-surface.c src/cairo-pdf-surface-private.h test/pdf-tagged-text.c

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Wed Jul 28 20:27:47 UTC 2021


 src/cairo-array-private.h       |    3 
 src/cairo-array.c               |    6 
 src/cairo-pdf-interchange.c     |  319 +++++++++++++---------
 src/cairo-pdf-surface-private.h |   17 +
 src/cairo-pdf-surface.c         |  559 ++++++++++++++++++++++++++++++++++------
 test/pdf-tagged-text.c          |   42 ++-
 6 files changed, 727 insertions(+), 219 deletions(-)

New commits:
commit 4e3f6bf0c2eb4ff5065bb18fb846019d4fef597f
Merge: d60e3f350 fb6f3eb32
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Wed Jul 28 20:27:45 2021 +0000

    Merge branch 'pdf-object-streams' into 'master'
    
    pdf: use cross-reference stream for PDF >= 1.5
    
    See merge request cairo/cairo!197

commit fb6f3eb32efd372e84460720e22970b7fef571fe
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Wed Jul 28 06:18:01 2021 +0930

    pdf-tagged-text: generate both 1.4 and 1.5 PDFs

diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c
index 62233a7f3..5bfb55886 100644
--- a/test/pdf-tagged-text.c
+++ b/test/pdf-tagged-text.c
@@ -510,23 +510,27 @@ check_created_pdf(cairo_test_context_t *ctx, const char* filename)
 }
 
 static cairo_test_status_t
-preamble (cairo_test_context_t *ctx)
+create_pdf (cairo_test_context_t *ctx, cairo_bool_t check_output)
 {
     cairo_surface_t *surface;
     cairo_t *cr;
     cairo_status_t status, status2;
     cairo_test_status_t result;
+    cairo_pdf_version_t version;
     char *filename;
     const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
 
-    if (! cairo_test_is_target_enabled (ctx, "pdf"))
-	return CAIRO_TEST_UNTESTED;
+    /* check_created_pdf() only works with version 1.4. In version 1.5
+     * the text that is searched for is compressed. */
+    version = check_output ? CAIRO_PDF_VERSION_1_4 : CAIRO_PDF_VERSION_1_5;
 
-    xasprintf (&filename, "%s/%s.pdf", path, BASENAME);
+    xasprintf (&filename, "%s/%s-%s.pdf",
+               path,
+               BASENAME,
+               check_output ? "1.4" : "1.5");
     surface = cairo_pdf_surface_create (filename, PAGE_WIDTH, PAGE_HEIGHT);
 
-    /* Disable object stream compression as this prevents check_created_pdf() from working */
-    cairo_pdf_surface_restrict_to_version (surface, CAIRO_PDF_VERSION_1_4);
+    cairo_pdf_surface_restrict_to_version (surface, version);
 
     cr = cairo_create (surface);
     create_document (surface, cr);
@@ -545,13 +549,38 @@ preamble (cairo_test_context_t *ctx)
 	return CAIRO_TEST_FAILURE;
     }
 
-    result = check_created_pdf(ctx, filename);
+    result = CAIRO_TEST_SUCCESS;
+    if (check_output)
+        result = check_created_pdf(ctx, filename);
 
     free (filename);
 
     return result;
 }
 
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+    cairo_surface_t *surface;
+    cairo_t *cr;
+    cairo_status_t status, status2;
+    cairo_test_status_t result;
+    char *filename;
+    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
+
+    if (! cairo_test_is_target_enabled (ctx, "pdf"))
+	return CAIRO_TEST_UNTESTED;
+
+    /* Create version 1.5 PDF. This can only be manually checked */
+    create_pdf (ctx, FALSE);
+
+    /* Create version 1.4 PDF and checkout output */
+    result = create_pdf (ctx, TRUE);
+
+
+    return result;
+}
+
 CAIRO_TEST (pdf_tagged_text,
 	    "Check tagged text, hyperlinks and PDF document features",
 	    "pdf", /* keywords */
commit 6b8d8712fbf62d54e79ec4788e1cea3e5ffa53e9
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Mon Jul 26 18:33:34 2021 +0930

    pdf-tagged-text test: disable object stream compression
    
    to allow the test to find the strings it is looking for.

diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c
index 95b779306..62233a7f3 100644
--- a/test/pdf-tagged-text.c
+++ b/test/pdf-tagged-text.c
@@ -525,6 +525,9 @@ preamble (cairo_test_context_t *ctx)
     xasprintf (&filename, "%s/%s.pdf", path, BASENAME);
     surface = cairo_pdf_surface_create (filename, PAGE_WIDTH, PAGE_HEIGHT);
 
+    /* Disable object stream compression as this prevents check_created_pdf() from working */
+    cairo_pdf_surface_restrict_to_version (surface, CAIRO_PDF_VERSION_1_4);
+
     cr = cairo_create (surface);
     create_document (surface, cr);
 
commit 90193cc3a2259356ac068f2491f469e10abed141
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Jul 18 07:54:30 2021 +0930

    pdf: convert all document interchange features to use object streams

diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
index 8158161fb..aacc728e5 100644
--- a/src/cairo-pdf-interchange.c
+++ b/src/cairo-pdf-interchange.c
@@ -228,34 +228,36 @@ cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface
     int i, num_mcid, first_page;
     cairo_pdf_resource_t *page_res;
     cairo_pdf_struct_tree_node_t *child;
+    cairo_int_status_t status;
+
+    status = _cairo_pdf_surface_object_begin (surface, node->res);
+    if (unlikely (status))
+	return status;
 
-    _cairo_pdf_surface_update_object (surface, node->res);
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Type /StructElem\n"
 				 "   /S /%s\n"
 				 "   /P %d 0 R\n",
-				 node->res.id,
 				 node->name,
 				 node->parent->res.id);
 
     if (! cairo_list_is_empty (&node->children)) {
 	if (cairo_list_is_singular (&node->children) && node->annot_res.id == 0) {
 	    child = cairo_list_first_entry (&node->children, cairo_pdf_struct_tree_node_t, link);
-	    _cairo_output_stream_printf (surface->output, "   /K %d 0 R\n", child->res.id);
+	    _cairo_output_stream_printf (surface->object_stream.stream, "   /K %d 0 R\n", child->res.id);
 	} else {
-	    _cairo_output_stream_printf (surface->output, "   /K [ ");
+	    _cairo_output_stream_printf (surface->object_stream.stream, "   /K [ ");
 	    if (node->annot_res.id != 0) {
-		_cairo_output_stream_printf (surface->output,
+		_cairo_output_stream_printf (surface->object_stream.stream,
 					     "<< /Type /OBJR /Obj %d 0 R >> ",
 					     node->annot_res.id);
 	    }
 	    cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
 				      &node->children, link)
 	    {
-		_cairo_output_stream_printf (surface->output, "%d 0 R ", child->res.id);
+		_cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
 	    }
-	    _cairo_output_stream_printf (surface->output, "]\n");
+	    _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
 	}
     } else {
 	num_mcid = _cairo_array_num_elements (&node->mcid);
@@ -263,14 +265,14 @@ cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface
 	    mcid_elem = _cairo_array_index (&node->mcid, 0);
 	    first_page = mcid_elem->page;
 	    page_res = _cairo_array_index (&surface->pages, first_page - 1);
-	    _cairo_output_stream_printf (surface->output, "   /Pg %d 0 R\n", page_res->id);
+	    _cairo_output_stream_printf (surface->object_stream.stream, "   /Pg %d 0 R\n", page_res->id);
 
 	    if (num_mcid == 1 && node->annot_res.id == 0) {
-		_cairo_output_stream_printf (surface->output, "   /K %d\n", mcid_elem->mcid);
+		_cairo_output_stream_printf (surface->object_stream.stream, "   /K %d\n", mcid_elem->mcid);
 	    } else {
-		_cairo_output_stream_printf (surface->output, "   /K [ ");
+		_cairo_output_stream_printf (surface->object_stream.stream, "   /K [ ");
 		if (node->annot_res.id != 0) {
-		    _cairo_output_stream_printf (surface->output,
+		    _cairo_output_stream_printf (surface->object_stream.stream,
 						 "<< /Type /OBJR /Obj %d 0 R >> ",
 						 node->annot_res.id);
 		}
@@ -278,23 +280,24 @@ cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface
 		    mcid_elem = _cairo_array_index (&node->mcid, i);
 		    page_res = _cairo_array_index (&surface->pages, mcid_elem->page - 1);
 		    if (mcid_elem->page == first_page) {
-			_cairo_output_stream_printf (surface->output, "%d ", mcid_elem->mcid);
+			_cairo_output_stream_printf (surface->object_stream.stream, "%d ", mcid_elem->mcid);
 		    } else {
-			_cairo_output_stream_printf (surface->output,
+			_cairo_output_stream_printf (surface->object_stream.stream,
 						     "\n       << /Type /MCR /Pg %d 0 R /MCID %d >> ",
 						     page_res->id,
 						     mcid_elem->mcid);
 		    }
 		}
-		_cairo_output_stream_printf (surface->output, "]\n");
+		_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
 	    }
 	}
     }
-    _cairo_output_stream_printf (surface->output,
-				 ">>\n"
-				 "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 ">>\n");
 
-    return _cairo_output_stream_get_status (surface->output);
+    _cairo_pdf_surface_object_end (surface);
+
+    return _cairo_output_stream_get_status (surface->object_stream.stream);
 }
 
 static void
@@ -338,13 +341,13 @@ cairo_pdf_interchange_write_explicit_dest (cairo_pdf_surface_t *surface,
     _cairo_array_copy_element (&surface->page_heights, page - 1, &height);
     _cairo_array_copy_element (&surface->pages, page - 1, &res);
     if (has_pos) {
-       _cairo_output_stream_printf (surface->output,
+       _cairo_output_stream_printf (surface->object_stream.stream,
                                     "[%d 0 R /XYZ %f %f 0]\n",
                                     res.id,
                                     x,
                                     height - y);
     } else {
-       _cairo_output_stream_printf (surface->output,
+       _cairo_output_stream_printf (surface->object_stream.stream,
                                     "[%d 0 R /XYZ null null 0]\n",
                                     res.id);
     }
@@ -387,7 +390,7 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface,
 	    if (named_dest->attrs.y_valid)
 		y = named_dest->attrs.y;
 
-	    _cairo_output_stream_printf (surface->output, "   /Dest ");
+	    _cairo_output_stream_printf (surface->object_stream.stream, "   /Dest ");
 	    status = cairo_pdf_interchange_write_explicit_dest (surface,
                                                                 named_dest->page,
                                                                 TRUE,
@@ -401,7 +404,7 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface,
 	if (unlikely (status))
 	    return status;
 
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /Dest %s\n",
 				     dest);
 	free (dest);
@@ -410,7 +413,7 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface,
 	    return CAIRO_INT_STATUS_TAG_ERROR;
 
 	if (link_attrs->page <= (int)_cairo_array_num_elements (&surface->pages)) {
-	    _cairo_output_stream_printf (surface->output, "   /Dest ");
+	    _cairo_output_stream_printf (surface->object_stream.stream, "   /Dest ");
 	    status = cairo_pdf_interchange_write_explicit_dest (surface,
 								link_attrs->page,
 								link_attrs->has_pos,
@@ -428,7 +431,7 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface,
 	    if (link_res.id == 0)
 		return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /Dest %d 0 R\n",
 					 link_res.id);
 
@@ -456,7 +459,7 @@ cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t   *surface,
 	    return status;
 
     } else if (link_attrs->link_type == TAG_LINK_URI) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /A <<\n"
 				     "      /Type /Action\n"
 				     "      /S /URI\n"
@@ -464,7 +467,7 @@ cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t   *surface,
 				     "   >>\n",
 				     link_attrs->uri);
     } else if (link_attrs->link_type == TAG_LINK_FILE) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /A <<\n"
 				     "      /Type /Action\n"
 				     "      /S /GoToR\n"
@@ -475,24 +478,24 @@ cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t   *surface,
 	    if (unlikely (status))
 		return status;
 
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "      /D %s\n",
 					 dest);
 	    free (dest);
 	} else {
 	    if (link_attrs->has_pos) {
-		_cairo_output_stream_printf (surface->output,
+		_cairo_output_stream_printf (surface->object_stream.stream,
 					     "      /D [%d %f %f 0]\n",
 					     link_attrs->page,
 					     link_attrs->pos.x,
 					     link_attrs->pos.y);
 	    } else {
-		_cairo_output_stream_printf (surface->output,
+		_cairo_output_stream_printf (surface->object_stream.stream,
 					     "      /D [%d null null 0]\n",
 					     link_attrs->page);
 	    }
 	}
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   >>\n");
     }
 
@@ -522,25 +525,28 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
 	sp = _cairo_array_num_elements (&ic->parent_tree) - 1;
 
 	node->annot_res = _cairo_pdf_surface_new_object (surface);
+	if (node->annot_res.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
 	status = _cairo_array_append (&surface->page_annots, &node->annot_res);
 	if (unlikely (status))
 	    return status;
 
-	_cairo_pdf_surface_update_object (surface, node->annot_res);
-	_cairo_output_stream_printf (surface->output,
-				     "%d 0 obj\n"
+	status = _cairo_pdf_surface_object_begin (surface, node->annot_res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "<< /Type /Annot\n"
 				     "   /Subtype /Link\n"
 				     "   /StructParent %d\n",
-				     node->annot_res.id,
 				     sp);
 
 	height = surface->height;
 	if (num_rects > 0) {
 	    cairo_rectangle_int_t bbox_rect;
 
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /QuadPoints [ ");
 	    for (i = 0; i < num_rects; i++) {
 		cairo_rectangle_t rectf;
@@ -553,31 +559,31 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
 		else
 		    _cairo_rectangle_union (&bbox_rect, &recti);
 
-		write_rect_to_pdf_quad_points (surface->output, &rectf, height);
-		_cairo_output_stream_printf (surface->output, " ");
+		write_rect_to_pdf_quad_points (surface->object_stream.stream, &rectf, height);
+		_cairo_output_stream_printf (surface->object_stream.stream, " ");
 	    }
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "]\n"
 					 "   /Rect [ ");
-	    write_rect_int_to_pdf_bbox (surface->output, &bbox_rect, height);
-	    _cairo_output_stream_printf (surface->output, " ]\n");
+	    write_rect_int_to_pdf_bbox (surface->object_stream.stream, &bbox_rect, height);
+	    _cairo_output_stream_printf (surface->object_stream.stream, " ]\n");
 	} else {
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /Rect [ ");
-	    write_rect_int_to_pdf_bbox (surface->output, &node->extents.extents, height);
-	    _cairo_output_stream_printf (surface->output, " ]\n");
+	    write_rect_int_to_pdf_bbox (surface->object_stream.stream, &node->extents.extents, height);
+	    _cairo_output_stream_printf (surface->object_stream.stream, " ]\n");
 	}
 
 	status = cairo_pdf_interchange_write_link_action (surface, &annot->link_attrs);
 	if (unlikely (status))
 	    return status;
 
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /BS << /W 0 >>"
-				     ">>\n"
-				     "endobj\n");
+				     ">>\n");
 
-	status = _cairo_output_stream_get_status (surface->output);
+	_cairo_pdf_surface_object_end (surface);
+	status = _cairo_output_stream_get_status (surface->object_stream.stream);
     }
 
     return status;
@@ -614,41 +620,47 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_pdf_struct_tree_node_t *child;
+    cairo_int_status_t status;
 
     if (cairo_list_is_empty (&ic->struct_root->children))
 	return CAIRO_STATUS_SUCCESS;
 
     surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
+    if (surface->struct_tree_root.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
     ic->struct_root->res = surface->struct_tree_root;
 
     cairo_pdf_interchange_walk_struct_tree (surface, ic->struct_root, cairo_pdf_interchange_write_node_object);
 
     child = cairo_list_first_entry (&ic->struct_root->children, cairo_pdf_struct_tree_node_t, link);
-    _cairo_pdf_surface_update_object (surface, surface->struct_tree_root);
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+
+    status = _cairo_pdf_surface_object_begin (surface, surface->struct_tree_root);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Type /StructTreeRoot\n"
 				 "   /ParentTree %d 0 R\n",
-				 surface->struct_tree_root.id,
 				 ic->parent_tree_res.id);
 
     if (cairo_list_is_singular (&ic->struct_root->children)) {
 	child = cairo_list_first_entry (&ic->struct_root->children, cairo_pdf_struct_tree_node_t, link);
-	_cairo_output_stream_printf (surface->output, "   /K [ %d 0 R ]\n", child->res.id);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /K [ %d 0 R ]\n", child->res.id);
     } else {
-	_cairo_output_stream_printf (surface->output, "   /K [ ");
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /K [ ");
 
 	cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
 				  &ic->struct_root->children, link)
 	{
-	    _cairo_output_stream_printf (surface->output, "%d 0 R ", child->res.id);
+	    _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
 	}
-	_cairo_output_stream_printf (surface->output, "]\n");
+	_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
     }
 
-    _cairo_output_stream_printf (surface->output,
-				 ">>\n"
-				 "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -686,17 +698,23 @@ cairo_pdf_interchange_write_page_parent_elems (cairo_pdf_surface_t *surface)
     num_elems = _cairo_array_num_elements (&ic->mcid_to_tree);
     if (num_elems > 0) {
 	res = _cairo_pdf_surface_new_object (surface);
-	_cairo_output_stream_printf (surface->output,
-				     "%d 0 obj\n"
-				     "[\n",
-				     res.id);
+	if (res.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	status = _cairo_pdf_surface_object_begin (surface, res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "[\n");
 	for (i = 0; i < num_elems; i++) {
 	    _cairo_array_copy_element (&ic->mcid_to_tree, i, &node);
-	    _cairo_output_stream_printf (surface->output, "  %d 0 R\n", node->res.id);
+	    _cairo_output_stream_printf (surface->object_stream.stream, "  %d 0 R\n", node->res.id);
 	}
-	_cairo_output_stream_printf (surface->output,
-				     "]\n"
-				     "endobj\n");
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "]\n");
+	_cairo_pdf_surface_object_end (surface);
+
 	status = _cairo_array_append (&ic->parent_tree, &res);
 	surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
     }
@@ -710,27 +728,33 @@ cairo_pdf_interchange_write_parent_tree (cairo_pdf_surface_t *surface)
     int num_elems, i;
     cairo_pdf_resource_t *res;
     cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status;
 
     num_elems = _cairo_array_num_elements (&ic->parent_tree);
     if (num_elems > 0) {
 	ic->parent_tree_res = _cairo_pdf_surface_new_object (surface);
-	_cairo_output_stream_printf (surface->output,
-				     "%d 0 obj\n"
-				     "<< /Nums [\n",
-				     ic->parent_tree_res.id);
+	if (ic->parent_tree_res.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	status = _cairo_pdf_surface_object_begin (surface, ic->parent_tree_res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "<< /Nums [\n");
 	for (i = 0; i < num_elems; i++) {
 	    res = _cairo_array_index (&ic->parent_tree, i);
 	    if (res->id) {
-		_cairo_output_stream_printf (surface->output,
+		_cairo_output_stream_printf (surface->object_stream.stream,
 					     "   %d %d 0 R\n",
 					     i,
 					     res->id);
 	    }
 	}
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "  ]\n"
-				     ">>\n"
-				     "endobj\n");
+				     ">>\n");
+	_cairo_pdf_surface_object_end (surface);
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -751,19 +775,24 @@ cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
 
     _cairo_array_copy_element (&ic->outline, 0, &outline);
     outline->res = _cairo_pdf_surface_new_object (surface);
+    if (outline->res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
     surface->outlines_dict_res = outline->res;
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+    status = _cairo_pdf_surface_object_begin (surface, outline->res);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Type /Outlines\n"
 				 "   /First %d 0 R\n"
 				 "   /Last %d 0 R\n"
 				 "   /Count %d\n"
-				 ">>\n"
-				 "endobj\n",
-				 outline->res.id,
+				 ">>\n",
 				 outline->first_child->res.id,
 				 outline->last_child->res.id,
 				 outline->count);
+    _cairo_pdf_surface_object_end (surface);
 
     for (i = 1; i < num_elems; i++) {
 	_cairo_array_copy_element (&ic->outline, i, &outline);
@@ -773,29 +802,31 @@ cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
 	if (unlikely (status))
 	    return status;
 
-	_cairo_output_stream_printf (surface->output,
-				     "%d 0 obj\n"
+	status = _cairo_pdf_surface_object_begin (surface, outline->res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "<< /Title %s\n"
 				     "   /Parent %d 0 R\n",
-				     outline->res.id,
 				     name,
 				     outline->parent->res.id);
 	free (name);
 
 	if (outline->prev) {
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /Prev %d 0 R\n",
 					 outline->prev->res.id);
 	}
 
 	if (outline->next) {
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /Next %d 0 R\n",
 					 outline->next->res.id);
 	}
 
 	if (outline->first_child) {
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /First %d 0 R\n"
 					 "   /Last %d 0 R\n"
 					 "   /Count %d\n",
@@ -810,7 +841,7 @@ cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
 		flags |= 1;
 	    if (outline->flags & CAIRO_PDF_OUTLINE_FLAG_BOLD)
 		flags |= 2;
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /F %d\n",
 					 flags);
 	}
@@ -819,9 +850,9 @@ cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
 	if (unlikely (status))
 	    return status;
 
-	_cairo_output_stream_printf (surface->output,
-				     ">>\n"
-				     "endobj\n");
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     ">>\n");
+	_cairo_pdf_surface_object_end (surface);
     }
 
     return status;
@@ -887,6 +918,7 @@ cairo_pdf_interchange_write_forward_links (cairo_pdf_surface_t *surface)
 {
     int num_elems, i;
     cairo_pdf_forward_link_t *link;
+    cairo_int_status_t status;
 
     num_elems = _cairo_array_num_elements (&surface->forward_links);
     for (i = 0; i < num_elems; i++) {
@@ -894,10 +926,9 @@ cairo_pdf_interchange_write_forward_links (cairo_pdf_surface_t *surface)
 	if (link->page > (int)_cairo_array_num_elements (&surface->pages))
 	    return CAIRO_INT_STATUS_TAG_ERROR;
 
-	_cairo_pdf_surface_update_object (surface, link->res);
-	_cairo_output_stream_printf (surface->output,
-				     "%d 0 obj\n",
-				     link->res.id);
+	status = _cairo_pdf_surface_object_begin (surface, link->res);
+	if (unlikely (status))
+	    return status;
 
 	cairo_pdf_interchange_write_explicit_dest (surface,
 						   link->page,
@@ -905,8 +936,7 @@ cairo_pdf_interchange_write_forward_links (cairo_pdf_surface_t *surface)
 						   link->pos.x,
 						   link->pos.y);
 
-	_cairo_output_stream_printf (surface->output,
-				     "endobj\n");
+	_cairo_pdf_surface_object_end (surface);
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -938,10 +968,15 @@ cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
 	return CAIRO_STATUS_SUCCESS;
 
     surface->page_labels_res = _cairo_pdf_surface_new_object (surface);
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
-				 "<< /Nums [\n",
-				 surface->page_labels_res.id);
+    if (surface->page_labels_res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = _cairo_pdf_surface_object_begin (surface, surface->page_labels_res);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 "<< /Nums [\n");
     prefix = NULL;
     prev_prefix = NULL;
     num = 0;
@@ -956,10 +991,10 @@ cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
 	}
 
 	if (!strcmp_null (prefix, prev_prefix) || num != prev_num + 1) {
-	    _cairo_output_stream_printf (surface->output,  "   %d << ", i);
+	    _cairo_output_stream_printf (surface->object_stream.stream,  "   %d << ", i);
 
 	    if (num)
-		_cairo_output_stream_printf (surface->output,  "/S /D /St %d ", num);
+		_cairo_output_stream_printf (surface->object_stream.stream,  "/S /D /St %d ", num);
 
 	    if (prefix) {
 		char *s;
@@ -967,11 +1002,11 @@ cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
 		if (unlikely (status))
 		    return status;
 
-		_cairo_output_stream_printf (surface->output,  "/P %s ", s);
+		_cairo_output_stream_printf (surface->object_stream.stream,  "/P %s ", s);
 		free (s);
 	    }
 
-	    _cairo_output_stream_printf (surface->output,  ">>\n");
+	    _cairo_output_stream_printf (surface->object_stream.stream,  ">>\n");
 	}
 	free (prev_prefix);
 	prev_prefix = prefix;
@@ -980,10 +1015,10 @@ cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
     }
     free (prefix);
     free (prev_prefix);
-    _cairo_output_stream_printf (surface->output,
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "  ]\n"
-				 ">>\n"
-				 "endobj\n");
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -1012,6 +1047,7 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
 {
     int i;
     cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status;
 
     if (ic->num_dests == 0) {
 	ic->dests_res.id = 0;
@@ -1027,10 +1063,15 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
     qsort (ic->sorted_dests, ic->num_dests, sizeof (cairo_pdf_named_dest_t *), _dest_compare);
 
     ic->dests_res = _cairo_pdf_surface_new_object (surface);
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
-				 "<< /Names [\n",
-				 ic->dests_res.id);
+    if (ic->dests_res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = _cairo_pdf_surface_object_begin (surface, ic->dests_res);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 "<< /Names [\n");
     for (i = 0; i < ic->num_dests; i++) {
 	cairo_pdf_named_dest_t *dest = ic->sorted_dests[i];
 	cairo_pdf_resource_t page_res;
@@ -1054,17 +1095,17 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
 
 	_cairo_array_copy_element (&surface->pages, dest->page - 1, &page_res);
 	_cairo_array_copy_element (&surface->page_heights, dest->page - 1, &height);
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   (%s) [%d 0 R /XYZ %f %f 0]\n",
 				     dest->attrs.name,
 				     page_res.id,
 				     x,
 				     height - y);
     }
-    _cairo_output_stream_printf (surface->output,
-				     "  ]\n"
-				     ">>\n"
-				     "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 "  ]\n"
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -1082,12 +1123,17 @@ cairo_pdf_interchange_write_names_dict (cairo_pdf_surface_t *surface)
     surface->names_dict_res.id = 0;
     if (ic->dests_res.id != 0) {
 	surface->names_dict_res = _cairo_pdf_surface_new_object (surface);
-	_cairo_output_stream_printf (surface->output,
-				     "%d 0 obj\n"
-				     "<< /Dests %d 0 R >>\n"
-				     "endobj\n",
-				     surface->names_dict_res.id,
+	if (surface->names_dict_res.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	status = _cairo_pdf_surface_object_begin (surface, surface->names_dict_res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "<< /Dests %d 0 R >>\n",
 				     ic->dests_res.id);
+	_cairo_pdf_surface_object_end (surface);
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -1097,41 +1143,44 @@ static cairo_int_status_t
 cairo_pdf_interchange_write_docinfo (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status;
 
     surface->docinfo_res = _cairo_pdf_surface_new_object (surface);
     if (surface->docinfo_res.id == 0)
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+    status = _cairo_pdf_surface_object_begin (surface, surface->docinfo_res);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Producer (cairo %s (https://cairographics.org))\n",
-				 surface->docinfo_res.id,
 				 cairo_version_string ());
 
     if (ic->docinfo.title)
-	_cairo_output_stream_printf (surface->output, "   /Title %s\n", ic->docinfo.title);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /Title %s\n", ic->docinfo.title);
 
     if (ic->docinfo.author)
-	_cairo_output_stream_printf (surface->output, "   /Author %s\n", ic->docinfo.author);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /Author %s\n", ic->docinfo.author);
 
     if (ic->docinfo.subject)
-	_cairo_output_stream_printf (surface->output, "   /Subject %s\n", ic->docinfo.subject);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /Subject %s\n", ic->docinfo.subject);
 
     if (ic->docinfo.keywords)
-	_cairo_output_stream_printf (surface->output, "   /Keywords %s\n", ic->docinfo.keywords);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /Keywords %s\n", ic->docinfo.keywords);
 
     if (ic->docinfo.creator)
-	_cairo_output_stream_printf (surface->output, "   /Creator %s\n", ic->docinfo.creator);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /Creator %s\n", ic->docinfo.creator);
 
     if (ic->docinfo.create_date)
-	_cairo_output_stream_printf (surface->output, "   /CreationDate %s\n", ic->docinfo.create_date);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /CreationDate %s\n", ic->docinfo.create_date);
 
     if (ic->docinfo.mod_date)
-	_cairo_output_stream_printf (surface->output, "   /ModDate %s\n", ic->docinfo.mod_date);
+	_cairo_output_stream_printf (surface->object_stream.stream, "   /ModDate %s\n", ic->docinfo.mod_date);
 
-    _cairo_output_stream_printf (surface->output,
-				 ">>\n"
-				 "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
 
     return CAIRO_STATUS_SUCCESS;
 }
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index b548af032..7decdc7e7 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -2170,6 +2170,7 @@ _cairo_pdf_surface_object_begin (cairo_pdf_surface_t *surface,
 	object->u.compressed_obj.index = _cairo_array_num_elements (&surface->object_stream.objects) - 1;
 
     } else {
+	_cairo_pdf_surface_update_object (surface, resource);
 	_cairo_output_stream_printf (surface->output,
 				     "%d 0 obj\n",
 				     resource.id);
@@ -6886,7 +6887,7 @@ _cairo_pdf_surface_write_xref_stream (cairo_pdf_surface_t  *surface,
 
     if (!surface->compress_streams) {
 	/* Adobe Reader requires xref streams to be flate encoded (PDF
-	 * Reference 1.7, implemenation note 20). This means
+	 * Reference 1.7, implementation note 20). This means
 	 * compression must always be enabled on this stream. To
 	 * facilitate debugging when compress_stream is disabled, emit
 	 * a human readable format of the xref stream as PDF comments.
commit bd514f6b08c1b31a75948fd99c147319e5aa649f
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Jul 18 07:16:22 2021 +0930

    pdf: add support for object streams for PDF >= 1.5
    
    This allows all objects that were previously emitted uncompressed to
    be compressed into a an object stream.
    
    Currently only /Page, /Pages, and /Catalog have been converted to use
    object streams.

diff --git a/src/cairo-array-private.h b/src/cairo-array-private.h
index 35b29e5fc..8e779d41a 100644
--- a/src/cairo-array-private.h
+++ b/src/cairo-array-private.h
@@ -85,6 +85,9 @@ _cairo_array_num_elements (const cairo_array_t *array);
 cairo_private unsigned int
 _cairo_array_size (const cairo_array_t *array);
 
+cairo_private void
+_cairo_array_sort (const cairo_array_t *array, int (*compar)(const void *, const void *));
+
 CAIRO_END_DECLS
 
 #endif /* CAIRO_ARRAY_PRIVATE_H */
diff --git a/src/cairo-array.c b/src/cairo-array.c
index af8b7e982..60f45db4e 100644
--- a/src/cairo-array.c
+++ b/src/cairo-array.c
@@ -532,3 +532,9 @@ _cairo_user_data_array_foreach (cairo_user_data_array_t     *array,
 	    func (slots[i].key, slots[i].user_data, closure);
     }
 }
+
+void
+_cairo_array_sort (const cairo_array_t *array, int (*compar)(const void *, const void *))
+{
+    qsort (array->elements, array->num_elements, array->element_size, compar);
+}
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 65fd8c741..673e20a6d 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -55,6 +55,7 @@ typedef struct _cairo_pdf_resource {
     unsigned int id;
 } cairo_pdf_resource_t;
 
+
 #define CAIRO_NUM_OPERATORS (CAIRO_OPERATOR_HSL_LUMINOSITY + 1)
 
 typedef struct _cairo_pdf_group_resources {
@@ -315,6 +316,13 @@ struct _cairo_pdf_surface {
 	cairo_bool_t is_knockout;
     } group_stream;
 
+    struct {
+	cairo_bool_t active;
+	cairo_output_stream_t *stream;
+	cairo_pdf_resource_t resource;
+	cairo_array_t objects;
+    } object_stream;
+
     cairo_surface_clipper_t clipper;
 
     cairo_pdf_operators_t pdf_operators;
@@ -377,6 +385,13 @@ _cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
 				  const char             *name,
 				  const char             *attributes);
 
+cairo_private cairo_int_status_t
+_cairo_pdf_surface_object_begin (cairo_pdf_surface_t *surface,
+				 cairo_pdf_resource_t resource);
+
+cairo_private void
+_cairo_pdf_surface_object_end (cairo_pdf_surface_t *surface);
+
 cairo_private cairo_int_status_t
 _cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
 				const char          *name);
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 09b2648f1..b548af032 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -81,6 +81,7 @@
  *  - PDF Stream
  *  - Content Stream
  *  - Group Stream
+ *  - Object stream
  *
  * Calling _cairo_output_stream_printf (surface->output, ...) will
  * write to the currently open stream.
@@ -110,6 +111,16 @@
  *   memory until it is closed. This allows some optimization such as
  *   including the Resource dictionary and stream length inside the
  *   XObject instead of using an indirect object.
+ *
+ * Object Stream (PDF 1.5)
+ *   An Object Stream may be opened and closed with the following functions:
+ *     _cairo_pdf_surface_open_object_stream ()
+ *     _cairo_pdf_surface_close_object_stream ()
+ *
+ *  An Object Stream contains one or more objects compressed into a stream.
+ *  Only non stream objects are permitted. When emitting objects intended for
+ *  the Object Stream, enclose the emit object operation with
+ *  _cairo_pdf_surface_object_begin()/_cairo_pdf_surface_object_end().
  */
 
 /**
@@ -222,10 +233,29 @@ static const char *_cairo_pdf_supported_mime_types[] =
     NULL
 };
 
+/* PDF cross-reference stream types */
+typedef enum {
+    PDF_OBJECT_FREE = 0,
+    PDF_OBJECT_UNCOMPRESSED = 1,
+    PDF_OBJECT_COMPRESSED = 2,
+} cairo_pdf_object_type_t;
+
 typedef struct _cairo_pdf_object {
-    long offset;
+    cairo_pdf_object_type_t type;
+    union {
+	long offset; /* type == PDF_OBJECT_UNCOMPRESSED */
+	struct compressed_obj {  /* type == PDF_OBJECT_COMPRESSED */
+	    cairo_pdf_resource_t xref_stream;
+	    int index;
+	} compressed_obj;
+    } u;
 } cairo_pdf_object_t;
 
+typedef struct _cairo_xref_stream_object {
+    cairo_pdf_resource_t resource;
+    long offset;
+} cairo_xref_stream_object_t;
+
 typedef struct _cairo_pdf_font {
     unsigned int font_id;
     unsigned int subset_id;
@@ -276,11 +306,12 @@ _cairo_pdf_surface_emit_surface (cairo_pdf_surface_t        *surface,
 static cairo_int_status_t
 _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface);
 
-static void
+static cairo_int_status_t
 _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface);
 
-static cairo_pdf_resource_t
-_cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface);
+static cairo_int_status_t
+_cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface,
+				  cairo_pdf_resource_t catalog);
 
 static long
 _cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface);
@@ -296,9 +327,6 @@ static cairo_int_status_t
 _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface,
 						    cairo_bool_t         finish);
 
-static cairo_int_status_t
-_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface);
-
 static cairo_int_status_t
 _cairo_pdf_surface_emit_font_subsets (cairo_pdf_surface_t *surface);
 
@@ -315,7 +343,11 @@ _cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface)
     cairo_int_status_t status;
     cairo_pdf_object_t object;
 
-    object.offset = _cairo_output_stream_get_position (surface->output);
+    /* Default to Uncompressed. If this object is used with
+     * _cairo_pdf_surface_object_begin() and Object Streams are
+     * enabled it will be changed to Compressed. */
+    object.type = PDF_OBJECT_UNCOMPRESSED;
+    object.u.offset = _cairo_output_stream_get_position (surface->output);
 
     status = _cairo_array_append (&surface->objects, &object);
     if (unlikely (status)) {
@@ -336,7 +368,7 @@ _cairo_pdf_surface_update_object (cairo_pdf_surface_t	*surface,
     cairo_pdf_object_t *object;
 
     object = _cairo_array_index (&surface->objects, resource.id - 1);
-    object->offset = _cairo_output_stream_get_position (surface->output);
+    object->u.offset = _cairo_output_stream_get_position (surface->output);
 }
 
 static void
@@ -472,6 +504,9 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->group_stream.active = FALSE;
     surface->group_stream.stream = NULL;
     surface->group_stream.mem_stream = NULL;
+    surface->object_stream.active = FALSE;
+    surface->object_stream.stream = NULL;
+    _cairo_array_init (&surface->object_stream.objects, sizeof (cairo_xref_stream_object_t));
 
     surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE;
 
@@ -2093,6 +2128,181 @@ _cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
     return status;
 }
 
+static cairo_int_status_t
+_cairo_pdf_surface_open_object_stream (cairo_pdf_surface_t *surface)
+{
+    if (surface->pdf_version < CAIRO_PDF_VERSION_1_5) {
+	/* Object streams not supported. All objects will be written
+	 * directly to the file. */
+	assert (surface->pdf_stream.active == FALSE);
+	assert (surface->group_stream.active == FALSE);
+	surface->object_stream.stream = surface->output;
+    } else {
+	surface->object_stream.resource = _cairo_pdf_surface_new_object (surface);
+	if (surface->object_stream.resource.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	_cairo_array_truncate (&surface->object_stream.objects, 0);
+	surface->object_stream.stream = _cairo_memory_stream_create ();
+	surface->object_stream.active = TRUE;
+    }
+    return _cairo_output_stream_get_status (surface->object_stream.stream);
+}
+
+cairo_int_status_t
+_cairo_pdf_surface_object_begin (cairo_pdf_surface_t *surface,
+				 cairo_pdf_resource_t resource)
+{
+    cairo_xref_stream_object_t xref_obj;
+    cairo_pdf_object_t *object;
+    cairo_int_status_t status;
+
+    if (surface->object_stream.active) {
+	xref_obj.resource = resource;
+	xref_obj.offset = _cairo_output_stream_get_position (surface->object_stream.stream);
+	status = _cairo_array_append (&surface->object_stream.objects, &xref_obj);
+	if (unlikely (status))
+	    return status;
+
+	object = _cairo_array_index (&surface->objects, resource.id - 1);
+	object->type = PDF_OBJECT_COMPRESSED;
+	object->u.compressed_obj.xref_stream = surface->object_stream.resource;
+	object->u.compressed_obj.index = _cairo_array_num_elements (&surface->object_stream.objects) - 1;
+
+    } else {
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n",
+				     resource.id);
+    }
+    return CAIRO_INT_STATUS_SUCCESS;
+}
+
+void
+_cairo_pdf_surface_object_end (cairo_pdf_surface_t *surface)
+{
+    if (!surface->object_stream.active) {
+	_cairo_output_stream_printf (surface->output,
+				     "endobj\n");
+    }
+}
+
+static int _cairo_xref_stream_object_compare (const void *a, const void *b)
+{
+    const cairo_xref_stream_object_t *a_obj = a;
+    const cairo_xref_stream_object_t *b_obj = b;
+
+    if (a_obj->offset < b_obj->offset)
+	return -1;
+    else if (a_obj->offset > b_obj->offset)
+	return 1;
+    else
+	return 0;
+}
+
+static cairo_int_status_t
+_cairo_pdf_surface_close_object_stream (cairo_pdf_surface_t *surface)
+{
+    int i, num_objects;
+    cairo_xref_stream_object_t *xref_obj;
+    long start_pos, length;
+    cairo_output_stream_t *index_stream;
+    cairo_output_stream_t *deflate_stream;
+    cairo_pdf_resource_t length_res;
+    cairo_int_status_t status;
+    cairo_pdf_object_t *object;
+
+    if (!surface->object_stream.active) {
+	surface->object_stream.stream = NULL;
+	return CAIRO_INT_STATUS_SUCCESS;
+    }
+
+    num_objects = _cairo_array_num_elements (&surface->object_stream.objects);
+    if (num_objects == 0) {
+	object = _cairo_array_index (&surface->objects, surface->object_stream.resource.id - 1);
+	object->type = PDF_OBJECT_FREE;
+	return CAIRO_INT_STATUS_SUCCESS;
+    }
+
+    index_stream = _cairo_memory_stream_create ();
+    /* PDF requires the object id/offset pairs to be sorted by offset. */
+    _cairo_array_sort (&surface->object_stream.objects, _cairo_xref_stream_object_compare);
+    for (i = 0; i < num_objects; i++) {
+	xref_obj = _cairo_array_index (&surface->object_stream.objects, i);
+	_cairo_output_stream_printf (index_stream,
+				     "%d %ld\n",
+				     xref_obj->resource.id,
+				     xref_obj->offset);
+    }
+
+    length_res = _cairo_pdf_surface_new_object (surface);
+    if (length_res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    _cairo_pdf_surface_update_object (surface, surface->object_stream.resource);
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Type /ObjStm\n"
+                                 "   /Length %d 0 R\n"
+                                 "   /N %d\n"
+                                 "   /First %d\n",
+				 surface->object_stream.resource.id,
+				 length_res.id,
+				 num_objects,
+				 _cairo_memory_stream_length (index_stream));
+
+    if (surface->compress_streams) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /Filter /FlateDecode\n");
+    }
+
+    _cairo_output_stream_printf (surface->output,
+				 ">>\n"
+				 "stream\n");
+
+    start_pos = _cairo_output_stream_get_position (surface->output);
+    if (surface->compress_streams) {
+	deflate_stream = _cairo_deflate_stream_create (surface->output);
+	_cairo_memory_stream_copy (index_stream, deflate_stream);
+	_cairo_memory_stream_copy (surface->object_stream.stream, deflate_stream);
+	status = _cairo_output_stream_destroy (deflate_stream);
+	if (unlikely (status))
+	    return status;
+
+	length = _cairo_output_stream_get_position (surface->output) - start_pos;
+    } else {
+	_cairo_memory_stream_copy (index_stream, surface->output);
+	_cairo_memory_stream_copy (surface->object_stream.stream, surface->output);
+	length = _cairo_output_stream_get_position (surface->output) - start_pos;
+    }
+
+    _cairo_output_stream_printf (surface->output,
+				 "\n"
+				 "endstream\n"
+				 "endobj\n");
+
+    _cairo_pdf_surface_update_object (surface,
+				      length_res);
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "   %ld\n"
+				 "endobj\n",
+				 length_res.id,
+				 length);
+
+    status = _cairo_output_stream_destroy (index_stream);
+    if (unlikely (status))
+	return status;
+
+    status = _cairo_output_stream_destroy (surface->object_stream.stream);
+    if (unlikely (status))
+	return status;
+
+    surface->object_stream.stream = NULL;
+    surface->object_stream.active = FALSE;
+
+    return _cairo_output_stream_get_status (surface->output);
+}
+
 static cairo_int_status_t
 _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 					const cairo_box_double_t  *bbox,
@@ -2223,6 +2433,10 @@ _cairo_pdf_surface_finish (void *abstract_surface)
 
     _cairo_pdf_surface_clear (surface);
 
+    status = _cairo_pdf_surface_open_object_stream (surface);
+    if (unlikely (status))
+	return status;
+
     /* Emit unbounded surfaces */
     _cairo_pdf_surface_write_patterns_and_smask_groups (surface, TRUE);
 
@@ -2230,15 +2444,25 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     if (status == CAIRO_STATUS_SUCCESS)
 	status = _cairo_pdf_surface_emit_font_subsets (surface);
 
-    _cairo_pdf_surface_write_pages (surface);
+    status = _cairo_pdf_surface_write_pages (surface);
+    if (unlikely (status))
+	return status;
 
     status = _cairo_pdf_interchange_write_document_objects (surface);
     if (unlikely (status))
 	return status;
 
-    catalog = _cairo_pdf_surface_write_catalog (surface);
-    if (catalog.id == 0 && status == CAIRO_STATUS_SUCCESS)
-	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+    catalog = _cairo_pdf_surface_new_object (surface);
+    if (catalog.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = _cairo_pdf_surface_write_catalog (surface, catalog);
+    if (unlikely (status))
+	return status;
+
+    status = _cairo_pdf_surface_close_object_stream (surface);
+    if (unlikely (status))
+	return status;
 
     if (surface->pdf_version >= CAIRO_PDF_VERSION_1_5)
     {
@@ -2306,6 +2530,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     _cairo_array_fini (&surface->alpha_linear_functions);
     _cairo_array_fini (&surface->page_patterns);
     _cairo_array_fini (&surface->page_surfaces);
+    _cairo_array_fini (&surface->object_stream.objects);
 
     size = _cairo_array_num_elements (&surface->doc_surfaces);
     for (i = 0; i < size; i++) {
@@ -5141,34 +5366,38 @@ _cairo_pdf_surface_get_font_options (void                  *abstract_surface,
     _cairo_font_options_set_round_glyph_positions (options, CAIRO_ROUND_GLYPH_POS_OFF);
 }
 
-static void
+static cairo_int_status_t
 _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_resource_t page;
     int num_pages, i;
+    cairo_int_status_t status;
 
-    _cairo_pdf_surface_update_object (surface, surface->pages_resource);
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+    status = _cairo_pdf_surface_object_begin (surface, surface->pages_resource);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Type /Pages\n"
-				 "   /Kids [ ",
-				 surface->pages_resource.id);
+				 "   /Kids [ ");
 
     num_pages = _cairo_array_num_elements (&surface->pages);
     for (i = 0; i < num_pages; i++) {
 	_cairo_array_copy_element (&surface->pages, i, &page);
-	_cairo_output_stream_printf (surface->output, "%d 0 R ", page.id);
+	_cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", page.id);
     }
 
-    _cairo_output_stream_printf (surface->output, "]\n");
-    _cairo_output_stream_printf (surface->output, "   /Count %d\n", num_pages);
+    _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
+    _cairo_output_stream_printf (surface->object_stream.stream, "   /Count %d\n", num_pages);
 
 
     /* TODO: Figure out which other defaults to be inherited by /Page
      * objects. */
-    _cairo_output_stream_printf (surface->output,
-				 ">>\n"
-				 "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
+
+    return CAIRO_INT_STATUS_SUCCESS;
 }
 
 cairo_int_status_t
@@ -6452,55 +6681,54 @@ BAIL:
     return status;
 }
 
-static cairo_pdf_resource_t
-_cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface)
+static cairo_int_status_t
+_cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface,
+				  cairo_pdf_resource_t catalog)
 {
-    cairo_pdf_resource_t catalog;
+    cairo_status_t status;
 
-    catalog = _cairo_pdf_surface_new_object (surface);
-    if (catalog.id == 0)
-	return catalog;
+    status = _cairo_pdf_surface_object_begin (surface, catalog);
+    if (unlikely (status))
+	return status;
 
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Type /Catalog\n"
 				 "   /Pages %d 0 R\n",
-				 catalog.id,
 				 surface->pages_resource.id);
 
     if (surface->struct_tree_root.id != 0) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /StructTreeRoot %d 0 R\n",
 				     surface->struct_tree_root.id);
 	if (surface->tagged) {
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "   /MarkInfo << /Marked true >>\n");
 	}
     }
 
     if (surface->outlines_dict_res.id != 0) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /Outlines %d 0 R\n",
 				     surface->outlines_dict_res.id);
     }
 
     if (surface->page_labels_res.id != 0) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /PageLabels %d 0 R\n",
 				     surface->page_labels_res.id);
     }
 
     if (surface->names_dict_res.id != 0) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /Names %d 0 R\n",
 				     surface->names_dict_res.id);
     }
 
-    _cairo_output_stream_printf (surface->output,
-				 ">>\n"
-				 "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
 
-    return catalog;
+    return status;
 }
 
 static long
@@ -6523,7 +6751,7 @@ _cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface)
 				 "0000000000 65535 f \n");
     for (i = 0; i < num_objects; i++) {
 	object = _cairo_array_index (&surface->objects, i);
-	snprintf (buffer, sizeof buffer, "%010ld", object->offset);
+	snprintf (buffer, sizeof buffer, "%010ld", object->u.offset);
 	_cairo_output_stream_printf (surface->output,
 				     "%s 00000 n \n", buffer);
     }
@@ -6570,7 +6798,7 @@ _cairo_write_xref_stream_entrys (cairo_pdf_surface_t   *surface,
     /* PDF requires this to be first entry */
     _cairo_write_xref_stream_entry (stream,
 				    0,
-				    0, /* type 0 == free */
+				    PDF_OBJECT_FREE,
 				    field2_size,
 				    0, /* next free object number */
 				    0xffff, /* next generation number */
@@ -6579,13 +6807,31 @@ _cairo_write_xref_stream_entrys (cairo_pdf_surface_t   *surface,
     num_objects = _cairo_array_num_elements (&surface->objects);
     for (i = 0; i < num_objects; i++) {
 	object = _cairo_array_index (&surface->objects, i);
-	_cairo_write_xref_stream_entry (stream,
-					i + 1,
-					1, /* type 1 == in use, not in obj stream */
-					field2_size,
-					object->offset,
-					0, /* generation number */
-					write_as_comments);
+	if (object->type == PDF_OBJECT_UNCOMPRESSED) {
+	    _cairo_write_xref_stream_entry (stream,
+					    i + 1,
+					    object->type,
+					    field2_size,
+					    object->u.offset,
+					    0, /* generation number */
+					    write_as_comments);
+	} else if (object->type == PDF_OBJECT_COMPRESSED) {
+	    _cairo_write_xref_stream_entry (stream,
+					    i + 1,
+					    object->type,
+					    field2_size,
+					    object->u.compressed_obj.xref_stream.id,
+					    object->u.compressed_obj.index,
+					    write_as_comments);
+	} else {
+	    _cairo_write_xref_stream_entry (stream,
+					    i + 1,
+					    PDF_OBJECT_FREE,
+					    field2_size,
+					    0,
+					    0xffff,
+					    write_as_comments);
+	}
     }
 }
 
@@ -6614,7 +6860,6 @@ _cairo_pdf_surface_write_xref_stream (cairo_pdf_surface_t  *surface,
 
     mem_stream = _cairo_memory_stream_create ();
     xref_stream = _cairo_deflate_stream_create (mem_stream);
-
     _cairo_write_xref_stream_entrys (surface, xref_stream, offset_bytes, FALSE);
 
     status = _cairo_output_stream_destroy (xref_stream);
@@ -6647,7 +6892,7 @@ _cairo_pdf_surface_write_xref_stream (cairo_pdf_surface_t  *surface,
 	 * a human readable format of the xref stream as PDF comments.
 	 */
 	_cairo_output_stream_printf (surface->output,
-				     "%%   id   type  offset  gen\n");
+				     "%%   id   type  offset/obj  gen/index\n");
 	_cairo_write_xref_stream_entrys (surface, surface->output, offset_bytes, TRUE);
     }
 
@@ -6659,7 +6904,8 @@ _cairo_pdf_surface_write_xref_stream (cairo_pdf_surface_t  *surface,
 	return status;
 
     _cairo_output_stream_printf (surface->output,
-				 "\nendstream\n"
+				 "\n"
+				 "endstream\n"
 				 "endobj\n");
 
     return _cairo_output_stream_get_status (surface->output);
@@ -7018,6 +7264,10 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
     cairo_int_status_t status;
     unsigned int i, len, page_num, num_annots;
 
+    status = _cairo_pdf_surface_open_object_stream (surface);
+    if (unlikely (status))
+	return status;
+
     status = _cairo_pdf_interchange_write_page_objects (surface);
     if (unlikely (status))
 	return status;
@@ -7086,9 +7336,12 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 
     page_num = _cairo_array_num_elements (&surface->pages);
     page = _cairo_array_index (&surface->pages, page_num - 1);
-    _cairo_pdf_surface_update_object (surface, *page);
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
+
+    status = _cairo_pdf_surface_object_begin (surface, *page);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				 "<< /Type /Page %% %d\n"
 				 "   /Parent %d 0 R\n"
 				 "   /MediaBox [ 0 0 %f %f ]\n"
@@ -7100,7 +7353,6 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 				 "      /CS /DeviceRGB\n"
 				 "   >>\n"
 				 "   /Resources %d 0 R\n",
-				 page->id,
 				 page_num,
 				 surface->pages_resource.id,
 				 surface->width,
@@ -7109,39 +7361,39 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 				 surface->content_resources.id);
 
     if (surface->page_parent_tree >= 0) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /StructParents %d\n",
 				     surface->page_parent_tree);
     }
 
     num_annots = _cairo_array_num_elements (&surface->page_annots);
     if (num_annots > 0) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /Annots [ ");
 	for (i = 0; i < num_annots; i++) {
 	    _cairo_array_copy_element (&surface->page_annots, i, &res);
-	    _cairo_output_stream_printf (surface->output,
+	    _cairo_output_stream_printf (surface->object_stream.stream,
 					 "%d 0 R ",
 					 res.id);
 	}
-	_cairo_output_stream_printf (surface->output, "]\n");
+	_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
     }
 
     if (thumbnail_res.id) {
-	_cairo_output_stream_printf (surface->output,
+	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   /Thumb %d 0 R\n",
 				     thumbnail_res.id);
     }
 
-    _cairo_output_stream_printf (surface->output,
-				 ">>\n"
-				 "endobj\n");
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 ">>\n");
+    _cairo_pdf_surface_object_end (surface);
 
     status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface, FALSE);
     if (unlikely (status))
 	return status;
 
-    return CAIRO_STATUS_SUCCESS;
+    return _cairo_pdf_surface_close_object_stream (surface);
 }
 
 static cairo_int_status_t
commit 0f382eb0875f2102ba26e30c6ea1dfbaf1abff96
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Fri Jul 16 19:18:02 2021 +0930

    pdf: use cross-reference stream for PDF >= 1.5
    
    This reduces the output size and is required for object streams.

diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 3fb8ffaf7..65fd8c741 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -288,7 +288,7 @@ struct _cairo_pdf_surface {
     cairo_pdf_resource_t struct_tree_root;
 
     cairo_pdf_version_t pdf_version;
-    cairo_bool_t compress_content;
+    cairo_bool_t compress_streams;
 
     cairo_pdf_resource_t content;
     cairo_pdf_resource_t content_resources;
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index a9caed8ff..09b2648f1 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -285,6 +285,13 @@ _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface);
 static long
 _cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface);
 
+static cairo_int_status_t
+_cairo_pdf_surface_write_xref_stream (cairo_pdf_surface_t  *surface,
+				      cairo_pdf_resource_t  xref_res,
+				      cairo_pdf_resource_t  root_res,
+				      cairo_pdf_resource_t  info_res,
+				      long                 *xref_offset);
+
 static cairo_int_status_t
 _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface,
 						    cairo_bool_t         finish);
@@ -459,7 +466,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
 
     surface->struct_tree_root.id = 0;
     surface->pdf_version = CAIRO_PDF_VERSION_1_5;
-    surface->compress_content = TRUE;
+    surface->compress_streams = TRUE;
     surface->pdf_stream.active = FALSE;
     surface->pdf_stream.old_output = NULL;
     surface->group_stream.active = FALSE;
@@ -506,7 +513,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->thumbnail_image = NULL;
 
     if (getenv ("CAIRO_DEBUG_PDF") != NULL)
-	surface->compress_content = FALSE;
+	surface->compress_streams = FALSE;
 
     surface->paginated_surface =  _cairo_paginated_surface_create (
 	                                  &surface->base,
@@ -1954,7 +1961,7 @@ _cairo_pdf_surface_write_memory_stream (cairo_pdf_surface_t         *surface,
 				 resource.id,
 				 _cairo_memory_stream_length (mem_stream));
 
-    if (surface->compress_content) {
+    if (surface->compress_streams) {
 	_cairo_output_stream_printf (surface->output,
 				     "   /Filter /FlateDecode\n");
     }
@@ -2003,7 +2010,7 @@ _cairo_pdf_surface_open_group (cairo_pdf_surface_t         *surface,
 
     surface->group_stream.mem_stream = _cairo_memory_stream_create ();
 
-    if (surface->compress_content) {
+    if (surface->compress_streams) {
 	surface->group_stream.stream =
 	    _cairo_deflate_stream_create (surface->group_stream.mem_stream);
     } else {
@@ -2057,7 +2064,7 @@ _cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
     if (unlikely (status))
 	return status;
 
-    if (surface->compress_content) {
+    if (surface->compress_streams) {
 	status = _cairo_output_stream_destroy (surface->group_stream.stream);
 	surface->group_stream.stream = NULL;
 
@@ -2109,7 +2116,7 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 	    status =
 		_cairo_pdf_surface_open_stream (surface,
 						resource,
-						surface->compress_content,
+						surface->compress_streams,
 						"   /Type /XObject\n"
 						"   /Subtype /Form\n"
 						"   /BBox [ %f %f %f %f ]\n"
@@ -2129,7 +2136,7 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 	    status =
 		_cairo_pdf_surface_open_stream (surface,
 						resource,
-						surface->compress_content,
+						surface->compress_streams,
 						"   /Type /XObject\n"
 						"   /Subtype /Form\n"
 						"   /BBox [ %f %f %f %f ]\n"
@@ -2144,7 +2151,7 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 	status =
 	    _cairo_pdf_surface_open_stream (surface,
 					    resource,
-					    surface->compress_content,
+					    surface->compress_streams,
 					    NULL);
 	_cairo_output_stream_printf (surface->output,
 				     "1 0 0 -1 0 %f cm\n",
@@ -2212,6 +2219,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     cairo_pdf_source_surface_t doc_surface;
     cairo_pdf_jbig2_global_t *global;
     char *label;
+    cairo_pdf_resource_t xref_res;
 
     _cairo_pdf_surface_clear (surface);
 
@@ -2232,18 +2240,26 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     if (catalog.id == 0 && status == CAIRO_STATUS_SUCCESS)
 	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-    offset = _cairo_pdf_surface_write_xref (surface);
-
-    _cairo_output_stream_printf (surface->output,
-				 "trailer\n"
-				 "<< /Size %d\n"
-				 "   /Root %d 0 R\n"
-				 "   /Info %d 0 R\n"
-				 ">>\n",
-				 surface->next_available_resource.id,
-				 catalog.id,
-				 surface->docinfo_res.id);
-
+    if (surface->pdf_version >= CAIRO_PDF_VERSION_1_5)
+    {
+	xref_res = _cairo_pdf_surface_new_object (surface);
+	status = _cairo_pdf_surface_write_xref_stream (surface,
+						       xref_res,
+						       catalog,
+						       surface->docinfo_res,
+						       &offset);
+    } else {
+	offset = _cairo_pdf_surface_write_xref (surface);
+	_cairo_output_stream_printf (surface->output,
+				     "trailer\n"
+				     "<< /Size %d\n"
+				     "   /Root %d 0 R\n"
+				     "   /Info %d 0 R\n"
+				     ">>\n",
+				     surface->next_available_resource.id,
+				     catalog.id,
+				     surface->docinfo_res.id);
+    }
     _cairo_output_stream_printf (surface->output,
 				 "startxref\n"
 				 "%ld\n"
@@ -4223,7 +4239,7 @@ cairo_pdf_surface_emit_transparency_group (cairo_pdf_surface_t  *surface,
     }
     status = _cairo_pdf_surface_open_stream (surface,
 					     NULL,
-					     surface->compress_content,
+					     surface->compress_streams,
 					     "   /Type /XObject\n"
 					     "   /Subtype /Form\n"
 					     "   /FormType 1\n"
@@ -5338,7 +5354,7 @@ _cairo_pdf_surface_emit_to_unicode_stream (cairo_pdf_surface_t		*surface,
 
     status = _cairo_pdf_surface_open_stream (surface,
 					      NULL,
-					      surface->compress_content,
+					      surface->compress_streams,
 					      NULL);
     if (unlikely (status))
 	return status;
@@ -6221,7 +6237,7 @@ _cairo_pdf_surface_emit_type3_font_subset (cairo_pdf_surface_t		*surface,
     for (i = 0; i < font_subset->num_glyphs; i++) {
 	status = _cairo_pdf_surface_open_stream (surface,
 						 NULL,
-						 surface->compress_content,
+						 surface->compress_streams,
 						 NULL);
 	if (unlikely (status))
 	    break;
@@ -6515,6 +6531,140 @@ _cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface)
     return offset;
 }
 
+static void
+_cairo_write_xref_stream_entry (cairo_output_stream_t *stream,
+				int                    id,
+				int                    type,
+				int                    field2_size,
+				long                   field2,
+				int                    field3,
+				cairo_bool_t           write_as_comments)
+{
+    char buf[20];
+    int i;
+
+    if (write_as_comments) {
+	_cairo_output_stream_printf (stream, "%% %5d %2d %10ld  %d\n", id, type, field2, field3);
+    } else {
+	/* Each field is big endian */
+	buf[0] = type; /* field 1 */
+	for (i = field2_size - 1; i >= 0; i--) {
+	    buf[i + 1] = field2 & 0xff;
+	    field2 >>= 8;
+	}
+	buf[field2_size + 1] = field3 >> 8;
+	buf[field2_size + 2] = field3 & 0xff;
+	_cairo_output_stream_write (stream, buf, field2_size + 3);
+    }
+}
+
+static void
+_cairo_write_xref_stream_entrys (cairo_pdf_surface_t   *surface,
+				 cairo_output_stream_t *stream,
+				 int                    field2_size,
+				 cairo_bool_t           write_as_comments)
+{
+    cairo_pdf_object_t *object;
+    int num_objects, i;
+
+    /* PDF requires this to be first entry */
+    _cairo_write_xref_stream_entry (stream,
+				    0,
+				    0, /* type 0 == free */
+				    field2_size,
+				    0, /* next free object number */
+				    0xffff, /* next generation number */
+				    write_as_comments);
+
+    num_objects = _cairo_array_num_elements (&surface->objects);
+    for (i = 0; i < num_objects; i++) {
+	object = _cairo_array_index (&surface->objects, i);
+	_cairo_write_xref_stream_entry (stream,
+					i + 1,
+					1, /* type 1 == in use, not in obj stream */
+					field2_size,
+					object->offset,
+					0, /* generation number */
+					write_as_comments);
+    }
+}
+
+static cairo_int_status_t
+_cairo_pdf_surface_write_xref_stream (cairo_pdf_surface_t  *surface,
+				      cairo_pdf_resource_t  xref_res,
+				      cairo_pdf_resource_t  root_res,
+				      cairo_pdf_resource_t  info_res,
+				      long                 *xref_offset)
+{
+    cairo_output_stream_t *mem_stream;
+    cairo_output_stream_t *xref_stream;
+    long offset;
+    int offset_bytes;
+    cairo_status_t status;
+
+    *xref_offset = _cairo_output_stream_get_position (surface->output);
+
+    /* Find the minimum number of bytes required to represent offsets in the generated file (up to this point). */
+    offset_bytes = 0;
+    offset = *xref_offset;
+    while (offset > 0) {
+	offset >>= 8;
+	offset_bytes++;
+    }
+
+    mem_stream = _cairo_memory_stream_create ();
+    xref_stream = _cairo_deflate_stream_create (mem_stream);
+
+    _cairo_write_xref_stream_entrys (surface, xref_stream, offset_bytes, FALSE);
+
+    status = _cairo_output_stream_destroy (xref_stream);
+    if (unlikely (status))
+	return status;
+
+    _cairo_pdf_surface_update_object (surface, xref_res);
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Type /XRef\n"
+				 "   /Length %d\n"
+				 "   /Filter /FlateDecode\n"
+				 "   /Size %d\n"
+				 "   /W [1 %d 2]\n"
+				 "   /Root %d 0 R\n"
+				 "   /Info %d 0 R\n"
+				 ">>\n",
+				 xref_res.id,
+				 _cairo_memory_stream_length (mem_stream),
+				 surface->next_available_resource.id,
+				 offset_bytes,
+				 root_res.id,
+				 info_res.id);
+
+    if (!surface->compress_streams) {
+	/* Adobe Reader requires xref streams to be flate encoded (PDF
+	 * Reference 1.7, implemenation note 20). This means
+	 * compression must always be enabled on this stream. To
+	 * facilitate debugging when compress_stream is disabled, emit
+	 * a human readable format of the xref stream as PDF comments.
+	 */
+	_cairo_output_stream_printf (surface->output,
+				     "%%   id   type  offset  gen\n");
+	_cairo_write_xref_stream_entrys (surface, surface->output, offset_bytes, TRUE);
+    }
+
+    _cairo_output_stream_printf (surface->output,
+				 "stream\n");
+    _cairo_memory_stream_copy (mem_stream, surface->output);
+    status = _cairo_output_stream_destroy (mem_stream);
+    if (unlikely (status))
+	return status;
+
+    _cairo_output_stream_printf (surface->output,
+				 "\nendstream\n"
+				 "endobj\n");
+
+    return _cairo_output_stream_get_status (surface->output);
+}
+
 static cairo_int_status_t
 _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t	*surface,
 				     cairo_pdf_smask_group_t	*group)


More information about the cairo-commit mailing list