diff --git a/doc/public/tmpl/cairo-status.sgml b/doc/public/tmpl/cairo-status.sgml index de5f35fea..37ed0db7a 100644 --- a/doc/public/tmpl/cairo-status.sgml +++ b/doc/public/tmpl/cairo-status.sgml @@ -42,6 +42,7 @@ Decoding cairo's status @CAIRO_STATUS_INVALID_VISUAL: @CAIRO_STATUS_FILE_NOT_FOUND: @CAIRO_STATUS_INVALID_DASH: +@CAIRO_STATUS_INVALID_DSC_COMMENT: diff --git a/src/cairo-array.c b/src/cairo-array.c index ed9538b0d..7252101b2 100644 --- a/src/cairo-array.c +++ b/src/cairo-array.c @@ -200,10 +200,8 @@ _cairo_array_index (cairo_array_t *array, int index) * which in the num_elements==0 case gets the NULL pointer here, * but never dereferences it. */ - if (array->elements == NULL) { - assert (index == 0); + if (index == 0 && array->num_elements == 0) return NULL; - } assert (0 <= index && index < array->num_elements); diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c index bb9a0edf7..1099aca31 100644 --- a/src/cairo-ps-surface.c +++ b/src/cairo-ps-surface.c @@ -104,6 +104,12 @@ typedef struct cairo_ps_surface { cairo_hash_table_t *fonts; unsigned int max_font; + + cairo_array_t dsc_header_comments; + cairo_array_t dsc_setup_comments; + cairo_array_t dsc_page_setup_comments; + + cairo_array_t *dsc_comment_target; } cairo_ps_surface_t; @@ -170,6 +176,8 @@ static void _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface) { time_t now; + char **comments; + int i, num_comments; now = time (NULL); @@ -187,8 +195,18 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface) _cairo_output_stream_printf (surface->final_stream, "%%%%DocumentData: Clean7Bit\n" - "%%%%LanguageLevel: 2\n" - "%%%%Orientation: Portrait\n" + "%%%%LanguageLevel: 2\n"); + + num_comments = _cairo_array_num_elements (&surface->dsc_header_comments); + comments = _cairo_array_index (&surface->dsc_header_comments, 0); + for (i = 0; i < num_comments; i++) { + _cairo_output_stream_printf (surface->final_stream, + "%s\n", comments[i]); + free (comments[i]); + comments[i] = NULL; + } + + _cairo_output_stream_printf (surface->final_stream, "%%%%EndComments\n"); _cairo_output_stream_printf (surface->final_stream, @@ -202,6 +220,23 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface) "/R{setrgbcolor}bind def\n" "/S{show}bind def\n" "%%%%EndProlog\n"); + + num_comments = _cairo_array_num_elements (&surface->dsc_setup_comments); + if (num_comments) { + _cairo_output_stream_printf (surface->final_stream, + "%%%%BeginSetup\n"); + + comments = _cairo_array_index (&surface->dsc_setup_comments, 0); + for (i = 0; i < num_comments; i++) { + _cairo_output_stream_printf (surface->final_stream, + "%s\n", comments[i]); + free (comments[i]); + comments[i] = NULL; + } + + _cairo_output_stream_printf (surface->final_stream, + "%%%%EndSetup\n"); + } } static cairo_bool_t @@ -558,6 +593,12 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream, surface->num_pages = 0; + _cairo_array_init (&surface->dsc_header_comments, sizeof (char *)); + _cairo_array_init (&surface->dsc_setup_comments, sizeof (char *)); + _cairo_array_init (&surface->dsc_page_setup_comments, sizeof (char *)); + + surface->dsc_comment_target = &surface->dsc_header_comments; + return _cairo_paginated_surface_create (&surface->base, CAIRO_CONTENT_COLOR_ALPHA, width, height, @@ -742,6 +783,200 @@ cairo_ps_surface_set_size (cairo_surface_t *surface, ps_surface->height = height_in_points; } +/** + * cairo_ps_surface_dsc_comment: + * @surface: a PostScript cairo_surface_t + * @comment: a comment string to be emitted into the PostScript output + * + * Emit a comment into the PostScript output for the given surface. + * + * The comment is expected to conform to the PostScript Language + * Document Structuring Conventions (DSC). Please see that manual for + * details on the available comments and their meanings. In + * particular, the %%IncludeFeature comment allows a + * device-independent means of controlling printer device features. So + * the PostScript Printer Description Files Specification will also be + * a useful reference. + * + * The comment string must begin with a percent character (%) and the + * total length of the string (including any initial percent + * characters) must not exceed 255 characters. Violating either of + * these conditions will place @surface into an error state. But + * beyond these two conditions, this function will not enforce + * conformance of the comment with any particular specification. + * + * The comment string should not have a trailing newline. + * + * The DSC specifies different sections in which particular comments + * can appear. This function provides for comments to be emitted + * within three sections: the header, the Setup section, and the + * PageSetup section. Comments appearing in the first two sections + * apply to the entire document while comments in the BeginPageSetup + * section apply only to a single page. + * + * For comments to appear in the header section, this function should + * be called after the surface is created, but before a call to + * cairo_ps_surface_begin_setup(). + * + * For comments to appear in the Setup section, this function should + * be called after a call to cairo_ps_surface_begin_setup() but before + * a call to cairo_ps_surface_begin_page_setup(). + * + * For comments to appear in the PageSetup section, this function + * should be called after a call to cairo_ps_surface_begin_page_setup(). + * + * Note that it is only necessary to call cairo_ps_surface_begin_page_setup() + * for the first page of any surface. After a call to + * cairo_show_page() or cairo_copy_page() comments are unambiguously + * directed to the PageSetup section of the current page. But it + * doesn't hurt to call this function at the beginning of every page + * as that consistency may make the calling code simpler. + * + * As a final note, cairo automatically generates several comments on + * its own. As such, applications must not manually generate any of + * the following comments: + * + * Header section: %!PS-Adobe-3.0, %%Creator, %%CreationDate, %%Pages, + * %%BoundingBox, %%DocumentData, %%LanguageLevel, %%EndComments. + * + * Setup section: %%BeginSetup, %%EndSetup + * + * PageSetup section: %%BeginPageSetup, %%PageBoundingBox, + * %%EndPageSetup. + * + * Other sections: %%BeginProlog, %%EndProlog, %%Page, %%Trailer, %%EOF + * + * Here is an example sequence showing how this function might be used: + * + * + * cairo_surface_t *surface = cairo_ps_surface_create (filename, width, height); + * + * cairo_ps_surface_dsc_comment (surface, "%%Title: My excellent document"); + * cairo_ps_surface_dsc_comment (surface, "%%Copyright: Copyright (C) 2006 Cairo Lover") + * + * cairo_ps_surface_dsc_begin_setup (surface); + * cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *MediaColor White"); + * + * cairo_ps_surface_dsc_begin_page_setup (surface); + * cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *PageSize A3"); + * cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *InputSlot LargeCapacity"); + * cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *MediaType Glossy"); + * cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *MediaColor Blue"); + * + * ... draw to first page here .. + * + * cairo_show_page (cr); + * + * cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *PageSize A5"); + * ... + * + **/ +void +cairo_ps_surface_dsc_comment (cairo_surface_t *surface, + const char *comment) +{ + cairo_ps_surface_t *ps_surface; + cairo_status_t status; + char *comment_copy; + + status = _extract_ps_surface (surface, &ps_surface); + if (status) { + _cairo_surface_set_error (surface, CAIRO_STATUS_SURFACE_TYPE_MISMATCH); + return; + } + + /* A couple of sanity checks on the comment value. */ + if (comment == NULL) { + _cairo_surface_set_error (surface, CAIRO_STATUS_NULL_POINTER); + return; + } + + if (comment[0] != '%' || strlen (comment) > 255) { + _cairo_surface_set_error (surface, CAIRO_STATUS_INVALID_DSC_COMMENT); + return; + } + + /* Then, copy the comment and store it in the appropriate array. */ + comment_copy = strdup (comment); + if (comment_copy == NULL) { + _cairo_surface_set_error (surface, CAIRO_STATUS_NO_MEMORY); + return; + } + + status = _cairo_array_append (ps_surface->dsc_comment_target, &comment_copy); + if (status) { + free (comment_copy); + _cairo_surface_set_error (surface, status); + return; + } +} + +/** + * cairo_ps_surface_dsc_begin_setup: + * @surface: a PostScript cairo_surface_t + * + * This function indicates that subsequent calls to + * cairo_ps_surface_dsc_comment() should direct comments to the Setup + * section of the PostScript output. + * + * This function should be called at most once per surface, and must + * be called before any call to cairo_ps_surface_dsc_begin_page_setup() + * and before any drawing is performed to the surface. + * + * See cairo_ps_surface_dsc_comment() for more details. + **/ +void +cairo_ps_surface_dsc_begin_setup (cairo_surface_t *surface) +{ + cairo_ps_surface_t *ps_surface; + cairo_status_t status; + + status = _extract_ps_surface (surface, &ps_surface); + if (status) { + _cairo_surface_set_error (surface, CAIRO_STATUS_SURFACE_TYPE_MISMATCH); + return; + } + + if (ps_surface->dsc_comment_target == &ps_surface->dsc_header_comments) + { + ps_surface->dsc_comment_target = &ps_surface->dsc_setup_comments; + } +} + +/** + * cairo_ps_surface_dsc_begin_setup: + * @surface: a PostScript cairo_surface_t + * + * This function indicates that subsequent calls to + * cairo_ps_surface_dsc_comment() should direct comments to the + * PageSetup section of the PostScript output. + * + * This function call is only needed for the first page of a + * surface. It should be called after any call to + * cairo_ps_surface_dsc_begin_setup() and before any drawing is + * performed to the surface. + * + * See cairo_ps_surface_dsc_comment() for more details. + **/ +void +cairo_ps_surface_dsc_begin_page_setup (cairo_surface_t *surface) +{ + cairo_ps_surface_t *ps_surface; + cairo_status_t status; + + status = _extract_ps_surface (surface, &ps_surface); + if (status) { + _cairo_surface_set_error (surface, CAIRO_STATUS_SURFACE_TYPE_MISMATCH); + return; + } + + if (ps_surface->dsc_comment_target == &ps_surface->dsc_header_comments || + ps_surface->dsc_comment_target == &ps_surface->dsc_setup_comments) + { + ps_surface->dsc_comment_target = &ps_surface->dsc_page_setup_comments; + } +} + /* A word wrap stream can be used as a filter to do word wrapping on * top of an existing output stream. The word wrapping is quite * simple, using isspace to determine characters that separate @@ -872,6 +1107,8 @@ _cairo_ps_surface_finish (void *abstract_surface) cairo_status_t status; cairo_ps_surface_t *surface = abstract_surface; cairo_output_stream_t *final_stream, *word_wrap; + int i, num_comments; + char **comments; /* Save final_stream to be restored later. */ final_stream = surface->final_stream; @@ -902,6 +1139,24 @@ _cairo_ps_surface_finish (void *abstract_surface) status = _cairo_output_stream_get_status (surface->final_stream); _cairo_output_stream_destroy (surface->final_stream); + num_comments = _cairo_array_num_elements (&surface->dsc_header_comments); + comments = _cairo_array_index (&surface->dsc_header_comments, 0); + for (i = 0; i < num_comments; i++) + free (comments[i]); + _cairo_array_fini (&surface->dsc_header_comments); + + num_comments = _cairo_array_num_elements (&surface->dsc_setup_comments); + comments = _cairo_array_index (&surface->dsc_setup_comments, 0); + for (i = 0; i < num_comments; i++) + free (comments[i]); + _cairo_array_fini (&surface->dsc_setup_comments); + + num_comments = _cairo_array_num_elements (&surface->dsc_page_setup_comments); + comments = _cairo_array_index (&surface->dsc_page_setup_comments, 0); + for (i = 0; i < num_comments; i++) + free (comments[i]); + _cairo_array_fini (&surface->dsc_page_setup_comments); + return status; } @@ -909,6 +1164,8 @@ static cairo_int_status_t _cairo_ps_surface_start_page (void *abstract_surface) { cairo_ps_surface_t *surface = abstract_surface; + int i, num_comments; + char **comments; /* Increment before print so page numbers start at 1. */ surface->num_pages++; @@ -920,6 +1177,16 @@ _cairo_ps_surface_start_page (void *abstract_surface) _cairo_output_stream_printf (surface->stream, "%%%%BeginPageSetup\n"); + num_comments = _cairo_array_num_elements (&surface->dsc_page_setup_comments); + comments = _cairo_array_index (&surface->dsc_page_setup_comments, 0); + for (i = 0; i < num_comments; i++) { + _cairo_output_stream_printf (surface->stream, + "%s\n", comments[i]); + free (comments[i]); + comments[i] = NULL; + } + _cairo_array_truncate (&surface->dsc_page_setup_comments, 0); + _cairo_output_stream_printf (surface->stream, "%%%%PageBoundingBox: %d %d %d %d\n", 0, 0, diff --git a/src/cairo-ps.h b/src/cairo-ps.h index 75c624e69..2e1d98835 100644 --- a/src/cairo-ps.h +++ b/src/cairo-ps.h @@ -59,10 +59,20 @@ cairo_ps_surface_create_for_stream (cairo_write_func_t write_func, double height_in_points); cairo_public void -cairo_ps_surface_set_size (cairo_surface_t *abstract_surface, +cairo_ps_surface_set_size (cairo_surface_t *surface, double width_in_points, double height_in_points); +cairo_public void +cairo_ps_surface_dsc_comment (cairo_surface_t *surface, + const char *comment); + +cairo_public void +cairo_ps_surface_dsc_begin_setup (cairo_surface_t *surface); + +cairo_public void +cairo_ps_surface_dsc_begin_page_setup (cairo_surface_t *surface); + cairo_public void cairo_ps_surface_set_dpi (cairo_surface_t *surface, double x_dpi, diff --git a/src/cairo.c b/src/cairo.c index 70f14066d..a829dd7cd 100644 --- a/src/cairo.c +++ b/src/cairo.c @@ -62,7 +62,7 @@ static const cairo_t cairo_nil = { * a bit of a pain, but it should be easy to always catch as long as * one adds a new test case to test a trigger of the new status value. */ -#define CAIRO_STATUS_LAST_STATUS CAIRO_STATUS_INVALID_DASH +#define CAIRO_STATUS_LAST_STATUS CAIRO_STATUS_INVALID_DSC_COMMENT /** * _cairo_error: @@ -2683,6 +2683,8 @@ cairo_status_to_string (cairo_status_t status) return "file not found"; case CAIRO_STATUS_INVALID_DASH: return "invalid value for a dash setting"; + case CAIRO_STATUS_INVALID_DSC_COMMENT: + return "invalid value for a DSC comment"; } return ""; diff --git a/src/cairo.h b/src/cairo.h index db9354074..ea4e20b28 100644 --- a/src/cairo.h +++ b/src/cairo.h @@ -167,6 +167,7 @@ typedef struct _cairo_user_data_key { * @CAIRO_STATUS_INVALID_VISUAL: invalid value for an input Visual* * @CAIRO_STATUS_FILE_NOT_FOUND: file not found * @CAIRO_STATUS_INVALID_DASH: invalid value for a dash setting + * @CAIRO_STATUS_INVALID_DSC_COMMENT: invalid value for a DSC comment * * #cairo_status_t is used to indicate errors that can occur when * using Cairo. In some cases it is returned directly by functions. @@ -193,7 +194,8 @@ typedef enum _cairo_status { CAIRO_STATUS_INVALID_FORMAT, CAIRO_STATUS_INVALID_VISUAL, CAIRO_STATUS_FILE_NOT_FOUND, - CAIRO_STATUS_INVALID_DASH + CAIRO_STATUS_INVALID_DASH, + CAIRO_STATUS_INVALID_DSC_COMMENT, } cairo_status_t; /** diff --git a/test/ps-features.c b/test/ps-features.c index 501e4121b..4f5affc9c 100644 --- a/test/ps-features.c +++ b/test/ps-features.c @@ -44,41 +44,42 @@ struct { const char *page_size; + const char *page_size_alias; const char *orientation; double width_in_points; double height_in_points; } pages[] = { - {"na_letter_8.5x11in", "portrait", + {"na_letter_8.5x11in", "letter", "portrait", INCHES_TO_POINTS(8.5), INCHES_TO_POINTS(11)}, - {"na_letter_8.5x11in", "landscape", + {"na_letter_8.5x11in", "letter", "landscape", INCHES_TO_POINTS(11), INCHES_TO_POINTS(8.5)}, - {"iso_a4_210x297mm", "portrait", + {"iso_a4_210x297mm", "a4", "portrait", MM_TO_POINTS(210), MM_TO_POINTS(297)}, - {"iso_a4_210x297mm", "landscape", + {"iso_a4_210x297mm", "a4", "landscape", MM_TO_POINTS(297), MM_TO_POINTS(210)}, - {"iso_a5_148x210mm", "portrait", + {"iso_a5_148x210mm", "a5", "portrait", MM_TO_POINTS(148), MM_TO_POINTS(210)}, - {"iso_a5_148x210mm", "landscape", + {"iso_a5_148x210mm", "a5", "landscape", MM_TO_POINTS(210), MM_TO_POINTS(148)}, - {"iso_a6_105x148mm", "portrait", + {"iso_a6_105x148mm", "a6", "portrait", MM_TO_POINTS(105), MM_TO_POINTS(148)}, - {"iso_a6_105x148mm", "landscape", + {"iso_a6_105x148mm", "a6", "landscape", MM_TO_POINTS(148), MM_TO_POINTS(105)}, - {"iso_a7_74x105mm", "portrait", + {"iso_a7_74x105mm", "a7", "portrait", MM_TO_POINTS(74), MM_TO_POINTS(105)}, - {"iso_a7_74x105mm", "landscape", + {"iso_a7_74x105mm", "a7", "landscape", MM_TO_POINTS(105), MM_TO_POINTS(74)}, - {"iso_a8_52x74mm", "portrait", + {"iso_a8_52x74mm", "a8", "portrait", MM_TO_POINTS(52), MM_TO_POINTS(74)}, - {"iso_a8_52x74mm", "landscape", + {"iso_a8_52x74mm", "a8", "landscape", MM_TO_POINTS(74), MM_TO_POINTS(52)}, - {"iso_a9_37x52mm", "portrait", + {"iso_a9_37x52mm", "a9", "portrait", MM_TO_POINTS(37), MM_TO_POINTS(52)}, - {"iso_a9_37x52mm", "landscape", + {"iso_a9_37x52mm", "a9", "landscape", MM_TO_POINTS(52), MM_TO_POINTS(37)}, - {"iso_a10_26x37mm", "portrait", + {"iso_a10_26x37mm", "a10", "portrait", MM_TO_POINTS(26), MM_TO_POINTS(37)}, - {"iso_a10_26x37mm", "landscape", + {"iso_a10_26x37mm", "a10", "landscape", MM_TO_POINTS(37), MM_TO_POINTS(26)} }; @@ -90,6 +91,7 @@ main (void) cairo_status_t status; char *filename; int i; + char dsc[255]; printf("\n"); @@ -100,6 +102,13 @@ main (void) * page. */ surface = cairo_ps_surface_create (filename, 0, 0); + cairo_ps_surface_dsc_comment (surface, "%%Title: ps-features"); + cairo_ps_surface_dsc_comment (surface, "%%Copyright: Copyright (C) 2006 Red Hat, Inc."); + + cairo_ps_surface_dsc_begin_setup (surface); + cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *PageSize letter"); + cairo_ps_surface_dsc_comment (surface, "%%IncludeFeature: *MediaColor White"); + cr = cairo_create (surface); cairo_select_font_face (cr, "Bitstream Vera Sans", @@ -111,6 +120,14 @@ main (void) cairo_ps_surface_set_size (surface, pages[i].width_in_points, pages[i].height_in_points); + cairo_ps_surface_dsc_begin_page_setup (surface); + snprintf (dsc, 255, "%%IncludeFeature: *PageSize %s", pages[i].page_size_alias); + cairo_ps_surface_dsc_comment (surface, dsc); + if (i % 2) { + snprintf (dsc, 255, "%%IncludeFeature: *MediaType Glossy"); + cairo_ps_surface_dsc_comment (surface, dsc); + } + cairo_move_to (cr, TEXT_SIZE, TEXT_SIZE); cairo_show_text (cr, pages[i].page_size); cairo_show_text (cr, " - "); @@ -129,7 +146,10 @@ main (void) return CAIRO_TEST_FAILURE; } - printf ("multi-page-size: Please check %s to ensure it looks/prints correctly.\n", filename); + printf ("ps-features: Please check %s to ensure it looks/prints correctly.\n", filename); + + cairo_debug_reset_static_data (); + FcFini (); return CAIRO_TEST_SUCCESS; }