Images¶
An image in the context is an Image, created from the template so that it can
add its relationship to the right part:
from docx.shared import Mm
from docxtpl import Template
tpl = Template("letter.docx")
tpl.render({"logo": tpl.image("acme-logo.png", width=Mm(30))})
tpl.save("letter-acme.docx")
In the template, where the picture goes:
{{ logo }}
No prefix is needed. The image is placed where the tag was, in the middle of a sentence if that is where you typed it.
Sizing¶
Sizes are docx.shared lengths — Mm, Cm, Inches, Pt, Emu:
- neither given: the image's native size, at its own resolution;
- one given: the other scales to preserve the aspect ratio;
- both given: exactly that, aspect ratio or not.
tpl.image("chart.png", width=Mm(160)) # -- height follows --
tpl.image("stamp.png", width=Mm(20), height=Mm(20))
Where the image comes from¶
Four shapes, all accepted wherever this documentation says a path:
tpl.image("acme-logo.png") # -- a path --
tpl.image(pathlib.Path("acme-logo.png")) # -- or anything path-like --
tpl.image(response.content) # -- the bytes themselves --
tpl.image(buffer) # -- any stream holding them --
So an image that was generated rather than stored needs no temporary file:
import io
import matplotlib.pyplot as plt
buffer = io.BytesIO()
plt.savefig(buffer, format="png")
tpl.render({"chart": tpl.image(buffer, width=Mm(160))})
Note that buffer is passed as it is — there is no getvalue() and no
seek(0). A stream is read once, in full, when the Image is created, and its
position is put back exactly where it was found. That matters twice over: the
stream is yours and may be something you are reading too, and the same Image
rendered in every iteration of a loop would have nothing left to read on the
second pass.
A path is the exception, and is opened at render time rather than at construction — so a missing file is reported when the document is built, as it always has been.
The same image used in twenty places is stored in the package once: the parts are matched by content, not by how many objects were created.
When it is not an image¶
Data that is not in a format the document model can identify raises
ImageError, naming what was given:
ImageError: the 4096 bytes read from BytesIO is not in an image format this
package can read. PNG, JPEG, GIF, TIFF, BMP, SVG, WebP, WMF and EMF are
recognised; anything else has to be converted before it reaches the template.
It is a ValueError as well, so an except ValueError around a render keeps
working.
Images in headers and footers¶
A w:drawing refers to its image by relationship id, and a relationship belongs
to a part. An image rendered into a page header must be related from the
header part; relating it from the document part gives a header Word draws as a
red X, with nothing wrong-looking in the XML.
This is handled for you — the relationship is added to whichever part is being
rendered — but it is the reason Image needs the template and cannot be built
standalone.
Replacing an image the template already has¶
Some pictures cannot be reached by a tag at all: a logo placed in the header, a chart's backing workbook, an embedded spreadsheet. For those, the template keeps a placeholder and the caller swaps the bytes:
tpl.replace_picture("image1.png", "acme-logo.png")
tpl.replace_media("image2.png", banner_bytes)
tpl.replace_embedded("oleObject1.bin", workbook_stream)
tpl.replace_zip_member("word/media/image3.png", chart_bytes)
The replacement takes the same four shapes an Image does.
The name is the member's filename inside the package — open the .docx with any
zip tool to find it. Replacements are recorded and applied when you save(), so
they survive repeated renders and two saves produce the same bytes. Naming a
member that is not there raises KeyError listing what the package holds,
rather than silently shipping the placeholder.
reset_replacements() discards the pending ones.