Browse Source

embed images in exported html

Le Tan 7 years ago
parent
commit
cba8b1c047
6 changed files with 155 additions and 8 deletions
  1. 16 0
      src/dialog/vexportdialog.cpp
  2. 7 1
      src/dialog/vexportdialog.h
  3. 38 0
      src/utils/vwebutils.cpp
  4. 3 0
      src/utils/vwebutils.h
  5. 81 6
      src/vexporter.cpp
  6. 10 1
      src/vexporter.h

+ 16 - 0
src/dialog/vexportdialog.cpp

@@ -313,11 +313,23 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
     m_embedStyleCB = new QCheckBox(tr("Embed CSS styles"), this);
     m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file"));
 
+    // Embed images as data URI.
+    m_embedImagesCB = new QCheckBox(tr("Embed images"), this);
+    m_embedImagesCB->setToolTip(tr("Embed images as data URI"));
+
     // Complete HTML.
     m_completeHTMLCB = new QCheckBox(tr("Complete page"), this);
     m_completeHTMLCB->setToolTip(tr("Export the whole web page along with pictures "
                                     "which may not keep the HTML link structure of "
                                     "the original page"));
+    connect(m_completeHTMLCB, &QCheckBox::stateChanged,
+            this, [this](int p_state) {
+                bool checked = p_state == Qt::Checked;
+                m_embedImagesCB->setEnabled(checked);
+                if (!checked) {
+                    m_embedImagesCB->setChecked(false);
+                }
+            });
 
     // Mime HTML.
     m_mimeHTMLCB = new QCheckBox(tr("MIME HTML"), this);
@@ -332,6 +344,7 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
     QFormLayout *advLayout = new QFormLayout();
     advLayout->addRow(m_embedStyleCB);
     advLayout->addRow(m_completeHTMLCB);
+    advLayout->addRow(m_embedImagesCB);
     advLayout->addRow(m_mimeHTMLCB);
 
     advLayout->setContentsMargins(0, 0, 0, 0);
@@ -435,6 +448,8 @@ void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
 
     m_completeHTMLCB->setChecked(s_opt.m_htmlOpt.m_completeHTML);
 
+    m_embedImagesCB->setChecked(s_opt.m_htmlOpt.m_embedImages);
+
     m_mimeHTMLCB->setChecked(s_opt.m_htmlOpt.m_mimeHTML);
 
     m_tableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_enableTableOfContents);
@@ -534,6 +549,7 @@ void VExportDialog::startExport()
                                          m_wkExtraArgsEdit->text()),
                          ExportHTMLOption(m_embedStyleCB->isChecked(),
                                           m_completeHTMLCB->isChecked(),
+                                          m_embedImagesCB->isChecked(),
                                           m_mimeHTMLCB->isChecked()),
                          ExportCustomOption((ExportCustomOption::SourceFormat)
                                             m_customSrcFormatCB->currentData().toInt(),

+ 7 - 1
src/dialog/vexportdialog.h

@@ -57,21 +57,25 @@ struct ExportHTMLOption
     ExportHTMLOption()
         : m_embedCssStyle(true),
           m_completeHTML(true),
+          m_embedImages(true),
           m_mimeHTML(false)
     {
     }
 
     ExportHTMLOption(bool p_embedCssStyle,
                      bool p_completeHTML,
+                     bool p_embedImages,
                      bool p_mimeHTML)
         : m_embedCssStyle(p_embedCssStyle),
           m_completeHTML(p_completeHTML),
+          m_embedImages(p_embedImages),
           m_mimeHTML(p_mimeHTML)
     {
     }
 
     bool m_embedCssStyle;
     bool m_completeHTML;
+    bool m_embedImages;
     bool m_mimeHTML;
 };
 
@@ -431,7 +435,9 @@ private:
 
     QCheckBox *m_embedStyleCB;
 
-    QCheckBox *m_completeHTMLCB;;
+    QCheckBox *m_completeHTMLCB;
+
+    QCheckBox *m_embedImagesCB;
 
     QCheckBox *m_mimeHTMLCB;
 

+ 38 - 0
src/utils/vwebutils.cpp

@@ -5,6 +5,7 @@
 #include <QDebug>
 #include <QDir>
 #include <QUrl>
