diff --git a/src/dialog/vexportdialog.cpp b/src/dialog/vexportdialog.cpp index 4961cff8..dc010b43 100644 --- a/src/dialog/vexportdialog.cpp +++ b/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(), diff --git a/src/dialog/vexportdialog.h b/src/dialog/vexportdialog.h index d9d33b19..ed8b7109 100644 --- a/src/dialog/vexportdialog.h +++ b/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; diff --git a/src/utils/vwebutils.cpp b/src/utils/vwebutils.cpp index addf6111..fdb9af63 100644 --- a/src/utils/vwebutils.cpp +++ b/src/utils/vwebutils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #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; +} diff --git a/src/utils/vwebutils.h b/src/utils/vwebutils.h index dd1a368b..f488b43b 100644 --- a/src/utils/vwebutils.h +++ b/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 { diff --git a/src/vexporter.cpp b/src/vexporter.cpp index e0985cd5..57864bb2 100644 --- a/src/vexporter.cpp +++ b/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("]*)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("").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); diff --git a/src/vexporter.h b/src/vexporter.h index 0afa9a76..edd85d02 100644 --- a/src/vexporter.h +++ b/src/vexporter.h @@ -133,24 +133,33 @@ private: int startProcess(const QString &p_cmd); + // @p_embedImages: embed 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 . // 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 . + static bool embedBodyResources(const QUrl &p_baseUrl, QString &p_html); + static QString getResourceRelativePath(const QString &p_file); QPageLayout m_pageLayout;