1 /* vim: set sw=4 sts=4: -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
2 /* cairo - a vector graphics library with display and print output
3  *
4  * Copyright © 2004 Red Hat, Inc
5  * Copyright © 2005-2007 Emmanuel Pacaud <emmanuel.pacaud@free.fr>
6  * Copyright © 2006 Red Hat, Inc
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it either under the terms of the GNU Lesser General Public
10  * License version 2.1 as published by the Free Software Foundation
11  * (the "LGPL") or, at your option, under the terms of the Mozilla
12  * Public License Version 1.1 (the "MPL"). If you do not alter this
13  * notice, a recipient may use your version of this file under either
14  * the MPL or the LGPL.
15  *
16  * You should have received a copy of the LGPL along with this library
17  * in the file COPYING-LGPL-2.1; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
19  * You should have received a copy of the MPL along with this library
20  * in the file COPYING-MPL-1.1
21  *
22  * The contents of this file are subject to the Mozilla Public License
23  * Version 1.1 (the "License"); you may not use this file except in
24  * compliance with the License. You may obtain a copy of the License at
25  * http://www.mozilla.org/MPL/
26  *
27  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
28  * OF ANY KIND, either express or implied. See the LGPL or the MPL for
29  * the specific language governing rights and limitations.
30  *
31  * The Original Code is the cairo graphics library.
32  *
33  * The Initial Developer of the Original Code is University of Southern
34  * California.
35  *
36  * Contributor(s):
37  *	Kristian Høgsberg <krh@redhat.com>
38  *	Emmanuel Pacaud <emmanuel.pacaud@free.fr>
39  *	Carl Worth <cworth@cworth.org>
40  */
41 
42 #define _BSD_SOURCE /* for snprintf() */
43 #include "cairoint.h"
44 #include "cairo-svg.h"
45 #include "cairo-analysis-surface-private.h"
46 #include "cairo-error-private.h"
47 #include "cairo-image-info-private.h"
48 #include "cairo-recording-surface-private.h"
49 #include "cairo-output-stream-private.h"
50 #include "cairo-path-fixed-private.h"
51 #include "cairo-paginated-private.h"
52 #include "cairo-scaled-font-subsets-private.h"
53 #include "cairo-surface-clipper-private.h"
54 #include "cairo-svg-surface-private.h"
55 
56 /**
57  * SECTION:cairo-svg
58  * @Title: SVG Surfaces
59  * @Short_Description: Rendering SVG documents
60  * @See_Also: #cairo_surface_t
61  *
62  * The SVG surface is used to render cairo graphics to
63  * SVG files and is a multi-page vector surface backend.
64  */
65 
66 /**
67  * CAIRO_HAS_SVG_SURFACE:
68  *
69  * Defined if the SVG surface backend is available.
70  * This macro can be used to conditionally compile backend-specific code.
71  */
72 
73 typedef struct cairo_svg_page cairo_svg_page_t;
74 
75 static const int invalid_pattern_id = -1;
76 
77 static const cairo_svg_version_t _cairo_svg_versions[] =
78 {
79     CAIRO_SVG_VERSION_1_1,
80     CAIRO_SVG_VERSION_1_2
81 };
82 
83 #define CAIRO_SVG_VERSION_LAST ARRAY_LENGTH (_cairo_svg_versions)
84 
85 static void
86 _cairo_svg_surface_emit_path (cairo_output_stream_t	*output,
87 			      cairo_path_fixed_t	*path,
88 			      const cairo_matrix_t	*ctm_inverse);
89 
90 static cairo_bool_t
_cairo_svg_version_has_page_set_support(cairo_svg_version_t version)91 _cairo_svg_version_has_page_set_support (cairo_svg_version_t version)
92 {
93     return version > CAIRO_SVG_VERSION_1_1;
94 }
95 
96 static const char * _cairo_svg_version_strings[CAIRO_SVG_VERSION_LAST] =
97 {
98     "SVG 1.1",
99     "SVG 1.2"
100 };
101 
102 static const char * _cairo_svg_internal_version_strings[CAIRO_SVG_VERSION_LAST] =
103 {
104     "1.1",
105     "1.2"
106 };
107 
108 struct cairo_svg_page {
109     unsigned int surface_id;
110     unsigned int clip_level;
111     cairo_output_stream_t *xml_node;
112 };
113 
114 struct cairo_svg_document {
115     cairo_output_stream_t *output_stream;
116     unsigned long refcount;
117     cairo_surface_t *owner;
118     cairo_bool_t finished;
119 
120     double width;
121     double height;
122 
123     cairo_output_stream_t *xml_node_defs;
124     cairo_output_stream_t *xml_node_glyphs;
125 
126     unsigned int linear_pattern_id;
127     unsigned int radial_pattern_id;
128     unsigned int pattern_id;
129     unsigned int filter_id;
130     unsigned int clip_id;
131     unsigned int mask_id;
132 
133     cairo_bool_t alpha_filter;
134 
135     cairo_svg_version_t svg_version;
136 
137     cairo_scaled_font_subsets_t *font_subsets;
138 };
139 
140 static cairo_status_t
141 _cairo_svg_document_create (cairo_output_stream_t	 *stream,
142 			    double			  width,
143 			    double			  height,
144 			    cairo_svg_version_t		  version,
145 			    cairo_svg_document_t	**document_out);
146 
147 static cairo_status_t
148 _cairo_svg_document_destroy (cairo_svg_document_t *document);
149 
150 static cairo_status_t
151 _cairo_svg_document_finish (cairo_svg_document_t *document);
152 
153 static cairo_svg_document_t *
154 _cairo_svg_document_reference (cairo_svg_document_t *document);
155 
156 static unsigned int
157 _cairo_svg_document_allocate_mask_id (cairo_svg_document_t *document);
158 
159 static cairo_surface_t *
160 _cairo_svg_surface_create_for_document (cairo_svg_document_t	*document,
161 					cairo_content_t		 content,
162 					double			 width,
163 					double			 height);
164 static cairo_surface_t *
165 _cairo_svg_surface_create_for_stream_internal (cairo_output_stream_t	*stream,
166 					       double			 width,
167 					       double			 height,
168 					       cairo_svg_version_t	 version);
169 
170 static const cairo_surface_backend_t cairo_svg_surface_backend;
171 static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend;
172 
173 /**
174  * cairo_svg_surface_create_for_stream:
175  * @write_func: a #cairo_write_func_t to accept the output data, may be %NULL
176  *              to indicate a no-op @write_func. With a no-op @write_func,
177  *              the surface may be queried or used as a source without
178  *              generating any temporary files.
179  * @closure: the closure argument for @write_func
180  * @width_in_points: width of the surface, in points (1 point == 1/72.0 inch)
181  * @height_in_points: height of the surface, in points (1 point == 1/72.0 inch)
182  *
183  * Creates a SVG surface of the specified size in points to be written
184  * incrementally to the stream represented by @write_func and @closure.
185  *
186  * Return value: a pointer to the newly created surface. The caller
187  * owns the surface and should call cairo_surface_destroy() when done
188  * with it.
189  *
190  * This function always returns a valid pointer, but it will return a
191  * pointer to a "nil" surface if an error such as out of memory
192  * occurs. You can use cairo_surface_status() to check for this.
193  *
194  * Since: 1.2
195  */
196 cairo_surface_t *
cairo_svg_surface_create_for_stream(cairo_write_func_t write_func,void * closure,double width,double height)197 cairo_svg_surface_create_for_stream (cairo_write_func_t		 write_func,
198 				     void			*closure,
199 				     double			 width,
200 				     double			 height)
201 {
202     cairo_output_stream_t *stream;
203 
204     stream = _cairo_output_stream_create (write_func, NULL, closure);
205     if (_cairo_output_stream_get_status (stream))
206 	return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
207 
208     return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1);
209 }
210 
211 /**
212  * cairo_svg_surface_create:
213  * @filename: a filename for the SVG output (must be writable), %NULL may be
214  *            used to specify no output. This will generate a SVG surface that
215  *            may be queried and used as a source, without generating a
216  *            temporary file.
217  * @width_in_points: width of the surface, in points (1 point == 1/72.0 inch)
218  * @height_in_points: height of the surface, in points (1 point == 1/72.0 inch)
219  *
220  * Creates a SVG surface of the specified size in points to be written
221  * to @filename.
222  *
223  * The SVG surface backend recognizes the following MIME types for the
224  * data attached to a surface (see cairo_surface_set_mime_data()) when
225  * it is used as a source pattern for drawing on this surface:
226  * %CAIRO_MIME_TYPE_JPEG, %CAIRO_MIME_TYPE_PNG,
227  * %CAIRO_MIME_TYPE_URI. If any of them is specified, the SVG backend
228  * emits a href with the content of MIME data instead of a surface
229  * snapshot (PNG, Base64-encoded) in the corresponding image tag.
230  *
231  * The unofficial MIME type %CAIRO_MIME_TYPE_URI is examined
232  * first. If present, the URI is emitted as is: assuring the
233  * correctness of URI is left to the client code.
234  *
235  * If %CAIRO_MIME_TYPE_URI is not present, but %CAIRO_MIME_TYPE_JPEG
236  * or %CAIRO_MIME_TYPE_PNG is specified, the corresponding data is
237  * Base64-encoded and emitted.
238  *
239  * Return value: a pointer to the newly created surface. The caller
240  * owns the surface and should call cairo_surface_destroy() when done
241  * with it.
242  *
243  * This function always returns a valid pointer, but it will return a
244  * pointer to a "nil" surface if an error such as out of memory
245  * occurs. You can use cairo_surface_status() to check for this.
246  *
247  * Since: 1.2
248  **/
249 cairo_surface_t *
cairo_svg_surface_create(const char * filename,double width,double height)250 cairo_svg_surface_create (const char	*filename,
251 			  double	 width,
252 			  double	 height)
253 {
254     cairo_output_stream_t *stream;
255 
256     stream = _cairo_output_stream_create_for_filename (filename);
257     if (_cairo_output_stream_get_status (stream))
258 	return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
259 
260     return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1);
261 }
262 
263 static cairo_bool_t
_cairo_surface_is_svg(cairo_surface_t * surface)264 _cairo_surface_is_svg (cairo_surface_t *surface)
265 {
266     return surface->backend == &cairo_svg_surface_backend;
267 }
268 
269 /* If the abstract_surface is a paginated surface, and that paginated
270  * surface's target is a svg_surface, then set svg_surface to that
271  * target. Otherwise return FALSE.
272  */
273 static cairo_bool_t
_extract_svg_surface(cairo_surface_t * surface,cairo_svg_surface_t ** svg_surface)274 _extract_svg_surface (cairo_surface_t		 *surface,
275 		      cairo_svg_surface_t	**svg_surface)
276 {
277     cairo_surface_t *target;
278     cairo_status_t status_ignored;
279 
280     if (surface->status)
281 	return FALSE;
282     if (surface->finished) {
283 	status_ignored = _cairo_surface_set_error (surface,
284 						   _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
285         return FALSE;
286     }
287 
288     if (! _cairo_surface_is_paginated (surface)) {
289 	status_ignored = _cairo_surface_set_error (surface,
290 						   _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH));
291 	return FALSE;
292     }
293 
294     target = _cairo_paginated_surface_get_target (surface);
295     if (target->status) {
296 	status_ignored = _cairo_surface_set_error (surface,
297 						   target->status);
298 	return FALSE;
299     }
300     if (target->finished) {
301 	status_ignored = _cairo_surface_set_error (surface,
302 						   _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
303         return FALSE;
304     }
305 
306     if (! _cairo_surface_is_svg (target)) {
307 	status_ignored = _cairo_surface_set_error (surface,
308 						   _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH));
309 	return FALSE;
310     }
311 
312     *svg_surface = (cairo_svg_surface_t *) target;
313     return TRUE;
314 }
315 
316 /**
317  * cairo_svg_surface_restrict_to_version:
318  * @surface: a SVG #cairo_surface_t
319  * @version: SVG version
320  *
321  * Restricts the generated SVG file to @version. See cairo_svg_get_versions()
322  * for a list of available version values that can be used here.
323  *
324  * This function should only be called before any drawing operations
325  * have been performed on the given surface. The simplest way to do
326  * this is to call this function immediately after creating the
327  * surface.
328  *
329  * Since: 1.2
330  **/
331 void
cairo_svg_surface_restrict_to_version(cairo_surface_t * abstract_surface,cairo_svg_version_t version)332 cairo_svg_surface_restrict_to_version (cairo_surface_t		*abstract_surface,
333 				       cairo_svg_version_t	 version)
334 {
335     cairo_svg_surface_t *surface = NULL; /* hide compiler warning */
336 
337     if (! _extract_svg_surface (abstract_surface, &surface))
338 	return;
339 
340     if (version < CAIRO_SVG_VERSION_LAST)
341 	surface->document->svg_version = version;
342 }
343 
344 /**
345  * cairo_svg_get_versions:
346  * @versions: supported version list
347  * @num_versions: list length
348  *
349  * Used to retrieve the list of supported versions. See
350  * cairo_svg_surface_restrict_to_version().
351  *
352  * Since: 1.2
353  **/
354 void
cairo_svg_get_versions(cairo_svg_version_t const ** versions,int * num_versions)355 cairo_svg_get_versions (cairo_svg_version_t const	**versions,
356                         int				 *num_versions)
357 {
358     if (versions != NULL)
359 	*versions = _cairo_svg_versions;
360 
361     if (num_versions != NULL)
362 	*num_versions = CAIRO_SVG_VERSION_LAST;
363 }
364 
365 /**
366  * cairo_svg_version_to_string:
367  * @version: a version id
368  *
369  * Get the string representation of the given @version id. This function
370  * will return %NULL if @version isn't valid. See cairo_svg_get_versions()
371  * for a way to get the list of valid version ids.
372  *
373  * Return value: the string associated to given version.
374  *
375  * Since: 1.2
376  **/
377 const char *
cairo_svg_version_to_string(cairo_svg_version_t version)378 cairo_svg_version_to_string (cairo_svg_version_t version)
379 {
380     if (version >= CAIRO_SVG_VERSION_LAST)
381 	return NULL;
382 
383     return _cairo_svg_version_strings[version];
384 }
385 
386 static cairo_bool_t
_cliprect_covers_surface(cairo_svg_surface_t * surface,cairo_path_fixed_t * path)387 _cliprect_covers_surface (cairo_svg_surface_t *surface,
388 			  cairo_path_fixed_t *path)
389 {
390     cairo_box_t box;
391 
392     if (_cairo_path_fixed_is_box (path, &box)) {
393 	if (box.p1.x <= 0 &&
394 	    box.p1.y <= 0 &&
395 	    _cairo_fixed_to_double (box.p2.x) >= surface->width &&
396 	    _cairo_fixed_to_double (box.p2.y) >= surface->height)
397 	{
398 	    return TRUE;
399 	}
400     }
401 
402     return FALSE;
403 }
404 
405 static cairo_status_t
_cairo_svg_surface_clipper_intersect_clip_path(cairo_surface_clipper_t * clipper,cairo_path_fixed_t * path,cairo_fill_rule_t fill_rule,double tolerance,cairo_antialias_t antialias)406 _cairo_svg_surface_clipper_intersect_clip_path (cairo_surface_clipper_t *clipper,
407 						cairo_path_fixed_t	*path,
408 						cairo_fill_rule_t	 fill_rule,
409 						double			 tolerance,
410 						cairo_antialias_t	 antialias)
411 {
412     cairo_svg_surface_t *surface = cairo_container_of (clipper,
413 						       cairo_svg_surface_t,
414 						       clipper);
415     cairo_svg_document_t *document = surface->document;
416     unsigned int i;
417 
418     if (path == NULL) {
419 	for (i = 0; i < surface->clip_level; i++)
420 	    _cairo_output_stream_printf (surface->xml_node, "</g>\n");
421 
422 	surface->clip_level = 0;
423 	return CAIRO_STATUS_SUCCESS;
424     }
425 
426     /* skip trivial whole-page clips */
427     if (_cliprect_covers_surface (surface, path))
428 	return CAIRO_STATUS_SUCCESS;
429 
430     _cairo_output_stream_printf (document->xml_node_defs,
431 				 "<clipPath id=\"clip%d\">\n"
432 				 "  <path ",
433 				 document->clip_id);
434     _cairo_svg_surface_emit_path (document->xml_node_defs, path, NULL);
435 
436     _cairo_output_stream_printf (document->xml_node_defs,
437 				 "/>\n"
438 				 "</clipPath>\n");
439 
440     _cairo_output_stream_printf (surface->xml_node,
441 				 "<g clip-path=\"url(#clip%d)\" "
442 				 "clip-rule=\"%s\">\n",
443 				 document->clip_id,
444 				 fill_rule == CAIRO_FILL_RULE_EVEN_ODD ?
445 				 "evenodd" : "nonzero");
446 
447     document->clip_id++;
448     surface->clip_level++;
449 
450     return CAIRO_STATUS_SUCCESS;
451 }
452 
453 static cairo_surface_t *
_cairo_svg_surface_create_for_document(cairo_svg_document_t * document,cairo_content_t content,double width,double height)454 _cairo_svg_surface_create_for_document (cairo_svg_document_t	*document,
455 					cairo_content_t		 content,
456 					double			 width,
457 					double			 height)
458 {
459     cairo_svg_surface_t *surface;
460     cairo_surface_t *paginated;
461     cairo_status_t status, status_ignored;
462 
463     surface = malloc (sizeof (cairo_svg_surface_t));
464     if (unlikely (surface == NULL))
465 	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
466 
467     _cairo_surface_init (&surface->base,
468 			 &cairo_svg_surface_backend,
469 			 NULL, /* device */
470 			 content);
471 
472     surface->width = width;
473     surface->height = height;
474 
475     surface->document = _cairo_svg_document_reference (document);
476 
477     surface->clip_level = 0;
478     _cairo_surface_clipper_init (&surface->clipper,
479 				 _cairo_svg_surface_clipper_intersect_clip_path);
480 
481     surface->base_clip = document->clip_id++;
482     surface->is_base_clip_emitted = FALSE;
483 
484     surface->xml_node = _cairo_memory_stream_create ();
485     status = _cairo_output_stream_get_status (surface->xml_node);
486     if (unlikely (status))
487 	goto CLEANUP;
488 
489     _cairo_array_init (&surface->page_set, sizeof (cairo_svg_page_t));
490 
491     if (content == CAIRO_CONTENT_COLOR) {
492 	_cairo_output_stream_printf (surface->xml_node,
493 				     "<rect width=\"%f\" height=\"%f\" "
494 				     "style=\"opacity:1;stroke:none;"
495 				     "fill:rgb(0,0,0);\"/>\n",
496 				     width, height);
497 	status = _cairo_output_stream_get_status (surface->xml_node);
498 	if (unlikely (status))
499 	    goto CLEANUP;
500     }
501 
502     surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE;
503     surface->force_fallbacks = FALSE;
504     surface->content = content;
505 
506     paginated = _cairo_paginated_surface_create (&surface->base,
507 					         surface->content,
508 						 &cairo_svg_surface_paginated_backend);
509     status = paginated->status;
510     if (status == CAIRO_STATUS_SUCCESS) {
511 	/* paginated keeps the only reference to surface now, drop ours */
512 	cairo_surface_destroy (&surface->base);
513 	return paginated;
514     }
515 
516     /* ignore status as we are on the error path */
517 CLEANUP:
518     status_ignored = _cairo_output_stream_destroy (surface->xml_node);
519     status_ignored = _cairo_svg_document_destroy (document);
520 
521     free (surface);
522 
523     return _cairo_surface_create_in_error (status);
524 }
525 
526 static cairo_surface_t *
_cairo_svg_surface_create_for_stream_internal(cairo_output_stream_t * stream,double width,double height,cairo_svg_version_t version)527 _cairo_svg_surface_create_for_stream_internal (cairo_output_stream_t	*stream,
528 					       double			 width,
529 					       double			 height,
530 					       cairo_svg_version_t	 version)
531 {
532     cairo_svg_document_t *document = NULL; /* silence compiler */
533     cairo_surface_t *surface;
534     cairo_status_t status;
535 
536     status = _cairo_svg_document_create (stream,
537 	                                 width, height, version,
538 					 &document);
539     if (unlikely (status)) {
540 	surface =  _cairo_surface_create_in_error (status);
541 	/* consume the output stream on behalf of caller */
542 	status = _cairo_output_stream_destroy (stream);
543 	return surface;
544     }
545 
546     surface = _cairo_svg_surface_create_for_document (document, CAIRO_CONTENT_COLOR_ALPHA,
547 						      width, height);
548     if (surface->status) {
549 	status = _cairo_svg_document_destroy (document);
550 	return surface;
551     }
552 
553     document->owner = surface;
554     status = _cairo_svg_document_destroy (document);
555     /* the ref count should be 2 at this point */
556     assert (status == CAIRO_STATUS_SUCCESS);
557 
558     return surface;
559 }
560 
561 static cairo_svg_page_t *
_cairo_svg_surface_store_page(cairo_svg_surface_t * surface)562 _cairo_svg_surface_store_page (cairo_svg_surface_t *surface)
563 {
564     unsigned int i;
565     cairo_svg_page_t page;
566     cairo_output_stream_t *stream;
567     cairo_status_t status;
568 
569     stream = _cairo_memory_stream_create ();
570     if (_cairo_output_stream_get_status (stream)) {
571 	status = _cairo_output_stream_destroy (stream);
572 	return NULL;
573     }
574 
575     page.surface_id = surface->base.unique_id;
576     page.clip_level = surface->clip_level;
577     page.xml_node = surface->xml_node;
578 
579     if (_cairo_array_append (&surface->page_set, &page)) {
580 	status = _cairo_output_stream_destroy (stream);
581 	return NULL;
582     }
583 
584     surface->xml_node = stream;
585     surface->clip_level = 0;
586     for (i = 0; i < page.clip_level; i++)
587 	_cairo_output_stream_printf (page.xml_node, "</g>\n");
588 
589     _cairo_surface_clipper_reset (&surface->clipper);
590 
591     return _cairo_array_index (&surface->page_set,
592 			       surface->page_set.num_elements - 1);
593 }
594 
595 static cairo_int_status_t
_cairo_svg_surface_copy_page(void * abstract_surface)596 _cairo_svg_surface_copy_page (void *abstract_surface)
597 {
598     cairo_svg_surface_t *surface = abstract_surface;
599     cairo_svg_page_t *page;
600 
601     page = _cairo_svg_surface_store_page (surface);
602     if (unlikely (page == NULL))
603 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
604 
605     _cairo_memory_stream_copy (page->xml_node, surface->xml_node);
606 
607     return CAIRO_STATUS_SUCCESS;
608 }
609 
610 static cairo_int_status_t
_cairo_svg_surface_show_page(void * abstract_surface)611 _cairo_svg_surface_show_page (void *abstract_surface)
612 {
613     cairo_svg_surface_t *surface = abstract_surface;
614 
615     if (unlikely (_cairo_svg_surface_store_page (surface) == NULL))
616 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
617 
618     return CAIRO_STATUS_SUCCESS;
619 }
620 
621 static void
_cairo_svg_surface_emit_transform(cairo_output_stream_t * output,char const * attribute_str,const cairo_matrix_t * object_matrix,const cairo_matrix_t * parent_matrix)622 _cairo_svg_surface_emit_transform (cairo_output_stream_t *output,
623 				   char const		 *attribute_str,
624 				   const cairo_matrix_t	 *object_matrix,
625 				   const cairo_matrix_t  *parent_matrix)
626 {
627     cairo_matrix_t matrix = *object_matrix;
628 
629     if (parent_matrix != NULL)
630 	cairo_matrix_multiply (&matrix, &matrix, parent_matrix);
631 
632     if (!_cairo_matrix_is_identity (&matrix))
633 	_cairo_output_stream_printf (output,
634 				     "%s=\"matrix(%f,%f,%f,%f,%f,%f)\"",
635 				     attribute_str,
636 				     matrix.xx, matrix.yx,
637 				     matrix.xy, matrix.yy,
638 				     matrix.x0, matrix.y0);
639 }
640 
641 typedef struct {
642     cairo_output_stream_t *output;
643     const cairo_matrix_t *ctm_inverse;
644 } svg_path_info_t;
645 
646 static cairo_status_t
_cairo_svg_path_move_to(void * closure,const cairo_point_t * point)647 _cairo_svg_path_move_to (void *closure,
648 			 const cairo_point_t *point)
649 {
650     svg_path_info_t *info = closure;
651     double x = _cairo_fixed_to_double (point->x);
652     double y = _cairo_fixed_to_double (point->y);
653 
654     if (info->ctm_inverse)
655 	cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
656 
657     _cairo_output_stream_printf (info->output, "M %f %f ", x, y);
658 
659     return CAIRO_STATUS_SUCCESS;
660 }
661 
662 static cairo_status_t
_cairo_svg_path_line_to(void * closure,const cairo_point_t * point)663 _cairo_svg_path_line_to (void *closure,
664 			 const cairo_point_t *point)
665 {
666     svg_path_info_t *info = closure;
667     double x = _cairo_fixed_to_double (point->x);
668     double y = _cairo_fixed_to_double (point->y);
669 
670     if (info->ctm_inverse)
671 	cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
672 
673     _cairo_output_stream_printf (info->output, "L %f %f ", x, y);
674 
675     return CAIRO_STATUS_SUCCESS;
676 }
677 
678 static cairo_status_t
_cairo_svg_path_curve_to(void * closure,const cairo_point_t * b,const cairo_point_t * c,const cairo_point_t * d)679 _cairo_svg_path_curve_to (void          *closure,
680 			  const cairo_point_t *b,
681 			  const cairo_point_t *c,
682 			  const cairo_point_t *d)
683 {
684     svg_path_info_t *info = closure;
685     double bx = _cairo_fixed_to_double (b->x);
686     double by = _cairo_fixed_to_double (b->y);
687     double cx = _cairo_fixed_to_double (c->x);
688     double cy = _cairo_fixed_to_double (c->y);
689     double dx = _cairo_fixed_to_double (d->x);
690     double dy = _cairo_fixed_to_double (d->y);
691 
692     if (info->ctm_inverse) {
693 	cairo_matrix_transform_point (info->ctm_inverse, &bx, &by);
694 	cairo_matrix_transform_point (info->ctm_inverse, &cx, &cy);
695 	cairo_matrix_transform_point (info->ctm_inverse, &dx, &dy);
696     }
697 
698     _cairo_output_stream_printf (info->output,
699 				 "C %f %f %f %f %f %f ",
700 				 bx, by, cx, cy, dx, dy);
701 
702     return CAIRO_STATUS_SUCCESS;
703 }
704 
705 static cairo_status_t
_cairo_svg_path_close_path(void * closure)706 _cairo_svg_path_close_path (void *closure)
707 {
708     svg_path_info_t *info = closure;
709 
710     _cairo_output_stream_printf (info->output, "Z ");
711 
712     return CAIRO_STATUS_SUCCESS;
713 }
714 
715 static void
_cairo_svg_surface_emit_path(cairo_output_stream_t * output,cairo_path_fixed_t * path,const cairo_matrix_t * ctm_inverse)716 _cairo_svg_surface_emit_path (cairo_output_stream_t	*output,
717 			      cairo_path_fixed_t	*path,
718 			      const cairo_matrix_t	*ctm_inverse)
719 {
720     cairo_status_t status;
721     svg_path_info_t info;
722 
723     _cairo_output_stream_printf (output, "d=\"");
724 
725     info.output = output;
726     info.ctm_inverse = ctm_inverse;
727     status = _cairo_path_fixed_interpret (path,
728 					  CAIRO_DIRECTION_FORWARD,
729 					  _cairo_svg_path_move_to,
730 					  _cairo_svg_path_line_to,
731 					  _cairo_svg_path_curve_to,
732 					  _cairo_svg_path_close_path,
733 					  &info);
734     assert (status == CAIRO_STATUS_SUCCESS);
735 
736     _cairo_output_stream_printf (output, "\"");
737 }
738 
739 static cairo_int_status_t
_cairo_svg_document_emit_outline_glyph_data(cairo_svg_document_t * document,cairo_scaled_font_t * scaled_font,unsigned long glyph_index)740 _cairo_svg_document_emit_outline_glyph_data (cairo_svg_document_t	*document,
741 					     cairo_scaled_font_t	*scaled_font,
742 					     unsigned long		 glyph_index)
743 {
744     cairo_scaled_glyph_t *scaled_glyph;
745     cairo_int_status_t status;
746 
747     status = _cairo_scaled_glyph_lookup (scaled_font,
748 					 glyph_index,
749 					 CAIRO_SCALED_GLYPH_INFO_METRICS|
750 					 CAIRO_SCALED_GLYPH_INFO_PATH,
751 					 &scaled_glyph);
752     if (unlikely (status))
753 	return status;
754 
755     _cairo_output_stream_printf (document->xml_node_glyphs,
756 				 "<path style=\"stroke:none;\" ");
757 
758     _cairo_svg_surface_emit_path (document->xml_node_glyphs,
759 				  scaled_glyph->path, NULL);
760 
761     _cairo_output_stream_printf (document->xml_node_glyphs,
762 				 "/>\n");
763 
764     return status;
765 }
766 
767 static cairo_int_status_t
_cairo_svg_document_emit_bitmap_glyph_data(cairo_svg_document_t * document,cairo_scaled_font_t * scaled_font,unsigned long glyph_index)768 _cairo_svg_document_emit_bitmap_glyph_data (cairo_svg_document_t	*document,
769 					    cairo_scaled_font_t		*scaled_font,
770 					    unsigned long		 glyph_index)
771 {
772     cairo_scaled_glyph_t *scaled_glyph;
773     cairo_image_surface_t *image;
774     cairo_status_t status;
775     uint8_t *row, *byte;
776     int rows, cols;
777     int x, y, bit;
778 
779     status = _cairo_scaled_glyph_lookup (scaled_font,
780 					 glyph_index,
781 					 CAIRO_SCALED_GLYPH_INFO_METRICS |
782 					 CAIRO_SCALED_GLYPH_INFO_SURFACE,
783 					 &scaled_glyph);
784     if (unlikely (status))
785 	return status;
786 
787     image = _cairo_image_surface_coerce_to_format (scaled_glyph->surface,
788 					           CAIRO_FORMAT_A1);
789     status = image->base.status;
790     if (unlikely (status))
791 	return status;
792 
793     _cairo_output_stream_printf (document->xml_node_glyphs, "<g");
794     _cairo_svg_surface_emit_transform (document->xml_node_glyphs, " transform",
795 				       &image->base.device_transform_inverse, NULL);
796     _cairo_output_stream_printf (document->xml_node_glyphs, ">/n");
797 
798     for (y = 0, row = image->data, rows = image->height; rows; row += image->stride, rows--, y++) {
799 	for (x = 0, byte = row, cols = (image->width + 7) / 8; cols; byte++, cols--) {
800 	    uint8_t output_byte = CAIRO_BITSWAP8_IF_LITTLE_ENDIAN (*byte);
801 	    for (bit = 7; bit >= 0 && x < image->width; bit--, x++) {
802 		if (output_byte & (1 << bit)) {
803 		    _cairo_output_stream_printf (document->xml_node_glyphs,
804 						 "<rect x=\"%d\" y=\"%d\" width=\"1\" height=\"1\"/>\n",
805 						 x, y);
806 		}
807 	    }
808 	}
809     }
810     _cairo_output_stream_printf (document->xml_node_glyphs, "</g>\n");
811 
812     cairo_surface_destroy (&image->base);
813 
814     return CAIRO_STATUS_SUCCESS;
815 }
816 
817 static cairo_status_t
_cairo_svg_document_emit_glyph(cairo_svg_document_t * document,cairo_scaled_font_t * scaled_font,unsigned long scaled_font_glyph_index,unsigned int font_id,unsigned int subset_glyph_index)818 _cairo_svg_document_emit_glyph (cairo_svg_document_t	*document,
819 				cairo_scaled_font_t	*scaled_font,
820 				unsigned long		 scaled_font_glyph_index,
821 				unsigned int		 font_id,
822 				unsigned int		 subset_glyph_index)
823 {
824     cairo_status_t	     status;
825 
826     _cairo_output_stream_printf (document->xml_node_glyphs,
827 				 "<symbol overflow=\"visible\" id=\"glyph%d-%d\">\n",
828 				 font_id,
829 				 subset_glyph_index);
830 
831     status = _cairo_svg_document_emit_outline_glyph_data (document,
832 							  scaled_font,
833 							  scaled_font_glyph_index);
834     if (status == CAIRO_INT_STATUS_UNSUPPORTED)
835 	status = _cairo_svg_document_emit_bitmap_glyph_data (document,
836 							     scaled_font,
837 							     scaled_font_glyph_index);
838     if (unlikely (status))
839 	return status;
840 
841     _cairo_output_stream_printf (document->xml_node_glyphs, "</symbol>\n");
842 
843     return CAIRO_STATUS_SUCCESS;
844 }
845 
846 static cairo_status_t
_cairo_svg_document_emit_font_subset(cairo_scaled_font_subset_t * font_subset,void * closure)847 _cairo_svg_document_emit_font_subset (cairo_scaled_font_subset_t	*font_subset,
848 				      void				*closure)
849 {
850     cairo_svg_document_t *document = closure;
851     unsigned int i;
852     cairo_status_t status = CAIRO_STATUS_SUCCESS;
853 
854     _cairo_scaled_font_freeze_cache (font_subset->scaled_font);
855     for (i = 0; i < font_subset->num_glyphs; i++) {
856 	status = _cairo_svg_document_emit_glyph (document,
857 					         font_subset->scaled_font,
858 					         font_subset->glyphs[i],
859 					         font_subset->font_id, i);
860 	if (unlikely (status))
861 	    break;
862     }
863     _cairo_scaled_font_thaw_cache (font_subset->scaled_font);
864 
865     return status;
866 }
867 
868 static cairo_status_t
_cairo_svg_document_emit_font_subsets(cairo_svg_document_t * document)869 _cairo_svg_document_emit_font_subsets (cairo_svg_document_t *document)
870 {
871     cairo_status_t status;
872 
873     status = _cairo_scaled_font_subsets_foreach_scaled (document->font_subsets,
874                                                         _cairo_svg_document_emit_font_subset,
875                                                         document);
876     if (unlikely (status))
877 	goto FAIL;
878 
879     status = _cairo_scaled_font_subsets_foreach_user (document->font_subsets,
880 						      _cairo_svg_document_emit_font_subset,
881 						      document);
882 
883   FAIL:
884     _cairo_scaled_font_subsets_destroy (document->font_subsets);
885     document->font_subsets = NULL;
886 
887     return status;
888 }
889 
890 static char const *
891 _cairo_svg_surface_operators[] = {
892     "clear",
893 
894     "src", "src-over", "src-in",
895     "src-out", "src-atop",
896 
897     "dst", "dst-over", "dst-in",
898     "dst-out", "dst-atop",
899 
900     "xor", "plus",
901     "color-dodge", /* FIXME: saturate ? */
902 
903     "multiply",	"screen", "overlay",
904     "darken", "lighten",
905     "color-dodge", "color-burn",
906     "hard-light", "soft-light",
907     "difference", "exclusion"
908 };
909 
910 static cairo_bool_t
_cairo_svg_surface_analyze_operator(cairo_svg_surface_t * surface,cairo_operator_t op)911 _cairo_svg_surface_analyze_operator (cairo_svg_surface_t   *surface,
912 				      cairo_operator_t	     op)
913 {
914     /* guard against newly added operators */
915     if (op >= ARRAY_LENGTH (_cairo_svg_surface_operators))
916 	return CAIRO_INT_STATUS_UNSUPPORTED;
917 
918     /* allow operators being NULL if they are unsupported */
919     if (_cairo_svg_surface_operators[op] == NULL)
920 	return CAIRO_INT_STATUS_UNSUPPORTED;
921 
922     return CAIRO_STATUS_SUCCESS;
923 }
924 
925 static cairo_int_status_t
_cairo_svg_surface_analyze_operation(cairo_svg_surface_t * surface,cairo_operator_t op,const cairo_pattern_t * pattern)926 _cairo_svg_surface_analyze_operation (cairo_svg_surface_t   *surface,
927 				      cairo_operator_t	     op,
928 				      const cairo_pattern_t *pattern)
929 {
930     cairo_svg_document_t *document = surface->document;
931 
932     if (surface->force_fallbacks &&
933 	surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
934     {
935 	return CAIRO_INT_STATUS_UNSUPPORTED;
936     }
937 
938     /* SVG doesn't support extend reflect for image pattern */
939     if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE &&
940 	pattern->extend == CAIRO_EXTEND_REFLECT)
941 	return CAIRO_INT_STATUS_UNSUPPORTED;
942 
943     if (document->svg_version >= CAIRO_SVG_VERSION_1_2)
944 	return _cairo_svg_surface_analyze_operator (surface, op);
945 
946     if (op == CAIRO_OPERATOR_OVER)
947 	return CAIRO_STATUS_SUCCESS;
948 
949     /* The SOURCE operator is only supported if there is nothing
950      * painted underneath. */
951     if (op == CAIRO_OPERATOR_SOURCE)
952 	return CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY;
953 
954     return CAIRO_INT_STATUS_UNSUPPORTED;
955 }
956 
957 static cairo_int_status_t
_cairo_svg_surface_operation_supported(cairo_svg_surface_t * surface,cairo_operator_t op,const cairo_pattern_t * pattern)958 _cairo_svg_surface_operation_supported (cairo_svg_surface_t	*surface,
959 					cairo_operator_t	 op,
960 					const cairo_pattern_t	*pattern)
961 {
962     return _cairo_svg_surface_analyze_operation (surface, op, pattern) != CAIRO_INT_STATUS_UNSUPPORTED;
963 }
964 
965 static cairo_status_t
_cairo_svg_surface_finish(void * abstract_surface)966 _cairo_svg_surface_finish (void *abstract_surface)
967 {
968     cairo_status_t status, status2;
969     cairo_svg_surface_t *surface = abstract_surface;
970     cairo_svg_document_t *document = surface->document;
971     cairo_svg_page_t *page;
972     unsigned int i;
973 
974     if (_cairo_paginated_surface_get_target (document->owner) == &surface->base)
975 	status = _cairo_svg_document_finish (document);
976     else
977 	status = CAIRO_STATUS_SUCCESS;
978 
979     if (surface->xml_node != NULL) {
980 	status2 = _cairo_output_stream_destroy (surface->xml_node);
981 	if (status == CAIRO_STATUS_SUCCESS)
982 	    status = status2;
983     }
984 
985     for (i = 0; i < surface->page_set.num_elements; i++) {
986 	page = _cairo_array_index (&surface->page_set, i);
987 	status2 = _cairo_output_stream_destroy (page->xml_node);
988 	if (status == CAIRO_STATUS_SUCCESS)
989 	    status = status2;
990     }
991     _cairo_array_fini (&surface->page_set);
992 
993     _cairo_surface_clipper_reset (&surface->clipper);
994 
995     status2 = _cairo_svg_document_destroy (document);
996     if (status == CAIRO_STATUS_SUCCESS)
997 	status = status2;
998 
999     return status;
1000 }
1001 
1002 
1003 static void
_cairo_svg_surface_emit_alpha_filter(cairo_svg_document_t * document)1004 _cairo_svg_surface_emit_alpha_filter (cairo_svg_document_t *document)
1005 {
1006     if (document->alpha_filter)
1007 	return;
1008 
1009     _cairo_output_stream_printf (document->xml_node_defs,
1010 				 "<filter id=\"alpha\" "
1011 				 "filterUnits=\"objectBoundingBox\" "
1012 				 "x=\"0%%\" y=\"0%%\" "
1013 				 "width=\"100%%\" height=\"100%%\">\n"
1014 				 "  <feColorMatrix type=\"matrix\" "
1015 				 "in=\"SourceGraphic\" "
1016 				 "values=\"0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0\"/>\n"
1017 				 "</filter>\n");
1018 
1019     document->alpha_filter = TRUE;
1020 }
1021 
1022 typedef struct {
1023     cairo_output_stream_t *output;
1024     unsigned int in_mem;
1025     unsigned int trailing;
1026     unsigned char src[3];
1027 } base64_write_closure_t;
1028 
1029 static char const base64_table[64] =
1030 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1031 
1032 static cairo_status_t
base64_write_func(void * closure,const unsigned char * data,unsigned int length)1033 base64_write_func (void *closure,
1034 		   const unsigned char *data,
1035 		   unsigned int length)
1036 {
1037     base64_write_closure_t *info = (base64_write_closure_t *) closure;
1038     unsigned int i;
1039     unsigned char *src;
1040 
1041     src = info->src;
1042 
1043     if (info->in_mem + length < 3) {
1044 	for (i = 0; i < length; i++) {
1045 	    src[i + info->in_mem] = *data++;
1046 	}
1047 	info->in_mem += length;
1048 	return CAIRO_STATUS_SUCCESS;
1049     }
1050 
1051     do {
1052 	unsigned char dst[4];
1053 
1054 	for (i = info->in_mem; i < 3; i++) {
1055 	    src[i] = *data++;
1056 	    length--;
1057 	}
1058 	info->in_mem = 0;
1059 
1060 	dst[0] = base64_table[src[0] >> 2];
1061 	dst[1] = base64_table[(src[0] & 0x03) << 4 | src[1] >> 4];
1062 	dst[2] = base64_table[(src[1] & 0x0f) << 2 | src[2] >> 6];
1063 	dst[3] = base64_table[src[2] & 0xfc >> 2];
1064 	/* Special case for the last missing bits */
1065 	switch (info->trailing) {
1066 	    case 2:
1067 		dst[2] = '=';
1068 	    case 1:
1069 		dst[3] = '=';
1070 	    default:
1071 		break;
1072 	}
1073 	_cairo_output_stream_write (info->output, dst, 4);
1074     } while (length >= 3);
1075 
1076     for (i = 0; i < length; i++) {
1077 	src[i] = *data++;
1078     }
1079     info->in_mem = length;
1080 
1081     return _cairo_output_stream_get_status (info->output);
1082 }
1083 
1084 static cairo_int_status_t
_cairo_surface_base64_encode_jpeg(cairo_surface_t * surface,cairo_output_stream_t * output)1085 _cairo_surface_base64_encode_jpeg (cairo_surface_t       *surface,
1086 				   cairo_output_stream_t *output)
1087 {
1088     const unsigned char *mime_data;
1089     unsigned long mime_data_length;
1090     cairo_image_info_t image_info;
1091     base64_write_closure_t info;
1092     cairo_status_t status;
1093 
1094     cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_JPEG,
1095 				 &mime_data, &mime_data_length);
1096     if (mime_data == NULL)
1097 	return CAIRO_INT_STATUS_UNSUPPORTED;
1098 
1099     status = _cairo_image_info_get_jpeg_info (&image_info, mime_data, mime_data_length);
1100     if (unlikely (status))
1101 	return status;
1102 
1103     _cairo_output_stream_printf (output, "data:image/jpeg;base64,");
1104 
1105     info.output = output;
1106     info.in_mem = 0;
1107     info.trailing = 0;
1108 
1109     status = base64_write_func (&info, mime_data, mime_data_length);
1110     if (unlikely (status))
1111 	return status;
1112 
1113     if (info.in_mem > 0) {
1114 	memset (info.src + info.in_mem, 0, 3 - info.in_mem);
1115 	info.trailing = 3 - info.in_mem;
1116 	info.in_mem = 3;
1117 	status = base64_write_func (&info, NULL, 0);
1118     }
1119 
1120     return status;
1121 }
1122 
1123 static cairo_int_status_t
_cairo_surface_base64_encode_png(cairo_surface_t * surface,cairo_output_stream_t * output)1124 _cairo_surface_base64_encode_png (cairo_surface_t       *surface,
1125 				  cairo_output_stream_t *output)
1126 {
1127     const unsigned char *mime_data;
1128     unsigned long mime_data_length;
1129     base64_write_closure_t info;
1130     cairo_status_t status;
1131 
1132     cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_PNG,
1133 				 &mime_data, &mime_data_length);
1134     if (unlikely (surface->status))
1135 	return surface->status;
1136     if (mime_data == NULL)
1137 	return CAIRO_INT_STATUS_UNSUPPORTED;
1138 
1139     _cairo_output_stream_printf (output, "data:image/png;base64,");
1140 
1141     info.output = output;
1142     info.in_mem = 0;
1143     info.trailing = 0;
1144 
1145     status = base64_write_func (&info, mime_data, mime_data_length);
1146     if (unlikely (status))
1147 	return status;
1148 
1149     if (info.in_mem > 0) {
1150 	memset (info.src + info.in_mem, 0, 3 - info.in_mem);
1151 	info.trailing = 3 - info.in_mem;
1152 	info.in_mem = 3;
1153 	status = base64_write_func (&info, NULL, 0);
1154     }
1155 
1156     return status;
1157 }
1158 
1159 static cairo_int_status_t
_cairo_surface_base64_encode(cairo_surface_t * surface,cairo_output_stream_t * output)1160 _cairo_surface_base64_encode (cairo_surface_t       *surface,
1161 			      cairo_output_stream_t *output)
1162 {
1163     cairo_status_t status;
1164     base64_write_closure_t info;
1165 
1166     status = _cairo_surface_base64_encode_jpeg (surface, output);
1167     if (status != CAIRO_INT_STATUS_UNSUPPORTED)
1168 	return status;
1169 
1170     status = _cairo_surface_base64_encode_png (surface, output);
1171     if (status != CAIRO_INT_STATUS_UNSUPPORTED)
1172 	return status;
1173 
1174     info.output = output;
1175     info.in_mem = 0;
1176     info.trailing = 0;
1177 
1178     _cairo_output_stream_printf (info.output, "data:image/png;base64,");
1179 
1180     status = cairo_surface_write_to_png_stream (surface, base64_write_func,
1181 						(void *) &info);
1182 
1183     if (unlikely (status))
1184 	return status;
1185 
1186     if (info.in_mem > 0) {
1187 	memset (info.src + info.in_mem, 0, 3 - info.in_mem);
1188 	info.trailing = 3 - info.in_mem;
1189 	info.in_mem = 3;
1190 	status = base64_write_func (&info, NULL, 0);
1191     }
1192 
1193     return status;
1194 }
1195 
1196 static void
_cairo_svg_surface_emit_operator(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op)1197 _cairo_svg_surface_emit_operator (cairo_output_stream_t *output,
1198 				  cairo_svg_surface_t   *surface,
1199 				  cairo_operator_t	 op)
1200 {
1201     if (surface->document->svg_version >= CAIRO_SVG_VERSION_1_2 &&
1202 	op != CAIRO_OPERATOR_OVER) {
1203 	_cairo_output_stream_printf (output, " comp-op=\"%s\"", _cairo_svg_surface_operators[op]);
1204 	if (!_cairo_operator_bounded_by_source (op))
1205 	   _cairo_output_stream_printf (output, " clip-to-self=\"true\"");
1206     }
1207 }
1208 
1209 static void
_cairo_svg_surface_emit_operator_for_style(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op)1210 _cairo_svg_surface_emit_operator_for_style (cairo_output_stream_t *output,
1211 					    cairo_svg_surface_t   *surface,
1212 					    cairo_operator_t	 op)
1213 {
1214     if (surface->document->svg_version >= CAIRO_SVG_VERSION_1_2 &&
1215 	op != CAIRO_OPERATOR_OVER) {
1216 	_cairo_output_stream_printf (output, "comp-op:%s;", _cairo_svg_surface_operators[op]);
1217 	if (!_cairo_operator_bounded_by_source (op))
1218 	   _cairo_output_stream_printf (output, "clip-to-self:true;");
1219     }
1220 }
1221 
1222 /**
1223  * _cairo_svg_surface_emit_attr_value:
1224  *
1225  * Write the value to output the stream as a sequence of characters,
1226  * while escaping those which have special meaning in the XML
1227  * attribute's value context: &amp; and &quot;.
1228  **/
1229 static void
_cairo_svg_surface_emit_attr_value(cairo_output_stream_t * stream,const unsigned char * value,unsigned int length)1230 _cairo_svg_surface_emit_attr_value (cairo_output_stream_t *stream,
1231 				    const unsigned char *value,
1232 				    unsigned int length)
1233 {
1234     const unsigned char *p;
1235     const unsigned char *q;
1236     unsigned int i;
1237 
1238     /* we'll accumulate non-special chars in [q, p) range */
1239     p = value;
1240     q = p;
1241     for (i = 0; i < length; i++, p++) {
1242 	if (*p == '&' || *p == '"') {
1243 	    /* flush what's left before special char */
1244 	    if (p != q) {
1245 		_cairo_output_stream_write (stream, q, p - q);
1246 		q = p + 1;
1247 	    }
1248 
1249 	    if (*p == '&')
1250 		_cairo_output_stream_printf (stream, "&amp;");
1251 	    else // p == '"'
1252 		_cairo_output_stream_printf (stream, "&quot;");
1253 	}
1254     }
1255 
1256     /* flush the trailing chars if any */
1257     if (p != q)
1258 	_cairo_output_stream_write (stream, q, p - q);
1259 }
1260 
1261 static cairo_status_t
_cairo_svg_surface_emit_surface(cairo_svg_document_t * document,cairo_surface_t * surface)1262 _cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
1263 				 cairo_surface_t *surface)
1264 {
1265     cairo_rectangle_int_t extents;
1266     cairo_bool_t is_bounded;
1267     cairo_status_t status;
1268     const unsigned char *uri;
1269     unsigned long uri_len;
1270 
1271     if (_cairo_user_data_array_get_data (&surface->user_data,
1272 					 (cairo_user_data_key_t *) document))
1273     {
1274 	return CAIRO_STATUS_SUCCESS;
1275     }
1276 
1277     is_bounded = _cairo_surface_get_extents (surface, &extents);
1278     assert (is_bounded);
1279 
1280     _cairo_output_stream_printf (document->xml_node_defs,
1281 				 "<image id=\"image%d\" width=\"%d\" height=\"%d\"",
1282 				 surface->unique_id,
1283 				 extents.width, extents.height);
1284 
1285     _cairo_output_stream_printf (document->xml_node_defs, " xlink:href=\"");
1286 
1287     cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_URI,
1288 				 &uri, &uri_len);
1289     if (uri != NULL) {
1290 	_cairo_svg_surface_emit_attr_value (document->xml_node_defs,
1291 					    uri, uri_len);
1292     } else {
1293 	status = _cairo_surface_base64_encode (surface,
1294 					       document->xml_node_defs);
1295 	if (unlikely (status))
1296 	    return status;
1297     }
1298 
1299     _cairo_output_stream_printf (document->xml_node_defs, "\"/>\n");
1300 
1301     /* and tag it */
1302     return _cairo_user_data_array_set_data (&surface->user_data,
1303 					    (cairo_user_data_key_t *) document,
1304 					    document, NULL);
1305 }
1306 
1307 static cairo_status_t
_cairo_svg_surface_emit_composite_surface_pattern(cairo_output_stream_t * output,cairo_svg_surface_t * svg_surface,cairo_operator_t op,cairo_surface_pattern_t * pattern,int pattern_id,const cairo_matrix_t * parent_matrix,const char * extra_attributes)1308 _cairo_svg_surface_emit_composite_surface_pattern (cairo_output_stream_t   *output,
1309 						   cairo_svg_surface_t	 *svg_surface,
1310 						   cairo_operator_t	  op,
1311 						   cairo_surface_pattern_t *pattern,
1312 						   int			  pattern_id,
1313 						   const cairo_matrix_t	 *parent_matrix,
1314 						   const char		 *extra_attributes)
1315 {
1316     cairo_status_t status;
1317     cairo_matrix_t p2u;
1318 
1319     p2u = pattern->base.matrix;
1320     status = cairo_matrix_invert (&p2u);
1321     /* cairo_pattern_set_matrix ensures the matrix is invertible */
1322     assert (status == CAIRO_STATUS_SUCCESS);
1323 
1324     status = _cairo_svg_surface_emit_surface (svg_surface->document,
1325 					      pattern->surface);
1326     if (unlikely (status))
1327 	return status;
1328 
1329     if (pattern_id != invalid_pattern_id) {
1330 	cairo_rectangle_int_t extents;
1331 	cairo_bool_t is_bounded;
1332 
1333 	is_bounded = _cairo_surface_get_extents (pattern->surface, &extents);
1334 	assert (is_bounded);
1335 
1336 	_cairo_output_stream_printf (output,
1337 				     "<pattern id=\"pattern%d\" "
1338 				     "patternUnits=\"userSpaceOnUse\" "
1339 				     "width=\"%d\" height=\"%d\" ",
1340 				     pattern_id,
1341 				     extents.width, extents.height);
1342 	_cairo_svg_surface_emit_transform (output,
1343 					   " patternTransform",
1344 					   &p2u, parent_matrix);
1345 	_cairo_output_stream_printf (output, ">\n  ");
1346     }
1347 
1348     _cairo_output_stream_printf (output,
1349 				 "<use xlink:href=\"#image%d\"",
1350 				 pattern->surface->unique_id);
1351     if (extra_attributes)
1352 	_cairo_output_stream_printf (output, " %s", extra_attributes);
1353 
1354     if (pattern_id == invalid_pattern_id) {
1355 	_cairo_svg_surface_emit_operator (output, svg_surface, op);
1356 	_cairo_svg_surface_emit_transform (output,
1357 					   " transform",
1358 					   &p2u, parent_matrix);
1359     }
1360     _cairo_output_stream_printf (output, "/>\n");
1361 
1362 
1363     if (pattern_id != invalid_pattern_id)
1364 	_cairo_output_stream_printf (output, "</pattern>\n");
1365 
1366     return CAIRO_STATUS_SUCCESS;
1367 }
1368 
1369 static cairo_status_t
_cairo_svg_surface_emit_recording_surface(cairo_svg_document_t * document,cairo_recording_surface_t * source)1370 _cairo_svg_surface_emit_recording_surface (cairo_svg_document_t      *document,
1371 					   cairo_recording_surface_t *source)
1372 {
1373     cairo_status_t status;
1374     cairo_surface_t *paginated_surface;
1375     cairo_svg_surface_t *svg_surface;
1376     cairo_array_t *page_set;
1377 
1378     cairo_output_stream_t *contents;
1379 
1380     if (_cairo_user_data_array_get_data (&source->base.user_data,
1381 					 (cairo_user_data_key_t *) document))
1382     {
1383 	return CAIRO_STATUS_SUCCESS;
1384     }
1385 
1386     paginated_surface = _cairo_svg_surface_create_for_document (document,
1387 								source->content,
1388 								source->extents_pixels.width,
1389 								source->extents_pixels.height);
1390     if (unlikely (paginated_surface->status))
1391 	return paginated_surface->status;
1392 
1393     svg_surface = (cairo_svg_surface_t *)
1394     _cairo_paginated_surface_get_target (paginated_surface);
1395     cairo_surface_set_fallback_resolution (paginated_surface,
1396 					   document->owner->x_fallback_resolution,
1397 					   document->owner->y_fallback_resolution);
1398     cairo_surface_set_device_offset (&svg_surface->base,
1399 				     -source->extents_pixels.x,
1400 				     -source->extents_pixels.y);
1401 
1402     status = _cairo_recording_surface_replay (&source->base, paginated_surface);
1403     if (unlikely (status)) {
1404 	cairo_surface_destroy (paginated_surface);
1405 	return status;
1406     }
1407 
1408     cairo_surface_show_page (paginated_surface);
1409     status = cairo_surface_status (paginated_surface);
1410     if (unlikely (status)) {
1411 	cairo_surface_destroy (paginated_surface);
1412 	return status;
1413     }
1414 
1415     if (! svg_surface->is_base_clip_emitted) {
1416 	svg_surface->is_base_clip_emitted = TRUE;
1417 	_cairo_output_stream_printf (document->xml_node_defs,
1418 				     "<clipPath id=\"clip%d\">\n"
1419 				     "  <rect width=\"%f\" height=\"%f\"/>\n"
1420 				     "</clipPath>\n",
1421 				     svg_surface->base_clip,
1422 				     svg_surface->width,
1423 				     svg_surface->height);
1424     }
1425 
1426     if (source->content == CAIRO_CONTENT_ALPHA) {
1427 	_cairo_svg_surface_emit_alpha_filter (document);
1428 	_cairo_output_stream_printf (document->xml_node_defs,
1429 				     "<g id=\"surface%d\" "
1430 				     "clip-path=\"url(#clip%d)\" "
1431 				     "filter=\"url(#alpha)\">\n",
1432 				     source->base.unique_id,
1433 				     svg_surface->base_clip);
1434     } else {
1435 	_cairo_output_stream_printf (document->xml_node_defs,
1436 				     "<g id=\"surface%d\" "
1437 				     "clip-path=\"url(#clip%d)\">\n",
1438 				     source->base.unique_id,
1439 				     svg_surface->base_clip);
1440     }
1441 
1442     contents = svg_surface->xml_node;
1443     page_set = &svg_surface->page_set;
1444 
1445     if (_cairo_memory_stream_length (contents) > 0) {
1446 	if (unlikely (_cairo_svg_surface_store_page (svg_surface) == NULL)) {
1447 	    cairo_surface_destroy (paginated_surface);
1448 	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1449 	}
1450     }
1451 
1452     if (page_set->num_elements > 0) {
1453 	cairo_svg_page_t *page;
1454 
1455 	page = _cairo_array_index (page_set, page_set->num_elements - 1);
1456 	_cairo_memory_stream_copy (page->xml_node, document->xml_node_defs);
1457     }
1458 
1459     _cairo_output_stream_printf (document->xml_node_defs, "</g>\n");
1460 
1461     status = cairo_surface_status (paginated_surface);
1462     cairo_surface_destroy (paginated_surface);
1463 
1464     if (unlikely (status))
1465 	return status;
1466 
1467     /* and tag it */
1468     return _cairo_user_data_array_set_data (&source->base.user_data,
1469 					    (cairo_user_data_key_t *) document,
1470 					    document, NULL);
1471 }
1472 
1473 static cairo_status_t
_cairo_svg_surface_emit_composite_recording_pattern(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op,cairo_surface_pattern_t * pattern,int pattern_id,const cairo_matrix_t * parent_matrix,const char * extra_attributes)1474 _cairo_svg_surface_emit_composite_recording_pattern (cairo_output_stream_t	*output,
1475 						     cairo_svg_surface_t	*surface,
1476 						     cairo_operator_t	         op,
1477 						     cairo_surface_pattern_t	*pattern,
1478 						     int			 pattern_id,
1479 						     const cairo_matrix_t	*parent_matrix,
1480 						     const char			*extra_attributes)
1481 {
1482     cairo_svg_document_t *document = surface->document;
1483     cairo_recording_surface_t *recording_surface;
1484     cairo_matrix_t p2u;
1485     cairo_status_t status;
1486 
1487     p2u = pattern->base.matrix;
1488     status = cairo_matrix_invert (&p2u);
1489     /* cairo_pattern_set_matrix ensures the matrix is invertible */
1490     assert (status == CAIRO_STATUS_SUCCESS);
1491 
1492     recording_surface = (cairo_recording_surface_t *) pattern->surface;
1493     status = _cairo_svg_surface_emit_recording_surface (document, recording_surface);
1494     if (unlikely (status))
1495 	return status;
1496 
1497     if (pattern_id != invalid_pattern_id) {
1498 	_cairo_output_stream_printf (output,
1499 				     "<pattern id=\"pattern%d\" "
1500 				     "patternUnits=\"userSpaceOnUse\" "
1501 				     "width=\"%d\" height=\"%d\"",
1502 				     pattern_id,
1503 				     recording_surface->extents.width,
1504 				     recording_surface->extents.height);
1505 	_cairo_svg_surface_emit_transform (output, " patternTransform", &p2u, parent_matrix);
1506 	_cairo_output_stream_printf (output, ">\n");
1507     }
1508 
1509     _cairo_output_stream_printf (output,
1510 				 "<use xlink:href=\"#surface%d\"",
1511 				 recording_surface->base.unique_id);
1512 
1513     if (pattern_id == invalid_pattern_id) {
1514 	_cairo_svg_surface_emit_operator (output, surface, op);
1515 	_cairo_svg_surface_emit_transform (output, " transform", &p2u, parent_matrix);
1516     }
1517 
1518     if (extra_attributes)
1519 	_cairo_output_stream_printf (output, " %s", extra_attributes);
1520 
1521     _cairo_output_stream_printf (output, "/>\n");
1522 
1523     if (pattern_id != invalid_pattern_id)
1524 	_cairo_output_stream_printf (output, "</pattern>\n");
1525 
1526     return CAIRO_STATUS_SUCCESS;
1527 }
1528 
1529 static cairo_status_t
_cairo_svg_surface_emit_composite_pattern(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op,cairo_surface_pattern_t * pattern,int pattern_id,const cairo_matrix_t * parent_matrix,const char * extra_attributes)1530 _cairo_svg_surface_emit_composite_pattern (cairo_output_stream_t   *output,
1531 					   cairo_svg_surface_t	   *surface,
1532 					   cairo_operator_t	    op,
1533 					   cairo_surface_pattern_t *pattern,
1534 					   int			    pattern_id,
1535 					   const cairo_matrix_t	   *parent_matrix,
1536 					   const char		   *extra_attributes)
1537 {
1538 
1539     if (_cairo_surface_is_recording (pattern->surface)) {
1540 	return _cairo_svg_surface_emit_composite_recording_pattern (output, surface,
1541 								    op, pattern,
1542 								    pattern_id,
1543 								    parent_matrix,
1544 								    extra_attributes);
1545     }
1546 
1547     return _cairo_svg_surface_emit_composite_surface_pattern (output, surface,
1548 							      op, pattern,
1549 							      pattern_id,
1550 							      parent_matrix,
1551 							      extra_attributes);
1552 }
1553 
1554 static cairo_status_t
_cairo_svg_surface_emit_solid_pattern(cairo_svg_surface_t * surface,cairo_solid_pattern_t * pattern,cairo_output_stream_t * style,cairo_bool_t is_stroke)1555 _cairo_svg_surface_emit_solid_pattern (cairo_svg_surface_t    *surface,
1556 				       cairo_solid_pattern_t  *pattern,
1557 				       cairo_output_stream_t  *style,
1558 				       cairo_bool_t	       is_stroke)
1559 {
1560     _cairo_output_stream_printf (style, is_stroke ?
1561 				 "stroke:rgb(%f%%,%f%%,%f%%);stroke-opacity:%f;":
1562 				 "fill:rgb(%f%%,%f%%,%f%%);fill-opacity:%f;",
1563 				 pattern->color.red * 100.0,
1564 				 pattern->color.green * 100.0,
1565 				 pattern->color.blue * 100.0,
1566 				 pattern->color.alpha);
1567 
1568     return CAIRO_STATUS_SUCCESS;
1569 }
1570 
1571 static cairo_status_t
_cairo_svg_surface_emit_surface_pattern(cairo_svg_surface_t * surface,cairo_surface_pattern_t * pattern,cairo_output_stream_t * style,cairo_bool_t is_stroke,const cairo_matrix_t * parent_matrix)1572 _cairo_svg_surface_emit_surface_pattern (cairo_svg_surface_t	 *surface,
1573 					 cairo_surface_pattern_t *pattern,
1574 					 cairo_output_stream_t   *style,
1575 					 cairo_bool_t		  is_stroke,
1576 					 const cairo_matrix_t	 *parent_matrix)
1577 {
1578     cairo_svg_document_t *document = surface->document;
1579     cairo_status_t status;
1580     int pattern_id;
1581 
1582     pattern_id = document->pattern_id++;
1583     status = _cairo_svg_surface_emit_composite_pattern (document->xml_node_defs,
1584 	                                                surface, CAIRO_OPERATOR_SOURCE, pattern,
1585 							pattern_id, parent_matrix, NULL);
1586     if (unlikely (status))
1587 	return status;
1588 
1589     _cairo_output_stream_printf (style,
1590 				 "%s:url(#pattern%d);",
1591 				 is_stroke ? "stroke" : "fill",
1592 				 pattern_id);
1593 
1594     return CAIRO_STATUS_SUCCESS;
1595 }
1596 
1597 static cairo_status_t
_cairo_svg_surface_emit_pattern_stops(cairo_output_stream_t * output,cairo_gradient_pattern_t const * pattern,double start_offset,cairo_bool_t reverse_stops,cairo_bool_t emulate_reflect)1598 _cairo_svg_surface_emit_pattern_stops (cairo_output_stream_t          *output,
1599 				       cairo_gradient_pattern_t const *pattern,
1600 				       double			       start_offset,
1601 				       cairo_bool_t		       reverse_stops,
1602 				       cairo_bool_t		       emulate_reflect)
1603 {
1604     cairo_gradient_stop_t *stops;
1605     double offset;
1606     unsigned int n_stops;
1607     unsigned int i;
1608 
1609     if (pattern->n_stops < 1)
1610 	return CAIRO_STATUS_SUCCESS;
1611 
1612     if (pattern->n_stops == 1) {
1613 	    _cairo_output_stream_printf (output,
1614 					 "<stop offset=\"%f\" style=\""
1615 					 "stop-color:rgb(%f%%,%f%%,%f%%);"
1616 					 "stop-opacity:%f;\"/>\n",
1617 					 pattern->stops[0].offset,
1618 					 pattern->stops[0].color.red   * 100.0,
1619 					 pattern->stops[0].color.green * 100.0,
1620 					 pattern->stops[0].color.blue  * 100.0,
1621 					 pattern->stops[0].color.alpha);
1622 	    return CAIRO_STATUS_SUCCESS;
1623     }
1624 
1625     if (emulate_reflect || reverse_stops) {
1626 	n_stops = emulate_reflect ? pattern->n_stops * 2 - 2: pattern->n_stops;
1627 	stops = _cairo_malloc_ab (n_stops, sizeof (cairo_gradient_stop_t));
1628 	if (unlikely (stops == NULL))
1629 	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1630 
1631 	for (i = 0; i < pattern->n_stops; i++) {
1632 	    if (reverse_stops) {
1633 		stops[i] = pattern->stops[pattern->n_stops - i - 1];
1634 		stops[i].offset = 1.0 - stops[i].offset;
1635 	    } else
1636 		stops[i] = pattern->stops[i];
1637 	    if (emulate_reflect) {
1638 		stops[i].offset /= 2;
1639 		if (i > 0 && i < (pattern->n_stops - 1)) {
1640 		    if (reverse_stops) {
1641 			stops[i + pattern->n_stops - 1] = pattern->stops[i];
1642 			stops[i + pattern->n_stops - 1].offset =
1643 			    0.5 + 0.5 * stops[i + pattern->n_stops - 1].offset;
1644 		    } else {
1645 			stops[i + pattern->n_stops - 1] = pattern->stops[pattern->n_stops - i - 1];
1646 			stops[i + pattern->n_stops - 1].offset =
1647 			    1 - 0.5 * stops[i + pattern->n_stops - 1].offset;
1648 		    }
1649 		}
1650 	    }
1651 	}
1652     } else {
1653 	n_stops = pattern->n_stops;
1654 	stops = pattern->stops;
1655     }
1656 
1657     if (start_offset >= 0.0)
1658 	for (i = 0; i < n_stops; i++) {
1659 	    offset = start_offset + (1 - start_offset ) * stops[i].offset;
1660 	    _cairo_output_stream_printf (output,
1661 					 "<stop offset=\"%f\" style=\""
1662 					 "stop-color:rgb(%f%%,%f%%,%f%%);"
1663 					 "stop-opacity:%f;\"/>\n",
1664 					 offset,
1665 					 stops[i].color.red   * 100.0,
1666 					 stops[i].color.green * 100.0,
1667 					 stops[i].color.blue  * 100.0,
1668 					 stops[i].color.alpha);
1669 	}
1670     else {
1671 	cairo_bool_t found = FALSE;
1672 	unsigned int offset_index;
1673 	cairo_color_stop_t offset_color_start, offset_color_stop;
1674 
1675 	for (i = 0; i < n_stops; i++) {
1676 	    if (stops[i].offset >= -start_offset) {
1677 		if (i > 0) {
1678 		    if (stops[i].offset != stops[i-1].offset) {
1679 			double x0, x1;
1680 			cairo_color_stop_t *color0, *color1;
1681 
1682 			x0 = stops[i-1].offset;
1683 			x1 = stops[i].offset;
1684 			color0 = &stops[i-1].color;
1685 			color1 = &stops[i].color;
1686 			offset_color_start.red = color0->red + (color1->red - color0->red)
1687 			    * (-start_offset - x0) / (x1 - x0);
1688 			offset_color_start.green = color0->green + (color1->green - color0->green)
1689 			    * (-start_offset - x0) / (x1 - x0);
1690 			offset_color_start.blue = color0->blue + (color1->blue - color0->blue)
1691 			    * (-start_offset - x0) / (x1 - x0);
1692 			offset_color_start.alpha = color0->alpha + (color1->alpha - color0->alpha)
1693 			    * (-start_offset - x0) / (x1 - x0);
1694 			offset_color_stop = offset_color_start;
1695 		    } else {
1696 			offset_color_stop = stops[i-1].color;
1697 			offset_color_start = stops[i].color;
1698 		    }
1699 		} else
1700 			offset_color_stop = offset_color_start = stops[i].color;
1701 	    offset_index = i;
1702 	    found = TRUE;
1703 	    break;
1704 	    }
1705 	}
1706 
1707 	if (!found) {
1708 	    offset_index = n_stops - 1;
1709 	    offset_color_stop = offset_color_start = stops[offset_index].color;
1710 	}
1711 
1712 	_cairo_output_stream_printf (output,
1713 				     "<stop offset=\"0\" style=\""
1714 				     "stop-color:rgb(%f%%,%f%%,%f%%);"
1715 				     "stop-opacity:%f;\"/>\n",
1716 				     offset_color_start.red   * 100.0,
1717 				     offset_color_start.green * 100.0,
1718 				     offset_color_start.blue  * 100.0,
1719 				     offset_color_start.alpha);
1720 	for (i = offset_index; i < n_stops; i++) {
1721 	    _cairo_output_stream_printf (output,
1722 					 "<stop offset=\"%f\" style=\""
1723 					 "stop-color:rgb(%f%%,%f%%,%f%%);"
1724 					 "stop-opacity:%f;\"/>\n",
1725 					 stops[i].offset + start_offset,
1726 					 stops[i].color.red   * 100.0,
1727 					 stops[i].color.green * 100.0,
1728 					 stops[i].color.blue  * 100.0,
1729 					 stops[i].color.alpha);
1730 	}
1731 	for (i = 0; i < offset_index; i++) {
1732 	    _cairo_output_stream_printf (output,
1733 					 "<stop offset=\"%f\" style=\""
1734 					 "stop-color:rgb(%f%%,%f%%,%f%%);"
1735 					 "stop-opacity:%f;\"/>\n",
1736 					 1.0 + stops[i].offset + start_offset,
1737 					 stops[i].color.red   * 100.0,
1738 					 stops[i].color.green * 100.0,
1739 					 stops[i].color.blue  * 100.0,
1740 					 stops[i].color.alpha);
1741 	}
1742 
1743 	_cairo_output_stream_printf (output,
1744 				     "<stop offset=\"1\" style=\""
1745 				     "stop-color:rgb(%f%%,%f%%,%f%%);"
1746 				     "stop-opacity:%f;\"/>\n",
1747 				     offset_color_stop.red   * 100.0,
1748 				     offset_color_stop.green * 100.0,
1749 				     offset_color_stop.blue  * 100.0,
1750 				     offset_color_stop.alpha);
1751 
1752     }
1753 
1754     if (reverse_stops || emulate_reflect)
1755 	free (stops);
1756 
1757     return CAIRO_STATUS_SUCCESS;
1758 }
1759 
1760 static void
_cairo_svg_surface_emit_pattern_extend(cairo_output_stream_t * output,cairo_pattern_t * pattern)1761 _cairo_svg_surface_emit_pattern_extend (cairo_output_stream_t *output,
1762 					cairo_pattern_t       *pattern)
1763 {
1764     switch (pattern->extend) {
1765 	case CAIRO_EXTEND_REPEAT:
1766 	    _cairo_output_stream_printf (output, "spreadMethod=\"repeat\" ");
1767 	    break;
1768 	case CAIRO_EXTEND_REFLECT:
1769 	    _cairo_output_stream_printf (output, "spreadMethod=\"reflect\" ");
1770 	    break;
1771 	case CAIRO_EXTEND_NONE:
1772 	case CAIRO_EXTEND_PAD:
1773 	    break;
1774     }
1775 }
1776 
1777 static cairo_status_t
_cairo_svg_surface_emit_linear_pattern(cairo_svg_surface_t * surface,cairo_linear_pattern_t * pattern,cairo_output_stream_t * style,cairo_bool_t is_stroke,const cairo_matrix_t * parent_matrix)1778 _cairo_svg_surface_emit_linear_pattern (cairo_svg_surface_t    *surface,
1779 					cairo_linear_pattern_t *pattern,
1780 					cairo_output_stream_t  *style,
1781 					cairo_bool_t	        is_stroke,
1782 					const cairo_matrix_t   *parent_matrix)
1783 {
1784     cairo_svg_document_t *document = surface->document;
1785     double x0, y0, x1, y1;
1786     cairo_matrix_t p2u;
1787     cairo_status_t status;
1788 
1789     p2u = pattern->base.base.matrix;
1790     status = cairo_matrix_invert (&p2u);
1791     /* cairo_pattern_set_matrix ensures the matrix is invertible */
1792     assert (status == CAIRO_STATUS_SUCCESS);
1793 
1794     x0 = _cairo_fixed_to_double (pattern->p1.x);
1795     y0 = _cairo_fixed_to_double (pattern->p1.y);
1796     x1 = _cairo_fixed_to_double (pattern->p2.x);
1797     y1 = _cairo_fixed_to_double (pattern->p2.y);
1798 
1799     _cairo_output_stream_printf (document->xml_node_defs,
1800 				 "<linearGradient id=\"linear%d\" "
1801 				 "gradientUnits=\"userSpaceOnUse\" "
1802 				 "x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" ",
1803 				 document->linear_pattern_id,
1804 				 x0, y0, x1, y1);
1805 
1806     _cairo_svg_surface_emit_pattern_extend (document->xml_node_defs, &pattern->base.base),
1807     _cairo_svg_surface_emit_transform (document->xml_node_defs, "gradientTransform", &p2u, parent_matrix);
1808     _cairo_output_stream_printf (document->xml_node_defs, ">\n");
1809 
1810     status = _cairo_svg_surface_emit_pattern_stops (document->xml_node_defs,
1811 	                                            &pattern->base, 0.0,
1812 						    FALSE, FALSE);
1813     if (unlikely (status))
1814 	return status;
1815 
1816     _cairo_output_stream_printf (document->xml_node_defs,
1817 				 "</linearGradient>\n");
1818 
1819     _cairo_output_stream_printf (style,
1820 				 "%s:url(#linear%d);",
1821 				 is_stroke ? "stroke" : "fill",
1822 				 document->linear_pattern_id);
1823 
1824     document->linear_pattern_id++;
1825 
1826     return CAIRO_STATUS_SUCCESS;
1827 }
1828 
1829 static cairo_status_t
_cairo_svg_surface_emit_radial_pattern(cairo_svg_surface_t * surface,cairo_radial_pattern_t * pattern,cairo_output_stream_t * style,cairo_bool_t is_stroke,const cairo_matrix_t * parent_matrix)1830 _cairo_svg_surface_emit_radial_pattern (cairo_svg_surface_t    *surface,
1831 					cairo_radial_pattern_t *pattern,
1832 					cairo_output_stream_t  *style,
1833 					cairo_bool_t            is_stroke,
1834 					const cairo_matrix_t   *parent_matrix)
1835 {
1836     cairo_svg_document_t *document = surface->document;
1837     cairo_matrix_t p2u;
1838     cairo_extend_t extend;
1839     double x0, y0, x1, y1, r0, r1;
1840     double fx, fy;
1841     cairo_bool_t reverse_stops;
1842     cairo_status_t status;
1843     cairo_point_t *c0, *c1;
1844     cairo_fixed_t radius0, radius1;
1845 
1846     extend = pattern->base.base.extend;
1847 
1848     if (pattern->r1 < pattern->r2) {
1849 	c0 = &pattern->c1;
1850 	c1 = &pattern->c2;
1851 	radius0 = pattern->r1;
1852 	radius1 = pattern->r2;
1853 	reverse_stops = FALSE;
1854     } else {
1855 	c0 = &pattern->c2;
1856 	c1 = &pattern->c1;
1857 	radius0 = pattern->r2;
1858 	radius1 = pattern->r1;
1859 	reverse_stops = TRUE;
1860     }
1861 
1862     x0 = _cairo_fixed_to_double (c0->x);
1863     y0 = _cairo_fixed_to_double (c0->y);
1864     r0 = _cairo_fixed_to_double (radius0);
1865     x1 = _cairo_fixed_to_double (c1->x);
1866     y1 = _cairo_fixed_to_double (c1->y);
1867     r1 = _cairo_fixed_to_double (radius1);
1868 
1869     p2u = pattern->base.base.matrix;
1870     status = cairo_matrix_invert (&p2u);
1871     /* cairo_pattern_set_matrix ensures the matrix is invertible */
1872     assert (status == CAIRO_STATUS_SUCCESS);
1873 
1874     if (pattern->r1 == pattern->r2) {
1875 	unsigned int n_stops = pattern->base.n_stops;
1876 
1877 	_cairo_output_stream_printf (document->xml_node_defs,
1878 				     "<radialGradient id=\"radial%d\" "
1879 				     "gradientUnits=\"userSpaceOnUse\" "
1880 				     "cx=\"%f\" cy=\"%f\" "
1881 				     "fx=\"%f\" fy=\"%f\" r=\"%f\" ",
1882 				     document->radial_pattern_id,
1883 				     x1, y1,
1884 				     x1, y1, r1);
1885 	_cairo_svg_surface_emit_transform (document->xml_node_defs,
1886 					   "gradientTransform",
1887 					   &p2u, parent_matrix);
1888 	_cairo_output_stream_printf (document->xml_node_defs, ">\n");
1889 
1890 	if (extend == CAIRO_EXTEND_NONE || n_stops < 1)
1891 	    _cairo_output_stream_printf (document->xml_node_defs,
1892 					 "<stop offset=\"0\" style=\""
1893 					 "stop-color:rgb(0%%,0%%,0%%);"
1894 					 "stop-opacity:0;\"/>\n");
1895 	else {
1896 	    _cairo_output_stream_printf (document->xml_node_defs,
1897 					 "<stop offset=\"0\" style=\""
1898 					 "stop-color:rgb(%f%%,%f%%,%f%%);"
1899 					 "stop-opacity %f;\"/>\n",
1900 					 pattern->base.stops[0].color.red   * 100.0,
1901 					 pattern->base.stops[0].color.green * 100.0,
1902 					 pattern->base.stops[0].color.blue  * 100.0,
1903 					 pattern->base.stops[0].color.alpha);
1904 	    if (n_stops > 1)
1905 		_cairo_output_stream_printf (document->xml_node_defs,
1906 					     "<stop offset=\"0\" style=\""
1907 					     "stop-color:rgb(%f%%,%f%%,%f%%);"
1908 					     "stop-opacity:%f;\"/>\n",
1909 					     pattern->base.stops[n_stops - 1].color.red   * 100.0,
1910 					     pattern->base.stops[n_stops - 1].color.green * 100.0,
1911 					     pattern->base.stops[n_stops - 1].color.blue  * 100.0,
1912 					     pattern->base.stops[n_stops - 1].color.alpha);
1913 	}
1914 
1915     } else {
1916 	double offset, r, x, y;
1917 	cairo_bool_t emulate_reflect = FALSE;
1918 
1919 	fx = (r1 * x0 - r0 * x1) / (r1 - r0);
1920 	fy = (r1 * y0 - r0 * y1) / (r1 - r0);
1921 
1922 	/* SVG doesn't support the inner circle and use instead a gradient focal.
1923 	 * That means we need to emulate the cairo behaviour by processing the
1924 	 * cairo gradient stops.
1925 	 * The CAIRO_EXTENT_NONE and CAIRO_EXTENT_PAD modes are quite easy to handle,
1926 	 * it's just a matter of stop position translation and calculation of
1927 	 * the corresponding SVG radial gradient focal.
1928 	 * The CAIRO_EXTENT_REFLECT and CAIRO_EXTEND_REPEAT modes require to compute a new
1929 	 * radial gradient, with an new outer circle, equal to r1 - r0 in the CAIRO_EXTEND_REPEAT
1930 	 * case, and 2 * (r1 - r0) in the CAIRO_EXTENT_REFLECT case, and a new gradient stop
1931 	 * list that maps to the original cairo stop list.
1932 	 */
1933 	if ((extend == CAIRO_EXTEND_REFLECT
1934 	     || extend == CAIRO_EXTEND_REPEAT)
1935 	    && r0 > 0.0) {
1936 	    double r_org = r1;
1937 
1938 	    if (extend == CAIRO_EXTEND_REFLECT) {
1939 		r1 = 2 * r1 - r0;
1940 		emulate_reflect = TRUE;
1941 	    }
1942 
1943 	    offset = fmod (r1, r1 - r0) / (r1 - r0) - 1.0;
1944 	    r = r1 - r0;
1945 
1946 	    /* New position of outer circle. */
1947 	    x = r * (x1 - fx) / r_org + fx;
1948 	    y = r * (y1 - fy) / r_org + fy;
1949 
1950 	    x1 = x;
1951 	    y1 = y;
1952 	    r1 = r;
1953 	    r0 = 0.0;
1954 	} else {
1955 	    offset = r0 / r1;
1956 	}
1957 
1958 	_cairo_output_stream_printf (document->xml_node_defs,
1959 				     "<radialGradient id=\"radial%d\" "
1960 				     "gradientUnits=\"userSpaceOnUse\" "
1961 				     "cx=\"%f\" cy=\"%f\" "
1962 				     "fx=\"%f\" fy=\"%f\" r=\"%f\" ",
1963 				     document->radial_pattern_id,
1964 				     x1, y1,
1965 				     fx, fy, r1);
1966 
1967 	if (emulate_reflect)
1968 	    _cairo_output_stream_printf (document->xml_node_defs, "spreadMethod=\"repeat\" ");
1969 	else
1970 	    _cairo_svg_surface_emit_pattern_extend (document->xml_node_defs, &pattern->base.base);
1971 	_cairo_svg_surface_emit_transform (document->xml_node_defs, "gradientTransform", &p2u, parent_matrix);
1972 	_cairo_output_stream_printf (document->xml_node_defs, ">\n");
1973 
1974 	/* To support cairo's EXTEND_NONE, (for which SVG has no similar
1975 	 * notion), we add transparent color stops on either end of the
1976 	 * user-provided stops. */
1977 	if (extend == CAIRO_EXTEND_NONE) {
1978 	    _cairo_output_stream_printf (document->xml_node_defs,
1979 					 "<stop offset=\"0\" style=\""
1980 					 "stop-color:rgb(0%%,0%%,0%%);"
1981 					 "stop-opacity:0;\"/>\n");
1982 	    if (r0 != 0.0)
1983 		_cairo_output_stream_printf (document->xml_node_defs,
1984 					     "<stop offset=\"%f\" style=\""
1985 					     "stop-color:rgb(0%%,0%%,0%%);"
1986 					     "stop-opacity:0;\"/>\n",
1987 					     r0 / r1);
1988 	}
1989 	status = _cairo_svg_surface_emit_pattern_stops (document->xml_node_defs,
1990 		                                        &pattern->base, offset,
1991 							reverse_stops,
1992 							emulate_reflect);
1993 	if (unlikely (status))
1994 	    return status;
1995 
1996 	if (pattern->base.base.extend == CAIRO_EXTEND_NONE)
1997 	    _cairo_output_stream_printf (document->xml_node_defs,
1998 					 "<stop offset=\"1.0\" style=\""
1999 					 "stop-color:rgb(0%%,0%%,0%%);"
2000 					 "stop-opacity:0;\"/>\n");
2001     }
2002 
2003     _cairo_output_stream_printf (document->xml_node_defs,
2004 				 "</radialGradient>\n");
2005 
2006     _cairo_output_stream_printf (style,
2007 				 "%s:url(#radial%d);",
2008 				 is_stroke ? "stroke" : "fill",
2009 				 document->radial_pattern_id);
2010 
2011     document->radial_pattern_id++;
2012 
2013     return CAIRO_STATUS_SUCCESS;
2014 }
2015 
2016 static cairo_status_t
_cairo_svg_surface_emit_pattern(cairo_svg_surface_t * surface,const cairo_pattern_t * pattern,cairo_output_stream_t * output,cairo_bool_t is_stroke,const cairo_matrix_t * parent_matrix)2017 _cairo_svg_surface_emit_pattern (cairo_svg_surface_t   *surface,
2018 				 const cairo_pattern_t       *pattern,
2019 				 cairo_output_stream_t *output,
2020 				 cairo_bool_t		is_stroke,
2021 				 const cairo_matrix_t  *parent_matrix)
2022 {
2023     switch (pattern->type) {
2024     case CAIRO_PATTERN_TYPE_SOLID:
2025 	return _cairo_svg_surface_emit_solid_pattern (surface, (cairo_solid_pattern_t *) pattern,
2026 						      output, is_stroke);
2027 
2028     case CAIRO_PATTERN_TYPE_SURFACE:
2029 	return _cairo_svg_surface_emit_surface_pattern (surface, (cairo_surface_pattern_t *) pattern,
2030 							output, is_stroke, parent_matrix);
2031 
2032     case CAIRO_PATTERN_TYPE_LINEAR:
2033 	return _cairo_svg_surface_emit_linear_pattern (surface, (cairo_linear_pattern_t *) pattern,
2034 						       output, is_stroke, parent_matrix);
2035 
2036     case CAIRO_PATTERN_TYPE_RADIAL:
2037 	return _cairo_svg_surface_emit_radial_pattern (surface, (cairo_radial_pattern_t *) pattern,
2038 						       output, is_stroke, parent_matrix);
2039     }
2040     return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH);
2041 }
2042 
2043 static cairo_status_t
_cairo_svg_surface_emit_fill_style(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op,const cairo_pattern_t * source,cairo_fill_rule_t fill_rule,const cairo_matrix_t * parent_matrix)2044 _cairo_svg_surface_emit_fill_style (cairo_output_stream_t	*output,
2045 				    cairo_svg_surface_t		*surface,
2046 				    cairo_operator_t		 op,
2047 				    const cairo_pattern_t	*source,
2048 				    cairo_fill_rule_t		 fill_rule,
2049 				    const cairo_matrix_t	*parent_matrix)
2050 {
2051     _cairo_output_stream_printf (output,
2052 				 "fill-rule:%s;",
2053 				 fill_rule == CAIRO_FILL_RULE_EVEN_ODD ?
2054 				 "evenodd" : "nonzero");
2055     _cairo_svg_surface_emit_operator_for_style (output, surface, op);
2056     return _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, parent_matrix);
2057 }
2058 
2059 static cairo_status_t
_cairo_svg_surface_emit_stroke_style(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op,const cairo_pattern_t * source,const cairo_stroke_style_t * stroke_style,const cairo_matrix_t * parent_matrix)2060 _cairo_svg_surface_emit_stroke_style (cairo_output_stream_t	   *output,
2061 				      cairo_svg_surface_t	   *surface,
2062 				      cairo_operator_t		    op,
2063 				      const cairo_pattern_t	   *source,
2064 				      const cairo_stroke_style_t   *stroke_style,
2065 				      const cairo_matrix_t	   *parent_matrix)
2066 {
2067     cairo_status_t status;
2068     const char *line_cap, *line_join;
2069     unsigned int i;
2070 
2071     switch (stroke_style->line_cap) {
2072 	case CAIRO_LINE_CAP_BUTT:
2073 	    line_cap = "butt";
2074 	    break;
2075 	case CAIRO_LINE_CAP_ROUND:
2076 	    line_cap = "round";
2077 	    break;
2078 	case CAIRO_LINE_CAP_SQUARE:
2079 	    line_cap = "square";
2080 	    break;
2081 	default:
2082 	    ASSERT_NOT_REACHED;
2083     }
2084 
2085     switch (stroke_style->line_join) {
2086 	case CAIRO_LINE_JOIN_MITER:
2087 	    line_join = "miter";
2088 	    break;
2089 	case CAIRO_LINE_JOIN_ROUND:
2090 	    line_join = "round";
2091 	    break;
2092 	case CAIRO_LINE_JOIN_BEVEL:
2093 	    line_join = "bevel";
2094 	    break;
2095 	default:
2096 	    ASSERT_NOT_REACHED;
2097     }
2098 
2099     _cairo_output_stream_printf (output,
2100 				 "stroke-width:%f;"
2101 				 "stroke-linecap:%s;"
2102 				 "stroke-linejoin:%s;",
2103 				 stroke_style->line_width,
2104 				 line_cap,
2105 				 line_join);
2106 
2107      status = _cairo_svg_surface_emit_pattern (surface, source, output, TRUE, parent_matrix);
2108      if (unlikely (status))
2109 	 return status;
2110 
2111      _cairo_svg_surface_emit_operator_for_style (output, surface, op);
2112 
2113     if (stroke_style->num_dashes > 0) {
2114 	_cairo_output_stream_printf (output, "stroke-dasharray:");
2115 	for (i = 0; i < stroke_style->num_dashes; i++) {
2116 	    _cairo_output_stream_printf (output, "%f",
2117 					 stroke_style->dash[i]);
2118 	    if (i + 1 < stroke_style->num_dashes)
2119 		_cairo_output_stream_printf (output, ",");
2120 	    else
2121 		_cairo_output_stream_printf (output, ";");
2122 	}
2123 	if (stroke_style->dash_offset != 0.0) {
2124 	    _cairo_output_stream_printf (output,
2125 					 "stroke-dashoffset:%f;",
2126 					 stroke_style->dash_offset);
2127 	}
2128     }
2129 
2130     _cairo_output_stream_printf (output,
2131 				 "stroke-miterlimit:%f;",
2132 				 stroke_style->miter_limit);
2133 
2134     return CAIRO_STATUS_SUCCESS;
2135 }
2136 
2137 static cairo_int_status_t
_cairo_svg_surface_fill_stroke(void * abstract_surface,cairo_operator_t fill_op,const cairo_pattern_t * fill_source,cairo_fill_rule_t fill_rule,double fill_tolerance,cairo_antialias_t fill_antialias,cairo_path_fixed_t * path,cairo_operator_t stroke_op,const cairo_pattern_t * stroke_source,const cairo_stroke_style_t * stroke_style,const cairo_matrix_t * stroke_ctm,const cairo_matrix_t * stroke_ctm_inverse,double stroke_tolerance,cairo_antialias_t stroke_antialias,cairo_clip_t * clip)2138 _cairo_svg_surface_fill_stroke (void			*abstract_surface,
2139 				cairo_operator_t	 fill_op,
2140 				const cairo_pattern_t	*fill_source,
2141 				cairo_fill_rule_t	 fill_rule,
2142 				double			 fill_tolerance,
2143 				cairo_antialias_t	 fill_antialias,
2144 				cairo_path_fixed_t	*path,
2145 				cairo_operator_t	 stroke_op,
2146 				const cairo_pattern_t	*stroke_source,
2147 				const cairo_stroke_style_t	*stroke_style,
2148 				const cairo_matrix_t		*stroke_ctm,
2149 				const cairo_matrix_t		*stroke_ctm_inverse,
2150 				double			 stroke_tolerance,
2151 				cairo_antialias_t	 stroke_antialias,
2152 				cairo_clip_t		*clip)
2153 {
2154     cairo_svg_surface_t *surface = abstract_surface;
2155     cairo_status_t status;
2156 
2157     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
2158     if (unlikely (status))
2159 	return status;
2160 
2161     _cairo_output_stream_printf (surface->xml_node, "<path style=\"");
2162     status = _cairo_svg_surface_emit_fill_style (surface->xml_node, surface, fill_op,
2163 						 fill_source, fill_rule, stroke_ctm_inverse);
2164     if (unlikely (status))
2165 	return status;
2166 
2167     status = _cairo_svg_surface_emit_stroke_style (surface->xml_node, surface, stroke_op,
2168 						   stroke_source, stroke_style, stroke_ctm_inverse);
2169     if (unlikely (status))
2170 	return status;
2171 
2172     _cairo_output_stream_printf (surface->xml_node, "\" ");
2173 
2174     _cairo_svg_surface_emit_path (surface->xml_node, path, stroke_ctm_inverse);
2175 
2176     _cairo_svg_surface_emit_transform (surface->xml_node, " transform", stroke_ctm, NULL);
2177     _cairo_output_stream_printf (surface->xml_node, "/>\n");
2178 
2179     return CAIRO_STATUS_SUCCESS;
2180 }
2181 
2182 static cairo_int_status_t
_cairo_svg_surface_fill(void * abstract_surface,cairo_operator_t op,const cairo_pattern_t * source,cairo_path_fixed_t * path,cairo_fill_rule_t fill_rule,double tolerance,cairo_antialias_t antialias,cairo_clip_t * clip)2183 _cairo_svg_surface_fill (void			*abstract_surface,
2184 			 cairo_operator_t	 op,
2185 			 const cairo_pattern_t	*source,
2186 			 cairo_path_fixed_t	*path,
2187 			 cairo_fill_rule_t	 fill_rule,
2188 			 double			 tolerance,
2189 			 cairo_antialias_t	 antialias,
2190 			 cairo_clip_t		*clip)
2191 {
2192     cairo_svg_surface_t *surface = abstract_surface;
2193     cairo_status_t status;
2194 
2195     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
2196 	return _cairo_svg_surface_analyze_operation (surface, op, source);
2197 
2198     assert (_cairo_svg_surface_operation_supported (surface, op, source));
2199 
2200     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
2201     if (unlikely (status))
2202 	return status;
2203 
2204     _cairo_output_stream_printf (surface->xml_node, "<path style=\" stroke:none;");
2205     status = _cairo_svg_surface_emit_fill_style (surface->xml_node, surface, op, source, fill_rule, NULL);
2206     if (unlikely (status))
2207 	return status;
2208 
2209     _cairo_output_stream_printf (surface->xml_node, "\" ");
2210 
2211     _cairo_svg_surface_emit_path (surface->xml_node, path, NULL);
2212 
2213     _cairo_output_stream_printf (surface->xml_node, "/>\n");
2214 
2215     return CAIRO_STATUS_SUCCESS;
2216 }
2217 
2218 static cairo_bool_t
_cairo_svg_surface_get_extents(void * abstract_surface,cairo_rectangle_int_t * rectangle)2219 _cairo_svg_surface_get_extents (void		        *abstract_surface,
2220 				cairo_rectangle_int_t   *rectangle)
2221 {
2222     cairo_svg_surface_t *surface = abstract_surface;
2223 
2224     rectangle->x = 0;
2225     rectangle->y = 0;
2226 
2227     /* XXX: The conversion to integers here is pretty bogus, (not to
2228      * mention the arbitrary limitation of width to a short(!). We
2229      * may need to come up with a better interface for get_size.
2230      */
2231     rectangle->width  = ceil (surface->width);
2232     rectangle->height = ceil (surface->height);
2233 
2234     return TRUE;
2235 }
2236 
2237 static cairo_status_t
_cairo_svg_surface_emit_paint(cairo_output_stream_t * output,cairo_svg_surface_t * surface,cairo_operator_t op,const cairo_pattern_t * source,const cairo_pattern_t * mask_source,const char * extra_attributes)2238 _cairo_svg_surface_emit_paint (cairo_output_stream_t *output,
2239 			       cairo_svg_surface_t   *surface,
2240 			       cairo_operator_t	      op,
2241 			       const cairo_pattern_t	     *source,
2242 			       const cairo_pattern_t	     *mask_source,
2243 			       const char	     *extra_attributes)
2244 {
2245     cairo_status_t status;
2246 
2247     if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
2248 	source->extend == CAIRO_EXTEND_NONE)
2249 	return _cairo_svg_surface_emit_composite_pattern (output,
2250 							  surface,
2251 							  op,
2252 							  (cairo_surface_pattern_t *) source,
2253 							  invalid_pattern_id,
2254 							  mask_source ? &mask_source->matrix :NULL,
2255 							  extra_attributes);
2256 
2257     _cairo_output_stream_printf (output,
2258 				 "<rect x=\"0\" y=\"0\" "
2259 				 "width=\"%f\" height=\"%f\" "
2260 				 "style=\"",
2261 				 surface->width, surface->height);
2262     _cairo_svg_surface_emit_operator_for_style (output, surface, op);
2263     status = _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, NULL);
2264     if (unlikely (status))
2265 	return status;
2266 
2267     _cairo_output_stream_printf (output, "stroke:none;\"");
2268 
2269     if (extra_attributes)
2270 	_cairo_output_stream_printf (output, " %s", extra_attributes);
2271 
2272     _cairo_output_stream_printf (output, "/>\n");
2273 
2274     return CAIRO_STATUS_SUCCESS;
2275 }
2276 
2277 static cairo_int_status_t
_cairo_svg_surface_paint(void * abstract_surface,cairo_operator_t op,const cairo_pattern_t * source,cairo_clip_t * clip)2278 _cairo_svg_surface_paint (void		    *abstract_surface,
2279 			  cairo_operator_t   op,
2280 			  const cairo_pattern_t   *source,
2281 			  cairo_clip_t		  *clip)
2282 {
2283     cairo_status_t status;
2284     cairo_svg_surface_t *surface = abstract_surface;
2285 
2286     /* Emulation of clear and source operators, when no clipping region
2287      * is defined. We just delete existing content of surface root node,
2288      * and exit early if operator is clear.
2289      */
2290     if ((op == CAIRO_OPERATOR_CLEAR || op == CAIRO_OPERATOR_SOURCE) &&
2291 	clip == NULL)
2292     {
2293 	switch (surface->paginated_mode) {
2294 	case CAIRO_PAGINATED_MODE_FALLBACK:
2295 	    ASSERT_NOT_REACHED;
2296 	case CAIRO_PAGINATED_MODE_ANALYZE:
2297 	    return CAIRO_STATUS_SUCCESS;
2298 
2299 	case CAIRO_PAGINATED_MODE_RENDER:
2300 	    status = _cairo_output_stream_destroy (surface->xml_node);
2301 	    if (unlikely (status)) {
2302 		surface->xml_node = NULL;
2303 		return status;
2304 	    }
2305 
2306 	    surface->xml_node = _cairo_memory_stream_create ();
2307 	    if (_cairo_output_stream_get_status (surface->xml_node)) {
2308 		status = _cairo_output_stream_destroy (surface->xml_node);
2309 		surface->xml_node = NULL;
2310 		return status;
2311 	    }
2312 
2313 	    if (op == CAIRO_OPERATOR_CLEAR) {
2314 		if (surface->content == CAIRO_CONTENT_COLOR) {
2315 		    _cairo_output_stream_printf (surface->xml_node,
2316 						 "<rect "
2317 						 "width=\"%f\" height=\"%f\" "
2318 						 "style=\"opacity:1;"
2319 						 "stroke:none;"
2320 						 "fill:rgb(0,0,0);\"/>\n",
2321 						 surface->width, surface->height);
2322 		}
2323 		return CAIRO_STATUS_SUCCESS;
2324 	    }
2325 	    break;
2326 	}
2327     } else {
2328 	if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
2329 	    return _cairo_svg_surface_analyze_operation (surface, op, source);
2330 
2331 	assert (_cairo_svg_surface_operation_supported (surface, op, source));
2332     }
2333 
2334     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
2335     if (unlikely (status))
2336 	return status;
2337 
2338     return _cairo_svg_surface_emit_paint (surface->xml_node,
2339 					  surface, op, source, 0, NULL);
2340 }
2341 
2342 static cairo_int_status_t
_cairo_svg_surface_mask(void * abstract_surface,cairo_operator_t op,const cairo_pattern_t * source,const cairo_pattern_t * mask,cairo_clip_t * clip)2343 _cairo_svg_surface_mask (void		    *abstract_surface,
2344 			 cairo_operator_t     op,
2345 			 const cairo_pattern_t	    *source,
2346 			 const cairo_pattern_t	    *mask,
2347 			 cairo_clip_t		    *clip)
2348 {
2349     cairo_status_t status;
2350     cairo_svg_surface_t *surface = abstract_surface;
2351     cairo_svg_document_t *document = surface->document;
2352     cairo_output_stream_t *mask_stream;
2353     char buffer[64];
2354     cairo_bool_t discard_filter = FALSE;
2355     unsigned int mask_id;
2356 
2357     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
2358 	cairo_status_t source_status, mask_status;
2359 
2360 	source_status = _cairo_svg_surface_analyze_operation (surface, op, source);
2361 	if (_cairo_status_is_error (source_status))
2362 	    return source_status;
2363 
2364 	if (mask->has_component_alpha) {
2365 	    mask_status = CAIRO_INT_STATUS_UNSUPPORTED;
2366 	} else {
2367 	    mask_status = _cairo_svg_surface_analyze_operation (surface, op, mask);
2368 	    if (_cairo_status_is_error (mask_status))
2369 		return mask_status;
2370 	}
2371 
2372 	return _cairo_analysis_surface_merge_status (source_status,
2373 						     mask_status);
2374     }
2375 
2376     assert (_cairo_svg_surface_operation_supported (surface, op, source));
2377     assert (_cairo_svg_surface_operation_supported (surface, CAIRO_OPERATOR_OVER, mask));
2378 
2379     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
2380     if (unlikely (status))
2381 	return status;
2382 
2383     if (mask->type == CAIRO_PATTERN_TYPE_SURFACE) {
2384 	const cairo_surface_pattern_t *surface_pattern = (const cairo_surface_pattern_t*) mask;
2385 	cairo_content_t content = cairo_surface_get_content (surface_pattern->surface);
2386 	if (content == CAIRO_CONTENT_ALPHA)
2387 	    discard_filter = TRUE;
2388     }
2389 
2390     if (!discard_filter)
2391 	_cairo_svg_surface_emit_alpha_filter (document);
2392 
2393     /* _cairo_svg_surface_emit_paint() will output a pattern definition to
2394      * document->xml_node_defs so we need to write the mask element to
2395      * a temporary stream and then copy that to xml_node_defs. */
2396     mask_stream = _cairo_memory_stream_create ();
2397     if (_cairo_output_stream_get_status (mask_stream))
2398 	return _cairo_output_stream_destroy (mask_stream);
2399 
2400     mask_id = _cairo_svg_document_allocate_mask_id (document);
2401 
2402     _cairo_output_stream_printf (mask_stream,
2403 				 "<mask id=\"mask%d\">\n"
2404 				 "%s",
2405 				 mask_id,
2406 				 discard_filter ? "" : "  <g filter=\"url(#alpha)\">\n");
2407     status = _cairo_svg_surface_emit_paint (mask_stream, surface, CAIRO_OPERATOR_OVER, mask, source, NULL);
2408     if (unlikely (status)) {
2409 	cairo_status_t ignore = _cairo_output_stream_destroy (mask_stream);
2410 	return status;
2411 	(void) ignore;
2412     }
2413 
2414     _cairo_output_stream_printf (mask_stream,
2415 				 "%s"
2416 				 "</mask>\n",
2417 				 discard_filter ? "" : "  </g>\n");
2418     _cairo_memory_stream_copy (mask_stream, document->xml_node_defs);
2419 
2420     status = _cairo_output_stream_destroy (mask_stream);
2421     if (unlikely (status))
2422 	return status;
2423 
2424     snprintf (buffer, sizeof buffer, "mask=\"url(#mask%d)\"",
2425 	      mask_id);
2426     status = _cairo_svg_surface_emit_paint (surface->xml_node, surface, op, source, 0, buffer);
2427     if (unlikely (status))
2428 	return status;
2429 
2430     return CAIRO_STATUS_SUCCESS;
2431 }
2432 
2433 static cairo_int_status_t
_cairo_svg_surface_stroke(void * abstract_dst,cairo_operator_t op,const cairo_pattern_t * source,cairo_path_fixed_t * path,const cairo_stroke_style_t * stroke_style,const cairo_matrix_t * ctm,const cairo_matrix_t * ctm_inverse,double tolerance,cairo_antialias_t antialias,cairo_clip_t * clip)2434 _cairo_svg_surface_stroke (void			*abstract_dst,
2435 			   cairo_operator_t      op,
2436 			   const cairo_pattern_t *source,
2437 			   cairo_path_fixed_t	*path,
2438 			   const cairo_stroke_style_t *stroke_style,
2439 			   const cairo_matrix_t	*ctm,
2440 			   const cairo_matrix_t	*ctm_inverse,
2441 			   double		 tolerance,
2442 			   cairo_antialias_t	 antialias,
2443 			   cairo_clip_t		*clip)
2444 {
2445     cairo_svg_surface_t *surface = abstract_dst;
2446     cairo_status_t status;
2447 
2448     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
2449 	return _cairo_svg_surface_analyze_operation (surface, op, source);
2450 
2451     assert (_cairo_svg_surface_operation_supported (surface, op, source));
2452 
2453     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
2454     if (unlikely (status))
2455 	return status;
2456 
2457     _cairo_output_stream_printf (surface->xml_node, "<path style=\"fill:none;");
2458     status = _cairo_svg_surface_emit_stroke_style (surface->xml_node, surface, op,
2459 						   source, stroke_style, ctm_inverse);
2460     if (unlikely (status))
2461 	return status;
2462 
2463     _cairo_output_stream_printf (surface->xml_node, "\" ");
2464 
2465     _cairo_svg_surface_emit_path (surface->xml_node, path, ctm_inverse);
2466 
2467     _cairo_svg_surface_emit_transform (surface->xml_node, " transform", ctm, NULL);
2468     _cairo_output_stream_printf (surface->xml_node, "/>\n");
2469 
2470     return CAIRO_STATUS_SUCCESS;
2471 }
2472 
2473 static cairo_int_status_t
_cairo_svg_surface_show_glyphs(void * abstract_surface,cairo_operator_t op,const cairo_pattern_t * pattern,cairo_glyph_t * glyphs,int num_glyphs,cairo_scaled_font_t * scaled_font,cairo_clip_t * clip,int * remaining_glyphs)2474 _cairo_svg_surface_show_glyphs (void			*abstract_surface,
2475 				cairo_operator_t	 op,
2476 				const cairo_pattern_t	*pattern,
2477 				cairo_glyph_t		*glyphs,
2478 				int			 num_glyphs,
2479 				cairo_scaled_font_t	*scaled_font,
2480 				cairo_clip_t		*clip,
2481 				int			*remaining_glyphs)
2482 {
2483     cairo_svg_surface_t *surface = abstract_surface;
2484     cairo_svg_document_t *document = surface->document;
2485     cairo_path_fixed_t path;
2486     cairo_status_t status;
2487     cairo_scaled_font_subsets_glyph_t subset_glyph;
2488     int i;
2489 
2490     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
2491 	return _cairo_svg_surface_analyze_operation (surface, op, pattern);
2492 
2493     assert (_cairo_svg_surface_operation_supported (surface, op, pattern));
2494 
2495     if (num_glyphs <= 0)
2496 	return CAIRO_STATUS_SUCCESS;
2497 
2498     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
2499     if (unlikely (status))
2500 	return status;
2501 
2502     /* FIXME it's probably possible to apply a pattern of a gradient to
2503      * a group of symbols, but I don't know how yet. Gradients or patterns
2504      * are translated by x and y properties of use element. */
2505     if (pattern->type != CAIRO_PATTERN_TYPE_SOLID)
2506 	goto FALLBACK;
2507 
2508     _cairo_output_stream_printf (surface->xml_node, "<g style=\"");
2509     status = _cairo_svg_surface_emit_pattern (surface, pattern,
2510 	                                      surface->xml_node, FALSE, NULL);
2511     if (unlikely (status))
2512 	return status;
2513 
2514     _cairo_svg_surface_emit_operator_for_style (surface->xml_node, surface, op);
2515 
2516     _cairo_output_stream_printf (surface->xml_node, "\">\n");
2517 
2518     for (i = 0; i < num_glyphs; i++) {
2519 	status = _cairo_scaled_font_subsets_map_glyph (document->font_subsets,
2520 						       scaled_font, glyphs[i].index,
2521 						       NULL, 0,
2522                                                        &subset_glyph);
2523 	if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
2524 	    _cairo_output_stream_printf (surface->xml_node, "</g>\n");
2525 
2526 	    glyphs += i;
2527 	    num_glyphs -= i;
2528 	    goto FALLBACK;
2529 	}
2530 
2531 	if (unlikely (status))
2532 	    return status;
2533 
2534 	_cairo_output_stream_printf (surface->xml_node,
2535 				     "  <use xlink:href=\"#glyph%d-%d\" "
2536 				     "x=\"%f\" y=\"%f\"/>\n",
2537 				     subset_glyph.font_id,
2538                                      subset_glyph.subset_glyph_index,
2539 				     glyphs[i].x, glyphs[i].y);
2540     }
2541 
2542     _cairo_output_stream_printf (surface->xml_node, "</g>\n");
2543 
2544     return CAIRO_STATUS_SUCCESS;
2545 
2546 FALLBACK:
2547     _cairo_path_fixed_init (&path);
2548 
2549     status = _cairo_scaled_font_glyph_path (scaled_font,
2550 					    (cairo_glyph_t *) glyphs,
2551 					    num_glyphs, &path);
2552 
2553     if (unlikely (status)) {
2554 	_cairo_path_fixed_fini (&path);
2555 	return status;
2556     }
2557 
2558     status = _cairo_svg_surface_fill (abstract_surface, op, pattern,
2559 				      &path, CAIRO_FILL_RULE_WINDING,
2560 				      0.0, CAIRO_ANTIALIAS_SUBPIXEL,
2561 				      clip);
2562 
2563     _cairo_path_fixed_fini (&path);
2564 
2565     return status;
2566 }
2567 
2568 static void
_cairo_svg_surface_get_font_options(void * abstract_surface,cairo_font_options_t * options)2569 _cairo_svg_surface_get_font_options (void                  *abstract_surface,
2570 				     cairo_font_options_t  *options)
2571 {
2572     _cairo_font_options_init_default (options);
2573 
2574     cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
2575     cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_OFF);
2576     cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_GRAY);
2577 }
2578 
2579 static const cairo_surface_backend_t cairo_svg_surface_backend = {
2580 	CAIRO_SURFACE_TYPE_SVG,
2581 	NULL, /* create_similar: handled by wrapper */
2582 	_cairo_svg_surface_finish,
2583 	NULL, /* acquire_source_image */
2584 	NULL, /* release_source_image */
2585 	NULL, /* acquire_dest_image */
2586 	NULL, /* release_dest_image */
2587 	NULL, /* clone_similar */
2588 	NULL, /* _cairo_svg_surface_composite, */
2589 	NULL, /* _cairo_svg_surface_fill_rectangles, */
2590 	NULL, /* _cairo_svg_surface_composite_trapezoids,*/
2591 	NULL, /* create_span_renderer */
2592 	NULL, /* check_span_renderer */
2593 	_cairo_svg_surface_copy_page,
2594 	_cairo_svg_surface_show_page,
2595 	_cairo_svg_surface_get_extents,
2596 	NULL, /* _cairo_svg_surface_old_show_glyphs, */
2597 	_cairo_svg_surface_get_font_options,
2598 	NULL, /* flush */
2599 	NULL, /* mark dirty rectangle */
2600 	NULL, /* scaled font fini */
2601 	NULL, /* scaled glyph fini */
2602 	_cairo_svg_surface_paint,
2603 	_cairo_svg_surface_mask,
2604 	_cairo_svg_surface_stroke,
2605 	_cairo_svg_surface_fill,
2606 	_cairo_svg_surface_show_glyphs,
2607 	NULL, /* snapshot */
2608 	NULL, /* is_similar */
2609 	_cairo_svg_surface_fill_stroke
2610 };
2611 
2612 static cairo_status_t
_cairo_svg_document_create(cairo_output_stream_t * output_stream,double width,double height,cairo_svg_version_t version,cairo_svg_document_t ** document_out)2613 _cairo_svg_document_create (cairo_output_stream_t	 *output_stream,
2614 			    double			  width,
2615 			    double			  height,
2616 			    cairo_svg_version_t		  version,
2617 			    cairo_svg_document_t	**document_out)
2618 {
2619     cairo_svg_document_t *document;
2620     cairo_status_t status, status_ignored;
2621 
2622     if (output_stream->status)
2623 	return output_stream->status;
2624 
2625     document = malloc (sizeof (cairo_svg_document_t));
2626     if (unlikely (document == NULL))
2627 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
2628 
2629     /* The use of defs for font glyphs imposes no per-subset limit. */
2630     document->font_subsets = _cairo_scaled_font_subsets_create_scaled ();
2631     if (unlikely (document->font_subsets == NULL)) {
2632 	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
2633 	goto CLEANUP_DOCUMENT;
2634     }
2635 
2636     document->output_stream = output_stream;
2637     document->refcount = 1;
2638     document->owner = NULL;
2639     document->finished = FALSE;
2640     document->width = width;
2641     document->height = height;
2642 
2643     document->linear_pattern_id = 0;
2644     document->radial_pattern_id = 0;
2645     document->pattern_id = 0;
2646     document->filter_id = 0;
2647     document->clip_id = 0;
2648     document->mask_id = 0;
2649 
2650     document->xml_node_defs = _cairo_memory_stream_create ();
2651     status = _cairo_output_stream_get_status (document->xml_node_defs);
2652     if (unlikely (status))
2653 	goto CLEANUP_NODE_DEFS;
2654 
2655     document->xml_node_glyphs = _cairo_memory_stream_create ();
2656     status = _cairo_output_stream_get_status (document->xml_node_glyphs);
2657     if (unlikely (status))
2658 	goto CLEANUP_NODE_GLYPHS;
2659 
2660     document->alpha_filter = FALSE;
2661 
2662     document->svg_version = version;
2663 
2664     *document_out = document;
2665     return CAIRO_STATUS_SUCCESS;
2666 
2667   CLEANUP_NODE_GLYPHS:
2668     status_ignored = _cairo_output_stream_destroy (document->xml_node_glyphs);
2669   CLEANUP_NODE_DEFS:
2670     status_ignored = _cairo_output_stream_destroy (document->xml_node_defs);
2671     _cairo_scaled_font_subsets_destroy (document->font_subsets);
2672   CLEANUP_DOCUMENT:
2673     free (document);
2674     return status;
2675 }
2676 
2677 static cairo_svg_document_t *
_cairo_svg_document_reference(cairo_svg_document_t * document)2678 _cairo_svg_document_reference (cairo_svg_document_t *document)
2679 {
2680     document->refcount++;
2681 
2682     return document;
2683 }
2684 
2685 static unsigned int
_cairo_svg_document_allocate_mask_id(cairo_svg_document_t * document)2686 _cairo_svg_document_allocate_mask_id (cairo_svg_document_t *document)
2687 {
2688     return document->mask_id++;
2689 }
2690 
2691 static cairo_status_t
_cairo_svg_document_destroy(cairo_svg_document_t * document)2692 _cairo_svg_document_destroy (cairo_svg_document_t *document)
2693 {
2694     cairo_status_t status;
2695 
2696     document->refcount--;
2697     if (document->refcount > 0)
2698       return CAIRO_STATUS_SUCCESS;
2699 
2700     status = _cairo_svg_document_finish (document);
2701 
2702     free (document);
2703 
2704     return status;
2705 }
2706 
2707 static cairo_status_t
_cairo_svg_document_finish(cairo_svg_document_t * document)2708 _cairo_svg_document_finish (cairo_svg_document_t *document)
2709 {
2710     cairo_status_t status, status2;
2711     cairo_output_stream_t *output = document->output_stream;
2712     cairo_svg_page_t *page;
2713     unsigned int i;
2714 
2715     if (document->finished)
2716 	return CAIRO_STATUS_SUCCESS;
2717 
2718     /*
2719      * Should we add DOCTYPE?
2720      *
2721      * Google says no.
2722      *
2723      * http://tech.groups.yahoo.com/group/svg-developers/message/48562:
2724      *   There's a bunch of issues, but just to pick a few:
2725      *   - they'll give false positives.
2726      *   - they'll give false negatives.
2727      *   - they're namespace-unaware.
2728      *   - they don't wildcard.
2729      *   So when they say OK they really haven't checked anything, when
2730      *   they say NOT OK they might be on crack, and like all
2731      *   namespace-unaware things they're a dead branch of the XML tree.
2732      *
2733      * http://jwatt.org/svg/authoring/:
2734      *   Unfortunately the SVG DTDs are a source of so many issues that the
2735      *   SVG WG has decided not to write one for the upcoming SVG 1.2
2736      *   standard. In fact SVG WG members are even telling people not to use
2737      *   a DOCTYPE declaration in SVG 1.0 and 1.1 documents.
2738      */
2739 
2740     _cairo_output_stream_printf (output,
2741 				 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2742 				 "<svg xmlns=\"http://www.w3.org/2000/svg\" "
2743 				 "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
2744 				 "width=\"%fpt\" height=\"%fpt\" "
2745 				 "viewBox=\"0 0 %f %f\" version=\"%s\">\n",
2746 				 document->width, document->height,
2747 				 document->width, document->height,
2748 				 _cairo_svg_internal_version_strings [document->svg_version]);
2749 
2750     status = _cairo_svg_document_emit_font_subsets (document);
2751 
2752     if (_cairo_memory_stream_length (document->xml_node_glyphs) > 0 ||
2753 	_cairo_memory_stream_length (document->xml_node_defs) > 0) {
2754 	_cairo_output_stream_printf (output, "<defs>\n");
2755 	if (_cairo_memory_stream_length (document->xml_node_glyphs) > 0) {
2756 	    _cairo_output_stream_printf (output, "<g>\n");
2757 	    _cairo_memory_stream_copy (document->xml_node_glyphs, output);
2758 	    _cairo_output_stream_printf (output, "</g>\n");
2759 	}
2760 	_cairo_memory_stream_copy (document->xml_node_defs, output);
2761 	_cairo_output_stream_printf (output, "</defs>\n");
2762     }
2763 
2764     if (document->owner != NULL) {
2765 	cairo_svg_surface_t *surface;
2766 
2767 	surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (document->owner);
2768 	if (surface->xml_node != NULL &&
2769 		_cairo_memory_stream_length (surface->xml_node) > 0) {
2770 	    if (unlikely (_cairo_svg_surface_store_page (surface) == NULL)) {
2771 		if (status == CAIRO_STATUS_SUCCESS)
2772 		    status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
2773 	    }
2774 	}
2775 
2776 	if (surface->page_set.num_elements > 1 &&
2777 	    _cairo_svg_version_has_page_set_support (document->svg_version)) {
2778 	    _cairo_output_stream_printf (output, "<pageSet>\n");
2779 	    for (i = 0; i < surface->page_set.num_elements; i++) {
2780 		page = _cairo_array_index (&surface->page_set, i);
2781 		_cairo_output_stream_printf (output, "<page>\n");
2782 		_cairo_output_stream_printf (output,
2783 					     "<g id=\"surface%d\">\n",
2784 					     page->surface_id);
2785 		_cairo_memory_stream_copy (page->xml_node, output);
2786 		_cairo_output_stream_printf (output, "</g>\n</page>\n");
2787 	    }
2788 	    _cairo_output_stream_printf (output, "</pageSet>\n");
2789 	} else if (surface->page_set.num_elements > 0) {
2790 	    page = _cairo_array_index (&surface->page_set, surface->page_set.num_elements - 1);
2791 	    _cairo_output_stream_printf (output,
2792 					 "<g id=\"surface%d\">\n",
2793 					 page->surface_id);
2794 	    _cairo_memory_stream_copy (page->xml_node, output);
2795 	    _cairo_output_stream_printf (output, "</g>\n");
2796 	}
2797     }
2798 
2799     _cairo_output_stream_printf (output, "</svg>\n");
2800 
2801     status2 = _cairo_output_stream_destroy (document->xml_node_glyphs);
2802     if (status == CAIRO_STATUS_SUCCESS)
2803 	status = status2;
2804 
2805     status2 = _cairo_output_stream_destroy (document->xml_node_defs);
2806     if (status == CAIRO_STATUS_SUCCESS)
2807 	status = status2;
2808 
2809     status2 = _cairo_output_stream_destroy (output);
2810     if (status == CAIRO_STATUS_SUCCESS)
2811 	status = status2;
2812 
2813     document->finished = TRUE;
2814 
2815     return status;
2816 }
2817 
2818 static void
_cairo_svg_surface_set_paginated_mode(void * abstract_surface,cairo_paginated_mode_t paginated_mode)2819 _cairo_svg_surface_set_paginated_mode (void			*abstract_surface,
2820 				       cairo_paginated_mode_t	 paginated_mode)
2821 {
2822     cairo_svg_surface_t *surface = abstract_surface;
2823 
2824     surface->paginated_mode = paginated_mode;
2825 }
2826 
2827 static cairo_bool_t
_cairo_svg_surface_supports_fine_grained_fallbacks(void * abstract_surface)2828 _cairo_svg_surface_supports_fine_grained_fallbacks (void	*abstract_surface)
2829 {
2830     cairo_svg_surface_t *surface = abstract_surface;
2831     cairo_int_status_t status = CAIRO_INT_STATUS_UNSUPPORTED;
2832 
2833     if (surface->document->svg_version >= CAIRO_SVG_VERSION_1_2) {
2834 	status =  _cairo_svg_surface_analyze_operator (surface,
2835 						       CAIRO_OPERATOR_SOURCE);
2836     }
2837 
2838     return status == CAIRO_STATUS_SUCCESS;
2839 }
2840 
2841 static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend = {
2842     NULL /*_cairo_svg_surface_start_page*/,
2843     _cairo_svg_surface_set_paginated_mode,
2844     NULL, /* _cairo_svg_surface_set_bounding_box */
2845     NULL, /* _cairo_svg_surface_set_fallback_images_required */
2846     _cairo_svg_surface_supports_fine_grained_fallbacks,
2847 
2848 };
2849