blob: 6dbf50f94f402dc5b65ccb0ae61fd0df9e67320b [file] [log] [blame]
/* poppler-page.cc: qt interface to poppler
* Copyright (C) 2005, Net Integration Technologies, Inc.
* Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
* Copyright (C) 2005-2010, Albert Astals Cid <aacid@kde.org>
* Copyright (C) 2005, Stefan Kebekus <stefan.kebekus@math.uni-koeln.de>
* Copyright (C) 2006-2010, Pino Toscano <pino@kde.org>
* Copyright (C) 2008 Carlos Garcia Campos <carlosgc@gnome.org>
* Copyright (C) 2009 Shawn Rutledge <shawn.t.rutledge@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <poppler-qt4.h>
#include <QtCore/QHash>
#include <QtCore/QMap>
#include <QtCore/QVarLengthArray>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <config.h>
#include <PDFDoc.h>
#include <Catalog.h>
#include <Form.h>
#include <ErrorCodes.h>
#include <TextOutputDev.h>
#include <Annot.h>
#include <Link.h>
#include <ArthurOutputDev.h>
#if defined(HAVE_SPLASH)
#include <SplashOutputDev.h>
#include <splash/SplashBitmap.h>
#endif
#include "poppler-private.h"
#include "poppler-page-transition-private.h"
#include "poppler-page-private.h"
#include "poppler-link-extractor-private.h"
#include "poppler-annotation-helper.h"
#include "poppler-annotation-private.h"
#include "poppler-form.h"
namespace Poppler {
class DummyAnnotation : public Annotation
{
public:
DummyAnnotation()
: Annotation( *new AnnotationPrivate() )
{
}
virtual SubType subType() const { return A_BASE; }
};
Link* PageData::convertLinkActionToLink(::LinkAction * a, const QRectF &linkArea)
{
return convertLinkActionToLink(a, parentDoc, linkArea);
}
Link* PageData::convertLinkActionToLink(::LinkAction * a, DocumentData *parentDoc, const QRectF &linkArea)
{
if ( !a )
return NULL;
Link * popplerLink = NULL;
switch ( a->getKind() )
{
case actionGoTo:
{
LinkGoTo * g = (LinkGoTo *) a;
const LinkDestinationData ldd( g->getDest(), g->getNamedDest(), parentDoc, false );
// create link: no ext file, namedDest, object pointer
popplerLink = new LinkGoto( linkArea, QString::null, LinkDestination( ldd ) );
}
break;
case actionGoToR:
{
LinkGoToR * g = (LinkGoToR *) a;
// copy link file
const QString fileName = UnicodeParsedString( g->getFileName() );
const LinkDestinationData ldd( g->getDest(), g->getNamedDest(), parentDoc, !fileName.isEmpty() );
// ceate link: fileName, namedDest, object pointer
popplerLink = new LinkGoto( linkArea, fileName, LinkDestination( ldd ) );
}
break;
case actionLaunch:
{
LinkLaunch * e = (LinkLaunch *)a;
GooString * p = e->getParams();
popplerLink = new LinkExecute( linkArea, e->getFileName()->getCString(), p ? p->getCString() : 0 );
}
break;
case actionNamed:
{
const char * name = ((LinkNamed *)a)->getName()->getCString();
if ( !strcmp( name, "NextPage" ) )
popplerLink = new LinkAction( linkArea, LinkAction::PageNext );
else if ( !strcmp( name, "PrevPage" ) )
popplerLink = new LinkAction( linkArea, LinkAction::PagePrev );
else if ( !strcmp( name, "FirstPage" ) )
popplerLink = new LinkAction( linkArea, LinkAction::PageFirst );
else if ( !strcmp( name, "LastPage" ) )
popplerLink = new LinkAction( linkArea, LinkAction::PageLast );
else if ( !strcmp( name, "GoBack" ) )
popplerLink = new LinkAction( linkArea, LinkAction::HistoryBack );
else if ( !strcmp( name, "GoForward" ) )
popplerLink = new LinkAction( linkArea, LinkAction::HistoryForward );
else if ( !strcmp( name, "Quit" ) )
popplerLink = new LinkAction( linkArea, LinkAction::Quit );
else if ( !strcmp( name, "GoToPage" ) )
popplerLink = new LinkAction( linkArea, LinkAction::GoToPage );
else if ( !strcmp( name, "Find" ) )
popplerLink = new LinkAction( linkArea, LinkAction::Find );
else if ( !strcmp( name, "FullScreen" ) )
popplerLink = new LinkAction( linkArea, LinkAction::Presentation );
else if ( !strcmp( name, "Close" ) )
{
// acroread closes the document always, doesnt care whether
// its presentation mode or not
// popplerLink = new LinkAction( linkArea, LinkAction::EndPresentation );
popplerLink = new LinkAction( linkArea, LinkAction::Close );
}
else
{
// TODO
}
}
break;
case actionURI:
{
popplerLink = new LinkBrowse( linkArea, ((LinkURI *)a)->getURI()->getCString() );
}
break;
case actionSound:
{
::LinkSound *ls = (::LinkSound *)a;
popplerLink = new LinkSound( linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject( ls->getSound() ) );
}
break;
case actionJavaScript:
{
::LinkJavaScript *ljs = (::LinkJavaScript *)a;
popplerLink = new LinkJavaScript( linkArea, UnicodeParsedString(ljs->getScript()) );
}
break;
case actionMovie:
/* TODO this (Movie link)
m_type = Movie;
LinkMovie * m = (LinkMovie *) a;
// copy Movie parameters (2 IDs and a const char *)
Ref * r = m->getAnnotRef();
m_refNum = r->num;
m_refGen = r->gen;
copyString( m_uri, m->getTitle()->getCString() );
*/ break;
case actionUnknown:
break;
}
return popplerLink;
}
Page::Page(DocumentData *doc, int index) {
m_page = new PageData();
m_page->index = index;
m_page->parentDoc = doc;
m_page->page = doc->doc->getCatalog()->getPage(m_page->index + 1);
m_page->transition = 0;
}
Page::~Page()
{
delete m_page->transition;
delete m_page;
}
QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate) const
{
int rotation = (int)rotate * 90;
QImage img;
switch(m_page->parentDoc->m_backend)
{
case Poppler::Document::SplashBackend:
{
#if defined(HAVE_SPLASH)
SplashOutputDev *splash_output = static_cast<SplashOutputDev *>(m_page->parentDoc->getOutputDev());
m_page->parentDoc->doc->displayPageSlice(splash_output, m_page->index + 1, xres, yres,
rotation, false, true, false, x, y, w, h);
SplashBitmap *bitmap = splash_output->getBitmap();
int bw = bitmap->getWidth();
int bh = bitmap->getHeight();
SplashColorPtr dataPtr = splash_output->getBitmap()->getDataPtr();
if (QSysInfo::BigEndian == QSysInfo::ByteOrder)
{
uchar c;
int count = bw * bh * 4;
for (int k = 0; k < count; k += 4)
{
c = dataPtr[k];
dataPtr[k] = dataPtr[k+3];
dataPtr[k+3] = c;
c = dataPtr[k+1];
dataPtr[k+1] = dataPtr[k+2];
dataPtr[k+2] = c;
}
}
// construct a qimage SHARING the raw bitmap data in memory
QImage tmpimg( dataPtr, bw, bh, QImage::Format_ARGB32 );
img = tmpimg.copy();
// unload underlying xpdf bitmap
splash_output->startPage( 0, NULL );
#endif
break;
}
case Poppler::Document::ArthurBackend:
{
QSize size = pageSize();
QImage tmpimg(w == -1 ? qRound( size.width() * xres / 72.0 ) : w, h == -1 ? qRound( size.height() * yres / 72.0 ) : h, QImage::Format_ARGB32);
QPainter painter(&tmpimg);
if (m_page->parentDoc->m_hints & Document::Antialiasing)
painter.setRenderHint(QPainter::Antialiasing);
if (m_page->parentDoc->m_hints & Document::TextAntialiasing)
painter.setRenderHint(QPainter::TextAntialiasing);
painter.translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y);
ArthurOutputDev arthur_output(&painter);
arthur_output.startDoc(m_page->parentDoc->doc->getXRef());
m_page->parentDoc->doc->displayPageSlice(&arthur_output,
m_page->index + 1,
xres,
yres,
rotation,
false,
true,
false,
x,
y,
w,
h);
painter.end();
img = tmpimg;
break;
}
}
return img;
}
QImage Page::thumbnail() const
{
unsigned char* data = 0;
int w = 0;
int h = 0;
int rowstride = 0;
GBool r = m_page->page->loadThumb(&data, &w, &h, &rowstride);
QImage ret;
if (r)
{
// first construct a temporary image with the data got,
// then force a copy of it so we can free the raw thumbnail data
ret = QImage(data, w, h, rowstride, QImage::Format_RGB888).copy();
gfree(data);
}
return ret;
}
QString Page::text(const QRectF &r) const
{
TextOutputDev *output_dev;
GooString *s;
PDFRectangle *rect;
QString result;
output_dev = new TextOutputDev(0, gFalse, gFalse, gFalse);
m_page->parentDoc->doc->displayPageSlice(output_dev, m_page->index + 1, 72, 72,
0, false, true, false, -1, -1, -1, -1);
if (r.isNull())
{
rect = m_page->page->getCropBox();
s = output_dev->getText(rect->x1, rect->y1, rect->x2, rect->y2);
}
else
{
s = output_dev->getText(r.left(), r.top(), r.right(), r.bottom());
}
result = QString::fromUtf8(s->getCString());
delete output_dev;
delete s;
return result;
}
bool Page::search(const QString &text, double &sLeft, double &sTop, double &sRight, double &sBottom, SearchDirection direction, SearchMode caseSensitive, Rotation rotate) const
{
const QChar * str = text.unicode();
int len = text.length();
QVector<Unicode> u(len);
for (int i = 0; i < len; ++i) u[i] = str[i].unicode();
GBool sCase;
if (caseSensitive == CaseSensitive) sCase = gTrue;
else sCase = gFalse;
bool found = false;
int rotation = (int)rotate * 90;
// fetch ourselves a textpage
TextOutputDev td(NULL, gTrue, gFalse, gFalse);
m_page->parentDoc->doc->displayPage( &td, m_page->index + 1, 72, 72, rotation, false, true, false );
TextPage *textPage=td.takeText();
if (direction == FromTop)
found = textPage->findText( u.data(), len,
gTrue, gTrue, gFalse, gFalse, sCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
else if ( direction == NextResult )
found = textPage->findText( u.data(), len,
gFalse, gTrue, gTrue, gFalse, sCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
else if ( direction == PreviousResult )
found = textPage->findText( u.data(), len,
gFalse, gTrue, gTrue, gFalse, sCase, gTrue, &sLeft, &sTop, &sRight, &sBottom );
textPage->decRefCnt();
return found;
}
bool Page::search(const QString &text, QRectF &rect, SearchDirection direction, SearchMode caseSensitive, Rotation rotate) const
{
double sLeft, sTop, sRight, sBottom;
sLeft = rect.left();
sTop = rect.top();
sRight = rect.right();
sBottom = rect.bottom();
bool found = search(text, sLeft, sTop, sRight, sBottom, direction, caseSensitive, rotate);
rect.setLeft( sLeft );
rect.setTop( sTop );
rect.setRight( sRight );
rect.setBottom( sBottom );
return found;
}
QList<TextBox*> Page::textList(Rotation rotate) const
{
TextOutputDev *output_dev;
QList<TextBox*> output_list;
output_dev = new TextOutputDev(0, gFalse, gFalse, gFalse);
int rotation = (int)rotate * 90;
m_page->parentDoc->doc->displayPageSlice(output_dev, m_page->index + 1, 72, 72,
rotation, false, false, false, -1, -1, -1, -1);
TextWordList *word_list = output_dev->makeWordList();
if (!word_list) {
delete output_dev;
return output_list;
}
QHash<TextWord *, TextBox*> wordBoxMap;
for (int i = 0; i < word_list->getLength(); i++) {
TextWord *word = word_list->get(i);
GooString *gooWord = word->getText();
QString string = QString::fromUtf8(gooWord->getCString());
delete gooWord;
double xMin, yMin, xMax, yMax;
word->getBBox(&xMin, &yMin, &xMax, &yMax);
TextBox* text_box = new TextBox(string, QRectF(xMin, yMin, xMax-xMin, yMax-yMin));
text_box->m_data->hasSpaceAfter = word->hasSpaceAfter() == gTrue;
text_box->m_data->charBBoxes.reserve(word->getLength());
for (int j = 0; j < word->getLength(); ++j)
{
word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax);
text_box->m_data->charBBoxes.append(QRectF(xMin, yMin, xMax-xMin, yMax-yMin));
}
wordBoxMap.insert(word, text_box);
output_list.append(text_box);
}
for (int i = 0; i < word_list->getLength(); i++) {
TextWord *word = word_list->get(i);
TextBox* text_box = wordBoxMap.value(word);
text_box->m_data->nextWord = wordBoxMap.value(word->nextWord());
}
delete word_list;
delete output_dev;
return output_list;
}
PageTransition *Page::transition() const
{
if (!m_page->transition) {
Object o;
PageTransitionParams params;
params.dictObj = m_page->page->getTrans(&o);
if (params.dictObj->isDict()) m_page->transition = new PageTransition(params);
o.free();
}
return m_page->transition;
}
Link *Page::action( PageAction act ) const
{
if ( act == Page::Opening || act == Page::Closing )
{
Object o;
m_page->page->getActions(&o);
if (!o.isDict())
{
o.free();
return 0;
}
Dict *dict = o.getDict();
Object o2;
const char *key = act == Page::Opening ? "O" : "C";
dict->lookup((char*)key, &o2);
::LinkAction *lact = ::LinkAction::parseAction(&o2, m_page->parentDoc->doc->getCatalog()->getBaseURI() );
o2.free();
o.free();
Link *popplerLink = NULL;
if (lact != NULL)
{
popplerLink = m_page->convertLinkActionToLink(lact, QRectF());
delete lact;
}
return popplerLink;
}
return 0;
}
QSizeF Page::pageSizeF() const
{
Page::Orientation orient = orientation();
if ( ( Page::Landscape == orient ) || (Page::Seascape == orient ) ) {
return QSizeF( m_page->page->getCropHeight(), m_page->page->getCropWidth() );
} else {
return QSizeF( m_page->page->getCropWidth(), m_page->page->getCropHeight() );
}
}
QSize Page::pageSize() const
{
return pageSizeF().toSize();
}
Page::Orientation Page::orientation() const
{
const int rotation = m_page->page->getRotate();
switch (rotation) {
case 90:
return Page::Landscape;
break;
case 180:
return Page::UpsideDown;
break;
case 270:
return Page::Seascape;
break;
default:
return Page::Portrait;
}
}
void Page::defaultCTM(double *CTM, double dpiX, double dpiY, int rotate, bool upsideDown)
{
m_page->page->getDefaultCTM(CTM, dpiX, dpiY, rotate, gFalse, upsideDown);
}
QList<Link*> Page::links() const
{
LinkExtractorOutputDev link_dev(m_page);
m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1);
QList<Link*> popplerLinks = link_dev.links();
return popplerLinks;
}
QList<Annotation*> Page::annotations() const
{
::Page *pdfPage = m_page->page;
Annots* annots = pdfPage->getAnnots(m_page->parentDoc->doc->getCatalog());
const uint numAnnotations = annots->getNumAnnots();
if ( numAnnotations == 0 )
{
delete annots;
return QList<Annotation*>();
}
// ID to Annotation/PopupWindow maps
QMap< int, Annotation * > annotationsMap;
QHash< AnnotPopup *, PopupWindow * > popupsMap;
// lists of Windows and Revisions that needs resolution
QLinkedList< ResolveRevision > resolveRevList;
QLinkedList< ResolveWindow > resolvePopList;
QLinkedList< PostProcessText > ppTextList;
// build a normalized transform matrix for this page at 100% scale
GfxState * gfxState = new GfxState( 72.0, 72.0, pdfPage->getCropBox(), pdfPage->getRotate(), gTrue );
double * gfxCTM = gfxState->getCTM();
double MTX[6];
for ( int i = 0; i < 6; i+=2 )
{
MTX[i] = gfxCTM[i] / pdfPage->getCropWidth();
MTX[i+1] = gfxCTM[i+1] / pdfPage->getCropHeight();
}
delete gfxState;
/** 1 - PARSE ALL ANNOTATIONS AND POPUPS FROM THE PAGE */
for ( uint j = 0; j < numAnnotations; j++ )
{
// get the j-th annotation
Annot * ann = annots->getAnnot( j );
if ( !ann )
{
qDebug() << "Annot" << j << "is null.";
continue;
}
Annotation * annotation = 0;
int annotID = ann->getId();
AnnotMarkup * markupann = dynamic_cast< AnnotMarkup * >( ann );
bool addToPage = true; // Popup annots are added to custom queue
/** 1.1. GET Subtype */
Annot::AnnotSubtype subType = ann->getType();
/** 1.2. CREATE Annotation / PopupWindow and PARSE specific params */
switch ( subType )
{
case Annot::typeText:
{
// parse TextAnnotation params
AnnotText * textann = static_cast< AnnotText * >( ann );
TextAnnotation * t = new TextAnnotation();
annotation = t;
// -> textType
t->setTextType( TextAnnotation::Linked );
// -> textIcon
t->setTextIcon( QString::fromLatin1( textann->getIcon()->getCString() ) );
// request for postprocessing window geometry
PostProcessText request;
request.textAnnotation = t;
request.opened = textann->getOpen();
ppTextList.append( request );
break;
}
case Annot::typeFreeText:
{
// parse TextAnnotation params
AnnotFreeText * textann = static_cast< AnnotFreeText * >( ann );
TextAnnotation * t = new TextAnnotation();
annotation = t;
// -> textType
t->setTextType( TextAnnotation::InPlace );
#if 0
// -> textFont
QString textFormat;
XPDFReader::lookupString( annotDict, "DA", textFormat );
// TODO, fill t->textFont using textFormat if not empty
#endif
// -> inplaceAlign
t->setInplaceAlign( textann->getQuadding() );
// -> inplaceText (simple)
QString tmpstring;
GooString *styleString = textann->getStyleString();
if ( styleString )
tmpstring = UnicodeParsedString( styleString );
#if 0
// -> inplaceText (complex override)
XPDFReader::lookupString( annotDict, "RC", tmpstring );
#endif
t->setInplaceText( tmpstring );
// -> inplaceCallout
AnnotCalloutLine *callout = textann->getCalloutLine();
if ( callout )
{
QPointF tmppoint;
XPDFReader::transform( MTX, callout->getX1(), callout->getY1(), tmppoint );
t->setCalloutPoint( 0, tmppoint );
XPDFReader::transform( MTX, callout->getX2(), callout->getY2(), tmppoint );
t->setCalloutPoint( 1, tmppoint );
if ( AnnotCalloutMultiLine * callout_v6 = dynamic_cast< AnnotCalloutMultiLine * >( callout ) )
{
XPDFReader::transform( MTX, callout_v6->getX3(), callout_v6->getY3(), tmppoint );
t->setCalloutPoint( 2, tmppoint );
}
}
// -> inplaceIntent
t->setInplaceIntent( (TextAnnotation::InplaceIntent)textann->getIntent() );
AnnotBorderEffect *border_effect = textann->getBorderEffect();
if ( border_effect )
{
// -> style.effect
t->style.effect = (Annotation::LineEffect)border_effect->getEffectType();
// -> style.effectIntensity
t->style.effectIntensity = (int)border_effect->getIntensity();
}
break;
}
case Annot::typeLine:
{
// parse LineAnnotation params
AnnotLine * lineann = static_cast< AnnotLine * >( ann );
LineAnnotation * l = new LineAnnotation();
annotation = l;
// -> linePoints
QLinkedList<QPointF> linePoints;
QPointF p;
XPDFReader::transform( MTX, lineann->getX1(), lineann->getY1(), p );
linePoints.push_back( p );
XPDFReader::transform( MTX, lineann->getX2(), lineann->getY2(), p );
linePoints.push_back( p );
l->setLinePoints( linePoints );
// -> lineStartStyle
l->setLineStartStyle( (LineAnnotation::TermStyle)lineann->getStartStyle() );
// -> lineEndStyle
l->setLineEndStyle( (LineAnnotation::TermStyle)lineann->getEndStyle() );
// -> lineClosed
l->setLineClosed( false );
// -> lineInnerColor
l->setLineInnerColor( convertAnnotColor( lineann->getInteriorColor() ) );
// -> lineLeadingFwdPt
l->setLineLeadingForwardPoint( lineann->getLeaderLineLength() );
// -> lineLeadingBackPt
l->setLineLeadingBackPoint( lineann->getLeaderLineExtension() );
// -> lineShowCaption
l->setLineShowCaption( lineann->getCaption() );
// -> lineIntent
l->setLineIntent( (LineAnnotation::LineIntent)( lineann->getIntent() + 1 ) );
break;
}
case Annot::typePolygon:
case Annot::typePolyLine:
{
// parse LineAnnotation params
AnnotPolygon * polyann = static_cast< AnnotPolygon * >( ann );
LineAnnotation * l = new LineAnnotation();
annotation = l;
// -> polyPoints
QLinkedList<QPointF> polyPoints;
AnnotPath * vertices = polyann->getVertices();
for ( int i = 0; i < vertices->getCoordsLength(); ++i )
{
QPointF p;
XPDFReader::transform( MTX, vertices->getX( i ), vertices->getY( i ), p );
polyPoints.push_back( p );
}
l->setLinePoints( polyPoints );
// -> polyStartStyle
l->setLineStartStyle( (LineAnnotation::TermStyle)polyann->getStartStyle() );
// -> polyEndStyle
l->setLineEndStyle( (LineAnnotation::TermStyle)polyann->getEndStyle() );
// -> polyClosed
l->setLineClosed( subType == Annot::typePolygon );
// -> polyInnerColor
l->setLineInnerColor( convertAnnotColor( polyann->getInteriorColor() ) );
// -> polyIntent
switch ( polyann->getIntent() )
{
case AnnotPolygon::polygonCloud:
l->setLineIntent( LineAnnotation::PolygonCloud );
break;
case AnnotPolygon::polylineDimension:
case AnnotPolygon::polygonDimension:
l->setLineIntent( LineAnnotation::Dimension );
break;
}
break;
}
case Annot::typeSquare:
case Annot::typeCircle:
{
// parse GeomAnnotation params
AnnotGeometry * geomann = static_cast< AnnotGeometry * >( ann );
GeomAnnotation * g = new GeomAnnotation();
annotation = g;
// -> highlightType
if ( subType == Annot::typeSquare )
g->setGeomType( GeomAnnotation::InscribedSquare );
else if ( subType == Annot::typeCircle )
g->setGeomType( GeomAnnotation::InscribedCircle );
// -> geomInnerColor
g->setGeomInnerColor( convertAnnotColor( geomann->getInteriorColor() ) );
AnnotBorderEffect *border_effect = geomann->getBorderEffect();
if ( border_effect )
{
// -> style.effect
g->style.effect = (Annotation::LineEffect)border_effect->getEffectType();
// -> style.effectIntensity
g->style.effectIntensity = (int)border_effect->getIntensity();
}
// TODO RD
break;
}
case Annot::typeHighlight:
case Annot::typeUnderline:
case Annot::typeSquiggly:
case Annot::typeStrikeOut:
{
AnnotTextMarkup * hlann = static_cast< AnnotTextMarkup * >( ann );
HighlightAnnotation * h = new HighlightAnnotation();
annotation = h;
// -> highlightType
if ( subType == Annot::typeHighlight )
h->setHighlightType( HighlightAnnotation::Highlight );
else if ( subType == Annot::typeUnderline )
h->setHighlightType( HighlightAnnotation::Underline );
else if ( subType == Annot::typeSquiggly )
h->setHighlightType( HighlightAnnotation::Squiggly );
else if ( subType == Annot::typeStrikeOut )
h->setHighlightType( HighlightAnnotation::StrikeOut );
// -> highlightQuads
AnnotQuadrilaterals *hlquads = hlann->getQuadrilaterals();
if ( !hlquads || !hlquads->getQuadrilateralsLength() )
{
qDebug() << "Not enough quads for a Highlight annotation.";
delete annotation;
continue;
}
QList< HighlightAnnotation::Quad > quads;
const int quadsCount = hlquads->getQuadrilateralsLength();
for ( int q = 0; q < quadsCount; ++q )
{
HighlightAnnotation::Quad quad;
XPDFReader::transform( MTX, hlquads->getX1( q ), hlquads->getY1( q ), quad.points[ 0 ] );
XPDFReader::transform( MTX, hlquads->getX2( q ), hlquads->getY2( q ), quad.points[ 1 ] );
XPDFReader::transform( MTX, hlquads->getX3( q ), hlquads->getY3( q ), quad.points[ 2 ] );
XPDFReader::transform( MTX, hlquads->getX4( q ), hlquads->getY4( q ), quad.points[ 3 ] );
// ### PDF1.6 specs says that point are in ccw order, but in fact
// points 3 and 4 are swapped in every PDF around!
QPointF tmpPoint = quad.points[ 2 ];
quad.points[ 2 ] = quad.points[ 3 ];
quad.points[ 3 ] = tmpPoint;
// initialize other properties and append quad
quad.capStart = true; // unlinked quads are always capped
quad.capEnd = true; // unlinked quads are always capped
quad.feather = 0.1; // default feather
quads.append( quad );
}
h->setHighlightQuads( quads );
break;
}
case Annot::typeStamp:
{
AnnotStamp * stampann = static_cast< AnnotStamp * >( ann );
StampAnnotation * s = new StampAnnotation();
annotation = s;
// -> stampIcon
s->setStampIconName( QString::fromLatin1( stampann->getIcon()->getCString() ) );
break;
}
case Annot::typeInk:
{
// parse InkAnnotation params
AnnotInk * inkann = static_cast< AnnotInk * >( ann );
InkAnnotation * k = new InkAnnotation();
annotation = k;
// -> inkPaths
AnnotPath ** paths = inkann->getInkList();
if ( !paths || !inkann->getInkListLength() )
{
qDebug() << "InkList not present for ink annot";
delete annotation;
continue;
}
QList< QLinkedList<QPointF> > inkPaths;
const int pathsNumber = inkann->getInkListLength();
for ( int m = 0; m < pathsNumber; m++ )
{
// transform each path in a list of normalized points ..
QLinkedList<QPointF> localList;
AnnotPath * path = paths[ m ];
const int pointsNumber = path ? path->getCoordsLength() : 0;
for ( int n = 0; n < pointsNumber; ++n )
{
// get the x,y numbers for current point
double x = path->getX( n );
double y = path->getY( n );
// add normalized point to localList
QPointF np;
XPDFReader::transform( MTX, x, y, np );
localList.push_back( np );
}
// ..and add it to the annotation
inkPaths.push_back( localList );
}
k->setInkPaths( inkPaths );
break;
}
case Annot::typePopup:
{
AnnotPopup * popupann = static_cast< AnnotPopup * >( ann );
// create PopupWindow and add it to the popupsMap
PopupWindow * popup = new PopupWindow();
popupsMap[ popupann ] = popup;
addToPage = false;
// get window specific properties if any
popup->shown = popupann->getOpen();
// no need to parse parent annotation id
//XPDFReader::lookupIntRef( annotDict, "Parent", popup->... );
// use the 'dummy annotation' for getting other parameters
popup->dummyAnnotation = new DummyAnnotation();
annotation = popup->dummyAnnotation;
break;
}
case Annot::typeLink:
{
// parse Link params
AnnotLink * linkann = static_cast< AnnotLink * >( ann );
LinkAnnotation * l = new LinkAnnotation();
annotation = l;
// -> hlMode
l->setLinkHighlightMode( (LinkAnnotation::HighlightMode)linkann->getLinkEffect() );
// -> link region
// TODO
// reading link action
if ( !linkann->getDest()->isNull() )
{
::LinkAction *act = ::LinkAction::parseDest( linkann->getDest() );
Link * popplerLink = m_page->convertLinkActionToLink( act, QRectF() );
delete act;
if ( popplerLink )
{
l->setLinkDestination( popplerLink );
}
}
break;
}
case Annot::typeCaret:
{
// parse CaretAnnotation params
AnnotCaret * caretann = static_cast< AnnotCaret * >( ann );
CaretAnnotation * c = new CaretAnnotation();
annotation = c;
// -> caretSymbol
c->setCaretSymbol( (CaretAnnotation::CaretSymbol)caretann->getSymbol() );
// TODO RD
break;
}
case Annot::typeFileAttachment:
{
AnnotFileAttachment * attachann = static_cast< AnnotFileAttachment * >( ann );
FileAttachmentAnnotation * f = new FileAttachmentAnnotation();
annotation = f;
// -> fileIcon
f->setFileIconName( QString::fromLatin1( attachann->getName()->getCString() ) );
// -> embeddedFile
EmbFile *embfile = new EmbFile( attachann->getFile(), attachann->getContents() );
f->setEmbeddedFile( new EmbeddedFile( embfile ) );
break;
}
case Annot::typeSound:
{
AnnotSound * soundann = static_cast< AnnotSound * >( ann );
SoundAnnotation * s = new SoundAnnotation();
annotation = s;
// -> soundIcon
s->setSoundIconName( QString::fromLatin1( soundann->getName()->getCString() ) );
// -> sound
s->setSound( new SoundObject( soundann->getSound() ) );
break;
}
case Annot::typeMovie:
{
AnnotMovie * movieann = static_cast< AnnotMovie * >( ann );
MovieAnnotation * m = new MovieAnnotation();
annotation = m;
// -> movie
MovieObject *movie = new MovieObject( movieann );
m->setMovie( movie );
// -> movieTitle
GooString * movietitle = movieann->getTitle();
if ( movietitle )
{
m->setMovieTitle( QString::fromLatin1( movietitle->getCString() ) );
}
break;
}
// special case for ignoring unknwon annotations
case Annot::typeUnknown:
continue;
default:
{
#define CASE_FOR_TYPE( thetype ) \
case Annot::type ## thetype: \
type = #thetype ; \
break;
QByteArray type;
switch ( subType )
{
CASE_FOR_TYPE( Widget )
CASE_FOR_TYPE( Screen )
CASE_FOR_TYPE( PrinterMark )
CASE_FOR_TYPE( TrapNet )
CASE_FOR_TYPE( Watermark )
CASE_FOR_TYPE( 3D )
default: type = QByteArray::number( subType );
}
// MISSING: Widget, Screen, PrinterMark, TrapNet, Watermark, 3D
qDebug() << "Annotation" << type.constData() << "not supported.";
continue;
#undef CASE_FOR_TYPE
}
}
/** 1.3. PARSE common parameters */
// -> boundary
PDFRectangle *boundary = ann->getRect();
// transform annotation rect to uniform coords
QPointF topLeft, bottomRight;
XPDFReader::transform( MTX, boundary->x1, boundary->y1, topLeft );
XPDFReader::transform( MTX, boundary->x2, boundary->y2, bottomRight );
QRectF boundaryRect;
boundaryRect.setTopLeft(topLeft);
boundaryRect.setBottomRight(bottomRight);
if ( boundaryRect.left() > boundaryRect.right() )
{
double aux = boundaryRect.left();
boundaryRect.setLeft( boundaryRect.right() );
boundaryRect.setRight(aux);
}
if ( boundaryRect.top() > boundaryRect.bottom() )
{
double aux = boundaryRect.top();
boundaryRect.setTop( boundaryRect.bottom() );
boundaryRect.setBottom(aux);
//annotation->rUnscaledWidth = (r[2] > r[0]) ? r[2] - r[0] : r[0] - r[2];
//annotation->rUnscaledHeight = (r[3] > r[1]) ? r[3] - r[1] : r[1] - r[3];
}
annotation->setBoundary( boundaryRect );
// -> contents
annotation->setContents( UnicodeParsedString( ann->getContents() ) );
// -> uniqueName
annotation->setUniqueName( UnicodeParsedString( ann->getName() ) );
// -> modifyDate (and -> creationDate)
GooString *modDate = ann->getModified();
if ( modDate )
{
annotation->setModificationDate( convertDate( modDate->getCString() ) );
}
if ( annotation->creationDate().isNull() && !annotation->modificationDate().isNull() )
annotation->setCreationDate( annotation->modificationDate() );
// -> flags: set the external attribute since it's embedded on file
int annoflags = 0;
annoflags |= Annotation::External;
// -> flags
int flags = ann->getFlags();
if ( flags & Annot::flagHidden )
annoflags |= Annotation::Hidden;
if ( flags & Annot::flagNoZoom )
annoflags |= Annotation::FixedSize;
if ( flags & Annot::flagNoRotate )
annoflags |= Annotation::FixedRotation;
if ( !( flags & Annot::flagPrint ) )
annoflags |= Annotation::DenyPrint;
if ( flags & Annot::flagReadOnly )
annoflags |= (Annotation::DenyWrite | Annotation::DenyDelete);
if ( flags & Annot::flagLocked )
annoflags |= Annotation::DenyDelete;
if ( flags & Annot::flagToggleNoView )
annoflags |= Annotation::ToggleHidingOnMouse;
annotation->setFlags( annoflags );
// -> style
AnnotBorder *border = ann->getBorder();
if ( border )
{
if ( border->getType() == AnnotBorder::typeArray )
{
AnnotBorderArray * border_array = static_cast< AnnotBorderArray * >( border );
// -> style.xCorners
annotation->style.xCorners = border_array->getHorizontalCorner();
// -> style.yCorners
annotation->style.yCorners = border_array->getVerticalCorner();
}
// -> style.width
annotation->style.width = border->getWidth();
// -> style.style
annotation->style.style = (Annotation::LineStyle)( 1 << border->getStyle() );
#if 0
// -> style.marks and style.spaces
// TODO
Object dashArray;
bsObj.getDict()->lookup( "D", &dashArray );
if ( dashArray.isArray() )
{
int dashMarks = 3;
int dashSpaces = 0;
Object intObj;
dashArray.arrayGet( 0, &intObj );
if ( intObj.isInt() )
dashMarks = intObj.getInt();
intObj.free();
dashArray.arrayGet( 1, &intObj );
if ( intObj.isInt() )
dashSpaces = intObj.getInt();
intObj.free();
annotation->style.marks = dashMarks;
annotation->style.spaces = dashSpaces;
}
#endif
}
// -> style.color
annotation->style.color = convertAnnotColor( ann->getColor() );
/** 1.4. PARSE markup { common, Popup, Revision } parameters */
if ( markupann )
{
// -> creationDate
GooString *createDate = markupann->getDate();
if ( createDate )
{
annotation->setCreationDate( convertDate( createDate->getCString() ) );
}
// -> style.opacity
annotation->style.opacity = markupann->getOpacity();
// -> window.title and author
annotation->window.title = UnicodeParsedString( markupann->getLabel() );
annotation->setAuthor( annotation->window.title );
// -> window.summary
annotation->window.summary = UnicodeParsedString( markupann->getSubject() );
#if 0
// -> window.text
// TODO
XPDFReader::lookupString( annotDict, "RC", annotation->window.text );
#endif
// if a popup is referenced, schedule for resolving it later
AnnotPopup *popup = markupann->getPopup();
if ( popup )
{
ResolveWindow request;
request.popup = popup;
request.annotation = annotation;
resolvePopList.append( request );
}
// if an older version is referenced, schedule for reparenting
int parentID = markupann->getInReplyToID();
if ( parentID )
{
ResolveRevision request;
request.nextAnnotation = annotation;
request.nextAnnotationID = annotID;
request.prevAnnotationID = parentID;
// -> request.nextScope
request.nextScope = Annotation::Reply;
switch ( markupann->getReplyTo() )
{
case AnnotMarkup::replyTypeR:
request.nextScope = Annotation::Reply;
break;
case AnnotMarkup::replyTypeGroup:
request.nextScope = Annotation::Group;
break;
}
// -> revision.type (StateModel is deduced from type, not parsed)
request.nextType = Annotation::None;
if ( subType == Annot::typeText )
{
AnnotText * textann = static_cast< AnnotText * >( ann );
switch ( textann->getState() )
{
case AnnotText::stateMarked:
request.nextType = Annotation::Marked;
break;
case AnnotText::stateUnmarked:
request.nextType = Annotation::Unmarked;
break;
case AnnotText::stateAccepted:
request.nextType = Annotation::Accepted;
break;
case AnnotText::stateRejected:
request.nextType = Annotation::Rejected;
break;
case AnnotText::stateCancelled:
request.nextType = Annotation::Cancelled;
break;
case AnnotText::stateCompleted:
request.nextType = Annotation::Completed;
break;
case AnnotText::stateNone:
case AnnotText::stateUnknown:
;
}
}
// schedule for later reparenting
resolveRevList.append( request );
}
}
/** 1.5. ADD ANNOTATION to the annotationsMap */
if ( addToPage )
{
if ( annotationsMap.contains( annotID ) )
qDebug() << "Clash for annotations with ID:" << annotID;
annotationsMap[ annotID ] = annotation;
}
} // end Annotation/PopupWindow parsing loop
/** 2 - RESOLVE POPUPS (popup.* -> annotation.window) */
if ( !resolvePopList.isEmpty() && !popupsMap.isEmpty() )
{
QLinkedList< ResolveWindow >::iterator it = resolvePopList.begin(),
end = resolvePopList.end();
for ( ; it != end; ++it )
{
const ResolveWindow & request = *it;
if ( !popupsMap.contains( request.popup ) )
// warn aboud problems in popup resolving logic
qDebug() << "Cannot resolve popup"
<< request.popup << ".";
else
{
// set annotation's window properties taking ones from popup
PopupWindow * pop = popupsMap[ request.popup ];
Annotation * pa = pop->dummyAnnotation;
Annotation::Window & w = request.annotation->window;
// transfer properties to Annotation's window
w.flags = pa->flags() & (Annotation::Hidden |
Annotation::FixedSize | Annotation::FixedRotation);
if ( !pop->shown )
w.flags |= Annotation::Hidden;
w.topLeft.setX(pa->boundary().left());
w.topLeft.setY(pa->boundary().top());
w.width = (int)( pa->boundary().right() - pa->boundary().left() );
w.height = (int)( pa->boundary().bottom() - pa->boundary().top() );
}
}
// clear data
QHash< AnnotPopup *, PopupWindow * >::Iterator dIt = popupsMap.begin(), dEnd = popupsMap.end();
for ( ; dIt != dEnd; ++dIt )
{
PopupWindow * p = dIt.value();
delete p->dummyAnnotation;
delete p;
}
}
/** 3 - RESOLVE REVISIONS (parent.revisions.append( children )) */
if ( !resolveRevList.isEmpty() )
{
// append children to parents
QVarLengthArray< int > excludeIDs( resolveRevList.count() ); // can't even reach this size
int excludeIndex = 0; // index in excludeIDs array
QLinkedList< ResolveRevision >::iterator it = resolveRevList.begin(), end = resolveRevList.end();
for ( ; it != end; ++it )
{
const ResolveRevision & request = *it;
int parentID = request.prevAnnotationID;
if ( !annotationsMap.contains( parentID ) )
// warn about problems in reparenting logic
qDebug() << "Cannot reparent annotation to"
<< parentID << ".";
else
{
// compile and add a Revision structure to the parent annotation
Annotation::Revision childRevision;
childRevision.annotation = request.nextAnnotation;
childRevision.scope = request.nextScope;
childRevision.type = request.nextType;
annotationsMap[ parentID ]->revisions().append( childRevision );
// exclude child annotation from being rooted in page
excludeIDs[ excludeIndex++ ] = request.nextAnnotationID;
}
}
// prevent children from being attached to page as roots
for ( int i = 0; i < excludeIndex; i++ )
annotationsMap.remove( excludeIDs[ i ] );
}
/** 4 - POSTPROCESS TextAnnotations (when window geom is embedded) */
if ( !ppTextList.isEmpty() )
{
QLinkedList< PostProcessText >::const_iterator it = ppTextList.begin(), end = ppTextList.end();
for ( ; it != end; ++it )
{
const PostProcessText & request = *it;
Annotation::Window & window = request.textAnnotation->window;
// if not present, 'create' the window in-place over the annotation
if ( window.flags == -1 )
{
window.flags = 0;
QRectF geom = request.textAnnotation->boundary();
// initialize window geometry to annotation's one
window.width = (int)( geom.right() - geom.left() );
window.height = (int)( geom.bottom() - geom.top() );
window.topLeft.setX( geom.left() > 0.0 ? geom.left() : 0.0 );
window.topLeft.setY( geom.top() > 0.0 ? geom.top() : 0.0 );
}
// (pdf) if text is not 'opened', force window hiding. if the window
// was parsed from popup, the flag should already be set
if ( !request.opened && window.flags != -1 )
window.flags |= Annotation::Hidden;
}
}
delete annots;
/** 5 - finally RETURN ANNOTATIONS */
return annotationsMap.values();
}
QList<FormField*> Page::formFields() const
{
QList<FormField*> fields;
::Page *p = m_page->page;
::FormPageWidgets * form = p->getPageWidgets();
int formcount = form->getNumWidgets();
for (int i = 0; i < formcount; ++i)
{
::FormWidget *fm = form->getWidget(i);
FormField * ff = NULL;
switch (fm->getType())
{
case formButton:
{
ff = new FormFieldButton(m_page->parentDoc, p, static_cast<FormWidgetButton*>(fm));
}
break;
case formText:
{
ff = new FormFieldText(m_page->parentDoc, p, static_cast<FormWidgetText*>(fm));
}
break;
case formChoice:
{
ff = new FormFieldChoice(m_page->parentDoc, p, static_cast<FormWidgetChoice*>(fm));
}
break;
default: ;
}
if (ff)
fields.append(ff);
}
return fields;
}
double Page::duration() const
{
return m_page->page->getDuration();
}
QString Page::label() const
{
GooString goo;
if (!m_page->parentDoc->doc->getCatalog()->indexToLabel(m_page->index, &goo))
return QString();
return QString(goo.getCString());
}
}