diff options
author | Adrian Johnson <ajohnson@redneon.com> | 2013-12-07 15:48:26 +1030 |
---|---|---|
committer | Adrian Johnson <ajohnson@redneon.com> | 2013-12-07 15:54:49 +1030 |
commit | dcbe16eb40b488f89f2398181f4c3f8a65f84b52 (patch) | |
tree | c35a532c5cfa7707f62ae31e6a4e98194ebedb53 | |
parent | 31eff5c6eb57ad379689748fd8c60a5ffe0ba481 (diff) |
pdf/ps: avoid outputting excess decimal places in matrices
Sometimes as a result of rounding errors in matrix transformations the
matrices in ps/pdf output look like:
0.000000000000000061 1 1 -0.000000000000000061 0 842 cm
This patch rounds to zero matrix elements that are very small compared to
other elements in the same matrix.
-rw-r--r-- | src/cairo-output-stream-private.h | 201 | ||||
-rw-r--r-- | src/cairo-output-stream.c | 805 | ||||
-rw-r--r-- | src/cairo-pdf-operators.c | 1550 | ||||
-rw-r--r-- | src/cairo-pdf-surface.c | 44 | ||||
-rw-r--r-- | src/cairo-ps-surface.c | 40 |
5 files changed, 2591 insertions, 49 deletions
diff --git a/src/cairo-output-stream-private.h b/src/cairo-output-stream-private.h index edaabbe..38a137f 100644 --- a/src/cairo-output-stream-private.h +++ b/src/cairo-output-stream-private.h @@ -0,0 +1,201 @@ +/* cairo - a vector graphics library with display and print output + * + * Copyright © 2006 Red Hat, Inc + * + * 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 Red Hat, Inc. + * + * Author(s): + * Kristian Høgsberg <krh@redhat.com> + */ + +#ifndef CAIRO_OUTPUT_STREAM_PRIVATE_H +#define CAIRO_OUTPUT_STREAM_PRIVATE_H + +#include "cairo-compiler-private.h" +#include "cairo-types-private.h" + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> + +typedef cairo_status_t +(*cairo_output_stream_write_func_t) (cairo_output_stream_t *output_stream, + const unsigned char *data, + unsigned int length); + +typedef cairo_status_t +(*cairo_output_stream_flush_func_t) (cairo_output_stream_t *output_stream); + +typedef cairo_status_t +(*cairo_output_stream_close_func_t) (cairo_output_stream_t *output_stream); + +struct _cairo_output_stream { + cairo_output_stream_write_func_t write_func; + cairo_output_stream_flush_func_t flush_func; + cairo_output_stream_close_func_t close_func; + unsigned long position; + cairo_status_t status; + cairo_bool_t closed; +}; + +extern const cairo_private cairo_output_stream_t _cairo_output_stream_nil; + +cairo_private void +_cairo_output_stream_init (cairo_output_stream_t *stream, + cairo_output_stream_write_func_t write_func, + cairo_output_stream_flush_func_t flush_func, + cairo_output_stream_close_func_t close_func); + +cairo_private cairo_status_t +_cairo_output_stream_fini (cairo_output_stream_t *stream); + + +/* We already have the following declared in cairo.h: + +typedef cairo_status_t (*cairo_write_func_t) (void *closure, + const unsigned char *data, + unsigned int length); +*/ +typedef cairo_status_t (*cairo_close_func_t) (void *closure); + + +/* This function never returns %NULL. If an error occurs (NO_MEMORY) + * while trying to create the output stream this function returns a + * valid pointer to a nil output stream. + * + * Note that even with a nil surface, the close_func callback will be + * called by a call to _cairo_output_stream_close or + * _cairo_output_stream_destroy. + */ +cairo_private cairo_output_stream_t * +_cairo_output_stream_create (cairo_write_func_t write_func, + cairo_close_func_t close_func, + void *closure); + +cairo_private cairo_output_stream_t * +_cairo_output_stream_create_in_error (cairo_status_t status); + +/* Tries to flush any buffer maintained by the stream or its delegates. */ +cairo_private cairo_status_t +_cairo_output_stream_flush (cairo_output_stream_t *stream); + +/* Returns the final status value associated with this object, just + * before its last gasp. This final status value will capture any + * status failure returned by the stream's close_func as well. */ +cairo_private cairo_status_t +_cairo_output_stream_close (cairo_output_stream_t *stream); + +/* Returns the final status value associated with this object, just + * before its last gasp. This final status value will capture any + * status failure returned by the stream's close_func as well. */ +cairo_private cairo_status_t +_cairo_output_stream_destroy (cairo_output_stream_t *stream); + +cairo_private void +_cairo_output_stream_write (cairo_output_stream_t *stream, + const void *data, size_t length); + +cairo_private void +_cairo_output_stream_write_hex_string (cairo_output_stream_t *stream, + const unsigned char *data, + size_t length); + +cairo_private void +_cairo_output_stream_vprintf (cairo_output_stream_t *stream, + const char *fmt, + va_list ap) CAIRO_PRINTF_FORMAT ( 2, 0); + +cairo_private void +_cairo_output_stream_printf (cairo_output_stream_t *stream, + const char *fmt, + ...) CAIRO_PRINTF_FORMAT (2, 3); + +/* Print matrix element values with rounding of insignificant digits. */ +void +_cairo_output_stream_print_matrix (cairo_output_stream_t *stream, + const cairo_matrix_t *matrix); + +cairo_private long +_cairo_output_stream_get_position (cairo_output_stream_t *stream); + +cairo_private cairo_status_t +_cairo_output_stream_get_status (cairo_output_stream_t *stream); + +/* This function never returns %NULL. If an error occurs (NO_MEMORY or + * WRITE_ERROR) while trying to create the output stream this function + * returns a valid pointer to a nil output stream. + * + * Note: Even if a nil surface is returned, the caller should still + * call _cairo_output_stream_destroy (or _cairo_output_stream_close at + * least) in order to ensure that everything is properly cleaned up. + */ +cairo_private cairo_output_stream_t * +_cairo_output_stream_create_for_filename (const char *filename); + +/* This function never returns %NULL. If an error occurs (NO_MEMORY or + * WRITE_ERROR) while trying to create the output stream this function + * returns a valid pointer to a nil output stream. + * + * The caller still "owns" file and is responsible for calling fclose + * on it when finished. The stream will not do this itself. + */ +cairo_private cairo_output_stream_t * +_cairo_output_stream_create_for_file (FILE *file); + +cairo_private cairo_output_stream_t * +_cairo_memory_stream_create (void); + +cairo_private void +_cairo_memory_stream_copy (cairo_output_stream_t *base, + cairo_output_stream_t *dest); + +cairo_private int +_cairo_memory_stream_length (cairo_output_stream_t *stream); + +cairo_private cairo_status_t +_cairo_memory_stream_destroy (cairo_output_stream_t *abstract_stream, + unsigned char **data_out, + unsigned long *length_out); + +cairo_private cairo_output_stream_t * +_cairo_null_stream_create (void); + +/* cairo-base85-stream.c */ +cairo_private cairo_output_stream_t * +_cairo_base85_stream_create (cairo_output_stream_t *output); + +/* cairo-base64-stream.c */ +cairo_private cairo_output_stream_t * +_cairo_base64_stream_create (cairo_output_stream_t *output); + +/* cairo-deflate-stream.c */ +cairo_private cairo_output_stream_t * +_cairo_deflate_stream_create (cairo_output_stream_t *output); + + +#endif /* CAIRO_OUTPUT_STREAM_PRIVATE_H */ diff --git a/src/cairo-output-stream.c b/src/cairo-output-stream.c index cc7e300..6d6c180 100644 --- a/src/cairo-output-stream.c +++ b/src/cairo-output-stream.c @@ -0,0 +1,805 @@ +/* cairo-output-stream.c: Output stream abstraction + * + * Copyright © 2005 Red Hat, Inc + * + * 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 Red Hat, Inc. + * + * Author(s): + * Kristian Høgsberg <krh@redhat.com> + */ + +#define _BSD_SOURCE /* for snprintf() */ +#include "cairoint.h" + +#include "cairo-output-stream-private.h" + +#include "cairo-array-private.h" +#include "cairo-error-private.h" +#include "cairo-compiler-private.h" + +#include <stdio.h> +#include <locale.h> +#include <errno.h> + +/* Numbers printed with %f are printed with this number of significant + * digits after the decimal. + */ +#define SIGNIFICANT_DIGITS_AFTER_DECIMAL 6 + +/* Numbers printed with %g are assumed to only have %CAIRO_FIXED_FRAC_BITS + * bits of precision available after the decimal point. + * + * FIXED_POINT_DECIMAL_DIGITS specifies the minimum number of decimal + * digits after the decimal point required to preserve the available + * precision. + * + * The conversion is: + * + * <programlisting> + * FIXED_POINT_DECIMAL_DIGITS = ceil( CAIRO_FIXED_FRAC_BITS * ln(2)/ln(10) ) + * </programlisting> + * + * We can replace ceil(x) with (int)(x+1) since x will never be an + * integer for any likely value of %CAIRO_FIXED_FRAC_BITS. + */ +#define FIXED_POINT_DECIMAL_DIGITS ((int)(CAIRO_FIXED_FRAC_BITS*0.301029996 + 1)) + +void +_cairo_output_stream_init (cairo_output_stream_t *stream, + cairo_output_stream_write_func_t write_func, + cairo_output_stream_flush_func_t flush_func, + cairo_output_stream_close_func_t close_func) +{ + stream->write_func = write_func; + stream->flush_func = flush_func; + stream->close_func = close_func; + stream->position = 0; + stream->status = CAIRO_STATUS_SUCCESS; + stream->closed = FALSE; +} + +cairo_status_t +_cairo_output_stream_fini (cairo_output_stream_t *stream) +{ + return _cairo_output_stream_close (stream); +} + +const cairo_output_stream_t _cairo_output_stream_nil = { + NULL, /* write_func */ + NULL, /* flush_func */ + NULL, /* close_func */ + 0, /* position */ + CAIRO_STATUS_NO_MEMORY, + FALSE /* closed */ +}; + +static const cairo_output_stream_t _cairo_output_stream_nil_write_error = { + NULL, /* write_func */ + NULL, /* flush_func */ + NULL, /* close_func */ + 0, /* position */ + CAIRO_STATUS_WRITE_ERROR, + FALSE /* closed */ +}; + +typedef struct _cairo_output_stream_with_closure { + cairo_output_stream_t base; + cairo_write_func_t write_func; + cairo_close_func_t close_func; + void *closure; +} cairo_output_stream_with_closure_t; + + +static cairo_status_t +closure_write (cairo_output_stream_t *stream, + const unsigned char *data, unsigned int length) +{ + cairo_output_stream_with_closure_t *stream_with_closure = + (cairo_output_stream_with_closure_t *) stream; + + if (stream_with_closure->write_func == NULL) + return CAIRO_STATUS_SUCCESS; + + return stream_with_closure->write_func (stream_with_closure->closure, + data, length); +} + +static cairo_status_t +closure_close (cairo_output_stream_t *stream) +{ + cairo_output_stream_with_closure_t *stream_with_closure = + (cairo_output_stream_with_closure_t *) stream; + + if (stream_with_closure->close_func != NULL) + return stream_with_closure->close_func (stream_with_closure->closure); + else + return CAIRO_STATUS_SUCCESS; +} + +cairo_output_stream_t * +_cairo_output_stream_create (cairo_write_func_t write_func, + cairo_close_func_t close_func, + void *closure) +{ + cairo_output_stream_with_closure_t *stream; + + stream = malloc (sizeof (cairo_output_stream_with_closure_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, + closure_write, NULL, closure_close); + stream->write_func = write_func; + stream->close_func = close_func; + stream->closure = closure; + + return &stream->base; +} + +cairo_output_stream_t * +_cairo_output_stream_create_in_error (cairo_status_t status) +{ + cairo_output_stream_t *stream; + + /* check for the common ones */ + if (status == CAIRO_STATUS_NO_MEMORY) + return (cairo_output_stream_t *) &_cairo_output_stream_nil; + if (status == CAIRO_STATUS_WRITE_ERROR) + return (cairo_output_stream_t *) &_cairo_output_stream_nil_write_error; + + stream = malloc (sizeof (cairo_output_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, NULL, NULL, NULL); + stream->status = status; + + return stream; +} + +cairo_status_t +_cairo_output_stream_flush (cairo_output_stream_t *stream) +{ + cairo_status_t status; + + if (stream->closed) + return stream->status; + + if (stream == &_cairo_output_stream_nil || + stream == &_cairo_output_stream_nil_write_error) + { + return stream->status; + } + + if (stream->flush_func) { + status = stream->flush_func (stream); + /* Don't overwrite a pre-existing status failure. */ + if (stream->status == CAIRO_STATUS_SUCCESS) + stream->status = status; + } + + return stream->status; +} + +cairo_status_t +_cairo_output_stream_close (cairo_output_stream_t *stream) +{ + cairo_status_t status; + + if (stream->closed) + return stream->status; + + if (stream == &_cairo_output_stream_nil || + stream == &_cairo_output_stream_nil_write_error) + { + return stream->status; + } + + if (stream->close_func) { + status = stream->close_func (stream); + /* Don't overwrite a pre-existing status failure. */ + if (stream->status == CAIRO_STATUS_SUCCESS) + stream->status = status; + } + + stream->closed = TRUE; + + return stream->status; +} + +cairo_status_t +_cairo_output_stream_destroy (cairo_output_stream_t *stream) +{ + cairo_status_t status; + + assert (stream != NULL); + + if (stream == &_cairo_output_stream_nil || + stream == &_cairo_output_stream_nil_write_error) + { + return stream->status; + } + + status = _cairo_output_stream_fini (stream); + free (stream); + + return status; +} + +void +_cairo_output_stream_write (cairo_output_stream_t *stream, + const void *data, size_t length) +{ + if (length == 0) + return; + + if (stream->status) + return; + + stream->status = stream->write_func (stream, data, length); + stream->position += length; +} + +void +_cairo_output_stream_write_hex_string (cairo_output_stream_t *stream, + const unsigned char *data, + size_t length) +{ + const char hex_chars[] = "0123456789abcdef"; + char buffer[2]; + unsigned int i, column; + + if (stream->status) + return; + + for (i = 0, column = 0; i < length; i++, column++) { + if (column == 38) { + _cairo_output_stream_write (stream, "\n", 1); + column = 0; + } + buffer[0] = hex_chars[(data[i] >> 4) & 0x0f]; + buffer[1] = hex_chars[data[i] & 0x0f]; + _cairo_output_stream_write (stream, buffer, 2); + } +} + +/* Format a double in a locale independent way and trim trailing + * zeros. Based on code from Alex Larson <alexl@redhat.com>. + * http://mail.gnome.org/archives/gtk-devel-list/2001-October/msg00087.html + * + * The code in the patch is copyright Red Hat, Inc under the LGPL, but + * has been relicensed under the LGPL/MPL dual license for inclusion + * into cairo (see COPYING). -- Kristian Høgsberg <krh@redhat.com> + */ +static void +_cairo_dtostr (char *buffer, size_t size, double d, cairo_bool_t limited_precision) +{ + struct lconv *locale_data; + const char *decimal_point; + int decimal_point_len; + char *p; + int decimal_len; + int num_zeros, decimal_digits; + + /* Omit the minus sign from negative zero. */ + if (d == 0.0) + d = 0.0; + + locale_data = localeconv (); + decimal_point = locale_data->decimal_point; + decimal_point_len = strlen (decimal_point); + + assert (decimal_point_len != 0); + + if (limited_precision) { + snprintf (buffer, size, "%.*f", FIXED_POINT_DECIMAL_DIGITS, d); + } else { + /* Using "%f" to print numbers less than 0.1 will result in + * reduced precision due to the default 6 digits after the + * decimal point. + * + * For numbers is < 0.1, we print with maximum precision and count + * the number of zeros between the decimal point and the first + * significant digit. We then print the number again with the + * number of decimal places that gives us the required number of + * significant digits. This ensures the number is correctly + * rounded. + */ + if (fabs (d) >= 0.1) { + snprintf (buffer, size, "%f", d); + } else { + snprintf (buffer, size, "%.18f", d); + p = buffer; + + if (*p == '+' || *p == '-') + p++; + + while (_cairo_isdigit (*p)) + p++; + + if (strncmp (p, decimal_point, decimal_point_len) == 0) + p += decimal_point_len; + + num_zeros = 0; + while (*p++ == '0') + num_zeros++; + + decimal_digits = num_zeros + SIGNIFICANT_DIGITS_AFTER_DECIMAL; + + if (decimal_digits < 18) + snprintf (buffer, size, "%.*f", decimal_digits, d); + } + } + p = buffer; + + if (*p == '+' || *p == '-') + p++; + + while (_cairo_isdigit (*p)) + p++; + + if (strncmp (p, decimal_point, decimal_point_len) == 0) { + *p = '.'; + decimal_len = strlen (p + decimal_point_len); + memmove (p + 1, p + decimal_point_len, decimal_len); + p[1 + decimal_len] = 0; + + /* Remove trailing zeros and decimal point if possible. */ + for (p = p + decimal_len; *p == '0'; p--) + *p = 0; + + if (*p == '.') { + *p = 0; + p--; + } + } +} + +enum { + LENGTH_MODIFIER_LONG = 0x100 +}; + +/* Here's a limited reimplementation of printf. The reason for doing + * this is primarily to special case handling of doubles. We want + * locale independent formatting of doubles and we want to trim + * trailing zeros. This is handled by dtostr() above, and the code + * below handles everything else by calling snprintf() to do the + * formatting. This functionality is only for internal use and we + * only implement the formats we actually use. + */ +void +_cairo_output_stream_vprintf (cairo_output_stream_t *stream, + const char *fmt, va_list ap) +{ +#define SINGLE_FMT_BUFFER_SIZE 32 + char buffer[512], single_fmt[SINGLE_FMT_BUFFER_SIZE]; + int single_fmt_length; + char *p; + const char *f, *start; + int length_modifier, width; + cairo_bool_t var_width; + + if (stream->status) + return; + + f = fmt; + p = buffer; + while (*f != '\0') { + if (p == buffer + sizeof (buffer)) { + _cairo_output_stream_write (stream, buffer, sizeof (buffer)); + p = buffer; + } + + if (*f != '%') { + *p++ = *f++; + continue; + } + + start = f; + f++; + + if (*f == '0') + f++; + + var_width = FALSE; + if (*f == '*') { + var_width = TRUE; + f++; + } + + while (_cairo_isdigit (*f)) + f++; + + length_modifier = 0; + if (*f == 'l') { + length_modifier = LENGTH_MODIFIER_LONG; + f++; + } + + /* The only format strings exist in the cairo implementation + * itself. So there's an internal consistency problem if any + * of them is larger than our format buffer size. */ + single_fmt_length = f - start + 1; + assert (single_fmt_length + 1 <= SINGLE_FMT_BUFFER_SIZE); + + /* Reuse the format string for this conversion. */ + memcpy (single_fmt, start, single_fmt_length); + single_fmt[single_fmt_length] = '\0'; + + /* Flush contents of buffer before snprintf()'ing into it. */ + _cairo_output_stream_write (stream, buffer, p - buffer); + + /* We group signed and unsigned together in this switch, the + * only thing that matters here is the size of the arguments, + * since we're just passing the data through to sprintf(). */ + switch (*f | length_modifier) { + case '%': + buffer[0] = *f; + buffer[1] = 0; + break; + case 'd': + case 'u': + case 'o': + case 'x': + case 'X': + if (var_width) { + width = va_arg (ap, int); + snprintf (buffer, sizeof buffer, + single_fmt, width, va_arg (ap, int)); + } else { + snprintf (buffer, sizeof buffer, single_fmt, va_arg (ap, int)); + } + break; + case 'd' | LENGTH_MODIFIER_LONG: + case 'u' | LENGTH_MODIFIER_LONG: + case 'o' | LENGTH_MODIFIER_LONG: + case 'x' | LENGTH_MODIFIER_LONG: + case 'X' | LENGTH_MODIFIER_LONG: + if (var_width) { + width = va_arg (ap, int); + snprintf (buffer, sizeof buffer, + single_fmt, width, va_arg (ap, long int)); + } else { + snprintf (buffer, sizeof buffer, + single_fmt, va_arg (ap, long int)); + } + break; + case 's': + snprintf (buffer, sizeof buffer, + single_fmt, va_arg (ap, const char *)); + break; + case 'f': + _cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), FALSE); + break; + case 'g': + _cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), TRUE); + break; + case 'c': + buffer[0] = va_arg (ap, int); + buffer[1] = 0; + break; + default: + ASSERT_NOT_REACHED; + } + p = buffer + strlen (buffer); + f++; + } + + _cairo_output_stream_write (stream, buffer, p - buffer); +} + +void +_cairo_output_stream_printf (cairo_output_stream_t *stream, + const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + + _cairo_output_stream_vprintf (stream, fmt, ap); + + va_end (ap); +} + +/* Matrix elements that are smaller than the value of the largest element * MATRIX_ROUNDING_TOLERANCE + * are rounded down to zero. */ +#define MATRIX_ROUNDING_TOLERANCE 1e-12 + +void +_cairo_output_stream_print_matrix (cairo_output_stream_t *stream, + const cairo_matrix_t *matrix) +{ + cairo_matrix_t m; + double s, e; + + m = *matrix; + 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); + + e = s * MATRIX_ROUNDING_TOLERANCE; + if (fabs(m.xx) < e) + m.xx = 0; + if (fabs(m.xy) < e) + m.xy = 0; + if (fabs(m.yx) < e) + m.yx = 0; + if (fabs(m.yy) < e) + m.yy = 0; + if (fabs(m.x0) < e) + m.x0 = 0; + if (fabs(m.y0) < e) + m.y0 = 0; + + _cairo_output_stream_printf (stream, + "%f %f %f %f %f %f", + m.xx, m.yx, m.xy, m.yy, m.x0, m.y0); +} + +long +_cairo_output_stream_get_position (cairo_output_stream_t *stream) +{ + return stream->position; +} + +cairo_status_t +_cairo_output_stream_get_status (cairo_output_stream_t *stream) +{ + return stream->status; +} + +/* Maybe this should be a configure time option, so embedded targets + * don't have to pull in stdio. */ + + +typedef struct _stdio_stream { + cairo_output_stream_t base; + FILE *file; +} stdio_stream_t; + +static cairo_status_t +stdio_write (cairo_output_stream_t *base, + const unsigned char *data, unsigned int length) +{ + stdio_stream_t *stream = (stdio_stream_t *) base; + + if (fwrite (data, 1, length, stream->file) != length) + return _cairo_error (CAIRO_STATUS_WRITE_ERROR); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +stdio_flush (cairo_output_stream_t *base) +{ + stdio_stream_t *stream = (stdio_stream_t *) base; + + fflush (stream->file); + + if (ferror (stream->file)) + return _cairo_error (CAIRO_STATUS_WRITE_ERROR); + else + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +stdio_close (cairo_output_stream_t *base) +{ + cairo_status_t status; + stdio_stream_t *stream = (stdio_stream_t *) base; + + status = stdio_flush (base); + + fclose (stream->file); + + return status; +} + +cairo_output_stream_t * +_cairo_output_stream_create_for_file (FILE *file) +{ + stdio_stream_t *stream; + + if (file == NULL) { + _cairo_error_throw (CAIRO_STATUS_WRITE_ERROR); + return (cairo_output_stream_t *) &_cairo_output_stream_nil_write_error; + } + + stream = malloc (sizeof *stream); + 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, + stdio_write, stdio_flush, stdio_flush); + stream->file = file; + + return &stream->base; +} + +cairo_output_stream_t * +_cairo_output_stream_create_for_filename (const char *filename) +{ + stdio_stream_t *stream; + FILE *file; + + if (filename == NULL) + return _cairo_null_stream_create (); + + file = fopen (filename, "wb"); + if (file == NULL) { + switch (errno) { + case ENOMEM: + _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); + return (cairo_output_stream_t *) &_cairo_output_stream_nil; + default: + _cairo_error_throw (CAIRO_STATUS_WRITE_ERROR); + return (cairo_output_stream_t *) &_cairo_output_stream_nil_write_error; + } + } + + stream = malloc (sizeof *stream); + if (unlikely (stream == NULL)) { + fclose (file); + _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); + return (cairo_output_stream_t *) &_cairo_output_stream_nil; + } + + _cairo_output_stream_init (&stream->base, + stdio_write, stdio_flush, stdio_close); + stream->file = file; + + return &stream->base; +} + + +typedef struct _memory_stream { + cairo_output_stream_t base; + cairo_array_t array; +} memory_stream_t; + +static cairo_status_t +memory_write (cairo_output_stream_t *base, + const unsigned char *data, unsigned int length) +{ + memory_stream_t *stream = (memory_stream_t *) base; + + return _cairo_array_append_multiple (&stream->array, data, length); +} + +static cairo_status_t +memory_close (cairo_output_stream_t *base) +{ + memory_stream_t *stream = (memory_stream_t *) base; + + _cairo_array_fini (&stream->array); + + return CAIRO_STATUS_SUCCESS; +} + +cairo_output_stream_t * +_cairo_memory_stream_create (void) +{ + memory_stream_t *stream; + + stream = malloc (sizeof *stream); + 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, memory_write, NULL, memory_close); + _cairo_array_init (&stream->array, 1); + + return &stream->base; +} + +cairo_status_t +_cairo_memory_stream_destroy (cairo_output_stream_t *abstract_stream, + unsigned char **data_out, + unsigned long *length_out) +{ + memory_stream_t *stream; + cairo_status_t status; + + status = abstract_stream->status; + if (unlikely (status)) + return _cairo_output_stream_destroy (abstract_stream); + + stream = (memory_stream_t *) abstract_stream; + + *length_out = _cairo_array_num_elements (&stream->array); + *data_out = malloc (*length_out); + if (unlikely (*data_out == NULL)) { + status = _cairo_output_stream_destroy (abstract_stream); + assert (status == CAIRO_STATUS_SUCCESS); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + memcpy (*data_out, _cairo_array_index (&stream->array, 0), *length_out); + + return _cairo_output_stream_destroy (abstract_stream); +} + +void +_cairo_memory_stream_copy (cairo_output_stream_t *base, + cairo_output_stream_t *dest) +{ + memory_stream_t *stream = (memory_stream_t *) base; + + if (dest->status) + return; + + if (base->status) { + dest->status = base->status; + return; + } + + _cairo_output_stream_write (dest, + _cairo_array_index (&stream->array, 0), + _cairo_array_num_elements (&stream->array)); +} + +int +_cairo_memory_stream_length (cairo_output_stream_t *base) +{ + memory_stream_t *stream = (memory_stream_t *) base; + + return _cairo_array_num_elements (&stream->array); +} + +static cairo_status_t +null_write (cairo_output_stream_t *base, + const unsigned char *data, unsigned int length) +{ + return CAIRO_STATUS_SUCCESS; +} + +cairo_output_stream_t * +_cairo_null_stream_create (void) +{ + cairo_output_stream_t *stream; + + stream = malloc (sizeof *stream); + 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, null_write, NULL, NULL); + + return stream; +} diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c index fceaf1c..84d2441 100644 --- a/src/cairo-pdf-operators.c +++ b/src/cairo-pdf-operators.c @@ -0,0 +1,1550 @@ +/* -*- 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) +{ + 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 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; + 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->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, 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->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, 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_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, 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; + } + + 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 */ diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c index a8a1217..c200c28 100644 --- a/src/cairo-pdf-surface.c +++ b/src/cairo-pdf-surface.c @@ -3843,11 +3843,11 @@ _cairo_pdf_surface_output_gradient (cairo_pdf_surface_t *surface, _cairo_output_stream_printf (surface->output, "<< /Type /Pattern\n" " /PatternType 2\n" - " /Matrix [ %f %f %f %f %f %f ]\n" - " /Shading\n", - pat_to_pdf->xx, pat_to_pdf->yx, - pat_to_pdf->xy, pat_to_pdf->yy, - pat_to_pdf->x0, pat_to_pdf->y0); + " /Matrix [ "); + _cairo_output_stream_print_matrix (surface->output, pat_to_pdf); + _cairo_output_stream_printf (surface->output, + " ]\n" + " /Shading\n"); } if (pdf_pattern->pattern->type == CAIRO_PATTERN_TYPE_LINEAR) { @@ -4105,14 +4105,14 @@ _cairo_pdf_surface_emit_mesh_pattern (cairo_pdf_surface_t *surface, "%d 0 obj\n" "<< /Type /Pattern\n" " /PatternType 2\n" - " /Matrix [ %f %f %f %f %f %f ]\n" + " /Matrix [ ", + pdf_pattern->pattern_res.id); + _cairo_output_stream_print_matrix (surface->output, &pat_to_pdf); + _cairo_output_stream_printf (surface->output, + " ]\n" " /Shading %d 0 R\n" ">>\n" "endobj\n", - pdf_pattern->pattern_res.id, - pat_to_pdf.xx, pat_to_pdf.yx, - pat_to_pdf.xy, pat_to_pdf.yy, - pat_to_pdf.x0, pat_to_pdf.y0, res.id); if (pdf_pattern->gstate_res.id != 0) { @@ -4166,14 +4166,14 @@ _cairo_pdf_surface_emit_mesh_pattern (cairo_pdf_surface_t *surface, "%d 0 obj\n" "<< /Type /Pattern\n" " /PatternType 2\n" - " /Matrix [ %f %f %f %f %f %f ]\n" + " /Matrix [ ", + mask_resource.id); + _cairo_output_stream_print_matrix (surface->output, &pat_to_pdf); + _cairo_output_stream_printf (surface->output, + " ]\n" " /Shading %d 0 R\n" ">>\n" "endobj\n", - mask_resource.id, - pat_to_pdf.xx, pat_to_pdf.yx, - pat_to_pdf.xy, pat_to_pdf.yy, - pat_to_pdf.x0, pat_to_pdf.y0, res.id); status = cairo_pdf_surface_emit_transparency_group (surface, @@ -4302,11 +4302,8 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface, return status; if (! _cairo_matrix_is_identity (&pdf_p2d)) { - _cairo_output_stream_printf (surface->output, - "%f %f %f %f %f %f cm\n", - pdf_p2d.xx, pdf_p2d.yx, - pdf_p2d.xy, pdf_p2d.yy, - pdf_p2d.x0, pdf_p2d.y0); + _cairo_output_stream_print_matrix (surface->output, &pdf_p2d); + _cairo_output_stream_printf (surface->output, " cm\n"); } status = _cairo_pdf_surface_add_alpha (surface, 1.0, &alpha); @@ -4357,11 +4354,8 @@ _cairo_pdf_surface_paint_gradient (cairo_pdf_surface_t *surface, return status; if (! _cairo_matrix_is_identity (&pat_to_pdf)) { - _cairo_output_stream_printf (surface->output, - "%f %f %f %f %f %f cm\n", - pat_to_pdf.xx, pat_to_pdf.yx, - pat_to_pdf.xy, pat_to_pdf.yy, - pat_to_pdf.x0, pat_to_pdf.y0); + _cairo_output_stream_print_matrix (surface->output, &pat_to_pdf); + _cairo_output_stream_printf (surface->output, " cm\n"); } status = _cairo_pdf_surface_add_shading (surface, shading_res); diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c index dfab4f7..f4ae3a8 100644 --- a/src/cairo-ps-surface.c +++ b/src/cairo-ps-surface.c @@ -3251,11 +3251,9 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t *surface, cairo_matrix_scale (&ps_p2d, 1.0, -1.0); if (! _cairo_matrix_is_identity (&ps_p2d)) { - _cairo_output_stream_printf (surface->stream, - "[ %f %f %f %f %f %f ] concat\n", - ps_p2d.xx, ps_p2d.yx, - ps_p2d.xy, ps_p2d.yy, - ps_p2d.x0, ps_p2d.y0); + _cairo_output_stream_printf (surface->stream, "[ "); + _cairo_output_stream_print_matrix (surface->stream, &ps_p2d); + _cairo_output_stream_printf (surface->stream, " ] concat\n"); } status = _cairo_ps_surface_emit_surface (surface, @@ -3444,12 +3442,10 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t *surface, cairo_matrix_translate (&ps_p2d, 0.0, pattern_height); cairo_matrix_scale (&ps_p2d, 1.0, -1.0); + _cairo_output_stream_printf (surface->stream, "[ "); + _cairo_output_stream_print_matrix (surface->stream, &ps_p2d); _cairo_output_stream_printf (surface->stream, - "[ %f %f %f %f %f %f ]\n", - ps_p2d.xx, ps_p2d.yx, - ps_p2d.xy, ps_p2d.yy, - ps_p2d.x0, ps_p2d.y0); - _cairo_output_stream_printf (surface->stream, + " ]\n" "makepattern setpattern\n"); release_source: @@ -3823,11 +3819,10 @@ _cairo_ps_surface_emit_gradient (cairo_ps_surface_t *surface, if (is_ps_pattern) { _cairo_output_stream_printf (surface->stream, ">>\n" - "[ %f %f %f %f %f %f ]\n" - "makepattern setpattern\n", - pat_to_ps.xx, pat_to_ps.yx, - pat_to_ps.xy, pat_to_ps.yy, - pat_to_ps.x0, pat_to_ps.y0); + "[ "); + _cairo_output_stream_print_matrix (surface->stream, &pat_to_ps); + _cairo_output_stream_printf (surface->stream, " ]\n" + "makepattern setpattern\n"); } else { _cairo_output_stream_printf (surface->stream, "shfill\n"); @@ -3905,11 +3900,10 @@ _cairo_ps_surface_emit_mesh_pattern (cairo_ps_surface_t *surface, if (is_ps_pattern) { _cairo_output_stream_printf (surface->stream, ">>\n" - "[ %f %f %f %f %f %f ]\n", - pat_to_ps.xx, pat_to_ps.yx, - pat_to_ps.xy, pat_to_ps.yy, - pat_to_ps.x0, pat_to_ps.y0); + "[ \n"); + _cairo_output_stream_print_matrix (surface->stream, &pat_to_ps); _cairo_output_stream_printf (surface->stream, + " ]\n" "makepattern\n" "setpattern\n"); } else { @@ -4008,11 +4002,9 @@ _cairo_ps_surface_paint_gradient (cairo_ps_surface_t *surface, cairo_matrix_multiply (&pat_to_ps, &pat_to_ps, &surface->cairo_to_ps); if (! _cairo_matrix_is_identity (&pat_to_ps)) { - _cairo_output_stream_printf (surface->stream, - "[%f %f %f %f %f %f] concat\n", - pat_to_ps.xx, pat_to_ps.yx, - pat_to_ps.xy, pat_to_ps.yy, - pat_to_ps.x0, pat_to_ps.y0); + _cairo_output_stream_printf (surface->stream, "["); + _cairo_output_stream_print_matrix (surface->stream, &pat_to_ps); + _cairo_output_stream_printf (surface->stream, "] concat\n"); } if (source->type == CAIRO_PATTERN_TYPE_MESH) { |