1
/*
2
 * Copyright © 2023 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
#include "config.h"
28

            
29
#include <math.h>
30
#include <stdio.h>
31
#include <stdlib.h>
32
#include <cairo.h>
33

            
34
#if CAIRO_HAS_PDF_SURFACE && CAIRO_HAS_PS_SURFACE && CAIRO_HAS_SVG_SURFACE
35

            
36
#include <cairo-pdf.h>
37
#include <cairo-ps.h>
38
#include <cairo-svg.h>
39

            
40
#ifdef HAVE_UNISTD_H
41
#include <unistd.h>
42
#include <errno.h>
43
#endif
44

            
45
#include "cairo-test.h"
46
#include "buffer-diff.h"
47

            
48
/* This test is to ensure that _cairo_recording_surface_replay_and_create_regions()
49
 * works with a recording surface source re-used for multiple paginated surfaces.
50
 * The prior use of the source with a paginated surface should not affect the region
51
 * analysis on subsequent surfaces.
52
 *
53
 * If test output should only contain fallback images for unsupported
54
 * operations.  If the recording surface is incorrectly re-using the
55
 * analysis from a different target, some operations may be missing
56
 * from the ouput (recording surface marked the operation as supported
57
 * when it is not) or some operations may be have fallbacks for
58
 * natively suported opetions (recording surface marked a supported
59
 * operation as unsupprted).
60
 *
61
 * To create ref images, run the test for one target at a time to
62
 * prevent re-use of the recording surface for different targets.
63
 */
64

            
65

            
66
#define SIZE 40
67
#define PAD 25
68
#define PAGE_SIZE (SIZE*3 + PAD*4)
69

            
70
/* Apply a slight rotation and use a very low fallback resolution to
71
 * ensure fallback images are apparent in the output. */
72
#define ROTATE 5
73
#define FALLBACK_PPI 18
74

            
75
static void
76
3
create_recordings (cairo_operator_t    op,
77
                   cairo_surface_t   **recording,
78
                   cairo_surface_t   **recording_group)
79
{
80
3
    *recording = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
81
3
    cairo_t *cr = cairo_create (*recording);
82

            
83
3
    cairo_rotate (cr, ROTATE*M_PI/180.0);
84

            
85
3
    cairo_rectangle (cr, 0, 0, SIZE*3.0/4.0, SIZE*3.0/4.0);
86
3
    cairo_set_source_rgb (cr, 1, 0, 0);
87
3
    cairo_fill (cr);
88

            
89
3
    cairo_set_operator (cr, op);
90

            
91
3
    cairo_rectangle (cr, SIZE/4.0, SIZE/4.0, SIZE*3.0/4.0, SIZE*3.0/4.0);
92
3
    cairo_set_source_rgb (cr, 0, 1, 0);
93
3
    cairo_fill (cr);
94

            
95
3
    cairo_destroy (cr);
96

            
97
3
    *recording_group = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
98
3
    cr = cairo_create (*recording_group);
99

            
100
3
    cairo_set_source_surface (cr, *recording, 0, 0);
101
3
    cairo_paint (cr);
102

            
103
3
    cairo_destroy (cr);
104
3
}
105

            
106
static void
107
_xunlink (const cairo_test_context_t *ctx, const char *pathname)
108
{
109
    if (unlink (pathname) < 0 && errno != ENOENT) {
110
	cairo_test_log (ctx, "Error: Cannot remove %s: %s\n",
111
			pathname, strerror (errno));
112
	exit (1);
113
    }
114
}
115

            
116
static cairo_bool_t
117
check_result (cairo_test_context_t *ctx,
118
	      const cairo_boilerplate_target_t *target,
119
	      const char *test_name,
120
	      const char *base_name,
121
	      cairo_surface_t *surface)
