CairoOutputDev.cc 102 KB
Newer Older
Kristian Høgsberg's avatar
Kristian Høgsberg committed
1 2 3 4 5 6 7 8
//========================================================================
//
// CairoOutputDev.cc
//
// Copyright 2003 Glyph & Cog, LLC
// Copyright 2004 Red Hat, Inc
//
//========================================================================
9 10 11 12 13

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
14 15 16
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
17 18
// Copyright (C) 2005-2008 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
19
// Copyright (C) 2005, 2009, 2012, 2017, 2018 Albert Astals Cid <aacid@kde.org>
20
// Copyright (C) 2005 Nickolay V. Shmyrev <nshmyrev@yandex.ru>
Albert Astals Cid's avatar
Albert Astals Cid committed
21
// Copyright (C) 2006-2011, 2013, 2014, 2017, 2018 Carlos Garcia Campos <carlosgc@gnome.org>
22
// Copyright (C) 2008 Carl Worth <cworth@cworth.org>
Albert Astals Cid's avatar
Albert Astals Cid committed
23
// Copyright (C) 2008-2017 Adrian Johnson <ajohnson@redneon.com>
24
// Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
Albert Astals Cid's avatar
Albert Astals Cid committed
25
// Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk>
26
// Copyright (C) 2008, 2012 Hib Eris <hib@hiberis.nl>
27
// Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu>
28
// Copyright (C) 2011-2014 Thomas Freitag <Thomas.Freitag@alfa.de>
Albert Astals Cid's avatar
Albert Astals Cid committed
29
// Copyright (C) 2012 Patrick Pfeifer <p2000@mailinator.com>
Albert Astals Cid's avatar
Albert Astals Cid committed
30
// Copyright (C) 2012, 2015, 2016 Jason Crain <jason@aquaticape.us>
Albert Astals Cid's avatar
Albert Astals Cid committed
31
// Copyright (C) 2015 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
32
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
33 34 35 36 37
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
Kristian Høgsberg's avatar
Kristian Høgsberg committed
38 39 40 41 42 43 44

#include <config.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

45
#include <cstdint>
Kristian Høgsberg's avatar
Kristian Høgsberg committed
46 47
#include <string.h>
#include <math.h>
48
#include <assert.h>
Kristian Høgsberg's avatar
Kristian Høgsberg committed
49 50 51 52 53 54
#include <cairo.h>

#include "goo/gfile.h"
#include "GlobalParams.h"
#include "Error.h"
#include "Object.h"
55
#include "Gfx.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
56 57
#include "GfxState.h"
#include "GfxFont.h"
58
#include "Page.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
59 60
#include "Link.h"
#include "FontEncodingTables.h"
61
#include "PDFDocEncoding.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
62 63 64 65
#include <fofi/FoFiTrueType.h>
#include <splash/SplashBitmap.h>
#include "CairoOutputDev.h"
#include "CairoFontEngine.h"
66
#include "CairoRescaleBox.h"
67
#include "UnicodeMap.h"
68
#include "JBIG2Stream.h"
Kristian Høgsberg's avatar
Kristian Høgsberg committed
69 70
//------------------------------------------------------------------------

71
// #define LOG_CAIRO
Kristian Høgsberg's avatar
Kristian Høgsberg committed
72

73 74 75 76 77
// To limit memory usage and improve performance when printing, limit
// cairo images to this size. 8192 is sufficient for an A2 sized
// 300ppi image.
#define MAX_PRINT_IMAGE_SIZE 8192

Kristian Høgsberg's avatar
Kristian Høgsberg committed
78 79 80 81 82 83
#ifdef LOG_CAIRO
#define LOG(x) (x)
#else
#define LOG(x)
#endif

84 85 86 87 88 89
static inline void printMatrix(cairo_matrix_t *matrix){
	printf("%f %f, %f %f (%f %f)\n", matrix->xx, matrix->yx,
			matrix->xy, matrix->yy,
			matrix->x0, matrix->y0);
}

Adrian Johnson's avatar
Adrian Johnson committed
90 91 92 93 94

#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define MAX(a,b) (((a) > (b)) ? (a) : (b))


95 96 97 98
//------------------------------------------------------------------------
// CairoImage
//------------------------------------------------------------------------

99
CairoImage::CairoImage (double x1, double y1, double x2, double y2) {
100
  this->image = nullptr;
101 102 103 104 105 106 107 108 109 110
  this->x1 = x1;
  this->y1 = y1;
  this->x2 = x2;
  this->y2 = y2;
}

CairoImage::~CairoImage () {
  if (image)
    cairo_surface_destroy (image);
}
Kristian Høgsberg's avatar
Kristian Høgsberg committed
111

