mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-13 02:42:14 +01:00
initial changes
This commit is contained in:
2
Makefile
2
Makefile
@@ -74,6 +74,7 @@ LOG_FIELDS_PREFIX=
|
||||
PDFENGINES_ENGINES=
|
||||
PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk
|
||||
PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk
|
||||
PDFENGINES_FLATTEN_ENGINES=qpdf
|
||||
PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine
|
||||
PDFENGINES_READ_METADATA_ENGINES=exiftool
|
||||
PDFENGINES_WRITE_METADATA_ENGINES=exiftool
|
||||
@@ -143,6 +144,7 @@ run: ## Start a Gotenberg container
|
||||
--pdfengines-engines=$(PDFENGINES_ENGINES) \
|
||||
--pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \
|
||||
--pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \
|
||||
--pdfengines-convert-engines=$(PDFENGINES_FLATTEN_ENGINES) \
|
||||
--pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \
|
||||
--pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \
|
||||
--pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \
|
||||
|
||||
@@ -38,6 +38,7 @@ func (mod *ValidatorMock) Validate() error {
|
||||
type PdfEngineMock struct {
|
||||
MergeMock func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error
|
||||
SplitMock func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error)
|
||||
FlattenMock func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error
|
||||
ConvertMock func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error
|
||||
ReadMetadataMock func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error)
|
||||
WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error
|
||||
@@ -51,6 +52,10 @@ func (engine *PdfEngineMock) Split(ctx context.Context, logger *zap.Logger, mode
|
||||
return engine.SplitMock(ctx, logger, mode, inputPath, outputDirPath)
|
||||
}
|
||||
|
||||
func (engine *PdfEngineMock) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
return engine.FlattenMock(ctx, logger, inputPath, outputPath)
|
||||
}
|
||||
|
||||
func (engine *PdfEngineMock) Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
|
||||
return engine.ConvertMock(ctx, logger, formats, inputPath, outputPath)
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ type PdfEngine interface {
|
||||
// Split splits a given PDF file.
|
||||
Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error)
|
||||
|
||||
// Flatten PDF refers to merging the annotations (such as markup, widgets, 3D models, etc.) into a static area that is part of the PDF document
|
||||
// By flattening and merging existing annotation appearances with page content, the original annotations are deleted from the PDF pages.
|
||||
Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error
|
||||
|
||||
// Convert transforms a given PDF to the specified formats defined in
|
||||
// PdfFormats. If no format, it does nothing.
|
||||
Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error
|
||||
|
||||
@@ -63,6 +63,11 @@ func (engine *ExifTool) Split(ctx context.Context, logger *zap.Logger, mode gote
|
||||
return nil, fmt.Errorf("split PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Flatten is not available in this implementation.
|
||||
func (engine *ExifTool) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("flatten PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Convert is not available in this implementation.
|
||||
func (engine *ExifTool) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
|
||||
@@ -56,6 +56,11 @@ func (engine *LibreOfficePdfEngine) Split(ctx context.Context, logger *zap.Logge
|
||||
return nil, fmt.Errorf("split PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Flatten is not available in this implementation.
|
||||
func (engine *LibreOfficePdfEngine) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("Flatten PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Convert converts the given PDF to a specific PDF format. Currently, only the
|
||||
// PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/UA formats are available. If another
|
||||
// PDF format is requested, it returns a [gotenberg.ErrPdfFormatNotSupported]
|
||||
|
||||
@@ -107,6 +107,11 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenb
|
||||
return outputPaths, nil
|
||||
}
|
||||
|
||||
// Flatten is not available in this implementation.
|
||||
func (engine *PdfCpu) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("flatten PDF with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Convert is not available in this implementation.
|
||||
func (engine *PdfCpu) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type multiPdfEngines struct {
|
||||
mergeEngines []gotenberg.PdfEngine
|
||||
splitEngines []gotenberg.PdfEngine
|
||||
flattenEngines []gotenberg.PdfEngine
|
||||
convertEngines []gotenberg.PdfEngine
|
||||
readMetadataEngines []gotenberg.PdfEngine
|
||||
writeMetadataEngines []gotenberg.PdfEngine
|
||||
@@ -22,6 +23,7 @@ type multiPdfEngines struct {
|
||||
func newMultiPdfEngines(
|
||||
mergeEngines,
|
||||
splitEngines,
|
||||
flattenEngines,
|
||||
convertEngines,
|
||||
readMetadataEngines,
|
||||
writeMetadataEngines []gotenberg.PdfEngine,
|
||||
@@ -29,6 +31,7 @@ func newMultiPdfEngines(
|
||||
return &multiPdfEngines{
|
||||
mergeEngines: mergeEngines,
|
||||
splitEngines: splitEngines,
|
||||
flattenEngines: flattenEngines,
|
||||
convertEngines: convertEngines,
|
||||
readMetadataEngines: readMetadataEngines,
|
||||
writeMetadataEngines: writeMetadataEngines,
|
||||
@@ -98,6 +101,29 @@ func (multi *multiPdfEngines) Split(ctx context.Context, logger *zap.Logger, mod
|
||||
return nil, fmt.Errorf("split PDF with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
func (multi *multiPdfEngines) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
var err error
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
for _, engine := range multi.flattenEngines {
|
||||
go func(engine gotenberg.PdfEngine) {
|
||||
errChan <- engine.Flatten(ctx, logger, inputPath, outputPath)
|
||||
}(engine)
|
||||
|
||||
select {
|
||||
case mergeErr := <-errChan:
|
||||
errored := multierr.AppendInto(&err, mergeErr)
|
||||
if !errored {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("flatten PDF with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Convert converts the given PDF to a specific PDF format. thanks to its
|
||||
// children. If the context is done, it stops and returns an error.
|
||||
func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
|
||||
@@ -29,6 +29,7 @@ func init() {
|
||||
type PdfEngines struct {
|
||||
mergeNames []string
|
||||
splitNames []string
|
||||
flattenNames []string
|
||||
convertNames []string
|
||||
readMetadataNames []string
|
||||
writeMetadataNames []string
|
||||
@@ -44,6 +45,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
|
||||
fs := flag.NewFlagSet("pdfengines", flag.ExitOnError)
|
||||
fs.StringSlice("pdfengines-merge-engines", []string{"qpdf", "pdfcpu", "pdftk"}, "Set the PDF engines and their order for the merge feature - empty means all")
|
||||
fs.StringSlice("pdfengines-split-engines", []string{"pdfcpu", "qpdf", "pdftk"}, "Set the PDF engines and their order for the split feature - empty means all")
|
||||
fs.StringSlice("pdfengines-flatten-engines", []string{"qpdf"}, "Set the PDF engines and their order for the flatten feature - empty means all")
|
||||
fs.StringSlice("pdfengines-convert-engines", []string{"libreoffice-pdfengine"}, "Set the PDF engines and their order for the convert feature - empty means all")
|
||||
fs.StringSlice("pdfengines-read-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the read metadata feature - empty means all")
|
||||
fs.StringSlice("pdfengines-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature - empty means all")
|
||||
@@ -67,6 +69,7 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||
flags := ctx.ParsedFlags()
|
||||
mergeNames := flags.MustStringSlice("pdfengines-merge-engines")
|
||||
splitNames := flags.MustStringSlice("pdfengines-split-engines")
|
||||
flattenNames := flags.MustStringSlice("pdfengines-flatten-engines")
|
||||
convertNames := flags.MustStringSlice("pdfengines-convert-engines")
|
||||
readMetadataNames := flags.MustStringSlice("pdfengines-read-metadata-engines")
|
||||
writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines")
|
||||
@@ -106,6 +109,10 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||
mod.splitNames = splitNames
|
||||
}
|
||||
|
||||
if len(flattenNames) > 0 {
|
||||
mod.flattenNames = flattenNames
|
||||
}
|
||||
|
||||
mod.convertNames = defaultNames
|
||||
if len(convertNames) > 0 {
|
||||
mod.convertNames = convertNames
|
||||
@@ -212,6 +219,7 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) {
|
||||
return newMultiPdfEngines(
|
||||
engines(mod.mergeNames),
|
||||
engines(mod.splitNames),
|
||||
engines(mod.flattenNames),
|
||||
engines(mod.convertNames),
|
||||
engines(mod.readMetadataNames),
|
||||
engines(mod.writeMetadataNames),
|
||||
@@ -234,6 +242,7 @@ func (mod *PdfEngines) Routes() ([]api.Route, error) {
|
||||
return []api.Route{
|
||||
mergeRoute(engine),
|
||||
splitRoute(engine),
|
||||
flattenRoute(engine),
|
||||
convertRoute(engine),
|
||||
readMetadataRoute(engine),
|
||||
writeMetadataRoute(engine),
|
||||
|
||||
@@ -27,6 +27,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
||||
ctx *gotenberg.Context
|
||||
expectedMergePdfEngines []string
|
||||
expectedSplitPdfEngines []string
|
||||
expectedFlattenPdfEngines []string
|
||||
expectedConvertPdfEngines []string
|
||||
expectedReadMetadataPdfEngines []string
|
||||
expectedWriteMetadataPdfEngines []string
|
||||
@@ -68,6 +69,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
||||
}(),
|
||||
expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"},
|
||||
expectedSplitPdfEngines: []string{"pdfcpu", "qpdf", "pdftk"},
|
||||
expectedFlattenPdfEngines: []string{"qpdf"},
|
||||
expectedConvertPdfEngines: []string{"libreoffice-pdfengine"},
|
||||
expectedReadMetadataPdfEngines: []string{"exiftool"},
|
||||
expectedWriteMetadataPdfEngines: []string{"exiftool"},
|
||||
@@ -109,7 +111,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
||||
}
|
||||
|
||||
fs := new(PdfEngines).Descriptor().FlagSet
|
||||
err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"})
|
||||
err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-flatten-engines=c", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
@@ -128,6 +130,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
||||
|
||||
expectedMergePdfEngines: []string{"b"},
|
||||
expectedSplitPdfEngines: []string{"a"},
|
||||
expectedFlattenPdfEngines: []string{"c"},
|
||||
expectedConvertPdfEngines: []string{"b"},
|
||||
expectedReadMetadataPdfEngines: []string{"a"},
|
||||
expectedWriteMetadataPdfEngines: []string{"a"},
|
||||
@@ -185,6 +188,10 @@ func TestPdfEngines_Provision(t *testing.T) {
|
||||
t.Fatalf("expected %d merge names but got %d", len(tc.expectedMergePdfEngines), len(mod.mergeNames))
|
||||
}
|
||||
|
||||
if len(tc.expectedFlattenPdfEngines) != len(mod.flattenNames) {
|
||||
t.Fatalf("expected %d flatten names but got %d", len(tc.expectedFlattenPdfEngines), len(mod.flattenNames))
|
||||
}
|
||||
|
||||
if len(tc.expectedConvertPdfEngines) != len(mod.convertNames) {
|
||||
t.Fatalf("expected %d convert names but got %d", len(tc.expectedConvertPdfEngines), len(mod.convertNames))
|
||||
}
|
||||
@@ -382,7 +389,7 @@ func TestPdfEngines_Routes(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
scenario: "routes not disabled",
|
||||
expectRoutes: 5,
|
||||
expectRoutes: 6,
|
||||
disableRoutes: false,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -202,6 +202,20 @@ func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.S
|
||||
return outputPaths, nil
|
||||
}
|
||||
|
||||
func FlattenStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string) ([]string, error) {
|
||||
outputPaths := make([]string, len(inputPaths))
|
||||
for i, inputPath := range inputPaths {
|
||||
outputPaths[i] = ctx.GeneratePath(".pdf")
|
||||
|
||||
err := engine.Flatten(ctx, ctx.Log(), inputPath, outputPaths[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("flatten '%s': %w", inputPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return outputPaths, nil
|
||||
}
|
||||
|
||||
// ConvertStub transforms a given PDF to the specified formats defined in
|
||||
// [gotenberg.PdfFormats]. If no format, it does nothing and returns the input
|
||||
// paths.
|
||||
@@ -347,6 +361,51 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
}
|
||||
}
|
||||
|
||||
func flattenRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return api.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/forms/pdfengines/flatten",
|
||||
IsMultipart: true,
|
||||
Handler: func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*api.Context)
|
||||
|
||||
form := ctx.FormData()
|
||||
|
||||
var inputPaths []string
|
||||
err := form.
|
||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||
Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
outputPaths, err := FlattenStub(ctx, engine, inputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert PDFs: %w", err)
|
||||
}
|
||||
|
||||
if len(outputPaths) > 1 {
|
||||
// If .zip archive, keep the original filename.
|
||||
for i, inputPath := range inputPaths {
|
||||
err = ctx.Rename(outputPaths[i], inputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rename output path: %w", err)
|
||||
}
|
||||
|
||||
outputPaths[i] = inputPath
|
||||
}
|
||||
}
|
||||
|
||||
err = ctx.AddOutputPaths(outputPaths...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add output paths: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// convertRoute returns an [api.Route] which can convert PDFs to a specific ODF
|
||||
// format.
|
||||
func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
|
||||
@@ -99,6 +99,11 @@ func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths [
|
||||
return fmt.Errorf("merge PDFs with PDFtk: %w", err)
|
||||
}
|
||||
|
||||
// Flatten is not available in this implementation.
|
||||
func (engine *PdfTk) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("flatten PDF with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Convert is not available in this implementation.
|
||||
func (engine *PdfTk) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("convert PDF to '%+v' with PDFtk: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
|
||||
@@ -101,6 +101,11 @@ func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []
|
||||
return fmt.Errorf("merge PDFs with QPDF: %w", err)
|
||||
}
|
||||
|
||||
// Flatten is not available in this implementation.
|
||||
func (engine *QPdf) Flatten(ctx context.Context, logger *zap.Logger, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("flatten PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
}
|
||||
|
||||
// Convert is not available in this implementation.
|
||||
func (engine *QPdf) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return fmt.Errorf("convert PDF to '%+v' with QPDF: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||
|
||||
Reference in New Issue
Block a user