embed images in exported html

This commit is contained in:
Le Tan 2018-04-24 07:51:57 +08:00
parent 26ecf08f4a
commit cba8b1c047
6 changed files with 155 additions and 8 deletions

View File

@ -313,11 +313,23 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
m_embedStyleCB = new QCheckBox(tr("Embed CSS styles"), this); m_embedStyleCB = new QCheckBox(tr("Embed CSS styles"), this);
m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file")); 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. // Complete HTML.
m_completeHTMLCB = new QCheckBox(tr("Complete page"), this); m_completeHTMLCB = new QCheckBox(tr("Complete page"), this);
m_completeHTMLCB->setToolTip(tr("Export the whole web page along with pictures " m_completeHTMLCB->setToolTip(tr("Export the whole web page along with pictures "
"which may not keep the HTML link structure of " "which may not keep the HTML link structure of "
"the original page")); "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. // Mime HTML.
m_mimeHTMLCB = new QCheckBox(tr("MIME HTML"), this); m_mimeHTMLCB = new QCheckBox(tr("MIME HTML"), this);
@ -332,6 +344,7 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
QFormLayout *advLayout = new QFormLayout(); QFormLayout *advLayout = new QFormLayout();
advLayout->addRow(m_embedStyleCB); advLayout->addRow(m_embedStyleCB);
advLayout->addRow(m_completeHTMLCB); advLayout->addRow(m_completeHTMLCB);
advLayout->addRow(m_embedImagesCB);
advLayout->addRow(m_mimeHTMLCB); advLayout->addRow(m_mimeHTMLCB);
advLayout->setContentsMargins(0, 0, 0, 0); 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_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_mimeHTMLCB->setChecked(s_opt.m_htmlOpt.m_mimeHTML);
m_tableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_enableTableOfContents); m_tableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_enableTableOfContents);
@ -534,6 +549,7 @@ void VExportDialog::startExport()
m_wkExtraArgsEdit->text()), m_wkExtraArgsEdit->text()),
ExportHTMLOption(m_embedStyleCB->isChecked(), ExportHTMLOption(m_embedStyleCB->isChecked(),
m_completeHTMLCB->isChecked(), m_completeHTMLCB->isChecked(),
m_embedImagesCB->isChecked(),
m_mimeHTMLCB->isChecked()), m_mimeHTMLCB->isChecked()),
ExportCustomOption((ExportCustomOption::SourceFormat) ExportCustomOption((ExportCustomOption::SourceFormat)
m_customSrcFormatCB->currentData().toInt(), m_customSrcFormatCB->currentData().toInt(),

View File

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

View File

@ -5,6 +5,7 @@
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QUrl> #include <QUrl>
#include <QImageReader>
#include "vpalette.h" #include "vpalette.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
@ -924,3 +925,40 @@ QString VWebUtils::copyResource(const QUrl &p_url, const QString &p_folder) cons
return succ ? targetFile : QString(); 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;
}

View File

@ -25,6 +25,9 @@ public:
// Return the target file path on success or empty string on failure. // Return the target file path on success or empty string on failure.
QString copyResource(const QUrl &p_url, const QString &p_folder) const; 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: private:
struct CopyTargetAction struct CopyTargetAction
{ {

View File

@ -336,7 +336,8 @@ bool VExporter::exportToPDFViaWK(VDocument *p_webDocument,
p_styleContent, p_styleContent,
p_bodyContent, p_bodyContent,
true, true,
true)) { true,
false)) {
pdfExported = -1; pdfExported = -1;
return; return;
} }
@ -395,7 +396,8 @@ bool VExporter::exportToCustom(VDocument *p_webDocument,
p_styleContent, p_styleContent,
p_bodyContent, p_bodyContent,
true, true,
true)) { true,
false)) {
exported = -1; exported = -1;
return; return;
} }
@ -561,7 +563,8 @@ bool VExporter::exportToHTML(VDocument *p_webDocument,
p_styleContent, p_styleContent,
p_bodyContent, p_bodyContent,
p_opt.m_embedCssStyle, p_opt.m_embedCssStyle,
p_opt.m_completeHTML)) { p_opt.m_completeHTML,
p_opt.m_embedImages)) {
htmlExported = -1; htmlExported = -1;
return; return;
} }
@ -610,6 +613,33 @@ bool VExporter::fixStyleResources(const QString &p_folder,
return altered; 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, bool VExporter::fixBodyResources(const QUrl &p_baseUrl,
const QString &p_folder, const QString &p_folder,
QString &p_html) QString &p_html)
@ -651,6 +681,45 @@ bool VExporter::fixBodyResources(const QUrl &p_baseUrl,
return altered; 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) QString VExporter::getResourceRelativePath(const QString &p_file)
{ {
int idx = p_file.lastIndexOf('/'); int idx = p_file.lastIndexOf('/');
@ -901,7 +970,8 @@ bool VExporter::outputToHTMLFile(const QString &p_file,
const QString &p_styleContent, const QString &p_styleContent,
const QString &p_bodyContent, const QString &p_bodyContent,
bool p_embedCssStyle, bool p_embedCssStyle,
bool p_completeHTML) bool p_completeHTML,
bool p_embedImages)
{ {
QFile file(p_file); QFile file(p_file);
if (!file.open(QFile::WriteOnly)) { if (!file.open(QFile::WriteOnly)) {
@ -916,7 +986,7 @@ bool VExporter::outputToHTMLFile(const QString &p_file,
QString html(m_exportHtmlTemplate); QString html(m_exportHtmlTemplate);
if (!p_styleContent.isEmpty() && p_embedCssStyle) { if (!p_styleContent.isEmpty() && p_embedCssStyle) {
QString content(p_styleContent); QString content(p_styleContent);
fixStyleResources(resFolderPath, content); embedStyleResources(content);
html.replace(HtmlHolder::c_styleHolder, content); html.replace(HtmlHolder::c_styleHolder, content);
} }
@ -926,7 +996,12 @@ bool VExporter::outputToHTMLFile(const QString &p_file,
if (p_completeHTML) { if (p_completeHTML) {
QString content(p_bodyContent); 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); html.replace(HtmlHolder::c_bodyHolder, content);
} else { } else {
html.replace(HtmlHolder::c_bodyHolder, p_bodyContent); html.replace(HtmlHolder::c_bodyHolder, p_bodyContent);

View File

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