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