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