1
/*
2
 * Copyright © 2017 Adrian Johnson
3
 *
4
 * Permission is hereby granted, free of charge, to any person
5
 * obtaining a copy of this software and associated documentation
6
 * files (the "Software"), to deal in the Software without
7
 * restriction, including without limitation the rights to use, copy,
8
 * modify, merge, publish, distribute, sublicense, and/or sell copies
9
 * of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 *
24
 * Author: Adrian Johnson <ajohnson@redneon.com>
25
 */
26

            
27

            
28
/* Check that source surfaces with same CAIRO_MIME_TYPE_UNIQUE_ID are
29
 * embedded only once in PDF/PS.
30
 *
31
 * To exercise all the surface embedding code in PS/PDF, four types of
32
 * source surfaces are painted on each page, each with its own UNIQUE_ID:
33
 * - an image surface
34
 * - a recording surface with a jpeg mime attached
35
 * - a bounded recording surface
36
 * - an unbounded recording surface.
37
 *
38
 * Four pages are generated. Each source is clipped starting with the
39
 * smallest area on the first page increasing to the unclipped size on
40
 * the last page. This is to ensure the output does not embed the
41
 * source clipped to a smaller size than used on subsequent pages.
42
 *
43
 * The test verifies the use of UNIQUE_ID by comparing the file size
44
 * with the expected size.
45
 */
46

            
47
#include "cairo-test.h"
48

            
49
#include <math.h>
50
#include <stdio.h>
51
#include <errno.h>
52

            
53
#include <cairo.h>
54

            
55
#if CAIRO_HAS_PS_SURFACE
56
#include <cairo-ps.h>
57
#endif
58

            
59
#if CAIRO_HAS_PDF_SURFACE
60
#include <cairo-pdf.h>
61
#endif
62

            
63
#define NUM_PAGES 4
64

            
65
#define WIDTH  275
66
#define HEIGHT 275
67

            
68
#define BASENAME "mime-unique-id"
69

            
70

            
71
/* Expected file size to check that surfaces are embedded only once.
72
 * SIZE_TOLERANCE should be large enough to allow some variation in
73
 * file size due to changes to the PS/PDF surfaces while being small
74
 * enough to catch any attempt to embed the surface more than
75
 * once. The compressed size of each surface embedded in PDF is:
76
 * - image:    108,774
77
 * - jpeg:      11,400
78
 * - recording: 17,518
79
 *
80
 * If the size check fails, manually check the output and if the
81
 * surfaces are still embedded only once, update the expected sizes.
82
 *
83
 * Note: The PS2 output will embed the image more than once due to the
84
 * lower MAX_L2_FORM_DATA for PS2 in cairo-ps-surface.c.
85
 */
86
#define PS2_EXPECTED_SIZE 626926
87
#define PS3_EXPECTED_SIZE 381555
88
#define PDF_EXPECTED_SIZE 162692
89
#define SIZE_TOLERANCE      5000
90

            
91
static const char *png_filename = "romedalen.png";
92
static const char *jpeg_filename = "romedalen.jpg";
93

            
94
static FILE *
95
my_fopen (cairo_test_context_t *ctx, const char *pathname, const char *mode)
96
{
97
    FILE *f = fopen (pathname, mode);
98
    if (f == NULL && errno == ENOENT && ctx->srcdir) {
99
	char *srcdir_pathname;
100
	xasprintf (&srcdir_pathname, "%s/%s", ctx->srcdir, pathname);
101
	f = fopen (srcdir_pathname, mode);
102
	free (srcdir_pathname);
103
    }
104
    return f;
105
}
106

            
107
static cairo_test_status_t
108
create_image_surface (cairo_test_context_t *ctx, cairo_surface_t **surface)
109
{
110
    cairo_status_t status;
111
    const char *unique_id = "image";
112

            
113
    *surface = cairo_test_create_surface_from_png (ctx, png_filename);
114
    status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
115
					  (unsigned char *)unique_id,
116
					  strlen (unique_id),
117
					  NULL, NULL);
