Andreas Butti 7 years ago
commit 9b1dc75a3f
  1. 19
      README.md
  2. 72
      src/control/XournalMain.cpp
  3. 3
      src/control/XournalMain.h
  4. 3
      src/control/jobs/BaseExportJob.h
  5. 155
      src/control/jobs/CustomExportJob.cpp
  6. 28
      src/control/jobs/CustomExportJob.h
  7. 215
      src/control/jobs/ImageExport.cpp
  8. 122
      src/control/jobs/ImageExport.h
  9. 9
      src/control/jobs/ProgressListener.h
  10. 3
      src/model/Document.cpp
  11. 3
      src/model/Document.h
  12. 5
      src/util/XournalTypeList.h

@ -118,7 +118,7 @@ into the file, so if the PDF is deleted, the background is lost.
Therefor Xournal++ reads *.xoj files, and can also export *.xoj.
On exporting to *.xoj all Xournal++ specific Extension are lost, like addtional
Background types.
*.xopp can theretically be read by Xournal, as long as you do not use any new
*.xopp can theoretically be read by Xournal, as long as you do not use any new
feature, Xournal does not open files at all if there are new attributes or
unknown values, because of this Xournal++ will add the extension .xopp to all
saved files.
@ -136,6 +136,23 @@ merged, even if they are not 100% finished.
See [GitHub:xournalpp](http://github.com/xournalpp/xournalpp) for current development. You can also join
our Gitter channel via badge on top.
## FAQ
### Q: Secondary stylus button only works when there is no contact
This is due to a driver setting, which you can configure with `TPCButton` or `TabletPCButton` directive if using `Wacom` driver (but other drivers might have this setting too).
Here's a `/usr/share/X11/xorg.conf.d/30-wacom.conf` example snippet:
```
Section "InputClass"
Identifier "Wacom tablets class"
MatchProduct "Wacom"
MatchDevicePath "/dev/input/event*"
MatchIsTablet "on"
Driver "wacom"
Option "TabletPCButton" "on"
Option "TPCButton" "on"
EndSection
```
## Code documentation
The code documentation is generated using Doxygen.

@ -2,6 +2,8 @@
#include "Control.h"
#include "control/jobs/ImageExport.h"
#include "control/jobs/ProgressListener.h"
#include "gui/GladeSearchpath.h"
#include "gui/MainWindow.h"
#include "gui/toolbarMenubar/model/ToolbarColorNames.h"
@ -11,6 +13,7 @@
#include "undo/EmergencySaveRestore.h"
#include "xojfile/LoadHandler.h"
#include <config.h>
#include <config-dev.h>
#include <config-paths.h>
@ -206,6 +209,58 @@ void XournalMain::checkForEmergencySave(Control* control) {
gtk_widget_destroy(dialog);
}
int XournalMain::exportImg(const char* input, const char* output)
{
XOJ_CHECK_TYPE(XournalMain);
LoadHandler loader;
Document* doc = loader.loadDocument(input);
if (doc == NULL)
{
g_error("%s", loader.getLastError().c_str());
return -2;
}
GFile* file = g_file_new_for_commandline_arg(output);
char* cpath = g_file_get_path(file);
string path = cpath;
g_free(cpath);
g_object_unref(file);
ExportGraphicsFormat format = EXPORT_GRAPHICS_PNG;
if (StringUtils::endsWith(path, ".svg"))
{
format = EXPORT_GRAPHICS_SVG;
}
PageRangeVector exportRange;
exportRange.push_back(new PageRangeEntry(0, doc->getPageCount() - 1));
DummyProgressListener progress;
ImageExport imgExport(doc, path, format, false, exportRange);
imgExport.exportGraphics(&progress);
for (PageRangeEntry* e : exportRange)
{
delete e;
}
exportRange.clear();
string errorMsg = imgExport.getLastErrorMsg();
if (errorMsg != "")
{
g_message("Error exporting image: %s\n", errorMsg.c_str());
return -3;
}
g_message("%s", _("Image file successfully created"));
return 0; // no error
}
int XournalMain::exportPdf(const char* input, const char* output)
{
XOJ_CHECK_TYPE(XournalMain);
@ -222,18 +277,20 @@ int XournalMain::exportPdf(const char* input, const char* output)
GFile* file = g_file_new_for_commandline_arg(output);
XojPdfExport* pdfe = XojPdfExportFactory::createExport(doc, NULL);
if (!pdfe->createPdf(g_file_get_path(file)))
char* cpath = g_file_get_path(file);
string path = cpath;
g_free(cpath);
g_object_unref(file);
if (!pdfe->createPdf(path))
{
g_error("%s", pdfe->getLastError().c_str());
g_object_unref(file);
delete pdfe;
return -3;
}
delete pdfe;
g_object_unref(file);
g_message("%s", _("PDF file successfully created"));
return 0; // no error
@ -250,13 +307,16 @@ int XournalMain::run(int argc, char* argv[])
gchar** optFilename = NULL;
gchar* pdfFilename = NULL;
gchar* imgFilename = NULL;
int openAtPageNumber = -1;
string create_pdf = _("PDF output filename");
string create_img = _("Image output filename (.png / .svg)");
string page_jump = _("Jump to Page (first Page: 1)");
string audio_folder = _("Absolute path for the audio files playback");
GOptionEntry options[] = {
{ "create-pdf", 'p', 0, G_OPTION_ARG_FILENAME, &pdfFilename, create_pdf.c_str(), NULL },
{ "create-img", 'i', 0, G_OPTION_ARG_FILENAME, &imgFilename, create_img.c_str(), NULL },
{ "page", 'n', 0, G_OPTION_ARG_INT, &openAtPageNumber, page_jump.c_str(), "N" },
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &optFilename, "<input>", NULL },
{NULL}
@ -280,6 +340,10 @@ int XournalMain::run(int argc, char* argv[])
{
return exportPdf(*optFilename, pdfFilename);
}
if (imgFilename && optFilename && *optFilename)
{
return exportImg(*optFilename, imgFilename);
}
// Checks for input method compatibility

@ -33,11 +33,12 @@ private:
void checkForEmergencySave(Control* control);
int exportPdf(const char* input, const char* output);
int exportImg(const char* input, const char* output);
void initSettingsPath();
void initResourcePath(GladeSearchpath* gladePath);
string findResourcePath(string searchFile);
private:
XOJ_TYPE_ATTRIB;
};

@ -48,6 +48,9 @@ protected:
Path filename;
/**
* Error message to show to the user
*/
string errorMsg;
class ExportType

@ -1,5 +1,6 @@
#include "CustomExportJob.h"
#include "SaveJob.h"
#include "ImageExport.h"
#include "control/Control.h"
#include "control/xojfile/XojExportHandler.h"
@ -23,6 +24,8 @@ CustomExportJob::CustomExportJob(Control* control)
filters[_("PDF with plain background")] = new ExportType(".pdf", true);
filters[_("PNG graphics")] = new ExportType(".png", false);
filters[_("PNG with transparent background")] = new ExportType(".png", true);
filters[_("SVG graphics")] = new ExportType(".svg", false);
filters[_("SVG with transparent background")] = new ExportType(".svg", true);
filters[_("Xournal (Compatibility)")] = new ExportType(".xoj", false);
}
@ -41,7 +44,6 @@ CustomExportJob::~CustomExportJob()
delete filter.second;
}
XOJ_RELEASE_TYPE(CustomExportJob);
}
@ -94,7 +96,17 @@ bool CustomExportJob::showFilechooser()
if (filename.hasExtension(".pdf"))
{
dlg->removeDpiSelection();
exportTypePdf = true;
format = EXPORT_GRAPHICS_PDF;
}
else if (filename.hasExtension(".svg"))
{
dlg->removeDpiSelection();
format = EXPORT_GRAPHICS_SVG;
}
else if (filename.hasExtension(".png"))
{
dlg->removeDpiSelection();
format = EXPORT_GRAPHICS_PNG;
}
dlg->initPages(control->getCurrentPageNo() + 1, doc->getPageCount());
@ -114,141 +126,20 @@ bool CustomExportJob::showFilechooser()
return true;
}
void CustomExportJob::createSurface(double width, double height)
{
XOJ_CHECK_TYPE(CustomExportJob);
this->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width * this->pngDpi / 72.0,
height * this->pngDpi / 72.0);
this->cr = cairo_create(this->surface);
double factor = this->pngDpi / 72.0;
cairo_scale(this->cr, factor, factor);
}
bool CustomExportJob::freeSurface(int id)
{
XOJ_CHECK_TYPE(CustomExportJob);
cairo_destroy(this->cr);
string filepath = getFilenameWithNumber(id);
cairo_status_t status = cairo_surface_write_to_png(surface, filepath.c_str());
cairo_surface_destroy(surface);
// we ignore this problem
if (status != CAIRO_STATUS_SUCCESS)
{
return false;
}
return true;
}
/**
* Get a filename with a numer, e.g. .../export-1.png, if the no is -1, return .../export.png
*/
string CustomExportJob::getFilenameWithNumber(int no)
{
if (no == -1)
{
// No number to add
return filename.str();
}
string filepath = filename.str();
size_t dotPos = filepath.find_last_of(".");
if (dotPos == string::npos)
{
// No file extension, add number
return filepath + "-" + std::to_string(no);
}
return filepath.substr(0, dotPos) + "-" + std::to_string(no) + filepath.substr(dotPos);
}
/**
* Export a single PNG page
* Create one Graphics file per page
*/
void CustomExportJob::exportPngPage(int pageId, int id, double zoom, DocumentView& view)
{
Document* doc = control->getDocument();
doc->lock();
PageRef page = doc->getPage(pageId);
doc->unlock();
createSurface(page->getWidth(), page->getHeight());
if (page->getBackgroundType().isPdfPage())
{
int pgNo = page->getPdfPageNr();
XojPdfPageSPtr popplerPage = doc->getPdfPage(pgNo);
PdfView::drawPage(NULL, popplerPage, cr, zoom, page->getWidth(), page->getHeight());
}
bool hideBackground = filters[this->chosenFilterName]->withoutBackground;
view.drawPage(page, this->cr, true, hideBackground);
if (!freeSurface(id))
{
// could not create this file...
this->errorMsg = _("Error export PDF Page");
return;
}
}
/**
* Create one PNG file per page
*/
void CustomExportJob::exportPng()
void CustomExportJob::exportGraphics()
{
XOJ_CHECK_TYPE(CustomExportJob);
// don't lock the page here for the whole flow, else we get a dead lock...
// the ui is blocked, so there should be no changes...
Document* doc = control->getDocument();
int count = doc->getPageCount();
bool onePage = ((this->exportRange.size() == 1) && (this->exportRange[0]->getFirst() == this->exportRange[0]->getLast()));
char selectedPages[count];
int selectedCount = 0;
for (int i = 0; i < count; i++)
{
selectedPages[i] = 0;
}
for (PageRangeEntry* e : this->exportRange)
{
for (int x = e->getFirst(); x <= e->getLast(); x++)
{
selectedPages[x] = 1;
selectedCount++;
}
}
this->control->setMaximumState(selectedCount);
bool hideBackground = filters[this->chosenFilterName]->withoutBackground;
DocumentView view;
double zoom = this->pngDpi / 72.0;
int current = 0;
ImageExport imgExport(control->getDocument(), filename, format, hideBackground, exportRange);
imgExport.setPngDpi(pngDpi);
imgExport.exportGraphics(control);
for (int i = 0; i < count; i++)
{
int id = i + 1;
if (onePage)
{
id = -1;
}
if (selectedPages[i])
{
this->control->setCurrentState(current++);
exportPngPage(i, id, zoom, view);
}
}
errorMsg = imgExport.getLastErrorMsg();
}
void CustomExportJob::run()
@ -273,7 +164,7 @@ void CustomExportJob::run()
callAfterRun();
}
}
else if (exportTypePdf)
else if (format == EXPORT_GRAPHICS_PDF)
{
// don't lock the page here for the whole flow, else we get a dead lock...
// the ui is blocked, so there should be no changes...
@ -292,7 +183,7 @@ void CustomExportJob::run()
}
else
{
exportPng();
exportGraphics();
}
}

