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: & and ".
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, "&");
1251 else // p == '"'
1252 _cairo_output_stream_printf (stream, """);
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