118
    if (status) {
119
	cairo_surface_destroy (*surface);
120
	return cairo_test_status_from_status (ctx, status);
121
    }
122

            
123
    return CAIRO_TEST_SUCCESS;
124
}
125

            
126
static cairo_test_status_t
127
create_recording_surface_with_mime_jpg (cairo_test_context_t *ctx, cairo_surface_t **surface)
128
{
129
    cairo_status_t status;
130
    FILE *f;
131
    unsigned char *data;
132
    long len;
133
    const char *unique_id = "jpeg";
134
    cairo_rectangle_t extents = { 0, 0, 1, 1 };
135

            
136
    *surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &extents);
137
    f = my_fopen (ctx, jpeg_filename, "rb");
138
    if (f == NULL) {
139
	cairo_test_log (ctx, "Unable to open file %s\n", jpeg_filename);
140
	return CAIRO_TEST_FAILURE;
141
    }
142

            
143
    fseek (f, 0, SEEK_END);
144
    len = ftell(f);
145
    fseek (f, 0, SEEK_SET);
146
    data = malloc (len);
147
    if (fread(data, len, 1, f) != 1) {
148
	cairo_test_log (ctx, "Unable to read file %s\n", jpeg_filename);
149
	return CAIRO_TEST_FAILURE;
150
    }
151

            
152
    fclose(f);
153
    status = cairo_surface_set_mime_data (*surface,
154
					  CAIRO_MIME_TYPE_JPEG,
155
					  data, len,
156
					  free, data);
157
    if (status) {
158
	cairo_surface_destroy (*surface);
159
	return cairo_test_status_from_status (ctx, status);
160
    }
161

            
162
    status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
163
					  (unsigned char *)unique_id,
164
					  strlen (unique_id),
165
					  NULL, NULL);
166
    if (status) {
167
	cairo_surface_destroy (*surface);
168
	return cairo_test_status_from_status (ctx, status);
169
    }
170

            
171
    return CAIRO_TEST_SUCCESS;
172
}
173

            
174
static void
175
draw_tile (cairo_t *cr)
176
{
177
    cairo_move_to (cr, 10 + 5, 10);
178
    cairo_arc (cr, 10, 10, 5, 0, 2*M_PI);
179
    cairo_close_path (cr);
180
    cairo_set_source_rgb (cr, 1, 0, 0);
181
    cairo_fill (cr);
182

            
183
    cairo_move_to (cr, 30, 10-10*0.43);
184
    cairo_line_to (cr, 25, 10+10*0.43);
185
    cairo_line_to (cr, 35, 10+10*0.43);
186
    cairo_close_path (cr);
187
    cairo_set_source_rgb (cr, 0, 1, 0);
188
    cairo_fill (cr);
189

            
190
    cairo_rectangle (cr, 5, 25, 10, 10);
191
    cairo_set_source_rgb (cr, 0, 0, 0);
192
    cairo_fill (cr);
193

            
194
    cairo_save (cr);
195
    cairo_translate (cr, 30, 30);
196
    cairo_rotate (cr, M_PI/4.0);
197
    cairo_rectangle (cr, -5, -5, 10, 10);
198
    cairo_set_source_rgb (cr, 1, 0, 1);
199
    cairo_fill (cr);
200
    cairo_restore (cr);
201
}
202

            
203
#define RECORDING_SIZE 800
204
#define TILE_SIZE 40
205

            
206
static cairo_test_status_t
207
create_recording_surface (cairo_test_context_t *ctx, cairo_surface_t **surface, cairo_bool_t bounded)
208
{
209
    cairo_status_t status;
210
    int x, y;
211
    cairo_t *cr;
212
    cairo_matrix_t ctm;
213
    int start, size;
214
    const char *bounded_id = "recording bounded";
215
    const char *unbounded_id = "recording unbounded";
216
    cairo_rectangle_t extents = { 0, 0, RECORDING_SIZE, RECORDING_SIZE };
217

            
218
    if (bounded) {
219
	*surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &extents);
220
	start = 0;
221
	size = RECORDING_SIZE;
222
    } else {
223
	*surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
224
	start = RECORDING_SIZE / 2;
225
	size = RECORDING_SIZE * 2;
226
    }