112 113 114 115 116 117
void CairoImage::setImage (cairo_surface_t *image) {
  if (this->image)
    cairo_surface_destroy (this->image);
  this->image = cairo_surface_reference (image);
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
118 119 120 121
//------------------------------------------------------------------------
// CairoOutputDev
//------------------------------------------------------------------------

122 123 124 125 126 127 128 129 130
// We cannot tie the lifetime of an FT_Library object to that of
// CairoOutputDev, since any FT_Faces created with it may end up with a
// reference by Cairo which can be held long after the CairoOutputDev is
// deleted.  The simplest way to avoid problems is to never tear down the
// FT_Library instance; to avoid leaks, just use a single global instance
// initialized the first time it is needed.
FT_Library CairoOutputDev::ft_lib;
GBool CairoOutputDev::ft_lib_initialized = gFalse;

131
CairoOutputDev::CairoOutputDev() {
132
  doc = nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
133

134 135 136 137 138
  if (!ft_lib_initialized) {
    FT_Init_FreeType(&ft_lib);
    ft_lib_initialized = gTrue;
  }

139
  fontEngine = nullptr;
140
  fontEngine_owner = gFalse;
141 142
  glyphs = nullptr;
  fill_pattern = nullptr;
143
  fill_color.r = fill_color.g = fill_color.b = 0;
144
  stroke_pattern = nullptr;
145
  stroke_color.r = stroke_color.g = stroke_color.b = 0;
146 147
  stroke_opacity = 1.0;
  fill_opacity = 1.0;
148 149 150 151
  textClipPath = nullptr;
  strokePathClip = nullptr;
  cairo = nullptr;
  currentFont = nullptr;
152 153 154
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
  prescaleImages = gFalse;
#else
155
  prescaleImages = gTrue;
156
#endif
157
  printing = gTrue;
158
  use_show_text_glyphs = gFalse;
159
  inUncoloredPattern = gFalse;
160
  inType3Char = gFalse;
161
  t3_glyph_has_bbox = gFalse;
162
  text_matrix_valid = gTrue;
163
  antialias = CAIRO_ANTIALIAS_DEFAULT;
164

165 166 167 168 169 170
  groupColorSpaceStack = nullptr;
  maskStack = nullptr;
  group = nullptr;
  mask = nullptr;
  shape = nullptr;
  cairo_shape = nullptr;
171
  knockoutCount = 0;
172

173 174
  text = nullptr;
  actualText = nullptr;
175 176 177

  // the SA parameter supposedly defaults to false, but Acrobat
  // apparently hardwires it to true
178
  stroke_adjust = gTrue;
179 180
  align_stroke_coords = gFalse;
  adjusted_stroke_width = gFalse;
181
  xref = nullptr;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
182 183 184
}

CairoOutputDev::~CairoOutputDev() {
185
  if (fontEngine_owner && fontEngine) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
186 187
    delete fontEngine;
  }
188

189 190
  if (cairo)
    cairo_destroy (cairo);
191 192
  cairo_pattern_destroy (stroke_pattern);
  cairo_pattern_destroy (fill_pattern);
193 194
  if (group)
    cairo_pattern_destroy (group);
Chris Wilson's avatar
Chris Wilson committed
195 196
  if (mask)
    cairo_pattern_destroy (mask);
197 198
  if (shape)
    cairo_pattern_destroy (shape);
199
  if (text) 
200 201 202
    text->decRefCnt();
  if (actualText)
    delete actualText;  
203
}
Kristian Høgsberg's avatar
Kristian Høgsberg committed
204

205
void CairoOutputDev::setCairo(cairo_t *cairo)
206
{
207
  if (this->cairo != nullptr) {
208 209
    cairo_status_t status = cairo_status (this->cairo);
    if (status) {
210
      error(errInternal, -1, "cairo context error: {0:s}\n", cairo_status_to_string(status));
211
    }
212
    cairo_destroy (this->cairo);
213
    assert(!cairo_shape);
214
  }
215
  if (cairo != nullptr) {
216
    this->cairo = cairo_reference (cairo);
217 218 219
	/* save the initial matrix so that we can use it for type3 fonts. */
	//XXX: is this sufficient? could we miss changes to the matrix somehow?
	cairo_get_matrix(cairo, &orig_matrix);
220
	setContextAntialias(cairo, antialias);
221
  } else {
222 223
    this->cairo = nullptr;
    this->cairo_shape = nullptr;
224
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
225 226
}

227 228 229 230
void CairoOutputDev::setTextPage(TextPage *text)
{
  if (this->text) 
    this->text->decRefCnt();
231 232
  if (actualText)
    delete actualText;
233 234 235
  if (text) {
    this->text = text;
    this->text->incRefCnt();
236
    actualText = new ActualText(text);
237
  } else {
238 239
    this->text = nullptr;
    actualText = nullptr;
240 241 242
  }
}

243 244 245 246
void CairoOutputDev::setAntialias(cairo_antialias_t antialias)
{
  this->antialias = antialias;
  if (cairo)
247
    setContextAntialias (cairo, antialias);
248
  if (cairo_shape)
249
    setContextAntialias (cairo_shape, antialias);
250 251
}

252
void CairoOutputDev::setContextAntialias(cairo_t *cr, cairo_antialias_t antialias)
253 254
{
  cairo_font_options_t *font_options;
255
  cairo_set_antialias (cr, antialias);
256 257 258 259 260 261 262
  font_options = cairo_font_options_create ();
  cairo_get_font_options (cr, font_options);
  cairo_font_options_set_antialias (font_options, antialias);
  cairo_set_font_options (cr, font_options);
  cairo_font_options_destroy (font_options);
}

263
void CairoOutputDev::startDoc(PDFDoc *docA,
264
			      CairoFontEngine *parentFontEngine) {
265
  doc = docA;
266 267 268 269 270 271 272 273
  if (parentFontEngine) {
    fontEngine = parentFontEngine;
  } else {
    if (fontEngine) {
      delete fontEngine;
    }
    fontEngine = new CairoFontEngine(ft_lib);
    fontEngine_owner = gTrue;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
274
  }
Thomas Freitag's avatar
Thomas Freitag committed
275
  xref = doc->getXRef();
Kristian Høgsberg's avatar
Kristian Høgsberg committed
276 277
}

Thomas Freitag's avatar
Thomas Freitag committed
278
void CairoOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) {
279 280 281
  /* set up some per page defaults */
  cairo_pattern_destroy(fill_pattern);
  cairo_pattern_destroy(stroke_pattern);
282 283

  fill_pattern = cairo_pattern_create_rgb(0., 0., 0.);
284
  fill_color.r = fill_color.g = fill_color.b = 0;
285
  stroke_pattern = cairo_pattern_reference(fill_pattern);
286
  stroke_color.r = stroke_color.g = stroke_color.b = 0;
287 288 289

  if (text)
    text->startPage(state);
290
  if (xrefA != nullptr) {
Thomas Freitag's avatar
Thomas Freitag committed
291 292
    xref = xrefA;
  }
293 294 295 296 297
}

