1
/* cairo - a vector graphics library with display and print output
2
 *
3
 * Copyright © 2011 Intel Corporation
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it either under the terms of the GNU Lesser General Public
7
 * License version 2.1 as published by the Free Software Foundation
8
 * (the "LGPL") or, at your option, under the terms of the Mozilla
9
 * Public License Version 1.1 (the "MPL"). If you do not alter this
10
 * notice, a recipient may use your version of this file under either
11
 * the MPL or the LGPL.
12
 *
13
 * You should have received a copy of the LGPL along with this library
14
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
15
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
16
 * You should have received a copy of the MPL along with this library
17
 * in the file COPYING-MPL-1.1
18
 *
19
 * The contents of this file are subject to the Mozilla Public License
20
 * Version 1.1 (the "License"); you may not use this file except in
21
 * compliance with the License. You may obtain a copy of the License at
22
 * http://www.mozilla.org/MPL/
23
 *
24
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
25
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
26
 * the specific language governing rights and limitations.
27
 *
28
 * The Original Code is the cairo graphics library.
29
 *
30
 * The Initial Developer of the Original Code is Intel Corporation.
31
 *
32
 * Contributor(s):
33
 *      Chris Wilson <chris@chris-wilson.co.uk>
34
 */
35

            
36
#include "cairoint.h"
37

            
38
#include "cairo-surface-observer-private.h"
39
#include "cairo-surface-observer-inline.h"
40

            
41
#include "cairo-array-private.h"
42
#include "cairo-combsort-inline.h"
43
#include "cairo-composite-rectangles-private.h"
44
#include "cairo-error-private.h"
45
#include "cairo-image-surface-private.h"
46
#include "cairo-list-inline.h"
47
#include "cairo-pattern-private.h"
48
#include "cairo-output-stream-private.h"
49
#include "cairo-recording-surface-private.h"
50
#include "cairo-surface-subsurface-inline.h"
51
#include "cairo-reference-count-private.h"
52

            
53
#if CAIRO_HAS_SCRIPT_SURFACE
54
#include "cairo-script-private.h"
55
#endif
56

            
57
/**
58
 * SECTION:cairo-surface-observer
59
 * @Title: Surface Observer
60
 * @Short_Description: Observing other surfaces
61
 * @See_Also: #cairo_surface_t
62
 *
63
 * A surface that exists solely to watch what another surface is doing.
64
 **/
65

            
66
/**
67
 * CAIRO_HAS_OBSERVER_SURFACE:
68
 *
69
 * Defined if the observer surface backend is available.
70
 * This macro can be used to conditionally compile backend-specific code.
71
 *
72
 * Since: 1.12
73
 **/
74

            
75
static const cairo_surface_backend_t _cairo_surface_observer_backend;
76

            
77
/* observation/stats */
78

            
79
static void init_stats (struct stat *s)
80
{
81
    s->min = HUGE_VAL;
82
    s->max = -HUGE_VAL;
83
}
84

            
85
static void init_extents (struct extents *e)
86
{
87
    init_stats (&e->area);
88
}
89

            
90
static void init_pattern (struct pattern *p)
91
{
92
}
93

            
94
static void init_path (struct path *p)
95
{
96
}
97

            
98
static void init_clip (struct clip *c)
99
{
100
}
101

            
102
static void init_paint (struct paint *p)
103
{
104
    init_extents (&p->extents);
105
    init_pattern (&p->source);
106
    init_clip (&p->clip);
107
}
108

            
109
static void init_mask (struct mask *m)
110
{
111
    init_extents (&m->extents);
112
    init_pattern (&m->source);
113
    init_pattern (&m->mask);
114
    init_clip (&m->clip);
115
}
116

            
117
static void init_fill (struct fill *f)
118
{
119
    init_extents (&f->extents);
120
    init_pattern (&f->source);
121
    init_path (&f->path);
122
    init_clip (&f->clip);
123
}
124

            
125
static void init_stroke (struct stroke *s)
126
{
127
    init_extents (&s->extents);
128
    init_pattern (&s->source);
129
    init_path (&s->path);
130
    init_clip (&s->clip);
131
}
132

            
133
static void init_glyphs (struct glyphs *g)
134
{
135
    init_extents (&g->extents);
136
    init_pattern (&g->source);
137
    init_clip (&g->clip);
138
}
139

            
140
static cairo_status_t
141
log_init (cairo_observation_t *log,
142
	  cairo_bool_t record)
143
{
144
    memset (log, 0, sizeof(*log));
145

            
146
    init_paint (&log->paint);
147
    init_mask (&log->mask);
148
    init_fill (&log->fill);
149
    init_stroke (&log->stroke);
150
    init_glyphs (&log->glyphs);
151

            
152
    _cairo_array_init (&log->timings, sizeof (cairo_observation_record_t));
153

            
154
    if (record) {
155
	log->record = (cairo_recording_surface_t *)
156
	    cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
157
	if (unlikely (log->record->base.status))
158
	    return log->record->base.status;
159

            
160
	log->record->optimize_clears = FALSE;
161
    }
162

            
163
    return CAIRO_STATUS_SUCCESS;
164
}
165

            
166
static void
167
log_fini (cairo_observation_t *log)
168
{
169
    _cairo_array_fini (&log->timings);
170
    cairo_surface_destroy (&log->record->base);
171
}
172

            
173
static cairo_surface_t*
174
get_pattern_surface (const cairo_pattern_t *pattern)
175
{
176
    return ((cairo_surface_pattern_t *)pattern)->surface;
177
}
178

            
179
static int
180
classify_pattern (const cairo_pattern_t *pattern,
181
		  const cairo_surface_t *target)
182
{
183
    int classify;
184

            
185
    switch (pattern->type) {
186
    case CAIRO_PATTERN_TYPE_SURFACE:
187
	if (get_pattern_surface (pattern)->type == target->type)
188
	    classify = 0;
189
	else if (get_pattern_surface (pattern)->type == CAIRO_SURFACE_TYPE_RECORDING)
190
	    classify = 1;
191
	else
192
	    classify = 2;
193
	break;
194
    default:
195
    case CAIRO_PATTERN_TYPE_SOLID:
196
	classify = 3;
197
	break;
198
    case CAIRO_PATTERN_TYPE_LINEAR:
199
	classify = 4;
200
	break;
201
    case CAIRO_PATTERN_TYPE_RADIAL:
202
	classify = 5;
203
	break;
204
    case CAIRO_PATTERN_TYPE_MESH:
205
	classify = 6;
206
	break;
207
    case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
208
	classify = 7;
209
	break;
210
    }
211
    return classify;
212
}
213

            
214
static void
215
add_pattern (struct pattern *stats,
216
	     const cairo_pattern_t *pattern,
217
	     const cairo_surface_t *target)
218
{
219
    stats->type[classify_pattern(pattern, target)]++;
220
}
221

            
222
static int
223
classify_path (const cairo_path_fixed_t *path,
224
	       cairo_bool_t is_fill)
225
{
226
    int classify;
227

            
228
    /* XXX improve for stroke */
229
    classify = -1;
230
    if (is_fill) {
231
	if (path->fill_is_empty)
232
	    classify = 0;
233
	else if (_cairo_path_fixed_fill_is_rectilinear (path))
234
	    classify = path->fill_maybe_region ? 1 : 2;
235
    } else {
236
	if (_cairo_path_fixed_stroke_is_rectilinear (path))
237
	    classify = 2;
238
    }
239
    if (classify == -1)
240
	classify = 3 + (path->has_curve_to != 0);
241

            
242
    return classify;
243
}
244

            
245
static void
246
add_path (struct path *stats,
247
	  const cairo_path_fixed_t *path,
248
	  cairo_bool_t is_fill)
249
{
250
    stats->type[classify_path(path, is_fill)]++;
251
}
252

            
253
static int
254
classify_clip (const cairo_clip_t *clip)
255
{
256
    int classify;
257

            
258
    if (clip == NULL)
259
	classify = 0;
260
    else if (_cairo_clip_is_region (clip))
261
	classify = 1;
262
    else if (clip->path == NULL)
263
	classify = 2;
264
    else if (clip->path->prev == NULL)
265
	classify = 3;
266
    else if (_cairo_clip_is_polygon (clip))
267
	classify = 4;
268
    else
269
	classify = 5;
270

            
271
    return classify;
272
}
273

            
274
static void
275
add_clip (struct clip *stats,
276
	  const cairo_clip_t *clip)
277
{
278
    stats->type[classify_clip (clip)]++;
279
}
280

            
281
static void
282
stats_add (struct stat *s, double v)
283
{
284
    if (v < s->min)
285
	s->min = v;
286
    if (v > s->max)
287
	s->max = v;
288
    s->sum += v;
289
    s->sum_sq += v*v;
290
    s->count++;
291
}
292

            
293
static void
294
add_extents (struct extents *stats,
295
	     const cairo_composite_rectangles_t *extents)
