| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 | // Package wkhtmltopdf contains wrappers around the wkhtmltopdf commandline toolpackage wkhtmltopdfimport (	"bytes"	"errors"	"fmt"	"io"	"io/ioutil"	"os"	"os/exec"	"path/filepath"	"strings")var binPath string //the cached paths as used by findPath()// SetPath sets the path to wkhtmltopdffunc SetPath(path string) {	binPath = path}// GetPath gets the path to wkhtmltopdffunc GetPath() string {	return binPath}// Page is the input struct for each pagetype Page struct {	Input string	PageOptions}// InputFile returns the input string and is part of the page interfacefunc (p *Page) InputFile() string {	return p.Input}// Args returns the argument slice and is part of the page interfacefunc (p *Page) Args() []string {	return p.PageOptions.Args()}// Reader returns the io.Reader and is part of the page interfacefunc (p *Page) Reader() io.Reader {	return nil}// NewPage creates a new input page from a local or web resource (filepath or URL)func NewPage(input string) *Page {	return &Page{		Input:       input,		PageOptions: NewPageOptions(),	}}// PageReader is one input page (a HTML document) that is read from an io.Reader// You can add only one Page from a readertype PageReader struct {	Input io.Reader	PageOptions}// InputFile returns the input string and is part of the page interfacefunc (pr *PageReader) InputFile() string {	return "-"}// Args returns the argument slice and is part of the page interfacefunc (pr *PageReader) Args() []string {	return pr.PageOptions.Args()}//Reader returns the io.Reader and is part of the page interfacefunc (pr *PageReader) Reader() io.Reader {	return pr.Input}// NewPageReader creates a new PageReader from an io.Readerfunc NewPageReader(input io.Reader) *PageReader {	return &PageReader{		Input:       input,		PageOptions: NewPageOptions(),	}}type page interface {	Args() []string	InputFile() string	Reader() io.Reader}// PageOptions are options for each input pagetype PageOptions struct {	pageOptions	headerAndFooterOptions}// Args returns the argument slicefunc (po *PageOptions) Args() []string {	return append(append([]string{}, po.pageOptions.Args()...), po.headerAndFooterOptions.Args()...)}// NewPageOptions returns a new PageOptions struct with all optionsfunc NewPageOptions() PageOptions {	return PageOptions{		pageOptions:            newPageOptions(),		headerAndFooterOptions: newHeaderAndFooterOptions(),	}}// cover pagetype cover struct {	Input string	pageOptions}// table of contentstype toc struct {	Include bool	allTocOptions}type allTocOptions struct {	pageOptions	tocOptions}// PDFGenerator is the main wkhtmltopdf struct, always use NewPDFGenerator to obtain a new PDFGenerator structtype PDFGenerator struct {	globalOptions	outlineOptions	Cover      cover	TOC        toc	OutputFile string //filename to write to, default empty (writes to internal buffer)	binPath string	outbuf  bytes.Buffer	pages   []page}//Args returns the commandline arguments as a string slicefunc (pdfg *PDFGenerator) Args() []string {	args := []string{}	args = append(args, pdfg.globalOptions.Args()...)	args = append(args, pdfg.outlineOptions.Args()...)	if pdfg.Cover.Input != "" {		args = append(args, "cover")		args = append(args, pdfg.Cover.Input)		args = append(args, pdfg.Cover.pageOptions.Args()...)	}	if pdfg.TOC.Include {		args = append(args, "toc")		args = append(args, pdfg.TOC.pageOptions.Args()...)		args = append(args, pdfg.TOC.tocOptions.Args()...)	}	for _, page := range pdfg.pages {		args = append(args, "page")		args = append(args, page.InputFile())		args = append(args, page.Args()...)	}	if pdfg.OutputFile != "" {		args = append(args, pdfg.OutputFile)	} else {		args = append(args, "-")	}	return args}// ArgString returns Args as a single stringfunc (pdfg *PDFGenerator) ArgString() string {	return strings.Join(pdfg.Args(), " ")}// AddPage adds a new input page to the document.// A page is an input HTML page, it can span multiple pages in the output document.// It is a Page when read from file or URL or a PageReader when read from memory.func (pdfg *PDFGenerator) AddPage(p page) {	pdfg.pages = append(pdfg.pages, p)}// SetPages resets all pagesfunc (pdfg *PDFGenerator) SetPages(p []page) {	pdfg.pages = p}// Buffer returns the embedded output buffer used if OutputFile is emptyfunc (pdfg *PDFGenerator) Buffer() *bytes.Buffer {	return &pdfg.outbuf}// Bytes returns the output byte slice from the output buffer used if OutputFile is emptyfunc (pdfg *PDFGenerator) Bytes() []byte {	return pdfg.outbuf.Bytes()}// WriteFile writes the contents of the output buffer to a filefunc (pdfg *PDFGenerator) WriteFile(filename string) error {	return ioutil.WriteFile(filename, pdfg.Bytes(), 0666)}//findPath finds the path to wkhtmltopdf by//- first looking in the current dir//- looking in the PATH and PATHEXT environment dirs//- using the WKHTMLTOPDF_PATH environment dir//The path is cached, meaning you can not change the location of wkhtmltopdf in//a running program once it has been foundfunc (pdfg *PDFGenerator) findPath() error {	const exe = "wkhtmltopdf"	if binPath != "" {		pdfg.binPath = binPath		return nil	}	exeDir, err := filepath.Abs(filepath.Dir(os.Args[0]))	if err != nil {		return err	}	path, err := exec.LookPath(filepath.Join(exeDir, exe))	if err == nil && path != "" {		binPath = path		pdfg.binPath = path		return nil	}	path, err = exec.LookPath(exe)	if err == nil && path != "" {		binPath = path		pdfg.binPath = path		return nil	}	dir := os.Getenv("WKHTMLTOPDF_PATH")	if dir == "" {		return fmt.Errorf("%s not found", exe)	}	path, err = exec.LookPath(filepath.Join(dir, exe))	if err == nil && path != "" {		binPath = path		pdfg.binPath = path		return nil	}	return fmt.Errorf("%s not found", exe)}// Create creates the PDF document and stores it in the internal buffer if no error is returnedfunc (pdfg *PDFGenerator) Create() error {	return pdfg.run()}func (pdfg *PDFGenerator) run() error {	errbuf := &bytes.Buffer{}	cmd := exec.Command(pdfg.binPath, pdfg.Args()...)	cmd.Stdout = &pdfg.outbuf	cmd.Stderr = errbuf	//if there is a pageReader page (from Stdin) we set Stdin to that reader	for _, page := range pdfg.pages {		if page.Reader() != nil {			cmd.Stdin = page.Reader()			break		}	}	err := cmd.Run()	if err != nil {		errStr := errbuf.String()		if strings.TrimSpace(errStr) == "" {			errStr = err.Error()		}		return errors.New(errStr)	}	return nil}// NewPDFGenerator returns a new PDFGenerator struct with all options created and// checks if wkhtmltopdf can be found on the systemfunc NewPDFGenerator() (*PDFGenerator, error) {	pdfg := &PDFGenerator{		globalOptions:  newGlobalOptions(),		outlineOptions: newOutlineOptions(),		Cover: cover{			pageOptions: newPageOptions(),		},		TOC: toc{			allTocOptions: allTocOptions{				tocOptions:  newTocOptions(),				pageOptions: newPageOptions(),			},		},	}	err := pdfg.findPath()	return pdfg, err}
 |