+#include <QImageReader>
 
 #include "vpalette.h"
 #include "vconfigmanager.h"
@@ -924,3 +925,40 @@ QString VWebUtils::copyResource(const QUrl &p_url, const QString &p_folder) cons
 
     return succ ? targetFile : QString();
 }
+
+QString VWebUtils::dataURI(const QUrl &p_url) const
+{
+    QString uri;
+    Q_ASSERT(!p_url.isRelative());
+    QString file = p_url.isLocalFile() ? p_url.toLocalFile() : p_url.toString();
+    QString suffix(QFileInfo(file).suffix().toLower());
+
+    if (!QImageReader::supportedImageFormats().contains(suffix.toLatin1())) {
+        return uri;
+    }
+
+    QByteArray data;
+    if (p_url.scheme() == "https" || p_url.scheme() == "http") {
+        // Download it.
+        data = VDownloader::downloadSync(p_url);
+    } else if (QFileInfo::exists(file)) {
+        QFile fi(file);
+        if (fi.open(QIODevice::ReadOnly)) {
+            data = fi.readAll();
+            fi.close();
+        }
+    }
+
+    if (data.isEmpty()) {
+        return uri;
+    }
+
+    if (suffix == "svg") {
+        uri = QString("data:image/svg+xml;utf8,%1").arg(QString::fromUtf8(data));
+        uri.replace('\r', "").replace('\n', "");
+    } else {
+        uri = QString("data:image/%1;base64,%2").arg(suffix).arg(QString::fromUtf8(data.toBase64()));
+    }
+
+    return uri;
+}

+ 3 - 0
src/utils/vwebutils.h

@@ -25,6 +25,9 @@ public:
     // Return the target file path on success or empty string on failure.
     QString copyResource(const QUrl &p_url, const QString &p_folder) const;
 
+    // Return a dataURI of @p_url if it is an image.
+    QString dataURI(const QUrl &p_url) const;
+
 private:
     struct CopyTargetAction
     {

+ 81 - 6
src/vexporter.cpp

@@ -336,7 +336,8 @@ bool VExporter::exportToPDFViaWK(VDocument *p_webDocument,
                                       p_styleContent,
                                       p_bodyContent,
                                       true,
-                                      true)) {
+                                      true,
+                                      false)) {
                     pdfExported = -1;
                     return;
                 }
@@ -395,7 +396,8 @@ bool VExporter::exportToCustom(VDocument *p_webDocument,
                                       p_styleContent,
                                       p_bodyContent,
                                       true,
-                                      true)) {
+                                      true,
+                                      false)) {
                     exported = -1;
                     return;
                 }
@@ -561,7 +563,8 @@ bool VExporter::exportToHTML(VDocument *p_webDocument,
                                       p_styleContent,
                                       p_bodyContent,
                                       p_opt.m_embedCssStyle,
-                                      p_opt.m_completeHTML)) {
+                                      p_opt.m_completeHTML,
+                                      p_opt.m_embedImages)) {
                     htmlExported = -1;
                     return;
                 }
@@ -610,6 +613,33 @@ bool VExporter::fixStyleResources(const QString &p_folder,
     return altered;
 }
 
+bool VExporter::embedStyleResources(QString &p_html)
+{
+    bool altered = false;
+    QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);");
+
+    int pos = 0;
+    while (pos < p_html.size()) {
+        int idx = p_html.indexOf(reg, pos);
+        if (idx == -1) {
+            break;
+        }
+
+        QString dataURI = g_webUtils->dataURI(QUrl(reg.cap(1)));
+        if (dataURI.isEmpty()) {
+            pos = idx + reg.matchedLength();
+        } else {
+            // Replace the url string in html.
+            QString newUrl = QString("url('%1');").arg(dataURI);
+            p_html.replace(idx, reg.matchedLength(), newUrl);
+            pos = idx + newUrl.size();
+            altered = true;
+        }
+    }
+
+    return altered;
+}
+
 bool VExporter::fixBodyResources(const QUrl &p_baseUrl,
                                  const QString &p_folder,
                                  QString &p_html)
@@ -651,6 +681,45 @@ bool VExporter::fixBodyResources(const QUrl &p_baseUrl,
     return altered;
 }
 
