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 | |
100 | CairoImage::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 | |
109 | CairoImage::~CairoImage() |
110 | { |
111 | if (image) { |
112 | cairo_surface_destroy(surface: image); |
113 | } |
114 | } |
115 | |
116 | void 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. |
134 | FT_Library CairoOutputDev::ft_lib; |
135 | std::once_flag CairoOutputDev::ft_lib_once_flag; |
136 | |
137 | CairoOutputDev::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 | |
191 | CairoOutputDev::~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 | |
223 | void 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 | |
244 | bool 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 | |
252 | void 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 | |
270 | void 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 | |
280 | void 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 | |
303 | void 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. |
317 | void 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 | |
346 | void 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: "ed_name); |
392 | emittedDestinations.insert(x: quoted_name.toStr()); |
393 | |
394 | GooString attrib; |
395 | attrib.appendf(fmt: "name={0:t} " , "ed_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 | |
414 | void CairoOutputDev::endPage() |
415 | { |
416 | if (textPage) { |
417 | textPage->endPage(); |
418 | textPage->coalesce(physLayout: true, fixedPitch: 0, doHTML: false); |
419 | } |
420 | } |
421 | |
422 | void 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 | |
436 | void CairoOutputDev::endForm(Object *obj, Ref id) |
437 | { |
438 | if (logicalStruct && isPDF()) { |
439 | currentStructParents = structParentsStack.back(); |
440 | structParentsStack.pop_back(); |
441 | } |
442 | } |
443 | |
444 | void 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 | |
472 | bool 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 | |
488 | void 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 | |
506 | bool 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 | |
581 | AnnotLink *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 | |
602 | bool 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 | |
616 | void 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 | |
625 | int 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 | |
652 | bool 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 | |
676 | void 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 | |
707 | void 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 | |
725 | void 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 | |
761 | void 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 | |
790 | void 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 | |
835 | void 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 | |
854 | void 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 | |
872 | void 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 | |
907 | void 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 | |
918 | void CairoOutputDev::updateFlatness(GfxState *state) |
919 | { |
920 | // cairo_set_tolerance (cairo, state->getFlatness()); |
921 | } |
922 | |
923 | void 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 | |
941 | void 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 | |
959 | void 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 | |
967 | void 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 | |
1005 | void 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 | |
1022 | void 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 | |
1040 | void 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 | |
1062 | void 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 | |
1084 | void 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 | |
1103 | void 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 | |
1159 | void 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 */ |
1222 | void 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 | |
1264 | void 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 | |
1310 | void 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 | |
1341 | void 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 | |
1379 | void 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 | |
1402 | bool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat, GfxTilingPattern *tPat, const double *mat, int x0, int y0, int x1, int y1, double xStep, double yStep) |
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) |
1531 | bool 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 | |
1620 | bool 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 | |
1644 | bool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading) |
1645 | { |
1646 | return (shading->getExtend0() == shading->getExtend1()); |
1647 | } |
1648 | |
1649 | bool 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 | |
1684 | bool 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) |
1690 | bool 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 | |
1739 | bool 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 | |
1819 | void 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 | |
1832 | void 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 | |
1845 | void 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 | |
1865 | void 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 | |
1881 | void 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 | |
1904 | void CairoOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, const Unicode *u, int uLen) |
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 | |
1939 | void 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 | |
2018 | finish: |
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 | |
2029 | bool 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 | |
2057 | void CairoOutputDev::endType3Char(GfxState *state) |
2058 | { |
2059 | cairo_restore(cr: cairo); |
2060 | if (cairo_shape) { |
2061 | cairo_restore(cr: cairo_shape); |
2062 | } |
2063 | } |
2064 | |
2065 | void 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 | |
2072 | void 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 | |
2084 | void CairoOutputDev::beginTextObject(GfxState *state) { } |
2085 | |
2086 | void 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 | |
2101 | void CairoOutputDev::beginActualText(GfxState *state, const GooString *text) |
2102 | { |
2103 | if (textPage) { |
2104 | actualText->begin(state, text); |
2105 | } |
2106 | } |
2107 | |
2108 | void CairoOutputDev::endActualText(GfxState *state) |
2109 | { |
2110 | if (textPage) { |
2111 | actualText->end(state); |
2112 | } |
2113 | } |
2114 | |
2115 | static inline int splashRound(SplashCoord x) |
2116 | { |
2117 | return (int)floor(x: x + 0.5); |
2118 | } |
2119 | |
2120 | static inline int splashCeil(SplashCoord x) |
2121 | { |
2122 | return (int)ceil(x: x); |
2123 | } |
2124 | |
2125 | static inline int splashFloor(SplashCoord x) |
2126 | { |
2127 | return (int)floor(x: x); |
2128 | } |
2129 | |
2130 | static 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 | |
2143 | void 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 | |
2190 | void 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 | |
2207 | void 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 | |
2263 | static 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? */ |
2274 | void 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 | |
2393 | void 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 | |
2410 | void 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 */ |
2419 | static 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 | |
2440 | void 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 | |
2479 | cairo_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 | |
2509 | void 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 | |
2564 | void 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 | |
2627 | void 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 | |
2637 | void 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 | |
2742 | cleanup: |
2743 | imgStr->close(); |
2744 | delete imgStr; |
2745 | } |
2746 | |
2747 | void 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 | |
3038 | void CairoOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate) |
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 | |
3184 | cleanup: |
3185 | imgStr->close(); |
3186 | delete imgStr; |
3187 | } |
3188 | |
3189 | static 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 | |
3197 | static 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)? |
3213 | void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, |
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 | |
3380 | cleanup: |
3381 | imgStr->close(); |
3382 | delete imgStr; |
3383 | } |
3384 | |
3385 | bool 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 | |
3414 | static 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) |
3425 | static 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) |
3450 | bool 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) |
3480 | bool 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 | |
3504 | void 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 | |
3612 | class RescaleDrawImage : public CairoRescaleBox |
3613 | { |
3614 | private: |
3615 | ImageStream *imgStr; |
3616 | GfxRGB *lookup; |
3617 | int width; |
3618 | GfxImageColorMap *colorMap; |
3619 | const int *maskColors; |
3620 | int current_row; |
3621 | bool imageError; |
3622 | |
3623 | public: |
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 | |
3780 | RescaleDrawImage::~RescaleDrawImage() = default; |
3781 | |
3782 | void 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 | |
3887 | void 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 | |
3923 | void 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 | |
3943 | CairoImageOutputDev::CairoImageOutputDev() |
3944 | { |
3945 | images = nullptr; |
3946 | numImages = 0; |
3947 | size = 0; |
3948 | imgDrawCbk = nullptr; |
3949 | imgDrawCbkData = nullptr; |
3950 | } |
3951 | |
3952 | CairoImageOutputDev::~CairoImageOutputDev() |
3953 | { |
3954 | int i; |
3955 | |
3956 | for (i = 0; i < numImages; i++) { |
3957 | delete images[i]; |
3958 | } |
3959 | gfree(p: images); |
3960 | } |
3961 | |
3962 | void 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 | |
3971 | void 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 | |
3995 | void 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 | |
4023 | void 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 | |
4054 | void 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 | |
4082 | void CairoImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, |
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 | |
4111 | void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate) |
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 | |