1 //========================================================================
2 //
3 // CairoOutputDev.cc
4 //
5 // Copyright 2003 Glyph & Cog, LLC
6 // Copyright 2004 Red Hat, Inc
7 //
8 //========================================================================
9
10 //========================================================================
11 //
12 // Modified under the Poppler project - http://poppler.freedesktop.org
13 //
14 // All changes made under the Poppler project to this file are licensed
15 // under GPL version 2 or later
16 //
17 // Copyright (C) 2005-2008 Jeff Muizelaar <jeff@infidigm.net>
18 // Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
19 // Copyright (C) 2005, 2009, 2012, 2017-2021 Albert Astals Cid <aacid@kde.org>
20 // Copyright (C) 2005 Nickolay V. Shmyrev <nshmyrev@yandex.ru>
21 // Copyright (C) 2006-2011, 2013, 2014, 2017, 2018 Carlos Garcia Campos <carlosgc@gnome.org>
22 // Copyright (C) 2008 Carl Worth <cworth@cworth.org>
23 // Copyright (C) 2008-2018, 2021 Adrian Johnson <ajohnson@redneon.com>
24 // Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
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>
29 // Copyright (C) 2012 Patrick Pfeifer <p2000@mailinator.com>
30 // Copyright (C) 2012, 2015, 2016 Jason Crain <jason@aquaticape.us>
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 // Copyright (C) 2018, 2020 Adam Reichold <adam.reichold@t-online.de>
34 // Copyright (C) 2019, 2020 Marek Kasik <mkasik@redhat.com>
35 // Copyright (C) 2020 Michal <sudolskym@gmail.com>
36 // Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
37 // Copyright (C) 2021 Uli Schlachter <psychon@znc.in>
38 // Copyright (C) 2021 Christian Persch <chpe@src.gnome.org>
39 //
40 // To see a description of the changes please see the Changelog file that
41 // came with your tarball or type make ChangeLog if you are building from git
42 //
43 //========================================================================
44
45 #include <config.h>
46
47 #include <cstdint>
48 #include <cstring>
49 #include <cmath>
50 #include <cassert>
51 #include <cairo.h>
52
53 #include "goo/gfile.h"
54 #include "GlobalParams.h"
55 #include "Error.h"
56 #include "Object.h"
57 #include "Gfx.h"
58 #include "GfxState.h"
59 #include "GfxFont.h"
60 #include "Page.h"
61 #include "Link.h"
62 #include "FontEncodingTables.h"
63 #include "PDFDocEncoding.h"
64 #include <fofi/FoFiTrueType.h>
65 #include <splash/SplashBitmap.h>
66 #include "CairoOutputDev.h"
67 #include "CairoFontEngine.h"
68 #include "CairoRescaleBox.h"
69 #include "UnicodeMap.h"
70 #include "JBIG2Stream.h"
71 //------------------------------------------------------------------------
72
73 // #define LOG_CAIRO
74
75 // To limit memory usage and improve performance when printing, limit
76 // cairo images to this size. 8192 is sufficient for an A2 sized
77 // 300ppi image.
78 #define MAX_PRINT_IMAGE_SIZE 8192
79
80 #ifdef LOG_CAIRO
81 # define LOG(x) (x)
82 #else
83 # define LOG(x)
84 #endif
85
86 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
87 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
88
89 //------------------------------------------------------------------------
90 // CairoImage
91 //------------------------------------------------------------------------
92
CairoImage(double x1A,double y1A,double x2A,double y2A)93 CairoImage::CairoImage(double x1A, double y1A, double x2A, double y2A)
94 {
95 image = nullptr;
96 x1 = x1A;
97 y1 = y1A;
98 x2 = x2A;
99 y2 = y2A;
100 }
101
~CairoImage()102 CairoImage::~CairoImage()
103 {
104 if (image)
105 cairo_surface_destroy(image);
106 }
107
setImage(cairo_surface_t * i)108 void CairoImage::setImage(cairo_surface_t *i)
109 {
110 if (image)
111 cairo_surface_destroy(image);
112 image = cairo_surface_reference(i);
113 }
114
115 //------------------------------------------------------------------------
116 // CairoOutputDev
117 //------------------------------------------------------------------------
118
119 // We cannot tie the lifetime of an FT_Library object to that of
120 // CairoOutputDev, since any FT_Faces created with it may end up with a
121 // reference by Cairo which can be held long after the CairoOutputDev is
122 // deleted. The simplest way to avoid problems is to never tear down the
123 // FT_Library instance; to avoid leaks, just use a single global instance
124 // initialized the first time it is needed.
125 FT_Library CairoOutputDev::ft_lib;
126 std::once_flag CairoOutputDev::ft_lib_once_flag;
127
CairoOutputDev()128 CairoOutputDev::CairoOutputDev()
129 {
130 doc = nullptr;
131
132 std::call_once(ft_lib_once_flag, FT_Init_FreeType, &ft_lib);
133
134 fontEngine = nullptr;
135 fontEngine_owner = false;
136 glyphs = nullptr;
137 fill_pattern = nullptr;
138 fill_color.r = fill_color.g = fill_color.b = 0;
139 stroke_pattern = nullptr;
140 stroke_color.r = stroke_color.g = stroke_color.b = 0;
141 stroke_opacity = 1.0;
142 fill_opacity = 1.0;
143 textClipPath = nullptr;
144 strokePathClip = nullptr;
145 cairo = nullptr;
146 currentFont = nullptr;
147 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
148 prescaleImages = false;
149 #else
150 prescaleImages = true;
151 #endif
152 printing = true;
153 use_show_text_glyphs = false;
154 inUncoloredPattern = false;
155 inType3Char = false;
156 t3_glyph_has_bbox = false;
157 text_matrix_valid = true;
158
159 groupColorSpaceStack = nullptr;
160 maskStack = nullptr;
161 group = nullptr;
162 mask = nullptr;
163 shape = nullptr;
164 cairo_shape = nullptr;
165 knockoutCount = 0;
166
167 textPage = nullptr;
168 actualText = nullptr;
169
170 // the SA parameter supposedly defaults to false, but Acrobat
171 // apparently hardwires it to true
172 stroke_adjust = true;
173 align_stroke_coords = false;
174 adjusted_stroke_width = false;
175 xref = nullptr;
176 }
177
~CairoOutputDev()178 CairoOutputDev::~CairoOutputDev()
179 {
180 if (fontEngine_owner && fontEngine) {
181 delete fontEngine;
182 }
183 if (textClipPath) {
184 cairo_path_destroy(textClipPath);
185 textClipPath = nullptr;
186 }
187
188 if (cairo)
189 cairo_destroy(cairo);
190 cairo_pattern_destroy(stroke_pattern);
191 cairo_pattern_destroy(fill_pattern);
192 if (group)
193 cairo_pattern_destroy(group);
194 if (mask)
195 cairo_pattern_destroy(mask);
196 if (shape)
197 cairo_pattern_destroy(shape);
198 if (textPage)
199 textPage->decRefCnt();
200 if (actualText)
201 delete actualText;
202 }
203
setCairo(cairo_t * c)204 void CairoOutputDev::setCairo(cairo_t *c)
205 {
206 if (cairo != nullptr) {
207 cairo_status_t status = cairo_status(cairo);
208 if (status) {
209 error(errInternal, -1, "cairo context error: {0:s}\n", cairo_status_to_string(status));
210 }
211 cairo_destroy(cairo);
212 assert(!cairo_shape);
213 }
214 if (c != nullptr) {
215 cairo = cairo_reference(c);
216 /* save the initial matrix so that we can use it for type3 fonts. */
217 // XXX: is this sufficient? could we miss changes to the matrix somehow?
218 cairo_get_matrix(cairo, &orig_matrix);
219 } else {
220 cairo = nullptr;
221 cairo_shape = nullptr;
222 }
223 }
224
setTextPage(TextPage * text)225 void CairoOutputDev::setTextPage(TextPage *text)
226 {
227 if (textPage)
228 textPage->decRefCnt();
229 if (actualText)
230 delete actualText;
231 if (text) {
232 textPage = text;
233 textPage->incRefCnt();
234 actualText = new ActualText(text);
235 } else {
236 textPage = nullptr;
237 actualText = nullptr;
238 }
239 }
240
copyAntialias(cairo_t * cr,cairo_t * source_cr)241 void CairoOutputDev::copyAntialias(cairo_t *cr, cairo_t *source_cr)
242 {
243 cairo_set_antialias(cr, cairo_get_antialias(source_cr));
244
245 cairo_font_options_t *font_options = cairo_font_options_create();
246 cairo_get_font_options(source_cr, font_options);
247 cairo_set_font_options(cr, font_options);
248 cairo_font_options_destroy(font_options);
249 }
250
startDoc(PDFDoc * docA,CairoFontEngine * parentFontEngine)251 void CairoOutputDev::startDoc(PDFDoc *docA, CairoFontEngine *parentFontEngine)
252 {
253 doc = docA;
254 if (parentFontEngine) {
255 fontEngine = parentFontEngine;
256 } else {
257 if (fontEngine) {
258 delete fontEngine;
259 }
260 fontEngine = new CairoFontEngine(ft_lib);
261 fontEngine_owner = true;
262 }
263 xref = doc->getXRef();
264 }
265
startPage(int pageNum,GfxState * state,XRef * xrefA)266 void CairoOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA)
267 {
268 /* set up some per page defaults */
269 cairo_pattern_destroy(fill_pattern);
270 cairo_pattern_destroy(stroke_pattern);
271
272 fill_pattern = cairo_pattern_create_rgb(0., 0., 0.);
273 fill_color.r = fill_color.g = fill_color.b = 0;
274 stroke_pattern = cairo_pattern_reference(fill_pattern);
275 stroke_color.r = stroke_color.g = stroke_color.b = 0;
276
277 if (textPage)
278 textPage->startPage(state);
279 if (xrefA != nullptr) {
280 xref = xrefA;
281 }
282 }
283
endPage()284 void CairoOutputDev::endPage()
285 {
286 if (textPage) {
287 textPage->endPage();
288 textPage->coalesce(true, 0, false);
289 }
290 }
291
saveState(GfxState * state)292 void CairoOutputDev::saveState(GfxState *state)
293 {
294 LOG(printf("save\n"));
295 cairo_save(cairo);
296 if (cairo_shape)
297 cairo_save(cairo_shape);
298
299 MaskStack *ms = new MaskStack;
300 ms->mask = cairo_pattern_reference(mask);
301 ms->mask_matrix = mask_matrix;
302 ms->next = maskStack;
303 maskStack = ms;
304
305 if (strokePathClip)
306 strokePathClip->ref_count++;
307 }
308
restoreState(GfxState * state)309 void CairoOutputDev::restoreState(GfxState *state)
310 {
311 LOG(printf("restore\n"));
312 cairo_restore(cairo);
313 if (cairo_shape)
314 cairo_restore(cairo_shape);
315
316 text_matrix_valid = true;
317
318 /* These aren't restored by cairo_restore() since we keep them in
319 * the output device. */
320 updateFillColor(state);
321 updateStrokeColor(state);
322 updateFillOpacity(state);
323 updateStrokeOpacity(state);
324 updateBlendMode(state);
325
326 MaskStack *ms = maskStack;
327 if (ms) {
328 if (mask)
329 cairo_pattern_destroy(mask);
330 mask = ms->mask;
331 mask_matrix = ms->mask_matrix;
332 maskStack = ms->next;
333 delete ms;
334 }
335
336 if (strokePathClip && --strokePathClip->ref_count == 0) {
337 delete strokePathClip->path;
338 if (strokePathClip->dashes)
339 gfree(strokePathClip->dashes);
340 gfree(strokePathClip);
341 strokePathClip = nullptr;
342 }
343 }
344
updateAll(GfxState * state)345 void CairoOutputDev::updateAll(GfxState *state)
346 {
347 updateLineDash(state);
348 updateLineJoin(state);
349 updateLineCap(state);
350 updateLineWidth(state);
351 updateFlatness(state);
352 updateMiterLimit(state);
353 updateFillColor(state);
354 updateStrokeColor(state);
355 updateFillOpacity(state);
356 updateStrokeOpacity(state);
357 updateBlendMode(state);
358 needFontUpdate = true;
359 if (textPage)
360 textPage->updateFont(state);
361 }
362
setDefaultCTM(const double * ctm)363 void CairoOutputDev::setDefaultCTM(const double *ctm)
364 {
365 cairo_matrix_t matrix;
366 matrix.xx = ctm[0];
367 matrix.yx = ctm[1];
368 matrix.xy = ctm[2];
369 matrix.yy = ctm[3];
370 matrix.x0 = ctm[4];
371 matrix.y0 = ctm[5];
372
373 cairo_transform(cairo, &matrix);
374 if (cairo_shape)
375 cairo_transform(cairo_shape, &matrix);
376
377 OutputDev::setDefaultCTM(ctm);
378 }
379
updateCTM(GfxState * state,double m11,double m12,double m21,double m22,double m31,double m32)380 void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32)
381 {
382 cairo_matrix_t matrix, invert_matrix;
383 matrix.xx = m11;
384 matrix.yx = m12;
385 matrix.xy = m21;
386 matrix.yy = m22;
387 matrix.x0 = m31;
388 matrix.y0 = m32;
389
390 /* Make sure the matrix is invertible before setting it.
391 * cairo will blow up if we give it a matrix that's not
392 * invertible, so we need to check before passing it
393 * to cairo_transform. Ignoring it is likely to give better
394 * results than not rendering anything at all. See #14398
395 *
396 * Ideally, we could do the cairo_transform
397 * and then check if anything went wrong and fix it then
398 * instead of having to invert the matrix. */
399 invert_matrix = matrix;
400 if (cairo_matrix_invert(&invert_matrix)) {
401 error(errSyntaxWarning, -1, "matrix not invertible\n");
402 return;
403 }
404
405 cairo_transform(cairo, &matrix);
406 if (cairo_shape)
407 cairo_transform(cairo_shape, &matrix);
408 updateLineDash(state);
409 updateLineJoin(state);
410 updateLineCap(state);
411 updateLineWidth(state);
412 }
413
updateLineDash(GfxState * state)414 void CairoOutputDev::updateLineDash(GfxState *state)
415 {
416 double *dashPattern;
417 int dashLength;
418 double dashStart;
419
420 state->getLineDash(&dashPattern, &dashLength, &dashStart);
421 cairo_set_dash(cairo, dashPattern, dashLength, dashStart);
422 if (cairo_shape)
423 cairo_set_dash(cairo_shape, dashPattern, dashLength, dashStart);
424 }
425
updateFlatness(GfxState * state)426 void CairoOutputDev::updateFlatness(GfxState *state)
427 {
428 // cairo_set_tolerance (cairo, state->getFlatness());
429 }
430
updateLineJoin(GfxState * state)431 void CairoOutputDev::updateLineJoin(GfxState *state)
432 {
433 switch (state->getLineJoin()) {
434 case 0:
435 cairo_set_line_join(cairo, CAIRO_LINE_JOIN_MITER);
436 break;
437 case 1:
438 cairo_set_line_join(cairo, CAIRO_LINE_JOIN_ROUND);
439 break;
440 case 2:
441 cairo_set_line_join(cairo, CAIRO_LINE_JOIN_BEVEL);
442 break;
443 }
444 if (cairo_shape)
445 cairo_set_line_join(cairo_shape, cairo_get_line_join(cairo));
446 }
447
updateLineCap(GfxState * state)448 void CairoOutputDev::updateLineCap(GfxState *state)
449 {
450 switch (state->getLineCap()) {
451 case 0:
452 cairo_set_line_cap(cairo, CAIRO_LINE_CAP_BUTT);
453 break;
454 case 1:
455 cairo_set_line_cap(cairo, CAIRO_LINE_CAP_ROUND);
456 break;
457 case 2:
458 cairo_set_line_cap(cairo, CAIRO_LINE_CAP_SQUARE);
459 break;
460 }
461 if (cairo_shape)
462 cairo_set_line_cap(cairo_shape, cairo_get_line_cap(cairo));
463 }
464
updateMiterLimit(GfxState * state)465 void CairoOutputDev::updateMiterLimit(GfxState *state)
466 {
467 cairo_set_miter_limit(cairo, state->getMiterLimit());
468 if (cairo_shape)
469 cairo_set_miter_limit(cairo_shape, state->getMiterLimit());
470 }
471
updateLineWidth(GfxState * state)472 void CairoOutputDev::updateLineWidth(GfxState *state)
473 {
474 LOG(printf("line width: %f\n", state->getLineWidth()));
475 adjusted_stroke_width = false;
476 double width = state->getLineWidth();
477 if (stroke_adjust && !printing) {
478 double x, y;
479 x = y = width;
480
481 /* find out line width in device units */
482 cairo_user_to_device_distance(cairo, &x, &y);
483 if (fabs(x) <= 1.0 && fabs(y) <= 1.0) {
484 /* adjust width to at least one device pixel */
485 x = y = 1.0;
486 cairo_device_to_user_distance(cairo, &x, &y);
487 width = MIN(fabs(x), fabs(y));
488 adjusted_stroke_width = true;
489 }
490 } else if (width == 0.0) {
491 /* Cairo does not support 0 line width == 1 device pixel. Find out
492 * how big pixels (device unit) are in the x and y
493 * directions. Choose the smaller of the two as our line width.
494 */
495 double x = 1.0, y = 1.0;
496 if (printing) {
497 // assume printer pixel size is 1/600 inch
498 x = 72.0 / 600;
499 y = 72.0 / 600;
500 }
501 cairo_device_to_user_distance(cairo, &x, &y);
502 width = MIN(fabs(x), fabs(y));
503 }
504 cairo_set_line_width(cairo, width);
505 if (cairo_shape)
506 cairo_set_line_width(cairo_shape, cairo_get_line_width(cairo));
507 }
508
updateFillColor(GfxState * state)509 void CairoOutputDev::updateFillColor(GfxState *state)
510 {
511 GfxRGB color = fill_color;
512
513 if (inUncoloredPattern)
514 return;
515
516 state->getFillRGB(&fill_color);
517 if (cairo_pattern_get_type(fill_pattern) != CAIRO_PATTERN_TYPE_SOLID || color.r != fill_color.r || color.g != fill_color.g || color.b != fill_color.b) {
518 cairo_pattern_destroy(fill_pattern);
519 fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), fill_opacity);
520
521 LOG(printf("fill color: %d %d %d\n", fill_color.r, fill_color.g, fill_color.b));
522 }
523 }
524
updateStrokeColor(GfxState * state)525 void CairoOutputDev::updateStrokeColor(GfxState *state)
526 {
527 GfxRGB color = stroke_color;
528
529 if (inUncoloredPattern)
530 return;
531
532 state->getStrokeRGB(&stroke_color);
533 if (cairo_pattern_get_type(fill_pattern) != CAIRO_PATTERN_TYPE_SOLID || color.r != stroke_color.r || color.g != stroke_color.g || color.b != stroke_color.b) {
534 cairo_pattern_destroy(stroke_pattern);
535 stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r), colToDbl(stroke_color.g), colToDbl(stroke_color.b), stroke_opacity);
536
537 LOG(printf("stroke color: %d %d %d\n", stroke_color.r, stroke_color.g, stroke_color.b));
538 }
539 }
540
updateFillOpacity(GfxState * state)541 void CairoOutputDev::updateFillOpacity(GfxState *state)
542 {
543 double opacity = fill_opacity;
544
545 if (inUncoloredPattern)
546 return;
547
548 fill_opacity = state->getFillOpacity();
549 if (opacity != fill_opacity) {
550 cairo_pattern_destroy(fill_pattern);
551 fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), fill_opacity);
552
553 LOG(printf("fill opacity: %f\n", fill_opacity));
554 }
555 }
556
updateStrokeOpacity(GfxState * state)557 void CairoOutputDev::updateStrokeOpacity(GfxState *state)
558 {
559 double opacity = stroke_opacity;
560
561 if (inUncoloredPattern)
562 return;
563
564 stroke_opacity = state->getStrokeOpacity();
565 if (opacity != stroke_opacity) {
566 cairo_pattern_destroy(stroke_pattern);
567 stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r), colToDbl(stroke_color.g), colToDbl(stroke_color.b), stroke_opacity);
568
569 LOG(printf("stroke opacity: %f\n", stroke_opacity));
570 }
571 }
572
updateFillColorStop(GfxState * state,double offset)573 void CairoOutputDev::updateFillColorStop(GfxState *state, double offset)
574 {
575 if (inUncoloredPattern)
576 return;
577
578 state->getFillRGB(&fill_color);
579
580 // If stroke pattern is set then the current fill is clipped
581 // to a stroke path. In that case, the stroke opacity has to be used
582 // rather than the fill opacity.
583 // See https://gitlab.freedesktop.org/poppler/poppler/issues/178
584 auto opacity = (state->getStrokePattern()) ? state->getStrokeOpacity() : state->getFillOpacity();
585
586 cairo_pattern_add_color_stop_rgba(fill_pattern, offset, colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), opacity);
587 LOG(printf("fill color stop: %f (%d, %d, %d, %d)\n", offset, fill_color.r, fill_color.g, fill_color.b, dblToCol(opacity)));
588 }
589
updateBlendMode(GfxState * state)590 void CairoOutputDev::updateBlendMode(GfxState *state)
591 {
592 switch (state->getBlendMode()) {
593 default:
594 case gfxBlendNormal:
595 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
596 break;
597 case gfxBlendMultiply:
598 cairo_set_operator(cairo, CAIRO_OPERATOR_MULTIPLY);
599 break;
600 case gfxBlendScreen:
601 cairo_set_operator(cairo, CAIRO_OPERATOR_SCREEN);
602 break;
603 case gfxBlendOverlay:
604 cairo_set_operator(cairo, CAIRO_OPERATOR_OVERLAY);
605 break;
606 case gfxBlendDarken:
607 cairo_set_operator(cairo, CAIRO_OPERATOR_DARKEN);
608 break;
609 case gfxBlendLighten:
610 cairo_set_operator(cairo, CAIRO_OPERATOR_LIGHTEN);
611 break;
612 case gfxBlendColorDodge:
613 cairo_set_operator(cairo, CAIRO_OPERATOR_COLOR_DODGE);
614 break;
615 case gfxBlendColorBurn:
616 cairo_set_operator(cairo, CAIRO_OPERATOR_COLOR_BURN);
617 break;
618 case gfxBlendHardLight:
619 cairo_set_operator(cairo, CAIRO_OPERATOR_HARD_LIGHT);
620 break;
621 case gfxBlendSoftLight:
622 cairo_set_operator(cairo, CAIRO_OPERATOR_SOFT_LIGHT);
623 break;
624 case gfxBlendDifference:
625 cairo_set_operator(cairo, CAIRO_OPERATOR_DIFFERENCE);
626 break;
627 case gfxBlendExclusion:
628 cairo_set_operator(cairo, CAIRO_OPERATOR_EXCLUSION);
629 break;
630 case gfxBlendHue:
631 cairo_set_operator(cairo, CAIRO_OPERATOR_HSL_HUE);
632 break;
633 case gfxBlendSaturation:
634 cairo_set_operator(cairo, CAIRO_OPERATOR_HSL_SATURATION);
635 break;
636 case gfxBlendColor:
637 cairo_set_operator(cairo, CAIRO_OPERATOR_HSL_COLOR);
638 break;
639 case gfxBlendLuminosity:
640 cairo_set_operator(cairo, CAIRO_OPERATOR_HSL_LUMINOSITY);
641 break;
642 }
643 LOG(printf("blend mode: %d\n", (int)state->getBlendMode()));
644 }
645
updateFont(GfxState * state)646 void CairoOutputDev::updateFont(GfxState *state)
647 {
648 cairo_font_face_t *font_face;
649 cairo_matrix_t matrix, invert_matrix;
650
651 LOG(printf("updateFont() font=%s\n", state->getFont()->getName()->c_str()));
652
653 needFontUpdate = false;
654
655 // FIXME: use cairo font engine?
656 if (textPage)
657 textPage->updateFont(state);
658
659 currentFont = fontEngine->getFont(state->getFont(), doc, printing, xref);
660
661 if (!currentFont)
662 return;
663
664 font_face = currentFont->getFontFace();
665 cairo_set_font_face(cairo, font_face);
666
667 use_show_text_glyphs = state->getFont()->hasToUnicodeCMap() && cairo_surface_has_show_text_glyphs(cairo_get_target(cairo));
668
669 double fontSize = state->getFontSize();
670 const double *m = state->getTextMat();
671 /* NOTE: adjusting by a constant is hack. The correct solution
672 * is probably to use user-fonts and compute the scale on a per
673 * glyph basis instead of for the entire font */
674 double w = currentFont->getSubstitutionCorrection(state->getFont());
675 matrix.xx = m[0] * fontSize * state->getHorizScaling() * w;
676 matrix.yx = m[1] * fontSize * state->getHorizScaling() * w;
677 matrix.xy = -m[2] * fontSize;
678 matrix.yy = -m[3] * fontSize;
679 matrix.x0 = 0;
680 matrix.y0 = 0;
681
682 LOG(printf("font matrix: %f %f %f %f\n", matrix.xx, matrix.yx, matrix.xy, matrix.yy));
683
684 /* Make sure the font matrix is invertible before setting it. cairo
685 * will blow up if we give it a matrix that's not invertible, so we
686 * need to check before passing it to cairo_set_font_matrix. Ignoring it
687 * is likely to give better results than not rendering anything at
688 * all. See #18254.
689 */
690 invert_matrix = matrix;
691 if (cairo_matrix_invert(&invert_matrix)) {
692 error(errSyntaxWarning, -1, "font matrix not invertible");
693 text_matrix_valid = false;
694 return;
695 }
696
697 cairo_set_font_matrix(cairo, &matrix);
698 text_matrix_valid = true;
699 }
700
701 /* Tolerance in pixels for checking if strokes are horizontal or vertical
702 * lines in device space */
703 #define STROKE_COORD_TOLERANCE 0.5
704
705 /* Align stroke coordinate i if the point is the start or end of a
706 * horizontal or vertical line */
alignStrokeCoords(const GfxSubpath * subpath,int i,double * x,double * y)707 void CairoOutputDev::alignStrokeCoords(const GfxSubpath *subpath, int i, double *x, double *y)
708 {
709 double x1, y1, x2, y2;
710 bool align = false;
711
712 x1 = subpath->getX(i);
713 y1 = subpath->getY(i);
714 cairo_user_to_device(cairo, &x1, &y1);
715
716 // Does the current coord and prev coord form a horiz or vert line?
717 if (i > 0 && !subpath->getCurve(i - 1)) {
718 x2 = subpath->getX(i - 1);
719 y2 = subpath->getY(i - 1);
720 cairo_user_to_device(cairo, &x2, &y2);
721 if (fabs(x2 - x1) < STROKE_COORD_TOLERANCE || fabs(y2 - y1) < STROKE_COORD_TOLERANCE)
722 align = true;
723 }
724
725 // Does the current coord and next coord form a horiz or vert line?
726 if (i < subpath->getNumPoints() - 1 && !subpath->getCurve(i + 1)) {
727 x2 = subpath->getX(i + 1);
728 y2 = subpath->getY(i + 1);
729 cairo_user_to_device(cairo, &x2, &y2);
730 if (fabs(x2 - x1) < STROKE_COORD_TOLERANCE || fabs(y2 - y1) < STROKE_COORD_TOLERANCE)
731 align = true;
732 }
733
734 *x = subpath->getX(i);
735 *y = subpath->getY(i);
736 if (align) {
737 /* see http://www.cairographics.org/FAQ/#sharp_lines */
738 cairo_user_to_device(cairo, x, y);
739 *x = floor(*x) + 0.5;
740 *y = floor(*y) + 0.5;
741 cairo_device_to_user(cairo, x, y);
742 }
743 }
744
745 #undef STROKE_COORD_TOLERANCE
746
doPath(cairo_t * c,GfxState * state,const GfxPath * path)747 void CairoOutputDev::doPath(cairo_t *c, GfxState *state, const GfxPath *path)
748 {
749 int i, j;
750 double x, y;
751 cairo_new_path(c);
752 for (i = 0; i < path->getNumSubpaths(); ++i) {
753 const GfxSubpath *subpath = path->getSubpath(i);
754 if (subpath->getNumPoints() > 0) {
755 if (align_stroke_coords) {
756 alignStrokeCoords(subpath, 0, &x, &y);
757 } else {
758 x = subpath->getX(0);
759 y = subpath->getY(0);
760 }
761 cairo_move_to(c, x, y);
762 j = 1;
763 while (j < subpath->getNumPoints()) {
764 if (subpath->getCurve(j)) {
765 if (align_stroke_coords) {
766 alignStrokeCoords(subpath, j + 2, &x, &y);
767 } else {
768 x = subpath->getX(j + 2);
769 y = subpath->getY(j + 2);
770 }
771 cairo_curve_to(c, subpath->getX(j), subpath->getY(j), subpath->getX(j + 1), subpath->getY(j + 1), x, y);
772
773 j += 3;
774 } else {
775 if (align_stroke_coords) {
776 alignStrokeCoords(subpath, j, &x, &y);
777 } else {
778 x = subpath->getX(j);
779 y = subpath->getY(j);
780 }
781 cairo_line_to(c, x, y);
782 ++j;
783 }
784 }
785 if (subpath->isClosed()) {
786 LOG(printf("close\n"));
787 cairo_close_path(c);
788 }
789 }
790 }
791 }
792
stroke(GfxState * state)793 void CairoOutputDev::stroke(GfxState *state)
794 {
795 if (inType3Char) {
796 GfxGray gray;
797 state->getFillGray(&gray);
798 if (colToDbl(gray) > 0.5)
799 return;
800 }
801
802 if (adjusted_stroke_width)
803 align_stroke_coords = true;
804 doPath(cairo, state, state->getPath());
805 align_stroke_coords = false;
806 cairo_set_source(cairo, stroke_pattern);
807 LOG(printf("stroke\n"));
808 if (strokePathClip) {
809 cairo_push_group(cairo);
810 cairo_stroke(cairo);
811 cairo_pop_group_to_source(cairo);
812 fillToStrokePathClip(state);
813 } else {
814 cairo_stroke(cairo);
815 }
816 if (cairo_shape) {
817 doPath(cairo_shape, state, state->getPath());
818 cairo_stroke(cairo_shape);
819 }
820 }
821
fill(GfxState * state)822 void CairoOutputDev::fill(GfxState *state)
823 {
824 if (inType3Char) {
825 GfxGray gray;
826 state->getFillGray(&gray);
827 if (colToDbl(gray) > 0.5)
828 return;
829 }
830
831 doPath(cairo, state, state->getPath());
832 cairo_set_fill_rule(cairo, CAIRO_FILL_RULE_WINDING);
833 cairo_set_source(cairo, fill_pattern);
834 LOG(printf("fill\n"));
835 // XXX: how do we get the path
836 if (mask) {
837 cairo_save(cairo);
838 cairo_clip(cairo);
839 if (strokePathClip) {
840 cairo_push_group(cairo);
841 fillToStrokePathClip(state);
842 cairo_pop_group_to_source(cairo);
843 }
844 cairo_set_matrix(cairo, &mask_matrix);
845 cairo_mask(cairo, mask);
846 cairo_restore(cairo);
847 } else if (strokePathClip) {
848 fillToStrokePathClip(state);
849 } else {
850 cairo_fill(cairo);
851 }
852 if (cairo_shape) {
853 cairo_set_fill_rule(cairo_shape, CAIRO_FILL_RULE_WINDING);
854 doPath(cairo_shape, state, state->getPath());
855 cairo_fill(cairo_shape);
856 }
857 }
858
eoFill(GfxState * state)859 void CairoOutputDev::eoFill(GfxState *state)
860 {
861 doPath(cairo, state, state->getPath());
862 cairo_set_fill_rule(cairo, CAIRO_FILL_RULE_EVEN_ODD);
863 cairo_set_source(cairo, fill_pattern);
864 LOG(printf("fill-eo\n"));
865
866 if (mask) {
867 cairo_save(cairo);
868 cairo_clip(cairo);
869 cairo_set_matrix(cairo, &mask_matrix);
870 cairo_mask(cairo, mask);
871 cairo_restore(cairo);
872 } else {
873 cairo_fill(cairo);
874 }
875 if (cairo_shape) {
876 cairo_set_fill_rule(cairo_shape, CAIRO_FILL_RULE_EVEN_ODD);
877 doPath(cairo_shape, state, state->getPath());
878 cairo_fill(cairo_shape);
879 }
880 }
881
tilingPatternFill(GfxState * state,Gfx * gfxA,Catalog * cat,GfxTilingPattern * tPat,const double * mat,int x0,int y0,int x1,int y1,double xStep,double yStep)882 bool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat, GfxTilingPattern *tPat, const double *mat, int x0, int y0, int x1, int y1, double xStep, double yStep)
883 {
884 PDFRectangle box;
885 Gfx *gfx;
886 cairo_pattern_t *pattern;
887 cairo_surface_t *surface;
888 cairo_matrix_t matrix;
889 cairo_matrix_t pattern_matrix;
890 cairo_t *old_cairo;
891 double xMin, yMin, xMax, yMax;
892 double width, height;
893 double scaleX, scaleY;
894 int surface_width, surface_height;
895 StrokePathClip *strokePathTmp;
896 bool adjusted_stroke_width_tmp;
897 cairo_pattern_t *maskTmp;
898 const double *bbox = tPat->getBBox();
899 const double *pmat = tPat->getMatrix();
900 const int paintType = tPat->getPaintType();
901 Dict *resDict = tPat->getResDict();
902 Object *str = tPat->getContentStream();
903
904 width = bbox[2] - bbox[0];
905 height = bbox[3] - bbox[1];
906
907 if (xStep != width || yStep != height)
908 return false;
909 /* TODO: implement the other cases here too */
910
911 // Find the width and height of the transformed pattern
912 cairo_get_matrix(cairo, &matrix);
913 cairo_matrix_init(&pattern_matrix, mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
914 cairo_matrix_multiply(&matrix, &matrix, &pattern_matrix);
915
916 double widthX = width, widthY = 0;
917 cairo_matrix_transform_distance(&matrix, &widthX, &widthY);
918 surface_width = ceil(sqrt(widthX * widthX + widthY * widthY));
919
920 double heightX = 0, heightY = height;
921 cairo_matrix_transform_distance(&matrix, &heightX, &heightY);
922 surface_height = ceil(sqrt(heightX * heightX + heightY * heightY));
923 scaleX = surface_width / width;
924 scaleY = surface_height / height;
925
926 surface = cairo_surface_create_similar(cairo_get_target(cairo), CAIRO_CONTENT_COLOR_ALPHA, surface_width, surface_height);
927 if (cairo_surface_status(surface))
928 return false;
929
930 old_cairo = cairo;
931 cairo = cairo_create(surface);
932 cairo_surface_destroy(surface);
933 copyAntialias(cairo, old_cairo);
934
935 box.x1 = bbox[0];
936 box.y1 = bbox[1];
937 box.x2 = bbox[2];
938 box.y2 = bbox[3];
939 cairo_scale(cairo, scaleX, scaleY);
940 cairo_translate(cairo, -box.x1, -box.y1);
941
942 strokePathTmp = strokePathClip;
943 strokePathClip = nullptr;
944 adjusted_stroke_width_tmp = adjusted_stroke_width;
945 maskTmp = mask;
946 mask = nullptr;
947 gfx = new Gfx(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA);
948 if (paintType == 2)
949 inUncoloredPattern = true;
950 gfx->display(str);
951 if (paintType == 2)
952 inUncoloredPattern = false;
953 delete gfx;
954 strokePathClip = strokePathTmp;
955 adjusted_stroke_width = adjusted_stroke_width_tmp;
956 mask = maskTmp;
957
958 pattern = cairo_pattern_create_for_surface(cairo_get_target(cairo));
959 cairo_destroy(cairo);
960 cairo = old_cairo;
961 if (cairo_pattern_status(pattern))
962 return false;
963
964 // Cairo can fail if the pattern translation is too large. Fix by making the
965 // translation smaller.
966 const double det = pmat[0] * pmat[3] - pmat[1] * pmat[2];
967
968 // Find the number of repetitions of pattern we need to shift by. Transform
969 // the translation component of pmat (pmat[4] and pmat[5]) into the pattern's
970 // coordinate system by multiplying by inverse of pmat, then divide by
971 // pattern size (xStep and yStep).
972 const double xoffset = round((pmat[3] * pmat[4] - pmat[2] * pmat[5]) / (xStep * det));
973 const double yoffset = -round((pmat[1] * pmat[4] - pmat[0] * pmat[5]) / (yStep * det));
974
975 if (!std::isfinite(xoffset) || !std::isfinite(yoffset)) {
976 error(errSyntaxWarning, -1, "CairoOutputDev: Singular matrix in tilingPatternFill");
977 return false;
978 }
979
980 // Shift pattern_matrix by multiples of the pattern size.
981 pattern_matrix.x0 -= xoffset * pattern_matrix.xx * xStep + yoffset * pattern_matrix.xy * yStep;
982 pattern_matrix.y0 -= xoffset * pattern_matrix.yx * xStep + yoffset * pattern_matrix.yy * yStep;
983
984 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
985 cairo_rectangle(cairo, xMin, yMin, xMax - xMin, yMax - yMin);
986
987 cairo_matrix_init_scale(&matrix, scaleX, scaleY);
988 cairo_matrix_translate(&matrix, -box.x1, -box.y1);
989 cairo_pattern_set_matrix(pattern, &matrix);
990
991 cairo_transform(cairo, &pattern_matrix);
992 cairo_set_source(cairo, pattern);
993 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
994 if (strokePathClip) {
995 fillToStrokePathClip(state);
996 } else {
997 cairo_fill(cairo);
998 }
999
1000 cairo_pattern_destroy(pattern);
1001
1002 return true;
1003 }
1004
1005 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
functionShadedFill(GfxState * state,GfxFunctionShading * shading)1006 bool CairoOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading)
1007 {
1008 // Function shaded fills are subdivided to rectangles that are the
1009 // following size in device space. Note when printing this size is
1010 // in points.
1011 const int subdivide_pixels = 10;
1012
1013 double x_begin, x_end, x1, x2;
1014 double y_begin, y_end, y1, y2;
1015 double x_step;
1016 double y_step;
1017 GfxColor color;
1018 GfxRGB rgb;
1019 cairo_matrix_t mat;
1020
1021 const double *matrix = shading->getMatrix();
1022 mat.xx = matrix[0];
1023 mat.yx = matrix[1];
1024 mat.xy = matrix[2];
1025 mat.yy = matrix[3];
1026 mat.x0 = matrix[4];
1027 mat.y0 = matrix[5];
1028 if (cairo_matrix_invert(&mat)) {
1029 error(errSyntaxWarning, -1, "matrix not invertible\n");
1030 return false;
1031 }
1032
1033 // get cell size in pattern space
1034 x_step = y_step = subdivide_pixels;
1035 cairo_matrix_transform_distance(&mat, &x_step, &y_step);
1036
1037 cairo_pattern_destroy(fill_pattern);
1038 fill_pattern = cairo_pattern_create_mesh();
1039 cairo_pattern_set_matrix(fill_pattern, &mat);
1040 shading->getDomain(&x_begin, &y_begin, &x_end, &y_end);
1041
1042 for (x1 = x_begin; x1 < x_end; x1 += x_step) {
1043 x2 = x1 + x_step;
1044 if (x2 > x_end)
1045 x2 = x_end;
1046
1047 for (y1 = y_begin; y1 < y_end; y1 += y_step) {
1048 y2 = y1 + y_step;
1049 if (y2 > y_end)
1050 y2 = y_end;
1051
1052 cairo_mesh_pattern_begin_patch(fill_pattern);
1053 cairo_mesh_pattern_move_to(fill_pattern, x1, y1);
1054 cairo_mesh_pattern_line_to(fill_pattern, x2, y1);
1055 cairo_mesh_pattern_line_to(fill_pattern, x2, y2);
1056 cairo_mesh_pattern_line_to(fill_pattern, x1, y2);
1057
1058 shading->getColor(x1, y1, &color);
1059 shading->getColorSpace()->getRGB(&color, &rgb);
1060 cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 0, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b));
1061
1062 shading->getColor(x2, y1, &color);
1063 shading->getColorSpace()->getRGB(&color, &rgb);
1064 cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 1, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b));
1065
1066 shading->getColor(x2, y2, &color);
1067 shading->getColorSpace()->getRGB(&color, &rgb);
1068 cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 2, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b));
1069
1070 shading->getColor(x1, y2, &color);
1071 shading->getColorSpace()->getRGB(&color, &rgb);
1072 cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, 3, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b));
1073
1074 cairo_mesh_pattern_end_patch(fill_pattern);
1075 }
1076 }
1077
1078 double xMin, yMin, xMax, yMax;
1079 // get the clip region bbox
1080 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1081 state->moveTo(xMin, yMin);
1082 state->lineTo(xMin, yMax);
1083 state->lineTo(xMax, yMax);
1084 state->lineTo(xMax, yMin);
1085 state->closePath();
1086 fill(state);
1087 state->clearPath();
1088
1089 return true;
1090 }
1091 #endif /* CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) */
1092
axialShadedFill(GfxState * state,GfxAxialShading * shading,double tMin,double tMax)1093 bool CairoOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax)
1094 {
1095 double x0, y0, x1, y1;
1096 double dx, dy;
1097
1098 shading->getCoords(&x0, &y0, &x1, &y1);
1099 dx = x1 - x0;
1100 dy = y1 - y0;
1101
1102 cairo_pattern_destroy(fill_pattern);
1103 fill_pattern = cairo_pattern_create_linear(x0 + tMin * dx, y0 + tMin * dy, x0 + tMax * dx, y0 + tMax * dy);
1104 if (!shading->getExtend0() && !shading->getExtend1())
1105 cairo_pattern_set_extend(fill_pattern, CAIRO_EXTEND_NONE);
1106 else
1107 cairo_pattern_set_extend(fill_pattern, CAIRO_EXTEND_PAD);
1108
1109 LOG(printf("axial-sh\n"));
1110
1111 // TODO: use the actual stops in the shading in the case
1112 // of linear interpolation (Type 2 Exponential functions with N=1)
1113 return false;
1114 }
1115
axialShadedSupportExtend(GfxState * state,GfxAxialShading * shading)1116 bool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading)
1117 {
1118 return (shading->getExtend0() == shading->getExtend1());
1119 }
1120
radialShadedFill(GfxState * state,GfxRadialShading * shading,double sMin,double sMax)1121 bool CairoOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax)
1122 {
1123 double x0, y0, r0, x1, y1, r1;
1124 double dx, dy, dr;
1125 cairo_matrix_t matrix;
1126 double scale;
1127
1128 shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
1129 dx = x1 - x0;
1130 dy = y1 - y0;
1131 dr = r1 - r0;
1132
1133 // Cairo/pixman do not work well with a very large or small scaled
1134 // matrix. See cairo bug #81657.
1135 //
1136 // As a workaround, scale the pattern by the average of the vertical
1137 // and horizontal scaling of the current transformation matrix.
1138 cairo_get_matrix(cairo, &matrix);
1139 scale = (sqrt(matrix.xx * matrix.xx + matrix.yx * matrix.yx) + sqrt(matrix.xy * matrix.xy + matrix.yy * matrix.yy)) / 2;
1140 cairo_matrix_init_scale(&matrix, scale, scale);
1141
1142 cairo_pattern_destroy(fill_pattern);
1143 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);
1144 cairo_pattern_set_matrix(fill_pattern, &matrix);
1145 if (shading->getExtend0() && shading->getExtend1())
1146 cairo_pattern_set_extend(fill_pattern, CAIRO_EXTEND_PAD);
1147 else
1148 cairo_pattern_set_extend(fill_pattern, CAIRO_EXTEND_NONE);
1149
1150 LOG(printf("radial-sh\n"));
1151
1152 return false;
1153 }
1154
radialShadedSupportExtend(GfxState * state,GfxRadialShading * shading)1155 bool CairoOutputDev::radialShadedSupportExtend(GfxState *state, GfxRadialShading *shading)
1156 {
1157 return (shading->getExtend0() == shading->getExtend1());
1158 }
1159
1160 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
gouraudTriangleShadedFill(GfxState * state,GfxGouraudTriangleShading * shading)1161 bool CairoOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading)
1162 {
1163 double x0, y0, x1, y1, x2, y2;
1164 GfxColor color[3];
1165 int i, j;
1166 GfxRGB rgb;
1167
1168 cairo_pattern_destroy(fill_pattern);
1169 fill_pattern = cairo_pattern_create_mesh();
1170
1171 for (i = 0; i < shading->getNTriangles(); i++) {
1172 if (shading->isParameterized()) {
1173 double color0, color1, color2;
1174 shading->getTriangle(i, &x0, &y0, &color0, &x1, &y1, &color1, &x2, &y2, &color2);
1175 shading->getParameterizedColor(color0, &color[0]);
1176 shading->getParameterizedColor(color1, &color[1]);
1177 shading->getParameterizedColor(color2, &color[2]);
1178 } else {
1179 shading->getTriangle(i, &x0, &y0, &color[0], &x1, &y1, &color[1], &x2, &y2, &color[2]);
1180 }
1181
1182 cairo_mesh_pattern_begin_patch(fill_pattern);
1183
1184 cairo_mesh_pattern_move_to(fill_pattern, x0, y0);
1185 cairo_mesh_pattern_line_to(fill_pattern, x1, y1);
1186 cairo_mesh_pattern_line_to(fill_pattern, x2, y2);
1187
1188 for (j = 0; j < 3; j++) {
1189 shading->getColorSpace()->getRGB(&color[j], &rgb);
1190 cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, j, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b));
1191 }
1192
1193 cairo_mesh_pattern_end_patch(fill_pattern);
1194 }
1195
1196 double xMin, yMin, xMax, yMax;
1197 // get the clip region bbox
1198 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1199 state->moveTo(xMin, yMin);
1200 state->lineTo(xMin, yMax);
1201 state->lineTo(xMax, yMax);
1202 state->lineTo(xMax, yMin);
1203 state->closePath();
1204 fill(state);
1205 state->clearPath();
1206
1207 return true;
1208 }
1209
patchMeshShadedFill(GfxState * state,GfxPatchMeshShading * shading)1210 bool CairoOutputDev::patchMeshShadedFill(GfxState *state, GfxPatchMeshShading *shading)
1211 {
1212 int i, j, k;
1213
1214 cairo_pattern_destroy(fill_pattern);
1215 fill_pattern = cairo_pattern_create_mesh();
1216
1217 for (i = 0; i < shading->getNPatches(); i++) {
1218 const GfxPatch *patch = shading->getPatch(i);
1219 GfxColor color;
1220 GfxRGB rgb;
1221
1222 cairo_mesh_pattern_begin_patch(fill_pattern);
1223
1224 cairo_mesh_pattern_move_to(fill_pattern, patch->x[0][0], patch->y[0][0]);
1225 cairo_mesh_pattern_curve_to(fill_pattern, patch->x[0][1], patch->y[0][1], patch->x[0][2], patch->y[0][2], patch->x[0][3], patch->y[0][3]);
1226
1227 cairo_mesh_pattern_curve_to(fill_pattern, patch->x[1][3], patch->y[1][3], patch->x[2][3], patch->y[2][3], patch->x[3][3], patch->y[3][3]);
1228
1229 cairo_mesh_pattern_curve_to(fill_pattern, patch->x[3][2], patch->y[3][2], patch->x[3][1], patch->y[3][1], patch->x[3][0], patch->y[3][0]);
1230
1231 cairo_mesh_pattern_curve_to(fill_pattern, patch->x[2][0], patch->y[2][0], patch->x[1][0], patch->y[1][0], patch->x[0][0], patch->y[0][0]);
1232
1233 cairo_mesh_pattern_set_control_point(fill_pattern, 0, patch->x[1][1], patch->y[1][1]);
1234 cairo_mesh_pattern_set_control_point(fill_pattern, 1, patch->x[1][2], patch->y[1][2]);
1235 cairo_mesh_pattern_set_control_point(fill_pattern, 2, patch->x[2][2], patch->y[2][2]);
1236 cairo_mesh_pattern_set_control_point(fill_pattern, 3, patch->x[2][1], patch->y[2][1]);
1237
1238 for (j = 0; j < 4; j++) {
1239 int u, v;
1240
1241 switch (j) {
1242 case 0:
1243 u = 0;
1244 v = 0;
1245 break;
1246 case 1:
1247 u = 0;
1248 v = 1;
1249 break;
1250 case 2:
1251 u = 1;
1252 v = 1;
1253 break;
1254 case 3:
1255 u = 1;
1256 v = 0;
1257 break;
1258 }
1259
1260 if (shading->isParameterized()) {
1261 shading->getParameterizedColor(patch->color[u][v].c[0], &color);
1262 } else {
1263 for (k = 0; k < shading->getColorSpace()->getNComps(); k++) {
1264 // simply cast to the desired type; that's all what is needed.
1265 color.c[k] = GfxColorComp(patch->color[u][v].c[k]);
1266 }
1267 }
1268
1269 shading->getColorSpace()->getRGB(&color, &rgb);
1270 cairo_mesh_pattern_set_corner_color_rgb(fill_pattern, j, colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b));
1271 }
1272 cairo_mesh_pattern_end_patch(fill_pattern);
1273 }
1274
1275 double xMin, yMin, xMax, yMax;
1276 // get the clip region bbox
1277 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1278 state->moveTo(xMin, yMin);
1279 state->lineTo(xMin, yMax);
1280 state->lineTo(xMax, yMax);
1281 state->lineTo(xMax, yMin);
1282 state->closePath();
1283 fill(state);
1284 state->clearPath();
1285
1286 return true;
1287 }
1288 #endif /* CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) */
1289
clip(GfxState * state)1290 void CairoOutputDev::clip(GfxState *state)
1291 {
1292 doPath(cairo, state, state->getPath());
1293 cairo_set_fill_rule(cairo, CAIRO_FILL_RULE_WINDING);
1294 cairo_clip(cairo);
1295 LOG(printf("clip\n"));
1296 if (cairo_shape) {
1297 doPath(cairo_shape, state, state->getPath());
1298 cairo_set_fill_rule(cairo_shape, CAIRO_FILL_RULE_WINDING);
1299 cairo_clip(cairo_shape);
1300 }
1301 }
1302
eoClip(GfxState * state)1303 void CairoOutputDev::eoClip(GfxState *state)
1304 {
1305 doPath(cairo, state, state->getPath());
1306 cairo_set_fill_rule(cairo, CAIRO_FILL_RULE_EVEN_ODD);
1307 cairo_clip(cairo);
1308 LOG(printf("clip-eo\n"));
1309 if (cairo_shape) {
1310 doPath(cairo_shape, state, state->getPath());
1311 cairo_set_fill_rule(cairo_shape, CAIRO_FILL_RULE_EVEN_ODD);
1312 cairo_clip(cairo_shape);
1313 }
1314 }
1315
clipToStrokePath(GfxState * state)1316 void CairoOutputDev::clipToStrokePath(GfxState *state)
1317 {
1318 LOG(printf("clip-to-stroke-path\n"));
1319 strokePathClip = (StrokePathClip *)gmalloc(sizeof(*strokePathClip));
1320 strokePathClip->path = state->getPath()->copy();
1321 cairo_get_matrix(cairo, &strokePathClip->ctm);
1322 strokePathClip->line_width = cairo_get_line_width(cairo);
1323 strokePathClip->dash_count = cairo_get_dash_count(cairo);
1324 if (strokePathClip->dash_count) {
1325 strokePathClip->dashes = (double *)gmallocn(sizeof(double), strokePathClip->dash_count);
1326 cairo_get_dash(cairo, strokePathClip->dashes, &strokePathClip->dash_offset);
1327 } else {
1328 strokePathClip->dashes = nullptr;
1329 }
1330 strokePathClip->cap = cairo_get_line_cap(cairo);
1331 strokePathClip->join = cairo_get_line_join(cairo);
1332 strokePathClip->miter = cairo_get_miter_limit(cairo);
1333 strokePathClip->ref_count = 1;
1334 }
1335
fillToStrokePathClip(GfxState * state)1336 void CairoOutputDev::fillToStrokePathClip(GfxState *state)
1337 {
1338 cairo_save(cairo);
1339
1340 cairo_set_matrix(cairo, &strokePathClip->ctm);
1341 cairo_set_line_width(cairo, strokePathClip->line_width);
1342 cairo_set_dash(cairo, strokePathClip->dashes, strokePathClip->dash_count, strokePathClip->dash_offset);
1343 cairo_set_line_cap(cairo, strokePathClip->cap);
1344 cairo_set_line_join(cairo, strokePathClip->join);
1345 cairo_set_miter_limit(cairo, strokePathClip->miter);
1346 doPath(cairo, state, strokePathClip->path);
1347 cairo_stroke(cairo);
1348
1349 cairo_restore(cairo);
1350 }
1351
beginString(GfxState * state,const GooString * s)1352 void CairoOutputDev::beginString(GfxState *state, const GooString *s)
1353 {
1354 int len = s->getLength();
1355
1356 if (needFontUpdate)
1357 updateFont(state);
1358
1359 if (!currentFont)
1360 return;
1361
1362 glyphs = (cairo_glyph_t *)gmallocn(len, sizeof(cairo_glyph_t));
1363 glyphCount = 0;
1364 if (use_show_text_glyphs) {
1365 clusters = (cairo_text_cluster_t *)gmallocn(len, sizeof(cairo_text_cluster_t));
1366 clusterCount = 0;
1367 utf8Max = len * 2; // start with twice the number of glyphs. we will realloc if we need more.
1368 utf8 = (char *)gmalloc(utf8Max);
1369 utf8Count = 0;
1370 }
1371 }
1372
drawChar(GfxState * state,double x,double y,double dx,double dy,double originX,double originY,CharCode code,int nBytes,const Unicode * u,int uLen)1373 void CairoOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, const Unicode *u, int uLen)
1374 {
1375 if (currentFont) {
1376 glyphs[glyphCount].index = currentFont->getGlyph(code, u, uLen);
1377 glyphs[glyphCount].x = x - originX;
1378 glyphs[glyphCount].y = y - originY;
1379 glyphCount++;
1380 if (use_show_text_glyphs) {
1381 const UnicodeMap *utf8Map = globalParams->getUtf8Map();
1382 if (utf8Max - utf8Count < uLen * 6) {
1383 // utf8 encoded characters can be up to 6 bytes
1384 if (utf8Max > uLen * 6)
1385 utf8Max *= 2;
1386 else
1387 utf8Max += 2 * uLen * 6;
1388 utf8 = (char *)grealloc(utf8, utf8Max);
1389 }
1390 clusters[clusterCount].num_bytes = 0;
1391 for (int i = 0; i < uLen; i++) {
1392 int size = utf8Map->mapUnicode(u[i], utf8 + utf8Count, utf8Max - utf8Count);
1393 utf8Count += size;
1394 clusters[clusterCount].num_bytes += size;
1395 }
1396 clusters[clusterCount].num_glyphs = 1;
1397 clusterCount++;
1398 }
1399 }
1400
1401 if (!textPage)
1402 return;
1403 actualText->addChar(state, x, y, dx, dy, code, nBytes, u, uLen);
1404 }
1405
endString(GfxState * state)1406 void CairoOutputDev::endString(GfxState *state)
1407 {
1408 int render;
1409
1410 if (!currentFont)
1411 return;
1412
1413 // endString can be called without a corresponding beginString. If this
1414 // happens glyphs will be null so don't draw anything, just return.
1415 // XXX: OutputDevs should probably not have to deal with this...
1416 if (!glyphs)
1417 return;
1418
1419 // ignore empty strings and invisible text -- this is used by
1420 // Acrobat Capture
1421 render = state->getRender();
1422 if (render == 3 || glyphCount == 0 || !text_matrix_valid) {
1423 goto finish;
1424 }
1425
1426 if (!(render & 1)) {
1427 LOG(printf("fill string\n"));
1428 cairo_set_source(cairo, fill_pattern);
1429 if (use_show_text_glyphs)
1430 cairo_show_text_glyphs(cairo, utf8, utf8Count, glyphs, glyphCount, clusters, clusterCount, (cairo_text_cluster_flags_t)0);
1431 else
1432 cairo_show_glyphs(cairo, glyphs, glyphCount);
1433 if (cairo_shape)
1434 cairo_show_glyphs(cairo_shape, glyphs, glyphCount);
1435 }
1436
1437 // stroke
1438 if ((render & 3) == 1 || (render & 3) == 2) {
1439 LOG(printf("stroke string\n"));
1440 cairo_set_source(cairo, stroke_pattern);
1441 cairo_glyph_path(cairo, glyphs, glyphCount);
1442 cairo_stroke(cairo);
1443 if (cairo_shape) {
1444 cairo_glyph_path(cairo_shape, glyphs, glyphCount);
1445 cairo_stroke(cairo_shape);
1446 }
1447 }
1448
1449 // clip
1450 if ((render & 4)) {
1451 LOG(printf("clip string\n"));
1452 // append the glyph path to textClipPath.
1453
1454 // set textClipPath as the currentPath
1455 if (textClipPath) {
1456 cairo_append_path(cairo, textClipPath);
1457 if (cairo_shape) {
1458 cairo_append_path(cairo_shape, textClipPath);
1459 }
1460 cairo_path_destroy(textClipPath);
1461 }
1462
1463 // append the glyph path
1464 cairo_glyph_path(cairo, glyphs, glyphCount);
1465
1466 // move the path back into textClipPath
1467 // and clear the current path
1468 textClipPath = cairo_copy_path(cairo);
1469 cairo_new_path(cairo);
1470 if (cairo_shape) {
1471 cairo_new_path(cairo_shape);
1472 }
1473 }
1474
1475 finish:
1476 gfree(glyphs);
1477 glyphs = nullptr;
1478 if (use_show_text_glyphs) {
1479 gfree(clusters);
1480 clusters = nullptr;
1481 gfree(utf8);
1482 utf8 = nullptr;
1483 }
1484 }
1485
beginType3Char(GfxState * state,double x,double y,double dx,double dy,CharCode code,const Unicode * u,int uLen)1486 bool CairoOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, const Unicode *u, int uLen)
1487 {
1488
1489 cairo_save(cairo);
1490 cairo_matrix_t matrix;
1491
1492 const double *ctm = state->getCTM();
1493 matrix.xx = ctm[0];
1494 matrix.yx = ctm[1];
1495 matrix.xy = ctm[2];
1496 matrix.yy = ctm[3];
1497 matrix.x0 = ctm[4];
1498 matrix.y0 = ctm[5];
1499 /* Restore the original matrix and then transform to matrix needed for the
1500 * type3 font. This is ugly but seems to work. Perhaps there is a better way to do it?*/
1501 cairo_set_matrix(cairo, &orig_matrix);
1502 cairo_transform(cairo, &matrix);
1503 if (cairo_shape) {
1504 cairo_save(cairo_shape);
1505 cairo_set_matrix(cairo_shape, &orig_matrix);
1506 cairo_transform(cairo_shape, &matrix);
1507 }
1508 cairo_pattern_destroy(stroke_pattern);
1509 cairo_pattern_reference(fill_pattern);
1510 stroke_pattern = fill_pattern;
1511 return false;
1512 }
1513
endType3Char(GfxState * state)1514 void CairoOutputDev::endType3Char(GfxState *state)
1515 {
1516 cairo_restore(cairo);
1517 if (cairo_shape) {
1518 cairo_restore(cairo_shape);
1519 }
1520 }
1521
type3D0(GfxState * state,double wx,double wy)1522 void CairoOutputDev::type3D0(GfxState *state, double wx, double wy)
1523 {
1524 t3_glyph_wx = wx;
1525 t3_glyph_wy = wy;
1526 }
1527
type3D1(GfxState * state,double wx,double wy,double llx,double lly,double urx,double ury)1528 void CairoOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury)
1529 {
1530 t3_glyph_wx = wx;
1531 t3_glyph_wy = wy;
1532 t3_glyph_bbox[0] = llx;
1533 t3_glyph_bbox[1] = lly;
1534 t3_glyph_bbox[2] = urx;
1535 t3_glyph_bbox[3] = ury;
1536 t3_glyph_has_bbox = true;
1537 }
1538
beginTextObject(GfxState * state)1539 void CairoOutputDev::beginTextObject(GfxState *state) { }
1540
endTextObject(GfxState * state)1541 void CairoOutputDev::endTextObject(GfxState *state)
1542 {
1543 if (textClipPath) {
1544 // clip the accumulated text path
1545 cairo_append_path(cairo, textClipPath);
1546 cairo_clip(cairo);
1547 if (cairo_shape) {
1548 cairo_append_path(cairo_shape, textClipPath);
1549 cairo_clip(cairo_shape);
1550 }
1551 cairo_path_destroy(textClipPath);
1552 textClipPath = nullptr;
1553 }
1554 }
1555
beginActualText(GfxState * state,const GooString * text)1556 void CairoOutputDev::beginActualText(GfxState *state, const GooString *text)
1557 {
1558 if (textPage)
1559 actualText->begin(state, text);
1560 }
1561
endActualText(GfxState * state)1562 void CairoOutputDev::endActualText(GfxState *state)
1563 {
1564 if (textPage)
1565 actualText->end(state);
1566 }
1567
splashRound(SplashCoord x)1568 static inline int splashRound(SplashCoord x)
1569 {
1570 return (int)floor(x + 0.5);
1571 }
1572
splashCeil(SplashCoord x)1573 static inline int splashCeil(SplashCoord x)
1574 {
1575 return (int)ceil(x);
1576 }
1577
splashFloor(SplashCoord x)1578 static inline int splashFloor(SplashCoord x)
1579 {
1580 return (int)floor(x);
1581 }
1582
cairo_surface_create_similar_clip(cairo_t * cairo,cairo_content_t content)1583 static cairo_surface_t *cairo_surface_create_similar_clip(cairo_t *cairo, cairo_content_t content)
1584 {
1585 cairo_pattern_t *pattern;
1586 cairo_surface_t *surface = nullptr;
1587
1588 cairo_push_group_with_content(cairo, content);
1589 pattern = cairo_pop_group(cairo);
1590 cairo_pattern_get_surface(pattern, &surface);
1591 cairo_surface_reference(surface);
1592 cairo_pattern_destroy(pattern);
1593 return surface;
1594 }
1595
beginTransparencyGroup(GfxState *,const double *,GfxColorSpace * blendingColorSpace,bool,bool knockout,bool forSoftMask)1596 void CairoOutputDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/, GfxColorSpace *blendingColorSpace, bool /*isolated*/, bool knockout, bool forSoftMask)
1597 {
1598 /* push color space */
1599 ColorSpaceStack *css = new ColorSpaceStack;
1600 css->cs = blendingColorSpace;
1601 css->knockout = knockout;
1602 cairo_get_matrix(cairo, &css->group_matrix);
1603 css->next = groupColorSpaceStack;
1604 groupColorSpaceStack = css;
1605
1606 LOG(printf("begin transparency group. knockout: %s\n", knockout ? "yes" : "no"));
1607
1608 if (knockout) {
1609 knockoutCount++;
1610 if (!cairo_shape) {
1611 /* create a surface for tracking the shape */
1612 cairo_surface_t *cairo_shape_surface = cairo_surface_create_similar_clip(cairo, CAIRO_CONTENT_ALPHA);
1613 cairo_shape = cairo_create(cairo_shape_surface);
1614 cairo_surface_destroy(cairo_shape_surface);
1615 copyAntialias(cairo_shape, cairo);
1616
1617 /* the color doesn't matter as long as it is opaque */
1618 cairo_set_source_rgb(cairo_shape, 0, 0, 0);
1619 cairo_matrix_t matrix;
1620 cairo_get_matrix(cairo, &matrix);
1621 cairo_set_matrix(cairo_shape, &matrix);
1622 }
1623 }
1624 if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) {
1625 /* we need to track the shape */
1626 cairo_push_group(cairo_shape);
1627 }
1628 if (false && forSoftMask)
1629 cairo_push_group_with_content(cairo, CAIRO_CONTENT_ALPHA);
1630 else
1631 cairo_push_group(cairo);
1632
1633 /* push_group has an implicit cairo_save() */
1634 if (knockout) {
1635 /*XXX: let's hope this matches the semantics needed */
1636 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
1637 } else {
1638 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
1639 }
1640 }
1641
endTransparencyGroup(GfxState *)1642 void CairoOutputDev::endTransparencyGroup(GfxState * /*state*/)
1643 {
1644 if (group)
1645 cairo_pattern_destroy(group);
1646 group = cairo_pop_group(cairo);
1647
1648 LOG(printf("end transparency group\n"));
1649
1650 if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) {
1651 if (shape)
1652 cairo_pattern_destroy(shape);
1653 shape = cairo_pop_group(cairo_shape);
1654 }
1655 }
1656
paintTransparencyGroup(GfxState *,const double *)1657 void CairoOutputDev::paintTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/)
1658 {
1659 LOG(printf("paint transparency group\n"));
1660
1661 cairo_save(cairo);
1662 cairo_set_matrix(cairo, &groupColorSpaceStack->group_matrix);
1663
1664 if (shape) {
1665 /* OPERATOR_SOURCE w/ a mask is defined as (src IN mask) ADD (dest OUT mask)
1666 * however our source has already been clipped to mask so we only need to
1667 * do ADD and OUT */
1668
1669 /* clear the shape mask */
1670 cairo_set_source(cairo, shape);
1671 cairo_set_operator(cairo, CAIRO_OPERATOR_DEST_OUT);
1672 cairo_paint(cairo);
1673 cairo_set_operator(cairo, CAIRO_OPERATOR_ADD);
1674 }
1675 cairo_set_source(cairo, group);
1676
1677 if (!mask) {
1678 cairo_paint_with_alpha(cairo, fill_opacity);
1679 cairo_status_t status = cairo_status(cairo);
1680 if (status)
1681 printf("BAD status: %s\n", cairo_status_to_string(status));
1682 } else {
1683 if (fill_opacity < 1.0) {
1684 cairo_push_group(cairo);
1685 }
1686 cairo_save(cairo);
1687 cairo_set_matrix(cairo, &mask_matrix);
1688 cairo_mask(cairo, mask);
1689 cairo_restore(cairo);
1690 if (fill_opacity < 1.0) {
1691 cairo_pop_group_to_source(cairo);
1692 cairo_paint_with_alpha(cairo, fill_opacity);
1693 }
1694 cairo_pattern_destroy(mask);
1695 mask = nullptr;
1696 }
1697
1698 if (shape) {
1699 if (cairo_shape) {
1700 cairo_set_source(cairo_shape, shape);
1701 cairo_paint(cairo_shape);
1702 cairo_set_source_rgb(cairo_shape, 0, 0, 0);
1703 }
1704 cairo_pattern_destroy(shape);
1705 shape = nullptr;
1706 }
1707
1708 popTransparencyGroup();
1709 cairo_restore(cairo);
1710 }
1711
luminocity(uint32_t x)1712 static int luminocity(uint32_t x)
1713 {
1714 int r = (x >> 16) & 0xff;
1715 int g = (x >> 8) & 0xff;
1716 int b = (x >> 0) & 0xff;
1717 // an arbitrary integer approximation of .3*r + .59*g + .11*b
1718 int y = (r * 19661 + g * 38666 + b * 7209 + 32829) >> 16;
1719 return y;
1720 }
1721
1722 /* XXX: do we need to deal with shape here? */
setSoftMask(GfxState * state,const double * bbox,bool alpha,Function * transferFunc,GfxColor * backdropColor)1723 void CairoOutputDev::setSoftMask(GfxState *state, const double *bbox, bool alpha, Function *transferFunc, GfxColor *backdropColor)
1724 {
1725 cairo_pattern_destroy(mask);
1726
1727 LOG(printf("set softMask\n"));
1728
1729 if (!alpha || transferFunc) {
1730 /* We need to mask according to the luminocity of the group.
1731 * So we paint the group to an image surface convert it to a luminocity map
1732 * and then use that as the mask. */
1733
1734 /* Get clip extents in device space */
1735 double x1, y1, x2, y2, x_min, y_min, x_max, y_max;
1736 cairo_clip_extents(cairo, &x1, &y1, &x2, &y2);
1737 cairo_user_to_device(cairo, &x1, &y1);
1738 cairo_user_to_device(cairo, &x2, &y2);
1739 x_min = MIN(x1, x2);
1740 y_min = MIN(y1, y2);
1741 x_max = MAX(x1, x2);
1742 y_max = MAX(y1, y2);
1743 cairo_clip_extents(cairo, &x1, &y1, &x2, &y2);
1744 cairo_user_to_device(cairo, &x1, &y2);
1745 cairo_user_to_device(cairo, &x2, &y1);
1746 x_min = MIN(x_min, MIN(x1, x2));
1747 y_min = MIN(y_min, MIN(y1, y2));
1748 x_max = MAX(x_max, MAX(x1, x2));
1749 y_max = MAX(y_max, MAX(y1, y2));
1750
1751 int width = (int)(ceil(x_max) - floor(x_min));
1752 int height = (int)(ceil(y_max) - floor(y_min));
1753
1754 /* Get group device offset */
1755 double x_offset, y_offset;
1756 if (cairo_get_group_target(cairo) == cairo_get_target(cairo)) {
1757 cairo_surface_get_device_offset(cairo_get_group_target(cairo), &x_offset, &y_offset);
1758 } else {
1759 cairo_surface_t *pats;
1760 cairo_pattern_get_surface(group, &pats);
1761 cairo_surface_get_device_offset(pats, &x_offset, &y_offset);
1762 }
1763
1764 /* Adjust extents by group offset */
1765 x_min += x_offset;
1766 y_min += y_offset;
1767
1768 cairo_surface_t *source = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1769 cairo_t *maskCtx = cairo_create(source);
1770 copyAntialias(maskCtx, cairo);
1771
1772 // XXX: hopefully this uses the correct color space */
1773 if (!alpha && groupColorSpaceStack->cs) {
1774 GfxRGB backdropColorRGB;
1775 groupColorSpaceStack->cs->getRGB(backdropColor, &backdropColorRGB);
1776 /* paint the backdrop */
1777 cairo_set_source_rgb(maskCtx, colToDbl(backdropColorRGB.r), colToDbl(backdropColorRGB.g), colToDbl(backdropColorRGB.b));
1778 }
1779 cairo_paint(maskCtx);
1780
1781 /* Copy source ctm to mask ctm and translate origin so that the
1782 * mask appears it the same location on the source surface. */
1783 cairo_matrix_t mat, tmat;
1784 cairo_matrix_init_translate(&tmat, -x_min, -y_min);
1785 cairo_get_matrix(cairo, &mat);
1786 cairo_matrix_multiply(&mat, &mat, &tmat);
1787 cairo_set_matrix(maskCtx, &mat);
1788
1789 /* make the device offset of the new mask match that of the group */
1790 cairo_surface_set_device_offset(source, x_offset, y_offset);
1791
1792 /* paint the group */
1793 cairo_set_source(maskCtx, group);
1794 cairo_paint(maskCtx);
1795
1796 /* XXX status = cairo_status(maskCtx); */
1797 cairo_destroy(maskCtx);
1798
1799 /* convert to a luminocity map */
1800 uint32_t *source_data = reinterpret_cast<uint32_t *>(cairo_image_surface_get_data(source));
1801 /* get stride in units of 32 bits */
1802 ptrdiff_t stride = cairo_image_surface_get_stride(source) / 4;
1803 for (int y = 0; y < height; y++) {
1804 for (int x = 0; x < width; x++) {
1805 int lum = alpha ? fill_opacity : luminocity(source_data[y * stride + x]);
1806 if (transferFunc) {
1807 double lum_in, lum_out;
1808 lum_in = lum / 256.0;
1809 transferFunc->transform(&lum_in, &lum_out);
1810 lum = (int)(lum_out * 255.0 + 0.5);
1811 }
1812 source_data[y * stride + x] = lum << 24;
1813 }
1814 }
1815 cairo_surface_mark_dirty(source);
1816
1817 /* setup the new mask pattern */
1818 mask = cairo_pattern_create_for_surface(source);
1819 cairo_get_matrix(cairo, &mask_matrix);
1820
1821 if (cairo_get_group_target(cairo) == cairo_get_target(cairo)) {
1822 cairo_pattern_set_matrix(mask, &mat);
1823 } else {
1824 cairo_matrix_t patMatrix;
1825 cairo_pattern_get_matrix(group, &patMatrix);
1826 /* Apply x_min, y_min offset to it appears in the same location as source. */
1827 cairo_matrix_multiply(&patMatrix, &patMatrix, &tmat);
1828 cairo_pattern_set_matrix(mask, &patMatrix);
1829 }
1830
1831 cairo_surface_destroy(source);
1832 } else if (alpha) {
1833 mask = cairo_pattern_reference(group);
1834 cairo_get_matrix(cairo, &mask_matrix);
1835 }
1836
1837 popTransparencyGroup();
1838 }
1839
popTransparencyGroup()1840 void CairoOutputDev::popTransparencyGroup()
1841 {
1842 /* pop color space */
1843 ColorSpaceStack *css = groupColorSpaceStack;
1844 if (css->knockout) {
1845 knockoutCount--;
1846 if (!knockoutCount) {
1847 /* we don't need to track the shape anymore because
1848 * we are not above any knockout groups */
1849 cairo_destroy(cairo_shape);
1850 cairo_shape = nullptr;
1851 }
1852 }
1853 groupColorSpaceStack = css->next;
1854 delete css;
1855 }
1856
clearSoftMask(GfxState *)1857 void CairoOutputDev::clearSoftMask(GfxState * /*state*/)
1858 {
1859 if (mask)
1860 cairo_pattern_destroy(mask);
1861 mask = nullptr;
1862 }
1863
1864 /* Taken from cairo/doc/tutorial/src/singular.c */
get_singular_values(const cairo_matrix_t * matrix,double * major,double * minor)1865 static void get_singular_values(const cairo_matrix_t *matrix, double *major, double *minor)
1866 {
1867 double xx = matrix->xx, xy = matrix->xy;
1868 double yx = matrix->yx, yy = matrix->yy;
1869
1870 double a = xx * xx + yx * yx;
1871 double b = xy * xy + yy * yy;
1872 double k = xx * xy + yx * yy;
1873
1874 double f = (a + b) * .5;
1875 double g = (a - b) * .5;
1876 double delta = sqrt(g * g + k * k);
1877
1878 if (major)
1879 *major = sqrt(f + delta);
1880 if (minor)
1881 *minor = sqrt(f - delta);
1882 }
1883
getScaledSize(const cairo_matrix_t * matrix,int orig_width,int orig_height,int * scaledWidth,int * scaledHeight)1884 void CairoOutputDev::getScaledSize(const cairo_matrix_t *matrix, int orig_width, int orig_height, int *scaledWidth, int *scaledHeight)
1885 {
1886 double xScale;
1887 double yScale;
1888 if (orig_width > orig_height)
1889 get_singular_values(matrix, &xScale, &yScale);
1890 else
1891 get_singular_values(matrix, &yScale, &xScale);
1892
1893 int tx, tx2, ty, ty2; /* the integer co-ordinates of the resulting image */
1894 if (xScale >= 0) {
1895 tx = splashRound(matrix->x0 - 0.01);
1896 tx2 = splashRound(matrix->x0 + xScale + 0.01) - 1;
1897 } else {
1898 tx = splashRound(matrix->x0 + 0.01) - 1;
1899 tx2 = splashRound(matrix->x0 + xScale - 0.01);
1900 }
1901 *scaledWidth = abs(tx2 - tx) + 1;
1902 // scaledWidth = splashRound(fabs(xScale));
1903 if (*scaledWidth == 0) {
1904 // technically, this should draw nothing, but it generally seems
1905 // better to draw a one-pixel-wide stripe rather than throwing it
1906 // away
1907 *scaledWidth = 1;
1908 }
1909 if (yScale >= 0) {
1910 ty = splashFloor(matrix->y0 + 0.01);
1911 ty2 = splashCeil(matrix->y0 + yScale - 0.01);
1912 } else {
1913 ty = splashCeil(matrix->y0 - 0.01);
1914 ty2 = splashFloor(matrix->y0 + yScale + 0.01);
1915 }
1916 *scaledHeight = abs(ty2 - ty);
1917 if (*scaledHeight == 0) {
1918 *scaledHeight = 1;
1919 }
1920 }
1921
getFilterForSurface(cairo_surface_t * image,bool interpolate)1922 cairo_filter_t CairoOutputDev::getFilterForSurface(cairo_surface_t *image, bool interpolate)
1923 {
1924 if (interpolate)
1925 return CAIRO_FILTER_GOOD;
1926
1927 int orig_width = cairo_image_surface_get_width(image);
1928 int orig_height = cairo_image_surface_get_height(image);
1929 if (orig_width == 0 || orig_height == 0)
1930 return CAIRO_FILTER_NEAREST;
1931
1932 /* When printing, don't change the interpolation. */
1933 if (printing)
1934 return CAIRO_FILTER_NEAREST;
1935
1936 cairo_matrix_t matrix;
1937 cairo_get_matrix(cairo, &matrix);
1938 int scaled_width, scaled_height;
1939 getScaledSize(&matrix, orig_width, orig_height, &scaled_width, &scaled_height);
1940
1941 /* When scale factor is >= 400% we don't interpolate. See bugs #25268, #9860 */
1942 if (scaled_width / orig_width >= 4 || scaled_height / orig_height >= 4)
1943 return CAIRO_FILTER_NEAREST;
1944
1945 return CAIRO_FILTER_GOOD;
1946 }
1947
drawImageMask(GfxState * state,Object * ref,Stream * str,int width,int height,bool invert,bool interpolate,bool inlineImg)1948 void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg)
1949 {
1950
1951 /* FIXME: Doesn't the image mask support any colorspace? */
1952 cairo_set_source(cairo, fill_pattern);
1953
1954 /* work around a cairo bug when scaling 1x1 surfaces */
1955 if (width == 1 && height == 1) {
1956 ImageStream *imgStr;
1957 unsigned char pix;
1958 int invert_bit;
1959
1960 imgStr = new ImageStream(str, width, 1, 1);
1961 imgStr->reset();
1962 imgStr->getPixel(&pix);
1963 imgStr->close();
1964 delete imgStr;
1965
1966 invert_bit = invert ? 1 : 0;
1967 if (pix ^ invert_bit)
1968 return;
1969
1970 cairo_save(cairo);
1971 cairo_rectangle(cairo, 0., 0., width, height);
1972 cairo_fill(cairo);
1973 cairo_restore(cairo);
1974 if (cairo_shape) {
1975 cairo_save(cairo_shape);
1976 cairo_rectangle(cairo_shape, 0., 0., width, height);
1977 cairo_fill(cairo_shape);
1978 cairo_restore(cairo_shape);
1979 }
1980 return;
1981 }
1982
1983 /* shape is 1.0 for painted areas, 0.0 for unpainted ones */
1984
1985 cairo_matrix_t matrix;
1986 cairo_get_matrix(cairo, &matrix);
1987 // XXX: it is possible that we should only do sub pixel positioning if
1988 // we are rendering fonts */
1989 if (!printing
1990 && prescaleImages
1991 /* not rotated */
1992 && matrix.xy == 0
1993 && matrix.yx == 0
1994 /* axes not flipped / not 180 deg rotated */
1995 && matrix.xx > 0 && (upsideDown() ? -1 : 1) * matrix.yy > 0) {
1996 drawImageMaskPrescaled(state, ref, str, width, height, invert, interpolate, inlineImg);
1997 } else {
1998 drawImageMaskRegular(state, ref, str, width, height, invert, interpolate, inlineImg);
1999 }
2000 }
2001
setSoftMaskFromImageMask(GfxState * state,Object * ref,Stream * str,int width,int height,bool invert,bool inlineImg,double * baseMatrix)2002 void CairoOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg, double *baseMatrix)
2003 {
2004
2005 /* FIXME: Doesn't the image mask support any colorspace? */
2006 cairo_set_source(cairo, fill_pattern);
2007
2008 /* work around a cairo bug when scaling 1x1 surfaces */
2009 if (width == 1 && height == 1) {
2010 ImageStream *imgStr;
2011 unsigned char pix;
2012 int invert_bit;
2013
2014 imgStr = new ImageStream(str, width, 1, 1);
2015 imgStr->reset();
2016 imgStr->getPixel(&pix);
2017 imgStr->close();
2018 delete imgStr;
2019
2020 invert_bit = invert ? 1 : 0;
2021 if (!(pix ^ invert_bit)) {
2022 cairo_save(cairo);
2023 cairo_rectangle(cairo, 0., 0., width, height);
2024 cairo_fill(cairo);
2025 cairo_restore(cairo);
2026 if (cairo_shape) {
2027 cairo_save(cairo_shape);
2028 cairo_rectangle(cairo_shape, 0., 0., width, height);
2029 cairo_fill(cairo_shape);
2030 cairo_restore(cairo_shape);
2031 }
2032 }
2033 } else {
2034 cairo_push_group_with_content(cairo, CAIRO_CONTENT_ALPHA);
2035
2036 /* shape is 1.0 for painted areas, 0.0 for unpainted ones */
2037
2038 cairo_matrix_t matrix;
2039 cairo_get_matrix(cairo, &matrix);
2040 // XXX: it is possible that we should only do sub pixel positioning if
2041 // we are rendering fonts */
2042 if (!printing && prescaleImages && matrix.xy == 0.0 && matrix.yx == 0.0) {
2043 drawImageMaskPrescaled(state, ref, str, width, height, invert, false, inlineImg);
2044 } else {
2045 drawImageMaskRegular(state, ref, str, width, height, invert, false, inlineImg);
2046 }
2047
2048 if (state->getFillColorSpace()->getMode() == csPattern) {
2049 cairo_set_source_rgb(cairo, 1, 1, 1);
2050 cairo_set_matrix(cairo, &mask_matrix);
2051 cairo_mask(cairo, mask);
2052 }
2053
2054 if (mask)
2055 cairo_pattern_destroy(mask);
2056 mask = cairo_pop_group(cairo);
2057 }
2058
2059 saveState(state);
2060 double bbox[4] = { 0, 0, 1, 1 }; // dummy
2061 beginTransparencyGroup(state, bbox, state->getFillColorSpace(), true, false, false);
2062 }
2063
unsetSoftMaskFromImageMask(GfxState * state,double * baseMatrix)2064 void CairoOutputDev::unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix)
2065 {
2066 double bbox[4] = { 0, 0, 1, 1 }; // dummy
2067
2068 endTransparencyGroup(state);
2069 restoreState(state);
2070 paintTransparencyGroup(state, bbox);
2071 clearSoftMask(state);
2072 }
2073
drawImageMaskRegular(GfxState * state,Object * ref,Stream * str,int width,int height,bool invert,bool interpolate,bool inlineImg)2074 void CairoOutputDev::drawImageMaskRegular(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg)
2075 {
2076 unsigned char *buffer;
2077 unsigned char *dest;
2078 cairo_surface_t *image;
2079 cairo_pattern_t *pattern;
2080 int x, y, i, bit;
2081 ImageStream *imgStr;
2082 unsigned char *pix;
2083 cairo_matrix_t matrix;
2084 int invert_bit;
2085 ptrdiff_t row_stride;
2086 cairo_filter_t filter;
2087
2088 /* TODO: Do we want to cache these? */
2089 imgStr = new ImageStream(str, width, 1, 1);
2090 imgStr->reset();
2091
2092 image = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height);
2093 if (cairo_surface_status(image))
2094 goto cleanup;
2095
2096 buffer = cairo_image_surface_get_data(image);
2097 row_stride = cairo_image_surface_get_stride(image);
2098
2099 invert_bit = invert ? 1 : 0;
2100
2101 for (y = 0; y < height; y++) {
2102 pix = imgStr->getLine();
2103 dest = buffer + y * row_stride;
2104 i = 0;
2105 bit = 0;
2106 for (x = 0; x < width; x++) {
2107 if (bit == 0)
2108 dest[i] = 0;
2109 if (!(pix[x] ^ invert_bit)) {
2110 #ifdef WORDS_BIGENDIAN
2111 dest[i] |= (1 << (7 - bit));
2112 #else
2113 dest[i] |= (1 << bit);
2114 #endif
2115 }
2116 bit++;
2117 if (bit > 7) {
2118 bit = 0;
2119 i++;
2120 }
2121 }
2122 }
2123
2124 filter = getFilterForSurface(image, interpolate);
2125
2126 cairo_surface_mark_dirty(image);
2127 pattern = cairo_pattern_create_for_surface(image);
2128 cairo_surface_destroy(image);
2129 if (cairo_pattern_status(pattern))
2130 goto cleanup;
2131
2132 LOG(printf("drawImageMask %dx%d\n", width, height));
2133
2134 cairo_pattern_set_filter(pattern, filter);
2135
2136 cairo_matrix_init_translate(&matrix, 0, height);
2137 cairo_matrix_scale(&matrix, width, -height);
2138 cairo_pattern_set_matrix(pattern, &matrix);
2139 if (cairo_pattern_status(pattern)) {
2140 cairo_pattern_destroy(pattern);
2141 goto cleanup;
2142 }
2143
2144 if (state->getFillColorSpace()->getMode() == csPattern) {
2145 mask = cairo_pattern_reference(pattern);
2146 cairo_get_matrix(cairo, &mask_matrix);
2147 } else if (!printing) {
2148 cairo_save(cairo);
2149 cairo_rectangle(cairo, 0., 0., 1., 1.);
2150 cairo_clip(cairo);
2151 if (strokePathClip) {
2152 cairo_push_group(cairo);
2153 fillToStrokePathClip(state);
2154 cairo_pop_group_to_source(cairo);
2155 }
2156 cairo_mask(cairo, pattern);
2157 cairo_restore(cairo);
2158 } else {
2159 cairo_mask(cairo, pattern);
2160 }
2161
2162 if (cairo_shape) {
2163 cairo_save(cairo_shape);
2164 cairo_set_source(cairo_shape, pattern);
2165 if (!printing) {
2166 cairo_rectangle(cairo_shape, 0., 0., 1., 1.);
2167 cairo_fill(cairo_shape);
2168 } else {
2169 cairo_mask(cairo_shape, pattern);
2170 }
2171 cairo_restore(cairo_shape);
2172 }
2173
2174 cairo_pattern_destroy(pattern);
2175
2176 cleanup:
2177 imgStr->close();
2178 delete imgStr;
2179 }
2180
drawImageMaskPrescaled(GfxState * state,Object * ref,Stream * str,int width,int height,bool invert,bool interpolate,bool inlineImg)2181 void CairoOutputDev::drawImageMaskPrescaled(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg)
2182 {
2183 unsigned char *buffer;
2184 cairo_surface_t *image;
2185 cairo_pattern_t *pattern;
2186 ImageStream *imgStr;
2187 unsigned char *pix;
2188 cairo_matrix_t matrix;
2189 int invert_bit;
2190 ptrdiff_t row_stride;
2191
2192 /* cairo does a very poor job of scaling down images so we scale them ourselves */
2193
2194 LOG(printf("drawImageMaskPrescaled %dx%d\n", width, height));
2195
2196 /* this scaling code is adopted from the splash image scaling code */
2197 cairo_get_matrix(cairo, &matrix);
2198 #if 0
2199 printf("[%f %f], [%f %f], %f %f\n", matrix.xx, matrix.xy, matrix.yx, matrix.yy, matrix.x0, matrix.y0);
2200 #endif
2201 /* this whole computation should be factored out */
2202 double xScale = matrix.xx;
2203 double yScale = matrix.yy;
2204 int tx, tx2, ty, ty2; /* the integer co-ordinates of the resulting image */
2205 int scaledHeight;
2206 int scaledWidth;
2207 if (xScale >= 0) {
2208 tx = splashRound(matrix.x0 - 0.01);
2209 tx2 = splashRound(matrix.x0 + xScale + 0.01) - 1;
2210 } else {
2211 tx = splashRound(matrix.x0 + 0.01) - 1;
2212 tx2 = splashRound(matrix.x0 + xScale - 0.01);
2213 }
2214 scaledWidth = abs(tx2 - tx) + 1;
2215 // scaledWidth = splashRound(fabs(xScale));
2216 if (scaledWidth == 0) {
2217 // technically, this should draw nothing, but it generally seems
2218 // better to draw a one-pixel-wide stripe rather than throwing it
2219 // away
2220 scaledWidth = 1;
2221 }
2222 if (yScale >= 0) {
2223 ty = splashFloor(matrix.y0 + 0.01);
2224 ty2 = splashCeil(matrix.y0 + yScale - 0.01);
2225 } else {
2226 ty = splashCeil(matrix.y0 - 0.01);
2227 ty2 = splashFloor(matrix.y0 + yScale + 0.01);
2228 }
2229 scaledHeight = abs(ty2 - ty);
2230 if (scaledHeight == 0) {
2231 scaledHeight = 1;
2232 }
2233 #if 0
2234 printf("xscale: %g, yscale: %g\n", xScale, yScale);
2235 printf("width: %d, height: %d\n", width, height);
2236 printf("scaledWidth: %d, scaledHeight: %d\n", scaledWidth, scaledHeight);
2237 #endif
2238
2239 /* compute the required padding */
2240 /* Padding is used to preserve the aspect ratio.
2241 We compute total_pad to make (height+total_pad)/scaledHeight as close to height/yScale as possible */
2242 int head_pad = 0;
2243 int tail_pad = 0;
2244 int total_pad = splashRound(height * (scaledHeight / fabs(yScale)) - height);
2245
2246 /* compute the two pieces of padding */
2247 if (total_pad > 0) {
2248 // XXX: i'm not positive fabs() is correct
2249 float tail_error = fabs(matrix.y0 - ty);
2250 float head_error = fabs(ty2 - (matrix.y0 + yScale));
2251 float tail_fraction = tail_error / (tail_error + head_error);
2252 tail_pad = splashRound(total_pad * tail_fraction);
2253 head_pad = total_pad - tail_pad;
2254 } else {
2255 tail_pad = 0;
2256 head_pad = 0;
2257 }
2258 int origHeight = height;
2259 height += tail_pad;
2260 height += head_pad;
2261 #if 0
2262 printf("head_pad: %d tail_pad: %d\n", head_pad, tail_pad);
2263 printf("origHeight: %d height: %d\n", origHeight, height);
2264 printf("ty: %d, ty2: %d\n", ty, ty2);
2265 #endif
2266
2267 /* TODO: Do we want to cache these? */
2268 imgStr = new ImageStream(str, width, 1, 1);
2269 imgStr->reset();
2270
2271 invert_bit = invert ? 1 : 0;
2272
2273 image = cairo_image_surface_create(CAIRO_FORMAT_A8, scaledWidth, scaledHeight);
2274 if (cairo_surface_status(image)) {
2275 imgStr->close();
2276 delete imgStr;
2277 return;
2278 }
2279
2280 buffer = cairo_image_surface_get_data(image);
2281 row_stride = cairo_image_surface_get_stride(image);
2282
2283 int yp = height / scaledHeight;
2284 int yq = height % scaledHeight;
2285 int xp = width / scaledWidth;
2286 int xq = width % scaledWidth;
2287 int yt = 0;
2288 int origHeight_c = origHeight;
2289 /* use MIN() because yp might be > origHeight because of padding */
2290 unsigned char *pixBuf = (unsigned char *)malloc(MIN(yp + 1, origHeight) * width);
2291 int lastYStep = 1;
2292 int total = 0;
2293 for (int y = 0; y < scaledHeight; y++) {
2294 // y scale Bresenham
2295 int yStep = yp;
2296 yt += yq;
2297
2298 if (yt >= scaledHeight) {
2299 yt -= scaledHeight;
2300 ++yStep;
2301 }
2302
2303 // read row (s) from image ignoring the padding as appropriate
2304 {
2305 int n = (yp > 0) ? yStep : lastYStep;
2306 total += n;
2307 if (n > 0) {
2308 unsigned char *p = pixBuf;
2309 int head_pad_count = head_pad;
2310 int origHeight_count = origHeight;
2311 int tail_pad_count = tail_pad;
2312 for (int i = 0; i < n; i++) {
2313 // get row
2314 if (head_pad_count) {
2315 head_pad_count--;
2316 } else if (origHeight_count) {
2317 pix = imgStr->getLine();
2318 for (int j = 0; j < width; j++) {
2319 if (pix[j] ^ invert_bit)
2320 p[j] = 0;
2321 else
2322 p[j] = 255;
2323 }
2324 origHeight_count--;
2325 p += width;
2326 } else if (tail_pad_count) {
2327 tail_pad_count--;
2328 } else {
2329 printf("%d %d\n", n, total);
2330 assert(0 && "over run\n");
2331 }
2332 }
2333 }
2334 }
2335
2336 lastYStep = yStep;
2337
2338 int xt = 0;
2339 int xSrc = 0;
2340 int n = yStep > 0 ? yStep : 1;
2341 int origN = n;
2342
2343 /* compute the size of padding and pixels that will be used for this row */
2344 int head_pad_size = MIN(n, head_pad);
2345 n -= head_pad_size;
2346 head_pad -= MIN(head_pad_size, yStep);
2347
2348 int pix_size = MIN(n, origHeight);
2349 n -= pix_size;
2350 origHeight -= MIN(pix_size, yStep);
2351
2352 int tail_pad_size = MIN(n, tail_pad);
2353 n -= tail_pad_size;
2354 tail_pad -= MIN(tail_pad_size, yStep);
2355 if (n != 0) {
2356 printf("n = %d (%d %d %d)\n", n, head_pad_size, pix_size, tail_pad_size);
2357 assert(n == 0);
2358 }
2359
2360 for (int x = 0; x < scaledWidth; ++x) {
2361 int xStep = xp;
2362 xt += xq;
2363 if (xt >= scaledWidth) {
2364 xt -= scaledWidth;
2365 ++xStep;
2366 }
2367 int m = xStep > 0 ? xStep : 1;
2368 float pixAcc0 = 0;
2369 /* could m * head_pad_size * tail_pad_size overflow? */
2370 if (invert_bit) {
2371 pixAcc0 += m * head_pad_size * tail_pad_size * 255;
2372 } else {
2373 pixAcc0 += m * head_pad_size * tail_pad_size * 0;
2374 }
2375 /* Accumulate all of the source pixels for the destination pixel */
2376 for (int i = 0; i < pix_size; ++i) {
2377 for (int j = 0; j < m; ++j) {
2378 if (xSrc + i * width + j > MIN(yp + 1, origHeight_c) * width) {
2379 printf("%d > %d (%d %d %d %d) (%d %d %d)\n", xSrc + i * width + j, MIN(yp + 1, origHeight_c) * width, xSrc, i, width, j, yp, origHeight_c, width);
2380 printf("%d %d %d\n", head_pad_size, pix_size, tail_pad_size);
2381 assert(0 && "bad access\n");
2382 }
2383 pixAcc0 += pixBuf[xSrc + i * width + j];
2384 }
2385 }
2386 buffer[y * row_stride + x] = splashFloor(pixAcc0 / (origN * m));
2387 xSrc += xStep;
2388 }
2389 }
2390 free(pixBuf);
2391
2392 cairo_surface_mark_dirty(image);
2393 pattern = cairo_pattern_create_for_surface(image);
2394 cairo_surface_destroy(image);
2395 if (cairo_pattern_status(pattern)) {
2396 imgStr->close();
2397 delete imgStr;
2398 return;
2399 }
2400
2401 /* we should actually be using CAIRO_FILTER_NEAREST here. However,
2402 * cairo doesn't yet do minifaction filtering causing scaled down
2403 * images with CAIRO_FILTER_NEAREST to look really bad */
2404 cairo_pattern_set_filter(pattern, interpolate ? CAIRO_FILTER_GOOD : CAIRO_FILTER_FAST);
2405
2406 if (state->getFillColorSpace()->getMode() == csPattern) {
2407 cairo_matrix_init_translate(&matrix, 0, scaledHeight);
2408 cairo_matrix_scale(&matrix, scaledWidth, -scaledHeight);
2409 cairo_pattern_set_matrix(pattern, &matrix);
2410 if (cairo_pattern_status(pattern)) {
2411 cairo_pattern_destroy(pattern);
2412 imgStr->close();
2413 delete imgStr;
2414 return;
2415 }
2416
2417 mask = cairo_pattern_reference(pattern);
2418 cairo_get_matrix(cairo, &mask_matrix);
2419 } else {
2420 cairo_save(cairo);
2421
2422 /* modify our current transformation so that the prescaled image
2423 * goes where it is supposed to */
2424 cairo_get_matrix(cairo, &matrix);
2425 cairo_scale(cairo, 1.0 / matrix.xx, 1.0 / matrix.yy);
2426 // get integer co-ords
2427 cairo_translate(cairo, tx - matrix.x0, ty2 - matrix.y0);
2428 if (yScale > 0)
2429 cairo_scale(cairo, 1, -1);
2430
2431 cairo_rectangle(cairo, 0., 0., scaledWidth, scaledHeight);
2432 cairo_clip(cairo);
2433 if (strokePathClip) {
2434 cairo_push_group(cairo);
2435 fillToStrokePathClip(state);
2436 cairo_pop_group_to_source(cairo);
2437 }
2438 cairo_mask(cairo, pattern);
2439
2440 // cairo_get_matrix(cairo, &matrix);
2441 // printf("mask at: [%f %f], [%f %f], %f %f\n\n", matrix.xx, matrix.xy, matrix.yx, matrix.yy, matrix.x0, matrix.y0);
2442 cairo_restore(cairo);
2443 }
2444
2445 if (cairo_shape) {
2446 cairo_save(cairo_shape);
2447
2448 /* modify our current transformation so that the prescaled image
2449 * goes where it is supposed to */
2450 cairo_get_matrix(cairo_shape, &matrix);
2451 cairo_scale(cairo_shape, 1.0 / matrix.xx, 1.0 / matrix.yy);
2452 // get integer co-ords
2453 cairo_translate(cairo_shape, tx - matrix.x0, ty2 - matrix.y0);
2454 if (yScale > 0)
2455 cairo_scale(cairo_shape, 1, -1);
2456
2457 cairo_rectangle(cairo_shape, 0., 0., scaledWidth, scaledHeight);
2458 cairo_fill(cairo_shape);
2459
2460 cairo_restore(cairo_shape);
2461 }
2462
2463 cairo_pattern_destroy(pattern);
2464
2465 imgStr->close();
2466 delete imgStr;
2467 }
2468
drawMaskedImage(GfxState * state,Object * ref,Stream * str,int width,int height,GfxImageColorMap * colorMap,bool interpolate,Stream * maskStr,int maskWidth,int maskHeight,bool maskInvert,bool maskInterpolate)2469 void CairoOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate)
2470 {
2471 ImageStream *maskImgStr, *imgStr;
2472 ptrdiff_t row_stride;
2473 unsigned char *maskBuffer, *buffer;
2474 unsigned char *maskDest;
2475 unsigned int *dest;
2476 cairo_surface_t *maskImage, *image;
2477 cairo_pattern_t *maskPattern, *pattern;
2478 cairo_matrix_t matrix;
2479 cairo_matrix_t maskMatrix;
2480 unsigned char *pix;
2481 int x, y;
2482 int invert_bit;
2483 cairo_filter_t filter;
2484 cairo_filter_t maskFilter;
2485
2486 maskImgStr = new ImageStream(maskStr, maskWidth, 1, 1);
2487 maskImgStr->reset();
2488
2489 maskImage = cairo_image_surface_create(CAIRO_FORMAT_A8, maskWidth, maskHeight);
2490 if (cairo_surface_status(maskImage)) {
2491 maskImgStr->close();
2492 delete maskImgStr;
2493 return;
2494 }
2495
2496 maskBuffer = cairo_image_surface_get_data(maskImage);
2497 row_stride = cairo_image_surface_get_stride(maskImage);
2498
2499 invert_bit = maskInvert ? 1 : 0;
2500
2501 for (y = 0; y < maskHeight; y++) {
2502 pix = maskImgStr->getLine();
2503 maskDest = maskBuffer + y * row_stride;
2504 for (x = 0; x < maskWidth; x++) {
2505 if (pix[x] ^ invert_bit)
2506 *maskDest++ = 0;
2507 else
2508 *maskDest++ = 255;
2509 }
2510 }
2511
2512 maskImgStr->close();
2513 delete maskImgStr;
2514
2515 maskFilter = getFilterForSurface(maskImage, maskInterpolate);
2516
2517 cairo_surface_mark_dirty(maskImage);
2518 maskPattern = cairo_pattern_create_for_surface(maskImage);
2519 cairo_surface_destroy(maskImage);
2520 if (cairo_pattern_status(maskPattern))
2521 return;
2522
2523 #if 0
2524 /* ICCBased color space doesn't do any color correction
2525 * so check its underlying color space as well */
2526 int is_identity_transform;
2527 is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
2528 (colorMap->getColorSpace()->getMode() == csICCBased &&
2529 ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB);
2530 #endif
2531
2532 /* TODO: Do we want to cache these? */
2533 imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
2534 imgStr->reset();
2535
2536 image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
2537 if (cairo_surface_status(image))
2538 goto cleanup;
2539
2540 buffer = cairo_image_surface_get_data(image);
2541 row_stride = cairo_image_surface_get_stride(image);
2542 for (y = 0; y < height; y++) {
2543 dest = reinterpret_cast<unsigned int *>(buffer + y * row_stride);
2544 pix = imgStr->getLine();
2545 colorMap->getRGBLine(pix, dest, width);
2546 }
2547
2548 filter = getFilterForSurface(image, interpolate);
2549
2550 cairo_surface_mark_dirty(image);
2551 pattern = cairo_pattern_create_for_surface(image);
2552 cairo_surface_destroy(image);
2553 if (cairo_pattern_status(pattern))
2554 goto cleanup;
2555
2556 LOG(printf("drawMaskedImage %dx%d\n", width, height));
2557
2558 cairo_pattern_set_filter(pattern, filter);
2559 cairo_pattern_set_filter(maskPattern, maskFilter);
2560
2561 if (!printing) {
2562 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);
2563 cairo_pattern_set_extend(maskPattern, CAIRO_EXTEND_PAD);
2564 }
2565
2566 cairo_matrix_init_translate(&matrix, 0, height);
2567 cairo_matrix_scale(&matrix, width, -height);
2568 cairo_pattern_set_matrix(pattern, &matrix);
2569 if (cairo_pattern_status(pattern)) {
2570 cairo_pattern_destroy(pattern);
2571 cairo_pattern_destroy(maskPattern);
2572 goto cleanup;
2573 }
2574
2575 cairo_matrix_init_translate(&maskMatrix, 0, maskHeight);
2576 cairo_matrix_scale(&maskMatrix, maskWidth, -maskHeight);
2577 cairo_pattern_set_matrix(maskPattern, &maskMatrix);
2578 if (cairo_pattern_status(maskPattern)) {
2579 cairo_pattern_destroy(maskPattern);
2580 cairo_pattern_destroy(pattern);
2581 goto cleanup;
2582 }
2583
2584 if (!printing) {
2585 cairo_save(cairo);
2586 cairo_set_source(cairo, pattern);
2587 cairo_rectangle(cairo, 0., 0., 1., 1.);
2588 cairo_clip(cairo);
2589 cairo_mask(cairo, maskPattern);
2590 cairo_restore(cairo);
2591 } else {
2592 cairo_set_source(cairo, pattern);
2593 cairo_mask(cairo, maskPattern);
2594 }
2595
2596 if (cairo_shape) {
2597 cairo_save(cairo_shape);
2598 cairo_set_source(cairo_shape, pattern);
2599 if (!printing) {
2600 cairo_rectangle(cairo_shape, 0., 0., 1., 1.);
2601 cairo_fill(cairo_shape);
2602 } else {
2603 cairo_mask(cairo_shape, pattern);
2604 }
2605 cairo_restore(cairo_shape);
2606 }
2607
2608 cairo_pattern_destroy(maskPattern);
2609 cairo_pattern_destroy(pattern);
2610
2611 cleanup:
2612 imgStr->close();
2613 delete imgStr;
2614 }
2615
2616 // XXX: is this affect by AIS(alpha is shape)?
drawSoftMaskedImage(GfxState * state,Object * ref,Stream * str,int width,int height,GfxImageColorMap * colorMap,bool interpolate,Stream * maskStr,int maskWidth,int maskHeight,GfxImageColorMap * maskColorMap,bool maskInterpolate)2617 void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap,
2618 bool maskInterpolate)
2619 {
2620 ImageStream *maskImgStr, *imgStr;
2621 ptrdiff_t row_stride;
2622 unsigned char *maskBuffer, *buffer;
2623 unsigned char *maskDest;
2624 unsigned int *dest;
2625 cairo_surface_t *maskImage, *image;
2626 cairo_pattern_t *maskPattern, *pattern;
2627 cairo_matrix_t maskMatrix, matrix;
2628 unsigned char *pix;
2629 int y;
2630 cairo_filter_t filter;
2631 cairo_filter_t maskFilter;
2632
2633 maskImgStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits());
2634 maskImgStr->reset();
2635
2636 maskImage = cairo_image_surface_create(CAIRO_FORMAT_A8, maskWidth, maskHeight);
2637 if (cairo_surface_status(maskImage)) {
2638 maskImgStr->close();
2639 delete maskImgStr;
2640 return;
2641 }
2642
2643 maskBuffer = cairo_image_surface_get_data(maskImage);
2644 row_stride = cairo_image_surface_get_stride(maskImage);
2645 for (y = 0; y < maskHeight; y++) {
2646 maskDest = (unsigned char *)(maskBuffer + y * row_stride);
2647 pix = maskImgStr->getLine();
2648 if (likely(pix != nullptr)) {
2649 maskColorMap->getGrayLine(pix, maskDest, maskWidth);
2650 }
2651 }
2652
2653 maskImgStr->close();
2654 delete maskImgStr;
2655
2656 maskFilter = getFilterForSurface(maskImage, maskInterpolate);
2657
2658 cairo_surface_mark_dirty(maskImage);
2659 maskPattern = cairo_pattern_create_for_surface(maskImage);
2660 cairo_surface_destroy(maskImage);
2661 if (cairo_pattern_status(maskPattern))
2662 return;
2663
2664 #if 0
2665 /* ICCBased color space doesn't do any color correction
2666 * so check its underlying color space as well */
2667 int is_identity_transform;
2668 is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
2669 (colorMap->getColorSpace()->getMode() == csICCBased &&
2670 ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB);
2671 #endif
2672
2673 /* TODO: Do we want to cache these? */
2674 imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
2675 imgStr->reset();
2676
2677 image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
2678 if (cairo_surface_status(image))
2679 goto cleanup;
2680
2681 buffer = cairo_image_surface_get_data(image);
2682 row_stride = cairo_image_surface_get_stride(image);
2683 for (y = 0; y < height; y++) {
2684 dest = reinterpret_cast<unsigned int *>(buffer + y * row_stride);
2685 pix = imgStr->getLine();
2686 colorMap->getRGBLine(pix, dest, width);
2687 }
2688
2689 filter = getFilterForSurface(image, interpolate);
2690
2691 cairo_surface_mark_dirty(image);
2692
2693 setMimeData(state, str, ref, colorMap, image, height);
2694
2695 pattern = cairo_pattern_create_for_surface(image);
2696 cairo_surface_destroy(image);
2697 if (cairo_pattern_status(pattern))
2698 goto cleanup;
2699
2700 LOG(printf("drawSoftMaskedImage %dx%d\n", width, height));
2701
2702 cairo_pattern_set_filter(pattern, filter);
2703 cairo_pattern_set_filter(maskPattern, maskFilter);
2704
2705 if (!printing) {
2706 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);
2707 cairo_pattern_set_extend(maskPattern, CAIRO_EXTEND_PAD);
2708 }
2709
2710 cairo_matrix_init_translate(&matrix, 0, height);
2711 cairo_matrix_scale(&matrix, width, -height);
2712 cairo_pattern_set_matrix(pattern, &matrix);
2713 if (cairo_pattern_status(pattern)) {
2714 cairo_pattern_destroy(pattern);
2715 cairo_pattern_destroy(maskPattern);
2716 goto cleanup;
2717 }
2718
2719 cairo_matrix_init_translate(&maskMatrix, 0, maskHeight);
2720 cairo_matrix_scale(&maskMatrix, maskWidth, -maskHeight);
2721 cairo_pattern_set_matrix(maskPattern, &maskMatrix);
2722 if (cairo_pattern_status(maskPattern)) {
2723 cairo_pattern_destroy(maskPattern);
2724 cairo_pattern_destroy(pattern);
2725 goto cleanup;
2726 }
2727
2728 if (fill_opacity != 1.0)
2729 cairo_push_group(cairo);
2730 else
2731 cairo_save(cairo);
2732
2733 cairo_set_source(cairo, pattern);
2734 if (!printing) {
2735 cairo_rectangle(cairo, 0., 0., 1., 1.);
2736 cairo_clip(cairo);
2737 }
2738 cairo_mask(cairo, maskPattern);
2739
2740 if (fill_opacity != 1.0) {
2741 cairo_pop_group_to_source(cairo);
2742 cairo_save(cairo);
2743 if (!printing) {
2744 cairo_rectangle(cairo, 0., 0., 1., 1.);
2745 cairo_clip(cairo);
2746 }
2747 cairo_paint_with_alpha(cairo, fill_opacity);
2748 }
2749 cairo_restore(cairo);
2750
2751 if (cairo_shape) {
2752 cairo_save(cairo_shape);
2753 cairo_set_source(cairo_shape, pattern);
2754 if (!printing) {
2755 cairo_rectangle(cairo_shape, 0., 0., 1., 1.);
2756 cairo_fill(cairo_shape);
2757 } else {
2758 cairo_mask(cairo_shape, pattern);
2759 }
2760 cairo_restore(cairo_shape);
2761 }
2762
2763 cairo_pattern_destroy(maskPattern);
2764 cairo_pattern_destroy(pattern);
2765
2766 cleanup:
2767 imgStr->close();
2768 delete imgStr;
2769 }
2770
getStreamData(Stream * str,char ** buffer,int * length)2771 bool CairoOutputDev::getStreamData(Stream *str, char **buffer, int *length)
2772 {
2773 int len, i;
2774 char *strBuffer;
2775
2776 len = 0;
2777 str->close();
2778 str->reset();
2779 while (str->getChar() != EOF)
2780 len++;
2781 if (len == 0)
2782 return false;
2783
2784 strBuffer = (char *)gmalloc(len);
2785
2786 str->close();
2787 str->reset();
2788 for (i = 0; i < len; ++i)
2789 strBuffer[i] = str->getChar();
2790
2791 *buffer = strBuffer;
2792 *length = len;
2793
2794 return true;
2795 }
2796
colorMapHasIdentityDecodeMap(GfxImageColorMap * colorMap)2797 static bool colorMapHasIdentityDecodeMap(GfxImageColorMap *colorMap)
2798 {
2799 for (int i = 0; i < colorMap->getNumPixelComps(); i++) {
2800 if (colorMap->getDecodeLow(i) != 0.0 || colorMap->getDecodeHigh(i) != 1.0)
2801 return false;
2802 }
2803 return true;
2804 }
2805
2806 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2)
setMimeIdFromRef(cairo_surface_t * surface,const char * mime_type,const char * mime_id_prefix,Ref ref)2807 static cairo_status_t setMimeIdFromRef(cairo_surface_t *surface, const char *mime_type, const char *mime_id_prefix, Ref ref)
2808 {
2809 GooString *mime_id;
2810 char *idBuffer;
2811 cairo_status_t status;
2812
2813 mime_id = new GooString;
2814
2815 if (mime_id_prefix)
2816 mime_id->append(mime_id_prefix);
2817
2818 mime_id->appendf("{0:d}-{1:d}", ref.gen, ref.num);
2819
2820 idBuffer = copyString(mime_id->c_str());
2821 status = cairo_surface_set_mime_data(surface, mime_type, (const unsigned char *)idBuffer, mime_id->getLength(), gfree, idBuffer);
2822 delete mime_id;
2823 if (status)
2824 gfree(idBuffer);
2825 return status;
2826 }
2827 #endif
2828
2829 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
setMimeDataForJBIG2Globals(Stream * str,cairo_surface_t * image)2830 bool CairoOutputDev::setMimeDataForJBIG2Globals(Stream *str, cairo_surface_t *image)
2831 {
2832 JBIG2Stream *jb2Str = static_cast<JBIG2Stream *>(str);
2833 Object *globalsStr = jb2Str->getGlobalsStream();
2834 char *globalsBuffer;
2835 int globalsLength;
2836
2837 // nothing to do for JBIG2 stream without Globals
2838 if (!globalsStr->isStream())
2839 return true;
2840
2841 if (setMimeIdFromRef(image, CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID, nullptr, jb2Str->getGlobalsStreamRef()))
2842 return false;
2843
2844 if (!getStreamData(globalsStr->getStream(), &globalsBuffer, &globalsLength))
2845 return false;
2846
2847 if (cairo_surface_set_mime_data(image, CAIRO_MIME_TYPE_JBIG2_GLOBAL, (const unsigned char *)globalsBuffer, globalsLength, gfree, (void *)globalsBuffer)) {
2848 gfree(globalsBuffer);
2849 return false;
2850 }
2851
2852 return true;
2853 }
2854 #endif
2855
2856 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10)
setMimeDataForCCITTParams(Stream * str,cairo_surface_t * image,int height)2857 bool CairoOutputDev::setMimeDataForCCITTParams(Stream *str, cairo_surface_t *image, int height)
2858 {
2859 CCITTFaxStream *ccittStr = static_cast<CCITTFaxStream *>(str);
2860
2861 GooString params;
2862 params.appendf("Columns={0:d}", ccittStr->getColumns());
2863 params.appendf(" Rows={0:d}", height);
2864 params.appendf(" K={0:d}", ccittStr->getEncoding());
2865 params.appendf(" EndOfLine={0:d}", ccittStr->getEndOfLine() ? 1 : 0);
2866 params.appendf(" EncodedByteAlign={0:d}", ccittStr->getEncodedByteAlign() ? 1 : 0);
2867 params.appendf(" EndOfBlock={0:d}", ccittStr->getEndOfBlock() ? 1 : 0);
2868 params.appendf(" BlackIs1={0:d}", ccittStr->getBlackIs1() ? 1 : 0);
2869 params.appendf(" DamagedRowsBeforeError={0:d}", ccittStr->getDamagedRowsBeforeError());
2870
2871 char *p = strdup(params.c_str());
2872 if (cairo_surface_set_mime_data(image, CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, (const unsigned char *)p, params.getLength(), gfree, (void *)p)) {
2873 gfree(p);
2874 return false;
2875 }
2876
2877 return true;
2878 }
2879 #endif
2880
setMimeData(GfxState * state,Stream * str,Object * ref,GfxImageColorMap * colorMap,cairo_surface_t * image,int height)2881 void CairoOutputDev::setMimeData(GfxState *state, Stream *str, Object *ref, GfxImageColorMap *colorMap, cairo_surface_t *image, int height)
2882 {
2883 char *strBuffer;
2884 int len;
2885 Object obj;
2886 GfxColorSpace *colorSpace;
2887 StreamKind strKind = str->getKind();
2888 const char *mime_type;
2889 cairo_status_t status;
2890
2891 if (!printing)
2892 return;
2893
2894 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2)
2895 // Since 1.5.10 the cairo PS backend stores images with UNIQUE_ID in PS memory so the
2896 // image can be re-used multiple times. As we don't know how large the images are or
2897 // how many times they are used, there is no benefit in enabling this. Issue #106
2898 if (cairo_surface_get_type(cairo_get_target(cairo)) != CAIRO_SURFACE_TYPE_PS) {
2899 if (ref && ref->isRef()) {
2900 status = setMimeIdFromRef(image, CAIRO_MIME_TYPE_UNIQUE_ID, "poppler-surface-", ref->getRef());
2901 if (status)
2902 return;
2903 }
2904 }
2905 #endif
2906
2907 switch (strKind) {
2908 case strDCT:
2909 mime_type = CAIRO_MIME_TYPE_JPEG;
2910 break;
2911 case strJPX:
2912 mime_type = CAIRO_MIME_TYPE_JP2;
2913 break;
2914 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
2915 case strJBIG2:
2916 mime_type = CAIRO_MIME_TYPE_JBIG2;
2917 break;
2918 #endif
2919 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10)
2920 case strCCITTFax:
2921 mime_type = CAIRO_MIME_TYPE_CCITT_FAX;
2922 break;
2923 #endif
2924 default:
2925 mime_type = nullptr;
2926 break;
2927 }
2928
2929 obj = str->getDict()->lookup("ColorSpace");
2930 colorSpace = GfxColorSpace::parse(nullptr, &obj, this, state);
2931
2932 // colorspace in stream dict may be different from colorspace in jpx
2933 // data
2934 if (strKind == strJPX && colorSpace)
2935 return;
2936
2937 // only embed mime data for gray, rgb, and cmyk colorspaces.
2938 if (colorSpace) {
2939 GfxColorSpaceMode mode = colorSpace->getMode();
2940 delete colorSpace;
2941 switch (mode) {
2942 case csDeviceGray:
2943 case csCalGray:
2944 case csDeviceRGB:
2945 case csCalRGB:
2946 case csDeviceCMYK:
2947 case csICCBased:
2948 break;
2949
2950 case csLab:
2951 case csIndexed:
2952 case csSeparation:
2953 case csDeviceN:
2954 case csPattern:
2955 return;
2956 }
2957 }
2958
2959 if (!colorMapHasIdentityDecodeMap(colorMap))
2960 return;
2961
2962 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
2963 if (strKind == strJBIG2 && !setMimeDataForJBIG2Globals(str, image))
2964 return;
2965 #endif
2966
2967 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10)
2968 if (strKind == strCCITTFax && !setMimeDataForCCITTParams(str, image, height))
2969 return;
2970 #endif
2971
2972 if (mime_type) {
2973 if (getStreamData(str->getNextStream(), &strBuffer, &len))
2974 status = cairo_surface_set_mime_data(image, mime_type, (const unsigned char *)strBuffer, len, gfree, strBuffer);
2975
2976 if (status)
2977 gfree(strBuffer);
2978 }
2979 }
2980
2981 class RescaleDrawImage : public CairoRescaleBox
2982 {
2983 private:
2984 ImageStream *imgStr;
2985 GfxRGB *lookup;
2986 int width;
2987 GfxImageColorMap *colorMap;
2988 const int *maskColors;
2989 int current_row;
2990 bool imageError;
2991
2992 public:
2993 ~RescaleDrawImage() override;
getSourceImage(Stream * str,int widthA,int height,int scaledWidth,int scaledHeight,bool printing,GfxImageColorMap * colorMapA,const int * maskColorsA)2994 cairo_surface_t *getSourceImage(Stream *str, int widthA, int height, int scaledWidth, int scaledHeight, bool printing, GfxImageColorMap *colorMapA, const int *maskColorsA)
2995 {
2996 cairo_surface_t *image = nullptr;
2997 int i;
2998
2999 lookup = nullptr;
3000 colorMap = colorMapA;
3001 maskColors = maskColorsA;
3002 width = widthA;
3003 current_row = -1;
3004 imageError = false;
3005
3006 /* TODO: Do we want to cache these? */
3007 imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
3008 imgStr->reset();
3009
3010 #if 0
3011 /* ICCBased color space doesn't do any color correction
3012 * so check its underlying color space as well */
3013 int is_identity_transform;
3014 is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB ||
3015 (colorMap->getColorSpace()->getMode() == csICCBased &&
3016 ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB);
3017 #endif
3018
3019 // special case for one-channel (monochrome/gray/separation) images:
3020 // build a lookup table here
3021 if (colorMap->getNumPixelComps() == 1) {
3022 int n;
3023 unsigned char pix;
3024
3025 n = 1 << colorMap->getBits();
3026 lookup = (GfxRGB *)gmallocn(n, sizeof(GfxRGB));
3027 for (i = 0; i < n; ++i) {
3028 pix = (unsigned char)i;
3029
3030 colorMap->getRGB(&pix, &lookup[i]);
3031 }
3032 }
3033
3034 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
3035 bool needsCustomDownscaling = false;
3036 #else
3037 bool needsCustomDownscaling = true;
3038 #endif
3039
3040 if (printing) {
3041 if (width > MAX_PRINT_IMAGE_SIZE || height > MAX_PRINT_IMAGE_SIZE) {
3042 if (width > height) {
3043 scaledWidth = MAX_PRINT_IMAGE_SIZE;
3044 scaledHeight = MAX_PRINT_IMAGE_SIZE * (double)height / width;
3045 } else {
3046 scaledHeight = MAX_PRINT_IMAGE_SIZE;
3047 scaledWidth = MAX_PRINT_IMAGE_SIZE * (double)width / height;
3048 }
3049 needsCustomDownscaling = true;
3050
3051 if (scaledWidth == 0) {
3052 scaledWidth = 1;
3053 }
3054 if (scaledHeight == 0) {
3055 scaledHeight = 1;
3056 }
3057 } else {
3058 needsCustomDownscaling = false;
3059 }
3060 }
3061
3062 if (!needsCustomDownscaling || scaledWidth >= width || scaledHeight >= height) {
3063 // No downscaling. Create cairo image containing the source image data.
3064 unsigned char *buffer;
3065 ptrdiff_t stride;
3066
3067 image = cairo_image_surface_create(maskColors ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height);
3068 if (cairo_surface_status(image))
3069 goto cleanup;
3070
3071 buffer = cairo_image_surface_get_data(image);
3072 stride = cairo_image_surface_get_stride(image);
3073 for (int y = 0; y < height; y++) {
3074 uint32_t *dest = reinterpret_cast<uint32_t *>(buffer + y * stride);
3075 getRow(y, dest);
3076 }
3077 } else {
3078 // // Downscaling required. Create cairo image the size of the
3079 // rescaled image and // downscale the source image data into
3080 // the cairo image. downScaleImage() will call getRow() to read
3081 // source image data from the image stream. This avoids having
3082 // to create an image the size of the source image which may
3083 // exceed cairo's 32676x32767 image size limit (and also saves a
3084 // lot of memory).
3085 image = cairo_image_surface_create(maskColors ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, scaledWidth, scaledHeight);
3086 if (cairo_surface_status(image))
3087 goto cleanup;
3088
3089 downScaleImage(width, height, scaledWidth, scaledHeight, 0, 0, scaledWidth, scaledHeight, image);
3090 }
3091 cairo_surface_mark_dirty(image);
3092
3093 cleanup:
3094 gfree(lookup);
3095 imgStr->close();
3096 delete imgStr;
3097 return image;
3098 }
3099
getRow(int row_num,uint32_t * row_data)3100 void getRow(int row_num, uint32_t *row_data) override
3101 {
3102 unsigned char *pix;
3103
3104 if (row_num <= current_row)
3105 return;
3106
3107 while (current_row < row_num) {
3108 pix = imgStr->getLine();
3109 current_row++;
3110 }
3111
3112 if (unlikely(pix == nullptr)) {
3113 memset(row_data, 0, width * 4);
3114 if (!imageError) {
3115 error(errInternal, -1, "Bad image stream");
3116 imageError = true;
3117 }
3118 } else if (lookup) {
3119 unsigned char *p = pix;
3120 GfxRGB rgb;
3121
3122 for (int i = 0; i < width; i++) {
3123 rgb = lookup[*p];
3124 row_data[i] = ((int)colToByte(rgb.r) << 16) | ((int)colToByte(rgb.g) << 8) | ((int)colToByte(rgb.b) << 0);
3125 p++;
3126 }
3127 } else {
3128 colorMap->getRGBLine(pix, row_data, width);
3129 }
3130
3131 if (maskColors) {
3132 for (int x = 0; x < width; x++) {
3133 bool is_opaque = false;
3134 for (int i = 0; i < colorMap->getNumPixelComps(); ++i) {
3135 if (pix[i] < maskColors[2 * i] || pix[i] > maskColors[2 * i + 1]) {
3136 is_opaque = true;
3137 break;
3138 }
3139 }
3140 if (is_opaque)
3141 *row_data |= 0xff000000;
3142 else
3143 *row_data = 0;
3144 row_data++;
3145 pix += colorMap->getNumPixelComps();
3146 }
3147 }
3148 }
3149 };
3150
3151 RescaleDrawImage::~RescaleDrawImage() = default;
3152
drawImage(GfxState * state,Object * ref,Stream * str,int widthA,int heightA,GfxImageColorMap * colorMap,bool interpolate,const int * maskColors,bool inlineImg)3153 void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int widthA, int heightA, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg)
3154 {
3155 cairo_surface_t *image;
3156 cairo_pattern_t *pattern, *maskPattern;
3157 cairo_matrix_t matrix;
3158 int width, height;
3159 int scaledWidth, scaledHeight;
3160 cairo_filter_t filter = CAIRO_FILTER_GOOD;
3161 RescaleDrawImage rescale;
3162
3163 LOG(printf("drawImage %dx%d\n", widthA, heightA));
3164
3165 cairo_get_matrix(cairo, &matrix);
3166 getScaledSize(&matrix, widthA, heightA, &scaledWidth, &scaledHeight);
3167 image = rescale.getSourceImage(str, widthA, heightA, scaledWidth, scaledHeight, printing, colorMap, maskColors);
3168 if (!image)
3169 return;
3170
3171 width = cairo_image_surface_get_width(image);
3172 height = cairo_image_surface_get_height(image);
3173 if (width == widthA && height == heightA)
3174 filter = getFilterForSurface(image, interpolate);
3175
3176 if (!inlineImg) { /* don't read stream twice if it is an inline image */
3177 // cairo 1.15.10 allows mime image data to have different size to cairo image
3178 // mime image size will be scaled to same size as cairo image
3179 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10)
3180 bool requireSameSize = false;
3181 #else
3182 bool requireSameSize = true;
3183 #endif
3184 if (!requireSameSize || (width == widthA && height == heightA))
3185 setMimeData(state, str, ref, colorMap, image, heightA);
3186 }
3187
3188 pattern = cairo_pattern_create_for_surface(image);
3189 cairo_surface_destroy(image);
3190 if (cairo_pattern_status(pattern))
3191 return;
3192
3193 cairo_pattern_set_filter(pattern, filter);
3194
3195 if (!printing)
3196 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);
3197
3198 cairo_matrix_init_translate(&matrix, 0, height);
3199 cairo_matrix_scale(&matrix, width, -height);
3200 cairo_pattern_set_matrix(pattern, &matrix);
3201 if (cairo_pattern_status(pattern)) {
3202 cairo_pattern_destroy(pattern);
3203 return;
3204 }
3205
3206 if (!mask && fill_opacity != 1.0) {
3207 maskPattern = cairo_pattern_create_rgba(1., 1., 1., fill_opacity);
3208 } else if (mask) {
3209 maskPattern = cairo_pattern_reference(mask);
3210 } else {
3211 maskPattern = nullptr;
3212 }
3213
3214 cairo_save(cairo);
3215 cairo_set_source(cairo, pattern);
3216 if (!printing)
3217 cairo_rectangle(cairo, 0., 0., 1., 1.);
3218 if (maskPattern) {
3219 if (!printing)
3220 cairo_clip(cairo);
3221 if (mask)
3222 cairo_set_matrix(cairo, &mask_matrix);
3223 cairo_mask(cairo, maskPattern);
3224 } else {
3225 if (printing)
3226 cairo_paint(cairo);
3227 else
3228 cairo_fill(cairo);
3229 }
3230 cairo_restore(cairo);
3231
3232 cairo_pattern_destroy(maskPattern);
3233
3234 if (cairo_shape) {
3235 cairo_save(cairo_shape);
3236 cairo_set_source(cairo_shape, pattern);
3237 if (printing) {
3238 cairo_paint(cairo_shape);
3239 } else {
3240 cairo_rectangle(cairo_shape, 0., 0., 1., 1.);
3241 cairo_fill(cairo_shape);
3242 }
3243 cairo_restore(cairo_shape);
3244 }
3245
3246 cairo_pattern_destroy(pattern);
3247 }
3248
3249 //------------------------------------------------------------------------
3250 // ImageOutputDev
3251 //------------------------------------------------------------------------
3252
CairoImageOutputDev()3253 CairoImageOutputDev::CairoImageOutputDev()
3254 {
3255 images = nullptr;
3256 numImages = 0;
3257 size = 0;
3258 imgDrawCbk = nullptr;
3259 imgDrawCbkData = nullptr;
3260 }
3261
~CairoImageOutputDev()3262 CairoImageOutputDev::~CairoImageOutputDev()
3263 {
3264 int i;
3265
3266 for (i = 0; i < numImages; i++)
3267 delete images[i];
3268 gfree(images);
3269 }
3270
saveImage(CairoImage * image)3271 void CairoImageOutputDev::saveImage(CairoImage *image)
3272 {
3273 if (numImages >= size) {
3274 size += 16;
3275 images = (CairoImage **)greallocn(images, size, sizeof(CairoImage *));
3276 }
3277 images[numImages++] = image;
3278 }
3279
getBBox(GfxState * state,int width,int height,double * x1,double * y1,double * x2,double * y2)3280 void CairoImageOutputDev::getBBox(GfxState *state, int width, int height, double *x1, double *y1, double *x2, double *y2)
3281 {
3282 const double *ctm = state->getCTM();
3283 cairo_matrix_t matrix;
3284 cairo_matrix_init(&matrix, ctm[0], ctm[1], -ctm[2], -ctm[3], ctm[2] + ctm[4], ctm[3] + ctm[5]);
3285
3286 int scaledWidth, scaledHeight;
3287 getScaledSize(&matrix, width, height, &scaledWidth, &scaledHeight);
3288
3289 if (matrix.xx >= 0) {
3290 *x1 = matrix.x0;
3291 } else {
3292 *x1 = matrix.x0 - scaledWidth;
3293 }
3294 *x2 = *x1 + scaledWidth;
3295
3296 if (matrix.yy >= 0) {
3297 *y1 = matrix.y0;
3298 } else {
3299 *y1 = matrix.y0 - scaledHeight;
3300 }
3301 *y2 = *y1 + scaledHeight;
3302 }
3303
drawImageMask(GfxState * state,Object * ref,Stream * str,int width,int height,bool invert,bool interpolate,bool inlineImg)3304 void CairoImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg)
3305 {
3306 cairo_t *cr;
3307 cairo_surface_t *surface;
3308 double x1, y1, x2, y2;
3309 CairoImage *image;
3310
3311 getBBox(state, width, height, &x1, &y1, &x2, &y2);
3312
3313 image = new CairoImage(x1, y1, x2, y2);
3314 saveImage(image);
3315
3316 if (imgDrawCbk && imgDrawCbk(numImages - 1, imgDrawCbkData)) {
3317 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3318 cr = cairo_create(surface);
3319 setCairo(cr);
3320 cairo_translate(cr, 0, height);
3321 cairo_scale(cr, width, -height);
3322
3323 CairoOutputDev::drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
3324 image->setImage(surface);
3325
3326 setCairo(nullptr);
3327 cairo_surface_destroy(surface);
3328 cairo_destroy(cr);
3329 }
3330 }
3331
setSoftMaskFromImageMask(GfxState * state,Object * ref,Stream * str,int width,int height,bool invert,bool inlineImg,double * baseMatrix)3332 void CairoImageOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg, double *baseMatrix)
3333 {
3334 cairo_t *cr;
3335 cairo_surface_t *surface;
3336 double x1, y1, x2, y2;
3337 CairoImage *image;
3338
3339 getBBox(state, width, height, &x1, &y1, &x2, &y2);
3340
3341 image = new CairoImage(x1, y1, x2, y2);
3342 saveImage(image);
3343
3344 if (imgDrawCbk && imgDrawCbk(numImages - 1, imgDrawCbkData)) {
3345 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3346 cr = cairo_create(surface);
3347 setCairo(cr);
3348 cairo_translate(cr, 0, height);
3349 cairo_scale(cr, width, -height);
3350
3351 CairoOutputDev::drawImageMask(state, ref, str, width, height, invert, inlineImg, false);
3352 if (state->getFillColorSpace()->getMode() == csPattern) {
3353 cairo_mask(cairo, mask);
3354 }
3355 image->setImage(surface);
3356
3357 setCairo(nullptr);
3358 cairo_surface_destroy(surface);
3359 cairo_destroy(cr);
3360 }
3361 }
3362
drawImage(GfxState * state,Object * ref,Stream * str,int width,int height,GfxImageColorMap * colorMap,bool interpolate,const int * maskColors,bool inlineImg)3363 void CairoImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg)
3364 {
3365 cairo_t *cr;
3366 cairo_surface_t *surface;
3367 double x1, y1, x2, y2;
3368 CairoImage *image;
3369
3370 getBBox(state, width, height, &x1, &y1, &x2, &y2);
3371
3372 image = new CairoImage(x1, y1, x2, y2);
3373 saveImage(image);
3374
3375 if (imgDrawCbk && imgDrawCbk(numImages - 1, imgDrawCbkData)) {
3376 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3377 cr = cairo_create(surface);
3378 setCairo(cr);
3379 cairo_translate(cr, 0, height);
3380 cairo_scale(cr, width, -height);
3381
3382 CairoOutputDev::drawImage(state, ref, str, width, height, colorMap, interpolate, maskColors, inlineImg);
3383 image->setImage(surface);
3384
3385 setCairo(nullptr);
3386 cairo_surface_destroy(surface);
3387 cairo_destroy(cr);
3388 }
3389 }
3390
drawSoftMaskedImage(GfxState * state,Object * ref,Stream * str,int width,int height,GfxImageColorMap * colorMap,bool interpolate,Stream * maskStr,int maskWidth,int maskHeight,GfxImageColorMap * maskColorMap,bool maskInterpolate)3391 void CairoImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap,
3392 bool maskInterpolate)
3393 {
3394 cairo_t *cr;
3395 cairo_surface_t *surface;
3396 double x1, y1, x2, y2;
3397 CairoImage *image;
3398
3399 getBBox(state, width, height, &x1, &y1, &x2, &y2);
3400
3401 image = new CairoImage(x1, y1, x2, y2);
3402 saveImage(image);
3403
3404 if (imgDrawCbk && imgDrawCbk(numImages - 1, imgDrawCbkData)) {
3405 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3406 cr = cairo_create(surface);
3407 setCairo(cr);
3408 cairo_translate(cr, 0, height);
3409 cairo_scale(cr, width, -height);
3410
3411 CairoOutputDev::drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
3412 image->setImage(surface);
3413
3414 setCairo(nullptr);
3415 cairo_surface_destroy(surface);
3416 cairo_destroy(cr);
3417 }
3418 }
3419
drawMaskedImage(GfxState * state,Object * ref,Stream * str,int width,int height,GfxImageColorMap * colorMap,bool interpolate,Stream * maskStr,int maskWidth,int maskHeight,bool maskInvert,bool maskInterpolate)3420 void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate)
3421 {
3422 cairo_t *cr;
3423 cairo_surface_t *surface;
3424 double x1, y1, x2, y2;
3425 CairoImage *image;
3426
3427 getBBox(state, width, height, &x1, &y1, &x2, &y2);
3428
3429 image = new CairoImage(x1, y1, x2, y2);
3430 saveImage(image);
3431
3432 if (imgDrawCbk && imgDrawCbk(numImages - 1, imgDrawCbkData)) {
3433 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3434 cr = cairo_create(surface);
3435 setCairo(cr);
3436 cairo_translate(cr, 0, height);
3437 cairo_scale(cr, width, -height);
3438
3439 CairoOutputDev::drawMaskedImage(state, ref, str, width, height, colorMap, interpolate, maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
3440 image->setImage(surface);
3441
3442 setCairo(nullptr);
3443 cairo_surface_destroy(surface);
3444 cairo_destroy(cr);
3445 }
3446 }
3447