blob: 4bc2947964ed993d32c09c8a0c80b449bb59b5be [file] [log] [blame]
/* -*- 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 _BSD_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 <time.h>
#include <zlib.h>
/* Issues:
*
* - We embed an image in the stream each time it's composited. We
* could add generation counters to surfaces and remember the stream
* ID for a particular generation for a particular surface.
*
* - Backend specific meta data.
*/
/*
* 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.
*
* 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 identifer. 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 but shared by all JBIG2 images with the same
* %CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID.
**/
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,
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 cairo_pdf_resource_t
_cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface);
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_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_info (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_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;
static 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;
}
static 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;
cairo_matrix_init (&surface->cairo_to_pdf, 1, 0, 0, -1, 0, height);
_cairo_pdf_operators_set_cairo_to_pdf_matrix (&surface->pdf_operators,
&surface->cairo_to_pdf);
}
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 = 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);
surface->output = output;
surface->width = width;
surface->height = height;
cairo_matrix_init (&surface->cairo_to_pdf, 1, 0, 0, -1, 0, height);
_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->jbig2_global, sizeof (cairo_pdf_jbig2_global_t));
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->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);
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);
}
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);
}
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;
}
group->extents = *extents;
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_jbig2_image_info (cairo_surface_t *source,
cairo_image_info_t *info,
const unsigned char **mime_data,
unsigned long *mime_data_length)
{
cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JBIG2,
mime_data, mime_data_length);
if (*mime_data == NULL)
return CAIRO_INT_STATUS_UNSUPPORTED;
return _cairo_image_info_get_jbig2_info (info, *mime_data, *mime_data_length);
}
static cairo_int_status_t
_get_jpx_image_info (cairo_surface_t *source,
cairo_image_info_t *info,
const unsigned char **mime_data,
unsigned long *mime_data_length)
{
cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JP2,
mime_data, mime_data_length);
if (*mime_data == NULL)
return CAIRO_INT_STATUS_UNSUPPORTED;
return _cairo_image_info_get_jpx_info (info, *mime_data, *mime_data_length);
}
static cairo_int_status_t
_get_jpeg_image_info (cairo_surface_t *source,
cairo_image_info_t *info,
const unsigned char **mime_data,
unsigned long *mime_data_length)
{
cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JPEG,
mime_data, mime_data_length);
if (*mime_data == NULL)
return CAIRO_INT_STATUS_UNSUPPORTED;
return _cairo_image_info_get_jpeg_info (info, *mime_data, *mime_data_length);
}
static cairo_int_status_t
_get_source_surface_size (cairo_surface_t *source,
int *width,
int *height,
cairo_rectangle_int_t *extents)
{
cairo_int_status_t status;
cairo_image_info_t info;
const unsigned char *mime_data;
unsigned long mime_data_length;
if (source->type == CAIRO_SURFACE_TYPE_RECORDING) {
if (source->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) {
cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) source;
*extents = sub->extents;
*width = extents->width;
*height = extents->height;
} else {
cairo_surface_t *free_me = NULL;
cairo_rectangle_int_t surf_extents;
cairo_box_t box;
cairo_bool_t bounded;
if (_cairo_surface_is_snapshot (source))
free_me = source = _cairo_surface_snapshot_get_target (source);
status = _cairo_recording_surface_get_ink_bbox ((cairo_recording_surface_t *)source,
&box, NULL);
if (unlikely (status)) {
cairo_surface_destroy (free_me);
return status;
}
bounded = _cairo_surface_get_extents (source, &surf_extents);
cairo_surface_destroy (free_me);
*width = surf_extents.width;
*height = surf_extents.height;
_cairo_box_round_to_rectangle (&box, extents);
}
return CAIRO_STATUS_SUCCESS;
}
extents->x = 0;
extents->y = 0;
status = _get_jbig2_image_info (source, &info, &mime_data, &mime_data_length);
if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
*width = info.width;
*height = info.height;
extents->width = info.width;
extents->height = info.height;
return status;
}
status = _get_jpx_image_info (source, &info, &mime_data, &mime_data_length);
if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
*width = info.width;
*height = info.height;
extents->width = info.width;
extents->height = info.height;
return status;
}
status = _get_jpeg_image_info (source, &info, &mime_data, &mime_data_length);
if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
*width = info.width;
*height = info.height;
extents->width = info.width;
extents->height = info.height;
return status;
}
if (! _cairo_surface_get_extents (source, extents))
return CAIRO_INT_STATUS_UNSUPPORTED;
*width = extents->width;
*height = extents->height;
return CAIRO_STATUS_SUCCESS;
}
/**
* _cairo_pdf_surface_add_source_surface:
* @surface: the pdf surface
* @source_surface: A #cairo_surface_t to use as the source surface
* @source_pattern: A #cairo_pattern_t of type SURFACE or RASTER_SOURCE to use as the source
* @op: the operator used to composite this source
* @filter: filter type of the source pattern
* @stencil_mask: if true, the surface will be written to the PDF as an /ImageMask
* @smask: if true, only the alpha channel will be written (images only)
* @extents: extents of the operation that is using this source
* @smask_res: if not NULL, the image written will specify this resource as the smask for the image (images only)
* @surface_res: return PDF resource number of the surface
* @width: returns width of surface
* @height: returns height of surface
* @x_offset: x offset of surface
* @t_offset: y offset of surface
* @source_extents: returns extents of source (either ink extents or extents needed to cover @extents)
*
* 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 image. A hash table of all images
* in the PDF files (keyed by CAIRO_MIME_TYPE_UNIQUE_ID or surface
* unique_id) 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,
const cairo_rectangle_int_t *extents,
cairo_pdf_resource_t *smask_res,
cairo_pdf_resource_t *surface_res,
int *width,
int *height,
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_bool_t interpolate;
unsigned char *unique_id = NULL;
unsigned long unique_id_length = 0;
cairo_image_surface_t *image;
void *image_extra;
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_offset = 0;
*y_offset = 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_offset, y_offset);
} else {
cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source_pattern;
source_surface = surface_pattern->surface;
}
}
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) {
*surface_res = surface_entry->surface_res;
*width = surface_entry->width;
*height = surface_entry->height;
*source_extents = surface_entry->extents;
status = CAIRO_STATUS_SUCCESS;
} else {
status = _get_source_surface_size (source_surface,
width,
height,
source_extents);
if (unlikely(status))
goto release_source;
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 release_source;
}
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;
}
}
release_source:
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);
if (status || surface_entry)
return status;
surface_entry = malloc (sizeof (cairo_pdf_source_surface_entry_t));
if (surface_entry == NULL) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
goto fail1;
}
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->unique_id_length = unique_id_length;
surface_entry->unique_id = unique_id;
surface_entry->width = *width;
surface_entry->height = *height;
surface_entry->extents = *source_extents;
if (smask_res)
surface_entry->smask_res = *smask_res;
else
surface_entry->smask_res.id = 0;
_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;
}
status = _cairo_array_append (&surface->page_surfaces, &src_surface);
if (unlikely (status))
goto fail3;
status = _cairo_hash_table_insert (surface->all_surfaces,
&surface_entry->base);
if (unlikely(status))
goto fail3;
*surface_res = surface_entry->surface_res;
return status;
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:
free (unique_id);
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;
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 in PDF coordinates from extents in cairo coordinates */
static void
_get_bbox_from_extents (double surface_height,
const cairo_rectangle_int_t *extents,
cairo_box_double_t *bbox)
{
bbox->p1.x = extents->x;
bbox->p1.y = surface_height - (extents->y + extents->height);
bbox->p2.x = extents->x + extents->width;
bbox->p2.y = surface_height - extents->y;
}
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);
}
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);
}
if (unlikely (status))
return status;
surface->content = surface->pdf_stream.self;
_cairo_output_stream_printf (surface->output, "q\n");
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 info, catalog;
cairo_status_t status, status2;
int size, i;
cairo_pdf_jbig2_global_t *global;
status = surface->base.status;
if (status == CAIRO_STATUS_SUCCESS)
status = _cairo_pdf_surface_emit_font_subsets (surface);
_cairo_pdf_surface_write_pages (surface);
info = _cairo_pdf_surface_write_info (surface);
if (info.id == 0 && status == CAIRO_STATUS_SUCCESS)
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
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,
info.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_surface_clear (surface);
_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_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);
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_truncate (&surface->page_surfaces, 0);
_cairo_surface_clipper_reset (&surface->clipper);
return status;
}
static cairo_int_status_t
_cairo_pdf_surface_start_page (void *abstract_surface)
{
cairo_pdf_surface_t *surface = abstract_surface;
/* 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);
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;
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_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_resource_t *surface_res,
int *width,
int *height,
double *x_offset,
double *y_offset)
{
cairo_image_surface_t *image;
cairo_surface_t *pad_image;
void *image_extra;
cairo_int_status_t status;
int w, h;
cairo_rectangle_int_t extents2;
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,
FALSE,
source->filter,
FALSE,
FALSE,
extents,
NULL,
surface_res,
width,
height,
x_offset,
y_offset,
&extents2);
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 = 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_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;
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,