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

source code of poppler/poppler/CairoOutputDev.cc