void CairoOutputDev::endPage() {
  if (text) {
    text->endPage();
298
    text->coalesce(gTrue, 0, gFalse);
299
  }
300 301
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
302 303 304
void CairoOutputDev::saveState(GfxState *state) {
  LOG(printf ("save\n"));
  cairo_save (cairo);
305 306
  if (cairo_shape)
      cairo_save (cairo_shape);
307 308 309

  MaskStack *ms = new MaskStack;
  ms->mask = cairo_pattern_reference(mask);
Adrian Johnson's avatar
Adrian Johnson committed
310
  ms->mask_matrix = mask_matrix;
311 312
  ms->next = maskStack;
  maskStack = ms;
313 314 315

  if (strokePathClip)
    strokePathClip->ref_count++;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
316 317 318 319 320
}

void CairoOutputDev::restoreState(GfxState *state) {
  LOG(printf ("restore\n"));
  cairo_restore (cairo);
321 322
  if (cairo_shape)
      cairo_restore (cairo_shape);
323

324 325
  text_matrix_valid = gTrue;

326 327 328 329 330 331
  /* These aren't restored by cairo_restore() since we keep them in
   * the output device. */
  updateFillColor(state);
  updateStrokeColor(state);
  updateFillOpacity(state);
  updateStrokeOpacity(state);
332
  updateBlendMode(state);
333 334

  MaskStack* ms = maskStack;
335
  if (ms) {
336 337
    if (mask)
      cairo_pattern_destroy(mask);
338
    mask = ms->mask;
Adrian Johnson's avatar
Adrian Johnson committed
339
    mask_matrix = ms->mask_matrix;
340 341 342
    maskStack = ms->next;
    delete ms;
  }
343 344 345 346 347 348

  if (strokePathClip && --strokePathClip->ref_count == 0) {
    delete strokePathClip->path;
    if (strokePathClip->dashes)
      gfree (strokePathClip->dashes);
    gfree (strokePathClip);
349
    strokePathClip = nullptr;
350
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
351 352 353 354 355 356 357 358 359 360 361
}

void CairoOutputDev::updateAll(GfxState *state) {
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);
  updateFlatness(state);
  updateMiterLimit(state);
  updateFillColor(state);
  updateStrokeColor(state);
362 363
  updateFillOpacity(state);
  updateStrokeOpacity(state);
364
  updateBlendMode(state);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
365
  needFontUpdate = gTrue;
366 367
  if (text)
    text->updateFont(state);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
368 369
}

370 371 372 373 374 375 376 377 378
void CairoOutputDev::setDefaultCTM(double *ctm) {
  cairo_matrix_t matrix;
  matrix.xx = ctm[0];
  matrix.yx = ctm[1];
  matrix.xy = ctm[2];
  matrix.yy = ctm[3];
  matrix.x0 = ctm[4];
  matrix.y0 = ctm[5];

379
  cairo_transform (cairo, &matrix);
380
  if (cairo_shape)
381
      cairo_transform (cairo_shape, &matrix);
382 383 384 385

  OutputDev::setDefaultCTM(ctm);
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
386 387 388
void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12,
				double m21, double m22,
				double m31, double m32) {
389
  cairo_matrix_t matrix, invert_matrix;
390 391 392 393 394 395 396
  matrix.xx = m11;
  matrix.yx = m12;
  matrix.xy = m21;
  matrix.yy = m22;
  matrix.x0 = m31;
  matrix.y0 = m32;

397 398 399 400 401 402 403 404
  /* Make sure the matrix is invertible before setting it.
   * cairo will blow up if we give it a matrix that's not
   * invertible, so we need to check before passing it
   * to cairo_transform. Ignoring it is likely to give better
   * results than not rendering anything at all. See #14398
   *
   * Ideally, we could do the cairo_transform
   * and then check if anything went wrong and fix it then
405 406 407
   * instead of having to invert the matrix. */
  invert_matrix = matrix;
  if (cairo_matrix_invert(&invert_matrix)) {
408
    error(errSyntaxWarning, -1, "matrix not invertible\n");
409 410 411
    return;
  }

412
  cairo_transform (cairo, &matrix);
413 414
  if (cairo_shape)
    cairo_transform (cairo_shape, &matrix);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
415 416 417 418 419 420 421 422 423 424 425 426
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);
}

void CairoOutputDev::updateLineDash(GfxState *state) {
  double *dashPattern;
  int dashLength;
  double dashStart;

  state->getLineDash(&dashPattern, &dashLength, &dashStart);
427
  cairo_set_dash (cairo, dashPattern, dashLength, dashStart);
428 429
  if (cairo_shape)
    cairo_set_dash (cairo_shape, dashPattern, dashLength, dashStart);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
430 431 432
}

void CairoOutputDev::updateFlatness(GfxState *state) {
433
  // cairo_set_tolerance (cairo, state->getFlatness());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447
}

void CairoOutputDev::updateLineJoin(GfxState *state) {
  switch (state->getLineJoin()) {
  case 0:
    cairo_set_line_join (cairo, CAIRO_LINE_JOIN_MITER);
    break;
  case 1:
    cairo_set_line_join (cairo, CAIRO_LINE_JOIN_ROUND);
    break;
  case 2:
    cairo_set_line_join (cairo, CAIRO_LINE_JOIN_BEVEL);
    break;
  }
448 449
  if (cairo_shape)
    cairo_set_line_join (cairo_shape, cairo_get_line_join(cairo));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
450 451 452 453 454 455 456 457 458 459 460 461 462 463
}

void CairoOutputDev::updateLineCap(GfxState *state) {
  switch (state->getLineCap()) {
  case 0:
    cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT);
    break;
  case 1:
    cairo_set_line_cap (cairo, CAIRO_LINE_CAP_ROUND);
    break;
  case 2:
    cairo_set_line_cap (cairo, CAIRO_LINE_CAP_SQUARE);
    break;
  }
464 465
  if (cairo_shape)
    cairo_set_line_cap (cairo_shape, cairo_get_line_cap(cairo));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
466 467 468 469
}

void CairoOutputDev::updateMiterLimit(GfxState *state) {
  cairo_set_miter_limit (cairo, state->getMiterLimit());
470 471
  if (cairo_shape)
    cairo_set_miter_limit (cairo_shape, state->getMiterLimit());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
472 473 474
}

