Copy Annotation onto Multiple Pages

This commit is contained in:
Jakub Melka
2020-12-30 17:06:13 +01:00
parent 6f0b72ba3b
commit 19dd761f35
10 changed files with 124 additions and 47 deletions

View File

@@ -76,6 +76,7 @@ SOURCES += \
sources/pdfprogress.cpp \
sources/pdfredact.cpp \
sources/pdfsecurityhandler.cpp \
sources/pdfselectpagesdialog.cpp \
sources/pdfsignaturehandler.cpp \
sources/pdfsnapper.cpp \
sources/pdfstructuretree.cpp \
@@ -143,6 +144,7 @@ HEADERS += \
sources/pdfprogress.h \
sources/pdfredact.h \
sources/pdfsecurityhandler.h \
sources/pdfselectpagesdialog.h \
sources/pdfsignaturehandler.h \
sources/pdfsignaturehandler_impl.h \
sources/pdfsnapper.h \
@@ -174,7 +176,8 @@ HEADERS += \
sources/pdfimage.h
FORMS += \
sources/pdfrenderingerrorswidget.ui
sources/pdfrenderingerrorswidget.ui \
sources/pdfselectpagesdialog.ui
RESOURCES += cmaps.qrc

View File

@@ -29,6 +29,7 @@
#include "pdfpainterutils.h"
#include "pdfdocumentbuilder.h"
#include "pdfobjecteditorwidget.h"
#include "pdfselectpagesdialog.h"
#include <QMenu>
#include <QDialog>
@@ -2010,7 +2011,42 @@ void PDFWidgetAnnotationManager::onShowPopupAnnotation()
void PDFWidgetAnnotationManager::onCopyAnnotation()
{
pdf::PDFSelectPagesDialog dialog(tr("Copy Annotation"), tr("Copy Annotation onto Multiple Pages"),
m_document->getCatalog()->getPageCount(), m_proxy->getWidget()->getDrawWidget()->getCurrentPages(), m_proxy->getWidget());
if (dialog.exec() == QDialog::Accepted)
{
std::vector<PDFInteger> pages = dialog.getSelectedPages();
const PDFInteger currentPageIndex = m_document->getCatalog()->getPageIndexFromPageReference(m_editableAnnotationPage);
for (PDFInteger& pageIndex : pages)
{
--pageIndex;
}
auto it = std::find(pages.begin(), pages.end(), currentPageIndex);
if (it != pages.end())
{
pages.erase(it);
}
if (pages.empty())
{
return;
}
PDFDocumentModifier modifier(m_document);
modifier.markAnnotationsChanged();
for (const PDFInteger pageIndex : pages)
{
modifier.getBuilder()->copyAnnotation(m_document->getCatalog()->getPage(pageIndex)->getPageReference(), m_editableAnnotation);
}
if (modifier.finalize())
{
emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
}
}
}
void PDFWidgetAnnotationManager::onEditAnnotation()

View File

@@ -1111,6 +1111,36 @@ PDFObjectReference PDFDocumentBuilder::getDocumentInfo() const
return PDFObjectReference();
}
void PDFDocumentBuilder::copyAnnotation(PDFObjectReference pageReference, PDFObjectReference annotationReference)
{
PDFObjectReference copiedAnnotation = addObject(getObjectByReference(annotationReference));
PDFObjectFactory factory;
factory.beginDictionary();
factory.beginDictionaryItem("P");
factory << pageReference;
factory.endDictionaryItem();
factory.beginDictionaryItem("Popup");
factory << PDFObject();
factory.endDictionaryItem();
factory.beginDictionaryItem("IRT");
factory << PDFObject();
factory.endDictionaryItem();
factory.endDictionary();
mergeTo(copiedAnnotation, factory.takeObject());
factory.beginDictionary();
factory.beginDictionaryItem("Annots");
factory.beginArray();
factory << copiedAnnotation;
factory.endArray();
factory.endDictionaryItem();
factory.endDictionary();
PDFObject pageAnnots = factory.takeObject();
appendTo(pageReference, pageAnnots);
}
PDFObjectReference PDFDocumentBuilder::getCatalogReference() const
{
if (const PDFDictionary* trailerDictionary = getDictionaryFromObject(m_storage.getTrailerDictionary()))

View File

@@ -416,6 +416,11 @@ public:
/// Returns document info reference
PDFObjectReference getDocumentInfo() const;
/// Copies existing annotation to another page
/// \param pageReference Page reference (onto which is annotation copied)
/// \param annotationReference Annotation reference
void copyAnnotation(PDFObjectReference pageReference, PDFObjectReference annotationReference);
/* START GENERATED CODE */
/// Appends a new page after last page.

View File

@@ -78,9 +78,7 @@ PDFDocument PDFRedact::perform(Options options)
}
builder.setPageRotation(newPageReference, page->getPageRotation());
// TODO: Popisek redakce anotace, Overlay text
// TODO: Redact searched text
// TODO: Duplikace redakce na vice stranek
PDFPageContentStreamBuilder contentStreamBuilder(&builder);
@@ -131,7 +129,12 @@ PDFDocument PDFRedact::perform(Options options)
if (options.testFlag(CopyOutline))
{
builder.setOutline(m_document->getCatalog()->getOutlineRootPtr().data());
const PDFOutlineItem* outlineItem = m_document->getCatalog()->getOutlineRootPtr().data();
if (outlineItem)
{
builder.setOutline(outlineItem);
}
}
PDFDocument redactedDocument = builder.build();
@@ -141,5 +144,4 @@ PDFDocument PDFRedact::perform(Options options)
return optimizer.takeOptimizedDocument();
}
} // namespace pdf

