| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtGui module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include "qpdf_p.h" | 
| 41 |  | 
| 42 | #ifndef QT_NO_PDF | 
| 43 |  | 
| 44 | #include "qplatformdefs.h" | 
| 45 |  | 
| 46 | #include <private/qfont_p.h> | 
| 47 | #include <private/qmath_p.h> | 
| 48 | #include <private/qpainter_p.h> | 
| 49 |  | 
| 50 | #include <qbuffer.h> | 
| 51 | #include <qcryptographichash.h> | 
| 52 | #include <qdatetime.h> | 
| 53 | #include <qdebug.h> | 
| 54 | #include <qfile.h> | 
| 55 | #include <qimagewriter.h> | 
| 56 | #include <qnumeric.h> | 
| 57 | #include <qtemporaryfile.h> | 
| 58 | #include <quuid.h> | 
| 59 |  | 
| 60 | #ifndef QT_NO_COMPRESS | 
| 61 | #include <zlib.h> | 
| 62 | #endif | 
| 63 |  | 
| 64 | #ifdef QT_NO_COMPRESS | 
| 65 | static const bool do_compress = false; | 
| 66 | #else | 
| 67 | static const bool do_compress = true; | 
| 68 | #endif | 
| 69 |  | 
| 70 | // might be helpful for smooth transforms of images | 
| 71 | // Can't use it though, as gs generates completely wrong images if this is true. | 
| 72 | static const bool interpolateImages = false; | 
| 73 |  | 
| 74 | static void initResources() | 
| 75 | { | 
| 76 |     Q_INIT_RESOURCE(qpdf); | 
| 77 | } | 
| 78 |  | 
| 79 | QT_BEGIN_NAMESPACE | 
| 80 |  | 
| 81 | inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() | 
| 82 | { | 
| 83 |     QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures; | 
| 84 |     f &= ~(QPaintEngine::PorterDuff | 
| 85 |            | QPaintEngine::PerspectiveTransform | 
| 86 |            | QPaintEngine::ObjectBoundingModeGradients | 
| 87 |            | QPaintEngine::ConicalGradientFill); | 
| 88 |     return f; | 
| 89 | } | 
| 90 |  | 
| 91 | extern bool qt_isExtendedRadialGradient(const QBrush &brush); | 
| 92 |  | 
| 93 | // helper function to remove transparency from brush in PDF/A-1b mode | 
| 94 | static void removeTransparencyFromBrush(QBrush &brush) | 
| 95 | { | 
| 96 |     if (brush.style() == Qt::SolidPattern) { | 
| 97 |         QColor color = brush.color(); | 
| 98 |         if (color.alpha() != 255) { | 
| 99 |             color.setAlpha(255); | 
| 100 |             brush.setColor(color); | 
| 101 |         } | 
| 102 |  | 
| 103 |         return; | 
| 104 |     } | 
| 105 |  | 
| 106 |     if (qt_isExtendedRadialGradient(brush)) { | 
| 107 |         brush = QBrush(Qt::black); // the safest we can do so far... | 
| 108 |         return; | 
| 109 |     } | 
| 110 |  | 
| 111 |     if (brush.style() == Qt::LinearGradientPattern | 
| 112 |         || brush.style() == Qt::RadialGradientPattern | 
| 113 |         || brush.style() == Qt::ConicalGradientPattern) { | 
| 114 |  | 
| 115 |         QGradientStops stops = brush.gradient()->stops(); | 
| 116 |         for (int i = 0; i < stops.size(); ++i) { | 
| 117 |             if (stops[i].second.alpha() != 255) | 
| 118 |                 stops[i].second.setAlpha(255); | 
| 119 |         } | 
| 120 |  | 
| 121 |         const_cast<QGradient*>(brush.gradient())->setStops(stops); | 
| 122 |         return; | 
| 123 |     } | 
| 124 |  | 
| 125 |     if (brush.style() == Qt::TexturePattern) { | 
| 126 |         // handled inside QPdfEnginePrivate::addImage() already | 
| 127 |         return; | 
| 128 |     } | 
| 129 | } | 
| 130 |  | 
| 131 |  | 
| 132 | /* also adds a space at the end of the number */ | 
| 133 | const char *qt_real_to_string(qreal val, char *buf) { | 
| 134 |     const char *ret = buf; | 
| 135 |  | 
| 136 |     if (qIsNaN(val)) { | 
| 137 |         *(buf++) = '0'; | 
| 138 |         *(buf++) = ' '; | 
| 139 |         *buf = 0; | 
| 140 |         return ret; | 
| 141 |     } | 
| 142 |  | 
| 143 |     if (val < 0) { | 
| 144 |         *(buf++) = '-'; | 
| 145 |         val = -val; | 
| 146 |     } | 
| 147 |     unsigned int ival = (unsigned int) val; | 
| 148 |     qreal frac = val - (qreal)ival; | 
| 149 |  | 
| 150 |     int ifrac = (int)(frac * 1000000000); | 
| 151 |     if (ifrac == 1000000000) { | 
| 152 |         ++ival; | 
| 153 |         ifrac = 0; | 
| 154 |     } | 
| 155 |     char output[256]; | 
| 156 |     int i = 0; | 
| 157 |     while (ival) { | 
| 158 |         output[i] = '0' + (ival % 10); | 
| 159 |         ++i; | 
| 160 |         ival /= 10; | 
| 161 |     } | 
| 162 |     int fact = 100000000; | 
| 163 |     if (i == 0) { | 
| 164 |         *(buf++) = '0'; | 
| 165 |     } else { | 
| 166 |         while (i) { | 
| 167 |             *(buf++) = output[--i]; | 
| 168 |             fact /= 10; | 
| 169 |             ifrac /= 10; | 
| 170 |         } | 
| 171 |     } | 
| 172 |  | 
| 173 |     if (ifrac) { | 
| 174 |         *(buf++) =  '.'; | 
| 175 |         while (fact) { | 
| 176 |             *(buf++) = '0' + ((ifrac/fact) % 10); | 
| 177 |             fact /= 10; | 
| 178 |         } | 
| 179 |     } | 
| 180 |     *(buf++) = ' '; | 
| 181 |     *buf = 0; | 
| 182 |     return ret; | 
| 183 | } | 
| 184 |  | 
| 185 | const char *qt_int_to_string(int val, char *buf) { | 
| 186 |     const char *ret = buf; | 
| 187 |     if (val < 0) { | 
| 188 |         *(buf++) = '-'; | 
| 189 |         val = -val; | 
| 190 |     } | 
| 191 |     char output[256]; | 
| 192 |     int i = 0; | 
| 193 |     while (val) { | 
| 194 |         output[i] = '0' + (val % 10); | 
| 195 |         ++i; | 
| 196 |         val /= 10; | 
| 197 |     } | 
| 198 |     if (i == 0) { | 
| 199 |         *(buf++) = '0'; | 
| 200 |     } else { | 
| 201 |         while (i) | 
| 202 |             *(buf++) = output[--i]; | 
| 203 |     } | 
| 204 |     *(buf++) = ' '; | 
| 205 |     *buf = 0; | 
| 206 |     return ret; | 
| 207 | } | 
| 208 |  | 
| 209 |  | 
| 210 | namespace QPdf { | 
| 211 |     ByteStream::ByteStream(QByteArray *byteArray, bool fileBacking) | 
| 212 |             : dev(new QBuffer(byteArray)), | 
| 213 |             fileBackingEnabled(fileBacking), | 
| 214 |             fileBackingActive(false), | 
| 215 |             handleDirty(false) | 
| 216 |     { | 
| 217 |         dev->open(QIODevice::ReadWrite | QIODevice::Append); | 
| 218 |     } | 
| 219 |  | 
| 220 |     ByteStream::ByteStream(bool fileBacking) | 
| 221 |             : dev(new QBuffer(&ba)), | 
| 222 |             fileBackingEnabled(fileBacking), | 
| 223 |             fileBackingActive(false), | 
| 224 |             handleDirty(false) | 
| 225 |     { | 
| 226 |         dev->open(QIODevice::ReadWrite); | 
| 227 |     } | 
| 228 |  | 
| 229 |     ByteStream::~ByteStream() | 
| 230 |     { | 
| 231 |         delete dev; | 
| 232 |     } | 
| 233 |  | 
| 234 |     ByteStream &ByteStream::operator <<(char chr) | 
| 235 |     { | 
| 236 |         if (handleDirty) prepareBuffer(); | 
| 237 |         dev->write(&chr, 1); | 
| 238 |         return *this; | 
| 239 |     } | 
| 240 |  | 
| 241 |     ByteStream &ByteStream::operator <<(const char *str) | 
| 242 |     { | 
| 243 |         if (handleDirty) prepareBuffer(); | 
| 244 |         dev->write(str, strlen(str)); | 
| 245 |         return *this; | 
| 246 |     } | 
| 247 |  | 
| 248 |     ByteStream &ByteStream::operator <<(const QByteArray &str) | 
| 249 |     { | 
| 250 |         if (handleDirty) prepareBuffer(); | 
| 251 |         dev->write(str); | 
| 252 |         return *this; | 
| 253 |     } | 
| 254 |  | 
| 255 |     ByteStream &ByteStream::operator <<(const ByteStream &src) | 
| 256 |     { | 
| 257 |         Q_ASSERT(!src.dev->isSequential()); | 
| 258 |         if (handleDirty) prepareBuffer(); | 
| 259 |         // We do play nice here, even though it looks ugly. | 
| 260 |         // We save the position and restore it afterwards. | 
| 261 |         ByteStream &s = const_cast<ByteStream&>(src); | 
| 262 |         qint64 pos = s.dev->pos(); | 
| 263 |         s.dev->reset(); | 
| 264 |         while (!s.dev->atEnd()) { | 
| 265 |             QByteArray buf = s.dev->read(chunkSize()); | 
| 266 |             dev->write(buf); | 
| 267 |         } | 
| 268 |         s.dev->seek(pos); | 
| 269 |         return *this; | 
| 270 |     } | 
| 271 |  | 
| 272 |     ByteStream &ByteStream::operator <<(qreal val) { | 
| 273 |         char buf[256]; | 
| 274 |         qt_real_to_string(val, buf); | 
| 275 |         *this << buf; | 
| 276 |         return *this; | 
| 277 |     } | 
| 278 |  | 
| 279 |     ByteStream &ByteStream::operator <<(int val) { | 
| 280 |         char buf[256]; | 
| 281 |         qt_int_to_string(val, buf); | 
| 282 |         *this << buf; | 
| 283 |         return *this; | 
| 284 |     } | 
| 285 |  | 
| 286 |     ByteStream &ByteStream::operator <<(const QPointF &p) { | 
| 287 |         char buf[256]; | 
| 288 |         qt_real_to_string(p.x(), buf); | 
| 289 |         *this << buf; | 
| 290 |         qt_real_to_string(p.y(), buf); | 
| 291 |         *this << buf; | 
| 292 |         return *this; | 
| 293 |     } | 
| 294 |  | 
| 295 |     QIODevice *ByteStream::stream() | 
| 296 |     { | 
| 297 |         dev->reset(); | 
| 298 |         handleDirty = true; | 
| 299 |         return dev; | 
| 300 |     } | 
| 301 |  | 
| 302 |     void ByteStream::clear() | 
| 303 |     { | 
| 304 |         dev->open(QIODevice::ReadWrite | QIODevice::Truncate); | 
| 305 |     } | 
| 306 |  | 
| 307 |     void ByteStream::constructor_helper(QByteArray *ba) | 
| 308 |     { | 
| 309 |         delete dev; | 
| 310 |         dev = new QBuffer(ba); | 
| 311 |         dev->open(QIODevice::ReadWrite); | 
| 312 |     } | 
| 313 |  | 
| 314 |     void ByteStream::prepareBuffer() | 
| 315 |     { | 
| 316 |         Q_ASSERT(!dev->isSequential()); | 
| 317 |         qint64 size = dev->size(); | 
| 318 |         if (fileBackingEnabled && !fileBackingActive | 
| 319 |                 && size > maxMemorySize()) { | 
| 320 |             // Switch to file backing. | 
| 321 |             QTemporaryFile *newFile = new QTemporaryFile; | 
| 322 |             newFile->open(); | 
| 323 |             dev->reset(); | 
| 324 |             while (!dev->atEnd()) { | 
| 325 |                 QByteArray buf = dev->read(chunkSize()); | 
| 326 |                 newFile->write(buf); | 
| 327 |             } | 
| 328 |             delete dev; | 
| 329 |             dev = newFile; | 
| 330 |             ba.clear(); | 
| 331 |             fileBackingActive = true; | 
| 332 |         } | 
| 333 |         if (dev->pos() != size) { | 
| 334 |             dev->seek(size); | 
| 335 |             handleDirty = false; | 
| 336 |         } | 
| 337 |     } | 
| 338 | } | 
| 339 |  | 
| 340 | #define QT_PATH_ELEMENT(elm) | 
| 341 |  | 
| 342 | QByteArray QPdf::generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags) | 
| 343 | { | 
| 344 |     QByteArray result; | 
| 345 |     if (!path.elementCount()) | 
| 346 |         return result; | 
| 347 |  | 
| 348 |     ByteStream s(&result); | 
| 349 |  | 
| 350 |     int start = -1; | 
| 351 |     for (int i = 0; i < path.elementCount(); ++i) { | 
| 352 |         const QPainterPath::Element &elm = path.elementAt(i); | 
| 353 |         switch (elm.type) { | 
| 354 |         case QPainterPath::MoveToElement: | 
| 355 |             if (start >= 0 | 
| 356 |                 && path.elementAt(start).x == path.elementAt(i-1).x | 
| 357 |                 && path.elementAt(start).y == path.elementAt(i-1).y) | 
| 358 |                 s << "h\n" ; | 
| 359 |             s << matrix.map(QPointF(elm.x, elm.y)) << "m\n" ; | 
| 360 |             start = i; | 
| 361 |                 break; | 
| 362 |         case QPainterPath::LineToElement: | 
| 363 |             s << matrix.map(QPointF(elm.x, elm.y)) << "l\n" ; | 
| 364 |             break; | 
| 365 |         case QPainterPath::CurveToElement: | 
| 366 |             Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement); | 
| 367 |             Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement); | 
| 368 |             s << matrix.map(QPointF(elm.x, elm.y)) | 
| 369 |               << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y)) | 
| 370 |               << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y)) | 
| 371 |               << "c\n" ; | 
| 372 |             i += 2; | 
| 373 |             break; | 
| 374 |         default: | 
| 375 |             qFatal("QPdf::generatePath(), unhandled type: %d" , elm.type); | 
| 376 |         } | 
| 377 |     } | 
| 378 |     if (start >= 0 | 
| 379 |         && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x | 
| 380 |         && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y) | 
| 381 |         s << "h\n" ; | 
| 382 |  | 
| 383 |     Qt::FillRule fillRule = path.fillRule(); | 
| 384 |  | 
| 385 |     const char *op = "" ; | 
| 386 |     switch (flags) { | 
| 387 |     case ClipPath: | 
| 388 |         op = (fillRule == Qt::WindingFill) ? "W n\n"  : "W* n\n" ; | 
| 389 |         break; | 
| 390 |     case FillPath: | 
| 391 |         op = (fillRule == Qt::WindingFill) ? "f\n"  : "f*\n" ; | 
| 392 |         break; | 
| 393 |     case StrokePath: | 
| 394 |         op = "S\n" ; | 
| 395 |         break; | 
| 396 |     case FillAndStrokePath: | 
| 397 |         op = (fillRule == Qt::WindingFill) ? "B\n"  : "B*\n" ; | 
| 398 |         break; | 
| 399 |     } | 
| 400 |     s << op; | 
| 401 |     return result; | 
| 402 | } | 
| 403 |  | 
| 404 | QByteArray QPdf::generateMatrix(const QTransform &matrix) | 
| 405 | { | 
| 406 |     QByteArray result; | 
| 407 |     ByteStream s(&result); | 
| 408 |     s << matrix.m11() | 
| 409 |       << matrix.m12() | 
| 410 |       << matrix.m21() | 
| 411 |       << matrix.m22() | 
| 412 |       << matrix.dx() | 
| 413 |       << matrix.dy() | 
| 414 |       << "cm\n" ; | 
| 415 |     return result; | 
| 416 | } | 
| 417 |  | 
| 418 | QByteArray QPdf::generateDashes(const QPen &pen) | 
| 419 | { | 
| 420 |     QByteArray result; | 
| 421 |     ByteStream s(&result); | 
| 422 |     s << '['; | 
| 423 |  | 
| 424 |     QList<qreal> dasharray = pen.dashPattern(); | 
| 425 |     qreal w = pen.widthF(); | 
| 426 |     if (w < 0.001) | 
| 427 |         w = 1; | 
| 428 |     for (int i = 0; i < dasharray.size(); ++i) { | 
| 429 |         qreal dw = dasharray.at(i)*w; | 
| 430 |         if (dw < 0.0001) dw = 0.0001; | 
| 431 |         s << dw; | 
| 432 |     } | 
| 433 |     s << ']'; | 
| 434 |     s << pen.dashOffset() * w; | 
| 435 |     s << " d\n" ; | 
| 436 |     return result; | 
| 437 | } | 
| 438 |  | 
| 439 |  | 
| 440 |  | 
| 441 | static const char* const pattern_for_brush[] = { | 
| 442 |     nullptr, // NoBrush | 
| 443 |     nullptr, // SolidPattern | 
| 444 |     "0 J\n"  | 
| 445 |     "6 w\n"  | 
| 446 |     "[] 0 d\n"  | 
| 447 |     "4 0 m\n"  | 
| 448 |     "4 8 l\n"  | 
| 449 |     "0 4 m\n"  | 
| 450 |     "8 4 l\n"  | 
| 451 |     "S\n" , // Dense1Pattern | 
| 452 |  | 
| 453 |     "0 J\n"  | 
| 454 |     "2 w\n"  | 
| 455 |     "[6 2] 1 d\n"  | 
| 456 |     "0 0 m\n"  | 
| 457 |     "0 8 l\n"  | 
| 458 |     "8 0 m\n"  | 
| 459 |     "8 8 l\n"  | 
| 460 |     "S\n"  | 
| 461 |     "[] 0 d\n"  | 
| 462 |     "2 0 m\n"  | 
| 463 |     "2 8 l\n"  | 
| 464 |     "6 0 m\n"  | 
| 465 |     "6 8 l\n"  | 
| 466 |     "S\n"  | 
| 467 |     "[6 2] -3 d\n"  | 
| 468 |     "4 0 m\n"  | 
| 469 |     "4 8 l\n"  | 
| 470 |     "S\n" , // Dense2Pattern | 
| 471 |  | 
| 472 |     "0 J\n"  | 
| 473 |     "2 w\n"  | 
| 474 |     "[6 2] 1 d\n"  | 
| 475 |     "0 0 m\n"  | 
| 476 |     "0 8 l\n"  | 
| 477 |     "8 0 m\n"  | 
| 478 |     "8 8 l\n"  | 
| 479 |     "S\n"  | 
| 480 |     "[2 2] -1 d\n"  | 
| 481 |     "2 0 m\n"  | 
| 482 |     "2 8 l\n"  | 
| 483 |     "6 0 m\n"  | 
| 484 |     "6 8 l\n"  | 
| 485 |     "S\n"  | 
| 486 |     "[6 2] -3 d\n"  | 
| 487 |     "4 0 m\n"  | 
| 488 |     "4 8 l\n"  | 
| 489 |     "S\n" , // Dense3Pattern | 
| 490 |  | 
| 491 |     "0 J\n"  | 
| 492 |     "2 w\n"  | 
| 493 |     "[2 2] 1 d\n"  | 
| 494 |     "0 0 m\n"  | 
| 495 |     "0 8 l\n"  | 
| 496 |     "8 0 m\n"  | 
| 497 |     "8 8 l\n"  | 
| 498 |     "S\n"  | 
| 499 |     "[2 2] -1 d\n"  | 
| 500 |     "2 0 m\n"  | 
| 501 |     "2 8 l\n"  | 
| 502 |     "6 0 m\n"  | 
| 503 |     "6 8 l\n"  | 
| 504 |     "S\n"  | 
| 505 |     "[2 2] 1 d\n"  | 
| 506 |     "4 0 m\n"  | 
| 507 |     "4 8 l\n"  | 
| 508 |     "S\n" , // Dense4Pattern | 
| 509 |  | 
| 510 |     "0 J\n"  | 
| 511 |     "2 w\n"  | 
| 512 |     "[2 6] -1 d\n"  | 
| 513 |     "0 0 m\n"  | 
| 514 |     "0 8 l\n"  | 
| 515 |     "8 0 m\n"  | 
| 516 |     "8 8 l\n"  | 
| 517 |     "S\n"  | 
| 518 |     "[2 2] 1 d\n"  | 
| 519 |     "2 0 m\n"  | 
| 520 |     "2 8 l\n"  | 
| 521 |     "6 0 m\n"  | 
| 522 |     "6 8 l\n"  | 
| 523 |     "S\n"  | 
| 524 |     "[2 6] 3 d\n"  | 
| 525 |     "4 0 m\n"  | 
| 526 |     "4 8 l\n"  | 
| 527 |     "S\n" , // Dense5Pattern | 
| 528 |  | 
| 529 |     "0 J\n"  | 
| 530 |     "2 w\n"  | 
| 531 |     "[2 6] -1 d\n"  | 
| 532 |     "0 0 m\n"  | 
| 533 |     "0 8 l\n"  | 
| 534 |     "8 0 m\n"  | 
| 535 |     "8 8 l\n"  | 
| 536 |     "S\n"  | 
| 537 |     "[2 6] 3 d\n"  | 
| 538 |     "4 0 m\n"  | 
| 539 |     "4 8 l\n"  | 
| 540 |     "S\n" , // Dense6Pattern | 
| 541 |  | 
| 542 |     "0 J\n"  | 
| 543 |     "2 w\n"  | 
| 544 |     "[2 6] -1 d\n"  | 
| 545 |     "0 0 m\n"  | 
| 546 |     "0 8 l\n"  | 
| 547 |     "8 0 m\n"  | 
| 548 |     "8 8 l\n"  | 
| 549 |     "S\n" , // Dense7Pattern | 
| 550 |  | 
| 551 |     "1 w\n"  | 
| 552 |     "0 4 m\n"  | 
| 553 |     "8 4 l\n"  | 
| 554 |     "S\n" , // HorPattern | 
| 555 |  | 
| 556 |     "1 w\n"  | 
| 557 |     "4 0 m\n"  | 
| 558 |     "4 8 l\n"  | 
| 559 |     "S\n" , // VerPattern | 
| 560 |  | 
| 561 |     "1 w\n"  | 
| 562 |     "4 0 m\n"  | 
| 563 |     "4 8 l\n"  | 
| 564 |     "0 4 m\n"  | 
| 565 |     "8 4 l\n"  | 
| 566 |     "S\n" , // CrossPattern | 
| 567 |  | 
| 568 |     "1 w\n"  | 
| 569 |     "-1 5 m\n"  | 
| 570 |     "5 -1 l\n"  | 
| 571 |     "3 9 m\n"  | 
| 572 |     "9 3 l\n"  | 
| 573 |     "S\n" , // BDiagPattern | 
| 574 |  | 
| 575 |     "1 w\n"  | 
| 576 |     "-1 3 m\n"  | 
| 577 |     "5 9 l\n"  | 
| 578 |     "3 -1 m\n"  | 
| 579 |     "9 5 l\n"  | 
| 580 |     "S\n" , // FDiagPattern | 
| 581 |  | 
| 582 |     "1 w\n"  | 
| 583 |     "-1 3 m\n"  | 
| 584 |     "5 9 l\n"  | 
| 585 |     "3 -1 m\n"  | 
| 586 |     "9 5 l\n"  | 
| 587 |     "-1 5 m\n"  | 
| 588 |     "5 -1 l\n"  | 
| 589 |     "3 9 m\n"  | 
| 590 |     "9 3 l\n"  | 
| 591 |     "S\n" , // DiagCrossPattern | 
| 592 | }; | 
| 593 |  | 
| 594 | QByteArray QPdf::patternForBrush(const QBrush &b) | 
| 595 | { | 
| 596 |     int style = b.style(); | 
| 597 |     if (style > Qt::DiagCrossPattern) | 
| 598 |         return QByteArray(); | 
| 599 |     return pattern_for_brush[style]; | 
| 600 | } | 
| 601 |  | 
| 602 |  | 
| 603 | static void moveToHook(qfixed x, qfixed y, void *data) | 
| 604 | { | 
| 605 |     QPdf::Stroker *t = (QPdf::Stroker *)data; | 
| 606 |     if (!t->first) | 
| 607 |         *t->stream << "h\n" ; | 
| 608 |     if (!t->cosmeticPen) | 
| 609 |         t->matrix.map(x, y, &x, &y); | 
| 610 |     *t->stream << x << y << "m\n" ; | 
| 611 |     t->first = false; | 
| 612 | } | 
| 613 |  | 
| 614 | static void lineToHook(qfixed x, qfixed y, void *data) | 
| 615 | { | 
| 616 |     QPdf::Stroker *t = (QPdf::Stroker *)data; | 
| 617 |     if (!t->cosmeticPen) | 
| 618 |         t->matrix.map(x, y, &x, &y); | 
| 619 |     *t->stream << x << y << "l\n" ; | 
| 620 | } | 
| 621 |  | 
| 622 | static void cubicToHook(qfixed c1x, qfixed c1y, | 
| 623 |                         qfixed c2x, qfixed c2y, | 
| 624 |                         qfixed ex, qfixed ey, | 
| 625 |                         void *data) | 
| 626 | { | 
| 627 |     QPdf::Stroker *t = (QPdf::Stroker *)data; | 
| 628 |     if (!t->cosmeticPen) { | 
| 629 |         t->matrix.map(c1x, c1y, &c1x, &c1y); | 
| 630 |         t->matrix.map(c2x, c2y, &c2x, &c2y); | 
| 631 |         t->matrix.map(ex, ey, &ex, &ey); | 
| 632 |     } | 
| 633 |     *t->stream << c1x << c1y | 
| 634 |                << c2x << c2y | 
| 635 |                << ex << ey | 
| 636 |                << "c\n" ; | 
| 637 | } | 
| 638 |  | 
| 639 | QPdf::Stroker::Stroker() | 
| 640 |     : stream(nullptr), | 
| 641 |     first(true), | 
| 642 |     dashStroker(&basicStroker) | 
| 643 | { | 
| 644 |     stroker = &basicStroker; | 
| 645 |     basicStroker.setMoveToHook(moveToHook); | 
| 646 |     basicStroker.setLineToHook(lineToHook); | 
| 647 |     basicStroker.setCubicToHook(cubicToHook); | 
| 648 |     cosmeticPen = true; | 
| 649 |     basicStroker.setStrokeWidth(.1); | 
| 650 | } | 
| 651 |  | 
| 652 | void QPdf::Stroker::setPen(const QPen &pen, QPainter::RenderHints hints) | 
| 653 | { | 
| 654 |     if (pen.style() == Qt::NoPen) { | 
| 655 |         stroker = nullptr; | 
| 656 |         return; | 
| 657 |     } | 
| 658 |     qreal w = pen.widthF(); | 
| 659 |     bool zeroWidth = w < 0.0001; | 
| 660 |     cosmeticPen = qt_pen_is_cosmetic(pen, hints); | 
| 661 |     if (zeroWidth) | 
| 662 |         w = .1; | 
| 663 |  | 
| 664 |     basicStroker.setStrokeWidth(w); | 
| 665 |     basicStroker.setCapStyle(pen.capStyle()); | 
| 666 |     basicStroker.setJoinStyle(pen.joinStyle()); | 
| 667 |     basicStroker.setMiterLimit(pen.miterLimit()); | 
| 668 |  | 
| 669 |     QList<qreal> dashpattern = pen.dashPattern(); | 
| 670 |     if (zeroWidth) { | 
| 671 |         for (int i = 0; i < dashpattern.size(); ++i) | 
| 672 |             dashpattern[i] *= 10.; | 
| 673 |     } | 
| 674 |     if (!dashpattern.isEmpty()) { | 
| 675 |         dashStroker.setDashPattern(dashpattern); | 
| 676 |         dashStroker.setDashOffset(pen.dashOffset()); | 
| 677 |         stroker = &dashStroker; | 
| 678 |     } else { | 
| 679 |         stroker = &basicStroker; | 
| 680 |     } | 
| 681 | } | 
| 682 |  | 
| 683 | void QPdf::Stroker::strokePath(const QPainterPath &path) | 
| 684 | { | 
| 685 |     if (!stroker) | 
| 686 |         return; | 
| 687 |     first = true; | 
| 688 |  | 
| 689 |     stroker->strokePath(path, this, cosmeticPen ? matrix : QTransform()); | 
| 690 |     *stream << "h f\n" ; | 
| 691 | } | 
| 692 |  | 
| 693 | QByteArray QPdf::ascii85Encode(const QByteArray &input) | 
| 694 | { | 
| 695 |     int isize = input.size()/4*4; | 
| 696 |     QByteArray output; | 
| 697 |     output.resize(input.size()*5/4+7); | 
| 698 |     char *out = output.data(); | 
| 699 |     const uchar *in = (const uchar *)input.constData(); | 
| 700 |     for (int i = 0; i < isize; i += 4) { | 
| 701 |         uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3]; | 
| 702 |         if (val == 0) { | 
| 703 |             *out = 'z'; | 
| 704 |             ++out; | 
| 705 |         } else { | 
| 706 |             char base[5]; | 
| 707 |             base[4] = val % 85; | 
| 708 |             val /= 85; | 
| 709 |             base[3] = val % 85; | 
| 710 |             val /= 85; | 
| 711 |             base[2] = val % 85; | 
| 712 |             val /= 85; | 
| 713 |             base[1] = val % 85; | 
| 714 |             val /= 85; | 
| 715 |             base[0] = val % 85; | 
| 716 |             *(out++) = base[0] + '!'; | 
| 717 |             *(out++) = base[1] + '!'; | 
| 718 |             *(out++) = base[2] + '!'; | 
| 719 |             *(out++) = base[3] + '!'; | 
| 720 |             *(out++) = base[4] + '!'; | 
| 721 |         } | 
| 722 |     } | 
| 723 |     //write the last few bytes | 
| 724 |     int remaining = input.size() - isize; | 
| 725 |     if (remaining) { | 
| 726 |         uint val = 0; | 
| 727 |         for (int i = isize; i < input.size(); ++i) | 
| 728 |             val = (val << 8) + in[i]; | 
| 729 |         val <<= 8*(4-remaining); | 
| 730 |         char base[5]; | 
| 731 |         base[4] = val % 85; | 
| 732 |         val /= 85; | 
| 733 |         base[3] = val % 85; | 
| 734 |         val /= 85; | 
| 735 |         base[2] = val % 85; | 
| 736 |         val /= 85; | 
| 737 |         base[1] = val % 85; | 
| 738 |         val /= 85; | 
| 739 |         base[0] = val % 85; | 
| 740 |         for (int i = 0; i < remaining+1; ++i) | 
| 741 |             *(out++) = base[i] + '!'; | 
| 742 |     } | 
| 743 |     *(out++) = '~'; | 
| 744 |     *(out++) = '>'; | 
| 745 |     output.resize(out-output.data()); | 
| 746 |     return output; | 
| 747 | } | 
| 748 |  | 
| 749 | const char *QPdf::toHex(ushort u, char *buffer) | 
| 750 | { | 
| 751 |     int i = 3; | 
| 752 |     while (i >= 0) { | 
| 753 |         ushort hex = (u & 0x000f); | 
| 754 |         if (hex < 0x0a) | 
| 755 |             buffer[i] = '0'+hex; | 
| 756 |         else | 
| 757 |             buffer[i] = 'A'+(hex-0x0a); | 
| 758 |         u = u >> 4; | 
| 759 |         i--; | 
| 760 |     } | 
| 761 |     buffer[4] = '\0'; | 
| 762 |     return buffer; | 
| 763 | } | 
| 764 |  | 
| 765 | const char *QPdf::toHex(uchar u, char *buffer) | 
| 766 | { | 
| 767 |     int i = 1; | 
| 768 |     while (i >= 0) { | 
| 769 |         ushort hex = (u & 0x000f); | 
| 770 |         if (hex < 0x0a) | 
| 771 |             buffer[i] = '0'+hex; | 
| 772 |         else | 
| 773 |             buffer[i] = 'A'+(hex-0x0a); | 
| 774 |         u = u >> 4; | 
| 775 |         i--; | 
| 776 |     } | 
| 777 |     buffer[2] = '\0'; | 
| 778 |     return buffer; | 
| 779 | } | 
| 780 |  | 
| 781 |  | 
| 782 | QPdfPage::QPdfPage() | 
| 783 |     : QPdf::ByteStream(true) // Enable file backing | 
| 784 | { | 
| 785 | } | 
| 786 |  | 
| 787 | void QPdfPage::streamImage(int w, int h, uint object) | 
| 788 | { | 
| 789 |     *this << w << "0 0 "  << -h << "0 "  << h << "cm /Im"  << object << " Do\n" ; | 
| 790 |     if (!images.contains(object)) | 
| 791 |         images.append(object); | 
| 792 | } | 
| 793 |  | 
| 794 |  | 
| 795 | QPdfEngine::QPdfEngine(QPdfEnginePrivate &dd) | 
| 796 |     : QPaintEngine(dd, qt_pdf_decide_features()) | 
| 797 | { | 
| 798 | } | 
| 799 |  | 
| 800 | QPdfEngine::QPdfEngine() | 
| 801 |     : QPaintEngine(*new QPdfEnginePrivate(), qt_pdf_decide_features()) | 
| 802 | { | 
| 803 | } | 
| 804 |  | 
| 805 | void QPdfEngine::setOutputFilename(const QString &filename) | 
| 806 | { | 
| 807 |     Q_D(QPdfEngine); | 
| 808 |     d->outputFileName = filename; | 
| 809 | } | 
| 810 |  | 
| 811 |  | 
| 812 | void QPdfEngine::drawPoints (const QPointF *points, int pointCount) | 
| 813 | { | 
| 814 |     if (!points) | 
| 815 |         return; | 
| 816 |  | 
| 817 |     Q_D(QPdfEngine); | 
| 818 |     QPainterPath p; | 
| 819 |     for (int i=0; i!=pointCount;++i) { | 
| 820 |         p.moveTo(points[i]); | 
| 821 |         p.lineTo(points[i] + QPointF(0, 0.001)); | 
| 822 |     } | 
| 823 |  | 
| 824 |     bool hadBrush = d->hasBrush; | 
| 825 |     d->hasBrush = false; | 
| 826 |     drawPath(p); | 
| 827 |     d->hasBrush = hadBrush; | 
| 828 | } | 
| 829 |  | 
| 830 | void QPdfEngine::drawLines (const QLineF *lines, int lineCount) | 
| 831 | { | 
| 832 |     if (!lines) | 
| 833 |         return; | 
| 834 |  | 
| 835 |     Q_D(QPdfEngine); | 
| 836 |     QPainterPath p; | 
| 837 |     for (int i=0; i!=lineCount;++i) { | 
| 838 |         p.moveTo(lines[i].p1()); | 
| 839 |         p.lineTo(lines[i].p2()); | 
| 840 |     } | 
| 841 |     bool hadBrush = d->hasBrush; | 
| 842 |     d->hasBrush = false; | 
| 843 |     drawPath(p); | 
| 844 |     d->hasBrush = hadBrush; | 
| 845 | } | 
| 846 |  | 
| 847 | void QPdfEngine::drawRects (const QRectF *rects, int rectCount) | 
| 848 | { | 
| 849 |     if (!rects) | 
| 850 |         return; | 
| 851 |  | 
| 852 |     Q_D(QPdfEngine); | 
| 853 |  | 
| 854 |     if (d->clipEnabled && d->allClipped) | 
| 855 |         return; | 
| 856 |     if (!d->hasPen && !d->hasBrush) | 
| 857 |         return; | 
| 858 |  | 
| 859 |     if (d->simplePen || !d->hasPen) { | 
| 860 |         // draw strokes natively in this case for better output | 
| 861 |         if(!d->simplePen && !d->stroker.matrix.isIdentity()) | 
| 862 |             *d->currentPage << "q\n"  << QPdf::generateMatrix(d->stroker.matrix); | 
| 863 |         for (int i = 0; i < rectCount; ++i) | 
| 864 |             *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n" ; | 
| 865 |         *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n"  : "S\n" ) : "f\n" ); | 
| 866 |         if(!d->simplePen && !d->stroker.matrix.isIdentity()) | 
| 867 |             *d->currentPage << "Q\n" ; | 
| 868 |     } else { | 
| 869 |         QPainterPath p; | 
| 870 |         for (int i=0; i!=rectCount; ++i) | 
| 871 |             p.addRect(rects[i]); | 
| 872 |         drawPath(p); | 
| 873 |     } | 
| 874 | } | 
| 875 |  | 
| 876 | void QPdfEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) | 
| 877 | { | 
| 878 |     Q_D(QPdfEngine); | 
| 879 |  | 
| 880 |     if (!points || !pointCount) | 
| 881 |         return; | 
| 882 |  | 
| 883 |     bool hb = d->hasBrush; | 
| 884 |     QPainterPath p; | 
| 885 |  | 
| 886 |     switch(mode) { | 
| 887 |         case OddEvenMode: | 
| 888 |             p.setFillRule(Qt::OddEvenFill); | 
| 889 |             break; | 
| 890 |         case ConvexMode: | 
| 891 |         case WindingMode: | 
| 892 |             p.setFillRule(Qt::WindingFill); | 
| 893 |             break; | 
| 894 |         case PolylineMode: | 
| 895 |             d->hasBrush = false; | 
| 896 |             break; | 
| 897 |         default: | 
| 898 |             break; | 
| 899 |     } | 
| 900 |  | 
| 901 |     p.moveTo(points[0]); | 
| 902 |     for (int i = 1; i < pointCount; ++i) | 
| 903 |         p.lineTo(points[i]); | 
| 904 |  | 
| 905 |     if (mode != PolylineMode) | 
| 906 |         p.closeSubpath(); | 
| 907 |     drawPath(p); | 
| 908 |  | 
| 909 |     d->hasBrush = hb; | 
| 910 | } | 
| 911 |  | 
| 912 | void QPdfEngine::drawPath (const QPainterPath &p) | 
| 913 | { | 
| 914 |     Q_D(QPdfEngine); | 
| 915 |  | 
| 916 |     if (d->clipEnabled && d->allClipped) | 
| 917 |         return; | 
| 918 |     if (!d->hasPen && !d->hasBrush) | 
| 919 |         return; | 
| 920 |  | 
| 921 |     if (d->simplePen) { | 
| 922 |         // draw strokes natively in this case for better output | 
| 923 |         *d->currentPage << QPdf::generatePath(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath); | 
| 924 |     } else { | 
| 925 |         if (d->hasBrush) | 
| 926 |             *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath); | 
| 927 |         if (d->hasPen) { | 
| 928 |             *d->currentPage << "q\n" ; | 
| 929 |             QBrush b = d->brush; | 
| 930 |             d->brush = d->pen.brush(); | 
| 931 |             setBrush(); | 
| 932 |             d->stroker.strokePath(p); | 
| 933 |             *d->currentPage << "Q\n" ; | 
| 934 |             d->brush = b; | 
| 935 |         } | 
| 936 |     } | 
| 937 | } | 
| 938 |  | 
| 939 | void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr) | 
| 940 | { | 
| 941 |     if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull()) | 
| 942 |         return; | 
| 943 |     Q_D(QPdfEngine); | 
| 944 |  | 
| 945 |     QBrush b = d->brush; | 
| 946 |  | 
| 947 |     QRect sourceRect = sr.toRect(); | 
| 948 |     QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap; | 
| 949 |     QImage image = pm.toImage(); | 
| 950 |     bool bitmap = true; | 
| 951 |     const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering); | 
| 952 |     const int object = d->addImage(image, &bitmap, lossless, pm.cacheKey()); | 
| 953 |     if (object < 0) | 
| 954 |         return; | 
| 955 |  | 
| 956 |     *d->currentPage << "q\n" ; | 
| 957 |  | 
| 958 |     if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) { | 
| 959 |         int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity)); | 
| 960 |         if (stateObject) | 
| 961 |             *d->currentPage << "/GState"  << stateObject << "gs\n" ; | 
| 962 |         else | 
| 963 |             *d->currentPage << "/GSa gs\n" ; | 
| 964 |     } else { | 
| 965 |         *d->currentPage << "/GSa gs\n" ; | 
| 966 |     } | 
| 967 |  | 
| 968 |     *d->currentPage | 
| 969 |         << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), | 
| 970 |                                            rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); | 
| 971 |     if (bitmap) { | 
| 972 |         // set current pen as d->brush | 
| 973 |         d->brush = d->pen.brush(); | 
| 974 |     } | 
| 975 |     setBrush(); | 
| 976 |     d->currentPage->streamImage(image.width(), image.height(), object); | 
| 977 |     *d->currentPage << "Q\n" ; | 
| 978 |  | 
| 979 |     d->brush = b; | 
| 980 | } | 
| 981 |  | 
| 982 | void QPdfEngine::drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags) | 
| 983 | { | 
| 984 |     if (sr.isEmpty() || rectangle.isEmpty() || image.isNull()) | 
| 985 |         return; | 
| 986 |     Q_D(QPdfEngine); | 
| 987 |  | 
| 988 |     QRect sourceRect = sr.toRect(); | 
| 989 |     QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image; | 
| 990 |     bool bitmap = true; | 
| 991 |     const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering); | 
| 992 |     const int object = d->addImage(im, &bitmap, lossless, im.cacheKey()); | 
| 993 |     if (object < 0) | 
| 994 |         return; | 
| 995 |  | 
| 996 |     *d->currentPage << "q\n" ; | 
| 997 |  | 
| 998 |     if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) { | 
| 999 |         int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity)); | 
| 1000 |         if (stateObject) | 
| 1001 |             *d->currentPage << "/GState"  << stateObject << "gs\n" ; | 
| 1002 |         else | 
| 1003 |             *d->currentPage << "/GSa gs\n" ; | 
| 1004 |     } else { | 
| 1005 |         *d->currentPage << "/GSa gs\n" ; | 
| 1006 |     } | 
| 1007 |  | 
| 1008 |     *d->currentPage | 
| 1009 |         << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), | 
| 1010 |                                            rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); | 
| 1011 |     setBrush(); | 
| 1012 |     d->currentPage->streamImage(im.width(), im.height(), object); | 
| 1013 |     *d->currentPage << "Q\n" ; | 
| 1014 | } | 
| 1015 |  | 
| 1016 | void QPdfEngine::drawTiledPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QPointF &point) | 
| 1017 | { | 
| 1018 |     Q_D(QPdfEngine); | 
| 1019 |  | 
| 1020 |     bool bitmap = (pixmap.depth() == 1); | 
| 1021 |     QBrush b = d->brush; | 
| 1022 |     QPointF bo = d->brushOrigin; | 
| 1023 |     bool hp = d->hasPen; | 
| 1024 |     d->hasPen = false; | 
| 1025 |     bool hb = d->hasBrush; | 
| 1026 |     d->hasBrush = true; | 
| 1027 |  | 
| 1028 |     d->brush = QBrush(pixmap); | 
| 1029 |     if (bitmap) | 
| 1030 |         // #### fix bitmap case where we have a brush pen | 
| 1031 |         d->brush.setColor(d->pen.color()); | 
| 1032 |  | 
| 1033 |     d->brushOrigin = -point; | 
| 1034 |     *d->currentPage << "q\n" ; | 
| 1035 |     setBrush(); | 
| 1036 |  | 
| 1037 |     drawRects(&rectangle, 1); | 
| 1038 |     *d->currentPage << "Q\n" ; | 
| 1039 |  | 
| 1040 |     d->hasPen = hp; | 
| 1041 |     d->hasBrush = hb; | 
| 1042 |     d->brush = b; | 
| 1043 |     d->brushOrigin = bo; | 
| 1044 | } | 
| 1045 |  | 
| 1046 | void QPdfEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) | 
| 1047 | { | 
| 1048 |     Q_D(QPdfEngine); | 
| 1049 |  | 
| 1050 |     if (!d->hasPen || (d->clipEnabled && d->allClipped)) | 
| 1051 |         return; | 
| 1052 |  | 
| 1053 |     if (d->stroker.matrix.type() >= QTransform::TxProject) { | 
| 1054 |         QPaintEngine::drawTextItem(p, textItem); | 
| 1055 |         return; | 
| 1056 |     } | 
| 1057 |  | 
| 1058 |     *d->currentPage << "q\n" ; | 
| 1059 |     if(!d->simplePen) | 
| 1060 |         *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); | 
| 1061 |  | 
| 1062 |     bool hp = d->hasPen; | 
| 1063 |     d->hasPen = false; | 
| 1064 |     QBrush b = d->brush; | 
| 1065 |     d->brush = d->pen.brush(); | 
| 1066 |     setBrush(); | 
| 1067 |  | 
| 1068 |     const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); | 
| 1069 |     Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi); | 
| 1070 |     d->drawTextItem(p, ti); | 
| 1071 |     d->hasPen = hp; | 
| 1072 |     d->brush = b; | 
| 1073 |     *d->currentPage << "Q\n" ; | 
| 1074 | } | 
| 1075 |  | 
| 1076 | void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url) | 
| 1077 | { | 
| 1078 |     Q_D(QPdfEngine); | 
| 1079 |  | 
| 1080 |     const uint annot = d->addXrefEntry(-1); | 
| 1081 |     const QByteArray urlascii = url.toEncoded(); | 
| 1082 |     int len = urlascii.size(); | 
| 1083 |     QVarLengthArray<char> url_esc; | 
| 1084 |     url_esc.reserve(len + 1); | 
| 1085 |     for (int j = 0; j < len; j++) { | 
| 1086 |         if (urlascii[j] == '(' || urlascii[j] == ')' || urlascii[j] == '\\') | 
| 1087 |             url_esc.append('\\'); | 
| 1088 |         url_esc.append(urlascii[j]); | 
| 1089 |     } | 
| 1090 |     url_esc.append('\0'); | 
| 1091 |  | 
| 1092 |     char buf[256]; | 
| 1093 |     const QRectF rr = d->pageMatrix().mapRect(r); | 
| 1094 |     d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n" ); | 
| 1095 |  | 
| 1096 |     if (d->pdfVersion == QPdfEngine::Version_A1b) | 
| 1097 |         d->xprintf("/F 4\n" ); // enable print flag, disable all other | 
| 1098 |  | 
| 1099 |     d->xprintf("/Rect [" ); | 
| 1100 |     d->xprintf("%s " , qt_real_to_string(rr.left(), buf)); | 
| 1101 |     d->xprintf("%s " , qt_real_to_string(rr.top(), buf)); | 
| 1102 |     d->xprintf("%s " , qt_real_to_string(rr.right(), buf)); | 
| 1103 |     d->xprintf("%s" , qt_real_to_string(rr.bottom(), buf)); | 
| 1104 |     d->xprintf("]\n/Border [0 0 0]\n/A <<\n" ); | 
| 1105 |     d->xprintf("/Type /Action\n/S /URI\n/URI (%s)\n" , url_esc.constData()); | 
| 1106 |     d->xprintf(">>\n>>\n" ); | 
| 1107 |     d->xprintf("endobj\n" ); | 
| 1108 |     d->currentPage->annotations.append(annot); | 
| 1109 | } | 
| 1110 |  | 
| 1111 | void QPdfEngine::updateState(const QPaintEngineState &state) | 
| 1112 | { | 
| 1113 |     Q_D(QPdfEngine); | 
| 1114 |  | 
| 1115 |     QPaintEngine::DirtyFlags flags = state.state(); | 
| 1116 |  | 
| 1117 |     if (flags & DirtyTransform) | 
| 1118 |         d->stroker.matrix = state.transform(); | 
| 1119 |  | 
| 1120 |     if (flags & DirtyPen) { | 
| 1121 |         if (d->pdfVersion == QPdfEngine::Version_A1b) { | 
| 1122 |             QPen pen = state.pen(); | 
| 1123 |  | 
| 1124 |             QColor penColor = pen.color(); | 
| 1125 |             if (penColor.alpha() != 255) | 
| 1126 |                 penColor.setAlpha(255); | 
| 1127 |             pen.setColor(penColor); | 
| 1128 |  | 
| 1129 |             QBrush penBrush = pen.brush(); | 
| 1130 |             removeTransparencyFromBrush(penBrush); | 
| 1131 |             pen.setBrush(penBrush); | 
| 1132 |  | 
| 1133 |             d->pen = pen; | 
| 1134 |         } else { | 
| 1135 |             d->pen = state.pen(); | 
| 1136 |         } | 
| 1137 |         d->hasPen = d->pen.style() != Qt::NoPen; | 
| 1138 |         d->stroker.setPen(d->pen, state.renderHints()); | 
| 1139 |         QBrush penBrush = d->pen.brush(); | 
| 1140 |         bool cosmeticPen = qt_pen_is_cosmetic(d->pen, state.renderHints()); | 
| 1141 |         bool oldSimple = d->simplePen; | 
| 1142 |         d->simplePen = (d->hasPen && !cosmeticPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque() && d->opacity == 1.0); | 
| 1143 |         if (oldSimple != d->simplePen) | 
| 1144 |             flags |= DirtyTransform; | 
| 1145 |     } else if (flags & DirtyHints) { | 
| 1146 |         d->stroker.setPen(d->pen, state.renderHints()); | 
| 1147 |     } | 
| 1148 |     if (flags & DirtyBrush) { | 
| 1149 |         if (d->pdfVersion == QPdfEngine::Version_A1b) { | 
| 1150 |             QBrush brush = state.brush(); | 
| 1151 |             removeTransparencyFromBrush(brush); | 
| 1152 |             d->brush = brush; | 
| 1153 |         } else { | 
| 1154 |             d->brush = state.brush(); | 
| 1155 |         } | 
| 1156 |         if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern) | 
| 1157 |             d->brush.setStyle(Qt::NoBrush); | 
| 1158 |         d->hasBrush = d->brush.style() != Qt::NoBrush; | 
| 1159 |     } | 
| 1160 |     if (flags & DirtyBrushOrigin) { | 
| 1161 |         d->brushOrigin = state.brushOrigin(); | 
| 1162 |         flags |= DirtyBrush; | 
| 1163 |     } | 
| 1164 |     if (flags & DirtyOpacity) { | 
| 1165 |         d->opacity = state.opacity(); | 
| 1166 |         if (d->simplePen && d->opacity != 1.0) { | 
| 1167 |             d->simplePen = false; | 
| 1168 |             flags |= DirtyTransform; | 
| 1169 |         } | 
| 1170 |     } | 
| 1171 |  | 
| 1172 |     bool ce = d->clipEnabled; | 
| 1173 |     if (flags & DirtyClipPath) { | 
| 1174 |         d->clipEnabled = true; | 
| 1175 |         updateClipPath(state.clipPath(), state.clipOperation()); | 
| 1176 |     } else if (flags & DirtyClipRegion) { | 
| 1177 |         d->clipEnabled = true; | 
| 1178 |         QPainterPath path; | 
| 1179 |         for (const QRect &rect : state.clipRegion()) | 
| 1180 |             path.addRect(rect); | 
| 1181 |         updateClipPath(path, state.clipOperation()); | 
| 1182 |         flags |= DirtyClipPath; | 
| 1183 |     } else if (flags & DirtyClipEnabled) { | 
| 1184 |         d->clipEnabled = state.isClipEnabled(); | 
| 1185 |     } | 
| 1186 |  | 
| 1187 |     if (ce != d->clipEnabled) | 
| 1188 |         flags |= DirtyClipPath; | 
| 1189 |     else if (!d->clipEnabled) | 
| 1190 |         flags &= ~DirtyClipPath; | 
| 1191 |  | 
| 1192 |     setupGraphicsState(flags); | 
| 1193 | } | 
| 1194 |  | 
| 1195 | void QPdfEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags) | 
| 1196 | { | 
| 1197 |     Q_D(QPdfEngine); | 
| 1198 |     if (flags & DirtyClipPath) | 
| 1199 |         flags |= DirtyTransform|DirtyPen|DirtyBrush; | 
| 1200 |  | 
| 1201 |     if (flags & DirtyTransform) { | 
| 1202 |         *d->currentPage << "Q\n" ; | 
| 1203 |         flags |= DirtyPen|DirtyBrush; | 
| 1204 |     } | 
| 1205 |  | 
| 1206 |     if (flags & DirtyClipPath) { | 
| 1207 |         *d->currentPage << "Q q\n" ; | 
| 1208 |  | 
| 1209 |         d->allClipped = false; | 
| 1210 |         if (d->clipEnabled && !d->clips.isEmpty()) { | 
| 1211 |             for (int i = 0; i < d->clips.size(); ++i) { | 
| 1212 |                 if (d->clips.at(i).isEmpty()) { | 
| 1213 |                     d->allClipped = true; | 
| 1214 |                     break; | 
| 1215 |                 } | 
| 1216 |             } | 
| 1217 |             if (!d->allClipped) { | 
| 1218 |                 for (int i = 0; i < d->clips.size(); ++i) { | 
| 1219 |                     *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath); | 
| 1220 |                 } | 
| 1221 |             } | 
| 1222 |         } | 
| 1223 |     } | 
| 1224 |  | 
| 1225 |     if (flags & DirtyTransform) { | 
| 1226 |         *d->currentPage << "q\n" ; | 
| 1227 |         if (d->simplePen && !d->stroker.matrix.isIdentity()) | 
| 1228 |             *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); | 
| 1229 |     } | 
| 1230 |     if (flags & DirtyBrush) | 
| 1231 |         setBrush(); | 
| 1232 |     if (d->simplePen && (flags & DirtyPen)) | 
| 1233 |         setPen(); | 
| 1234 | } | 
| 1235 |  | 
| 1236 | extern QPainterPath qt_regionToPath(const QRegion ®ion); | 
| 1237 |  | 
| 1238 | void QPdfEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op) | 
| 1239 | { | 
| 1240 |     Q_D(QPdfEngine); | 
| 1241 |     QPainterPath path = d->stroker.matrix.map(p); | 
| 1242 |     //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op; | 
| 1243 |  | 
| 1244 |     if (op == Qt::NoClip) { | 
| 1245 |         d->clipEnabled = false; | 
| 1246 |         d->clips.clear(); | 
| 1247 |     } else if (op == Qt::ReplaceClip) { | 
| 1248 |         d->clips.clear(); | 
| 1249 |         d->clips.append(path); | 
| 1250 |     } else if (op == Qt::IntersectClip) { | 
| 1251 |         d->clips.append(path); | 
| 1252 |     } else { // UniteClip | 
| 1253 |         // ask the painter for the current clipping path. that's the easiest solution | 
| 1254 |         path = painter()->clipPath(); | 
| 1255 |         path = d->stroker.matrix.map(path); | 
| 1256 |         d->clips.clear(); | 
| 1257 |         d->clips.append(path); | 
| 1258 |     } | 
| 1259 | } | 
| 1260 |  | 
| 1261 | void QPdfEngine::setPen() | 
| 1262 | { | 
| 1263 |     Q_D(QPdfEngine); | 
| 1264 |     if (d->pen.style() == Qt::NoPen) | 
| 1265 |         return; | 
| 1266 |     QBrush b = d->pen.brush(); | 
| 1267 |     Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque()); | 
| 1268 |  | 
| 1269 |     QColor rgba = b.color(); | 
| 1270 |     if (d->grayscale) { | 
| 1271 |         qreal gray = qGray(rgba.rgba())/255.; | 
| 1272 |         *d->currentPage << gray << gray << gray; | 
| 1273 |     } else { | 
| 1274 |         *d->currentPage << rgba.redF() | 
| 1275 |                         << rgba.greenF() | 
| 1276 |                         << rgba.blueF(); | 
| 1277 |     } | 
| 1278 |     *d->currentPage << "SCN\n" ; | 
| 1279 |  | 
| 1280 |     *d->currentPage << d->pen.widthF() << "w " ; | 
| 1281 |  | 
| 1282 |     int pdfCapStyle = 0; | 
| 1283 |     switch(d->pen.capStyle()) { | 
| 1284 |     case Qt::FlatCap: | 
| 1285 |         pdfCapStyle = 0; | 
| 1286 |         break; | 
| 1287 |     case Qt::SquareCap: | 
| 1288 |         pdfCapStyle = 2; | 
| 1289 |         break; | 
| 1290 |     case Qt::RoundCap: | 
| 1291 |         pdfCapStyle = 1; | 
| 1292 |         break; | 
| 1293 |     default: | 
| 1294 |         break; | 
| 1295 |     } | 
| 1296 |     *d->currentPage << pdfCapStyle << "J " ; | 
| 1297 |  | 
| 1298 |     int pdfJoinStyle = 0; | 
| 1299 |     switch(d->pen.joinStyle()) { | 
| 1300 |     case Qt::MiterJoin: | 
| 1301 |     case Qt::SvgMiterJoin: | 
| 1302 |         *d->currentPage << qMax(qreal(1.0), d->pen.miterLimit()) << "M " ; | 
| 1303 |         pdfJoinStyle = 0; | 
| 1304 |         break; | 
| 1305 |     case Qt::BevelJoin: | 
| 1306 |         pdfJoinStyle = 2; | 
| 1307 |         break; | 
| 1308 |     case Qt::RoundJoin: | 
| 1309 |         pdfJoinStyle = 1; | 
| 1310 |         break; | 
| 1311 |     default: | 
| 1312 |         break; | 
| 1313 |     } | 
| 1314 |     *d->currentPage << pdfJoinStyle << "j " ; | 
| 1315 |  | 
| 1316 |     *d->currentPage << QPdf::generateDashes(d->pen); | 
| 1317 | } | 
| 1318 |  | 
| 1319 |  | 
| 1320 | void QPdfEngine::setBrush() | 
| 1321 | { | 
| 1322 |     Q_D(QPdfEngine); | 
| 1323 |     Qt::BrushStyle style = d->brush.style(); | 
| 1324 |     if (style == Qt::NoBrush) | 
| 1325 |         return; | 
| 1326 |  | 
| 1327 |     bool specifyColor; | 
| 1328 |     int gStateObject = 0; | 
| 1329 |     int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject); | 
| 1330 |     if (!patternObject && !specifyColor) | 
| 1331 |         return; | 
| 1332 |  | 
| 1333 |     *d->currentPage << (patternObject ? "/PCSp cs "  : "/CSp cs " ); | 
| 1334 |     if (specifyColor) { | 
| 1335 |         QColor rgba = d->brush.color(); | 
| 1336 |         if (d->grayscale) { | 
| 1337 |             qreal gray = qGray(rgba.rgba())/255.; | 
| 1338 |             *d->currentPage << gray << gray << gray; | 
| 1339 |         } else { | 
| 1340 |             *d->currentPage << rgba.redF() | 
| 1341 |                             << rgba.greenF() | 
| 1342 |                             << rgba.blueF(); | 
| 1343 |         } | 
| 1344 |     } | 
| 1345 |     if (patternObject) | 
| 1346 |         *d->currentPage << "/Pat"  << patternObject; | 
| 1347 |     *d->currentPage << "scn\n" ; | 
| 1348 |  | 
| 1349 |     if (gStateObject) | 
| 1350 |         *d->currentPage << "/GState"  << gStateObject << "gs\n" ; | 
| 1351 |     else | 
| 1352 |         *d->currentPage << "/GSa gs\n" ; | 
| 1353 | } | 
| 1354 |  | 
| 1355 |  | 
| 1356 | bool QPdfEngine::newPage() | 
| 1357 | { | 
| 1358 |     Q_D(QPdfEngine); | 
| 1359 |     if (!isActive()) | 
| 1360 |         return false; | 
| 1361 |     d->newPage(); | 
| 1362 |  | 
| 1363 |     setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath); | 
| 1364 |     QFile *outfile = qobject_cast<QFile*> (d->outDevice); | 
| 1365 |     if (outfile && outfile->error() != QFile::NoError) | 
| 1366 |         return false; | 
| 1367 |     return true; | 
| 1368 | } | 
| 1369 |  | 
| 1370 | QPaintEngine::Type QPdfEngine::type() const | 
| 1371 | { | 
| 1372 |     return QPaintEngine::Pdf; | 
| 1373 | } | 
| 1374 |  | 
| 1375 | void QPdfEngine::setResolution(int resolution) | 
| 1376 | { | 
| 1377 |     Q_D(QPdfEngine); | 
| 1378 |     d->resolution = resolution; | 
| 1379 | } | 
| 1380 |  | 
| 1381 | int QPdfEngine::resolution() const | 
| 1382 | { | 
| 1383 |     Q_D(const QPdfEngine); | 
| 1384 |     return d->resolution; | 
| 1385 | } | 
| 1386 |  | 
| 1387 | void QPdfEngine::setPdfVersion(PdfVersion version) | 
| 1388 | { | 
| 1389 |     Q_D(QPdfEngine); | 
| 1390 |     d->pdfVersion = version; | 
| 1391 | } | 
| 1392 |  | 
| 1393 | void QPdfEngine::setDocumentXmpMetadata(const QByteArray &xmpMetadata) | 
| 1394 | { | 
| 1395 |     Q_D(QPdfEngine); | 
| 1396 |     d->xmpDocumentMetadata = xmpMetadata; | 
| 1397 | } | 
| 1398 |  | 
| 1399 | QByteArray QPdfEngine::documentXmpMetadata() const | 
| 1400 | { | 
| 1401 |     Q_D(const QPdfEngine); | 
| 1402 |     return d->xmpDocumentMetadata; | 
| 1403 | } | 
| 1404 |  | 
| 1405 | void QPdfEngine::setPageLayout(const QPageLayout &pageLayout) | 
| 1406 | { | 
| 1407 |     Q_D(QPdfEngine); | 
| 1408 |     d->m_pageLayout = pageLayout; | 
| 1409 | } | 
| 1410 |  | 
| 1411 | void QPdfEngine::setPageSize(const QPageSize &pageSize) | 
| 1412 | { | 
| 1413 |     Q_D(QPdfEngine); | 
| 1414 |     d->m_pageLayout.setPageSize(pageSize); | 
| 1415 | } | 
| 1416 |  | 
| 1417 | void QPdfEngine::setPageOrientation(QPageLayout::Orientation orientation) | 
| 1418 | { | 
| 1419 |     Q_D(QPdfEngine); | 
| 1420 |     d->m_pageLayout.setOrientation(orientation); | 
| 1421 | } | 
| 1422 |  | 
| 1423 | void QPdfEngine::setPageMargins(const QMarginsF &margins, QPageLayout::Unit units) | 
| 1424 | { | 
| 1425 |     Q_D(QPdfEngine); | 
| 1426 |     d->m_pageLayout.setUnits(units); | 
| 1427 |     d->m_pageLayout.setMargins(margins); | 
| 1428 | } | 
| 1429 |  | 
| 1430 | QPageLayout QPdfEngine::pageLayout() const | 
| 1431 | { | 
| 1432 |     Q_D(const QPdfEngine); | 
| 1433 |     return d->m_pageLayout; | 
| 1434 | } | 
| 1435 |  | 
| 1436 | // Metrics are in Device Pixels | 
| 1437 | int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const | 
| 1438 | { | 
| 1439 |     Q_D(const QPdfEngine); | 
| 1440 |     int val; | 
| 1441 |     switch (metricType) { | 
| 1442 |     case QPaintDevice::PdmWidth: | 
| 1443 |         val = d->m_pageLayout.paintRectPixels(d->resolution).width(); | 
| 1444 |         break; | 
| 1445 |     case QPaintDevice::PdmHeight: | 
| 1446 |         val = d->m_pageLayout.paintRectPixels(d->resolution).height(); | 
| 1447 |         break; | 
| 1448 |     case QPaintDevice::PdmDpiX: | 
| 1449 |     case QPaintDevice::PdmDpiY: | 
| 1450 |         val = d->resolution; | 
| 1451 |         break; | 
| 1452 |     case QPaintDevice::PdmPhysicalDpiX: | 
| 1453 |     case QPaintDevice::PdmPhysicalDpiY: | 
| 1454 |         val = 1200; | 
| 1455 |         break; | 
| 1456 |     case QPaintDevice::PdmWidthMM: | 
| 1457 |         val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).width()); | 
| 1458 |         break; | 
| 1459 |     case QPaintDevice::PdmHeightMM: | 
| 1460 |         val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).height()); | 
| 1461 |         break; | 
| 1462 |     case QPaintDevice::PdmNumColors: | 
| 1463 |         val = INT_MAX; | 
| 1464 |         break; | 
| 1465 |     case QPaintDevice::PdmDepth: | 
| 1466 |         val = 32; | 
| 1467 |         break; | 
| 1468 |     case QPaintDevice::PdmDevicePixelRatio: | 
| 1469 |         val = 1; | 
| 1470 |         break; | 
| 1471 |     case QPaintDevice::PdmDevicePixelRatioScaled: | 
| 1472 |         val = 1 * QPaintDevice::devicePixelRatioFScale(); | 
| 1473 |         break; | 
| 1474 |     default: | 
| 1475 |         qWarning("QPdfWriter::metric: Invalid metric command" ); | 
| 1476 |         return 0; | 
| 1477 |     } | 
| 1478 |     return val; | 
| 1479 | } | 
| 1480 |  | 
| 1481 | QPdfEnginePrivate::QPdfEnginePrivate() | 
| 1482 |     : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false), | 
| 1483 |       pdfVersion(QPdfEngine::Version_1_4), | 
| 1484 |       outDevice(nullptr), ownsDevice(false), | 
| 1485 |       embedFonts(true), | 
| 1486 |       grayscale(false), | 
| 1487 |       m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10)) | 
| 1488 | { | 
| 1489 |     initResources(); | 
| 1490 |     resolution = 1200; | 
| 1491 |     currentObject = 1; | 
| 1492 |     currentPage = nullptr; | 
| 1493 |     stroker.stream = nullptr; | 
| 1494 |  | 
| 1495 |     streampos = 0; | 
| 1496 |  | 
| 1497 |     stream = new QDataStream; | 
| 1498 | } | 
| 1499 |  | 
| 1500 | bool QPdfEngine::begin(QPaintDevice *pdev) | 
| 1501 | { | 
| 1502 |     Q_D(QPdfEngine); | 
| 1503 |     d->pdev = pdev; | 
| 1504 |  | 
| 1505 |     if (!d->outDevice) { | 
| 1506 |         if (!d->outputFileName.isEmpty()) { | 
| 1507 |             QFile *file = new QFile(d->outputFileName); | 
| 1508 |             if (!file->open(QFile::WriteOnly|QFile::Truncate)) { | 
| 1509 |                 delete file; | 
| 1510 |                 return false; | 
| 1511 |             } | 
| 1512 |             d->outDevice = file; | 
| 1513 |         } else { | 
| 1514 |             return false; | 
| 1515 |         } | 
| 1516 |         d->ownsDevice = true; | 
| 1517 |     } | 
| 1518 |  | 
| 1519 |     d->currentObject = 1; | 
| 1520 |  | 
| 1521 |     d->currentPage = new QPdfPage; | 
| 1522 |     d->stroker.stream = d->currentPage; | 
| 1523 |     d->opacity = 1.0; | 
| 1524 |  | 
| 1525 |     d->stream->setDevice(d->outDevice); | 
| 1526 |  | 
| 1527 |     d->streampos = 0; | 
| 1528 |     d->hasPen = true; | 
| 1529 |     d->hasBrush = false; | 
| 1530 |     d->clipEnabled = false; | 
| 1531 |     d->allClipped = false; | 
| 1532 |  | 
| 1533 |     d->xrefPositions.clear(); | 
| 1534 |     d->pageRoot = 0; | 
| 1535 |     d->embeddedfilesRoot = 0; | 
| 1536 |     d->namesRoot = 0; | 
| 1537 |     d->catalog = 0; | 
| 1538 |     d->info = 0; | 
| 1539 |     d->graphicsState = 0; | 
| 1540 |     d->patternColorSpace = 0; | 
| 1541 |     d->simplePen = false; | 
| 1542 |  | 
| 1543 |     d->pages.clear(); | 
| 1544 |     d->imageCache.clear(); | 
| 1545 |     d->alphaCache.clear(); | 
| 1546 |  | 
| 1547 |     setActive(true); | 
| 1548 |     d->writeHeader(); | 
| 1549 |     newPage(); | 
| 1550 |  | 
| 1551 |     return true; | 
| 1552 | } | 
| 1553 |  | 
| 1554 | bool QPdfEngine::end() | 
| 1555 | { | 
| 1556 |     Q_D(QPdfEngine); | 
| 1557 |     d->writeTail(); | 
| 1558 |  | 
| 1559 |     d->stream->setDevice(nullptr); | 
| 1560 |  | 
| 1561 |     qDeleteAll(d->fonts); | 
| 1562 |     d->fonts.clear(); | 
| 1563 |     delete d->currentPage; | 
| 1564 |     d->currentPage = nullptr; | 
| 1565 |  | 
| 1566 |     if (d->outDevice && d->ownsDevice) { | 
| 1567 |         d->outDevice->close(); | 
| 1568 |         delete d->outDevice; | 
| 1569 |         d->outDevice = nullptr; | 
| 1570 |     } | 
| 1571 |  | 
| 1572 |     d->fileCache.clear(); | 
| 1573 |  | 
| 1574 |     setActive(false); | 
| 1575 |     return true; | 
| 1576 | } | 
| 1577 |  | 
| 1578 | void QPdfEngine::addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType) | 
| 1579 | { | 
| 1580 |     Q_D(QPdfEngine); | 
| 1581 |     d->fileCache.push_back({fileName, data, mimeType}); | 
| 1582 | } | 
| 1583 |  | 
| 1584 | QPdfEnginePrivate::~QPdfEnginePrivate() | 
| 1585 | { | 
| 1586 |     qDeleteAll(fonts); | 
| 1587 |     delete currentPage; | 
| 1588 |     delete stream; | 
| 1589 | } | 
| 1590 |  | 
| 1591 | void QPdfEnginePrivate::() | 
| 1592 | { | 
| 1593 |     addXrefEntry(0,false); | 
| 1594 |  | 
| 1595 |     // Keep in sync with QPdfEngine::PdfVersion! | 
| 1596 |     static const char mapping[][4] = { | 
| 1597 |         "1.4" , // Version_1_4 | 
| 1598 |         "1.4" , // Version_A1b | 
| 1599 |         "1.6" , // Version_1_6 | 
| 1600 |     }; | 
| 1601 |     static const size_t numMappings = sizeof mapping / sizeof *mapping; | 
| 1602 |     const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0]; | 
| 1603 |  | 
| 1604 |     xprintf("%%PDF-%s\n" , verStr); | 
| 1605 |     xprintf("%%\303\242\303\243\n" ); | 
| 1606 |  | 
| 1607 |     writeInfo(); | 
| 1608 |  | 
| 1609 |     int metaDataObj = -1; | 
| 1610 |     int outputIntentObj = -1; | 
| 1611 |     if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) { | 
| 1612 |         metaDataObj = writeXmpDcumentMetaData(); | 
| 1613 |     } | 
| 1614 |     if (pdfVersion == QPdfEngine::Version_A1b) { | 
| 1615 |         outputIntentObj = writeOutputIntent(); | 
| 1616 |     } | 
| 1617 |  | 
| 1618 |     catalog = addXrefEntry(-1); | 
| 1619 |     pageRoot = requestObject(); | 
| 1620 |     if (!fileCache.isEmpty()) { | 
| 1621 |         namesRoot = requestObject(); | 
| 1622 |         embeddedfilesRoot = requestObject(); | 
| 1623 |     } | 
| 1624 |  | 
| 1625 |     // catalog | 
| 1626 |     { | 
| 1627 |         QByteArray catalog; | 
| 1628 |         QPdf::ByteStream s(&catalog); | 
| 1629 |         s << "<<\n"  | 
| 1630 |           << "/Type /Catalog\n"  | 
| 1631 |           << "/Pages "  << pageRoot << "0 R\n" ; | 
| 1632 |  | 
| 1633 |         // Embedded files, if any | 
| 1634 |         if (!fileCache.isEmpty()) | 
| 1635 |             s << "/Names "  << embeddedfilesRoot << "0 R\n" ; | 
| 1636 |  | 
| 1637 |         if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) | 
| 1638 |             s << "/Metadata "  << metaDataObj << "0 R\n" ; | 
| 1639 |  | 
| 1640 |         if (pdfVersion == QPdfEngine::Version_A1b) | 
| 1641 |             s << "/OutputIntents ["  << outputIntentObj << "0 R]\n" ; | 
| 1642 |  | 
| 1643 |         s << ">>\n"  | 
| 1644 |           << "endobj\n" ; | 
| 1645 |  | 
| 1646 |         write(catalog); | 
| 1647 |     } | 
| 1648 |  | 
| 1649 |     if (!fileCache.isEmpty()) { | 
| 1650 |         addXrefEntry(embeddedfilesRoot); | 
| 1651 |         xprintf("<</EmbeddedFiles %d 0 R>>\n"  | 
| 1652 |                 "endobj\n" , namesRoot); | 
| 1653 |     } | 
| 1654 |  | 
| 1655 |     // graphics state | 
| 1656 |     graphicsState = addXrefEntry(-1); | 
| 1657 |     xprintf("<<\n"  | 
| 1658 |             "/Type /ExtGState\n"  | 
| 1659 |             "/SA true\n"  | 
| 1660 |             "/SM 0.02\n"  | 
| 1661 |             "/ca 1.0\n"  | 
| 1662 |             "/CA 1.0\n"  | 
| 1663 |             "/AIS false\n"  | 
| 1664 |             "/SMask /None"  | 
| 1665 |             ">>\n"  | 
| 1666 |             "endobj\n" ); | 
| 1667 |  | 
| 1668 |     // color space for pattern | 
| 1669 |     patternColorSpace = addXrefEntry(-1); | 
| 1670 |     xprintf("[/Pattern /DeviceRGB]\n"  | 
| 1671 |             "endobj\n" ); | 
| 1672 | } | 
| 1673 |  | 
| 1674 | void QPdfEnginePrivate::writeInfo() | 
| 1675 | { | 
| 1676 |     info = addXrefEntry(-1); | 
| 1677 |     xprintf("<<\n/Title " ); | 
| 1678 |     printString(title); | 
| 1679 |     xprintf("\n/Creator " ); | 
| 1680 |     printString(creator); | 
| 1681 |     xprintf("\n/Producer " ); | 
| 1682 |     printString(QString::fromLatin1("Qt "  QT_VERSION_STR)); | 
| 1683 |     QDateTime now = QDateTime::currentDateTime(); | 
| 1684 |     QTime t = now.time(); | 
| 1685 |     QDate d = now.date(); | 
| 1686 |     xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d" , | 
| 1687 |             d.year(), | 
| 1688 |             d.month(), | 
| 1689 |             d.day(), | 
| 1690 |             t.hour(), | 
| 1691 |             t.minute(), | 
| 1692 |             t.second()); | 
| 1693 |     int offset = now.offsetFromUtc(); | 
| 1694 |     int hours  = (offset / 60) / 60; | 
| 1695 |     int mins   = (offset / 60) % 60; | 
| 1696 |     if (offset < 0) | 
| 1697 |         xprintf("-%02d'%02d')\n" , -hours, -mins); | 
| 1698 |     else if (offset > 0) | 
| 1699 |         xprintf("+%02d'%02d')\n" , hours , mins); | 
| 1700 |     else | 
| 1701 |         xprintf("Z)\n" ); | 
| 1702 |     xprintf(">>\n"  | 
| 1703 |             "endobj\n" ); | 
| 1704 | } | 
| 1705 |  | 
| 1706 | int QPdfEnginePrivate::writeXmpDcumentMetaData() | 
| 1707 | { | 
| 1708 |     const int metaDataObj = addXrefEntry(-1); | 
| 1709 |     QByteArray metaDataContent; | 
| 1710 |  | 
| 1711 |     if (xmpDocumentMetadata.isEmpty()) { | 
| 1712 |         const QString producer(QString::fromLatin1("Qt "  QT_VERSION_STR)); | 
| 1713 |  | 
| 1714 |         const QDateTime now = QDateTime::currentDateTime(); | 
| 1715 |         const QDate date = now.date(); | 
| 1716 |         const QTime time = now.time(); | 
| 1717 |         const QString timeStr = | 
| 1718 |                 QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d" , | 
| 1719 |                                   date.year(), date.month(), date.day(), | 
| 1720 |                                   time.hour(), time.minute(), time.second()); | 
| 1721 |  | 
| 1722 |         const int offset = now.offsetFromUtc(); | 
| 1723 |         const int hours  = (offset / 60) / 60; | 
| 1724 |         const int mins   = (offset / 60) % 60; | 
| 1725 |         QString tzStr; | 
| 1726 |         if (offset < 0) | 
| 1727 |             tzStr = QString::asprintf("-%02d:%02d" , -hours, -mins); | 
| 1728 |         else if (offset > 0) | 
| 1729 |             tzStr = QString::asprintf("+%02d:%02d" , hours , mins); | 
| 1730 |         else | 
| 1731 |             tzStr = QLatin1String("Z" ); | 
| 1732 |  | 
| 1733 |         const QString metaDataDate = timeStr + tzStr; | 
| 1734 |  | 
| 1735 |         QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml" )); | 
| 1736 |         metaDataFile.open(QIODevice::ReadOnly); | 
| 1737 |         metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(), | 
| 1738 |                                                                         title.toHtmlEscaped(), | 
| 1739 |                                                                         creator.toHtmlEscaped(), | 
| 1740 |                                                                         metaDataDate).toUtf8(); | 
| 1741 |     } | 
| 1742 |     else | 
| 1743 |         metaDataContent = xmpDocumentMetadata; | 
| 1744 |  | 
| 1745 |     xprintf("<<\n"  | 
| 1746 |             "/Type /Metadata /Subtype /XML\n"  | 
| 1747 |             "/Length %d\n"  | 
| 1748 |             ">>\n"  | 
| 1749 |             "stream\n" , metaDataContent.size()); | 
| 1750 |     write(metaDataContent); | 
| 1751 |     xprintf("\nendstream\n"  | 
| 1752 |             "endobj\n" ); | 
| 1753 |  | 
| 1754 |     return metaDataObj; | 
| 1755 | } | 
| 1756 |  | 
| 1757 | int QPdfEnginePrivate::writeOutputIntent() | 
| 1758 | { | 
| 1759 |     const int colorProfile = addXrefEntry(-1); | 
| 1760 |     { | 
| 1761 |         QFile colorProfileFile(QLatin1String(":/qpdf/sRGB2014.icc" )); | 
| 1762 |         colorProfileFile.open(QIODevice::ReadOnly); | 
| 1763 |         const QByteArray colorProfileData = colorProfileFile.readAll(); | 
| 1764 |  | 
| 1765 |         QByteArray data; | 
| 1766 |         QPdf::ByteStream s(&data); | 
| 1767 |         int length_object = requestObject(); | 
| 1768 |  | 
| 1769 |         s << "<<\n" ; | 
| 1770 |         s << "/N 3\n" ; | 
| 1771 |         s << "/Alternate /DeviceRGB\n" ; | 
| 1772 |         s << "/Length "  << length_object << "0 R\n" ; | 
| 1773 |         s << "/Filter /FlateDecode\n" ; | 
| 1774 |         s << ">>\n" ; | 
| 1775 |         s << "stream\n" ; | 
| 1776 |         write(data); | 
| 1777 |         const int len = writeCompressed(colorProfileData); | 
| 1778 |         write("\nendstream\n"  | 
| 1779 |               "endobj\n" ); | 
| 1780 |         addXrefEntry(length_object); | 
| 1781 |         xprintf("%d\n"  | 
| 1782 |                 "endobj\n" , len); | 
| 1783 |     } | 
| 1784 |  | 
| 1785 |     const int outputIntent = addXrefEntry(-1); | 
| 1786 |     { | 
| 1787 |         xprintf("<<\n" ); | 
| 1788 |         xprintf("/Type /OutputIntent\n" ); | 
| 1789 |         xprintf("/S/GTS_PDFA1\n" ); | 
| 1790 |         xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n" ); | 
| 1791 |         xprintf("/DestOutputProfile %d 0 R\n" , colorProfile); | 
| 1792 |         xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n" ); | 
| 1793 |         xprintf("/RegistryName(http://www.color.org)\n" ); | 
| 1794 |         xprintf(">>\n" ); | 
| 1795 |         xprintf("endobj\n" ); | 
| 1796 |     } | 
| 1797 |  | 
| 1798 |     return outputIntent; | 
| 1799 | } | 
| 1800 |  | 
| 1801 | void QPdfEnginePrivate::() | 
| 1802 | { | 
| 1803 |     addXrefEntry(pageRoot); | 
| 1804 |  | 
| 1805 |     xprintf("<<\n"  | 
| 1806 |             "/Type /Pages\n"  | 
| 1807 |             "/Kids \n"  | 
| 1808 |             "[\n" ); | 
| 1809 |     int size = pages.size(); | 
| 1810 |     for (int i = 0; i < size; ++i) | 
| 1811 |         xprintf("%d 0 R\n" , pages[i]); | 
| 1812 |     xprintf("]\n" ); | 
| 1813 |  | 
| 1814 |     //xprintf("/Group <</S /Transparency /I true /K false>>\n"); | 
| 1815 |     xprintf("/Count %d\n" , pages.size()); | 
| 1816 |  | 
| 1817 |     xprintf("/ProcSet [/PDF /Text /ImageB /ImageC]\n"  | 
| 1818 |             ">>\n"  | 
| 1819 |             "endobj\n" ); | 
| 1820 | } | 
| 1821 |  | 
| 1822 | void QPdfEnginePrivate::writeAttachmentRoot() | 
| 1823 | { | 
| 1824 |     if (fileCache.isEmpty()) | 
| 1825 |         return; | 
| 1826 |  | 
| 1827 |     QList<int> attachments; | 
| 1828 |     const int size = fileCache.size(); | 
| 1829 |     for (int i = 0; i < size; ++i) { | 
| 1830 |         auto attachment = fileCache.at(i); | 
| 1831 |         const int attachmentID = addXrefEntry(-1); | 
| 1832 |         xprintf("<<\n" ); | 
| 1833 |         if (do_compress) | 
| 1834 |             xprintf("/Filter /FlateDecode\n" ); | 
| 1835 |  | 
| 1836 |         const int lenobj = requestObject(); | 
| 1837 |         xprintf("/Length %d 0 R\n" , lenobj); | 
| 1838 |         int len = 0; | 
| 1839 |         xprintf(">>\nstream\n" ); | 
| 1840 |         len = writeCompressed(attachment.data); | 
| 1841 |         xprintf("\nendstream\n"  | 
| 1842 |                 "endobj\n" ); | 
| 1843 |         addXrefEntry(lenobj); | 
| 1844 |         xprintf("%d\n"  | 
| 1845 |                 "endobj\n" , len); | 
| 1846 |  | 
| 1847 |         attachments.push_back(addXrefEntry(-1)); | 
| 1848 |         xprintf("<<\n"  | 
| 1849 |                 "/F (%s)" , attachment.fileName.toLatin1().constData()); | 
| 1850 |  | 
| 1851 |         xprintf("\n/EF <</F %d 0 R>>\n"  | 
| 1852 |                 "/Type/Filespec\n"  | 
| 1853 |                  , attachmentID); | 
| 1854 |         if (!attachment.mimeType.isEmpty()) | 
| 1855 |             xprintf("/Subtype/%s\n" , | 
| 1856 |                     attachment.mimeType.replace(QLatin1String("/" ), | 
| 1857 |                                                 QLatin1String("#2F" )).toLatin1().constData()); | 
| 1858 |         xprintf(">>\nendobj\n" ); | 
| 1859 |     } | 
| 1860 |  | 
| 1861 |     // names | 
| 1862 |     addXrefEntry(namesRoot); | 
| 1863 |     xprintf("<</Names[" ); | 
| 1864 |     for (int i = 0; i < size; ++i) { | 
| 1865 |         auto attachment = fileCache.at(i); | 
| 1866 |         printString(attachment.fileName); | 
| 1867 |         xprintf("%d 0 R\n" , attachments.at(i)); | 
| 1868 |     } | 
| 1869 |     xprintf("]>>\n"  | 
| 1870 |             "endobj\n" ); | 
| 1871 | } | 
| 1872 |  | 
| 1873 | void QPdfEnginePrivate::embedFont(QFontSubset *font) | 
| 1874 | { | 
| 1875 |     //qDebug() << "embedFont" << font->object_id; | 
| 1876 |     int fontObject = font->object_id; | 
| 1877 |     QByteArray fontData = font->toTruetype(); | 
| 1878 | #ifdef FONT_DUMP | 
| 1879 |     static int i = 0; | 
| 1880 |     QString fileName("font%1.ttf" ); | 
| 1881 |     fileName = fileName.arg(i++); | 
| 1882 |     QFile ff(fileName); | 
| 1883 |     ff.open(QFile::WriteOnly); | 
| 1884 |     ff.write(fontData); | 
| 1885 |     ff.close(); | 
| 1886 | #endif | 
| 1887 |  | 
| 1888 |     int fontDescriptor = requestObject(); | 
| 1889 |     int fontstream = requestObject(); | 
| 1890 |     int cidfont = requestObject(); | 
| 1891 |     int toUnicode = requestObject(); | 
| 1892 |     int cidset = requestObject(); | 
| 1893 |  | 
| 1894 |     QFontEngine::Properties properties = font->fontEngine->properties(); | 
| 1895 |     QByteArray postscriptName = properties.postscriptName.replace(' ', '_'); | 
| 1896 |  | 
| 1897 |     { | 
| 1898 |         qreal scale = 1000/properties.emSquare.toReal(); | 
| 1899 |         addXrefEntry(fontDescriptor); | 
| 1900 |         QByteArray descriptor; | 
| 1901 |         QPdf::ByteStream s(&descriptor); | 
| 1902 |         s << "<< /Type /FontDescriptor\n"  | 
| 1903 |             "/FontName /Q" ; | 
| 1904 |         int tag = fontDescriptor; | 
| 1905 |         for (int i = 0; i < 5; ++i) { | 
| 1906 |             s << (char)('A' + (tag % 26)); | 
| 1907 |             tag /= 26; | 
| 1908 |         } | 
| 1909 |         s <<  '+' << postscriptName << "\n"  | 
| 1910 |             "/Flags "  << 4 << "\n"  | 
| 1911 |             "/FontBBox ["  | 
| 1912 |           << properties.boundingBox.x()*scale | 
| 1913 |           << -(properties.boundingBox.y() + properties.boundingBox.height())*scale | 
| 1914 |           << (properties.boundingBox.x() + properties.boundingBox.width())*scale | 
| 1915 |           << -properties.boundingBox.y()*scale  << "]\n"  | 
| 1916 |             "/ItalicAngle "  << properties.italicAngle.toReal() << "\n"  | 
| 1917 |             "/Ascent "  << properties.ascent.toReal()*scale << "\n"  | 
| 1918 |             "/Descent "  << -properties.descent.toReal()*scale << "\n"  | 
| 1919 |             "/CapHeight "  << properties.capHeight.toReal()*scale << "\n"  | 
| 1920 |             "/StemV "  << properties.lineWidth.toReal()*scale << "\n"  | 
| 1921 |             "/FontFile2 "  << fontstream << "0 R\n"  | 
| 1922 |             "/CIDSet "  << cidset << "0 R\n"  | 
| 1923 |             ">>\nendobj\n" ; | 
| 1924 |         write(descriptor); | 
| 1925 |     } | 
| 1926 |     { | 
| 1927 |         addXrefEntry(fontstream); | 
| 1928 |         QByteArray ; | 
| 1929 |         QPdf::ByteStream s(&header); | 
| 1930 |  | 
| 1931 |         int length_object = requestObject(); | 
| 1932 |         s << "<<\n"  | 
| 1933 |             "/Length1 "  << fontData.size() << "\n"  | 
| 1934 |             "/Length "  << length_object << "0 R\n" ; | 
| 1935 |         if (do_compress) | 
| 1936 |             s << "/Filter /FlateDecode\n" ; | 
| 1937 |         s << ">>\n"  | 
| 1938 |             "stream\n" ; | 
| 1939 |         write(header); | 
| 1940 |         int len = writeCompressed(fontData); | 
| 1941 |         write("\nendstream\n"  | 
| 1942 |               "endobj\n" ); | 
| 1943 |         addXrefEntry(length_object); | 
| 1944 |         xprintf("%d\n"  | 
| 1945 |                 "endobj\n" , len); | 
| 1946 |     } | 
| 1947 |     { | 
| 1948 |         addXrefEntry(cidfont); | 
| 1949 |         QByteArray cid; | 
| 1950 |         QPdf::ByteStream s(&cid); | 
| 1951 |         s << "<< /Type /Font\n"  | 
| 1952 |             "/Subtype /CIDFontType2\n"  | 
| 1953 |             "/BaseFont /"  << postscriptName << "\n"  | 
| 1954 |             "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n"  | 
| 1955 |             "/FontDescriptor "  << fontDescriptor << "0 R\n"  | 
| 1956 |             "/CIDToGIDMap /Identity\n"  | 
| 1957 |           << font->widthArray() << | 
| 1958 |             ">>\n"  | 
| 1959 |             "endobj\n" ; | 
| 1960 |         write(cid); | 
| 1961 |     } | 
| 1962 |     { | 
| 1963 |         addXrefEntry(toUnicode); | 
| 1964 |         QByteArray touc = font->createToUnicodeMap(); | 
| 1965 |         xprintf("<< /Length %d >>\n"  | 
| 1966 |                 "stream\n" , touc.length()); | 
| 1967 |         write(touc); | 
| 1968 |         write("\nendstream\n"  | 
| 1969 |               "endobj\n" ); | 
| 1970 |     } | 
| 1971 |     { | 
| 1972 |         addXrefEntry(fontObject); | 
| 1973 |         QByteArray font; | 
| 1974 |         QPdf::ByteStream s(&font); | 
| 1975 |         s << "<< /Type /Font\n"  | 
| 1976 |             "/Subtype /Type0\n"  | 
| 1977 |             "/BaseFont /"  << postscriptName << "\n"  | 
| 1978 |             "/Encoding /Identity-H\n"  | 
| 1979 |             "/DescendantFonts ["  << cidfont << "0 R]\n"  | 
| 1980 |             "/ToUnicode "  << toUnicode << "0 R"  | 
| 1981 |             ">>\n"  | 
| 1982 |             "endobj\n" ; | 
| 1983 |         write(font); | 
| 1984 |     } | 
| 1985 |     { | 
| 1986 |         QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0); | 
| 1987 |         int byteCounter = 0; | 
| 1988 |         int bitCounter = 0; | 
| 1989 |         for (int i = 0; i < font->nGlyphs(); ++i) { | 
| 1990 |             cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter)); | 
| 1991 |  | 
| 1992 |             bitCounter++; | 
| 1993 |             if (bitCounter == 8) { | 
| 1994 |                 bitCounter = 0; | 
| 1995 |                 byteCounter++; | 
| 1996 |             } | 
| 1997 |         } | 
| 1998 |  | 
| 1999 |         addXrefEntry(cidset); | 
| 2000 |         xprintf("<<\n" ); | 
| 2001 |         xprintf("/Length %d\n" , cidSetStream.size()); | 
| 2002 |         xprintf(">>\n" ); | 
| 2003 |         xprintf("stream\n" ); | 
| 2004 |         write(cidSetStream); | 
| 2005 |         xprintf("\nendstream\n" ); | 
| 2006 |         xprintf("endobj\n" ); | 
| 2007 |     } | 
| 2008 | } | 
| 2009 |  | 
| 2010 | qreal QPdfEnginePrivate::calcUserUnit() const | 
| 2011 | { | 
| 2012 |     // PDF standards < 1.6 support max 200x200in pages (no UserUnit) | 
| 2013 |     if (pdfVersion < QPdfEngine::Version_1_6) | 
| 2014 |         return 1.0; | 
| 2015 |  | 
| 2016 |     const int maxLen = qMax(currentPage->pageSize.width(), currentPage->pageSize.height()); | 
| 2017 |     if (maxLen <= 14400) | 
| 2018 |         return 1.0; // for pages up to 200x200in (14400x14400 units) use default scaling | 
| 2019 |  | 
| 2020 |     // for larger pages, rescale units so we can have up to 381x381km | 
| 2021 |     return qMin(maxLen / 14400.0, 75000.0); | 
| 2022 | } | 
| 2023 |  | 
| 2024 | void QPdfEnginePrivate::writeFonts() | 
| 2025 | { | 
| 2026 |     for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) { | 
| 2027 |         embedFont(*it); | 
| 2028 |         delete *it; | 
| 2029 |     } | 
| 2030 |     fonts.clear(); | 
| 2031 | } | 
| 2032 |  | 
| 2033 | void QPdfEnginePrivate::writePage() | 
| 2034 | { | 
| 2035 |     if (pages.empty()) | 
| 2036 |         return; | 
| 2037 |  | 
| 2038 |     *currentPage << "Q Q\n" ; | 
| 2039 |  | 
| 2040 |     uint pageStream = requestObject(); | 
| 2041 |     uint pageStreamLength = requestObject(); | 
| 2042 |     uint resources = requestObject(); | 
| 2043 |     uint annots = requestObject(); | 
| 2044 |  | 
| 2045 |     qreal userUnit = calcUserUnit(); | 
| 2046 |  | 
| 2047 |     addXrefEntry(pages.constLast()); | 
| 2048 |     xprintf("<<\n"  | 
| 2049 |             "/Type /Page\n"  | 
| 2050 |             "/Parent %d 0 R\n"  | 
| 2051 |             "/Contents %d 0 R\n"  | 
| 2052 |             "/Resources %d 0 R\n"  | 
| 2053 |             "/Annots %d 0 R\n"  | 
| 2054 |             "/MediaBox [0 0 %s %s]\n" , | 
| 2055 |             pageRoot, pageStream, resources, annots, | 
| 2056 |             // make sure we use the pagesize from when we started the page, since the user may have changed it | 
| 2057 |             QByteArray::number(currentPage->pageSize.width() / userUnit, 'f').constData(), | 
| 2058 |             QByteArray::number(currentPage->pageSize.height() / userUnit, 'f').constData()); | 
| 2059 |  | 
| 2060 |     if (pdfVersion >= QPdfEngine::Version_1_6) | 
| 2061 |         xprintf("/UserUnit %s\n" , QByteArray::number(userUnit, 'f').constData()); | 
| 2062 |  | 
| 2063 |     xprintf(">>\n"  | 
| 2064 |             "endobj\n" ); | 
| 2065 |  | 
| 2066 |     addXrefEntry(resources); | 
| 2067 |     xprintf("<<\n"  | 
| 2068 |             "/ColorSpace <<\n"  | 
| 2069 |             "/PCSp %d 0 R\n"  | 
| 2070 |             "/CSp /DeviceRGB\n"  | 
| 2071 |             "/CSpg /DeviceGray\n"  | 
| 2072 |             ">>\n"  | 
| 2073 |             "/ExtGState <<\n"  | 
| 2074 |             "/GSa %d 0 R\n" , | 
| 2075 |             patternColorSpace, graphicsState); | 
| 2076 |  | 
| 2077 |     for (int i = 0; i < currentPage->graphicStates.size(); ++i) | 
| 2078 |         xprintf("/GState%d %d 0 R\n" , currentPage->graphicStates.at(i), currentPage->graphicStates.at(i)); | 
| 2079 |     xprintf(">>\n" ); | 
| 2080 |  | 
| 2081 |     xprintf("/Pattern <<\n" ); | 
| 2082 |     for (int i = 0; i < currentPage->patterns.size(); ++i) | 
| 2083 |         xprintf("/Pat%d %d 0 R\n" , currentPage->patterns.at(i), currentPage->patterns.at(i)); | 
| 2084 |     xprintf(">>\n" ); | 
| 2085 |  | 
| 2086 |     xprintf("/Font <<\n" ); | 
| 2087 |     for (int i = 0; i < currentPage->fonts.size();++i) | 
| 2088 |         xprintf("/F%d %d 0 R\n" , currentPage->fonts[i], currentPage->fonts[i]); | 
| 2089 |     xprintf(">>\n" ); | 
| 2090 |  | 
| 2091 |     xprintf("/XObject <<\n" ); | 
| 2092 |     for (int i = 0; i<currentPage->images.size(); ++i) { | 
| 2093 |         xprintf("/Im%d %d 0 R\n" , currentPage->images.at(i), currentPage->images.at(i)); | 
| 2094 |     } | 
| 2095 |     xprintf(">>\n" ); | 
| 2096 |  | 
| 2097 |     xprintf(">>\n"  | 
| 2098 |             "endobj\n" ); | 
| 2099 |  | 
| 2100 |     addXrefEntry(annots); | 
| 2101 |     xprintf("[ " ); | 
| 2102 |     for (int i = 0; i<currentPage->annotations.size(); ++i) { | 
| 2103 |         xprintf("%d 0 R " , currentPage->annotations.at(i)); | 
| 2104 |     } | 
| 2105 |     xprintf("]\nendobj\n" ); | 
| 2106 |  | 
| 2107 |     addXrefEntry(pageStream); | 
| 2108 |     xprintf("<<\n"  | 
| 2109 |             "/Length %d 0 R\n" , pageStreamLength); // object number for stream length object | 
| 2110 |     if (do_compress) | 
| 2111 |         xprintf("/Filter /FlateDecode\n" ); | 
| 2112 |  | 
| 2113 |     xprintf(">>\n" ); | 
| 2114 |     xprintf("stream\n" ); | 
| 2115 |     QIODevice *content = currentPage->stream(); | 
| 2116 |     int len = writeCompressed(content); | 
| 2117 |     xprintf("\nendstream\n"  | 
| 2118 |             "endobj\n" ); | 
| 2119 |  | 
| 2120 |     addXrefEntry(pageStreamLength); | 
| 2121 |     xprintf("%d\nendobj\n" ,len); | 
| 2122 | } | 
| 2123 |  | 
| 2124 | void QPdfEnginePrivate::writeTail() | 
| 2125 | { | 
| 2126 |     writePage(); | 
| 2127 |     writeFonts(); | 
| 2128 |     writePageRoot(); | 
| 2129 |     writeAttachmentRoot(); | 
| 2130 |  | 
| 2131 |     addXrefEntry(xrefPositions.size(),false); | 
| 2132 |     xprintf("xref\n"  | 
| 2133 |             "0 %d\n"  | 
| 2134 |             "%010d 65535 f \n" , xrefPositions.size()-1, xrefPositions[0]); | 
| 2135 |  | 
| 2136 |     for (int i = 1; i < xrefPositions.size()-1; ++i) | 
| 2137 |         xprintf("%010d 00000 n \n" , xrefPositions[i]); | 
| 2138 |  | 
| 2139 |     { | 
| 2140 |         QByteArray trailer; | 
| 2141 |         QPdf::ByteStream s(&trailer); | 
| 2142 |  | 
| 2143 |         s << "trailer\n"  | 
| 2144 |           << "<<\n"  | 
| 2145 |           << "/Size "  << xrefPositions.size() - 1 << "\n"  | 
| 2146 |           << "/Info "  << info << "0 R\n"  | 
| 2147 |           << "/Root "  << catalog << "0 R\n" ; | 
| 2148 |  | 
| 2149 |         if (pdfVersion == QPdfEngine::Version_A1b) { | 
| 2150 |             const QString uniqueId = QUuid::createUuid().toString(); | 
| 2151 |             const QByteArray fileIdentifier = QCryptographicHash::hash(uniqueId.toLatin1(), QCryptographicHash::Md5).toHex(); | 
| 2152 |             s << "/ID [ <"  << fileIdentifier << "> <"  << fileIdentifier << "> ]\n" ; | 
| 2153 |         } | 
| 2154 |  | 
| 2155 |         s << ">>\n"  | 
| 2156 |           << "startxref\n"  << xrefPositions.constLast() << "\n"  | 
| 2157 |           << "%%EOF\n" ; | 
| 2158 |  | 
| 2159 |         write(trailer); | 
| 2160 |     } | 
| 2161 | } | 
| 2162 |  | 
| 2163 | int QPdfEnginePrivate::addXrefEntry(int object, bool printostr) | 
| 2164 | { | 
| 2165 |     if (object < 0) | 
| 2166 |         object = requestObject(); | 
| 2167 |  | 
| 2168 |     if (object>=xrefPositions.size()) | 
| 2169 |         xrefPositions.resize(object+1); | 
| 2170 |  | 
| 2171 |     xrefPositions[object] = streampos; | 
| 2172 |     if (printostr) | 
| 2173 |         xprintf("%d 0 obj\n" ,object); | 
| 2174 |  | 
| 2175 |     return object; | 
| 2176 | } | 
| 2177 |  | 
| 2178 | void QPdfEnginePrivate::printString(const QString &string) | 
| 2179 | { | 
| 2180 |     if (string.isEmpty()) { | 
| 2181 |         write("()" ); | 
| 2182 |         return; | 
| 2183 |     } | 
| 2184 |  | 
| 2185 |     // The 'text string' type in PDF is encoded either as PDFDocEncoding, or | 
| 2186 |     // Unicode UTF-16 with a Unicode byte order mark as the first character | 
| 2187 |     // (0xfeff), with the high-order byte first. | 
| 2188 |     QByteArray array("(\xfe\xff" ); | 
| 2189 |     const ushort *utf16 = string.utf16(); | 
| 2190 |  | 
| 2191 |     for (int i=0; i < string.size(); ++i) { | 
| 2192 |         char part[2] = {char((*(utf16 + i)) >> 8), char((*(utf16 + i)) & 0xff)}; | 
| 2193 |         for(int j=0; j < 2; ++j) { | 
| 2194 |             if (part[j] == '(' || part[j] == ')' || part[j] == '\\') | 
| 2195 |                 array.append('\\'); | 
| 2196 |             array.append(part[j]); | 
| 2197 |         } | 
| 2198 |     } | 
| 2199 |     array.append(')'); | 
| 2200 |     write(array); | 
| 2201 | } | 
| 2202 |  | 
| 2203 |  | 
| 2204 | void QPdfEnginePrivate::xprintf(const char* fmt, ...) | 
| 2205 | { | 
| 2206 |     if (!stream) | 
| 2207 |         return; | 
| 2208 |  | 
| 2209 |     const int msize = 10000; | 
| 2210 |     char buf[msize]; | 
| 2211 |  | 
| 2212 |     va_list args; | 
| 2213 |     va_start(args, fmt); | 
| 2214 |     int bufsize = qvsnprintf(buf, msize, fmt, args); | 
| 2215 |     va_end(args); | 
| 2216 |  | 
| 2217 |     if (Q_LIKELY(bufsize < msize)) { | 
| 2218 |         stream->writeRawData(buf, bufsize); | 
| 2219 |     } else { | 
| 2220 |         // Fallback for abnormal cases | 
| 2221 |         QScopedArrayPointer<char> tmpbuf(new char[bufsize + 1]); | 
| 2222 |         va_start(args, fmt); | 
| 2223 |         bufsize = qvsnprintf(tmpbuf.data(), bufsize + 1, fmt, args); | 
| 2224 |         va_end(args); | 
| 2225 |         stream->writeRawData(tmpbuf.data(), bufsize); | 
| 2226 |     } | 
| 2227 |     streampos += bufsize; | 
| 2228 | } | 
| 2229 |  | 
| 2230 | int QPdfEnginePrivate::writeCompressed(QIODevice *dev) | 
| 2231 | { | 
| 2232 | #ifndef QT_NO_COMPRESS | 
| 2233 |     if (do_compress) { | 
| 2234 |         int size = QPdfPage::chunkSize(); | 
| 2235 |         int sum = 0; | 
| 2236 |         ::z_stream zStruct; | 
| 2237 |         zStruct.zalloc = Z_NULL; | 
| 2238 |         zStruct.zfree = Z_NULL; | 
| 2239 |         zStruct.opaque = Z_NULL; | 
| 2240 |         if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) { | 
| 2241 |             qWarning("QPdfStream::writeCompressed: Error in deflateInit()" ); | 
| 2242 |             return sum; | 
| 2243 |         } | 
| 2244 |         zStruct.avail_in = 0; | 
| 2245 |         QByteArray in, out; | 
| 2246 |         out.resize(size); | 
| 2247 |         while (!dev->atEnd() || zStruct.avail_in != 0) { | 
| 2248 |             if (zStruct.avail_in == 0) { | 
| 2249 |                 in = dev->read(size); | 
| 2250 |                 zStruct.avail_in = in.size(); | 
| 2251 |                 zStruct.next_in = reinterpret_cast<unsigned char*>(in.data()); | 
| 2252 |                 if (in.size() <= 0) { | 
| 2253 |                     qWarning("QPdfStream::writeCompressed: Error in read()" ); | 
| 2254 |                     ::deflateEnd(&zStruct); | 
| 2255 |                     return sum; | 
| 2256 |                 } | 
| 2257 |             } | 
| 2258 |             zStruct.next_out = reinterpret_cast<unsigned char*>(out.data()); | 
| 2259 |             zStruct.avail_out = out.size(); | 
| 2260 |             if (::deflate(&zStruct, 0) != Z_OK) { | 
| 2261 |                 qWarning("QPdfStream::writeCompressed: Error in deflate()" ); | 
| 2262 |                 ::deflateEnd(&zStruct); | 
| 2263 |                 return sum; | 
| 2264 |             } | 
| 2265 |             int written = out.size() - zStruct.avail_out; | 
| 2266 |             stream->writeRawData(out.constData(), written); | 
| 2267 |             streampos += written; | 
| 2268 |             sum += written; | 
| 2269 |         } | 
| 2270 |         int ret; | 
| 2271 |         do { | 
| 2272 |             zStruct.next_out = reinterpret_cast<unsigned char*>(out.data()); | 
| 2273 |             zStruct.avail_out = out.size(); | 
| 2274 |             ret = ::deflate(&zStruct, Z_FINISH); | 
| 2275 |             if (ret != Z_OK && ret != Z_STREAM_END) { | 
| 2276 |                 qWarning("QPdfStream::writeCompressed: Error in deflate()" ); | 
| 2277 |                 ::deflateEnd(&zStruct); | 
| 2278 |                 return sum; | 
| 2279 |             } | 
| 2280 |             int written = out.size() - zStruct.avail_out; | 
| 2281 |             stream->writeRawData(out.constData(), written); | 
| 2282 |             streampos += written; | 
| 2283 |             sum += written; | 
| 2284 |         } while (ret == Z_OK); | 
| 2285 |  | 
| 2286 |         ::deflateEnd(&zStruct); | 
| 2287 |  | 
| 2288 |         return sum; | 
| 2289 |     } else | 
| 2290 | #endif | 
| 2291 |     { | 
| 2292 |         QByteArray arr; | 
| 2293 |         int sum = 0; | 
| 2294 |         while (!dev->atEnd()) { | 
| 2295 |             arr = dev->read(QPdfPage::chunkSize()); | 
| 2296 |             stream->writeRawData(arr.constData(), arr.size()); | 
| 2297 |             streampos += arr.size(); | 
| 2298 |             sum += arr.size(); | 
| 2299 |         } | 
| 2300 |         return sum; | 
| 2301 |     } | 
| 2302 | } | 
| 2303 |  | 
| 2304 | int QPdfEnginePrivate::writeCompressed(const char *src, int len) | 
| 2305 | { | 
| 2306 | #ifndef QT_NO_COMPRESS | 
| 2307 |     if(do_compress) { | 
| 2308 |         uLongf destLen = len + len/100 + 13; // zlib requirement | 
| 2309 |         Bytef* dest = new Bytef[destLen]; | 
| 2310 |         if (Z_OK == ::compress(dest, &destLen, (const Bytef*) src, (uLongf)len)) { | 
| 2311 |             stream->writeRawData((const char*)dest, destLen); | 
| 2312 |         } else { | 
| 2313 |             qWarning("QPdfStream::writeCompressed: Error in compress()" ); | 
| 2314 |             destLen = 0; | 
| 2315 |         } | 
| 2316 |         delete [] dest; | 
| 2317 |         len = destLen; | 
| 2318 |     } else | 
| 2319 | #endif | 
| 2320 |     { | 
| 2321 |         stream->writeRawData(src,len); | 
| 2322 |     } | 
| 2323 |     streampos += len; | 
| 2324 |     return len; | 
| 2325 | } | 
| 2326 |  | 
| 2327 | int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth, | 
| 2328 |                                   int maskObject, int softMaskObject, bool dct, bool isMono) | 
| 2329 | { | 
| 2330 |     int image = addXrefEntry(-1); | 
| 2331 |     xprintf("<<\n"  | 
| 2332 |             "/Type /XObject\n"  | 
| 2333 |             "/Subtype /Image\n"  | 
| 2334 |             "/Width %d\n"  | 
| 2335 |             "/Height %d\n" , width, height); | 
| 2336 |  | 
| 2337 |     if (depth == 1) { | 
| 2338 |         if (!isMono) { | 
| 2339 |             xprintf("/ImageMask true\n"  | 
| 2340 |                     "/Decode [1 0]\n" ); | 
| 2341 |         } else { | 
| 2342 |             xprintf("/BitsPerComponent 1\n"  | 
| 2343 |                     "/ColorSpace /DeviceGray\n" ); | 
| 2344 |         } | 
| 2345 |     } else { | 
| 2346 |         xprintf("/BitsPerComponent 8\n"  | 
| 2347 |                 "/ColorSpace %s\n" , (depth == 32) ? "/DeviceRGB"  : "/DeviceGray" ); | 
| 2348 |     } | 
| 2349 |     if (maskObject > 0) | 
| 2350 |         xprintf("/Mask %d 0 R\n" , maskObject); | 
| 2351 |     if (softMaskObject > 0) | 
| 2352 |         xprintf("/SMask %d 0 R\n" , softMaskObject); | 
| 2353 |  | 
| 2354 |     int lenobj = requestObject(); | 
| 2355 |     xprintf("/Length %d 0 R\n" , lenobj); | 
| 2356 |     if (interpolateImages) | 
| 2357 |         xprintf("/Interpolate true\n" ); | 
| 2358 |     int len = 0; | 
| 2359 |     if (dct) { | 
| 2360 |         //qDebug("DCT"); | 
| 2361 |         xprintf("/Filter /DCTDecode\n>>\nstream\n" ); | 
| 2362 |         write(data); | 
| 2363 |         len = data.length(); | 
| 2364 |     } else { | 
| 2365 |         if (do_compress) | 
| 2366 |             xprintf("/Filter /FlateDecode\n>>\nstream\n" ); | 
| 2367 |         else | 
| 2368 |             xprintf(">>\nstream\n" ); | 
| 2369 |         len = writeCompressed(data); | 
| 2370 |     } | 
| 2371 |     xprintf("\nendstream\n"  | 
| 2372 |             "endobj\n" ); | 
| 2373 |     addXrefEntry(lenobj); | 
| 2374 |     xprintf("%d\n"  | 
| 2375 |             "endobj\n" , len); | 
| 2376 |     return image; | 
| 2377 | } | 
| 2378 |  | 
| 2379 | struct QGradientBound { | 
| 2380 |     qreal start; | 
| 2381 |     qreal stop; | 
| 2382 |     int function; | 
| 2383 |     bool reverse; | 
| 2384 | }; | 
| 2385 | Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE); | 
| 2386 |  | 
| 2387 | int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha) | 
| 2388 | { | 
| 2389 |     QGradientStops stops = gradient->stops(); | 
| 2390 |     if (stops.isEmpty()) { | 
| 2391 |         stops << QGradientStop(0, Qt::black); | 
| 2392 |         stops << QGradientStop(1, Qt::white); | 
| 2393 |     } | 
| 2394 |     if (stops.at(0).first > 0) | 
| 2395 |         stops.prepend(QGradientStop(0, stops.at(0).second)); | 
| 2396 |     if (stops.at(stops.size() - 1).first < 1) | 
| 2397 |         stops.append(QGradientStop(1, stops.at(stops.size() - 1).second)); | 
| 2398 |  | 
| 2399 |     QList<int> functions; | 
| 2400 |     const int numStops = stops.size(); | 
| 2401 |     functions.reserve(numStops - 1); | 
| 2402 |     for (int i = 0; i < numStops - 1; ++i) { | 
| 2403 |         int f = addXrefEntry(-1); | 
| 2404 |         QByteArray data; | 
| 2405 |         QPdf::ByteStream s(&data); | 
| 2406 |         s << "<<\n"  | 
| 2407 |              "/FunctionType 2\n"  | 
| 2408 |              "/Domain [0 1]\n"  | 
| 2409 |              "/N 1\n" ; | 
| 2410 |         if (alpha) { | 
| 2411 |             s << "/C0 ["  << stops.at(i).second.alphaF() << "]\n"  | 
| 2412 |                  "/C1 ["  << stops.at(i + 1).second.alphaF() << "]\n" ; | 
| 2413 |         } else { | 
| 2414 |             s << "/C0 ["  << stops.at(i).second.redF() << stops.at(i).second.greenF() <<  stops.at(i).second.blueF() << "]\n"  | 
| 2415 |                  "/C1 ["  << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() <<  stops.at(i + 1).second.blueF() << "]\n" ; | 
| 2416 |         } | 
| 2417 |         s << ">>\n"  | 
| 2418 |              "endobj\n" ; | 
| 2419 |         write(data); | 
| 2420 |         functions << f; | 
| 2421 |     } | 
| 2422 |  | 
| 2423 |     QList<QGradientBound> gradientBounds; | 
| 2424 |     gradientBounds.reserve((to - from) * (numStops - 1)); | 
| 2425 |  | 
| 2426 |     for (int step = from; step < to; ++step) { | 
| 2427 |         if (reflect && step % 2) { | 
| 2428 |             for (int i = numStops - 1; i > 0; --i) { | 
| 2429 |                 QGradientBound b; | 
| 2430 |                 b.start = step + 1 - qBound(qreal(0.), stops.at(i).first, qreal(1.)); | 
| 2431 |                 b.stop = step + 1 - qBound(qreal(0.), stops.at(i - 1).first, qreal(1.)); | 
| 2432 |                 b.function = functions.at(i - 1); | 
| 2433 |                 b.reverse = true; | 
| 2434 |                 gradientBounds << b; | 
| 2435 |             } | 
| 2436 |         } else { | 
| 2437 |             for (int i = 0; i < numStops - 1; ++i) { | 
| 2438 |                 QGradientBound b; | 
| 2439 |                 b.start = step + qBound(qreal(0.), stops.at(i).first, qreal(1.)); | 
| 2440 |                 b.stop = step + qBound(qreal(0.), stops.at(i + 1).first, qreal(1.)); | 
| 2441 |                 b.function = functions.at(i); | 
| 2442 |                 b.reverse = false; | 
| 2443 |                 gradientBounds << b; | 
| 2444 |             } | 
| 2445 |         } | 
| 2446 |     } | 
| 2447 |  | 
| 2448 |     // normalize bounds to [0..1] | 
| 2449 |     qreal bstart = gradientBounds.at(0).start; | 
| 2450 |     qreal bend = gradientBounds.at(gradientBounds.size() - 1).stop; | 
| 2451 |     qreal norm = 1./(bend - bstart); | 
| 2452 |     for (int i = 0; i < gradientBounds.size(); ++i) { | 
| 2453 |         gradientBounds[i].start = (gradientBounds[i].start - bstart)*norm; | 
| 2454 |         gradientBounds[i].stop = (gradientBounds[i].stop - bstart)*norm; | 
| 2455 |     } | 
| 2456 |  | 
| 2457 |     int function; | 
| 2458 |     if (gradientBounds.size() > 1) { | 
| 2459 |         function = addXrefEntry(-1); | 
| 2460 |         QByteArray data; | 
| 2461 |         QPdf::ByteStream s(&data); | 
| 2462 |         s << "<<\n"  | 
| 2463 |              "/FunctionType 3\n"  | 
| 2464 |              "/Domain [0 1]\n"  | 
| 2465 |              "/Bounds [" ; | 
| 2466 |         for (int i = 1; i < gradientBounds.size(); ++i) | 
| 2467 |             s << gradientBounds.at(i).start; | 
| 2468 |         s << "]\n"  | 
| 2469 |              "/Encode [" ; | 
| 2470 |         for (int i = 0; i < gradientBounds.size(); ++i) | 
| 2471 |             s << (gradientBounds.at(i).reverse ? "1 0 "  : "0 1 " ); | 
| 2472 |         s << "]\n"  | 
| 2473 |              "/Functions [" ; | 
| 2474 |         for (int i = 0; i < gradientBounds.size(); ++i) | 
| 2475 |             s << gradientBounds.at(i).function << "0 R " ; | 
| 2476 |         s << "]\n"  | 
| 2477 |              ">>\n"  | 
| 2478 |              "endobj\n" ; | 
| 2479 |         write(data); | 
| 2480 |     } else { | 
| 2481 |         function = functions.at(0); | 
| 2482 |     } | 
| 2483 |     return function; | 
| 2484 | } | 
| 2485 |  | 
| 2486 | int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradient, const QTransform &matrix, bool alpha) | 
| 2487 | { | 
| 2488 |     QPointF start = gradient->start(); | 
| 2489 |     QPointF stop = gradient->finalStop(); | 
| 2490 |     QPointF offset = stop - start; | 
| 2491 |     Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode); | 
| 2492 |  | 
| 2493 |     int from = 0; | 
| 2494 |     int to = 1; | 
| 2495 |     bool reflect = false; | 
| 2496 |     switch (gradient->spread()) { | 
| 2497 |     case QGradient::PadSpread: | 
| 2498 |         break; | 
| 2499 |     case QGradient::ReflectSpread: | 
| 2500 |         reflect = true; | 
| 2501 |         Q_FALLTHROUGH(); | 
| 2502 |     case QGradient::RepeatSpread: { | 
| 2503 |         // calculate required bounds | 
| 2504 |         QRectF  = m_pageLayout.fullRectPixels(resolution); | 
| 2505 |         QTransform inv = matrix.inverted(); | 
| 2506 |         QPointF page_rect[4] = { inv.map(pageRect.topLeft()), | 
| 2507 |                                  inv.map(pageRect.topRight()), | 
| 2508 |                                  inv.map(pageRect.bottomLeft()), | 
| 2509 |                                  inv.map(pageRect.bottomRight()) }; | 
| 2510 |  | 
| 2511 |         qreal length = offset.x()*offset.x() + offset.y()*offset.y(); | 
| 2512 |  | 
| 2513 |         // find the max and min values in offset and orth direction that are needed to cover | 
| 2514 |         // the whole page | 
| 2515 |         from = INT_MAX; | 
| 2516 |         to = INT_MIN; | 
| 2517 |         for (int i = 0; i < 4; ++i) { | 
| 2518 |             qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length; | 
| 2519 |             from = qMin(from, qFloor(off)); | 
| 2520 |             to = qMax(to, qCeil(off)); | 
| 2521 |         } | 
| 2522 |  | 
| 2523 |         stop = start + to * offset; | 
| 2524 |         start = start + from * offset;\ | 
| 2525 |         break; | 
| 2526 |     } | 
| 2527 |     } | 
| 2528 |  | 
| 2529 |     int function = createShadingFunction(gradient, from, to, reflect, alpha); | 
| 2530 |  | 
| 2531 |     QByteArray shader; | 
| 2532 |     QPdf::ByteStream s(&shader); | 
| 2533 |     s << "<<\n"  | 
| 2534 |         "/ShadingType 2\n"  | 
| 2535 |         "/ColorSpace "  << (alpha ? "/DeviceGray\n"  : "/DeviceRGB\n" ) << | 
| 2536 |         "/AntiAlias true\n"  | 
| 2537 |         "/Coords ["  << start.x() << start.y() << stop.x() << stop.y() << "]\n"  | 
| 2538 |         "/Extend [true true]\n"  | 
| 2539 |         "/Function "  << function << "0 R\n"  | 
| 2540 |         ">>\n"  | 
| 2541 |         "endobj\n" ; | 
| 2542 |     int shaderObject = addXrefEntry(-1); | 
| 2543 |     write(shader); | 
| 2544 |     return shaderObject; | 
| 2545 | } | 
| 2546 |  | 
| 2547 | int QPdfEnginePrivate::generateRadialGradientShader(const QRadialGradient *gradient, const QTransform &matrix, bool alpha) | 
| 2548 | { | 
| 2549 |     QPointF p1 = gradient->center(); | 
| 2550 |     qreal r1 = gradient->centerRadius(); | 
| 2551 |     QPointF p0 = gradient->focalPoint(); | 
| 2552 |     qreal r0 = gradient->focalRadius(); | 
| 2553 |  | 
| 2554 |     Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode); | 
| 2555 |  | 
| 2556 |     int from = 0; | 
| 2557 |     int to = 1; | 
| 2558 |     bool reflect = false; | 
| 2559 |     switch (gradient->spread()) { | 
| 2560 |     case QGradient::PadSpread: | 
| 2561 |         break; | 
| 2562 |     case QGradient::ReflectSpread: | 
| 2563 |         reflect = true; | 
| 2564 |         Q_FALLTHROUGH(); | 
| 2565 |     case QGradient::RepeatSpread: { | 
| 2566 |         Q_ASSERT(qFuzzyIsNull(r0)); // QPainter emulates if this is not 0 | 
| 2567 |  | 
| 2568 |         QRectF  = m_pageLayout.fullRectPixels(resolution); | 
| 2569 |         QTransform inv = matrix.inverted(); | 
| 2570 |         QPointF page_rect[4] = { inv.map(pageRect.topLeft()), | 
| 2571 |                                  inv.map(pageRect.topRight()), | 
| 2572 |                                  inv.map(pageRect.bottomLeft()), | 
| 2573 |                                  inv.map(pageRect.bottomRight()) }; | 
| 2574 |  | 
| 2575 |         // increase to until the whole page fits into it | 
| 2576 |         bool done = false; | 
| 2577 |         while (!done) { | 
| 2578 |             QPointF center = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y())); | 
| 2579 |             double radius = r0 + to*(r1 - r0); | 
| 2580 |             double r2 = radius*radius; | 
| 2581 |             done = true; | 
| 2582 |             for (int i = 0; i < 4; ++i) { | 
| 2583 |                 QPointF off = page_rect[i] - center; | 
| 2584 |                 if (off.x()*off.x() + off.y()*off.y() > r2) { | 
| 2585 |                     ++to; | 
| 2586 |                     done = false; | 
| 2587 |                     break; | 
| 2588 |                 } | 
| 2589 |             } | 
| 2590 |         } | 
| 2591 |         p1 = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y())); | 
| 2592 |         r1 = r0 + to*(r1 - r0); | 
| 2593 |         break; | 
| 2594 |     } | 
| 2595 |     } | 
| 2596 |  | 
| 2597 |     int function = createShadingFunction(gradient, from, to, reflect, alpha); | 
| 2598 |  | 
| 2599 |     QByteArray shader; | 
| 2600 |     QPdf::ByteStream s(&shader); | 
| 2601 |     s << "<<\n"  | 
| 2602 |         "/ShadingType 3\n"  | 
| 2603 |         "/ColorSpace "  << (alpha ? "/DeviceGray\n"  : "/DeviceRGB\n" ) << | 
| 2604 |         "/AntiAlias true\n"  | 
| 2605 |         "/Domain [0 1]\n"  | 
| 2606 |         "/Coords ["  << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 << "]\n"  | 
| 2607 |         "/Extend [true true]\n"  | 
| 2608 |         "/Function "  << function << "0 R\n"  | 
| 2609 |         ">>\n"  | 
| 2610 |         "endobj\n" ; | 
| 2611 |     int shaderObject = addXrefEntry(-1); | 
| 2612 |     write(shader); | 
| 2613 |     return shaderObject; | 
| 2614 | } | 
| 2615 |  | 
| 2616 | int QPdfEnginePrivate::generateGradientShader(const QGradient *gradient, const QTransform &matrix, bool alpha) | 
| 2617 | { | 
| 2618 |     switch (gradient->type()) { | 
| 2619 |     case QGradient::LinearGradient: | 
| 2620 |         return generateLinearGradientShader(static_cast<const QLinearGradient *>(gradient), matrix, alpha); | 
| 2621 |     case QGradient::RadialGradient: | 
| 2622 |         return generateRadialGradientShader(static_cast<const QRadialGradient *>(gradient), matrix, alpha); | 
| 2623 |     case QGradient::ConicalGradient: | 
| 2624 |         Q_UNIMPLEMENTED(); // ### Implement me! | 
| 2625 |         break; | 
| 2626 |     case QGradient::NoGradient: | 
| 2627 |         break; | 
| 2628 |     } | 
| 2629 |     return 0; | 
| 2630 | } | 
| 2631 |  | 
| 2632 | int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QTransform &matrix, int *gStateObject) | 
| 2633 | { | 
| 2634 |     const QGradient *gradient = b.gradient(); | 
| 2635 |  | 
| 2636 |     if (!gradient || gradient->coordinateMode() != QGradient::LogicalMode) | 
| 2637 |         return 0; | 
| 2638 |  | 
| 2639 |     QRectF  = m_pageLayout.fullRectPixels(resolution); | 
| 2640 |  | 
| 2641 |     QTransform m = b.transform() * matrix; | 
| 2642 |     int shaderObject = generateGradientShader(gradient, m); | 
| 2643 |  | 
| 2644 |     QByteArray str; | 
| 2645 |     QPdf::ByteStream s(&str); | 
| 2646 |     s << "<<\n"  | 
| 2647 |         "/Type /Pattern\n"  | 
| 2648 |         "/PatternType 2\n"  | 
| 2649 |         "/Shading "  << shaderObject << "0 R\n"  | 
| 2650 |         "/Matrix ["  | 
| 2651 |       << m.m11() | 
| 2652 |       << m.m12() | 
| 2653 |       << m.m21() | 
| 2654 |       << m.m22() | 
| 2655 |       << m.dx() | 
| 2656 |       << m.dy() << "]\n" ; | 
| 2657 |     s << ">>\n"  | 
| 2658 |         "endobj\n" ; | 
| 2659 |  | 
| 2660 |     int patternObj = addXrefEntry(-1); | 
| 2661 |     write(str); | 
| 2662 |     currentPage->patterns.append(patternObj); | 
| 2663 |  | 
| 2664 |     if (!b.isOpaque()) { | 
| 2665 |         bool ca = true; | 
| 2666 |         QGradientStops stops = gradient->stops(); | 
| 2667 |         int a = stops.at(0).second.alpha(); | 
| 2668 |         for (int i = 1; i < stops.size(); ++i) { | 
| 2669 |             if (stops.at(i).second.alpha() != a) { | 
| 2670 |                 ca = false; | 
| 2671 |                 break; | 
| 2672 |             } | 
| 2673 |         } | 
| 2674 |         if (ca) { | 
| 2675 |             *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha()); | 
| 2676 |         } else { | 
| 2677 |             int alphaShaderObject = generateGradientShader(gradient, m, true); | 
| 2678 |  | 
| 2679 |             QByteArray content; | 
| 2680 |             QPdf::ByteStream c(&content); | 
| 2681 |             c << "/Shader"  << alphaShaderObject << "sh\n" ; | 
| 2682 |  | 
| 2683 |             QByteArray form; | 
| 2684 |             QPdf::ByteStream f(&form); | 
| 2685 |             f << "<<\n"  | 
| 2686 |                 "/Type /XObject\n"  | 
| 2687 |                 "/Subtype /Form\n"  | 
| 2688 |                 "/BBox [0 0 "  << pageRect.width() << pageRect.height() << "]\n"  | 
| 2689 |                 "/Group <</S /Transparency >>\n"  | 
| 2690 |                 "/Resources <<\n"  | 
| 2691 |                 "/Shading << /Shader"  << alphaShaderObject << alphaShaderObject << "0 R >>\n"  | 
| 2692 |                 ">>\n" ; | 
| 2693 |  | 
| 2694 |             f << "/Length "  << content.length() << "\n"  | 
| 2695 |                 ">>\n"  | 
| 2696 |                 "stream\n"  | 
| 2697 |               << content | 
| 2698 |               << "\nendstream\n"  | 
| 2699 |                 "endobj\n" ; | 
| 2700 |  | 
| 2701 |             int softMaskFormObject = addXrefEntry(-1); | 
| 2702 |             write(form); | 
| 2703 |             *gStateObject = addXrefEntry(-1); | 
| 2704 |             xprintf("<< /SMask << /S /Alpha /G %d 0 R >> >>\n"  | 
| 2705 |                     "endobj\n" , softMaskFormObject); | 
| 2706 |             currentPage->graphicStates.append(*gStateObject); | 
| 2707 |         } | 
| 2708 |     } | 
| 2709 |  | 
| 2710 |     return patternObj; | 
| 2711 | } | 
| 2712 |  | 
| 2713 | int QPdfEnginePrivate::addConstantAlphaObject(int brushAlpha, int penAlpha) | 
| 2714 | { | 
| 2715 |     if (brushAlpha == 255 && penAlpha == 255) | 
| 2716 |         return 0; | 
| 2717 |     uint object = alphaCache.value(QPair<uint, uint>(brushAlpha, penAlpha), 0); | 
| 2718 |     if (!object) { | 
| 2719 |         object = addXrefEntry(-1); | 
| 2720 |         QByteArray alphaDef; | 
| 2721 |         QPdf::ByteStream s(&alphaDef); | 
| 2722 |         s << "<<\n/ca "  << (brushAlpha/qreal(255.)) << '\n'; | 
| 2723 |         s << "/CA "  << (penAlpha/qreal(255.)) << "\n>>" ; | 
| 2724 |         xprintf("%s\nendobj\n" , alphaDef.constData()); | 
| 2725 |         alphaCache.insert(QPair<uint, uint>(brushAlpha, penAlpha), object); | 
| 2726 |     } | 
| 2727 |     if (currentPage->graphicStates.indexOf(object) < 0) | 
| 2728 |         currentPage->graphicStates.append(object); | 
| 2729 |  | 
| 2730 |     return object; | 
| 2731 | } | 
| 2732 |  | 
| 2733 |  | 
| 2734 | int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, int *gStateObject) | 
| 2735 | { | 
| 2736 |     Q_Q(QPdfEngine); | 
| 2737 |  | 
| 2738 |     int paintType = 2; // Uncolored tiling | 
| 2739 |     int w = 8; | 
| 2740 |     int h = 8; | 
| 2741 |  | 
| 2742 |     *specifyColor = true; | 
| 2743 |     *gStateObject = 0; | 
| 2744 |  | 
| 2745 |     QTransform matrix = m; | 
| 2746 |     matrix.translate(brushOrigin.x(), brushOrigin.y()); | 
| 2747 |     matrix = matrix * pageMatrix(); | 
| 2748 |     //qDebug() << brushOrigin << matrix; | 
| 2749 |  | 
| 2750 |     Qt::BrushStyle style = brush.style(); | 
| 2751 |     if (style == Qt::LinearGradientPattern || style == Qt::RadialGradientPattern) {// && style <= Qt::ConicalGradientPattern) { | 
| 2752 |         *specifyColor = false; | 
| 2753 |         return gradientBrush(brush, matrix, gStateObject); | 
| 2754 |     } | 
| 2755 |  | 
| 2756 |     if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0) | 
| 2757 |         *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity), | 
| 2758 |                                                qRound(pen.color().alpha() * opacity)); | 
| 2759 |  | 
| 2760 |     int imageObject = -1; | 
| 2761 |     QByteArray pattern = QPdf::patternForBrush(brush); | 
| 2762 |     if (pattern.isEmpty()) { | 
| 2763 |         if (brush.style() != Qt::TexturePattern) | 
| 2764 |             return 0; | 
| 2765 |         QImage image = brush.textureImage(); | 
| 2766 |         bool bitmap = true; | 
| 2767 |         const bool lossless = q->painter()->testRenderHint(QPainter::LosslessImageRendering); | 
| 2768 |         imageObject = addImage(image, &bitmap, lossless, image.cacheKey()); | 
| 2769 |         if (imageObject != -1) { | 
| 2770 |             QImage::Format f = image.format(); | 
| 2771 |             if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) { | 
| 2772 |                 paintType = 1; // Colored tiling | 
| 2773 |                 *specifyColor = false; | 
| 2774 |             } | 
| 2775 |             w = image.width(); | 
| 2776 |             h = image.height(); | 
| 2777 |             QTransform m(w, 0, 0, -h, 0, h); | 
| 2778 |             QPdf::ByteStream s(&pattern); | 
| 2779 |             s << QPdf::generateMatrix(m); | 
| 2780 |             s << "/Im"  << imageObject << " Do\n" ; | 
| 2781 |         } | 
| 2782 |     } | 
| 2783 |  | 
| 2784 |     QByteArray str; | 
| 2785 |     QPdf::ByteStream s(&str); | 
| 2786 |     s << "<<\n"  | 
| 2787 |         "/Type /Pattern\n"  | 
| 2788 |         "/PatternType 1\n"  | 
| 2789 |         "/PaintType "  << paintType << "\n"  | 
| 2790 |         "/TilingType 1\n"  | 
| 2791 |         "/BBox [0 0 "  << w << h << "]\n"  | 
| 2792 |         "/XStep "  << w << "\n"  | 
| 2793 |         "/YStep "  << h << "\n"  | 
| 2794 |         "/Matrix ["  | 
| 2795 |       << matrix.m11() | 
| 2796 |       << matrix.m12() | 
| 2797 |       << matrix.m21() | 
| 2798 |       << matrix.m22() | 
| 2799 |       << matrix.dx() | 
| 2800 |       << matrix.dy() << "]\n"  | 
| 2801 |         "/Resources \n<< " ; // open resource tree | 
| 2802 |     if (imageObject > 0) { | 
| 2803 |         s << "/XObject << /Im"  << imageObject << ' ' << imageObject << "0 R >> " ; | 
| 2804 |     } | 
| 2805 |     s << ">>\n"  | 
| 2806 |         "/Length "  << pattern.length() << "\n"  | 
| 2807 |         ">>\n"  | 
| 2808 |         "stream\n"  | 
| 2809 |       << pattern | 
| 2810 |       << "\nendstream\n"  | 
| 2811 |         "endobj\n" ; | 
| 2812 |  | 
| 2813 |     int patternObj = addXrefEntry(-1); | 
| 2814 |     write(str); | 
| 2815 |     currentPage->patterns.append(patternObj); | 
| 2816 |     return patternObj; | 
| 2817 | } | 
| 2818 |  | 
| 2819 | static inline bool is_monochrome(const QList<QRgb> &colorTable) | 
| 2820 | { | 
| 2821 |     return colorTable.size() == 2 | 
| 2822 |         && colorTable.at(0) == QColor(Qt::black).rgba() | 
| 2823 |         && colorTable.at(1) == QColor(Qt::white).rgba() | 
| 2824 |         ; | 
| 2825 | } | 
| 2826 |  | 
| 2827 | /*! | 
| 2828 |  * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed. | 
| 2829 |  */ | 
| 2830 | int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless, qint64 serial_no) | 
| 2831 | { | 
| 2832 |     if (img.isNull()) | 
| 2833 |         return -1; | 
| 2834 |  | 
| 2835 |     int object = imageCache.value(serial_no); | 
| 2836 |     if(object) | 
| 2837 |         return object; | 
| 2838 |  | 
| 2839 |     QImage image = img; | 
| 2840 |     QImage::Format format = image.format(); | 
| 2841 |  | 
| 2842 |     if (pdfVersion == QPdfEngine::Version_A1b) { | 
| 2843 |         if (image.hasAlphaChannel()) { | 
| 2844 |             // transparent images are not allowed in PDF/A-1b, so we convert it to | 
| 2845 |             // a format without alpha channel first | 
| 2846 |  | 
| 2847 |             QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32); | 
| 2848 |             alphaLessImage.fill(Qt::white); | 
| 2849 |  | 
| 2850 |             QPainter p(&alphaLessImage); | 
| 2851 |             p.drawImage(0, 0, image); | 
| 2852 |  | 
| 2853 |             image = alphaLessImage; | 
| 2854 |             format = image.format(); | 
| 2855 |         } | 
| 2856 |     } | 
| 2857 |  | 
| 2858 |     if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) { | 
| 2859 |         if (format == QImage::Format_MonoLSB) | 
| 2860 |             image = image.convertToFormat(QImage::Format_Mono); | 
| 2861 |         format = QImage::Format_Mono; | 
| 2862 |     } else { | 
| 2863 |         *bitmap = false; | 
| 2864 |         if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) { | 
| 2865 |             image = image.convertToFormat(QImage::Format_ARGB32); | 
| 2866 |             format = QImage::Format_ARGB32; | 
| 2867 |         } | 
| 2868 |     } | 
| 2869 |  | 
| 2870 |     int w = image.width(); | 
| 2871 |     int h = image.height(); | 
| 2872 |     int d = image.depth(); | 
| 2873 |  | 
| 2874 |     if (format == QImage::Format_Mono) { | 
| 2875 |         int bytesPerLine = (w + 7) >> 3; | 
| 2876 |         QByteArray data; | 
| 2877 |         data.resize(bytesPerLine * h); | 
| 2878 |         char *rawdata = data.data(); | 
| 2879 |         for (int y = 0; y < h; ++y) { | 
| 2880 |             memcpy(rawdata, image.constScanLine(y), bytesPerLine); | 
| 2881 |             rawdata += bytesPerLine; | 
| 2882 |         } | 
| 2883 |         object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable())); | 
| 2884 |     } else { | 
| 2885 |         QByteArray softMaskData; | 
| 2886 |         bool dct = false; | 
| 2887 |         QByteArray imageData; | 
| 2888 |         bool hasAlpha = false; | 
| 2889 |         bool hasMask = false; | 
| 2890 |  | 
| 2891 |         if (QImageWriter::supportedImageFormats().contains("jpeg" ) && !grayscale && !lossless) { | 
| 2892 |             QBuffer buffer(&imageData); | 
| 2893 |             QImageWriter writer(&buffer, "jpeg" ); | 
| 2894 |             writer.setQuality(94); | 
| 2895 |             writer.write(image); | 
| 2896 |             dct = true; | 
| 2897 |  | 
| 2898 |             if (format != QImage::Format_RGB32) { | 
| 2899 |                 softMaskData.resize(w * h); | 
| 2900 |                 uchar *sdata = (uchar *)softMaskData.data(); | 
| 2901 |                 for (int y = 0; y < h; ++y) { | 
| 2902 |                     const QRgb *rgb = (const QRgb *)image.constScanLine(y); | 
| 2903 |                     for (int x = 0; x < w; ++x) { | 
| 2904 |                         uchar alpha = qAlpha(*rgb); | 
| 2905 |                         *sdata++ = alpha; | 
| 2906 |                         hasMask |= (alpha < 255); | 
| 2907 |                         hasAlpha |= (alpha != 0 && alpha != 255); | 
| 2908 |                         ++rgb; | 
| 2909 |                     } | 
| 2910 |                 } | 
| 2911 |             } | 
| 2912 |         } else { | 
| 2913 |             imageData.resize(grayscale ? w * h : 3 * w * h); | 
| 2914 |             uchar *data = (uchar *)imageData.data(); | 
| 2915 |             softMaskData.resize(w * h); | 
| 2916 |             uchar *sdata = (uchar *)softMaskData.data(); | 
| 2917 |             for (int y = 0; y < h; ++y) { | 
| 2918 |                 const QRgb *rgb = (const QRgb *)image.constScanLine(y); | 
| 2919 |                 if (grayscale) { | 
| 2920 |                     for (int x = 0; x < w; ++x) { | 
| 2921 |                         *(data++) = qGray(*rgb); | 
| 2922 |                         uchar alpha = qAlpha(*rgb); | 
| 2923 |                         *sdata++ = alpha; | 
| 2924 |                         hasMask |= (alpha < 255); | 
| 2925 |                         hasAlpha |= (alpha != 0 && alpha != 255); | 
| 2926 |                         ++rgb; | 
| 2927 |                     } | 
| 2928 |                 } else { | 
| 2929 |                     for (int x = 0; x < w; ++x) { | 
| 2930 |                         *(data++) = qRed(*rgb); | 
| 2931 |                         *(data++) = qGreen(*rgb); | 
| 2932 |                         *(data++) = qBlue(*rgb); | 
| 2933 |                         uchar alpha = qAlpha(*rgb); | 
| 2934 |                         *sdata++ = alpha; | 
| 2935 |                         hasMask |= (alpha < 255); | 
| 2936 |                         hasAlpha |= (alpha != 0 && alpha != 255); | 
| 2937 |                         ++rgb; | 
| 2938 |                     } | 
| 2939 |                 } | 
| 2940 |             } | 
| 2941 |             if (format == QImage::Format_RGB32) | 
| 2942 |                 hasAlpha = hasMask = false; | 
| 2943 |         } | 
| 2944 |         int maskObject = 0; | 
| 2945 |         int softMaskObject = 0; | 
| 2946 |         if (hasAlpha) { | 
| 2947 |             softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0); | 
| 2948 |         } else if (hasMask) { | 
| 2949 |             // dither the soft mask to 1bit and add it. This also helps PDF viewers | 
| 2950 |             // without transparency support | 
| 2951 |             int bytesPerLine = (w + 7) >> 3; | 
| 2952 |             QByteArray mask(bytesPerLine * h, 0); | 
| 2953 |             uchar *mdata = (uchar *)mask.data(); | 
| 2954 |             const uchar *sdata = (const uchar *)softMaskData.constData(); | 
| 2955 |             for (int y = 0; y < h; ++y) { | 
| 2956 |                 for (int x = 0; x < w; ++x) { | 
| 2957 |                     if (*sdata) | 
| 2958 |                         mdata[x>>3] |= (0x80 >> (x&7)); | 
| 2959 |                     ++sdata; | 
| 2960 |                 } | 
| 2961 |                 mdata += bytesPerLine; | 
| 2962 |             } | 
| 2963 |             maskObject = writeImage(mask, w, h, 1, 0, 0); | 
| 2964 |         } | 
| 2965 |         object = writeImage(imageData, w, h, grayscale ? 8 : 32, | 
| 2966 |                             maskObject, softMaskObject, dct); | 
| 2967 |     } | 
| 2968 |     imageCache.insert(serial_no, object); | 
| 2969 |     return object; | 
| 2970 | } | 
| 2971 |  | 
| 2972 | void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) | 
| 2973 | { | 
| 2974 |     Q_Q(QPdfEngine); | 
| 2975 |  | 
| 2976 |     if (ti.charFormat.isAnchor()) { | 
| 2977 |         qreal size = ti.fontEngine->fontDef.pixelSize; | 
| 2978 |         int synthesized = ti.fontEngine->synthesized(); | 
| 2979 |         qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; | 
| 2980 |         Q_ASSERT(stretch > qreal(0)); | 
| 2981 |  | 
| 2982 |         QTransform trans; | 
| 2983 |         // Build text rendering matrix (Trm). We need it to map the text area to user | 
| 2984 |         // space units on the PDF page. | 
| 2985 |         trans = QTransform(size*stretch, 0, 0, size, 0, 0); | 
| 2986 |         // Apply text matrix (Tm). | 
| 2987 |         trans *= QTransform(1,0,0,-1,p.x(),p.y()); | 
| 2988 |         // Apply page displacement (Identity for first page). | 
| 2989 |         trans *= stroker.matrix; | 
| 2990 |         // Apply Current Transformation Matrix (CTM) | 
| 2991 |         trans *= pageMatrix(); | 
| 2992 |         qreal x1, y1, x2, y2; | 
| 2993 |         trans.map(0, 0, &x1, &y1); | 
| 2994 |         trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2); | 
| 2995 |  | 
| 2996 |         uint annot = addXrefEntry(-1); | 
| 2997 |         QByteArray x1s, y1s, x2s, y2s; | 
| 2998 |         x1s.setNum(static_cast<double>(x1), 'f'); | 
| 2999 |         y1s.setNum(static_cast<double>(y1), 'f'); | 
| 3000 |         x2s.setNum(static_cast<double>(x2), 'f'); | 
| 3001 |         y2s.setNum(static_cast<double>(y2), 'f'); | 
| 3002 |         QByteArray rectData = x1s + ' ' + y1s + ' ' + x2s + ' ' + y2s; | 
| 3003 |         xprintf("<<\n/Type /Annot\n/Subtype /Link\n" ); | 
| 3004 |  | 
| 3005 |         if (pdfVersion == QPdfEngine::Version_A1b) | 
| 3006 |             xprintf("/F 4\n" ); // enable print flag, disable all other | 
| 3007 |  | 
| 3008 |         xprintf("/Rect [" ); | 
| 3009 |         xprintf(rectData.constData()); | 
| 3010 | #ifdef Q_DEBUG_PDF_LINKS | 
| 3011 |         xprintf("]\n/Border [16 16 1]\n/A <<\n" ); | 
| 3012 | #else | 
| 3013 |         xprintf("]\n/Border [0 0 0]\n/A <<\n" ); | 
| 3014 | #endif | 
| 3015 |         xprintf("/Type /Action\n/S /URI\n/URI (%s)\n" , | 
| 3016 |                 ti.charFormat.anchorHref().toLatin1().constData()); | 
| 3017 |         xprintf(">>\n>>\n" ); | 
| 3018 |         xprintf("endobj\n" ); | 
| 3019 |  | 
| 3020 |         if (!currentPage->annotations.contains(annot)) { | 
| 3021 |             currentPage->annotations.append(annot); | 
| 3022 |         } | 
| 3023 |     } | 
| 3024 |  | 
| 3025 |     QFontEngine *fe = ti.fontEngine; | 
| 3026 |  | 
| 3027 |     QFontEngine::FaceId face_id = fe->faceId(); | 
| 3028 |     bool noEmbed = false; | 
| 3029 |     if (!embedFonts | 
| 3030 |         || face_id.filename.isEmpty() | 
| 3031 |         || fe->fsType & 0x200 /* bitmap embedding only */ | 
| 3032 |         || fe->fsType == 2 /* no embedding allowed */) { | 
| 3033 |         *currentPage << "Q\n" ; | 
| 3034 |         q->QPaintEngine::drawTextItem(p, ti); | 
| 3035 |         *currentPage << "q\n" ; | 
| 3036 |         if (face_id.filename.isEmpty()) | 
| 3037 |             return; | 
| 3038 |         noEmbed = true; | 
| 3039 |     } | 
| 3040 |  | 
| 3041 |     QFontSubset *font = fonts.value(face_id, nullptr); | 
| 3042 |     if (!font) { | 
| 3043 |         font = new QFontSubset(fe, requestObject()); | 
| 3044 |         font->noEmbed = noEmbed; | 
| 3045 |     } | 
| 3046 |     fonts.insert(face_id, font); | 
| 3047 |  | 
| 3048 |     if (!currentPage->fonts.contains(font->object_id)) | 
| 3049 |         currentPage->fonts.append(font->object_id); | 
| 3050 |  | 
| 3051 |     qreal size = ti.fontEngine->fontDef.pixelSize; | 
| 3052 |  | 
| 3053 |     QVarLengthArray<glyph_t> glyphs; | 
| 3054 |     QVarLengthArray<QFixedPoint> positions; | 
| 3055 |     QTransform m = QTransform::fromTranslate(p.x(), p.y()); | 
| 3056 |     ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags, | 
| 3057 |                                      glyphs, positions); | 
| 3058 |     if (glyphs.size() == 0) | 
| 3059 |         return; | 
| 3060 |     int synthesized = ti.fontEngine->synthesized(); | 
| 3061 |     qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; | 
| 3062 |     Q_ASSERT(stretch > qreal(0)); | 
| 3063 |  | 
| 3064 |     *currentPage << "BT\n"  | 
| 3065 |                  << "/F"  << font->object_id << size << "Tf "  | 
| 3066 |                  << stretch << (synthesized & QFontEngine::SynthesizedItalic | 
| 3067 |                                 ? "0 .3 -1 0 0 Tm\n"  | 
| 3068 |                                 : "0 0 -1 0 0 Tm\n" ); | 
| 3069 |  | 
| 3070 |  | 
| 3071 | #if 0 | 
| 3072 |     // #### implement actual text for complex languages | 
| 3073 |     const unsigned short *logClusters = ti.logClusters; | 
| 3074 |     int pos = 0; | 
| 3075 |     do { | 
| 3076 |         int end = pos + 1; | 
| 3077 |         while (end < ti.num_chars && logClusters[end] == logClusters[pos]) | 
| 3078 |             ++end; | 
| 3079 |         *currentPage << "/Span << /ActualText <FEFF" ; | 
| 3080 |         for (int i = pos; i < end; ++i) { | 
| 3081 |             s << toHex((ushort)ti.chars[i].unicode(), buf); | 
| 3082 |         } | 
| 3083 |         *currentPage << "> >>\n"  | 
| 3084 |             "BDC\n"  | 
| 3085 |             "<" ; | 
| 3086 |         int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end]; | 
| 3087 |         for (int gs = logClusters[pos]; gs < ge; ++gs) | 
| 3088 |             *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf); | 
| 3089 |         *currentPage << "> Tj\n"  | 
| 3090 |             "EMC\n" ; | 
| 3091 |         pos = end; | 
| 3092 |     } while (pos < ti.num_chars); | 
| 3093 | #else | 
| 3094 |     qreal last_x = 0.; | 
| 3095 |     qreal last_y = 0.; | 
| 3096 |     for (int i = 0; i < glyphs.size(); ++i) { | 
| 3097 |         qreal x = positions[i].x.toReal(); | 
| 3098 |         qreal y = positions[i].y.toReal(); | 
| 3099 |         if (synthesized & QFontEngine::SynthesizedItalic) | 
| 3100 |             x += .3*y; | 
| 3101 |         x /= stretch; | 
| 3102 |         char buf[5]; | 
| 3103 |         int g = font->addGlyph(glyphs[i]); | 
| 3104 |         *currentPage << x - last_x << last_y - y << "Td <"  | 
| 3105 |                      << QPdf::toHex((ushort)g, buf) << "> Tj\n" ; | 
| 3106 |         last_x = x; | 
| 3107 |         last_y = y; | 
| 3108 |     } | 
| 3109 |     if (synthesized & QFontEngine::SynthesizedBold) { | 
| 3110 |         *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic | 
| 3111 |                             ? "0 .3 -1 0 0 Tm\n"  | 
| 3112 |                             : "0 0 -1 0 0 Tm\n" ); | 
| 3113 |         *currentPage << "/Span << /ActualText <> >> BDC\n" ; | 
| 3114 |         last_x = 0.5*fe->lineThickness().toReal(); | 
| 3115 |         last_y = 0.; | 
| 3116 |         for (int i = 0; i < glyphs.size(); ++i) { | 
| 3117 |             qreal x = positions[i].x.toReal(); | 
| 3118 |             qreal y = positions[i].y.toReal(); | 
| 3119 |             if (synthesized & QFontEngine::SynthesizedItalic) | 
| 3120 |                 x += .3*y; | 
| 3121 |             x /= stretch; | 
| 3122 |             char buf[5]; | 
| 3123 |             int g = font->addGlyph(glyphs[i]); | 
| 3124 |             *currentPage << x - last_x << last_y - y << "Td <"  | 
| 3125 |                         << QPdf::toHex((ushort)g, buf) << "> Tj\n" ; | 
| 3126 |             last_x = x; | 
| 3127 |             last_y = y; | 
| 3128 |         } | 
| 3129 |         *currentPage << "EMC\n" ; | 
| 3130 |     } | 
| 3131 | #endif | 
| 3132 |  | 
| 3133 |     *currentPage << "ET\n" ; | 
| 3134 | } | 
| 3135 |  | 
| 3136 | QTransform QPdfEnginePrivate::pageMatrix() const | 
| 3137 | { | 
| 3138 |     qreal userUnit = calcUserUnit(); | 
| 3139 |     qreal scale = 72. / userUnit / resolution; | 
| 3140 |     QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit); | 
| 3141 |     if (m_pageLayout.mode() != QPageLayout::FullPageMode) { | 
| 3142 |         QRect r = m_pageLayout.paintRectPixels(resolution); | 
| 3143 |         tmp.translate(r.left(), r.top()); | 
| 3144 |     } | 
| 3145 |     return tmp; | 
| 3146 | } | 
| 3147 |  | 
| 3148 | void QPdfEnginePrivate::newPage() | 
| 3149 | { | 
| 3150 |     if (currentPage && currentPage->pageSize.isEmpty()) | 
| 3151 |         currentPage->pageSize = m_pageLayout.fullRectPoints().size(); | 
| 3152 |     writePage(); | 
| 3153 |  | 
| 3154 |     delete currentPage; | 
| 3155 |     currentPage = new QPdfPage; | 
| 3156 |     currentPage->pageSize = m_pageLayout.fullRectPoints().size(); | 
| 3157 |     stroker.stream = currentPage; | 
| 3158 |     pages.append(requestObject()); | 
| 3159 |  | 
| 3160 |     *currentPage << "/GSa gs /CSp cs /CSp CS\n"  | 
| 3161 |                  << QPdf::generateMatrix(pageMatrix()) | 
| 3162 |                  << "q q\n" ; | 
| 3163 | } | 
| 3164 |  | 
| 3165 | QT_END_NAMESPACE | 
| 3166 |  | 
| 3167 | #endif // QT_NO_PDF | 
| 3168 |  |