From c6f6a87499ce6aa10c04f9f47645b86ea95f3787 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 12 Dec 2018 16:16:35 +0100 Subject: [PATCH] now using latest version of unoconv, allowing to specify paper size and orientation + updating go client (#24) --- build/base/Dockerfile | 9 ++- build/docs/02-html.md | 40 ++++------- build/docs/03-markdown.md | 7 +- build/docs/04-office.md | 63 +++++++++++++++-- build/docs/05-merge.md | 7 +- docs/index.html | 124 +++++++++++++++++++++------------ internal/app/api/office.go | 11 +++ internal/pkg/printer/office.go | 40 +++++++++-- pkg/README.md | 24 +++---- pkg/client.go | 25 +++++-- pkg/doc.go | 27 +------ pkg/html.go | 108 +++++++++++++++++----------- pkg/html_test.go | 29 ++++---- pkg/markdown.go | 124 ++++++++++++++++++++------------- pkg/markdown_test.go | 33 +++++---- pkg/merge.go | 34 ++++----- pkg/merge_test.go | 11 ++- pkg/office.go | 47 +++++++------ pkg/office_test.go | 11 +-- 19 files changed, 466 insertions(+), 308 deletions(-) diff --git a/build/base/Dockerfile b/build/base/Dockerfile index deaa2f9b..86c4e0df 100644 --- a/build/base/Dockerfile +++ b/build/base/Dockerfile @@ -11,7 +11,7 @@ LABEL authors="Julien Neuhart " RUN echo "deb http://httpredir.debian.org/debian/ stretch main contrib non-free" > /etc/apt/sources.list &&\ apt-get update &&\ - apt-get install -y curl wget + apt-get install -y curl wget python3-pip # |-------------------------------------------------------------------------- # | PM2 @@ -39,7 +39,10 @@ RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add # | Unoconv # |-------------------------------------------------------------------------- # | -# | Installs MS fonts and unoconv for Office documents conversions. +# | Installs unoconv and LibreOffice for Office documents conversions. # | -RUN apt-get -y install ttf-mscorefonts-installer unoconv \ No newline at end of file +RUN pip3 install unoconv &&\ + # https://github.com/nextcloud/docker/issues/380 + mkdir -p /usr/share/man/man1mkdir -p /usr/share/man/man1 &&\ + apt-get -y install libreoffice \ No newline at end of file diff --git a/build/docs/02-html.md b/build/docs/02-html.md index b8d37ad4..676fa8e0 100644 --- a/build/docs/02-html.md +++ b/build/docs/02-html.md @@ -43,9 +43,7 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - } + req, _ := gotenberg.NewHTMLRequest("index.html") dest := "result.pdf" c.Store(req, dest) } @@ -119,13 +117,9 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - Options: &gotenberg.HTMLOptions{ - HeaderFilePath: "header.html", - FooterFilePath: "footer.html", - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetHeader("header.html") + req.SetFooter("footer.html") dest := "result.pdf" c.Store(req, dest) } @@ -213,14 +207,12 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - AssetFilePaths: []string{ - "style.css", - "img.png", - "font.woff", - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetAssets([]string{ + "font.woff", + "img.gif", + "style.css", + }) dest := "result.pdf" c.Store(req, dest) } @@ -279,14 +271,10 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - Options: &gotenberg.HTMLOptions{ - PaperSize: gotenberg.A4, - PaperMargins: gotenberg.NoMargins, - Landscape: true - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetPaperSize(gotenberg.A4) + req.SetMargins(gotenberg.NormalMargins) + req.SetLandscape(true) dest := "result.pdf" c.Store(req, dest) } diff --git a/build/docs/03-markdown.md b/build/docs/03-markdown.md index 2953026a..545ec3df 100644 --- a/build/docs/03-markdown.md +++ b/build/docs/03-markdown.md @@ -46,12 +46,7 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.MarkdownRequest{ - IndexFilePath: "index.html", - MarkdownFilePaths: []string{ - "file.md", - }, - } + req, _ := gotenberg.NewMarkdownRequest("index.html", []string{"file.md"}) dest := "result.pdf" c.Store(req, dest) } diff --git a/build/docs/04-office.md b/build/docs/04-office.md index 4c630b21..6c9567cc 100644 --- a/build/docs/04-office.md +++ b/build/docs/04-office.md @@ -44,12 +44,7 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.OfficeRequest{ - FilePaths: []string{ - "document.docx", - "document2.docx", - }, - } + req, _ := gotenberg.NewOfficeRequest([]string{"document.docx", "document2.docx"}) dest := "result.pdf" c.Store(req, dest) } @@ -72,6 +67,62 @@ $dirPath = "/foo"; $filename = $client->store($request, $dirPath); ``` +## Paper size and orientation + +You may also customize the resulting PDF format. + +By default, it will be rendered with `A4` size `portrait` orientation. + +> Paper size has to be provided in `inches`. +> Also, you have to set both `paperWidth` and `paperHeight`. + +### cURL + +```bash +$ curl --request POST \ + --url http://localhost:3000/convert/office \ + --header 'Content-Type: multipart/form-data' \ + --form files=@document.docx \ + --form paperWidth=8.27 \ + --form paperHeight=11.27 \ + --form landscape=true \ + > result.pdf +``` + +### Go + +```golang +import "github.com/thecodingmachine/gotenberg/pkg" + +func main() { + c := &gotenberg.Client{Hostname: "http://localhost:3000"} + req, _ := gotenberg.NewOfficeRequest([]string{"document.docx"}) + req.SetPaperSize(A4) + req.SetLandscape(true) + 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', 'document.docx'), +]; +$request = new OfficeRequest($files); +$request->setPaperSize(Request::A4); +$request->setMargins(Request::NO_MARGINS); +$request->setLandscape(true); +$dirPath = "/foo"; +$filename = $client->store($request, $dirPath); +``` + ## Fonts By default, only `ttf-mscorefonts` fonts are installed. diff --git a/build/docs/05-merge.md b/build/docs/05-merge.md index 43de973b..6ecc5ddb 100644 --- a/build/docs/05-merge.md +++ b/build/docs/05-merge.md @@ -29,12 +29,7 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.MergeRequest{ - FilePaths: []string{ - "file.pdf", - "file2.pdf", - }, - } + req, _ := gotenberg.NewMergeRequest([]string{"file.pdf", "file2.pdf"}) dest := "result.pdf" c.Store(req, dest) } diff --git a/docs/index.html b/docs/index.html index 34fd700f..23fbea6d 100755 --- a/docs/index.html +++ b/docs/index.html @@ -187,9 +187,7 @@ which will be converted to PDF.

func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - } + req, _ := gotenberg.NewHTMLRequest("index.html") dest := "result.pdf" c.Store(req, dest) } @@ -271,13 +269,9 @@ Also, footer.html CSS properties override the ones from heade func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - Options: &gotenberg.HTMLOptions{ - HeaderFilePath: "header.html", - FooterFilePath: "footer.html", - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetHeader("header.html") + req.SetFooter("footer.html") dest := "result.pdf" c.Store(req, dest) } @@ -370,14 +364,12 @@ see to the fonts section.

