blob: 86b4c5cdf7277e78a1adfe45183ec46ce639ec5e [file] [log] [blame]
/* poppler-page.cc: qt interface to poppler
* Copyright (C) 2005, Net Integration Technologies, Inc.
*
* 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/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>
#if defined(HAVE_SPLASH)
#include <SplashOutputDev.h>
#include <splash/SplashBitmap.h>
#include <ArthurOutputDev.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)
{
if ( !a )
return NULL;
Link * popplerLink = NULL;
switch ( a->getKind() )
{
case actionGoTo:
{
LinkGoTo * g = (LinkGoTo *) a;
// create link: no ext file, namedDest, object pointer
popplerLink = new LinkGoto( linkArea, QString::null, LinkDestination( LinkDestinationData(g->getDest(), g->getNamedDest(), parentDoc ) ) );
}
break;
case actionGoToR:
{
LinkGoToR * g = (LinkGoToR *) a;
// copy link file
const char * fileName = g->getFileName()->getCString();
// ceate link: fileName, namedDest, object pointer
popplerLink = new LinkGoto( linkArea, (QString)fileName, LinkDestination( LinkDestinationData(g->getDest(), g->getNamedDest(), parentDoc ) ) );
}
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 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:
{
#if defined(HAVE_SPLASH)
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.save();
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.restore();
painter.end();
img = tmpimg;
#endif
break;
}
}
return img;
}
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, QRectF &rect, 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;
double sLeft, sTop, sRight, sBottom;
sLeft = rect.left();
sTop = rect.top();
sRight = rect.right();
sBottom = rect.bottom();
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,
gTrue, gFalse, gFalse, gTrue, sCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
delete textPage;
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;
}
QMap<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
{
Object annotArray;
::Page *pdfPage = m_page->page;
pdfPage->getAnnots( &annotArray );
if ( !annotArray.isArray() || annotArray.arrayGetLength() < 1 )
{
annotArray.free();
return QList<Annotation*>();
}
// ID to Annotation/PopupWindow maps
QMap< int, Annotation * > annotationsMap;
QMap< int, 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->getMediaBox(), 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 */
Object annot;
Object annotRef; // no need to free this (impl. dependent!)
uint numAnnotations = annotArray.arrayGetLength();
for ( uint j = 0; j < numAnnotations; j++ )
{
// get the j-th annotation
annotArray.arrayGet( j, &annot );
if ( !annot.isDict() )
{
qDebug() << "PDFGenerator: annot not dictionary.";
annot.free();
continue;
}
Annotation * annotation = 0;
Dict * annotDict = annot.getDict();
int annotID = annotArray.arrayGetNF( j, &annotRef )->getRefNum();
bool parseMarkup = true, // nearly all supported annots are markup
addToPage = true; // Popup annots are added to custom queue
/** 1.1. GET Subtype */
QString subType;
XPDFReader::lookupName( annotDict, "Subtype", subType );
if ( subType.isEmpty() )
{
qDebug() << "Annot has no Subtype";
annot.free();
continue;
}
/** 1.2. CREATE Annotation / PopupWindow and PARSE specific params */
if ( subType == "Text" || subType == "FreeText" )
{
// parse TextAnnotation params
TextAnnotation * t = new TextAnnotation();
annotation = t;
if ( subType == "Text" )
{
// -> textType
t->setTextType( TextAnnotation::Linked );
// -> textIcon
QString tmpstring;
XPDFReader::lookupName( annotDict, "Name", tmpstring );
if ( !tmpstring.isEmpty() )
{
tmpstring = tmpstring.toLower();
tmpstring.remove( ' ' );
t->setTextIcon( tmpstring );
}
// request for postprocessing window geometry
PostProcessText request;
request.textAnnotation = t;
request.opened = false;
XPDFReader::lookupBool( annotDict, "Open", request.opened );
ppTextList.append( request );
}
else
{
// NOTE: please provide testcases for FreeText (don't have any) - Enrico
// -> textType
t->setTextType( TextAnnotation::InPlace );
// -> textFont
QString textFormat;
XPDFReader::lookupString( annotDict, "DA", textFormat );
// TODO, fill t->textFont using textFormat if not empty
// -> inplaceAlign
int tmpint = 0;
XPDFReader::lookupInt( annotDict, "Q", tmpint );
t->setInplaceAlign( tmpint );
// -> inplaceText (simple)
QString tmpstring;
XPDFReader::lookupString( annotDict, "DS", tmpstring );
// -> inplaceText (complex override)
XPDFReader::lookupString( annotDict, "RC", tmpstring );
t->setInplaceText( tmpstring );
// -> inplaceCallout
double c[6];
int n = XPDFReader::lookupNumArray( annotDict, "CL", c, 6 );
if ( n >= 4 )
{
QPointF tmppoint;
XPDFReader::transform( MTX, c[0], c[1], tmppoint );
t->setCalloutPoint( 0, tmppoint );
XPDFReader::transform( MTX, c[2], c[3], tmppoint );
t->setCalloutPoint( 1, tmppoint );
if ( n == 6 )
{
XPDFReader::transform( MTX, c[4], c[5], tmppoint );
t->setCalloutPoint( 2, tmppoint );
}
}
// -> inplaceIntent
QString intentName;
XPDFReader::lookupString( annotDict, "IT", intentName );
if ( intentName == "FreeTextCallout" )
t->setInplaceIntent( TextAnnotation::Callout );
else if ( intentName == "FreeTextTypeWriter" )
t->setInplaceIntent( TextAnnotation::TypeWriter );
}
}
else if ( subType == "Line" || subType == "Polygon" || subType == "PolyLine" )
{
// parse LineAnnotation params
LineAnnotation * l = new LineAnnotation();
annotation = l;
// -> linePoints
double c[100];
int num = XPDFReader::lookupNumArray( annotDict, (char*)((subType == "Line") ? "L" : "Vertices"), c, 100 );
if ( num < 4 || (num % 2) != 0 )
{
qDebug() << "L/Vertices wrong fol Line/Poly.";
delete annotation;
annot.free();
continue;
}
QLinkedList<QPointF> linePoints;
for ( int i = 0; i < num; i += 2 )
{
QPointF p;
XPDFReader::transform( MTX, c[i], c[i+1], p );
linePoints.push_back( p );
}
l->setLinePoints( linePoints );
// -> lineStartStyle, lineEndStyle
Object leArray;
annotDict->lookup( "LE", &leArray );
if ( leArray.isArray() && leArray.arrayGetLength() == 2 )
{
// -> lineStartStyle
Object styleObj;
leArray.arrayGet( 0, &styleObj );
if ( styleObj.isName() )
{
const char * name = styleObj.getName();
if ( !strcmp( name, "Square" ) )
l->setLineStartStyle( LineAnnotation::Square );
else if ( !strcmp( name, "Circle" ) )
l->setLineStartStyle( LineAnnotation::Circle );
else if ( !strcmp( name, "Diamond" ) )
l->setLineStartStyle( LineAnnotation::Diamond );
else if ( !strcmp( name, "OpenArrow" ) )
l->setLineStartStyle( LineAnnotation::OpenArrow );
else if ( !strcmp( name, "ClosedArrow" ) )
l->setLineStartStyle( LineAnnotation::ClosedArrow );
else if ( !strcmp( name, "None" ) )
l->setLineStartStyle( LineAnnotation::None );
else if ( !strcmp( name, "Butt" ) )
l->setLineStartStyle( LineAnnotation::Butt );
else if ( !strcmp( name, "ROpenArrow" ) )
l->setLineStartStyle( LineAnnotation::ROpenArrow );
else if ( !strcmp( name, "RClosedArrow" ) )
l->setLineStartStyle( LineAnnotation::RClosedArrow );
else if ( !strcmp( name, "Slash" ) )
l->setLineStartStyle( LineAnnotation::Slash );
}
styleObj.free();
// -> lineEndStyle
leArray.arrayGet( 1, &styleObj );
if ( styleObj.isName() )
{
const char * name = styleObj.getName();
if ( !strcmp( name, "Square" ) )
l->setLineEndStyle( LineAnnotation::Square );
else if ( !strcmp( name, "Circle" ) )
l->setLineEndStyle( LineAnnotation::Circle );
else if ( !strcmp( name, "Diamond" ) )
l->setLineEndStyle( LineAnnotation::Diamond );
else if ( !strcmp( name, "OpenArrow" ) )
l->setLineEndStyle( LineAnnotation::OpenArrow );
else if ( !strcmp( name, "ClosedArrow" ) )
l->setLineEndStyle( LineAnnotation::ClosedArrow );
else if ( !strcmp( name, "None" ) )
l->setLineEndStyle( LineAnnotation::None );
else if ( !strcmp( name, "Butt" ) )
l->setLineEndStyle( LineAnnotation::Butt );
else if ( !strcmp( name, "ROpenArrow" ) )
l->setLineEndStyle( LineAnnotation::ROpenArrow );
else if ( !strcmp( name, "RClosedArrow" ) )
l->setLineEndStyle( LineAnnotation::RClosedArrow );
else if ( !strcmp( name, "Slash" ) )
l->setLineEndStyle( LineAnnotation::Slash );
}
styleObj.free();
}
leArray.free();
// -> lineClosed
l->setLineClosed( subType == "Polygon" );
// -> lineInnerColor
QColor tmpcolor = l->lineInnerColor();
XPDFReader::lookupColor( annotDict, "IC", tmpcolor );
l->setLineInnerColor( tmpcolor );
// -> lineLeadingFwdPt
double tmpdouble = l->lineLeadingForwardPoint();
XPDFReader::lookupNum( annotDict, "LL", tmpdouble );
l->setLineLeadingForwardPoint( tmpdouble );
// -> lineLeadingBackPt
tmpdouble = l->lineLeadingBackPoint();
XPDFReader::lookupNum( annotDict, "LLE", tmpdouble );
l->setLineLeadingBackPoint( tmpdouble );
// -> lineShowCaption
bool tmpbool = l->lineShowCaption();
XPDFReader::lookupBool( annotDict, "Cap", tmpbool );
l->setLineShowCaption( tmpbool );
// -> lineIntent
QString intentName;
XPDFReader::lookupString( annotDict, "IT", intentName );
if ( intentName == "LineArrow" )
l->setLineIntent( LineAnnotation::Arrow );
else if ( intentName == "LineDimension" )
l->setLineIntent( LineAnnotation::Dimension );
else if ( intentName == "PolygonCloud" )
l->setLineIntent( LineAnnotation::PolygonCloud );
}
else if ( subType == "Square" || subType == "Circle" )
{
// parse GeomAnnotation params
GeomAnnotation * g = new GeomAnnotation();
annotation = g;
// -> geomType
if ( subType == "Square" )
g->setGeomType( GeomAnnotation::InscribedSquare );
else
g->setGeomType( GeomAnnotation::InscribedCircle );
// -> geomInnerColor
QColor tmpcolor = g->geomInnerColor();
XPDFReader::lookupColor( annotDict, "IC", tmpcolor );
g->setGeomInnerColor( tmpcolor );
// TODO RD
}
else if ( subType == "Highlight" || subType == "Underline" ||
subType == "Squiggly" || subType == "StrikeOut" )
{
// parse HighlightAnnotation params
HighlightAnnotation * h = new HighlightAnnotation();
annotation = h;
// -> highlightType
if ( subType == "Highlight" )
h->setHighlightType( HighlightAnnotation::Highlight );
else if ( subType == "Underline" )
h->setHighlightType( HighlightAnnotation::Underline );
else if ( subType == "Squiggly" )
h->setHighlightType( HighlightAnnotation::Squiggly );
else if ( subType == "StrikeOut" )
h->setHighlightType( HighlightAnnotation::StrikeOut );
// -> highlightQuads
double c[80];
int num = XPDFReader::lookupNumArray( annotDict, "QuadPoints", c, 80 );
if ( num < 8 || (num % 8) != 0 )
{
qDebug() << "Wrong QuadPoints for a Highlight annotation.";
delete annotation;
annot.free();
continue;
}
QList< HighlightAnnotation::Quad > quads;
for ( int q = 0; q < num; q += 8 )
{
HighlightAnnotation::Quad quad;
for ( int p = 0; p < 4; p++ )
XPDFReader::transform( MTX, c[ q + p*2 ], c[ q + p*2 + 1 ], quad.points[ p ] );
// ### 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 oroperties 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 );
}
else if ( subType == "Stamp" )
{
// parse StampAnnotation params
StampAnnotation * s = new StampAnnotation();
annotation = s;
// -> stampIconName
QString tmpstring = s->stampIconName();
XPDFReader::lookupName( annotDict, "Name", tmpstring );
s->setStampIconName( tmpstring );
}
else if ( subType == "Ink" )
{
// parse InkAnnotation params
InkAnnotation * k = new InkAnnotation();
annotation = k;
// -> inkPaths
Object pathsArray;
annotDict->lookup( "InkList", &pathsArray );
if ( !pathsArray.isArray() || pathsArray.arrayGetLength() < 1 )
{
qDebug() << "InkList not present for ink annot";
delete annotation;
annot.free();
continue;
}
int pathsNumber = pathsArray.arrayGetLength();
QList< QLinkedList<QPointF> > inkPaths;
for ( int m = 0; m < pathsNumber; m++ )
{
// transform each path in a list of normalized points ..
QLinkedList<QPointF> localList;
Object pointsArray;
pathsArray.arrayGet( m, &pointsArray );
if ( pointsArray.isArray() )
{
int pointsNumber = pointsArray.arrayGetLength();
for ( int n = 0; n < pointsNumber; n+=2 )
{
// get the x,y numbers for current point
Object numObj;
double x = pointsArray.arrayGet( n, &numObj )->getNum();
numObj.free();
double y = pointsArray.arrayGet( n+1, &numObj )->getNum();
numObj.free();
// add normalized point to localList
QPointF np;
XPDFReader::transform( MTX, x, y, np );
localList.push_back( np );
}
}
pointsArray.free();
// ..and add it to the annotation
inkPaths.push_back( localList );
}
k->setInkPaths( inkPaths );
pathsArray.free();
}
else if ( subType == "Popup" )
{
// create PopupWindow and add it to the popupsMap
PopupWindow * popup = new PopupWindow();
popupsMap[ annotID ] = popup;
parseMarkup = false;
addToPage = false;
// get window specific properties if any
popup->shown = false;
XPDFReader::lookupBool( annotDict, "Open", popup->shown );
// 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;
}
else if ( subType == "Link" )
{
// parse Link params
LinkAnnotation * l = new LinkAnnotation();
annotation = l;
// -> hlMode
QString hlModeString;
XPDFReader::lookupName( annotDict, "H", hlModeString );
if ( hlModeString == "N" )
l->setLinkHighlightMode( LinkAnnotation::None );
else if ( hlModeString == "I" )
l->setLinkHighlightMode( LinkAnnotation::Invert );
else if ( hlModeString == "O" )
l->setLinkHighlightMode( LinkAnnotation::Outline );
else if ( hlModeString == "P" )
l->setLinkHighlightMode( LinkAnnotation::Push );
// -> link region
double c[8];
int num = XPDFReader::lookupNumArray( annotDict, "QuadPoints", c, 8 );
if ( num > 0 && num != 8 )
{
qDebug() << "Wrong QuadPoints for a Link annotation.";
delete annotation;
annot.free();
continue;
}
if ( num == 8 )
{
QPointF tmppoint;
XPDFReader::transform( MTX, c[ 0 ], c[ 1 ], tmppoint );
l->setLinkRegionPoint( 0, tmppoint );
XPDFReader::transform( MTX, c[ 2 ], c[ 3 ], tmppoint );
l->setLinkRegionPoint( 1, tmppoint );
XPDFReader::transform( MTX, c[ 4 ], c[ 5 ], tmppoint );
l->setLinkRegionPoint( 2, tmppoint );
XPDFReader::transform( MTX, c[ 6 ], c[ 7 ], tmppoint );
l->setLinkRegionPoint( 3, tmppoint );
}
// reading link action
Object objPA;
annotDict->lookup( "PA", &objPA );
if (!objPA.isNull())
{
::LinkAction * a = ::LinkAction::parseAction( &objPA, m_page->parentDoc->doc->getCatalog()->getBaseURI() );
Link * popplerLink = m_page->convertLinkActionToLink( a, QRectF() );
if ( popplerLink )
{
l->setLinkDestination( popplerLink );
}
objPA.free();
}
}
else
{
// MISSING: Caret, FileAttachment, Sound, Movie, Widget,
// Screen, PrinterMark, TrapNet, Watermark, 3D
qDebug() << "Annotation" << subType << "not supported";
annot.free();
continue;
}
/** 1.3. PARSE common parameters */
// -> boundary
double r[4];
if ( XPDFReader::lookupNumArray( annotDict, "Rect", r, 4 ) != 4 )
{
qDebug() << "Rect is missing for annotation.";
annot.free();
continue;
}
// transform annotation rect to uniform coords
QPointF topLeft, bottomRight;
XPDFReader::transform( MTX, r[0], r[1], topLeft );
XPDFReader::transform( MTX, r[2], r[3], 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
QString tmpstring = annotation->contents();
XPDFReader::lookupString( annotDict, "Contents", tmpstring );
annotation->setContents( tmpstring );
// -> uniqueName
tmpstring = annotation->uniqueName();
XPDFReader::lookupString( annotDict, "NM", tmpstring );
annotation->setUniqueName( tmpstring );
// -> modifyDate (and -> creationDate)
QDateTime tmpdatetime = annotation->modificationDate();
XPDFReader::lookupDate( annotDict, "M", tmpdatetime );
annotation->setModificationDate( tmpdatetime );
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 = 0;
XPDFReader::lookupInt( annotDict, "F", flags );
if ( flags & 0x2 )
annoflags |= Annotation::Hidden;
if ( flags & 0x8 )
annoflags |= Annotation::FixedSize;
if ( flags & 0x10 )
annoflags |= Annotation::FixedRotation;
if ( !(flags & 0x4) )
annoflags |= Annotation::DenyPrint;
if ( flags & 0x40 )
annoflags |= (Annotation::DenyWrite | Annotation::DenyDelete);
if ( flags & 0x80 )
annoflags |= Annotation::DenyDelete;
if ( flags & 0x100 )
annoflags |= Annotation::ToggleHidingOnMouse;
annotation->setFlags( annoflags );
// -> style (Border(old spec), BS, BE)
double border[3];
int bn = XPDFReader::lookupNumArray( annotDict, "Border", border, 3 );
if ( bn == 3 )
{
// -> style.xCorners
annotation->style.xCorners = border[0];
// -> style.yCorners
annotation->style.yCorners = border[1];
// -> style.width
annotation->style.width = border[2];
}
Object bsObj;
annotDict->lookup( "BS", &bsObj );
if ( bsObj.isDict() )
{
// -> style.width
XPDFReader::lookupNum( bsObj.getDict(), "W", annotation->style.width );
// -> style.style
QString styleName;
XPDFReader::lookupName( bsObj.getDict(), "S", styleName );
if ( styleName == "S" )
annotation->style.style = Annotation::Solid;
else if ( styleName == "D" )
annotation->style.style = Annotation::Dashed;
else if ( styleName == "B" )
annotation->style.style = Annotation::Beveled;
else if ( styleName == "I" )
annotation->style.style = Annotation::Inset;
else if ( styleName == "U" )
annotation->style.style = Annotation::Underline;
// -> style.marks and style.spaces
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;
}
dashArray.free();
}
bsObj.free();
Object beObj;
annotDict->lookup( "BE", &beObj );
if ( beObj.isDict() )
{
// -> style.effect
QString effectName;
XPDFReader::lookupName( beObj.getDict(), "S", effectName );
if ( effectName == "C" )
annotation->style.effect = Annotation::Cloudy;
// -> style.effectIntensity
int intensityInt = -1;
XPDFReader::lookupInt( beObj.getDict(), "I", intensityInt );
if ( intensityInt != -1 )
annotation->style.effectIntensity = (double)intensityInt;
}
beObj.free();
// -> style.color
XPDFReader::lookupColor( annotDict, "C", annotation->style.color );
/** 1.4. PARSE markup { common, Popup, Revision } parameters */
if ( parseMarkup )
{
// -> creationDate
tmpdatetime = annotation->creationDate();
XPDFReader::lookupDate( annotDict, "CreationDate", tmpdatetime );
annotation->setCreationDate( tmpdatetime );
// -> style.opacity
XPDFReader::lookupNum( annotDict, "CA", annotation->style.opacity );
// -> window.title and author
XPDFReader::lookupString( annotDict, "T", annotation->window.title );
annotation->setAuthor( annotation->window.title );
// -> window.summary
XPDFReader::lookupString( annotDict, "Subj", annotation->window.summary );
// -> window.text
XPDFReader::lookupString( annotDict, "RC", annotation->window.text );
// if a popup is referenced, schedule for resolving it later
int popupID = 0;
XPDFReader::lookupIntRef( annotDict, "Popup", popupID );
if ( popupID )
{
ResolveWindow request;
request.popupWindowID = popupID;
request.annotation = annotation;
resolvePopList.append( request );
}
// if an older version is referenced, schedule for reparenting
int parentID = 0;
XPDFReader::lookupIntRef( annotDict, "IRT", parentID );
if ( parentID )
{
ResolveRevision request;
request.nextAnnotation = annotation;
request.nextAnnotationID = annotID;
request.prevAnnotationID = parentID;
// -> request.nextScope
request.nextScope = Annotation::Reply;
Object revObj;
annotDict->lookup( "RT", &revObj );
if ( revObj.isName() )
{
const char * name = revObj.getName();
if ( !strcmp( name, "R" ) )
request.nextScope = Annotation::Reply;
else if ( !strcmp( name, "Group" ) )
request.nextScope = Annotation::Group;
}
revObj.free();
// -> revision.type (StateModel is deduced from type, not parsed)
request.nextType = Annotation::None;
annotDict->lookup( "State", &revObj );
if ( revObj.isString() )
{
const char * name = revObj.getString()->getCString();
if ( !strcmp( name, "Marked" ) )
request.nextType = Annotation::Marked;
else if ( !strcmp( name, "Unmarked" ) )
request.nextType = Annotation::Unmarked;
else if ( !strcmp( name, "Accepted" ) )
request.nextType = Annotation::Accepted;
else if ( !strcmp( name, "Rejected" ) )
request.nextType = Annotation::Rejected;
else if ( !strcmp( name, "Cancelled" ) )
request.nextType = Annotation::Cancelled;
else if ( !strcmp( name, "Completed" ) )
request.nextType = Annotation::Completed;
else if ( !strcmp( name, "None" ) )
request.nextType = Annotation::None;
}
revObj.free();
// schedule for later reparenting
resolveRevList.append( request );
}
}
// free annot object
annot.free();
/** 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.popupWindowID ) )
// warn aboud problems in popup resolving logic
qDebug() << "Cannot resolve popup"
<< request.popupWindowID << ".";
else
{
// set annotation's window properties taking ones from popup
PopupWindow * pop = popupsMap[ request.popupWindowID ];
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
QMap< int, 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;
}
}
annotArray.free();
/** 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());
}
}