1 /* poppler-page.cc: qt interface to poppler
2  * Copyright (C) 2005, Net Integration Technologies, Inc.
3  * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
4  * Copyright (C) 2005-2010, Albert Astals Cid <aacid@kde.org>
5  * Copyright (C) 2005, Stefan Kebekus <stefan.kebekus@math.uni-koeln.de>
6  * Copyright (C) 2006-2010, Pino Toscano <pino@kde.org>
7  * Copyright (C) 2008 Carlos Garcia Campos <carlosgc@gnome.org>
8  * Copyright (C) 2009 Shawn Rutledge <shawn.t.rutledge@gmail.com>
9  * Copyright (C) 2010, Guillermo Amaral <gamaral@kdab.com>
10  * Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
11  * Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
12  * Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
27  */
28 
29 #include <poppler-qt4.h>
30 
31 #include <QtCore/QHash>
32 #include <QtCore/QMap>
33 #include <QtCore/QVarLengthArray>
34 #include <QtGui/QImage>
35 #include <QtGui/QPainter>
36 
37 #include <config.h>
38 #include <PDFDoc.h>
39 #include <Catalog.h>
40 #include <Form.h>
41 #include <ErrorCodes.h>
42 #include <TextOutputDev.h>
43 #include <Annot.h>
44 #include <Link.h>
45 #include <ArthurOutputDev.h>
46 #if defined(HAVE_SPLASH)
47 #include <SplashOutputDev.h>
48 #include <splash/SplashBitmap.h>
49 #endif
50 
51 #include "poppler-private.h"
52 #include "poppler-page-transition-private.h"
53 #include "poppler-page-private.h"
54 #include "poppler-link-extractor-private.h"
55 #include "poppler-annotation-helper.h"
56 #include "poppler-annotation-private.h"
57 #include "poppler-form.h"
58 
59 namespace Poppler {
60 
61 class DummyAnnotation : public Annotation
62 {
63     public:
DummyAnnotation()64         DummyAnnotation()
65             : Annotation( *new AnnotationPrivate() )
66         {
67         }
68 
subType() const69         virtual SubType subType() const { return A_BASE; }
70 };
71 
convertLinkActionToLink(::LinkAction * a,const QRectF & linkArea)72 Link* PageData::convertLinkActionToLink(::LinkAction * a, const QRectF &linkArea)
73 {
74     return convertLinkActionToLink(a, parentDoc, linkArea);
75 }
76 
convertLinkActionToLink(::LinkAction * a,DocumentData * parentDoc,const QRectF & linkArea)77 Link* PageData::convertLinkActionToLink(::LinkAction * a, DocumentData *parentDoc, const QRectF &linkArea)
78 {
79   if ( !a )
80     return NULL;
81 
82   Link * popplerLink = NULL;
83   switch ( a->getKind() )
84   {
85     case actionGoTo:
86     {
87       LinkGoTo * g = (LinkGoTo *) a;
88       const LinkDestinationData ldd( g->getDest(), g->getNamedDest(), parentDoc, false );
89       // create link: no ext file, namedDest, object pointer
90       popplerLink = new LinkGoto( linkArea, QString::null, LinkDestination( ldd ) );
91     }
92     break;
93 
94     case actionGoToR:
95     {
96       LinkGoToR * g = (LinkGoToR *) a;
97       // copy link file
98       const QString fileName = UnicodeParsedString( g->getFileName() );
99       const LinkDestinationData ldd( g->getDest(), g->getNamedDest(), parentDoc, !fileName.isEmpty() );
100       // ceate link: fileName, namedDest, object pointer
101       popplerLink = new LinkGoto( linkArea, fileName, LinkDestination( ldd ) );
102     }
103     break;
104 
105     case actionLaunch:
106     {
107       LinkLaunch * e = (LinkLaunch *)a;
108       GooString * p = e->getParams();
109       popplerLink = new LinkExecute( linkArea, e->getFileName()->getCString(), p ? p->getCString() : 0 );
110     }
111     break;
112 
113     case actionNamed:
114     {
115       const char * name = ((LinkNamed *)a)->getName()->getCString();
116       if ( !strcmp( name, "NextPage" ) )
117         popplerLink = new LinkAction( linkArea, LinkAction::PageNext );
118       else if ( !strcmp( name, "PrevPage" ) )
119         popplerLink = new LinkAction( linkArea, LinkAction::PagePrev );
120       else if ( !strcmp( name, "FirstPage" ) )
121         popplerLink = new LinkAction( linkArea, LinkAction::PageFirst );
122       else if ( !strcmp( name, "LastPage" ) )
123         popplerLink = new LinkAction( linkArea, LinkAction::PageLast );
124       else if ( !strcmp( name, "GoBack" ) )
125         popplerLink = new LinkAction( linkArea, LinkAction::HistoryBack );
126       else if ( !strcmp( name, "GoForward" ) )
127         popplerLink = new LinkAction( linkArea, LinkAction::HistoryForward );
128       else if ( !strcmp( name, "Quit" ) )
129         popplerLink = new LinkAction( linkArea, LinkAction::Quit );
130       else if ( !strcmp( name, "GoToPage" ) )
131         popplerLink = new LinkAction( linkArea, LinkAction::GoToPage );
132       else if ( !strcmp( name, "Find" ) )
133         popplerLink = new LinkAction( linkArea, LinkAction::Find );
134       else if ( !strcmp( name, "FullScreen" ) )
135         popplerLink = new LinkAction( linkArea, LinkAction::Presentation );
136       else if ( !strcmp( name, "Print" ) )
137         popplerLink = new LinkAction( linkArea, LinkAction::Print );
138       else if ( !strcmp( name, "Close" ) )
139       {
140         // acroread closes the document always, doesnt care whether
141         // its presentation mode or not
142         // popplerLink = new LinkAction( linkArea, LinkAction::EndPresentation );
143         popplerLink = new LinkAction( linkArea, LinkAction::Close );
144       }
145       else
146       {
147         // TODO
148       }
149     }
150     break;
151 
152     case actionURI:
153     {
154       popplerLink = new LinkBrowse( linkArea, ((LinkURI *)a)->getURI()->getCString() );
155     }
156     break;
157 
158     case actionSound:
159     {
160       ::LinkSound *ls = (::LinkSound *)a;
161       popplerLink = new LinkSound( linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject( ls->getSound() ) );
162     }
163     break;
164 
165     case actionJavaScript:
166     {
167       ::LinkJavaScript *ljs = (::LinkJavaScript *)a;
168       popplerLink = new LinkJavaScript( linkArea, UnicodeParsedString(ljs->getScript()) );
169     }
170     break;
171 
172     case actionMovie:
173 /*      TODO this (Movie link)
174           m_type = Movie;
175           LinkMovie * m = (LinkMovie *) a;
176           // copy Movie parameters (2 IDs and a const char *)
177           Ref * r = m->getAnnotRef();
178           m_refNum = r->num;
179           m_refGen = r->gen;
180           copyString( m_uri, m->getTitle()->getCString() );
181 */  break;
182 
183     case actionUnknown:
184     break;
185   }
186 
187   return popplerLink;
188 }
189 
190 
Page(DocumentData * doc,int index)191 Page::Page(DocumentData *doc, int index) {
192   m_page = new PageData();
193   m_page->index = index;
194   m_page->parentDoc = doc;
195   m_page->page = doc->doc->getPage(m_page->index + 1);
196   m_page->transition = 0;
197 }
198 
~Page()199 Page::~Page()
200 {
201   delete m_page->transition;
202   delete m_page;
203 }
204 
renderToImage(double xres,double yres,int x,int y,int w,int h,Rotation rotate) const205 QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate) const
206 {
207   int rotation = (int)rotate * 90;
208   QImage img;
209   switch(m_page->parentDoc->m_backend)
210   {
211     case Poppler::Document::SplashBackend:
212     {
213 #if defined(HAVE_SPLASH)
214       SplashOutputDev *splash_output = static_cast<SplashOutputDev *>(m_page->parentDoc->getOutputDev());
215 
216       m_page->parentDoc->doc->displayPageSlice(splash_output, m_page->index + 1, xres, yres,
217 						 rotation, false, true, false, x, y, w, h);
218 
219       SplashBitmap *bitmap = splash_output->getBitmap();
220       int bw = bitmap->getWidth();
221       int bh = bitmap->getHeight();
222 
223       SplashColorPtr dataPtr = splash_output->getBitmap()->getDataPtr();
224 
225       if (QSysInfo::BigEndian == QSysInfo::ByteOrder)
226       {
227         uchar c;
228         int count = bw * bh * 4;
229         for (int k = 0; k < count; k += 4)
230         {
231           c = dataPtr[k];
232           dataPtr[k] = dataPtr[k+3];
233           dataPtr[k+3] = c;
234 
235           c = dataPtr[k+1];
236           dataPtr[k+1] = dataPtr[k+2];
237           dataPtr[k+2] = c;
238         }
239       }
240 
241       // construct a qimage SHARING the raw bitmap data in memory
242       QImage tmpimg( dataPtr, bw, bh, QImage::Format_ARGB32 );
243       img = tmpimg.copy();
244       // unload underlying xpdf bitmap
245       splash_output->startPage( 0, NULL );
246 #endif
247       break;
248     }
249     case Poppler::Document::ArthurBackend:
250     {
251       QSize size = pageSize();
252       QImage tmpimg(w == -1 ? qRound( size.width() * xres / 72.0 ) : w, h == -1 ? qRound( size.height() * yres / 72.0 ) : h, QImage::Format_ARGB32);
253 
254       QPainter painter(&tmpimg);
255       renderToPainter(&painter, xres, yres, x, y, w, h, rotate, DontSaveAndRestore);
256       painter.end();
257       img = tmpimg;
258       break;
259     }
260   }
261 
262   return img;
263 }
264 
renderToPainter(QPainter * painter,double xres,double yres,int x,int y,int w,int h,Rotation rotate,PainterFlags flags) const265 bool Page::renderToPainter(QPainter* painter, double xres, double yres, int x, int y, int w, int h, Rotation rotate, PainterFlags flags) const
266 {
267   if (!painter)
268     return false;
269 
270   switch(m_page->parentDoc->m_backend)
271   {
272     case Poppler::Document::SplashBackend:
273       return false;
274     case Poppler::Document::ArthurBackend:
275     {
276       const bool savePainter = !(flags & DontSaveAndRestore);
277       if (savePainter)
278          painter->save();
279       if (m_page->parentDoc->m_hints & Document::Antialiasing)
280           painter->setRenderHint(QPainter::Antialiasing);
281       if (m_page->parentDoc->m_hints & Document::TextAntialiasing)
282           painter->setRenderHint(QPainter::TextAntialiasing);
283       painter->translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y);
284       ArthurOutputDev arthur_output(painter);
285       arthur_output.startDoc(m_page->parentDoc->doc->getXRef());
286       m_page->parentDoc->doc->displayPageSlice(&arthur_output,
287                                                m_page->index + 1,
288                                                xres,
289                                                yres,
290                                                (int)rotate * 90,
291                                                false,
292                                                true,
293                                                false,
294                                                x,
295                                                y,
296                                                w,
297                                                h);
298       if (savePainter)
299          painter->restore();
300       return true;
301     }
302   }
303   return false;
304 }
305 
thumbnail() const306 QImage Page::thumbnail() const
307 {
308   unsigned char* data = 0;
309   int w = 0;
310   int h = 0;
311   int rowstride = 0;
312   GBool r = m_page->page->loadThumb(&data, &w, &h, &rowstride);
313   QImage ret;
314   if (r)
315   {
316     // first construct a temporary image with the data got,
317     // then force a copy of it so we can free the raw thumbnail data
318     ret = QImage(data, w, h, rowstride, QImage::Format_RGB888).copy();
319     gfree(data);
320   }
321   return ret;
322 }
323 
text(const QRectF & r,TextLayout textLayout) const324 QString Page::text(const QRectF &r, TextLayout textLayout) const
325 {
326   TextOutputDev *output_dev;
327   GooString *s;
328   PDFRectangle *rect;
329   QString result;
330 
331   const GBool rawOrder = textLayout == RawOrderLayout;
332   output_dev = new TextOutputDev(0, gFalse, rawOrder, gFalse);
333   m_page->parentDoc->doc->displayPageSlice(output_dev, m_page->index + 1, 72, 72,
334       0, false, true, false, -1, -1, -1, -1);
335   if (r.isNull())
336   {
337     rect = m_page->page->getCropBox();
338     s = output_dev->getText(rect->x1, rect->y1, rect->x2, rect->y2);
339   }
340   else
341   {
342     s = output_dev->getText(r.left(), r.top(), r.right(), r.bottom());
343   }
344 
345   result = QString::fromUtf8(s->getCString());
346 
347   delete output_dev;
348   delete s;
349   return result;
350 }
351 
text(const QRectF & r) const352 QString Page::text(const QRectF &r) const
353 {
354   return text(r, PhysicalLayout);
355 }
356 
search(const QString & text,double & sLeft,double & sTop,double & sRight,double & sBottom,SearchDirection direction,SearchMode caseSensitive,Rotation rotate) const357 bool Page::search(const QString &text, double &sLeft, double &sTop, double &sRight, double &sBottom, SearchDirection direction, SearchMode caseSensitive, Rotation rotate) const
358 {
359   const QChar * str = text.unicode();
360   int len = text.length();
361   QVector<Unicode> u(len);
362   for (int i = 0; i < len; ++i) u[i] = str[i].unicode();
363 
364   GBool sCase;
365   if (caseSensitive == CaseSensitive) sCase = gTrue;
366   else sCase = gFalse;
367 
368   bool found = false;
369 
370   int rotation = (int)rotate * 90;
371 
372   // fetch ourselves a textpage
373   TextOutputDev td(NULL, gTrue, gFalse, gFalse);
374   m_page->parentDoc->doc->displayPage( &td, m_page->index + 1, 72, 72, rotation, false, true, false );
375   TextPage *textPage=td.takeText();
376 
377   if (direction == FromTop)
378     found = textPage->findText( u.data(), len,
379             gTrue, gTrue, gFalse, gFalse, sCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
380   else if ( direction == NextResult )
381     found = textPage->findText( u.data(), len,
382             gFalse, gTrue, gTrue, gFalse, sCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
383   else if ( direction == PreviousResult )
384     found = textPage->findText( u.data(), len,
385             gFalse, gTrue, gTrue, gFalse, sCase, gTrue, &sLeft, &sTop, &sRight, &sBottom );
386 
387   textPage->decRefCnt();
388 
389   return found;
390 }
391 
search(const QString & text,QRectF & rect,SearchDirection direction,SearchMode caseSensitive,Rotation rotate) const392 bool Page::search(const QString &text, QRectF &rect, SearchDirection direction, SearchMode caseSensitive, Rotation rotate) const
393 {
394   double sLeft, sTop, sRight, sBottom;
395   sLeft = rect.left();
396   sTop = rect.top();
397   sRight = rect.right();
398   sBottom = rect.bottom();
399 
400   bool found = search(text, sLeft, sTop, sRight, sBottom, direction, caseSensitive, rotate);
401 
402   rect.setLeft( sLeft );
403   rect.setTop( sTop );
404   rect.setRight( sRight );
405   rect.setBottom( sBottom );
406 
407   return found;
408 }
409 
textList(Rotation rotate) const410 QList<TextBox*> Page::textList(Rotation rotate) const
411 {
412   TextOutputDev *output_dev;
413 
414   QList<TextBox*> output_list;
415 
416   output_dev = new TextOutputDev(0, gFalse, gFalse, gFalse);
417 
418   int rotation = (int)rotate * 90;
419 
420   m_page->parentDoc->doc->displayPageSlice(output_dev, m_page->index + 1, 72, 72,
421       rotation, false, false, false, -1, -1, -1, -1);
422 
423   TextWordList *word_list = output_dev->makeWordList();
424 
425   if (!word_list) {
426     delete output_dev;
427     return output_list;
428   }
429 
430   QHash<TextWord *, TextBox*> wordBoxMap;
431 
432   for (int i = 0; i < word_list->getLength(); i++) {
433     TextWord *word = word_list->get(i);
434     GooString *gooWord = word->getText();
435     QString string = QString::fromUtf8(gooWord->getCString());
436     delete gooWord;
437     double xMin, yMin, xMax, yMax;
438     word->getBBox(&xMin, &yMin, &xMax, &yMax);
439 
440     TextBox* text_box = new TextBox(string, QRectF(xMin, yMin, xMax-xMin, yMax-yMin));
441     text_box->m_data->hasSpaceAfter = word->hasSpaceAfter() == gTrue;
442     text_box->m_data->charBBoxes.reserve(word->getLength());
443     for (int j = 0; j < word->getLength(); ++j)
444     {
445         word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax);
446         text_box->m_data->charBBoxes.append(QRectF(xMin, yMin, xMax-xMin, yMax-yMin));
447     }
448 
449     wordBoxMap.insert(word, text_box);
450 
451     output_list.append(text_box);
452   }
453 
454   for (int i = 0; i < word_list->getLength(); i++) {
455     TextWord *word = word_list->get(i);
456     TextBox* text_box = wordBoxMap.value(word);
457     text_box->m_data->nextWord = wordBoxMap.value(word->nextWord());
458   }
459 
460   delete word_list;
461   delete output_dev;
462 
463   return output_list;
464 }
465 
transition() const466 PageTransition *Page::transition() const
467 {
468   if (!m_page->transition) {
469     Object o;
470     PageTransitionParams params;
471     params.dictObj = m_page->page->getTrans(&o);
472     if (params.dictObj->isDict()) m_page->transition = new PageTransition(params);
473     o.free();
474   }
475   return m_page->transition;
476 }
477 
action(PageAction act) const478 Link *Page::action( PageAction act ) const
479 {
480   if ( act == Page::Opening || act == Page::Closing )
481   {
482     Object o;
483     m_page->page->getActions(&o);
484     if (!o.isDict())
485     {
486       o.free();
487       return 0;
488     }
489     Dict *dict = o.getDict();
490     Object o2;
491     const char *key = act == Page::Opening ? "O" : "C";
492     dict->lookup((char*)key, &o2);
493     ::LinkAction *lact = ::LinkAction::parseAction(&o2, m_page->parentDoc->doc->getCatalog()->getBaseURI() );
494     o2.free();
495     o.free();
496     Link *popplerLink = NULL;
497     if (lact != NULL)
498     {
499       popplerLink = m_page->convertLinkActionToLink(lact, QRectF());
500       delete lact;
501     }
502     return popplerLink;
503   }
504   return 0;
505 }
506 
pageSizeF() const507 QSizeF Page::pageSizeF() const
508 {
509   Page::Orientation orient = orientation();
510   if ( ( Page::Landscape == orient ) || (Page::Seascape == orient ) ) {
511       return QSizeF( m_page->page->getCropHeight(), m_page->page->getCropWidth() );
512   } else {
513     return QSizeF( m_page->page->getCropWidth(), m_page->page->getCropHeight() );
514   }
515 }
516 
pageSize() const517 QSize Page::pageSize() const
518 {
519   return pageSizeF().toSize();
520 }
521 
orientation() const522 Page::Orientation Page::orientation() const
523 {
524   const int rotation = m_page->page->getRotate();
525   switch (rotation) {
526   case 90:
527     return Page::Landscape;
528     break;
529   case 180:
530     return Page::UpsideDown;
531     break;
532   case 270:
533     return Page::Seascape;
534     break;
535   default:
536     return Page::Portrait;
537   }
538 }
539 
defaultCTM(double * CTM,double dpiX,double dpiY,int rotate,bool upsideDown)540 void Page::defaultCTM(double *CTM, double dpiX, double dpiY, int rotate, bool upsideDown)
541 {
542   m_page->page->getDefaultCTM(CTM, dpiX, dpiY, rotate, gFalse, upsideDown);
543 }
544 
links() const545 QList<Link*> Page::links() const
546 {
547   LinkExtractorOutputDev link_dev(m_page);
548   m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1);
549   QList<Link*> popplerLinks = link_dev.links();
550 
551   return popplerLinks;
552 }
553 
annotations() const554 QList<Annotation*> Page::annotations() const
555 {
556     ::Page *pdfPage = m_page->page;
557     Annots* annots = pdfPage->getAnnots(m_page->parentDoc->doc->getCatalog());
558     const uint numAnnotations = annots->getNumAnnots();
559     if ( numAnnotations == 0 )
560     {
561         delete annots;
562         return QList<Annotation*>();
563     }
564 
565     // ID to Annotation/PopupWindow maps
566     QMap< int, Annotation * > annotationsMap;
567     QHash< AnnotPopup *, PopupWindow * > popupsMap;
568     // lists of Windows and Revisions that needs resolution
569     QLinkedList< ResolveRevision > resolveRevList;
570     QLinkedList< ResolveWindow > resolvePopList;
571     QLinkedList< PostProcessText > ppTextList;
572 
573     // build a normalized transform matrix for this page at 100% scale
574     GfxState * gfxState = new GfxState( 72.0, 72.0, pdfPage->getCropBox(), pdfPage->getRotate(), gTrue );
575     double * gfxCTM = gfxState->getCTM();
576     double MTX[6];
577     for ( int i = 0; i < 6; i+=2 )
578     {
579         MTX[i] = gfxCTM[i] / pdfPage->getCropWidth();
580         MTX[i+1] = gfxCTM[i+1] / pdfPage->getCropHeight();
581     }
582     delete gfxState;
583 
584     /** 1 - PARSE ALL ANNOTATIONS AND POPUPS FROM THE PAGE */
585     for ( uint j = 0; j < numAnnotations; j++ )
586     {
587         // get the j-th annotation
588         Annot * ann = annots->getAnnot( j );
589         if ( !ann )
590         {
591             qDebug() << "Annot" << j << "is null.";
592             continue;
593         }
594 
595         Annotation * annotation = 0;
596         int annotID = ann->getId();
597         AnnotMarkup * markupann = dynamic_cast< AnnotMarkup * >( ann );
598         bool addToPage = true;      // Popup annots are added to custom queue
599 
600         /** 1.1. GET Subtype */
601         Annot::AnnotSubtype subType = ann->getType();
602 
603         /** 1.2. CREATE Annotation / PopupWindow and PARSE specific params */
604         switch ( subType )
605         {
606             case Annot::typeText:
607             {
608                 // parse TextAnnotation params
609                 AnnotText * textann = static_cast< AnnotText * >( ann );
610                 TextAnnotation * t = new TextAnnotation();
611                 annotation = t;
612                 // -> textType
613                 t->setTextType( TextAnnotation::Linked );
614                 // -> textIcon
615                 t->setTextIcon( QString::fromLatin1( textann->getIcon()->getCString() ) );
616                 // request for postprocessing window geometry
617                 PostProcessText request;
618                 request.textAnnotation = t;
619                 request.opened = textann->getOpen();
620                 ppTextList.append( request );
621                 break;
622             }
623             case Annot::typeFreeText:
624             {
625                 // parse TextAnnotation params
626                 AnnotFreeText * textann = static_cast< AnnotFreeText * >( ann );
627                 TextAnnotation * t = new TextAnnotation();
628                 annotation = t;
629                 // -> textType
630                 t->setTextType( TextAnnotation::InPlace );
631 #if 0
632                 // -> textFont
633                 QString textFormat;
634                 XPDFReader::lookupString( annotDict, "DA", textFormat );
635                 // TODO, fill t->textFont using textFormat if not empty
636 #endif
637                 // -> inplaceAlign
638                 t->setInplaceAlign( textann->getQuadding() );
639                 // -> inplaceText (simple)
640                 QString tmpstring;
641                 GooString *styleString = textann->getStyleString();
642                 if ( styleString )
643                     tmpstring = UnicodeParsedString( styleString );
644 #if 0
645                 // -> inplaceText (complex override)
646                 XPDFReader::lookupString( annotDict, "RC", tmpstring );
647 #endif
648                 t->setInplaceText( tmpstring );
649                 // -> inplaceCallout
650                 AnnotCalloutLine *callout = textann->getCalloutLine();
651                 if ( callout )
652                 {
653                     QPointF tmppoint;
654                     XPDFReader::transform( MTX, callout->getX1(), callout->getY1(), tmppoint );
655                     t->setCalloutPoint( 0, tmppoint );
656                     XPDFReader::transform( MTX, callout->getX2(), callout->getY2(), tmppoint );
657                     t->setCalloutPoint( 1, tmppoint );
658                     if ( AnnotCalloutMultiLine * callout_v6 = dynamic_cast< AnnotCalloutMultiLine * >( callout ) )
659                     {
660                         XPDFReader::transform( MTX, callout_v6->getX3(), callout_v6->getY3(), tmppoint );
661                         t->setCalloutPoint( 2, tmppoint );
662                     }
663                 }
664                 // -> inplaceIntent
665                 t->setInplaceIntent( (TextAnnotation::InplaceIntent)textann->getIntent() );
666                 AnnotBorderEffect *border_effect = textann->getBorderEffect();
667                 if ( border_effect )
668                 {
669                     // -> style.effect
670                     t->style.effect = (Annotation::LineEffect)border_effect->getEffectType();
671                     // -> style.effectIntensity
672                     t->style.effectIntensity = (int)border_effect->getIntensity();
673                 }
674                 break;
675             }
676             case Annot::typeLine:
677             {
678                 // parse LineAnnotation params
679                 AnnotLine * lineann = static_cast< AnnotLine * >( ann );
680                 LineAnnotation * l = new LineAnnotation();
681                 annotation = l;
682 
683                 // -> linePoints
684                 QLinkedList<QPointF> linePoints;
685                 QPointF p;
686                 XPDFReader::transform( MTX, lineann->getX1(), lineann->getY1(), p );
687                 linePoints.push_back( p );
688                 XPDFReader::transform( MTX, lineann->getX2(), lineann->getY2(), p );
689                 linePoints.push_back( p );
690                 l->setLinePoints( linePoints );
691                 // -> lineStartStyle
692                 l->setLineStartStyle( (LineAnnotation::TermStyle)lineann->getStartStyle() );
693                 // -> lineEndStyle
694                 l->setLineEndStyle( (LineAnnotation::TermStyle)lineann->getEndStyle() );
695                 // -> lineClosed
696                 l->setLineClosed( false );
697                 // -> lineInnerColor
698                 l->setLineInnerColor( convertAnnotColor( lineann->getInteriorColor() ) );
699                 // -> lineLeadingFwdPt
700                 l->setLineLeadingForwardPoint( lineann->getLeaderLineLength() );
701                 // -> lineLeadingBackPt
702                 l->setLineLeadingBackPoint( lineann->getLeaderLineExtension() );
703                 // -> lineShowCaption
704                 l->setLineShowCaption( lineann->getCaption() );
705                 // -> lineIntent
706                 l->setLineIntent( (LineAnnotation::LineIntent)( lineann->getIntent() + 1 ) );
707                 break;
708             }
709             case Annot::typePolygon:
710             case Annot::typePolyLine:
711             {
712                 // parse LineAnnotation params
713                 AnnotPolygon * polyann = static_cast< AnnotPolygon * >( ann );
714                 LineAnnotation * l = new LineAnnotation();
715                 annotation = l;
716 
717                 // -> polyPoints
718                 QLinkedList<QPointF> polyPoints;
719                 AnnotPath * vertices = polyann->getVertices();
720                 for ( int i = 0; i < vertices->getCoordsLength(); ++i )
721                 {
722                     QPointF p;
723                     XPDFReader::transform( MTX, vertices->getX( i ), vertices->getY( i ), p );
724                     polyPoints.push_back( p );
725                 }
726                 l->setLinePoints( polyPoints );
727                 // -> polyStartStyle
728                 l->setLineStartStyle( (LineAnnotation::TermStyle)polyann->getStartStyle() );
729                 // -> polyEndStyle
730                 l->setLineEndStyle( (LineAnnotation::TermStyle)polyann->getEndStyle() );
731                 // -> polyClosed
732                 l->setLineClosed( subType == Annot::typePolygon );
733                 // -> polyInnerColor
734                 l->setLineInnerColor( convertAnnotColor( polyann->getInteriorColor() ) );
735                 // -> polyIntent
736                 switch ( polyann->getIntent() )
737                 {
738                     case AnnotPolygon::polygonCloud:
739                         l->setLineIntent( LineAnnotation::PolygonCloud );
740                         break;
741                     case AnnotPolygon::polylineDimension:
742                     case AnnotPolygon::polygonDimension:
743                         l->setLineIntent( LineAnnotation::Dimension );
744                         break;
745                 }
746                 break;
747             }
748             case Annot::typeSquare:
749             case Annot::typeCircle:
750             {
751                 // parse GeomAnnotation params
752                 AnnotGeometry * geomann = static_cast< AnnotGeometry * >( ann );
753                 GeomAnnotation * g = new GeomAnnotation();
754                 annotation = g;
755 
756                 // -> highlightType
757                 if ( subType == Annot::typeSquare )
758                     g->setGeomType( GeomAnnotation::InscribedSquare );
759                 else if ( subType == Annot::typeCircle )
760                     g->setGeomType( GeomAnnotation::InscribedCircle );
761 
762                 // -> geomInnerColor
763                 g->setGeomInnerColor( convertAnnotColor( geomann->getInteriorColor() ) );
764 
765                 AnnotBorderEffect *border_effect = geomann->getBorderEffect();
766                 if ( border_effect )
767                 {
768                     // -> style.effect
769                     g->style.effect = (Annotation::LineEffect)border_effect->getEffectType();
770                     // -> style.effectIntensity
771                     g->style.effectIntensity = (int)border_effect->getIntensity();
772                 }
773 
774                 // TODO RD
775 
776                 break;
777             }
778             case Annot::typeHighlight:
779             case Annot::typeUnderline:
780             case Annot::typeSquiggly:
781             case Annot::typeStrikeOut:
782             {
783                 AnnotTextMarkup * hlann = static_cast< AnnotTextMarkup * >( ann );
784                 HighlightAnnotation * h = new HighlightAnnotation();
785                 annotation = h;
786 
787                 // -> highlightType
788                 if ( subType == Annot::typeHighlight )
789                     h->setHighlightType( HighlightAnnotation::Highlight );
790                 else if ( subType == Annot::typeUnderline )
791                     h->setHighlightType( HighlightAnnotation::Underline );
792                 else if ( subType == Annot::typeSquiggly )
793                     h->setHighlightType( HighlightAnnotation::Squiggly );
794                 else if ( subType == Annot::typeStrikeOut )
795                     h->setHighlightType( HighlightAnnotation::StrikeOut );
796 
797                 // -> highlightQuads
798                 AnnotQuadrilaterals *hlquads = hlann->getQuadrilaterals();
799                 if ( !hlquads || !hlquads->getQuadrilateralsLength() )
800                 {
801                     qDebug() << "Not enough quads for a Highlight annotation.";
802                     delete annotation;
803                     continue;
804                 }
805                 QList< HighlightAnnotation::Quad > quads;
806                 const int quadsCount = hlquads->getQuadrilateralsLength();
807                 for ( int q = 0; q < quadsCount; ++q )
808                 {
809                     HighlightAnnotation::Quad quad;
810                     XPDFReader::transform( MTX, hlquads->getX1( q ), hlquads->getY1( q ), quad.points[ 0 ] );
811                     XPDFReader::transform( MTX, hlquads->getX2( q ), hlquads->getY2( q ), quad.points[ 1 ] );
812                     XPDFReader::transform( MTX, hlquads->getX3( q ), hlquads->getY3( q ), quad.points[ 2 ] );
813                     XPDFReader::transform( MTX, hlquads->getX4( q ), hlquads->getY4( q ), quad.points[ 3 ] );
814                     // ### PDF1.6 specs says that point are in ccw order, but in fact
815                     // points 3 and 4 are swapped in every PDF around!
816                     QPointF tmpPoint = quad.points[ 2 ];
817                     quad.points[ 2 ] = quad.points[ 3 ];
818                     quad.points[ 3 ] = tmpPoint;
819                     // initialize other properties and append quad
820                     quad.capStart = true;       // unlinked quads are always capped
821                     quad.capEnd = true;         // unlinked quads are always capped
822                     quad.feather = 0.1;         // default feather
823                     quads.append( quad );
824                 }
825                 h->setHighlightQuads( quads );
826                 break;
827             }
828             case Annot::typeStamp:
829             {
830                 AnnotStamp * stampann = static_cast< AnnotStamp * >( ann );
831                 StampAnnotation * s = new StampAnnotation();
832                 annotation = s;
833                 // -> stampIcon
834                 s->setStampIconName( QString::fromLatin1( stampann->getIcon()->getCString() ) );
835                 break;
836             }
837             case Annot::typeInk:
838             {
839                 // parse InkAnnotation params
840                 AnnotInk * inkann = static_cast< AnnotInk * >( ann );
841                 InkAnnotation * k = new InkAnnotation();
842                 annotation = k;
843 
844                 // -> inkPaths
845                 AnnotPath ** paths = inkann->getInkList();
846                 if ( !paths || !inkann->getInkListLength() )
847                 {
848                     qDebug() << "InkList not present for ink annot";
849                     delete annotation;
850                     continue;
851                 }
852                 QList< QLinkedList<QPointF> > inkPaths;
853                 const int pathsNumber = inkann->getInkListLength();
854                 for ( int m = 0; m < pathsNumber; m++ )
855                 {
856                     // transform each path in a list of normalized points ..
857                     QLinkedList<QPointF> localList;
858                     AnnotPath * path = paths[ m ];
859                     const int pointsNumber = path ? path->getCoordsLength() : 0;
860                     for ( int n = 0; n < pointsNumber; ++n )
861                     {
862                         // get the x,y numbers for current point
863                         double x = path->getX( n );
864                         double y = path->getY( n );
865                         // add normalized point to localList
866                         QPointF np;
867                         XPDFReader::transform( MTX, x, y, np );
868                         localList.push_back( np );
869                     }
870                     // ..and add it to the annotation
871                     inkPaths.push_back( localList );
872                 }
873                 k->setInkPaths( inkPaths );
874                 break;
875             }
876             case Annot::typePopup:
877             {
878                 AnnotPopup * popupann = static_cast< AnnotPopup * >( ann );
879 
880                 // create PopupWindow and add it to the popupsMap
881                 PopupWindow * popup = new PopupWindow();
882                 popupsMap[ popupann ] = popup;
883                 addToPage = false;
884 
885                 // get window specific properties if any
886                 popup->shown = popupann->getOpen();
887                 // no need to parse parent annotation id
888                 //XPDFReader::lookupIntRef( annotDict, "Parent", popup->... );
889 
890                 // use the 'dummy annotation' for getting other parameters
891                 popup->dummyAnnotation = new DummyAnnotation();
892                 annotation = popup->dummyAnnotation;
893 
894                 break;
895             }
896             case Annot::typeLink:
897             {
898                 // parse Link params
899                 AnnotLink * linkann = static_cast< AnnotLink * >( ann );
900                 LinkAnnotation * l = new LinkAnnotation();
901                 annotation = l;
902 
903                 // -> hlMode
904                 l->setLinkHighlightMode( (LinkAnnotation::HighlightMode)linkann->getLinkEffect() );
905 
906                 // -> link region
907                 // TODO
908 
909                 // reading link action
910                 if ( !linkann->getDest()->isNull() )
911                 {
912                     ::LinkAction *act = ::LinkAction::parseDest( linkann->getDest() );
913                     Link * popplerLink = m_page->convertLinkActionToLink( act, QRectF() );
914                     delete act;
915                     if ( popplerLink )
916                     {
917                         l->setLinkDestination( popplerLink );
918                     }
919                 }
920 
921                 break;
922             }
923             case Annot::typeCaret:
924             {
925                 // parse CaretAnnotation params
926                 AnnotCaret * caretann = static_cast< AnnotCaret * >( ann );
927                 CaretAnnotation * c = new CaretAnnotation();
928                 annotation = c;
929 
930                 // -> caretSymbol
931                 c->setCaretSymbol( (CaretAnnotation::CaretSymbol)caretann->getSymbol() );
932 
933                 // TODO RD
934 
935                 break;
936             }
937             case Annot::typeFileAttachment:
938             {
939                 AnnotFileAttachment * attachann = static_cast< AnnotFileAttachment * >( ann );
940                 FileAttachmentAnnotation * f = new FileAttachmentAnnotation();
941                 annotation = f;
942                 // -> fileIcon
943                 f->setFileIconName( QString::fromLatin1( attachann->getName()->getCString() ) );
944                 // -> embeddedFile
945                 EmbFile *embfile = new EmbFile( attachann->getFile(), attachann->getContents() );
946                 f->setEmbeddedFile( new EmbeddedFile( embfile ) );
947                 break;
948             }
949             case Annot::typeSound:
950             {
951                 AnnotSound * soundann = static_cast< AnnotSound * >( ann );
952                 SoundAnnotation * s = new SoundAnnotation();
953                 annotation = s;
954 
955                 // -> soundIcon
956                 s->setSoundIconName( QString::fromLatin1( soundann->getName()->getCString() ) );
957                 // -> sound
958                 s->setSound( new SoundObject( soundann->getSound() ) );
959 
960                 break;
961             }
962             case Annot::typeMovie:
963             {
964                 AnnotMovie * movieann = static_cast< AnnotMovie * >( ann );
965                 MovieAnnotation * m = new MovieAnnotation();
966                 annotation = m;
967 
968                 // -> movie
969                 MovieObject *movie = new MovieObject( movieann );
970                 m->setMovie( movie );
971                 // -> movieTitle
972                 GooString * movietitle = movieann->getTitle();
973                 if ( movietitle )
974                 {
975                     m->setMovieTitle( QString::fromLatin1( movietitle->getCString() ) );
976                 }
977 
978                 break;
979             }
980             // special case for ignoring unknwon annotations
981             case Annot::typeUnknown:
982                 continue;
983             default:
984             {
985 #define CASE_FOR_TYPE( thetype ) \
986                     case Annot::type ## thetype: \
987                         type = #thetype ; \
988                         break;
989                 QByteArray type;
990                 switch ( subType )
991                 {
992                     CASE_FOR_TYPE( Widget )
993                     CASE_FOR_TYPE( Screen )
994                     CASE_FOR_TYPE( PrinterMark )
995                     CASE_FOR_TYPE( TrapNet )
996                     CASE_FOR_TYPE( Watermark )
997                     CASE_FOR_TYPE( 3D )
998                     default: type = QByteArray::number( subType );
999                 }
1000                 // MISSING: Widget, Screen, PrinterMark, TrapNet, Watermark, 3D
1001                 qDebug() << "Annotation" << type.constData() << "not supported.";
1002                 continue;
1003 #undef CASE_FOR_TYPE
1004             }
1005         }
1006 
1007         /** 1.3. PARSE common parameters */
1008         // -> boundary
1009         PDFRectangle *boundary = ann->getRect();
1010         // transform annotation rect to uniform coords
1011         QPointF topLeft, bottomRight;
1012         XPDFReader::transform( MTX, boundary->x1, boundary->y1, topLeft );
1013         XPDFReader::transform( MTX, boundary->x2, boundary->y2, bottomRight );
1014         QRectF boundaryRect;
1015         boundaryRect.setTopLeft(topLeft);
1016         boundaryRect.setBottomRight(bottomRight);
1017         if ( boundaryRect.left() > boundaryRect.right() )
1018         {
1019             double aux = boundaryRect.left();
1020             boundaryRect.setLeft( boundaryRect.right() );
1021             boundaryRect.setRight(aux);
1022         }
1023         if ( boundaryRect.top() > boundaryRect.bottom() )
1024         {
1025             double aux = boundaryRect.top();
1026             boundaryRect.setTop( boundaryRect.bottom() );
1027             boundaryRect.setBottom(aux);
1028            //annotation->rUnscaledWidth = (r[2] > r[0]) ? r[2] - r[0] : r[0] - r[2];
1029            //annotation->rUnscaledHeight = (r[3] > r[1]) ? r[3] - r[1] : r[1] - r[3];
1030         }
1031         annotation->setBoundary( boundaryRect );
1032         // -> contents
1033         annotation->setContents( UnicodeParsedString( ann->getContents() ) );
1034         // -> uniqueName
1035         annotation->setUniqueName( UnicodeParsedString( ann->getName() ) );
1036         // -> modifyDate (and -> creationDate)
1037         GooString *modDate = ann->getModified();
1038         if ( modDate )
1039         {
1040             annotation->setModificationDate( convertDate( modDate->getCString() ) );
1041         }
1042         if ( annotation->creationDate().isNull() && !annotation->modificationDate().isNull() )
1043             annotation->setCreationDate( annotation->modificationDate() );
1044         // -> flags: set the external attribute since it's embedded on file
1045         int annoflags = 0;
1046         annoflags |= Annotation::External;
1047         // -> flags
1048         int flags = ann->getFlags();
1049         if ( flags & Annot::flagHidden )
1050             annoflags |= Annotation::Hidden;
1051         if ( flags & Annot::flagNoZoom )
1052             annoflags |= Annotation::FixedSize;
1053         if ( flags & Annot::flagNoRotate )
1054             annoflags |= Annotation::FixedRotation;
1055         if ( !( flags & Annot::flagPrint ) )
1056             annoflags |= Annotation::DenyPrint;
1057         if ( flags & Annot::flagReadOnly )
1058             annoflags |= (Annotation::DenyWrite | Annotation::DenyDelete);
1059         if ( flags & Annot::flagLocked )
1060             annoflags |= Annotation::DenyDelete;
1061         if ( flags & Annot::flagToggleNoView )
1062             annoflags |= Annotation::ToggleHidingOnMouse;
1063         annotation->setFlags( annoflags );
1064         // -> style
1065         AnnotBorder *border = ann->getBorder();
1066         if ( border )
1067         {
1068             if ( border->getType() == AnnotBorder::typeArray )
1069             {
1070                 AnnotBorderArray * border_array = static_cast< AnnotBorderArray * >( border );
1071                 // -> style.xCorners
1072                 annotation->style.xCorners = border_array->getHorizontalCorner();
1073                 // -> style.yCorners
1074                 annotation->style.yCorners = border_array->getVerticalCorner();
1075             }
1076             // -> style.width
1077             annotation->style.width = border->getWidth();
1078             // -> style.style
1079             annotation->style.style = (Annotation::LineStyle)( 1 << border->getStyle() );
1080 #if 0
1081             // -> style.marks and style.spaces
1082             // TODO
1083             Object dashArray;
1084             bsObj.getDict()->lookup( "D", &dashArray );
1085             if ( dashArray.isArray() )
1086             {
1087                 int dashMarks = 3;
1088                 int dashSpaces = 0;
1089                 Object intObj;
1090                 dashArray.arrayGet( 0, &intObj );
1091                 if ( intObj.isInt() )
1092                     dashMarks = intObj.getInt();
1093                 intObj.free();
1094                 dashArray.arrayGet( 1, &intObj );
1095                 if ( intObj.isInt() )
1096                     dashSpaces = intObj.getInt();
1097                 intObj.free();
1098                 annotation->style.marks = dashMarks;
1099                 annotation->style.spaces = dashSpaces;
1100             }
1101 #endif
1102         }
1103         // -> style.color
1104         annotation->style.color = convertAnnotColor( ann->getColor() );
1105 
1106         /** 1.4. PARSE markup { common, Popup, Revision } parameters */
1107         if ( markupann )
1108         {
1109             // -> creationDate
1110             GooString *createDate = markupann->getDate();
1111             if ( createDate )
1112             {
1113                 annotation->setCreationDate( convertDate( createDate->getCString() ) );
1114             }
1115             // -> style.opacity
1116             annotation->style.opacity = markupann->getOpacity();
1117             // -> window.title and author
1118             annotation->window.title = UnicodeParsedString( markupann->getLabel() );
1119             annotation->setAuthor( annotation->window.title );
1120             // -> window.summary
1121             annotation->window.summary = UnicodeParsedString( markupann->getSubject() );
1122 #if 0
1123             // -> window.text
1124             // TODO
1125             XPDFReader::lookupString( annotDict, "RC", annotation->window.text );
1126 #endif
1127 
1128             // if a popup is referenced, schedule for resolving it later
1129             AnnotPopup *popup = markupann->getPopup();
1130             if ( popup )
1131             {
1132                 ResolveWindow request;
1133                 request.popup = popup;
1134                 request.annotation = annotation;
1135                 resolvePopList.append( request );
1136             }
1137 
1138             // if an older version is referenced, schedule for reparenting
1139             int parentID = markupann->getInReplyToID();
1140             if ( parentID )
1141             {
1142                 ResolveRevision request;
1143                 request.nextAnnotation = annotation;
1144                 request.nextAnnotationID = annotID;
1145                 request.prevAnnotationID = parentID;
1146 
1147                 // -> request.nextScope
1148                 request.nextScope = Annotation::Reply;
1149                 switch ( markupann->getReplyTo() )
1150                 {
1151                     case AnnotMarkup::replyTypeR:
1152                         request.nextScope = Annotation::Reply;
1153                         break;
1154                     case AnnotMarkup::replyTypeGroup:
1155                         request.nextScope = Annotation::Group;
1156                         break;
1157                 }
1158 
1159                 // -> revision.type (StateModel is deduced from type, not parsed)
1160                 request.nextType = Annotation::None;
1161                 if ( subType == Annot::typeText )
1162                 {
1163                     AnnotText * textann = static_cast< AnnotText * >( ann );
1164                     switch ( textann->getState() )
1165                     {
1166                         case AnnotText::stateMarked:
1167                             request.nextType = Annotation::Marked;
1168                             break;
1169                         case AnnotText::stateUnmarked:
1170                             request.nextType = Annotation::Unmarked;
1171                             break;
1172                         case AnnotText::stateAccepted:
1173                             request.nextType = Annotation::Accepted;
1174                             break;
1175                         case AnnotText::stateRejected:
1176                             request.nextType = Annotation::Rejected;
1177                             break;
1178                         case AnnotText::stateCancelled:
1179                             request.nextType = Annotation::Cancelled;
1180                             break;
1181                         case AnnotText::stateCompleted:
1182                             request.nextType = Annotation::Completed;
1183                             break;
1184                         case AnnotText::stateNone:
1185                         case AnnotText::stateUnknown:
1186                             ;
1187                     }
1188                 }
1189 
1190                 // schedule for later reparenting
1191                 resolveRevList.append( request );
1192             }
1193         }
1194 
1195         /** 1.5. ADD ANNOTATION to the annotationsMap  */
1196         if ( addToPage )
1197         {
1198             if ( annotationsMap.contains( annotID ) )
1199                 qDebug() << "Clash for annotations with ID:" << annotID;
1200             annotationsMap[ annotID ] = annotation;
1201         }
1202     } // end Annotation/PopupWindow parsing loop
1203 
1204     /** 2 - RESOLVE POPUPS (popup.* -> annotation.window) */
1205     if ( !resolvePopList.isEmpty() && !popupsMap.isEmpty() )
1206     {
1207         QLinkedList< ResolveWindow >::iterator it = resolvePopList.begin(),
1208                                               end = resolvePopList.end();
1209         for ( ; it != end; ++it )
1210         {
1211             const ResolveWindow & request = *it;
1212             if ( !popupsMap.contains( request.popup ) )
1213                 // warn aboud problems in popup resolving logic
1214                 qDebug() << "Cannot resolve popup"
1215                           << request.popup << ".";
1216             else
1217             {
1218                 // set annotation's window properties taking ones from popup
1219                 PopupWindow * pop = popupsMap[ request.popup ];
1220                 Annotation * pa = pop->dummyAnnotation;
1221                 Annotation::Window & w = request.annotation->window;
1222 
1223                 // transfer properties to Annotation's window
1224                 w.flags = pa->flags() & (Annotation::Hidden |
1225                     Annotation::FixedSize | Annotation::FixedRotation);
1226                 if ( !pop->shown )
1227                     w.flags |= Annotation::Hidden;
1228                 w.topLeft.setX(pa->boundary().left());
1229                 w.topLeft.setY(pa->boundary().top());
1230                 w.width = (int)( pa->boundary().right() - pa->boundary().left() );
1231                 w.height = (int)( pa->boundary().bottom() - pa->boundary().top() );
1232             }
1233         }
1234 
1235         // clear data
1236         QHash< AnnotPopup *, PopupWindow * >::Iterator dIt = popupsMap.begin(), dEnd = popupsMap.end();
1237         for ( ; dIt != dEnd; ++dIt )
1238         {
1239             PopupWindow * p = dIt.value();
1240             delete p->dummyAnnotation;
1241             delete p;
1242         }
1243     }
1244 
1245     /** 3 - RESOLVE REVISIONS (parent.revisions.append( children )) */
1246     if ( !resolveRevList.isEmpty() )
1247     {
1248         // append children to parents
1249         QVarLengthArray< int > excludeIDs( resolveRevList.count() );   // can't even reach this size
1250         int excludeIndex = 0;                       // index in excludeIDs array
1251         QLinkedList< ResolveRevision >::iterator it = resolveRevList.begin(), end = resolveRevList.end();
1252         for ( ; it != end; ++it )
1253         {
1254             const ResolveRevision & request = *it;
1255             int parentID = request.prevAnnotationID;
1256             if ( !annotationsMap.contains( parentID ) )
1257                 // warn about problems in reparenting logic
1258                 qDebug() << "Cannot reparent annotation to"
1259                           << parentID << ".";
1260             else
1261             {
1262                 // compile and add a Revision structure to the parent annotation
1263                 Annotation::Revision childRevision;
1264                 childRevision.annotation = request.nextAnnotation;
1265                 childRevision.scope = request.nextScope;
1266                 childRevision.type = request.nextType;
1267                 annotationsMap[ parentID ]->revisions().append( childRevision );
1268                 // exclude child annotation from being rooted in page
1269                 excludeIDs[ excludeIndex++ ] = request.nextAnnotationID;
1270             }
1271         }
1272 
1273         // prevent children from being attached to page as roots
1274         for ( int i = 0; i < excludeIndex; i++ )
1275             annotationsMap.remove( excludeIDs[ i ] );
1276     }
1277 
1278     /** 4 - POSTPROCESS TextAnnotations (when window geom is embedded) */
1279     if ( !ppTextList.isEmpty() )
1280     {
1281         QLinkedList< PostProcessText >::const_iterator it = ppTextList.begin(), end = ppTextList.end();
1282         for ( ; it != end; ++it )
1283         {
1284             const PostProcessText & request = *it;
1285             Annotation::Window & window = request.textAnnotation->window;
1286             // if not present, 'create' the window in-place over the annotation
1287             if ( window.flags == -1 )
1288             {
1289                 window.flags = 0;
1290                 QRectF geom = request.textAnnotation->boundary();
1291                 // initialize window geometry to annotation's one
1292                 window.width = (int)( geom.right() - geom.left() );
1293                 window.height = (int)( geom.bottom() - geom.top() );
1294                 window.topLeft.setX( geom.left() > 0.0 ? geom.left() : 0.0 );
1295                 window.topLeft.setY( geom.top() > 0.0 ? geom.top() : 0.0 );
1296             }
1297             // (pdf) if text is not 'opened', force window hiding. if the window
1298             // was parsed from popup, the flag should already be set
1299             if ( !request.opened && window.flags != -1 )
1300                 window.flags |= Annotation::Hidden;
1301         }
1302     }
1303 
1304     delete annots;
1305     /** 5 - finally RETURN ANNOTATIONS */
1306     return annotationsMap.values();
1307 }
1308 
formFields() const1309 QList<FormField*> Page::formFields() const
1310 {
1311   QList<FormField*> fields;
1312   ::Page *p = m_page->page;
1313   ::FormPageWidgets * form = p->getPageWidgets();
1314   int formcount = form->getNumWidgets();
1315   for (int i = 0; i < formcount; ++i)
1316   {
1317     ::FormWidget *fm = form->getWidget(i);
1318     FormField * ff = NULL;
1319     switch (fm->getType())
1320     {
1321       case formButton:
1322       {
1323         ff = new FormFieldButton(m_page->parentDoc, p, static_cast<FormWidgetButton*>(fm));
1324       }
1325       break;
1326 
1327       case formText:
1328       {
1329         ff = new FormFieldText(m_page->parentDoc, p, static_cast<FormWidgetText*>(fm));
1330       }
1331       break;
1332 
1333       case formChoice:
1334       {
1335         ff = new FormFieldChoice(m_page->parentDoc, p, static_cast<FormWidgetChoice*>(fm));
1336       }
1337       break;
1338 
1339       default: ;
1340     }
1341 
1342     if (ff)
1343       fields.append(ff);
1344   }
1345 
1346   return fields;
1347 }
1348 
duration() const1349 double Page::duration() const
1350 {
1351   return m_page->page->getDuration();
1352 }
1353 
label() const1354 QString Page::label() const
1355 {
1356   GooString goo;
1357   if (!m_page->parentDoc->doc->getCatalog()->indexToLabel(m_page->index, &goo))
1358     return QString();
1359 
1360   return QString(goo.getCString());
1361 }
1362 
1363 
1364 }
1365