feat(pdfengines): add support for flattening annotations (#1105)

* initial changes

* Add tests

* Fix edge case when we need to regenerate appearances

* Fix comments

* Add missing comment

* Add missing comment

* Add missing comment

* Add flatten option to the merge route

* Add flatten option to the libreoffice convert route

* Add flatten option to the chromium convert route

* Revert "Add flatten option to the chromium convert route"

This reverts commit cdab8b4e6b.

* Ignore lint false positives

* Add missing tests

* Add flatten route tests

* Replace input instead of creating a new file

* create copy before flatten in tests

---------

Co-authored-by: Peter Chakalov <peter.chakalov@abraxa.com>
This commit is contained in:
Peter Chakalov
2025-01-28 15:38:27 +02:00
committed by GitHub
parent a9b44ee39e
commit b418f1eb05
23 changed files with 634 additions and 4 deletions

View File

@@ -35,9 +35,12 @@ func (mod *ValidatorMock) Validate() error {
}
// PdfEngineMock is a mock for the [PdfEngine] interface.
//
//nolint:dupl
type PdfEngineMock struct {
MergeMock func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error
SplitMock func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error)
FlattenMock func(ctx context.Context, logger *zap.Logger, inputPath string) error
ConvertMock func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error
ReadMetadataMock func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error)
WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error
@@ -51,6 +54,10 @@ func (engine *PdfEngineMock) Split(ctx context.Context, logger *zap.Logger, mode
return engine.SplitMock(ctx, logger, mode, inputPath, outputDirPath)
}
func (engine *PdfEngineMock) Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error {
return engine.FlattenMock(ctx, logger, inputPath)
}
func (engine *PdfEngineMock) Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
return engine.ConvertMock(ctx, logger, formats, inputPath, outputPath)
}

View File

@@ -56,6 +56,9 @@ func TestPDFEngineMock(t *testing.T) {
SplitMock: func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) {
return nil, nil
},
FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error {
return nil
},
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
return nil
},
@@ -77,6 +80,11 @@ func TestPDFEngineMock(t *testing.T) {
t.Errorf("expected no error from PdfEngineMock.Split, but got: %v", err)
}
err = mock.Flatten(context.Background(), zap.NewNop(), "")
if err != nil {
t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err)
}
err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "")
if err != nil {
t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err)

View File

@@ -88,6 +88,8 @@ type PdfFormats struct {
// PdfEngine provides an interface for operations on PDFs. Implementations
// can utilize various tools like PDFtk, or implement functionality directly in
// Go.
//
//nolint:dupl
type PdfEngine interface {
// Merge combines multiple PDFs into a single PDF. The resulting page order
// is determined by the order of files provided in inputPaths.
@@ -96,6 +98,11 @@ type PdfEngine interface {
// Split splits a given PDF file.
Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error)
// Flatten merges existing annotation appearances with page content, effectively deleting the original annotations.
// This process can flatten forms as well, as forms share a relationship with annotations.
// Note that this operation is irreversible.
Flatten(ctx context.Context, logger *zap.Logger, inputPath string) error
// Convert transforms a given PDF to the specified formats defined in
// PdfFormats. If no format, it does nothing.
Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error