mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-12 10:22:14 +01:00
feat(pdfengines): add watermark and stamp feature
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user