'How to include images in xhtml2pdf generated pdf files?

I am running a streamlit app which generates reports containing images and dataframes. I have used jinja2 to generate the html file from a template. Then, I would now like to convert to a pdf file using xhtml2pdf to download.

How to do that?


from jinja2 import Environment, FileSystemLoader


def convert_html_to_pdf(source_html, output_filename="temp/report.pdf"):

    result_file = io.BytesIO()
    pdf = pisa.CreatePDF(
        source_html,                
        dest=result_file)
    return pdf.getvalue()


def load_template():
    env = Environment(loader=FileSystemLoader('templates'))
    template = env.get_template('catAnalysisTemplate.html')
    return template


def render_report(data, filename="report"):
    template = load_template()
    html = template.render(data)
    # with open(f'temp/{filename}.html', 'w') as f:
    #     f.write(html)
    pdf = convert_html_to_pdf(html)

    return [html, pdf]

This works fine except the images are not included in the pdf file. My static images are stored in

img/
   logo.png

and the charts I may generate it in memory as like

def plot_co_attainment(qp):
    img = io.BytesIO()
    data = qp.co_attainment()[["Level", "Perc_Attainment"]]
    plt.figure(dpi=150)
    plt.bar(data["Level"], data["Perc_Attainment"], width=0.5, color=colors)
    for i, val in enumerate(data["Perc_Attainment"].values):
        plt.text(i, val, str(val) + "%",
                 horizontalalignment='center',
                 verticalalignment='bottom',
                 fontdict={'fontweight': 500, 'size': 20})
    plt.xlabel("Course Outcomes")
    plt.ylabel("Percentage of Attainment")
    plt.ylim((0, 110))
    plt.savefig(buf, format='jpg')
    return buf

How do I connect the dots and get the images in my pdf file?



Solution 1:[1]

I am having the same issue. The way I solved it was to use a link_handler and return the data as a data: uri containing the png image data.

This example will take the src attribute and use it to generate a square image in that color, which will be embedded in the PDF. Sadly this doesn't let you modify the image tag itself so you can't change the sizes/classes or anything else.

Using something like this opens the way to embedding just about anything without having to add them to your template directly.

from base64 import b64encode
from io import BytesIO

from xhtml2pdf import pisa
from PIL import Image

html_src = """
<body>
    <div>
        <img src="red"/>
        <img src="green"/>
        <img src="blue"/>
    </div>
</body>
"""


def link_callback(src_attr, *args):
    """
    Returns the image data for use by the pdf renderer
    """
    img_out = BytesIO()
    img = Image.new("RGB", (100, 100), src_attr)
    img.save(img_out, "png")
    return f"data:image/png;base64,{b64encode(img_out.getvalue())}"


def main():
    with open("one.pdf", "wb") as f:
        pizza = pisa.CreatePDF(
            html_src,
            dest=f,
            link_callback=link_callback,
        )


if __name__ == "__main__":
    main()

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1