void CairoOutputDev::updateLineWidth(GfxState *state) {
475
  LOG(printf ("line width: %f\n", state->getLineWidth()));
476
  adjusted_stroke_width = gFalse;
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
  double width = state->getLineWidth();
  if (stroke_adjust && !printing) {
    double x, y;
    x = y = width;

    /* find out line width in device units */
    cairo_user_to_device_distance(cairo, &x, &y);
    if (fabs(x) <= 1.0 && fabs(y) <= 1.0) {
      /* adjust width to at least one device pixel */
      x = y = 1.0;
      cairo_device_to_user_distance(cairo, &x, &y);
      width = MIN(fabs(x),fabs(y));
      adjusted_stroke_width = gTrue;
    }
  } else if (width == 0.0) {
    /* Cairo does not support 0 line width == 1 device pixel. Find out
     * how big pixels (device unit) are in the x and y
     * directions. Choose the smaller of the two as our line width.
     */
496
    double x = 1.0, y = 1.0;
497 498 499 500 501
    if (printing) {
      // assume printer pixel size is 1/600 inch
      x = 72.0/600;
      y = 72.0/600;
    }
502
    cairo_device_to_user_distance(cairo, &x, &y);
503
    width = MIN(fabs(x),fabs(y));
504
  }
505
  cairo_set_line_width (cairo, width);
506 507
  if (cairo_shape)
    cairo_set_line_width (cairo_shape, cairo_get_line_width (cairo));
Kristian Høgsberg's avatar
Kristian Høgsberg committed
508 509 510
}

void CairoOutputDev::updateFillColor(GfxState *state) {
511
  GfxRGB color = fill_color;
512

513 514 515
  if (inUncoloredPattern)
    return;

516
  state->getFillRGB(&fill_color);
517 518
  if (cairo_pattern_get_type (fill_pattern) != CAIRO_PATTERN_TYPE_SOLID ||
      color.r != fill_color.r ||
519 520 521 522
      color.g != fill_color.g ||
      color.b != fill_color.b)
  {
    cairo_pattern_destroy(fill_pattern);
523 524 525
    fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r),
					     colToDbl(fill_color.g),
					     colToDbl(fill_color.b),
526 527 528 529 530
					     fill_opacity);

    LOG(printf ("fill color: %d %d %d\n",
		fill_color.r, fill_color.g, fill_color.b));
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
531 532 533
}

void CairoOutputDev::updateStrokeColor(GfxState *state) {
534
  GfxRGB color = stroke_color;
535

536 537 538
  if (inUncoloredPattern)
    return;

539
  state->getStrokeRGB(&stroke_color);
540 541
  if (cairo_pattern_get_type (fill_pattern) != CAIRO_PATTERN_TYPE_SOLID ||
      color.r != stroke_color.r ||
542 543 544 545
      color.g != stroke_color.g ||
      color.b != stroke_color.b)
  {
    cairo_pattern_destroy(stroke_pattern);
546 547 548
    stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r),
					       colToDbl(stroke_color.g),
					       colToDbl(stroke_color.b),
549 550 551 552 553
					       stroke_opacity);

    LOG(printf ("stroke color: %d %d %d\n",
		stroke_color.r, stroke_color.g, stroke_color.b));
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
554 555
}

556
void CairoOutputDev::updateFillOpacity(GfxState *state) {
557
  double opacity = fill_opacity;
558

559 560 561
  if (inUncoloredPattern)
    return;

562 563 564
  fill_opacity = state->getFillOpacity();
  if (opacity != fill_opacity) {
    cairo_pattern_destroy(fill_pattern);
565 566 567
    fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r),
					     colToDbl(fill_color.g),
					     colToDbl(fill_color.b),
568 569 570 571
					     fill_opacity);

    LOG(printf ("fill opacity: %f\n", fill_opacity));
  }
572 573 574
}

void CairoOutputDev::updateStrokeOpacity(GfxState *state) {
575
  double opacity = stroke_opacity;
576

577 578 579
  if (inUncoloredPattern)
    return;

580 581 582
  stroke_opacity = state->getStrokeOpacity();
  if (opacity != stroke_opacity) {
    cairo_pattern_destroy(stroke_pattern);
583 584 585
    stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r),
					       colToDbl(stroke_color.g),
					       colToDbl(stroke_color.b),
586 587 588 589
					       stroke_opacity);

    LOG(printf ("stroke opacity: %f\n", stroke_opacity));
  }
590 591
}

592
void CairoOutputDev::updateFillColorStop(GfxState *state, double offset) {
593 594 595
  if (inUncoloredPattern)
    return;

596 597 598
  state->getFillRGB(&fill_color);

  cairo_pattern_add_color_stop_rgba(fill_pattern, offset,
599 600 601
				    colToDbl(fill_color.r),
				    colToDbl(fill_color.g),
				    colToDbl(fill_color.b),
602 603 604 605 606
				    fill_opacity);
  LOG(printf ("fill color stop: %f (%d, %d, %d)\n",
	      offset, fill_color.r, fill_color.g, fill_color.b));
}

607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
void CairoOutputDev::updateBlendMode(GfxState *state) {
  switch (state->getBlendMode()) {
  default:
  case gfxBlendNormal:
    cairo_set_operator (cairo, CAIRO_OPERATOR_OVER);
    break;
  case gfxBlendMultiply:
    cairo_set_operator (cairo, CAIRO_OPERATOR_MULTIPLY);
    break;
  case gfxBlendScreen:
    cairo_set_operator (cairo, CAIRO_OPERATOR_SCREEN);
    break;
  case gfxBlendOverlay:
    cairo_set_operator (cairo, CAIRO_OPERATOR_OVERLAY);
    break;
  case gfxBlendDarken:
    cairo_set_operator (cairo, CAIRO_OPERATOR_DARKEN);
    break;
  case gfxBlendLighten:
    cairo_set_operator (cairo, CAIRO_OPERATOR_LIGHTEN);
    break;
  case gfxBlendColorDodge:
    cairo_set_operator (cairo, CAIRO_OPERATOR_COLOR_DODGE);
    break;
  case gfxBlendColorBurn:
    cairo_set_operator (cairo, CAIRO_OPERATOR_COLOR_BURN);
    break;
  case gfxBlendHardLight:
    cairo_set_operator (cairo, CAIRO_OPERATOR_HARD_LIGHT);
    break;
  case gfxBlendSoftLight:
    cairo_set_operator (cairo, CAIRO_OPERATOR_SOFT_LIGHT);
    break;
  case gfxBlendDifference:
    cairo_set_operator (cairo, CAIRO_OPERATOR_DIFFERENCE);
    break;
  case gfxBlendExclusion:
    cairo_set_operator (cairo, CAIRO_OPERATOR_EXCLUSION);
    break;
  case gfxBlendHue:
    cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_HUE);
    break;
  case gfxBlendSaturation:
    cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_SATURATION);
    break;
  case gfxBlendColor:
    cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_COLOR);
    break;
  case gfxBlendLuminosity:
    cairo_set_operator (cairo, CAIRO_OPERATOR_HSL_LUMINOSITY);
    break;
  }
  LOG(printf ("blend mode: %d\n", (int)state->getBlendMode()));
}

