diff --git a/.gitignore b/.gitignore index 9e35549..83b1ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pdf assets -dst \ No newline at end of file +dst +*.zip \ No newline at end of file diff --git a/converter/converter.go b/converter/converter.go index 0eabf11..3191219 100644 --- a/converter/converter.go +++ b/converter/converter.go @@ -1,6 +1,8 @@ package converter import ( + "archive/zip" + "bytes" "context" "errors" "fmt" @@ -63,7 +65,7 @@ func (c *Converter) SetProgressCallback(cb func(progress int)) { } // Convert converts all given input files -func (c *Converter) Convert(files ...models.File) error { +func (c *Converter) ConvertToPdf(files ...models.File) error { for i, f := range files { if c.progress != nil { c.progress(i + 1) @@ -127,7 +129,7 @@ func (c *Converter) Convert(files ...models.File) error { } // Convert converts all given input files -func (c *Converter) ConvertHtmls(htmls ...models.Html) error { +func (c *Converter) ConvertHtmlsToPdf(htmls ...models.Html) error { for i, h := range htmls { if c.progress != nil { c.progress(i + 1) @@ -183,6 +185,126 @@ func (c *Converter) ConvertHtmls(htmls ...models.Html) error { return nil } +// Convert converts all given input files +func (c *Converter) ConvertHtmlsToBytes(htmls ...models.Html) ([][]byte, error) { + var output [][]byte + for i, h := range htmls { + if c.progress != nil { + c.progress(i + 1) + } + if len(h.Html) == 0 { + return nil, fmt.Errorf("no .html input provided") + } else if h.Output == "" || filepath.Ext(h.Output) != ".pdf" { + return nil, fmt.Errorf("no .pdf output file path provided: %s", h.Output) + } + + taskCtx, taskCancel := chromedp.NewContext(c.browserCtx) + timeoutCtx, timeoutCancel := context.WithTimeout(taskCtx, 60*time.Second) + + var pdfData []byte + err := chromedp.Run(timeoutCtx, + // Start with a blank page + chromedp.Navigate("about:blank"), + // Inject HTML directly + chromedp.ActionFunc(func(ctx context.Context) error { + frameTree, err := page.GetFrameTree().Do(ctx) + if err != nil { + return err + } + + return page.SetDocumentContent(frameTree.Frame.ID, string(h.Html)).Do(ctx) + }), + chromedp.WaitReady("body", chromedp.ByQuery), + chromedp.ActionFunc(func(ctx context.Context) error { + buf, _, err := page.PrintToPDF(). + WithPrintBackground(true). + WithPaperWidth(8.27). + WithPaperHeight(11.69). + Do(ctx) + if err != nil { + return err + } + pdfData = buf + return nil + }), + ) + + timeoutCancel() + taskCancel() + if err != nil { + return nil, err + } + + // add to array of bytes + output = append(output, pdfData) + } + return output, nil +} + +// Convert html to pdf and return zip as bytes +func (c *Converter) ConvertHtmlsToZip(htmls ...models.Html) ([]byte, error) { + zipBuf := new(bytes.Buffer) + zipWriter := zip.NewWriter(zipBuf) + + for i, h := range htmls { + if c.progress != nil { + c.progress(i + 1) + } + if len(h.Html) == 0 { + return nil, fmt.Errorf("no .html input provided") + } else if h.Output == "" || filepath.Ext(h.Output) != ".pdf" { + return nil, fmt.Errorf("no .pdf output file path provided: %s", h.Output) + } + + taskCtx, taskCancel := chromedp.NewContext(c.browserCtx) + timeoutCtx, timeoutCancel := context.WithTimeout(taskCtx, 60*time.Second) + + var pdfData []byte + err := chromedp.Run(timeoutCtx, + // Start with a blank page + chromedp.Navigate("about:blank"), + // Inject HTML directly + chromedp.ActionFunc(func(ctx context.Context) error { + frameTree, err := page.GetFrameTree().Do(ctx) + if err != nil { + return err + } + + return page.SetDocumentContent(frameTree.Frame.ID, string(h.Html)).Do(ctx) + }), + chromedp.WaitReady("body", chromedp.ByQuery), + chromedp.ActionFunc(func(ctx context.Context) error { + buf, _, err := page.PrintToPDF(). + WithPrintBackground(true). + WithPaperWidth(8.27). + WithPaperHeight(11.69). + Do(ctx) + if err != nil { + return err + } + pdfData = buf + return nil + }), + ) + + timeoutCancel() + taskCancel() + if err != nil { + return nil, err + } + + // add to zip + f, err := zipWriter.Create(h.Output) + if err != nil { + return nil, err + } + f.Write(pdfData) + } + zipWriter.Close() + + return zipBuf.Bytes(), nil +} + func (c *Converter) Close() { // Close browser first, then allocator if c.browserCancel != nil { diff --git a/html2pdf.go b/html2pdf.go index bcdb10b..9434a6b 100644 --- a/html2pdf.go +++ b/html2pdf.go @@ -17,7 +17,7 @@ func Convert(chromePath, inputFile, outputFile string) error { } defer c.Close() - return c.Convert(input) + return c.ConvertToPdf(input) } // NewConverterInstance start new chrome headless shell instance diff --git a/html2pdf_test.go b/html2pdf_test.go index ff520b2..32f5ef7 100644 --- a/html2pdf_test.go +++ b/html2pdf_test.go @@ -53,7 +53,7 @@ func TestConvertFiles(t *testing.T) { fmt.Println(progress) }) - if err := c.Convert(input...); err != nil { + if err := c.ConvertToPdf(input...); err != nil { t.Fatal(err) } t.Log("test successfull") @@ -94,7 +94,7 @@ func TestConvertHtml(t *testing.T) { } defer c.Close() - err = c.ConvertHtmls(models.Html{Html: html, Output: outputPath}) + err = c.ConvertHtmlsToPdf(models.Html{Html: html, Output: outputPath}) if err != nil { t.Fatalf("ConvertHtml failed: %v", err) } @@ -117,3 +117,71 @@ func TestConvertHtml(t *testing.T) { t.Log("ConvertHtml test successful") } + +func TestConvertHtmlsToPDFZip(t *testing.T) { + t.Log("start test ConvertHtml") + + // Skip in short mode (useful for CI) + if testing.Short() { + t.Skip("skipping ConvertHtml integration test in short mode") + } + + html := []byte(` + + +
+ +This is a test.
+ + +`) + + outputPath := filepath.Join("test.pdf") + + c, err := converter.NewConverter("assets/chrome-headless-shell/win64") + if err != nil { + t.Fatal(err) + } + defer c.Close() + + b, err := c.ConvertHtmlsToZip(models.Html{Html: html, Output: outputPath}) + if err != nil { + t.Fatalf("ConvertHtml failed: %v", err) + } + + f, err := os.Create("test.zip") + if err != nil { + t.Fatalf("create file: %v", err) + } + defer f.Close() + _, err = f.Write(b) + if err != nil { + t.Fatalf("write to file: %v", err) + } + + // // Assert PDF exists + // data, err := os.ReadFile(outputPath) + // if err != nil { + // t.Fatalf("PDF not created: %v", err) + // } + + // // Assert non-empty + // if len(data) == 0 { + // t.Fatal("PDF file is empty") + // } + + // // Assert valid PDF header + // if !bytes.HasPrefix(data, []byte("%PDF-")) { + // t.Fatalf("output is not a valid PDF (missing %%PDF- header)") + // } + + t.Log("ConvertHtml test successful") +}