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