Kristian Høgsberg's avatar
Kristian Høgsberg committed
662
void CairoOutputDev::updateFont(GfxState *state) {
663
  cairo_font_face_t *font_face;
664
  cairo_matrix_t matrix, invert_matrix;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
665 666

  LOG(printf ("updateFont() font=%s\n", state->getFont()->getName()->getCString()));
667

Kristian Høgsberg's avatar
Kristian Høgsberg committed
668 669
  needFontUpdate = gFalse;

670 671 672 673
  //FIXME: use cairo font engine?
  if (text)
    text->updateFont(state);
  
Thomas Freitag's avatar
Thomas Freitag committed
674
  currentFont = fontEngine->getFont (state->getFont(), doc, printing, xref);
675

676 677
  if (!currentFont)
    return;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
678

679 680
  font_face = currentFont->getFontFace();
  cairo_set_font_face (cairo, font_face);
681 682 683

  use_show_text_glyphs = state->getFont()->hasToUnicodeCMap() &&
    cairo_surface_has_show_text_glyphs (cairo_get_target (cairo));
684 685 686
 
  double fontSize = state->getFontSize();
  double *m = state->getTextMat();
687 688 689 690 691 692
  /* NOTE: adjusting by a constant is hack. The correct solution
   * is probably to use user-fonts and compute the scale on a per
   * glyph basis instead of for the entire font */
  double w = currentFont->getSubstitutionCorrection(state->getFont());
  matrix.xx = m[0] * fontSize * state->getHorizScaling() * w;
  matrix.yx = m[1] * fontSize * state->getHorizScaling() * w;
693 694
  matrix.xy = -m[2] * fontSize;
  matrix.yy = -m[3] * fontSize;
695 696
  matrix.x0 = 0;
  matrix.y0 = 0;
697

698 699
  LOG(printf ("font matrix: %f %f %f %f\n", matrix.xx, matrix.yx, matrix.xy, matrix.yy));

700 701 702 703 704 705 706 707
 /* Make sure the font matrix is invertible before setting it.  cairo
  * will blow up if we give it a matrix that's not invertible, so we
  * need to check before passing it to cairo_set_font_matrix. Ignoring it
  * is likely to give better results than not rendering anything at
  * all. See #18254.
  */
  invert_matrix = matrix;
  if (cairo_matrix_invert(&invert_matrix)) {
708 709
    error(errSyntaxWarning, -1, "font matrix not invertible");
    text_matrix_valid = gFalse;
710 711 712
    return;
  }

713
  cairo_set_font_matrix (cairo, &matrix);
714
  text_matrix_valid = gTrue;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
715 716
}

717 718 719 720 721 722 723
/* Tolerance in pixels for checking if strokes are horizontal or vertical
 * lines in device space */
#define STROKE_COORD_TOLERANCE 0.5

/* Align stroke coordinate i if the point is the start or end of a
 * horizontal or vertical line */
void CairoOutputDev::alignStrokeCoords(GfxSubpath *subpath, int i, double *x, double *y)
724
{
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
  double x1, y1, x2, y2;
  GBool align = gFalse;

  x1 = subpath->getX(i);
  y1 = subpath->getY(i);
  cairo_user_to_device (cairo, &x1, &y1);

  // Does the current coord and prev coord form a horiz or vert line?
  if (i > 0 && !subpath->getCurve(i - 1)) {
    x2 = subpath->getX(i - 1);
    y2 = subpath->getY(i - 1);
    cairo_user_to_device (cairo, &x2, &y2);
    if (fabs(x2 - x1) < STROKE_COORD_TOLERANCE || fabs(y2 - y1) < STROKE_COORD_TOLERANCE)
      align = gTrue;
  }

  // Does the current coord and next coord form a horiz or vert line?
  if (i < subpath->getNumPoints() - 1 && !subpath->getCurve(i + 1)) {
    x2 = subpath->getX(i + 1);
    y2 = subpath->getY(i + 1);
    cairo_user_to_device (cairo, &x2, &y2);
    if (fabs(x2 - x1) < STROKE_COORD_TOLERANCE || fabs(y2 - y1) < STROKE_COORD_TOLERANCE)
      align = gTrue;
  }

  *x = subpath->getX(i);
  *y = subpath->getY(i);
  if (align) {
    /* see http://www.cairographics.org/FAQ/#sharp_lines */
    cairo_user_to_device (cairo, x, y);
    *x = floor(*x) + 0.5;
    *y = floor(*y) + 0.5;
    cairo_device_to_user (cairo, x, y);
  }
759 760
}

761 762
#undef STROKE_COORD_TOLERANCE

763
void CairoOutputDev::doPath(cairo_t *cairo, GfxState *state, GfxPath *path) {
Kristian Høgsberg's avatar
Kristian Høgsberg committed
764 765
  GfxSubpath *subpath;
  int i, j;
766
  double x, y;
767
  cairo_new_path (cairo);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
768 769 770
  for (i = 0; i < path->getNumSubpaths(); ++i) {
    subpath = path->getSubpath(i);
    if (subpath->getNumPoints() > 0) {
771
      if (align_stroke_coords) {
772
        alignStrokeCoords(subpath, 0, &x, &y);
773
      } else {
774 775
        x = subpath->getX(0);
        y = subpath->getY(0);
776
      }
777
      cairo_move_to (cairo, x, y);
778
      j = 1;
Kristian Høgsberg's avatar
Kristian Høgsberg committed
779 780
      while (j < subpath->getNumPoints()) {
	if (subpath->getCurve(j)) {
781 782 783 784 785 786
	  if (align_stroke_coords) {
            alignStrokeCoords(subpath, j + 2, &x, &y);
          } else {
            x = subpath->getX(j+2);
            y = subpath->getY(j+2);
          }
787 788 789
	  cairo_curve_to( cairo,
			  subpath->getX(j), subpath->getY(j),
			  subpath->getX(j+1), subpath->getY(j+1),
790
			  x, y);
791

Kristian Høgsberg's avatar
Kristian Høgsberg committed
792 793
	  j += 3;
	} else {
794
	  if (align_stroke_coords) {
795 796 797 798 799 800
            alignStrokeCoords(subpath, j, &x, &y);
          } else {
            x = subpath->getX(j);
            y = subpath->getY(j);
          }
          cairo_line_to (cairo, x, y);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
801 802 803 804 805 806 807 808 809 810 811 812
	  ++j;
	}
      }
      if (subpath->isClosed()) {
	LOG (printf ("close\n"));
	cairo_close_path (cairo);
      }
    }
  }
}

