diff --git a/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro b/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro
index 60f81b9..69d1e45 100644
--- a/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro
+++ b/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro
@@ -42,6 +42,7 @@ INSTALLS += application
SOURCES += \
aboutdialog.cpp \
+ assembleoutputsettingsdialog.cpp \
main.cpp \
mainwindow.cpp \
pageitemdelegate.cpp \
@@ -49,10 +50,12 @@ SOURCES += \
FORMS += \
aboutdialog.ui \
+ assembleoutputsettingsdialog.ui \
mainwindow.ui
HEADERS += \
aboutdialog.h \
+ assembleoutputsettingsdialog.h \
mainwindow.h \
pageitemdelegate.h \
pageitemmodel.h
diff --git a/Pdf4QtDocPageOrganizer/aboutdialog.h b/Pdf4QtDocPageOrganizer/aboutdialog.h
index 0a5dd75..66507b4 100644
--- a/Pdf4QtDocPageOrganizer/aboutdialog.h
+++ b/Pdf4QtDocPageOrganizer/aboutdialog.h
@@ -15,8 +15,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with Pdf4Qt. If not, see .
-#ifndef PDFABOUTDIALOG_H
-#define PDFABOUTDIALOG_H
+#ifndef PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H
+#define PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H
#include
@@ -42,4 +42,4 @@ private:
} // namespace pdfdocpage
-#endif // PDFABOUTDIALOG_H
+#endif // PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H
diff --git a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp
new file mode 100644
index 0000000..b6d587c
--- /dev/null
+++ b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 Jakub Melka
+//
+// This file is part of Pdf4Qt.
+//
+// Pdf4Qt is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// with the written consent of the copyright owner, any later version.
+//
+// Pdf4Qt is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Pdf4Qt. If not, see .
+
+#include "assembleoutputsettingsdialog.h"
+#include "ui_assembleoutputsettingsdialog.h"
+
+#include "pdfwidgetutils.h"
+
+#include
+
+namespace pdfdocpage
+{
+
+AssembleOutputSettingsDialog::AssembleOutputSettingsDialog(QString directory, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::AssembleOutputSettingsDialog)
+{
+ ui->setupUi(this);
+ ui->directoryEdit->setText(directory);
+
+ pdf::PDFWidgetUtils::scaleWidget(this, QSize(450, 150));
+}
+
+AssembleOutputSettingsDialog::~AssembleOutputSettingsDialog()
+{
+ delete ui;
+}
+
+QString AssembleOutputSettingsDialog::getDirectory() const
+{
+ return ui->directoryEdit->text();
+}
+
+QString AssembleOutputSettingsDialog::getFileName() const
+{
+ return ui->fileTemplateEdit->text();
+}
+
+bool AssembleOutputSettingsDialog::isOverwriteFiles() const
+{
+ return ui->overwriteFilesCheckBox->isChecked();
+}
+
+void AssembleOutputSettingsDialog::on_selectDirectoryButton_clicked()
+{
+ QString directory = QFileDialog::getExistingDirectory(this, tr("Select output directory"), ui->directoryEdit->text());
+ if (!directory.isEmpty())
+ {
+ ui->directoryEdit->setText(directory);
+ }
+}
+
+} // namespace pdfdocpage
diff --git a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h
new file mode 100644
index 0000000..0968d60
--- /dev/null
+++ b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 Jakub Melka
+//
+// This file is part of Pdf4Qt.
+//
+// Pdf4Qt is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// with the written consent of the copyright owner, any later version.
+//
+// Pdf4Qt is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Pdf4Qt. If not, see .
+
+#ifndef PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H
+#define PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H
+
+#include
+
+namespace Ui
+{
+class AssembleOutputSettingsDialog;
+}
+
+namespace pdfdocpage
+{
+
+class AssembleOutputSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AssembleOutputSettingsDialog(QString directory, QWidget* parent);
+ virtual ~AssembleOutputSettingsDialog() override;
+
+ QString getDirectory() const;
+ QString getFileName() const;
+ bool isOverwriteFiles() const;
+
+private slots:
+ void on_selectDirectoryButton_clicked();
+
+private:
+ Ui::AssembleOutputSettingsDialog* ui;
+};
+
+} // namespace pdfdocpage
+
+#endif // PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H
diff --git a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui
new file mode 100644
index 0000000..32da2b7
--- /dev/null
+++ b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui
@@ -0,0 +1,121 @@
+
+
+ AssembleOutputSettingsDialog
+
+
+
+ 0
+ 0
+ 614
+ 213
+
+
+
+ Assemble Documents
+
+
+ -
+
+
+ Assemble Documents
+
+
+
-
+
+
+ File template
+
+
+
+ -
+
+
+ ...
+
+
+
+ -
+
+
+ <html><head/><body><p>In a template file name, you can use symbols '#' for output document number (means output document index, not input document) or '@' for page number of input document (if document contains more pages, it is a page number of a original document), or '%' for index of input document. Use more '#' or '@' or '%' for setting minimal number of digits (if number has less digits, the they are padded with zero).</p></body></html>
+
+
+ true
+
+
+
+ -
+
+
+ doc-#.pdf
+
+
+
+ -
+
+
+ Generate into directory
+
+
+
+ -
+
+
+ -
+
+
+ Overwrite existing files
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ AssembleOutputSettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ AssembleOutputSettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/Pdf4QtDocPageOrganizer/mainwindow.cpp b/Pdf4QtDocPageOrganizer/mainwindow.cpp
index f3b737f..a4bda46 100644
--- a/Pdf4QtDocPageOrganizer/mainwindow.cpp
+++ b/Pdf4QtDocPageOrganizer/mainwindow.cpp
@@ -19,6 +19,7 @@
#include "ui_mainwindow.h"
#include "aboutdialog.h"
+#include "assembleoutputsettingsdialog.h"
#include "pdfwidgetutils.h"
#include "pdfdocumentreader.h"
@@ -489,9 +490,102 @@ void MainWindow::performOperation(Operation operation)
break;
}
- case Operation::ReplaceSelection:
- Q_ASSERT(false);
+ case Operation::Unite:
+ case Operation::Separate:
+ case Operation::SeparateGrouped:
+ {
+ PageItemModel::AssembleMode assembleMode = PageItemModel::AssembleMode::Unite;
+
+ switch (operation)
+ {
+ case Operation::Unite:
+ assembleMode = PageItemModel::AssembleMode::Unite;
+ break;
+
+ case Operation::Separate:
+ assembleMode = PageItemModel::AssembleMode::Separate;
+ break;
+
+ case Operation::SeparateGrouped:
+ assembleMode = PageItemModel::AssembleMode::SeparateGrouped;
+ break;
+
+ default:
+ Q_ASSERT(false);
+ }
+
+ std::vector> assembledDocuments = m_model->getAssembledPages(assembleMode);
+
+ // Check we have something to process
+ if (assembledDocuments.empty())
+ {
+ QMessageBox::critical(this, tr("Error"), tr("No documents to assemble."));
+ break;
+ }
+
+ AssembleOutputSettingsDialog dialog(m_settings.directory, this);
+ if (dialog.exec() == QDialog::Accepted)
+ {
+ pdf::PDFDocumentManipulator manipulator;
+
+ // Add documents and images
+ for (const auto& documentItem : m_model->getDocuments())
+ {
+ manipulator.addDocument(documentItem.first, &documentItem.second.document);
+ }
+ for (const auto& imageItem : m_model->getImages())
+ {
+ manipulator.addImage(imageItem.first, imageItem.second.image);
+ }
+
+ // Jakub Melka: create assembled documents
+ pdf::PDFOperationResult result(true);
+ std::vector> assembledDocumentStorage;
+
+ int sourceDocumentIndex = 1;
+ int assembledDocumentIndex = 1;
+ int sourcePageIndex = 1;
+ int documentCount = int(m_model->getDocuments().size());
+
+ QString directory = dialog.getDirectory();
+ QString fileNameTemplate = dialog.getFileName();
+ const bool isOverwriteEnabled = dialog.isOverwriteFiles();
+
+ for (const std::vector& assembledPages : assembledDocuments)
+ {
+ pdf::PDFOperationResult currentResult = manipulator.assemble(assembledPages);
+ if (!currentResult && result)
+ {
+ result = currentResult;
+ break;
+ }
+
+ pdf::PDFDocumentManipulator::AssembledPage samplePage = assembledPages.front();
+ sourceDocumentIndex = samplePage.documentIndex == -1 ? documentCount + samplePage.imageIndex : samplePage.documentIndex;
+ sourcePageIndex = qMax(samplePage.pageIndex + 1, 1);
+
+ QString fileName = fileNameTemplate;
+
+ if (!fileName.endsWith(".pdf"))
+ {
+ fileName += ".pdf";
+ }
+
+ assembledDocumentStorage.emplace_back(std::make_pair(std::move(fileName), manipulator.takeAssembledDocument()));
+ ++assembledDocumentIndex;
+ }
+
+ if (!result)
+ {
+ QMessageBox::critical(this, tr("Error"), result.getErrorMessage());
+ break;
+ }
+
+
+ }
+
break;
+ }
case Operation::InsertImage:
{
@@ -512,10 +606,9 @@ void MainWindow::performOperation(Operation operation)
}
case Operation::InsertPDF:
-
- case Operation::Unite:
- case Operation::Separate:
- case Operation::SeparateGrouped:
+ case Operation::ReplaceSelection:
+ Q_ASSERT(false);
+ break;
default:
Q_ASSERT(false);
diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp
index 32a6fa3..6e2d2fe 100644
--- a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp
+++ b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp
@@ -848,6 +848,97 @@ Qt::ItemFlags PageItemModel::flags(const QModelIndex& index) const
return flags;
}
+std::vector> PageItemModel::getAssembledPages(AssembleMode mode) const
+{
+ std::vector> result;
+
+ auto createAssembledPage = [this](const PageGroupItem::GroupItem& item)
+ {
+ pdf::PDFDocumentManipulator::AssembledPage assembledPage;
+
+ assembledPage.documentIndex = item.documentIndex;
+ assembledPage.imageIndex = item.imageIndex;
+ assembledPage.pageIndex = item.pageIndex;
+
+ if (assembledPage.pageIndex > 0)
+ {
+ --assembledPage.pageIndex;
+ }
+
+ pdf::PageRotation originalPageRotation = pdf::PageRotation::None;
+ if (item.pageType == PT_DocumentPage)
+ {
+ auto it = m_documents.find(item.documentIndex);
+ if (it != m_documents.cend())
+ {
+ const pdf::PDFPage* page = it->second.document.getCatalog()->getPage(item.pageIndex - 1);
+ originalPageRotation = page->getPageRotation();
+ }
+ }
+
+ assembledPage.pageRotation = pdf::getPageRotationCombined(originalPageRotation, item.pageAdditionalRotation);
+ assembledPage.pageSize = pdf::PDFPage::getRotatedSize(item.rotatedPageDimensionsMM, pdf::getPageRotationInversed(item.pageAdditionalRotation));
+
+ return assembledPage;
+ };
+
+ switch (mode)
+ {
+ case AssembleMode::Unite:
+ {
+ std::vector unitedDocument;
+
+ for (const auto& pageGroupItem : m_pageGroupItems)
+ {
+ for (const auto& groupItem : pageGroupItem.groups)
+ {
+ unitedDocument.emplace_back(createAssembledPage(groupItem));
+ }
+ }
+
+ result.emplace_back(std::move(unitedDocument));
+ break;
+ }
+
+ case AssembleMode::Separate:
+ {
+ for (const auto& pageGroupItem : m_pageGroupItems)
+ {
+ for (const auto& groupItem : pageGroupItem.groups)
+ {
+ result.emplace_back().emplace_back(createAssembledPage(groupItem));
+ }
+ }
+ break;
+ }
+
+ case AssembleMode::SeparateGrouped:
+ {
+ for (const auto& pageGroupItem : m_pageGroupItems)
+ {
+ std::vector groupDocument;
+ for (const auto& groupItem : pageGroupItem.groups)
+ {
+ groupDocument.emplace_back(createAssembledPage(groupItem));
+ }
+ result.emplace_back(std::move(groupDocument));
+ }
+ break;
+ }
+
+ default:
+ {
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ // Remove empty documents
+ result.erase(std::remove_if(result.begin(), result.end(), [](const auto& pages) { return pages.empty(); }), result.end());
+
+ return result;
+}
+
void PageItemModel::clear()
{
beginResetModel();
diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.h b/Pdf4QtDocPageOrganizer/pageitemmodel.h
index 016e8c0..8b3db55 100644
--- a/Pdf4QtDocPageOrganizer/pageitemmodel.h
+++ b/Pdf4QtDocPageOrganizer/pageitemmodel.h
@@ -20,6 +20,7 @@
#include "pdfdocument.h"
#include "pdfutils.h"
+#include "pdfdocumentmanipulator.h"
#include
#include
@@ -100,6 +101,15 @@ public:
virtual Qt::DropActions supportedDragActions() const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
+ enum class AssembleMode
+ {
+ Unite,
+ Separate,
+ SeparateGrouped
+ };
+
+ std::vector> getAssembledPages(AssembleMode mode) const;
+
/// Clear all data and undo/redo
void clear();
@@ -153,6 +163,9 @@ public:
static QString getMimeDataType() { return QLatin1String("application/pagemodel.PDF4QtDocPageOrganizer"); }
+ const std::map& getDocuments() const { return m_documents; }
+ const std::map& getImages() const { return m_images; }
+
private:
void createDocumentGroup(int index);
QString getGroupNameFromDocument(int index) const;
diff --git a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp
index 5dd2fff..12603d7 100644
--- a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp
+++ b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp
@@ -29,6 +29,10 @@ PDFOperationResult PDFDocumentManipulator::assemble(const AssembledPages& pages)
return tr("Empty page list.");
}
+ m_flags = None;
+ m_mergedObjects = { };
+ m_assembledDocument = PDFDocument();
+
try
{
classify(pages);
diff --git a/Pdf4QtLib/sources/pdfpage.cpp b/Pdf4QtLib/sources/pdfpage.cpp
index 68dcd39..f0e29d7 100644
--- a/Pdf4QtLib/sources/pdfpage.cpp
+++ b/Pdf4QtLib/sources/pdfpage.cpp
@@ -207,6 +207,23 @@ PDFObject PDFPage::getOutputIntents(const PDFObjectStorage* storage) const
return getObjectFromPageDictionary(storage, "OutputIntents");
}
+QSizeF PDFPage::getRotatedSize(const QSizeF& size, PageRotation rotation)
+{
+ switch (rotation)
+ {
+ case PageRotation::None:
+ case PageRotation::Rotate180:
+ // Preserve rotation
+ break;
+
+ case PageRotation::Rotate90:
+ case PageRotation::Rotate270:
+ return size.transposed();
+ }
+
+ return size;
+}
+
QRectF PDFPage::getRotatedBox(const QRectF& rect, PageRotation rotation)
{
switch (rotation)
diff --git a/Pdf4QtLib/sources/pdfpage.h b/Pdf4QtLib/sources/pdfpage.h
index 5195799..34093dc 100644
--- a/Pdf4QtLib/sources/pdfpage.h
+++ b/Pdf4QtLib/sources/pdfpage.h
@@ -84,6 +84,34 @@ constexpr PageRotation getPageRotationRotatedLeft(PageRotation rotation)
return PageRotation::None;
}
+constexpr PageRotation getPageRotationCombined(PageRotation r1, PageRotation r2)
+{
+ while (r1 != PageRotation::None)
+ {
+ r2 = getPageRotationRotatedRight(r2);
+ r1 = getPageRotationRotatedLeft(r1);
+ }
+
+ return r2;
+}
+
+constexpr PageRotation getPageRotationInversed(PageRotation rotation)
+{
+ switch (rotation)
+ {
+ case PageRotation::None:
+ return PageRotation::None;
+ case PageRotation::Rotate90:
+ return PageRotation::Rotate270;
+ case PageRotation::Rotate180:
+ return PageRotation::Rotate180;
+ case PageRotation::Rotate270:
+ return PageRotation::Rotate90;
+ }
+
+ return PageRotation::None;
+}
+
/// This class represents attributes, which are inheritable. Also allows merging from
/// parents.
class PDFPageInheritableAttributes
@@ -241,6 +269,7 @@ public:
/// of 1 / 72 inch. Default value is 1.0.
inline PDFReal getUserUnit() const { return m_userUnit; }
+ static QSizeF getRotatedSize(const QSizeF& size, PageRotation rotation);
static QRectF getRotatedBox(const QRectF& rect, PageRotation rotation);
private: