1 /* This file is part of the KDE project
2    Copyright (C) 2010 KO GmbH <jos.van.den.oever@kogmbh.com>
3    Copyright (C) 2012 Sven Langkamp <sven.langkamp@gmail.com>
4    Copyright (C) 2015-2016 Friedrich W. H. Kossebau <kossebau@kde.org>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10 
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15 
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19    Boston, MA 02110-1301, USA.
20 */
21 
22 #include "OkularOdtGenerator.h"
23 
24 #include <QDebug>
25 #include <QImage>
26 #include <QPainter>
27 #include <QTextDocument>
28 #include <QMimeDatabase>
29 #include <QMimeType>
30 
31 #include <KoDocumentEntry.h>
32 #include <KoPart.h>
33 #include <KWDocument.h>
34 #include <KWPage.h>
35 #include <KWCanvasItem.h>
36 #include <frames/KWTextFrameSet.h>
37 #include <KoShapeManager.h>
38 #include <KoDocumentInfo.h>
39 #include <KoGlobal.h>
40 #include <KoParagraphStyle.h>
41 #include <KoTextLayoutRootArea.h>
42 #include <KoCharAreaInfo.h>
43 
44 #include <okular/core/page.h>
45 
46 
OkularOdtGenerator(QObject * parent,const QVariantList & args)47 OkularOdtGenerator::OkularOdtGenerator( QObject *parent, const QVariantList &args )
48     : Okular::Generator( parent, args )
49 {
50     m_doc = 0;
51     setFeature( TextExtraction );
52 }
53 
~OkularOdtGenerator()54 OkularOdtGenerator::~OkularOdtGenerator()
55 {
56 }
57 
calculateViewport(const QTextBlock & block,KoTextDocumentLayout * textDocumentLayout)58 static Okular::DocumentViewport calculateViewport( const QTextBlock &block,
59                                                    KoTextDocumentLayout* textDocumentLayout )
60 {
61     KoTextLayoutRootArea *rootArea = textDocumentLayout->rootAreaForPosition(block.position());
62 
63     QRectF rect = textDocumentLayout->blockBoundingRect( block );
64     rect.translate(-(rootArea->referenceRect().topLeft()));
65 
66     KoShape *shape = rootArea->associatedShape();
67     rect.translate(shape->absolutePosition(KoFlake::TopLeftCorner));
68 
69     KWPage* page = static_cast<KWPage *>(rootArea->page());
70     rect.translate(static_cast<qreal>(0.0), -(page->offsetInDocument()));
71 
72     const qreal pageHeight = page->height();
73     const qreal pageWidth = page->width();
74     const int pageNumber = page->pageNumber();
75 
76     Okular::DocumentViewport viewport( pageNumber-1 );
77     viewport.rePos.normalizedX = static_cast<double>(rect.x()) / static_cast<double>(pageWidth);
78     viewport.rePos.normalizedY = static_cast<double>(rect.y()) / static_cast<double>(pageHeight);
79     viewport.rePos.enabled = true;
80     viewport.rePos.pos = Okular::DocumentViewport::TopLeft;
81 
82     return viewport;
83 }
84 
loadDocument(const QString & fileName,QVector<Okular::Page * > & pages)85 bool OkularOdtGenerator::loadDocument( const QString &fileName, QVector<Okular::Page*> &pages )
86 {
87     const QString mimetype = QMimeDatabase().mimeTypeForFile(fileName).name();
88 
89     QString error;
90     KoDocumentEntry documentEntry = KoDocumentEntry::queryByMimeType(mimetype);
91     KoPart *part = documentEntry.createKoPart(&error);
92 
93     if (!error.isEmpty()) {
94         qWarning() << "Error creating document" << mimetype << error;
95         return 0;
96     }
97 
98     m_doc = static_cast<KWDocument*>(part->document());
99     const QUrl url = QUrl::fromLocalFile(fileName);
100     m_doc->setCheckAutoSaveFile(false);
101     m_doc->setAutoErrorHandlingEnabled(false); // show error dialogs
102     if (!m_doc->openUrl(url)) {
103         return false;
104     }
105 
106     while (!m_doc->layoutFinishedAtleastOnce()) {
107         QCoreApplication::processEvents();
108 
109         if (!QCoreApplication::hasPendingEvents())
110             break;
111     }
112 
113     KWPageManager *pageManager = m_doc->pageManager();
114     int pageCount = pageManager->pages().count();
115     for(int i = 1; i <= pageCount; ++i) {
116 
117         KWPage kwpage = pageManager->page(i);
118 
119         Okular::Page * page = new Okular::Page( i-1, kwpage.width(), kwpage.height(), Okular::Rotation0 );
120         pages.append(page);
121     }
122 
123     // meta data
124     const KoDocumentInfo *documentInfo = m_doc->documentInfo();
125     m_documentInfo.set( Okular::DocumentInfo::MimeType, mimetype );
126     m_documentInfo.set( Okular::DocumentInfo::Producer, documentInfo->originalGenerator() );
127     m_documentInfo.set( Okular::DocumentInfo::Title,       documentInfo->aboutInfo("title") );
128     m_documentInfo.set( Okular::DocumentInfo::Subject,     documentInfo->aboutInfo("subject") );
129     m_documentInfo.set( Okular::DocumentInfo::Keywords,     documentInfo->aboutInfo("keyword") );
130     m_documentInfo.set( Okular::DocumentInfo::Description, documentInfo->aboutInfo("description") );
131     m_documentInfo.set( "language",    KoGlobal::languageFromTag(documentInfo->aboutInfo("language")),  i18n("Language"));
132 
133     const QString creationDate = documentInfo->aboutInfo("creation-date");
134     if (!creationDate.isEmpty()) {
135         QDateTime t = QDateTime::fromString(creationDate, Qt::ISODate);
136         m_documentInfo.set( Okular::DocumentInfo::CreationDate, QLocale().toString(t, QLocale::ShortFormat) );
137     }
138     m_documentInfo.set( Okular::DocumentInfo::Creator,  documentInfo->aboutInfo("initial-creator") );
139 
140     const QString modificationDate = documentInfo->aboutInfo("date");
141     if (!modificationDate.isEmpty()) {
142         QDateTime t = QDateTime::fromString(modificationDate, Qt::ISODate);
143         m_documentInfo.set( Okular::DocumentInfo::ModificationDate, QLocale().toString(t, QLocale::ShortFormat) );
144     }
145     m_documentInfo.set( Okular::DocumentInfo::Author, documentInfo->aboutInfo("creator") );
146 
147     // ToC
148     QDomNode parentNode = m_documentSynopsis;
149 
150     QStack< QPair<int,QDomNode> > parentNodeStack;
151     parentNodeStack.push( qMakePair( 0, parentNode ) );
152 
153     QTextDocument* textDocument = m_doc->mainFrameSet()->document();
154     KoTextDocumentLayout* textDocumentLayout = static_cast<KoTextDocumentLayout *>(textDocument->documentLayout());
155 
156     QTextBlock block = textDocument->begin();
157     for (; block.isValid(); block = block.next()) {
158         int blockLevel = 0;
159         if (block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) {
160             blockLevel = block.blockFormat().intProperty(KoParagraphStyle::OutlineLevel);
161         }
162 
163         // no blockLevel yet?
164         if (blockLevel == 0) {
165             continue;
166         }
167 
168         Okular::DocumentViewport viewport = calculateViewport( block, textDocumentLayout );
169 
170         QDomElement item = m_documentSynopsis.createElement( block.text() );
171         item.setAttribute( "Viewport", viewport.toString() );
172 
173         // we need a parent, which has to be at a higher heading level than this heading level
174         // so we just work through the stack
175         while ( ! parentNodeStack.isEmpty() ) {
176             int parentLevel = parentNodeStack.top().first;
177             if ( parentLevel < blockLevel ) {
178                 // this is OK as a parent
179                 parentNode = parentNodeStack.top().second;
180                 break;
181             } else {
182                 // we'll need to be further into the stack
183                 parentNodeStack.pop();
184             }
185         }
186         parentNode.appendChild( item );
187         parentNodeStack.push( qMakePair( blockLevel, QDomNode(item) ) );
188     }
189 
190     return true;
191 }
192 
doCloseDocument()193 bool OkularOdtGenerator::doCloseDocument()
194 {
195     delete m_doc;
196     m_doc = 0;
197 
198     m_documentInfo = Okular::DocumentInfo();
199     m_documentSynopsis = Okular::DocumentSynopsis();
200 
201     return true;
202 }
203 
canGeneratePixmap() const204 bool OkularOdtGenerator::canGeneratePixmap() const
205 {
206     return true;
207 }
208 
generatePixmap(Okular::PixmapRequest * request)209 void OkularOdtGenerator::generatePixmap( Okular::PixmapRequest *request )
210 {
211     QPixmap* pix;
212     if (!m_doc) {
213         pix = new QPixmap(request->width(), request->height());
214         QPainter painter(pix);
215         painter.fillRect(0 ,0 , request->width(), request->height(), Qt::white);
216     } else {
217 
218         // use shape manager from canvasItem even for QWidget environments
219         // if using the shape manager from one of the views there is no guarantee
220         // that the view, its canvas and the shapemanager is not destroyed in between
221         KoShapeManager* shapeManager = static_cast<KWCanvasItem*>(m_doc->documentPart()->canvasItem(m_doc))->shapeManager();
222 
223         KWPageManager *pageManager = m_doc->pageManager();
224 
225         KWPage page = pageManager->page(request->pageNumber()+1);
226 
227         pix = new QPixmap(request->width(), request->height());
228         QPainter painter(pix);
229 
230         QSize rSize(request->width(), request->height());
231 
232         pix = new QPixmap();
233         pix->convertFromImage(page.thumbnail(rSize, shapeManager, true));
234     }
235 
236     request->page()->setPixmap( request->observer(), pix );
237 
238     signalPixmapRequestDone( request );
239 }
240 
canGenerateTextPage() const241 bool OkularOdtGenerator::canGenerateTextPage() const
242 {
243     return true;
244 }
245 
textPage(Okular::Page * page)246 Okular::TextPage* OkularOdtGenerator::textPage( Okular::Page *page )
247 {
248     QTextDocument* textDocument = m_doc->mainFrameSet()->document();
249     KoTextDocumentLayout* textDocumentLayout = static_cast<KoTextDocumentLayout *>(textDocument->documentLayout());
250 
251     KoTextLayoutRootArea *rootArea = 0;
252     foreach(KoTextLayoutRootArea *area, textDocumentLayout->rootAreas()) {
253         if (area->page()->pageNumber() == page->number()+1) {
254             rootArea = area;
255             break;
256         }
257     }
258 
259     if (!rootArea) {
260         return 0;
261     }
262 
263     const QVector<KoCharAreaInfo> charAreaInfos = rootArea->generateCharAreaInfos();
264 
265     // TODO: text from master pages (headers/footers), text in floating shapes
266     if (charAreaInfos.isEmpty()) {
267         return 0;
268     }
269 
270     KWPage* wpage = static_cast<KWPage *>(rootArea->page());
271     KoShape *shape = rootArea->associatedShape();
272 
273     const QPointF offset = shape->absolutePosition(KoFlake::TopLeftCorner)
274                            + QPointF(static_cast<qreal>(0.0), -(wpage->offsetInDocument()))
275                            - rootArea->referenceRect().topLeft();
276 
277     const double pageHeight = wpage->height();
278     const double pageWidth = wpage->width();
279 
280     Okular::TextPage *textPage = new Okular::TextPage;
281     foreach(const KoCharAreaInfo &charAreaInfo, charAreaInfos) {
282         const QRectF rect = charAreaInfo.rect.translated(offset);
283         const double left = static_cast<double>(rect.left()) / pageWidth;
284         const double top = static_cast<double>(rect.top()) / pageHeight;
285         const double right = static_cast<double>(rect.right()) / pageWidth;
286         const double bottom = static_cast<double>(rect.bottom()) / pageHeight;
287         textPage->append( charAreaInfo.character,
288                           new Okular::NormalizedRect( left, top, right, bottom ) );
289     }
290     return textPage;
291 }
292 
293 
generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> & keys) const294 Okular::DocumentInfo OkularOdtGenerator::generateDocumentInfo( const QSet<Okular::DocumentInfo::Key> &keys ) const
295 {
296     Q_UNUSED(keys);
297 
298     return m_documentInfo;
299 }
300 
generateDocumentSynopsis()301 const Okular::DocumentSynopsis* OkularOdtGenerator::generateDocumentSynopsis()
302 {
303     return m_documentSynopsis.hasChildNodes() ? &m_documentSynopsis : 0;
304 }
305