summaryrefslogtreecommitdiff
path: root/src/cairo-pdf-operators.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cairo-pdf-operators.c')
-rw-r--r--src/cairo-pdf-operators.c1560
1 files changed, 1560 insertions, 0 deletions
diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
new file mode 100644
index 000000000..dcee25f0c
--- /dev/null
+++ b/src/cairo-pdf-operators.c
@@ -0,0 +1,1560 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2004 Red Hat, Inc
+ * Copyright © 2006 Red Hat, Inc
+ * Copyright © 2007, 2008 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is University of Southern
+ * California.
+ *
+ * Contributor(s):
+ * Kristian Høgsberg <krh@redhat.com>
+ * Carl Worth <cworth@cworth.org>
+ * Adrian Johnson <ajohnson@redneon.com>
+ */
+
+#include "cairoint.h"
+
+#if CAIRO_HAS_PDF_OPERATORS
+
+#include "cairo-error-private.h"
+#include "cairo-pdf-operators-private.h"
+#include "cairo-path-fixed-private.h"
+#include "cairo-output-stream-private.h"
+#include "cairo-scaled-font-subsets-private.h"
+
+static cairo_status_t
+_cairo_pdf_operators_end_text (cairo_pdf_operators_t *pdf_operators);
+
+
+void
+_cairo_pdf_operators_init (cairo_pdf_operators_t *pdf_operators,
+ cairo_output_stream_t *stream,
+ cairo_matrix_t *cairo_to_pdf,
+ cairo_scaled_font_subsets_t *font_subsets,
+ cairo_bool_t ps)
+{
+ pdf_operators->stream = stream;
+ pdf_operators->cairo_to_pdf = *cairo_to_pdf;
+ pdf_operators->font_subsets = font_subsets;
+ pdf_operators->ps_output = ps;
+ pdf_operators->use_font_subset = NULL;
+ pdf_operators->use_font_subset_closure = NULL;
+ pdf_operators->in_text_object = FALSE;
+ pdf_operators->num_glyphs = 0;
+ pdf_operators->has_line_style = FALSE;
+ pdf_operators->use_actual_text = FALSE;
+}
+
+cairo_status_t
+_cairo_pdf_operators_fini (cairo_pdf_operators_t *pdf_operators)
+{
+ return _cairo_pdf_operators_flush (pdf_operators);
+}
+
+void
+_cairo_pdf_operators_set_font_subsets_callback (cairo_pdf_operators_t *pdf_operators,
+ cairo_pdf_operators_use_font_subset_t use_font_subset,
+ void *closure)
+{
+ pdf_operators->use_font_subset = use_font_subset;
+ pdf_operators->use_font_subset_closure = closure;
+}
+
+/* Change the output stream to a different stream.
+ * _cairo_pdf_operators_flush() should always be called before calling
+ * this function.
+ */
+void
+_cairo_pdf_operators_set_stream (cairo_pdf_operators_t *pdf_operators,
+ cairo_output_stream_t *stream)
+{
+ pdf_operators->stream = stream;
+ pdf_operators->has_line_style = FALSE;
+}
+
+void
+_cairo_pdf_operators_set_cairo_to_pdf_matrix (cairo_pdf_operators_t *pdf_operators,
+ cairo_matrix_t *cairo_to_pdf)
+{
+ pdf_operators->cairo_to_pdf = *cairo_to_pdf;
+ pdf_operators->has_line_style = FALSE;
+}
+
+cairo_private void
+_cairo_pdf_operators_enable_actual_text (cairo_pdf_operators_t *pdf_operators,
+ cairo_bool_t enable)
+{
+ pdf_operators->use_actual_text = enable;
+}
+
+/* Finish writing out any pending commands to the stream. This
+ * function must be called by the surface before emitting anything
+ * into the PDF stream.
+ *
+ * pdf_operators may leave the emitted PDF for some operations
+ * unfinished in case subsequent operations can be merged. This
+ * function will finish off any incomplete operation so the stream
+ * will be in a state where the surface may emit its own PDF
+ * operations (eg changing patterns).
+ *
+ */
+cairo_status_t
+_cairo_pdf_operators_flush (cairo_pdf_operators_t *pdf_operators)
+{
+ cairo_status_t status = CAIRO_STATUS_SUCCESS;
+
+ if (pdf_operators->in_text_object)
+ status = _cairo_pdf_operators_end_text (pdf_operators);
+
+ return status;
+}
+
+/* Reset the known graphics state of the PDF consumer. ie no
+ * assumptions will be made about the state. The next time a
+ * particular graphics state is required (eg line width) the state
+ * operator is always emitted and then remembered for subsequent
+ * operatations.
+ *
+ * This should be called when starting a new stream or after emitting
+ * the 'Q' operator (where pdf-operators functions were called inside
+ * the q/Q pair).
+ */
+void
+_cairo_pdf_operators_reset (cairo_pdf_operators_t *pdf_operators)
+{
+ pdf_operators->has_line_style = FALSE;
+}
+
+/* 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
+ * words. Any word that will cause the column count exceed the given
+ * max_column will have a '\n' character emitted before it.
+ *
+ * The stream is careful to maintain integrity for words that cross
+ * the boundary from one call to write to the next.
+ *
+ * Note: This stream does not guarantee that the output will never
+ * exceed max_column. In particular, if a single word is larger than
+ * max_column it will not be broken up.
+ */
+
+typedef enum _cairo_word_wrap_state {
+ WRAP_STATE_DELIMITER,
+ WRAP_STATE_WORD,
+ WRAP_STATE_STRING,
+ WRAP_STATE_HEXSTRING
+} cairo_word_wrap_state_t;
+
+
+typedef struct _word_wrap_stream {
+ cairo_output_stream_t base;
+ cairo_output_stream_t *output;
+ int max_column;
+ cairo_bool_t ps_output;
+ int column;
+ cairo_word_wrap_state_t state;
+ cairo_bool_t in_escape;
+ int escape_digits;
+} word_wrap_stream_t;
+
+
+
+/* Emit word bytes up to the next delimiter character */
+static int
+_word_wrap_stream_count_word_up_to (word_wrap_stream_t *stream,
+ const unsigned char *data, int length)
+{
+ const unsigned char *s = data;
+ int count = 0;
+
+ while (length--) {
+ if (_cairo_isspace (*s) || *s == '<' || *s == '(') {
+ stream->state = WRAP_STATE_DELIMITER;
+ break;
+ }
+
+ count++;
+ stream->column++;
+ s++;
+ }
+
+ if (count)
+ _cairo_output_stream_write (stream->output, data, count);
+
+ return count;
+}
+
+
+/* Emit hexstring bytes up to either the end of the ASCII hexstring or the number
+ * of columns remaining.
+ */
+static int
+_word_wrap_stream_count_hexstring_up_to (word_wrap_stream_t *stream,
+ const unsigned char *data, int length)
+{
+ const unsigned char *s = data;
+ int count = 0;
+ cairo_bool_t newline = FALSE;
+
+ while (length--) {
+ count++;
+ stream->column++;
+ if (*s == '>') {
+ stream->state = WRAP_STATE_DELIMITER;
+ break;
+ }
+
+ if (stream->column > stream->max_column) {
+ newline = TRUE;
+ break;
+ }
+ s++;
+ }
+
+ if (count)
+ _cairo_output_stream_write (stream->output, data, count);
+
+ if (newline) {
+ _cairo_output_stream_printf (stream->output, "\n");
+ stream->column = 0;
+ }
+
+ return count;
+}
+
+/* Count up to either the end of the string or the number of columns
+ * remaining.
+ */
+static int
+_word_wrap_stream_count_string_up_to (word_wrap_stream_t *stream,
+ const unsigned char *data, int length)
+{
+ const unsigned char *s = data;
+ int count = 0;
+ cairo_bool_t newline = FALSE;
+
+ while (length--) {
+ count++;
+ stream->column++;
+ if (!stream->in_escape) {
+ if (*s == ')') {
+ stream->state = WRAP_STATE_DELIMITER;
+ break;
+ }
+ if (*s == '\\') {
+ stream->in_escape = TRUE;
+ stream->escape_digits = 0;
+ } else if (stream->ps_output && stream->column > stream->max_column) {
+ newline = TRUE;
+ break;
+ }
+ } else {
+ if (!_cairo_isdigit(*s) || ++stream->escape_digits == 3)
+ stream->in_escape = FALSE;
+ }
+ s++;
+ }
+
+ if (count)
+ _cairo_output_stream_write (stream->output, data, count);
+
+ if (newline) {
+ _cairo_output_stream_printf (stream->output, "\\\n");
+ stream->column = 0;
+ }
+
+ return count;
+}
+
+static cairo_status_t
+_word_wrap_stream_write (cairo_output_stream_t *base,
+ const unsigned char *data,
+ unsigned int length)
+{
+ word_wrap_stream_t *stream = (word_wrap_stream_t *) base;
+ int count;
+
+ while (length) {
+ switch (stream->state) {
+ case WRAP_STATE_WORD:
+ count = _word_wrap_stream_count_word_up_to (stream, data, length);
+ break;
+ case WRAP_STATE_HEXSTRING:
+ count = _word_wrap_stream_count_hexstring_up_to (stream, data, length);
+ break;
+ case WRAP_STATE_STRING:
+ count = _word_wrap_stream_count_string_up_to (stream, data, length);
+ break;
+ case WRAP_STATE_DELIMITER:
+ count = 1;
+ stream->column++;
+ if (*data == '\n' || stream->column >= stream->max_column) {
+ _cairo_output_stream_printf (stream->output, "\n");
+ stream->column = 0;
+ } else if (*data == '<') {
+ stream->state = WRAP_STATE_HEXSTRING;
+ } else if (*data == '(') {
+ stream->state = WRAP_STATE_STRING;
+ } else if (!_cairo_isspace (*data)) {
+ stream->state = WRAP_STATE_WORD;
+ }
+ if (*data != '\n')
+ _cairo_output_stream_write (stream->output, data, 1);
+ break;
+
+ default:
+ ASSERT_NOT_REACHED;
+ count = length;
+ break;
+ }
+ data += count;
+ length -= count;
+ }
+
+ return _cairo_output_stream_get_status (stream->output);
+}
+
+static cairo_status_t
+_word_wrap_stream_close (cairo_output_stream_t *base)
+{
+ word_wrap_stream_t *stream = (word_wrap_stream_t *) base;
+
+ return _cairo_output_stream_get_status (stream->output);
+}
+
+static cairo_output_stream_t *
+_word_wrap_stream_create (cairo_output_stream_t *output, cairo_bool_t ps, int max_column)
+{
+ word_wrap_stream_t *stream;
+
+ if (output->status)
+ return _cairo_output_stream_create_in_error (output->status);
+
+ stream = malloc (sizeof (word_wrap_stream_t));
+ if (unlikely (stream == NULL)) {
+ _cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
+ return (cairo_output_stream_t *) &_cairo_output_stream_nil;
+ }
+
+ _cairo_output_stream_init (&stream->base,
+ _word_wrap_stream_write,
+ NULL,
+ _word_wrap_stream_close);
+ stream->output = output;
+ stream->max_column = max_column;
+ stream->ps_output = ps;
+ stream->column = 0;
+ stream->state = WRAP_STATE_DELIMITER;
+ stream->in_escape = FALSE;
+ stream->escape_digits = 0;
+
+ return &stream->base;
+}
+
+typedef struct _pdf_path_info {
+ cairo_output_stream_t *output;
+ cairo_matrix_t *path_transform;
+ cairo_line_cap_t line_cap;
+ cairo_point_t last_move_to_point;
+ cairo_bool_t has_sub_path;
+} pdf_path_info_t;
+
+static cairo_status_t
+_cairo_pdf_path_move_to (void *closure,
+ const cairo_point_t *point)
+{
+ pdf_path_info_t *info = closure;
+ double x = _cairo_fixed_to_double (point->x);
+ double y = _cairo_fixed_to_double (point->y);
+
+ info->last_move_to_point = *point;
+ info->has_sub_path = FALSE;
+ cairo_matrix_transform_point (info->path_transform, &x, &y);
+ _cairo_output_stream_printf (info->output,
+ "%g %g m ", x, y);
+
+ return _cairo_output_stream_get_status (info->output);
+}
+
+static cairo_status_t
+_cairo_pdf_path_line_to (void *closure,
+ const cairo_point_t *point)
+{
+ pdf_path_info_t *info = closure;
+ double x = _cairo_fixed_to_double (point->x);
+ double y = _cairo_fixed_to_double (point->y);
+
+ if (info->line_cap != CAIRO_LINE_CAP_ROUND &&
+ ! info->has_sub_path &&
+ point->x == info->last_move_to_point.x &&
+ point->y == info->last_move_to_point.y)
+ {
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ info->has_sub_path = TRUE;
+ cairo_matrix_transform_point (info->path_transform, &x, &y);
+ _cairo_output_stream_printf (info->output,
+ "%g %g l ", x, y);
+
+ return _cairo_output_stream_get_status (info->output);
+}
+
+static cairo_status_t
+_cairo_pdf_path_curve_to (void *closure,
+ const cairo_point_t *b,
+ const cairo_point_t *c,
+ const cairo_point_t *d)
+{
+ pdf_path_info_t *info = closure;
+ double bx = _cairo_fixed_to_double (b->x);
+ double by = _cairo_fixed_to_double (b->y);
+ double cx = _cairo_fixed_to_double (c->x);
+ double cy = _cairo_fixed_to_double (c->y);
+ double dx = _cairo_fixed_to_double (d->x);
+ double dy = _cairo_fixed_to_double (d->y);
+
+ info->has_sub_path = TRUE;
+ cairo_matrix_transform_point (info->path_transform, &bx, &by);
+ cairo_matrix_transform_point (info->path_transform, &cx, &cy);
+ cairo_matrix_transform_point (info->path_transform, &dx, &dy);
+ _cairo_output_stream_printf (info->output,
+ "%g %g %g %g %g %g c ",
+ bx, by, cx, cy, dx, dy);
+ return _cairo_output_stream_get_status (info->output);
+}
+
+static cairo_status_t
+_cairo_pdf_path_close_path (void *closure)
+{
+ pdf_path_info_t *info = closure;
+
+ if (info->line_cap != CAIRO_LINE_CAP_ROUND &&
+ ! info->has_sub_path)
+ {
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ _cairo_output_stream_printf (info->output,
+ "h\n");
+
+ return _cairo_output_stream_get_status (info->output);
+}
+
+static cairo_status_t
+_cairo_pdf_path_rectangle (pdf_path_info_t *info, cairo_box_t *box)
+{
+ double x1 = _cairo_fixed_to_double (box->p1.x);
+ double y1 = _cairo_fixed_to_double (box->p1.y);
+ double x2 = _cairo_fixed_to_double (box->p2.x);
+ double y2 = _cairo_fixed_to_double (box->p2.y);
+
+ cairo_matrix_transform_point (info->path_transform, &x1, &y1);
+ cairo_matrix_transform_point (info->path_transform, &x2, &y2);
+ _cairo_output_stream_printf (info->output,
+ "%g %g %g %g re ",
+ x1, y1, x2 - x1, y2 - y1);
+
+ return _cairo_output_stream_get_status (info->output);
+}
+
+/* The line cap value is needed to workaround the fact that PostScript
+ * and PDF semantics for stroking degenerate sub-paths do not match
+ * cairo semantics. (PostScript draws something for any line cap
+ * value, while cairo draws something only for round caps).
+ *
+ * When using this function to emit a path to be filled, rather than
+ * stroked, simply pass %CAIRO_LINE_CAP_ROUND which will guarantee that
+ * the stroke workaround will not modify the path being emitted.
+ */
+static cairo_status_t
+_cairo_pdf_operators_emit_path (cairo_pdf_operators_t *pdf_operators,
+ const cairo_path_fixed_t*path,
+ cairo_matrix_t *path_transform,
+ cairo_line_cap_t line_cap)
+{
+ cairo_output_stream_t *word_wrap;
+ cairo_status_t status, status2;
+ pdf_path_info_t info;
+ cairo_box_t box;
+
+ word_wrap = _word_wrap_stream_create (pdf_operators->stream, pdf_operators->ps_output, 72);
+ status = _cairo_output_stream_get_status (word_wrap);
+ if (unlikely (status))
+ return _cairo_output_stream_destroy (word_wrap);
+
+ info.output = word_wrap;
+ info.path_transform = path_transform;
+ info.line_cap = line_cap;
+ if (_cairo_path_fixed_is_rectangle (path, &box) &&
+ ((path_transform->xx == 0 && path_transform->yy == 0) ||
+ (path_transform->xy == 0 && path_transform->yx == 0))) {
+ status = _cairo_pdf_path_rectangle (&info, &box);
+ } else {
+ status = _cairo_path_fixed_interpret (path,
+ _cairo_pdf_path_move_to,
+ _cairo_pdf_path_line_to,
+ _cairo_pdf_path_curve_to,
+ _cairo_pdf_path_close_path,
+ &info);
+ }
+
+ status2 = _cairo_output_stream_destroy (word_wrap);
+ if (status == CAIRO_STATUS_SUCCESS)
+ status = status2;
+
+ return status;
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_clip (cairo_pdf_operators_t *pdf_operators,
+ const cairo_path_fixed_t *path,
+ cairo_fill_rule_t fill_rule)
+{
+ const char *pdf_operator;
+ cairo_status_t status;
+
+ if (pdf_operators->in_text_object) {
+ status = _cairo_pdf_operators_end_text (pdf_operators);
+ if (unlikely (status))
+ return status;
+ }
+
+ if (! path->has_current_point) {
+ /* construct an empty path */
+ _cairo_output_stream_printf (pdf_operators->stream, "0 0 m ");
+ } else {
+ status = _cairo_pdf_operators_emit_path (pdf_operators,
+ path,
+ &pdf_operators->cairo_to_pdf,
+ CAIRO_LINE_CAP_ROUND);
+ if (unlikely (status))
+ return status;
+ }
+
+ switch (fill_rule) {
+ default:
+ ASSERT_NOT_REACHED;
+ case CAIRO_FILL_RULE_WINDING:
+ pdf_operator = "W";
+ break;
+ case CAIRO_FILL_RULE_EVEN_ODD:
+ pdf_operator = "W*";
+ break;
+ }
+
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%s n\n",
+ pdf_operator);
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+static int
+_cairo_pdf_line_cap (cairo_line_cap_t cap)
+{
+ switch (cap) {
+ case CAIRO_LINE_CAP_BUTT:
+ return 0;
+ case CAIRO_LINE_CAP_ROUND:
+ return 1;
+ case CAIRO_LINE_CAP_SQUARE:
+ return 2;
+ default:
+ ASSERT_NOT_REACHED;
+ return 0;
+ }
+}
+
+static int
+_cairo_pdf_line_join (cairo_line_join_t join)
+{
+ switch (join) {
+ case CAIRO_LINE_JOIN_MITER:
+ return 0;
+ case CAIRO_LINE_JOIN_ROUND:
+ return 1;
+ case CAIRO_LINE_JOIN_BEVEL:
+ return 2;
+ default:
+ ASSERT_NOT_REACHED;
+ return 0;
+ }
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_emit_stroke_style (cairo_pdf_operators_t *pdf_operators,
+ const cairo_stroke_style_t *style,
+ double scale)
+{
+ double *dash = style->dash;
+ int num_dashes = style->num_dashes;
+ double dash_offset = style->dash_offset;
+ double line_width = style->line_width * scale;
+
+ /* PostScript has "special needs" when it comes to zero-length
+ * dash segments with butt caps. It apparently (at least
+ * according to ghostscript) draws hairlines for this
+ * case. That's not what the cairo semantics want, so we first
+ * touch up the array to eliminate any 0.0 values that will
+ * result in "on" segments.
+ */
+ if (num_dashes && style->line_cap == CAIRO_LINE_CAP_BUTT) {
+ int i;
+
+ /* If there's an odd number of dash values they will each get
+ * interpreted as both on and off. So we first explicitly
+ * expand the array to remove the duplicate usage so that we
+ * can modify some of the values.
+ */
+ if (num_dashes % 2) {
+ dash = _cairo_malloc_abc (num_dashes, 2, sizeof (double));
+ if (unlikely (dash == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ memcpy (dash, style->dash, num_dashes * sizeof (double));
+ memcpy (dash + num_dashes, style->dash, num_dashes * sizeof (double));
+
+ num_dashes *= 2;
+ }
+
+ for (i = 0; i < num_dashes; i += 2) {
+ if (dash[i] == 0.0) {
+ /* Do not modify the dashes in-place, as we may need to also
+ * replay this stroke to an image fallback.
+ */
+ if (dash == style->dash) {
+ dash = _cairo_malloc_ab (num_dashes, sizeof (double));
+ if (unlikely (dash == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ memcpy (dash, style->dash, num_dashes * sizeof (double));
+ }
+
+ /* If we're at the front of the list, we first rotate
+ * two elements from the end of the list to the front
+ * of the list before folding away the 0.0. Or, if
+ * there are only two dash elements, then there is
+ * nothing at all to draw.
+ */
+ if (i == 0) {
+ double last_two[2];
+
+ if (num_dashes == 2) {
+ free (dash);
+ return CAIRO_INT_STATUS_NOTHING_TO_DO;
+ }
+
+ /* The cases of num_dashes == 0, 1, or 3 elements
+ * cannot exist, so the rotation of 2 elements
+ * will always be safe */
+ memcpy (last_two, dash + num_dashes - 2, sizeof (last_two));
+ memmove (dash + 2, dash, (num_dashes - 2) * sizeof (double));
+ memcpy (dash, last_two, sizeof (last_two));
+ dash_offset += dash[0] + dash[1];
+ i = 2;
+ }
+ dash[i-1] += dash[i+1];
+ num_dashes -= 2;
+ memmove (dash + i, dash + i + 2, (num_dashes - i) * sizeof (double));
+ /* If we might have just rotated, it's possible that
+ * we rotated a 0.0 value to the front of the list.
+ * Set i to -2 so it will get incremented to 0. */
+ if (i == 2)
+ i = -2;
+ }
+ }
+ }
+
+ if (!pdf_operators->has_line_style || pdf_operators->line_width != line_width) {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%f w\n",
+ line_width);
+ pdf_operators->line_width = line_width;
+ }
+
+ if (!pdf_operators->has_line_style || pdf_operators->line_cap != style->line_cap) {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%d J\n",
+ _cairo_pdf_line_cap (style->line_cap));
+ pdf_operators->line_cap = style->line_cap;
+ }
+
+ if (!pdf_operators->has_line_style || pdf_operators->line_join != style->line_join) {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%d j\n",
+ _cairo_pdf_line_join (style->line_join));
+ pdf_operators->line_join = style->line_join;
+ }
+
+ if (num_dashes) {
+ int d;
+
+ _cairo_output_stream_printf (pdf_operators->stream, "[");
+ for (d = 0; d < num_dashes; d++)
+ _cairo_output_stream_printf (pdf_operators->stream, " %f", dash[d] * scale);
+ _cairo_output_stream_printf (pdf_operators->stream, "] %f d\n",
+ dash_offset * scale);
+ pdf_operators->has_dashes = TRUE;
+ } else if (!pdf_operators->has_line_style || pdf_operators->has_dashes) {
+ _cairo_output_stream_printf (pdf_operators->stream, "[] 0.0 d\n");
+ pdf_operators->has_dashes = FALSE;
+ }
+ if (dash != style->dash)
+ free (dash);
+
+ if (!pdf_operators->has_line_style || pdf_operators->miter_limit != style->miter_limit) {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%f M ",
+ style->miter_limit < 1.0 ? 1.0 : style->miter_limit);
+ pdf_operators->miter_limit = style->miter_limit;
+ }
+ pdf_operators->has_line_style = TRUE;
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+/* Scale the matrix so the largest absolute value of the non
+ * translation components is 1.0. Return the scale required to restore
+ * the matrix to the original values.
+ *
+ * eg the matrix [ 100 0 0 50 20 10 ]
+ *
+ * is rescaled to [ 1 0 0 0.5 0.2 0.1 ]
+ * and the scale returned is 100
+ */
+static void
+_cairo_matrix_factor_out_scale (cairo_matrix_t *m, double *scale)
+{
+ double s;
+
+ s = fabs (m->xx);
+ if (fabs (m->xy) > s)
+ s = fabs (m->xy);
+ if (fabs (m->yx) > s)
+ s = fabs (m->yx);
+ if (fabs (m->yy) > s)
+ s = fabs (m->yy);
+ *scale = s;
+ s = 1.0/s;
+ cairo_matrix_scale (m, s, s);
+}
+
+static cairo_int_status_t
+_cairo_pdf_operators_emit_stroke (cairo_pdf_operators_t *pdf_operators,
+ const cairo_path_fixed_t *path,
+ const cairo_stroke_style_t *style,
+ const cairo_matrix_t *ctm,
+ const cairo_matrix_t *ctm_inverse,
+ const char *pdf_operator)
+{
+ cairo_int_status_t status;
+ cairo_matrix_t m, path_transform;
+ cairo_bool_t has_ctm = TRUE;
+ double scale = 1.0;
+
+ if (pdf_operators->in_text_object) {
+ status = _cairo_pdf_operators_end_text (pdf_operators);
+ if (unlikely (status))
+ return status;
+ }
+
+ /* Optimize away the stroke ctm when it does not affect the
+ * stroke. There are other ctm cases that could be optimized
+ * however this is the most common.
+ */
+ if (fabs(ctm->xx) == 1.0 && fabs(ctm->yy) == 1.0 &&
+ fabs(ctm->xy) == 0.0 && fabs(ctm->yx) == 0.0)
+ {
+ has_ctm = FALSE;
+ }
+
+ /* The PDF CTM is transformed to the user space CTM when stroking
+ * so the corect pen shape will be used. This also requires that
+ * the path be transformed to user space when emitted. The
+ * conversion of path coordinates to user space may cause rounding
+ * errors. For example the device space point (1.234, 3.142) when
+ * transformed to a user space CTM of [100 0 0 100 0 0] will be
+ * emitted as (0.012, 0.031).
+ *
+ * To avoid the rounding problem we scale the user space CTM
+ * matrix so that all the non translation components of the matrix
+ * are <= 1. The line width and and dashes are scaled by the
+ * inverse of the scale applied to the CTM. This maintains the
+ * shape of the stroke pen while keeping the user space CTM within
+ * the range that maximizes the precision of the emitted path.
+ */
+ if (has_ctm) {
+ m = *ctm;
+ /* Zero out the translation since it does not affect the pen
+ * shape however it may cause unnecessary digits to be emitted.
+ */
+ m.x0 = 0.0;
+ m.y0 = 0.0;
+ _cairo_matrix_factor_out_scale (&m, &scale);
+ path_transform = m;
+ status = cairo_matrix_invert (&path_transform);
+ if (unlikely (status))
+ return status;
+
+ cairo_matrix_multiply (&m, &m, &pdf_operators->cairo_to_pdf);
+ }
+
+ status = _cairo_pdf_operators_emit_stroke_style (pdf_operators, style, scale);
+ if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
+ return CAIRO_STATUS_SUCCESS;
+ if (unlikely (status))
+ return status;
+
+ if (has_ctm) {
+ _cairo_output_stream_printf (pdf_operators->stream, "q ");
+ _cairo_output_stream_print_matrix (pdf_operators->stream, &m);
+ _cairo_output_stream_printf (pdf_operators->stream, " cm\n");
+ } else {
+ path_transform = pdf_operators->cairo_to_pdf;
+ }
+
+ status = _cairo_pdf_operators_emit_path (pdf_operators,
+ path,
+ &path_transform,
+ style->line_cap);
+ if (unlikely (status))
+ return status;
+
+ _cairo_output_stream_printf (pdf_operators->stream, "%s", pdf_operator);
+ if (has_ctm)
+ _cairo_output_stream_printf (pdf_operators->stream, " Q");
+
+ _cairo_output_stream_printf (pdf_operators->stream, "\n");
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_stroke (cairo_pdf_operators_t *pdf_operators,
+ const cairo_path_fixed_t *path,
+ const cairo_stroke_style_t *style,
+ const cairo_matrix_t *ctm,
+ const cairo_matrix_t *ctm_inverse)
+{
+ return _cairo_pdf_operators_emit_stroke (pdf_operators,
+ path,
+ style,
+ ctm,
+ ctm_inverse,
+ "S");
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_fill (cairo_pdf_operators_t *pdf_operators,
+ const cairo_path_fixed_t *path,
+ cairo_fill_rule_t fill_rule)
+{
+ const char *pdf_operator;
+ cairo_status_t status;
+
+ if (pdf_operators->in_text_object) {
+ status = _cairo_pdf_operators_end_text (pdf_operators);
+ if (unlikely (status))
+ return status;
+ }
+
+ status = _cairo_pdf_operators_emit_path (pdf_operators,
+ path,
+ &pdf_operators->cairo_to_pdf,
+ CAIRO_LINE_CAP_ROUND);
+ if (unlikely (status))
+ return status;
+
+ switch (fill_rule) {
+ default:
+ ASSERT_NOT_REACHED;
+ case CAIRO_FILL_RULE_WINDING:
+ pdf_operator = "f";
+ break;
+ case CAIRO_FILL_RULE_EVEN_ODD:
+ pdf_operator = "f*";
+ break;
+ }
+
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%s\n",
+ pdf_operator);
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_fill_stroke (cairo_pdf_operators_t *pdf_operators,
+ const cairo_path_fixed_t *path,
+ cairo_fill_rule_t fill_rule,
+ const cairo_stroke_style_t *style,
+ const cairo_matrix_t *ctm,
+ const cairo_matrix_t *ctm_inverse)
+{
+ const char *operator;
+
+ switch (fill_rule) {
+ default:
+ ASSERT_NOT_REACHED;
+ case CAIRO_FILL_RULE_WINDING:
+ operator = "B";
+ break;
+ case CAIRO_FILL_RULE_EVEN_ODD:
+ operator = "B*";
+ break;
+ }
+
+ return _cairo_pdf_operators_emit_stroke (pdf_operators,
+ path,
+ style,
+ ctm,
+ ctm_inverse,
+ operator);
+}
+
+static void
+_cairo_pdf_operators_emit_glyph_index (cairo_pdf_operators_t *pdf_operators,
+ cairo_output_stream_t *stream,
+ unsigned int glyph)
+{
+ if (pdf_operators->is_latin) {
+ if (glyph == '(' || glyph == ')' || glyph == '\\')
+ _cairo_output_stream_printf (stream, "\\%c", glyph);
+ else if (glyph >= 0x20 && glyph <= 0x7e)
+ _cairo_output_stream_printf (stream, "%c", glyph);
+ else
+ _cairo_output_stream_printf (stream, "\\%03o", glyph);
+ } else {
+ _cairo_output_stream_printf (stream,
+ "%0*x",
+ pdf_operators->hex_width,
+ glyph);
+ }
+}
+
+#define GLYPH_POSITION_TOLERANCE 0.001
+
+/* Emit the string of glyphs using the 'Tj' operator. This requires
+ * that the glyphs are positioned at their natural glyph advances. */
+static cairo_status_t
+_cairo_pdf_operators_emit_glyph_string (cairo_pdf_operators_t *pdf_operators,
+ cairo_output_stream_t *stream)
+{
+ int i;
+
+ _cairo_output_stream_printf (stream, "%s", pdf_operators->is_latin ? "(" : "<");
+ for (i = 0; i < pdf_operators->num_glyphs; i++) {
+ _cairo_pdf_operators_emit_glyph_index (pdf_operators,
+ stream,
+ pdf_operators->glyphs[i].glyph_index);
+ pdf_operators->cur_x += pdf_operators->glyphs[i].x_advance;
+ }
+ _cairo_output_stream_printf (stream, "%sTj\n", pdf_operators->is_latin ? ")" : ">");
+
+ return _cairo_output_stream_get_status (stream);
+}
+
+/* Emit the string of glyphs using the 'TJ' operator.
+ *
+ * The TJ operator takes an array of strings of glyphs. Each string of
+ * glyphs is displayed using the glyph advances of each glyph to
+ * position the glyphs. A relative adjustment to the glyph advance may
+ * be specified by including the adjustment between two strings. The
+ * adjustment is in units of text space * -1000.
+ */
+static cairo_status_t
+_cairo_pdf_operators_emit_glyph_string_with_positioning (
+ cairo_pdf_operators_t *pdf_operators,
+ cairo_output_stream_t *stream)
+{
+ int i;
+
+ _cairo_output_stream_printf (stream, "[%s", pdf_operators->is_latin ? "(" : "<");
+ for (i = 0; i < pdf_operators->num_glyphs; i++) {
+ if (pdf_operators->glyphs[i].x_position != pdf_operators->cur_x)
+ {
+ double delta = pdf_operators->glyphs[i].x_position - pdf_operators->cur_x;
+ int rounded_delta;
+
+ delta = -1000.0*delta;
+ /* As the delta is in 1/1000 of a unit of text space,
+ * rounding to an integer should still provide sufficient
+ * precision. We round the delta before adding to Tm_x so
+ * that we keep track of the accumulated rounding error in
+ * the PDF interpreter and compensate for it when
+ * calculating subsequent deltas.
+ */
+ rounded_delta = _cairo_lround (delta);
+ if (abs(rounded_delta) < 3)
+ rounded_delta = 0;
+ if (rounded_delta != 0) {
+ if (pdf_operators->is_latin) {
+ _cairo_output_stream_printf (stream,
+ ")%d(",
+ rounded_delta);
+ } else {
+ _cairo_output_stream_printf (stream,
+ ">%d<",
+ rounded_delta);
+ }
+ }
+
+ /* Convert the rounded delta back to text
+ * space before adding to the current text
+ * position. */
+ delta = rounded_delta/-1000.0;
+ pdf_operators->cur_x += delta;
+ }
+
+ _cairo_pdf_operators_emit_glyph_index (pdf_operators,
+ stream,
+ pdf_operators->glyphs[i].glyph_index);
+ pdf_operators->cur_x += pdf_operators->glyphs[i].x_advance;
+ }
+ _cairo_output_stream_printf (stream, "%s]TJ\n", pdf_operators->is_latin ? ")" : ">");
+
+ return _cairo_output_stream_get_status (stream);
+}
+
+static cairo_status_t
+_cairo_pdf_operators_flush_glyphs (cairo_pdf_operators_t *pdf_operators)
+{
+ cairo_output_stream_t *word_wrap_stream;
+ cairo_status_t status, status2;
+ int i;
+ double x;
+
+ if (pdf_operators->num_glyphs == 0)
+ return CAIRO_STATUS_SUCCESS;
+
+ word_wrap_stream = _word_wrap_stream_create (pdf_operators->stream, pdf_operators->ps_output, 72);
+ status = _cairo_output_stream_get_status (word_wrap_stream);
+ if (unlikely (status))
+ return _cairo_output_stream_destroy (word_wrap_stream);
+
+ /* Check if glyph advance used to position every glyph */
+ x = pdf_operators->cur_x;
+ for (i = 0; i < pdf_operators->num_glyphs; i++) {
+ if (fabs(pdf_operators->glyphs[i].x_position - x) > GLYPH_POSITION_TOLERANCE)
+ break;
+ x += pdf_operators->glyphs[i].x_advance;
+ }
+ if (i == pdf_operators->num_glyphs) {
+ status = _cairo_pdf_operators_emit_glyph_string (pdf_operators,
+ word_wrap_stream);
+ } else {
+ status = _cairo_pdf_operators_emit_glyph_string_with_positioning (
+ pdf_operators, word_wrap_stream);
+ }
+
+ pdf_operators->num_glyphs = 0;
+ pdf_operators->glyph_buf_x_pos = pdf_operators->cur_x;
+ status2 = _cairo_output_stream_destroy (word_wrap_stream);
+ if (status == CAIRO_STATUS_SUCCESS)
+ status = status2;
+
+ return status;
+}
+
+static cairo_status_t
+_cairo_pdf_operators_add_glyph (cairo_pdf_operators_t *pdf_operators,
+ cairo_scaled_font_subsets_glyph_t *glyph,
+ double x_position)
+{
+ double x, y;
+
+ x = glyph->x_advance;
+ y = glyph->y_advance;
+ if (glyph->is_scaled)
+ cairo_matrix_transform_distance (&pdf_operators->font_matrix_inverse, &x, &y);
+
+ pdf_operators->glyphs[pdf_operators->num_glyphs].x_position = x_position;
+ pdf_operators->glyphs[pdf_operators->num_glyphs].glyph_index = glyph->subset_glyph_index;
+ pdf_operators->glyphs[pdf_operators->num_glyphs].x_advance = x;
+ pdf_operators->glyph_buf_x_pos += x;
+ pdf_operators->num_glyphs++;
+ if (pdf_operators->num_glyphs == PDF_GLYPH_BUFFER_SIZE)
+ return _cairo_pdf_operators_flush_glyphs (pdf_operators);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+/* Use 'Tm' operator to set the PDF text matrix. */
+static cairo_status_t
+_cairo_pdf_operators_set_text_matrix (cairo_pdf_operators_t *pdf_operators,
+ cairo_matrix_t *matrix)
+{
+ cairo_matrix_t inverse;
+ cairo_status_t status;
+
+ /* We require the matrix to be invertable. */
+ inverse = *matrix;
+ status = cairo_matrix_invert (&inverse);
+ if (unlikely (status))
+ return status;
+
+ pdf_operators->text_matrix = *matrix;
+ pdf_operators->cur_x = 0;
+ pdf_operators->cur_y = 0;
+ pdf_operators->glyph_buf_x_pos = 0;
+ _cairo_output_stream_print_matrix (pdf_operators->stream, &pdf_operators->text_matrix);
+ _cairo_output_stream_printf (pdf_operators->stream, " Tm\n");
+
+ pdf_operators->cairo_to_pdftext = *matrix;
+ status = cairo_matrix_invert (&pdf_operators->cairo_to_pdftext);
+ assert (status == CAIRO_STATUS_SUCCESS);
+ cairo_matrix_multiply (&pdf_operators->cairo_to_pdftext,
+ &pdf_operators->cairo_to_pdf,
+ &pdf_operators->cairo_to_pdftext);
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+#define TEXT_MATRIX_TOLERANCE 1e-6
+
+/* Set the translation components of the PDF text matrix to x, y. The
+ * 'Td' operator is used to transform the text matrix.
+ */
+static cairo_status_t
+_cairo_pdf_operators_set_text_position (cairo_pdf_operators_t *pdf_operators,
+ double x,
+ double y)
+{
+ cairo_matrix_t translate, inverse;
+ cairo_status_t status;
+
+ /* The Td operator transforms the text_matrix with:
+ *
+ * text_matrix' = T x text_matrix
+ *
+ * where T is a translation matrix with the translation components
+ * set to the Td operands tx and ty.
+ */
+ inverse = pdf_operators->text_matrix;
+ status = cairo_matrix_invert (&inverse);
+ assert (status == CAIRO_STATUS_SUCCESS);
+ pdf_operators->text_matrix.x0 = x;
+ pdf_operators->text_matrix.y0 = y;
+ cairo_matrix_multiply (&translate, &pdf_operators->text_matrix, &inverse);
+ if (fabs(translate.x0) < TEXT_MATRIX_TOLERANCE)
+ translate.x0 = 0.0;
+ if (fabs(translate.y0) < TEXT_MATRIX_TOLERANCE)
+ translate.y0 = 0.0;
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%f %f Td\n",
+ translate.x0,
+ translate.y0);
+ pdf_operators->cur_x = 0;
+ pdf_operators->cur_y = 0;
+ pdf_operators->glyph_buf_x_pos = 0;
+
+ pdf_operators->cairo_to_pdftext = pdf_operators->text_matrix;
+ status = cairo_matrix_invert (&pdf_operators->cairo_to_pdftext);
+ assert (status == CAIRO_STATUS_SUCCESS);
+ cairo_matrix_multiply (&pdf_operators->cairo_to_pdftext,
+ &pdf_operators->cairo_to_pdf,
+ &pdf_operators->cairo_to_pdftext);
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+/* Select the font using the 'Tf' operator. The font size is set to 1
+ * as we use the 'Tm' operator to set the font scale.
+ */
+static cairo_status_t
+_cairo_pdf_operators_set_font_subset (cairo_pdf_operators_t *pdf_operators,
+ cairo_scaled_font_subsets_glyph_t *subset_glyph)
+{
+ cairo_status_t status;
+
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "/f-%d-%d 1 Tf\n",
+ subset_glyph->font_id,
+ subset_glyph->subset_id);
+ if (pdf_operators->use_font_subset) {
+ status = pdf_operators->use_font_subset (subset_glyph->font_id,
+ subset_glyph->subset_id,
+ pdf_operators->use_font_subset_closure);
+ if (unlikely (status))
+ return status;
+ }
+ pdf_operators->font_id = subset_glyph->font_id;
+ pdf_operators->subset_id = subset_glyph->subset_id;
+ pdf_operators->is_latin = subset_glyph->is_latin;
+
+ if (subset_glyph->is_composite)
+ pdf_operators->hex_width = 4;
+ else
+ pdf_operators->hex_width = 2;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_cairo_pdf_operators_begin_text (cairo_pdf_operators_t *pdf_operators)
+{
+ _cairo_output_stream_printf (pdf_operators->stream, "BT\n");
+
+ pdf_operators->in_text_object = TRUE;
+ pdf_operators->num_glyphs = 0;
+ pdf_operators->glyph_buf_x_pos = 0;
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+static cairo_status_t
+_cairo_pdf_operators_end_text (cairo_pdf_operators_t *pdf_operators)
+{
+ cairo_status_t status;
+
+ status = _cairo_pdf_operators_flush_glyphs (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ _cairo_output_stream_printf (pdf_operators->stream, "ET\n");
+
+ pdf_operators->in_text_object = FALSE;
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+/* Compare the scale components of two matrices. The translation
+ * components are ignored. */
+static cairo_bool_t
+_cairo_matrix_scale_equal (cairo_matrix_t *a, cairo_matrix_t *b)
+{
+ return (a->xx == b->xx &&
+ a->xy == b->xy &&
+ a->yx == b->yx &&
+ a->yy == b->yy);
+}
+
+static cairo_status_t
+_cairo_pdf_operators_begin_actualtext (cairo_pdf_operators_t *pdf_operators,
+ const char *utf8,
+ int utf8_len)
+{
+ uint16_t *utf16;
+ int utf16_len;
+ cairo_status_t status;
+ int i;
+
+ _cairo_output_stream_printf (pdf_operators->stream, "/Span << /ActualText <feff");
+ if (utf8_len) {
+ status = _cairo_utf8_to_utf16 (utf8, utf8_len, &utf16, &utf16_len);
+ if (unlikely (status))
+ return status;
+
+ for (i = 0; i < utf16_len; i++) {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "%04x", (int) (utf16[i]));
+ }
+ free (utf16);
+ }
+ _cairo_output_stream_printf (pdf_operators->stream, "> >> BDC\n");
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+static cairo_status_t
+_cairo_pdf_operators_end_actualtext (cairo_pdf_operators_t *pdf_operators)
+{
+ _cairo_output_stream_printf (pdf_operators->stream, "EMC\n");
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+static cairo_status_t
+_cairo_pdf_operators_emit_glyph (cairo_pdf_operators_t *pdf_operators,
+ cairo_glyph_t *glyph,
+ cairo_scaled_font_subsets_glyph_t *subset_glyph)
+{
+ double x, y;
+ cairo_status_t status;
+
+ if (pdf_operators->is_new_text_object ||
+ pdf_operators->font_id != subset_glyph->font_id ||
+ pdf_operators->subset_id != subset_glyph->subset_id)
+ {
+ status = _cairo_pdf_operators_flush_glyphs (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ status = _cairo_pdf_operators_set_font_subset (pdf_operators, subset_glyph);
+ if (unlikely (status))
+ return status;
+
+ pdf_operators->is_new_text_object = FALSE;
+ }
+
+ x = glyph->x;
+ y = glyph->y;
+ cairo_matrix_transform_point (&pdf_operators->cairo_to_pdftext, &x, &y);
+
+ /* The TJ operator for displaying text strings can only set
+ * the horizontal position of the glyphs. If the y position
+ * (in text space) changes, use the Td operator to change the
+ * current position to the next glyph. We also use the Td
+ * operator to move the current position if the horizontal
+ * position changes by more than 10 (in text space
+ * units). This is becauses the horizontal glyph positioning
+ * in the TJ operator is intended for kerning and there may be
+ * PDF consumers that do not handle very large position
+ * adjustments in TJ.
+ */
+ if (fabs(x - pdf_operators->glyph_buf_x_pos) > 10 ||
+ fabs(y - pdf_operators->cur_y) > GLYPH_POSITION_TOLERANCE)
+ {
+ status = _cairo_pdf_operators_flush_glyphs (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ x = glyph->x;
+ y = glyph->y;
+ cairo_matrix_transform_point (&pdf_operators->cairo_to_pdf, &x, &y);
+ status = _cairo_pdf_operators_set_text_position (pdf_operators, x, y);
+ if (unlikely (status))
+ return status;
+
+ x = 0.0;
+ y = 0.0;
+ }
+
+ status = _cairo_pdf_operators_add_glyph (pdf_operators,
+ subset_glyph,
+ x);
+ return status;
+}
+
+/* A utf8_len of -1 indicates no unicode text. A utf8_len = 0 is an
+ * empty string.
+ */
+static cairo_int_status_t
+_cairo_pdf_operators_emit_cluster (cairo_pdf_operators_t *pdf_operators,
+ const char *utf8,
+ int utf8_len,
+ cairo_glyph_t *glyphs,
+ int num_glyphs,
+ cairo_text_cluster_flags_t cluster_flags,
+ cairo_scaled_font_t *scaled_font)
+{
+ cairo_scaled_font_subsets_glyph_t subset_glyph;
+ cairo_glyph_t *cur_glyph;
+ cairo_status_t status = CAIRO_STATUS_SUCCESS;
+ int i;
+
+ /* If the cluster maps 1 glyph to 1 or more unicode characters, we
+ * first try _map_glyph() with the unicode string to see if it can
+ * use toUnicode to map our glyph to the unicode. This will fail
+ * if the glyph is already mapped to a different unicode string.
+ *
+ * We also go through this path if no unicode mapping was
+ * supplied (utf8_len < 0).
+ *
+ * Mapping a glyph to a zero length unicode string requires the
+ * use of ActualText.
+ */
+ if (num_glyphs == 1 && utf8_len != 0) {
+ status = _cairo_scaled_font_subsets_map_glyph (pdf_operators->font_subsets,
+ scaled_font,
+ glyphs->index,
+ utf8,
+ utf8_len,
+ &subset_glyph);
+ if (unlikely (status))
+ return status;
+
+ if (subset_glyph.utf8_is_mapped || utf8_len < 0) {
+ status = _cairo_pdf_operators_emit_glyph (pdf_operators,
+ glyphs,
+ &subset_glyph);
+ if (unlikely (status))
+ return status;
+
+ return CAIRO_STATUS_SUCCESS;
+ }
+ }
+
+ if (pdf_operators->use_actual_text) {
+ /* Fallback to using ActualText to map zero or more glyphs to a
+ * unicode string. */
+ status = _cairo_pdf_operators_flush_glyphs (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ status = _cairo_pdf_operators_begin_actualtext (pdf_operators, utf8, utf8_len);
+ if (unlikely (status))
+ return status;
+ }
+
+ if (cluster_flags & CAIRO_TEXT_CLUSTER_FLAG_BACKWARD)
+ cur_glyph = glyphs + num_glyphs - 1;
+ else
+ cur_glyph = glyphs;
+
+ /* XXX
+ * If no glyphs, we should put *something* here for the text to be selectable. */
+ for (i = 0; i < num_glyphs; i++) {
+ status = _cairo_scaled_font_subsets_map_glyph (pdf_operators->font_subsets,
+ scaled_font,
+ cur_glyph->index,
+ NULL, -1,
+ &subset_glyph);
+ if (unlikely (status))
+ return status;
+
+ status = _cairo_pdf_operators_emit_glyph (pdf_operators,
+ cur_glyph,
+ &subset_glyph);
+ if (unlikely (status))
+ return status;
+
+ if ((cluster_flags & CAIRO_TEXT_CLUSTER_FLAG_BACKWARD))
+ cur_glyph--;
+ else
+ cur_glyph++;
+ }
+
+ if (pdf_operators->use_actual_text) {
+ status = _cairo_pdf_operators_flush_glyphs (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ status = _cairo_pdf_operators_end_actualtext (pdf_operators);
+ }
+
+ return status;
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_show_text_glyphs (cairo_pdf_operators_t *pdf_operators,
+ 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_status_t status;
+ int i;
+ cairo_matrix_t text_matrix, invert_y_axis;
+ double x, y;
+ const char *cur_text;
+ cairo_glyph_t *cur_glyph;
+
+ pdf_operators->font_matrix_inverse = scaled_font->font_matrix;
+ status = cairo_matrix_invert (&pdf_operators->font_matrix_inverse);
+ if (status == CAIRO_STATUS_INVALID_MATRIX)
+ return CAIRO_STATUS_SUCCESS;
+ assert (status == CAIRO_STATUS_SUCCESS);
+
+ pdf_operators->is_new_text_object = FALSE;
+ if (pdf_operators->in_text_object == FALSE) {
+ status = _cairo_pdf_operators_begin_text (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ /* Force Tm and Tf to be emitted when starting a new text
+ * object.*/
+ pdf_operators->is_new_text_object = TRUE;
+ }
+
+ cairo_matrix_init_scale (&invert_y_axis, 1, -1);
+ text_matrix = scaled_font->scale;
+
+ /* Invert y axis in font space */
+ cairo_matrix_multiply (&text_matrix, &text_matrix, &invert_y_axis);
+
+ /* Invert y axis in device space */
+ cairo_matrix_multiply (&text_matrix, &invert_y_axis, &text_matrix);
+
+ if (pdf_operators->is_new_text_object ||
+ ! _cairo_matrix_scale_equal (&pdf_operators->text_matrix, &text_matrix))
+ {
+ status = _cairo_pdf_operators_flush_glyphs (pdf_operators);
+ if (unlikely (status))
+ return status;
+
+ x = glyphs[0].x;
+ y = glyphs[0].y;
+ cairo_matrix_transform_point (&pdf_operators->cairo_to_pdf, &x, &y);
+ text_matrix.x0 = x;
+ text_matrix.y0 = y;
+ status = _cairo_pdf_operators_set_text_matrix (pdf_operators, &text_matrix);
+ if (status == CAIRO_STATUS_INVALID_MATRIX)
+ return CAIRO_STATUS_SUCCESS;
+ if (unlikely (status))
+ return status;
+ }
+
+ if (num_clusters > 0) {
+ cur_text = utf8;
+ if ((cluster_flags & CAIRO_TEXT_CLUSTER_FLAG_BACKWARD))
+ cur_glyph = glyphs + num_glyphs;
+ else
+ cur_glyph = glyphs;
+ for (i = 0; i < num_clusters; i++) {
+ if ((cluster_flags & CAIRO_TEXT_CLUSTER_FLAG_BACKWARD))
+ cur_glyph -= clusters[i].num_glyphs;
+ status = _cairo_pdf_operators_emit_cluster (pdf_operators,
+ cur_text,
+ clusters[i].num_bytes,
+ cur_glyph,
+ clusters[i].num_glyphs,
+ cluster_flags,
+ scaled_font);
+ if (unlikely (status))
+ return status;
+
+ cur_text += clusters[i].num_bytes;
+ if (!(cluster_flags & CAIRO_TEXT_CLUSTER_FLAG_BACKWARD))
+ cur_glyph += clusters[i].num_glyphs;
+ }
+ } else {
+ for (i = 0; i < num_glyphs; i++) {
+ status = _cairo_pdf_operators_emit_cluster (pdf_operators,
+ NULL,
+ -1, /* no unicode string available */
+ &glyphs[i],
+ 1,
+ FALSE,
+ scaled_font);
+ if (unlikely (status))
+ return status;
+ }
+ }
+
+ return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+#endif /* CAIRO_HAS_PDF_OPERATORS */