Logo Search packages:      
Sourcecode: cairo version File versions  Download package

cairo-pdf-operators.c

/* -*- 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 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 (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,
                          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_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,
                       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,
                     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,
                          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);
}

#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 <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->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;
    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 */

Generated by  Doxygen 1.6.0   Back to index