diff --git a/filters/words/epub/exportepub2.cpp b/filters/words/epub/exportepub2.cpp index 8c5da8d8bb3..17e5655024e 100644 --- a/filters/words/epub/exportepub2.cpp +++ b/filters/words/epub/exportepub2.cpp @@ -1,523 +1,522 @@ /* This file is part of the KDE project Copyright (C) 2012 Mojtaba Shahi Senobari Copyright (C) 2012 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "exportepub2.h" // Qt #include #include #include #include // KF5 #include // Calligra #include #include #include #include // This plugin #include "OdfParser.h" #include "OdtHtmlConverter.h" #include "EpubFile.h" #include "EpubExportDebug.h" #include "WmfPainterBackend.h" #include "EmfParser.h" #include "EmfOutputPainterStrategy.h" -#include "EmfOutputDebugStrategy.h" #include "SvmParser.h" #include "SvmPainterBackend.h" K_PLUGIN_FACTORY_WITH_JSON(ExportEpub2Factory, "calligra_filter_odt2epub2.json", registerPlugin();) // Needed to instantiate the plugin factory. #include "exportepub2.moc" ExportEpub2::ExportEpub2(QObject *parent, const QVariantList&) : KoFilter(parent) { } ExportEpub2::~ExportEpub2() { } KoFilter::ConversionStatus ExportEpub2::convert(const QByteArray &from, const QByteArray &to) { // Check mimetypes if (from != "application/vnd.oasis.opendocument.text" || to != "application/epub+zip") { return KoFilter::NotImplemented; } // Open the infile and return an error if it fails. KoStore *odfStore = KoStore::createStore(m_chain->inputFile(), KoStore::Read, "", KoStore::Auto); if (!odfStore->open("mimetype")) { errorEpub << "Unable to open input file!" << endl; delete odfStore; return KoFilter::FileNotFound; } odfStore->close(); // Start the conversion OdtHtmlConverter converter; OdfParser odfParser; EpubFile epub; KoFilter::ConversionStatus status; // ---------------------------------------------------------------- // Parse input files // Parse meta.xml into m_metadata status = odfParser.parseMetadata(odfStore, m_metadata); if (status != KoFilter::OK) { delete odfStore; return status; } // Parse manifest status = odfParser.parseManifest(odfStore, m_manifest); if (status != KoFilter::OK) { delete odfStore; return status; } // ---------------------------------------------------------------- // Create content files. // Create html contents. // m_imagesSrcList is an output parameter from the conversion. OdtHtmlConverter::ConversionOptions options = { true, // do put styles in css file true, // do break into chapters false // It is not mobi }; status = converter.convertContent(odfStore, m_metadata, &m_manifest, &options, &epub, m_imagesSrcList, m_mediaFilesList); if (status != KoFilter::OK) { delete odfStore; return status; } // Extract images status = extractImages(odfStore, &epub); if (status != KoFilter::OK) { delete odfStore; return status; } // Extract media files status = extractMediaFiles(&epub); if (status != KoFilter::OK) { delete odfStore; return status; } // Check for cover image status = extractCoverImage(odfStore, &epub); if (status != KoFilter::OK) { delete odfStore; return status; } // ---------------------------------------------------------------- // Write the finished epub file to disk epub.writeEpub(m_chain->outputFile(), to, m_metadata); delete odfStore; return KoFilter::OK; } KoFilter::ConversionStatus ExportEpub2::extractImages(KoStore *odfStore, EpubFile *epubFile) { // Extract images and add them to epubFile one by one QByteArray imgContent; int imgId = 1; foreach (const QString &imgSrc, m_imagesSrcList.keys()) { debugEpub << imgSrc; if (!odfStore->hasFile(imgSrc)) { warnEpub << "Can not to extract this image, image "<< imgSrc<< "is an external image"; // Ignore the external image. continue; } if (!odfStore->extractFile(imgSrc, imgContent)) { debugEpub << "Can not to extract file"; return KoFilter::FileNotFound; } VectorType type = vectorType(imgContent); QSizeF qSize = m_imagesSrcList.value(imgSrc); switch (type) { case ExportEpub2::VectorTypeSvm: { debugEpub << "Svm file"; QSize size(qSize.width(), qSize.height()); QByteArray output; if (!convertSvm(imgContent, output, size)) { debugEpub << "Svm Parse error"; return KoFilter::ParsingError; } epubFile->addContentFile(("image" + QString::number(imgId++)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output); break; } case ExportEpub2::VectorTypeEmf: { debugEpub << "EMF file"; QSize size(qSize.width(), qSize.height()); QByteArray output; if (!convertEmf(imgContent, output, size)) { debugEpub << "EMF Parse error"; return KoFilter::ParsingError; } epubFile->addContentFile(("image" + QString::number(imgId++)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output); break; } case ExportEpub2::VectorTypeWmf: { debugEpub << "WMF file"; QByteArray output; if (!convertWmf(imgContent, output, qSize)) { debugEpub << "WMF Parse error"; return KoFilter::ParsingError; } epubFile->addContentFile(("image" + QString::number(imgId++)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output); break; } // If it's not one of the types we can convert, let's just // assume that the image can be used as it is. The user // will find out soon anyway when s/he tries to look at // the image. case ExportEpub2::VectorTypeOther: { debugEpub << "Other file"; epubFile->addContentFile(("image" + QString::number(imgId++)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), m_manifest.value(imgSrc).toUtf8(), imgContent); break; } default: debugEpub << ""; } } return KoFilter::OK; } bool ExportEpub2::convertSvm(QByteArray &input, QByteArray &output, QSize size) { QBuffer *outBuf = new QBuffer(&output); QSvgGenerator generator; generator.setOutputDevice(outBuf); generator.setSize(QSize(200, 200)); generator.setTitle("Svg image"); generator.setDescription("This is an svg image that is converted from svm by Calligra"); Libsvm::SvmParser svmParser; QPainter painter; if (!painter.begin(&generator)) { debugEpub << "Can not open the painter"; return false; } painter.scale(50,50); Libsvm::SvmPainterBackend svmPainterBackend(&painter, size); svmParser.setBackend(&svmPainterBackend); if (!svmParser.parse(input)) { debugEpub << "Can not Parse the Svm file"; return false; } painter.end(); return true; } bool ExportEpub2::convertEmf(QByteArray &input, QByteArray &output, QSize size) { QBuffer *outBuf = new QBuffer(&output); QSvgGenerator generator; generator.setOutputDevice(outBuf); generator.setSize(QSize(200, 200)); generator.setTitle("Svg image"); generator.setDescription("This is an svg image that is converted from EMF by Calligra"); Libemf::Parser emfParser; QPainter painter; if (!painter.begin(&generator)) { debugEpub << "Can not open the painter"; return false; } painter.scale(50,50); Libemf::OutputPainterStrategy emfPaintOutput(painter, size, true ); emfParser.setOutput( &emfPaintOutput ); if (!emfParser.load(input)) { debugEpub << "Can not Parse the EMF file"; return false; } painter.end(); return true; } bool ExportEpub2::convertWmf(QByteArray &input, QByteArray &output, QSizeF size) { QBuffer *outBuf = new QBuffer(&output); QSvgGenerator generator; generator.setOutputDevice(outBuf); generator.setSize(QSize(200, 200)); generator.setTitle("Svg image"); generator.setDescription("This is an svg image that is converted from WMF by Calligra"); QPainter painter; if (!painter.begin(&generator)) { debugEpub << "Can not open the painter"; return false; } painter.scale(50,50); Libwmf::WmfPainterBackend wmfPainter(&painter, size); if (!wmfPainter.load(input)) { debugEpub << "Can not Parse the WMF file"; return false; } // Actually paint the WMF. painter.save(); wmfPainter.play(); painter.restore(); painter.end(); return true; } // ---------------------------------------------------------------- // These functions were taken from the vector shape. ExportEpub2::VectorType ExportEpub2::vectorType(QByteArray &content) { if (isSvm(content)) return ExportEpub2::VectorTypeSvm; if (isEmf(content)) return ExportEpub2::VectorTypeEmf; if (isWmf(content)) return ExportEpub2::VectorTypeWmf; return ExportEpub2::VectorTypeOther; } bool ExportEpub2::isSvm(QByteArray &content) { if (content.startsWith("VCLMTF")) return true; return false; } bool ExportEpub2::isEmf(QByteArray &content) { const char *data = content.constData(); const int size = content.count(); // This is how the 'file' command identifies an EMF. // 1. Check type int offset = 0; int result = (int) data[offset]; result |= (int) data[offset+1] << 8; result |= (int) data[offset+2] << 16; result |= (int) data[offset+3] << 24; qint32 mark = result; if (mark != 0x00000001) { return false; } // 2. An EMF has the string " EMF" at the start + offset 40. if (size > 44 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F'){ return true; } return false; } bool ExportEpub2::isWmf(QByteArray &content) { const char *data = content.constData(); const int size = content.count(); if (size < 10) return false; // This is how the 'file' command identifies a WMF. if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232'){ return true; } if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){ return true; } if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){ return true; } return false; } KoFilter::ConversionStatus ExportEpub2::extractMediaFiles(EpubFile *epubFile) { QByteArray mediaContent; foreach (const QString &mediaId, m_mediaFilesList.keys()) { QString mediaSrc = m_mediaFilesList.value(mediaId); // Remove scheme (file://) from the path QUrl mediaPath(mediaSrc); mediaSrc = mediaPath.path(); QFile file (mediaSrc); if (!file.open(QIODevice::ReadOnly)) { debugEpub << "Unable to open" << mediaSrc; return KoFilter::FileNotFound; } mediaContent = file.readAll(); const QString mimetype = QMimeDatabase().mimeTypeForFileNameAndData(mediaSrc.section("/", -1), mediaContent).name(); epubFile->addContentFile(mediaId.section("#", -1), epubFile->pathPrefix() + mediaSrc.section("/", -1), mimetype.toUtf8(), mediaContent); } return KoFilter::OK; } KoFilter::ConversionStatus ExportEpub2::extractCoverImage(KoStore *odfStore, EpubFile *epubFile) { // Find Cover from manifest QString coverPath; foreach (const QString &path, m_manifest.keys()) { if (path.contains("coverImage.")) { coverPath = path; break; } } // There is no cover image. if (coverPath.isEmpty()) { return KoFilter::OK; } // Extact cover data. QByteArray coverData; if (!odfStore->extractFile(coverPath, coverData)) { debugEpub << "Can not to extract file" + coverPath; return KoFilter::FileNotFound; } // Add it to epub file content. QByteArray mime = m_manifest.value(coverPath).toUtf8(); epubFile->addContentFile(QString("cover-image"), QString((epubFile->pathPrefix() + coverPath.section('/', -1))), mime, coverData); // Write the html for cover. writeCoverImage(epubFile, coverPath.section('/', -1)); return KoFilter::OK; } void ExportEpub2::writeCoverImage(EpubFile *epubFile, const QString coverPath) { QByteArray coverHtmlContent; QBuffer buff(&coverHtmlContent); KoXmlWriter writer(&buff); writer.startDocument(nullptr, nullptr, nullptr); //xml version, etc... writer.startElement("html"); writer.addAttribute("xmlns", "http://www.w3.org/1999/xhtml"); writer.addAttribute("xml:lang", "en"); writer.startElement("head"); writer.startElement("meta"); writer.addAttribute("http-equiv", "Content-Type"); writer.addAttribute("content", "text/html; charset=UTF-8"); writer.endElement(); writer.startElement("title"); writer.addTextNode("Cover"); writer.endElement(); writer.startElement("style"); writer.addAttribute("type", "text/css"); writer.addAttribute("title", "override_css"); writer.addTextNode("\n"); writer.addTextNode(" @page { padding:Opt; margin:Opt } \n"); writer.addTextNode(" body { text-align:center; padding:Opt; margin:Opt } \n"); writer.addTextNode(" img { padding:Opt; margin:Opt; max-height: 100% ; max-width: 100% } \n"); writer.endElement(); //style writer.endElement(); // head writer.startElement("body"); writer.startElement("div"); writer.addAttribute("id", "cover-image"); writer.startElement("img"); writer.addAttribute("src", coverPath); writer.addAttribute("alt", "Cover Image"); writer.endElement(); writer.endElement(); // div writer.endElement(); // body writer.endElement(); // html // Add cover html to content epubFile->addContentFile(QString("cover"), QString(epubFile->pathPrefix() + "cover.xhtml") , "application/xhtml+xml", coverHtmlContent, QString("Cover")); } diff --git a/filters/words/epub/exporthtml.cpp b/filters/words/epub/exporthtml.cpp index b26b67e241c..068b8594e47 100644 --- a/filters/words/epub/exporthtml.cpp +++ b/filters/words/epub/exporthtml.cpp @@ -1,408 +1,407 @@ /* This file is part of the KDE project Copyright (C) 2012 Mojtaba Shahi Senobari Copyright (C) 2012 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "exporthtml.h" // Qt #include #include #include // KF5 #include // Calligra #include #include #include // This plugin #include "OdfParser.h" #include "OdtHtmlConverter.h" #include "HtmlFile.h" #include "HtmlExportDebug.h" #include "WmfPainterBackend.h" #include "EmfParser.h" #include "EmfOutputPainterStrategy.h" -#include "EmfOutputDebugStrategy.h" #include "SvmParser.h" #include "SvmPainterBackend.h" K_PLUGIN_FACTORY_WITH_JSON(ExportHtmlFactory, "calligra_filter_odt2html.json", registerPlugin();) // Needed to instantiate the plugin factory. #include "exporthtml.moc" ExportHtml::ExportHtml(QObject *parent, const QVariantList&) : KoFilter(parent) { } ExportHtml::~ExportHtml() { } KoFilter::ConversionStatus ExportHtml::convert(const QByteArray &from, const QByteArray &to) { // Check mimetypes if (from != "application/vnd.oasis.opendocument.text" || to != "text/html") { return KoFilter::NotImplemented; } // Open the infile and return an error if it fails. KoStore *odfStore = KoStore::createStore(m_chain->inputFile(), KoStore::Read, "", KoStore::Auto); if (!odfStore->open("mimetype")) { errorHtml << "Unable to open input file!" << endl; delete odfStore; return KoFilter::FileNotFound; } odfStore->close(); // Start the conversion KoFilter::ConversionStatus status; // ---------------------------------------------------------------- // Parse input files OdfParser odfParser; // Parse meta.xml into m_metadata status = odfParser.parseMetadata(odfStore, m_metadata); if (status != KoFilter::OK) { delete odfStore; return status; } // Parse manifest status = odfParser.parseManifest(odfStore, m_manifest); if (status != KoFilter::OK) { delete odfStore; return status; } // ---------------------------------------------------------------- // Create content files. // Create html contents. // m_imagesSrcList is an output parameter from the conversion. HtmlFile html; html.setPathPrefix("./"); const QString outputFileName = m_chain->outputFile().section('/', -1); const int dotPosition = outputFileName.indexOf('.'); html.setFilePrefix(outputFileName.left(dotPosition)); html.setFileSuffix(dotPosition != -1 ? outputFileName.mid(dotPosition) : QString()); OdtHtmlConverter converter; OdtHtmlConverter::ConversionOptions options = { false, // don't put styles in css file false, // don't break into chapters false // It is not mobi. }; QHash mediaFilesList; status = converter.convertContent(odfStore, m_metadata, &m_manifest, &options, &html, m_imagesSrcList, mediaFilesList); if (status != KoFilter::OK) { delete odfStore; return status; } // Extract images status = extractImages(odfStore, &html); if (status != KoFilter::OK) { delete odfStore; return status; } // ---------------------------------------------------------------- // Write the finished html file to disk html.writeHtml(m_chain->outputFile()); delete odfStore; return KoFilter::OK; } KoFilter::ConversionStatus ExportHtml::extractImages(KoStore *odfStore, HtmlFile *htmlFile) { // Extract images and add them to htmlFile one by one QByteArray imgContent; int imgId = 1; foreach (const QString &imgSrc, m_imagesSrcList.keys()) { debugHtml << imgSrc; if (!odfStore->extractFile(imgSrc, imgContent)) { debugHtml << "Can not to extract file"; return KoFilter::FileNotFound; } #if 1 htmlFile->addContentFile(("image" + QString::number(imgId)), // id (htmlFile->filePrefix() + imgSrc.section('/', -1)), // filename m_manifest.value(imgSrc).toUtf8(), imgContent); #else VectorType type = vectorType(imgContent); QSizeF qSize = m_imagesSrcList.value(imgSrc); switch (type) { case ExportHtml::VectorTypeSvm: { debugHtml << "Svm file"; QSize size(qSize.width(), qSize.height()); QByteArray output; if (!convertSvm(imgContent, output, size)) { debugHtml << "Svm Parse error"; return KoFilter::ParsingError; } epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output); break; } case ExportHtml::VectorTypeEmf: { debugHtml << "EMF file"; QSize size(qSize.width(), qSize.height()); QByteArray output; if (!convertEmf(imgContent, output, size)) { debugHtml << "EMF Parse error"; return KoFilter::ParsingError; } epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output); break; } case ExportHtml::VectorTypeWmf: { debugHtml << "WMF file"; QByteArray output; if (!convertWmf(imgContent, output, qSize)) { debugHtml << "WMF Parse error"; return KoFilter::ParsingError; } epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output); break; } // If it's not one of the types we can convert, let's just // assume that the image can be used as it is. The user // will find out soon anyway when s/he tries to look at // the image. case ExportHtml::VectorTypeOther: { debugHtml << "Other file"; epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), m_manifest.value(imgSrc).toUtf8(), imgContent); break; } default: debugHtml << ""; } #endif } return KoFilter::OK; } #if 0 bool ExportHtml::convertSvm(QByteArray &input, QByteArray &output, QSize size) { QBuffer *outBuf = new QBuffer(&output); QSvgGenerator generator; generator.setOutputDevice(outBuf); generator.setSize(QSize(200, 200)); generator.setTitle("Svg image"); generator.setDescription("This is an svg image that is converted from svm by Calligra"); Libsvm::SvmParser svmParser; QPainter painter; if (!painter.begin(&generator)) { debugHtml << "Can not open the painter"; return false; } painter.scale(50,50); Libsvm::SvmPainterBackend svmPainterBackend(&painter, size); svmParser.setBackend(&svmPainterBackend); if (!svmParser.parse(input)) { debugHtml << "Can not Parse the Svm file"; return false; } painter.end(); return true; } bool ExportHtml::convertEmf(QByteArray &input, QByteArray &output, QSize size) { QBuffer *outBuf = new QBuffer(&output); QSvgGenerator generator; generator.setOutputDevice(outBuf); generator.setSize(QSize(200, 200)); generator.setTitle("Svg image"); generator.setDescription("This is an svg image that is converted from EMF by Calligra"); Libemf::Parser emfParser; QPainter painter; if (!painter.begin(&generator)) { debugHtml << "Can not open the painter"; return false; } painter.scale(50,50); Libemf::OutputPainterStrategy emfPaintOutput(painter, size, true ); emfParser.setOutput( &emfPaintOutput ); if (!emfParser.load(input)) { debugHtml << "Can not Parse the EMF file"; return false; } painter.end(); return true; } bool ExportHtml::convertWmf(QByteArray &input, QByteArray &output, QSizeF size) { QBuffer *outBuf = new QBuffer(&output); QSvgGenerator generator; generator.setOutputDevice(outBuf); generator.setSize(QSize(200, 200)); generator.setTitle("Svg image"); generator.setDescription("This is an svg image that is converted from WMF by Calligra"); QPainter painter; if (!painter.begin(&generator)) { debugHtml << "Can not open the painter"; return false; } painter.scale(50,50); Libwmf::WmfPainterBackend wmfPainter(&painter, size); if (!wmfPainter.load(input)) { debugHtml << "Can not Parse the WMF file"; return false; } // Actually paint the WMF. painter.save(); wmfPainter.play(); painter.restore(); painter.end(); return true; } // ---------------------------------------------------------------- // These functions were taken from the vector shape. ExportHtml::VectorType ExportHtml::vectorType(QByteArray &content) { if (isSvm(content)) return ExportHtml::VectorTypeSvm; if (isEmf(content)) return ExportHtml::VectorTypeEmf; if (isWmf(content)) return ExportHtml::VectorTypeWmf; return ExportHtml::VectorTypeOther; } bool ExportHtml::isSvm(QByteArray &content) { if (content.startsWith("VCLMTF")) return true; return false; } bool ExportHtml::isEmf(QByteArray &content) { const char *data = content.constData(); const int size = content.count(); // This is how the 'file' command identifies an EMF. // 1. Check type int offset = 0; int result = (int) data[offset]; result |= (int) data[offset+1] << 8; result |= (int) data[offset+2] << 16; result |= (int) data[offset+3] << 24; qint32 mark = result; if (mark != 0x00000001) { return false; } // 2. An EMF has the string " EMF" at the start + offset 40. if (size > 44 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F'){ return true; } return false; } bool ExportHtml::isWmf(QByteArray &content) { const char *data = content.constData(); const int size = content.count(); if (size < 10) return false; // This is how the 'file' command identifies a WMF. if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232'){ return true; } if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){ return true; } if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){ return true; } return false; } #endif diff --git a/libs/vectorimage/CMakeLists.txt b/libs/vectorimage/CMakeLists.txt index e656d72855c..2d8b073a2d7 100644 --- a/libs/vectorimage/CMakeLists.txt +++ b/libs/vectorimage/CMakeLists.txt @@ -1,42 +1,52 @@ include_directories( ${VECTORIMAGE_INCLUDES} ) set(vectorimage_LIB_SRCS libemf/EmfRecords.cpp libemf/EmfObjects.cpp libemf/EmfHeader.cpp libemf/BitmapHeader.cpp libemf/Bitmap.cpp libemf/EmfParser.cpp libemf/EmfOutput.cpp - libemf/EmfOutputDebugStrategy.cpp libemf/EmfOutputPainterStrategy.cpp libsvm/SvmStructs.cpp libsvm/SvmGraphicsContext.cpp libsvm/SvmParser.cpp libsvm/SvmPainterBackend.cpp libwmf/WmfStack.cpp libwmf/WmfDeviceContext.cpp libwmf/WmfParser.cpp libwmf/WmfAbstractBackend.cpp libwmf/WmfPainterBackend.cpp libwmf/WmfWriter.cpp VectorImageDebug.cpp ) +option(LIBEMF_DEBUG "Enable the kovectorimage emf debug strategy. Usually you'll want to leave this development support option off." OFF) +if(LIBEMF_DEBUG) +set(vectorimage_LIB_SRCS + ${vectorimage_LIB_SRCS} + libemf/EmfOutputDebugStrategy.cpp + ) +endif() + add_library(kovectorimage SHARED ${vectorimage_LIB_SRCS}) generate_export_header(kovectorimage BASE_NAME kovectorimage) target_link_libraries(kovectorimage PUBLIC Qt5::Gui PRIVATE Qt5::PrintSupport ) set_target_properties(kovectorimage PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) +if(LIBEMF_DEBUG) + target_compile_definitions(kovectorimage PUBLIC -DLIBEMF_DEBUG=true) +endif() install(TARGETS kovectorimage ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/plugins/vectorshape/VectorShape.cpp b/plugins/vectorshape/VectorShape.cpp index ddaf2bec70a..6fd05fd4839 100644 --- a/plugins/vectorshape/VectorShape.cpp +++ b/plugins/vectorshape/VectorShape.cpp @@ -1,505 +1,505 @@ /* This file is part of the KDE project * * Copyright (C) 2009 - 2011 Inge Wallin * Copyright (C) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "VectorShape.h" // Posix #include // Qt #include #include #include #include #include #include #include #include // Calligra #include "KoUnit.h" #include "KoStore.h" #include "KoXmlNS.h" #include "KoXmlReader.h" #include "KoXmlWriter.h" #include #include #include #include #include // Wmf support #include "WmfPainterBackend.h" // Vector shape #include "VectorDebug.h" #include "EmfParser.h" #include "EmfOutputPainterStrategy.h" #include "EmfOutputDebugStrategy.h" #include "SvmParser.h" #include "SvmPainterBackend.h" // Comment out to get uncached painting, which is good for debugging //#define VECTORSHAPE_PAINT_UNCACHED // Comment out to get unthreaded painting, which is good for debugging //#define VECTORSHAPE_PAINT_UNTHREADED VectorShape::VectorShape() : KoFrameShape( KoXmlNS::draw, "image" ) , m_type(VectorTypeNone) , m_isRendering(false) { setShapeId(VectorShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 8 ), CM_TO_POINT( 5 ) ) ); m_cache.setMaxCost(3); } VectorShape::~VectorShape() { // Wait for the render-thread to finish before the shape is allowed to be // destroyed so we can make sure to prevent crashes or unwanted // side-effects. Maybe as alternate we could just kill the render-thread... QMutexLocker locker(&m_mutex); } // Methods specific to the vector shape. QByteArray VectorShape::compressedContents() const { return m_contents; } VectorShape::VectorType VectorShape::vectorType() const { return m_type; } void VectorShape::setCompressedContents(const QByteArray &newContents, VectorType vectorType) { QMutexLocker locker(&m_mutex); m_contents = newContents; m_type = vectorType; m_cache.clear(); update(); } // ---------------------------------------------------------------- // Painting RenderThread::RenderThread(const QByteArray &contents, VectorShape::VectorType type, const QSizeF &size, const QSize &boundingSize, qreal zoomX, qreal zoomY) : QObject(), QRunnable(), m_contents(contents), m_type(type), m_size(size), m_boundingSize(boundingSize), m_zoomX(zoomX), m_zoomY(zoomY) { setAutoDelete(true); } RenderThread::~RenderThread() { } void RenderThread::run() { QImage *image = new QImage(m_boundingSize, QImage::Format_ARGB32); image->fill(0); QPainter painter; if (!painter.begin(image)) { warnVector << "Failed to create image-cache"; delete image; image = 0; } else { painter.scale(m_zoomX, m_zoomY); draw(painter); painter.end(); } emit finished(m_boundingSize, image); } void RenderThread::draw(QPainter &painter) { // If the data is uninitialized, e.g. because loading failed, draw the null shape. if (m_contents.isEmpty()) { drawNull(painter); return; } // Actually draw the contents switch (m_type) { case VectorShape::VectorTypeWmf: drawWmf(painter); break; case VectorShape::VectorTypeEmf: drawEmf(painter); break; case VectorShape::VectorTypeSvm: drawSvm(painter); break; case VectorShape::VectorTypeSvg: drawSvg(painter); break; case VectorShape::VectorTypeNone: default: drawNull(painter); } } void RenderThread::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), m_size); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(rect); painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } void RenderThread::drawWmf(QPainter &painter) const { Libwmf::WmfPainterBackend wmfPainter(&painter, m_size); if (!wmfPainter.load(m_contents)) { drawNull(painter); return; } painter.save(); // Actually paint the WMF. wmfPainter.play(); painter.restore(); } void RenderThread::drawEmf(QPainter &painter) const { // FIXME: Make emfOutput use QSizeF QSize shapeSizeInt( m_size.width(), m_size.height() ); //debugVector << "-------------------------------------------"; //debugVector << "size: " << shapeSizeInt << m_size; //debugVector << "position: " << position(); //debugVector << "-------------------------------------------"; Libemf::Parser emfParser; -#if 1 // Set to 0 to get debug output +#ifndef LIBEMF_DEBUG // Create a new painter output strategy. Last param = true means keep aspect ratio. Libemf::OutputPainterStrategy emfPaintOutput( painter, shapeSizeInt, true ); emfParser.setOutput( &emfPaintOutput ); #else Libemf::OutputDebugStrategy emfDebugOutput; emfParser.setOutput( &emfDebugOutput ); #endif emfParser.load(m_contents); } void RenderThread::drawSvm(QPainter &painter) const { QSize shapeSizeInt( m_size.width(), m_size.height() ); Libsvm::SvmParser svmParser; // Create a new painter backend. Libsvm::SvmPainterBackend svmPaintOutput(&painter, shapeSizeInt); svmParser.setBackend(&svmPaintOutput); svmParser.parse(m_contents); } void RenderThread::drawSvg(QPainter &painter) const { QSvgRenderer renderer(m_contents); renderer.render(&painter, QRectF(0, 0, m_size.width(), m_size.height())); } void VectorShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { #ifdef VECTORSHAPE_PAINT_UNCACHED bool useCache = false; #else bool useCache = true; #endif #ifdef VECTORSHAPE_PAINT_UNTHREADED bool asynchronous = false; #else // Since the backends may use QPainter::drawText we need to make sure to only // use threads if the font-backend supports that what is in most cases. bool asynchronous = QFontDatabase::supportsThreadedFontRendering(); #endif QImage *cache = render(converter, asynchronous, useCache); if (cache) { // paint cached image Q_ASSERT(!cache->isNull()); QVector clipRects = painter.clipRegion().rects(); foreach (const QRect &rc, clipRects) { painter.drawImage(rc.topLeft(), *cache, rc); } } } void VectorShape::renderFinished(const QSize &boundingSize, QImage *image) { if (image) { m_cache.insert(boundingSize.height(), image); update(); } m_isRendering = false; } // ---------------------------------------------------------------- // Loading and Saving void VectorShape::saveOdf(KoShapeSavingContext & context) const { QMutexLocker locker(&m_mutex); KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &xmlWriter = context.xmlWriter(); QString fileName = fileSaver.getFilename("VectorImages/Image"); QByteArray mimeType; switch (m_type) { case VectorTypeWmf: mimeType = "image/x-wmf"; break; case VectorTypeEmf: mimeType = "image/x-emf"; break; case VectorTypeSvm: mimeType = "image/x-svm"; // mimetype as used inside LO/AOO break; case VectorTypeSvg: mimeType = "image/svg+xml"; default: // FIXME: What here? mimeType = "application/x-what"; break; } xmlWriter.startElement("draw:frame"); saveOdfAttributes(context, OdfAllAttributes); fileSaver.embedFile(xmlWriter, "draw:image", fileName, mimeType, qUncompress(m_contents)); xmlWriter.endElement(); // draw:frame } bool VectorShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { //debugVector <<"Loading ODF frame in the vector shape. Element = " << element.tagName(); loadOdfAttributes(element, context, OdfAllAttributes); return loadOdfFrame(element, context); } inline static int read32(const char *buffer, const int offset) { // little endian int result = (int) buffer[offset]; result |= (int) buffer[offset+1] << 8; result |= (int) buffer[offset+2] << 16; result |= (int) buffer[offset+3] << 24; return result; } // Load the actual contents within the vector shape. bool VectorShape::loadOdfFrameElement(const KoXmlElement & element, KoShapeLoadingContext &context) { //debugVector <<"Loading ODF element: " << element.tagName(); QMutexLocker locker(&m_mutex); // Get the reference to the vector file. If there is no href, then just return. const QString href = element.attribute("href"); if (href.isEmpty()) return false; // Try to open the embedded file. KoStore *store = context.odfLoadingContext().store(); bool result = store->open(href); if (!result) { return false; } int size = store->size(); if (size < 88) { store->close(); return false; } m_contents = store->read(size); store->close(); if (m_contents.count() < size) { debugVector << "Too few bytes read: " << m_contents.count() << " instead of " << size; return false; } // Try to recognize the type. We should do this before the // compression below, because that's a semi-expensive operation. m_type = vectorType(m_contents); // Return false if we didn't manage to identify the type. if (m_type == VectorTypeNone) return false; // Compress for biiiig memory savings. m_contents = qCompress(m_contents); return true; } void VectorShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { render(converter, asynchronous, true); } QImage* VectorShape::render(const KoViewConverter &converter, bool asynchronous, bool useCache) const { QRectF rect = converter.documentToView(boundingRect()); int id = rect.size().toSize().height(); QImage *cache = useCache ? m_cache[id] : 0; if (!cache || cache->isNull()) { // recreate the cached image cache = 0; if (!m_isRendering) { m_isRendering = true; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); QMutexLocker locker(&m_mutex); const QByteArray uncompressedContents = m_type != VectorShape::VectorTypeNone ? qUncompress(m_contents) : QByteArray(); RenderThread *t = new RenderThread(uncompressedContents, m_type, size(), rect.size().toSize(), zoomX, zoomY); connect(t, SIGNAL(finished(QSize,QImage*)), this, SLOT(renderFinished(QSize,QImage*))); if (asynchronous) { // render and paint the image threaded QThreadPool::globalInstance()->start(t); } else { // non-threaded rendering and painting of the image t->run(); cache = m_cache[id]; } } } return cache; } VectorShape::VectorType VectorShape::vectorType(const QByteArray &newContents) { VectorType vectorType; if (isWmf(newContents)) { vectorType = VectorShape::VectorTypeWmf; } else if (isEmf(newContents)) { vectorType = VectorShape::VectorTypeEmf; } else if (isSvm(newContents)) { vectorType = VectorShape::VectorTypeSvm; } else if (isSvg(newContents)) { vectorType = VectorShape::VectorTypeSvg; } else { vectorType = VectorShape::VectorTypeNone; } return vectorType; } bool VectorShape::isWmf(const QByteArray &bytes) { debugVector << "Check for WMF"; const char *data = bytes.constData(); const int size = bytes.count(); if (size < 10) return false; // This is how the 'file' command identifies a WMF. if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232') { // FIXME: Is this a compressed wmf? Check it up. debugVector << "WMF identified: header 1"; return true; } if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') { debugVector << "WMF identified: header 2"; return true; } if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') { debugVector << "WMF identified: header 3"; return true; } return false; } bool VectorShape::isEmf(const QByteArray &bytes) { debugVector << "Check for EMF"; const char *data = bytes.constData(); const int size = bytes.count(); // This is how the 'file' command identifies an EMF. // 1. Check type qint32 mark = read32(data, 0); if (mark != 0x00000001) { //debugVector << "Not an EMF: mark = " << mark << " instead of 0x00000001"; return false; } // 2. An EMF has the string " EMF" at the start + offset 40. if (size > 44 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F') { debugVector << "EMF identified"; return true; } return false; } bool VectorShape::isSvm(const QByteArray &bytes) { debugVector << "Check for SVM"; // Check the SVM signature. if (bytes.startsWith("VCLMTF")) { debugVector << "SVM identified"; return true; } return false; } bool VectorShape::isSvg(const QByteArray &bytes) { debugVector << "Check for SVG"; return (bytes.contains("svg")); }