@ -12,6 +12,7 @@
#pragma once
#include "BaseExportJob.h"
#include "ImageExport.h"
#include "view/DocumentView.h"
@ -19,6 +20,7 @@
#include <i18n.h>
#include <map>
class CustomExportJob : public BaseExportJob
{
public:
@ -39,33 +41,29 @@ protected:
virtual void addFilterToDialog();
/**
* Export a single PNG page
* Create one Graphics file per page
*/
void exportPngPage(int pageId, int id, double zoom, DocumentView& view);
void exportGraphics();
/**
* Create one PNG file per page
*/
void exportPng();
void createSurface(double width, double height);
bool freeSurface(int id);
string getFilenameWithNumber(int no);
virtual bool isUriValid(string& uri);
private:
XOJ_TYPE_ATTRIB;
/**
* The range to export
*/
PageRangeVector exportRange;
int pngDpi = 300;
cairo_surface_t* surface = NULL;
cairo_t* cr = NULL;
/**
* PNG dpi
*/
int pngDpi = 300;
/**
* PDF Export, else PNG Export
* Export graphics format
*/
bool exportTypePdf = false;
ExportGraphicsFormat format = EXPORT_GRAPHICS_UNDEFINED;
/**
* XOJ Export, else PNG Export

@ -0,0 +1,215 @@
#include "ImageExport.h"
#include "control/jobs/ProgressListener.h"
#include "model/Document.h"
#include "view/PdfView.h"
#include <cairo-svg.h>
#include <i18n.h>
ImageExport::ImageExport(Document* doc, Path filename, ExportGraphicsFormat format, bool hideBackground, PageRangeVector& exportRange)
: doc(doc),
filename(filename),
format(format),
hideBackground(hideBackground),
exportRange(exportRange)
{
XOJ_INIT_TYPE(ImageExport);
}
ImageExport::~ImageExport()
{
XOJ_CHECK_TYPE(ImageExport);
XOJ_RELEASE_TYPE(ImageExport);
}
/**
* PNG dpi
*/
void ImageExport::setPngDpi(int dpi)
{
XOJ_CHECK_TYPE(ImageExport);
this->pngDpi = dpi;
}
/**
* @return the last error message to show to the user
*/
string ImageExport::getLastErrorMsg()
{
XOJ_CHECK_TYPE(ImageExport);
return lastError;
}
/**
* Create surface
*/
void ImageExport::createSurface(double width, double height, int id)
{
XOJ_CHECK_TYPE(ImageExport);
if (format == EXPORT_GRAPHICS_PNG)
{
this->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width * this->pngDpi / 72.0,
height * this->pngDpi / 72.0);
this->cr = cairo_create(this->surface);
double factor = this->pngDpi / 72.0;
cairo_scale(this->cr, factor, factor);
}
else if (format == EXPORT_GRAPHICS_SVG)
{
string filepath = getFilenameWithNumber(id);
this->surface = cairo_svg_surface_create(filepath.c_str(), width, height);
cairo_svg_surface_restrict_to_version(this->surface, CAIRO_SVG_VERSION_1_2);
this->cr = cairo_create(this->surface);
}
else
{
g_error("Unsupported graphics format: %i", format);
}
}
/**
* Free / store the surface
*/
bool ImageExport::freeSurface(int id)
{
XOJ_CHECK_TYPE(ImageExport);
cairo_destroy(this->cr);
cairo_status_t status = CAIRO_STATUS_SUCCESS;
if (format == EXPORT_GRAPHICS_PNG)
{
string filepath = getFilenameWithNumber(id);
status = cairo_surface_write_to_png(surface, filepath.c_str());
}
cairo_surface_destroy(surface);
// we ignore this problem
if (status != CAIRO_STATUS_SUCCESS)
{
return false;
}
return true;
}
/**
* Get a filename with a number, e.g. .../export-1.png, if the no is -1, return .../export.png
*/
string ImageExport::getFilenameWithNumber(int no)
{
XOJ_CHECK_TYPE(ImageExport);
if (no == -1)
{
// No number to add
return filename.str();
}
string filepath = filename.str();
size_t dotPos = filepath.find_last_of(".");
if (dotPos == string::npos)
{
// No file extension, add number
return filepath + "-" + std::to_string(no);
}
return filepath.substr(0, dotPos) + "-" + std::to_string(no) + filepath.substr(dotPos);
}
/**
* Export a single PNG page
*/
void ImageExport::exportImagePage(int pageId, int id, double zoom, ExportGraphicsFormat format, DocumentView& view)
{
XOJ_CHECK_TYPE(ImageExport);
doc->lock();
PageRef page = doc->getPage(pageId);
doc->unlock();
createSurface(page->getWidth(), page->getHeight(), id);
cairo_status_t state = cairo_surface_status(this->surface);
if (state != CAIRO_STATUS_SUCCESS)
{
this->lastError = _("Error save image #1");
return;
}
if (page->getBackgroundType().isPdfPage())
{
int pgNo = page->getPdfPageNr();
XojPdfPageSPtr popplerPage = doc->getPdfPage(pgNo);
PdfView::drawPage(NULL, popplerPage, cr, zoom, page->getWidth(), page->getHeight());
}
view.drawPage(page, this->cr, true, hideBackground);
if (!freeSurface(id))
{
// could not create this file...
this->lastError = _("Error save image #2");
return;
}
}
/**
* Create one Graphics file per page
*/
void ImageExport::exportGraphics(ProgressListener* stateListener)
{
XOJ_CHECK_TYPE(ImageExport);
// don't lock the page here for the whole flow, else we get a dead lock...
// the ui is blocked, so there should be no changes...
int count = doc->getPageCount();
bool onePage = ((this->exportRange.size() == 1) && (this->exportRange[0]->getFirst() == this->exportRange[0]->getLast()));
char selectedPages[count];
int selectedCount = 0;
for (int i = 0; i < count; i++)
{
selectedPages[i] = 0;
}
for (PageRangeEntry* e : this->exportRange)
{
for (int x = e->getFirst(); x <= e->getLast(); x++)
{
selectedPages[x] = 1;
selectedCount++;
}
}
stateListener->setMaximumState(selectedCount);
DocumentView view;
double zoom = this->pngDpi / 72.0;
int current = 0;
for (int i = 0; i < count; i++)
{
int id = i + 1;
if (onePage)
{
id = -1;
}
if (selectedPages[i])
{
stateListener->setCurrentState(current++);
exportImagePage(i, id, zoom, format, view);
}
}
}

