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: