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