refactor(pdfengines): embeds => attachments

This commit is contained in:
Julien Neuhart
2026-03-08 15:49:09 +01:00
parent 7af3cd1ff5
commit cf9fd7eedc
92 changed files with 5763 additions and 3082 deletions

View File

@@ -84,7 +84,7 @@ func FormDataPdfSplitMode(form *api.FormData, mandatory bool) gotenberg.SplitMod
}
// FormDataPdfFormats creates [gotenberg.PdfFormats] from the form data.
// Fallback to the default value if the considered key is not present.
// Fallback to default value if the considered key is not present.
func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats {
var (
pdfa string
@@ -128,44 +128,6 @@ func FormDataPdfMetadata(form *api.FormData, mandatory bool) map[string]any {
return metadata
}
// FormDataPdfBookmarks creates bookmarks from the form data.
func FormDataPdfBookmarks(form *api.FormData, mandatory bool) any {
var bookmarks any
bookmarksFunc := func(value string) error {
if len(value) > 0 {
var list []gotenberg.Bookmark
err := json.Unmarshal([]byte(value), &list)
if err == nil {
bookmarks = list
return nil
}
var m map[string][]gotenberg.Bookmark
err = json.Unmarshal([]byte(value), &m)
if err == nil {
bookmarks = m
return nil
}
return fmt.Errorf("unmarshal bookmarks: %w", err)
}
return nil
}
if mandatory {
form.MandatoryCustom("bookmarks", func(value string) error {
return bookmarksFunc(value)
})
} else {
form.Custom("bookmarks", func(value string) error {
return bookmarksFunc(value)
})
}
return bookmarks
}
// MergeStub merges given PDFs. If only one input PDF, it does nothing and
// returns the corresponding input path.
func MergeStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string) (string, error) {
@@ -292,49 +254,12 @@ func WriteMetadataStub(ctx *api.Context, engine gotenberg.PdfEngine, metadata ma
return nil
}
// WriteBookmarksStub writes the bookmarks into PDF files. If no bookmarks, it
// does nothing.
func WriteBookmarksStub(ctx *api.Context, engine gotenberg.PdfEngine, bookmarks any, inputPaths []string) error {
if bookmarks == nil {
return nil
}
switch b := bookmarks.(type) {
case []gotenberg.Bookmark:
if len(b) == 0 {
return nil
}
for _, inputPath := range inputPaths {
err := engine.WriteBookmarks(ctx, ctx.Log(), inputPath, b)
if err != nil {
return fmt.Errorf("write bookmarks into '%s': %w", inputPath, err)
}
}
case map[string][]gotenberg.Bookmark:
for _, inputPath := range inputPaths {
filename := filepath.Base(inputPath)
if specificBookmarks, ok := b[filename]; ok {
err := engine.WriteBookmarks(ctx, ctx.Log(), inputPath, specificBookmarks)
if err != nil {
return fmt.Errorf("write bookmarks into '%s': %w", inputPath, err)
}
}
}
default:
// Should not happen.
return fmt.Errorf("bookmarks type '%T' not supported", bookmarks)
}
return nil
}
// FormDataPdfEmbeds extracts embedded file paths from form data.
// Only files uploaded with the "embeds" field name are included.
func FormDataPdfEmbeds(form *api.FormData) []string {
var embedPaths []string
form.Embeds(&embedPaths)
return embedPaths
// FormDataPdfAttachments extracts attached file paths from form data.
// Only files uploaded with the "attachments" field name are included.
func FormDataPdfAttachments(form *api.FormData) []string {
var attachmentsPaths []string
form.Attachments(&attachmentsPaths)
return attachmentsPaths
}
// FormDataPdfEncrypt extracts encryption parameters from form data.
@@ -360,16 +285,16 @@ func EncryptPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, userPassword,
return nil
}
// EmbedFilesStub embeds files into PDF files.
func EmbedFilesStub(ctx *api.Context, engine gotenberg.PdfEngine, embedPaths []string, inputPaths []string) error {
if len(embedPaths) == 0 {
// AddAttachmentsStub adds attachments into PDF files.
func AddAttachmentsStub(ctx *api.Context, engine gotenberg.PdfEngine, attachmentsPaths []string, inputPaths []string) error {
if len(attachmentsPaths) == 0 {
return nil
}
for _, inputPath := range inputPaths {
err := engine.EmbedFiles(ctx, ctx.Log(), embedPaths, inputPath)
err := engine.AddAttachments(ctx, ctx.Log(), attachmentsPaths, inputPath)
if err != nil {
return fmt.Errorf("embed files into PDF '%s': %w", inputPath, err)
return fmt.Errorf("add attachments into PDF '%s': %w", inputPath, err)
}
}
@@ -388,9 +313,8 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
form := ctx.FormData()
pdfFormats := FormDataPdfFormats(form)
metadata := FormDataPdfMetadata(form, false)
bookmarks := FormDataPdfBookmarks(form, false)
userPassword, ownerPassword := FormDataPdfEncrypt(form)
embedPaths := FormDataPdfEmbeds(form)
attachmentsPaths := FormDataPdfAttachments(form)
var inputPaths []string
var flatten bool
@@ -402,13 +326,6 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
return fmt.Errorf("validate form data: %w", err)
}
if b, ok := bookmarks.(map[string][]gotenberg.Bookmark); ok {
err = WriteBookmarksStub(ctx, engine, b, inputPaths)
if err != nil {
return fmt.Errorf("write bookmarks: %w", err)
}
}
outputPath := ctx.GeneratePath(".pdf")
err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath)
if err != nil {
@@ -420,16 +337,9 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
return fmt.Errorf("convert PDF: %w", err)
}
err = EmbedFilesStub(ctx, engine, embedPaths, outputPaths)
err = AddAttachmentsStub(ctx, engine, attachmentsPaths, outputPaths)
if err != nil {
return fmt.Errorf("embed files into PDFs: %w", err)
}
if b, ok := bookmarks.([]gotenberg.Bookmark); ok {
err = WriteBookmarksStub(ctx, engine, b, outputPaths)
if err != nil {
return fmt.Errorf("write bookmarks: %w", err)
}
return fmt.Errorf("add attachments into PDFs: %w", err)
}
err = WriteMetadataStub(ctx, engine, metadata, outputPaths)
@@ -473,7 +383,7 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
pdfFormats := FormDataPdfFormats(form)
metadata := FormDataPdfMetadata(form, false)
userPassword, ownerPassword := FormDataPdfEncrypt(form)
embedPaths := FormDataPdfEmbeds(form)
attachmentsPaths := FormDataPdfAttachments(form)
var inputPaths []string
var flatten bool
@@ -495,9 +405,9 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
return fmt.Errorf("convert PDFs: %w", err)
}
err = EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths)
err = AddAttachmentsStub(ctx, engine, attachmentsPaths, convertOutputPaths)
if err != nil {
return fmt.Errorf("embed files into PDFs: %w", err)
return fmt.Errorf("add attachments into PDFs: %w", err)
}
err = WriteMetadataStub(ctx, engine, metadata, convertOutputPaths)
@@ -709,83 +619,6 @@ func writeMetadataRoute(engine gotenberg.PdfEngine) api.Route {
}
}
// readBookmarksRoute returns an [api.Route] which returns the bookmarks of PDFs.
func readBookmarksRoute(engine gotenberg.PdfEngine) api.Route {
return api.Route{
Method: http.MethodPost,
Path: "/forms/pdfengines/bookmarks/read",
IsMultipart: true,
Handler: func(c echo.Context) error {
ctx := c.Get("context").(*api.Context)
var inputPaths []string
err := ctx.FormData().
MandatoryPaths([]string{".pdf"}, &inputPaths).
Validate()
if err != nil {
return fmt.Errorf("validate form data: %w", err)
}
res := make(map[string][]gotenberg.Bookmark, len(inputPaths))
for _, inputPath := range inputPaths {
bookmarks, err := engine.ReadBookmarks(ctx, ctx.Log(), inputPath)
if err != nil {
return fmt.Errorf("read bookmarks: %w", err)
}
res[filepath.Base(inputPath)] = bookmarks
}
err = c.JSON(http.StatusOK, res)
if err != nil {
if strings.Contains(err.Error(), "request method or response status code does not allow body") {
// High probability that the user is using the webhook
// feature. It does not make sense for this route.
return api.ErrNoOutputFile
}
return fmt.Errorf("return JSON response: %w", err)
}
return api.ErrNoOutputFile
},
}
}
// writeBookmarksRoute returns an [api.Route] which can write bookmarks into PDFs.
func writeBookmarksRoute(engine gotenberg.PdfEngine) api.Route {
return api.Route{
Method: http.MethodPost,
Path: "/forms/pdfengines/bookmarks/write",
IsMultipart: true,
Handler: func(c echo.Context) error {
ctx := c.Get("context").(*api.Context)
form := ctx.FormData()
bookmarks := FormDataPdfBookmarks(form, true)
var inputPaths []string
err := form.
MandatoryPaths([]string{".pdf"}, &inputPaths).
Validate()
if err != nil {
return fmt.Errorf("validate form data: %w", err)
}
err = WriteBookmarksStub(ctx, engine, bookmarks, inputPaths)
if err != nil {
return fmt.Errorf("write bookmarks: %w", err)
}
err = ctx.AddOutputPaths(inputPaths...)
if err != nil {
return fmt.Errorf("add output paths: %w", err)
}
return nil
},
}
}
// encryptRoute returns an [api.Route] which can add password protection to PDFs.
func encryptRoute(engine gotenberg.PdfEngine) api.Route {
return api.Route{
@@ -824,18 +657,17 @@ func encryptRoute(engine gotenberg.PdfEngine) api.Route {
}
}
// embedRoute returns an [api.Route] which can add embedded files to PDFs.
// TODO: attachments instead?
func embedRoute(engine gotenberg.PdfEngine) api.Route {
// attachmentsRoute returns an [api.Route] which can add attached files to PDFs.
func attachmentsRoute(engine gotenberg.PdfEngine) api.Route {
return api.Route{
Method: http.MethodPost,
Path: "/forms/pdfengines/embed",
Path: "/forms/pdfengines/attachments/add",
IsMultipart: true,
Handler: func(c echo.Context) error {
ctx := c.Get("context").(*api.Context)
form := ctx.FormData()
embedPaths := FormDataPdfEmbeds(form)
attachmentsPaths := FormDataPdfAttachments(form)
var inputPaths []string
err := form.
@@ -844,9 +676,10 @@ func embedRoute(engine gotenberg.PdfEngine) api.Route {
if err != nil {
return fmt.Errorf("validate form data: %w", err)
}
err = EmbedFilesStub(ctx, engine, embedPaths, inputPaths)
err = AddAttachmentsStub(ctx, engine, attachmentsPaths, inputPaths)
if err != nil {
return fmt.Errorf("embed files into PDFs: %w", err)
return fmt.Errorf("add attachments into PDFs: %w", err)
}
err = ctx.AddOutputPaths(inputPaths...)