void CairoOutputDev::stroke(GfxState *state) {
813 814 815 816 817 818 819
  if (inType3Char) {
      GfxGray gray;
      state->getFillGray(&gray);
      if (colToDbl(gray) > 0.5)
	  return;
  }

820 821
  if (adjusted_stroke_width)
    align_stroke_coords = gTrue;
822
  doPath (cairo, state, state->getPath());
823
  align_stroke_coords = gFalse;
824
  cairo_set_source (cairo, stroke_pattern);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
825
  LOG(printf ("stroke\n"));
826 827 828 829 830 831 832 833
  if (strokePathClip) {
    cairo_push_group (cairo);
    cairo_stroke (cairo);
    cairo_pop_group_to_source (cairo);
    fillToStrokePathClip (state);
  } else {
    cairo_stroke (cairo);
  }
834 835 836 837
  if (cairo_shape) {
    doPath (cairo_shape, state, state->getPath());
    cairo_stroke (cairo_shape);
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
838 839 840
}

void CairoOutputDev::fill(GfxState *state) {
841 842 843 844 845 846 847
  if (inType3Char) {
      GfxGray gray;
      state->getFillGray(&gray);
      if (colToDbl(gray) > 0.5)
	  return;
  }

848
  doPath (cairo, state, state->getPath());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
849
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING);
850
  cairo_set_source (cairo, fill_pattern);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
851
  LOG(printf ("fill\n"));
852
  //XXX: how do we get the path
853
  if (mask) {
Adrian Johnson's avatar
Adrian Johnson committed
854
    cairo_save (cairo);
855
    cairo_clip (cairo);
856 857 858 859 860
    if (strokePathClip) {
      cairo_push_group (cairo);
      fillToStrokePathClip (state);
      cairo_pop_group_to_source (cairo);
    }
Adrian Johnson's avatar
Adrian Johnson committed
861
    cairo_set_matrix (cairo, &mask_matrix);
862
    cairo_mask (cairo, mask);
Adrian Johnson's avatar
Adrian Johnson committed
863
    cairo_restore (cairo);
Adrian Johnson's avatar
Adrian Johnson committed
864
  } else if (strokePathClip) {
865
    fillToStrokePathClip(state);
866 867 868
  } else {
    cairo_fill (cairo);
  }
869 870 871 872 873
  if (cairo_shape) {
    cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_WINDING);
    doPath (cairo_shape, state, state->getPath());
    cairo_fill (cairo_shape);
  }
Kristian Høgsberg's avatar
Kristian Høgsberg committed
874 875 876
}

void CairoOutputDev::eoFill(GfxState *state) {
877
  doPath (cairo, state, state->getPath());
Kristian Høgsberg's avatar
Kristian Høgsberg committed
878
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
879
  cairo_set_source (cairo, fill_pattern);
Kristian Høgsberg's avatar
Kristian Høgsberg committed
880
  LOG(printf ("fill-eo\n"));
881

882 883 884 885 886 887 888 889 890
  if (mask) {
    cairo_save (cairo);
    cairo_clip (cairo);
    cairo_set_matrix (cairo, &mask_matrix);
    cairo_mask (cairo, mask);
    cairo_restore (cairo);
  } else {
    cairo_fill (cairo);
  }
891 892 893 894 895 896
  if (cairo_shape) {
    cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_EVEN_ODD);
    doPath (cairo_shape, state, state->getPath());
    cairo_fill (cairo_shape);
  }

Kristian Høgsberg's avatar
Kristian Høgsberg committed
897 898
}

Thomas Freitag's avatar
Thomas Freitag committed
899
GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat, Object *str,
900
					double *pmat, int paintType, int /*tilingType*/, Dict *resDict,