122
{
123
    const char *format;
124
    char *ref_name;
125
    char *png_name;
126
    char *diff_name;
127
    cairo_surface_t *test_image, *ref_image, *diff_image;
128
    buffer_diff_result_t result;
129
    cairo_status_t status;
130
    cairo_bool_t ret;
131

            
132
    if (target->finish_surface != NULL) {
133
	status = target->finish_surface (surface);
134
	if (status) {
135
	    cairo_test_log (ctx, "Error: Failed to finish surface: %s\n",
136
		    cairo_status_to_string (status));
137
	    cairo_surface_destroy (surface);
138
	    return FALSE;
139
	}
140
    }
141

            
142
    xasprintf (&png_name,  "%s.out.png", base_name);
143
    xasprintf (&diff_name, "%s.diff.png", base_name);
144

            
145
    test_image = target->get_image_surface (surface, 0, PAGE_SIZE, PAGE_SIZE);
146
    if (cairo_surface_status (test_image)) {
147
	cairo_test_log (ctx, "Error: Failed to extract page: %s\n",
148
		        cairo_status_to_string (cairo_surface_status (test_image)));
149
	cairo_surface_destroy (test_image);
150
	free (png_name);
151
	free (diff_name);
152
	return FALSE;
153
    }
154

            
155
    _xunlink (ctx, png_name);
156
    status = cairo_surface_write_to_png (test_image, png_name);
157
    if (status) {
158
	cairo_test_log (ctx, "Error: Failed to write output image: %s\n",
159
		cairo_status_to_string (status));
160
	cairo_surface_destroy (test_image);
161
	free (png_name);
162
	free (diff_name);
163
	return FALSE;
164
    }
165

            
166
    format = cairo_boilerplate_content_name (target->content);
167
    ref_name = cairo_test_reference_filename (ctx,
168
					      base_name,
169
					      test_name,
170
					      target->name,
171
					      target->basename,
172
					      format,
173
					      CAIRO_TEST_REF_SUFFIX,
174
					      CAIRO_TEST_PNG_EXTENSION);
175
    if (ref_name == NULL) {
176
	cairo_test_log (ctx, "Error: Cannot find reference image for %s\n",
177
		        base_name);
178
	cairo_surface_destroy (test_image);
179
	free (png_name);
180
	free (diff_name);
181
	return FALSE;
182
    }
183

            
184
    ref_image = cairo_test_get_reference_image (ctx, ref_name,
185
	    target->content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED);
186
    if (cairo_surface_status (ref_image)) {
187
	cairo_test_log (ctx, "Error: Cannot open reference image for %s: %s\n",
188
		        ref_name,
189
		cairo_status_to_string (cairo_surface_status (ref_image)));
190
	cairo_surface_destroy (ref_image);
191
	cairo_surface_destroy (test_image);
192
	free (png_name);
193
	free (diff_name);
194
	free (ref_name);
195
	return FALSE;
196
    }
197

            
198
    diff_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
199
	    PAGE_SIZE, PAGE_SIZE);
200

            
201
    ret = TRUE;
202
    status = image_diff (ctx,
203
	    test_image, ref_image, diff_image,
204
	    &result);
205
    _xunlink (ctx, diff_name);
206
    if (status) {
207
	cairo_test_log (ctx, "Error: Failed to compare images: %s\n",
208
			cairo_status_to_string (status));
209
	ret = FALSE;
210
    } else if (image_diff_is_failure (&result, target->error_tolerance))
211
    {
212
	ret = FALSE;
213

            
214
	status = cairo_surface_write_to_png (diff_image, diff_name);
215
	if (status) {
216
	    cairo_test_log (ctx, "Error: Failed to write differences image: %s\n",
217
		    cairo_status_to_string (status));
218
	}
219
    }
220

            
221
    cairo_surface_destroy (test_image);
222
    cairo_surface_destroy (diff_image);
223
    free (png_name);
224
    free (diff_name);
225
    free (ref_name);
226

            
227
    return ret;
