From b4edb9481b8d1cb572e1ac2cfcf058619950ce05 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Sun, 2 Dec 2018 11:37:45 +0100 Subject: [PATCH] add functions to export page or svg to pdf and png formats (#22) --- chrome.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ export.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 export.go diff --git a/chrome.go b/chrome.go index 9f5f5b4..7a9362b 100644 --- a/chrome.go +++ b/chrome.go @@ -438,6 +438,61 @@ func (c *chrome) bounds() (Bounds, error) { return bounds.Bounds, err } +func (c *chrome) pdf(width, height int) ([]byte, error) { + result, err := c.send("Page.printToPDF", h{ + "paperWidth": float32(width) / 96, + "paperHeight": float32(height) / 96, + }) + if err != nil { + return nil, err + } + pdf := struct { + Data []byte `json:"data"` + }{} + err = json.Unmarshal(result, &pdf) + return pdf.Data, err +} + +func (c *chrome) png(x, y, width, height int, bg uint32, scale float32) ([]byte, error) { + if x == 0 && y == 0 && width == 0 && height == 0 { + // By default either use SVG size if it's an SVG, or use A4 page size + bounds, err := c.eval(`document.rootElement ? [document.rootElement.x.baseVal.value, document.rootElement.y.baseVal.value, document.rootElement.width.baseVal.value, document.rootElement.height.baseVal.value] : [0,0,816,1056]`) + if err != nil { + return nil, err + } + rect := make([]int, 4) + if err := json.Unmarshal(bounds, &rect); err != nil { + return nil, err + } + x, y, width, height = rect[0], rect[1], rect[2], rect[3] + } + + _, err := c.send("Emulation.setDefaultBackgroundColorOverride", h{ + "color": h{ + "r": (bg >> 16) & 0xff, + "g": (bg >> 8) & 0xff, + "b": bg & 0xff, + "a": (bg >> 24) & 0xff, + }, + }) + if err != nil { + return nil, err + } + result, err := c.send("Page.captureScreenshot", h{ + "clip": h{ + "x": x, "y": y, "width": width, "height": height, "scale": scale, + }, + }) + if err != nil { + return nil, err + } + pdf := struct { + Data []byte `json:"data"` + }{} + err = json.Unmarshal(result, &pdf) + return pdf.Data, err +} + func (c *chrome) kill() error { if c.ws != nil { if err := c.ws.Close(); err != nil { diff --git a/export.go b/export.go new file mode 100644 index 0000000..b0b2178 --- /dev/null +++ b/export.go @@ -0,0 +1,56 @@ +package lorca + +import ( + "fmt" + "io/ioutil" + "os" +) + +const ( + // PageA4Width is a width of an A4 page in pixels at 96dpi + PageA4Width = 816 + // PageA4Height is a height of an A4 page in pixels at 96dpi + PageA4Height = 1056 +) + +// PDF converts a given URL (may be a local file) to a PDF file. Script is +// evaluated before the page is printed to PDF, you may modify the contents of +// the page there of wait until the page is fully rendered. Width and height +// are page bounds in pixels. PDF by default uses 96dpi density. For A4 page +// you may use PageA4Width and PageA4Height constants. +func PDF(url, script string, width, height int) ([]byte, error) { + return doHeadless(url, func(c *chrome) ([]byte, error) { + c.eval(script) + return c.pdf(width, height) + }) +} + +// PNG converts a given URL (may be a local file) to a PNG image. Script is +// evaluated before the "screenshot" is taken, so you can modify the contents +// of a URL there. Image bounds are provides in pixels. Background is in ARGB +// format, the default value of zero keeps the background transparent. Scale +// allows zooming the page in and out. +// +// This function is most convenient to convert SVG to PNG of different sizes, +// for example when preparing Lorca app icons. +func PNG(url, script string, x, y, width, height int, bg uint32, scale float32) ([]byte, error) { + return doHeadless(url, func(c *chrome) ([]byte, error) { + c.eval(script) + return c.png(x, y, width, height, bg, scale) + }) +} + +func doHeadless(url string, f func(c *chrome) ([]byte, error)) ([]byte, error) { + dir, err := ioutil.TempDir("", "lorca") + if err != nil { + return nil, err + } + defer os.RemoveAll(dir) + args := append(defaultChromeArgs, fmt.Sprintf("--user-data-dir=%s", dir), "--remote-debugging-port=0", "--headless", url) + chrome, err := newChromeWithArgs(ChromeExecutable(), args...) + if err != nil { + return nil, err + } + defer chrome.kill() + return f(chrome) +}