296
{
297
    const cairo_rectangle_int_t *r = extents->is_bounded ? &extents->bounded :&extents->unbounded;
298
    stats_add (&stats->area, r->width * r->height);
299
    stats->bounded += extents->is_bounded != 0;
300
    stats->unbounded += extents->is_bounded == 0;
301
}
302

            
303
/* device interface */
304

            
305
static void
306
_cairo_device_observer_lock (void *_device)
307
{
308
    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
309
    cairo_status_t ignored;
310

            
311
    /* cairo_device_acquire() can fail for nil and finished
312
     * devices. We don't care about observing them. */
313
    ignored = cairo_device_acquire (device->target);
314
}
315

            
316
static void
317
_cairo_device_observer_unlock (void *_device)
318
{
319
    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
320
    cairo_device_release (device->target);
321
}
322

            
323
static cairo_status_t
324
_cairo_device_observer_flush (void *_device)
325
{
326
    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
327

            
328
    if (device->target == NULL)
329
	return CAIRO_STATUS_SUCCESS;
330

            
331
    cairo_device_flush (device->target);
332
    return device->target->status;
333
}
334

            
335
static void
336
_cairo_device_observer_finish (void *_device)
337
{
338
    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
339
    log_fini (&device->log);
340
    cairo_device_finish (device->target);
341
}
342

            
343
static void
344
_cairo_device_observer_destroy (void *_device)
345
{
346
    cairo_device_observer_t *device = (cairo_device_observer_t *) _device;
347
    cairo_device_destroy (device->target);
348
    free (device);
349
}
350

            
351
static const cairo_device_backend_t _cairo_device_observer_backend = {
352
    CAIRO_INTERNAL_DEVICE_TYPE_OBSERVER,
353

            
354
    _cairo_device_observer_lock,
355
    _cairo_device_observer_unlock,
356

            
357
    _cairo_device_observer_flush,
358
    _cairo_device_observer_finish,
359
    _cairo_device_observer_destroy,
360
};
361

            
362
static cairo_device_t *
363
_cairo_device_create_observer_internal (cairo_device_t *target,
364
					cairo_bool_t record)
365
{
366
    cairo_device_observer_t *device;
367
    cairo_status_t status;
368

            
369
    device = _cairo_calloc (sizeof (cairo_device_observer_t));
370
    if (unlikely (device == NULL))
371
	return _cairo_device_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
372

            
373
    _cairo_device_init (&device->base, &_cairo_device_observer_backend);
374
    status = log_init (&device->log, record);
375
    if (unlikely (status)) {
376
	free (device);
377
	return _cairo_device_create_in_error (status);
378
    }
379

            
380
    device->target = cairo_device_reference (target);
381

            
382
    return &device->base;
383
}
384

            
385
/* surface interface */
386

            
387
static cairo_device_observer_t *
388
to_device (cairo_surface_observer_t *suface)
389
{
390
    return (cairo_device_observer_t *)suface->base.device;
391
}
392

            
393
static cairo_surface_t *
394
_cairo_surface_create_observer_internal (cairo_device_t *device,
395
					 cairo_surface_t *target)
396
{
397
    cairo_surface_observer_t *surface;
398
    cairo_status_t status;
399

            
400
    surface = _cairo_calloc (sizeof (cairo_surface_observer_t));
401
    if (unlikely (surface == NULL))
402
	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
403

            
404
    _cairo_surface_init (&surface->base,
405
			 &_cairo_surface_observer_backend, device,
406
			 target->content,
407
			 target->is_vector);
408

            
409
    status = log_init (&surface->log,
410
		       ((cairo_device_observer_t *)device)->log.record != NULL);
411
    if (unlikely (status)) {
412
	free (surface);
413
	return _cairo_surface_create_in_error (status);
414
    }
415

            
416
    surface->target = cairo_surface_reference (target);
417
    surface->base.type = surface->target->type;
418
    surface->base.is_clear = surface->target->is_clear;
419

            
420
    cairo_list_init (&surface->paint_callbacks);
421
    cairo_list_init (&surface->mask_callbacks);
422
    cairo_list_init (&surface->fill_callbacks);
423
    cairo_list_init (&surface->stroke_callbacks);
424
    cairo_list_init (&surface->glyphs_callbacks);
425

            
426
    cairo_list_init (&surface->flush_callbacks);
427
    cairo_list_init (&surface->finish_callbacks);
428

            
429
    surface->log.num_surfaces++;
430
    to_device (surface)->log.num_surfaces++;
431

            
432
    return &surface->base;
433
}
434

            
435
static inline void
436
do_callbacks (cairo_surface_observer_t *surface, cairo_list_t *head)
437
{
438
    struct callback_list *cb;
439

            
440
    cairo_list_foreach_entry (cb, struct callback_list, head, link)
441
	cb->func (&surface->base, surface->target, cb->data);
442
}
443

            
444

            
445
static cairo_status_t
446
_cairo_surface_observer_finish (void *abstract_surface)
447
{
448
    cairo_surface_observer_t *surface = abstract_surface;
449

            
450
    do_callbacks (surface, &surface->finish_callbacks);
451

            
452
    cairo_surface_destroy (surface->target);
453
    log_fini (&surface->log);
454

            
455
    return CAIRO_STATUS_SUCCESS;
456
}
457

            
458
static cairo_surface_t *
459
_cairo_surface_observer_create_similar (void *abstract_other,
460
					cairo_content_t content,
461
					int width, int height)
462
{
463
    cairo_surface_observer_t *other = abstract_other;
464
    cairo_surface_t *target, *surface;
465

            
466
    target = NULL;
467
    if (other->target->backend->create_similar)
468
	target = other->target->backend->create_similar (other->target, content,
469
							 width, height);
470
    if (target == NULL)
471
	target = _cairo_image_surface_create_with_content (content,
472
							   width, height);
473

            
474
    surface = _cairo_surface_create_observer_internal (other->base.device,
475
						       target);
476
    cairo_surface_destroy (target);
477

            
478
    return surface;
479
}
480

            
481
static cairo_surface_t *
482
_cairo_surface_observer_create_similar_image (void *other,
483
					      cairo_format_t format,
484
					      int width, int height)
485
{
486
    cairo_surface_observer_t *surface = other;
487

            
488
    if (surface->target->backend->create_similar_image)
489
	return surface->target->backend->create_similar_image (surface->target,
490
							       format,
491
							       width, height);
492

            
493
    return NULL;
494
}
495

            
496
static cairo_image_surface_t *
497
_cairo_surface_observer_map_to_image (void *abstract_surface,
498
				      const cairo_rectangle_int_t *extents)
499
{
500
    cairo_surface_observer_t *surface = abstract_surface;
501
    return _cairo_surface_map_to_image (surface->target, extents);
502
}
503

            
504
static cairo_int_status_t
505
_cairo_surface_observer_unmap_image (void *abstract_surface,
506
				     cairo_image_surface_t *image)
507
{
508
    cairo_surface_observer_t *surface = abstract_surface;
509
    return _cairo_surface_unmap_image (surface->target, image);
510
}
511

            
512
static void
513
record_target (cairo_observation_record_t *r,
514
	       cairo_surface_t *target)
515
{
516
    cairo_rectangle_int_t extents;
517

            
518
    r->target_content = target->content;
519
    if (_cairo_surface_get_extents (target, &extents)) {
520
	r->target_width = extents.width;
521
	r->target_height = extents.height;
522
    } else {
523
	r->target_width = -1;
524
	r->target_height = -1;
525
    }
526
}
527

            
528
static cairo_observation_record_t *
529
record_paint (cairo_observation_record_t *r,
530
	      cairo_surface_t *target,
531
	      cairo_operator_t op,
532
	      const cairo_pattern_t *source,
533
	      const cairo_clip_t *clip,
534
	      cairo_time_t elapsed)
535
{
536
    record_target (r, target);
537

            
538
    r->op = op;
539
    r->source = classify_pattern (source, target);
540
    r->mask = -1;
541
    r->num_glyphs = -1;
542
    r->path = -1;
543
    r->fill_rule = -1;
544
    r->tolerance = -1;
545
    r->antialias = -1;
546
    r->clip = classify_clip (clip);
547
    r->elapsed = elapsed;
548

            
549
    return r;
550
}
551

            
552
static cairo_observation_record_t *
553
record_mask (cairo_observation_record_t *r,
554
	     cairo_surface_t *target,
555
	     cairo_operator_t op,
556
	     const cairo_pattern_t *source,
557
	     const cairo_pattern_t *mask,
558
	     const cairo_clip_t *clip,
559
	     cairo_time_t elapsed)
560
{
561
    record_target (r, target);
562

            
563
    r->op = op;
564
    r->source = classify_pattern (source, target);
565
    r->mask = classify_pattern (mask, target);
566
    r->num_glyphs = -1;
567
    r->path = -1;
568
    r->fill_rule = -1;
569
    r->tolerance = -1;
570
    r->antialias = -1;
571
    r->clip = classify_clip (clip);
572
    r->elapsed = elapsed;
573

            
574
    return r;
575
}
576

            
577
static cairo_observation_record_t *
578
record_fill (cairo_observation_record_t *r,
579
	     cairo_surface_t		*target,
580
	     cairo_operator_t		op,
581
	     const cairo_pattern_t	*source,
582
	     const cairo_path_fixed_t	*path,
583
	     cairo_fill_rule_t		 fill_rule,
584
	     double			 tolerance,
585
	     cairo_antialias_t		 antialias,
586
	     const cairo_clip_t		*clip,
587
	     cairo_time_t elapsed)
