diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c index 5c57973bc..cd6c981fc 100644 --- a/src/cairo-pdf-surface.c +++ b/src/cairo-pdf-surface.c @@ -2310,44 +2310,6 @@ _cairo_pdf_surface_open_object_stream (cairo_pdf_surface_t *surface) 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_pdf_surface_update_object (surface, resource); - _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) @@ -2467,6 +2429,58 @@ _cairo_pdf_surface_close_object_stream (cairo_pdf_surface_t *surface) return _cairo_output_stream_get_status (surface->output); } +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; + int num_objects; + + if (surface->object_stream.active) { + num_objects = _cairo_array_num_elements (&surface->object_stream.objects); + if (unlikely (num_objects == 65536)) { + // In the XRef table, the generation number is stored as a 16-bit integer. + // Therefore, we can only have 65536 objects in a single object stream. + // Close the current object stream and open a new one. + status = _cairo_pdf_surface_close_object_stream(surface); + if (unlikely (status)) + return status; + status = _cairo_pdf_surface_open_object_stream(surface); + if (unlikely (status)) + return status; + } + + 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_pdf_surface_update_object (surface, resource); + _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 cairo_int_status_t _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t *surface, const cairo_box_double_t *bbox, diff --git a/test/meson.build b/test/meson.build index 3b460afc5..9d564d7f8 100644 --- a/test/meson.build +++ b/test/meson.build @@ -452,6 +452,7 @@ test_quartz_sources = [ test_pdf_sources = [ 'pdf-features.c', + 'pdf-large-objstm.c', 'pdf-mime-data.c', 'pdf-operators-text.c', 'pdf-surface-source.c', diff --git a/test/pdf-large-objstm.c b/test/pdf-large-objstm.c new file mode 100644 index 000000000..834e2c126 --- /dev/null +++ b/test/pdf-large-objstm.c @@ -0,0 +1,178 @@ +/* + * Copyright © 2023 Adrian Johnson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Author: Adrian Johnson + */ + +#include "cairo-test.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_MMAP +#include +#endif + +#ifdef HAVE_UNISTD_H +#include /* __unix__ */ +#endif +#if HAVE_SYS_WAIT_H +#include +#endif + +#include +#include + +/* Test PDF with a large number of objects to ensure that + * object streams are correctly handled. + */ + +#define BASENAME "pdf-large-objstm" + +static void +draw_link(cairo_t *cr) +{ + cairo_tag_begin(cr, CAIRO_TAG_LINK, "uri='http://www.mozilla.org/'"); + cairo_move_to (cr, 0, 0); + cairo_show_text (cr, "a"); + cairo_tag_end(cr, CAIRO_TAG_LINK); +} + +#ifdef HAVE_MMAP +static cairo_test_status_t +check_contains_string(cairo_test_context_t *ctx, const void *hay, size_t size, const char *needle) +{ + if (memmem(hay, size, needle, strlen(needle))) + return CAIRO_TEST_SUCCESS; + + cairo_test_log (ctx, "Failed to find expected string in generated PDF: %s\n", needle); + return CAIRO_TEST_FAILURE; +} +#endif + +static cairo_test_status_t +check_created_pdf(cairo_test_context_t *ctx, const char* filename) +{ + cairo_test_status_t result = CAIRO_TEST_SUCCESS; + int fd; + struct stat st; +#ifdef HAVE_MMAP + void *contents; +#endif + + fd = open(filename, O_RDONLY, 0); + if (fd < 0) { + cairo_test_log (ctx, "Failed to open generated PDF file %s: %s\n", filename, strerror(errno)); + return CAIRO_TEST_FAILURE; + } + + if (fstat(fd, &st) == -1) + { + cairo_test_log (ctx, "Failed to stat generated PDF file %s: %s\n", filename, strerror(errno)); + close(fd); + return CAIRO_TEST_FAILURE; + } + + if (st.st_size == 0) + { + cairo_test_log (ctx, "Generated PDF file %s is empty\n", filename); + close(fd); + return CAIRO_TEST_FAILURE; + } + +#ifdef HAVE_MMAP + + printf("Checking generated PDF file %s\n", filename); + + contents = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (contents == NULL) + { + cairo_test_log (ctx, "Failed to mmap generated PDF file %s: %s\n", filename, strerror(errno)); + close(fd); + return CAIRO_TEST_FAILURE; + } + + /* check metadata */ + result |= check_contains_string(ctx, contents, st.st_size, "/N 65536"); + result |= check_contains_string(ctx, contents, st.st_size, "/N 14470"); + + munmap(contents, st.st_size); +#endif + + close(fd); + + return result; +} + +static cairo_test_status_t +create_pdf (cairo_test_context_t *ctx) +{ + cairo_surface_t *surface; + cairo_t *cr; + cairo_test_status_t result; + char *filename; + const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : "."; + + xasprintf (&filename, "%s/%s.pdf", path, BASENAME); + surface = cairo_pdf_surface_create (filename, 100, 100); + cr = cairo_create(surface); + + for (int i = 0; i < 40000; i++) { + draw_link(cr); + } + + cairo_show_page (cr); + + cairo_surface_destroy (surface); + cairo_destroy (cr); + + result = check_created_pdf(ctx, filename); + + free (filename); + + return result; +} + +static cairo_test_status_t +preamble (cairo_test_context_t *ctx) +{ + cairo_test_status_t result; + + if (! cairo_test_is_target_enabled (ctx, "pdf")) + return CAIRO_TEST_UNTESTED; + + result = create_pdf (ctx); + + return result; +} + +CAIRO_TEST (pdf_large_objstm, + "Test with a lot of PDF objects", + "source", /* keywords */ + NULL, /* requirements */ + 100, 100, + preamble, NULL)