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
65class QPainterOutputDevType3Font
66{
67public:
68 QPainterOutputDevType3Font(PDFDoc *doc, const std::shared_ptr<Gfx8BitFont> &font);
69
70 const QPicture &getGlyph(int gid) const;
71
72private:
73 PDFDoc *m_doc;
74 std::shared_ptr<Gfx8BitFont> m_font;
75
76 mutable std::vector<std::unique_ptr<QPicture>> glyphs;
77
78public:
79 std::vector<int> codeToGID;
80};
81
82QPainterOutputDevType3Font::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
107const 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
147QPainterOutputDev::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
163QPainterOutputDev::~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
172void 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
183void QPainterOutputDev::startPage(int pageNum, GfxState *state, XRef *) { }
184
185void QPainterOutputDev::endPage() { }
186
187void 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
198void 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
214void 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
221void 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.
228void 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
241void 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
274void QPainterOutputDev::updateFlatness(GfxState *state)
275{
276 // qDebug() << "updateFlatness";
277}
278
279void 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
298void 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
314void QPainterOutputDev::updateMiterLimit(GfxState *state)
315{
316 m_currentPen.setMiterLimit(state->getMiterLimit());
317 m_painter.top()->setPen(m_currentPen);
318}
319
320void 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
331void 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
340void 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
350void 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
403void QPainterOutputDev::updateFillOpacity(GfxState *state)
404{
405 QColor brushColour = m_currentBrush.color();
406 brushColour.setAlphaF(state->getFillOpacity());
407 m_currentBrush.setColor(brushColour);
408}
409
410void 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
418void 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
666static 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
694void QPainterOutputDev::stroke(GfxState *state)
695{
696 m_painter.top()->strokePath(path: convertPath(state, path: state->getPath(), fillRule: Qt::OddEvenFill), pen: m_currentPen);
697}
698
699void QPainterOutputDev::fill(GfxState *state)
700{
701 m_painter.top()->fillPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::WindingFill), brush: m_currentBrush);
702}
703
704void QPainterOutputDev::eoFill(GfxState *state)
705{
706 m_painter.top()->fillPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::OddEvenFill), brush: m_currentBrush);
707}
708
709bool 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
837void QPainterOutputDev::clip(GfxState *state)
838{
839 m_painter.top()->setClipPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::WindingFill), op: Qt::IntersectClip);
840}
841
842void QPainterOutputDev::eoClip(GfxState *state)
843{
844 m_painter.top()->setClipPath(path: convertPath(state, path: state->getPath(), fillRule: Qt::OddEvenFill), op: Qt::IntersectClip);
845}
846
847void 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
865void 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
971void QPainterOutputDev::type3D0(GfxState *state, double wx, double wy) { }
972
973void QPainterOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { }
974
975void QPainterOutputDev::endTextObject(GfxState *state) { }
976
977void 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.
1014void 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
1062void 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
1117void 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
1127void 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
1146void 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

source code of poppler/qt6/src/QPainterOutputDev.cc