588
{
589
    record_target (r, target);
590

            
591
    r->op = op;
592
    r->source = classify_pattern (source, target);
593
    r->mask = -1;
594
    r->num_glyphs = -1;
595
    r->path = classify_path (path, TRUE);
596
    r->fill_rule = fill_rule;
597
    r->tolerance = tolerance;
598
    r->antialias = antialias;
599
    r->clip = classify_clip (clip);
600
    r->elapsed = elapsed;
601

            
602
    return r;
603
}
604

            
605
static cairo_observation_record_t *
606
record_stroke (cairo_observation_record_t *r,
607
	       cairo_surface_t		*target,
608
	       cairo_operator_t		op,
609
	       const cairo_pattern_t	*source,
610
	       const cairo_path_fixed_t	*path,
611
	       const cairo_stroke_style_t	*style,
612
	       const cairo_matrix_t	*ctm,
613
	       const cairo_matrix_t	*ctm_inverse,
614
	       double			 tolerance,
615
	       cairo_antialias_t	 antialias,
616
	       const cairo_clip_t	*clip,
617
	       cairo_time_t		 elapsed)
618
{
619
    record_target (r, target);
620

            
621
    r->op = op;
622
    r->source = classify_pattern (source, target);
623
    r->mask = -1;
624
    r->num_glyphs = -1;
625
    r->path = classify_path (path, FALSE);
626
    r->fill_rule = -1;
627
    r->tolerance = tolerance;
628
    r->antialias = antialias;
629
    r->clip = classify_clip (clip);
630
    r->elapsed = elapsed;
631

            
632
    return r;
633
}
634

            
635
static cairo_observation_record_t *
636
record_glyphs (cairo_observation_record_t *r,
637
	       cairo_surface_t		*target,
638
	       cairo_operator_t		op,
639
	       const cairo_pattern_t	*source,
640
	       cairo_glyph_t		*glyphs,
641
	       int			 num_glyphs,
642
	       cairo_scaled_font_t	*scaled_font,
643
	       const cairo_clip_t	*clip,
644
	       cairo_time_t		 elapsed)
645
{
646
    record_target (r, target);
647

            
648
    r->op = op;
649
    r->source = classify_pattern (source, target);
650
    r->mask = -1;
651
    r->path = -1;
652
    r->num_glyphs = num_glyphs;
653
    r->fill_rule = -1;
654
    r->tolerance = -1;
655
    r->antialias = -1;
656
    r->clip = classify_clip (clip);
657
    r->elapsed = elapsed;
658

            
659
    return r;
660
}
661

            
662
static void
663
add_record (cairo_observation_t *log,
664
	    cairo_observation_record_t *r)
665
{
666
    cairo_int_status_t status;
667

            
668
    r->index = log->record ? log->record->commands.num_elements : 0;
669

            
670
    status = _cairo_array_append (&log->timings, r);
671
    assert (status == CAIRO_INT_STATUS_SUCCESS);
672
}
673

            
674
static void
675
_cairo_surface_sync (cairo_surface_t *target, int x, int y)
676
{
677
    cairo_rectangle_int_t extents;
678

            
679
    extents.x = x;
680
    extents.y = y;
681
    extents.width  = 1;
682
    extents.height = 1;
683

            
684
    _cairo_surface_unmap_image (target,
685
				_cairo_surface_map_to_image (target,
686
							     &extents));
687
}
688

            
689
static void
690
midpt (const cairo_composite_rectangles_t *extents, int *x, int *y)
691
{
692
    *x = extents->bounded.x + extents->bounded.width / 2;
693
    *y = extents->bounded.y + extents->bounded.height / 2;
694
}
695

            
696
static void
697
add_record_paint (cairo_observation_t *log,
698
		 cairo_surface_t *target,
699
		 cairo_operator_t op,
700
		 const cairo_pattern_t *source,
701
		 const cairo_clip_t *clip,
702
		 cairo_time_t elapsed)
703
{
704
    cairo_observation_record_t record;
705
    cairo_int_status_t status;
706

            
707
    add_record (log,
708
		record_paint (&record, target, op, source, clip, elapsed));
709

            
710
    /* We have to bypass the high-level surface layer in case it tries to be
711
     * too smart and discard operations; we need to record exactly what just
712
     * happened on the target.
713
     */
714
    if (log->record) {
715
	status = log->record->base.backend->paint (&log->record->base,
716
						   op, source, clip);
717
	assert (status == CAIRO_INT_STATUS_SUCCESS);
718
    }
719

            
720
    if (_cairo_time_gt (elapsed, log->paint.slowest.elapsed))
721
	log->paint.slowest = record;
722
    log->paint.elapsed = _cairo_time_add (log->paint.elapsed, elapsed);
723
}
724

            
725
static cairo_int_status_t
726
_cairo_surface_observer_paint (void *abstract_surface,
727
			       cairo_operator_t op,
728
			       const cairo_pattern_t *source,
729
			       const cairo_clip_t *clip)
730
{
731
    cairo_surface_observer_t *surface = abstract_surface;
732
    cairo_device_observer_t *device = to_device (surface);
733
    cairo_composite_rectangles_t composite;
734
    cairo_int_status_t status;
735
    cairo_time_t t;
736
    int x, y;
737

            
738
    /* XXX device locking */
739

            
740
    surface->log.paint.count++;
741
    surface->log.paint.operators[op]++;
742
    add_pattern (&surface->log.paint.source, source, surface->target);
743
    add_clip (&surface->log.paint.clip, clip);
744

            
745
    device->log.paint.count++;
746
    device->log.paint.operators[op]++;
747
    add_pattern (&device->log.paint.source, source, surface->target);
748
    add_clip (&device->log.paint.clip, clip);
749

            
750
    status = _cairo_composite_rectangles_init_for_paint (&composite,
751
							 surface->target,
752
							 op, source,
753
							 clip);
754
    if (unlikely (status)) {
755
	surface->log.paint.noop++;
756
	device->log.paint.noop++;
757
	return status;
758
    }
759

            
760
    midpt (&composite, &x, &y);
761

            
762
    add_extents (&surface->log.paint.extents, &composite);
763
    add_extents (&device->log.paint.extents, &composite);
764
    _cairo_composite_rectangles_fini (&composite);
765

            
766
    t = _cairo_time_get ();
767
    status = _cairo_surface_paint (surface->target,
768
				   op, source,
769
				   clip);
770
    if (unlikely (status))
771
	return status;
772

            
773
    _cairo_surface_sync (surface->target, x, y);
774
    t = _cairo_time_get_delta (t);
775

            
776
    add_record_paint (&surface->log, surface->target, op, source, clip, t);
777
    add_record_paint (&device->log, surface->target, op, source, clip, t);
778

            
779
    do_callbacks (surface, &surface->paint_callbacks);
780

            
781
    return CAIRO_STATUS_SUCCESS;
782
}
783

            
784
static void
785
add_record_mask (cairo_observation_t *log,
786
		 cairo_surface_t *target,
787
		 cairo_operator_t op,
788
		 const cairo_pattern_t *source,
789
		 const cairo_pattern_t *mask,
790
		 const cairo_clip_t *clip,
791
		 cairo_time_t elapsed)
792
{
793
    cairo_observation_record_t record;
794
    cairo_int_status_t status;
795

            
796
    add_record (log,
797
		record_mask (&record, target, op, source, mask, clip, elapsed));
798

            
799
    if (log->record) {
800
	status = log->record->base.backend->mask (&log->record->base,
801
						  op, source, mask, clip);
802
	assert (status == CAIRO_INT_STATUS_SUCCESS);
803
    }
804

            
805
    if (_cairo_time_gt (elapsed, log->mask.slowest.elapsed))
806
	log->mask.slowest = record;
807
    log->mask.elapsed = _cairo_time_add (log->mask.elapsed, elapsed);
808
}
809

            
810
static cairo_int_status_t
811
_cairo_surface_observer_mask (void *abstract_surface,
812
			      cairo_operator_t op,
813
			      const cairo_pattern_t *source,
814
			      const cairo_pattern_t *mask,
815
			      const cairo_clip_t *clip)