901 902 903 904 905 906 907 908 909
					double *mat, double *bbox,
					int x0, int y0, int x1, int y1,
					double xStep, double yStep)
{
  PDFRectangle box;
  Gfx *gfx;
  cairo_pattern_t *pattern;
  cairo_surface_t *surface;
  cairo_matrix_t matrix;
910
  cairo_matrix_t pattern_matrix;
911 912
  cairo_t *old_cairo;
  double xMin, yMin, xMax, yMax;
913
  double width, height;
914
  double scaleX, scaleY;
915
  int surface_width, surface_height;
Adrian Johnson's avatar
Adrian Johnson committed
916
  StrokePathClip *strokePathTmp;
917
  GBool adjusted_stroke_width_tmp;
918
  cairo_pattern_t *maskTmp;
919

920 921 922 923
  width = bbox[2] - bbox[0];
  height = bbox[3] - bbox[1];

  if (xStep != width || yStep != height)
924 925 926
    return gFalse;
  /* TODO: implement the other cases here too */

927 928 929 930 931 932 933 934 935 936 937 938
  // Find the width and height of the transformed pattern
  cairo_get_matrix (cairo, &matrix);
  cairo_matrix_init (&pattern_matrix, mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
  cairo_matrix_multiply (&matrix, &matrix, &pattern_matrix);

  double widthX = width, widthY = 0;
  cairo_matrix_transform_distance (&matrix, &widthX, &widthY);
  surface_width = ceil (sqrt (widthX * widthX + widthY * widthY));

  double heightX = 0, heightY = height;
  cairo_matrix_transform_distance (&matrix, &heightX, &heightY);
  surface_height = ceil (sqrt (heightX * heightX + heightY * heightY));
939 940
  scaleX = surface_width / width;
  scaleY = surface_height / height;
941

942 943
  surface = cairo_surface_create_similar (cairo_get_target (cairo),
					  CAIRO_CONTENT_COLOR_ALPHA,
944
					  surface_width, surface_height);
945 946 947 948 949 950
  if (cairo_surface_status (surface))
    return gFalse;

  old_cairo = cairo;
  cairo = cairo_create (surface);
  cairo_surface_destroy (surface);
951
  setContextAntialias(cairo, antialias);
952 953 954

  box.x1 = bbox[0]; box.y1 = bbox[1];
  box.x2 = bbox[2]; box.y2 = bbox[3];
955 956 957
  cairo_scale (cairo, scaleX, scaleY);
  cairo_translate (cairo, -box.x1, -box.y1);

Adrian Johnson's avatar
Adrian Johnson committed
958
  strokePathTmp = strokePathClip;
959
  strokePathClip = nullptr;
960
  adjusted_stroke_width_tmp = adjusted_stroke_width;
961
  maskTmp = mask;
962
  mask = nullptr;
963
  gfx = new Gfx(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA);
964 965
  if (paintType == 2)
    inUncoloredPattern = gTrue;
966
  gfx->display(str);
967 968
  if (paintType == 2)
    inUncoloredPattern = gFalse;
969
  delete gfx;
Adrian Johnson's avatar
Adrian Johnson committed
970
  strokePathClip = strokePathTmp;
971
  adjusted_stroke_width = adjusted_stroke_width_tmp;
972
  mask = maskTmp;
973 974 975 976 977 978 979 980 981 982

  pattern = cairo_pattern_create_for_surface (cairo_get_target (cairo));
  cairo_destroy (cairo);
  cairo = old_cairo;
  if (cairo_pattern_status (pattern))
    return gFalse;

  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
  cairo_rectangle (cairo, xMin, yMin, xMax - xMin, yMax - yMin);

983 984
  cairo_matrix_init_scale (&matrix, scaleX, scaleY);
  cairo_matrix_translate (&matrix, -box.x1, -box.y1);
985 986
  cairo_pattern_set_matrix (pattern, &matrix);

987
  cairo_transform (cairo, &pattern_matrix);
988 989
  cairo_set_source (cairo, pattern);
  cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
Adrian Johnson's avatar
Adrian Johnson committed
990
  if (strokePathClip) {
991
    fillToStrokePathClip(state);
Adrian Johnson's avatar
Adrian Johnson committed
992 993 994
  } else {
    cairo_fill (cairo);
  }
995 996 997 998 999 1000

  cairo_pattern_destroy (pattern);

  return gTrue;
}

1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
GBool CairoOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading)
{
  // Function shaded fills are subdivided to rectangles that are the
  // following size in device space.  Note when printing this size is
  // in points.
  const int subdivide_pixels = 10;

  double x_begin, x_end, x1, x2;
  double y_begin, y_end, y1, y2;
  double x_step;
  double y_step;
  GfxColor color;
  GfxRGB rgb;
  double *matrix;
  cairo_matrix_t mat;

  matrix = shading->getMatrix();
  mat.xx = matrix[0];
  mat.yx = matrix[1];
  mat.xy = matrix[2];
  mat.yy = matrix[3];
  mat.x0 = matrix[4];
  mat.y0 = matrix[5];
  if (cairo_matrix_invert(&mat)) {
    error(errSyntaxWarning, -1, "matrix not invertible\n");
    return gFalse;
    }

  // get cell size in pattern space
  x_step = y_step = subdivide_pixels;
  cairo_matrix_transform_distance (&mat, &x_step, &y_step);

  cairo_pattern_destroy(fill_pattern);
  fill_pattern = cairo_pattern_create_mesh ();
  cairo_pattern_set_matrix(fill_pattern, &mat);
  shading->getDomain(&x_begin, &y_begin, &x_end, &y_end);

  for (x1 = x_begin; x1 < x_end; x1 += x_step) {
    x2 = x1 + x_step;
    if (x2 > x_end)
      x2 = x_end;

    for (y1 = y_begin; y1 < y_end; y1 += y_step) {
      y2 = y1 + y_step;
      if (y2 > y_end)
	y2 = y_end;

      cairo_mesh_pattern_begin_patch (fill_pattern);
      cairo_mesh_pattern_move_to (fill_pattern, x1, y1);
      cairo_mesh_pattern_line_to (fill_pattern, x2, y1);
      cairo_mesh_pattern_line_to (fill_pattern, x2, y2);
      cairo_mesh_pattern_line_to (fill_pattern, x1, y2);

      shading->getColor(x1, y1, &color);
      shading->getColorSpace()->getRGB(&color, &rgb);
      cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 0,
					       colToDbl(rgb.r),
					       colToDbl(rgb.g),
					       colToDbl(rgb.b));

      shading->getColor(x2, y1, &color);
      shading->getColorSpace()->getRGB(&color, &rgb);
      cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 1,
					       colToDbl(rgb.r),
					       colToDbl(rgb.g),
					       colToDbl(rgb.b));

      shading->getColor(x2, y2, &color);
      shading->getColorSpace()->getRGB(&color, &rgb);
      cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 2,
					       colToDbl(rgb.r),
					       colToDbl(rgb.g),
					       colToDbl(rgb.b));

      shading->getColor(x1, y2, &color);
      shading->getColorSpace()->getRGB(&color, &rgb);
      cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 3,
					       colToDbl(rgb.r),
					       colToDbl(rgb.g),
					       colToDbl(rgb.b));

      cairo_mesh_pattern_end_patch (fill_pattern);
    }
  }

  double xMin, yMin, xMax, yMax;
  // get the clip region bbox
  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
  state->moveTo(xMin, yMin);
  state->lineTo(xMin, yMax);
  state->lineTo(xMax, yMax);
  state->lineTo(xMax, yMin);
  state->closePath();
  fill(state);
  state->clearPath();

  return gTrue;
}
#endif /* CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) */

1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
GBool CairoOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) {
  double x0, y0, x1, y1;
  double dx, dy;

  shading->getCoords(&x0, &y0, &x1, &y1);
  dx = x1 - x0;
  dy = y1 - y0;

  cairo_pattern_destroy(fill_pattern);
  fill_pattern = cairo_pattern_create_linear (x0 + tMin * dx, y0 + tMin * dy,
					      x0 + tMax * dx, y0 + tMax * dy);
1113 1114 1115 1116
  if (!shading->getExtend0() && !shading->getExtend1())
    cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_NONE);
  else
    cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_PAD);
