Skip to content

watermark

watermark

Watermark support — the faint "DRAFT" or "CONFIDENTIAL" behind a document's content.

Word implements a watermark as a VML shape inside a header, not as DrawingML. VML is deprecated in the specification, but this is what current versions of Word write and what they render correctly; a DrawingML equivalent does not display the same way, and a watermark that looks wrong is worse than none. So VML it is, and docx.oxml.ns carries the v:, o: and w10: namespaces for it.

A watermark is a header artefact, so which pages show one follows from which header applies to them. Both API entry points therefore write into all three header types — default, first-page and even-page — because a watermark that vanishes on page 1 of a document with a distinct first-page header reads as a bug rather than as a setting.

Scope:

  • Document.add_text_watermark applies the watermark to the whole document.
  • Section.add_text_watermark applies it to one section. Where that section's headers are inherited from an earlier one, the watermark goes into the header actually in force, which the earlier section shares — a header that is inherited is the same header, and there is no way to mark up one section's copy of it alone without first breaking the link.

Watermark

Watermark(shape: _Element)

A watermark in a header — the faint text or image behind the document content.

Not constructed directly; reached through Section.watermark or returned by Section.add_text_watermark and Section.add_image_watermark.

Source code in src/docx/watermark.py
def __init__(self, shape: _Element):
    self._shape = shape

is_image property

is_image: bool

True when this is an image watermark rather than a text one.

text property

text: str | None

The watermark text, or None for an image watermark.

remove

remove() -> None

Remove this watermark from the document.

The whole w:pict is removed, and the run holding it too when that leaves the run empty, so nothing is left behind that Word would render as a stray space.

Source code in src/docx/watermark.py
def remove(self) -> None:
    """Remove this watermark from the document.

    The whole `w:pict` is removed, and the run holding it too when that leaves the
    run empty, so nothing is left behind that Word would render as a stray space.
    """
    pict = self._shape.getparent()
    if pict is None:
        return
    r = pict.getparent()
    if r is None:
        return
    r.remove(pict)
    if r.tag == qn("w:r") and len(r.xpath("./*[not(self::w:rPr)]")) == 0:
        parent = r.getparent()
        if parent is not None:
            parent.remove(r)

iter_watermarks

iter_watermarks(
    hdrftr: _BaseHeaderFooter,
) -> Iterator[Watermark]

Generate a Watermark for each watermark shape in hdrftr.

Source code in src/docx/watermark.py
def iter_watermarks(hdrftr: _BaseHeaderFooter) -> Iterator[Watermark]:
    """Generate a |Watermark| for each watermark shape in `hdrftr`."""
    for shape in hdrftr.part.element.xpath(
        f'.//w:pict/v:shape[starts-with(@id, "{_TEXT_SHAPE_ID}")]'
        f' | .//w:pict/v:shape[starts-with(@id, "{_IMAGE_SHAPE_ID}")]'
    ):
        yield Watermark(shape)

iter_watermark_headers

iter_watermark_headers(
    sections: Iterable[Section],
) -> Iterator[_BaseHeaderFooter]

Generate the header objects a watermark should be written into for sections.

All three header types of each section, skipping any whose definition has already been generated. A header inherited from an earlier section is that earlier section's header, so writing to both would give it two watermarks.

Source code in src/docx/watermark.py
def iter_watermark_headers(sections: Iterable[Section]) -> Iterator[_BaseHeaderFooter]:
    """Generate the header objects a watermark should be written into for `sections`.

    All three header types of each section, skipping any whose definition has already
    been generated. A header inherited from an earlier section *is* that earlier
    section's header, so writing to both would give it two watermarks.
    """
    seen: List[int] = []
    for section in sections:
        for header in (section.header, section.first_page_header, section.even_page_header):
            part_id = id(header.part)
            if part_id in seen:
                continue
            seen.append(part_id)
            yield header

add_text_watermark

add_text_watermark(
    headers: Iterable[_BaseHeaderFooter],
    text: str,
    font: str = "Calibri",
    font_size: Length | int | None = None,
    color: str = "C0C0C0",
    opacity: float | None = None,
    angle: float = 315,
    width: Length | int = _DEFAULT_WIDTH,
    height: Length | int = _DEFAULT_HEIGHT,
    bold: bool = False,
    italic: bool = False,
) -> List[Watermark]

Add a text watermark to each of headers, returning the watermarks added.

Source code in src/docx/watermark.py
def add_text_watermark(
    headers: Iterable[_BaseHeaderFooter],
    text: str,
    font: str = "Calibri",
    font_size: Length | int | None = None,
    color: str = "C0C0C0",
    opacity: float | None = None,
    angle: float = 315,
    width: Length | int = _DEFAULT_WIDTH,
    height: Length | int = _DEFAULT_HEIGHT,
    bold: bool = False,
    italic: bool = False,
) -> List[Watermark]:
    """Add a text watermark to each of `headers`, returning the watermarks added."""
    return [
        Watermark(
            _add_shape(
                header,
                _TEXT_SHAPETYPE_XML,
                _text_shape_xml(
                    text, font, font_size, color, opacity, angle, width, height, bold, italic
                ),
            )
        )
        for header in headers
    ]

add_image_watermark

add_image_watermark(
    headers: Iterable[_BaseHeaderFooter],
    image_descriptor: str | PathLike[str] | IO[bytes],
    width: Length | int | None = None,
    height: Length | int | None = None,
    washout: bool = True,
    scale: float = 1.0,
) -> List[Watermark]

Add an image watermark to each of headers, returning the watermarks added.

The image is related to each header part separately, since a relationship belongs to the part that refers to it.

Source code in src/docx/watermark.py
def add_image_watermark(
    headers: Iterable[_BaseHeaderFooter],
    image_descriptor: str | os.PathLike[str] | IO[bytes],
    width: Length | int | None = None,
    height: Length | int | None = None,
    washout: bool = True,
    scale: float = 1.0,
) -> List[Watermark]:
    """Add an image watermark to each of `headers`, returning the watermarks added.

    The image is related to each header part separately, since a relationship belongs to
    the part that refers to it.
    """
    watermarks: List[Watermark] = []
    for header in headers:
        rId, image = header.part.get_or_add_image(image_descriptor)
        cx, cy = image.scaled_dimensions(width, height)
        watermarks.append(
            Watermark(
                _add_shape(
                    header,
                    _IMAGE_SHAPETYPE_XML,
                    _image_shape_xml(
                        rId, image.filename, Emu(int(cx * scale)), Emu(int(cy * scale)), washout
                    ),
                )
            )
        )
    return watermarks

remove_watermarks

remove_watermarks(
    headers: Iterable[_BaseHeaderFooter],
) -> int

Remove every watermark from each of headers, returning how many were removed.

Source code in src/docx/watermark.py
def remove_watermarks(headers: Iterable[_BaseHeaderFooter]) -> int:
    """Remove every watermark from each of `headers`, returning how many were removed."""
    removed = 0
    for header in headers:
        for watermark in list(iter_watermarks(header)):
            watermark.remove()
            removed += 1
    return removed