816
{
817
    cairo_surface_observer_t *surface = abstract_surface;
818
    cairo_device_observer_t *device = to_device (surface);
819
    cairo_composite_rectangles_t composite;
820
    cairo_int_status_t status;
821
    cairo_time_t t;
822
    int x, y;
823

            
824
    surface->log.mask.count++;
825
    surface->log.mask.operators[op]++;
826
    add_pattern (&surface->log.mask.source, source, surface->target);
827
    add_pattern (&surface->log.mask.mask, mask, surface->target);
828
    add_clip (&surface->log.mask.clip, clip);
829

            
830
    device->log.mask.count++;
831
    device->log.mask.operators[op]++;
832
    add_pattern (&device->log.mask.source, source, surface->target);
833
    add_pattern (&device->log.mask.mask, mask, surface->target);
834
    add_clip (&device->log.mask.clip, clip);
835

            
836
    status = _cairo_composite_rectangles_init_for_mask (&composite,
837
							surface->target,
838
							op, source, mask,
839
							clip);
840
    if (unlikely (status)) {
841
	surface->log.mask.noop++;
842
	device->log.mask.noop++;
843
	return status;
844
    }
845

            
846
    midpt (&composite, &x, &y);
847

            
848
    add_extents (&surface->log.mask.extents, &composite);
849
    add_extents (&device->log.mask.extents, &composite);
850
    _cairo_composite_rectangles_fini (&composite);
851

            
852
    t = _cairo_time_get ();
853
    status =  _cairo_surface_mask (surface->target,
854
				   op, source, mask,
855
				   clip);
856
    if (unlikely (status))
857
	return status;
858

            
859
    _cairo_surface_sync (surface->target, x, y);
860
    t = _cairo_time_get_delta (t);
861

            
862
    add_record_mask (&surface->log,
863
		     surface->target, op, source, mask, clip,
864
		     t);
865
    add_record_mask (&device->log,
866
		     surface->target, op, source, mask, clip,
867
		     t);
868

            
869
    do_callbacks (surface, &surface->mask_callbacks);
870

            
871
    return CAIRO_STATUS_SUCCESS;
872
}
873

            
874
static void
875
add_record_fill (cairo_observation_t *log,
876
		 cairo_surface_t *target,
877
		 cairo_operator_t		op,
878
		 const cairo_pattern_t		*source,
879
		 const cairo_path_fixed_t	*path,
880
		 cairo_fill_rule_t		 fill_rule,
881
		 double				 tolerance,
882
		 cairo_antialias_t		 antialias,
883
		 const cairo_clip_t		 *clip,
884
		 cairo_time_t elapsed)
885
{
886
    cairo_observation_record_t record;
887
    cairo_int_status_t status;
888

            
889
    add_record (log,
890
		record_fill (&record,
891
			     target, op, source,
892
			     path, fill_rule, tolerance, antialias,
893
			     clip, elapsed));
894

            
895
    if (log->record) {
896
	status = log->record->base.backend->fill (&log->record->base,
897
						  op, source,
898
						  path, fill_rule,
899
						  tolerance, antialias,
900
						  clip);
901
	assert (status == CAIRO_INT_STATUS_SUCCESS);
902
    }
903

            
904
    if (_cairo_time_gt (elapsed, log->fill.slowest.elapsed))
905
	log->fill.slowest = record;
906
    log->fill.elapsed = _cairo_time_add (log->fill.elapsed, elapsed);
907
}
908

            
909
static cairo_int_status_t
910
_cairo_surface_observer_fill (void			*abstract_surface,
911
			      cairo_operator_t		op,
912
			      const cairo_pattern_t	*source,
913
			      const cairo_path_fixed_t	*path,
914
			      cairo_fill_rule_t		fill_rule,
915
			      double			 tolerance,
916
			      cairo_antialias_t		antialias,
917
			      const cairo_clip_t	*clip)
918
{
919
    cairo_surface_observer_t *surface = abstract_surface;
920
    cairo_device_observer_t *device = to_device (surface);
921
    cairo_composite_rectangles_t composite;
922
    cairo_int_status_t status;
923
    cairo_time_t t;
924
    int x, y;
925

            
926
    surface->log.fill.count++;
927
    surface->log.fill.operators[op]++;
928
    surface->log.fill.fill_rule[fill_rule]++;
929
    surface->log.fill.antialias[antialias]++;
930
    add_pattern (&surface->log.fill.source, source, surface->target);
931
    add_path (&surface->log.fill.path, path, TRUE);
932
    add_clip (&surface->log.fill.clip, clip);
933

            
934
    device->log.fill.count++;
935
    device->log.fill.operators[op]++;
936
    device->log.fill.fill_rule[fill_rule]++;
937
    device->log.fill.antialias[antialias]++;
938
    add_pattern (&device->log.fill.source, source, surface->target);
939
    add_path (&device->log.fill.path, path, TRUE);
940
    add_clip (&device->log.fill.clip, clip);
941

            
942
    status = _cairo_composite_rectangles_init_for_fill (&composite,
943
							surface->target,
944
							op, source, path,
945
							clip);
946
    if (unlikely (status)) {
947
	surface->log.fill.noop++;
948
	device->log.fill.noop++;
949
	return status;
950
    }
951

            
952
    midpt (&composite, &x, &y);
953

            
954
    add_extents (&surface->log.fill.extents, &composite);
955
    add_extents (&device->log.fill.extents, &composite);
956
    _cairo_composite_rectangles_fini (&composite);
957

            
958
    t = _cairo_time_get ();
959
    status = _cairo_surface_fill (surface->target,
960
				  op, source, path,
961
				  fill_rule, tolerance, antialias,
962
				  clip);
963
    if (unlikely (status))
964
	return status;
965

            
966
    _cairo_surface_sync (surface->target, x, y);
967
    t = _cairo_time_get_delta (t);
968

            
969
    add_record_fill (&surface->log,
970
		     surface->target, op, source, path,
971
		     fill_rule, tolerance, antialias,
972
		     clip, t);
973

            
974
    add_record_fill (&device->log,
975
		     surface->target, op, source, path,
976
		     fill_rule, tolerance, antialias,
977
		     clip, t);
978

            
979
    do_callbacks (surface, &surface->fill_callbacks);
980

            
981
    return CAIRO_STATUS_SUCCESS;
982
}
983

            
984
static void
985
add_record_stroke (cairo_observation_t *log,
986
		 cairo_surface_t *target,
987
		 cairo_operator_t		 op,
988
		 const cairo_pattern_t		*source,
989
		 const cairo_path_fixed_t	*path,
990
		 const cairo_stroke_style_t	*style,
991
		 const cairo_matrix_t		*ctm,
992
		 const cairo_matrix_t		*ctm_inverse,
993
		 double				 tolerance,
994
		 cairo_antialias_t		 antialias,
995
		 const cairo_clip_t		*clip,
996
		 cairo_time_t elapsed)
