1 /*
2     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "pdfextractoroutputdevice_p.h"
8 #include "pdfimage.h"
9 #include "pdfimage_p.h"
10 #include "popplerutils_p.h"
11 
12 #include <QDebug>
13 
14 using namespace KItinerary;
15 
16 #ifdef HAVE_POPPLER
PdfExtractorOutputDevice()17 PdfExtractorOutputDevice::PdfExtractorOutputDevice()
18     : TextOutputDev(nullptr, false, 0, false, false)
19 {
20 }
21 
drawImage(GfxState * state,Object * ref,Stream * str,int width,int height,GfxImageColorMap * colorMap,bool interpolate,PopplerMaskColors * maskColors,bool inlineImg)22 void PdfExtractorOutputDevice::drawImage(GfxState* state, Object* ref, Stream* str, int width, int height, GfxImageColorMap* colorMap, bool interpolate, PopplerMaskColors* maskColors, bool inlineImg)
23 {
24     Q_UNUSED(str)
25     Q_UNUSED(interpolate)
26     Q_UNUSED(maskColors)
27     Q_UNUSED(inlineImg)
28 
29     if (!colorMap || !colorMap->isOk() || !ref || !ref->isRef()) {
30         return;
31     }
32 
33     QImage::Format format;
34     if (colorMap->getColorSpace()->getMode() == csIndexed) {
35         format = QImage::Format_RGB888;
36     } else if (colorMap->getNumPixelComps() == 1 && (colorMap->getBits() >= 1 && colorMap->getBits() <= 8)) {
37         format = QImage::Format_Grayscale8;
38     } else if (colorMap->getNumPixelComps() == 3 && colorMap->getBits() == 8) {
39         format = QImage::Format_RGB888;
40     } else {
41         return;
42     }
43 
44     PdfImage pdfImg;
45     pdfImg.d->m_refNum = ref->getRef().num;
46     pdfImg.d->m_refGen = ref->getRef().gen;
47 
48 #if KPOPPLER_VERSION >= QT_VERSION_CHECK(0, 69, 0)
49     pdfImg.d->m_colorMap.reset(colorMap->copy());
50 #endif
51     pdfImg.d->m_sourceHeight = height;
52     pdfImg.d->m_sourceWidth = width;
53     pdfImg.d->m_width = width;
54     pdfImg.d->m_height = height;
55     // deal with aspect-ratio changing scaling
56     const auto sourceAspectRatio = (double)width / (double)height;
57     const auto targetAspectRatio = state->getCTM()[0] / -state->getCTM()[3];
58     if (!qFuzzyCompare(sourceAspectRatio, targetAspectRatio) && qFuzzyIsNull(state->getCTM()[1]) && qFuzzyIsNull(state->getCTM()[2])) {
59         if (targetAspectRatio > sourceAspectRatio) {
60             pdfImg.d->m_width = width * targetAspectRatio / sourceAspectRatio;
61         } else {
62             pdfImg.d->m_height = height * sourceAspectRatio / targetAspectRatio;
63         }
64     }
65     pdfImg.d->m_transform = PopplerUtils::currentTransform(state);
66     pdfImg.d->m_format = format;
67     m_images.push_back(pdfImg);
68 }
69 
saveState(GfxState * state)70 void PdfExtractorOutputDevice::saveState(GfxState *state)
71 {
72     Q_UNUSED(state)
73     m_vectorOps.push_back(VectorOp{VectorOp::PushState, {}, {}});
74 }
75 
restoreState(GfxState * state)76 void PdfExtractorOutputDevice::restoreState(GfxState *state)
77 {
78     Q_UNUSED(state)
79     if (m_vectorOps.empty()) {
80         return;
81     }
82     const auto &lastOp = *(m_vectorOps.end() -1);
83     if (lastOp.type == VectorOp::PushState) {
84         m_vectorOps.resize(m_vectorOps.size() - 1);
85     } else {
86         m_vectorOps.push_back(VectorOp{VectorOp::PopState, {}, {}});
87     }
88 }
89 
isRelevantStroke(const QPen & pen)90 static bool isRelevantStroke(const QPen &pen)
91 {
92     return !qFuzzyCompare(pen.widthF(), 0.0) && pen.color() == Qt::black;
93 }
94 
isRectangularPath(const QPainterPath & path)95 static bool isRectangularPath(const QPainterPath &path)
96 {
97     qreal x = 0.0, y = 0.0;
98     for (int i = 0; i < path.elementCount(); ++i) {
99         const auto elem = path.elementAt(i);
100         switch (elem.type) {
101             case QPainterPath::MoveToElement:
102                 x = elem.x;
103                 y = elem.y;
104                 break;
105             case QPainterPath::LineToElement:
106                 if (x != elem.x && y != elem.y) {
107                     qDebug() << "path contains diagonal line, discarding";
108                     return false;
109                 }
110                 x = elem.x;
111                 y = elem.y;
112                 break;
113             case QPainterPath::CurveToElement:
114             case QPainterPath::CurveToDataElement:
115                 qDebug() << "path contains a curve, discarding";
116                 return false;
117         }
118     }
119 
120     return true;
121 }
122 
stroke(GfxState * state)123 void PdfExtractorOutputDevice::stroke(GfxState *state)
124 {
125     const auto pen = PopplerUtils::currentPen(state);
126     if (!isRelevantStroke(pen)) {
127         return;
128     }
129 
130     const auto path = PopplerUtils::convertPath(state->getPath(), Qt::WindingFill);
131     if (!isRectangularPath(path)) {
132         return;
133     }
134     const auto t = PopplerUtils::currentTransform(state);
135     m_vectorOps.push_back(VectorOp{VectorOp::Path, t, {path, pen, QBrush()}});
136 }
137 
isRelevantFill(const QBrush & brush)138 static bool isRelevantFill(const QBrush &brush)
139 {
140     return brush.color() == Qt::black;
141 }
142 
fill(GfxState * state)143 void PdfExtractorOutputDevice::fill(GfxState *state)
144 {
145     const auto brush = PopplerUtils::currentBrush(state);
146     if (!isRelevantFill(brush)) {
147         return;
148     }
149 
150     const auto path = PopplerUtils::convertPath(state->getPath(), Qt::WindingFill);
151     const auto b = path.boundingRect();
152     if (b.width() == 0  || b.height() == 0) {
153         return;
154     }
155 
156     const auto t = PopplerUtils::currentTransform(state);
157     m_vectorOps.push_back(VectorOp{VectorOp::Path, t, {path, QPen(), brush}});
158 }
159 
eoFill(GfxState * state)160 void PdfExtractorOutputDevice::eoFill(GfxState *state)
161 {
162     const auto brush = PopplerUtils::currentBrush(state);
163     if (!isRelevantFill(brush)) {
164         return;
165     }
166 
167     const auto path = PopplerUtils::convertPath(state->getPath(), Qt::OddEvenFill);
168     const auto b = path.boundingRect();
169     if (b.width() == 0  || b.height() == 0) {
170         return;
171     }
172 
173     const auto t = PopplerUtils::currentTransform(state);
174     m_vectorOps.push_back(VectorOp{VectorOp::Path, t, {path, QPen(), brush}});
175 }
176 
finalize()177 void PdfExtractorOutputDevice::finalize()
178 {
179     // remove single state groups, then try to merge adjacents paths
180     std::vector<VectorOp> mergedOps;
181     mergedOps.reserve(m_vectorOps.size());
182     for (auto it = m_vectorOps.begin(); it != m_vectorOps.end(); ++it) {
183         if ((*it).type == VectorOp::PushState && std::distance(it, m_vectorOps.end()) >= 2 && (*(it + 1)).type == VectorOp::Path && (*(it + 2)).type == VectorOp::PopState) {
184             ++it;
185             mergedOps.push_back(*it);
186             ++it;
187         } else {
188             mergedOps.push_back(*it);
189         }
190     }
191     //qDebug() << m_vectorOps.size() << mergedOps.size();
192 
193     std::vector<PdfVectorPicture::PathStroke> strokes;
194     QTransform t;
195     for (const auto &op : mergedOps) {
196         if (op.type == VectorOp::Path) {
197             if (t.isIdentity()) {
198                 t = op.transform;
199             }
200             if (t != op.transform) {
201                 //qDebug() << "diffent transforms for strokes, not supported yet";
202                 continue;
203             }
204             strokes.push_back(op.stroke);
205         } else if (!strokes.empty()) {
206             PdfVectorPicture pic;
207             pic.setStrokes(std::move(strokes));
208             pic.setTransform(t);
209             addVectorImage(pic);
210             t = QTransform();
211         }
212     }
213     if (!strokes.empty()) {
214         PdfVectorPicture pic;
215         pic.setStrokes(std::move(strokes));
216         pic.setTransform(t);
217         addVectorImage(pic);
218     }
219 }
220 
addVectorImage(const PdfVectorPicture & pic)221 void PdfExtractorOutputDevice::addVectorImage(const PdfVectorPicture &pic)
222 {
223     if (pic.pathElementsCount() < 400) { // not complex enough for a barcode
224         return;
225     }
226 
227     PdfImage img;
228     img.d->m_height = pic.height();
229     img.d->m_width = pic.width();
230     img.d->m_sourceHeight = pic.sourceHeight();
231     img.d->m_sourceWidth = pic.sourceWidth();
232     img.d->m_transform = pic.transform();
233     img.d->m_vectorPicture = pic;
234     m_images.push_back(img);
235 }
236 
237 #endif
238