@ -0,0 +1,122 @@
/*
* Xournal++
*
* Image export implementation
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include "view/DocumentView.h"
#include <PageRange.h>
#include <Path.h>
#include <XournalType.h>
#include <gtk/gtk.h>
class Document;
class ProgressListener;
enum ExportGraphicsFormat {
EXPORT_GRAPHICS_UNDEFINED,
EXPORT_GRAPHICS_PDF,
EXPORT_GRAPHICS_PNG,
EXPORT_GRAPHICS_SVG
};
class ImageExport
{
public:
ImageExport(Document* doc, Path filename, ExportGraphicsFormat format, bool hideBackground, PageRangeVector& exportRange);
virtual ~ImageExport();
public:
/**
* PNG dpi
*/
void setPngDpi(int dpi);
/**
* @return The last error message to show to the user
*/
string getLastErrorMsg();
/**
* Create one Graphics file per page
*/
void exportGraphics(ProgressListener* stateListener);
private:
/**
* Create surface
*/
void createSurface(double width, double height, int id);
/**
* Free / store the surface
*/
bool freeSurface(int id);
/**
* Get a filename with a number, e.g. .../export-1.png, if the no is -1, return .../export.png
*/
string getFilenameWithNumber(int no);
/**
* Export a single Image page
*/
void exportImagePage(int pageId, int id, double zoom, ExportGraphicsFormat format, DocumentView& view);
public:
XOJ_TYPE_ATTRIB;
/**
* Document to export
*/
Document* doc = NULL;
/**
* Filename for export
*/
Path filename;
/**
* Export graphics format
*/
ExportGraphicsFormat format = EXPORT_GRAPHICS_UNDEFINED;
/**
* Do not export the Background
*/
bool hideBackground = false;
/**
* The range to export
*/
PageRangeVector& exportRange;
/**
* PNG dpi
*/
int pngDpi = 300;
/**
* Export surface
*/
cairo_surface_t* surface = NULL;
/**
* Cairo context
*/
cairo_t* cr = NULL;
/**
* The last error message to show to the user
*/
string lastError;
};