997
{
998
    cairo_observation_record_t record;
999
    cairo_int_status_t status;
    add_record (log,
		record_stroke (&record,
			       target, op, source,
			       path, style, ctm,ctm_inverse,
			       tolerance, antialias,
			       clip, elapsed));
    if (log->record) {
	status = log->record->base.backend->stroke (&log->record->base,
						    op, source,
						    path, style, ctm,ctm_inverse,
						    tolerance, antialias,
						    clip);
	assert (status == CAIRO_INT_STATUS_SUCCESS);
    }
    if (_cairo_time_gt (elapsed, log->stroke.slowest.elapsed))
	log->stroke.slowest = record;
    log->stroke.elapsed = _cairo_time_add (log->stroke.elapsed, elapsed);
}
static cairo_int_status_t
_cairo_surface_observer_stroke (void				*abstract_surface,
				cairo_operator_t		 op,
				const cairo_pattern_t		*source,
				const cairo_path_fixed_t	*path,
				const cairo_stroke_style_t	*style,
				const cairo_matrix_t		*ctm,
				const cairo_matrix_t		*ctm_inverse,
				double				 tolerance,
				cairo_antialias_t		 antialias,
				const cairo_clip_t		*clip)
{
    cairo_surface_observer_t *surface = abstract_surface;
    cairo_device_observer_t *device = to_device (surface);
    cairo_composite_rectangles_t composite;
    cairo_int_status_t status;
    cairo_time_t t;
    int x, y;
    surface->log.stroke.count++;
    surface->log.stroke.operators[op]++;
    surface->log.stroke.antialias[antialias]++;
    surface->log.stroke.caps[style->line_cap]++;
    surface->log.stroke.joins[style->line_join]++;
    add_pattern (&surface->log.stroke.source, source, surface->target);
    add_path (&surface->log.stroke.path, path, FALSE);
    add_clip (&surface->log.stroke.clip, clip);
    device->log.stroke.count++;
    device->log.stroke.operators[op]++;
    device->log.stroke.antialias[antialias]++;
    device->log.stroke.caps[style->line_cap]++;
    device->log.stroke.joins[style->line_join]++;
    add_pattern (&device->log.stroke.source, source, surface->target);
    add_path (&device->log.stroke.path, path, FALSE);
    add_clip (&device->log.stroke.clip, clip);
    status = _cairo_composite_rectangles_init_for_stroke (&composite,
							  surface->target,
							  op, source,
							  path, style, ctm,
							  clip);
    if (unlikely (status)) {
	surface->log.stroke.noop++;
	device->log.stroke.noop++;
	return status;
    }
    midpt (&composite, &x, &y);
    add_extents (&surface->log.stroke.extents, &composite);
    add_extents (&device->log.stroke.extents, &composite);
    _cairo_composite_rectangles_fini (&composite);
    t = _cairo_time_get ();
    status = _cairo_surface_stroke (surface->target,
				  op, source, path,
				  style, ctm, ctm_inverse,
				  tolerance, antialias,
				  clip);
    if (unlikely (status))
	return status;
    _cairo_surface_sync (surface->target, x, y);
    t = _cairo_time_get_delta (t);
    add_record_stroke (&surface->log,
		       surface->target, op, source, path,
		       style, ctm,ctm_inverse,
		       tolerance, antialias,
		       clip, t);
    add_record_stroke (&device->log,
		       surface->target, op, source, path,
		       style, ctm,ctm_inverse,
		       tolerance, antialias,
		       clip, t);
    do_callbacks (surface, &surface->stroke_callbacks);
    return CAIRO_STATUS_SUCCESS;
}
static void
add_record_glyphs (cairo_observation_t	*log,
		   cairo_surface_t	*target,
		   cairo_operator_t	 op,
		   const cairo_pattern_t*source,
		   cairo_glyph_t	*glyphs,
		   int			 num_glyphs,
		   cairo_scaled_font_t	*scaled_font,
		   const cairo_clip_t	*clip,
		   cairo_time_t elapsed)
{
    cairo_observation_record_t record;
    cairo_int_status_t status;
    add_record (log,
		record_glyphs (&record,
			       target, op, source,
			       glyphs, num_glyphs, scaled_font,
			       clip, elapsed));
    if (log->record) {
	status = log->record->base.backend->show_text_glyphs (&log->record->base,
							      op, source,
							      NULL, 0,
							      glyphs, num_glyphs,
							      NULL, 0, 0,
							      scaled_font,
							      clip);
	assert (status == CAIRO_INT_STATUS_SUCCESS);
    }
    if (_cairo_time_gt (elapsed, log->glyphs.slowest.elapsed))
	log->glyphs.slowest = record;
    log->glyphs.elapsed = _cairo_time_add (log->glyphs.elapsed, elapsed);
}
static cairo_int_status_t
_cairo_surface_observer_glyphs (void			*abstract_surface,
				cairo_operator_t	 op,
				const cairo_pattern_t	*source,
				cairo_glyph_t		*glyphs,
				int			 num_glyphs,
				cairo_scaled_font_t	*scaled_font,
				const cairo_clip_t		*clip)
{
    cairo_surface_observer_t *surface = abstract_surface;
    cairo_device_observer_t *device = to_device (surface);
    cairo_composite_rectangles_t composite;
    cairo_int_status_t status;
    cairo_glyph_t *dev_glyphs;
    cairo_time_t t;
    int x, y;
    surface->log.glyphs.count++;
    surface->log.glyphs.operators[op]++;
    add_pattern (&surface->log.glyphs.source, source, surface->target);
    add_clip (&surface->log.glyphs.clip, clip);
    device->log.glyphs.count++;
    device->log.glyphs.operators[op]++;
    add_pattern (&device->log.glyphs.source, source, surface->target);
    add_clip (&device->log.glyphs.clip, clip);
    status = _cairo_composite_rectangles_init_for_glyphs (&composite,
							  surface->target,
							  op, source,
							  scaled_font,
							  glyphs, num_glyphs,
							  clip,
							  NULL);
    if (unlikely (status)) {
	surface->log.glyphs.noop++;
	device->log.glyphs.noop++;
	return status;
    }
    midpt (&composite, &x, &y);
    add_extents (&surface->log.glyphs.extents, &composite);
    add_extents (&device->log.glyphs.extents, &composite);
    _cairo_composite_rectangles_fini (&composite);
    /* XXX We have to copy the glyphs, because the backend is allowed to
     * modify! */
    dev_glyphs = _cairo_malloc_ab (num_glyphs, sizeof (cairo_glyph_t));
    if (unlikely (dev_glyphs == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    memcpy (dev_glyphs, glyphs, num_glyphs * sizeof (cairo_glyph_t));
    t = _cairo_time_get ();
    status = _cairo_surface_show_text_glyphs (surface->target, op, source,
					      NULL, 0,
					      dev_glyphs, num_glyphs,
					      NULL, 0, 0,
					      scaled_font,
					      clip);
    free (dev_glyphs);
    if (unlikely (status))
	return status;
    _cairo_surface_sync (surface->target, x, y);
    t = _cairo_time_get_delta (t);
    add_record_glyphs (&surface->log,
		       surface->target, op, source,
		       glyphs, num_glyphs, scaled_font,
		       clip, t);
    add_record_glyphs (&device->log,
		       surface->target, op, source,
		       glyphs, num_glyphs, scaled_font,
		       clip, t);
    do_callbacks (surface, &surface->glyphs_callbacks);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_surface_observer_flush (void *abstract_surface, unsigned flags)
{
    cairo_surface_observer_t *surface = abstract_surface;
    do_callbacks (surface, &surface->flush_callbacks);
    return _cairo_surface_flush (surface->target, flags);
}
static cairo_status_t
_cairo_surface_observer_mark_dirty (void *abstract_surface,
				      int x, int y,
				      int width, int height)
{
    cairo_surface_observer_t *surface = abstract_surface;
    cairo_status_t status;
    status = CAIRO_STATUS_SUCCESS;
    if (surface->target->backend->mark_dirty_rectangle)
	status = surface->target->backend->mark_dirty_rectangle (surface->target,
						       x,y, width,height);
    return status;
}
static cairo_int_status_t
_cairo_surface_observer_copy_page (void *abstract_surface)
{
    cairo_surface_observer_t *surface = abstract_surface;
    cairo_status_t status;
    status = CAIRO_STATUS_SUCCESS;
    if (surface->target->backend->copy_page)
	status = surface->target->backend->copy_page (surface->target);
    return status;
}
static cairo_int_status_t
_cairo_surface_observer_show_page (void *abstract_surface)
{
    cairo_surface_observer_t *surface = abstract_surface;
    cairo_status_t status;
    status = CAIRO_STATUS_SUCCESS;
    if (surface->target->backend->show_page)
	status = surface->target->backend->show_page (surface->target);
    return status;
}
static cairo_bool_t
_cairo_surface_observer_get_extents (void *abstract_surface,
				     cairo_rectangle_int_t *extents)
{
    cairo_surface_observer_t *surface = abstract_surface;
    return _cairo_surface_get_extents (surface->target, extents);
}
static void
_cairo_surface_observer_get_font_options (void *abstract_surface,
					  cairo_font_options_t *options)
{
    cairo_surface_observer_t *surface = abstract_surface;
    if (surface->target->backend->get_font_options != NULL)
	surface->target->backend->get_font_options (surface->target, options);
}
static cairo_surface_t *
_cairo_surface_observer_source (void                    *abstract_surface,
				cairo_rectangle_int_t	*extents)
{
    cairo_surface_observer_t *surface = abstract_surface;
    return _cairo_surface_get_source (surface->target, extents);
}
static cairo_status_t
_cairo_surface_observer_acquire_source_image (void                    *abstract_surface,
						cairo_image_surface_t  **image_out,
						void                   **image_extra)
{
    cairo_surface_observer_t *surface = abstract_surface;
    surface->log.num_sources_acquired++;
    to_device (surface)->log.num_sources_acquired++;
    return _cairo_surface_acquire_source_image (surface->target,
						image_out, image_extra);
}
static void
_cairo_surface_observer_release_source_image (void                   *abstract_surface,
						cairo_image_surface_t  *image,
						void                   *image_extra)
{
    cairo_surface_observer_t *surface = abstract_surface;
    _cairo_surface_release_source_image (surface->target, image, image_extra);
}
static cairo_surface_t *
_cairo_surface_observer_snapshot (void *abstract_surface)
{
    cairo_surface_observer_t *surface = abstract_surface;
    /* XXX hook onto the snapshot so that we measure number of reads */
    if (surface->target->backend->snapshot)
	return surface->target->backend->snapshot (surface->target);
    return NULL;
}
static cairo_t *
_cairo_surface_observer_create_context(void *target)
{
    cairo_surface_observer_t *surface = target;
    if (_cairo_surface_is_subsurface (&surface->base))
	surface = (cairo_surface_observer_t *)
	    _cairo_surface_subsurface_get_target (&surface->base);
    surface->log.num_contexts++;
    to_device (surface)->log.num_contexts++;
    return surface->target->backend->create_context (target);
}
static const cairo_surface_backend_t _cairo_surface_observer_backend = {
    CAIRO_INTERNAL_SURFACE_TYPE_OBSERVER,
    _cairo_surface_observer_finish,
    _cairo_surface_observer_create_context,
    _cairo_surface_observer_create_similar,
    _cairo_surface_observer_create_similar_image,
    _cairo_surface_observer_map_to_image,
    _cairo_surface_observer_unmap_image,
    _cairo_surface_observer_source,
    _cairo_surface_observer_acquire_source_image,
    _cairo_surface_observer_release_source_image,
    _cairo_surface_observer_snapshot,
    _cairo_surface_observer_copy_page,
    _cairo_surface_observer_show_page,
    _cairo_surface_observer_get_extents,
    _cairo_surface_observer_get_font_options,
    _cairo_surface_observer_flush,
    _cairo_surface_observer_mark_dirty,
    _cairo_surface_observer_paint,
    _cairo_surface_observer_mask,
    _cairo_surface_observer_stroke,
    _cairo_surface_observer_fill,
    NULL, /* fill-stroke */
    _cairo_surface_observer_glyphs,
};
/**
 * cairo_surface_create_observer:
 * @target: an existing surface for which the observer will watch
 * @mode: sets the mode of operation (normal vs. record)
 *
 * Create a new surface that exists solely to watch another is doing. In
 * the process it will log operations and times, which are fast, which are
 * slow, which are frequent, etc.
 *
 * The @mode parameter can be set to either %CAIRO_SURFACE_OBSERVER_NORMAL
 * or %CAIRO_SURFACE_OBSERVER_RECORD_OPERATIONS, to control whether or not
 * the internal observer should record operations.
 *
 * Return value: a pointer to the newly allocated surface. The caller
 * owns the surface and should call cairo_surface_destroy() when done
 * with it.
 *
 * This function always returns a valid pointer, but it will return a
 * pointer to a "nil" surface if @other is already in an error state
 * or any other error occurs.
 *
 * Since: 1.12
 **/
cairo_surface_t *
cairo_surface_create_observer (cairo_surface_t *target,
			       cairo_surface_observer_mode_t mode)
{
    cairo_device_t *device;
    cairo_surface_t *surface;
    cairo_bool_t record;
    if (unlikely (target->status))
	return _cairo_surface_create_in_error (target->status);
    if (unlikely (target->finished))
	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
    record = mode & CAIRO_SURFACE_OBSERVER_RECORD_OPERATIONS;
    device = _cairo_device_create_observer_internal (target->device, record);
    if (unlikely (device->status))
	return _cairo_surface_create_in_error (device->status);
    surface = _cairo_surface_create_observer_internal (device, target);
    cairo_device_destroy (device);
    return surface;
}
static cairo_status_t
_cairo_surface_observer_add_callback (cairo_list_t *head,
				      cairo_surface_observer_callback_t func,
				      void *data)
{
    struct callback_list *cb;
    cb = _cairo_calloc (sizeof (*cb));
    if (unlikely (cb == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    cairo_list_add (&cb->link, head);
    cb->func = func;
    cb->data = data;
    return CAIRO_STATUS_SUCCESS;
}
/**
 * cairo_surface_observer_add_paint_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback function for paint operations
 * @data: closure to pass to the callback
 *
 * Adds a callback for paint operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.12
 **/
cairo_status_t
cairo_surface_observer_add_paint_callback (cairo_surface_t *abstract_surface,
					    cairo_surface_observer_callback_t func,
					    void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->paint_callbacks,
						 func, data);
}
/**
 * cairo_surface_observer_add_mask_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback function for mask operations
 * @data: closure to pass to the callback
 *
 * Adds a callback for mask operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.12
 **/
cairo_status_t
cairo_surface_observer_add_mask_callback (cairo_surface_t *abstract_surface,
					  cairo_surface_observer_callback_t func,
					  void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->mask_callbacks,
						 func, data);
}
/**
 * cairo_surface_observer_add_fill_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback function for fill operations
 * @data: closure to pass to the callback
 *
 * Adds a callback for fill operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.12
 **/
cairo_status_t
cairo_surface_observer_add_fill_callback (cairo_surface_t *abstract_surface,
					  cairo_surface_observer_callback_t func,
					  void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->fill_callbacks,
						 func, data);
}
/**
 * cairo_surface_observer_add_stroke_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback function for stroke operations
 * @data: closure to pass to the callback
 *
 * Adds a callback for stroke operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.12
 **/
cairo_status_t
cairo_surface_observer_add_stroke_callback (cairo_surface_t *abstract_surface,
					    cairo_surface_observer_callback_t func,
					    void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->stroke_callbacks,
						 func, data);
}
/**
 * cairo_surface_observer_add_glyphs_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback function for glyph operations
 * @data: closure to pass to the callback
 *
 * Adds a callback for glyph operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.10
 **/
cairo_status_t
cairo_surface_observer_add_glyphs_callback (cairo_surface_t *abstract_surface,
					    cairo_surface_observer_callback_t func,
					    void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->glyphs_callbacks,
						 func, data);
}
/**
 * cairo_surface_observer_add_flush_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback for flush operations
 * @data: closure to pass to the callback
 *
 * Adds a callback for flush operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.10
 **/
