[poppler] poppler/qt4/src: Makefile.am, 1.10,
1.11 poppler-annotation-helper.h, NONE,
1.1 poppler-annotation.cc, NONE, 1.1 poppler-annotation.h,
NONE, 1.1 poppler-link.cc, 1.2, 1.3 poppler-link.h, 1.1,
1.2 poppler-page.cc, 1.20, 1.21 poppler-qt4.h, 1.31, 1.32
Albert Astals Cid
aacid at kemper.freedesktop.org
Fri May 12 13:40:07 PDT 2006
Update of /cvs/poppler/poppler/qt4/src
In directory kemper:/tmp/cvs-serv24273/qt4/src
Modified Files:
Makefile.am poppler-link.cc poppler-link.h poppler-page.cc
poppler-qt4.h
Added Files:
poppler-annotation-helper.h poppler-annotation.cc
poppler-annotation.h
Log Message:
* qt4/src/Makefile.am
* qt4/src/poppler-annotation-helper.h
* qt4/src/poppler-annotation.cc
* qt4/src/poppler-annotation.h
* qt4/src/poppler-link.cc
* qt4/src/poppler-link.h
* qt4/src/poppler-page.cc
* qt4/src/poppler-qt4.h: Code for annotations stripped from oKular,
it's all based on Enrico's work, so ask him for details, the problem
is that he left KDE development a while ago.
Index: Makefile.am
===================================================================
RCS file: /cvs/poppler/poppler/qt4/src/Makefile.am,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- Makefile.am 9 May 2006 20:07:06 -0000 1.10
+++ Makefile.am 12 May 2006 20:40:05 -0000 1.11
@@ -10,6 +10,7 @@
poppler_include_HEADERS = \
poppler-qt4.h \
poppler-link.h \
+ poppler-annotation.h \
../../qt/poppler-page-transition.h
lib_LTLIBRARIES = libpoppler-qt4.la
@@ -21,6 +22,7 @@
poppler-embeddedfile.cc \
poppler-textbox.cc \
poppler-link.cc \
+ poppler-annotation.cc \
../../qt/poppler-page-transition.cc \
poppler-private.h
--- NEW FILE: poppler-annotation-helper.h ---
/* poppler-annotation-helper.h: qt interface to poppler
* Copyright (C) 2006, Albert Astals Cid <aacid at kde.org>
* Adapting code from
* Copyright (C) 2004 by Enrico Ros <eros.kde at email.it>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <QtCore/QDebug>
namespace Poppler {
class XPDFReader
{
public:
// find named symbol and parse it
static void lookupName( Dict *, const char *, QString & dest );
static void lookupString( Dict *, const char *, QString & dest );
static void lookupBool( Dict *, const char *, bool & dest );
static void lookupInt( Dict *, const char *, int & dest );
static void lookupNum( Dict *, const char *, double & dest );
static int lookupNumArray( Dict *, const char *, double * dest, int len );
static void lookupColor( Dict *, const char *, QColor & color );
static void lookupIntRef( Dict *, const char *, int & dest );
static void lookupDate( Dict *, const char *, QDateTime & dest );
// transform from user coords to normalized ones using the matrix M
static inline void transform( double * M, double x, double y, QPointF &res );
};
void XPDFReader::lookupName( Dict * dict, const char * type, QString & dest )
{
Object nameObj;
dict->lookup( type, &nameObj );
if ( nameObj.isNull() )
return;
if ( nameObj.isName() )
dest = nameObj.getName();
else
qDebug() << type << " is not Name." << endl;
nameObj.free();
}
void XPDFReader::lookupString( Dict * dict, const char * type, QString & dest )
{
Object stringObj;
dict->lookup( type, &stringObj );
if ( stringObj.isNull() )
return;
if ( stringObj.isString() )
dest = stringObj.getString()->getCString();
else
qDebug() << type << " is not String." << endl;
stringObj.free();
}
void XPDFReader::lookupBool( Dict * dict, const char * type, bool & dest )
{
Object boolObj;
dict->lookup( type, &boolObj );
if ( boolObj.isNull() )
return;
if ( boolObj.isBool() )
dest = boolObj.getBool() == gTrue;
else
qDebug() << type << " is not Bool." << endl;
boolObj.free();
}
void XPDFReader::lookupInt( Dict * dict, const char * type, int & dest )
{
Object intObj;
dict->lookup( type, &intObj );
if ( intObj.isNull() )
return;
if ( intObj.isInt() )
dest = intObj.getInt();
else
qDebug() << type << " is not Int." << endl;
intObj.free();
}
void XPDFReader::lookupNum( Dict * dict, const char * type, double & dest )
{
Object numObj;
dict->lookup( type, &numObj );
if ( numObj.isNull() )
return;
if ( numObj.isNum() )
dest = numObj.getNum();
else
qDebug() << type << " is not Num." << endl;
numObj.free();
}
int XPDFReader::lookupNumArray( Dict * dict, const char * type, double * dest, int len )
{
Object arrObj;
dict->lookup( type, &arrObj );
if ( arrObj.isNull() )
return 0;
Object numObj;
if ( arrObj.isArray() )
{
len = qMin( len, arrObj.arrayGetLength() );
for ( int i = 0; i < len; i++ )
{
dest[i] = arrObj.arrayGet( i, &numObj )->getNum();
numObj.free();
}
}
else
{
len = 0;
qDebug() << type << "is not Array." << endl;
}
arrObj.free();
return len;
}
void XPDFReader::lookupColor( Dict * dict, const char * type, QColor & dest )
{
double c[3];
if ( XPDFReader::lookupNumArray( dict, type, c, 3 ) == 3 )
dest = QColor( (int)(c[0]*255.0), (int)(c[1]*255.0), (int)(c[2]*255.0));
}
void XPDFReader::lookupIntRef( Dict * dict, const char * type, int & dest )
{
Object refObj;
dict->lookupNF( type, &refObj );
if ( refObj.isNull() )
return;
if ( refObj.isRef() )
dest = refObj.getRefNum();
else
qDebug() << type << " is not Ref." << endl;
refObj.free();
}
void XPDFReader::lookupDate( Dict * dict, const char * type, QDateTime & dest )
{
Object dateObj;
dict->lookup( type, &dateObj );
if ( dateObj.isNull() )
return;
if ( dateObj.isString() )
{
const char * s = dateObj.getString()->getCString();
if ( s[0] == 'D' && s[1] == ':' )
s += 2;
int year, mon, day, hour, min, sec;
if ( sscanf( s, "%4d%2d%2d%2d%2d%2d", &year, &mon, &day, &hour, &min, &sec ) == 6 )
{
QDate d( year, mon, day );
QTime t( hour, min, sec );
if ( d.isValid() && t.isValid() )
dest = QDateTime(d, t);
}
else
qDebug() << "Wrong Date format '" << s << "' for '" << type << "'." << endl;
}
else
qDebug() << type << " is not Date" << endl;
dateObj.free();
}
void XPDFReader::transform( double * M, double x, double y, QPointF &res )
{
res.setX( M[0] * x + M[2] * y + M[4] );
res.setY( M[1] * x + M[3] * y + M[5] );
}
/** @short Helper classes for CROSSDEPS resolving and DS conversion. */
struct ResolveRevision
{
int prevAnnotationID; // ID of the annotation to be reparended
int nextAnnotationID; // (only needed for speeding up resolving)
Annotation * nextAnnotation; // annotation that will act as parent
Annotation::RevScope nextScope; // scope of revision (Reply)
Annotation::RevType nextType; // type of revision (None)
};
struct ResolveWindow
{
int popupWindowID; // ID of the (maybe shared) window
Annotation * annotation; // annotation having the popup window
};
struct PostProcessText // this handles a special pdf case conversion
{
Annotation * textAnnotation; // a popup text annotation (not FreeText)
bool opened; // pdf property to convert to window flags
};
struct PopupWindow
{
Annotation * dummyAnnotation; // window properties (in pdf as Annotation)
bool shown; // converted to Annotation::Hidden flag
};
}
--- NEW FILE: poppler-annotation.cc ---
/* poppler-annotation.cc: qt interface to poppler
* Copyright (C) 2006, Albert Astals Cid
* Adapting code from
* Copyright (C) 2004 by Enrico Ros <eros.kde at email.it>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// qt/kde includes
#include <QtXml/QDomElement>
#include <QtGui/QColor>
// local includes
#include "poppler-annotation.h"
namespace Poppler {
//BEGIN AnnotationUtils implementation
Annotation * AnnotationUtils::createAnnotation( const QDomElement & annElement )
{
// safety check on annotation element
if ( !annElement.hasAttribute( "type" ) )
return 0;
// build annotation of given type
Annotation * annotation = 0;
int typeNumber = annElement.attribute( "type" ).toInt();
switch ( typeNumber )
{
case Annotation::AText:
annotation = new TextAnnotation( annElement );
break;
case Annotation::ALine:
annotation = new LineAnnotation( annElement );
break;
case Annotation::AGeom:
annotation = new GeomAnnotation( annElement );
break;
case Annotation::AHighlight:
annotation = new HighlightAnnotation( annElement );
break;
case Annotation::AStamp:
annotation = new StampAnnotation( annElement );
break;
case Annotation::AInk:
annotation = new InkAnnotation( annElement );
break;
}
// return created annotation
return annotation;
}
void AnnotationUtils::storeAnnotation( const Annotation * ann, QDomElement & annElement,
QDomDocument & document )
{
// save annotation's type as element's attribute
annElement.setAttribute( "type", (uint)ann->subType() );
// append all annotation data as children of this node
ann->store( annElement, document );
}
QDomElement AnnotationUtils::findChildElement( const QDomNode & parentNode,
const QString & name )
{
// loop through the whole children and return a 'name' named element
QDomNode subNode = parentNode.firstChild();
while( subNode.isElement() )
{
QDomElement element = subNode.toElement();
if ( element.tagName() == name )
return element;
subNode = subNode.nextSibling();
}
// if the name can't be found, return a dummy null element
return QDomElement();
}
//END AnnotationUtils implementation
//BEGIN Annotation implementation
Annotation::Style::Style()
: opacity( 1.0 ), width( 1.0 ), style( Solid ), xCorners( 0.0 ),
yCorners( 0.0 ), marks( 3 ), spaces( 0 ), effect( NoEffect ),
effectIntensity( 1.0 ) {}
Annotation::Window::Window()
: flags( -1 ), width( 0 ), height( 0 ) {}
Annotation::Revision::Revision()
: annotation( 0 ), scope( Reply ), type( None ) {}
Annotation::Annotation()
: flags( 0 ) {}
Annotation::~Annotation()
{
// delete all children revisions
if ( revisions.isEmpty() )
return;
QLinkedList< Revision >::iterator it = revisions.begin(), end = revisions.end();
for ( ; it != end; ++it )
delete (*it).annotation;
}
Annotation::Annotation( const QDomNode & annNode )
: flags( 0 )
{
// get the [base] element of the annotation node
QDomElement e = AnnotationUtils::findChildElement( annNode, "base" );
if ( e.isNull() )
return;
// parse -contents- attributes
if ( e.hasAttribute( "author" ) )
author = e.attribute( "author" );
if ( e.hasAttribute( "contents" ) )
contents = e.attribute( "contents" );
if ( e.hasAttribute( "uniqueName" ) )
uniqueName = e.attribute( "uniqueName" );
if ( e.hasAttribute( "modifyDate" ) )
modifyDate = QDateTime::fromString( e.attribute( "modifyDate" ) );
if ( e.hasAttribute( "creationDate" ) )
creationDate = QDateTime::fromString( e.attribute( "creationDate" ) );
// parse -other- attributes
if ( e.hasAttribute( "flags" ) )
flags = e.attribute( "flags" ).toInt();
if ( e.hasAttribute( "color" ) )
style.color = QColor( e.attribute( "color" ) );
if ( e.hasAttribute( "opacity" ) )
style.opacity = e.attribute( "opacity" ).toDouble();
// parse -the-subnodes- (describing Style, Window, Revision(s) structures)
// Note: all subnodes if present must be 'attributes complete'
QDomNode eSubNode = e.firstChild();
while ( eSubNode.isElement() )
{
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
// parse boundary
if ( ee.tagName() == "boundary" )
{
boundary.setLeft(ee.attribute( "l" ).toDouble());
boundary.setTop(ee.attribute( "t" ).toDouble());
boundary.setRight(ee.attribute( "r" ).toDouble());
boundary.setBottom(ee.attribute( "b" ).toDouble());
}
// parse penStyle if not default
else if ( ee.tagName() == "penStyle" )
{
style.width = ee.attribute( "width" ).toDouble();
style.style = (LineStyle)ee.attribute( "style" ).toInt();
style.xCorners = ee.attribute( "xcr" ).toDouble();
style.yCorners = ee.attribute( "ycr" ).toDouble();
style.marks = ee.attribute( "marks" ).toInt();
style.spaces = ee.attribute( "spaces" ).toInt();
}
// parse effectStyle if not default
else if ( ee.tagName() == "penEffect" )
{
style.effect = (LineEffect)ee.attribute( "effect" ).toInt();
style.effectIntensity = ee.attribute( "intensity" ).toDouble();
}
// parse window if present
else if ( ee.tagName() == "window" )
{
window.flags = ee.attribute( "flags" ).toInt();
window.topLeft.setX(ee.attribute( "top" ).toDouble());
window.topLeft.setY(ee.attribute( "left" ).toDouble());
window.width = ee.attribute( "width" ).toInt();
window.height = ee.attribute( "height" ).toInt();
window.title = ee.attribute( "title" );
window.summary = ee.attribute( "summary" );
// parse window subnodes
QDomNode winNode = ee.firstChild();
for ( ; winNode.isElement(); winNode = winNode.nextSibling() )
{
QDomElement winElement = winNode.toElement();
if ( winElement.tagName() == "text" )
window.text = winElement.firstChild().toCDATASection().data();
}
}
}
// get the [revisions] element of the annotation node
QDomNode revNode = annNode.firstChild();
for ( ; revNode.isElement(); revNode = revNode.nextSibling() )
{
QDomElement revElement = revNode.toElement();
if ( revElement.tagName() != "revision" )
continue;
// compile the Revision structure crating annotation
Revision rev;
rev.scope = (RevScope)revElement.attribute( "revScope" ).toInt();
rev.type = (RevType)revElement.attribute( "revType" ).toInt();
rev.annotation = AnnotationUtils::createAnnotation( revElement );
// if annotation is valid, add revision to internal list
if ( rev.annotation );
revisions.append( rev );
}
}
void Annotation::store( QDomNode & annNode, QDomDocument & document ) const
{
// create [base] element of the annotation node
QDomElement e = document.createElement( "base" );
annNode.appendChild( e );
// store -contents- attributes
if ( !author.isEmpty() )
e.setAttribute( "author", author );
if ( !contents.isEmpty() )
e.setAttribute( "contents", contents );
if ( !uniqueName.isEmpty() )
e.setAttribute( "uniqueName", uniqueName );
if ( modifyDate.isValid() )
e.setAttribute( "modifyDate", modifyDate.toString() );
if ( creationDate.isValid() )
e.setAttribute( "creationDate", creationDate.toString() );
// store -other- attributes
if ( flags )
e.setAttribute( "flags", flags );
if ( style.color.isValid() && style.color != Qt::black )
e.setAttribute( "color", style.color.name() );
if ( style.opacity != 1.0 )
e.setAttribute( "opacity", style.opacity );
// Sub-Node-1 - boundary
QDomElement bE = document.createElement( "boundary" );
e.appendChild( bE );
bE.setAttribute( "l", (double)boundary.left() );
bE.setAttribute( "t", (double)boundary.top() );
bE.setAttribute( "r", (double)boundary.right() );
bE.setAttribute( "b", (double)boundary.bottom() );
// Sub-Node-2 - penStyle
if ( style.width != 1 || style.style != Solid || style.xCorners != 0 ||
style.yCorners != 0.0 || style.marks != 3 || style.spaces != 0 )
{
QDomElement psE = document.createElement( "penStyle" );
e.appendChild( psE );
psE.setAttribute( "width", style.width );
psE.setAttribute( "style", (int)style.style );
psE.setAttribute( "xcr", style.xCorners );
psE.setAttribute( "ycr", style.yCorners );
psE.setAttribute( "marks", style.marks );
psE.setAttribute( "spaces", style.spaces );
}
// Sub-Node-3 - penEffect
if ( style.effect != NoEffect || style.effectIntensity != 1.0 )
{
QDomElement peE = document.createElement( "penEffect" );
e.appendChild( peE );
peE.setAttribute( "effect", (int)style.effect );
peE.setAttribute( "intensity", style.effectIntensity );
}
// Sub-Node-4 - window
if ( window.flags != -1 || !window.title.isEmpty() ||
!window.summary.isEmpty() || !window.text.isEmpty() )
{
QDomElement wE = document.createElement( "window" );
e.appendChild( wE );
wE.setAttribute( "flags", window.flags );
wE.setAttribute( "top", window.topLeft.x() );
wE.setAttribute( "left", window.topLeft.y() );
wE.setAttribute( "width", window.width );
wE.setAttribute( "height", window.height );
wE.setAttribute( "title", window.title );
wE.setAttribute( "summary", window.summary );
// store window.text as a subnode, because we need escaped data
if ( !window.text.isEmpty() )
{
QDomElement escapedText = document.createElement( "text" );
wE.appendChild( escapedText );
QDomCDATASection textCData = document.createCDATASection( window.text );
escapedText.appendChild( textCData );
}
}
// create [revision] element of the annotation node (if any)
if ( revisions.isEmpty() )
return;
// add all revisions as children of revisions element
QLinkedList< Revision >::const_iterator it = revisions.begin(), end = revisions.end();
for ( ; it != end; ++it )
{
// create revision element
const Revision & revision = *it;
QDomElement r = document.createElement( "revision" );
annNode.appendChild( r );
// set element attributes
r.setAttribute( "revScope", (int)revision.scope );
r.setAttribute( "revType", (int)revision.type );
// use revision as the annotation element, so fill it up
AnnotationUtils::storeAnnotation( revision.annotation, r, document );
}
}
//END AnnotationUtils implementation
/** TextAnnotation [Annotation] */
TextAnnotation::TextAnnotation()
: Annotation(), textType( Linked ), textIcon( "Comment" ),
inplaceAlign( 0 ), inplaceIntent( Unknown )
{}
TextAnnotation::TextAnnotation( const QDomNode & node )
: Annotation( node ), textType( Linked ), textIcon( "Comment" ),
inplaceAlign( 0 ), inplaceIntent( Unknown )
{
// loop through the whole children looking for a 'text' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != "text" )
continue;
// parse the attributes
if ( e.hasAttribute( "type" ) )
textType = (TextAnnotation::TextType)e.attribute( "type" ).toInt();
if ( e.hasAttribute( "icon" ) )
textIcon = e.attribute( "icon" );
if ( e.hasAttribute( "font" ) )
textFont.fromString( e.attribute( "font" ) );
if ( e.hasAttribute( "align" ) )
inplaceAlign = e.attribute( "align" ).toInt();
if ( e.hasAttribute( "intent" ) )
inplaceIntent = (TextAnnotation::InplaceIntent)e.attribute( "intent" ).toInt();
// parse the subnodes
QDomNode eSubNode = e.firstChild();
while ( eSubNode.isElement() )
{
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
if ( ee.tagName() == "escapedText" )
{
inplaceText = ee.firstChild().toCDATASection().data();
}
else if ( ee.tagName() == "callout" )
{
inplaceCallout[0].setX(ee.attribute( "ax" ).toDouble());
inplaceCallout[0].setY(ee.attribute( "ay" ).toDouble());
inplaceCallout[1].setX(ee.attribute( "bx" ).toDouble());
inplaceCallout[1].setY(ee.attribute( "by" ).toDouble());
inplaceCallout[2].setX(ee.attribute( "cx" ).toDouble());
inplaceCallout[2].setY(ee.attribute( "cy" ).toDouble());
}
}
// loading complete
break;
}
}
void TextAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// recurse to parent objects storing properties
Annotation::store( node, document );
// create [text] element
QDomElement textElement = document.createElement( "text" );
node.appendChild( textElement );
// store the optional attributes
if ( textType != Linked )
textElement.setAttribute( "type", (int)textType );
if ( textIcon != "Comment" )
textElement.setAttribute( "icon", textIcon );
if ( inplaceAlign )
textElement.setAttribute( "align", inplaceAlign );
if ( inplaceIntent != Unknown )
textElement.setAttribute( "intent", (int)inplaceIntent );
textElement.setAttribute( "font", textFont.toString() );
// Sub-Node-1 - escapedText
if ( !inplaceText.isEmpty() )
{
QDomElement escapedText = document.createElement( "escapedText" );
textElement.appendChild( escapedText );
QDomCDATASection textCData = document.createCDATASection( inplaceText );
escapedText.appendChild( textCData );
}
// Sub-Node-2 - callout
if ( inplaceCallout[0].x() != 0.0 )
{
QDomElement calloutElement = document.createElement( "callout" );
textElement.appendChild( calloutElement );
calloutElement.setAttribute( "ax", inplaceCallout[0].x() );
calloutElement.setAttribute( "ay", inplaceCallout[0].y() );
calloutElement.setAttribute( "bx", inplaceCallout[1].x() );
calloutElement.setAttribute( "by", inplaceCallout[1].y() );
calloutElement.setAttribute( "cx", inplaceCallout[2].x() );
calloutElement.setAttribute( "cy", inplaceCallout[2].y() );
}
}
/** LineAnnotation [Annotation] */
LineAnnotation::LineAnnotation()
: Annotation(), lineStartStyle( None ), lineEndStyle( None ),
lineClosed( false ), lineLeadingFwdPt( 0 ), lineLeadingBackPt( 0 ),
lineShowCaption( false ), lineIntent( Unknown )
{}
LineAnnotation::LineAnnotation( const QDomNode & node )
: Annotation( node ), lineStartStyle( None ), lineEndStyle( None ),
lineClosed( false ), lineLeadingFwdPt( 0 ), lineLeadingBackPt( 0 ),
lineShowCaption( false ), lineIntent( Unknown )
{
// loop through the whole children looking for a 'line' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != "line" )
continue;
// parse the attributes
if ( e.hasAttribute( "startStyle" ) )
lineStartStyle = (LineAnnotation::TermStyle)e.attribute( "startStyle" ).toInt();
if ( e.hasAttribute( "endStyle" ) )
lineEndStyle = (LineAnnotation::TermStyle)e.attribute( "endStyle" ).toInt();
if ( e.hasAttribute( "closed" ) )
lineClosed = e.attribute( "closed" ).toInt();
if ( e.hasAttribute( "innerColor" ) )
lineInnerColor = QColor( e.attribute( "innerColor" ) );
if ( e.hasAttribute( "leadFwd" ) )
lineLeadingFwdPt = e.attribute( "leadFwd" ).toDouble();
if ( e.hasAttribute( "leadBack" ) )
lineLeadingBackPt = e.attribute( "leadBack" ).toDouble();
if ( e.hasAttribute( "showCaption" ) )
lineShowCaption = e.attribute( "showCaption" ).toInt();
if ( e.hasAttribute( "intent" ) )
lineIntent = (LineAnnotation::LineIntent)e.attribute( "intent" ).toInt();
// parse all 'point' subnodes
QDomNode pointNode = e.firstChild();
while ( pointNode.isElement() )
{
QDomElement pe = pointNode.toElement();
pointNode = pointNode.nextSibling();
if ( pe.tagName() != "point" )
continue;
QPointF p(pe.attribute( "x", "0.0" ).toDouble(), pe.attribute( "y", "0.0" ).toDouble());
linePoints.append( p );
}
// loading complete
break;
}
}
void LineAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// recurse to parent objects storing properties
Annotation::store( node, document );
// create [line] element
QDomElement lineElement = document.createElement( "line" );
node.appendChild( lineElement );
// store the attributes
if ( lineStartStyle != None )
lineElement.setAttribute( "startStyle", (int)lineStartStyle );
if ( lineEndStyle != None )
lineElement.setAttribute( "endStyle", (int)lineEndStyle );
if ( lineClosed )
lineElement.setAttribute( "closed", lineClosed );
if ( lineInnerColor.isValid() )
lineElement.setAttribute( "innerColor", lineInnerColor.name() );
if ( lineLeadingFwdPt != 0.0 )
lineElement.setAttribute( "leadFwd", lineLeadingFwdPt );
if ( lineLeadingBackPt != 0.0 )
lineElement.setAttribute( "leadBack", lineLeadingBackPt );
if ( lineShowCaption )
lineElement.setAttribute( "showCaption", lineShowCaption );
if ( lineIntent != Unknown )
lineElement.setAttribute( "intent", lineIntent );
// append the list of points
int points = linePoints.count();
if ( points > 1 )
{
QLinkedList<QPointF>::const_iterator it = linePoints.begin(), end = linePoints.end();
while ( it != end )
{
const QPointF & p = *it;
QDomElement pElement = document.createElement( "point" );
lineElement.appendChild( pElement );
pElement.setAttribute( "x", p.x() );
pElement.setAttribute( "y", p.y() );
}
}
}
/** GeomAnnotation [Annotation] */
GeomAnnotation::GeomAnnotation()
: Annotation(), geomType( InscribedSquare ), geomWidthPt( 18 )
{}
GeomAnnotation::GeomAnnotation( const QDomNode & node )
: Annotation( node ), geomType( InscribedSquare ), geomWidthPt( 18 )
{
// loop through the whole children looking for a 'geom' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != "geom" )
continue;
// parse the attributes
if ( e.hasAttribute( "type" ) )
geomType = (GeomAnnotation::GeomType)e.attribute( "type" ).toInt();
if ( e.hasAttribute( "color" ) )
geomInnerColor = QColor( e.attribute( "color" ) );
if ( e.hasAttribute( "width" ) )
geomWidthPt = e.attribute( "width" ).toInt();
// loading complete
break;
}
}
void GeomAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// recurse to parent objects storing properties
Annotation::store( node, document );
// create [geom] element
QDomElement geomElement = document.createElement( "geom" );
node.appendChild( geomElement );
// append the optional attributes
if ( geomType != InscribedSquare )
geomElement.setAttribute( "type", (int)geomType );
if ( geomInnerColor.isValid() )
geomElement.setAttribute( "color", geomInnerColor.name() );
if ( geomWidthPt != 18 )
geomElement.setAttribute( "width", geomWidthPt );
}
/** HighlightAnnotation [Annotation] */
HighlightAnnotation::HighlightAnnotation()
: Annotation(), highlightType( Highlight )
{}
HighlightAnnotation::HighlightAnnotation( const QDomNode & node )
: Annotation( node ), highlightType( Highlight )
{
// loop through the whole children looking for a 'hl' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != "hl" )
continue;
// parse the attributes
if ( e.hasAttribute( "type" ) )
highlightType = (HighlightAnnotation::HighlightType)e.attribute( "type" ).toInt();
// parse all 'quad' subnodes
QDomNode quadNode = e.firstChild();
for ( ; quadNode.isElement(); quadNode = quadNode.nextSibling() )
{
QDomElement qe = quadNode.toElement();
if ( qe.tagName() != "quad" )
continue;
Quad q;
q.points[0].setX(qe.attribute( "ax", "0.0" ).toDouble());
q.points[0].setY(qe.attribute( "ay", "0.0" ).toDouble());
q.points[1].setX(qe.attribute( "bx", "0.0" ).toDouble());
q.points[1].setY(qe.attribute( "by", "0.0" ).toDouble());
q.points[2].setX(qe.attribute( "cx", "0.0" ).toDouble());
q.points[2].setY(qe.attribute( "cy", "0.0" ).toDouble());
q.points[3].setX(qe.attribute( "dx", "0.0" ).toDouble());
q.points[3].setY(qe.attribute( "dy", "0.0" ).toDouble());
q.capStart = qe.hasAttribute( "start" );
q.capEnd = qe.hasAttribute( "end" );
q.feather = qe.attribute( "feather", "0.1" ).toDouble();
highlightQuads.append( q );
}
// loading complete
break;
}
}
void HighlightAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// recurse to parent objects storing properties
Annotation::store( node, document );
// create [hl] element
QDomElement hlElement = document.createElement( "hl" );
node.appendChild( hlElement );
// append the optional attributes
if ( highlightType != Highlight )
hlElement.setAttribute( "type", (int)highlightType );
if ( highlightQuads.count() < 1 )
return;
// append highlight quads, all children describe quads
QList< Quad >::const_iterator it = highlightQuads.begin(), end = highlightQuads.end();
for ( ; it != end; ++it )
{
QDomElement quadElement = document.createElement( "quad" );
hlElement.appendChild( quadElement );
const Quad & q = *it;
quadElement.setAttribute( "ax", q.points[0].x() );
quadElement.setAttribute( "ay", q.points[0].y() );
quadElement.setAttribute( "bx", q.points[1].x() );
quadElement.setAttribute( "by", q.points[1].y() );
quadElement.setAttribute( "cx", q.points[2].x() );
quadElement.setAttribute( "cy", q.points[2].y() );
quadElement.setAttribute( "dx", q.points[3].x() );
quadElement.setAttribute( "dy", q.points[3].y() );
if ( q.capStart )
quadElement.setAttribute( "start", 1 );
if ( q.capEnd )
quadElement.setAttribute( "end", 1 );
quadElement.setAttribute( "feather", q.feather );
}
}
/** StampAnnotation [Annotation] */
StampAnnotation::StampAnnotation()
: Annotation(), stampIconName( "oKular" )
{}
StampAnnotation::StampAnnotation( const QDomNode & node )
: Annotation( node ), stampIconName( "oKular" )
{
// loop through the whole children looking for a 'stamp' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != "stamp" )
continue;
// parse the attributes
if ( e.hasAttribute( "icon" ) )
stampIconName = e.attribute( "icon" );
// loading complete
break;
}
}
void StampAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// recurse to parent objects storing properties
Annotation::store( node, document );
// create [stamp] element
QDomElement stampElement = document.createElement( "stamp" );
node.appendChild( stampElement );
// append the optional attributes
if ( stampIconName != "oKular" )
stampElement.setAttribute( "icon", stampIconName );
}
/** InkAnnotation [Annotation] */
InkAnnotation::InkAnnotation()
: Annotation()
{}
InkAnnotation::InkAnnotation( const QDomNode & node )
: Annotation( node )
{
// loop through the whole children looking for a 'ink' element
QDomNode subNode = node.firstChild();
while( subNode.isElement() )
{
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if ( e.tagName() != "ink" )
continue;
// parse the 'path' subnodes
QDomNode pathNode = e.firstChild();
while ( pathNode.isElement() )
{
QDomElement pathElement = pathNode.toElement();
pathNode = pathNode.nextSibling();
if ( pathElement.tagName() != "path" )
continue;
// build each path parsing 'point' subnodes
QLinkedList<QPointF> path;
QDomNode pointNode = pathElement.firstChild();
while ( pointNode.isElement() )
{
QDomElement pointElement = pointNode.toElement();
pointNode = pointNode.nextSibling();
if ( pointElement.tagName() != "point" )
continue;
QPointF p(pointElement.attribute( "x", "0.0" ).toDouble(), pointElement.attribute( "y", "0.0" ).toDouble());
path.append( p );
}
// add the path to the path list if it contains at least 2 nodes
if ( path.count() >= 2 )
inkPaths.append( path );
}
// loading complete
break;
}
}
void InkAnnotation::store( QDomNode & node, QDomDocument & document ) const
{
// recurse to parent objects storing properties
Annotation::store( node, document );
// create [ink] element
QDomElement inkElement = document.createElement( "ink" );
node.appendChild( inkElement );
// append the optional attributes
if ( inkPaths.count() < 1 )
return;
QList< QLinkedList<QPointF> >::const_iterator pIt = inkPaths.begin(), pEnd = inkPaths.end();
for ( ; pIt != pEnd; ++pIt )
{
QDomElement pathElement = document.createElement( "path" );
inkElement.appendChild( pathElement );
const QLinkedList<QPointF> & path = *pIt;
QLinkedList<QPointF>::const_iterator iIt = path.begin(), iEnd = path.end();
for ( ; iIt != iEnd; ++iIt )
{
const QPointF & point = *iIt;
QDomElement pointElement = document.createElement( "point" );
pathElement.appendChild( pointElement );
pointElement.setAttribute( "x", point.x() );
pointElement.setAttribute( "y", point.y() );
}
}
}
}
--- NEW FILE: poppler-annotation.h ---
/* poppler-annotation.h: qt interface to poppler
* Copyright (C) 2006, Albert Astals Cid
* Adapting code from
* Copyright (C) 2004 by Enrico Ros <eros.kde at email.it>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _POPPLER_ANNOTATION_H_
#define _POPPLER_ANNOTATION_H_
#include <QtCore/QDateTime>
#include <QtCore/QLinkedList>
#include <QtCore/QPointF>
#include <QtCore/QRectF>
#include <QtGui/QFont>
namespace Poppler {
class Annotation;
/**
* @short Helper class for (recoursive) annotation retrieval/storage.
*
*/
class AnnotationUtils
{
public:
// restore an annotation (with revisions if needed) from a dom
// element. returns a pointer to the complete annotation or 0 if
// element is invalid.
static Annotation * createAnnotation( const QDomElement & annElement );
// save the 'ann' annotations as a child of parentElement taking
// care of saving all revisions if 'ann' has any.
static void storeAnnotation( const Annotation * ann,
QDomElement & annElement, QDomDocument & document );
// return an element called 'name' from the direct children of
// parentNode or a null element if not found
static QDomElement findChildElement( const QDomNode & parentNode,
const QString & name );
//static inline QRect annotationGeometry( const Annotation * ann,
// int pageWidth, int pageHeight, int scaledWidth, int scaledHeight ) const;
};
/**
* @short Annotation struct holds properties shared by all annotations.
*
* An Annotation is an object (text note, highlight, sound, popup window, ..)
* contained by a Page in the document.
*
*/
struct Annotation
{
// enum definitions
// WARNING!!! oKular uses that very same values so if you change them notify the author!
enum SubType { AText = 1, ALine = 2, AGeom = 3, AHighlight = 4, AStamp = 5,
AInk = 6, A_BASE = 0 };
enum Flag { Hidden = 1, FixedSize = 2, FixedRotation = 4, DenyPrint = 8,
DenyWrite = 16, DenyDelete = 32, ToggleHidingOnMouse = 64, External = 128 };
enum LineStyle { Solid = 1, Dashed = 2, Beveled = 4, Inset = 8, Underline = 16 };
enum LineEffect { NoEffect = 1, Cloudy = 2};
enum RevScope { Reply = 1, Group = 2, Delete = 4 };
enum RevType { None = 1, Marked = 2, Unmarked = 4, Accepted = 8, Rejected = 16, Cancelled = 32, Completed = 64 };
/** properties: contents related */
QString author; // ''
QString contents; // ''
QString uniqueName; // '#NUMBER#'
QDateTime modifyDate; // before or equal to currentDateTime()
QDateTime creationDate; // before or equal to modifyDate
/** properties: look/interaction related */
int flags; // 0
QRectF boundary; // valid or isNull()
struct Style
{
// appearance properties
QColor color; // black
double opacity; // 1.0
// pen properties
double width; // 1.0
LineStyle style; // LineStyle::Solid
double xCorners; // 0.0
double yCorners; // 0.0
int marks; // 3
int spaces; // 0
// pen effects
LineEffect effect; // LineEffect::NoEffect
double effectIntensity; // 1.0
// default initializer
Style();
} style;
/** properties: popup window */
struct Window
{
// window state (Hidden, FixedRotation, Deny* flags allowed)
int flags; // -1 (never initialized) -> 0 (if inited and shown)
// geometric properties
QPointF topLeft; // no default, inited to boundary.topLeft
int width; // no default
int height; // no default
// window contens/override properties
QString title; // '' text in the titlebar (overrides author)
QString summary; // '' short description (displayed if not empty)
QString text; // '' text for the window (overrides annot->contents)
// default initializer
Window();
} window;
/** properties: versioning */
struct Revision
{
// child revision
Annotation * annotation; // not null
// scope and type of revision
RevScope scope; // Reply
RevType type; // None
// default initializer
Revision();
};
QLinkedList< Revision > revisions; // empty by default
// methods: query annotation's type for runtime type identification
virtual SubType subType() const { return A_BASE; }
//QRect geometry( int scaledWidth, int scaledHeight, KPDFPage * page );
// methods: storage/retrieval from xml nodes
Annotation( const QDomNode & node );
virtual void store( QDomNode & parentNode, QDomDocument & document ) const;
// methods: default constructor / virtual destructor
Annotation();
virtual ~Annotation();
};
// a helper used to shorten the code presented below
#define AN_COMMONDECL( className, rttiType )\
className();\
className( const class QDomNode & node );\
void store( QDomNode & parentNode, QDomDocument & document ) const;\
SubType subType() const { return rttiType; }
struct TextAnnotation : public Annotation
{
// common stuff for Annotation derived classes
AN_COMMONDECL( TextAnnotation, AText );
// local enums
enum TextType { Linked, InPlace };
enum InplaceIntent { Unknown, Callout, TypeWriter };
// data fields
TextType textType; // Linked
QString textIcon; // 'Comment'
QFont textFont; // app def font
int inplaceAlign; // 0:left, 1:center, 2:right
QString inplaceText; // '' overrides contents
QPointF inplaceCallout[3]; //
InplaceIntent inplaceIntent; // Unknown
};
struct LineAnnotation : public Annotation
{
// common stuff for Annotation derived classes
AN_COMMONDECL( LineAnnotation, ALine )
// local enums
enum TermStyle { Square, Circle, Diamond, OpenArrow, ClosedArrow, None,
Butt, ROpenArrow, RClosedArrow, Slash };
enum LineIntent { Unknown, Arrow, Dimension, PolygonCloud };
// data fields (note uses border for rendering style)
QLinkedList<QPointF> linePoints;
TermStyle lineStartStyle; // None
TermStyle lineEndStyle; // None
bool lineClosed; // false (if true draw close shape)
QColor lineInnerColor; //
double lineLeadingFwdPt; // 0.0
double lineLeadingBackPt; // 0.0
bool lineShowCaption; // false
LineIntent lineIntent; // Unknown
};
struct GeomAnnotation : public Annotation
{
// common stuff for Annotation derived classes
AN_COMMONDECL( GeomAnnotation, AGeom )
// common enums
enum GeomType { InscribedSquare, InscribedCircle };
// data fields (note uses border for rendering style)
GeomType geomType; // InscribedSquare
QColor geomInnerColor; //
int geomWidthPt; // 18
};
struct HighlightAnnotation : public Annotation
{
// common stuff for Annotation derived classes
AN_COMMONDECL( HighlightAnnotation, AHighlight )
// local enums
enum HighlightType { Highlight, Squiggly, Underline, StrikeOut };
// data fields
HighlightType highlightType; // Highlight
struct Quad
{
QPointF points[4]; // 8 valid coords
bool capStart; // false (vtx 1-4) [K]
bool capEnd; // false (vtx 2-3) [K]
double feather; // 0.1 (in range 0..1) [K]
};
QList< Quad > highlightQuads; // not empty
};
struct StampAnnotation : public Annotation
{
// common stuff for Annotation derived classes
AN_COMMONDECL( StampAnnotation, AStamp )
// data fields
QString stampIconName; // 'kpdf'
};
struct InkAnnotation : public Annotation
{
// common stuff for Annotation derived classes
AN_COMMONDECL( InkAnnotation, AInk )
// data fields
QList< QLinkedList<QPointF> > inkPaths;
};
}
#endif
Index: poppler-link.cc
===================================================================
RCS file: /cvs/poppler/poppler/qt4/src/poppler-link.cc,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- poppler-link.cc 9 May 2006 20:07:06 -0000 1.2
+++ poppler-link.cc 12 May 2006 20:40:05 -0000 1.3
@@ -1,5 +1,7 @@
-/* poppler-page.cc: qt interface to poppler
+/* poppler-link.cc: qt interface to poppler
* Copyright (C) 2006, Albert Astals Cid
+ * Adapting code from
+ * Copyright (C) 2004 by Enrico Ros <eros.kde at email.it>
*
* 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
Index: poppler-link.h
===================================================================
RCS file: /cvs/poppler/poppler/qt4/src/poppler-link.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- poppler-link.h 9 May 2006 20:07:06 -0000 1.1
+++ poppler-link.h 12 May 2006 20:40:05 -0000 1.2
@@ -1,4 +1,4 @@
-/* poppler-link.cc: qt interface to poppler
+/* poppler-link.h: qt interface to poppler
* Copyright (C) 2006, Albert Astals Cid <aacid at kde.org>
* Adapting code from
* Copyright (C) 2004 by Enrico Ros <eros.kde at email.it>
Index: poppler-page.cc
===================================================================
RCS file: /cvs/poppler/poppler/qt4/src/poppler-page.cc,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- poppler-page.cc 9 May 2006 20:07:06 -0000 1.20
+++ poppler-page.cc 12 May 2006 20:40:05 -0000 1.21
@@ -33,6 +33,7 @@
#include "poppler-private.h"
#include "poppler-page-transition-private.h"
+#include "poppler-annotation-helper.h"
namespace Poppler {
@@ -405,4 +406,694 @@
return popplerLinks;
}
+QList<Annotation*> Page::annotations() const
+{
+ Object annotArray;
+ ::Page *pdfPage = m_page->parentDoc->m_doc->doc.getCatalog()->getPage(m_page->index + 1);
+ pdfPage->getAnnots( &annotArray );
+ if ( !annotArray.isArray() || annotArray.arrayGetLength() < 1 )
+ 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." << endl;
+ 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" << endl;
+ 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->textType = TextAnnotation::Linked;
+ // -> textIcon
+ XPDFReader::lookupName( annotDict, "Name", t->textIcon );
+ if ( !t->textIcon.isEmpty() )
+ {
+ t->textIcon = t->textIcon.toLower();
+ t->textIcon.remove( ' ' );
+ }
+ // 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->textType = TextAnnotation::InPlace;
+ // -> textFont
+ QString textFormat;
+ XPDFReader::lookupString( annotDict, "DA", textFormat );
+ // TODO, fill t->textFont using textFormat if not empty
+ // -> inplaceAlign
+ XPDFReader::lookupInt( annotDict, "Q", t->inplaceAlign );
+ // -> inplaceText (simple)
+ XPDFReader::lookupString( annotDict, "DS", t->inplaceText );
+ // -> inplaceText (complex override)
+ XPDFReader::lookupString( annotDict, "RC", t->inplaceText );
+ // -> inplaceCallout
+ double c[6];
+ int n = XPDFReader::lookupNumArray( annotDict, "CL", c, 6 );
+ if ( n >= 4 )
+ {
+ XPDFReader::transform( MTX, c[0], c[1], t->inplaceCallout[0] );
+ XPDFReader::transform( MTX, c[2], c[3], t->inplaceCallout[1] );
+ if ( n == 6 )
+ XPDFReader::transform( MTX, c[4], c[5], t->inplaceCallout[2] );
+ }
+ // -> inplaceIntent
+ QString intentName;
+ XPDFReader::lookupString( annotDict, "IT", intentName );
+ if ( intentName == "FreeTextCallout" )
+ t->inplaceIntent = TextAnnotation::Callout;
+ else if ( intentName == "FreeTextTypeWriter" )
+ t->inplaceIntent = 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, (subType == "Line") ? "L" : "Vertices", c, 100 );
+ if ( num < 4 || (num % 2) != 0 )
+ {
+ qDebug() << "L/Vertices wrong fol Line/Poly." << endl;
+ delete annotation;
+ annot.free();
+ continue;
+ }
+ for ( int i = 0; i < num; i += 2 )
+ {
+ QPointF p;
+ XPDFReader::transform( MTX, c[i], c[i+1], p );
+ l->linePoints.push_back( p );
+ }
+ // -> 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->lineStartStyle = LineAnnotation::Square;
+ else if ( !strcmp( name, "Circle" ) )
+ l->lineStartStyle = LineAnnotation::Circle;
+ else if ( !strcmp( name, "Diamond" ) )
+ l->lineStartStyle = LineAnnotation::Diamond;
+ else if ( !strcmp( name, "OpenArrow" ) )
+ l->lineStartStyle = LineAnnotation::OpenArrow;
+ else if ( !strcmp( name, "ClosedArrow" ) )
+ l->lineStartStyle = LineAnnotation::ClosedArrow;
+ else if ( !strcmp( name, "None" ) )
+ l->lineStartStyle = LineAnnotation::None;
+ else if ( !strcmp( name, "Butt" ) )
+ l->lineStartStyle = LineAnnotation::Butt;
+ else if ( !strcmp( name, "ROpenArrow" ) )
+ l->lineStartStyle = LineAnnotation::ROpenArrow;
+ else if ( !strcmp( name, "RClosedArrow" ) )
+ l->lineStartStyle = LineAnnotation::RClosedArrow;
+ else if ( !strcmp( name, "Slash" ) )
+ l->lineStartStyle = LineAnnotation::Slash;
+ }
+ styleObj.free();
+ // -> lineEndStyle
+ leArray.arrayGet( 1, &styleObj );
+ if ( styleObj.isName() )
+ {
+ const char * name = styleObj.getName();
+ if ( !strcmp( name, "Square" ) )
+ l->lineEndStyle = LineAnnotation::Square;
+ else if ( !strcmp( name, "Circle" ) )
+ l->lineEndStyle = LineAnnotation::Circle;
+ else if ( !strcmp( name, "Diamond" ) )
+ l->lineEndStyle = LineAnnotation::Diamond;
+ else if ( !strcmp( name, "OpenArrow" ) )
+ l->lineEndStyle = LineAnnotation::OpenArrow;
+ else if ( !strcmp( name, "ClosedArrow" ) )
+ l->lineEndStyle = LineAnnotation::ClosedArrow;
+ else if ( !strcmp( name, "None" ) )
+ l->lineEndStyle = LineAnnotation::None;
+ else if ( !strcmp( name, "Butt" ) )
+ l->lineEndStyle = LineAnnotation::Butt;
+ else if ( !strcmp( name, "ROpenArrow" ) )
+ l->lineEndStyle = LineAnnotation::ROpenArrow;
+ else if ( !strcmp( name, "RClosedArrow" ) )
+ l->lineEndStyle = LineAnnotation::RClosedArrow;
+ else if ( !strcmp( name, "Slash" ) )
+ l->lineEndStyle = LineAnnotation::Slash;
+ }
+ styleObj.free();
+ }
+ leArray.free();
+ // -> lineClosed
+ l->lineClosed = subType == "Polygon";
+ // -> lineInnerColor
+ XPDFReader::lookupColor( annotDict, "IC", l->lineInnerColor );
+ // -> lineLeadingFwdPt
+ XPDFReader::lookupNum( annotDict, "LL", l->lineLeadingFwdPt );
+ // -> lineLeadingBackPt
+ XPDFReader::lookupNum( annotDict, "LLE", l->lineLeadingBackPt );
+ // -> lineShowCaption
+ XPDFReader::lookupBool( annotDict, "Cap", l->lineShowCaption );
+ // -> lineIntent
+ QString intentName;
+ XPDFReader::lookupString( annotDict, "IT", intentName );
+ if ( intentName == "LineArrow" )
+ l->lineIntent = LineAnnotation::Arrow;
+ else if ( intentName == "LineDimension" )
+ l->lineIntent = LineAnnotation::Dimension;
+ else if ( intentName == "PolygonCloud" )
+ l->lineIntent = LineAnnotation::PolygonCloud;
+ }
+ else if ( subType == "Square" || subType == "Circle" )
+ {
+ // parse GeomAnnotation params
+ GeomAnnotation * g = new GeomAnnotation();
+ annotation = g;
+
+ // -> geomType
+ if ( subType == "Square" )
+ g->geomType = GeomAnnotation::InscribedSquare;
+ else
+ g->geomType = GeomAnnotation::InscribedCircle;
+ // -> geomInnerColor
+ XPDFReader::lookupColor( annotDict, "IC", g->geomInnerColor );
+ // 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->highlightType = HighlightAnnotation::Highlight;
+ else if ( subType == "Underline" )
+ h->highlightType = HighlightAnnotation::Underline;
+ else if ( subType == "Squiggly" )
+ h->highlightType = HighlightAnnotation::Squiggly;
+ else if ( subType == "StrikeOut" )
+ h->highlightType = 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." << endl;
+ delete annotation;
+ annot.free();
+ continue;
+ }
+ 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
+ h->highlightQuads.append( quad );
+ }
+ }
+ else if ( subType == "Stamp" )
+ {
+ // parse StampAnnotation params
+ StampAnnotation * s = new StampAnnotation();
+ annotation = s;
+
+ // -> stampIconName
+ XPDFReader::lookupName( annotDict, "Name", s->stampIconName );
+ }
+ 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" << endl;
+ delete annotation;
+ annot.free();
+ continue;
+ }
+ int pathsNumber = pathsArray.arrayGetLength();
+ 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
+ k->inkPaths.push_back( localList );
+ }
+ 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 Annotation();
+ annotation = popup->dummyAnnotation;
+ }
+ else if ( subType == "Link" )
+ {
+ // ignore links (this may change in future)
+ annot.free();
+ continue;
+ }
+ else
+ {
+ // MISSING: Caret, FileAttachment, Sound, Movie, Widget,
+ // Screen, PrinterMark, TrapNet, Watermark, 3D
+ qDebug() << "annotation '" << subType << "' not supported" << endl;
+ 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." << endl;
+ 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 );
+ annotation->boundary.setTopLeft(topLeft);
+ annotation->boundary.setBottomRight(bottomRight);
+ if ( annotation->boundary.left() > annotation->boundary.right() )
+ {
+ double aux = annotation->boundary.left();
+ annotation->boundary.setLeft(annotation->boundary.right());
+ annotation->boundary.setRight(aux);
+ }
+ if ( annotation->boundary.top() > annotation->boundary.bottom() )
+ {
+ double aux = annotation->boundary.top();
+ annotation->boundary.setTop(annotation->boundary.bottom());
+ annotation->boundary.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];
+ }
+ // -> contents
+ XPDFReader::lookupString( annotDict, "Contents", annotation->contents );
+ // -> uniqueName
+ XPDFReader::lookupString( annotDict, "NM", annotation->uniqueName );
+ // -> modifyDate (and -> creationDate)
+ XPDFReader::lookupDate( annotDict, "M", annotation->modifyDate );
+ if ( annotation->creationDate.isNull() && !annotation->modifyDate.isNull() )
+ annotation->creationDate = annotation->modifyDate;
+ // -> flags: set the external attribute since it's embedded on file
+ annotation->flags |= Annotation::External;
+ // -> flags
+ int flags = 0;
+ XPDFReader::lookupInt( annotDict, "F", flags );
+ if ( flags & 0x2 )
+ annotation->flags |= Annotation::Hidden;
+ if ( flags & 0x8 )
+ annotation->flags |= Annotation::FixedSize;
+ if ( flags & 0x10 )
+ annotation->flags |= Annotation::FixedRotation;
+ if ( !(flags & 0x4) )
+ annotation->flags |= Annotation::DenyPrint;
+ if ( flags & 0x40 )
+ annotation->flags |= (Annotation::DenyWrite | Annotation::DenyDelete);
+ if ( flags & 0x80 )
+ annotation->flags |= Annotation::DenyDelete;
+ if ( flags & 0x100 )
+ annotation->flags |= Annotation::ToggleHidingOnMouse;
+ // -> 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
+ XPDFReader::lookupDate( annotDict, "CreationDate", annotation->creationDate );
+ // -> style.opacity
+ XPDFReader::lookupNum( annotDict, "CA", annotation->style.opacity );
+ // -> window.title and author
+ XPDFReader::lookupString( annotDict, "T", annotation->window.title );
+ annotation->author = 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() << "PDFGenerator: clash for annotations with ID:" << annotID << endl;
+ 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() << "PDFGenerator: can't resolve popup "
+ << request.popupWindowID << "." << endl;
+ 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
+ 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() << "PDFGenerator: can't reparent annotation to "
+ << parentID << "." << endl;
+ 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;
+ }
+ }
+
+ /** 5 - finally RETURN ANNOTATIONS */
+ return annotationsMap.values();
+}
+
+
}
Index: poppler-qt4.h
===================================================================
RCS file: /cvs/poppler/poppler/qt4/src/poppler-qt4.h,v
retrieving revision 1.31
retrieving revision 1.32
diff -u -d -r1.31 -r1.32
--- poppler-qt4.h 9 May 2006 20:07:06 -0000 1.31
+++ poppler-qt4.h 12 May 2006 20:40:05 -0000 1.32
@@ -28,8 +28,9 @@
#include <QtGui/QPixmap>
#include <QtXml/QDomDocument>
-#include <poppler-page-transition.h>
+#include <poppler-annotation.h>
#include <poppler-link.h>
+#include <poppler-page-transition.h>
class EmbFile;
@@ -395,6 +396,11 @@
*/
QList<Link*> links() const;
+ /**
+ Returns the annotations of the page
+ */
+ QList<Annotation*> annotations () const;
+
private:
Page(const Document *doc, int index);
PageData *m_page;
More information about the poppler
mailing list