diff --git a/build/docs/content/04-html.md b/build/docs/content/04-html.md index 513fc6f8..85c426d5 100644 --- a/build/docs/content/04-html.md +++ b/build/docs/content/04-html.md @@ -301,6 +301,53 @@ $dest = 'result.pdf'; $client->store($request, $dest); ``` +## Page ranges + +You may specify the page ranges to convert. + +The format is the same as the one from the print options +of Google Chrome, e.g. `1-5,8,11-13`. + +### cURL + +```bash +$ curl --request POST \ + --url http://localhost:3000/convert/html \ + --header 'Content-Type: multipart/form-data' \ + --form files=@index.html \ + --form pageRanges='1-3,5' \ + -o result.pdf +``` + +### Go + +```golang +import "github.com/thecodingmachine/gotenberg-go-client/v7" + +c := &gotenberg.Client{Hostname: "http://localhost:3000"} +index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file") +req := gotenberg.NewHTMLRequest(index) +req.PageRanges("1-3,5") +dest := "result.pdf" +c.Store(req, dest) +``` + +### PHP + +```php +use TheCodingMachine\Gotenberg\Client; +use TheCodingMachine\Gotenberg\DocumentFactory; +use TheCodingMachine\Gotenberg\HTMLRequest; +use TheCodingMachine\Gotenberg\Request; + +$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()); +$index = DocumentFactory::makeFromPath('index.html', '/path/to/file'); +$request = new HTMLRequest($index); +$request->setPageRanges('1-3,5'); +$dest = 'result.pdf'; +$client->store($request, $dest); +``` + ## Wait delay In some cases, you may want to wait a certain amount of time to make sure the diff --git a/build/docs/content/07-office.md b/build/docs/content/07-office.md index d9545027..a024b236 100644 --- a/build/docs/content/07-office.md +++ b/build/docs/content/07-office.md @@ -114,3 +114,54 @@ $request->setLandscape(true); $dest = 'result.pdf'; $client->store($request, $dest); ``` + +## Page ranges + +You may specify the page ranges to convert. + +The format is the same as the one from the print options +of LibreOffice, e.g. `1-1` or `1-4`. + +> **Attention:** if more than one document, the page ranges will be +> applied for each document. + +### cURL + +```bash +$ curl --request POST \ + --url http://localhost:3000/convert/office \ + --header 'Content-Type: multipart/form-data' \ + --form files=@document.docx \ + --form pageRanges='1-3' \ + -o result.pdf +``` + +### Go + +```golang +import "github.com/thecodingmachine/gotenberg-go-client/v6" + +c := &gotenberg.Client{Hostname: "http://localhost:3000"} +doc, _ := gotenberg.NewDocumentFromPath("document.docx", "/path/to/file") +req := gotenberg.NewOfficeRequest(doc) +req.PageRanges("1-3") +dest := "result.pdf" +c.Store(req, dest) +``` + +### PHP + +```php +use TheCodingMachine\Gotenberg\Client; +use TheCodingMachine\Gotenberg\DocumentFactory; +use TheCodingMachine\Gotenberg\OfficeRequest; + +$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()); +$files = [ + DocumentFactory::makeFromPath('document.docx', '/path/to/file'), +]; +$request = new OfficeRequest($files); +$request->setPageRanges('1-3'); +$dest = 'result.pdf'; +$client->store($request, $dest); +``` diff --git a/docs/index.html b/docs/index.html index af20018c..6769a751 100755 --- a/docs/index.html +++ b/docs/index.html @@ -740,6 +740,58 @@ $dest = 'result.pdf'; $client->store($request, $dest); +

Page ranges

+ +

You may specify the page ranges to convert.

+ +

The format is the same as the one from the print options +of Google Chrome, e.g. 1-5,8,11-13.

+ +

cURL

+ +
$ curl --request POST \
+    --url http://localhost:3000/convert/html \
+    --header 'Content-Type: multipart/form-data' \
+    --form files=@index.html \
+    --form pageRanges='1-3,5' \
+    -o result.pdf
+
+ +

Go