227

            
228
    /* Draw each tile instead of creating a cairo pattern to make size
229
     * of the emitted recording as large as possible.
230
     */
231
    cr = cairo_create (*surface);
232
    cairo_set_source_rgb (cr, 1, 1, 0);
233
    cairo_paint (cr);
234
    cairo_get_matrix (cr, &ctm);
235
    for (y = start; y < size; y += TILE_SIZE) {
236
	for (x = start; x < size; x += TILE_SIZE) {
237
	    draw_tile (cr);
238
	    cairo_translate (cr, TILE_SIZE, 0);
239
	}
240
	cairo_matrix_translate (&ctm, 0, TILE_SIZE);
241
	cairo_set_matrix (cr, &ctm);
242
    }
243
    cairo_destroy (cr);
244

            
245
    status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
246
					  (unsigned char *)(bounded ? bounded_id : unbounded_id),
247
					  strlen (bounded ? bounded_id : unbounded_id),
248
					  NULL, NULL);
249
    if (status) {
250
	cairo_surface_destroy (*surface);
251
	return cairo_test_status_from_status (ctx, status);
252
    }
253

            
254
    return CAIRO_TEST_SUCCESS;
255
}
256

            
257
/* Draw @source scaled to fit @rect and clipped to a rectangle
258
 * @clip_margin units smaller on each side.  @rect will be stroked
259
 * with a solid line and the clip rect stroked with a dashed line.
260
 */
