Merge branch 'issue907' into 'master'

Fix invalid pdf generation with too many objects in object stream

See merge request cairo/cairo!642
This commit is contained in:
calixteman 2026-04-03 07:29:19 +00:00
commit b13698ff65
3 changed files with 231 additions and 38 deletions

View file

@ -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,

View file

@ -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',

178
test/pdf-large-objstm.c Normal file
View file

@ -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 <ajohnson@redneon.com>
*/
#include "cairo-test.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* __unix__ */
#endif
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <cairo.h>
#include <cairo-pdf.h>
/* 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)