cairo_status_t
cairo_surface_observer_add_flush_callback (cairo_surface_t *abstract_surface,
					   cairo_surface_observer_callback_t func,
					   void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->flush_callbacks,
						 func, data);
}
/**
 * cairo_surface_observer_add_finish_callback:
 * @abstract_surface: a #cairo_surface_observer_t
 * @func: callback function for the finish operation
 * @data: closure to pass to the callback
 *
 * Adds a callback for finish operations on the observed surface.
 *
 * Returns: the status of the surface
 *
 * Since: 1.10
 **/
cairo_status_t
cairo_surface_observer_add_finish_callback (cairo_surface_t *abstract_surface,
					    cairo_surface_observer_callback_t func,
					    void *data)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return abstract_surface->status;
    if (! _cairo_surface_is_observer (abstract_surface))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *)abstract_surface;
    return _cairo_surface_observer_add_callback (&surface->finish_callbacks,
						 func, data);
}
static void
print_extents (cairo_output_stream_t *stream, const struct extents *e)
{
    _cairo_output_stream_printf (stream,
				 "  extents: total %g, avg %g [unbounded %d]\n",
				 e->area.sum,
				 e->area.sum / e->area.count,
				 e->unbounded);
}
static inline int ordercmp (int a, int b, const unsigned int *array)
{
    /* high to low */
    return array[b] - array[a];
}
CAIRO_COMBSORT_DECLARE_WITH_DATA (sort_order, int, ordercmp)
static void
print_array (cairo_output_stream_t *stream,
	     const unsigned int *array,
	     const char **names,
	     int count)
{
    int order[64];
    int i, j;
    assert (count < ARRAY_LENGTH (order));
    for (i = j = 0; i < count; i++) {
	if (array[i] != 0)
	    order[j++] = i;
    }
    sort_order (order, j, (void *)array);
    for (i = 0; i < j; i++)
	_cairo_output_stream_printf (stream, " %d %s%s",
				     array[order[i]], names[order[i]],
				     i < j -1 ? "," : "");
}
static const char *operator_names[] = {
    "CLEAR",	/* CAIRO_OPERATOR_CLEAR */
    "SOURCE",	/* CAIRO_OPERATOR_SOURCE */
    "OVER",		/* CAIRO_OPERATOR_OVER */
    "IN",		/* CAIRO_OPERATOR_IN */
    "OUT",		/* CAIRO_OPERATOR_OUT */
    "ATOP",		/* CAIRO_OPERATOR_ATOP */
    "DEST",		/* CAIRO_OPERATOR_DEST */
    "DEST_OVER",	/* CAIRO_OPERATOR_DEST_OVER */
    "DEST_IN",	/* CAIRO_OPERATOR_DEST_IN */
    "DEST_OUT",	/* CAIRO_OPERATOR_DEST_OUT */
    "DEST_ATOP",	/* CAIRO_OPERATOR_DEST_ATOP */
    "XOR",		/* CAIRO_OPERATOR_XOR */
    "ADD",		/* CAIRO_OPERATOR_ADD */
    "SATURATE",	/* CAIRO_OPERATOR_SATURATE */
    "MULTIPLY",	/* CAIRO_OPERATOR_MULTIPLY */
    "SCREEN",	/* CAIRO_OPERATOR_SCREEN */
    "OVERLAY",	/* CAIRO_OPERATOR_OVERLAY */
    "DARKEN",	/* CAIRO_OPERATOR_DARKEN */
    "LIGHTEN",	/* CAIRO_OPERATOR_LIGHTEN */
    "DODGE",	/* CAIRO_OPERATOR_COLOR_DODGE */
    "BURN",		/* CAIRO_OPERATOR_COLOR_BURN */
    "HARD_LIGHT",	/* CAIRO_OPERATOR_HARD_LIGHT */
    "SOFT_LIGHT",	/* CAIRO_OPERATOR_SOFT_LIGHT */
    "DIFFERENCE",	/* CAIRO_OPERATOR_DIFFERENCE */
    "EXCLUSION",	/* CAIRO_OPERATOR_EXCLUSION */
    "HSL_HUE",	/* CAIRO_OPERATOR_HSL_HUE */
    "HSL_SATURATION", /* CAIRO_OPERATOR_HSL_SATURATION */
    "HSL_COLOR",	/* CAIRO_OPERATOR_HSL_COLOR */
    "HSL_LUMINOSITY" /* CAIRO_OPERATOR_HSL_LUMINOSITY */
};
static void
print_operators (cairo_output_stream_t *stream, unsigned int *array)
{
    _cairo_output_stream_printf (stream, "  op:");
    print_array (stream, array, operator_names, NUM_OPERATORS);
    _cairo_output_stream_printf (stream, "\n");
}
static const char *fill_rule_names[] = {
    "non-zero",
    "even-odd",
};
static void
print_fill_rule (cairo_output_stream_t *stream, unsigned int *array)
{
    _cairo_output_stream_printf (stream, "  fill rule:");
    print_array (stream, array, fill_rule_names, ARRAY_LENGTH(fill_rule_names));
    _cairo_output_stream_printf (stream, "\n");
}
static const char *cap_names[] = {
    "butt",		/* CAIRO_LINE_CAP_BUTT */
    "round",	/* CAIRO_LINE_CAP_ROUND */
    "square"	/* CAIRO_LINE_CAP_SQUARE */
};
static void
print_line_caps (cairo_output_stream_t *stream, unsigned int *array)
{
    _cairo_output_stream_printf (stream, "  caps:");
    print_array (stream, array, cap_names, NUM_CAPS);
    _cairo_output_stream_printf (stream, "\n");
}
static const char *join_names[] = {
    "miter",	/* CAIRO_LINE_JOIN_MITER */
    "round",	/* CAIRO_LINE_JOIN_ROUND */
    "bevel",	/* CAIRO_LINE_JOIN_BEVEL */
};
static void
print_line_joins (cairo_output_stream_t *stream, unsigned int *array)
{
    _cairo_output_stream_printf (stream, "  joins:");
    print_array (stream, array, join_names, NUM_JOINS);
    _cairo_output_stream_printf (stream, "\n");
}
static const char *antialias_names[] = {
    "default",
    "none",
    "gray",
    "subpixel",
    "fast",
    "good",
    "best"
};
static void
print_antialias (cairo_output_stream_t *stream, unsigned int *array)
{
    _cairo_output_stream_printf (stream, "  antialias:");
    print_array (stream, array, antialias_names, NUM_ANTIALIAS);
    _cairo_output_stream_printf (stream, "\n");
}
static const char *pattern_names[] = {
    "native",
    "record",
    "other surface",
    "solid",
    "linear",
    "radial",
    "mesh",
    "raster"
};
static void
print_pattern (cairo_output_stream_t *stream,
	       const char *name,
	       const struct pattern *p)
{
    _cairo_output_stream_printf (stream, "  %s:", name);
    print_array (stream, p->type, pattern_names, ARRAY_LENGTH (pattern_names));
    _cairo_output_stream_printf (stream, "\n");
}
static const char *path_names[] = {
    "empty",
    "pixel-aligned",
    "rectliinear",
    "straight",
    "curved",
};
static void
print_path (cairo_output_stream_t *stream,
	    const struct path *p)
{
    _cairo_output_stream_printf (stream, "  path:");
    print_array (stream, p->type, path_names, ARRAY_LENGTH (path_names));
    _cairo_output_stream_printf (stream, "\n");
}
static const char *clip_names[] = {
    "none",
    "region",
    "boxes",
    "single path",
    "polygon",
    "general",
};
static void
print_clip (cairo_output_stream_t *stream, const struct clip *c)
{
    _cairo_output_stream_printf (stream, "  clip:");
    print_array (stream, c->type, clip_names, ARRAY_LENGTH (clip_names));
    _cairo_output_stream_printf (stream, "\n");
}
static void
print_record (cairo_output_stream_t *stream,
	      cairo_observation_record_t *r)
{
    _cairo_output_stream_printf (stream, "  op: %s\n", operator_names[r->op]);
    _cairo_output_stream_printf (stream, "  source: %s\n",
				 pattern_names[r->source]);
    if (r->mask != -1)
	_cairo_output_stream_printf (stream, "  mask: %s\n",
				     pattern_names[r->mask]);
    if (r->num_glyphs != -1)
	_cairo_output_stream_printf (stream, "  num_glyphs: %d\n",
				     r->num_glyphs);
    if (r->path != -1)
	_cairo_output_stream_printf (stream, "  path: %s\n",
				    path_names[r->path]);
    if (r->fill_rule != -1)
	_cairo_output_stream_printf (stream, "  fill rule: %s\n",
				     fill_rule_names[r->fill_rule]);
    if (r->antialias != -1)
	_cairo_output_stream_printf (stream, "  antialias: %s\n",
				     antialias_names[r->antialias]);
    _cairo_output_stream_printf (stream, "  clip: %s\n", clip_names[r->clip]);
    _cairo_output_stream_printf (stream, "  elapsed: %f ns\n",
				 _cairo_time_to_ns (r->elapsed));
}
static double percent (cairo_time_t a, cairo_time_t b)
{
    /* Fake %.1f */
    return _cairo_round (_cairo_time_to_s (a) * 1000 /
			 _cairo_time_to_s (b)) / 10;
}
static cairo_bool_t
replay_record (cairo_observation_t *log,
	       cairo_observation_record_t *r,
	       cairo_device_t *script)
{
#if CAIRO_HAS_SCRIPT_SURFACE
    cairo_surface_t *surface;
    cairo_int_status_t status;
    if (log->record == NULL || script == NULL)
	return FALSE;
    surface = cairo_script_surface_create (script,
					   r->target_content,
					   r->target_width,
					   r->target_height);
    status =
	_cairo_recording_surface_replay_one (log->record, r->index, surface);
    cairo_surface_destroy (surface);
    assert (status == CAIRO_INT_STATUS_SUCCESS);
    return TRUE;
#else
    return FALSE;
#endif
}
static cairo_time_t
_cairo_observation_total_elapsed (cairo_observation_t *log)
{
    cairo_time_t total;
    total = log->paint.elapsed;
    total = _cairo_time_add (total, log->mask.elapsed);
    total = _cairo_time_add (total, log->fill.elapsed);
    total = _cairo_time_add (total, log->stroke.elapsed);
    total = _cairo_time_add (total, log->glyphs.elapsed);
    return total;
}
static void
_cairo_observation_print (cairo_output_stream_t *stream,
			  cairo_observation_t *log)
{
    cairo_device_t *script;
    cairo_time_t total;
#if CAIRO_HAS_SCRIPT_SURFACE
    script = _cairo_script_context_create_internal (stream);
    _cairo_script_context_attach_snapshots (script, FALSE);
#else
    script = NULL;
#endif
    total = _cairo_observation_total_elapsed (log);
    _cairo_output_stream_printf (stream, "elapsed: %f\n",
				 _cairo_time_to_ns (total));
    _cairo_output_stream_printf (stream, "surfaces: %d\n",
				 log->num_surfaces);
    _cairo_output_stream_printf (stream, "contexts: %d\n",
				 log->num_contexts);
    _cairo_output_stream_printf (stream, "sources acquired: %d\n",
				 log->num_sources_acquired);
    _cairo_output_stream_printf (stream, "paint: count %d [no-op %d], elapsed %f [%f%%]\n",
				 log->paint.count, log->paint.noop,
				 _cairo_time_to_ns (log->paint.elapsed),
				 percent (log->paint.elapsed, total));
    if (log->paint.count) {
	print_extents (stream, &log->paint.extents);
	print_operators (stream, log->paint.operators);
	print_pattern (stream, "source", &log->paint.source);
	print_clip (stream, &log->paint.clip);
	_cairo_output_stream_printf (stream, "slowest paint: %f%%\n",
				     percent (log->paint.slowest.elapsed,
					      log->paint.elapsed));
	print_record (stream, &log->paint.slowest);
	_cairo_output_stream_printf (stream, "\n");
	if (replay_record (log, &log->paint.slowest, script))
	    _cairo_output_stream_printf (stream, "\n\n");
    }
    _cairo_output_stream_printf (stream, "mask: count %d [no-op %d], elapsed %f [%f%%]\n",
				 log->mask.count, log->mask.noop,
				 _cairo_time_to_ns (log->mask.elapsed),
				 percent (log->mask.elapsed, total));
    if (log->mask.count) {
	print_extents (stream, &log->mask.extents);
	print_operators (stream, log->mask.operators);
	print_pattern (stream, "source", &log->mask.source);
	print_pattern (stream, "mask", &log->mask.mask);
	print_clip (stream, &log->mask.clip);
	_cairo_output_stream_printf (stream, "slowest mask: %f%%\n",
				     percent (log->mask.slowest.elapsed,
					      log->mask.elapsed));
	print_record (stream, &log->mask.slowest);
	_cairo_output_stream_printf (stream, "\n");
	if (replay_record (log, &log->mask.slowest, script))
	    _cairo_output_stream_printf (stream, "\n\n");
    }
    _cairo_output_stream_printf (stream, "fill: count %d [no-op %d], elaspsed %f [%f%%]\n",
				 log->fill.count, log->fill.noop,
				 _cairo_time_to_ns (log->fill.elapsed),
				 percent (log->fill.elapsed, total));
    if (log->fill.count) {
	print_extents (stream, &log->fill.extents);
	print_operators (stream, log->fill.operators);
	print_pattern (stream, "source", &log->fill.source);
	print_path (stream, &log->fill.path);
	print_fill_rule (stream, log->fill.fill_rule);
	print_antialias (stream, log->fill.antialias);
	print_clip (stream, &log->fill.clip);
	_cairo_output_stream_printf (stream, "slowest fill: %f%%\n",
				     percent (log->fill.slowest.elapsed,
					      log->fill.elapsed));
	print_record (stream, &log->fill.slowest);
	_cairo_output_stream_printf (stream, "\n");
	if (replay_record (log, &log->fill.slowest, script))
	    _cairo_output_stream_printf (stream, "\n\n");
    }
    _cairo_output_stream_printf (stream, "stroke: count %d [no-op %d], elapsed %f [%f%%]\n",
				 log->stroke.count, log->stroke.noop,
				 _cairo_time_to_ns (log->stroke.elapsed),
				 percent (log->stroke.elapsed, total));
    if (log->stroke.count) {
	print_extents (stream, &log->stroke.extents);
	print_operators (stream, log->stroke.operators);
	print_pattern (stream, "source", &log->stroke.source);
	print_path (stream, &log->stroke.path);
	print_antialias (stream, log->stroke.antialias);
	print_line_caps (stream, log->stroke.caps);
	print_line_joins (stream, log->stroke.joins);
	print_clip (stream, &log->stroke.clip);
	_cairo_output_stream_printf (stream, "slowest stroke: %f%%\n",
				     percent (log->stroke.slowest.elapsed,
					      log->stroke.elapsed));
	print_record (stream, &log->stroke.slowest);
	_cairo_output_stream_printf (stream, "\n");
	if (replay_record (log, &log->stroke.slowest, script))
	    _cairo_output_stream_printf (stream, "\n\n");
    }
    _cairo_output_stream_printf (stream, "glyphs: count %d [no-op %d], elasped %f [%f%%]\n",
				 log->glyphs.count, log->glyphs.noop,
				 _cairo_time_to_ns (log->glyphs.elapsed),
				 percent (log->glyphs.elapsed, total));
    if (log->glyphs.count) {
	print_extents (stream, &log->glyphs.extents);
	print_operators (stream, log->glyphs.operators);
	print_pattern (stream, "source", &log->glyphs.source);
	print_clip (stream, &log->glyphs.clip);
	_cairo_output_stream_printf (stream, "slowest glyphs: %f%%\n",
				     percent (log->glyphs.slowest.elapsed,
					      log->glyphs.elapsed));
	print_record (stream, &log->glyphs.slowest);
	_cairo_output_stream_printf (stream, "\n");
	if (replay_record (log, &log->glyphs.slowest, script))
	    _cairo_output_stream_printf (stream, "\n\n");
    }
    cairo_device_destroy (script);
}
/**
 * cairo_surface_observer_print:
 * @abstract_surface: a #cairo_surface_observer_t
 * @write_func: callback for writing on a stream
 * @closure: data to pass to @write_func
 *
 * Prints the observer log using the given callback.
 *
 * Returns: the status of the print operation
 *
 * Since: 1.12
 **/