+ +
import "github.com/thecodingmachine/gotenberg-go-client/v7"
+
+c := &gotenberg.Client{Hostname: "http://localhost:3000"}
+index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
+req := gotenberg.NewHTMLRequest(index)
+req.PageRanges("1-3,5")
+dest := "result.pdf"
+c.Store(req, dest)
+
+ +

PHP

+ +
use TheCodingMachine\Gotenberg\Client;
+use TheCodingMachine\Gotenberg\DocumentFactory;
+use TheCodingMachine\Gotenberg\HTMLRequest;
+use TheCodingMachine\Gotenberg\Request;
+
+$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
+$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
+$request = new HTMLRequest($index);
+$request->setPageRanges('1-3,5');
+$dest = 'result.pdf';
+$client->store($request, $dest);
+
+

Wait delay

@@ -1181,6 +1233,64 @@ $request = new OfficeRequest($files); $request->setLandscape(true); $dest = 'result.pdf'; $client->store($request, $dest); + + +

Page ranges

+ +

You may specify the page ranges to convert.

+ +

The format is the same as the one from the print options +of LibreOffice, e.g. 1-1 or 1-4.

+ +
+

Attention: if more than one document, the page ranges will be +applied for each document.

+
+ +

cURL

+ +
$ curl --request POST \
+    --url http://localhost:3000/convert/office \
+    --header 'Content-Type: multipart/form-data' \
+    --form files=@document.docx \
+    --form pageRanges='1-3' \
+    -o result.pdf
+
+ +

Go

+ +
import "github.com/thecodingmachine/gotenberg-go-client/v6"
+
+c := &gotenberg.Client{Hostname: "http://localhost:3000"}
+doc, _ := gotenberg.NewDocumentFromPath("document.docx", "/path/to/file")
+req := gotenberg.NewOfficeRequest(doc)
+req.PageRanges("1-3")
+dest := "result.pdf"
+c.Store(req, dest)
+
+ +

PHP

+ +
use TheCodingMachine\Gotenberg\Client;
+use TheCodingMachine\Gotenberg\DocumentFactory;
+use TheCodingMachine\Gotenberg\OfficeRequest;
+
+$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
+$files = [
+    DocumentFactory::makeFromPath('document.docx', '/path/to/file'),
+];
+$request = new OfficeRequest($files);
+$request->setPageRanges('1-3');
+$dest = 'result.pdf';
+$client->store($request, $dest);
 