func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - AssetFilePaths: []string{ - "style.css", - "img.png", - "font.woff", - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetAssets([]string{ + "font.woff", + "img.gif", + "style.css", + }) dest := "result.pdf" c.Store(req, dest) } @@ -443,14 +435,10 @@ Also, you have to set both paperWidth and paperHeight. func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - Options: &gotenberg.HTMLOptions{ - PaperSize: gotenberg.A4, - PaperMargins: gotenberg.NoMargins, - Landscape: true - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetPaperSize(gotenberg.A4) + req.SetMargins(gotenberg.NormalMargins) + req.SetLandscape(true) dest := "result.pdf" c.Store(req, dest) } @@ -528,12 +516,7 @@ in the file index.html. This function will convert a given markdown func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.MarkdownRequest{ - IndexFilePath: "index.html", - MarkdownFilePaths: []string{ - "file.md", - }, - } + req, _ := gotenberg.NewMarkdownRequest("index.html", []string{"file.md"}) dest := "result.pdf" c.Store(req, dest) } @@ -613,12 +596,7 @@ See the scalability section to find how to mitigate t func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.OfficeRequest{ - FilePaths: []string{ - "document.docx", - "document2.docx", - }, - } + req, _ := gotenberg.NewOfficeRequest([]string{"document.docx", "document2.docx"}) dest := "result.pdf" c.Store(req, dest) } @@ -642,6 +620,69 @@ See the scalability section to find how to mitigate t $filename = $client->store($request, $dirPath); +

Paper size and orientation

+ +

You may also customize the resulting PDF format.

+ +

By default, it will be rendered with A4 size portrait orientation.

+ +
+

Paper size has to be provided in inches. +Also, you have to set both paperWidth and paperHeight.

+
+ +

cURL

+ +
$ curl --request POST \
+    --url http://localhost:3000/convert/office \
+    --header 'Content-Type: multipart/form-data' \
+    --form files=@document.docx \
+    --form paperWidth=8.27 \
+    --form paperHeight=11.27 \
+    --form landscape=true \
+    > result.pdf
+
+ +

Go

