add new presentation mode for PDF export

This commit adds a new PDF export mode, called "presentation", similar
to what we get from a LaTeX-Beamer document (or Powerpoint).  When
exporting to this mode, each xournalpp page will be rendered to as
many pdf pages as there are layers on the xournalpp page. First, Layer
1 is rendered, then Layer 1 + Layer 2, etc. until all layers are
rendered.

It is accessible by selecting "Save as" in the File menu, and choosing
a PDF export. Then a specific box has to be checked in the Export
Dialog.

Exporting to presentation mode can also be done with the command-line
flag "--export-presentation". For instance:

xournalpp -p output.pdf --export-presentation file.xopp
upstream-master
Vu Ngoc San 5 years ago committed by Roland Lötscher
parent 451b825454
commit fffd1424c2
  1. 24
      src/control/XournalMain.cpp
  2. 4
      src/control/jobs/BaseExportJob.cpp
  3. 5
      src/control/jobs/CustomExportJob.cpp
  4. 5
      src/control/jobs/CustomExportJob.h
  5. 2
      src/control/jobs/PdfExportJob.cpp
  6. 10
      src/gui/dialog/ExportDialog.cpp
  7. 7
      src/gui/dialog/ExportDialog.h
  8. 40
      src/pdf/base/XojCairoPdfExport.cpp
  9. 8
      src/pdf/base/XojCairoPdfExport.h
  10. 4
      src/pdf/base/XojPdfExport.h
  11. 18
      ui/exportSettings.glade

