/* vim: set sw=4 sts=4: -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ /* cairo - a vector graphics library with display and print output * * Copyright © 2004 Red Hat, Inc * Copyright © 2005-2007 Emmanuel Pacaud * Copyright © 2006 Red Hat, Inc * Copyright © 2020-2021 Anton Danilkin * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is University of Southern * California. * * Contributor(s): * Kristian Høgsberg * Emmanuel Pacaud * Carl Worth * Anton Danilkin */ #include "cairoint.h" #include "cairo-svg.h" #include "cairo-array-private.h" #include "cairo-default-context-private.h" #include "cairo-error-private.h" #include "cairo-image-info-private.h" #include "cairo-image-surface-private.h" #include "cairo-recording-surface-inline.h" #include "cairo-output-stream-private.h" #include "cairo-paginated-private.h" #include "cairo-scaled-font-subsets-private.h" #include "cairo-surface-clipper-private.h" #include "cairo-surface-snapshot-inline.h" #include "cairo-svg-surface-private.h" /** * SECTION:cairo-svg * @Title: SVG Surfaces * @Short_Description: Rendering SVG documents * @See_Also: #cairo_surface_t * * The SVG surface is used to render cairo graphics to * SVG files and is a multi-page vector surface backend. **/ typedef struct _cairo_svg_source_surface { cairo_hash_entry_t base; unsigned int id; unsigned char *unique_id; unsigned long unique_id_length; cairo_bool_t transitive_paint_used; } cairo_svg_source_surface_t; /* * _cairo_svg_paint_element and _cairo_svg_paint are used to implement paints in transformed recording patterns. */ typedef struct _cairo_svg_paint_element { unsigned int source_id; cairo_matrix_t matrix; } cairo_svg_paint_element_t; typedef struct _cairo_svg_paint { cairo_hash_entry_t base; unsigned int source_id; cairo_array_t paint_elements; cairo_box_double_t box; } cairo_svg_paint_t; static void _cairo_svg_source_surface_init_key (cairo_svg_source_surface_t *source_surface) { if (source_surface->unique_id && source_surface->unique_id_length > 0) { source_surface->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE, source_surface->unique_id, source_surface->unique_id_length); } else { source_surface->base.hash = source_surface->id; } } static cairo_bool_t _cairo_svg_source_surface_equal (const void *key_a, const void *key_b) { const cairo_svg_source_surface_t *a = key_a; const cairo_svg_source_surface_t *b = key_b; if (a->unique_id && b->unique_id && a->unique_id_length == b->unique_id_length) { return memcmp (a->unique_id, b->unique_id, a->unique_id_length) == 0; } return a->id == b->id; } static void _cairo_svg_source_surface_pluck (void *entry, void *closure) { cairo_svg_source_surface_t *source_surface = entry; cairo_hash_table_t *patterns = closure; _cairo_hash_table_remove (patterns, &source_surface->base); free (source_surface->unique_id); free (source_surface); } static void _cairo_svg_paint_init_key (cairo_svg_paint_t *paint) { paint->base.hash = paint->source_id; } static cairo_bool_t _cairo_svg_paint_equal (const void *key_a, const void *key_b) { const cairo_svg_paint_t *a = key_a; const cairo_svg_paint_t *b = key_b; return a->source_id == b->source_id; } static void _cairo_svg_paint_pluck (void *entry, void *closure) { cairo_svg_paint_t *paint = entry; cairo_hash_table_t *patterns = closure; _cairo_hash_table_remove (patterns, &paint->base); _cairo_array_fini (&paint->paint_elements); free (paint); } static void _cairo_svg_paint_box_add_padding (cairo_box_double_t *box) { double width = box->p2.x - box->p1.x; double height = box->p2.y - box->p1.y; box->p1.x -= width / 10.0; box->p1.y -= height / 10.0; box->p2.x += width / 10.0; box->p2.y += height / 10.0; } enum cairo_svg_stream_element_type { CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT, CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT, }; enum cairo_svg_stream_paint_dependent_element_type { CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION, }; typedef struct _cairo_svg_stream_element { enum cairo_svg_stream_element_type type; union { struct { cairo_output_stream_t *output_stream; } text; struct { unsigned int source_id; enum cairo_svg_stream_paint_dependent_element_type type; } paint_dependent; }; } cairo_svg_stream_element_t; typedef struct _cairo_svg_stream { cairo_status_t status; cairo_array_t elements; } cairo_svg_stream_t; static cairo_svg_stream_t _cairo_svg_stream_create () { cairo_svg_stream_t svg_stream; svg_stream.status = CAIRO_STATUS_SUCCESS; _cairo_array_init (&svg_stream.elements, sizeof (cairo_svg_stream_element_t)); return svg_stream; } static void _cairo_svg_stream_write (cairo_svg_stream_t *svg_stream, const void *data, size_t length) { cairo_status_t status; cairo_svg_stream_element_t *last_element = NULL; if (svg_stream->elements.num_elements > 0) { last_element = _cairo_array_index (&svg_stream->elements, svg_stream->elements.num_elements - 1); } if (last_element == NULL || last_element->type != CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) { cairo_svg_stream_element_t element; element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT; element.text.output_stream = _cairo_memory_stream_create(); status = _cairo_array_append (&svg_stream->elements, &element); if (unlikely (status)) { if (svg_stream->status == CAIRO_STATUS_SUCCESS) { svg_stream->status = status; } return; } last_element = _cairo_array_index (&svg_stream->elements, svg_stream->elements.num_elements - 1); } _cairo_output_stream_write (last_element->text.output_stream, data, length); } static void CAIRO_PRINTF_FORMAT (2, 0) _cairo_svg_stream_printf (cairo_svg_stream_t *svg_stream, const char *fmt, ...) { cairo_status_t status; cairo_svg_stream_element_t *last_element = NULL; if (svg_stream->elements.num_elements > 0) { last_element = _cairo_array_index (&svg_stream->elements, svg_stream->elements.num_elements - 1); } if (last_element == NULL || last_element->type != CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) { cairo_svg_stream_element_t element; element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT; element.text.output_stream = _cairo_memory_stream_create(); status = _cairo_array_append (&svg_stream->elements, &element); if (unlikely (status)) { if (svg_stream->status == CAIRO_STATUS_SUCCESS) { svg_stream->status = status; } return; } last_element = _cairo_array_index (&svg_stream->elements, svg_stream->elements.num_elements - 1); } va_list ap; va_start (ap, fmt); _cairo_output_stream_vprintf (last_element->text.output_stream, fmt, ap); va_end (ap); } static void _cairo_svg_stream_append_paint_dependent (cairo_svg_stream_t *svg_stream, unsigned int source_id, enum cairo_svg_stream_paint_dependent_element_type type) { cairo_status_t status; cairo_svg_stream_element_t element; element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT; element.paint_dependent.source_id = source_id; element.paint_dependent.type = type; status = _cairo_array_append (&svg_stream->elements, &element); if (svg_stream->status == CAIRO_STATUS_SUCCESS) { svg_stream->status = status; } } static void _cairo_svg_stream_copy (cairo_svg_stream_t *from, cairo_svg_stream_t *to) { cairo_status_t status; if (unlikely (from->status)) { if (to->status == CAIRO_STATUS_SUCCESS) { to->status = from->status; } return; } for (unsigned int i = 0; i < from->elements.num_elements; i++) { cairo_svg_stream_element_t *element = _cairo_array_index (&from->elements, i); cairo_svg_stream_element_t element_copy = *element; if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) { element_copy.text.output_stream = _cairo_memory_stream_create (); _cairo_memory_stream_copy (element->text.output_stream, element_copy.text.output_stream); if (to->status == CAIRO_STATUS_SUCCESS) { to->status = element->text.output_stream->status; } } status = _cairo_array_append (&to->elements, &element_copy); if (unlikely (status)) { if (to->status == CAIRO_STATUS_SUCCESS) { to->status = status; } return; } } } static void _cairo_svg_stream_copy_to_output_stream (cairo_svg_stream_t *from, cairo_output_stream_t *to, cairo_hash_table_t *paints) { if (unlikely (from->status)) { if (to->status == CAIRO_STATUS_SUCCESS) { to->status = from->status; } return; } for (unsigned int i = 0; i < from->elements.num_elements; i++) { cairo_svg_stream_element_t *element = _cairo_array_index (&from->elements, i); if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) { _cairo_memory_stream_copy (element->text.output_stream, to); } if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT) { cairo_svg_paint_t paint_key; paint_key.source_id = element->paint_dependent.source_id; _cairo_svg_paint_init_key (&paint_key); cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (paints, &paint_key.base); assert (found_paint_entry); switch (element->paint_dependent.type) { case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE: _cairo_output_stream_printf (to, " x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\"", found_paint_entry->box.p1.x, found_paint_entry->box.p1.y, found_paint_entry->box.p2.x - found_paint_entry->box.p1.x, found_paint_entry->box.p2.y - found_paint_entry->box.p1.y); break; case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN: _cairo_output_stream_printf (to, " x=\"0\" y=\"0\" width=\"%f\" height=\"%f\"", found_paint_entry->box.p2.x - found_paint_entry->box.p1.x, found_paint_entry->box.p2.y - found_paint_entry->box.p1.y); break; case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION: _cairo_output_stream_printf (to, " transform=\"translate(%f, %f)\"", found_paint_entry->box.p1.x, found_paint_entry->box.p1.y); break; case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION: _cairo_output_stream_printf (to, " transform=\"translate(%f, %f)\"", -found_paint_entry->box.p1.x, -found_paint_entry->box.p1.y); break; } } } } static cairo_status_t _cairo_svg_stream_destroy (cairo_svg_stream_t *svg_stream) { cairo_status_t status = svg_stream->status; for (unsigned int i = 0; i < svg_stream->elements.num_elements; i++) { cairo_svg_stream_element_t *element = _cairo_array_index (&svg_stream->elements, i); if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) { cairo_status_t element_status = _cairo_output_stream_destroy (element->text.output_stream); if (status == CAIRO_STATUS_SUCCESS) { status = element_status; } } } _cairo_array_fini (&svg_stream->elements); return status; } /** * CAIRO_HAS_SVG_SURFACE: * * Defined if the SVG surface backend is available. * This macro can be used to conditionally compile backend-specific code. * * Since: 1.2 **/ static const unsigned int invalid_pattern_id = -1; static const cairo_svg_version_t _cairo_svg_versions[] = { CAIRO_SVG_VERSION_1_1, CAIRO_SVG_VERSION_1_2 }; #define CAIRO_SVG_VERSION_LAST ARRAY_LENGTH (_cairo_svg_versions) static const char *_cairo_svg_supported_mime_types[] = { CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_PNG, CAIRO_MIME_TYPE_UNIQUE_ID, CAIRO_MIME_TYPE_URI, NULL }; static void _cairo_svg_surface_emit_path (cairo_svg_stream_t *output, const cairo_path_fixed_t *path, const cairo_matrix_t *ctm_inverse); static const char * _cairo_svg_version_strings[CAIRO_SVG_VERSION_LAST] = { "SVG 1.1", "SVG 1.2" }; static const char * _cairo_svg_unit_strings[] = { "", "em", "ex", "px", "in", "cm", "mm", "pt", "pc", "%" }; enum cairo_svg_filter { CAIRO_SVG_FILTER_REMOVE_COLOR, CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA, CAIRO_SVG_FILTER_COLOR_TO_ALPHA, CAIRO_SVG_FILTER_LAST_STATIC_FILTER, CAIRO_SVG_FILTER_OVER, CAIRO_SVG_FILTER_IN, CAIRO_SVG_FILTER_OUT, CAIRO_SVG_FILTER_ATOP, CAIRO_SVG_FILTER_XOR, CAIRO_SVG_FILTER_ADD, CAIRO_SVG_FILTER_MULTIPLY, CAIRO_SVG_FILTER_SCREEN, CAIRO_SVG_FILTER_OVERLAY, CAIRO_SVG_FILTER_DARKEN, CAIRO_SVG_FILTER_LIGHTEN, CAIRO_SVG_FILTER_COLOR_DODGE, CAIRO_SVG_FILTER_COLOR_BURN, CAIRO_SVG_FILTER_HARD_LIGHT, CAIRO_SVG_FILTER_SOFT_LIGHT, CAIRO_SVG_FILTER_DIFFERENCE, CAIRO_SVG_FILTER_EXCLUSION, CAIRO_SVG_FILTER_HUE, CAIRO_SVG_FILTER_SATURATION, CAIRO_SVG_FILTER_COLOR, CAIRO_SVG_FILTER_LUMINOSITY, }; typedef struct _cairo_svg_page { cairo_svg_stream_t xml_node; } cairo_svg_page_t; typedef struct _cairo_svg_document { cairo_output_stream_t *output_stream; unsigned long refcount; cairo_surface_t *owner; cairo_bool_t finished; double width; double height; cairo_svg_unit_t unit; cairo_svg_stream_t xml_node_defs; cairo_svg_stream_t xml_node_glyphs; cairo_svg_stream_t xml_node_filters; unsigned int linear_pattern_id; unsigned int radial_pattern_id; unsigned int pattern_id; unsigned int clip_id; unsigned int mask_id; unsigned int compositing_group_id; unsigned int filter_id; cairo_bool_t filters_emitted[CAIRO_SVG_FILTER_LAST_STATIC_FILTER]; cairo_svg_version_t svg_version; cairo_scaled_font_subsets_t *font_subsets; cairo_hash_table_t *paints; } cairo_svg_document_t; // Must be compatible with the struct _cairo_svg_surface_start. typedef struct _cairo_svg_surface { cairo_surface_t base; cairo_bool_t force_fallbacks; unsigned int source_id; unsigned int depth; double width; double height; cairo_bool_t surface_bounded; cairo_svg_document_t *document; cairo_svg_stream_t xml_node; cairo_array_t page_set; cairo_hash_table_t *source_surfaces; cairo_surface_clipper_t clipper; cairo_svg_stream_t *current_clipper_stream; unsigned int clip_level; cairo_bool_t transitive_paint_used; cairo_paginated_mode_t paginated_mode; } cairo_svg_surface_t; static cairo_status_t _cairo_svg_document_create (cairo_output_stream_t *stream, double width, double height, cairo_svg_version_t version, cairo_svg_document_t **document_out); static cairo_status_t _cairo_svg_document_destroy (cairo_svg_document_t *document); static cairo_status_t _cairo_svg_document_finish (cairo_svg_document_t *document); static cairo_svg_document_t * _cairo_svg_document_reference (cairo_svg_document_t *document); static cairo_surface_t * _cairo_svg_surface_create_for_document (cairo_svg_document_t *document, cairo_content_t content, double width, double height, cairo_bool_t bounded); 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); static cairo_status_t _cairo_svg_surface_emit_composite_pattern (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, cairo_surface_pattern_t *pattern, unsigned int pattern_id, const cairo_matrix_t *parent_matrix); static cairo_status_t _cairo_svg_surface_emit_paint (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, cairo_bool_t at_origin); static const cairo_surface_backend_t cairo_svg_surface_backend; static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend; /** * cairo_svg_surface_create_for_stream: * @write_func: a #cairo_write_func_t to accept the output data, may be %NULL * to indicate a no-op @write_func. With a no-op @write_func, * the surface may be queried or used as a source without * generating any temporary files. * @closure: the closure argument for @write_func * @width_in_points: width of the surface, in points (1 point == 1/72.0 inch) * @height_in_points: height of the surface, in points (1 point == 1/72.0 inch) * * Creates a SVG surface of the specified size in points to be written * incrementally to the stream represented by @write_func and @closure. * * Return value: a pointer to the newly created surface. The caller * owns the surface and should call cairo_surface_destroy() when done * with it. * * This function always returns a valid pointer, but it will return a * pointer to a "nil" surface if an error such as out of memory * occurs. You can use cairo_surface_status() to check for this. * * Since: 1.2 **/ cairo_surface_t * cairo_svg_surface_create_for_stream (cairo_write_func_t write_func, void *closure, double width, double height) { cairo_output_stream_t *stream; stream = _cairo_output_stream_create (write_func, NULL, closure); if (_cairo_output_stream_get_status (stream)) return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream)); return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1); } /** * cairo_svg_surface_create: * @filename: a filename for the SVG output (must be writable), %NULL may be * used to specify no output. This will generate a SVG surface that * may be queried and used as a source, without generating a * temporary file. * @width_in_points: width of the surface, in points (1 point == 1/72.0 inch) * @height_in_points: height of the surface, in points (1 point == 1/72.0 inch) * * Creates a SVG surface of the specified size in points to be written * to @filename. * * The SVG surface backend recognizes the following MIME types for the * data attached to a surface (see cairo_surface_set_mime_data()) when * it is used as a source pattern for drawing on this surface: * %CAIRO_MIME_TYPE_JPEG, %CAIRO_MIME_TYPE_PNG, * %CAIRO_MIME_TYPE_URI. If any of them is specified, the SVG backend * emits a href with the content of MIME data instead of a surface * snapshot (PNG, Base64-encoded) in the corresponding image tag. * * The unofficial MIME type %CAIRO_MIME_TYPE_URI is examined * first. If present, the URI is emitted as is: assuring the * correctness of URI is left to the client code. * * If %CAIRO_MIME_TYPE_URI is not present, but %CAIRO_MIME_TYPE_JPEG * or %CAIRO_MIME_TYPE_PNG is specified, the corresponding data is * Base64-encoded and emitted. * * If %CAIRO_MIME_TYPE_UNIQUE_ID is present, all surfaces with the same * unique identifier will only be embedded once. * * Return value: a pointer to the newly created surface. The caller * owns the surface and should call cairo_surface_destroy() when done * with it. * * This function always returns a valid pointer, but it will return a * pointer to a "nil" surface if an error such as out of memory * occurs. You can use cairo_surface_status() to check for this. * * Since: 1.2 **/ cairo_surface_t * cairo_svg_surface_create (const char *filename, double width, double height) { cairo_output_stream_t *stream; stream = _cairo_output_stream_create_for_filename (filename); if (_cairo_output_stream_get_status (stream)) return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream)); return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1); } static cairo_bool_t _cairo_surface_is_svg (cairo_surface_t *surface) { return surface->backend == &cairo_svg_surface_backend; } /* If the abstract_surface is a paginated surface, and that paginated * surface's target is a svg_surface, then set svg_surface to that * target. Otherwise return FALSE. */ static cairo_bool_t _extract_svg_surface (cairo_surface_t *surface, cairo_svg_surface_t **svg_surface) { cairo_surface_t *target; if (surface->status) return FALSE; if (surface->finished) { (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED)); return FALSE; } if (!_cairo_surface_is_paginated (surface)) { (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); return FALSE; } target = _cairo_paginated_surface_get_target (surface); if (target->status) { (void) _cairo_surface_set_error (surface, target->status); return FALSE; } if (target->finished) { (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED)); return FALSE; } if (!_cairo_surface_is_svg (target)) { (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); return FALSE; } *svg_surface = (cairo_svg_surface_t *) target; return TRUE; } /** * cairo_svg_surface_restrict_to_version: * @surface: a SVG #cairo_surface_t * @version: SVG version * * Restricts the generated SVG file to @version. See cairo_svg_get_versions() * for a list of available version values that can be used here. * * This function should only be called before any drawing operations * have been performed on the given surface. The simplest way to do * this is to call this function immediately after creating the * surface. * * Since: 1.2 **/ void cairo_svg_surface_restrict_to_version (cairo_surface_t *abstract_surface, cairo_svg_version_t version) { cairo_svg_surface_t *surface; if (! _extract_svg_surface (abstract_surface, &surface)) return; if (version < CAIRO_SVG_VERSION_LAST) surface->document->svg_version = version; } /** * cairo_svg_get_versions: * @versions: supported version list * @num_versions: list length * * Used to retrieve the list of supported versions. See * cairo_svg_surface_restrict_to_version(). * * Since: 1.2 **/ void cairo_svg_get_versions (cairo_svg_version_t const **versions, int *num_versions) { if (versions != NULL) *versions = _cairo_svg_versions; if (num_versions != NULL) *num_versions = CAIRO_SVG_VERSION_LAST; } /** * cairo_svg_version_to_string: * @version: a version id * * Get the string representation of the given @version id. This function * will return %NULL if @version isn't valid. See cairo_svg_get_versions() * for a way to get the list of valid version ids. * * Return value: the string associated to given version. * * Since: 1.2 **/ const char * cairo_svg_version_to_string (cairo_svg_version_t version) { if (version >= CAIRO_SVG_VERSION_LAST) return NULL; return _cairo_svg_version_strings[version]; } /** * cairo_svg_surface_set_document_unit: * @surface: a SVG #cairo_surface_t * @unit: SVG unit * * Use the specified unit for the width and height of the generated SVG file. * See #cairo_svg_unit_t for a list of available unit values that can be used * here. * * This function can be called at any time before generating the SVG file. * * However to minimize the risk of ambiguities it's recommended to call it * before any drawing operations have been performed on the given surface, to * make it clearer what the unit used in the drawing operations is. * * The simplest way to do this is to call this function immediately after * creating the SVG surface. * * Note if this function is never called, the default unit for SVG documents * generated by cairo will be user unit. * * Since: 1.16 **/ void cairo_svg_surface_set_document_unit (cairo_surface_t *abstract_surface, cairo_svg_unit_t unit) { cairo_svg_surface_t *surface; if (! _extract_svg_surface (abstract_surface, &surface)) return; if (unit <= CAIRO_SVG_UNIT_PERCENT) surface->document->unit = unit; } /** * cairo_svg_surface_get_document_unit: * @surface: a SVG #cairo_surface_t * * Get the unit of the SVG surface. * * If the surface passed as an argument is not a SVG surface, the function * sets the error status to CAIRO_STATUS_SURFACE_TYPE_MISMATCH and returns * CAIRO_SVG_UNIT_USER. * * Return value: the SVG unit of the SVG surface. * * Since: 1.16 **/ cairo_svg_unit_t cairo_svg_surface_get_document_unit (cairo_surface_t *abstract_surface) { cairo_svg_surface_t *surface; if (! _extract_svg_surface (abstract_surface, &surface)) { _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH); return CAIRO_SVG_UNIT_USER; } return surface->document->unit; } static void _cairo_svg_paint_compute (cairo_svg_document_t *document, cairo_svg_paint_t *paint) { for (unsigned int i = 0; i < paint->paint_elements.num_elements; i++) { cairo_svg_paint_element_t *paint_element = _cairo_array_index (&paint->paint_elements, i); cairo_svg_paint_t paint_key; paint_key.source_id = paint_element->source_id; _cairo_svg_paint_init_key (&paint_key); cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (document->paints, &paint_key.base); assert (found_paint_entry); _cairo_svg_paint_compute (document, found_paint_entry); cairo_box_double_t box = found_paint_entry->box; _cairo_matrix_transform_bounding_box (&paint_element->matrix, &box.p1.x, &box.p1.y, &box.p2.x, &box.p2.y, NULL); _cairo_svg_paint_box_add_padding (&box); if (i == 0) { paint->box = box; } else { paint->box.p1.x = MIN (paint->box.p1.x, box.p1.x); paint->box.p1.y = MIN (paint->box.p1.y, box.p1.y); paint->box.p2.x = MAX (paint->box.p2.x, box.p2.x); paint->box.p2.y = MAX (paint->box.p2.y, box.p2.y); } } _cairo_array_truncate (&paint->paint_elements, 0); } static void _cairo_svg_paint_compute_func (void *entry, void *closure) { cairo_svg_paint_t *paint = entry; cairo_svg_document_t *document = closure; _cairo_svg_paint_compute (document, paint); } static cairo_status_t _cairo_svg_surface_add_source_surface (cairo_svg_surface_t *surface, cairo_surface_t *source_surface, cairo_bool_t *is_new, cairo_svg_source_surface_t **result_source_surface) { cairo_status_t status; cairo_svg_source_surface_t source_surface_key; source_surface_key.id = source_surface->unique_id; cairo_surface_get_mime_data (source_surface, CAIRO_MIME_TYPE_UNIQUE_ID, (const unsigned char **) &source_surface_key.unique_id, &source_surface_key.unique_id_length); _cairo_svg_source_surface_init_key (&source_surface_key); cairo_svg_source_surface_t *found_source_surface_entry = _cairo_hash_table_lookup (surface->source_surfaces, &source_surface_key.base); if (found_source_surface_entry) { *is_new = FALSE; *result_source_surface = found_source_surface_entry; return CAIRO_STATUS_SUCCESS; } unsigned char *unique_id = NULL; unsigned long unique_id_length = 0; if (source_surface_key.unique_id && source_surface_key.unique_id_length > 0) { unique_id = _cairo_malloc (source_surface_key.unique_id_length); if (unique_id == NULL) { return _cairo_error (CAIRO_STATUS_NO_MEMORY); } unique_id_length = source_surface_key.unique_id_length; memcpy (unique_id, source_surface_key.unique_id, unique_id_length); } else { unique_id = NULL; unique_id_length = 0; } cairo_svg_source_surface_t *source_surface_entry = malloc (sizeof (cairo_svg_source_surface_t)); if (source_surface_entry == NULL) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto fail; } source_surface_entry->id = source_surface_key.id; source_surface_entry->unique_id_length = unique_id_length; source_surface_entry->unique_id = unique_id; _cairo_svg_source_surface_init_key (source_surface_entry); status = _cairo_hash_table_insert (surface->source_surfaces, &source_surface_entry->base); if (unlikely (status)) { goto fail; } *is_new = TRUE; *result_source_surface = source_surface_entry; return CAIRO_STATUS_SUCCESS; fail: free (unique_id); free (source_surface_entry); return status; } static cairo_bool_t _cairo_svg_surface_cliprect_covers_surface (cairo_svg_surface_t *surface, cairo_path_fixed_t *path) { cairo_box_t box; return surface->surface_bounded && _cairo_path_fixed_is_box (path, &box) && box.p1.x <= 0 && box.p1.y <= 0 && _cairo_fixed_to_double (box.p2.x) >= surface->width && _cairo_fixed_to_double (box.p2.y) >= surface->height; } 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) { cairo_svg_surface_t *surface = cairo_container_of (clipper, cairo_svg_surface_t, clipper); cairo_svg_document_t *document = surface->document; if (path == NULL) { for (unsigned int i = 0; i < surface->clip_level; i++) { _cairo_svg_stream_printf (surface->current_clipper_stream, "\n"); } surface->clip_level = 0; return CAIRO_STATUS_SUCCESS; } /* skip trivial whole-page clips */ if (_cairo_svg_surface_cliprect_covers_surface (surface, path)) { return CAIRO_STATUS_SUCCESS; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n", document->clip_id); _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, path, NULL); _cairo_svg_stream_printf (&document->xml_node_defs, "/>\n"); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (surface->current_clipper_stream, "\n", document->clip_id); document->clip_id++; surface->clip_level++; return CAIRO_STATUS_SUCCESS; } static void _cairo_svg_surface_reset_clip (cairo_svg_surface_t *surface) { _cairo_surface_clipper_reset (&surface->clipper); if (surface->current_clipper_stream != NULL) { for (unsigned int i = 0; i < surface->clip_level; i++) { _cairo_svg_stream_printf (surface->current_clipper_stream, "\n"); } } surface->clip_level = 0; } static cairo_status_t _cairo_svg_surface_set_clip (cairo_svg_surface_t *surface, cairo_svg_stream_t *clipper_stream, const cairo_clip_t *clip) { if (surface->current_clipper_stream != clipper_stream) { _cairo_svg_surface_reset_clip (surface); surface->current_clipper_stream = clipper_stream; } return _cairo_surface_clipper_set_clip (&surface->clipper, clip); } static cairo_surface_t * _cairo_svg_surface_create_for_document (cairo_svg_document_t *document, cairo_content_t content, double width, double height, cairo_bool_t bounded) { cairo_svg_surface_t *surface; cairo_surface_t *paginated; cairo_status_t status; surface = _cairo_malloc (sizeof (cairo_svg_surface_t)); if (unlikely (surface == NULL)) { return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } _cairo_surface_init (&surface->base, &cairo_svg_surface_backend, NULL, /* device */ content, TRUE); /* is_vector */ surface->source_id = surface->base.unique_id; surface->depth = 0; surface->width = width; surface->height = height; surface->surface_bounded = bounded; surface->document = _cairo_svg_document_reference (document); surface->xml_node = _cairo_svg_stream_create (); _cairo_array_init (&surface->page_set, sizeof (cairo_svg_page_t)); surface->source_surfaces = _cairo_hash_table_create (_cairo_svg_source_surface_equal); if (unlikely (surface->source_surfaces == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto CLEANUP; } _cairo_surface_clipper_init (&surface->clipper, _cairo_svg_surface_clipper_intersect_clip_path); surface->current_clipper_stream = NULL; surface->clip_level = 0; surface->transitive_paint_used = FALSE; surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE; surface->force_fallbacks = FALSE; paginated = _cairo_paginated_surface_create (&surface->base, surface->base.content, &cairo_svg_surface_paginated_backend); status = paginated->status; if (status == CAIRO_STATUS_SUCCESS) { /* paginated keeps the only reference to surface now, drop ours */ cairo_surface_destroy (&surface->base); return paginated; } /* ignore status as we are on the error path */ CLEANUP: (void) _cairo_svg_stream_destroy (&surface->xml_node); (void) _cairo_svg_document_destroy (document); free (surface); return _cairo_surface_create_in_error (status); } 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) { cairo_svg_document_t *document; cairo_surface_t *surface; cairo_status_t status; status = _cairo_svg_document_create (stream, width, height, version, &document); if (unlikely (status)) { surface = _cairo_surface_create_in_error (status); /* consume the output stream on behalf of caller */ status = _cairo_output_stream_destroy (stream); return surface; } surface = _cairo_svg_surface_create_for_document (document, CAIRO_CONTENT_COLOR_ALPHA, width, height, TRUE); if (surface->status) { return surface; } document->owner = surface; status = _cairo_svg_document_destroy (document); /* the ref count should be 2 at this point */ assert (status == CAIRO_STATUS_SUCCESS); return surface; } static cairo_svg_page_t * _cairo_svg_surface_store_page (cairo_svg_surface_t *surface) { _cairo_svg_surface_reset_clip (surface); cairo_svg_page_t page; page.xml_node = surface->xml_node; if (_cairo_array_append (&surface->page_set, &page)) { return NULL; } surface->xml_node = _cairo_svg_stream_create (); return _cairo_array_index (&surface->page_set, surface->page_set.num_elements - 1); } static cairo_int_status_t _cairo_svg_surface_copy_page (void *abstract_surface) { cairo_svg_surface_t *surface = abstract_surface; cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface); if (unlikely (page == NULL)) { return _cairo_error (CAIRO_STATUS_NO_MEMORY); } _cairo_svg_stream_copy (&page->xml_node, &surface->xml_node); return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_svg_surface_show_page (void *abstract_surface) { cairo_svg_surface_t *surface = abstract_surface; cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface); if (unlikely (page == NULL)) { return _cairo_error (CAIRO_STATUS_NO_MEMORY); } return CAIRO_STATUS_SUCCESS; } static void _cairo_svg_surface_emit_transform (cairo_svg_stream_t *output, char const *attribute_name, const cairo_matrix_t *object_matrix, const cairo_matrix_t *parent_matrix) { cairo_matrix_t matrix = *object_matrix; if (parent_matrix != NULL) { cairo_matrix_multiply (&matrix, &matrix, parent_matrix); } if (!_cairo_matrix_is_identity (&matrix)) { _cairo_svg_stream_printf (output, " %s=\"matrix(%f, %f, %f, %f, %f, %f)\"", attribute_name, matrix.xx, matrix.yx, matrix.xy, matrix.yy, matrix.x0, matrix.y0); } } typedef struct { cairo_svg_stream_t *output; const cairo_matrix_t *ctm_inverse; } svg_path_info_t; static cairo_status_t _cairo_svg_path_move_to (void *closure, const cairo_point_t *point) { svg_path_info_t *info = closure; double x = _cairo_fixed_to_double (point->x); double y = _cairo_fixed_to_double (point->y); if (info->ctm_inverse) cairo_matrix_transform_point (info->ctm_inverse, &x, &y); _cairo_svg_stream_printf (info->output, "M %f %f ", x, y); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_path_line_to (void *closure, const cairo_point_t *point) { svg_path_info_t *info = closure; double x = _cairo_fixed_to_double (point->x); double y = _cairo_fixed_to_double (point->y); if (info->ctm_inverse) cairo_matrix_transform_point (info->ctm_inverse, &x, &y); _cairo_svg_stream_printf (info->output, "L %f %f ", x, y); return CAIRO_STATUS_SUCCESS; } 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) { svg_path_info_t *info = closure; double bx = _cairo_fixed_to_double (b->x); double by = _cairo_fixed_to_double (b->y); double cx = _cairo_fixed_to_double (c->x); double cy = _cairo_fixed_to_double (c->y); double dx = _cairo_fixed_to_double (d->x); double dy = _cairo_fixed_to_double (d->y); if (info->ctm_inverse) { cairo_matrix_transform_point (info->ctm_inverse, &bx, &by); cairo_matrix_transform_point (info->ctm_inverse, &cx, &cy); cairo_matrix_transform_point (info->ctm_inverse, &dx, &dy); } _cairo_svg_stream_printf (info->output, "C %f %f %f %f %f %f ", bx, by, cx, cy, dx, dy); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_path_close_path (void *closure) { svg_path_info_t *info = closure; _cairo_svg_stream_printf (info->output, "Z "); return CAIRO_STATUS_SUCCESS; } static void _cairo_svg_surface_emit_path (cairo_svg_stream_t *output, const cairo_path_fixed_t *path, const cairo_matrix_t *ctm_inverse) { cairo_status_t status; svg_path_info_t info; _cairo_svg_stream_printf (output, " d=\""); info.output = output; info.ctm_inverse = ctm_inverse; status = _cairo_path_fixed_interpret (path, _cairo_svg_path_move_to, _cairo_svg_path_line_to, _cairo_svg_path_curve_to, _cairo_svg_path_close_path, &info); assert (status == CAIRO_STATUS_SUCCESS); _cairo_svg_stream_printf (output, "\""); } 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) { cairo_scaled_glyph_t *scaled_glyph; cairo_int_status_t status; status = _cairo_scaled_glyph_lookup (scaled_font, glyph_index, CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_PATH, NULL, /* foreground color */ &scaled_glyph); if (unlikely (status)) { return status; } if (_cairo_path_fixed_size (scaled_glyph->path) != 0) { _cairo_svg_stream_printf (&document->xml_node_glyphs, "xml_node_glyphs, scaled_glyph->path, NULL); _cairo_svg_stream_printf (&document->xml_node_glyphs, "/>\n"); } return status; } 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) { cairo_status_t status; cairo_scaled_glyph_t *scaled_glyph; status = _cairo_scaled_glyph_lookup (scaled_font, glyph_index, CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_SURFACE, NULL, /* foreground color */ &scaled_glyph); if (unlikely (status)) { return status; } cairo_bool_t use_recording_surface = (scaled_glyph->has_info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) != 0; cairo_matrix_t glyph_matrix = scaled_glyph->surface->base.device_transform_inverse; cairo_image_surface_t *glyph_image_surface = scaled_glyph->surface; // Attempt to recognize a common pattern for a bitmap font and extract the original glyph image from it cairo_surface_t *extracted_surface; cairo_image_surface_t *extracted_image = NULL; void *extracted_image_extra; if (use_recording_surface) { cairo_recording_surface_t *recording_surface = (cairo_recording_surface_t *) scaled_glyph->recording_surface; if (recording_surface->commands.num_elements == 1) { cairo_command_t *command = *((cairo_command_t **) _cairo_array_index (&recording_surface->commands, 0)); if (command->header.type == CAIRO_COMMAND_MASK && command->header.op == CAIRO_OPERATOR_OVER && command->header.clip == NULL && command->mask.source.base.type == CAIRO_PATTERN_TYPE_SOLID && _cairo_color_equal (&command->mask.source.solid.color, _cairo_stock_color (CAIRO_STOCK_BLACK)) && command->mask.mask.base.extend == CAIRO_EXTEND_NONE && command->mask.mask.base.type == CAIRO_PATTERN_TYPE_SURFACE && command->mask.mask.surface.surface->type == CAIRO_SURFACE_TYPE_IMAGE) { extracted_surface = command->mask.mask.surface.surface; if (_cairo_surface_acquire_source_image (extracted_surface, &extracted_image, &extracted_image_extra) == CAIRO_STATUS_SUCCESS) { if (extracted_image->format == CAIRO_FORMAT_A1 || extracted_image->format == CAIRO_FORMAT_A8) { use_recording_surface = FALSE; glyph_image_surface = extracted_image; glyph_matrix = command->mask.mask.base.matrix; status = cairo_matrix_invert (&glyph_matrix); assert (status == CAIRO_STATUS_SUCCESS); } } } } } cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document, CAIRO_CONTENT_COLOR_ALPHA, 0, 0, FALSE); cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface); status = paginated_surface->status; if (unlikely (status)) { goto cleanup; } unsigned int source_id = svg_surface->base.unique_id; cairo_surface_set_fallback_resolution (paginated_surface, document->owner->x_fallback_resolution, document->owner->y_fallback_resolution); cairo_svg_stream_t temporary_stream = _cairo_svg_stream_create (); unsigned int mask_id = document->mask_id++; _cairo_svg_stream_printf (&temporary_stream, "\n", mask_id); cairo_pattern_t *pattern = cairo_pattern_create_for_surface (use_recording_surface ? scaled_glyph->recording_surface : &glyph_image_surface->base); _cairo_svg_surface_emit_composite_pattern (&temporary_stream, svg_surface, (cairo_surface_pattern_t *) pattern, invalid_pattern_id, NULL); cairo_pattern_destroy (pattern); _cairo_svg_stream_printf (&temporary_stream, "\n"); _cairo_svg_stream_copy (&temporary_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (&temporary_stream); if (unlikely (status)) { goto cleanup; } svg_surface->transitive_paint_used = TRUE; _cairo_svg_stream_printf (&document->xml_node_glyphs, "xml_node_glyphs, source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE); _cairo_svg_stream_printf (&document->xml_node_glyphs, " mask=\"url(#mask-%d)\"", mask_id); if (!use_recording_surface) { _cairo_svg_surface_emit_transform (&document->xml_node_glyphs, "transform", &glyph_matrix, NULL); } _cairo_svg_stream_printf (&document->xml_node_glyphs, "/>\n"); cairo_svg_paint_t *paint_entry = malloc (sizeof (cairo_svg_paint_t)); if (paint_entry == NULL) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto cleanup; } paint_entry->source_id = source_id; paint_entry->box.p1.x = 0; paint_entry->box.p1.y = 0; paint_entry->box.p2.x = glyph_image_surface->width; paint_entry->box.p2.y = glyph_image_surface->height; if (use_recording_surface) { _cairo_matrix_transform_bounding_box (&glyph_matrix, &paint_entry->box.p1.x, &paint_entry->box.p1.y, &paint_entry->box.p2.x, &paint_entry->box.p2.y, NULL); } _cairo_svg_paint_box_add_padding (&paint_entry->box); _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t)); _cairo_svg_paint_init_key (paint_entry); status = _cairo_hash_table_insert (document->paints, &paint_entry->base); if (unlikely (status)) { goto cleanup; } cleanup: if (status == CAIRO_STATUS_SUCCESS) { status = cairo_surface_status (paginated_surface); } cairo_surface_destroy (paginated_surface); if (extracted_image != NULL) { _cairo_surface_release_source_image (extracted_surface, extracted_image, extracted_image_extra); } return status; } static cairo_int_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) { cairo_int_status_t status; _cairo_svg_stream_printf (&document->xml_node_glyphs, "\n", font_id, subset_glyph_index); status = _cairo_svg_document_emit_outline_glyph_data (document, scaled_font, scaled_font_glyph_index); if (status == CAIRO_INT_STATUS_UNSUPPORTED) status = _cairo_svg_document_emit_bitmap_glyph_data (document, scaled_font, scaled_font_glyph_index); if (unlikely (status)) return status; _cairo_svg_stream_printf (&document->xml_node_glyphs, "\n"); return CAIRO_INT_STATUS_SUCCESS; } static cairo_int_status_t _cairo_svg_document_emit_font_subset (cairo_scaled_font_subset_t *font_subset, void *closure) { cairo_svg_document_t *document = closure; cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS; unsigned int i; _cairo_scaled_font_freeze_cache (font_subset->scaled_font); for (i = 0; i < font_subset->num_glyphs; i++) { status = _cairo_svg_document_emit_glyph (document, font_subset->scaled_font, font_subset->glyphs[i], font_subset->font_id, i); if (unlikely (status)) break; } _cairo_scaled_font_thaw_cache (font_subset->scaled_font); return status; } static cairo_status_t _cairo_svg_document_emit_font_subsets (cairo_svg_document_t *document) { cairo_status_t status; status = _cairo_scaled_font_subsets_foreach_scaled (document->font_subsets, _cairo_svg_document_emit_font_subset, document); _cairo_scaled_font_subsets_destroy (document->font_subsets); document->font_subsets = NULL; return status; } static cairo_bool_t _cairo_svg_surface_are_operation_and_pattern_supported (cairo_svg_surface_t *surface, cairo_operator_t op, const cairo_pattern_t *pattern) { if (surface->force_fallbacks) { return FALSE; } if (op == CAIRO_OPERATOR_SATURATE) { return FALSE; } /* SVG 1.1 does not support these operators. We already have code for them for SVG 2 * that can be enabled when SVG 2 becomes widespread. */ if (op == CAIRO_OPERATOR_OVERLAY || op == CAIRO_OPERATOR_COLOR_DODGE || op == CAIRO_OPERATOR_COLOR_BURN || op == CAIRO_OPERATOR_HARD_LIGHT || op == CAIRO_OPERATOR_SOFT_LIGHT || op == CAIRO_OPERATOR_DIFFERENCE || op == CAIRO_OPERATOR_EXCLUSION || op == CAIRO_OPERATOR_HSL_HUE || op == CAIRO_OPERATOR_HSL_SATURATION || op == CAIRO_OPERATOR_HSL_COLOR || op == CAIRO_OPERATOR_HSL_LUMINOSITY) { return FALSE; } if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) { /* Do not cause stack overflow because of too deep or infinite recording surfaces. */ if (((cairo_surface_pattern_t *) pattern)->surface->type == CAIRO_SURFACE_TYPE_RECORDING && surface->depth > 1000) { return FALSE; } /* SVG doesn't support extends reflect and pad for surface pattern. */ if (pattern->extend != CAIRO_EXTEND_NONE && pattern->extend != CAIRO_EXTEND_REPEAT) { return FALSE; } } /* SVG 1.1 does not support the focal point (fx, fy) that is outside of the circle defined by (cx, cy) and r. */ if (pattern->type == CAIRO_PATTERN_TYPE_RADIAL) { cairo_radial_pattern_t *radial_pattern = (cairo_radial_pattern_t *) pattern; double max_radius; if (radial_pattern->cd1.radius > radial_pattern->cd2.radius) { max_radius = radial_pattern->cd1.radius; } else { max_radius = radial_pattern->cd2.radius; } cairo_point_double_t c1 = radial_pattern->cd1.center; cairo_point_double_t c2 = radial_pattern->cd2.center; if ((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y) >= max_radius * max_radius) { return FALSE; } } if (pattern->type == CAIRO_PATTERN_TYPE_MESH) { return FALSE; } if (pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) { return FALSE; } return TRUE; } static cairo_status_t _cairo_svg_surface_finish (void *abstract_surface) { cairo_status_t status, final_status; cairo_svg_surface_t *surface = abstract_surface; if (_cairo_paginated_surface_get_target (surface->document->owner) == &surface->base) { final_status = _cairo_svg_document_finish (surface->document); } else { final_status = CAIRO_STATUS_SUCCESS; } status = _cairo_svg_stream_destroy (&surface->xml_node); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } for (unsigned int i = 0; i < surface->page_set.num_elements; i++) { cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, i); status = _cairo_svg_stream_destroy (&page->xml_node); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } } _cairo_array_fini (&surface->page_set); _cairo_surface_clipper_reset (&surface->clipper); _cairo_hash_table_foreach (surface->source_surfaces, _cairo_svg_source_surface_pluck, surface->source_surfaces); _cairo_hash_table_destroy (surface->source_surfaces); status = _cairo_svg_document_destroy (surface->document); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } return final_status; } static const char * _cairo_svg_surface_emit_static_filter (cairo_svg_document_t *document, enum cairo_svg_filter filter) { if (!document->filters_emitted[filter]) { document->filters_emitted[filter] = TRUE; if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR) { // (r, g, b, a) -> (1, 1, 1, a) _cairo_svg_stream_printf (&document->xml_node_filters, "\n" "\n" "\n"); } else if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA) { // (r, g, b, a) -> (1, 1, 1, 1 - a) _cairo_svg_stream_printf (&document->xml_node_filters, "\n" "\n" "\n"); } else if (filter == CAIRO_SVG_FILTER_COLOR_TO_ALPHA) { // (r, g, b, a) -> (1, 1, 1, 0.2126 * r + 0.7152 * g + 0.0722 * b) _cairo_svg_stream_printf (&document->xml_node_filters, "\n" "\n" "\n"); } } if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR) { return "remove-color"; } else if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA) { return "remove-color-and-invert-alpha"; } else if (filter == CAIRO_SVG_FILTER_COLOR_TO_ALPHA) { return "color-to-alpha"; } else { ASSERT_NOT_REACHED; } return FALSE; /* squelch warning */ } #define _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER(operation) \ _cairo_svg_stream_printf (&surface->document->xml_node_filters, \ "\n" \ "document->xml_node_filters, \ surface->source_id, \ CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \ _cairo_svg_stream_printf (&surface->document->xml_node_filters, \ "/>\n" \ "document->xml_node_filters, \ surface->source_id, \ CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \ _cairo_svg_stream_printf (&surface->document->xml_node_filters, \ "/>\n" \ "\n" \ "\n", \ filter_id, \ source_compositing_group_id, \ destination_compositing_group_id); #define _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER(mode) \ _cairo_svg_stream_printf (&surface->document->xml_node_filters, \ "\n" \ "document->xml_node_filters, \ surface->source_id, \ CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \ _cairo_svg_stream_printf (&surface->document->xml_node_filters, \ "/>\n" \ "document->xml_node_filters, \ surface->source_id, \ CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \ _cairo_svg_stream_printf (&surface->document->xml_node_filters, \ "/>\n" \ "\n" \ "\n", \ filter_id, \ source_compositing_group_id, \ destination_compositing_group_id); static unsigned int _cairo_svg_surface_emit_parametric_filter (cairo_svg_surface_t *surface, enum cairo_svg_filter filter, unsigned int source_compositing_group_id, unsigned int destination_compositing_group_id) { unsigned int filter_id = surface->document->filter_id++; switch (filter) { case CAIRO_SVG_FILTER_OVER: _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("over") break; case CAIRO_SVG_FILTER_IN: _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("in") break; case CAIRO_SVG_FILTER_OUT: _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("out") break; case CAIRO_SVG_FILTER_ATOP: _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("atop") break; case CAIRO_SVG_FILTER_XOR: _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("xor") break; case CAIRO_SVG_FILTER_ADD: // This can also be done with , but it is not in SVG 1.1 _cairo_svg_stream_printf (&surface->document->xml_node_filters, "\n" "document->xml_node_filters, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); _cairo_svg_stream_printf (&surface->document->xml_node_filters, "/>\n" "document->xml_node_filters, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); _cairo_svg_stream_printf (&surface->document->xml_node_filters, "/>\n" "\n" "\n"); break; case CAIRO_SVG_FILTER_MULTIPLY: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("multiply") break; case CAIRO_SVG_FILTER_SCREEN: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("screen") break; case CAIRO_SVG_FILTER_OVERLAY: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("overlay") break; case CAIRO_SVG_FILTER_DARKEN: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("darken") break; case CAIRO_SVG_FILTER_LIGHTEN: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("lighten") break; case CAIRO_SVG_FILTER_COLOR_DODGE: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color-dodge") break; case CAIRO_SVG_FILTER_COLOR_BURN: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color-burn") break; case CAIRO_SVG_FILTER_HARD_LIGHT: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("hard-light") break; case CAIRO_SVG_FILTER_SOFT_LIGHT: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("soft-light") break; case CAIRO_SVG_FILTER_DIFFERENCE: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("difference") break; case CAIRO_SVG_FILTER_EXCLUSION: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("exclusion") break; case CAIRO_SVG_FILTER_HUE: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("hue") break; case CAIRO_SVG_FILTER_SATURATION: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("saturation") break; case CAIRO_SVG_FILTER_COLOR: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color") break; case CAIRO_SVG_FILTER_LUMINOSITY: _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("luminosity") break; case CAIRO_SVG_FILTER_REMOVE_COLOR: case CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA: case CAIRO_SVG_FILTER_COLOR_TO_ALPHA: case CAIRO_SVG_FILTER_LAST_STATIC_FILTER: default: ASSERT_NOT_REACHED; } return filter_id; } typedef struct { cairo_svg_stream_t *output; unsigned int in_mem; unsigned int trailing; unsigned char src[3]; } base64_write_closure_t; static char const base64_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static cairo_status_t base64_write_func (void *closure, const unsigned char *data, unsigned int length) { base64_write_closure_t *info = (base64_write_closure_t *) closure; unsigned int i; unsigned char *src; src = info->src; if (info->in_mem + length < 3) { for (i = 0; i < length; i++) { src[i + info->in_mem] = *data++; } info->in_mem += length; return CAIRO_STATUS_SUCCESS; } do { unsigned char dst[4]; for (i = info->in_mem; i < 3; i++) { src[i] = *data++; length--; } info->in_mem = 0; dst[0] = base64_table[src[0] >> 2]; dst[1] = base64_table[(src[0] & 0x03) << 4 | src[1] >> 4]; dst[2] = base64_table[(src[1] & 0x0f) << 2 | src[2] >> 6]; dst[3] = base64_table[src[2] & 0xfc >> 2]; /* Special case for the last missing bits */ switch (info->trailing) { case 2: dst[2] = '='; /* fall through */ case 1: dst[3] = '='; default: break; } _cairo_svg_stream_write (info->output, dst, 4); } while (length >= 3); for (i = 0; i < length; i++) { src[i] = *data++; } info->in_mem = length; return info->output->status; } static cairo_int_status_t _cairo_surface_base64_encode_jpeg (cairo_surface_t *surface, cairo_svg_stream_t *output) { const unsigned char *mime_data; unsigned long mime_data_length; cairo_image_info_t image_info; base64_write_closure_t info; cairo_status_t status; cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_JPEG, &mime_data, &mime_data_length); if (mime_data == NULL) return CAIRO_INT_STATUS_UNSUPPORTED; status = _cairo_image_info_get_jpeg_info (&image_info, mime_data, mime_data_length); if (unlikely (status)) return status; if (image_info.num_components == 4) return CAIRO_INT_STATUS_UNSUPPORTED; _cairo_svg_stream_printf (output, "data:image/jpeg;base64,"); info.output = output; info.in_mem = 0; info.trailing = 0; status = base64_write_func (&info, mime_data, mime_data_length); if (unlikely (status)) return status; if (info.in_mem > 0) { memset (info.src + info.in_mem, 0, 3 - info.in_mem); info.trailing = 3 - info.in_mem; info.in_mem = 3; status = base64_write_func (&info, NULL, 0); } return status; } static cairo_int_status_t _cairo_surface_base64_encode_png (cairo_surface_t *surface, cairo_svg_stream_t *output) { const unsigned char *mime_data; unsigned long mime_data_length; base64_write_closure_t info; cairo_status_t status; cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_PNG, &mime_data, &mime_data_length); if (unlikely (surface->status)) return surface->status; if (mime_data == NULL) return CAIRO_INT_STATUS_UNSUPPORTED; _cairo_svg_stream_printf (output, "data:image/png;base64,"); info.output = output; info.in_mem = 0; info.trailing = 0; status = base64_write_func (&info, mime_data, mime_data_length); if (unlikely (status)) return status; if (info.in_mem > 0) { memset (info.src + info.in_mem, 0, 3 - info.in_mem); info.trailing = 3 - info.in_mem; info.in_mem = 3; status = base64_write_func (&info, NULL, 0); } return status; } static cairo_int_status_t _cairo_surface_base64_encode (cairo_surface_t *surface, cairo_svg_stream_t *output) { cairo_int_status_t status; base64_write_closure_t info; status = _cairo_surface_base64_encode_jpeg (surface, output); if (status != CAIRO_INT_STATUS_UNSUPPORTED) return status; status = _cairo_surface_base64_encode_png (surface, output); if (status != CAIRO_INT_STATUS_UNSUPPORTED) return status; info.output = output; info.in_mem = 0; info.trailing = 0; _cairo_svg_stream_printf (info.output, "data:image/png;base64,"); status = cairo_surface_write_to_png_stream (surface, base64_write_func, (void *) &info); if (unlikely (status)) return status; if (info.in_mem > 0) { memset (info.src + info.in_mem, 0, 3 - info.in_mem); info.trailing = 3 - info.in_mem; info.in_mem = 3; status = base64_write_func (&info, NULL, 0); } return status; } /** * _cairo_svg_surface_emit_attr_value: * * Write the value to output the stream as a sequence of characters, * while escaping those which have special meaning in the XML * attribute's value context: & and ". **/ static void _cairo_svg_surface_emit_attr_value (cairo_svg_stream_t *stream, const unsigned char *value, unsigned int length) { const unsigned char *p; const unsigned char *q; unsigned int i; /* we'll accumulate non-special chars in [q, p) range */ p = value; q = p; for (i = 0; i < length; i++, p++) { if (*p == '&' || *p == '"') { /* flush what's left before special char */ if (p != q) { _cairo_svg_stream_write (stream, q, p - q); q = p + 1; } if (*p == '&') _cairo_svg_stream_printf (stream, "&"); else // p == '"' _cairo_svg_stream_printf (stream, """); } } /* flush the trailing chars if any */ if (p != q) _cairo_svg_stream_write (stream, q, p - q); } static cairo_status_t _cairo_svg_surface_emit_surface (cairo_svg_document_t *document, cairo_surface_t *surface, unsigned int source_id) { cairo_rectangle_int_t extents; cairo_bool_t is_bounded; cairo_status_t status; const unsigned char *uri; unsigned long uri_len; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded); _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, " xlink:href=\""); cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_URI, &uri, &uri_len); if (uri != NULL) { _cairo_svg_surface_emit_attr_value (&document->xml_node_defs, uri, uri_len); } else { status = _cairo_surface_base64_encode (surface, &document->xml_node_defs); if (unlikely (status)) return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\""); } _cairo_svg_stream_printf (&document->xml_node_defs, "/>\n"); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_composite_surface_pattern (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, cairo_surface_pattern_t *pattern, unsigned int pattern_id, const cairo_matrix_t *parent_matrix) { cairo_status_t status; cairo_matrix_t p2u = pattern->base.matrix; status = cairo_matrix_invert (&p2u); /* cairo_pattern_set_matrix ensures the matrix is invertible */ assert (status == CAIRO_STATUS_SUCCESS); cairo_bool_t is_new; cairo_svg_source_surface_t *source_surface; status = _cairo_svg_surface_add_source_surface (surface, pattern->surface, &is_new, &source_surface); if (unlikely (status)) { return status; } unsigned int source_id = source_surface->id; if (is_new) { status = _cairo_svg_surface_emit_surface (surface->document, pattern->surface, source_id); if (unlikely (status)) { return status; } } if (pattern_id != invalid_pattern_id) { cairo_rectangle_int_t extents; cairo_bool_t is_bounded; is_bounded = _cairo_surface_get_extents (pattern->surface, &extents); assert (is_bounded); _cairo_svg_stream_printf (output, "\n"); } _cairo_svg_stream_printf (output, "surface->content == CAIRO_CONTENT_ALPHA) { cairo_bool_t can_skip_filter = FALSE; if (pattern->surface->backend && pattern->surface->backend->type == CAIRO_SURFACE_TYPE_IMAGE && (((cairo_image_surface_t *) pattern->surface)->format == CAIRO_FORMAT_A1 || ((cairo_image_surface_t *) pattern->surface)->format == CAIRO_FORMAT_A8)) { can_skip_filter = TRUE; } if (!can_skip_filter) { _cairo_svg_stream_printf (output, " filter=\"url(#filter-%s)\"", _cairo_svg_surface_emit_static_filter (surface->document, CAIRO_SVG_FILTER_COLOR_TO_ALPHA)); } } if (pattern_id == invalid_pattern_id) { _cairo_svg_surface_emit_transform (output, "transform", &p2u, parent_matrix); } _cairo_svg_stream_printf (output, "/>\n"); if (pattern_id != invalid_pattern_id) { _cairo_svg_stream_printf (output, "\n"); } return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_recording_surface (cairo_svg_surface_t *surface, cairo_recording_surface_t *source, unsigned int source_id, cairo_bool_t *transitive_paint_used) { cairo_status_t status; cairo_svg_document_t *document = surface->document; cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document, source->base.content, 0, 0, FALSE); cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface); if (unlikely (paginated_surface->status)) { return paginated_surface->status; } svg_surface->source_id = source_id; svg_surface->depth = surface->depth + 1; cairo_rectangle_int_t extents; cairo_bool_t bounded = _cairo_surface_get_extents (&source->base, &extents); cairo_surface_set_fallback_resolution (paginated_surface, document->owner->x_fallback_resolution, document->owner->y_fallback_resolution); if (source->base.content == CAIRO_CONTENT_COLOR) { _cairo_svg_surface_emit_paint (&svg_surface->xml_node, svg_surface, &_cairo_pattern_black.base, FALSE); } status = _cairo_recording_surface_replay (&source->base, paginated_surface); if (unlikely (status)) { cairo_surface_destroy (paginated_surface); return status; } cairo_surface_show_page (paginated_surface); status = cairo_surface_status (paginated_surface); if (unlikely (status)) { cairo_surface_destroy (paginated_surface); return status; } unsigned int clip_id; if (bounded) { clip_id = document->clip_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n" "\n" "\n", clip_id, extents.x, extents.y, extents.width, extents.height); } _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, " clip-path=\"url(#clip-%d)\"", clip_id); } if (source->base.content == CAIRO_CONTENT_ALPHA) { _cairo_svg_stream_printf (&document->xml_node_defs, " filter=\"url(#filter-%s)\"", _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR)); } _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); if (svg_surface->xml_node.elements.num_elements > 0) { cairo_svg_page_t *page = _cairo_svg_surface_store_page (svg_surface); if (unlikely (page == NULL)) { cairo_surface_destroy (paginated_surface); return _cairo_error (CAIRO_STATUS_NO_MEMORY); } } if (svg_surface->page_set.num_elements > 0) { cairo_svg_page_t *page = _cairo_array_index (&svg_surface->page_set, svg_surface->page_set.num_elements - 1); _cairo_svg_stream_copy (&page->xml_node, &document->xml_node_defs); } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); *transitive_paint_used = svg_surface->transitive_paint_used; status = cairo_surface_status (paginated_surface); cairo_surface_destroy (paginated_surface); return status; } static cairo_recording_surface_t * _cairo_svg_surface_to_recording_surface (const cairo_surface_pattern_t *pattern) { cairo_surface_t *surface = pattern->surface; if (_cairo_surface_is_paginated (surface)) surface = _cairo_paginated_surface_get_recording (surface); if (_cairo_surface_is_snapshot (surface)) surface = _cairo_surface_snapshot_get_target (surface); return (cairo_recording_surface_t *) surface; } static cairo_bool_t _cairo_svg_surface_svg_pattern_should_be_used (const cairo_pattern_t *pattern) { cairo_rectangle_int_t extents; return pattern->type == CAIRO_PATTERN_TYPE_SURFACE && pattern->extend == CAIRO_EXTEND_REPEAT && _cairo_surface_get_extents (((cairo_surface_pattern_t *) pattern)->surface, &extents); } static cairo_bool_t _cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (const cairo_pattern_t *pattern) { return pattern->type == CAIRO_PATTERN_TYPE_SURFACE && !_cairo_svg_surface_svg_pattern_should_be_used (pattern); } static cairo_status_t _cairo_svg_surface_emit_composite_recording_pattern (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, cairo_surface_pattern_t *pattern, unsigned int pattern_id, const cairo_matrix_t *parent_matrix) { cairo_status_t status; cairo_svg_document_t *document = surface->document; cairo_matrix_t p2u = pattern->base.matrix; status = cairo_matrix_invert (&p2u); /* cairo_pattern_set_matrix ensures the matrix is invertible */ assert (status == CAIRO_STATUS_SUCCESS); cairo_bool_t is_new; cairo_svg_source_surface_t *source_surface; status = _cairo_svg_surface_add_source_surface (surface, pattern->surface, &is_new, &source_surface); if (unlikely (status)) { return status; } unsigned int source_id = source_surface->id; cairo_recording_surface_t *recording_surface = _cairo_svg_surface_to_recording_surface (pattern); if (is_new) { status = _cairo_svg_surface_emit_recording_surface (surface, recording_surface, source_id, &source_surface->transitive_paint_used); if (unlikely (status)) { return status; } if (source_surface->transitive_paint_used) { cairo_svg_paint_t *paint_entry = malloc (sizeof (cairo_svg_paint_t)); if (paint_entry == NULL) { return _cairo_error (CAIRO_STATUS_NO_MEMORY); } paint_entry->source_id = source_id; _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t)); _cairo_svg_paint_init_key (paint_entry); status = _cairo_hash_table_insert (document->paints, &paint_entry->base); if (unlikely (status)) { return status; } } } if (source_surface->transitive_paint_used) { cairo_svg_paint_t paint_key; paint_key.source_id = source_id; _cairo_svg_paint_init_key (&paint_key); cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (document->paints, &paint_key.base); assert (found_paint_entry); cairo_svg_paint_element_t paint_element; paint_element.source_id = surface->source_id; paint_element.matrix = pattern->base.matrix; if (parent_matrix != NULL) { cairo_matrix_t parent_matrix_inverse = *parent_matrix; status = cairo_matrix_invert (&parent_matrix_inverse); /* cairo_pattern_set_matrix ensures the matrix is invertible */ assert (status == CAIRO_STATUS_SUCCESS); cairo_matrix_multiply (&paint_element.matrix, &parent_matrix_inverse, &paint_element.matrix); } status = _cairo_array_append (&found_paint_entry->paint_elements, &paint_element); if (unlikely (status)) { return status; } surface->transitive_paint_used = TRUE; } if (pattern_id != invalid_pattern_id) { assert (!recording_surface->unbounded); _cairo_svg_stream_printf (output, "extents_pixels.x, recording_surface->extents_pixels.y, recording_surface->extents_pixels.width, recording_surface->extents_pixels.height, recording_surface->extents_pixels.x, recording_surface->extents_pixels.y, recording_surface->extents_pixels.width, recording_surface->extents_pixels.height); _cairo_svg_surface_emit_transform (output, "patternTransform", &p2u, parent_matrix); _cairo_svg_stream_printf (output, ">\n"); } _cairo_svg_stream_printf (output, "\n"); if (pattern_id != invalid_pattern_id) { _cairo_svg_stream_printf (output, "\n"); } return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_composite_pattern (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, cairo_surface_pattern_t *pattern, unsigned int pattern_id, const cairo_matrix_t *parent_matrix) { if (pattern_id != invalid_pattern_id) { assert (_cairo_svg_surface_svg_pattern_should_be_used (&pattern->base)); } if (pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) { return _cairo_svg_surface_emit_composite_recording_pattern (output, surface, pattern, pattern_id, parent_matrix); } else { return _cairo_svg_surface_emit_composite_surface_pattern (output, surface, pattern, pattern_id, parent_matrix); } } static cairo_status_t _cairo_svg_surface_emit_solid_pattern (cairo_svg_surface_t *surface, cairo_solid_pattern_t *pattern, cairo_svg_stream_t *output, cairo_bool_t is_stroke) { _cairo_svg_stream_printf (output, is_stroke ? " stroke=\"rgb(%f%%, %f%%, %f%%)\" stroke-opacity=\"%f\"" : " fill=\"rgb(%f%%, %f%%, %f%%)\" fill-opacity=\"%f\"", pattern->color.red * 100.0, pattern->color.green * 100.0, pattern->color.blue * 100.0, pattern->color.alpha); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_surface_pattern (cairo_svg_surface_t *surface, cairo_surface_pattern_t *pattern, cairo_svg_stream_t *output, cairo_bool_t is_stroke, const cairo_matrix_t *parent_matrix) { cairo_svg_document_t *document = surface->document; cairo_status_t status; unsigned int pattern_id = document->pattern_id++; status = _cairo_svg_surface_emit_composite_pattern (&document->xml_node_defs, surface, pattern, pattern_id, parent_matrix); if (unlikely (status)) return status; _cairo_svg_stream_printf (output, is_stroke ? " stroke=\"url(#pattern-%d)\"" : " fill=\"url(#pattern-%d)\"", pattern_id); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_pattern_stops (cairo_svg_stream_t *output, const cairo_gradient_pattern_t *pattern, double start_offset, cairo_bool_t reverse_stops, cairo_bool_t emulate_reflect) { cairo_gradient_stop_t *stops; unsigned int n_stops; if (pattern->n_stops < 1) { return CAIRO_STATUS_SUCCESS; } if (pattern->n_stops == 1) { _cairo_svg_stream_printf (output, "\n", pattern->stops[0].offset, pattern->stops[0].color.red * 100.0, pattern->stops[0].color.green * 100.0, pattern->stops[0].color.blue * 100.0, pattern->stops[0].color.alpha); return CAIRO_STATUS_SUCCESS; } if (emulate_reflect || reverse_stops) { n_stops = emulate_reflect ? pattern->n_stops * 2 - 2 : pattern->n_stops; stops = _cairo_malloc_ab (n_stops, sizeof (cairo_gradient_stop_t)); if (unlikely (stops == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); for (unsigned int i = 0; i < pattern->n_stops; i++) { if (reverse_stops) { stops[i] = pattern->stops[pattern->n_stops - i - 1]; stops[i].offset = 1.0 - stops[i].offset; } else { stops[i] = pattern->stops[i]; } if (emulate_reflect) { stops[i].offset *= 0.5; if (i > 0 && i < pattern->n_stops - 1) { if (reverse_stops) { stops[i + pattern->n_stops - 1] = pattern->stops[i]; stops[i + pattern->n_stops - 1].offset = 0.5 + 0.5 * stops[i + pattern->n_stops - 1].offset; } else { stops[i + pattern->n_stops - 1] = pattern->stops[pattern->n_stops - i - 1]; stops[i + pattern->n_stops - 1].offset = 1.0 - 0.5 * stops[i + pattern->n_stops - 1].offset; } } } } } else { n_stops = pattern->n_stops; stops = pattern->stops; } if (start_offset >= 0.0) { for (unsigned int i = 0; i < n_stops; i++) { _cairo_svg_stream_printf (output, "\n", start_offset + (1.0 - start_offset) * stops[i].offset, stops[i].color.red * 100.0, stops[i].color.green * 100.0, stops[i].color.blue * 100.0, stops[i].color.alpha); } } else { cairo_bool_t found = FALSE; unsigned int offset_index; cairo_color_stop_t offset_color_start, offset_color_stop; for (unsigned int i = 0; i <= n_stops; i++) { double x1 = i == n_stops ? stops[0].offset + 1 : stops[i].offset; cairo_color_stop_t *color1 = i == n_stops ? &stops[0].color : &stops[i].color; if (x1 >= -start_offset) { if (i > 0) { double x0 = stops[i - 1].offset; cairo_color_stop_t *color0 = &stops[i - 1].color; if (x0 != x1) { offset_color_start.red = color0->red + (color1->red - color0->red) * (-start_offset - x0) / (x1 - x0); offset_color_start.green = color0->green + (color1->green - color0->green) * (-start_offset - x0) / (x1 - x0); offset_color_start.blue = color0->blue + (color1->blue - color0->blue) * (-start_offset - x0) / (x1 - x0); offset_color_start.alpha = color0->alpha + (color1->alpha - color0->alpha) * (-start_offset - x0) / (x1 - x0); offset_color_stop = offset_color_start; } else { offset_color_stop = stops[i - 1].color; offset_color_start = stops[i].color; } } else { offset_color_stop = offset_color_start = stops[i].color; } offset_index = i; found = TRUE; break; } } if (!found) { offset_index = n_stops - 1; offset_color_stop = offset_color_start = stops[offset_index].color; } _cairo_svg_stream_printf (output, "\n", offset_color_start.red * 100.0, offset_color_start.green * 100.0, offset_color_start.blue * 100.0, offset_color_start.alpha); for (unsigned int i = offset_index; i < n_stops; i++) { _cairo_svg_stream_printf (output, "\n", stops[i].offset + start_offset, stops[i].color.red * 100.0, stops[i].color.green * 100.0, stops[i].color.blue * 100.0, stops[i].color.alpha); } for (unsigned int i = 0; i < offset_index; i++) { _cairo_svg_stream_printf (output, "\n", 1.0 + stops[i].offset + start_offset, stops[i].color.red * 100.0, stops[i].color.green * 100.0, stops[i].color.blue * 100.0, stops[i].color.alpha); } _cairo_svg_stream_printf (output, "\n", offset_color_stop.red * 100.0, offset_color_stop.green * 100.0, offset_color_stop.blue * 100.0, offset_color_stop.alpha); } if (reverse_stops || emulate_reflect) { free (stops); } return CAIRO_STATUS_SUCCESS; } static void _cairo_svg_surface_emit_pattern_extend (cairo_svg_stream_t *output, cairo_pattern_t *pattern) { switch (pattern->extend) { case CAIRO_EXTEND_REPEAT: _cairo_svg_stream_printf (output, " spreadMethod=\"repeat\""); break; case CAIRO_EXTEND_REFLECT: _cairo_svg_stream_printf (output, " spreadMethod=\"reflect\""); break; case CAIRO_EXTEND_NONE: case CAIRO_EXTEND_PAD: break; } } static cairo_status_t _cairo_svg_surface_emit_linear_pattern (cairo_svg_surface_t *surface, cairo_linear_pattern_t *pattern, cairo_svg_stream_t *output, cairo_bool_t is_stroke, const cairo_matrix_t *parent_matrix) { cairo_status_t status; cairo_svg_document_t *document = surface->document; cairo_matrix_t p2u = pattern->base.base.matrix; status = cairo_matrix_invert (&p2u); /* cairo_pattern_set_matrix ensures the matrix is invertible */ assert (status == CAIRO_STATUS_SUCCESS); unsigned int linear_pattern_id = document->linear_pattern_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "pd1.x, pattern->pd1.y, pattern->pd2.x, pattern->pd2.y); _cairo_svg_surface_emit_pattern_extend (&document->xml_node_defs, &pattern->base.base); _cairo_svg_surface_emit_transform (&document->xml_node_defs, "gradientTransform", &p2u, parent_matrix); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); status = _cairo_svg_surface_emit_pattern_stops (&document->xml_node_defs, &pattern->base, 0.0, FALSE, FALSE); if (unlikely (status)) return status; _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (output, is_stroke ? " stroke=\"url(#linear-pattern-%d)\"" : " fill=\"url(#linear-pattern-%d)\"", linear_pattern_id); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_radial_pattern (cairo_svg_surface_t *surface, cairo_radial_pattern_t *pattern, cairo_svg_stream_t *output, cairo_bool_t is_stroke, const cairo_matrix_t *parent_matrix) { cairo_status_t status; cairo_svg_document_t *document = surface->document; cairo_extend_t extend = pattern->base.base.extend; cairo_bool_t reverse_stops; cairo_circle_double_t *c0, *c1; if (pattern->cd1.radius < pattern->cd2.radius) { c0 = &pattern->cd1; c1 = &pattern->cd2; reverse_stops = FALSE; } else { c0 = &pattern->cd2; c1 = &pattern->cd1; reverse_stops = TRUE; } double x0 = c0->center.x; double y0 = c0->center.y; double r0 = c0->radius; double x1 = c1->center.x; double y1 = c1->center.y; double r1 = c1->radius; cairo_matrix_t p2u = pattern->base.base.matrix; status = cairo_matrix_invert (&p2u); /* cairo_pattern_set_matrix ensures the matrix is invertible */ assert (status == CAIRO_STATUS_SUCCESS); unsigned int radial_pattern_id = document->radial_pattern_id++; double start_offset; cairo_bool_t emulate_reflect = FALSE; double fx = (r1 * x0 - r0 * x1) / (r1 - r0); double fy = (r1 * y0 - r0 * y1) / (r1 - r0); /* SVG doesn't support the inner circle and use instead a gradient focal. * That means we need to emulate the cairo behaviour by processing the * cairo gradient stops. * The CAIRO_EXTEND_NONE and CAIRO_EXTEND_PAD modes are quite easy to handle, * it's just a matter of stop position translation and calculation of * the corresponding SVG radial gradient focal. * The CAIRO_EXTEND_REFLECT and CAIRO_EXTEND_REPEAT modes require to compute a new * radial gradient, with an new outer circle, equal to r1 - r0 in the CAIRO_EXTEND_REPEAT * case, and 2 * r1 - r0 in the CAIRO_EXTEND_REFLECT case, and a new gradient stop * list that maps to the original cairo stop list. */ if ((extend == CAIRO_EXTEND_REFLECT || extend == CAIRO_EXTEND_REPEAT) && r0 > 0.0) { double r_org = r1; if (extend == CAIRO_EXTEND_REFLECT) { r1 = 2.0 * r1 - r0; emulate_reflect = TRUE; } start_offset = fmod (r1, r1 - r0) / (r1 - r0) - 1.0; double r = r1 - r0; /* New position of outer circle. */ double x = r * (x1 - fx) / r_org + fx; double y = r * (y1 - fy) / r_org + fy; x1 = x; y1 = y; r1 = r; r0 = 0.0; } else { start_offset = r0 / r1; } _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, " spreadMethod=\"repeat\""); } else { _cairo_svg_surface_emit_pattern_extend (&document->xml_node_defs, &pattern->base.base); } _cairo_svg_surface_emit_transform (&document->xml_node_defs, "gradientTransform", &p2u, parent_matrix); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); /* To support cairo's EXTEND_NONE, (for which SVG has no similar * notion), we add transparent color stops on either end of the * user-provided stops. */ if (extend == CAIRO_EXTEND_NONE) { _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); if (r0 != 0.0) { _cairo_svg_stream_printf (&document->xml_node_defs, "\n", r0 / r1); } } status = _cairo_svg_surface_emit_pattern_stops (&document->xml_node_defs, &pattern->base, start_offset, reverse_stops, emulate_reflect); if (unlikely (status)) { return status; } if (pattern->base.base.extend == CAIRO_EXTEND_NONE) { _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (output, is_stroke ? " stroke=\"url(#radial-pattern-%d)\"" : " fill=\"url(#radial-pattern-%d)\"", radial_pattern_id); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_svg_surface_emit_pattern (cairo_svg_surface_t *surface, const cairo_pattern_t *pattern, cairo_svg_stream_t *output, cairo_bool_t is_stroke, const cairo_matrix_t *parent_matrix) { switch (pattern->type) { case CAIRO_PATTERN_TYPE_SOLID: return _cairo_svg_surface_emit_solid_pattern (surface, (cairo_solid_pattern_t *) pattern, output, is_stroke); case CAIRO_PATTERN_TYPE_SURFACE: return _cairo_svg_surface_emit_surface_pattern (surface, (cairo_surface_pattern_t *) pattern, output, is_stroke, parent_matrix); case CAIRO_PATTERN_TYPE_LINEAR: return _cairo_svg_surface_emit_linear_pattern (surface, (cairo_linear_pattern_t *) pattern, output, is_stroke, parent_matrix); case CAIRO_PATTERN_TYPE_RADIAL: return _cairo_svg_surface_emit_radial_pattern (surface, (cairo_radial_pattern_t *) pattern, output, is_stroke, parent_matrix); case CAIRO_PATTERN_TYPE_MESH: case CAIRO_PATTERN_TYPE_RASTER_SOURCE: ASSERT_NOT_REACHED; } return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); } static cairo_status_t _cairo_svg_surface_emit_fill_style (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, cairo_fill_rule_t fill_rule, const cairo_matrix_t *parent_matrix) { _cairo_svg_stream_printf (output, " fill-rule=\"%s\"", fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero"); return _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, parent_matrix); } static cairo_status_t _cairo_svg_surface_emit_stroke_style (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, const cairo_stroke_style_t *stroke_style, const cairo_matrix_t *parent_matrix) { cairo_status_t status; const char *line_cap, *line_join; unsigned int i; switch (stroke_style->line_cap) { case CAIRO_LINE_CAP_BUTT: line_cap = "butt"; break; case CAIRO_LINE_CAP_ROUND: line_cap = "round"; break; case CAIRO_LINE_CAP_SQUARE: line_cap = "square"; break; default: ASSERT_NOT_REACHED; } switch (stroke_style->line_join) { case CAIRO_LINE_JOIN_MITER: line_join = "miter"; break; case CAIRO_LINE_JOIN_ROUND: line_join = "round"; break; case CAIRO_LINE_JOIN_BEVEL: line_join = "bevel"; break; default: ASSERT_NOT_REACHED; } if (stroke_style->is_hairline) { _cairo_svg_stream_printf (output, " stroke-width=\"1px\"" " stroke-linecap=\"%s\"" " stroke-linejoin=\"%s\"" " style=\"vector-effect: non-scaling-stroke\"", line_cap, line_join); } else { _cairo_svg_stream_printf (output, " stroke-width=\"%f\"" " stroke-linecap=\"%s\"" " stroke-linejoin=\"%s\"", stroke_style->line_width, line_cap, line_join); } status = _cairo_svg_surface_emit_pattern (surface, source, output, TRUE, parent_matrix); if (unlikely (status)) { return status; } if (stroke_style->num_dashes > 0) { _cairo_svg_stream_printf (output, " stroke-dasharray=\""); for (i = 0; i < stroke_style->num_dashes; i++) { _cairo_svg_stream_printf (output, "%f", stroke_style->dash[i]); _cairo_svg_stream_printf (output, i + 1 < stroke_style->num_dashes ? " " : "\""); } if (stroke_style->dash_offset != 0.0) { _cairo_svg_stream_printf (output, " stroke-dashoffset=\"%f\"", stroke_style->dash_offset); } } _cairo_svg_stream_printf (output, " stroke-miterlimit=\"%f\"", stroke_style->miter_limit); return CAIRO_STATUS_SUCCESS; } static cairo_bool_t _cairo_svg_surface_get_extents (void *abstract_surface, cairo_rectangle_int_t *rectangle) { cairo_svg_surface_t *surface = abstract_surface; rectangle->x = 0; rectangle->y = 0; /* XXX: The conversion to integers here is pretty bogus, (not to * mention the arbitrary limitation of width to a short(!). We * may need to come up with a better interface for get_size. */ rectangle->width = ceil (surface->width); rectangle->height = ceil (surface->height); return surface->surface_bounded; } static cairo_status_t _cairo_svg_surface_emit_paint (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, cairo_bool_t at_origin) { cairo_status_t status; if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source)) { return _cairo_svg_surface_emit_composite_pattern (output, surface, (cairo_surface_pattern_t *) source, invalid_pattern_id, NULL); } surface->transitive_paint_used = TRUE; _cairo_svg_stream_printf (output, "source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); } else { _cairo_svg_stream_append_paint_dependent (output, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE); } status = _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, NULL); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (output, "/>\n"); return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_svg_surface_do_operator (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, cairo_operator_t op, const cairo_clip_t *clip, cairo_svg_stream_t *mask_stream, cairo_svg_stream_t *source_stream, cairo_svg_stream_t *destination_stream) { cairo_status_t status; cairo_svg_document_t *document = surface->document; // For operators that do not always produce opaque output, we first need to emit a black paint // if the content does not have alpha if (surface->base.content == CAIRO_CONTENT_COLOR && (op == CAIRO_OPERATOR_CLEAR || op == CAIRO_OPERATOR_SOURCE || op == CAIRO_OPERATOR_IN || op == CAIRO_OPERATOR_OUT || op == CAIRO_OPERATOR_DEST_IN || op == CAIRO_OPERATOR_DEST_OUT || op == CAIRO_OPERATOR_DEST_ATOP || op == CAIRO_OPERATOR_XOR)) { _cairo_svg_surface_emit_paint (output, surface, &_cairo_pattern_black.base, FALSE); } if (op == CAIRO_OPERATOR_CLEAR) { /* * The result is the same as one of the SOURCE operation application with the same arguments, * but with an empty source. */ status = _cairo_svg_stream_destroy (source_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); (void) _cairo_svg_stream_destroy (mask_stream); return status; } cairo_svg_stream_t empty_stream = _cairo_svg_stream_create (); return _cairo_svg_surface_do_operator (output, surface, CAIRO_OPERATOR_SOURCE, clip, mask_stream, &empty_stream, destination_stream); } if (op == CAIRO_OPERATOR_SOURCE) { /* * Below we use the "Bounded" equation with SOURCE as the operation from the "Clipping and masking" section * of https://cairographics.org/operators/: * result = source LEPR_(clip IN mask) destination * * It is equivalent to: * result = (source IN (clip IN mask)) ADD (destination IN (NOT (clip IN mask))) * * 1. We put the clip masked with the mask into the SVG group `lerp_compositing_group`. * 2. `positive_lerp_mask` is an SVG mask with `lerp_compositing_group`. * 3. `negative_lerp_mask` is an SVG mask with inverted `lerp_compositing_group`. * 5. We put the source masked with `positive_lerp_mask` into the SVG group `lerped_source_compositing_group`. * 6. We put the destination masked with `negative_lerp_mask` into * the SVG group `lerped_destination_compositing_group`. * 7. The result is addition of `lerped_source_compositing_group` and `lerped_destination_compositing_group`. */ unsigned int lerp_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_clear.base, FALSE); status = _cairo_svg_surface_set_clip (surface, &document->xml_node_defs, clip); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); (void) _cairo_svg_stream_destroy (source_stream); (void) _cairo_svg_stream_destroy (mask_stream); return status; } _cairo_svg_stream_copy (mask_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (mask_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); (void) _cairo_svg_stream_destroy (source_stream); return status; } _cairo_svg_surface_reset_clip (surface); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int positive_lerp_mask_id = document->mask_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", positive_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerp_compositing_group_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int negative_lerp_mask_id = document->mask_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", negative_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerp_compositing_group_id, _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA)); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int lerped_source_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerped_source_compositing_group_id, positive_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_stream_copy (source_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (source_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int lerped_destination_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerped_destination_compositing_group_id, negative_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_stream_copy (destination_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (destination_stream); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (&surface->xml_node, "xml_node, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION); _cairo_svg_stream_printf (&surface->xml_node, ">\n"); status = _cairo_svg_surface_emit_paint (&surface->xml_node, surface, &_cairo_pattern_black.base, TRUE); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (&surface->xml_node, "\n"); return CAIRO_STATUS_SUCCESS; } if (op == CAIRO_OPERATOR_DEST) { /* * The result is the destination. */ _cairo_svg_stream_copy (destination_stream, &surface->xml_node); status = _cairo_svg_stream_destroy (destination_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (source_stream); (void) _cairo_svg_stream_destroy (mask_stream); return status; } status = _cairo_svg_stream_destroy (source_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (source_stream); return status; } status = _cairo_svg_stream_destroy (source_stream); if (unlikely (status)) { return status; } return CAIRO_STATUS_SUCCESS; } /* * Below we use the "XRender" equation from the "Clipping and masking" section * of https://cairographics.org/operators/: * result = ((source IN mask) OP destination) LERP_clip destination * * It is equivalent to: * result = (((source IN mask) OP destination) IN clip) ADD (destination IN (NOT clip)) * * 1. We put the clip into the SVG group `lerp_compositing_group`. * 2. `positive_lerp_mask` is an SVG mask with `lerp_compositing_group`. * 3. `negative_lerp_mask` is an SVG mask with inverted `lerp_compositing_group`. * 4. We put the mask into the SVG mask `mask_mask`. * 5. We put the source masked with `mask_mask` into the SVG group `masked_source_compositing_group`. * 6. We put the destination into the SVG group `destination_compositing_group`. * 7. `lerped_operation_compositing_group` is an SVG group of operation applied to * (`masked_source_compositing_group`, `destination_compositing_group`) * masked with `positive_lerp_mask`. * 8. `lerped_destination_compositing_group` is an SVG group of `destination_compositing_group` * masked with `negative_lerp_mask`. * 9. The result is addition of `lerped_operation_compositing_group` and `lerped_destination_compositing_group`. */ unsigned int lerp_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_clear.base, FALSE); status = _cairo_svg_surface_set_clip (surface, &document->xml_node_defs, clip); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); (void) _cairo_svg_stream_destroy (source_stream); (void) _cairo_svg_stream_destroy (mask_stream); return status; } status = _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_white.base, FALSE); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); (void) _cairo_svg_stream_destroy (source_stream); (void) _cairo_svg_stream_destroy (mask_stream); return status; } _cairo_svg_surface_reset_clip (surface); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int positive_lerp_mask_id = document->mask_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", positive_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerp_compositing_group_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int negative_lerp_mask_id = document->mask_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", negative_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerp_compositing_group_id, _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA)); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int mask_mask_id = document->mask_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", mask_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_stream_copy (mask_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (mask_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (source_stream); (void) _cairo_svg_stream_destroy (destination_stream); return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int masked_source_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", masked_source_compositing_group_id, mask_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_stream_copy (source_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (source_stream); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (destination_stream); return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int destination_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION); _cairo_svg_stream_printf (&document->xml_node_defs, ">\n"); _cairo_svg_stream_copy (destination_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (destination_stream); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int lerped_operation_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "xml_node_defs, " filter=\"url(#filter-%d)\" mask=\"url(#mask-%d)\">\n", filter_id, positive_lerp_mask_id); status = _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_black.base, TRUE); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); unsigned int lerped_destination_compositing_group_id = document->compositing_group_id++; _cairo_svg_stream_printf (&document->xml_node_defs, "\n", lerped_destination_compositing_group_id, negative_lerp_mask_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n", destination_compositing_group_id); _cairo_svg_stream_printf (&document->xml_node_defs, "\n"); _cairo_svg_stream_printf (&surface->xml_node, "xml_node, surface->source_id, CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION); _cairo_svg_stream_printf (&surface->xml_node, ">\n"); status = _cairo_svg_surface_emit_paint (&surface->xml_node, surface, &_cairo_pattern_black.base, TRUE); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (&surface->xml_node, "\n"); return CAIRO_STATUS_SUCCESS; } #define _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL(OPERATOR_IMPL, SOURCE, ...) \ if (op == CAIRO_OPERATOR_OVER) { \ status = _cairo_svg_surface_set_clip (surface, &surface->xml_node, clip); \ if (unlikely (status)) { \ return status; \ } \ return OPERATOR_IMPL (&surface->xml_node, surface, SOURCE, ## __VA_ARGS__); \ } else { \ _cairo_svg_surface_reset_clip (surface); \ cairo_svg_stream_t mask_stream = _cairo_svg_stream_create (); \ status = OPERATOR_IMPL (&mask_stream, surface, &_cairo_pattern_white.base, ## __VA_ARGS__); \ if (unlikely (status)) { \ (void) _cairo_svg_stream_destroy (&mask_stream); \ return status; \ } \ cairo_svg_stream_t source_stream = _cairo_svg_stream_create (); \ status = _cairo_svg_surface_emit_paint (&source_stream, \ surface, \ SOURCE, \ FALSE); \ if (unlikely (status)) { \ (void) _cairo_svg_stream_destroy (&source_stream); \ (void) _cairo_svg_stream_destroy (&mask_stream); \ return status; \ } \ cairo_svg_stream_t destination_stream = surface->xml_node; \ surface->xml_node = _cairo_svg_stream_create (); \ return _cairo_svg_surface_do_operator (&surface->xml_node, \ surface, \ op, \ clip, \ &mask_stream, \ &source_stream, \ &destination_stream); \ } static cairo_int_status_t _cairo_svg_surface_paint_impl (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source) { return _cairo_svg_surface_emit_paint (output, surface, source, FALSE); } static cairo_int_status_t _cairo_svg_surface_paint (void *abstract_surface, cairo_operator_t op, const cairo_pattern_t *source, const cairo_clip_t *clip) { cairo_status_t status; cairo_svg_surface_t *surface = abstract_surface; /* Emulation of clear and source operators, when no clipping region * is defined. We just delete existing content of surface root node, * and exit early if operator is clear. */ if ((op == CAIRO_OPERATOR_CLEAR || op == CAIRO_OPERATOR_SOURCE) && clip == NULL) { switch (surface->paginated_mode) { case CAIRO_PAGINATED_MODE_ANALYZE: return CAIRO_STATUS_SUCCESS; case CAIRO_PAGINATED_MODE_RENDER: status = _cairo_svg_stream_destroy (&surface->xml_node); if (unlikely (status)) { return status; } surface->xml_node = _cairo_svg_stream_create (); if (op == CAIRO_OPERATOR_CLEAR) { return CAIRO_STATUS_SUCCESS; } break; case CAIRO_PAGINATED_MODE_FALLBACK: ASSERT_NOT_REACHED; } } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) ? CAIRO_STATUS_SUCCESS : CAIRO_INT_STATUS_UNSUPPORTED; } _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_paint_impl, source) } static cairo_int_status_t _cairo_svg_surface_mask_impl (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, const cairo_pattern_t *mask) { cairo_status_t status; cairo_svg_document_t *document = surface->document; /* _cairo_svg_surface_emit_paint() will output a pattern definition to * document->xml_node_defs so we need to write the mask element to * a temporary stream and then copy that to xml_node_defs. */ cairo_svg_stream_t temporary_stream = _cairo_svg_stream_create (); unsigned int mask_id = document->mask_id++; _cairo_svg_stream_printf (&temporary_stream, "\n", mask_id); _cairo_svg_stream_printf (&temporary_stream, "\n", _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR)); status = _cairo_svg_surface_emit_paint (&temporary_stream, surface, mask, FALSE); if (unlikely (status)) { (void) _cairo_svg_stream_destroy (&temporary_stream); return status; } _cairo_svg_stream_printf (&temporary_stream, "\n"); _cairo_svg_stream_printf (&temporary_stream, "\n"); _cairo_svg_stream_copy (&temporary_stream, &document->xml_node_defs); status = _cairo_svg_stream_destroy (&temporary_stream); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (output, "\n", mask_id); status = _cairo_svg_surface_emit_paint (output, surface, source, FALSE); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (output, "\n"); return CAIRO_STATUS_SUCCESS; } 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, const cairo_clip_t *clip) { cairo_status_t status; cairo_svg_surface_t *surface = abstract_surface; if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) && _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, mask) ? CAIRO_STATUS_SUCCESS : CAIRO_INT_STATUS_UNSUPPORTED; } _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_mask_impl, source, mask) } static cairo_int_status_t _cairo_svg_surface_stroke_impl (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, const 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_status_t status; cairo_bool_t svg_clip_or_svg_mask_should_be_used = _cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source); unsigned int mask_id; cairo_svg_stream_t *output_stream = output; if (svg_clip_or_svg_mask_should_be_used) { mask_id = surface->document->mask_id++; output_stream = &surface->document->xml_node_defs; _cairo_svg_stream_printf (output_stream, "\n", mask_id); } _cairo_svg_stream_printf (output_stream, "\n"); if (svg_clip_or_svg_mask_should_be_used) { _cairo_svg_stream_printf (output_stream, "\n"); _cairo_svg_stream_printf (output, "\n", mask_id); status = _cairo_svg_surface_emit_composite_pattern (output, surface, (cairo_surface_pattern_t *) source, invalid_pattern_id, NULL); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (output, "\n"); } return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_svg_surface_stroke (void *abstract_dst, cairo_operator_t op, const cairo_pattern_t *source, const 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, const cairo_clip_t *clip) { cairo_svg_surface_t *surface = abstract_dst; cairo_status_t status; if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) ? CAIRO_STATUS_SUCCESS : CAIRO_INT_STATUS_UNSUPPORTED; } _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_stroke_impl, source, path, stroke_style, ctm, ctm_inverse, tolerance, antialias) } static cairo_int_status_t _cairo_svg_surface_fill_impl (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { cairo_status_t status; if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source)) { _cairo_svg_stream_printf (&surface->document->xml_node_defs, "\n", surface->document->clip_id); _cairo_svg_stream_printf (&surface->document->xml_node_defs, "document->xml_node_defs, path, NULL); _cairo_svg_stream_printf (&surface->document->xml_node_defs, "/>\n"); _cairo_svg_stream_printf (&surface->document->xml_node_defs, "\n"); _cairo_svg_stream_printf (output, "\n", surface->document->clip_id++); status = _cairo_svg_surface_emit_composite_pattern (output, surface, (cairo_surface_pattern_t *) source, invalid_pattern_id, NULL); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (output, ""); } else { _cairo_svg_stream_printf (output, "\n"); } return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_svg_surface_fill (void *abstract_surface, cairo_operator_t op, const cairo_pattern_t *source, const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias, const cairo_clip_t *clip) { cairo_svg_surface_t *surface = abstract_surface; cairo_status_t status; if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) ? CAIRO_STATUS_SUCCESS : CAIRO_INT_STATUS_UNSUPPORTED; } _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_fill_impl, source, path, fill_rule, tolerance, antialias) } 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, const 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, const cairo_clip_t *clip) { cairo_svg_surface_t *surface = abstract_surface; cairo_status_t status; if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (fill_source) || _cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (stroke_source) || fill_op != CAIRO_OPERATOR_OVER || stroke_op != CAIRO_OPERATOR_OVER) { return CAIRO_INT_STATUS_UNSUPPORTED; } if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { return _cairo_svg_surface_are_operation_and_pattern_supported (surface, fill_op, fill_source) && _cairo_svg_surface_are_operation_and_pattern_supported (surface, stroke_op, stroke_source) ? CAIRO_STATUS_SUCCESS : CAIRO_INT_STATUS_UNSUPPORTED; } status = _cairo_svg_surface_set_clip (surface, &surface->xml_node, clip); if (unlikely (status)) { return status; } _cairo_svg_stream_printf (&surface->xml_node, "xml_node, surface, fill_source, fill_rule, stroke_ctm_inverse); if (unlikely (status)) { return status; } status = _cairo_svg_surface_emit_stroke_style (&surface->xml_node, surface, stroke_source, stroke_style, stroke_ctm_inverse); if (unlikely (status)) { return status; } _cairo_svg_surface_emit_path (&surface->xml_node, path, stroke_ctm_inverse); _cairo_svg_surface_emit_transform (&surface->xml_node, "transform", stroke_ctm, NULL); _cairo_svg_stream_printf (&surface->xml_node, "/>\n"); return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_svg_surface_show_glyphs_impl (cairo_svg_stream_t *output, cairo_svg_surface_t *surface, const cairo_pattern_t *source, cairo_glyph_t *glyphs, int num_glyphs, cairo_scaled_font_t *scaled_font) { cairo_status_t status; cairo_svg_document_t *document = surface->document; if (num_glyphs <= 0) { return CAIRO_STATUS_SUCCESS; } /* FIXME it's probably possible to apply a source of a gradient to * a group of symbols, but I don't know how yet. Gradients or patterns * are translated by x and y properties of use element. */ if (source->type != CAIRO_PATTERN_TYPE_SOLID) { goto fallback; } _cairo_svg_stream_printf (output, "\n"); for (int i = 0; i < num_glyphs; i++) { cairo_scaled_font_subsets_glyph_t subset_glyph; status = _cairo_scaled_font_subsets_map_glyph (document->font_subsets, scaled_font, glyphs[i].index, NULL, 0, &subset_glyph); if ((cairo_int_status_t) status == CAIRO_INT_STATUS_UNSUPPORTED) { _cairo_svg_stream_printf (output, "\n"); glyphs += i; num_glyphs -= i; goto fallback; } if (unlikely (status)) { return status; } _cairo_svg_stream_printf (output, "\n", subset_glyph.font_id, subset_glyph.subset_glyph_index, glyphs[i].x, glyphs[i].y); } _cairo_svg_stream_printf (output, "\n"); return CAIRO_STATUS_SUCCESS; fallback:; cairo_path_fixed_t path; _cairo_path_fixed_init (&path); status = _cairo_scaled_font_glyph_path (scaled_font, (cairo_glyph_t *) glyphs, num_glyphs, &path); if (unlikely (status)) { _cairo_path_fixed_fini (&path); return status; } status = _cairo_svg_surface_fill_impl (output, surface, source, &path, CAIRO_FILL_RULE_WINDING, 0.0, CAIRO_ANTIALIAS_DEFAULT); _cairo_path_fixed_fini (&path); return status; } static cairo_int_status_t _cairo_svg_surface_show_glyphs (void *abstract_surface, cairo_operator_t op, const cairo_pattern_t *source, cairo_glyph_t *glyphs, int num_glyphs, cairo_scaled_font_t *scaled_font, const cairo_clip_t *clip) { cairo_svg_surface_t *surface = abstract_surface; cairo_int_status_t status; if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) ? CAIRO_STATUS_SUCCESS : CAIRO_INT_STATUS_UNSUPPORTED; } _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_show_glyphs_impl, source, glyphs, num_glyphs, scaled_font) } static void _cairo_svg_surface_get_font_options (void *abstract_surface, cairo_font_options_t *options) { _cairo_font_options_init_default (options); cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE); cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_OFF); cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_GRAY); _cairo_font_options_set_round_glyph_positions (options, CAIRO_ROUND_GLYPH_POS_OFF); } static const char ** _cairo_svg_surface_get_supported_mime_types (void *abstract_surface) { return _cairo_svg_supported_mime_types; } static const cairo_surface_backend_t cairo_svg_surface_backend = { CAIRO_SURFACE_TYPE_SVG, _cairo_svg_surface_finish, _cairo_default_context_create, NULL, /* create_similar: handled by wrapper */ NULL, /* create_similar_image */ NULL, /* map to image */ NULL, /* unmap image */ _cairo_surface_default_source, NULL, /* acquire_source_image */ NULL, /* release_source_image */ NULL, /* snapshot */ _cairo_svg_surface_copy_page, _cairo_svg_surface_show_page, _cairo_svg_surface_get_extents, _cairo_svg_surface_get_font_options, NULL, /* flush */ NULL, /* mark dirty rectangle */ _cairo_svg_surface_paint, _cairo_svg_surface_mask, _cairo_svg_surface_stroke, _cairo_svg_surface_fill, _cairo_svg_surface_fill_stroke, _cairo_svg_surface_show_glyphs, NULL, /* has_show_text_glyphs */ NULL, /* show_text_glyphs */ _cairo_svg_surface_get_supported_mime_types, }; 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) { cairo_svg_document_t *document; if (output_stream->status) { return output_stream->status; } document = _cairo_malloc (sizeof (cairo_svg_document_t)); if (unlikely (document == NULL)) { return _cairo_error (CAIRO_STATUS_NO_MEMORY); } document->output_stream = output_stream; document->refcount = 1; document->owner = NULL; document->finished = FALSE; document->width = width; document->height = height; document->unit = CAIRO_SVG_UNIT_USER; document->xml_node_defs = _cairo_svg_stream_create (); document->xml_node_glyphs = _cairo_svg_stream_create (); document->xml_node_filters = _cairo_svg_stream_create (); document->linear_pattern_id = 0; document->radial_pattern_id = 0; document->pattern_id = 0; document->clip_id = 0; document->mask_id = 0; document->compositing_group_id = 0; document->filter_id = 0; for (enum cairo_svg_filter filter = 0; filter < CAIRO_SVG_FILTER_LAST_STATIC_FILTER; filter++) { document->filters_emitted[filter] = FALSE; } document->svg_version = version; /* The use of defs for font glyphs imposes no per-subset limit. */ document->font_subsets = _cairo_scaled_font_subsets_create_scaled (); if (unlikely (document->font_subsets == NULL)) { (void) _cairo_svg_stream_destroy(&document->xml_node_defs); (void) _cairo_svg_stream_destroy(&document->xml_node_glyphs); (void) _cairo_svg_stream_destroy(&document->xml_node_filters); free (document); return _cairo_error (CAIRO_STATUS_NO_MEMORY); } document->paints = _cairo_hash_table_create (_cairo_svg_paint_equal); if (unlikely (document->paints == NULL)) { (void) _cairo_svg_stream_destroy(&document->xml_node_defs); (void) _cairo_svg_stream_destroy(&document->xml_node_glyphs); (void) _cairo_svg_stream_destroy(&document->xml_node_filters); _cairo_scaled_font_subsets_destroy (document->font_subsets); free (document); return _cairo_error (CAIRO_STATUS_NO_MEMORY); } *document_out = document; return CAIRO_STATUS_SUCCESS; } static cairo_svg_document_t * _cairo_svg_document_reference (cairo_svg_document_t *document) { document->refcount++; return document; } static cairo_status_t _cairo_svg_document_destroy (cairo_svg_document_t *document) { cairo_status_t status; document->refcount--; if (document->refcount > 0) return CAIRO_STATUS_SUCCESS; status = _cairo_svg_document_finish (document); free (document); return status; } static cairo_status_t _cairo_svg_document_finish (cairo_svg_document_t *document) { if (document->finished) { return CAIRO_STATUS_SUCCESS; } document->finished = TRUE; cairo_status_t status, final_status = CAIRO_STATUS_SUCCESS; cairo_output_stream_t *output = document->output_stream; /* * Should we add DOCTYPE? * * Google says no. * * http://tech.groups.yahoo.com/group/svg-developers/message/48562: * There's a bunch of issues, but just to pick a few: * - they'll give false positives. * - they'll give false negatives. * - they're namespace-unaware. * - they don't wildcard. * So when they say OK they really haven't checked anything, when * they say NOT OK they might be on crack, and like all * namespace-unaware things they're a dead branch of the XML tree. * * http://jwatt.org/svg/authoring/: * Unfortunately the SVG DTDs are a source of so many issues that the * SVG WG has decided not to write one for the upcoming SVG 1.2 * standard. In fact SVG WG members are even telling people not to use * a DOCTYPE declaration in SVG 1.0 and 1.1 documents. */ _cairo_output_stream_printf (output, "\n" "\n", document->width, _cairo_svg_unit_strings[document->unit], document->height, _cairo_svg_unit_strings[document->unit], document->width, document->height); status = _cairo_svg_document_emit_font_subsets (document); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } cairo_svg_surface_t *surface = NULL; if (document->owner != NULL) { surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (document->owner); if (surface->xml_node.elements.num_elements > 0) { cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface); if (final_status == CAIRO_STATUS_SUCCESS && page == NULL) { final_status = _cairo_error (CAIRO_STATUS_NO_MEMORY); } } if (surface->transitive_paint_used) { cairo_svg_paint_t *paint_entry = malloc (sizeof (cairo_svg_paint_t)); if (paint_entry == NULL) { return _cairo_error (CAIRO_STATUS_NO_MEMORY); } paint_entry->source_id = surface->source_id; paint_entry->box.p1.x = 0; paint_entry->box.p1.y = 0; paint_entry->box.p2.x = document->width; paint_entry->box.p2.y = document->height; _cairo_svg_paint_box_add_padding (&paint_entry->box); _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t)); _cairo_svg_paint_init_key (paint_entry); status = _cairo_hash_table_insert (document->paints, &paint_entry->base); if (unlikely (status)) { return status; } } } _cairo_hash_table_foreach (document->paints, _cairo_svg_paint_compute_func, document); if (document->xml_node_filters.elements.num_elements > 0 || document->xml_node_glyphs.elements.num_elements > 0 || document->xml_node_defs.elements.num_elements > 0) { _cairo_output_stream_printf (output, "\n"); _cairo_svg_stream_copy_to_output_stream (&document->xml_node_filters, output, document->paints); if (document->xml_node_glyphs.elements.num_elements > 0) { _cairo_output_stream_printf (output, "\n"); _cairo_svg_stream_copy_to_output_stream (&document->xml_node_glyphs, output, document->paints); _cairo_output_stream_printf (output, "\n"); } _cairo_svg_stream_copy_to_output_stream (&document->xml_node_defs, output, document->paints); _cairo_output_stream_printf (output, "\n"); } if (document->owner != NULL) { if (surface->page_set.num_elements == 1) { cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, 0); _cairo_svg_stream_copy_to_output_stream (&page->xml_node, output, document->paints); } else if (surface->page_set.num_elements > 1) { _cairo_output_stream_printf (output, "\n"); for (unsigned int i = 0; i < surface->page_set.num_elements; i++) { cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, i); _cairo_output_stream_printf (output, "\n"); _cairo_svg_stream_copy_to_output_stream (&page->xml_node, output, document->paints); _cairo_output_stream_printf (output, "\n"); } _cairo_output_stream_printf (output, "\n"); } } _cairo_output_stream_printf (output, "\n"); status = _cairo_svg_stream_destroy (&document->xml_node_defs); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } status = _cairo_svg_stream_destroy (&document->xml_node_glyphs); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } status = _cairo_svg_stream_destroy (&document->xml_node_filters); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } _cairo_hash_table_foreach (document->paints, _cairo_svg_paint_pluck, document->paints); _cairo_hash_table_destroy (document->paints); status = _cairo_output_stream_destroy (output); if (final_status == CAIRO_STATUS_SUCCESS) { final_status = status; } return final_status; } static cairo_int_status_t _cairo_svg_surface_set_paginated_mode (void *abstract_surface, cairo_paginated_mode_t paginated_mode) { cairo_svg_surface_t *surface = abstract_surface; surface->paginated_mode = paginated_mode; return CAIRO_STATUS_SUCCESS; } static cairo_bool_t _cairo_svg_surface_supports_fine_grained_fallbacks (void *abstract_surface) { return TRUE; } static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend = { NULL /*_cairo_svg_surface_start_page*/, _cairo_svg_surface_set_paginated_mode, NULL, /* _cairo_svg_surface_set_bounding_box */ NULL, /* _cairo_svg_surface_set_fallback_images_required */ _cairo_svg_surface_supports_fine_grained_fallbacks, };