1 /* This file is part of the KDE project
2
3 Copyright (C) 2012 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
4 Copyright (C) 2012 Inge Wallin <inge@lysator.liu.se>
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
23 // Own
24 #include "exporthtml.h"
25
26 // Qt
27 #include <QSvgGenerator>
28 #include <QBuffer>
29 #include <QPainter>
30
31 // KF5
32 #include <kpluginfactory.h>
33
34 // Calligra
35 #include <KoFilterChain.h>
36 #include <KoXmlReader.h>
37 #include <KoXmlNS.h>
38
39 // This plugin
40 #include "OdfParser.h"
41 #include "OdtHtmlConverter.h"
42 #include "HtmlFile.h"
43 #include "HtmlExportDebug.h"
44
45 #include "WmfPainterBackend.h"
46
47 #include "EmfParser.h"
48 #include "EmfOutputPainterStrategy.h"
49 #include "SvmParser.h"
50 #include "SvmPainterBackend.h"
51
52
53 K_PLUGIN_FACTORY_WITH_JSON(ExportHtmlFactory, "calligra_filter_odt2html.json",
54 registerPlugin<ExportHtml>();)
55
56 // Needed to instantiate the plugin factory.
57 #include "exporthtml.moc"
58
59
ExportHtml(QObject * parent,const QVariantList &)60 ExportHtml::ExportHtml(QObject *parent, const QVariantList&)
61 : KoFilter(parent)
62 {
63 }
64
~ExportHtml()65 ExportHtml::~ExportHtml()
66 {
67 }
68
69
convert(const QByteArray & from,const QByteArray & to)70 KoFilter::ConversionStatus ExportHtml::convert(const QByteArray &from, const QByteArray &to)
71 {
72 // Check mimetypes
73 if (from != "application/vnd.oasis.opendocument.text" || to != "text/html") {
74 return KoFilter::NotImplemented;
75 }
76
77 // Open the infile and return an error if it fails.
78 KoStore *odfStore = KoStore::createStore(m_chain->inputFile(), KoStore::Read,
79 "", KoStore::Auto);
80 if (!odfStore->open("mimetype")) {
81 errorHtml << "Unable to open input file!" << endl;
82 delete odfStore;
83 return KoFilter::FileNotFound;
84 }
85 odfStore->close();
86
87 // Start the conversion
88 KoFilter::ConversionStatus status;
89
90 // ----------------------------------------------------------------
91 // Parse input files
92
93 OdfParser odfParser;
94
95 // Parse meta.xml into m_metadata
96 status = odfParser.parseMetadata(odfStore, m_metadata);
97 if (status != KoFilter::OK) {
98 delete odfStore;
99 return status;
100 }
101
102 // Parse manifest
103 status = odfParser.parseManifest(odfStore, m_manifest);
104 if (status != KoFilter::OK) {
105 delete odfStore;
106 return status;
107 }
108
109 // ----------------------------------------------------------------
110 // Create content files.
111
112 // Create html contents.
113 // m_imagesSrcList is an output parameter from the conversion.
114 HtmlFile html;
115 html.setPathPrefix("./");
116 const QString outputFileName = m_chain->outputFile().section('/', -1);
117 const int dotPosition = outputFileName.indexOf('.');
118 html.setFilePrefix(outputFileName.left(dotPosition));
119 html.setFileSuffix(dotPosition != -1 ? outputFileName.mid(dotPosition) : QString());
120 OdtHtmlConverter converter;
121 OdtHtmlConverter::ConversionOptions options = {
122 false, // don't put styles in css file
123 false, // don't break into chapters
124 false // It is not mobi.
125 };
126 QHash<QString, QString> mediaFilesList;
127 status = converter.convertContent(odfStore, m_metadata, &m_manifest, &options, &html,
128 m_imagesSrcList, mediaFilesList);
129 if (status != KoFilter::OK) {
130 delete odfStore;
131 return status;
132 }
133
134 // Extract images
135 status = extractImages(odfStore, &html);
136 if (status != KoFilter::OK) {
137 delete odfStore;
138 return status;
139 }
140
141 // ----------------------------------------------------------------
142 // Write the finished html file to disk
143
144 html.writeHtml(m_chain->outputFile());
145
146 delete odfStore;
147
148 return KoFilter::OK;
149 }
150
151
extractImages(KoStore * odfStore,HtmlFile * htmlFile)152 KoFilter::ConversionStatus ExportHtml::extractImages(KoStore *odfStore, HtmlFile *htmlFile)
153 {
154 // Extract images and add them to htmlFile one by one
155 QByteArray imgContent;
156 int imgId = 1;
157 foreach (const QString &imgSrc, m_imagesSrcList.keys()) {
158 debugHtml << imgSrc;
159 if (!odfStore->extractFile(imgSrc, imgContent)) {
160 debugHtml << "Can not to extract file";
161 return KoFilter::FileNotFound;
162 }
163
164 #if 1
165 htmlFile->addContentFile(("image" + QString::number(imgId)), // id
166 (htmlFile->filePrefix() + imgSrc.section('/', -1)), // filename
167 m_manifest.value(imgSrc).toUtf8(), imgContent);
168 #else
169
170 VectorType type = vectorType(imgContent);
171 QSizeF qSize = m_imagesSrcList.value(imgSrc);
172 switch (type) {
173
174 case ExportHtml::VectorTypeSvm:
175 {
176 debugHtml << "Svm file";
177 QSize size(qSize.width(), qSize.height());
178 QByteArray output;
179 if (!convertSvm(imgContent, output, size)) {
180 debugHtml << "Svm Parse error";
181 return KoFilter::ParsingError;
182 }
183
184 epubFile->addContentFile(("image" + QString::number(imgId)),
185 (epubFile->pathPrefix() + imgSrc.section('/', -1)),
186 "image/svg+xml", output);
187 break;
188 }
189 case ExportHtml::VectorTypeEmf:
190 {
191 debugHtml << "EMF file";
192 QSize size(qSize.width(), qSize.height());
193 QByteArray output;
194 if (!convertEmf(imgContent, output, size)) {
195 debugHtml << "EMF Parse error";
196 return KoFilter::ParsingError;
197 }
198
199 epubFile->addContentFile(("image" + QString::number(imgId)),
200 (epubFile->pathPrefix() + imgSrc.section('/', -1)),
201 "image/svg+xml", output);
202 break;
203 }
204 case ExportHtml::VectorTypeWmf:
205 {
206 debugHtml << "WMF file";
207 QByteArray output;
208 if (!convertWmf(imgContent, output, qSize)) {
209 debugHtml << "WMF Parse error";
210 return KoFilter::ParsingError;
211 }
212
213 epubFile->addContentFile(("image" + QString::number(imgId)),
214 (epubFile->pathPrefix() + imgSrc.section('/', -1)),
215 "image/svg+xml", output);
216 break;
217 }
218
219 // If it's not one of the types we can convert, let's just
220 // assume that the image can be used as it is. The user
221 // will find out soon anyway when s/he tries to look at
222 // the image.
223 case ExportHtml::VectorTypeOther:
224 {
225 debugHtml << "Other file";
226 epubFile->addContentFile(("image" + QString::number(imgId)),
227 (epubFile->pathPrefix() + imgSrc.section('/', -1)),
228 m_manifest.value(imgSrc).toUtf8(), imgContent);
229 break;
230 }
231
232 default:
233 debugHtml << "";
234 }
235 #endif
236 }
237 return KoFilter::OK;
238 }
239
240 #if 0
241
242 bool ExportHtml::convertSvm(QByteArray &input, QByteArray &output, QSize size)
243 {
244
245 QBuffer *outBuf = new QBuffer(&output);
246 QSvgGenerator generator;
247 generator.setOutputDevice(outBuf);
248 generator.setSize(QSize(200, 200));
249 generator.setTitle("Svg image");
250 generator.setDescription("This is an svg image that is converted from svm by Calligra");
251
252 Libsvm::SvmParser svmParser;
253
254 QPainter painter;
255
256 if (!painter.begin(&generator)) {
257 debugHtml << "Can not open the painter";
258 return false;
259 }
260
261 painter.scale(50,50);
262 Libsvm::SvmPainterBackend svmPainterBackend(&painter, size);
263 svmParser.setBackend(&svmPainterBackend);
264 if (!svmParser.parse(input)) {
265 debugHtml << "Can not Parse the Svm file";
266 return false;
267 }
268 painter.end();
269
270 return true;
271 }
272
273 bool ExportHtml::convertEmf(QByteArray &input, QByteArray &output, QSize size)
274 {
275 QBuffer *outBuf = new QBuffer(&output);
276 QSvgGenerator generator;
277 generator.setOutputDevice(outBuf);
278 generator.setSize(QSize(200, 200));
279 generator.setTitle("Svg image");
280 generator.setDescription("This is an svg image that is converted from EMF by Calligra");
281
282 Libemf::Parser emfParser;
283
284 QPainter painter;
285
286 if (!painter.begin(&generator)) {
287 debugHtml << "Can not open the painter";
288 return false;
289 }
290
291 painter.scale(50,50);
292 Libemf::OutputPainterStrategy emfPaintOutput(painter, size, true );
293 emfParser.setOutput( &emfPaintOutput );
294 if (!emfParser.load(input)) {
295 debugHtml << "Can not Parse the EMF file";
296 return false;
297 }
298 painter.end();
299
300 return true;
301 }
302
303 bool ExportHtml::convertWmf(QByteArray &input, QByteArray &output, QSizeF size)
304 {
305 QBuffer *outBuf = new QBuffer(&output);
306 QSvgGenerator generator;
307 generator.setOutputDevice(outBuf);
308 generator.setSize(QSize(200, 200));
309 generator.setTitle("Svg image");
310 generator.setDescription("This is an svg image that is converted from WMF by Calligra");
311
312 QPainter painter;
313
314 if (!painter.begin(&generator)) {
315 debugHtml << "Can not open the painter";
316 return false;
317 }
318
319 painter.scale(50,50);
320 Libwmf::WmfPainterBackend wmfPainter(&painter, size);
321 if (!wmfPainter.load(input)) {
322 debugHtml << "Can not Parse the WMF file";
323 return false;
324 }
325
326 // Actually paint the WMF.
327 painter.save();
328 wmfPainter.play();
329 painter.restore();
330 painter.end();
331
332 return true;
333 }
334
335 // ----------------------------------------------------------------
336 // These functions were taken from the vector shape.
337
338 ExportHtml::VectorType ExportHtml::vectorType(QByteArray &content)
339 {
340 if (isSvm(content))
341 return ExportHtml::VectorTypeSvm;
342 if (isEmf(content))
343 return ExportHtml::VectorTypeEmf;
344 if (isWmf(content))
345 return ExportHtml::VectorTypeWmf;
346
347 return ExportHtml::VectorTypeOther;
348 }
349
350 bool ExportHtml::isSvm(QByteArray &content)
351 {
352 if (content.startsWith("VCLMTF"))
353 return true;
354 return false;
355 }
356
357 bool ExportHtml::isEmf(QByteArray &content)
358 {
359 const char *data = content.constData();
360 const int size = content.count();
361
362 // This is how the 'file' command identifies an EMF.
363 // 1. Check type
364 int offset = 0;
365 int result = (int) data[offset];
366 result |= (int) data[offset+1] << 8;
367 result |= (int) data[offset+2] << 16;
368 result |= (int) data[offset+3] << 24;
369
370 qint32 mark = result;
371 if (mark != 0x00000001) {
372 return false;
373 }
374
375 // 2. An EMF has the string " EMF" at the start + offset 40.
376 if (size > 44 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F'){
377 return true;
378 }
379
380 return false;
381 }
382
383 bool ExportHtml::isWmf(QByteArray &content)
384 {
385 const char *data = content.constData();
386 const int size = content.count();
387
388 if (size < 10)
389 return false;
390
391 // This is how the 'file' command identifies a WMF.
392 if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232'){
393 return true;
394 }
395
396 if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){
397 return true;
398 }
399
400 if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){
401 return true;
402 }
403
404 return false;
405 }
406
407 #endif
408