+bool VExporter::embedBodyResources(const QUrl &p_baseUrl, QString &p_html)
+{
+    bool altered = false;
+    if (p_baseUrl.isEmpty()) {
+        return altered;
+    }
+
+    QRegExp reg("<img ([^>]*)src=\"([^\"]+)\"([^>]*)>");
+
+    int pos = 0;
+    while (pos < p_html.size()) {
+        int idx = p_html.indexOf(reg, pos);
+        if (idx == -1) {
+            break;
+        }
+
+        if (reg.cap(2).isEmpty()) {
+            pos = idx + reg.matchedLength();
+            continue;
+        }
+
+        QUrl srcUrl(p_baseUrl.resolved(reg.cap(2)));
+        QString dataURI = g_webUtils->dataURI(srcUrl);
+        if (dataURI.isEmpty()) {
+            pos = idx + reg.matchedLength();
+        } else {
+            // Replace the url string in html.
+            QString newUrl = QString("<img %1src='%2'%3>").arg(reg.cap(1))
+                                                          .arg(dataURI)
+                                                          .arg(reg.cap(3));
+            p_html.replace(idx, reg.matchedLength(), newUrl);
+            pos = idx + newUrl.size();
+            altered = true;
+        }
+    }
+
+    return altered;
+}
+
 QString VExporter::getResourceRelativePath(const QString &p_file)
 {
     int idx = p_file.lastIndexOf('/');
@@ -901,7 +970,8 @@ bool VExporter::outputToHTMLFile(const QString &p_file,
                                  const QString &p_styleContent,
                                  const QString &p_bodyContent,
                                  bool p_embedCssStyle,
-                                 bool p_completeHTML)
+                                 bool p_completeHTML,
+                                 bool p_embedImages)
 {
     QFile file(p_file);
     if (!file.open(QFile::WriteOnly)) {
@@ -916,7 +986,7 @@ bool VExporter::outputToHTMLFile(const QString &p_file,
     QString html(m_exportHtmlTemplate);
     if (!p_styleContent.isEmpty() && p_embedCssStyle) {
         QString content(p_styleContent);
-        fixStyleResources(resFolderPath, content);
+        embedStyleResources(content);
         html.replace(HtmlHolder::c_styleHolder, content);
     }
 
@@ -926,7 +996,12 @@ bool VExporter::outputToHTMLFile(const QString &p_file,
 
     if (p_completeHTML) {
         QString content(p_bodyContent);
-        fixBodyResources(m_baseUrl, resFolderPath, content);
+        if (p_embedImages) {
+            embedBodyResources(m_baseUrl, content);
+        } else {
+            fixBodyResources(m_baseUrl, resFolderPath, content);
+        }
+
         html.replace(HtmlHolder::c_bodyHolder, content);
     } else {
         html.replace(HtmlHolder::c_bodyHolder, p_bodyContent);

+ 10 - 1
src/vexporter.h

@@ -133,24 +133,33 @@ private:
 
     int startProcess(const QString &p_cmd);
 
+    // @p_embedImages: embed <img> as data URI.
     bool outputToHTMLFile(const QString &p_file,
                           const QString &p_headContent,
                           const QString &p_styleContent,
                           const QString &p_bodyContent,
                           bool p_embedCssStyle,
-                          bool p_completeHTML);
+                          bool p_completeHTML,
+                          bool p_embedImages);
 
     // Fix @p_html's resources like url("...") with "file" or "qrc" schema.
     // Copy the resource to @p_folder and fix the url string.
     static bool fixStyleResources(const QString &p_folder,
                                   QString &p_html);
 
+    // Fix @p_html's resources like url("...") with "file" or "qrc" schema.
+    // Embed the image data in data URIs.
+    static bool embedStyleResources(QString &p_html);
+
     // Fix @p_html's resources like <img>.
     // Copy the resource to @p_folder and fix the url string.
     static bool fixBodyResources(const QUrl &p_baseUrl,
                                  const QString &p_folder,
                                  QString &p_html);
 
+    // Embed @p_html's resources like <img>.
+    static bool embedBodyResources(const QUrl &p_baseUrl, QString &p_html);
+
     static QString getResourceRelativePath(const QString &p_file);
 
     QPageLayout m_pageLayout;