cairo_status_t
cairo_surface_observer_print (cairo_surface_t *abstract_surface,
			      cairo_write_func_t write_func,
			      void *closure)
{
    cairo_output_stream_t *stream;
    cairo_surface_observer_t *surface;
    if (unlikely (abstract_surface->status))
	return abstract_surface->status;
    if (unlikely (! _cairo_surface_is_observer (abstract_surface)))
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    surface = (cairo_surface_observer_t *) abstract_surface;
    stream = _cairo_output_stream_create (write_func, NULL, closure);
    _cairo_observation_print (stream, &surface->log);
    return _cairo_output_stream_destroy (stream);
}
/**
 * cairo_surface_observer_elapsed:
 * @abstract_surface: a #cairo_surface_observer_t
 *
 * Returns the total observation time.
 *
 * Returns: the elapsed time, in nanoseconds
 *
 * Since: 1.12
 **/
double
cairo_surface_observer_elapsed (cairo_surface_t *abstract_surface)
{
    cairo_surface_observer_t *surface;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_surface->ref_count)))
	return -1;
    if (! _cairo_surface_is_observer (abstract_surface))
	return -1;
    surface = (cairo_surface_observer_t *) abstract_surface;
    return _cairo_time_to_ns (_cairo_observation_total_elapsed (&surface->log));
}
/**
 * cairo_device_observer_print:
 * @abstract_device: the observed #cairo_device_t
 * @write_func: the write function
 * @closure: data to pass to the @write_func
 *
 * Prints the device log using the given function.
 *
 * Returns: the status after the operation
 *
 * Since: 1.12
 **/