261
static void
262
draw_surface (cairo_t *cr, cairo_surface_t *source, cairo_rectangle_int_t *rect, int clip_margin)
263
{
264
    cairo_surface_type_t type;
265
    int width, height;
266
    cairo_rectangle_t extents;
267
    const double dashes[2] = { 2, 2 };
268

            
269
    type = cairo_surface_get_type (source);
270
    if (type == CAIRO_SURFACE_TYPE_IMAGE) {
271
	width = cairo_image_surface_get_width (source);
272
	height = cairo_image_surface_get_height (source);
273
    } else {
274
	if (cairo_recording_surface_get_extents (source, &extents)) {
275
	    width = extents.width;
276
	    height = extents.height;
277
	} else {
278
	    width = RECORDING_SIZE;
279
	    height = RECORDING_SIZE;
280
	}
281
    }
282

            
283
    cairo_save (cr);
284
    cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
285
    cairo_stroke (cr);
286
    cairo_rectangle (cr,
287
		     rect->x + clip_margin,
288
		     rect->y + clip_margin,
289
		     rect->width - clip_margin*2,
290
		     rect->height - clip_margin*2);
291
    cairo_set_dash (cr, dashes, 2, 0);
292
    cairo_stroke_preserve (cr);
293
    cairo_clip (cr);
294

            
295
    cairo_translate (cr, rect->x, rect->y);
296
    cairo_scale (cr, (double)rect->width/width, (double)rect->height/height);
297
    cairo_set_source_surface (cr, source, 0, 0);
298
    cairo_paint (cr);
299

            
300
    cairo_restore (cr);
301
}
302

            
303
static cairo_test_status_t
304
draw_pages (cairo_test_context_t *ctx, cairo_surface_t *surface)
305
{
306
    cairo_t *cr;
307
    int i;
308
    cairo_rectangle_int_t img_rect;
309
    cairo_rectangle_int_t jpg_rect;
310
    cairo_rectangle_int_t bounded_rect;
311
    cairo_rectangle_int_t unbounded_rect;
312
    int clip_margin;
313
    cairo_surface_t *source;
314
    cairo_test_status_t status;
315

            
316
    cr = cairo_create (surface);
317

            
318
    /* target area to fill with the image source */
319
    img_rect.x = 25;
320
    img_rect.y = 25;
321
    img_rect.width = 100;
322
    img_rect.height = 100;
323

            
324
    /* target area to fill with the recording with jpeg mime source */
325
    jpg_rect.x = 150;
326
    jpg_rect.y = 25;
327
    jpg_rect.width = 100;
328
    jpg_rect.height = 100;
329

            
330
    /* target area to fill with the bounded recording source */
331
    bounded_rect.x = 25;
332
    bounded_rect.y = 150;
333
    bounded_rect.width = 100;
334
    bounded_rect.height = 100;
335

            
336
    /* target area to fill with the unbounded recording source */
337
    unbounded_rect.x = 150;
338
    unbounded_rect.y = 150;
339
    unbounded_rect.width = 100;
340
    unbounded_rect.height = 100;
341

            
342
    /* Draw the image and recording surface on each page. The sources
343
     * are clipped starting with a small clip area on the first page
344
     * and increasing to the source size on last page to ensure the
345
     * embedded source is not clipped to the area used on the first
346
     * page.
347
     *
348
     * The sources are created each time they are used to ensure
349
     * CAIRO_MIME_TYPE_UNIQUE_ID is tested.
350
     */
351
    for (i = 0; i < NUM_PAGES; i++) {
352
	clip_margin = (NUM_PAGES - i - 1) * 5;
353

            
354
	status = create_image_surface (ctx, &source);
355
	if (status)
356
	    return status;
357
	draw_surface (cr, source, &img_rect, clip_margin);
358
	cairo_surface_destroy (source);
359

            
360
	status = create_recording_surface_with_mime_jpg (ctx, &source);
361
	if (status)
362
	    return status;
363
	draw_surface (cr, source, &jpg_rect, clip_margin);
364
	cairo_surface_destroy (source);
365

            
366
	status = create_recording_surface (ctx, &source, TRUE);
367
	if (status)
368
	    return status;
369
	draw_surface (cr, source, &bounded_rect, clip_margin);
370
	cairo_surface_destroy (source);
371

            
372
	status = create_recording_surface (ctx, &source, FALSE);
373
	if (status)
374
	    return status;
375
	draw_surface (cr, source, &unbounded_rect, clip_margin);
376
	cairo_surface_destroy (source);
377

            
378
	cairo_show_page (cr);
379
    }
380

            
381
    cairo_destroy (cr);
382

            
383
    return CAIRO_TEST_SUCCESS;
384
}
385

            
386
static cairo_test_status_t
387
check_file_size (cairo_test_context_t *ctx, const char *filename, long expected_size)
388
{
389
    FILE *f;
390
    long size;
391

            
392
    f = my_fopen (ctx, filename, "rb");
393
    if (f == NULL) {
394
	cairo_test_log (ctx, "Unable to open file %s\n", filename);
395
	return CAIRO_TEST_FAILURE;
396
    }
397

            
398
    fseek (f, 0, SEEK_END);
399
    size = ftell (f);
400
    fclose(f);
401

            
402
    if (labs(size - expected_size) > SIZE_TOLERANCE) {
403
	cairo_test_log (ctx,
404
			"mime-unique-id: File %s has size %ld. Expected size %ld +/- %ld."
405
			" Check if surfaces are embedded once.\n",
406
			filename, size, expected_size, (long)SIZE_TOLERANCE);
407
	return CAIRO_TEST_FAILURE;
408
    }
409

            
410
    return CAIRO_TEST_SUCCESS;
411
}
412

            
413
static cairo_test_status_t
414
1
preamble (cairo_test_context_t *ctx)
415
{
416
    cairo_surface_t *surface;
417
    cairo_status_t status;
418
    char *filename;
419
1
    cairo_test_status_t result = CAIRO_TEST_UNTESTED;
420
    cairo_test_status_t test_status;
421
1
    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
422

            
423
#if CAIRO_HAS_PS_SURFACE
424
1
    if (cairo_test_is_target_enabled (ctx, "ps2"))
425
    {
426
	xasprintf (&filename, "%s/%s.ps2.out.ps", path, BASENAME);
427
	surface = cairo_ps_surface_create (filename, WIDTH, HEIGHT);
428
	status = cairo_surface_status (surface);
429
	if (status) {
430
	    cairo_test_log (ctx, "Failed to create ps surface for file %s: %s\n",
431
			    filename, cairo_status_to_string (status));
432
	    test_status = CAIRO_TEST_FAILURE;
433
	    goto ps2_finish;
434
	}
435

            
436
	cairo_ps_surface_restrict_to_level (surface, CAIRO_PS_LEVEL_2);
437

            
438
	test_status = draw_pages (ctx, surface);
439
	cairo_surface_destroy (surface);
440

            
441
	if (test_status == CAIRO_TEST_SUCCESS)
442
	    test_status = check_file_size (ctx, filename, PS2_EXPECTED_SIZE);
443

            
444
      ps2_finish:
445
	cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
446
			ctx->test->name,
447
			"ps2",
448
			test_status ? "FAIL" : "PASS");
449

            
450
	if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
451
	    result = test_status;
452

            
453
	free (filename);
454
    }