@ -19,3 +19,12 @@ public:
virtual ~ProgressListener() { };
};
class DummyProgressListener : public ProgressListener
{
public:
virtual void setMaximumState(int max) {};
virtual void setCurrentState(int state) {};
virtual ~DummyProgressListener() { };
};

@ -435,6 +435,9 @@ void Document::setPageSize(PageRef p, double width, double height)
}
}
/**
* @return The last error message to show to the user
*/
string Document::getLastErrorMsg()
{
XOJ_CHECK_TYPE(Document);

@ -54,6 +54,9 @@ public:
size_t indexOf(PageRef page);
/**
* @return The last error message to show to the user
*/
string getLastErrorMsg();
bool isPdfDocumentLoaded();

@ -155,8 +155,8 @@ XOJ_DECLARE_TYPE(PdfExportJob, 144);
XOJ_DECLARE_TYPE(BackgroundImage, 145);
XOJ_DECLARE_TYPE(PartList, 146);
XOJ_DECLARE_TYPE(PageRangeEntry, 147);
XOJ_DECLARE_TYPE(ImageExport, 148);
XOJ_DECLARE_TYPE(ColorSelectImage, 149);
XOJ_DECLARE_TYPE(EditSelectionContents, 150);
XOJ_DECLARE_TYPE(PageLayerPosEntry, 151);
XOJ_DECLARE_TYPE(BackgroundImageContents, 152);
@ -285,7 +285,6 @@ XOJ_DECLARE_TYPE(Plugin, 275);
XOJ_DECLARE_TYPE(MenuEntry, 276);
XOJ_DECLARE_TYPE(PluginDialog, 277);
XOJ_DECLARE_TYPE(PluginDialogEntry, 278);
XOJ_DECLARE_TYPE(ColorSelectImage, 279);

Loading…
Cancel
Save