diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index e5f01f07..3ca9acb3 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -409,9 +409,11 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { var inputPaths []string var flatten bool + var autoIndexBookmarks bool err := form. MandatoryPaths([]string{".pdf"}, &inputPaths). Bool("flatten", &flatten, false). + Bool("autoIndexBookmarks", &autoIndexBookmarks, false). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) @@ -436,19 +438,36 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { var finalBookmarks []gotenberg.Bookmark if b, ok := bookmarks.([]gotenberg.Bookmark); ok { finalBookmarks = b - } else if b, ok := bookmarks.(map[string][]gotenberg.Bookmark); ok { - offset := 0 - for _, inputPath := range inputPaths { - filename := filepath.Base(inputPath) - if fileBookmarks, ok := b[filename]; ok { - finalBookmarks = append(finalBookmarks, shiftBookmarks(fileBookmarks, offset)...) - } + } else { + bMap, _ := bookmarks.(map[string][]gotenberg.Bookmark) + if bMap != nil || autoIndexBookmarks { + offset := 0 + for _, inputPath := range inputPaths { + filename := filepath.Base(inputPath) - pageCount, err := engine.PageCount(ctx, ctx.Log(), inputPath) - if err != nil { - return fmt.Errorf("get page count of '%s': %w", filename, err) + var fileBookmarks []gotenberg.Bookmark + if bMap != nil { + fileBookmarks = bMap[filename] + } + + if len(fileBookmarks) == 0 && autoIndexBookmarks { + fb, err := engine.ReadBookmarks(ctx, ctx.Log(), inputPath) + if err != nil { + return fmt.Errorf("read bookmarks of '%s': %w", filename, err) + } + fileBookmarks = fb + } + + if len(fileBookmarks) > 0 { + finalBookmarks = append(finalBookmarks, shiftBookmarks(fileBookmarks, offset)...) + } + + pageCount, err := engine.PageCount(ctx, ctx.Log(), inputPath) + if err != nil { + return fmt.Errorf("get page count of '%s': %w", filename, err) + } + offset += pageCount } - offset += pageCount } } diff --git a/test/integration/features/pdfengines_merge.feature b/test/integration/features/pdfengines_merge.feature index 1c1676c7..1a02f85c 100644 --- a/test/integration/features/pdfengines_merge.feature +++ b/test/integration/features/pdfengines_merge.feature @@ -140,6 +140,16 @@ Feature: /forms/pdfengines/merge """ Invalid form data: form field 'bookmarks' is invalid (got 'foo', resulting to unmarshal bookmarks: invalid character 'o' in literal false (expecting 'a')) """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | autoIndexBookmarks | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'autoIndexBookmarks' is invalid (got 'foo', resulting to strconv.ParseBool: parsing "foo": invalid syntax) + """ @convert Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1) @@ -281,6 +291,67 @@ Feature: /forms/pdfengines/merge } """ + @bookmarks + Scenario: POST /forms/pdfengines/merge (Auto-index Bookmarks) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1_with_bookmarks.pdf | file | + | files | testdata/page_2_with_bookmarks.pdf | file | + | autoIndexBookmarks | true | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": [ + { + "title": "Page 1", + "page": 1 + }, + { + "title": "Page 2", + "page": 2 + } + ] + } + """ + + @bookmarks + Scenario: POST /forms/pdfengines/merge (Auto-index Bookmarks + Bookmarks Map) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2_with_bookmarks.pdf | file | + | bookmarks | {"page_1.pdf":[{"title":"Page 1 Index","page":1}]} | field | + | autoIndexBookmarks | true | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": [ + { + "title": "Page 1 Index", + "page": 1 + }, + { + "title": "Page 2", + "page": 2 + } + ] + } + """ + @flatten Scenario: POST /forms/pdfengines/merge (Flatten) Given I have a default Gotenberg container diff --git a/test/integration/testdata/page_1_with_bookmarks.pdf b/test/integration/testdata/page_1_with_bookmarks.pdf new file mode 100644 index 00000000..ba40d0f8 Binary files /dev/null and b/test/integration/testdata/page_1_with_bookmarks.pdf differ diff --git a/test/integration/testdata/page_2_with_bookmarks.pdf b/test/integration/testdata/page_2_with_bookmarks.pdf new file mode 100644 index 00000000..4e213d7f Binary files /dev/null and b/test/integration/testdata/page_2_with_bookmarks.pdf differ