455

            
456
1
    if (cairo_test_is_target_enabled (ctx, "ps3"))
457
    {
458
	xasprintf (&filename, "%s/%s.ps3.out.ps", path, BASENAME);
459
	surface = cairo_ps_surface_create (filename, WIDTH, HEIGHT);
460
	status = cairo_surface_status (surface);
461
	if (status) {
462
	    cairo_test_log (ctx, "Failed to create ps surface for file %s: %s\n",
463
			    filename, cairo_status_to_string (status));
464
	    test_status = CAIRO_TEST_FAILURE;
465
	    goto ps3_finish;
466
	}
467

            
468
	test_status = draw_pages (ctx, surface);
469
	cairo_surface_destroy (surface);
470

            
471
	if (test_status == CAIRO_TEST_SUCCESS)
472
	    test_status = check_file_size (ctx, filename, PS3_EXPECTED_SIZE);
473

            
474
      ps3_finish:
475
	cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
476
			ctx->test->name,
477
			"ps3",
478
			test_status ? "FAIL" : "PASS");
479

            
480
	if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
481
	    result = test_status;
482

            
483
	free (filename);
484
    }
485
#endif
486

            
487
#if CAIRO_HAS_PDF_SURFACE
488
1
    if (cairo_test_is_target_enabled (ctx, "pdf"))
489
    {
490
	xasprintf (&filename, "%s/%s.pdf.out.pdf", path, BASENAME);
491
	surface = cairo_pdf_surface_create (filename, WIDTH, HEIGHT);
492
	status = cairo_surface_status (surface);
493
	if (status) {
494
	    cairo_test_log (ctx, "Failed to create pdf surface for file %s: %s\n",
495
			    filename, cairo_status_to_string (status));
496
	    test_status = CAIRO_TEST_FAILURE;
497
	    goto pdf_finish;
498
	}
499

            
500
	test_status = draw_pages (ctx, surface);
501
	cairo_surface_destroy (surface);
502

            
503
	if (test_status == CAIRO_TEST_SUCCESS)
504
	    test_status = check_file_size (ctx, filename, PDF_EXPECTED_SIZE);
505

            
506

            
507
      pdf_finish:
508
	cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
509
			ctx->test->name,
510
			"pdf",
511
			test_status ? "FAIL" : "PASS");
512

            
513
	if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
514
	    result = test_status;
515

            
516
	free (filename);
517
    }
518
#endif
519

            
520
1
    return result;
521
}
522

            
523
1
CAIRO_TEST (mime_unique_id,
524
	    "Check that paginated surfaces embed only one copy of surfaces with the same CAIRO_MIME_TYPE_UNIQUE_ID.",
525
	    "paginated", /* keywords */
526
	    "target=vector", /* requirements */
527
	    0, 0,
528
	    preamble, NULL)