cairo-pdf-surface.c [plain text]
#define _BSD_SOURCE
#include "cairoint.h"
#include "cairo-pdf.h"
#include "cairo-pdf-surface-private.h"
#include "cairo-pdf-operators-private.h"
#include "cairo-analysis-surface-private.h"
#include "cairo-composite-rectangles-private.h"
#include "cairo-error-private.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-subsurface-private.h"
#include "cairo-type3-glyph-surface-private.h"
#include <time.h>
#include <zlib.h>
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"
};
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_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_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_status_t
_cairo_pdf_surface_close_stream (cairo_pdf_surface_t *surface);
static cairo_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_status_t
_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface);
static cairo_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_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)) {
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,
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));
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;
}
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);
_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) {
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);
status_ignored = _cairo_output_stream_destroy (output);
return _cairo_surface_create_in_error (status);
}
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_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;
}
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;
}
void
cairo_pdf_surface_restrict_to_version (cairo_surface_t *abstract_surface,
cairo_pdf_version_t version)
{
cairo_pdf_surface_t *surface = NULL;
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);
}
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;
}
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];
}
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;
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->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->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->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_status_t
_cairo_pdf_surface_add_alpha (cairo_pdf_surface_t *surface,
double alpha,
int *index)
{
int num_alphas, i;
double other;
cairo_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_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_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_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_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_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) {
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:
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, *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->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)
{
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;
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);
if (group->utf8)
free (group->utf8);
if (group->glyphs)
free (group->glyphs);
if (group->clusters)
free (group->clusters);
if (group->scaled_font)
cairo_scaled_font_destroy (group->scaled_font);
free (group);
}
static cairo_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;
return (a->id == b->id) && (a->interpolate == b->interpolate);
}
static void
_cairo_pdf_source_surface_init_key (cairo_pdf_source_surface_entry_t *key)
{
key->base.hash = key->id;
}
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_status_t
_get_source_surface_size (cairo_surface_t *source,
int *width,
int *height)
{
cairo_status_t status;
cairo_rectangle_int_t extents;
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;
*width = sub->extents.width;
*height = sub->extents.height;
} else {
cairo_recording_surface_t *recording_surface = (cairo_recording_surface_t *) source;
cairo_box_t bbox;
status = _cairo_recording_surface_get_bbox (recording_surface, &bbox, NULL);
if (unlikely (status))
return status;
_cairo_box_round_to_rectangle (&bbox, &extents);
*width = extents.width;
*height = extents.height;
}
return CAIRO_STATUS_SUCCESS;
}
status = _get_jpx_image_info (source, &info, &mime_data, &mime_data_length);
if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
*width = info.width;
*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;
return status;
}
if (! _cairo_surface_get_extents (source, &extents))
return CAIRO_INT_STATUS_UNSUPPORTED;
*width = extents.width;
*height = extents.height;
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
cairo_surface_t *source,
cairo_filter_t filter,
cairo_pdf_resource_t *surface_res,
int *width,
int *height)
{
cairo_pdf_source_surface_t src_surface;
cairo_pdf_source_surface_entry_t surface_key;
cairo_pdf_source_surface_entry_t *surface_entry;
cairo_status_t status;
cairo_bool_t interpolate;
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;
}
surface_key.id = source->unique_id;
surface_key.interpolate = interpolate;
_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;
return CAIRO_STATUS_SUCCESS;
}
surface_entry = malloc (sizeof (cairo_pdf_source_surface_entry_t));
if (surface_entry == NULL)
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
surface_entry->id = surface_key.id;
surface_entry->interpolate = interpolate;
_cairo_pdf_source_surface_init_key (surface_entry);
src_surface.hash_entry = surface_entry;
src_surface.surface = cairo_surface_reference (source);
surface_entry->surface_res = _cairo_pdf_surface_new_object (surface);
if (surface_entry->surface_res.id == 0) {
cairo_surface_destroy (source);
free (surface_entry);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
status = _get_source_surface_size (source, &surface_entry->width,
&surface_entry->height);
status = _cairo_array_append (&surface->page_surfaces, &src_surface);
if (unlikely (status)) {
cairo_surface_destroy (source);
free (surface_entry);
return status;
}
status = _cairo_hash_table_insert (surface->all_surfaces,
&surface_entry->base);
*surface_res = surface_entry->surface_res;
*width = surface_entry->width;
*height = surface_entry->height;
return status;
}
static cairo_bool_t
_gradient_stops_are_opaque (const cairo_gradient_pattern_t *gradient)
{
unsigned int i;
for (i = 0; i < gradient->n_stops; i++)
if (! CAIRO_COLOR_IS_OPAQUE (&gradient->stops[i].color))
return FALSE;
return TRUE;
}
static cairo_status_t
_cairo_pdf_surface_add_pdf_pattern (cairo_pdf_surface_t *surface,
const cairo_pattern_t *pattern,
const cairo_rectangle_int_t *extents,
cairo_pdf_resource_t *pattern_res,
cairo_pdf_resource_t *gstate_res)
{
cairo_pdf_pattern_t pdf_pattern;
cairo_status_t status;
if (pattern->type == CAIRO_PATTERN_TYPE_SOLID) {
pattern_res->id = 0;
gstate_res->id = 0;
return CAIRO_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;
if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR ||
pattern->type == CAIRO_PATTERN_TYPE_RADIAL)
{
cairo_gradient_pattern_t *gradient = (cairo_gradient_pattern_t *) pattern;
if (! _gradient_stops_are_opaque (gradient)) {
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_STATUS_SUCCESS;
}
static cairo_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_status_t
_cairo_pdf_surface_close_stream (cairo_pdf_surface_t *surface)
{
cairo_status_t status;
long length;
if (! surface->pdf_stream.active)
return CAIRO_STATUS_SUCCESS;
status = _cairo_pdf_operators_flush (&surface->pdf_operators);
if (surface->pdf_stream.compressed) {
cairo_status_t status2;
status2 = _cairo_output_stream_destroy (surface->output);
if (likely (status == CAIRO_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_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)
{
_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 [ 0 0 %f %f ]\n"
" /Group <<\n"
" /Type /Group\n"
" /S /Transparency\n"
" /CS /DeviceRGB\n",
surface->width,
surface->height);
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_status_t
_cairo_pdf_surface_open_group (cairo_pdf_surface_t *surface,
cairo_pdf_resource_t *resource)
{
cairo_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;
return status;
}
static cairo_status_t
_cairo_pdf_surface_open_knockout_group (cairo_pdf_surface_t *surface)
{
cairo_status_t status;
status = _cairo_pdf_surface_open_group (surface, NULL);
if (unlikely (status))
return status;
surface->group_stream.is_knockout = TRUE;
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
cairo_pdf_resource_t *group)
{
cairo_status_t status = CAIRO_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);
if (group)
*group = surface->group_stream.resource;
status2 = _cairo_output_stream_destroy (surface->group_stream.mem_stream);
if (status == CAIRO_STATUS_SUCCESS)
status = status2;
surface->group_stream.mem_stream = NULL;
surface->group_stream.stream = NULL;
return status;
}
static cairo_status_t
_cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t *surface,
cairo_pdf_resource_t *resource,
cairo_bool_t is_form)
{
cairo_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) {
status =
_cairo_pdf_surface_open_stream (surface,
resource,
surface->compress_content,
" /Type /XObject\n"
" /Subtype /Form\n"
" /BBox [ 0 0 %f %f ]\n"
" /Group <<\n"
" /Type /Group\n"
" /S /Transparency\n"
" /CS /DeviceRGB\n"
" >>\n"
" /Resources %d 0 R\n",
surface->width,
surface->height,
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_status_t
_cairo_pdf_surface_close_content_stream (cairo_pdf_surface_t *surface)
{
cairo_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);
}
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;
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);
status2 = _cairo_pdf_operators_fini (&surface->pdf_operators);
if (status == CAIRO_STATUS_SUCCESS)
status = status2;
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;
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;
}
_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;
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_status_t status;
cairo_pdf_surface_t *surface = abstract_surface;
surface->has_fallback_images = has_fallbacks;
status = _cairo_pdf_surface_open_content_stream (surface, NULL, 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_status_t
_cairo_pdf_surface_emit_smask (cairo_pdf_surface_t *surface,
cairo_image_surface_t *image,
cairo_pdf_resource_t *stream_ret)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
char *alpha;
unsigned long alpha_size;
uint32_t *pixel32;
uint8_t *pixel8;
int i, x, y;
cairo_bool_t opaque;
uint8_t a;
assert (image->format == CAIRO_FORMAT_ARGB32 ||
image->format == CAIRO_FORMAT_A8 ||
image->format == CAIRO_FORMAT_A1 );
stream_ret->id = 0;
if (image->format == CAIRO_FORMAT_A1) {
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;
}
opaque = TRUE;
i = 0;
for (y = 0; y < image->height; y++) {
if (image->format == CAIRO_FORMAT_ARGB32) {
pixel32 = (uint32_t *) (image->data + y * image->stride);
for (x = 0; x < image->width; x++, pixel32++) {
a = (*pixel32 & 0xff000000) >> 24;
alpha[i++] = a;
if (a != 0xff)
opaque = FALSE;
}
} else if (image->format == CAIRO_FORMAT_A8){
pixel8 = (uint8_t *) (image->data + y * image->stride);
for (x = 0; x < image->width; x++, pixel8++) {
a = *pixel8;
alpha[i++] = a;
if (a != 0xff)
opaque = FALSE;
}
} else {
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;
if (a != 0xff)
opaque = FALSE;
}
}
}
if (opaque)
goto CLEANUP_ALPHA;
status = _cairo_pdf_surface_open_stream (surface,
NULL,
TRUE,
" /Type /XObject\n"
" /Subtype /Image\n"
" /Width %d\n"
" /Height %d\n"
" /ColorSpace /DeviceGray\n"
" /BitsPerComponent %d\n",
image->width, image->height,
image->format == CAIRO_FORMAT_A1 ? 1 : 8);
if (unlikely (status))
goto CLEANUP_ALPHA;
*stream_ret = surface->pdf_stream.self;
_cairo_output_stream_write (surface->output, alpha, alpha_size);
status = _cairo_pdf_surface_close_stream (surface);
CLEANUP_ALPHA:
free (alpha);
CLEANUP:
return status;
}
static cairo_status_t
_cairo_pdf_surface_emit_image (cairo_pdf_surface_t *surface,
cairo_image_surface_t *image,
cairo_pdf_resource_t *image_res,
cairo_filter_t filter)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
char *rgb;
unsigned long rgb_size;
uint32_t *pixel;
int i, x, y;
cairo_pdf_resource_t smask = {0};
cairo_bool_t need_smask;
const char *interpolate = "true";
assert (image->format == CAIRO_FORMAT_RGB24 ||
image->format == CAIRO_FORMAT_ARGB32 ||
image->format == CAIRO_FORMAT_A8 ||
image->format == CAIRO_FORMAT_A1);
rgb_size = image->height * image->width * 3;
rgb = _cairo_malloc_abc (image->width, image->height, 3);
if (unlikely (rgb == 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);
for (x = 0; x < image->width; x++, pixel++) {
if (image->format == CAIRO_FORMAT_ARGB32) {
uint8_t a;
a = (*pixel & 0xff000000) >> 24;
if (a == 0) {
rgb[i++] = 0;
rgb[i++] = 0;
rgb[i++] = 0;
} else {
rgb[i++] = (((*pixel & 0xff0000) >> 16) * 255 + a / 2) / a;
rgb[i++] = (((*pixel & 0x00ff00) >> 8) * 255 + a / 2) / a;
rgb[i++] = (((*pixel & 0x0000ff) >> 0) * 255 + a / 2) / a;
}
} else if (image->format == CAIRO_FORMAT_RGB24) {
rgb[i++] = (*pixel & 0x00ff0000) >> 16;
rgb[i++] = (*pixel & 0x0000ff00) >> 8;
rgb[i++] = (*pixel & 0x000000ff) >> 0;
} else {
rgb[i++] = 0;
rgb[i++] = 0;
rgb[i++] = 0;
}
}
}
need_smask = FALSE;
if (image->format == CAIRO_FORMAT_ARGB32 ||
image->format == CAIRO_FORMAT_A8 ||
image->format == CAIRO_FORMAT_A1) {
status = _cairo_pdf_surface_emit_smask (surface, image, &smask);
if (unlikely (status))
goto CLEANUP_RGB;
if (smask.id)
need_smask = TRUE;
}
switch (filter) {
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;
}
#define IMAGE_DICTIONARY " /Type /XObject\n" \
" /Subtype /Image\n" \
" /Width %d\n" \
" /Height %d\n" \
" /ColorSpace /DeviceRGB\n" \
" /Interpolate %s\n" \
" /BitsPerComponent 8\n"
if (need_smask)
status = _cairo_pdf_surface_open_stream (surface,
image_res,
TRUE,
IMAGE_DICTIONARY
" /SMask %d 0 R\n",
image->width, image->height,
interpolate,
smask.id);
else
status = _cairo_pdf_surface_open_stream (surface,
image_res,
TRUE,
IMAGE_DICTIONARY,
image->width, image->height,
interpolate);
if (unlikely (status))
goto CLEANUP_RGB;
#undef IMAGE_DICTIONARY
_cairo_output_stream_write (surface->output, rgb, rgb_size);
status = _cairo_pdf_surface_close_stream (surface);
CLEANUP_RGB:
free (rgb);
CLEANUP:
return status;
}
static cairo_int_status_t
_cairo_pdf_surface_emit_jpx_image (cairo_pdf_surface_t *surface,
cairo_surface_t *source,
cairo_pdf_resource_t res)
{
cairo_status_t status;
const unsigned char *mime_data;
unsigned long mime_data_length;
cairo_image_info_t info;
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;
status = _cairo_pdf_surface_open_stream (surface,
&res,
FALSE,
" /Type /XObject\n"
" /Subtype /Image\n"
" /Width %d\n"
" /Height %d\n"
" /Filter /JPXDecode\n",
info.width,
info.height);
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_resource_t res)
{
cairo_status_t status;
const unsigned char *mime_data;
unsigned long mime_data_length;
cairo_image_info_t info;
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 (info.num_components != 1 && info.num_components != 3)
return CAIRO_INT_STATUS_UNSUPPORTED;
status = _cairo_pdf_surface_open_stream (surface,
&res,
FALSE,
" /Type /XObject\n"
" /Subtype /Image\n"
" /Width %d\n"
" /Height %d\n"
" /ColorSpace %s\n"
" /BitsPerComponent %d\n"
" /Filter /DCTDecode\n",
info.width,
info.height,
info.num_components == 1 ? "/DeviceGray" : "/DeviceRGB",
info.bits_per_component);
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_status_t
_cairo_pdf_surface_emit_image_surface (cairo_pdf_surface_t *surface,
cairo_surface_t *source,
cairo_pdf_resource_t resource,
cairo_bool_t interpolate)
{
cairo_image_surface_t *image;
void *image_extra;
cairo_status_t status;
status = _cairo_pdf_surface_emit_jpx_image (surface, source, resource);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
status = _cairo_pdf_surface_emit_jpeg_image (surface, source, resource);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
status = _cairo_surface_acquire_source_image (source, &image, &image_extra);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_emit_image (surface, image,
&resource, interpolate);
if (unlikely (status))
goto BAIL;
BAIL:
_cairo_surface_release_source_image (source, image, image_extra);
return status;
}
static cairo_status_t
_cairo_pdf_surface_emit_padded_image_surface (cairo_pdf_surface_t *surface,
cairo_pdf_pattern_t *pdf_pattern,
cairo_pdf_resource_t *resource,
int *width,
int *height,
int *origin_x,
int *origin_y)
{
cairo_image_surface_t *image;
cairo_surface_t *pad_image;
void *image_extra;
cairo_status_t status;
cairo_surface_pattern_t *pattern = (cairo_surface_pattern_t *) pdf_pattern->pattern;
int x = 0;
int y = 0;
cairo_bool_t interpolate;
status = _cairo_surface_acquire_source_image (pattern->surface, &image, &image_extra);
if (unlikely (status))
return status;
pad_image = &image->base;
if (pattern->base.extend == CAIRO_EXTEND_PAD) {
cairo_box_t box;
cairo_rectangle_int_t rect;
cairo_surface_pattern_t pad_pattern;
_cairo_box_from_rectangle (&box, &pdf_pattern->extents);
_cairo_matrix_transform_bounding_box_fixed (&pattern->base.matrix, &box, NULL);
_cairo_box_round_to_rectangle (&box, &rect);
x = -rect.x;
y = -rect.y;
pad_image = _cairo_image_surface_create_with_content (pattern->surface->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, -x, -y);
pad_pattern.base.extend = CAIRO_EXTEND_PAD;
status = _cairo_surface_composite (CAIRO_OPERATOR_SOURCE,
&pad_pattern.base,
NULL,
pad_image,
0, 0,
0, 0,
0, 0,
rect.width,
rect.height,
NULL);
_cairo_pattern_fini (&pad_pattern.base);
if (unlikely (status))
goto BAIL;
}
switch (pdf_pattern->pattern->filter) {
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;
}
*resource = _cairo_pdf_surface_new_object (surface);
if (resource->id == 0) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
goto BAIL;
}
status = _cairo_pdf_surface_emit_image (surface, (cairo_image_surface_t *)pad_image,
resource, interpolate);
if (unlikely (status))
goto BAIL;
*width = ((cairo_image_surface_t *)pad_image)->width;
*height = ((cairo_image_surface_t *)pad_image)->height;
*origin_x = x;
*origin_y = y;
BAIL:
if (pad_image != &image->base)
cairo_surface_destroy (pad_image);
_cairo_surface_release_source_image (pattern->surface, image, image_extra);
return status;
}
static cairo_status_t
_cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t *surface,
cairo_surface_t *recording_surface,
cairo_pdf_resource_t resource)
{
double old_width, old_height;
cairo_paginated_mode_t old_paginated_mode;
cairo_rectangle_int_t recording_extents;
cairo_bool_t is_bounded;
cairo_status_t status;
int alpha = 0;
is_bounded = _cairo_surface_get_extents (recording_surface, &recording_extents);
assert (is_bounded);
old_width = surface->width;
old_height = surface->height;
old_paginated_mode = surface->paginated_mode;
_cairo_pdf_surface_set_size_internal (surface,
recording_extents.width,
recording_extents.height);
surface->paginated_mode = CAIRO_PAGINATED_MODE_RENDER;
_cairo_pdf_group_resources_clear (&surface->resources);
status = _cairo_pdf_surface_open_content_stream (surface, &resource, TRUE);
if (unlikely (status))
return status;
if (cairo_surface_get_content (recording_surface) == CAIRO_CONTENT_COLOR) {
status = _cairo_pdf_surface_add_alpha (surface, 1.0, &alpha);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"q /a%d gs 0 0 0 rg 0 0 %f %f re f Q\n",
alpha,
surface->width,
surface->height);
}
status = _cairo_recording_surface_replay_region (recording_surface,
NULL,
&surface->base,
CAIRO_RECORDING_REGION_NATIVE);
assert (status != CAIRO_INT_STATUS_UNSUPPORTED);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_close_content_stream (surface);
_cairo_pdf_surface_set_size_internal (surface,
old_width,
old_height);
surface->paginated_mode = old_paginated_mode;
return status;
}
static cairo_status_t
_cairo_pdf_surface_emit_recording_subsurface (cairo_pdf_surface_t *surface,
cairo_surface_t *recording_surface,
const cairo_rectangle_int_t *extents,
cairo_pdf_resource_t resource)
{
double old_width, old_height;
cairo_paginated_mode_t old_paginated_mode;
cairo_status_t status;
int alpha = 0;
old_width = surface->width;
old_height = surface->height;
old_paginated_mode = surface->paginated_mode;
_cairo_pdf_surface_set_size_internal (surface,
extents->width,
extents->height);
surface->paginated_mode = CAIRO_PAGINATED_MODE_RENDER;
_cairo_pdf_group_resources_clear (&surface->resources);
status = _cairo_pdf_surface_open_content_stream (surface, &resource, TRUE);
if (unlikely (status))
return status;
if (cairo_surface_get_content (recording_surface) == CAIRO_CONTENT_COLOR) {
status = _cairo_pdf_surface_add_alpha (surface, 1.0, &alpha);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"q /a%d gs 0 0 0 rg 0 0 %f %f re f Q\n",
alpha,
surface->width,
surface->height);
}
status = _cairo_recording_surface_replay_region (recording_surface,
extents,
&surface->base,
CAIRO_RECORDING_REGION_NATIVE);
assert (status != CAIRO_INT_STATUS_UNSUPPORTED);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_close_content_stream (surface);
_cairo_pdf_surface_set_size_internal (surface,
old_width,
old_height);
surface->paginated_mode = old_paginated_mode;
return status;
}
static cairo_status_t
_cairo_pdf_surface_emit_surface (cairo_pdf_surface_t *surface,
cairo_pdf_source_surface_t *src_surface)
{
if (src_surface->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
if (src_surface->surface->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) {
cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) src_surface->surface;
return _cairo_pdf_surface_emit_recording_subsurface (surface,
sub->target,
&sub->extents,
src_surface->hash_entry->surface_res);
} else {
return _cairo_pdf_surface_emit_recording_surface (surface,
src_surface->surface,
src_surface->hash_entry->surface_res);
}
} else {
return _cairo_pdf_surface_emit_image_surface (surface,
src_surface->surface,
src_surface->hash_entry->surface_res,
src_surface->hash_entry->interpolate);
}
}
static cairo_status_t
_cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t *surface,
cairo_pdf_pattern_t *pdf_pattern)
{
cairo_surface_pattern_t *pattern = (cairo_surface_pattern_t *) pdf_pattern->pattern;
cairo_status_t status;
cairo_pdf_resource_t pattern_resource = {0};
cairo_matrix_t cairo_p2d, pdf_p2d;
cairo_extend_t extend = cairo_pattern_get_extend (&pattern->base);
double xstep, ystep;
int pattern_width = 0;
int pattern_height = 0;
int origin_x = 0;
int origin_y = 0;
int bbox_x, bbox_y;
char draw_surface[200];
if (pattern->base.extend == CAIRO_EXTEND_PAD &&
pattern->surface->type != CAIRO_SURFACE_TYPE_RECORDING)
{
status = _cairo_pdf_surface_emit_padded_image_surface (surface,
pdf_pattern,
&pattern_resource,
&pattern_width,
&pattern_height,
&origin_x,
&origin_y);
}
else
{
status = _cairo_pdf_surface_add_source_surface (surface,
pattern->surface,
pdf_pattern->pattern->filter,
&pattern_resource,
&pattern_width,
&pattern_height);
}
if (unlikely (status))
return status;
bbox_x = pattern_width;
bbox_y = pattern_height;
switch (extend) {
case CAIRO_EXTEND_PAD:
case CAIRO_EXTEND_NONE:
{
double x1 = 0.0, y1 = 0.0;
double x2 = surface->width, y2 = surface->height;
_cairo_matrix_transform_bounding_box (&pattern->base.matrix,
&x1, &y1, &x2, &y2,
NULL);
xstep = ystep = ceil ((x2 - x1) + (y2 - y1) +
pattern_width + pattern_height);
}
break;
case CAIRO_EXTEND_REPEAT:
xstep = pattern_width;
ystep = pattern_height;
break;
case CAIRO_EXTEND_REFLECT:
bbox_x = pattern_width*2;
bbox_y = pattern_height*2;
xstep = bbox_x;
ystep = bbox_y;
break;
default:
ASSERT_NOT_REACHED;
xstep = 0;
ystep = 0;
}
cairo_p2d = pattern->base.matrix;
status = cairo_matrix_invert (&cairo_p2d);
assert (status == CAIRO_STATUS_SUCCESS);
cairo_matrix_multiply (&pdf_p2d, &cairo_p2d, &surface->cairo_to_pdf);
cairo_matrix_translate (&pdf_p2d, -origin_x, -origin_y);
cairo_matrix_translate (&pdf_p2d, 0.0, pattern_height);
cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
_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 [0 0 %d %d]\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_x, bbox_y,
xstep, ystep,
pdf_p2d.xx, pdf_p2d.yx,
pdf_p2d.xy, pdf_p2d.yy,
pdf_p2d.x0, pdf_p2d.y0,
pattern_resource.id,
pattern_resource.id);
if (unlikely (status))
return status;
if (pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
snprintf(draw_surface,
sizeof (draw_surface),
"/x%d Do\n",
pattern_resource.id);
} else {
snprintf(draw_surface,
sizeof (draw_surface),
"q %d 0 0 %d 0 0 cm /x%d Do Q",
pattern_width,
pattern_height,
pattern_resource.id);
}
if (extend == CAIRO_EXTEND_REFLECT) {
_cairo_output_stream_printf (surface->output,
"q 0 0 %d %d re W n %s Q\n"
"q -1 0 0 1 %d 0 cm 0 0 %d %d re W n %s Q\n"
"q 1 0 0 -1 0 %d cm 0 0 %d %d re W n %s Q\n"
"q -1 0 0 -1 %d %d cm 0 0 %d %d re W n %s Q\n",
pattern_width, pattern_height,
draw_surface,
pattern_width*2, pattern_width, pattern_height,
draw_surface,
pattern_height*2, pattern_width, pattern_height,
draw_surface,
pattern_width*2, pattern_height*2, pattern_width, pattern_height,
draw_surface);
} 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_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_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_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_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_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_status_t status;
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;
}
}
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_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_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 (n_stops <= 2) {
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 {
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_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_status_t
cairo_pdf_surface_emit_transparency_group (cairo_pdf_surface_t *surface,
cairo_pdf_resource_t gstate_resource,
cairo_pdf_resource_t gradient_mask)
{
cairo_pdf_resource_t smask_resource;
cairo_status_t status;
status = _cairo_pdf_surface_open_stream (surface,
NULL,
surface->compress_content,
" /Type /XObject\n"
" /Subtype /Form\n"
" /FormType 1\n"
" /BBox [ 0 0 %f %f ]\n"
" /Resources\n"
" << /ExtGState\n"
" << /a0 << /ca 1 /CA 1 >>"
" >>\n"
" /Pattern\n"
" << /p%d %d 0 R >>\n"
" >>\n"
" /Group\n"
" << /Type /Group\n"
" /S /Transparency\n"
" /CS /DeviceGray\n"
" >>\n",
surface->width,
surface->height,
gradient_mask.id,
gradient_mask.id);
if (unlikely (status))
return status;
_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);
_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 cairo_status_t
_cairo_pdf_surface_emit_linear_pattern (cairo_pdf_surface_t *surface,
cairo_pdf_pattern_t *pdf_pattern)
{
cairo_linear_pattern_t *pattern = (cairo_linear_pattern_t *) pdf_pattern->pattern;
cairo_pdf_resource_t color_function, alpha_function;
double x1, y1, x2, y2;
double _x1, _y1, _x2, _y2;
cairo_matrix_t pat_to_pdf;
cairo_extend_t extend;
cairo_status_t status;
cairo_gradient_pattern_t *gradient = &pattern->base;
double first_stop, last_stop;
int repeat_begin = 0, repeat_end = 1;
assert (pattern->base.n_stops != 0);
extend = cairo_pattern_get_extend (pdf_pattern->pattern);
pat_to_pdf = pattern->base.base.matrix;
status = cairo_matrix_invert (&pat_to_pdf);
assert (status == CAIRO_STATUS_SUCCESS);
cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &surface->cairo_to_pdf);
first_stop = gradient->stops[0].offset;
last_stop = gradient->stops[gradient->n_stops - 1].offset;
if (pattern->base.base.extend == CAIRO_EXTEND_REPEAT ||
pattern->base.base.extend == CAIRO_EXTEND_REFLECT) {
double dx, dy;
int x_rep = 0, y_rep = 0;
x1 = _cairo_fixed_to_double (pattern->p1.x);
y1 = _cairo_fixed_to_double (pattern->p1.y);
cairo_matrix_transform_point (&pat_to_pdf, &x1, &y1);
x2 = _cairo_fixed_to_double (pattern->p2.x);
y2 = _cairo_fixed_to_double (pattern->p2.y);
cairo_matrix_transform_point (&pat_to_pdf, &x2, &y2);
dx = fabs (x2 - x1);
dy = fabs (y2 - y1);
if (dx > 1e-6)
x_rep = ceil (surface->width/dx);
if (dy > 1e-6)
y_rep = ceil (surface->height/dy);
repeat_end = MAX (x_rep, y_rep);
repeat_begin = -repeat_end;
first_stop = repeat_begin;
last_stop = repeat_end;
}
x1 = _cairo_fixed_to_double (pattern->p1.x);
y1 = _cairo_fixed_to_double (pattern->p1.y);
x2 = _cairo_fixed_to_double (pattern->p2.x);
y2 = _cairo_fixed_to_double (pattern->p2.y);
_x1 = x1 + (x2 - x1)*first_stop;
_y1 = y1 + (y2 - y1)*first_stop;
_x2 = x1 + (x2 - x1)*last_stop;
_y2 = y1 + (y2 - y1)*last_stop;
x1 = _x1;
x2 = _x2;
y1 = _y1;
y2 = _y2;
if ((pattern->base.base.extend == CAIRO_EXTEND_NONE ||
pattern->base.base.extend == CAIRO_EXTEND_PAD) &&
gradient->n_stops == 2) {
first_stop = 0.0;
last_stop = 1.0;
}
status = _cairo_pdf_surface_emit_pattern_stops (surface,
&pattern->base,
&color_function,
&alpha_function);
if (unlikely (status))
return status;
if (pattern->base.base.extend == CAIRO_EXTEND_REPEAT ||
pattern->base.base.extend == CAIRO_EXTEND_REFLECT) {
status = _cairo_pdf_surface_emit_repeating_function (surface,
&pattern->base,
&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->base,
&alpha_function,
repeat_begin,
repeat_end);
if (unlikely (status))
return status;
}
}
_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 [ %f %f %f %f %f %f ]\n"
" /Shading\n"
" << /ShadingType 2\n"
" /ColorSpace /DeviceRGB\n"
" /Coords [ %f %f %f %f ]\n"
" /Domain [ %f %f ]\n"
" /Function %d 0 R\n",
pdf_pattern->pattern_res.id,
pat_to_pdf.xx, pat_to_pdf.yx,
pat_to_pdf.xy, pat_to_pdf.yy,
pat_to_pdf.x0, pat_to_pdf.y0,
x1, y1, x2, y2,
first_stop, last_stop,
color_function.id);
if (extend == CAIRO_EXTEND_PAD) {
_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,
" >>\n"
">>\n"
"endobj\n");
if (alpha_function.id != 0) {
cairo_pdf_resource_t mask_resource;
assert (pdf_pattern->gstate_res.id != 0);
mask_resource = _cairo_pdf_surface_new_object (surface);
if (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 [ %f %f %f %f %f %f ]\n"
" /Shading\n"
" << /ShadingType 2\n"
" /ColorSpace /DeviceGray\n"
" /Coords [ %f %f %f %f ]\n"
" /Domain [ %f %f ]\n"
" /Function %d 0 R\n",
mask_resource.id,
pat_to_pdf.xx, pat_to_pdf.yx,
pat_to_pdf.xy, pat_to_pdf.yy,
pat_to_pdf.x0, pat_to_pdf.y0,
x1, y1, x2, y2,
first_stop, last_stop,
alpha_function.id);
if (extend == CAIRO_EXTEND_PAD) {
_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,
" >>\n"
">>\n"
"endobj\n");
status = _cairo_pdf_surface_add_pattern (surface, mask_resource);
if (unlikely (status))
return status;
status = cairo_pdf_surface_emit_transparency_group (surface,
pdf_pattern->gstate_res,
mask_resource);
if (unlikely (status))
return status;
}
return _cairo_output_stream_get_status (surface->output);
}
static cairo_status_t
_cairo_pdf_surface_emit_radial_pattern (cairo_pdf_surface_t *surface,
cairo_pdf_pattern_t *pdf_pattern)
{
cairo_pdf_resource_t color_function, alpha_function;
double x1, y1, x2, y2, r1, r2;
cairo_matrix_t pat_to_pdf;
cairo_extend_t extend;
cairo_status_t status;
cairo_radial_pattern_t *pattern = (cairo_radial_pattern_t *) pdf_pattern->pattern;
assert (pattern->base.n_stops != 0);
extend = cairo_pattern_get_extend (pdf_pattern->pattern);
status = _cairo_pdf_surface_emit_pattern_stops (surface,
&pattern->base,
&color_function,
&alpha_function);
if (unlikely (status))
return status;
pat_to_pdf = pattern->base.base.matrix;
status = cairo_matrix_invert (&pat_to_pdf);
assert (status == CAIRO_STATUS_SUCCESS);
cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &surface->cairo_to_pdf);
x1 = _cairo_fixed_to_double (pattern->c1.x);
y1 = _cairo_fixed_to_double (pattern->c1.y);
r1 = _cairo_fixed_to_double (pattern->r1);
x2 = _cairo_fixed_to_double (pattern->c2.x);
y2 = _cairo_fixed_to_double (pattern->c2.y);
r2 = _cairo_fixed_to_double (pattern->r2);
_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 [ %f %f %f %f %f %f ]\n"
" /Shading\n"
" << /ShadingType 3\n"
" /ColorSpace /DeviceRGB\n"
" /Coords [ %f %f %f %f %f %f ]\n"
" /Function %d 0 R\n",
pdf_pattern->pattern_res.id,
pat_to_pdf.xx, pat_to_pdf.yx,
pat_to_pdf.xy, pat_to_pdf.yy,
pat_to_pdf.x0, pat_to_pdf.y0,
x1, y1, r1, x2, y2, r2,
color_function.id);
if (extend == CAIRO_EXTEND_PAD) {
_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,
" >>\n"
">>\n"
"endobj\n");
if (alpha_function.id != 0) {
cairo_pdf_resource_t mask_resource;
assert (pdf_pattern->gstate_res.id != 0);
mask_resource = _cairo_pdf_surface_new_object (surface);
if (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 [ %f %f %f %f %f %f ]\n"
" /Shading\n"
" << /ShadingType 3\n"
" /ColorSpace /DeviceGray\n"
" /Coords [ %f %f %f %f %f %f ]\n"
" /Function %d 0 R\n",
mask_resource.id,
pat_to_pdf.xx, pat_to_pdf.yx,
pat_to_pdf.xy, pat_to_pdf.yy,
pat_to_pdf.x0, pat_to_pdf.y0,
x1, y1, r1, x2, y2, r2,
alpha_function.id);
if (extend == CAIRO_EXTEND_PAD) {
_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,
" >>\n"
">>\n"
"endobj\n");
status = cairo_pdf_surface_emit_transparency_group (surface,
pdf_pattern->gstate_res,
mask_resource);
if (unlikely (status))
return status;
}
return _cairo_output_stream_get_status (surface->output);
}
static cairo_status_t
_cairo_pdf_surface_emit_pattern (cairo_pdf_surface_t *surface, cairo_pdf_pattern_t *pdf_pattern)
{
double old_width, old_height;
cairo_status_t status;
old_width = surface->width;
old_height = surface->height;
_cairo_pdf_surface_set_size_internal (surface,
pdf_pattern->width,
pdf_pattern->height);
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:
status = _cairo_pdf_surface_emit_surface_pattern (surface, pdf_pattern);
break;
case CAIRO_PATTERN_TYPE_LINEAR:
status = _cairo_pdf_surface_emit_linear_pattern (surface, pdf_pattern);
break;
case CAIRO_PATTERN_TYPE_RADIAL:
status = _cairo_pdf_surface_emit_radial_pattern (surface, pdf_pattern);
break;
default:
ASSERT_NOT_REACHED;
status = _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH);
break;
}
_cairo_pdf_surface_set_size_internal (surface,
old_width,
old_height);
return status;
}
static cairo_status_t
_cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
cairo_surface_pattern_t *source)
{
cairo_pdf_resource_t surface_res;
int width, height;
cairo_matrix_t cairo_p2d, pdf_p2d;
cairo_status_t status;
int alpha;
status = _cairo_pdf_surface_add_source_surface (surface,
source->surface,
source->base.filter,
&surface_res,
&width,
&height);
if (unlikely (status))
return status;
cairo_p2d = source->base.matrix;
status = cairo_matrix_invert (&cairo_p2d);
assert (status == CAIRO_STATUS_SUCCESS);
pdf_p2d = surface->cairo_to_pdf;
cairo_matrix_multiply (&pdf_p2d, &cairo_p2d, &pdf_p2d);
cairo_matrix_translate (&pdf_p2d, 0.0, height);
cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
if (source->surface->type != CAIRO_SURFACE_TYPE_RECORDING)
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_printf (surface->output,
"%f %f %f %f %f %f cm\n",
pdf_p2d.xx, pdf_p2d.yx,
pdf_p2d.xy, pdf_p2d.yy,
pdf_p2d.x0, pdf_p2d.y0);
}
status = _cairo_pdf_surface_add_alpha (surface, 1.0, &alpha);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"/a%d gs /x%d Do\n",
alpha,
surface_res.id);
return _cairo_pdf_surface_add_xobject (surface, surface_res);
}
static cairo_status_t
_cairo_pdf_surface_select_operator (cairo_pdf_surface_t *surface,
cairo_operator_t op)
{
cairo_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_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_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;
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_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;
rectangle->x = 0;
rectangle->y = 0;
rectangle->width = ceil (surface->width);
rectangle->height = ceil (surface->height);
return TRUE;
}
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);
}
static cairo_pdf_resource_t
_cairo_pdf_surface_write_info (cairo_pdf_surface_t *surface)
{
cairo_pdf_resource_t info;
info = _cairo_pdf_surface_new_object (surface);
if (info.id == 0)
return info;
_cairo_output_stream_printf (surface->output,
"%d 0 obj\n"
"<< /Creator (cairo %s (http://cairographics.org))\n"
" /Producer (cairo %s (http://cairographics.org))\n"
">>\n"
"endobj\n",
info.id,
cairo_version_string (),
cairo_version_string ());
return info;
}
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);
_cairo_output_stream_printf (surface->output,
">>\n"
"endobj\n");
}
static cairo_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_status_t status;
int i;
if (utf8 && *utf8) {
status = _cairo_utf8_to_utf16 (utf8, -1, &utf16, &utf16_len);
if (unlikely (status))
return status;
}
_cairo_output_stream_printf (surface->output, "<");
if (utf16 == NULL || utf16_len == 0) {
_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, ">");
if (utf16)
free (utf16);
return CAIRO_STATUS_SUCCESS;
}
#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;
c = initval;
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_bool_t is_composite,
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 (is_composite) {
_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) {
num_bfchar = font_subset->num_glyphs;
_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 {
num_bfchar = font_subset->num_glyphs - 1;
_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 (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_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;
cairo_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,
" /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, TRUE,
&to_unicode_stream);
if (_cairo_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->font_name) {
_cairo_output_stream_printf (surface->output,
" /FontFamily (%s)\n",
subset->font_name);
}
_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);
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_status_t
_cairo_pdf_surface_emit_cff_font_subset (cairo_pdf_surface_t *surface,
cairo_scaled_font_subset_t *font_subset)
{
cairo_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_status_t
_cairo_pdf_surface_emit_cff_fallback_font (cairo_pdf_surface_t *surface,
cairo_scaled_font_subset_t *font_subset)
{
cairo_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_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_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_status_t status;
unsigned long length;
unsigned int i;
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, FALSE,
&to_unicode_stream);
if (_cairo_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"
" /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 0\n"
" /LastChar %d\n"
" /FontDescriptor %d 0 R\n"
" /Widths [",
subset_resource.id,
tag,
subset->base_font,
font_subset->num_glyphs - 1,
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");
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);
}
#if CAIRO_HAS_FT_FONT
static cairo_status_t
_cairo_pdf_surface_emit_type1_font_subset (cairo_pdf_surface_t *surface,
cairo_scaled_font_subset_t *font_subset)
{
cairo_status_t status;
cairo_type1_subset_t subset;
char name[64];
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;
}
#endif
static cairo_status_t
_cairo_pdf_surface_emit_type1_fallback_font (cairo_pdf_surface_t *surface,
cairo_scaled_font_subset_t *font_subset)
{
cairo_status_t status;
cairo_type1_subset_t subset;
char name[64];
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_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_status_t status;
cairo_pdf_font_t font;
cairo_truetype_subset_t subset;
unsigned int i;
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 (&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, TRUE,
&to_unicode_stream);
if (_cairo_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.font_name) {
_cairo_output_stream_printf (surface->output,
" /FontFamily (%s)\n",
subset.font_name);
}
_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"
" /FontFile2 %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);
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_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;
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_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_status_t status = CAIRO_STATUS_SUCCESS;
cairo_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);
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_STATUS_SUCCESS)
status = status2;
return status;
}
static cairo_status_t
_cairo_pdf_surface_emit_type3_font_subset (cairo_pdf_surface_t *surface,
cairo_scaled_font_subset_t *font_subset)
{
cairo_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);
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, FALSE,
&to_unicode_stream);
if (_cairo_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.p2.y),
_cairo_fixed_to_double (font_bbox.p2.x),
- _cairo_fixed_to_double (font_bbox.p1.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_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_status_t status;
if (font_subset->is_composite) {
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_cff_fallback_font (surface, font_subset);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
} else {
#if CAIRO_HAS_FT_FONT
status = _cairo_pdf_surface_emit_type1_font_subset (surface, font_subset);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
#endif
status = _cairo_pdf_surface_emit_type1_fallback_font (surface, font_subset);
if (status != CAIRO_INT_STATUS_UNSUPPORTED)
return status;
}
ASSERT_NOT_REACHED;
return CAIRO_STATUS_SUCCESS;
}
static cairo_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_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_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_pdf_surface_emit_font_subsets (cairo_pdf_surface_t *surface)
{
cairo_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"
">>\n"
"endobj\n",
catalog.id,
surface->pages_resource.id);
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_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_status_t status;
status = _cairo_pdf_surface_open_group (surface, NULL);
if (unlikely (status))
return status;
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, group->mask, NULL,
&pattern_res, &gstate_res);
if (unlikely (status))
return status;
if (gstate_res.id != 0) {
smask_group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (smask_group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
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,
"0 0 %f %f re f\n",
surface->width, surface->height);
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;
status = _cairo_pdf_surface_open_group (surface, &group->source_res);
if (unlikely (status))
return status;
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, group->source, NULL,
&pattern_res, &gstate_res);
if (unlikely (status))
return status;
if (gstate_res.id != 0) {
smask_group = _cairo_pdf_surface_create_smask_group (surface);
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,
"0 0 %f %f re f\n",
surface->width, surface->height);
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;
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);
_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_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_status_t status;
old_width = surface->width;
old_height = surface->height;
_cairo_pdf_surface_set_size_internal (surface,
group->width,
group->height);
if (group->operation == PDF_MASK) {
status = _cairo_pdf_surface_write_mask_group (surface, group);
goto RESTORE_SIZE;
}
status = _cairo_pdf_surface_open_group (surface, &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:
_cairo_pdf_surface_set_size_internal (surface,
old_width,
old_height);
return status;
}
static cairo_status_t
_cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface)
{
cairo_pdf_pattern_t pattern;
cairo_pdf_smask_group_t *group;
cairo_pdf_source_surface_t src_surface;
int pattern_index, group_index, surface_index;
cairo_status_t status;
pattern_index = 0;
group_index = 0;
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)))
{
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);
if (unlikely (status))
return status;
}
}
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
{
cairo_pdf_resource_t page, knockout, res;
cairo_status_t status;
int i, len;
_cairo_pdf_group_resources_clear (&surface->resources);
if (surface->has_fallback_images) {
status = _cairo_pdf_surface_open_knockout_group (surface);
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, 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;
}
page = _cairo_pdf_surface_new_object (surface);
if (page.id == 0)
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
_cairo_output_stream_printf (surface->output,
"%d 0 obj\n"
"<< /Type /Page\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"
" /CS /DeviceRGB\n"
" >>\n"
" /Resources %d 0 R\n"
">>\n"
"endobj\n",
page.id,
surface->pages_resource.id,
surface->width,
surface->height,
surface->content.id,
surface->content_resources.id);
status = _cairo_array_append (&surface->pages, &page);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface);
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;
extend = cairo_pattern_get_extend (&pattern->base);
switch (extend) {
case CAIRO_EXTEND_NONE:
case CAIRO_EXTEND_REPEAT:
case CAIRO_EXTEND_REFLECT:
case CAIRO_EXTEND_PAD:
return TRUE;
}
ASSERT_NOT_REACHED;
return FALSE;
}
static cairo_bool_t
_gradient_pattern_supported (const cairo_pattern_t *pattern)
{
cairo_extend_t extend;
extend = cairo_pattern_get_extend ((cairo_pattern_t *) pattern);
if (pattern->type == CAIRO_PATTERN_TYPE_RADIAL) {
double x1, y1, x2, y2, r1, r2, d;
cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern;
if (extend == CAIRO_EXTEND_REPEAT ||
extend == CAIRO_EXTEND_REFLECT) {
return FALSE;
}
x1 = _cairo_fixed_to_double (radial->c1.x);
y1 = _cairo_fixed_to_double (radial->c1.y);
r1 = _cairo_fixed_to_double (radial->r1);
x2 = _cairo_fixed_to_double (radial->c2.x);
y2 = _cairo_fixed_to_double (radial->c2.y);
r2 = _cairo_fixed_to_double (radial->r2);
d = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
if (d > fabs(r2 - r1)) {
return FALSE;
}
}
return TRUE;
}
static cairo_bool_t
_pattern_supported (const cairo_pattern_t *pattern)
{
if (pattern->type == CAIRO_PATTERN_TYPE_SOLID)
return TRUE;
if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR ||
pattern->type == CAIRO_PATTERN_TYPE_RADIAL)
return _gradient_pattern_supported (pattern);
if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE)
return _surface_pattern_supported ((cairo_surface_pattern_t *) pattern);
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)
return CAIRO_INT_STATUS_UNSUPPORTED;
else
return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
}
}
return CAIRO_STATUS_SUCCESS;
}
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 {
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_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);
return _cairo_pdf_surface_open_content_stream (surface, NULL, TRUE);
}
static cairo_int_status_t
_cairo_pdf_surface_paint (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
cairo_clip_t *clip)
{
cairo_pdf_surface_t *surface = abstract_surface;
cairo_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_paint (&extents,
surface->width, surface->height,
op, source, clip);
if (unlikely (status)) {
if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
return CAIRO_STATUS_SUCCESS;
return status;
}
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
return _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
} else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_FALLBACK) {
status = _cairo_pdf_surface_start_fallback (surface);
if (unlikely (status))
return status;
}
assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded));
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
status = _cairo_pdf_operators_flush (&surface->pdf_operators);
if (unlikely (status))
return status;
if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
source->extend == CAIRO_EXTEND_NONE)
{
_cairo_output_stream_printf (surface->output, "q\n");
status = _cairo_pdf_surface_paint_surface_pattern (surface,
(cairo_surface_pattern_t *) source);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output, "Q\n");
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,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO))
return CAIRO_STATUS_SUCCESS;
if (unlikely (status))
return status;
if (gstate_res.id != 0) {
group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
group->operation = PDF_PAINT;
status = _cairo_pattern_create_copy (&group->source, source);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
group->source_res = pattern_res;
status = _cairo_pdf_surface_add_smask_group (surface, group);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
status = _cairo_pdf_surface_add_smask (surface, gstate_res);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_add_xobject (surface, 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,
group->group_res.id);
} else {
status = _cairo_pdf_surface_select_pattern (surface, source,
pattern_res, FALSE);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"0 0 %f %f re f\n",
surface->width, surface->height);
status = _cairo_pdf_surface_unselect_pattern (surface);
if (unlikely (status))
return status;
}
return _cairo_output_stream_get_status (surface->output);
}
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,
cairo_clip_t *clip)
{
cairo_pdf_surface_t *surface = abstract_surface;
cairo_pdf_smask_group_t *group;
cairo_status_t status;
cairo_composite_rectangles_t extents;
status = _cairo_composite_rectangles_init_for_mask (&extents,
surface->width, surface->height,
op, source, mask, clip);
if (unlikely (status)) {
if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
return CAIRO_STATUS_SUCCESS;
return status;
}
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
cairo_status_t source_status, mask_status;
source_status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
if (_cairo_status_is_error (source_status))
return source_status;
if (mask->has_component_alpha) {
mask_status = CAIRO_INT_STATUS_UNSUPPORTED;
} else {
mask_status = _cairo_pdf_surface_analyze_operation (surface, op, mask, &extents.bounded);
if (_cairo_status_is_error (mask_status))
return mask_status;
}
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))
return status;
}
assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded));
assert (_cairo_pdf_surface_operation_supported (surface, op, mask, &extents.bounded));
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
if (unlikely (status))
return status;
group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
group->operation = PDF_MASK;
status = _cairo_pattern_create_copy (&group->source, source);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
status = _cairo_pattern_create_copy (&group->mask, mask);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
group->source_res = _cairo_pdf_surface_new_object (surface);
if (group->source_res.id == 0) {
_cairo_pdf_smask_group_destroy (group);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
status = _cairo_pdf_surface_add_smask_group (surface, group);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
status = _cairo_pdf_surface_add_smask (surface, group->group_res);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_add_xobject (surface, group->source_res);
if (unlikely (status))
return status;
status = _cairo_pdf_operators_flush (&surface->pdf_operators);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output,
"q /s%d gs /x%d Do Q\n",
group->group_res.id,
group->source_res.id);
return _cairo_output_stream_get_status (surface->output);
}
static cairo_int_status_t
_cairo_pdf_surface_stroke (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
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,
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_status_t status;
status = _cairo_composite_rectangles_init_for_stroke (&extents,
surface->width,
surface->height,
op, source,
path, style, ctm,
clip);
if (unlikely (status)) {
if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
return CAIRO_STATUS_SUCCESS;
return status;
}
if (extents.is_bounded) {
status = _cairo_path_fixed_stroke_extents (path, style,
ctm, ctm_inverse,
tolerance,
&extents.mask);
if (unlikely (status))
return status;
if (! _cairo_rectangle_intersect (&extents.bounded, &extents.mask))
return CAIRO_STATUS_SUCCESS;
}
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
return _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded));
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
if (unlikely (status))
return status;
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, source,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO))
return CAIRO_STATUS_SUCCESS;
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
if (gstate_res.id != 0) {
group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
group->operation = PDF_STROKE;
status = _cairo_pattern_create_copy (&group->source, source);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
group->source_res = pattern_res;
status = _cairo_path_fixed_init_copy (&group->path, path);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
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);
return status;
}
status = _cairo_pdf_surface_add_smask (surface, gstate_res);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_add_xobject (surface, group->group_res);
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 /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))
return status;
status = _cairo_pdf_operators_stroke (&surface->pdf_operators,
path,
style,
ctm,
ctm_inverse);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_unselect_pattern (surface);
if (unlikely (status))
return status;
}
return _cairo_output_stream_get_status (surface->output);
}
static cairo_int_status_t
_cairo_pdf_surface_fill (void *abstract_surface,
cairo_operator_t op,
const cairo_pattern_t *source,
cairo_path_fixed_t *path,
cairo_fill_rule_t fill_rule,
double tolerance,
cairo_antialias_t antialias,
cairo_clip_t *clip)
{
cairo_pdf_surface_t *surface = abstract_surface;
cairo_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->width,
surface->height,
op, source, path,
clip);
if (unlikely (status)) {
if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
return CAIRO_STATUS_SUCCESS;
return status;
}
if (extents.is_bounded) {
_cairo_path_fixed_fill_extents (path,
fill_rule,
tolerance,
&extents.mask);
if (! _cairo_rectangle_intersect (&extents.bounded, &extents.mask))
return CAIRO_STATUS_SUCCESS;
}
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
return _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
} else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_FALLBACK) {
status = _cairo_pdf_surface_start_fallback (surface);
if (unlikely (status))
return status;
}
assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded));
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
source->extend == CAIRO_EXTEND_NONE)
{
status = _cairo_pdf_operators_flush (&surface->pdf_operators);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output, "q\n");
status = _cairo_pdf_operators_clip (&surface->pdf_operators,
path,
fill_rule);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_paint_surface_pattern (surface,
(cairo_surface_pattern_t *) source);
if (unlikely (status))
return status;
_cairo_output_stream_printf (surface->output, "Q\n");
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,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO))
return CAIRO_STATUS_SUCCESS;
if (unlikely (status))
return status;
if (gstate_res.id != 0) {
group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
group->operation = PDF_FILL;
status = _cairo_pattern_create_copy (&group->source, source);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
group->source_res = pattern_res;
status = _cairo_path_fixed_init_copy (&group->path, path);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
group->fill_rule = fill_rule;
status = _cairo_pdf_surface_add_smask_group (surface, group);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
status = _cairo_pdf_surface_add_smask (surface, gstate_res);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_add_xobject (surface, group->group_res);
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 /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))
return status;
status = _cairo_pdf_operators_fill (&surface->pdf_operators,
path,
fill_rule);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_unselect_pattern (surface);
if (unlikely (status))
return status;
}
return _cairo_output_stream_get_status (surface->output);
}
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,
cairo_path_fixed_t *path,
cairo_operator_t stroke_op,
const cairo_pattern_t *stroke_source,
const cairo_stroke_style_t *stroke_style,
const cairo_matrix_t *stroke_ctm,
const cairo_matrix_t *stroke_ctm_inverse,
double stroke_tolerance,
cairo_antialias_t stroke_antialias,
cairo_clip_t *clip)
{
cairo_pdf_surface_t *surface = abstract_surface;
cairo_status_t status;
cairo_pdf_resource_t fill_pattern_res, stroke_pattern_res, gstate_res;
cairo_rectangle_int_t extents;
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
return CAIRO_INT_STATUS_UNSUPPORTED;
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;
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, fill_op);
if (unlikely (status))
return status;
status = _cairo_surface_fill_extents (&surface->base,
fill_op, fill_source, path, fill_rule,
fill_tolerance, fill_antialias,
clip, &extents);
if (unlikely (status))
return status;
fill_pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, fill_source,
&extents,
&fill_pattern_res,
&gstate_res);
if (unlikely (status))
return status;
assert (gstate_res.id == 0);
status = _cairo_surface_stroke_extents (&surface->base,
stroke_op, stroke_source, path,
stroke_style, stroke_ctm, stroke_ctm_inverse,
stroke_tolerance, stroke_antialias,
clip, &extents);
if (unlikely (status))
return status;
stroke_pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface,
stroke_source,
&extents,
&stroke_pattern_res,
&gstate_res);
if (unlikely (status))
return status;
assert (gstate_res.id == 0);
status = _cairo_pdf_surface_select_pattern (surface, fill_source,
fill_pattern_res, FALSE);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_pattern (surface, stroke_source,
stroke_pattern_res, TRUE);
if (unlikely (status))
return status;
status = _cairo_pdf_operators_fill_stroke (&surface->pdf_operators,
path,
fill_rule,
stroke_style,
stroke_ctm,
stroke_ctm_inverse);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_unselect_pattern (surface);
if (unlikely (status))
return status;
return _cairo_output_stream_get_status (surface->output);
}
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,
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_status_t status;
status = _cairo_composite_rectangles_init_for_glyphs (&extents,
surface->width,
surface->height,
op, source,
scaled_font,
glyphs, num_glyphs,
clip,
&overlap);
if (unlikely (status)) {
if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
return CAIRO_STATUS_SUCCESS;
return status;
}
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
return _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
assert (_cairo_pdf_surface_operation_supported (surface, op, source, &extents.bounded));
status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
if (unlikely (status))
return status;
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, source,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO))
return CAIRO_STATUS_SUCCESS;
if (unlikely (status))
return status;
status = _cairo_pdf_surface_select_operator (surface, op);
if (unlikely (status))
return status;
if (gstate_res.id != 0) {
group = _cairo_pdf_surface_create_smask_group (surface);
if (unlikely (group == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
group->operation = PDF_SHOW_GLYPHS;
status = _cairo_pattern_create_copy (&group->source, source);
if (unlikely (status)) {
_cairo_pdf_smask_group_destroy (group);
return status;
}
group->source_res = pattern_res;
if (utf8_len) {
group->utf8 = malloc (utf8_len);
if (unlikely (group->utf8 == NULL)) {
_cairo_pdf_smask_group_destroy (group);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
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);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
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);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
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);
return status;
}
status = _cairo_pdf_surface_add_smask (surface, gstate_res);
if (unlikely (status))
return status;
status = _cairo_pdf_surface_add_xobject (surface, group->group_res);
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 /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))
return status;
if (! _cairo_pattern_is_opaque (source, &extents.bounded)) {
status = _cairo_pdf_operators_flush (&surface->pdf_operators);
if (unlikely (status))
return status;
}
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))
return status;
status = _cairo_pdf_surface_unselect_pattern (surface);
if (unlikely (status))
return status;
}
return _cairo_output_stream_get_status (surface->output);
}
static void
_cairo_pdf_surface_set_paginated_mode (void *abstract_surface,
cairo_paginated_mode_t paginated_mode)
{
cairo_pdf_surface_t *surface = abstract_surface;
surface->paginated_mode = paginated_mode;
}
static const cairo_surface_backend_t cairo_pdf_surface_backend = {
CAIRO_SURFACE_TYPE_PDF,
NULL,
_cairo_pdf_surface_finish,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
_cairo_pdf_surface_show_page,
_cairo_pdf_surface_get_extents,
NULL,
_cairo_pdf_surface_get_font_options,
NULL,
NULL,
NULL,
NULL,
_cairo_pdf_surface_paint,
_cairo_pdf_surface_mask,
_cairo_pdf_surface_stroke,
_cairo_pdf_surface_fill,
NULL,
NULL,
NULL,
_cairo_pdf_surface_fill_stroke,
NULL,
NULL,
_cairo_pdf_surface_has_show_text_glyphs,
_cairo_pdf_surface_show_text_glyphs,
};
static const cairo_paginated_surface_backend_t
cairo_pdf_surface_paginated_backend = {
_cairo_pdf_surface_start_page,
_cairo_pdf_surface_set_paginated_mode,
NULL,
_cairo_pdf_surface_has_fallback_images,
_cairo_pdf_surface_supports_fine_grained_fallbacks,
};