diff options
author | sanine <sanine.not@pm.me> | 2022-10-12 12:03:23 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-10-12 12:03:23 -0500 |
commit | 530ffd0b7d3c39757b20f00716e486b5caf89aff (patch) | |
tree | 76b35fdf57317038acf6b828871f6ae25fce2ebe /libs/cairo-1.16.0/src/cairo-pdf-surface.c | |
parent | 3dbe9332e47c143a237db12440f134caebd1cfbe (diff) |
add cairo
Diffstat (limited to 'libs/cairo-1.16.0/src/cairo-pdf-surface.c')
-rw-r--r-- | libs/cairo-1.16.0/src/cairo-pdf-surface.c | 8521 |
1 files changed, 8521 insertions, 0 deletions
diff --git a/libs/cairo-1.16.0/src/cairo-pdf-surface.c b/libs/cairo-1.16.0/src/cairo-pdf-surface.c new file mode 100644 index 0000000..7eb61aa --- /dev/null +++ b/libs/cairo-1.16.0/src/cairo-pdf-surface.c @@ -0,0 +1,8521 @@ +/* -*- 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 © 2006 Red Hat, Inc + * Copyright © 2007, 2008 Adrian Johnson + * + * 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 <krh@redhat.com> + * Carl Worth <cworth@cworth.org> + * Adrian Johnson <ajohnson@redneon.com> + */ + +#define _DEFAULT_SOURCE /* for snprintf() */ +#include "cairoint.h" + +#include "cairo-pdf.h" +#include "cairo-pdf-surface-private.h" +#include "cairo-pdf-operators-private.h" +#include "cairo-pdf-shading-private.h" + +#include "cairo-array-private.h" +#include "cairo-analysis-surface-private.h" +#include "cairo-composite-rectangles-private.h" +#include "cairo-default-context-private.h" +#include "cairo-error-private.h" +#include "cairo-image-surface-inline.h" +#include "cairo-image-info-private.h" +#include "cairo-recording-surface-private.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-surface-subsurface-private.h" +#include "cairo-type3-glyph-surface-private.h" + +#include <zlib.h> + +/* + * Page Structure of the Generated PDF: + * + * Each page requiring fallbacks images contains a knockout group at + * the top level. The first operation of the knockout group paints a + * group containing all the supported drawing operations. Fallback + * images (if any) are painted in the knockout group. This ensures + * that fallback images do not composite with any content under the + * fallback images. + * + * Streams: + * + * This PDF surface has three types of streams: + * - PDF Stream + * - Content Stream + * - Group Stream + * + * Calling _cairo_output_stream_printf (surface->output, ...) will + * write to the currently open stream. + * + * PDF Stream: + * A PDF Stream may be opened and closed with the following functions: + * _cairo_pdf_surface_open stream () + * _cairo_pdf_surface_close_stream () + * + * PDF Streams are written directly to the PDF file. They are used for + * fonts, images and patterns. + * + * Content Stream: + * The Content Stream is opened and closed with the following functions: + * _cairo_pdf_surface_open_content_stream () + * _cairo_pdf_surface_close_content_stream () + * + * The Content Stream contains the text and graphics operators. + * + * Group Stream: + * A Group Stream may be opened and closed with the following functions: + * _cairo_pdf_surface_open_group () + * _cairo_pdf_surface_close_group () + * + * A Group Stream is a Form XObject. It is used for short sequences + * of operators. As the content is very short the group is stored in + * memory until it is closed. This allows some optimization such as + * including the Resource dictionary and stream length inside the + * XObject instead of using an indirect object. + */ + +/** + * SECTION:cairo-pdf + * @Title: PDF Surfaces + * @Short_Description: Rendering PDF documents + * @See_Also: #cairo_surface_t + * + * The PDF surface is used to render cairo graphics to Adobe + * PDF files and is a multi-page vector surface backend. + * + * The following mime types are supported: %CAIRO_MIME_TYPE_JPEG, + * %CAIRO_MIME_TYPE_JP2, %CAIRO_MIME_TYPE_UNIQUE_ID, + * %CAIRO_MIME_TYPE_JBIG2, %CAIRO_MIME_TYPE_JBIG2_GLOBAL, + * %CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID, + * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS. + * + * # JBIG2 Images # + * JBIG2 data in PDF must be in the embedded format as described in + * ISO/IEC 11544. Image specific JBIG2 data must be in + * %CAIRO_MIME_TYPE_JBIG2. Any global segments in the JBIG2 data + * (segments with page association field set to 0) must be in + * %CAIRO_MIME_TYPE_JBIG2_GLOBAL. The global data may be shared by + * multiple images. All images sharing the same global data must set + * %CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID to a unique identifier. At least + * one of the images must provide the global data using + * %CAIRO_MIME_TYPE_JBIG2_GLOBAL. The global data will only be + * embedded once and shared by all JBIG2 images with the same + * %CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID. + * + * # CCITT Fax Images # {#ccitt} + * The %CAIRO_MIME_TYPE_CCITT_FAX mime data requires a number of decoding + * parameters These parameters are specified using %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS. + * + * %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS mime data must contain a string of the form + * "param1=value1 param2=value2 ...". + * + * @Columns: [required] An integer specifying the width of the image in pixels. + * + * @Rows: [required] An integer specifying the height of the image in scan lines. + * + * @K: [optional] An integer identifying the encoding scheme used. < 0 + * is 2 dimensional Group 4, = 0 is Group3 1 dimensional, > 0 is mixed 1 + * and 2 dimensional encoding. Default is 0. + * + * @EndOfLine: [optional] If true end-of-line bit patterns are present. Default is false. + * + * @EncodedByteAlign: [optional] If true the end of line is padded + * with 0 bits so the next line begins on a byte boundary. Default is false. + * + * @EndOfBlock: [optional] If true the data contains an end-of-block pattern. Default is true. + * + * @BlackIs1: [optional] If true 1 bits are black pixels. Default is false. + * + * @DamagedRowsBeforeError: [optional] An integer specifying the + * number of damages rows tolerated before an error occurs. Default is 0. + * + * Boolean values may be "true" or "false", or 1 or 0. + * + * These parameters are the same as the CCITTFaxDecode parameters in the + * [PostScript Language Reference](https://www.adobe.com/products/postscript/pdfs/PLRM.pdf) + * and [Portable Document Format (PDF)](https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf). + * Refer to these documents for further details. + * + * An example %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS string is: + * + * <programlisting> + * "Columns=10230 Rows=40000 K=1 EndOfLine=true EncodedByteAlign=1 BlackIs1=false" + * </programlisting> + * + **/ + +static cairo_bool_t +_cairo_pdf_surface_get_extents (void *abstract_surface, + cairo_rectangle_int_t *rectangle); + +/** + * CAIRO_HAS_PDF_SURFACE: + * + * Defined if the PDF surface backend is available. + * This macro can be used to conditionally compile backend-specific code. + * + * Since: 1.2 + **/ + +static const cairo_pdf_version_t _cairo_pdf_versions[] = +{ + CAIRO_PDF_VERSION_1_4, + CAIRO_PDF_VERSION_1_5 +}; + +#define CAIRO_PDF_VERSION_LAST ARRAY_LENGTH (_cairo_pdf_versions) + +static const char * _cairo_pdf_version_strings[CAIRO_PDF_VERSION_LAST] = +{ + "PDF 1.4", + "PDF 1.5" +}; + +static const char *_cairo_pdf_supported_mime_types[] = +{ + CAIRO_MIME_TYPE_JPEG, + CAIRO_MIME_TYPE_JP2, + CAIRO_MIME_TYPE_UNIQUE_ID, + CAIRO_MIME_TYPE_JBIG2, + CAIRO_MIME_TYPE_JBIG2_GLOBAL, + CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID, + CAIRO_MIME_TYPE_CCITT_FAX, + CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, + NULL +}; + +typedef struct _cairo_pdf_object { + long offset; +} cairo_pdf_object_t; + +typedef struct _cairo_pdf_font { + unsigned int font_id; + unsigned int subset_id; + cairo_pdf_resource_t subset_resource; +} cairo_pdf_font_t; + +typedef struct _cairo_pdf_rgb_linear_function { + cairo_pdf_resource_t resource; + double color1[3]; + double color2[3]; +} cairo_pdf_rgb_linear_function_t; + +typedef struct _cairo_pdf_alpha_linear_function { + cairo_pdf_resource_t resource; + double alpha1; + double alpha2; +} cairo_pdf_alpha_linear_function_t; + +static void +_cairo_pdf_surface_clear (cairo_pdf_surface_t *surface); + +static void +_cairo_pdf_smask_group_destroy (cairo_pdf_smask_group_t *group); + +static cairo_int_status_t +_cairo_pdf_surface_add_font (unsigned int font_id, + unsigned int subset_id, + void *closure); + +static void +_cairo_pdf_group_resources_init (cairo_pdf_group_resources_t *res); + +static cairo_int_status_t +_cairo_pdf_surface_open_stream (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t *resource, + cairo_bool_t compressed, + const char *fmt, + ...) CAIRO_PRINTF_FORMAT(4, 5); +static cairo_int_status_t +_cairo_pdf_surface_close_stream (cairo_pdf_surface_t *surface); + +static cairo_int_status_t +_cairo_pdf_surface_emit_surface (cairo_pdf_surface_t *surface, + cairo_pdf_source_surface_t *source, + cairo_bool_t test, + cairo_bool_t *is_image); + +static cairo_int_status_t +_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface); + +static void +_cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface); + +static cairo_pdf_resource_t +_cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface); + +static long +_cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface); + +static cairo_int_status_t +_cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface, + cairo_bool_t finish); + +static cairo_int_status_t +_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface); + +static cairo_int_status_t +_cairo_pdf_surface_emit_font_subsets (cairo_pdf_surface_t *surface); + +static cairo_bool_t +_cairo_pdf_source_surface_equal (const void *key_a, const void *key_b); + +static const cairo_surface_backend_t cairo_pdf_surface_backend; +static const cairo_paginated_surface_backend_t cairo_pdf_surface_paginated_backend; + +cairo_pdf_resource_t +_cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface) +{ + cairo_pdf_resource_t resource; + cairo_int_status_t status; + cairo_pdf_object_t object; + + object.offset = _cairo_output_stream_get_position (surface->output); + + status = _cairo_array_append (&surface->objects, &object); + if (unlikely (status)) { + resource.id = 0; + return resource; + } + + resource = surface->next_available_resource; + surface->next_available_resource.id++; + + return resource; +} + +void +_cairo_pdf_surface_update_object (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t resource) +{ + cairo_pdf_object_t *object; + + object = _cairo_array_index (&surface->objects, resource.id - 1); + object->offset = _cairo_output_stream_get_position (surface->output); +} + +static void +_cairo_pdf_surface_set_size_internal (cairo_pdf_surface_t *surface, + double width, + double height) +{ + surface->width = width; + surface->height = height; + surface->surface_extents.x = 0; + surface->surface_extents.y = 0; + surface->surface_extents.width = ceil (surface->width); + surface->surface_extents.height = ceil (surface->height); +} + +static cairo_bool_t +_path_covers_bbox (cairo_pdf_surface_t *surface, + cairo_path_fixed_t *path) +{ + cairo_box_t box; + + return _cairo_path_fixed_is_box (path, &box) && + box.p1.x <= 0 && + box.p1.y <= 0 && + box.p2.x >= _cairo_fixed_from_double (surface->width) && + box.p2.y >= _cairo_fixed_from_double (surface->height); +} + +static cairo_status_t +_cairo_pdf_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_pdf_surface_t *surface = cairo_container_of (clipper, + cairo_pdf_surface_t, + clipper); + cairo_int_status_t status; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + if (path == NULL) { + _cairo_output_stream_printf (surface->output, "Q q\n"); + + surface->current_pattern_is_solid_color = FALSE; + _cairo_pdf_operators_reset (&surface->pdf_operators); + + return CAIRO_STATUS_SUCCESS; + } + + if (_path_covers_bbox (surface, path)) + return CAIRO_STATUS_SUCCESS; + + return _cairo_pdf_operators_clip (&surface->pdf_operators, path, fill_rule); +} + +static cairo_surface_t * +_cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output, + double width, + double height) +{ + cairo_pdf_surface_t *surface; + cairo_status_t status, status_ignored; + + surface = _cairo_malloc (sizeof (cairo_pdf_surface_t)); + if (unlikely (surface == NULL)) { + /* destroy stream on behalf of caller */ + status = _cairo_output_stream_destroy (output); + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + } + + _cairo_surface_init (&surface->base, + &cairo_pdf_surface_backend, + NULL, /* device */ + CAIRO_CONTENT_COLOR_ALPHA, + TRUE); /* is_vector */ + + surface->output = output; + surface->width = width; + surface->height = height; + cairo_matrix_init (&surface->cairo_to_pdf, 1, 0, 0, 1, 0, 0); + surface->in_xobject = FALSE; + surface->surface_extents.x = 0; + surface->surface_extents.y = 0; + surface->surface_extents.width = ceil (surface->width); + surface->surface_extents.height = ceil (surface->height); + surface->surface_bounded = TRUE; + + _cairo_array_init (&surface->objects, sizeof (cairo_pdf_object_t)); + _cairo_array_init (&surface->pages, sizeof (cairo_pdf_resource_t)); + _cairo_array_init (&surface->rgb_linear_functions, sizeof (cairo_pdf_rgb_linear_function_t)); + _cairo_array_init (&surface->alpha_linear_functions, sizeof (cairo_pdf_alpha_linear_function_t)); + _cairo_array_init (&surface->fonts, sizeof (cairo_pdf_font_t)); + _cairo_array_init (&surface->smask_groups, sizeof (cairo_pdf_smask_group_t *)); + _cairo_array_init (&surface->knockout_group, sizeof (cairo_pdf_resource_t)); + + _cairo_array_init (&surface->page_patterns, sizeof (cairo_pdf_pattern_t)); + _cairo_array_init (&surface->page_surfaces, sizeof (cairo_pdf_source_surface_t)); + _cairo_array_init (&surface->doc_surfaces, sizeof (cairo_pdf_source_surface_t)); + _cairo_array_init (&surface->jbig2_global, sizeof (cairo_pdf_jbig2_global_t)); + _cairo_array_init (&surface->page_heights, sizeof (double)); + surface->all_surfaces = _cairo_hash_table_create (_cairo_pdf_source_surface_equal); + if (unlikely (surface->all_surfaces == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto BAIL0; + } + + _cairo_pdf_group_resources_init (&surface->resources); + + surface->font_subsets = _cairo_scaled_font_subsets_create_composite (); + if (! surface->font_subsets) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto BAIL1; + } + + _cairo_scaled_font_subsets_enable_latin_subset (surface->font_subsets, TRUE); + + surface->next_available_resource.id = 1; + surface->pages_resource = _cairo_pdf_surface_new_object (surface); + if (surface->pages_resource.id == 0) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto BAIL2; + } + + surface->struct_tree_root.id = 0; + surface->pdf_version = CAIRO_PDF_VERSION_1_5; + surface->compress_content = TRUE; + surface->pdf_stream.active = FALSE; + surface->pdf_stream.old_output = NULL; + surface->group_stream.active = FALSE; + surface->group_stream.stream = NULL; + surface->group_stream.mem_stream = NULL; + + surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE; + + surface->force_fallbacks = FALSE; + surface->select_pattern_gstate_saved = FALSE; + surface->current_pattern_is_solid_color = FALSE; + surface->current_operator = CAIRO_OPERATOR_OVER; + surface->header_emitted = FALSE; + + _cairo_surface_clipper_init (&surface->clipper, + _cairo_pdf_surface_clipper_intersect_clip_path); + + _cairo_pdf_operators_init (&surface->pdf_operators, + surface->output, + &surface->cairo_to_pdf, + surface->font_subsets, + FALSE); + _cairo_pdf_operators_set_font_subsets_callback (&surface->pdf_operators, + _cairo_pdf_surface_add_font, + surface); + _cairo_pdf_operators_enable_actual_text(&surface->pdf_operators, TRUE); + + status = _cairo_pdf_interchange_init (surface); + if (unlikely (status)) + goto BAIL2; + + surface->page_parent_tree = -1; + _cairo_array_init (&surface->page_annots, sizeof (cairo_pdf_resource_t)); + surface->tagged = FALSE; + surface->current_page_label = NULL; + _cairo_array_init (&surface->page_labels, sizeof (char *)); + surface->outlines_dict_res.id = 0; + surface->names_dict_res.id = 0; + surface->docinfo_res.id = 0; + surface->page_labels_res.id = 0; + surface->thumbnail_width = 0; + surface->thumbnail_height = 0; + surface->thumbnail_image = NULL; + + if (getenv ("CAIRO_DEBUG_PDF") != NULL) + surface->compress_content = FALSE; + + surface->paginated_surface = _cairo_paginated_surface_create ( + &surface->base, + CAIRO_CONTENT_COLOR_ALPHA, + &cairo_pdf_surface_paginated_backend); + + status = surface->paginated_surface->status; + if (status == CAIRO_STATUS_SUCCESS) { + /* paginated keeps the only reference to surface now, drop ours */ + cairo_surface_destroy (&surface->base); + return surface->paginated_surface; + } + +BAIL2: + _cairo_scaled_font_subsets_destroy (surface->font_subsets); +BAIL1: + _cairo_hash_table_destroy (surface->all_surfaces); +BAIL0: + _cairo_array_fini (&surface->objects); + free (surface); + + /* destroy stream on behalf of caller */ + status_ignored = _cairo_output_stream_destroy (output); + + return _cairo_surface_create_in_error (status); +} + +/** + * cairo_pdf_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 PDF 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_pdf_surface_create_for_stream (cairo_write_func_t write_func, + void *closure, + double width_in_points, + double height_in_points) +{ + cairo_output_stream_t *output; + + output = _cairo_output_stream_create (write_func, NULL, closure); + if (_cairo_output_stream_get_status (output)) + return _cairo_surface_create_in_error (_cairo_output_stream_destroy (output)); + + return _cairo_pdf_surface_create_for_stream_internal (output, + width_in_points, + height_in_points); +} + +/** + * cairo_pdf_surface_create: + * @filename: a filename for the PDF output (must be writable), %NULL may be + * used to specify no output. This will generate a PDF 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 PDF surface of the specified size in points to be written + * to @filename. + * + * 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_pdf_surface_create (const char *filename, + double width_in_points, + double height_in_points) +{ + cairo_output_stream_t *output; + + output = _cairo_output_stream_create_for_filename (filename); + if (_cairo_output_stream_get_status (output)) + return _cairo_surface_create_in_error (_cairo_output_stream_destroy (output)); + + return _cairo_pdf_surface_create_for_stream_internal (output, + width_in_points, + height_in_points); +} + +static cairo_bool_t +_cairo_surface_is_pdf (cairo_surface_t *surface) +{ + return surface->backend == &cairo_pdf_surface_backend; +} + +/* If the abstract_surface is a paginated surface, and that paginated + * surface's target is a pdf_surface, then set pdf_surface to that + * target. Otherwise return FALSE. + */ +static cairo_bool_t +_extract_pdf_surface (cairo_surface_t *surface, + cairo_pdf_surface_t **pdf_surface) +{ + cairo_surface_t *target; + cairo_status_t status_ignored; + + if (surface->status) + return FALSE; + if (surface->finished) { + status_ignored = _cairo_surface_set_error (surface, + _cairo_error (CAIRO_STATUS_SURFACE_FINISHED)); + return FALSE; + } + + if (! _cairo_surface_is_paginated (surface)) { + status_ignored = _cairo_surface_set_error (surface, + _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); + return FALSE; + } + + target = _cairo_paginated_surface_get_target (surface); + if (target->status) { + status_ignored = _cairo_surface_set_error (surface, + target->status); + return FALSE; + } + if (target->finished) { + status_ignored = _cairo_surface_set_error (surface, + _cairo_error (CAIRO_STATUS_SURFACE_FINISHED)); + return FALSE; + } + + if (! _cairo_surface_is_pdf (target)) { + status_ignored = _cairo_surface_set_error (surface, + _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); + return FALSE; + } + + *pdf_surface = (cairo_pdf_surface_t *) target; + return TRUE; +} + +/** + * cairo_pdf_surface_restrict_to_version: + * @surface: a PDF #cairo_surface_t + * @version: PDF version + * + * Restricts the generated PDF file to @version. See cairo_pdf_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.10 + **/ +void +cairo_pdf_surface_restrict_to_version (cairo_surface_t *abstract_surface, + cairo_pdf_version_t version) +{ + cairo_pdf_surface_t *surface = NULL; /* hide compiler warning */ + + if (! _extract_pdf_surface (abstract_surface, &surface)) + return; + + if (version < CAIRO_PDF_VERSION_LAST) + surface->pdf_version = version; + + _cairo_pdf_operators_enable_actual_text(&surface->pdf_operators, + version >= CAIRO_PDF_VERSION_1_5); +} + +/** + * cairo_pdf_get_versions: + * @versions: supported version list + * @num_versions: list length + * + * Used to retrieve the list of supported versions. See + * cairo_pdf_surface_restrict_to_version(). + * + * Since: 1.10 + **/ +void +cairo_pdf_get_versions (cairo_pdf_version_t const **versions, + int *num_versions) +{ + if (versions != NULL) + *versions = _cairo_pdf_versions; + + if (num_versions != NULL) + *num_versions = CAIRO_PDF_VERSION_LAST; +} + +/** + * cairo_pdf_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_pdf_get_versions() + * for a way to get the list of valid version ids. + * + * Return value: the string associated to given version. + * + * Since: 1.10 + **/ +const char * +cairo_pdf_version_to_string (cairo_pdf_version_t version) +{ + if (version >= CAIRO_PDF_VERSION_LAST) + return NULL; + + return _cairo_pdf_version_strings[version]; +} + +/** + * cairo_pdf_surface_set_size: + * @surface: a PDF #cairo_surface_t + * @width_in_points: new surface width, in points (1 point == 1/72.0 inch) + * @height_in_points: new surface height, in points (1 point == 1/72.0 inch) + * + * Changes the size of a PDF surface for the current (and + * subsequent) pages. + * + * This function should only be called before any drawing operations + * have been performed on the current page. The simplest way to do + * this is to call this function immediately after creating the + * surface or immediately after completing a page with either + * cairo_show_page() or cairo_copy_page(). + * + * Since: 1.2 + **/ +void +cairo_pdf_surface_set_size (cairo_surface_t *surface, + double width_in_points, + double height_in_points) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + cairo_status_t status; + + if (! _extract_pdf_surface (surface, &pdf_surface)) + return; + + _cairo_pdf_surface_set_size_internal (pdf_surface, + width_in_points, + height_in_points); + status = _cairo_paginated_surface_set_size (pdf_surface->paginated_surface, + width_in_points, + height_in_points); + if (status) + status = _cairo_surface_set_error (surface, status); +} + +/** + * CAIRO_PDF_OUTLINE_ROOT: + * + * The root outline item in cairo_pdf_surface_add_outline(). + * + * Since: 1.16 + **/ + +/** + * cairo_pdf_surface_add_outline: + * @surface: a PDF #cairo_surface_t + * @parent_id: the id of the parent item or %CAIRO_PDF_OUTLINE_ROOT if this is a top level item. + * @utf8: the name of the outline + * @link_attribs: the link attributes specifying where this outline links to + * @flags: outline item flags + * + * Add an item to the document outline hierarchy with the name @utf8 + * that links to the location specified by @link_attribs. Link + * attributes have the same keys and values as the [Link Tag][link], + * excluding the "rect" attribute. The item will be a child of the + * item with id @parent_id. Use %CAIRO_PDF_OUTLINE_ROOT as the parent + * id of top level items. + * + * Return value: the id for the added item. + * + * Since: 1.16 + **/ +int +cairo_pdf_surface_add_outline (cairo_surface_t *surface, + int parent_id, + const char *utf8, + const char *link_attribs, + cairo_pdf_outline_flags_t flags) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + cairo_status_t status; + int id = 0; + + if (! _extract_pdf_surface (surface, &pdf_surface)) + return 0; + + status = _cairo_pdf_interchange_add_outline (pdf_surface, + parent_id, + utf8, + link_attribs, + flags, + &id); + if (status) + status = _cairo_surface_set_error (surface, status); + + return id; +} + +/** + * cairo_pdf_surface_set_metadata: + * @surface: a PDF #cairo_surface_t + * @metadata: The metadata item to set. + * @utf8: metadata value + * + * Set document metadata. The %CAIRO_PDF_METADATA_CREATE_DATE and + * %CAIRO_PDF_METADATA_MOD_DATE values must be in ISO-8601 format: + * YYYY-MM-DDThh:mm:ss. An optional timezone of the form "[+/-]hh:mm" + * or "Z" for UTC time can be appended. All other metadata values can be any UTF-8 + * string. + * + * For example: + * <informalexample><programlisting> + * cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_TITLE, "My Document"); + * cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATE_DATE, "2015-12-31T23:59+02:00"); + * </programlisting></informalexample> + * + * Since: 1.16 + **/ +void +cairo_pdf_surface_set_metadata (cairo_surface_t *surface, + cairo_pdf_metadata_t metadata, + const char *utf8) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + cairo_status_t status; + + if (! _extract_pdf_surface (surface, &pdf_surface)) + return; + + status = _cairo_pdf_interchange_set_metadata (pdf_surface, metadata, utf8); + if (status) + status = _cairo_surface_set_error (surface, status); +} + +/** + * cairo_pdf_surface_set_page_label: + * @surface: a PDF #cairo_surface_t + * @utf8: The page label. + * + * Set page label for the current page. + * + * Since: 1.16 + **/ +void +cairo_pdf_surface_set_page_label (cairo_surface_t *surface, + const char *utf8) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + + if (! _extract_pdf_surface (surface, &pdf_surface)) + return; + + free (pdf_surface->current_page_label); + pdf_surface->current_page_label = utf8 ? strdup (utf8) : NULL; +} + +/** + * cairo_pdf_surface_set_thumbnail_size: + * @surface: a PDF #cairo_surface_t + * @width: Thumbnail width. + * @height: Thumbnail height + * + * Set the thumbnail image size for the current and all subsequent + * pages. Setting a width or height of 0 disables thumbnails for the + * current and subsequent pages. + * + * Since: 1.16 + **/ +void +cairo_pdf_surface_set_thumbnail_size (cairo_surface_t *surface, + int width, + int height) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + + if (! _extract_pdf_surface (surface, &pdf_surface)) + return; + + pdf_surface->thumbnail_width = width; + pdf_surface->thumbnail_height = height; +} + +static void +_cairo_pdf_surface_clear (cairo_pdf_surface_t *surface) +{ + int i, size; + cairo_pdf_pattern_t *pattern; + cairo_pdf_source_surface_t *src_surface; + cairo_pdf_smask_group_t *group; + + size = _cairo_array_num_elements (&surface->page_patterns); + for (i = 0; i < size; i++) { + pattern = (cairo_pdf_pattern_t *) _cairo_array_index (&surface->page_patterns, i); + cairo_pattern_destroy (pattern->pattern); + } + _cairo_array_truncate (&surface->page_patterns, 0); + + size = _cairo_array_num_elements (&surface->page_surfaces); + for (i = 0; i < size; i++) { + src_surface = (cairo_pdf_source_surface_t *) _cairo_array_index (&surface->page_surfaces, i); + cairo_surface_destroy (src_surface->surface); + } + _cairo_array_truncate (&surface->page_surfaces, 0); + + size = _cairo_array_num_elements (&surface->smask_groups); + for (i = 0; i < size; i++) { + _cairo_array_copy_element (&surface->smask_groups, i, &group); + _cairo_pdf_smask_group_destroy (group); + } + _cairo_array_truncate (&surface->smask_groups, 0); + _cairo_array_truncate (&surface->knockout_group, 0); + _cairo_array_truncate (&surface->page_annots, 0); + + cairo_surface_destroy (&surface->thumbnail_image->base); + surface->thumbnail_image = NULL; +} + +static void +_cairo_pdf_group_resources_init (cairo_pdf_group_resources_t *res) +{ + int i; + + for (i = 0; i < CAIRO_NUM_OPERATORS; i++) + res->operators[i] = FALSE; + + _cairo_array_init (&res->alphas, sizeof (double)); + _cairo_array_init (&res->smasks, sizeof (cairo_pdf_resource_t)); + _cairo_array_init (&res->patterns, sizeof (cairo_pdf_resource_t)); + _cairo_array_init (&res->shadings, sizeof (cairo_pdf_resource_t)); + _cairo_array_init (&res->xobjects, sizeof (cairo_pdf_resource_t)); + _cairo_array_init (&res->fonts, sizeof (cairo_pdf_font_t)); +} + +static void +_cairo_pdf_group_resources_fini (cairo_pdf_group_resources_t *res) +{ + _cairo_array_fini (&res->alphas); + _cairo_array_fini (&res->smasks); + _cairo_array_fini (&res->patterns); + _cairo_array_fini (&res->shadings); + _cairo_array_fini (&res->xobjects); + _cairo_array_fini (&res->fonts); +} + +static void +_cairo_pdf_group_resources_clear (cairo_pdf_group_resources_t *res) +{ + int i; + + for (i = 0; i < CAIRO_NUM_OPERATORS; i++) + res->operators[i] = FALSE; + + _cairo_array_truncate (&res->alphas, 0); + _cairo_array_truncate (&res->smasks, 0); + _cairo_array_truncate (&res->patterns, 0); + _cairo_array_truncate (&res->shadings, 0); + _cairo_array_truncate (&res->xobjects, 0); + _cairo_array_truncate (&res->fonts, 0); +} + +static void +_cairo_pdf_surface_add_operator (cairo_pdf_surface_t *surface, + cairo_operator_t op) +{ + cairo_pdf_group_resources_t *res = &surface->resources; + + res->operators[op] = TRUE; +} + +static cairo_int_status_t +_cairo_pdf_surface_add_alpha (cairo_pdf_surface_t *surface, + double alpha, + int *index) +{ + int num_alphas, i; + double other; + cairo_int_status_t status; + cairo_pdf_group_resources_t *res = &surface->resources; + + num_alphas = _cairo_array_num_elements (&res->alphas); + for (i = 0; i < num_alphas; i++) { + _cairo_array_copy_element (&res->alphas, i, &other); + if (alpha == other) { + *index = i; + return CAIRO_STATUS_SUCCESS; + } + } + + status = _cairo_array_append (&res->alphas, &alpha); + if (unlikely (status)) + return status; + + *index = _cairo_array_num_elements (&res->alphas) - 1; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_add_smask (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t smask) +{ + return _cairo_array_append (&(surface->resources.smasks), &smask); +} + +static cairo_int_status_t +_cairo_pdf_surface_add_pattern (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t pattern) +{ + return _cairo_array_append (&(surface->resources.patterns), &pattern); +} + +static cairo_int_status_t +_cairo_pdf_surface_add_shading (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t shading) +{ + return _cairo_array_append (&(surface->resources.shadings), &shading); +} + + +static cairo_int_status_t +_cairo_pdf_surface_add_xobject (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t xobject) +{ + return _cairo_array_append (&(surface->resources.xobjects), &xobject); +} + +static cairo_int_status_t +_cairo_pdf_surface_add_font (unsigned int font_id, + unsigned int subset_id, + void *closure) +{ + cairo_pdf_surface_t *surface = closure; + cairo_pdf_font_t font; + int num_fonts, i; + cairo_int_status_t status; + cairo_pdf_group_resources_t *res = &surface->resources; + + num_fonts = _cairo_array_num_elements (&res->fonts); + for (i = 0; i < num_fonts; i++) { + _cairo_array_copy_element (&res->fonts, i, &font); + if (font.font_id == font_id && + font.subset_id == subset_id) + return CAIRO_STATUS_SUCCESS; + } + + num_fonts = _cairo_array_num_elements (&surface->fonts); + for (i = 0; i < num_fonts; i++) { + _cairo_array_copy_element (&surface->fonts, i, &font); + if (font.font_id == font_id && + font.subset_id == subset_id) + return _cairo_array_append (&res->fonts, &font); + } + + font.font_id = font_id; + font.subset_id = subset_id; + font.subset_resource = _cairo_pdf_surface_new_object (surface); + if (font.subset_resource.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + status = _cairo_array_append (&surface->fonts, &font); + if (unlikely (status)) + return status; + + return _cairo_array_append (&res->fonts, &font); +} + +static cairo_pdf_resource_t +_cairo_pdf_surface_get_font_resource (cairo_pdf_surface_t *surface, + unsigned int font_id, + unsigned int subset_id) +{ + cairo_pdf_font_t font; + int num_fonts, i; + + num_fonts = _cairo_array_num_elements (&surface->fonts); + for (i = 0; i < num_fonts; i++) { + _cairo_array_copy_element (&surface->fonts, i, &font); + if (font.font_id == font_id && font.subset_id == subset_id) + return font.subset_resource; + } + + font.subset_resource.id = 0; + return font.subset_resource; +} + +static const char * +_cairo_operator_to_pdf_blend_mode (cairo_operator_t op) +{ + switch (op) { + /* The extend blend mode operators */ + case CAIRO_OPERATOR_MULTIPLY: return "Multiply"; + case CAIRO_OPERATOR_SCREEN: return "Screen"; + case CAIRO_OPERATOR_OVERLAY: return "Overlay"; + case CAIRO_OPERATOR_DARKEN: return "Darken"; + case CAIRO_OPERATOR_LIGHTEN: return "Lighten"; + case CAIRO_OPERATOR_COLOR_DODGE: return "ColorDodge"; + case CAIRO_OPERATOR_COLOR_BURN: return "ColorBurn"; + case CAIRO_OPERATOR_HARD_LIGHT: return "HardLight"; + case CAIRO_OPERATOR_SOFT_LIGHT: return "SoftLight"; + case CAIRO_OPERATOR_DIFFERENCE: return "Difference"; + case CAIRO_OPERATOR_EXCLUSION: return "Exclusion"; + case CAIRO_OPERATOR_HSL_HUE: return "Hue"; + case CAIRO_OPERATOR_HSL_SATURATION: return "Saturation"; + case CAIRO_OPERATOR_HSL_COLOR: return "Color"; + case CAIRO_OPERATOR_HSL_LUMINOSITY: return "Luminosity"; + + default: + /* The original Porter-Duff set */ + case CAIRO_OPERATOR_CLEAR: + case CAIRO_OPERATOR_SOURCE: + case CAIRO_OPERATOR_OVER: + case CAIRO_OPERATOR_IN: + case CAIRO_OPERATOR_OUT: + case CAIRO_OPERATOR_ATOP: + case CAIRO_OPERATOR_DEST: + case CAIRO_OPERATOR_DEST_OVER: + case CAIRO_OPERATOR_DEST_IN: + case CAIRO_OPERATOR_DEST_OUT: + case CAIRO_OPERATOR_DEST_ATOP: + case CAIRO_OPERATOR_XOR: + case CAIRO_OPERATOR_ADD: + case CAIRO_OPERATOR_SATURATE: + return "Normal"; + } +} + +static void +_cairo_pdf_surface_emit_group_resources (cairo_pdf_surface_t *surface, + cairo_pdf_group_resources_t *res) +{ + int num_alphas, num_smasks, num_resources, i; + double alpha; + cairo_pdf_resource_t *smask, *pattern, *shading, *xobject; + cairo_pdf_font_t *font; + + _cairo_output_stream_printf (surface->output, "<<\n"); + + num_alphas = _cairo_array_num_elements (&res->alphas); + num_smasks = _cairo_array_num_elements (&res->smasks); + if (num_alphas > 0 || num_smasks > 0) { + _cairo_output_stream_printf (surface->output, + " /ExtGState <<\n"); + + for (i = 0; i < CAIRO_NUM_OPERATORS; i++) { + if (res->operators[i]) { + _cairo_output_stream_printf (surface->output, + " /b%d << /BM /%s >>\n", + i, _cairo_operator_to_pdf_blend_mode(i)); + } + } + + for (i = 0; i < num_alphas; i++) { + _cairo_array_copy_element (&res->alphas, i, &alpha); + _cairo_output_stream_printf (surface->output, + " /a%d << /CA %f /ca %f >>\n", + i, alpha, alpha); + } + + for (i = 0; i < num_smasks; i++) { + smask = _cairo_array_index (&res->smasks, i); + _cairo_output_stream_printf (surface->output, + " /s%d %d 0 R\n", + smask->id, smask->id); + } + + _cairo_output_stream_printf (surface->output, + " >>\n"); + } + + num_resources = _cairo_array_num_elements (&res->patterns); + if (num_resources > 0) { + _cairo_output_stream_printf (surface->output, + " /Pattern <<"); + for (i = 0; i < num_resources; i++) { + pattern = _cairo_array_index (&res->patterns, i); + _cairo_output_stream_printf (surface->output, + " /p%d %d 0 R", + pattern->id, pattern->id); + } + + _cairo_output_stream_printf (surface->output, + " >>\n"); + } + + num_resources = _cairo_array_num_elements (&res->shadings); + if (num_resources > 0) { + _cairo_output_stream_printf (surface->output, + " /Shading <<"); + for (i = 0; i < num_resources; i++) { + shading = _cairo_array_index (&res->shadings, i); + _cairo_output_stream_printf (surface->output, + " /sh%d %d 0 R", + shading->id, shading->id); + } + + _cairo_output_stream_printf (surface->output, + " >>\n"); + } + + num_resources = _cairo_array_num_elements (&res->xobjects); + if (num_resources > 0) { + _cairo_output_stream_printf (surface->output, + " /XObject <<"); + + for (i = 0; i < num_resources; i++) { + xobject = _cairo_array_index (&res->xobjects, i); + _cairo_output_stream_printf (surface->output, + " /x%d %d 0 R", + xobject->id, xobject->id); + } + + _cairo_output_stream_printf (surface->output, + " >>\n"); + } + + num_resources = _cairo_array_num_elements (&res->fonts); + if (num_resources > 0) { + _cairo_output_stream_printf (surface->output," /Font <<\n"); + for (i = 0; i < num_resources; i++) { + font = _cairo_array_index (&res->fonts, i); + _cairo_output_stream_printf (surface->output, + " /f-%d-%d %d 0 R\n", + font->font_id, + font->subset_id, + font->subset_resource.id); + } + _cairo_output_stream_printf (surface->output, " >>\n"); + } + + _cairo_output_stream_printf (surface->output, + ">>\n"); +} + +static cairo_pdf_smask_group_t * +_cairo_pdf_surface_create_smask_group (cairo_pdf_surface_t *surface, + const cairo_rectangle_int_t *extents) +{ + cairo_pdf_smask_group_t *group; + + group = calloc (1, sizeof (cairo_pdf_smask_group_t)); + if (unlikely (group == NULL)) { + _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); + return NULL; + } + + group->group_res = _cairo_pdf_surface_new_object (surface); + if (group->group_res.id == 0) { + _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); + free (group); + return NULL; + } + group->width = surface->width; + group->height = surface->height; + if (extents != NULL) { + group->extents = *extents; + } else { + group->extents.x = 0; + group->extents.y = 0; + group->extents.width = surface->width; + group->extents.height = surface->height; + } + + return group; +} + +static void +_cairo_pdf_smask_group_destroy (cairo_pdf_smask_group_t *group) +{ + if (group->operation == PDF_FILL || group->operation == PDF_STROKE) + _cairo_path_fixed_fini (&group->path); + if (group->source) + cairo_pattern_destroy (group->source); + if (group->mask) + cairo_pattern_destroy (group->mask); + free (group->utf8); + free (group->glyphs); + free (group->clusters); + if (group->scaled_font) + cairo_scaled_font_destroy (group->scaled_font); + free (group); +} + +static cairo_int_status_t +_cairo_pdf_surface_add_smask_group (cairo_pdf_surface_t *surface, + cairo_pdf_smask_group_t *group) +{ + return _cairo_array_append (&surface->smask_groups, &group); +} + +static cairo_bool_t +_cairo_pdf_source_surface_equal (const void *key_a, const void *key_b) +{ + const cairo_pdf_source_surface_entry_t *a = key_a; + const cairo_pdf_source_surface_entry_t *b = key_b; + + if (a->interpolate != b->interpolate) + return FALSE; + + 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_pdf_source_surface_init_key (cairo_pdf_source_surface_entry_t *key) +{ + if (key->unique_id && key->unique_id_length > 0) { + key->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE, + key->unique_id, key->unique_id_length); + } else { + key->base.hash = key->id; + } +} + +static cairo_int_status_t +_cairo_pdf_surface_acquire_source_image_from_pattern (cairo_pdf_surface_t *surface, + const cairo_pattern_t *pattern, + cairo_image_surface_t **image, + void **image_extra) +{ + switch (pattern->type) { + case CAIRO_PATTERN_TYPE_SURFACE: { + cairo_surface_pattern_t *surf_pat = (cairo_surface_pattern_t *) pattern; + return _cairo_surface_acquire_source_image (surf_pat->surface, image, image_extra); + } break; + + case CAIRO_PATTERN_TYPE_RASTER_SOURCE: { + cairo_surface_t *surf; + surf = _cairo_raster_source_pattern_acquire (pattern, &surface->base, NULL); + if (!surf) + return CAIRO_INT_STATUS_UNSUPPORTED; + assert (_cairo_surface_is_image (surf)); + *image = (cairo_image_surface_t *) surf; + } break; + + case CAIRO_PATTERN_TYPE_SOLID: + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: + default: + ASSERT_NOT_REACHED; + break; + } + + return CAIRO_STATUS_SUCCESS; +} + +static void +_cairo_pdf_surface_release_source_image_from_pattern (cairo_pdf_surface_t *surface, + const cairo_pattern_t *pattern, + cairo_image_surface_t *image, + void *image_extra) +{ + switch (pattern->type) { + case CAIRO_PATTERN_TYPE_SURFACE: { + cairo_surface_pattern_t *surf_pat = (cairo_surface_pattern_t *) pattern; + _cairo_surface_release_source_image (surf_pat->surface, image, image_extra); + } break; + + case CAIRO_PATTERN_TYPE_RASTER_SOURCE: + _cairo_raster_source_pattern_release (pattern, &image->base); + break; + + case CAIRO_PATTERN_TYPE_SOLID: + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: + default: + + ASSERT_NOT_REACHED; + break; + } +} + +static cairo_int_status_t +_get_source_surface_extents (cairo_surface_t *source, + cairo_rectangle_int_t *extents, + cairo_bool_t *bounded, + cairo_bool_t *subsurface) +{ + cairo_int_status_t status; + + *bounded = TRUE; + *subsurface = FALSE; + if (source->type == CAIRO_SURFACE_TYPE_RECORDING) { + cairo_surface_t *free_me = NULL; + + if (_cairo_surface_is_snapshot (source)) + free_me = source = _cairo_surface_snapshot_get_target (source); + + if (source->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) { + cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) source; + + *extents = sub->extents; + *subsurface = TRUE; + } else { + cairo_box_t box; + + *bounded = _cairo_surface_get_extents (source, extents); + if (! *bounded) { + status = _cairo_recording_surface_get_ink_bbox ((cairo_recording_surface_t *)source, + &box, NULL); + if (unlikely (status)) { + cairo_surface_destroy (free_me); + return status; + } + _cairo_box_round_to_rectangle (&box, extents); + } + } + cairo_surface_destroy (free_me); + } else { + *bounded = _cairo_surface_get_extents (source, extents); + } + + return CAIRO_STATUS_SUCCESS; +} + +/** + * _cairo_pdf_surface_add_source_surface: + * @surface: [in] the pdf surface + * @source_surface: [in] A #cairo_surface_t to use as the source surface + * @source_pattern: [in] A #cairo_pattern_t of type SURFACE or RASTER_SOURCE to use as the source + * @op: [in] the operator used to composite this source + * @filter: [in] filter type of the source pattern + * @stencil_mask: [in] if true, the surface will be written to the PDF as an /ImageMask + * @smask: [in] if true, only the alpha channel will be written (images only) + * @need_transp_group: [in] if true and an XObject is used, make it a Transparency group + * @extents: [in] extents of the operation that is using this source + * @smask_res: [in] if not NULL, the image written will specify this resource as the smask for + * the image (images only) + * @pdf_source: [out] return pdf_source_surface entry in hash table + * @x_offset: [out] if not NULL return x offset of surface + * @y_offset: [out] if not NULL return y offset of surface + * @source_extents: [out] if not NULL return operation extents in source space + * + * Add surface or raster_source pattern to list of surfaces to be + * written to the PDF file when the current page is finished. Returns + * a PDF resource to reference the surface. A hash table of all + * surfaces in the PDF file (keyed by CAIRO_MIME_TYPE_UNIQUE_ID or + * surface unique_id) is used to ensure surfaces with the same id are + * only written once to the PDF file. + * + * Only one of @source_pattern or @source_surface is to be + * specified. Set the other to NULL. + **/ +static cairo_int_status_t +_cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface, + cairo_surface_t *source_surface, + const cairo_pattern_t *source_pattern, + cairo_operator_t op, + cairo_filter_t filter, + cairo_bool_t stencil_mask, + cairo_bool_t smask, + cairo_bool_t need_transp_group, + const cairo_rectangle_int_t *extents, + cairo_pdf_resource_t *smask_res, + cairo_pdf_source_surface_entry_t **pdf_source, + double *x_offset, + double *y_offset, + cairo_rectangle_int_t *source_extents) +{ + cairo_pdf_source_surface_t src_surface; + cairo_pdf_source_surface_entry_t surface_key; + cairo_pdf_source_surface_entry_t *surface_entry; + cairo_int_status_t status = CAIRO_STATUS_SUCCESS; + cairo_bool_t interpolate; + unsigned char *unique_id = NULL; + unsigned long unique_id_length = 0; + cairo_image_surface_t *image; + void *image_extra; + cairo_box_t box; + cairo_rectangle_int_t op_extents; + double x, y; + cairo_bool_t subsurface; + + switch (filter) { + default: + case CAIRO_FILTER_GOOD: + case CAIRO_FILTER_BEST: + case CAIRO_FILTER_BILINEAR: + interpolate = TRUE; + break; + case CAIRO_FILTER_FAST: + case CAIRO_FILTER_NEAREST: + case CAIRO_FILTER_GAUSSIAN: + interpolate = FALSE; + break; + } + + x = 0; + y = 0; + if (source_pattern) { + if (source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) { + status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, source_pattern, + &image, &image_extra); + if (unlikely (status)) + return status; + source_surface = &image->base; + cairo_surface_get_device_offset (source_surface, &x, &y); + } else { + cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source_pattern; + source_surface = surface_pattern->surface; + } + } + if (x_offset) + *x_offset = x; + if (y_offset) + *y_offset = y; + + /* transform operation extents to pattern space */ + op_extents = *extents; + if (source_pattern) + { + _cairo_box_from_rectangle (&box, extents); + _cairo_matrix_transform_bounding_box_fixed (&source_pattern->matrix, &box, NULL); + _cairo_box_round_to_rectangle (&box, &op_extents); + } + if (source_extents) + *source_extents = op_extents; + + surface_key.id = source_surface->unique_id; + surface_key.interpolate = interpolate; + cairo_surface_get_mime_data (source_surface, CAIRO_MIME_TYPE_UNIQUE_ID, + (const unsigned char **) &surface_key.unique_id, + &surface_key.unique_id_length); + _cairo_pdf_source_surface_init_key (&surface_key); + surface_entry = _cairo_hash_table_lookup (surface->all_surfaces, &surface_key.base); + if (surface_entry) { + if (pdf_source) + *pdf_source = surface_entry; + + if (source_pattern && source_pattern->extend != CAIRO_EXTEND_NONE) + _cairo_unbounded_rectangle_init (&op_extents); + + _cairo_rectangle_intersect (&op_extents, &surface_entry->extents); + _cairo_rectangle_union (&surface_entry->required_extents, &op_extents); + status = CAIRO_STATUS_SUCCESS; + } + + if (status || surface_entry) { + if (source_pattern && source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) + _cairo_pdf_surface_release_source_image_from_pattern (surface, source_pattern, image, image_extra); + return status; + } + + if (surface_key.unique_id && surface_key.unique_id_length > 0) { + unique_id = _cairo_malloc (surface_key.unique_id_length); + if (unique_id == NULL) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto fail1; + } + + unique_id_length = surface_key.unique_id_length; + memcpy (unique_id, surface_key.unique_id, unique_id_length); + } else { + unique_id = NULL; + unique_id_length = 0; + } + + surface_entry = _cairo_malloc (sizeof (cairo_pdf_source_surface_entry_t)); + if (surface_entry == NULL) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto fail1; + } + + if (pdf_source) + *pdf_source = surface_entry; + surface_entry->id = surface_key.id; + surface_entry->operator = op; + surface_entry->interpolate = interpolate; + surface_entry->stencil_mask = stencil_mask; + surface_entry->smask = smask; + surface_entry->need_transp_group = need_transp_group; + surface_entry->unique_id_length = unique_id_length; + surface_entry->unique_id = unique_id; + if (smask_res) + surface_entry->smask_res = *smask_res; + else + surface_entry->smask_res.id = 0; + + status = _get_source_surface_extents (source_surface, + &surface_entry->extents, + &surface_entry->bounded, + &subsurface); + if (unlikely (status)) + goto fail2; + + if (subsurface) { + *x_offset = -surface_entry->extents.x; + *y_offset = -surface_entry->extents.y; + } + + if (source_pattern && source_pattern->extend != CAIRO_EXTEND_NONE) + _cairo_unbounded_rectangle_init (&op_extents); + + _cairo_rectangle_intersect (&op_extents, &surface_entry->extents); + surface_entry->required_extents = op_extents; + + _cairo_pdf_source_surface_init_key (surface_entry); + + src_surface.hash_entry = surface_entry; + if (source_pattern && source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) { + src_surface.type = CAIRO_PATTERN_TYPE_RASTER_SOURCE; + src_surface.surface = NULL; + status = _cairo_pattern_create_copy (&src_surface.raster_pattern, source_pattern); + if (unlikely (status)) + goto fail2; + + } else { + src_surface.type = CAIRO_PATTERN_TYPE_SURFACE; + src_surface.surface = cairo_surface_reference (source_surface); + src_surface.raster_pattern = NULL; + } + + surface_entry->surface_res = _cairo_pdf_surface_new_object (surface); + if (surface_entry->surface_res.id == 0) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto fail3; + } + + /* Test if surface will be emitted as image or recording */ + status = _cairo_pdf_surface_emit_surface (surface, &src_surface, TRUE, &surface_entry->emit_image); + if (unlikely (status)) + goto fail3; + + if (surface_entry->bounded) { + status = _cairo_array_append (&surface->page_surfaces, &src_surface); + if (unlikely (status)) + goto fail3; + } else { + status = _cairo_array_append (&surface->doc_surfaces, &src_surface); + if (unlikely (status)) + goto fail3; + } + + status = _cairo_hash_table_insert (surface->all_surfaces, + &surface_entry->base); + if (unlikely(status)) + goto fail3; + + if (source_pattern && source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) + _cairo_pdf_surface_release_source_image_from_pattern (surface, source_pattern, image, image_extra); + + return CAIRO_STATUS_SUCCESS; + +fail3: + if (source_pattern && source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) + cairo_pattern_destroy (src_surface.raster_pattern); + else + cairo_surface_destroy (src_surface.surface); + +fail2: + free (surface_entry); + +fail1: + if (unique_id) + free (unique_id); + + if (source_pattern && source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) + _cairo_pdf_surface_release_source_image_from_pattern (surface, source_pattern, image, image_extra); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t *surface, + const cairo_pattern_t *pattern, + cairo_operator_t op, + const cairo_rectangle_int_t *extents, + cairo_bool_t is_shading, + cairo_pdf_resource_t *pattern_res, + cairo_pdf_resource_t *gstate_res) +{ + cairo_pdf_pattern_t pdf_pattern; + cairo_int_status_t status; + + pdf_pattern.is_shading = is_shading; + pdf_pattern.operator = op; + + /* Solid colors are emitted into the content stream */ + if (pattern->type == CAIRO_PATTERN_TYPE_SOLID) { + pattern_res->id = 0; + gstate_res->id = 0; + return CAIRO_INT_STATUS_SUCCESS; + } + + status = _cairo_pattern_create_copy (&pdf_pattern.pattern, pattern); + if (unlikely (status)) + return status; + + pdf_pattern.pattern_res = _cairo_pdf_surface_new_object (surface); + if (pdf_pattern.pattern_res.id == 0) { + cairo_pattern_destroy (pdf_pattern.pattern); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + pdf_pattern.gstate_res.id = 0; + + /* gradient patterns require an smask object to implement transparency */ + if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR || + pattern->type == CAIRO_PATTERN_TYPE_RADIAL || + pattern->type == CAIRO_PATTERN_TYPE_MESH) + { + double min_alpha; + + _cairo_pattern_alpha_range (pattern, &min_alpha, NULL); + if (! CAIRO_ALPHA_IS_OPAQUE (min_alpha)) { + pdf_pattern.gstate_res = _cairo_pdf_surface_new_object (surface); + if (pdf_pattern.gstate_res.id == 0) { + cairo_pattern_destroy (pdf_pattern.pattern); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + } + } + + pdf_pattern.width = surface->width; + pdf_pattern.height = surface->height; + if (extents != NULL) { + pdf_pattern.extents = *extents; + } else { + pdf_pattern.extents.x = 0; + pdf_pattern.extents.y = 0; + pdf_pattern.extents.width = surface->width; + pdf_pattern.extents.height = surface->height; + } + + *pattern_res = pdf_pattern.pattern_res; + *gstate_res = pdf_pattern.gstate_res; + /* If the pattern requires a gstate it will be drawn from within + * an XObject. The initial space of each XObject has an inverted + * Y-axis. */ + pdf_pattern.inverted_y_axis = pdf_pattern.gstate_res.id ? TRUE : surface->in_xobject; + + status = _cairo_array_append (&surface->page_patterns, &pdf_pattern); + if (unlikely (status)) { + cairo_pattern_destroy (pdf_pattern.pattern); + return status; + } + + return CAIRO_INT_STATUS_SUCCESS; +} + +/* Get BBox from extents */ +static void +_get_bbox_from_extents (const cairo_rectangle_int_t *extents, + cairo_box_double_t *bbox) +{ + bbox->p1.x = extents->x; + bbox->p1.y = extents->y; + bbox->p2.x = extents->x + extents->width; + bbox->p2.y = extents->y + extents->height; +} + +static cairo_int_status_t +_cairo_pdf_surface_add_pdf_shading (cairo_pdf_surface_t *surface, + const cairo_pattern_t *pattern, + cairo_operator_t op, + const cairo_rectangle_int_t *extents, + cairo_pdf_resource_t *shading_res, + cairo_pdf_resource_t *gstate_res) +{ + return _cairo_pdf_surface_add_pdf_pattern_or_shading (surface, + pattern, + op, + extents, + TRUE, + shading_res, + gstate_res); +} + +static cairo_int_status_t +_cairo_pdf_surface_add_pdf_pattern (cairo_pdf_surface_t *surface, + const cairo_pattern_t *pattern, + cairo_operator_t op, + const cairo_rectangle_int_t *extents, + cairo_pdf_resource_t *pattern_res, + cairo_pdf_resource_t *gstate_res) +{ + return _cairo_pdf_surface_add_pdf_pattern_or_shading (surface, + pattern, + op, + extents, + FALSE, + pattern_res, + gstate_res); +} + +static cairo_int_status_t +_cairo_pdf_surface_open_stream (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t *resource, + cairo_bool_t compressed, + const char *fmt, + ...) +{ + va_list ap; + cairo_pdf_resource_t self, length; + cairo_output_stream_t *output = NULL; + + if (resource) { + self = *resource; + _cairo_pdf_surface_update_object (surface, self); + } else { + self = _cairo_pdf_surface_new_object (surface); + if (self.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + length = _cairo_pdf_surface_new_object (surface); + if (length.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + if (compressed) { + output = _cairo_deflate_stream_create (surface->output); + if (_cairo_output_stream_get_status (output)) + return _cairo_output_stream_destroy (output); + } + + surface->pdf_stream.active = TRUE; + surface->pdf_stream.self = self; + surface->pdf_stream.length = length; + surface->pdf_stream.compressed = compressed; + surface->current_pattern_is_solid_color = FALSE; + surface->current_operator = CAIRO_OPERATOR_OVER; + _cairo_pdf_operators_reset (&surface->pdf_operators); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Length %d 0 R\n", + surface->pdf_stream.self.id, + surface->pdf_stream.length.id); + if (compressed) + _cairo_output_stream_printf (surface->output, + " /Filter /FlateDecode\n"); + + if (fmt != NULL) { + va_start (ap, fmt); + _cairo_output_stream_vprintf (surface->output, fmt, ap); + va_end (ap); + } + + _cairo_output_stream_printf (surface->output, + ">>\n" + "stream\n"); + + surface->pdf_stream.start_offset = _cairo_output_stream_get_position (surface->output); + + if (compressed) { + assert (surface->pdf_stream.old_output == NULL); + surface->pdf_stream.old_output = surface->output; + surface->output = output; + _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->output); + } + _cairo_pdf_operators_reset (&surface->pdf_operators); + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +_cairo_pdf_surface_close_stream (cairo_pdf_surface_t *surface) +{ + cairo_int_status_t status; + long length; + + if (! surface->pdf_stream.active) + return CAIRO_INT_STATUS_SUCCESS; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + + if (surface->pdf_stream.compressed) { + cairo_int_status_t status2; + + status2 = _cairo_output_stream_destroy (surface->output); + if (likely (status == CAIRO_INT_STATUS_SUCCESS)) + status = status2; + + surface->output = surface->pdf_stream.old_output; + _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->output); + surface->pdf_stream.old_output = NULL; + } + + length = _cairo_output_stream_get_position (surface->output) - + surface->pdf_stream.start_offset; + _cairo_output_stream_printf (surface->output, + "\n" + "endstream\n" + "endobj\n"); + + _cairo_pdf_surface_update_object (surface, + surface->pdf_stream.length); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + " %ld\n" + "endobj\n", + surface->pdf_stream.length.id, + length); + + surface->pdf_stream.active = FALSE; + + if (likely (status == CAIRO_INT_STATUS_SUCCESS)) + status = _cairo_output_stream_get_status (surface->output); + + return status; +} + +static void +_cairo_pdf_surface_write_memory_stream (cairo_pdf_surface_t *surface, + cairo_output_stream_t *mem_stream, + cairo_pdf_resource_t resource, + cairo_pdf_group_resources_t *resources, + cairo_bool_t is_knockout_group, + const cairo_box_double_t *bbox) +{ + _cairo_pdf_surface_update_object (surface, resource); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /XObject\n" + " /Length %d\n", + resource.id, + _cairo_memory_stream_length (mem_stream)); + + if (surface->compress_content) { + _cairo_output_stream_printf (surface->output, + " /Filter /FlateDecode\n"); + } + + _cairo_output_stream_printf (surface->output, + " /Subtype /Form\n" + " /BBox [ %f %f %f %f ]\n" + " /Group <<\n" + " /Type /Group\n" + " /S /Transparency\n" + " /I true\n" + " /CS /DeviceRGB\n", + bbox->p1.x, bbox->p1.y, bbox->p2.x, bbox->p2.y); + + if (is_knockout_group) + _cairo_output_stream_printf (surface->output, + " /K true\n"); + + _cairo_output_stream_printf (surface->output, + " >>\n" + " /Resources\n"); + _cairo_pdf_surface_emit_group_resources (surface, resources); + _cairo_output_stream_printf (surface->output, + ">>\n" + "stream\n"); + _cairo_memory_stream_copy (mem_stream, surface->output); + _cairo_output_stream_printf (surface->output, + "endstream\n" + "endobj\n"); +} + +static cairo_int_status_t +_cairo_pdf_surface_open_group (cairo_pdf_surface_t *surface, + const cairo_box_double_t *bbox, + cairo_pdf_resource_t *resource) +{ + cairo_int_status_t status; + + assert (surface->pdf_stream.active == FALSE); + assert (surface->group_stream.active == FALSE); + + surface->group_stream.active = TRUE; + surface->current_pattern_is_solid_color = FALSE; + surface->current_operator = CAIRO_OPERATOR_OVER; + _cairo_pdf_operators_reset (&surface->pdf_operators); + + surface->group_stream.mem_stream = _cairo_memory_stream_create (); + + if (surface->compress_content) { + surface->group_stream.stream = + _cairo_deflate_stream_create (surface->group_stream.mem_stream); + } else { + surface->group_stream.stream = surface->group_stream.mem_stream; + } + status = _cairo_output_stream_get_status (surface->group_stream.stream); + + surface->group_stream.old_output = surface->output; + surface->output = surface->group_stream.stream; + _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->output); + _cairo_pdf_group_resources_clear (&surface->resources); + + if (resource) { + surface->group_stream.resource = *resource; + } else { + surface->group_stream.resource = _cairo_pdf_surface_new_object (surface); + if (surface->group_stream.resource.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + surface->group_stream.is_knockout = FALSE; + surface->group_stream.bbox = *bbox; + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_open_knockout_group (cairo_pdf_surface_t *surface, + const cairo_box_double_t *bbox) +{ + cairo_int_status_t status; + + status = _cairo_pdf_surface_open_group (surface, bbox, NULL); + if (unlikely (status)) + return status; + + surface->group_stream.is_knockout = TRUE; + + return CAIRO_INT_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface, + cairo_pdf_resource_t *group) +{ + cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS, status2; + + assert (surface->pdf_stream.active == FALSE); + assert (surface->group_stream.active == TRUE); + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + if (surface->compress_content) { + status = _cairo_output_stream_destroy (surface->group_stream.stream); + surface->group_stream.stream = NULL; + + _cairo_output_stream_printf (surface->group_stream.mem_stream, + "\n"); + } + surface->output = surface->group_stream.old_output; + _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->output); + surface->group_stream.active = FALSE; + _cairo_pdf_surface_write_memory_stream (surface, + surface->group_stream.mem_stream, + surface->group_stream.resource, + &surface->resources, + surface->group_stream.is_knockout, + &surface->group_stream.bbox); + if (group) + *group = surface->group_stream.resource; + + status2 = _cairo_output_stream_destroy (surface->group_stream.mem_stream); + if (status == CAIRO_INT_STATUS_SUCCESS) + status = status2; + + surface->group_stream.mem_stream = NULL; + surface->group_stream.stream = NULL; + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t *surface, + const cairo_box_double_t *bbox, + cairo_pdf_resource_t *resource, + cairo_bool_t is_form, + cairo_bool_t is_group) +{ + cairo_int_status_t status; + + assert (surface->pdf_stream.active == FALSE); + assert (surface->group_stream.active == FALSE); + + surface->content_resources = _cairo_pdf_surface_new_object (surface); + if (surface->content_resources.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + if (is_form) { + assert (bbox != NULL); + + if (is_group) { + status = + _cairo_pdf_surface_open_stream (surface, + resource, + surface->compress_content, + " /Type /XObject\n" + " /Subtype /Form\n" + " /BBox [ %f %f %f %f ]\n" + " /Group <<\n" + " /Type /Group\n" + " /S /Transparency\n" + " /I true\n" + " /CS /DeviceRGB\n" + " >>\n" + " /Resources %d 0 R\n", + bbox->p1.x, + bbox->p1.y, + bbox->p2.x, + bbox->p2.y, + surface->content_resources.id); + } else { + status = + _cairo_pdf_surface_open_stream (surface, + resource, + surface->compress_content, + " /Type /XObject\n" + " /Subtype /Form\n" + " /BBox [ %f %f %f %f ]\n" + " /Resources %d 0 R\n", + bbox->p1.x, + bbox->p1.y, + bbox->p2.x, + bbox->p2.y, + surface->content_resources.id); + } + } else { + status = + _cairo_pdf_surface_open_stream (surface, + resource, + surface->compress_content, + NULL); + _cairo_output_stream_printf (surface->output, + "1 0 0 -1 0 %f cm\n", + surface->height); + } + if (unlikely (status)) + return status; + + surface->content = surface->pdf_stream.self; + + _cairo_output_stream_printf (surface->output, "q\n"); + _cairo_pdf_operators_reset (&surface->pdf_operators); + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +_cairo_pdf_surface_close_content_stream (cairo_pdf_surface_t *surface) +{ + cairo_int_status_t status; + + assert (surface->pdf_stream.active == TRUE); + assert (surface->group_stream.active == FALSE); + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "Q\n"); + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) + return status; + + _cairo_pdf_surface_update_object (surface, surface->content_resources); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n", + surface->content_resources.id); + _cairo_pdf_surface_emit_group_resources (surface, &surface->resources); + _cairo_output_stream_printf (surface->output, + "endobj\n"); + + return _cairo_output_stream_get_status (surface->output); +} + +static void +_cairo_pdf_source_surface_entry_pluck (void *entry, void *closure) +{ + cairo_pdf_source_surface_entry_t *surface_entry = entry; + cairo_hash_table_t *patterns = closure; + + _cairo_hash_table_remove (patterns, &surface_entry->base); + free (surface_entry->unique_id); + + free (surface_entry); +} + +static cairo_status_t +_cairo_pdf_surface_finish (void *abstract_surface) +{ + cairo_pdf_surface_t *surface = abstract_surface; + long offset; + cairo_pdf_resource_t catalog; + cairo_status_t status, status2; + int size, i; + cairo_pdf_jbig2_global_t *global; + char *label; + + _cairo_pdf_surface_clear (surface); + + /* Emit unbounded surfaces */ + _cairo_pdf_surface_write_patterns_and_smask_groups (surface, TRUE); + + status = surface->base.status; + if (status == CAIRO_STATUS_SUCCESS) + status = _cairo_pdf_surface_emit_font_subsets (surface); + + _cairo_pdf_surface_write_pages (surface); + + status = _cairo_pdf_interchange_write_document_objects (surface); + if (unlikely (status)) + return status; + + catalog = _cairo_pdf_surface_write_catalog (surface); + if (catalog.id == 0 && status == CAIRO_STATUS_SUCCESS) + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + + offset = _cairo_pdf_surface_write_xref (surface); + + _cairo_output_stream_printf (surface->output, + "trailer\n" + "<< /Size %d\n" + " /Root %d 0 R\n" + " /Info %d 0 R\n" + ">>\n", + surface->next_available_resource.id, + catalog.id, + surface->docinfo_res.id); + + _cairo_output_stream_printf (surface->output, + "startxref\n" + "%ld\n" + "%%%%EOF\n", + offset); + + /* pdf_operators has already been flushed when the last stream was + * closed so we should never be writing anything here - however, + * the stream may itself be in an error state. */ + status2 = _cairo_pdf_operators_fini (&surface->pdf_operators); + if (status == CAIRO_STATUS_SUCCESS) + status = status2; + + /* close any active streams still open due to fatal errors */ + status2 = _cairo_pdf_surface_close_stream (surface); + if (status == CAIRO_STATUS_SUCCESS) + status = status2; + + if (surface->group_stream.stream != NULL) { + status2 = _cairo_output_stream_destroy (surface->group_stream.stream); + if (status == CAIRO_STATUS_SUCCESS) + status = status2; + } + if (surface->group_stream.mem_stream != NULL) { + status2 = _cairo_output_stream_destroy (surface->group_stream.mem_stream); + if (status == CAIRO_STATUS_SUCCESS) + status = status2; + } + if (surface->pdf_stream.active) + surface->output = surface->pdf_stream.old_output; + if (surface->group_stream.active) + surface->output = surface->group_stream.old_output; + + /* and finish the pdf surface */ + status2 = _cairo_output_stream_destroy (surface->output); + if (status == CAIRO_STATUS_SUCCESS) + status = status2; + + _cairo_pdf_group_resources_fini (&surface->resources); + + _cairo_array_fini (&surface->objects); + _cairo_array_fini (&surface->pages); + _cairo_array_fini (&surface->rgb_linear_functions); + _cairo_array_fini (&surface->alpha_linear_functions); + _cairo_array_fini (&surface->page_patterns); + _cairo_array_fini (&surface->page_surfaces); + _cairo_array_fini (&surface->doc_surfaces); + _cairo_hash_table_foreach (surface->all_surfaces, + _cairo_pdf_source_surface_entry_pluck, + surface->all_surfaces); + _cairo_hash_table_destroy (surface->all_surfaces); + _cairo_array_fini (&surface->smask_groups); + _cairo_array_fini (&surface->fonts); + _cairo_array_fini (&surface->knockout_group); + _cairo_array_fini (&surface->page_annots); + + if (surface->font_subsets) { + _cairo_scaled_font_subsets_destroy (surface->font_subsets); + surface->font_subsets = NULL; + } + + size = _cairo_array_num_elements (&surface->jbig2_global); + for (i = 0; i < size; i++) { + global = (cairo_pdf_jbig2_global_t *) _cairo_array_index (&surface->jbig2_global, i); + free(global->id); + if (!global->emitted) + return _cairo_error (CAIRO_STATUS_JBIG2_GLOBAL_MISSING); + } + _cairo_array_fini (&surface->jbig2_global); + _cairo_array_fini (&surface->page_heights); + + size = _cairo_array_num_elements (&surface->page_labels); + for (i = 0; i < size; i++) { + _cairo_array_copy_element (&surface->page_labels, i, &label); + free (label); + } + _cairo_array_fini (&surface->page_labels); + + _cairo_surface_clipper_reset (&surface->clipper); + + return _cairo_pdf_interchange_fini (surface); +} + +static cairo_int_status_t +_cairo_pdf_surface_start_page (void *abstract_surface) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_pdf_resource_t page; + cairo_int_status_t status; + + /* Document header */ + if (! surface->header_emitted) { + const char *version; + + switch (surface->pdf_version) { + case CAIRO_PDF_VERSION_1_4: + version = "1.4"; + break; + default: + case CAIRO_PDF_VERSION_1_5: + version = "1.5"; + break; + } + + _cairo_output_stream_printf (surface->output, + "%%PDF-%s\n", version); + _cairo_output_stream_printf (surface->output, + "%%%c%c%c%c\n", 181, 237, 174, 251); + surface->header_emitted = TRUE; + } + + _cairo_pdf_group_resources_clear (&surface->resources); + surface->in_xobject = FALSE; + + page = _cairo_pdf_surface_new_object (surface); + if (page.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + status = _cairo_array_append (&surface->pages, &page); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_has_fallback_images (void *abstract_surface, + cairo_bool_t has_fallbacks) +{ + cairo_int_status_t status; + cairo_pdf_surface_t *surface = abstract_surface; + cairo_box_double_t bbox; + + surface->has_fallback_images = has_fallbacks; + surface->in_xobject = has_fallbacks; + bbox.p1.x = 0; + bbox.p1.y = 0; + bbox.p2.x = surface->width; + bbox.p2.y = surface->height; + status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, has_fallbacks, has_fallbacks); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_bool_t +_cairo_pdf_surface_supports_fine_grained_fallbacks (void *abstract_surface) +{ + return TRUE; +} + +static cairo_bool_t +_cairo_pdf_surface_requires_thumbnail_image (void *abstract_surface, + int *width, + int *height) +{ + cairo_pdf_surface_t *surface = abstract_surface; + + if (surface->thumbnail_width > 0 && surface->thumbnail_height > 0) { + *width = surface->thumbnail_width; + *height = surface->thumbnail_height; + return TRUE; + } + + return FALSE; +} + +static cairo_int_status_t +_cairo_pdf_surface_set_thumbnail_image (void *abstract_surface, + cairo_image_surface_t *image) +{ + cairo_pdf_surface_t *surface = abstract_surface; + + surface->thumbnail_image = (cairo_image_surface_t *)cairo_surface_reference(&image->base); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_add_padded_image_surface (cairo_pdf_surface_t *surface, + const cairo_pattern_t *source, + const cairo_rectangle_int_t *extents, + cairo_pdf_source_surface_entry_t **pdf_source, + double *x_offset, + double *y_offset, + cairo_rectangle_int_t *source_extents) +{ + cairo_image_surface_t *image; + cairo_surface_t *pad_image; + void *image_extra; + cairo_int_status_t status; + int w, h; + cairo_box_t box; + cairo_rectangle_int_t rect; + cairo_surface_pattern_t pad_pattern; + + status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, source, + &image, &image_extra); + if (unlikely (status)) + return status; + + pad_image = &image->base; + + /* get the operation extents in pattern space */ + _cairo_box_from_rectangle (&box, extents); + _cairo_matrix_transform_bounding_box_fixed (&source->matrix, &box, NULL); + _cairo_box_round_to_rectangle (&box, &rect); + + /* Check if image needs padding to fill extents */ + w = image->width; + h = image->height; + if (_cairo_fixed_integer_ceil(box.p1.x) < 0 || + _cairo_fixed_integer_ceil(box.p1.y) < 0 || + _cairo_fixed_integer_floor(box.p2.x) > w || + _cairo_fixed_integer_floor(box.p2.y) > h) + { + pad_image = _cairo_image_surface_create_with_content (image->base.content, + rect.width, + rect.height); + if (pad_image->status) { + status = pad_image->status; + goto BAIL; + } + + _cairo_pattern_init_for_surface (&pad_pattern, &image->base); + cairo_matrix_init_translate (&pad_pattern.base.matrix, rect.x, rect.y); + pad_pattern.base.extend = CAIRO_EXTEND_PAD; + status = _cairo_surface_paint (pad_image, + CAIRO_OPERATOR_SOURCE, &pad_pattern.base, + NULL); + _cairo_pattern_fini (&pad_pattern.base); + if (unlikely (status)) + goto BAIL; + } + + status = _cairo_pdf_surface_add_source_surface (surface, + pad_image, + NULL, + CAIRO_OPERATOR_OVER, /* not used for images */ + source->filter, + FALSE, /* stencil mask */ + FALSE, /* smask */ + FALSE, /* need_transp_group */ + extents, + NULL, /* smask_res */ + pdf_source, + x_offset, + y_offset, + source_extents); + if (unlikely (status)) + goto BAIL; + + if (pad_image != &image->base) { + /* If using a padded image, replace _add_source_surface + * x/y_offset with padded image offset. Note: + * _add_source_surface only sets a non zero x/y_offset for + * RASTER_SOURCE patterns. _add_source_surface will always set + * x/y_offset to 0 for surfaces so we can ignore the returned + * offset and replace it with the offset required for the + * padded image */ + *x_offset = rect.x; + *y_offset = rect.y; + } + +BAIL: + if (pad_image != &image->base) + cairo_surface_destroy (pad_image); + + _cairo_pdf_surface_release_source_image_from_pattern (surface, source, image, image_extra); + + return status; +} + +/* Emit alpha channel from the image into stream_res. + */ +static cairo_int_status_t +_cairo_pdf_surface_emit_smask (cairo_pdf_surface_t *surface, + cairo_image_surface_t *image, + cairo_bool_t stencil_mask, + cairo_bool_t interpolate, + cairo_pdf_resource_t *stream_res) +{ + cairo_int_status_t status = CAIRO_STATUS_SUCCESS; + char *alpha; + unsigned long alpha_size; + uint32_t *pixel32; + uint8_t *pixel8; + int i, x, y, bit, a; + cairo_image_transparency_t transparency; + + /* This is the only image format we support, which simplifies things. */ + assert (image->format == CAIRO_FORMAT_ARGB32 || + image->format == CAIRO_FORMAT_RGB24 || + image->format == CAIRO_FORMAT_A8 || + image->format == CAIRO_FORMAT_A1 ); + + transparency = _cairo_image_analyze_transparency (image); + if (stencil_mask) { + assert (transparency == CAIRO_IMAGE_IS_OPAQUE || + transparency == CAIRO_IMAGE_HAS_BILEVEL_ALPHA); + } else { + assert (transparency != CAIRO_IMAGE_IS_OPAQUE); + } + + if (transparency == CAIRO_IMAGE_HAS_BILEVEL_ALPHA || transparency == CAIRO_IMAGE_IS_OPAQUE) { + alpha_size = (image->width + 7) / 8 * image->height; + alpha = _cairo_malloc_ab ((image->width+7) / 8, image->height); + } else { + alpha_size = image->height * image->width; + alpha = _cairo_malloc_ab (image->height, image->width); + } + + if (unlikely (alpha == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP; + } + + i = 0; + for (y = 0; y < image->height; y++) { + if (transparency == CAIRO_IMAGE_IS_OPAQUE) { + for (x = 0; x < (image->width + 7) / 8; x++) + alpha[i++] = 0xff; + } else if (image->format == CAIRO_FORMAT_A1) { + pixel8 = (uint8_t *) (image->data + y * image->stride); + + for (x = 0; x < (image->width + 7) / 8; x++, pixel8++) { + a = *pixel8; + a = CAIRO_BITSWAP8_IF_LITTLE_ENDIAN (a); + alpha[i++] = a; + } + } else { + pixel8 = (uint8_t *) (image->data + y * image->stride); + pixel32 = (uint32_t *) (image->data + y * image->stride); + bit = 7; + for (x = 0; x < image->width; x++) { + if (image->format == CAIRO_FORMAT_ARGB32) { + a = (*pixel32 & 0xff000000) >> 24; + pixel32++; + } else { + a = *pixel8; + pixel8++; + } + + if (transparency == CAIRO_IMAGE_HAS_ALPHA) { + alpha[i++] = a; + } else { /* transparency == CAIRO_IMAGE_HAS_BILEVEL_ALPHA or CAIRO_IMAGE_IS_OPAQUE */ + if (bit == 7) + alpha[i] = 0; + if (a != 0) + alpha[i] |= (1 << bit); + bit--; + if (bit < 0) { + bit = 7; + i++; + } + } + } + if (bit != 7) + i++; + } + } + + if (stencil_mask) { + status = _cairo_pdf_surface_open_stream (surface, + stream_res, + TRUE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /ImageMask true\n" + " /Width %d\n" + " /Height %d\n" + " /Interpolate %s\n" + " /BitsPerComponent 1\n" + " /Decode [1 0]\n", + image->width, image->height, + interpolate ? "true" : "false"); + } else { + status = _cairo_pdf_surface_open_stream (surface, + stream_res, + TRUE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /Width %d\n" + " /Height %d\n" + " /ColorSpace /DeviceGray\n" + " /Interpolate %s\n" + " /BitsPerComponent %d\n", + image->width, image->height, + interpolate ? "true" : "false", + transparency == CAIRO_IMAGE_HAS_ALPHA ? 8 : 1); + } + if (unlikely (status)) + goto CLEANUP_ALPHA; + + _cairo_output_stream_write (surface->output, alpha, alpha_size); + status = _cairo_pdf_surface_close_stream (surface); + + CLEANUP_ALPHA: + free (alpha); + CLEANUP: + return status; +} + +/** + * _cairo_pdf_surface_emit_image: + * @surface: the pdf surface + * @image_surf: The image to write + * @surface_entry: Contains image resource, smask resource, interpolate and stencil mask parameters. + * + * Emit an image stream using the @image_res resource and write out + * the image data from @image_surf. If @smask_res is not null, @smask_res will + * be specified as the smask for the image. Otherwise emit the an smask if + * the image is requires one. + **/ +static cairo_int_status_t +_cairo_pdf_surface_emit_image (cairo_pdf_surface_t *surface, + cairo_image_surface_t *image_surf, + cairo_pdf_source_surface_entry_t *surface_entry) +{ + cairo_int_status_t status = CAIRO_STATUS_SUCCESS; + char *data; + unsigned long data_size; + uint32_t *pixel; + int i, x, y, bit; + cairo_pdf_resource_t smask = {0}; /* squelch bogus compiler warning */ + cairo_bool_t need_smask; + cairo_image_color_t color; + cairo_image_surface_t *image; + cairo_image_transparency_t transparency; + char smask_buf[30]; + + image = image_surf; + if (image->format != CAIRO_FORMAT_RGB24 && + image->format != CAIRO_FORMAT_ARGB32 && + image->format != CAIRO_FORMAT_A8 && + image->format != CAIRO_FORMAT_A1) + { + cairo_surface_t *surf; + cairo_surface_pattern_t pattern; + + surf = _cairo_image_surface_create_with_content (image_surf->base.content, + image_surf->width, + image_surf->height); + image = (cairo_image_surface_t *) surf; + if (surf->status) { + status = surf->status; + goto CLEANUP; + } + + _cairo_pattern_init_for_surface (&pattern, &image_surf->base); + status = _cairo_surface_paint (surf, + CAIRO_OPERATOR_SOURCE, &pattern.base, + NULL); + _cairo_pattern_fini (&pattern.base); + if (unlikely (status)) + goto CLEANUP; + } + + if (surface_entry->smask || surface_entry->stencil_mask) { + return _cairo_pdf_surface_emit_smask (surface, image, + surface_entry->stencil_mask, + surface_entry->interpolate, + &surface_entry->surface_res); + } + + color = _cairo_image_analyze_color (image); + switch (color) { + default: + case CAIRO_IMAGE_UNKNOWN_COLOR: + ASSERT_NOT_REACHED; + case CAIRO_IMAGE_IS_COLOR: + data_size = image->height * image->width * 3; + data = _cairo_malloc_abc (image->width, image->height, 3); + break; + + case CAIRO_IMAGE_IS_GRAYSCALE: + data_size = image->height * image->width; + data = _cairo_malloc_ab (image->width, image->height); + break; + case CAIRO_IMAGE_IS_MONOCHROME: + data_size = (image->width + 7) / 8 * image->height; + data = _cairo_malloc_ab ((image->width+7) / 8, image->height); + break; + } + if (unlikely (data == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP; + } + + i = 0; + for (y = 0; y < image->height; y++) { + pixel = (uint32_t *) (image->data + y * image->stride); + + bit = 7; + for (x = 0; x < image->width; x++, pixel++) { + int r, g, b; + + /* XXX: We're un-premultiplying alpha here. My reading of the PDF + * specification suggests that we should be able to avoid having + * to do this by filling in the SMask's Matte dictionary + * appropriately, but my attempts to do that so far have + * failed. */ + if (image->format == CAIRO_FORMAT_ARGB32) { + uint8_t a; + a = (*pixel & 0xff000000) >> 24; + if (a == 0) { + r = g = b = 0; + } else { + r = (((*pixel & 0xff0000) >> 16) * 255 + a / 2) / a; + g = (((*pixel & 0x00ff00) >> 8) * 255 + a / 2) / a; + b = (((*pixel & 0x0000ff) >> 0) * 255 + a / 2) / a; + } + } else if (image->format == CAIRO_FORMAT_RGB24) { + r = (*pixel & 0x00ff0000) >> 16; + g = (*pixel & 0x0000ff00) >> 8; + b = (*pixel & 0x000000ff) >> 0; + } else { + r = g = b = 0; + } + + switch (color) { + case CAIRO_IMAGE_IS_COLOR: + case CAIRO_IMAGE_UNKNOWN_COLOR: + data[i++] = r; + data[i++] = g; + data[i++] = b; + break; + + case CAIRO_IMAGE_IS_GRAYSCALE: + data[i++] = r; + break; + + case CAIRO_IMAGE_IS_MONOCHROME: + if (bit == 7) + data[i] = 0; + if (r != 0) + data[i] |= (1 << bit); + bit--; + if (bit < 0) { + bit = 7; + i++; + } + break; + } + } + if (bit != 7) + i++; + } + + if (surface_entry->smask_res.id != 0) { + need_smask = TRUE; + smask = surface_entry->smask_res; + } else { + need_smask = FALSE; + if (image->format == CAIRO_FORMAT_ARGB32 || + image->format == CAIRO_FORMAT_A8 || + image->format == CAIRO_FORMAT_A1) + { + transparency = _cairo_image_analyze_transparency (image); + if (transparency != CAIRO_IMAGE_IS_OPAQUE) { + need_smask = TRUE; + smask = _cairo_pdf_surface_new_object (surface); + if (smask.id == 0) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto CLEANUP_RGB; + } + + status = _cairo_pdf_surface_emit_smask (surface, image, FALSE, surface_entry->interpolate, &smask); + if (unlikely (status)) + goto CLEANUP_RGB; + } + } + } + + if (need_smask) + snprintf(smask_buf, sizeof(smask_buf), " /SMask %d 0 R\n", smask.id); + else + smask_buf[0] = 0; + + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + TRUE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /Width %d\n" + " /Height %d\n" + " /ColorSpace %s\n" + " /Interpolate %s\n" + " /BitsPerComponent %d\n" + "%s", + image->width, + image->height, + color == CAIRO_IMAGE_IS_COLOR ? "/DeviceRGB" : "/DeviceGray", + surface_entry->interpolate ? "true" : "false", + color == CAIRO_IMAGE_IS_MONOCHROME? 1 : 8, + smask_buf); + if (unlikely (status)) + goto CLEANUP_RGB; + +#undef IMAGE_DICTIONARY + + _cairo_output_stream_write (surface->output, data, data_size); + status = _cairo_pdf_surface_close_stream (surface); + +CLEANUP_RGB: + free (data); +CLEANUP: + if (image != image_surf) + cairo_surface_destroy (&image->base); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_lookup_jbig2_global (cairo_pdf_surface_t *surface, + const unsigned char *global_id, + unsigned long global_id_length, + cairo_pdf_jbig2_global_t **entry) +{ + cairo_pdf_jbig2_global_t global; + int size, i; + cairo_int_status_t status; + + size = _cairo_array_num_elements (&surface->jbig2_global); + for (i = 0; i < size; i++) { + *entry = (cairo_pdf_jbig2_global_t *) _cairo_array_index (&surface->jbig2_global, i); + if ((*entry)->id && global_id && (*entry)->id_length == global_id_length + && memcmp((*entry)->id, global_id, global_id_length) == 0) { + return CAIRO_STATUS_SUCCESS; + } + } + + global.id = _cairo_malloc (global_id_length); + if (unlikely (global.id == NULL)) { + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + memcpy (global.id, global_id, global_id_length); + global.id_length = global_id_length; + global.res = _cairo_pdf_surface_new_object (surface); + if (global.res.id == 0) { + free(global.id); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + global.emitted = FALSE; + status = _cairo_array_append (&surface->jbig2_global, &global); + if (unlikely(status)) + return status; + + size = _cairo_array_num_elements (&surface->jbig2_global); + *entry = (cairo_pdf_jbig2_global_t *) _cairo_array_index (&surface->jbig2_global, size - 1); + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_jbig2_image (cairo_pdf_surface_t *surface, + cairo_surface_t *source, + cairo_pdf_source_surface_entry_t *surface_entry, + cairo_bool_t test) +{ + cairo_int_status_t status; + const unsigned char *mime_data; + unsigned long mime_data_length; + cairo_image_info_t info; + const unsigned char *global_id; + unsigned long global_id_length; + const unsigned char *global_data; + unsigned long global_data_length; + cairo_pdf_jbig2_global_t *global_entry = NULL; /* hide compiler warning */ + char smask_buf[30]; + char decode_parms_buf[100]; + + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JBIG2, + &mime_data, &mime_data_length); + if (mime_data == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + status = _cairo_image_info_get_jbig2_info (&info, mime_data, mime_data_length); + if (status) + return status; + + /* At this point we know emitting jbig2 will succeed. */ + if (test) + return CAIRO_STATUS_SUCCESS; + + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID, + &global_id, &global_id_length); + if (global_id && global_id_length > 0) { + status = _cairo_pdf_surface_lookup_jbig2_global (surface, global_id, global_id_length, &global_entry); + if (unlikely(status)) + return status; + + if (!global_entry->emitted) { + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JBIG2_GLOBAL, + &global_data, &global_data_length); + if (global_data) { + status = _cairo_pdf_surface_open_stream (surface, &global_entry->res, FALSE, NULL); + if (unlikely(status)) + return status; + + _cairo_output_stream_write (surface->output, global_data, global_data_length); + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely(status)) + return status; + + global_entry->emitted = TRUE; + } + } + + snprintf(decode_parms_buf, sizeof(decode_parms_buf), + " /DecodeParms << /JBIG2Globals %d 0 R >>\n", global_entry->res.id); + } else { + decode_parms_buf[0] = 0; + } + + if (surface_entry->smask_res.id) + snprintf(smask_buf, sizeof(smask_buf), " /SMask %d 0 R\n", surface_entry->smask_res.id); + else + smask_buf[0] = 0; + + if (surface_entry->stencil_mask) { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /ImageMask true\n" + " /Width %d\n" + " /Height %d\n" + " /Interpolate %s\n" + " /BitsPerComponent 1\n" + " /Decode [1 0]\n" + " /Filter /JPXDecode\n" + "%s", + info.width, + info.height, + surface_entry->interpolate ? "true" : "false", + decode_parms_buf); + } else { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /Width %d\n" + " /Height %d\n" + " /ColorSpace /DeviceGray\n" + " /BitsPerComponent 1\n" + " /Interpolate %s\n" + "%s" + " /Filter /JBIG2Decode\n" + "%s", + info.width, + info.height, + surface_entry->interpolate ? "true" : "false", + smask_buf, + decode_parms_buf); + } + if (unlikely(status)) + return status; + + _cairo_output_stream_write (surface->output, mime_data, mime_data_length); + status = _cairo_pdf_surface_close_stream (surface); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_jpx_image (cairo_pdf_surface_t *surface, + cairo_surface_t *source, + cairo_pdf_source_surface_entry_t *surface_entry, + cairo_bool_t test) +{ + cairo_int_status_t status; + const unsigned char *mime_data; + unsigned long mime_data_length; + cairo_image_info_t info; + char smask_buf[30]; + + if (surface->pdf_version < CAIRO_PDF_VERSION_1_5) + return CAIRO_INT_STATUS_UNSUPPORTED; + + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JP2, + &mime_data, &mime_data_length); + if (mime_data == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + status = _cairo_image_info_get_jpx_info (&info, mime_data, mime_data_length); + if (status) + return status; + + if ((surface_entry->smask || surface_entry->stencil_mask) && info.num_components != 1) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if ((surface_entry->stencil_mask) && info.bits_per_component != 1) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if (surface_entry->smask_res.id) + snprintf(smask_buf, sizeof(smask_buf), " /SMask %d 0 R\n", surface_entry->smask_res.id); + else + smask_buf[0] = 0; + + /* At this point we know emitting jpx will succeed. */ + if (test) + return CAIRO_STATUS_SUCCESS; + + if (surface_entry->stencil_mask) { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /ImageMask true\n" + " /Width %d\n" + " /Height %d\n" + " /Interpolate %s\n" + " /BitsPerComponent 1\n" + " /Decode [1 0]\n" + " /Filter /JPXDecode\n", + info.width, + info.height, + surface_entry->interpolate ? "true" : "false"); + } else { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /Width %d\n" + " /Height %d\n" + " /Interpolate %s\n" + "%s" + " /Filter /JPXDecode\n", + info.width, + info.height, + surface_entry->interpolate ? "true" : "false", + smask_buf); + } + if (status) + return status; + + _cairo_output_stream_write (surface->output, mime_data, mime_data_length); + status = _cairo_pdf_surface_close_stream (surface); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_jpeg_image (cairo_pdf_surface_t *surface, + cairo_surface_t *source, + cairo_pdf_source_surface_entry_t *surface_entry, + cairo_bool_t test) +{ + cairo_int_status_t status; + const unsigned char *mime_data; + unsigned long mime_data_length; + cairo_image_info_t info; + const char *colorspace; + char smask_buf[30]; + + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JPEG, + &mime_data, &mime_data_length); + if (unlikely (source->status)) + return source->status; + if (mime_data == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + status = _cairo_image_info_get_jpeg_info (&info, mime_data, mime_data_length); + if (unlikely (status)) + return status; + + if ((surface_entry->smask || surface_entry->stencil_mask) && info.num_components != 1) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if ((surface_entry->stencil_mask) && info.bits_per_component != 1) + return CAIRO_INT_STATUS_UNSUPPORTED; + + switch (info.num_components) { + case 1: + colorspace = "/DeviceGray"; + break; + case 3: + colorspace = "/DeviceRGB"; + break; + case 4: + colorspace = "/DeviceCMYK"; + break; + default: + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + /* At this point we know emitting jpeg will succeed. */ + if (test) + return CAIRO_STATUS_SUCCESS; + + if (surface_entry->smask_res.id) + snprintf(smask_buf, sizeof(smask_buf), " /SMask %d 0 R\n", surface_entry->smask_res.id); + else + smask_buf[0] = 0; + + if (surface_entry->stencil_mask) { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /ImageMask true\n" + " /Width %d\n" + " /Height %d\n" + " /Interpolate %s\n" + " /BitsPerComponent 1\n" + " /Decode [1 0]\n" + " /Filter /DCTDecode\n", + info.width, + info.height, + surface_entry->interpolate ? "true" : "false"); + } else { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /Width %d\n" + " /Height %d\n" + " /ColorSpace %s\n" + " /Interpolate %s\n" + " /BitsPerComponent %d\n" + "%s" + " /Filter /DCTDecode\n", + info.width, + info.height, + colorspace, + surface_entry->interpolate ? "true" : "false", + info.bits_per_component, + smask_buf); + } + if (unlikely (status)) + return status; + + _cairo_output_stream_write (surface->output, mime_data, mime_data_length); + status = _cairo_pdf_surface_close_stream (surface); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_ccitt_image (cairo_pdf_surface_t *surface, + cairo_surface_t *source, + cairo_pdf_source_surface_entry_t *surface_entry, + cairo_bool_t test) +{ + cairo_status_t status; + const unsigned char *ccitt_data; + unsigned long ccitt_data_len; + const unsigned char *ccitt_params_string; + unsigned long ccitt_params_string_len; + char *params, *p, *end; + cairo_ccitt_params_t ccitt_params; + char buf[300]; + + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_CCITT_FAX, + &ccitt_data, &ccitt_data_len); + if (unlikely (source->status)) + return source->status; + if (ccitt_data == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, + &ccitt_params_string, &ccitt_params_string_len); + if (unlikely (source->status)) + return source->status; + if (ccitt_params_string == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* ensure params_string is null terminated */ + params = malloc (ccitt_params_string_len + 1); + memcpy (params, ccitt_params_string, ccitt_params_string_len); + params[ccitt_params_string_len] = 0; + status = _cairo_tag_parse_ccitt_params (params, &ccitt_params); + if (unlikely(status)) + return source->status; + + free (params); + + /* At this point we know emitting jbig2 will succeed. */ + if (test) + return CAIRO_STATUS_SUCCESS; + + p = buf; + *p = 0; + end = buf + sizeof(buf) - 1; + p += snprintf (p, end - p, "/Columns %d /Rows %d /K %d", + ccitt_params.columns, + ccitt_params.rows, + ccitt_params.k); + if (ccitt_params.end_of_line) + p += snprintf (p, end - p, " /EndOfLine true"); + + if (ccitt_params.encoded_byte_align) + p += snprintf (p, end - p, " /EncodedByteAlign true"); + + if (!ccitt_params.end_of_block) + p += snprintf (p, end - p, " /EndOfBlock false"); + + if (ccitt_params.black_is_1) + p += snprintf (p, end - p, " /BlackIs1 true"); + + if (ccitt_params.damaged_rows_before_error > 0) { + p += snprintf (p, end - p, " /DamagedRowsBeforeError %d", + ccitt_params.damaged_rows_before_error); + } + + if (surface_entry->stencil_mask) { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /ImageMask true\n" + " /Width %d\n" + " /Height %d\n" + " /Interpolate %s\n" + " /BitsPerComponent 1\n" + " /Decode [1 0]\n" + " /Filter /CCITTFaxDecode\n" + " /DecodeParms << %s >> ", + ccitt_params.columns, + ccitt_params.rows, + surface_entry->interpolate ? "true" : "false", + buf); + } else { + status = _cairo_pdf_surface_open_stream (surface, + &surface_entry->surface_res, + FALSE, + " /Type /XObject\n" + " /Subtype /Image\n" + " /Width %d\n" + " /Height %d\n" + " /ColorSpace /DeviceGray\n" + " /BitsPerComponent 1\n" + " /Interpolate %s\n" + " /Filter /CCITTFaxDecode\n" + " /DecodeParms << %s >> ", + ccitt_params.columns, + ccitt_params.rows, + surface_entry->interpolate ? "true" : "false", + buf); + } + if (unlikely (status)) + return status; + + _cairo_output_stream_write (surface->output, ccitt_data, ccitt_data_len); + status = _cairo_pdf_surface_close_stream (surface); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t *surface, + cairo_pdf_source_surface_t *pdf_source) +{ + cairo_rectangle_int_t old_surface_extents; + cairo_bool_t old_surface_bounded; + cairo_paginated_mode_t old_paginated_mode; + cairo_surface_clipper_t old_clipper; + cairo_bool_t old_in_xobject; + cairo_box_double_t bbox; + cairo_int_status_t status; + int alpha = 0; + cairo_surface_t *free_me = NULL; + cairo_surface_t *source; + const cairo_rectangle_int_t *extents; + cairo_bool_t is_subsurface; + cairo_bool_t transparency_group; + cairo_recording_surface_t *recording; + + assert (pdf_source->type == CAIRO_PATTERN_TYPE_SURFACE); + + if (pdf_source->hash_entry->bounded) { + extents = &pdf_source->hash_entry->extents; + } else { + extents = &pdf_source->hash_entry->required_extents; + } + + is_subsurface = FALSE; + source = pdf_source->surface; + if (_cairo_surface_is_snapshot (source)) + free_me = source = _cairo_surface_snapshot_get_target (source); + + if (source->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) { + cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) source; + + source = sub->target; + extents = &sub->extents; + is_subsurface = TRUE; + } + + assert (source->type == CAIRO_SURFACE_TYPE_RECORDING); + recording = (cairo_recording_surface_t *) source; + + old_in_xobject = surface->in_xobject; + old_surface_extents = surface->surface_extents; + old_surface_bounded = surface->surface_bounded; + old_paginated_mode = surface->paginated_mode; + old_clipper = surface->clipper; + surface->surface_extents = *extents; + _cairo_surface_clipper_init (&surface->clipper, + _cairo_pdf_surface_clipper_intersect_clip_path); + + _cairo_pdf_operators_reset (&surface->pdf_operators); + surface->in_xobject = TRUE; + surface->surface_extents = *extents; + surface->surface_bounded = TRUE; + + /* Patterns are emitted after fallback images. The paginated mode + * needs to be set to _RENDER while the recording surface is replayed + * back to this surface. + */ + surface->paginated_mode = CAIRO_PAGINATED_MODE_RENDER; + _cairo_pdf_group_resources_clear (&surface->resources); + _get_bbox_from_extents (extents, &bbox); + + /* We can optimize away the transparency group allowing the viewer + * to replay the group in place when: + * - ca/CA when painting this groups is 1.0 (need_transp_group is FALSE), + * - all operators are OVER, and + * - the recording contains only opaque and/or clear alpha. + */ + transparency_group = pdf_source->hash_entry->need_transp_group || + !(pdf_source->hash_entry->operator == CAIRO_OPERATOR_OVER && + _cairo_recording_surface_has_only_bilevel_alpha (recording) && + _cairo_recording_surface_has_only_op_over (recording)); + + status = _cairo_pdf_surface_open_content_stream (surface, + &bbox, + &pdf_source->hash_entry->surface_res, + TRUE, + transparency_group); + if (unlikely (status)) + goto err; + + if (source->content == CAIRO_CONTENT_COLOR) { + status = _cairo_pdf_surface_add_alpha (surface, 1.0, &alpha); + if (unlikely (status)) + goto err; + + _cairo_output_stream_printf (surface->output, + "q /a%d gs 0 0 0 rg %d %d %d %d re f Q\n", + alpha, + extents->x, + extents->y, + extents->width, + extents->height); + } + + status = _cairo_recording_surface_replay_region (source, + is_subsurface ? extents : NULL, + &surface->base, + CAIRO_RECORDING_REGION_NATIVE); + assert (status != CAIRO_INT_STATUS_UNSUPPORTED); + if (unlikely (status)) + goto err; + + status = _cairo_pdf_surface_close_content_stream (surface); + + _cairo_surface_clipper_reset (&surface->clipper); + surface->clipper = old_clipper; + _cairo_pdf_operators_reset (&surface->pdf_operators); + surface->in_xobject = old_in_xobject; + surface->paginated_mode = old_paginated_mode; + surface->surface_extents = old_surface_extents; + surface->surface_bounded = old_surface_bounded; + +err: + cairo_surface_destroy (free_me); + return status; +} + +/** + * _cairo_pdf_surface_emit_surface: + * @surface: [in] the pdf surface + * @source: [in] #cairo_pdf_source_surface_t containing the surface to write + * @test: [in] if true, test what type of surface will be emitted. + * @is_image: [out] if @test is true, returns TRUE if the surface will be emitted + * as an Image XObject. + * + * If @test is FALSE, emit @src_surface as an XObject. + * If @test is TRUE, don't emit anything. Set @is_image based on the output that would be emitted. + **/ +static cairo_int_status_t +_cairo_pdf_surface_emit_surface (cairo_pdf_surface_t *surface, + cairo_pdf_source_surface_t *source, + cairo_bool_t test, + cairo_bool_t *is_image) +{ + cairo_image_surface_t *image; + void *image_extra; + cairo_int_status_t status; + + /* Try all the supported mime types and recording type, falling through + * each option if unsupported */ + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { + status = _cairo_pdf_surface_emit_jbig2_image (surface, + source->surface, + source->hash_entry, + test); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) { + *is_image = TRUE; + return status; + } + + status = _cairo_pdf_surface_emit_jpx_image (surface, + source->surface, + source->hash_entry, + test); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) { + *is_image = TRUE; + return status; + } + + status = _cairo_pdf_surface_emit_jpeg_image (surface, + source->surface, + source->hash_entry, + test); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) { + *is_image = TRUE; + return status; + } + + status = _cairo_pdf_surface_emit_ccitt_image (surface, + source->surface, + source->hash_entry, + test); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) { + *is_image = TRUE; + return status; + } + + if (source->surface->type == CAIRO_SURFACE_TYPE_RECORDING) { + if (test) { + *is_image = FALSE; + return CAIRO_INT_STATUS_SUCCESS; + } else { + return _cairo_pdf_surface_emit_recording_surface (surface, source); + } + } + } + + /* The only option left is to emit as an image surface */ + + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { + status = _cairo_surface_acquire_source_image (source->surface, &image, &image_extra); + } else { + status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, + source->raster_pattern, + &image, + &image_extra); + } + if (unlikely (status)) + return status; + + if (test) { + *is_image = TRUE; + } else { + status = _cairo_pdf_surface_emit_image (surface, + image, + source->hash_entry); + } + + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { + _cairo_surface_release_source_image (source->surface, image, image_extra); + } else { + _cairo_pdf_surface_release_source_image_from_pattern (surface, + source->raster_pattern, + image, + image_extra); + } + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t *surface, + cairo_pdf_pattern_t *pdf_pattern) +{ + cairo_pattern_t *pattern = pdf_pattern->pattern; + cairo_int_status_t status; + cairo_matrix_t cairo_p2d, pdf_p2d; + cairo_extend_t extend = cairo_pattern_get_extend (pattern); + double xstep, ystep; + cairo_rectangle_int_t pattern_extents; + double x_offset; + double y_offset; + char draw_surface[50]; + char draw_surface2[200]; + cairo_box_double_t bbox; + cairo_matrix_t mat; + cairo_pdf_source_surface_entry_t *pdf_source; + cairo_rectangle_int_t op_extents; + + assert (pattern->type == CAIRO_PATTERN_TYPE_SURFACE); + if (pattern->extend == CAIRO_EXTEND_PAD) { + status = _cairo_pdf_surface_add_padded_image_surface (surface, + pattern, + &pdf_pattern->extents, + &pdf_source, + &x_offset, + &y_offset, + &op_extents); + } else { + status = _cairo_pdf_surface_add_source_surface (surface, + NULL, + pattern, + pdf_pattern->operator, + pattern->filter, + FALSE, /* stencil mask */ + FALSE, /* smask */ + FALSE, /* need_transp_group */ + &pdf_pattern->extents, + NULL, /* smask_res */ + &pdf_source, + &x_offset, + &y_offset, + &op_extents); + } + if (unlikely (status)) + return status; + + pattern_extents = pdf_source->extents; + if (!pdf_source->bounded) + { + extend = CAIRO_EXTEND_NONE; + _cairo_rectangle_intersect (&pattern_extents, &op_extents); + } + + switch (extend) { + case CAIRO_EXTEND_PAD: + case CAIRO_EXTEND_NONE: + { + /* In PS/PDF, (as far as I can tell), all patterns are + * repeating. So we support cairo's EXTEND_NONE semantics + * by setting the repeat step size to a size large enough + * to guarantee that no more than a single occurrence will + * be visible. + * + * First, map the surface extents into pattern space (since + * xstep and ystep are in pattern space). Then use an upper + * bound on the length of the diagonal of the pattern image + * and the surface as repeat size. This guarantees to never + * repeat visibly. + */ + double x1 = 0.0, y1 = 0.0; + double x2 = surface->surface_extents.width; + double y2 = surface->surface_extents.height; + _cairo_matrix_transform_bounding_box (&pattern->matrix, + &x1, &y1, &x2, &y2, + NULL); + + /* Rather than computing precise bounds of the union, just + * add the surface extents unconditionally. We only + * required an answer that's large enough, we don't really + * care if it's not as tight as possible.*/ + xstep = ystep = ceil ((x2 - x1) + (y2 - y1) + + pattern_extents.width + pattern_extents.height); + } + break; + case CAIRO_EXTEND_REPEAT: + xstep = pattern_extents.width; + ystep = pattern_extents.height; + break; + + case CAIRO_EXTEND_REFLECT: + pattern_extents.width *= 2; + pattern_extents.height *= 2; + xstep = pattern_extents.width; + ystep = pattern_extents.height; + break; + + /* All the rest (if any) should have been analyzed away, so this + * case should be unreachable. */ + default: + ASSERT_NOT_REACHED; + xstep = 0; + ystep = 0; + } + + /* At this point, (that is, within the surface backend interface), + * the pattern's matrix maps from cairo's device space to cairo's + * pattern space, (both with their origin at the upper-left, and + * cairo's pattern space of size width,height). + * + * Then, we must emit a PDF pattern object that maps from its own + * pattern space, (which has a size that we establish in the BBox + * dictionary entry), to the PDF page's *initial* space, (which + * does not benefit from the Y-axis flipping matrix that we emit + * on each page). So the PDF patterns matrix maps from a + * (width,height) pattern space to a device space with the origin + * in the lower-left corner. + * + * So to handle all of that, we start with an identity matrix for + * the PDF pattern to device matrix. We translate it up by the + * image height then flip it in the Y direction, (moving us from + * the PDF origin to cairo's origin). We then multiply in the + * inverse of the cairo pattern matrix, (since it maps from device + * to pattern, while we're setting up pattern to device). Finally, + * we translate back down by the image height and flip again to + * end up at the lower-left origin that PDF expects. + * + * Additionally, within the stream that paints the pattern itself, + * we are using a PDF image object that has a size of (1,1) so we + * have to scale it up by the image width and height to fill our + * pattern cell. + */ + cairo_p2d = pattern->matrix; + status = cairo_matrix_invert (&cairo_p2d); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + + if (pdf_pattern->inverted_y_axis) + cairo_matrix_init (&mat, 1, 0, 0, 1, 0, 0); + else + cairo_matrix_init (&mat, 1, 0, 0, -1, 0, surface->height); + + cairo_matrix_multiply (&pdf_p2d, &cairo_p2d, &mat); + cairo_matrix_translate (&pdf_p2d, x_offset, y_offset); + if (pdf_source->emit_image) { + cairo_matrix_translate (&pdf_p2d, 0.0, pdf_source->extents.height); + cairo_matrix_scale (&pdf_p2d, 1.0, -1.0); + } + + _get_bbox_from_extents (&pattern_extents, &bbox); + _cairo_pdf_surface_update_object (surface, pdf_pattern->pattern_res); + status = _cairo_pdf_surface_open_stream (surface, + &pdf_pattern->pattern_res, + FALSE, + " /PatternType 1\n" + " /BBox [ %f %f %f %f ]\n" + " /XStep %f\n" + " /YStep %f\n" + " /TilingType 1\n" + " /PaintType 1\n" + " /Matrix [ %f %f %f %f %f %f ]\n" + " /Resources << /XObject << /x%d %d 0 R >> >>\n", + bbox.p1.x, bbox.p1.y, bbox.p2.x, bbox.p2.y, + xstep, ystep, + pdf_p2d.xx, pdf_p2d.yx, + pdf_p2d.xy, pdf_p2d.yy, + pdf_p2d.x0, pdf_p2d.y0, + pdf_source->surface_res.id, + pdf_source->surface_res.id); + if (unlikely (status)) + return status; + + if (pdf_source->emit_image) { + snprintf(draw_surface, + sizeof (draw_surface), + "q %d 0 0 %d 0 0 cm /x%d Do Q", + pdf_source->extents.width, + pdf_source->extents.height, + pdf_source->surface_res.id); + } else { + snprintf(draw_surface, + sizeof (draw_surface), + "/x%d Do", + pdf_source->surface_res.id); + } + + if (extend == CAIRO_EXTEND_REFLECT) { + cairo_rectangle_int_t p_extents = pdf_source->extents; + snprintf(draw_surface2, + sizeof (draw_surface2), + "%d %d %d %d re W n %s", + p_extents.x, p_extents.y, + p_extents.width, p_extents.height, + draw_surface); + + _cairo_output_stream_printf (surface->output, "q %s Q\n", draw_surface2); + + cairo_matrix_init_translate (&mat, p_extents.x, p_extents.y); + cairo_matrix_scale (&mat, -1, 1); + cairo_matrix_translate (&mat, -2*p_extents.width, 0); + cairo_matrix_translate (&mat, -p_extents.x, -p_extents.y); + _cairo_output_stream_printf (surface->output, "q "); + _cairo_output_stream_print_matrix (surface->output, &mat); + _cairo_output_stream_printf (surface->output, " cm %s Q\n", draw_surface2); + + cairo_matrix_init_translate (&mat, p_extents.x, p_extents.y); + cairo_matrix_scale (&mat, 1, -1); + cairo_matrix_translate (&mat, 0, -2*p_extents.height); + cairo_matrix_translate (&mat, -p_extents.x, -p_extents.y); + _cairo_output_stream_printf (surface->output, "q "); + _cairo_output_stream_print_matrix (surface->output, &mat); + _cairo_output_stream_printf (surface->output, " cm %s Q\n", draw_surface2); + + cairo_matrix_init_translate (&mat, p_extents.x, p_extents.y); + cairo_matrix_scale (&mat, -1, -1); + cairo_matrix_translate (&mat, -2*p_extents.width, -2*p_extents.height); + cairo_matrix_translate (&mat, -p_extents.x, -p_extents.y); + _cairo_output_stream_printf (surface->output, "q "); + _cairo_output_stream_print_matrix (surface->output, &mat); + _cairo_output_stream_printf (surface->output, " cm %s Q\n", draw_surface2); + } else { + _cairo_output_stream_printf (surface->output, + " %s \n", + draw_surface); + } + + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) + return status; + + return _cairo_output_stream_get_status (surface->output); +} + +typedef struct _cairo_pdf_color_stop { + double offset; + double color[4]; + cairo_pdf_resource_t resource; +} cairo_pdf_color_stop_t; + +static cairo_int_status_t +cairo_pdf_surface_emit_rgb_linear_function (cairo_pdf_surface_t *surface, + cairo_pdf_color_stop_t *stop1, + cairo_pdf_color_stop_t *stop2, + cairo_pdf_resource_t *function) +{ + int num_elems, i; + cairo_pdf_rgb_linear_function_t elem; + cairo_pdf_resource_t res; + cairo_int_status_t status; + + num_elems = _cairo_array_num_elements (&surface->rgb_linear_functions); + for (i = 0; i < num_elems; i++) { + _cairo_array_copy_element (&surface->rgb_linear_functions, i, &elem); + if (memcmp (&elem.color1[0], &stop1->color[0], sizeof (double)*3) != 0) + continue; + if (memcmp (&elem.color2[0], &stop2->color[0], sizeof (double)*3) != 0) + continue; + *function = elem.resource; + return CAIRO_STATUS_SUCCESS; + } + + res = _cairo_pdf_surface_new_object (surface); + if (res.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /FunctionType 2\n" + " /Domain [ 0 1 ]\n" + " /C0 [ %f %f %f ]\n" + " /C1 [ %f %f %f ]\n" + " /N 1\n" + ">>\n" + "endobj\n", + res.id, + stop1->color[0], + stop1->color[1], + stop1->color[2], + stop2->color[0], + stop2->color[1], + stop2->color[2]); + + elem.resource = res; + memcpy (&elem.color1[0], &stop1->color[0], sizeof (double)*3); + memcpy (&elem.color2[0], &stop2->color[0], sizeof (double)*3); + + status = _cairo_array_append (&surface->rgb_linear_functions, &elem); + *function = res; + + return status; +} + +static cairo_int_status_t +cairo_pdf_surface_emit_alpha_linear_function (cairo_pdf_surface_t *surface, + cairo_pdf_color_stop_t *stop1, + cairo_pdf_color_stop_t *stop2, + cairo_pdf_resource_t *function) +{ + int num_elems, i; + cairo_pdf_alpha_linear_function_t elem; + cairo_pdf_resource_t res; + cairo_int_status_t status; + + num_elems = _cairo_array_num_elements (&surface->alpha_linear_functions); + for (i = 0; i < num_elems; i++) { + _cairo_array_copy_element (&surface->alpha_linear_functions, i, &elem); + if (elem.alpha1 != stop1->color[3]) + continue; + if (elem.alpha2 != stop2->color[3]) + continue; + *function = elem.resource; + return CAIRO_STATUS_SUCCESS; + } + + res = _cairo_pdf_surface_new_object (surface); + if (res.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /FunctionType 2\n" + " /Domain [ 0 1 ]\n" + " /C0 [ %f ]\n" + " /C1 [ %f ]\n" + " /N 1\n" + ">>\n" + "endobj\n", + res.id, + stop1->color[3], + stop2->color[3]); + + elem.resource = res; + elem.alpha1 = stop1->color[3]; + elem.alpha2 = stop2->color[3]; + + status = _cairo_array_append (&surface->alpha_linear_functions, &elem); + *function = res; + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_stitched_colorgradient (cairo_pdf_surface_t *surface, + unsigned int n_stops, + cairo_pdf_color_stop_t *stops, + cairo_bool_t is_alpha, + cairo_pdf_resource_t *function) +{ + cairo_pdf_resource_t res; + unsigned int i; + cairo_int_status_t status; + + /* emit linear gradients between pairs of subsequent stops... */ + for (i = 0; i < n_stops-1; i++) { + if (is_alpha) { + status = cairo_pdf_surface_emit_alpha_linear_function (surface, + &stops[i], + &stops[i+1], + &stops[i].resource); + if (unlikely (status)) + return status; + } else { + status = cairo_pdf_surface_emit_rgb_linear_function (surface, + &stops[i], + &stops[i+1], + &stops[i].resource); + if (unlikely (status)) + return status; + } + } + + /* ... and stitch them together */ + res = _cairo_pdf_surface_new_object (surface); + if (res.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /FunctionType 3\n" + " /Domain [ %f %f ]\n", + res.id, + stops[0].offset, + stops[n_stops - 1].offset); + + _cairo_output_stream_printf (surface->output, + " /Functions [ "); + for (i = 0; i < n_stops-1; i++) + _cairo_output_stream_printf (surface->output, + "%d 0 R ", stops[i].resource.id); + _cairo_output_stream_printf (surface->output, + "]\n"); + + _cairo_output_stream_printf (surface->output, + " /Bounds [ "); + for (i = 1; i < n_stops-1; i++) + _cairo_output_stream_printf (surface->output, + "%f ", stops[i].offset); + _cairo_output_stream_printf (surface->output, + "]\n"); + + _cairo_output_stream_printf (surface->output, + " /Encode [ "); + for (i = 1; i < n_stops; i++) + _cairo_output_stream_printf (surface->output, + "0 1 "); + _cairo_output_stream_printf (surface->output, + "]\n"); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + *function = res; + + return _cairo_output_stream_get_status (surface->output); +} + + +static void +calc_gradient_color (cairo_pdf_color_stop_t *new_stop, + cairo_pdf_color_stop_t *stop1, + cairo_pdf_color_stop_t *stop2) +{ + int i; + double offset = stop1->offset / (stop1->offset + 1.0 - stop2->offset); + + for (i = 0; i < 4; i++) + new_stop->color[i] = stop1->color[i] + offset*(stop2->color[i] - stop1->color[i]); +} + +#define COLOR_STOP_EPSILON 1e-6 + +static cairo_int_status_t +_cairo_pdf_surface_emit_pattern_stops (cairo_pdf_surface_t *surface, + cairo_gradient_pattern_t *pattern, + cairo_pdf_resource_t *color_function, + cairo_pdf_resource_t *alpha_function) +{ + cairo_pdf_color_stop_t *allstops, *stops; + unsigned int n_stops; + unsigned int i; + cairo_bool_t emit_alpha = FALSE; + cairo_int_status_t status; + + color_function->id = 0; + alpha_function->id = 0; + + allstops = _cairo_malloc_ab ((pattern->n_stops + 2), sizeof (cairo_pdf_color_stop_t)); + if (unlikely (allstops == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + stops = &allstops[1]; + n_stops = pattern->n_stops; + + for (i = 0; i < n_stops; i++) { + stops[i].color[0] = pattern->stops[i].color.red; + stops[i].color[1] = pattern->stops[i].color.green; + stops[i].color[2] = pattern->stops[i].color.blue; + stops[i].color[3] = pattern->stops[i].color.alpha; + if (!CAIRO_ALPHA_IS_OPAQUE (stops[i].color[3])) + emit_alpha = TRUE; + stops[i].offset = pattern->stops[i].offset; + } + + if (pattern->base.extend == CAIRO_EXTEND_REPEAT || + pattern->base.extend == CAIRO_EXTEND_REFLECT) { + if (stops[0].offset > COLOR_STOP_EPSILON) { + if (pattern->base.extend == CAIRO_EXTEND_REFLECT) + memcpy (allstops, stops, sizeof (cairo_pdf_color_stop_t)); + else + calc_gradient_color (&allstops[0], &stops[0], &stops[n_stops-1]); + stops = allstops; + n_stops++; + } + stops[0].offset = 0.0; + + if (stops[n_stops-1].offset < 1.0 - COLOR_STOP_EPSILON) { + if (pattern->base.extend == CAIRO_EXTEND_REFLECT) { + memcpy (&stops[n_stops], + &stops[n_stops - 1], + sizeof (cairo_pdf_color_stop_t)); + } else { + calc_gradient_color (&stops[n_stops], &stops[0], &stops[n_stops-1]); + } + n_stops++; + } + stops[n_stops-1].offset = 1.0; + } + + if (stops[0].offset == stops[n_stops - 1].offset) { + /* + * The first and the last stops have the same offset, but we + * don't want a function with an empty domain, because that + * would provoke underdefined behaviour from rasterisers. + * This can only happen with EXTEND_PAD, because EXTEND_NONE + * is optimised into a clear pattern in cairo-gstate, and + * REFLECT/REPEAT are always transformed to have the first + * stop at t=0 and the last stop at t=1. Thus we want a step + * function going from the first color to the last one. + * + * This can be accomplished by stitching three functions: + * - a constant first color function, + * - a step from the first color to the last color (with empty domain) + * - a constant last color function + */ + cairo_pdf_color_stop_t pad_stops[4]; + + assert (pattern->base.extend == CAIRO_EXTEND_PAD); + + pad_stops[0] = pad_stops[1] = stops[0]; + pad_stops[2] = pad_stops[3] = stops[n_stops - 1]; + + pad_stops[0].offset = 0; + pad_stops[3].offset = 1; + + status = _cairo_pdf_surface_emit_stitched_colorgradient (surface, + 4, + pad_stops, + FALSE, + color_function); + if (unlikely (status)) + goto BAIL; + + if (emit_alpha) { + status = _cairo_pdf_surface_emit_stitched_colorgradient (surface, + 4, + pad_stops, + TRUE, + alpha_function); + if (unlikely (status)) + goto BAIL; + } + } else if (n_stops == 2) { + /* no need for stitched function */ + status = cairo_pdf_surface_emit_rgb_linear_function (surface, + &stops[0], + &stops[n_stops - 1], + color_function); + if (unlikely (status)) + goto BAIL; + + if (emit_alpha) { + status = cairo_pdf_surface_emit_alpha_linear_function (surface, + &stops[0], + &stops[n_stops - 1], + alpha_function); + if (unlikely (status)) + goto BAIL; + } + } else { + /* multiple stops: stitch. XXX possible optimization: regularly spaced + * stops do not require stitching. XXX */ + status = _cairo_pdf_surface_emit_stitched_colorgradient (surface, + n_stops, + stops, + FALSE, + color_function); + if (unlikely (status)) + goto BAIL; + + if (emit_alpha) { + status = _cairo_pdf_surface_emit_stitched_colorgradient (surface, + n_stops, + stops, + TRUE, + alpha_function); + if (unlikely (status)) + goto BAIL; + } + } + +BAIL: + free (allstops); + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_repeating_function (cairo_pdf_surface_t *surface, + cairo_gradient_pattern_t *pattern, + cairo_pdf_resource_t *function, + int begin, + int end) +{ + cairo_pdf_resource_t res; + int i; + + res = _cairo_pdf_surface_new_object (surface); + if (res.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /FunctionType 3\n" + " /Domain [ %d %d ]\n", + res.id, + begin, + end); + + _cairo_output_stream_printf (surface->output, + " /Functions [ "); + for (i = begin; i < end; i++) + _cairo_output_stream_printf (surface->output, + "%d 0 R ", function->id); + _cairo_output_stream_printf (surface->output, + "]\n"); + + _cairo_output_stream_printf (surface->output, + " /Bounds [ "); + for (i = begin + 1; i < end; i++) + _cairo_output_stream_printf (surface->output, + "%d ", i); + _cairo_output_stream_printf (surface->output, + "]\n"); + + _cairo_output_stream_printf (surface->output, + " /Encode [ "); + for (i = begin; i < end; i++) { + if ((i % 2) && pattern->base.extend == CAIRO_EXTEND_REFLECT) { + _cairo_output_stream_printf (surface->output, + "1 0 "); + } else { + _cairo_output_stream_printf (surface->output, + "0 1 "); + } + } + _cairo_output_stream_printf (surface->output, + "]\n"); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + *function = res; + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +cairo_pdf_surface_emit_transparency_group (cairo_pdf_surface_t *surface, + cairo_pdf_pattern_t *pdf_pattern, + cairo_pdf_resource_t gstate_resource, + cairo_pdf_resource_t gradient_mask) +{ + cairo_pdf_resource_t smask_resource; + cairo_int_status_t status; + char buf[100]; + double x1, y1, x2, y2; + + if (pdf_pattern->is_shading) { + snprintf(buf, sizeof(buf), + " /Shading\n" + " << /sh%d %d 0 R >>\n", + gradient_mask.id, + gradient_mask.id); + } else { + snprintf(buf, sizeof(buf), + " /Pattern\n" + " << /p%d %d 0 R >>\n", + gradient_mask.id, + gradient_mask.id); + } + + if (pdf_pattern->is_shading) { + cairo_box_t box; + + /* When emitting a shading operator we are in cairo pattern + * coordinates. _cairo_pdf_surface_paint_gradient has set the + * ctm to the pattern matrix (including the conversion from + * pdf to cairo coordinates) */ + _cairo_box_from_rectangle (&box, &pdf_pattern->extents); + _cairo_box_to_doubles (&box, &x1, &y1, &x2, &y2); + _cairo_matrix_transform_bounding_box (&pdf_pattern->pattern->matrix, &x1, &y1, &x2, &y2, NULL); + } else { + cairo_box_double_t box; + + /* When emitting a shading pattern we are in pdf page + * coordinates. The color and alpha shading patterns painted + * in the XObject below contain the cairo pattern to pdf page + * matrix in the /Matrix entry of the pattern. */ + _get_bbox_from_extents (&pdf_pattern->extents, &box); + x1 = box.p1.x; + y1 = box.p1.y; + x2 = box.p2.x; + y2 = box.p2.y; + } + status = _cairo_pdf_surface_open_stream (surface, + NULL, + surface->compress_content, + " /Type /XObject\n" + " /Subtype /Form\n" + " /FormType 1\n" + " /BBox [ %f %f %f %f ]\n" + " /Resources\n" + " << /ExtGState\n" + " << /a0 << /ca 1 /CA 1 >>" + " >>\n" + "%s" + " >>\n" + " /Group\n" + " << /Type /Group\n" + " /S /Transparency\n" + " /I true\n" + " /CS /DeviceGray\n" + " >>\n", + x1,y1,x2,y2, + buf); + if (unlikely (status)) + return status; + + if (pdf_pattern->is_shading) { + _cairo_output_stream_printf (surface->output, + "/a0 gs /sh%d sh\n", + gradient_mask.id); + } else { + _cairo_output_stream_printf (surface->output, + "q\n" + "/a0 gs\n" + "/Pattern cs /p%d scn\n" + "0 0 %f %f re\n" + "f\n" + "Q\n", + gradient_mask.id, + surface->width, + surface->height); + } + + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) + return status; + + smask_resource = _cairo_pdf_surface_new_object (surface); + if (smask_resource.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Mask\n" + " /S /Luminosity\n" + " /G %d 0 R\n" + ">>\n" + "endobj\n", + smask_resource.id, + surface->pdf_stream.self.id); + + /* Create GState which uses the transparency group as an SMask. */ + _cairo_pdf_surface_update_object (surface, gstate_resource); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /ExtGState\n" + " /SMask %d 0 R\n" + " /ca 1\n" + " /CA 1\n" + " /AIS false\n" + ">>\n" + "endobj\n", + gstate_resource.id, + smask_resource.id); + + return _cairo_output_stream_get_status (surface->output); +} + +static void +_cairo_pdf_surface_output_gradient (cairo_pdf_surface_t *surface, + const cairo_pdf_pattern_t *pdf_pattern, + cairo_pdf_resource_t pattern_resource, + const cairo_matrix_t *pat_to_pdf, + const cairo_circle_double_t*start, + const cairo_circle_double_t*end, + const double *domain, + const char *colorspace, + cairo_pdf_resource_t color_function) +{ + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n", + pattern_resource.id); + + if (!pdf_pattern->is_shading) { + _cairo_output_stream_printf (surface->output, + "<< /Type /Pattern\n" + " /PatternType 2\n" + " /Matrix [ "); + _cairo_output_stream_print_matrix (surface->output, pat_to_pdf); + _cairo_output_stream_printf (surface->output, + " ]\n" + " /Shading\n"); + } + + if (pdf_pattern->pattern->type == CAIRO_PATTERN_TYPE_LINEAR) { + _cairo_output_stream_printf (surface->output, + " << /ShadingType 2\n" + " /ColorSpace %s\n" + " /Coords [ %f %f %f %f ]\n", + colorspace, + start->center.x, start->center.y, + end->center.x, end->center.y); + } else { + _cairo_output_stream_printf (surface->output, + " << /ShadingType 3\n" + " /ColorSpace %s\n" + " /Coords [ %f %f %f %f %f %f ]\n", + colorspace, + start->center.x, start->center.y, + MAX (start->radius, 0), + end->center.x, end->center.y, + MAX (end->radius, 0)); + } + + _cairo_output_stream_printf (surface->output, + " /Domain [ %f %f ]\n", + domain[0], domain[1]); + + if (pdf_pattern->pattern->extend != CAIRO_EXTEND_NONE) { + _cairo_output_stream_printf (surface->output, + " /Extend [ true true ]\n"); + } else { + _cairo_output_stream_printf (surface->output, + " /Extend [ false false ]\n"); + } + + _cairo_output_stream_printf (surface->output, + " /Function %d 0 R\n" + " >>\n", + color_function.id); + + if (!pdf_pattern->is_shading) { + _cairo_output_stream_printf (surface->output, + ">>\n"); + } + + _cairo_output_stream_printf (surface->output, + "endobj\n"); +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_gradient (cairo_pdf_surface_t *surface, + cairo_pdf_pattern_t *pdf_pattern) +{ + cairo_gradient_pattern_t *pattern = (cairo_gradient_pattern_t *) pdf_pattern->pattern; + cairo_pdf_resource_t color_function, alpha_function; + cairo_matrix_t pat_to_pdf; + cairo_circle_double_t start, end; + double domain[2]; + cairo_int_status_t status; + cairo_matrix_t mat; + + assert (pattern->n_stops != 0); + + status = _cairo_pdf_surface_emit_pattern_stops (surface, + pattern, + &color_function, + &alpha_function); + if (unlikely (status)) + return status; + + pat_to_pdf = pattern->base.matrix; + status = cairo_matrix_invert (&pat_to_pdf); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + + if (pdf_pattern->inverted_y_axis) + cairo_matrix_init (&mat, 1, 0, 0, 1, 0, 0); + else + cairo_matrix_init (&mat, 1, 0, 0, -1, 0, surface->height); + + cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &mat); + + if (pattern->base.extend == CAIRO_EXTEND_REPEAT || + pattern->base.extend == CAIRO_EXTEND_REFLECT) + { + double bounds_x1, bounds_x2, bounds_y1, bounds_y2; + double x_scale, y_scale, tolerance; + + /* TODO: use tighter extents */ + bounds_x1 = 0; + bounds_y1 = 0; + bounds_x2 = surface->width; + bounds_y2 = surface->height; + _cairo_matrix_transform_bounding_box (&pattern->base.matrix, + &bounds_x1, &bounds_y1, + &bounds_x2, &bounds_y2, + NULL); + + x_scale = surface->base.x_resolution / surface->base.x_fallback_resolution; + y_scale = surface->base.y_resolution / surface->base.y_fallback_resolution; + + tolerance = fabs (_cairo_matrix_compute_determinant (&pattern->base.matrix)); + tolerance /= _cairo_matrix_transformed_circle_major_axis (&pattern->base.matrix, 1); + tolerance *= MIN (x_scale, y_scale); + + _cairo_gradient_pattern_box_to_parameter (pattern, + bounds_x1, bounds_y1, + bounds_x2, bounds_y2, + tolerance, domain); + } else if (pattern->stops[0].offset == pattern->stops[pattern->n_stops - 1].offset) { + /* + * If the first and the last stop offset are the same, then + * the color function is a step function. + * _cairo_ps_surface_emit_pattern_stops emits it as a stitched + * function no matter how many stops the pattern has. The + * domain of the stitched function will be [0 1] in this case. + * + * This is done to avoid emitting degenerate gradients for + * EXTEND_PAD patterns having a step color function. + */ + domain[0] = 0.0; + domain[1] = 1.0; + + assert (pattern->base.extend == CAIRO_EXTEND_PAD); + } else { + domain[0] = pattern->stops[0].offset; + domain[1] = pattern->stops[pattern->n_stops - 1].offset; + } + + /* PDF requires the first and last stop to be the same as the + * extreme coordinates. For repeating patterns this moves the + * extreme coordinates out to the begin/end of the repeating + * function. For non repeating patterns this may move the extreme + * coordinates in if there are not stops at offset 0 and 1. */ + _cairo_gradient_pattern_interpolate (pattern, domain[0], &start); + _cairo_gradient_pattern_interpolate (pattern, domain[1], &end); + + if (pattern->base.extend == CAIRO_EXTEND_REPEAT || + pattern->base.extend == CAIRO_EXTEND_REFLECT) + { + int repeat_begin, repeat_end; + + repeat_begin = floor (domain[0]); + repeat_end = ceil (domain[1]); + + status = _cairo_pdf_surface_emit_repeating_function (surface, + pattern, + &color_function, + repeat_begin, + repeat_end); + if (unlikely (status)) + return status; + + if (alpha_function.id != 0) { + status = _cairo_pdf_surface_emit_repeating_function (surface, + pattern, + &alpha_function, + repeat_begin, + repeat_end); + if (unlikely (status)) + return status; + } + } else if (pattern->n_stops <= 2) { + /* For EXTEND_NONE and EXTEND_PAD if there are only two stops a + * Type 2 function is used by itself without a stitching + * function. Type 2 functions always have the domain [0 1] */ + domain[0] = 0.0; + domain[1] = 1.0; + } + + _cairo_pdf_surface_update_object (surface, pdf_pattern->pattern_res); + _cairo_pdf_surface_output_gradient (surface, pdf_pattern, + pdf_pattern->pattern_res, + &pat_to_pdf, &start, &end, domain, + "/DeviceRGB", color_function); + + if (alpha_function.id != 0) { + cairo_pdf_resource_t mask_resource; + + assert (pdf_pattern->gstate_res.id != 0); + + /* Create pattern for SMask. */ + mask_resource = _cairo_pdf_surface_new_object (surface); + if (mask_resource.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_pdf_surface_output_gradient (surface, pdf_pattern, + mask_resource, + &pat_to_pdf, &start, &end, domain, + "/DeviceGray", alpha_function); + + status = cairo_pdf_surface_emit_transparency_group (surface, + pdf_pattern, + pdf_pattern->gstate_res, + mask_resource); + if (unlikely (status)) + return status; + } + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_mesh_pattern (cairo_pdf_surface_t *surface, + cairo_pdf_pattern_t *pdf_pattern) +{ + cairo_matrix_t pat_to_pdf; + cairo_int_status_t status; + cairo_pattern_t *pattern = pdf_pattern->pattern; + cairo_pdf_shading_t shading; + int i; + cairo_pdf_resource_t res; + cairo_matrix_t mat; + + pat_to_pdf = pattern->matrix; + status = cairo_matrix_invert (&pat_to_pdf); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + + if (pdf_pattern->inverted_y_axis) + cairo_matrix_init (&mat, 1, 0, 0, 1, 0, 0); + else + cairo_matrix_init (&mat, 1, 0, 0, -1, 0, surface->height); + + cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &mat); + + status = _cairo_pdf_shading_init_color (&shading, (cairo_mesh_pattern_t *) pattern); + if (unlikely (status)) + return status; + + res = _cairo_pdf_surface_new_object (surface); + if (unlikely (res.id == 0)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /ShadingType %d\n" + " /ColorSpace /DeviceRGB\n" + " /BitsPerCoordinate %d\n" + " /BitsPerComponent %d\n" + " /BitsPerFlag %d\n" + " /Decode [", + res.id, + shading.shading_type, + shading.bits_per_coordinate, + shading.bits_per_component, + shading.bits_per_flag); + + for (i = 0; i < shading.decode_array_length; i++) + _cairo_output_stream_printf (surface->output, "%f ", shading.decode_array[i]); + + _cairo_output_stream_printf (surface->output, + "]\n" + " /Length %ld\n" + ">>\n" + "stream\n", + shading.data_length); + + _cairo_output_stream_write (surface->output, shading.data, shading.data_length); + + _cairo_output_stream_printf (surface->output, + "\nendstream\n" + "endobj\n"); + + _cairo_pdf_shading_fini (&shading); + + _cairo_pdf_surface_update_object (surface, pdf_pattern->pattern_res); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Pattern\n" + " /PatternType 2\n" + " /Matrix [ ", + pdf_pattern->pattern_res.id); + _cairo_output_stream_print_matrix (surface->output, &pat_to_pdf); + _cairo_output_stream_printf (surface->output, + " ]\n" + " /Shading %d 0 R\n" + ">>\n" + "endobj\n", + res.id); + + if (pdf_pattern->gstate_res.id != 0) { + cairo_pdf_resource_t mask_resource; + + /* Create pattern for SMask. */ + res = _cairo_pdf_surface_new_object (surface); + if (unlikely (res.id == 0)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + status = _cairo_pdf_shading_init_alpha (&shading, (cairo_mesh_pattern_t *) pattern); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /ShadingType %d\n" + " /ColorSpace /DeviceGray\n" + " /BitsPerCoordinate %d\n" + " /BitsPerComponent %d\n" + " /BitsPerFlag %d\n" + " /Decode [", + res.id, + shading.shading_type, + shading.bits_per_coordinate, + shading.bits_per_component, + shading.bits_per_flag); + + for (i = 0; i < shading.decode_array_length; i++) + _cairo_output_stream_printf (surface->output, "%f ", shading.decode_array[i]); + + _cairo_output_stream_printf (surface->output, + "]\n" + " /Length %ld\n" + ">>\n" + "stream\n", + shading.data_length); + + _cairo_output_stream_write (surface->output, shading.data, shading.data_length); + + _cairo_output_stream_printf (surface->output, + "\nendstream\n" + "endobj\n"); + _cairo_pdf_shading_fini (&shading); + + mask_resource = _cairo_pdf_surface_new_object (surface); + if (unlikely (mask_resource.id == 0)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Pattern\n" + " /PatternType 2\n" + " /Matrix [ ", + mask_resource.id); + _cairo_output_stream_print_matrix (surface->output, &pat_to_pdf); + _cairo_output_stream_printf (surface->output, + " ]\n" + " /Shading %d 0 R\n" + ">>\n" + "endobj\n", + res.id); + + status = cairo_pdf_surface_emit_transparency_group (surface, + pdf_pattern, + pdf_pattern->gstate_res, + mask_resource); + if (unlikely (status)) + return status; + } + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_pattern (cairo_pdf_surface_t *surface, cairo_pdf_pattern_t *pdf_pattern) +{ + cairo_int_status_t status; + + switch (pdf_pattern->pattern->type) { + case CAIRO_PATTERN_TYPE_SOLID: + ASSERT_NOT_REACHED; + status = _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + break; + + case CAIRO_PATTERN_TYPE_SURFACE: + case CAIRO_PATTERN_TYPE_RASTER_SOURCE: + status = _cairo_pdf_surface_emit_surface_pattern (surface, pdf_pattern); + break; + + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + status = _cairo_pdf_surface_emit_gradient (surface, pdf_pattern); + break; + + case CAIRO_PATTERN_TYPE_MESH: + status = _cairo_pdf_surface_emit_mesh_pattern (surface, pdf_pattern); + break; + + default: + ASSERT_NOT_REACHED; + status = _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + break; + } + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_rectangle_int_t *extents, + double alpha, + cairo_pdf_resource_t *smask_res, + cairo_bool_t stencil_mask) +{ + cairo_matrix_t cairo_p2d, pdf_p2d; + cairo_int_status_t status; + int alpha_id; + double x_offset; + double y_offset; + cairo_pdf_source_surface_entry_t *pdf_source; + + if (source->extend == CAIRO_EXTEND_PAD && + !(source->type == CAIRO_PATTERN_TYPE_SURFACE && + ((cairo_surface_pattern_t *)source)->surface->type == CAIRO_SURFACE_TYPE_RECORDING)) + { + status = _cairo_pdf_surface_add_padded_image_surface (surface, + source, + extents, + &pdf_source, + &x_offset, + &y_offset, + NULL); + } else { + status = _cairo_pdf_surface_add_source_surface (surface, + NULL, + source, + op, + source->filter, + stencil_mask, + FALSE, /* smask */ + alpha != 1.0, /* need_transp_group */ + extents, + smask_res, + &pdf_source, + &x_offset, + &y_offset, + NULL); + } + if (unlikely (status)) + return status; + + cairo_p2d = source->matrix; + status = cairo_matrix_invert (&cairo_p2d); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + + pdf_p2d = surface->cairo_to_pdf; + cairo_matrix_multiply (&pdf_p2d, &cairo_p2d, &pdf_p2d); + cairo_matrix_translate (&pdf_p2d, x_offset, y_offset); + if (pdf_source->emit_image) { + int width, height; + + if (pdf_source->bounded) { + width = pdf_source->extents.width; + height = pdf_source->extents.height; + } else { + /* We can't scale an image to an unbounded surface size so just set the size to 1 */ + width = 1; + height = 1; + } + + cairo_matrix_translate (&pdf_p2d, 0.0, height); + cairo_matrix_scale (&pdf_p2d, 1.0, -1.0); + cairo_matrix_scale (&pdf_p2d, width, height); + } + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + if (! _cairo_matrix_is_identity (&pdf_p2d)) { + _cairo_output_stream_print_matrix (surface->output, &pdf_p2d); + _cairo_output_stream_printf (surface->output, " cm\n"); + } + + status = _cairo_pdf_surface_add_alpha (surface, alpha, &alpha_id); + if (unlikely (status)) + return status; + + if (stencil_mask) { + _cairo_output_stream_printf (surface->output, + "/x%d Do\n", + pdf_source->surface_res.id); + } else { + _cairo_output_stream_printf (surface->output, + "/a%d gs /x%d Do\n", + alpha_id, + pdf_source->surface_res.id); + } + + return _cairo_pdf_surface_add_xobject (surface, pdf_source->surface_res); +} + +static cairo_int_status_t +_cairo_pdf_surface_paint_gradient (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_rectangle_int_t *extents, + double alpha) +{ + cairo_pdf_resource_t shading_res, gstate_res; + cairo_matrix_t pat_to_pdf; + cairo_int_status_t status; + int alpha_id; + + status = _cairo_pdf_surface_add_pdf_shading (surface, source, + op, extents, + &shading_res, &gstate_res); + if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO)) + return CAIRO_INT_STATUS_SUCCESS; + if (unlikely (status)) + return status; + + pat_to_pdf = source->matrix; + status = cairo_matrix_invert (&pat_to_pdf); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &surface->cairo_to_pdf); + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + if (! _cairo_matrix_is_identity (&pat_to_pdf)) { + _cairo_output_stream_print_matrix (surface->output, &pat_to_pdf); + _cairo_output_stream_printf (surface->output, " cm\n"); + } + + status = _cairo_pdf_surface_add_shading (surface, shading_res); + if (unlikely (status)) + return status; + + if (gstate_res.id != 0) { + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "/s%d gs /sh%d sh\n", + gstate_res.id, + shading_res.id); + } else { + status = _cairo_pdf_surface_add_alpha (surface, alpha, &alpha_id); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "/a%d gs /sh%d sh\n", + alpha_id, + shading_res.id); + } + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_paint_pattern (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_rectangle_int_t *extents, + double alpha, + cairo_bool_t mask) +{ + switch (source->type) { + case CAIRO_PATTERN_TYPE_SURFACE: + case CAIRO_PATTERN_TYPE_RASTER_SOURCE: + return _cairo_pdf_surface_paint_surface_pattern (surface, + op, + source, + extents, + alpha, + NULL, + mask); + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: + return _cairo_pdf_surface_paint_gradient (surface, + op, + source, + extents, + alpha); + + case CAIRO_PATTERN_TYPE_SOLID: + default: + ASSERT_NOT_REACHED; + return CAIRO_STATUS_SUCCESS; + } +} + +static cairo_bool_t +_can_paint_pattern (const cairo_pattern_t *pattern) +{ + switch (pattern->type) { + case CAIRO_PATTERN_TYPE_SOLID: + return FALSE; + + case CAIRO_PATTERN_TYPE_SURFACE: + case CAIRO_PATTERN_TYPE_RASTER_SOURCE: + return (pattern->extend == CAIRO_EXTEND_NONE || + pattern->extend == CAIRO_EXTEND_PAD); + + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + return TRUE; + + case CAIRO_PATTERN_TYPE_MESH: + return FALSE; + + default: + ASSERT_NOT_REACHED; + return FALSE; + } +} + +static cairo_int_status_t +_cairo_pdf_surface_select_operator (cairo_pdf_surface_t *surface, + cairo_operator_t op) +{ + cairo_int_status_t status; + + if (op == surface->current_operator) + return CAIRO_STATUS_SUCCESS; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "/b%d gs\n", op); + surface->current_operator = op; + _cairo_pdf_surface_add_operator (surface, op); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_select_pattern (cairo_pdf_surface_t *surface, + const cairo_pattern_t *pattern, + cairo_pdf_resource_t pattern_res, + cairo_bool_t is_stroke) +{ + cairo_int_status_t status; + int alpha; + const cairo_color_t *solid_color = NULL; + + if (pattern->type == CAIRO_PATTERN_TYPE_SOLID) { + const cairo_solid_pattern_t *solid = (const cairo_solid_pattern_t *) pattern; + + solid_color = &solid->color; + } + + if (solid_color != NULL) { + if (surface->current_pattern_is_solid_color == FALSE || + surface->current_color_red != solid_color->red || + surface->current_color_green != solid_color->green || + surface->current_color_blue != solid_color->blue || + surface->current_color_is_stroke != is_stroke) + { + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "%f %f %f ", + solid_color->red, + solid_color->green, + solid_color->blue); + + if (is_stroke) + _cairo_output_stream_printf (surface->output, "RG "); + else + _cairo_output_stream_printf (surface->output, "rg "); + + surface->current_color_red = solid_color->red; + surface->current_color_green = solid_color->green; + surface->current_color_blue = solid_color->blue; + surface->current_color_is_stroke = is_stroke; + } + + if (surface->current_pattern_is_solid_color == FALSE || + surface->current_color_alpha != solid_color->alpha) + { + status = _cairo_pdf_surface_add_alpha (surface, solid_color->alpha, &alpha); + if (unlikely (status)) + return status; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "/a%d gs\n", + alpha); + surface->current_color_alpha = solid_color->alpha; + } + + surface->current_pattern_is_solid_color = TRUE; + } else { + status = _cairo_pdf_surface_add_alpha (surface, 1.0, &alpha); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_add_pattern (surface, pattern_res); + if (unlikely (status)) + return status; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + /* fill-stroke calls select_pattern twice. Don't save if the + * gstate is already saved. */ + if (!surface->select_pattern_gstate_saved) + _cairo_output_stream_printf (surface->output, "q "); + + if (is_stroke) { + _cairo_output_stream_printf (surface->output, + "/Pattern CS /p%d SCN ", + pattern_res.id); + } else { + _cairo_output_stream_printf (surface->output, + "/Pattern cs /p%d scn ", + pattern_res.id); + } + _cairo_output_stream_printf (surface->output, + "/a%d gs\n", + alpha); + surface->select_pattern_gstate_saved = TRUE; + surface->current_pattern_is_solid_color = FALSE; + } + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +_cairo_pdf_surface_unselect_pattern (cairo_pdf_surface_t *surface) +{ + cairo_int_status_t status; + + if (surface->select_pattern_gstate_saved) { + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "Q\n"); + _cairo_pdf_operators_reset (&surface->pdf_operators); + surface->current_pattern_is_solid_color = FALSE; + } + surface->select_pattern_gstate_saved = FALSE; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_show_page (void *abstract_surface) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_int_status_t status; + + status = _cairo_array_append (&surface->page_heights, &surface->height); + if (unlikely (status)) + return status; + + status = _cairo_array_append (&surface->page_labels, &surface->current_page_label); + if (unlikely (status)) + return status; + + surface->current_page_label = NULL; + + status = _cairo_pdf_interchange_end_page_content (surface); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_close_content_stream (surface); + if (unlikely (status)) + return status; + + _cairo_surface_clipper_reset (&surface->clipper); + + status = _cairo_pdf_surface_write_page (surface); + if (unlikely (status)) + return status; + + _cairo_pdf_surface_clear (surface); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_bool_t +_cairo_pdf_surface_get_extents (void *abstract_surface, + cairo_rectangle_int_t *rectangle) +{ + cairo_pdf_surface_t *surface = abstract_surface; + + if (surface->surface_bounded) + *rectangle = surface->surface_extents; + + return surface->surface_bounded; +} + +static void +_cairo_pdf_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 void +_cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface) +{ + cairo_pdf_resource_t page; + int num_pages, i; + + _cairo_pdf_surface_update_object (surface, surface->pages_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Pages\n" + " /Kids [ ", + surface->pages_resource.id); + + num_pages = _cairo_array_num_elements (&surface->pages); + for (i = 0; i < num_pages; i++) { + _cairo_array_copy_element (&surface->pages, i, &page); + _cairo_output_stream_printf (surface->output, "%d 0 R ", page.id); + } + + _cairo_output_stream_printf (surface->output, "]\n"); + _cairo_output_stream_printf (surface->output, " /Count %d\n", num_pages); + + + /* TODO: Figure out which other defaults to be inherited by /Page + * objects. */ + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); +} + +cairo_int_status_t +_cairo_utf8_to_pdf_string (const char *utf8, char **str_out) +{ + int i; + int len; + cairo_bool_t ascii; + char *str; + cairo_int_status_t status = CAIRO_STATUS_SUCCESS; + + ascii = TRUE; + len = strlen (utf8); + for (i = 0; i < len; i++) { + unsigned c = utf8[i]; + if (c < 32 || c > 126 || c == '(' || c == ')' || c == '\\') { + ascii = FALSE; + break; + } + } + + if (ascii) { + str = _cairo_malloc (len + 3); + if (str == NULL) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + str[0] = '('; + for (i = 0; i < len; i++) + str[i+1] = utf8[i]; + str[i+1] = ')'; + str[i+2] = 0; + } else { + uint16_t *utf16 = NULL; + int utf16_len = 0; + + status = _cairo_utf8_to_utf16 (utf8, -1, &utf16, &utf16_len); + if (unlikely (status)) + return status; + + str = _cairo_malloc (utf16_len*4 + 7); + if (str == NULL) { + free (utf16); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + strcpy (str, "<FEFF"); + for (i = 0; i < utf16_len; i++) + snprintf (str + 4*i + 5, 5, "%04X", utf16[i]); + + strcat (str, ">"); + free (utf16); + } + *str_out = str; + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_unicode_for_glyph (cairo_pdf_surface_t *surface, + const char *utf8) +{ + uint16_t *utf16 = NULL; + int utf16_len = 0; + cairo_int_status_t status; + int i; + + if (utf8 && *utf8) { + status = _cairo_utf8_to_utf16 (utf8, -1, &utf16, &utf16_len); + if (unlikely (status == CAIRO_INT_STATUS_INVALID_STRING)) { + utf16 = NULL; + utf16_len = 0; + } else if (unlikely (status)) { + return status; + } + } + + _cairo_output_stream_printf (surface->output, "<"); + if (utf16 == NULL || utf16_len == 0) { + /* According to the "ToUnicode Mapping File Tutorial" + * http://www.adobe.com/devnet/acrobat/pdfs/5411.ToUnicode.pdf + * + * Glyphs that do not map to a Unicode code point must be + * mapped to 0xfffd "REPLACEMENT CHARACTER". + */ + _cairo_output_stream_printf (surface->output, + "fffd"); + } else { + for (i = 0; i < utf16_len; i++) + _cairo_output_stream_printf (surface->output, + "%04x", (int) (utf16[i])); + } + _cairo_output_stream_printf (surface->output, ">"); + + free (utf16); + + return CAIRO_STATUS_SUCCESS; +} + +/* Bob Jenkins hash + * + * Public domain code from: + * http://burtleburtle.net/bob/hash/doobs.html + */ + +#define HASH_MIX(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +static uint32_t +_hash_data (const unsigned char *data, int length, uint32_t initval) +{ + uint32_t a, b, c, len; + + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + while (len >= 12) { + a += (data[0] + ((uint32_t)data[1]<<8) + ((uint32_t)data[2]<<16) + ((uint32_t)data[3]<<24)); + b += (data[4] + ((uint32_t)data[5]<<8) + ((uint32_t)data[6]<<16) + ((uint32_t)data[7]<<24)); + c += (data[8] + ((uint32_t)data[9]<<8) + ((uint32_t)data[10]<<16)+ ((uint32_t)data[11]<<24)); + HASH_MIX (a,b,c); + data += 12; + len -= 12; + } + + c += length; + switch(len) { + case 11: c+= ((uint32_t) data[10] << 24); + case 10: c+= ((uint32_t) data[9] << 16); + case 9 : c+= ((uint32_t) data[8] << 8); + case 8 : b+= ((uint32_t) data[7] << 24); + case 7 : b+= ((uint32_t) data[6] << 16); + case 6 : b+= ((uint32_t) data[5] << 8); + case 5 : b+= data[4]; + case 4 : a+= ((uint32_t) data[3] << 24); + case 3 : a+= ((uint32_t) data[2] << 16); + case 2 : a+= ((uint32_t) data[1] << 8); + case 1 : a+= data[0]; + } + HASH_MIX (a,b,c); + + return c; +} + +static void +_create_font_subset_tag (cairo_scaled_font_subset_t *font_subset, + const char *font_name, + char *tag) +{ + uint32_t hash; + int i; + long numerator; + ldiv_t d; + + hash = _hash_data ((unsigned char *) font_name, strlen(font_name), 0); + hash = _hash_data ((unsigned char *) (font_subset->glyphs), + font_subset->num_glyphs * sizeof(unsigned long), hash); + + numerator = abs (hash); + for (i = 0; i < 6; i++) { + d = ldiv (numerator, 26); + numerator = d.quot; + tag[i] = 'A' + d.rem; + } + tag[i] = 0; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_to_unicode_stream (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset, + cairo_pdf_resource_t *stream) +{ + unsigned int i, num_bfchar; + cairo_int_status_t status; + + stream->id = 0; + + status = _cairo_pdf_surface_open_stream (surface, + NULL, + surface->compress_content, + NULL); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo\n" + "<< /Registry (Adobe)\n" + " /Ordering (UCS)\n" + " /Supplement 0\n" + ">> def\n" + "/CMapName /Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n"); + + if (font_subset->is_composite && !font_subset->is_latin) { + _cairo_output_stream_printf (surface->output, + "<0000> <ffff>\n"); + } else { + _cairo_output_stream_printf (surface->output, + "<00> <ff>\n"); + } + + _cairo_output_stream_printf (surface->output, + "endcodespacerange\n"); + + if (font_subset->is_scaled) { + /* Type 3 fonts include glyph 0 in the subset */ + num_bfchar = font_subset->num_glyphs; + + /* The CMap specification has a limit of 100 characters per beginbfchar operator */ + _cairo_output_stream_printf (surface->output, + "%d beginbfchar\n", + num_bfchar > 100 ? 100 : num_bfchar); + + for (i = 0; i < num_bfchar; i++) { + if (i != 0 && i % 100 == 0) { + _cairo_output_stream_printf (surface->output, + "endbfchar\n" + "%d beginbfchar\n", + num_bfchar - i > 100 ? 100 : num_bfchar - i); + } + _cairo_output_stream_printf (surface->output, "<%02x> ", i); + status = _cairo_pdf_surface_emit_unicode_for_glyph (surface, + font_subset->utf8[i]); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "\n"); + } + } else { + /* Other fonts reserve glyph 0 for .notdef. Omit glyph 0 from the /ToUnicode map */ + num_bfchar = font_subset->num_glyphs - 1; + + /* The CMap specification has a limit of 100 characters per beginbfchar operator */ + _cairo_output_stream_printf (surface->output, + "%d beginbfchar\n", + num_bfchar > 100 ? 100 : num_bfchar); + + for (i = 0; i < num_bfchar; i++) { + if (i != 0 && i % 100 == 0) { + _cairo_output_stream_printf (surface->output, + "endbfchar\n" + "%d beginbfchar\n", + num_bfchar - i > 100 ? 100 : num_bfchar - i); + } + if (font_subset->is_latin) + _cairo_output_stream_printf (surface->output, "<%02x> ", font_subset->to_latin_char[i + 1]); + else if (font_subset->is_composite) + _cairo_output_stream_printf (surface->output, "<%04x> ", i + 1); + else + _cairo_output_stream_printf (surface->output, "<%02x> ", i + 1); + + status = _cairo_pdf_surface_emit_unicode_for_glyph (surface, + font_subset->utf8[i + 1]); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "\n"); + } + } + + _cairo_output_stream_printf (surface->output, + "endbfchar\n"); + + _cairo_output_stream_printf (surface->output, + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n"); + + *stream = surface->pdf_stream.self; + return _cairo_pdf_surface_close_stream (surface); +} + +#define PDF_UNITS_PER_EM 1000 + +static cairo_int_status_t +_cairo_pdf_surface_emit_cff_font (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset, + cairo_cff_subset_t *subset) +{ + cairo_pdf_resource_t stream, descriptor, cidfont_dict; + cairo_pdf_resource_t subset_resource, to_unicode_stream; + cairo_pdf_font_t font; + unsigned int i, last_glyph; + cairo_int_status_t status; + char tag[10]; + + _create_font_subset_tag (font_subset, subset->ps_name, tag); + + subset_resource = _cairo_pdf_surface_get_font_resource (surface, + font_subset->font_id, + font_subset->subset_id); + if (subset_resource.id == 0) + return CAIRO_STATUS_SUCCESS; + + status = _cairo_pdf_surface_open_stream (surface, + NULL, + TRUE, + font_subset->is_latin ? + " /Subtype /Type1C\n" : + " /Subtype /CIDFontType0C\n"); + if (unlikely (status)) + return status; + + stream = surface->pdf_stream.self; + _cairo_output_stream_write (surface->output, + subset->data, subset->data_length); + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_emit_to_unicode_stream (surface, + font_subset, + &to_unicode_stream); + if (_cairo_int_status_is_error (status)) + return status; + + descriptor = _cairo_pdf_surface_new_object (surface); + if (descriptor.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /FontDescriptor\n" + " /FontName /%s+%s\n", + descriptor.id, + tag, + subset->ps_name); + + if (subset->family_name_utf8) { + char *pdf_str; + + status = _cairo_utf8_to_pdf_string (subset->family_name_utf8, &pdf_str); + if (likely (status == CAIRO_INT_STATUS_SUCCESS)) { + _cairo_output_stream_printf (surface->output, + " /FontFamily %s\n", + pdf_str); + free (pdf_str); + } else if (status != CAIRO_INT_STATUS_INVALID_STRING) { + return status; + } + } + + _cairo_output_stream_printf (surface->output, + " /Flags 4\n" + " /FontBBox [ %ld %ld %ld %ld ]\n" + " /ItalicAngle 0\n" + " /Ascent %ld\n" + " /Descent %ld\n" + " /CapHeight %ld\n" + " /StemV 80\n" + " /StemH 80\n" + " /FontFile3 %u 0 R\n" + ">>\n" + "endobj\n", + (long)(subset->x_min*PDF_UNITS_PER_EM), + (long)(subset->y_min*PDF_UNITS_PER_EM), + (long)(subset->x_max*PDF_UNITS_PER_EM), + (long)(subset->y_max*PDF_UNITS_PER_EM), + (long)(subset->ascent*PDF_UNITS_PER_EM), + (long)(subset->descent*PDF_UNITS_PER_EM), + (long)(subset->y_max*PDF_UNITS_PER_EM), + stream.id); + + if (font_subset->is_latin) { + /* find last glyph used */ + for (i = 255; i >= 32; i--) + if (font_subset->latin_to_subset_glyph_index[i] > 0) + break; + + last_glyph = i; + _cairo_pdf_surface_update_object (surface, subset_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /Type1\n" + " /BaseFont /%s+%s\n" + " /FirstChar 32\n" + " /LastChar %d\n" + " /FontDescriptor %d 0 R\n" + " /Encoding /WinAnsiEncoding\n" + " /Widths [", + subset_resource.id, + tag, + subset->ps_name, + last_glyph, + descriptor.id); + + for (i = 32; i < last_glyph + 1; i++) { + int glyph = font_subset->latin_to_subset_glyph_index[i]; + if (glyph > 0) { + _cairo_output_stream_printf (surface->output, + " %ld", + (long)(subset->widths[glyph]*PDF_UNITS_PER_EM)); + } else { + _cairo_output_stream_printf (surface->output, " 0"); + } + } + + _cairo_output_stream_printf (surface->output, + " ]\n"); + + if (to_unicode_stream.id != 0) + _cairo_output_stream_printf (surface->output, + " /ToUnicode %d 0 R\n", + to_unicode_stream.id); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + } else { + cidfont_dict = _cairo_pdf_surface_new_object (surface); + if (cidfont_dict.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /CIDFontType0\n" + " /BaseFont /%s+%s\n" + " /CIDSystemInfo\n" + " << /Registry (Adobe)\n" + " /Ordering (Identity)\n" + " /Supplement 0\n" + " >>\n" + " /FontDescriptor %d 0 R\n" + " /W [0 [", + cidfont_dict.id, + tag, + subset->ps_name, + descriptor.id); + + for (i = 0; i < font_subset->num_glyphs; i++) + _cairo_output_stream_printf (surface->output, + " %ld", + (long)(subset->widths[i]*PDF_UNITS_PER_EM)); + + _cairo_output_stream_printf (surface->output, + " ]]\n" + ">>\n" + "endobj\n"); + + _cairo_pdf_surface_update_object (surface, subset_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /Type0\n" + " /BaseFont /%s+%s\n" + " /Encoding /Identity-H\n" + " /DescendantFonts [ %d 0 R]\n", + subset_resource.id, + tag, + subset->ps_name, + cidfont_dict.id); + + if (to_unicode_stream.id != 0) + _cairo_output_stream_printf (surface->output, + " /ToUnicode %d 0 R\n", + to_unicode_stream.id); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + } + + font.font_id = font_subset->font_id; + font.subset_id = font_subset->subset_id; + font.subset_resource = subset_resource; + status = _cairo_array_append (&surface->fonts, &font); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_cff_font_subset (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset) +{ + cairo_int_status_t status; + cairo_cff_subset_t subset; + char name[64]; + + snprintf (name, sizeof name, "CairoFont-%d-%d", + font_subset->font_id, font_subset->subset_id); + status = _cairo_cff_subset_init (&subset, name, font_subset); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_emit_cff_font (surface, font_subset, &subset); + + _cairo_cff_subset_fini (&subset); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_cff_fallback_font (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset) +{ + cairo_int_status_t status; + cairo_cff_subset_t subset; + char name[64]; + + /* CFF fallback subsetting does not work with 8-bit glyphs unless + * they are a latin subset */ + if (!font_subset->is_composite && !font_subset->is_latin) + return CAIRO_INT_STATUS_UNSUPPORTED; + + snprintf (name, sizeof name, "CairoFont-%d-%d", + font_subset->font_id, font_subset->subset_id); + status = _cairo_cff_fallback_init (&subset, name, font_subset); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_emit_cff_font (surface, font_subset, &subset); + + _cairo_cff_fallback_fini (&subset); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_type1_font (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset, + cairo_type1_subset_t *subset) +{ + cairo_pdf_resource_t stream, descriptor, subset_resource, to_unicode_stream; + cairo_pdf_font_t font; + cairo_int_status_t status; + unsigned long length; + unsigned int i, last_glyph; + char tag[10]; + + _create_font_subset_tag (font_subset, subset->base_font, tag); + + subset_resource = _cairo_pdf_surface_get_font_resource (surface, + font_subset->font_id, + font_subset->subset_id); + if (subset_resource.id == 0) + return CAIRO_STATUS_SUCCESS; + + length = subset->header_length + subset->data_length + subset->trailer_length; + status = _cairo_pdf_surface_open_stream (surface, + NULL, + TRUE, + " /Length1 %lu\n" + " /Length2 %lu\n" + " /Length3 %lu\n", + subset->header_length, + subset->data_length, + subset->trailer_length); + if (unlikely (status)) + return status; + + stream = surface->pdf_stream.self; + _cairo_output_stream_write (surface->output, subset->data, length); + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_emit_to_unicode_stream (surface, + font_subset, + &to_unicode_stream); + if (_cairo_int_status_is_error (status)) + return status; + + last_glyph = font_subset->num_glyphs - 1; + if (font_subset->is_latin) { + /* find last glyph used */ + for (i = 255; i >= 32; i--) + if (font_subset->latin_to_subset_glyph_index[i] > 0) + break; + + last_glyph = i; + } + + descriptor = _cairo_pdf_surface_new_object (surface); + if (descriptor.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /FontDescriptor\n" + " /FontName /%s+%s\n" + " /Flags 4\n" + " /FontBBox [ %ld %ld %ld %ld ]\n" + " /ItalicAngle 0\n" + " /Ascent %ld\n" + " /Descent %ld\n" + " /CapHeight %ld\n" + " /StemV 80\n" + " /StemH 80\n" + " /FontFile %u 0 R\n" + ">>\n" + "endobj\n", + descriptor.id, + tag, + subset->base_font, + (long)(subset->x_min*PDF_UNITS_PER_EM), + (long)(subset->y_min*PDF_UNITS_PER_EM), + (long)(subset->x_max*PDF_UNITS_PER_EM), + (long)(subset->y_max*PDF_UNITS_PER_EM), + (long)(subset->ascent*PDF_UNITS_PER_EM), + (long)(subset->descent*PDF_UNITS_PER_EM), + (long)(subset->y_max*PDF_UNITS_PER_EM), + stream.id); + + _cairo_pdf_surface_update_object (surface, subset_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /Type1\n" + " /BaseFont /%s+%s\n" + " /FirstChar %d\n" + " /LastChar %d\n" + " /FontDescriptor %d 0 R\n", + subset_resource.id, + tag, + subset->base_font, + font_subset->is_latin ? 32 : 0, + last_glyph, + descriptor.id); + + if (font_subset->is_latin) + _cairo_output_stream_printf (surface->output, " /Encoding /WinAnsiEncoding\n"); + + _cairo_output_stream_printf (surface->output, " /Widths ["); + if (font_subset->is_latin) { + for (i = 32; i < last_glyph + 1; i++) { + int glyph = font_subset->latin_to_subset_glyph_index[i]; + if (glyph > 0) { + _cairo_output_stream_printf (surface->output, + " %ld", + (long)(subset->widths[glyph]*PDF_UNITS_PER_EM)); + } else { + _cairo_output_stream_printf (surface->output, " 0"); + } + } + } else { + for (i = 0; i < font_subset->num_glyphs; i++) + _cairo_output_stream_printf (surface->output, + " %ld", + (long)(subset->widths[i]*PDF_UNITS_PER_EM)); + } + + _cairo_output_stream_printf (surface->output, + " ]\n"); + + if (to_unicode_stream.id != 0) + _cairo_output_stream_printf (surface->output, + " /ToUnicode %d 0 R\n", + to_unicode_stream.id); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + font.font_id = font_subset->font_id; + font.subset_id = font_subset->subset_id; + font.subset_resource = subset_resource; + return _cairo_array_append (&surface->fonts, &font); +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_type1_font_subset (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset) +{ + cairo_int_status_t status; + cairo_type1_subset_t subset; + char name[64]; + + /* 16-bit glyphs not compatible with Type 1 fonts */ + if (font_subset->is_composite && !font_subset->is_latin) + return CAIRO_INT_STATUS_UNSUPPORTED; + + snprintf (name, sizeof name, "CairoFont-%d-%d", + font_subset->font_id, font_subset->subset_id); + status = _cairo_type1_subset_init (&subset, name, font_subset, FALSE); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_emit_type1_font (surface, font_subset, &subset); + + _cairo_type1_subset_fini (&subset); + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_type1_fallback_font (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset) +{ + cairo_int_status_t status; + cairo_type1_subset_t subset; + char name[64]; + + /* 16-bit glyphs not compatible with Type 1 fonts */ + if (font_subset->is_composite && !font_subset->is_latin) + return CAIRO_INT_STATUS_UNSUPPORTED; + + snprintf (name, sizeof name, "CairoFont-%d-%d", + font_subset->font_id, font_subset->subset_id); + status = _cairo_type1_fallback_init_binary (&subset, name, font_subset); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_emit_type1_font (surface, font_subset, &subset); + + _cairo_type1_fallback_fini (&subset); + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_truetype_font_subset (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset) +{ + cairo_pdf_resource_t stream, descriptor, cidfont_dict; + cairo_pdf_resource_t subset_resource, to_unicode_stream; + cairo_int_status_t status; + cairo_pdf_font_t font; + cairo_truetype_subset_t subset; + unsigned int i, last_glyph; + char tag[10]; + + subset_resource = _cairo_pdf_surface_get_font_resource (surface, + font_subset->font_id, + font_subset->subset_id); + if (subset_resource.id == 0) + return CAIRO_STATUS_SUCCESS; + + status = _cairo_truetype_subset_init_pdf (&subset, font_subset); + if (unlikely (status)) + return status; + + _create_font_subset_tag (font_subset, subset.ps_name, tag); + + status = _cairo_pdf_surface_open_stream (surface, + NULL, + TRUE, + " /Length1 %lu\n", + subset.data_length); + if (unlikely (status)) { + _cairo_truetype_subset_fini (&subset); + return status; + } + + stream = surface->pdf_stream.self; + _cairo_output_stream_write (surface->output, + subset.data, subset.data_length); + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) { + _cairo_truetype_subset_fini (&subset); + return status; + } + + status = _cairo_pdf_surface_emit_to_unicode_stream (surface, + font_subset, + &to_unicode_stream); + if (_cairo_int_status_is_error (status)) { + _cairo_truetype_subset_fini (&subset); + return status; + } + + descriptor = _cairo_pdf_surface_new_object (surface); + if (descriptor.id == 0) { + _cairo_truetype_subset_fini (&subset); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /FontDescriptor\n" + " /FontName /%s+%s\n", + descriptor.id, + tag, + subset.ps_name); + + if (subset.family_name_utf8) { + char *pdf_str; + + status = _cairo_utf8_to_pdf_string (subset.family_name_utf8, &pdf_str); + if (likely (status == CAIRO_INT_STATUS_SUCCESS)) { + _cairo_output_stream_printf (surface->output, + " /FontFamily %s\n", + pdf_str); + free (pdf_str); + } else if (status != CAIRO_INT_STATUS_INVALID_STRING) { + return status; + } + } + + _cairo_output_stream_printf (surface->output, + " /Flags %d\n" + " /FontBBox [ %ld %ld %ld %ld ]\n" + " /ItalicAngle 0\n" + " /Ascent %ld\n" + " /Descent %ld\n" + " /CapHeight %ld\n" + " /StemV 80\n" + " /StemH 80\n" + " /FontFile2 %u 0 R\n" + ">>\n" + "endobj\n", + font_subset->is_latin ? 32 : 4, + (long)(subset.x_min*PDF_UNITS_PER_EM), + (long)(subset.y_min*PDF_UNITS_PER_EM), + (long)(subset.x_max*PDF_UNITS_PER_EM), + (long)(subset.y_max*PDF_UNITS_PER_EM), + (long)(subset.ascent*PDF_UNITS_PER_EM), + (long)(subset.descent*PDF_UNITS_PER_EM), + (long)(subset.y_max*PDF_UNITS_PER_EM), + stream.id); + + if (font_subset->is_latin) { + /* find last glyph used */ + for (i = 255; i >= 32; i--) + if (font_subset->latin_to_subset_glyph_index[i] > 0) + break; + + last_glyph = i; + _cairo_pdf_surface_update_object (surface, subset_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /TrueType\n" + " /BaseFont /%s+%s\n" + " /FirstChar 32\n" + " /LastChar %d\n" + " /FontDescriptor %d 0 R\n" + " /Encoding /WinAnsiEncoding\n" + " /Widths [", + subset_resource.id, + tag, + subset.ps_name, + last_glyph, + descriptor.id); + + for (i = 32; i < last_glyph + 1; i++) { + int glyph = font_subset->latin_to_subset_glyph_index[i]; + if (glyph > 0) { + _cairo_output_stream_printf (surface->output, + " %ld", + (long)(subset.widths[glyph]*PDF_UNITS_PER_EM)); + } else { + _cairo_output_stream_printf (surface->output, " 0"); + } + } + + _cairo_output_stream_printf (surface->output, + " ]\n"); + + if (to_unicode_stream.id != 0) + _cairo_output_stream_printf (surface->output, + " /ToUnicode %d 0 R\n", + to_unicode_stream.id); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + } else { + cidfont_dict = _cairo_pdf_surface_new_object (surface); + if (cidfont_dict.id == 0) { + _cairo_truetype_subset_fini (&subset); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /CIDFontType2\n" + " /BaseFont /%s+%s\n" + " /CIDSystemInfo\n" + " << /Registry (Adobe)\n" + " /Ordering (Identity)\n" + " /Supplement 0\n" + " >>\n" + " /FontDescriptor %d 0 R\n" + " /W [0 [", + cidfont_dict.id, + tag, + subset.ps_name, + descriptor.id); + + for (i = 0; i < font_subset->num_glyphs; i++) + _cairo_output_stream_printf (surface->output, + " %ld", + (long)(subset.widths[i]*PDF_UNITS_PER_EM)); + + _cairo_output_stream_printf (surface->output, + " ]]\n" + ">>\n" + "endobj\n"); + + _cairo_pdf_surface_update_object (surface, subset_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /Type0\n" + " /BaseFont /%s+%s\n" + " /Encoding /Identity-H\n" + " /DescendantFonts [ %d 0 R]\n", + subset_resource.id, + tag, + subset.ps_name, + cidfont_dict.id); + + if (to_unicode_stream.id != 0) + _cairo_output_stream_printf (surface->output, + " /ToUnicode %d 0 R\n", + to_unicode_stream.id); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + } + + font.font_id = font_subset->font_id; + font.subset_id = font_subset->subset_id; + font.subset_resource = subset_resource; + status = _cairo_array_append (&surface->fonts, &font); + + _cairo_truetype_subset_fini (&subset); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_emit_imagemask (cairo_image_surface_t *image, + cairo_output_stream_t *stream) +{ + uint8_t *byte, output_byte; + int row, col, num_cols; + + /* The only image type supported by Type 3 fonts are 1-bit image + * masks */ + assert (image->format == CAIRO_FORMAT_A1); + + _cairo_output_stream_printf (stream, + "BI\n" + "/IM true\n" + "/W %d\n" + "/H %d\n" + "/BPC 1\n" + "/D [1 0]\n", + image->width, + image->height); + + _cairo_output_stream_printf (stream, + "ID "); + + num_cols = (image->width + 7) / 8; + for (row = 0; row < image->height; row++) { + byte = image->data + row * image->stride; + for (col = 0; col < num_cols; col++) { + output_byte = CAIRO_BITSWAP8_IF_LITTLE_ENDIAN (*byte); + _cairo_output_stream_write (stream, &output_byte, 1); + byte++; + } + } + + _cairo_output_stream_printf (stream, + "\nEI\n"); + + return _cairo_output_stream_get_status (stream); +} + +static cairo_int_status_t +_cairo_pdf_surface_analyze_user_font_subset (cairo_scaled_font_subset_t *font_subset, + void *closure) +{ + cairo_pdf_surface_t *surface = closure; + cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS; + cairo_int_status_t status2; + unsigned int i; + cairo_surface_t *type3_surface; + cairo_output_stream_t *null_stream; + + null_stream = _cairo_null_stream_create (); + type3_surface = _cairo_type3_glyph_surface_create (font_subset->scaled_font, + null_stream, + _cairo_pdf_emit_imagemask, + surface->font_subsets, + FALSE); + if (unlikely (type3_surface->status)) { + status2 = _cairo_output_stream_destroy (null_stream); + return type3_surface->status; + } + + _cairo_type3_glyph_surface_set_font_subsets_callback (type3_surface, + _cairo_pdf_surface_add_font, + surface); + + for (i = 0; i < font_subset->num_glyphs; i++) { + status = _cairo_type3_glyph_surface_analyze_glyph (type3_surface, + font_subset->glyphs[i]); + if (unlikely (status)) + break; + } + + cairo_surface_destroy (type3_surface); + status2 = _cairo_output_stream_destroy (null_stream); + if (status == CAIRO_INT_STATUS_SUCCESS) + status = status2; + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_type3_font_subset (cairo_pdf_surface_t *surface, + cairo_scaled_font_subset_t *font_subset) +{ + cairo_int_status_t status = CAIRO_STATUS_SUCCESS; + cairo_pdf_resource_t *glyphs, encoding, char_procs, subset_resource, to_unicode_stream; + cairo_pdf_font_t font; + double *widths; + unsigned int i; + cairo_box_t font_bbox = {{0,0},{0,0}}; + cairo_box_t bbox = {{0,0},{0,0}}; + cairo_surface_t *type3_surface; + + if (font_subset->num_glyphs == 0) + return CAIRO_STATUS_SUCCESS; + + subset_resource = _cairo_pdf_surface_get_font_resource (surface, + font_subset->font_id, + font_subset->subset_id); + if (subset_resource.id == 0) + return CAIRO_STATUS_SUCCESS; + + glyphs = _cairo_malloc_ab (font_subset->num_glyphs, sizeof (cairo_pdf_resource_t)); + if (unlikely (glyphs == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + widths = _cairo_malloc_ab (font_subset->num_glyphs, sizeof (double)); + if (unlikely (widths == NULL)) { + free (glyphs); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + _cairo_pdf_group_resources_clear (&surface->resources); + type3_surface = _cairo_type3_glyph_surface_create (font_subset->scaled_font, + NULL, + _cairo_pdf_emit_imagemask, + surface->font_subsets, + FALSE); + if (unlikely (type3_surface->status)) { + free (glyphs); + free (widths); + return type3_surface->status; + } + + _cairo_type3_glyph_surface_set_font_subsets_callback (type3_surface, + _cairo_pdf_surface_add_font, + surface); + + for (i = 0; i < font_subset->num_glyphs; i++) { + status = _cairo_pdf_surface_open_stream (surface, + NULL, + surface->compress_content, + NULL); + if (unlikely (status)) + break; + + glyphs[i] = surface->pdf_stream.self; + status = _cairo_type3_glyph_surface_emit_glyph (type3_surface, + surface->output, + font_subset->glyphs[i], + &bbox, + &widths[i]); + if (unlikely (status)) + break; + + status = _cairo_pdf_surface_close_stream (surface); + if (unlikely (status)) + break; + + if (i == 0) { + font_bbox.p1.x = bbox.p1.x; + font_bbox.p1.y = bbox.p1.y; + font_bbox.p2.x = bbox.p2.x; + font_bbox.p2.y = bbox.p2.y; + } else { + if (bbox.p1.x < font_bbox.p1.x) + font_bbox.p1.x = bbox.p1.x; + if (bbox.p1.y < font_bbox.p1.y) + font_bbox.p1.y = bbox.p1.y; + if (bbox.p2.x > font_bbox.p2.x) + font_bbox.p2.x = bbox.p2.x; + if (bbox.p2.y > font_bbox.p2.y) + font_bbox.p2.y = bbox.p2.y; + } + } + cairo_surface_destroy (type3_surface); + if (unlikely (status)) { + free (glyphs); + free (widths); + return status; + } + + encoding = _cairo_pdf_surface_new_object (surface); + if (encoding.id == 0) { + free (glyphs); + free (widths); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Encoding\n" + " /Differences [0", encoding.id); + for (i = 0; i < font_subset->num_glyphs; i++) + _cairo_output_stream_printf (surface->output, + " /%d", i); + _cairo_output_stream_printf (surface->output, + "]\n" + ">>\n" + "endobj\n"); + + char_procs = _cairo_pdf_surface_new_object (surface); + if (char_procs.id == 0) { + free (glyphs); + free (widths); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<<\n", char_procs.id); + for (i = 0; i < font_subset->num_glyphs; i++) + _cairo_output_stream_printf (surface->output, + " /%d %d 0 R\n", + i, glyphs[i].id); + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + free (glyphs); + + status = _cairo_pdf_surface_emit_to_unicode_stream (surface, + font_subset, + &to_unicode_stream); + if (_cairo_int_status_is_error (status)) { + free (widths); + return status; + } + + _cairo_pdf_surface_update_object (surface, subset_resource); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Font\n" + " /Subtype /Type3\n" + " /FontBBox [%f %f %f %f]\n" + " /FontMatrix [ 1 0 0 -1 0 0 ]\n" + " /Encoding %d 0 R\n" + " /CharProcs %d 0 R\n" + " /FirstChar 0\n" + " /LastChar %d\n", + subset_resource.id, + _cairo_fixed_to_double (font_bbox.p1.x), + _cairo_fixed_to_double (font_bbox.p1.y), + _cairo_fixed_to_double (font_bbox.p2.x), + _cairo_fixed_to_double (font_bbox.p2.y), + encoding.id, + char_procs.id, + font_subset->num_glyphs - 1); + + _cairo_output_stream_printf (surface->output, + " /Widths ["); + for (i = 0; i < font_subset->num_glyphs; i++) + _cairo_output_stream_printf (surface->output, " %f", widths[i]); + _cairo_output_stream_printf (surface->output, + "]\n"); + free (widths); + + _cairo_output_stream_printf (surface->output, + " /Resources\n"); + _cairo_pdf_surface_emit_group_resources (surface, &surface->resources); + + if (to_unicode_stream.id != 0) + _cairo_output_stream_printf (surface->output, + " /ToUnicode %d 0 R\n", + to_unicode_stream.id); + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + font.font_id = font_subset->font_id; + font.subset_id = font_subset->subset_id; + font.subset_resource = subset_resource; + return _cairo_array_append (&surface->fonts, &font); +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_unscaled_font_subset (cairo_scaled_font_subset_t *font_subset, + void *closure) +{ + cairo_pdf_surface_t *surface = closure; + cairo_int_status_t status; + + status = _cairo_pdf_surface_emit_cff_font_subset (surface, font_subset); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + status = _cairo_pdf_surface_emit_truetype_font_subset (surface, font_subset); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + status = _cairo_pdf_surface_emit_type1_font_subset (surface, font_subset); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + status = _cairo_pdf_surface_emit_cff_fallback_font (surface, font_subset); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + status = _cairo_pdf_surface_emit_type1_fallback_font (surface, font_subset); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + ASSERT_NOT_REACHED; + return CAIRO_INT_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_scaled_font_subset (cairo_scaled_font_subset_t *font_subset, + void *closure) +{ + cairo_pdf_surface_t *surface = closure; + cairo_int_status_t status; + + status = _cairo_pdf_surface_emit_type3_font_subset (surface, font_subset); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + + ASSERT_NOT_REACHED; + return CAIRO_INT_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_emit_font_subsets (cairo_pdf_surface_t *surface) +{ + cairo_int_status_t status; + + status = _cairo_scaled_font_subsets_foreach_user (surface->font_subsets, + _cairo_pdf_surface_analyze_user_font_subset, + surface); + if (unlikely (status)) + goto BAIL; + + status = _cairo_scaled_font_subsets_foreach_unscaled (surface->font_subsets, + _cairo_pdf_surface_emit_unscaled_font_subset, + surface); + if (unlikely (status)) + goto BAIL; + + status = _cairo_scaled_font_subsets_foreach_scaled (surface->font_subsets, + _cairo_pdf_surface_emit_scaled_font_subset, + surface); + if (unlikely (status)) + goto BAIL; + + status = _cairo_scaled_font_subsets_foreach_user (surface->font_subsets, + _cairo_pdf_surface_emit_scaled_font_subset, + surface); + +BAIL: + _cairo_scaled_font_subsets_destroy (surface->font_subsets); + surface->font_subsets = NULL; + + return status; +} + +static cairo_pdf_resource_t +_cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface) +{ + cairo_pdf_resource_t catalog; + + catalog = _cairo_pdf_surface_new_object (surface); + if (catalog.id == 0) + return catalog; + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Catalog\n" + " /Pages %d 0 R\n", + catalog.id, + surface->pages_resource.id); + + if (surface->struct_tree_root.id != 0) { + _cairo_output_stream_printf (surface->output, + " /StructTreeRoot %d 0 R\n", + surface->struct_tree_root.id); + if (surface->tagged) { + _cairo_output_stream_printf (surface->output, + " /MarkInfo << /Marked true >>\n"); + } + } + + if (surface->outlines_dict_res.id != 0) { + _cairo_output_stream_printf (surface->output, + " /Outlines %d 0 R\n", + surface->outlines_dict_res.id); + } + + if (surface->page_labels_res.id != 0) { + _cairo_output_stream_printf (surface->output, + " /PageLabels %d 0 R\n", + surface->page_labels_res.id); + } + + if (surface->names_dict_res.id != 0) { + _cairo_output_stream_printf (surface->output, + " /Names %d 0 R\n", + surface->names_dict_res.id); + } + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + return catalog; +} + +static long +_cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface) +{ + cairo_pdf_object_t *object; + int num_objects, i; + long offset; + char buffer[11]; + + num_objects = _cairo_array_num_elements (&surface->objects); + + offset = _cairo_output_stream_get_position (surface->output); + _cairo_output_stream_printf (surface->output, + "xref\n" + "%d %d\n", + 0, num_objects + 1); + + _cairo_output_stream_printf (surface->output, + "0000000000 65535 f \n"); + for (i = 0; i < num_objects; i++) { + object = _cairo_array_index (&surface->objects, i); + snprintf (buffer, sizeof buffer, "%010ld", object->offset); + _cairo_output_stream_printf (surface->output, + "%s 00000 n \n", buffer); + } + + return offset; +} + +static cairo_int_status_t +_cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t *surface, + cairo_pdf_smask_group_t *group) +{ + cairo_pdf_resource_t mask_group; + cairo_pdf_resource_t smask; + cairo_pdf_smask_group_t *smask_group; + cairo_pdf_resource_t pattern_res, gstate_res; + cairo_int_status_t status; + cairo_box_double_t bbox; + + /* Create mask group */ + _get_bbox_from_extents (&group->extents, &bbox); + status = _cairo_pdf_surface_open_group (surface, &bbox, NULL); + if (unlikely (status)) + return status; + + if (_can_paint_pattern (group->mask)) { + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_surface_paint_pattern (surface, + CAIRO_OPERATOR_OVER, + group->mask, + &group->extents, + 1.0, /* alpha */ + FALSE); /* mask */ + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "Q\n"); + } else { + pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, group->mask, + CAIRO_OPERATOR_OVER, + NULL, + &pattern_res, &gstate_res); + if (unlikely (status)) + return status; + + if (gstate_res.id != 0) { + smask_group = _cairo_pdf_surface_create_smask_group (surface, &group->extents); + if (unlikely (smask_group == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + smask_group->width = group->width; + smask_group->height = group->height; + smask_group->operation = PDF_PAINT; + smask_group->source = cairo_pattern_reference (group->mask); + smask_group->source_res = pattern_res; + status = _cairo_pdf_surface_add_smask_group (surface, smask_group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (smask_group); + return status; + } + + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_add_xobject (surface, smask_group->group_res); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + gstate_res.id, + smask_group->group_res.id); + } else { + status = _cairo_pdf_surface_select_pattern (surface, group->mask, pattern_res, FALSE); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "%f %f %f %f re f\n", + bbox.p1.x, + bbox.p1.y, + bbox.p2.x - bbox.p1.x, + bbox.p2.y - bbox.p1.y); + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + return status; + } + } + + status = _cairo_pdf_surface_close_group (surface, &mask_group); + if (unlikely (status)) + return status; + + /* Create source group */ + status = _cairo_pdf_surface_open_group (surface, &bbox, &group->source_res); + if (unlikely (status)) + return status; + + if (_can_paint_pattern (group->source)) { + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_surface_paint_pattern (surface, + CAIRO_OPERATOR_OVER, + group->source, + &group->extents, + 1.0, /* alpha */ + FALSE); /* mask */ + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "Q\n"); + } else { + pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, group->source, + CAIRO_OPERATOR_OVER, + NULL, + &pattern_res, &gstate_res); + if (unlikely (status)) + return status; + + if (gstate_res.id != 0) { + smask_group = _cairo_pdf_surface_create_smask_group (surface, &group->extents); + if (unlikely (smask_group == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + smask_group->operation = PDF_PAINT; + smask_group->source = cairo_pattern_reference (group->source); + smask_group->source_res = pattern_res; + status = _cairo_pdf_surface_add_smask_group (surface, smask_group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (smask_group); + return status; + } + + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_add_xobject (surface, smask_group->group_res); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + gstate_res.id, + smask_group->group_res.id); + } else { + status = _cairo_pdf_surface_select_pattern (surface, group->source, pattern_res, FALSE); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "%f %f %f %f re f\n", + bbox.p1.x, + bbox.p1.y, + bbox.p2.x - bbox.p1.x, + bbox.p2.y - bbox.p1.y); + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + return status; + } + } + + status = _cairo_pdf_surface_close_group (surface, NULL); + if (unlikely (status)) + return status; + + /* Create an smask based on the alpha component of mask_group */ + smask = _cairo_pdf_surface_new_object (surface); + if (smask.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Mask\n" + " /S /Alpha\n" + " /G %d 0 R\n" + ">>\n" + "endobj\n", + smask.id, + mask_group.id); + + /* Create a GState that uses the smask */ + _cairo_pdf_surface_update_object (surface, group->group_res); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /ExtGState\n" + " /SMask %d 0 R\n" + " /ca 1\n" + " /CA 1\n" + " /AIS false\n" + ">>\n" + "endobj\n", + group->group_res.id, + smask.id); + + return _cairo_output_stream_get_status (surface->output); +} + +static cairo_int_status_t +_cairo_pdf_surface_write_smask_group (cairo_pdf_surface_t *surface, + cairo_pdf_smask_group_t *group) +{ + double old_width, old_height; + cairo_bool_t old_in_xobject; + cairo_int_status_t status; + cairo_box_double_t bbox; + cairo_rectangle_int_t old_surface_extents; + + old_width = surface->width; + old_height = surface->height; + old_surface_extents = surface->surface_extents; + old_in_xobject = surface->in_xobject; + surface->in_xobject = TRUE; + _cairo_pdf_surface_set_size_internal (surface, + group->width, + group->height); + _cairo_pdf_operators_reset (&surface->pdf_operators); + /* _mask is a special case that requires two groups - source + * and mask as well as a smask and gstate dictionary */ + if (group->operation == PDF_MASK) { + status = _cairo_pdf_surface_write_mask_group (surface, group); + goto RESTORE_SIZE; + } + + _get_bbox_from_extents (&group->extents, &bbox); + status = _cairo_pdf_surface_open_group (surface, &bbox, &group->group_res); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_select_pattern (surface, + group->source, + group->source_res, + group->operation == PDF_STROKE); + if (unlikely (status)) + return status; + + switch (group->operation) { + case PDF_PAINT: + _cairo_output_stream_printf (surface->output, + "0 0 %f %f re f\n", + surface->width, surface->height); + break; + case PDF_MASK: + ASSERT_NOT_REACHED; + break; + case PDF_FILL: + status = _cairo_pdf_operators_fill (&surface->pdf_operators, + &group->path, + group->fill_rule); + break; + case PDF_STROKE: + status = _cairo_pdf_operators_stroke (&surface->pdf_operators, + &group->path, + &group->style, + &group->ctm, + &group->ctm_inverse); + break; + case PDF_SHOW_GLYPHS: + status = _cairo_pdf_operators_show_text_glyphs (&surface->pdf_operators, + group->utf8, group->utf8_len, + group->glyphs, group->num_glyphs, + group->clusters, group->num_clusters, + group->cluster_flags, + group->scaled_font); + break; + } + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_close_group (surface, NULL); + +RESTORE_SIZE: + surface->in_xobject = old_in_xobject; + _cairo_pdf_surface_set_size_internal (surface, + old_width, + old_height); + surface->surface_extents = old_surface_extents; + _cairo_pdf_operators_reset (&surface->pdf_operators); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface, + cairo_bool_t finish) +{ + cairo_pdf_pattern_t pattern; + cairo_pdf_smask_group_t *group; + cairo_pdf_source_surface_t src_surface; + unsigned int pattern_index, group_index, surface_index, doc_surface_index; + cairo_int_status_t status; + cairo_bool_t is_image; + + /* Writing out PDF_MASK groups will cause additional smask groups + * to be appended to surface->smask_groups. Additional patterns + * may also be appended to surface->patterns. + * + * Writing recording surface patterns will cause additional patterns + * and groups to be appended. + */ + pattern_index = 0; + group_index = 0; + surface_index = 0; + doc_surface_index = 0; + while ((pattern_index < _cairo_array_num_elements (&surface->page_patterns)) || + (group_index < _cairo_array_num_elements (&surface->smask_groups)) || + (surface_index < _cairo_array_num_elements (&surface->page_surfaces)) || + (finish && (doc_surface_index < _cairo_array_num_elements (&surface->doc_surfaces)))) + { + for (; group_index < _cairo_array_num_elements (&surface->smask_groups); group_index++) { + _cairo_array_copy_element (&surface->smask_groups, group_index, &group); + status = _cairo_pdf_surface_write_smask_group (surface, group); + if (unlikely (status)) + return status; + } + + for (; pattern_index < _cairo_array_num_elements (&surface->page_patterns); pattern_index++) { + _cairo_array_copy_element (&surface->page_patterns, pattern_index, &pattern); + status = _cairo_pdf_surface_emit_pattern (surface, &pattern); + if (unlikely (status)) + return status; + } + + for (; surface_index < _cairo_array_num_elements (&surface->page_surfaces); surface_index++) { + _cairo_array_copy_element (&surface->page_surfaces, surface_index, &src_surface); + status = _cairo_pdf_surface_emit_surface (surface, &src_surface, FALSE, &is_image); + if (unlikely (status)) + return status; + } + + if (finish) { + for (; doc_surface_index < _cairo_array_num_elements (&surface->doc_surfaces); doc_surface_index++) { + _cairo_array_copy_element (&surface->doc_surfaces, doc_surface_index, &src_surface); + status = _cairo_pdf_surface_emit_surface (surface, &src_surface, FALSE, &is_image); + if (unlikely (status)) + return status; + } + } + } + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface) +{ + cairo_pdf_resource_t knockout, res, thumbnail_res; + cairo_pdf_resource_t *page; + cairo_int_status_t status; + unsigned int i, len, page_num, num_annots; + + status = _cairo_pdf_interchange_write_page_objects (surface); + if (unlikely (status)) + return status; + + _cairo_pdf_group_resources_clear (&surface->resources); + if (surface->has_fallback_images) { + cairo_rectangle_int_t extents; + cairo_box_double_t bbox; + + extents.x = 0; + extents.y = 0; + extents.width = ceil (surface->width); + extents.height = ceil (surface->height); + _get_bbox_from_extents (&extents, &bbox); + status = _cairo_pdf_surface_open_knockout_group (surface, &bbox); + if (unlikely (status)) + return status; + + len = _cairo_array_num_elements (&surface->knockout_group); + for (i = 0; i < len; i++) { + _cairo_array_copy_element (&surface->knockout_group, i, &res); + _cairo_output_stream_printf (surface->output, + "/x%d Do\n", + res.id); + status = _cairo_pdf_surface_add_xobject (surface, res); + if (unlikely (status)) + return status; + } + _cairo_output_stream_printf (surface->output, + "/x%d Do\n", + surface->content.id); + status = _cairo_pdf_surface_add_xobject (surface, surface->content); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_close_group (surface, &knockout); + if (unlikely (status)) + return status; + + _cairo_pdf_group_resources_clear (&surface->resources); + status = _cairo_pdf_surface_open_content_stream (surface, NULL, NULL, FALSE, FALSE); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, + "/x%d Do\n", + knockout.id); + status = _cairo_pdf_surface_add_xobject (surface, knockout); + if (unlikely (status)) + return status; + + status = _cairo_pdf_surface_close_content_stream (surface); + if (unlikely (status)) + return status; + } + + thumbnail_res.id = 0; + if (surface->thumbnail_image) { + cairo_pdf_source_surface_entry_t entry; + + memset (&entry, 0, sizeof (entry)); + thumbnail_res = _cairo_pdf_surface_new_object (surface); + entry.surface_res = thumbnail_res; + _cairo_pdf_surface_emit_image (surface, surface->thumbnail_image, &entry); + } + + page_num = _cairo_array_num_elements (&surface->pages); + page = _cairo_array_index (&surface->pages, page_num - 1); + _cairo_pdf_surface_update_object (surface, *page); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Page %% %d\n" + " /Parent %d 0 R\n" + " /MediaBox [ 0 0 %f %f ]\n" + " /Contents %d 0 R\n" + " /Group <<\n" + " /Type /Group\n" + " /S /Transparency\n" + " /I true\n" + " /CS /DeviceRGB\n" + " >>\n" + " /Resources %d 0 R\n", + page->id, + page_num, + surface->pages_resource.id, + surface->width, + surface->height, + surface->content.id, + surface->content_resources.id); + + if (surface->page_parent_tree >= 0) { + _cairo_output_stream_printf (surface->output, + " /StructParents %d\n", + surface->page_parent_tree); + } + + num_annots = _cairo_array_num_elements (&surface->page_annots); + if (num_annots > 0) { + _cairo_output_stream_printf (surface->output, + " /Annots [ "); + for (i = 0; i < num_annots; i++) { + _cairo_array_copy_element (&surface->page_annots, i, &res); + _cairo_output_stream_printf (surface->output, + "%d 0 R ", + res.id); + } + _cairo_output_stream_printf (surface->output, "]\n"); + } + + if (thumbnail_res.id) { + _cairo_output_stream_printf (surface->output, + " /Thumb %d 0 R\n", + thumbnail_res.id); + } + + _cairo_output_stream_printf (surface->output, + ">>\n" + "endobj\n"); + + status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface, FALSE); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_int_status_t +_cairo_pdf_surface_analyze_surface_pattern_transparency (cairo_pdf_surface_t *surface, + cairo_surface_pattern_t *pattern) +{ + cairo_image_surface_t *image; + void *image_extra; + cairo_int_status_t status; + cairo_image_transparency_t transparency; + + status = _cairo_surface_acquire_source_image (pattern->surface, + &image, + &image_extra); + if (unlikely (status)) + return status; + + if (image->base.status) + return image->base.status; + + transparency = _cairo_image_analyze_transparency (image); + if (transparency == CAIRO_IMAGE_IS_OPAQUE) + status = CAIRO_STATUS_SUCCESS; + else + status = CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY; + + _cairo_surface_release_source_image (pattern->surface, image, image_extra); + + return status; +} + +static cairo_bool_t +_surface_pattern_supported (cairo_surface_pattern_t *pattern) +{ + cairo_extend_t extend; + + if (pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) + return TRUE; + + if (pattern->surface->backend->acquire_source_image == NULL) + return FALSE; + + /* Does an ALPHA-only source surface even make sense? Maybe, but I + * don't think it's worth the extra code to support it. */ + +/* XXX: Need to write this function here... + if (pattern->surface->content == CAIRO_CONTENT_ALPHA) + return FALSE; +*/ + + extend = cairo_pattern_get_extend (&pattern->base); + switch (extend) { + case CAIRO_EXTEND_NONE: + case CAIRO_EXTEND_REPEAT: + case CAIRO_EXTEND_REFLECT: + /* There's no point returning FALSE for EXTEND_PAD, as the image + * surface does not currently implement it either */ + case CAIRO_EXTEND_PAD: + return TRUE; + } + + ASSERT_NOT_REACHED; + return FALSE; +} + +static cairo_bool_t +_pattern_supported (const cairo_pattern_t *pattern) +{ + switch (pattern->type) { + case CAIRO_PATTERN_TYPE_SOLID: + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: + case CAIRO_PATTERN_TYPE_MESH: + case CAIRO_PATTERN_TYPE_RASTER_SOURCE: + return TRUE; + + case CAIRO_PATTERN_TYPE_SURFACE: + return _surface_pattern_supported ((cairo_surface_pattern_t *) pattern); + + default: + ASSERT_NOT_REACHED; + return FALSE; + } +} + +static cairo_bool_t +_pdf_operator_supported (cairo_operator_t op) +{ + switch (op) { + case CAIRO_OPERATOR_OVER: + case CAIRO_OPERATOR_MULTIPLY: + case CAIRO_OPERATOR_SCREEN: + case CAIRO_OPERATOR_OVERLAY: + case CAIRO_OPERATOR_DARKEN: + case CAIRO_OPERATOR_LIGHTEN: + case CAIRO_OPERATOR_COLOR_DODGE: + case CAIRO_OPERATOR_COLOR_BURN: + case CAIRO_OPERATOR_HARD_LIGHT: + case CAIRO_OPERATOR_SOFT_LIGHT: + case CAIRO_OPERATOR_DIFFERENCE: + case CAIRO_OPERATOR_EXCLUSION: + case CAIRO_OPERATOR_HSL_HUE: + case CAIRO_OPERATOR_HSL_SATURATION: + case CAIRO_OPERATOR_HSL_COLOR: + case CAIRO_OPERATOR_HSL_LUMINOSITY: + return TRUE; + + default: + case CAIRO_OPERATOR_CLEAR: + case CAIRO_OPERATOR_SOURCE: + case CAIRO_OPERATOR_IN: + case CAIRO_OPERATOR_OUT: + case CAIRO_OPERATOR_ATOP: + case CAIRO_OPERATOR_DEST: + case CAIRO_OPERATOR_DEST_OVER: + case CAIRO_OPERATOR_DEST_IN: + case CAIRO_OPERATOR_DEST_OUT: + case CAIRO_OPERATOR_DEST_ATOP: + case CAIRO_OPERATOR_XOR: + case CAIRO_OPERATOR_ADD: + case CAIRO_OPERATOR_SATURATE: + return FALSE; + } +} + +static cairo_int_status_t +_cairo_pdf_surface_analyze_operation (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *pattern, + const cairo_rectangle_int_t *extents) +{ + if (surface->force_fallbacks && + surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (! _pattern_supported (pattern)) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if (_pdf_operator_supported (op)) { + if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) { + cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern; + + if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) { + if (pattern->extend == CAIRO_EXTEND_PAD) { + cairo_box_t box; + cairo_rectangle_int_t rect; + cairo_rectangle_int_t rec_extents; + + /* get the operation extents in pattern space */ + _cairo_box_from_rectangle (&box, extents); + _cairo_matrix_transform_bounding_box_fixed (&pattern->matrix, &box, NULL); + _cairo_box_round_to_rectangle (&box, &rect); + + /* Check if surface needs padding to fill extents */ + if (_cairo_surface_get_extents (surface_pattern->surface, &rec_extents)) { + if (_cairo_fixed_integer_ceil(box.p1.x) < rec_extents.x || + _cairo_fixed_integer_ceil(box.p1.y) < rec_extents.y || + _cairo_fixed_integer_floor(box.p2.y) > rec_extents.x + rec_extents.width || + _cairo_fixed_integer_floor(box.p2.y) > rec_extents.y + rec_extents.height) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + } + } + return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN; + } + } + + return CAIRO_STATUS_SUCCESS; + } + + + /* The SOURCE operator is supported if the pattern is opaque or if + * there is nothing painted underneath. */ + if (op == CAIRO_OPERATOR_SOURCE) { + if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) { + cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern; + + if (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) { + if (_cairo_pattern_is_opaque (pattern, extents)) { + return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN; + } else { + /* FIXME: The analysis surface does not yet have + * the capability to analyze a non opaque recording + * surface and mark it supported if there is + * nothing underneath. For now recording surfaces of + * type CONTENT_COLOR_ALPHA painted with + * OPERATOR_SOURCE will result in a fallback + * image. */ + + return CAIRO_INT_STATUS_UNSUPPORTED; + } + } else { + return _cairo_pdf_surface_analyze_surface_pattern_transparency (surface, + surface_pattern); + } + } + + if (_cairo_pattern_is_opaque (pattern, extents)) + return CAIRO_STATUS_SUCCESS; + else + return CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY; + } + + return CAIRO_INT_STATUS_UNSUPPORTED; +} + +static cairo_bool_t +_cairo_pdf_surface_operation_supported (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *pattern, + const cairo_rectangle_int_t *extents) +{ + return _cairo_pdf_surface_analyze_operation (surface, op, pattern, extents) != CAIRO_INT_STATUS_UNSUPPORTED; +} + +static cairo_int_status_t +_cairo_pdf_surface_start_fallback (cairo_pdf_surface_t *surface) +{ + cairo_box_double_t bbox; + cairo_int_status_t status; + + status = _cairo_pdf_surface_close_content_stream (surface); + if (unlikely (status)) + return status; + + status = _cairo_array_append (&surface->knockout_group, &surface->content); + if (unlikely (status)) + return status; + + _cairo_pdf_group_resources_clear (&surface->resources); + bbox.p1.x = 0; + bbox.p1.y = 0; + bbox.p2.x = surface->width; + bbox.p2.y = surface->height; + status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE); + if (unlikely (status)) + return status; + + return _cairo_pdf_interchange_begin_page_content (surface); +} + +/* If source is an opaque image and mask is an image and both images + * have the same bounding box we can emit them as a image/smask pair. + */ +static cairo_int_status_t +_cairo_pdf_surface_emit_combined_smask (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_pattern_t *mask, + const cairo_rectangle_int_t *extents) +{ + cairo_int_status_t status; + cairo_image_surface_t *image; + void *image_extra; + cairo_image_transparency_t transparency; + int src_width, src_height; + int mask_width, mask_height; + double src_x_offset, src_y_offset; + double mask_x_offset, mask_y_offset; + double src_x1, src_y1, src_x2, src_y2; + double mask_x1, mask_y1, mask_x2, mask_y2; + cairo_matrix_t p2u; + double src_radius, mask_radius, e; + cairo_bool_t need_smask; + cairo_pdf_source_surface_entry_t *pdf_source; + + /* Check that source and mask are images */ + + if (!((source->type == CAIRO_PATTERN_TYPE_SURFACE || source->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) && + (mask->type == CAIRO_PATTERN_TYPE_SURFACE || mask->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE))) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if (source->type == CAIRO_PATTERN_TYPE_SURFACE && + ((cairo_surface_pattern_t *) source)->surface->type == CAIRO_SURFACE_TYPE_RECORDING) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (mask->type == CAIRO_PATTERN_TYPE_SURFACE && + ((cairo_surface_pattern_t *) mask)->surface->type == CAIRO_SURFACE_TYPE_RECORDING) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (source->extend != CAIRO_EXTEND_NONE || mask->extend != CAIRO_EXTEND_NONE) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* Check that source is opaque and get image sizes */ + + status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, source, + &image, &image_extra); + if (unlikely (status)) + return status; + + if (image->base.status) + return image->base.status; + + src_width = image->width; + src_height = image->height; + if (source->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) { + cairo_surface_get_device_offset (&image->base, &src_x_offset, &src_y_offset); + } else { + src_x_offset = 0; + src_y_offset = 0; + } + + transparency = _cairo_image_analyze_transparency (image); + _cairo_pdf_surface_release_source_image_from_pattern (surface, source, image, image_extra); + + if (transparency != CAIRO_IMAGE_IS_OPAQUE) + return CAIRO_INT_STATUS_UNSUPPORTED; + + status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, mask, + &image, &image_extra); + if (unlikely (status)) + return status; + + if (image->base.status) + return image->base.status; + + mask_width = image->width; + mask_height = image->height; + if (mask->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) { + cairo_surface_get_device_offset (&image->base, &mask_x_offset, &mask_y_offset); + } else { + mask_x_offset = 0; + mask_y_offset = 0; + } + + transparency = _cairo_image_analyze_transparency (image); + need_smask = transparency != CAIRO_IMAGE_IS_OPAQUE; + + _cairo_pdf_surface_release_source_image_from_pattern (surface, mask, image, image_extra); + + /* Check that both images have the same extents with a tolerance + * of half the smallest source pixel. */ + + p2u = source->matrix; + status = cairo_matrix_invert (&p2u); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + src_x1 = 0; + src_y1 = 0; + src_x2 = src_width; + src_y2 = src_height; + cairo_matrix_transform_point (&p2u, &src_x1, &src_y1); + cairo_matrix_transform_point (&p2u, &src_x2, &src_y2); + src_radius = _cairo_matrix_transformed_circle_major_axis (&p2u, 0.5); + + p2u = mask->matrix; + status = cairo_matrix_invert (&p2u); + /* cairo_pattern_set_matrix ensures the matrix is invertible */ + assert (status == CAIRO_INT_STATUS_SUCCESS); + mask_x1 = 0; + mask_y1 = 0; + mask_x2 = mask_width; + mask_y2 = mask_height; + cairo_matrix_transform_point (&p2u, &mask_x1, &mask_y1); + cairo_matrix_transform_point (&p2u, &mask_x2, &mask_y2); + mask_radius = _cairo_matrix_transformed_circle_major_axis (&p2u, 0.5); + + if (src_radius < mask_radius) + e = src_radius; + else + e = mask_radius; + + if (fabs(src_x1 - mask_x1) > e || + fabs(src_x2 - mask_x2) > e || + fabs(src_y1 - mask_y1) > e || + fabs(src_y2 - mask_y2) > e) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* Check both images have same device offset */ + if (fabs(src_x_offset - mask_x_offset) > e || + fabs(src_y_offset - mask_y_offset) > e) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if (need_smask) { + status = _cairo_pdf_surface_add_source_surface (surface, + NULL, + mask, + op, + source->filter, + FALSE, /* stencil mask */ + TRUE, /* smask */ + FALSE, /* need_transp_group */ + extents, + NULL, /* smask_res */ + &pdf_source, + NULL, + NULL, + NULL); + if (unlikely (status)) + return status; + } + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_surface_paint_surface_pattern (surface, op, source, extents, + 1.0, /* alpha */ + need_smask ? &pdf_source->surface_res : NULL, + FALSE); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "Q\n"); + + status = _cairo_output_stream_get_status (surface->output); + + + return status; +} + +/* A PDF stencil mask is an A1 mask used with the current color */ +static cairo_int_status_t +_cairo_pdf_surface_emit_stencil_mask (cairo_pdf_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_pattern_t *mask, + const cairo_rectangle_int_t *extents) +{ + cairo_int_status_t status; + cairo_image_surface_t *image; + void *image_extra; + cairo_image_transparency_t transparency; + cairo_pdf_resource_t pattern_res = {0}; + + if (! (source->type == CAIRO_PATTERN_TYPE_SOLID && + (mask->type == CAIRO_PATTERN_TYPE_SURFACE || mask->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE))) + return CAIRO_INT_STATUS_UNSUPPORTED; + + if (mask->type == CAIRO_PATTERN_TYPE_SURFACE && + ((cairo_surface_pattern_t *) mask)->surface->type == CAIRO_SURFACE_TYPE_RECORDING) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, mask, + &image, &image_extra); + if (unlikely (status)) + return status; + + if (image->base.status) + return image->base.status; + + transparency = _cairo_image_analyze_transparency (image); + if (transparency != CAIRO_IMAGE_IS_OPAQUE && + transparency != CAIRO_IMAGE_HAS_BILEVEL_ALPHA) + { + status = CAIRO_INT_STATUS_UNSUPPORTED; + goto cleanup; + } + + status = _cairo_pdf_surface_select_pattern (surface, source, + pattern_res, FALSE); + if (unlikely (status)) + return status; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_surface_paint_surface_pattern (surface, op, mask, extents, 1.0, NULL, TRUE); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->output, "Q\n"); + + status = _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_pdf_surface_release_source_image_from_pattern (surface, mask, image, image_extra); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_set_clip (cairo_pdf_surface_t *surface, + cairo_composite_rectangles_t *composite) +{ + cairo_clip_t *clip = composite->clip; + + if (_cairo_composite_rectangles_can_reduce_clip (composite, clip)) + clip = NULL; + + if (clip == NULL) { + if (_cairo_composite_rectangles_can_reduce_clip (composite, + surface->clipper.clip)) + return CAIRO_STATUS_SUCCESS; + } + + return _cairo_surface_clipper_set_clip (&surface->clipper, clip); +} + +static cairo_int_status_t +_cairo_pdf_surface_paint (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_clip_t *clip) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_pdf_smask_group_t *group; + cairo_pdf_resource_t pattern_res, gstate_res; + cairo_composite_rectangles_t extents; + cairo_int_status_t status; + + status = _cairo_composite_rectangles_init_for_paint (&extents, + &surface->base, + op, source, clip); + if (unlikely (status)) + return status; + + status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded); + if (unlikely (status)) + return status; + + if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { + status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded); + goto cleanup; + } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_FALLBACK) { + status = _cairo_pdf_surface_start_fallback (surface); + if (unlikely (status)) + goto cleanup; + } + + assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded)); + + status = _cairo_pdf_surface_set_clip (surface, &extents); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_operator (surface, op); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + + if (_can_paint_pattern (source)) { + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_surface_paint_pattern (surface, + op, + source, + &extents.bounded, + 1.0, /* alpha */ + FALSE); /* mask */ + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, "Q\n"); + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + } + + pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op, + &extents.bounded, + &pattern_res, &gstate_res); + if (unlikely (status)) + goto cleanup; + + if (gstate_res.id != 0) { + group = _cairo_pdf_surface_create_smask_group (surface, &extents.bounded); + if (unlikely (group == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + group->operation = PDF_PAINT; + status = _cairo_pattern_create_copy (&group->source, source); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + group->source_res = pattern_res; + status = _cairo_pdf_surface_add_smask_group (surface, group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_add_xobject (surface, group->group_res); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + gstate_res.id, + group->group_res.id); + } else { + status = _cairo_pdf_surface_select_pattern (surface, source, + pattern_res, FALSE); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, + "%d %d %d %d re f\n", + surface->surface_extents.x, + surface->surface_extents.y, + surface->surface_extents.width, + surface->surface_extents.height); + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + goto cleanup; + } + + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_composite_rectangles_fini (&extents); + return status; +} + +static cairo_int_status_t +_cairo_pdf_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_pdf_surface_t *surface = abstract_surface; + cairo_pdf_smask_group_t *group; + cairo_composite_rectangles_t extents; + cairo_int_status_t status; + cairo_rectangle_int_t r; + cairo_box_t box; + double alpha; + + status = _cairo_composite_rectangles_init_for_mask (&extents, + &surface->base, + op, source, mask, clip); + if (unlikely (status)) + return status; + + status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded); + if (unlikely (status)) + return status; + + if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { + cairo_int_status_t source_status, mask_status; + + status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded); + if (_cairo_int_status_is_error (status)) + goto cleanup; + source_status = status; + + if (mask->has_component_alpha) { + status = CAIRO_INT_STATUS_UNSUPPORTED; + } else { + status = _cairo_pdf_surface_analyze_operation (surface, op, mask, &extents.bounded); + if (_cairo_int_status_is_error (status)) + goto cleanup; + } + mask_status = status; + + _cairo_composite_rectangles_fini (&extents); + return _cairo_analysis_surface_merge_status (source_status, + mask_status); + } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_FALLBACK) { + status = _cairo_pdf_surface_start_fallback (surface); + if (unlikely (status)) + goto cleanup; + } + + assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded)); + assert (_cairo_pdf_surface_operation_supported (surface, op, mask, &extents.bounded)); + + /* get the accurate extents */ + status = _cairo_pattern_get_ink_extents (source, &r); + if (unlikely (status)) + goto cleanup; + + /* XXX slight impedance mismatch */ + _cairo_box_from_rectangle (&box, &r); + status = _cairo_composite_rectangles_intersect_source_extents (&extents, + &box); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pattern_get_ink_extents (mask, &r); + if (unlikely (status)) + goto cleanup; + + _cairo_box_from_rectangle (&box, &r); + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &box); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_set_clip (surface, &extents); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_operator (surface, op); + if (unlikely (status)) + goto cleanup; + + /* Check if we can combine source and mask into a smask image */ + status = _cairo_pdf_surface_emit_combined_smask (surface, op, source, mask, &extents.bounded); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + goto cleanup; + + /* Check if we can use a stencil mask */ + status = _cairo_pdf_surface_emit_stencil_mask (surface, op, source, mask, &extents.bounded); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + goto cleanup; + + /* Check if we can set ca/CA instead of an smask. We could handle + * other source patterns as well but for now this is the easiest, + * and most common, case to handle. */ + if (_cairo_pattern_is_constant_alpha (mask, &extents.bounded, &alpha) && + _can_paint_pattern (source)) { + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_surface_paint_pattern (surface, + op, + source, + &extents.bounded, + alpha, + FALSE); /* mask */ + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, "Q\n"); + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + } + + group = _cairo_pdf_surface_create_smask_group (surface, &extents.bounded); + if (unlikely (group == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + group->operation = PDF_MASK; + status = _cairo_pattern_create_copy (&group->source, source); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + status = _cairo_pattern_create_copy (&group->mask, mask); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + group->source_res = _cairo_pdf_surface_new_object (surface); + if (group->source_res.id == 0) { + _cairo_pdf_smask_group_destroy (group); + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + status = _cairo_pdf_surface_add_smask_group (surface, group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + status = _cairo_pdf_surface_add_smask (surface, group->group_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_add_xobject (surface, group->source_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + group->group_res.id, + group->source_res.id); + + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_composite_rectangles_fini (&extents); + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_stroke (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const cairo_path_fixed_t *path, + const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + const cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_antialias_t antialias, + const cairo_clip_t *clip) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_pdf_smask_group_t *group; + cairo_pdf_resource_t pattern_res, gstate_res; + cairo_composite_rectangles_t extents; + cairo_int_status_t status; + + status = _cairo_composite_rectangles_init_for_stroke (&extents, + &surface->base, + op, source, + path, style, ctm, + clip); + if (unlikely (status)) + return status; + + /* use the more accurate extents */ + if (extents.is_bounded) { + cairo_rectangle_int_t mask; + cairo_box_t box; + + status = _cairo_path_fixed_stroke_extents (path, style, + ctm, ctm_inverse, + tolerance, + &mask); + if (unlikely (status)) + goto cleanup; + + _cairo_box_from_rectangle (&box, &mask); + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &box); + if (unlikely (status)) + goto cleanup; + } + + status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded); + if (unlikely (status)) + goto cleanup; + + if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { + status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded); + goto cleanup; + } + + assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded)); + + status = _cairo_pdf_surface_set_clip (surface, &extents); + if (unlikely (status)) + goto cleanup; + + pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op, + &extents.bounded, + &pattern_res, &gstate_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_operator (surface, op); + if (unlikely (status)) + goto cleanup; + + if (gstate_res.id != 0) { + group = _cairo_pdf_surface_create_smask_group (surface, &extents.bounded); + if (unlikely (group == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + group->operation = PDF_STROKE; + status = _cairo_pattern_create_copy (&group->source, source); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + group->source_res = pattern_res; + status = _cairo_path_fixed_init_copy (&group->path, path); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + group->style = *style; + group->ctm = *ctm; + group->ctm_inverse = *ctm_inverse; + status = _cairo_pdf_surface_add_smask_group (surface, group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_add_xobject (surface, group->group_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + gstate_res.id, + group->group_res.id); + } else { + status = _cairo_pdf_surface_select_pattern (surface, source, pattern_res, TRUE); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_stroke (&surface->pdf_operators, + path, + style, + ctm, + ctm_inverse); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + goto cleanup; + } + + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_composite_rectangles_fini (&extents); + return status; +} + +static cairo_int_status_t +_cairo_pdf_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_pdf_surface_t *surface = abstract_surface; + cairo_int_status_t status; + cairo_pdf_smask_group_t *group; + cairo_pdf_resource_t pattern_res, gstate_res; + cairo_composite_rectangles_t extents; + + status = _cairo_composite_rectangles_init_for_fill (&extents, + &surface->base, + op, source, path, + clip); + if (unlikely (status)) + return status; + + /* use the more accurate extents */ + if (extents.is_bounded) { + cairo_rectangle_int_t mask; + cairo_box_t box; + + _cairo_path_fixed_fill_extents (path, + fill_rule, + tolerance, + &mask); + + _cairo_box_from_rectangle (&box, &mask); + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &box); + if (unlikely (status)) + goto cleanup; + } + + status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded); + if (unlikely (status)) + goto cleanup; + + if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { + status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded); + goto cleanup; + } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_FALLBACK) { + status = _cairo_pdf_surface_start_fallback (surface); + if (unlikely (status)) + goto cleanup; + } + + assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded)); + + status = _cairo_pdf_surface_set_clip (surface, &extents); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_operator (surface, op); + if (unlikely (status)) + goto cleanup; + + if (_can_paint_pattern (source)) { + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, "q\n"); + status = _cairo_pdf_operators_clip (&surface->pdf_operators, + path, + fill_rule); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_paint_pattern (surface, + op, + source, + &extents.bounded, + 1.0, /* alpha */ + FALSE); /* mask */ + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, "Q\n"); + status = _cairo_output_stream_get_status (surface->output); + goto cleanup; + } + + pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op, + &extents.bounded, + &pattern_res, &gstate_res); + if (unlikely (status)) + goto cleanup; + + if (gstate_res.id != 0) { + group = _cairo_pdf_surface_create_smask_group (surface, &extents.bounded); + if (unlikely (group == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + group->operation = PDF_FILL; + status = _cairo_pattern_create_copy (&group->source, source); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + group->source_res = pattern_res; + status = _cairo_path_fixed_init_copy (&group->path, path); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + group->fill_rule = fill_rule; + status = _cairo_pdf_surface_add_smask_group (surface, group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_add_xobject (surface, group->group_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + gstate_res.id, + group->group_res.id); + } else { + status = _cairo_pdf_surface_select_pattern (surface, source, pattern_res, FALSE); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_fill (&surface->pdf_operators, + path, + fill_rule); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + goto cleanup; + } + + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_composite_rectangles_fini (&extents); + return status; +} + +static cairo_int_status_t +_cairo_pdf_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_pdf_surface_t *surface = abstract_surface; + cairo_int_status_t status; + cairo_pdf_resource_t fill_pattern_res, stroke_pattern_res, gstate_res; + cairo_composite_rectangles_t extents; + + /* During analysis we return unsupported and let the _fill and + * _stroke functions that are on the fallback path do the analysis + * for us. During render we may still encounter unsupported + * combinations of fill/stroke patterns. However we can return + * unsupported anytime to let the _fill and _stroke functions take + * over. + */ + if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* PDF rendering of fill-stroke is not the same as cairo when + * either the fill or stroke is not opaque. + */ + if ( !_cairo_pattern_is_opaque (fill_source, NULL) || + !_cairo_pattern_is_opaque (stroke_source, NULL)) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (fill_op != stroke_op) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* Compute the operation extents using the stroke which will naturally + * be larger than the fill extents. + */ + status = _cairo_composite_rectangles_init_for_stroke (&extents, + &surface->base, + stroke_op, stroke_source, + path, stroke_style, stroke_ctm, + clip); + if (unlikely (status)) + return status; + + /* use the more accurate extents */ + if (extents.is_bounded) { + cairo_rectangle_int_t mask; + cairo_box_t box; + + status = _cairo_path_fixed_stroke_extents (path, stroke_style, + stroke_ctm, stroke_ctm_inverse, + stroke_tolerance, + &mask); + if (unlikely (status)) + goto cleanup; + + _cairo_box_from_rectangle (&box, &mask); + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &box); + if (unlikely (status)) + goto cleanup; + } + + status = _cairo_pdf_surface_set_clip (surface, &extents); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_operator (surface, fill_op); + if (unlikely (status)) + goto cleanup; + + /* use the more accurate extents */ + if (extents.is_bounded) { + cairo_rectangle_int_t mask; + cairo_box_t box; + + _cairo_path_fixed_fill_extents (path, + fill_rule, + fill_tolerance, + &mask); + + _cairo_box_from_rectangle (&box, &mask); + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &box); + if (unlikely (status)) + goto cleanup; + } + + status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded); + if (unlikely (status)) + goto cleanup; + + fill_pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, fill_source, + fill_op, + &extents.bounded, + &fill_pattern_res, + &gstate_res); + if (unlikely (status)) + goto cleanup; + + assert (gstate_res.id == 0); + + stroke_pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, + stroke_source, + stroke_op, + &extents.bounded, + &stroke_pattern_res, + &gstate_res); + if (unlikely (status)) + goto cleanup; + + assert (gstate_res.id == 0); + + /* As PDF has separate graphics state for fill and stroke we can + * select both at the same time */ + status = _cairo_pdf_surface_select_pattern (surface, fill_source, + fill_pattern_res, FALSE); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_pattern (surface, stroke_source, + stroke_pattern_res, TRUE); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_fill_stroke (&surface->pdf_operators, + path, + fill_rule, + stroke_style, + stroke_ctm, + stroke_ctm_inverse); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + goto cleanup; + + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_composite_rectangles_fini (&extents); + return status; +} + +static cairo_bool_t +_cairo_pdf_surface_has_show_text_glyphs (void *abstract_surface) +{ + return TRUE; +} + +static cairo_int_status_t +_cairo_pdf_surface_show_text_glyphs (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + const char *utf8, + int utf8_len, + cairo_glyph_t *glyphs, + int num_glyphs, + const cairo_text_cluster_t *clusters, + int num_clusters, + cairo_text_cluster_flags_t cluster_flags, + cairo_scaled_font_t *scaled_font, + const cairo_clip_t *clip) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_pdf_smask_group_t *group; + cairo_pdf_resource_t pattern_res, gstate_res; + cairo_composite_rectangles_t extents; + cairo_bool_t overlap; + cairo_int_status_t status; + + status = _cairo_composite_rectangles_init_for_glyphs (&extents, + &surface->base, + op, source, + scaled_font, + glyphs, num_glyphs, + clip, + &overlap); + if (unlikely (status)) + return status; + + status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded); + if (unlikely (status)) + return status; + + if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) { + status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded); + goto cleanup; + } + + assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded)); + + status = _cairo_pdf_surface_set_clip (surface, &extents); + if (unlikely (status)) + goto cleanup; + + pattern_res.id = 0; + gstate_res.id = 0; + status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op, + &extents.bounded, + &pattern_res, &gstate_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_select_operator (surface, op); + if (unlikely (status)) + goto cleanup; + + if (gstate_res.id != 0) { + group = _cairo_pdf_surface_create_smask_group (surface, &extents.bounded); + if (unlikely (group == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + group->operation = PDF_SHOW_GLYPHS; + status = _cairo_pattern_create_copy (&group->source, source); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + group->source_res = pattern_res; + + if (utf8_len) { + group->utf8 = _cairo_malloc (utf8_len); + if (unlikely (group->utf8 == NULL)) { + _cairo_pdf_smask_group_destroy (group); + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + memcpy (group->utf8, utf8, utf8_len); + } + group->utf8_len = utf8_len; + + if (num_glyphs) { + group->glyphs = _cairo_malloc_ab (num_glyphs, sizeof (cairo_glyph_t)); + if (unlikely (group->glyphs == NULL)) { + _cairo_pdf_smask_group_destroy (group); + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + memcpy (group->glyphs, glyphs, sizeof (cairo_glyph_t) * num_glyphs); + } + group->num_glyphs = num_glyphs; + + if (num_clusters) { + group->clusters = _cairo_malloc_ab (num_clusters, sizeof (cairo_text_cluster_t)); + if (unlikely (group->clusters == NULL)) { + _cairo_pdf_smask_group_destroy (group); + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + memcpy (group->clusters, clusters, sizeof (cairo_text_cluster_t) * num_clusters); + } + group->num_clusters = num_clusters; + + group->scaled_font = cairo_scaled_font_reference (scaled_font); + status = _cairo_pdf_surface_add_smask_group (surface, group); + if (unlikely (status)) { + _cairo_pdf_smask_group_destroy (group); + goto cleanup; + } + + status = _cairo_pdf_surface_add_smask (surface, gstate_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_add_xobject (surface, group->group_res); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + + _cairo_output_stream_printf (surface->output, + "q /s%d gs /x%d Do Q\n", + gstate_res.id, + group->group_res.id); + } else { + status = _cairo_pdf_surface_select_pattern (surface, source, pattern_res, FALSE); + if (unlikely (status)) + goto cleanup; + + /* Each call to show_glyphs() with a transclucent pattern must + * be in a separate text object otherwise overlapping text + * from separate calls to show_glyphs will not composite with + * each other. */ + if (! _cairo_pattern_is_opaque (source, &extents.bounded)) { + status = _cairo_pdf_operators_flush (&surface->pdf_operators); + if (unlikely (status)) + goto cleanup; + } + + status = _cairo_pdf_operators_show_text_glyphs (&surface->pdf_operators, + utf8, utf8_len, + glyphs, num_glyphs, + clusters, num_clusters, + cluster_flags, + scaled_font); + if (unlikely (status)) + goto cleanup; + + status = _cairo_pdf_surface_unselect_pattern (surface); + if (unlikely (status)) + goto cleanup; + } + + _cairo_composite_rectangles_fini (&extents); + return _cairo_output_stream_get_status (surface->output); + +cleanup: + _cairo_composite_rectangles_fini (&extents); + return status; +} + +static const char ** +_cairo_pdf_surface_get_supported_mime_types (void *abstract_surface) +{ + return _cairo_pdf_supported_mime_types; +} + +static cairo_int_status_t +_cairo_pdf_surface_tag (void *abstract_surface, + cairo_bool_t begin, + const char *tag_name, + const char *attributes, + const cairo_pattern_t *source, + const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + const cairo_matrix_t *ctm_inverse, + const cairo_clip_t *clip) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_int_status_t status = 0; + + if (begin) + status = _cairo_pdf_interchange_tag_begin (surface, tag_name, attributes); + else + status = _cairo_pdf_interchange_tag_end (surface, tag_name); + + return status; +} + +static cairo_int_status_t +_cairo_pdf_surface_set_paginated_mode (void *abstract_surface, + cairo_paginated_mode_t paginated_mode) +{ + cairo_pdf_surface_t *surface = abstract_surface; + cairo_int_status_t status; + + surface->paginated_mode = paginated_mode; + status = _cairo_pdf_interchange_begin_page_content (surface); + if (unlikely (status)) + return status; + + if (paginated_mode == CAIRO_PAGINATED_MODE_RENDER) { + surface->surface_extents.x = 0; + surface->surface_extents.y = 0; + surface->surface_extents.width = ceil (surface->width); + surface->surface_extents.height = ceil (surface->height); + } + + return CAIRO_INT_STATUS_SUCCESS; +} + +static const cairo_surface_backend_t cairo_pdf_surface_backend = { + CAIRO_SURFACE_TYPE_PDF, + _cairo_pdf_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 */ + + NULL, /* _cairo_pdf_surface_copy_page */ + _cairo_pdf_surface_show_page, + + _cairo_pdf_surface_get_extents, + _cairo_pdf_surface_get_font_options, + + NULL, /* flush */ + NULL, /* mark_dirty_rectangle */ + + /* Here are the drawing functions */ + _cairo_pdf_surface_paint, + _cairo_pdf_surface_mask, + _cairo_pdf_surface_stroke, + _cairo_pdf_surface_fill, + _cairo_pdf_surface_fill_stroke, + NULL, /* show_glyphs */ + _cairo_pdf_surface_has_show_text_glyphs, + _cairo_pdf_surface_show_text_glyphs, + _cairo_pdf_surface_get_supported_mime_types, + _cairo_pdf_surface_tag, +}; + +static const cairo_paginated_surface_backend_t +cairo_pdf_surface_paginated_backend = { + _cairo_pdf_surface_start_page, + _cairo_pdf_surface_set_paginated_mode, + NULL, /* set_bounding_box */ + _cairo_pdf_surface_has_fallback_images, + _cairo_pdf_surface_supports_fine_grained_fallbacks, + _cairo_pdf_surface_requires_thumbnail_image, + _cairo_pdf_surface_set_thumbnail_image, +}; |