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