OSDN Git Service

okular: fix xps generator printing
[kde/kde-extraapps.git] / okular / generators / xps / generator_xps.cpp
1 /*
2   Copyright (C)  2006, 2009  Brad Hards <bradh@frogmouth.net>
3
4   This program is free software; you can redistribute it and/or
5   modify it under the terms of the GNU General Public License
6   as published by the Free Software Foundation; either version 2
7   of the License, or (at your option) any later version
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17   02110-1301, USA.
18 */
19
20 #include "generator_xps.h"
21
22 #include <qdatetime.h>
23 #include <qfile.h>
24 #include <qlist.h>
25 #include <qpainter.h>
26 #include <qprinter.h>
27 #include <qpen.h>
28 #include <kaboutdata.h>
29 #include <kglobal.h>
30 #include <klocale.h>
31 #include <kurl.h>
32 #include <ktemporaryfile.h>
33 #include <QBuffer>
34 #include <QFileInfo>
35 #include <QImageReader>
36 #include <QMutex>
37
38 #include <core/document.h>
39 #include <core/page.h>
40 #include <core/area.h>
41 #include <core/utils.h>
42
43 #include <sys/stat.h>
44
45 const int XpsDebug = 4712;
46
47 static KAboutData createAboutData()
48 {
49     KAboutData aboutData(
50          "okular_xps",
51          "okular_xps",
52          ki18n( "XPS Backend" ),
53          "0.3.3",
54          ki18n( "An XPS backend" ),
55          KAboutData::License_GPL,
56          ki18n( "© 2006-2007 Brad Hards\n"
57                 "© 2007 Jiri Klement\n"
58                 "© 2008 Pino Toscano" )
59     );
60     aboutData.addAuthor( ki18n( "Brad Hards" ), KLocalizedString(), "bradh@frogmouth.net" );
61     aboutData.addAuthor( ki18n( "Jiri Klement" ), KLocalizedString(), "jiri.klement@gmail.com" );
62     aboutData.addAuthor( ki18n( "Pino Toscano" ), KLocalizedString(), "pino@kde.org" );
63     return aboutData;
64 }
65
66 OKULAR_EXPORT_PLUGIN( XpsGenerator, createAboutData() )
67
68 Q_DECLARE_METATYPE( QGradient* )
69 Q_DECLARE_METATYPE( XpsPathFigure* )
70 Q_DECLARE_METATYPE( XpsPathGeometry* )
71
72 // From Qt4
73 static int hex2int(char hex)
74 {
75     QChar hexchar = QLatin1Char(hex);
76     int v;
77     if (hexchar.isDigit())
78         v = hexchar.digitValue();
79     else if (hexchar >= QLatin1Char('A') && hexchar <= QLatin1Char('F'))
80         v = hexchar.cell() - 'A' + 10;
81     else if (hexchar >= QLatin1Char('a') && hexchar <= QLatin1Char('f'))
82         v = hexchar.cell() - 'a' + 10;
83     else
84         v = -1;
85     return v;
86 }
87
88 // Modified from Qt4
89 static QColor hexToRgba(const char *name)
90 {
91     if(name[0] != '#')
92         return QColor();
93     name++; // eat the leading '#'
94     int len = qstrlen(name);
95     int r, g, b;
96     int a = 255;
97     if (len == 6) {
98         r = (hex2int(name[0]) << 4) + hex2int(name[1]);
99         g = (hex2int(name[2]) << 4) + hex2int(name[3]);
100         b = (hex2int(name[4]) << 4) + hex2int(name[5]);
101     } else if (len == 8) {
102         a = (hex2int(name[0]) << 4) + hex2int(name[1]);
103         r = (hex2int(name[2]) << 4) + hex2int(name[3]);
104         g = (hex2int(name[4]) << 4) + hex2int(name[5]);
105         b = (hex2int(name[6]) << 4) + hex2int(name[7]);
106     } else {
107         r = g = b = -1;
108     }
109     if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255) {
110         return QColor();
111     }
112     return QColor(r,g,b,a);
113 }
114
115 static QRectF stringToRectF( const QString &data )
116 {
117     QStringList numbers = data.split(',');
118     QPointF origin( numbers.at(0).toDouble(), numbers.at(1).toDouble() );
119     QSizeF size( numbers.at(2).toDouble(), numbers.at(3).toDouble() );
120     return QRectF( origin, size );
121 }
122
123 // Read next token of abbreviated path data
124 static bool nextAbbPathToken(AbbPathToken *token)
125 {
126     int *curPos = &token->curPos;
127     QString data = token->data;
128
129     while ((*curPos < data.length()) && (data.at(*curPos).isSpace()))
130     {
131         (*curPos)++;
132     }
133
134     if (*curPos == data.length())
135     {
136         token->type = abtEOF;
137         return true;
138     }
139
140     QChar ch = data.at(*curPos);
141
142     if (ch.isNumber() || (ch == '+') || (ch == '-'))
143     {
144         int start = *curPos;
145         while ((*curPos < data.length()) && (!data.at(*curPos).isSpace()) && (data.at(*curPos) != ',') && (!data.at(*curPos).isLetter() || data.at(*curPos) == 'e'))
146     {
147         (*curPos)++;
148     }
149     token->number = data.mid(start, *curPos - start).toDouble();
150     token->type = abtNumber;
151
152     } else if (ch == ',')
153     {
154         token->type = abtComma;
155         (*curPos)++;
156     } else if (ch.isLetter())
157     {
158         token->type = abtCommand;
159         token->command = data.at(*curPos).cell();
160         (*curPos)++;
161     } else
162     {
163         (*curPos)++;
164         return false;
165     }
166
167     return true;
168 }
169
170 /**
171     Read point (two reals delimited by comma) from abbreviated path data
172 */
173 static QPointF getPointFromString(AbbPathToken *token, bool relative, const QPointF &currentPosition) {
174     //TODO Check grammar
175
176     QPointF result;
177     result.rx() = token->number;
178     nextAbbPathToken(token);
179     nextAbbPathToken(token); // ,
180     result.ry() = token->number;
181     nextAbbPathToken(token);
182
183     if (relative)
184     {
185         result += currentPosition;
186     }
187
188     return result;
189 }
190
191 /**
192     Read point (two reals delimited by comma) from string
193 */
194 static QPointF getPointFromString(const QString &string)
195 {
196     const int commaPos = string.indexOf(QLatin1Char(','));
197     if (commaPos == -1 || string.indexOf(QLatin1Char(','), commaPos + 1) != -1)
198         return QPointF();
199
200     QPointF result;
201     bool ok = false;
202     QStringRef ref = string.midRef(0, commaPos);
203     result.setX(QString::fromRawData(ref.constData(), ref.count()).toDouble(&ok));
204     if (!ok)
205         return QPointF();
206
207     ref = string.midRef(commaPos + 1);
208     result.setY(QString::fromRawData(ref.constData(), ref.count()).toDouble(&ok));
209     if (!ok)
210         return QPointF();
211
212     return result;
213 }
214
215 static Qt::FillRule fillRuleFromString( const QString &data, Qt::FillRule def = Qt::OddEvenFill )
216 {
217     if ( data == QLatin1String( "EvenOdd" ) ) {
218         return Qt::OddEvenFill;
219     } else if ( data == QLatin1String( "NonZero" ) ) {
220         return Qt::WindingFill;
221     }
222     return def;
223 }
224
225 /**
226     Parse an abbreviated path "Data" description
227     \param data the string containing the whitespace separated values
228
229     \see XPS specification 4.2.3 and Appendix G
230 */
231 static QPainterPath parseAbbreviatedPathData( const QString &data)
232 {
233     QPainterPath path;
234
235     AbbPathToken token;
236
237     token.data = data;
238     token.curPos = 0;
239
240     nextAbbPathToken(&token);
241
242     // Used by Smooth cubic curve (command s)
243     char lastCommand = ' ';
244     QPointF lastSecondControlPoint;
245
246     while (true)
247     {
248         if (token.type != abtCommand)
249         {
250             if (token.type != abtEOF)
251             {
252                 kDebug(XpsDebug).nospace() << "Error in parsing abbreviated path data (" << token.type << "@" << token.curPos << "): " << data;
253             }
254             return path;
255         }
256
257         char command = QChar(token.command).toLower().cell();
258         bool isRelative = QChar(token.command).isLower();
259         QPointF currPos = path.currentPosition();
260         nextAbbPathToken(&token);
261
262         switch (command) {
263             case 'f':
264                 int rule;
265                 rule = (int)token.number;
266                 if (rule == 0)
267                 {
268                     path.setFillRule(Qt::OddEvenFill);
269                 }
270                 else if (rule == 1)
271                 {
272                     // In xps specs rule 1 means NonZero fill. I think it's equivalent to WindingFill but I'm not sure
273                     path.setFillRule(Qt::WindingFill);
274                 }
275                 nextAbbPathToken(&token);
276                 break;
277             case 'm': // Move
278                 while (token.type == abtNumber)
279                 {
280                     QPointF point = getPointFromString(&token, isRelative, currPos);
281                     path.moveTo(point);
282                 }
283                 break;
284             case 'l': // Line
285                 while (token.type == abtNumber)
286                 {
287                     QPointF point = getPointFromString(&token, isRelative, currPos);
288                     path.lineTo(point);
289                 }
290                 break;
291             case 'h': // Horizontal line
292                 while (token.type == abtNumber)
293                 {
294                     double x = token.number;
295                     if ( isRelative )
296                         x += path.currentPosition().x();
297                     path.lineTo(x, path.currentPosition().y());
298                     nextAbbPathToken(&token);
299                 }
300                 break;
301             case 'v': // Vertical line
302                 while (token.type == abtNumber)
303                 {
304                     double y = token.number;
305                     if ( isRelative )
306                         y += path.currentPosition().y();
307                     path.lineTo(path.currentPosition().x(), y);
308                     nextAbbPathToken(&token);
309                 }
310                 break;
311             case 'c': // Cubic bezier curve
312                 while (token.type == abtNumber)
313                 {
314                     QPointF firstControl = getPointFromString(&token, isRelative, currPos);
315                     QPointF secondControl = getPointFromString(&token, isRelative, currPos);
316                     QPointF endPoint = getPointFromString(&token, isRelative, currPos);
317                     path.cubicTo(firstControl, secondControl, endPoint);
318
319                     lastSecondControlPoint = secondControl;
320                 }
321                 break;
322             case 'q': // Quadratic bezier curve
323                 while (token.type == abtNumber)
324                 {
325                     QPointF point1 = getPointFromString(&token, isRelative, currPos);
326                     QPointF point2 = getPointFromString(&token, isRelative, currPos);
327                     path.quadTo(point1, point2);
328                 }
329                 break;
330             case 's': // Smooth cubic bezier curve
331                 while (token.type == abtNumber)
332                 {
333                     QPointF firstControl;
334                     if ((lastCommand == 's') || (lastCommand == 'c'))
335                     {
336                         firstControl = lastSecondControlPoint + (lastSecondControlPoint + path.currentPosition());
337                     }
338                     else
339                     {
340                         firstControl = path.currentPosition();
341                     }
342                     QPointF secondControl = getPointFromString(&token, isRelative, currPos);
343                     QPointF endPoint = getPointFromString(&token, isRelative, currPos);
344                     path.cubicTo(firstControl, secondControl, endPoint);
345                 }
346                 break;
347             case 'a': // Arc
348                 //TODO Implement Arc drawing
349                 while (token.type == abtNumber)
350                 {
351                     /*QPointF rp =*/ getPointFromString(&token, isRelative, currPos);
352                     /*double r = token.number;*/
353                     nextAbbPathToken(&token);
354                     /*double fArc = token.number; */
355                     nextAbbPathToken(&token);
356                     /*double fSweep = token.number; */
357                     nextAbbPathToken(&token);
358                     /*QPointF point = */getPointFromString(&token, isRelative, currPos);
359                 }
360                 break;
361             case 'z': // Close path
362                 path.closeSubpath();
363                 break;
364         }
365
366         lastCommand = command;
367     }
368
369     return path;
370 }
371
372 /**
373    Parse a "Matrix" attribute string
374    \param csv the comma separated list of values
375    \return the QTransform corresponding to the affine transform
376    given in the attribute
377
378    \see XPS specification 7.4.1
379 */
380 static QTransform attsToMatrix( const QString &csv )
381 {
382     QStringList values = csv.split( ',' );
383     if ( values.count() != 6 ) {
384         return QTransform(); // that is an identity matrix - no effect
385     }
386     return QTransform( values.at(0).toDouble(), values.at(1).toDouble(),
387                     values.at(2).toDouble(), values.at(3).toDouble(),
388                     values.at(4).toDouble(), values.at(5).toDouble() );
389 }
390
391 /**
392    \return Brush with given color or brush specified by reference to resource
393 */
394 static QBrush parseRscRefColorForBrush( const QString &data )
395 {
396     if (data[0] == '{') {
397         //TODO
398         kDebug(XpsDebug) << "Reference" << data;
399         return QBrush();
400     } else {
401         return QBrush( hexToRgba( data.toLatin1() ) );
402     }
403 }
404
405 /**
406    \return Pen with given color or Pen specified by reference to resource
407 */
408 static QPen parseRscRefColorForPen( const QString &data )
409 {
410     if (data[0] == '{') {
411         //TODO
412         kDebug(XpsDebug) << "Reference" << data;
413         return QPen();
414     } else {
415         return QPen( hexToRgba( data.toLatin1() ) );
416     }
417 }
418
419 /**
420    \return Matrix specified by given data or by referenced dictionary
421 */
422 static QTransform parseRscRefMatrix( const QString &data )
423 {
424     if (data[0] == '{') {
425         //TODO
426         kDebug(XpsDebug) << "Reference" << data;
427         return QTransform();
428     } else {
429         return attsToMatrix( data );
430     }
431 }
432
433 /**
434    \return Path specified by given data or by referenced dictionary
435 */
436 static QPainterPath parseRscRefPath( const QString &data )
437 {
438     if (data[0] == '{') {
439         //TODO
440         kDebug(XpsDebug) << "Reference" << data;
441         return QPainterPath();
442     } else {
443         return parseAbbreviatedPathData( data );
444     }
445 }
446
447 /**
448    \return The filepath of the entry
449 */
450 static QString entryFilePath(const QString &entry )
451 {
452     QString ret = entry;
453     if ( ret[ 0 ] == QLatin1Char( '/') ) {
454         ret = ret.mid( 1, ret.size() - 1);
455     }
456     return ret;
457 }
458
459 /**
460    \return The path of the entry
461 */
462 static QString entryPath(const QString &entry )
463 {
464     const int index = entry.lastIndexOf( QChar::fromLatin1( '/' ) );
465     QString ret = entry.mid( 0, index );
466     if ( index > 0 ) {
467         ret.append( QChar::fromLatin1( '/' ) );
468     }
469     return ret;
470 }
471
472 /**
473    \return The path of the entry
474 */
475 static QString entryPath( const KArchiveEntry* entry )
476 {
477     return QFile::decodeName(entry->pathname);
478 }
479
480 /**
481    \return The absolute path of the \p location, according to \p path if it's non-absolute
482    \note ZIP paths are not actually absolute, they must not start with slash
483 */
484 static QString absolutePath( const QString &path, const QString &location )
485 {
486     QString retPath;
487     if ( location.at( 0 ) == QLatin1Char( '/' ) ) {
488         // already absolute
489         retPath = location.mid(1, location.size() - 1);
490     } else {
491         KUrl url = KUrl::fromPath( path );
492         url.setFileName( location );
493         retPath = url.toLocalFile();
494     }
495     // it seems paths & file names can also be percent-encoded
496     // (XPS won't ever finish surprising me)
497     if ( retPath.contains( QLatin1Char( '%' ) ) ) {
498         retPath = QUrl::fromPercentEncoding( retPath.toUtf8() );
499     }
500     return retPath;
501 }
502
503 /**
504    Read the content of an archive entry in both the cases:
505    a) single file
506       + foobar
507    b) directory
508       + foobar/
509         + [0].piece
510         + [1].piece
511         + ...
512         + [x].last.piece
513
514    \see XPS specification 10.1.2
515 */
516 static QByteArray readFileOrDirectoryParts(KArchive *archive, const KArchiveEntry *entry, QString *pathOfFile = 0 )
517 {
518     QByteArray data;
519     if ( S_ISDIR(entry->mode) ) {
520         const QList<KArchiveEntry> entries = archive->list( entry->pathname );
521         Q_FOREACH ( const KArchiveEntry &ee, entries ) {
522             if ( !S_ISREG(ee.mode) )
523                 continue;
524
525             data.append( archive->data( ee.pathname ) );
526         }
527     } else {
528         data.append( archive->data( entry->pathname) );
529         if ( pathOfFile ) {
530             *pathOfFile = entryPath( entry );
531         }
532     }
533     return data;
534 }
535
536 /**
537    Load the resource \p fileName from the specified \p archive using the case sensitivity \p cs
538 */
539 static const KArchiveEntry loadEntry( KArchive *archive, const QString &fileName, Qt::CaseSensitivity cs )
540 {
541     // first attempt: loading the entry straight as requested
542     const KArchiveEntry entry = archive->entry( fileName );
543     // in case sensitive mode, or if we actually found something, return what we found
544     if ( cs == Qt::CaseSensitive || !entry.isNull() ) {
545         return entry;
546     }
547
548     QString path;
549     QString entryName;
550     const int index = fileName.lastIndexOf( QChar::fromLatin1( '/' ) );
551     QString ret;
552     if ( index > 0 ) {
553         path = fileName.left( index );
554         entryName = fileName.mid( index + 1 );
555     } else {
556         path = '/';
557         entryName = fileName;
558     }
559     const KArchiveEntry newEntry = archive->entry( path );
560     if ( S_ISDIR(newEntry.mode) ) {
561         Q_FOREACH ( const KArchiveEntry &relEntry, archive->list( newEntry.pathname ) ) {
562             if ( QFile::decodeName(relEntry.pathname).compare( entryName, Qt::CaseInsensitive ) == 0 ) {
563                 return relEntry;
564             }
565         }
566     }
567     return KArchiveEntry();
568 }
569
570 static QColor interpolatedColor( const QColor &c1, const QColor &c2 )
571 {
572     QColor res;
573     res.setAlpha( ( c1.alpha() + c2.alpha() ) / 2 );
574     res.setRed( ( c1.red() + c2.red() ) / 2 );
575     res.setGreen( ( c1.green() + c2.green() ) / 2 );
576     res.setBlue( ( c1.blue() + c2.blue() ) / 2 );
577     return res;
578 }
579
580 static bool xpsGradientLessThan( const XpsGradient &g1, const XpsGradient &g2 )
581 {
582     return qFuzzyCompare( g1.offset, g2.offset )
583         ? g1.color.name() < g2.color.name()
584         : g1.offset < g2.offset;
585 }
586
587 static int xpsGradientWithOffset( const QList<XpsGradient> &gradients, double offset )
588 {
589     int i = 0;
590     Q_FOREACH ( const XpsGradient &grad, gradients ) {
591         if ( grad.offset == offset ) {
592             return i;
593         }
594         ++i;
595     }
596     return -1;
597 }
598
599 /**
600    Preprocess a list of gradients.
601
602    \see XPS specification 11.3.1.1
603 */
604 static void preprocessXpsGradients( QList<XpsGradient> &gradients )
605 {
606     if ( gradients.isEmpty() )
607         return;
608
609     // sort the gradients (case 1.)
610     qStableSort( gradients.begin(), gradients.end(), xpsGradientLessThan );
611
612     // no gradient with stop 0.0 (case 2.)
613     if ( xpsGradientWithOffset( gradients, 0.0 ) == -1 ) {
614         int firstGreaterThanZero = 0;
615         while ( firstGreaterThanZero < gradients.count() && gradients.at( firstGreaterThanZero ).offset < 0.0 )
616             ++firstGreaterThanZero;
617         // case 2.a: no gradients with stop less than 0.0
618         if ( firstGreaterThanZero == 0 ) {
619             gradients.prepend( XpsGradient( 0.0, gradients.first().color ) );
620         }
621         // case 2.b: some gradients with stop more than 0.0
622         else if ( firstGreaterThanZero != gradients.count() ) {
623             QColor col1 = gradients.at( firstGreaterThanZero - 1 ).color;
624             QColor col2 = gradients.at( firstGreaterThanZero ).color;
625             for ( int i = 0; i < firstGreaterThanZero; ++i ) {
626                 gradients.removeFirst();
627             }
628             gradients.prepend( XpsGradient( 0.0, interpolatedColor( col1, col2 ) ) );
629         }
630         // case 2.c: no gradients with stop more than 0.0
631         else {
632             XpsGradient newGrad( 0.0, gradients.last().color );
633             gradients.clear();
634             gradients.append( newGrad );
635         }
636     }
637
638     if ( gradients.isEmpty() )
639         return;
640
641     // no gradient with stop 1.0 (case 3.)
642     if ( xpsGradientWithOffset( gradients, 1.0 ) == -1 ) {
643         int firstLessThanOne = gradients.count() - 1;
644         while ( firstLessThanOne >= 0 && gradients.at( firstLessThanOne ).offset > 1.0 )
645             --firstLessThanOne;
646         // case 2.a: no gradients with stop greater than 1.0
647         if ( firstLessThanOne == gradients.count() - 1 ) {
648             gradients.append( XpsGradient( 1.0, gradients.last().color ) );
649         }
650         // case 2.b: some gradients with stop more than 1.0
651         else if ( firstLessThanOne != -1 ) {
652             QColor col1 = gradients.at( firstLessThanOne ).color;
653             QColor col2 = gradients.at( firstLessThanOne + 1 ).color;
654             for ( int i = firstLessThanOne + 1; i < gradients.count(); ++i ) {
655                 gradients.removeLast();
656             }
657             gradients.append( XpsGradient( 1.0, interpolatedColor( col1, col2 ) ) );
658         }
659         // case 2.c: no gradients with stop less than 1.0
660         else {
661             XpsGradient newGrad( 1.0, gradients.first().color );
662             gradients.clear();
663             gradients.append( newGrad );
664         }
665     }
666 }
667
668 static void addXpsGradientsToQGradient( const QList<XpsGradient> &gradients, QGradient *qgrad )
669 {
670     Q_FOREACH ( const XpsGradient &grad, gradients ) {
671         qgrad->setColorAt( grad.offset, grad.color );
672     }
673 }
674
675 static void applySpreadStyleToQGradient( const QString &style, QGradient *qgrad )
676 {
677     if ( style.isEmpty() )
678         return;
679
680     if ( style == QLatin1String( "Pad" ) ) {
681         qgrad->setSpread( QGradient::PadSpread );
682     } else if ( style == QLatin1String( "Reflect" ) ) {
683         qgrad->setSpread( QGradient::ReflectSpread );
684     } else if ( style == QLatin1String( "Repeat" ) ) {
685         qgrad->setSpread( QGradient::RepeatSpread );
686     }
687 }
688
689 /**
690     Read an UnicodeString
691     \param string the raw value of UnicodeString
692
693     \see XPS specification 5.1.4
694 */
695 static QString unicodeString( const QString &raw )
696 {
697     QString ret;
698     if ( raw.startsWith( QLatin1String( "{}" ) ) ) {
699         ret = raw.mid( 2 );
700     } else {
701         ret = raw;
702     }
703     return ret;
704 }
705
706
707 XpsHandler::XpsHandler(XpsPage *page): m_page(page)
708 {
709     m_painter = NULL;
710 }
711
712 XpsHandler::~XpsHandler()
713 {
714 }
715
716 bool XpsHandler::startDocument()
717 {
718     kDebug(XpsDebug) << "start document" << m_page->m_fileName ;
719
720     XpsRenderNode node;
721     node.name = "document";
722     m_nodes.push(node);
723
724     return true;
725 }
726
727 bool XpsHandler::startElement( const QString &nameSpace,
728                                const QString &localName,
729                                const QString &qname,
730                                const QXmlAttributes & atts )
731 {
732     Q_UNUSED( nameSpace )
733     Q_UNUSED( qname )
734
735     XpsRenderNode node;
736     node.name = localName;
737     node.attributes = atts;
738     processStartElement( node );
739     m_nodes.push(node);
740
741
742     return true;
743 }
744
745 bool XpsHandler::endElement( const QString &nameSpace,
746                              const QString &localName,
747                              const QString &qname)
748 {
749     Q_UNUSED( nameSpace )
750     Q_UNUSED( qname )
751
752
753     XpsRenderNode node = m_nodes.pop();
754     if (node.name != localName) {
755         kDebug(XpsDebug) << "Name doesn't match";
756     }
757     processEndElement( node );
758     node.children.clear();
759     m_nodes.top().children.append(node);
760
761     return true;
762 }
763
764 void XpsHandler::processGlyph( XpsRenderNode &node )
765 {
766     //TODO Currently ignored attributes: CaretStops, DeviceFontName, IsSideways, OpacityMask, Name, FixedPage.NavigateURI, xml:lang, x:key
767     //TODO Indices is only partially implemented
768     //TODO Currently ignored child elements: Clip, OpacityMask
769     //Handled separately: RenderTransform
770
771     QString att;
772
773     m_painter->save();
774
775     // Get font (doesn't work well because qt doesn't allow to load font from file)
776     // This works despite the fact that font size isn't specified in points as required by qt. It's because I set point size to be equal to drawing unit.
777     float fontSize = node.attributes.value("FontRenderingEmSize").toFloat();
778     // kDebug(XpsDebug) << "Font Rendering EmSize:" << fontSize;
779     // a value of 0.0 means the text is not visible (see XPS specs, chapter 12, "Glyphs")
780     if ( fontSize < 0.1 ) {
781         m_painter->restore();
782         return;
783     }
784     QFont font = m_page->m_file->getFontByName( node.attributes.value("FontUri"), fontSize );
785     att = node.attributes.value( "StyleSimulations" );
786     if  ( !att.isEmpty() ) {
787         if ( att == QLatin1String( "ItalicSimulation" ) ) {
788             font.setItalic( true );
789         } else if ( att == QLatin1String( "BoldSimulation" ) ) {
790             font.setBold( true );
791         } else if ( att == QLatin1String( "BoldItalicSimulation" ) ) {
792             font.setItalic( true );
793             font.setBold( true );
794         }
795     }
796     m_painter->setFont(font);
797
798     //Origin
799     QPointF origin( node.attributes.value("OriginX").toDouble(), node.attributes.value("OriginY").toDouble() );
800
801     //Fill
802     QBrush brush;
803     att = node.attributes.value("Fill");
804     if (att.isEmpty()) {
805         QVariant data = node.getChildData( "Glyphs.Fill" );
806         if (data.canConvert<QBrush>()) {
807             brush = data.value<QBrush>();
808         } else {
809             // no "Fill" attribute and no "Glyphs.Fill" child, so show nothing
810             // (see XPS specs, 5.10)
811             m_painter->restore();
812             return;
813         }
814     } else {
815         brush = parseRscRefColorForBrush( att );
816         if ( brush.style() > Qt::NoBrush && brush.style() < Qt::LinearGradientPattern
817              && brush.color().alpha() == 0 ) {
818             m_painter->restore();
819             return;
820         }
821     }
822     m_painter->setBrush( brush );
823     m_painter->setPen( QPen( brush, 0 ) );
824
825     // Opacity
826     att = node.attributes.value("Opacity");
827     if (! att.isEmpty()) {
828         bool ok = true;
829         double value = att.toDouble( &ok );
830         if ( ok && value >= 0.1 ) {
831             m_painter->setOpacity( value );
832         } else {
833             m_painter->restore();
834             return;
835         }
836     }
837
838     //RenderTransform
839     att = node.attributes.value("RenderTransform");
840     if (!att.isEmpty()) {
841         m_painter->setWorldTransform( parseRscRefMatrix( att ), true);
842     }
843
844     // Clip
845     att = node.attributes.value( "Clip" );
846     if ( !att.isEmpty() ) {
847         QPainterPath clipPath = parseRscRefPath( att );
848         if ( !clipPath.isEmpty() ) {
849             m_painter->setClipPath( clipPath );
850         }
851     }
852
853     // BiDiLevel - default Left-to-Right
854     m_painter->setLayoutDirection( Qt::LeftToRight );
855     att = node.attributes.value( "BiDiLevel" );
856     if ( !att.isEmpty() ) {
857         if ( (att.toInt() % 2) == 1 ) {
858             // odd BiDiLevel, so Right-to-Left
859             m_painter->setLayoutDirection( Qt::RightToLeft );
860         }
861     }
862
863     // Indices - partial handling only
864     att = node.attributes.value( "Indices" );
865     QList<qreal> advanceWidths;
866     if ( ! att.isEmpty() ) {
867         QStringList indicesElements = att.split( ';' );
868         for( int i = 0; i < indicesElements.size(); ++i ) {
869             if ( indicesElements.at(i).contains( "," ) ) {
870                 QStringList parts = indicesElements.at(i).split( ',' );
871                 if (parts.size() == 2 ) {
872                     // regular advance case, no offsets
873                     advanceWidths.append( parts.at(1).toDouble() * fontSize / 100.0 );
874                 } else if (parts.size() == 3 ) {
875                     // regular advance case, with uOffset
876                     qreal AdvanceWidth = parts.at(1).toDouble() * fontSize / 100.0 ;
877                     qreal uOffset = parts.at(2).toDouble() / 100.0;
878                     advanceWidths.append( AdvanceWidth + uOffset );
879                 } else {
880                     // has vertical offset, but don't know how to handle that yet
881                     kDebug(XpsDebug) << "Unhandled Indices element: " << indicesElements.at(i);
882                     advanceWidths.append( -1.0 );
883                 }
884             } else {
885                 // no special advance case
886                 advanceWidths.append( -1.0 );
887             }
888         }
889     }
890
891     // UnicodeString
892     QString stringToDraw( unicodeString( node.attributes.value( "UnicodeString" ) ) );
893     QPointF originAdvance(0, 0);
894     QFontMetrics metrics = m_painter->fontMetrics();
895     for ( int i = 0; i < stringToDraw.size(); ++i ) {
896         QChar thisChar = stringToDraw.at( i );
897         m_painter->drawText( origin + originAdvance, QString( thisChar ) );
898         const qreal advanceWidth = advanceWidths.value( i, qreal(-1.0) );
899         if ( advanceWidth > 0.0 ) {
900             originAdvance.rx() += advanceWidth;
901         } else {
902             originAdvance.rx() += metrics.width( thisChar );
903         }
904     }
905     // kDebug(XpsDebug) << "Glyphs: " << atts.value("Fill") << ", " << atts.value("FontUri");
906     // kDebug(XpsDebug) << "    Origin: " << atts.value("OriginX") << "," << atts.value("OriginY");
907     // kDebug(XpsDebug) << "    Unicode: " << atts.value("UnicodeString");
908
909     m_painter->restore();
910 }
911
912 void XpsHandler::processFill( XpsRenderNode &node )
913 {
914     //TODO Ignored child elements: VirtualBrush
915
916     if (node.children.size() != 1) {
917         kDebug(XpsDebug) << "Fill element should have exactly one child";
918     } else {
919         node.data = node.children[0].data;
920     }
921 }
922
923 void XpsHandler::processStroke( XpsRenderNode &node )
924 {
925     //TODO Ignored child elements: VirtualBrush
926
927     if (node.children.size() != 1) {
928         kDebug(XpsDebug) << "Stroke element should have exactly one child";
929     } else {
930         node.data = node.children[0].data;
931     }
932 }
933
934 void XpsHandler::processImageBrush( XpsRenderNode &node )
935 {
936     //TODO Ignored attributes: Opacity, x:key, TileMode, ViewBoxUnits, ViewPortUnits
937     //TODO Check whether transformation works for non standard situations (viewbox different that whole image, Transform different that simple move & scale, Viewport different than [0, 0, 1, 1]
938
939     QString att;
940     QBrush brush;
941
942     QRectF viewport = stringToRectF( node.attributes.value( "Viewport" ) );
943     QRectF viewbox = stringToRectF( node.attributes.value( "Viewbox" ) );
944     QImage image = m_page->loadImageFromFile( node.attributes.value( "ImageSource" ) );
945
946     // Matrix which can transform [0, 0, 1, 1] rectangle to given viewbox
947     QTransform viewboxMatrix = QTransform( viewbox.width() * image.physicalDpiX() / 96, 0, 0, viewbox.height() * image.physicalDpiY() / 96, viewbox.x(), viewbox.y() );
948
949     // Matrix which can transform [0, 0, 1, 1] rectangle to given viewport
950     //TODO Take ViewPort into account
951     QTransform viewportMatrix;
952     att = node.attributes.value( "Transform" );
953     if ( att.isEmpty() ) {
954         QVariant data = node.getChildData( "ImageBrush.Transform" );
955         if (data.canConvert<QTransform>()) {
956             viewportMatrix = data.value<QTransform>();
957         } else {
958             viewportMatrix = QTransform();
959         }
960     } else {
961         viewportMatrix = parseRscRefMatrix( att );
962     }
963     viewportMatrix = viewportMatrix * QTransform( viewport.width(), 0, 0, viewport.height(), viewport.x(), viewport.y() );
964
965
966     brush = QBrush( image );
967     brush.setTransform( viewboxMatrix.inverted() * viewportMatrix );
968
969     node.data = qVariantFromValue( brush );
970 }
971
972 void XpsHandler::processPath( XpsRenderNode &node )
973 {
974     //TODO Ignored attributes: Clip, OpacityMask, StrokeEndLineCap, StorkeStartLineCap, Name, FixedPage.NavigateURI, xml:lang, x:key, AutomationProperties.Name, AutomationProperties.HelpText, SnapsToDevicePixels
975     //TODO Ignored child elements: RenderTransform, Clip, OpacityMask
976     // Handled separately: RenderTransform
977     m_painter->save();
978
979     QString att;
980     QVariant data;
981
982     // Get path
983     XpsPathGeometry * pathdata = node.getChildData( "Path.Data" ).value< XpsPathGeometry * >();
984     att = node.attributes.value( "Data" );
985     if (! att.isEmpty() ) {
986         QPainterPath path = parseRscRefPath( att );
987         delete pathdata;
988         pathdata = new XpsPathGeometry();
989         pathdata->paths.append( new XpsPathFigure( path, true ) );
990     }
991     if ( !pathdata ) {
992         // nothing to draw
993         m_painter->restore();
994         return;
995     }
996
997     // Set Fill
998     att = node.attributes.value( "Fill" );
999     QBrush brush;
1000     if (! att.isEmpty() ) {
1001         brush = parseRscRefColorForBrush( att );
1002     } else {
1003         data = node.getChildData( "Path.Fill" );
1004         if (data.canConvert<QBrush>()) {
1005             brush = data.value<QBrush>();
1006         }
1007     }
1008     m_painter->setBrush( brush );
1009
1010     // Stroke (pen)
1011     att = node.attributes.value( "Stroke" );
1012     QPen pen( Qt::transparent );
1013     if  (! att.isEmpty() ) {
1014         pen = parseRscRefColorForPen( att );
1015     } else {
1016         data = node.getChildData( "Path.Stroke" );
1017         if (data.canConvert<QBrush>()) {
1018             pen.setBrush( data.value<QBrush>() );
1019         }
1020     }
1021     att = node.attributes.value( "StrokeThickness" );
1022     if  (! att.isEmpty() ) {
1023         bool ok = false;
1024         int thickness = att.toInt( &ok );
1025         if (ok)
1026             pen.setWidth( thickness );
1027     }
1028     att = node.attributes.value( "StrokeDashArray" );
1029     if  ( !att.isEmpty() ) {
1030         const QStringList pieces = att.split( QLatin1Char( ' ' ), QString::SkipEmptyParts );
1031         QVector<qreal> dashPattern( pieces.count() );
1032         bool ok = false;
1033         for ( int i = 0; i < pieces.count(); ++i ) {
1034             qreal value = pieces.at( i ).toInt( &ok );
1035             if ( ok ) {
1036                 dashPattern[i] = value;
1037             } else {
1038                 break;
1039             }
1040         }
1041         if ( ok ) {
1042             pen.setDashPattern( dashPattern );
1043         }
1044     }
1045     att = node.attributes.value( "StrokeDashOffset" );
1046     if  ( !att.isEmpty() ) {
1047         bool ok = false;
1048         int offset = att.toInt( &ok );
1049         if ( ok )
1050             pen.setDashOffset( offset );
1051     }
1052     att = node.attributes.value( "StrokeDashCap" );
1053     if  ( !att.isEmpty() ) {
1054         Qt::PenCapStyle cap = Qt::FlatCap;
1055         if ( att == QLatin1String( "Flat" ) ) {
1056             cap = Qt::FlatCap;
1057         } else if ( att == QLatin1String( "Round" ) ) {
1058             cap = Qt::RoundCap;
1059         } else if ( att == QLatin1String( "Square" ) ) {
1060             cap = Qt::SquareCap;
1061         }
1062         // ### missing "Triangle"
1063         pen.setCapStyle( cap );
1064     }
1065     att = node.attributes.value( "StrokeLineJoin" );
1066     if  ( !att.isEmpty() ) {
1067         Qt::PenJoinStyle joinStyle = Qt::MiterJoin;
1068         if ( att == QLatin1String( "Miter" ) ) {
1069             joinStyle = Qt::MiterJoin;
1070         } else if ( att == QLatin1String( "Bevel" ) ) {
1071             joinStyle = Qt::BevelJoin;
1072         } else if ( att == QLatin1String( "Round" ) ) {
1073             joinStyle = Qt::RoundJoin;
1074         }
1075         pen.setJoinStyle( joinStyle );
1076     }
1077     att = node.attributes.value( "StrokeMiterLimit" );
1078     if  ( !att.isEmpty() ) {
1079         bool ok = false;
1080         double limit = att.toDouble( &ok );
1081         if ( ok ) {
1082             // we have to divide it by two, as XPS consider half of the stroke width,
1083             // while Qt the whole of it
1084             pen.setMiterLimit( limit / 2 );
1085         }
1086     }
1087     m_painter->setPen( pen );
1088
1089     // Opacity
1090     att = node.attributes.value("Opacity");
1091     if (! att.isEmpty()) {
1092         m_painter->setOpacity(att.toDouble());
1093     }
1094
1095     // RenderTransform
1096     att = node.attributes.value( "RenderTransform" );
1097     if (! att.isEmpty() ) {
1098         m_painter->setWorldTransform( parseRscRefMatrix( att ), true );
1099     }
1100     if ( !pathdata->transform.isIdentity() ) {
1101         m_painter->setWorldTransform( pathdata->transform, true );
1102     }
1103
1104     Q_FOREACH ( XpsPathFigure *figure, pathdata->paths ) {
1105         m_painter->setBrush( figure->isFilled ? brush : QBrush() );
1106         m_painter->drawPath( figure->path );
1107     }
1108
1109     delete pathdata;
1110
1111     m_painter->restore();
1112 }
1113
1114 void XpsHandler::processPathData( XpsRenderNode &node )
1115 {
1116     if (node.children.size() != 1) {
1117         kDebug(XpsDebug) << "Path.Data element should have exactly one child";
1118     } else {
1119         node.data = node.children[0].data;
1120     }
1121 }
1122
1123 void XpsHandler::processPathGeometry( XpsRenderNode &node )
1124 {
1125     XpsPathGeometry * geom = new XpsPathGeometry();
1126
1127     Q_FOREACH ( const XpsRenderNode &child, node.children ) {
1128         if ( child.data.canConvert<XpsPathFigure *>() ) {
1129             XpsPathFigure *figure = child.data.value<XpsPathFigure *>();
1130             geom->paths.append( figure );
1131         }
1132     }
1133
1134     QString att;
1135
1136     att = node.attributes.value( "Figures" );
1137     if ( !att.isEmpty() ) {
1138         QPainterPath path = parseRscRefPath( att );
1139         qDeleteAll( geom->paths );
1140         geom->paths.clear();
1141         geom->paths.append( new XpsPathFigure( path, true ) );
1142     }
1143
1144     att = node.attributes.value( "FillRule" );
1145     if ( !att.isEmpty() ) {
1146         geom->fillRule = fillRuleFromString( att );
1147     }
1148
1149     // Transform
1150     att = node.attributes.value( "Transform" );
1151     if ( !att.isEmpty() ) {
1152         geom->transform = parseRscRefMatrix( att );
1153     }
1154
1155     if ( !geom->paths.isEmpty() ) {
1156         node.data = qVariantFromValue( geom );
1157     } else {
1158         delete geom;
1159     }
1160 }
1161
1162 void XpsHandler::processPathFigure( XpsRenderNode &node )
1163 {
1164     //TODO Ignored child elements: ArcSegment
1165
1166     QString att;
1167     QPainterPath path;
1168
1169     att = node.attributes.value( "StartPoint" );
1170     if ( !att.isEmpty() ) {
1171         QPointF point = getPointFromString( att );
1172         path.moveTo( point );
1173     } else {
1174         return;
1175     }
1176
1177     Q_FOREACH ( const XpsRenderNode &child, node.children ) {
1178         bool isStroked = true;
1179         att = node.attributes.value( "IsStroked" );
1180         if ( !att.isEmpty() ) {
1181             isStroked = att == QLatin1String( "true" );
1182         }
1183         if ( !isStroked ) {
1184             continue;
1185         }
1186
1187         // PolyLineSegment
1188         if ( child.name == QLatin1String( "PolyLineSegment" ) ) {
1189             att = child.attributes.value( "Points" );
1190             if ( !att.isEmpty() ) {
1191                 const QStringList points = att.split( QLatin1Char( ' ' ), QString::SkipEmptyParts );
1192                 Q_FOREACH ( const QString &p, points ) {
1193                     QPointF point = getPointFromString( p );
1194                     path.lineTo( point );
1195                 }
1196             }
1197         }
1198         // PolyBezierSegment
1199         else if ( child.name == QLatin1String( "PolyBezierSegment" ) ) {
1200             att = child.attributes.value( "Points" );
1201             if ( !att.isEmpty() ) {
1202                 const QStringList points = att.split( QLatin1Char( ' ' ), QString::SkipEmptyParts );
1203                 if ( points.count() % 3 == 0 ) {
1204                     for ( int i = 0; i < points.count(); ) {
1205                         QPointF firstControl = getPointFromString( points.at( i++ ) );
1206                         QPointF secondControl = getPointFromString( points.at( i++ ) );
1207                         QPointF endPoint = getPointFromString( points.at( i++ ) );
1208                         path.cubicTo(firstControl, secondControl, endPoint);
1209                     }
1210                 }
1211             }
1212         }
1213         // PolyQuadraticBezierSegment
1214         else if ( child.name == QLatin1String( "PolyQuadraticBezierSegment" ) ) {
1215             att = child.attributes.value( "Points" );
1216             if ( !att.isEmpty() ) {
1217                 const QStringList points = att.split( QLatin1Char( ' ' ), QString::SkipEmptyParts );
1218                 if ( points.count() % 2 == 0 ) {
1219                     for ( int i = 0; i < points.count(); ) {
1220                         QPointF point1 = getPointFromString( points.at( i++ ) );
1221                         QPointF point2 = getPointFromString( points.at( i++ ) );
1222                         path.quadTo( point1, point2 );
1223                     }
1224                 }
1225             }
1226         }
1227     }
1228
1229     bool closePath = false;
1230     att = node.attributes.value( "IsClosed" );
1231     if ( !att.isEmpty() ) {
1232         closePath = att == QLatin1String( "true" );
1233     }
1234     if ( closePath ) {
1235         path.closeSubpath();
1236     }
1237
1238     bool isFilled = true;
1239     att = node.attributes.value( "IsFilled" );
1240     if ( !att.isEmpty() ) {
1241         isFilled = att == QLatin1String( "true" );
1242     }
1243
1244     if ( !path.isEmpty() ) {
1245         node.data = qVariantFromValue( new XpsPathFigure( path, isFilled ) );
1246     }
1247 }
1248
1249 void XpsHandler::processStartElement( XpsRenderNode &node )
1250 {
1251     if (node.name == "Canvas") {
1252         m_painter->save();
1253         QString att = node.attributes.value( "RenderTransform" );
1254         if ( !att.isEmpty() ) {
1255             m_painter->setWorldTransform( parseRscRefMatrix( att ), true );
1256         }
1257         att = node.attributes.value( "Opacity" );
1258         if ( !att.isEmpty() ) {
1259             double value = att.toDouble();
1260             if ( value > 0.0 && value <= 1.0 ) {
1261                 m_painter->setOpacity( m_painter->opacity() * value );
1262             } else {
1263                 // setting manually to 0 is necessary to "disable"
1264                 // all the stuff inside
1265                 m_painter->setOpacity( 0.0 );
1266             }
1267         }
1268     }
1269 }
1270
1271 void XpsHandler::processEndElement( XpsRenderNode &node )
1272 {
1273     if (node.name == "Glyphs") {
1274         processGlyph( node );
1275     } else if (node.name == "Path") {
1276         processPath( node );
1277     } else if (node.name == "MatrixTransform") {
1278         //TODO Ignoring x:key
1279         node.data = qVariantFromValue( QTransform( attsToMatrix( node.attributes.value( "Matrix" ) ) ) );
1280     } else if ((node.name == "Canvas.RenderTransform") || (node.name == "Glyphs.RenderTransform") || (node.name == "Path.RenderTransform"))  {
1281         QVariant data = node.getRequiredChildData( "MatrixTransform" );
1282         if (data.canConvert<QTransform>()) {
1283             m_painter->setWorldTransform( data.value<QTransform>(), true );
1284         }
1285     } else if (node.name == "Canvas") {
1286         m_painter->restore();
1287     } else if ((node.name == "Path.Fill") || (node.name == "Glyphs.Fill")) {
1288         processFill( node );
1289     } else if (node.name == "Path.Stroke") {
1290         processStroke( node );
1291     } else if (node.name == "SolidColorBrush") {
1292         //TODO Ignoring opacity, x:key
1293         node.data = qVariantFromValue( QBrush( QColor( hexToRgba( node.attributes.value( "Color" ).toLatin1() ) ) ) );
1294     } else if (node.name == "ImageBrush") {
1295         processImageBrush( node );
1296     } else if (node.name == "ImageBrush.Transform") {
1297         node.data = node.getRequiredChildData( "MatrixTransform" );
1298     } else if (node.name == "LinearGradientBrush") {
1299         XpsRenderNode * gradients = node.findChild( "LinearGradientBrush.GradientStops" );
1300         if ( gradients && gradients->data.canConvert< QGradient * >() ) {
1301             QPointF start = getPointFromString( node.attributes.value( "StartPoint" ) );
1302             QPointF end = getPointFromString( node.attributes.value( "EndPoint" ) );
1303             QLinearGradient * qgrad = static_cast< QLinearGradient * >( gradients->data.value< QGradient * >() );
1304             qgrad->setStart( start );
1305             qgrad->setFinalStop( end );
1306             applySpreadStyleToQGradient( node.attributes.value( "SpreadMethod" ), qgrad );
1307             node.data = qVariantFromValue( QBrush( *qgrad ) );
1308             delete qgrad;
1309         }
1310     } else if (node.name == "RadialGradientBrush") {
1311         XpsRenderNode * gradients = node.findChild( "RadialGradientBrush.GradientStops" );
1312         if ( gradients && gradients->data.canConvert< QGradient * >() ) {
1313             QPointF center = getPointFromString( node.attributes.value( "Center" ) );
1314             QPointF origin = getPointFromString( node.attributes.value( "GradientOrigin" ) );
1315             double radiusX = node.attributes.value( "RadiusX" ).toDouble();
1316             double radiusY = node.attributes.value( "RadiusY" ).toDouble();
1317             QRadialGradient * qgrad = static_cast< QRadialGradient * >( gradients->data.value< QGradient * >() );
1318             qgrad->setCenter( center );
1319             qgrad->setFocalPoint( origin );
1320             // TODO what in case of different radii?
1321             qgrad->setRadius( qMin( radiusX, radiusY ) );
1322             applySpreadStyleToQGradient( node.attributes.value( "SpreadMethod" ), qgrad );
1323             node.data = qVariantFromValue( QBrush( *qgrad ) );
1324             delete qgrad;
1325         }
1326     } else if (node.name == "LinearGradientBrush.GradientStops") {
1327         QList<XpsGradient> gradients;
1328         Q_FOREACH ( const XpsRenderNode &child, node.children ) {
1329             double offset = child.attributes.value( "Offset" ).toDouble();
1330             QColor color = hexToRgba( child.attributes.value( "Color" ).toLatin1() );
1331             gradients.append( XpsGradient( offset, color ) );
1332         }
1333         preprocessXpsGradients( gradients );
1334         if ( !gradients.isEmpty() ) {
1335             QLinearGradient * qgrad = new QLinearGradient();
1336             addXpsGradientsToQGradient( gradients, qgrad );
1337             node.data = qVariantFromValue< QGradient * >( qgrad );
1338         }
1339     } else if (node.name == "RadialGradientBrush.GradientStops") {
1340         QList<XpsGradient> gradients;
1341         Q_FOREACH ( const XpsRenderNode &child, node.children ) {
1342             double offset = child.attributes.value( "Offset" ).toDouble();
1343             QColor color = hexToRgba( child.attributes.value( "Color" ).toLatin1() );
1344             gradients.append( XpsGradient( offset, color ) );
1345         }
1346         preprocessXpsGradients( gradients );
1347         if ( !gradients.isEmpty() ) {
1348             QRadialGradient * qgrad = new QRadialGradient();
1349             addXpsGradientsToQGradient( gradients, qgrad );
1350             node.data = qVariantFromValue< QGradient * >( qgrad );
1351         }
1352     } else if (node.name == "PathFigure") {
1353         processPathFigure( node );
1354     } else if (node.name == "PathGeometry") {
1355         processPathGeometry( node );
1356     } else if (node.name == "Path.Data") {
1357         processPathData( node );
1358     } else {
1359         //kDebug(XpsDebug) << "Unknown element: " << node->name;
1360     }
1361 }
1362
1363 XpsPage::XpsPage(XpsFile *file, const QString &fileName): m_file( file ),
1364     m_fileName( fileName ), m_pageIsRendered(false)
1365 {
1366     m_pageImage = NULL;
1367
1368     // kDebug(XpsDebug) << "page file name: " << fileName;
1369
1370     const KArchiveEntry pageFile = m_file->xpsArchive()->entry( fileName );
1371
1372     QXmlStreamReader xml;
1373     xml.addData( readFileOrDirectoryParts( m_file->xpsArchive(), &pageFile ) );
1374     while ( !xml.atEnd() )
1375     {
1376         xml.readNext();
1377         if ( xml.isStartElement() && ( xml.name() == "FixedPage" ) )
1378         {
1379             QXmlStreamAttributes attributes = xml.attributes();
1380             m_pageSize.setWidth( attributes.value( "Width" ).toString().toDouble() );
1381             m_pageSize.setHeight( attributes.value( "Height" ).toString().toDouble() );
1382             break;
1383         }
1384     }
1385     if ( xml.error() )
1386     {
1387         kDebug(XpsDebug) << "Could not parse XPS page:" << xml.errorString();
1388     }
1389 }
1390
1391 XpsPage::~XpsPage()
1392 {
1393     delete m_pageImage;
1394 }
1395
1396 bool XpsPage::renderToImage( QImage *p )
1397 {
1398
1399     if ((m_pageImage == NULL) || (m_pageImage->size() != p->size())) {
1400         delete m_pageImage;
1401         m_pageImage = new QImage( p->size(), QImage::Format_ARGB32 );
1402         // Set one point = one drawing unit. Useful for fonts, because xps specifies font size using drawing units, not points as usual
1403         m_pageImage->setDotsPerMeterX( 2835 );
1404         m_pageImage->setDotsPerMeterY( 2835 );
1405
1406         m_pageIsRendered = false;
1407     }
1408     if (! m_pageIsRendered) {
1409         m_pageImage->fill( qRgba( 255, 255, 255, 255 ) );
1410         QPainter painter( m_pageImage );
1411         renderToPainter( &painter );
1412         m_pageIsRendered = true;
1413     }
1414
1415     *p = *m_pageImage;
1416
1417     return true;
1418 }
1419
1420 bool XpsPage::renderToPainter( QPainter *painter )
1421 {
1422     XpsHandler handler( this );
1423     handler.m_painter = painter;
1424     handler.m_painter->setWorldTransform(QTransform().scale((qreal)painter->device()->width() / size().width(), (qreal)painter->device()->height() / size().height()));
1425     QXmlSimpleReader parser;
1426     parser.setContentHandler( &handler );
1427     parser.setErrorHandler( &handler );
1428     const KArchiveEntry pageFile = m_file->xpsArchive()->entry( m_fileName );
1429     QByteArray data = readFileOrDirectoryParts( m_file->xpsArchive(), &pageFile );
1430     QBuffer buffer( &data );
1431     QXmlInputSource source( &buffer );
1432     bool ok = parser.parse( source );
1433     kDebug(XpsDebug) << "Parse result: " << ok;
1434
1435     return true;
1436 }
1437
1438 QSizeF XpsPage::size() const
1439 {
1440     return m_pageSize;
1441 }
1442
1443 QFont XpsFile::getFontByName( const QString &fileName, float size )
1444 {
1445     // kDebug(XpsDebug) << "trying to get font: " << fileName << ", size: " << size;
1446
1447     int index = m_fontCache.value(fileName, -1);
1448     if (index == -1)
1449     {
1450         index = loadFontByName(fileName);
1451         m_fontCache[fileName] = index;
1452     }
1453     if ( index == -1 ) {
1454         kWarning(XpsDebug) << "Requesting uknown font:" << fileName;
1455         return QFont();
1456     }
1457     return QFont(m_fonts.at(index));
1458 }
1459
1460 int XpsFile::loadFontByName( const QString &fileName )
1461 {
1462     // kDebug(XpsDebug) << "font file name: " << fileName;
1463
1464     const KArchiveEntry fontFile = loadEntry( m_xpsArchive, entryFilePath( fileName ), Qt::CaseInsensitive );
1465     if ( fontFile.isNull() ) {
1466         return -1;
1467     }
1468
1469     QByteArray fontData = readFileOrDirectoryParts( m_xpsArchive, &fontFile ); // once per file, according to the docs
1470
1471     int result = -1;
1472     KTemporaryFile tempfile;
1473     tempfile.setSuffix(QFileInfo(fileName).suffix());
1474     if (!tempfile.open()) {
1475         return result;
1476     }
1477
1478     if (tempfile.write(fontData) != fontData.size()) {
1479         return result;
1480     }
1481
1482     tempfile.setAutoRemove(false);
1483     m_fonts.append(tempfile.fileName());
1484     result = (m_fonts.size() - 1);
1485
1486     // kDebug(XpsDebug) << "Saved font: " << tempfile.fileName();
1487
1488     return result; // a font ID
1489 }
1490
1491 KArchive * XpsFile::xpsArchive() {
1492     return m_xpsArchive;
1493 }
1494
1495 QImage XpsPage::loadImageFromFile( const QString &fileName )
1496 {
1497     // kDebug(XpsDebug) << "image file name: " << fileName;
1498
1499     if ( fileName.at( 0 ) == QLatin1Char( '{' ) ) {
1500         // for example: '{ColorConvertedBitmap /Resources/bla.wdp /Resources/foobar.icc}'
1501         // TODO: properly read a ColorConvertedBitmap
1502         return QImage();
1503     }
1504
1505     QString absoluteFileName = absolutePath( entryPath( m_fileName ), fileName );
1506     const KArchiveEntry imageFile = loadEntry( m_file->xpsArchive(), absoluteFileName, Qt::CaseInsensitive );
1507     if ( imageFile.isNull() ) {
1508         // image not found
1509         return QImage();
1510     }
1511
1512     /* WORKAROUND:
1513         XPS standard requires to use 96dpi for images which doesn't have dpi specified (in file). When Qt loads such an image,
1514         it sets its dpi to qt_defaultDpi and doesn't allow to find out that it happend.
1515
1516         To workaround this I used this procedure: load image, set its dpi to 96, load image again. When dpi isn't set in file,
1517         dpi set by me stays unchanged.
1518
1519         Trolltech task ID: 159527.
1520
1521     */
1522
1523     QImage image;
1524     QByteArray data = m_file->xpsArchive()->data( imageFile.pathname );
1525
1526     QBuffer buffer(&data);
1527     buffer.open(QBuffer::ReadOnly);
1528
1529     QImageReader reader(&buffer);
1530     image = reader.read();
1531
1532     image.setDotsPerMeterX(qRound(96 / 0.0254));
1533     image.setDotsPerMeterY(qRound(96 / 0.0254));
1534   
1535     buffer.seek(0);
1536     reader.setDevice(&buffer);
1537     reader.read(&image);
1538
1539     return image;
1540 }
1541
1542 Okular::TextPage* XpsPage::textPage()
1543 {
1544     // kDebug(XpsDebug) << "Parsing XpsPage, text extraction";
1545
1546     Okular::TextPage* textPage = new Okular::TextPage();
1547
1548     const KArchiveEntry pageFile = m_file->xpsArchive()->entry( m_fileName );
1549     QXmlStreamReader xml;
1550     xml.addData( readFileOrDirectoryParts( m_file->xpsArchive(), &pageFile ) );
1551
1552     QTransform matrix = QTransform();
1553     QStack<QTransform> matrices;
1554     matrices.push( QTransform() );
1555     bool useMatrix = false;
1556     QXmlStreamAttributes glyphsAtts;
1557
1558     while ( ! xml.atEnd() ) {
1559         xml.readNext();
1560         if ( xml.isStartElement() ) {
1561             if ( xml.name() == "Canvas") {
1562                 matrices.push(matrix);
1563
1564                 QString att = xml.attributes().value( "RenderTransform" ).toString();
1565                 if (!att.isEmpty()) {
1566                     matrix = parseRscRefMatrix( att ) * matrix;
1567                 }
1568             } else if ((xml.name() == "Canvas.RenderTransform") || (xml.name() == "Glyphs.RenderTransform")) {
1569                 useMatrix = true;
1570             } else if (xml.name() == "MatrixTransform") {
1571                 if (useMatrix) {
1572                     matrix = attsToMatrix( xml.attributes().value("Matrix").toString() ) * matrix;
1573                 }
1574             } else if (xml.name() == "Glyphs") {
1575                 matrices.push( matrix );
1576                 glyphsAtts = xml.attributes();
1577             } else if ( (xml.name() == "Path") || (xml.name() == "Path.Fill") || (xml.name() == "SolidColorBrush")
1578                         || (xml.name() == "ImageBrush") ||  (xml.name() == "ImageBrush.Transform")
1579                         || (xml.name() == "Path.OpacityMask") || (xml.name() == "Path.Data")
1580                         || (xml.name() == "PathGeometry") || (xml.name() == "PathFigure")
1581                         || (xml.name() == "PolyLineSegment") ) {
1582                 // those are only graphical - no use in text handling
1583             } else if ( (xml.name() == "FixedPage") || (xml.name() == "FixedPage.Resources") ) {
1584                 // not useful for text extraction
1585             } else {
1586                 kDebug(XpsDebug) << "Unhandled element in Text Extraction start: " << xml.name().toString();
1587             }
1588         } else if (xml.isEndElement() ) {
1589             if (xml.name() == "Canvas") {
1590                 matrix = matrices.pop();
1591             } else if ((xml.name() == "Canvas.RenderTransform") || (xml.name() == "Glyphs.RenderTransform")) {
1592                 useMatrix = false;
1593             } else if (xml.name() == "MatrixTransform") {
1594                 // not clear if we need to do anything here yet.
1595             } else if (xml.name() == "Glyphs") {
1596                 QString att = glyphsAtts.value( "RenderTransform" ).toString();
1597                 if (!att.isEmpty()) {
1598                     matrix = parseRscRefMatrix( att ) * matrix;
1599                 }
1600                 QString text = unicodeString( glyphsAtts.value( "UnicodeString" ).toString() );
1601
1602                 // Get font (doesn't work well because qt doesn't allow to load font from file)
1603                 QFont font = m_file->getFontByName( glyphsAtts.value( "FontUri" ).toString(),
1604                                                     glyphsAtts.value("FontRenderingEmSize").toString().toFloat() * 72 / 96 );
1605                 QFontMetrics metrics = QFontMetrics( font );
1606                 // Origin
1607                 QPointF origin( glyphsAtts.value("OriginX").toString().toDouble(),
1608                                 glyphsAtts.value("OriginY").toString().toDouble() );
1609
1610
1611                 int lastWidth = 0;
1612                 for (int i = 0; i < text.length(); i++) {
1613                     int width = metrics.width( text.mid(0, i + 1));
1614
1615                     Okular::NormalizedRect * rect = new Okular::NormalizedRect( (origin.x() + lastWidth) / m_pageSize.width(),
1616                                                                                 (origin.y() - metrics.height()) / m_pageSize.height(),
1617                                                                                 (origin.x() + width) / m_pageSize.width(),
1618                                                                                 origin.y() / m_pageSize.height() );
1619                     rect->transform( matrix );
1620                     textPage->append( text.mid(i, 1), rect );
1621
1622                     lastWidth = width;
1623                 }
1624
1625                 matrix = matrices.pop();
1626             } else if ( (xml.name() == "Path") || (xml.name() == "Path.Fill") || (xml.name() == "SolidColorBrush")
1627                         || (xml.name() == "ImageBrush") ||  (xml.name() == "ImageBrush.Transform")
1628                         || (xml.name() == "Path.OpacityMask") || (xml.name() == "Path.Data")
1629                         || (xml.name() == "PathGeometry") || (xml.name() == "PathFigure")
1630                         || (xml.name() == "PolyLineSegment") ) {
1631                 // those are only graphical - no use in text handling
1632             } else if ( (xml.name() == "FixedPage") || (xml.name() == "FixedPage.Resources") ) {
1633                 // not useful for text extraction
1634             } else {
1635                 kDebug(XpsDebug) << "Unhandled element in Text Extraction end: " << xml.name().toString();
1636             }
1637         }
1638     }
1639     if ( xml.error() ) {
1640         kDebug(XpsDebug) << "Error parsing XpsPage text: " << xml.errorString();
1641     }
1642     return textPage;
1643 }
1644
1645 void XpsDocument::parseDocumentStructure( const QString &documentStructureFileName )
1646 {
1647     kDebug(XpsDebug) << "document structure file name: " << documentStructureFileName;
1648     m_haveDocumentStructure = false;
1649
1650     QXmlStreamReader xml;
1651     xml.addData( m_file->xpsArchive()->data( documentStructureFileName ) );
1652
1653     while ( !xml.atEnd() )
1654     {
1655         xml.readNext();
1656
1657         if ( xml.isStartElement() ) {
1658             if ( xml.name() == "DocumentStructure" ) {
1659                 // just a container for optional outline and story elements - nothing to do here
1660             } else if ( xml.name() == "DocumentStructure.Outline" ) {
1661                 kDebug(XpsDebug) << "found DocumentStructure.Outline";
1662             } else if ( xml.name() == "DocumentOutline" ) {
1663                 kDebug(XpsDebug) << "found DocumentOutline";
1664                 m_docStructure = new Okular::DocumentSynopsis;
1665             } else if ( xml.name() == "OutlineEntry" ) {
1666                 m_haveDocumentStructure = true;
1667                 QXmlStreamAttributes attributes = xml.attributes();
1668                 int outlineLevel = attributes.value( "OutlineLevel").toString().toInt();
1669                 QString description = attributes.value("Description").toString();
1670                 QDomElement synopsisElement = m_docStructure->createElement( description );
1671                 synopsisElement.setAttribute( "OutlineLevel",  outlineLevel );
1672                 QString target = attributes.value("OutlineTarget").toString();
1673                 int hashPosition = target.lastIndexOf( '#' );
1674                 target = target.mid( hashPosition + 1 );
1675                 // kDebug(XpsDebug) << "target: " << target;
1676                 Okular::DocumentViewport viewport;
1677                 viewport.pageNumber = m_docStructurePageMap.value( target );
1678                 synopsisElement.setAttribute( "Viewport",  viewport.toString() );
1679                 if ( outlineLevel == 1 ) {
1680                     // kDebug(XpsDebug) << "Description: "
1681                     // << outlineEntryElement.attribute( "Description" );
1682                     m_docStructure->appendChild( synopsisElement );
1683                 } else {
1684                     // find the last next highest element (so it this is level 3, we need
1685                     // to find the most recent level 2 node)
1686                     QDomNode maybeParentNode = m_docStructure->lastChild();
1687                     while ( !maybeParentNode.isNull() )
1688                     {
1689                         if ( maybeParentNode.toElement().attribute( "OutlineLevel" ).toInt() == ( outlineLevel - 1 ) )  {
1690                             // we have the right parent
1691                             maybeParentNode.appendChild( synopsisElement );
1692                             break;
1693                         }
1694                         maybeParentNode = maybeParentNode.lastChild();
1695                     }
1696                 }
1697             } else if ( xml.name() == "Story" ) {
1698                 // we need to handle Story here, but I have no idea what to do with it.
1699             } else if ( xml.name() == "StoryFragment" ) {
1700                 // we need to handle StoryFragment here, but I have no idea what to do with it.
1701             } else if ( xml.name() == "StoryFragmentReference" ) {
1702                 // we need to handle StoryFragmentReference here, but I have no idea what to do with it.
1703             } else {
1704                 kDebug(XpsDebug) << "Unhandled entry in DocumentStructure: " << xml.name().toString();
1705             }
1706         }
1707     }
1708 }
1709
1710 const Okular::DocumentSynopsis * XpsDocument::documentStructure()
1711 {
1712     return m_docStructure;
1713 }
1714
1715 bool XpsDocument::hasDocumentStructure()
1716 {
1717     return m_haveDocumentStructure;
1718 }
1719
1720 XpsDocument::XpsDocument(XpsFile *file, const QString &fileName): m_file(file), m_haveDocumentStructure( false ), m_docStructure( 0 )
1721 {
1722     kDebug(XpsDebug) << "document file name: " << fileName;
1723
1724     const KArchiveEntry documentEntry = file->xpsArchive()->entry( fileName );
1725     QString documentFilePath = fileName;
1726     const QString documentEntryPath = entryPath( fileName );
1727
1728     QXmlStreamReader docXml;
1729     docXml.addData( readFileOrDirectoryParts( file->xpsArchive(), &documentEntry, &documentFilePath ) );
1730     while( !docXml.atEnd() ) {
1731         docXml.readNext();
1732         if ( docXml.isStartElement() ) {
1733             if ( docXml.name() == "PageContent" ) {
1734                 QString pagePath = docXml.attributes().value("Source").toString();
1735                 kDebug(XpsDebug) << "Page Path: " << pagePath;
1736                 XpsPage *page = new XpsPage( file, absolutePath( documentFilePath, pagePath ) );
1737                 m_pages.append(page);
1738             } else if ( docXml.name() == "PageContent.LinkTargets" ) {
1739                 // do nothing - wait for the real LinkTarget elements
1740             } else if ( docXml.name() == "LinkTarget" ) {
1741                 QString targetName = docXml.attributes().value( "Name" ).toString();
1742                 if ( ! targetName.isEmpty() ) {
1743                     m_docStructurePageMap[ targetName ] = m_pages.count() - 1;
1744                 }
1745             } else if ( docXml.name() == "FixedDocument" ) {
1746                 // we just ignore this - it is just a container
1747             } else {
1748                 kDebug(XpsDebug) << "Unhandled entry in FixedDocument: " << docXml.name().toString();
1749             }
1750         }
1751     }
1752     if ( docXml.error() ) {
1753         kDebug(XpsDebug) << "Could not parse main XPS document file: " << docXml.errorString();
1754     }
1755
1756     // There might be a relationships entry for this document - typically used to tell us where to find the
1757     // content structure description
1758
1759     // We should be able to find this using a reference from some other part of the document, but I can't see it.
1760     const int slashPosition = fileName.lastIndexOf( '/' );
1761     const QString documentRelationshipFile = absolutePath( documentEntryPath, "_rels/" + fileName.mid( slashPosition + 1 ) + ".rels" );
1762
1763     const KArchiveEntry relFile = file->xpsArchive()->entry(documentRelationshipFile);
1764
1765     QString documentStructureFile;
1766     if ( !relFile.isNull() ) {
1767         QXmlStreamReader xml;
1768         xml.addData( readFileOrDirectoryParts( file->xpsArchive(), &relFile ) );
1769         while ( !xml.atEnd() )
1770         {
1771             xml.readNext();
1772             if ( xml.isStartElement() && ( xml.name() == "Relationship" ) ) {
1773                 QXmlStreamAttributes attributes = xml.attributes();
1774                 if ( attributes.value( "Type" ).toString() == "http://schemas.microsoft.com/xps/2005/06/documentstructure" ) {
1775                     documentStructureFile  = attributes.value( "Target" ).toString();
1776                 } else {
1777                     kDebug(XpsDebug) << "Unknown document relationships element: "
1778                                      << attributes.value( "Type" ).toString() << " : "
1779                                      << attributes.value( "Target" ).toString();
1780                 }
1781             }
1782         }
1783         if ( xml.error() ) {
1784             kDebug(XpsDebug) << "Could not parse XPS page relationships file ( "
1785                              << documentRelationshipFile
1786                              << " ) - " << xml.errorString();
1787         }
1788     } else { // the page relationship file didn't exist in the zipfile
1789         // this isn't fatal
1790         kDebug(XpsDebug) << "Could not open Document relationship file from " << documentRelationshipFile;
1791     }
1792
1793     if ( ! documentStructureFile.isEmpty() )
1794     {
1795         // kDebug(XpsDebug) << "Document structure filename: " << documentStructureFile;
1796         // make the document path absolute
1797         documentStructureFile = absolutePath( documentEntryPath, documentStructureFile );
1798         // kDebug(XpsDebug) << "Document structure absolute path: " << documentStructureFile;
1799         parseDocumentStructure( documentStructureFile );
1800     }
1801
1802 }
1803
1804 XpsDocument::~XpsDocument()
1805 {
1806     for (int i = 0; i < m_pages.size(); i++) {
1807         delete m_pages.at( i );
1808     }
1809     m_pages.clear();
1810
1811     if ( m_docStructure )
1812         delete m_docStructure;
1813 }
1814
1815 int XpsDocument::numPages() const
1816 {
1817     return m_pages.size();
1818 }
1819
1820 XpsPage* XpsDocument::page(int pageNum) const
1821 {
1822     return m_pages.at(pageNum);
1823 }
1824
1825 XpsFile::XpsFile() : m_docInfo( 0 )
1826 {
1827 }
1828
1829
1830 XpsFile::~XpsFile()
1831 {
1832     m_fontCache.clear();
1833     foreach (const QString &fontfile, m_fonts) {
1834         QFile::remove(fontfile);
1835     }
1836 }
1837
1838
1839 bool XpsFile::loadDocument(const QString &filename)
1840 {
1841     m_xpsArchive = new KArchive( filename );
1842     if ( m_xpsArchive->isReadable() == true ) {
1843         kDebug(XpsDebug) << "Successful open of " << filename;
1844     } else {
1845         kDebug(XpsDebug) << "Could not open XPS archive: " << filename;
1846         delete m_xpsArchive;
1847         return false;
1848     }
1849
1850     // The only fixed entry in XPS is /_rels/.rels
1851     const KArchiveEntry relEntry = m_xpsArchive->entry("_rels/.rels");
1852     if ( relEntry.isNull() ) {
1853         // this might occur if we can't read the zip directory, or it doesn't have the relationships entry
1854         kDebug(XpsDebug) << "No relationships found in archive: " << filename;
1855         return false;
1856     }
1857
1858     QXmlStreamReader relXml;
1859     relXml.addData( readFileOrDirectoryParts( m_xpsArchive, &relEntry ) );
1860
1861     QString fixedRepresentationFileName;
1862     // We work through the relationships document and pull out each element.
1863     while ( !relXml.atEnd() )
1864     {
1865         relXml.readNext();
1866         if ( relXml.isStartElement() ) {
1867             if ( relXml.name() == "Relationship" ) {
1868                 QXmlStreamAttributes attributes = relXml.attributes();
1869                 QString type = attributes.value( "Type" ).toString();
1870                 QString target = attributes.value( "Target" ).toString();
1871                 if ( "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail" == type ) {
1872                     m_thumbnailFileName = target;
1873                 } else if ( "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation" == type ) {
1874                     fixedRepresentationFileName = target;
1875                 } else if ("http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" == type) {
1876                     m_corePropertiesFileName = target;
1877                 } else if ("http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin" == type) {
1878                     m_signatureOrigin = target;
1879                 } else {
1880                     kDebug(XpsDebug) << "Unknown relationships element: " << type << " : " << target;
1881                 }
1882             } else if ( relXml.name() == "Relationships" ) {
1883                 // nothing to do here - this is just the container level
1884             } else {
1885                 kDebug(XpsDebug) << "unexpected element in _rels/.rels: " << relXml.name().toString();
1886             }
1887         }
1888     }
1889
1890     if ( relXml.error() ) {
1891         kDebug(XpsDebug) << "Could not parse _rels/.rels: " << relXml.errorString();
1892         return false;
1893     }
1894
1895     if ( fixedRepresentationFileName.isEmpty() ) {
1896         // FixedRepresentation is a required part of the XPS document
1897         kDebug(XpsDebug) << "fixedrepresentation not found in document";
1898         return false;
1899     }
1900
1901     const KArchiveEntry fixedRepEntry = m_xpsArchive->entry( fixedRepresentationFileName );
1902     QString fixedRepresentationFilePath = fixedRepresentationFileName;
1903
1904     QXmlStreamReader fixedRepXml;
1905     fixedRepXml.addData( readFileOrDirectoryParts( m_xpsArchive, &fixedRepEntry, &fixedRepresentationFileName ) );
1906
1907     while ( !fixedRepXml.atEnd() )
1908     {
1909         fixedRepXml.readNext();
1910         if ( fixedRepXml.isStartElement() ) {
1911             if ( fixedRepXml.name() == "DocumentReference" ) {
1912                 const QString source = fixedRepXml.attributes().value("Source").toString();
1913                 XpsDocument *doc = new XpsDocument( this, absolutePath( fixedRepresentationFilePath, source ) );
1914                 for (int lv = 0; lv < doc->numPages(); ++lv) {
1915                     // our own copy of the pages list
1916                     m_pages.append( doc->page( lv ) );
1917                 }
1918                 m_documents.append(doc);
1919             } else if ( fixedRepXml.name() == "FixedDocumentSequence") {
1920                 // we don't do anything here - this is just a container for one or more DocumentReference elements
1921             } else {
1922                 kDebug(XpsDebug) << "Unhandled entry in FixedDocumentSequence: " << fixedRepXml.name().toString();
1923             }
1924         }
1925     }
1926     if ( fixedRepXml.error() ) {
1927         kDebug(XpsDebug) << "Could not parse FixedRepresentation file:" << fixedRepXml.errorString();
1928         return false;
1929     }
1930
1931     return true;
1932 }
1933
1934 const Okular::DocumentInfo * XpsFile::generateDocumentInfo()
1935 {
1936     if ( m_docInfo )
1937         return m_docInfo;
1938
1939     m_docInfo = new Okular::DocumentInfo();
1940
1941     m_docInfo->set( Okular::DocumentInfo::MimeType, "application/oxps" );
1942
1943     if ( ! m_corePropertiesFileName.isEmpty() ) {
1944         QXmlStreamReader xml;
1945         xml.addData( m_xpsArchive->data(m_corePropertiesFileName) );
1946         while ( !xml.atEnd() )
1947         {
1948             xml.readNext();
1949             if ( xml.isEndElement() )
1950                 break;
1951             if ( xml.isStartElement() )
1952             {
1953                 if (xml.name() == "title") {
1954                     m_docInfo->set( Okular::DocumentInfo::Title, xml.readElementText() );
1955                 } else if (xml.name() == "subject") {
1956                     m_docInfo->set( Okular::DocumentInfo::Subject, xml.readElementText() );
1957                 } else if (xml.name() == "description") {
1958                     m_docInfo->set( Okular::DocumentInfo::Description, xml.readElementText() );
1959                 } else if (xml.name() == "creator") {
1960                     m_docInfo->set( Okular::DocumentInfo::Creator, xml.readElementText() );
1961                 } else if (xml.name() == "category") {
1962                     m_docInfo->set( Okular::DocumentInfo::Category, xml.readElementText() );
1963                 } else if (xml.name() == "created") {
1964                     QDateTime createdDate = QDateTime::fromString( xml.readElementText(), "yyyy-MM-ddThh:mm:ssZ" );
1965                     m_docInfo->set( Okular::DocumentInfo::CreationDate, KGlobal::locale()->formatDateTime( createdDate, KLocale::LongDate, true ) );
1966                 } else if (xml.name() == "modified") {
1967                     QDateTime modifiedDate = QDateTime::fromString( xml.readElementText(), "yyyy-MM-ddThh:mm:ssZ" );
1968                     m_docInfo->set( Okular::DocumentInfo::ModificationDate, KGlobal::locale()->formatDateTime( modifiedDate, KLocale::LongDate, true ) );
1969                 } else if (xml.name() == "keywords") {
1970                     m_docInfo->set( Okular::DocumentInfo::Keywords, xml.readElementText() );
1971                 } else if (xml.name() == "revision") {
1972                     m_docInfo->set( "revision", xml.readElementText(), i18n( "Revision" ) );
1973                 }
1974             }
1975         }
1976         if ( xml.error() )
1977         {
1978             kDebug(XpsDebug) << "Could not parse XPS core properties:" << xml.errorString();
1979         }
1980     } else {
1981         kDebug(XpsDebug) << "No core properties filename";
1982     }
1983
1984     m_docInfo->set( Okular::DocumentInfo::Pages, QString::number(numPages()) );
1985
1986     return m_docInfo;
1987 }
1988
1989 bool XpsFile::closeDocument()
1990 {
1991
1992     if ( m_docInfo )
1993         delete m_docInfo;
1994
1995     m_docInfo = 0;
1996
1997     qDeleteAll( m_documents );
1998     m_documents.clear();
1999
2000     delete m_xpsArchive;
2001
2002     return true;
2003 }
2004
2005 int XpsFile::numPages() const
2006 {
2007     return m_pages.size();
2008 }
2009
2010 int XpsFile::numDocuments() const
2011 {
2012     return m_documents.size();
2013 }
2014
2015 XpsDocument* XpsFile::document(int documentNum) const
2016 {
2017     return m_documents.at( documentNum );
2018 }
2019
2020 XpsPage* XpsFile::page(int pageNum) const
2021 {
2022     return m_pages.at( pageNum );
2023 }
2024
2025 XpsGenerator::XpsGenerator( QObject *parent, const QVariantList &args )
2026   : Okular::Generator( parent, args ), m_xpsFile( 0 )
2027 {
2028     setFeature( TextExtraction );
2029     setFeature( PrintNative );
2030 }
2031
2032 XpsGenerator::~XpsGenerator()
2033 {
2034 }
2035
2036 bool XpsGenerator::loadDocument( const QString & fileName, QVector<Okular::Page*> & pagesVector )
2037 {
2038     m_xpsFile = new XpsFile();
2039
2040     m_xpsFile->loadDocument( fileName );
2041     pagesVector.resize( m_xpsFile->numPages() );
2042
2043     int pagesVectorOffset = 0;
2044
2045     for (int docNum = 0; docNum < m_xpsFile->numDocuments(); ++docNum )
2046     {
2047         XpsDocument *doc = m_xpsFile->document( docNum );
2048         for (int pageNum = 0; pageNum < doc->numPages(); ++pageNum )
2049         {
2050             QSizeF pageSize = doc->page( pageNum )->size();
2051             pagesVector[pagesVectorOffset] = new Okular::Page( pagesVectorOffset, pageSize.width(), pageSize.height(), Okular::Rotation0 );
2052             ++pagesVectorOffset;
2053         }
2054     }
2055
2056     return true;
2057 }
2058
2059 bool XpsGenerator::doCloseDocument()
2060 {
2061     m_xpsFile->closeDocument();
2062     delete m_xpsFile;
2063     m_xpsFile = 0;
2064
2065     return true;
2066 }
2067
2068 QImage XpsGenerator::image( Okular::PixmapRequest * request )
2069 {
2070     QMutexLocker lock( userMutex() );
2071     QSize size( (int)request->width(), (int)request->height() );
2072     QImage image( size, QImage::Format_RGB32 );
2073     XpsPage *pageToRender = m_xpsFile->page( request->page()->number() );
2074     pageToRender->renderToImage( &image );
2075     return image;
2076 }
2077
2078 Okular::TextPage* XpsGenerator::textPage( Okular::Page * page )
2079 {
2080     QMutexLocker lock( userMutex() );
2081     XpsPage * xpsPage = m_xpsFile->page( page->number() );
2082     return xpsPage->textPage();
2083 }
2084
2085 const Okular::DocumentInfo * XpsGenerator::generateDocumentInfo()
2086 {
2087     kDebug(XpsDebug) << "generating document metadata";
2088
2089     return m_xpsFile->generateDocumentInfo();
2090 }
2091
2092 const Okular::DocumentSynopsis * XpsGenerator::generateDocumentSynopsis()
2093 {
2094     kDebug(XpsDebug) << "generating document synopsis";
2095
2096     // we only generate the synopsis for the first file.
2097     if ( !m_xpsFile || !m_xpsFile->document( 0 ) )
2098         return NULL;
2099
2100     if ( m_xpsFile->document( 0 )->hasDocumentStructure() )
2101         return m_xpsFile->document( 0 )->documentStructure();
2102
2103
2104     return NULL;
2105 }
2106
2107 Okular::ExportFormat::List XpsGenerator::exportFormats() const
2108 {
2109     static Okular::ExportFormat::List formats;
2110     if ( formats.isEmpty() ) {
2111         formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) );
2112     }
2113     return formats;
2114 }
2115
2116 bool XpsGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format )
2117 {
2118     if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) {
2119         QFile f( fileName );
2120         if ( !f.open( QIODevice::WriteOnly ) )
2121             return false;
2122
2123         QTextStream ts( &f );
2124         for ( int i = 0; i < m_xpsFile->numPages(); ++i )
2125         {
2126             Okular::TextPage* textPage = m_xpsFile->page(i)->textPage();
2127             QString text = textPage->text();
2128             ts << text;
2129             ts << QChar('\n');
2130             delete textPage;
2131         }
2132         f.close();
2133
2134         return true;
2135     }
2136
2137     return false;
2138 }
2139
2140 bool XpsGenerator::print( QPrinter &printer )
2141 {
2142     QList<int> pageList = Okular::Utils::pageList( printer, document()->pages(),
2143                                                    document()->currentPage() + 1,
2144                                                    document()->bookmarkedPageList() );
2145
2146     QPainter painter( &printer );
2147
2148     for ( int i = 0; i < pageList.count(); ++i )
2149     {
2150         if ( i != 0 )
2151             printer.newPage();
2152
2153         const int page = pageList.at( i ) - 1;
2154         XpsPage *pageToRender = m_xpsFile->page( page );
2155         QImage image( pageToRender->size().toSize(), QImage::Format_RGB32 );
2156         pageToRender->renderToImage( &image );
2157         painter.drawImage(0, 0, image);
2158     }
2159
2160     return true;
2161 }
2162
2163 XpsRenderNode * XpsRenderNode::findChild( const QString &name )
2164 {
2165     for (int i = 0; i < children.size(); i++) {
2166         if (children[i].name == name) {
2167             return &children[i];
2168         }
2169     }
2170
2171     return NULL;
2172 }
2173
2174 QVariant XpsRenderNode::getRequiredChildData( const QString &name )
2175 {
2176     XpsRenderNode * child = findChild( name );
2177     if (child == NULL) {
2178         kDebug(XpsDebug) << "Required element " << name << " is missing in " << this->name;
2179         return QVariant();
2180     }
2181
2182     return child->data;
2183 }
2184
2185 QVariant XpsRenderNode::getChildData( const QString &name )
2186 {
2187     XpsRenderNode * child = findChild( name );
2188     if (child == NULL) {
2189         return QVariant();
2190     } else {
2191         return child->data;
2192     }
2193 }
2194
2195 #include "moc_generator_xps.cpp"
2196