1117

1118 1119
  LOG (printf ("axial-sh\n"));

1120 1121 1122 1123 1124
  // TODO: use the actual stops in the shading in the case
  // of linear interpolation (Type 2 Exponential functions with N=1)
  return gFalse;
}

1125 1126 1127 1128 1129
GBool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading)
{
  return (shading->getExtend0() == shading->getExtend1());
}

1130 1131 1132
GBool CairoOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax) {
  double x0, y0, r0, x1, y1, r1;
  double dx, dy, dr;
Jason Crain's avatar
Jason Crain committed
1133 1134
  cairo_matrix_t matrix;
  double scale;
1135 1136 1137 1138 1139

  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
  dx = x1 - x0;
  dy = y1 - y0;
  dr = r1 - r0;
Jason Crain's avatar
Jason Crain committed
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150

  // Cairo/pixman do not work well with a very large or small scaled
  // matrix.  See cairo bug #81657.
  //
  // As a workaround, scale the pattern by the average of the vertical
  // and horizontal scaling of the current transformation matrix.
  cairo_get_matrix(cairo, &matrix);
  scale = (sqrt(matrix.xx * matrix.xx + matrix.yx * matrix.yx)
	   + sqrt(matrix.xy * matrix.xy + matrix.yy * matrix.yy)) / 2;
  cairo_matrix_init_scale(&matrix, scale, scale);

1151
  cairo_pattern_destroy(fill_pattern);
Jason Crain's avatar
Jason Crain committed
1152 1153 1154 1155 1156 1157 1158
  fill_pattern = cairo_pattern_create_radial ((x0 + sMin * dx) * scale,
					      (y0 + sMin * dy) * scale,
					      (r0 + sMin * dr) * scale,
					      (x0 + sMax * dx) * scale,
					      (y0 + sMax * dy) * scale,
					      (r0 + sMax * dr) * scale);
  cairo_pattern_set_matrix(fill_pattern, &matrix);
1159 1160 1161 1162 1163
  if (shading->getExtend0() && shading->getExtend1())
    cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_PAD);
  else
    cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_NONE);

1164 1165
  LOG (printf ("radial-sh\n"));

1166 1167 1168 1169 1170 1171 1172 1173
  return gFalse;
}

GBool CairoOutputDev::radialShadedSupportExtend(GfxState *state, GfxRadialShading *shading)
{
  return (shading->getExtend0() == shading->getExtend1());
}

1174
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
GBool CairoOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading)
{
  double x0, y0, x1, y1, x2, y2;
  GfxColor color[3];
  int i, j;
  GfxRGB rgb;

  cairo_pattern_destroy(fill_pattern);
  fill_pattern = cairo_pattern_create_mesh ();

  for (i = 0; i < shading->getNTriangles(); i++) {
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
    if (shading->isParameterized()) {
      double color0, color1, color2;
      shading->getTriangle(i, &x0, &y0, &color0,
                              &x1, &y1, &color1,
                              &x2, &y2, &color2);
      shading->getParameterizedColor(color0, &color[0]);
      shading->getParameterizedColor(color1, &color[1]);
      shading->getParameterizedColor(color2, &color[2]);
    } else {
      shading->getTriangle(i,
                           &x0, &y0, &color[0],
                           &x1, &y1, &color[1],
                           &x2, &y2, &color[2]);

    }
1201

1202
    cairo_mesh_pattern_begin_patch (fill_pattern);
1203

1204 1205 1206
    cairo_mesh_pattern_move_to (fill_pattern, x0, y0);
    cairo_mesh_pattern_line_to (fill_pattern, x1, y1);
    cairo_mesh_pattern_line_to (fill_pattern, x2, y2);
1207 1208 1209

    for (j = 0; j < 3; j++) {
	shading->getColorSpace()->getRGB(&color[j], &rgb);
1210
	cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, j,
1211 1212 1213 1214 1215
						 colToDbl(rgb.r),
						 colToDbl(rgb.g),
						 colToDbl(rgb.b));
    }

1216
    cairo_mesh_pattern_end_patch (fill_pattern);
1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244
  }

  double xMin, yMin, xMax, yMax;
  // get the clip region bbox
  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
  state->moveTo(xMin, yMin);
  state->lineTo(xMin, yMax);
  state->lineTo(xMax, yMax);
  state->lineTo(xMax, yMin);
  state->closePath();
  fill(state);
  state->clearPath();

  return gTrue;
}

GBool CairoOutputDev::patchMeshShadedFill(GfxState *state, GfxPatchMeshShading *shading)
{
  int i, j, k;

  cairo_pattern_destroy(fill_pattern);
  fill_pattern = cairo_pattern_create_mesh ();

  for (i = 0; i < shading->getNPatches(); i++) {
    GfxPatch *patch = shading->getPatch(i);
    GfxColor color;
    GfxRGB rgb;

1245
    cairo_mesh_pattern_begin_patch (fill_pattern);
1246

1247 1248
    cairo_mesh_pattern_move_to (fill_pattern, patch->x[0][0], patch->y[0][0]);
    cairo_mesh_pattern_curve_to (fill_pattern,
1249 1250 1251 1252
			    patch->x[0][1], patch->y[0][1],
			    patch->x[0][2], patch->y[0][2],
			    patch->x[0][3], patch->y[0][3]);

1253
    cairo_mesh_pattern_curve_to (fill_pattern,
1254 1255 1256 1257
			    patch->x[1][3], patch->y[1][3],
			    patch->x[2][3], patch->y[2][3],
			    patch->x[3][3], patch->y[3][3]);

1258
    cairo_mesh_pattern_curve_to (fill_pattern,
1259 1260 1261 1262
			    patch->x[3][2], patch->y[3][2],
			    patch->x[3][1], patch->y[3][1],
			    patch->x[3][0], patch->y[3][0]);

1263
    cairo_mesh_pattern_curve_to (fill_pattern,
1264 1265 1266 1267
			    patch->x[2][0], patch->y[2][0],
			    patch->x[1][0], patch->y[1][0],
			    patch->x[0][0], patch->y[0][0]);