/* -*- 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 * Carl Worth * Adrian Johnson */ #include "cairoint.h" #if CAIRO_HAS_PDF_OPERATORS #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) { pdf_operators->stream = stream; pdf_operators->cairo_to_pdf = *cairo_to_pdf; pdf_operators->font_subsets = font_subsets; 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 struct _word_wrap_stream { cairo_output_stream_t base; cairo_output_stream_t *output; int max_column; int column; cairo_bool_t last_write_was_space; cairo_bool_t in_hexstring; cairo_bool_t empty_hexstring; } word_wrap_stream_t; static int _count_word_up_to (const unsigned char *s, int length) { int word = 0; while (length--) { if (! (_cairo_isspace (*s) || *s == '<')) { s++; word++; } else { return word; } } return word; } /* Count up to either the end of the ASCII hexstring or the number * of columns remaining. */ static int _count_hexstring_up_to (const unsigned char *s, int length, int columns) { int word = 0; while (length--) { if (*s++ != '>') word++; else return word; columns--; if (columns < 0 && word > 1) return word; } return word; } 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; cairo_bool_t newline; int word; while (length) { if (*data == '<') { stream->in_hexstring = TRUE; stream->empty_hexstring = TRUE; stream->last_write_was_space = FALSE; data++; length--; _cairo_output_stream_printf (stream->output, "<"); stream->column++; } else if (*data == '>') { stream->in_hexstring = FALSE; stream->last_write_was_space = FALSE; data++; length--; _cairo_output_stream_printf (stream->output, ">"); stream->column++; } else if (_cairo_isspace (*data)) { newline = (*data == '\n' || *data == '\r'); if (! newline && stream->column >= stream->max_column) { _cairo_output_stream_printf (stream->output, "\n"); stream->column = 0; } _cairo_output_stream_write (stream->output, data, 1); data++; length--; if (newline) { stream->column = 0; } else stream->column++; stream->last_write_was_space = TRUE; } else { if (stream->in_hexstring) { word = _count_hexstring_up_to (data, length, MAX (stream->max_column - stream->column, 0)); } else { word = _count_word_up_to (data, length); } /* Don't wrap if this word is a continuation of a non hex * string word from a previous call to write. */ if (stream->column + word >= stream->max_column) { if (stream->last_write_was_space || (stream->in_hexstring && !stream->empty_hexstring)) { _cairo_output_stream_printf (stream->output, "\n"); stream->column = 0; } } _cairo_output_stream_write (stream->output, data, word); data += word; length -= word; stream->column += word; stream->last_write_was_space = FALSE; if (stream->in_hexstring) stream->empty_hexstring = FALSE; } } 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, 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->column = 0; stream->last_write_was_space = FALSE; stream->in_hexstring = FALSE; stream->empty_hexstring = TRUE; 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, 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, 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)) { status = _cairo_pdf_path_rectangle (&info, &box); } else { status = _cairo_path_fixed_interpret (path, CAIRO_DIRECTION_FORWARD, _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, cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule) { const char *pdf_operator; cairo_status_t 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) { case CAIRO_FILL_RULE_WINDING: pdf_operator = "W"; break; case CAIRO_FILL_RULE_EVEN_ODD: pdf_operator = "W*"; break; default: ASSERT_NOT_REACHED; } _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, 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, cairo_path_fixed_t *path, cairo_stroke_style_t *style, cairo_matrix_t *ctm, cairo_matrix_t *ctm_inverse, const char *pdf_operator) { cairo_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 %f %f %f %f %f %f cm\n", m.xx, m.yx, m.xy, m.yy, m.x0, m.y0); } 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, cairo_path_fixed_t *path, cairo_stroke_style_t *style, cairo_matrix_t *ctm, 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, 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) { case CAIRO_FILL_RULE_WINDING: pdf_operator = "f"; break; case CAIRO_FILL_RULE_EVEN_ODD: pdf_operator = "f*"; break; default: ASSERT_NOT_REACHED; } _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, cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, cairo_stroke_style_t *style, cairo_matrix_t *ctm, cairo_matrix_t *ctm_inverse) { const char *operator; switch (fill_rule) { case CAIRO_FILL_RULE_WINDING: operator = "B"; break; case CAIRO_FILL_RULE_EVEN_ODD: operator = "B*"; break; default: ASSERT_NOT_REACHED; } return _cairo_pdf_operators_emit_stroke (pdf_operators, path, style, ctm, ctm_inverse, operator); } #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, "<"); for (i = 0; i < pdf_operators->num_glyphs; i++) { _cairo_output_stream_printf (stream, "%0*x", pdf_operators->hex_width, pdf_operators->glyphs[i].glyph_index); pdf_operators->cur_x += pdf_operators->glyphs[i].x_advance; } _cairo_output_stream_printf (stream, ">Tj\n"); 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, "[<"); 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 (rounded_delta != 0) { _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_output_stream_printf (stream, "%0*x", pdf_operators->hex_width, pdf_operators->glyphs[i].glyph_index); pdf_operators->cur_x += pdf_operators->glyphs[i].x_advance; } _cairo_output_stream_printf (stream, ">]TJ\n"); 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, 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; 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->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; _cairo_output_stream_printf (pdf_operators->stream, "%f %f %f %f %f %f Tm\n", pdf_operators->text_matrix.xx, pdf_operators->text_matrix.yx, pdf_operators->text_matrix.xy, pdf_operators->text_matrix.yy, pdf_operators->text_matrix.x0, pdf_operators->text_matrix.y0); 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->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; 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; 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 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->cur_x) > 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; 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; } } /* 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; 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++; } 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; if (unlikely (status)) return status; 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 */