View File

@@ -0,0 +1,135 @@
// Copyright (C) 2020 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
// (at your option) 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 <https://www.gnu.org/licenses/>.
#include "pdfselectpagesdialog.h"
#include "ui_pdfselectpagesdialog.h"
#include "pdfwidgetutils.h"
#include <QMessageBox>
namespace pdf
{
PDFSelectPagesDialog::PDFSelectPagesDialog(QString windowTitle,
QString groupBoxTitle,
pdf::PDFInteger pageCount,
const std::vector<pdf::PDFInteger>& visiblePages,
QWidget* parent) :
QDialog(parent),
ui(new Ui::PDFSelectPagesDialog),
m_pageCount(pageCount),
m_visiblePages(visiblePages)
{
ui->setupUi(this);
setWindowTitle(windowTitle);
ui->selectPagesGroupBox->setTitle(groupBoxTitle);
for (pdf::PDFInteger& pageIndex : m_visiblePages)
{
++pageIndex;
}
m_evenPages.reserve(pageCount / 2);
m_oddPages.reserve(pageCount / 2 + 1);
for (pdf::PDFInteger i = 1; i <= pageCount; ++i)
{
if (i % 2 == 1)
{
m_oddPages.push_back(i);
}
else
{
m_evenPages.push_back(i);
}
}
connect(ui->customPageRangeRadioButton, &QRadioButton::toggled, this, &PDFSelectPagesDialog::updateUi);
setMinimumWidth(pdf::PDFWidgetUtils::scaleDPI_x(this, 400));
updateUi();
}
PDFSelectPagesDialog::~PDFSelectPagesDialog()
{
delete ui;
}
std::vector<pdf::PDFInteger> PDFSelectPagesDialog::getSelectedPages() const
{
std::vector<pdf::PDFInteger> result;
if (ui->visiblePagesRadioButton->isChecked())
{
result = m_visiblePages;
}
else if (ui->allPagesRadioButton->isChecked())
{
result.resize(m_pageCount, 0);
std::iota(result.begin(), result.end(), 1);
}
else if (ui->evenPagesRadioButton->isChecked())
{
result = m_evenPages;
}
else if (ui->oddPagesRadioButton->isChecked())
{
result = m_oddPages;
}
else if (ui->customPageRangeRadioButton->isChecked())
{
QString errorMessage;
result = pdf::PDFClosedIntervalSet::parse(1, m_pageCount, ui->customPageRangeEdit->text(), &errorMessage).unfold();
}
else
{
Q_ASSERT(false);
}
return result;
}
void PDFSelectPagesDialog::updateUi()
{
ui->customPageRangeEdit->setEnabled(ui->customPageRangeRadioButton->isChecked());
}
void PDFSelectPagesDialog::accept()
{
if (ui->customPageRangeRadioButton->isChecked())
{
QString errorMessage;
pdf::PDFClosedIntervalSet::parse(1, m_pageCount, ui->customPageRangeEdit->text(), &errorMessage);
if (!errorMessage.isEmpty())
{
QMessageBox::critical(this, tr("Error"), errorMessage);
return;
}
}
if (getSelectedPages().empty())
{
QMessageBox::critical(this, tr("Error"), tr("Selected page range is empty."));
return;
}
QDialog::accept();
}
} // namespace pdf

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2020 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
// (at your option) 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 <https://www.gnu.org/licenses/>.
#ifndef PDFSELECTPAGESDIALOG_H
#define PDFSELECTPAGESDIALOG_H
#include "pdfutils.h"
#include <QDialog>
namespace Ui
{
class PDFSelectPagesDialog;
}
namespace pdf
{
class Pdf4QtLIBSHARED_EXPORT PDFSelectPagesDialog : public QDialog
{
Q_OBJECT
public:
explicit PDFSelectPagesDialog(QString windowTitle,
QString groupBoxTitle,
pdf::PDFInteger pageCount,
const std::vector<pdf::PDFInteger>& visiblePages,
QWidget* parent);
virtual ~PDFSelectPagesDialog() override;
virtual void accept() override;
std::vector<pdf::PDFInteger> getSelectedPages() const;
private:
void updateUi();
Ui::PDFSelectPagesDialog* ui;
pdf::PDFInteger m_pageCount;
std::vector<pdf::PDFInteger> m_visiblePages;
std::vector<pdf::PDFInteger> m_evenPages;
std::vector<pdf::PDFInteger> m_oddPages;
};
} // namespace pdf
#endif // PDFSELECTPAGESDIALOG_H

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PDFSelectPagesDialog</class>
<widget class="QDialog" name="PDFSelectPagesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>218</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="selectPagesGroupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<widget class="QRadioButton" name="allPagesRadioButton">
<property name="text">
<string>All pages</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="customPageRangeEdit"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QRadioButton" name="evenPagesRadioButton">
<property name="text">
<string>Even pages</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QRadioButton" name="oddPagesRadioButton">
<property name="text">
<string>Odd pages</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QRadioButton" name="customPageRangeRadioButton">
<property name="text">
<string>Custom page range:</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QRadioButton" name="visiblePagesRadioButton">
<property name="text">
<string>Visible pages</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PDFSelectPagesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PDFSelectPagesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>