1 /* poppler-document.cc: qt interface to poppler 2 * Copyright (C) 2005, Net Integration Technologies, Inc. 3 * Copyright (C) 2005, 2008, Brad Hards <bradh@frogmouth.net> 4 * Copyright (C) 2005-2010, 2012, 2013, 2015, Albert Astals Cid <aacid@kde.org> 5 * Copyright (C) 2006-2010, Pino Toscano <pino@kde.org> 6 * Copyright (C) 2010, 2011 Hib Eris <hib@hiberis.nl> 7 * Copyright (C) 2012 Koji Otani <sho@bbr.jp> 8 * Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de> 9 * Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it> 10 * Copyright (C) 2014 Adam Reichold <adamreichold@myopera.com> 11 * Copyright (C) 2015 William Bader <williambader@hotmail.com> 12 * 13 * This program is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License as published by 15 * the Free Software Foundation; either version 2, or (at your option) 16 * any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License 24 * along with this program; if not, write to the Free Software 25 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. 26 */ 27 28 #include "poppler-qt5.h" 29 30 #include <config.h> 31 #include <ErrorCodes.h> 32 #include <GlobalParams.h> 33 #include <Outline.h> 34 #include <PDFDoc.h> 35 #include <Stream.h> 36 #include <Catalog.h> 37 #include <ViewerPreferences.h> 38 #include <DateInfo.h> 39 #include <GfxState.h> 40 41 #include <QtCore/QDebug> 42 #include <QtCore/QFile> 43 #include <QtCore/QByteArray> 44 45 #include "poppler-private.h" 46 #include "poppler-page-private.h" 47 48 #if defined(USE_CMS) 49 #if defined(USE_LCMS1) 50 #include <lcms.h> 51 #else 52 #include <lcms2.h> 53 #endif 54 #endif 55 56 namespace Poppler { 57 58 int DocumentData::count = 0; 59 load(const QString & filePath,const QByteArray & ownerPassword,const QByteArray & userPassword)60 Document *Document::load(const QString &filePath, const QByteArray &ownerPassword, 61 const QByteArray &userPassword) 62 { 63 DocumentData *doc = new DocumentData(filePath, 64 new GooString(ownerPassword.data()), 65 new GooString(userPassword.data())); 66 return DocumentData::checkDocument(doc); 67 } 68 loadFromData(const QByteArray & fileContents,const QByteArray & ownerPassword,const QByteArray & userPassword)69 Document *Document::loadFromData(const QByteArray &fileContents, 70 const QByteArray &ownerPassword, 71 const QByteArray &userPassword) 72 { 73 // create stream 74 DocumentData *doc = new DocumentData(fileContents, 75 new GooString(ownerPassword.data()), 76 new GooString(userPassword.data())); 77 return DocumentData::checkDocument(doc); 78 } 79 checkDocument(DocumentData * doc)80 Document *DocumentData::checkDocument(DocumentData *doc) 81 { 82 Document *pdoc; 83 if (doc->doc->isOk() || doc->doc->getErrorCode() == errEncrypted) { 84 pdoc = new Document(doc); 85 if (doc->doc->getErrorCode() == errEncrypted) 86 pdoc->m_doc->locked = true; 87 else 88 { 89 pdoc->m_doc->locked = false; 90 pdoc->m_doc->fillMembers(); 91 } 92 return pdoc; 93 } 94 else 95 { 96 delete doc; 97 } 98 return NULL; 99 } 100 Document(DocumentData * dataA)101 Document::Document(DocumentData *dataA) 102 { 103 m_doc = dataA; 104 } 105 ~Document()106 Document::~Document() 107 { 108 delete m_doc; 109 } 110 page(int index) const111 Page *Document::page(int index) const 112 { 113 Page *page = new Page(m_doc, index); 114 if (page->m_page->page == NULL) { 115 delete page; 116 return NULL; 117 } 118 119 return page; 120 } 121 isLocked() const122 bool Document::isLocked() const 123 { 124 return m_doc->locked; 125 } 126 unlock(const QByteArray & ownerPassword,const QByteArray & userPassword)127 bool Document::unlock(const QByteArray &ownerPassword, 128 const QByteArray &userPassword) 129 { 130 if (m_doc->locked) { 131 /* racier then it needs to be */ 132 DocumentData *doc2; 133 if (!m_doc->fileContents.isEmpty()) 134 { 135 doc2 = new DocumentData(m_doc->fileContents, 136 new GooString(ownerPassword.data()), 137 new GooString(userPassword.data())); 138 } 139 else 140 { 141 doc2 = new DocumentData(m_doc->m_filePath, 142 new GooString(ownerPassword.data()), 143 new GooString(userPassword.data())); 144 } 145 if (!doc2->doc->isOk()) { 146 delete doc2; 147 } else { 148 delete m_doc; 149 m_doc = doc2; 150 m_doc->locked = false; 151 m_doc->fillMembers(); 152 } 153 } 154 return m_doc->locked; 155 } 156 pageMode() const157 Document::PageMode Document::pageMode() const 158 { 159 switch (m_doc->doc->getCatalog()->getPageMode()) { 160 case Catalog::pageModeNone: 161 return UseNone; 162 case Catalog::pageModeOutlines: 163 return UseOutlines; 164 case Catalog::pageModeThumbs: 165 return UseThumbs; 166 case Catalog::pageModeFullScreen: 167 return FullScreen; 168 case Catalog::pageModeOC: 169 return UseOC; 170 case Catalog::pageModeAttach: 171 return UseAttach; 172 default: 173 return UseNone; 174 } 175 } 176 pageLayout() const177 Document::PageLayout Document::pageLayout() const 178 { 179 switch (m_doc->doc->getCatalog()->getPageLayout()) { 180 case Catalog::pageLayoutNone: 181 return NoLayout; 182 case Catalog::pageLayoutSinglePage: 183 return SinglePage; 184 case Catalog::pageLayoutOneColumn: 185 return OneColumn; 186 case Catalog::pageLayoutTwoColumnLeft: 187 return TwoColumnLeft; 188 case Catalog::pageLayoutTwoColumnRight: 189 return TwoColumnRight; 190 case Catalog::pageLayoutTwoPageLeft: 191 return TwoPageLeft; 192 case Catalog::pageLayoutTwoPageRight: 193 return TwoPageRight; 194 default: 195 return NoLayout; 196 } 197 } 198 textDirection() const199 Qt::LayoutDirection Document::textDirection() const 200 { 201 if (!m_doc->doc->getCatalog()->getViewerPreferences()) 202 return Qt::LayoutDirectionAuto; 203 204 switch (m_doc->doc->getCatalog()->getViewerPreferences()->getDirection()) { 205 case ViewerPreferences::directionL2R: 206 return Qt::LeftToRight; 207 case ViewerPreferences::directionR2L: 208 return Qt::RightToLeft; 209 default: 210 return Qt::LayoutDirectionAuto; 211 } 212 } 213 numPages() const214 int Document::numPages() const 215 { 216 return m_doc->doc->getNumPages(); 217 } 218 fonts() const219 QList<FontInfo> Document::fonts() const 220 { 221 QList<FontInfo> ourList; 222 FontIterator it( 0, m_doc ); 223 while ( it.hasNext() ) 224 { 225 ourList += it.next(); 226 } 227 return ourList; 228 } 229 embeddedFiles() const230 QList<EmbeddedFile*> Document::embeddedFiles() const 231 { 232 return m_doc->m_embeddedFiles; 233 } 234 newFontIterator(int startPage) const235 FontIterator* Document::newFontIterator( int startPage ) const 236 { 237 return new FontIterator( startPage, m_doc ); 238 } 239 fontData(const FontInfo & fi) const240 QByteArray Document::fontData(const FontInfo &fi) const 241 { 242 QByteArray result; 243 if (fi.isEmbedded()) 244 { 245 Object refObj, strObj; 246 XRef *xref = m_doc->doc->getXRef()->copy(); 247 248 refObj.initRef(fi.m_data->embRef.num, fi.m_data->embRef.gen); 249 refObj.fetch(xref, &strObj); 250 refObj.free(); 251 if (strObj.isStream()) 252 { 253 int c; 254 strObj.streamReset(); 255 while ((c = strObj.streamGetChar()) != EOF) 256 { 257 result.append((char)c); 258 } 259 strObj.streamClose(); 260 } 261 strObj.free(); 262 delete xref; 263 } 264 return result; 265 } 266 267 /* borrowed from kpdf */ info(const QString & type) const268 QString Document::info( const QString & type ) const 269 { 270 // [Albert] Code adapted from pdfinfo.cc on xpdf 271 Object info; 272 if ( m_doc->locked ) 273 return QString(); 274 275 QScopedPointer<XRef> xref(m_doc->doc->getXRef()->copy()); 276 if (!xref) 277 return QString(); 278 xref->getDocInfo(&info); 279 if ( !info.isDict() ) 280 return QString(); 281 282 QString result; 283 Object obj; 284 GooString *s1; 285 Dict *infoDict = info.getDict(); 286 287 if ( infoDict->lookup( type.toLatin1().data(), &obj )->isString() ) 288 { 289 s1 = obj.getString(); 290 result = UnicodeParsedString(s1); 291 obj.free(); 292 info.free(); 293 return result; 294 } 295 obj.free(); 296 info.free(); 297 return QString(); 298 } 299 infoKeys() const300 QStringList Document::infoKeys() const 301 { 302 QStringList keys; 303 304 Object info; 305 if ( m_doc->locked ) 306 return QStringList(); 307 308 QScopedPointer<XRef> xref(m_doc->doc->getXRef()->copy()); 309 if (!xref) 310 return QStringList(); 311 xref->getDocInfo(&info); 312 if ( !info.isDict() ) 313 return QStringList(); 314 315 Dict *infoDict = info.getDict(); 316 // somehow iterate over keys in infoDict 317 keys.reserve(infoDict->getLength()); 318 for( int i=0; i < infoDict->getLength(); ++i ) { 319 keys.append( QString::fromLatin1(infoDict->getKey(i)) ); 320 } 321 322 info.free(); 323 return keys; 324 } 325 326 /* borrowed from kpdf */ date(const QString & type) const327 QDateTime Document::date( const QString & type ) const 328 { 329 // [Albert] Code adapted from pdfinfo.cc on xpdf 330 if ( m_doc->locked ) 331 return QDateTime(); 332 333 Object info; 334 QScopedPointer<XRef> xref(m_doc->doc->getXRef()->copy()); 335 if (!xref) 336 return QDateTime(); 337 xref->getDocInfo(&info); 338 if ( !info.isDict() ) { 339 info.free(); 340 return QDateTime(); 341 } 342 343 Object obj; 344 Dict *infoDict = info.getDict(); 345 QDateTime result; 346 347 if ( infoDict->lookup( type.toLatin1().data(), &obj )->isString() ) 348 { 349 char *aux = obj.getString()->getCString(); 350 result = Poppler::convertDate(aux); 351 } 352 obj.free(); 353 info.free(); 354 return result; 355 } 356 isEncrypted() const357 bool Document::isEncrypted() const 358 { 359 return m_doc->doc->isEncrypted(); 360 } 361 isLinearized() const362 bool Document::isLinearized() const 363 { 364 return m_doc->doc->isLinearized(); 365 } 366 okToPrint() const367 bool Document::okToPrint() const 368 { 369 return m_doc->doc->okToPrint(); 370 } 371 okToPrintHighRes() const372 bool Document::okToPrintHighRes() const 373 { 374 return m_doc->doc->okToPrintHighRes(); 375 } 376 okToChange() const377 bool Document::okToChange() const 378 { 379 return m_doc->doc->okToChange(); 380 } 381 okToCopy() const382 bool Document::okToCopy() const 383 { 384 return m_doc->doc->okToCopy(); 385 } 386 okToAddNotes() const387 bool Document::okToAddNotes() const 388 { 389 return m_doc->doc->okToAddNotes(); 390 } 391 okToFillForm() const392 bool Document::okToFillForm() const 393 { 394 return m_doc->doc->okToFillForm(); 395 } 396 okToCreateFormFields() const397 bool Document::okToCreateFormFields() const 398 { 399 return ( okToFillForm() && okToChange() ); 400 } 401 okToExtractForAccessibility() const402 bool Document::okToExtractForAccessibility() const 403 { 404 return m_doc->doc->okToAccessibility(); 405 } 406 okToAssemble() const407 bool Document::okToAssemble() const 408 { 409 return m_doc->doc->okToAssemble(); 410 } 411 getPdfVersion(int * major,int * minor) const412 void Document::getPdfVersion(int *major, int *minor) const 413 { 414 if (major) 415 *major = m_doc->doc->getPDFMajorVersion(); 416 if (minor) 417 *minor = m_doc->doc->getPDFMinorVersion(); 418 } 419 page(const QString & label) const420 Page *Document::page(const QString &label) const 421 { 422 GooString label_g(label.toLatin1().data()); 423 int index; 424 425 if (!m_doc->doc->getCatalog()->labelToIndex (&label_g, &index)) 426 return NULL; 427 428 return page(index); 429 } 430 hasEmbeddedFiles() const431 bool Document::hasEmbeddedFiles() const 432 { 433 return (!(0 == m_doc->doc->getCatalog()->numEmbeddedFiles())); 434 } 435 toc() const436 QDomDocument *Document::toc() const 437 { 438 Outline * outline = m_doc->doc->getOutline(); 439 if ( !outline ) 440 return NULL; 441 442 GooList * items = outline->getItems(); 443 if ( !items || items->getLength() < 1 ) 444 return NULL; 445 446 QDomDocument *toc = new QDomDocument(); 447 if ( items->getLength() > 0 ) 448 m_doc->addTocChildren( toc, toc, items ); 449 450 return toc; 451 } 452 linkDestination(const QString & name)453 LinkDestination *Document::linkDestination( const QString &name ) 454 { 455 GooString * namedDest = QStringToGooString( name ); 456 LinkDestinationData ldd(NULL, namedDest, m_doc, false); 457 LinkDestination *ld = new LinkDestination(ldd); 458 delete namedDest; 459 return ld; 460 } 461 setPaperColor(const QColor & color)462 void Document::setPaperColor(const QColor &color) 463 { 464 m_doc->setPaperColor(color); 465 } 466 setColorDisplayProfile(void * outputProfileA)467 void Document::setColorDisplayProfile(void* outputProfileA) 468 { 469 #if defined(USE_CMS) 470 GfxColorSpace::setDisplayProfile((cmsHPROFILE)outputProfileA); 471 #else 472 Q_UNUSED(outputProfileA); 473 #endif 474 } 475 setColorDisplayProfileName(const QString & name)476 void Document::setColorDisplayProfileName(const QString &name) 477 { 478 #if defined(USE_CMS) 479 GooString *profileName = QStringToGooString( name ); 480 GfxColorSpace::setDisplayProfileName(profileName); 481 delete profileName; 482 #else 483 Q_UNUSED(name); 484 #endif 485 } 486 colorRgbProfile() const487 void* Document::colorRgbProfile() const 488 { 489 #if defined(USE_CMS) 490 return (void*)GfxColorSpace::getRGBProfile(); 491 #else 492 return NULL; 493 #endif 494 } 495 colorDisplayProfile() const496 void* Document::colorDisplayProfile() const 497 { 498 #if defined(USE_CMS) 499 return (void*)GfxColorSpace::getDisplayProfile(); 500 #else 501 return NULL; 502 #endif 503 } 504 paperColor() const505 QColor Document::paperColor() const 506 { 507 return m_doc->paperColor; 508 } 509 setRenderBackend(Document::RenderBackend backend)510 void Document::setRenderBackend( Document::RenderBackend backend ) 511 { 512 // no need to delete the outputdev as for the moment we always create a splash one 513 // as the arthur one does not allow "precaching" due to it's signature 514 // delete m_doc->m_outputDev; 515 // m_doc->m_outputDev = NULL; 516 m_doc->m_backend = backend; 517 } 518 renderBackend() const519 Document::RenderBackend Document::renderBackend() const 520 { 521 return m_doc->m_backend; 522 } 523 availableRenderBackends()524 QSet<Document::RenderBackend> Document::availableRenderBackends() 525 { 526 QSet<Document::RenderBackend> ret; 527 #if defined(HAVE_SPLASH) 528 ret << Document::SplashBackend; 529 #endif 530 ret << Document::ArthurBackend; 531 return ret; 532 } 533 setRenderHint(Document::RenderHint hint,bool on)534 void Document::setRenderHint( Document::RenderHint hint, bool on ) 535 { 536 const bool touchesOverprinting = hint & Document::OverprintPreview; 537 538 int hintForOperation = hint; 539 if (touchesOverprinting && !isOverprintPreviewAvailable()) 540 hintForOperation = hintForOperation & ~(int)Document::OverprintPreview; 541 542 if ( on ) 543 m_doc->m_hints |= hintForOperation; 544 else 545 m_doc->m_hints &= ~hintForOperation; 546 547 } 548 renderHints() const549 Document::RenderHints Document::renderHints() const 550 { 551 return Document::RenderHints( m_doc->m_hints ); 552 } 553 psConverter() const554 PSConverter *Document::psConverter() const 555 { 556 return new PSConverter(m_doc); 557 } 558 pdfConverter() const559 PDFConverter *Document::pdfConverter() const 560 { 561 return new PDFConverter(m_doc); 562 } 563 metadata() const564 QString Document::metadata() const 565 { 566 QString result; 567 Catalog *catalog = m_doc->doc->getCatalog(); 568 if (catalog && catalog->isOk()) 569 { 570 GooString *s = catalog->readMetadata(); 571 if (s) result = UnicodeParsedString(s); 572 delete s; 573 } 574 return result; 575 } 576 hasOptionalContent() const577 bool Document::hasOptionalContent() const 578 { 579 return ( m_doc->doc->getOptContentConfig() && m_doc->doc->getOptContentConfig()->hasOCGs() ); 580 } 581 optionalContentModel()582 OptContentModel *Document::optionalContentModel() 583 { 584 if (m_doc->m_optContentModel.isNull()) { 585 m_doc->m_optContentModel = new OptContentModel(m_doc->doc->getOptContentConfig(), 0); 586 } 587 return (OptContentModel *)m_doc->m_optContentModel; 588 } 589 scripts() const590 QStringList Document::scripts() const 591 { 592 Catalog *catalog = m_doc->doc->getCatalog(); 593 const int numScripts = catalog->numJS(); 594 QStringList scripts; 595 for (int i = 0; i < numScripts; ++i) { 596 GooString *s = catalog->getJS(i); 597 if (s) { 598 scripts.append(UnicodeParsedString(s)); 599 delete s; 600 } 601 } 602 return scripts; 603 } 604 getPdfId(QByteArray * permanentId,QByteArray * updateId) const605 bool Document::getPdfId(QByteArray *permanentId, QByteArray *updateId) const 606 { 607 GooString gooPermanentId; 608 GooString gooUpdateId; 609 610 if (!m_doc->doc->getID(permanentId ? &gooPermanentId : 0, updateId ? &gooUpdateId : 0)) 611 return false; 612 613 if (permanentId) 614 *permanentId = gooPermanentId.getCString(); 615 if (updateId) 616 *updateId = gooUpdateId.getCString(); 617 618 return true; 619 } 620 formType() const621 Document::FormType Document::formType() const 622 { 623 switch ( m_doc->doc->getCatalog()->getFormType() ) 624 { 625 case Catalog::NoForm: 626 return Document::NoForm; 627 case Catalog::AcroForm: 628 return Document::AcroForm; 629 case Catalog::XfaForm: 630 return Document::XfaForm; 631 } 632 633 return Document::NoForm; // make gcc happy 634 } 635 convertDate(char * dateString)636 QDateTime convertDate( char *dateString ) 637 { 638 int year, mon, day, hour, min, sec, tzHours, tzMins; 639 char tz; 640 641 if ( parseDateString( dateString, &year, &mon, &day, &hour, &min, &sec, &tz, &tzHours, &tzMins ) ) 642 { 643 QDate d( year, mon, day ); 644 QTime t( hour, min, sec ); 645 if ( d.isValid() && t.isValid() ) { 646 QDateTime dt( d, t, Qt::UTC ); 647 if ( tz ) { 648 // then we have some form of timezone 649 if ( 'Z' == tz ) { 650 // We are already at UTC 651 } else if ( '+' == tz ) { 652 // local time is ahead of UTC 653 dt = dt.addSecs(-1*((tzHours*60)+tzMins)*60); 654 } else if ( '-' == tz ) { 655 // local time is behind UTC 656 dt = dt.addSecs(((tzHours*60)+tzMins)*60); 657 } else { 658 qWarning("unexpected tz val"); 659 } 660 } 661 return dt; 662 } 663 } 664 return QDateTime(); 665 } 666 isCmsAvailable()667 bool isCmsAvailable() 668 { 669 #if defined(USE_CMS) 670 return true; 671 #else 672 return false; 673 #endif 674 } 675 isOverprintPreviewAvailable()676 bool isOverprintPreviewAvailable() { 677 #if SPLASH_CMYK 678 return true; 679 #else 680 return false; 681 #endif 682 } 683 684 } 685 686