228
}
229

            
230
#define TEST_ROWS 2
231
#define TEST_COLS 3
232

            
233
static void
234
3
draw (cairo_t *cr, cairo_surface_t *recordings[TEST_ROWS][TEST_COLS])
235
{
236
3
    cairo_translate (cr, PAD, PAD);
237
9
    for (int row = 0; row < TEST_ROWS; row++) {
238
6
        cairo_save (cr);
239
24
        for (int col = 0; col < TEST_COLS; col++) {
240
18
            cairo_save (cr);
241
18
            cairo_set_source_surface (cr, recordings[row][col], 0, 0);
242
18
            cairo_paint (cr);
243
18
            cairo_restore (cr);
244
18
            cairo_translate (cr, SIZE + PAD, 0);
245
        }
246
6
        cairo_restore (cr);
247
6
        cairo_translate (cr, 0, SIZE + PAD);
248
    }
249
3
}
250

            
251
#define NUM_TEST_SURFACES 3
252

            
253
typedef struct _test_surface {
254
    const cairo_boilerplate_target_t *target;
255
    cairo_surface_t *surface;
256
    char *base_name;
257
    void *closure;
258
} test_surface_t;
259

            
260
static test_surface_t test_surfaces[NUM_TEST_SURFACES];
261

            
262
static void
263
1
init_test_surfaces()
264
{
265
1
    memset (test_surfaces, 0, sizeof (*test_surfaces));
266
1
    test_surfaces[0].surface = cairo_pdf_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE);
267
1
    test_surfaces[1].surface = cairo_ps_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE);
268
1
    test_surfaces[2].surface = cairo_svg_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE);
269
1
}
270

            
271
static void
272
add_test_surface (const cairo_boilerplate_target_t *target,
273
                  cairo_surface_t *surface,
274
                  char *base_name,
275
                  void *closure)
