summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Johnson <ajohnson@redneon.com>2017-11-09 20:52:36 +1030
committerAdrian Johnson <ajohnson@redneon.com>2017-11-09 20:52:36 +1030
commit5e4707a10d476604614bddcc9ba8b06f6002e6d6 (patch)
tree3047f3ad91d9c836be279bd43e391eb9bc7d1ca8
parent38fbe621cf80d560cfc27b54b5417b62cda64c8a (diff)
Add mime-unique-id test
to check that PS/PDF embeds images with CAIRO_MIME_TYPE_UNIQUE_ID only once.
-rw-r--r--test/Makefile.sources2
-rwxr-xr-xtest/mime-unique-id.c511
2 files changed, 512 insertions, 1 deletions
diff --git a/test/Makefile.sources b/test/Makefile.sources
index 1ae62464f..a3134d51f 100644
--- a/test/Makefile.sources
+++ b/test/Makefile.sources
@@ -440,7 +440,7 @@ xlib_surface_test_sources = \
xlib_xrender_surface_test_sources = get-xrender-format.c
-multi_page_surface_test_sources = multi-page.c
+multi_page_surface_test_sources = multi-page.c mime-unique-id.c
fallback_resolution_test_sources = fallback-resolution.c
diff --git a/test/mime-unique-id.c b/test/mime-unique-id.c
new file mode 100755
index 000000000..78957fc51
--- /dev/null
+++ b/test/mime-unique-id.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright © 2017 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>
+ */
+
+
+/* Check that source surfaces with same CAIRO_MIME_TYPE_UNIQUE_ID are
+ * embedded only once in PDF/PS.
+ *
+ * To exercise all the surface embedding code in PS/PDF, four types of
+ * source surfaces are painted on each page, each with its own UNIQUE_ID:
+ * - an image surface
+ * - a recording surface with a jpeg mime attached
+ * - a bounded recording surface
+ * - an unbounded recording surface.
+ *
+ * Four pages are generated. Each source is clipped starting with the
+ * smallest area on the first page increasing to the unclipped size on
+ * the last page. This is to ensure the output does not embed the
+ * source clipped to a smaller size than used on subsequent pages.
+ *
+ * The test verifies the use of UNIQUE_ID by comparing the file size
+ * with the expected size.
+ */
+
+#include "cairo-test.h"
+
+#include <math.h>
+#include <stdio.h>
+
+#include <cairo.h>
+
+#if CAIRO_HAS_PS_SURFACE
+#include <cairo-ps.h>
+#endif
+
+#if CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+
+#define NUM_PAGES 4
+
+#define WIDTH 275
+#define HEIGHT 275
+
+#define BASENAME "mime-unique-id"
+
+
+/* Expected file size to check that surfaces are embedded only once.
+ * SIZE_TOLERANCE should be large enough to allow some variation in
+ * file size due to changes to the PS/PDF surfaces while being small
+ * enough to catch any attempt to embed the surface more than
+ * once. The compressed size of each surface embedded in PDF is:
+ * - image: 111,952
+ * - jpeg: 11,400
+ * - recording: 17,518
+ *
+ * If the size check fails, manually check the output and if the
+ * surfaces are still embedded only once, update the expected sizes.
+ */
+#define PS2_EXPECTED_SIZE 315362
+#define PS3_EXPECTED_SIZE 315362
+#define PDF_EXPECTED_SIZE 142968
+#define SIZE_TOLERANCE 5000
+
+static const char *png_filename = "romedalen.png";
+static const char *jpeg_filename = "romedalen.jpg";
+
+static cairo_test_status_t
+create_image_surface (cairo_test_context_t *ctx, cairo_surface_t **surface)
+{
+ cairo_status_t status;
+ const char *unique_id = "image";
+
+ *surface = cairo_test_create_surface_from_png (ctx, png_filename);
+ status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+ (unsigned char *)unique_id,
+ strlen (unique_id),
+ NULL, NULL);
+ if (status) {
+ cairo_surface_destroy (*surface);
+ return cairo_test_status_from_status (ctx, status);
+ }
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+create_recording_surface_with_mime_jpg (cairo_test_context_t *ctx, cairo_surface_t **surface)
+{
+ cairo_status_t status;
+ FILE *f;
+ unsigned char *data;
+ long len;
+ const char *unique_id = "jpeg";
+ cairo_rectangle_t extents = { 0, 0, 1, 1 };
+
+ *surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &extents);
+ f = fopen (jpeg_filename, "rb");
+ if (f == NULL) {
+ cairo_test_log (ctx, "Unable to open file %s\n", jpeg_filename);
+ return CAIRO_TEST_FAILURE;
+ }
+
+ fseek (f, 0, SEEK_END);
+ len = ftell(f);
+ fseek (f, 0, SEEK_SET);
+ data = malloc (len);
+ if (fread(data, len, 1, f) != 1) {
+ cairo_test_log (ctx, "Unable to read file %s\n", jpeg_filename);
+ return CAIRO_TEST_FAILURE;
+ }
+
+ fclose(f);
+ status = cairo_surface_set_mime_data (*surface,
+ CAIRO_MIME_TYPE_JPEG,
+ data, len,
+ free, data);
+ if (status) {
+ cairo_surface_destroy (*surface);
+ return cairo_test_status_from_status (ctx, status);
+ }
+
+ status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+ (unsigned char *)unique_id,
+ strlen (unique_id),
+ NULL, NULL);
+ if (status) {
+ cairo_surface_destroy (*surface);
+ return cairo_test_status_from_status (ctx, status);
+ }
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+static void
+draw_tile (cairo_t *cr)
+{
+ cairo_move_to (cr, 10 + 5, 10);
+ cairo_arc (cr, 10, 10, 5, 0, 2*M_PI);
+ cairo_close_path (cr);
+ cairo_set_source_rgb (cr, 1, 0, 0);
+ cairo_fill (cr);
+
+ cairo_move_to (cr, 30, 10-10*0.43);
+ cairo_line_to (cr, 25, 10+10*0.43);
+ cairo_line_to (cr, 35, 10+10*0.43);
+ cairo_close_path (cr);
+ cairo_set_source_rgb (cr, 0, 1, 0);
+ cairo_fill (cr);
+
+ cairo_rectangle (cr, 5, 25, 10, 10);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_fill (cr);
+
+ cairo_save (cr);
+ cairo_translate (cr, 30, 30);
+ cairo_rotate (cr, M_PI/4.0);
+ cairo_rectangle (cr, -5, -5, 10, 10);
+ cairo_set_source_rgb (cr, 1, 0, 1);
+ cairo_fill (cr);
+ cairo_restore (cr);
+}
+
+#define RECORDING_SIZE 800
+#define TILE_SIZE 40
+
+static cairo_test_status_t
+create_recording_surface (cairo_test_context_t *ctx, cairo_surface_t **surface, cairo_bool_t bounded)
+{
+ cairo_status_t status;
+ int x, y;
+ cairo_t *cr;
+ cairo_matrix_t ctm;
+ int start, size;
+ const char *bounded_id = "recording bounded";
+ const char *unbounded_id = "recording unbounded";
+ cairo_rectangle_t extents = { 0, 0, RECORDING_SIZE, RECORDING_SIZE };
+
+ if (bounded) {
+ *surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &extents);
+ start = 0;
+ size = RECORDING_SIZE;
+ } else {
+ *surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+ start = RECORDING_SIZE / 2;
+ size = RECORDING_SIZE * 2;
+ }
+
+ /* Draw each tile instead of creating a cairo pattern to make size
+ * of the emitted recording as large as possible.
+ */
+ cr = cairo_create (*surface);
+ cairo_set_source_rgb (cr, 1, 1, 0);
+ cairo_paint (cr);
+ cairo_get_matrix (cr, &ctm);
+ for (y = start; y < size; y += TILE_SIZE) {
+ for (x = start; x < size; x += TILE_SIZE) {
+ draw_tile (cr);
+ cairo_translate (cr, TILE_SIZE, 0);
+ }
+ cairo_matrix_translate (&ctm, 0, TILE_SIZE);
+ cairo_set_matrix (cr, &ctm);
+ }
+ cairo_destroy (cr);
+
+ status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+ (unsigned char *)(bounded ? bounded_id : unbounded_id),
+ strlen (bounded ? bounded_id : unbounded_id),
+ NULL, NULL);
+ if (status) {
+ cairo_surface_destroy (*surface);
+ return cairo_test_status_from_status (ctx, status);
+ }
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+/* Draw @source scaled to fit @rect and clipped to a rectangle
+ * @clip_margin units smaller on each side. @rect will be stroked
+ * with a solid line and the clip rect stroked with a dashed line.
+ */
+static void
+draw_surface (cairo_t *cr, cairo_surface_t *source, cairo_rectangle_int_t *rect, int clip_margin)
+{
+ cairo_surface_type_t type;
+ int width, height;
+ cairo_rectangle_t extents;
+ const double dashes[2] = { 2, 2 };
+
+ type = cairo_surface_get_type (source);
+ if (type == CAIRO_SURFACE_TYPE_IMAGE) {
+ width = cairo_image_surface_get_width (source);
+ height = cairo_image_surface_get_height (source);
+ } else {
+ if (cairo_recording_surface_get_extents (source, &extents)) {
+ width = extents.width;
+ height = extents.height;
+ } else {
+ width = RECORDING_SIZE;
+ height = RECORDING_SIZE;
+ }
+ }
+
+ cairo_save (cr);
+ cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
+ cairo_stroke (cr);
+ cairo_rectangle (cr,
+ rect->x + clip_margin,
+ rect->y + clip_margin,
+ rect->width - clip_margin*2,
+ rect->height - clip_margin*2);
+ cairo_set_dash (cr, dashes, 2, 0);
+ cairo_stroke_preserve (cr);
+ cairo_clip (cr);
+
+ cairo_translate (cr, rect->x, rect->y);
+ cairo_scale (cr, (double)rect->width/width, (double)rect->height/height);
+ cairo_set_source_surface (cr, source, 0, 0);
+ cairo_paint (cr);
+
+ cairo_restore (cr);
+}
+
+static cairo_test_status_t
+draw_pages (cairo_test_context_t *ctx, cairo_surface_t *surface)
+{
+ cairo_t *cr;
+ int i;
+ cairo_rectangle_int_t img_rect;
+ cairo_rectangle_int_t jpg_rect;
+ cairo_rectangle_int_t bounded_rect;
+ cairo_rectangle_int_t unbounded_rect;
+ int clip_margin;
+ cairo_surface_t *source;
+ cairo_test_status_t status;
+
+ cr = cairo_create (surface);
+
+ /* target area to fill with the image source */
+ img_rect.x = 25;
+ img_rect.y = 25;
+ img_rect.width = 100;
+ img_rect.height = 100;
+
+ /* target area to fill with the recording with jpeg mime source */
+ jpg_rect.x = 150;
+ jpg_rect.y = 25;
+ jpg_rect.width = 100;
+ jpg_rect.height = 100;
+
+ /* target area to fill with the bounded recording source */
+ bounded_rect.x = 25;
+ bounded_rect.y = 150;
+ bounded_rect.width = 100;
+ bounded_rect.height = 100;
+
+ /* target area to fill with the unbounded recording source */
+ unbounded_rect.x = 150;
+ unbounded_rect.y = 150;
+ unbounded_rect.width = 100;
+ unbounded_rect.height = 100;
+
+ /* Draw the image and recording surface on each page. The sources
+ * are clipped starting with a small clip area on the first page
+ * and increasing to the source size on last page to ensure the
+ * embedded source is not clipped to the area used on the first
+ * page.
+ *
+ * The sources are created each time they are used to ensure
+ * CAIRO_MIME_TYPE_UNIQUE_ID is tested.
+ */
+ for (i = 0; i < NUM_PAGES; i++) {
+ clip_margin = (NUM_PAGES - i - 1) * 5;
+
+ status = create_image_surface (ctx, &source);
+ if (status)
+ return status;
+ draw_surface (cr, source, &img_rect, clip_margin);
+ cairo_surface_destroy (source);
+
+ status = create_recording_surface_with_mime_jpg (ctx, &source);
+ if (status)
+ return status;
+ draw_surface (cr, source, &jpg_rect, clip_margin);
+ cairo_surface_destroy (source);
+
+ status = create_recording_surface (ctx, &source, TRUE);
+ if (status)
+ return status;
+ draw_surface (cr, source, &bounded_rect, clip_margin);
+ cairo_surface_destroy (source);
+
+ status = create_recording_surface (ctx, &source, FALSE);
+ if (status)
+ return status;
+ draw_surface (cr, source, &unbounded_rect, clip_margin);
+ cairo_surface_destroy (source);
+
+ cairo_show_page (cr);
+ }
+
+ cairo_destroy (cr);
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+check_file_size (cairo_test_context_t *ctx, const char *filename, long expected_size)
+{
+ FILE *f;
+ long size;
+
+ f = fopen (filename, "rb");
+ if (f == NULL) {
+ cairo_test_log (ctx, "Unable to open file %s\n", filename);
+ return CAIRO_TEST_FAILURE;
+ }
+
+ fseek (f, 0, SEEK_END);
+ size = ftell (f);
+ fclose(f);
+
+ if (labs(size - expected_size) > SIZE_TOLERANCE) {
+ cairo_test_log (ctx,
+ "mime-unique-id: File %s has size %ld. Expected size %ld +/- %ld."
+ " Check if surfaces are embedded once.\n",
+ filename, size, expected_size, (long)SIZE_TOLERANCE);
+ return CAIRO_TEST_FAILURE;
+ }
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+ cairo_surface_t *surface;
+ cairo_status_t status;
+ char *filename;
+ cairo_test_status_t result = CAIRO_TEST_UNTESTED;
+ cairo_test_status_t test_status;
+ const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
+
+#if CAIRO_HAS_PS_SURFACE
+ if (cairo_test_is_target_enabled (ctx, "ps2"))
+ {
+ xasprintf (&filename, "%s/%s.ps2.out.ps", path, BASENAME);
+ surface = cairo_ps_surface_create (filename, WIDTH, HEIGHT);
+ status = cairo_surface_status (surface);
+ if (status) {
+ cairo_test_log (ctx, "Failed to create ps surface for file %s: %s\n",
+ filename, cairo_status_to_string (status));
+ test_status = CAIRO_TEST_FAILURE;
+ goto ps2_finish;
+ }
+
+ cairo_ps_surface_restrict_to_level (surface, CAIRO_PS_LEVEL_2);
+
+ test_status = draw_pages (ctx, surface);
+ cairo_surface_destroy (surface);
+
+ if (test_status == CAIRO_TEST_SUCCESS)
+ test_status = check_file_size (ctx, filename, PS2_EXPECTED_SIZE);
+
+ ps2_finish:
+ cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
+ ctx->test->name,
+ "ps2",
+ test_status ? "FAIL" : "PASS");
+
+ if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
+ result = test_status;
+
+ free (filename);
+ }
+
+ if (cairo_test_is_target_enabled (ctx, "ps3"))
+ {
+ xasprintf (&filename, "%s/%s.ps3.out.ps", path, BASENAME);
+ surface = cairo_ps_surface_create (filename, WIDTH, HEIGHT);
+ status = cairo_surface_status (surface);
+ if (status) {
+ cairo_test_log (ctx, "Failed to create ps surface for file %s: %s\n",
+ filename, cairo_status_to_string (status));
+ test_status = CAIRO_TEST_FAILURE;
+ goto ps3_finish;
+ }
+
+ test_status = draw_pages (ctx, surface);
+ cairo_surface_destroy (surface);
+
+ if (test_status == CAIRO_TEST_SUCCESS)
+ test_status = check_file_size (ctx, filename, PS3_EXPECTED_SIZE);
+
+ ps3_finish:
+ cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
+ ctx->test->name,
+ "ps3",
+ test_status ? "FAIL" : "PASS");
+
+ if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
+ result = test_status;
+
+ free (filename);
+ }
+#endif
+
+#if CAIRO_HAS_PDF_SURFACE
+ if (cairo_test_is_target_enabled (ctx, "pdf"))
+ {
+ xasprintf (&filename, "%s/%s.pdf.out.pdf", path, BASENAME);
+ surface = cairo_pdf_surface_create (filename, WIDTH, HEIGHT);
+ status = cairo_surface_status (surface);
+ if (status) {
+ cairo_test_log (ctx, "Failed to create pdf surface for file %s: %s\n",
+ filename, cairo_status_to_string (status));
+ test_status = CAIRO_TEST_FAILURE;
+ goto pdf_finish;
+ }
+
+ test_status = draw_pages (ctx, surface);
+ cairo_surface_destroy (surface);
+
+ if (test_status == CAIRO_TEST_SUCCESS)
+ test_status = check_file_size (ctx, filename, PDF_EXPECTED_SIZE);
+
+
+ pdf_finish:
+ cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
+ ctx->test->name,
+ "pdf",
+ test_status ? "FAIL" : "PASS");
+
+ if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
+ result = test_status;
+
+ free (filename);
+ }
+#endif
+
+ return result;
+}
+
+CAIRO_TEST (mime_unique_id,
+ "Check that paginated surfaces embed only one copy of surfaces with the same CAIRO_MIME_TYPE_UNIQUE_ID.",
+ "paginated", /* keywords */
+ "target=vector", /* requirements */
+ 0, 0,
+ preamble, NULL)