mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-07-02 00:17:40 +08:00
feat(pdfengines): add watermark and stamp feature
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ make test-integration TAGS=chromium-convert-html
|
||||
make test-integration TAGS="merge,split"
|
||||
```
|
||||
|
||||
Available tags: `chromium`, `chromium-concurrent`, `chromium-convert-html`, `chromium-convert-markdown`, `chromium-convert-url`, `debug`, `health`, `libreoffice`, `libreoffice-convert`, `output-filename`, `pdfengines`, `pdfengines-convert`, `pdfengines-embed`, `embed`, `pdfengines-encrypt`, `encrypt`, `pdfengines-flatten`, `flatten`, `pdfengines-merge`, `merge`, `pdfengines-metadata`, `metadata`, `pdfengines-split`, `split`, `pdfengines-bookmarks`, `bookmarks`, `prometheus-metrics`, `root`, `version`, `webhook`, `download-from`.
|
||||
Available tags: `chromium`, `chromium-concurrent`, `chromium-convert-html`, `chromium-convert-markdown`, `chromium-convert-url`, `debug`, `health`, `libreoffice`, `libreoffice-convert`, `output-filename`, `pdfengines`, `pdfengines-convert`, `pdfengines-embed`, `embed`, `pdfengines-encrypt`, `encrypt`, `pdfengines-flatten`, `flatten`, `pdfengines-merge`, `merge`, `pdfengines-metadata`, `metadata`, `pdfengines-split`, `split`, `pdfengines-watermark`, `watermark`, `pdfengines-stamp`, `stamp`, `pdfengines-bookmarks`, `bookmarks`, `prometheus-metrics`, `root`, `version`, `webhook`, `download-from`.
|
||||
|
||||
Other useful flags:
|
||||
|
||||
|
||||
@@ -195,6 +195,10 @@ NO_CONCURRENCY=false
|
||||
# metadata
|
||||
# pdfengines-split
|
||||
# split
|
||||
# pdfengines-watermark
|
||||
# watermark
|
||||
# pdfengines-stamp
|
||||
# stamp
|
||||
# pdfengines-bookmarks
|
||||
# bookmarks
|
||||
# prometheus-metrics
|
||||
|
||||
@@ -166,8 +166,6 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
||||
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/minio/minlz v1.1.0 h1:rUOGu3EP4EqJC5k3qCsIwEnZiJULKqtRyDdqbhlvMmQ=
|
||||
github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
@@ -198,8 +196,6 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -214,8 +210,6 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
@@ -265,30 +259,20 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
@@ -299,8 +283,6 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
|
||||
@@ -322,8 +304,6 @@ golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -57,6 +57,8 @@ type PdfEngineMock struct {
|
||||
EncryptMock func(ctx context.Context, logger *zap.Logger, inputPath, userPassword, ownerPassword string) error
|
||||
EmbedFilesMock func(ctx context.Context, logger *zap.Logger, filePaths []string, inputPath string) error
|
||||
WriteBookmarksMock func(ctx context.Context, logger *zap.Logger, inputPath string, bookmarks []Bookmark) error
|
||||
WatermarkMock func(ctx context.Context, logger *zap.Logger, inputPath string, stamp Stamp) error
|
||||
StampMock func(ctx context.Context, logger *zap.Logger, inputPath string, stamp Stamp) error
|
||||
}
|
||||
|
||||
func (engine *PdfEngineMock) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||
@@ -103,6 +105,14 @@ func (engine *PdfEngineMock) WriteBookmarks(ctx context.Context, logger *zap.Log
|
||||
return engine.WriteBookmarksMock(ctx, logger, inputPath, bookmarks)
|
||||
}
|
||||
|
||||
func (engine *PdfEngineMock) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp Stamp) error {
|
||||
return engine.WatermarkMock(ctx, logger, inputPath, stamp)
|
||||
}
|
||||
|
||||
func (engine *PdfEngineMock) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp Stamp) error {
|
||||
return engine.StampMock(ctx, logger, inputPath, stamp)
|
||||
}
|
||||
|
||||
// PdfEngineProviderMock is a mock for the [PdfEngineProvider] interface.
|
||||
type PdfEngineProviderMock struct {
|
||||
PdfEngineMock func() (PdfEngine, error)
|
||||
|
||||
@@ -28,6 +28,10 @@ var (
|
||||
// ErrPdfEncryptionNotSupported is returned when encryption
|
||||
// is not supported by the PDF engine.
|
||||
ErrPdfEncryptionNotSupported = errors.New("encryption not supported")
|
||||
|
||||
// ErrPdfStampSourceNotSupported is returned when a stamp source type
|
||||
// is not supported by the PDF engine.
|
||||
ErrPdfStampSourceNotSupported = errors.New("stamp source not supported")
|
||||
)
|
||||
|
||||
// PdfEngineInvalidArgsError represents an error returned by a PDF engine when
|
||||
@@ -49,6 +53,33 @@ func NewPdfEngineInvalidArgs(engine, msg string) error {
|
||||
return &PdfEngineInvalidArgsError{engine, msg}
|
||||
}
|
||||
|
||||
const (
|
||||
// StampSourceText represents a text-based stamp source.
|
||||
StampSourceText string = "text"
|
||||
|
||||
// StampSourceImage represents an image-based stamp source.
|
||||
StampSourceImage string = "image"
|
||||
|
||||
// StampSourcePDF represents a PDF-based stamp source.
|
||||
StampSourcePDF string = "pdf"
|
||||
)
|
||||
|
||||
// Stamp gathers the data required to apply a watermark or stamp to a PDF.
|
||||
type Stamp struct {
|
||||
// Source is one of "text", "image", or "pdf".
|
||||
Source string
|
||||
|
||||
// Expression is the text content (for text source) or file path (for
|
||||
// image/pdf source).
|
||||
Expression string
|
||||
|
||||
// Pages is the optional page range to apply the stamp to.
|
||||
Pages string
|
||||
|
||||
// Options holds engine-specific styling options.
|
||||
Options map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
// SplitModeIntervals represents a mode where a PDF is split at specific
|
||||
// intervals.
|
||||
@@ -166,6 +197,12 @@ type PdfEngine interface {
|
||||
// without modifying the main PDF content.
|
||||
// TODO: attachments instead? Rename the route?
|
||||
EmbedFiles(ctx context.Context, logger *zap.Logger, filePaths []string, inputPath string) error
|
||||
|
||||
// Watermark applies a watermark (behind page content) to a PDF file.
|
||||
Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp Stamp) error
|
||||
|
||||
// Stamp applies a stamp (on top of page content) to a PDF file.
|
||||
Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp Stamp) error
|
||||
}
|
||||
|
||||
// PdfEngineProvider offers an interface to instantiate a [PdfEngine].
|
||||
|
||||
@@ -17,9 +17,15 @@ import (
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
// EmbedsFormField represents the form field name for embedding files.
|
||||
const (
|
||||
// EmbedsFormField represents the form field name for embedding files.
|
||||
EmbedsFormField string = "embeds"
|
||||
|
||||
// WatermarksFormField represents the form field name for watermark files.
|
||||
WatermarksFormField string = "watermarks"
|
||||
|
||||
// StampsFormField represents the form field name for stamp files.
|
||||
StampsFormField string = "stamps"
|
||||
)
|
||||
|
||||
// FormData is a helper for validating and hydrating values from a
|
||||
@@ -406,17 +412,57 @@ func (form *FormData) MandatoryPaths(extensions []string, target *[]string) *For
|
||||
return form
|
||||
}
|
||||
|
||||
// Watermarks binds the absolute paths of form data files that should be
|
||||
// used as watermark sources. Only files uploaded with the "watermarks"
|
||||
// field name will be included.
|
||||
func (form *FormData) Watermarks(target *[]string) *FormData {
|
||||
if form.errors != nil {
|
||||
return form
|
||||
}
|
||||
|
||||
if paths, ok := form.filesByField[WatermarksFormField]; ok {
|
||||
*target = append(*target, paths...)
|
||||
}
|
||||
|
||||
return form
|
||||
}
|
||||
|
||||
// Stamps binds the absolute paths of form data files that should be
|
||||
// used as stamp sources. Only files uploaded with the "stamps"
|
||||
// field name will be included.
|
||||
func (form *FormData) Stamps(target *[]string) *FormData {
|
||||
if form.errors != nil {
|
||||
return form
|
||||
}
|
||||
|
||||
if paths, ok := form.filesByField[StampsFormField]; ok {
|
||||
*target = append(*target, paths...)
|
||||
}
|
||||
|
||||
return form
|
||||
}
|
||||
|
||||
// paths bind the absolute paths of form data files, according to a list of
|
||||
// file extensions, to a string slice variable.
|
||||
// embeds are excluded.
|
||||
// embeds, watermarks, and stamps are excluded.
|
||||
func (form *FormData) paths(extensions []string, target *[]string) *FormData {
|
||||
embeds, ok := form.filesByField[EmbedsFormField]
|
||||
watermarks, wmOk := form.filesByField[WatermarksFormField]
|
||||
stamps, stOk := form.filesByField[StampsFormField]
|
||||
|
||||
for filename, path := range form.files {
|
||||
if ok && slices.Contains(embeds, path) {
|
||||
continue
|
||||
}
|
||||
|
||||
if wmOk && slices.Contains(watermarks, path) {
|
||||
continue
|
||||
}
|
||||
|
||||
if stOk && slices.Contains(stamps, path) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ext := range extensions {
|
||||
// See https://github.com/gotenberg/gotenberg/issues/228.
|
||||
if strings.ToLower(filepath.Ext(filename)) == ext {
|
||||
|
||||
@@ -61,6 +61,10 @@ func ParseError(err error) (int, string) {
|
||||
return http.StatusBadRequest, "At least one PDF engine cannot process the requested metadata, while others may have failed to convert due to different issues"
|
||||
}
|
||||
|
||||
if errors.Is(err, gotenberg.ErrPdfStampSourceNotSupported) {
|
||||
return http.StatusBadRequest, "At least one PDF engine cannot process the requested stamp source type, while others may have failed due to different issues"
|
||||
}
|
||||
|
||||
var invalidArgsError *gotenberg.PdfEngineInvalidArgsError
|
||||
if errors.As(err, &invalidArgsError) {
|
||||
return http.StatusBadRequest, invalidArgsError.Error()
|
||||
|
||||
@@ -415,6 +415,10 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
||||
watermarkFiles := pdfengines.FormDataPdfWatermarkFiles(form)
|
||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
||||
stampFiles := pdfengines.FormDataPdfStampFiles(form)
|
||||
|
||||
var url string
|
||||
err := form.
|
||||
@@ -424,7 +428,14 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths)
|
||||
if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && len(watermarkFiles) > 0 {
|
||||
watermark.Expression = watermarkFiles[0]
|
||||
}
|
||||
if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && len(stampFiles) > 0 {
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths, watermark, stamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert URL to PDF: %w", err)
|
||||
}
|
||||
@@ -478,6 +489,10 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
||||
watermarkFiles := pdfengines.FormDataPdfWatermarkFiles(form)
|
||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
||||
stampFiles := pdfengines.FormDataPdfStampFiles(form)
|
||||
|
||||
var inputPath string
|
||||
err := form.
|
||||
@@ -487,8 +502,15 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && len(watermarkFiles) > 0 {
|
||||
watermark.Expression = watermarkFiles[0]
|
||||
}
|
||||
if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && len(stampFiles) > 0 {
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("file://%s", inputPath)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths, watermark, stamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert HTML to PDF: %w", err)
|
||||
}
|
||||
@@ -543,6 +565,10 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
||||
watermarkFiles := pdfengines.FormDataPdfWatermarkFiles(form)
|
||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
||||
stampFiles := pdfengines.FormDataPdfStampFiles(form)
|
||||
|
||||
var (
|
||||
inputPath string
|
||||
@@ -557,12 +583,19 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && len(watermarkFiles) > 0 {
|
||||
watermark.Expression = watermarkFiles[0]
|
||||
}
|
||||
if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && len(stampFiles) > 0 {
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
url, err := markdownToHtml(ctx, inputPath, markdownPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
||||
}
|
||||
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths, watermark, stamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert markdown to PDF: %w", err)
|
||||
}
|
||||
@@ -686,7 +719,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
|
||||
return fmt.Sprintf("file://%s", inputPath), nil
|
||||
}
|
||||
|
||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]any, userPassword, ownerPassword string, embedPaths []string) error {
|
||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]any, userPassword, ownerPassword string, embedPaths []string, watermark, stamp gotenberg.Stamp) error {
|
||||
outputPath := ctx.GeneratePath(".pdf")
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1130.
|
||||
filename := ctx.OutputFilename(outputPath)
|
||||
@@ -758,6 +791,16 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
||||
return fmt.Errorf("convert PDF(s): %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.WatermarkStub(ctx, engine, watermark, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.StampStub(ctx, engine, stamp, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("embed files into PDFs: %w", err)
|
||||
|
||||
@@ -254,6 +254,16 @@ func (engine *ExifTool) EmbedFiles(ctx context.Context, logger *zap.Logger, file
|
||||
return fmt.Errorf("embed files with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Watermark is not available in this implementation.
|
||||
func (engine *ExifTool) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return fmt.Errorf("watermark PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Stamp is not available in this implementation.
|
||||
func (engine *ExifTool) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return fmt.Errorf("stamp PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.Module = (*ExifTool)(nil)
|
||||
|
||||
@@ -147,6 +147,29 @@ type Options struct {
|
||||
// Possible values are: 75, 150, 300, 600 and 1200.
|
||||
MaxImageResolution int
|
||||
|
||||
// NativeWatermarkText specifies the text for a watermark to be drawn on
|
||||
// every page of the exported PDF file.
|
||||
// See https://help.libreoffice.org/latest/en-US/text/shared/guide/pdf_params.html.
|
||||
NativeWatermarkText string
|
||||
|
||||
// NativeWatermarkColor specifies the color for the watermark text as a
|
||||
// decimal long value. Default is 8388223 (light green).
|
||||
NativeWatermarkColor int
|
||||
|
||||
// NativeWatermarkFontHeight specifies the font size for the watermark text.
|
||||
NativeWatermarkFontHeight int
|
||||
|
||||
// NativeWatermarkRotateAngle specifies the rotation angle for the watermark
|
||||
// text in tenths of a degree (e.g., 450 = 45°).
|
||||
NativeWatermarkRotateAngle int
|
||||
|
||||
// NativeWatermarkFontName specifies the font name for the watermark text.
|
||||
// Default is "Helvetica".
|
||||
NativeWatermarkFontName string
|
||||
|
||||
// NativeTiledWatermarkText specifies the tiled watermark text.
|
||||
NativeTiledWatermarkText string
|
||||
|
||||
// PdfFormats allows to convert the resulting PDF to PDF/A-1b, PDF/A-2b,
|
||||
// PDF/A-3b and PDF/UA.
|
||||
PdfFormats gotenberg.PdfFormats
|
||||
@@ -178,6 +201,12 @@ func DefaultOptions() Options {
|
||||
Quality: 90,
|
||||
ReduceImageResolution: false,
|
||||
MaxImageResolution: 300,
|
||||
NativeWatermarkText: "",
|
||||
NativeWatermarkColor: 8388223,
|
||||
NativeWatermarkFontHeight: 0,
|
||||
NativeWatermarkRotateAngle: 0,
|
||||
NativeWatermarkFontName: "Helvetica",
|
||||
NativeTiledWatermarkText: "",
|
||||
PdfFormats: gotenberg.PdfFormats{
|
||||
PdfA: "",
|
||||
PdfUa: false,
|
||||
|
||||
@@ -302,6 +302,30 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP
|
||||
args = append(args, "--export", fmt.Sprintf("ReduceImageResolution=%t", options.ReduceImageResolution))
|
||||
args = append(args, "--export", fmt.Sprintf("MaxImageResolution=%d", options.MaxImageResolution))
|
||||
|
||||
if options.NativeWatermarkText != "" {
|
||||
args = append(args, "--export", fmt.Sprintf("Watermark=%s", options.NativeWatermarkText))
|
||||
}
|
||||
|
||||
if options.NativeWatermarkColor != 0 {
|
||||
args = append(args, "--export", fmt.Sprintf("WatermarkColor=%d", options.NativeWatermarkColor))
|
||||
}
|
||||
|
||||
if options.NativeWatermarkFontHeight > 0 {
|
||||
args = append(args, "--export", fmt.Sprintf("WatermarkFontHeight=%d", options.NativeWatermarkFontHeight))
|
||||
}
|
||||
|
||||
if options.NativeWatermarkRotateAngle != 0 {
|
||||
args = append(args, "--export", fmt.Sprintf("WatermarkRotateAngle=%d", options.NativeWatermarkRotateAngle))
|
||||
}
|
||||
|
||||
if options.NativeWatermarkFontName != "" && options.NativeWatermarkFontName != "Helvetica" {
|
||||
args = append(args, "--export", fmt.Sprintf("WatermarkFontName=%s", options.NativeWatermarkFontName))
|
||||
}
|
||||
|
||||
if options.NativeTiledWatermarkText != "" {
|
||||
args = append(args, "--export", fmt.Sprintf("TiledWatermark=%s", options.NativeTiledWatermarkText))
|
||||
}
|
||||
|
||||
switch options.PdfFormats.PdfA {
|
||||
case "":
|
||||
case gotenberg.PdfA1b:
|
||||
|
||||
@@ -116,6 +116,16 @@ func (engine *LibreOfficePdfEngine) EmbedFiles(ctx context.Context, logger *zap.
|
||||
return fmt.Errorf("embed files with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Watermark is not available in this implementation.
|
||||
func (engine *LibreOfficePdfEngine) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return fmt.Errorf("watermark PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Stamp is not available in this implementation.
|
||||
func (engine *LibreOfficePdfEngine) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return fmt.Errorf("stamp PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.Module = (*LibreOfficePdfEngine)(nil)
|
||||
|
||||
@@ -32,6 +32,10 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
||||
watermarkFiles := pdfengines.FormDataPdfWatermarkFiles(form)
|
||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
||||
stampFiles := pdfengines.FormDataPdfStampFiles(form)
|
||||
|
||||
zeroValuedSplitMode := gotenberg.SplitMode{}
|
||||
|
||||
@@ -60,6 +64,12 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
quality int
|
||||
reduceImageResolution bool
|
||||
maxImageResolution int
|
||||
nativeWatermarkText string
|
||||
nativeWatermarkColor int
|
||||
nativeWatermarkFontHeight int
|
||||
nativeWatermarkRotateAngle int
|
||||
nativeWatermarkFontName string
|
||||
nativeTiledWatermarkText string
|
||||
nativePdfFormats bool
|
||||
merge bool
|
||||
flatten bool
|
||||
@@ -128,6 +138,48 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
maxImageResolution = intValue
|
||||
return nil
|
||||
}).
|
||||
String("nativeWatermarkText", &nativeWatermarkText, defaultOptions.NativeWatermarkText).
|
||||
Custom("nativeWatermarkColor", func(value string) error {
|
||||
if value == "" {
|
||||
nativeWatermarkColor = defaultOptions.NativeWatermarkColor
|
||||
return nil
|
||||
}
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nativeWatermarkColor = intValue
|
||||
return nil
|
||||
}).
|
||||
Custom("nativeWatermarkFontHeight", func(value string) error {
|
||||
if value == "" {
|
||||
nativeWatermarkFontHeight = defaultOptions.NativeWatermarkFontHeight
|
||||
return nil
|
||||
}
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if intValue < 0 {
|
||||
return errors.New("value is inferior to 0")
|
||||
}
|
||||
nativeWatermarkFontHeight = intValue
|
||||
return nil
|
||||
}).
|
||||
Custom("nativeWatermarkRotateAngle", func(value string) error {
|
||||
if value == "" {
|
||||
nativeWatermarkRotateAngle = defaultOptions.NativeWatermarkRotateAngle
|
||||
return nil
|
||||
}
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nativeWatermarkRotateAngle = intValue
|
||||
return nil
|
||||
}).
|
||||
String("nativeWatermarkFontName", &nativeWatermarkFontName, defaultOptions.NativeWatermarkFontName).
|
||||
String("nativeTiledWatermarkText", &nativeTiledWatermarkText, defaultOptions.NativeTiledWatermarkText).
|
||||
Bool("nativePdfFormats", &nativePdfFormats, true).
|
||||
Bool("merge", &merge, false).
|
||||
Bool("flatten", &flatten, false).
|
||||
@@ -136,6 +188,13 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && len(watermarkFiles) > 0 {
|
||||
watermark.Expression = watermarkFiles[0]
|
||||
}
|
||||
if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && len(stampFiles) > 0 {
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
outputPaths := make([]string, len(inputPaths))
|
||||
for i, inputPath := range inputPaths {
|
||||
outputPaths[i] = ctx.GeneratePath(".pdf")
|
||||
@@ -163,6 +222,12 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
Quality: quality,
|
||||
ReduceImageResolution: reduceImageResolution,
|
||||
MaxImageResolution: maxImageResolution,
|
||||
NativeWatermarkText: nativeWatermarkText,
|
||||
NativeWatermarkColor: nativeWatermarkColor,
|
||||
NativeWatermarkFontHeight: nativeWatermarkFontHeight,
|
||||
NativeWatermarkRotateAngle: nativeWatermarkRotateAngle,
|
||||
NativeWatermarkFontName: nativeWatermarkFontName,
|
||||
NativeTiledWatermarkText: nativeTiledWatermarkText,
|
||||
}
|
||||
|
||||
if nativePdfFormats && splitMode == zeroValuedSplitMode {
|
||||
@@ -253,6 +318,16 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
}
|
||||
}
|
||||
|
||||
err = pdfengines.WatermarkStub(ctx, engine, watermark, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.StampStub(ctx, engine, stamp, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.EmbedFilesStub(ctx, engine, embedPaths, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("embed files into PDFs: %w", err)
|
||||
|
||||
@@ -379,6 +379,57 @@ func (engine *PdfCpu) Encrypt(ctx context.Context, logger *zap.Logger, inputPath
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watermark applies a watermark (behind page content) to a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return engine.applyStampOrWatermark(ctx, logger, "watermark", inputPath, stamp)
|
||||
}
|
||||
|
||||
// Stamp applies a stamp (on top of page content) to a PDF file using pdfcpu.
|
||||
func (engine *PdfCpu) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return engine.applyStampOrWatermark(ctx, logger, "stamp", inputPath, stamp)
|
||||
}
|
||||
|
||||
func (engine *PdfCpu) applyStampOrWatermark(ctx context.Context, logger *zap.Logger, command string, inputPath string, stamp gotenberg.Stamp) error {
|
||||
var mode string
|
||||
switch stamp.Source {
|
||||
case gotenberg.StampSourceText:
|
||||
mode = "text"
|
||||
case gotenberg.StampSourceImage:
|
||||
mode = "image"
|
||||
case gotenberg.StampSourcePDF:
|
||||
mode = "pdf"
|
||||
default:
|
||||
return fmt.Errorf("%s PDF with pdfcpu: %w", command, gotenberg.ErrPdfStampSourceNotSupported)
|
||||
}
|
||||
|
||||
// Build description from Options map.
|
||||
var descParts []string
|
||||
for k, v := range stamp.Options {
|
||||
descParts = append(descParts, fmt.Sprintf("%s:%s", k, v))
|
||||
}
|
||||
description := strings.Join(descParts, ", ")
|
||||
|
||||
args := []string{command, "add", "-mode", mode}
|
||||
|
||||
if stamp.Pages != "" {
|
||||
args = append(args, "-pages", stamp.Pages)
|
||||
}
|
||||
|
||||
args = append(args, "--", stamp.Expression, description, inputPath, inputPath)
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s PDF with pdfcpu: %w", command, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.Module = (*PdfCpu)(nil)
|
||||
|
||||
@@ -22,6 +22,8 @@ type multiPdfEngines struct {
|
||||
embedEngines []gotenberg.PdfEngine
|
||||
readBookmarksEngines []gotenberg.PdfEngine
|
||||
writeBookmarksEngines []gotenberg.PdfEngine
|
||||
watermarkEngines []gotenberg.PdfEngine
|
||||
stampEngines []gotenberg.PdfEngine
|
||||
}
|
||||
|
||||
func newMultiPdfEngines(
|
||||
@@ -34,7 +36,9 @@ func newMultiPdfEngines(
|
||||
passwordEngines,
|
||||
embedEngines,
|
||||
readBookmarksEngines,
|
||||
writeBookmarksEngines []gotenberg.PdfEngine,
|
||||
writeBookmarksEngines,
|
||||
watermarkEngines,
|
||||
stampEngines []gotenberg.PdfEngine,
|
||||
) *multiPdfEngines {
|
||||
return &multiPdfEngines{
|
||||
mergeEngines: mergeEngines,
|
||||
@@ -47,6 +51,8 @@ func newMultiPdfEngines(
|
||||
embedEngines: embedEngines,
|
||||
readBookmarksEngines: readBookmarksEngines,
|
||||
writeBookmarksEngines: writeBookmarksEngines,
|
||||
watermarkEngines: watermarkEngines,
|
||||
stampEngines: stampEngines,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +375,56 @@ func (multi *multiPdfEngines) EmbedFiles(ctx context.Context, logger *zap.Logger
|
||||
return fmt.Errorf("embed files into PDF using multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Watermark applies a watermark (behind page content) to a PDF file using the
|
||||
// first available engine that supports watermarking.
|
||||
func (multi *multiPdfEngines) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
var err error
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
for _, engine := range multi.watermarkEngines {
|
||||
go func(engine gotenberg.PdfEngine) {
|
||||
errChan <- engine.Watermark(ctx, logger, inputPath, stamp)
|
||||
}(engine)
|
||||
|
||||
select {
|
||||
case watermarkErr := <-errChan:
|
||||
errored := multierr.AppendInto(&err, watermarkErr)
|
||||
if !errored {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("watermark PDF with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Stamp applies a stamp (on top of page content) to a PDF file using the
|
||||
// first available engine that supports stamping.
|
||||
func (multi *multiPdfEngines) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
var err error
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
for _, engine := range multi.stampEngines {
|
||||
go func(engine gotenberg.PdfEngine) {
|
||||
errChan <- engine.Stamp(ctx, logger, inputPath, stamp)
|
||||
}(engine)
|
||||
|
||||
select {
|
||||
case stampErr := <-errChan:
|
||||
errored := multierr.AppendInto(&err, stampErr)
|
||||
if !errored {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("stamp PDF with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.PdfEngine = (*multiPdfEngines)(nil)
|
||||
|
||||
@@ -38,6 +38,8 @@ type PdfEngines struct {
|
||||
embedNames []string
|
||||
readBookmarksNames []string
|
||||
writeBookmarksNames []string
|
||||
watermarkNames []string
|
||||
stampNames []string
|
||||
engines []gotenberg.PdfEngine
|
||||
disableRoutes bool
|
||||
}
|
||||
@@ -58,6 +60,8 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
|
||||
fs.StringSlice("pdfengines-embed-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the file embedding feature - empty means all")
|
||||
fs.StringSlice("pdfengines-read-bookmarks-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the read bookmarks feature - empty means all")
|
||||
fs.StringSlice("pdfengines-write-bookmarks-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the write bookmarks feature - empty means all")
|
||||
fs.StringSlice("pdfengines-watermark-engines", []string{"pdfcpu", "pdftk"}, "Set the PDF engines and their order for the watermark feature - empty means all")
|
||||
fs.StringSlice("pdfengines-stamp-engines", []string{"pdfcpu", "pdftk"}, "Set the PDF engines and their order for the stamp feature - empty means all")
|
||||
fs.Bool("pdfengines-disable-routes", false, "Disable the routes")
|
||||
|
||||
// Deprecated flags.
|
||||
@@ -87,6 +91,8 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||
embedNames := flags.MustStringSlice("pdfengines-embed-engines")
|
||||
readBookmarksNames := flags.MustStringSlice("pdfengines-read-bookmarks-engines")
|
||||
writeBookmarksNames := flags.MustStringSlice("pdfengines-write-bookmarks-engines")
|
||||
watermarkNames := flags.MustStringSlice("pdfengines-watermark-engines")
|
||||
stampNames := flags.MustStringSlice("pdfengines-stamp-engines")
|
||||
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
|
||||
|
||||
engines, err := ctx.Modules(new(gotenberg.PdfEngine))
|
||||
@@ -163,6 +169,16 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||
mod.writeBookmarksNames = writeBookmarksNames
|
||||
}
|
||||
|
||||
mod.watermarkNames = defaultNames
|
||||
if len(watermarkNames) > 0 {
|
||||
mod.watermarkNames = watermarkNames
|
||||
}
|
||||
|
||||
mod.stampNames = defaultNames
|
||||
if len(stampNames) > 0 {
|
||||
mod.stampNames = stampNames
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -214,6 +230,8 @@ func (mod *PdfEngines) Validate() error {
|
||||
findNonExistingEngines(mod.embedNames)
|
||||
findNonExistingEngines(mod.readBookmarksNames)
|
||||
findNonExistingEngines(mod.writeBookmarksNames)
|
||||
findNonExistingEngines(mod.watermarkNames)
|
||||
findNonExistingEngines(mod.stampNames)
|
||||
|
||||
if len(nonExistingEngines) == 0 {
|
||||
return nil
|
||||
@@ -236,6 +254,8 @@ func (mod *PdfEngines) SystemMessages() []string {
|
||||
fmt.Sprintf("embed engines - %s", strings.Join(mod.embedNames[:], " ")),
|
||||
fmt.Sprintf("read bookmarks engines - %s", strings.Join(mod.readBookmarksNames[:], " ")),
|
||||
fmt.Sprintf("write bookmarks engines - %s", strings.Join(mod.writeBookmarksNames[:], " ")),
|
||||
fmt.Sprintf("watermark engines - %s", strings.Join(mod.watermarkNames[:], " ")),
|
||||
fmt.Sprintf("stamp engines - %s", strings.Join(mod.stampNames[:], " ")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +286,8 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) {
|
||||
engines(mod.embedNames),
|
||||
engines(mod.readBookmarksNames),
|
||||
engines(mod.writeBookmarksNames),
|
||||
engines(mod.watermarkNames),
|
||||
engines(mod.stampNames),
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -293,6 +315,8 @@ func (mod *PdfEngines) Routes() ([]api.Route, error) {
|
||||
writeBookmarksRoute(engine),
|
||||
encryptRoute(engine),
|
||||
embedRoute(engine),
|
||||
watermarkRoute(engine),
|
||||
stampRoute(engine),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -391,6 +391,122 @@ func EmbedFilesStub(ctx *api.Context, engine gotenberg.PdfEngine, embedPaths []s
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormDataPdfWatermark creates a [gotenberg.Stamp] for watermarking from the
|
||||
// form data.
|
||||
func FormDataPdfWatermark(form *api.FormData, mandatory bool) gotenberg.Stamp {
|
||||
return formDataPdfStampOrWatermark(form, "watermark", mandatory)
|
||||
}
|
||||
|
||||
// FormDataPdfStamp creates a [gotenberg.Stamp] for stamping from the form data.
|
||||
func FormDataPdfStamp(form *api.FormData, mandatory bool) gotenberg.Stamp {
|
||||
return formDataPdfStampOrWatermark(form, "stamp", mandatory)
|
||||
}
|
||||
|
||||
func formDataPdfStampOrWatermark(form *api.FormData, prefix string, mandatory bool) gotenberg.Stamp {
|
||||
var (
|
||||
source string
|
||||
expression string
|
||||
pages string
|
||||
options map[string]string
|
||||
)
|
||||
|
||||
sourceFunc := func(value string) error {
|
||||
if value != "" && value != gotenberg.StampSourceText && value != gotenberg.StampSourceImage && value != gotenberg.StampSourcePDF {
|
||||
return fmt.Errorf("wrong value, expected either '%s', '%s' or '%s'", gotenberg.StampSourceText, gotenberg.StampSourceImage, gotenberg.StampSourcePDF)
|
||||
}
|
||||
source = value
|
||||
return nil
|
||||
}
|
||||
|
||||
optionsFunc := func(value string) error {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal([]byte(value), &options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal %s options: %w", prefix, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if mandatory {
|
||||
form.
|
||||
MandatoryCustom(prefix+"Source", func(value string) error {
|
||||
return sourceFunc(value)
|
||||
}).
|
||||
String(prefix+"Expression", &expression, "").
|
||||
String(prefix+"Pages", &pages, "").
|
||||
Custom(prefix+"Options", func(value string) error {
|
||||
return optionsFunc(value)
|
||||
})
|
||||
} else {
|
||||
form.
|
||||
Custom(prefix+"Source", func(value string) error {
|
||||
return sourceFunc(value)
|
||||
}).
|
||||
String(prefix+"Expression", &expression, "").
|
||||
String(prefix+"Pages", &pages, "").
|
||||
Custom(prefix+"Options", func(value string) error {
|
||||
return optionsFunc(value)
|
||||
})
|
||||
}
|
||||
|
||||
return gotenberg.Stamp{
|
||||
Source: source,
|
||||
Expression: expression,
|
||||
Pages: pages,
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// FormDataPdfWatermarkFiles extracts watermark file paths from form data.
|
||||
func FormDataPdfWatermarkFiles(form *api.FormData) []string {
|
||||
var paths []string
|
||||
form.Watermarks(&paths)
|
||||
return paths
|
||||
}
|
||||
|
||||
// FormDataPdfStampFiles extracts stamp file paths from form data.
|
||||
func FormDataPdfStampFiles(form *api.FormData) []string {
|
||||
var paths []string
|
||||
form.Stamps(&paths)
|
||||
return paths
|
||||
}
|
||||
|
||||
// WatermarkStub applies a watermark to a list of PDF files. If the stamp has
|
||||
// no source, it does nothing.
|
||||
func WatermarkStub(ctx *api.Context, engine gotenberg.PdfEngine, stamp gotenberg.Stamp, inputPaths []string) error {
|
||||
if stamp.Source == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, inputPath := range inputPaths {
|
||||
err := engine.Watermark(ctx, ctx.Log(), inputPath, stamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark '%s': %w", inputPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StampStub applies a stamp to a list of PDF files. If the stamp has
|
||||
// no source, it does nothing.
|
||||
func StampStub(ctx *api.Context, engine gotenberg.PdfEngine, stamp gotenberg.Stamp, inputPaths []string) error {
|
||||
if stamp.Source == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, inputPath := range inputPaths {
|
||||
err := engine.Stamp(ctx, ctx.Log(), inputPath, stamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp '%s': %w", inputPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeRoute returns an [api.Route] which can merge PDFs.
|
||||
func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return api.Route{
|
||||
@@ -406,6 +522,10 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
bookmarks := FormDataPdfBookmarks(form, false)
|
||||
userPassword, ownerPassword := FormDataPdfEncrypt(form)
|
||||
embedPaths := FormDataPdfEmbeds(form)
|
||||
watermark := FormDataPdfWatermark(form, false)
|
||||
watermarkFiles := FormDataPdfWatermarkFiles(form)
|
||||
stamp := FormDataPdfStamp(form, false)
|
||||
stampFiles := FormDataPdfStampFiles(form)
|
||||
|
||||
var inputPaths []string
|
||||
var flatten bool
|
||||
@@ -419,6 +539,13 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && len(watermarkFiles) > 0 {
|
||||
watermark.Expression = watermarkFiles[0]
|
||||
}
|
||||
if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && len(stampFiles) > 0 {
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
outputPath := ctx.GeneratePath(".pdf")
|
||||
err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath)
|
||||
if err != nil {
|
||||
@@ -430,6 +557,16 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("convert PDF: %w", err)
|
||||
}
|
||||
|
||||
err = WatermarkStub(ctx, engine, watermark, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = StampStub(ctx, engine, stamp, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = EmbedFilesStub(ctx, engine, embedPaths, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("embed files into PDFs: %w", err)
|
||||
@@ -520,6 +657,10 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
metadata := FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := FormDataPdfEncrypt(form)
|
||||
embedPaths := FormDataPdfEmbeds(form)
|
||||
watermark := FormDataPdfWatermark(form, false)
|
||||
watermarkFiles := FormDataPdfWatermarkFiles(form)
|
||||
stamp := FormDataPdfStamp(form, false)
|
||||
stampFiles := FormDataPdfStampFiles(form)
|
||||
|
||||
var inputPaths []string
|
||||
var flatten bool
|
||||
@@ -531,6 +672,13 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && len(watermarkFiles) > 0 {
|
||||
watermark.Expression = watermarkFiles[0]
|
||||
}
|
||||
if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && len(stampFiles) > 0 {
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
outputPaths, err := SplitPdfStub(ctx, engine, mode, inputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("split PDFs: %w", err)
|
||||
@@ -541,6 +689,16 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("convert PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = WatermarkStub(ctx, engine, watermark, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = StampStub(ctx, engine, stamp, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("embed files into PDFs: %w", err)
|
||||
@@ -904,3 +1062,105 @@ func embedRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// watermarkRoute returns an [api.Route] which can add watermarks to PDFs.
|
||||
//
|
||||
//nolint:dupl
|
||||
func watermarkRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return api.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/forms/pdfengines/watermark",
|
||||
IsMultipart: true,
|
||||
Handler: func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*api.Context)
|
||||
|
||||
form := ctx.FormData()
|
||||
stamp := FormDataPdfWatermark(form, true)
|
||||
watermarkFiles := FormDataPdfWatermarkFiles(form)
|
||||
|
||||
var inputPaths []string
|
||||
err := form.
|
||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||
Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF {
|
||||
if len(watermarkFiles) == 0 {
|
||||
return api.WrapError(
|
||||
errors.New("no watermark file provided"),
|
||||
api.NewSentinelHttpError(
|
||||
http.StatusBadRequest,
|
||||
"Invalid form data: a watermark file is required for image or pdf source",
|
||||
),
|
||||
)
|
||||
}
|
||||
stamp.Expression = watermarkFiles[0]
|
||||
}
|
||||
|
||||
err = WatermarkStub(ctx, engine, stamp, inputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = ctx.AddOutputPaths(inputPaths...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add output paths: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// stampRoute returns an [api.Route] which can add stamps to PDFs.
|
||||
//
|
||||
//nolint:dupl
|
||||
func stampRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return api.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/forms/pdfengines/stamp",
|
||||
IsMultipart: true,
|
||||
Handler: func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*api.Context)
|
||||
|
||||
form := ctx.FormData()
|
||||
stamp := FormDataPdfStamp(form, true)
|
||||
stampFiles := FormDataPdfStampFiles(form)
|
||||
|
||||
var inputPaths []string
|
||||
err := form.
|
||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||
Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
if stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF {
|
||||
if len(stampFiles) == 0 {
|
||||
return api.WrapError(
|
||||
errors.New("no stamp file provided"),
|
||||
api.NewSentinelHttpError(
|
||||
http.StatusBadRequest,
|
||||
"Invalid form data: a stamp file is required for image or pdf source",
|
||||
),
|
||||
)
|
||||
}
|
||||
stamp.Expression = stampFiles[0]
|
||||
}
|
||||
|
||||
err = StampStub(ctx, engine, stamp, inputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = ctx.AddOutputPaths(inputPaths...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add output paths: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,64 @@ func (engine *PdfTk) EmbedFiles(ctx context.Context, logger *zap.Logger, filePat
|
||||
return fmt.Errorf("embed files with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Watermark applies a watermark (behind page content) to a PDF file using PDFtk.
|
||||
// Only PDF source is supported.
|
||||
func (engine *PdfTk) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
if stamp.Source != gotenberg.StampSourcePDF {
|
||||
return fmt.Errorf("watermark PDF with PDFtk: %w", gotenberg.ErrPdfStampSourceNotSupported)
|
||||
}
|
||||
|
||||
tmpPath := inputPath + ".tmp"
|
||||
|
||||
args := []string{inputPath, "background", stamp.Expression, "output", tmpPath}
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("watermark PDF with PDFtk: %w", err)
|
||||
}
|
||||
|
||||
err = os.Rename(tmpPath, inputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rename temporary output file with input file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stamp applies a stamp (on top of page content) to a PDF file using PDFtk.
|
||||
// Only PDF source is supported.
|
||||
func (engine *PdfTk) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
if stamp.Source != gotenberg.StampSourcePDF {
|
||||
return fmt.Errorf("stamp PDF with PDFtk: %w", gotenberg.ErrPdfStampSourceNotSupported)
|
||||
}
|
||||
|
||||
tmpPath := inputPath + ".tmp"
|
||||
|
||||
args := []string{inputPath, "stamp", stamp.Expression, "output", tmpPath}
|
||||
|
||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create command: %w", err)
|
||||
}
|
||||
|
||||
_, err = cmd.Exec()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stamp PDF with PDFtk: %w", err)
|
||||
}
|
||||
|
||||
err = os.Rename(tmpPath, inputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rename temporary output file with input file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.Module = (*PdfTk)(nil)
|
||||
|
||||
@@ -221,6 +221,16 @@ func (engine *QPdf) EmbedFiles(ctx context.Context, logger *zap.Logger, filePath
|
||||
return fmt.Errorf("embed files with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Watermark is not available in this implementation.
|
||||
func (engine *QPdf) Watermark(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return fmt.Errorf("watermark PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Stamp is not available in this implementation.
|
||||
func (engine *QPdf) Stamp(ctx context.Context, logger *zap.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||
return fmt.Errorf("stamp PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
var (
|
||||
_ gotenberg.Module = (*QPdf)(nil)
|
||||
_ gotenberg.Provisioner = (*QPdf)(nil)
|
||||
|
||||
@@ -988,6 +988,28 @@ Feature: /forms/chromium/convert/html
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the response PDF(s) should be encrypted
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/chromium/convert/html (Watermark - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
||||
| files | testdata/page-1-html/index.html | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@stamp
|
||||
Scenario: POST /forms/chromium/convert/html (Stamp - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
||||
| files | testdata/page-1-html/index.html | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@embed
|
||||
Scenario: POST /forms/chromium/convert/html (Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
@@ -1007,9 +1029,11 @@ Feature: /forms/chromium/convert/html
|
||||
# FIXME: once decrypt is done, add encrypt and check after the content of the PDF.
|
||||
@convert
|
||||
@metadata
|
||||
@watermark
|
||||
@stamp
|
||||
@flatten
|
||||
@embed
|
||||
Scenario: POST /forms/chromium/convert/html (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds)
|
||||
Scenario: POST /forms/chromium/convert/html (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/chromium/convert/html" endpoint with the following form data and header(s):
|
||||
| files | testdata/page-1-html/index.html | file |
|
||||
@@ -1025,7 +1049,7 @@ Feature: /forms/chromium/convert/html
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then there should be the following file(s) in the response:
|
||||
| foo.pdf |
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 9 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 11 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s)
|
||||
Then the response PDF(s) should be flatten
|
||||
Then the response PDF(s) should have the "embed_1.xml" file embedded
|
||||
|
||||
@@ -563,6 +563,62 @@ Feature: /forms/libreoffice/convert
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the response PDF(s) should be encrypted
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/libreoffice/convert (Watermark - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.docx | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@stamp
|
||||
Scenario: POST /forms/libreoffice/convert (Stamp - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.docx | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/libreoffice/convert (Native Watermark - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.docx | file |
|
||||
| nativeWatermarkText | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/libreoffice/convert (Native Watermark - Text with Options)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.docx | file |
|
||||
| nativeWatermarkText | DRAFT | field |
|
||||
| nativeWatermarkColor | 16711680 | field |
|
||||
| nativeWatermarkFontHeight | 48 | field |
|
||||
| nativeWatermarkRotateAngle | 450 | field |
|
||||
| nativeWatermarkFontName | Courier | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/libreoffice/convert (Native Watermark - Tiled)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.docx | file |
|
||||
| nativeTiledWatermarkText | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@embed
|
||||
Scenario: POST /forms/libreoffice/convert (Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
@@ -582,15 +638,21 @@ Feature: /forms/libreoffice/convert
|
||||
# FIXME: once decrypt is done, add encrypt and check after the content of the PDF.
|
||||
@convert
|
||||
@metadata
|
||||
@watermark
|
||||
@stamp
|
||||
@flatten
|
||||
@embed
|
||||
Scenario: POST /forms/libreoffice/convert (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds)
|
||||
Scenario: POST /forms/libreoffice/convert (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/libreoffice/convert" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.docx | file |
|
||||
| pdfa | PDF/A-1b | field |
|
||||
| pdfua | true | field |
|
||||
| metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
| flatten | true | field |
|
||||
| embeds | testdata/embed_1.xml | file |
|
||||
| embeds | testdata/embed_2.xml | file |
|
||||
@@ -600,8 +662,8 @@ Feature: /forms/libreoffice/convert
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then there should be the following file(s) in the response:
|
||||
| foo.pdf |
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 10 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 12 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 5 failed rule(s)
|
||||
Then the response PDF(s) should be flatten
|
||||
Then the response PDF(s) should have the "embed_1.xml" file embedded
|
||||
Then the response PDF(s) should have the "embed_2.xml" file embedded
|
||||
|
||||
@@ -401,6 +401,30 @@ Feature: /forms/pdfengines/merge
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the response PDF(s) should be encrypted
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/pdfengines/merge (Watermark - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| files | testdata/page_2.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@stamp
|
||||
Scenario: POST /forms/pdfengines/merge (Stamp - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| files | testdata/page_2.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
@embed
|
||||
Scenario: POST /foo/forms/pdfengines/merge (Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
@@ -417,10 +441,12 @@ Feature: /forms/pdfengines/merge
|
||||
# FIXME: once decrypt is done, add encrypt and check after the content of the PDF.
|
||||
@convert
|
||||
@metadata
|
||||
@watermark
|
||||
@stamp
|
||||
@flatten
|
||||
@embed
|
||||
@bookmarks
|
||||
Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds & Bookmarks)
|
||||
Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds & Bookmarks)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
@@ -428,6 +454,10 @@ Feature: /forms/pdfengines/merge
|
||||
| pdfa | PDF/A-1b | field |
|
||||
| pdfua | true | field |
|
||||
| metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
| bookmarks | [{"title":"Merged Index","page":1}] | field |
|
||||
| flatten | true | field |
|
||||
| embeds | testdata/embed_1.xml | file |
|
||||
@@ -447,8 +477,8 @@ Feature: /forms/pdfengines/merge
|
||||
"""
|
||||
Page 2
|
||||
"""
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 10 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 12 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 5 failed rule(s)
|
||||
Then the response PDF(s) should be flatten
|
||||
Then the response PDF(s) should have the "embed_1.xml" file embedded
|
||||
Then the response PDF(s) should have the "embed_2.xml" file embedded
|
||||
|
||||
@@ -473,6 +473,32 @@ Feature: /forms/pdfengines/split
|
||||
Then there should be 2 PDF(s) in the response
|
||||
Then the response PDF(s) should be encrypted
|
||||
|
||||
@watermark
|
||||
Scenario: POST /forms/pdfengines/split (Watermark - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s):
|
||||
| files | testdata/pages_3.pdf | file |
|
||||
| splitMode | intervals | field |
|
||||
| splitSpan | 2 | field |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/zip"
|
||||
Then there should be 2 PDF(s) in the response
|
||||
|
||||
@stamp
|
||||
Scenario: POST /forms/pdfengines/split (Stamp - Text)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s):
|
||||
| files | testdata/pages_3.pdf | file |
|
||||
| splitMode | intervals | field |
|
||||
| splitSpan | 2 | field |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/zip"
|
||||
Then there should be 2 PDF(s) in the response
|
||||
|
||||
@embed
|
||||
Scenario: POST /foo/forms/pdfengines/split (Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
@@ -494,20 +520,26 @@ Feature: /forms/pdfengines/split
|
||||
# FIXME: once decrypt is done, add encrypt and check after the content of the PDFs.
|
||||
@convert
|
||||
@metadata
|
||||
@watermark
|
||||
@stamp
|
||||
@flatten
|
||||
@embed
|
||||
Scenario: POST /forms/pdfengines/split (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds)
|
||||
Scenario: POST /forms/pdfengines/split (PDF/A-1b & PDF/UA-1 & Metadata & Watermark & Stamp & Flatten & Embeds)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/split" endpoint with the following form data and header(s):
|
||||
| files | testdata/pages_3.pdf | file |
|
||||
| splitMode | intervals | field |
|
||||
| splitSpan | 2 | field |
|
||||
| pdfa | PDF/A-1b | field |
|
||||
| pdfua | true | field |
|
||||
| metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field |
|
||||
| flatten | true | field |
|
||||
| embeds | testdata/embed_1.xml | file |
|
||||
| embeds | testdata/embed_2.xml | file |
|
||||
| files | testdata/pages_3.pdf | file |
|
||||
| splitMode | intervals | field |
|
||||
| splitSpan | 2 | field |
|
||||
| pdfa | PDF/A-1b | field |
|
||||
| pdfua | true | field |
|
||||
| metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
| flatten | true | field |
|
||||
| embeds | testdata/embed_1.xml | file |
|
||||
| embeds | testdata/embed_2.xml | file |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/zip"
|
||||
Then there should be 2 PDF(s) in the response
|
||||
@@ -528,8 +560,8 @@ Feature: /forms/pdfengines/split
|
||||
"""
|
||||
Page 3
|
||||
"""
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 10 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 2 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/A-1b" with a tolerance of 12 failed rule(s)
|
||||
Then the response PDF(s) should be valid "PDF/UA-1" with a tolerance of 5 failed rule(s)
|
||||
Then the response PDF(s) should be flatten
|
||||
Then the response PDF(s) should have the "embed_1.xml" file embedded
|
||||
Then the response PDF(s) should have the "embed_2.xml" file embedded
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
@pdfengines
|
||||
@pdfengines-stamp
|
||||
@stamp
|
||||
Feature: /forms/pdfengines/stamp
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Text - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the "page_1.pdf" PDF should have 1 page(s)
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Text with Pages - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/pages_3.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
| stampPages | 1-2 | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the "pages_3.pdf" PDF should have 3 page(s)
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Text with Options - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | SAMPLE | field |
|
||||
| stampOptions | {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"} | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Image - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stamps | testdata/watermark.png | file |
|
||||
| stampSource | image | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (PDF - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stamps | testdata/page_2.pdf | file |
|
||||
| stampSource | pdf | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (PDF - pdftk)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdftk |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stamps | testdata/page_2.pdf | file |
|
||||
| stampSource | pdf | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Text - pdftk unsupported)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_STAMP_ENGINES | pdftk |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
At least one PDF engine cannot process the requested stamp source type, while others may have failed due to different issues
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Many PDFs)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| files | testdata/page_2.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | DRAFT | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/zip"
|
||||
Then there should be 2 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Bad Request - No Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: form field 'stampSource' is required
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Bad Request - Invalid Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | foo | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should contain string:
|
||||
"""
|
||||
Invalid form data: form field 'stampSource' is invalid
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Bad Request - Missing File for Image Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | image | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: a stamp file is required for image or pdf source
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Bad Request - Missing File for PDF Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | pdf | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: a stamp file is required for image or pdf source
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Bad Request - No PDF)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: no form file found for extensions: [.pdf]
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Routes Disabled)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_DISABLE_ROUTES | true |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 404
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Gotenberg Trace)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
| Gotenberg-Trace | forms_pdfengines_stamp | header |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then the response header "Gotenberg-Trace" should be "forms_pdfengines_stamp"
|
||||
Then the Gotenberg container should log the following entries:
|
||||
| "trace":"forms_pdfengines_stamp" |
|
||||
|
||||
@webhook
|
||||
Scenario: POST /forms/pdfengines/stamp (Webhook)
|
||||
Given I have a default Gotenberg container
|
||||
Given I have a webhook server
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
|
||||
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
|
||||
Then the response status code should be 204
|
||||
When I wait for the asynchronous request to the webhook
|
||||
Then the webhook request header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the webhook request
|
||||
|
||||
Scenario: POST /forms/pdfengines/stamp (Basic Auth)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| API_ENABLE_BASIC_AUTH | true |
|
||||
| GOTENBERG_API_BASIC_AUTH_USERNAME | foo |
|
||||
| GOTENBERG_API_BASIC_AUTH_PASSWORD | bar |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 401
|
||||
|
||||
Scenario: POST /foo/forms/pdfengines/stamp (Root Path)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| API_ENABLE_DEBUG_ROUTE | true |
|
||||
| API_ROOT_PATH | /foo/ |
|
||||
When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/stamp" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| stampSource | text | field |
|
||||
| stampExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
@@ -0,0 +1,211 @@
|
||||
@pdfengines
|
||||
@pdfengines-watermark
|
||||
@watermark
|
||||
Feature: /forms/pdfengines/watermark
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Text - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the "page_1.pdf" PDF should have 1 page(s)
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Text with Pages - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/pages_3.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | DRAFT | field |
|
||||
| watermarkPages | 1-2 | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
Then the "pages_3.pdf" PDF should have 3 page(s)
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Text with Options - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | SAMPLE | field |
|
||||
| watermarkOptions | {"scale":"0.5 abs","rot":"45","fillcolor":"#FF0000"} | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Image - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarks | testdata/watermark.png | file |
|
||||
| watermarkSource | image | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (PDF - pdfcpu)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarks | testdata/page_2.pdf | file |
|
||||
| watermarkSource | pdf | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (PDF - pdftk)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdftk |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarks | testdata/page_2.pdf | file |
|
||||
| watermarkSource | pdf | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Text - pdftk unsupported)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_WATERMARK_ENGINES | pdftk |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
At least one PDF engine cannot process the requested stamp source type, while others may have failed due to different issues
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Many PDFs)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| files | testdata/page_2.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | DRAFT | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/zip"
|
||||
Then there should be 2 PDF(s) in the response
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Bad Request - No Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: form field 'watermarkSource' is required
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Bad Request - Invalid Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | foo | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should contain string:
|
||||
"""
|
||||
Invalid form data: form field 'watermarkSource' is invalid
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Bad Request - Missing File for Image Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | image | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: a watermark file is required for image or pdf source
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Bad Request - Missing File for PDF Source)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | pdf | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: a watermark file is required for image or pdf source
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Bad Request - No PDF)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 400
|
||||
Then the response body should match string:
|
||||
"""
|
||||
Invalid form data: no form file found for extensions: [.pdf]
|
||||
"""
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Routes Disabled)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| PDFENGINES_DISABLE_ROUTES | true |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 404
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Gotenberg Trace)
|
||||
Given I have a default Gotenberg container
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
| Gotenberg-Trace | forms_pdfengines_watermark | header |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
Then the response header "Gotenberg-Trace" should be "forms_pdfengines_watermark"
|
||||
Then the Gotenberg container should log the following entries:
|
||||
| "trace":"forms_pdfengines_watermark" |
|
||||
|
||||
@webhook
|
||||
Scenario: POST /forms/pdfengines/watermark (Webhook)
|
||||
Given I have a default Gotenberg container
|
||||
Given I have a webhook server
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
|
||||
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
|
||||
Then the response status code should be 204
|
||||
When I wait for the asynchronous request to the webhook
|
||||
Then the webhook request header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the webhook request
|
||||
|
||||
Scenario: POST /forms/pdfengines/watermark (Basic Auth)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| API_ENABLE_BASIC_AUTH | true |
|
||||
| GOTENBERG_API_BASIC_AUTH_USERNAME | foo |
|
||||
| GOTENBERG_API_BASIC_AUTH_PASSWORD | bar |
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 401
|
||||
|
||||
Scenario: POST /foo/forms/pdfengines/watermark (Root Path)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| API_ENABLE_DEBUG_ROUTE | true |
|
||||
| API_ROOT_PATH | /foo/ |
|
||||
When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/watermark" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| watermarkSource | text | field |
|
||||
| watermarkExpression | CONFIDENTIAL | field |
|
||||
Then the response status code should be 200
|
||||
Then the response header "Content-Type" should be "application/pdf"
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 77 B |
Reference in New Issue
Block a user