276
{
277
    for (int i = 0; i < NUM_TEST_SURFACES; i++) {
278
        if (cairo_surface_get_type (test_surfaces[i].surface) == cairo_surface_get_type (surface)) {
279
            cairo_surface_destroy (test_surfaces[i].surface);
280
            test_surfaces[i].target = target;
281
            test_surfaces[i].surface = surface;
282
            test_surfaces[i].base_name = base_name;
283
            test_surfaces[i].closure = closure;
284
            break;
285
        }
286
    }
287
}
288

            
289
static void
290
1
destroy_test_surfaces()
291
{
292
4
    for (int i = 0; i < NUM_TEST_SURFACES; i++) {
293
3
        cairo_surface_destroy (test_surfaces[i].surface);
294
3
        if (test_surfaces[i].target && test_surfaces[i].target->cleanup)
295
            test_surfaces[i].target->cleanup (test_surfaces[i].closure);
296
3
        if (test_surfaces[i].base_name)
297
            free (test_surfaces[i].base_name);
298
    }
299
1
}
300

            
301

            
302
static cairo_test_status_t
303
1
preamble (cairo_test_context_t *ctx)
304
{
305
    cairo_t *cr;
306
1
    cairo_test_status_t ret = CAIRO_TEST_UNTESTED;
307
    unsigned int i;
308
1
    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
309
    cairo_surface_t *recordings[TEST_ROWS][TEST_COLS];
310
1
    const char *test_name = "create-regions";
311
    char *base_name;
312

            
313
    /* Each row displays three recordings. One with operations
314
     * supported by all paginated surfaces (OVER), one with operations
315
     * supported by PDF but not PS or SVG surfaces (DIFFERENCE), and
316
     * one with operations supported by SVG but not PDF or PS
317
     * (DEST_XOR).
318
     *
319
     * The recordings for the first row is a single recording. The
320
     * recordings for the second row contains the first row recording
321
     * inside another recording to test the use of cloned recording
322
     * surfaces.
323
     *
324
     * We are looking to see that fallback images are only used for
325
     * unsupported operations.
326
     */
327
1
    create_recordings (CAIRO_OPERATOR_OVER,       &recordings[0][0], &recordings[1][0]);
328
1
    create_recordings (CAIRO_OPERATOR_DIFFERENCE, &recordings[0][1], &recordings[1][1]);
329
1
    create_recordings (CAIRO_OPERATOR_XOR,        &recordings[0][2], &recordings[1][2]);
330

            
331
1
    init_test_surfaces();
332
4
    for (i = 0; i < ctx->num_targets; i++) {
333
3
	const cairo_boilerplate_target_t *target = ctx->targets_to_test[i];
334
3
	cairo_surface_t *surface = NULL;
335
        void *closure;
336
	const char *format;
337

            
338
        /* This test only works on surfaces that support fine grained fallbacks */
339
3
        if (! (target->expected_type == CAIRO_SURFACE_TYPE_PDF ||
340
3
               target->expected_type == CAIRO_SURFACE_TYPE_PS ||
341
3
               target->expected_type ==CAIRO_SURFACE_TYPE_SVG))
342
3
	    continue;
343

            
344
	if (! cairo_test_is_target_enabled (ctx, target->name))
345
	    continue;
346

            
347
	if (ret == CAIRO_TEST_UNTESTED)
348
	    ret = CAIRO_TEST_SUCCESS;
349

            
350
	format = cairo_boilerplate_content_name (target->content);
351
        base_name = NULL;
352
        xasprintf (&base_name, "%s/%s.%s.%s",
353
                   path, test_name,
354
                   target->name,
355
                   format);
356

            
357
        surface = (target->create_surface) (base_name,
358
                                            target->content,
359
                                            PAGE_SIZE, PAGE_SIZE,
360
                                            PAGE_SIZE, PAGE_SIZE,
361
                                            CAIRO_BOILERPLATE_MODE_TEST,
362
                                            &closure);
363
        if (surface == NULL || cairo_surface_status (surface)) {
364
            cairo_test_log (ctx, "Failed to generate surface: %s.%s\n",
365
                            target->name,
366
                            format);
367
            ret = CAIRO_TEST_FAILURE;
368
            break;
369
        }
370

            
371
        cairo_surface_set_fallback_resolution (surface, FALLBACK_PPI, FALLBACK_PPI);
372
        add_test_surface (target, surface, base_name, closure);
373
    }
374
 
375
4
    for (int i = 0; i < NUM_TEST_SURFACES; i++) {
376
	cairo_status_t status;
377

            
378
3
        if (test_surfaces[i].target != NULL) {
379
            cairo_test_log (ctx,
380
                            "Testing create-regions with %s target\n",
381
                            test_surfaces[i].target->name);
382
            printf ("%s:\t", test_surfaces[i].base_name);
383
            fflush (stdout);
384
        }
385

            
386
3
        cr = cairo_create (test_surfaces[i].surface);
387

            
388
3
        draw (cr, recordings);
389

            
390
3
        status = cairo_status (cr);
391
3
        cairo_destroy (cr);
392
3
        cairo_surface_finish (test_surfaces[i].surface);
393

            
394
3
        if (test_surfaces[i].target) {
395
            cairo_bool_t pass = FALSE;
396
            if (status) {
397
                cairo_test_log (ctx, "Error: Failed to create target surface: %s\n",
398
                                cairo_status_to_string (status));
399
                ret = CAIRO_TEST_FAILURE;
400
            } else {
401
                /* extract the image and compare it to our reference */
402
                if (! check_result (ctx, test_surfaces[i].target, test_name, test_surfaces[i].base_name, test_surfaces[i].surface))
403
                    ret = CAIRO_TEST_FAILURE;
404
                else
405
                    pass = TRUE;
406
            }
407

            
408
            if (pass) {
409
                printf ("PASS\n");
410
            } else {
411
                printf ("FAIL\n");
412
            }
413
            fflush (stdout);
414
        }
415
    }
416

            
417
1
    destroy_test_surfaces();
418

            
419
3
    for (int row = 0; row < TEST_ROWS; row++) {
420
8
        for (int col = 0; col < TEST_COLS; col++) {
421
6
            cairo_surface_destroy (recordings[row][col]);
422
        }
423
    }
424

            
425
1
    return ret;
426
}
427

            
428
1
CAIRO_TEST (create_regions,
429
	    "Check region analysis when re-used with different surfaces",
430
	    "fallback", /* keywords */
431
	    NULL, /* requirements */
432
	    0, 0,
433
	    preamble, NULL)
434

            
435
#endif /* CAIRO_HAS_PDF_SURFACE && CAIRO_HAS_PS_SURFACE && CAIRO_HAS_SVG_SURFACE */