mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
1054 lines
31 KiB
C++
1054 lines
31 KiB
C++
#include "vexporter.h"
|
|
|
|
#include <QDebug>
|
|
#include <QWidget>
|
|
#include <QWebChannel>
|
|
#include <QWebEngineProfile>
|
|
#include <QRegExp>
|
|
#include <QProcess>
|
|
#include <QTemporaryDir>
|
|
#include <QScopedPointer>
|
|
#include <QCoreApplication>
|
|
|
|
#include "vconfigmanager.h"
|
|
#include "vfile.h"
|
|
#include "vwebview.h"
|
|
#include "utils/vutils.h"
|
|
#include "vpreviewpage.h"
|
|
#include "vconstants.h"
|
|
#include "vmarkdownconverter.h"
|
|
#include "vdocument.h"
|
|
#include "utils/vwebutils.h"
|
|
|
|
extern VConfigManager *g_config;
|
|
|
|
extern VWebUtils *g_webUtils;
|
|
|
|
VExporter::VExporter(QWidget *p_parent)
|
|
: QObject(p_parent),
|
|
m_webViewer(NULL),
|
|
m_state(ExportState::Idle),
|
|
m_askedToStop(false)
|
|
{
|
|
}
|
|
|
|
static QString marginToStrMM(qreal p_margin)
|
|
{
|
|
return QString("%1mm").arg(p_margin);
|
|
}
|
|
|
|
void VExporter::prepareExport(const ExportOption &p_opt)
|
|
{
|
|
bool isPdf = p_opt.m_format == ExportFormat::PDF
|
|
|| p_opt.m_format == ExportFormat::OnePDF
|
|
|| (p_opt.m_format == ExportFormat::Custom
|
|
&& p_opt.m_customOpt.m_pdfLike);
|
|
bool extraToc = isPdf
|
|
&& !p_opt.m_pdfOpt.m_wkhtmltopdf
|
|
&& p_opt.m_pdfOpt.m_enableTableOfContents;
|
|
|
|
m_htmlTemplate = VUtils::generateHtmlTemplate(p_opt.m_renderer,
|
|
p_opt.m_renderBg,
|
|
p_opt.m_renderStyle,
|
|
p_opt.m_renderCodeBlockStyle,
|
|
isPdf,
|
|
isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf,
|
|
extraToc);
|
|
|
|
bool outline = p_opt.m_htmlOpt.m_outlinePanel
|
|
&& !isPdf
|
|
&& (p_opt.m_format == ExportFormat::HTML
|
|
|| p_opt.m_format == ExportFormat::Custom);
|
|
m_exportHtmlTemplate = VUtils::generateExportHtmlTemplate(p_opt.m_renderBg,
|
|
isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf,
|
|
outline);
|
|
|
|
m_pageLayout = *(p_opt.m_pdfOpt.m_layout);
|
|
|
|
prepareWKArguments(p_opt.m_pdfOpt);
|
|
}
|
|
|
|
void VExporter::prepareWKArguments(const ExportPDFOption &p_opt)
|
|
{
|
|
m_wkArgs.clear();
|
|
m_wkArgs << "--page-size" << m_pageLayout.pageSize().key();
|
|
m_wkArgs << "--orientation"
|
|
<< (m_pageLayout.orientation() == QPageLayout::Portrait ? "Portrait" : "Landscape");
|
|
|
|
QMarginsF marginsMM = m_pageLayout.margins(QPageLayout::Millimeter);
|
|
m_wkArgs << "--margin-bottom" << marginToStrMM(marginsMM.bottom());
|
|
m_wkArgs << "--margin-left" << marginToStrMM(marginsMM.left());
|
|
m_wkArgs << "--margin-right" << marginToStrMM(marginsMM.right());
|
|
m_wkArgs << "--margin-top" << marginToStrMM(marginsMM.top());
|
|
|
|
QString footer;
|
|
switch (p_opt.m_wkPageNumber) {
|
|
case ExportPageNumber::Left:
|
|
footer = "--footer-left";
|
|
break;
|
|
|
|
case ExportPageNumber::Center:
|
|
footer = "--footer-center";
|
|
break;
|
|
|
|
case ExportPageNumber::Right:
|
|
footer = "--footer-right";
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!footer.isEmpty()) {
|
|
m_wkArgs << footer << "[page]"
|
|
<< "--footer-spacing" << QString::number(marginsMM.bottom() / 3, 'f', 2);
|
|
}
|
|
|
|
// Title.
|
|
if (!p_opt.m_wkTitle.isEmpty()) {
|
|
m_wkArgs << "--title" << p_opt.m_wkTitle;
|
|
}
|
|
|
|
m_wkArgs << "--encoding" << "utf-8";
|
|
m_wkArgs << (p_opt.m_wkEnableBackground ? "--background" : "--no-background");
|
|
|
|
// Delay for MathJax.
|
|
if (p_opt.m_wkhtmltopdf) {
|
|
m_wkArgs << "--javascript-delay" << "10000";
|
|
}
|
|
|
|
// Append additional global option.
|
|
if (!p_opt.m_wkExtraArgs.isEmpty()) {
|
|
m_wkArgs.append(VUtils::parseCombinedArgString(p_opt.m_wkExtraArgs));
|
|
}
|
|
|
|
// TOC option.
|
|
if (p_opt.m_enableTableOfContents) {
|
|
m_wkArgs << "toc" << "--toc-text-size-shrink" << "1.0";
|
|
}
|
|
}
|
|
|
|
bool VExporter::exportPDF(VFile *p_file,
|
|
const ExportOption &p_opt,
|
|
const QString &p_outputFile,
|
|
QString *p_errMsg)
|
|
{
|
|
return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg);
|
|
}
|
|
|
|
bool VExporter::exportHTML(VFile *p_file,
|
|
const ExportOption &p_opt,
|
|
const QString &p_outputFile,
|
|
QString *p_errMsg)
|
|
{
|
|
return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg);
|
|
}
|
|
|
|
static void replaceArgument(QString &p_cmd, const QString &p_arg, const QString &p_val)
|
|
{
|
|
if (p_val.startsWith("\"")) {
|
|
// Check if the arg has been already surrounded by ".
|
|
int pos = 0;
|
|
while (pos < p_cmd.size()) {
|
|
int idx = p_cmd.indexOf(p_arg, pos);
|
|
if (idx == -1) {
|
|
break;
|
|
}
|
|
|
|
int len = p_arg.size();
|
|
int nidx = idx;
|
|
if (idx > 0 && p_cmd[idx - 1] == '"') {
|
|
--nidx;
|
|
len += 1;
|
|
}
|
|
|
|
if (idx + p_arg.size() < p_cmd.size()
|
|
&& p_cmd[idx + p_arg.size()] == '"') {
|
|
len += 1;
|
|
}
|
|
|
|
p_cmd.replace(nidx, len, p_val);
|
|
pos = nidx + p_val.size() - len;
|
|
}
|
|
} else {
|
|
p_cmd.replace(p_arg, p_val);
|
|
}
|
|
}
|
|
|
|
static QString evaluateCommand(const ExportCustomOption &p_opt,
|
|
const QString &p_input,
|
|
const QString &p_inputFolder,
|
|
const QString &p_output)
|
|
{
|
|
QString cmd(p_opt.m_cmd);
|
|
replaceArgument(cmd, "%0", p_input);
|
|
replaceArgument(cmd, "%1", p_output);
|
|
replaceArgument(cmd, "%2", QDir::toNativeSeparators(p_opt.m_cssUrl));
|
|
replaceArgument(cmd, "%3", p_inputFolder);
|
|
replaceArgument(cmd, "%4", QDir::toNativeSeparators(p_opt.m_codeBlockCssUrl));
|
|
|
|
return cmd;
|
|
}
|
|
|
|
bool VExporter::exportCustom(VFile *p_file,
|
|
const ExportOption &p_opt,
|
|
const QString &p_outputFile,
|
|
QString *p_errMsg)
|
|
{
|
|
const ExportCustomOption &customOpt = p_opt.m_customOpt;
|
|
if (customOpt.m_srcFormat == ExportCustomOption::Markdown) {
|
|
// Use Markdown file as input.
|
|
QList<QString> files;
|
|
files.append(QDir::toNativeSeparators(p_file->fetchPath()));
|
|
return convertFilesViaCustom(files, p_outputFile, customOpt, p_errMsg);
|
|
} else {
|
|
return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg);
|
|
}
|
|
}
|
|
|
|
void VExporter::initWebViewer(VFile *p_file, const ExportOption &p_opt)
|
|
{
|
|
Q_ASSERT(!m_webViewer);
|
|
|
|
m_webViewer = new VWebView(p_file, static_cast<QWidget *>(parent()));
|
|
m_webViewer->hide();
|
|
|
|
VPreviewPage *page = new VPreviewPage(m_webViewer);
|
|
m_webViewer->setPage(page);
|
|
connect(page, &VPreviewPage::loadFinished,
|
|
this, &VExporter::handleLoadFinished);
|
|
connect(page->profile(), &QWebEngineProfile::downloadRequested,
|
|
this, &VExporter::handleDownloadRequested);
|
|
|
|
m_webDocument = new VDocument(p_file, m_webViewer);
|
|
connect(m_webDocument, &VDocument::logicsFinished,
|
|
this, &VExporter::handleLogicsFinished);
|
|
|
|
QWebChannel *channel = new QWebChannel(m_webViewer);
|
|
channel->registerObject(QStringLiteral("content"), m_webDocument);
|
|
page->setWebChannel(channel);
|
|
|
|
// Need to generate HTML using Hoedown.
|
|
if (p_opt.m_renderer == MarkdownConverterType::Hoedown) {
|
|
VMarkdownConverter mdConverter;
|
|
QString toc;
|
|
QString html = mdConverter.generateHtml(p_file->getContent(),
|
|
g_config->getMarkdownExtensions(),
|
|
toc);
|
|
bool isPdf = p_opt.m_format == ExportFormat::PDF
|
|
|| p_opt.m_format == ExportFormat::OnePDF;
|
|
bool extraToc = isPdf
|
|
&& !p_opt.m_pdfOpt.m_wkhtmltopdf
|
|
&& p_opt.m_pdfOpt.m_enableTableOfContents;
|
|
if (extraToc && !toc.isEmpty()) {
|
|
// Add toc to html.
|
|
QString div = "<div class=\"vnote-toc\">" + toc + "</div>\n";
|
|
html = div + html;
|
|
}
|
|
|
|
m_webDocument->setHtml(html);
|
|
}
|
|
|
|
m_baseUrl = p_file->getBaseUrl();
|
|
m_webViewer->setHtml(m_htmlTemplate, m_baseUrl);
|
|
}
|
|
|
|
void VExporter::handleLogicsFinished()
|
|
{
|
|
Q_ASSERT(!(m_noteState & NoteState::WebLogicsReady));
|
|
m_noteState = NoteState(m_noteState | NoteState::WebLogicsReady);
|
|
}
|
|
|
|
void VExporter::handleLoadFinished(bool p_ok)
|
|
{
|
|
Q_ASSERT(!(m_noteState & NoteState::WebLoadFinished));
|
|
m_noteState = NoteState(m_noteState | NoteState::WebLoadFinished);
|
|
|
|
if (!p_ok) {
|
|
m_noteState = NoteState(m_noteState | NoteState::Failed);
|
|
}
|
|
}
|
|
|
|
void VExporter::clearWebViewer()
|
|
{
|
|
// m_webDocument will be freeed by QObject.
|
|
delete m_webViewer;
|
|
m_webViewer = NULL;
|
|
m_webDocument = NULL;
|
|
m_baseUrl.clear();
|
|
}
|
|
|
|
bool VExporter::exportToPDF(VWebView *p_webViewer,
|
|
const QString &p_filePath,
|
|
const QPageLayout &p_layout)
|
|
{
|
|
int pdfPrinted = 0;
|
|
p_webViewer->page()->printToPdf([&, this](const QByteArray &p_result) {
|
|
if (p_result.isEmpty() || this->m_state == ExportState::Cancelled) {
|
|
pdfPrinted = -1;
|
|
return;
|
|
}
|
|
|
|
V_ASSERT(!p_filePath.isEmpty());
|
|
|
|
if (!VUtils::writeFileToDisk(p_filePath, p_result)) {
|
|
pdfPrinted = -1;
|
|
return;
|
|
}
|
|
|
|
pdfPrinted = 1;
|
|
}, p_layout);
|
|
|
|
while (pdfPrinted == 0) {
|
|
VUtils::sleepWait(100);
|
|
|
|
if (m_state == ExportState::Cancelled) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pdfPrinted == 1;
|
|
}
|
|
|
|
bool VExporter::exportToPDFViaWK(VDocument *p_webDocument,
|
|
const ExportPDFOption &p_opt,
|
|
const QString &p_filePath,
|
|
QString *p_errMsg)
|
|
{
|
|
int pdfExported = 0;
|
|
|
|
connect(p_webDocument, &VDocument::htmlContentFinished,
|
|
this, [&, this](const QString &p_headContent,
|
|
const QString &p_styleContent,
|
|
const QString &p_bodyContent) {
|
|
if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) {
|
|
pdfExported = -1;
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(!p_filePath.isEmpty());
|
|
|
|
// Save HTML to a temp dir.
|
|
QTemporaryDir tmpDir;
|
|
if (!tmpDir.isValid()) {
|
|
pdfExported = -1;
|
|
return;
|
|
}
|
|
|
|
QString htmlPath = tmpDir.filePath("vnote_tmp.html");
|
|
QString title = p_webDocument->getFile()->getName();
|
|
title = QFileInfo(title).completeBaseName();
|
|
if (!outputToHTMLFile(htmlPath,
|
|
title,
|
|
p_headContent,
|
|
p_styleContent,
|
|
p_bodyContent,
|
|
true,
|
|
true,
|
|
false)) {
|
|
pdfExported = -1;
|
|
return;
|
|
}
|
|
|
|
// Convert via wkhtmltopdf.
|
|
QList<QString> files;
|
|
files.append(htmlPath);
|
|
if (!htmlsToPDFViaWK(files, p_filePath, p_opt, p_errMsg)) {
|
|
pdfExported = -1;
|
|
} else {
|
|
pdfExported = 1;
|
|
}
|
|
});
|
|
|
|
p_webDocument->getHtmlContentAsync();
|
|
|
|
while (pdfExported == 0) {
|
|
VUtils::sleepWait(100);
|
|
|
|
if (m_state == ExportState::Cancelled) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pdfExported == 1;
|
|
}
|
|
|
|
bool VExporter::exportToCustom(VDocument *p_webDocument,
|
|
const ExportCustomOption &p_opt,
|
|
const QString &p_filePath,
|
|
QString *p_errMsg)
|
|
{
|
|
int exported = 0;
|
|
|
|
connect(p_webDocument, &VDocument::htmlContentFinished,
|
|
this, [&, this](const QString &p_headContent,
|
|
const QString &p_styleContent,
|
|
const QString &p_bodyContent) {
|
|
if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) {
|
|
exported = -1;
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(!p_filePath.isEmpty());
|
|
|
|
// Save HTML to a temp dir.
|
|
QTemporaryDir tmpDir;
|
|
if (!tmpDir.isValid()) {
|
|
exported = -1;
|
|
return;
|
|
}
|
|
|
|
QString htmlPath = tmpDir.filePath("vnote_tmp.html");
|
|
QString title = p_webDocument->getFile()->getName();
|
|
title = QFileInfo(title).completeBaseName();
|
|
if (!outputToHTMLFile(htmlPath,
|
|
title,
|
|
p_headContent,
|
|
p_styleContent,
|
|
p_bodyContent,
|
|
true,
|
|
true,
|
|
false)) {
|
|
exported = -1;
|
|
return;
|
|
}
|
|
|
|
// Convert via custom command.
|
|
QList<QString> files;
|
|
files.append(htmlPath);
|
|
if (!convertFilesViaCustom(files, p_filePath, p_opt, p_errMsg)) {
|
|
exported = -1;
|
|
} else {
|
|
exported = 1;
|
|
}
|
|
});
|
|
|
|
p_webDocument->getHtmlContentAsync();
|
|
|
|
while (exported == 0) {
|
|
VUtils::sleepWait(100);
|
|
|
|
if (m_state == ExportState::Cancelled) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return exported == 1;
|
|
}
|
|
|
|
bool VExporter::exportViaWebView(VFile *p_file,
|
|
const ExportOption &p_opt,
|
|
const QString &p_outputFile,
|
|
QString *p_errMsg)
|
|
{
|
|
Q_UNUSED(p_errMsg);
|
|
|
|
bool ret = false;
|
|
|
|
bool isOpened = p_file->isOpened();
|
|
if (!isOpened && !p_file->open()) {
|
|
goto exit;
|
|
}
|
|
|
|
Q_ASSERT(m_state == ExportState::Idle);
|
|
m_state = ExportState::Busy;
|
|
|
|
clearNoteState();
|
|
|
|
initWebViewer(p_file, p_opt);
|
|
|
|
while (!isNoteStateReady()) {
|
|
VUtils::sleepWait(100);
|
|
|
|
if (m_state == ExportState::Cancelled) {
|
|
goto exit;
|
|
}
|
|
|
|
if (isNoteStateFailed()) {
|
|
m_state = ExportState::Failed;
|
|
goto exit;
|
|
}
|
|
|
|
if (m_askedToStop) {
|
|
m_state = ExportState::Cancelled;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Wait to ensure Web side is really ready.
|
|
VUtils::sleepWait(200);
|
|
|
|
if (m_state == ExportState::Cancelled) {
|
|
goto exit;
|
|
}
|
|
|
|
{
|
|
|
|
bool exportRet = false;
|
|
switch (p_opt.m_format) {
|
|
case ExportFormat::PDF:
|
|
V_FALLTHROUGH;
|
|
case ExportFormat::OnePDF:
|
|
if (p_opt.m_pdfOpt.m_wkhtmltopdf) {
|
|
exportRet = exportToPDFViaWK(m_webDocument,
|
|
p_opt.m_pdfOpt,
|
|
p_outputFile,
|
|
p_errMsg);
|
|
} else {
|
|
exportRet = exportToPDF(m_webViewer,
|
|
p_outputFile,
|
|
m_pageLayout);
|
|
}
|
|
|
|
break;
|
|
|
|
case ExportFormat::HTML:
|
|
if (p_opt.m_htmlOpt.m_mimeHTML) {
|
|
exportRet = exportToMHTML(m_webViewer,
|
|
p_opt.m_htmlOpt,
|
|
p_outputFile);
|
|
} else {
|
|
exportRet = exportToHTML(m_webDocument,
|
|
p_opt.m_htmlOpt,
|
|
p_outputFile);
|
|
}
|
|
|
|
break;
|
|
|
|
case ExportFormat::Custom:
|
|
exportRet = exportToCustom(m_webDocument,
|
|
p_opt.m_customOpt,
|
|
p_outputFile,
|
|
p_errMsg);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
clearNoteState();
|
|
|
|
if (!isOpened) {
|
|
p_file->close();
|
|
}
|
|
|
|
if (exportRet) {
|
|
m_state = ExportState::Successful;
|
|
} else {
|
|
m_state = ExportState::Failed;
|
|
}
|
|
|
|
}
|
|
|
|
exit:
|
|
clearWebViewer();
|
|
|
|
if (m_state == ExportState::Successful) {
|
|
ret = true;
|
|
}
|
|
|
|
m_state = ExportState::Idle;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool VExporter::exportToHTML(VDocument *p_webDocument,
|
|
const ExportHTMLOption &p_opt,
|
|
const QString &p_filePath)
|
|
{
|
|
int htmlExported = 0;
|
|
|
|
connect(p_webDocument, &VDocument::htmlContentFinished,
|
|
this, [&, this](const QString &p_headContent,
|
|
const QString &p_styleContent,
|
|
const QString &p_bodyContent) {
|
|
if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) {
|
|
htmlExported = -1;
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(!p_filePath.isEmpty());
|
|
|
|
QString title = p_webDocument->getFile()->getName();
|
|
title = QFileInfo(title).completeBaseName();
|
|
if (!outputToHTMLFile(p_filePath,
|
|
title,
|
|
p_headContent,
|
|
p_styleContent,
|
|
p_bodyContent,
|
|
p_opt.m_embedCssStyle,
|
|
p_opt.m_completeHTML,
|
|
p_opt.m_embedImages)) {
|
|
htmlExported = -1;
|
|
return;
|
|
}
|
|
|
|
htmlExported = 1;
|
|
});
|
|
|
|
p_webDocument->getHtmlContentAsync();
|
|
|
|
while (htmlExported == 0) {
|
|
VUtils::sleepWait(100);
|
|
|
|
if (m_state == ExportState::Cancelled) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return htmlExported == 1;
|
|
}
|
|
|
|
bool VExporter::fixStyleResources(const QString &p_folder,
|
|
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 targetFile = g_webUtils->copyResource(QUrl(reg.cap(1)), p_folder);
|
|
if (targetFile.isEmpty()) {
|
|
pos = idx + reg.matchedLength();
|
|
} else {
|
|
// Replace the url string in html.
|
|
QString newUrl = QString("url(\"%1\");").arg(getResourceRelativePath(targetFile));
|
|
p_html.replace(idx, reg.matchedLength(), newUrl);
|
|
pos = idx + newUrl.size();
|
|
altered = true;
|
|
}
|
|
}
|
|
|
|
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)), false);
|
|
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)
|
|
{
|
|
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 targetFile = g_webUtils->copyResource(srcUrl, p_folder);
|
|
if (targetFile.isEmpty()) {
|
|
pos = idx + reg.matchedLength();
|
|
} else {
|
|
// Replace the url string in html.
|
|
QString newUrl = QString("<img %1src=\"%2\"%3>").arg(reg.cap(1))
|
|
.arg(getResourceRelativePath(targetFile))
|
|
.arg(reg.cap(3));
|
|
p_html.replace(idx, reg.matchedLength(), newUrl);
|
|
pos = idx + newUrl.size();
|
|
altered = true;
|
|
}
|
|
}
|
|
|
|
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('/');
|
|
int idx2 = p_file.lastIndexOf('/', idx - 1);
|
|
Q_ASSERT(idx > 0 && idx2 < idx);
|
|
return "." + p_file.mid(idx2);
|
|
}
|
|
|
|
bool VExporter::exportToMHTML(VWebView *p_webViewer,
|
|
const ExportHTMLOption &p_opt,
|
|
const QString &p_filePath)
|
|
{
|
|
Q_UNUSED(p_opt);
|
|
|
|
m_downloadState = QWebEngineDownloadItem::DownloadRequested;
|
|
|
|
p_webViewer->page()->save(p_filePath, QWebEngineDownloadItem::MimeHtmlSaveFormat);
|
|
|
|
while (m_downloadState == QWebEngineDownloadItem::DownloadRequested
|
|
|| m_downloadState == QWebEngineDownloadItem::DownloadInProgress) {
|
|
VUtils::sleepWait(100);
|
|
}
|
|
|
|
return m_downloadState == QWebEngineDownloadItem::DownloadCompleted;
|
|
}
|
|
|
|
void VExporter::handleDownloadRequested(QWebEngineDownloadItem *p_item)
|
|
{
|
|
if (p_item->savePageFormat() == QWebEngineDownloadItem::MimeHtmlSaveFormat) {
|
|
connect(p_item, &QWebEngineDownloadItem::stateChanged,
|
|
this, [this](QWebEngineDownloadItem::DownloadState p_state) {
|
|
m_downloadState = p_state;
|
|
});
|
|
}
|
|
}
|
|
|
|
static QString combineArgs(QStringList &p_args)
|
|
{
|
|
QString str;
|
|
for (const QString &arg : p_args) {
|
|
QString tmp;
|
|
if (arg.contains(' ')) {
|
|
tmp = '"' + arg + '"';
|
|
} else {
|
|
tmp = arg;
|
|
}
|
|
|
|
if (str.isEmpty()) {
|
|
str = tmp;
|
|
} else {
|
|
str = str + ' ' + tmp;
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
bool VExporter::htmlsToPDFViaWK(const QList<QString> &p_htmlFiles,
|
|
const QString &p_filePath,
|
|
const ExportPDFOption &p_opt,
|
|
QString *p_errMsg)
|
|
{
|
|
// Note: system's locale settings (Language for non-Unicode programs) is important to wkhtmltopdf.
|
|
// Input file could be encoded via QUrl::fromLocalFile(p_htmlFile).toString(QUrl::EncodeUnicode) to
|
|
// handle non-ASCII path.
|
|
|
|
QStringList args(m_wkArgs);
|
|
|
|
for (auto const & it : p_htmlFiles) {
|
|
args << QDir::toNativeSeparators(it);
|
|
}
|
|
|
|
args << QDir::toNativeSeparators(p_filePath);
|
|
|
|
QString cmd = p_opt.m_wkPath + " " + combineArgs(args);
|
|
emit outputLog(cmd);
|
|
qDebug() << "wkhtmltopdf cmd:" << cmd;
|
|
int ret = startProcess(p_opt.m_wkPath, args);
|
|
qDebug() << "wkhtmltopdf returned" << ret;
|
|
if (m_askedToStop) {
|
|
return ret == 0;
|
|
}
|
|
|
|
switch (ret) {
|
|
case -2:
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to start wkhtmltopdf (%1).").arg(cmd));
|
|
break;
|
|
|
|
case -1:
|
|
VUtils::addErrMsg(p_errMsg, tr("wkhtmltopdf crashed (%1).").arg(cmd));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret == 0;
|
|
}
|
|
|
|
bool VExporter::convertFilesViaCustom(const QList<QString> &p_files,
|
|
const QString &p_filePath,
|
|
const ExportCustomOption &p_opt,
|
|
QString *p_errMsg)
|
|
{
|
|
QString input;
|
|
QString inputFolder;
|
|
for (auto const & it : p_files) {
|
|
if (!input.isEmpty()) {
|
|
input += " ";
|
|
}
|
|
|
|
if (!inputFolder.isEmpty()) {
|
|
inputFolder += p_opt.m_folderSep;
|
|
}
|
|
|
|
QString tmp = QDir::toNativeSeparators(it);
|
|
input += ("\"" + tmp + "\"");
|
|
inputFolder += ("\"" + VUtils::basePathFromPath(tmp) + "\"");
|
|
}
|
|
|
|
QString output = QDir::toNativeSeparators(p_filePath);
|
|
QString cmd = evaluateCommand(p_opt,
|
|
input,
|
|
inputFolder,
|
|
output);
|
|
emit outputLog(cmd);
|
|
qDebug() << "custom cmd:" << cmd;
|
|
int ret = startProcess(cmd);
|
|
qDebug() << "custom cmd returned" << ret;
|
|
if (m_askedToStop) {
|
|
return ret == 0;
|
|
}
|
|
|
|
switch (ret) {
|
|
case -2:
|
|
VUtils::addErrMsg(p_errMsg, tr("Fail to start custom command (%1).").arg(cmd));
|
|
break;
|
|
|
|
case -1:
|
|
VUtils::addErrMsg(p_errMsg, tr("Custom command crashed (%1).").arg(cmd));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret == 0;
|
|
}
|
|
|
|
int VExporter::exportPDFInOne(const QList<QString> &p_htmlFiles,
|
|
const ExportOption &p_opt,
|
|
const QString &p_outputFile,
|
|
QString *p_errMsg)
|
|
{
|
|
if (!htmlsToPDFViaWK(p_htmlFiles, p_outputFile, p_opt.m_pdfOpt, p_errMsg)) {
|
|
return 0;
|
|
}
|
|
|
|
return p_htmlFiles.size();
|
|
}
|
|
|
|
int VExporter::startProcess(const QString &p_program, const QStringList &p_args)
|
|
{
|
|
int ret = 0;
|
|
QScopedPointer<QProcess> process(new QProcess(this));
|
|
process->start(p_program, p_args);
|
|
bool finished = false;
|
|
bool started = false;
|
|
while (true) {
|
|
QProcess::ProcessError err = process->error();
|
|
if (err == QProcess::FailedToStart
|
|
|| err == QProcess::Crashed) {
|
|
emit outputLog(tr("QProcess error %1.").arg(err));
|
|
if (err == QProcess::FailedToStart) {
|
|
ret = -2;
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (started) {
|
|
if (process->state() == QProcess::NotRunning) {
|
|
finished = true;
|
|
}
|
|
} else {
|
|
if (process->state() != QProcess::NotRunning) {
|
|
started = true;
|
|
}
|
|
}
|
|
|
|
if (process->waitForFinished(500)) {
|
|
// Finished.
|
|
finished = true;
|
|
}
|
|
|
|
QByteArray outBa = process->readAllStandardOutput();
|
|
QByteArray errBa = process->readAllStandardError();
|
|
QString msg;
|
|
if (!outBa.isEmpty()) {
|
|
msg += QString::fromLocal8Bit(outBa);
|
|
}
|
|
|
|
if (!errBa.isEmpty()) {
|
|
msg += QString::fromLocal8Bit(errBa);
|
|
}
|
|
|
|
if (!msg.isEmpty()) {
|
|
emit outputLog(msg);
|
|
}
|
|
|
|
if (finished) {
|
|
QProcess::ExitStatus sta = process->exitStatus();
|
|
if (sta == QProcess::CrashExit) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
ret = process->exitCode();
|
|
break;
|
|
}
|
|
|
|
QCoreApplication::processEvents();
|
|
|
|
if (m_askedToStop) {
|
|
process->kill();
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int VExporter::startProcess(const QString &p_cmd)
|
|
{
|
|
QStringList args = VUtils::parseCombinedArgString(p_cmd);
|
|
if (args.isEmpty()) {
|
|
return -2;
|
|
}
|
|
|
|
return startProcess(args.first(), args.mid(1));
|
|
}
|
|
|
|
bool VExporter::outputToHTMLFile(const QString &p_file,
|
|
const QString &p_title,
|
|
const QString &p_headContent,
|
|
const QString &p_styleContent,
|
|
const QString &p_bodyContent,
|
|
bool p_embedCssStyle,
|
|
bool p_completeHTML,
|
|
bool p_embedImages)
|
|
{
|
|
QFile file(p_file);
|
|
if (!file.open(QFile::WriteOnly)) {
|
|
return false;
|
|
}
|
|
|
|
QString resFolder = QFileInfo(p_file).completeBaseName() + "_files";
|
|
QString resFolderPath = QDir(VUtils::basePathFromPath(p_file)).filePath(resFolder);
|
|
|
|
qDebug() << "HTML files folder" << resFolderPath;
|
|
|
|
QString html(m_exportHtmlTemplate);
|
|
if (!p_title.isEmpty()) {
|
|
html.replace(HtmlHolder::c_headTitleHolder,
|
|
"<title>" + VUtils::escapeHtml(p_title) + "</title>");
|
|
}
|
|
|
|
if (!p_styleContent.isEmpty() && p_embedCssStyle) {
|
|
QString content(p_styleContent);
|
|
embedStyleResources(content);
|
|
html.replace(HtmlHolder::c_styleHolder, content);
|
|
}
|
|
|
|
if (!p_headContent.isEmpty()) {
|
|
html.replace(HtmlHolder::c_headHolder, p_headContent);
|
|
}
|
|
|
|
if (p_completeHTML) {
|
|
QString content(p_bodyContent);
|
|
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);
|
|
}
|
|
|
|
file.write(html.toUtf8());
|
|
file.close();
|
|
|
|
// Delete empty resource folder.
|
|
QDir dir(resFolderPath);
|
|
if (dir.isEmpty()) {
|
|
dir.cdUp();
|
|
dir.rmdir(resFolder);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int VExporter::exportCustomInOne(const QList<QString> &p_files,
|
|
const ExportOption &p_opt,
|
|
const QString &p_outputFile,
|
|
QString *p_errMsg)
|
|
{
|
|
if (!convertFilesViaCustom(p_files, p_outputFile, p_opt.m_customOpt, p_errMsg)) {
|
|
return 0;
|
|
}
|
|
|
|
return p_files.size();
|
|
}
|