如何将散景图的html输出转换为pdf

时间:2018-08-09 09:23:37

标签: jinja2 bokeh google-colaboratory weasyprint

我已经在Google colab中使用bokeh创建了一个图形,并希望将其嵌入PDF(作为jinja2模板的一部分)。 Jinja2 html输出显示为pdf,但散景图除外。

当我尝试仅转换散景图时,我只会得到空白页:

from weasyprint import HTML

HTML('bokeh-test.html').write_pdf("report3.pdf")

有没有一种方法可以使Weasyprint与Bokeh的html输出配合使用? 我使用的是google-colab,因此我可以安装的内容受到限制。 我使用https://www.sejda.com/html-to-pdf将html转换为pdf,并显示了图形。

散景图html输出:

<!DOCTYPE html>
<html lang="en">
  
  <head>
    
      <meta charset="utf-8">
      <title>Bokeh Plot</title>
      
      
        
          
        <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.css" type="text/css" />
        
        
          
        <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.js"></script>
        <script type="text/javascript">
            Bokeh.set_log_level("info");
        </script>
        
      
      
    
  </head>
  
  
  <body>
    
      
        
          
          
            <div class="bk-root" id="ef113f06-aa36-4375-be30-7e11ddafa77a"></div>
          
        
      
      
        <script type="application/json" id="4ee5c914-fee4-4720-8c61-acaf6a1f5e0b">
          {"57a9d4e3-e61b-42d1-9e38-2d3437421187":{"roots":{"references":[{"attributes":{"bottom_units":"screen","fill_alpha":{"value":0.5},"fill_color":{"value":"lightgrey"},"left_units":"screen","level":"overlay","line_alpha":{"value":1.0},"line_color":{"value":"black"},"line_dash":[4,4],"line_width":{"value":2},"plot":null,"render_mode":"css","right_units":"screen","top_units":"screen"},"id":"517c2134-ed27-444c-915e-3ffb477d5f1c","type":"BoxAnnotation"},{"attributes":{"data_source":{"id":"5431ac6b-2caf-4a54-b371-e853e6bd7a1f","type":"ColumnDataSource"},"glyph":{"id":"7d8c79a4-27fd-4fee-ba41-e68b7308b250","type":"VBar"},"hover_glyph":null,"muted_glyph":null,"nonselection_glyph":{"id":"04df65f3-0a25-4a1f-b75b-70d459e720eb","type":"VBar"},"selection_glyph":null,"view":{"id":"f7508f8e-52c6-4a67-864a-e1d20de6373d","type":"CDSView"}},"id":"178d9ac7-fe8f-4c12-ae2b-27c0630e9bfe","type":"GlyphRenderer"},{"attributes":{"overlay":{"id":"517c2134-ed27-444c-915e-3ffb477d5f1c","type":"BoxAnnotation"}},"id":"59417e60-4616-4ceb-8cb8-0a2e8215b228","type":"BoxZoomTool"},{"attributes":{},"id":"ab2cfea9-982a-470d-a222-7b2c43be98f3","type":"CategoricalScale"},{"attributes":{"callback":null},"id":"41aa3083-6fba-495e-b0e0-5201619117c8","type":"DataRange1d"},{"attributes":{"plot":{"id":"68a2a747-e658-4f80-bc64-925e7307eafe","subtype":"Figure","type":"Plot"},"ticker":{"id":"935d6bf7-1198-4252-9ed2-6e479bd5ef62","type":"CategoricalTicker"}},"id":"b8e5a61d-76cf-4647-87d0-a6bdfc08e31c","type":"Grid"},{"attributes":{},"id":"6941c212-764a-4b31-8412-11e0b3262395","type":"CategoricalTickFormatter"},{"attributes":{},"id":"9e3d4322-4d16-4912-b0ad-79611d031253","type":"BasicTickFormatter"},{"attributes":{"callback":null,"factors":["2017-39","2017-40","2017-41","2017-42","2017-43","2017-44","2017-45","2017-46","2017-47","2017-48","2017-49","2017-50","2017-51","2017-52","2018-01","2018-02","2018-03","2018-04","2018-05","2018-06","2018-07","2018-08","2018-09","2018-10","2018-11","2018-12","2018-13","2018-14","2018-15","2018-16","2018-17","2018-18","2018-19","2018-20","2018-21","2018-22"]},"id":"d8e79791-ca6d-4f6c-ae30-d06e808dad55","type":"FactorRange"},{"attributes":{"fill_alpha":{"value":0.1},"fill_color":{"value":"#1f77b4"},"line_alpha":{"value":0.1},"line_color":{"value":"#1f77b4"},"top":{"field":"duration"},"width":{"value":0.8},"x":{"field":"index"}},"id":"04df65f3-0a25-4a1f-b75b-70d459e720eb","type":"VBar"},{"attributes":{"plot":null,"text":"Hours driven each week","text_font_size":{"value":"18pt"}},"id":"47d89887-fdc8-491c-af53-50acfc4d7385","type":"Title"},{"attributes":{"axis_label":"Hours","formatter":{"id":"9e3d4322-4d16-4912-b0ad-79611d031253","type":"BasicTickFormatter"},"plot":{"id":"68a2a747-e658-4f80-bc64-925e7307eafe","subtype":"Figure","type":"Plot"},"ticker":{"id":"0b715b2c-d93a-4787-a350-b9d270f0bd01","type":"BasicTicker"}},"id":"e70ed347-e9ca-4595-8959-9611cc88c2a3","type":"LinearAxis"},{"attributes":{"callback":null,"data":{"duration":{"__ndarray__":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAzczMzMzMF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERERERERAJA3t3d3d3dKUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","dtype":"float64","shape":[36]},"index":["2017-39","2017-40","2017-41","2017-42","2017-43","2017-44","2017-45","2017-46","2017-47","2017-48","2017-49","2017-50","2017-51","2017-52","2018-01","2018-02","2018-03","2018-04","2018-05","2018-06","2018-07","2018-08","2018-09","2018-10","2018-11","2018-12","2018-13","2018-14","2018-15","2018-16","2018-17","2018-18","2018-19","2018-20","2018-21","2018-22"]},"selected":{"id":"a5b6187f-ffd1-43eb-92ca-2ed60bddcce5","type":"Selection"},"selection_policy":{"id":"c11dc4a7-7148-407e-90ac-86c53b64e997","type":"UnionRenderers"}},"id":"5431ac6b-2caf-4a54-b371-e853e6bd7a1f","type":"ColumnDataSource"},{"attributes":{},"id":"01c941d4-a31f-403a-9ba9-fc35388abc24","type":"WheelZoomTool"},{"attributes":{"below":[{"id":"991d44e6-2849-4b16-8b3c-67ce9f05b42c","type":"CategoricalAxis"}],"left":[{"id":"e70ed347-e9ca-4595-8959-9611cc88c2a3","type":"LinearAxis"}],"plot_height":400,"plot_width":1000,"renderers":[{"id":"991d44e6-2849-4b16-8b3c-67ce9f05b42c","type":"CategoricalAxis"},{"id":"b8e5a61d-76cf-4647-87d0-a6bdfc08e31c","type":"Grid"},{"id":"e70ed347-e9ca-4595-8959-9611cc88c2a3","type":"LinearAxis"},{"id":"00ddf32c-f48f-4cc5-ae77-64c914eb7fc5","type":"Grid"},{"id":"517c2134-ed27-444c-915e-3ffb477d5f1c","type":"BoxAnnotation"},{"id":"178d9ac7-fe8f-4c12-ae2b-27c0630e9bfe","type":"GlyphRenderer"}],"title":{"id":"47d89887-fdc8-491c-af53-50acfc4d7385","type":"Title"},"toolbar":{"id":"d7d3a411-b28c-4f3f-9c99-144f1151b83f","type":"Toolbar"},"x_range":{"id":"d8e79791-ca6d-4f6c-ae30-d06e808dad55","type":"FactorRange"},"x_scale":{"id":"ab2cfea9-982a-470d-a222-7b2c43be98f3","type":"CategoricalScale"},"y_range":{"id":"41aa3083-6fba-495e-b0e0-5201619117c8","type":"DataRange1d"},"y_scale":{"id":"40cda5bc-3a56-4c31-8fd8-14fc3c5e1880","type":"LinearScale"}},"id":"68a2a747-e658-4f80-bc64-925e7307eafe","subtype":"Figure","type":"Plot"},{"attributes":{},"id":"5de2c2ac-a957-4b3e-a38a-af1af0153811","type":"HelpTool"},{"attributes":{},"id":"a5b6187f-ffd1-43eb-92ca-2ed60bddcce5","type":"Selection"},{"attributes":{},"id":"c11dc4a7-7148-407e-90ac-86c53b64e997","type":"UnionRenderers"},{"attributes":{},"id":"935d6bf7-1198-4252-9ed2-6e479bd5ef62","type":"CategoricalTicker"},{"attributes":{"fill_color":{"value":"#1f77b4"},"line_color":{"value":"#1f77b4"},"top":{"field":"duration"},"width":{"value":0.8},"x":{"field":"index"}},"id":"7d8c79a4-27fd-4fee-ba41-e68b7308b250","type":"VBar"},{"attributes":{"source":{"id":"5431ac6b-2caf-4a54-b371-e853e6bd7a1f","type":"ColumnDataSource"}},"id":"f7508f8e-52c6-4a67-864a-e1d20de6373d","type":"CDSView"},{"attributes":{"axis_label":"Date","formatter":{"id":"6941c212-764a-4b31-8412-11e0b3262395","type":"CategoricalTickFormatter"},"major_label_orientation":45,"plot":{"id":"68a2a747-e658-4f80-bc64-925e7307eafe","subtype":"Figure","type":"Plot"},"ticker":{"id":"935d6bf7-1198-4252-9ed2-6e479bd5ef62","type":"CategoricalTicker"}},"id":"991d44e6-2849-4b16-8b3c-67ce9f05b42c","type":"CategoricalAxis"},{"attributes":{},"id":"e63ed713-5f57-4972-b2f1-b47e836113be","type":"PanTool"},{"attributes":{},"id":"40cda5bc-3a56-4c31-8fd8-14fc3c5e1880","type":"LinearScale"},{"attributes":{"active_drag":"auto","active_inspect":"auto","active_multi":null,"active_scroll":"auto","active_tap":"auto","tools":[{"id":"e63ed713-5f57-4972-b2f1-b47e836113be","type":"PanTool"},{"id":"01c941d4-a31f-403a-9ba9-fc35388abc24","type":"WheelZoomTool"},{"id":"59417e60-4616-4ceb-8cb8-0a2e8215b228","type":"BoxZoomTool"},{"id":"cd7c9a7f-62e7-407d-85e8-c80859cce5f0","type":"SaveTool"},{"id":"53d31065-2af5-417a-9aa5-b83dcc1ba3d3","type":"ResetTool"},{"id":"5de2c2ac-a957-4b3e-a38a-af1af0153811","type":"HelpTool"}]},"id":"d7d3a411-b28c-4f3f-9c99-144f1151b83f","type":"Toolbar"},{"attributes":{},"id":"cd7c9a7f-62e7-407d-85e8-c80859cce5f0","type":"SaveTool"},{"attributes":{"dimension":1,"plot":{"id":"68a2a747-e658-4f80-bc64-925e7307eafe","subtype":"Figure","type":"Plot"},"ticker":{"id":"0b715b2c-d93a-4787-a350-b9d270f0bd01","type":"BasicTicker"}},"id":"00ddf32c-f48f-4cc5-ae77-64c914eb7fc5","type":"Grid"},{"attributes":{},"id":"53d31065-2af5-417a-9aa5-b83dcc1ba3d3","type":"ResetTool"},{"attributes":{},"id":"0b715b2c-d93a-4787-a350-b9d270f0bd01","type":"BasicTicker"}],"root_ids":["68a2a747-e658-4f80-bc64-925e7307eafe"]},"title":"Bokeh Application","version":"0.13.0"}}
        </script>
        <script type="text/javascript">
          (function() {
            var fn = function() {
              Bokeh.safely(function() {
                (function(root) {
                  function embed_document(root) {
                    
                  var docs_json = document.getElementById('4ee5c914-fee4-4720-8c61-acaf6a1f5e0b').textContent;
                  var render_items = [{"docid":"57a9d4e3-e61b-42d1-9e38-2d3437421187","roots":{"68a2a747-e658-4f80-bc64-925e7307eafe":"ef113f06-aa36-4375-be30-7e11ddafa77a"}}];
                  root.Bokeh.embed.embed_items(docs_json, render_items);
                
                  }
                  if (root.Bokeh !== undefined) {
                    embed_document(root);
                  } else {
                    var attempts = 0;
                    var timer = setInterval(function(root) {
                      if (root.Bokeh !== undefined) {
                        embed_document(root);
                        clearInterval(timer);
                      }
                      attempts++;
                      if (attempts > 100) {
                        console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing")
                        clearInterval(timer);
                      }
                    }, 10, root)
                  }
                })(window);
              });
            };
            if (document.readyState != "loading") fn();
            else document.addEventListener("DOMContentLoaded", fn);
          })();
        </script>
    
  </body>
  
</html>

2 个答案:

答案 0 :(得分:0)

散景图实际上是JavaScript应用程序,可以渲染到HTML画布,并响应和处理UI事件。 PDF无法直接执行任何操作,因为它们无法运行JavaScript代码。我非常怀疑任何程序化HTML到PDF工具都可以对它做任何有用的事情。 AFAIK最好的选择是使用Bokeh's export API显式导出PNG:

from bokeh.io import export_png

export_png(plot, filename="plot.png")

还有一种在浏览器中生成SVG的模式,并且HTML-to-PDF工具可能能够转换这种输出:

plot.output_backend = "svg"

但是从Bokeh 0.13开始,Bokeh中的SVG输出存在一些限制和已知问题。您只需要尝试一下,即可看到它足以满足您的特定用例。

答案 1 :(得分:0)

我已经使用这个(非常粗略的)代码进行 HTML 到 PDF 的转换,即使在相当复杂的模板化布局中嵌入散景图,它通常也能工作。有两个引擎:

  1. 使用无头 Chromium 的本地安装(您可以将 Chromium 设为 Google Chrome 可执行文件的别名或为路径创建新的函数参数)。
  2. sejda 网络服务 API,不需要任何本地安装(pip install requests 除外),但需要一个免费的令牌帐户,并且文档大小的上限似乎为 25 MB。< /li>
import os
import subprocess
import tempfile as tmp

import requests

def html_to_pdf(document, compress=False, engine='chromium',
                url='https://api.sejda.com/v2/html-pdf', token=None,
                webservice_options=None):
    """
    Convert html to pdf using headless chromium or sejda webservice.

    Optionally compress with ghostscript (also needs to be installed) if using chromium. For webservice_options,
    see https://www.sejda.com/developers#html-pdf-api for docs.

    Parameters
    ----------
    document: string
        Full html document to render.

    Returns
    -------
    pdf: bytes
        The rendered pdf document as a bytestring.

    Notes
    -----
    Unfortunately requires disk (temporary files) when using chromium engine.

    """
    if webservice_options is None:
        webservice_options = {}
    if engine == 'sejda':
        payload = {'htmlCode': document, **webservice_options}
        response = requests.post(url, json=payload, headers={'Authorization': f'Token: {token}'})
        response.raise_for_status()
        static_bytes = response.content
    elif engine == 'chromium':
        with tmp.NamedTemporaryFile('w', suffix='.html', encoding='utf-8') as interactive_file:
            interactive_file.write(document)
            interactive_filename = interactive_file.name
            static_filename = interactive_filename.replace('.html', '.pdf')

            command = 'chromium --headless --disable-gpu --audio-output-channels=0 --print-to-pdf={pdf} {html}'.format(pdf=static_filename, html=interactive_filename)
            subprocess.check_call(command.split())

            if compress:
                # needs svg to run
                small_static_filename = static_filename.replace('.pdf', '_small.pdf')
                command = 'gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -dNOPAUSE -dBATCH -dQUIET -sOutputFile={pdfout} {pdfin}'.format(pdfin=static_filename, pdfout=small_static_filename)
                subprocess.check_call(command.split())

            with open(small_static_filename if compress else static_filename, 'rb') as static_file:
                static_bytes = static_file.read()

            os.remove(static_filename)
            if compress:
                os.remove(small_static_filename)
    else:
        raise ValueError(f'invalid engine: {engine}')

    return static_bytes