From 5ffbaf9e2f7da103da8d015b5f928e25f9433b60 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Thu, 9 Nov 2017 20:52:36 +1030 Subject: [PATCH] ps: add CAIRO_MIME_TYPE_EPS mime type for embedding EPS files --- doc/public/cairo-sections.txt | 2 + src/cairo-ps-surface-private.h | 1 + src/cairo-ps-surface.c | 183 +++++++++++++++++++++++++++-- src/cairo-surface.c | 18 +++ src/cairo-tag-attributes-private.h | 7 ++ src/cairo-tag-attributes.c | 47 ++++++++ src/cairo.h | 2 + 7 files changed, 252 insertions(+), 8 deletions(-) diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt index 40c214834..a735869e2 100644 --- a/doc/public/cairo-sections.txt +++ b/doc/public/cairo-sections.txt @@ -244,6 +244,8 @@ cairo_device_observer_stroke_elapsed CAIRO_HAS_MIME_SURFACE CAIRO_MIME_TYPE_CCITT_FAX CAIRO_MIME_TYPE_CCITT_FAX_PARAMS +CAIRO_MIME_TYPE_EPS +CAIRO_MIME_TYPE_EPS_PARAMS CAIRO_MIME_TYPE_JBIG2 CAIRO_MIME_TYPE_JBIG2_GLOBAL CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID diff --git a/src/cairo-ps-surface-private.h b/src/cairo-ps-surface-private.h index 651acdb35..e9e10c9b0 100644 --- a/src/cairo-ps-surface-private.h +++ b/src/cairo-ps-surface-private.h @@ -76,6 +76,7 @@ typedef struct cairo_ps_surface { cairo_output_stream_t *stream; cairo_bool_t eps; + cairo_bool_t contains_eps; cairo_content_t content; double width; double height; diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c index 076fe9eb6..fe09ed663 100644 --- a/src/cairo-ps-surface.c +++ b/src/cairo-ps-surface.c @@ -116,7 +116,9 @@ * * The following mime types are supported: %CAIRO_MIME_TYPE_JPEG, * %CAIRO_MIME_TYPE_UNIQUE_ID, - * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS. + * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, + * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, + * %CAIRO_MIME_TYPE_EPS, %CAIRO_MIME_TYPE_EPS_PARAMS. * * Source surfaces used by the PostScript surface that have a * %CAIRO_MIME_TYPE_UNIQUE_ID mime type will be stored in PostScript @@ -126,6 +128,24 @@ * * The %CAIRO_MIME_TYPE_CCITT_FAX and %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS mime types * are documented in [CCITT Fax Images][ccitt]. + * + * # Embedding EPS files # {#eps} + * + * Encapsulated PostScript files can be embedded in the PS output by + * setting the CAIRO_MIME_TYPE_EPS mime data on a surface to the EPS + * data and painting the surface. The EPS will be scaled and + * translated to the extents of the surface the EPS data is attached + * to. + * + * The %CAIRO_MIME_TYPE_EPS mime type requires the + * %CAIRO_MIME_TYPE_EPS_PARAMS mime data to also be provided in order + * to specify the embeddding parameters. %CAIRO_MIME_TYPE_EPS_PARAMS + * mime data must contain a string of the form "bbox=[llx lly urx + * ury]" that specifies the bounding box (in PS coordinates) of the + * EPS graphics. The parameters are: lower left x, lower left y, upper + * right x, upper right y. Normally the bbox data is identical to the + * %%%BoundingBox data in the EPS file. + * **/ /** @@ -153,6 +173,8 @@ typedef struct { /* input params */ cairo_surface_t *src_surface; cairo_operator_t op; + const cairo_rectangle_int_t *src_surface_extents; + cairo_bool_t src_surface_bounded; const cairo_rectangle_int_t *src_op_extents; /* operation extents in src space */ cairo_filter_t filter; cairo_bool_t stencil_mask; /* TRUE if source is to be used as a mask */ @@ -162,6 +184,7 @@ typedef struct { cairo_bool_t is_image; /* returns TRUE if PS image will be emitted */ /* FALSE if recording will be emitted */ long approx_size; + int eod_count; } cairo_emit_surface_params_t; static const cairo_surface_backend_t cairo_ps_surface_backend; @@ -354,7 +377,6 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface) if (surface->eps) { _cairo_output_stream_printf (surface->final_stream, - "save\n" "50 dict begin\n"); } else { _cairo_output_stream_printf (surface->final_stream, @@ -457,6 +479,22 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface) " } ifelse\n" "} def\n"); } + if (surface->contains_eps) { + _cairo_output_stream_printf (surface->final_stream, + "/cairo_eps_begin {\n" + " /cairo_save_state save def\n" + " /dict_count countdictstack def\n" + " /op_count count 1 sub def\n" + " userdict begin\n" + " /showpage { } def\n" + " 0 g 0 J 1 w 0 j 10 M [ ] 0 d n\n" + "} bind def\n" + "/cairo_eps_end {\n" + " count op_count sub { pop } repeat\n" + " countdictstack dict_count sub { end } repeat\n" + " cairo_save_state restore\n" + "} bind def\n"); + } _cairo_output_stream_printf (surface->final_stream, "%%%%EndProlog\n"); @@ -954,7 +992,7 @@ _cairo_ps_surface_emit_footer (cairo_ps_surface_t *surface) if (surface->eps) { _cairo_output_stream_printf (surface->final_stream, - "end restore\n"); + "end\n"); } _cairo_output_stream_printf (surface->final_stream, @@ -1162,6 +1200,7 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream, surface->document_bbox_p2.x = 0; surface->document_bbox_p2.y = 0; surface->total_form_size = 0; + surface->contains_eps = FALSE; _cairo_surface_clipper_init (&surface->clipper, _cairo_ps_surface_clipper_intersect_clip_path); @@ -3160,6 +3199,127 @@ _cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t *surface, return status; } +/* The '|' character is not used in PS (including ASCII85). We can + * speed up the search by first searching for the first char before + * comparing strings. + */ +#define SUBFILE_FILTER_EOD "|EOD|" + +/* Count number of non overlapping occurrences of SUBFILE_FILTER_EOD in data. */ +static int +count_eod_strings (const unsigned char *data, unsigned long data_len) +{ + const unsigned char *p = data; + const unsigned char *end; + int first_char, len, count; + const char *eod_str = SUBFILE_FILTER_EOD; + + first_char = eod_str[0]; + len = strlen (eod_str); + p = data; + end = data + data_len - len + 1; + count = 0; + while (p < end) { + p = memchr (p, first_char, end - p); + if (!p) + break; + + if (memcmp (p, eod_str, len) == 0) { + count++; + p += len; + } + } + + return count; +} + +static cairo_status_t +_cairo_ps_surface_emit_eps (cairo_ps_surface_t *surface, + cairo_emit_surface_mode_t mode, + cairo_emit_surface_params_t *params) +{ + cairo_status_t status; + const unsigned char *eps_data = NULL; + unsigned long eps_data_len; + const unsigned char *eps_params_string = NULL; + unsigned long eps_params_string_len; + char *params_string = NULL; + cairo_eps_params_t eps_params; + cairo_matrix_t mat; + double eps_width, eps_height; + + if (unlikely (params->src_surface->status)) + return params->src_surface->status; + + /* We only embed EPS with level 3 as we may use ReusableStreamDecode and we + * don't know what level the EPS file requires. */ + if (surface->ps_level == CAIRO_PS_LEVEL_2) + return CAIRO_INT_STATUS_UNSUPPORTED; + + cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_EPS, + &eps_data, &eps_data_len); + if (eps_data == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_EPS_PARAMS, + &eps_params_string, &eps_params_string_len); + if (eps_params_string == NULL) + return CAIRO_INT_STATUS_UNSUPPORTED; + + /* ensure params_string is null terminated */ + params_string = malloc (eps_params_string_len + 1); + memcpy (params_string, eps_params_string, eps_params_string_len); + params_string[eps_params_string_len] = 0; + status = _cairo_tag_parse_eps_params (params_string, &eps_params); + if (unlikely(status)) + return status; + + /* At this point we know emitting EPS will succeed. */ + if (mode == CAIRO_EMIT_SURFACE_ANALYZE) { + params->is_image = FALSE; + params->approx_size = eps_data_len; + surface->contains_eps = TRUE; + + /* Find number of occurences of SUBFILE_FILTER_EOD in the EPS data. + * We will need it before emitting the data if a ReusableStream is used. + */ + params->eod_count = count_eod_strings (eps_data, eps_data_len); + return CAIRO_STATUS_SUCCESS; + } + + surface->ps_level_used = CAIRO_PS_LEVEL_3; + _cairo_output_stream_printf (surface->stream, "cairo_eps_begin\n"); + + eps_width = eps_params.bbox.p2.x - eps_params.bbox.p1.x; + eps_height = eps_params.bbox.p2.y - eps_params.bbox.p1.y; + cairo_matrix_init_translate (&mat, + params->src_surface_extents->x, + params->src_surface_extents->y); + cairo_matrix_scale (&mat, + params->src_surface_extents->width/eps_width, + params->src_surface_extents->height/eps_height); + cairo_matrix_scale (&mat, 1, -1); + cairo_matrix_translate (&mat, -eps_params.bbox.p1.x, -eps_params.bbox.p2.y); + + if (! _cairo_matrix_is_identity (&mat)) { + _cairo_output_stream_printf (surface->stream, "[ "); + _cairo_output_stream_print_matrix (surface->stream, &mat); + _cairo_output_stream_printf (surface->stream, " ] concat\n"); + } + + _cairo_output_stream_printf (surface->stream, + "%f %f %f %f rectclip\n", + eps_params.bbox.p1.x, + eps_params.bbox.p1.y, + eps_width, + eps_height); + + _cairo_output_stream_write (surface->stream, eps_data, eps_data_len); + _cairo_output_stream_printf (surface->stream, "\ncairo_eps_end\n"); + + return CAIRO_STATUS_SUCCESS; +} + static cairo_status_t _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t *surface, cairo_surface_t *recording_surface, @@ -3457,6 +3617,14 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t *surface, return status; } + status = _cairo_ps_surface_emit_eps (surface, mode, params); + if (status == CAIRO_INT_STATUS_SUCCESS) { + params->is_image = FALSE; + goto surface_emitted; + } + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + status = _cairo_ps_surface_emit_jpeg_image (surface, mode, params); if (status == CAIRO_INT_STATUS_SUCCESS) { params->is_image = TRUE; @@ -3527,11 +3695,6 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t *surface, return status; } -/* The '|' character is not used in PS (including ASCII85) so we can - * use it as a /SubFileDecode EOD marker and assume EODCount will be 0. - */ -#define SUBFILE_FILTER_EOD "|EOD|" - static void _cairo_ps_form_emit (void *entry, void *closure) { @@ -3771,6 +3934,8 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t *surface, params.src_surface = image ? &image->base : source_surface; params.op = op; + params.src_surface_extents = &src_surface_extents; + params.src_surface_bounded = src_surface_bounded; params.src_op_extents = &src_op_extents; params.filter = pattern->filter; params.stencil_mask = stencil_mask; @@ -3920,6 +4085,8 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t *surface, params.src_surface = image ? &image->base : source_surface; params.op = op; + params.src_surface_extents = &pattern_extents; + params.src_surface_bounded = bounded; params.src_op_extents = &src_op_extents; params.filter = pattern->filter; params.stencil_mask = FALSE; diff --git a/src/cairo-surface.c b/src/cairo-surface.c index e04c478fb..961894a14 100644 --- a/src/cairo-surface.c +++ b/src/cairo-surface.c @@ -1280,6 +1280,24 @@ _cairo_surface_has_mime_image (cairo_surface_t *surface) * Since: 1.16 **/ +/** + * CAIRO_MIME_TYPE_EPS: + * + * Encapsulated PostScript file. + * [Encapsulated PostScript File Format Specification](http://wwwimages.adobe.com/content/dam/Adobe/endevnet/postscript/pdfs/5002.EPSF_Spec.pdf) + * + * Since: 1.16 + **/ + +/** + * CAIRO_MIME_TYPE_EPS_PARAMS: + * + * Embedding parameters Encapsulated PostScript data. + * See [Embedding EPS files][eps]. + * + * Since: 1.16 + **/ + /** * CAIRO_MIME_TYPE_JBIG2: * diff --git a/src/cairo-tag-attributes-private.h b/src/cairo-tag-attributes-private.h index 30bb48ed9..3f5fa5b64 100644 --- a/src/cairo-tag-attributes-private.h +++ b/src/cairo-tag-attributes-private.h @@ -39,6 +39,7 @@ #include "cairo-array-private.h" #include "cairo-error-private.h" +#include "cairo-types-private.h" typedef enum { TAG_LINK_INVALID = 0, @@ -79,6 +80,9 @@ typedef struct _cairo_ccitt_params { int damaged_rows_before_error; } cairo_ccitt_params_t; +typedef struct _cairo_eps_params { + cairo_box_double_t bbox; +} cairo_eps_params_t; cairo_private cairo_int_status_t _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs); @@ -89,4 +93,7 @@ _cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *de cairo_private cairo_int_status_t _cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *dest_attrs); +cairo_private cairo_int_status_t +_cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *dest_attrs); + #endif /* CAIRO_TAG_ATTRIBUTES_PRIVATE_H */ diff --git a/src/cairo-tag-attributes.c b/src/cairo-tag-attributes.c index 64173402d..05902b9c3 100644 --- a/src/cairo-tag-attributes.c +++ b/src/cairo-tag-attributes.c @@ -143,6 +143,20 @@ static attribute_spec_t _ccitt_params_spec[] = { NULL } }; +/* + * bbox - Bounding box of EPS file. The format is [ llx lly urx ury ] + * llx - lower left x xoordinate + * lly - lower left y xoordinate + * urx - upper right x xoordinate + * ury - upper right y xoordinate + * all cordinates are in PostScript coordinates. + */ +static attribute_spec_t _eps_params_spec[] = +{ + { "bbox", ATTRIBUTE_FLOAT, 4 }, + { NULL } +}; + typedef union { cairo_bool_t b; int i; @@ -649,3 +663,36 @@ _cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *cci return status; } + +cairo_int_status_t +_cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *eps_params) +{ + cairo_list_t list; + cairo_int_status_t status; + attribute_t *attr; + attrib_val_t val; + + cairo_list_init (&list); + status = parse_attributes (attributes, _eps_params_spec, &list); + if (unlikely (status)) + goto cleanup; + + cairo_list_foreach_entry (attr, attribute_t, &list, link) + { + if (strcmp (attr->name, "bbox") == 0) { + _cairo_array_copy_element (&attr->array, 0, &val); + eps_params->bbox.p1.x = val.f; + _cairo_array_copy_element (&attr->array, 1, &val); + eps_params->bbox.p1.y = val.f; + _cairo_array_copy_element (&attr->array, 2, &val); + eps_params->bbox.p2.x = val.f; + _cairo_array_copy_element (&attr->array, 3, &val); + eps_params->bbox.p2.y = val.f; + } + } + + cleanup: + free_attributes_list (&list); + + return status; +} diff --git a/src/cairo.h b/src/cairo.h index 671be5a89..e48157252 100644 --- a/src/cairo.h +++ b/src/cairo.h @@ -2455,6 +2455,8 @@ cairo_surface_set_user_data (cairo_surface_t *surface, #define CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID "application/x-cairo.jbig2-global-id" #define CAIRO_MIME_TYPE_CCITT_FAX "image/g3fax" #define CAIRO_MIME_TYPE_CCITT_FAX_PARAMS "application/x-cairo.ccitt.params" +#define CAIRO_MIME_TYPE_EPS "application/postscript" +#define CAIRO_MIME_TYPE_EPS_PARAMS "application/x-cairo.eps.params" cairo_public void cairo_surface_get_mime_data (cairo_surface_t *surface,