export: support exporting PDF via wkhtmltopdf tool

This commit is contained in:
Le Tan 2018-03-01 20:19:58 +08:00
parent 3bee0365e9
commit 935bb4d3b4
20 changed files with 729 additions and 64 deletions

View File

@ -2,6 +2,7 @@
#include <QtWidgets>
#include <QCoreApplication>
#include <QProcess>
#ifndef QT_NO_PRINTER
#include <QPrinter>
@ -18,6 +19,7 @@
#include "vnotefile.h"
#include "vnote.h"
#include "vexporter.h"
#include "vlineedit.h"
extern VConfigManager *g_config;
@ -25,7 +27,7 @@ extern VNote *g_vnote;
QString VExportDialog::s_lastOutputFolder;
ExportFormat VExportDialog::s_lastExportFormat = ExportFormat::Markdown;
ExportOption VExportDialog::s_opt;
#define LOGERR(x) do { QString msg = (x); \
VUtils::addErrMsg(p_errMsg, msg); \
@ -45,7 +47,8 @@ VExportDialog::VExportDialog(VNotebook *p_notebook,
m_cart(p_cart),
m_pageLayout(QPageLayout(QPageSize(QPageSize::A4),
QPageLayout::Portrait,
QMarginsF(0.3, 0.3, 0.3, 0.3))),
QMarginsF(10, 16, 10, 10),
QPageLayout::Millimeter)),
m_inExport(false),
m_askedToStop(false)
{
@ -56,6 +59,10 @@ VExportDialog::VExportDialog(VNotebook *p_notebook,
setupUI();
m_exporter = new VExporter(this);
connect(m_exporter, &VExporter::outputLog,
this, [this](const QString &p_log) {
appendLogLine(p_log);
});
initUIFields(p_renderer);
@ -68,6 +75,8 @@ void VExportDialog::setupUI()
m_srcCB = VUtils::getComboBox();
m_srcCB->setToolTip(tr("Choose notes to export"));
m_srcCB->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
connect(m_srcCB, SIGNAL(currentIndexChanged(int)),
this, SLOT(handleCurrentSrcChanged(int)));
// Target format.
m_formatCB = VUtils::getComboBox();
@ -97,12 +106,12 @@ void VExportDialog::setupUI()
m_outputEdit = new VLineEdit(s_lastOutputFolder);
connect(m_outputEdit, &QLineEdit::textChanged,
this, &VExportDialog::handleInputChanged);
m_browseBtn = new QPushButton(tr("&Browse"));
connect(m_browseBtn, &QPushButton::clicked,
this, &VExportDialog::handleBrowseBtnClicked);
QPushButton *browseBtn = new QPushButton(tr("&Browse"));
connect(browseBtn, &QPushButton::clicked,
this, &VExportDialog::handleOutputBrowseBtnClicked);
QHBoxLayout *outputLayout = new QHBoxLayout();
outputLayout->addWidget(m_outputEdit);
outputLayout->addWidget(m_browseBtn);
outputLayout->addWidget(browseBtn);
m_basicBox = new QGroupBox(tr("Information"));
@ -150,10 +159,12 @@ void VExportDialog::setupUI()
m_basicBox->setLayout(basicLayout);
// Settings box.
m_generalSettings = setupGeneralAdvancedSettings();
m_htmlSettings = setupHTMLAdvancedSettings();
m_pdfSettings = setupPDFAdvancedSettings();
QVBoxLayout *advLayout = new QVBoxLayout();
advLayout->addWidget(m_generalSettings);
advLayout->addWidget(m_htmlSettings);
advLayout->addWidget(m_pdfSettings);
@ -190,8 +201,75 @@ QWidget *VExportDialog::setupPDFAdvancedSettings()
layoutLayout->addWidget(layoutBtn);
layoutLayout->addStretch();
// Use wkhtmltopdf.
m_wkhtmltopdfCB = new QCheckBox(tr("Use wkhtmltopdf"));
m_wkhtmltopdfCB->setToolTip(tr("Use wkhtmltopdf tool to generate PDF (wkhtmltopdf needed to be installed)"));
connect(m_wkhtmltopdfCB, &QCheckBox::stateChanged,
this, [this](int p_state) {
bool checked = p_state == Qt::Checked;
m_wkPathEdit->setEnabled(checked);
m_wkPathBrowseBtn->setEnabled(checked);
m_wkBackgroundCB->setEnabled(checked);
m_wkTableOfContentsCB->setEnabled(checked);
m_wkPageNumberCB->setEnabled(checked);
m_wkExtraArgsEdit->setEnabled(checked);
});
QPushButton *wkBtn = new QPushButton(tr("Download wkhtmltopdf"));
connect(wkBtn, &QPushButton::clicked,
this, [this]() {
QString url("https://wkhtmltopdf.org/downloads.html");
QDesktopServices::openUrl(QUrl(url));
});
QHBoxLayout *wkLayout = new QHBoxLayout();
wkLayout->addWidget(m_wkhtmltopdfCB);
wkLayout->addStretch();
wkLayout->addWidget(wkBtn);
// wkhtmltopdf Path.
m_wkPathEdit = new VLineEdit();
m_wkPathEdit->setToolTip(tr("Tell VNote where to find wkhtmlpdf tool"));
m_wkPathEdit->setEnabled(m_wkhtmltopdfCB->isChecked());
m_wkPathBrowseBtn = new QPushButton(tr("&Browse"));
m_wkPathBrowseBtn->setEnabled(m_wkhtmltopdfCB->isChecked());
connect(m_wkPathBrowseBtn, &QPushButton::clicked,
this, &VExportDialog::handleWkPathBrowseBtnClicked);
QHBoxLayout *wkPathLayout = new QHBoxLayout();
wkPathLayout->addWidget(m_wkPathEdit);
wkPathLayout->addWidget(m_wkPathBrowseBtn);
// wkhtmltopdf enable background.
m_wkBackgroundCB = new QCheckBox(tr("Enable background"));
m_wkBackgroundCB->setToolTip(tr("Enable background when printing"));
m_wkBackgroundCB->setEnabled(m_wkhtmltopdfCB->isChecked());
// wkhtmltopdf enable table of contents.
m_wkTableOfContentsCB = new QCheckBox(tr("Enable Table Of Contents"));
m_wkTableOfContentsCB->setToolTip(tr("Add a table of contents to the document"));
m_wkTableOfContentsCB->setEnabled(m_wkhtmltopdfCB->isChecked());
// wkhtmltopdf page number.
m_wkPageNumberCB = VUtils::getComboBox();
m_wkPageNumberCB->setToolTip(tr("Append page number as footer"));
m_wkPageNumberCB->setEnabled(m_wkhtmltopdfCB->isChecked());
// wkhtmltopdf extra argumnets.
m_wkExtraArgsEdit = new VLineEdit();
m_wkExtraArgsEdit->setToolTip(tr("Additional arguments passed to wkhtmltopdf"));
m_wkExtraArgsEdit->setPlaceholderText(tr("Use \" to enclose arguments containing space"));
m_wkExtraArgsEdit->setEnabled(m_wkhtmltopdfCB->isChecked());
QFormLayout *advLayout = new QFormLayout();
advLayout->addRow(tr("Page layout:"), layoutLayout);
advLayout->addRow(wkLayout);
advLayout->addRow(tr("wkhtmltopdf path:"), wkPathLayout);
advLayout->addRow(m_wkBackgroundCB);
advLayout->addRow(m_wkTableOfContentsCB);
advLayout->addRow(tr("Page number:"), m_wkPageNumberCB);
advLayout->addRow(tr("Additional arguments:"), m_wkExtraArgsEdit);
advLayout->setContentsMargins(0, 0, 0, 0);
@ -206,14 +284,12 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
// Embed CSS styles.
m_embedStyleCB = new QCheckBox(tr("Embed CSS styles"), this);
m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file"));
m_embedStyleCB->setChecked(true);
// 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"));
m_completeHTMLCB->setChecked(true);
// Mime HTML.
m_mimeHTMLCB = new QCheckBox(tr("MIME HTML"), this);
@ -238,6 +314,24 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
return wid;
}
QWidget *VExportDialog::setupGeneralAdvancedSettings()
{
// Include subfolders.
m_subfolderCB = new QCheckBox(tr("Process subfolders"));
m_subfolderCB->setToolTip(tr("Process subfolders recursively"));
m_subfolderCB->setChecked(true);
QFormLayout *advLayout = new QFormLayout();
advLayout->addRow(m_subfolderCB);
advLayout->setContentsMargins(0, 0, 0, 0);
QWidget *wid = new QWidget();
wid->setLayout(advLayout);
return wid;
}
void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
{
// Notes to export.
@ -248,7 +342,7 @@ void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
if (m_directory) {
m_srcCB->addItem(tr("Current Folder (%1)").arg(m_directory->getName()),
(int)ExportSource::CurrentDirectory);
(int)ExportSource::CurrentFolder);
}
if (m_notebook) {
@ -265,7 +359,7 @@ void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
m_formatCB->addItem(tr("Markdown"), (int)ExportFormat::Markdown);
m_formatCB->addItem(tr("HTML"), (int)ExportFormat::HTML);
m_formatCB->addItem(tr("PDF"), (int)ExportFormat::PDF);
m_formatCB->setCurrentIndex(m_formatCB->findData((int)s_lastExportFormat));
m_formatCB->setCurrentIndex(m_formatCB->findData((int)s_opt.m_format));
// Markdown renderer.
m_rendererCB->addItem(tr("Hoedown"), MarkdownConverterType::Hoedown);
@ -281,8 +375,11 @@ void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
m_renderBgCB->addItem(bgColors[i].m_name, bgColors[i].m_name);
}
m_renderBgCB->setCurrentIndex(
m_renderBgCB->findData(g_config->getCurRenderBackgroundColor()));
if (s_opt.m_renderBg.isEmpty()) {
s_opt.m_renderBg = g_config->getCurRenderBackgroundColor();
}
m_renderBgCB->setCurrentIndex(m_renderBgCB->findData(s_opt.m_renderBg));
// Markdown rendering style.
QList<QString> styles = g_config->getCssStyles();
@ -301,6 +398,56 @@ void VExportDialog::initUIFields(MarkdownConverterType p_renderer)
m_renderCodeBlockStyleCB->setCurrentIndex(
m_renderCodeBlockStyleCB->findData(g_config->getCodeBlockCssStyle()));
m_embedStyleCB->setChecked(s_opt.m_htmlOpt.m_embedCssStyle);
m_completeHTMLCB->setChecked(s_opt.m_htmlOpt.m_completeHTML);
m_mimeHTMLCB->setChecked(s_opt.m_htmlOpt.m_mimeHTML);
m_wkhtmltopdfCB->setChecked(s_opt.m_pdfOpt.m_wkhtmltopdf);
// wkhtmltopdf path.
m_wkPathEdit->setText(g_config->getWkhtmltopdfPath());
m_wkBackgroundCB->setChecked(s_opt.m_pdfOpt.m_wkEnableBackground);
m_wkTableOfContentsCB->setChecked(s_opt.m_pdfOpt.m_wkEnableTableOfContents);
// wkhtmltopdf page number.
m_wkPageNumberCB->addItem(tr("None"), (int)ExportPageNumber::None);
m_wkPageNumberCB->addItem(tr("Left"), (int)ExportPageNumber::Left);
m_wkPageNumberCB->addItem(tr("Center"), (int)ExportPageNumber::Center);
m_wkPageNumberCB->addItem(tr("Right"), (int)ExportPageNumber::Right);
m_wkPageNumberCB->setCurrentIndex(m_wkPageNumberCB->findData((int)s_opt.m_pdfOpt.m_wkPageNumber));
m_wkExtraArgsEdit->setText(g_config->getWkhtmltopdfArgs());
}
bool VExportDialog::checkWkhtmltopdfExecutable(const QString &p_file)
{
QStringList args;
args << "--version";
int ret = QProcess::execute(p_file, args);
switch (ret) {
case -2:
appendLogLine(tr("Fail to start wkhtmltopdf."));
break;
case -1:
appendLogLine(tr("wkhtmltopdf crashed."));
break;
case 0:
appendLogLine(tr("Use %1.").arg(p_file));
break;
default:
appendLogLine(tr("wkhtmltopdf returned %1.").arg(ret));
break;
}
return ret == 0;
}
void VExportDialog::startExport()
@ -315,45 +462,62 @@ void VExportDialog::startExport()
QString outputFolder = QDir::cleanPath(QDir(getOutputDirectory()).absolutePath());
ExportOption opt((ExportSource)m_srcCB->currentData().toInt(),
(ExportFormat)m_formatCB->currentData().toInt(),
(MarkdownConverterType)m_rendererCB->currentData().toInt(),
m_renderBgCB->currentData().toString(),
m_renderStyleCB->currentData().toString(),
m_renderCodeBlockStyleCB->currentData().toString(),
&m_pageLayout,
ExportHTMLOption(m_embedStyleCB->isChecked(),
m_completeHTMLCB->isChecked(),
m_mimeHTMLCB->isChecked()));
s_lastExportFormat = opt.m_format;
s_opt = ExportOption((ExportSource)m_srcCB->currentData().toInt(),
(ExportFormat)m_formatCB->currentData().toInt(),
(MarkdownConverterType)m_rendererCB->currentData().toInt(),
m_renderBgCB->currentData().toString(),
m_renderStyleCB->currentData().toString(),
m_renderCodeBlockStyleCB->currentData().toString(),
m_subfolderCB->isChecked(),
ExportPDFOption(&m_pageLayout,
m_wkhtmltopdfCB->isChecked(),
QDir::toNativeSeparators(m_wkPathEdit->text()),
m_wkBackgroundCB->isChecked(),
m_wkTableOfContentsCB->isChecked(),
(ExportPageNumber)m_wkPageNumberCB->currentData().toInt(),
m_wkExtraArgsEdit->text()),
ExportHTMLOption(m_embedStyleCB->isChecked(),
m_completeHTMLCB->isChecked(),
m_mimeHTMLCB->isChecked()));
m_consoleEdit->clear();
appendLogLine(tr("Export to %1.").arg(outputFolder));
if (opt.m_format == ExportFormat::PDF
|| opt.m_format == ExportFormat::HTML) {
m_exporter->prepareExport(opt);
if (s_opt.m_format == ExportFormat::PDF
|| s_opt.m_format == ExportFormat::HTML) {
m_exporter->prepareExport(s_opt);
if (s_opt.m_format == ExportFormat::PDF
&& s_opt.m_pdfOpt.m_wkhtmltopdf) {
g_config->setWkhtmltopdfPath(s_opt.m_pdfOpt.m_wkPath);
g_config->setWkhtmltopdfArgs(s_opt.m_pdfOpt.m_wkExtraArgs);
if (!checkWkhtmltopdfExecutable(s_opt.m_pdfOpt.m_wkPath)) {
m_inExport = false;
m_exportBtn->setEnabled(true);
return;
}
}
}
int ret = 0;
QString msg;
switch ((int)opt.m_source) {
case (int)ExportSource::CurrentNote:
ret = doExport(m_file, opt, outputFolder, &msg);
switch (s_opt.m_source) {
case ExportSource::CurrentNote:
ret = doExport(m_file, s_opt, outputFolder, &msg);
break;
case (int)ExportSource::CurrentDirectory:
ret = doExport(m_directory, opt, outputFolder, &msg);
case ExportSource::CurrentFolder:
ret = doExport(m_directory, s_opt, outputFolder, &msg);
break;
case (int)ExportSource::CurrentNotebook:
ret = doExport(m_notebook, opt, outputFolder, &msg);
case ExportSource::CurrentNotebook:
ret = doExport(m_notebook, s_opt, outputFolder, &msg);
break;
case (int)ExportSource::Cart:
ret = doExport(m_cart, opt, outputFolder, &msg);
case ExportSource::Cart:
ret = doExport(m_cart, s_opt, outputFolder, &msg);
break;
default:
@ -386,7 +550,7 @@ QString VExportDialog::getOutputDirectory() const
return m_outputEdit->text();
}
void VExportDialog::handleBrowseBtnClicked()
void VExportDialog::handleOutputBrowseBtnClicked()
{
QString initPath = getOutputDirectory();
if (!QFileInfo::exists(initPath)) {
@ -405,6 +569,29 @@ void VExportDialog::handleBrowseBtnClicked()
}
}
void VExportDialog::handleWkPathBrowseBtnClicked()
{
QString initPath = m_wkPathEdit->text();
if (!QFileInfo::exists(initPath)) {
initPath = QCoreApplication::applicationDirPath();
}
#if defined(Q_OS_WIN)
QString filter = tr("Executable (*.exe)");
#else
QString filter;
#endif
QString filePath = QFileDialog::getOpenFileName(this,
tr("Select wkhtmltopdf Executable"),
initPath,
filter);
if (!filePath.isEmpty()) {
m_wkPathEdit->setText(filePath);
}
}
void VExportDialog::handleInputChanged()
{
// Source.
@ -492,13 +679,15 @@ int VExportDialog::doExport(VDirectory *p_directory,
ret += doExport(file, p_opt, outputPath, p_errMsg);
}
// Export subfolder.
for (auto const & dir : p_directory->getSubDirs()) {
if (!checkUserAction()) {
goto exit;
}
// Export subfolders.
if (p_opt.m_processSubfolders) {
for (auto const & dir : p_directory->getSubDirs()) {
if (!checkUserAction()) {
goto exit;
}
ret += doExport(dir, p_opt, outputPath, p_errMsg);
ret += doExport(dir, p_opt, outputPath, p_errMsg);
}
}
exit:
@ -731,8 +920,9 @@ void VExportDialog::handleLayoutBtnClicked()
return;
}
m_pageLayout.setUnits(QPageLayout::Millimeter);
m_pageLayout.setPageSize(printer.pageLayout().pageSize());
m_pageLayout.setMargins(printer.pageLayout().margins());
m_pageLayout.setMargins(printer.pageLayout().margins(QPageLayout::Millimeter));
m_pageLayout.setOrientation(printer.pageLayout().orientation());
updatePageLayoutLabel();
@ -741,8 +931,6 @@ void VExportDialog::handleLayoutBtnClicked()
void VExportDialog::updatePageLayoutLabel()
{
qDebug() << "page layout margins:" << m_pageLayout.margins();
m_layoutLabel->setText(QString("%1, %2").arg(m_pageLayout.pageSize().name())
.arg(m_pageLayout.orientation() == QPageLayout::Portrait ?
tr("Portrait") : tr("Landscape")));
@ -771,3 +959,21 @@ void VExportDialog::handleCurrentFormatChanged(int p_index)
m_pdfSettings->setVisible(pdfEnabled);
m_htmlSettings->setVisible(htmlEnabled);
}
void VExportDialog::handleCurrentSrcChanged(int p_index)
{
bool subfolderEnabled = false;
if (p_index >= 0) {
switch (m_srcCB->currentData().toInt()) {
case (int)ExportSource::CurrentFolder:
subfolderEnabled = true;
break;
default:
break;
}
}
m_subfolderCB->setVisible(subfolderEnabled);
}

View File

@ -19,12 +19,13 @@ class VFile;
class VCart;
class VExporter;
class QCheckBox;
class VLineEdit;
enum class ExportSource
{
CurrentNote = 0,
CurrentDirectory,
CurrentFolder,
CurrentNotebook,
Cart
};
@ -38,8 +39,24 @@ enum class ExportFormat
};
enum class ExportPageNumber
{
None = 0,
Left,
Center,
Right
};
struct ExportHTMLOption
{
ExportHTMLOption()
: m_embedCssStyle(true),
m_completeHTML(true),
m_mimeHTML(false)
{
}
ExportHTMLOption(bool p_embedCssStyle,
bool p_completeHTML,
bool p_mimeHTML)
@ -55,15 +72,62 @@ struct ExportHTMLOption
};
struct ExportPDFOption
{
ExportPDFOption()
: m_layout(NULL),
m_wkhtmltopdf(false),
m_wkEnableBackground(true),
m_wkEnableTableOfContents(false),
m_wkPageNumber(ExportPageNumber::None)
{
}
ExportPDFOption(QPageLayout *p_layout,
bool p_wkhtmltopdf,
const QString &p_wkPath,
bool p_wkEnableBackground,
bool p_wkEnableTableOfContents,
ExportPageNumber p_wkPageNumber,
const QString &p_wkExtraArgs)
: m_layout(p_layout),
m_wkhtmltopdf(p_wkhtmltopdf),
m_wkPath(p_wkPath),
m_wkEnableBackground(p_wkEnableBackground),
m_wkEnableTableOfContents(p_wkEnableTableOfContents),
m_wkPageNumber(p_wkPageNumber),
m_wkExtraArgs(p_wkExtraArgs)
{
}
QPageLayout *m_layout;
bool m_wkhtmltopdf;
QString m_wkPath;
bool m_wkEnableBackground;
bool m_wkEnableTableOfContents;
ExportPageNumber m_wkPageNumber;
QString m_wkExtraArgs;
};
struct ExportOption
{
ExportOption()
: m_source(ExportSource::CurrentNote),
m_format(ExportFormat::Markdown),
m_renderer(MarkdownConverterType::MarkdownIt),
m_processSubfolders(false)
{
}
ExportOption(ExportSource p_source,
ExportFormat p_format,
MarkdownConverterType p_renderer,
const QString &p_renderBg,
const QString &p_renderStyle,
const QString &p_renderCodeBlockStyle,
QPageLayout *p_layout,
bool p_processSubfolders,
const ExportPDFOption &p_pdfOpt,
const ExportHTMLOption &p_htmlOpt)
: m_source(p_source),
m_format(p_format),
@ -71,7 +135,8 @@ struct ExportOption
m_renderBg(p_renderBg),
m_renderStyle(p_renderStyle),
m_renderCodeBlockStyle(p_renderCodeBlockStyle),
m_layout(p_layout),
m_processSubfolders(p_processSubfolders),
m_pdfOpt(p_pdfOpt),
m_htmlOpt(p_htmlOpt)
{
}
@ -85,7 +150,11 @@ struct ExportOption
QString m_renderStyle;
QString m_renderCodeBlockStyle;
QPageLayout *m_layout;
// Whether process subfolders recursively when source is CurrentFolder.
bool m_processSubfolders;
ExportPDFOption m_pdfOpt;
ExportHTMLOption m_htmlOpt;
};
@ -105,7 +174,9 @@ public:
private slots:
void startExport();
void handleBrowseBtnClicked();
void handleOutputBrowseBtnClicked();
void handleWkPathBrowseBtnClicked();
void handleInputChanged();
@ -113,6 +184,8 @@ private slots:
void handleCurrentFormatChanged(int p_index);
void handleCurrentSrcChanged(int p_index);
private:
void setupUI();
@ -120,6 +193,8 @@ private:
QWidget *setupHTMLAdvancedSettings();
QWidget *setupGeneralAdvancedSettings();
void initUIFields(MarkdownConverterType p_renderer);
QString getOutputDirectory() const;
@ -167,6 +242,8 @@ private:
void updatePageLayoutLabel();
bool checkWkhtmltopdfExecutable(const QString &p_file);
QComboBox *m_srcCB;
QComboBox *m_formatCB;
@ -181,8 +258,6 @@ private:
VLineEdit *m_outputEdit;
QPushButton *m_browseBtn;
QGroupBox *m_basicBox;
QGroupBox *m_settingBox;
@ -191,6 +266,8 @@ private:
QWidget *m_htmlSettings;
QWidget *m_generalSettings;
QPlainTextEdit *m_consoleEdit;
QDialogButtonBox *m_btnBox;
@ -201,12 +278,28 @@ private:
QLabel *m_layoutLabel;
QCheckBox *m_wkhtmltopdfCB;
VLineEdit *m_wkPathEdit;
QPushButton *m_wkPathBrowseBtn;
QCheckBox *m_wkBackgroundCB;
QCheckBox *m_wkTableOfContentsCB;
QComboBox *m_wkPageNumberCB;
VLineEdit *m_wkExtraArgsEdit;
QCheckBox *m_embedStyleCB;
QCheckBox *m_completeHTMLCB;;
QCheckBox *m_mimeHTMLCB;
QCheckBox *m_subfolderCB;
VNotebook *m_notebook;
VDirectory *m_directory;
@ -229,8 +322,7 @@ private:
// Last output folder path.
static QString s_lastOutputFolder;
// Last export format.
static ExportFormat s_lastExportFormat;
static ExportOption s_opt;
};
#endif // VEXPORTDIALOG_H

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html>
<meta charset="utf-8">
<head>
<style type="text/css">

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html>
<meta charset="utf-8">
<head>
<style type="text/css">

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" type="text/css" href="CSS_PLACE_HOLDER">

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<polygon fill="#4D5765" points="128,192 256,320 384,192 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@ -226,6 +226,7 @@ button_icon_danger_fg=@danger_icon_fg
combobox_border=@border_bg
combobox_fg=@content_fg
combobox_bg=@content_bg
combobox_disabled_fg=@disabled_fg
combobox_view_border=@border_bg
combobox_view_selected_bg=@selected_bg
combobox_view_selected_fg=@selected_fg
@ -252,6 +253,7 @@ label_titlelabel_bg=@title_bg
lineedit_border=@border_bg
lineedit_fg=@edit_fg
lineedit_bg=@edit_bg
lineedit_disabled_fg=@disabled_fg
lineedit_focus_bg=@edit_focus_bg
lineedit_focus_border=@edit_focus_border
lineedit_selection_fg=@edit_selection_fg

View File

@ -521,6 +521,10 @@ QComboBox {
border: 1px solid @combobox_border;
}
QComboBox:disabled {
color: @combobox_disabled_fg;
}
QComboBox:focus, QComboBox:on {
background-color: @combobox_focus_bg;
border: 2px solid @combobox_focus_border;
@ -540,6 +544,12 @@ QComboBox::down-arrow {
height: 20px;
}
QComboBox::down-arrow:disabled {
image: url(arrow_dropdown_disabled.svg);
width: 20px;
height: 20px;
}
QComboBox QAbstractItemView {
padding: 2px;
border: 1px solid @combobox_view_border;
@ -697,6 +707,10 @@ QLineEdit:focus {
background: @lineedit_focus_bg;
}
QLineEdit:disabled {
color: @lineedit_disabled_fg;
}
QLineEdit[VimCommandLine="true"] {
padding: 0px;
margin: 0px;

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<polygon fill="#C0C0C0" points="128,192 256,320 384,192 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@ -220,6 +220,7 @@ buttonmenuitem_decoration_text_fg=@master_dark_bg
combobox_border=@border_bg
combobox_fg=@content_fg
combobox_bg=@content_bg
combobox_disabled_fg=@disabled_fg
combobox_view_border=@border_bg
combobox_view_selected_bg=@selected_bg
combobox_view_selected_fg=@selected_fg
@ -246,6 +247,7 @@ label_titlelabel_bg=@title_bg
lineedit_border=@border_bg
lineedit_fg=@edit_fg
lineedit_bg=@edit_bg
lineedit_disabled_fg=@disabled_fg
lineedit_focus_bg=@edit_focus_bg
lineedit_focus_border=@edit_focus_border
lineedit_selection_fg=@edit_selection_fg

View File

@ -521,6 +521,10 @@ QComboBox {
border: 1px solid @combobox_border;
}
QComboBox:disabled {
color: @combobox_disabled_fg;
}
QComboBox:focus, QComboBox:on {
background-color: @combobox_focus_bg;
border: 2px solid @combobox_focus_border;
@ -540,6 +544,12 @@ QComboBox::down-arrow {
height: 20px;
}
QComboBox::down-arrow:disabled {
image: url(arrow_dropdown_disabled.svg);
width: 20px;
height: 20px;
}
QComboBox QAbstractItemView {
padding: 2px;
border: 1px solid @combobox_view_border;
@ -697,6 +707,10 @@ QLineEdit:focus {
background: @lineedit_focus_bg;
}
QLineEdit:disabled {
color: @lineedit_disabled_fg;
}
QLineEdit[VimCommandLine="true"] {
padding: 0px;
margin: 0px;

View File

@ -4,6 +4,6 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<polygon points="128,192 256,320 384,192 "/>
<polygon points="128,192 256,320 384,192 " fill="#333333"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 533 B

After

Width:  |  Height:  |  Size: 548 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<polygon points="128,192 256,320 384,192 " fill="#C0C0C0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@ -193,6 +193,7 @@ buttonmenuitem_decoration_text_fg=#00796B
combobox_border=@border_bg
combobox_fg=@content_fg
combobox_bg=@content_bg
combobox_disabled_fg=@disabled_fg
combobox_view_border=@border_bg
combobox_view_selected_bg=@selected_bg
combobox_view_selected_fg=@selected_fg
@ -210,6 +211,7 @@ label_titlelabel_bg=@title_bg
lineedit_border=@border_bg
lineedit_fg=@content_fg
lineedit_bg=@content_bg
lineedit_disabled_fg=@disabled_fg
lineedit_selection_fg=@selection_fg
lineedit_selection_bg=@selection_bg

View File

@ -453,6 +453,10 @@ QComboBox {
border: 1px solid @combobox_border;
}
QComboBox:disabled {
color: @combobox_disabled_fg;
}
QComboBox:focus {
background-color: @combobox_focus_bg;
}
@ -471,6 +475,12 @@ QComboBox::down-arrow {
height: 20px;
}
QComboBox::down-arrow:disabled {
image: url(arrow_dropdown_disabled.svg);
width: 20px;
height: 20px;
}
QComboBox QAbstractItemView {
padding: 2px;
border: 1px solid @combobox_view_border;
@ -602,6 +612,10 @@ QLineEdit {
selection-background-color: @lineedit_selection_bg;
}
QLineEdit:disabled {
color: @lineedit_disabled_fg;
}
QLineEdit[VimCommandLine="true"] {
padding: 0px;
margin: 0px;

View File

@ -201,6 +201,14 @@ single_click_close_previous_tab=true
; Whether enable auto wildcard match in simple search like list and tree widgets
enable_wildcard_in_simple_search=true
[export]
; Path of the wkhtmltopdf tool
wkhtmltopdf=wkhtmltopdf
; Additional arguments to wkhtmltopdf
; Double quotes to enclose arguments with spaces
wkhtmltopdfArgs=
[web]
; Location and configuration for Mathjax
mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_HTMLorMML

View File

@ -454,6 +454,12 @@ public:
bool getEnableAutoSave() const;
void setEnableAutoSave(bool p_enabled);
QString getWkhtmltopdfPath() const;
void setWkhtmltopdfPath(const QString &p_path);
QString getWkhtmltopdfArgs() const;
void setWkhtmltopdfArgs(const QString &p_args);
private:
// Look up a config from user and default settings.
QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@ -2083,4 +2089,26 @@ inline void VConfigManager::setEnableAutoSave(bool p_enabled)
{
setConfigToSettings("global", "enable_auto_save", p_enabled);
}
inline QString VConfigManager::getWkhtmltopdfPath() const
{
return getConfigFromSettings("export",
"wkhtmltopdf").toString();
}
inline void VConfigManager::setWkhtmltopdfPath(const QString &p_file)
{
setConfigToSettings("export", "wkhtmltopdf", p_file);
}
inline QString VConfigManager::getWkhtmltopdfArgs() const
{
return getConfigFromSettings("export",
"wkhtmltopdfArgs").toString();
}
inline void VConfigManager::setWkhtmltopdfArgs(const QString &p_file)
{
setConfigToSettings("export", "wkhtmltopdfArgs", p_file);
}
#endif // VCONFIGMANAGER_H

View File

@ -5,6 +5,7 @@
#include <QWebChannel>
#include <QWebEngineProfile>
#include <QRegExp>
#include <QProcess>
#include "vconfigmanager.h"
#include "vfile.h"
@ -27,6 +28,11 @@ VExporter::VExporter(QWidget *p_parent)
{
}
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,
@ -37,7 +43,100 @@ void VExporter::prepareExport(const ExportOption &p_opt)
m_exportHtmlTemplate = VUtils::generateExportHtmlTemplate(p_opt.m_renderBg);
m_pageLayout = *(p_opt.m_layout);
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);
}
// 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,
@ -149,6 +248,81 @@ bool VExporter::exportToPDF(VWebView *p_webViewer,
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());
QString htmlPath = p_filePath + ".vnote.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 vis wkhtmltopdf.
if (!htmlToPDFViaWK(htmlPath, p_filePath, p_opt, p_errMsg)) {
pdfExported = -1;
}
// Clean up.
VUtils::deleteFile(htmlPath);
VUtils::deleteDirectory(resFolderPath);
if (pdfExported == 0) {
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,
@ -195,9 +369,17 @@ bool VExporter::exportViaWebView(VFile *p_file,
bool exportRet = false;
switch (p_opt.m_format) {
case ExportFormat::PDF:
exportRet = exportToPDF(m_webViewer,
p_outputFile,
m_pageLayout);
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:
@ -261,7 +443,6 @@ bool VExporter::exportToHTML(VDocument *p_webDocument,
Q_ASSERT(!p_filePath.isEmpty());
QFile file(p_filePath);
if (!file.open(QFile::WriteOnly)) {
htmlExported = -1;
return;
@ -421,3 +602,55 @@ void VExporter::handleDownloadRequested(QWebEngineDownloadItem *p_item)
});
}
}
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::htmlToPDFViaWK(const QString &p_htmlFile,
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);
args << QDir::toNativeSeparators(p_htmlFile);
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;
}

View File

@ -5,6 +5,7 @@
#include <QPageLayout>
#include <QUrl>
#include <QWebEngineDownloadItem>
#include <QStringList>
#include "dialog/vexportdialog.h"
@ -14,6 +15,7 @@ class VDocument;
class VExporter : public QObject
{
Q_OBJECT
public:
explicit VExporter(QWidget *p_parent = nullptr);
@ -29,6 +31,10 @@ public:
const QString &p_outputFile,
QString *p_errMsg = NULL);
signals:
// Request to output log.
void outputLog(const QString &p_log);
private slots:
void handleLogicsFinished();
@ -76,6 +82,11 @@ private:
const QString &p_filePath,
const QPageLayout &p_layout);
bool exportToPDFViaWK(VDocument *p_webDocument,
const ExportPDFOption &p_opt,
const QString &p_filePath,
QString *p_errMsg = NULL);
bool exportToHTML(VDocument *p_webDocument,
const ExportHTMLOption &p_opt,
const QString &p_filePath);
@ -84,6 +95,12 @@ private:
const ExportHTMLOption &p_opt,
const QString &p_filePath);
bool htmlToPDFViaWK(const QString &p_htmlFile,
const QString &p_filePath,
const ExportPDFOption &p_opt,
QString *p_errMsg = NULL);
void prepareWKArguments(const ExportPDFOption &p_opt);
// Fix @p_html's resources like url("...") with "file" or "qrc" schema.
// Copy the resource to @p_folder and fix the url string.
@ -119,6 +136,9 @@ private:
// Download state used for MIME HTML.
QWebEngineDownloadItem::DownloadState m_downloadState;
// Arguments for wkhtmltopdf.
QStringList m_wkArgs;
};
inline void VExporter::clearNoteState()

View File

@ -240,5 +240,8 @@
<file>resources/themes/v_pure/v_pure_mermaid.css</file>
<file>resources/themes/v_white/v_white_mermaid.css</file>
<file>resources/themes/v_moonlight/v_moonlight_mermaid.css</file>
<file>resources/themes/v_moonlight/arrow_dropdown_disabled.svg</file>
<file>resources/themes/v_pure/arrow_dropdown_disabled.svg</file>
<file>resources/themes/v_white/arrow_dropdown_disabled.svg</file>
</qresource>
</RCC>