mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Redacting tool
This commit is contained in:
@@ -74,6 +74,7 @@ SOURCES += \
|
||||
sources/pdfpattern.cpp \
|
||||
sources/pdfplugin.cpp \
|
||||
sources/pdfprogress.cpp \
|
||||
sources/pdfredact.cpp \
|
||||
sources/pdfsecurityhandler.cpp \
|
||||
sources/pdfsignaturehandler.cpp \
|
||||
sources/pdfsnapper.cpp \
|
||||
@@ -140,6 +141,7 @@ HEADERS += \
|
||||
sources/pdfpattern.h \
|
||||
sources/pdfplugin.h \
|
||||
sources/pdfprogress.h \
|
||||
sources/pdfredact.h \
|
||||
sources/pdfsecurityhandler.h \
|
||||
sources/pdfsignaturehandler.h \
|
||||
sources/pdfsignaturehandler_impl.h \
|
||||
|
@@ -1152,6 +1152,18 @@ void PDFDocumentBuilder::updateDocumentInfo(PDFObject info)
|
||||
mergeTo(infoReference, info);
|
||||
}
|
||||
|
||||
void PDFDocumentBuilder::setDocumentInfo(PDFObjectReference infoReference)
|
||||
{
|
||||
// Update the trailer dictionary
|
||||
PDFObjectFactory objectFactory;
|
||||
objectFactory.beginDictionary();
|
||||
objectFactory.beginDictionaryItem("Info");
|
||||
objectFactory << infoReference;
|
||||
objectFactory.endDictionaryItem();
|
||||
objectFactory.endDictionary();
|
||||
m_storage.updateTrailerDictionary(objectFactory.takeObject());
|
||||
}
|
||||
|
||||
QRectF PDFDocumentBuilder::getPolygonsBoundingRect(const Polygons& polygons) const
|
||||
{
|
||||
QRectF rect;
|
||||
|
@@ -406,6 +406,15 @@ public:
|
||||
/// \param b Second 'Names' entry
|
||||
void mergeNames(PDFObjectReference a, PDFObjectReference b);
|
||||
|
||||
/// Updates document info by merging object info to actual info
|
||||
void updateDocumentInfo(PDFObject info);
|
||||
|
||||
/// Sets document info reference to trailer dictionary
|
||||
void setDocumentInfo(PDFObjectReference infoReference);
|
||||
|
||||
/// Returns document info reference
|
||||
PDFObjectReference getDocumentInfo() const;
|
||||
|
||||
/* START GENERATED CODE */
|
||||
|
||||
/// Appends a new page after last page.
|
||||
@@ -1439,8 +1448,6 @@ private:
|
||||
QString getProducerString() const;
|
||||
PDFObjectReference getPageTreeRoot() const;
|
||||
PDFInteger getPageTreeRootChildCount() const;
|
||||
PDFObjectReference getDocumentInfo() const;
|
||||
void updateDocumentInfo(PDFObject info);
|
||||
QRectF getPolygonsBoundingRect(const Polygons& Polygons) const;
|
||||
PDFObjectReference createOutlineItem(const PDFOutlineItem* root, bool writeOutlineData);
|
||||
|
||||
|
@@ -611,6 +611,98 @@ void PDFPrecompiledPage::draw(QPainter* painter, const QRectF& cropBox, const QM
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void PDFPrecompiledPage::redact(QPainterPath redactPath, const QMatrix& matrix, QColor color)
|
||||
{
|
||||
if (redactPath.isEmpty())
|
||||
{
|
||||
// Nothing to be redacted
|
||||
return;
|
||||
}
|
||||
|
||||
std::stack<QMatrix> worldMatrixStack;
|
||||
worldMatrixStack.push(matrix);
|
||||
|
||||
if (color.isValid())
|
||||
{
|
||||
m_instructions.insert(m_instructions.begin(), Instruction(InstructionType::SaveGraphicState, 0));
|
||||
}
|
||||
|
||||
// Process all instructions
|
||||
for (const Instruction& instruction : m_instructions)
|
||||
{
|
||||
switch (instruction.type)
|
||||
{
|
||||
case InstructionType::DrawPath:
|
||||
{
|
||||
QMatrix matrix = worldMatrixStack.top().inverted();
|
||||
QPainterPath mappedRedactPath = matrix.map(redactPath);
|
||||
PathPaintData& path = m_paths[instruction.dataIndex];
|
||||
path.path = path.path.subtracted(mappedRedactPath);
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::DrawImage:
|
||||
{
|
||||
ImageData& data = m_images[instruction.dataIndex];
|
||||
QImage& image = data.image;
|
||||
|
||||
QMatrix imageTransform(1.0 / image.width(), 0, 0, 1.0 / image.height(), 0, 0);
|
||||
QMatrix worldMatrix = imageTransform * worldMatrixStack.top();
|
||||
|
||||
// Jakub Melka: Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
|
||||
// to the opposite (so the image is then unchanged)
|
||||
worldMatrix.translate(0, image.height());
|
||||
worldMatrix.scale(1, -1);
|
||||
|
||||
QPainter painter(&image);
|
||||
painter.setWorldMatrix(worldMatrix.inverted());
|
||||
painter.drawPath(redactPath);
|
||||
painter.end();
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::DrawMesh:
|
||||
// We do not redact mesh
|
||||
break;
|
||||
|
||||
case InstructionType::Clip:
|
||||
{
|
||||
QMatrix matrix = worldMatrixStack.top().inverted();
|
||||
QPainterPath mappedRedactPath = matrix.map(redactPath);
|
||||
m_clips[instruction.dataIndex].clipPath = m_clips[instruction.dataIndex].clipPath.subtracted(mappedRedactPath);
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::SaveGraphicState:
|
||||
worldMatrixStack.push(worldMatrixStack.top());
|
||||
break;
|
||||
|
||||
case InstructionType::RestoreGraphicState:
|
||||
worldMatrixStack.pop();
|
||||
break;
|
||||
|
||||
case InstructionType::SetWorldMatrix:
|
||||
worldMatrixStack.top() = m_matrices[instruction.dataIndex];
|
||||
break;
|
||||
|
||||
case InstructionType::SetCompositionMode:
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (color.isValid())
|
||||
{
|
||||
addRestoreGraphicState();
|
||||
addPath(Qt::NoPen, QBrush(color), matrix.map(redactPath), false);
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPrecompiledPage::addPath(QPen pen, QBrush brush, QPainterPath path, bool isText)
|
||||
{
|
||||
m_instructions.emplace_back(InstructionType::DrawPath, m_paths.size());
|
||||
|
@@ -189,6 +189,12 @@ public:
|
||||
/// \param features Renderer features
|
||||
void draw(QPainter* painter, const QRectF& cropBox, const QMatrix& pagePointToDevicePointMatrix, PDFRenderer::Features features) const;
|
||||
|
||||
/// Redact path - remove all content intersecting given path,
|
||||
/// and fill redact path with given color.
|
||||
/// \param redactPath Redaction path in page coordinates
|
||||
/// \param color Redaction color (if invalid, nothing is being drawn)
|
||||
void redact(QPainterPath redactPath, const QMatrix& matrix, QColor color);
|
||||
|
||||
void addPath(QPen pen, QBrush brush, QPainterPath path, bool isText);
|
||||
void addClip(QPainterPath path);
|
||||
void addImage(QImage image);
|
||||
|
@@ -22,6 +22,7 @@ namespace pdf
|
||||
|
||||
PDFPlugin::PDFPlugin(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_dataExchangeInterface(nullptr),
|
||||
m_widget(nullptr),
|
||||
m_cmsManager(nullptr),
|
||||
m_document(nullptr)
|
||||
@@ -29,6 +30,11 @@ PDFPlugin::PDFPlugin(QObject* parent) :
|
||||
|
||||
}
|
||||
|
||||
void PDFPlugin::setDataExchangeInterface(IPluginDataExchange* dataExchangeInterface)
|
||||
{
|
||||
m_dataExchangeInterface = dataExchangeInterface;
|
||||
}
|
||||
|
||||
void PDFPlugin::setWidget(PDFWidget* widget)
|
||||
{
|
||||
m_widget = widget;
|
||||
|
@@ -44,6 +44,15 @@ struct Pdf4QtLIBSHARED_EXPORT PDFPluginInfo
|
||||
};
|
||||
using PDFPluginInfos = std::vector<PDFPluginInfo>;
|
||||
|
||||
class IPluginDataExchange
|
||||
{
|
||||
public:
|
||||
explicit IPluginDataExchange() = default;
|
||||
virtual ~IPluginDataExchange() = default;
|
||||
|
||||
virtual QString getOriginalFileName() const = 0;
|
||||
};
|
||||
|
||||
class Pdf4QtLIBSHARED_EXPORT PDFPlugin : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -51,12 +60,14 @@ class Pdf4QtLIBSHARED_EXPORT PDFPlugin : public QObject
|
||||
public:
|
||||
explicit PDFPlugin(QObject* parent);
|
||||
|
||||
virtual void setDataExchangeInterface(IPluginDataExchange* dataExchangeInterface);
|
||||
virtual void setWidget(PDFWidget* widget);
|
||||
virtual void setCMSManager(PDFCMSManager* manager);
|
||||
virtual void setDocument(const PDFModifiedDocument& document);
|
||||
virtual std::vector<QAction*> getActions() const;
|
||||
|
||||
protected:
|
||||
IPluginDataExchange* m_dataExchangeInterface;
|
||||
PDFWidget* m_widget;
|
||||
PDFCMSManager* m_cmsManager;
|
||||
PDFDocument* m_document;
|
||||
|
140
Pdf4QtLib/sources/pdfredact.cpp
Normal file
140
Pdf4QtLib/sources/pdfredact.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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 "pdfredact.h"
|
||||
#include "pdfpainter.h"
|
||||
#include "pdfdocumentbuilder.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
PDFRedact::PDFRedact(const PDFDocument* document,
|
||||
const PDFFontCache* fontCache,
|
||||
const PDFCMS* cms,
|
||||
const PDFOptionalContentActivity* optionalContentActivity,
|
||||
const PDFMeshQualitySettings* meshQualitySettings,
|
||||
QColor redactFillColor) :
|
||||
m_document(document),
|
||||
m_fontCache(fontCache),
|
||||
m_cms(cms),
|
||||
m_optionalContentActivity(optionalContentActivity),
|
||||
m_meshQualitySettings(meshQualitySettings),
|
||||
m_redactFillColor(redactFillColor)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PDFDocument PDFRedact::perform(Options options)
|
||||
{
|
||||
PDFDocumentBuilder builder;
|
||||
builder.createDocument();
|
||||
|
||||
PDFRenderer renderer(m_document,
|
||||
m_fontCache,
|
||||
m_cms,
|
||||
m_optionalContentActivity,
|
||||
PDFRenderer::None,
|
||||
*m_meshQualitySettings);
|
||||
|
||||
for (size_t i = 0; i < m_document->getCatalog()->getPageCount(); ++i)
|
||||
{
|
||||
const PDFPage* page = m_document->getCatalog()->getPage(i);
|
||||
|
||||
PDFPrecompiledPage compiledPage;
|
||||
renderer.compile(&compiledPage, i);
|
||||
|
||||
PDFObjectReference newPageReference = builder.appendPage(page->getMediaBox());
|
||||
|
||||
if (!page->getCropBox().isEmpty())
|
||||
{
|
||||
builder.setPageCropBox(newPageReference, page->getCropBox());
|
||||
}
|
||||
if (!page->getBleedBox().isEmpty())
|
||||
{
|
||||
builder.setPageBleedBox(newPageReference, page->getBleedBox());
|
||||
}
|
||||
if (!page->getTrimBox().isEmpty())
|
||||
{
|
||||
builder.setPageTrimBox(newPageReference, page->getTrimBox());
|
||||
}
|
||||
if (!page->getArtBox().isEmpty())
|
||||
{
|
||||
builder.setPageArtBox(newPageReference, page->getArtBox());
|
||||
}
|
||||
|
||||
// TODO: Nastavit natoceni stranky
|
||||
// TODO: Popisek redakce anotace, Overlay text
|
||||
// TODO: Redact searched text
|
||||
// TODO: Duplikace redakce na vice stranek
|
||||
|
||||
PDFPageContentStreamBuilder contentStreamBuilder(&builder);
|
||||
|
||||
QPainterPath redactPath;
|
||||
|
||||
for (const PDFObjectReference& annotationReference : page->getAnnotations())
|
||||
{
|
||||
PDFAnnotationPtr annotation = PDFAnnotation::parse(&m_document->getStorage(), annotationReference);
|
||||
if (!annotation || annotation->getType() != AnnotationType::Redact)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have redact annotation here
|
||||
const PDFRedactAnnotation* redactAnnotation = dynamic_cast<const PDFRedactAnnotation*>(annotation.get());
|
||||
Q_ASSERT(redactAnnotation);
|
||||
|
||||
redactPath.addPath(redactAnnotation->getRedactionRegion().getPath());
|
||||
}
|
||||
|
||||
QMatrix matrix;
|
||||
matrix.translate(0, page->getMediaBox().height());
|
||||
matrix.scale(1.0, -1.0);
|
||||
|
||||
QPainter* painter = contentStreamBuilder.begin(newPageReference);
|
||||
compiledPage.redact(redactPath, matrix, m_redactFillColor);
|
||||
compiledPage.draw(painter, QRectF(), matrix, PDFRenderer::None);
|
||||
contentStreamBuilder.end(painter);
|
||||
}
|
||||
|
||||
if (options.testFlag(CopyTitle))
|
||||
{
|
||||
builder.setDocumentTitle(m_document->getInfo()->title);
|
||||
}
|
||||
|
||||
if (options.testFlag(CopyMetadata))
|
||||
{
|
||||
PDFObject info = m_document->getTrailerDictionary()->get("Info");
|
||||
if (!info.isNull())
|
||||
{
|
||||
std::vector<PDFObject> copiedObjects = builder.copyFrom({ info }, m_document->getStorage(), true);
|
||||
if (copiedObjects.size() == 1 && copiedObjects.front().isReference())
|
||||
{
|
||||
builder.setDocumentInfo(copiedObjects.front().getReference());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.testFlag(CopyOutline))
|
||||
{
|
||||
builder.setOutline(m_document->getCatalog()->getOutlineRootPtr().data());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
} // namespace pdf
|
63
Pdf4QtLib/sources/pdfredact.h
Normal file
63
Pdf4QtLib/sources/pdfredact.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 PDFREDACT_H
|
||||
#define PDFREDACT_H
|
||||
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfrenderer.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
/// Create redacted document from the document, which have redact annotations.
|
||||
/// Redacted document has removed content marked by these annotations, and
|
||||
/// annotations themselfs are removed.
|
||||
class Pdf4QtLIBSHARED_EXPORT PDFRedact
|
||||
{
|
||||
public:
|
||||
explicit PDFRedact(const PDFDocument* document,
|
||||
const PDFFontCache* fontCache,
|
||||
const PDFCMS* cms,
|
||||
const PDFOptionalContentActivity* optionalContentActivity,
|
||||
const PDFMeshQualitySettings* meshQualitySettings,
|
||||
QColor redactFillColor);
|
||||
|
||||
enum Option
|
||||
{
|
||||
None = 0x0000,
|
||||
CopyTitle = 0x0001,
|
||||
CopyMetadata = 0x0002,
|
||||
CopyOutline = 0x0004
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
|
||||
pdf::PDFDocument perform(Options options);
|
||||
|
||||
private:
|
||||
const PDFDocument* m_document;
|
||||
const PDFFontCache* m_fontCache;
|
||||
const PDFCMS* m_cms;
|
||||
const PDFOptionalContentActivity* m_optionalContentActivity;
|
||||
const PDFMeshQualitySettings* m_meshQualitySettings;
|
||||
QColor m_redactFillColor;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFREDACT_H
|
@@ -50,6 +50,7 @@ public:
|
||||
|
||||
enum Feature
|
||||
{
|
||||
None = 0x0000,
|
||||
Antialiasing = 0x0001, ///< Antialiasing for lines, shapes, etc.
|
||||
TextAntialiasing = 0x0002, ///< Antialiasing for drawing text
|
||||
SmoothImages = 0x0004, ///< Adjust images to the device space using smooth transformation (slower, but better image quality)
|
||||
|
Reference in New Issue
Block a user