adrg / go-wkhtmltopdf

Handcrafted Go bindings for wkhtmltopdf and high-level HTML to PDF conversion interface
https://pkg.go.dev/github.com/adrg/go-wkhtmltopdf
MIT License
230 stars 21 forks source link

Creating PDF files in a loop #38

Open zaky opened 1 year ago

zaky commented 1 year ago

func main() { for _, v := range []string{"import1.pdf", "import2.pdf", "import3.pdf"} { CreatePDFFile("header.html", "footer.html", "content.html", v) } }

func CreatePDFFile(header, footer, content, output string) error { // Initialize library. if err := pdf.Init(); err != nil { log.Print("fail init", err)

    return err
}
defer pdf.Destroy()
//Insert the content inside the template
// Create object from URL.
object, err := pdf.NewObject(content)
if err != nil {
    log.Print("fail new object", err)
    return err
}
defer object.Destroy()
object.Header.CustomLocation = header
object.Footer.CustomLocation = footer
converter, err = pdf.NewConverter()
if err != nil {
    log.Print("fail new converter", err)
    return err
}

defer converter.Destroy()

converter.PaperSize = pdf.Letter

// Add created objects to the converter.
converter.Add(object)

// Convert objects and save the output PDF document.
outFile, err := os.Create(output)
if err != nil {
    log.Print("fail create", err)
    return err
}
defer outFile.Close()

// Run converter.
if err := converter.Run(outFile); err != nil {
    log.Print("fail run", err)
    return err
}
return nil

}

Runing this code will create 1 valid file, the first one and two invalid files.

adrg commented 1 year ago

Hi @zaky

The library needs to be initialized only once, when the application starts. This should work fine:

package main

import (
    "log"
    "os"

    pdf "github.com/adrg/go-wkhtmltopdf"
)

func main() {
    // Initialize library.
    if err := pdf.Init(); err != nil {
        log.Fatal(err)
    }
    defer pdf.Destroy()

    for _, v := range []string{"import1.pdf", "import2.pdf", "import3.pdf"} {
        if err := CreatePDFFile("header.html", "footer.html", "content.html", v); err != nil {
            log.Fatal(err)
        }
    }
}

func CreatePDFFile(header, footer, content, output string) error {
    // Create object from URLs.
    object, err := pdf.NewObject(content)
    if err != nil {
        return err
    }
    defer object.Destroy()

    object.Header.CustomLocation = header
    object.Footer.CustomLocation = footer

    // Create converter.
    converter, err := pdf.NewConverter()
    if err != nil {
        return err
    }
    defer converter.Destroy()

    converter.PaperSize = pdf.Letter

    // Add created objects to the converter.
    converter.Add(object)

    // Convert objects and save the output PDF document.
    outFile, err := os.Create(output)
    if err != nil {
        return err
    }
    defer outFile.Close()

    // Run converter.
    if err := converter.Run(outFile); err != nil {
        return err
    }

    return nil
}
zaky commented 1 year ago

Actually the code is part of a web server. What I fount is that if I put the pdf.init/pdf.destroy in main the first request is working fine but not the following requests. From the second request the server jut stock.

adrg commented 1 year ago

For usage inside goroutines see the Basic web page to PDF conversion server or the Configurable web page to PDF conversion server examples. Issue https://github.com/adrg/go-wkhtmltopdf/issues/3 is also relevant.

The library must be initialized on the main thread and the conversion must be performed on the main thread as well. This is a known limitation of wkhtmltox, not of this package.

zbykovskyi commented 8 months ago

I suppose, as mentioned in previous comment, this code block

func init() {
    // Set main function to run on the main thread.
    runtime.LockOSThread()
}

in general cases prevents situation with library stuck