mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 04:32:15 +01:00
feat(pdfengines): apply multiple stamps and watermarks on every route
This commit is contained in:
@@ -494,6 +494,21 @@ func (form *FormData) Stamps(target *[]string) *FormData {
|
|||||||
return form
|
return form
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watermarks binds the absolute paths of every file uploaded with the
|
||||||
|
// "watermark" field name, in submission order. Unlike [FormData.Watermark], it
|
||||||
|
// keeps all of them so a route can apply several watermarks in a single request.
|
||||||
|
func (form *FormData) Watermarks(target *[]string) *FormData {
|
||||||
|
if form.errors != nil {
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
if paths, ok := form.filesByField[WatermarkFormField]; ok {
|
||||||
|
*target = paths
|
||||||
|
}
|
||||||
|
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
// Strings binds every value submitted for key, in submission order. A field
|
// Strings binds every value submitted for key, in submission order. A field
|
||||||
// repeated in the multipart body (e.g. multiple "stampSource") contributes one
|
// repeated in the multipart body (e.g. multiple "stampSource") contributes one
|
||||||
// entry per occurrence, which lets a route read parallel field arrays.
|
// entry per occurrence, which lets a route read parallel field arrays.
|
||||||
|
|||||||
@@ -1880,3 +1880,18 @@ func TestFormData_Stamps(t *testing.T) {
|
|||||||
t.Errorf("expected nil when no stamp file was uploaded, got %+v", none)
|
t.Errorf("expected nil when no stamp file was uploaded, got %+v", none)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormData_Watermarks(t *testing.T) {
|
||||||
|
form := &FormData{
|
||||||
|
filesByField: map[string][]string{
|
||||||
|
WatermarkFormField: {"/tmp/abc/a.png"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var got []string
|
||||||
|
form.Watermarks(&got)
|
||||||
|
|
||||||
|
if want := []string{"/tmp/abc/a.png"}; !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("expected %+v, got %+v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -474,10 +474,16 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
||||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
watermarks, wErr := pdfengines.FormDataPdfWatermarks(form)
|
||||||
watermarkFile := pdfengines.FormDataPdfWatermarkFile(form)
|
if wErr != nil {
|
||||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
return fmt.Errorf("form data watermarks: %w", wErr)
|
||||||
stampFile := pdfengines.FormDataPdfStampFile(form)
|
}
|
||||||
|
stamps, sErr := pdfengines.FormDataPdfStamps(form)
|
||||||
|
if sErr != nil {
|
||||||
|
return fmt.Errorf("form data stamps: %w", sErr)
|
||||||
|
}
|
||||||
|
var watermarkFiles, stampFiles []string
|
||||||
|
form.Watermarks(&watermarkFiles).Stamps(&stampFiles)
|
||||||
rotateAngle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
rotateAngle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
||||||
optimizeImages, imageQuality := pdfengines.FormDataPdfOptimize(form)
|
optimizeImages, imageQuality := pdfengines.FormDataPdfOptimize(form)
|
||||||
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
||||||
@@ -496,16 +502,16 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("reject URL scheme: %w", err)
|
return fmt.Errorf("reject URL scheme: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.EnsureWatermarkFile(&watermark, watermarkFile)
|
err = pdfengines.BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
}
|
}
|
||||||
err = pdfengines.EnsureStampFile(&stamp, stampFile)
|
err = pdfengines.BindStampFiles(stamps, stampFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate stamp: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, encrypt, embedPaths, embedsMetadata, facturX, facturxXmlPath, watermark, stamp, rotateAngle, rotatePages, optimizeImages, imageQuality)
|
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, encrypt, embedPaths, embedsMetadata, facturX, facturxXmlPath, watermarks, stamps, rotateAngle, rotatePages, optimizeImages, imageQuality)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert URL to PDF: %w", err)
|
return fmt.Errorf("convert URL to PDF: %w", err)
|
||||||
}
|
}
|
||||||
@@ -564,10 +570,16 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
||||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
watermarks, wErr := pdfengines.FormDataPdfWatermarks(form)
|
||||||
watermarkFile := pdfengines.FormDataPdfWatermarkFile(form)
|
if wErr != nil {
|
||||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
return fmt.Errorf("form data watermarks: %w", wErr)
|
||||||
stampFile := pdfengines.FormDataPdfStampFile(form)
|
}
|
||||||
|
stamps, sErr := pdfengines.FormDataPdfStamps(form)
|
||||||
|
if sErr != nil {
|
||||||
|
return fmt.Errorf("form data stamps: %w", sErr)
|
||||||
|
}
|
||||||
|
var watermarkFiles, stampFiles []string
|
||||||
|
form.Watermarks(&watermarkFiles).Stamps(&stampFiles)
|
||||||
rotateAngle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
rotateAngle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
||||||
optimizeImages, imageQuality := pdfengines.FormDataPdfOptimize(form)
|
optimizeImages, imageQuality := pdfengines.FormDataPdfOptimize(form)
|
||||||
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
||||||
@@ -581,18 +593,18 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.EnsureWatermarkFile(&watermark, watermarkFile)
|
err = pdfengines.BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
}
|
}
|
||||||
err = pdfengines.EnsureStampFile(&stamp, stampFile)
|
err = pdfengines.BindStampFiles(stamps, stampFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate stamp: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("file://%s", inputPath)
|
url := fmt.Sprintf("file://%s", inputPath)
|
||||||
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
||||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, encrypt, embedPaths, embedsMetadata, facturX, facturxXmlPath, watermark, stamp, rotateAngle, rotatePages, optimizeImages, imageQuality)
|
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, encrypt, embedPaths, embedsMetadata, facturX, facturxXmlPath, watermarks, stamps, rotateAngle, rotatePages, optimizeImages, imageQuality)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert HTML to PDF: %w", err)
|
return fmt.Errorf("convert HTML to PDF: %w", err)
|
||||||
}
|
}
|
||||||
@@ -648,10 +660,16 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
||||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
watermarks, wErr := pdfengines.FormDataPdfWatermarks(form)
|
||||||
watermarkFile := pdfengines.FormDataPdfWatermarkFile(form)
|
if wErr != nil {
|
||||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
return fmt.Errorf("form data watermarks: %w", wErr)
|
||||||
stampFile := pdfengines.FormDataPdfStampFile(form)
|
}
|
||||||
|
stamps, sErr := pdfengines.FormDataPdfStamps(form)
|
||||||
|
if sErr != nil {
|
||||||
|
return fmt.Errorf("form data stamps: %w", sErr)
|
||||||
|
}
|
||||||
|
var watermarkFiles, stampFiles []string
|
||||||
|
form.Watermarks(&watermarkFiles).Stamps(&stampFiles)
|
||||||
rotateAngle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
rotateAngle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
||||||
optimizeImages, imageQuality := pdfengines.FormDataPdfOptimize(form)
|
optimizeImages, imageQuality := pdfengines.FormDataPdfOptimize(form)
|
||||||
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
||||||
@@ -670,13 +688,13 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.EnsureWatermarkFile(&watermark, watermarkFile)
|
err = pdfengines.BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
}
|
}
|
||||||
err = pdfengines.EnsureStampFile(&stamp, stampFile)
|
err = pdfengines.BindStampFiles(stamps, stampFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate stamp: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := markdownToHtml(ctx, inputPath, markdownPaths)
|
url, err := markdownToHtml(ctx, inputPath, markdownPaths)
|
||||||
@@ -685,7 +703,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
options.AllowedFilePrefixes = []string{ctx.DirPath()}
|
||||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, encrypt, embedPaths, embedsMetadata, facturX, facturxXmlPath, watermark, stamp, rotateAngle, rotatePages, optimizeImages, imageQuality)
|
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, encrypt, embedPaths, embedsMetadata, facturX, facturxXmlPath, watermarks, stamps, rotateAngle, rotatePages, optimizeImages, imageQuality)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert markdown to PDF: %w", err)
|
return fmt.Errorf("convert markdown to PDF: %w", err)
|
||||||
}
|
}
|
||||||
@@ -810,7 +828,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
|
|||||||
return fmt.Sprintf("file://%s", inputPath), nil
|
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, encrypt gotenberg.EncryptOptions, embedPaths []string, embedsMetadata map[string]map[string]string, facturX gotenberg.FacturX, facturxXmlPath string, watermark, stamp gotenberg.Stamp, rotateAngle int, rotatePages string, optimizeImages bool, imageQuality int) 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, encrypt gotenberg.EncryptOptions, embedPaths []string, embedsMetadata map[string]map[string]string, facturX gotenberg.FacturX, facturxXmlPath string, watermarks, stamps []gotenberg.Stamp, rotateAngle int, rotatePages string, optimizeImages bool, imageQuality int) error {
|
||||||
outputPath := ctx.GeneratePath(".pdf")
|
outputPath := ctx.GeneratePath(".pdf")
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/1130.
|
// See https://github.com/gotenberg/gotenberg/issues/1130.
|
||||||
filename := ctx.OutputFilename(outputPath)
|
filename := ctx.OutputFilename(outputPath)
|
||||||
@@ -892,12 +910,12 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
return fmt.Errorf("split PDF: %w", err)
|
return fmt.Errorf("split PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.WatermarkStub(ctx, engine, watermark, outputPaths)
|
err = pdfengines.WatermarkStub(ctx, engine, watermarks, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("watermark PDFs: %w", err)
|
return fmt.Errorf("watermark PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.StampStub(ctx, engine, stamp, outputPaths)
|
err = pdfengines.StampStub(ctx, engine, stamps, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stamp PDFs: %w", err)
|
return fmt.Errorf("stamp PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,16 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
encrypt := pdfengines.FormDataPdfEncrypt(form)
|
||||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||||
watermark := pdfengines.FormDataPdfWatermark(form, false)
|
watermarks, wErr := pdfengines.FormDataPdfWatermarks(form)
|
||||||
watermarkFile := pdfengines.FormDataPdfWatermarkFile(form)
|
if wErr != nil {
|
||||||
stamp := pdfengines.FormDataPdfStamp(form, false)
|
return fmt.Errorf("form data watermarks: %w", wErr)
|
||||||
stampFile := pdfengines.FormDataPdfStampFile(form)
|
}
|
||||||
|
stamps, sErr := pdfengines.FormDataPdfStamps(form)
|
||||||
|
if sErr != nil {
|
||||||
|
return fmt.Errorf("form data stamps: %w", sErr)
|
||||||
|
}
|
||||||
|
var watermarkFiles, stampFiles []string
|
||||||
|
form.Watermarks(&watermarkFiles).Stamps(&stampFiles)
|
||||||
angle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
angle, rotatePages := pdfengines.FormDataPdfRotate(form, false)
|
||||||
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
embedsMetadata := pdfengines.FormDataPdfEmbedsMetadata(form)
|
||||||
facturX, facturxXmlPath := pdfengines.FormDataPdfFacturX(form)
|
facturX, facturxXmlPath := pdfengines.FormDataPdfFacturX(form)
|
||||||
@@ -311,13 +317,13 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.EnsureWatermarkFile(&watermark, watermarkFile)
|
err = pdfengines.BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
}
|
}
|
||||||
err = pdfengines.EnsureStampFile(&stamp, stampFile)
|
err = pdfengines.BindStampFiles(stamps, stampFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate stamp: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.ValidatePdfFormatsCompat(pdfFormats, encrypt.UserPassword, embedPaths)
|
err = pdfengines.ValidatePdfFormatsCompat(pdfFormats, encrypt.UserPassword, embedPaths)
|
||||||
@@ -339,7 +345,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
// requested. The conversion runs as a post-processing step below.
|
// requested. The conversion runs as a post-processing step below.
|
||||||
pdfFormats = pdfengines.FacturXPdfFormats(ctx, engine, facturX, pdfFormats, true, nil)
|
pdfFormats = pdfengines.FacturXPdfFormats(ctx, engine, facturX, pdfFormats, true, nil)
|
||||||
|
|
||||||
hasPostProcessing := watermark.Source != "" || stamp.Source != "" || angle != 0 ||
|
hasPostProcessing := len(watermarks) > 0 || len(stamps) > 0 || angle != 0 ||
|
||||||
len(embedPaths) > 0 || len(metadata) > 0 || flatten || facturX.ConformanceLevel != ""
|
len(embedPaths) > 0 || len(metadata) > 0 || flatten || facturX.ConformanceLevel != ""
|
||||||
|
|
||||||
outputPaths := make([]string, len(inputPaths))
|
outputPaths := make([]string, len(inputPaths))
|
||||||
@@ -494,12 +500,12 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.WatermarkStub(ctx, engine, watermark, outputPaths)
|
err = pdfengines.WatermarkStub(ctx, engine, watermarks, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("watermark PDFs: %w", err)
|
return fmt.Errorf("watermark PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.StampStub(ctx, engine, stamp, outputPaths)
|
err = pdfengines.StampStub(ctx, engine, stamps, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stamp PDFs: %w", err)
|
return fmt.Errorf("stamp PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -829,125 +829,33 @@ func EmbedFilesStub(ctx *api.Context, engine gotenberg.PdfEngine, embedPaths []s
|
|||||||
return nil
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormDataPdfWatermarkFile extracts the watermark file path from form data.
|
|
||||||
func FormDataPdfWatermarkFile(form *api.FormData) string {
|
|
||||||
var path string
|
|
||||||
form.Watermark(&path)
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormDataPdfStampFile extracts the stamp file path from form data.
|
|
||||||
func FormDataPdfStampFile(form *api.FormData) string {
|
|
||||||
var path string
|
|
||||||
form.Stamp(&path)
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnsureStampFile validates that, when stamp.Source is image or pdf, an
|
|
||||||
// uploaded stamp file was supplied, and replaces stamp.Expression with
|
|
||||||
// uploadedFile in that case. Returning an [api] HTTP 400 error prevents
|
|
||||||
// an anonymous caller from passing an arbitrary filesystem path via
|
|
||||||
// stampExpression and having pdfcpu read it. Source values of text or
|
|
||||||
// empty are passed through unchanged.
|
|
||||||
func EnsureStampFile(stamp *gotenberg.Stamp, uploadedFile string) error {
|
|
||||||
if stamp.Source != gotenberg.StampSourceImage && stamp.Source != gotenberg.StampSourcePDF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if uploadedFile == "" {
|
|
||||||
return api.WrapError(
|
|
||||||
errors.New("no stamp file provided for image or pdf source"),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"Invalid form data: a stamp file is required for image or pdf source",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
stamp.Expression = uploadedFile
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormDataPdfStamps builds the ordered list of stamps from the repeated stamp
|
// FormDataPdfStamps builds the ordered list of stamps from the repeated stamp
|
||||||
// fields: stampSource, stampExpression, stampPages and stampOptions. The number
|
// fields. See [formDataPdfStampsOrWatermarks].
|
||||||
// of stamps equals the number of stampSource values, so a single occurrence of
|
|
||||||
// each field yields one stamp, preserving the single-stamp behavior. Fields are
|
|
||||||
// aligned by position; a missing expression, pages or options entry defaults to
|
|
||||||
// empty. Image and pdf stamps take their file from the uploaded stamp files, in
|
|
||||||
// order (see [BindStampFiles]).
|
|
||||||
func FormDataPdfStamps(form *api.FormData) ([]gotenberg.Stamp, error) {
|
func FormDataPdfStamps(form *api.FormData) ([]gotenberg.Stamp, error) {
|
||||||
|
return formDataPdfStampsOrWatermarks(form, "stamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormDataPdfWatermarks builds the ordered list of watermarks from the repeated
|
||||||
|
// watermark fields. See [formDataPdfStampsOrWatermarks].
|
||||||
|
func FormDataPdfWatermarks(form *api.FormData) ([]gotenberg.Stamp, error) {
|
||||||
|
return formDataPdfStampsOrWatermarks(form, "watermark")
|
||||||
|
}
|
||||||
|
|
||||||
|
// formDataPdfStampsOrWatermarks builds the ordered list of stamps or watermarks
|
||||||
|
// from the repeated {prefix}Source, {prefix}Expression, {prefix}Pages and
|
||||||
|
// {prefix}Options fields. The number of entries equals the number of
|
||||||
|
// {prefix}Source values, so a single occurrence of each field yields one entry,
|
||||||
|
// preserving the single-stamp/watermark behavior. Fields are aligned by
|
||||||
|
// position; a missing expression, pages or options entry defaults to empty.
|
||||||
|
// Image and pdf entries take their file from the uploaded files, in order (see
|
||||||
|
// [bindStampOrWatermarkFiles]).
|
||||||
|
func formDataPdfStampsOrWatermarks(form *api.FormData, prefix string) ([]gotenberg.Stamp, error) {
|
||||||
var sources, expressions, pages, options []string
|
var sources, expressions, pages, options []string
|
||||||
form.
|
form.
|
||||||
Strings("stampSource", &sources).
|
Strings(prefix+"Source", &sources).
|
||||||
Strings("stampExpression", &expressions).
|
Strings(prefix+"Expression", &expressions).
|
||||||
Strings("stampPages", &pages).
|
Strings(prefix+"Pages", &pages).
|
||||||
Strings("stampOptions", &options)
|
Strings(prefix+"Options", &options)
|
||||||
|
|
||||||
at := func(values []string, i int) string {
|
at := func(values []string, i int) string {
|
||||||
if i < len(values) {
|
if i < len(values) {
|
||||||
@@ -960,10 +868,10 @@ func FormDataPdfStamps(form *api.FormData) ([]gotenberg.Stamp, error) {
|
|||||||
for i, source := range sources {
|
for i, source := range sources {
|
||||||
if source != gotenberg.StampSourceText && source != gotenberg.StampSourceImage && source != gotenberg.StampSourcePDF {
|
if source != gotenberg.StampSourceText && source != gotenberg.StampSourceImage && source != gotenberg.StampSourcePDF {
|
||||||
return nil, api.WrapError(
|
return nil, api.WrapError(
|
||||||
fmt.Errorf("wrong stampSource value '%s'", source),
|
fmt.Errorf("wrong %sSource value '%s'", prefix, source),
|
||||||
api.NewSentinelHttpError(
|
api.NewSentinelHttpError(
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
fmt.Sprintf("Invalid form data: form field 'stampSource' is invalid (got '%s', resulting to wrong value, expected either '%s', '%s' or '%s')", source, gotenberg.StampSourceText, gotenberg.StampSourceImage, gotenberg.StampSourcePDF),
|
fmt.Sprintf("Invalid form data: form field '%sSource' is invalid (got '%s', resulting to wrong value, expected either '%s', '%s' or '%s')", prefix, source, gotenberg.StampSourceText, gotenberg.StampSourceImage, gotenberg.StampSourcePDF),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -973,10 +881,10 @@ func FormDataPdfStamps(form *api.FormData) ([]gotenberg.Stamp, error) {
|
|||||||
err := json.Unmarshal([]byte(raw), &opts)
|
err := json.Unmarshal([]byte(raw), &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, api.WrapError(
|
return nil, api.WrapError(
|
||||||
fmt.Errorf("unmarshal stampOptions: %w", err),
|
fmt.Errorf("unmarshal %sOptions: %w", prefix, err),
|
||||||
api.NewSentinelHttpError(
|
api.NewSentinelHttpError(
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
"Invalid form data: form field 'stampOptions' is invalid",
|
fmt.Sprintf("Invalid form data: form field '%sOptions' is invalid", prefix),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -993,82 +901,77 @@ func FormDataPdfStamps(form *api.FormData) ([]gotenberg.Stamp, error) {
|
|||||||
return stamps, nil
|
return stamps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindStampFiles assigns each image or pdf stamp its uploaded file, consuming
|
// BindStampFiles assigns each image or pdf stamp its uploaded stamp file. See
|
||||||
// stampFiles in order. Text stamps take no file. It returns an [api] HTTP 400
|
// [bindStampOrWatermarkFiles].
|
||||||
// error when an image or pdf stamp has no file left to consume, which also
|
|
||||||
// prevents an anonymous caller from passing an arbitrary filesystem path via
|
|
||||||
// stampExpression.
|
|
||||||
func BindStampFiles(stamps []gotenberg.Stamp, stampFiles []string) error {
|
func BindStampFiles(stamps []gotenberg.Stamp, stampFiles []string) error {
|
||||||
|
return bindStampOrWatermarkFiles(stamps, stampFiles, "stamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindWatermarkFiles assigns each image or pdf watermark its uploaded watermark
|
||||||
|
// file. See [bindStampOrWatermarkFiles].
|
||||||
|
func BindWatermarkFiles(watermarks []gotenberg.Stamp, watermarkFiles []string) error {
|
||||||
|
return bindStampOrWatermarkFiles(watermarks, watermarkFiles, "watermark")
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStampOrWatermarkFiles assigns each image or pdf entry its uploaded file,
|
||||||
|
// consuming files in order. Text entries take no file. It returns an [api] HTTP
|
||||||
|
// 400 error when an image or pdf entry has no file left to consume, which also
|
||||||
|
// prevents an anonymous caller from passing an arbitrary filesystem path via
|
||||||
|
// the expression field. kind is "stamp" or "watermark" and shapes the error.
|
||||||
|
func bindStampOrWatermarkFiles(stamps []gotenberg.Stamp, files []string, kind string) error {
|
||||||
fileIndex := 0
|
fileIndex := 0
|
||||||
for i := range stamps {
|
for i := range stamps {
|
||||||
if stamps[i].Source != gotenberg.StampSourceImage && stamps[i].Source != gotenberg.StampSourcePDF {
|
if stamps[i].Source != gotenberg.StampSourceImage && stamps[i].Source != gotenberg.StampSourcePDF {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fileIndex >= len(stampFiles) {
|
if fileIndex >= len(files) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
errors.New("not enough stamp files for the image or pdf stamps"),
|
fmt.Errorf("not enough %s files for the image or pdf entries", kind),
|
||||||
api.NewSentinelHttpError(
|
api.NewSentinelHttpError(
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
"Invalid form data: a stamp file is required for image or pdf source",
|
fmt.Sprintf("Invalid form data: a %s file is required for image or pdf source", kind),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
stamps[i].Expression = stampFiles[fileIndex]
|
stamps[i].Expression = files[fileIndex]
|
||||||
fileIndex++
|
fileIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureWatermarkFile mirrors [EnsureStampFile] for a watermark. The
|
// WatermarkStub applies each watermark to a list of PDF files, in order.
|
||||||
// shape is identical: image or pdf sources must be accompanied by an
|
// Entries with no source are skipped, so an empty list does nothing.
|
||||||
// uploaded file, and the file path replaces watermark.Expression to
|
func WatermarkStub(ctx *api.Context, engine gotenberg.PdfEngine, watermarks []gotenberg.Stamp, inputPaths []string) error {
|
||||||
// prevent pdfcpu from reading an attacker-controlled path.
|
for _, watermark := range watermarks {
|
||||||
func EnsureWatermarkFile(watermark *gotenberg.Stamp, uploadedFile string) error {
|
if watermark.Source == "" {
|
||||||
if watermark.Source != gotenberg.StampSourceImage && watermark.Source != gotenberg.StampSourcePDF {
|
continue
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
if uploadedFile == "" {
|
|
||||||
return api.WrapError(
|
|
||||||
errors.New("no watermark file provided for image or pdf source"),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"Invalid form data: a watermark file is required for image or pdf source",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
watermark.Expression = uploadedFile
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatermarkStub applies a watermark to a list of PDF files. If the stamp has
|
for _, inputPath := range inputPaths {
|
||||||
// no source, it does nothing.
|
err := engine.Watermark(ctx, ctx.Log(), inputPath, watermark)
|
||||||
func WatermarkStub(ctx *api.Context, engine gotenberg.PdfEngine, stamp gotenberg.Stamp, inputPaths []string) error {
|
if err != nil {
|
||||||
if stamp.Source == "" {
|
return fmt.Errorf("watermark '%s': %w", inputPath, err)
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StampStub applies a stamp to a list of PDF files. If the stamp has
|
// StampStub applies each stamp to a list of PDF files, in order. Entries with
|
||||||
// no source, it does nothing.
|
// no source are skipped, so an empty list does nothing.
|
||||||
func StampStub(ctx *api.Context, engine gotenberg.PdfEngine, stamp gotenberg.Stamp, inputPaths []string) error {
|
func StampStub(ctx *api.Context, engine gotenberg.PdfEngine, stamps []gotenberg.Stamp, inputPaths []string) error {
|
||||||
if stamp.Source == "" {
|
for _, stamp := range stamps {
|
||||||
return nil
|
if stamp.Source == "" {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for _, inputPath := range inputPaths {
|
for _, inputPath := range inputPaths {
|
||||||
err := engine.Stamp(ctx, ctx.Log(), inputPath, stamp)
|
err := engine.Stamp(ctx, ctx.Log(), inputPath, stamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stamp '%s': %w", inputPath, err)
|
return fmt.Errorf("stamp '%s': %w", inputPath, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1090,10 +993,16 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
bookmarks := FormDataPdfBookmarks(form, false)
|
bookmarks := FormDataPdfBookmarks(form, false)
|
||||||
encrypt := FormDataPdfEncrypt(form)
|
encrypt := FormDataPdfEncrypt(form)
|
||||||
embedPaths := FormDataPdfEmbeds(form)
|
embedPaths := FormDataPdfEmbeds(form)
|
||||||
watermark := FormDataPdfWatermark(form, false)
|
watermarks, wErr := FormDataPdfWatermarks(form)
|
||||||
watermarkFile := FormDataPdfWatermarkFile(form)
|
if wErr != nil {
|
||||||
stamp := FormDataPdfStamp(form, false)
|
return fmt.Errorf("form data watermarks: %w", wErr)
|
||||||
stampFile := FormDataPdfStampFile(form)
|
}
|
||||||
|
stamps, sErr := FormDataPdfStamps(form)
|
||||||
|
if sErr != nil {
|
||||||
|
return fmt.Errorf("form data stamps: %w", sErr)
|
||||||
|
}
|
||||||
|
var watermarkFiles, stampFiles []string
|
||||||
|
form.Watermarks(&watermarkFiles).Stamps(&stampFiles)
|
||||||
angle, rotatePages := FormDataPdfRotate(form, false)
|
angle, rotatePages := FormDataPdfRotate(form, false)
|
||||||
embedsMetadata := FormDataPdfEmbedsMetadata(form)
|
embedsMetadata := FormDataPdfEmbedsMetadata(form)
|
||||||
facturX, facturxXmlPath := FormDataPdfFacturX(form)
|
facturX, facturxXmlPath := FormDataPdfFacturX(form)
|
||||||
@@ -1113,13 +1022,13 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = EnsureWatermarkFile(&watermark, watermarkFile)
|
err = BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
}
|
}
|
||||||
err = EnsureStampFile(&stamp, stampFile)
|
err = BindStampFiles(stamps, stampFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate stamp: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ValidatePdfFormatsCompat(pdfFormats, encrypt.UserPassword, embedPaths)
|
err = ValidatePdfFormatsCompat(pdfFormats, encrypt.UserPassword, embedPaths)
|
||||||
@@ -1145,12 +1054,12 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
|
|
||||||
outputPaths := []string{outputPath}
|
outputPaths := []string{outputPath}
|
||||||
|
|
||||||
err = WatermarkStub(ctx, engine, watermark, outputPaths)
|
err = WatermarkStub(ctx, engine, watermarks, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("watermark PDFs: %w", err)
|
return fmt.Errorf("watermark PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = StampStub(ctx, engine, stamp, outputPaths)
|
err = StampStub(ctx, engine, stamps, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stamp PDFs: %w", err)
|
return fmt.Errorf("stamp PDFs: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1285,10 +1194,16 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
metadata := FormDataPdfMetadata(form, false)
|
metadata := FormDataPdfMetadata(form, false)
|
||||||
encrypt := FormDataPdfEncrypt(form)
|
encrypt := FormDataPdfEncrypt(form)
|
||||||
embedPaths := FormDataPdfEmbeds(form)
|
embedPaths := FormDataPdfEmbeds(form)
|
||||||
watermark := FormDataPdfWatermark(form, false)
|
watermarks, wErr := FormDataPdfWatermarks(form)
|
||||||
watermarkFile := FormDataPdfWatermarkFile(form)
|
if wErr != nil {
|
||||||
stamp := FormDataPdfStamp(form, false)
|
return fmt.Errorf("form data watermarks: %w", wErr)
|
||||||
stampFile := FormDataPdfStampFile(form)
|
}
|
||||||
|
stamps, sErr := FormDataPdfStamps(form)
|
||||||
|
if sErr != nil {
|
||||||
|
return fmt.Errorf("form data stamps: %w", sErr)
|
||||||
|
}
|
||||||
|
var watermarkFiles, stampFiles []string
|
||||||
|
form.Watermarks(&watermarkFiles).Stamps(&stampFiles)
|
||||||
angle, rotatePages := FormDataPdfRotate(form, false)
|
angle, rotatePages := FormDataPdfRotate(form, false)
|
||||||
embedsMetadata := FormDataPdfEmbedsMetadata(form)
|
embedsMetadata := FormDataPdfEmbedsMetadata(form)
|
||||||
facturX, facturxXmlPath := FormDataPdfFacturX(form)
|
facturX, facturxXmlPath := FormDataPdfFacturX(form)
|
||||||
@@ -1304,13 +1219,13 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = EnsureWatermarkFile(&watermark, watermarkFile)
|
err = BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
}
|
}
|
||||||
err = EnsureStampFile(&stamp, stampFile)
|
err = BindStampFiles(stamps, stampFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate stamp: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ValidatePdfFormatsCompat(pdfFormats, encrypt.UserPassword, embedPaths)
|
err = ValidatePdfFormatsCompat(pdfFormats, encrypt.UserPassword, embedPaths)
|
||||||
@@ -1333,12 +1248,12 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("split PDFs: %w", err)
|
return fmt.Errorf("split PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = WatermarkStub(ctx, engine, watermark, outputPaths)
|
err = WatermarkStub(ctx, engine, watermarks, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("watermark PDFs: %w", err)
|
return fmt.Errorf("watermark PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = StampStub(ctx, engine, stamp, outputPaths)
|
err = StampStub(ctx, engine, stamps, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stamp PDFs: %w", err)
|
return fmt.Errorf("stamp PDFs: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1816,23 +1731,37 @@ func watermarkRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
|
|
||||||
form := ctx.FormData()
|
form := ctx.FormData()
|
||||||
stamp := FormDataPdfWatermark(form, true)
|
watermarks, err := FormDataPdfWatermarks(form)
|
||||||
watermarkFile := FormDataPdfWatermarkFile(form)
|
if err != nil {
|
||||||
|
return fmt.Errorf("form data watermarks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var inputPaths []string
|
var inputPaths []string
|
||||||
err := form.
|
var watermarkFiles []string
|
||||||
|
err = form.
|
||||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||||
|
Watermarks(&watermarkFiles).
|
||||||
Validate()
|
Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = EnsureWatermarkFile(&stamp, watermarkFile)
|
if len(watermarks) == 0 {
|
||||||
if err != nil {
|
return api.WrapError(
|
||||||
return fmt.Errorf("validate watermark: %w", err)
|
errors.New("no watermark provided"),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"Invalid form data: form field 'watermarkSource' is required",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = WatermarkStub(ctx, engine, stamp, inputPaths)
|
err = BindWatermarkFiles(watermarks, watermarkFiles)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bind watermark files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = WatermarkStub(ctx, engine, watermarks, inputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("watermark PDFs: %w", err)
|
return fmt.Errorf("watermark PDFs: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1893,11 +1822,9 @@ func stampRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("bind stamp files: %w", err)
|
return fmt.Errorf("bind stamp files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, stamp := range stamps {
|
err = StampStub(ctx, engine, stamps, inputPaths)
|
||||||
err = StampStub(ctx, engine, stamp, inputPaths)
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("stamp PDFs: %w", err)
|
||||||
return fmt.Errorf("stamp PDFs: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.AddOutputPaths(inputPaths...)
|
err = ctx.AddOutputPaths(inputPaths...)
|
||||||
|
|||||||
@@ -156,3 +156,26 @@ func TestBindStampFiles(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormDataPdfWatermarks(t *testing.T) {
|
||||||
|
ctx := &api.ContextMock{Context: &api.Context{}}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"watermarkSource": {"text", "image"},
|
||||||
|
"watermarkExpression": {"DRAFT"},
|
||||||
|
"watermarkOptions": {`{"opacity":"0.5"}`, ""},
|
||||||
|
})
|
||||||
|
form := ctx.FormData()
|
||||||
|
|
||||||
|
got, err := FormDataPdfWatermarks(form)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []gotenberg.Stamp{
|
||||||
|
{Source: "text", Expression: "DRAFT", Options: map[string]string{"opacity": "0.5"}},
|
||||||
|
{Source: "image"},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("watermarks = %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,19 +53,36 @@ Feature: /forms/pdfengines/stamp
|
|||||||
|
|
||||||
# Repeating the stamp fields applies several stamps in one request, in order.
|
# Repeating the stamp fields applies several stamps in one request, in order.
|
||||||
# Image and pdf stamps consume the uploaded stamp files in order; text stamps
|
# Image and pdf stamps consume the uploaded stamp files in order; text stamps
|
||||||
# take none. See https://github.com/gotenberg/gotenberg/pull/1601.
|
# take none. Both text stamps must land, so their content is asserted (the
|
||||||
|
# options keep them unrotated and apart so pdftotext reads them cleanly).
|
||||||
|
# See https://github.com/gotenberg/gotenberg/pull/1601.
|
||||||
Scenario: POST /forms/pdfengines/stamp (Multiple Stamps - pdfcpu)
|
Scenario: POST /forms/pdfengines/stamp (Multiple Stamps - pdfcpu)
|
||||||
Given I have a Gotenberg container with the following environment variable(s):
|
Given I have a Gotenberg container with the following environment variable(s):
|
||||||
| PDFENGINES_STAMP_ENGINES | pdfcpu |
|
| 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):
|
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_1.pdf | file |
|
||||||
| stampSource | text | field |
|
| stampSource | text | field |
|
||||||
| stampExpression | CONFIDENTIAL | field |
|
| stampExpression | STAMPONE | field |
|
||||||
| stampSource | image | field |
|
| stampOptions | {"rotation":"0","position":"tl","scale":"0.2 abs"} | field |
|
||||||
| stamp | testdata/watermark.png | file |
|
| stampSource | text | field |
|
||||||
|
| stampExpression | STAMPTWO | field |
|
||||||
|
| stampOptions | {"rotation":"0","position":"br","scale":"0.2 abs"} | field |
|
||||||
|
| stampSource | image | field |
|
||||||
|
| stamp | testdata/watermark.png | file |
|
||||||
|
| Gotenberg-Output-Filename | foo | header |
|
||||||
Then the response status code should be 200
|
Then the response status code should be 200
|
||||||
Then the response header "Content-Type" should be "application/pdf"
|
Then the response header "Content-Type" should be "application/pdf"
|
||||||
Then there should be 1 PDF(s) in the response
|
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 "foo.pdf" PDF should have the following content at page 1:
|
||||||
|
"""
|
||||||
|
STAMPONE
|
||||||
|
"""
|
||||||
|
Then the "foo.pdf" PDF should have the following content at page 1:
|
||||||
|
"""
|
||||||
|
STAMPTWO
|
||||||
|
"""
|
||||||
|
|
||||||
Scenario: POST /forms/pdfengines/stamp (PDF - pdfcpu)
|
Scenario: POST /forms/pdfengines/stamp (PDF - pdfcpu)
|
||||||
Given I have a Gotenberg container with the following environment variable(s):
|
Given I have a Gotenberg container with the following environment variable(s):
|
||||||
|
|||||||
@@ -51,6 +51,39 @@ Feature: /forms/pdfengines/watermark
|
|||||||
Then the response header "Content-Type" should be "application/pdf"
|
Then the response header "Content-Type" should be "application/pdf"
|
||||||
Then there should be 1 PDF(s) in the response
|
Then there should be 1 PDF(s) in the response
|
||||||
|
|
||||||
|
# Repeating the watermark fields applies several watermarks in one request, in
|
||||||
|
# order. Image and pdf watermarks consume the uploaded watermark files in
|
||||||
|
# order; text watermarks take none. Both text watermarks must land, so their
|
||||||
|
# content is asserted (the options keep them unrotated and apart so pdftotext
|
||||||
|
# reads them cleanly).
|
||||||
|
Scenario: POST /forms/pdfengines/watermark (Multiple Watermarks - 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 | MARKONE | field |
|
||||||
|
| watermarkOptions | {"rotation":"0","position":"tl","scale":"0.2 abs"} | field |
|
||||||
|
| watermarkSource | text | field |
|
||||||
|
| watermarkExpression | MARKTWO | field |
|
||||||
|
| watermarkOptions | {"rotation":"0","position":"br","scale":"0.2 abs"} | field |
|
||||||
|
| watermarkSource | image | field |
|
||||||
|
| watermark | testdata/watermark.png | file |
|
||||||
|
| Gotenberg-Output-Filename | foo | header |
|
||||||
|
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 there should be the following file(s) in the response:
|
||||||
|
| foo.pdf |
|
||||||
|
Then the "foo.pdf" PDF should have the following content at page 1:
|
||||||
|
"""
|
||||||
|
MARKONE
|
||||||
|
"""
|
||||||
|
Then the "foo.pdf" PDF should have the following content at page 1:
|
||||||
|
"""
|
||||||
|
MARKTWO
|
||||||
|
"""
|
||||||
|
|
||||||
Scenario: POST /forms/pdfengines/watermark (PDF - pdfcpu)
|
Scenario: POST /forms/pdfengines/watermark (PDF - pdfcpu)
|
||||||
Given I have a Gotenberg container with the following environment variable(s):
|
Given I have a Gotenberg container with the following environment variable(s):
|
||||||
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
| PDFENGINES_WATERMARK_ENGINES | pdfcpu |
|
||||||
|
|||||||
Reference in New Issue
Block a user