1 | //======================================================================== |
2 | // |
3 | // QPainterOutputDev.cc |
4 | // |
5 | // Copyright 2003 Glyph & Cog, LLC |
6 | // |
7 | //======================================================================== |
8 | |
9 | //======================================================================== |
10 | // |
11 | // Modified under the Poppler project - http://poppler.freedesktop.org |
12 | // |
13 | // All changes made under the Poppler project to this file are licensed |
14 | // under GPL version 2 or later |
15 | // |
16 | // Copyright (C) 2005 Brad Hards <bradh@frogmouth.net> |
17 | // Copyright (C) 2005-2009, 2011, 2012, 2014, 2015, 2018, 2019, 2021, 2022 Albert Astals Cid <aacid@kde.org> |
18 | // Copyright (C) 2008, 2010 Pino Toscano <pino@kde.org> |
19 | // Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org> |
20 | // Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com> |
21 | // Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com> |
22 | // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com> |
23 | // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
24 | // Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org> |
25 | // Copyright (C) 2013 Mihai Niculescu <q.quark@gmail.com> |
26 | // Copyright (C) 2017, 2018, 2020-2022 Oliver Sander <oliver.sander@tu-dresden.de> |
27 | // Copyright (C) 2017, 2022 Adrian Johnson <ajohnson@redneon.com> |
28 | // 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 |
29 | // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
30 | // |
31 | // To see a description of the changes please see the Changelog file that |
32 | // came with your tarball or type make ChangeLog if you are building from git |
33 | // |
34 | //======================================================================== |
35 | |
36 | #include <config.h> |
37 | |
38 | #include <cstring> |
39 | #include <cmath> |
40 | |
41 | #include <array> |
42 | |
43 | #include "goo/ft_utils.h" |
44 | #include "goo/gfile.h" |
45 | #include "GlobalParams.h" |
46 | #include "Error.h" |
47 | #include "Object.h" |
48 | #include "GfxState.h" |
49 | #include "GfxFont.h" |
50 | #include "Link.h" |
51 | #include "FontEncodingTables.h" |
52 | #include <fofi/FoFiTrueType.h> |
53 | #include <fofi/FoFiType1C.h> |
54 | #include "QPainterOutputDev.h" |
55 | #include "Page.h" |
56 | #include "Gfx.h" |
57 | #include "PDFDoc.h" |
58 | |
59 | #include <QtCore/QtDebug> |
60 | #include <QRawFont> |
61 | #include <QGlyphRun> |
62 | #include <QtGui/QPainterPath> |
63 | #include <QPicture> |
64 | |
65 | class QPainterOutputDevType3Font |
66 | { |
67 | public: |
68 | QPainterOutputDevType3Font(PDFDoc *doc, const std::shared_ptr<Gfx8BitFont> &font); |
69 | |
70 | const QPicture &getGlyph(int gid) const; |
71 | |
72 | private: |
73 | PDFDoc *m_doc; |
74 | std::shared_ptr<Gfx8BitFont> m_font; |
75 | |
76 | mutable std::vector<std::unique_ptr<QPicture>> glyphs; |
77 | |
78 | public: |
79 | std::vector<int> codeToGID; |
80 | }; |
81 | |
82 | QPainterOutputDevType3Font::QPainterOutputDevType3Font(PDFDoc *doc, const std::shared_ptr<Gfx8BitFont> &font) : m_doc(doc), m_font(font) |
83 | { |
84 | char *name; |
85 | const Dict *charProcs = font->getCharProcs(); |
86 | |
87 | // Storage for the rendered glyphs |
88 | glyphs.resize(new_size: charProcs->getLength()); |
89 | |
90 | // Compute the code-to-GID map |
91 | char **enc = font->getEncoding(); |
92 | |
93 | codeToGID.resize(new_size: 256); |
94 | |
95 | for (int i = 0; i < 256; ++i) { |
96 | codeToGID[i] = 0; |
97 | if (charProcs && (name = enc[i])) { |
98 | for (int j = 0; j < charProcs->getLength(); j++) { |
99 | if (strcmp(s1: name, s2: charProcs->getKey(i: j)) == 0) { |
100 | codeToGID[i] = j; |
101 | } |
102 | } |
103 | } |
104 | } |
105 | } |
106 | |
107 | const QPicture &QPainterOutputDevType3Font::getGlyph(int gid) const |
108 | { |
109 | if (!glyphs[gid]) { |
110 | |
111 | // Glyph has not been rendered before: render it now |
112 | |
113 | // Smallest box that contains all the glyphs from this font |
114 | const double *fontBBox = m_font->getFontBBox(); |
115 | PDFRectangle box(fontBBox[0], fontBBox[1], fontBBox[2], fontBBox[3]); |
116 | |
117 | Dict *resDict = m_font->getResources(); |
118 | |
119 | QPainter glyphPainter; |
120 | glyphs[gid] = std::make_unique<QPicture>(); |
121 | glyphPainter.begin(glyphs[gid].get()); |
122 | auto output_dev = std::make_unique<QPainterOutputDev>(args: &glyphPainter); |
123 | |
124 | auto gfx = std::make_unique<Gfx>(args: m_doc, args: output_dev.get(), args&: resDict, |
125 | args: &box, // pagebox |
126 | args: nullptr // cropBox |
127 | ); |
128 | |
129 | output_dev->startDoc(doc: m_doc); |
130 | |
131 | output_dev->startPage(pageNum: 1, state: gfx->getState(), xref: gfx->getXRef()); |
132 | |
133 | const Dict *charProcs = m_font->getCharProcs(); |
134 | Object charProc = charProcs->getVal(i: gid); |
135 | gfx->display(obj: &charProc); |
136 | |
137 | glyphPainter.end(); |
138 | } |
139 | |
140 | return *glyphs[gid]; |
141 | } |
142 | |
143 | //------------------------------------------------------------------------ |
144 | // QPainterOutputDev |
145 | //------------------------------------------------------------------------ |
146 | |
147 | QPainterOutputDev::QPainterOutputDev(QPainter *painter) : m_lastTransparencyGroupPicture(nullptr), m_hintingPreference(QFont::PreferDefaultHinting) |
148 | { |
149 | m_painter.push(x: painter); |
150 | m_currentBrush = QBrush(Qt::SolidPattern); |
151 | |
152 | auto error = FT_Init_FreeType(alibrary: &m_ftLibrary); |
153 | if (error) { |
154 | qCritical() << "An error occurred will initializing the FreeType library" ; |
155 | } |
156 | |
157 | // as of FT 2.1.8, CID fonts are indexed by CID instead of GID |
158 | FT_Int major, minor, patch; |
159 | FT_Library_Version(library: m_ftLibrary, amajor: &major, aminor: &minor, apatch: &patch); |
160 | m_useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7))); |
161 | } |
162 | |
163 | QPainterOutputDev::~QPainterOutputDev() |
164 | { |
165 | for (auto &codeToGID : m_codeToGIDCache) { |
166 | gfree(p: const_cast<int *>(codeToGID.second)); |
167 | } |
168 | |
169 | FT_Done_FreeType(library: m_ftLibrary); |
170 | } |
171 | |
172 | void QPainterOutputDev::startDoc(PDFDoc *doc) |
173 | { |
174 | xref = doc->getXRef(); |
175 | m_doc = doc; |
176 | |
177 | for (auto &codeToGID : m_codeToGIDCache) { |
178 | gfree(p: const_cast<int *>(codeToGID.second)); |
179 | } |
180 | m_codeToGIDCache.clear(); |
181 | } |
182 | |
183 | void QPainterOutputDev::startPage(int pageNum, GfxState *state, XRef *) { } |
184 | |
185 | void QPainterOutputDev::endPage() { } |
186 | |
187 | void QPainterOutputDev::saveState(GfxState *state) |
188 | { |
189 | m_currentPenStack.push(x: m_currentPen); |
190 | m_currentBrushStack.push(x: m_currentBrush); |
191 | m_rawFontStack.push(x: m_rawFont); |
192 | m_type3FontStack.push(x: m_currentType3Font); |
193 | m_codeToGIDStack.push(x: m_codeToGID); |
194 | |
195 | m_painter.top()->save(); |
196 | } |
197 | |
198 | void QPainterOutputDev::restoreState(GfxState *state) |
199 | { |
200 | m_painter.top()->restore(); |
201 | |
202 | m_codeToGID = m_codeToGIDStack.top(); |
203 | m_codeToGIDStack.pop(); |
204 | m_rawFont = m_rawFontStack.top(); |
205 | m_rawFontStack.pop(); |
206 | m_currentType3Font = m_type3FontStack.top(); |
207 | m_type3FontStack.pop(); |
208 | m_currentBrush = m_currentBrushStack.top(); |
209 | m_currentBrushStack.pop(); |
210 | m_currentPen = m_currentPenStack.top(); |
211 | m_currentPenStack.pop(); |
212 | } |
213 | |
214 | void QPainterOutputDev::updateAll(GfxState *state) |
215 | { |
216 | OutputDev::updateAll(state); |
217 | m_needFontUpdate = true; |
218 | } |
219 | |
220 | // Set CTM (Current Transformation Matrix) to a fixed matrix |
221 | void QPainterOutputDev::setDefaultCTM(const double *ctm) |
222 | { |
223 | m_painter.top()->setTransform(transform: QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5])); |
224 | } |
225 | |
226 | // Update the CTM (Current Transformation Matrix), i.e., compose the old |
227 | // CTM with a new matrix. |
228 | void QPainterOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) |
229 | { |
230 | updateLineDash(state); |
231 | updateLineJoin(state); |
232 | updateLineCap(state); |
233 | updateLineWidth(state); |
234 | |
235 | QTransform update(m11, m12, m21, m22, m31, m32); |
236 | |
237 | // We could also set (rather than update) the painter transformation to state->getCMT(); |
238 | m_painter.top()->setTransform(transform: update, combine: true); |
239 | } |
240 | |
241 | void QPainterOutputDev::updateLineDash(GfxState *state) |
242 | { |
243 | double dashStart; |
244 | const std::vector<double> &dashPattern = state->getLineDash(start: &dashStart); |
245 | |
246 | // Special handling for zero-length patterns, i.e., solid lines. |
247 | // Simply calling QPen::setDashPattern with an empty pattern does *not* |
248 | // result in a solid line. Rather, the current pattern is unchanged. |
249 | // See the implementation of the setDashPattern method in the file qpen.cpp. |
250 | if (dashPattern.empty()) { |
251 | m_currentPen.setStyle(Qt::SolidLine); |
252 | m_painter.top()->setPen(m_currentPen); |
253 | return; |
254 | } |
255 | |
256 | QVector<qreal> pattern(dashPattern.size()); |
257 | double scaling = state->getLineWidth(); |
258 | |
259 | // Negative line widths are not allowed, width 0 counts as 'one pixel width'. |
260 | if (scaling <= 0) { |
261 | scaling = 1.0; |
262 | } |
263 | |
264 | for (std::vector<double>::size_type i = 0; i < dashPattern.size(); ++i) { |
265 | // pdf measures the dash pattern in dots, but Qt uses the |
266 | // line width as the unit. |
267 | pattern[i] = dashPattern[i] / scaling; |
268 | } |
269 | m_currentPen.setDashPattern(pattern); |
270 | m_currentPen.setDashOffset(dashStart); |
271 | m_painter.top()->setPen(m_currentPen); |
272 | } |
273 | |
274 | void QPainterOutputDev::updateFlatness(GfxState *state) |
275 | { |
276 | // qDebug() << "updateFlatness"; |
277 | } |
278 | |
279 | void QPainterOutputDev::updateLineJoin(GfxState *state) |
280 | { |
281 | switch (state->getLineJoin()) { |
282 | case 0: |
283 | // The correct style here is Qt::SvgMiterJoin, *not* Qt::MiterJoin. |
284 | // The two differ in what to do if the miter limit is exceeded. |
285 | // See https://bugs.freedesktop.org/show_bug.cgi?id=102356 |
286 | m_currentPen.setJoinStyle(Qt::SvgMiterJoin); |
287 | break; |
288 | case 1: |
289 | m_currentPen.setJoinStyle(Qt::RoundJoin); |
290 | break; |
291 | case 2: |
292 | m_currentPen.setJoinStyle(Qt::BevelJoin); |
293 | break; |
294 | } |
295 | m_painter.top()->setPen(m_currentPen); |
296 | } |
297 | |
298 | void QPainterOutputDev::updateLineCap(GfxState *state) |
299 | { |
300 | switch (state->getLineCap()) { |
301 | case 0: |
302 | m_currentPen.setCapStyle(Qt::FlatCap); |
303 | break; |
304 | case 1: |
305 | m_currentPen.setCapStyle(Qt::RoundCap); |
306 | break; |
307 | case 2: |
308 | m_currentPen.setCapStyle(Qt::SquareCap); |
309 | break; |
310 | } |
311 | m_painter.top()->setPen(m_currentPen); |
312 | } |
313 | |
314 | void QPainterOutputDev::updateMiterLimit(GfxState *state) |
315 | { |
316 | m_currentPen.setMiterLimit(state->getMiterLimit()); |
317 | m_painter.top()->setPen(m_currentPen); |
318 | } |
319 | |
320 | void QPainterOutputDev::updateLineWidth(GfxState *state) |
321 | { |
322 | m_currentPen.setWidthF(state->getLineWidth()); |
323 | m_painter.top()->setPen(m_currentPen); |
324 | // The updateLineDash method needs to know the line width, but it is sometimes |
325 | // called before the updateLineWidth method. To make sure that the last call |
326 | // to updateLineDash before a drawing operation is always with the correct line |
327 | // width, we call it here, right after a change to the line width. |
328 | updateLineDash(state); |
329 | } |
330 | |
331 | void QPainterOutputDev::updateFillColor(GfxState *state) |
332 | { |
333 | GfxRGB rgb; |
334 | QColor brushColour = m_currentBrush.color(); |
335 | state->getFillRGB(rgb: &rgb); |
336 | brushColour.setRgbF(r: colToDbl(x: rgb.r), g: colToDbl(x: rgb.g), b: colToDbl(x: rgb.b), a: brushColour.alphaF()); |
337 | m_currentBrush.setColor(brushColour); |
338 | } |
339 | |
340 | void QPainterOutputDev::updateStrokeColor(GfxState *state) |
341 | { |
342 | GfxRGB rgb; |
343 | QColor penColour = m_currentPen.color(); |
344 | state->getStrokeRGB(rgb: &rgb); |
345 | penColour.setRgbF(r: colToDbl(x: rgb.r), g: colToDbl(x: rgb.g), b: colToDbl(x: rgb.b), a: penColour.alphaF()); |
346 | m_currentPen.setColor(penColour); |
347 | m_painter.top()->setPen(m_currentPen); |
348 | } |
349 | |
350 | void QPainterOutputDev::updateBlendMode(GfxState *state) |
351 | { |
352 | GfxBlendMode blendMode = state->getBlendMode(); |
353 | |
354 | // missing composition modes in QPainter: |
355 | // - CompositionMode_Hue |
356 | // - CompositionMode_Color |
357 | // - CompositionMode_Luminosity |
358 | // - CompositionMode_Saturation |
359 | |
360 | switch (blendMode) { |
361 | case gfxBlendMultiply: |
362 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Multiply); |
363 | break; |
364 | case gfxBlendScreen: |
365 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Screen); |
366 | break; |
367 | case gfxBlendDarken: |
368 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Darken); |
369 | break; |
370 | case gfxBlendLighten: |
371 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Lighten); |
372 | break; |
373 | case gfxBlendColorDodge: |
374 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorDodge); |
375 | break; |
376 | case gfxBlendColorBurn: |
377 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorBurn); |
378 | break; |
379 | case gfxBlendHardLight: |
380 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_HardLight); |
381 | break; |
382 | case gfxBlendSoftLight: |
383 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_SoftLight); |
384 | break; |
385 | case gfxBlendDifference: |
386 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Difference); |
387 | break; |
388 | case gfxBlendExclusion: |
389 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Exclusion); |
390 | break; |
391 | case gfxBlendColor: |
392 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_Plus); |
393 | break; |
394 | default: |
395 | qDebug() << "Unsupported blend mode, falling back to CompositionMode_SourceOver" ; |
396 | [[fallthrough]]; |
397 | case gfxBlendNormal: |
398 | m_painter.top()->setCompositionMode(QPainter::CompositionMode_SourceOver); |
399 | break; |
400 | } |
401 | } |
402 | |
403 | void QPainterOutputDev::updateFillOpacity(GfxState *state) |
404 | { |
405 | QColor brushColour = m_currentBrush.color(); |
406 | brushColour.setAlphaF(state->getFillOpacity()); |
407 | m_currentBrush.setColor(brushColour); |
408 | } |
409 | |
410 | void QPainterOutputDev::updateStrokeOpacity(GfxState *state) |
411 | { |
412 | QColor penColour = m_currentPen.color(); |
413 | penColour.setAlphaF(state->getStrokeOpacity()); |
414 | m_currentPen.setColor(penColour); |
415 | m_painter.top()->setPen(m_currentPen); |
416 | } |
417 | |
418 | void QPainterOutputDev::updateFont(GfxState *state) |
419 | { |
420 | const std::shared_ptr<GfxFont> &gfxFont = state->getFont(); |
421 | if (!gfxFont) { |
422 | return; |
423 | } |
424 | |
425 | // The key to look in the font caches |
426 | QPainterFontID fontID = { *gfxFont->getID(), state->getFontSize() }; |
427 | |
428 | // Current font is a type3 font |
429 | if (gfxFont->getType() == fontType3) { |
430 | auto cacheEntry = m_type3FontCache.find(x: fontID); |
431 | |
432 | if (cacheEntry != m_type3FontCache.end()) { |
433 | |
434 | // Take the font from the cache |
435 | m_currentType3Font = cacheEntry->second.get(); |
436 | |
437 | } else { |
438 | |
439 | m_currentType3Font = new QPainterOutputDevType3Font(m_doc, std::static_pointer_cast<Gfx8BitFont>(r: gfxFont)); |
440 | m_type3FontCache.insert(x: std::make_pair(x&: fontID, y: std::unique_ptr<QPainterOutputDevType3Font>(m_currentType3Font))); |
441 | } |
442 | |
443 | return; |
444 | } |
445 | |
446 | // Non-type3: is the font in the cache? |
447 | auto cacheEntry = m_rawFontCache.find(x: fontID); |
448 | |
449 | if (cacheEntry != m_rawFontCache.end()) { |
450 | |
451 | // Take the font from the cache |
452 | m_rawFont = cacheEntry->second.get(); |
453 | |
454 | } else { |
455 | |
456 | // New font: load it into the cache |
457 | float fontSize = state->getFontSize(); |
458 | |
459 | std::optional<GfxFontLoc> fontLoc = gfxFont->locateFont(xref, ps: nullptr); |
460 | |
461 | if (fontLoc) { |
462 | // load the font from respective location |
463 | switch (fontLoc->locType) { |
464 | case gfxFontLocEmbedded: { // if there is an embedded font, read it to memory |
465 | const std::optional<std::vector<unsigned char>> fontData = gfxFont->readEmbFontFile(xref); |
466 | |
467 | // fontData gets copied in the QByteArray constructor |
468 | m_rawFont = new QRawFont(QByteArray(fontData ? (const char *)fontData->data() : nullptr, fontData ? fontData->size() : 0), fontSize, m_hintingPreference); |
469 | m_rawFontCache.insert(x: std::make_pair(x&: fontID, y: std::unique_ptr<QRawFont>(m_rawFont))); |
470 | |
471 | break; |
472 | } |
473 | case gfxFontLocExternal: { // font is in an external font file |
474 | QString fontFile(fontLoc->path.c_str()); |
475 | m_rawFont = new QRawFont(fontFile, fontSize, m_hintingPreference); |
476 | m_rawFontCache.insert(x: std::make_pair(x&: fontID, y: std::unique_ptr<QRawFont>(m_rawFont))); |
477 | break; |
478 | } |
479 | case gfxFontLocResident: { // font resides in a PS printer |
480 | qDebug() << "Resident Font Resident not implemented yet!" ; |
481 | |
482 | break; |
483 | } |
484 | } // end switch |
485 | |
486 | } else { |
487 | qDebug() << "Font location not found!" ; |
488 | return; |
489 | } |
490 | } |
491 | |
492 | if (!m_rawFont->isValid()) { |
493 | qDebug() << "RawFont is not valid" ; |
494 | } |
495 | |
496 | // ***************************************************************************** |
497 | // We have now successfully loaded the font into a QRawFont object. This |
498 | // allows us to draw all the glyphs in the font. However, what is missing is |
499 | // the charcode-to-glyph-index mapping. Apparently, Qt does not provide this |
500 | // information at all. Therefore, we need to figure it ourselves, using |
501 | // FoFi and FreeType. |
502 | // ***************************************************************************** |
503 | |
504 | m_needFontUpdate = false; |
505 | |
506 | GfxFontType fontType = gfxFont->getType(); |
507 | |
508 | // Default: no codeToGID table |
509 | m_codeToGID = nullptr; |
510 | |
511 | // check the font file cache |
512 | Ref id = *gfxFont->getID(); |
513 | |
514 | auto codeToGIDIt = m_codeToGIDCache.find(x: id); |
515 | |
516 | if (codeToGIDIt != m_codeToGIDCache.end()) { |
517 | |
518 | m_codeToGID = codeToGIDIt->second; |
519 | |
520 | } else { |
521 | |
522 | std::optional<std::vector<unsigned char>> fontBuffer; |
523 | |
524 | std::optional<GfxFontLoc> fontLoc = gfxFont->locateFont(xref, ps: nullptr); |
525 | if (!fontLoc) { |
526 | error(category: errSyntaxError, pos: -1, msg: "Couldn't find a font for '{0:s}'" , gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)" ); |
527 | return; |
528 | } |
529 | |
530 | // embedded font |
531 | if (fontLoc->locType == gfxFontLocEmbedded) { |
532 | // if there is an embedded font, read it to memory |
533 | fontBuffer = gfxFont->readEmbFontFile(xref); |
534 | if (!fontBuffer) { |
535 | return; |
536 | } |
537 | |
538 | // external font |
539 | } else { // gfxFontLocExternal |
540 | // Hmm, fontType has already been set to gfxFont->getType() above. |
541 | // Can it really assume a different value here? |
542 | fontType = fontLoc->fontType; |
543 | } |
544 | |
545 | switch (fontType) { |
546 | case fontType1: |
547 | case fontType1C: |
548 | case fontType1COT: { |
549 | // Load the font face using FreeType |
550 | const int faceIndex = 0; // We always load the zero-th face from a font |
551 | FT_Face freeTypeFace; |
552 | |
553 | if (fontLoc->locType != gfxFontLocEmbedded) { |
554 | if (ft_new_face_from_file(library: m_ftLibrary, filename_utf8: fontLoc->path.c_str(), face_index: faceIndex, aface: &freeTypeFace)) { |
555 | error(category: errSyntaxError, pos: -1, msg: "Couldn't create a FreeType face for '{0:s}'" , gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)" ); |
556 | return; |
557 | } |
558 | } else { |
559 | if (FT_New_Memory_Face(library: m_ftLibrary, file_base: (const FT_Byte *)fontBuffer->data(), file_size: fontBuffer->size(), face_index: faceIndex, aface: &freeTypeFace)) { |
560 | error(category: errSyntaxError, pos: -1, msg: "Couldn't create a FreeType face for '{0:s}'" , gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)" ); |
561 | return; |
562 | } |
563 | } |
564 | |
565 | const char *name; |
566 | |
567 | int *codeToGID = (int *)gmallocn(count: 256, size: sizeof(int)); |
568 | for (int i = 0; i < 256; ++i) { |
569 | codeToGID[i] = 0; |
570 | if ((name = ((const char **)((Gfx8BitFont *)gfxFont.get())->getEncoding())[i])) { |
571 | codeToGID[i] = (int)FT_Get_Name_Index(face: freeTypeFace, glyph_name: (char *)name); |
572 | if (codeToGID[i] == 0) { |
573 | name = GfxFont::getAlternateName(name); |
574 | if (name) { |
575 | codeToGID[i] = FT_Get_Name_Index(face: freeTypeFace, glyph_name: (char *)name); |
576 | } |
577 | } |
578 | } |
579 | } |
580 | |
581 | FT_Done_Face(face: freeTypeFace); |
582 | |
583 | m_codeToGIDCache[id] = codeToGID; |
584 | |
585 | break; |
586 | } |
587 | case fontTrueType: |
588 | case fontTrueTypeOT: { |
589 | auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? FoFiTrueType::load(fileName: fontLoc->path.c_str()) : FoFiTrueType::make(fileA: fontBuffer->data(), lenA: fontBuffer->size()); |
590 | |
591 | m_codeToGIDCache[id] = (ff) ? ((Gfx8BitFont *)gfxFont.get())->getCodeToGIDMap(ff: ff.get()) : nullptr; |
592 | |
593 | break; |
594 | } |
595 | case fontCIDType0: |
596 | case fontCIDType0C: { |
597 | int *cidToGIDMap = nullptr; |
598 | int nCIDs = 0; |
599 | |
600 | // check for a CFF font |
601 | if (!m_useCIDs) { |
602 | auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? std::unique_ptr<FoFiType1C>(FoFiType1C::load(fileName: fontLoc->path.c_str())) : std::unique_ptr<FoFiType1C>(FoFiType1C::make(fileA: fontBuffer->data(), lenA: fontBuffer->size())); |
603 | |
604 | cidToGIDMap = (ff) ? ff->getCIDToGIDMap(nCIDs: &nCIDs) : nullptr; |
605 | } |
606 | |
607 | m_codeToGIDCache[id] = cidToGIDMap; |
608 | |
609 | break; |
610 | } |
611 | case fontCIDType0COT: { |
612 | int *codeToGID = nullptr; |
613 | |
614 | if (((GfxCIDFont *)gfxFont.get())->getCIDToGID()) { |
615 | int codeToGIDLen = ((GfxCIDFont *)gfxFont.get())->getCIDToGIDLen(); |
616 | codeToGID = (int *)gmallocn(count: codeToGIDLen, size: sizeof(int)); |
617 | memcpy(dest: codeToGID, src: ((GfxCIDFont *)gfxFont.get())->getCIDToGID(), n: codeToGIDLen * sizeof(int)); |
618 | } |
619 | |
620 | int *cidToGIDMap = nullptr; |
621 | int nCIDs = 0; |
622 | |
623 | if (!codeToGID && !m_useCIDs) { |
624 | auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? FoFiTrueType::load(fileName: fontLoc->path.c_str()) : FoFiTrueType::make(fileA: fontBuffer->data(), lenA: fontBuffer->size()); |
625 | |
626 | if (ff && ff->isOpenTypeCFF()) { |
627 | cidToGIDMap = ff->getCIDToGIDMap(nCIDs: &nCIDs); |
628 | } |
629 | } |
630 | |
631 | m_codeToGIDCache[id] = codeToGID ? codeToGID : cidToGIDMap; |
632 | |
633 | break; |
634 | } |
635 | case fontCIDType2: |
636 | case fontCIDType2OT: { |
637 | int *codeToGID = nullptr; |
638 | int codeToGIDLen = 0; |
639 | if (((GfxCIDFont *)gfxFont.get())->getCIDToGID()) { |
640 | codeToGIDLen = ((GfxCIDFont *)gfxFont.get())->getCIDToGIDLen(); |
641 | if (codeToGIDLen) { |
642 | codeToGID = (int *)gmallocn(count: codeToGIDLen, size: sizeof(int)); |
643 | memcpy(dest: codeToGID, src: ((GfxCIDFont *)gfxFont.get())->getCIDToGID(), n: codeToGIDLen * sizeof(int)); |
644 | } |
645 | } else { |
646 | auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? FoFiTrueType::load(fileName: fontLoc->path.c_str()) : FoFiTrueType::make(fileA: fontBuffer->data(), lenA: fontBuffer->size()); |
647 | if (!ff) { |
648 | return; |
649 | } |
650 | codeToGID = ((GfxCIDFont *)gfxFont.get())->getCodeToGIDMap(ff: ff.get(), codeToGIDLen: &codeToGIDLen); |
651 | } |
652 | |
653 | m_codeToGIDCache[id] = codeToGID; |
654 | |
655 | break; |
656 | } |
657 | default: |
658 | // this shouldn't happen |
659 | return; |
660 | } |
661 | |
662 | m_codeToGID = m_codeToGIDCache[id]; |
663 | } |
664 | } |
665 | |
666 | static QPainterPath convertPath(GfxState *state, const GfxPath *path, Qt::FillRule fillRule) |
667 | { |
668 | int i, j; |
669 | |
670 | QPainterPath qPath; |
671 | qPath.setFillRule(fillRule); |
672 | for (i = 0; i < path->getNumSubpaths(); ++i) { |
673 | const GfxSubpath *subpath = path->getSubpath(i); |
674 | if (subpath->getNumPoints() > 0) { |
675 | qPath.moveTo(x: subpath->getX(i: 0), y: subpath->getY(i: 0)); |
676 | j = 1; |
677 | while (j < subpath->getNumPoints()) { |
678 | if (subpath->getCurve(i: j)) { |
679 | qPath.cubicTo(ctrlPt1x: subpath->getX(i: j), ctrlPt1y: subpath->getY(i: j), ctrlPt2x: subpath->getX(i: j + 1), ctrlPt2y: subpath->getY(i: j + 1), endPtx: subpath->getX(i: j + 2), endPty: subpath->getY(i: j + 2)); |
680 | j += 3; |
681 | } else { |
682 | qPath.lineTo(x: subpath->getX(i: j), y: subpath->getY(i: j)); |
683 | ++j; |
684 | } |
685 | } |
686 | if (subpath->isClosed()) { |
687 | qPath.closeSubpath(); |
688 | } |
689 | } |
690 | } |
691 | return qPath; |
692 | } |
693 | |
694 | void QPainterOutputDev::stroke(GfxState *state) |
695 | { |
696 | m_painter.top()->strokePath(path: convertPath(state, path: state->getPath(), fillRule: Qt::OddEvenFill), pen: m_currentPen); |
697 | } |
698 | |
699 | void QPainterOutputDev::fill(GfxState *state) |
700 | { |
701 | m_painter.top()->fillPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::WindingFill), brush: m_currentBrush); |
702 | } |
703 | |
704 | void QPainterOutputDev::eoFill(GfxState *state) |
705 | { |
706 | m_painter.top()->fillPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::OddEvenFill), brush: m_currentBrush); |
707 | } |
708 | |
709 | bool QPainterOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) |
710 | { |
711 | double x0, y0, x1, y1; |
712 | shading->getCoords(x0A: &x0, y0A: &y0, x1A: &x1, y1A: &y1); |
713 | |
714 | // get the clip region bbox |
715 | double xMin, yMin, xMax, yMax; |
716 | state->getUserClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax); |
717 | |
718 | // get the function domain |
719 | double t0 = shading->getDomain0(); |
720 | double t1 = shading->getDomain1(); |
721 | |
722 | // Max number of splits along the t axis |
723 | constexpr int maxSplits = 256; |
724 | |
725 | // Max delta allowed in any color component |
726 | const double colorDelta = (dblToCol(x: 1 / 256.0)); |
727 | |
728 | // Number of color space components |
729 | auto nComps = shading->getColorSpace()->getNComps(); |
730 | // If the clipping region is a stroke, then the current operation counts as a stroke |
731 | // rather than as a fill, and the opacity has to be set accordingly. |
732 | // See https://gitlab.freedesktop.org/poppler/poppler/-/issues/178 |
733 | auto opacity = (state->getStrokePattern()) ? state->getStrokeOpacity() : state->getFillOpacity(); |
734 | |
735 | // Helper function to test two color objects for 'almost-equality' |
736 | auto isSameGfxColor = [&nComps, &colorDelta](const GfxColor &colorA, const GfxColor &colorB) { |
737 | for (int k = 0; k < nComps; ++k) { |
738 | if (abs(x: colorA.c[k] - colorB.c[k]) > colorDelta) { |
739 | return false; |
740 | } |
741 | } |
742 | return true; |
743 | }; |
744 | |
745 | // Helper function: project a number into an interval |
746 | // With C++17 this is part of the standard library |
747 | auto clamp = [](double v, double lo, double hi) { return std::min(a: std::max(a: v, b: lo), b: hi); }; |
748 | |
749 | // ta stores all parameter values where we evaluate the input shading function. |
750 | // In between, QLinearGradient will interpolate linearly. |
751 | // We set up the array with three values. |
752 | std::array<double, maxSplits + 1> ta; |
753 | ta[0] = tMin; |
754 | std::array<int, maxSplits + 1> next; |
755 | next[0] = maxSplits / 2; |
756 | ta[maxSplits / 2] = 0.5 * (tMin + tMax); |
757 | next[maxSplits / 2] = maxSplits; |
758 | ta[maxSplits] = tMax; |
759 | |
760 | // compute the color at t = tMin |
761 | double tt = clamp(t0 + (t1 - t0) * tMin, t0, t1); |
762 | |
763 | GfxColor color0, color1; |
764 | shading->getColor(t: tt, color: &color0); |
765 | |
766 | // Construct a gradient object and set its color at one parameter end |
767 | QLinearGradient gradient(QPointF(x0 + tMin * (x1 - x0), y0 + tMin * (y1 - y0)), QPointF(x0 + tMax * (x1 - x0), y0 + tMax * (y1 - y0))); |
768 | |
769 | GfxRGB rgb; |
770 | shading->getColorSpace()->getRGB(color: &color0, rgb: &rgb); |
771 | QColor qColor(colToByte(x: rgb.r), colToByte(x: rgb.g), colToByte(x: rgb.b), dblToByte(x: opacity)); |
772 | gradient.setColorAt(pos: 0, color: qColor); |
773 | |
774 | // Look for more relevant parameter values by bisection |
775 | int i = 0; |
776 | while (i < maxSplits) { |
777 | |
778 | int j = next[i]; |
779 | while (j > i + 1) { |
780 | |
781 | // Next parameter value to try |
782 | tt = clamp(t0 + (t1 - t0) * ta[j], t0, t1); |
783 | shading->getColor(t: tt, color: &color1); |
784 | |
785 | // j is a good next color stop if the input shading can be approximated well |
786 | // on the interval (ta[i], ta[j]) by a linear interpolation. |
787 | // We test this by comparing the real color in the middle between ta[i] and ta[j] |
788 | // with the linear interpolant there. |
789 | auto midPoint = 0.5 * (ta[i] + ta[j]); |
790 | GfxColor colorAtMidPoint; |
791 | shading->getColor(t: midPoint, color: &colorAtMidPoint); |
792 | |
793 | GfxColor linearlyInterpolatedColor; |
794 | for (int ii = 0; ii < nComps; ii++) { |
795 | linearlyInterpolatedColor.c[ii] = 0.5 * (color0.c[ii] + color1.c[ii]); |
796 | } |
797 | |
798 | // If the two colors are equal, ta[j] is a good place for the next color stop; take it! |
799 | if (isSameGfxColor(colorAtMidPoint, linearlyInterpolatedColor)) { |
800 | break; |
801 | } |
802 | |
803 | // Otherwise: bisect further |
804 | int k = (i + j) / 2; |
805 | ta[k] = midPoint; |
806 | next[i] = k; |
807 | next[k] = j; |
808 | j = k; |
809 | } |
810 | |
811 | // set the color |
812 | shading->getColorSpace()->getRGB(color: &color1, rgb: &rgb); |
813 | qColor.setRgb(r: colToByte(x: rgb.r), g: colToByte(x: rgb.g), b: colToByte(x: rgb.b), a: dblToByte(x: opacity)); |
814 | gradient.setColorAt(pos: (ta[j] - tMin) / (tMax - tMin), color: qColor); |
815 | |
816 | // Move to the next parameter region |
817 | color0 = color1; |
818 | i = next[i]; |
819 | } |
820 | |
821 | state->moveTo(x: xMin, y: yMin); |
822 | state->lineTo(x: xMin, y: yMax); |
823 | state->lineTo(x: xMax, y: yMax); |
824 | state->lineTo(x: xMax, y: yMin); |
825 | state->closePath(); |
826 | |
827 | // Actually paint the shaded region |
828 | QBrush newBrush(gradient); |
829 | m_painter.top()->fillPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::WindingFill), brush: newBrush); |
830 | |
831 | state->clearPath(); |
832 | |
833 | // True means: The shaded region has been painted |
834 | return true; |
835 | } |
836 | |
837 | void QPainterOutputDev::clip(GfxState *state) |
838 | { |
839 | m_painter.top()->setClipPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::WindingFill), op: Qt::IntersectClip); |
840 | } |
841 | |
842 | void QPainterOutputDev::eoClip(GfxState *state) |
843 | { |
844 | m_painter.top()->setClipPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::OddEvenFill), op: Qt::IntersectClip); |
845 | } |
846 | |
847 | void QPainterOutputDev::clipToStrokePath(GfxState *state) |
848 | { |
849 | QPainterPath clipPath = convertPath(state, path: state->getPath(), fillRule: Qt::WindingFill); |
850 | |
851 | // Get the outline of 'clipPath' as a separate path |
852 | QPainterPathStroker stroker; |
853 | stroker.setWidth(state->getLineWidth()); |
854 | stroker.setCapStyle(m_currentPen.capStyle()); |
855 | stroker.setJoinStyle(m_currentPen.joinStyle()); |
856 | stroker.setMiterLimit(state->getMiterLimit()); |
857 | stroker.setDashPattern(m_currentPen.dashPattern()); |
858 | stroker.setDashOffset(m_currentPen.dashOffset()); |
859 | QPainterPath clipPathOutline = stroker.createStroke(path: clipPath); |
860 | |
861 | // The interior of the outline is the desired clipping region |
862 | m_painter.top()->setClipPath(path: clipPathOutline, op: Qt::IntersectClip); |
863 | } |
864 | |
865 | void QPainterOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, const Unicode *u, int uLen) |
866 | { |
867 | |
868 | // First handle type3 fonts |
869 | const std::shared_ptr<GfxFont> &gfxFont = state->getFont(); |
870 | |
871 | GfxFontType fontType = gfxFont->getType(); |
872 | if (fontType == fontType3) { |
873 | |
874 | ///////////////////////////////////////////////////////////////////// |
875 | // Draw the QPicture that contains the glyph onto the page |
876 | ///////////////////////////////////////////////////////////////////// |
877 | |
878 | // Store the QPainter state; we need to modify it temporarily |
879 | m_painter.top()->save(); |
880 | |
881 | // Make the glyph position the coordinate origin -- that's our center of scaling |
882 | m_painter.top()->translate(offset: QPointF(x - originX, y - originY)); |
883 | |
884 | const double *mat = gfxFont->getFontMatrix(); |
885 | QTransform fontMatrix(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); |
886 | |
887 | // Scale with the font size |
888 | fontMatrix.scale(sx: state->getFontSize(), sy: state->getFontSize()); |
889 | m_painter.top()->setTransform(transform: fontMatrix, combine: true); |
890 | |
891 | // Apply the text matrix on top |
892 | const double *textMat = state->getTextMat(); |
893 | |
894 | QTransform textTransform(textMat[0] * state->getHorizScaling(), textMat[1] * state->getHorizScaling(), textMat[2], textMat[3], 0, 0); |
895 | |
896 | m_painter.top()->setTransform(transform: textTransform, combine: true); |
897 | |
898 | // Actually draw the glyph |
899 | int gid = m_currentType3Font->codeToGID[code]; |
900 | m_painter.top()->drawPicture(p: QPointF(0, 0), picture: m_currentType3Font->getGlyph(gid)); |
901 | |
902 | // Restore transformation |
903 | m_painter.top()->restore(); |
904 | |
905 | return; |
906 | } |
907 | |
908 | // check for invisible text -- this is used by Acrobat Capture |
909 | int render = state->getRender(); |
910 | if (render == 3 || !m_rawFont) { |
911 | qDebug() << "Invisible text found!" ; |
912 | return; |
913 | } |
914 | |
915 | if (!(render & 1)) { |
916 | quint32 glyphIndex = (m_codeToGID) ? m_codeToGID[code] : code; |
917 | QPointF glyphPosition = QPointF(x - originX, y - originY); |
918 | |
919 | // QGlyphRun objects can hold an entire sequence of glyphs, and it would possibly |
920 | // be more efficient to simply note the glyph and glyph position here and then |
921 | // draw several glyphs at once in the endString method. What keeps us from doing |
922 | // that is the transformation below: each glyph needs to be drawn upside down, |
923 | // i.e., reflected at its own baseline. Since we have no guarantee that this |
924 | // baseline is the same for all glyphs in a string we have to do it one by one. |
925 | QGlyphRun glyphRun; |
926 | glyphRun.setRawData(glyphIndexArray: &glyphIndex, glyphPositionArray: &glyphPosition, size: 1); |
927 | glyphRun.setRawFont(*m_rawFont); |
928 | |
929 | // Store the QPainter state; we need to modify it temporarily |
930 | m_painter.top()->save(); |
931 | |
932 | // Apply the text matrix to the glyph. The glyph is not scaled by the font size, |
933 | // because the font in m_rawFont already has the correct size. |
934 | // Additionally, the CTM is upside down, i.e., it contains a negative Y-scaling |
935 | // entry. Therefore, Qt will paint the glyphs upside down. We need to temporarily |
936 | // reflect the page at glyphPosition.y(). |
937 | |
938 | // Make the glyph position the coordinate origin -- that's our center of scaling |
939 | const double *textMat = state->getTextMat(); |
940 | |
941 | m_painter.top()->translate(offset: QPointF(glyphPosition.x(), glyphPosition.y())); |
942 | |
943 | QTransform textTransform(textMat[0] * state->getHorizScaling(), textMat[1] * state->getHorizScaling(), |
944 | -textMat[2], // reflect at the horizontal axis, |
945 | -textMat[3], // because CTM is upside-down. |
946 | 0, 0); |
947 | |
948 | m_painter.top()->setTransform(transform: textTransform, combine: true); |
949 | |
950 | // We are painting a filled glyph here. But QPainter uses the pen to draw even filled text, |
951 | // not the brush. (see, e.g., http://doc.qt.io/qt-5/qpainter.html#setPen ) |
952 | // Therefore we have to temporarily overwrite the pen color. |
953 | |
954 | // Since we are drawing a filled glyph, one would really expect to have m_currentBrush |
955 | // have the correct color. However, somehow state->getFillRGB can change without |
956 | // updateFillColor getting called. Then m_currentBrush may not contain the correct color. |
957 | GfxRGB rgb; |
958 | state->getFillRGB(rgb: &rgb); |
959 | QColor fontColor; |
960 | fontColor.setRgbF(r: colToDbl(x: rgb.r), g: colToDbl(x: rgb.g), b: colToDbl(x: rgb.b), a: state->getFillOpacity()); |
961 | m_painter.top()->setPen(fontColor); |
962 | |
963 | // Actually draw the glyph |
964 | m_painter.top()->drawGlyphRun(position: QPointF(-glyphPosition.x(), -glyphPosition.y()), glyphRun); |
965 | |
966 | // Restore transformation and pen color |
967 | m_painter.top()->restore(); |
968 | } |
969 | } |
970 | |
971 | void QPainterOutputDev::type3D0(GfxState *state, double wx, double wy) { } |
972 | |
973 | void QPainterOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { } |
974 | |
975 | void QPainterOutputDev::endTextObject(GfxState *state) { } |
976 | |
977 | void QPainterOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg) |
978 | { |
979 | auto imgStr = std::make_unique<ImageStream>(args&: str, args&: width, |
980 | args: 1, // numPixelComps |
981 | args: 1 // getBits |
982 | ); |
983 | imgStr->reset(); |
984 | |
985 | // TODO: Would using QImage::Format_Mono be more efficient here? |
986 | QImage image(width, height, QImage::Format_ARGB32); |
987 | unsigned int *data = reinterpret_cast<unsigned int *>(image.bits()); |
988 | int stride = image.bytesPerLine() / 4; |
989 | |
990 | QRgb fillColor = m_currentBrush.color().rgb(); |
991 | |
992 | for (int y = 0; y < height; y++) { |
993 | |
994 | unsigned char *pix = imgStr->getLine(); |
995 | |
996 | // Invert the vertical coordinate: y is increasing from top to bottom |
997 | // on the page, but y is increasing bottom to top in the picture. |
998 | unsigned int *dest = data + (height - 1 - y) * stride; |
999 | |
1000 | for (int x = 0; x < width; x++) { |
1001 | |
1002 | bool opaque = ((bool)pix[x]) == invert; |
1003 | dest[x] = (opaque) ? fillColor : 0; |
1004 | } |
1005 | } |
1006 | |
1007 | // At this point, the QPainter coordinate transformation (CTM) is such |
1008 | // that QRect(0,0,1,1) is exactly the area of the image. |
1009 | m_painter.top()->drawImage(r: QRect(0, 0, 1, 1), image); |
1010 | imgStr->close(); |
1011 | } |
1012 | |
1013 | // TODO: lots more work here. |
1014 | void QPainterOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg) |
1015 | { |
1016 | unsigned int *data; |
1017 | unsigned int *line; |
1018 | int x, y; |
1019 | unsigned char *pix; |
1020 | int i; |
1021 | QImage image; |
1022 | int stride; |
1023 | |
1024 | /* TODO: Do we want to cache these? */ |
1025 | auto imgStr = std::make_unique<ImageStream>(args&: str, args&: width, args: colorMap->getNumPixelComps(), args: colorMap->getBits()); |
1026 | imgStr->reset(); |
1027 | |
1028 | image = QImage(width, height, QImage::Format_ARGB32); |
1029 | data = reinterpret_cast<unsigned int *>(image.bits()); |
1030 | stride = image.bytesPerLine() / 4; |
1031 | for (y = 0; y < height; y++) { |
1032 | pix = imgStr->getLine(); |
1033 | // Invert the vertical coordinate: y is increasing from top to bottom |
1034 | // on the page, but y is increasing bottom to top in the picture. |
1035 | line = data + (height - 1 - y) * stride; |
1036 | colorMap->getRGBLine(in: pix, out: line, length: width); |
1037 | |
1038 | if (maskColors) { |
1039 | for (x = 0; x < width; x++) { |
1040 | for (i = 0; i < colorMap->getNumPixelComps(); ++i) { |
1041 | if (pix[i] < maskColors[2 * i] * 255 || pix[i] > maskColors[2 * i + 1] * 255) { |
1042 | *line = *line | 0xff000000; |
1043 | break; |
1044 | } |
1045 | } |
1046 | pix += colorMap->getNumPixelComps(); |
1047 | line++; |
1048 | } |
1049 | } else { |
1050 | for (x = 0; x < width; x++) { |
1051 | *line = *line | 0xff000000; |
1052 | line++; |
1053 | } |
1054 | } |
1055 | } |
1056 | |
1057 | // At this point, the QPainter coordinate transformation (CTM) is such |
1058 | // that QRect(0,0,1,1) is exactly the area of the image. |
1059 | m_painter.top()->drawImage(r: QRect(0, 0, 1, 1), image); |
1060 | } |
1061 | |
1062 | void QPainterOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, |
1063 | bool maskInterpolate) |
1064 | { |
1065 | // Bail out if the image size doesn't match the mask size. I don't know |
1066 | // what to do in this case. |
1067 | if (width != maskWidth || height != maskHeight) { |
1068 | qDebug() << "Soft mask size does not match image size!" ; |
1069 | drawImage(state, ref, str, width, height, colorMap, interpolate, maskColors: nullptr, inlineImg: false); |
1070 | return; |
1071 | } |
1072 | |
1073 | // Bail out if the mask isn't a single channel. I don't know |
1074 | // what to do in this case. |
1075 | if (maskColorMap->getColorSpace()->getNComps() != 1) { |
1076 | qDebug() << "Soft mask is not a single 8-bit channel!" ; |
1077 | drawImage(state, ref, str, width, height, colorMap, interpolate, maskColors: nullptr, inlineImg: false); |
1078 | return; |
1079 | } |
1080 | |
1081 | /* TODO: Do we want to cache these? */ |
1082 | auto imgStr = std::make_unique<ImageStream>(args&: str, args&: width, args: colorMap->getNumPixelComps(), args: colorMap->getBits()); |
1083 | imgStr->reset(); |
1084 | |
1085 | auto maskImageStr = std::make_unique<ImageStream>(args&: maskStr, args&: maskWidth, args: maskColorMap->getNumPixelComps(), args: maskColorMap->getBits()); |
1086 | maskImageStr->reset(); |
1087 | |
1088 | QImage image(width, height, QImage::Format_ARGB32); |
1089 | unsigned int *data = reinterpret_cast<unsigned int *>(image.bits()); |
1090 | int stride = image.bytesPerLine() / 4; |
1091 | |
1092 | std::vector<unsigned char> maskLine(maskWidth); |
1093 | |
1094 | for (int y = 0; y < height; y++) { |
1095 | |
1096 | unsigned char *pix = imgStr->getLine(); |
1097 | unsigned char *maskPix = maskImageStr->getLine(); |
1098 | |
1099 | // Invert the vertical coordinate: y is increasing from top to bottom |
1100 | // on the page, but y is increasing bottom to top in the picture. |
1101 | unsigned int *line = data + (height - 1 - y) * stride; |
1102 | colorMap->getRGBLine(in: pix, out: line, length: width); |
1103 | |
1104 | // Apply the mask values to the image alpha channel |
1105 | maskColorMap->getGrayLine(in: maskPix, out: maskLine.data(), length: width); |
1106 | for (int x = 0; x < width; x++) { |
1107 | *line = *line | (maskLine[x] << 24); |
1108 | line++; |
1109 | } |
1110 | } |
1111 | |
1112 | // At this point, the QPainter coordinate transformation (CTM) is such |
1113 | // that QRect(0,0,1,1) is exactly the area of the image. |
1114 | m_painter.top()->drawImage(r: QRect(0, 0, 1, 1), image); |
1115 | } |
1116 | |
1117 | void QPainterOutputDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/, GfxColorSpace * /*blendingColorSpace*/, bool /*isolated*/, bool /*knockout*/, bool /*forSoftMask*/) |
1118 | { |
1119 | // The entire transparency group will be painted into a |
1120 | // freshly created QPicture object. Since an existing painter |
1121 | // cannot change its paint device, we need to construct a |
1122 | // new QPainter object as well. |
1123 | m_qpictures.push(x: new QPicture); |
1124 | m_painter.push(x: new QPainter(m_qpictures.top())); |
1125 | } |
1126 | |
1127 | void QPainterOutputDev::endTransparencyGroup(GfxState * /*state*/) |
1128 | { |
1129 | // Stop painting into the group |
1130 | m_painter.top()->end(); |
1131 | |
1132 | // Kill the painter that has been used for the transparency group |
1133 | delete (m_painter.top()); |
1134 | m_painter.pop(); |
1135 | |
1136 | // Store the QPicture object that holds the result of the transparency group |
1137 | // painting. It will be painted and deleted in the method paintTransparencyGroup. |
1138 | if (m_lastTransparencyGroupPicture) { |
1139 | qDebug() << "Found a transparency group that has not been painted" ; |
1140 | delete (m_lastTransparencyGroupPicture); |
1141 | } |
1142 | m_lastTransparencyGroupPicture = m_qpictures.top(); |
1143 | m_qpictures.pop(); |
1144 | } |
1145 | |
1146 | void QPainterOutputDev::paintTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/) |
1147 | { |
1148 | // Actually draw the transparency group |
1149 | m_painter.top()->drawPicture(x: 0, y: 0, p: *m_lastTransparencyGroupPicture); |
1150 | |
1151 | // And delete it |
1152 | delete (m_lastTransparencyGroupPicture); |
1153 | m_lastTransparencyGroupPicture = nullptr; |
1154 | } |
1155 | |