@ -52,7 +52,8 @@ auto migrateSettings() -> MigrateResult;
void checkForErrorlog();
void checkForEmergencySave(Control* control);
auto exportPdf(const char* input, const char* output, const char* range, bool noBackground) -> int;
auto exportPdf(const char* input, const char* output, const char* range, bool noBackground, bool presentationMode)
-> int;
auto exportImg(const char* input, const char* output, const char* range, int pngDpi, int pngWidth, int pngHeight,
bool noBackground) -> int;
@ -301,10 +302,13 @@ auto exportImg(const char* input, const char* output, const char* range, int png
* @param output Path to the output file
* @param range Page range to be parsed. If range=nullptr, exports the whole file
* @param noBackground If true, the exported pdf file has white background
* @param presentationMode If true, then for each xournalpp page, instead of rendering one PDF page, the page layers are
* rendered one by one to produce as many pages as there are layers.
*
* @return 0 on success, -2 on failure opening the input file, -3 on export failure
*/
auto exportPdf(const char* input, const char* output, const char* range, bool noBackground) -> int {
auto exportPdf(const char* input, const char* output, const char* range, bool noBackground, bool presentationMode)
-> int {
LoadHandler loader;
Document* doc = loader.loadDocument(input);
@ -327,14 +331,14 @@ auto exportPdf(const char* input, const char* output, const char* range, bool no
// Parse the range
PageRangeVector exportRange = PageRange::parse(range, doc->getPageCount());
// Do the export
exportSuccess = pdfe->createPdf(path, exportRange);
exportSuccess = pdfe->createPdf(path, exportRange, presentationMode);
// Clean up
for (PageRangeEntry* e: exportRange) {
delete e;
}
exportRange.clear();
} else {
exportSuccess = pdfe->createPdf(path);
exportSuccess = pdfe->createPdf(path, presentationMode);
}
if (!exportSuccess) {
@ -370,7 +374,10 @@ struct XournalMainPrivate {
int exportPngDpi = -1;
int exportPngWidth = -1;
int exportPngHeight = -1;
bool exportNoBackground = false;
gboolean exportNoBackground =
false; // don't use bool, see
// https://stackoverflow.com/questions/21152042/is-glib-command-line-parsing-order-sensitive
gboolean presentationMode = false;
std::unique_ptr<GladeSearchpath> gladePath;
std::unique_ptr<Control> control;
std::unique_ptr<MainWindow> win;
@ -577,7 +584,7 @@ auto on_handle_local_options(GApplication*, GVariantDict*, XMPtr app_data) -> gi
if (app_data->pdfFilename && app_data->optFilename && *app_data->optFilename) {
return exportPdf(*app_data->optFilename, app_data->pdfFilename, app_data->exportRange,
app_data->exportNoBackground);
app_data->exportNoBackground, app_data->presentationMode);
}
if (app_data->imgFilename && app_data->optFilename && *app_data->optFilename) {
return exportImg(*app_data->optFilename, app_data->imgFilename, app_data->exportRange, app_data->exportPngDpi,
@ -631,6 +638,11 @@ auto XournalMain::run(int argc, char** argv) -> int {
" The exported file has transparent or white background,\n"
" depending on what its format supports\n"),
0},
GOptionEntry{"export-presentation", 0, 0, G_OPTION_ARG_NONE, &app_data.presentationMode,
_("Export in presentation mode\n"
" In PDF export, layers are rendered one by one,\n"
" suitable for presentation\n"),
0},
GOptionEntry{"export-range", 0, 0, G_OPTION_ARG_STRING, &app_data.exportRange,
_("Only export the pages specified by RANGE (e.g. \"2-3,5,7-\")\n"
" No effect without -p/--create-pdf or -i/--create-img"),

@ -68,7 +68,7 @@ auto BaseExportJob::showFilechooser() -> bool {
gtk_widget_destroy(dialog);
return false;
}
auto file = Util::fromGFilename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
auto file = Util::fromGFilename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
Util::clearExtensions(file);
// Since we add the extension after the OK button, we have to check manually on existing files
if (testAndSetFilepath(std::move(file)) && control->askToReplace(this->filepath)) {
@ -85,7 +85,7 @@ auto BaseExportJob::showFilechooser() -> bool {
auto BaseExportJob::testAndSetFilepath(fs::path file) -> bool {
try {
if(fs::is_directory(file.parent_path())){
if (fs::is_directory(file.parent_path())) {
this->filepath = std::move(file);
return true;
}

@ -75,7 +75,7 @@ auto CustomExportJob::showFilechooser() -> bool {
doc->lock();
auto* dlg = new ExportDialog(control->getGladeSearchPath());
if (filepath.extension() == ".pdf") {
dlg->removeQualitySetting();
dlg->showPresentationMode();
format = EXPORT_GRAPHICS_PDF;
} else if (filepath.extension() == ".svg") {
dlg->removeQualitySetting();
@ -94,6 +94,7 @@ auto CustomExportJob::showFilechooser() -> bool {
}
exportRange = dlg->getRange();
presentationMode = dlg->presentationMode();
if (format == EXPORT_GRAPHICS_PNG) {
pngQualityParameter = dlg->getPngQualityParameter();
@ -142,7 +143,7 @@ void CustomExportJob::run() {
pdfe->setNoBackgroundExport(filters[this->chosenFilterName]->withoutBackground);
if (!pdfe->createPdf(this->filepath, exportRange)) {
if (!pdfe->createPdf(this->filepath, exportRange, presentationMode)) {
this->errorMsg = pdfe->getLastError();
}

@ -67,6 +67,11 @@ private:
*/
bool exportTypeXoj = false;
/**
* Export Layers as pages
*/
bool presentationMode = false;
string lastError;
string chosenFilterName;

@ -32,7 +32,7 @@ void PdfExportJob::run() {
XojPdfExport* pdfe = XojPdfExportFactory::createExport(doc, control);
doc->unlock();
if (!pdfe->createPdf(this->filepath)) {
if (!pdfe->createPdf(this->filepath, false)) {
if (control->getWindow()) {
callAfterRun();
} else {

@ -8,6 +8,7 @@
ExportDialog::ExportDialog(GladeSearchpath* gladeSearchPath):
GladeGui(gladeSearchPath, "exportSettings.glade", "exportDialog") {
gtk_widget_hide(get("cbPresentationMode"));
g_signal_connect(get("rdRangePages"), "toggled", G_CALLBACK(+[](GtkToggleButton* togglebutton, ExportDialog* self) {
gtk_widget_set_sensitive(self->get("txtPages"), gtk_toggle_button_get_active(togglebutton));
}),
@ -44,6 +45,11 @@ void ExportDialog::removeQualitySetting() {
gtk_widget_hide(get("cbQuality"));
}
void ExportDialog::showPresentationMode() {
gtk_widget_show(get("cbPresentationMode"));
removeQualitySetting();
}
void ExportDialog::selectQualityCriterion(GtkComboBox* comboBox, ExportDialog* self) {
int activeCriterion = gtk_combo_box_get_active(comboBox);
switch (activeCriterion) {
@ -70,6 +76,10 @@ auto ExportDialog::getPngQualityParameter() -> RasterImageQualityParameter {
auto ExportDialog::isConfirmed() const -> bool { return this->confirmed; }
auto ExportDialog::presentationMode() -> bool {
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(get("cbPresentationMode")));
}
auto ExportDialog::getRange() -> PageRangeVector {
GtkWidget* rdRangeCurrent = get("rdRangeCurrent");
GtkWidget* rdRangePages = get("rdRangePages");

@ -27,6 +27,7 @@ public:
void initPages(int current, int count);
bool isConfirmed() const;
PageRangeVector getRange();
bool presentationMode();
/**
* @brief Reads the quality parameter from the dialog
@ -40,6 +41,12 @@ public:
*/
void removeQualitySetting();
/**
* @brief Show "presentation mode" checkbox and hide quality settings
* (both cannot be shown at the same time)
*/
void showPresentationMode();
/**
* @brief Handler for changes in combobox cbQuality
*/

@ -1,5 +1,6 @@
#include "XojCairoPdfExport.h"
#include <map>
#include <sstream>
#include <stack>
@ -119,7 +120,29 @@ void XojCairoPdfExport::exportPage(size_t page) {
cairo_restore(this->cr);
}
auto XojCairoPdfExport::createPdf(fs::path const& file, PageRangeVector& range) -> bool {
// export layers one by one to produce as many PDF pages as there are layers.
void XojCairoPdfExport::exportPageLayers(size_t page) {
PageRef p = doc->getPage(page);
// We keep a copy of the layers initial Visible state
std::map<Layer*, bool> initialVisibility;
for (const auto& layer: *p->getLayers()) {
initialVisibility[layer] = layer->isVisible();
layer->setVisible(false);
}
// We draw as many pages as there are layers. The first page has
// only Layer 1 visible, the last has all layers visible.
for (const auto& layer: *p->getLayers()) {
layer->setVisible(true);
exportPage(page);
}
// We restore the initial visibilities
for (const auto& layer: *p->getLayers()) layer->setVisible(initialVisibility[layer]);
}
auto XojCairoPdfExport::createPdf(fs::path const& file, PageRangeVector& range, bool presentationMode) -> bool {
if (range.empty()) {
this->lastError = _("No pages to export!");
return false;
@ -145,7 +168,11 @@ auto XojCairoPdfExport::createPdf(fs::path const& file, PageRangeVector& range)
continue;
}
exportPage(i);
if (presentationMode) {
exportPageLayers(i);
} else {
exportPage(i);
}
if (this->progressListener) {
this->progressListener->setCurrentState(c++);
@ -157,7 +184,7 @@ auto XojCairoPdfExport::createPdf(fs::path const& file, PageRangeVector& range)
return true;
}
auto XojCairoPdfExport::createPdf(fs::path const& file) -> bool {
auto XojCairoPdfExport::createPdf(fs::path const& file, bool presentationMode) -> bool {
if (doc->getPageCount() < 1) {
lastError = _("No pages to export!");
return false;
@ -173,7 +200,12 @@ auto XojCairoPdfExport::createPdf(fs::path const& file) -> bool {
}
for (int i = 0; i < count; i++) {
exportPage(i);
if (presentationMode) {
exportPageLayers(i);
} else {
exportPage(i);
}
if (this->progressListener) {
this->progressListener->setCurrentState(i);

@ -23,8 +23,8 @@ public:
virtual ~XojCairoPdfExport();
public:
virtual bool createPdf(fs::path const& file);
virtual bool createPdf(fs::path const& file, PageRangeVector& range);
virtual bool createPdf(fs::path const& file, bool presentationMode);
virtual bool createPdf(fs::path const& file, PageRangeVector& range, bool presentationMode);
virtual string getLastError();
/**
@ -47,6 +47,10 @@ private:
#endif
void endPdf();
void exportPage(size_t page);
/**
* Export as a PDF document where each additional layer creates a
* new page */
void exportPageLayers(size_t page);
private:
Document* doc = nullptr;

@ -24,8 +24,8 @@ public:
virtual ~XojPdfExport();
public:
virtual bool createPdf(fs::path const& file) = 0;
virtual bool createPdf(fs::path const& file, PageRangeVector& range) = 0;
virtual bool createPdf(fs::path const& file, bool presentationMode) = 0;
virtual bool createPdf(fs::path const& file, PageRangeVector& range, bool presentationMode) = 0;
virtual string getLastError() = 0;
/**

@ -328,6 +328,24 @@ Set export parameters</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="cbPresentationMode">
<property name="label" translatable="yes">Export as a presentation</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If enabled, the layers of each page will be added one by one. The resulting PDF file can be used for a presentation.</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<!-- Same place as "Image quality"; only one of them is shown -->
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>

Loading…
Cancel
Save