cairo_status_t
cairo_device_observer_print (cairo_device_t *abstract_device,
			     cairo_write_func_t write_func,
			     void *closure)
{
    cairo_output_stream_t *stream;
    cairo_device_observer_t *device;
    if (unlikely (abstract_device->status))
	return abstract_device->status;
    if (unlikely (! _cairo_device_is_observer (abstract_device)))
	return _cairo_error (CAIRO_STATUS_DEVICE_TYPE_MISMATCH);
    device = (cairo_device_observer_t *) abstract_device;
    stream = _cairo_output_stream_create (write_func, NULL, closure);
    _cairo_observation_print (stream, &device->log);
    return _cairo_output_stream_destroy (stream);
}
/**
 * cairo_device_observer_elapsed:
 * @abstract_device: the observed #cairo_device_t
 *
 * Returns the total elapsed time of the observation.
 *
 * Returns: the elapsed time, in nanoseconds.
 *
 * Since: 1.12
 **/
double
cairo_device_observer_elapsed (cairo_device_t *abstract_device)
{
    cairo_device_observer_t *device;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
	return -1;
    if (! _cairo_device_is_observer (abstract_device))
	return -1;
    device = (cairo_device_observer_t *) abstract_device;
    return _cairo_time_to_ns (_cairo_observation_total_elapsed (&device->log));
}
/**
 * cairo_device_observer_paint_elapsed:
 * @abstract_device: the observed #cairo_device_t
 *
 * Returns the elapsed time of the paint operations.
 *
 * Returns: the elapsed time, in nanoseconds.
 *
 * Since: 1.12
 **/
double
cairo_device_observer_paint_elapsed (cairo_device_t *abstract_device)
{
    cairo_device_observer_t *device;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
	return -1;
    if (! _cairo_device_is_observer (abstract_device))
	return -1;
    device = (cairo_device_observer_t *) abstract_device;
    return _cairo_time_to_ns (device->log.paint.elapsed);
}
/**
 * cairo_device_observer_mask_elapsed:
 * @abstract_device: the observed #cairo_device_t
 *
 * Returns the elapsed time of the mask operations.
 *
 * Returns: the elapsed time, in nanoseconds
 *
 * Since: 1.12
 **/
double
cairo_device_observer_mask_elapsed (cairo_device_t *abstract_device)
{
    cairo_device_observer_t *device;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
	return -1;
    if (! _cairo_device_is_observer (abstract_device))
	return -1;
    device = (cairo_device_observer_t *) abstract_device;
    return _cairo_time_to_ns (device->log.mask.elapsed);
}
/**
 * cairo_device_observer_fill_elapsed:
 * @abstract_device: the observed #cairo_device_t
 *
 * Returns the elapsed time of the fill operations.
 *
 * Returns: the elapsed time, in nanoseconds.
 *
 * Since: 1.12
 **/
double
cairo_device_observer_fill_elapsed (cairo_device_t *abstract_device)
{
    cairo_device_observer_t *device;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
	return -1;
    if (! _cairo_device_is_observer (abstract_device))
	return -1;
    device = (cairo_device_observer_t *) abstract_device;
    return _cairo_time_to_ns (device->log.fill.elapsed);
}
/**
 * cairo_device_observer_stroke_elapsed:
 * @abstract_device: the observed #cairo_device_t
 *
 * Returns the elapsed time of the stroke operations.
 *
 * Returns: the elapsed time, in nanoseconds.
 *
 * Since: 1.12
 **/
double
cairo_device_observer_stroke_elapsed (cairo_device_t *abstract_device)
{
    cairo_device_observer_t *device;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
	return -1;
    if (! _cairo_device_is_observer (abstract_device))
	return -1;
    device = (cairo_device_observer_t *) abstract_device;
    return _cairo_time_to_ns (device->log.stroke.elapsed);
}
/**
 * cairo_device_observer_glyphs_elapsed:
 * @abstract_device: the observed #cairo_device_t
 *
 * Returns the elapsed time of the glyph operations.
 *
 * Returns: the elapsed time, in nanoseconds.
 *
 * Since: 1.12
 **/
double
cairo_device_observer_glyphs_elapsed (cairo_device_t *abstract_device)
{
    cairo_device_observer_t *device;
    if (unlikely (CAIRO_REFERENCE_COUNT_IS_INVALID (&abstract_device->ref_count)))
	return -1;
    if (! _cairo_device_is_observer (abstract_device))
	return -1;
    device = (cairo_device_observer_t *) abstract_device;
    return _cairo_time_to_ns (device->log.glyphs.elapsed);
}