diff --git a/internal/app/xhttp/option.go b/internal/app/xhttp/option.go index 2afb6984..5a9a3759 100644 --- a/internal/app/xhttp/option.go +++ b/internal/app/xhttp/option.go @@ -48,23 +48,29 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro if err != nil { return printer.ChromePrinterOptions{}, err } + pageRanges, err := r.StringArg(resource.PageRangesArgKey, "") + if err != nil { + return printer.ChromePrinterOptions{}, err + } googleChromeRpccBufferSize, err := resource.GoogleChromeRpccBufferSizeArg(r, config) if err != nil { return printer.ChromePrinterOptions{}, err } return printer.ChromePrinterOptions{ - WaitTimeout: waitTimeout, - WaitDelay: waitDelay, - HeaderHTML: headerHTML, - FooterHTML: footerHTML, - PaperWidth: paperWidth, - PaperHeight: paperHeight, - MarginTop: marginTop, - MarginBottom: marginBottom, - MarginLeft: marginLeft, - MarginRight: marginRight, - Landscape: landscape, - RpccBufferSize: googleChromeRpccBufferSize, + WaitTimeout: waitTimeout, + WaitDelay: waitDelay, + HeaderHTML: headerHTML, + FooterHTML: footerHTML, + PaperWidth: paperWidth, + PaperHeight: paperHeight, + MarginTop: marginTop, + MarginBottom: marginBottom, + MarginLeft: marginLeft, + MarginRight: marginRight, + Landscape: landscape, + PageRanges: pageRanges, + RpccBufferSize: googleChromeRpccBufferSize, + CustomHTTPHeaders: make(map[string]string), }, nil } opts, err := resolver() @@ -85,9 +91,14 @@ func officePrinterOptions(r resource.Resource, config conf.Config) (printer.Offi if err != nil { return printer.OfficePrinterOptions{}, err } + pageRanges, err := r.StringArg(resource.PageRangesArgKey, "") + if err != nil { + return printer.OfficePrinterOptions{}, err + } return printer.OfficePrinterOptions{ WaitTimeout: waitTimeout, Landscape: landscape, + PageRanges: pageRanges, }, nil } opts, err := resolver() diff --git a/internal/app/xhttp/pkg/resource/arg.go b/internal/app/xhttp/pkg/resource/arg.go index 706e8952..80063925 100644 --- a/internal/app/xhttp/pkg/resource/arg.go +++ b/internal/app/xhttp/pkg/resource/arg.go @@ -51,6 +51,9 @@ const ( // LandscapeArgKey is the key // of the argument "landscape". LandscapeArgKey ArgKey = "landscape" + // PageRangesArgKey is the key + // of the argument "pageRanges". + PageRangesArgKey ArgKey = "pageRanges" // GoogleChromeRpccBufferSizeArgKey is the key // of the argument "googleChromeRpccBufferSize". GoogleChromeRpccBufferSizeArgKey ArgKey = "googleChromeRpccBufferSize" @@ -76,6 +79,7 @@ func ArgKeys() []ArgKey { MarginLeftArgKey, MarginRightArgKey, LandscapeArgKey, + PageRangesArgKey, GoogleChromeRpccBufferSizeArgKey, } } diff --git a/internal/app/xhttp/pkg/resource/arg_test.go b/internal/app/xhttp/pkg/resource/arg_test.go index 1485c5d5..230add46 100644 --- a/internal/app/xhttp/pkg/resource/arg_test.go +++ b/internal/app/xhttp/pkg/resource/arg_test.go @@ -24,6 +24,7 @@ func TestArgKeys(t *testing.T) { MarginLeftArgKey, MarginRightArgKey, LandscapeArgKey, + PageRangesArgKey, GoogleChromeRpccBufferSizeArgKey, } assert.Equal(t, expected, ArgKeys()) diff --git a/internal/pkg/printer/chrome.go b/internal/pkg/printer/chrome.go index 10337fab..4fe79d33 100644 --- a/internal/pkg/printer/chrome.go +++ b/internal/pkg/printer/chrome.go @@ -42,6 +42,7 @@ type ChromePrinterOptions struct { MarginLeft float64 MarginRight float64 Landscape bool + PageRanges string RpccBufferSize int64 CustomHTTPHeaders map[string]string } @@ -62,6 +63,7 @@ func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions { MarginLeft: 1.0, MarginRight: 1.0, Landscape: false, + PageRanges: "", RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(), CustomHTTPHeaders: make(map[string]string), } @@ -163,23 +165,35 @@ func (p chromePrinter) Print(destination string) error { } else { p.logger.DebugOp(op, "no wait delay to apply, moving on...") } + printToPdfArgs := page.NewPrintToPDFArgs(). + SetPaperWidth(p.opts.PaperWidth). + SetPaperHeight(p.opts.PaperHeight). + SetMarginTop(p.opts.MarginTop). + SetMarginBottom(p.opts.MarginBottom). + SetMarginLeft(p.opts.MarginLeft). + SetMarginRight(p.opts.MarginRight). + SetLandscape(p.opts.Landscape). + SetDisplayHeaderFooter(true). + SetHeaderTemplate(p.opts.HeaderHTML). + SetFooterTemplate(p.opts.FooterHTML). + SetPrintBackground(true) + if p.opts.PageRanges != "" { + printToPdfArgs.SetPageRanges(p.opts.PageRanges) + } // print the page to PDF. print, err := targetClient.Page.PrintToPDF( ctx, - page.NewPrintToPDFArgs(). - SetPaperWidth(p.opts.PaperWidth). - SetPaperHeight(p.opts.PaperHeight). - SetMarginTop(p.opts.MarginTop). - SetMarginBottom(p.opts.MarginBottom). - SetMarginLeft(p.opts.MarginLeft). - SetMarginRight(p.opts.MarginRight). - SetLandscape(p.opts.Landscape). - SetDisplayHeaderFooter(true). - SetHeaderTemplate(p.opts.HeaderHTML). - SetFooterTemplate(p.opts.FooterHTML). - SetPrintBackground(true), + printToPdfArgs, ) if err != nil { + // find a way to check it in the handlers? + if strings.Contains(err.Error(), "Page range syntax error") { + return xerror.Invalid( + op, + fmt.Sprintf("'%s' is not a valid Google Chrome page ranges", p.opts.PageRanges), + err, + ) + } if strings.Contains(err.Error(), "rpcc: message too large") { return xerror.Invalid( op, diff --git a/internal/pkg/printer/html_test.go b/internal/pkg/printer/html_test.go index 4a233163..e69539fa 100644 --- a/internal/pkg/printer/html_test.go +++ b/internal/pkg/printer/html_test.go @@ -38,6 +38,26 @@ func TestHTMLPrinter(t *testing.T) { assert.Nil(t, err) err = os.RemoveAll(dest) assert.Nil(t, err) + // options with a page ranges. + opts = DefaultChromePrinterOptions(config) + opts.PageRanges = "1" + p = NewHTMLPrinter(logger, fpath, opts) + dest = test.GenerateDestination() + err = p.Print(dest) + assert.Nil(t, err) + err = os.RemoveAll(dest) + assert.Nil(t, err) + // should not be OK as options have + // a wrong page ranges. + opts = DefaultChromePrinterOptions(config) + opts.PageRanges = "foo" + p = NewHTMLPrinter(logger, fpath, opts) + dest = test.GenerateDestination() + err = p.Print(dest) + test.AssertError(t, err) + assert.Equal(t, xerror.InvalidCode, xerror.Code(err)) + err = os.RemoveAll(dest) + assert.Nil(t, err) // should not be OK as context.Context // should timeout. opts = DefaultChromePrinterOptions(config) diff --git a/internal/pkg/printer/markdown_test.go b/internal/pkg/printer/markdown_test.go index c5115a3a..9627cdea 100644 --- a/internal/pkg/printer/markdown_test.go +++ b/internal/pkg/printer/markdown_test.go @@ -40,6 +40,28 @@ func TestMarkdownPrinter(t *testing.T) { assert.Nil(t, err) err = os.RemoveAll(dest) assert.Nil(t, err) + // options with a page ranges. + opts = DefaultChromePrinterOptions(config) + opts.PageRanges = "1" + p, err = NewMarkdownPrinter(logger, fpath, opts) + assert.Nil(t, err) + dest = test.GenerateDestination() + err = p.Print(dest) + assert.Nil(t, err) + err = os.RemoveAll(dest) + assert.Nil(t, err) + // should not be OK as options have + // a wrong page ranges. + opts = DefaultChromePrinterOptions(config) + opts.PageRanges = "foo" + p, err = NewMarkdownPrinter(logger, fpath, opts) + assert.Nil(t, err) + dest = test.GenerateDestination() + err = p.Print(dest) + test.AssertError(t, err) + assert.Equal(t, xerror.InvalidCode, xerror.Code(err)) + err = os.RemoveAll(dest) + assert.Nil(t, err) // should not be OK as context.Context // should timeout. opts = DefaultChromePrinterOptions(config) diff --git a/internal/pkg/printer/office.go b/internal/pkg/printer/office.go index 1b31d461..020e5688 100644 --- a/internal/pkg/printer/office.go +++ b/internal/pkg/printer/office.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "sort" + "strings" "github.com/phayes/freeport" "github.com/thecodingmachine/gotenberg/internal/pkg/conf" @@ -27,6 +28,7 @@ type officePrinter struct { type OfficePrinterOptions struct { WaitTimeout float64 Landscape bool + PageRanges string } // DefaultOfficePrinterOptions returns the default @@ -35,6 +37,7 @@ func DefaultOfficePrinterOptions(config conf.Config) OfficePrinterOptions { return OfficePrinterOptions{ WaitTimeout: config.DefaultWaitTimeout(), Landscape: false, + PageRanges: "", } } @@ -62,7 +65,7 @@ func (p officePrinter) Print(destination string) error { baseFilename := xrand.Get() tmpDest := fmt.Sprintf("%s/%d%s.pdf", dirPath, i, baseFilename) p.logger.DebugfOp(op, "converting '%s' to PDF...", fpath) - if err := unoconv(ctx, p.logger, fpath, tmpDest, p.opts); err != nil { + if err := p.unoconv(ctx, fpath, tmpDest); err != nil { return err } p.logger.DebugfOp(op, "'%s.pdf' created", baseFilename) @@ -88,7 +91,7 @@ func (p officePrinter) Print(destination string) error { return nil } -func unoconv(ctx context.Context, logger xlog.Logger, fpath, destination string, opts OfficePrinterOptions) error { +func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) error { const op string = "printer.unoconv" resolver := func() error { port, err := freeport.GetFreePort() @@ -103,11 +106,25 @@ func unoconv(ctx context.Context, logger xlog.Logger, fpath, destination string, "--format", "pdf", } - if opts.Landscape { + if p.opts.Landscape { args = append(args, "--printer", "PaperOrientation=landscape") } + if p.opts.PageRanges != "" { + args = append(args, "--export", fmt.Sprintf("PageRange=%s", p.opts.PageRanges)) + } args = append(args, "--output", destination, fpath) - return xexec.Run(ctx, logger, "unoconv", args...) + if err := xexec.Run(ctx, p.logger, "unoconv", args...); err != nil { + // find a way to check it in the handlers? + if p.opts.PageRanges != "" && strings.Contains(err.Error(), "exit status 5") { + return xerror.Invalid( + op, + fmt.Sprintf("'%s' is not a valid LibreOffice page ranges", p.opts.PageRanges), + err, + ) + } + return err + } + return nil } if err := resolver(); err != nil { return xerror.New(op, err) diff --git a/internal/pkg/printer/office_test.go b/internal/pkg/printer/office_test.go index b6a0235b..5eb8992e 100644 --- a/internal/pkg/printer/office_test.go +++ b/internal/pkg/printer/office_test.go @@ -46,6 +46,26 @@ func TestOfficePrinter(t *testing.T) { assert.Nil(t, err) err = os.RemoveAll(dest) assert.Nil(t, err) + // options with page ranges. + opts = DefaultOfficePrinterOptions(config) + opts.PageRanges = "1-1" + p = NewOfficePrinter(logger, []string{fpaths[0]}, opts) + dest = test.GenerateDestination() + err = p.Print(dest) + assert.Nil(t, err) + err = os.RemoveAll(dest) + assert.Nil(t, err) + // should not be OK as options have + // a wrong page ranges. + opts = DefaultOfficePrinterOptions(config) + opts.PageRanges = "foo" + p = NewOfficePrinter(logger, []string{fpaths[0]}, opts) + dest = test.GenerateDestination() + err = p.Print(dest) + test.AssertError(t, err) + assert.Equal(t, xerror.InvalidCode, xerror.Code(err)) + err = os.RemoveAll(dest) + assert.Nil(t, err) // should not be OK as context.Context // should timeout. opts = DefaultOfficePrinterOptions(config) diff --git a/internal/pkg/printer/url_test.go b/internal/pkg/printer/url_test.go index 2c0b99f7..ef477e75 100644 --- a/internal/pkg/printer/url_test.go +++ b/internal/pkg/printer/url_test.go @@ -38,6 +38,26 @@ func TestURLPrinter(t *testing.T) { assert.Nil(t, err) err = os.RemoveAll(dest) assert.Nil(t, err) + // options with a pages ranges. + opts = DefaultChromePrinterOptions(config) + opts.PageRanges = "1" + p = NewURLPrinter(logger, URL, opts) + dest = test.GenerateDestination() + err = p.Print(dest) + assert.Nil(t, err) + err = os.RemoveAll(dest) + assert.Nil(t, err) + // should not be OK as options have + // a wrong page ranges. + opts = DefaultChromePrinterOptions(config) + opts.PageRanges = "foo" + p = NewURLPrinter(logger, URL, opts) + dest = test.GenerateDestination() + err = p.Print(dest) + test.AssertError(t, err) + assert.Equal(t, xerror.InvalidCode, xerror.Code(err)) + err = os.RemoveAll(dest) + assert.Nil(t, err) // should not be OK as context.Context // should timeout. opts = DefaultChromePrinterOptions(config)