+ +
import "github.com/thecodingmachine/gotenberg/pkg"
+
+func main() {
+    c := &gotenberg.Client{Hostname: "http://localhost:3000"}
+    req, _ := gotenberg.NewOfficeRequest([]string{"document.docx"})
+    req.SetPaperSize(A4)
+    req.SetLandscape(true)
+    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', 'document.docx'),
+];
+$request = new OfficeRequest($files);
+$request->setPaperSize(Request::A4);
+$request->setMargins(Request::NO_MARGINS);
+$request->setLandscape(true);
+$dirPath = "/foo";
+$filename = $client->store($request, $dirPath);
+
+

Fonts

@@ -692,12 +733,7 @@ will merge them and return the resulting PDF file.

func main() { c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.MergeRequest{ - FilePaths: []string{ - "file.pdf", - "file2.pdf", - }, - } + req, _ := gotenberg.NewMergeRequest([]string{"file.pdf", "file2.pdf"}) dest := "result.pdf" c.Store(req, dest) } diff --git a/internal/app/api/office.go b/internal/app/api/office.go index 2bb0743b..f556c909 100644 --- a/internal/app/api/office.go +++ b/internal/app/api/office.go @@ -37,5 +37,16 @@ func convertOffice(c echo.Context) error { return errors.New("no suitable office documents to convert") } p := &printer.Office{Context: ctx, FilePaths: fpaths} + paperSize, err := r.paperSize() + if err != nil { + return err + } + p.PaperWidth = paperSize[0] + p.PaperHeight = paperSize[1] + landscape, err := r.landscape() + if err != nil { + return err + } + p.Landscape = landscape return print(c, p, r) } diff --git a/internal/pkg/printer/office.go b/internal/pkg/printer/office.go index d6358221..b6f1f441 100644 --- a/internal/pkg/printer/office.go +++ b/internal/pkg/printer/office.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "sync" "github.com/thecodingmachine/gotenberg/internal/pkg/rand" @@ -16,8 +17,11 @@ var mu sync.Mutex // Office facilitates Office documents to PDF conversion. type Office struct { - Context context.Context - FilePaths []string + Context context.Context + FilePaths []string + PaperWidth float64 + PaperHeight float64 + Landscape bool } // Print converts Office documents to PDF. @@ -32,14 +36,24 @@ func (o *Office) Print(destination string) error { return err } tmpDest := fmt.Sprintf("%s/%s.pdf", dirPath, baseFilename) + paperSize, err := unoconvPaperSize(o.PaperWidth, o.PaperHeight) + if err != nil { + return err + } + cmdArgs := []string{ + "--format", + "pdf", + "--printer", + paperSize, + } + if o.Landscape { + cmdArgs = append(cmdArgs, "--printer", "PaperOrientation=landscape") + } + cmdArgs = append(cmdArgs, "--output", tmpDest, fpath) cmd := exec.CommandContext( o.Context, "unoconv", - "--format", - "pdf", - "--output", - tmpDest, - fpath, + cmdArgs..., ) _, err = cmd.Output() if o.Context.Err() == context.DeadlineExceeded { @@ -56,6 +70,18 @@ func (o *Office) Print(destination string) error { return Merge(fpaths, destination) } +func unoconvPaperSize(paperWidth, paperHeight float64) (string, error) { + width, err := strconv.Atoi(fmt.Sprintf("%.0f", paperWidth*25.4)) + if err != nil { + return "", fmt.Errorf("%0.f: converting width to millimiter: %v", paperWidth, err) + } + height, err := strconv.Atoi(fmt.Sprintf("%.0f", paperHeight*25.4)) + if err != nil { + return "", fmt.Errorf("%0.f: converting height to millimiter: %v", paperHeight, err) + } + return fmt.Sprintf("PaperSize=%dx%d", width, height), nil +} + // Compile-time checks to ensure type implements desired interfaces. var ( _ = Printer(new(Office)) diff --git a/pkg/README.md b/pkg/README.md index 51c11903..c2bbe83d 100644 --- a/pkg/README.md +++ b/pkg/README.md @@ -16,19 +16,17 @@ import "github.com/thecodingmachine/gotenberg/pkg" func main() { // HTML conversion example. c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - AssetFilePaths: []string{ - "style.css", - "img.png", - }, - Options: &gotenberg.HTMLOptions{ - HeaderFilePath: "header.html", - FooterFilePath: "footer.html", - PaperSize: gotenberg.A4, - PaperMargins: gotenberg.NormalMargins, - }, - } + req, _ := gotenberg.NewHTMLRequest("index.html") + req.SetHeader("header.html") + req.SetFooter("footer.html") + req.SetAssets([]string{ + "font.woff", + "img.gif", + "style.css", + }) + req.SetPaperSize(A4) + req.SetMargins(NormalMargins) + req.SetLandscape(false) dest := "foo.pdf" c.Store(req, dest) } diff --git a/pkg/client.go b/pkg/client.go index 5b9e5573..c43f58ee 100644 --- a/pkg/client.go +++ b/pkg/client.go @@ -59,18 +59,35 @@ type Client struct { // form values and form files to // the Gotenberg API. type Request interface { - validate() error + SetWebhookURL(webhookURL string) getPostURL() string getFormValues() map[string]string getFormFiles() map[string]string } +// ChromeRequest is a type for sending +// conversion requests which will be +// handle by Google Chrome. +type ChromeRequest interface { + SetHeader(fpath string) error + SetFooter(fpath string) error + SetAssets(fpaths []string) error + SetPaperSize(size [2]float64) + SetMargins(margins [4]float64) + SetLandscape(isLandscape bool) +} + +// UnoconvRequest is a type for sending +// conversion requests which will be +// handle by unoconv. +type UnoconvRequest interface { + SetPaperSize(size [2]float64) + SetLandscape(landscape bool) +} + // Post sends a request to the Gotenberg API // and returns the response. func (c *Client) Post(req Request) (*http.Response, error) { - if err := req.validate(); err != nil { - return nil, err - } body, contentType, err := multipartForm(req) if err != nil { return nil, err diff --git a/pkg/doc.go b/pkg/doc.go index 022b107b..a69d241b 100644 --- a/pkg/doc.go +++ b/pkg/doc.go @@ -2,30 +2,7 @@ Package gotenberg is a Go client for interacting with a Gotenberg API. -For instance, if you want to convert HTML: - - import "github.com/thecodingmachine/gotenberg/pkg" - - func main() { - // HTML conversion example. - c := &gotenberg.Client{Hostname: "http://localhost:3000"} - req := &gotenberg.HTMLRequest{ - IndexFilePath: "index.html", - AssetFilePaths: []string{ - "style.css", - "img.png", - }, - Options: &gotenberg.HTMLOptions{ - HeaderFilePath: "header.html", - FooterFilePath: "footer.html", - PaperSize: gotenberg.A4, - PaperMargins: gotenberg.NormalMargins, - }, - } - dest := "foo.pdf" - c.Store(req, dest) - } - -For more complete usages, head to the https://thecodingmachine.gotenberg.github.io. +For more complete usages, head to the documentation: +https://thecodingmachine.gotenberg.github.io. */ package gotenberg diff --git a/pkg/html.go b/pkg/html.go index afef66bd..f489f31e 100644 --- a/pkg/html.go +++ b/pkg/html.go @@ -9,64 +9,89 @@ import ( // HTMLRequest facilitates HTML conversion // with the Gotenberg API. type HTMLRequest struct { - IndexFilePath string - AssetFilePaths []string - Options *HTMLOptions + indexFilePath string + headerFilePath string + footerFilePath string + assetFilePaths []string + values map[string]string } -// HTMLOptions gathers all options -// for HTML conversion with the Gotenberg API. -type HTMLOptions struct { - WebHookURL string - HeaderFilePath string - FooterFilePath string - PaperSize [2]float64 - PaperMargins [4]float64 - Landscape bool +// NewHTMLRequest create HTMLRequest. +func NewHTMLRequest(indexFilePath string) (*HTMLRequest, error) { + if !fileExists(indexFilePath) { + return nil, fmt.Errorf("%s: index file does not exist", indexFilePath) + } + return &HTMLRequest{indexFilePath: indexFilePath, values: make(map[string]string)}, nil } -func (html *HTMLRequest) validate() error { - if !fileExists(html.IndexFilePath) { - return fmt.Errorf("%s: index file does not exist", html.IndexFilePath) - } - if html.Options.HeaderFilePath != "" && !fileExists(html.Options.HeaderFilePath) { - return fmt.Errorf("%s: header file does not exist", html.Options.HeaderFilePath) - } - if html.Options.FooterFilePath != "" && !fileExists(html.Options.FooterFilePath) { - return fmt.Errorf("%s: footer file does not exist", html.Options.FooterFilePath) +// SetWebhookURL sets webhookURL form field. +func (html *HTMLRequest) SetWebhookURL(webhookURL string) { + html.values[webhookURL] = webhookURL +} + +// SetHeader sets header form file. +func (html *HTMLRequest) SetHeader(fpath string) error { + if !fileExists(fpath) { + return fmt.Errorf("%s: header file does not exist", fpath) } + html.headerFilePath = fpath return nil } +// SetFooter sets footer form file. +func (html *HTMLRequest) SetFooter(fpath string) error { + if !fileExists(fpath) { + return fmt.Errorf("%s: footer file does not exist", fpath) + } + html.footerFilePath = fpath + return nil +} + +// SetAssets sets assets form files. +func (html *HTMLRequest) SetAssets(fpaths []string) error { + for _, fpath := range fpaths { + if !fileExists(fpath) { + return fmt.Errorf("%s: file does not exist", fpath) + } + } + html.assetFilePaths = fpaths + return nil +} + +// SetPaperSize sets paperWidth and paperHeight form fields. +func (html *HTMLRequest) SetPaperSize(size [2]float64) { + html.values[paperWidth] = fmt.Sprintf("%f", size[0]) + html.values[paperHeight] = fmt.Sprintf("%f", size[1]) +} + +// SetMargins sets marginTop, marginBottom, +// marginLeft and marginRight form fields. +func (html *HTMLRequest) SetMargins(margins [4]float64) { + html.values[marginTop] = fmt.Sprintf("%f", margins[0]) + html.values[marginBottom] = fmt.Sprintf("%f", margins[1]) + html.values[marginLeft] = fmt.Sprintf("%f", margins[2]) + html.values[marginRight] = fmt.Sprintf("%f", margins[3]) +} + +// SetLandscape sets landscape form field. +func (html *HTMLRequest) SetLandscape(isLandscape bool) { + html.values[landscape] = strconv.FormatBool(isLandscape) +} + func (html *HTMLRequest) getPostURL() string { return "/convert/html" } func (html *HTMLRequest) getFormValues() map[string]string { - if html.Options == nil { - html.Options = &HTMLOptions{} - } - values := make(map[string]string) - values[webhookURL] = html.Options.WebHookURL - values[paperWidth] = fmt.Sprintf("%f", html.Options.PaperSize[0]) - values[paperHeight] = fmt.Sprintf("%f", html.Options.PaperSize[1]) - values[marginTop] = fmt.Sprintf("%f", html.Options.PaperMargins[0]) - values[marginBottom] = fmt.Sprintf("%f", html.Options.PaperMargins[1]) - values[marginLeft] = fmt.Sprintf("%f", html.Options.PaperMargins[2]) - values[marginRight] = fmt.Sprintf("%f", html.Options.PaperMargins[3]) - values[landscape] = strconv.FormatBool(html.Options.Landscape) - return values + return html.values } func (html *HTMLRequest) getFormFiles() map[string]string { - if html.Options == nil { - html.Options = &HTMLOptions{} - } files := make(map[string]string) - files["index.html"] = html.IndexFilePath - files["header.html"] = html.Options.HeaderFilePath - files["footer.html"] = html.Options.FooterFilePath - for _, fpath := range html.AssetFilePaths { + files["index.html"] = html.indexFilePath + files["header.html"] = html.headerFilePath + files["footer.html"] = html.footerFilePath + for _, fpath := range html.assetFilePaths { files[filepath.Base(fpath)] = fpath } return files @@ -75,4 +100,5 @@ func (html *HTMLRequest) getFormFiles() map[string]string { // Compile-time checks to ensure type implements desired interfaces. var ( _ = Request(new(HTMLRequest)) + _ = ChromeRequest(new(HTMLRequest)) ) diff --git a/pkg/html_test.go b/pkg/html_test.go index 821a22a2..23cad563 100644 --- a/pkg/html_test.go +++ b/pkg/html_test.go @@ -13,20 +13,21 @@ import ( func TestHTML(t *testing.T) { c := &Client{Hostname: "http://localhost:3000"} - req := &HTMLRequest{ - IndexFilePath: test.HTMLTestFilePath(t, "index.html"), - AssetFilePaths: []string{ - test.HTMLTestFilePath(t, "font.woff"), - test.HTMLTestFilePath(t, "img.gif"), - test.HTMLTestFilePath(t, "style.css"), - }, - Options: &HTMLOptions{ - HeaderFilePath: test.HTMLTestFilePath(t, "header.html"), - FooterFilePath: test.HTMLTestFilePath(t, "footer.html"), - PaperSize: A4, - PaperMargins: NormalMargins, - }, - } + req, err := NewHTMLRequest(test.HTMLTestFilePath(t, "index.html")) + require.Nil(t, err) + err = req.SetHeader(test.HTMLTestFilePath(t, "header.html")) + require.Nil(t, err) + err = req.SetFooter(test.HTMLTestFilePath(t, "footer.html")) + require.Nil(t, err) + err = req.SetAssets([]string{ + test.HTMLTestFilePath(t, "font.woff"), + test.HTMLTestFilePath(t, "img.gif"), + test.HTMLTestFilePath(t, "style.css"), + }) + require.Nil(t, err) + req.SetPaperSize(A4) + req.SetMargins(NormalMargins) + req.SetLandscape(false) dirPath, err := rand.Get() require.Nil(t, err) dest := fmt.Sprintf("%s/foo.pdf", dirPath) diff --git a/pkg/markdown.go b/pkg/markdown.go index 4b615a16..56da4211 100644 --- a/pkg/markdown.go +++ b/pkg/markdown.go @@ -9,73 +9,102 @@ import ( // MarkdownRequest facilitates Markdown conversion // with the Gotenberg API. type MarkdownRequest struct { - IndexFilePath string - MarkdownFilePaths []string - AssetFilePaths []string - Options *MarkdownOptions + indexFilePath string + markdownFilePaths []string + headerFilePath string + footerFilePath string + assetFilePaths []string + values map[string]string } -// MarkdownOptions gathers all options -// for Markdown conversion with the Gotenberg API. -type MarkdownOptions struct { - WebHookURL string - HeaderFilePath string - FooterFilePath string - PaperSize [2]float64 - PaperMargins [4]float64 - Landscape bool -} - -func (markdown *MarkdownRequest) validate() error { - if !fileExists(markdown.IndexFilePath) { - return fmt.Errorf("%s: index file does not exist", markdown.IndexFilePath) +// NewMarkdownRequest create MarkdownRequest. +func NewMarkdownRequest(indexFilePath string, markdownFilePaths []string) (*MarkdownRequest, error) { + if !fileExists(indexFilePath) { + return nil, fmt.Errorf("%s: index file does not exist", indexFilePath) } - if markdown.Options.HeaderFilePath != "" && !fileExists(markdown.Options.HeaderFilePath) { - return fmt.Errorf("%s: header file does not exist", markdown.Options.HeaderFilePath) - } - if markdown.Options.FooterFilePath != "" && !fileExists(markdown.Options.FooterFilePath) { - return fmt.Errorf("%s: footer file does not exist", markdown.Options.FooterFilePath) - } - for _, fpath := range markdown.MarkdownFilePaths { + for _, fpath := range markdownFilePaths { if !fileExists(fpath) { - return fmt.Errorf("%s: markdown file does not exist", fpath) + return nil, fmt.Errorf("%s: markdown file does not exist", fpath) } } + return &MarkdownRequest{ + indexFilePath: indexFilePath, + markdownFilePaths: markdownFilePaths, + values: make(map[string]string), + }, nil +} + +// SetWebhookURL sets webhookURL form field. +func (markdown *MarkdownRequest) SetWebhookURL(webhookURL string) { + markdown.values[webhookURL] = webhookURL +} + +// SetHeader sets header form file. +func (markdown *MarkdownRequest) SetHeader(fpath string) error { + if !fileExists(fpath) { + return fmt.Errorf("%s: header file does not exist", fpath) + } + markdown.headerFilePath = fpath return nil } +// SetFooter sets footer form file. +func (markdown *MarkdownRequest) SetFooter(fpath string) error { + if !fileExists(fpath) { + return fmt.Errorf("%s: footer file does not exist", fpath) + } + markdown.footerFilePath = fpath + return nil +} + +// SetAssets sets assets form files. +func (markdown *MarkdownRequest) SetAssets(fpaths []string) error { + for _, fpath := range fpaths { + if !fileExists(fpath) { + return fmt.Errorf("%s: file does not exist", fpath) + } + } + markdown.assetFilePaths = fpaths + return nil +} + +// SetPaperSize sets paperWidth and paperHeight form fields. +func (markdown *MarkdownRequest) SetPaperSize(size [2]float64) { + markdown.values[paperWidth] = fmt.Sprintf("%f", size[0]) + markdown.values[paperHeight] = fmt.Sprintf("%f", size[1]) +} + +// SetMargins sets marginTop, marginBottom, +// marginLeft and marginRight form fields. +func (markdown *MarkdownRequest) SetMargins(margins [4]float64) { + markdown.values[marginTop] = fmt.Sprintf("%f", margins[0]) + markdown.values[marginBottom] = fmt.Sprintf("%f", margins[1]) + markdown.values[marginLeft] = fmt.Sprintf("%f", margins[2]) + markdown.values[marginRight] = fmt.Sprintf("%f", margins[3]) +} + +// SetLandscape sets landscape form field. +func (markdown *MarkdownRequest) SetLandscape(isLandscape bool) { + markdown.values[landscape] = strconv.FormatBool(isLandscape) +} + func (markdown *MarkdownRequest) getPostURL() string { return "/convert/markdown" } func (markdown *MarkdownRequest) getFormValues() map[string]string { - if markdown.Options == nil { - markdown.Options = &MarkdownOptions{} - } - values := make(map[string]string) - values[webhookURL] = markdown.Options.WebHookURL - values[paperWidth] = fmt.Sprintf("%f", markdown.Options.PaperSize[0]) - values[paperHeight] = fmt.Sprintf("%f", markdown.Options.PaperSize[1]) - values[marginTop] = fmt.Sprintf("%f", markdown.Options.PaperMargins[0]) - values[marginBottom] = fmt.Sprintf("%f", markdown.Options.PaperMargins[1]) - values[marginLeft] = fmt.Sprintf("%f", markdown.Options.PaperMargins[2]) - values[marginRight] = fmt.Sprintf("%f", markdown.Options.PaperMargins[3]) - values[landscape] = strconv.FormatBool(markdown.Options.Landscape) - return values + return markdown.values } func (markdown *MarkdownRequest) getFormFiles() map[string]string { - if markdown.Options == nil { - markdown.Options = &MarkdownOptions{} - } files := make(map[string]string) - files["index.html"] = markdown.IndexFilePath - files["header.html"] = markdown.Options.HeaderFilePath - files["footer.html"] = markdown.Options.FooterFilePath - for _, fpath := range markdown.MarkdownFilePaths { + files["index.html"] = markdown.indexFilePath + files["header.html"] = markdown.headerFilePath + files["footer.html"] = markdown.footerFilePath + for _, fpath := range markdown.markdownFilePaths { files[filepath.Base(fpath)] = fpath } - for _, fpath := range markdown.AssetFilePaths { + for _, fpath := range markdown.assetFilePaths { files[filepath.Base(fpath)] = fpath } return files @@ -84,4 +113,5 @@ func (markdown *MarkdownRequest) getFormFiles() map[string]string { // Compile-time checks to ensure type implements desired interfaces. var ( _ = Request(new(MarkdownRequest)) + _ = ChromeRequest(new(MarkdownRequest)) ) diff --git a/pkg/markdown_test.go b/pkg/markdown_test.go index a232df0e..6a188c18 100644 --- a/pkg/markdown_test.go +++ b/pkg/markdown_test.go @@ -13,25 +13,28 @@ import ( func TestMarkdown(t *testing.T) { c := &Client{Hostname: "http://localhost:3000"} - req := &MarkdownRequest{ - IndexFilePath: test.MarkdownTestFilePath(t, "index.html"), - MarkdownFilePaths: []string{ + req, err := NewMarkdownRequest( + test.MarkdownTestFilePath(t, "index.html"), + []string{ test.MarkdownTestFilePath(t, "paragraph1.md"), test.MarkdownTestFilePath(t, "paragraph2.md"), test.MarkdownTestFilePath(t, "paragraph3.md"), }, - AssetFilePaths: []string{ - test.HTMLTestFilePath(t, "font.woff"), - test.HTMLTestFilePath(t, "img.gif"), - test.HTMLTestFilePath(t, "style.css"), - }, - Options: &MarkdownOptions{ - HeaderFilePath: test.MarkdownTestFilePath(t, "header.html"), - FooterFilePath: test.MarkdownTestFilePath(t, "footer.html"), - PaperSize: A4, - PaperMargins: NormalMargins, - }, - } + ) + require.Nil(t, err) + err = req.SetHeader(test.MarkdownTestFilePath(t, "header.html")) + require.Nil(t, err) + err = req.SetFooter(test.MarkdownTestFilePath(t, "footer.html")) + require.Nil(t, err) + err = req.SetAssets([]string{ + test.MarkdownTestFilePath(t, "font.woff"), + test.MarkdownTestFilePath(t, "img.gif"), + test.MarkdownTestFilePath(t, "style.css"), + }) + require.Nil(t, err) + req.SetPaperSize(A4) + req.SetMargins(NormalMargins) + req.SetLandscape(false) dirPath, err := rand.Get() require.Nil(t, err) dest := fmt.Sprintf("%s/foo.pdf", dirPath) diff --git a/pkg/merge.go b/pkg/merge.go index f4e6690d..f876b479 100644 --- a/pkg/merge.go +++ b/pkg/merge.go @@ -8,24 +8,23 @@ import ( // MergeRequest facilitates merging PDF // with the Gotenberg API. type MergeRequest struct { - FilePaths []string - Options *MergeOptions + filePaths []string + values map[string]string } -// MergeOptions gathers all options -// for merging PDF -// with the Gotenberg API. -type MergeOptions struct { - WebHookURL string -} - -func (merge *MergeRequest) validate() error { - for _, fpath := range merge.FilePaths { +// NewMergeRequest create MergeRequest. +func NewMergeRequest(fpaths []string) (*MergeRequest, error) { + for _, fpath := range fpaths { if !fileExists(fpath) { - return fmt.Errorf("%s: PDF file does not exist", fpath) + return nil, fmt.Errorf("%s: file does not exist", fpath) } } - return nil + return &MergeRequest{filePaths: fpaths, values: make(map[string]string)}, nil +} + +// SetWebhookURL sets webhookURL form field. +func (merge *MergeRequest) SetWebhookURL(webhookURL string) { + merge.values[webhookURL] = webhookURL } func (merge *MergeRequest) getPostURL() string { @@ -33,17 +32,12 @@ func (merge *MergeRequest) getPostURL() string { } func (merge *MergeRequest) getFormValues() map[string]string { - if merge.Options == nil { - merge.Options = &MergeOptions{} - } - values := make(map[string]string) - values[webhookURL] = merge.Options.WebHookURL - return values + return merge.values } func (merge *MergeRequest) getFormFiles() map[string]string { files := make(map[string]string) - for _, fpath := range merge.FilePaths { + for _, fpath := range merge.filePaths { files[filepath.Base(fpath)] = fpath } return files diff --git a/pkg/merge_test.go b/pkg/merge_test.go index 752357d8..a7221e56 100644 --- a/pkg/merge_test.go +++ b/pkg/merge_test.go @@ -13,12 +13,11 @@ import ( func TestMerge(t *testing.T) { c := &Client{Hostname: "http://localhost:3000"} - req := &MergeRequest{ - FilePaths: []string{ - test.PDFTestFilePath(t, "gotenberg.pdf"), - test.PDFTestFilePath(t, "gotenberg.pdf"), - }, - } + req, err := NewMergeRequest([]string{ + test.PDFTestFilePath(t, "gotenberg.pdf"), + test.PDFTestFilePath(t, "gotenberg.pdf"), + }) + require.Nil(t, err) dirPath, err := rand.Get() require.Nil(t, err) dest := fmt.Sprintf("%s/foo.pdf", dirPath) diff --git a/pkg/office.go b/pkg/office.go index f2f183b0..06a19079 100644 --- a/pkg/office.go +++ b/pkg/office.go @@ -3,29 +3,40 @@ package gotenberg import ( "fmt" "path/filepath" + "strconv" ) // OfficeRequest facilitates Office documents // conversion with the Gotenberg API. type OfficeRequest struct { - FilePaths []string - Options *OfficeOptions + filePaths []string + values map[string]string } -// OfficeOptions gathers all options -// for Office documents conversion -// with the Gotenberg API. -type OfficeOptions struct { - WebHookURL string -} - -func (office *OfficeRequest) validate() error { - for _, fpath := range office.FilePaths { +// NewOfficeRequest create OfficeRequest. +func NewOfficeRequest(fpaths []string) (*OfficeRequest, error) { + for _, fpath := range fpaths { if !fileExists(fpath) { - return fmt.Errorf("%s: office file does not exist", fpath) + return nil, fmt.Errorf("%s: file does not exist", fpath) } } - return nil + return &OfficeRequest{filePaths: fpaths, values: make(map[string]string)}, nil +} + +// SetWebhookURL sets webhookURL form field. +func (office *OfficeRequest) SetWebhookURL(webhookURL string) { + office.values[webhookURL] = webhookURL +} + +// SetPaperSize sets paperWidth and paperHeight form fields. +func (office *OfficeRequest) SetPaperSize(size [2]float64) { + office.values[paperWidth] = fmt.Sprintf("%f", size[0]) + office.values[paperHeight] = fmt.Sprintf("%f", size[1]) +} + +// SetLandscape sets landscape form field. +func (office *OfficeRequest) SetLandscape(isLandscape bool) { + office.values[landscape] = strconv.FormatBool(isLandscape) } func (office *OfficeRequest) getPostURL() string { @@ -33,17 +44,12 @@ func (office *OfficeRequest) getPostURL() string { } func (office *OfficeRequest) getFormValues() map[string]string { - if office.Options == nil { - office.Options = &OfficeOptions{} - } - values := make(map[string]string) - values[webhookURL] = office.Options.WebHookURL - return values + return office.values } func (office *OfficeRequest) getFormFiles() map[string]string { files := make(map[string]string) - for _, fpath := range office.FilePaths { + for _, fpath := range office.filePaths { files[filepath.Base(fpath)] = fpath } return files @@ -52,4 +58,5 @@ func (office *OfficeRequest) getFormFiles() map[string]string { // Compile-time checks to ensure type implements desired interfaces. var ( _ = Request(new(OfficeRequest)) + _ = UnoconvRequest(new(OfficeRequest)) ) diff --git a/pkg/office_test.go b/pkg/office_test.go index db80d857..e1b276b9 100644 --- a/pkg/office_test.go +++ b/pkg/office_test.go @@ -13,11 +13,12 @@ import ( func TestOffice(t *testing.T) { c := &Client{Hostname: "http://localhost:3000"} - req := &OfficeRequest{ - FilePaths: []string{ - test.OfficeTestFilePath(t, "document.docx"), - }, - } + req, err := NewOfficeRequest([]string{ + test.OfficeTestFilePath(t, "document.docx"), + }) + require.Nil(t, err) + req.SetPaperSize(A4) + req.SetLandscape(false) dirPath, err := rand.Get() require.Nil(t, err) dest := fmt.Sprintf("%s/foo.pdf", dirPath)