| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- #include "vexporter.h"
- #include <QDebug>
- #include <QWidget>
- #include <QWebChannel>
- #include <QWebEngineProfile>
- #include <QRegExp>
- #include <QProcess>
- #include <QTemporaryDir>
- #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)
- {
- }
- static QString marginToStrMM(qreal p_margin)
- {
- return QString("%1mm").arg(p_margin);
- }
- void VExporter::prepareExport(const ExportOption &p_opt)
- {
- m_htmlTemplate = VUtils::generateHtmlTemplate(p_opt.m_renderer,
- p_opt.m_renderBg,
- p_opt.m_renderStyle,
- p_opt.m_renderCodeBlockStyle,
- p_opt.m_format == ExportFormat::PDF
- || p_opt.m_format == ExportFormat::OnePDF);
- m_exportHtmlTemplate = VUtils::generateExportHtmlTemplate(p_opt.m_renderBg);
- m_pageLayout = *(p_opt.m_pdfOpt.m_layout);
- prepareWKArguments(p_opt.m_pdfOpt);
- }
- // From QProcess code.
- static QStringList parseCombinedArgString(const QString &program)
- {
- QStringList args;
- QString tmp;
- int quoteCount = 0;
- bool inQuote = false;
- // handle quoting. tokens can be surrounded by double quotes
- // "hello world". three consecutive double quotes represent
- // the quote character itself.
- for (int i = 0; i < program.size(); ++i) {
- if (program.at(i) == QLatin1Char('"')) {
- ++quoteCount;
- if (quoteCount == 3) {
- // third consecutive quote
- quoteCount = 0;
- tmp += program.at(i);
- }
- continue;
- }
- if (quoteCount) {
- if (quoteCount == 1)
- inQuote = !inQuote;
- quoteCount = 0;
- }
- if (!inQuote && program.at(i).isSpace()) {
- if (!tmp.isEmpty()) {
- args += tmp;
- tmp.clear();
- }
- } else {
- tmp += program.at(i);
- }
- }
- if (!tmp.isEmpty())
- args += tmp;
- return args;
- }
- void VExporter::prepareWKArguments(const ExportPDFOption &p_opt)
- {
- m_wkArgs.clear();
- m_wkArgs << "--quiet";
- m_wkArgs << "--encoding" << "utf-8";
- 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());
- m_wkArgs << (p_opt.m_wkEnableBackground ? "--background" : "--no-background");
- 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;
- }
- // Append additional arguments.
- if (!p_opt.m_wkExtraArgs.isEmpty()) {
- m_wkArgs.append(parseCombinedArgString(p_opt.m_wkExtraArgs));
- }
- if (p_opt.m_wkEnableTableOfContents) {
- 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);
- }
- 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);
- 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");
- QFile file(htmlPath);
- if (!file.open(QFile::WriteOnly)) {
- pdfExported = -1;
- return;
- }
- QString resFolder = QFileInfo(htmlPath).completeBaseName() + "_files";
- QString resFolderPath = QDir(VUtils::basePathFromPath(htmlPath)).filePath(resFolder);
- qDebug() << "temp HTML files folder" << resFolderPath;
- QString html(m_exportHtmlTemplate);
- if (!p_styleContent.isEmpty()) {
- QString content(p_styleContent);
- fixStyleResources(resFolderPath, content);
- html.replace(HtmlHolder::c_styleHolder, content);
- }
- if (!p_headContent.isEmpty()) {
- html.replace(HtmlHolder::c_headHolder, p_headContent);
- }
- QString content(p_bodyContent);
- fixBodyResources(m_baseUrl, resFolderPath, content);
- html.replace(HtmlHolder::c_bodyHolder, content);
- file.write(html.toUtf8());
- file.close();
- // 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::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;
- }
- }
- // 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;
- 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());
- QFile file(p_filePath);
- if (!file.open(QFile::WriteOnly)) {
- htmlExported = -1;
- return;
- }
- QString resFolder = QFileInfo(p_filePath).completeBaseName() + "_files";
- QString resFolderPath = QDir(VUtils::basePathFromPath(p_filePath)).filePath(resFolder);
- qDebug() << "HTML files folder" << resFolderPath;
- QString html(m_exportHtmlTemplate);
- if (!p_styleContent.isEmpty() && p_opt.m_embedCssStyle) {
- QString content(p_styleContent);
- fixStyleResources(resFolderPath, content);
- html.replace(HtmlHolder::c_styleHolder, content);
- }
- if (!p_headContent.isEmpty()) {
- html.replace(HtmlHolder::c_headHolder, p_headContent);
- }
- if (p_opt.m_completeHTML) {
- QString content(p_bodyContent);
- 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);
- }
- 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::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;
- }
- 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);
- int ret = QProcess::execute(p_opt.m_wkPath, args);
- qDebug() << "wkhtmltopdf returned" << ret << cmd;
- 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;
- }
- 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();
- }
|