mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-15 11:52:14 +01:00
feat(pdfengines): add rotate feature
This commit is contained in:
@@ -24,6 +24,7 @@ type multiPdfEngines struct {
|
||||
writeBookmarksEngines []gotenberg.PdfEngine
|
||||
watermarkEngines []gotenberg.PdfEngine
|
||||
stampEngines []gotenberg.PdfEngine
|
||||
rotateEngines []gotenberg.PdfEngine
|
||||
}
|
||||
|
||||
func newMultiPdfEngines(
|
||||
@@ -38,7 +39,8 @@ func newMultiPdfEngines(
|
||||
readBookmarksEngines,
|
||||
writeBookmarksEngines,
|
||||
watermarkEngines,
|
||||
stampEngines []gotenberg.PdfEngine,
|
||||
stampEngines,
|
||||
rotateEngines []gotenberg.PdfEngine,
|
||||
) *multiPdfEngines {
|
||||
return &multiPdfEngines{
|
||||
mergeEngines: mergeEngines,
|
||||
@@ -53,6 +55,7 @@ func newMultiPdfEngines(
|
||||
writeBookmarksEngines: writeBookmarksEngines,
|
||||
watermarkEngines: watermarkEngines,
|
||||
stampEngines: stampEngines,
|
||||
rotateEngines: rotateEngines,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +428,31 @@ func (multi *multiPdfEngines) Stamp(ctx context.Context, logger *zap.Logger, inp
|
||||
return fmt.Errorf("stamp PDF with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Rotate rotates pages of a PDF file using the first available engine that
|
||||
// supports rotation.
|
||||
func (multi *multiPdfEngines) Rotate(ctx context.Context, logger *zap.Logger, inputPath string, angle int, pages string) error {
|
||||
var err error
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
for _, engine := range multi.rotateEngines {
|
||||
go func(engine gotenberg.PdfEngine) {
|
||||
errChan <- engine.Rotate(ctx, logger, inputPath, angle, pages)
|
||||
}(engine)
|
||||
|
||||
select {
|
||||
case rotateErr := <-errChan:
|
||||
errored := multierr.AppendInto(&err, rotateErr)
|
||||
if !errored {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("rotate PDF with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.PdfEngine = (*multiPdfEngines)(nil)
|
||||
|
||||
@@ -40,6 +40,7 @@ type PdfEngines struct {
|
||||
writeBookmarksNames []string
|
||||
watermarkNames []string
|
||||
stampNames []string
|
||||
rotateNames []string
|
||||
engines []gotenberg.PdfEngine
|
||||
disableRoutes bool
|
||||
}
|
||||
@@ -62,6 +63,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
|
||||
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.StringSlice("pdfengines-rotate-engines", []string{"pdfcpu", "pdftk"}, "Set the PDF engines and their order for the rotate feature - empty means all")
|
||||
fs.Bool("pdfengines-disable-routes", false, "Disable the routes")
|
||||
|
||||
// Deprecated flags.
|
||||
@@ -93,6 +95,7 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||
writeBookmarksNames := flags.MustStringSlice("pdfengines-write-bookmarks-engines")
|
||||
watermarkNames := flags.MustStringSlice("pdfengines-watermark-engines")
|
||||
stampNames := flags.MustStringSlice("pdfengines-stamp-engines")
|
||||
rotateNames := flags.MustStringSlice("pdfengines-rotate-engines")
|
||||
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
|
||||
|
||||
engines, err := ctx.Modules(new(gotenberg.PdfEngine))
|
||||
@@ -179,6 +182,11 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||
mod.stampNames = stampNames
|
||||
}
|
||||
|
||||
mod.rotateNames = defaultNames
|
||||
if len(rotateNames) > 0 {
|
||||
mod.rotateNames = rotateNames
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -232,6 +240,7 @@ func (mod *PdfEngines) Validate() error {
|
||||
findNonExistingEngines(mod.writeBookmarksNames)
|
||||
findNonExistingEngines(mod.watermarkNames)
|
||||
findNonExistingEngines(mod.stampNames)
|
||||
findNonExistingEngines(mod.rotateNames)
|
||||
|
||||
if len(nonExistingEngines) == 0 {
|
||||
return nil
|
||||
@@ -256,6 +265,7 @@ func (mod *PdfEngines) SystemMessages() []string {
|
||||
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[:], " ")),
|
||||
fmt.Sprintf("rotate engines - %s", strings.Join(mod.rotateNames[:], " ")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +298,7 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) {
|
||||
engines(mod.writeBookmarksNames),
|
||||
engines(mod.watermarkNames),
|
||||
engines(mod.stampNames),
|
||||
engines(mod.rotateNames),
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -317,6 +328,7 @@ func (mod *PdfEngines) Routes() ([]api.Route, error) {
|
||||
embedRoute(engine),
|
||||
watermarkRoute(engine),
|
||||
stampRoute(engine),
|
||||
rotateRoute(engine),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,56 @@ func FormDataPdfBookmarks(form *api.FormData, mandatory bool) any {
|
||||
return bookmarks
|
||||
}
|
||||
|
||||
// FormDataPdfRotate creates rotation parameters from the form data.
|
||||
func FormDataPdfRotate(form *api.FormData, mandatory bool) (int, string) {
|
||||
var angle int
|
||||
var pages string
|
||||
|
||||
angleFunc := func(value string) error {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v != 90 && v != 180 && v != 270 {
|
||||
return errors.New("wrong value, expected 90, 180, or 270")
|
||||
}
|
||||
angle = v
|
||||
return nil
|
||||
}
|
||||
|
||||
if mandatory {
|
||||
form.MandatoryCustom("rotateAngle", func(value string) error {
|
||||
return angleFunc(value)
|
||||
})
|
||||
} else {
|
||||
form.Custom("rotateAngle", func(value string) error {
|
||||
return angleFunc(value)
|
||||
})
|
||||
}
|
||||
form.String("rotatePages", &pages, "")
|
||||
|
||||
return angle, pages
|
||||
}
|
||||
|
||||
// RotateStub rotates pages of PDF files. If angle is 0, it does nothing.
|
||||
func RotateStub(ctx *api.Context, engine gotenberg.PdfEngine, angle int, pages string, inputPaths []string) error {
|
||||
if angle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, inputPath := range inputPaths {
|
||||
err := engine.Rotate(ctx, ctx.Log(), inputPath, angle, pages)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rotate '%s': %w", inputPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePdfFormatsCompat checks for incompatible combinations of PDF formats
|
||||
// with other features and returns an appropriate error if found.
|
||||
func ValidatePdfFormatsCompat(pdfFormats gotenberg.PdfFormats, userPassword string, embedPaths []string) error {
|
||||
@@ -561,6 +611,7 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
watermarkFiles := FormDataPdfWatermarkFiles(form)
|
||||
stamp := FormDataPdfStamp(form, false)
|
||||
stampFiles := FormDataPdfStampFiles(form)
|
||||
angle, rotatePages := FormDataPdfRotate(form, false)
|
||||
|
||||
var inputPaths []string
|
||||
var flatten bool
|
||||
@@ -604,6 +655,11 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = RotateStub(ctx, engine, angle, rotatePages, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rotate PDFs: %w", err)
|
||||
}
|
||||
|
||||
if flatten {
|
||||
err = FlattenStub(ctx, engine, outputPaths)
|
||||
if err != nil {
|
||||
@@ -706,6 +762,7 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
watermarkFiles := FormDataPdfWatermarkFiles(form)
|
||||
stamp := FormDataPdfStamp(form, false)
|
||||
stampFiles := FormDataPdfStampFiles(form)
|
||||
angle, rotatePages := FormDataPdfRotate(form, false)
|
||||
|
||||
var inputPaths []string
|
||||
var flatten bool
|
||||
@@ -744,6 +801,11 @@ func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("stamp PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = RotateStub(ctx, engine, angle, rotatePages, outputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rotate PDFs: %w", err)
|
||||
}
|
||||
|
||||
if flatten {
|
||||
err = FlattenStub(ctx, engine, outputPaths)
|
||||
if err != nil {
|
||||
@@ -1216,3 +1278,38 @@ func stampRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// rotateRoute returns an [api.Route] which can rotate pages of PDFs.
|
||||
func rotateRoute(engine gotenberg.PdfEngine) api.Route {
|
||||
return api.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/forms/pdfengines/rotate",
|
||||
IsMultipart: true,
|
||||
Handler: func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*api.Context)
|
||||
|
||||
form := ctx.FormData()
|
||||
angle, pages := FormDataPdfRotate(form, true)
|
||||
|
||||
var inputPaths []string
|
||||
err := form.
|
||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||
Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
err = RotateStub(ctx, engine, angle, pages, inputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rotate PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = ctx.AddOutputPaths(inputPaths...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add output paths: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user